Skip to content

Add post-quantum consensus key registry (EIP-8450) - #5782

Draft
kevaundray wants to merge 17 commits into
ethereum:masterfrom
kevaundray:kw/8450
Draft

kevaundray wants to merge 17 commits into
ethereum:masterfrom
kevaundray:kw/8450

Conversation

@kevaundray

@kevaundray kevaundray commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Add new EIP

This adds the pq public key registry ontop of Heze.

TLDR:

  • Validators register a leanXMSS public key and a VRF root, authorized by their BLS signing key and proven by a leanXMSS signature at leaf index 0
  • Registered keys are dormant and can be replaced at most once every MIN_PQ_KEY_CHANGE_DELAY epochs
  • Registrations are a new block operation and gossip topic, and the keys live in lists parallel to the validator registry. (This is kind of similar to BLSToExchange where we upgrade 0x00 withdrawal credentials to 0x01)

New Library

  • leanXMSS verification and signing uses py-lean-pq currently

Add EIP-8450 as a feature on top of Heze. Validators register a
leanXMSS public key and a VRF root, authorized by their BLS signing
key and proven by a leanXMSS signature at leaf index 0. Registered keys
are dormant and can be replaced at most once every
MIN_PQ_KEY_CHANGE_DELAY epochs. Registrations are a new block operation
and gossip topic, and the keys live in lists parallel to the validator
registry.

leanXMSS verification and signing are backed by py-lean-pq in tests and
follow the BLS setting, so tests that run without BLS also skip leanXMSS
checks. Public keys are always derived since they are written to the
state. The feature is added to the CI test matrix.
@github-actions github-actions Bot added the testing CI, actions, tests, testing infra label Oct 28, 2026
@kevaundray

Copy link
Copy Markdown
Contributor Author

Blocked on ethereum/EIPs#12592

Once the clock reaches the EIP-8450 fork epoch, the head state can still
be a Heze state until the first block of the fork epoch is imported.
That state has no `pq_public_keys` or `pq_key_change_epochs`, so gossip
validation would fail while reading them. Ignore registrations until the
head state is at or after the fork, as the EIP now specifies.
Replay protection relies on each change being applied in a later epoch
than the change it names. With a delay of zero, a change applied in the
epoch it names stores that epoch again, so the same registration could
be included twice. State the constraint next to the preset, matching
the EIP. Current presets use 256 and 8, so neither is affected.
Registry entries no longer carry a version byte, so `PQPublicKeys` is
the leanXMSS key and the VRF root (64 bytes), and a registration is 2580
bytes. Whether a validator has registered is read from its change epoch,
which is `FAR_FUTURE_EPOCH` until the first registration. The
hash-based primitives are now specified by their interface only, as in
the EIP, which no longer depends on EIP-CRYPTO: `xmss.Verify`,
`xmss_signature_index`, and `vrf.Verify`, which this upgrade does not
call. This matches ethereum/EIPs#12592.
Stateful signing is not assumed to be deterministic, so signing the same
message twice at one leaf index is also a leaf reuse. A signer therefore
signs at `PQ_POSSESSION_XMSS_INDEX` only once per key, keeps the proof,
and resubmits the same bytes in every registration of the key. The
crypto section also drops construction details, since the constructions
are specified outside this upgrade. This matches ethereum/EIPs#12592.
Comment thread specs/_features/eip8450/beacon-chain.md Resolved
Comment thread specs/_features/eip8450/beacon-chain.md Resolved
Comment thread specs/_features/eip8450/beacon-chain.md Resolved
Comment thread specs/_features/eip8450/beacon-chain.md Resolved
Comment thread specs/_features/eip8450/beacon-chain.md Resolved
Comment thread specs/_features/eip8450/beacon-chain.md Resolved
Comment thread specs/_features/eip8450/beacon-chain.md Outdated
Comment on lines +280 to +282
pq_public_keys: ValidatorPQPublicKeys
# [New in EIP8450]
pq_key_change_epochs: ValidatorPQKeyChangeEpochs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also (with the exception of ptc_window ugh) we try not to use acronyms in structure fields. It would be a bit verbose, but we might want to expand "pq" to "post_quantum" here.

Comment thread specs/_features/eip8450/beacon-chain.md Resolved
Comment thread specs/_features/eip8450/beacon-chain.md Resolved
A registration is now rejected if any registered validator holds its
leanXMSS key, including the validator that registers. A change never
reuses the current key or its proof, so each proof of possession is
signed once and only resubmitted unchanged when its registration was not
included. Changing only the VRF root therefore requires a new leanXMSS
key. This matches ethereum/EIPs#12592.
Consensus only rejects a leanXMSS key that a validator currently holds,
so returning to an earlier key, or registering a key that another
validator released, is valid on chain. The honest validator guide now
states the separate signer rule: each key is used in at most one
registration, for one validator, so its proof is signed once. A
registration that is not included stays in the operation pools and is
included later with the same bytes. This matches ethereum/EIPs#12592.
Returning to one's own earlier key could reuse the stored first proof,
since its message is unchanged, while taking another validator's released
key needs a proof of a different message at leaf 0. The signer rule
still forbids both, so a signer never has to keep old proofs. This
matches ethereum/EIPs#12592.
Builders register a leanXMSS key through the same operation, message,
proof of possession, and gossip topic as validators, naming their index
with `BUILDER_INDEX_FLAG` set. Their keys are stored in lists parallel to
`builders`. Builder indices are reused once a builder has withdrawn, so
a builder's entry starts at its deposit epoch instead of
`FAR_FUTURE_EPOCH`, and a registration signed for an earlier builder at
the same index never applies. Builders register a zero VRF root and must
be active. Key uniqueness spans validators and builders.

`PQKeyRegistration.validator_index` is renamed to `index` and
`PQKeyPossession.validator_pubkey` to `pubkey`, with the SSZ layout
unchanged. This matches ethereum/EIPs#12592.
The `beacon_block` topic now rejects a block with more than
`MAX_PQ_KEY_REGISTRATIONS` registrations before it is processed. The
`pq_key_registration` topic checks key uniqueness before the BLS
signature, cheapest first. The validator guide notes that a validator
and a builder may share a BLS public key, so a key released by one can
be registered by the other with the same proof bytes. This matches
ethereum/EIPs#12592.
An entry holds keys exactly when its leanXMSS key is not all-zero, since
a registration writes a nonzero key and the current epoch together and
onboarding writes the all-zero key and the never-registered marker
together. Registration therefore reads and writes the selected keys and
change epoch lists directly, gates the change delay on the stored key,
and rejects the all-zero key explicitly. Only `is_pq_public_key_in_use`
and `is_valid_pq_possession_proof` remain as helpers. Outcomes are
unchanged except that an all-zero key now fails the explicit check. This
matches ethereum/EIPs#12592.
@jtraglia jtraglia changed the title Add EIP-8450 post-quantum validator key registry Add post-quantum validator key registry (EIP-8450) Oct 29, 2026
Gossip now ignores a builder registration whose deposit epoch is not
finalized in the head state, since that depends on the node's view of
finality, and still rejects one for a builder that has initiated an
exit. Notes state that builder index reuse relies on
`MIN_BUILDER_WITHDRAWABILITY_DELAY` being at least 1, and that reading
the builder's BLS public key after `parent_execution_requests` cannot
diverge from batch signature verification on a valid block. Random
`ssz_static` block bodies are capped at `MAX_PQ_KEY_REGISTRATIONS`
registrations, as for the other block operations.
`PQPublicKeys` becomes `PQKeyMaterial`, since it holds a VRF root as
well as a public key, with the state fields, list types, and the
registration field renamed to match. Names that refer to actual public
keys are unchanged. SSZ layout and hash roots are unchanged. This
matches ethereum/EIPs#12592.
Field comments on `PQKeyRegistration` and `SignedPQKeyRegistration`
become notes above the containers. The registration and possession
domains now pass `GENESIS_FORK_VERSION` explicitly instead of relying on
the default, which replaces the comments and notes saying so.
The four new `BeaconState` fields spell out "post_quantum" instead of
the "pq" abbreviation, since container fields avoid acronyms. Type,
function, and block body names are unchanged. SSZ layout and hash roots
are unchanged.
@kevaundray kevaundray changed the title Add post-quantum validator key registry (EIP-8450) Add post-quantum consensus key registry (EIP-8450) Oct 30, 2026
`xmss_signature_index` becomes `get_xmss_signature_index` and decodes
with `ENDIANNESS`. `process_pq_key_registration` reads and writes the
validator or builder lists directly in each branch instead of aliasing
them. Gossip checks that depend on the node's head state, namely an
index not yet in the head state, a stale previous change epoch, and a
key already in use, now IGNORE instead of REJECT. Notes cover how a key
map is maintained and how a faulty first-seen registration is replaced.

Tests now cover each BLS signature failure separately, a replay after a
later change released the key, an exact registration count limit in
the state transition, two registrations for one index in a block, a
builder registering soon after its deposit, the all-zero key when every
entry holds a key, and a key released by builder index reuse. This
matches ethereum/EIPs#12599.
tcoratger added a commit to tcoratger/ethlambda that referenced this pull request Oct 30, 2026
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 8, 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 added a commit to tcoratger/ethlambda that referenced this pull request Nov 1, 2026
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>
Comment on lines +12 to +15
- [New `ValidatorPQKeyMaterial`](#new-validatorpqkeymaterial)
- [New `ValidatorPQKeyChangeEpochs`](#new-validatorpqkeychangeepochs)
- [New `BuilderPQKeyMaterial`](#new-builderpqkeymaterial)
- [New `BuilderPQKeyChangeEpochs`](#new-builderpqkeychangeepochs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need separate types for validators & builders.

Let's halve these and name them PQKeyMaterial and PQKeyChangeEpochs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err correction, PQKeyMaterial should be PQKeyMaterials.

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

testing CI, actions, tests, testing infra

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants