Off-chain proof construction
1. Note selection
The client chooses input notes whose total value covers the intended withdrawal and fees.
2. Merkle proofs
For each note, the client obtains a Merkle path proving inclusion under a specific root.
3. Nullifiers
The client computes one nullifier per input using:
VoidHash(receiverViewPriv, spendNonce)
4. Change outputs
Any excess value is allocated to new output notes with new commitments.
5. Groth16 proof
A witness is built and a Groth16 proof is generated that all circuit constraints hold.
On-chain validation
- Proof verification. The program verifies the Groth16 proof and checks the layout of public inputs.
- Nullifier checks. Every input nullifier is checked against
NullifierFlagPDAs; reuse is rejected. - Amount validation. The equality is enforced: Σ inputs + extAmountIn = Σ outputs + publicAmount.
- Solvency check. The pool must hold at least
publicAmountlamports. - Fee calculation. A relayer fee of 0.15% plus a fixed gas buffer is computed.
State changes
- Nullifier flags. One
NullifierFlagPDA is created per consumed note. - Tree updates. Output commitments are appended to the tree, changing the root.
- Prepared transaction. A
PreparedTxPDA is created, capturing recipient, public amount, fee, and expiry.