Registration circuit
What the registration proof shows about a new identity commitment, its eleven public values, how it shares the outgoing viewing key with the guardian nodes, and what the register instruction checks around it.
The registration proof is made once per account. It shows that a new identity commitment is built correctly from non-zero keys, has not been registered before, and that its outgoing viewing key has been split correctly into three shares for the guardian nodes.
What it proves
Your identity commitment (sometimes called the user commitment) is a Poseidon fold of eight slot leaves, each a hash of one value and its own blinder; see your identity commitment. The diagram below shows the eight slots.
The circuit takes the four keys, the platform wallet address, the hash of your ML-KEM-768 public key and the eight blinders as private inputs. The chart below shows what it does with them.
flowchart LR K["Keys and blinders"] --> I["Identity commitment"] I --> N["Absent from registration tree"] K --> S["Three key shares"] S --> R["Sharing root"] N --> F["Fold of 11 values"] R --> F
The checks are these.
The keys are non-zero
The spending key, the nullifier key, the outgoing viewing key and the proof-authorisation key each come with a second private input that must be their inverse. An inverse exists only for a non-zero value, so the product being one proves the key is not zero.
The blinders of slots 4 to 7 are non-zero
The same inverse method forces the blinders of slots 4 to 7 to be non-zero. Every leaf has its own blinder so that a party who learns one slot's value and blinder cannot confirm a guess about any other slot. Slots 4 to 7 hold values that are public or zero (the platform wallet address, the key hash and two zeros), so with a zero blinder their leaves could be confirmed by hashing a guess. Slots 0 to 3 hold the keys, which are already forced non-zero.
The identity commitment is built correctly
The circuit hashes each slot's value with its blinder under a domain specific to that slot, then hashes the eight leaves into the identity commitment. Slot 4 binds the platform wallet address, slot 6 binds the hash of the ML-KEM-768 public key, and slots 5 (retired) and 7 (reserved) commit to zero. Because the platform wallet address and the key hash are also public values, the identity commitment is tied to the wallet that signs the registration and to the key the data endorser checks.
The outgoing viewing key is shared two-of-three
The circuit builds a line through the outgoing viewing key with a slope proven non-zero, and a second line through a blinding value, also with a non-zero slope. It evaluates both lines at x = 1, 2 and 3, hashes each pair into a share leaf tied to its x-coordinate, and hashes the three leaves into a sharing root. Any two shares rebuild the key; one alone reveals nothing about it. A zero slope would make every share equal the key itself, so the non-zero check is what keeps a single guardian node from reading it. See secret sharing.
The identity commitment is not yet registered
The registration tree is an indexed tree of every identity commitment the forester has inserted. The circuit proves the new identity commitment falls strictly between a leaf of that tree and the leaf's successor at the referenced root, the same gap argument the nullifier-freshness circuit uses. This stops an identity from being registered twice once the first registration has reached the tree.
The circuit is RegistrationCircuit() and has 11,112 non-linear constraints.
| Entry | circuits/zk/registration.circom |
|---|---|
| Instantiation | RegistrationCircuit() |
| DEDUP_HIGH_ARITY | 8 |
| DEDUP_HIGH_LEVELS | 1 |
| DEDUP_LOW_ARITY | 8 |
| DEDUP_LOW_LEVELS | 11 |
| IDENTITY_SLOTS | 8 |
| SHAMIR_THRESHOLD | 2 |
| SHAMIR_TOTAL_NODES | 3 |
circuits/zk/circom-manifest.json.The eleven public values
The circuit's public input is a Poseidon fold of eleven values, and the register handler rebuilds it from its own sources.
Sharing threshold and node count (slots 0 and 1). The handler reads them from the guardian cluster's account. The circuit fixes them as the constants 2 and 3, so the proof fails unless the cluster in the payload is a two-of-three cluster.
Identity commitment and sharing root (slots 2 and 3). Both come from the payload. The handler requires each to be a canonical field element, below the field modulus, before using it.
Referenced registration-tree root (slot 4). The payload names it by sequence number, and the handler reads it from the registration staging buffer's ring of recent roots.
Platform wallet address (slots 5 and 6). The two halves come from the address of the account that signed as the platform wallet (the code's bound wallet), never from the payload. A proof made for one wallet cannot register another.
Hash of the ML-KEM-768 public key (slots 7 and 8). The two halves come from the payload. The data endorser checks the hash against the key before it signs.
Context digest (slots 9 and 10). The handler computes Keccak-256 over the payload up to the signature and the addresses of the registration's six accounts, the fee payer's first, and folds its two halves.
What the handler checks around it
register runs these checks in order. Nothing is recorded unless all of them pass.
Check the payload and the signers
The identity commitment and the sharing root must be canonical field elements. The fee payer and the platform wallet must both sign.
Resolve the referenced root
The referenced root must be an active root of the registration staging buffer, which the code calls the turnstile. It keeps the last 120 roots of the registration tree, and an older root is refused as stale.
Resolve the cluster and compute the digest
The handler resolves the guardian cluster named in the payload and refuses it unless its threshold and node count are the 2 of 3 the circuit fixes. It then computes the context digest over the payload up to the signature and folds the eleven public values.
Verify the combined signature
The guardian cluster's nodes and the data endorser sign the digest together, and the handler verifies their combined signature. The guardian nodes sign only after each has checked its share of the outgoing viewing key; the data endorser signs only after checking the key hash and the registration memo digest.
Verify the proof
The handler verifies the proof against the fold, under the 2-of-3 registration key compiled into the program.
Stage and append
The identity commitment is staged in the registration staging buffer, which refuses one already staged, and appended to the pool's identity tree. From then on it can send and receive notes.
What it does not prove
A few facts sit outside the circuit.
The platform wallet halves are a real address. The circuit does not range-check them. The handler fills them from a signing account, so they are always a real 32-byte address.
The ML-KEM-768 key behind the hash is the one published. The circuit sees only the hash. The data endorser checks that the key it is given hashes to the value in the payload, and that the registration memo digest matches that key.
An identity commitment staged but not yet in the registration tree is new. The proof's non-membership check covers only identities the forester has inserted. The registration staging buffer refuses a repeat of any identity it still holds.
Each guardian node received the share its leaf commits to. The circuit commits to the shares but cannot deliver them. Each guardian node checks its own share before it signs.
The proof-authorisation key is ever used. It is committed and forced non-zero, and no current circuit uses it.
The sharing root is public and each share leaf is a hiding commitment, so the proof reveals nothing about the outgoing viewing key.
Related
Last updated
Nullifier freshness circuit
The second proof in every value transaction, which shows the three nullifiers are absent from the nullifier tree at a recent root, who makes it, and what it leaves to the staging buffer.
Batch append circuit
The forester's proof that twelve staged values were inserted into an indexed tree correctly, what the program supplies to it, and what happens to the pool while no forester runs.