fix(eip8130): canonical sender_auth encodings (tx-hash/gas malleability) - #4439
fix(eip8130): canonical sender_auth encodings (tx-hash/gas malleability)#4439chunter-cb wants to merge 2 commits into
Conversation
…ash/gas malleability
sender_auth cannot be covered by the sender/payer signature hashes (a
signature cannot sign over itself), yet every auth byte is billed as
EIP-2028 payload gas. Combined with non-canonical auth encodings, a relayer
could mutate the auth blob to mint a second valid transaction hash and shrink
the execution-gas budget for the same signer without the key.
Two encodings were accepted for the same authorization:
- EOA sender path: alloy's parser normalizes v in {0,1,27,28} to the same
parity, so v could be flipped to a non-canonical byte (txid malleability).
Enforce v in {27,28} (Electrum notation) and a strict 65-byte length before
parsing, matching the k1 authenticator's existing check. Applied to both the
checked and unchecked recovery paths so all entry points agree.
- WebAuthn: abi_decode_params ignores trailing/non-canonical ABI bytes, which
decode to the same value (and pass the P-256 check) but change the tx hash
and inflate payload gas. Require the input to equal its canonical ABI
re-encoding, rejecting only malleated blobs (honest abi.encode output is
unaffected). Covers both sender and payer auth via the shared dispatch.
Adds consensus tests asserting that changing an unsigned authentication byte
(EOA v -> {0,1}; WebAuthn trailing byte) invalidates the transaction.
Review SummaryThis PR correctly identifies and fixes two real auth-encoding malleability vectors in the EIP-8130 pipeline. The changes are minimal, well-targeted, and thoroughly documented. Changes Reviewed1. EOA sender v canonicalization (signed.rs): Adds a pre-parse guard in recover_eoa_sender_with requiring sender_auth to be exactly 65 bytes with v in {27, 28}. This closes the gap where alloy Signature::try_from would accept and normalize v in {0, 1} to the same parity, enabling a relayer to flip the v-byte and produce a distinct (but valid) transaction hash. 2. WebAuthn ABI canonicalization (dispatch.rs): After ABI-decoding the WebAuthn blob, re-encodes and compares against the original input to reject non-canonical ABI encodings (trailing bytes, non-minimal dynamic offsets). The abi_encode_params() allocation is negligible relative to the P-256 + SHA-256 work already in this path. AssessmentNo issues found. Both fixes are correct:
|
Summary
Closes an auth-encoding malleability + gas-griefing vector on the EIP-8130 pipeline.
sender_auth/payer_authcannot be covered by the sender/payer signature hashes (a signature can't sign over itself), yet every auth byte is billed as EIP-2028 payload gas (Eip8130IntrinsicGas::payload_costfolds over the full signed encoding). Because two distinct byte encodings were accepted for the same authorization, any relayer could mutate the auth blob to:Root causes fixed
EOA sender
v— the empty-senderpath recovered throughalloy_primitives::Signature::try_from, whosenormalize_vmaps{0,1,27,28}to the same parity. Sovcould be flipped to a non-canonical byte and still recover the same signer. Now enforce a strict 65-byte length andv ∈ {27,28}(Electrum notation) before parsing, matching the k1 authenticator's existingrecover_k1check. Applied in the sharedrecover_eoa_sender_with, so the checked and unchecked paths agree (no path can accept a tx another rejects). Honest tooling (Signature::as_bytes→27 + y_parity) is unaffected.WebAuthn —
abi_decode_paramsignores trailing / non-canonical ABI bytes, which decode to the same value and still pass the P-256 check, but change the tx hash and inflate payload gas. Now require the input to equal its canonical ABI re-encoding (decoded.abi_encode_params() != data → reject). Covers sender and payer auth via the shared dispatch. P-256 already enforced a fixed 129-byte length, so no trailing bytes were possible there.Tests
recover_eoa_sender_rejects_noncanonical_v: rewritingvfrom{27,28}to the{0,1}encoding of the same parity is rejected on both recovery paths.webauthn_rejects_trailing_bytes: appending an unsigned trailing byte to an otherwise-valid blob is rejected.Test plan
cargo test -p base-execution-eip8130(187 passed)cargo test -p base-common-consensus --all-features(eip8130 suite green; new test passes)cargo clippy -p base-execution-eip8130clean (default features)Notes / parity
The tx-level sender/payer signatures and intrinsic-gas accounting are node/protocol concerns, not
Keystore.solsurface, so there is no contract-parity regression. The WebAuthn canonical-ABI check is intentionally stricter than OZabi.decode(which tolerates trailing bytes), but only rejects malleated blobs — honestabi.encodeoutput always matches its canonical re-encoding — so no honest transaction is rejected.