PHP WebShell

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

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

import * as paillierBigint from 'paillier-bigint';
import 'should';
import sinon from 'sinon';
import {
  EcdsaPaillierProof,
  EcdsaRangeProof,
  EcdsaTypes,
  EcdsaZkVProof,
  HashCommitment,
  hexToBigInt,
  bigIntToBufferBE,
} from '@bitgo/sdk-lib-mpc';
import { Ecdsa } from '../../../../../../src/account-lib/mpc/tss';
import {
  PublicUTShare,
  PublicVAShareWithProofs,
  SignCombineRT,
} from '../../../../../../src/account-lib/mpc/tss/ecdsa/types';
import { paillierKeyPairs } from './fixtures';
import { loadWebAssembly } from '@bitgo/sdk-opensslbytes';

const openSSLBytes = loadWebAssembly().buffer;

describe('ecdsa tss', function () {
  const ecdsa = new Ecdsa();

  let signCombine1: SignCombineRT, signCombine2: SignCombineRT;

  before('generate key and sign phase 1 to 4', async function () {
    const paillierKeyStub = sinon.stub(paillierBigint, 'generateRandomKeys');

    paillierKeyStub.onCall(0).returns(Promise.resolve(paillierKeyPairs[0]));
    paillierKeyStub.onCall(1).returns(Promise.resolve(paillierKeyPairs[1]));
    paillierKeyStub.onCall(2).returns(Promise.resolve(paillierKeyPairs[2]));

    const [keyShare1, keyShare2, keyShare3] = await Promise.all([
      ecdsa.keyShare(1, 2, 3),
      ecdsa.keyShare(2, 2, 3),
      ecdsa.keyShare(3, 2, 3),
    ]);

    const [keyCombined1, keyCombined2, keyCombined3] = [
      ecdsa.keyCombine(keyShare1.pShare, [keyShare2.nShares[1], keyShare3.nShares[1]]),
      ecdsa.keyCombine(keyShare2.pShare, [keyShare1.nShares[2], keyShare3.nShares[2]]),
      ecdsa.keyCombine(keyShare3.pShare, [keyShare1.nShares[3], keyShare2.nShares[3]]),
    ];

    keyCombined1.xShare.y.should.equal(keyCombined2.xShare.y);
    keyCombined1.xShare.y.should.equal(keyCombined3.xShare.y);

    // Collect all VSS from nShares and verify Schnorr proofs against X_i.
    // Note that this is something WP needs to do after keyCombine/keyDerive.
    const Y = hexToBigInt(keyCombined1.xShare.y);

    const VSSs = [
      [hexToBigInt(keyShare1.nShares[2].v!)],
      [hexToBigInt(keyShare2.nShares[3].v!)],
      [hexToBigInt(keyShare3.nShares[1].v!)],
    ];

    ecdsa.verifySchnorrProofX(Y, VSSs, 1, keyCombined1.xShare.schnorrProofX).should.be.true();
    ecdsa.verifySchnorrProofX(Y, VSSs, 2, keyCombined2.xShare.schnorrProofX).should.be.true();
    ecdsa.verifySchnorrProofX(Y, VSSs, 3, keyCombined3.xShare.schnorrProofX).should.be.true();

    // Verify Schnorr proofs against X_i for keyDerive and subsequent keyCombine.
    const path = 'm/0/1/2';
    const keyDerive1 = ecdsa.keyDerive(keyShare1.pShare, [keyShare2.nShares[1], keyShare3.nShares[1]], path);

    // Note the VSSs used here are different from the ones used above.
    const derivedY = hexToBigInt(keyDerive1.xShare.y);
    const derivedVSSs = [
      [hexToBigInt(keyDerive1.nShares[2].v!)],
      [hexToBigInt(keyShare2.nShares[3].v!)],
      [hexToBigInt(keyShare3.nShares[1].v!)],
    ];
    ecdsa.verifySchnorrProofX(derivedY, derivedVSSs, 1, keyDerive1.xShare.schnorrProofX).should.be.true();

    const keyCombined2FromKeyDerive1 = ecdsa.keyCombine(keyShare2.pShare, [
      keyDerive1.nShares[2],
      keyShare3.nShares[2],
    ]);
    ecdsa
      .verifySchnorrProofX(derivedY, derivedVSSs, 2, keyCombined2FromKeyDerive1.xShare.schnorrProofX)
      .should.be.true();

    const [ntilde1, ntilde2] = await Promise.all([
      EcdsaRangeProof.generateNtilde(openSSLBytes, 512),
      EcdsaRangeProof.generateNtilde(openSSLBytes, 512),
    ]);

    const [serializeNtilde1, serializeNtilde2] = [
      EcdsaTypes.serializeNtildeWithProofs(ntilde1),
      EcdsaTypes.serializeNtildeWithProofs(ntilde2),
    ];

    const [index1, index2] = [keyCombined1.xShare.i, keyCombined2.xShare.i];

    const [paillierN1to2, paillierN2to1] = [keyCombined1.yShares[index2].n, keyCombined2.yShares[index1].n];

    const [paillierChallenger1to2, paillierChallenger2to1] = await Promise.all([
      EcdsaPaillierProof.generateP(hexToBigInt(paillierN1to2)),
      EcdsaPaillierProof.generateP(hexToBigInt(paillierN2to1)),
    ]);

    const [xShare1, xShare2] = [
      ecdsa.appendChallenge(
        keyCombined1.xShare,
        serializeNtilde1,
        EcdsaTypes.serializePaillierChallenge({ p: paillierChallenger1to2 })
      ),
      ecdsa.appendChallenge(
        keyCombined2.xShare,
        serializeNtilde2,
        EcdsaTypes.serializePaillierChallenge({ p: paillierChallenger2to1 })
      ),
    ];

    const yShare2 = ecdsa.appendChallenge(
      keyCombined1.yShares[index2],
      serializeNtilde2,
      EcdsaTypes.serializePaillierChallenge({ p: paillierChallenger2to1 })
    );

    const signShares = await ecdsa.signShare(xShare1, yShare2);

    const signConvertS21 = await ecdsa.signConvertStep1({
      xShare: xShare2,
      yShare: keyCombined2.yShares[index1],
      kShare: signShares.kShare,
    });
    const signConvertS12 = await ecdsa.signConvertStep2({
      aShare: signConvertS21.aShare,
      wShare: signShares.wShare,
    });
    const signConvertS21_2 = await ecdsa.signConvertStep3({
      muShare: signConvertS12.muShare,
      bShare: signConvertS21.bShare,
    });

    [signCombine1, signCombine2] = [
      ecdsa.signCombine({
        gShare: signConvertS12.gShare,
        signIndex: {
          i: signConvertS12.muShare.i,
          j: signConvertS12.muShare.j,
        },
      }),
      ecdsa.signCombine({
        gShare: signConvertS21_2.gShare,
        signIndex: {
          i: signConvertS21_2.signIndex.i,
          j: signConvertS21_2.signIndex.j,
        },
      }),
    ];
  });

  it('sign phase 5 should succeed', async function () {
    // TODO(HSM-129): There is a bug signing unhashed message (although this deviates from DSA spec) if the message is a little long.
    //       Some discrepancy between Ecdsa.sign and secp256k1.recoverPublicKey on handling the message input.
    const message = Buffer.from('GG18 PHASE 5');

    // Starting phase 5
    // In addition to returning s_i, Ecdsa.sign() now also calculates data needed for steps 5A and 5B:
    //   - sample random l_i and rho_i (5A)
    //   - computes V_i and A_i (5B)]
    //   - computes commitment and decommitment of (V_i, A_i) (5A, 5B)
    //   - generates proofs of knowledge of s_i, l_i, rho_i w.r.t. V_i, A_i (5B)
    const [sign1, sign2] = [
      ecdsa.generateVAProofs(message, ecdsa.sign(message, signCombine1.oShare, signCombine2.dShare)),
      ecdsa.generateVAProofs(message, ecdsa.sign(message, signCombine2.oShare, signCombine1.dShare)),
    ];

    sign1.R.should.equal(sign2.R);
    sign1.y.should.equal(sign2.y);
    sign1.m.toString('hex').should.equal(sign2.m.toString('hex'));

    // Step 5A: Calculations done by Ecdsa.sign() above, and broadcast commitment of (V_i, A_i) to other parties.
    //          The following values will be sent via communication channel at the end of 5A.
    // const commitmentVA1 = sign1.comDecomVA.commitment;
    // const commitmentVA2 = sign2.comDecomVA.commitment;

    // Step 5B: After having received all commitments of (V_i, A_i) from other parties,
    //          each party will broadcast decommitment of (V_i, A_i) and ZK proofs returned by Ecdsa.sign() above
    //          to other parties.  Then Ecdsa.verifyVAShares() below will:
    //   - verify commitments of (V_i, A_i) received from other parties (5B)
    //   - verify ZK proofs of (V_i, A_i) received from other parties (5B)
    //   - calculate V out of G, y, r, m, and all V_i, calculate A as sum of all A_i (5B)
    //   - calculate U_i and T_i and commitment and decommitment of (U_i, T_i) (5C)
    const [publicVAShares_1, publicVAShares_2] = [sign1 as PublicVAShareWithProofs, sign2 as PublicVAShareWithProofs];
    const [UT1, UT2] = [
      ecdsa.verifyVAShares(sign1, [publicVAShares_2]),
      ecdsa.verifyVAShares(sign2, [publicVAShares_1]),
    ];

    // Step 5C: Calculations of U_i, T_i done by Ecdsa.verifyVAShares above,
    //          and broadcast commitment of (U_i, T_i) to other parties.
    //          The following values will be sent via communication channel at the end of 5C.
    // const commitmentUT1 = UT1.comDecomUT.commitment;
    // const commitmentUT2 = UT2.comDecomUT.commitment;

    // Step 5D: After having received all commitments of (U_i, T_i) from other parties,
    //          each party will broadcast decommitment of (U_i, T_i) returned by Ecdsa.verifyVAShares() above
    //          to other parties.  Then Ecdsa.verifyUTShares() below will:
    //  - verify commitments of (U_i, T_i) received from other parties (5D)
    //  - calculate U as sum of all U_i, calculate T as sum of all T_i (5D)
    //  - if U equals T, then return own SShare which is being returned by Ecdsa.sign() right now (5E)
    const [publicUTShares_1, publicUTShares_2] = [UT1 as PublicUTShare, UT2 as PublicUTShare];
    const [signature1, signature2] = [
      ecdsa.verifyUTShares(UT1, [publicUTShares_2]),
      ecdsa.verifyUTShares(UT2, [publicUTShares_1]),
    ];

    // Step 5E: Broadcast s_i returned by Ecdsa.verifyUTShares() above to other parties.
    //          Verify the sum of s_i should be a valid signature.
    const signature = ecdsa.constructSignature([signature1, signature2]);
    ecdsa.verify(message, signature).should.be.true();
  });

  it('sign phase 5 fail - malicious player cheats with bad s share', async function () {
    const message = Buffer.from('GG18 PHASE 5');

    const [sign1, sign2] = [
      ecdsa.generateVAProofs(message, ecdsa.sign(message, signCombine1.oShare, signCombine2.dShare)),
      ecdsa.generateVAProofs(message, ecdsa.sign(message, signCombine2.oShare, signCombine1.dShare)),
    ];

    sign1.R.should.equal(sign2.R);
    sign1.y.should.equal(sign2.y);
    sign1.m.toString('hex').should.equal(sign2.m.toString('hex'));

    // Change the s share of sign1, and recalcualte its V along with the commitment/proof by following protocol.
    const bad_s = hexToBigInt(sign1.s) + BigInt(1);

    const bad_V = Ecdsa.curve.pointAdd(
      Ecdsa.curve.pointMultiply(hexToBigInt(sign1.R), bad_s),
      Ecdsa.curve.basePointMult(sign1.l)
    );

    const comDecom_V_A = HashCommitment.createCommitment(
      Buffer.concat([
        bigIntToBufferBE(bad_V, Ecdsa.curve.pointBytes),
        bigIntToBufferBE(sign1.A, Ecdsa.curve.pointBytes),
      ])
    );
    const zkVProof = EcdsaZkVProof.createZkVProof(
      bad_V,
      bad_s,
      sign1.l,
      hexToBigInt(sign1.R),
      Ecdsa.curve,
      sign1.proofContext
    );

    sign1.s = bigIntToBufferBE(bad_s, 32).toString('hex');
    sign1.V = bad_V;
    sign1.comDecomVA = comDecom_V_A;
    sign1.zkVProofV = zkVProof;

    // 5B will pass.
    const [publicVAShares_1, publicVAShares_2] = [sign1 as PublicVAShareWithProofs, sign2 as PublicVAShareWithProofs];
    const [UT1, UT2] = [
      ecdsa.verifyVAShares(sign1, [publicVAShares_2]),
      ecdsa.verifyVAShares(sign2, [publicVAShares_1]),
    ];

    // But verification at beginning of 5E will fail.
    const [publicUTShares_1, publicUTShares_2] = [UT1 as PublicUTShare, UT2 as PublicUTShare];
    (() => ecdsa.verifyUTShares(UT1, [publicUTShares_2])).should.throw('Sum of all U_i does not match sum of all T_i');
    (() => ecdsa.verifyUTShares(UT2, [publicUTShares_1])).should.throw('Sum of all U_i does not match sum of all T_i');
  });
});

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


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