Skip to content

Commit f2eed03

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

24 files changed

+4097
-2864
lines changed

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.1033",
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 & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,6 @@ describe('postIndependentKey', () => {
6565

6666
const kmsNock = nock(kmsUrl).post(`/key`).reply(200, mockKmsResponse);
6767

68-
console.log(cfg.kmsUrl);
69-
70-
console.warn(nock.activeMocks());
71-
console.warn(nock.isActive());
72-
7368
const response = await agent
7469
.post(`/api/${coin}/key/independent`)
7570
.set('Authorization', `Bearer ${accessToken}`)
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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+
};
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 cases
135+
// it('should throw 400 Bad Request if coin is not ECDSA', async () => {
136+
// const input = {
137+
// txHex:
138+
// '02f6824268018502540be4008504a817c80083030d409443442e403d64d29c4f64065d0c1a0e8edc03d6c88801550f7dca700000823078c0',
139+
// pub: commonKeychain,
140+
// };
141+
142+
// // non-ecdsa coin used in mpc v2 recovery
143+
// const signatureResponse = await agent
144+
// .post(`/api/${nonEcdsaCoin}/mpcv2/recovery`)
145+
// .set('Authorization', `Bearer ${accessToken}`)
146+
// .send(input);
147+
148+
// signatureResponse.status.should.equal(400);
149+
// signatureResponse.body.should.have.property('error');
150+
// signatureResponse.body.error.should.equal(
151+
// `Enclave does not support Mpc V2 recovery for coin family: ${nonEcdsaCoin}`,
152+
// );
153+
// });
154+
155+
// it('should throw 400 Bad Request if failed to construct eth transaction from message hex', async () => {
156+
// const input = {
157+
// txHex: 'invalid-hex',
158+
// pub: commonKeychain,
159+
// };
160+
161+
// // nocks for KMS responses
162+
// nock(kmsUrl)
163+
// .get(`/key/${input.pub}`)
164+
// .query({ source: 'user', useLocalEncipherment: false })
165+
// .reply(200, mockKmsUserResponse);
166+
// nock(kmsUrl)
167+
// .get(`/key/${input.pub}`)
168+
// .query({ source: 'backup', useLocalEncipherment: false })
169+
// .reply(200, mockKmsBackupResponse);
170+
171+
// const signatureResponse = await agent
172+
// .post(`/api/${ethLikeCoin}/mpcv2/recovery`)
173+
// .set('Authorization', `Bearer ${accessToken}`)
174+
// .send(input);
175+
176+
// signatureResponse.status.should.equal(400);
177+
// signatureResponse.body.should.have.property('error');
178+
// signatureResponse.body.error.should.startWith(
179+
// 'Failed to construct eth transaction from message hex',
180+
// );
181+
// });
182+
});

0 commit comments

Comments
 (0)