Skip to content

Commit acef0ae

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

22 files changed

+4531
-17654
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: 0 additions & 10 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,8 +35,6 @@ describe('postIndependentKey', () => {
3935
allowSelfSigned: true,
4036
};
4137

42-
configStub = sinon.stub(configModule, 'initConfig').returns(cfg);
43-
4438
// app setup
4539
app = enclavedApp(cfg);
4640
agent = request.agent(app);
@@ -50,10 +44,6 @@ describe('postIndependentKey', () => {
5044
nock.cleanAll();
5145
});
5246

53-
after(() => {
54-
configStub.restore();
55-
});
56-
5747
// test cases
5848
it('should post an independent key successfully', async () => {
5949
const mockKmsResponse = {
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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 case
136+
it('should throw 400 Bad Request if failed to construct eth transaction from message hex', async () => {
137+
const input = {
138+
txHex: 'invalid-hex',
139+
pub: commonKeychain,
140+
};
141+
142+
// nocks for KMS responses
143+
nock(kmsUrl)
144+
.get(`/key/${input.pub}`)
145+
.query({ source: 'user', useLocalEncipherment: false })
146+
.reply(200, mockKmsUserResponse);
147+
nock(kmsUrl)
148+
.get(`/key/${input.pub}`)
149+
.query({ source: 'backup', useLocalEncipherment: false })
150+
.reply(200, mockKmsBackupResponse);
151+
152+
const signatureResponse = await agent
153+
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
154+
.set('Authorization', `Bearer ${accessToken}`)
155+
.send(input);
156+
157+
signatureResponse.status.should.equal(400);
158+
signatureResponse.body.should.have.property('error');
159+
signatureResponse.body.error.should.startWith(
160+
'Failed to construct eth transaction from message hex',
161+
);
162+
});
163+
});

0 commit comments

Comments
 (0)