Repository navigation
Conversation
The EVM TSS verifier cannot check a pending-approval transaction against what the user approved, because the signing path only hands it the resolveEffectiveTxParams output. A compromised server can then label arbitrary calldata as a payment and the user key share signs it (TOB-BITGOEDMPC-2 / INFOSEC-671). The ecdsa and ecdsaMPCv2 signing paths now pass the stored txRequest.intent, the caller's untouched txParams (callerTxParams) and the chain name into verifyTransaction. The caller's params are passed separately because once resolveEffectiveTxParams back-fills type and recipients from the stored intent, txParams alone cannot tell what the caller declared. The chain name also fixes token-name resolution from amount.symbol, matching the EdDSA path. BitGoAPI gains the client-side strictTssVerification and onVerificationWarning options, set from local options only and never from server data, plus the TssVerificationWarning reporting types. The kebab-case DeFi spellings (defi-approve, defi-deposit, defi-withdraw, defi-redeem) join NO_RECIPIENT_TX_TYPES, so stored DeFi intents reach the verifier instead of failing the missing-recipients guard. This only waives that guard: the abstract-eth verifier in the next commit checks approve, deposit and withdraw calldata and flags defi-redeem, which has no handler yet. The two must ship together. Ticket: CECHO-2460 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
verifyTssTransaction trusted the server-declared txParams.type and only decoded two calldata shapes under transfer. A compromised server could label approve(attacker, MAX) or any contract call as a payment, or redirect a payment on the pending-approval path, and the user key share would sign it (TOB-BITGOEDMPC-2 / INFOSEC-671). The verifier now decodes to/value/data and compares them with the intent. The intent type picks a handler, and each handler accepts a fixed set of shapes: native send, ERC-20 or ERC-7984 transfer, exact dApp call, batcher batch/batchTransferFrom, tokenApproval to the batcher for MAX, fillNonce, consolidation to the base address, and the DeFi calldata checks. Anything else, including a type with no handler, is a violation. contractCall and customTx throw, since EVM cannot create them. Exactly one source speaks for the user. When the caller declares a type or recipients, only its own params count, and the stored intent is used only when the caller declared nothing (pending approval). The asset comes from local identity only: the recipient's tokenAddress or tokenName, the top-level tokenName, and the coin instance. Identity that cannot be resolved, or that names two assets, is a violation, and the calldata shape must match the asset. Checks that threw before keep throwing, now on the pending-approval path too. New checks warn through console.warn and the optional onVerificationWarning callback, and throw when the client sets strictTssVerification. EvmCoin.verifyLegacyTssTransaction, which returned true without decoding, is removed so kavaevm and prividiumeth run the parent verifier. The sdk-coin-eth wrapper-approve tests move off customTx, which now throws, and the README documents the contract. Ticket: CECHO-2460 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The verifier changes what EVM TSS signing accepts, so the audit cases need regression coverage that fails if a check quietly stops firing. The sdk-coin-evm suite runs on kavaevm (legacy) and hbarevm (EIP-1559). It covers mismatched destination, value and calldata, approve(attacker, MAX) labelled as a payment, ERC-20 and ERC-7984 transfers, tokenApproval, fillNonce, unhandled intent types, the pending-approval path through resolveEffectiveTxParams with a stored intent (defi-* intents included, with tampered amounts rejected), networks with no configured batcher, and the warning payload. It also pins intent isolation and asset binding: stored calldata cannot fill a caller recipient, a server-named token cannot replace the caller's token, and native and token shapes cannot be swapped. The sdk-coin-bsc suite covers batcher flows on tbsc, which has a real batcher: native batch and token batchTransferFrom happy paths and mismatches, selector pinning, single-asset batches, and approve(batcher, MAX) against approve(attacker, MAX). Ticket: CECHO-2460 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
This was referenced Nov 3, 2026
Contributor
|
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Makes EVM TSS signing fail closed: before the user key share signs,
verifyTssTransactiondecodes the transaction bytes and comparesto/value/datawith the user intent, on both signing paths (direct send = the caller'stxParams; pending approval = the storedtxRequest.intent).This PR replaces the four-PR stack #9972 → #9973 → #9975 → #10114 with one change. The final tree is byte-identical to #10114's head; the content is the same, regrouped into three commits so no commit carries the pre-review verifier:
fix(sdk-core): carries the stored intent, the caller's untouchedtxParams(callerTxParams) and the chain name intoverifyTransaction; adds the client-sidestrictTssVerification/onVerificationWarningoptions and theTssVerificationWarningtypes; lets stored kebab-case DeFi intents past the missing-recipients guard.fix(abstract-eth): the fail-closed verifier (tssVerification.ts), with the fix(abstract-eth): verify EVM TSS payments against declared asset #10114 review fixes folded in:batch/batchTransferFrom,tokenApprovalto the batcher for MAX,fillNonce, consolidation, and the DeFi calldata checks. Anything else is a violation;contractCall/customTxthrow.EvmCoin.verifyLegacyTssTransaction, so kavaevm / prividiumeth run the parent verifier instead of returningtrue.test(sdk-coin-evm): acceptance suites on kavaevm, hbarevm and tbsc (batcher).Why
TOB-BITGOEDMPC-2 / INFOSEC-671:
verifyTssTransactiontrusted the server-declaredtxParams.type. A compromised server could labelapprove(attacker, MAX)or any contract call as a payment, or redirect a payment on the pending-approval path, and the key share would sign it.The stack could not land piece by piece: #9972 relaxes the DeFi recipient guard that #9973's handlers rely on, so master would sign stored DeFi intents unchecked between the two merges, and #10114 rewrote the core of #9973.
strictTssVerification)approve(attacker, MAX)under a native payment intentcontractCallintentTest plan
BitGo SDKCI on this PR.Open items
defi-redeemhas no handler. With this PR it reaches the verifier and only warns in default mode, where master rejected it at the recipients guard. Decide before release: drop the redeem entries fromNO_RECIPIENT_TX_TYPES, or make that violation fatal.strictTssVerificationdefaults to off, so the new checks log rather than block for default clients until a follow-up release flips it.callerTxParamstoVerifyTransactionOptions. Out-of-repoverifyTransactionimplementations won't get the caller / stored split until they read it.Supersedes #9972, #9973, #9975, #10114.
Ticket: CECHO-2460
🤖 Generated with Claude Code