Zero-Knowledge Circuit Architecture
Privacy Cash uses circom 2.0.0 circuits to enable private transactions on Solana. The circuit implementation follows a Universal JoinSplit design that supports flexible multi-input, multi-output transactions.Circuit Overview
The main transaction circuit is defined incircuits/transaction.circom and instantiated with:
levels = 26: Merkle tree depth (supports 33,554,432 transactions, matching Light Protocol v1)nIns = 2: Number of input UTXOsnOuts = 2: Number of output UTXOs
root: Merkle tree root for membership proofpublicAmount: External amount (deposit/withdrawal) minus feeextDataHash: Hash of external transaction datainputNullifier[nIns]: Nullifiers to prevent double-spendingoutputCommitment[nOuts]: Output commitments
UTXO Structure
Each UTXO (Unspent Transaction Output) contains:hash(amount, pubKey, blinding, mintAddress)
Nullifier: hash(commitment, merklePath, sign(privKey, commitment, merklePath))
Input Verification
For each input UTXO, the circuit verifies:1. Keypair Derivation
2. Commitment Calculation
3. Signature Generation
4. Nullifier Computation
- Only the owner can create a valid nullifier (requires private key)
- Each UTXO can only be spent once (nullifier is deterministic)
- Nullifiers cannot be linked to commitments without the private key
5. Merkle Proof Verification
Output Verification
For each output UTXO:1. Commitment Generation
2. Amount Range Check
Invariants and Safety Checks
1. Balance Conservation
2. Unique Nullifiers
3. External Data Binding
extDataHash is included in the witness and cannot be changed without invalidating the proof.
Merkle Proof Circuit
TheMerkleProof template verifies membership in a Poseidon-based Merkle tree:
- Convert
pathIndicesto binary representation - For each level, use the index bit to determine left/right positioning
- Hash adjacent nodes using Poseidon(2)
- Climb the tree until reaching the root
Switcher component swaps left and right inputs based on the path index bit, ensuring the correct hash order.
Security Considerations
Input Validation
- No range check on inputs: Input amounts are not range-checked because they must be valid outputs from previous transactions or zero-value dummy UTXOs.
- Output range check: Output amounts are checked to fit in 248 bits to prevent overflow in
sumOuts.
Overflow Prevention
Important:nIns and nOuts must always be less than 16 to prevent sumOuts from overflowing. With 248-bit amounts and a maximum of 15 outputs, the sum is bounded by 15 * 2^248 < 2^252, well within the field size.
Poseidon Hash Function
All hashing uses Poseidon, a zk-SNARK-friendly hash function optimized for algebraic circuits. Poseidon provides:- High efficiency in constraint systems (fewer constraints than SHA-256)
- Strong security guarantees
- Resistance to algebraic attacks
Circuit Compilation
The circuit is compiled to generate:- R1CS constraints: Arithmetic constraints representing the circuit logic
- Witness generation code: JavaScript/WASM for computing circuit signals
- Verification key: Used for on-chain proof verification
- Private keys for input UTXOs
- Input amounts, blinding factors, and merkle paths
- Output amounts, public keys, and blinding factors