Skip to content

Commit

Permalink
feat(sdk-core): phase 5 of gg18 signing
Browse files Browse the repository at this point in the history
 - 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
  • Loading branch information
zhongxishen committed Jul 28, 2023
1 parent c7e754d commit fdaa8c4
Show file tree
Hide file tree
Showing 24 changed files with 791 additions and 39 deletions.
4 changes: 2 additions & 2 deletions modules/sdk-core/.mocharc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
};
41 changes: 32 additions & 9 deletions modules/sdk-core/src/account-lib/mpc/curves/ed25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -26,52 +29,68 @@ 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))
)
);
}

verify(message: Buffer, signature: Buffer, publicKey: bigint): boolean {
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
Expand All @@ -82,4 +101,8 @@ export class Ed25519Curve implements BaseCurve {
order(): bigint {
return BigInt('0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed') * BigInt('0x08');
}

scalarBytes = privateKeySize;

pointBytes = publicKeySize;
}
127 changes: 125 additions & 2 deletions modules/sdk-core/src/account-lib/mpc/tss/ecdsa/ecdsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
EcdsaPaillierProof,
EcdsaRangeProof,
EcdsaTypes,
EcdsaZkVProof,
HashCommitment,
Schnorr,
randomPositiveCoPrimeTo,
hexToBigInt,
minModulusBitLength,
Expand All @@ -27,6 +30,8 @@ import {
NShare,
OShare,
PShare,
PublicUTShare,
PublicVAShare,
RangeProofWithCheckShare,
Signature,
SignCombine,
Expand All @@ -42,6 +47,8 @@ import {
SignShareRT,
SShare,
SubkeyShare,
UTShare,
VAShare,
WShare,
XShare,
XShareWithChallenges,
Expand Down Expand Up @@ -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));
Expand All @@ -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
Expand Down
24 changes: 23 additions & 1 deletion modules/sdk-core/src/account-lib/mpc/tss/ecdsa/types.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 {}
Loading

0 comments on commit fdaa8c4

Please sign in to comment.