PHP WebShell

Текущая директория: /opt/BitGoJS/modules/account-lib/test/unit/mpc/tss/ecdsa

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

/**
 * @prettier
 */
import assert from 'assert';
import { Hash, randomBytes } from 'crypto';
import { Ecdsa, ECDSA, hexToBigInt } from '@bitgo/sdk-core';
import { EcdsaPaillierProof, EcdsaTypes, Schnorr, SchnorrProof } from '@bitgo/sdk-lib-mpc';
import * as sinon from 'sinon';
import createKeccakHash from 'keccak';
import * as paillierBigint from 'paillier-bigint';
import {
  schnorrProofs,
  ntildes,
  paillerKeys,
  mockNShares,
  mockPShare,
  mockDKeyShare,
  mockEKeyShare,
  mockFKeyShare,
} from '../fixtures/ecdsa';

describe('TSS ECDSA TESTS', function () {
  const MPC = new Ecdsa();
  const base = BigInt('0x010000000000000000000000000000000000000000000000000000000000000000'); // 2^256
  let keyShares: ECDSA.KeyCombined[];
  let commonPublicKey: string;
  const seed = Buffer.from(
    'c4d1583a0b7b88626b56f0c83ee6df4d95d99cca73893ffb57c5e4411fa1b2b9c87456080e8d3f03462f065688abc28be2d4af3164d593c50b55269b435ea48d',
    'hex',
  );
  let A: ECDSA.KeyShare, B: ECDSA.KeyShare, C: ECDSA.KeyShare;
  before(async () => {
    const paillierMock = sinon
      .stub(paillierBigint, 'generateRandomKeys')
      .onCall(0)
      .resolves(paillerKeys[0] as unknown as paillierBigint.KeyPair)
      .onCall(1)
      .resolves(paillerKeys[1] as unknown as paillierBigint.KeyPair)
      .onCall(2)
      .resolves(paillerKeys[2] as unknown as paillierBigint.KeyPair)
      .onCall(3)
      .resolves(paillerKeys[0] as unknown as paillierBigint.KeyPair)
      .onCall(4)
      .resolves(paillerKeys[1] as unknown as paillierBigint.KeyPair)
      .onCall(5)
      .resolves(paillerKeys[2] as unknown as paillierBigint.KeyPair);

    const schnorrProofMock = sinon
      .stub(Schnorr, 'createSchnorrProof')
      .onCall(0)
      .returns(schnorrProofs[0] as unknown as SchnorrProof)
      .onCall(1)
      .returns(schnorrProofs[1] as unknown as SchnorrProof)
      .onCall(2)
      .returns(schnorrProofs[2] as unknown as SchnorrProof)
      .onCall(3)
      .returns(schnorrProofs[3] as unknown as SchnorrProof)
      .onCall(4)
      .returns(schnorrProofs[4] as unknown as SchnorrProof)
      .onCall(5)
      .returns(schnorrProofs[5] as unknown as SchnorrProof);

    [A, B, C] = await Promise.all([MPC.keyShare(1, 2, 3), MPC.keyShare(2, 2, 3), MPC.keyShare(3, 2, 3)]);

    // Needs to run this serially for testing deterministic key generation
    // to get specific paillier keys to be assigned
    const D = await MPC.keyShare(1, 2, 3, seed);
    const E = await MPC.keyShare(2, 2, 3, seed);
    const F = await MPC.keyShare(3, 2, 3, seed);

    const aKeyCombine = MPC.keyCombine(A.pShare, [B.nShares[1], C.nShares[1]]);
    const bKeyCombine = MPC.keyCombine(B.pShare, [A.nShares[2], C.nShares[2]]);
    const cKeyCombine = MPC.keyCombine(C.pShare, [A.nShares[3], B.nShares[3]]);

    // Shares with specific seeds
    const dKeyCombine = MPC.keyCombine(D.pShare, [E.nShares[1], F.nShares[1]]);
    const eKeyCombine = MPC.keyCombine(E.pShare, [D.nShares[2], F.nShares[2]]);
    const fKeyCombine = MPC.keyCombine(F.pShare, [D.nShares[3], E.nShares[3]]);

    // Shares for derived keys.
    const path = 'm/0/1';
    const aKeyDerive = MPC.keyDerive(A.pShare, [B.nShares[1], C.nShares[1]], path);
    const gKeyCombine: ECDSA.KeyCombined = {
      xShare: aKeyDerive.xShare,
      yShares: aKeyCombine.yShares,
    };
    const hKeyCombine = MPC.keyCombine(B.pShare, [aKeyDerive.nShares[2], C.nShares[2]]);
    keyShares = [
      aKeyCombine,
      bKeyCombine,
      cKeyCombine,
      dKeyCombine,
      eKeyCombine,
      fKeyCombine,
      gKeyCombine,
      hKeyCombine,
    ];
    commonPublicKey = aKeyCombine.xShare.y;
    paillierMock.reset();
    paillierMock.restore();
    schnorrProofMock.reset();
    schnorrProofMock.restore();
  });

  describe('Ecdsa Key Generation Test', function () {
    it('should generate keys with correct threshold and share number', async function () {
      for (let index = 0; index < 3; index++) {
        const participantOne = (index % 3) + 1;
        const participantTwo = ((index + 1) % 3) + 1;
        const participantThree = ((index + 2) % 3) + 1;
        keyShares[index].xShare.i.should.equal(participantOne);
        keyShares[index].xShare.y.should.equal(commonPublicKey);
        keyShares[index].xShare.m.should.not.be.Null;
        keyShares[index].xShare.l.should.not.be.Null;
        keyShares[index].xShare.n.should.not.be.Null;

        const chaincode = BigInt('0x' + keyShares[index].xShare.chaincode);
        const isChainCodeValid = chaincode > BigInt(0) && chaincode <= base;
        isChainCodeValid.should.equal(true);

        keyShares[index].yShares[participantTwo].i.should.equal(participantOne);
        keyShares[index].yShares[participantThree].i.should.equal(participantOne);
        keyShares[index].yShares[participantTwo].j.should.equal(participantTwo);
        keyShares[index].yShares[participantThree].j.should.equal(participantThree);
        keyShares[index].yShares[participantTwo].n.should.not.be.Null;
        keyShares[index].yShares[participantThree].n.should.not.be.Null;

        const publicKeyPrefix = keyShares[index].xShare.y.slice(0, 2);
        const isRightPrefix = publicKeyPrefix === '03' || publicKeyPrefix === '02';
        isRightPrefix.should.equal(true);
      }
    });

    it('should generate keyshares with specific seed', async function () {
      // Keys should be deterministic when using seed
      const [, , , D, E, F] = keyShares;
      assert.deepEqual(D, mockDKeyShare);
      assert.deepEqual(E, mockEKeyShare);
      assert.deepEqual(F, mockFKeyShare);
    });

    it('should fail if seed is length less than 64 bytes', async function () {
      await MPC.keyShare(1, 2, 3, randomBytes(16)).should.be.rejectedWith(
        'Seed must have a length of at least 64 bytes',
      );
      await MPC.keyShare(1, 2, 3, randomBytes(32)).should.be.rejectedWith(
        'Seed must have a length of at least 64 bytes',
      );
    });

    it('should pass if seed length is greater than 64', async function () {
      const paillierMock = sinon
        .stub(paillierBigint, 'generateRandomKeys')
        .onCall(0)
        .resolves(paillerKeys[0] as unknown as paillierBigint.KeyPair);
      const seed72Bytes = Buffer.from(
        '4f7e914dc9ec696398675d1544aab61cb7a67662ffcbdb4079ec5d682be565d87c1b2de75c943dec14c96586984860268779498e6732473aed9ed9c2538f50bea0af926bdccc0134',
        'hex',
      );
      (await MPC.keyShare(1, 2, 3, seed72Bytes)).pShare.u.length.should.equal(64);
      paillierMock.restore();
    });

    it('should calculate correct chaincode while combining', async function () {
      const keyCombine = MPC.keyCombine(mockPShare, mockNShares);
      keyCombine.xShare.chaincode.should.equal('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc32');
    });

    it('should fail to generate keys with invalid threshold and share number', async function () {
      const invalidConfigs = [
        { index: 1, threshold: 5, numShares: 3 },
        { index: -1, threshold: 2, numShares: 3 },
        { index: 1, threshold: 2, numShares: 1 },
      ];
      for (let index = 0; index < invalidConfigs.length; index++) {
        try {
          await MPC.keyShare(
            invalidConfigs[index].index,
            invalidConfigs[index].threshold,
            invalidConfigs[index].numShares,
          );
        } catch (e) {
          e.should.equal('Invalid KeyShare Config');
        }
      }
    });

    it('should derive unhardened child keys', async function () {
      // parent key
      const aKeyCombine = keyShares[0];
      const commonKeychain = aKeyCombine.xShare.y + aKeyCombine.xShare.chaincode;

      for (let index = 0; index < 10; index++) {
        const path = `m/0/0/${index}`;

        const subkey = MPC.keyDerive(A.pShare, [B.nShares[1], C.nShares[1]], path);

        const derive1: string = MPC.deriveUnhardened(commonKeychain, path);
        const derive2: string = MPC.deriveUnhardened(commonKeychain, path);

        derive1.should.equal(derive2, 'derivation should be deterministic');

        (subkey.xShare.y + subkey.xShare.chaincode).should.equal(
          derive1,
          'subkey common keychain should match derived keychain',
        );
      }
    });
  });

  describe('ECDSA Signing', async function () {
    let config: { signerOne: ECDSA.KeyCombined; signerTwo: ECDSA.KeyCombined; hash?: string; shouldHash?: boolean }[];

    before(() => {
      const [A, B, C, D, E, F, G, H] = keyShares;

      config = [
        { signerOne: A, signerTwo: B },
        { signerOne: A, signerTwo: C },
        { signerOne: B, signerTwo: A },
        { signerOne: B, signerTwo: C },
        { signerOne: C, signerTwo: A },
        { signerOne: C, signerTwo: B },

        // Checks signing with specific seed
        { signerOne: D, signerTwo: E },
        { signerOne: E, signerTwo: F },
        { signerOne: F, signerTwo: D },

        // Checks with specific hashing algorithm
        { signerOne: A, signerTwo: B, hash: 'keccak256' },

        // checks with no hashing
        { signerOne: A, signerTwo: B, shouldHash: false },

        // Checks with derived subkey
        { signerOne: G, signerTwo: H },
      ];
    });

    for (let index = 0; index < 9; index++) {
      it(`should properly sign the message case ${index}`, async function () {
        // Step One
        // signerOne, signerTwo have decided to sign the message
        const signerOne = config[index].signerOne;
        const signerOneIndex = signerOne.xShare.i;
        const signerTwo = config[index].signerTwo;
        const signerTwoIndex = signerTwo.xShare.i;

        const [signerOneToTwoPaillierChallenge, signerTwoToOnePaillierChallenge] = await Promise.all([
          EcdsaPaillierProof.generateP(hexToBigInt(signerOne.yShares[signerTwoIndex].n)),
          EcdsaPaillierProof.generateP(hexToBigInt(signerTwo.yShares[signerOneIndex].n)),
        ]);
        // Step Two
        // First signer generates their range proof challenge.
        const signerOneXShare: ECDSA.XShareWithChallenges = MPC.appendChallenge(
          signerOne.xShare,
          EcdsaTypes.serializeNtilde(ntildes[index]),
          EcdsaTypes.serializePaillierChallenge({ p: signerOneToTwoPaillierChallenge }),
        );

        // Step Three
        //  Second signer generates their range proof challenge.
        const signerTwoXShare: ECDSA.XShareWithChallenges = MPC.appendChallenge(
          signerTwo.xShare,
          EcdsaTypes.serializeNtilde(ntildes[index + 1]),
          EcdsaTypes.serializePaillierChallenge({ p: signerTwoToOnePaillierChallenge }),
        );
        const signerTwoChallenge = { ntilde: signerTwoXShare.ntilde, h1: signerTwoXShare.h1, h2: signerTwoXShare.h2 };

        // Step Four
        // First signer receives the challenge from the second signer and appends it to their YShare
        const signerTwoYShare: ECDSA.YShareWithChallenges = MPC.appendChallenge(
          signerOne.yShares[signerTwoIndex],
          signerTwoChallenge,
          EcdsaTypes.serializePaillierChallenge({ p: signerTwoToOnePaillierChallenge }),
        );

        // Step Five
        // Sign Shares are created by one of the participants (signerOne)
        // with its private XShare and YShare corresponding to the other participant (signerTwo)
        // This step produces a private WShare which signerOne saves and KShare which signerOne sends to signerTwo
        const signShares = await MPC.signShare(signerOneXShare, signerTwoYShare);

        // Step Six
        // signerTwo receives the KShare from signerOne and uses it produce private
        // BShare (Beta Share) which signerTwo saves and AShare (Alpha Share)
        // which is sent to signerOne

        const signConvertS21 = await MPC.signConvertStep1({
          xShare: signerTwoXShare,
          yShare: signerTwo.yShares[signerOneIndex], // YShare corresponding to the other participant signerOne
          kShare: signShares.kShare,
        });

        // Step Seven
        // signerOne receives the AShare from signerTwo and signerOne using the private WShare from step two
        // uses it produce private GShare (Gamma Share) and MUShare (Mu Share) which
        // is sent to signerTwo to produce its Gamma Share
        const signConvertS12 = await MPC.signConvertStep2({
          aShare: signConvertS21.aShare,
          wShare: signShares.wShare,
        });

        // Step Eight
        // signerTwo receives the MUShare from signerOne and signerOne using the private BShare from step three
        // uses it produce private GShare (Gamma Share)
        const signConvertS21_2 = await MPC.signConvertStep3({
          muShare: signConvertS12.muShare,
          bShare: signConvertS21.bShare,
        });

        // Step Nine
        // signerOne and signerTwo both have successfully generated GShares and they use
        // the sign combine function to generate their private omicron shares and
        // delta shares which they share to each other

        const [signCombineOne, signCombineTwo] = [
          MPC.signCombine({
            gShare: signConvertS12.gShare,
            signIndex: {
              i: signConvertS12.muShare.i,
              j: signConvertS12.muShare.j,
            },
          }),
          MPC.signCombine({
            gShare: signConvertS21_2.gShare,
            signIndex: {
              i: signConvertS21_2.signIndex.i,
              j: signConvertS21_2.signIndex.j,
            },
          }),
        ];

        const MESSAGE = Buffer.from('TOO MANY SECRETS');

        // Step Ten
        // signerOne and signerTwo shares the delta share from each other
        // and finally signs the message using their private OShare
        // and delta share received from the other signer

        const hashGenerator = (hashType?: string): Hash | undefined => {
          return hashType === 'keccak256' ? (createKeccakHash('keccak256') as Hash) : undefined;
        };
        const [signA, signB] = [
          MPC.sign(
            MESSAGE,
            signCombineOne.oShare,
            signCombineTwo.dShare,
            hashGenerator(config[index].hash),
            config[index].shouldHash,
          ),
          MPC.sign(
            MESSAGE,
            signCombineTwo.oShare,
            signCombineOne.dShare,
            hashGenerator(config[index].hash),
            config[index].shouldHash,
          ),
        ];

        // Step Eleven
        // Construct the final signature

        const signature = MPC.constructSignature([signA, signB]);

        // Step Twelve
        // Verify signature

        const isValid = MPC.verify(MESSAGE, signature, hashGenerator(config[index].hash), config[index].shouldHash);
        isValid.should.equal(true);
      });
    }
  });
});

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


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