Skip to content

Commit 0759b97

Browse files
authored
Merge pull request #71 from BitGo/WP-5168
feat(ebe, mbe): added recovery to MPCv2
2 parents b13fafc + d97ad1f commit 0759b97

File tree

18 files changed

+4507
-142
lines changed

18 files changed

+4507
-142
lines changed

masterBitgoExpress.json

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -462,9 +462,6 @@
462462
"ledgerSequenceDelta": {
463463
"type": "number"
464464
},
465-
"gasPrice": {
466-
"type": "number"
467-
},
468465
"noSplitChange": {
469466
"type": "boolean"
470467
},
@@ -762,6 +759,36 @@
762759
"coinSpecificParams": {
763760
"type": "object",
764761
"properties": {
762+
"ecdsaCosmosLikeRecoverySpecificParams": {
763+
"type": "object",
764+
"properties": {
765+
"rootAddress": {
766+
"type": "string"
767+
}
768+
},
769+
"required": [
770+
"rootAddress"
771+
]
772+
},
773+
"ecdsaEthLikeRecoverySpecificParams": {
774+
"type": "object",
775+
"properties": {
776+
"apiKey": {
777+
"type": "string"
778+
},
779+
"bitgoDestinationAddress": {
780+
"type": "string"
781+
},
782+
"walletContractAddress": {
783+
"type": "string"
784+
}
785+
},
786+
"required": [
787+
"apiKey",
788+
"bitgoDestinationAddress",
789+
"walletContractAddress"
790+
]
791+
},
765792
"evmRecoveryOptions": {
766793
"type": "object",
767794
"properties": {

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: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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 accessToken = 'test-token';
22+
23+
// sinon stubs
24+
let configStub: sinon.SinonStub;
25+
26+
// kms nocks setup
27+
const [userShare, backupShare] = await DklsUtils.generateDKGKeyShares();
28+
const userKeyShare = userShare.getKeyShare().toString('base64');
29+
const backupKeyShare = backupShare.getKeyShare().toString('base64');
30+
const commonKeychain = DklsTypes.getCommonKeychain(userShare.getKeyShare());
31+
32+
const mockKmsUserResponse = {
33+
prv: JSON.stringify(userKeyShare),
34+
pub: commonKeychain,
35+
source: 'user',
36+
type: 'tss',
37+
};
38+
39+
const mockKmsBackupResponse = {
40+
prv: JSON.stringify(backupKeyShare),
41+
pub: commonKeychain,
42+
source: 'backup',
43+
type: 'tss',
44+
};
45+
const input = {
46+
txHex:
47+
'02f6824268018502540be4008504a817c80083030d409443442e403d64d29c4f64065d0c1a0e8edc03d6c88801550f7dca700000823078c0',
48+
pub: commonKeychain,
49+
};
50+
51+
before(async () => {
52+
// nock config
53+
nock.disableNetConnect();
54+
nock.enableNetConnect('127.0.0.1');
55+
56+
// app config
57+
cfg = {
58+
appMode: AppMode.ENCLAVED,
59+
port: 0, // Let OS assign a free port
60+
bind: 'localhost',
61+
timeout: 60000,
62+
kmsUrl: kmsUrl,
63+
httpLoggerFile: '',
64+
tlsMode: TlsMode.DISABLED,
65+
allowSelfSigned: true,
66+
recoveryMode: true,
67+
};
68+
69+
configStub = sinon.stub(configModule, 'initConfig').returns(cfg);
70+
71+
// app setup
72+
app = enclavedApp(cfg);
73+
agent = request.agent(app);
74+
});
75+
76+
afterEach(() => {
77+
nock.cleanAll();
78+
});
79+
80+
after(() => {
81+
configStub.restore();
82+
});
83+
84+
// happy path test
85+
it('should be sign a Mpc V2 Recovery', async () => {
86+
// nocks for KMS responses
87+
const userKmsNock = nock(kmsUrl)
88+
.get(`/key/${input.pub}`)
89+
.query({ source: 'user', useLocalEncipherment: false })
90+
.reply(200, mockKmsUserResponse)
91+
.persist();
92+
const backupKmsNock = nock(kmsUrl)
93+
.get(`/key/${input.pub}`)
94+
.query({ source: 'backup', useLocalEncipherment: false })
95+
.reply(200, mockKmsBackupResponse)
96+
.persist();
97+
98+
const ethLikeSignatureResponse = await agent
99+
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
100+
.set('Authorization', `Bearer ${accessToken}`)
101+
.send(input);
102+
103+
ethLikeSignatureResponse.status.should.equal(200);
104+
ethLikeSignatureResponse.body.should.have.property('txHex');
105+
ethLikeSignatureResponse.body.txHex.should.equal(input.txHex);
106+
107+
ethLikeSignatureResponse.body.should.have.property('stringifiedSignature');
108+
const ethLikeSignature = JSON.parse(ethLikeSignatureResponse.body.stringifiedSignature);
109+
ethLikeSignature.should.have.property('recid');
110+
ethLikeSignature.should.have.property('r');
111+
ethLikeSignature.should.have.property('s');
112+
ethLikeSignature.should.have.property('y');
113+
114+
const cosmosLikeSignatureResponse = await agent
115+
.post(`/api/${cosmosLikeCoin}/mpcv2/recovery`)
116+
.set('Authorization', `Bearer ${accessToken}`)
117+
.send(input);
118+
119+
cosmosLikeSignatureResponse.status.should.equal(200);
120+
cosmosLikeSignatureResponse.body.should.have.property('txHex');
121+
cosmosLikeSignatureResponse.body.txHex.should.equal(input.txHex);
122+
123+
cosmosLikeSignatureResponse.body.should.have.property('stringifiedSignature');
124+
const cosmosLikeSignature = JSON.parse(cosmosLikeSignatureResponse.body.stringifiedSignature);
125+
cosmosLikeSignature.should.have.property('recid');
126+
cosmosLikeSignature.should.have.property('r');
127+
cosmosLikeSignature.should.have.property('s');
128+
cosmosLikeSignature.should.have.property('y');
129+
130+
userKmsNock.isDone().should.be.true();
131+
backupKmsNock.isDone().should.be.true();
132+
});
133+
134+
// failure test case
135+
it('should throw 400 Bad Request if failed to construct eth transaction from message hex', async () => {
136+
const input = {
137+
txHex: 'invalid-hex',
138+
pub: commonKeychain,
139+
};
140+
141+
// nocks for KMS responses
142+
nock(kmsUrl)
143+
.get(`/key/${input.pub}`)
144+
.query({ source: 'user', useLocalEncipherment: false })
145+
.reply(200, mockKmsUserResponse);
146+
nock(kmsUrl)
147+
.get(`/key/${input.pub}`)
148+
.query({ source: 'backup', useLocalEncipherment: false })
149+
.reply(200, mockKmsBackupResponse);
150+
151+
const signatureResponse = await agent
152+
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
153+
.set('Authorization', `Bearer ${accessToken}`)
154+
.send(input);
155+
156+
signatureResponse.status.should.equal(400);
157+
signatureResponse.body.should.have.property('error');
158+
signatureResponse.body.error.should.startWith(
159+
'Failed to construct eth transaction from message hex',
160+
);
161+
});
162+
});

0 commit comments

Comments
 (0)