Skip to content

Commit e7b9900

Browse files
committed
feat(ebe, mbe): add mpcv2 recovery support for eth-like coins
Ticket: WP-5168
1 parent a67ab71 commit e7b9900

24 files changed

Lines changed: 7137 additions & 4477 deletions

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,19 @@
2626
"@api-ts/superagent-wrapper": "^1.3.3",
2727
"@api-ts/typed-express-router": "^1.1.13",
2828
"@bitgo-beta/sdk-lib-mpc": "8.2.1-alpha.291",
29+
"@bitgo/abstract-cosmos": "^11.9.7",
30+
"@bitgo/abstract-eth": "^24.8.0",
2931
"@bitgo/abstract-utxo": "^9.21.4",
3032
"@bitgo/sdk-coin-ada": "^4.11.5",
33+
"@bitgo/sdk-coin-atom": "^13.5.7",
3134
"@bitgo/sdk-coin-dot": "^4.3.5",
3235
"@bitgo/sdk-coin-near": "^2.7.0",
3336
"@bitgo/sdk-coin-sol": "^4.12.5",
3437
"@bitgo/sdk-coin-sui": "^5.15.5",
3538
"@bitgo/sdk-core": "^35.3.0",
3639
"@bitgo/statics": "^54.6.0",
3740
"@commitlint/config-conventional": "^19.8.1",
41+
"@ethereumjs/tx": "^3.3.0",
3842
"bitgo": "^48.1.0",
3943
"body-parser": "^1.20.3",
4044
"connect-timeout": "^1.9.0",
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
// atom, cordao, sei, thor, : integration test and unit test, refactor unit test for repeated code use TODO
13+
14+
describe('recoveryMpcV2', async () => {
15+
let cfg: EnclavedConfig;
16+
let app: express.Application;
17+
let agent: request.SuperAgentTest;
18+
19+
// test config
20+
const kmsUrl = 'http://kms.invalid';
21+
const ethLikeCoin = 'hteth';
22+
const nonEcdsaCoin = 'btc';
23+
const notImplementedCoin = '';
24+
const accessToken = 'test-token';
25+
26+
// sinon stubs
27+
let configStub: sinon.SinonStub;
28+
29+
// kms nocks setup
30+
const [userShare, backupShare] = await DklsUtils.generateDKGKeyShares();
31+
const userKeyShare = userShare.getKeyShare().toString('base64');
32+
const backupKeyShare = backupShare.getKeyShare().toString('base64');
33+
const commonKeychain = DklsTypes.getCommonKeychain(userShare.getKeyShare());
34+
35+
const mockKmsUserResponse = {
36+
prv: JSON.stringify(userKeyShare),
37+
pub: commonKeychain,
38+
source: 'user',
39+
type: 'tss',
40+
};
41+
42+
const mockKmsBackupResponse = {
43+
prv: JSON.stringify(backupKeyShare),
44+
pub: commonKeychain,
45+
source: 'backup',
46+
type: 'tss',
47+
};
48+
49+
before(async () => {
50+
// nock config
51+
nock.disableNetConnect();
52+
nock.enableNetConnect('127.0.0.1');
53+
54+
// app config
55+
cfg = {
56+
appMode: AppMode.ENCLAVED,
57+
port: 0, // Let OS assign a free port
58+
bind: 'localhost',
59+
timeout: 60000,
60+
logFile: '',
61+
kmsUrl: kmsUrl,
62+
tlsMode: TlsMode.DISABLED,
63+
mtlsRequestCert: false,
64+
allowSelfSigned: true,
65+
};
66+
67+
configStub = sinon.stub(configModule, 'initConfig').returns(cfg);
68+
69+
// app setup
70+
app = enclavedApp(cfg);
71+
agent = request.agent(app);
72+
});
73+
74+
afterEach(() => {
75+
nock.cleanAll();
76+
});
77+
78+
after(() => {
79+
configStub.restore();
80+
});
81+
82+
// happy path test
83+
it('should be sign a Mpc V2 Recovery', async () => {
84+
const input = {
85+
txHex:
86+
'02f6824268018502540be4008504a817c80083030d409443442e403d64d29c4f64065d0c1a0e8edc03d6c88801550f7dca700000823078c0',
87+
pub: commonKeychain,
88+
};
89+
// nocks for KMS responses
90+
nock(kmsUrl)
91+
.get(`/key/${input.pub}`)
92+
.query({ source: 'user', useLocalEncipherment: false })
93+
.reply(200, mockKmsUserResponse);
94+
nock(kmsUrl)
95+
.get(`/key/${input.pub}`)
96+
.query({ source: 'backup', useLocalEncipherment: false })
97+
.reply(200, mockKmsBackupResponse);
98+
99+
const signatureResponse = await agent
100+
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
101+
.set('Authorization', `Bearer ${accessToken}`)
102+
.send(input);
103+
104+
signatureResponse.status.should.equal(200);
105+
signatureResponse.body.should.have.property('txHex');
106+
signatureResponse.body.txHex.should.equal(input.txHex);
107+
108+
signatureResponse.body.should.have.property('stringifiedSignature');
109+
const signature = JSON.parse(signatureResponse.body.stringifiedSignature);
110+
signature.should.have.property('recid');
111+
signature.should.have.property('r');
112+
signature.should.have.property('s');
113+
signature.should.have.property('y');
114+
});
115+
116+
// failure test cases
117+
it('should throw 400 Bad Request if coin is not ECDSA', async () => {
118+
const input = {
119+
txHex:
120+
'02f6824268018502540be4008504a817c80083030d409443442e403d64d29c4f64065d0c1a0e8edc03d6c88801550f7dca700000823078c0',
121+
pub: commonKeychain,
122+
};
123+
124+
// non-ecdsa coin used in mpc v2 recovery
125+
const signatureResponse = await agent
126+
.post(`/api/${nonEcdsaCoin}/mpcv2/recovery`)
127+
.set('Authorization', `Bearer ${accessToken}`)
128+
.send(input);
129+
130+
signatureResponse.status.should.equal(400);
131+
signatureResponse.body.should.have.property('error');
132+
signatureResponse.body.error.should.equal(`Enclave does not support Mpc V2 recovery for coin family: ${nonEcdsaCoin}`);
133+
})
134+
135+
it('should throw 501 Not Implemented if coin is not implemented', async () => {
136+
const input = {
137+
txHex:
138+
'02f6824268018502540be4008504a817c80083030d409443442e403d64d29c4f64065d0c1a0e8edc03d6c88801550f7dca700000823078c0',
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+
// not implemented coin used in mpc v2 recovery
153+
const signatureResponse = await agent
154+
.post(`/api/${notImplementedCoin}/mpcv2/recovery`)
155+
.set('Authorization', `Bearer ${accessToken}`)
156+
.send(input);
157+
158+
signatureResponse.status.should.equal(501);
159+
signatureResponse.body.should.have.property('error');
160+
signatureResponse.body.error.should.equal(`Enclave does not support Mpc V2 recovery for coin family: ${notImplementedCoin}`);
161+
});
162+
163+
it('should throw 400 Bad Request if failed to construct eth transaction from message hex', async () => {
164+
const input = {
165+
txHex: 'invalid-hex',
166+
pub: commonKeychain,
167+
};
168+
169+
// nocks for KMS responses
170+
nock(kmsUrl)
171+
.get(`/key/${input.pub}`)
172+
.query({ source: 'user', useLocalEncipherment: false })
173+
.reply(200, mockKmsUserResponse);
174+
nock(kmsUrl)
175+
.get(`/key/${input.pub}`)
176+
.query({ source: 'backup', useLocalEncipherment: false })
177+
.reply(200, mockKmsBackupResponse);
178+
179+
const signatureResponse = await agent
180+
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
181+
.set('Authorization', `Bearer ${accessToken}`)
182+
.send(input);
183+
184+
signatureResponse.status.should.equal(400);
185+
signatureResponse.body.should.have.property('error');
186+
signatureResponse.body.error.should.startWith('Failed to construct eth transaction from message hex');
187+
});
188+
});

0 commit comments

Comments
 (0)