Skip to content

Commit a2af08d

Browse files
committed
feat(sdk-core): add verifyPeerMessageRoundThree to EddsaDSGMethods
Add verifyPeerMessageRoundThree to modules/sdk-core/src/bitgo/tss/eddsa/eddsaMPCv2.ts, mirroring the call signature of verifyPeerMessageRoundOne and verifyPeerMessageRoundTwo. It decodes and GPG-verifies the MPS DSG round-3 message via MPSComms.verifyMpsMessage. Round 3 previously had no such helper, forcing callers to inline MPSComms.verifyMpsMessage directly instead of using the symmetric round1/round2 helpers already exposed by EddsaDSGMethods. Ticket: WCI-1191 Session-Id: 0716649e-75cb-46dd-8d6a-ae0a701167d6 Task-Id: 3a82317d-853d-4878-97a8-50cf6fa938c5
1 parent 3086eaa commit a2af08d

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

modules/sdk-core/src/bitgo/tss/eddsa/eddsaMPCv2.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
EddsaMPCv2SignatureShareRound2Input,
77
EddsaMPCv2SignatureShareRound2Output,
88
EddsaMPCv2SignatureShareRound3Input,
9+
EddsaMPCv2SignedMessage,
910
} from '@bitgo/public-types';
1011
import { SignatureShareRecord, SignatureShareType } from '../../utils/tss/baseTypes';
1112
import { MPCv2PartiesEnum } from '../../utils/tss/ecdsa/typesMPCv2';
@@ -100,6 +101,19 @@ export async function verifyPeerMessageRoundTwo(
100101
};
101102
}
102103

104+
/**
105+
* Verifies the peer's round-3 PGP signature and returns the raw deserialized
106+
* message ready for `DSG.handleIncomingMessages`.
107+
*/
108+
export async function verifyPeerMessageRoundThree(
109+
parsedRound3Output: { data: { msg3: EddsaMPCv2SignedMessage } },
110+
peerGpgKey: openpgp.Key,
111+
peerPartyId: MPCv2PartiesEnum = MPCv2PartiesEnum.BITGO
112+
): Promise<MPSTypes.DeserializedMessage> {
113+
const rawBytes = await MPSComms.verifyMpsMessage(parsedRound3Output.data.msg3, peerGpgKey);
114+
return { from: peerPartyId, payload: new Uint8Array(rawBytes) };
115+
}
116+
103117
/**
104118
* Builds the round-3 signature share record (final signer message).
105119
*

modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
getSignatureShareRoundThree,
3535
verifyPeerMessageRoundOne,
3636
verifyPeerMessageRoundTwo,
37+
verifyPeerMessageRoundThree,
3738
} from '../../../../../../src/bitgo/tss/eddsa/eddsaMPCv2';
3839
import { getInitializedMpcInstance } from '../../../../../../src/bitgo/tss/eddsa/eddsa';
3940
import { getBitgoSignatureShare } from '../../../../../../src/bitgo/tss/common';
@@ -344,6 +345,54 @@ describe('EdDSA MPS DSG helper functions', async () => {
344345
assert.ok(parsed.data.msg3.message, 'msg3.message should be set');
345346
assert.ok(parsed.data.msg3.signature, 'msg3.signature should be set');
346347
});
348+
349+
it('verifyPeerMessageRoundThree should verify a valid BitGo round-3 message', async () => {
350+
const messageBuffer = Buffer.from(signableHex, 'hex');
351+
const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER);
352+
await userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO);
353+
const userMsg1 = userDsg.getFirstMessage();
354+
355+
const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO);
356+
await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER);
357+
const bitgoMsg1 = bitgoDsg.getFirstMessage();
358+
359+
const [bitgoMsg2] = bitgoDsg.handleIncomingMessages([bitgoMsg1, userMsg1]);
360+
const bitgoSignedMsg1 = await MPSComms.detachSignMpsMessage(Buffer.from(bitgoMsg1.payload), bitgoGpgPrivKey);
361+
const bitgoDeserializedMsg1 = await verifyPeerMessageRoundOne(
362+
{ type: 'round1Output', data: { msg1: bitgoSignedMsg1 } },
363+
bitgoGpgPubKey
364+
);
365+
const [userMsg2] = userDsg.handleIncomingMessages([userMsg1, bitgoDeserializedMsg1]);
366+
const [bitgoMsg3] = bitgoDsg.handleIncomingMessages([bitgoMsg2, userMsg2]);
367+
const bitgoSignedMsg3 = await MPSComms.detachSignMpsMessage(Buffer.from(bitgoMsg3.payload), bitgoGpgPrivKey);
368+
369+
const round3Output = {
370+
type: 'round3Output' as const,
371+
data: { msg3: bitgoSignedMsg3 },
372+
};
373+
374+
const result = await verifyPeerMessageRoundThree(round3Output, bitgoGpgPubKey);
375+
376+
assert.strictEqual(result.from, MPCv2PartiesEnum.BITGO);
377+
assert.ok(result.payload.length > 0, 'payload should be non-empty');
378+
});
379+
380+
it('verifyPeerMessageRoundThree should throw on a tampered message', async () => {
381+
const round3Output = {
382+
type: 'round3Output' as const,
383+
data: {
384+
msg3: {
385+
message: Buffer.from('tampered').toString('base64'),
386+
signature: '-----BEGIN PGP SIGNATURE-----\n\nINVALID\n-----END PGP SIGNATURE-----\n',
387+
},
388+
},
389+
};
390+
391+
await assert.rejects(
392+
verifyPeerMessageRoundThree(round3Output, bitgoGpgPubKey),
393+
'should throw on invalid signature'
394+
);
395+
});
347396
});
348397

349398
describe('getEddsaMPCv2RecoveryKeyShares', () => {

0 commit comments

Comments
 (0)