Double-spend prevention
- Unique nullifiers. Each note maps to a single nullifier derived from a secret key and spend nonce.
- On-chain flags. For every observed nullifier, a
NullifierFlagPDA is created and stored permanently. - Strict rejection. Reuse results in a revert.
Merkle tree integrity
- Root history. Only roots present in the history buffer are accepted, preventing arbitrary tree replacement.
- Atomic operations. Commitments and root updates occur in one transaction.
- Deterministic hashing. VoidHash ensures a unique root for any set of leaves.
Proof verification
- Groth16 enforcement. All join–split transitions must carry a valid proof or fail with
InvalidProof. - Input validation. Public inputs are checked for length, encoding, and consistency.
- All-or-nothing. Any invalid component triggers a full revert.
Pool solvency
- Tracked totals. The program maintains counters for total deposited and withdrawn.
- Solvency invariant. Before any withdrawal, the program enforces
pool_lamports ≥ deposited - withdrawn.
Relayer authorization
- Authorized key. Only the configured relayer key may execute prepared withdrawals.
- Economic incentives. The fee model compensates relayers for risk and gas.