Skip to content

Commit 1df85e1

Browse files
committed
fix (ebe, mbe): added better error messages and test cases for keygen endpoints
TICKET: WP-5329
1 parent b13fafc commit 1df85e1

File tree

9 files changed

+323
-29
lines changed

9 files changed

+323
-29
lines changed

src/__tests__/api/enclaved/postIndependentKey.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import express from 'express';
88

99
import * as sinon from 'sinon';
1010
import * as configModule from '../../../initConfig';
11+
import coinFactory from '../../../shared/coinFactory';
12+
import { resolve } from 'path';
13+
import { BaseCoin } from '@bitgo-beta/sdk-core';
1114

1215
describe('postIndependentKey', () => {
1316
let cfg: EnclavedConfig;
@@ -86,4 +89,20 @@ describe('postIndependentKey', () => {
8689

8790
response.status.should.equal(400);
8891
});
92+
93+
it('should fail if there is an error in creating the public and private key pairs', async () => {
94+
sinon.stub(coinFactory, 'getCoin').returns(Promise.resolve({
95+
keychains: () => ({
96+
create: () => ({}),
97+
})
98+
} as unknown as BaseCoin));
99+
100+
const response = await agent
101+
.post(`/api/${coin}/key/independent`)
102+
.set('Authorization', `Bearer ${accessToken}`)
103+
.send({ source: 'user' });
104+
105+
response.status.should.equal(500);
106+
response.body.should.have.property('details', 'BitGo SDK failed to create public key');
107+
});
89108
});

src/__tests__/api/enclaved/postMpcV2Key.test.ts

Lines changed: 269 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('postMpcV2Key', () => {
1818

1919
// test config
2020
const kmsUrl = 'http://kms.invalid';
21-
const coin = 'tsol';
21+
const coin = 'hteth';
2222
const accessToken = 'test-token';
2323

2424
// sinon stubs
@@ -48,15 +48,7 @@ describe('postMpcV2Key', () => {
4848
agent = request.agent(app);
4949
});
5050

51-
afterEach(() => {
52-
nock.cleanAll();
53-
});
54-
55-
after(() => {
56-
configStub.restore();
57-
});
58-
59-
it('should be able to create a new MPC V2 wallet', async () => {
51+
beforeEach(() => {
6052
// nocks for KMS responses
6153
nock(kmsUrl)
6254
.post(`/generateDataKey`)
@@ -93,7 +85,17 @@ describe('postMpcV2Key', () => {
9385
.persist();
9486

9587
nock(kmsUrl).post(`/postKey`).reply(200, {}).persist();
88+
});
9689

90+
afterEach(() => {
91+
nock.cleanAll();
92+
});
93+
94+
after(() => {
95+
configStub.restore();
96+
});
97+
98+
it('should be able to create a new MPC V2 wallet', async () => {
9799
// mocking bitgo's GPG key generation session
98100
const bitgoGpgKey = await bitgoSdk.generateGPGKeyPair('secp256k1');
99101
const bitgoGpgPub = {
@@ -470,4 +472,261 @@ describe('postMpcV2Key', () => {
470472
);
471473
userFinalizeResponse.body.commonKeychain.should.equal(bitgoCommonKeychain);
472474
});
475+
476+
it('should throw an error if round number is invalid', async () => {
477+
const response = await agent
478+
.post(`/api/${coin}/mpcv2/round`)
479+
.set('Authorization', `Bearer ${accessToken}`)
480+
.send({
481+
source: 'user',
482+
encryptedData: '',
483+
encryptedDataKey: '',
484+
round: 5,
485+
p2pMessages: {
486+
bitgo: {},
487+
counterParty: {},
488+
},
489+
});
490+
491+
response.status.should.equal(400);
492+
response.body.should.have.property('details', 'Round must be between 1 and 4');
493+
});
494+
495+
it('should throw error if round number is not sequential', async () => {
496+
const userInitResponse = await agent
497+
.post(`/api/${coin}/mpcv2/initialize`)
498+
.set('Authorization', `Bearer ${accessToken}`)
499+
.send({ source: 'user' });
500+
501+
const backupInitResponse = await agent
502+
.post(`/api/${coin}/mpcv2/initialize`)
503+
.set('Authorization', `Bearer ${accessToken}`)
504+
.send({ source: 'backup' });
505+
506+
// round 1
507+
const userRound1Response = await agent
508+
.post(`/api/${coin}/mpcv2/round`)
509+
.set('Authorization', `Bearer ${accessToken}`)
510+
.send({
511+
source: 'user',
512+
encryptedData: userInitResponse.body.encryptedData,
513+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
514+
round: 1,
515+
bitgoGpgPub: userInitResponse.body.gpgPub,
516+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
517+
});
518+
519+
// round 3 without round 2
520+
const skipRoundResponse = await agent
521+
.post(`/api/${coin}/mpcv2/round`)
522+
.set('Authorization', `Bearer ${accessToken}`)
523+
.send({
524+
source: 'user',
525+
encryptedData: userRound1Response.body.encryptedData,
526+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
527+
round: 3,
528+
bitgoGpgPub: userRound1Response.body.gpgPub,
529+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
530+
p2pMessages: {
531+
bitgo: {},
532+
counterParty: {},
533+
},
534+
});
535+
536+
skipRoundResponse.status.should.equal(422);
537+
skipRoundResponse.body.should.have.property(
538+
'details',
539+
'Round mismatch: expected 2, got 3',
540+
);
541+
542+
// repeating round 1
543+
const repeatRoundResponse = await agent
544+
.post(`/api/${coin}/mpcv2/round`)
545+
.set('Authorization', `Bearer ${accessToken}`)
546+
.send({
547+
source: 'user',
548+
encryptedData: userRound1Response.body.encryptedData,
549+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
550+
round: 1,
551+
bitgoGpgPub: userRound1Response.body.gpgPub,
552+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
553+
});
554+
555+
repeatRoundResponse.status.should.equal(422);
556+
repeatRoundResponse.body.should.have.property(
557+
'details',
558+
'Round mismatch: expected 2, got 1',
559+
);
560+
});
561+
562+
it('should throw error if broadcastMessages or p2pMessages does not contain all required messages', async () => {
563+
const userInitResponse = await agent
564+
.post(`/api/${coin}/mpcv2/initialize`)
565+
.set('Authorization', `Bearer ${accessToken}`)
566+
.send({ source: 'user' });
567+
568+
const backupInitResponse = await agent
569+
.post(`/api/${coin}/mpcv2/initialize`)
570+
.set('Authorization', `Bearer ${accessToken}`)
571+
.send({ source: 'backup' });
572+
573+
// round 1
574+
const userRound1Response = await agent
575+
.post(`/api/${coin}/mpcv2/round`)
576+
.set('Authorization', `Bearer ${accessToken}`)
577+
.send({
578+
source: 'user',
579+
encryptedData: userInitResponse.body.encryptedData,
580+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
581+
round: 1,
582+
bitgoGpgPub: userInitResponse.body.gpgPub,
583+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
584+
});
585+
586+
// round 2 with missing broadcastMessages
587+
const response = await agent
588+
.post(`/api/${coin}/mpcv2/round`)
589+
.set('Authorization', `Bearer ${accessToken}`)
590+
.send({
591+
source: 'user',
592+
encryptedData: userInitResponse.body.encryptedData,
593+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
594+
round: 2,
595+
});
596+
597+
response.status.should.equal(400);
598+
response.body.should.have.property(
599+
'details',
600+
'At least one of broadcastMessages or p2pMessages must be provided',
601+
);
602+
603+
// round 2 with missing p2pMessages
604+
const response2 = await agent
605+
.post(`/api/${coin}/mpcv2/round`)
606+
.set('Authorization', `Bearer ${accessToken}`)
607+
.send({
608+
source: 'user',
609+
encryptedData: userRound1Response.body.encryptedData,
610+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
611+
round: 2,
612+
p2pMessages: {},
613+
});
614+
615+
response2.status.should.equal(400);
616+
response2.body.should.have.property(
617+
'details',
618+
'p2pMessages did not contain all required messages',
619+
);
620+
621+
// round 2 with missing broadcastMessages
622+
const response3 = await agent
623+
.post(`/api/${coin}/mpcv2/round`)
624+
.set('Authorization', `Bearer ${accessToken}`)
625+
.send({
626+
source: 'user',
627+
encryptedData: userRound1Response.body.encryptedData,
628+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
629+
round: 2,
630+
broadcastMessages: {},
631+
});
632+
633+
response3.status.should.equal(400);
634+
response3.body.should.have.property(
635+
'details',
636+
'broadcastMessages did not contain all required messages',
637+
);
638+
});
639+
640+
it('should throw error if counterPartyGpgPub does not match expected value', async () => {
641+
const userInitResponse = await agent
642+
.post(`/api/${coin}/mpcv2/initialize`)
643+
.set('Authorization', `Bearer ${accessToken}`)
644+
.send({ source: 'user' });
645+
646+
const backupInitResponse = await agent
647+
.post(`/api/${coin}/mpcv2/initialize`)
648+
.set('Authorization', `Bearer ${accessToken}`)
649+
.send({ source: 'backup' });
650+
651+
// round 1
652+
const userRound1Response = await agent
653+
.post(`/api/${coin}/mpcv2/round`)
654+
.set('Authorization', `Bearer ${accessToken}`)
655+
.send({
656+
source: 'user',
657+
encryptedData: userInitResponse.body.encryptedData,
658+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
659+
round: 1,
660+
bitgoGpgPub: userInitResponse.body.gpgPub,
661+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
662+
});
663+
664+
// round 2 with incorrect counterPartyGpgPub
665+
const response = await agent
666+
.post(`/api/${coin}/mpcv2/round`)
667+
.set('Authorization', `Bearer ${accessToken}`)
668+
.send({
669+
source: 'user',
670+
encryptedData: userRound1Response.body.encryptedData,
671+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
672+
round: 2,
673+
bitgoGpgPub: userRound1Response.body.gpgPub,
674+
counterPartyGpgPub: 'incorrect-gpg-pub',
675+
broadcastMessages: {
676+
bitgo: {},
677+
counterParty: {},
678+
},
679+
});
680+
681+
response.status.should.equal(422);
682+
response.body.should.have.property(
683+
'details',
684+
`Counterparty GPG public key mismatch: expected ${backupInitResponse.body.gpgPub}, got incorrect-gpg-pub`,
685+
);
686+
});
687+
688+
it('should throw error if encrypted state misses the required messages', async () => {
689+
const userInitResponse = await agent
690+
.post(`/api/${coin}/mpcv2/initialize`)
691+
.set('Authorization', `Bearer ${accessToken}`)
692+
.send({ source: 'user' });
693+
694+
const backupInitResponse = await agent
695+
.post(`/api/${coin}/mpcv2/initialize`)
696+
.set('Authorization', `Bearer ${accessToken}`)
697+
.send({ source: 'backup' });
698+
699+
// round 1
700+
const userRound1Response = await agent
701+
.post(`/api/${coin}/mpcv2/round`)
702+
.set('Authorization', `Bearer ${accessToken}`)
703+
.send({
704+
source: 'user',
705+
encryptedData: userInitResponse.body.encryptedData,
706+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
707+
round: 1,
708+
bitgoGpgPub: userInitResponse.body.gpgPub,
709+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
710+
});
711+
712+
// round 2 with missing p2pMessages
713+
const response = await agent
714+
.post(`/api/${coin}/mpcv2/round`)
715+
.set('Authorization', `Bearer ${accessToken}`)
716+
.send({
717+
source: 'user',
718+
encryptedData: '',
719+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
720+
round: 2,
721+
bitgoGpgPub: userRound1Response.body.gpgPub,
722+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
723+
p2pMessages: {},
724+
});
725+
726+
response.status.should.equal(400);
727+
response.body.should.have.property(
728+
'details',
729+
'p2pMessages did not contain all required messages',
730+
);
731+
});
473732
});

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,4 +1174,18 @@ describe('POST /api/:coin/wallet/generate', () => {
11741174

11751175
response2.status.should.equal(400);
11761176
});
1177+
1178+
it('should fail when enterprise input is undefined', async () => {
1179+
const response = await agent
1180+
.post(`/api/${coin}/wallet/generate`)
1181+
.set('Authorization', `Bearer ${accessToken}`)
1182+
.send({
1183+
label: 'test_wallet',
1184+
enterprise: 123,
1185+
multisigType: 'tss',
1186+
});
1187+
1188+
response.status.should.equal(400);
1189+
response.body.error.should.equal('Invalid enterprise');
1190+
});
11771191
});

0 commit comments

Comments
 (0)