From f3502723f176180cca329d4104046e8e2c1c4aa0 Mon Sep 17 00:00:00 2001 From: Bahaa Desoky Date: Tue, 19 May 2026 17:13:47 -0400 Subject: [PATCH] feat(sdk-core): add v2 decryption support for gg18 Ticket: WCN-283 --- .../test/v2/unit/internal/tssUtils/ecdsa.ts | 77 ++++++++++++++++++- .../src/bitgo/utils/tss/ecdsa/ecdsa.ts | 32 +++++--- 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts index 9786e02c2a..6e9c010a43 100644 --- a/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts +++ b/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts @@ -426,6 +426,34 @@ describe('TSS Ecdsa Utils:', async function () { await Promise.all(testCasesPromises); }); + it('createParticipantKeychain should produce a v2-encrypted encryptedPrv when encryptionVersion: 2', async function () { + const passphrase = 'test-passphrase'; + // Stub keychains.add to capture the params sent to the API so we can inspect encryptedPrv + const addStub = sinon.stub(baseCoin.keychains(), 'add').resolves({ id: '1', pub: '', type: 'tss' } as Keychain); + try { + await tssUtils.createParticipantKeychain( + userGpgKey, + userLocalBackupGpgKey, + bitgoPublicKey, + 1, + userKeyShare, + backupKeyShare, + nockedBitGoKeychain, + passphrase, + undefined, + undefined, + 2 // encryptionVersion + ); + sinon.assert.calledOnce(addStub); + const addParams = addStub.firstCall.args[0] as { encryptedPrv?: string }; + assert.ok(addParams.encryptedPrv, 'encryptedPrv must be passed to keychains.add'); + const envelope = JSON.parse(addParams.encryptedPrv!); + assert.strictEqual(envelope.v, 2, 'encryptedPrv must use v2 (Argon2id) envelope'); + } finally { + addStub.restore(); + } + }); + it('should fail to generate TSS keychains when received invalid number of wallet signatures', async function () { const bitgoKeychain = await generateBitgoKeychain({ coin: coinName, @@ -983,7 +1011,28 @@ describe('TSS Ecdsa Utils:', async function () { encryptedWShare: bitgo.encrypt({ input: JSON.stringify(wShare), password: mockPassword }), walletPassphrase: 'password1', }) - .should.be.rejectedWith("password error - ccm: tag doesn't match"); + .should.be.rejectedWith('incorrect password'); + }); + + it('createOfflineMuDeltaShare should succeed with v2-encrypted wShare', async function () { + const mockPassword = 'password'; + const alphaLength = 1536; + const deltaLength = 64; + const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' }); + const encryptedWShare = await bitgo.encryptAsync({ + input: JSON.stringify(wShare), + password: mockPassword, + encryptionVersion: 2, + }); + assert.strictEqual(JSON.parse(encryptedWShare).v, 2, 'pre-condition: wShare must be v2-encrypted'); + const step2SigningMaterial = await tssUtils.createOfflineMuDeltaShare({ + aShareFromBitgo: aShare, + bitgoChallenge: bitgoChallenges, + encryptedWShare, + walletPassphrase: mockPassword, + }); + step2SigningMaterial.muDShare.muShare.alpha.length.should.equal(alphaLength); + step2SigningMaterial.muDShare.dShare.delta.length.should.equal(deltaLength); }); it('createOfflineSShare should succeed', async function () { @@ -1006,6 +1055,32 @@ describe('TSS Ecdsa Utils:', async function () { step3SigningMaterial.s.length.should.equal(privKeyLength); }); + it('createOfflineSShare should succeed with v2-encrypted oShare', async function () { + const mockPassword = 'password'; + const pubKeyLength = 66; + const privKeyLength = 64; + const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' }); + const encryptedOShare = await bitgo.encryptAsync({ + input: JSON.stringify(oShare), + password: mockPassword, + encryptionVersion: 2, + }); + assert.strictEqual(JSON.parse(encryptedOShare).v, 2, 'pre-condition: oShare must be v2-encrypted'); + const step3SigningMaterial = await tssUtils.createOfflineSShare({ + tssParams: { + txRequest: txRequest, + reqId: reqId, + }, + dShareFromBitgo: dShare, + encryptedOShare, + walletPassphrase: mockPassword, + requestType: RequestType.tx, + }); + step3SigningMaterial.R.length.should.equal(pubKeyLength); + step3SigningMaterial.y.length.should.equal(pubKeyLength); + step3SigningMaterial.s.length.should.equal(privKeyLength); + }); + it('createOfflineSShare should fail with txId passed', async function () { const mockPassword = 'password'; const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' }); diff --git a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts index 2d0f0c84ca..d38dbc5898 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts @@ -49,7 +49,7 @@ import { TxRequestChallengeResponse, } from '../../../tss/types'; import { BaseEcdsaUtils } from './base'; -import { IRequestTracer } from '../../../../api'; +import { EncryptionVersion, IRequestTracer } from '../../../../api'; const encryptNShare = ECDSAMethods.encryptNShare; @@ -183,6 +183,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { passphrase, originalPasscodeEncryptionCode, webauthnInfo, + encryptionVersion, }: CreateEcdsaKeychainParams): Promise { if (!passphrase) { throw new Error('Please provide a wallet passphrase'); @@ -198,7 +199,8 @@ export class EcdsaUtils extends BaseEcdsaUtils { bitgoKeychain, passphrase, originalPasscodeEncryptionCode, - webauthnInfo + webauthnInfo, + encryptionVersion ); } @@ -210,6 +212,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { bitgoKeychain, bitgoPublicGpgKey, passphrase, + encryptionVersion, }: CreateEcdsaKeychainParams): Promise { assert(backupKeyShare.userHeldKeyShare); assert(passphrase); @@ -221,7 +224,10 @@ export class EcdsaUtils extends BaseEcdsaUtils { userKeyShare, backupKeyShare.userHeldKeyShare, bitgoKeychain, - passphrase + passphrase, + undefined, + undefined, + encryptionVersion ); } @@ -312,7 +318,8 @@ export class EcdsaUtils extends BaseEcdsaUtils { bitgoKeychain: Keychain, passphrase: string, originalPasscodeEncryptionCode?: string, - webauthnInfo?: WebauthnKeyEncryptionInfo + webauthnInfo?: WebauthnKeyEncryptionInfo, + encryptionVersion?: EncryptionVersion ): Promise { const bitgoKeyShares = bitgoKeychain.keyShares; if (!bitgoKeyShares) { @@ -402,9 +409,10 @@ export class EcdsaUtils extends BaseEcdsaUtils { keyType: 'tss' as KeyType, commonKeychain: bitgoKeychain.commonKeychain, prv: prv, - encryptedPrv: this.bitgo.encrypt({ + encryptedPrv: await this.bitgo.encryptAsync({ input: prv, password: passphrase, + encryptionVersion, }), originalPasscodeEncryptionCode, webauthnDevices: @@ -499,7 +507,10 @@ export class EcdsaUtils extends BaseEcdsaUtils { userPublicGpgKey: userPublicGpgKey, kShare: userSignShare.kShare, wShare: params.walletPassphrase - ? this.bitgo.encrypt({ input: JSON.stringify(userSignShare.wShare), password: params.walletPassphrase }) + ? await this.bitgo.encryptAsync({ + input: JSON.stringify(userSignShare.wShare), + password: params.walletPassphrase, + }) : userSignShare.wShare, }; } @@ -529,7 +540,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { i: userGammaAndMuShares.muShare.i, }, oShare: params.walletPassphrase - ? this.bitgo.encrypt({ + ? await this.bitgo.encryptAsync({ input: JSON.stringify(userOmicronAndDeltaShare.oShare), password: params.walletPassphrase, }) @@ -584,7 +595,10 @@ export class EcdsaUtils extends BaseEcdsaUtils { encryptedWShare: string; walletPassphrase: string; }): Promise { - const decryptedWShare = this.bitgo.decrypt({ input: params.encryptedWShare, password: params.walletPassphrase }); + const decryptedWShare = await this.bitgo.decryptAsync({ + input: params.encryptedWShare, + password: params.walletPassphrase, + }); return await this.createTssEcdsaStep2SigningMaterial({ aShareFromBitgo: params.aShareFromBitgo, bitgoChallenge: params.bitgoChallenge, @@ -618,7 +632,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { } catch (err) { hash = undefined; } - const decryptedOShare = this.bitgo.decrypt({ input: encryptedOShare, password: walletPassphrase }); + const decryptedOShare = await this.bitgo.decryptAsync({ input: encryptedOShare, password: walletPassphrase }); const { i, R, s, y } = await ECDSAMethods.createUserSignatureShare( JSON.parse(decryptedOShare), dShareFromBitgo,