Skip to main content

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

Each level doubles the number of nodes until reaching 2^26 leaves at the bottom.

Sparse Tree Optimization

Instead of storing all 2^27 - 1 nodes (~134M nodes), Privacy Cash stores:
Only the rightmost path from root to next insertion point.
From merkle_tree.rs:900
Storage: 26 subtrees × 32 bytes = 832 bytes (instead of gigabytes!)

Poseidon Hash Function

Privacy Cash uses Poseidon, a hash function designed specifically for zero-knowledge proofs.

Why Poseidon?

Poseidon requires far fewer constraints in ZK circuits than traditional hashes.Result: Faster proving, smaller circuits, lower costs.

Poseidon Specification

Inputs: Two 32-byte field elements Output: One 32-byte field element

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:
From merkle_tree.rs:41
This efficiently computes the new root using only stored subtrees.
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:
From lib.rs:351
This allows clients to sync the tree and decrypt their commitments.

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
Verification steps:
  1. Extract path direction bits from pathIndices
  2. For each level, place leaf/hash on correct side based on bit
  3. Hash left and right children using Poseidon
  4. 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

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

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