diff --git a/noir-projects/aztec-nr/aztec/src/keys/point_to_symmetric_key.nr b/noir-projects/aztec-nr/aztec/src/keys/point_to_symmetric_key.nr index f1ad2b43938..5f2fbfb911e 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/point_to_symmetric_key.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/point_to_symmetric_key.nr @@ -1,18 +1,19 @@ use crate::utils::point::point_to_bytes; -use dep::protocol_types::{ - constants::GENERATOR_INDEX__SYMMETRIC_KEY, point::Point, scalar::Scalar, utils::arr_copy_slice, -}; +use dep::protocol_types::{constants::GENERATOR_INDEX__SYMMETRIC_KEY, point::Point, scalar::Scalar}; use std::{embedded_curve_ops::multi_scalar_mul, hash::sha256}; // TODO(#5726): This function is called deriveAESSecret in TS. I don't like point_to_symmetric_key name much since // point is not the only input of the function. Unify naming with TS once we have a better name. pub fn point_to_symmetric_key(secret: Scalar, point: Point) -> [u8; 32] { - let shared_secret: Point = multi_scalar_mul([point], [secret]); - let shared_secret = point_to_bytes(shared_secret); - let mut shared_secret_bytes_with_separator = [0 as u8; 33]; - shared_secret_bytes_with_separator = - arr_copy_slice(shared_secret, shared_secret_bytes_with_separator, 0); + let shared_secret = point_to_bytes(multi_scalar_mul([point], [secret])); + + let mut shared_secret_bytes_with_separator: [u8; 33] = std::mem::zeroed(); + for i in 0..shared_secret.len() { + shared_secret_bytes_with_separator[i] = shared_secret[i]; + } + shared_secret_bytes_with_separator[32] = GENERATOR_INDEX__SYMMETRIC_KEY; + sha256(shared_secret_bytes_with_separator) } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr index 99efea10032..c79c2e18730 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr @@ -141,7 +141,7 @@ where let filter_args = options.filter_args; let filtered_notes = filter_fn(opt_notes, filter_args); - let notes = crate::utils::collapse_array(filtered_notes); + let notes = crate::utils::array::collapse(filtered_notes); let mut note_hashes: BoundedVec = BoundedVec::new(); diff --git a/noir-projects/aztec-nr/aztec/src/note/utils.nr b/noir-projects/aztec-nr/aztec/src/note/utils.nr index 6f3031f0384..184d70c8f2b 100644 --- a/noir-projects/aztec-nr/aztec/src/note/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/note/utils.nr @@ -1,15 +1,12 @@ use crate::{ context::PrivateContext, note::{note_header::NoteHeader, note_interface::{NoteInterface, NullifiableNote}}, + utils::array, }; -use dep::protocol_types::{ - hash::{ - compute_siloed_note_hash as compute_siloed_note_hash, - compute_siloed_nullifier as compute_siloed_nullifier_from_preimage, - compute_unique_note_hash, - }, - utils::arr_copy_slice, +use dep::protocol_types::hash::{ + compute_siloed_note_hash as compute_siloed_note_hash, + compute_siloed_nullifier as compute_siloed_nullifier_from_preimage, compute_unique_note_hash, }; pub fn compute_siloed_nullifier( @@ -128,7 +125,7 @@ pub unconstrained fn compute_note_hash_and_optionally_a_nullifier + NullifiableNote, { - let mut note = deserialize_content(arr_copy_slice(serialized_note, [0; N], 0)); + let mut note = deserialize_content(array::subarray(serialized_note, 0)); note.set_header(note_header); let note_hash = note.compute_note_hash(); diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_l1_to_l2_membership_witness.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_l1_to_l2_membership_witness.nr index 313791269c4..d648f7f2b12 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_l1_to_l2_membership_witness.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_l1_to_l2_membership_witness.nr @@ -1,6 +1,5 @@ -use dep::protocol_types::{ - address::AztecAddress, constants::L1_TO_L2_MSG_TREE_HEIGHT, utils::arr_copy_slice, -}; +use crate::utils::array; +use dep::protocol_types::{address::AztecAddress, constants::L1_TO_L2_MSG_TREE_HEIGHT}; /// Returns the leaf index and sibling path of an entry in the L1 to L2 messaging tree, which can then be used to prove /// its existence. @@ -12,7 +11,7 @@ pub unconstrained fn get_l1_to_l2_membership_witness( let returned_message = get_l1_to_l2_membership_witness_oracle(contract_address, message_hash, secret); let leaf_index = returned_message[0]; - let sibling_path = arr_copy_slice(returned_message, [0; L1_TO_L2_MSG_TREE_HEIGHT], 1); + let sibling_path = array::subarray(returned_message, 1); (leaf_index, sibling_path) } diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr index 02f8a0267cc..7a6a28b385b 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr @@ -1,7 +1,5 @@ -use dep::protocol_types::{ - constants::{ARCHIVE_HEIGHT, NOTE_HASH_TREE_HEIGHT}, - utils::arr_copy_slice, -}; +use crate::utils::array; +use dep::protocol_types::constants::{ARCHIVE_HEIGHT, NOTE_HASH_TREE_HEIGHT}; global NOTE_HASH_TREE_ID: Field = 1; global ARCHIVE_TREE_ID: Field = 4; @@ -31,7 +29,7 @@ pub unconstrained fn get_membership_witness( leaf_value: Field, ) -> MembershipWitness { let fields: [Field; M] = get_membership_witness_oracle(block_number, tree_id, leaf_value); - MembershipWitness { index: fields[0], path: arr_copy_slice(fields, [0; N], 1) } + MembershipWitness { index: fields[0], path: array::subarray(fields, 1) } } // Note: get_nullifier_membership_witness function is implemented in get_nullifier_membership_witness.nr diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_nullifier_membership_witness.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_nullifier_membership_witness.nr index e5f87124753..d41ac58cf14 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_nullifier_membership_witness.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_nullifier_membership_witness.nr @@ -1,7 +1,6 @@ +use crate::utils::array; use dep::protocol_types::{ - abis::nullifier_leaf_preimage::{NULLIFIER_LEAF_PREIMAGE_LENGTH, NullifierLeafPreimage}, - constants::NULLIFIER_TREE_HEIGHT, - utils::arr_copy_slice, + abis::nullifier_leaf_preimage::NullifierLeafPreimage, constants::NULLIFIER_TREE_HEIGHT, }; // INDEX_LENGTH + NULLIFIER_LEAF_PREIMAGE_LENGTH + NULLIFIER_TREE_HEIGHT @@ -15,15 +14,12 @@ pub struct NullifierMembershipWitness { impl NullifierMembershipWitness { pub fn deserialize(fields: [Field; NULLIFIER_MEMBERSHIP_WITNESS]) -> Self { - let leaf_preimage_fields = arr_copy_slice(fields, [0; NULLIFIER_LEAF_PREIMAGE_LENGTH], 1); + let serialized_leaf_preimage = array::subarray(fields, 1); + Self { index: fields[0], - leaf_preimage: NullifierLeafPreimage::deserialize(leaf_preimage_fields), - path: arr_copy_slice( - fields, - [0; NULLIFIER_TREE_HEIGHT], - 1 + NULLIFIER_LEAF_PREIMAGE_LENGTH, - ), + leaf_preimage: NullifierLeafPreimage::deserialize(serialized_leaf_preimage), + path: array::subarray(fields, 1 + serialized_leaf_preimage.len()), } } } diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_public_data_witness.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_public_data_witness.nr index 517a8e2d59a..07879b4fd3e 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_public_data_witness.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_public_data_witness.nr @@ -1,6 +1,5 @@ -use dep::protocol_types::{ - constants::PUBLIC_DATA_TREE_HEIGHT, data::PublicDataTreeLeafPreimage, utils::arr_copy_slice, -}; +use crate::utils::array; +use dep::protocol_types::{constants::PUBLIC_DATA_TREE_HEIGHT, data::PublicDataTreeLeafPreimage}; global LEAF_PREIMAGE_LENGTH: u32 = 4; global PUBLIC_DATA_WITNESS: u32 = 45; @@ -30,10 +29,6 @@ pub unconstrained fn get_public_data_witness( next_index: fields[3] as u32, next_slot: fields[4], }, - path: arr_copy_slice( - fields, - [0; PUBLIC_DATA_TREE_HEIGHT], - 1 + LEAF_PREIMAGE_LENGTH, - ), + path: array::subarray(fields, 1 + LEAF_PREIMAGE_LENGTH), } } diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 737f69d20f4..6d2cdab8f83 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -1,9 +1,8 @@ -use crate::note::{note_header::NoteHeader, note_interface::NoteInterface}; +use crate::{note::{note_header::NoteHeader, note_interface::NoteInterface}, utils::array}; use dep::protocol_types::{ address::AztecAddress, indexed_tagging_secret::{INDEXED_TAGGING_SECRET_LENGTH, IndexedTaggingSecret}, - utils::arr_copy_slice, }; /// Notifies the simulator that a note has been created, so that it can be returned in future read requests in the same @@ -181,12 +180,14 @@ where let return_header_length: u32 = 2; // num_notes & contract_address. let extra_preimage_length: u32 = 2; // nonce & note_hash_counter. let read_offset: u32 = return_header_length + i * (N + extra_preimage_length); + let nonce = fields[read_offset]; let note_hash_counter = fields[read_offset + 1] as u32; - let header = NoteHeader { contract_address, nonce, storage_slot, note_hash_counter }; - let serialized_note = arr_copy_slice(fields, [0; N], read_offset + 2); - let mut note = Note::deserialize_content(serialized_note); - note.set_header(header); + let note_content = array::subarray(fields, read_offset + 2); + + let mut note = Note::deserialize_content(note_content); + note.set_header(NoteHeader { contract_address, nonce, storage_slot, note_hash_counter }); + placeholder_opt_notes[i] = Option::some(note); }; } diff --git a/noir-projects/aztec-nr/aztec/src/utils/array/collapse.nr b/noir-projects/aztec-nr/aztec/src/utils/array/collapse.nr new file mode 100644 index 00000000000..22ac88aeb33 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/utils/array/collapse.nr @@ -0,0 +1,237 @@ +/// Collapses an array of `Option`s with sparse `Some` values into a `BoundedVec`, essentially unwrapping the `Option`s +/// 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, +{ + // Computing the collpased BoundedVec would result in a very large number of constraints, since we'd need to loop + // over the input array and conditionally write to a dynamic vec index, which is a very unfriendly pattern to the + // proving backend. + // Instead, we use an unconstrained function to produce the final collapsed array, along with some hints, and then + // verify that the input and collapsed arrays are equivalent. + let (collapsed, collapsed_to_input_index_mapping) = unsafe { get_collapse_hints(input) }; + verify_collapse_hints(input, collapsed, collapsed_to_input_index_mapping); + collapsed +} + +fn verify_collapse_hints( + 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); + } else { + // BoundedVec assumes that the unused parts of the storage are zeroed out (e.g. in the Eq impl), so we make + // sure that this property holds. + assert_eq( + collapsed.get_unchecked(i), + std::mem::zeroed(), + "Dirty collapsed vec storage", + ); + } + } + // 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 get_collapse_hints( + 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) +} + +mod test { + use super::{collapse, verify_collapse_hints}; + + #[test] + unconstrained fn collapse_empty_array() { + let original: [Option; 2] = [Option::none(), Option::none()]; + let collapsed = collapse(original); + + assert_eq(collapsed.len(), 0); + } + + #[test] + unconstrained 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] + unconstrained 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] + unconstrained fn collapse_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] + unconstrained fn collapse_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] + unconstrained 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]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); + } + + #[test(should_fail_with = "Wrong collapsed vec length")] + unconstrained 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]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); + } + + #[test(should_fail_with = "Collapse hint vec length mismatch")] + unconstrained 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]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); + } + + #[test(should_fail_with = "Out of bounds index hint")] + unconstrained 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]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); + } + + #[test(should_fail)] + unconstrained 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]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); + } + + #[test(should_fail_with = "Wrong collapsed vec content")] + unconstrained 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]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); + } + + #[test(should_fail_with = "Wrong collapsed vec order")] + unconstrained 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]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); + } + + #[test(should_fail_with = "Dirty collapsed vec storage")] + unconstrained 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]); + // We have to use the unchecked setter as we're knowingly writing past the length, breaking its invariants. + collapsed.set_unchecked(2, 1); + + let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 2]); + + verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); + } + +} diff --git a/noir-projects/aztec-nr/aztec/src/utils/array/mod.nr b/noir-projects/aztec-nr/aztec/src/utils/array/mod.nr new file mode 100644 index 00000000000..832615e787c --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/utils/array/mod.nr @@ -0,0 +1,5 @@ +mod collapse; +mod subarray; + +pub use collapse::collapse; +pub use subarray::subarray; diff --git a/noir-projects/aztec-nr/aztec/src/utils/array/subarray.nr b/noir-projects/aztec-nr/aztec/src/utils/array/subarray.nr new file mode 100644 index 00000000000..fc4b7567185 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/utils/array/subarray.nr @@ -0,0 +1,59 @@ +/// Returns `DST_LEN` elements from a source array, starting at `offset`. `DST_LEN` must be large enough to hold all of +/// the elements past `offset`. +/// +/// Example: +/// ``` +/// let foo: [Field; 2] = subarray([1, 2, 3, 4, 5], 2); +/// assert_eq(foo, [3, 4]); +/// ``` +pub fn subarray( + src: [Field; SRC_LEN], + offset: u32, +) -> [Field; DST_LEN] { + assert(offset + DST_LEN <= SRC_LEN, "offset too large"); + + let mut dst: [Field; DST_LEN] = std::mem::zeroed(); + for i in 0..DST_LEN { + dst[i] = src[i + offset]; + } + + dst +} + +mod test { + use super::subarray; + + #[test] + unconstrained fn subarray_into_empty() { + // In all of these cases we're setting DST_LEN to be 0, so we always get back an emtpy array. + assert_eq(subarray([], 0), []); + assert_eq(subarray([1, 2, 3, 4, 5], 0), []); + assert_eq(subarray([1, 2, 3, 4, 5], 2), []); + } + + #[test] + unconstrained fn subarray_complete() { + assert_eq(subarray([], 0), []); + assert_eq(subarray([1, 2, 3, 4, 5], 0), [1, 2, 3, 4, 5]); + } + + #[test] + unconstrained fn subarray_different_end_sizes() { + // We implicitly select how many values to read in the size of the return array + assert_eq(subarray([1, 2, 3, 4, 5], 1), [2, 3, 4, 5]); + assert_eq(subarray([1, 2, 3, 4, 5], 1), [2, 3, 4]); + assert_eq(subarray([1, 2, 3, 4, 5], 1), [2, 3]); + assert_eq(subarray([1, 2, 3, 4, 5], 1), [2]); + } + + #[test(should_fail)] + unconstrained fn subarray_offset_too_large() { + // With an offset of 1 we can only request up to 4 elements + let _: [_; 5] = subarray([1, 2, 3, 4, 5], 1); + } + + #[test(should_fail)] + unconstrained fn subarray_bad_return_value() { + assert_eq(subarray([1, 2, 3, 4, 5], 1), [3, 3, 4, 5]); + } +} diff --git a/noir-projects/aztec-nr/aztec/src/utils/collapse_array.nr b/noir-projects/aztec-nr/aztec/src/utils/collapse_array.nr deleted file mode 100644 index ff5bc2adb82..00000000000 --- a/noir-projects/aztec-nr/aztec/src/utils/collapse_array.nr +++ /dev/null @@ -1,104 +0,0 @@ -// 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_array(input: [Option; N]) -> BoundedVec -where - T: Eq, -{ - // Computing the collpased BoundedVec would result in a very large number of constraints, since we'd need to loop - // over the input array and conditionally write to a dynamic vec index, which is a very unfriendly pattern to the - // proving backend. - // Instead, we use an unconstrained function to produce the final collapsed array, along with some hints, and then - // verify that the input and collapsed arrays are equivalent. - let (collapsed, collapsed_to_input_index_mapping) = unsafe { get_collapse_hints(input) }; - verify_collapse_hints(input, collapsed, collapsed_to_input_index_mapping); - collapsed -} - -pub(crate) fn verify_collapse_hints( - 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); - } else { - // BoundedVec assumes that the unused parts of the storage are zeroed out (e.g. in the Eq impl), so we make - // sure that this property holds. - assert_eq( - collapsed.get_unchecked(i), - std::mem::zeroed(), - "Dirty collapsed vec storage", - ); - } - } - // 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 get_collapse_hints( - 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/mod.nr b/noir-projects/aztec-nr/aztec/src/utils/mod.nr index d7abdd8234f..92ce0a09344 100644 --- a/noir-projects/aztec-nr/aztec/src/utils/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/utils/mod.nr @@ -1,9 +1,7 @@ pub mod bytes; -pub mod collapse_array; +pub mod array; pub mod comparison; pub mod point; -pub mod test; pub mod to_bytes; pub use crate::utils::bytes::{bytes_to_fields, fields_to_bytes}; -pub use crate::utils::collapse_array::collapse_array; diff --git a/noir-projects/aztec-nr/aztec/src/utils/test.nr b/noir-projects/aztec-nr/aztec/src/utils/test.nr deleted file mode 100644 index 644a6baeafe..00000000000 --- a/noir-projects/aztec-nr/aztec/src/utils/test.nr +++ /dev/null @@ -1,127 +0,0 @@ -use super::collapse_array::{collapse_array, verify_collapse_hints}; - -#[test] -unconstrained fn collapse_empty_array() { - let original: [Option; 2] = [Option::none(), Option::none()]; - let collapsed = collapse_array(original); - - assert_eq(collapsed.len(), 0); -} - -#[test] -unconstrained fn collapse_non_sparse_array() { - let original = [Option::some(7), Option::some(3), Option::none()]; - let collapsed = collapse_array(original); - - assert_eq(collapsed.len(), 2); - assert_eq(collapsed.get(0), 7); - assert_eq(collapsed.get(1), 3); -} - -#[test] -unconstrained fn collapse_sparse_array() { - let original = [Option::some(7), Option::none(), Option::some(3)]; - let collapsed = collapse_array(original); - - assert_eq(collapsed.len(), 2); - assert_eq(collapsed.get(0), 7); - assert_eq(collapsed.get(1), 3); -} - -#[test] -unconstrained fn collapse_array_front_padding() { - let original = - [Option::none(), Option::none(), Option::some(7), Option::none(), Option::some(3)]; - let collapsed = collapse_array(original); - - assert_eq(collapsed.len(), 2); - assert_eq(collapsed.get(0), 7); - assert_eq(collapsed.get(1), 3); -} - -#[test] -unconstrained fn collapse_array_back_padding() { - let original = - [Option::some(7), Option::none(), Option::some(3), Option::none(), Option::none()]; - let collapsed = collapse_array(original); - - assert_eq(collapsed.len(), 2); - assert_eq(collapsed.get(0), 7); - assert_eq(collapsed.get(1), 3); -} - -#[test] -unconstrained 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]); - - verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); -} - -#[test(should_fail_with = "Wrong collapsed vec length")] -unconstrained 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]); - - verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); -} - -#[test(should_fail_with = "Collapse hint vec length mismatch")] -unconstrained 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]); - - verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); -} - -#[test(should_fail_with = "Out of bounds index hint")] -unconstrained 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]); - - verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); -} - -#[test(should_fail)] -unconstrained 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]); - - verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); -} - -#[test(should_fail_with = "Wrong collapsed vec content")] -unconstrained 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]); - - verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); -} - -#[test(should_fail_with = "Wrong collapsed vec order")] -unconstrained 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]); - - verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); -} - -#[test(should_fail_with = "Dirty collapsed vec storage")] -unconstrained 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]); - // We have to use the unchecked setter as we're knowingly writing past the length, breaking its invariants. - collapsed.set_unchecked(2, 1); - - let collapsed_to_input_index_mapping = BoundedVec::from_array([0, 2]); - - verify_collapse_hints(original, collapsed, collapsed_to_input_index_mapping); -} diff --git a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/auth_oracle.nr b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/auth_oracle.nr index 063720d28e1..53e883aca57 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/auth_oracle.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/auth_oracle.nr @@ -1,8 +1,7 @@ use dep::authwit::auth_witness; -use dep::aztec::protocol_types::{ - address::PartialAddress, - public_keys::{PUBLIC_KEYS_LENGTH, PublicKeys}, - utils::arr_copy_slice, +use dep::aztec::{ + protocol_types::{address::PartialAddress, public_keys::{PUBLIC_KEYS_LENGTH, PublicKeys}}, + utils::array, }; pub struct AuthWitness { @@ -18,7 +17,7 @@ impl AuthWitness { signature[i] = values[i + PUBLIC_KEYS_LENGTH] as u8; } Self { - keys: PublicKeys::deserialize(arr_copy_slice(values, [0; PUBLIC_KEYS_LENGTH], 0)), + keys: PublicKeys::deserialize(array::subarray(values, 0)), signature, partial_address: PartialAddress::from_field(values[76]), } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr b/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr index f66f0418fe7..e8751bf5220 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr @@ -12,7 +12,6 @@ use crate::{ merkle_tree::membership::MembershipWitness, public_keys::{IvpkM, NpkM, OvpkM, PublicKeys, TpkM}, traits::{Deserialize, Empty, FromField, Serialize, ToField}, - utils, }; // We do below because `use crate::point::Point;` does not work @@ -148,11 +147,6 @@ impl AztecAddress { pub fn assert_is_zero(self) { assert(self.to_field() == 0); } - - pub fn conditional_assign(predicate: bool, lhs: Self, rhs: Self) -> Self { - let result = utils::conditional_assign(predicate, rhs.to_field(), lhs.to_field()); - Self { inner: result } - } } #[test] diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/address/eth_address.nr b/noir-projects/noir-protocol-circuits/crates/types/src/address/eth_address.nr index 465ae83c61e..56b8e2d7d91 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/address/eth_address.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/address/eth_address.nr @@ -1,4 +1,4 @@ -use crate::{constants::ETH_ADDRESS_LENGTH, traits::{Deserialize, Empty, Serialize, ToField}, utils}; +use crate::{constants::ETH_ADDRESS_LENGTH, traits::{Deserialize, Empty, Serialize, ToField}}; pub struct EthAddress { inner: Field, @@ -51,9 +51,4 @@ impl EthAddress { pub fn assert_is_zero(self) { assert(self.to_field() == 0); } - - pub fn conditional_assign(predicate: bool, lhs: Self, rhs: Self) -> Self { - let result = utils::conditional_assign(predicate, rhs.to_field(), lhs.to_field()); - Self { inner: result } - } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/block_header.nr b/noir-projects/noir-protocol-circuits/crates/types/src/block_header.nr index 842a7eaa308..4d77ce8088c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/block_header.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/block_header.nr @@ -1,17 +1,11 @@ use crate::{ - abis::{ - append_only_tree_snapshot::{APPEND_ONLY_TREE_SNAPSHOT_LENGTH, AppendOnlyTreeSnapshot}, - global_variables::GlobalVariables, - }, - constants::{ - BLOCK_HEADER_LENGTH, CONTENT_COMMITMENT_LENGTH, GENERATOR_INDEX__BLOCK_HASH, - GLOBAL_VARIABLES_LENGTH, STATE_REFERENCE_LENGTH, - }, + abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot, global_variables::GlobalVariables}, + constants::{BLOCK_HEADER_LENGTH, GENERATOR_INDEX__BLOCK_HASH}, content_commitment::ContentCommitment, hash::poseidon2_hash_with_separator, state_reference::StateReference, traits::{Deserialize, Empty, Hash, Serialize}, - utils::arr_copy_slice, + utils::arrays::subarray, }; // docs:start:block-header @@ -54,20 +48,17 @@ impl Deserialize for BlockHeader { fn deserialize(serialized: [Field; BLOCK_HEADER_LENGTH]) -> Self { let mut offset = 0; - let last_archive_fields = - arr_copy_slice(serialized, [0; APPEND_ONLY_TREE_SNAPSHOT_LENGTH], offset); - offset = offset + APPEND_ONLY_TREE_SNAPSHOT_LENGTH; + let last_archive_fields = subarray(serialized, offset); + offset = offset + last_archive_fields.len(); - let content_commitment_fields = - arr_copy_slice(serialized, [0; CONTENT_COMMITMENT_LENGTH], offset); - offset = offset + CONTENT_COMMITMENT_LENGTH; + let content_commitment_fields = subarray(serialized, offset); + offset = offset + content_commitment_fields.len(); - let state_fields = arr_copy_slice(serialized, [0; STATE_REFERENCE_LENGTH], offset); - offset = offset + STATE_REFERENCE_LENGTH; + let state_fields = subarray(serialized, offset); + offset = offset + state_fields.len(); - let global_variables_fields = - arr_copy_slice(serialized, [0; GLOBAL_VARIABLES_LENGTH], offset); - offset = offset + GLOBAL_VARIABLES_LENGTH; + let global_variables_fields = subarray(serialized, offset); + offset = offset + global_variables_fields.len(); let total_fees = serialized[offset]; offset = offset + 1; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/state_reference.nr b/noir-projects/noir-protocol-circuits/crates/types/src/state_reference.nr index 2af97314c02..1e96e52a37c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/state_reference.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/state_reference.nr @@ -1,10 +1,10 @@ use crate::{ - abis::append_only_tree_snapshot::{APPEND_ONLY_TREE_SNAPSHOT_LENGTH, AppendOnlyTreeSnapshot}, - constants::{PARTIAL_STATE_REFERENCE_LENGTH, STATE_REFERENCE_LENGTH}, + abis::append_only_tree_snapshot::AppendOnlyTreeSnapshot, + constants::STATE_REFERENCE_LENGTH, partial_state_reference::PartialStateReference, traits::{Deserialize, Empty, Serialize}, - utils::arr_copy_slice, }; +use super::utils::arrays::subarray; pub struct StateReference { pub l1_to_l2_message_tree: AppendOnlyTreeSnapshot, @@ -32,12 +32,10 @@ impl Deserialize for StateReference { fn deserialize(serialized: [Field; STATE_REFERENCE_LENGTH]) -> StateReference { let mut offset = 0; - let l1_to_l2_message_tree_fields = - arr_copy_slice(serialized, [0; APPEND_ONLY_TREE_SNAPSHOT_LENGTH], offset); - offset = offset + APPEND_ONLY_TREE_SNAPSHOT_LENGTH; + let l1_to_l2_message_tree_fields = subarray(serialized, offset); + offset = offset + l1_to_l2_message_tree_fields.len(); - let partial_fields = - arr_copy_slice(serialized, [0; PARTIAL_STATE_REFERENCE_LENGTH], offset); + let partial_fields = subarray(serialized, offset); StateReference { l1_to_l2_message_tree: AppendOnlyTreeSnapshot::deserialize( diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr index 41d609572b7..97d90406a7d 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr @@ -41,6 +41,20 @@ pub use sort_by_counter::{sort_by_counter_asc, sort_by_counter_desc}; use crate::traits::{Empty, is_empty}; +pub fn subarray( + src: [Field; SRC_LEN], + offset: u32, +) -> [Field; DST_LEN] { + assert(offset + DST_LEN <= SRC_LEN, "offset too large"); + + let mut dst: [Field; DST_LEN] = std::mem::zeroed(); + for i in 0..DST_LEN { + dst[i] = src[i + offset]; + } + + dst +} + pub fn array_to_bounded_vec(array: [T; N]) -> BoundedVec where T: Empty + Eq, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/mod.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/mod.nr index 113da583999..d88d62eb507 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/mod.nr @@ -6,24 +6,3 @@ pub mod arrays; pub mod field; pub mod reader; pub mod uint256; - -// if predicate == true then return lhs, else return rhs -pub fn conditional_assign(predicate: bool, lhs: Field, rhs: Field) -> Field { - if predicate { - lhs - } else { - rhs - } -} - -pub fn arr_copy_slice( - src: [T; N], - mut dst: [T; M], - offset: u32, -) -> [T; M] { - let iterator_len = if N > M { M } else { N }; - for i in 0..iterator_len { - dst[i] = src[i + offset]; - } - dst -}