Skip to content

fix(txpool): add EIP-2 signature malleability check and unify tx hash computation#17

Open
wpank wants to merge 1 commit into
Nunchi-trade:mainfrom
wpank:fix/txpool-eip2-malleability-and-hash
Open

fix(txpool): add EIP-2 signature malleability check and unify tx hash computation#17
wpank wants to merge 1 commit into
Nunchi-trade:mainfrom
wpank:fix/txpool-eip2-malleability-and-hash

Conversation

@wpank
Copy link
Copy Markdown

@wpank wpank commented May 30, 2026

Summary

Fixes two critical security issues in the transaction pool:

  • EIP-2 signature malleability (closes #195): The validator accepted ECDSA signatures with high s values, allowing an observer to take any valid pending transaction and produce a second valid transaction with a different hash but the same sender and nonce. This pollutes the mempool and breaks hash-based transaction identity. The fix adds a normalize_s() check after Signature::from_slice() to reject signatures where s is in the upper half of the secp256k1 curve order.

  • Inconsistent tx hash computation (closes #197): The Mempool::insert() path computed the transaction hash as keccak256(raw_2718_bytes) while the validator path computed it as keccak256(rlp_encode(envelope)). Neither is the canonical Ethereum transaction hash (keccak256(encode_2718(envelope))), and the mismatch meant the same transaction inserted via different paths would have different hashes in the pool's by_hash map, bypassing duplicate detection. Both paths now use envelope.tx_hash() which returns the canonical hash.

Additionally fixes a pre-existing compile error where rejection_reason() was missing match arms for GasLimitTooHigh and InitcodeTooLarge error variants, and removes the now-unused alloy-rlp dependency.

Changes

  • crates/node/txpool/src/validator.rs: Add EIP-2 low-s check, use canonical tx_hash(), remove unused keccak256 import
  • crates/node/txpool/src/pool.rs: Use canonical tx_hash() in tx_to_ordered(), add missing match arms in rejection_reason()
  • crates/node/txpool/Cargo.toml: Remove unused alloy-rlp dependency

Test plan

  • Existing validator tests pass (they use k256::ecdsa::SigningKey::sign_prehash_recoverable which produces low-s signatures by default)
  • Verify that a manually constructed high-s signature is rejected by recover_sender_and_hash()
  • Verify that Mempool::insert() and validator validate() produce the same hash for the same transaction

🤖 Generated with Claude Code

… computation

Add EIP-2 low-s signature validation to reject transactions with high-s
values, preventing transaction malleability attacks where an observer can
create a second valid transaction with a different hash from any pending
transaction. Also unify hash computation across both insertion paths
(validator and Mempool::insert) to use the canonical Ethereum tx hash
via envelope.tx_hash() instead of divergent keccak256 encodings.

Closes #195, #197

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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