From c7f126f68f9ebfed370248daa3321fb14145c5c0 Mon Sep 17 00:00:00 2001 From: Islam Amin Date: Thu, 15 Feb 2024 01:54:36 -0500 Subject: [PATCH] feat(sdk-lib-mpc): combine partial signatures util support Ticket: HSM-341 --- modules/sdk-core/src/account-lib/mpc/util.ts | 25 ++++++++++++++++ modules/sdk-lib-mpc/src/tss/ecdsa-dkls/dsg.ts | 7 +++-- .../sdk-lib-mpc/src/tss/ecdsa-dkls/index.ts | 1 + .../sdk-lib-mpc/src/tss/ecdsa-dkls/types.ts | 11 +++++-- .../sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts | 29 +++++++++++++++++++ .../test/unit/tss/ecdsa/dklsDsg.ts | 14 +++++++-- 6 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 modules/sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts diff --git a/modules/sdk-core/src/account-lib/mpc/util.ts b/modules/sdk-core/src/account-lib/mpc/util.ts index ad5f2c522e..3af40b10c8 100644 --- a/modules/sdk-core/src/account-lib/mpc/util.ts +++ b/modules/sdk-core/src/account-lib/mpc/util.ts @@ -6,8 +6,33 @@ import { bigIntToBufferBE, clamp, getPaillierPublicKey, + DklsUtils, + DklsTypes, } from '@bitgo/sdk-lib-mpc'; +/** + * Combines serialized partial signatures from parties participating in DSG. + * @param round4DsgMessages - round 4 serialized broadcast messages payloads from participating parties + * @returns {DklsTypes.SerializedDklsSignature} - serialized final signature + */ +export function combineRound4DklsDsgMessages( + round4DsgMessages: DklsTypes.SerializedBroadcastMessage[] +): DklsTypes.SerializedDklsSignature { + const round4DsgMessagesDeser = round4DsgMessages.map(DklsTypes.deserializeBroadcastMessage); + const signatureR = round4DsgMessagesDeser.find((m) => m.signatureR !== undefined)?.signatureR; + if (!signatureR) { + throw Error('None of the round 4 Dkls messages contain a Signature.R value.'); + } + const signatureDeser = DklsUtils.combinePartialSignatures( + round4DsgMessagesDeser.map((m) => m.payload), + Buffer.from(signatureR).toString('hex') + ); + return { + R: Buffer.from(signatureDeser.R).toString('hex'), + S: Buffer.from(signatureDeser.S).toString('hex'), + }; +} + /** * @deprecated - use exported methods from @bitgo/sdk-lib-mpc instead */ diff --git a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/dsg.ts b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/dsg.ts index 9056cdf475..7074a87cf3 100644 --- a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/dsg.ts +++ b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/dsg.ts @@ -1,11 +1,11 @@ import { SignSession, Keyshare, Message } from '@silencelaboratories/dkls-wasm-ll-node'; -import { DeserializedBroadcastMessage, DeserializedMessages, DklsSignature, DsgState } from './types'; +import { DeserializedBroadcastMessage, DeserializedDklsSignature, DeserializedMessages, DsgState } from './types'; import { decode } from 'cbor'; export class Dsg { protected dsgSession: SignSession | undefined; protected dsgSessionBytes: Uint8Array; - private _signature: DklsSignature | undefined; + private _signature: DeserializedDklsSignature | undefined; protected keyShare: Keyshare; protected messageHash: Buffer; protected derivationPath: string; @@ -73,7 +73,7 @@ export class Dsg { } } - get signature(): DklsSignature { + get signature(): DeserializedDklsSignature { if (!this._signature) { throw Error('Can not request signature. Signature not produced yet.'); } @@ -131,6 +131,7 @@ export class Dsg { { payload: nextRoundMessages[0].payload, from: nextRoundMessages[0].from_id, + signatureR: decode(this.dsgSession.toBytes()).round.WaitMsg4.r, }, ], p2pMessages: [], diff --git a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/index.ts b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/index.ts index 8fc767a23d..3f311a4e8a 100644 --- a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/index.ts +++ b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/index.ts @@ -2,3 +2,4 @@ export * as DklsDkg from './dkg'; export * as DklsDsg from './dsg'; export * as DklsTypes from './types'; export * as DklsComms from './commsLayer'; +export * as DklsUtils from './util'; diff --git a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/types.ts b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/types.ts index 3b5e3cd011..1e90a826b8 100644 --- a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/types.ts +++ b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/types.ts @@ -5,6 +5,7 @@ import { decode } from 'cbor'; interface BroadcastMessage { payload: T; from: number; + signatureR?: T; } // P2P message meant to be sent to a specific party @@ -47,14 +48,16 @@ export type PartyGpgKey = { partyId: number; gpgKey: string; }; -export type DklsSignature = { - R: Uint8Array; - S: Uint8Array; +export type DklsSignature = { + R: T; + S: T; }; export type SerializedBroadcastMessage = BroadcastMessage; export type DeserializedBroadcastMessage = BroadcastMessage; export type SerializedP2PMessage = P2PMessage; export type DeserializedP2PMessage = P2PMessage; +export type SerializedDklsSignature = DklsSignature; +export type DeserializedDklsSignature = DklsSignature; export type AuthEncP2PMessage = P2PMessage; export type AuthBroadcastMessage = BroadcastMessage; export type SerializedMessages = { @@ -113,6 +116,7 @@ export function deserializeBroadcastMessage(message: SerializedBroadcastMessage) return { from: message.from, payload: new Uint8Array(Buffer.from(message.payload, 'base64')), + signatureR: message.signatureR ? new Uint8Array(Buffer.from(message.signatureR, 'base64')) : undefined, }; } @@ -137,6 +141,7 @@ export function serializeBroadcastMessage(message: DeserializedBroadcastMessage) return { from: message.from, payload: Buffer.from(message.payload).toString('base64'), + signatureR: message.signatureR ? Buffer.from(message.signatureR).toString('base64') : undefined, }; } diff --git a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts new file mode 100644 index 0000000000..c45ac9be4e --- /dev/null +++ b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/util.ts @@ -0,0 +1,29 @@ +import { Signature } from '@noble/secp256k1'; +import { Secp256k1Curve } from '../../curves'; +import { bigIntFromBufferBE, bigIntToBufferBE } from '../../util'; +import { DeserializedDklsSignature } from './types'; +import { decode } from 'cbor'; + +/** + * Combines partial signatures from parties participating in DSG. + * @param round4MessagePayloads - round 4 message payloads from participating parties + * @param rHex - hex representation of the r value in the signature + * @returns {DeserializedMessages} - messages to send to other parties for the next round + */ +export function combinePartialSignatures(round4MessagePayloads: Uint8Array[], rHex: string): DeserializedDklsSignature { + const r = bigIntFromBufferBE(Buffer.from(rHex, 'hex').subarray(1)); + const s0Arr = round4MessagePayloads.map((p) => decode(p).s_0); + const s1Arr = round4MessagePayloads.map((p) => decode(p).s_1); + const s0BigInts = s0Arr.map((s0) => bigIntFromBufferBE(Buffer.from(s0))); + const s1BigInts = s1Arr.map((s1) => bigIntFromBufferBE(Buffer.from(s1))); + const secp256k1Curve = new Secp256k1Curve(); + const s0Sum = s0BigInts.slice(1).reduce((sumSoFar, s0) => secp256k1Curve.scalarAdd(sumSoFar, s0), s0BigInts[0]); + const s1Sum = s1BigInts.slice(1).reduce((sumSoFar, s1) => secp256k1Curve.scalarAdd(sumSoFar, s1), s1BigInts[0]); + const s = secp256k1Curve.scalarMult(s0Sum, secp256k1Curve.scalarInvert(s1Sum)); + const sig = new Signature(r, s); + const normalizedSig = sig.normalizeS(); + return { + R: new Uint8Array(bigIntToBufferBE(normalizedSig.r)), + S: new Uint8Array(bigIntToBufferBE(normalizedSig.s)), + }; +} diff --git a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts index 68ad09808d..6f9200f254 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts @@ -1,11 +1,10 @@ -import { DklsDsg } from '../../../../src/tss/ecdsa-dkls'; +import { DklsDsg, DklsUtils } from '../../../../src/tss/ecdsa-dkls'; import * as fs from 'fs'; import * as crypto from 'crypto'; import should from 'should'; import { Keyshare } from '@silencelaboratories/dkls-wasm-ll-node'; import { decode } from 'cbor'; import { Secp256k1Bip32HdTree, bigIntFromBufferBE, bigIntToBufferBE } from '../../../../src'; - import * as secp256k1 from 'secp256k1'; describe('DKLS Dsg 2x3', function () { @@ -91,6 +90,17 @@ describe('DKLS Dsg 2x3', function () { broadcastMessages: party2Round4Messages.broadcastMessages, }); should.exist(party1.signature); + should.exist(party1Round4Messages.broadcastMessages[0].signatureR); + const combinedSigUsingUtil = DklsUtils.combinePartialSignatures( + [party1Round4Messages.broadcastMessages[0].payload, party2Round4Messages.broadcastMessages[0].payload], + Buffer.from(party1Round4Messages.broadcastMessages[0].signatureR!).toString('hex') + ); + ( + (combinedSigUsingUtil.R.every((p) => party1.signature.R.includes(p)) && + party1.signature.R.every((p) => combinedSigUsingUtil.R.includes(p))) || + (party1.signature.S.every((p) => combinedSigUsingUtil.S.includes(p)) && + combinedSigUsingUtil.S.every((p) => party1.signature.S.includes(p))) + ).should.equal(true); // //////////// party2.handleIncomingMessages({ p2pMessages: [],