Skip to content

fix(validator): reject block signing when signing key mismatches genesis validator key#2203

Merged
Mirko-von-Leipzig merged 5 commits into
0xMiden:nextfrom
mmagician:mmagician-claude/validator-key-mismatch
Jun 5, 2026
Merged

fix(validator): reject block signing when signing key mismatches genesis validator key#2203
Mirko-von-Leipzig merged 5 commits into
0xMiden:nextfrom
mmagician:mmagician-claude/validator-key-mismatch

Conversation

@mmagician
Copy link
Copy Markdown
Contributor

Problem

On a real network deployment, the block producer rejects block 1 with InvalidSignature (crates/block-producer/src/block_builder/mod.rs):

if !signature.verify(header.commitment(), header.validator_key()) {
    return Err(BuildBlockError::InvalidSignature);
}

The block header's validator_key is carried forward from the genesis block (miden-protocol ProposedBlock::into_header_and_body copies prev_block_header.validator_key()), and the genesis validator_key is set once, at bootstrap, from the bootstrap signer's public key. If the running validator signs with a different key than the one used to bootstrap genesis, block 1 carries the bootstrap key in its header but is signed by the start key, so verification fails.

This is invisible on the validator side: validate_block signs the header with its own key and never checks that key against the header's validator_key. The validator's own KMS self-check passes (it verifies against its own key), so the only place the mismatch surfaces is the block producer, as an opaque InvalidSignature on block 1.

It passed in integration tests because they use the same key for both bootstrap and signing. The likely production trigger is the silent insecure-key fallback in start (--key.hex has a default and KMS is only used when --key.kms-id is present), so a missing MIDEN_VALIDATOR_KMS_KEY_ID env var causes the validator to silently sign with the insecure default key.

Fix

validate_block now compares the signer's public key against the header's validator_key before signing, and returns a clear BlockValidationError::ValidatorKeyMismatch (naming both keys) instead of handing back a signature the block producer cannot use. The check is placed after the existing prev-commitment check to preserve error ordering.

This is a defense-in-depth + clear-error change. The underlying root cause is operational: the validator must sign with the same key embedded in genesis at bootstrap (same --key.kms-id / hex on both bootstrap and start).

Tests

This path had no coverage: no test exercised the block-producer's post-signing verification for block 1, and TestValidator::new() was itself constructing a misconfigured validator (genesis key != signing key) yet every test passed.

  • block_one_signature_verifies_against_header_key mirrors the block producer's signature.verify(header.commitment(), header.validator_key()) check for block 1. It fails on the pre-fix code (reproducing the bug) and passes after TestValidator::new() is corrected.
  • signing_key_mismatch_rejected asserts a validator with genesis key != signing key rejects block 1 with ValidatorKeyMismatch, both via the gRPC handler and validate_block directly.
  • TestValidator::new() is now correctly configured (one key for both), with a with_keys(genesis_key, signing_key) constructor for the mismatch case.

Verification

  • cargo test -p miden-validator --lib: 15 passed, 1 ignored (needs diesel CLI).
  • cargo clippy --locked -p miden-validator --all-targets --all-features -- -D warnings: clean.
  • Nightly rustfmt + comment reflow (make lint formatters): clean.

Possible follow-up (not in this PR)

Fail fast at validator startup: compare signer.public_key() against the stored chain-tip's validator_key in Validator::serve() and refuse to start with a clear error, so a misconfigured key is caught before serving rather than per-block. Optionally remove the silent insecure-key default in start (or gate it behind an explicit --insecure flag).

🤖 Generated with Claude Code

@mmagician mmagician force-pushed the mmagician-claude/validator-key-mismatch branch 2 times, most recently from 0ed6d1b to d3e7e60 Compare June 4, 2026 16:57
claude added 2 commits June 4, 2026 16:58
…sis validator key

The block's `validator_key` is carried forward from the genesis block, so a
validator started with a different key than the one used to bootstrap genesis
produces a signature that does not verify against `header.validator_key()`. The
block producer then rejects the block with an opaque `InvalidSignature`, which
is what causes block 1 to be rejected on a misconfigured network.

`validate_block` now compares the signer's public key against the header's
`validator_key` before signing and fails with a clear `ValidatorKeyMismatch`
error naming both keys.

Adds two tests in the validator server suite:
- `block_one_signature_verifies_against_header_key` mirrors the block
  producer's post-signing check for block 1 (previously untested, which is why
  the bug slipped through).
- `signing_key_mismatch_rejected` asserts that validating a block with a signer
  whose key differs from the chain's validator key is rejected.

`TestValidator::new()` was itself constructing a misconfigured validator (two
different random keys); it now signs with the genesis key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mmagician mmagician force-pushed the mmagician-claude/validator-key-mismatch branch from d3e7e60 to 628301d Compare June 4, 2026 16:58
@mmagician mmagician marked this pull request as ready for review June 4, 2026 16:59
Copy link
Copy Markdown
Contributor

@bobbinth bobbinth left a comment

Choose a reason for hiding this comment

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

Looks good! Thank you! (I reviewed non-test code)

Comment thread bin/validator/src/block_validation/mod.rs
Comment thread bin/validator/src/block_validation/mod.rs Outdated
Comment thread CHANGELOG.md Outdated
Comment thread bin/validator/src/server/tests.rs Outdated
@Mirko-von-Leipzig Mirko-von-Leipzig enabled auto-merge (squash) June 5, 2026 11:11
auto-merge was automatically disabled June 5, 2026 11:13

Head branch was modified

@Mirko-von-Leipzig Mirko-von-Leipzig merged commit 99b32cb into 0xMiden:next Jun 5, 2026
20 checks passed
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.

4 participants