Skip to content

Commit 1a4d80e

Browse files
committed
feat(ebe, mbe): added recovery to MPCv2
Ticket: WP-5168
1 parent ed73ab3 commit 1a4d80e

22 files changed

+12572
-6519
lines changed

package-lock.json

Lines changed: 520 additions & 71 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"@bitgo-beta/statics": "15.1.1-beta.1046",
102102
"@bitgo/wasm-miniscript": "2.0.0-beta.7",
103103
"@commitlint/config-conventional": "^19.8.1",
104+
"@ethereumjs/tx": "^3.3.0",
104105
"body-parser": "^1.20.3",
105106
"connect-timeout": "^1.9.0",
106107
"debug": "^3.1.0",

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ 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 * as configModule from '../../../initConfig';
11-
129
describe('postIndependentKey', () => {
1310
let cfg: EnclavedConfig;
1411
let app: express.Application;
@@ -20,7 +17,6 @@ describe('postIndependentKey', () => {
2017
const accessToken = 'test-token';
2118

2219
// sinon stubs
23-
let configStub: sinon.SinonStub;
2420

2521
before(() => {
2622
// nock config
@@ -39,7 +35,7 @@ describe('postIndependentKey', () => {
3935
allowSelfSigned: true,
4036
};
4137

42-
configStub = sinon.stub(configModule, 'initConfig').returns(cfg);
38+
// configStub = sinon.stub(configModule, 'initConfig').returns(cfg);
4339

4440
// app setup
4541
app = enclavedApp(cfg);
@@ -50,10 +46,6 @@ describe('postIndependentKey', () => {
5046
nock.cleanAll();
5147
});
5248

53-
after(() => {
54-
configStub.restore();
55-
});
56-
5749
// test cases
5850
it('should post an independent key successfully', async () => {
5951
const mockKmsResponse = {
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { AppMode, EnclavedConfig, TlsMode } from '../../../initConfig';
2+
import { app as enclavedApp } from '../../../enclavedApp';
3+
4+
import express from 'express';
5+
import nock from 'nock';
6+
import 'should';
7+
import * as request from 'supertest';
8+
import * as sinon from 'sinon';
9+
import * as configModule from '../../../initConfig';
10+
import { DklsTypes, DklsUtils } from '@bitgo-beta/sdk-lib-mpc';
11+
12+
describe('recoveryMpcV2', async () => {
13+
let cfg: EnclavedConfig;
14+
let app: express.Application;
15+
let agent: request.SuperAgentTest;
16+
17+
// test config
18+
const kmsUrl = 'http://kms.invalid';
19+
const ethLikeCoin = 'hteth';
20+
const cosmosLikeCoin = 'tsei';
21+
const nonEcdsaCoin = 'btc';
22+
const accessToken = 'test-token';
23+
24+
// sinon stubs
25+
let configStub: sinon.SinonStub;
26+
27+
// kms nocks setup
28+
const [userShare, backupShare] = await DklsUtils.generateDKGKeyShares();
29+
const userKeyShare = userShare.getKeyShare().toString('base64');
30+
const backupKeyShare = backupShare.getKeyShare().toString('base64');
31+
const commonKeychain = DklsTypes.getCommonKeychain(userShare.getKeyShare());
32+
33+
const mockKmsUserResponse = {
34+
prv: JSON.stringify(userKeyShare),
35+
pub: commonKeychain,
36+
source: 'user',
37+
type: 'tss',
38+
};
39+
40+
const mockKmsBackupResponse = {
41+
prv: JSON.stringify(backupKeyShare),
42+
pub: commonKeychain,
43+
source: 'backup',
44+
type: 'tss',
45+
};
46+
const input = {
47+
txHex:
48+
'02f6824268018502540be4008504a817c80083030d409443442e403d64d29c4f64065d0c1a0e8edc03d6c88801550f7dca700000823078c0',
49+
pub: commonKeychain,
50+
};
51+
52+
before(async () => {
53+
// nock config
54+
nock.disableNetConnect();
55+
nock.enableNetConnect('127.0.0.1');
56+
57+
// app config
58+
cfg = {
59+
appMode: AppMode.ENCLAVED,
60+
port: 0, // Let OS assign a free port
61+
bind: 'localhost',
62+
timeout: 60000,
63+
kmsUrl: kmsUrl,
64+
httpLoggerFile: '',
65+
tlsMode: TlsMode.DISABLED,
66+
allowSelfSigned: true,
67+
recoveryMode: true,
68+
};
69+
70+
configStub = sinon.stub(configModule, 'initConfig').returns(cfg);
71+
72+
// app setup
73+
app = enclavedApp(cfg);
74+
agent = request.agent(app);
75+
});
76+
77+
afterEach(() => {
78+
nock.cleanAll();
79+
});
80+
81+
after(() => {
82+
configStub.restore();
83+
});
84+
85+
// happy path test
86+
it('should be sign a Mpc V2 Recovery', async () => {
87+
// nocks for KMS responses
88+
const userKmsNock = nock(kmsUrl)
89+
.get(`/key/${input.pub}`)
90+
.query({ source: 'user', useLocalEncipherment: false })
91+
.reply(200, mockKmsUserResponse)
92+
.persist();
93+
const backupKmsNock = nock(kmsUrl)
94+
.get(`/key/${input.pub}`)
95+
.query({ source: 'backup', useLocalEncipherment: false })
96+
.reply(200, mockKmsBackupResponse)
97+
.persist();
98+
99+
const ethLikeSignatureResponse = await agent
100+
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
101+
.set('Authorization', `Bearer ${accessToken}`)
102+
.send(input);
103+
104+
ethLikeSignatureResponse.status.should.equal(200);
105+
ethLikeSignatureResponse.body.should.have.property('txHex');
106+
ethLikeSignatureResponse.body.txHex.should.equal(input.txHex);
107+
108+
ethLikeSignatureResponse.body.should.have.property('stringifiedSignature');
109+
const ethLikeSignature = JSON.parse(ethLikeSignatureResponse.body.stringifiedSignature);
110+
ethLikeSignature.should.have.property('recid');
111+
ethLikeSignature.should.have.property('r');
112+
ethLikeSignature.should.have.property('s');
113+
ethLikeSignature.should.have.property('y');
114+
115+
const cosmosLikeSignatureResponse = await agent
116+
.post(`/api/${cosmosLikeCoin}/mpcv2/recovery`)
117+
.set('Authorization', `Bearer ${accessToken}`)
118+
.send(input);
119+
120+
cosmosLikeSignatureResponse.status.should.equal(200);
121+
cosmosLikeSignatureResponse.body.should.have.property('txHex');
122+
cosmosLikeSignatureResponse.body.txHex.should.equal(input.txHex);
123+
124+
cosmosLikeSignatureResponse.body.should.have.property('stringifiedSignature');
125+
const cosmosLikeSignature = JSON.parse(cosmosLikeSignatureResponse.body.stringifiedSignature);
126+
cosmosLikeSignature.should.have.property('recid');
127+
cosmosLikeSignature.should.have.property('r');
128+
cosmosLikeSignature.should.have.property('s');
129+
cosmosLikeSignature.should.have.property('y');
130+
131+
userKmsNock.isDone().should.be.true();
132+
backupKmsNock.isDone().should.be.true();
133+
});
134+
135+
// failure test cases
136+
it('should throw 400 Bad Request if coin is not ECDSA', async () => {
137+
const input = {
138+
txHex:
139+
'02f6824268018502540be4008504a817c80083030d409443442e403d64d29c4f64065d0c1a0e8edc03d6c88801550f7dca700000823078c0',
140+
pub: commonKeychain,
141+
};
142+
143+
// non-ecdsa coin used in mpc v2 recovery
144+
const signatureResponse = await agent
145+
.post(`/api/${nonEcdsaCoin}/mpcv2/recovery`)
146+
.set('Authorization', `Bearer ${accessToken}`)
147+
.send(input);
148+
149+
signatureResponse.status.should.equal(400);
150+
signatureResponse.body.should.have.property('error');
151+
signatureResponse.body.error.should.equal(
152+
`Enclave does not support Mpc V2 recovery for coin family: ${nonEcdsaCoin}`,
153+
);
154+
});
155+
156+
it('should throw 400 Bad Request if failed to construct eth transaction from message hex', async () => {
157+
const input = {
158+
txHex: 'invalid-hex',
159+
pub: commonKeychain,
160+
};
161+
162+
// nocks for KMS responses
163+
nock(kmsUrl)
164+
.get(`/key/${input.pub}`)
165+
.query({ source: 'user', useLocalEncipherment: false })
166+
.reply(200, mockKmsUserResponse);
167+
nock(kmsUrl)
168+
.get(`/key/${input.pub}`)
169+
.query({ source: 'backup', useLocalEncipherment: false })
170+
.reply(200, mockKmsBackupResponse);
171+
172+
const signatureResponse = await agent
173+
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
174+
.set('Authorization', `Bearer ${accessToken}`)
175+
.send(input);
176+
177+
signatureResponse.status.should.equal(400);
178+
signatureResponse.body.should.have.property('error');
179+
signatureResponse.body.error.should.startWith(
180+
'Failed to construct eth transaction from message hex',
181+
);
182+
});
183+
});

0 commit comments

Comments
 (0)