@bitpay-labs/bitcore-wallet-service 11.11.1 → 11.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +1 -1
- package/package.json +5 -5
- package/src/lib/chain/btc/index.ts +3 -2
- package/src/lib/chain/eth/index.ts +134 -117
- package/src/lib/chain/sol/index.ts +1 -1
- package/src/lib/chain/xrp/index.ts +1 -1
- package/src/lib/common/constants.ts +1 -1
- package/src/lib/common/utils.ts +18 -1
- package/src/lib/model/tsskeygen.ts +16 -0
- package/src/lib/routes/maintenance.ts +11 -2
- package/src/lib/routes/middleware/authTssRequest.ts +28 -16
- package/src/lib/routes/middleware/verifyTssMessage.ts +4 -1
- package/src/lib/routes/services.ts +7 -1
- package/src/lib/routes/tss.ts +33 -30
- package/src/lib/routes/wallets.ts +3 -1
- package/src/lib/server.ts +198 -105
- package/src/lib/storage.ts +129 -20
- package/src/lib/tss.ts +111 -7
- package/test/chain/eth.test.ts +70 -0
- package/test/chain/sol.test.ts +1 -1
- package/test/chain/xrp.test.ts +1 -1
- package/test/expressapp.test.ts +243 -0
- package/test/integration/addressReads.test.ts +503 -0
- package/test/integration/bcmonitor.test.ts +44 -0
- package/test/integration/server.test.ts +251 -49
- package/test/integration/tss.test.ts +428 -0
- package/test/integration/walletReads.test.ts +488 -0
- package/ts_build/package.json +4 -4
- package/ts_build/src/lib/chain/btc/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/btc/index.js +3 -2
- package/ts_build/src/lib/chain/btc/index.js.map +1 -1
- package/ts_build/src/lib/chain/eth/index.d.ts +8 -1
- package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/eth/index.js +114 -102
- package/ts_build/src/lib/chain/eth/index.js.map +1 -1
- package/ts_build/src/lib/chain/sol/index.js +1 -1
- package/ts_build/src/lib/chain/sol/index.js.map +1 -1
- package/ts_build/src/lib/chain/xrp/index.js +1 -1
- package/ts_build/src/lib/chain/xrp/index.js.map +1 -1
- package/ts_build/src/lib/common/constants.d.ts +1 -1
- package/ts_build/src/lib/common/constants.js +1 -1
- package/ts_build/src/lib/common/index.d.ts +2 -1
- package/ts_build/src/lib/common/index.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.d.ts +1 -0
- package/ts_build/src/lib/common/utils.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.js +16 -1
- package/ts_build/src/lib/common/utils.js.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.d.ts +10 -0
- package/ts_build/src/lib/model/tsskeygen.d.ts.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.js +3 -0
- package/ts_build/src/lib/model/tsskeygen.js.map +1 -1
- package/ts_build/src/lib/routes/maintenance.d.ts.map +1 -1
- package/ts_build/src/lib/routes/maintenance.js +12 -4
- package/ts_build/src/lib/routes/maintenance.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts +6 -2
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.js +21 -14
- package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.js +2 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.js.map +1 -1
- package/ts_build/src/lib/routes/services.d.ts.map +1 -1
- package/ts_build/src/lib/routes/services.js +6 -1
- package/ts_build/src/lib/routes/services.js.map +1 -1
- package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
- package/ts_build/src/lib/routes/tss.js +32 -30
- package/ts_build/src/lib/routes/tss.js.map +1 -1
- package/ts_build/src/lib/routes/wallets.d.ts.map +1 -1
- package/ts_build/src/lib/routes/wallets.js +2 -1
- package/ts_build/src/lib/routes/wallets.js.map +1 -1
- package/ts_build/src/lib/server.d.ts +24 -3
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +148 -97
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/src/lib/storage.d.ts +26 -10
- package/ts_build/src/lib/storage.d.ts.map +1 -1
- package/ts_build/src/lib/storage.js +99 -20
- package/ts_build/src/lib/storage.js.map +1 -1
- package/ts_build/src/lib/tss.d.ts +12 -0
- package/ts_build/src/lib/tss.d.ts.map +1 -1
- package/ts_build/src/lib/tss.js +64 -5
- package/ts_build/src/lib/tss.js.map +1 -1
- package/ts_build/test/chain/eth.test.js +59 -0
- package/ts_build/test/chain/eth.test.js.map +1 -1
- package/ts_build/test/chain/sol.test.js +1 -1
- package/ts_build/test/chain/sol.test.js.map +1 -1
- package/ts_build/test/chain/xrp.test.js +1 -1
- package/ts_build/test/chain/xrp.test.js.map +1 -1
- package/ts_build/test/expressapp.test.js +222 -0
- package/ts_build/test/expressapp.test.js.map +1 -1
- package/ts_build/test/integration/addressReads.test.d.ts +2 -0
- package/ts_build/test/integration/addressReads.test.d.ts.map +1 -0
- package/ts_build/test/integration/addressReads.test.js +465 -0
- package/ts_build/test/integration/addressReads.test.js.map +1 -0
- package/ts_build/test/integration/bcmonitor.test.js +40 -0
- package/ts_build/test/integration/bcmonitor.test.js.map +1 -1
- package/ts_build/test/integration/server.test.js +228 -48
- package/ts_build/test/integration/server.test.js.map +1 -1
- package/ts_build/test/integration/tss.test.d.ts.map +1 -1
- package/ts_build/test/integration/tss.test.js +344 -0
- package/ts_build/test/integration/tss.test.js.map +1 -1
- package/ts_build/test/integration/walletReads.test.d.ts +2 -0
- package/ts_build/test/integration/walletReads.test.d.ts.map +1 -0
- package/ts_build/test/integration/walletReads.test.js +436 -0
- package/ts_build/test/integration/walletReads.test.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tss.test.d.ts","sourceRoot":"","sources":["../../../test/integration/tss.test.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tss.test.d.ts","sourceRoot":"","sources":["../../../test/integration/tss.test.ts"],"names":[],"mappings":"AAEA,OAAO,sBAAsB,CAAC"}
|
|
@@ -36,13 +36,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const util_1 = __importDefault(require("util"));
|
|
39
40
|
const sinon_1 = __importDefault(require("sinon"));
|
|
40
41
|
require("chai/register-should");
|
|
42
|
+
const bitcore_tss_1 = require("@bitpay-labs/bitcore-tss");
|
|
41
43
|
const crypto_wallet_core_1 = require("@bitpay-labs/crypto-wallet-core");
|
|
42
44
|
const supertest_1 = __importDefault(require("supertest"));
|
|
43
45
|
const twoOfThree = __importStar(require("./data/tss/2of3"));
|
|
44
46
|
const index_1 = __importDefault(require("../../src/index"));
|
|
45
47
|
const helpers_1 = __importDefault(require("./helpers"));
|
|
48
|
+
const TestData = __importStar(require("../testdata"));
|
|
46
49
|
const tss_1 = require("../../src/lib/tss");
|
|
47
50
|
const tsssign_1 = require("../../src/lib/model/tsssign");
|
|
48
51
|
const tsskeygen_1 = require("../../src/lib/model/tsskeygen");
|
|
@@ -454,6 +457,12 @@ describe('TSS', function () {
|
|
|
454
457
|
const res = await post(path, { message }, partyAuth(0));
|
|
455
458
|
expectError(res, 400, 'TSS_INVALID_MESSAGE');
|
|
456
459
|
});
|
|
460
|
+
it('should error if messages are empty arrays for an existing session', async function () {
|
|
461
|
+
await createKeyGenSession({ joined: [1, 2] });
|
|
462
|
+
const message = { ...round1Message(0), broadcastMessages: [], p2pMessages: [] };
|
|
463
|
+
const res = await post(path, { message }, partyAuth(0));
|
|
464
|
+
expectError(res, 400, 'TSS_INVALID_MESSAGE');
|
|
465
|
+
});
|
|
457
466
|
});
|
|
458
467
|
describe('joining a v1.2 session', function () {
|
|
459
468
|
it('should let a registered party join', async function () {
|
|
@@ -751,6 +760,266 @@ describe('TSS', function () {
|
|
|
751
760
|
});
|
|
752
761
|
});
|
|
753
762
|
});
|
|
763
|
+
describe('BWS join secrets', function () {
|
|
764
|
+
const path = `/v1/tss/keygen/${SESSION_ID}/secret`;
|
|
765
|
+
const ethSecrets = { 1: 'ethSecretForParty1', 2: 'ethSecretForParty2' };
|
|
766
|
+
const storeSecrets = (body, auth = partyAuth(0)) => post(path, body, auth);
|
|
767
|
+
const getSecret = (auth, query = { chain: 'eth', network: 'livenet' }) => {
|
|
768
|
+
const qs = new URLSearchParams(query).toString();
|
|
769
|
+
return get(path + (qs ? '?' + qs : ''), auth);
|
|
770
|
+
};
|
|
771
|
+
const fetchSession = () => storage.fetchTssKeyGenSession({ id: SESSION_ID });
|
|
772
|
+
describe('POST /v1/tss/keygen/:id/secret', function () {
|
|
773
|
+
it('should store the secrets per chain and network', async function () {
|
|
774
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
775
|
+
(await storeSecrets({ chain: 'eth', network: 'livenet', secrets: ethSecrets })).status.should.equal(200);
|
|
776
|
+
(await storeSecrets({ chain: 'arb', network: 'livenet', secrets: { 1: 'arb1', 2: 'arb2' } })).status.should.equal(200);
|
|
777
|
+
const session = await fetchSession();
|
|
778
|
+
session.bwsJoinSecrets.should.deep.equal({
|
|
779
|
+
'eth:livenet': ethSecrets,
|
|
780
|
+
'arb:livenet': { 1: 'arb1', 2: 'arb2' }
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
it('should key the secrets by partyId when given keygen session copayer IDs', async function () {
|
|
784
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
785
|
+
const res = await storeSecrets({ chain: 'eth', network: 'livenet', secrets: { [copayerIds[2]]: 'two', [copayerIds[1]]: 'one' } });
|
|
786
|
+
res.status.should.equal(200);
|
|
787
|
+
const session = await fetchSession();
|
|
788
|
+
session.bwsJoinSecrets['eth:livenet'].should.deep.equal({ 1: 'one', 2: 'two' });
|
|
789
|
+
});
|
|
790
|
+
it('should normalize the chain and network', async function () {
|
|
791
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
792
|
+
(await storeSecrets({ chain: 'ETH', network: 'testnet', secrets: ethSecrets })).status.should.equal(200);
|
|
793
|
+
(await storeSecrets({ chain: 'btc', network: 'mainnet', secrets: ethSecrets })).status.should.equal(200);
|
|
794
|
+
const session = await fetchSession();
|
|
795
|
+
Object.keys(session.bwsJoinSecrets).should.deep.equal(['eth:sepolia', 'btc:livenet']);
|
|
796
|
+
});
|
|
797
|
+
it('should overwrite the secrets for the same chain and network', async function () {
|
|
798
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
799
|
+
(await storeSecrets({ chain: 'eth', network: 'livenet', secrets: { 1: 'old1', 2: 'old2' } })).status.should.equal(200);
|
|
800
|
+
(await storeSecrets({ chain: 'eth', network: 'livenet', secrets: ethSecrets })).status.should.equal(200);
|
|
801
|
+
const session = await fetchSession();
|
|
802
|
+
session.bwsJoinSecrets.should.deep.equal({ 'eth:livenet': ethSecrets });
|
|
803
|
+
});
|
|
804
|
+
it('should store the secrets after the session has expired', async function () {
|
|
805
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2], timeLimit: 1 });
|
|
806
|
+
expireSession();
|
|
807
|
+
const res = await storeSecrets({ chain: 'eth', network: 'livenet', secrets: ethSecrets });
|
|
808
|
+
res.status.should.equal(200);
|
|
809
|
+
(await fetchSession()).bwsJoinSecrets['eth:livenet'].should.deep.equal(ethSecrets);
|
|
810
|
+
});
|
|
811
|
+
it('should store the secret sent by a legacy (< v1.3) client', async function () {
|
|
812
|
+
await createKeyGenSession({ version: 1.2, joined: [1, 2] });
|
|
813
|
+
const res = await storeSecrets({ secret: 'legacySecret' });
|
|
814
|
+
res.status.should.equal(200);
|
|
815
|
+
const session = await fetchSession();
|
|
816
|
+
session.bwsJoinSecret.should.equal('legacySecret');
|
|
817
|
+
session.bwsJoinSecrets.should.deep.equal({});
|
|
818
|
+
});
|
|
819
|
+
it('should error if the requester is not the session leader', async function () {
|
|
820
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
821
|
+
const res = await storeSecrets({ chain: 'eth', network: 'livenet', secrets: ethSecrets }, partyAuth(1));
|
|
822
|
+
expectError(res, 400, 'TSS_GENERIC_ERROR', 'Only the session creator can store the BWS join secret');
|
|
823
|
+
});
|
|
824
|
+
it('should error if the copayer is not in the session', async function () {
|
|
825
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
826
|
+
const res = await storeSecrets({ chain: 'eth', network: 'livenet', secrets: ethSecrets }, { copayerId: 'notInSession', privKey: randomKey() });
|
|
827
|
+
expectError(res, 401, 'NOT_AUTHORIZED');
|
|
828
|
+
});
|
|
829
|
+
for (const [description, body] of [
|
|
830
|
+
['a party is missing a secret', { chain: 'eth', network: 'livenet', secrets: { 1: 'one' } }],
|
|
831
|
+
['a secret is for the leader', { chain: 'eth', network: 'livenet', secrets: { ...ethSecrets, 0: 'zero' } }],
|
|
832
|
+
['a secret is for a partyId >= n', { chain: 'eth', network: 'livenet', secrets: { ...ethSecrets, 3: 'three' } }],
|
|
833
|
+
['a secret is for a copayer that is not in the session', { chain: 'eth', network: 'livenet', secrets: { 1: 'one', notInSession: 'two' } }],
|
|
834
|
+
['a secret is not a string', { chain: 'eth', network: 'livenet', secrets: { 1: 'one', 2: { $ne: null } } }],
|
|
835
|
+
['a secret is empty', { chain: 'eth', network: 'livenet', secrets: { 1: 'one', 2: '' } }],
|
|
836
|
+
['secrets is not an object', { chain: 'eth', network: 'livenet', secrets: 'abc' }],
|
|
837
|
+
['secrets is an array', { chain: 'eth', network: 'livenet', secrets: ['zero', 'one', 'two'] }],
|
|
838
|
+
['secrets is missing', { chain: 'eth', network: 'livenet' }],
|
|
839
|
+
['chain is missing', { network: 'livenet', secrets: ethSecrets }],
|
|
840
|
+
['chain is not supported', { chain: 'foo', network: 'livenet', secrets: ethSecrets }],
|
|
841
|
+
['chain is a Mongo path', { chain: 'eth.livenet', network: 'livenet', secrets: ethSecrets }],
|
|
842
|
+
['network is missing', { chain: 'eth', secrets: ethSecrets }],
|
|
843
|
+
['network is not supported', { chain: 'eth', network: 'foo', secrets: ethSecrets }],
|
|
844
|
+
]) {
|
|
845
|
+
it(`should error if ${description}`, async function () {
|
|
846
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
847
|
+
const res = await storeSecrets(body);
|
|
848
|
+
expectError(res, 400, 'TSS_GENERIC_ERROR');
|
|
849
|
+
(await fetchSession()).bwsJoinSecrets.should.deep.equal({});
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
describe('GET /v1/tss/keygen/:id/secret', function () {
|
|
854
|
+
beforeEach(async function () {
|
|
855
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
856
|
+
await storage.storeTssKeyBwsJoinSecrets({ id: SESSION_ID, chain: 'eth', network: 'livenet', secrets: ethSecrets });
|
|
857
|
+
});
|
|
858
|
+
it('should return each party its own secret for the chain and network', async function () {
|
|
859
|
+
for (const partyId of [1, 2]) {
|
|
860
|
+
const res = await getSecret(partyAuth(partyId));
|
|
861
|
+
res.status.should.equal(200);
|
|
862
|
+
res.body.secret.should.equal(ethSecrets[partyId]);
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
it('should normalize the chain and network', async function () {
|
|
866
|
+
const res = await getSecret(partyAuth(1), { chain: 'ETH', network: 'mainnet' });
|
|
867
|
+
res.status.should.equal(200);
|
|
868
|
+
res.body.secret.should.equal(ethSecrets[1]);
|
|
869
|
+
});
|
|
870
|
+
it('should return the secret after the session has expired', async function () {
|
|
871
|
+
await storage.db.collection(index_1.default.Storage.collections.TSS_KEYGEN).deleteMany({});
|
|
872
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2], timeLimit: 1 });
|
|
873
|
+
await storage.storeTssKeyBwsJoinSecrets({ id: SESSION_ID, chain: 'eth', network: 'livenet', secrets: ethSecrets });
|
|
874
|
+
const roundPath = `/v1/tss/keygen/${SESSION_ID}/0`;
|
|
875
|
+
(await fetchSession()).isExpired().should.equal(false);
|
|
876
|
+
const live = await get(roundPath, partyAuth(1));
|
|
877
|
+
live.status.should.equal(200);
|
|
878
|
+
(await getSecret(partyAuth(1))).body.secret.should.equal(ethSecrets[1]);
|
|
879
|
+
expireSession();
|
|
880
|
+
(await fetchSession()).isExpired().should.equal(true);
|
|
881
|
+
expectError(await get(roundPath, partyAuth(1)), 400, 'TSS_SESSION_EXPIRED');
|
|
882
|
+
const res = await getSecret(partyAuth(1));
|
|
883
|
+
res.status.should.equal(200);
|
|
884
|
+
res.body.secret.should.equal(ethSecrets[1]);
|
|
885
|
+
});
|
|
886
|
+
it('should error if there is no secret for the chain', async function () {
|
|
887
|
+
const res = await getSecret(partyAuth(1), { chain: 'arb', network: 'livenet' });
|
|
888
|
+
expectError(res, 400, 'TSS_BWS_JOIN_SECRET_NOT_FOUND');
|
|
889
|
+
});
|
|
890
|
+
it('should error if there is no secret for the network', async function () {
|
|
891
|
+
const res = await getSecret(partyAuth(1), { chain: 'eth', network: 'testnet' });
|
|
892
|
+
expectError(res, 400, 'TSS_BWS_JOIN_SECRET_NOT_FOUND');
|
|
893
|
+
});
|
|
894
|
+
it('should error if the chain is not supported', async function () {
|
|
895
|
+
const res = await getSecret(partyAuth(1), { chain: 'foo', network: 'livenet' });
|
|
896
|
+
expectError(res, 400, 'TSS_GENERIC_ERROR', 'Invalid chain provided');
|
|
897
|
+
});
|
|
898
|
+
it('should error if only one of chain and network is given', async function () {
|
|
899
|
+
const res = await getSecret(partyAuth(1), { chain: 'eth' });
|
|
900
|
+
expectError(res, 400, 'TSS_GENERIC_ERROR', 'Invalid network provided');
|
|
901
|
+
});
|
|
902
|
+
it('should error if the copayer is not in the session', async function () {
|
|
903
|
+
const res = await getSecret({ copayerId: 'notInSession', privKey: randomKey() });
|
|
904
|
+
expectError(res, 401, 'NOT_AUTHORIZED');
|
|
905
|
+
});
|
|
906
|
+
it('should reject another party identity', async function () {
|
|
907
|
+
const res = await getSecret({ copayerId: copayerIds[2], privKey: parties[1].authKey });
|
|
908
|
+
expectError(res, 401, 'NOT_AUTHORIZED', 'Invalid signature');
|
|
909
|
+
});
|
|
910
|
+
it('should not return the legacy secret to clients that send the chain and network', async function () {
|
|
911
|
+
await storage.storeTssKeyBwsJoinSecret({ id: SESSION_ID, secret: 'legacySecret' });
|
|
912
|
+
const res = await getSecret(partyAuth(1), { chain: 'arb', network: 'livenet' });
|
|
913
|
+
expectError(res, 400, 'TSS_BWS_JOIN_SECRET_NOT_FOUND');
|
|
914
|
+
});
|
|
915
|
+
describe('legacy (v1.2) session', function () {
|
|
916
|
+
beforeEach(async function () {
|
|
917
|
+
await storage.db.collection(index_1.default.Storage.collections.TSS_KEYGEN).deleteMany({});
|
|
918
|
+
await createKeyGenSession({ version: 1.2, joined: [1, 2] });
|
|
919
|
+
await storage.storeTssKeyBwsJoinSecret({ id: SESSION_ID, secret: 'legacySecret' });
|
|
920
|
+
});
|
|
921
|
+
it('should return the legacy secret to legacy clients', async function () {
|
|
922
|
+
const res = await getSecret(partyAuth(1), {});
|
|
923
|
+
res.status.should.equal(200);
|
|
924
|
+
res.body.secret.should.equal('legacySecret');
|
|
925
|
+
});
|
|
926
|
+
it('should return the chain secret for a wallet created with the existing key', async function () {
|
|
927
|
+
await storage.storeTssKeyBwsJoinSecrets({ id: SESSION_ID, chain: 'arb', network: 'livenet', secrets: { 1: 'arb1', 2: 'arb2' } });
|
|
928
|
+
const res = await getSecret(partyAuth(1), { chain: 'arb', network: 'livenet' });
|
|
929
|
+
res.status.should.equal(200);
|
|
930
|
+
res.body.secret.should.equal('arb1');
|
|
931
|
+
});
|
|
932
|
+
it('should error if there is no secret for the chain', async function () {
|
|
933
|
+
const res = await getSecret(partyAuth(1), { chain: 'arb', network: 'livenet' });
|
|
934
|
+
expectError(res, 400, 'TSS_BWS_JOIN_SECRET_NOT_FOUND');
|
|
935
|
+
});
|
|
936
|
+
});
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
describe('TSS wallets', function () {
|
|
940
|
+
const createWallet = (body) => app.post(urlPrefix + '/v2/wallets/').send({
|
|
941
|
+
name: 'tss wallet',
|
|
942
|
+
m: vector.m,
|
|
943
|
+
n: vector.n,
|
|
944
|
+
pubKey: TestData.keyPair.pub,
|
|
945
|
+
chain: 'arb',
|
|
946
|
+
network: 'testnet',
|
|
947
|
+
tssKeyId: SESSION_ID,
|
|
948
|
+
...body
|
|
949
|
+
});
|
|
950
|
+
const joinWallet = (walletId, partyId, body = {}) => app.post(`${urlPrefix}/v2/wallets/${walletId}/copayers/`).send(helpers_1.default.getSignedCopayerOpts({
|
|
951
|
+
walletId,
|
|
952
|
+
name: 'party' + partyId,
|
|
953
|
+
xPubKey: getXPubKey(parties[partyId].seed),
|
|
954
|
+
requestPubKey: authPubKey(partyId),
|
|
955
|
+
chain: 'arb',
|
|
956
|
+
...body
|
|
957
|
+
}));
|
|
958
|
+
beforeEach(async function () {
|
|
959
|
+
await storage.db.collection(index_1.default.Storage.collections.WALLETS).deleteMany({});
|
|
960
|
+
await storage.db.collection(index_1.default.Storage.collections.COPAYERS_LOOKUP).deleteMany({});
|
|
961
|
+
await createKeyGenSession({ version: 1.3, joined: [1, 2] });
|
|
962
|
+
await storage.storeTssKeySharedPubKey({ id: SESSION_ID, publicKey: 'sharedPublicKey' });
|
|
963
|
+
});
|
|
964
|
+
describe('POST /v2/wallets/', function () {
|
|
965
|
+
it('should create a wallet on another chain than the key generation session', async function () {
|
|
966
|
+
const res = await createWallet({ tssKeygenCopayerId: copayerIds[0] });
|
|
967
|
+
res.status.should.equal(200);
|
|
968
|
+
const wallet = await util_1.default.promisify(storage.fetchWallet).call(storage, res.body.walletId);
|
|
969
|
+
wallet.chain.should.equal('arb');
|
|
970
|
+
wallet.tssKeyId.should.equal(SESSION_ID);
|
|
971
|
+
wallet.tssVersion.should.equal(1.3);
|
|
972
|
+
wallet.m.should.equal(1);
|
|
973
|
+
wallet.n.should.equal(1);
|
|
974
|
+
});
|
|
975
|
+
it('should error if the keygen session copayer ID is missing', async function () {
|
|
976
|
+
const res = await createWallet({});
|
|
977
|
+
expectError(res, 400, 'BADREQUEST', 'Copayer ID is required for TSS version >= 1.3');
|
|
978
|
+
});
|
|
979
|
+
it('should error if the keygen session copayer ID is not the session leader', async function () {
|
|
980
|
+
const res = await createWallet({ tssKeygenCopayerId: copayerIds[1] });
|
|
981
|
+
expectError(res, 400, 'BADREQUEST', 'Only the TSS session leader can create a wallet');
|
|
982
|
+
});
|
|
983
|
+
it('should not require the keygen session copayer ID for a legacy (v1.2) session', async function () {
|
|
984
|
+
await storage.db.collection(index_1.default.Storage.collections.TSS_KEYGEN).deleteMany({});
|
|
985
|
+
await createKeyGenSession({ version: 1.2, joined: [1, 2] });
|
|
986
|
+
await storage.storeTssKeySharedPubKey({ id: SESSION_ID, publicKey: 'sharedPublicKey' });
|
|
987
|
+
const res = await createWallet({});
|
|
988
|
+
res.status.should.equal(200);
|
|
989
|
+
});
|
|
990
|
+
});
|
|
991
|
+
describe('POST /v2/wallets/:id/copayers/', function () {
|
|
992
|
+
let walletId;
|
|
993
|
+
beforeEach(async function () {
|
|
994
|
+
const res = await createWallet({ tssKeygenCopayerId: copayerIds[0] });
|
|
995
|
+
walletId = res.body.walletId;
|
|
996
|
+
});
|
|
997
|
+
it('should let the parties join with their keygen session copayer IDs', async function () {
|
|
998
|
+
for (const partyId of [0, 1, 2]) {
|
|
999
|
+
const res = await joinWallet(walletId, partyId, { tssKeygenChain: 'btc' });
|
|
1000
|
+
res.status.should.equal(200);
|
|
1001
|
+
res.body.copayerId.should.equal(xPubToCopayerId('arb', getXPubKey(parties[partyId].seed)));
|
|
1002
|
+
}
|
|
1003
|
+
const wallet = await util_1.default.promisify(storage.fetchWallet).call(storage, walletId);
|
|
1004
|
+
wallet.copayers.length.should.equal(3);
|
|
1005
|
+
});
|
|
1006
|
+
it('should error if the keygen chain is not given for a different wallet chain', async function () {
|
|
1007
|
+
const res = await joinWallet(walletId, 1);
|
|
1008
|
+
expectError(res, 400, 'TSS_NON_PARTICIPANT');
|
|
1009
|
+
});
|
|
1010
|
+
it('should error if the keygen chain is invalid', async function () {
|
|
1011
|
+
const res = await joinWallet(walletId, 1, { tssKeygenChain: 'foo' });
|
|
1012
|
+
expectError(res, 400, 'BADREQUEST', 'Invalid TSS keygen chain');
|
|
1013
|
+
});
|
|
1014
|
+
it('should error if the copayer was not in the key generation session', async function () {
|
|
1015
|
+
const res = await joinWallet(walletId, 1, {
|
|
1016
|
+
xPubKey: getXPubKey(Buffer.alloc(32, 1)),
|
|
1017
|
+
tssKeygenChain: 'btc'
|
|
1018
|
+
});
|
|
1019
|
+
expectError(res, 400, 'TSS_NON_PARTICIPANT');
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
});
|
|
754
1023
|
});
|
|
755
1024
|
describe('Signing', function () {
|
|
756
1025
|
const sigMessage = (partyId) => ({
|
|
@@ -784,6 +1053,14 @@ describe('TSS', function () {
|
|
|
784
1053
|
});
|
|
785
1054
|
describe('POST /v1/tss/sign/:id', function () {
|
|
786
1055
|
const path = `/v1/tss/sign/${SESSION_ID}`;
|
|
1056
|
+
const broadcastMessage = (partyId, round = 0) => ({
|
|
1057
|
+
...sigMessage(partyId),
|
|
1058
|
+
round,
|
|
1059
|
+
broadcastMessages: [{
|
|
1060
|
+
from: partyId,
|
|
1061
|
+
payload: bitcore_tss_1.utils.detachSignData(Buffer.from(`round ${round} from party ${partyId}`), parties[partyId].authKey)
|
|
1062
|
+
}]
|
|
1063
|
+
});
|
|
787
1064
|
it('should error if session is expired', async function () {
|
|
788
1065
|
await createSigSession({ timeLimit: 1 });
|
|
789
1066
|
expireSession();
|
|
@@ -791,6 +1068,73 @@ describe('TSS', function () {
|
|
|
791
1068
|
const res = await post(path, { message }, partyAuth(1));
|
|
792
1069
|
expectError(res, 400, 'TSS_SESSION_EXPIRED');
|
|
793
1070
|
});
|
|
1071
|
+
it('should start a new session with party 0\'s round 0 message', async function () {
|
|
1072
|
+
const res = await post(path, { message: broadcastMessage(0), m: 2, version: 1.1 }, partyAuth(0));
|
|
1073
|
+
res.status.should.equal(200);
|
|
1074
|
+
const session = await storage.fetchTssSigSession({ id: SESSION_ID });
|
|
1075
|
+
session.m.should.equal(2);
|
|
1076
|
+
session.participants.should.deep.equal([{ partyId: 0, copayerId: copayerIds[0] }]);
|
|
1077
|
+
});
|
|
1078
|
+
it('should let another copayer join with its round 0 message', async function () {
|
|
1079
|
+
(await post(path, { message: broadcastMessage(0), m: 2, version: 1.1 }, partyAuth(0))).status.should.equal(200);
|
|
1080
|
+
const res = await post(path, { message: broadcastMessage(1), version: 1.1 }, partyAuth(1));
|
|
1081
|
+
res.status.should.equal(200);
|
|
1082
|
+
const session = await storage.fetchTssSigSession({ id: SESSION_ID });
|
|
1083
|
+
session.participants.should.deep.equal([
|
|
1084
|
+
{ partyId: 0, copayerId: copayerIds[0] },
|
|
1085
|
+
{ partyId: 1, copayerId: copayerIds[1] }
|
|
1086
|
+
]);
|
|
1087
|
+
session.getCurrentRound().should.equal(1);
|
|
1088
|
+
});
|
|
1089
|
+
it('should error if a copayer joins a full session', async function () {
|
|
1090
|
+
await createSigSession();
|
|
1091
|
+
const res = await post(path, { message: broadcastMessage(2), version: 1.1 }, partyAuth(2));
|
|
1092
|
+
expectError(res, 400, 'TSS_MAX_PARTICIPANTS_REACHED');
|
|
1093
|
+
});
|
|
1094
|
+
it('should error if a non-participant sends a message for a later round', async function () {
|
|
1095
|
+
await createSigSession();
|
|
1096
|
+
const res = await post(path, { message: broadcastMessage(2, 1), version: 1.1 }, partyAuth(2));
|
|
1097
|
+
expectError(res, 401, 'NOT_AUTHORIZED', 'Copayer not found in session');
|
|
1098
|
+
});
|
|
1099
|
+
it('should error if messages are empty arrays in an m > 1 session', async function () {
|
|
1100
|
+
await createSigSession();
|
|
1101
|
+
const message = { ...sigMessage(0), round: 1 };
|
|
1102
|
+
const res = await post(path, { message, version: 1.1 }, partyAuth(0));
|
|
1103
|
+
expectError(res, 400, 'TSS_INVALID_MESSAGE');
|
|
1104
|
+
});
|
|
1105
|
+
describe('1-of-N session', function () {
|
|
1106
|
+
const start = () => post(path, { message: broadcastMessage(0), m: 1, version: 1.1 }, partyAuth(0));
|
|
1107
|
+
it('should accept rounds that have no messages for other parties', async function () {
|
|
1108
|
+
(await start()).status.should.equal(200);
|
|
1109
|
+
for (const round of [1, 2, 3, 4]) {
|
|
1110
|
+
const message = round === 3 ? broadcastMessage(0, round) : { ...sigMessage(0), round };
|
|
1111
|
+
const res = await post(path, { message, version: 1.1 }, partyAuth(0));
|
|
1112
|
+
res.status.should.equal(200, `round ${round}: ${res.text}`);
|
|
1113
|
+
}
|
|
1114
|
+
const session = await storage.fetchTssSigSession({ id: SESSION_ID });
|
|
1115
|
+
session.rounds.length.should.equal(5);
|
|
1116
|
+
session.getCurrentRound().should.equal(5);
|
|
1117
|
+
const res = await get(`/v1/tss/sign/${SESSION_ID}/4?maxWaitTime=0`, partyAuth(0));
|
|
1118
|
+
res.status.should.equal(200);
|
|
1119
|
+
JSON.parse(res.text).messages.should.deep.equal([]);
|
|
1120
|
+
});
|
|
1121
|
+
it('should error if round 0 has no broadcast message', async function () {
|
|
1122
|
+
const res = await post(path, { message: sigMessage(0), m: 1, version: 1.1 }, partyAuth(0));
|
|
1123
|
+
expectError(res, 400, 'TSS_INVALID_MESSAGE');
|
|
1124
|
+
});
|
|
1125
|
+
it('should still verify broadcast signatures', async function () {
|
|
1126
|
+
(await start()).status.should.equal(200);
|
|
1127
|
+
const message = broadcastMessage(0, 1);
|
|
1128
|
+
message.broadcastMessages[0].payload = bitcore_tss_1.utils.detachSignData(Buffer.from('forged'), randomKey());
|
|
1129
|
+
const res = await post(path, { message, version: 1.1 }, partyAuth(0));
|
|
1130
|
+
expectError(res, 400, 'TSS_INVALID_MESSAGE_SIG');
|
|
1131
|
+
});
|
|
1132
|
+
it('should error if another copayer tries to join', async function () {
|
|
1133
|
+
(await start()).status.should.equal(200);
|
|
1134
|
+
const res = await post(path, { message: broadcastMessage(1), version: 1.1 }, partyAuth(1));
|
|
1135
|
+
expectError(res, 400, 'TSS_MAX_PARTICIPANTS_REACHED');
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
794
1138
|
});
|
|
795
1139
|
describe('GET /v1/tss/sign/:id/:round', function () {
|
|
796
1140
|
const path = `/v1/tss/sign/${SESSION_ID}/0?maxWaitTime=0`;
|