bitgo
Version:
BitGo JavaScript SDK
756 lines • 146 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const assert = require("assert");
const nock = require("nock");
const openpgp = require("openpgp");
const crypto = require("crypto");
const sdk_test_1 = require("@bitgo/sdk-test");
const sdk_core_1 = require("@bitgo/sdk-core");
const sdk_lib_mpc_1 = require("@bitgo/sdk-lib-mpc");
const public_types_1 = require("@bitgo/public-types");
const src_1 = require("../../../../../../src");
const v1Fixtures = require("./fixtures/mpcv1KeyShares");
describe('TSS Ecdsa MPCv2 Utils:', async function () {
const coinName = 'hteth';
const walletId = '5b34252f1bf349930e34020a00000000';
const enterpriseId = '6449153a6f6bc20006d66771cdbe15d3';
let storedUserCommitment2;
let storedBackupCommitment2;
let storedBitgoCommitment2;
let bgUrl;
let tssUtils;
let wallet;
let bitgo;
let baseCoin;
let bitGoGgpKey;
let constants;
let bitgoGpgPrvKey;
let userGpgPubKey;
let backupGpgPubKey;
let bitgoGpgPubKey;
beforeEach(async function () {
nock.cleanAll();
await nockGetBitgoPublicKeyBasedOnFeatureFlags(coinName, enterpriseId, bitGoGgpKey);
nock(bgUrl).get('/api/v1/client/constants').times(16).reply(200, { ttl: 3600, constants });
});
before(async function () {
bitGoGgpKey = await openpgp.generateKey({
userIDs: [
{
name: 'bitgo',
email: 'bitgo@test.com',
},
],
curve: 'secp256k1',
});
constants = {
mpc: {
bitgoPublicKey: bitGoGgpKey.publicKey,
bitgoMPCv2PublicKey: bitGoGgpKey.publicKey,
},
};
bitgoGpgPubKey = {
partyId: 2,
gpgKey: bitGoGgpKey.publicKey,
};
bitgoGpgPrvKey = {
partyId: 2,
gpgKey: bitGoGgpKey.privateKey,
};
bitgo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' });
bitgo.initializeTestVars();
baseCoin = bitgo.coin(coinName);
bgUrl = sdk_core_1.common.Environments[bitgo.getEnv()].uri;
const walletData = {
id: walletId,
enterprise: enterpriseId,
coin: coinName,
coinSpecific: {},
multisigType: 'tss',
};
wallet = new sdk_core_1.Wallet(bitgo, baseCoin, walletData);
tssUtils = new sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils(bitgo, baseCoin, wallet);
});
after(function () {
nock.cleanAll();
});
describe('Retrofit MPCv1 to MPCv2 keys', async function () {
it('should generate TSS MPCv2 keys from MPCv1 keys and sign a message', async function () {
const retrofitData = tssUtils.getMpcV2RetrofitDataFromMpcV1Keys({
mpcv1UserKeyShare: JSON.stringify(v1Fixtures.mockUserSigningMaterial),
mpcv1BackupKeyShare: JSON.stringify(v1Fixtures.mockBackupSigningMaterial),
});
const bitgoRetrofitData = {
xiList: retrofitData.mpcv2UserKeyShare.xiList,
xShare: v1Fixtures.mockBitGoShareSigningMaterial.xShare,
};
const [user, backup, bitgo] = await sdk_lib_mpc_1.DklsUtils.generateDKGKeyShares(retrofitData.mpcv2UserKeyShare, retrofitData.mpcv2BackupKeyShare, bitgoRetrofitData);
assert.ok(bitgo.getKeyShare());
const messageToSign = crypto.createHash('sha256').update(Buffer.from('ffff', 'hex')).digest();
const derivationPath = 'm/999/988/0/0';
const signature = await sdk_lib_mpc_1.DklsUtils.executeTillRound(5, new sdk_lib_mpc_1.DklsDsg.Dsg(user.getKeyShare(), 0, derivationPath, messageToSign), new sdk_lib_mpc_1.DklsDsg.Dsg(backup.getKeyShare(), 1, derivationPath, messageToSign));
const convertedSignature = sdk_lib_mpc_1.DklsUtils.verifyAndConvertDklsSignature(Buffer.from('ffff', 'hex'), signature, v1Fixtures.mockBitGoShareSigningMaterial.xShare.y + v1Fixtures.mockBitGoShareSigningMaterial.xShare.chaincode, derivationPath);
assert.ok(convertedSignature);
convertedSignature.split(':').length.should.equal(4);
});
});
describe('TSS key chains', async function () {
it('should generate TSS MPCv2 keys', async function () {
const bitgoSession = new sdk_lib_mpc_1.DklsDkg.Dkg(3, 2, 2);
const round1Nock = await nockKeyGenRound1(bitgoSession, 1);
const round2Nock = await nockKeyGenRound2(bitgoSession, 1);
const round3Nock = await nockKeyGenRound3(bitgoSession, 1);
const addKeyNock = await nockAddKeyChain(coinName, 3);
const params = {
passphrase: 'test',
enterprise: enterpriseId,
originalPasscodeEncryptionCode: '123456',
};
const { userKeychain, backupKeychain, bitgoKeychain } = await tssUtils.createKeychains(params);
assert.ok(round1Nock.isDone());
assert.ok(round2Nock.isDone());
assert.ok(round3Nock.isDone());
assert.ok(addKeyNock.isDone());
assert.ok(userKeychain);
assert.equal(userKeychain.source, 'user');
assert.ok(userKeychain.commonKeychain);
assert.ok(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.validateCommonKeychainPublicKey(userKeychain.commonKeychain));
assert.ok(userKeychain.encryptedPrv);
assert.ok(bitgo.decrypt({ input: userKeychain.encryptedPrv, password: params.passphrase }));
assert.ok(backupKeychain);
assert.equal(backupKeychain.source, 'backup');
assert.ok(backupKeychain.commonKeychain);
assert.ok(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.validateCommonKeychainPublicKey(backupKeychain.commonKeychain));
assert.ok(backupKeychain.encryptedPrv);
assert.ok(bitgo.decrypt({ input: backupKeychain.encryptedPrv, password: params.passphrase }));
assert.ok(bitgoKeychain);
assert.equal(bitgoKeychain.source, 'bitgo');
});
it('should generate TSS MPCv2 keys for retrofit', async function () {
const xiList = [
Array.from((0, sdk_lib_mpc_1.bigIntToBufferBE)(BigInt(1), 32)),
Array.from((0, sdk_lib_mpc_1.bigIntToBufferBE)(BigInt(2), 32)),
Array.from((0, sdk_lib_mpc_1.bigIntToBufferBE)(BigInt(3), 32)),
];
const bitgoRetrofitData = {
xiList,
xShare: v1Fixtures.mockBitGoShareSigningMaterial.xShare,
};
const bitgoSession = new sdk_lib_mpc_1.DklsDkg.Dkg(3, 2, sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BITGO, undefined, bitgoRetrofitData);
const round1Nock = await nockKeyGenRound1(bitgoSession, 1);
const round2Nock = await nockKeyGenRound2(bitgoSession, 1);
const round3Nock = await nockKeyGenRound3(bitgoSession, 1);
const addKeyNock = await nockAddKeyChain(coinName, 3);
const params = {
passphrase: 'test',
enterprise: enterpriseId,
originalPasscodeEncryptionCode: '123456',
retrofit: {
decryptedUserKey: JSON.stringify(v1Fixtures.mockUserSigningMaterial),
decryptedBackupKey: JSON.stringify(v1Fixtures.mockBackupSigningMaterial),
walletId: '123',
},
};
const { userKeychain, backupKeychain, bitgoKeychain } = await tssUtils.createKeychains(params);
assert.ok(round1Nock.isDone());
assert.ok(round2Nock.isDone());
assert.ok(round3Nock.isDone());
assert.ok(addKeyNock.isDone());
assert.ok(userKeychain);
assert.equal(userKeychain.source, 'user');
assert.ok(userKeychain.commonKeychain);
assert.ok(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.validateCommonKeychainPublicKey(userKeychain.commonKeychain));
assert.ok(userKeychain.encryptedPrv);
assert.ok(bitgo.decrypt({ input: userKeychain.encryptedPrv, password: params.passphrase }));
assert.ok(backupKeychain);
assert.equal(backupKeychain.source, 'backup');
assert.ok(backupKeychain.commonKeychain);
assert.ok(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.validateCommonKeychainPublicKey(backupKeychain.commonKeychain));
assert.ok(backupKeychain.encryptedPrv);
assert.ok(bitgo.decrypt({ input: backupKeychain.encryptedPrv, password: params.passphrase }));
assert.ok(bitgoKeychain);
assert.equal(bitgoKeychain.source, 'bitgo');
});
it('should create TSS key chains', async function () {
const nockPromises = [
nockKeychain({
coin: coinName,
keyChain: { id: '1', pub: '1', type: 'tss', reducedEncryptedPrv: '' },
source: 'user',
}),
nockKeychain({
coin: coinName,
keyChain: { id: '2', pub: '2', type: 'tss', reducedEncryptedPrv: '' },
source: 'backup',
}),
nockKeychain({
coin: coinName,
keyChain: { id: '3', pub: '3', type: 'tss', reducedEncryptedPrv: '' },
source: 'bitgo',
}),
];
const [nockedUserKeychain, nockedBackupKeychain, nockedBitGoKeychain] = await Promise.all(nockPromises);
const bitgoKeychainPromise = tssUtils.createParticipantKeychain(sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BITGO, 'test');
const usersKeychainPromise = tssUtils.createParticipantKeychain(sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.USER, 'test', Buffer.from('test'), Buffer.from('test'), 'passphrase', 'test');
const backupKeychainPromise = tssUtils.createParticipantKeychain(sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BACKUP, 'test', Buffer.from('test'), Buffer.from('test'), 'passphrase', 'test');
const [userKeychain, backupKeychain, bitgoKeychain] = await Promise.all([
usersKeychainPromise,
backupKeychainPromise,
bitgoKeychainPromise,
]);
({ ...userKeychain, reducedEncryptedPrv: '' }).should.deepEqual(nockedUserKeychain);
({ ...backupKeychain, reducedEncryptedPrv: '' }).should.deepEqual(nockedBackupKeychain);
({ ...bitgoKeychain, reducedEncryptedPrv: '' }).should.deepEqual(nockedBitGoKeychain);
});
it('should create TSS MPCv2 key chains with OVCs', async function () {
const MPCv2SMCUtils = new sdk_core_1.ECDSAUtils.MPCv2SMCUtils(bitgo, baseCoin);
const bitgoSession = new sdk_lib_mpc_1.DklsDkg.Dkg(3, 2, 2);
const round1Nock = await nockKeyGenRound1(bitgoSession, 1);
const round2Nock = await nockKeyGenRound2(bitgoSession, 1);
const round3Nock = await nockKeyGenRound3(bitgoSession, 1);
const addKeyNock = await nockAddKeyChain(coinName, 3);
// OVC 1 - User GPG key
const userGgpKey = await openpgp.generateKey({
userIDs: [
{
name: 'user',
email: 'user@test.com',
},
],
curve: 'secp256k1',
});
const userGpgPrvKey = {
partyId: 0,
gpgKey: userGgpKey.privateKey,
};
// Round 1 User
const userSession = new sdk_lib_mpc_1.DklsDkg.Dkg(3, 2, 0);
let OVC1ToOVC2Round1Payload;
{
const userBroadcastMsg1Unsigned = await userSession.initDkg();
const userMsgs1Signed = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages({ broadcastMessages: [sdk_lib_mpc_1.DklsTypes.serializeBroadcastMessage(userBroadcastMsg1Unsigned)], p2pMessages: [] }, [], [userGpgPrvKey]);
const userMsg1 = userMsgs1Signed.broadcastMessages.find((m) => m.from === 0);
assert.ok(userMsg1, 'userMsg1 not found');
OVC1ToOVC2Round1Payload = {
tssVersion: '0.0.1',
walletType: public_types_1.WalletTypeEnum.tss,
coin: 'eth',
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC2Round1Data,
ovc: {
[public_types_1.OVCIndexEnum.ONE]: {
gpgPubKey: userGgpKey.publicKey,
ovcMsg1: userMsg1,
},
},
};
}
// OVC 2 - Backup GPG key
const backupGgpKey = await openpgp.generateKey({
userIDs: [
{
name: 'backup',
email: 'backup@test.com',
},
],
curve: 'secp256k1',
});
const backupGpgPrvKey = {
partyId: 1,
gpgKey: backupGgpKey.privateKey,
};
// Round 1 Backup
const backupSession = new sdk_lib_mpc_1.DklsDkg.Dkg(3, 2, 1);
let OVC2ToBitgoRound1Payload;
{
assert.ok(OVC1ToOVC2Round1Payload.state === 0, 'OVC1ToOVC2Round1Payload.state should be 0');
const backupBroadcastMsg1Unsigned = await backupSession.initDkg();
const backupMsgs1Signed = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages({ broadcastMessages: [sdk_lib_mpc_1.DklsTypes.serializeBroadcastMessage(backupBroadcastMsg1Unsigned)], p2pMessages: [] }, [], [backupGpgPrvKey]);
const backupMsg1 = backupMsgs1Signed.broadcastMessages.find((m) => m.from === 1);
assert.ok(backupMsg1, 'backupMsg1 not found');
OVC2ToBitgoRound1Payload = {
...OVC1ToOVC2Round1Payload,
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForBitgoRound1Data,
ovc: {
...OVC1ToOVC2Round1Payload.ovc,
[public_types_1.OVCIndexEnum.TWO]: {
gpgPubKey: backupGgpKey.publicKey,
ovcMsg1: backupMsg1,
},
},
};
}
// Round 1 BitGo
const bitgoToOVC1Round1Payload = await MPCv2SMCUtils.keyGenRound1('testId', OVC2ToBitgoRound1Payload);
// Round 2 User
let OVC1ToOVC2Round2Payload;
{
assert.ok(bitgoToOVC1Round1Payload.state === 2, 'bitgoToOVC1Round1Payload.state should be 2');
const toUserRound1BroadcastMessages = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [],
broadcastMessages: [
bitgoToOVC1Round1Payload.ovc[public_types_1.OVCIndexEnum.TWO].ovcMsg1,
bitgoToOVC1Round1Payload.platform.bitgoMsg1,
],
}, [bitgoGpgPubKey, { partyId: 1, gpgKey: bitgoToOVC1Round1Payload.ovc[public_types_1.OVCIndexEnum.TWO].gpgPubKey }], [userGpgPrvKey]);
const userRound2P2PMessages = userSession.handleIncomingMessages({
p2pMessages: [],
broadcastMessages: toUserRound1BroadcastMessages.broadcastMessages.map(sdk_lib_mpc_1.DklsTypes.deserializeBroadcastMessage),
});
const userRound2Messages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(sdk_lib_mpc_1.DklsTypes.serializeMessages(userRound2P2PMessages), [{ partyId: 1, gpgKey: bitgoToOVC1Round1Payload.ovc[public_types_1.OVCIndexEnum.TWO].gpgPubKey }, bitgoGpgPubKey], [userGpgPrvKey]);
const userToBackupMsg2 = userRound2Messages.p2pMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.USER && m.to === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BACKUP);
assert.ok(userToBackupMsg2, 'userToBackupMsg2 not found');
const userToBitgoMsg2 = userRound2Messages.p2pMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.USER && m.to === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BITGO);
assert.ok(userToBitgoMsg2, 'userToBitgoMsg2 not found');
OVC1ToOVC2Round2Payload = {
...bitgoToOVC1Round1Payload,
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC2Round2Data,
ovc: {
...bitgoToOVC1Round1Payload.ovc,
[public_types_1.OVCIndexEnum.ONE]: Object.assign(bitgoToOVC1Round1Payload.ovc[public_types_1.OVCIndexEnum.ONE], {
ovcToBitgoMsg2: userToBitgoMsg2,
ovcToOvcMsg2: userToBackupMsg2,
}),
},
};
}
// Round 2 Backup
let OVC2ToBitgoRound2Payload;
{
assert.ok(OVC1ToOVC2Round2Payload.state === 3, 'bitgoToOVC1Round1Payload.state should be 3');
const toBackupRound1BroadcastMessages = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [],
broadcastMessages: [
bitgoToOVC1Round1Payload.ovc[public_types_1.OVCIndexEnum.ONE].ovcMsg1,
bitgoToOVC1Round1Payload.platform.bitgoMsg1,
],
}, [bitgoGpgPubKey, { partyId: 0, gpgKey: OVC1ToOVC2Round2Payload.ovc[public_types_1.OVCIndexEnum.ONE].gpgPubKey }], [backupGpgPrvKey]);
const backupRound2P2PMessages = backupSession.handleIncomingMessages({
p2pMessages: [],
broadcastMessages: toBackupRound1BroadcastMessages.broadcastMessages.map(sdk_lib_mpc_1.DklsTypes.deserializeBroadcastMessage),
});
const backupRound2Messages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(sdk_lib_mpc_1.DklsTypes.serializeMessages(backupRound2P2PMessages), [{ partyId: 0, gpgKey: bitgoToOVC1Round1Payload.ovc[public_types_1.OVCIndexEnum.ONE].gpgPubKey }, bitgoGpgPubKey], [backupGpgPrvKey]);
const backupToUserMsg2 = backupRound2Messages.p2pMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BACKUP && m.to === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.USER);
assert.ok(backupToUserMsg2, 'backupToUserMsg2 not found');
const backupToBitgoMsg2 = backupRound2Messages.p2pMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BACKUP && m.to === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BITGO);
assert.ok(backupToBitgoMsg2, 'backupToBitgoMsg2 not found');
OVC2ToBitgoRound2Payload = {
...OVC1ToOVC2Round2Payload,
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForBitgoRound2Data,
ovc: {
...OVC1ToOVC2Round2Payload.ovc,
[public_types_1.OVCIndexEnum.TWO]: Object.assign(OVC1ToOVC2Round2Payload.ovc[public_types_1.OVCIndexEnum.TWO], {
ovcToBitgoMsg2: backupToBitgoMsg2,
ovcToOvcMsg2: backupToUserMsg2,
}),
},
};
}
// Round 2 BitGo
// call bitgo round 2
const bitgoToOVC1Round2Payload = await MPCv2SMCUtils.keyGenRound2('testId', OVC2ToBitgoRound2Payload);
// Round 3A User
let OVC1ToOVC2Round3Payload;
{
assert.ok(bitgoToOVC1Round2Payload.state === 5, 'bitgoToOVC1Round2Payload.state should be 5');
const toUserRound2P2PMessages = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [
bitgoToOVC1Round2Payload.ovc[public_types_1.OVCIndexEnum.TWO].ovcToOvcMsg2,
bitgoToOVC1Round2Payload.platform.ovc[public_types_1.OVCIndexEnum.ONE].bitgoToOvcMsg2,
],
broadcastMessages: [],
}, [bitgoGpgPubKey, { partyId: 1, gpgKey: bitgoToOVC1Round2Payload.ovc[public_types_1.OVCIndexEnum.TWO].gpgPubKey }], [userGpgPrvKey]);
const userRound3AP2PMessages = userSession.handleIncomingMessages({
p2pMessages: toUserRound2P2PMessages.p2pMessages.map(sdk_lib_mpc_1.DklsTypes.deserializeP2PMessage),
broadcastMessages: [],
}).p2pMessages;
const userRound3AMessages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(sdk_lib_mpc_1.DklsTypes.serializeMessages({
p2pMessages: userRound3AP2PMessages,
broadcastMessages: [],
}), [{ partyId: 1, gpgKey: bitgoToOVC1Round2Payload.ovc[public_types_1.OVCIndexEnum.TWO].gpgPubKey }, bitgoGpgPubKey], [userGpgPrvKey]);
const userToBitgoMsg3 = userRound3AMessages.p2pMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.USER && m.to === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BITGO);
assert.ok(userToBitgoMsg3, 'userToBitgoMsg3 not found');
const userToBackupMsg3 = userRound3AMessages.p2pMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.USER && m.to === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BACKUP);
assert.ok(userToBackupMsg3, 'userToBackupMsg3 not found');
OVC1ToOVC2Round3Payload = {
...bitgoToOVC1Round2Payload,
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC2Round3Data,
ovc: {
...bitgoToOVC1Round2Payload.ovc,
[public_types_1.OVCIndexEnum.ONE]: Object.assign(bitgoToOVC1Round2Payload.ovc[public_types_1.OVCIndexEnum.ONE], {
ovcToBitgoMsg3: userToBitgoMsg3,
ovcToOvcMsg3: userToBackupMsg3,
}),
},
};
}
// Round 3 Backup
let OVC2ToOVC1Round3Payload;
{
assert.ok(OVC1ToOVC2Round3Payload.state === 6, 'OVC1ToOVC2Round3Payload.state should be 6');
const toBackupRound3P2PMessages = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [
OVC1ToOVC2Round3Payload.ovc[public_types_1.OVCIndexEnum.ONE].ovcToOvcMsg2,
OVC1ToOVC2Round3Payload.platform.ovc[public_types_1.OVCIndexEnum.TWO].bitgoToOvcMsg2,
],
broadcastMessages: [],
}, [bitgoGpgPubKey, { partyId: 0, gpgKey: OVC1ToOVC2Round3Payload.ovc[public_types_1.OVCIndexEnum.ONE].gpgPubKey }], [backupGpgPrvKey]);
const backupRound3P2PMessages = backupSession.handleIncomingMessages({
p2pMessages: toBackupRound3P2PMessages.p2pMessages.map(sdk_lib_mpc_1.DklsTypes.deserializeP2PMessage),
broadcastMessages: [],
});
const backupRound3Messages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(sdk_lib_mpc_1.DklsTypes.serializeMessages(backupRound3P2PMessages), [{ partyId: 0, gpgKey: OVC1ToOVC2Round3Payload.ovc[public_types_1.OVCIndexEnum.ONE].gpgPubKey }, bitgoGpgPubKey], [backupGpgPrvKey]);
const backupToBitgoMsg3 = backupRound3Messages.p2pMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BACKUP && m.to === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BITGO);
assert.ok(backupToBitgoMsg3, 'backupToBitgoMsg3 not found');
const backupToUserMsg3 = backupRound3Messages.p2pMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BACKUP && m.to === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.USER);
assert.ok(backupToUserMsg3, 'backupToUserMsg3 not found');
const toBackupRound3Messages = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [
{
...OVC1ToOVC2Round3Payload.ovc[public_types_1.OVCIndexEnum.ONE].ovcToOvcMsg3,
commitment: OVC1ToOVC2Round3Payload.ovc[public_types_1.OVCIndexEnum.ONE].ovcToOvcMsg2.commitment,
},
{
...OVC1ToOVC2Round3Payload.platform.ovc[public_types_1.OVCIndexEnum.TWO].bitgoToOvcMsg3,
commitment: OVC1ToOVC2Round3Payload.platform.bitgoCommitment2,
},
],
broadcastMessages: [],
}, [bitgoGpgPubKey, { partyId: 0, gpgKey: OVC1ToOVC2Round3Payload.ovc[public_types_1.OVCIndexEnum.ONE].gpgPubKey }], [backupGpgPrvKey]);
const backupRound4Messages = backupSession.handleIncomingMessages({
p2pMessages: toBackupRound3Messages.p2pMessages.map(sdk_lib_mpc_1.DklsTypes.deserializeP2PMessage),
broadcastMessages: [],
}).broadcastMessages;
const backupRound4BroadcastMessages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(sdk_lib_mpc_1.DklsTypes.serializeMessages({
p2pMessages: [],
broadcastMessages: backupRound4Messages,
}), [], [backupGpgPrvKey]);
const backupMsg4 = backupRound4BroadcastMessages.broadcastMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.BACKUP);
assert.ok(backupMsg4, 'backupMsg4 not found');
OVC2ToOVC1Round3Payload = {
...OVC1ToOVC2Round3Payload,
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC1Round3bData,
ovc: {
...OVC1ToOVC2Round3Payload.ovc,
[public_types_1.OVCIndexEnum.TWO]: Object.assign(OVC1ToOVC2Round3Payload.ovc[public_types_1.OVCIndexEnum.TWO], {
ovcToOvcMsg3: backupToUserMsg3,
ovcToBitgoMsg3: backupToBitgoMsg3,
ovcMsg4: backupMsg4,
}),
},
};
}
// Round 3B User
let OVC1ToBitgoRound3BPayload;
{
assert.ok(OVC2ToOVC1Round3Payload.state === 7, 'OVC2ToOVC1Round3Payload.state should be 7');
const toUserRound4Messages = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [
{
...OVC2ToOVC1Round3Payload.ovc[public_types_1.OVCIndexEnum.TWO].ovcToOvcMsg3,
commitment: OVC2ToOVC1Round3Payload.ovc[public_types_1.OVCIndexEnum.TWO].ovcToOvcMsg2.commitment,
},
{
...OVC2ToOVC1Round3Payload.platform.ovc[public_types_1.OVCIndexEnum.ONE].bitgoToOvcMsg3,
commitment: OVC2ToOVC1Round3Payload.platform.bitgoCommitment2,
},
],
broadcastMessages: [],
}, [bitgoGpgPubKey, { partyId: 1, gpgKey: OVC2ToOVC1Round3Payload.ovc[public_types_1.OVCIndexEnum.TWO].gpgPubKey }], [userGpgPrvKey]);
const userRound4BroadcastMessages = userSession.handleIncomingMessages({
p2pMessages: toUserRound4Messages.p2pMessages.map(sdk_lib_mpc_1.DklsTypes.deserializeP2PMessage),
broadcastMessages: [],
}).broadcastMessages;
assert.ok(userRound4BroadcastMessages.length === 1, 'userRound4BroadcastMessages length should be 1');
const userRound4Messages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(sdk_lib_mpc_1.DklsTypes.serializeMessages({
p2pMessages: [],
broadcastMessages: userRound4BroadcastMessages,
}), [], [userGpgPrvKey]);
const userMsg4 = userRound4Messages.broadcastMessages.find((m) => m.from === sdk_core_1.ECDSAUtils.MPCv2PartiesEnum.USER);
assert.ok(userMsg4, 'userMsg4 not found');
OVC1ToBitgoRound3BPayload = {
...OVC2ToOVC1Round3Payload,
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForBitgoRound3Data,
ovc: {
...OVC2ToOVC1Round3Payload.ovc,
[public_types_1.OVCIndexEnum.ONE]: Object.assign(OVC2ToOVC1Round3Payload.ovc[public_types_1.OVCIndexEnum.ONE], {
ovcMsg4: userMsg4,
}),
},
};
}
// Round 3 BitGo
// creates bitgo keychain
const bitgoToOVC1Round3Payload = await MPCv2SMCUtils.keyGenRound3('testId', OVC1ToBitgoRound3BPayload);
// Round 4 User
let userCommonKeychain;
let OVC1ToOVC2Round4Payload;
{
assert.ok(bitgoToOVC1Round3Payload.state === 9, 'bitgoToOVC1Round3Payload.state should be 9');
assert.ok(bitgoToOVC1Round3Payload.bitGoKeyId, 'bitgoToOVC1Round3Payload.bitGoKeyId not found');
const toUserBitgoRound3Msg = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [],
broadcastMessages: [
bitgoToOVC1Round3Payload.ovc[public_types_1.OVCIndexEnum.TWO].ovcMsg4,
bitgoToOVC1Round3Payload.platform.bitgoMsg4,
],
}, [bitgoGpgPubKey, { partyId: 1, gpgKey: bitgoToOVC1Round3Payload.ovc[public_types_1.OVCIndexEnum.TWO].gpgPubKey }], [userGpgPrvKey]);
userSession.handleIncomingMessages({
p2pMessages: [],
broadcastMessages: toUserBitgoRound3Msg.broadcastMessages.map(sdk_lib_mpc_1.DklsTypes.deserializeBroadcastMessage),
});
const userPrivateMaterial = userSession.getKeyShare();
userCommonKeychain = sdk_lib_mpc_1.DklsTypes.getCommonKeychain(userPrivateMaterial);
assert.equal(bitgoToOVC1Round3Payload.platform.commonKeychain, userCommonKeychain, 'User and Bitgo Common keychains do not match');
const userPrv = userPrivateMaterial.toString('base64');
assert.ok(userPrv, 'userPrv not found');
OVC1ToOVC2Round4Payload = {
bitgoKeyId: bitgoToOVC1Round3Payload.bitGoKeyId,
...bitgoToOVC1Round3Payload,
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC2GenerateKey,
};
}
// Round 4 Backup
let backupCommonKeychain;
{
assert.ok(OVC1ToOVC2Round4Payload.state === 10, 'OVC1ToOVC2Round4Payload.state should be 10');
assert.ok(OVC1ToOVC2Round4Payload.bitgoKeyId, 'OVC1ToOVC2Round4Payload.bitGoKeyId not found');
const toBackupBitgoRound3Msg = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [],
broadcastMessages: [
OVC1ToOVC2Round4Payload.ovc[public_types_1.OVCIndexEnum.ONE].ovcMsg4,
OVC1ToOVC2Round4Payload.platform.bitgoMsg4,
],
}, [bitgoGpgPubKey, { partyId: 0, gpgKey: OVC1ToOVC2Round4Payload.ovc[public_types_1.OVCIndexEnum.ONE].gpgPubKey }], [backupGpgPrvKey]);
backupSession.handleIncomingMessages({
p2pMessages: [],
broadcastMessages: toBackupBitgoRound3Msg.broadcastMessages.map(sdk_lib_mpc_1.DklsTypes.deserializeBroadcastMessage),
});
const backupPrivateMaterial = backupSession.getKeyShare();
backupCommonKeychain = sdk_lib_mpc_1.DklsTypes.getCommonKeychain(backupPrivateMaterial);
assert.equal(OVC1ToOVC2Round4Payload.platform.commonKeychain, backupCommonKeychain, 'Backup and Bitgo Common keychains do not match');
const backupPrv = backupPrivateMaterial.toString('base64');
assert.ok(backupPrv, 'backupPrv not found');
}
// Round 4 BitGo
// creates user and backup keychain
const keychains = await MPCv2SMCUtils.uploadClientKeys(bitgoToOVC1Round3Payload.bitGoKeyId, userCommonKeychain, backupCommonKeychain);
assert.deepEqual(keychains.userKeychain, {
commonKeychain: userCommonKeychain,
type: 'tss',
source: 'user',
id: 'user',
});
assert.deepEqual(keychains.backupKeychain, {
commonKeychain: backupCommonKeychain,
type: 'tss',
source: 'backup',
id: 'backup',
});
assert.ok(round1Nock.isDone());
assert.ok(round2Nock.isDone());
assert.ok(round3Nock.isDone());
assert.ok(addKeyNock.isDone());
});
it('should throw for MPCv2 SMC utils if the state is invalid', async function () {
const MPCv2SMCUtils = new sdk_core_1.ECDSAUtils.MPCv2SMCUtils(bitgo, baseCoin);
const invalidPayload = {
state: public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC1Round2Data,
};
await assert.rejects(async () => await MPCv2SMCUtils.keyGenRound1('testId', invalidPayload), {
message: `Invalid state for round 1, expected: ${public_types_1.KeyCreationMPCv2StateEnum.WaitingForBitgoRound1Data}, got: ${public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC1Round2Data}`,
});
await assert.rejects(async () => await MPCv2SMCUtils.keyGenRound2('testId', invalidPayload), {
message: `Invalid state for round 2, expected: ${public_types_1.KeyCreationMPCv2StateEnum.WaitingForBitgoRound2Data}, got: ${public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC1Round2Data}`,
});
await assert.rejects(async () => await MPCv2SMCUtils.keyGenRound3('testId', invalidPayload), {
message: `Invalid state for round 3, expected: ${public_types_1.KeyCreationMPCv2StateEnum.WaitingForBitgoRound3Data}, got: ${public_types_1.KeyCreationMPCv2StateEnum.WaitingForOVC1Round2Data}`,
});
});
});
async function nockKeychain(params, times = 1) {
nock(bgUrl)
.post(`/api/v2/${params.coin}/key`, (body) => {
return body.keyType === 'tss' && body.source === params.source;
})
.times(times)
.reply(200, params.keyChain);
return params.keyChain;
}
async function nockGetBitgoPublicKeyBasedOnFeatureFlags(coin, enterpriseId, bitgoGpgKeyPair) {
const bitgoGPGPublicKeyResponse = {
name: 'irrelevant',
publicKey: bitgoGpgKeyPair.publicKey,
mpcv2PublicKey: bitgoGpgKeyPair.publicKey,
enterpriseId,
};
nock(bgUrl).get(`/api/v2/${coin}/tss/pubkey`).query({ enterpriseId }).reply(200, bitgoGPGPublicKeyResponse);
return bitgoGPGPublicKeyResponse;
}
async function nockKeyGenRound1(bitgoSession, times = 1) {
return nock(bgUrl)
.post(`/api/v2/mpc/generatekey`, (body) => body.round === 'MPCv2-R1')
.times(times)
.reply(200, async (uri, { payload }) => {
const { userGpgPublicKey, backupGpgPublicKey, userMsg1, backupMsg1 } = payload;
userGpgPubKey = {
partyId: 0,
gpgKey: userGpgPublicKey,
};
backupGpgPubKey = {
partyId: 1,
gpgKey: backupGpgPublicKey,
};
const bitgoBroadcastMsg1Unsigned = await bitgoSession.initDkg();
const bitgoMsgs1Signed = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages({ broadcastMessages: [sdk_lib_mpc_1.DklsTypes.serializeBroadcastMessage(bitgoBroadcastMsg1Unsigned)], p2pMessages: [] }, [], [bitgoGpgPrvKey]);
const bitgoMsg1 = bitgoMsgs1Signed.broadcastMessages.find((m) => m.from === 2);
assert.ok(bitgoMsg1, 'bitgoMsg1 not found');
const round1IncomingMsgs = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [],
broadcastMessages: [
{ from: 0, payload: userMsg1 },
{ from: 1, payload: backupMsg1 },
],
}, [userGpgPubKey, backupGpgPubKey], [bitgoGpgPrvKey]);
const round2Messages = sdk_lib_mpc_1.DklsTypes.serializeMessages(bitgoSession.handleIncomingMessages(sdk_lib_mpc_1.DklsTypes.deserializeMessages(round1IncomingMsgs)));
const round2SignedMessages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(round2Messages, [userGpgPubKey, backupGpgPubKey], [bitgoGpgPrvKey]);
const bitgoToUserMsg2 = round2SignedMessages.p2pMessages.find((m) => m.to === 0);
const bitgoToBackupMsg2 = round2SignedMessages.p2pMessages.find((m) => m.to === 1);
assert.ok(bitgoToUserMsg2, 'bitgoToUserMsg2 not found');
assert.ok(bitgoToBackupMsg2, 'bitgoToBackupMsg2 not found');
assert.ok(bitgoToUserMsg2.commitment, 'bitgoToUserMsg2.commitment not found');
storedBitgoCommitment2 = bitgoToUserMsg2?.commitment;
return {
sessionId: 'testid',
bitgoMsg1: { from: 2, ...bitgoMsg1.payload },
bitgoToBackupMsg2: {
from: 2,
to: 1,
encryptedMessage: bitgoToBackupMsg2.payload.encryptedMessage,
signature: bitgoToBackupMsg2.payload.signature,
},
bitgoToUserMsg2: {
from: 2,
to: 0,
encryptedMessage: bitgoToUserMsg2.payload.encryptedMessage,
signature: bitgoToUserMsg2.payload.signature,
},
walletGpgPubKeySigs: 'something',
};
});
}
async function nockKeyGenRound2(bitgoSession, times = 1) {
return nock(bgUrl)
.post(`/api/v2/mpc/generatekey`, (body) => body.round === 'MPCv2-R2')
.times(times)
.reply(200, async (uri, { payload }) => {
const { sessionId, userMsg2, backupMsg2, userCommitment2, backupCommitment2 } = payload;
storedUserCommitment2 = userCommitment2;
storedBackupCommitment2 = backupCommitment2;
const round2IncomingMsgs = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [
{
from: userMsg2.from,
to: userMsg2.to,
payload: { signature: userMsg2.signature, encryptedMessage: userMsg2.encryptedMessage },
},
{
from: backupMsg2.from,
to: backupMsg2.to,
payload: { signature: backupMsg2.signature, encryptedMessage: backupMsg2.encryptedMessage },
},
],
broadcastMessages: [],
}, [userGpgPubKey, backupGpgPubKey], [bitgoGpgPrvKey]);
const round3Messages = sdk_lib_mpc_1.DklsTypes.serializeMessages(bitgoSession.handleIncomingMessages(sdk_lib_mpc_1.DklsTypes.deserializeMessages(round2IncomingMsgs)));
const round3SignedMessages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(round3Messages, [userGpgPubKey, backupGpgPubKey], [bitgoGpgPrvKey]);
const bitgoToUserMsg3 = round3SignedMessages.p2pMessages.find((m) => m.to === 0);
const bitgoToBackupMsg3 = round3SignedMessages.p2pMessages.find((m) => m.to === 1);
assert.ok(bitgoToUserMsg3, 'bitgoToUserMsg3 not found');
assert.ok(bitgoToBackupMsg3, 'bitgoToBackupMsg3 not found');
return {
sessionId,
bitgoCommitment2: storedBitgoCommitment2,
bitgoToUserMsg3: {
from: 2,
to: 0,
encryptedMessage: bitgoToUserMsg3.payload.encryptedMessage,
signature: bitgoToUserMsg3.payload.signature,
},
bitgoToBackupMsg3: {
from: 2,
to: 1,
encryptedMessage: bitgoToBackupMsg3.payload.encryptedMessage,
signature: bitgoToBackupMsg3.payload.signature,
},
};
});
}
async function nockKeyGenRound3(bitgoSession, times = 1) {
return nock(bgUrl)
.post(`/api/v2/mpc/generatekey`, (body) => body.round === 'MPCv2-R3')
.times(times)
.reply(200, async (uri, { payload }) => {
const { sessionId, userMsg3, userMsg4, backupMsg3, backupMsg4 } = payload;
const round3IncomingMsgs = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [
{
from: userMsg3.from,
to: userMsg3.to,
payload: { signature: userMsg3.signature, encryptedMessage: userMsg3.encryptedMessage },
commitment: storedUserCommitment2,
},
{
from: backupMsg3.from,
to: backupMsg3.to,
payload: { signature: backupMsg3.signature, encryptedMessage: backupMsg3.encryptedMessage },
commitment: storedBackupCommitment2,
},
],
broadcastMessages: [],
}, [userGpgPubKey, backupGpgPubKey], [bitgoGpgPrvKey]);
const round4Messages = sdk_lib_mpc_1.DklsTypes.serializeMessages(bitgoSession.handleIncomingMessages(sdk_lib_mpc_1.DklsTypes.deserializeMessages(round3IncomingMsgs)));
const round4SignedMessages = await sdk_lib_mpc_1.DklsComms.encryptAndAuthOutgoingMessages(round4Messages, [], [bitgoGpgPrvKey]);
const bitgoMsg4 = round4SignedMessages.broadcastMessages.find((m) => m.from === 2);
assert.ok(bitgoMsg4, 'bitgoMsg4 not found');
const round4IncomingMsgs = await sdk_lib_mpc_1.DklsComms.decryptAndVerifyIncomingMessages({
p2pMessages: [],
broadcastMessages: [
{
from: userMsg4.from,
payload: { signature: userMsg4.signature, message: userMsg4.message },
},
{
from: backupMsg4.from,
payload: { signature: backupMsg4.signature, message: backupMsg4.message },
},
],
}, [userGpgPubKey, backupGpgPubKey], []);
bitgoSession.handleIncomingMessages(sdk_lib_mpc_1.DklsTypes.deserializeMessages(round4IncomingMsgs));
const keyShare = bitgoSession.getKeyShare();
const commonKeychain = sdk_lib_mpc_1.DklsTypes.getCommonKeychain(keyShare);
return {
sessionId,
commonKeychain: commonKeychain,
bitgoMsg4: { from: 2, ...bitgoMsg4.payload },
};
});
}
async function nockAddKeyChain(coin, times = 1) {
return nock('https://bitgo.fakeurl')
.post(`/api/v2/${coin}/key`, (body) => body.keyType === 'tss' && body.isMPCv2)
.times(times)
.reply(200, async (uri, requestBody) => {
const key = {
id: requestBody.source,
source: requestBody.source,
type: requestBody.keyType,
commonKeychain: requestBody.commonKeychain,
encryptedPrv: requestBody.encryptedPrv,
};
// nock gets
nock('https://bitgo.fakeurl').get(`/api/v2/${coin}/key/${requestBody.source}`).reply(200, key);
return key;
});
}
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlS2V5Y2hhaW5zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vdGVzdC92Mi91bml0L2ludGVybmFsL3Rzc1V0aWxzL2VjZHNhTVBDdjIvY3JlYXRlS2V5Y2hhaW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsaUNBQWlDO0FBQ2pDLDZCQUE4QjtBQUM5QixtQ0FBbUM7QUFDbkMsaUNBQWlDO0FBRWpDLDhDQUF3RDtBQUN4RCw4Q0FBcUc7QUFDckcsb0RBQXlHO0FBQ3pHLHNEQWlCNkI7QUFFN0IsK0NBQWlFO0FBQ2pFLHdEQUF3RDtBQUV4RCxRQUFRLENBQUMsd0JBQXdCLEVBQUUsS0FBSztJQUN0QyxNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUM7SUFDekIsTUFBTSxRQUFRLEdBQUcsa0NBQWtDLENBQUM7SUFDcEQsTUFBTSxZQUFZLEdBQUcsa0NBQWtDLENBQUM7SUFDeEQsSUFBSSxxQkFBNkIsQ0FBQztJQUNsQyxJQUFJLHVCQUErQixDQUFDO0lBQ3BDLElBQUksc0JBQThCLENBQUM7SUFFbkMsSUFBSSxLQUFhLENBQUM7SUFDbEIsSUFBSSxRQUFvQyxDQUFDO0lBQ3pDLElBQUksTUFBYyxDQUFDO0lBQ25CLElBQUksS0FBeUIsQ0FBQztJQUM5QixJQUFJLFFBQWtCLENBQUM7SUFDdkIsSUFBSSxXQUVILENBQUM7SUFDRixJQUFJLFNBQTJFLENBQUM7SUFDaEYsSUFBSSxjQUFtRCxDQUFDO0lBQ3hELElBQUksYUFBa0QsQ0FBQztJQUN2RCxJQUFJLGVBQW9ELENBQUM7SUFDekQsSUFBSSxjQUFtRCxDQUFDO0lBRXhELFVBQVUsQ0FBQyxLQUFLO1FBQ2QsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ2hCLE1BQU0sd0NBQXdDLENBQUMsUUFBUSxFQUFFLFlBQVksRUFBRSxXQUFXLENBQUMsQ0FBQztRQUNwRixJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDLDBCQUEw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