UNPKG

bitgo

Version:
756 lines • 146 kB
"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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