Skip to content

Commit 73076a9

Browse files
committed
[frontend] Add merkle tree verifier
1 parent 9de321f commit 73076a9

File tree

2 files changed

+304
-0
lines changed

2 files changed

+304
-0
lines changed
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
use super::tweak::verify_tree_tweak;
2+
use crate::{
3+
circuits::{keccak::Keccak, multiplexer::multi_wire_multiplex},
4+
compiler::{CircuitBuilder, Wire},
5+
};
6+
7+
/// Verifies a Merkle tree authentication path.
8+
///
9+
/// This circuit verifies that a given leaf hash is part of a Merkle tree
10+
/// by reconstructing the path from leaf to root using the provided
11+
/// authentication path (sibling hashes).
12+
///
13+
/// # Arguments
14+
///
15+
/// * `builder` - Circuit builder for constructing constraints
16+
/// * `param` - Cryptographic parameter (32 bytes as 4x64-bit LE wires)
17+
/// * `leaf_hash` - The leaf hash to verify (32 bytes as 4x64-bit LE wires)
18+
/// * `leaf_index` - Index of the leaf in the tree (as a wire)
19+
/// * `auth_path` - Authentication path: sibling hashes from leaf to root
20+
/// * `root_hash` - Expected root hash (32 bytes as 4x64-bit LE wires)
21+
///
22+
/// # Returns
23+
///
24+
/// A vector of Keccak hashers that need witness population
25+
pub fn verify_merkle_path(
26+
builder: &CircuitBuilder,
27+
param: &[Wire],
28+
leaf_hash: &[Wire; 4],
29+
leaf_index: Wire,
30+
auth_path: &[[Wire; 4]],
31+
root_hash: &[Wire; 4],
32+
) -> Vec<Keccak> {
33+
assert_eq!(param.len(), 4, "param must be 32 bytes as 4 wires");
34+
35+
let tree_height = auth_path.len();
36+
let mut hashers = Vec::with_capacity(tree_height);
37+
let mut current_hash = *leaf_hash;
38+
let mut current_index = leaf_index;
39+
40+
// Process each level of the tree
41+
for level in 0..tree_height {
42+
let sibling_hash = auth_path[level];
43+
44+
// Determine if current node is left or right child
45+
// If current_index is even (LSB = 0), current is left child
46+
// If current_index is odd (LSB = 1), current is right child
47+
let is_left = builder.bnot(builder.band(current_index, builder.add_constant_64(1)));
48+
49+
// Select left and right hashes based on position
50+
// If is_left: left = current, right = sibling
51+
// If !is_left: left = sibling, right = current
52+
let left_hash = multi_wire_multiplex(builder, &[&sibling_hash, &current_hash], is_left)
53+
.try_into()
54+
.expect("multi_wire_multiplex should return 4 wires");
55+
let right_hash = multi_wire_multiplex(builder, &[&current_hash, &sibling_hash], is_left)
56+
.try_into()
57+
.expect("multi_wire_multiplex should return 4 wires");
58+
59+
// Compute parent index: parent_index = current_index / 2
60+
let parent_index = builder.shr(current_index, 1);
61+
62+
// Create output hash wire for this level
63+
let parent_hash: [Wire; 4] = std::array::from_fn(|_| builder.add_witness());
64+
65+
// Create tree node hasher
66+
let level_wire = builder.add_constant_64(level as u64);
67+
let hasher = verify_tree_tweak(
68+
builder,
69+
param.to_vec(),
70+
param.len() * 8,
71+
left_hash,
72+
right_hash,
73+
level_wire,
74+
parent_index,
75+
parent_hash,
76+
);
77+
78+
hashers.push(hasher);
79+
80+
// Move up the tree
81+
current_hash = parent_hash;
82+
current_index = parent_index;
83+
}
84+
85+
// Assert that the final hash matches the expected root
86+
builder.assert_eq_v("merkle_root_check", current_hash, *root_hash);
87+
88+
hashers
89+
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use binius_core::Word;
94+
use sha3::{Digest, Keccak256};
95+
96+
use super::*;
97+
use crate::{
98+
circuits::hash_based_sig::tweak::{TREE_TWEAK, build_tree_tweak},
99+
constraint_verifier::verify_constraints,
100+
util::pack_bytes_into_wires_le,
101+
};
102+
103+
/// Helper to compute tree node hash using Keccak
104+
fn hash_tree_node_keccak(
105+
param: &[u8],
106+
left: &[u8; 32],
107+
right: &[u8; 32],
108+
level: u32,
109+
index: u32,
110+
) -> [u8; 32] {
111+
let mut hasher = Keccak256::new();
112+
hasher.update(param);
113+
hasher.update([TREE_TWEAK]);
114+
hasher.update(level.to_le_bytes());
115+
hasher.update(index.to_le_bytes());
116+
hasher.update(left);
117+
hasher.update(right);
118+
hasher.finalize().into()
119+
}
120+
121+
#[test]
122+
fn test_merkle_path_verification() {
123+
// Build a simple 4-leaf tree for testing
124+
// Tree structure:
125+
// root
126+
// / \
127+
// n2 n3
128+
// / \ / \
129+
// l0 l1 l2 l3
130+
131+
let builder = CircuitBuilder::new();
132+
133+
// Create input wires
134+
let param: Vec<Wire> = (0..4).map(|_| builder.add_inout()).collect();
135+
let leaf_hash: [Wire; 4] = std::array::from_fn(|_| builder.add_inout());
136+
let leaf_index = builder.add_inout();
137+
let root_hash: [Wire; 4] = std::array::from_fn(|_| builder.add_inout());
138+
139+
// Authentication path (2 levels for 4-leaf tree)
140+
let auth_path: Vec<[Wire; 4]> = (0..2)
141+
.map(|_| std::array::from_fn(|_| builder.add_inout()))
142+
.collect();
143+
144+
// Create the verification circuit
145+
let hashers =
146+
verify_merkle_path(&builder, &param, &leaf_hash, leaf_index, &auth_path, &root_hash);
147+
148+
let circuit = builder.build();
149+
let mut w = circuit.new_witness_filler();
150+
151+
// Set up test data
152+
let param_bytes = b"test_merkle_tree_parameter!!!!!!";
153+
pack_bytes_into_wires_le(&mut w, &param, param_bytes);
154+
155+
// Create leaf hashes
156+
let leaf0 = b"leaf_0_hash_value_32_bytes!!!!!!";
157+
let leaf1 = b"leaf_1_hash_value_32_bytes!!!!!!";
158+
let leaf2 = b"leaf_2_hash_value_32_bytes!!!!!!";
159+
let leaf3 = b"leaf_3_hash_value_32_bytes!!!!!!";
160+
161+
// Compute internal nodes
162+
let node2 = hash_tree_node_keccak(param_bytes, leaf0, leaf1, 0, 0);
163+
let node3 = hash_tree_node_keccak(param_bytes, leaf2, leaf3, 0, 1);
164+
let root = hash_tree_node_keccak(param_bytes, &node2, &node3, 1, 0);
165+
166+
// Test verification for leaf 1 (index 1)
167+
// Path: [leaf0 (sibling at level 0), node3 (sibling at level 1)]
168+
pack_bytes_into_wires_le(&mut w, &leaf_hash, leaf1);
169+
w[leaf_index] = Word::from_u64(1);
170+
pack_bytes_into_wires_le(&mut w, &auth_path[0], leaf0);
171+
pack_bytes_into_wires_le(&mut w, &auth_path[1], &node3);
172+
pack_bytes_into_wires_le(&mut w, &root_hash, &root);
173+
174+
// Populate hashers
175+
// Level 0: hash(leaf0, leaf1) with index 0
176+
let hasher0 = &hashers[0];
177+
let message0 = build_tree_tweak(param_bytes, leaf0, leaf1, 0, 0);
178+
hasher0.populate_message(&mut w, &message0);
179+
hasher0.populate_digest(&mut w, node2);
180+
181+
// Level 1: hash(node2, node3) with index 0
182+
let hasher1 = &hashers[1];
183+
let message1 = build_tree_tweak(param_bytes, &node2, &node3, 1, 0);
184+
hasher1.populate_message(&mut w, &message1);
185+
hasher1.populate_digest(&mut w, root);
186+
187+
// Populate witness and verify
188+
circuit.populate_wire_witness(&mut w).unwrap();
189+
190+
let cs = circuit.constraint_system();
191+
verify_constraints(cs, &w.into_value_vec()).unwrap();
192+
}
193+
194+
#[test]
195+
fn test_negative_merkle_invalid_auth_path() {
196+
// Circuit does not verify invalid auth path
197+
let builder = CircuitBuilder::new();
198+
199+
let param: Vec<Wire> = (0..4).map(|_| builder.add_inout()).collect();
200+
let leaf_hash: [Wire; 4] = std::array::from_fn(|_| builder.add_inout());
201+
let leaf_index = builder.add_inout();
202+
let root_hash: [Wire; 4] = std::array::from_fn(|_| builder.add_inout());
203+
let auth_path: Vec<[Wire; 4]> = (0..2)
204+
.map(|_| std::array::from_fn(|_| builder.add_inout()))
205+
.collect();
206+
207+
let hashers =
208+
verify_merkle_path(&builder, &param, &leaf_hash, leaf_index, &auth_path, &root_hash);
209+
210+
let circuit = builder.build();
211+
let mut w = circuit.new_witness_filler();
212+
213+
let param_bytes = b"test_merkle_tree_parameter!!!!!!";
214+
pack_bytes_into_wires_le(&mut w, &param, param_bytes);
215+
216+
let leaf0 = b"leaf_0_hash_value_32_bytes!!!!!!";
217+
let leaf1 = b"leaf_1_hash_value_32_bytes!!!!!!";
218+
let leaf2 = b"leaf_2_hash_value_32_bytes!!!!!!";
219+
let leaf3 = b"leaf_3_hash_value_32_bytes!!!!!!";
220+
221+
let node2 = hash_tree_node_keccak(param_bytes, leaf0, leaf1, 0, 0);
222+
let node3 = hash_tree_node_keccak(param_bytes, leaf2, leaf3, 0, 1);
223+
let root = hash_tree_node_keccak(param_bytes, &node2, &node3, 1, 0);
224+
225+
// Test verification for leaf 2 (index 2)
226+
// Path: [leaf3 (sibling at level 0), node2 incorrect!]
227+
pack_bytes_into_wires_le(&mut w, &leaf_hash, leaf2);
228+
w[leaf_index] = Word::from_u64(2);
229+
pack_bytes_into_wires_le(&mut w, &auth_path[0], leaf3);
230+
pack_bytes_into_wires_le(&mut w, &auth_path[1], &node3);
231+
pack_bytes_into_wires_le(&mut w, &root_hash, &root);
232+
233+
// Populate hashers
234+
// Level 0: hash(leaf2, leaf3) with index 1
235+
let hasher0 = &hashers[0];
236+
let message0 = build_tree_tweak(param_bytes, leaf2, leaf3, 0, 1);
237+
hasher0.populate_message(&mut w, &message0);
238+
hasher0.populate_digest(&mut w, node3);
239+
240+
// Level 1: hash(node2, node3) with index 0
241+
let hasher1 = &hashers[1];
242+
let message1 = build_tree_tweak(param_bytes, &node2, &node3, 1, 0);
243+
hasher1.populate_message(&mut w, &message1);
244+
hasher1.populate_digest(&mut w, root);
245+
246+
assert!(circuit.populate_wire_witness(&mut w).is_err());
247+
}
248+
249+
#[test]
250+
fn test_negative_merkle_invalid_hash_population() {
251+
// The circuit does not verify an invalid hash population
252+
let builder = CircuitBuilder::new();
253+
254+
let param: Vec<Wire> = (0..4).map(|_| builder.add_inout()).collect();
255+
let leaf_hash: [Wire; 4] = std::array::from_fn(|_| builder.add_inout());
256+
let leaf_index = builder.add_inout();
257+
let root_hash: [Wire; 4] = std::array::from_fn(|_| builder.add_inout());
258+
let auth_path: Vec<[Wire; 4]> = (0..2)
259+
.map(|_| std::array::from_fn(|_| builder.add_inout()))
260+
.collect();
261+
262+
let hashers =
263+
verify_merkle_path(&builder, &param, &leaf_hash, leaf_index, &auth_path, &root_hash);
264+
265+
let circuit = builder.build();
266+
let mut w = circuit.new_witness_filler();
267+
268+
let param_bytes = b"test_merkle_tree_parameter!!!!!!";
269+
pack_bytes_into_wires_le(&mut w, &param, param_bytes);
270+
271+
let leaf0 = b"leaf_0_hash_value_32_bytes!!!!!!";
272+
let leaf1 = b"leaf_1_hash_value_32_bytes!!!!!!";
273+
let leaf2 = b"leaf_2_hash_value_32_bytes!!!!!!";
274+
let leaf3 = b"leaf_3_hash_value_32_bytes!!!!!!";
275+
276+
let node2 = hash_tree_node_keccak(param_bytes, leaf0, leaf1, 0, 0);
277+
let node3 = hash_tree_node_keccak(param_bytes, leaf2, leaf3, 0, 1);
278+
let root = hash_tree_node_keccak(param_bytes, &node2, &node3, 1, 0);
279+
280+
// Test verification for leaf 2 (index 2)
281+
// Path: [leaf3 (sibling at level 0), node2 (sibling at level 1)]
282+
pack_bytes_into_wires_le(&mut w, &leaf_hash, leaf2);
283+
w[leaf_index] = Word::from_u64(2);
284+
pack_bytes_into_wires_le(&mut w, &auth_path[0], leaf3);
285+
pack_bytes_into_wires_le(&mut w, &auth_path[1], &node2);
286+
pack_bytes_into_wires_le(&mut w, &root_hash, &root);
287+
288+
// Populate hashers
289+
// Level 0: hash(leaf2, leaf3) with index 1
290+
let hasher0 = &hashers[0];
291+
let message0 = build_tree_tweak(param_bytes, leaf2, leaf3, 0, 1);
292+
hasher0.populate_message(&mut w, &message0);
293+
hasher0.populate_digest(&mut w, node3);
294+
295+
// Level 1: hash(node2, leaf3) with index 0 - incorrect!
296+
let hasher1 = &hashers[1];
297+
let message1 = build_tree_tweak(param_bytes, &node2, leaf3, 1, 0);
298+
hasher1.populate_message(&mut w, &message1);
299+
hasher1.populate_digest(&mut w, root);
300+
301+
assert!(circuit.populate_wire_witness(&mut w).is_err());
302+
}
303+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod chain;
22
pub mod codeword;
3+
pub mod merkle_tree;
34
pub mod tweak;
45
pub mod winternitz_ots;

0 commit comments

Comments
 (0)