diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts index 16da87f15a..5cad5b6b82 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts @@ -37,7 +37,6 @@ import { isV2Envelope, } from '../baseTypes'; import { InvalidTransactionError } from '../../../errors'; -import { resolveEffectiveTxParams } from '../recipientUtils'; import { CreateEddsaBitGoKeychainParams, CreateEddsaKeychainParams, KeyShare, YShare } from './types'; import baseTSSUtils from '../baseTSSUtils'; import { BaseEddsaUtils } from './base'; @@ -691,13 +690,6 @@ export class EddsaUtils extends baseTSSUtils { ); unsignedTx = apiVersion === 'full' ? txRequestResolved.transactions![0].unsignedTx : txRequestResolved.unsignedTxs[0]; - - await this.baseCoin.verifyTransaction({ - txPrebuild: { txHex: unsignedTx.serializedTxHex ?? unsignedTx.signableHex }, - txParams: resolveEffectiveTxParams(txRequestResolved, params.txParams), - wallet: this.wallet, - walletType: this.wallet.multisigType(), - }); } else if (requestType === RequestType.message) { assert(txRequestResolved.messages?.length, 'Unable to find messages in txRequest for message signing'); const message = txRequestResolved.messages[0]; 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 d9a8a04eff..76f5a34557 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -42,7 +42,6 @@ import { TxRequest, isV2Envelope, } from '../baseTypes'; -import { resolveEffectiveTxParams } from '../recipientUtils'; import { EncryptionVersion } from '../../../../api'; import { BitGoBase } from '../../../bitgoBase'; import { BaseEddsaUtils } from './base'; @@ -433,10 +432,9 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { assert(txOrMessageToSign, 'Missing signableHex in unsignedTx'); derivationPath = unsignedTx.derivationPath; bufferContent = Buffer.from(txOrMessageToSign, 'hex'); - await this.baseCoin.verifyTransaction({ txPrebuild: { txHex: unsignedTx.serializedTxHex ?? txOrMessageToSign }, - txParams: resolveEffectiveTxParams(txRequest, params.txParams), + txParams: params.txParams || { recipients: [] }, wallet: this.wallet, walletType: this.wallet.multisigType(), }); diff --git a/modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts index b81f88bb57..9ff3f6cf55 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts @@ -27,13 +27,9 @@ export const NO_RECIPIENT_TX_TYPES = new Set([ 'enabletoken', 'disabletoken', 'customTx', - // DeFi operations — recipients/calldata built server-side from defiParams. - // camelCase variants match buildParams.type (SDK-facing); kebab-case variants match - // intent.intentType (WP-facing, used on the PA path where buildParams is absent). + // DeFi vault operations — recipients/calldata built server-side from defiParams 'defiApprove', 'defiDeposit', - 'defi-approve', - 'defi-deposit', // Smart contract invocations with no explicit SDK-level recipients 'contractCall', @@ -68,25 +64,6 @@ export const NO_RECIPIENT_TX_TYPES = new Set([ // with intentType 'import' (P-chain) or 'importtoc' (C-chain). 'import', 'importtoc', - - // SOL token account management - 'closeAssociatedTokenAccount', - - // ADA governance - 'voteDelegation', - - // CANTON multi-step transfer lifecycle - 'transferAcknowledge', - - // CANTON no-recipient workflow intents — per mpcUtils.ts exempt list and wallet.ts builders; - // these intents carry no client recipients - 'cosignDelegationAccept', - 'allocationAllocate', - 'allocationAllocateWithdrawn', - 'cantonEndInvestorOnboardingOffer', - 'cantonEndInvestorOnboardingAccept', - 'cantonEndInvestorOnboardingReject', - 'cantonParticipantOnboardingRequest', ]); /** 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 76233b8bdc..df2b3d17dc 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 @@ -1840,179 +1840,3 @@ describe('signRecoveryEddsaMPCv2', () => { ); }); }); - -describe('EdDSA MPCv2 signRequestBase recipient verification', () => { - let eddsaMPCv2Utils: EddsaMPCv2Utils; - let verifyTransactionStub: sinon.SinonStub; - - const walletId = 'wallet-verify-test'; - const signableHex = 'deadbeef'; - const serializedTxHex = 'cafebabe'; - const derivationPath = 'm/0'; - // Dummy key — tests only verify that verifyTransaction is called before MPC signing starts. - // Real DKG key generation is avoided to prevent WASM SIGSEGV on Node 22 CI. - const dummyPrv = randomBytes(64).toString('base64'); - - beforeEach(async () => { - verifyTransactionStub = sinon.stub().resolves(true); - - const mockBitgo = { - getEnv: sinon.stub().returns('test'), - setRequestTracer: sinon.stub(), - url: sinon.stub().callsFake((path: string) => `https://test.bitgo.com${path}`), - post: sinon.stub().returns({ - send: sinon.stub().returnsThis(), - set: sinon.stub().returnsThis(), - result: sinon.stub().rejects(new Error('mock: HTTP not available')), - }), - } as unknown as BitGoBase; - - const mockCoin = { - getMPCAlgorithm: sinon.stub().returns('eddsa'), - verifyTransaction: verifyTransactionStub, - } as unknown as IBaseCoin; - - const mockWallet = { - id: sinon.stub().returns(walletId), - multisigType: sinon.stub().returns('tss'), - multisigTypeVersion: sinon.stub().returns('MPCv2'), - } as unknown as IWallet; - - eddsaMPCv2Utils = new EddsaMPCv2Utils(mockBitgo, mockCoin, mockWallet); - sinon - .stub(eddsaMPCv2Utils as any, 'pickBitgoPubGpgKeyForSigning') - .resolves(await pgp.readKey({ armoredKey: (await generateGPGKeyPair('ed25519')).publicKey })); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('should call verifyTransaction with resolveEffectiveTxParams output', async () => { - const txRequest: TxRequest = { - txRequestId: 'txreq-verify-1', - walletId, - apiVersion: 'full', - transactions: [ - { - unsignedTx: { signableHex, serializedTxHex, derivationPath }, - signatureShares: [], - }, - ], - intent: { - intentType: 'payment', - recipients: [{ address: { address: 'solAddr1' }, amount: { value: '5000000', symbol: 'tsol' } }], - }, - unsignedTxs: [], - } as unknown as TxRequest; - - try { - await eddsaMPCv2Utils.signTxRequest({ - txRequest, - txParams: { recipients: [{ address: 'solAddr1', amount: '5000000' }] }, - prv: dummyPrv, - reqId: new RequestTracer(), - }); - } catch { - // Expected to fail at MPC signing rounds — we only care about verifyTransaction - } - - sinon.assert.calledOnce(verifyTransactionStub); - const call = verifyTransactionStub.getCall(0); - assert.strictEqual(call.args[0].txPrebuild.txHex, serializedTxHex); - assert.deepStrictEqual(call.args[0].txParams.recipients, [{ address: 'solAddr1', amount: '5000000' }]); - }); - - it('should resolve recipients from intent when txParams has none', async () => { - const txRequest: TxRequest = { - txRequestId: 'txreq-verify-2', - walletId, - apiVersion: 'full', - transactions: [ - { - unsignedTx: { signableHex, serializedTxHex, derivationPath }, - signatureShares: [], - }, - ], - intent: { - intentType: 'payment', - recipients: [{ address: { address: 'solAddr2' }, amount: { value: '1000', symbol: 'tsol' } }], - }, - unsignedTxs: [], - } as unknown as TxRequest; - - try { - await eddsaMPCv2Utils.signTxRequest({ - txRequest, - prv: dummyPrv, - reqId: new RequestTracer(), - }); - } catch { - // Expected to fail at MPC signing rounds - } - - sinon.assert.calledOnce(verifyTransactionStub); - const call = verifyTransactionStub.getCall(0); - assert.strictEqual(call.args[0].txParams.recipients[0].address, 'solAddr2'); - assert.strictEqual(call.args[0].txParams.recipients[0].amount, '1000'); - }); - - it('should not call verifyTransaction for message signing', async () => { - const txRequest: TxRequest = { - txRequestId: 'txreq-verify-msg', - walletId, - apiVersion: 'full', - messages: [ - { - messageEncoded: 'deadbeef', - derivationPath: 'm/0', - }, - ], - unsignedTxs: [], - } as unknown as TxRequest; - - try { - await eddsaMPCv2Utils.signTxRequestForMessage({ - txRequest, - prv: dummyPrv, - reqId: new RequestTracer(), - messageRaw: 'test message', - bufferToSign: Buffer.from('deadbeef', 'hex'), - }); - } catch { - // Expected to fail at MPC signing rounds - } - - sinon.assert.notCalled(verifyTransactionStub); - }); - - it('should use signableHex as fallback when serializedTxHex is missing', async () => { - const txRequest: TxRequest = { - txRequestId: 'txreq-verify-fallback', - walletId, - apiVersion: 'full', - transactions: [ - { - unsignedTx: { signableHex, derivationPath }, - signatureShares: [], - }, - ], - intent: { intentType: 'consolidate' }, - unsignedTxs: [], - } as unknown as TxRequest; - - try { - await eddsaMPCv2Utils.signTxRequest({ - txRequest, - prv: dummyPrv, - reqId: new RequestTracer(), - }); - } catch { - // Expected to fail at MPC signing rounds - } - - sinon.assert.calledOnce(verifyTransactionStub); - const call = verifyTransactionStub.getCall(0); - assert.strictEqual(call.args[0].txPrebuild.txHex, signableHex); - }); -}); diff --git a/modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts b/modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts index dcbecfb3b8..9e668cfafe 100644 --- a/modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts +++ b/modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts @@ -29,8 +29,6 @@ describe('recipientUtils', function () { 'customTx', 'defiApprove', 'defiDeposit', - 'defi-approve', - 'defi-deposit', 'contractCall', // Staking 'delegate', @@ -51,25 +49,16 @@ describe('recipientUtils', function () { 'transferOfferWithdrawn', 'cantonCommand', 'pledge', + // Avalanche / Flare cross-chain atomic imports 'import', 'importtoc', - 'closeAssociatedTokenAccount', - 'voteDelegation', - 'transferAcknowledge', - 'cosignDelegationAccept', - 'allocationAllocate', - 'allocationAllocateWithdrawn', - 'cantonEndInvestorOnboardingOffer', - 'cantonEndInvestorOnboardingAccept', - 'cantonEndInvestorOnboardingReject', - 'cantonParticipantOnboardingRequest', ]; expected.forEach((t) => assert.ok(NO_RECIPIENT_TX_TYPES.has(t), `${t} should be in NO_RECIPIENT_TX_TYPES`)); assert.strictEqual(NO_RECIPIENT_TX_TYPES.size, expected.length); }); it('does not contain value-transfer types', function () { - ['payment', 'fanout', 'vote', 'defi-redeem'].forEach((t) => { + ['payment', 'fanout', 'vote', 'defi-deposit', 'defi-redeem'].forEach((t) => { assert.ok(!NO_RECIPIENT_TX_TYPES.has(t), `${t} must NOT be in NO_RECIPIENT_TX_TYPES`); }); });