From 8e65cb91333d1d8ae96349de30c8d6647779dd44 Mon Sep 17 00:00:00 2001 From: Marzooqa Naeema Kather Date: Mon, 29 Jun 2026 16:47:26 +0530 Subject: [PATCH] fix(sdk-lib-mpc): lazy-load wasm-mps to fix browser DSG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static top-level import compiled to synchronous require() in CJS, causing fs.readFileSync at module init — unavailable in browser. DSG failed with: Cannot read properties of undefined (reading '__wbindgen_add_to_stack_pointer') Mirrors the lazy-load pattern from WCI-244 (DKG fix): - Browser: await import('@bitgo/wasm-mps/web') + webWasm.default() - Node: await import('@bitgo/wasm-mps') - initDsg() and restoreSession() made async - eddsaMPCv2.ts call sites updated to await initDsg() - Tests updated accordingly WCI-866 Co-Authored-By: Claude Sonnet 4.6 --- .../tssUtils/eddsaMPCv2/signTxRequest.ts | 4 +- .../test/unit/clientRoutes/externalSign.ts | 2 +- modules/sdk-coin-sol/src/sol.ts | 2 +- .../src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 16 +- .../unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 57 ++++--- modules/sdk-lib-mpc/src/tss/eddsa-mps/dsg.ts | 64 ++++++-- modules/sdk-lib-mpc/src/tss/eddsa-mps/util.ts | 8 +- .../sdk-lib-mpc/test/unit/tss/eddsa/derive.ts | 8 +- .../sdk-lib-mpc/test/unit/tss/eddsa/dsg.ts | 144 +++++++++--------- .../test/unit/tss/eddsa/eddsa-utils.ts | 36 ++--- 10 files changed, 186 insertions(+), 155 deletions(-) diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/signTxRequest.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/signTxRequest.ts index 3b6bf33dc5..e5403c3f4d 100644 --- a/modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/signTxRequest.ts +++ b/modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/signTxRequest.ts @@ -242,7 +242,7 @@ describe('signTxRequest:', function () { it('should throw if round 2 response has wrong type', async function () { const messageBuffer = Buffer.from(signableHex, 'hex'); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg( + await bitgoDsg.initDsg( bitgoKeyShare, messageBuffer, txRequest.transactions![0].unsignedTx.derivationPath, @@ -433,7 +433,7 @@ describe('signTxRequest:', function () { : txRequest.transactions![0].unsignedTx.signableHex; const messageBuffer = Buffer.from(txOrMessageToSign, 'hex'); const bitgoSession = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoSession.initDsg( + await bitgoSession.initDsg( bitgoKeyShare, messageBuffer, txRequest.transactions?.[0].unsignedTx.derivationPath || 'm/0', diff --git a/modules/express/test/unit/clientRoutes/externalSign.ts b/modules/express/test/unit/clientRoutes/externalSign.ts index 2eb1113264..0bffdab458 100644 --- a/modules/express/test/unit/clientRoutes/externalSign.ts +++ b/modules/express/test/unit/clientRoutes/externalSign.ts @@ -1113,7 +1113,7 @@ describe('External signer', () => { // Initialise BitGo-side DSG session (party 2, co-signing with User party 0) const message = Buffer.from(signableHex, 'hex'); const bitgoDsg = new EddsaMPSDsg.DSG(2 /* BITGO */); - bitgoDsg.initDsg(bitgoKeyShareBuffer, message, derivationPath, 0 /* USER */); + await bitgoDsg.initDsg(bitgoKeyShareBuffer, message, derivationPath, 0 /* USER */); const baseTxRequest = { txRequestId: 'eddsa-mpcv2-round-trip-test', diff --git a/modules/sdk-coin-sol/src/sol.ts b/modules/sdk-coin-sol/src/sol.ts index c605bd029f..c18e3ac584 100644 --- a/modules/sdk-coin-sol/src/sol.ts +++ b/modules/sdk-coin-sol/src/sol.ts @@ -1985,7 +1985,7 @@ export class Sol extends BaseCoin { throw new Error('EdDSA MPCv2 recovery: commonKeyChain from keycard does not match bitgoKey'); } - const signature = EDDSAUtils.signRecoveryEddsaMPCv2( + const signature = await EDDSAUtils.signRecoveryEddsaMPCv2( unsignedTransaction.signablePayload, currPath, userKeyShare, diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts index 76f5a34557..d0bb886e2b 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -452,7 +452,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { const partyId = params.mpcv2PartyId ?? MPCv2PartiesEnum.USER; const signerShareType = partyId === MPCv2PartiesEnum.USER ? SignatureShareType.USER : SignatureShareType.BACKUP; const userDsg = new EddsaMPSDsg.DSG(partyId); - userDsg.initDsg(userKeyShare, bufferContent, derivationPath, MPCv2PartiesEnum.BITGO); + await userDsg.initDsg(userKeyShare, bufferContent, derivationPath, MPCv2PartiesEnum.BITGO); const userMsg1 = userDsg.getFirstMessage(); // ── API Round 1 ─────────────────────────────────────────────────────────── @@ -581,7 +581,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { const userGpgPrvKey = await pgp.readPrivateKey({ armoredKey: userGpgKey.privateKey }); const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER); - userDsg.initDsg(userKeyShare, Buffer.from(signableHex, 'hex'), derivationPath, MPCv2PartiesEnum.BITGO); + await userDsg.initDsg(userKeyShare, Buffer.from(signableHex, 'hex'), derivationPath, MPCv2PartiesEnum.BITGO); const userMsg1 = userDsg.getFirstMessage(); const signatureShareRound1 = await getSignatureShareRoundOne(userMsg1, userGpgPrvKey); const sessionPayload = JSON.stringify({ @@ -693,7 +693,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { }; const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER); - userDsg.restoreSession(dsgSession); + await userDsg.restoreSession(dsgSession); const userMsg1: MPSTypes.DeserializedMessage = { from: MPCv2PartiesEnum.USER, payload: new Uint8Array(Buffer.from(userMsgPayload, 'base64')), @@ -800,7 +800,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { }; const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER); - userDsg.restoreSession(dsgSession); + await userDsg.restoreSession(dsgSession); const userMsg2: MPSTypes.DeserializedMessage = { from: MPCv2PartiesEnum.USER, payload: new Uint8Array(Buffer.from(userMsgPayload, 'base64')), @@ -1021,17 +1021,17 @@ export async function getEddsaMpcV2RecoveryKeySharesFromReducedKey( * @param commonKeyChain 128-hex-char string: 32-byte pub + 32-byte rootChainCode * @returns 64-byte Ed25519 signature Buffer */ -export function signRecoveryEddsaMPCv2( +export async function signRecoveryEddsaMPCv2( message: Buffer, derivationPath: string, userKeyShare: Buffer, backupKeyShare: Buffer, commonKeyChain: string -): Buffer { +): Promise { const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER); const backupDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BACKUP); - const signature = MPSUtil.executeTillRound( + const signature = (await MPSUtil.executeTillRound( 3, userDsg, backupDsg, @@ -1039,7 +1039,7 @@ export function signRecoveryEddsaMPCv2( backupKeyShare, message, derivationPath - ) as Buffer; + )) as Buffer; // deriveUnhardenedMps returns 128 hex chars: first 64 are the 32-byte public key const derivedKeychain = deriveUnhardenedMps(commonKeyChain, derivationPath); diff --git a/modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts b/modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts index df2b3d17dc..b115f3b8c8 100644 --- a/modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/test/unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -73,7 +73,7 @@ describe('EdDSA MPS DSG helper functions', async () => { it('getSignatureShareRoundOne should build a valid round-1 share', async () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER); - userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); + await userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); const userMsg1 = userDsg.getFirstMessage(); const share: SignatureShareRecord = await getSignatureShareRoundOne(userMsg1, userGpgPrivKey); @@ -94,7 +94,7 @@ describe('EdDSA MPS DSG helper functions', async () => { it('getSignatureShareRoundOne should build a valid backup round-1 share', async () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const backupDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BACKUP); - backupDsg.initDsg(backupKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); + await backupDsg.initDsg(backupKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); const backupMsg1 = backupDsg.getFirstMessage(); const share: SignatureShareRecord = await getSignatureShareRoundOne( @@ -119,7 +119,7 @@ describe('EdDSA MPS DSG helper functions', async () => { it('verifyPeerMessageRoundOne should verify a valid BitGo round-1 message', async () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); const bitgoMsg1 = bitgoDsg.getFirstMessage(); const bitgoSignedMsg1 = await MPSComms.detachSignMpsMessage(Buffer.from(bitgoMsg1.payload), bitgoGpgPrivKey); @@ -153,11 +153,11 @@ describe('EdDSA MPS DSG helper functions', async () => { it('getSignatureShareRoundTwo should build a valid round-2 share', async () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER); - userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); + await userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); const userMsg1 = userDsg.getFirstMessage(); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); const bitgoMsg1 = bitgoDsg.getFirstMessage(); const bitgoSignedMsg1 = await MPSComms.detachSignMpsMessage(Buffer.from(bitgoMsg1.payload), bitgoGpgPrivKey); @@ -185,11 +185,11 @@ describe('EdDSA MPS DSG helper functions', async () => { it('getSignatureShareRoundTwo should build a valid backup round-2 share', async () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const backupDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BACKUP); - backupDsg.initDsg(backupKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); + await backupDsg.initDsg(backupKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); const backupMsg1 = backupDsg.getFirstMessage(); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BACKUP); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BACKUP); const bitgoMsg1 = bitgoDsg.getFirstMessage(); const bitgoSignedMsg1 = await MPSComms.detachSignMpsMessage(Buffer.from(bitgoMsg1.payload), bitgoGpgPrivKey); @@ -221,11 +221,11 @@ describe('EdDSA MPS DSG helper functions', async () => { it('verifyPeerMessageRoundTwo should verify a valid BitGo round-2 message', async () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER); - userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); + await userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); const userMsg1 = userDsg.getFirstMessage(); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); const bitgoMsg1 = bitgoDsg.getFirstMessage(); const [bitgoMsg2] = bitgoDsg.handleIncomingMessages([bitgoMsg1, userMsg1]); @@ -261,11 +261,11 @@ describe('EdDSA MPS DSG helper functions', async () => { it('getSignatureShareRoundThree should build a valid round-3 share', async () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const userDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.USER); - userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); + await userDsg.initDsg(userKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); const userMsg1 = userDsg.getFirstMessage(); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); const bitgoMsg1 = bitgoDsg.getFirstMessage(); // Advance to round 2 @@ -302,11 +302,11 @@ describe('EdDSA MPS DSG helper functions', async () => { it('getSignatureShareRoundThree should build a valid backup round-3 share', async () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const backupDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BACKUP); - backupDsg.initDsg(backupKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); + await backupDsg.initDsg(backupKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BITGO); const backupMsg1 = backupDsg.getFirstMessage(); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BACKUP); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.BACKUP); const bitgoMsg1 = bitgoDsg.getFirstMessage(); const bitgoSignedMsg1 = await MPSComms.detachSignMpsMessage(Buffer.from(bitgoMsg1.payload), bitgoGpgPrivKey); @@ -698,7 +698,7 @@ describe('EddsaMPCv2Utils.createOfflineRound2Share', () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); const txRequestRound1 = await signBitgoEddsaRound1( bitgoDsg, @@ -756,7 +756,7 @@ describe('EddsaMPCv2Utils.createOfflineRound2Share', () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); const txRequestRound1 = await signBitgoEddsaRound1( bitgoDsg, @@ -817,7 +817,7 @@ describe('EddsaMPCv2Utils.createOfflineRound2Share', () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); const txRequestRound1 = await signBitgoEddsaRound1( bitgoDsg, @@ -869,7 +869,7 @@ describe('EddsaMPCv2Utils.createOfflineRound2Share', () => { const messageBuffer = Buffer.from(signableHex, 'hex'); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, derivationPath, MPCv2PartiesEnum.USER); const txRequestRound1 = await signBitgoEddsaRound1( bitgoDsg, @@ -1073,7 +1073,7 @@ describe('EddsaMPCv2Utils.createOfflineRound3Share', () => { const transaction = assertSingleTransaction(txRequest); const messageBuffer = Buffer.from(transaction.unsignedTx.signableHex, 'hex'); const bitgoDsg = new EddsaMPSDsg.DSG(MPCv2PartiesEnum.BITGO); - bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, transaction.unsignedTx.derivationPath, MPCv2PartiesEnum.USER); + await bitgoDsg.initDsg(bitgoKeyShare, messageBuffer, transaction.unsignedTx.derivationPath, MPCv2PartiesEnum.USER); const txRequestRound1 = await signBitgoEddsaRound1( bitgoDsg, @@ -1782,7 +1782,7 @@ describe('signRecoveryEddsaMPCv2', () => { const message = Buffer.from('deadbeef', 'hex'); const commonKeyChain = userDkg.getCommonKeychain(); - const signature = EDDSAUtils.signRecoveryEddsaMPCv2( + const signature = await EDDSAUtils.signRecoveryEddsaMPCv2( message, derivationPath, userDkg.getKeyShare(), @@ -1803,7 +1803,7 @@ describe('signRecoveryEddsaMPCv2', () => { const message = Buffer.from('deadbeef', 'hex'); const commonKeyChain = userDkg.getCommonKeychain(); - const signature = EDDSAUtils.signRecoveryEddsaMPCv2( + const signature = await EDDSAUtils.signRecoveryEddsaMPCv2( message, derivationPath, userDkg.getKeyShare(), @@ -1827,15 +1827,14 @@ describe('signRecoveryEddsaMPCv2', () => { const [wrongDkg] = await MPSUtil.generateEdDsaDKGKeyShares(); const message = Buffer.from('deadbeef', 'hex'); - assert.throws( - () => - EDDSAUtils.signRecoveryEddsaMPCv2( - message, - derivationPath, - userDkg.getKeyShare(), - backupDkg.getKeyShare(), - wrongDkg.getCommonKeychain() // key chain from a different wallet - ), + await assert.rejects( + EDDSAUtils.signRecoveryEddsaMPCv2( + message, + derivationPath, + userDkg.getKeyShare(), + backupDkg.getKeyShare(), + wrongDkg.getCommonKeychain() // key chain from a different wallet + ), /EdDSA MPCv2 recovery signature verification failed/ ); }); diff --git a/modules/sdk-lib-mpc/src/tss/eddsa-mps/dsg.ts b/modules/sdk-lib-mpc/src/tss/eddsa-mps/dsg.ts index a59ba857b1..e8a8a2f749 100644 --- a/modules/sdk-lib-mpc/src/tss/eddsa-mps/dsg.ts +++ b/modules/sdk-lib-mpc/src/tss/eddsa-mps/dsg.ts @@ -1,12 +1,11 @@ import assert from 'assert'; -import { - ed25519_dsg_round0_process, - ed25519_dsg_round1_process, - ed25519_dsg_round2_process, - ed25519_dsg_round3_process, -} from '@bitgo/wasm-mps'; +import type { MsgState } from '@bitgo/wasm-mps'; import { DeserializedMessage, DeserializedMessages, DsgState } from './types'; +type NodeWasmer = typeof import('@bitgo/wasm-mps'); +type WebWasmer = typeof import('@bitgo/wasm-mps/web'); +type WasmMps = NodeWasmer | WebWasmer; + /** * EdDSA Distributed Sign Generation (DSG) implementation using @bitgo/wasm-mps. * @@ -22,7 +21,7 @@ import { DeserializedMessage, DeserializedMessages, DsgState } from './types'; * @example * ```typescript * const dsg = new DSG(0); // partyIdx 0 - * dsg.initDsg(keyShare, message, 'm', 2); // counterpart is party 2 + * await dsg.initDsg(keyShare, message, 'm', 2); // counterpart is party 2 * const msg1 = dsg.getFirstMessage(); * const msg2 = dsg.handleIncomingMessages([msg1, peerMsg1]); // emits SignMsg2 * const msg3 = dsg.handleIncomingMessages([msg2[0], peerMsg2]); // emits SignMsg3 @@ -45,6 +44,8 @@ export class DSG { private dsgStateBytes: Buffer | null = null; /** Final 64-byte Ed25519 signature, available after WaitMsg3 -> Complete */ private signature: Buffer | null = null; + /** Lazily loaded WASM module */ + private wasmMps: WasmMps | null = null; protected dsgState: DsgState = DsgState.Uninitialized; @@ -64,6 +65,33 @@ export class DSG { return this.otherPartyIdx; } + private async loadWasmMps(): Promise { + if (!this.wasmMps) { + if ( + typeof window !== 'undefined' && + /* checks for electron processes */ + !window.process && + !window.process?.['type'] + ) { + // Browser: web build has explicit init() — guaranteed ready after await + // eslint-disable-next-line import/no-internal-modules -- @bitgo/wasm-mps exposes environment-specific subpath exports. + const webWasm = await import('@bitgo/wasm-mps/web'); + await webWasm.default(); + this.wasmMps = webWasm; + } else { + // Node.js: dynamic import() rewritten to require() by tsc → CJS build → readFileSync + this.wasmMps = await import('@bitgo/wasm-mps'); + } + } + } + + private getWasmMps(): WasmMps { + if (!this.wasmMps) { + throw Error('WASM module not loaded'); + } + return this.wasmMps; + } + /** * Initialises the DSG session. The keyshare must come from a prior DKG run, and * `otherPartyIdx` must be the single counterpart who will co-sign with this party. @@ -74,7 +102,8 @@ export class DSG { * @param otherPartyIdx - Party index of the single counterpart in this signing session. * Must differ from this party's own `partyIdx` and be in `[0, 2]`. */ - initDsg(keyShare: Buffer, message: Buffer, derivationPath: string, otherPartyIdx: number): void { + async initDsg(keyShare: Buffer, message: Buffer, derivationPath: string, otherPartyIdx: number): Promise { + await this.loadWasmMps(); if (!keyShare || keyShare.length === 0) { throw Error('Missing or invalid keyShare'); } @@ -108,9 +137,10 @@ export class DSG { assert(this.derivationPath !== null, 'derivationPath must be set after initDsg'); assert(this.message, 'message must be set after initDsg'); - let result; + const wasm = this.getWasmMps(); + let result: MsgState; try { - result = ed25519_dsg_round0_process(this.keyShare, this.derivationPath, this.message); + result = wasm.ed25519_dsg_round0_process(this.keyShare, this.derivationPath, this.message); } catch (err) { throw new Error(`Error while creating the first message from party ${this.partyIdx}: ${err}`); } @@ -159,12 +189,13 @@ export class DSG { throw Error(`Unexpected counterpart party index: got ${peerMsg.from}, expected ${this.otherPartyIdx}`); } const peerPayload = Buffer.from(peerMsg.payload); + const wasm = this.getWasmMps(); if (this.dsgState === DsgState.WaitMsg1) { assert(this.dsgStateBytes, 'dsgStateBytes must be set in WaitMsg1'); - let result; + let result: MsgState; try { - result = ed25519_dsg_round1_process(peerPayload, this.dsgStateBytes); + result = wasm.ed25519_dsg_round1_process(peerPayload, this.dsgStateBytes); } catch (err) { throw new Error(`Error while creating messages from party ${this.partyIdx}, round ${this.dsgState}: ${err}`); } @@ -175,9 +206,9 @@ export class DSG { if (this.dsgState === DsgState.WaitMsg2) { assert(this.dsgStateBytes, 'dsgStateBytes must be set in WaitMsg2'); - let result; + let result: MsgState; try { - result = ed25519_dsg_round2_process(peerPayload, this.dsgStateBytes); + result = wasm.ed25519_dsg_round2_process(peerPayload, this.dsgStateBytes); } catch (err) { throw new Error(`Error while creating messages from party ${this.partyIdx}, round ${this.dsgState}: ${err}`); } @@ -190,7 +221,7 @@ export class DSG { assert(this.dsgStateBytes, 'dsgStateBytes must be set in WaitMsg3'); let sigBytes; try { - sigBytes = ed25519_dsg_round3_process(peerPayload, this.dsgStateBytes); + sigBytes = wasm.ed25519_dsg_round3_process(peerPayload, this.dsgStateBytes); } catch (err) { throw new Error(`Error while creating messages from party ${this.partyIdx}, round ${this.dsgState}: ${err}`); } @@ -244,7 +275,8 @@ export class DSG { * Restores a previously exported session. Allows the protocol to continue from * where it left off, as if the round state was loaded from a database. */ - restoreSession(session: string): void { + async restoreSession(session: string): Promise { + await this.loadWasmMps(); const data = JSON.parse(session); if (!Object.values(DsgState).includes(data.dsgRound)) { throw Error(`Invalid dsgRound in session: ${data.dsgRound}`); diff --git a/modules/sdk-lib-mpc/src/tss/eddsa-mps/util.ts b/modules/sdk-lib-mpc/src/tss/eddsa-mps/util.ts index d19602424e..63aaf65a53 100644 --- a/modules/sdk-lib-mpc/src/tss/eddsa-mps/util.ts +++ b/modules/sdk-lib-mpc/src/tss/eddsa-mps/util.ts @@ -89,7 +89,7 @@ export async function generateEdDsaDKGKeyShares( * @param message - Raw message bytes to sign. * @param derivationPath - BIP-32-style derivation path, e.g. `"m"` or `"m/0/0"`. */ -export function executeTillRound( +export async function executeTillRound( round: number, party1Dsg: DSG, party2Dsg: DSG, @@ -97,12 +97,12 @@ export function executeTillRound( keyShare2: Buffer, message: Buffer, derivationPath: string -): DeserializedMessages[] | Buffer { +): Promise { if (round < 1 || round > 3) { throw Error('Invalid round number'); } - party1Dsg.initDsg(keyShare1, message, derivationPath, party2Dsg.getPartyIdx()); - party2Dsg.initDsg(keyShare2, message, derivationPath, party1Dsg.getPartyIdx()); + await party1Dsg.initDsg(keyShare1, message, derivationPath, party2Dsg.getPartyIdx()); + await party2Dsg.initDsg(keyShare2, message, derivationPath, party1Dsg.getPartyIdx()); const party1Round0Message = party1Dsg.getFirstMessage(); const party2Round0Message = party2Dsg.getFirstMessage(); diff --git a/modules/sdk-lib-mpc/test/unit/tss/eddsa/derive.ts b/modules/sdk-lib-mpc/test/unit/tss/eddsa/derive.ts index c948ddb5d3..0fa8a43c13 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/eddsa/derive.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/eddsa/derive.ts @@ -68,17 +68,17 @@ describe('deriveUnhardenedMps', function () { let sigAtM0: Buffer; let sigAtM01: Buffer; - before(function () { + before(async function () { const dsgA1 = new EddsaMPSDsg.DSG(0); - MPSUtil.executeTillRound(3, dsgA1, new EddsaMPSDsg.DSG(2), userKeyShare, bitgoKeyShare, MESSAGE, 'm'); + await MPSUtil.executeTillRound(3, dsgA1, new EddsaMPSDsg.DSG(2), userKeyShare, bitgoKeyShare, MESSAGE, 'm'); sigAtRoot = dsgA1.getSignature(); const dsgA2 = new EddsaMPSDsg.DSG(0); - MPSUtil.executeTillRound(3, dsgA2, new EddsaMPSDsg.DSG(2), userKeyShare, bitgoKeyShare, MESSAGE, 'm/0'); + await MPSUtil.executeTillRound(3, dsgA2, new EddsaMPSDsg.DSG(2), userKeyShare, bitgoKeyShare, MESSAGE, 'm/0'); sigAtM0 = dsgA2.getSignature(); const dsgA3 = new EddsaMPSDsg.DSG(0); - MPSUtil.executeTillRound(3, dsgA3, new EddsaMPSDsg.DSG(2), userKeyShare, bitgoKeyShare, MESSAGE, 'm/0/1'); + await MPSUtil.executeTillRound(3, dsgA3, new EddsaMPSDsg.DSG(2), userKeyShare, bitgoKeyShare, MESSAGE, 'm/0/1'); sigAtM01 = dsgA3.getSignature(); }); diff --git a/modules/sdk-lib-mpc/test/unit/tss/eddsa/dsg.ts b/modules/sdk-lib-mpc/test/unit/tss/eddsa/dsg.ts index edf89c6a95..478fcc5328 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/eddsa/dsg.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/eddsa/dsg.ts @@ -25,9 +25,9 @@ describe('EdDSA MPS DSG', function () { }); describe('DSG Initialization', function () { - it('should accept valid inputs and produce a first message', function () { + it('should accept valid inputs and produce a first message', async function () { const dsg = new EddsaMPSDsg.DSG(0); - dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); const msg = dsg.getFirstMessage(); assert.strictEqual(msg.from, 0, 'First message should be from party 0'); @@ -44,49 +44,49 @@ describe('EdDSA MPS DSG', function () { assert.throws(() => dsg.handleIncomingMessages([]), /DSG session not initialized/); }); - it('should throw when getSignature is called before completion', function () { + it('should throw when getSignature is called before completion', async function () { const dsg = new EddsaMPSDsg.DSG(0); - dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); assert.throws(() => dsg.getSignature(), /has not produced a signature yet/); }); - it('should throw on empty keyShare', function () { + it('should throw on empty keyShare', async function () { const dsg = new EddsaMPSDsg.DSG(0); - assert.throws(() => dsg.initDsg(Buffer.alloc(0), MESSAGE, 'm', 2), /Missing or invalid keyShare/); + await assert.rejects(dsg.initDsg(Buffer.alloc(0), MESSAGE, 'm', 2), /Missing or invalid keyShare/); }); - it('should throw on empty message', function () { + it('should throw on empty message', async function () { const dsg = new EddsaMPSDsg.DSG(0); - assert.throws(() => dsg.initDsg(userKeyShare, Buffer.alloc(0), 'm', 2), /Missing or invalid message/); + await assert.rejects(dsg.initDsg(userKeyShare, Buffer.alloc(0), 'm', 2), /Missing or invalid message/); }); - it('should throw when otherPartyIdx equals own partyIdx', function () { + it('should throw when otherPartyIdx equals own partyIdx', async function () { const dsg = new EddsaMPSDsg.DSG(0); - assert.throws(() => dsg.initDsg(userKeyShare, MESSAGE, 'm', 0), /Invalid otherPartyIdx/); + await assert.rejects(dsg.initDsg(userKeyShare, MESSAGE, 'm', 0), /Invalid otherPartyIdx/); }); - it('should throw when otherPartyIdx is out of range', function () { + it('should throw when otherPartyIdx is out of range', async function () { const dsg = new EddsaMPSDsg.DSG(0); - assert.throws(() => dsg.initDsg(userKeyShare, MESSAGE, 'm', 5), /Invalid otherPartyIdx/); + await assert.rejects(dsg.initDsg(userKeyShare, MESSAGE, 'm', 5), /Invalid otherPartyIdx/); }); - it('should throw when partyIdx is out of range', function () { + it('should throw when partyIdx is out of range', async function () { const dsg = new EddsaMPSDsg.DSG(7); - assert.throws(() => dsg.initDsg(userKeyShare, MESSAGE, 'm', 0), /Invalid partyIdx/); + await assert.rejects(dsg.initDsg(userKeyShare, MESSAGE, 'm', 0), /Invalid partyIdx/); }); - it('should throw when handleIncomingMessages is called before getFirstMessage', function () { + it('should throw when handleIncomingMessages is called before getFirstMessage', async function () { const dsg = new EddsaMPSDsg.DSG(0); - dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); assert.throws(() => dsg.handleIncomingMessages([]), /must call getFirstMessage/); }); }); describe('DSG Protocol Execution (2-of-3)', function () { - it('should complete full DSG between user (0) and bitgo (2) and produce identical signatures', function () { + it('should complete full DSG between user (0) and bitgo (2) and produce identical signatures', async function () { const dsgA = new EddsaMPSDsg.DSG(0); const dsgB = new EddsaMPSDsg.DSG(2); - MPSUtil.executeTillRound(3, dsgA, dsgB, userKeyShare, bitgoKeyShare, MESSAGE, 'm'); + await MPSUtil.executeTillRound(3, dsgA, dsgB, userKeyShare, bitgoKeyShare, MESSAGE, 'm'); assert.strictEqual(dsgA.getState(), 'Complete'); assert.strictEqual(dsgB.getState(), 'Complete'); @@ -98,8 +98,8 @@ describe('EdDSA MPS DSG', function () { assert.strictEqual(sigA.toString('hex'), sigB.toString('hex'), 'Both parties must produce identical signatures'); }); - it('should produce a signature that verifies under the DKG public key', function () { - const sig = MPSUtil.executeTillRound( + it('should produce a signature that verifies under the DKG public key', async function () { + const sig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -107,14 +107,14 @@ describe('EdDSA MPS DSG', function () { bitgoKeyShare, MESSAGE, 'm' - ) as Buffer; + )) as Buffer; const isValid = ed25519.verify(sig, MESSAGE, dkgPubKey); assert(isValid, 'Signature should verify under DKG public key'); }); - it('should sign the same message identically across all 2-of-3 party combinations', function () { - const userBackupSig = MPSUtil.executeTillRound( + it('should sign the same message identically across all 2-of-3 party combinations', async function () { + const userBackupSig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(1), @@ -122,8 +122,8 @@ describe('EdDSA MPS DSG', function () { backupKeyShare, MESSAGE, 'm' - ) as Buffer; - const backupBitgoSig = MPSUtil.executeTillRound( + )) as Buffer; + const backupBitgoSig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(1), new EddsaMPSDsg.DSG(2), @@ -131,8 +131,8 @@ describe('EdDSA MPS DSG', function () { bitgoKeyShare, MESSAGE, 'm' - ) as Buffer; - const userBitgoSig = MPSUtil.executeTillRound( + )) as Buffer; + const userBitgoSig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -140,7 +140,7 @@ describe('EdDSA MPS DSG', function () { bitgoKeyShare, MESSAGE, 'm' - ) as Buffer; + )) as Buffer; // Per-session nonce randomisation means signatures across DIFFERENT signing // sessions WILL differ. The invariant we test is that every 2-of-3 subset @@ -150,11 +150,11 @@ describe('EdDSA MPS DSG', function () { assert(ed25519.verify(userBitgoSig, MESSAGE, dkgPubKey), 'user+bitgo signature should verify'); }); - it('should sign arbitrary message lengths', function () { + it('should sign arbitrary message lengths', async function () { const shortMsg = Buffer.from([0x01]); const longMsg = Buffer.alloc(4096, 0xab); - const shortSig = MPSUtil.executeTillRound( + const shortSig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -162,8 +162,8 @@ describe('EdDSA MPS DSG', function () { bitgoKeyShare, shortMsg, 'm' - ) as Buffer; - const longSig = MPSUtil.executeTillRound( + )) as Buffer; + const longSig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -171,23 +171,23 @@ describe('EdDSA MPS DSG', function () { bitgoKeyShare, longMsg, 'm' - ) as Buffer; + )) as Buffer; assert(ed25519.verify(shortSig, shortMsg, dkgPubKey), '1-byte message signature should verify'); assert(ed25519.verify(longSig, longMsg, dkgPubKey), '4096-byte message signature should verify'); }); - it('should throw when handleIncomingMessages is called after completion', function () { + it('should throw when handleIncomingMessages is called after completion', async function () { const dsgA = new EddsaMPSDsg.DSG(0); - MPSUtil.executeTillRound(3, dsgA, new EddsaMPSDsg.DSG(2), userKeyShare, bitgoKeyShare, MESSAGE, 'm'); + await MPSUtil.executeTillRound(3, dsgA, new EddsaMPSDsg.DSG(2), userKeyShare, bitgoKeyShare, MESSAGE, 'm'); assert.throws(() => dsgA.handleIncomingMessages([]), /already completed/); }); - it('should fail when parties sign different messages', function () { + it('should fail when parties sign different messages', async function () { const dsg1 = new EddsaMPSDsg.DSG(0); const dsg2 = new EddsaMPSDsg.DSG(2); - dsg1.initDsg(userKeyShare, Buffer.from('MESSAGE'), 'm', 2); - dsg2.initDsg(bitgoKeyShare, Buffer.from('DIFFERENT_MESSAGE'), 'm', 0); + await dsg1.initDsg(userKeyShare, Buffer.from('MESSAGE'), 'm', 2); + await dsg2.initDsg(bitgoKeyShare, Buffer.from('DIFFERENT_MESSAGE'), 'm', 0); const r0_1 = dsg1.getFirstMessage(); const r0_2 = dsg2.getFirstMessage(); @@ -203,8 +203,8 @@ describe('EdDSA MPS DSG', function () { }); describe('Derivation Paths', function () { - it('should produce different signatures for different derivation paths', function () { - const rootSig = MPSUtil.executeTillRound( + it('should produce different signatures for different derivation paths', async function () { + const rootSig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -212,8 +212,8 @@ describe('EdDSA MPS DSG', function () { bitgoKeyShare, MESSAGE, 'm' - ) as Buffer; - const derivedSig = MPSUtil.executeTillRound( + )) as Buffer; + const derivedSig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -221,7 +221,7 @@ describe('EdDSA MPS DSG', function () { bitgoKeyShare, MESSAGE, 'm/0/1' - ) as Buffer; + )) as Buffer; assert.notStrictEqual( rootSig.toString('hex'), @@ -232,18 +232,18 @@ describe('EdDSA MPS DSG', function () { }); describe('Error Handling', function () { - it('should throw when handleIncomingMessages receives the wrong number of messages', function () { + it('should throw when handleIncomingMessages receives the wrong number of messages', async function () { const dsg = new EddsaMPSDsg.DSG(0); - dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); const own = dsg.getFirstMessage(); assert.throws(() => dsg.handleIncomingMessages([own]), /Expected 2 messages/); assert.throws(() => dsg.handleIncomingMessages([own, own, own]), /Expected 2 messages/); }); - it('should throw when counterpart message comes from an unexpected party', function () { + it('should throw when counterpart message comes from an unexpected party', async function () { const dsg = new EddsaMPSDsg.DSG(0); - dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); const own = dsg.getFirstMessage(); // Forge a "counterpart" message from party 1 instead of expected party 2 @@ -252,9 +252,9 @@ describe('EdDSA MPS DSG', function () { assert.throws(() => dsg.handleIncomingMessages([own, wrongPeer]), /Unexpected counterpart party index/); }); - it('should throw when both messages claim to come from this party', function () { + it('should throw when both messages claim to come from this party', async function () { const dsg = new EddsaMPSDsg.DSG(0); - dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); const own = dsg.getFirstMessage(); assert.throws(() => dsg.handleIncomingMessages([own, own]), /Expected exactly 1 counterpart message/); @@ -262,11 +262,11 @@ describe('EdDSA MPS DSG', function () { }); describe('Message Serialization', function () { - it('should serialize and deserialize DSG messages round-trip', function () { + it('should serialize and deserialize DSG messages round-trip', async function () { const dsgA = new EddsaMPSDsg.DSG(0); const dsgB = new EddsaMPSDsg.DSG(2); - dsgA.initDsg(userKeyShare, MESSAGE, 'm', 2); - dsgB.initDsg(bitgoKeyShare, MESSAGE, 'm', 0); + await dsgA.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsgB.initDsg(bitgoKeyShare, MESSAGE, 'm', 0); const a0 = dsgA.getFirstMessage(); const b0 = dsgB.getFirstMessage(); @@ -288,11 +288,11 @@ describe('EdDSA MPS DSG', function () { }); describe('Session Management', function () { - it('should export and restore DSG session and continue protocol to a valid signature', function () { + it('should export and restore DSG session and continue protocol to a valid signature', async function () { const dsgA = new EddsaMPSDsg.DSG(0); const dsgB = new EddsaMPSDsg.DSG(2); - dsgA.initDsg(userKeyShare, MESSAGE, 'm', 2); - dsgB.initDsg(bitgoKeyShare, MESSAGE, 'm', 0); + await dsgA.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsgB.initDsg(bitgoKeyShare, MESSAGE, 'm', 0); const a0 = dsgA.getFirstMessage(); const b0 = dsgB.getFirstMessage(); @@ -302,7 +302,7 @@ describe('EdDSA MPS DSG', function () { // Restore A in a fresh instance and finish the protocol from there. const restoredA = new EddsaMPSDsg.DSG(0); - restoredA.restoreSession(sessionA); + await restoredA.restoreSession(sessionA); assert.strictEqual(restoredA.getState(), dsgA.getState(), 'Restored state should match original'); const [a1] = restoredA.handleIncomingMessages([a0, b0]); @@ -321,17 +321,17 @@ describe('EdDSA MPS DSG', function () { assert(ed25519.verify(sigA, MESSAGE, dkgPubKey), 'Restored-session signature should verify under DKG pubkey'); }); - it('should throw when exporting session after completion', function () { + it('should throw when exporting session after completion', async function () { const dsgA = new EddsaMPSDsg.DSG(0); const dsgB = new EddsaMPSDsg.DSG(2); - MPSUtil.executeTillRound(3, dsgA, dsgB, userKeyShare, bitgoKeyShare, MESSAGE, 'm'); + await MPSUtil.executeTillRound(3, dsgA, dsgB, userKeyShare, bitgoKeyShare, MESSAGE, 'm'); assert.throws(() => dsgA.getSession(), /DSG session is complete\. Exporting the session is not allowed\./); assert.throws(() => dsgB.getSession(), /DSG session is complete\. Exporting the session is not allowed\./); }); - it('should throw when exporting session before the first message', function () { + it('should throw when exporting session before the first message', async function () { const dsg = new EddsaMPSDsg.DSG(0); - dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); assert.throws(() => dsg.getSession(), /must produce its first message before exporting/); }); @@ -340,31 +340,31 @@ describe('EdDSA MPS DSG', function () { assert.throws(() => dsg.getSession(), /DSG session not initialized/); }); - it('should throw when restoring a session with invalid fields', function () { + it('should throw when restoring a session with invalid fields', async function () { const dsg = new EddsaMPSDsg.DSG(0); - dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); + await dsg.initDsg(userKeyShare, MESSAGE, 'm', 2); dsg.getFirstMessage(); const session = JSON.parse(dsg.getSession()); - assert.throws( - () => new EddsaMPSDsg.DSG(0).restoreSession(JSON.stringify({ ...session, dsgRound: 'Invalid' })), + await assert.rejects( + new EddsaMPSDsg.DSG(0).restoreSession(JSON.stringify({ ...session, dsgRound: 'Invalid' })), /Invalid dsgRound in session/ ); - assert.throws( - () => new EddsaMPSDsg.DSG(0).restoreSession(JSON.stringify({ ...session, partyIdx: 4 })), + await assert.rejects( + new EddsaMPSDsg.DSG(0).restoreSession(JSON.stringify({ ...session, partyIdx: 4 })), /Invalid partyIdx in session/ ); - assert.throws( - () => new EddsaMPSDsg.DSG(0).restoreSession(JSON.stringify({ ...session, otherPartyIdx: 0 })), + await assert.rejects( + new EddsaMPSDsg.DSG(0).restoreSession(JSON.stringify({ ...session, otherPartyIdx: 0 })), /Invalid otherPartyIdx in session/ ); - assert.throws( - () => new EddsaMPSDsg.DSG(0).restoreSession(JSON.stringify({ ...session, dsgStateBytes: null })), + await assert.rejects( + new EddsaMPSDsg.DSG(0).restoreSession(JSON.stringify({ ...session, dsgStateBytes: null })), /requires dsgStateBytes/ ); - assert.throws( - () => new EddsaMPSDsg.DSG(1).restoreSession(JSON.stringify(session)), + await assert.rejects( + new EddsaMPSDsg.DSG(1).restoreSession(JSON.stringify(session)), /Session partyIdx 0 does not match instance 1/ ); }); diff --git a/modules/sdk-lib-mpc/test/unit/tss/eddsa/eddsa-utils.ts b/modules/sdk-lib-mpc/test/unit/tss/eddsa/eddsa-utils.ts index 1102468c8f..8ea94efb90 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/eddsa/eddsa-utils.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/eddsa/eddsa-utils.ts @@ -76,8 +76,8 @@ describe('EdDSA Utility Functions', function () { ]; PARTY_PAIRS.forEach(([p1, p2]) => { - it(`should produce a valid signature verifying under the DKG public key for parties ${p1}+${p2}`, function () { - const sig = MPSUtil.executeTillRound( + it(`should produce a valid signature verifying under the DKG public key for parties ${p1}+${p2}`, async function () { + const sig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(p1), new EddsaMPSDsg.DSG(p2), @@ -85,14 +85,14 @@ describe('EdDSA Utility Functions', function () { keySharesByIdx[p2], MESSAGE, 'm' - ) as Buffer; + )) as Buffer; assert.strictEqual(sig.length, 64); assert(ed25519.verify(sig, MESSAGE, dkgPubKey)); }); }); - it('should verify round-3 signature against root public key from getCommonKeychain()', function () { - const sig = MPSUtil.executeTillRound( + it('should verify round-3 signature against root public key from getCommonKeychain()', async function () { + const sig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -100,13 +100,13 @@ describe('EdDSA Utility Functions', function () { keySharesByIdx[2], MESSAGE, 'm' - ) as Buffer; + )) as Buffer; const rootPubKey = Buffer.from(userDkg.getCommonKeychain().slice(0, 64), 'hex'); assert(ed25519.verify(sig, MESSAGE, rootPubKey), 'should verify under root public key from getCommonKeychain()'); }); - it('should not verify under the root public key when signing at a derived path (m/0/0)', function () { - const sig = MPSUtil.executeTillRound( + it('should not verify under the root public key when signing at a derived path (m/0/0)', async function () { + const sig = (await MPSUtil.executeTillRound( 3, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -114,7 +114,7 @@ describe('EdDSA Utility Functions', function () { keySharesByIdx[2], MESSAGE, 'm/0/0' - ) as Buffer; + )) as Buffer; assert.strictEqual(sig.length, 64, 'Derived path signature must be 64 bytes'); const rootPubKey = Buffer.from(userDkg.getCommonKeychain().slice(0, 64), 'hex'); assert( @@ -123,8 +123,8 @@ describe('EdDSA Utility Functions', function () { ); }); - it('should return message arrays (not a Buffer) for intermediate round 1', function () { - const result = MPSUtil.executeTillRound( + it('should return message arrays (not a Buffer) for intermediate round 1', async function () { + const result = await MPSUtil.executeTillRound( 1, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -137,8 +137,8 @@ describe('EdDSA Utility Functions', function () { assert.strictEqual(result.length, 2, 'should contain message arrays for both parties'); }); - it('should return message arrays (not a Buffer) for intermediate round 2', function () { - const result = MPSUtil.executeTillRound( + it('should return message arrays (not a Buffer) for intermediate round 2', async function () { + const result = await MPSUtil.executeTillRound( 2, new EddsaMPSDsg.DSG(0), new EddsaMPSDsg.DSG(2), @@ -151,15 +151,15 @@ describe('EdDSA Utility Functions', function () { assert.strictEqual(result.length, 2, 'should contain message arrays for both parties'); }); - it('should throw for round out of range', function () { + it('should throw for round out of range', async function () { const dsg1 = new EddsaMPSDsg.DSG(0); const dsg2 = new EddsaMPSDsg.DSG(2); - assert.throws( - () => MPSUtil.executeTillRound(0, dsg1, dsg2, keySharesByIdx[0], keySharesByIdx[2], MESSAGE, 'm'), + await assert.rejects( + MPSUtil.executeTillRound(0, dsg1, dsg2, keySharesByIdx[0], keySharesByIdx[2], MESSAGE, 'm'), /Invalid round number/ ); - assert.throws( - () => MPSUtil.executeTillRound(4, dsg1, dsg2, keySharesByIdx[0], keySharesByIdx[2], MESSAGE, 'm'), + await assert.rejects( + MPSUtil.executeTillRound(4, dsg1, dsg2, keySharesByIdx[0], keySharesByIdx[2], MESSAGE, 'm'), /Invalid round number/ ); });