Found while closing #697. The wallet now recomputes the coordinator draw and re-derives the beacon from its own node (fa8ed99c0, 6dfefc239), which closes the half of the trust gap that can be closed. The roster is the half that remains.
The gap
verify_election(beacon, epoch, roster, n, claimed) proves the seat list follows from the beacon and roster published beside it. The beacon is now pinned to the chain — it is SHA256(domain ‖ epoch ‖ block_hash_at(anchor_height)) and the wallet derives the anchor height from the epoch, so a publisher cannot invent one.
The roster has no such anchor. It arrives from the same place as the result, so a node that trims it — omitting qualified peers to improve its own odds, or to seat itself outright — produces an election that verifies perfectly. "No self-nomination" holds against the draw; it does not hold against control of the input set.
What closing it needs
The qualified set has to come from consensus rather than from whoever answered. The mesh already carries node capabilities and an elder roster, and epoch::canonical_roster already canonicalises whatever it is given, so the missing piece is agreement on membership rather than the derivation.
Worth deciding as part of it:
- Which membership signal is authoritative — the elder set, capability-verified coordinators, or a purpose-built roster checkpoint.
- Whether a wallet can check the roster independently at all, or whether it can only detect disagreement between several nodes' published views. The latter is cheaper and might be enough: a wallet that asks two nodes and compares rosters catches a unilateral liar without needing consensus membership.
Related: #708 makes this moot in practice today, since the live roster has one member.
Found while closing #697. The wallet now recomputes the coordinator draw and re-derives the beacon from its own node (
fa8ed99c0,6dfefc239), which closes the half of the trust gap that can be closed. The roster is the half that remains.The gap
verify_election(beacon, epoch, roster, n, claimed)proves the seat list follows from the beacon and roster published beside it. The beacon is now pinned to the chain — it isSHA256(domain ‖ epoch ‖ block_hash_at(anchor_height))and the wallet derives the anchor height from the epoch, so a publisher cannot invent one.The roster has no such anchor. It arrives from the same place as the result, so a node that trims it — omitting qualified peers to improve its own odds, or to seat itself outright — produces an election that verifies perfectly. "No self-nomination" holds against the draw; it does not hold against control of the input set.
What closing it needs
The qualified set has to come from consensus rather than from whoever answered. The mesh already carries node capabilities and an elder roster, and
epoch::canonical_rosteralready canonicalises whatever it is given, so the missing piece is agreement on membership rather than the derivation.Worth deciding as part of it:
Related: #708 makes this moot in practice today, since the live roster has one member.