From d132f83d595315565d54590bec69d25f7371559e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 10 Dec 2024 12:19:37 -0300 Subject: [PATCH] chore: move some nr utils around (#10553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR improves bunch of small things. I noticed we were using small utils exported from protocol-circuits, notably `arr_copy_slice`, which seems like a bad idea as it we then unnecessarily couple those codebases. Also, said utility was quite awful: the name is bad (since 'slice' already has meaning inside of noir), and it mutated an argument which it also returned. I replaced this for `subarray`, which I think is much nicer. I also took the liberty of replacing usage of this in the protocol circuits, removing `conditional_assign` (which was unused) and improving some array management in which the array lengths were unnecessarily being imported and forced in situations were the compiler could already infer them. Finally I moved `collapse` into its own file as it was before in order to better structure our submodules with each having their own test etc. This helps avoid unnecessary exports of private pieces in order to test them. --------- Co-authored-by: Jan Beneš --- .../aztec/src/keys/point_to_symmetric_key.nr | 17 +- .../aztec/src/note/note_getter/mod.nr | 2 +- .../aztec-nr/aztec/src/note/utils.nr | 13 +- .../oracle/get_l1_to_l2_membership_witness.nr | 7 +- .../src/oracle/get_membership_witness.nr | 8 +- .../get_nullifier_membership_witness.nr | 16 +- .../src/oracle/get_public_data_witness.nr | 11 +- .../aztec-nr/aztec/src/oracle/notes.nr | 13 +- .../aztec/src/utils/array/collapse.nr | 237 ++++++++++++++++++ .../aztec-nr/aztec/src/utils/array/mod.nr | 5 + .../aztec/src/utils/array/subarray.nr | 59 +++++ .../aztec/src/utils/collapse_array.nr | 104 -------- noir-projects/aztec-nr/aztec/src/utils/mod.nr | 4 +- .../aztec-nr/aztec/src/utils/test.nr | 127 ---------- .../src/auth_oracle.nr | 9 +- .../crates/types/src/address/aztec_address.nr | 6 - .../crates/types/src/address/eth_address.nr | 7 +- .../crates/types/src/block_header.nr | 31 +-- .../crates/types/src/state_reference.nr | 14 +- .../crates/types/src/utils/arrays.nr | 14 ++ .../crates/types/src/utils/mod.nr | 21 -- 21 files changed, 375 insertions(+), 350 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/utils/array/collapse.nr create mode 100644 noir-projects/aztec-nr/aztec/src/utils/array/mod.nr create mode 100644 noir-projects/aztec-nr/aztec/src/utils/array/subarray.nr delete mode 100644 noir-projects/aztec-nr/aztec/src/utils/collapse_array.nr delete mode 100644 noir-projects/aztec-nr/aztec/src/utils/test.nr 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 -}