mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2024-12-05 02:10:18 +00:00
Add built-in service (Sig) which signs data and verifies signatures (#110)
This commit is contained in:
parent
25da21aeeb
commit
48fc017a1b
14
package-lock.json
generated
14
package-lock.json
generated
@ -12,7 +12,6 @@
|
|||||||
"@chainsafe/libp2p-noise": "4.0.0",
|
"@chainsafe/libp2p-noise": "4.0.0",
|
||||||
"@fluencelabs/avm": "^0.17.6",
|
"@fluencelabs/avm": "^0.17.6",
|
||||||
"async": "3.2.0",
|
"async": "3.2.0",
|
||||||
"base64-js": "1.5.1",
|
|
||||||
"bs58": "4.0.1",
|
"bs58": "4.0.1",
|
||||||
"cids": "0.8.1",
|
"cids": "0.8.1",
|
||||||
"it-length-prefixed": "3.0.1",
|
"it-length-prefixed": "3.0.1",
|
||||||
@ -31,6 +30,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.22",
|
"@types/jest": "^26.0.22",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
|
"js-base64": "^3.7.2",
|
||||||
"ts-jest": "^26.5.4",
|
"ts-jest": "^26.5.4",
|
||||||
"typedoc": "^0.21.9",
|
"typedoc": "^0.21.9",
|
||||||
"typescript": "^4.0.0"
|
"typescript": "^4.0.0"
|
||||||
@ -4668,6 +4668,12 @@
|
|||||||
"node": ">= 10.14.2"
|
"node": ">= 10.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js-base64": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -11936,6 +11942,12 @@
|
|||||||
"supports-color": "^7.0.0"
|
"supports-color": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"js-base64": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
"@chainsafe/libp2p-noise": "4.0.0",
|
"@chainsafe/libp2p-noise": "4.0.0",
|
||||||
"@fluencelabs/avm": "^0.17.6",
|
"@fluencelabs/avm": "^0.17.6",
|
||||||
"async": "3.2.0",
|
"async": "3.2.0",
|
||||||
"base64-js": "1.5.1",
|
|
||||||
"bs58": "4.0.1",
|
"bs58": "4.0.1",
|
||||||
"cids": "0.8.1",
|
"cids": "0.8.1",
|
||||||
"it-length-prefixed": "3.0.1",
|
"it-length-prefixed": "3.0.1",
|
||||||
@ -44,6 +43,7 @@
|
|||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"ts-jest": "^26.5.4",
|
"ts-jest": "^26.5.4",
|
||||||
"typedoc": "^0.21.9",
|
"typedoc": "^0.21.9",
|
||||||
"typescript": "^4.0.0"
|
"typescript": "^4.0.0",
|
||||||
|
"js-base64": "^3.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,75 @@
|
|||||||
import each from 'jest-each';
|
|
||||||
import { CallServiceData } from '../../internal/commonTypes';
|
import { CallServiceData } from '../../internal/commonTypes';
|
||||||
import { defaultServices } from '../../internal/defaultServices';
|
import each from 'jest-each';
|
||||||
|
import { BuiltInServiceContext, builtInServices } from '../../internal/builtInServices';
|
||||||
|
import { KeyPair } from '../../internal/KeyPair';
|
||||||
|
import { toUint8Array } from 'js-base64';
|
||||||
|
|
||||||
|
const key = '+cmeYlZKj+MfSa9dpHV+BmLPm6wq4inGlsPlQ1GvtPk=';
|
||||||
|
|
||||||
|
const context = (async () => {
|
||||||
|
const keyBytes = toUint8Array(key);
|
||||||
|
const kp = await KeyPair.fromEd25519SK(keyBytes);
|
||||||
|
const res: BuiltInServiceContext = {
|
||||||
|
peerKeyPair: kp,
|
||||||
|
peerId: kp.Libp2pPeerId.toB58String(),
|
||||||
|
};
|
||||||
|
return res;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const testData = [1, 2, 3, 4, 5, 6, 7, 9, 10];
|
||||||
|
|
||||||
|
// signature produced by KeyPair created from key above (`key` variable)
|
||||||
|
const testDataSig = [
|
||||||
|
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,
|
||||||
|
121, 122, 169, 36, 173, 58, 80, 170, 102, 137, 253, 157, 247, 168, 87, 162, 223, 188, 214, 203, 220, 52, 246, 29,
|
||||||
|
86, 77, 71, 224, 248, 16, 213, 254, 75, 78, 239, 243, 222, 241, 15,
|
||||||
|
];
|
||||||
|
|
||||||
|
// signature produced by KeyPair created from some random KeyPair
|
||||||
|
const testDataWrongSig = [
|
||||||
|
116, 247, 189, 118, 236, 53, 147, 123, 219, 75, 176, 105, 101, 108, 233, 137, 97, 14, 146, 132, 252, 70, 51, 153,
|
||||||
|
237, 167, 156, 150, 36, 90, 229, 108, 166, 231, 255, 137, 8, 246, 125, 0, 213, 150, 83, 196, 237, 221, 131, 159,
|
||||||
|
157, 159, 25, 109, 95, 160, 181, 65, 254, 238, 47, 156, 240, 151, 58, 14,
|
||||||
|
];
|
||||||
|
|
||||||
describe('Tests for default handler', () => {
|
describe('Tests for default handler', () => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
each`
|
each`
|
||||||
serviceId | fnName | args | retCode | result
|
serviceId | fnName | args | retCode | result
|
||||||
${'op'} | ${'identity'} | ${[]} | ${0} | ${{}}
|
${'op'} | ${'identity'} | ${[]} | ${0} | ${{}}
|
||||||
${'op'} | ${'identity'} | ${[1]} | ${0} | ${1}
|
${'op'} | ${'identity'} | ${[1]} | ${0} | ${1}
|
||||||
${'op'} | ${'identity'} | ${[1, 2]} | ${1} | ${'identity accepts up to 1 arguments, received 2 arguments'}
|
${'op'} | ${'identity'} | ${[1, 2]} | ${1} | ${'identity accepts up to 1 arguments, received 2 arguments'}
|
||||||
|
|
||||||
${'op'} | ${'noop'} | ${[1, 2]} | ${0} | ${{}}
|
${'op'} | ${'noop'} | ${[1, 2]} | ${0} | ${{}}
|
||||||
|
|
||||||
${'op'} | ${'array'} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
|
${'op'} | ${'array'} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]}
|
||||||
|
|
||||||
${'op'} | ${'concat'} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
|
${'op'} | ${'concat'} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]}
|
||||||
${'op'} | ${'concat'} | ${[[1, 2]]} | ${0} | ${[1, 2]}
|
${'op'} | ${'concat'} | ${[[1, 2]]} | ${0} | ${[1, 2]}
|
||||||
${'op'} | ${'concat'} | ${[]} | ${0} | ${[]}
|
${'op'} | ${'concat'} | ${[]} | ${0} | ${[]}
|
||||||
${'op'} | ${'concat'} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"}
|
${'op'} | ${'concat'} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"}
|
||||||
|
|
||||||
${'op'} | ${'string_to_b58'} | ${["test"]} | ${0} | ${"3yZe7d"}
|
${'op'} | ${'string_to_b58'} | ${["test"]} | ${0} | ${"3yZe7d"}
|
||||||
${'op'} | ${'string_to_b58'} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"}
|
${'op'} | ${'string_to_b58'} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"}
|
||||||
|
|
||||||
${'op'} | ${'string_from_b58'} | ${["3yZe7d"]} | ${0} | ${"test"}
|
${'op'} | ${'string_from_b58'} | ${["3yZe7d"]} | ${0} | ${"test"}
|
||||||
${'op'} | ${'string_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"}
|
${'op'} | ${'string_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"}
|
||||||
|
|
||||||
${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
|
${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"}
|
||||||
${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"}
|
${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"}
|
||||||
|
|
||||||
${'op'} | ${'bytes_from_b58'} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
|
${'op'} | ${'bytes_from_b58'} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]}
|
||||||
${'op'} | ${'bytes_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"}
|
${'op'} | ${'bytes_from_b58'} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"}
|
||||||
|
|
||||||
${'peer'} | ${'timeout'} | ${[200, []]} | ${0} | ${[]}}
|
${'peer'} | ${'timeout'} | ${[200, []]} | ${0} | ${[]}}
|
||||||
${'peer'} | ${'timeout'} | ${[200, ['test']]} | ${0} | ${['test']}}
|
${'peer'} | ${'timeout'} | ${[200, ['test']]} | ${0} | ${['test']}}
|
||||||
${'peer'} | ${'timeout'} | ${[]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
|
${'peer'} | ${'timeout'} | ${[]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
|
||||||
${'peer'} | ${'timeout'} | ${[200, 'test', 1]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
|
${'peer'} | ${'timeout'} | ${[200, 'test', 1]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'}}
|
||||||
|
|
||||||
|
${'sig'} | ${'verify'} | ${[testData, testDataSig]} | ${0} | ${true}}
|
||||||
|
${'sig'} | ${'verify'} | ${[testData, testDataWrongSig]} | ${0} | ${false}}
|
||||||
|
${'sig'} | ${'sign'} | ${[]} | ${1} | ${'sign accepts exactly one argument: data be signed in format of u8 array of bytes'}}
|
||||||
|
${'sig'} | ${'verify'} | ${[testData]} | ${1} | ${'verify accepts exactly two arguments: data and signature, both in format of u8 array of bytes'}}
|
||||||
`.test(
|
`.test(
|
||||||
//
|
//
|
||||||
'$fnName with $args expected retcode: $retCode and result: $result',
|
'$fnName with $args expected retcode: $retCode and result: $result',
|
||||||
@ -56,7 +90,7 @@ describe('Tests for default handler', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const fn = defaultServices[req.serviceId][req.fnName];
|
const fn = builtInServices(await context)[req.serviceId][req.fnName];
|
||||||
const res = await fn(req);
|
const res = await fn(req);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
@ -84,7 +118,7 @@ describe('Tests for default handler', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// act
|
// act
|
||||||
const fn = defaultServices[req.serviceId][req.fnName];
|
const fn = builtInServices(await context)[req.serviceId][req.fnName];
|
||||||
const res = await fn(req);
|
const res = await fn(req);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
@ -93,4 +127,166 @@ describe('Tests for default handler', () => {
|
|||||||
result: 'The JS implementation of Peer does not support identify',
|
result: 'The JS implementation of Peer does not support identify',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sig.sign should create the correct signature', async () => {
|
||||||
|
// arrange
|
||||||
|
const ctx = await context;
|
||||||
|
const req: CallServiceData = {
|
||||||
|
serviceId: 'sig',
|
||||||
|
fnName: 'sign',
|
||||||
|
args: [testData],
|
||||||
|
tetraplets: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
function_name: 'get_trust_bytes',
|
||||||
|
json_path: '',
|
||||||
|
peer_pk: '',
|
||||||
|
service_id: 'trust-graph',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
particleContext: {
|
||||||
|
particleId: 'some',
|
||||||
|
initPeerId: ctx.peerId,
|
||||||
|
timestamp: 595951200,
|
||||||
|
ttl: 595961200,
|
||||||
|
signature: 'sig',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
|
||||||
|
const res = await fn(req);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(res).toMatchObject({
|
||||||
|
retCode: 0,
|
||||||
|
result: testDataSig,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sign-verify call chain should work', async () => {
|
||||||
|
const ctx = await context;
|
||||||
|
const signReq: CallServiceData = {
|
||||||
|
serviceId: 'sig',
|
||||||
|
fnName: 'sign',
|
||||||
|
args: [testData],
|
||||||
|
tetraplets: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
function_name: 'get_trust_bytes',
|
||||||
|
json_path: '',
|
||||||
|
peer_pk: '',
|
||||||
|
service_id: 'trust-graph',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
particleContext: {
|
||||||
|
particleId: 'some',
|
||||||
|
initPeerId: ctx.peerId,
|
||||||
|
timestamp: 595951200,
|
||||||
|
ttl: 595961200,
|
||||||
|
signature: 'sig',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const signFn = builtInServices(ctx)[signReq.serviceId][signReq.fnName];
|
||||||
|
const signRes = await signFn(signReq);
|
||||||
|
|
||||||
|
const verifyReq: CallServiceData = {
|
||||||
|
serviceId: 'sig',
|
||||||
|
fnName: 'verify',
|
||||||
|
args: [testData, signRes.result],
|
||||||
|
tetraplets: [],
|
||||||
|
particleContext: {
|
||||||
|
particleId: 'some',
|
||||||
|
initPeerId: ctx.peerId,
|
||||||
|
timestamp: 595951200,
|
||||||
|
ttl: 595961200,
|
||||||
|
signature: 'sig',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyFn = builtInServices(ctx)[verifyReq.serviceId][verifyReq.fnName];
|
||||||
|
const verifyRes = await verifyFn(verifyReq);
|
||||||
|
|
||||||
|
expect(verifyRes).toMatchObject({
|
||||||
|
retCode: 0,
|
||||||
|
result: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sig.sign should not allow data from incorrect services', async () => {
|
||||||
|
// arrange
|
||||||
|
const ctx = await context;
|
||||||
|
const req: CallServiceData = {
|
||||||
|
serviceId: 'sig',
|
||||||
|
fnName: 'sign',
|
||||||
|
args: [testData],
|
||||||
|
tetraplets: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
function_name: 'some-other-fn',
|
||||||
|
json_path: '',
|
||||||
|
peer_pk: '',
|
||||||
|
service_id: 'cool-service',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
particleContext: {
|
||||||
|
particleId: 'some',
|
||||||
|
initPeerId: ctx.peerId,
|
||||||
|
timestamp: 595951200,
|
||||||
|
ttl: 595961200,
|
||||||
|
signature: 'sig',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
|
||||||
|
const res = await fn(req);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(res).toMatchObject({
|
||||||
|
retCode: 1,
|
||||||
|
result: expect.stringContaining("Only data from the following services is allowed to be signed:"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sig.sign should not allow particles initiated from other peers', async () => {
|
||||||
|
// arrange
|
||||||
|
const ctx = await context;
|
||||||
|
const req: CallServiceData = {
|
||||||
|
serviceId: 'sig',
|
||||||
|
fnName: 'sign',
|
||||||
|
args: [testData],
|
||||||
|
tetraplets: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
function_name: 'some-other-fn',
|
||||||
|
json_path: '',
|
||||||
|
peer_pk: '',
|
||||||
|
service_id: 'cool-service',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
particleContext: {
|
||||||
|
particleId: 'some',
|
||||||
|
initPeerId: (await KeyPair.randomEd25519()).Libp2pPeerId.toB58String(),
|
||||||
|
timestamp: 595951200,
|
||||||
|
ttl: 595961200,
|
||||||
|
signature: 'sig',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
const fn = builtInServices(ctx)[req.serviceId][req.fnName];
|
||||||
|
const res = await fn(req);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(res).toMatchObject({
|
||||||
|
retCode: 1,
|
||||||
|
result: 'sign is only allowed to be called on the same peer the particle was initiated from',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -33,7 +33,7 @@ import { createInterpreter, dataToString } from './utils';
|
|||||||
import { filter, pipe, Subject, tap } from 'rxjs';
|
import { filter, pipe, Subject, tap } from 'rxjs';
|
||||||
import { RequestFlow } from './compilerSupport/v1';
|
import { RequestFlow } from './compilerSupport/v1';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import { defaultServices } from './defaultServices';
|
import { BuiltInServiceContext, builtInServices } from './builtInServices';
|
||||||
import { instanceOf } from 'ts-pattern';
|
import { instanceOf } from 'ts-pattern';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -210,7 +210,10 @@ export class FluencePeer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._legacyCallServiceHandler = new LegacyCallServiceHandler();
|
this._legacyCallServiceHandler = new LegacyCallServiceHandler();
|
||||||
registerDefaultServices(this);
|
registerDefaultServices(this, {
|
||||||
|
peerKeyPair: this._keyPair,
|
||||||
|
peerId: this.getStatus().peerId,
|
||||||
|
});
|
||||||
|
|
||||||
this._startParticleProcessing();
|
this._startParticleProcessing();
|
||||||
}
|
}
|
||||||
@ -459,7 +462,7 @@ export class FluencePeer {
|
|||||||
this._execSingleCallRequest(req)
|
this._execSingleCallRequest(req)
|
||||||
.catch(
|
.catch(
|
||||||
(err): CallServiceResult => ({
|
(err): CallServiceResult => ({
|
||||||
retCode: ResultCodes.exceptionInHandler,
|
retCode: ResultCodes.error,
|
||||||
result: `Handler failed. fnName="${req.fnName}" serviceId="${
|
result: `Handler failed. fnName="${req.fnName}" serviceId="${
|
||||||
req.serviceId
|
req.serviceId
|
||||||
}" error: ${err.toString()}`,
|
}" error: ${err.toString()}`,
|
||||||
@ -529,7 +532,7 @@ export class FluencePeer {
|
|||||||
res = handler
|
res = handler
|
||||||
? await handler(req)
|
? await handler(req)
|
||||||
: {
|
: {
|
||||||
retCode: ResultCodes.unknownError,
|
retCode: ResultCodes.error,
|
||||||
result: `No handler has been registered for serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
result: `No handler has been registered for serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -576,10 +579,11 @@ function serviceFnKey(serviceId: string, fnName: string) {
|
|||||||
return `${serviceId}/${fnName}`;
|
return `${serviceId}/${fnName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerDefaultServices(peer: FluencePeer) {
|
function registerDefaultServices(peer: FluencePeer, context: BuiltInServiceContext) {
|
||||||
for (let serviceId in defaultServices) {
|
const ctx = builtInServices(context);
|
||||||
for (let fnName in defaultServices[serviceId]) {
|
for (let serviceId in ctx) {
|
||||||
const h = defaultServices[serviceId][fnName];
|
for (let fnName in ctx[serviceId]) {
|
||||||
|
const h = ctx[serviceId][fnName];
|
||||||
peer.internals.regHandler.common(serviceId, fnName, h);
|
peer.internals.regHandler.common(serviceId, fnName, h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,4 +54,12 @@ export class KeyPair {
|
|||||||
toEd25519PrivateKey(): Uint8Array {
|
toEd25519PrivateKey(): Uint8Array {
|
||||||
return this.Libp2pPeerId.privKey.marshal().subarray(0, 32);
|
return this.Libp2pPeerId.privKey.marshal().subarray(0, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signBytes(data: Uint8Array): Promise<Uint8Array> {
|
||||||
|
return this.Libp2pPeerId.privKey.sign(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(data: Uint8Array, signature: Uint8Array): Promise<boolean> {
|
||||||
|
return this.Libp2pPeerId.privKey.public.verify(data, signature);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
175
src/internal/builtInServices.ts
Normal file
175
src/internal/builtInServices.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Fluence Labs Limited
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CallServiceResult } from '@fluencelabs/avm';
|
||||||
|
import { encode, decode } from 'bs58';
|
||||||
|
import { PeerIdB58 } from 'src';
|
||||||
|
import { GenericCallServiceHandler, ResultCodes } from './commonTypes';
|
||||||
|
import { KeyPair } from './KeyPair';
|
||||||
|
|
||||||
|
const success = (result: any): CallServiceResult => {
|
||||||
|
return {
|
||||||
|
result: result,
|
||||||
|
retCode: ResultCodes.success,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const error = (error: string): CallServiceResult => {
|
||||||
|
return {
|
||||||
|
result: error,
|
||||||
|
retCode: ResultCodes.error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface BuiltInServiceContext {
|
||||||
|
peerKeyPair: KeyPair;
|
||||||
|
peerId: PeerIdB58;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function builtInServices(context: BuiltInServiceContext): {
|
||||||
|
[serviceId in string]: { [fnName in string]: GenericCallServiceHandler };
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
op: {
|
||||||
|
noop: (req) => {
|
||||||
|
return success({});
|
||||||
|
},
|
||||||
|
|
||||||
|
array: (req) => {
|
||||||
|
return success(req.args);
|
||||||
|
},
|
||||||
|
|
||||||
|
identity: (req) => {
|
||||||
|
if (req.args.length > 1) {
|
||||||
|
return error(`identity accepts up to 1 arguments, received ${req.args.length} arguments`);
|
||||||
|
} else {
|
||||||
|
return success(req.args.length === 0 ? {} : req.args[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
concat: (req) => {
|
||||||
|
const incorrectArgIndices = req.args //
|
||||||
|
.map((x, i) => [Array.isArray(x), i])
|
||||||
|
.filter(([isArray, _]) => !isArray)
|
||||||
|
.map(([_, index]) => index);
|
||||||
|
|
||||||
|
if (incorrectArgIndices.length > 0) {
|
||||||
|
const str = incorrectArgIndices.join(', ');
|
||||||
|
return error(`All arguments of 'concat' must be arrays: arguments ${str} are not`);
|
||||||
|
} else {
|
||||||
|
return success([].concat.apply([], req.args));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
string_to_b58: (req) => {
|
||||||
|
if (req.args.length !== 1) {
|
||||||
|
return error('string_to_b58 accepts only one string argument');
|
||||||
|
} else {
|
||||||
|
return success(encode(new TextEncoder().encode(req.args[0])));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
string_from_b58: (req) => {
|
||||||
|
if (req.args.length !== 1) {
|
||||||
|
return error('string_from_b58 accepts only one string argument');
|
||||||
|
} else {
|
||||||
|
return success(new TextDecoder().decode(decode(req.args[0])));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
bytes_to_b58: (req) => {
|
||||||
|
if (req.args.length !== 1 || !Array.isArray(req.args[0])) {
|
||||||
|
return error('bytes_to_b58 accepts only single argument: array of numbers');
|
||||||
|
} else {
|
||||||
|
const argumentArray = req.args[0] as number[];
|
||||||
|
return success(encode(new Uint8Array(argumentArray)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
bytes_from_b58: (req) => {
|
||||||
|
if (req.args.length !== 1) {
|
||||||
|
return error('bytes_from_b58 accepts only one string argument');
|
||||||
|
} else {
|
||||||
|
return success(Array.from(decode(req.args[0])));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
peer: {
|
||||||
|
timeout: (req) => {
|
||||||
|
if (req.args.length !== 2) {
|
||||||
|
return error('timeout accepts exactly two arguments: timeout duration in ms and a message string');
|
||||||
|
}
|
||||||
|
const durationMs = req.args[0];
|
||||||
|
const message = req.args[1];
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const res = success(message);
|
||||||
|
resolve(res);
|
||||||
|
}, durationMs);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
identify: (req) => {
|
||||||
|
return error('The JS implementation of Peer does not support identify');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
sig: {
|
||||||
|
sign: async (req) => {
|
||||||
|
if (req.args.length !== 1) {
|
||||||
|
return error('sign accepts exactly one argument: data be signed in format of u8 array of bytes');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.particleContext.initPeerId !== context.peerId) {
|
||||||
|
return error('sign is only allowed to be called on the same peer the particle was initiated from');
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = req.tetraplets[0][0];
|
||||||
|
const serviceFnPair = `${t.service_id}.${t.function_name}`;
|
||||||
|
|
||||||
|
const allowedServices = [
|
||||||
|
'trust-graph.get_trust_bytes',
|
||||||
|
'trust-graph.get_revocation_bytes',
|
||||||
|
'registry.get_key_bytes',
|
||||||
|
'registry.get_record_bytes',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (allowedServices.indexOf(serviceFnPair) === -1) {
|
||||||
|
return error(
|
||||||
|
'Only data from the following services is allowed to be signed: ' + allowedServices.join(', '),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [data] = req.args;
|
||||||
|
const signedData = await context.peerKeyPair.signBytes(Uint8Array.from(data));
|
||||||
|
return success(Array.from(signedData));
|
||||||
|
},
|
||||||
|
|
||||||
|
verify: async (req) => {
|
||||||
|
if (req.args.length !== 2) {
|
||||||
|
return error(
|
||||||
|
'verify accepts exactly two arguments: data and signature, both in format of u8 array of bytes',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const [data, signature] = req.args;
|
||||||
|
const result = await context.peerKeyPair.verify(Uint8Array.from(data), Uint8Array.from(signature));
|
||||||
|
return success(result);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -59,8 +59,7 @@ export interface CallParams<ArgName extends string | null> {
|
|||||||
|
|
||||||
export enum ResultCodes {
|
export enum ResultCodes {
|
||||||
success = 0,
|
success = 0,
|
||||||
unknownError = 1,
|
error = 1,
|
||||||
exceptionInHandler = 2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +37,7 @@ export const callLegacyCallServiceHandler = (
|
|||||||
|
|
||||||
if (res.retCode === undefined) {
|
if (res.retCode === undefined) {
|
||||||
res = {
|
res = {
|
||||||
retCode: ResultCodes.unknownError,
|
retCode: ResultCodes.error,
|
||||||
result: `The handler did not set any result. Make sure you are calling the right peer and the handler has been registered. Original request data was: serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
result: `The handler did not set any result. Make sure you are calling the right peer and the handler has been registered. Original request data was: serviceId='${req.serviceId}' fnName='${req.fnName}' args='${req.args}'`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2021 Fluence Labs Limited
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { CallServiceResult } from '@fluencelabs/avm';
|
|
||||||
import { encode, decode } from 'bs58';
|
|
||||||
import { GenericCallServiceHandler, ResultCodes } from './commonTypes';
|
|
||||||
|
|
||||||
const success = (result: any): CallServiceResult => {
|
|
||||||
return {
|
|
||||||
result: result,
|
|
||||||
retCode: ResultCodes.success,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const error = (error: string): CallServiceResult => {
|
|
||||||
return {
|
|
||||||
result: error,
|
|
||||||
retCode: ResultCodes.unknownError,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultServices: { [serviceId in string]: { [fnName in string]: GenericCallServiceHandler } } = {
|
|
||||||
op: {
|
|
||||||
noop: (req) => {
|
|
||||||
return success({});
|
|
||||||
},
|
|
||||||
|
|
||||||
array: (req) => {
|
|
||||||
return success(req.args);
|
|
||||||
},
|
|
||||||
|
|
||||||
identity: (req) => {
|
|
||||||
if (req.args.length > 1) {
|
|
||||||
return error(`identity accepts up to 1 arguments, received ${req.args.length} arguments`);
|
|
||||||
} else {
|
|
||||||
return success(req.args.length === 0 ? {} : req.args[0]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
concat: (req) => {
|
|
||||||
const incorrectArgIndices = req.args //
|
|
||||||
.map((x, i) => [Array.isArray(x), i])
|
|
||||||
.filter(([isArray, _]) => !isArray)
|
|
||||||
.map(([_, index]) => index);
|
|
||||||
|
|
||||||
if (incorrectArgIndices.length > 0) {
|
|
||||||
const str = incorrectArgIndices.join(', ');
|
|
||||||
return error(`All arguments of 'concat' must be arrays: arguments ${str} are not`);
|
|
||||||
} else {
|
|
||||||
return success([].concat.apply([], req.args));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
string_to_b58: (req) => {
|
|
||||||
if (req.args.length !== 1) {
|
|
||||||
return error('string_to_b58 accepts only one string argument');
|
|
||||||
} else {
|
|
||||||
return success(encode(new TextEncoder().encode(req.args[0])));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
string_from_b58: (req) => {
|
|
||||||
if (req.args.length !== 1) {
|
|
||||||
return error('string_from_b58 accepts only one string argument');
|
|
||||||
} else {
|
|
||||||
return success(new TextDecoder().decode(decode(req.args[0])));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bytes_to_b58: (req) => {
|
|
||||||
if (req.args.length !== 1 || !Array.isArray(req.args[0])) {
|
|
||||||
return error('bytes_to_b58 accepts only single argument: array of numbers');
|
|
||||||
} else {
|
|
||||||
const argumentArray = req.args[0] as number[];
|
|
||||||
return success(encode(new Uint8Array(argumentArray)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bytes_from_b58: (req) => {
|
|
||||||
if (req.args.length !== 1) {
|
|
||||||
return error('bytes_from_b58 accepts only one string argument');
|
|
||||||
} else {
|
|
||||||
return success(Array.from(decode(req.args[0])));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
peer: {
|
|
||||||
timeout: (req) => {
|
|
||||||
if (req.args.length !== 2) {
|
|
||||||
return error('timeout accepts exactly two arguments: timeout duration in ms and a message string');
|
|
||||||
}
|
|
||||||
const durationMs = req.args[0];
|
|
||||||
const message = req.args[1];
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const res = success(message);
|
|
||||||
resolve(res);
|
|
||||||
}, durationMs);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
identify: (req) => {
|
|
||||||
return error('The JS implementation of Peer does not support identify');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user