|
| 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, ¤t_hash], is_left) |
| 53 | + .try_into() |
| 54 | + .expect("multi_wire_multiplex should return 4 wires"); |
| 55 | + let right_hash = multi_wire_multiplex(builder, &[¤t_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, ¶m, &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, ¶m, 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, ¶m, &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, ¶m, 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, ¶m, &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, ¶m, 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 | +} |
0 commit comments