fix: reject duplicate validator keys, canonicalize digest order - #450
Open
samlaf wants to merge 1 commit into
Open
fix: reject duplicate validator keys, canonicalize digest order#450samlaf wants to merge 1 commit into
samlaf wants to merge 1 commit into
Conversation
Two ways a genesis file could quietly mean something other than what it says. A repeated node public key silently shrank the validator set. The genesis committee is inserted into consensus state keyed by node key (`get_initial_state` -> `set_account`) and consensus reads the committee back out of that map, so two entries naming one key launched a network with one fewer validator than the file listed, computing quorum over the smaller set, while startup logged the file's count. Nothing rejected it at load. A repeated consensus key is now rejected for the same reason — it is one signing identity under two names. Repeated withdrawal_credentials stay legal: one operator may run several validators and be paid at one address. Keys are compared as decoded bytes, so the same key written two ways (`0x` prefix, upper case) is still caught. Hex-decodability is now required at load rather than deferred to committee construction, since neither that comparison nor the ordering below is defined without it. Validator order was part of chain identity. `config_digest` hashes the SSZ encoding of the validator list, which follows stored order, so two files naming the same set in different orders derived different chain domains and their nodes could not authenticate each other as peers. The digest now sorts by node key before hashing. Sorting rather than rejecting unsorted input means no emitter has to be trusted to have sorted, an obligation every future writer of a genesis file would otherwise carry. That is a semantic change to a value deriving every consensus signing domain, but only for files that were unsorted. Every genesis Summit's tooling emits is already in node-key order, and the frozen digest vector for example_genesis.toml is unchanged. Bumping the domain tag would instead change every digest, including already-canonical ones, forcing a coordinated restart on running networks for no gain, so the tag stays at -v1. `config_digest` panics on a validator key that is not hex, matching `genesis_hash` directly above it: validate rejects it at load, and a digest derived from a key we could not read would silently place a node in a chain domain of its own. Left for a follow-up: the digest still hashes the key, hash, and address fields as their hex *text*, so two files that parse to identical values but spell them differently — `0x` prefix present or not, hex case — still derive different chain domains. That is the same class of accident this commit removes for ordering, and it is the reason a genesis file cannot yet be treated as mere transport for its values. The fix is to digest the decoded bytes as fixed-size SSZ fields, which changes every digest and so requires a GENESIS_CONFIG_DOMAIN_TAG bump — free now, a hard fork once any network pins a digest. Worth doing; kept out of this commit so this change stays tag-neutral. Tests: duplicate node key, the same key respelled, duplicate consensus key, repeated withdrawal credentials accepted, non-hex keys rejected, digest unchanged under reversal, and the frozen vector.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human Summary
2 changes related to processing the validator entries in the genesis file:
LLM Summary
Two ways a genesis file could quietly mean something other than what it says.
A repeated node public key silently shrank the validator set. The genesis committee is inserted into consensus state keyed by node key (
get_initial_state->set_account) and consensus reads the committee back out of that map, so two entries naming one key launched a network with one fewer validator than the file listed, computing quorum over the smaller set, while startup logged the file's count. Nothing rejected it at load. A repeated consensus key is now rejected for the same reason — it is one signing identity under two names. Repeated withdrawal_credentials stay legal: one operator may run several validators and be paid at one address.Keys are compared as decoded bytes, so the same key written two ways (
0xprefix, upper case) is still caught. Hex-decodability is now required at load rather than deferred to committee construction, since neither that comparison nor the ordering below is defined without it.Validator order was part of chain identity.
config_digesthashes the SSZ encoding of the validator list, which follows stored order, so two files naming the same set in different orders derived different chain domains and their nodes could not authenticate each other as peers. The digest now sorts by node key before hashing. Sorting rather than rejecting unsorted input means no emitter has to be trusted to have sorted, an obligation every future writer of a genesis file would otherwise carry.That is a semantic change to a value deriving every consensus signing domain, but only for files that were unsorted. Every genesis Summit's tooling emits is already in node-key order, and the frozen digest vector for example_genesis.toml is unchanged. Bumping the domain tag would instead change every digest, including already-canonical ones, forcing a coordinated restart on running networks for no gain, so the tag stays at -v1.
config_digestpanics on a validator key that is not hex, matchinggenesis_hashdirectly above it: validate rejects it at load, and a digest derived from a key we could not read would silently place a node in a chain domain of its own.Suggested Followup (but breaking change)
The digest still hashes the key, hash, and address fields as their hex text, so two files that parse to identical values but spell them differently —
0xprefix present or not, hex case — still derive different chain domains. That is the same class of accident this commit removes for ordering, and it is the reason a genesis file cannot yet be treated as mere transport for its values. The fix is to digest the decoded bytes as fixed-size SSZ fields, which changes every digest and so requires a GENESIS_CONFIG_DOMAIN_TAG bump — free now, a hard fork once any network pins a digest. Worth doing; kept out of this commit so this change stays tag-neutral.