Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

Commit 48fc017

Browse files
authored
Add built-in service (Sig) which signs data and verifies signatures (#110)
1 parent 25da21a commit 48fc017

File tree

9 files changed

+441
-168
lines changed

9 files changed

+441
-168
lines changed

package-lock.json

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
"@chainsafe/libp2p-noise": "4.0.0",
2424
"@fluencelabs/avm": "^0.17.6",
2525
"async": "3.2.0",
26-
"base64-js": "1.5.1",
2726
"bs58": "4.0.1",
2827
"cids": "0.8.1",
2928
"it-length-prefixed": "3.0.1",
@@ -44,6 +43,7 @@
4443
"jest": "^26.6.3",
4544
"ts-jest": "^26.5.4",
4645
"typedoc": "^0.21.9",
47-
"typescript": "^4.0.0"
46+
"typescript": "^4.0.0",
47+
"js-base64": "^3.7.2"
4848
}
4949
}

src/__test__/unit/builtInHandler.spec.ts

Lines changed: 229 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,75 @@
1-
import each from 'jest-each';
21
import { CallServiceData } from '../../internal/commonTypes';
3-
import { defaultServices } from '../../internal/defaultServices';
4-
5-
describe('Tests for default handler', () => {
6-
// prettier-ignore
7-
each`
8-
serviceId | fnName | args | retCode | result
9-
${'op'} | ${'identity'} | ${[]} | ${0} | ${{}}
10-
${'op'} | ${'identity'} | ${[1]} | ${0} | ${1}
11-
${'op'} | ${'identity'} | ${[1, 2]} | ${1} | ${'identity accepts up to 1 arguments, received 2 arguments'}
12-
13-
${'op'} | ${'noop'} | ${[1, 2]} | ${0} | ${{}}
14-
15-
${'op'} | ${'array'} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
2+
import each from 'jest-each';
3+
import { BuiltInServiceContext, builtInServices } from '../../internal/builtInServices';
4+
import { KeyPair } from '../../internal/KeyPair';
5+
import { toUint8Array } from 'js-base64';
166

17-
${'op'} | ${'concat'} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
18-
${'op'} | ${'concat'} | ${[[1, 2]]} | ${0} | ${[1, 2]}
19-
${'op'} | ${'concat'} | ${[]} | ${0} | ${[]}
20-
${'op'} | ${'concat'} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"}
7+
const key = '+cmeYlZKj+MfSa9dpHV+BmLPm6wq4inGlsPlQ1GvtPk=';
218

22-
${'op'} | ${'string_to_b58'} | ${["test"]} | ${0} | ${"3yZe7d"}
23-
${'op'} | ${'string_to_b58'} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"}
9+
const context = (async () => {
10+
const keyBytes = toUint8Array(key);
11+
const kp = await KeyPair.fromEd25519SK(keyBytes);
12+
const res: BuiltInServiceContext = {
13+
peerKeyPair: kp,
14+
peerId: kp.Libp2pPeerId.toB58String(),
15+
};
16+
return res;
17+
})();
2418

25-
${'op'} | ${'string_from_b58'} | ${["3yZe7d"]} | ${0} | ${"test"}
26-
${'op'} | ${'string_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"}
19+
const testData = [1, 2, 3, 4, 5, 6, 7, 9, 10];
2720

28-
${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
29-
${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"}
21+
// signature produced by KeyPair created from key above (`key` variable)
22+
const testDataSig = [
23+
224, 104, 245, 206, 140, 248, 27, 72, 68, 133, 111, 10, 164, 197, 242, 132, 107, 77, 224, 67, 99, 106, 76, 29, 144,
24+
121, 122, 169, 36, 173, 58, 80, 170, 102, 137, 253, 157, 247, 168, 87, 162, 223, 188, 214, 203, 220, 52, 246, 29,
25+
86, 77, 71, 224, 248, 16, 213, 254, 75, 78, 239, 243, 222, 241, 15,
26+
];
3027

31-
${'op'} | ${'bytes_from_b58'} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
32-
${'op'} | ${'bytes_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"}
28+
// signature produced by KeyPair created from some random KeyPair
29+
const testDataWrongSig = [
30+
116, 247, 189, 118, 236, 53, 147, 123, 219, 75, 176, 105, 101, 108, 233, 137, 97, 14, 146, 132, 252, 70, 51, 153,
31+
237, 167, 156, 150, 36, 90, 229, 108, 166, 231, 255, 137, 8, 246, 125, 0, 213, 150, 83, 196, 237, 221, 131, 159,
32+
157, 159, 25, 109, 95, 160, 181, 65, 254, 238, 47, 156, 240, 151, 58, 14,
33+
];
3334

34-
${'peer'} | ${'timeout'} | ${[200, []]} | ${0} | ${[]}}
35-
${'peer'} | ${'timeout'} | ${[200, ['test']]} | ${0} | ${['test']}}
36-
${'peer'} | ${'timeout'} | ${[]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
37-
${'peer'} | ${'timeout'} | ${[200, 'test', 1]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
38-
35+
describe('Tests for default handler', () => {
36+
// prettier-ignore
37+
each`
38+
serviceId | fnName | args | retCode | result
39+
${'op'} | ${'identity'} | ${[]} | ${0} | ${{}}
40+
${'op'} | ${'identity'} | ${[1]} | ${0} | ${1}
41+
${'op'} | ${'identity'} | ${[1, 2]} | ${1} | ${'identity accepts up to 1 arguments, received 2 arguments'}
42+
43+
${'op'} | ${'noop'} | ${[1, 2]} | ${0} | ${{}}
44+
45+
${'op'} | ${'array'} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
46+
47+
${'op'} | ${'concat'} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
48+
${'op'} | ${'concat'} | ${[[1, 2]]} | ${0} | ${[1, 2]}
49+
${'op'} | ${'concat'} | ${[]} | ${0} | ${[]}
50+
${'op'} | ${'concat'} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"}
51+
52+
${'op'} | ${'string_to_b58'} | ${["test"]} | ${0} | ${"3yZe7d"}
53+
${'op'} | ${'string_to_b58'} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"}
54+
55+
${'op'} | ${'string_from_b58'} | ${["3yZe7d"]} | ${0} | ${"test"}
56+
${'op'} | ${'string_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"}
57+
58+
${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
59+
${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"}
60+
61+
${'op'} | ${'bytes_from_b58'} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
62+
${'op'} | ${'bytes_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"}
63+
64+
${'peer'} | ${'timeout'} | ${[200, []]} | ${0} | ${[]}}
65+
${'peer'} | ${'timeout'} | ${[200, ['test']]} | ${0} | ${['test']}}
66+
${'peer'} | ${'timeout'} | ${[]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
67+
${'peer'} | ${'timeout'} | ${[200, 'test', 1]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
68+
69+
${'sig'} | ${'verify'} | ${[testData, testDataSig]} | ${0} | ${true}}
70+
${'sig'} | ${'verify'} | ${[testData, testDataWrongSig]} | ${0} | ${false}}
71+
${'sig'} | ${'sign'} | ${[]} | ${1} | ${'sign accepts exactly one argument: data be signed in format of u8 array of bytes'}}
72+
${'sig'} | ${'verify'} | ${[testData]} | ${1} | ${'verify accepts exactly two arguments: data and signature, both in format of u8 array of bytes'}}
3973
`.test(
4074
//
4175
'$fnName with $args expected retcode: $retCode and result: $result',
@@ -56,7 +90,7 @@ describe('Tests for default handler', () => {
5690
};
5791

5892
// act
59-
const fn = defaultServices[req.serviceId][req.fnName];
93+
const fn = builtInServices(await context)[req.serviceId][req.fnName];
6094
const res = await fn(req);
6195

6296
// assert
@@ -84,7 +118,7 @@ describe('Tests for default handler', () => {
84118
};
85119

86120
// act
87-
const fn = defaultServices[req.serviceId][req.fnName];
121+
const fn = builtInServices(await context)[req.serviceId][req.fnName];
88122
const res = await fn(req);
89123

90124
// assert
@@ -93,4 +127,166 @@ describe('Tests for default handler', () => {
93127
result: 'The JS implementation of Peer does not support identify',
94128
});
95129
});
130+
131+
it('sig.sign should create the correct signature', async () => {
132+
// arrange
133+
const ctx = await context;
134+
const req: CallServiceData = {
135+
serviceId: 'sig',
136+
fnName: 'sign',
137+
args: [testData],
138+
tetraplets: [
139+
[
140+
{
141+
function_name: 'get_trust_bytes',
142+
json_path: '',
143+
peer_pk: '',
144+
service_id: 'trust-graph',
145+
},
146+
],
147+
],
148+
particleContext: {
149+
particleId: 'some',
150+
initPeerId: ctx.peerId,
151+
timestamp: 595951200,
152+
ttl: 595961200,
153+
signature: 'sig',
154+
},
155+
};
156+
157+
// act
158+
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
159+
const res = await fn(req);
160+
161+
// assert
162+
expect(res).toMatchObject({
163+
retCode: 0,
164+
result: testDataSig,
165+
});
166+
});
167+
168+
it('sign-verify call chain should work', async () => {
169+
const ctx = await context;
170+
const signReq: CallServiceData = {
171+
serviceId: 'sig',
172+
fnName: 'sign',
173+
args: [testData],
174+
tetraplets: [
175+
[
176+
{
177+
function_name: 'get_trust_bytes',
178+
json_path: '',
179+
peer_pk: '',
180+
service_id: 'trust-graph',
181+
},
182+
],
183+
],
184+
particleContext: {
185+
particleId: 'some',
186+
initPeerId: ctx.peerId,
187+
timestamp: 595951200,
188+
ttl: 595961200,
189+
signature: 'sig',
190+
},
191+
};
192+
193+
const signFn = builtInServices(ctx)[signReq.serviceId][signReq.fnName];
194+
const signRes = await signFn(signReq);
195+
196+
const verifyReq: CallServiceData = {
197+
serviceId: 'sig',
198+
fnName: 'verify',
199+
args: [testData, signRes.result],
200+
tetraplets: [],
201+
particleContext: {
202+
particleId: 'some',
203+
initPeerId: ctx.peerId,
204+
timestamp: 595951200,
205+
ttl: 595961200,
206+
signature: 'sig',
207+
},
208+
};
209+
210+
const verifyFn = builtInServices(ctx)[verifyReq.serviceId][verifyReq.fnName];
211+
const verifyRes = await verifyFn(verifyReq);
212+
213+
expect(verifyRes).toMatchObject({
214+
retCode: 0,
215+
result: true,
216+
});
217+
});
218+
219+
it('sig.sign should not allow data from incorrect services', async () => {
220+
// arrange
221+
const ctx = await context;
222+
const req: CallServiceData = {
223+
serviceId: 'sig',
224+
fnName: 'sign',
225+
args: [testData],
226+
tetraplets: [
227+
[
228+
{
229+
function_name: 'some-other-fn',
230+
json_path: '',
231+
peer_pk: '',
232+
service_id: 'cool-service',
233+
},
234+
],
235+
],
236+
particleContext: {
237+
particleId: 'some',
238+
initPeerId: ctx.peerId,
239+
timestamp: 595951200,
240+
ttl: 595961200,
241+
signature: 'sig',
242+
},
243+
};
244+
245+
// act
246+
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
247+
const res = await fn(req);
248+
249+
// assert
250+
expect(res).toMatchObject({
251+
retCode: 1,
252+
result: expect.stringContaining("Only data from the following services is allowed to be signed:"),
253+
});
254+
});
255+
256+
it('sig.sign should not allow particles initiated from other peers', async () => {
257+
// arrange
258+
const ctx = await context;
259+
const req: CallServiceData = {
260+
serviceId: 'sig',
261+
fnName: 'sign',
262+
args: [testData],
263+
tetraplets: [
264+
[
265+
{
266+
function_name: 'some-other-fn',
267+
json_path: '',
268+
peer_pk: '',
269+
service_id: 'cool-service',
270+
},
271+
],
272+
],
273+
particleContext: {
274+
particleId: 'some',
275+
initPeerId: (await KeyPair.randomEd25519()).Libp2pPeerId.toB58String(),
276+
timestamp: 595951200,
277+
ttl: 595961200,
278+
signature: 'sig',
279+
},
280+
};
281+
282+
// act
283+
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
284+
const res = await fn(req);
285+
286+
// assert
287+
expect(res).toMatchObject({
288+
retCode: 1,
289+
result: 'sign is only allowed to be called on the same peer the particle was initiated from',
290+
});
291+
});
96292
});

0 commit comments

Comments
 (0)