A Merkle Tree implementation in Noir - Port from the Rust library merkletreers.
- No default hash function - You provide your own hash function (Poseidon, Pedersen, SHA256, etc.)
- No external dependencies - Pure Noir implementation
- Merkle root calculation - Build merkle trees from leaves
- Proof generation - Generate merkle proofs for any leaf
- Proof verification - Verify merkle proofs against a root
- High-level API -
MerkleTreestruct for convenient usage
Add this to your Nargo.toml:
[dependencies]
merklez = { tag = "0.5.0", git = "https://github.com/olivmath/merklez", directory = "merklez" }use merklez::{MerkleTree, Hash, Leaf};
// Define your hash function
fn my_hash(left: Hash, right: Hash) -> Hash {
// Use your preferred hash: Poseidon, Pedersen, SHA256, etc.
let mut result: Hash = [0; 32];
for i in 0..32 {
result[i] = left[i] ^ right[i];
}
result
}
fn main() {
// Create leaves
let mut leaf_a: Hash = [0; 32]; leaf_a[0] = 1;
let mut leaf_b: Hash = [0; 32]; leaf_b[0] = 2;
let mut leaf_c: Hash = [0; 32]; leaf_c[0] = 3;
let mut leaf_d: Hash = [0; 32]; leaf_d[0] = 4;
let leaves: [Leaf; 4] = [leaf_a, leaf_b, leaf_c, leaf_d];
// Build tree and get root
let tree = MerkleTree::new(leaves, my_hash);
let root = tree.root; // The merkle root hash
}use merklez::{MerkleTree, Proof, Hash, Leaf};
fn main() {
// ... create leaves and tree as above ...
let tree = MerkleTree::new(leaves, my_hash);
// Generate proof for a specific leaf
let proof: Proof<8> = tree.make_proof(leaf_c);
// The proof contains sibling nodes needed to reconstruct the root
// proof.len -> number of nodes in proof path
// proof.nodes -> array of Node { data: Hash, side: Side }
}use merklez::{MerkleTree, Proof, Hash, Leaf};
fn main() {
// ... create tree and proof ...
let tree = MerkleTree::new(leaves, my_hash);
let proof: Proof<8> = tree.make_proof(leaf_c);
// Verify proof against the tree
let is_valid: bool = tree.verify_merkle_proof(leaf_c, proof);
assert(is_valid);
}Prove that an element belongs to a set without revealing other elements. The root is public, leaf and proof are private.
use merklez::{Proof, merkle_proof_check, Hash};
// Circuit inputs: root is public, leaf and proof are private
fn main(pub root: Hash, leaf: Hash, proof: Proof<8>) {
// Verify the leaf is part of the tree with this root
let computed_root = merkle_proof_check(proof, leaf, my_hash);
assert(computed_root == root);
}Use cases:
- Whitelist verification (prove you're on an allowlist)
- Airdrop eligibility (prove you qualify without revealing all recipients)
- Anonymous voting (prove you're a registered voter)
Both root and leaf are private. Useful for proving knowledge of a valid set membership.
use merklez::{Proof, merkle_proof_check, Hash};
fn main(leaf: Hash, proof: Proof<8>, root: Hash) {
// All inputs are private
let computed_root = merkle_proof_check(proof, leaf, my_hash);
assert(computed_root == root);
}Verify a proof when you only have the root hash (no need to rebuild the tree).
use merklez::{MerkleTree, Proof, Hash, Leaf};
fn main(root: Hash, leaf: Hash, proof: Proof<8>) {
// Static verification - no tree instance needed
let is_valid = MerkleTree::<4>::static_verify_merkle_proof(
leaf,
proof,
root,
my_hash
);
assert(is_valid);
}Prove an element is NOT in a sorted merkle tree by showing its position would be between two adjacent leaves.
use merklez::{MerkleTree, Proof, Hash, Leaf};
fn main(
pub root: Hash,
target: Hash, // Element to prove is NOT in tree
left_neighbor: Hash, // Element just before target position
right_neighbor: Hash, // Element just after target position
proof_left: Proof<8>,
proof_right: Proof<8>
) {
// Verify both neighbors are in the tree
let left_valid = merkle_proof_check(proof_left, left_neighbor, my_hash);
let right_valid = merkle_proof_check(proof_right, right_neighbor, my_hash);
assert(left_valid == root);
assert(right_valid == root);
// Verify target would be between neighbors (tree must be sorted)
assert(left_neighbor < target);
assert(target < right_neighbor);
}For more control, use the functions directly instead of MerkleTree:
use merklez::{merkle_root, merkle_proof, merkle_proof_check, Proof, Hash, Leaf};
fn main() {
let leaves: [Leaf; 4] = [leaf_a, leaf_b, leaf_c, leaf_d];
// Calculate root directly
let root = merkle_root(leaves, my_hash);
// Generate proof for leaf_c
let proof: Proof<2> = merkle_proof(leaves, leaf_c, my_hash);
// Verify proof
let computed = merkle_proof_check(proof, leaf_c, my_hash);
assert(computed == root);
}Hash:[u8; 32]- 32-byte hashLeaf:Hash- Leaf node hashRoot:Hash- Root hashHashFn:fn(Hash, Hash) -> Hash- Hash function type
Represents the position of a node in the tree.
Side::left()- Create a left side markerSide::right()- Create a right side markeris_left()- Check if leftis_right()- Check if right
Represents a node in the merkle proof.
struct Node {
pub data: Hash,
pub side: Side
}Represents a merkle proof with maximum size N.
struct Proof<let N: u32> {
pub nodes: [Node; N],
pub len: u32
}Methods:
new()- Create empty proofpush(node: Node)- Add node to proofget(index: u32) -> Node- Get node at index
Calculates the merkle root from an array of leaves.
pub fn merkle_root<let N: u32>(
leaves: [Leaf; N],
hash_fn: HashFn
) -> RootType Parameters:
N: Number of leaves in the tree
Parameters:
leaves: Array of leaf hasheshash_fn: Custom hash function to combine two hashes
Returns: The merkle root hash
Generates a merkle proof for a specific leaf.
pub fn merkle_proof<let N: u32, let P: u32>(
leaves: [Leaf; N],
leaf: Leaf,
hash_fn: HashFn
) -> Proof<P>Type Parameters:
N: Number of leaves in the treeP: Proof capacity (useceil(log2(N))): N=2→P=1, N=3-4→P=2, N=5-8→P=3, N=9-16→P=4
Parameters:
leaves: Array of all leavesleaf: The leaf to generate a proof forhash_fn: Custom hash function
Returns: A Proof struct containing the path from leaf to root
Verifies a merkle proof and returns the computed root.
pub fn merkle_proof_check<let P: u32>(
proof: Proof<P>,
leaf: Leaf,
hash_fn: HashFn
) -> RootParameters:
proof: The proof to verifyleaf: The leaf to verifyhash_fn: Custom hash function
Returns: The computed root hash (compare with expected root to verify)
Zero-knowledge proof systems like Noir work best with specific hash functions optimized for their constraint systems. Different applications may need different hash functions:
- Poseidon: Optimized for zk-SNARKs
- Pedersen: Good for certain circuit optimizations
- Mimc: Another zk-friendly hash
- SHA256: Standard but expensive in circuits
By not providing a default, we ensure you choose the right hash for your use case.
To keep the library minimal and auditable. You can easily integrate with any hash function from the Noir standard library or your own implementation.
Noir doesn't have dynamic vectors like Rust. We use bounded arrays with a length field to simulate dynamic behavior. You need to specify maximum sizes at compile time:
// Proof can contain maximum 8 nodes
let proof: Proof<8> = merkle_proof(...);
// Tree can have maximum 16 leaves
let leaves: [Leaf; 16] = [...];Run the tests with:
nargo testSee the examples/ directory for more usage examples:
Basic Usage (how_to_use/):
merkle_root.nr- Calculate merkle root from leavesmerkle_proof.nr- Generate merkle proofs for elementsmerkle_proof_check.nr- Verify proofs using low-level APImerkle_verify.nr- Verify merkle proofs using high-level API
ZK Proof Patterns (how_to_proof/):
membership_public_root.nr- Membership proof with public root (whitelist, airdrops, voting)
Contributions are welcome! Please open an issue or PR.
Same as original merkletreers library.
This library is a port of merkletreers by @olivmath.
- merkletreers - Original Rust implementation
- Noir Language - The Noir documentation
- Merkle Trees - Wikipedia article