Skip to content

Commit a8528d3

Browse files
Merge branch 'add-partial-sig-combine-dkls'
2 parents 522d2df + c7f126f commit a8528d3

File tree

6 files changed

+79
-8
lines changed

6 files changed

+79
-8
lines changed

modules/sdk-core/src/account-lib/mpc/util.ts

+25
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,33 @@ import {
66
bigIntToBufferBE,
77
clamp,
88
getPaillierPublicKey,
9+
DklsUtils,
10+
DklsTypes,
911
} from '@bitgo/sdk-lib-mpc';
1012

13+
/**
14+
* Combines serialized partial signatures from parties participating in DSG.
15+
* @param round4DsgMessages - round 4 serialized broadcast messages payloads from participating parties
16+
* @returns {DklsTypes.SerializedDklsSignature} - serialized final signature
17+
*/
18+
export function combineRound4DklsDsgMessages(
19+
round4DsgMessages: DklsTypes.SerializedBroadcastMessage[]
20+
): DklsTypes.SerializedDklsSignature {
21+
const round4DsgMessagesDeser = round4DsgMessages.map(DklsTypes.deserializeBroadcastMessage);
22+
const signatureR = round4DsgMessagesDeser.find((m) => m.signatureR !== undefined)?.signatureR;
23+
if (!signatureR) {
24+
throw Error('None of the round 4 Dkls messages contain a Signature.R value.');
25+
}
26+
const signatureDeser = DklsUtils.combinePartialSignatures(
27+
round4DsgMessagesDeser.map((m) => m.payload),
28+
Buffer.from(signatureR).toString('hex')
29+
);
30+
return {
31+
R: Buffer.from(signatureDeser.R).toString('hex'),
32+
S: Buffer.from(signatureDeser.S).toString('hex'),
33+
};
34+
}
35+
1136
/**
1237
* @deprecated - use exported methods from @bitgo/sdk-lib-mpc instead
1338
*/

modules/sdk-lib-mpc/src/tss/ecdsa-dkls/dsg.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { SignSession, Keyshare, Message } from '@silencelaboratories/dkls-wasm-ll-node';
2-
import { DeserializedBroadcastMessage, DeserializedMessages, DklsSignature, DsgState } from './types';
2+
import { DeserializedBroadcastMessage, DeserializedDklsSignature, DeserializedMessages, DsgState } from './types';
33
import { decode } from 'cbor';
44

55
export class Dsg {
66
protected dsgSession: SignSession | undefined;
77
protected dsgSessionBytes: Uint8Array;
8-
private _signature: DklsSignature | undefined;
8+
private _signature: DeserializedDklsSignature | undefined;
99
protected keyShare: Keyshare;
1010
protected messageHash: Buffer;
1111
protected derivationPath: string;
@@ -73,7 +73,7 @@ export class Dsg {
7373
}
7474
}
7575

76-
get signature(): DklsSignature {
76+
get signature(): DeserializedDklsSignature {
7777
if (!this._signature) {
7878
throw Error('Can not request signature. Signature not produced yet.');
7979
}
@@ -131,6 +131,7 @@ export class Dsg {
131131
{
132132
payload: nextRoundMessages[0].payload,
133133
from: nextRoundMessages[0].from_id,
134+
signatureR: decode(this.dsgSession.toBytes()).round.WaitMsg4.r,
134135
},
135136
],
136137
p2pMessages: [],

modules/sdk-lib-mpc/src/tss/ecdsa-dkls/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * as DklsDkg from './dkg';
22
export * as DklsDsg from './dsg';
33
export * as DklsTypes from './types';
44
export * as DklsComms from './commsLayer';
5+
export * as DklsUtils from './util';

modules/sdk-lib-mpc/src/tss/ecdsa-dkls/types.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { decode } from 'cbor';
55
interface BroadcastMessage<T> {
66
payload: T;
77
from: number;
8+
signatureR?: T;
89
}
910

1011
// P2P message meant to be sent to a specific party
@@ -47,14 +48,16 @@ export type PartyGpgKey = {
4748
partyId: number;
4849
gpgKey: string;
4950
};
50-
export type DklsSignature = {
51-
R: Uint8Array;
52-
S: Uint8Array;
51+
export type DklsSignature<T> = {
52+
R: T;
53+
S: T;
5354
};
5455
export type SerializedBroadcastMessage = BroadcastMessage<string>;
5556
export type DeserializedBroadcastMessage = BroadcastMessage<Uint8Array>;
5657
export type SerializedP2PMessage = P2PMessage<string, string>;
5758
export type DeserializedP2PMessage = P2PMessage<Uint8Array, Uint8Array>;
59+
export type SerializedDklsSignature = DklsSignature<string>;
60+
export type DeserializedDklsSignature = DklsSignature<Uint8Array>;
5861
export type AuthEncP2PMessage = P2PMessage<AuthEncMessage, string>;
5962
export type AuthBroadcastMessage = BroadcastMessage<AuthMessage>;
6063
export type SerializedMessages = {
@@ -113,6 +116,7 @@ export function deserializeBroadcastMessage(message: SerializedBroadcastMessage)
113116
return {
114117
from: message.from,
115118
payload: new Uint8Array(Buffer.from(message.payload, 'base64')),
119+
signatureR: message.signatureR ? new Uint8Array(Buffer.from(message.signatureR, 'base64')) : undefined,
116120
};
117121
}
118122

@@ -137,6 +141,7 @@ export function serializeBroadcastMessage(message: DeserializedBroadcastMessage)
137141
return {
138142
from: message.from,
139143
payload: Buffer.from(message.payload).toString('base64'),
144+
signatureR: message.signatureR ? Buffer.from(message.signatureR).toString('base64') : undefined,
140145
};
141146
}
142147

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Signature } from '@noble/secp256k1';
2+
import { Secp256k1Curve } from '../../curves';
3+
import { bigIntFromBufferBE, bigIntToBufferBE } from '../../util';
4+
import { DeserializedDklsSignature } from './types';
5+
import { decode } from 'cbor';
6+
7+
/**
8+
* Combines partial signatures from parties participating in DSG.
9+
* @param round4MessagePayloads - round 4 message payloads from participating parties
10+
* @param rHex - hex representation of the r value in the signature
11+
* @returns {DeserializedMessages} - messages to send to other parties for the next round
12+
*/
13+
export function combinePartialSignatures(round4MessagePayloads: Uint8Array[], rHex: string): DeserializedDklsSignature {
14+
const r = bigIntFromBufferBE(Buffer.from(rHex, 'hex').subarray(1));
15+
const s0Arr = round4MessagePayloads.map((p) => decode(p).s_0);
16+
const s1Arr = round4MessagePayloads.map((p) => decode(p).s_1);
17+
const s0BigInts = s0Arr.map((s0) => bigIntFromBufferBE(Buffer.from(s0)));
18+
const s1BigInts = s1Arr.map((s1) => bigIntFromBufferBE(Buffer.from(s1)));
19+
const secp256k1Curve = new Secp256k1Curve();
20+
const s0Sum = s0BigInts.slice(1).reduce((sumSoFar, s0) => secp256k1Curve.scalarAdd(sumSoFar, s0), s0BigInts[0]);
21+
const s1Sum = s1BigInts.slice(1).reduce((sumSoFar, s1) => secp256k1Curve.scalarAdd(sumSoFar, s1), s1BigInts[0]);
22+
const s = secp256k1Curve.scalarMult(s0Sum, secp256k1Curve.scalarInvert(s1Sum));
23+
const sig = new Signature(r, s);
24+
const normalizedSig = sig.normalizeS();
25+
return {
26+
R: new Uint8Array(bigIntToBufferBE(normalizedSig.r)),
27+
S: new Uint8Array(bigIntToBufferBE(normalizedSig.s)),
28+
};
29+
}

modules/sdk-lib-mpc/test/unit/tss/ecdsa/dklsDsg.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { DklsDsg } from '../../../../src/tss/ecdsa-dkls';
1+
import { DklsDsg, DklsUtils } from '../../../../src/tss/ecdsa-dkls';
22
import * as fs from 'fs';
33
import * as crypto from 'crypto';
44
import should from 'should';
55
import { Keyshare } from '@silencelaboratories/dkls-wasm-ll-node';
66
import { decode } from 'cbor';
77
import { Secp256k1Bip32HdTree, bigIntFromBufferBE, bigIntToBufferBE } from '../../../../src';
8-
98
import * as secp256k1 from 'secp256k1';
109

1110
describe('DKLS Dsg 2x3', function () {
@@ -91,6 +90,17 @@ describe('DKLS Dsg 2x3', function () {
9190
broadcastMessages: party2Round4Messages.broadcastMessages,
9291
});
9392
should.exist(party1.signature);
93+
should.exist(party1Round4Messages.broadcastMessages[0].signatureR);
94+
const combinedSigUsingUtil = DklsUtils.combinePartialSignatures(
95+
[party1Round4Messages.broadcastMessages[0].payload, party2Round4Messages.broadcastMessages[0].payload],
96+
Buffer.from(party1Round4Messages.broadcastMessages[0].signatureR!).toString('hex')
97+
);
98+
(
99+
(combinedSigUsingUtil.R.every((p) => party1.signature.R.includes(p)) &&
100+
party1.signature.R.every((p) => combinedSigUsingUtil.R.includes(p))) ||
101+
(party1.signature.S.every((p) => combinedSigUsingUtil.S.includes(p)) &&
102+
combinedSigUsingUtil.S.every((p) => party1.signature.S.includes(p)))
103+
).should.equal(true);
94104
// ////////////
95105
party2.handleIncomingMessages({
96106
p2pMessages: [],

0 commit comments

Comments
 (0)