Skip to content

Commit

Permalink
feat(sdk-core): add MPCv2 wallet creation
Browse files Browse the repository at this point in the history
added MPCv2 wallet creation

WP-1686

TICKET: WP-1686
  • Loading branch information
alebusse committed Apr 17, 2024
1 parent e9d620f commit be9ebca
Show file tree
Hide file tree
Showing 16 changed files with 1,173 additions and 85 deletions.
428 changes: 428 additions & 0 deletions modules/bitgo/test/v2/unit/internal/tssUtils/ecdsaMPCv2.ts

Large diffs are not rendered by default.

109 changes: 104 additions & 5 deletions modules/bitgo/test/v2/unit/wallets.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
//
// Tests for Wallets
//

import * as assert from 'assert';
import * as nock from 'nock';
import * as sinon from 'sinon';
import * as should from 'should';
import * as _ from 'lodash';
import { TestBitGo } from '@bitgo/sdk-test';
import {
BlsUtils,
common,
Expand All @@ -12,11 +16,7 @@ import {
KeychainsTriplet,
GenerateWalletOptions,
} from '@bitgo/sdk-core';
import * as _ from 'lodash';
import { TestBitGo } from '@bitgo/sdk-test';
import { BitGo } from '../../../src/bitgo';
import * as sinon from 'sinon';
import * as should from 'should';

describe('V2 Wallets:', function () {
const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
Expand Down Expand Up @@ -702,6 +702,105 @@ describe('V2 Wallets:', function () {
});
});

describe('Generate TSS MPCv2 wallet:', async function () {
it('should create a new TSS MPCv2 wallet', async function () {
const hteth = bitgo.coin('hteth');
const sandbox = sinon.createSandbox();
const stubbedKeychainsTriplet: KeychainsTriplet = {
userKeychain: {
id: '1',
commonKeychain: 'userPub',
type: 'tss',
source: 'user',
},
backupKeychain: {
id: '2',
commonKeychain: 'userPub',
type: 'tss',
source: 'backup',
},
bitgoKeychain: {
id: '3',
commonKeychain: 'userPub',
type: 'tss',
source: 'bitgo',
},
};
sandbox.stub(ECDSAUtils.EcdsaMPCv2Utils.prototype, 'createKeychains').resolves(stubbedKeychainsTriplet);

const walletNock = nock('https://bitgo.fakeurl').post('/api/v2/hteth/wallet').reply(200);

const wallets = new Wallets(bitgo, hteth);

await wallets.generateWallet({
label: 'tss wallet',
passphrase: 'tss password',
multisigType: 'tss',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
walletVersion: 5,
});

walletNock.isDone().should.be.true();
sandbox.verifyAndRestore();
});

it('should throw for an unsupported coin', async function () {
const tpolygon = bitgo.coin('tpolygon');
const wallets = new Wallets(bitgo, tpolygon);

await assert.rejects(
async () => {
await wallets.generateWallet({
label: 'tss wallet',
passphrase: 'tss password',
multisigType: 'tss',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
walletVersion: 5,
});
},
{ message: 'coin polygon does not support TSS MPCv2 at this time' }
);
});

it('should throw for a cold wallet', async function () {
const hteth = bitgo.coin('hteth');
const wallets = new Wallets(bitgo, hteth);

await assert.rejects(
async () => {
await wallets.generateWallet({
label: 'tss wallet',
multisigType: 'tss',
enterprise: 'enterprise',
walletVersion: 5,
type: 'cold',
});
},
{ message: 'EVM TSS MPCv2 wallets are not supported for cold wallets' }
);
});

it('should throw for a custodial wallet', async function () {
const hteth = bitgo.coin('hteth');
const wallets = new Wallets(bitgo, hteth);

await assert.rejects(
async () => {
await wallets.generateWallet({
label: 'tss wallet',
multisigType: 'tss',
enterprise: 'enterprise',
walletVersion: 5,
type: 'custodial',
});
},
{ message: 'EVM TSS MPCv2 wallets are not supported for custodial wallets' }
);
});
});

describe('Generate BLS-DKG wallet:', function () {
const eth2 = bitgo.coin('eth2');

Expand Down
5 changes: 5 additions & 0 deletions modules/sdk-coin-eth/src/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export class Eth extends AbstractEthLikeNewCoins {
return true;
}

/** @inheritDoc */
supportsMPCv2(): boolean {
return true;
}

getMPCAlgorithm(): MPCAlgorithm {
return 'ecdsa';
}
Expand Down
3 changes: 2 additions & 1 deletion modules/sdk-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"dependencies": {
"@bitgo/bls-dkg": "^1.3.1",
"@bitgo/public-types": "2.1.0",
"@bitgo/public-types": "2.7.0",
"@bitgo/sdk-lib-mpc": "^9.2.0",
"@bitgo/statics": "^48.6.0",
"@bitgo/utxo-lib": "^9.35.0",
Expand All @@ -59,6 +59,7 @@
"ethereumjs-util": "7.1.5",
"fp-ts": "^2.12.2",
"io-ts": "2.1.3",
"io-ts-types": "0.5.16",
"keccak": "3.0.3",
"libsodium-wrappers-sumo": "^0.7.9",
"lodash": "^4.17.15",
Expand Down
8 changes: 8 additions & 0 deletions modules/sdk-core/src/bitgo/baseCoin/baseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ export abstract class BaseCoin implements IBaseCoin {
return false;
}

/**
* Flag indicating if this coin supports MPCv2 wallets.
* @returns {boolean} True if MPCv2 Wallets can be created for this coin
*/
supportsMPCv2(): boolean {
return false;
}

/**
* Flag indicating if the coin supports deriving a key with a seed (keyID)
* to the user/backup keys.
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ export interface IBaseCoin {
allowsAccountConsolidations(): boolean;
getTokenEnablementConfig(): TokenEnablementConfig;
supportsTss(): boolean;
supportsMPCv2(): boolean;
supportsDeriveKeyWithSeed(): boolean;
isEVM(): boolean;
supportsBlsDkg(): boolean;
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/keychain/iKeychains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export interface CreateMpcOptions {
originalPasscodeEncryptionCode?: string;
enterprise?: string;
backupProvider?: BackupProvider;
walletVersion?: number;
}

export interface GetKeysForSigningOptions {
Expand Down
7 changes: 6 additions & 1 deletion modules/sdk-core/src/bitgo/keychain/keychains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,12 @@ export class Keychains implements IKeychains {
let MpcUtils;
switch (params.multisigType) {
case 'tss':
MpcUtils = this.baseCoin.getMPCAlgorithm() === 'ecdsa' ? ECDSAUtils.EcdsaUtils : EDDSAUtils.default;
MpcUtils =
this.baseCoin.getMPCAlgorithm() === 'eddsa'
? EDDSAUtils.default
: params.walletVersion === 5
? ECDSAUtils.EcdsaMPCv2Utils
: ECDSAUtils.EcdsaUtils;
break;
case 'blsdkg':
if (_.isUndefined(params.passphrase)) {
Expand Down
73 changes: 73 additions & 0 deletions modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as openpgp from 'openpgp';
import { ec } from 'elliptic';

import { IBaseCoin } from '../../../baseCoin';
import baseTSSUtils from '../baseTSSUtils';
import { KeyShare } from './types';
import { BackupGpgKey } from '../baseTypes';
import { generateGPGKeyPair, getBitgoGpgPubKey, getTrustGpgPubKey } from '../../opengpgUtils';
import { BitGoBase } from '../../../bitgoBase';
import { IWallet } from '../../../wallet';

/** @inheritdoc */
export class BaseEcdsaUtils extends baseTSSUtils<KeyShare> {
// We do not have full support for 3-party verification (w/ external source) of key shares and signature shares. There is no 3rd party key service support with this release.
protected bitgoPublicGpgKey: openpgp.Key;

constructor(bitgo: BitGoBase, baseCoin: IBaseCoin, wallet?: IWallet) {
super(bitgo, baseCoin, wallet);
this.setBitgoGpgPubKey(bitgo);
}

private async setBitgoGpgPubKey(bitgo) {
this.bitgoPublicGpgKey = await getBitgoGpgPubKey(bitgo);
}

async getBitgoPublicGpgKey(): Promise<openpgp.Key> {
if (!this.bitgoPublicGpgKey) {
// retry getting bitgo's gpg key
await this.setBitgoGpgPubKey(this.bitgo);
if (!this.bitgoPublicGpgKey) {
throw new Error("Failed to get Bitgo's gpg key");
}
}

return this.bitgoPublicGpgKey;
}

/**
* Gets backup pub gpg key string
* if a third party provided then get from trust
* @param isThirdPartyBackup
*/
async getBackupGpgPubKey(isThirdPartyBackup = false): Promise<BackupGpgKey> {
return isThirdPartyBackup ? getTrustGpgPubKey(this.bitgo) : generateGPGKeyPair('secp256k1');
}

/**
* util function that checks that a commonKeychain is valid and can ultimately resolve to a valid public key
* @param commonKeychain - a user uploaded commonKeychain string
* @throws if the commonKeychain is invalid length or invalid format
*/

static validateCommonKeychainPublicKey(commonKeychain: string) {
const pub = BaseEcdsaUtils.getPublicKeyFromCommonKeychain(commonKeychain);
const secp256k1 = new ec('secp256k1');
const key = secp256k1.keyFromPublic(pub, 'hex');
return key.getPublic().encode('hex', false).slice(2);
}

/**
* Gets the common public key from commonKeychain.
*
* @param {String} commonKeychain common key chain between n parties
* @returns {string} encoded public key
*/
static getPublicKeyFromCommonKeychain(commonKeychain: string): string {
if (commonKeychain.length !== 130) {
throw new Error(`Invalid commonKeychain length, expected 130, got ${commonKeychain.length}`);
}
const commonPubHexStr = commonKeychain.slice(0, 66);
return commonPubHexStr;
}
}
Loading

0 comments on commit be9ebca

Please sign in to comment.