Getting started
How it works
A proof stands on three facts the Zcash chain already keeps. Vouch proves them about your notes without showing the notes, binds the proof to one statement, and carries the verdict wherever it is needed.
On this page
#The life of a proof
Four parties, and only files between them. The verifier writes a request (a link). The holder’s prover reads public chain data and the holder’s seed, and writes a proof. The verifier checks the proof against public roots. Nothing is sent to a server that the holder does not choose to send, and nothing on chain changes.
#Three facts the chain keeps
| Chain data | What it proves | In the proof |
|---|---|---|
| Note-commitment tree | A note existed at a block: the tree only grows. | Each note is a leaf under nc_root at nc_height. |
| Nullifier set | A note has not been spent: spending reveals its nullifier. | Each note’s nullifier is absent from the set at nf_root. |
| Spend authority | Who controls the notes. | A RedPallas signature under a re-randomisation of the notes’ key. |
#The proof
The proof is the Ironwood delegation circuit from Valar Group’s voting-circuits 0.12.1, the circuit the NU7 poll used to let holders delegate voting weight without revealing their notes. Upstream it proves only that the notes hold some value. Vouch adds one public input, min_ballots, and one constraint, so it proves the notes hold at least a public minimum. Halo2 over the Pasta curves (K = 12), so there is no trusted setup.
- Up to five notes per proof; value counts in units of 0.125 ZEC; unused slots are filled, so the count is hidden too.
- The statement (claim, audience, expiry, binding, heights, period) is signed with the notes’ spend authority, so no field can be edited after proving.
- A viewing key alone cannot make a proof: it can find the notes but cannot sign.
Details, with the binding and public-input diagrams, are in The circuit.
#Unmoved since block H
The tree root and the spent-set root are independent public inputs. Taking the tree at an earlier block H₀ and the spent set at a later block H₁ proves the notes were already in the chain at H₀ and have not moved by H₁. No circuit change was needed: only the verifier’s rule that tied the two heights together was relaxed, with a check that nc_height ≤ height.
The prover refuses a note created after H₀ with a clear error before any proving starts; the verifier answers NotDormantLongEnough when a proof’s H₀ is later than the verifier requires.
#Scopes and reuse
In format version 2 the circuit’s round id is the scope hash, so each note’s tag is stable within a scope and unlinkable across scopes. A verifier passes its registry of accepted tags to the verifier; a repeat is AlreadyUsed, judged only after the proof itself verifies. Reserves batches use the same tags to refuse a note counted twice across members.
#Exit certificates
Circuit 2 adds a reveal flag and the notes’ real nullifiers as public inputs. With the flag on, the certificate names the exact notes; when the deposit lands, the rail reads its nullifiers from the chain and checks every one was certified. Circuit 2 has its own verifying key; circuit 1 is byte-for-byte unchanged. See Exit certificates.
#Verification
One Rust function, pof-verify::verify, runs six checks cheapest first and returns a typed verdict. It runs as a CLI, as WASM in the browser and inside the attestor, and CI checks all three agree on every test vector. See The verification pipeline.
#Getting to Solana
A Solana program cannot verify Halo2 over Pallas or read Zcash state. So an attestor runs the same verifier and signs the verdict with Ed25519, and the programs check that signature through instruction introspection:
| Program | Does |
|---|---|
| pof-gate | Allowlisted, k-of-n attestations become one receipt per proof, bound to the holder’s wallet. |
| pof-credit | Opens a credit line from a receipt; draws only while the line’s latest proof is fresh. |
| pof-reserve | An attested reserves batch becomes a feed; its token mints only within the proven total, only while fresh. |
See Solana programs for every instruction, account and error, and Credit lines for the flow.