Skip to content

fix(abstract-eth): verify EVM TSS payments against declared asset - #10114

Closed
dpkjnr wants to merge 2 commits into
CECHO-2460-evm-tss-fail-closed-handlers-pt3from
CECHO-2460-evm-tss-fail-closed-handlers-pt4
Closed

dpkjnr wants to merge 2 commits into
CECHO-2460-evm-tss-fail-closed-handlers-pt3from
CECHO-2460-evm-tss-fail-closed-handlers-pt4

Conversation

@dpkjnr

@dpkjnr dpkjnr commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

What

Fixes the three blocking findings from the stack review on #9972 (all in part 2's tssVerification.ts) and adds regression tests for each.

  • The caller's own txParams are the only intent source when they declare a type or recipients. The signing paths (ecdsa, ecdsaMPCv2) pass the untouched caller input as callerTxParams, so resolveEffectiveTxParams back-filling cannot make server data look caller-declared. The stored tx request intent applies only when the caller declared nothing (pending-approval signing). It can no longer pick the handler, add calldata, name the token, or turn a caller-declared payment into a consolidation.
  • The asset is resolved from local identity only: the recipient's tokenAddress / tokenName, the top-level tokenName, and the coin instance. Identity that cannot be resolved to a contract, or that names two different assets, is a violation instead of a skipped check.
  • Calldata must match the asset. A native payment cannot carry token or batch calldata, a token payment cannot be a native send, and a batch must move a single asset with the matching method (batch for native, batchTransferFrom for tokens).
  • abstract-eth README documents the verifier contract and the strictTssVerification opt-in.

Why

TOB-BITGOEDMPC-2 / INFOSEC-671 review on this stack, all three findings P1:

  1. Server calldata filled a caller recipient that declared none, so approve(attacker, MAX) passed as an exact dApp call.
  2. Server token metadata replaced the caller's token, so a transfer to a substituted contract passed.
  3. Native and token payment shapes were interchangeable, and an unresolved token contract skipped the contract check.

Test plan

  • New tests: sdk-coin-evm declared intent isolation and asset binding (on kavaevm and hbarevm), sdk-coin-bsc batch asset binding, and ECDSA / MPCv2 wiring tests checking that callerTxParams and the stored intent reach verifyTransaction (and that pending approval passes no caller params).

  • Bite check: 18 sdk-coin-evm, 4 sdk-coin-bsc and both wiring tests fail on part 3's head and pass here.

  • Before/after on verifyTransaction (default mode / strictTssVerification):

    Case master this stack
    approve(attacker, MAX) under a native payment intent passes silently warns / throws
    ERC-20 transfer on a different token contract than declared passes silently warns / throws
    contractCall intent passes silently throws / throws
    legitimate native send, legitimate ERC-20 transfer pass pass / pass
  • Suites on the stack rebased onto current master: abstract-eth 188, sdk-coin-evm 124, sdk-coin-bsc 56, sdk-coin-eth 517, sdk-api 253, sdk-core 1063.

  • Pending: BitGo SDK CI on this head (it doesn't trigger for a non-master base; run it via workflow_dispatch). Part 3's run, covering everything below this PR, is green.

  • Pending: prod backtest of ~563k EVM TSS payments from the last 90 days (release gate, tracked on CECHO-2460).

Risk

  • Security-sensitive: this changes verification for every EVM TSS payment. In default mode the new checks only warn; the checks that already threw still throw, now also on the pending-approval path. The backtest covers false rejections before release.
  • Adds an optional callerTxParams to VerifyTransactionOptions (iBaseCoin.ts). Purely additive, but out-of-repo verifyTransaction implementations won't get the caller/stored split until they read it.

Stack

This PR is part 4 of 4; review and merge in order:

  1. fix(sdk-core): carry TSS intent and strict policy to verifyTransaction #9972 — sdk-core intent + strict policy plumbing (base: master)
  2. fix(abstract-eth): fail-closed EVM TSS handlers for verifyTssTransaction #9973 — abstract-eth fail-closed verifier (base: CECHO-2460-evm-tss-fail-closed-handlers-pt1)
  3. test(sdk-coin-evm): cover fail-closed EVM TSS verification paths #9975 — verification test suites (base: CECHO-2460-evm-tss-fail-closed-handlers-pt2)
  4. fix(abstract-eth): verify EVM TSS payments against declared asset #10114 — caller-declared intent and asset binding (base: CECHO-2460-evm-tss-fail-closed-handlers-pt3) ← you are here

Ticket: CECHO-2460

@linear-code

linear-code Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

CECHO-2460

Preserve caller-declared intent and reject unresolved or contradictory asset
and calldata combinations.

Ticket: CECHO-2460
The previous commit made the caller's txParams authoritative over the
stored tx request intent and bound EVM TSS payments to a locally resolved
asset. These tests pin that: stored calldata cannot fill a caller
recipient, a server-named token cannot replace the caller's token, native
and token shapes cannot be swapped, batches must move one asset with the
matching batch method, and unresolvable or conflicting token identity
fails closed. ECDSA and MPCv2 wiring tests check that callerTxParams and
the stored intent both reach verifyTransaction, and that pending approval
passes no caller params. The README documents the verifier's contract and
the strictTssVerification opt-in.

Ticket: CECHO-2460
@dpkjnr
dpkjnr force-pushed the CECHO-2460-evm-tss-fail-closed-handlers-pt4 branch from df2acc0 to ab76aae Compare November 3, 2026 10:49
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Compatibility checks are failing on Node.js 27-nightly (experimental, non-blocking). This does not block merge, but it signals an incompatibility to fix before this release becomes supported.

View run

@dpkjnr
dpkjnr requested a review from a team November 3, 2026 10:50
@dpkjnr
dpkjnr marked this pull request as ready for review November 3, 2026 11:07
@dpkjnr
dpkjnr requested review from a team as code owners November 3, 2026 11:08

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@dpkjnr

dpkjnr commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #10121, which carries the whole CECHO-2460 stack as three commits against master. Its tree is byte-identical to the head of #10114, so this PR's changes, part 4 (caller-declared intent and asset binding), are all there. The stack couldn't land piece by piece: #9972 relaxes the DeFi recipient guard that #9973's handlers rely on, and #10114 rewrote the core of #9973. Closing in favor of #10121; the branch is kept.

@dpkjnr dpkjnr closed this Nov 3, 2026
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.

1 participant