diff --git a/docs/docs/aztec/smart_contracts/functions/attributes.md b/docs/docs/aztec/smart_contracts/functions/attributes.md index 707241b7946..46ff05a666b 100644 --- a/docs/docs/aztec/smart_contracts/functions/attributes.md +++ b/docs/docs/aztec/smart_contracts/functions/attributes.md @@ -186,7 +186,7 @@ When a struct is annotated with `#[note]`, the Aztec macro applies a series of t 1. **NoteInterface Implementation**: The macro automatically implements most methods of the `NoteInterface` trait for the annotated struct. This includes: - - `serialize_content` and `deserialize_content` + - `pack_content` and `unpack_content` - `get_header` and `set_header` - `get_note_type_id` - `compute_note_hiding_point` @@ -219,14 +219,14 @@ struct CustomNote { ```rust impl CustomNote { - fn serialize_content(self: CustomNote) -> [Field; NOTE_SERIALIZED_LEN] { + fn pack_content(self: CustomNote) -> [Field; PACKED_NOTE_CONTENT_LEN] { [self.data, self.owner.to_field()] } - fn deserialize_content(serialized_note: [Field; NOTE_SERIALIZED_LEN]) -> Self { + fn unpack_content(packed_content: [Field; PACKED_NOTE_CONTENT_LEN]) -> Self { CustomNote { - data: serialized_note[0] as Field, - owner: Address::from_field(serialized_note[1]), + data: packed_content[0] as Field, + owner: Address::from_field(packed_content[1]), header: NoteHeader::empty() } } diff --git a/docs/docs/aztec/smart_contracts/functions/function_transforms.md b/docs/docs/aztec/smart_contracts/functions/function_transforms.md index 2254bb44a31..c87aeda95c1 100644 --- a/docs/docs/aztec/smart_contracts/functions/function_transforms.md +++ b/docs/docs/aztec/smart_contracts/functions/function_transforms.md @@ -30,7 +30,7 @@ For private functions, the context creation involves hashing all input parameter ```rust let mut args_hasher = ArgsHasher::new(); -// Hash each parameter +// Hash each parameter args_hasher.add(param1); args_hasher.add(param2); // add all parameters @@ -71,8 +71,8 @@ The context provides methods to call other contracts: ```rust let token_contract = TokenContract::at(token); ``` - -Under the hood, this creates a new instance of the contract interface with the specified address. + +Under the hood, this creates a new instance of the contract interface with the specified address. ## Private and public input injection @@ -102,7 +102,7 @@ This makes these inputs available to be consumed within private annotated functi ## Return value handling -Return values in Aztec contracts are processed differently from traditional smart contracts when using private functions. +Return values in Aztec contracts are processed differently from traditional smart contracts when using private functions. ### Private functions @@ -156,10 +156,10 @@ The function is automatically generated based on the note types defined in the c ```rust if (note_type_id == NoteType::get_note_type_id()) { aztec::note::utils::compute_note_hash_and_optionally_a_nullifier( - NoteType::deserialize_content, + NoteType::unpack_content, note_header, compute_nullifier, - serialized_note + packed_note_content ) } ``` @@ -206,7 +206,7 @@ The computed function signatures are integrated into the contract interface like - The function's parameters are extracted - The signature hash is computed using `compute_fn_signature_hash` - The placeholder in the contract interface is replaced with the computed hash - + This process ensures that each function in the contract has a unique, deterministic signature based on its name and parameter types. They are inspired by Solidity's function selector mechanism. ## Contract artifacts diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index d14f634ccc4..f1a1b0b245e 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -47,6 +47,25 @@ The way in which logs are assembled in this "default_aes128" strategy is has als You can remove this method from any custom notes or events that you've implemented. +### [Aztec.nr] Packing notes resulting in changes in `NoteInterface` +Note interface implementation generated by our macros now packs note content instead of serializing it +With this change notes are being less costly DA-wise to emit when some of the note struct members implements the `Packable` trait (this is typically the `UintNote` which represents `value` as `U128` that gets serialized as 2 fields but packed as 1). +This results in the following changes in the `NoteInterface`: + +```diff +pub trait NoteInterface { +- fn serialize_content(self) -> [Field; N]; ++ fn pack_content(self) -> [Field; N]; + +- fn deserialize_content(fields: [Field; N]) -> Self; ++ fn unpack_content(fields: [Field; N]) -> Self; + + fn get_header(self) -> NoteHeader; + fn set_header(&mut self, header: NoteHeader) -> (); + fn get_note_type_id() -> Field; + fn compute_note_hash(self) -> Field; +} +``` ## 0.72.0 ### Some functions in `aztec.js` and `@aztec/accounts` are now async diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/log_assembly_strategies/default_aes128/note.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/log_assembly_strategies/default_aes128/note.nr index eb368074aed..e80f82f07f7 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/log_assembly_strategies/default_aes128/note.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/log_assembly_strategies/default_aes128/note.nr @@ -221,7 +221,7 @@ fn compute_note_plaintext_for_this_strategy(note: Note) -> [u8 where Note: NoteInterface, { - let serialized_note = note.serialize_content(); + let packed_note = note.pack_content(); let note_header = note.get_header(); let storage_slot = note_header.storage_slot; @@ -237,8 +237,8 @@ where plaintext_bytes[32 + i] = note_type_id_bytes[i]; } - for i in 0..serialized_note.len() { - let bytes: [u8; 32] = serialized_note[i].to_be_bytes(); + for i in 0..packed_note.len() { + let bytes: [u8; 32] = packed_note[i].to_be_bytes(); for j in 0..32 { plaintext_bytes[64 + i * 32 + j] = bytes[j]; } diff --git a/noir-projects/aztec-nr/aztec/src/macros/events/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/events/mod.nr index cb1f1aa6a70..ced8b82c401 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/events/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/events/mod.nr @@ -5,7 +5,7 @@ comptime fn generate_event_interface(s: StructDefinition) -> Quoted { let name = s.name(); let typ = s.as_type(); let (serialization_fields, _) = - generate_serialize_to_fields(quote { self }, typ, &[quote {self.header}]); + generate_serialize_to_fields(quote { self }, typ, &[quote {self.header}], false); let content_len = serialization_fields.len(); let event_type_id = compute_event_selector(s); diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr index 45f01b7b9e7..78aca4f53a5 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr @@ -250,7 +250,9 @@ comptime fn transform_public(f: FunctionDefinition) -> Quoted { // Public functions undergo a lot of transformations from their Aztec.nr form. let original_params = f.parameters(); let args_len = original_params - .map(|(name, typ): (Quoted, Type)| generate_serialize_to_fields(name, typ, &[]).0.len()) + .map(|(name, typ): (Quoted, Type)| { + generate_serialize_to_fields(name, typ, &[], false).0.len() + }) .fold(0, |acc: u32, val: u32| acc + val); // Unlike in the private case, in public the `context` does not need to receive the hash of the original params. diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr index 2f951652348..981b6dc1155 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/mod.nr @@ -112,10 +112,10 @@ comptime fn generate_contract_interface(m: Module) -> Quoted { } comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted { - let mut max_note_length: u32 = 0; + let mut max_note_content_length: u32 = 0; let notes = NOTES.entries(); let body = if notes.len() > 0 { - max_note_length = notes.fold( + max_note_content_length = notes.fold( 0, |acc, (_, (_, len, _, _)): (Type, (StructDefinition, u32, Field, [(Quoted, u32, bool)]))| { if len > acc { @@ -138,7 +138,7 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted { if_statements_list = if_statements_list.push_back( quote { $if_or_else_if note_type_id == $typ::get_note_type_id() { - aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::deserialize_content, note_header, compute_nullifier, serialized_note) + aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack_content, note_header, compute_nullifier, packed_note_content) } }, ); @@ -166,7 +166,7 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted { storage_slot: Field, note_type_id: Field, compute_nullifier: bool, - serialized_note: [Field; $max_note_length], + packed_note_content: [Field; $max_note_content_length], ) -> pub [Field; 4] { $body } @@ -184,14 +184,14 @@ comptime fn generate_process_log() -> Quoted { // A typical implementation of the lambda looks something like this: // ``` - // |serialized_note_content: BoundedVec, note_header: NoteHeader, note_type_id: Field| { + // |packed_note_content: BoundedVec, note_header: NoteHeader, note_type_id: Field| { // let hashes = if note_type_id == MyNoteType::get_note_type_id() { - // assert(serialized_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH); + // assert(packed_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH); // dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier( - // MyNoteType::deserialize_content, + // MyNoteType::unpack_content, // note_header, // true, - // serialized_note_content.storage(), + // packed_note_content.storage(), // ) // } else { // panic(f"Unknown note type id {note_type_id}") @@ -213,7 +213,7 @@ comptime fn generate_process_log() -> Quoted { let mut if_note_type_id_match_statements_list = &[]; for i in 0..notes.len() { - let (typ, (_, serialized_note_length, _, _)) = notes[i]; + let (typ, (_, packed_note_content_length, _, _)) = notes[i]; let if_or_else_if = if i == 0 { quote { if } @@ -224,17 +224,17 @@ comptime fn generate_process_log() -> Quoted { if_note_type_id_match_statements_list = if_note_type_id_match_statements_list.push_back( quote { $if_or_else_if note_type_id == $typ::get_note_type_id() { - // As an extra safety check we make sure that the serialized_note_content bounded vec has the + // As an extra safety check we make sure that the packed_note_content bounded vec has the // expected length, to avoid scenarios in which compute_note_hash_and_optionally_a_nullifier // silently trims the end if the log were to be longer. - let expected_len = $serialized_note_length; - let actual_len = serialized_note_content.len(); + let expected_len = $packed_note_content_length; + let actual_len = packed_note_content.len(); assert( actual_len == expected_len, f"Expected note content of length {expected_len} but got {actual_len} for note type id {note_type_id}" ); - aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::deserialize_content, note_header, true, serialized_note_content.storage()) + aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack_content, note_header, true, packed_note_content.storage()) } }, ); @@ -256,7 +256,7 @@ comptime fn generate_process_log() -> Quoted { unique_note_hashes_in_tx, first_nullifier_in_tx, recipient, - |serialized_note_content: BoundedVec, note_header, note_type_id| { + |packed_note_content: BoundedVec, note_header, note_type_id| { let hashes = $if_note_type_id_match_statements else { panic(f"Unknown note type id {note_type_id}") diff --git a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr index 5cec843fd44..2f480ebf68c 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr @@ -8,9 +8,9 @@ use std::{ comptime global NOTE_HEADER_TYPE: Type = type_of(NoteHeader::empty()); -/// A map from note type to (note_struct_definition, serialized_note_length, note_type_id, fields). +/// A map from note type to (note_struct_definition, note_packed_len, note_type_id, fields). /// `fields` is an array of tuples where each tuple contains the name of the field/struct member (e.g. `amount` -/// in `TokenNote`), the index of where the serialized member starts in the serialized note and a flag indicating +/// in `TokenNote`), the index of where the packed member starts in the packed note and a flag indicating /// whether the field is nullable or not. pub comptime mut global NOTES: UHashMap> = UHashMap::default(); @@ -31,18 +31,14 @@ comptime fn get_next_note_type_id() -> Field { } /// Generates default `NoteInterface` implementation for a given note struct `s` and returns it as quote along with -/// the length of the serialized note. +/// the length of the packed note. /// /// impl NoteInterface for NoteStruct { -/// fn to_be_bytes(self, storage_slot: Field) -> [u8; N * 32 + 64] { +/// fn pack_content(self) -> [Field; N] { /// ... /// } /// -/// fn deserialize_content(serialized_content: [Field; N]) -> Self { -/// ... -/// } -/// -/// fn serialize_content(self) -> [Field; N] { +/// fn unpack_content(packed_content: [Field; N]) -> Self { /// ... /// } /// @@ -71,10 +67,13 @@ comptime fn generate_note_interface( let name = s.name(); let typ = s.as_type(); + // In notes we care about DA costs so we enable packing + let packing_enabled = true; + // First we compute note content serialization. We do that by passing the whole note struct // to the `generate_serialize_to_fields(...)` and omitting the header. let (content_fields_list, content_aux_vars_list) = - generate_serialize_to_fields(quote { self }, typ, &[quote {self.header}]); + generate_serialize_to_fields(quote { self }, typ, &[quote {self.header}], packing_enabled); // If there are `aux_vars` we need to join them with `;` and add a trailing `;` to the joined string. let content_aux_vars = if content_aux_vars_list.len() > 0 { @@ -86,13 +85,14 @@ comptime fn generate_note_interface( let content_fields = content_fields_list.join(quote {,}); let content_len = content_fields_list.len(); - let (deserialized_content, _) = generate_deserialize_from_fields( + let (unpacked_content, _) = generate_deserialize_from_fields( quote {}, typ, - quote { serialized_content }, // "serialized_content" is argument of NoteInterface::deserialize_content + quote { packed_content }, // "packed_content" is argument of NoteInterface::unpack_content 0, quote {header}, quote { aztec::note::note_header::NoteHeader::empty() }, + packing_enabled, ); // Second we compute quotes for MSM @@ -114,15 +114,15 @@ comptime fn generate_note_interface( ( quote { impl aztec::note::note_interface::NoteInterface<$content_len> for $name { - fn deserialize_content(serialized_content: [Field; $content_len]) -> Self { - $deserialized_content - } - - fn serialize_content(self) -> [Field; $content_len] { + fn pack_content(self) -> [Field; $content_len] { $content_aux_vars [$content_fields] } + fn unpack_content(packed_content: [Field; $content_len]) -> Self { + $unpacked_content + } + fn get_note_type_id() -> Field { $note_type_id } @@ -293,7 +293,8 @@ comptime fn generate_multi_scalar_mul( for i in 0..indexed_fields.len() { let (field_name, typ, index) = indexed_fields[i]; let start_generator_index = index + 1; - let (serialization_fields, aux_vars) = generate_serialize_to_fields(field_name, typ, &[]); + let (serialization_fields, aux_vars) = + generate_serialize_to_fields(field_name, typ, &[], true); for j in 0..serialization_fields.len() { let serialization_field = serialization_fields[j]; let generator_index = start_generator_index + j; @@ -356,10 +357,10 @@ comptime fn generate_multi_scalar_mul( /// log_plaintext[32 + i] = note_type_id_bytes[i]; /// } /// -/// let serialized_note = [npk_m_hash as Field, randomness as Field]; +/// let packed_note_content = [npk_m_hash as Field, randomness as Field]; /// -/// for i in 0..serialized_note.len() { -/// let bytes: [u8; 32] = serialized_note[i].to_be_bytes(); +/// for i in 0..packed_note_content.len() { +/// let bytes: [u8; 32] = packed_note_content[i].to_be_bytes(); /// for j in 0..32 { /// log_plaintext[64 + i * 32 + j] = bytes[j]; /// } @@ -487,7 +488,8 @@ comptime fn get_setup_log_plaintext_body( let to_omit = indexed_nullable_fields.map(|(name, _, _): (Quoted, Type, u32)| name).push_back( quote { header }, ); - let (fields_list, aux_vars) = generate_serialize_to_fields(quote { }, s.as_type(), to_omit); + let (fields_list, aux_vars) = + generate_serialize_to_fields(quote { }, s.as_type(), to_omit, true); // If there are `aux_vars` we need to join them with `;` and add a trailing `;` to the joined string. let aux_vars_for_serialization = if aux_vars.len() > 0 { @@ -510,10 +512,10 @@ comptime fn get_setup_log_plaintext_body( } $aux_vars_for_serialization - let serialized_note = [$fields]; + let packed_note_content = [$fields]; - for i in 0..serialized_note.len() { - let bytes: [u8; 32] = serialized_note[i].to_be_bytes(); + for i in 0..packed_note_content.len() { + let bytes: [u8; 32] = packed_note_content[i].to_be_bytes(); for j in 0..32 { log_plaintext[64 + i * 32 + j] = bytes[j]; } @@ -609,7 +611,7 @@ comptime fn generate_finalization_payload( quote { header }, ); let (nullable_fields_list, aux_vars) = - generate_serialize_to_fields(quote { }, s.as_type(), to_omit); + generate_serialize_to_fields(quote { }, s.as_type(), to_omit, true); // If there are `aux_vars` we need to join them with `;` and add a trailing `;` to the joined string. let aux_vars_for_serialization = if aux_vars.len() > 0 { @@ -624,8 +626,11 @@ comptime fn generate_finalization_payload( let nullable_fields = nullable_fields_list.join(quote {,}); // Now we compute quotes relevant to the multi-scalar multiplication. - let (generators_list, _, args_list, msm_aux_vars) = - generate_multi_scalar_mul(indexed_nullable_fields); + // Note 1: We ignore the `scalars_list` and `aux_vars` return values because it's not used by the `emit_note_hash` + // function. Instead, we use `public_values` (defined on the finalization payload struct) and the scalar list + // is computed in the for-loop below. + // Note 2: The `args_list` is not used for note hash MSM but instead for the `new` function. + let (generators_list, _, args_list, _) = generate_multi_scalar_mul(indexed_nullable_fields); // We generate scalars_list manually as we need it to refer self.public_values let mut scalars_list: [Quoted] = &[]; @@ -688,7 +693,6 @@ comptime fn generate_finalization_payload( let hiding_point: aztec::prelude::Point = self.context.storage_read(self.hiding_point_slot); assert(!aztec::protocol_types::traits::is_empty(hiding_point), "transfer not prepared"); - $msm_aux_vars let finalization_hiding_point = std::embedded_curve_ops::multi_scalar_mul( [$generators], [$scalars] @@ -793,11 +797,11 @@ comptime fn generate_partial_note_impl( } } -/// Registers a note struct `note` with the given `note_serialized_len`, `note_type_id`, `fixed_fields` and +/// Registers a note struct `note` with the given `note_packed_len`, `note_type_id`, `fixed_fields` and /// `nullable_fields` in the global `NOTES` map. comptime fn register_note( note: StructDefinition, - note_serialized_len: u32, + note_packed_len: u32, note_type_id: Field, fixed_fields: [(Quoted, Type, u32)], nullable_fields: [(Quoted, Type, u32)], @@ -812,7 +816,7 @@ comptime fn register_note( fields = fields.push_back((name, index, true)); } - NOTES.insert(note.as_type(), (note, note_serialized_len, note_type_id, fields)); + NOTES.insert(note.as_type(), (note, note_packed_len, note_type_id, fields)); } /// Separates note struct members into fixed and nullable ones. It also stores the index of where each struct member @@ -834,7 +838,7 @@ comptime fn index_note_fields( indexed_nullable_fields = indexed_nullable_fields.push_back((name, typ, counter)); } } - let (serialization_fields, _) = generate_serialize_to_fields(name, typ, &[]); + let (serialization_fields, _) = generate_serialize_to_fields(name, typ, &[], true); // Each struct member can occupy multiple fields so we need to increment the counter accordingly counter += serialization_fields.len(); } @@ -851,7 +855,7 @@ comptime fn inject_note_header(s: StructDefinition) { } } -/// Injects `NoteHeader` to the note struct if not present and generates the following: +/// Injects `NoteHeader` to the note struct `s` if not present and generates the following: /// - NoteTypeProperties /// - SetupPayload /// - FinalizationPayload @@ -861,10 +865,13 @@ comptime fn inject_note_header(s: StructDefinition) { /// - Registers the note in the global `NOTES` map. /// /// For more details on the generated code, see the individual functions. +/// +/// `nullable_fields` are a list of quotes passed in as varargs which are used to identify which fields/struct members +/// in the partial note are nullable. #[varargs] pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> Quoted { // We separate struct members into fixed ones and nullable ones and we store info about the start index of each - // member in the serialized note array. + // member in the packed note array. let (indexed_fixed_fields, indexed_nullable_fields) = index_note_fields(s, nullable_fields); // We inject NoteHeader if it's not present in the note struct. @@ -876,7 +883,7 @@ pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> generate_setup_payload(s, indexed_fixed_fields, indexed_nullable_fields); let (finalization_payload_impl, finalization_payload_name) = generate_finalization_payload(s, indexed_fixed_fields, indexed_nullable_fields); - let (note_interface_impl, note_serialized_len) = generate_note_interface( + let (note_interface_impl, note_packed_len) = generate_note_interface( s, note_type_id, indexed_fixed_fields, @@ -886,7 +893,7 @@ pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> generate_partial_note_impl(s, setup_payload_name, finalization_payload_name); register_note( s, - note_serialized_len, + note_packed_len, note_type_id, indexed_fixed_fields, indexed_nullable_fields, @@ -915,7 +922,7 @@ pub comptime fn note(s: StructDefinition) -> Quoted { let note_properties = generate_note_properties(s); let note_type_id = get_next_note_type_id(); - let (note_interface_impl, note_serialized_len) = generate_note_interface( + let (note_interface_impl, note_packed_len) = generate_note_interface( s, note_type_id, indexed_fixed_fields, @@ -923,7 +930,7 @@ pub comptime fn note(s: StructDefinition) -> Quoted { ); register_note( s, - note_serialized_len, + note_packed_len, note_type_id, indexed_fixed_fields, indexed_nullable_fields, @@ -952,7 +959,7 @@ pub comptime fn note_custom_interface(s: StructDefinition) -> Quoted { ); let name = s.name(); - let note_serialized_len = note_interface_impl + let note_packed_len = note_interface_impl .expect(f"Note {name} must implement NoteInterface trait") .trait_generic_args()[0] .as_constant() @@ -961,7 +968,7 @@ pub comptime fn note_custom_interface(s: StructDefinition) -> Quoted { let (indexed_fixed_fields, indexed_nullable_fields) = index_note_fields(s, &[]); register_note( s, - note_serialized_len, + note_packed_len, note_type_id, indexed_fixed_fields, indexed_nullable_fields, diff --git a/noir-projects/aztec-nr/aztec/src/note/discovery/mod.nr b/noir-projects/aztec-nr/aztec/src/note/discovery/mod.nr index 1f0c0316832..ddbcd80e9de 100644 --- a/noir-projects/aztec-nr/aztec/src/note/discovery/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/note/discovery/mod.nr @@ -30,14 +30,14 @@ pub struct NoteHashesAndNullifier { /// note in the contract given their contents. A typical implementation of such a function would look like this: /// /// ``` -/// |serialized_note_content, note_header, note_type_id| { +/// |packed_note_content, note_header, note_type_id| { /// let hashes = if note_type_id == MyNoteType::get_note_type_id() { -/// assert(serialized_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH); +/// assert(packed_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH); /// dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier( -/// MyNoteType::deserialize_content, +/// MyNoteType::unpack_content, /// note_header, /// true, -/// serialized_note_content.storage(), +/// packed_note_content.storage(), /// ) /// } else { /// panic(f"Unknown note type id {note_type_id}") @@ -59,7 +59,7 @@ pub unconstrained fn do_process_log( recipient: AztecAddress, compute_note_hash_and_nullifier: fn[Env](BoundedVec, NoteHeader, Field) -> Option, ) { - let (storage_slot, note_type_id, serialized_note_content) = + let (storage_slot, note_type_id, packed_note_content) = destructure_log_plaintext(log_plaintext); // We need to find the note's nonce, which is the one that results in one of the unique note hashes from tx_hash @@ -71,12 +71,8 @@ pub unconstrained fn do_process_log( let header = NoteHeader::new(context.this_address(), candidate_nonce, storage_slot); // TODO(#11157): handle failed note_hash_and_nullifier computation - let hashes = compute_note_hash_and_nullifier( - serialized_note_content, - header, - note_type_id, - ) - .unwrap(); + let hashes = + compute_note_hash_and_nullifier(packed_note_content, header, note_type_id).unwrap(); if hashes.unique_note_hash == expected_unique_note_hash { // TODO(#10726): push these into a vec to deliver all at once instead of having one oracle call per note @@ -86,7 +82,7 @@ pub unconstrained fn do_process_log( context.this_address(), // TODO(#10727): allow other contracts to deliver notes storage_slot, candidate_nonce, - serialized_note_content, + packed_note_content, hashes.note_hash, hashes.inner_nullifier, tx_hash, @@ -117,9 +113,9 @@ unconstrained fn destructure_log_plaintext( let storage_slot = log_plaintext.get(0); let note_type_id = log_plaintext.get(1); - let serialized_note_content = array::subbvec(log_plaintext, NOTE_LOG_RESERVED_FIELDS); + let packed_note_content = array::subbvec(log_plaintext, NOTE_LOG_RESERVED_FIELDS); - (storage_slot, note_type_id, serialized_note_content) + (storage_slot, note_type_id, packed_note_content) } fn for_each_in_bounded_vec( diff --git a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr index d284a8e90fc..2359e7a9c64 100644 --- a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr +++ b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr @@ -22,11 +22,11 @@ where note.set_header(header); let note_hash = note.compute_note_hash(); - let serialized_note = Note::serialize_content(*note); + let packed_note_content = Note::pack_content(*note); notify_created_note( storage_slot, Note::get_note_type_id(), - serialized_note, + packed_note_content, note_hash, note_hash_counter, ); 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 02f94ef64c3..560969ca198 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 @@ -17,13 +17,13 @@ pub use crate::note::constants::MAX_NOTES_PER_PAGE; mod test; fn extract_property_value_from_selector( - serialized_note: [Field; N], + packed_note_content: [Field; N], selector: PropertySelector, ) -> Field { - // Selectors use PropertySelectors in order to locate note properties inside the serialized note. - // This allows easier packing and custom (de)serialization schemas. A note property is located - // inside the serialized note using the index inside the array, a byte offset and a length. - let value: [u8; 32] = serialized_note[selector.index].to_be_bytes(); + // Selectors use PropertySelectors in order to locate note properties inside the packed note. + // This allows easier packing and custom (un)packing schemas. A note property is located + // inside the packed note using the index inside the array, a byte offset and a length. + let value: [u8; 32] = packed_note_content[selector.index].to_be_bytes(); let offset = selector.offset; let length = selector.length; let mut value_field = 0 as Field; @@ -47,14 +47,14 @@ where assert(header.storage_slot == storage_slot, "Mismatch note header storage slot."); } -fn check_note_fields( - serialized_note: [Field; N], +fn check_note_content( + packed_note_content: [Field; N], selects: BoundedVec, N>, ) { for i in 0..selects.len() { let select = selects.get_unchecked(i).unwrap_unchecked(); let value_field = - extract_property_value_from_selector(serialized_note, select.property_selector); + extract_property_value_from_selector(packed_note_content, select.property_selector); assert( compare(value_field, select.comparator, select.value.to_field()), @@ -154,9 +154,9 @@ where for i in 0..options.limit { if i < notes.len() { let note = notes.get_unchecked(i); - let fields = note.serialize_content(); + let fields = note.pack_content(); check_note_header(*context, storage_slot, note); - check_note_fields(fields, options.selects); + check_note_content(fields, options.selects); if i != 0 { check_notes_order(prev_fields, fields, options.sorts); } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr index 6f264fc4df2..94ed825f986 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr @@ -38,9 +38,9 @@ pub trait NullifiableNote { // docs:start:note_interface // Autogenerated by the #[note] macro pub trait NoteInterface { - fn serialize_content(self) -> [Field; N]; + fn pack_content(self) -> [Field; N]; - fn deserialize_content(fields: [Field; N]) -> Self; + fn unpack_content(fields: [Field; N]) -> Self; fn get_header(self) -> NoteHeader; diff --git a/noir-projects/aztec-nr/aztec/src/note/utils.nr b/noir-projects/aztec-nr/aztec/src/note/utils.nr index 55c5870d0ff..730540ec021 100644 --- a/noir-projects/aztec-nr/aztec/src/note/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/note/utils.nr @@ -117,15 +117,15 @@ where } pub unconstrained fn compute_note_hash_and_optionally_a_nullifier( - deserialize_content: fn([Field; N]) -> T, + unpack_content: fn([Field; N]) -> T, note_header: NoteHeader, compute_nullifier: bool, - serialized_note: [Field; S], + packed_note_content: [Field; S], ) -> [Field; 4] where T: NoteInterface + NullifiableNote, { - let mut note = deserialize_content(array::subarray(serialized_note, 0)); + let mut note = unpack_content(array::subarray(packed_note_content, 0)); note.set_header(note_header); let note_hash = note.compute_note_hash(); diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 642e1281915..f1d779c43c4 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -10,7 +10,7 @@ use dep::protocol_types::{ pub fn notify_created_note( storage_slot: Field, note_type_id: Field, - serialized_note: [Field; N], + packed_note_content: [Field; N], note_hash: Field, counter: u32, ) { @@ -20,7 +20,7 @@ pub fn notify_created_note( notify_created_note_oracle_wrapper( storage_slot, note_type_id, - serialized_note, + packed_note_content, note_hash, counter, ) @@ -45,14 +45,14 @@ pub fn notify_created_nullifier(nullifier: Field) { unconstrained fn notify_created_note_oracle_wrapper( storage_slot: Field, note_type_id: Field, - serialized_note: [Field; N], + packed_note_content: [Field; N], note_hash: Field, counter: u32, ) { let _ = notify_created_note_oracle( storage_slot, note_type_id, - serialized_note, + packed_note_content, note_hash, counter, ); @@ -62,7 +62,7 @@ unconstrained fn notify_created_note_oracle_wrapper( unconstrained fn notify_created_note_oracle( _storage_slot: Field, _note_type_id: Field, - _serialized_note: [Field; N], + _packed_note_content: [Field; N], _note_hash: Field, _counter: u32, ) -> Field {} @@ -200,7 +200,7 @@ where let note_hash_counter = fields[read_offset + 1] as u32; let note_content = array::subarray(fields, read_offset + 2); - let mut note = Note::deserialize_content(note_content); + let mut note = Note::unpack_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/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index 1ab5a63f863..d5e494cc5a7 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -148,11 +148,11 @@ impl TestEnvironment { let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; note.set_header(header); let note_hash = note.compute_note_hash(); - let serialized_note = Note::serialize_content(*note); + let packed_content = Note::pack_content(*note); notify_created_note( storage_slot, Note::get_note_type_id(), - serialized_note, + packed_content, note_hash, note_hash_counter, ); diff --git a/noir-projects/aztec-nr/aztec/src/test/mocks/mock_note.nr b/noir-projects/aztec-nr/aztec/src/test/mocks/mock_note.nr index b28c49a5ba8..9f96a0489d7 100644 --- a/noir-projects/aztec-nr/aztec/src/test/mocks/mock_note.nr +++ b/noir-projects/aztec-nr/aztec/src/test/mocks/mock_note.nr @@ -47,11 +47,11 @@ impl NullifiableNote for MockNote { } impl NoteInterface for MockNote { - fn serialize_content(self) -> [Field; MOCK_NOTE_LENGTH] { + fn pack_content(self) -> [Field; MOCK_NOTE_LENGTH] { [self.value] } - fn deserialize_content(fields: [Field; MOCK_NOTE_LENGTH]) -> Self { + fn unpack_content(fields: [Field; MOCK_NOTE_LENGTH]) -> Self { Self { value: fields[0], header: NoteHeader::empty() } } diff --git a/noir-projects/noir-contracts/contracts/ecdsa_public_key_note/src/lib.nr b/noir-projects/noir-contracts/contracts/ecdsa_public_key_note/src/lib.nr index b560bbacb68..d3d5589c300 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_public_key_note/src/lib.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_public_key_note/src/lib.nr @@ -21,13 +21,13 @@ pub struct EcdsaPublicKeyNote { } impl NoteInterface for EcdsaPublicKeyNote { - // Cannot use the automatic serialization since x and y don't fit. Serialize the note as 5 fields where: + // Cannot use the automatic packing since x and y don't fit. Pack the note as 5 fields where: // [0] = x[0..31] (upper bound excluded) // [1] = x[31] // [2] = y[0..31] // [3] = y[31] // [4] = owner - fn serialize_content(self) -> [Field; ECDSA_PUBLIC_KEY_NOTE_LEN] { + fn pack_content(self) -> [Field; ECDSA_PUBLIC_KEY_NOTE_LEN] { let mut x: Field = 0; let mut y: Field = 0; let mut mul: Field = 1; @@ -46,24 +46,24 @@ impl NoteInterface for EcdsaPublicKeyNote { [x, last_x, y, last_y, self.owner.to_field()] } - // Cannot use the automatic deserialization for the aforementioned reasons - fn deserialize_content(serialized_note: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote { + // Cannot use the automatic unpacking for the aforementioned reasons + fn unpack_content(packed_content: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote { let mut x: [u8; 32] = [0; 32]; let mut y: [u8; 32] = [0; 32]; - let part_x:[u8; 32] = serialized_note[0].to_be_bytes(); + let part_x:[u8; 32] = packed_content[0].to_be_bytes(); for i in 0..31 { x[i] = part_x[i + 1]; } - x[31] = serialized_note[1].to_be_bytes::<32>()[31]; + x[31] = packed_content[1].to_be_bytes::<32>()[31]; - let part_y:[u8; 32] = serialized_note[2].to_be_bytes(); + let part_y:[u8; 32] = packed_content[2].to_be_bytes(); for i in 0..31 { y[i] = part_y[i + 1]; } - y[31] = serialized_note[3].to_be_bytes::<32>()[31]; + y[31] = packed_content[3].to_be_bytes::<32>()[31]; - EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(serialized_note[4]), header: NoteHeader::empty() } + EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(packed_content[4]), header: NoteHeader::empty() } } fn get_note_type_id() -> Field { @@ -80,15 +80,15 @@ impl NoteInterface for EcdsaPublicKeyNote { } fn compute_note_hash(self) -> Field { - let serialized = self.serialize_content(); + let packed_content = self.pack_content(); std::embedded_curve_ops::multi_scalar_mul( [Gx_1, Gx_2, Gy_1, Gy_2, G_owner, G_slot], [ - from_field_unsafe(serialized[0]), - from_field_unsafe(serialized[1]), - from_field_unsafe(serialized[2]), - from_field_unsafe(serialized[3]), - from_field_unsafe(serialized[4]), + from_field_unsafe(packed_content[0]), + from_field_unsafe(packed_content[1]), + from_field_unsafe(packed_content[2]), + from_field_unsafe(packed_content[3]), + from_field_unsafe(packed_content[4]), from_field_unsafe(self.get_header().storage_slot) ] ).x diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr b/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr index 301be09198f..d08d8416f06 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr @@ -1,4 +1,4 @@ -use super::traits::{Deserialize, Serialize}; +use super::traits::{Deserialize, Packable, Serialize}; /// Generates code that deserializes a struct, primitive type, array or string from a field array. /// @@ -9,6 +9,8 @@ use super::traits::{Deserialize, Serialize}; /// - `num_already_consumed`: The number of fields already processed in previous recursion calls. /// - `to_replace`: The name of a specific field that should be replaced during deserialization. /// - `ro_replace_with`: The value to replace the `to_replace` field with (e.g., `NoteHeader::empty()`). +/// - `should_unpack`: A boolean indicating whether the type should be unpacked (see description of `Packable` +/// and `Serialize` trait for more information about the difference between packing and serialization). /// /// # Returns /// A tuple containing: @@ -50,6 +52,17 @@ use super::traits::{Deserialize, Serialize}; /// header: NoteHeader::empty() // Default/empty header /// } /// ``` +/// # Nested Struct Example with Unpacking +/// - given the same setup as above and given that U128, AztecAddress and Field implement the `Packable` trait +/// when skipping `header` the result we get is: +/// ``` +/// UintNote { +/// value: aztec::protocol_types::traits::Packable::unpack([fields[0]]), +/// owner: aztec::protocol_types::traits::Packable::unpack([fields[1]]), +/// randomness: aztec::protocol_types::traits::Packable::unpack([fields[2]]), +/// header: NoteHeader::empty() +/// } +/// ``` /// /// # Panics /// - If the deserialization logic encounters a type it does not support. @@ -61,6 +74,7 @@ pub comptime fn generate_deserialize_from_fields( num_already_consumed: u32, to_replace: Quoted, ro_replace_with: Quoted, + should_unpack: bool, ) -> (Quoted, u32) { let mut result = quote {}; // Counter for the number of fields consumed @@ -71,7 +85,30 @@ pub comptime fn generate_deserialize_from_fields( // The currently processed field should be replaced so we do so result = ro_replace_with; } else { - if typ.is_field() | typ.as_integer().is_some() | typ.is_bool() { + // If the type implements `Packable`, its length will be assigned to the `maybe_packed_len_typ` variable. + let maybe_packed_len_typ = std::meta::typ::fresh_type_variable(); + let packable_constraint = quote { Packable<$maybe_packed_len_typ> }.as_trait_constraint(); + + if (should_unpack & typ.implements(packable_constraint)) { + // Unpacking is enabled and the given type implements the `Packable` trait so we call the `unpack()` + // method, add the resulting field array to `aux_vars` and each field to `fields`. + let packed_len = maybe_packed_len_typ.as_constant().unwrap(); + + // We copy the packed fields into a new array and pass that to the unpack function in a quote + let mut packed_fields_quotes = &[]; + for i in 0..packed_len { + let index_in_field_array = i + num_already_consumed; + packed_fields_quotes = packed_fields_quotes.push_back( + quote { $field_array_name[$index_in_field_array] }, + ); + } + let packed_fields = packed_fields_quotes.join(quote {,}); + + // Now we call unpack on the type + result = quote { aztec::protocol_types::traits::Packable::unpack([ $packed_fields ]) }; + + consumed_counter = packed_len; + } else if typ.is_field() | typ.as_integer().is_some() | typ.is_bool() { // The field is a primitive so we just reference it in the field array result = quote { $field_array_name[$num_already_consumed] as $typ }; consumed_counter = 1; @@ -93,6 +130,7 @@ pub comptime fn generate_deserialize_from_fields( consumed_counter + num_already_consumed, to_replace, ro_replace_with, + should_unpack, ); // We increment the consumed counter by the number of fields consumed in the recursion consumed_counter += num_consumed_in_recursion; @@ -126,6 +164,7 @@ pub comptime fn generate_deserialize_from_fields( consumed_counter + num_already_consumed, to_replace, ro_replace_with, + should_unpack, ); // We increment the consumed counter by the number of fields consumed in the recursion consumed_counter += num_consumed_in_recursion; @@ -154,6 +193,7 @@ pub comptime fn generate_deserialize_from_fields( consumed_counter + num_already_consumed, to_replace, ro_replace_with, + should_unpack, ); // We should consume just one field in the recursion so we sanity check that @@ -185,12 +225,14 @@ pub comptime fn generate_deserialize_from_fields( } /// Generates code that serializes a type into an array of fields. Also generates auxiliary variables if necessary -/// for serialization. +/// for serialization. If `should_pack` is true, we check if the type implements the `Packable` trait and pack it +/// if it does. /// /// # Parameters /// - `name`: The base identifier (e.g., `self`, `some_var`). /// - `typ`: The type being serialized (e.g., a custom struct, array, or primitive type). /// - `omit`: A list of field names (as `Quoted`) to be excluded from the serialized output. +/// - `should_pack`: A boolean indicating whether the type should be packed. /// /// # Returns /// A tuple containing: @@ -210,7 +252,7 @@ pub comptime fn generate_deserialize_from_fields( /// /// Serializing the struct: /// ```rust -/// generate_serialize_to_fields(quote { my_u128 }, U128, &[]) +/// generate_serialize_to_fields(quote { my_u128 }, U128, &[], false) /// // Returns: /// // ([`my_u128.lo`, `my_u128.hi`], []) /// ``` @@ -227,7 +269,7 @@ pub comptime fn generate_deserialize_from_fields( /// /// Serializing while omitting `header`: /// ```rust -/// generate_serialize_to_fields(quote { self }, UintNote, &[quote { self.header }]) +/// generate_serialize_to_fields(quote { self }, UintNote, &[quote { self.header }], false) /// // Returns: /// // ([`self.value.lo`, `self.value.hi`, `self.randomness`], []) /// ``` @@ -235,7 +277,7 @@ pub comptime fn generate_deserialize_from_fields( /// ## Array /// For an array type: /// ```rust -/// generate_serialize_to_fields(quote { my_array }, [Field; 3], &[]) +/// generate_serialize_to_fields(quote { my_array }, [Field; 3], &[], false) /// // Returns: /// // ([`my_array[0]`, `my_array[1]`, `my_array[2]`], []) /// ``` @@ -243,12 +285,31 @@ pub comptime fn generate_deserialize_from_fields( /// ## String /// For a string field, where each character is serialized as a `Field`: /// ```rust -/// generate_serialize_to_fields(quote { my_string }, StringType, &[]) +/// generate_serialize_to_fields(quote { my_string }, StringType, &[], false) /// // Returns: /// // ([`my_string_as_bytes[0] as Field`, `my_string_as_bytes[1] as Field`, ...], /// // [`let my_string_as_bytes = my_string.as_bytes()`]) /// ``` /// +/// ## Nested Struct with Omitted Field and packing enabled +/// - U128 has a `Packable` implementation hence it will be packed. +/// +/// For a more complex struct: +/// ```rust +/// struct UintNote { +/// value: U128, +/// randomness: Field, +/// header: NoteHeader, +/// } +/// ``` +/// +/// Serializing while omitting `header`: +/// ```rust +/// generate_serialize_to_fields(quote { self }, UintNote, &[quote { self.header }], true) +/// // Returns: +/// // ([`value_packed[0]`, `self.randomness`], [`let value_packed = self.value.pack()`]) +/// ``` +/// /// # Panics /// - If the type is unsupported for serialization. /// - If the provided `typ` contains invalid constants or incompatible structures. @@ -256,13 +317,35 @@ pub comptime fn generate_serialize_to_fields( name: Quoted, typ: Type, omit: [Quoted], + should_pack: bool, ) -> ([Quoted], [Quoted]) { let mut fields = &[]; let mut aux_vars = &[]; // Proceed if none of the omit rules omits this name if !omit.any(|to_omit| to_omit == name) { - if typ.is_field() { + // If the type implements `Packable`, its length will be assigned to the `maybe_packed_len_typ` variable. + let maybe_packed_len_typ = std::meta::typ::fresh_type_variable(); + let packable_constraint = quote { Packable<$maybe_packed_len_typ> }.as_trait_constraint(); + + if (should_pack & typ.implements(packable_constraint)) { + // Packing is enabled and the given type implements the `Packable` trait so we call the `pack()` + // method, add the resulting field array to `aux_vars` and each field to `fields`. + let packed_len = maybe_packed_len_typ.as_constant().unwrap(); + + // We collapse the name to a one that gets tokenized as a single token (e.g. "self.value" -> "self_value"). + let name_at_one_token = collapse_to_one_token(name); + let packed_struct_name = f"{name_at_one_token}_aux_var".quoted_contents(); + + // We add the individual fields to the fields array + let packed_struct = quote { let $packed_struct_name = $name.pack() }; + for i in 0..packed_len { + fields = fields.push_back(quote { $packed_struct_name[$i] }); + } + + // We add the new auxiliary variable to the aux_vars array + aux_vars = aux_vars.push_back(packed_struct); + } else if typ.is_field() { // For field we just add the value to fields fields = fields.push_back(name); } else if typ.as_integer().is_some() | typ.is_bool() { @@ -282,7 +365,12 @@ pub comptime fn generate_serialize_to_fields( // can typically be `self` when implementing a method on a struct. quote { $name.$param_name } }; - generate_serialize_to_fields(quote {$maybe_prefixed_name}, param_type, omit) + generate_serialize_to_fields( + quote {$maybe_prefixed_name}, + param_type, + omit, + should_pack, + ) }); let struct_flattened_fields = struct_flattened.fold( &[], @@ -295,12 +383,16 @@ pub comptime fn generate_serialize_to_fields( fields = fields.append(struct_flattened_fields); aux_vars = aux_vars.append(struct_flattened_aux_vars); } else if typ.as_array().is_some() { - // For array we recursively call generate_serialize_to_fields for each element + // For array we recursively call `generate_serialize_to_fields(...)` for each element let (element_type, array_len) = typ.as_array().unwrap(); let array_len = array_len.as_constant().unwrap(); for i in 0..array_len { - let (element_fields, element_aux_vars) = - generate_serialize_to_fields(quote { $name[$i] }, element_type, omit); + let (element_fields, element_aux_vars) = generate_serialize_to_fields( + quote { $name[$i] }, + element_type, + omit, + should_pack, + ); fields = fields.append(element_fields); aux_vars = aux_vars.append(element_aux_vars); } @@ -330,9 +422,22 @@ pub comptime fn generate_serialize_to_fields( (fields, aux_vars) } +/// From a quote that gets tokenized to a multiple tokens we collapse it to a single token by replacing all `.` with `_`. +/// E.g. "self.value" -> "self_value" +comptime fn collapse_to_one_token(q: Quoted) -> Quoted { + let tokens = q.tokens(); + + let mut single_token = quote {}; + for token in tokens { + let new_token = if token == quote {.} { quote {_} } else { token }; + single_token = f"{single_token}{new_token}".quoted_contents(); + } + single_token +} + pub(crate) comptime fn derive_serialize(s: StructDefinition) -> Quoted { let typ = s.as_type(); - let (fields, aux_vars) = generate_serialize_to_fields(quote { self }, typ, &[]); + let (fields, aux_vars) = generate_serialize_to_fields(quote { self }, typ, &[], false); let aux_vars_for_serialization = if aux_vars.len() > 0 { let joint = aux_vars.join(quote {;}); quote { $joint; } @@ -354,7 +459,7 @@ pub(crate) comptime fn derive_serialize(s: StructDefinition) -> Quoted { pub(crate) comptime fn derive_deserialize(s: StructDefinition) -> Quoted { let typ = s.as_type(); - let (fields, _) = generate_serialize_to_fields(quote { self }, typ, &[]); + let (fields, _) = generate_serialize_to_fields(quote { self }, typ, &[], false); let serialized_len = fields.len(); let (deserialized, _) = generate_deserialize_from_fields( quote { self }, @@ -363,6 +468,7 @@ pub(crate) comptime fn derive_deserialize(s: StructDefinition) -> Quoted { 0, quote {}, quote {}, + false, ); quote { impl Deserialize<$serialized_len> for $typ { diff --git a/yarn-project/pxe/src/database/note_dao.ts b/yarn-project/pxe/src/database/note_dao.ts index 77b5ed85e80..242968609db 100644 --- a/yarn-project/pxe/src/database/note_dao.ts +++ b/yarn-project/pxe/src/database/note_dao.ts @@ -6,14 +6,14 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type NoteData } from '@aztec/simulator/client'; /** - * A Note Data Access Object, representing a note that was comitted to the note hash tree, holding all of the + * A Note Data Access Object, representing a note that was committed to the note hash tree, holding all of the * information required to use it during execution and manage its state. */ export class NoteDao implements NoteData { constructor( // Note information - /** The serialized content of the note, as will be returned in the getNotes oracle. */ + /** The packed content of the note, as will be returned in the getNotes oracle. */ public note: Note, /** The address of the contract that created the note (i.e. the address used by the kernel during siloing). */ public contractAddress: AztecAddress, diff --git a/yarn-project/simulator/src/client/pick_notes.ts b/yarn-project/simulator/src/client/pick_notes.ts index 26e65e64616..1e27fefa332 100644 --- a/yarn-project/simulator/src/client/pick_notes.ts +++ b/yarn-project/simulator/src/client/pick_notes.ts @@ -84,7 +84,7 @@ interface ContainsNote { note: Note; } -const selectPropertyFromSerializedNote = (noteData: Fr[], selector: PropertySelector): Fr => { +const selectPropertyFromPackedNoteContent = (noteData: Fr[], selector: PropertySelector): Fr => { const noteValueBuffer = noteData[selector.index].toBuffer(); const noteValue = noteValueBuffer.subarray(selector.offset, selector.offset + selector.length); return Fr.fromBuffer(noteValue); @@ -93,7 +93,7 @@ const selectPropertyFromSerializedNote = (noteData: Fr[], selector: PropertySele const selectNotes = (noteDatas: T[], selects: Select[]): T[] => noteDatas.filter(noteData => selects.every(({ selector, value, comparator }) => { - const noteValueFr = selectPropertyFromSerializedNote(noteData.note.items, selector); + const noteValueFr = selectPropertyFromPackedNoteContent(noteData.note.items, selector); const comparatorSelector = { [Comparator.EQ]: () => noteValueFr.equals(value), [Comparator.NEQ]: () => !noteValueFr.equals(value), @@ -117,8 +117,8 @@ const sortNotes = (a: Fr[], b: Fr[], sorts: Sort[], level = 0): number => { return 0; } - const aValue = selectPropertyFromSerializedNote(a, selector); - const bValue = selectPropertyFromSerializedNote(b, selector); + const aValue = selectPropertyFromPackedNoteContent(a, selector); + const bValue = selectPropertyFromPackedNoteContent(b, selector); const dir = order === 1 ? [-1, 1] : [1, -1]; return aValue.toBigInt() === bValue.toBigInt()