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..893cd48424 100644 --- a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/dsg.ts +++ b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/dsg.ts @@ -1,5 +1,11 @@ import { SignSession, Keyshare, Message } from '@silencelaboratories/dkls-wasm-ll-node'; -import { DeserializedBroadcastMessage, DeserializedMessages, DklsSignature, DsgState } from './types'; +import { + DeserializedBroadcastMessage, + DeserializedMessages, + DklsSignature, + DsgState, + PartialSignatureR, +} from './types'; import { decode } from 'cbor'; export class Dsg { @@ -98,7 +104,7 @@ export class Dsg { * @param messagesForIthRound - messages to process the current round * @returns {DeserializedMessages} - messages to send to other parties for the next round */ - handleIncomingMessages(messagesForIthRound: DeserializedMessages): DeserializedMessages { + handleIncomingMessages(messagesForIthRound: DeserializedMessages): DeserializedMessages & Partial { let nextRoundMessages: Message[] = []; let nextRoundDeserializedMessages: DeserializedMessages = { broadcastMessages: [], p2pMessages: [] }; this._restoreSession(); @@ -134,6 +140,7 @@ export class Dsg { }, ], p2pMessages: [], + r: decode(this.dsgSession.toBytes()).round.WaitMsg4.r, }; } else { // Update round data. 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..98ec71d096 100644 --- a/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/types.ts +++ b/modules/sdk-lib-mpc/src/tss/ecdsa-dkls/types.ts @@ -51,6 +51,9 @@ export type DklsSignature = { R: Uint8Array; S: Uint8Array; }; +export type PartialSignatureR = { + r: Uint8Array; +}; export type SerializedBroadcastMessage = BroadcastMessage; export type DeserializedBroadcastMessage = BroadcastMessage; export type SerializedP2PMessage = P2PMessage; 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..38d3eb0037 --- /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 { DklsSignature } 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): DklsSignature { + 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..fa7c5cf62f 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,16 @@ describe('DKLS Dsg 2x3', function () { broadcastMessages: party2Round4Messages.broadcastMessages, }); should.exist(party1.signature); + const combinedSigUsingUtil = DklsUtils.combinePartialSignatures( + [party1Round4Messages.broadcastMessages[0].payload, party2Round4Messages.broadcastMessages[0].payload], + Buffer.from(party1Round4Messages.r!).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: [],