Skip to content

Commit 9504b89

Browse files
committed
fix(ebe, mbe): fix error msg and test cases for keygen endpoints
TICKET: WP-5329
1 parent 188f192 commit 9504b89

File tree

9 files changed

+323
-31
lines changed

9 files changed

+323
-31
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import { app as enclavedApp } from '../../../enclavedApp';
66
import { AppMode, EnclavedConfig, TlsMode } from '../../../shared/types';
77
import express from 'express';
88

9+
import * as sinon from 'sinon';
10+
import coinFactory from '../../../shared/coinFactory';
11+
import { BaseCoin } from '@bitgo-beta/sdk-core';
12+
913
describe('postIndependentKey', () => {
1014
let cfg: EnclavedConfig;
1115
let app: express.Application;
@@ -76,4 +80,24 @@ describe('postIndependentKey', () => {
7680

7781
response.status.should.equal(400);
7882
});
83+
84+
it('should fail if there is an error in creating the public and private key pairs', async () => {
85+
const coinStub = sinon.stub(coinFactory, 'getCoin').returns(
86+
Promise.resolve({
87+
keychains: () => ({
88+
create: () => ({}),
89+
}),
90+
} as unknown as BaseCoin),
91+
);
92+
93+
const response = await agent
94+
.post(`/api/${coin}/key/independent`)
95+
.set('Authorization', `Bearer ${accessToken}`)
96+
.send({ source: 'user' });
97+
98+
response.status.should.equal(500);
99+
response.body.should.have.property('details', 'BitGo SDK failed to create public key');
100+
101+
coinStub.restore();
102+
});
79103
});

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

Lines changed: 263 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,255 @@ 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('details', 'Round mismatch: expected 2, got 3');
538+
539+
// repeating round 1
540+
const repeatRoundResponse = await agent
541+
.post(`/api/${coin}/mpcv2/round`)
542+
.set('Authorization', `Bearer ${accessToken}`)
543+
.send({
544+
source: 'user',
545+
encryptedData: userRound1Response.body.encryptedData,
546+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
547+
round: 1,
548+
bitgoGpgPub: userRound1Response.body.gpgPub,
549+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
550+
});
551+
552+
repeatRoundResponse.status.should.equal(422);
553+
repeatRoundResponse.body.should.have.property('details', 'Round mismatch: expected 2, got 1');
554+
});
555+
556+
it('should throw error if broadcastMessages or p2pMessages does not contain all required messages', async () => {
557+
const userInitResponse = await agent
558+
.post(`/api/${coin}/mpcv2/initialize`)
559+
.set('Authorization', `Bearer ${accessToken}`)
560+
.send({ source: 'user' });
561+
562+
const backupInitResponse = await agent
563+
.post(`/api/${coin}/mpcv2/initialize`)
564+
.set('Authorization', `Bearer ${accessToken}`)
565+
.send({ source: 'backup' });
566+
567+
// round 1
568+
const userRound1Response = await agent
569+
.post(`/api/${coin}/mpcv2/round`)
570+
.set('Authorization', `Bearer ${accessToken}`)
571+
.send({
572+
source: 'user',
573+
encryptedData: userInitResponse.body.encryptedData,
574+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
575+
round: 1,
576+
bitgoGpgPub: userInitResponse.body.gpgPub,
577+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
578+
});
579+
580+
// round 2 with missing broadcastMessages
581+
const response = await agent
582+
.post(`/api/${coin}/mpcv2/round`)
583+
.set('Authorization', `Bearer ${accessToken}`)
584+
.send({
585+
source: 'user',
586+
encryptedData: userInitResponse.body.encryptedData,
587+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
588+
round: 2,
589+
});
590+
591+
response.status.should.equal(400);
592+
response.body.should.have.property(
593+
'details',
594+
'At least one of broadcastMessages or p2pMessages must be provided',
595+
);
596+
597+
// round 2 with missing p2pMessages
598+
const response2 = await agent
599+
.post(`/api/${coin}/mpcv2/round`)
600+
.set('Authorization', `Bearer ${accessToken}`)
601+
.send({
602+
source: 'user',
603+
encryptedData: userRound1Response.body.encryptedData,
604+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
605+
round: 2,
606+
p2pMessages: {},
607+
});
608+
609+
response2.status.should.equal(400);
610+
response2.body.should.have.property(
611+
'details',
612+
'p2pMessages did not contain all required messages',
613+
);
614+
615+
// round 2 with missing broadcastMessages
616+
const response3 = await agent
617+
.post(`/api/${coin}/mpcv2/round`)
618+
.set('Authorization', `Bearer ${accessToken}`)
619+
.send({
620+
source: 'user',
621+
encryptedData: userRound1Response.body.encryptedData,
622+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
623+
round: 2,
624+
broadcastMessages: {},
625+
});
626+
627+
response3.status.should.equal(400);
628+
response3.body.should.have.property(
629+
'details',
630+
'broadcastMessages did not contain all required messages',
631+
);
632+
});
633+
634+
it('should throw error if counterPartyGpgPub does not match expected value', async () => {
635+
const userInitResponse = await agent
636+
.post(`/api/${coin}/mpcv2/initialize`)
637+
.set('Authorization', `Bearer ${accessToken}`)
638+
.send({ source: 'user' });
639+
640+
const backupInitResponse = await agent
641+
.post(`/api/${coin}/mpcv2/initialize`)
642+
.set('Authorization', `Bearer ${accessToken}`)
643+
.send({ source: 'backup' });
644+
645+
// round 1
646+
const userRound1Response = await agent
647+
.post(`/api/${coin}/mpcv2/round`)
648+
.set('Authorization', `Bearer ${accessToken}`)
649+
.send({
650+
source: 'user',
651+
encryptedData: userInitResponse.body.encryptedData,
652+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
653+
round: 1,
654+
bitgoGpgPub: userInitResponse.body.gpgPub,
655+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
656+
});
657+
658+
// round 2 with incorrect counterPartyGpgPub
659+
const response = await agent
660+
.post(`/api/${coin}/mpcv2/round`)
661+
.set('Authorization', `Bearer ${accessToken}`)
662+
.send({
663+
source: 'user',
664+
encryptedData: userRound1Response.body.encryptedData,
665+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
666+
round: 2,
667+
bitgoGpgPub: userRound1Response.body.gpgPub,
668+
counterPartyGpgPub: 'incorrect-gpg-pub',
669+
broadcastMessages: {
670+
bitgo: {},
671+
counterParty: {},
672+
},
673+
});
674+
675+
response.status.should.equal(422);
676+
response.body.should.have.property(
677+
'details',
678+
`Counterparty GPG public key mismatch: expected ${backupInitResponse.body.gpgPub}, got incorrect-gpg-pub`,
679+
);
680+
});
681+
682+
it('should throw error if encrypted state misses the required messages', async () => {
683+
const userInitResponse = await agent
684+
.post(`/api/${coin}/mpcv2/initialize`)
685+
.set('Authorization', `Bearer ${accessToken}`)
686+
.send({ source: 'user' });
687+
688+
const backupInitResponse = await agent
689+
.post(`/api/${coin}/mpcv2/initialize`)
690+
.set('Authorization', `Bearer ${accessToken}`)
691+
.send({ source: 'backup' });
692+
693+
// round 1
694+
const userRound1Response = await agent
695+
.post(`/api/${coin}/mpcv2/round`)
696+
.set('Authorization', `Bearer ${accessToken}`)
697+
.send({
698+
source: 'user',
699+
encryptedData: userInitResponse.body.encryptedData,
700+
encryptedDataKey: userInitResponse.body.encryptedDataKey,
701+
round: 1,
702+
bitgoGpgPub: userInitResponse.body.gpgPub,
703+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
704+
});
705+
706+
// round 2 with missing p2pMessages
707+
const response = await agent
708+
.post(`/api/${coin}/mpcv2/round`)
709+
.set('Authorization', `Bearer ${accessToken}`)
710+
.send({
711+
source: 'user',
712+
encryptedData: '',
713+
encryptedDataKey: userRound1Response.body.encryptedDataKey,
714+
round: 2,
715+
bitgoGpgPub: userRound1Response.body.gpgPub,
716+
counterPartyGpgPub: backupInitResponse.body.gpgPub,
717+
p2pMessages: {},
718+
});
719+
720+
response.status.should.equal(400);
721+
response.body.should.have.property(
722+
'details',
723+
'p2pMessages did not contain all required messages',
724+
);
725+
});
473726
});

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 coin does not support TSS', async () => {
1179+
const response = await agent
1180+
.post(`/api/tbtc/wallet/generate`)
1181+
.set('Authorization', `Bearer ${accessToken}`)
1182+
.send({
1183+
label: 'test_wallet',
1184+
enterprise: 'test_enterprise',
1185+
multisigType: 'tss',
1186+
});
1187+
1188+
response.status.should.equal(400);
1189+
response.body.details.should.equal('MPC wallet generation is not supported for coin tbtc');
1190+
});
11771191
});

0 commit comments

Comments
 (0)