Skip to content

feat(beacon): add EIP-8450's post-quantum key registry to heze - #714

Draft
tcoratger wants to merge 7 commits into
lambdaclass:heze-devnet-3from
tcoratger:feat/eip-8450-hstar
Draft

tcoratger wants to merge 7 commits into
lambdaclass:heze-devnet-3from
tcoratger:feat/eip-8450-hstar

Conversation

@tcoratger

@tcoratger tcoratger commented Oct 27, 2026 •

Copy link
Copy Markdown

🗒️ Description / Motivation

EIP-8450 gives every validator and builder a post-quantum key ahead of a signature switch. This PR puts it in heze, following the EIP as revised by ethereum/EIPs#12592 and #12599, and its consensus-specs PR, ethereum/consensus-specs#5782. That PR builds the registry as the eip8450 feature on top of heze; here it is part of heze itself.

This replaces the earlier version of this PR, which added a separate hstar fork on top of gloas, following the first draft of the EIP. The EIP has since changed a lot:

  • the withdrawal-address path, its execution-layer request and pending queue are gone;
  • the BLS signing key is the only authority, with a rate limit and replay protection;
  • builders can register;
  • a VRF root is stored beside the leanXMSS key.

The PR targets heze-devnet-3, the latest heze devnet: EIP-7805, EIP-8015, EIP-8365 and EIP-8198 (quick slots). Testing the registry alongside the other heze components gives a more faithful picture than testing it alone. It also ports the consensus-specs merges since v1.7.0-beta.4 that touch forks up to heze, one commit each.

What Changed

The registry (the last commit)

  • State and body. Heze's state appends four lists parallel to the validator and builder registries:
    • post_quantum_key_material, each index's leanXMSS key plus VRF root, zero for builders;
    • post_quantum_key_change_epochs, the epoch of each index's last change;
    • their builder twins.
      Heze's body appends pq_key_registrations. Each active_fields layout grows by exactly the appended fields, so every earlier field keeps its generalized index.
  • Unset entries. The gloas upgrade into heze fills the lists. New validators get an unset entry, and so do new builders, a reused builder index included. Shared gloas-family code asks BeaconState::pq_registry(), which is None before heze, rather than checking the fork.
  • process_pq_key_registration runs last in process_operations, in the spec's order. It checks:
    • the registrant: validator or builder, not exiting, an active builder, a zero VRF root for builders;
    • previous_change_epoch against the stored change, which blocks replays;
    • the change delay;
    • the registrant's BLS signature;
    • a nonzero key that no index holds;
    • the leanXMSS proof of possession of PQKeyPossession{pubkey, key}, at leaf 0.
  • Gossip. A new pq_key_registration topic, on heze digests only. It is validated as the spec's validate_pq_key_registration_gossip, IGNORE/REJECT order included. The first-seen record for a pair (index, change epoch) is judged at its spec position, so a later REJECT still wins over a duplicate.
  • Pool, production and API.
    • The pool keeps one registration per pair, and pruning drops stale or exiting ones.
    • Heze block production packs the pooled registrations that apply after the block's other operations.
    • GET/POST /eth/v1/beacon/pool/pq_key_registrations lists and submits registrations. It is not yet a Beacon API route.
  • Crypto. ethlambda_crypto::lean_xmss verifies through the lean-pq bindings. Nothing in the scheme is reimplemented.

Spec catch-ups since v1.7.0-beta.4 (six commits)

  • #5736: at the gloas fork, only builder-credential deposits fund builders.
  • #5764: voluntary-exit gossip is ignored while the validator has a pending partial withdrawal.
  • #5756: slot component durations round to the nearest millisecond, in the node and in the validator client. On this branch the rounding applies to EIP-8198's per-epoch slot duration.
  • #5765: fulu's is_data_available binds column sidecars to the block.
  • #5767: a block without blobs gets no data column sidecars.
  • #5763: a doc fix; the code already used the proposal slot.

Already handled here: #5724, #5780, #5768. Not applicable, since there is no implementation of those features: #5737 (partial columns), #5670 and #5788 (fast confirmation). The other merges touch only feature forks heze does not include in the specs (EIP-8025, EIP-8148).

Correctness / Behavior Guarantees

Invariants

  • Each registry list stays exactly as long as the registry it parallels.
  • Every applied registration moves its index's stored change epoch forward, so none applies twice.
  • A builder index that is reused starts from the new builder's deposit epoch, so a registration signed for the previous builder never applies.

Behavior changes

  • DB_VERSION 8 → 9: heze states and blocks change shape.
  • thiserror 2.0.18 → 2.0.21, required by lean-pq, resolved with make update under the 14-day cooldown.

Spec vectors

  • Heze is now the spec's eip8450 fork. The released heze vectors lack the registry, so they are listed in UNMODELED_FORKS. The eip8450 directory runs as heze instead.
  • Generating them. No release ships EIP-8450 vectors yet. make eip8450-spec-tests generates them with #5782's pyspec, pinned by commit. It also adds eip8450 to the forks ssz_static covers, in that temporary copy only.
  • Slot duration. #5782 predates EIP-8198 in heze, so its configs name no SLOT_DURATION_MS_HEZE. The harness's existing rule for such fixtures keeps the heze slot duration equal to the genesis one, matching how they were generated.
  • Without them, the run reports fixture_forks/missing/eip8450 as ignored, so the gap is visible.
  • Ignored eip8450 cases. Its fork and transition cases start from the registryless heze, so they are ignored.
  • Five beta.4 gloas fork cases are ignored by name, in SUPERSEDED_BY_5736. #5736 changed what they expect, and the same cases regenerated at #5736 pass.

New dependency: lean-pq (ethereum/lean-pq-bindings v0.8.0, pinned by commit). The spec vectors are generated with py-lean-pq, the same bindings, so proofs must verify under exactly that scheme. The leanvm pin here is the lean chain's XMSS (BLAKE2s, 42 chains, one tree), a different scheme. The two leanVM revisions coexist as separate crate graphs.

Tests Added / Run

Runs were kept to what the change needs.

  • EIP-8450 vectors generated from #5782 at b1b194cb, minimal preset, run as heze on heze-devnet-3: 10,366 passed, 0 failed. This covers:
    • all 52 operations/pq_key_registration cases;
    • all 21 networking/gossip_pq_key_registration cases;
    • ssz_static for the new and modified containers;
    • heze's sanity, epoch, fork-choice and gossip suites at the eip8450 shape.
      The ignored are the fork/transition cases and topics the node does not validate.
      The #5782 head has since merged master; that brings only spec fixes already ported here, no EIP-8450 change.
  • Gloas fork vectors regenerated at the same commit: 44/44 pass, which verifies the #5736 port.
  • Unit tests:
    • 35 registry tests with real BLS and leanXMSS keys, covering each operation rule, gossip ordering, packing and pruning;
    • the slot-component rounding, EIP-8198 quick-slot timing and validator slot-clock tests, where the #5756 port meets EIP-8198;
    • registry list helpers, XmssSignature encoding, and the lean-pq wrapper.
  • Checks: cargo fmt --check passes, and clippy -D warnings is clean on the workspace and on the beacon-spec-tests target.

Not run, to save compute: the mainnet preset of the vectors, the full beta.4 suite, and the whole-workspace tests. CI covers those.

Related Issues / PRs

✅ Verification Checklist

  • Ran make fmt: clean
  • Ran make lint (clippy with -D warnings): clean
  • Ran make test: only the impacted tests and the generated EIP-8450 vectors, listed above

🤖 Generated with Claude Code

@tcoratger
tcoratger force-pushed the feat/eip-8450-hstar branch from fb6b7aa to 9c4397c Compare October 30, 2026 17:11
@tcoratger
tcoratger changed the base branch from main to heze-devnet-2 October 30, 2026 17:11
@tcoratger tcoratger changed the title feat(beacon): add hstar, the post-quantum key registry of EIP-8450 feat(beacon): add EIP-8450's post-quantum key registry to heze Oct 30, 2026
tcoratger and others added 7 commits November 1, 2026 19:59
…rk (consensus-specs #5736)

onboard_builders_from_pending_deposits now keeps every deposit without a builder withdrawal credential queued, even when a builder holds its pubkey.
A valid builder deposit registers a builder even under a validator's pubkey or behind a pending validator deposit.
A builder deposit with a bad signature for a new pubkey stays queued only when it can still fund a validator, and is dropped otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…awals (consensus-specs #5764)

Electra's validate_voluntary_exit_gossip ignores an exit while the validator has a pending partial withdrawal, since process_voluntary_exit would reject it.
An ignored exit is not recorded as seen, so the validator can exit once the withdrawal is processed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d (consensus-specs #5756)

get_slot_component_duration_ms now rounds half up instead of flooring, so 3333 basis points of a 12 s slot is 4000 ms rather than 3999 ms.
The validator client's slot clock computes its duty offsets the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ble (consensus-specs #5765)

Each sampled sidecar's header must now hash to the block root, and its inclusion proof must tie its commitments to that header.
Before, sidecars built from unrelated blobs under another block's header made the block available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (consensus-specs #5767)

Fulu's get_data_column_sidecars and gloas's builder version returned NUMBER_OF_COLUMNS empty sidecars for a block without blobs, which every peer rejects.
Both now return none.
The live publishing paths already skipped sidecar construction for such blocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arent choice (consensus-specs #5763)

The proposer already decides should_build_on_full for the proposal slot, the slot the head state is advanced to.
Only this doc comment still named the wall-clock slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EIP-8450 gives every validator and builder a post-quantum key ahead of a
signature switch. This puts it in heze, where consensus-specs builds it as the
`eip8450` feature on top of heze (ethereum/consensus-specs#5782), following
the EIP as revised by ethereum/EIPs#12592 and #12599:

- Heze's state appends four lists parallel to the validator and builder
  registries: each index's key material (a leanXMSS key and a VRF root, zero
  for builders) and the epoch of its last change. Heze's body appends
  `pq_key_registrations`. The gloas upgrade into heze fills the lists, and new
  validators and builders, a reused builder index included, get unset entries.
- A registration names a validator or a builder, is authorized by the
  registrant's BLS key, and carries a leanXMSS proof of possession of the key
  for that BLS key, at the reserved leaf 0. It may replace registered keys once
  per MIN_PQ_KEY_CHANGE_DELAY epochs (256, 8 in minimal), and names the change
  it replaces, which blocks replays. A key any index holds is refused.
- Registrations travel as a new operation on the `pq_key_registration` topic,
  validated in the specification's order on a heze head, pooled per (index,
  change epoch), packed by heze block production when they apply after the
  other operations, and submitted through
  `/eth/v1/beacon/pool/pq_key_registrations` (not yet a Beacon API route).

The beacon suite runs the `eip8450` vectors as heze, and lists `heze` (without
the registry) as unmodeled. No release ships those vectors yet:
`make eip8450-spec-tests` generates them from #5782's pyspec, pinned by commit.
DB_VERSION goes to 9, since heze rows change shape.

New dependency: lean-pq, the leanXMSS bindings of ethereum/lean-pq-bindings
(v0.8.0, pinned by commit). The consensus-specs vectors are generated with
py-lean-pq, the Python side of the same bindings, and proofs must verify under
exactly that scheme. The `leanvm` pin here carries the lean chain's own XMSS
(BLAKE2s, 42 chains, one tree), a different scheme, so it cannot stand in.
thiserror moves to 2.0.21, which lean-pq requires, resolved under the 14-day
cooldown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tcoratger
tcoratger force-pushed the feat/eip-8450-hstar branch from 9c4397c to c1bfda7 Compare November 1, 2026 19:09
@tcoratger
tcoratger changed the base branch from heze-devnet-2 to heze-devnet-3 November 1, 2026 19:09

This branch has not been deployed

No deployments
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.

2 participants