Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions masterBitgoExpress.json
Original file line number Diff line number Diff line change
Expand Up @@ -462,9 +462,6 @@
"ledgerSequenceDelta": {
"type": "number"
},
"gasPrice": {
"type": "number"
},
"noSplitChange": {
"type": "boolean"
},
Expand Down Expand Up @@ -762,6 +759,36 @@
"coinSpecificParams": {
"type": "object",
"properties": {
"ecdsaCosmosLikeRecoverySpecificParams": {
"type": "object",
"properties": {
"rootAddress": {
"type": "string"
}
},
"required": [
"rootAddress"
]
},
"ecdsaEthLikeRecoverySpecificParams": {
"type": "object",
"properties": {
"apiKey": {
"type": "string"
},
"bitgoDestinationAddress": {
"type": "string"
},
"walletContractAddress": {
"type": "string"
}
},
"required": [
"apiKey",
"bitgoDestinationAddress",
"walletContractAddress"
]
},
"evmRecoveryOptions": {
"type": "object",
"properties": {
Expand Down
591 changes: 520 additions & 71 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"@bitgo-beta/statics": "15.1.1-beta.1046",
"@bitgo/wasm-miniscript": "2.0.0-beta.7",
"@commitlint/config-conventional": "^19.8.1",
"@ethereumjs/tx": "^3.3.0",
"body-parser": "^1.20.3",
"connect-timeout": "^1.9.0",
"debug": "^3.1.0",
Expand Down
10 changes: 0 additions & 10 deletions src/__tests__/api/enclaved/postIndependentKey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import { app as enclavedApp } from '../../../enclavedApp';
import { AppMode, EnclavedConfig, TlsMode } from '../../../shared/types';
import express from 'express';

import * as sinon from 'sinon';
import * as configModule from '../../../initConfig';

describe('postIndependentKey', () => {
let cfg: EnclavedConfig;
let app: express.Application;
Expand All @@ -20,7 +17,6 @@ describe('postIndependentKey', () => {
const accessToken = 'test-token';

// sinon stubs
let configStub: sinon.SinonStub;

before(() => {
// nock config
Expand All @@ -39,8 +35,6 @@ describe('postIndependentKey', () => {
allowSelfSigned: true,
};

configStub = sinon.stub(configModule, 'initConfig').returns(cfg);

// app setup
app = enclavedApp(cfg);
agent = request.agent(app);
Expand All @@ -50,10 +44,6 @@ describe('postIndependentKey', () => {
nock.cleanAll();
});

after(() => {
configStub.restore();
});

// test cases
it('should post an independent key successfully', async () => {
const mockKmsResponse = {
Expand Down
162 changes: 162 additions & 0 deletions src/__tests__/api/enclaved/recoveryMpcV2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { AppMode, EnclavedConfig, TlsMode } from '../../../initConfig';
import { app as enclavedApp } from '../../../enclavedApp';

import express from 'express';
import nock from 'nock';
import 'should';
import * as request from 'supertest';
import * as sinon from 'sinon';
import * as configModule from '../../../initConfig';
import { DklsTypes, DklsUtils } from '@bitgo-beta/sdk-lib-mpc';

describe('recoveryMpcV2', async () => {
let cfg: EnclavedConfig;
let app: express.Application;
let agent: request.SuperAgentTest;

// test config
const kmsUrl = 'http://kms.invalid';
const ethLikeCoin = 'hteth';
const cosmosLikeCoin = 'tsei';
const accessToken = 'test-token';

// sinon stubs
let configStub: sinon.SinonStub;

// kms nocks setup
const [userShare, backupShare] = await DklsUtils.generateDKGKeyShares();
const userKeyShare = userShare.getKeyShare().toString('base64');
const backupKeyShare = backupShare.getKeyShare().toString('base64');
const commonKeychain = DklsTypes.getCommonKeychain(userShare.getKeyShare());

const mockKmsUserResponse = {
prv: JSON.stringify(userKeyShare),
pub: commonKeychain,
source: 'user',
type: 'tss',
};

const mockKmsBackupResponse = {
prv: JSON.stringify(backupKeyShare),
pub: commonKeychain,
source: 'backup',
type: 'tss',
};
const input = {
txHex:
'02f6824268018502540be4008504a817c80083030d409443442e403d64d29c4f64065d0c1a0e8edc03d6c88801550f7dca700000823078c0',
pub: commonKeychain,
};

before(async () => {
// nock config
nock.disableNetConnect();
nock.enableNetConnect('127.0.0.1');

// app config
cfg = {
appMode: AppMode.ENCLAVED,
port: 0, // Let OS assign a free port
bind: 'localhost',
timeout: 60000,
kmsUrl: kmsUrl,
httpLoggerFile: '',
tlsMode: TlsMode.DISABLED,
allowSelfSigned: true,
recoveryMode: true,
};

configStub = sinon.stub(configModule, 'initConfig').returns(cfg);

// app setup
app = enclavedApp(cfg);
agent = request.agent(app);
});

afterEach(() => {
nock.cleanAll();
});

after(() => {
configStub.restore();
});

// happy path test
it('should be sign a Mpc V2 Recovery', async () => {
// nocks for KMS responses
const userKmsNock = nock(kmsUrl)
.get(`/key/${input.pub}`)
.query({ source: 'user', useLocalEncipherment: false })
.reply(200, mockKmsUserResponse)
.persist();
const backupKmsNock = nock(kmsUrl)
.get(`/key/${input.pub}`)
.query({ source: 'backup', useLocalEncipherment: false })
.reply(200, mockKmsBackupResponse)
.persist();

const ethLikeSignatureResponse = await agent
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
.set('Authorization', `Bearer ${accessToken}`)
.send(input);

ethLikeSignatureResponse.status.should.equal(200);
ethLikeSignatureResponse.body.should.have.property('txHex');
ethLikeSignatureResponse.body.txHex.should.equal(input.txHex);

ethLikeSignatureResponse.body.should.have.property('stringifiedSignature');
const ethLikeSignature = JSON.parse(ethLikeSignatureResponse.body.stringifiedSignature);
ethLikeSignature.should.have.property('recid');
ethLikeSignature.should.have.property('r');
ethLikeSignature.should.have.property('s');
ethLikeSignature.should.have.property('y');

const cosmosLikeSignatureResponse = await agent
.post(`/api/${cosmosLikeCoin}/mpcv2/recovery`)
.set('Authorization', `Bearer ${accessToken}`)
.send(input);

cosmosLikeSignatureResponse.status.should.equal(200);
cosmosLikeSignatureResponse.body.should.have.property('txHex');
cosmosLikeSignatureResponse.body.txHex.should.equal(input.txHex);

cosmosLikeSignatureResponse.body.should.have.property('stringifiedSignature');
const cosmosLikeSignature = JSON.parse(cosmosLikeSignatureResponse.body.stringifiedSignature);
cosmosLikeSignature.should.have.property('recid');
cosmosLikeSignature.should.have.property('r');
cosmosLikeSignature.should.have.property('s');
cosmosLikeSignature.should.have.property('y');

userKmsNock.isDone().should.be.true();
backupKmsNock.isDone().should.be.true();
});

// failure test case
it('should throw 400 Bad Request if failed to construct eth transaction from message hex', async () => {
const input = {
txHex: 'invalid-hex',
pub: commonKeychain,
};

// nocks for KMS responses
nock(kmsUrl)
.get(`/key/${input.pub}`)
.query({ source: 'user', useLocalEncipherment: false })
.reply(200, mockKmsUserResponse);
nock(kmsUrl)
.get(`/key/${input.pub}`)
.query({ source: 'backup', useLocalEncipherment: false })
.reply(200, mockKmsBackupResponse);

const signatureResponse = await agent
.post(`/api/${ethLikeCoin}/mpcv2/recovery`)
.set('Authorization', `Bearer ${accessToken}`)
.send(input);

signatureResponse.status.should.equal(400);
signatureResponse.body.should.have.property('error');
signatureResponse.body.error.should.startWith(
'Failed to construct eth transaction from message hex',
);
});
});
Loading