Milestone 3 · Security
Nuvex maintainers
The admission, in the project's own words
The status paragraph of the protocol README carries a limitation rather than hiding it in an issue tracker. It is the first thing a reader of this post should see, unedited.
“An operator who funds several keys can still choose among those outputs. The cost of each extra key is the configured minimum stake. A minimum of zero does not resist that.”
A node registers a VRF public key, and one key produces exactly one output for one alpha. Nothing stops the same operator from registering a second key, a third and a fourth before a request exists, proving the output under each of them, and submitting whichever output it prefers. Every one of those proofs verifies, because every one of them is a correct proof under a registered key.
Why the program cannot simply draw a winner
The obvious fix is to pick the fulfiller rather than let it pick itself. ADR 0003 explains why that instruction does not exist yet: there is no on-chain list of nodes, so a program has nothing to draw from, and a requester-chosen seed could be ground until a preferred key wins.
“There is no on-chain list of nodes. A program cannot draw a winner from every registered key. A seed chosen by the requester could be ground until a preferred key wins. This milestone does not add that draw.”
A weighted draw is also not a neutral piece of engineering. Weighting stake linearly favours capital and weighting reputation favours incumbents, so the ADR records the choice as an economic policy that has not been made. Reputation stays a plain counter and is not a weight.
- Node 01heartbeat in windowFulfilled
- Node 02heartbeat in windowEligible
- Node 03heartbeat in windowEligible
- Node 04heartbeat staleIneligible
The price of another identity
The only cost of an extra key is the deposit that key has to lock. The registry authority sets that minimum, and zero is an allowed value, which means the configuration can switch the defence off entirely. The threat model says so in the row for a Sybil cluster.
“Each identity must lock min_stake. A minimum of zero does not stop the choice. See ADR 0003.”
Minimum stake
configured
Heartbeat window
configured
Registered before request
required
Slash
available
What still holds
The weakness is a bias on which eligible output reaches the chain first. It is not a licence to invent an output, and the checks around it are implemented and tested.
- A proof is bound to one request: the alpha is built from the request account, the job type and the input, and is never read from instruction data.
- A node account must have been created at a slot strictly earlier than the request, so a key cannot be added after seeing one.
- The VRF public key is set at registration and update_node cannot replace it.
- The capability mask must include the VRF bit.
- A forged proof is rejected by the verifier, and the tests cover a late key, a missing capability bit and a forged proof.
The verification ADR draws the line in one sentence: uniqueness is something a consumer may rely on, and unbiasedness against a multi-key operator is not.
“A consumer cannot rely on the output being unbiased against an operator who registered many keys before the request.”
What would close it
Closing the gap needs an on-chain index of nodes to draw from, a weight that someone has to justify, and a seed that a requester cannot grind. None of those three exist in this tree, so the honest position is to publish the limitation next to the capability instead of describing the randomness as unbiased.
Nothing is deployed to mainnet while that stands: scripts/deploy-mainnet.sh exits before any transaction, and no independent audit has been commissioned. Milestone 4 copies the assigned node, stake and heartbeat from each request into a read model so they can be inspected without a cluster subscription. The account is still the result.

