mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
Red review of the oracle found the bytes unbreakable and the set-plane rule around them incomplete. This closes it. HIGH — one node, one key. Register enforced one-key-one-node but not the converse: a slice could name one node twice under two keys, each with a genuine proof, so possession did not catch it and one operator held several signer indices and several shares of the weight under one identity. FlattenValidatorSet's map input made that unrepresentable; a slice does not. A byNode set beside byKey refuses it (ErrDuplicateNode), in the same pass, so the set is a set on both axes — the rule weighted_merkle already states and registration.go had half of. HIGH — one canonical order, on every build. PublicKeyBytes, the sort key, was the UNCOMPRESSED encoding, which the crypto library returns as 96 bytes under blst and 48 under CIRCL — so the same set sorted two ways, and two nodes on different builds indexed the signer bits differently. It is the COMPRESSED encoding now, in both Register and the local FlattenValidatorSet, so the order is the same on every build. Under the canonical (purego) image this still matches upstream; under cgo it is now deterministic where upstream is not. The crypto root cause is upstream and live on the warp path — escalated separately, not fixed here. Also: key uniqueness now keys on the compressed bytes decoded from the point, not the caller's input buffer read three times (a caller could mutate it between the verify and the map insert); a zero-weight validator is refused (a signer that raises the count but not the weight, the phantom BuildWeightedValidatorSet already refuses); cert-store eviction drops the file the evicted slot names, not whatever the map points at, and unmaps a decision only if the map still names the removed file; and a canonicalCommitter test block exercises the cert-store re-key in the regime where the inner decision differs from the outer envelope — the case every prior test degraded past. Node width is 20 bytes, frozen in vectors/pop.json. Nothing here runs on mainnet: node pins a consensus version without these files, and Register has no non-test callers. A validator set that shares a key across node ids is now refused — the intended behaviour, and the seam with the P-chain step still to come. Co-authored-by: Hanzo Dev <[email protected]>