Overview
Merkle proofs allow proving that a specific UTXO commitment exists in the Merkle tree without revealing which leaf position it occupies. This is crucial for privacy as it breaks the link between inputs and outputs.MerkleProof Circuit
TheMerkleProof template verifies that a leaf is part of a Merkle tree with a given root:
merkleProof.circom
Parameters
levels: Depth of the Merkle tree (5 for Tornado Nova = 32 leaves)
Inputs
leaf: The UTXO commitment to prove membership forpathElements[levels]: Array of sibling hashes from leaf to rootpathIndices: Bit-encoded path (0 = left, 1 = right) as a single number
Output
root: The computed Merkle root (must match the expected root)
Circuit Implementation
Path Decoding
The circuit first converts the path indices into individual bits:Tree Traversal
For each level of the tree, the circuit:Switch Node Positions
Use the path bit to determine if the current hash should be on the left or right:The
Switcher component:- If
sel = 0:outL = L, outR = R(current node is on left) - If
sel = 1:outL = R, outR = L(current node is on right)
Root Output
After traversing all levels, the final hash is the Merkle root:Example Verification
Consider a tree with 3 levels (8 leaves):L2 exists:
-
Inputs:
leaf = L2pathElements = [L3, h1, h34]pathIndices = 0b010 = 2(binary: right, left, left)
-
Level 0: Hash
(L2, L3)→h2 -
Level 1: Hash
(h1, h2)→h12 -
Level 2: Hash
(h12, h34)→root
The prover must know the correct path elements and indices. The circuit only verifies the proof is valid.
MerkleTree Circuit
Tornado Nova also includes a helper circuit for building complete Merkle trees from leaf arrays:merkleTree.circom
TreeLayer Helper
Computes one layer of the tree:Building the Tree
TheMerkleTree circuit iterates from leaves to root:
Integration with Transaction Circuit
In the main transaction circuit, Merkle proofs verify each input UTXO:Poseidon Hash Function
All Merkle tree operations use the Poseidon hash function:- ZK-friendly: Optimized for arithmetic circuits
- Efficient: Fewer constraints than SHA-256 or Keccak
- Secure: 128-bit security level
- Inputs: 2 field elements for parent hashing
Security Properties
Soundness
It is computationally infeasible to:- Prove a leaf exists when it doesn’t
- Forge a valid path for an invalid commitment
- Find collisions in the Poseidon hash function
Privacy
- The Merkle proof reveals nothing about the leaf position
- All leaves in the anonymity set are equally likely
- Path indices are private inputs (not revealed on-chain)
- Only the root is public
Completeness
- Any valid leaf in the tree can be proven
- Proof size is logarithmic:
O(log n)fornleaves - Verification is efficient:
O(levels)operations
Tree Parameters
Tornado Nova uses the following Merkle tree configuration:| Parameter | Value | Description |
|---|---|---|
| Levels | 5 | Tree depth |
| Capacity | 32 leaves | 2^5 UTXOs |
| Path Size | 5 hashes | Proof components per input |
| Zero Leaf | Poseidon(zero, zero) | Default value for empty positions |
The zero leaf value (
11850551329423159860688778991827824730037759162201783566284850822760196767874) is computed as Poseidon(zero, zero) where zero = keccak256("tornado") % FIELD_SIZE.Performance Impact
Each Merkle proof verification adds:- Constraints: ~400 per proof (5 levels × ~80 per hash)
- Proving time: ~0.2s per proof
- Circuit size: Scales with number of inputs
- Total Merkle constraints: ~6,400 (16 × 400)
- Percentage of circuit: ~10-15% of total constraints
Next Steps
Transaction Circuits
See how Merkle proofs integrate into the full transaction circuit