From fdaa8c44653ecdbdf875927207ba9c32182b12b4 Mon Sep 17 00:00:00 2001 From: Zhongxi Shen Date: Wed, 31 May 2023 10:53:14 -0600 Subject: [PATCH] feat(sdk-core): phase 5 of gg18 signing - Hash commitment - Schnorr NIZK proof - NIZK proof of V value calculated in step 5A of GG18 signing - New functions of Ecdsa class to support phase 5 - Tests that demonstrate signing with phase 5 HSM-74 --- modules/sdk-core/.mocharc.js | 4 +- .../src/account-lib/mpc/curves/ed25519.ts | 41 +++- .../src/account-lib/mpc/tss/ecdsa/ecdsa.ts | 127 ++++++++++- .../src/account-lib/mpc/tss/ecdsa/types.ts | 24 +- .../unit/account-lib/mpc/tss/ecdsa/ecdsa.ts | 210 ++++++++++++++++++ modules/sdk-lib-mpc/.mocharc.js | 2 +- modules/sdk-lib-mpc/src/curves/baseCurve.ts | 4 + modules/sdk-lib-mpc/src/curves/secp256k1.ts | 19 +- modules/sdk-lib-mpc/src/hashCommitment.ts | 45 ++++ modules/sdk-lib-mpc/src/index.ts | 8 +- modules/sdk-lib-mpc/src/schnorrProof.ts | 70 ++++++ modules/sdk-lib-mpc/src/tss/ecdsa/index.ts | 1 + modules/sdk-lib-mpc/src/tss/ecdsa/types.ts | 6 + modules/sdk-lib-mpc/src/tss/ecdsa/zkVProof.ts | 80 +++++++ modules/sdk-lib-mpc/src/types.ts | 14 ++ modules/sdk-lib-mpc/src/util.ts | 12 +- .../sdk-lib-mpc/test/paillierproof.util.ts | 8 +- .../sdk-lib-mpc/test/unit/hashCommitment.ts | 50 +++++ modules/sdk-lib-mpc/test/unit/schnorrProof.ts | 34 +++ .../test/unit/tss/ecdsa/dlogproofs.ts | 2 +- .../test/unit/tss/ecdsa/paillierproof.ts | 2 +- .../test/unit/tss/ecdsa/rangeproof.ts | 3 +- .../test/unit/tss/ecdsa/zkVProof.ts | 58 +++++ modules/sdk-lib-mpc/test/unit/util.ts | 6 +- 24 files changed, 791 insertions(+), 39 deletions(-) create mode 100644 modules/sdk-core/test/unit/account-lib/mpc/tss/ecdsa/ecdsa.ts create mode 100644 modules/sdk-lib-mpc/src/hashCommitment.ts create mode 100644 modules/sdk-lib-mpc/src/schnorrProof.ts create mode 100644 modules/sdk-lib-mpc/src/tss/ecdsa/zkVProof.ts create mode 100644 modules/sdk-lib-mpc/src/types.ts create mode 100644 modules/sdk-lib-mpc/test/unit/hashCommitment.ts create mode 100644 modules/sdk-lib-mpc/test/unit/schnorrProof.ts create mode 100644 modules/sdk-lib-mpc/test/unit/tss/ecdsa/zkVProof.ts diff --git a/modules/sdk-core/.mocharc.js b/modules/sdk-core/.mocharc.js index 2a36945feb..806242df89 100644 --- a/modules/sdk-core/.mocharc.js +++ b/modules/sdk-core/.mocharc.js @@ -2,9 +2,9 @@ module.exports = { require: 'ts-node/register', - timeout: '20000', + timeout: '30000', reporter: 'min', 'reporter-option': ['cdn=true', 'json=false'], exit: true, - spec: ['test/unit/*.ts'], + spec: ['test/unit/**/*.ts'], }; diff --git a/modules/sdk-core/src/account-lib/mpc/curves/ed25519.ts b/modules/sdk-core/src/account-lib/mpc/curves/ed25519.ts index ce23fa66dd..3048aa85fb 100644 --- a/modules/sdk-core/src/account-lib/mpc/curves/ed25519.ts +++ b/modules/sdk-core/src/account-lib/mpc/curves/ed25519.ts @@ -3,6 +3,9 @@ import { randomBytes } from 'crypto'; import { bigIntFromBufferLE, bigIntToBufferLE } from '../util'; import BaseCurve from './baseCurve'; +const privateKeySize = 32; +const publicKeySize = 32; + export class Ed25519Curve implements BaseCurve { static initialized = false; @@ -26,44 +29,60 @@ export class Ed25519Curve implements BaseCurve { } scalarNegate(s: bigint): bigint { - return bigIntFromBufferLE(Buffer.from(sodium.crypto_core_ed25519_scalar_negate(bigIntToBufferLE(s, 32)))); + return bigIntFromBufferLE( + Buffer.from(sodium.crypto_core_ed25519_scalar_negate(bigIntToBufferLE(s, privateKeySize))) + ); } scalarInvert(s: bigint): bigint { - return bigIntFromBufferLE(Buffer.from(sodium.crypto_core_ed25519_scalar_invert(bigIntToBufferLE(s, 32)))); + return bigIntFromBufferLE( + Buffer.from(sodium.crypto_core_ed25519_scalar_invert(bigIntToBufferLE(s, privateKeySize))) + ); } scalarAdd(x: bigint, y: bigint): bigint { return bigIntFromBufferLE( - Buffer.from(sodium.crypto_core_ed25519_scalar_add(bigIntToBufferLE(x, 32), bigIntToBufferLE(y, 32))) + Buffer.from( + sodium.crypto_core_ed25519_scalar_add(bigIntToBufferLE(x, privateKeySize), bigIntToBufferLE(y, privateKeySize)) + ) ); } scalarSub(x: bigint, y: bigint): bigint { return bigIntFromBufferLE( - Buffer.from(sodium.crypto_core_ed25519_scalar_sub(bigIntToBufferLE(x, 32), bigIntToBufferLE(y, 32))) + Buffer.from( + sodium.crypto_core_ed25519_scalar_sub(bigIntToBufferLE(x, privateKeySize), bigIntToBufferLE(y, privateKeySize)) + ) ); } scalarMult(x: bigint, y: bigint): bigint { return bigIntFromBufferLE( - Buffer.from(sodium.crypto_core_ed25519_scalar_mul(bigIntToBufferLE(x, 32), bigIntToBufferLE(y, 32))) + Buffer.from( + sodium.crypto_core_ed25519_scalar_mul(bigIntToBufferLE(x, privateKeySize), bigIntToBufferLE(y, privateKeySize)) + ) ); } basePointMult(n: bigint): bigint { - return bigIntFromBufferLE(Buffer.from(sodium.crypto_scalarmult_ed25519_base_noclamp(bigIntToBufferLE(n, 32)))); + return bigIntFromBufferLE( + Buffer.from(sodium.crypto_scalarmult_ed25519_base_noclamp(bigIntToBufferLE(n, privateKeySize))) + ); } pointAdd(p: bigint, q: bigint): bigint { return bigIntFromBufferLE( - Buffer.from(sodium.crypto_core_ed25519_add(bigIntToBufferLE(p, 32), bigIntToBufferLE(q, 32))) + Buffer.from( + sodium.crypto_core_ed25519_add(bigIntToBufferLE(p, publicKeySize), bigIntToBufferLE(q, publicKeySize)) + ) ); } pointMultiply(p: bigint, s: bigint): bigint { return bigIntFromBufferLE( - Buffer.from(sodium.crypto_scalarmult_ed25519_noclamp(bigIntToBufferLE(s, 32), bigIntToBufferLE(p, 32))) + Buffer.from( + sodium.crypto_scalarmult_ed25519_noclamp(bigIntToBufferLE(s, publicKeySize), bigIntToBufferLE(p, publicKeySize)) + ) ); } @@ -71,7 +90,7 @@ export class Ed25519Curve implements BaseCurve { const signedMessage = Buffer.concat([signature, message]); try { // Returns the message which was signed if the signature is valid - const result = Buffer.from(sodium.crypto_sign_open(signedMessage, bigIntToBufferLE(publicKey, 32))); + const result = Buffer.from(sodium.crypto_sign_open(signedMessage, bigIntToBufferLE(publicKey, publicKeySize))); return Buffer.compare(message, result) === 0; } catch (error) { // Invalid signature causes an exception @@ -82,4 +101,8 @@ export class Ed25519Curve implements BaseCurve { order(): bigint { return BigInt('0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed') * BigInt('0x08'); } + + scalarBytes = privateKeySize; + + pointBytes = publicKeySize; } diff --git a/modules/sdk-core/src/account-lib/mpc/tss/ecdsa/ecdsa.ts b/modules/sdk-core/src/account-lib/mpc/tss/ecdsa/ecdsa.ts index 9400074514..5a55f32ebe 100644 --- a/modules/sdk-core/src/account-lib/mpc/tss/ecdsa/ecdsa.ts +++ b/modules/sdk-core/src/account-lib/mpc/tss/ecdsa/ecdsa.ts @@ -11,6 +11,9 @@ import { EcdsaPaillierProof, EcdsaRangeProof, EcdsaTypes, + EcdsaZkVProof, + HashCommitment, + Schnorr, randomPositiveCoPrimeTo, hexToBigInt, minModulusBitLength, @@ -27,6 +30,8 @@ import { NShare, OShare, PShare, + PublicUTShare, + PublicVAShare, RangeProofWithCheckShare, Signature, SignCombine, @@ -42,6 +47,8 @@ import { SignShareRT, SShare, SubkeyShare, + UTShare, + VAShare, WShare, XShare, XShareWithChallenges, @@ -1303,9 +1310,9 @@ export default class Ecdsa { * @param {DShare} dShare delta share received from the other participant * @param {Hash} hash hashing algorithm implementing Node`s standard crypto hash interface * @param {boolean} shouldHash if true, we hash the provided buffer before signing - * @returns {SShare} + * @returns {VAShare} */ - sign(M: Buffer, oShare: OShare, dShare: DShare, hash?: Hash, shouldHash = true): SShare { + sign(M: Buffer, oShare: OShare, dShare: DShare, hash?: Hash, shouldHash = true): VAShare { const m = shouldHash ? (hash || createHash('sha256')).update(M).digest() : M; const delta = Ecdsa.curve.scalarAdd(hexToBigInt(oShare.delta), hexToBigInt(dShare.delta)); @@ -1321,14 +1328,130 @@ export default class Ecdsa { Ecdsa.curve.scalarMult(bigIntFromU8ABE(m), hexToBigInt(oShare.k)), Ecdsa.curve.scalarMult(r, hexToBigInt(oShare.omicron)) ); + + const l = Ecdsa.curve.scalarRandom(); + const rho = Ecdsa.curve.scalarRandom(); + const V = Ecdsa.curve.pointAdd(Ecdsa.curve.pointMultiply(R, s), Ecdsa.curve.basePointMult(l)); + const A = Ecdsa.curve.basePointMult(rho); + + const comDecom_V_A = HashCommitment.createCommitment( + Buffer.concat([bigIntToBufferBE(V, Ecdsa.curve.pointBytes), bigIntToBufferBE(A, Ecdsa.curve.pointBytes)]) + ); + const zkVProof = EcdsaZkVProof.createZkVProof(V, s, l, R, Ecdsa.curve); + const schnorrProof = Schnorr.createSchnorrProof(A, rho, Ecdsa.curve); + return { i: oShare.i, y: oShare.y, R: pointR.toHex(true), s: bigIntToBufferBE(s, 32).toString('hex'), + m: m, + l: l, + rho: rho, + V: V, + A: A, + comDecomVA: comDecom_V_A, + zkVProofV: zkVProof, + schnorrProofA: schnorrProof, }; } + /** + * Verify V_i and A_i values of all other participants during signing phase 5 steps 5A and 5B. + * @param {VAShare} vaShare V_i, A_i info including SShare values of the currenct participant + * @param {PublicVAShare[]} publicVAShares public V_i, A_i info of all other participants + * @returns {UTShare} U_i, T_i info of the current participant if all verifications pass + */ + verifyVAShares(vaShare: VAShare, publicVAShares: PublicVAShare[]): UTShare { + publicVAShares.forEach((publicVAShare) => { + if ( + !HashCommitment.verifyCommitment(publicVAShare.comDecomVA.commitment, { + secret: Buffer.concat([ + bigIntToBufferBE(publicVAShare.V, Ecdsa.curve.pointBytes), + bigIntToBufferBE(publicVAShare.A, Ecdsa.curve.pointBytes), + ]), + blindingFactor: publicVAShare.comDecomVA.decommitment.blindingFactor, + }) + ) { + throw new Error('Could not verify commitment of V_i and A_i'); + } + if (!Schnorr.verifySchnorrProof(publicVAShare.A, publicVAShare.schnorrProofA, Ecdsa.curve)) { + throw new Error('Could not verify Schnorr proof of A_i'); + } + if ( + !EcdsaZkVProof.verifyZkVProof(publicVAShare.V, publicVAShare.zkVProofV, hexToBigInt(vaShare.R), Ecdsa.curve) + ) { + throw new Error('Could not verify ZK proof of V_i'); + } + }); + + const y = hexToBigInt(vaShare.y); + // r is R's x coordinate. R is in compressed form, so we need to slice off the first byte. + const r = hexToBigInt(vaShare.R.slice(2)); + + // Calculate aggregation of all V_i and A_i. + let V = Ecdsa.curve.pointAdd( + Ecdsa.curve.pointAdd( + Ecdsa.curve.basePointMult(Ecdsa.curve.scalarNegate(bigIntFromU8ABE(vaShare.m))), + Ecdsa.curve.pointMultiply(y, Ecdsa.curve.scalarNegate(r)) + ), + vaShare.V + ); + let A = vaShare.A; + publicVAShares.forEach((publicVAShare) => { + V = Ecdsa.curve.pointAdd(V, publicVAShare.V); + A = Ecdsa.curve.pointAdd(A, publicVAShare.A); + }); + + // Calculate U_i = rho_i * V and T_i = l_i * A. + const U = Ecdsa.curve.pointMultiply(V, vaShare.rho); + const T = Ecdsa.curve.pointMultiply(A, vaShare.l); + const comDecom_U_T = HashCommitment.createCommitment( + Buffer.concat([bigIntToBufferBE(U, Ecdsa.curve.pointBytes), bigIntToBufferBE(T, Ecdsa.curve.pointBytes)]) + ); + + return { + ...vaShare, + U, + T, + comDecomUT: comDecom_U_T, + }; + } + + /** + * Verify U_i and V_i values of all other participants during signing phase 5 steps 5C and 5D. + * @param {UTShare} utShare U_i, T_i info including SShare values of the currenct participant + * @param {PublicUTShare[]} publicUTShares public U_i, T_i info of all other participants + * @returns {SShare} SShare of the current participant if all verifications pass + */ + verifyUTShares(utShare: UTShare, publicUTShares: PublicUTShare[]): SShare { + let sigmaU = utShare.U; + let sigmaT = utShare.T; + + publicUTShares.forEach((publicUTShare) => { + if ( + !HashCommitment.verifyCommitment(publicUTShare.comDecomUT.commitment, { + secret: Buffer.concat([ + bigIntToBufferBE(publicUTShare.U, Ecdsa.curve.pointBytes), + bigIntToBufferBE(publicUTShare.T, Ecdsa.curve.pointBytes), + ]), + blindingFactor: publicUTShare.comDecomUT.decommitment.blindingFactor, + }) + ) { + throw new Error('Could not verify commitment of U_i and T_i'); + } + + sigmaU = Ecdsa.curve.pointAdd(sigmaU, publicUTShare.U); + sigmaT = Ecdsa.curve.pointAdd(sigmaT, publicUTShare.T); + }); + + if (sigmaU !== sigmaT) { + throw new Error('Sum of all U_i does not match sum of all T_i'); + } + + return { ...utShare }; + } + /** * Construct full signature by combining Sign Shares * @param {SShare[]} shares diff --git a/modules/sdk-core/src/account-lib/mpc/tss/ecdsa/types.ts b/modules/sdk-core/src/account-lib/mpc/tss/ecdsa/types.ts index 29e734b214..fe114ae50b 100644 --- a/modules/sdk-core/src/account-lib/mpc/tss/ecdsa/types.ts +++ b/modules/sdk-core/src/account-lib/mpc/tss/ecdsa/types.ts @@ -1,4 +1,4 @@ -import { EcdsaTypes } from '@bitgo/sdk-lib-mpc'; +import { EcdsaTypes, HashCommitDecommit, SchnorrProof } from '@bitgo/sdk-lib-mpc'; /** * @deprecated use DeserializedNtildeProof from sdk-lib-mpc instead @@ -282,3 +282,25 @@ export type Signature = { r: string; s: string; }; + +export interface PublicVAShare { + V: bigint; + A: bigint; + comDecomVA: HashCommitDecommit; + zkVProofV: EcdsaTypes.ZkVProof; + schnorrProofA: SchnorrProof; +} + +export interface VAShare extends SShare, PublicVAShare { + m: Buffer; + l: bigint; + rho: bigint; +} + +export interface PublicUTShare { + U: bigint; + T: bigint; + comDecomUT: HashCommitDecommit; +} + +export interface UTShare extends SShare, PublicUTShare {} diff --git a/modules/sdk-core/test/unit/account-lib/mpc/tss/ecdsa/ecdsa.ts b/modules/sdk-core/test/unit/account-lib/mpc/tss/ecdsa/ecdsa.ts new file mode 100644 index 0000000000..86b2878a28 --- /dev/null +++ b/modules/sdk-core/test/unit/account-lib/mpc/tss/ecdsa/ecdsa.ts @@ -0,0 +1,210 @@ +import 'should'; +import { + EcdsaPaillierProof, + EcdsaRangeProof, + EcdsaTypes, + EcdsaZkVProof, + HashCommitment, + hexToBigInt, + bigIntToBufferBE, +} from '@bitgo/sdk-lib-mpc'; +import { Ecdsa } from '../../../../../../src/account-lib/mpc/tss'; +import { PublicUTShare, PublicVAShare, SignCombineRT } from '../../../../../../src/account-lib/mpc/tss/ecdsa/types'; + +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 [keyShare1, keyShare2, keyShare3] = await Promise.all([ + ecdsa.keyShare(1, 2, 3), + ecdsa.keyShare(2, 2, 3), + ecdsa.keyShare(3, 2, 3), + ]); + + const [keyCombined1, keyCombined2] = [ + ecdsa.keyCombine(keyShare1.pShare, [keyShare2.nShares[1], keyShare3.nShares[1]]), + ecdsa.keyCombine(keyShare2.pShare, [keyShare1.nShares[2], keyShare3.nShares[2]]), + ]; + + const [ntilde1, ntilde2] = await Promise.all([ + EcdsaRangeProof.generateNtilde(512), + EcdsaRangeProof.generateNtilde(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.sign(message, signCombine1.oShare, signCombine2.dShare), + 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 PublicVAShare, sign2 as PublicVAShare]; + 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.sign(message, signCombine1.oShare, signCombine2.dShare), + 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.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 PublicVAShare, sign2 as PublicVAShare]; + 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'); + }); +}); diff --git a/modules/sdk-lib-mpc/.mocharc.js b/modules/sdk-lib-mpc/.mocharc.js index 2a36945feb..ae5ec5d5c8 100644 --- a/modules/sdk-lib-mpc/.mocharc.js +++ b/modules/sdk-lib-mpc/.mocharc.js @@ -6,5 +6,5 @@ module.exports = { reporter: 'min', 'reporter-option': ['cdn=true', 'json=false'], exit: true, - spec: ['test/unit/*.ts'], + spec: ['test/unit/**/*.ts'], }; diff --git a/modules/sdk-lib-mpc/src/curves/baseCurve.ts b/modules/sdk-lib-mpc/src/curves/baseCurve.ts index a76f984630..b955ce86cc 100644 --- a/modules/sdk-lib-mpc/src/curves/baseCurve.ts +++ b/modules/sdk-lib-mpc/src/curves/baseCurve.ts @@ -33,4 +33,8 @@ export interface BaseCurve { verify(message: Buffer, signature: Buffer, publicKey: bigint): boolean; // order of the curve order: () => bigint; + // the size of scalar of the curve in bytes + scalarBytes: number; + // the size of point of the curve in bytes + pointBytes: number; } diff --git a/modules/sdk-lib-mpc/src/curves/secp256k1.ts b/modules/sdk-lib-mpc/src/curves/secp256k1.ts index 1c14df5204..3b6250ecc9 100644 --- a/modules/sdk-lib-mpc/src/curves/secp256k1.ts +++ b/modules/sdk-lib-mpc/src/curves/secp256k1.ts @@ -3,6 +3,8 @@ import { BaseCurve } from './baseCurve'; import * as secp from '@noble/secp256k1'; const order = secp.CURVE.n; +const privateKeySize = 32; +const publicKeySize = 33; export class Secp256k1Curve implements BaseCurve { scalarRandom(): bigint { @@ -10,7 +12,7 @@ export class Secp256k1Curve implements BaseCurve { } scalarAdd(x: bigint, y: bigint): bigint { - return bigIntFromU8ABE(secp.utils.privateAdd(x, bigIntToBufferBE(y, 32))); + return bigIntFromU8ABE(secp.utils.privateAdd(x, bigIntToBufferBE(y, privateKeySize))); } scalarSub(x: bigint, y: bigint): bigint { @@ -35,28 +37,33 @@ export class Secp256k1Curve implements BaseCurve { } pointAdd(a: bigint, b: bigint): bigint { - const pointA = secp.Point.fromHex(bigIntToBufferBE(a, 32)); - const pointB = secp.Point.fromHex(bigIntToBufferBE(b, 32)); + const pointA = secp.Point.fromHex(bigIntToBufferBE(a, privateKeySize)); + const pointB = secp.Point.fromHex(bigIntToBufferBE(b, privateKeySize)); return bigIntFromU8ABE(pointA.add(pointB).toRawBytes(true)); } pointMultiply(p: bigint, s: bigint): bigint { - const pointA = secp.Point.fromHex(bigIntToBufferBE(p, 32)); + const pointA = secp.Point.fromHex(bigIntToBufferBE(p, privateKeySize)); return bigIntFromU8ABE(pointA.multiply(s).toRawBytes(true)); } basePointMult(n: bigint): bigint { - const point = bigIntToBufferBE(n, 32); + const point = bigIntToBufferBE(n, privateKeySize); return bigIntFromU8ABE(secp.getPublicKey(point, true)); } verify(message: Buffer, signature: Buffer, publicKey: bigint): boolean { return Buffer.from(secp.recoverPublicKey(message, signature.subarray(1), signature[0], true)).equals( - bigIntToBufferBE(publicKey, 33) + bigIntToBufferBE(publicKey, publicKeySize) ); } order(): bigint { return order; } + + scalarBytes = privateKeySize; + + // Always use compressed points. + pointBytes = publicKeySize; } diff --git a/modules/sdk-lib-mpc/src/hashCommitment.ts b/modules/sdk-lib-mpc/src/hashCommitment.ts new file mode 100644 index 0000000000..c6a30e8207 --- /dev/null +++ b/modules/sdk-lib-mpc/src/hashCommitment.ts @@ -0,0 +1,45 @@ +import { createHash, randomBytes } from 'crypto'; +import { HashCommitDecommit, HashDecommitment } from './types'; +import { bigIntToBufferBE } from './util'; + +const minRandomnessLength = 32; + +/** + * Create hash commitment and decommietment of a secret value. + * @param secret The secret value/message. + * @param r The randomness/nonce to be added to the commmitment. + * @returns The created commitment and decommitment. + */ +export function createCommitment(secret: Buffer, r: Buffer = randomBytes(minRandomnessLength)): HashCommitDecommit { + if (r.length < minRandomnessLength) { + throw new Error(`randomness must be at least ${minRandomnessLength} bytes long`); + } + return { + commitment: hash(secret, r), + decommitment: { + blindingFactor: r, + secret: secret, + }, + }; +} + +const bytesPerUint32 = 4; + +function hash(secret: Buffer, r: Buffer): Buffer { + return createHash('sha256') + .update(bigIntToBufferBE(BigInt(secret.length), bytesPerUint32)) + .update(secret) + .update(bigIntToBufferBE(BigInt(r.length), bytesPerUint32)) + .update(r) + .digest(); +} + +/** + * Verify hash commitment and decommietment of a secret value. + * @param commitment The commitment. + * @param decommietment The decommitment. + * @returns True if verification succeeds. + */ +export function verifyCommitment(commitment: Buffer, decommietment: HashDecommitment): boolean { + return hash(decommietment.secret, decommietment.blindingFactor).compare(commitment) === 0; +} diff --git a/modules/sdk-lib-mpc/src/index.ts b/modules/sdk-lib-mpc/src/index.ts index 8cb0108f26..c0643009c7 100644 --- a/modules/sdk-lib-mpc/src/index.ts +++ b/modules/sdk-lib-mpc/src/index.ts @@ -1,4 +1,8 @@ -export * from './tss'; export * from './curves'; -export * from './util'; export * from './openssl'; +export * from './tss'; + +export * as HashCommitment from './hashCommitment'; +export * as Schnorr from './schnorrProof'; +export * from './types'; +export * from './util'; diff --git a/modules/sdk-lib-mpc/src/schnorrProof.ts b/modules/sdk-lib-mpc/src/schnorrProof.ts new file mode 100644 index 0000000000..dc5ffc6ea8 --- /dev/null +++ b/modules/sdk-lib-mpc/src/schnorrProof.ts @@ -0,0 +1,70 @@ +/** + * Implementation of Schnorr Non-interactive Zero-Knowledge Proof. + * @see {@link https://datatracker.ietf.org/doc/rfc8235/} + */ +import { createHash } from 'crypto'; +import { BaseCurve as Curve } from './curves'; +import { SchnorrProof } from './types'; +import { bigIntFromBufferBE, bigIntToBufferBE } from './util'; + +/** + * Create a Schnorr Proof of knowledge of the discrete log of an Elliptic-curve point. + * @param A The curve point. + * @param a The discrete log of the curve point. + * @param curve The elliptic curve. + * @param additionalCtx Additional contextual information to associate with the proof. + * @returns The created proof. + */ +export function createSchnorrProof( + A: bigint, + a: bigint, + curve: Curve, + additionalCtx: Buffer = Buffer.from('') +): SchnorrProof { + const v = curve.scalarRandom(); + const V = curve.basePointMult(v); + + const c = nonInteractiveChallenge(V, A, curve, additionalCtx); + + const r = curve.scalarSub(v, curve.scalarMult(a, c)); + + return { + vPoint: V, + r: r, + }; +} + +function nonInteractiveChallenge(V: bigint, A: bigint, curve: Curve, additionalCtx: Buffer): bigint { + const G = curve.basePointMult(BigInt(1)); + + const hash = createHash('sha256'); + hash.update(bigIntToBufferBE(G, 32)); + hash.update(bigIntToBufferBE(V, 32)); + hash.update(bigIntToBufferBE(A, 32)); + hash.update(additionalCtx); + + return bigIntFromBufferBE(hash.digest()); +} + +/** + * Verify a Schnorr Proof of knowledge of the discrete log of an Elliptic-curve point. + * @param A The curve point. + * @param proof The schnorr proof. + * @param curve The elliptic curve. + * @param additionalCtx Additional contextual information that is supposed to associate with the proof. + * @returns True if the proof checks out. + */ +export function verifySchnorrProof( + A: bigint, + proof: SchnorrProof, + curve: Curve, + additionalCtx: Buffer = Buffer.from('') +): boolean { + const c = nonInteractiveChallenge(proof.vPoint, A, curve, additionalCtx); + + const rG = curve.basePointMult(proof.r); + + const cA = curve.pointMultiply(A, curve.scalarReduce(c)); + + return proof.vPoint === curve.pointAdd(rG, cA); +} diff --git a/modules/sdk-lib-mpc/src/tss/ecdsa/index.ts b/modules/sdk-lib-mpc/src/tss/ecdsa/index.ts index d7584a1cf8..3142c603e1 100644 --- a/modules/sdk-lib-mpc/src/tss/ecdsa/index.ts +++ b/modules/sdk-lib-mpc/src/tss/ecdsa/index.ts @@ -1,5 +1,6 @@ export * as EcdsaTypes from './types'; export * as EcdsaRangeProof from './rangeproof'; export * as EcdsaPaillierProof from './paillierproof'; +export * as EcdsaZkVProof from './zkVProof'; export const minModulusBitLength = 3072; diff --git a/modules/sdk-lib-mpc/src/tss/ecdsa/types.ts b/modules/sdk-lib-mpc/src/tss/ecdsa/types.ts index d6e85b0bb9..da919605fc 100644 --- a/modules/sdk-lib-mpc/src/tss/ecdsa/types.ts +++ b/modules/sdk-lib-mpc/src/tss/ecdsa/types.ts @@ -226,3 +226,9 @@ export interface RangeProofWithCheck { t2: bigint; u: bigint; } + +export interface ZkVProof { + Alpha: bigint; + t: bigint; + u: bigint; +} diff --git a/modules/sdk-lib-mpc/src/tss/ecdsa/zkVProof.ts b/modules/sdk-lib-mpc/src/tss/ecdsa/zkVProof.ts new file mode 100644 index 0000000000..34a08edd40 --- /dev/null +++ b/modules/sdk-lib-mpc/src/tss/ecdsa/zkVProof.ts @@ -0,0 +1,80 @@ +/** + * Zero Knowledge Proof of knowledge of the s and l that are behind the public value V = sR + lG. + * The V value is calculated in step 5A and the proof is created in step 5B of the GG18 signing protocol. + * @see {@link https://eprint.iacr.org/2019/114.pdf} section 4.3 for reference. + */ +import { createHash } from 'crypto'; +import { BaseCurve as Curve } from '../../curves'; +import { ZkVProof } from './types'; +import { bigIntFromBufferBE, bigIntToBufferBE } from '../../util'; + +/** + * Create a ZK Proof of knowledge of the s and l that are behind the public value V = sR + lG. + * @param V The curve point V. + * @param s The s that multiplies R. + * @param l The l that multiplies the curve genreator G. + * @param R The curve point R shared by all participants. + * @param curve The elliptic curve. + * @param additionalCtx Additional contextual information to associate with the proof. + * @returns The created proof. + */ +export function createZkVProof( + V: bigint, + s: bigint, + l: bigint, + R: bigint, + curve: Curve, + additionalCtx: Buffer = Buffer.from('') +): ZkVProof { + const a = curve.scalarRandom(); + const b = curve.scalarRandom(); + const Alpha = curve.pointAdd(curve.pointMultiply(R, a), curve.basePointMult(b)); + + const c = nonInteractiveChallenge(V, R, Alpha, curve, additionalCtx); + + const t = curve.scalarAdd(a, curve.scalarMult(c, s)); + const u = curve.scalarAdd(b, curve.scalarMult(c, l)); + + return { + Alpha: Alpha, + t: t, + u: u, + }; +} + +function nonInteractiveChallenge(V: bigint, R: bigint, Alpha: bigint, curve: Curve, additionalCtx: Buffer): bigint { + const G = curve.basePointMult(BigInt(1)); + + const hash = createHash('sha256'); + hash.update(bigIntToBufferBE(G, curve.pointBytes)); + hash.update(bigIntToBufferBE(R, curve.pointBytes)); + hash.update(bigIntToBufferBE(V, curve.pointBytes)); + hash.update(bigIntToBufferBE(Alpha, curve.pointBytes)); + hash.update(additionalCtx); + + return bigIntFromBufferBE(hash.digest()); +} + +/** + * Verify a ZK Proof of knowledge of the s and l that are behind the public value V = sR + lG. + * @param V The curve point V. + * @param proof The ZK proof. + * @param R The curve point R shared by all participants. + * @param curve The elliptic curve. + * @param additionalCtx Additional contextual information that is supposed to associate with the proof. + * @returns True if the proof checks out. + */ +export function verifyZkVProof( + V: bigint, + proof: ZkVProof, + R: bigint, + curve: Curve, + additionalCtx: Buffer = Buffer.from('') +): boolean { + const c = nonInteractiveChallenge(V, R, proof.Alpha, curve, additionalCtx); + + const lhs = curve.pointAdd(curve.pointMultiply(R, proof.t), curve.basePointMult(proof.u)); + const rhs = curve.pointAdd(proof.Alpha, curve.pointMultiply(V, curve.scalarReduce(c))); + + return lhs === rhs; +} diff --git a/modules/sdk-lib-mpc/src/types.ts b/modules/sdk-lib-mpc/src/types.ts new file mode 100644 index 0000000000..6635e0468a --- /dev/null +++ b/modules/sdk-lib-mpc/src/types.ts @@ -0,0 +1,14 @@ +export interface HashCommitDecommit { + commitment: Buffer; + decommitment: HashDecommitment; +} + +export interface HashDecommitment { + secret: Buffer; + blindingFactor: Buffer; +} + +export interface SchnorrProof { + vPoint: bigint; + r: bigint; +} diff --git a/modules/sdk-lib-mpc/src/util.ts b/modules/sdk-lib-mpc/src/util.ts index cbd32c3a2b..5a5910fd50 100644 --- a/modules/sdk-lib-mpc/src/util.ts +++ b/modules/sdk-lib-mpc/src/util.ts @@ -51,12 +51,12 @@ export function bigIntToHex(bigint: bigint, hexLength?: number): string { return hex; } -export function bigIntToBufferLE(n: bigint, bytes?: number): Buffer { +export function bigIntToBufferLE(n: bigint, minBytes?: number): Buffer { let v = n.toString(16); v = '0'.slice(0, v.length % 2) + v; const buf = Buffer.from(v, 'hex').reverse(); - if (bytes && buf.length < bytes) { - return Buffer.concat([buf, Buffer.alloc(bytes - buf.length)]); + if (minBytes && buf.length < minBytes) { + return Buffer.concat([buf, Buffer.alloc(minBytes - buf.length)]); } return buf; } @@ -65,12 +65,12 @@ export function bigIntFromBufferLE(buf: Buffer): bigint { return BigInt('0x' + Buffer.from(buf).reverse().toString('hex')); } -export function bigIntToBufferBE(n: bigint, bytes?: number): Buffer { +export function bigIntToBufferBE(n: bigint, minBytes?: number): Buffer { let v = n.toString(16); v = '0'.slice(0, v.length % 2) + v; const buf = Buffer.from(v, 'hex'); - if (bytes && buf.length < bytes) { - return Buffer.concat([Buffer.alloc(bytes - buf.length), buf]); + if (minBytes && buf.length < minBytes) { + return Buffer.concat([Buffer.alloc(minBytes - buf.length), buf]); } return buf; } diff --git a/modules/sdk-lib-mpc/test/paillierproof.util.ts b/modules/sdk-lib-mpc/test/paillierproof.util.ts index d523f0a501..600d263913 100644 --- a/modules/sdk-lib-mpc/test/paillierproof.util.ts +++ b/modules/sdk-lib-mpc/test/paillierproof.util.ts @@ -707,7 +707,7 @@ export const mockedPaillierProofs: { '8ddc0ccafcf2801b7b4608b6c88047dc06d6006eb0f6469070d4b80b63427396a84af215d6fb1f8f326a1085cfbeabd9c2680116e9fd2bf1510c37562b9ec63926b38b927f0b303861bfb8f76aa22136d402a9a72f9a225563ea8853fa358a07b75960e460787cd52dc7aa203145da59b49b1f2bfcdbcfdeb3a9d4a6d3700b5700fc50aa501344ed23f7936da1611791e4a0a39494450010744857e0f4015476f56b3571cf43e1b1d34fba44c4a7ec6b5af0e55b7693d22dc308aeefe156095e36414ee204ddce15e6a68bfa506f1c3564a2cb608b63bf0a3adfa389e7702c8aab567956be3a2f1653cbb28969d4a0fea49c109ceeb17b54de595df447ec24bf10f5f578dd6bfe38e9447c9c18a1244fb0d71d78dc094019a9b3596271d4ce4374678b3d983129e9213b497b2a95e74a4a5e9bbec22e7313f8446ad1f8b22e21fc55bd7cd9c48b10e0fc5c28ca03aa139779a6cb7f4536ad0e94c180099a14bc17dc3660bde3648b6f5ad30f9c88e8df25d5aa91f0f549a73012c076f6963e7b', '330d41e248bec16c8b9c24f65343fce08c2723c036573a9c5f3da7c75cf4a58d4ea00637a31a31aa2eb60275e1710272fcc2460f469e886f164ba3b50e3d6479baa8a0b70036c8c78efb62c9285723e35f0dd046b625ae5be7c70b202f663c82f375b653215bdae58256f7d3099eb5ca9fbfd13758896bf3c1b03ccd06db4e0445cc925e0f315ec8a112d3f25a9a09750dabd13a51f419274dc613f2e80eeab0e529ab28145127f0822e76d6d2dcfd975d2c12fb9b74ef2ac891364fd3161730780c46aca3913bbaf402704e2d1b6986492348b72605cdae40848773707e6818fc5330448f55c76c7c8e537e43e6d704e14008021c58ccc5107993f93a11a1b506cba6672235f5510b73d31108da0c76360edb868a880c137d3241052c645f30d65789fe08d3733185b0916691e101f25ae5e5a57db31a46f6ae3b7e0de293a7d9ed7e6616c695a0785b83d4a9bd1c037072cbe259ba242073656eaf62bbd8c7128bc09dd3b0d13411c11dcd5d44b71d38f2d6d70b3ca6840ba86772abeb0cff', '22323848274a1f25665a5613ef878f2886b9b8cc1fa64dcaffff5f8f58ea1ed67eae1af0a1d10775e736e9337d21d59da6620b43148d4736f253b931857d382442582a922753e5ee47308aaf0d7c679b4caa35c49d71980c77d1207655594e0c260791d07fcdd63a2044aaaddd68c1cf4f910cfd7dfaae9570d69b79f9ce1282a8a00c7838e67760f2b44a6a0ee51784869f9f0b9154e2f62c6ef1811c1415ff592ae72a88b4c4c0516d372855df591e69c0f1a82946a31b18246c766d94cae0f198ca4a7db5cf635d1be62cef4c078c068dbd3619d39269013f4d2bab3f292e30b741db3ef627072b050c280ef873f275d4d366cb826744c045f1824aca12eb35a7099313c9e668c097922249d1fc78902bf04b9f9f4947a6de62f7117cd11fb65037e915e67a33b4586fe6a591e5b657ab093b0e79637810237b9b50bfa7a2158435d07fdd85db10a9323a95b4edb0f407e88dc4d7fa8232f1c22a99989e9510f88f650fab9e636ff3d9562ae8b51aaee00d65a94ab2a68a67f2666c7285c4', - 'deb81e80025d964e45c47ec82677021f698e57237fc830e0e2dd3b1a282e1dba84d8bb0f0626bc5b8f127b079b16400cbf8a6387506e273e2c65aea440255b2c6630f211650bb86608d01b2383a37749a87196ee6e8e7c9549f0e93239130022918855b6f28af822c664bcbaf8094b6f8e7a13129a1006db7677301d7a8d8a38a9e92cbf195fe9eeef4eb3490b1152c89bcafbacef392a819c7af470211e423563f59a32746556b6bdc1779acc209393ee51b82156d05d1ce927e6e5250c12dfa9bf955cc9c1bbd71c6f448278ef9488a4f0077079ca9907313bb2f127088d1dd57461e7c365ce0f6238ccae73b32995970077428009140903318ebab7068e6f83df84501ab8befadba8e6c6e39fe2f407d33ff050409bd088e2d0335c8e192c25d4eaadb0d846d25819a90a76c38f4962a629640d49e9327d292b4cf991f721004c665a7fce8400d5eb0a3a7a70e076270bd414a8a7656bf0aefad54f1fa4da420c57d2ce4a93bba45af5f23501be868a58b0061876399da3e49a37e8e8d0', + '00deb81e80025d964e45c47ec82677021f698e57237fc830e0e2dd3b1a282e1dba84d8bb0f0626bc5b8f127b079b16400cbf8a6387506e273e2c65aea440255b2c6630f211650bb86608d01b2383a37749a87196ee6e8e7c9549f0e93239130022918855b6f28af822c664bcbaf8094b6f8e7a13129a1006db7677301d7a8d8a38a9e92cbf195fe9eeef4eb3490b1152c89bcafbacef392a819c7af470211e423563f59a32746556b6bdc1779acc209393ee51b82156d05d1ce927e6e5250c12dfa9bf955cc9c1bbd71c6f448278ef9488a4f0077079ca9907313bb2f127088d1dd57461e7c365ce0f6238ccae73b32995970077428009140903318ebab7068e6f83df84501ab8befadba8e6c6e39fe2f407d33ff050409bd088e2d0335c8e192c25d4eaadb0d846d25819a90a76c38f4962a629640d49e9327d292b4cf991f721004c665a7fce8400d5eb0a3a7a70e076270bd414a8a7656bf0aefad54f1fa4da420c57d2ce4a93bba45af5f23501be868a58b0061876399da3e49a37e8e8d0', '6d8e737436b00797a822dddf13236520ae8de545b258373c22be9ed5aa978a00359b1afbd916f26026272e20803e2c90eb4b27c1476730a5f7839553b854d0b4551b876a1bd81d1997225dbaea27338bcf4ca6ad5673a0f25562cfce2da6d18fbeaa06407a061e5f6861039780208ffa0959bfa52b6eb58100fe977c6bb794b6789d0ade1a40048937756721e1cd99c97efec12a1e487440048a48642e73cef25fb0fd914d59012162083b080285b4172c798a1c452a96f1b39825f8ffc581741f46ba441806afade3d112b5d71db6b55c43d7136cc946678dfbd4bf5406e8894d81d8f8cc071503a72889bef94032e43965427d052a8dc1e14f818345fca1fb64517958bb800ebc4b77b9f41dfb6c219ab07bde6809529d14966c8ba35ddc4aed17fc56e245f302b9a66775954137a02c90a426c01c3a20aa5d6cfde086c6f778f9eaf70f45eb659f2010294b5162afd7fe7d08de9f83fafc0560a5ee175a0749065955921e07fb07f0893353ca86b8176d2e9f47a5fe1dc157689c2c6c5d44', '062b86355079021e7ef9abf53f2da982dbd4b24ed8bd120b146b165560f0b26397571c2e2968a1f68c8b69950056accbbfecb3098083513711a4ebf137602627fef675a59f5762a9296f2a9bb6332a280ffe4e244445ae3bb8e922381f7540107591144324c61c4699ffd36f780aadcc8d3c84663ffbc32b6bc002779079e4a8730b127c911c2384baf7837738df8ca63ce2b84f785b03b488459040a2afaa2b252dc8adcec0b7a5c7c7bd464bc777e8cfb005d4d9790c2bfdeb919d4346bea0e215844f8aff63742bb0815c884ec77fdab0fc90f44271cc84e0335d797cd5a428138876b6d33a1d856034e6b4df67b508ba09bcaa89adb2d8e2cd8f0fc2d782cccb6b78eec123999196d2f0eb5492ba81e47ff47e44669c8a555664f0a84cffe2dba306b2cf8780edb3a2d25ab903ad8522a3ddb237fa2955b62e8337e5e58a8cefcb350bc0587aeb6a85fbab3617b735e4621e18f80eb7300ad846c0b983cca7798e0f359c5ee28113920f489c157436bd2152af02331d0a32ebbe5e943264', 'af0421d3aba0634da7dfa3453842c6977ad4c5ec63bd1decb476920292c59ec646e7ddede791a3564c36c8bf375a438ebaa278504778f40229ded68beb49972e0d251be68efc1345effc67822d53e52ee4604f76ee89494212b56ee34a329b8ba1fea7d70b9dd2447892a3be1b9c054d458cb189576fd226f3c024320c4fd3eecbb1d26b607d99ac41b26ea19e49dba72658d6ac6d9b2d3e8c8d67c5fe3ace7f833c966b281efe9b394b3f443493c7e3e76a8f516bdc4b865178382daa109c48de36f148b41937268a3d3ff4959681d35d7317cd774f6cbb7a669b1d3e620deb7a83fb35d659d2082d2e555653a333b735e7b893283f73eadaa0ed5a88891a6a05933ebca368251148b8dc30a52d17563ce5670d6c8adce1aa1c7c0a6ec70e704dd0f4a140d89d018379b3732543bc862bf5babdf5788dd0950e093843a9e50e5eb89bcc3ed6eee7a441c45d1b172ad5d77cf253cc3b5bcb5b7a8b730fa01f2cf48c786319b55895cf5aa8ac760c78c5b28e111f7785e29341df9240a68b7724', @@ -1511,7 +1511,7 @@ export const mockedPaillierProofs: { 'd5691361f2c6e1aa2d9efd068db50b6e3fda3fe76e9a8bf7423c3614eeba5337be6062dcd32844f182352e2cffa6a22d25f873b7804dd138a7406de0371d70a34a0c96b799799b98be542f263c06ba266c62c18d2299b6b5604623519c09a3595a0c3b21ef1b352718f58fbc71d9b88a76b816c4db3a1132e35108a68cf446f4afce9698ff8bbe2f1b3874fd1729d2719034f73f0b59e31234d0b06856c9e36a7633e633bd14935b35100afc617fc283517f52fafa0383a4aa16d1594a99968a09bbf22a099898f133c5458f7e943e9208e036e22cc4277c37bb6d07cf5e90b56724e107cf83795c8ca38235b687ec92e3fb02d0ba5855a83f30503a44ae00d6e16dccb0f9df38242a3f4c07394afd253c50338e850b196c77262fa5cd662560e251273efad57b50cd0578784ec6d9b5b3be7a74adc4133e9ce187c88cd98290277cab35a96a0e3d670317829edd48b1da153902efae66d47cb01f89911fb0b3f7a2519bc3f9fa5418eddb5acff56b77cfdfa7cb5eaaa6230c2fdf45a2037010', '4e0abdd03be7f2d9b757db43563644064bcc90b1d16004ed0c7e9e0ebe62f0007967e7df31661ae9215fdc33b8dccde247c60b5a0e83effeec32f669a04db2b1ceef85828a245f5fee6c6bd94dee14b8900d31102b9f39190be112af79f930424944af63f9b28e849d25cc0407fa8baa6df6b626b4b3ea0fac1fbcf34ea94d4020a03b763284656081a4a9ff1b4a61726a538338c422c324abb41e30f5cef589a90593253de8ca719c1451d98bf68ca189fc094a3c717c5b9aae4c6876f47f1a5267b61d3cb228dbf60fd779532057ab6d117146cba7eeea91e8d6734419d670f975134bdcd010dfd89070a713f0d89008b61c1b5bd9a2ab9dff4dec29a8a8b2b28fd39a6fd7ae24f9d8e7dbb0548cc8dad9e82afc1cfbea01e48426d2ee848ac36b288fded641bcd7e8d84b54141695a72ecb0b515eb0925b0a815d016069339fcd36ffbc4dc4b4231e0ee917f96d210f2bfc4c3bea94eacc1204810a71f0b25d3f6b928defbb05fea7857c200aedfa1fce5052d152b6e2de77ad46d8b86f32', '54f63fb913ad9c969ea52854d4cde5107b70732342c653559cc47896f9f1edecddc6a7fc1bbb8d6fe2fd6d3a37731e07dfd601e4f78eac2e417f923754d349c5863f6dea70a1851a10ea85bf4cfa005a7df7ac2dd6b52b2a3a729daca62115dea26852978d470dc1e46ea0c0d05e528658c2508edcd60aef228b04e2f3f871ee2bf401df6e2bd029dcae8159c0cc659786ebd27856492af8a00c9bf67df862dd1a8fe3bc439e100ac992601c25caa8ec9b154013ed895705da1acce3a3dcba9ef51625078bf5a2f3f8e330bca1d50bafdf81c029d7ecb9b4a013c378d64abe20669d38bece3ff13bf3bedc60adb18ef7590bccdd809f53411d45474ca9cbf5dcef5f7fc35d0618546bca44a47d3d3a0ab5bf3280ea6106833654b68f3d93e355e06d81cc6762d800fb4fe470094ee4703eafb5cdc3ceb73ade86228973a1d3133e1bad14097c8708170417e9a856d8aa238fdafbee06e5140d091c08ceef43535e654ade23190c77f1989f30db39e57c8d5c8472baeddea71ab335d91a54b014', - 'ac7540e896ad624ee4ad4d8884eeb1131cbec759f13111c33a19a4cff9b88065642ea30cca882435c8cb903ac797e521fbc0a25ba98b3ce97281d759c98e439d601d14debe8b337bf2e438442355b85bc94cecb53006264bde920d8046c01717b89d3f61ebbb6c9952cd9364e90c6f26f9abbd2895821e5bda31271f794a8bb2cb4867ae85dd0d49073880aa94083868f67ffa646bf79b2c16b856220c6d766fefb57ae828151bbeadee5ee32a56b99b40a9eed1f54865938e7584d442e7055280692649f233f090fcadcf4bf87cebdeabe6011bb3b0d74b3b1436a154e3f25230cc71ef686b644b3b1e576016e9789a04eaacb5e146bd74522aeacb11c3beaf590f96ec67b1947f6d1aa58eaadb74acd5218014b9a097f02b4d3077f8e7bc5765b29a25522051e36c461ce1c352b08ffcd96ec5751250bc2056f02243c66840a9c3d2ad6ed70549e59db3e8443e59a61db57d4c2042083619bc2e80e9d0f44d68e3414d99cfa3e2cd21d11a04e59d6f0cb6f4bad0b7ddb8fe231330224a53', + '00ac7540e896ad624ee4ad4d8884eeb1131cbec759f13111c33a19a4cff9b88065642ea30cca882435c8cb903ac797e521fbc0a25ba98b3ce97281d759c98e439d601d14debe8b337bf2e438442355b85bc94cecb53006264bde920d8046c01717b89d3f61ebbb6c9952cd9364e90c6f26f9abbd2895821e5bda31271f794a8bb2cb4867ae85dd0d49073880aa94083868f67ffa646bf79b2c16b856220c6d766fefb57ae828151bbeadee5ee32a56b99b40a9eed1f54865938e7584d442e7055280692649f233f090fcadcf4bf87cebdeabe6011bb3b0d74b3b1436a154e3f25230cc71ef686b644b3b1e576016e9789a04eaacb5e146bd74522aeacb11c3beaf590f96ec67b1947f6d1aa58eaadb74acd5218014b9a097f02b4d3077f8e7bc5765b29a25522051e36c461ce1c352b08ffcd96ec5751250bc2056f02243c66840a9c3d2ad6ed70549e59db3e8443e59a61db57d4c2042083619bc2e80e9d0f44d68e3414d99cfa3e2cd21d11a04e59d6f0cb6f4bad0b7ddb8fe231330224a53', '0345c7362242eb94aaa46f005f6e6bfe9f33933ad5ce3e1df44d918d68e9b85dc459b5aaba37b43cb24d48b5e437b6483d68e3064b6339521e79a0c530ba4c92d197aa0c02d2d0bbd5859056d0bc0558084ba5949f3b50d94012dacc884aa5d9402b85dd03275f3eeabe20a54d7099cd6a7590c459e06b9b7c8bb21c86214b04fba8c288a331725ce2ec7736982cfc19030e44b6cea2f1a6fb0d644ea88dbb37048502ef94786542b852c39ab487a11ae557da6920367f815b4a0b9253bc13d9df317e16b23dc9ac9fadc59a9f94d36a542c4d33c926c95e44aaf347371f07405347a7511e39f1a376e71b14796b1dd2e8caabff678e07bd4ca62d49cacc2966624616043c221a7d90036ca183b4b7f082ed0ab6bfd6b2afccbfb1afb62c1a49e22b44dcb3d90d7d97a3306785d06213d4b9a49e04d1b61b7dcf3e4e61e19d28ed9d62bfbc5a7e7ac8e18ca12dc2e8396314e7c081dab9fd56b2e5a74e63b0d2eaa005eebb5ab30765ecab4b7b3c7b02b49adddf0516efcc9db3d08759a1f90a', '4a2eeec99999d2168e14249718498c2bd7a78228a7ae33e21615304d3a4d0cb5dd917cdf9f128021a0fd8e25d56cbfa1a1b04d7c895d6c67c5a6352e3ab93dd846731d18622b3b25079355076ad02274a5d37116736c5c303e7fd44c37ded39fff165cdb97c240cb8e8107cdf4e5ddce521f32f3d1fec81bd4e2fddc8ba7c51e8dcf7343f137e40e5cc735f1ae935a4757f313ac45a17dfe8348f8f4fc8f66609db5036241fa11cd503335764929e094e18d36f267771350f1941e14bd16e0b8c7e4f8124efd1afec9d62909e8acafb4b9409b13dab3e48222fc2c16a0b035628ac994645f975bc3b3a4ac483c0132222078397b6e57726cf6a1c4d3b3816fa4700970a7fb4b5f85aaf3c33881447923135eb9285a9f4438a2adc15c6302b98e14208c56c2952686712a777c378496157b5c5e9e370635a192044f438180a324c82d03db5506dade5f6f3f4dca66a3b5100460bdccdb1c8debd1d2855e3e1ce7f6a817414786819f6bea578244291d72d5468fbdf93de09bed40575fa70eae1b', '5cc8fc26f0a55e5beb8ea554df449e4dc8f44317f082ef29b6d5eb97a94737e4530d1fb98e2f286cf50ef13cad8f00a4b8bf218a1fbaacf33779b9d5af0557074908f67103f34991cf5526670f9eb78283db457763ea614477700ccc23e26d5835cb96482f2d6dcc7c12a586dcf78340fdc8888ffd212529b8bea95804fb726b69f29ee63f6ad452fa9eb8974f4b76879fb35e26b77533399c871bc2a4eea91d69e45528447d2c2e81e5950bc2fadde628d414976650be119b1e9f0405a8743052ae7866fed5648f70eb005572976536d2ec07bdd7ad5fd3d7530547bd51140758475c786478244712e014c25084354cb711f03923bd8af2d63910516e6e79d4131c5779e56fa5c80223669d35cfa942a88309d7dad3f06e6d7780a92e82418bcfad0b8fdb30bd11b631784c5a322a78003ada450da040d39225754d36ca30ddb1567588de24e2788e8d42faf3695eb64576124c8b1df237050ab7afe1e0c7d78f52624892edac16d516d2c7c2cd7917a54a52b1e749ceb6ace93dc7bd94e04e', @@ -1839,7 +1839,7 @@ export const mockedPaillierProofs: { '89d5c4ede12ca3c7cb39db4d93aa85df4631035176083c12d197557661fd0c7929353808cead29d490adedd8d65d3ae2236c6a6a8ef106480fa1919183b12a3164e7b9c9cb5a012e5a0fe52c12bad4f87edd0bf8d182cb847df8e07376700504eeb740f2aec37de40d33bef0aefd823e082fd70654a01030017c91e1ccf7024db86c64b3d4e2a66e359b39238a4a53e9846a69fd7507e74d4bd0d01edf3e87703471e1e9872fbb8e38ce3a6c109715a056c4be560df62edc9f6703464307080cdeaaf130054ce7321b3a170acca712bbccd2ea5f34366d10b1161f8187b894ef8a37051b97536591cc2d07b6139950976c1f406daf27bfdc9d2cd06c97d4193bfb08465a87938ad9cb51fd18f95d702934839e409b38748172368b06f76536384d9a67d680f4b4d75a92f1a533221b603e641120409cd861ca4bf8b9fb1f0ec8412307dab85de65e0d28c1ba1e985f71ec1400bc2990faed2ed154ac9f10d0b4ddfc56849b5ae336126e54a9e69b88acf3eed12f92ae589eb2738860e966ca8c', '0b15bde3883abf5f619cd55661b5dad502d8a7662f0f571dac223044dd27572c3e3d0e4160454999f7eb255e441b35b4d915a26c3ea97531682cef13827d6461e740ae063c40d89a80f187c2023a5237830a0e6acac8e46966b31aea2fb3e3766355d2acb9674d596aff6aa3976bd9185c54982b8302620c4e66c0a149b077ee20fa7879991d6ad5042518adb768e56fd112b7afca4c74c489390b4f6e14133a6873f3791d2b295fe2845f4b37560e374670d10f7e9b961f09b2860714e2696f483dca83e430b709ea31b60cd320ed5318cf2e464126f61cb72ac12e472faa056acb5748a28f04d0e4f4e4840818b33f11583a4e5e73fb32e519177e2daa1859187eee41552cb477695dae4c8a325f39b8fce6bfe6bc4256e009e64a6fcf1ecb06326d5a54e6839cff72ce35452faff0e106f948cf17c266a544596bbe0dd56f76521d58113b7f3881e7a9931b7aa092d4515f1def4e2d509489d91b94da2147b2bdf21d189acf1170109e9d0cfe43e756834bdaeb19f1ab1fc9078a17f52190', '63f47c0482dec5a0c26abbff3a083418d461f5c7348cb42ab98377816eecfd7f7ed2606fa6b8a0121211f9e4b9323f9c2ea49358a9b59f19f374080a4ce73c55d6a634c10974f6a9c1cca53914c71b3afb041df5981202aab67ca6c90f471033641d8afb3b0d2e62c88bc1393894eaace9eab62d7ce037b7e9c8c6a8b6c152a0b6ca61891bd76238e89447ce4337599c07c86aeb11d69ff552412d26d4b0f685b57c73ada46fcefb76d12529055e80d49f6de6cc7d9c3c6eb97166d7bbecf9d99f7876740c31c434c1359ca1a43b3fd11d3c2a96fbcd2ae96c6701f92b120714698dcf020be61f725d4672eea6fbc88eb4c3702cc5bf14fe14f2af5357c793a3f4c5a7d28514eb6c4eeef7d6584242696951f9175044c57a949b682d11a6dab79f9febb258be2852a8561dc5c851c9a11fb2a95113f6825cc67220e1af567a2c59471a37b9826afa4b0ab56939cd908261bbd64fa6731c0e84db32a13525dd958c1d20b1a3ddce645ba7f23ce95a76ee1ebb17c80e85bc62925e0050400a3324', - 'c24c245fd8f644d6c5c0f68d49b59c782d9cc677295364973bdc39e170fd0e092458ff62bf288697f18859be4e5261e33bfa44f7a3ae2b727b8d4b979b085255522e6af954eb3fb7b0f5ba58103faafdfee3047c88b194295127a9bbb1fe1aed751309f03b3479bab391618246a6436696b6721417f218a38c54915098f61cc412ef0b26b26b4acc75467d4fa5a01308fc1773d5cd50c0165168a7f6e2083fd8e406d7d841558343ac25229e8270f959a4d89e48ed2582242ed728d2cc7a5a670c2799ed8b34f8a75371524a628c71d926844e278f69282bdeabd330acd2890ca5308880e97892324c83f7088f5dc502424b64c2e74387c4011612af8cab215d6cabfe6f557b082d2cf26ba1f424fe178beaae35165b96ac99cf8331e3d4e92c721a15c9e8e7fb50733cac960bc3be66a1e36a390e8828f9b646f83db00e9c9c28cfb87d1f0a3a3807c47bc38e35b385fe6cbe0494b6b8ffa1edb7fa8381e95be9196c861b741d6135c31c38f5e679d493f5b0bdd31d99d094cfffd3bafcce', + '00c24c245fd8f644d6c5c0f68d49b59c782d9cc677295364973bdc39e170fd0e092458ff62bf288697f18859be4e5261e33bfa44f7a3ae2b727b8d4b979b085255522e6af954eb3fb7b0f5ba58103faafdfee3047c88b194295127a9bbb1fe1aed751309f03b3479bab391618246a6436696b6721417f218a38c54915098f61cc412ef0b26b26b4acc75467d4fa5a01308fc1773d5cd50c0165168a7f6e2083fd8e406d7d841558343ac25229e8270f959a4d89e48ed2582242ed728d2cc7a5a670c2799ed8b34f8a75371524a628c71d926844e278f69282bdeabd330acd2890ca5308880e97892324c83f7088f5dc502424b64c2e74387c4011612af8cab215d6cabfe6f557b082d2cf26ba1f424fe178beaae35165b96ac99cf8331e3d4e92c721a15c9e8e7fb50733cac960bc3be66a1e36a390e8828f9b646f83db00e9c9c28cfb87d1f0a3a3807c47bc38e35b385fe6cbe0494b6b8ffa1edb7fa8381e95be9196c861b741d6135c31c38f5e679d493f5b0bdd31d99d094cfffd3bafcce', '7cdc0ae1241de98ce5719b04c9ccbd427eb146519b6c9a7307d6638d6e7d178583c3874d51fc886cd5d2d2ece3c8eea4bc0c4043291275dac4529ce723afe7ddf8a602470bbe137aacdb6354463233b47cfcb285ebcf18d93c71977eca2299b4ec623bfc4382a2688b85f211a5ea85df86a5a58b395e0f0004cc98a936cd77b0a33c32a09d90f7bde66b812b8375fb8fbd90ea88c651d53fd61387739859b2ead636ced28c12a6bd8d5216f68099f2d88fc2173b375ee58178ed525c86275b59e7f24d90f3f4bdf7f05efa72b965094832b70e26f4b4a05c7306edb85959a3f8dc1d512301d82338b55722c9feb04e6e5aa5226764f6ef770406f46bb70d1a6818edc53fff52527b97083c1cae2f0a78cefe6f71b3c4714fa5e0357a6ce89ef300ebac26a129aa8a26322bfb7ce5508b57b17665f7f2b17cbed3613b66929acfd96dc368279a88244de41d7992bd3d4a9804a25078beceba76884cfa6db6736a9c375afe2f64a23de2d429903eaea82c5f84d39a68cd4a6326f69f25a68aca63', '1e65915ffc2c2c33a1df6609bce312272901ec205407f7d8fa8d4b284cfc75375f4af3dbb1722d2a28f8aaf33ce0cb29e450986f26a4ef710e29c5687dc220fc7d93a23cd787f2ce2e0264e0d92e04b4234d21791b727d93116a40922d6c020d705661119f8469b160f3332c8d52009b1e4b323457da09e573a798a67cffb61efeb7e469c079fe1dd15232e073ef8c3c4252626cecb0accc68c2e6d93e15a8973d24c467a6c12fc514deea8e90301dcb6534b2f69efbc44edd4867693efe37285c79055887d29c4a23495a1850e010e628218cc42cb5d3aefe7d65dd722baef2b03d6279671683c386c3b0714f0c69904f206589ffa5f86bb597019644d3c16e7f3bcffb2408802419994c3c7184fd07fb3b15ccaf15eaaad6d051a5faefda0f6f1bbdfe624fbd707ad6486744fb3c6ab1733408c082a6c3a3b500402f80515001d1cbc7d5268b0746b8fb2fde65bbc8c78b53d038ad1be4ed88993cacb0bf038bcfd959652a63b4ff213025112e3fd4e442b2de88957c79358a0a3fa138bb7c', '57d5fc5a83933c1a33a773f98b702fd6bf58a67c220ca4506d4231d62333c20876333f8f0d9c42698dd6baae24c3dacadb67fa29d829d50d69500b09fa9e40cd78755b6cb85acfac2a19423fce382451f831de4b071869e05e0be4464f4ea6228226bd2e69c4539913dd36695e75b4128d907202acd2bee09453a8857e1bc1c02a2eb5ef36fb670dbfe913316fa5e3a240fb1b3469d621acec22fd7e2edc0b0887cc7cff13a250550357b1669bdfa3b6fae4c00d90ae673f79c6fce6adc694bb11e2f83794633691304c07a40f23b052b7eabcf84bdd29ac7127521be03502c981ec09535f46bf4684e5548f4a4e5e1eb9676426262b1474d2a003824548c5b3e8e78744623ff1c2e0cd79091df3d23cfacaa69527548de7b24eb23ecc3a605bc7eaf83f1636d0e91f64d81a7fc4ab35fe26328f83ce3ed46a5ce607fbb9f35b08776c662f2df04d9534d57143a27f88541ee27478c64c5eb68ea63f61c449324d34afb5ec7083a3d3080c76d774ea39f143406184d608ff550720db1a4f13cf', @@ -2317,7 +2317,7 @@ export const mockedPaillierProofs: { '83b46cbb9d5ba2b53d1b1ab93ef4f7e05734ac9d87948cdd530ac8136c61bf139d591a45c9d7f937f982f3c80bc0d4ace88d045514d479da3b9c62f0096f3e9cf254ceb62731c19ac999fe642e075283e6a48afc1c4e80414c736fb5e15eccfa2f1c85107d491d8dd27cabb8b5728c63d42afb783d0b21a54e39da4796746bbcf9a43ebc89dc2498fbb1138dadf134ba95cea8e184a396b6b34a86fe7bde99023c13b76437322021e908be10998f3810120fc75576d1af02cf8546fe0147ba7d84dfde05a67dbf0ce6a2df0023463faa25634a6e82ebd4f25ca9f8fb3e1f9a7fd1a144daa164f6d6dcc42d70e4d6860fbef30386901f40e32bb9edbc8b0924ca7951cf30bab2a42c6a035d2aa35617bb36d2fbb5dcaea62bc81c62a0ac33e763909a367bdce01c4c33e79ba8856b3501bd5c97f54f2339750caae6de31527948da3ef1946ef50f4d499d3afb87f38d2ddab8673997c9c61792d007d98b0bd9cb74f4f74607a29b4818ef6088b81c166fd61c6376155c71034468296fee73efb5', 'a56456e9af2cc0677f233ec295825c463adedb2efd3d73c27763224637c09cf9be784e453c60ac40f7d6cd7e88415d4f77bf967dec2cc3dec0cbdced09a09de5e66467f3ea17b5ea90084950f920d1461c692653301062b0f624596575e7d85d5d881aa05ba761aa22176a97dace89fe4a90aa5ed93808a18acc69c9c26615644597d754b9a6723b04d818c1cdf19b6d2f2677e3edd107a6d41a2eddec26eb68a90f641a9c0029901ca22cb4f59b5f1d08ed4f30a36cfdb342e214b11d93bc060820f7209d0da61ff08d41a06f600697b280fe211eddff6ab96d15fb3a90620eef6ecb6f8c1ecf3aac8f423a158f22e1a512aeeebda9ee5b2418a2d98b5f1f38d9faf23ed4d97d66e6eab604a4f98a7e82e46690a950766e460fe6a03d6fda3a1aabe8a2c51b145cc52363494663b4809313cc1640c70c55bacf8daeb29bcf122bc12e17a3bc9c87308642fa8fcf8febe452dba6191efec5f003c63640ee4f5cdb063b33e3d8ff4064b2e624d99a8df255af440d3cca2041b6d7fb37b958820d', '1e748eac8c59b03db45db93343ba25e049806540a5cf0e03bb6ebe16ca609ba41caf1250cea26d83dda4f0f62a4c388a270603d512247c2dc6259d8866ba97f3c2ceaea9e481d93f166d3ed7b98ad51d70ae5d39ef51773f22fcbd7060802c4da6cf7e478892e5b763f4a956c0afbca81dea18d23c619a7434e06b26a211b5dde61b6100f68361b027d2f9d820ad0f70b629b0020abcbe48d6d809adcf67bdcae8beeaefa524140b170aa6f52242e65e7f8f5750ba7b358c5c12b1f3e722924b2d49de1c441e64498b16ca1aa40147731a99332466cdd3d33845b83b6337f4b3cbf161c7a2ceb9f305175a7837583ae1a82cddb704112e05886f2bba92109c90659abec1fc85bdbf04787f2bd72015202913f0df073073d848472d4f328b54c94c0a2f7e7c40be40f63cf09d276004f2133725938f74e3ccd4e17d0194fd9646bc61f106381fa6c410b46a3bc1ef679523c9fbb8b3994d06a6d85931332b9195d3cd3549928207404a6eeaedbe52bb298de825191e41b6fed76298e72012e667', - '431f94c3564f7d18882fa1199d09a57cccd3627c9469d8f27f0feb71c0c0adf877e1e5513bfcb5d64f8d9c4938c6dcd5d28ca3615755df2bdbc7e52c486a846e10ee18770fb370e4f593ce82d882acf3a33239a8e20401d442bfab280703e327c1949a6fa95c03b437195bc598329cd08ba3a620c499e843cfea6b2f152ce192c38a1c82dccd22ab30a224a0e4f75ffccb7a20142d00ebcb4527b962d0af7fe71fa875868b0edb373f61294c930bb47ec5ae85f659cfd1e3b7a689a8e96a98ff83f4a077a656b08ea7269c77483da6820980e7a2adc25bfcbe18603d83081143a24f06a53a6ecf4ab20652b622f3311d1ca47841b712db3920340284061bffa605ac74e7a4739cff2556149f19fa0dab5c806bdfe73fe78ad8f64194a48e0827d35048efce4b2ab33af95e3a14711bf6c28581bd0ba2f4600f10383afd2a9dcf1bcecf42735333e767e341622c14b6bcaaf743335fcc6994277704cafc5e454eb74e130f215d43d7a50b0b97dc9f048f0e622465e96408ac06333c69df96a2', + '00431f94c3564f7d18882fa1199d09a57cccd3627c9469d8f27f0feb71c0c0adf877e1e5513bfcb5d64f8d9c4938c6dcd5d28ca3615755df2bdbc7e52c486a846e10ee18770fb370e4f593ce82d882acf3a33239a8e20401d442bfab280703e327c1949a6fa95c03b437195bc598329cd08ba3a620c499e843cfea6b2f152ce192c38a1c82dccd22ab30a224a0e4f75ffccb7a20142d00ebcb4527b962d0af7fe71fa875868b0edb373f61294c930bb47ec5ae85f659cfd1e3b7a689a8e96a98ff83f4a077a656b08ea7269c77483da6820980e7a2adc25bfcbe18603d83081143a24f06a53a6ecf4ab20652b622f3311d1ca47841b712db3920340284061bffa605ac74e7a4739cff2556149f19fa0dab5c806bdfe73fe78ad8f64194a48e0827d35048efce4b2ab33af95e3a14711bf6c28581bd0ba2f4600f10383afd2a9dcf1bcecf42735333e767e341622c14b6bcaaf743335fcc6994277704cafc5e454eb74e130f215d43d7a50b0b97dc9f048f0e622465e96408ac06333c69df96a2', '57b7d9264ec5df3add0d13d474fe3a9f2d7c42fc88bfc6739c23bbb7df7f85ce98f0e6ed485e344cf7f970f4b9b7ec7920ea0968bae465a457697781907b5742fbe082cb2d53bac75e1b32dfb6a407b94213f04a59aac2e647e6730cdbce4b641e91b9c2e8d06d676727b44f3e89fd72f91e614252ceb7d3df4f2fa2123194631d16cb76bf4934519db44c070c38550a0c1fc94471c0ba649089498629215bfadcd4cc46613d8c43f480051b3743cb7e9fa392e33f0328b81d60e80ee71cce3ebd049c5fd81a003f951b55e1992ad2340ad601cf226a59d2e23f0028f62205ac22b460765bba6b92c5c9fabb7adafb60daa31e58692334bad614ea3f5ccddedd5938238c9c596285abd496d57f50c6877e93fe44fdeeba722a38ef52f4e061d7a932489e810837e95c5bf9970e88c21416b22b7edfdbda7d64219bc38cc7a30f24079930eff0211fed14c8c7ca522c6369037f91b73998656514a33ae9911e2de0a2ca69a77ba4cef48527dd3defc5fa8191b49c2acb305fe1598f28b436d44f', '015706646f9bfcb3dacac8b5124c9678ca8e0f93b8f2a848f88a2e00612933c3a9a516a33885126066224688dcb49aafcde517b0bceacb3d7dab3df1b05d635628bb409859e481eb5c2b6d853d9af84c2c931a366f815e09c88ce5337cbe1c4e59fed9124c270a6915befeb78c2539fac2bbbb100542875de8dd1b67c698945efb942788f2fb1e775035ab37baac1399886aca1ae4ceeb8143889b376d9158f9876a56c94cb3478f37ac3141a0721efd3a3ed1a22d04c8ca235e0958a58855b48985a68a54ed821ae1db61254e563f49a265b9d2618a23ac3ebb95f6ee964ce26170b770d9154c879913aacf3cca0c723e6778841586a928425298193e696bd0f901fd1a352c13d432a3d40e2abc560a3067cc83da8c276371e18c9a924c9c84ea6d0684bd23a2b1966eb6460a4fe39c146a5e1c89a8874a9ad46cfa3524e8590914f6e2be6adc1e81b757fbbb370475ad308f6faceffa0e83070b3efbbd955512dd906277c6953c804da75b056434311b5675871a0859d4e18afb27b3d109a4', '236f0f31bf7de3a97ec0cd5fc70e9c1edd1d0e15fc0d0ff97b21bd396910ea638b81146830457969bc59106ece5c94b44bde4122f747b48c17a29338b5edc2230f14eb6ad8b065d734a10785e13508cffd4870fd6fb8ce664f23aa3ef309d8c1bc94b3b66f15946cbbc4e18dd220a2db577f392067bfccd91eb036661acda58b1e3e6f4e805a41d41d0d4695b2729d68d2c2259eb17c8cc267559e2049f22a0ae7f2955b9eec8f28b1ce404b96483c3363c28371831761504f953094c67a6e421dc9b9a526a8a27d5bf2c3576a957b05a3b06ed1c9950632dadb7561ee1916b7d1cb558e151baf0431e3fa0b192a7802307f0b87910de3680c4c72b72541d25e7a82244a854fbbc263091589449efbd51beb2372b5704a1b7508aba66132ff461c70072d2b30e679e2310c8a6c77fe63bd9924f4cd57dc9c3be4b3682b1c27c097549c3a41371e69e7827f4607e263bb3b5a4121c1215937213b7b3afdf62e4896828e5e4a3f3a2f0b517b71d5fcb1d549e18ac111aadf06806b4e2877577820', diff --git a/modules/sdk-lib-mpc/test/unit/hashCommitment.ts b/modules/sdk-lib-mpc/test/unit/hashCommitment.ts new file mode 100644 index 0000000000..35b456f37b --- /dev/null +++ b/modules/sdk-lib-mpc/test/unit/hashCommitment.ts @@ -0,0 +1,50 @@ +import 'should'; +import { randomBytes } from 'crypto'; +import { createCommitment, verifyCommitment } from '../../src/hashCommitment'; + +describe('hash commitment', function () { + it('create and verify commitment', function () { + const buf1 = Buffer.alloc(16); + buf1.fill(1); + const buf2 = Buffer.alloc(16); + buf2.fill(2); + + const comDecom = createCommitment(Buffer.concat([buf1, buf2])); + + verifyCommitment(comDecom.commitment, comDecom.decommitment).should.be.true(); + + const comDecom2 = createCommitment(Buffer.concat([buf1, buf2]), Buffer.concat([randomBytes(32), randomBytes(32)])); + + verifyCommitment(comDecom2.commitment, comDecom2.decommitment).should.be.true(); + }); + + it('invalid parameters', function () { + const secret = randomBytes(16); + const r = randomBytes(31); + + (() => createCommitment(secret, r)).should.throwError('randomness must be at least 32 bytes long'); + }); + + it('incorrect decommitment', function () { + const one = BigInt(1); + const two = BigInt(2); + const r0 = randomBytes(36); + const secret = Buffer.from(one.toString(16) + two.toString(16), 'hex'); + + const comDecom = createCommitment(secret, r0); + + const r1 = randomBytes(32); + + // decommit with a different nonce + verifyCommitment(comDecom.commitment, { + blindingFactor: r1, + secret: secret, + }).should.be.false(); + + // decommit with the wrong secret value + verifyCommitment(comDecom.commitment, { + blindingFactor: comDecom.decommitment.blindingFactor, + secret: Buffer.from('a-Z'), + }).should.be.false(); + }); +}); diff --git a/modules/sdk-lib-mpc/test/unit/schnorrProof.ts b/modules/sdk-lib-mpc/test/unit/schnorrProof.ts new file mode 100644 index 0000000000..61d683d5af --- /dev/null +++ b/modules/sdk-lib-mpc/test/unit/schnorrProof.ts @@ -0,0 +1,34 @@ +import 'should'; +import { Secp256k1Curve } from '../../src/curves'; +import { createSchnorrProof, verifySchnorrProof } from '../../src/schnorrProof'; + +describe('schnorr proof', function () { + const curve = new Secp256k1Curve(); + + it('create and verify proof', function () { + const a = curve.scalarRandom(); + const A = curve.basePointMult(a); + + verifySchnorrProof(A, createSchnorrProof(A, a, curve), curve).should.be.true(); + + const m = 'sign this'; + const schnorrProof = createSchnorrProof(A, a, curve, Buffer.from(m)); + + verifySchnorrProof(A, schnorrProof, curve, Buffer.from(m)).should.be.true(); + }); + + it('incorrect proof', function () { + const a = curve.scalarRandom(); + const A = curve.basePointMult(a); + + const schnorrProof = createSchnorrProof(A, a, curve); + + const a1 = curve.scalarAdd(a, BigInt(1)); + + // proof for the wrong point + verifySchnorrProof(curve.basePointMult(a1), schnorrProof, curve).should.be.false(); + + // verify proof with the wrong contextual info + verifySchnorrProof(A, schnorrProof, curve, Buffer.from('a-Z')).should.be.false(); + }); +}); diff --git a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dlogproofs.ts b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dlogproofs.ts index b195d5fc5c..80e55e7909 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dlogproofs.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dlogproofs.ts @@ -1,4 +1,4 @@ -import * as sinon from 'sinon'; +import sinon from 'sinon'; import { generateNtilde, generateNtildeProof, diff --git a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/paillierproof.ts b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/paillierproof.ts index 08e2364a6f..97fb227f28 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/paillierproof.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/paillierproof.ts @@ -1,6 +1,6 @@ import should from 'should'; -import { alpha, m, generateP, prove, verify } from '../../../../src/tss/ecdsa/paillierProof'; +import { alpha, m, generateP, prove, verify } from '../../../../src/tss/ecdsa/paillierproof'; import { hexToBigInt, minModulusBitLength, randomBigInt } from '../../../../src'; import { deserializePaillierChallenge, deserializePaillierChallengeProofs } from '../../../../src/tss/ecdsa/types'; import { mockedPaillierProofs } from '../../../paillierproof.util'; diff --git a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/rangeproof.ts b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/rangeproof.ts index cd6bdc2079..a017e537c8 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/rangeproof.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/rangeproof.ts @@ -1,4 +1,5 @@ -import * as sinon from 'sinon'; +import 'should'; +import sinon from 'sinon'; import * as paillierBigint from 'paillier-bigint'; import { EcdsaRangeProof, EcdsaTypes } from '../../../../src/tss/ecdsa'; import { randomPositiveCoPrimeTo, Secp256k1Curve, OpenSSL } from '../../../../src'; diff --git a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/zkVProof.ts b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/zkVProof.ts new file mode 100644 index 0000000000..eea135eedb --- /dev/null +++ b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/zkVProof.ts @@ -0,0 +1,58 @@ +import 'should'; +import { randomBytes } from 'crypto'; +import { Secp256k1Curve } from '../../../../src/curves'; +import { createZkVProof, verifyZkVProof } from '../../../../src/tss/ecdsa/zkVProof'; + +describe('zkV proof', function () { + const curve = new Secp256k1Curve(); + + it('create and verify proof', function () { + const s = curve.scalarRandom(); + const l = curve.scalarRandom(); + const R = curve.basePointMult(curve.scalarRandom()); + const V = curve.pointAdd(curve.pointMultiply(R, s), curve.basePointMult(l)); + + verifyZkVProof(V, createZkVProof(V, s, l, R, curve), R, curve).should.be.true(); + + const sessionId = randomBytes(32); + const zkVProof = createZkVProof(V, s, l, R, curve, sessionId); + + verifyZkVProof(V, zkVProof, R, curve, sessionId).should.be.true(); + }); + + it('proof not verify with partial V', function () { + const s = curve.scalarRandom(); + const l = curve.scalarRandom(); + const R = curve.basePointMult(curve.scalarRandom()); + const V = curve.pointAdd(curve.pointMultiply(R, s), curve.basePointMult(l)); + const partialV = curve.pointMultiply(R, s); // partial V is missing the lG term + + const zkVProof = createZkVProof(V, s, l, R, curve); + + verifyZkVProof(partialV, zkVProof, R, curve).should.be.false(); + }); + + it('proof created with bad s not verify', function () { + const s = curve.scalarRandom(); + const l = curve.scalarRandom(); + const R = curve.basePointMult(curve.scalarRandom()); + const V = curve.pointAdd(curve.pointMultiply(R, s), curve.basePointMult(l)); + + const s2 = curve.scalarRandom(); + const zkVProof = createZkVProof(V, s2, l, R, curve); + + verifyZkVProof(V, zkVProof, R, curve).should.be.false(); + }); + + it('proof not verify with wrong contextual info', function () { + const s = curve.scalarRandom(); + const l = curve.scalarRandom(); + const R = curve.basePointMult(curve.scalarRandom()); + const V = curve.pointAdd(curve.pointMultiply(R, s), curve.basePointMult(l)); + + const sessionId = randomBytes(32); + const zkVProof = createZkVProof(V, s, l, R, curve, sessionId); + + verifyZkVProof(V, zkVProof, R, curve).should.be.false(); + }); +}); diff --git a/modules/sdk-lib-mpc/test/unit/util.ts b/modules/sdk-lib-mpc/test/unit/util.ts index 2aa083464d..58c49911ba 100644 --- a/modules/sdk-lib-mpc/test/unit/util.ts +++ b/modules/sdk-lib-mpc/test/unit/util.ts @@ -10,11 +10,11 @@ describe('mpc utils', () => { it('should convert buffer to bigInt', () => { const tests = [ { - input: new Buffer('abcdef123', 'hex'), + input: Buffer.from('abcdef123', 'hex'), output: '2882400018', }, { - input: new Buffer( + input: Buffer.from( 'f1e4e82273030db23fe0e5e235caf0341b3e2c10e1e38b24901315f70e1b9d1efa44c53ce95e7328dd09924b1ab35b395cbbd32310e01d5fb166aafd03bd283885cb8be02e99c3de1c29137c3eb1394afa80e207fab8b7a80b176795469622d4f92650b3e4ad5d26119c514ebac5fba6a54251f32847faa6c23b53dfa079b6e0796b2b6bdb9af430c8346919756b5735500eb9621605f3e5712608e01b0a180231e8f912c6a0ed910e13e0df4e12f92d67faea8642f01b0c5aa7e5678016465bef75608e4b956be686a108e5d36b1d053c02932a5be26680e5f4db9e9a84491a32ea14c49d3dd7604fd0e4ced918399702969bc59481ec4a235b5181a2fe0b3031f791d495a0136e91e0288775645ec8cb05f2ac103b8dbe9a6febbdb43ccd16b14bc9705e8bd67591b7d6089155c9c20b90240d9cc082f26a721f77dacc458963352ee2b088652295197e16dce90a0dee9ca1a2e8b8244f53c64e69f04bbd104f06a3976b20a1fd64bc39459216463ee121f80176402c7ee11cc3708d0f2199', 'hex' ), @@ -29,7 +29,7 @@ describe('mpc utils', () => { it('should throw an error for empty input', () => { (() => { - bigIntFromBufferBE(new Buffer('', 'hex')); + bigIntFromBufferBE(Buffer.from('', 'hex')); }).should.throw('Cannot convert 0x to a BigInt'); }); });