From 6e23d3f068500f2f2d94ec894ff2351a40ae4440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 11 Jun 2024 18:34:56 +0000 Subject: [PATCH 1/5] Add better collapse fn --- noir-projects/aztec-nr/aztec/src/lib.nr | 1 + noir-projects/aztec-nr/aztec/src/utils.nr | 80 ++++++++++++++++++ .../aztec-nr/aztec/src/utils/test.nr | 83 +++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 noir-projects/aztec-nr/aztec/src/utils.nr create mode 100644 noir-projects/aztec-nr/aztec/src/utils/test.nr diff --git a/noir-projects/aztec-nr/aztec/src/lib.nr b/noir-projects/aztec-nr/aztec/src/lib.nr index 95bb3f2f9ca5..4b57ef1a71cf 100644 --- a/noir-projects/aztec-nr/aztec/src/lib.nr +++ b/noir-projects/aztec-nr/aztec/src/lib.nr @@ -12,5 +12,6 @@ mod prelude; mod public_storage; mod encrypted_logs; use dep::protocol_types; +mod utils; mod test; diff --git a/noir-projects/aztec-nr/aztec/src/utils.nr b/noir-projects/aztec-nr/aztec/src/utils.nr new file mode 100644 index 000000000000..0dddebc0ceba --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/utils.nr @@ -0,0 +1,80 @@ +use dep::protocol_types::traits::Eq; + +mod test; + +// Collapses an array of Options with sparse Some values into a BoundedVec, essentially unwrapping the Options and +// removing the None values. For example, given: +// input: [some(3), none(), some(1)] +// this returns +// collapsed: [3, 1] +pub fn collapse(input: [Option; N]) -> BoundedVec where T: Eq { + let (collapsed, collapsed_to_input_index_mapping) = collapse_helper(input); + collapse_contrain(input, collapsed, collapsed_to_input_index_mapping); + collapsed +} + +fn collapse_contrain( + input: [Option; N], + collapsed: BoundedVec, + collapsed_to_input_index_mapping: BoundedVec +) where T: Eq { + // collapsed should be a BoundedVec with all the non-none elements in input, in the same order. We need to lay down + // multiple constraints to guarantee this. + + // First we check that the number of elements is correct + let mut count = 0; + for i in 0..N { + if input[i].is_some() { + count += 1; + } + } + assert_eq(count, collapsed.len(), "Wrong collapsed vec length"); + + // Then we check that all elements exist in the original array, and are in the same order. To do this we use the + // auxiliary collapsed_to_input_index_mapping array, which at index n contains the index in the input array that + // corresponds to the collapsed entry at index n. + // Example: + // - input: [some(3), none(), some(1)] + // - collapsed: [3, 1] + // - collapsed_to_input_index_mapping: [0, 2] + // These two arrays should therefore have the same length. + assert_eq(collapsed.len(), collapsed_to_input_index_mapping.len(), "Collapse hint vec length mismatch"); + + // We now look at each collapsed entry and check that there is a valid equal entry in the input array. + let mut last_index = Option::none(); + for i in 0..N { + if i < collapsed.len() { + let input_index = collapsed_to_input_index_mapping.get_unchecked(i); + assert(input_index < N, "Out of bounds index hint"); + + assert_eq(collapsed.get_unchecked(i), input[input_index].unwrap(), "Wrong collapsed vec content"); + + // By requiring increasing input indices, we both guarantee that we're not looking at the same input + // element more than once, and that we're going over them in the original order. + if last_index.is_some() { + assert(input_index > last_index.unwrap_unchecked(), "Wrong collapsed vec order"); + } + last_index = Option::some(input_index); + } + } + // We now know that: + // - all values in the collapsed array exist in the input array + // - the order of the collapsed values is the same as in the input array + // - no input value is present more than once in the collapsed array + // - the number of elements in the collapsed array is the same as in the input array. + // Therefore, the collapsed array is correct. +} + +unconstrained fn collapse_helper(input: [Option; N]) -> (BoundedVec, BoundedVec) { + let mut collapsed: BoundedVec = BoundedVec::new(); + let mut collapsed_to_input_index_mapping: BoundedVec = BoundedVec::new(); + + for i in 0..N { + if input[i].is_some() { + collapsed.push(input[i].unwrap_unchecked()); + collapsed_to_input_index_mapping.push(i); + } + } + + (collapsed, collapsed_to_input_index_mapping) +} diff --git a/noir-projects/aztec-nr/aztec/src/utils/test.nr b/noir-projects/aztec-nr/aztec/src/utils/test.nr new file mode 100644 index 000000000000..839d44b66013 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/utils/test.nr @@ -0,0 +1,83 @@ +use crate::utils::{collapse, collapse_contrain}; + +#[test] +fn collapse_empty_array() { + let original: [Option; 2] = [Option::none(), Option::none()]; + let collapsed = collapse(original); + + assert_eq(collapsed.len(), 0); +} + +#[test] +fn collapse_non_sparse_array() { + let original = [Option::some(7), Option::some(3), Option::none()]; + let collapsed = collapse(original); + + assert_eq(collapsed.len(), 2); + assert_eq(collapsed.get(0), 7); + assert_eq(collapsed.get(1), 3); +} + +#[test] +fn collapse_sparse_array() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + let collapsed = collapse(original); + + assert_eq(collapsed.len(), 2); + assert_eq(collapsed.get(0), 7); + assert_eq(collapsed.get(1), 3); +} + +#[test] +fn collapse_constrain_good_hints() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + let collapsed = BoundedVec::from_array([7, 3]); + let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 2]); + + collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); +} + +#[test(should_fail_with="Wrong collapsed vec length")] +fn collapse_constrain_wrong_length() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + let collapsed = BoundedVec::from_array([7]); + let collapsed_to_input_index_mapping = BoundedVec::from_array([0]); + + collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); +} + +#[test(should_fail_with="Collapse hint vec length mismatch")] +fn collapse_constrain_hint_length_mismatch() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + let collapsed = BoundedVec::from_array([7, 3]); + let collapsed_to_input_index_mapping = BoundedVec::from_array([0]); + + collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); +} + +#[test(should_fail_with="Out of bounds index hint")] +fn collapse_constrain_out_of_bounds_index_hint() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + let collapsed = BoundedVec::from_array([7, 3]); + let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 5]); + + collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); +} + +#[test(should_fail_with="Wrong collapsed vec content")] +fn collapse_constrain_wrong_vec_content() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + let collapsed = BoundedVec::from_array([7, 42]); + let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 2]); + + collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); +} + +#[test(should_fail_with="Wrong collapsed vec order")] +fn collapse_constrain_wrong_vec_order() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + let collapsed = BoundedVec::from_array([3, 7]); + let collapsed_to_input_index_mapping = BoundedVec::from_array([2, 0]); + + collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); +} From 57f7f1fc213ab405802416ae289fb4749be75ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 11 Jun 2024 20:52:01 +0000 Subject: [PATCH 2/5] Add test for hints to none --- noir-projects/aztec-nr/aztec/src/utils/test.nr | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/noir-projects/aztec-nr/aztec/src/utils/test.nr b/noir-projects/aztec-nr/aztec/src/utils/test.nr index 839d44b66013..e249ce6906bd 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/test.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/test.nr @@ -64,6 +64,15 @@ fn collapse_constrain_out_of_bounds_index_hint() { collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); } +#[test(should_fail)] +fn collapse_constrain_hint_to_none() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + let collapsed = BoundedVec::from_array([7, 0]); + let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 1]); + + collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); +} + #[test(should_fail_with="Wrong collapsed vec content")] fn collapse_constrain_wrong_vec_content() { let original = [Option::some(7), Option::none(), Option::some(3)]; From b15837cb30434ca85e411b7c36f9ef8d0dce4b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 12 Jun 2024 11:05:45 +0000 Subject: [PATCH 3/5] Add padding tests --- .../aztec-nr/aztec/src/utils/test.nr | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/noir-projects/aztec-nr/aztec/src/utils/test.nr b/noir-projects/aztec-nr/aztec/src/utils/test.nr index e249ce6906bd..d7cf0022af69 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/test.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/test.nr @@ -28,6 +28,26 @@ fn collapse_sparse_array() { assert_eq(collapsed.get(1), 3); } +#[test] +fn collapse_array_front_padding() { + let original = [Option::none(), Option::none(), Option::some(7), Option::none(), Option::some(3)]; + let collapsed = collapse(original); + + assert_eq(collapsed.len(), 2); + assert_eq(collapsed.get(0), 7); + assert_eq(collapsed.get(1), 3); +} + +#[test] +fn collapse_array_back_padding() { + let original = [Option::some(7), Option::none(), Option::some(3), Option::none(), Option::none()]; + let collapsed = collapse(original); + + assert_eq(collapsed.len(), 2); + assert_eq(collapsed.get(0), 7); + assert_eq(collapsed.get(1), 3); +} + #[test] fn collapse_constrain_good_hints() { let original = [Option::some(7), Option::none(), Option::some(3)]; From 39918d4507b713b2214fd8b73d29aeee881b7f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 12 Jun 2024 11:08:33 +0000 Subject: [PATCH 4/5] Apply naming suggestions --- noir-projects/aztec-nr/aztec/src/utils.nr | 8 ++--- .../aztec-nr/aztec/src/utils/test.nr | 30 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/utils.nr b/noir-projects/aztec-nr/aztec/src/utils.nr index 0dddebc0ceba..08bae1394141 100644 --- a/noir-projects/aztec-nr/aztec/src/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/utils.nr @@ -8,12 +8,12 @@ mod test; // this returns // collapsed: [3, 1] pub fn collapse(input: [Option; N]) -> BoundedVec where T: Eq { - let (collapsed, collapsed_to_input_index_mapping) = collapse_helper(input); - collapse_contrain(input, collapsed, collapsed_to_input_index_mapping); + let (collapsed, collapsed_to_input_index_mapping) = get_collapse_hints(input); + verify_collapse_hints(input, collapsed, collapsed_to_input_index_mapping); collapsed } -fn collapse_contrain( +fn verify_collapse_hints( input: [Option; N], collapsed: BoundedVec, collapsed_to_input_index_mapping: BoundedVec @@ -65,7 +65,7 @@ fn collapse_contrain( // Therefore, the collapsed array is correct. } -unconstrained fn collapse_helper(input: [Option; N]) -> (BoundedVec, BoundedVec) { +unconstrained fn get_collapse_hints(input: [Option; N]) -> (BoundedVec, BoundedVec) { let mut collapsed: BoundedVec = BoundedVec::new(); let mut collapsed_to_input_index_mapping: BoundedVec = BoundedVec::new(); diff --git a/noir-projects/aztec-nr/aztec/src/utils/test.nr b/noir-projects/aztec-nr/aztec/src/utils/test.nr index d7cf0022af69..12db49624d71 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/test.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/test.nr @@ -1,4 +1,4 @@ -use crate::utils::{collapse, collapse_contrain}; +use crate::utils::{collapse, verify_collapse_hints}; #[test] fn collapse_empty_array() { @@ -49,64 +49,64 @@ fn collapse_array_back_padding() { } #[test] -fn collapse_constrain_good_hints() { +fn verify_collapse_hints_good_hints() { let original = [Option::some(7), Option::none(), Option::some(3)]; let collapsed = BoundedVec::from_array([7, 3]); let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 2]); - collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); } #[test(should_fail_with="Wrong collapsed vec length")] -fn collapse_constrain_wrong_length() { +fn verify_collapse_hints_wrong_length() { let original = [Option::some(7), Option::none(), Option::some(3)]; let collapsed = BoundedVec::from_array([7]); let collapsed_to_input_index_mapping = BoundedVec::from_array([0]); - collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); } #[test(should_fail_with="Collapse hint vec length mismatch")] -fn collapse_constrain_hint_length_mismatch() { +fn verify_collapse_hints_hint_length_mismatch() { let original = [Option::some(7), Option::none(), Option::some(3)]; let collapsed = BoundedVec::from_array([7, 3]); let collapsed_to_input_index_mapping = BoundedVec::from_array([0]); - collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); } #[test(should_fail_with="Out of bounds index hint")] -fn collapse_constrain_out_of_bounds_index_hint() { +fn verify_collapse_hints_out_of_bounds_index_hint() { let original = [Option::some(7), Option::none(), Option::some(3)]; let collapsed = BoundedVec::from_array([7, 3]); let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 5]); - collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); } #[test(should_fail)] -fn collapse_constrain_hint_to_none() { +fn verify_collapse_hints_hint_to_none() { let original = [Option::some(7), Option::none(), Option::some(3)]; let collapsed = BoundedVec::from_array([7, 0]); let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 1]); - collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); } #[test(should_fail_with="Wrong collapsed vec content")] -fn collapse_constrain_wrong_vec_content() { +fn verify_collapse_hints_wrong_vec_content() { let original = [Option::some(7), Option::none(), Option::some(3)]; let collapsed = BoundedVec::from_array([7, 42]); let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 2]); - collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); } #[test(should_fail_with="Wrong collapsed vec order")] -fn collapse_constrain_wrong_vec_order() { +fn verify_collapse_hints_wrong_vec_order() { let original = [Option::some(7), Option::none(), Option::some(3)]; let collapsed = BoundedVec::from_array([3, 7]); let collapsed_to_input_index_mapping = BoundedVec::from_array([2, 0]); - collapse_contrain(original, collapsed, collapsed_to_input_index_mapping); + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); } From faaea962f1de9756d136c808320ce5a4ee00e238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 12 Jun 2024 15:00:35 +0000 Subject: [PATCH 5/5] Add dirty check --- noir-projects/aztec-nr/aztec/src/utils.nr | 4 ++++ noir-projects/aztec-nr/aztec/src/utils/test.nr | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/noir-projects/aztec-nr/aztec/src/utils.nr b/noir-projects/aztec-nr/aztec/src/utils.nr index 08bae1394141..fb41f3fe0226 100644 --- a/noir-projects/aztec-nr/aztec/src/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/utils.nr @@ -55,6 +55,10 @@ fn verify_collapse_hints( assert(input_index > last_index.unwrap_unchecked(), "Wrong collapsed vec order"); } last_index = Option::some(input_index); + } else { + // We don't technically need to check past the length of the BoundedVec since those slots should not be + // accessible, but its fairly cheap to prevent dirty data from being stored there. + assert_eq(collapsed.get_unchecked(i), dep::std::unsafe::zeroed(), "Dirty collapsed vec"); } } // We now know that: diff --git a/noir-projects/aztec-nr/aztec/src/utils/test.nr b/noir-projects/aztec-nr/aztec/src/utils/test.nr index 12db49624d71..f24064b972c6 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/test.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/test.nr @@ -110,3 +110,15 @@ fn verify_collapse_hints_wrong_vec_order() { verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); } + +#[test(should_fail_with="Dirty collapsed vec")] +fn verify_collapse_hints_dirty_storage() { + let original = [Option::some(7), Option::none(), Option::some(3)]; + + let mut collapsed: BoundedVec = BoundedVec::from_array([7, 3]); + collapsed.storage[2] = 1; + + let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 2]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); +}