Architecture
The circuit
Vouch’s proofs are Halo2 proofs of the Ironwood delegation circuit, with two small, marked modifications: a threshold for every proof, and revealed nullifiers for exit certificates.
On this page
#The base circuit
The evidence in a .pof is a Halo2 proof (IPA commitments over the Pasta curves, K = 12, no trusted setup) of the delegation circuit from voting-circuits 0.12.1 by Valar Group, driven the way zcash_voting (Chainapsis) drives it. Upstream, holders used it to delegate voting weight in the NU7 poll. For up to five notes it proves:
- each note is a leaf of the Ironwood note-commitment tree at
nc_root; - each note is unspent: its real nullifier is not in the spent-nullifier IMT at
nf_imt_root, and the nullifier stays private; - the prover holds the keys the notes are addressed to:
rkis a re-randomisation of the notes’ak; - each note yields a governance nullifier (a tag) under the domain
dom, derived fromvote_round_id.
#Modification 1: the threshold
Upstream the circuit hides the value sum and enforces only that the notes are worth at least one ballot (0.125 ZEC). Vouch adds a 15th public input, min_ballots (offset 14), and one range constraint:
num_ballots − min_ballots ∈ [0, 2³⁰) num_ballots = Σ note values / 12,500,000 (committed, not revealed)
min_ballots = claim.zatoshi / 12,500,000 (derived by the verifier from the claim)So the proof says “at least min_ballots × 0.125 ZEC” and nothing more. The test vouch_threshold in the vendored crate checks it, together with the upstream suite.
#Modification 2: revealed nullifiers (circuit 2)
Exit certificates need the rail to match a deposit to the certified notes. Circuit 2 adds a boolean reveal (offset 15) and five revealed_nf_i (offsets 16–20), with two gates:
revealed_nf_i − reveal · real_nf_i = 0 for each slot i
reveal · (1 − reveal) = 0 reveal is 0 or 1With reveal = 1 the five real nullifiers are public; with 0 they are forced to zero. It uses the same configuration, so it still fits K = 12, but it is a different circuit with its own verifying key (vk-reveal.bin). Circuit 1’s verifying key and parameters are byte-identical to before, so every existing proof still verifies. The test vouch_reveal covers reveal on and off, a forged revealed nullifier, and a flag of 2.
#Public inputs
The file carries only what the verifier cannot derive: nf_signed, rk, cmx_new, van_comm, gov_null_1..5 (nine 32-byte values; rk is one compressed point that fills two circuit inputs), and for circuit 2 reveal, revealed_nf_1..5. pof-zk::instance_for assembles the full instance from those, the statement and the anchor.
#What binds the file to the proof
statement = blake2b-256[personal "vouch-stmt-v1"](head)
scope = blake2b-256[personal "vouch-scope-v2"](circuit ‖ audience ‖ epoch_le) (version 2)
round_id = statement (v1) or scope (v2), bits 254..255 cleared, as a Pallas base element
dom = Poseidon("governance authorization", round_id)
min_units = claim.zatoshi / 12_500_000
message = blake2b-256[personal "vouch-sig-v1"](statement ‖ public_inputs)
signature = RedPallas SpendAuth over message, under rk
subject = blake2b-256[personal "vouch-subject-v1"](statement) (the proof id)#One note, one slot
A finding the verifier closes
The circuit checks each of the five slots on its own, so on its own it would accept one note placed in several slots and count its value more than once. Upstream, the vote chain refuses a repeated governance nullifier. Vouch has no chain, so pof-zk refuses any proof whose five tags are not all distinct, before Halo2 runs; a repeated note always produces the same tag. A real-proof test checks that a note counted five times is refused.
#Keys and parameters
| File | What it is | Guarded by |
|---|---|---|
pof-zk/keys/params-k12.bin | The IPA parameters for K = 12. | embedded_vk_matches_keygen |
pof-zk/keys/vk-delegation.bin | Circuit 1’s verifying key. | embedded_vk_matches_keygen |
pof-zk/keys/vk-reveal.bin | Circuit 2’s verifying key. | embedded_vk_matches_keygen |