fix(rpc): close signed-read replay gap via decoder-level rejection#383
fix(rpc): close signed-read replay gap via decoder-level rejection#383samlaf wants to merge 1 commit intoveridise-audit-april-2026from
Conversation
Signed-read seismic transactions were previously rejected only at mempool admission. Block producers ingesting txs through non-mempool paths — builder API, private orderflow, or the EIP-712 `TypedData` variant of `eth_sendRawTransaction` which bypassed `Decodable2718` — could include a signed-read tx directly, letting an attacker who intercepted a signed `eth_call` payload replay it as an actual state-changing transaction. Consumes seismic-alloy's decoder-level rejection (#104, c1ce533) and updates this crate accordingly: * `send_raw_transaction`: the `TypedData` arm now decodes the EIP-712 payload into a `SeismicTxEnvelope`, re-encodes as RLP, and delegates to `EthTransactions::send_raw_transaction(bytes)`. All signed-tx ingress now funnels through a single `Decodable2718::typed_decode` pipeline, so decode-time invariants (including the new signed-read rejection) apply uniformly. Removes the parallel pool-admission pipeline that was the bypass. * For `eth_call`'s Bytes path, a new `recover_raw_seismic_call_tx` helper uses seismic-alloy's permissive `decode_2718_permit_seismic_calls` so legitimate signed-read payloads are still accepted. * Removes the now-redundant mempool-level `validate_signed_read_for_write` check and its associated error variant. Depends on seismic-alloy PR #104; the \`rev\` in Cargo.toml is temporary and should be bumped to the merged-main commit before landing.
|
Based on my analysis of the diff and the commit message, I can now provide a comprehensive review. Moves signed-read validation from mempool to decoder level to close replay attack vulnerability. The changes look correct and address an important security issue where attackers could replay intercepted Phase 1
Phase 2
The refactoring successfully consolidates transaction handling by removing the parallel |
Depends on SeismicSystems/seismic-alloy#104.
Signed-read seismic transactions were previously rejected only at mempool admission. This might (?) work in a TEE world but is fragile, and given that we are planning to go to mainnet without TEEs it was a real issue. We might also one day want to enable external block building via builder API, which would bypass the mempool.
signed_readcheck is now done as part of 2718 decoding, which happens in:eth_calluses a special purposerecover_raw_seismic_call_txfunction which allowssigned_read=truetxs.Side note
In
send_raw_transaction, theTypedDataarm now decodes the EIP-712 payload into aSeismicTxEnvelope, re-encodes as RLP, and delegates toEthTransactions::send_raw_transaction(bytes). This makes sure all ingestion paths go through the 2718 decoding function. Also added a TODO mentioning that this ingestion path is not needed, and we could update our clients to send via the Bytes path directly.More generally, this is a first step in the right direction, but I think the even cleaner design is to enforce signed_reads cryptoraphically instead. See the "Future hard-fork requiring change to SeismicTx" section in SeismicSystems/seismic-alloy#104