Repository navigation
Conversation
Contributor
This was referenced Nov 3, 2026
Contributor
|
|
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 ecdsa and ecdsaMPCv2 signing paths now pass the stored txRequest.intent, the caller's untouched txParams (callerTxParams) and the chain name into verifyTransaction. When the caller declares a type or recipients, only its own params count. The stored intent is used only when the caller declared nothing (pending approval), so server data never fills gaps in a caller's intent. The asset comes from local identity only; unresolvable or conflicting identity is a finding. The verifier decodes the bytes once and runs small checks picked by the intent type, each accepting a closed 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. contractCall and customTx throw. A token consolidation must carry no native value, which closes a path where transfer(baseAddress, 0) sent to any contract moved the native balance there. Checks return findings and one policy step decides warn or throw. Fatal findings, the checks that threw before, still throw with the same messages. Every other finding goes to console.warn and the new onVerificationWarning callback, or throws when the client sets the new client-side strictTssVerification BitGoAPI option, which is never read from server data. Stored kebab-case DeFi intents (defi-approve, defi-deposit, defi-withdraw) now pass the missing-recipients guard so their calldata is checked. defi-redeem has no handler and stays rejected. EvmCoin.verifyLegacyTssTransaction, which returned true without decoding, is removed so kavaevm and prividiumeth run the parent verifier. The verifier helpers stay internal to abstract-eth. Tests cover the audit cases on kavaevm, hbarevm and tbsc (batcher), and an end-to-end case runs the real verifier inside EcdsaUtils.signTxRequest to show rejected bytes never reach the signing round. Ticket: CECHO-2460 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dpkjnr
force-pushed
the
CECHO-2460-evm-tss-fail-closed
branch
from
November 4, 2026 06:25
fd8d18d to
90aceaa
Compare
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 once and comparesto/value/datawith the user intent, on both signing paths: direct send (the caller'stxParams) and pending approval (the storedtxRequest.intent).txParams(callerTxParams) and the chain name intoverifyTransaction. Stored kebab-case DeFi intents (defi-approve,defi-deposit,defi-withdraw) pass the missing-recipients guard so their calldata gets checked.defi-redeemhas no handler and stays rejected, as on master.batch/batchTransferFrom,tokenApprovalto the batcher for MAX,fillNonce, consolidation to the base address, and the DeFi calldata checks.contractCall/customTxthrow.console.warnand the newonVerificationWarningcallback, or throws when the client sets the new client-sidestrictTssVerificationoption onBitGoAPI.transfer(baseAddress, 0)sent to any contract passed the recipient check and could move the native balance.EvmCoin.verifyLegacyTssTransaction, which returnedtruewithout decoding, is removed, so kavaevm and prividiumeth run the parent verifier.@bitgo/abstract-eth.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.strictTssVerification)approve(attacker, MAX)under a native payment intentcontractCall/customTxintentconsolidationToBaseAddress)defi-redeemintentTest plan
EcdsaUtils.signTxRequest(kavaevm). Rejected bytes never reach the signing round.BitGo SDKCI on this PR.Open items
strictTssVerificationopts into throwing, as agreed on CECHO-2460. Whether that closes TOB-BITGOEDMPC-2 / INFOSEC-671 is still open with security (INFOSEC-691).strictTssVerificationyet; it buildsBitGofrom a fixed option list.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