ZK Circuits
The zero-knowledge circuits that power Senddy's privacy — Shield, Spend, and Association.
Overview
Senddy's zero-knowledge proofs are built with Noir, a domain-specific language for writing ZK circuits. Proofs are generated using the UltraHonk proving system and compiled to WebAssembly for client-side execution.
Universal setup
UltraHonk uses a universal trusted setup, meaning the same setup ceremony works for any circuit. This is a significant advantage:
- No per-circuit ceremony — Adding new circuits or upgrading existing ones doesn't require a new trusted setup
- Battle-tested parameters — The universal setup has been used across many production systems
- Upgradeable — The system can evolve without re-running ceremonies or asking users to trust new setups
Shield Circuit
The shield circuit converts public deposits into private notes.
Purpose: Prove that a deposit was correctly converted into private notes without revealing the note details.
What it proves:
- Total output value equals the deposit amount (value conservation)
- Note commitments are correctly computed (Poseidon2 hash of value, owner, randomness)
- Output notes belong to the depositor
Spend Circuit
The spend circuit transfers or withdraws private notes.
Purpose: Prove that the spender owns the input notes, they exist in the Merkle tree, and the input/output values balance.
What it proves:
- Each input note exists in the Merkle tree (membership proof via Merkle path)
- The spender knows the secret key for each input note
- Nullifiers are correctly derived from the commitment and secret key
- Total input value equals total output value plus fees and withdrawal (conservation)
- Supports optional withdrawal to a public Ethereum address
- Supports optional relayer fee for gasless transactions
Association Circuit
The association circuit enables optional, privacy-preserving compliance. It complements Senddy's deposit screening (which blocks sanctioned addresses) by giving legitimate users a way to prove their funds are clean after they've been in the private pool.
Purpose: Prove that a user's funds trace back to approved deposit sources without revealing their full transaction history.
What it proves:
- The note's nullifier is correctly derived from the user's secrets
- The source deposit exists in an Association Set Provider's (ASP) approved set (Merkle membership proof)
- A valid chain of custody links the deposit to the user's current note
Key properties:
- Voluntary — Association proofs are never required for basic usage
- Privacy-preserving — The proof reveals nothing about which specific deposit funded the note, only that it was in the approved set
- Multi-ASP support — Different ASPs can maintain different compliance standards for different jurisdictions
See Compliance for the full compliance model.
Proof Performance
| Metric | Value |
|---|---|
| Proving time (browser) | 2-5 seconds |
| Proving time (mobile) | 3-7 seconds |
| Proof size | ~8 KB |
| Verification (attestor) | < 1 second |
Proofs are generated client-side using WebAssembly. The UltraHonk proving system is optimized for browser performance.