Skip to content

consensus/parlia: validate BlockAccessListHash and SlotNumber in header verification - #3808

Merged
zlacfzy merged 1 commit into
bnb-chain:developfrom
zlacfzy:fix/parlia-verify-bal-slotnumber-nil
Sep 8, 2026
Merged

zlacfzy merged 1 commit into
bnb-chain:developfrom
zlacfzy:fix/parlia-verify-bal-slotnumber-nil

Conversation

@zlacfzy

@zlacfzy zlacfzy commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Description

Validate the two trailing rlp:"optional" header fields BlockAccessListHash (EIP-7928) and SlotNumber (EIP-7843) in Parlia's header verification, mirroring the existing fork-gated handling of RequestsHash and ParentBeaconRoot: they must be absent before Amsterdam and present from Amsterdam on.

Rationale

These optional fields are covered by the block hash but not by the Parlia seal preimage (EncodeSigHeader stops after RequestsHash), and header verification previously did not check them at all. Because they are absent from honest blocks before Amsterdam, the missing check left a validator-signed block's hash malleable: the fields could be added without invalidating the seal, yielding a different block hash on an otherwise validly-sealed block. Adding the same nil/presence validation Parlia already applies to the other post-Prague optional fields closes that gap. From Amsterdam the fields are legitimately present, and their content is bound by state validation (BlockAccessListHash is recomputed from execution) and the slot rules rather than by the seal.

Example

A pre-Amsterdam header carrying a non-nil BlockAccessListHash or SlotNumber is now rejected by VerifyHeader before seal verification, instead of being accepted.

Changes

  • consensus/parlia: in VerifyUnsealedHeader (on the VerifyHeader peer-import path and the bid pre-seal path), require BlockAccessListHash and SlotNumber to be nil before Amsterdam and non-nil from Amsterdam on.

…e Amsterdam

BlockAccessListHash (EIP-7928) and SlotNumber (EIP-7843) are trailing
rlp:"optional" header fields that are covered by the block hash but not
by the Parlia seal preimage (EncodeSigHeader stops after RequestsHash).
Parlia's header verification never validated them, so before Amsterdam an
unprivileged peer could append a crafted BlockAccessListHash to any
validator-signed block: RLP-valid, seal preimage and signature unchanged,
but a different block hash. Every node accepted the mutated block as
validly sealed, letting a peer with no stake or key grind the block hash
of an honest validator's block (e.g. to bias BLOCKHASH-based consumers or
win a same-height lower-hash fork-choice tie).

Validate these fields in VerifyUnsealedHeader, which is on the peer-import
path (VerifyHeader -> verifyHeader -> VerifyUnsealedHeader, before
verifySeal) as well as the bid pre-seal path, mirroring the existing
RequestsHash/ParentBeaconRoot handling: require them absent before
Amsterdam and present from Amsterdam on. This closes the live pre-Amsterdam
malleability; from Amsterdam the fields are legitimately present and their
content is bound by state validation and the slot rules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hashdit-bot

hashdit-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

Pull Request Review

This PR strengthens Parlia header verification by enforcing fork-gated presence rules for the optional BlockAccessListHash and SlotNumber fields. Both fields must be absent before Amsterdam and present from Amsterdam onward, preventing pre-fork block-hash malleability while preserving post-fork behavior.

Sensitive Content

No sensitive content detected.

Security Issues

No serious security issues detected.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

@zlacfzy
zlacfzy merged commit 1855d8f into bnb-chain:develop Sep 8, 2026
8 checks passed
chee-chyuan added a commit to bnb-chain/reth-bsc that referenced this pull request Sep 10, 2026
…fication (#499)

Ports bnb-chain/bsc#3808. `BlockAccessListHash` (EIP-7928) and `SlotNumber`
(EIP-7843) are trailing `rlp:"optional"` fields on alloy's `Header`: they decode
off the wire and are covered by the block hash, but the Parlia seal preimage stops
after `requests_hash` (`encode_header_with_chain_id`, mirroring go-bsc's
`EncodeSigHeader`), so the seal does not commit to them. Header verification did
not check them at all.

Honest BSC blocks never set either field -- every header reth-bsc assembles hard-codes
both to `None`, and every `NextBlockEnvAttributes` construction site passes
`slot_number: None`. So the gap was pure malleability: an unprivileged peer could
append a crafted value to a validator-signed header, changing its block hash while the
signature still recovers the same signer, and reth-bsc would accept the result as a
validly-sealed block.

`validate_unsealed_header_fields` -- go-bsc's `VerifyUnsealedHeader` scope, shared by
the peer-import path and the BidBlock pre-seal path -- now requires both fields to be
absent, alongside the presence rules it already applies to `withdrawals_root`, the 4844
fields, `parent_beacon_block_root` and `requests_hash`. Error strings match go-bsc's
("invalid BlockAccessListHash, have %#x, expected nil") so cross-client log comparison
stays mechanical.

go-bsc gates the rule on Amsterdam -- absent before, required from. BSC has not
scheduled Amsterdam and reth-bsc has no such fork, so only the "must be absent" half is
expressible here; the presence half lands with Amsterdam itself. This is recorded on the
helper so the second half is not forgotten.

Tests cover the helper's two rejections and the both-absent baseline, that the check is
actually reached from `validate_unsealed_header_fields` (an otherwise-valid pre-Cancun
header passes, the same header with either field does not), and the malleability itself:
appending either field leaves `hash_with_chain_id` unchanged while `hash_slow` differs,
which is precisely why the seal cannot catch it.

Co-authored-by: Claude Opus 5 (1M context) <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.

5 participants