Skip to content

Commit 2cba968

Browse files
Merge pull request #8464 from BitGo/WAL-377
fix(sdk-core): verify signatureR consistency across parties in DKLS round 4
2 parents bb87b20 + 0be6a60 commit 2cba968

File tree

2 files changed

+66
-2
lines changed
  • modules/sdk-core

2 files changed

+66
-2
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ export function combineRound4DklsDsgMessages(
1919
round4DsgMessages: DklsTypes.SerializedBroadcastMessage[]
2020
): DklsTypes.SerializedDklsSignature {
2121
const round4DsgMessagesDeser = round4DsgMessages.map(DklsTypes.deserializeBroadcastMessage);
22-
const signatureR = round4DsgMessagesDeser.find((m) => m.signatureR !== undefined)?.signatureR;
23-
if (!signatureR) {
22+
const messagesWithR = round4DsgMessagesDeser.filter((m) => m.signatureR !== undefined);
23+
if (messagesWithR.length === 0) {
2424
throw Error('None of the round 4 Dkls messages contain a Signature.R value.');
2525
}
26+
const rValues = messagesWithR.map((m) => Buffer.from(m.signatureR as Uint8Array).toString('hex'));
27+
if (!rValues.every((r) => r === rValues[0])) {
28+
throw new Error('signatureR mismatch across parties — possible protocol attack');
29+
}
30+
const signatureR = messagesWithR[0].signatureR as Uint8Array;
2631
const signatureDeser = DklsUtils.combinePartialSignatures(
2732
round4DsgMessagesDeser.map((m) => m.payload),
2833
Buffer.from(signatureR).toString('hex')
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import 'should';
2+
import sinon from 'sinon';
3+
import { DklsUtils, DklsTypes } from '@bitgo/sdk-lib-mpc';
4+
import { combineRound4DklsDsgMessages } from '../../../../src/account-lib/mpc/util';
5+
6+
function makeMsg(from: number, rHex?: string): DklsTypes.SerializedBroadcastMessage {
7+
return {
8+
payload: Buffer.from(`payload-${from}`).toString('base64'),
9+
from,
10+
signatureR: rHex ? Buffer.from(rHex, 'hex').toString('base64') : undefined,
11+
};
12+
}
13+
14+
describe('combineRound4DklsDsgMessages', function () {
15+
let stub: sinon.SinonStub;
16+
17+
beforeEach(function () {
18+
stub = sinon.stub(DklsUtils, 'combinePartialSignatures').returns({
19+
R: new Uint8Array([1, 2, 3]),
20+
S: new Uint8Array([4, 5, 6]),
21+
});
22+
});
23+
24+
afterEach(function () {
25+
stub.restore();
26+
});
27+
28+
it('throws when no message contains signatureR', function () {
29+
const msgs = [makeMsg(0), makeMsg(1), makeMsg(2)];
30+
(() => combineRound4DklsDsgMessages(msgs)).should.throw(
31+
'None of the round 4 Dkls messages contain a Signature.R value.'
32+
);
33+
});
34+
35+
it('throws when parties provide different signatureR values', function () {
36+
const msgs = [makeMsg(0, 'aabbcc'), makeMsg(1, 'ddeeff'), makeMsg(2, 'aabbcc')];
37+
(() => combineRound4DklsDsgMessages(msgs)).should.throw(
38+
'signatureR mismatch across parties — possible protocol attack'
39+
);
40+
});
41+
42+
it('succeeds when all parties agree on signatureR', function () {
43+
const rHex = 'aabbccddeeff0011';
44+
const msgs = [makeMsg(0, rHex), makeMsg(1, rHex), makeMsg(2, rHex)];
45+
const result = combineRound4DklsDsgMessages(msgs);
46+
result.should.have.property('R');
47+
result.should.have.property('S');
48+
stub.calledOnce.should.be.true();
49+
stub.firstCall.args[1].should.equal(rHex);
50+
});
51+
52+
it('succeeds when only one party provides signatureR', function () {
53+
const rHex = 'cafebabe';
54+
const msgs = [makeMsg(0, rHex), makeMsg(1), makeMsg(2)];
55+
const result = combineRound4DklsDsgMessages(msgs);
56+
result.should.have.property('R');
57+
stub.calledOnce.should.be.true();
58+
});
59+
});

0 commit comments

Comments
 (0)