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 e12ce662bddf..4e6ed1a3c055 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr @@ -34,15 +34,20 @@ comptime fn generate_note_interface( ) -> (Quoted, u32) { let name = s.name(); let typ = s.as_type(); - let (fields, aux_vars) = flatten_to_fields(quote { self }, typ, &[quote {self.header}]); - let aux_vars_for_serialization = if aux_vars.len() > 0 { - let joint = aux_vars.join(quote {;}); + + // First we compute note content serialization. We do that by passing the whole note struct + // to the `flatten_to_fields(...)` and omitting the header. + let (content_fields_list, content_aux_vars_list) = flatten_to_fields(quote { self }, typ, &[quote {self.header}]); + + // 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 { + let joint = content_aux_vars_list.join(quote {;}); quote { $joint; } } else { quote {} }; - let serialized_fields = fields.join(quote {,}); - let content_len = fields.len(); + let content_fields = content_fields_list.join(quote {,}); + let content_len = content_fields_list.len(); let (deserialized_content, _) = pack_from_fields( quote { self }, @@ -52,11 +57,12 @@ comptime fn generate_note_interface( &[(quote {header}, quote { aztec::note::note_header::NoteHeader::empty() })] ); - // `compute_note_hash()` is computed over all the fields so we need to merge fixed and nullable fields. + // Second we compute quotes for MSM + // `compute_note_hash()` is computed over all the fields so we need to merge fixed and nullable. let merged_fields = indexed_fixed_fields.append(indexed_nullable_fields); // Now we prefix each of the merged fields with `self.` since they refer to the struct members here. - let merged_fields = merged_fields.map(| (name, typ, index): (Quoted, Type, u32) | (quote { self.$name }, typ, index)); - let (new_generators_list, new_scalars_list, _, new_aux_vars) = generate_multi_scalar_mul(merged_fields); + let prefixed_merged_fields = merged_fields.map(| (name, typ, index): (Quoted, Type, u32) | (quote { self.$name }, typ, index)); + let (new_generators_list, new_scalars_list, _, new_aux_vars) = generate_multi_scalar_mul(prefixed_merged_fields); let new_generators = new_generators_list.push_back(quote { aztec::generators::G_slot }).join(quote {,}); let new_scalars = new_scalars_list.push_back(quote { std::hash::from_field_unsafe(self.header.storage_slot) }).join(quote {,}); @@ -90,8 +96,8 @@ comptime fn generate_note_interface( } fn serialize_content(self) -> [Field; $content_len] { - $aux_vars_for_serialization - [$serialized_fields] + $content_aux_vars + [$content_fields] } fn get_note_type_id() -> Field { @@ -224,117 +230,76 @@ comptime fn generate_multi_scalar_mul(indexed_fields: [(Quoted, Type, u32)]) -> (generators_list, scalars_list, args_list, aux_vars) } -comptime fn generate_note_hiding_point( +comptime fn generate_setup_payload( s: StructDefinition, + indexed_fixed_fields: [(Quoted, Type, u32)], indexed_nullable_fields: [(Quoted, Type, u32)] ) -> (Quoted, Quoted) { let name = s.name(); - let hiding_point_name = f"{name}HidingPoint".quoted_contents(); + let setup_payload_name = f"{name}SetupPayload".quoted_contents(); - let (finalize_generators_list, finalize_scalars_list, finalize_args_list, finalize_aux_vars) = generate_multi_scalar_mul(indexed_nullable_fields); - - let finalize_args = if finalize_args_list.len() > 0 { - &[quote {self}].append(finalize_args_list).join(quote {,}) - } else { - quote {self} - }; + // First we get the MSM related quotes + let (new_generators_list, new_scalars_list, new_args_list, new_aux_vars) = generate_multi_scalar_mul(indexed_fixed_fields); + let new_args = &[quote {mut self}].append(new_args_list).push_back(quote { storage_slot: Field }).join(quote {,}); + let new_generators = new_generators_list.push_back(quote { aztec::generators::G_slot }).join(quote {,}); + let new_scalars = new_scalars_list.push_back(quote { std::hash::from_field_unsafe(storage_slot) }).join(quote {,}); - let finalize_body = if indexed_nullable_fields.len() > 0 { - let finalize_generators = finalize_generators_list.join(quote {,}); - let finalize_scalars = finalize_scalars_list.join(quote {,}); - quote { - $finalize_aux_vars - let point = std::embedded_curve_ops::multi_scalar_mul( - [$finalize_generators], - [$finalize_scalars] - ) + self.inner; - point.x - } - } else { - quote { self.inner.x } - }; + // Then the log plaintext ones + let log_plaintext_length = indexed_fixed_fields.len() * 32 + 64; + let setup_log_plaintext = get_setup_log_plaintext_body(s, log_plaintext_length, indexed_nullable_fields); (quote { - struct $hiding_point_name { - inner: aztec::protocol_types::point::Point - } - - impl $hiding_point_name { - fn from_point(mut self, point: aztec::protocol_types::point::Point) -> $hiding_point_name { - self.inner = point; - self - } - - - fn finalize($finalize_args) -> Field { - $finalize_body - } - - fn to_point(self) -> aztec::protocol_types::point::Point { - self.inner - } + struct $setup_payload_name { + log_plaintext: [u8; $log_plaintext_length], + hiding_point: aztec::protocol_types::point::Point } - impl aztec::protocol_types::traits::Serialize for $hiding_point_name { - fn serialize(self) -> [Field; aztec::protocol_types::point::POINT_LENGTH] { - self.inner.serialize() - } - } + impl $setup_payload_name { + fn new($new_args) -> $setup_payload_name { + $new_aux_vars + let hiding_point = std::embedded_curve_ops::multi_scalar_mul( + [$new_generators], + [$new_scalars] + ); + $setup_log_plaintext - impl aztec::protocol_types::traits::Deserialize for $hiding_point_name { - fn deserialize(serialized: [Field; aztec::protocol_types::point::POINT_LENGTH]) -> $hiding_point_name { - $hiding_point_name { inner: aztec::protocol_types::point::Point::deserialize(serialized) } + $setup_payload_name { + log_plaintext, + hiding_point + } } } - impl aztec::protocol_types::traits::Empty for $hiding_point_name { + impl aztec::protocol_types::traits::Empty for $setup_payload_name { fn empty() -> Self { - Self { inner: aztec::protocol_types::point::Point::empty() } - } - } - - impl Eq for $hiding_point_name { - fn eq(self, other: Self) -> bool { - self.inner == other.inner + Self { log_plaintext: [0; $log_plaintext_length], hiding_point: aztec::protocol_types::point::Point::empty() } } } - - }, hiding_point_name) + }, setup_payload_name) } -comptime fn generate_partial_payload( +comptime fn get_setup_log_plaintext_body( s: StructDefinition, - hiding_point_name: Quoted, - indexed_fixed_fields: [(Quoted, Type, u32)], + log_plaintext_length: u32, indexed_nullable_fields: [(Quoted, Type, u32)] -) -> (Quoted, Quoted) { +) -> Quoted { let name = s.name(); - let partial_payload_name = f"{name}PartialPayload".quoted_contents(); - - let (new_generators_list, new_scalars_list, new_args_list, new_aux_vars) = generate_multi_scalar_mul(indexed_fixed_fields); - - let new_args = &[quote {mut self}].append(new_args_list).push_back(quote { storage_slot: Field }).join(quote {,}); - let new_generators = new_generators_list.push_back(quote { aztec::generators::G_slot }).join(quote {,}); - let new_scalars = new_scalars_list.push_back(quote { std::hash::from_field_unsafe(storage_slot) }).join(quote {,}); - - let log_plaintext_length = indexed_fixed_fields.len() * 32 + 64; // Now we compute serialization of the fixed fields. We do that by passing the whole note struct // to the flatten_to_fields function but we omit the NoteHeader and the nullable fields. - let typ = s.as_type(); - let mut to_omit = indexed_nullable_fields.map(| (name, _, _): (Quoted, Type, u32) | name); - to_omit = to_omit.push_back(quote { header }); - let (fields, aux_vars) = flatten_to_fields(quote { }, typ, to_omit); + let to_omit = indexed_nullable_fields.map(| (name, _, _): (Quoted, Type, u32) | name).push_back(quote { header }); + let (fields_list, aux_vars) = flatten_to_fields(quote { }, s.as_type(), to_omit); + + // 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 { let joint = aux_vars.join(quote {;}); quote { $joint; } } else { quote {} }; - let serialized_fields = fields.join(quote {,}); + let fields = fields_list.join(quote {,}); - // indexed_fixed_fields has preserved order so we can used to serialize the note to log - let partial_note_log_plaintext = quote { + quote { let mut log_plaintext: [u8; $log_plaintext_length] = [0; $log_plaintext_length]; let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes(); @@ -346,7 +311,7 @@ comptime fn generate_partial_payload( } $aux_vars_for_serialization - let serialized_note = [$serialized_fields]; + let serialized_note = [$fields]; for i in 0..serialized_note.len() { let bytes: [u8; 32] = serialized_note[i].to_be_bytes(); @@ -354,53 +319,86 @@ comptime fn generate_partial_payload( log_plaintext[64 + i * 32 + j] = bytes[j]; } } + } +} + +comptime fn generate_finalization_payload( + s: StructDefinition, + indexed_fixed_fields: [(Quoted, Type, u32)], + indexed_nullable_fields: [(Quoted, Type, u32)] +) -> (Quoted, Quoted) { + let name = s.name(); + let finalization_payload_name = f"{name}FinalizationPayload".quoted_contents(); + + // We compute serialization of the nullable fields which are to be emitted as an unencrypted log. We do that by + // passing the whole note struct to the `flatten_to_fields(...)` function but we omit the `NoteHeader` and + // the fixed fields. + let to_omit = indexed_fixed_fields.map(| (name, _, _): (Quoted, Type, u32) | name).push_back(quote { header }); + let (fields_list, aux_vars) = flatten_to_fields(quote { }, s.as_type(), to_omit); + + // 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 { + let joint = aux_vars.join(quote {;}); + quote { $joint; } + } else { + quote {} }; + // We compute the log length and we concatenate the fields into a single quote. + let log_length = fields_list.len(); + let fields = fields_list.join(quote {,}); + + // Now we compute quotes relevant to the multi-scalar multiplication. + let (generators_list, scalars_list, args_list, msm_aux_vars) = generate_multi_scalar_mul(indexed_nullable_fields); + + let generators = generators_list.join(quote {,}); + let scalars = scalars_list.join(quote {,}); + let args = args_list.join(quote {,}); + (quote { - struct $partial_payload_name { - log_plaintext: [u8; $log_plaintext_length], - hiding_point: $hiding_point_name + struct $finalization_payload_name { + log: [Field; $log_length], + note_hash: Field, } - impl $partial_payload_name { - fn new($new_args) -> $partial_payload_name { - $new_aux_vars - let point = std::embedded_curve_ops::multi_scalar_mul( - [$new_generators], - [$new_scalars] - ); - let hiding_point = $hiding_point_name::empty().from_point(point); - $partial_note_log_plaintext + impl $finalization_payload_name { + fn new(mut self, hiding_point: aztec::protocol_types::point::Point, $args) -> $finalization_payload_name { + $aux_vars_for_serialization + self.log = [$fields]; - $partial_payload_name { - log_plaintext, - hiding_point - } + $msm_aux_vars + let finalization_hiding_point = std::embedded_curve_ops::multi_scalar_mul( + [$generators], + [$scalars] + ) + hiding_point; + + self.note_hash = finalization_hiding_point.x; + self } } - impl aztec::protocol_types::traits::Empty for $partial_payload_name { + impl aztec::protocol_types::traits::Empty for $finalization_payload_name { fn empty() -> Self { - Self { log_plaintext: [0; $log_plaintext_length], hiding_point: $hiding_point_name::empty() } + Self { log: [0; $log_length], note_hash: 0 } } } - }, partial_payload_name) + }, finalization_payload_name) } comptime fn generate_partial_note_impl( s: StructDefinition, - hiding_point_name: Quoted, - partial_payload_name: Quoted + setup_payload_name: Quoted, + finalization_payload_name: Quoted ) -> Quoted { let name = s.name(); quote { - impl aztec::note::note_interface::PartialNote<$hiding_point_name, $partial_payload_name> for $name { - fn hiding_point() -> $hiding_point_name { - $hiding_point_name::empty() + impl aztec::note::note_interface::PartialNote<$setup_payload_name, $finalization_payload_name> for $name { + fn setup_payload() -> $setup_payload_name { + $setup_payload_name::empty() } - fn partial_payload() -> $partial_payload_name { - $partial_payload_name::empty() + fn finalization_payload() -> $finalization_payload_name { + $finalization_payload_name::empty() } } } @@ -477,15 +475,10 @@ pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> let (indexed_fixed_fields, indexed_nullable_fields) = index_note_fields(s, nullable_fields); let (common, note_type_id) = common_note_annotation(s); - let (note_hiding_point, hiding_point_name) = generate_note_hiding_point(s, indexed_nullable_fields); - let (partial_payload_impl, partial_payload_name) = generate_partial_payload( - s, - hiding_point_name, - indexed_fixed_fields, - indexed_nullable_fields - ); + let (setup_payload_impl, setup_payload_name) = 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(s, note_type_id, indexed_fixed_fields, indexed_nullable_fields); - let partial_note_impl = generate_partial_note_impl(s, hiding_point_name, partial_payload_name); + let partial_note_impl = generate_partial_note_impl(s, setup_payload_name, finalization_payload_name); register_note( s, note_serialized_len, @@ -496,8 +489,8 @@ pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> quote { $common - $note_hiding_point - $partial_payload_impl + $setup_payload_impl + $finalization_payload_impl $note_interface_impl $partial_note_impl } 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 329547dba1fa..7b1cc26f020b 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr @@ -5,10 +5,10 @@ pub trait NoteProperties { fn properties() -> T; } -pub trait PartialNote { - fn hiding_point() -> T; +pub trait PartialNote { + fn setup_payload() -> S; - fn partial_payload() -> P; + fn finalization_payload() -> F; } pub trait NullifiableNote { diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index e6f5bba85832..6ec923ba8c2f 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -154,10 +154,10 @@ contract NFT { let note_randomness = unsafe { random() }; - let note_partial_payload = NFTNote::partial_payload().new(to_npk_m_hash, note_randomness, to_note_slot); + let note_setup_payload = NFTNote::setup_payload().new(to_npk_m_hash, note_randomness, to_note_slot); // We encrypt and emit the partial note log - encrypt_and_emit_partial_log(&mut context, note_partial_payload.log_plaintext, to_keys, to); + encrypt_and_emit_partial_log(&mut context, note_setup_payload.log_plaintext, to_keys, to); // We make the msg_sender/transfer_preparer part of the slot preimage to ensure he cannot interfere with // non-sender's slots @@ -172,7 +172,7 @@ contract NFT { TRANSIENT_STORAGE_SLOT_PEDERSEN_INDEX ); - NFT::at(context.this_address())._store_point_in_transient_storage(note_partial_payload.hiding_point.to_point(), slot).enqueue(&mut context); + NFT::at(context.this_address())._store_point_in_transient_storage(note_setup_payload.hiding_point, slot).enqueue(&mut context); } #[public] @@ -206,25 +206,24 @@ contract NFT { ); // Read the hiding point from "transient" storage and check it's not empty to ensure the transfer was prepared - let mut hiding_point = NFTNote::hiding_point().from_point(context.storage_read(hiding_point_slot)); + let hiding_point: Point = context.storage_read(hiding_point_slot); assert(!is_empty(hiding_point), "transfer not prepared"); // Set the public NFT owner to zero public_owners_storage.write(AztecAddress::zero()); - // Finalize the hiding point with the `token_id` and insert the note - let note_hash = hiding_point.finalize(token_id); - context.push_note_hash(note_hash); + // Finalize the partial note with the `token_id` + let finalization_payload = NFTNote::finalization_payload().new(hiding_point, token_id); + + // We insert the finalization note hash + context.push_note_hash(finalization_payload.note_hash); // We emit the `token_id` as unencrypted event such that the `NoteProcessor` can use it to reconstruct the note - context.emit_unencrypted_log(token_id); + context.emit_unencrypted_log(finalization_payload.log); // At last we reset public storage to zero to achieve the effect of transient storage - kernels will squash // the writes - context.storage_write( - hiding_point_slot, - NFTNote::hiding_point().from_point(Point::empty()) - ); + context.storage_write(hiding_point_slot, Point::empty()); } /** diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 2eec3f91c808..1525d3fce63f 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -495,13 +495,13 @@ contract Token { random() }; - let fee_payer_partial_payload = TokenNote::partial_payload().new( + let fee_payer_setup_payload = TokenNote::setup_payload().new( fee_payer_npk_m_hash, fee_payer_randomness, storage.balances.at(fee_payer).set.storage_slot ); - let user_partial_payload = TokenNote::partial_payload().new( + let user_setup_payload = TokenNote::setup_payload().new( user_npk_m_hash, user_randomness, storage.balances.at(user).set.storage_slot @@ -510,22 +510,17 @@ contract Token { // 5. We encrypt and emit the partial note log encrypt_and_emit_partial_log( &mut context, - fee_payer_partial_payload.log_plaintext, + fee_payer_setup_payload.log_plaintext, fee_payer_keys, fee_payer ); - encrypt_and_emit_partial_log( - &mut context, - user_partial_payload.log_plaintext, - user_keys, - user - ); + encrypt_and_emit_partial_log(&mut context, user_setup_payload.log_plaintext, user_keys, user); // 6. We convert the hiding points to standard `Point` type as we cannot pass `TokenNoteHidingPoint` type // as an argument to a function due to macro limitations (the `TokenNoteHidingPoint` type is macro generated // and hence is not resolved soon enough by the compiler). - let fee_payer_point = fee_payer_partial_payload.hiding_point.to_point(); - let user_point = user_partial_payload.hiding_point.to_point(); + let fee_payer_point = fee_payer_setup_payload.hiding_point; + let user_point = user_setup_payload.hiding_point; // 7. Set the public teardown function to `complete_refund(...)`. Public teardown is the only time when a public // function has access to the final transaction fee, which is needed to compute the actual refund amount. @@ -547,8 +542,6 @@ contract Token { #[public] #[internal] fn complete_refund(fee_payer_point: Point, user_point: Point, funded_amount: Field) { - let mut fee_payer_hiding_point = TokenNote::hiding_point().from_point(fee_payer_point); - let mut user_hiding_point = TokenNote::hiding_point().from_point(user_point); // TODO(#7728): Remove the next line let funded_amount = U128::from_integer(funded_amount); let tx_fee = U128::from_integer(context.transaction_fee()); @@ -560,21 +553,19 @@ contract Token { // 2. We compute the refund amount as the difference between funded amount and tx fee. let refund_amount = funded_amount - tx_fee; - // 3. We finalize the hiding points with the correct amounts to get the note hashes. - let fee_payer_note_hash = fee_payer_hiding_point.finalize(tx_fee); - let user_note_hash = user_hiding_point.finalize(refund_amount); + // 3. We construct the note finalization payloads with the correct amounts and hiding points to get the note + // hashes and unencrypted logs. + let fee_payer_finalization_payload = TokenNote::finalization_payload().new(fee_payer_point, tx_fee); + let user_finalization_payload = TokenNote::finalization_payload().new(user_point, refund_amount); // 4. We emit the `tx_fee` and `refund_amount` as unencrypted event such that the `NoteProcessor` can use it // to reconstruct the note. - // TODO(#7728): Nuke the manual serialization on the next 2 lines - let serialized_tx_fee = [tx_fee.lo, tx_fee.hi]; - let serialized_refund_amount = [refund_amount.lo, refund_amount.hi]; - context.emit_unencrypted_log(serialized_tx_fee); - context.emit_unencrypted_log(serialized_refund_amount); + context.emit_unencrypted_log(fee_payer_finalization_payload.log); + context.emit_unencrypted_log(user_finalization_payload.log); // 5. At last we emit the note hashes. - context.push_note_hash(fee_payer_note_hash); - context.push_note_hash(user_note_hash); + context.push_note_hash(fee_payer_finalization_payload.note_hash); + context.push_note_hash(user_finalization_payload.note_hash); // --> Once the tx is settled user and fee recipient can add the notes to their pixies. } // docs:end:complete_refund