Skip to content

Commit 566f93b

Browse files
Merge pull request #8466 from BitGo/worktree-WAL-396-fix-dkg-restore-session
fix(sdk-lib-mpc): re-derive dkgState from WASM bytes in restoreSession
2 parents b99cd22 + 8d03ef9 commit 566f93b

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ export class Dkg {
119119
this.dkgState = DkgState.Round3;
120120
break;
121121
case 'WaitMsg4':
122-
this.dkgState = DkgState.Round4;
122+
// keyShareBuff present means keyshare() already ran and freed the session; bytes are frozen at WaitMsg4.
123+
this.dkgState = this.keyShareBuff ? DkgState.Complete : DkgState.Round4;
123124
break;
124125
case 'Ended':
125126
this.dkgState = DkgState.Complete;
@@ -339,7 +340,6 @@ export class Dkg {
339340
}
340341

341342
dkg.dkgSessionBytes = sessionData.dkgSessionBytes;
342-
dkg.dkgState = sessionData.dkgState;
343343

344344
if (sessionData.chainCodeCommitment) {
345345
dkg.chainCodeCommitment = sessionData.chainCodeCommitment;
@@ -350,6 +350,10 @@ export class Dkg {
350350
}
351351

352352
dkg._restoreSession();
353+
// Re-derive state from WASM bytes rather than trusting the caller-supplied dkgState.
354+
// This prevents a tampered or corrupted dkgState from causing handleIncomingMessages()
355+
// to take the wrong branch (e.g. skipping chain code commitment or calling keyshare() prematurely).
356+
dkg._deserializeState();
353357
return dkg;
354358
}
355359
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,47 @@ describe('DKLS Dkg 2x3', function () {
283283
assert.deepEqual(DklsTypes.getCommonKeychain(backupKeyShare), DklsTypes.getCommonKeychain(bitgoKeyShare));
284284
});
285285

286+
it('restoreSession() should ignore tampered dkgState and re-derive from WASM bytes', async function () {
287+
const user = new DklsDkg.Dkg(3, 2, 0);
288+
289+
// After initDkg() the WASM session encodes WaitMsg1 → DkgState.Round1
290+
await user.initDkg();
291+
292+
const legitimateSessionData = user.getSessionData();
293+
294+
// Tamper: claim the session is at Round4 when WASM bytes still say Round1
295+
const tamperedSessionData = {
296+
...legitimateSessionData,
297+
dkgState: DklsTypes.DkgState.Round4,
298+
};
299+
300+
const restoredUser = await DklsDkg.Dkg.restoreSession(3, 2, 0, tamperedSessionData);
301+
302+
// Must reflect the actual WASM state (Round1), not the tampered Round4
303+
assert.strictEqual(
304+
restoredUser['dkgState'],
305+
DklsTypes.DkgState.Round1,
306+
'restoreSession() must re-derive dkgState from WASM bytes and ignore caller-supplied value'
307+
);
308+
});
309+
310+
it('restoreSession() should restore a completed DKG session as DkgState.Complete', async function () {
311+
const [user] = await generateDKGKeyShares();
312+
const completedSessionData = user.getSessionData();
313+
314+
// dkgSessionBytes holds { round: 'Ended' }; restoreSession() must decode it as Complete
315+
// without reconstructing the (already freed) WASM session
316+
const restoredUser = await DklsDkg.Dkg.restoreSession(3, 2, 0, completedSessionData);
317+
318+
assert.strictEqual(
319+
restoredUser['dkgState'],
320+
DklsTypes.DkgState.Complete,
321+
'restoreSession() must decode "Ended" round marker as DkgState.Complete'
322+
);
323+
// Key share must still be accessible on the restored instance
324+
assert.ok(restoredUser.getKeyShare(), 'Key share should be accessible after restoring completed session');
325+
});
326+
286327
it('should successfully finish DKG using restored sessions', async function () {
287328
const user = new DklsDkg.Dkg(3, 2, 0);
288329
const backup = new DklsDkg.Dkg(3, 2, 1);

0 commit comments

Comments
 (0)