From c0ecb77cd0b09850b88d370fc9f5a8efbf7d1ae1 Mon Sep 17 00:00:00 2001 From: Tom French Date: Wed, 26 Apr 2023 00:15:54 +0100 Subject: [PATCH 1/8] chore: replace `check_membership` with `calculate_merkle_root` --- .../src/acvm_interop/pwg.rs | 285 +++++++++++++++++- .../src/acvm_interop/pwg/merkle.rs | 263 ---------------- 2 files changed, 281 insertions(+), 267 deletions(-) delete mode 100644 acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs index 74a35083..9f2da81b 100644 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs @@ -13,8 +13,6 @@ use crate::Barretenberg; use blake2::{Blake2s, Digest}; -mod merkle; - impl PartialWitnessGenerator for Barretenberg { fn solve_black_box_function_call( &self, @@ -48,9 +46,14 @@ impl PartialWitnessGenerator for Barretenberg { .map(|input| witness_to_value(initial_witness, input.witness)) .collect(); - let valid_proof = merkle::check_membership(self, hash_path?, root, index, leaf); + let calculated_merkle_root = calculate_merkle_root( + |left, right| self.compress_native(left, right), + hash_path?, + index, + leaf, + ); - let result = if valid_proof { + let result = if &calculated_merkle_root == root { FieldElement::one() } else { FieldElement::zero() @@ -162,3 +165,277 @@ impl PartialWitnessGenerator for Barretenberg { Ok(OpcodeResolution::Solved) } } + +// TODO: alter this method so that it only processes one hash per level rather than overriding +// the one of leaves for each level of the hash path +fn calculate_merkle_root( + hash_func: impl Fn(&FieldElement, &FieldElement) -> FieldElement, + hash_path: Vec<&FieldElement>, + index: &FieldElement, + leaf: &FieldElement, +) -> FieldElement { + let mut index_bits = index.bits(); + index_bits.reverse(); + + let mut current = *leaf; + + for (i, path_elem) in hash_path.into_iter().enumerate() { + let path_bit = index_bits[i]; + let (hash_left, hash_right) = if !path_bit { + (current, *path_elem) + } else { + (*path_elem, current) + }; + current = hash_func(&hash_left, &hash_right); + } + + current +} + +#[cfg(test)] +mod tests { + use crate::{pedersen::Pedersen, Barretenberg}; + use common::acvm::FieldElement; + use common::merkle::{MerkleTree, MessageHasher, PathHasher}; + + impl PathHasher for Barretenberg { + fn hash(&self, left: &FieldElement, right: &FieldElement) -> FieldElement { + self.compress_native(left, right) + } + + fn new() -> Self { + Barretenberg::new() + } + } + + #[test] + fn basic_interop_initial_root() { + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + // Test that the initial root is computed correctly + let tree: MerkleTree = MerkleTree::new(3, &temp_dir); + // Copied from barretenberg by copying the stdout from MemoryTree + let expected_hex = "04ccfbbb859b8605546e03dcaf41393476642859ff7f99446c054b841f0e05c8"; + assert_eq!(tree.root().to_hex(), expected_hex) + } + #[test] + fn basic_interop_hashpath() { + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + // Test that the hashpath is correct + let tree: MerkleTree = MerkleTree::new(3, &temp_dir); + + let path = tree.get_hash_path(0); + + let expected_hash_path = vec![ + ( + "1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", + "1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", + ), + ( + "0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", + "0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", + ), + ( + "22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", + "22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", + ), + ]; + + for (got, expected_segment) in path.into_iter().zip(expected_hash_path) { + assert_eq!(got.0.to_hex().as_str(), expected_segment.0); + assert_eq!(got.1.to_hex().as_str(), expected_segment.1) + } + } + + #[test] + fn basic_interop_update() { + // Test that computing the HashPath is correct + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); + + tree.update_message(0, &[0; 64]); + tree.update_message(1, &[1; 64]); + tree.update_message(2, &[2; 64]); + tree.update_message(3, &[3; 64]); + tree.update_message(4, &[4; 64]); + tree.update_message(5, &[5; 64]); + tree.update_message(6, &[6; 64]); + let root = tree.update_message(7, &[7; 64]); + + assert_eq!( + "0ef8e14db4762ebddadb23b2225f93ca200a4c9bd37130b4d028c971bbad16b5", + root.to_hex() + ); + + let path = tree.get_hash_path(2); + + let expected_hash_path = vec![ + ( + "06c2335d6f7acb84bbc7d0892cefebb7ca31169a89024f24814d5785e0d05324", + "12dc36b01cbd8a6248b04e08f0ec91aa6d11a91f030b4a7b1460281859942185", + ), + ( + "1f399ea0d6aaf602c7cbcb6ae8cda0e6b6487836c017163888ed4fd38b548389", + "220dd1b310caa4a6af755b4c893d956c48f31642b487164b258f2973aac2c28f", + ), + ( + "25cbb3084647221ffcb535945bb65bd70e0809834dc7a6d865a3f2bb046cdc29", + "2cc463fc8c9a4eda416f3e490876672f644708dd0330a915f6835d8396fa8f20", + ), + ]; + + for (got, expected_segment) in path.into_iter().zip(expected_hash_path) { + assert_eq!(got.0.to_hex().as_str(), expected_segment.0); + assert_eq!(got.1.to_hex().as_str(), expected_segment.1) + } + } + + #[test] + fn test_check_membership() { + struct Test<'a> { + // Index of the leaf in the MerkleTree + index: &'a str, + // Returns true if the leaf is indeed a part of the MerkleTree at the specified index + result: bool, + // The message is used to derive the leaf at `index` by using the specified hash + message: Vec, + // If this is true, then before checking for membership + // we update the tree with the message at that index + should_update_tree: bool, + error_msg: &'a str, + } + // Note these test cases are not independent. + // i.e. If you update index 0, then this will be saved for the next test + let tests = vec![ + Test { + index : "0", + result : true, + message : vec![0;64], + should_update_tree: false, + error_msg : "this should always be true, since the tree is initialized with 64 zeroes" + }, + Test { + index : "0", + result : false, + message : vec![10;64], + should_update_tree: false, + error_msg : "this should be false, since the tree was not updated, however the message which derives the leaf has changed" + }, + Test { + index : "0", + result : true, + message : vec![1;64], + should_update_tree: true, + error_msg : "this should be true, since we are updating the tree" + }, + Test { + index : "0", + result : true, + message : vec![1;64], + should_update_tree: false, + error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" + }, + Test { + index : "4", + result : true, + message : vec![0;64], + should_update_tree: false, + error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" + }, + ]; + + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + let mut msg_hasher: blake2::Blake2s = MessageHasher::new(); + + let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); + + for test_vector in tests { + let index = FieldElement::try_from_str(test_vector.index).unwrap(); + let index_as_usize: usize = test_vector.index.parse().unwrap(); + let mut index_bits = index.bits(); + index_bits.reverse(); + + let leaf = msg_hasher.hash(&test_vector.message); + + let mut root = tree.root(); + if test_vector.should_update_tree { + root = tree.update_message(index_as_usize, &test_vector.message); + } + + let hash_path = tree.get_hash_path(index_as_usize); + let mut hash_path_ref = Vec::new(); + for (i, path_pair) in hash_path.into_iter().enumerate() { + let path_bit = index_bits[i]; + let hash = if !path_bit { path_pair.1 } else { path_pair.0 }; + hash_path_ref.push(hash); + } + let hash_path_ref = hash_path_ref.iter().collect(); + + let bb = Barretenberg::new(); + let calculated_merkle_root = super::calculate_merkle_root( + |left, right| bb.compress_native(left, right), + hash_path_ref, + &index, + &leaf, + ); + let is_leaf_in_tree = root == calculated_merkle_root; + + assert_eq!( + is_leaf_in_tree, test_vector.result, + "{}", + test_vector.error_msg + ); + } + } + + // This test uses `update_leaf` directly rather than `update_message` + #[test] + fn simple_shield() { + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + + let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); + + let barretenberg = Barretenberg::new(); + let pubkey_x = FieldElement::from_hex( + "0x0bff8247aa94b08d1c680d7a3e10831bd8c8cf2ea2c756b0d1d89acdcad877ad", + ) + .unwrap(); + let pubkey_y = FieldElement::from_hex( + "0x2a5d7253a6ed48462fedb2d350cc768d13956310f54e73a8a47914f34a34c5c4", + ) + .unwrap(); + let (note_commitment_x, _) = barretenberg.encrypt(vec![pubkey_x, pubkey_y]); + dbg!(note_commitment_x.to_hex()); + let leaf = note_commitment_x; + + let index = FieldElement::try_from_str("0").unwrap(); + let index_as_usize: usize = 0_usize; + let mut index_bits = index.bits(); + index_bits.reverse(); + + let root = tree.update_leaf(index_as_usize, leaf); + + let hash_path = tree.get_hash_path(index_as_usize); + let mut hash_path_ref = Vec::new(); + for (i, path_pair) in hash_path.into_iter().enumerate() { + let path_bit = index_bits[i]; + let hash = if !path_bit { path_pair.1 } else { path_pair.0 }; + hash_path_ref.push(hash); + } + let hash_path_ref = hash_path_ref.iter().collect(); + let bb = Barretenberg::new(); + let calculated_merkle_root = super::calculate_merkle_root( + |left, right| bb.compress_native(left, right), + hash_path_ref, + &index, + &leaf, + ); + let is_leaf_in_tree = root == calculated_merkle_root; + + assert!(is_leaf_in_tree) + } +} diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs deleted file mode 100644 index a33f168f..00000000 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs +++ /dev/null @@ -1,263 +0,0 @@ -use crate::{pedersen::Pedersen, Barretenberg}; -use common::{acvm::FieldElement, merkle::PathHasher}; - -impl PathHasher for Barretenberg { - fn hash(&self, left: &FieldElement, right: &FieldElement) -> FieldElement { - self.compress_native(left, right) - } - - fn new() -> Self { - Barretenberg::new() - } -} - -// TODO: alter this method so that it only processes one hash per level rather than overriding -// the one of leaves for each level of the hash path -pub(super) fn check_membership( - path_hasher: &impl PathHasher, - hash_path: Vec<&FieldElement>, - root: &FieldElement, - index: &FieldElement, - leaf: &FieldElement, -) -> bool { - let mut index_bits = index.bits(); - index_bits.reverse(); - - let mut current = *leaf; - - for (i, path_elem) in hash_path.into_iter().enumerate() { - let path_bit = index_bits[i]; - let (hash_left, hash_right) = if !path_bit { - (current, *path_elem) - } else { - (*path_elem, current) - }; - current = path_hasher.hash(&hash_left, &hash_right); - } - - ¤t == root -} - -#[cfg(test)] -mod tests { - use super::*; - use common::merkle::{MerkleTree, MessageHasher}; - - #[test] - fn basic_interop_initial_root() { - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - // Test that the initial root is computed correctly - let tree: MerkleTree = MerkleTree::new(3, &temp_dir); - // Copied from barretenberg by copying the stdout from MemoryTree - let expected_hex = "04ccfbbb859b8605546e03dcaf41393476642859ff7f99446c054b841f0e05c8"; - assert_eq!(tree.root().to_hex(), expected_hex) - } - #[test] - fn basic_interop_hashpath() { - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - // Test that the hashpath is correct - let tree: MerkleTree = MerkleTree::new(3, &temp_dir); - - let path = tree.get_hash_path(0); - - let expected_hash_path = vec![ - ( - "1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", - "1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", - ), - ( - "0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", - "0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", - ), - ( - "22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", - "22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", - ), - ]; - - for (got, expected_segment) in path.into_iter().zip(expected_hash_path) { - assert_eq!(got.0.to_hex().as_str(), expected_segment.0); - assert_eq!(got.1.to_hex().as_str(), expected_segment.1) - } - } - - #[test] - fn basic_interop_update() { - // Test that computing the HashPath is correct - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); - - tree.update_message(0, &[0; 64]); - tree.update_message(1, &[1; 64]); - tree.update_message(2, &[2; 64]); - tree.update_message(3, &[3; 64]); - tree.update_message(4, &[4; 64]); - tree.update_message(5, &[5; 64]); - tree.update_message(6, &[6; 64]); - let root = tree.update_message(7, &[7; 64]); - - assert_eq!( - "0ef8e14db4762ebddadb23b2225f93ca200a4c9bd37130b4d028c971bbad16b5", - root.to_hex() - ); - - let path = tree.get_hash_path(2); - - let expected_hash_path = vec![ - ( - "06c2335d6f7acb84bbc7d0892cefebb7ca31169a89024f24814d5785e0d05324", - "12dc36b01cbd8a6248b04e08f0ec91aa6d11a91f030b4a7b1460281859942185", - ), - ( - "1f399ea0d6aaf602c7cbcb6ae8cda0e6b6487836c017163888ed4fd38b548389", - "220dd1b310caa4a6af755b4c893d956c48f31642b487164b258f2973aac2c28f", - ), - ( - "25cbb3084647221ffcb535945bb65bd70e0809834dc7a6d865a3f2bb046cdc29", - "2cc463fc8c9a4eda416f3e490876672f644708dd0330a915f6835d8396fa8f20", - ), - ]; - - for (got, expected_segment) in path.into_iter().zip(expected_hash_path) { - assert_eq!(got.0.to_hex().as_str(), expected_segment.0); - assert_eq!(got.1.to_hex().as_str(), expected_segment.1) - } - } - - #[test] - fn test_check_membership() { - struct Test<'a> { - // Index of the leaf in the MerkleTree - index: &'a str, - // Returns true if the leaf is indeed a part of the MerkleTree at the specified index - result: bool, - // The message is used to derive the leaf at `index` by using the specified hash - message: Vec, - // If this is true, then before checking for membership - // we update the tree with the message at that index - should_update_tree: bool, - error_msg: &'a str, - } - // Note these test cases are not independent. - // i.e. If you update index 0, then this will be saved for the next test - let tests = vec![ - Test { - index : "0", - result : true, - message : vec![0;64], - should_update_tree: false, - error_msg : "this should always be true, since the tree is initialized with 64 zeroes" - }, - Test { - index : "0", - result : false, - message : vec![10;64], - should_update_tree: false, - error_msg : "this should be false, since the tree was not updated, however the message which derives the leaf has changed" - }, - Test { - index : "0", - result : true, - message : vec![1;64], - should_update_tree: true, - error_msg : "this should be true, since we are updating the tree" - }, - Test { - index : "0", - result : true, - message : vec![1;64], - should_update_tree: false, - error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" - }, - Test { - index : "4", - result : true, - message : vec![0;64], - should_update_tree: false, - error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" - }, - ]; - - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - let mut msg_hasher: blake2::Blake2s = MessageHasher::new(); - - let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); - - for test_vector in tests { - let index = FieldElement::try_from_str(test_vector.index).unwrap(); - let index_as_usize: usize = test_vector.index.parse().unwrap(); - let mut index_bits = index.bits(); - index_bits.reverse(); - - let leaf = msg_hasher.hash(&test_vector.message); - - let mut root = tree.root(); - if test_vector.should_update_tree { - root = tree.update_message(index_as_usize, &test_vector.message); - } - - let hash_path = tree.get_hash_path(index_as_usize); - let mut hash_path_ref = Vec::new(); - for (i, path_pair) in hash_path.into_iter().enumerate() { - let path_bit = index_bits[i]; - let hash = if !path_bit { path_pair.1 } else { path_pair.0 }; - hash_path_ref.push(hash); - } - let hash_path_ref = hash_path_ref.iter().collect(); - let is_leaf_in_tree = - super::check_membership(&Barretenberg::new(), hash_path_ref, &root, &index, &leaf); - - assert_eq!( - is_leaf_in_tree, test_vector.result, - "{}", - test_vector.error_msg - ); - } - } - - // This test uses `update_leaf` directly rather than `update_message` - #[test] - fn simple_shield() { - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - - let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); - - let barretenberg = Barretenberg::new(); - let pubkey_x = FieldElement::from_hex( - "0x0bff8247aa94b08d1c680d7a3e10831bd8c8cf2ea2c756b0d1d89acdcad877ad", - ) - .unwrap(); - let pubkey_y = FieldElement::from_hex( - "0x2a5d7253a6ed48462fedb2d350cc768d13956310f54e73a8a47914f34a34c5c4", - ) - .unwrap(); - let (note_commitment_x, _) = barretenberg.encrypt(vec![pubkey_x, pubkey_y]); - dbg!(note_commitment_x.to_hex()); - let leaf = note_commitment_x; - - let index = FieldElement::try_from_str("0").unwrap(); - let index_as_usize: usize = 0_usize; - let mut index_bits = index.bits(); - index_bits.reverse(); - - let root = tree.update_leaf(index_as_usize, leaf); - - let hash_path = tree.get_hash_path(index_as_usize); - let mut hash_path_ref = Vec::new(); - for (i, path_pair) in hash_path.into_iter().enumerate() { - let path_bit = index_bits[i]; - let hash = if !path_bit { path_pair.1 } else { path_pair.0 }; - hash_path_ref.push(hash); - } - let hash_path_ref = hash_path_ref.iter().collect(); - let is_leaf_in_tree = - super::check_membership(&Barretenberg::new(), hash_path_ref, &root, &index, &leaf); - - assert!(is_leaf_in_tree) - } -} From e3a09487104f2826af113efaedbc14054f92ca11 Mon Sep 17 00:00:00 2001 From: Tom French Date: Wed, 26 Apr 2023 00:23:46 +0100 Subject: [PATCH 2/8] chore: replace `calculate_merkle_root` with iterator based impl --- .../src/acvm_interop/pwg.rs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs index 9f2da81b..3b8632b5 100644 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs @@ -177,19 +177,17 @@ fn calculate_merkle_root( let mut index_bits = index.bits(); index_bits.reverse(); - let mut current = *leaf; - - for (i, path_elem) in hash_path.into_iter().enumerate() { - let path_bit = index_bits[i]; - let (hash_left, hash_right) = if !path_bit { - (current, *path_elem) - } else { - (*path_elem, current) - }; - current = hash_func(&hash_left, &hash_right); - } - - current + index_bits.into_iter().zip(hash_path.into_iter()).fold( + *leaf, + |current_node, (path_bit, path_elem)| { + let (left, right) = if !path_bit { + (¤t_node, path_elem) + } else { + (path_elem, ¤t_node) + }; + hash_func(left, right) + }, + ) } #[cfg(test)] From 68806b97003656443d2368073978f657ecb67937 Mon Sep 17 00:00:00 2001 From: Tom French Date: Wed, 26 Apr 2023 12:25:27 +0100 Subject: [PATCH 3/8] chore: add an assert that hash path and index path match --- acvm_backend_barretenberg/src/acvm_interop/pwg.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs index 3b8632b5..f4ab7934 100644 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs @@ -174,9 +174,13 @@ fn calculate_merkle_root( index: &FieldElement, leaf: &FieldElement, ) -> FieldElement { - let mut index_bits = index.bits(); + let mut index_bits: Vec = index.bits(); index_bits.reverse(); + assert!( + hash_path.len() <= index_bits.len(), + "hash path exceeds max depth of tree" + ); index_bits.into_iter().zip(hash_path.into_iter()).fold( *leaf, |current_node, (path_bit, path_elem)| { From 6edcc8e8b667eb45430f927fbadc9e509cf9506e Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Wed, 26 Apr 2023 15:01:56 +0100 Subject: [PATCH 4/8] Update acvm_backend_barretenberg/src/acvm_interop/pwg.rs --- acvm_backend_barretenberg/src/acvm_interop/pwg.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs index f4ab7934..e70396b4 100644 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs @@ -166,8 +166,6 @@ impl PartialWitnessGenerator for Barretenberg { } } -// TODO: alter this method so that it only processes one hash per level rather than overriding -// the one of leaves for each level of the hash path fn calculate_merkle_root( hash_func: impl Fn(&FieldElement, &FieldElement) -> FieldElement, hash_path: Vec<&FieldElement>, From 56ef1ebd54cbc3a819f1855c82e452570fdf178e Mon Sep 17 00:00:00 2001 From: Tom French Date: Wed, 26 Apr 2023 15:18:27 +0100 Subject: [PATCH 5/8] chore: rename `calculate_merkle_root` to `compute_merkle_root` --- acvm_backend_barretenberg/src/acvm_interop/pwg.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs index e70396b4..2339d069 100644 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs @@ -46,14 +46,14 @@ impl PartialWitnessGenerator for Barretenberg { .map(|input| witness_to_value(initial_witness, input.witness)) .collect(); - let calculated_merkle_root = calculate_merkle_root( + let computed_merkle_root = compute_merkle_root( |left, right| self.compress_native(left, right), hash_path?, index, leaf, ); - let result = if &calculated_merkle_root == root { + let result = if &computed_merkle_root == root { FieldElement::one() } else { FieldElement::zero() @@ -166,7 +166,7 @@ impl PartialWitnessGenerator for Barretenberg { } } -fn calculate_merkle_root( +fn compute_merkle_root( hash_func: impl Fn(&FieldElement, &FieldElement) -> FieldElement, hash_path: Vec<&FieldElement>, index: &FieldElement, @@ -375,13 +375,13 @@ mod tests { let hash_path_ref = hash_path_ref.iter().collect(); let bb = Barretenberg::new(); - let calculated_merkle_root = super::calculate_merkle_root( + let computed_merkle_root = super::compute_merkle_root( |left, right| bb.compress_native(left, right), hash_path_ref, &index, &leaf, ); - let is_leaf_in_tree = root == calculated_merkle_root; + let is_leaf_in_tree = root == computed_merkle_root; assert_eq!( is_leaf_in_tree, test_vector.result, @@ -428,13 +428,13 @@ mod tests { } let hash_path_ref = hash_path_ref.iter().collect(); let bb = Barretenberg::new(); - let calculated_merkle_root = super::calculate_merkle_root( + let computed_merkle_root = super::compute_merkle_root( |left, right| bb.compress_native(left, right), hash_path_ref, &index, &leaf, ); - let is_leaf_in_tree = root == calculated_merkle_root; + let is_leaf_in_tree = root == computed_merkle_root; assert!(is_leaf_in_tree) } From 4beda62669a148df168fb830efb97ff5a9bb735c Mon Sep 17 00:00:00 2001 From: Tom French Date: Wed, 26 Apr 2023 16:18:41 +0100 Subject: [PATCH 6/8] chore: pull merkle tree implementation into `acvm-backend-barretenberg` --- Cargo.lock | 3 +- acvm_backend_barretenberg/Cargo.toml | 1 + .../src/acvm_interop/pwg.rs | 96 +----------- acvm_backend_barretenberg/src/lib.rs | 2 + .../src/merkle.rs | 143 ++++++++++++++++-- common/Cargo.toml | 2 - common/src/lib.rs | 1 - 7 files changed, 132 insertions(+), 116 deletions(-) rename {common => acvm_backend_barretenberg}/src/merkle.rs (65%) diff --git a/Cargo.lock b/Cargo.lock index fc0a411e..208c4d93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,7 @@ dependencies = [ "getrandom", "pkg-config", "rust-embed", + "sled", "tempfile", "wasmer", ] @@ -463,12 +464,10 @@ name = "common" version = "0.1.0" dependencies = [ "acvm", - "blake2", "dirs 3.0.2", "futures-util", "indicatif", "reqwest", - "sled", "tokio", ] diff --git a/acvm_backend_barretenberg/Cargo.toml b/acvm_backend_barretenberg/Cargo.toml index e05e0646..8f11f7dc 100644 --- a/acvm_backend_barretenberg/Cargo.toml +++ b/acvm_backend_barretenberg/Cargo.toml @@ -24,6 +24,7 @@ getrandom = { version = "0.2", optional = true } pkg-config = "0.3" [dev-dependencies] +sled = "0.34.6" tempfile = "*" [features] diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs index 2339d069..45587a61 100644 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs @@ -194,103 +194,9 @@ fn compute_merkle_root( #[cfg(test)] mod tests { + use crate::merkle::{MerkleTree, MessageHasher}; use crate::{pedersen::Pedersen, Barretenberg}; use common::acvm::FieldElement; - use common::merkle::{MerkleTree, MessageHasher, PathHasher}; - - impl PathHasher for Barretenberg { - fn hash(&self, left: &FieldElement, right: &FieldElement) -> FieldElement { - self.compress_native(left, right) - } - - fn new() -> Self { - Barretenberg::new() - } - } - - #[test] - fn basic_interop_initial_root() { - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - // Test that the initial root is computed correctly - let tree: MerkleTree = MerkleTree::new(3, &temp_dir); - // Copied from barretenberg by copying the stdout from MemoryTree - let expected_hex = "04ccfbbb859b8605546e03dcaf41393476642859ff7f99446c054b841f0e05c8"; - assert_eq!(tree.root().to_hex(), expected_hex) - } - #[test] - fn basic_interop_hashpath() { - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - // Test that the hashpath is correct - let tree: MerkleTree = MerkleTree::new(3, &temp_dir); - - let path = tree.get_hash_path(0); - - let expected_hash_path = vec![ - ( - "1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", - "1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", - ), - ( - "0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", - "0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", - ), - ( - "22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", - "22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", - ), - ]; - - for (got, expected_segment) in path.into_iter().zip(expected_hash_path) { - assert_eq!(got.0.to_hex().as_str(), expected_segment.0); - assert_eq!(got.1.to_hex().as_str(), expected_segment.1) - } - } - - #[test] - fn basic_interop_update() { - // Test that computing the HashPath is correct - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); - - tree.update_message(0, &[0; 64]); - tree.update_message(1, &[1; 64]); - tree.update_message(2, &[2; 64]); - tree.update_message(3, &[3; 64]); - tree.update_message(4, &[4; 64]); - tree.update_message(5, &[5; 64]); - tree.update_message(6, &[6; 64]); - let root = tree.update_message(7, &[7; 64]); - - assert_eq!( - "0ef8e14db4762ebddadb23b2225f93ca200a4c9bd37130b4d028c971bbad16b5", - root.to_hex() - ); - - let path = tree.get_hash_path(2); - - let expected_hash_path = vec![ - ( - "06c2335d6f7acb84bbc7d0892cefebb7ca31169a89024f24814d5785e0d05324", - "12dc36b01cbd8a6248b04e08f0ec91aa6d11a91f030b4a7b1460281859942185", - ), - ( - "1f399ea0d6aaf602c7cbcb6ae8cda0e6b6487836c017163888ed4fd38b548389", - "220dd1b310caa4a6af755b4c893d956c48f31642b487164b258f2973aac2c28f", - ), - ( - "25cbb3084647221ffcb535945bb65bd70e0809834dc7a6d865a3f2bb046cdc29", - "2cc463fc8c9a4eda416f3e490876672f644708dd0330a915f6835d8396fa8f20", - ), - ]; - - for (got, expected_segment) in path.into_iter().zip(expected_hash_path) { - assert_eq!(got.0.to_hex().as_str(), expected_segment.0); - assert_eq!(got.1.to_hex().as_str(), expected_segment.1) - } - } #[test] fn test_check_membership() { diff --git a/acvm_backend_barretenberg/src/lib.rs b/acvm_backend_barretenberg/src/lib.rs index 9ce54c92..7f748ced 100644 --- a/acvm_backend_barretenberg/src/lib.rs +++ b/acvm_backend_barretenberg/src/lib.rs @@ -12,6 +12,8 @@ mod acvm_interop; mod composer; #[cfg(all(feature = "native", test))] mod crs; +#[cfg(test)] +mod merkle; mod pedersen; mod pippenger; mod scalar_mul; diff --git a/common/src/merkle.rs b/acvm_backend_barretenberg/src/merkle.rs similarity index 65% rename from common/src/merkle.rs rename to acvm_backend_barretenberg/src/merkle.rs index 40105356..be912a5f 100644 --- a/common/src/merkle.rs +++ b/acvm_backend_barretenberg/src/merkle.rs @@ -1,14 +1,25 @@ -use acvm::FieldElement; +use crate::{pedersen::Pedersen, Barretenberg}; +use common::acvm::FieldElement; use std::{convert::TryInto, path::Path}; // Hashes the leaves up the path, on the way to the root -pub trait PathHasher { +pub(crate) trait PathHasher { fn new() -> Self; fn hash(&self, left: &FieldElement, right: &FieldElement) -> FieldElement; } +impl PathHasher for Barretenberg { + fn hash(&self, left: &FieldElement, right: &FieldElement) -> FieldElement { + self.compress_native(left, right) + } + + fn new() -> Self { + Barretenberg::new() + } +} + // Hashes the message into a leaf -pub trait MessageHasher { +pub(crate) trait MessageHasher { fn new() -> Self; fn hash(&mut self, msg: &[u8]) -> FieldElement; } @@ -36,15 +47,16 @@ impl MessageHasher for blake2::Blake2s { // // With sparse merkle, one can update at any index -pub(crate) type HashPath = Vec<(FieldElement, FieldElement)>; +type HashPath = Vec<(FieldElement, FieldElement)>; -pub fn flatten_path(path: Vec<(FieldElement, FieldElement)>) -> Vec { +#[allow(dead_code)] +fn flatten_path(path: Vec<(FieldElement, FieldElement)>) -> Vec { path.into_iter() .flat_map(|(left, right)| std::iter::once(left).chain(std::iter::once(right))) .collect() } -pub struct MerkleTree { +pub(crate) struct MerkleTree { depth: u32, total_size: u32, db: sled::Db, @@ -95,6 +107,7 @@ fn insert_preimage(db: &mut sled::Db, index: u32, value: Vec) { tree.insert(index.to_be_bytes(), value).unwrap(); } +#[allow(dead_code)] fn fetch_preimage(db: &sled::Db, index: usize) -> Vec { let tree = db.open_tree("preimages").unwrap(); @@ -122,6 +135,7 @@ fn insert_hash(db: &mut sled::Db, index: u32, hash: FieldElement) { .unwrap(); } +#[allow(dead_code)] fn find_hash_from_value(db: &sled::Db, leaf_value: &FieldElement) -> Option { let tree = db.open_tree("hashes").unwrap(); @@ -137,7 +151,8 @@ fn find_hash_from_value(db: &sled::Db, leaf_value: &FieldElement) -> Option MerkleTree { - pub fn from_path>( + #[allow(dead_code)] + pub(crate) fn from_path>( path: P, barretenberg: PH, msg_hasher: MH, @@ -159,7 +174,8 @@ impl MerkleTree { msg_hasher, } } - pub fn new>(depth: u32, path: P) -> MerkleTree { + + pub(crate) fn new>(depth: u32, path: P) -> MerkleTree { let barretenberg = PH::new(); let mut msg_hasher = MH::new(); @@ -213,7 +229,7 @@ impl MerkleTree { } } - pub fn get_hash_path(&self, mut index: usize) -> HashPath { + pub(crate) fn get_hash_path(&self, mut index: usize) -> HashPath { let mut path = HashPath::with_capacity(self.depth as usize); let mut offset = 0usize; @@ -231,7 +247,7 @@ impl MerkleTree { path } /// Updates the message at index and computes the new tree root - pub fn update_message(&mut self, index: usize, new_message: &[u8]) -> FieldElement { + pub(crate) fn update_message(&mut self, index: usize, new_message: &[u8]) -> FieldElement { let current = self.msg_hasher.hash(new_message); insert_preimage(&mut self.db, index as u32, new_message.to_vec()); @@ -250,19 +266,25 @@ impl MerkleTree { } } - pub fn find_index_from_leaf(&self, leaf_value: &FieldElement) -> Option { + #[allow(dead_code)] + pub(crate) fn find_index_from_leaf(&self, leaf_value: &FieldElement) -> Option { let index = find_hash_from_value(&self.db, leaf_value); index.map(|val| val as usize) } + #[allow(dead_code)] // TODO: this gets updated to be -1 on the latest barretenberg branch - pub fn find_index_for_empty_leaf(&self) -> usize { + pub(crate) fn find_index_for_empty_leaf(&self) -> usize { let index = fetch_empty_index(&self.db); index as usize } /// Update the element at index and compute the new tree root - pub fn update_leaf(&mut self, mut index: usize, mut current: FieldElement) -> FieldElement { + pub(crate) fn update_leaf( + &mut self, + mut index: usize, + mut current: FieldElement, + ) -> FieldElement { // Note that this method does not update the list of messages [preimages]| // use `update_message` to do this self.check_if_index_valid_and_increment(index); @@ -286,15 +308,104 @@ impl MerkleTree { insert_root(&mut self.db, current); current } + + #[allow(dead_code)] /// Gets a message at `index`. This is not the leaf - pub fn get_message_at_index(&self, index: usize) -> Vec { + pub(crate) fn get_message_at_index(&self, index: usize) -> Vec { fetch_preimage(&self.db, index) } - pub fn root(&self) -> FieldElement { + pub(crate) fn root(&self) -> FieldElement { fetch_root(&self.db) } - pub fn depth(&self) -> u32 { + + #[allow(dead_code)] + pub(crate) fn depth(&self) -> u32 { self.depth } } + +#[test] +fn basic_interop_initial_root() { + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + // Test that the initial root is computed correctly + let tree: MerkleTree = MerkleTree::new(3, &temp_dir); + // Copied from barretenberg by copying the stdout from MemoryTree + let expected_hex = "04ccfbbb859b8605546e03dcaf41393476642859ff7f99446c054b841f0e05c8"; + assert_eq!(tree.root().to_hex(), expected_hex) +} + +#[test] +fn basic_interop_hashpath() { + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + // Test that the hashpath is correct + let tree: MerkleTree = MerkleTree::new(3, &temp_dir); + + let path = tree.get_hash_path(0); + + let expected_hash_path = vec![ + ( + "1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", + "1cdcf02431ba623767fe389337d011df1048dcc24b98ed81cec97627bab454a0", + ), + ( + "0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", + "0b5e9666e7323ce925c28201a97ddf4144ac9d148448ed6f49f9008719c1b85b", + ), + ( + "22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", + "22ec636f8ad30ef78c42b7fe2be4a4cacf5a445cfb5948224539f59a11d70775", + ), + ]; + + for (got, expected_segment) in path.into_iter().zip(expected_hash_path) { + assert_eq!(got.0.to_hex().as_str(), expected_segment.0); + assert_eq!(got.1.to_hex().as_str(), expected_segment.1) + } +} + +#[test] +fn basic_interop_update() { + // Test that computing the HashPath is correct + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); + + tree.update_message(0, &[0; 64]); + tree.update_message(1, &[1; 64]); + tree.update_message(2, &[2; 64]); + tree.update_message(3, &[3; 64]); + tree.update_message(4, &[4; 64]); + tree.update_message(5, &[5; 64]); + tree.update_message(6, &[6; 64]); + let root = tree.update_message(7, &[7; 64]); + + assert_eq!( + "0ef8e14db4762ebddadb23b2225f93ca200a4c9bd37130b4d028c971bbad16b5", + root.to_hex() + ); + + let path = tree.get_hash_path(2); + + let expected_hash_path = vec![ + ( + "06c2335d6f7acb84bbc7d0892cefebb7ca31169a89024f24814d5785e0d05324", + "12dc36b01cbd8a6248b04e08f0ec91aa6d11a91f030b4a7b1460281859942185", + ), + ( + "1f399ea0d6aaf602c7cbcb6ae8cda0e6b6487836c017163888ed4fd38b548389", + "220dd1b310caa4a6af755b4c893d956c48f31642b487164b258f2973aac2c28f", + ), + ( + "25cbb3084647221ffcb535945bb65bd70e0809834dc7a6d865a3f2bb046cdc29", + "2cc463fc8c9a4eda416f3e490876672f644708dd0330a915f6835d8396fa8f20", + ), + ]; + + for (got, expected_segment) in path.into_iter().zip(expected_hash_path) { + assert_eq!(got.0.to_hex().as_str(), expected_segment.0); + assert_eq!(got.1.to_hex().as_str(), expected_segment.1) + } +} diff --git a/common/Cargo.toml b/common/Cargo.toml index b4285e87..52ac264e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -12,8 +12,6 @@ publish = false [dependencies] acvm = { version = "0.9.0", features = ["bn254"] } -sled = "0.34.6" -blake2 = "0.9.1" dirs = { version = "3.0", optional = true } reqwest = { version = "0.11.16", optional = true, default-features = false, features = ["stream", "rustls-tls"] } tokio = { version = "1.0", optional = true } diff --git a/common/src/lib.rs b/common/src/lib.rs index 9084ec93..e2919745 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -5,7 +5,6 @@ pub mod barretenberg_structures; #[cfg(feature = "std")] pub mod crs; -pub mod merkle; // Re-export acvm pub use acvm; From 6cd899ab9163b305555256cc2bf98e738889214d Mon Sep 17 00:00:00 2001 From: Tom French Date: Wed, 26 Apr 2023 16:51:33 +0100 Subject: [PATCH 7/8] chore: move `compute_merkle_root` back into `pwg/merkle.rs` --- .../src/acvm_interop/pwg.rs | 184 +----------------- .../src/acvm_interop/pwg/merkle.rs | 181 +++++++++++++++++ 2 files changed, 184 insertions(+), 181 deletions(-) create mode 100644 acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs index 45587a61..4952ab6e 100644 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg.rs +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg.rs @@ -13,6 +13,8 @@ use crate::Barretenberg; use blake2::{Blake2s, Digest}; +mod merkle; + impl PartialWitnessGenerator for Barretenberg { fn solve_black_box_function_call( &self, @@ -46,7 +48,7 @@ impl PartialWitnessGenerator for Barretenberg { .map(|input| witness_to_value(initial_witness, input.witness)) .collect(); - let computed_merkle_root = compute_merkle_root( + let computed_merkle_root = merkle::compute_merkle_root( |left, right| self.compress_native(left, right), hash_path?, index, @@ -165,183 +167,3 @@ impl PartialWitnessGenerator for Barretenberg { Ok(OpcodeResolution::Solved) } } - -fn compute_merkle_root( - hash_func: impl Fn(&FieldElement, &FieldElement) -> FieldElement, - hash_path: Vec<&FieldElement>, - index: &FieldElement, - leaf: &FieldElement, -) -> FieldElement { - let mut index_bits: Vec = index.bits(); - index_bits.reverse(); - - assert!( - hash_path.len() <= index_bits.len(), - "hash path exceeds max depth of tree" - ); - index_bits.into_iter().zip(hash_path.into_iter()).fold( - *leaf, - |current_node, (path_bit, path_elem)| { - let (left, right) = if !path_bit { - (¤t_node, path_elem) - } else { - (path_elem, ¤t_node) - }; - hash_func(left, right) - }, - ) -} - -#[cfg(test)] -mod tests { - use crate::merkle::{MerkleTree, MessageHasher}; - use crate::{pedersen::Pedersen, Barretenberg}; - use common::acvm::FieldElement; - - #[test] - fn test_check_membership() { - struct Test<'a> { - // Index of the leaf in the MerkleTree - index: &'a str, - // Returns true if the leaf is indeed a part of the MerkleTree at the specified index - result: bool, - // The message is used to derive the leaf at `index` by using the specified hash - message: Vec, - // If this is true, then before checking for membership - // we update the tree with the message at that index - should_update_tree: bool, - error_msg: &'a str, - } - // Note these test cases are not independent. - // i.e. If you update index 0, then this will be saved for the next test - let tests = vec![ - Test { - index : "0", - result : true, - message : vec![0;64], - should_update_tree: false, - error_msg : "this should always be true, since the tree is initialized with 64 zeroes" - }, - Test { - index : "0", - result : false, - message : vec![10;64], - should_update_tree: false, - error_msg : "this should be false, since the tree was not updated, however the message which derives the leaf has changed" - }, - Test { - index : "0", - result : true, - message : vec![1;64], - should_update_tree: true, - error_msg : "this should be true, since we are updating the tree" - }, - Test { - index : "0", - result : true, - message : vec![1;64], - should_update_tree: false, - error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" - }, - Test { - index : "4", - result : true, - message : vec![0;64], - should_update_tree: false, - error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" - }, - ]; - - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - let mut msg_hasher: blake2::Blake2s = MessageHasher::new(); - - let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); - - for test_vector in tests { - let index = FieldElement::try_from_str(test_vector.index).unwrap(); - let index_as_usize: usize = test_vector.index.parse().unwrap(); - let mut index_bits = index.bits(); - index_bits.reverse(); - - let leaf = msg_hasher.hash(&test_vector.message); - - let mut root = tree.root(); - if test_vector.should_update_tree { - root = tree.update_message(index_as_usize, &test_vector.message); - } - - let hash_path = tree.get_hash_path(index_as_usize); - let mut hash_path_ref = Vec::new(); - for (i, path_pair) in hash_path.into_iter().enumerate() { - let path_bit = index_bits[i]; - let hash = if !path_bit { path_pair.1 } else { path_pair.0 }; - hash_path_ref.push(hash); - } - let hash_path_ref = hash_path_ref.iter().collect(); - - let bb = Barretenberg::new(); - let computed_merkle_root = super::compute_merkle_root( - |left, right| bb.compress_native(left, right), - hash_path_ref, - &index, - &leaf, - ); - let is_leaf_in_tree = root == computed_merkle_root; - - assert_eq!( - is_leaf_in_tree, test_vector.result, - "{}", - test_vector.error_msg - ); - } - } - - // This test uses `update_leaf` directly rather than `update_message` - #[test] - fn simple_shield() { - use tempfile::tempdir; - let temp_dir = tempdir().unwrap(); - - let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); - - let barretenberg = Barretenberg::new(); - let pubkey_x = FieldElement::from_hex( - "0x0bff8247aa94b08d1c680d7a3e10831bd8c8cf2ea2c756b0d1d89acdcad877ad", - ) - .unwrap(); - let pubkey_y = FieldElement::from_hex( - "0x2a5d7253a6ed48462fedb2d350cc768d13956310f54e73a8a47914f34a34c5c4", - ) - .unwrap(); - let (note_commitment_x, _) = barretenberg.encrypt(vec![pubkey_x, pubkey_y]); - dbg!(note_commitment_x.to_hex()); - let leaf = note_commitment_x; - - let index = FieldElement::try_from_str("0").unwrap(); - let index_as_usize: usize = 0_usize; - let mut index_bits = index.bits(); - index_bits.reverse(); - - let root = tree.update_leaf(index_as_usize, leaf); - - let hash_path = tree.get_hash_path(index_as_usize); - let mut hash_path_ref = Vec::new(); - for (i, path_pair) in hash_path.into_iter().enumerate() { - let path_bit = index_bits[i]; - let hash = if !path_bit { path_pair.1 } else { path_pair.0 }; - hash_path_ref.push(hash); - } - let hash_path_ref = hash_path_ref.iter().collect(); - let bb = Barretenberg::new(); - let computed_merkle_root = super::compute_merkle_root( - |left, right| bb.compress_native(left, right), - hash_path_ref, - &index, - &leaf, - ); - let is_leaf_in_tree = root == computed_merkle_root; - - assert!(is_leaf_in_tree) - } -} diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs new file mode 100644 index 00000000..6518fd67 --- /dev/null +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs @@ -0,0 +1,181 @@ +use common::acvm::FieldElement; + +pub(super) fn compute_merkle_root( + hash_func: impl Fn(&FieldElement, &FieldElement) -> FieldElement, + hash_path: Vec<&FieldElement>, + index: &FieldElement, + leaf: &FieldElement, +) -> FieldElement { + let mut index_bits: Vec = index.bits(); + index_bits.reverse(); + + assert!( + hash_path.len() <= index_bits.len(), + "hash path exceeds max depth of tree" + ); + index_bits.into_iter().zip(hash_path.into_iter()).fold( + *leaf, + |current_node, (path_bit, path_elem)| { + let (left, right) = if !path_bit { + (¤t_node, path_elem) + } else { + (path_elem, ¤t_node) + }; + hash_func(left, right) + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::merkle::{MerkleTree, MessageHasher}; + use crate::{pedersen::Pedersen, Barretenberg}; + use common::acvm::FieldElement; + + #[test] + fn test_check_membership() { + struct Test<'a> { + // Index of the leaf in the MerkleTree + index: &'a str, + // Returns true if the leaf is indeed a part of the MerkleTree at the specified index + result: bool, + // The message is used to derive the leaf at `index` by using the specified hash + message: Vec, + // If this is true, then before checking for membership + // we update the tree with the message at that index + should_update_tree: bool, + error_msg: &'a str, + } + // Note these test cases are not independent. + // i.e. If you update index 0, then this will be saved for the next test + let tests = vec![ + Test { + index : "0", + result : true, + message : vec![0;64], + should_update_tree: false, + error_msg : "this should always be true, since the tree is initialized with 64 zeroes" + }, + Test { + index : "0", + result : false, + message : vec![10;64], + should_update_tree: false, + error_msg : "this should be false, since the tree was not updated, however the message which derives the leaf has changed" + }, + Test { + index : "0", + result : true, + message : vec![1;64], + should_update_tree: true, + error_msg : "this should be true, since we are updating the tree" + }, + Test { + index : "0", + result : true, + message : vec![1;64], + should_update_tree: false, + error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" + }, + Test { + index : "4", + result : true, + message : vec![0;64], + should_update_tree: false, + error_msg : "this should be true since the index at 4 has not been changed yet, so it would be [0;64]" + }, + ]; + + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + let mut msg_hasher: blake2::Blake2s = MessageHasher::new(); + + let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); + + for test_vector in tests { + let index = FieldElement::try_from_str(test_vector.index).unwrap(); + let index_as_usize: usize = test_vector.index.parse().unwrap(); + let mut index_bits = index.bits(); + index_bits.reverse(); + + let leaf = msg_hasher.hash(&test_vector.message); + + let mut root = tree.root(); + if test_vector.should_update_tree { + root = tree.update_message(index_as_usize, &test_vector.message); + } + + let hash_path = tree.get_hash_path(index_as_usize); + let mut hash_path_ref = Vec::new(); + for (i, path_pair) in hash_path.into_iter().enumerate() { + let path_bit = index_bits[i]; + let hash = if !path_bit { path_pair.1 } else { path_pair.0 }; + hash_path_ref.push(hash); + } + let hash_path_ref = hash_path_ref.iter().collect(); + + let bb = Barretenberg::new(); + let computed_merkle_root = super::compute_merkle_root( + |left, right| bb.compress_native(left, right), + hash_path_ref, + &index, + &leaf, + ); + let is_leaf_in_tree = root == computed_merkle_root; + + assert_eq!( + is_leaf_in_tree, test_vector.result, + "{}", + test_vector.error_msg + ); + } + } + + // This test uses `update_leaf` directly rather than `update_message` + #[test] + fn simple_shield() { + use tempfile::tempdir; + let temp_dir = tempdir().unwrap(); + + let mut tree: MerkleTree = MerkleTree::new(3, &temp_dir); + + let barretenberg = Barretenberg::new(); + let pubkey_x = FieldElement::from_hex( + "0x0bff8247aa94b08d1c680d7a3e10831bd8c8cf2ea2c756b0d1d89acdcad877ad", + ) + .unwrap(); + let pubkey_y = FieldElement::from_hex( + "0x2a5d7253a6ed48462fedb2d350cc768d13956310f54e73a8a47914f34a34c5c4", + ) + .unwrap(); + let (note_commitment_x, _) = barretenberg.encrypt(vec![pubkey_x, pubkey_y]); + dbg!(note_commitment_x.to_hex()); + let leaf = note_commitment_x; + + let index = FieldElement::try_from_str("0").unwrap(); + let index_as_usize: usize = 0_usize; + let mut index_bits = index.bits(); + index_bits.reverse(); + + let root = tree.update_leaf(index_as_usize, leaf); + + let hash_path = tree.get_hash_path(index_as_usize); + let mut hash_path_ref = Vec::new(); + for (i, path_pair) in hash_path.into_iter().enumerate() { + let path_bit = index_bits[i]; + let hash = if !path_bit { path_pair.1 } else { path_pair.0 }; + hash_path_ref.push(hash); + } + let hash_path_ref = hash_path_ref.iter().collect(); + let bb = Barretenberg::new(); + let computed_merkle_root = super::compute_merkle_root( + |left, right| bb.compress_native(left, right), + hash_path_ref, + &index, + &leaf, + ); + let is_leaf_in_tree = root == computed_merkle_root; + + assert!(is_leaf_in_tree) + } +} From e213c0b7d4964262007b2e057e961fc37e76e293 Mon Sep 17 00:00:00 2001 From: Tom French Date: Wed, 26 Apr 2023 17:09:20 +0100 Subject: [PATCH 8/8] chore: replace `assert` with `assert_eq` --- acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs b/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs index 6518fd67..642d8948 100644 --- a/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs +++ b/acvm_backend_barretenberg/src/acvm_interop/pwg/merkle.rs @@ -174,8 +174,7 @@ mod tests { &index, &leaf, ); - let is_leaf_in_tree = root == computed_merkle_root; - assert!(is_leaf_in_tree) + assert_eq!(root, computed_merkle_root) } }