PHP WebShell

Текущая директория: /opt/BitGoJS/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsaMPCv2

Просмотр файла: createKeychains.ts

import * as assert from 'assert';
import * as nock from 'nock';
import * as openpgp from 'openpgp';
import * as crypto from 'crypto';

import { TestableBG, TestBitGo } from '@bitgo/sdk-test';
import { AddKeychainOptions, BaseCoin, common, ECDSAUtils, Keychain, Wallet } from '@bitgo/sdk-core';
import { bigIntToBufferBE, DklsComms, DklsDkg, DklsDsg, DklsTypes, DklsUtils } from '@bitgo/sdk-lib-mpc';
import {
  KeyCreationMPCv2StateEnum,
  MPCv2KeyGenRound1Request,
  MPCv2KeyGenRound1Response,
  MPCv2KeyGenRound2Request,
  MPCv2KeyGenRound2Response,
  MPCv2KeyGenRound3Request,
  MPCv2KeyGenRound3Response,
  OVC1ToBitgoRound3Payload,
  OVC1ToOVC2Round1Payload,
  OVC1ToOVC2Round2Payload,
  OVC1ToOVC2Round3Payload,
  OVC2ToBitgoRound1Payload,
  OVC2ToBitgoRound2Payload,
  OVC2ToOVC1Round3Payload,
  OVCIndexEnum,
  WalletTypeEnum,
} from '@bitgo/public-types';
import { NonEmptyString } from 'io-ts-types';
import { BitGo, BitgoGPGPublicKey } from '../../../../../../src';
import * as v1Fixtures from './fixtures/mpcv1KeyShares';

describe('TSS Ecdsa MPCv2 Utils:', async function () {
  const coinName = 'hteth';
  const walletId = '5b34252f1bf349930e34020a00000000';
  const enterpriseId = '6449153a6f6bc20006d66771cdbe15d3';
  let storedUserCommitment2: string;
  let storedBackupCommitment2: string;
  let storedBitgoCommitment2: string;

  let bgUrl: string;
  let tssUtils: ECDSAUtils.EcdsaMPCv2Utils;
  let wallet: Wallet;
  let bitgo: TestableBG & BitGo;
  let baseCoin: BaseCoin;
  let bitGoGgpKey: openpgp.SerializedKeyPair<string> & {
    revocationCertificate: string;
  };
  let constants: { mpc: { bitgoPublicKey: string; bitgoMPCv2PublicKey: string } };
  let bitgoGpgPrvKey: { partyId: number; gpgKey: string };
  let userGpgPubKey: { partyId: number; gpgKey: string };
  let backupGpgPubKey: { partyId: number; gpgKey: string };
  let bitgoGpgPubKey: { partyId: number; gpgKey: string };

  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 = TestBitGo.decorate(BitGo, { env: 'mock' });
    bitgo.initializeTestVars();

    baseCoin = bitgo.coin(coinName);

    bgUrl = common.Environments[bitgo.getEnv()].uri;

    const walletData = {
      id: walletId,
      enterprise: enterpriseId,
      coin: coinName,
      coinSpecific: {},
      multisigType: 'tss',
    };
    wallet = new Wallet(bitgo, baseCoin, walletData);
    tssUtils = new 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: DklsTypes.RetrofitData = {
        xiList: retrofitData.mpcv2UserKeyShare.xiList,
        xShare: v1Fixtures.mockBitGoShareSigningMaterial.xShare,
      };
      const [user, backup, bitgo] = await 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 DklsUtils.executeTillRound(
        5,
        new DklsDsg.Dsg(user.getKeyShare(), 0, derivationPath, messageToSign),
        new DklsDsg.Dsg(backup.getKeyShare(), 1, derivationPath, messageToSign)
      );
      const convertedSignature = DklsUtils.verifyAndConvertDklsSignature(
        Buffer.from('ffff', 'hex'),
        signature as DklsTypes.DeserializedDklsSignature,
        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 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(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(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(bigIntToBufferBE(BigInt(1), 32)),
        Array.from(bigIntToBufferBE(BigInt(2), 32)),
        Array.from(bigIntToBufferBE(BigInt(3), 32)),
      ];
      const bitgoRetrofitData: DklsTypes.RetrofitData = {
        xiList,
        xShare: v1Fixtures.mockBitGoShareSigningMaterial.xShare,
      };
      const bitgoSession = new DklsDkg.Dkg(3, 2, 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: Parameters<typeof tssUtils.createKeychains>[0] = {
        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(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(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(ECDSAUtils.MPCv2PartiesEnum.BITGO, 'test');
      const usersKeychainPromise = tssUtils.createParticipantKeychain(
        ECDSAUtils.MPCv2PartiesEnum.USER,
        'test',
        Buffer.from('test'),
        Buffer.from('test'),
        'passphrase',
        'test'
      );
      const backupKeychainPromise = tssUtils.createParticipantKeychain(
        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 ECDSAUtils.MPCv2SMCUtils(bitgo, baseCoin);
      const bitgoSession = new 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 DklsDkg.Dkg(3, 2, 0);
      let OVC1ToOVC2Round1Payload: OVC1ToOVC2Round1Payload;
      {
        const userBroadcastMsg1Unsigned = await userSession.initDkg();
        const userMsgs1Signed = await DklsComms.encryptAndAuthOutgoingMessages(
          { broadcastMessages: [DklsTypes.serializeBroadcastMessage(userBroadcastMsg1Unsigned)], p2pMessages: [] },
          [],
          [userGpgPrvKey]
        );
        const userMsg1 = userMsgs1Signed.broadcastMessages.find((m) => m.from === 0);
        assert(userMsg1, 'userMsg1 not found');

        OVC1ToOVC2Round1Payload = {
          tssVersion: '0.0.1' as NonEmptyString,
          walletType: WalletTypeEnum.tss,
          coin: 'eth' as NonEmptyString,
          state: KeyCreationMPCv2StateEnum.WaitingForOVC2Round1Data,
          ovc: {
            [OVCIndexEnum.ONE]: {
              gpgPubKey: userGgpKey.publicKey as NonEmptyString,
              ovcMsg1: userMsg1 as any,
            },
          },
        };
      }

      // 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 DklsDkg.Dkg(3, 2, 1);
      let OVC2ToBitgoRound1Payload: OVC2ToBitgoRound1Payload;
      {
        assert(OVC1ToOVC2Round1Payload.state === 0, 'OVC1ToOVC2Round1Payload.state should be 0');
        const backupBroadcastMsg1Unsigned = await backupSession.initDkg();
        const backupMsgs1Signed = await DklsComms.encryptAndAuthOutgoingMessages(
          { broadcastMessages: [DklsTypes.serializeBroadcastMessage(backupBroadcastMsg1Unsigned)], p2pMessages: [] },
          [],
          [backupGpgPrvKey]
        );
        const backupMsg1 = backupMsgs1Signed.broadcastMessages.find((m) => m.from === 1);
        assert(backupMsg1, 'backupMsg1 not found');

        OVC2ToBitgoRound1Payload = {
          ...OVC1ToOVC2Round1Payload,
          state: KeyCreationMPCv2StateEnum.WaitingForBitgoRound1Data,
          ovc: {
            ...OVC1ToOVC2Round1Payload.ovc,
            [OVCIndexEnum.TWO]: {
              gpgPubKey: backupGgpKey.publicKey as NonEmptyString,
              ovcMsg1: backupMsg1 as any,
            },
          },
        };
      }

      // Round 1 BitGo
      const bitgoToOVC1Round1Payload = await MPCv2SMCUtils.keyGenRound1('testId', OVC2ToBitgoRound1Payload);

      // Round 2 User
      let OVC1ToOVC2Round2Payload: OVC1ToOVC2Round2Payload;
      {
        assert(bitgoToOVC1Round1Payload.state === 2, 'bitgoToOVC1Round1Payload.state should be 2');
        const toUserRound1BroadcastMessages = await DklsComms.decryptAndVerifyIncomingMessages(
          {
            p2pMessages: [],
            broadcastMessages: [
              bitgoToOVC1Round1Payload.ovc[OVCIndexEnum.TWO].ovcMsg1,
              bitgoToOVC1Round1Payload.platform.bitgoMsg1,
            ],
          },
          [bitgoGpgPubKey, { partyId: 1, gpgKey: bitgoToOVC1Round1Payload.ovc[OVCIndexEnum.TWO].gpgPubKey }],
          [userGpgPrvKey]
        );

        const userRound2P2PMessages = userSession.handleIncomingMessages({
          p2pMessages: [],
          broadcastMessages: toUserRound1BroadcastMessages.broadcastMessages.map(DklsTypes.deserializeBroadcastMessage),
        });
        const userRound2Messages = await DklsComms.encryptAndAuthOutgoingMessages(
          DklsTypes.serializeMessages(userRound2P2PMessages),
          [{ partyId: 1, gpgKey: bitgoToOVC1Round1Payload.ovc[OVCIndexEnum.TWO].gpgPubKey }, bitgoGpgPubKey],
          [userGpgPrvKey]
        );
        const userToBackupMsg2 = userRound2Messages.p2pMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.USER && m.to === ECDSAUtils.MPCv2PartiesEnum.BACKUP
        );
        assert(userToBackupMsg2, 'userToBackupMsg2 not found');
        const userToBitgoMsg2 = userRound2Messages.p2pMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.USER && m.to === ECDSAUtils.MPCv2PartiesEnum.BITGO
        );
        assert(userToBitgoMsg2, 'userToBitgoMsg2 not found');

        OVC1ToOVC2Round2Payload = {
          ...bitgoToOVC1Round1Payload,
          state: KeyCreationMPCv2StateEnum.WaitingForOVC2Round2Data,
          ovc: {
            ...bitgoToOVC1Round1Payload.ovc,
            [OVCIndexEnum.ONE]: Object.assign(bitgoToOVC1Round1Payload.ovc[OVCIndexEnum.ONE], {
              ovcToBitgoMsg2: userToBitgoMsg2,
              ovcToOvcMsg2: userToBackupMsg2,
            }) as any,
          },
        };
      }

      // Round 2 Backup
      let OVC2ToBitgoRound2Payload: OVC2ToBitgoRound2Payload;

      {
        assert(OVC1ToOVC2Round2Payload.state === 3, 'bitgoToOVC1Round1Payload.state should be 3');
        const toBackupRound1BroadcastMessages = await DklsComms.decryptAndVerifyIncomingMessages(
          {
            p2pMessages: [],
            broadcastMessages: [
              bitgoToOVC1Round1Payload.ovc[OVCIndexEnum.ONE].ovcMsg1,
              bitgoToOVC1Round1Payload.platform.bitgoMsg1,
            ],
          },
          [bitgoGpgPubKey, { partyId: 0, gpgKey: OVC1ToOVC2Round2Payload.ovc[OVCIndexEnum.ONE].gpgPubKey }],
          [backupGpgPrvKey]
        );

        const backupRound2P2PMessages = backupSession.handleIncomingMessages({
          p2pMessages: [],
          broadcastMessages: toBackupRound1BroadcastMessages.broadcastMessages.map(
            DklsTypes.deserializeBroadcastMessage
          ),
        });
        const backupRound2Messages = await DklsComms.encryptAndAuthOutgoingMessages(
          DklsTypes.serializeMessages(backupRound2P2PMessages),
          [{ partyId: 0, gpgKey: bitgoToOVC1Round1Payload.ovc[OVCIndexEnum.ONE].gpgPubKey }, bitgoGpgPubKey],
          [backupGpgPrvKey]
        );
        const backupToUserMsg2 = backupRound2Messages.p2pMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.BACKUP && m.to === ECDSAUtils.MPCv2PartiesEnum.USER
        );
        assert(backupToUserMsg2, 'backupToUserMsg2 not found');
        const backupToBitgoMsg2 = backupRound2Messages.p2pMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.BACKUP && m.to === ECDSAUtils.MPCv2PartiesEnum.BITGO
        );
        assert(backupToBitgoMsg2, 'backupToBitgoMsg2 not found');

        OVC2ToBitgoRound2Payload = {
          ...OVC1ToOVC2Round2Payload,
          state: KeyCreationMPCv2StateEnum.WaitingForBitgoRound2Data,
          ovc: {
            ...OVC1ToOVC2Round2Payload.ovc,
            [OVCIndexEnum.TWO]: Object.assign(OVC1ToOVC2Round2Payload.ovc[OVCIndexEnum.TWO], {
              ovcToBitgoMsg2: backupToBitgoMsg2,
              ovcToOvcMsg2: backupToUserMsg2,
            }) as any,
          },
        };
      }

      // Round 2 BitGo
      // call bitgo round 2
      const bitgoToOVC1Round2Payload = await MPCv2SMCUtils.keyGenRound2('testId', OVC2ToBitgoRound2Payload);

      // Round 3A User
      let OVC1ToOVC2Round3Payload: OVC1ToOVC2Round3Payload;
      {
        assert(bitgoToOVC1Round2Payload.state === 5, 'bitgoToOVC1Round2Payload.state should be 5');
        const toUserRound2P2PMessages = await DklsComms.decryptAndVerifyIncomingMessages(
          {
            p2pMessages: [
              bitgoToOVC1Round2Payload.ovc[OVCIndexEnum.TWO].ovcToOvcMsg2,
              bitgoToOVC1Round2Payload.platform.ovc[OVCIndexEnum.ONE].bitgoToOvcMsg2,
            ],
            broadcastMessages: [],
          },
          [bitgoGpgPubKey, { partyId: 1, gpgKey: bitgoToOVC1Round2Payload.ovc[OVCIndexEnum.TWO].gpgPubKey }],
          [userGpgPrvKey]
        );
        const userRound3AP2PMessages = userSession.handleIncomingMessages({
          p2pMessages: toUserRound2P2PMessages.p2pMessages.map(DklsTypes.deserializeP2PMessage),
          broadcastMessages: [],
        }).p2pMessages;

        const userRound3AMessages = await DklsComms.encryptAndAuthOutgoingMessages(
          DklsTypes.serializeMessages({
            p2pMessages: userRound3AP2PMessages,
            broadcastMessages: [],
          }),
          [{ partyId: 1, gpgKey: bitgoToOVC1Round2Payload.ovc[OVCIndexEnum.TWO].gpgPubKey }, bitgoGpgPubKey],
          [userGpgPrvKey]
        );

        const userToBitgoMsg3 = userRound3AMessages.p2pMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.USER && m.to === ECDSAUtils.MPCv2PartiesEnum.BITGO
        );
        assert(userToBitgoMsg3, 'userToBitgoMsg3 not found');
        const userToBackupMsg3 = userRound3AMessages.p2pMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.USER && m.to === ECDSAUtils.MPCv2PartiesEnum.BACKUP
        );
        assert(userToBackupMsg3, 'userToBackupMsg3 not found');

        OVC1ToOVC2Round3Payload = {
          ...bitgoToOVC1Round2Payload,
          state: KeyCreationMPCv2StateEnum.WaitingForOVC2Round3Data,
          ovc: {
            ...bitgoToOVC1Round2Payload.ovc,
            [OVCIndexEnum.ONE]: Object.assign(bitgoToOVC1Round2Payload.ovc[OVCIndexEnum.ONE], {
              ovcToBitgoMsg3: userToBitgoMsg3,
              ovcToOvcMsg3: userToBackupMsg3,
            }) as any,
          },
        };
      }

      // Round 3 Backup
      let OVC2ToOVC1Round3Payload: OVC2ToOVC1Round3Payload;
      {
        assert(OVC1ToOVC2Round3Payload.state === 6, 'OVC1ToOVC2Round3Payload.state should be 6');
        const toBackupRound3P2PMessages = await DklsComms.decryptAndVerifyIncomingMessages(
          {
            p2pMessages: [
              OVC1ToOVC2Round3Payload.ovc[OVCIndexEnum.ONE].ovcToOvcMsg2,
              OVC1ToOVC2Round3Payload.platform.ovc[OVCIndexEnum.TWO].bitgoToOvcMsg2,
            ],
            broadcastMessages: [],
          },
          [bitgoGpgPubKey, { partyId: 0, gpgKey: OVC1ToOVC2Round3Payload.ovc[OVCIndexEnum.ONE].gpgPubKey }],
          [backupGpgPrvKey]
        );

        const backupRound3P2PMessages = backupSession.handleIncomingMessages({
          p2pMessages: toBackupRound3P2PMessages.p2pMessages.map(DklsTypes.deserializeP2PMessage),
          broadcastMessages: [],
        });

        const backupRound3Messages = await DklsComms.encryptAndAuthOutgoingMessages(
          DklsTypes.serializeMessages(backupRound3P2PMessages),
          [{ partyId: 0, gpgKey: OVC1ToOVC2Round3Payload.ovc[OVCIndexEnum.ONE].gpgPubKey }, bitgoGpgPubKey],
          [backupGpgPrvKey]
        );

        const backupToBitgoMsg3 = backupRound3Messages.p2pMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.BACKUP && m.to === ECDSAUtils.MPCv2PartiesEnum.BITGO
        );
        assert(backupToBitgoMsg3, 'backupToBitgoMsg3 not found');
        const backupToUserMsg3 = backupRound3Messages.p2pMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.BACKUP && m.to === ECDSAUtils.MPCv2PartiesEnum.USER
        );
        assert(backupToUserMsg3, 'backupToUserMsg3 not found');

        const toBackupRound3Messages = await DklsComms.decryptAndVerifyIncomingMessages(
          {
            p2pMessages: [
              {
                ...OVC1ToOVC2Round3Payload.ovc[OVCIndexEnum.ONE].ovcToOvcMsg3,
                commitment: OVC1ToOVC2Round3Payload.ovc[OVCIndexEnum.ONE].ovcToOvcMsg2.commitment,
              },
              {
                ...OVC1ToOVC2Round3Payload.platform.ovc[OVCIndexEnum.TWO].bitgoToOvcMsg3,
                commitment: OVC1ToOVC2Round3Payload.platform.bitgoCommitment2,
              },
            ],
            broadcastMessages: [],
          },
          [bitgoGpgPubKey, { partyId: 0, gpgKey: OVC1ToOVC2Round3Payload.ovc[OVCIndexEnum.ONE].gpgPubKey }],
          [backupGpgPrvKey]
        );

        const backupRound4Messages = backupSession.handleIncomingMessages({
          p2pMessages: toBackupRound3Messages.p2pMessages.map(DklsTypes.deserializeP2PMessage),
          broadcastMessages: [],
        }).broadcastMessages;

        const backupRound4BroadcastMessages = await DklsComms.encryptAndAuthOutgoingMessages(
          DklsTypes.serializeMessages({
            p2pMessages: [],
            broadcastMessages: backupRound4Messages,
          }),
          [],
          [backupGpgPrvKey]
        );

        const backupMsg4 = backupRound4BroadcastMessages.broadcastMessages.find(
          (m) => m.from === ECDSAUtils.MPCv2PartiesEnum.BACKUP
        );
        assert(backupMsg4, 'backupMsg4 not found');

        OVC2ToOVC1Round3Payload = {
          ...OVC1ToOVC2Round3Payload,
          state: KeyCreationMPCv2StateEnum.WaitingForOVC1Round3bData,
          ovc: {
            ...OVC1ToOVC2Round3Payload.ovc,
            [OVCIndexEnum.TWO]: Object.assign(OVC1ToOVC2Round3Payload.ovc[OVCIndexEnum.TWO], {
              ovcToOvcMsg3: backupToUserMsg3,
              ovcToBitgoMsg3: backupToBitgoMsg3,
              ovcMsg4: backupMsg4,
            }) as any,
          },
        };
      }

      // Round 3B User
      let OVC1ToBitgoRound3BPayload: OVC1ToBitgoRound3Payload;
      {
        assert(OVC2ToOVC1Round3Payload.state === 7, 'OVC2ToOVC1Round3Payload.state should be 7');
        const toUserRound4Messages = await DklsComms.decryptAndVerifyIncomingMessages(
          {
            p2pMessages: [
              {
                ...OVC2ToOVC1Round3Payload.ovc[OVCIndexEnum.TWO].ovcToOvcMsg3,
                commitment: OVC2ToOVC1Round3Payload.ovc[OVCIndexEnum.TWO].ovcToOvcMsg2.commitment,
              },
              {
                ...OVC2ToOVC1Round3Payload.platform.ovc[OVCIndexEnum.ONE].bitgoToOvcMsg3,
                commitment: OVC2ToOVC1Round3Payload.platform.bitgoCommitment2,
              },
            ],
            broadcastMessages: [],
          },
          [bitgoGpgPubKey, { partyId: 1, gpgKey: OVC2ToOVC1Round3Payload.ovc[OVCIndexEnum.TWO].gpgPubKey }],
          [userGpgPrvKey]
        );

        const userRound4BroadcastMessages = userSession.handleIncomingMessages({
          p2pMessages: toUserRound4Messages.p2pMessages.map(DklsTypes.deserializeP2PMessage),
          broadcastMessages: [],
        }).broadcastMessages;
        assert(userRound4BroadcastMessages.length === 1, 'userRound4BroadcastMessages length should be 1');

        const userRound4Messages = await DklsComms.encryptAndAuthOutgoingMessages(
          DklsTypes.serializeMessages({
            p2pMessages: [],
            broadcastMessages: userRound4BroadcastMessages,
          }),
          [],
          [userGpgPrvKey]
        );

        const userMsg4 = userRound4Messages.broadcastMessages.find((m) => m.from === ECDSAUtils.MPCv2PartiesEnum.USER);
        assert(userMsg4, 'userMsg4 not found');

        OVC1ToBitgoRound3BPayload = {
          ...OVC2ToOVC1Round3Payload,
          state: KeyCreationMPCv2StateEnum.WaitingForBitgoRound3Data,
          ovc: {
            ...OVC2ToOVC1Round3Payload.ovc,
            [OVCIndexEnum.ONE]: Object.assign(OVC2ToOVC1Round3Payload.ovc[OVCIndexEnum.ONE], {
              ovcMsg4: userMsg4,
            }) as any,
          },
        };
      }

      // Round 3 BitGo
      // creates bitgo keychain
      const bitgoToOVC1Round3Payload = await MPCv2SMCUtils.keyGenRound3('testId', OVC1ToBitgoRound3BPayload);

      // Round 4 User
      let userCommonKeychain: string;
      let OVC1ToOVC2Round4Payload;
      {
        assert(bitgoToOVC1Round3Payload.state === 9, 'bitgoToOVC1Round3Payload.state should be 9');
        assert(bitgoToOVC1Round3Payload.bitGoKeyId, 'bitgoToOVC1Round3Payload.bitGoKeyId not found');
        const toUserBitgoRound3Msg = await DklsComms.decryptAndVerifyIncomingMessages(
          {
            p2pMessages: [],
            broadcastMessages: [
              bitgoToOVC1Round3Payload.ovc[OVCIndexEnum.TWO].ovcMsg4,
              bitgoToOVC1Round3Payload.platform.bitgoMsg4,
            ],
          },
          [bitgoGpgPubKey, { partyId: 1, gpgKey: bitgoToOVC1Round3Payload.ovc[OVCIndexEnum.TWO].gpgPubKey }],
          [userGpgPrvKey]
        );

        userSession.handleIncomingMessages({
          p2pMessages: [],
          broadcastMessages: toUserBitgoRound3Msg.broadcastMessages.map(DklsTypes.deserializeBroadcastMessage),
        });

        const userPrivateMaterial = userSession.getKeyShare();
        userCommonKeychain = DklsTypes.getCommonKeychain(userPrivateMaterial);
        assert.equal(
          bitgoToOVC1Round3Payload.platform.commonKeychain,
          userCommonKeychain,
          'User and Bitgo Common keychains do not match'
        );
        const userPrv = userPrivateMaterial.toString('base64');
        assert(userPrv, 'userPrv not found');

        OVC1ToOVC2Round4Payload = {
          bitgoKeyId: bitgoToOVC1Round3Payload.bitGoKeyId,
          ...bitgoToOVC1Round3Payload,
          state: KeyCreationMPCv2StateEnum.WaitingForOVC2GenerateKey,
        };
      }

      // Round 4 Backup
      let backupCommonKeychain: string;
      {
        assert(OVC1ToOVC2Round4Payload.state === 10, 'OVC1ToOVC2Round4Payload.state should be 10');
        assert(OVC1ToOVC2Round4Payload.bitgoKeyId, 'OVC1ToOVC2Round4Payload.bitGoKeyId not found');

        const toBackupBitgoRound3Msg = await DklsComms.decryptAndVerifyIncomingMessages(
          {
            p2pMessages: [],
            broadcastMessages: [
              OVC1ToOVC2Round4Payload.ovc[OVCIndexEnum.ONE].ovcMsg4,
              OVC1ToOVC2Round4Payload.platform.bitgoMsg4,
            ],
          },
          [bitgoGpgPubKey, { partyId: 0, gpgKey: OVC1ToOVC2Round4Payload.ovc[OVCIndexEnum.ONE].gpgPubKey }],
          [backupGpgPrvKey]
        );

        backupSession.handleIncomingMessages({
          p2pMessages: [],
          broadcastMessages: toBackupBitgoRound3Msg.broadcastMessages.map(DklsTypes.deserializeBroadcastMessage),
        });

        const backupPrivateMaterial = backupSession.getKeyShare();
        backupCommonKeychain = DklsTypes.getCommonKeychain(backupPrivateMaterial);
        assert.equal(
          OVC1ToOVC2Round4Payload.platform.commonKeychain,
          backupCommonKeychain,
          'Backup and Bitgo Common keychains do not match'
        );
        const backupPrv = backupPrivateMaterial.toString('base64');
        assert(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 ECDSAUtils.MPCv2SMCUtils(bitgo, baseCoin);
      const invalidPayload = {
        state: KeyCreationMPCv2StateEnum.WaitingForOVC1Round2Data,
      } as any;

      await assert.rejects(async () => await MPCv2SMCUtils.keyGenRound1('testId', invalidPayload), {
        message: `Invalid state for round 1, expected: ${KeyCreationMPCv2StateEnum.WaitingForBitgoRound1Data}, got: ${KeyCreationMPCv2StateEnum.WaitingForOVC1Round2Data}`,
      });
      await assert.rejects(async () => await MPCv2SMCUtils.keyGenRound2('testId', invalidPayload), {
        message: `Invalid state for round 2, expected: ${KeyCreationMPCv2StateEnum.WaitingForBitgoRound2Data}, got: ${KeyCreationMPCv2StateEnum.WaitingForOVC1Round2Data}`,
      });
      await assert.rejects(async () => await MPCv2SMCUtils.keyGenRound3('testId', invalidPayload), {
        message: `Invalid state for round 3, expected: ${KeyCreationMPCv2StateEnum.WaitingForBitgoRound3Data}, got: ${KeyCreationMPCv2StateEnum.WaitingForOVC1Round2Data}`,
      });
    });
  });

  async function nockKeychain(
    params: {
      coin: string;
      keyChain: Keychain;
      source: 'user' | 'backup' | 'bitgo';
    },
    times = 1
  ): Promise<Keychain> {
    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: string,
    enterpriseId: string,
    bitgoGpgKeyPair: openpgp.SerializedKeyPair<string>
  ): Promise<BitgoGPGPublicKey> {
    const bitgoGPGPublicKeyResponse: BitgoGPGPublicKey = {
      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: DklsDkg.Dkg, times = 1) {
    return nock(bgUrl)
      .post(`/api/v2/mpc/generatekey`, (body) => body.round === 'MPCv2-R1')
      .times(times)
      .reply(
        200,
        async (uri, { payload }: { payload: MPCv2KeyGenRound1Request }): Promise<MPCv2KeyGenRound1Response> => {
          const { userGpgPublicKey, backupGpgPublicKey, userMsg1, backupMsg1 } = payload;
          userGpgPubKey = {
            partyId: 0,
            gpgKey: userGpgPublicKey,
          };
          backupGpgPubKey = {
            partyId: 1,
            gpgKey: backupGpgPublicKey,
          };

          const bitgoBroadcastMsg1Unsigned = await bitgoSession.initDkg();
          const bitgoMsgs1Signed = await DklsComms.encryptAndAuthOutgoingMessages(
            { broadcastMessages: [DklsTypes.serializeBroadcastMessage(bitgoBroadcastMsg1Unsigned)], p2pMessages: [] },
            [],
            [bitgoGpgPrvKey]
          );
          const bitgoMsg1 = bitgoMsgs1Signed.broadcastMessages.find((m) => m.from === 2);
          assert(bitgoMsg1, 'bitgoMsg1 not found');

          const round1IncomingMsgs = await DklsComms.decryptAndVerifyIncomingMessages(
            {
              p2pMessages: [],
              broadcastMessages: [
                { from: 0, payload: userMsg1 },
                { from: 1, payload: backupMsg1 },
              ],
            },
            [userGpgPubKey, backupGpgPubKey],
            [bitgoGpgPrvKey]
          );

          const round2Messages = DklsTypes.serializeMessages(
            bitgoSession.handleIncomingMessages(DklsTypes.deserializeMessages(round1IncomingMsgs))
          );

          const round2SignedMessages = await 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(bitgoToUserMsg2, 'bitgoToUserMsg2 not found');
          assert(bitgoToBackupMsg2, 'bitgoToBackupMsg2 not found');
          assert(bitgoToUserMsg2.commitment, 'bitgoToUserMsg2.commitment not found');

          storedBitgoCommitment2 = bitgoToUserMsg2?.commitment;
          return {
            sessionId: 'testid' as NonEmptyString,
            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' as NonEmptyString,
          };
        }
      );
  }

  async function nockKeyGenRound2(bitgoSession: DklsDkg.Dkg, times = 1) {
    return nock(bgUrl)
      .post(`/api/v2/mpc/generatekey`, (body) => body.round === 'MPCv2-R2')
      .times(times)
      .reply(
        200,
        async (uri, { payload }: { payload: MPCv2KeyGenRound2Request }): Promise<MPCv2KeyGenRound2Response> => {
          const { sessionId, userMsg2, backupMsg2, userCommitment2, backupCommitment2 } = payload;
          storedUserCommitment2 = userCommitment2;
          storedBackupCommitment2 = backupCommitment2;
          const round2IncomingMsgs = await 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 = DklsTypes.serializeMessages(
            bitgoSession.handleIncomingMessages(DklsTypes.deserializeMessages(round2IncomingMsgs))
          );

          const round3SignedMessages = await 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(bitgoToUserMsg3, 'bitgoToUserMsg3 not found');
          assert(bitgoToBackupMsg3, 'bitgoToBackupMsg3 not found');

          return {
            sessionId,
            bitgoCommitment2: storedBitgoCommitment2 as NonEmptyString,
            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: DklsDkg.Dkg, times = 1) {
    return nock(bgUrl)
      .post(`/api/v2/mpc/generatekey`, (body) => body.round === 'MPCv2-R3')
      .times(times)
      .reply(
        200,
        async (uri, { payload }: { payload: MPCv2KeyGenRound3Request }): Promise<MPCv2KeyGenRound3Response> => {
          const { sessionId, userMsg3, userMsg4, backupMsg3, backupMsg4 } = payload;

          const round3IncomingMsgs = await 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 = DklsTypes.serializeMessages(
            bitgoSession.handleIncomingMessages(DklsTypes.deserializeMessages(round3IncomingMsgs))
          );
          const round4SignedMessages = await DklsComms.encryptAndAuthOutgoingMessages(
            round4Messages,
            [],
            [bitgoGpgPrvKey]
          );
          const bitgoMsg4 = round4SignedMessages.broadcastMessages.find((m) => m.from === 2);
          assert(bitgoMsg4, 'bitgoMsg4 not found');

          const round4IncomingMsgs = await 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(DklsTypes.deserializeMessages(round4IncomingMsgs));
          const keyShare = bitgoSession.getKeyShare();
          const commonKeychain = DklsTypes.getCommonKeychain(keyShare);

          return {
            sessionId,
            commonKeychain: commonKeychain as NonEmptyString,
            bitgoMsg4: { from: 2, ...bitgoMsg4.payload },
          };
        }
      );
  }

  async function nockAddKeyChain(coin: string, 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: AddKeychainOptions) => {
        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;
      });
  }
});

Выполнить команду


Для локальной разработки. Не используйте в интернете!