Skip to content

fix(eip8130): canonical sender_auth encodings (tx-hash/gas malleability) - #4439

Draft
chunter-cb wants to merge 1 commit into
eip8130-integrationfrom
feat/eip8130-auth-canonicalization
Draft

fix(eip8130): canonical sender_auth encodings (tx-hash/gas malleability)#4439
chunter-cb wants to merge 1 commit into
eip8130-integrationfrom
feat/eip8130-auth-canonicalization

Conversation

@chunter-cb

Copy link
Copy Markdown
Contributor

Summary

Closes an auth-encoding malleability + gas-griefing vector on the EIP-8130 pipeline.

sender_auth/payer_auth cannot 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_cost folds over the full signed encoding). Because two distinct byte encodings were accepted for the same authorization, any relayer could mutate the auth blob to:

  • mint a second valid transaction hash for the same signer (txid malleability), and
  • inflate sender-intrinsic gas, shrinking the execution-gas budget so signed call phases OOG-revert after inclusion — nonce consumed, fee paid, earlier phases committed — all without the signing key.

Root causes fixed

  1. EOA sender v — the empty-sender path recovered through alloy_primitives::Signature::try_from, whose normalize_v maps {0,1,27,28} to the same parity. So v could be flipped to a non-canonical byte and still recover the same signer. Now enforce a strict 65-byte length and v ∈ {27,28} (Electrum notation) before parsing, matching the k1 authenticator's existing recover_k1 check. Applied in the shared recover_eoa_sender_with, so the checked and unchecked paths agree (no path can accept a tx another rejects). Honest tooling (Signature::as_bytes27 + y_parity) is unaffected.

  2. WebAuthnabi_decode_params ignores 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: rewriting v from {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-eip8130 clean (default features)
  • Consider follow-up: broader consensus/e2e test asserting mempool↔inclusion symmetry under auth mutation.

Notes / parity

The tx-level sender/payer signatures and intrinsic-gas accounting are node/protocol concerns, not Keystore.sol surface, so there is no contract-parity regression. The WebAuthn canonical-ABI check is intentionally stricter than OZ abi.decode (which tolerates trailing bytes), but only rejects malleated blobs — honest abi.encode output always matches its canonical re-encoding — so no honest transaction is rejected.

…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.
@github-actions

Copy link
Copy Markdown
Contributor

Base Std historical fork tests

Fork Result Passed Failed Skipped base/base base-anvil base-std
Beryl pass 616 0 13 d2764728 6d744e03 4658f1b7
Cobalt pass 718 0 14 d2764728 ae7557c4 38567e20

View run

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