Skip to content

fix(sdk-core): pass txParams on EdDSA MPCv2 re-sign and PA paths#9061

Open
bitgo-ai-agent-dev[bot] wants to merge 1 commit into
masterfrom
wci-765-fix-eddsa-mpcv2-txparams
Open

fix(sdk-core): pass txParams on EdDSA MPCv2 re-sign and PA paths#9061
bitgo-ai-agent-dev[bot] wants to merge 1 commit into
masterfrom
wci-765-fix-eddsa-mpcv2-txparams

Conversation

@bitgo-ai-agent-dev

@bitgo-ai-agent-dev bitgo-ai-agent-dev Bot commented Jun 18, 2026

Copy link
Copy Markdown

Summary

EdDSA MPCv2 TSOL signing fails on the pending-approval (PA) path with:

failed to sign transaction Error: Number of tx outputs does not match with number
of txParams recipients

Root cause: eddsaMPCv2.signRequestBase re-verifies the transaction against
txParams.recipients before DSG starts:


  await this.baseCoin.verifyTransaction({
    txPrebuild: { txHex: unsignedTx.serializedTxHex ?? txOrMessageToSign },
    txParams: params.txParams || { recipients: [] },  // ← falls back to []
    ...
  });

Solana's verifyTransaction does a strict length check:

if (filteredRecipients.length !== filteredOutputs.length) {
  throw new Error('Number of tx outputs does not match with number of txParams
recipients');
}

Why ECDSA MPCv2 is unaffected: ETH's verifyTssTransaction gates its strict
recipient checks on txParams.type. Without a type, those checks are skipped and {
recipients: [] } passes silently.

Why EdDSA MPCv1 is unaffected: MPCv1's signRequestBase does not call
verifyTransaction with txParams in normal flows.

Fix

Derive txParams from the persisted txRequest.intent inside recreateTxRequest,
guarded to EdDSA MPCv2 only — matching the pattern established by WCI-505:

Ticket: WCI-765

@linear-code

linear-code Bot commented Jun 18, 2026

Copy link
Copy Markdown

WCI-765

@bitgo-ai-agent-dev bitgo-ai-agent-dev Bot force-pushed the wci-765-fix-eddsa-mpcv2-txparams branch from 83641a6 to 5fe3aca Compare June 18, 2026 11:22
@Marzooqa Marzooqa force-pushed the wci-765-fix-eddsa-mpcv2-txparams branch from 5fe3aca to f2a4114 Compare June 18, 2026 13:08
@Marzooqa Marzooqa marked this pull request as ready for review June 18, 2026 14:38
@Marzooqa Marzooqa requested review from a team as code owners June 18, 2026 14:38

@danielpeng1 danielpeng1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work; look into the fix for UI re-sign as well, unless you're planning to do that in a second PR, although it might be easier to reuse something for both paths.

Comment thread modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts
Comment thread modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts Outdated
Comment thread modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts Outdated
Comment thread modules/sdk-core/test/unit/bitgo/utils/tss/baseTSSUtils.ts
@danielpeng1

Copy link
Copy Markdown
Contributor

@claude review and verify validity of requested changes

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @danielpeng1's task —— View job


Review Analysis - Daniel's Feedback Valid ✅

I've analyzed the current PR implementation and Daniel's review feedback. All of Daniel's concerns are technically valid and should be addressed for a complete fix.

  • Read current PR implementation
  • Examine UI re-sign path in wallet.ts
  • Analyze recipient mapping concerns
  • Review test coverage issues
  • Provide comprehensive assessment

Key Issues Identified

1. UI Re-sign Path Missing Fix ⚠️

Valid Concern: The current fix only covers the PA path in recreateTxRequest(), but the UI re-sign path via signAndSendTxRequest()getUserKeyAndSignTssTransaction()signTransactionTss() is also broken.

Root Cause: In wallet.ts:4733, signTransactionTss() calls:

this.tssUtils!.signTxRequest({
  txRequest: params.txPrebuild.txRequestId,  // Just the ID
  txParams: params.txPrebuild.buildParams,   // Always undefined for re-sign
  ...
})

When re-signing, buildParams is undefined because only txRequestId is passed, leading to the same empty recipients: [] fallback.

2. Recipient Mapping Issues ⚠️

Valid Concerns:

  • Native SOL: Intent stores symbol: 'tsol' but native SOL txParams should NOT have tokenName (see test example)
  • Token mapping: Only set tokenName when r.amount.symbol !== this.baseCoin.getChain()
  • Non-payment txs: Need to map txParams.type for enableToken, etc., since SOL skips recipient checks when txParams.type === 'enabletoken'

3. Test Coverage Gaps ⚠️

Valid Concerns:

  • Current test expects tokenName: 'tsol' but should be { address, amount } only for native SOL
  • Missing test coverage for UI re-sign path (signAndSendTxRequest with only txRequestId)
  • Missing SPL token test case with symbol: 'tsol:usdc'tokenName: 'tsol:usdc'

Recommended Implementation Strategy

Daniel's suggestion for a shared helper function is the best approach:

  1. Extract shared helper used by both baseTSSUtils.ts and wallet.ts
  2. Handle all recipient mapping edge cases in one place
  3. Add comprehensive test coverage for both paths

This would ensure consistency and avoid code duplication while fixing both the PA path (already implemented) and the UI re-sign path (currently missing).

Technical Validation

  • ✅ PR correctly identifies the root cause (empty txParams.recipients)
  • ✅ Guard condition is appropriate (EdDSA MPCv2 only)
  • ✅ Intent parsing approach matches WCI-505 pattern
  • ❌ Missing UI re-sign path coverage
  • ❌ Incomplete recipient mapping logic
  • ❌ Test expectations don't match SOL verification requirements

Verdict: Daniel's requested changes are technically sound and necessary for a complete fix.


Job: https://github.com/BitGo/BitGoJS/actions/runs/12224654675
Branch: wci-765-fix-eddsa-mpcv2-txparams

@Marzooqa Marzooqa force-pushed the wci-765-fix-eddsa-mpcv2-txparams branch from f2a4114 to d222dd6 Compare June 19, 2026 10:44
EdDSA MPCv2 verifyTransaction() compares txParams.recipients to parsed
tx outputs before MPC signing. On re-sign and pending-approval flows
txParams was never derived, causing a "Number of tx outputs does not
match number of txParams recipients" error.

Add txParamsFromIntent() which maps the persisted IntentRecipient
shape on txRequest.intent into the flat ITransactionRecipient shape
expected by verifyTransaction callers.

Wire it in two places:
- recreateTxRequest: derive txParams after fetching the fresh
  txRequest so PA approve → auto-sign completes correctly.
- signTransactionTss: when buildParams is absent (re-sign via
  signAndSendTxRequest), fetch the txRequest and derive txParams
  so UI re-sign completes correctly.

Existing buildParams / sendMany paths are unchanged; the new
derivation only runs when buildParams is not already present.

Ticket: WCI-765
Session-Id: 1c44b40e-24c1-49b1-b454-86318c9a44a2
Task-Id: bad80f2c-73b6-4826-a6ee-49cc4344544c
@Marzooqa Marzooqa force-pushed the wci-765-fix-eddsa-mpcv2-txparams branch from d222dd6 to ca8ddb4 Compare June 19, 2026 10:50
@Marzooqa Marzooqa requested a review from danielpeng1 June 19, 2026 11:22

@danielpeng1 danielpeng1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm for both paths 👍

Comment on lines +60 to +63
const intentRecipients = (intent as PopulatedIntent | undefined)?.recipients;
if (!intentRecipients?.length) {
return undefined;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be accessed via type narrowing instead of casting?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also what if an intent is valid for having no recipients wouldn't this throw downstream during verification?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants