From ba2d858c652d4a6d54be4a107e103b3b337af7f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 6 Dec 2025 08:06:15 +0000 Subject: [PATCH 1/2] feat: implement merkle_check in Noir with comprehensive tests Add merkle proof verification functionality in Noir: - merkle_check function to verify proofs and compute root - Support for generic hash functions via HashFn type - Comprehensive tests for even, odd, and power-of-2 leaf counts - Uses Keccak256 for testing (matching Rust reference implementation) The function iterates through proof nodes and applies hashing based on node side (LEFT/RIGHT) to reconstruct the Merkle root from a leaf. --- merklez/src/lib.nr | 1 + merklez/src/merkle_check.nr | 58 +++++++++++++++ tests/src/lib.nr | 1 + tests/src/merkle_check/mod.nr | 136 ++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 tests/src/merkle_check/mod.nr diff --git a/merklez/src/lib.nr b/merklez/src/lib.nr index 7e20148..84a1393 100644 --- a/merklez/src/lib.nr +++ b/merklez/src/lib.nr @@ -40,3 +40,4 @@ mod utils; pub mod merkle_root; mod merkle_proof_mixed; pub mod merkle_proof; +pub mod merkle_check; diff --git a/merklez/src/merkle_check.nr b/merklez/src/merkle_check.nr index e69de29..22f9f86 100644 --- a/merklez/src/merkle_check.nr +++ b/merklez/src/merkle_check.nr @@ -0,0 +1,58 @@ +// ============================================================================= +// MERKLE CHECK - Verify a Merkle proof and compute the root +// ============================================================================= + +use crate::side::Side; +use crate::types::{HashFn, Leaf, Proof, Root}; + +// ============================================================================= +// MERKLE CHECK - Main function +// ============================================================================= + +/// Verifies a Merkle proof and computes the root hash +/// +/// This function takes a proof (list of sibling nodes with their positions), +/// a leaf value, and a hash function, then reconstructs the path to the root +/// by combining the leaf with each proof node according to their side indicator. +/// +/// # Arguments +/// * `proof` - A Proof struct containing sibling nodes and their positions +/// * `leaf` - The leaf value to verify +/// * `hash_fn` - Hash function to combine two hashes +/// +/// # Returns +/// The computed root hash after applying all proof nodes +/// +/// # Type Parameters +/// * `P` - Proof capacity (maximum number of nodes in the proof path) +/// +/// # How it works +/// Starting from the leaf, for each proof node: +/// - If the node is on the LEFT, compute hash(node.data, current_hash) +/// - If the node is on the RIGHT, compute hash(current_hash, node.data) +/// +/// The final hash is the Merkle root. +/// +/// # Example +/// ``` +/// let proof: Proof<3> = /* proof for a leaf */; +/// let leaf: Leaf = /* leaf to verify */; +/// let root = merkle_check(proof, leaf, hash_fn); +/// // root is the computed Merkle root +/// ``` +pub fn merkle_check(proof: Proof

, leaf: Leaf, hash_fn: HashFn) -> Root { + let mut current_hash = leaf; + + for i in 0..P { + if i < proof.len { + let node = proof.nodes[i]; + current_hash = if node.side.is_right { + hash_fn(current_hash, node.data) + } else { + hash_fn(node.data, current_hash) + }; + } + } + + current_hash +} diff --git a/tests/src/lib.nr b/tests/src/lib.nr index 1d1cef3..807fc82 100644 --- a/tests/src/lib.nr +++ b/tests/src/lib.nr @@ -1,2 +1,3 @@ mod merkle_proof; mod merkle_root; +mod merkle_check; diff --git a/tests/src/merkle_check/mod.nr b/tests/src/merkle_check/mod.nr new file mode 100644 index 0000000..f7c9541 --- /dev/null +++ b/tests/src/merkle_check/mod.nr @@ -0,0 +1,136 @@ +use dep::keccak256::keccak256; +use dep::merklez::merkle_check::merkle_check; +use dep::merklez::node::Node; +use dep::merklez::side::Side; +use dep::merklez::types::{Hash, Leaf, Proof, Root}; + +fn keccak_hash(left: Hash, right: Hash) -> Hash { + let mut input: [u8; 64] = [0u8; 64]; + for i in 0..32 { + input[i] = left[i]; + input[i + 32] = right[i]; + } + keccak256(input, 64u32) +} + +// ============================================================================= +// TEST: Merkle proof check with even number of leaves +// ============================================================================= +#[test] +fn merkle_proof_check_leaves_even() { + let setup_root: Root = [ + 144, 18, 241, 225, 138, 135, 121, 13, 46, 1, 250, 172, 231, 90, 170, 202, 56, 229, 61, + 244, 55, 205, 206, 44, 5, 82, 70, 77, 218, 74, 244, 156, + ]; + + let setup_leaf: Leaf = [ + 168, 152, 44, 137, 216, 9, 135, 251, 154, 81, 14, 37, 152, 30, 233, 23, 2, 6, 190, 33, + 175, 60, 142, 14, 179, 18, 239, 29, 51, 130, 231, 97, + ]; + + let node1 = Node { + data: [ + 209, 232, 174, 183, 149, 0, 73, 110, 243, 220, 46, 87, 186, 116, 106, 131, 21, + 208, 72, 183, 166, 100, 162, 191, 148, 141, 180, 250, 145, 150, 4, 131, + ], + side: Side::right(), + }; + + let node2 = Node { + data: [ + 104, 32, 63, 144, 233, 208, 125, 197, 133, 146, 89, 215, 83, 110, 135, 166, + 186, 157, 52, 95, 37, 82, 181, 185, 222, 41, 153, 221, 206, 156, 225, 191, + ], + side: Side::left(), + }; + + let proof = Proof { nodes: [node1, node2], len: 2 }; + + let result = merkle_check(proof, setup_leaf, keccak_hash); + + assert(result == setup_root); +} + +// ============================================================================= +// TEST: Merkle proof check with odd number of leaves +// ============================================================================= +#[test] +fn merkle_proof_check_leaves_odd() { + let setup_root: Root = [ + 29, 208, 210, 166, 174, 70, 109, 102, 92, 178, 110, 26, 49, 240, 124, 87, 174, 93, 247, + 210, 188, 85, 156, 213, 130, 109, 65, 123, 233, 20, 26, 93, + ]; + + let setup_leaf: Leaf = [ + 241, 145, 142, 133, 98, 35, 110, 177, 122, 220, 133, 2, 51, 47, 76, 156, 130, 188, 20, + 225, 155, 252, 10, 161, 10, 182, 116, 255, 117, 179, 210, 243, + ]; + + let node1 = Node { + data: [ + 11, 66, 182, 57, 60, 31, 83, 6, 15, 227, 221, 191, 205, 122, 173, 204, 168, + 148, 70, 90, 90, 67, 143, 105, 200, 125, 121, 11, 34, 153, 185, 178, + ], + side: Side::left(), + }; + + let node2 = Node { + data: [ + 128, 91, 33, 216, 70, 177, 137, 239, 174, 176, 55, 125, 107, 176, 210, 1, 179, + 135, 42, 54, 62, 96, 124, 37, 8, 143, 2, 91, 12, 106, 225, 248, + ], + side: Side::left(), + }; + + let node3 = Node { + data: [ + 168, 152, 44, 137, 216, 9, 135, 251, 154, 81, 14, 37, 152, 30, 233, 23, 2, 6, + 190, 33, 175, 60, 142, 14, 179, 18, 239, 29, 51, 130, 231, 97, + ], + side: Side::right(), + }; + + let proof = Proof { nodes: [node1, node2, node3], len: 3 }; + + let result = merkle_check(proof, setup_leaf, keccak_hash); + + assert(result == setup_root); +} + +// ============================================================================= +// TEST: Merkle proof check with power-of-2 number of leaves +// ============================================================================= +#[test] +fn merkle_proof_check_leaves_base_2() { + let setup_root: Root = [ + 104, 32, 63, 144, 233, 208, 125, 197, 133, 146, 89, 215, 83, 110, 135, 166, 186, 157, + 52, 95, 37, 82, 181, 185, 222, 41, 153, 221, 206, 156, 225, 191, + ]; + + let setup_leaf: Leaf = [ + 58, 194, 37, 22, 141, 245, 66, 18, 162, 92, 28, 1, 253, 53, 190, 191, 234, 64, 143, + 218, 194, 227, 29, 221, 111, 128, 164, 187, 249, 165, 241, 203, + ]; + + let node1 = Node { + data: [ + 181, 85, 61, 227, 21, 224, 237, 245, 4, 217, 21, 10, 248, 45, 175, 165, 196, + 102, 127, 166, 24, 237, 10, 111, 25, 198, 155, 65, 22, 108, 85, 16, + ], + side: Side::right(), + }; + + let node2 = Node { + data: [ + 210, 83, 165, 45, 76, 176, 13, 226, 137, 94, 133, 242, 82, 158, 41, 118, 230, + 170, 170, 92, 24, 16, 107, 104, 171, 102, 129, 62, 20, 65, 86, 105, + ], + side: Side::right(), + }; + + let proof = Proof { nodes: [node1, node2], len: 2 }; + + let result = merkle_check(proof, setup_leaf, keccak_hash); + + assert(result == setup_root); +} From 5563e91a8d05140e7b4975af3336cc57620a75c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 6 Dec 2025 08:09:49 +0000 Subject: [PATCH 2/2] docs: rename merkle_check to merkle_proof_check and add documentation Rename the function to merkle_proof_check for consistency with existing documentation and examples. Add comprehensive documentation: - Rename function from merkle_check to merkle_proof_check - Update all tests to use new function name - Add new example showing low-level API usage - Update README to list new example This ensures consistency across the codebase and makes the function name match what's already documented in README and used in examples. --- README.md | 3 +- examples/how_to_use/merkle_proof_check.nr | 36 +++++++++++++++++++++++ merklez/src/merkle_check.nr | 8 ++--- tests/src/merkle_check/mod.nr | 8 ++--- 4 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 examples/how_to_use/merkle_proof_check.nr diff --git a/README.md b/README.md index c6eacc5..45f7943 100644 --- a/README.md +++ b/README.md @@ -345,7 +345,8 @@ See the [`examples/`](./examples/) directory for more usage examples: **Basic Usage** (`how_to_use/`): - `merkle_root.nr` - Calculate merkle root from leaves - `merkle_proof.nr` - Generate merkle proofs for elements -- `merkle_verify.nr` - Verify merkle proofs +- `merkle_proof_check.nr` - Verify proofs using low-level API +- `merkle_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) diff --git a/examples/how_to_use/merkle_proof_check.nr b/examples/how_to_use/merkle_proof_check.nr new file mode 100644 index 0000000..6635692 --- /dev/null +++ b/examples/how_to_use/merkle_proof_check.nr @@ -0,0 +1,36 @@ +use merklez::{merkle_proof, merkle_proof_check, merkle_root, Proof}; +use sha256::sha256; + +fn main() { + // Step 1: Create some leaves + let mut leaf_a: [u8; 32] = [0; 32]; + leaf_a[0] = 1; + let mut leaf_b: [u8; 32] = [0; 32]; + leaf_b[0] = 2; + let mut leaf_c: [u8; 32] = [0; 32]; + leaf_c[0] = 3; + let mut leaf_d: [u8; 32] = [0; 32]; + leaf_d[0] = 4; + + let leaves: [[u8; 32]; 4] = [leaf_a, leaf_b, leaf_c, leaf_d]; + + // Step 2: Calculate merkle root + let root = merkle_root(leaves, sha256); + + // Step 3: Generate proof for leaf_c + let proof: Proof<2> = merkle_proof(leaves, leaf_c, sha256); + + // Step 4: Verify proof using merkle_proof_check + // This function reconstructs the root from the leaf and proof + let computed_root = merkle_proof_check(proof, leaf_c, sha256); + + // Step 5: Compare computed root with expected root + assert(computed_root == root); +} + +// you need add sha256 in your Nargo.toml: +// +// ```toml +// [dependencies] +// sha256 = { tag = "v0.2.1", git = "https://github.com/noir-lang/sha256" } +// ``` diff --git a/merklez/src/merkle_check.nr b/merklez/src/merkle_check.nr index 22f9f86..fd04cf6 100644 --- a/merklez/src/merkle_check.nr +++ b/merklez/src/merkle_check.nr @@ -1,12 +1,12 @@ // ============================================================================= -// MERKLE CHECK - Verify a Merkle proof and compute the root +// MERKLE PROOF CHECK - Verify a Merkle proof and compute the root // ============================================================================= use crate::side::Side; use crate::types::{HashFn, Leaf, Proof, Root}; // ============================================================================= -// MERKLE CHECK - Main function +// MERKLE PROOF CHECK - Main function // ============================================================================= /// Verifies a Merkle proof and computes the root hash @@ -37,10 +37,10 @@ use crate::types::{HashFn, Leaf, Proof, Root}; /// ``` /// let proof: Proof<3> = /* proof for a leaf */; /// let leaf: Leaf = /* leaf to verify */; -/// let root = merkle_check(proof, leaf, hash_fn); +/// let root = merkle_proof_check(proof, leaf, hash_fn); /// // root is the computed Merkle root /// ``` -pub fn merkle_check(proof: Proof

, leaf: Leaf, hash_fn: HashFn) -> Root { +pub fn merkle_proof_check(proof: Proof

, leaf: Leaf, hash_fn: HashFn) -> Root { let mut current_hash = leaf; for i in 0..P { diff --git a/tests/src/merkle_check/mod.nr b/tests/src/merkle_check/mod.nr index f7c9541..1a2e776 100644 --- a/tests/src/merkle_check/mod.nr +++ b/tests/src/merkle_check/mod.nr @@ -1,5 +1,5 @@ use dep::keccak256::keccak256; -use dep::merklez::merkle_check::merkle_check; +use dep::merklez::merkle_check::merkle_proof_check; use dep::merklez::node::Node; use dep::merklez::side::Side; use dep::merklez::types::{Hash, Leaf, Proof, Root}; @@ -46,7 +46,7 @@ fn merkle_proof_check_leaves_even() { let proof = Proof { nodes: [node1, node2], len: 2 }; - let result = merkle_check(proof, setup_leaf, keccak_hash); + let result = merkle_proof_check(proof, setup_leaf, keccak_hash); assert(result == setup_root); } @@ -92,7 +92,7 @@ fn merkle_proof_check_leaves_odd() { let proof = Proof { nodes: [node1, node2, node3], len: 3 }; - let result = merkle_check(proof, setup_leaf, keccak_hash); + let result = merkle_proof_check(proof, setup_leaf, keccak_hash); assert(result == setup_root); } @@ -130,7 +130,7 @@ fn merkle_proof_check_leaves_base_2() { let proof = Proof { nodes: [node1, node2], len: 2 }; - let result = merkle_check(proof, setup_leaf, keccak_hash); + let result = merkle_proof_check(proof, setup_leaf, keccak_hash); assert(result == setup_root); }