Skip to content

fix(validator): reject a manifest that reuses one key for both signing roles#1185

Merged
tcoratger merged 2 commits into
leanEthereum:mainfrom
adust09:fix/validator-dual-key-separation
Jul 6, 2026
Merged

fix(validator): reject a manifest that reuses one key for both signing roles#1185
tcoratger merged 2 commits into
leanEthereum:mainfrom
adust09:fix/validator-dual-key-separation

Conversation

@adust09

@adust09 adust09 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

ValidatorEntry documents that the attestation and proposal keys are separate "so one validator can sign both within the same slot without OTS conflict" — but nothing enforced it. The registry loader assigned keys without comparing them, and from_yaml never compared the two keys it loaded. A node whose manifest carried the same key in both fields signed a block proposal and an attestation for the same slot with the same stateful XMSS key — the one-time-signature state reuse the separation exists to prevent — and nothing surfaced it.

With identical key material in both fields, each signing path advanced its own field's copy from the pre-advance state, so the two signatures consumed overlapping OTS state (a key-compromise-class failure for hash-based schemes). Both loads succeeded and both signatures verified individually, so the misconfiguration stayed invisible.

Fix

Reject the misconfiguration at load time by comparing the manifest's two public keys, which leaves the secret bytes untouched and fires before any key file is decoded. This turns the docstring invariant into a client-checkable rule and discharges the formal model's WellFormed distinctness assumption at construction — the same shape as #1179 did for the store invariants.

Real manifests are unaffected: production key generation derives the attestation and proposal keypairs from two independent key_gen calls, so their public keys always differ.

Testing

  • Added test_same_key_for_both_roles_raises, which asserts the full error message and writes no key files, proving the check fires before any decode.
  • Updated the shared manifest-entry test helper so its two public keys differ, as the loader now requires.
  • uv run pytest tests/node/validator/ — 78 passed.
  • ruff lint + format, ty typecheck, codespell all pass.

Closes #1184

adust09 added 2 commits July 7, 2026 00:32
…g roles

ValidatorEntry documents that the attestation and proposal keys must be
separate so a validator can sign both a proposal and an attestation in the
same slot without one-time-signature state reuse. Nothing enforced it: the
registry loader assigned keys without comparing them, so a manifest carrying
the same key in both fields loaded silently. Both signatures then advanced
their own copy of the shared key from the pre-advance state, consuming
overlapping XMSS one-time state — a key-compromise-class failure that stayed
invisible because both loads and both signatures verify individually.

Reject the misconfiguration at load time by comparing the manifest's two
public keys, which leaves the secret bytes untouched. This turns the
docstring invariant into a client-checkable rule and discharges the formal
model's distinctness assumption at construction.

Closes leanEthereum#1184
The one-validator fixture manifest reused one placeholder public key for both
roles. The registry loader now rejects that, so make the two keys differ, as
a real manifest does.
@tcoratger tcoratger merged commit ae104fa into leanEthereum:main Jul 6, 2026
14 checks passed
@adust09 adust09 deleted the fix/validator-dual-key-separation branch July 7, 2026 01:07
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.

Validator dual-key separation is maintained only by convention (a same-key manifest silently reuses XMSS OTS state)

2 participants