What is a Merkle Tree?
A Merkle tree is a cryptographic data structure that allows efficient and secure verification of large data sets. In Privacy Cash, Merkle trees store all deposit commitments in a verifiable structure.Key Properties:
- Efficient proofs: Prove a leaf exists in a tree with only O(log n) data
- Tamper-evident: Any change to a leaf changes the root
- Append-only: New leaves are added but never removed
- Constant verification: Proof verification takes constant time
Why Merkle Trees?
Merkle trees enable Privacy Cash’s core privacy features:Membership Proofs
Prove a commitment exists in the tree without revealing which leaf it is.
Efficient Storage
Store millions of commitments with minimal on-chain state (only root + sparse subtrees).
Fast Verification
Verify membership in ~20,000 compute units (~0.4ms), enabling Solana-scale throughput.
Historical Proofs
Prove against old tree states using root history, allowing delayed withdrawals.
Tree Structure
Privacy Cash uses a binary sparse Merkle tree optimized for zero-knowledge proofs.Parameters
Tree Configuration
Binary Tree Layout
Sparse Tree Optimization
Instead of storing all 2^27 - 1 nodes (~134M nodes), Privacy Cash stores:- Active Subtrees
- Zero Values
- Root History
Only the rightmost path from root to next insertion point.Storage: 26 subtrees × 32 bytes = 832 bytes (instead of gigabytes!)
From merkle_tree.rs:900
Poseidon Hash Function
Privacy Cash uses Poseidon, a hash function designed specifically for zero-knowledge proofs.Why Poseidon?
- ZK-Friendly
- Security
- Performance
Poseidon requires far fewer constraints in ZK circuits than traditional hashes.
Result: Faster proving, smaller circuits, lower costs.
Poseidon Specification
Tree Operations
Initialization
When a new Merkle tree is created:1
Set Empty Root
Initialize the root to the hash of two empty level-1 subtrees:
From merkle_tree.rs:9
2
Initialize Subtrees
Set all subtrees to their zero values (empty tree state).
3
Add to Root History
Store the initial root at position 0 in the circular root history buffer.
Appending Commitments
When a new commitment is added:1
Check Capacity
Ensure the tree isn’t full:
From merkle_tree.rs:33
2
Compute Path
Calculate the Merkle path from leaf to root:This efficiently computes the new root using only stored subtrees.
From merkle_tree.rs:41
3
Update Root
Store the new root and update root history:
From merkle_tree.rs:65
4
Emit Event
Emit a commitment event with the new leaf and index:This allows clients to sync the tree and decrypt their commitments.
From lib.rs:351
Root Verification
When verifying a withdrawal proof:From merkle_tree.rs:79
Root history allows withdrawals using commitments from up to 100 transactions ago, providing flexibility in timing.
Merkle Proofs
A Merkle proof proves that a specific leaf exists in the tree at a given index.Proof Structure
Proof Generation (Client-Side)
1
Locate Leaf
Find the commitment’s index in the tree:
2
Collect Siblings
For each level, collect the sibling hash:
3
Encode Path
Convert the path to a binary number:
4
Package Proof
Create the proof object:
Proof Verification (Circuit)
The zero-knowledge circuit verifies the Merkle proof:From merkleProof.circom:9
- Extract path direction bits from pathIndices
- For each level, place leaf/hash on correct side based on bit
- Hash left and right children using Poseidon
- Compare final computed root with expected root
Proof size: 26 siblings × 32 bytes = 832 bytesVerification cost: 26 hashes × ~60 constraints = ~1,560 constraints
Multi-Token Trees
Privacy Cash maintains separate Merkle trees for different token types.Tree Organization
- SOL Tree
- SPL Token Trees
Native SOL has a dedicated tree:
Why Separate Trees?
Type Safety
Prevents mixing token types in proofs. You can’t withdraw USDC using a SOL commitment.
Independent Limits
Each token can have different deposit limits and fee structures.
Parallel Growth
Trees grow independently based on token usage, not artificially coupled.
Simplified Proofs
Circuit doesn’t need to verify mint addresses match across inputs/outputs.
Tree Synchronization
Clients must keep a local copy of the Merkle tree to generate proofs.Sync Strategies
- Event Listening
- Batch Sync
- Checkpoint Sync
Subscribe to commitment events and update tree in real-time:
Performance Analysis
Scalability
Cost Analysis
Merkle tree operations are extremely efficient on Solana. The sparse tree design keeps costs low even with millions of commitments.
Implementation Reference
Merkle Tree
merkle_tree.rsOn-chain Merkle tree implementation with append and verification logic
Merkle Proof Circuit
merkleProof.circomZK circuit for verifying Merkle proofs
Tree Account
lib.rs:896MerkleTreeAccount struct definition
Root Verification
lib.rs:221Root history verification in transact instruction
Next Steps
Commitments & Nullifiers
Learn about the leaves stored in Merkle trees
Zero-Knowledge Proofs
Understand how Merkle proofs are used in ZK circuits
Build with SDK
Start using Merkle trees via the Privacy Cash SDK
Tree Sync Guide
Learn best practices for keeping your tree in sync