From dc4e509c6c4e97be254b2ce39036abf813d5580a Mon Sep 17 00:00:00 2001 From: Support Bot Date: Tue, 14 Jul 2026 06:47:01 +0000 Subject: [PATCH 1/3] feat(sdk-core): add createOfflineKeyGenRound2Share to EddsaMPCv2Utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add createOfflineKeyGenRound2Share to EddsaMPCv2Utils in sdk-core, enabling external signer AKM flows to complete DKG round 2 without holding decrypted key material in memory across API calls. The method: - Decrypts encryptedUserGpgPrvKey and encryptedRound1Session with the wallet passphrase - Restores the DKG session via dkg.restoreSession - Verifies and deserializes all 3 incoming round-1 messages (own, counterParty, BitGo) via MPSComms.verifyMpsMessage - Calls dkg.handleIncomingMessages([ownMsg1, counterPartyMsg1, bitgoMsg1]) to produce ownMsg2 (WASM filters by partyIdx internally) - Signs ownMsg2 payload with the GPG private key via MPSComms.detachSignMpsMessage - Serializes {dkgSession, ownMsgPayload, ownMsgFrom} and encrypts it as encryptedRound2Session Also fix DKG.restoreSession in sdk-lib-mpc to be async and load WASM before restoring state (matching DSG.restoreSession behavior), which is required for handleIncomingMessages to work after a restore. Also fix createOfflineKeyGenRound1Share (WCI-889) to use this.bitgo.encrypt/decrypt instead of non-existent encryptAsync/ decryptAsync — BitGoBase.encrypt/decrypt already return Promise. Ticket: WCI-890 Session-Id: 2b6d8843-9bec-4a05-8725-18d450b7f097 Task-Id: dcea3d67-b592-483a-967b-968cc01f5b76 --- .../src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 85 ++++++ .../unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 248 +++++++++++++++++- modules/sdk-lib-mpc/src/tss/eddsa-mps/dkg.ts | 3 +- 3 files changed, 331 insertions(+), 5 deletions(-) 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 566bfa4a2d..d0f80613e3 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -55,6 +55,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { private static readonly MPS_DSG_SIGNING_ROUND1_STATE = 'MPS_DSG_SIGNING_ROUND1_STATE'; private static readonly MPS_DSG_SIGNING_ROUND2_STATE = 'MPS_DSG_SIGNING_ROUND2_STATE'; private static readonly MPS_DKG_KEYGEN_ROUND1_STATE = 'MPS_DKG_KEYGEN_ROUND1_STATE'; + private static readonly MPS_DKG_KEYGEN_ROUND2_STATE = 'MPS_DKG_KEYGEN_ROUND2_STATE'; /** @inheritdoc */ async createKeychains(params: { @@ -701,6 +702,90 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { } // #endregion + // #region KeyGenRound2Share + async createOfflineKeyGenRound2Share(params: { + walletPassphrase: string; + encryptedUserGpgPrvKey: string; + encryptedRound1Session: string; + bitgoGpgPubKey: string; + counterPartyGpgPubKey: string; + bitgoMsg1: MPSTypes.MPSSignedMessage; + counterPartyMsg1: MPSTypes.MPSSignedMessage; + }): Promise<{ + signedMsg2: MPSTypes.MPSSignedMessage; + encryptedRound2Session: string; + }> { + const { + walletPassphrase, + encryptedUserGpgPrvKey, + encryptedRound1Session, + bitgoGpgPubKey, + counterPartyGpgPubKey, + bitgoMsg1, + counterPartyMsg1, + } = params; + + const decryptedGpgPrvKey = await this.bitgo.decrypt({ input: encryptedUserGpgPrvKey, password: walletPassphrase }); + const gpgPrvKey = await pgp.readPrivateKey({ armoredKey: decryptedGpgPrvKey }); + + if (envRequiresBitgoPubGpgKeyConfig(this.bitgo.getEnv())) { + assert(isBitgoEddsaMpcv2PubKey(bitgoGpgPubKey), 'Invalid BitGo GPG public key'); + } + + const bitgoKeyObj = await pgp.readKey({ armoredKey: bitgoGpgPubKey }); + const counterPartyKeyObj = await pgp.readKey({ armoredKey: counterPartyGpgPubKey }); + + const decryptedRound1Session = await this.bitgo.decrypt({ input: encryptedRound1Session, password: walletPassphrase }); + + const { dkgSession, ownMsgPayload, ownMsgFrom } = JSON.parse(decryptedRound1Session) as { + dkgSession: string; + ownMsgPayload: string; + ownMsgFrom: number; + }; + + const dkg = new EddsaMPSDkg.DKG(3, 2, ownMsgFrom); + await dkg.restoreSession(dkgSession); + + const bitgoRawMsg1Bytes = await MPSComms.verifyMpsMessage(bitgoMsg1, bitgoKeyObj); + const bitgoDeserializedMsg1: MPSTypes.DeserializedMessage = { + from: MPCv2PartiesEnum.BITGO, + payload: new Uint8Array(bitgoRawMsg1Bytes), + }; + + const counterPartyRawMsg1Bytes = await MPSComms.verifyMpsMessage(counterPartyMsg1, counterPartyKeyObj); + const counterPartyDeserializedMsg1: MPSTypes.DeserializedMessage = { + from: ownMsgFrom === MPCv2PartiesEnum.USER ? MPCv2PartiesEnum.BACKUP : MPCv2PartiesEnum.USER, + payload: new Uint8Array(counterPartyRawMsg1Bytes), + }; + + const ownMsg1: MPSTypes.DeserializedMessage = { + from: ownMsgFrom, + payload: new Uint8Array(Buffer.from(ownMsgPayload, 'base64')), + }; + + const round2Msgs = dkg.handleIncomingMessages([ownMsg1, counterPartyDeserializedMsg1, bitgoDeserializedMsg1]); + assert(round2Msgs.length === 1, 'DKG round 1 should produce exactly one round 2 message'); + + const ownMsg2 = round2Msgs[0]; + const signedMsg2 = await MPSComms.detachSignMpsMessage(Buffer.from(ownMsg2.payload), gpgPrvKey); + + const sessionPayload = JSON.stringify({ + dkgSession: dkg.getSession(), + ownMsgPayload: Buffer.from(ownMsg2.payload).toString('base64'), + ownMsgFrom: ownMsg2.from, + }); + + const encryptedRound2Session = await this.bitgo.encrypt({ + input: sessionPayload, + password: walletPassphrase, + adata: `${EddsaMPCv2Utils.MPS_DKG_KEYGEN_ROUND2_STATE}:${ownMsgFrom}`, + encryptionVersion: 1, + }); + + return { signedMsg2, encryptedRound2Session }; + } + // #endregion + // #region Round1Share async createOfflineRound1Share(params: { txRequest: TxRequest; 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 664eab8e09..80597f6a9c 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 @@ -639,10 +639,8 @@ describe('EddsaMPCv2Utils.createOfflineKeyGenRound1Share', () => { }); }; mockBitgo = { - encrypt: sinon.stub().callsFake(sjclEncrypt), - encryptAsync: sinon.stub().callsFake(async (params) => sjclEncrypt(params)), - decrypt: sinon.stub().callsFake((params) => sjcl.decrypt(params.password, params.input)), - decryptAsync: sinon.stub().callsFake(async (params) => sjcl.decrypt(params.password, params.input)), + encrypt: sinon.stub().callsFake(async (params) => sjclEncrypt(params)), + decrypt: sinon.stub().callsFake(async (params) => sjcl.decrypt(params.password, params.input)), getEnv: sinon.stub().returns('mock'), } as unknown as BitGoBase; @@ -777,6 +775,248 @@ describe('EddsaMPCv2Utils.createOfflineKeyGenRound1Share', () => { }); }); +describe('EddsaMPCv2Utils.createOfflineKeyGenRound2Share', () => { + let eddsaMPCv2Utils: EddsaMPCv2Utils; + let mockBitgo: BitGoBase; + let userGpgKeyPair: pgp.SerializedKeyPair; + let backupGpgKeyPair: pgp.SerializedKeyPair; + let bitgoGpgKeyPair: pgp.SerializedKeyPair; + + const walletPassphrase = 'testPass'; + + before('generate GPG key pairs', async () => { + userGpgKeyPair = await generateGPGKeyPair('ed25519'); + backupGpgKeyPair = await generateGPGKeyPair('ed25519'); + bitgoGpgKeyPair = await generateGPGKeyPair('ed25519'); + }); + + function makeMockBitgo(): BitGoBase { + const sjclEncrypt = (params: { password: string; input: string; adata?: string }) => { + const salt = randomBytes(8); + const iv = randomBytes(16); + return sjcl.encrypt(params.password, params.input, { + salt: [bytesToWord(salt.subarray(0, 4)), bytesToWord(salt.subarray(4))], + iv: [ + bytesToWord(iv.subarray(0, 4)), + bytesToWord(iv.subarray(4, 8)), + bytesToWord(iv.subarray(8, 12)), + bytesToWord(iv.subarray(12, 16)), + ], + adata: params.adata, + }); + }; + return { + encrypt: sinon.stub().callsFake(async (params) => sjclEncrypt(params)), + decrypt: sinon.stub().callsFake(async (params) => sjcl.decrypt(params.password, params.input)), + getEnv: sinon.stub().returns('mock'), + } as unknown as BitGoBase; + } + + beforeEach(() => { + mockBitgo = makeMockBitgo(); + const mockCoin = { getMPCAlgorithm: sinon.stub().returns('eddsa') } as unknown as IBaseCoin; + eddsaMPCv2Utils = new EddsaMPCv2Utils(mockBitgo, mockCoin); + }); + + async function runRound1( + partyId: MPCv2PartiesEnum.USER | MPCv2PartiesEnum.BACKUP = MPCv2PartiesEnum.USER + ): Promise<{ + result: { signedMsg1: MPSTypes.MPSSignedMessage; encryptedRound1Session: string }; + ownGpgKeyPair: pgp.SerializedKeyPair; + counterPartyGpgKeyPair: pgp.SerializedKeyPair; + }> { + const ownGpgKeyPair = partyId === MPCv2PartiesEnum.USER ? userGpgKeyPair : backupGpgKeyPair; + const counterPartyGpgKeyPair = partyId === MPCv2PartiesEnum.USER ? backupGpgKeyPair : userGpgKeyPair; + const encryptedUserGpgPrvKey = sjcl.encrypt(walletPassphrase, ownGpgKeyPair.privateKey); + const result = await eddsaMPCv2Utils.createOfflineKeyGenRound1Share({ + walletPassphrase, + encryptedUserGpgPrvKey, + bitgoGpgPubKey: bitgoGpgKeyPair.publicKey, + counterPartyGpgPubKey: counterPartyGpgKeyPair.publicKey, + partyId, + }); + return { result, ownGpgKeyPair, counterPartyGpgKeyPair }; + } + + async function buildCounterPartyAndBitgoMsgs( + userRound1Result: MPSTypes.MPSSignedMessage, + backupRound1Result: MPSTypes.MPSSignedMessage + ): Promise<{ bitgoMsg1: MPSTypes.MPSSignedMessage; counterPartyMsg1ForUser: MPSTypes.MPSSignedMessage }> { + const bitgoGpgPrivKey = await pgp.readPrivateKey({ armoredKey: bitgoGpgKeyPair.privateKey }); + + // Build a real user DKG round1 to get the bitgo msg + const userGpgPrivKey = await pgp.readPrivateKey({ armoredKey: userGpgKeyPair.privateKey }); + const [, ownUserSk] = await MPSComms.extractEd25519KeyPair(userGpgPrivKey); + + const backupGpgPrivKey = await pgp.readPrivateKey({ armoredKey: backupGpgKeyPair.privateKey }); + const [, ownBackupSk] = await MPSComms.extractEd25519KeyPair(backupGpgPrivKey); + + const bitgoGpgPubKeyObj = await pgp.readKey({ armoredKey: bitgoGpgKeyPair.publicKey }); + const bitgoPk = await MPSComms.extractEd25519PublicKey(bitgoGpgPubKeyObj); + + const userGpgPubKeyObj = await pgp.readKey({ armoredKey: userGpgKeyPair.publicKey }); + const userPk = await MPSComms.extractEd25519PublicKey(userGpgPubKeyObj); + + const backupGpgPubKeyObj = await pgp.readKey({ armoredKey: backupGpgKeyPair.publicKey }); + const backupPk = await MPSComms.extractEd25519PublicKey(backupGpgPubKeyObj); + + const bitgoDkg = new EddsaMPSDkg.DKG(3, 2, MPCv2PartiesEnum.BITGO); + await bitgoDkg.initDkg( + (await MPSComms.extractEd25519KeyPair(bitgoGpgPrivKey))[1], + [userPk, backupPk] + ); + const bitgoRawMsg1 = bitgoDkg.getFirstMessage(); + const bitgoMsg1 = await MPSComms.detachSignMpsMessage(Buffer.from(bitgoRawMsg1.payload), bitgoGpgPrivKey); + + return { bitgoMsg1, counterPartyMsg1ForUser: backupRound1Result }; + } + + it('Round1 → Round2 produces valid { signedMsg2, encryptedRound2Session }', async () => { + // Run round 1 for all three parties + const { result: userRound1 } = await runRound1(MPCv2PartiesEnum.USER); + const { result: backupRound1 } = await runRound1(MPCv2PartiesEnum.BACKUP); + + const encryptedUserGpgPrvKey = sjcl.encrypt(walletPassphrase, userGpgKeyPair.privateKey); + const { bitgoMsg1, counterPartyMsg1ForUser } = await buildCounterPartyAndBitgoMsgs( + userRound1.signedMsg1, + backupRound1.signedMsg1 + ); + + const result = await eddsaMPCv2Utils.createOfflineKeyGenRound2Share({ + walletPassphrase, + encryptedUserGpgPrvKey, + encryptedRound1Session: userRound1.encryptedRound1Session, + bitgoGpgPubKey: bitgoGpgKeyPair.publicKey, + counterPartyGpgPubKey: backupGpgKeyPair.publicKey, + bitgoMsg1, + counterPartyMsg1: counterPartyMsg1ForUser, + }); + + assert.ok(result.signedMsg2.message, 'signedMsg2.message should be set'); + assert.ok( + result.signedMsg2.signature.includes('BEGIN PGP SIGNATURE'), + 'signedMsg2.signature should be PGP armored' + ); + assert.ok(JSON.parse(result.encryptedRound2Session).ct, 'encryptedRound2Session should be an SJCL JSON blob'); + + const sessionPayload = JSON.parse(sjcl.decrypt(walletPassphrase, result.encryptedRound2Session)); + assert.ok(sessionPayload.dkgSession, 'dkgSession should be persisted for finalize'); + assert.ok(sessionPayload.ownMsgPayload, 'ownMsgPayload should be persisted for finalize'); + assert.strictEqual(typeof sessionPayload.ownMsgFrom, 'number', 'ownMsgFrom should be a number'); + }); + + it('encryptedRound2Session should only be decryptable with correct walletPassphrase', async () => { + const { result: userRound1 } = await runRound1(MPCv2PartiesEnum.USER); + const { result: backupRound1 } = await runRound1(MPCv2PartiesEnum.BACKUP); + const encryptedUserGpgPrvKey = sjcl.encrypt(walletPassphrase, userGpgKeyPair.privateKey); + const { bitgoMsg1, counterPartyMsg1ForUser } = await buildCounterPartyAndBitgoMsgs( + userRound1.signedMsg1, + backupRound1.signedMsg1 + ); + + const result = await eddsaMPCv2Utils.createOfflineKeyGenRound2Share({ + walletPassphrase, + encryptedUserGpgPrvKey, + encryptedRound1Session: userRound1.encryptedRound1Session, + bitgoGpgPubKey: bitgoGpgKeyPair.publicKey, + counterPartyGpgPubKey: backupGpgKeyPair.publicKey, + bitgoMsg1, + counterPartyMsg1: counterPartyMsg1ForUser, + }); + + assert.throws( + () => sjcl.decrypt('wrongpassphrase', result.encryptedRound2Session), + 'should fail to decrypt with wrong passphrase' + ); + }); + + it('should reject tampered incoming messages (signature verification failure)', async () => { + const { result: userRound1 } = await runRound1(MPCv2PartiesEnum.USER); + const { result: backupRound1 } = await runRound1(MPCv2PartiesEnum.BACKUP); + const encryptedUserGpgPrvKey = sjcl.encrypt(walletPassphrase, userGpgKeyPair.privateKey); + const { bitgoMsg1 } = await buildCounterPartyAndBitgoMsgs(userRound1.signedMsg1, backupRound1.signedMsg1); + + const tamperedMsg: MPSTypes.MPSSignedMessage = { + message: Buffer.from('tampered').toString('base64'), + signature: '-----BEGIN PGP SIGNATURE-----\n\nINVALID\n-----END PGP SIGNATURE-----\n', + }; + + await assert.rejects( + () => + eddsaMPCv2Utils.createOfflineKeyGenRound2Share({ + walletPassphrase, + encryptedUserGpgPrvKey, + encryptedRound1Session: userRound1.encryptedRound1Session, + bitgoGpgPubKey: bitgoGpgKeyPair.publicKey, + counterPartyGpgPubKey: backupGpgKeyPair.publicKey, + bitgoMsg1, + counterPartyMsg1: tamperedMsg, + }), + 'should throw on tampered counterParty message' + ); + }); + + it('should reject wrong walletPassphrase for session decryption', async () => { + const { result: userRound1 } = await runRound1(MPCv2PartiesEnum.USER); + const { result: backupRound1 } = await runRound1(MPCv2PartiesEnum.BACKUP); + const encryptedUserGpgPrvKey = sjcl.encrypt(walletPassphrase, userGpgKeyPair.privateKey); + const { bitgoMsg1, counterPartyMsg1ForUser } = await buildCounterPartyAndBitgoMsgs( + userRound1.signedMsg1, + backupRound1.signedMsg1 + ); + + const wrongPassphraseBitgo = makeMockBitgo(); + (wrongPassphraseBitgo.decrypt as sinon.SinonStub).callsFake(() => { + throw new Error('ccm: tag does not match'); + }); + const mockCoin = { getMPCAlgorithm: sinon.stub().returns('eddsa') } as unknown as IBaseCoin; + const utilsWrongPass = new EddsaMPCv2Utils(wrongPassphraseBitgo, mockCoin); + + await assert.rejects( + () => + utilsWrongPass.createOfflineKeyGenRound2Share({ + walletPassphrase: 'wrongpassphrase', + encryptedUserGpgPrvKey, + encryptedRound1Session: userRound1.encryptedRound1Session, + bitgoGpgPubKey: bitgoGpgKeyPair.publicKey, + counterPartyGpgPubKey: backupGpgKeyPair.publicKey, + bitgoMsg1, + counterPartyMsg1: counterPartyMsg1ForUser, + }), + /ccm: tag does not match/ + ); + }); + + it('should reject non-BitGo pub key when env requires BitGo pub key config', async () => { + const { result: userRound1 } = await runRound1(MPCv2PartiesEnum.USER); + const { result: backupRound1 } = await runRound1(MPCv2PartiesEnum.BACKUP); + const encryptedUserGpgPrvKey = sjcl.encrypt(walletPassphrase, userGpgKeyPair.privateKey); + const { bitgoMsg1, counterPartyMsg1ForUser } = await buildCounterPartyAndBitgoMsgs( + userRound1.signedMsg1, + backupRound1.signedMsg1 + ); + + const prodMockBitgo = makeMockBitgo(); + (prodMockBitgo.getEnv as sinon.SinonStub).returns('prod'); + const mockCoin = { getMPCAlgorithm: sinon.stub().returns('eddsa') } as unknown as IBaseCoin; + const prodUtils = new EddsaMPCv2Utils(prodMockBitgo, mockCoin); + + await assert.rejects( + () => + prodUtils.createOfflineKeyGenRound2Share({ + walletPassphrase, + encryptedUserGpgPrvKey, + encryptedRound1Session: userRound1.encryptedRound1Session, + bitgoGpgPubKey: bitgoGpgKeyPair.publicKey, + counterPartyGpgPubKey: backupGpgKeyPair.publicKey, + bitgoMsg1, + counterPartyMsg1: counterPartyMsg1ForUser, + }), + /Invalid BitGo GPG public key/ + ); + }); +}); + describe('EddsaMPCv2Utils.createOfflineRound2Share', () => { let eddsaMPCv2Utils: EddsaMPCv2Utils; let mockBitgo: BitGoBase; diff --git a/modules/sdk-lib-mpc/src/tss/eddsa-mps/dkg.ts b/modules/sdk-lib-mpc/src/tss/eddsa-mps/dkg.ts index ff16e01be0..ea3f9042d3 100644 --- a/modules/sdk-lib-mpc/src/tss/eddsa-mps/dkg.ts +++ b/modules/sdk-lib-mpc/src/tss/eddsa-mps/dkg.ts @@ -274,7 +274,8 @@ export class DKG { * 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); this.dkgStateBytes = data.dkgStateBytes ? Buffer.from(data.dkgStateBytes, 'base64') : null; this.dkgState = data.dkgRound; From e6d42df962502f7a10e38b86f9b464387fc99c1a Mon Sep 17 00:00:00 2001 From: Support Bot Date: Tue, 14 Jul 2026 07:00:22 +0000 Subject: [PATCH 2/3] fix(sdk-core): fix lint errors and await DKG restoreSession in tests Fix prettier formatting in createOfflineKeyGenRound2Share (multi-line decrypt call) and in test helper (function signature and initDkg args). Remove unused variables from buildCounterPartyAndBitgoMsgs test helper. Update sdk-lib-mpc DKG session management test to await restoreSession, which is now async after the fix to load WASM before restoring state. Ticket: WCI-890 Session-Id: 2b6d8843-9bec-4a05-8725-18d450b7f097 Task-Id: dcea3d67-b592-483a-967b-968cc01f5b76 --- .../src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 5 ++++- .../unit/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 21 +++---------------- .../sdk-lib-mpc/test/unit/tss/eddsa/dkg.ts | 6 +++--- 3 files changed, 10 insertions(+), 22 deletions(-) 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 d0f80613e3..74649e4c04 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -735,7 +735,10 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { const bitgoKeyObj = await pgp.readKey({ armoredKey: bitgoGpgPubKey }); const counterPartyKeyObj = await pgp.readKey({ armoredKey: counterPartyGpgPubKey }); - const decryptedRound1Session = await this.bitgo.decrypt({ input: encryptedRound1Session, password: walletPassphrase }); + const decryptedRound1Session = await this.bitgo.decrypt({ + input: encryptedRound1Session, + password: walletPassphrase, + }); const { dkgSession, ownMsgPayload, ownMsgFrom } = JSON.parse(decryptedRound1Session) as { dkgSession: string; 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 80597f6a9c..6883e0db7a 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 @@ -818,9 +818,7 @@ describe('EddsaMPCv2Utils.createOfflineKeyGenRound2Share', () => { eddsaMPCv2Utils = new EddsaMPCv2Utils(mockBitgo, mockCoin); }); - async function runRound1( - partyId: MPCv2PartiesEnum.USER | MPCv2PartiesEnum.BACKUP = MPCv2PartiesEnum.USER - ): Promise<{ + async function runRound1(partyId: MPCv2PartiesEnum.USER | MPCv2PartiesEnum.BACKUP = MPCv2PartiesEnum.USER): Promise<{ result: { signedMsg1: MPSTypes.MPSSignedMessage; encryptedRound1Session: string }; ownGpgKeyPair: pgp.SerializedKeyPair; counterPartyGpgKeyPair: pgp.SerializedKeyPair; @@ -839,21 +837,11 @@ describe('EddsaMPCv2Utils.createOfflineKeyGenRound2Share', () => { } async function buildCounterPartyAndBitgoMsgs( - userRound1Result: MPSTypes.MPSSignedMessage, + _userRound1Result: MPSTypes.MPSSignedMessage, backupRound1Result: MPSTypes.MPSSignedMessage ): Promise<{ bitgoMsg1: MPSTypes.MPSSignedMessage; counterPartyMsg1ForUser: MPSTypes.MPSSignedMessage }> { const bitgoGpgPrivKey = await pgp.readPrivateKey({ armoredKey: bitgoGpgKeyPair.privateKey }); - // Build a real user DKG round1 to get the bitgo msg - const userGpgPrivKey = await pgp.readPrivateKey({ armoredKey: userGpgKeyPair.privateKey }); - const [, ownUserSk] = await MPSComms.extractEd25519KeyPair(userGpgPrivKey); - - const backupGpgPrivKey = await pgp.readPrivateKey({ armoredKey: backupGpgKeyPair.privateKey }); - const [, ownBackupSk] = await MPSComms.extractEd25519KeyPair(backupGpgPrivKey); - - const bitgoGpgPubKeyObj = await pgp.readKey({ armoredKey: bitgoGpgKeyPair.publicKey }); - const bitgoPk = await MPSComms.extractEd25519PublicKey(bitgoGpgPubKeyObj); - const userGpgPubKeyObj = await pgp.readKey({ armoredKey: userGpgKeyPair.publicKey }); const userPk = await MPSComms.extractEd25519PublicKey(userGpgPubKeyObj); @@ -861,10 +849,7 @@ describe('EddsaMPCv2Utils.createOfflineKeyGenRound2Share', () => { const backupPk = await MPSComms.extractEd25519PublicKey(backupGpgPubKeyObj); const bitgoDkg = new EddsaMPSDkg.DKG(3, 2, MPCv2PartiesEnum.BITGO); - await bitgoDkg.initDkg( - (await MPSComms.extractEd25519KeyPair(bitgoGpgPrivKey))[1], - [userPk, backupPk] - ); + await bitgoDkg.initDkg((await MPSComms.extractEd25519KeyPair(bitgoGpgPrivKey))[1], [userPk, backupPk]); const bitgoRawMsg1 = bitgoDkg.getFirstMessage(); const bitgoMsg1 = await MPSComms.detachSignMpsMessage(Buffer.from(bitgoRawMsg1.payload), bitgoGpgPrivKey); diff --git a/modules/sdk-lib-mpc/test/unit/tss/eddsa/dkg.ts b/modules/sdk-lib-mpc/test/unit/tss/eddsa/dkg.ts index f826536fec..72460aa6ba 100644 --- a/modules/sdk-lib-mpc/test/unit/tss/eddsa/dkg.ts +++ b/modules/sdk-lib-mpc/test/unit/tss/eddsa/dkg.ts @@ -286,9 +286,9 @@ describe('EdDSA MPS DKG', function () { const restoredBackup = new EddsaMPSDkg.DKG(3, 2, 1); const restoredBitgo = new EddsaMPSDkg.DKG(3, 2, 2); - restoredUser.restoreSession(userSession); - restoredBackup.restoreSession(backupSession); - restoredBitgo.restoreSession(bitgoSession); + await restoredUser.restoreSession(userSession); + await restoredBackup.restoreSession(backupSession); + await restoredBitgo.restoreSession(bitgoSession); assert.strictEqual(restoredUser.getState(), user.getState(), 'Restored state should match original'); assert.strictEqual(restoredBackup.getState(), backup.getState(), 'Restored backup state should match original'); From 9a7d47744bc96d33cd365ca2a0d679186bb2d35f Mon Sep 17 00:00:00 2001 From: Marzooqa Kather Date: Tue, 14 Jul 2026 09:24:46 +0000 Subject: [PATCH 3/3] fix(sdk-core): correct assert message in createOfflineKeyGenRound2Share The assert message incorrectly said "round 1" when it should say "round 2". Ticket: WCI-890 Session-Id: 2b6d8843-9bec-4a05-8725-18d450b7f097 Task-Id: dcea3d67-b592-483a-967b-968cc01f5b76 --- modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 74649e4c04..af1c91e278 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -767,7 +767,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { }; const round2Msgs = dkg.handleIncomingMessages([ownMsg1, counterPartyDeserializedMsg1, bitgoDeserializedMsg1]); - assert(round2Msgs.length === 1, 'DKG round 1 should produce exactly one round 2 message'); + assert(round2Msgs.length === 1, 'DKG round 2 should produce exactly one round 2 message'); const ownMsg2 = round2Msgs[0]; const signedMsg2 = await MPSComms.detachSignMpsMessage(Buffer.from(ownMsg2.payload), gpgPrvKey);