Skip to content

fix(lean): reject validator keys that share a key or mismatch genesis - #1593

Open
dimka90 wants to merge 1 commit into
ReamLabs:masterfrom
dimka90:fix/lean-validator-keys-match-genesis
Open

dimka90 wants to merge 1 commit into
ReamLabs:masterfrom
dimka90:fix/lean-validator-keys-match-genesis

Conversation

@dimka90

@dimka90 dimka90 commented Oct 13, 2026

Copy link
Copy Markdown

What was wrong?

Bug: load_validator_registry (crates/common/validator/lean/src/registry.rs) checks only that a validator's attester and proposer entries share the same index. It never checks the public keys:

  • A manifest that gives the same key to both roles loads without error.
  • Keys that don't match the genesis validators also load without error, and the node signs with them. That covers a swapped attester/proposer file, a wrong index, or a key from another network.

Problem:

  • Same key for both roles: leanSpec forbids this. Its ValidatorRegistry raises ValueError when the attestation and proposal keys are equal (src/lean_spec/node/validator/registry.py:217-226). XMSS is a stateful one-time signature, and a validator signs a proposal and an attestation in the same slot, so sharing one key across both roles reuses one-time state and breaks it.
  • Keys that don't match genesis: every signature the node makes fails verification at its peers. Nothing reports this locally, so the validator silently misses all its votes and blocks.

How was it fixed?

  • load_validator_registry now takes the genesis validators. Before loading any secret key, it fails when:

    • the attestation and proposal public keys are equal (the error is worded after leanSpec's);
    • the validator index is outside the genesis registry;
    • either public key differs from genesis_validators[index].
  • main.rs passes config.network.genesis_validators. Startup already calls .expect(...) on the result, so a bad manifest stops the node with a clear message.

  • New table-driven test test_load_validator_registry_checks_keys_against_genesis:

    • matching keys load;
    • each of these is rejected: the same key for both roles, swapped roles, a proposal key not in genesis, and an index past the registry.

    On current master, the "same key for both roles" case loads without error.

Limit: this compares the manifest's public keys with genesis. It cannot prove that the secret key file matches, because the leanSig backend used by the devnet5 build cannot derive a public key from a secret key. The leanVM backends can (XmssSecretKey::public_key()), so that check could follow in a separate PR.

Checks run:

  • cargo test -p ream-validator-lean --no-default-features --features devnet5 passes (13 s).
  • cargo clippy -p ream-validator-lean --all-targets --no-default-features --features devnet5 -- --deny warnings passes.
  • cargo +nightly fmt --all -- --check passes.
  • cargo check passes for the ream binary (devnet5) and for the crate with optimized-leanvm-b.

I did not run the full workspace make test: only the lean validator crate and main.rs changed.

Related, not changed here: verify_checkpoint_state does not compare a checkpoint state's validators with genesis.

To-Do

load_validator_registry accepted any manifest whose attester and proposer
entries shared an index. A manifest that assigned one key to both roles,
or keys that differ from the genesis registry, loaded without error, and
the node signed with them.

leanSpec's ValidatorRegistry rejects a manifest that gives the same key to
both roles, because XMSS is a stateful one-time signature and a validator
signs a proposal and an attestation in the same slot. Each validator's
keys must also equal its genesis entry, or every signature fails
verification at peers.

load_validator_registry now takes the genesis validators and fails on a
shared key, a key that differs from genesis, or an index outside the
genesis registry.

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.

1 participant