Skip to content

Commit edd9523

Browse files
authored
Merge pull request #190 from BitGo/WCN-84-scope-keychain-validation
fix: scope keychain validation to wallet type
2 parents 56340b6 + 7133bdf commit edd9523

3 files changed

Lines changed: 145 additions & 38 deletions

File tree

src/__tests__/api/master/sendMany.test.ts

Lines changed: 113 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/sendMany', () => {
441441
},
442442
],
443443
source: 'user',
444-
pubkey: 'xpub_user',
444+
commonKeychain: 'test-common-keychain',
445445
});
446446

447447
response.status.should.equal(200);
@@ -530,7 +530,7 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/sendMany', () => {
530530
},
531531
],
532532
source: 'user',
533-
pubkey: 'xpub_user',
533+
commonKeychain: 'test-common-keychain',
534534
});
535535

536536
response.status.should.equal(200);
@@ -604,7 +604,7 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/sendMany', () => {
604604
type: 'fillNonce',
605605
nonce: '2',
606606
source: 'user',
607-
pubkey: 'xpub_user',
607+
commonKeychain: 'test-common-keychain',
608608
});
609609

610610
response.status.should.equal(200);
@@ -667,7 +667,7 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/sendMany', () => {
667667
},
668668
],
669669
source: 'backup',
670-
pubkey: 'xpub_backup',
670+
commonKeychain: 'test-common-keychain',
671671
});
672672

673673
response.status.should.equal(400);
@@ -937,17 +937,17 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/sendMany', () => {
937937
signNock.done();
938938
});
939939

940-
it('should throw an error when neither the pubkey nor the commonKeychain is provided', async () => {
940+
it('should throw error when pubkey is missing for multisig wallet', async () => {
941941
const walletGetNock = nock(bitgoApiUrl)
942942
.get(`/api/v2/${coin}/wallet/${walletId}`)
943943
.matchHeader('any', () => true)
944944
.reply(200, {
945945
id: walletId,
946946
type: 'advanced',
947947
keys: ['user-key-id', 'backup-key-id', 'bitgo-key-id'],
948+
multisigType: 'onchain',
948949
});
949950

950-
// Mock keychain get request
951951
const keychainGetNock = nock(bitgoApiUrl)
952952
.get(`/api/v2/${coin}/key/user-key-id`)
953953
.matchHeader('any', () => true)
@@ -960,24 +960,121 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/sendMany', () => {
960960
.post(`/api/v1/${coin}/advancedwallet/${walletId}/sendMany`)
961961
.set('Authorization', `Bearer ${accessToken}`)
962962
.send({
963-
recipients: [
964-
{
965-
address: 'tb1qtest1',
966-
amount: '100000',
967-
},
968-
],
963+
recipients: [{ address: 'tb1qtest1', amount: '100000' }],
964+
source: 'user',
965+
});
966+
967+
response.status.should.equal(400);
968+
response.body.should.have.property('error');
969+
response.body.error.should.equal('BadRequestError');
970+
response.body.details.should.equal('pubkey must be provided for multisig user signing');
971+
972+
walletGetNock.done();
973+
keychainGetNock.done();
974+
});
975+
976+
it('should throw error when commonKeychain is missing for TSS wallet', async () => {
977+
const walletGetNock = nock(bitgoApiUrl)
978+
.get(`/api/v2/${coin}/wallet/${walletId}`)
979+
.matchHeader('any', () => true)
980+
.reply(200, {
981+
id: walletId,
982+
type: 'advanced',
983+
keys: ['user-key-id', 'backup-key-id', 'bitgo-key-id'],
984+
multisigType: 'tss',
985+
});
986+
987+
const keychainGetNock = nock(bitgoApiUrl)
988+
.get(`/api/v2/${coin}/key/user-key-id`)
989+
.matchHeader('any', () => true)
990+
.reply(200, {
991+
id: 'user-key-id',
992+
pub: 'xpub_user',
993+
commonKeychain: 'test-common-keychain',
994+
});
995+
996+
const multisigTypeStub = sinon.stub(Wallet.prototype, 'multisigType').returns('tss');
997+
998+
const response = await agent
999+
.post(`/api/v1/${coin}/advancedwallet/${walletId}/sendMany`)
1000+
.set('Authorization', `Bearer ${accessToken}`)
1001+
.send({
1002+
recipients: [{ address: 'tb1qtest1', amount: '100000' }],
9691003
source: 'user',
9701004
});
9711005

972-
console.log(response.body);
9731006
response.status.should.equal(400);
9741007
response.body.should.have.property('error');
9751008
response.body.error.should.equal('BadRequestError');
976-
response.body.details.should.equal(
977-
'Either pubkey or commonKeychain must be provided for user signing',
978-
);
1009+
response.body.details.should.equal('commonKeychain must be provided for TSS user signing');
9791010

9801011
walletGetNock.done();
9811012
keychainGetNock.done();
1013+
sinon.assert.calledOnce(multisigTypeStub);
1014+
});
1015+
1016+
it('should ignore commonKeychain param for multisig wallet', async () => {
1017+
const walletGetNock = nock(bitgoApiUrl)
1018+
.get(`/api/v2/${coin}/wallet/${walletId}`)
1019+
.matchHeader('any', () => true)
1020+
.reply(200, {
1021+
id: walletId,
1022+
type: 'advanced',
1023+
keys: ['user-key-id', 'backup-key-id', 'bitgo-key-id'],
1024+
multisigType: 'onchain',
1025+
});
1026+
1027+
const keychainGetNock = nock(bitgoApiUrl)
1028+
.get(`/api/v2/${coin}/key/user-key-id`)
1029+
.matchHeader('any', () => true)
1030+
.reply(200, {
1031+
id: 'user-key-id',
1032+
pub: 'xpub_user',
1033+
});
1034+
1035+
const prebuildStub = sinon.stub(Wallet.prototype, 'prebuildTransaction').resolves({
1036+
txHex: 'prebuilt-tx-hex',
1037+
txInfo: { nP2SHInputs: 1, nSegwitInputs: 0, nOutputs: 2 },
1038+
walletId,
1039+
});
1040+
1041+
const verifyStub = sinon.stub(Tbtc.prototype, 'verifyTransaction').resolves(true);
1042+
1043+
const signNock = nock(advancedWalletManagerUrl)
1044+
.post(`/api/${coin}/multisig/sign`)
1045+
.reply(200, {
1046+
halfSigned: {
1047+
txHex: 'signed-tx-hex',
1048+
txInfo: { nP2SHInputs: 1, nSegwitInputs: 0, nOutputs: 2 },
1049+
},
1050+
walletId: 'test-wallet-id',
1051+
source: 'user',
1052+
pub: 'xpub_user',
1053+
});
1054+
1055+
const submitNock = nock(bitgoApiUrl)
1056+
.post(`/api/v2/${coin}/wallet/${walletId}/tx/send`)
1057+
.matchHeader('any', () => true)
1058+
.reply(200, { txid: 'test-tx-id', status: 'signed' });
1059+
1060+
const response = await agent
1061+
.post(`/api/v1/${coin}/advancedwallet/${walletId}/sendMany`)
1062+
.set('Authorization', `Bearer ${accessToken}`)
1063+
.send({
1064+
recipients: [{ address: 'tb1qtest1', amount: '100000' }],
1065+
source: 'user',
1066+
pubkey: 'xpub_user',
1067+
commonKeychain: 'some-irrelevant-value',
1068+
});
1069+
1070+
response.status.should.equal(200);
1071+
response.body.should.have.property('txid', 'test-tx-id');
1072+
1073+
walletGetNock.done();
1074+
keychainGetNock.done();
1075+
sinon.assert.calledOnce(prebuildStub);
1076+
sinon.assert.calledOnce(verifyStub);
1077+
signNock.done();
1078+
submitNock.done();
9821079
});
9831080
});

src/masterBitgoExpress/handlers/handleSendMany.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,25 +103,31 @@ export async function handleSendMany(req: MasterApiSpecRouteRequest<'v1.wallet.s
103103
if (!signingKeychain) {
104104
throw new NotFoundError(`Signing keychain for ${params.source} not found`);
105105
}
106-
if (!params.pubkey && !params.commonKeychain) {
107-
throw new BadRequestError(
108-
`Either pubkey or commonKeychain must be provided for ${params.source} signing`,
109-
);
110-
}
111-
if (params.pubkey && signingKeychain.pub !== params.pubkey) {
112-
throw new BadRequestError(
113-
`Pub provided does not match the keychain on wallet for ${params.source}`,
114-
);
115-
}
116-
if (params.commonKeychain && signingKeychain.commonKeychain !== params.commonKeychain) {
117-
throw new BadRequestError(
118-
`Common keychain provided does not match the keychain on wallet for ${params.source}`,
119-
);
106+
const isTss = wallet.multisigType() === 'tss';
107+
108+
if (isTss) {
109+
if (!params.commonKeychain) {
110+
throw new BadRequestError(`commonKeychain must be provided for TSS ${params.source} signing`);
111+
}
112+
if (signingKeychain.commonKeychain !== params.commonKeychain) {
113+
throw new BadRequestError(
114+
`Common keychain provided does not match the keychain on wallet for ${params.source}`,
115+
);
116+
}
117+
} else {
118+
if (!params.pubkey) {
119+
throw new BadRequestError(`pubkey must be provided for multisig ${params.source} signing`);
120+
}
121+
if (signingKeychain.pub !== params.pubkey) {
122+
throw new BadRequestError(
123+
`Pub provided does not match the keychain on wallet for ${params.source}`,
124+
);
125+
}
120126
}
121127

122128
try {
123129
// Create MPC send parameters with custom signing functions
124-
if (wallet.multisigType() === 'tss') {
130+
if (isTss) {
125131
if (signingKeychain.source === 'backup') {
126132
throw new BadRequestError('Backup MPC signing not supported for sendMany');
127133
}

src/masterBitgoExpress/handlers/utils/utils.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,18 @@ export async function getWalletAndSigningKeychain({
4141
throw new Error(`Signing keychain for ${params.source} not found`);
4242
}
4343

44-
if (params.pubkey && params.pubkey !== signingKeychain.pub) {
45-
throw new Error(`Pub provided does not match the keychain on wallet for ${params.source}`);
46-
}
44+
const isTss = wallet.multisigType() === 'tss';
4745

48-
if (params.commonKeychain && signingKeychain.commonKeychain !== params.commonKeychain) {
49-
throw new Error(
50-
`Common keychain provided does not match the keychain on wallet for ${params.source}`,
51-
);
46+
if (isTss) {
47+
if (params.commonKeychain && signingKeychain.commonKeychain !== params.commonKeychain) {
48+
throw new Error(
49+
`Common keychain provided does not match the keychain on wallet for ${params.source}`,
50+
);
51+
}
52+
} else {
53+
if (params.pubkey && params.pubkey !== signingKeychain.pub) {
54+
throw new Error(`Pub provided does not match the keychain on wallet for ${params.source}`);
55+
}
5256
}
5357

5458
return { baseCoin, wallet, signingKeychain };

0 commit comments

Comments
 (0)