diff --git a/docs/docs/aztec/concepts/advanced/storage/storage_slots.md b/docs/docs/aztec/concepts/advanced/storage/storage_slots.md index 2bfc1ef43b03..3b86951dea8f 100644 --- a/docs/docs/aztec/concepts/advanced/storage/storage_slots.md +++ b/docs/docs/aztec/concepts/advanced/storage/storage_slots.md @@ -32,10 +32,10 @@ Nevertheless, the concept of a storage slot is very useful when writing applicat If we include the storage slot, as part of the note whose commitment is stored in the note hashes tree, we can _logically link_ all the notes that make up the storage slot. For the case of a balance, we can say that the balance is the sum of all the notes that have the same storage slot - in the same way that your physical wallet balance is the sum of all the physical notes in your wallet. -Similarly to how we siloed the public storage slots, we can silo our private storage by hashing the logical storage slot together with the note content. +Similarly to how we siloed the public storage slots, we can silo our private storage by hashing the packed note together with the logical storage slot. ```rust -note_hash = H(logical_storage_slot, note_content_hash); +note_hash = H([...packed_note, logical_storage_slot]); ``` Note hash siloing is done in the application circuit, since it is not necessary for security of the network (but only the application). diff --git a/docs/docs/aztec/smart_contracts/functions/attributes.md b/docs/docs/aztec/smart_contracts/functions/attributes.md index 1de3b944a4ea..62d4a6334442 100644 --- a/docs/docs/aztec/smart_contracts/functions/attributes.md +++ b/docs/docs/aztec/smart_contracts/functions/attributes.md @@ -218,67 +218,62 @@ struct CustomNote { ### After expansion ```rust -impl CustomNote { - fn pack_content(self: CustomNote) -> [Field; PACKED_NOTE_CONTENT_LEN] { - [self.data, self.owner.to_field()] - } - - fn unpack_content(packed_content: [Field; PACKED_NOTE_CONTENT_LEN]) -> Self { - CustomNote { - data: packed_content[0] as Field, - owner: Address::from_field(packed_content[1]), - header: NoteHeader::empty() - } - } - +impl NoteInterface for CustomNote { fn get_note_type_id() -> Field { // Assigned by macros by incrementing a counter 2 } - fn get_header(note: CustomNote) -> aztec::note::note_header::NoteHeader { - note.header + fn compute_note_hash(self, storage_slot: Field) -> Field { + let inputs = array_concat(self.pack(), [storage_slot]); + poseidon2_hash_with_separator(inputs, GENERATOR_INDEX__NOTE_HASH) } +} - fn set_header(self: &mut CustomNote, header: aztec::note::note_header::NoteHeader) { - self.header = header; +impl NullifiableNote for CustomNote { + + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { + let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash(); + let secret = context.request_nsk_app(owner_npk_m_hash); + poseidon2_hash_with_separator( + [ + note_hash_for_nullify, + secret + ], + GENERATOR_INDEX__NOTE_NULLIFIER as Field + ) } - fn compute_note_hiding_point(self: CustomNote) -> Point { - aztec::hash::pedersen_commitment( - self.serialize_content(), - aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_HIDING_POINT + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field { + // We set the note_hash_counter to 0 as the note is not transient and the concept of transient note does + // not make sense in an unconstrained context. + let retrieved_note = RetrievedNote { note: self, contract_address, nonce: note_nonce, note_hash_counter: 0 }; + let note_hash_for_nullify = compute_note_hash_for_nullify(retrieved_note, storage_slot); + let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash(); + let secret = get_nsk_app(owner_npk_m_hash); + poseidon2_hash_with_separator( + [ + note_hash_for_nullify, + secret + ], + GENERATOR_INDEX__NOTE_NULLIFIER as Field ) } +} - fn to_be_bytes(self, storage_slot: Field) -> [u8; 128] { - assert(128 == 2 * 32 + 64, "Note byte length must be equal to (serialized_length * 32) + 64 bytes"); - let serialized_note = self.serialize_content(); - - let mut buffer: [u8; 128] = [0; 128]; - - let storage_slot_bytes = storage_slot.to_be_bytes(32); - let note_type_id_bytes = CustomNote::get_note_type_id().to_be_bytes(32); - - for i in 0..32 { - buffer[i] = storage_slot_bytes[i]; - buffer[32 + i] = note_type_id_bytes[i]; - } +impl CustomNote { + pub fn new(x: [u8; 32], y: [u8; 32], owner: AztecAddress) -> Self { + CustomNote { x, y, owner } + } +} - for i in 0..serialized_note.len() { - let bytes = serialized_note[i].to_be_bytes(32); - for j in 0..32 { - buffer[64 + i * 32 + j] = bytes[j]; - } - } - buffer - } +impl Packable<2> for CustomNote { + fn pack(self) -> [Field; 2] { + [self.data, self.owner.to_field()] + } - pub fn properties() -> CustomNoteProperties { - CustomNoteProperties { - data: aztec::note::note_getter_options::PropertySelector { index: 0, offset: 0, length: 32 }, - owner: aztec::note::note_getter_options::PropertySelector { index: 1, offset: 0, length: 32 } - } + fn unpack(packed_content: [Field; 2]) -> CustomNote { + CustomNote { data: packed_content[0], owner: AztecAddress { inner: packed_content[1] } } } } diff --git a/docs/docs/aztec/smart_contracts/functions/function_transforms.md b/docs/docs/aztec/smart_contracts/functions/function_transforms.md index 67a22b36403b..cc82dcee1733 100644 --- a/docs/docs/aztec/smart_contracts/functions/function_transforms.md +++ b/docs/docs/aztec/smart_contracts/functions/function_transforms.md @@ -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::unpack_content, + NoteType::unpack, note_header, compute_nullifier, - packed_note_content + packed_note ) } ``` diff --git a/docs/docs/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md b/docs/docs/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md index 5c35a86c5a4e..cd0c387db20b 100644 --- a/docs/docs/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md +++ b/docs/docs/developers/guides/smart_contracts/writing_contracts/common_patterns/index.md @@ -81,7 +81,7 @@ See [partial notes](../../../../../aztec/concepts/advanced/storage/partial_notes When you send someone a note, the note hash gets added to the note hash tree. To spend the note, the receiver needs to get the note itself (the note hash preimage). There are two ways you can get a hold of your notes: -1. When sending someone a note, emit the note contents to the recipient (the function encrypts the log in such a way that only a recipient can decrypt it). PXE then tries to decrypt all the encrypted logs, and stores the successfully decrypted one. [More info here](../how_to_emit_event.md) +1. When sending someone a note, emit the note log to the recipient (the function encrypts the log in such a way that only a recipient can decrypt it). PXE then tries to decrypt all the encrypted logs, and stores the successfully decrypted one. [More info here](../how_to_emit_event.md) 2. Manually using `pxe.addNote()` - If you choose to not emit logs to save gas or when creating a note in the public domain and want to consume it in private domain (`encrypt_and_emit_note` shouldn't be called in the public domain because everything is public), like in the previous section where we created a note in public that doesn't have a designated owner. #include_code pxe_add_note yarn-project/end-to-end/src/composed/e2e_persistence.test.ts typescript diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index 20ef987cee3e..d054fff98109 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -92,7 +92,7 @@ The new check an indexed tree allows is non-membership of addresses of non proto ``` ### [Aztec.nr] Changes to `NoteInterface` -We removed `NoteHeader` from notes and we've introduced `RetrievedNote` struct. +We removed `NoteHeader` from notes, we've introduced a `RetrievedNote` struct and instead of the `pack_content` and `unpack_content` functions we make notes implement the standard `Packable` trait. This led us to do the following changes to `NoteInterface`: ```diff @@ -102,11 +102,12 @@ pub trait NullifiableNote { + unconstrained fn fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field; } -pub trait NoteInterface { +-pub trait NoteInterface { ++pub trait NoteInterface { +- fn pack_content(self) -> [Field; N]; +- fn unpack_content(fields: [Field; N]) -> Self; - fn get_header(self) -> NoteHeader; - - fn set_header(&mut self, header: NoteHeader) -> (); - - fn compute_note_hash(self) -> Field; + fn compute_note_hash(self, storage_slot: Field) -> Field; } @@ -121,13 +122,15 @@ These are the changes that needed to be done to our `EcdsaPublicKeyNote`: -use dep::aztec::prelude::{NoteHeader}; +use dep::aztec::prelude::{RetrievedNote}; -impl NoteInterface for EcdsaPublicKeyNote { -... - fn unpack_content(packed_content: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote { - ... -- EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(packed_content[4]), header: NoteHeader::empty() } -+ EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(packed_content[4]) } - } +- impl NoteInterface for EcdsaPublicKeyNote { ++ impl NoteInterface for EcdsaPublicKeyNote { +- fn pack_content(self) -> [Field; ECDSA_PUBLIC_KEY_NOTE_LEN] { +- ... +- } + +- fn unpack_content(packed_content: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote { +- ... +- } - fn get_header(self) -> NoteHeader { - self.header @@ -145,6 +148,25 @@ impl NoteInterface for EcdsaPublicKeyNote { } } +If you need to keep the custom implementation of the packing functionality, manually implement the `Packable` trait: + +```diff ++ use dep::aztec::protocol_types::traits::Packable; + ++impl Packable for YourNote { ++ fn pack(self) -> [Field; N] { ++ ... ++ } ++ ++ fn unpack(fields: [Field; N]) -> Self { ++ ... ++ } ++} +``` + +If you don't provide a custom implementation of the `Packable` trait, a default one will be generated. + +```diff impl NullifiableNote for EcdsaPublicKeyNote { ... - unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { diff --git a/noir-projects/aztec-nr/address-note/src/address_note.nr b/noir-projects/aztec-nr/address-note/src/address_note.nr index 27107f48bfb0..9cf95850d662 100644 --- a/noir-projects/aztec-nr/address-note/src/address_note.nr +++ b/noir-projects/aztec-nr/address-note/src/address_note.nr @@ -8,18 +8,19 @@ use dep::aztec::{ }, oracle::random::random, protocol_types::{ - address::AztecAddress, - constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, - traits::{Packable, Serialize}, + address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, + hash::poseidon2_hash_with_separator, traits::Serialize, }, }; +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + // docs:start:address_note_def // docs:start:address_note_struct // Stores an address #[note] -#[derive(Serialize)] +#[derive(Eq, Serialize)] pub struct AddressNote { address: AztecAddress, owner: AztecAddress, @@ -71,11 +72,3 @@ impl AddressNote { } // docs:end:address_note_def } - -impl Eq for AddressNote { - fn eq(self, other: Self) -> bool { - (self.address == other.address) - & (self.owner == other.owner) - & (self.randomness == other.randomness) - } -} 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 2e8b8f9af301..ad150a36e6bf 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 @@ -5,9 +5,7 @@ use crate::{ ecdh_shared_secret::derive_ecdh_shared_secret_using_aztec_address, ephemeral::generate_ephemeral_key_pair, }, - note::{ - note_emission::NoteEmission, note_interface::NoteInterface, retrieved_note::RetrievedNote, - }, + note::{note_emission::NoteEmission, note_interface::NoteInterface}, oracle::{ notes::{get_app_tag_as_sender, increment_app_tagging_secret_index_as_sender}, random::random, @@ -16,6 +14,7 @@ use crate::{ }; use dep::protocol_types::{ abis::note_hash::NoteHash, address::AztecAddress, constants::PRIVATE_LOG_SIZE_IN_FIELDS, + traits::Packable, }; use std::aes128::aes128_encrypt; @@ -219,9 +218,9 @@ fn compute_note_plaintext_for_this_strategy( storage_slot: Field, ) -> [u8; N * 32 + 64] where - Note: NoteInterface, + Note: NoteInterface + Packable, { - let packed_note = note.pack_content(); + let packed_note = note.pack(); let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes(); @@ -253,7 +252,7 @@ fn compute_log( sender: AztecAddress, ) -> [Field; PRIVATE_LOG_SIZE_IN_FIELDS] where - Note: NoteInterface, + Note: NoteInterface + Packable, { // ***************************************************************************** // Compute the shared secret @@ -417,7 +416,7 @@ unconstrained fn compute_log_unconstrained( sender: AztecAddress, ) -> [Field; PRIVATE_LOG_SIZE_IN_FIELDS] where - Note: NoteInterface, + Note: NoteInterface + Packable, { compute_log(context, note, storage_slot, recipient, sender) } @@ -432,7 +431,7 @@ pub fn encode_and_encrypt_note( sender: AztecAddress, ) -> fn[(&mut PrivateContext, AztecAddress, AztecAddress)](NoteEmission) -> () where - Note: NoteInterface, + Note: NoteInterface + Packable, { |e: NoteEmission| { let note = e.note; @@ -455,7 +454,7 @@ pub fn encode_and_encrypt_note_unconstrained( sender: AztecAddress, ) -> fn[(&mut PrivateContext, AztecAddress, AztecAddress)](NoteEmission) -> () where - Note: NoteInterface, + Note: NoteInterface + Packable, { |e: NoteEmission| { let note = e.note; @@ -464,7 +463,6 @@ where assert_note_exists(*context, note_hash_counter); - // Unconstrained logs have both their content and encryption unconstrained - it could occur that the // Unconstrained logs have both their content and encryption unconstrained - it could occur that the // recipient is unable to decrypt the payload. // Regarding the note hash counter, this is used for squashing. The kernel assumes that a given note can have diff --git a/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr index 5be54699bffd..638257716598 100644 --- a/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr @@ -11,23 +11,19 @@ use crate::{ }; trait ProveNoteInclusion { - fn prove_note_inclusion( + fn prove_note_inclusion( header: BlockHeader, retrieved_note: RetrievedNote, storage_slot: Field, ) where - Note: NoteInterface + NullifiableNote; + Note: NoteInterface + NullifiableNote; } impl ProveNoteInclusion for BlockHeader { - fn prove_note_inclusion( - self, - retrieved_note: RetrievedNote, - storage_slot: Field, - ) + fn prove_note_inclusion(self, retrieved_note: RetrievedNote, storage_slot: Field) where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { let note_hash = compute_note_hash_for_nullify(retrieved_note, storage_slot); diff --git a/noir-projects/aztec-nr/aztec/src/history/note_validity.nr b/noir-projects/aztec-nr/aztec/src/history/note_validity.nr index a8d738ff341a..2d58d06ee5fe 100644 --- a/noir-projects/aztec-nr/aztec/src/history/note_validity.nr +++ b/noir-projects/aztec-nr/aztec/src/history/note_validity.nr @@ -6,25 +6,25 @@ use crate::{ use dep::protocol_types::block_header::BlockHeader; trait ProveNoteValidity { - fn prove_note_validity( + fn prove_note_validity( header: BlockHeader, retrieved_note: RetrievedNote, storage_slot: Field, context: &mut PrivateContext, ) where - Note: NoteInterface + NullifiableNote; + Note: NoteInterface + NullifiableNote; } impl ProveNoteValidity for BlockHeader { - fn prove_note_validity( + fn prove_note_validity( self, retrieved_note: RetrievedNote, storage_slot: Field, context: &mut PrivateContext, ) where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { self.prove_note_inclusion(retrieved_note, storage_slot); self.prove_note_not_nullified(retrieved_note, storage_slot, context); diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr index 3d47e2d5094e..0cb4bd36d129 100644 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr @@ -43,26 +43,26 @@ impl ProveNullifierInclusion for BlockHeader { } trait ProveNoteIsNullified { - fn prove_note_is_nullified( + fn prove_note_is_nullified( header: BlockHeader, retrieved_note: RetrievedNote, storage_slot: Field, context: &mut PrivateContext, ) where - Note: NoteInterface + NullifiableNote; + Note: NoteInterface + NullifiableNote; } impl ProveNoteIsNullified for BlockHeader { // docs:start:prove_note_is_nullified - fn prove_note_is_nullified( + fn prove_note_is_nullified( self, retrieved_note: RetrievedNote, storage_slot: Field, context: &mut PrivateContext, ) where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { let nullifier = compute_siloed_nullifier(retrieved_note, storage_slot, context); diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr index acc417a2c681..755f2ee63533 100644 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr @@ -56,26 +56,26 @@ impl ProveNullifierNonInclusion for BlockHeader { } trait ProveNoteNotNullified { - fn prove_note_not_nullified( + fn prove_note_not_nullified( header: BlockHeader, retrieved_note: RetrievedNote, storage_slot: Field, context: &mut PrivateContext, ) where - Note: NoteInterface + NullifiableNote; + Note: NoteInterface + NullifiableNote; } impl ProveNoteNotNullified for BlockHeader { // docs:start:prove_note_not_nullified - fn prove_note_not_nullified( + fn prove_note_not_nullified( self, retrieved_note: RetrievedNote, storage_slot: Field, context: &mut PrivateContext, ) where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { let nullifier = compute_siloed_nullifier(retrieved_note, storage_slot, context); diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr index 3511953e2641..0f52cd3e661c 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_content_length: u32 = 0; + let mut max_note_packed_len: u32 = 0; let notes = NOTES.entries(); let body = if notes.len() > 0 { - max_note_content_length = notes.fold( + max_note_packed_len = 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::unpack_content, contract_address, nonce, compute_nullifier, storage_slot, packed_note_content) + aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack, contract_address, nonce, compute_nullifier, storage_slot, packed_note) } }, ); @@ -165,7 +165,7 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted { storage_slot: Field, note_type_id: Field, compute_nullifier: bool, - packed_note_content: [Field; $max_note_content_length], + packed_note: [Field; $max_note_packed_len], ) -> pub [Field; 4] { $body } @@ -173,26 +173,26 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted { } comptime fn generate_process_log() -> Quoted { - // This mandatory function processes a log emitted by the contract. This is currently used to recover note contents - // and deliver the note to PXE. + // This mandatory function processes a log emitted by the contract. This is currently used to recover a note and + // deliver it to PXE. // The bulk of the work of this function is done by aztec::note::discovery::do_process_log, so all we need to do // is call that function. However, one of its parameters is a lambda function that computes note hash and nullifier - // given note contents and metadata (e.g. note type id), since this behavior is contract-specific (as it + // given packed note and metadata (e.g. note type id), since this behavior is contract-specific (as it // depends on the note types implemented by each contract). // The job of this macro is therefore to implement this lambda function and then call `do_process_log` with it. // A typical implementation of the lambda looks something like this: // ``` - // |packed_note_content: BoundedVec, contract_address: AztecAddress, nonce: Field, storage_slot: Field, note_type_id: Field| { + // |packed_note: BoundedVec, contract_address: AztecAddress, nonce: Field, storage_slot: Field, note_type_id: Field| { // let hashes = if note_type_id == MyNoteType::get_note_type_id() { - // assert(packed_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH); + // assert(packed_note.len() == MY_NOTE_TYPE_PACKED_LENGTH); // dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier( - // MyNoteType::unpack_content, + // MyNoteType::unpack, // contract_address, // nonce, // true, // storage_slot, - // packed_note_content.storage(), + // packed_note.storage(), // ) // } else { // panic(f"Unknown note type id {note_type_id}") @@ -207,14 +207,14 @@ comptime fn generate_process_log() -> Quoted { // ``` // // We create this implementation by iterating over the different note types, creating an `if` or `else if` clause - // for each of them and calling `compute_note_hash_and_optionally_a_nullifier` with the note's deserialization - // function, and finally produce the required `NoteHashesAndNullifier` object. + // for each of them and calling `compute_note_hash_and_optionally_a_nullifier` with the note's `unpack` function, + // and finally produce the required `NoteHashesAndNullifier` object. let notes = NOTES.entries(); let mut if_note_type_id_match_statements_list = &[]; for i in 0..notes.len() { - let (typ, (_, packed_note_content_length, _, _)) = notes[i]; + let (typ, (_, packed_note_length, _, _)) = notes[i]; let if_or_else_if = if i == 0 { quote { if } @@ -225,17 +225,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 packed_note_content bounded vec has the + // As an extra safety check we make sure that the packed_note 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 = $packed_note_content_length; - let actual_len = packed_note_content.len(); + let expected_len = $packed_note_length; + let actual_len = packed_note.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}" + f"Expected packed note 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::unpack_content, contract_address, nonce, true, storage_slot, packed_note_content.storage()) + aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack, contract_address, nonce, true, storage_slot, packed_note.storage()) } }, ); @@ -257,7 +257,7 @@ comptime fn generate_process_log() -> Quoted { unique_note_hashes_in_tx, first_nullifier_in_tx, recipient, - |packed_note_content: BoundedVec, contract_address, nonce, storage_slot, note_type_id| { + |packed_note: BoundedVec, contract_address, nonce, storage_slot, note_type_id| { let hashes = $if_note_type_id_match_statements else { panic(f"Unknown note type id {note_type_id}") @@ -294,7 +294,7 @@ comptime fn generate_process_log() -> Quoted { comptime fn generate_note_exports() -> Quoted { let notes = NOTES.values(); - // Second value in each tuple is `note_serialized_len` and that is ignored here because it's only used when + // Second value in each tuple is `note_packed_len` and that is ignored here because it's only used when // generating the `compute_note_hash_and_optionally_a_nullifier` function. notes .map(|(s, _, note_type_id, fields): (StructDefinition, u32, Field, [(Quoted, u32, bool)])| { 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 7098dc3c3002..cfb3821aa423 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr @@ -1,9 +1,9 @@ use crate::{note::note_getter_options::PropertySelector, prelude::Point}; -use protocol_types::meta::{generate_deserialize_from_fields, generate_serialize_to_fields}; +use protocol_types::meta::{derive_packable_and_get_packed_len, generate_serialize_to_fields}; use std::{ collections::umap::UHashMap, hash::{BuildHasherDefault, derive_generators, poseidon2::Poseidon2Hasher}, - meta::{typ::fresh_type_variable, type_of, unquote}, + meta::{type_of, unquote}, }; /// A map from note type to (note_struct_definition, note_packed_len, note_type_id, fields). @@ -28,18 +28,59 @@ comptime fn get_next_note_type_id() -> Field { note_type_id } -/// Generates default `NoteInterface` implementation for a given note struct `s` and returns it as quote along with -/// the length of the packed note. +/// Generates a quote that implements `Packable` for a given struct `s`. +/// If the note struct already implements `Packable`, we return an empty quote. +comptime fn derive_packable_if_not_implemented_and_get_len(s: StructDefinition) -> (Quoted, u32) { + // We try to get the packed length of the note struct. If it does not implement `Packable`, we get Option::none() + let packed_len_typ = std::meta::typ::fresh_type_variable(); + // We don't care about the result of the implements check. We just want the get the packed length. + let _ = s.as_type().implements( + quote { crate::protocol_types::traits::Packable<$packed_len_typ> }.as_trait_constraint(), + ); + let maybe_packed_length = packed_len_typ.as_constant(); + + if maybe_packed_length.is_some() { + // We got some packed length meaning that the note struct implements `Packable`. For this reason we return + // an empty quote for the implementation and the packed length. + (quote {}, maybe_packed_length.unwrap()) + } else { + // We didn't manage to get the packed length which means the note struct doesn't implement `Packable` + // so we derive it and return it along with the packed length. + derive_packable_and_get_packed_len(s) + } +} + +/// Generates default `NoteInterface` implementation for a given note struct `s` and returns it as a quote. /// -/// impl NoteInterface for NoteStruct { -/// fn pack_content(self) -> [Field; N] { +/// impl NoteInterface for NoteStruct { +/// fn get_note_type_id() -> Field { /// ... /// } /// -/// fn unpack_content(packed_content: [Field; N]) -> Self { +/// fn compute_note_hash(self, storage_slot: Field) -> Field { /// ... /// } +/// } +comptime fn generate_note_interface(s: StructDefinition, note_type_id: Field) -> Quoted { + let name = s.name(); + + quote { + impl aztec::note::note_interface::NoteInterface for $name { + fn get_note_type_id() -> Field { + $note_type_id + } + + fn compute_note_hash(self, storage_slot: Field) -> Field { + let inputs = aztec::protocol_types::utils::arrays::array_concat(self.pack(), [storage_slot]); + aztec::protocol_types::hash::poseidon2_hash_with_separator(inputs, aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_HASH) + } + } + } +} + +/// Generates default `NoteInterface` implementation for a given partial note struct `s` and returns it as a quote. /// +/// impl NoteInterface for NoteStruct { /// fn get_note_type_id() -> Field { /// ... /// } @@ -49,6 +90,12 @@ comptime fn get_next_note_type_id() -> Field { /// } /// } /// +/// # On differences from `generate_note_interface` +/// We use multi-scalar multiplication (MSM) instead of Poseidon2 here since this is a partial note and therefore +/// does require MSM's additive homomorphism property (the property is used to add to the commitment in public). +/// We don't use this implementation for standard notes as well because Poseidon2 is significantly cheaper +/// constraints-wise. +/// /// # On including length in note hash preimage /// For a given commitment C = a*G1 + b*G2 + c*G3 we take an x-coordinate of C.x and use it as the hash. /// However, due to elliptic curve symmetry about the x-axis, for any x-coordinate, @@ -61,42 +108,15 @@ comptime fn get_next_note_type_id() -> Field { /// /// Since -l would be -3 (an extraordinarily large number that cannot be a valid preimage length), /// including the length protects against these collisions. -comptime fn generate_note_interface( +comptime fn generate_note_interface_for_partial_note( s: StructDefinition, note_type_id: Field, indexed_fixed_fields: [(Quoted, Type, u32)], indexed_nullable_fields: [(Quoted, Type, u32)], -) -> (Quoted, u32) { +) -> Quoted { 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(...)`. - let (content_fields_list, content_aux_vars_list) = - generate_serialize_to_fields(quote { self }, typ, &[], 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 { - let joint = content_aux_vars_list.join(quote {;}); - quote { $joint; } - } else { - quote {} - }; - let content_fields = content_fields_list.join(quote {,}); - let content_len = content_fields_list.len(); - - let (unpacked_content, _) = generate_deserialize_from_fields( - quote {}, - typ, - quote { packed_content }, // "packed_content" is argument of NoteInterface::unpack_content - 0, - packing_enabled, - ); - - // Second we compute quotes for MSM + // First 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. @@ -115,18 +135,8 @@ comptime fn generate_note_interface( .push_back(quote { std::hash::from_field_unsafe($merged_fields_len) }) .join(quote {,}); - ( - quote { - impl aztec::note::note_interface::NoteInterface<$content_len> for $name { - fn pack_content(self) -> [Field; $content_len] { - $content_aux_vars - [$content_fields] - } - - fn unpack_content(packed_content: [Field; $content_len]) -> Self { - $unpacked_content - } - + quote { + impl aztec::note::note_interface::NoteInterface for $name { fn get_note_type_id() -> Field { $note_type_id } @@ -140,9 +150,7 @@ comptime fn generate_note_interface( point.x } } - }, - content_len, - ) + } } /// Generates note properties struct for a given note struct `s`. @@ -405,10 +413,10 @@ comptime fn generate_multi_scalar_mul( /// log_plaintext[32 + i] = note_type_id_bytes[i]; /// } /// -/// let packed_note_content = [npk_m_hash as Field, randomness as Field]; +/// let packed_note = [npk_m_hash as Field, randomness as Field]; /// -/// for i in 0..packed_note_content.len() { -/// let bytes: [u8; 32] = packed_note_content[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 { /// log_plaintext[64 + i * 32 + j] = bytes[j]; /// } @@ -561,10 +569,10 @@ comptime fn get_setup_log_plaintext_body( } $aux_vars_for_serialization - let packed_note_content = [$fields]; + let packed_note = [$fields]; - for i in 0..packed_note_content.len() { - let bytes: [u8; 32] = packed_note_content[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 { log_plaintext[64 + i * 32 + j] = bytes[j]; } @@ -896,7 +904,9 @@ comptime fn index_note_fields( /// - PartialNote trait implementation /// - NoteExport /// - NoteInterface trait implementation -/// - Registers the note in the global `NOTES` map. +/// - Packable implementation +/// +/// Registers the note in the global `NOTES` map. /// /// For more details on the generated code, see the individual functions. /// @@ -914,7 +924,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_packed_len) = generate_note_interface( + let note_interface_impl = generate_note_interface_for_partial_note( s, note_type_id, indexed_fixed_fields, @@ -922,6 +932,8 @@ pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> ); let partial_note_impl = generate_partial_note_impl(s, setup_payload_name, finalization_payload_name); + let (packable_impl, note_packed_len) = derive_packable_if_not_implemented_and_get_len(s); + register_note( s, note_packed_len, @@ -936,13 +948,16 @@ pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> $finalization_payload_impl $note_interface_impl $partial_note_impl + $packable_impl } } /// Generates the following: /// - NoteTypeProperties /// - NoteInterface trait implementation -/// - Registers the note in the global `NOTES` map. +/// - Packable implementation +/// +/// Registers the note in the global `NOTES` map. /// /// For more details on the generated code, see the individual functions. pub comptime fn note(s: StructDefinition) -> Quoted { @@ -950,12 +965,9 @@ 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_packed_len) = generate_note_interface( - s, - note_type_id, - indexed_fixed_fields, - indexed_nullable_fields, - ); + let note_interface_impl = generate_note_interface(s, note_type_id); + let (packable_impl, note_packed_len) = derive_packable_if_not_implemented_and_get_len(s); + register_note( s, note_packed_len, @@ -967,28 +979,21 @@ pub comptime fn note(s: StructDefinition) -> Quoted { quote { $note_properties $note_interface_impl + $packable_impl } } /// Generates the following: /// - NoteTypeProperties +/// - Packable implementation +/// +/// Registers the note in the global `NOTES` map. /// /// For more details on the generated code, see the individual functions. pub comptime fn note_custom_interface(s: StructDefinition) -> Quoted { - let note_properties = generate_note_properties(s); + let (packable_impl, note_packed_len) = derive_packable_if_not_implemented_and_get_len(s); + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/12012): This is broken let note_type_id = get_next_note_type_id(); - let serialized_len_type = fresh_type_variable(); - let note_interface_impl = s.as_type().get_trait_impl( - quote { crate::note::note_interface::NoteInterface<$serialized_len_type> } - .as_trait_constraint(), - ); - let name = s.name(); - - let note_packed_len = note_interface_impl - .expect(f"Note {name} must implement NoteInterface trait") - .trait_generic_args()[0] - .as_constant() - .unwrap(); let (indexed_fixed_fields, indexed_nullable_fields) = index_note_fields(s, &[]); register_note( @@ -999,7 +1004,10 @@ pub comptime fn note_custom_interface(s: StructDefinition) -> Quoted { indexed_nullable_fields, ); + let note_properties = generate_note_properties(s); + quote { $note_properties + $packable_impl } } 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 21c5574d2a38..1416fc82324f 100644 --- a/noir-projects/aztec-nr/aztec/src/note/discovery/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/note/discovery/mod.nr @@ -11,10 +11,10 @@ use dep::protocol_types::{ hash::compute_note_hash_nonce, }; -// We reserve two fields in the note log that are not part of the note content: one for the storage slot, and one for +// We reserve two fields in the note log that are not part of the packed note: one for the storage slot, and one for // the note type id. global NOTE_LOG_RESERVED_FIELDS: u32 = 2; -pub global MAX_NOTE_SERIALIZED_LEN: u32 = PRIVATE_LOG_SIZE_IN_FIELDS - NOTE_LOG_RESERVED_FIELDS; +pub global MAX_NOTE_PACKED_LEN: u32 = PRIVATE_LOG_SIZE_IN_FIELDS - NOTE_LOG_RESERVED_FIELDS; pub struct NoteHashesAndNullifier { pub note_hash: Field, @@ -27,18 +27,18 @@ pub struct NoteHashesAndNullifier { /// created, along with the list of unique note hashes in said transaction. /// /// Additionally, this requires a `compute_note_hash_and_nullifier` lambda that is able to compute these values for any -/// note in the contract given their contents. A typical implementation of such a function would look like this: +/// note type in the contract given the packed note. A typical implementation of such a function would look like this: /// /// ``` -/// |packed_note_content, note_header, note_type_id| { +/// |packed_note, note_header, note_type_id| { /// let hashes = if note_type_id == MyNoteType::get_note_type_id() { -/// assert(packed_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH); +/// assert(packed_note.len() == MY_NOTE_TYPE_PACKED_LENGTH); /// dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier( -/// MyNoteType::unpack_content, +/// MyNoteType::unpack, /// contract_address, /// nonce, /// storage_slot, -/// packed_note_content.storage(), +/// packed_note.storage(), /// ) /// } else { /// panic(f"Unknown note type id {note_type_id}") @@ -58,10 +58,9 @@ pub unconstrained fn do_process_log( unique_note_hashes_in_tx: BoundedVec, first_nullifier_in_tx: Field, recipient: AztecAddress, - compute_note_hash_and_nullifier: fn[Env](BoundedVec, AztecAddress, Field, Field, Field) -> Option, + compute_note_hash_and_nullifier: fn[Env](BoundedVec, AztecAddress, Field, Field, Field) -> Option, ) { - let (storage_slot, note_type_id, packed_note_content) = - destructure_log_plaintext(log_plaintext); + let (storage_slot, note_type_id, packed_note) = 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 for_each_in_bounded_vec( @@ -71,7 +70,7 @@ pub unconstrained fn do_process_log( // TODO(#11157): handle failed note_hash_and_nullifier computation let hashes = compute_note_hash_and_nullifier( - packed_note_content, + packed_note, context.this_address(), candidate_nonce, storage_slot, @@ -87,7 +86,7 @@ pub unconstrained fn do_process_log( context.this_address(), // TODO(#10727): allow other contracts to deliver notes storage_slot, candidate_nonce, - packed_note_content, + packed_note, hashes.note_hash, hashes.inner_nullifier, tx_hash, @@ -96,9 +95,9 @@ pub unconstrained fn do_process_log( "Failed to deliver note", ); - // We don't exit the loop - it is possible (though rare) for the exact same note content to be present + // We don't exit the loop - it is possible (though rare) for the exact same packed note to be present // multiple times in the same transaction with different nonces. This typically doesn't happen due to - // notes containing random values in order to hide their contents. + // notes containing random values in order to protect against note hash preimage attacks. } }, ); @@ -106,21 +105,21 @@ pub unconstrained fn do_process_log( unconstrained fn destructure_log_plaintext( log_plaintext: BoundedVec, -) -> (Field, Field, BoundedVec) { +) -> (Field, Field, BoundedVec) { assert(log_plaintext.len() >= NOTE_LOG_RESERVED_FIELDS); // If NOTE_LOG_RESERVED_FIELDS is changed, causing the assertion below to fail, then the declarations for // `storage_slot` and `note_type_id` must be updated as well. static_assert( NOTE_LOG_RESERVED_FIELDS == 2, - "unepxected value for NOTE_LOG_RESERVED_FIELDS", + "unexpected value for NOTE_LOG_RESERVED_FIELDS", ); let storage_slot = log_plaintext.get(0); let note_type_id = log_plaintext.get(1); - let packed_note_content = array::subbvec(log_plaintext, NOTE_LOG_RESERVED_FIELDS); + let packed_note = array::subbvec(log_plaintext, NOTE_LOG_RESERVED_FIELDS); - (storage_slot, note_type_id, packed_note_content) + (storage_slot, note_type_id, packed_note) } 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 e17ea277aafd..b6db493b8642 100644 --- a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr +++ b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr @@ -6,6 +6,7 @@ use crate::note::{ utils::{compute_note_hash_for_nullify_internal, compute_note_hash_for_read_request}, }; use crate::oracle::notes::notify_created_note; +use protocol_types::traits::Packable; pub fn create_note( context: &mut PrivateContext, @@ -13,17 +14,17 @@ pub fn create_note( note: Note, ) -> NoteEmission where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { let note_hash_counter = context.side_effect_counter; let note_hash = note.compute_note_hash(storage_slot); - let packed_note_content = Note::pack_content(note); + let packed_note = Note::pack(note); notify_created_note( storage_slot, Note::get_note_type_id(), - packed_note_content, + packed_note, note_hash, note_hash_counter, ); @@ -34,13 +35,13 @@ where } // Note: This function is currently totally unused. -pub fn destroy_note( +pub fn destroy_note( context: &mut PrivateContext, retrieved_note: RetrievedNote, storage_slot: Field, ) where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { let note_hash_for_read_request = compute_note_hash_for_read_request(retrieved_note, storage_slot); @@ -48,13 +49,13 @@ where destroy_note_unsafe(context, retrieved_note, note_hash_for_read_request) } -pub fn destroy_note_unsafe( +pub fn destroy_note_unsafe( context: &mut PrivateContext, retrieved_note: RetrievedNote, note_hash_for_read_request: Field, ) where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { let note_hash_for_nullify = compute_note_hash_for_nullify_internal(retrieved_note, note_hash_for_read_request); 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 80496d877561..3e3bd3a1850a 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 @@ -9,8 +9,9 @@ use crate::note::{ }; use crate::oracle; use crate::utils::comparison::compare; -use dep::protocol_types::constants::{ - GET_NOTES_ORACLE_RETURN_LENGTH, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, +use dep::protocol_types::{ + constants::{GET_NOTES_ORACLE_RETURN_LENGTH, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL}, + traits::Packable, }; pub use crate::note::constants::MAX_NOTES_PER_PAGE; @@ -18,13 +19,13 @@ pub use crate::note::constants::MAX_NOTES_PER_PAGE; mod test; fn extract_property_value_from_selector( - packed_note_content: [Field; N], + packed_note: [Field; N], selector: PropertySelector, ) -> Field { // 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 value: [u8; 32] = packed_note[selector.index].to_be_bytes(); let offset = selector.offset; let length = selector.length; let mut value_field = 0 as Field; @@ -38,14 +39,11 @@ fn extract_property_value_from_selector( value_field } -fn check_note_content( - packed_note_content: [Field; N], - selects: BoundedVec, N>, -) { +fn check_packed_note(packed_note: [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(packed_note_content, select.property_selector); + extract_property_value_from_selector(packed_note, select.property_selector); assert( compare(value_field, select.comparator, select.value.to_field()), @@ -78,7 +76,7 @@ pub fn get_note( storage_slot: Field, ) -> (RetrievedNote, Field) where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { // Safety: Constraining that we got a valid note from the oracle is fairly straightforward: all we need to do // is check that the metadata is correct, and that the note exists. @@ -107,7 +105,7 @@ pub fn get_notes( options: NoteGetterOptions, ) -> (BoundedVec, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>, BoundedVec) where - Note: NoteInterface + NullifiableNote + Eq, + Note: NoteInterface + NullifiableNote + Eq + Packable, { // Safety: The notes are constrained below. let opt_notes = unsafe { get_notes_internal(storage_slot, options) }; @@ -132,17 +130,17 @@ fn constrain_get_notes_internal, ) -> (BoundedVec, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>, BoundedVec) where - Note: NoteInterface + NullifiableNote + Eq, + Note: NoteInterface + NullifiableNote + Eq + Packable, { // The filter is applied first to avoid pushing note read requests for notes we're not interested in. Note that - // while the filter function can technically mutate the contents of the notes (as opposed to simply removing some), - // the private kernel will later validate that these note actually exist, so transformations would cause for that - // check to fail. + // while the filter function can technically mutate the notes (as opposed to simply removing some), the private + // kernel will later validate that these note actually exist, so transformations would cause for that check + // to fail. let filter_fn = options.filter; let filter_args = options.filter_args; let filtered_notes = filter_fn(opt_notes, filter_args); - let notes = crate::utils::array::collapse(filtered_notes); + let notes: BoundedVec, 16> = crate::utils::array::collapse(filtered_notes); let mut note_hashes: BoundedVec = BoundedVec::new(); @@ -151,7 +149,7 @@ where // for the runtime length, and can therefore have fewer loop iterations. assert(notes.len() <= options.limit, "Got more notes than limit."); - let mut prev_fields = [0; N]; + let mut prev_packed_note = [0; N]; for i in 0..options.limit { if i < notes.len() { let retrieved_note = notes.get_unchecked(i); @@ -164,12 +162,12 @@ where "Note contract address mismatch.", ); - let fields = retrieved_note.note.pack_content(); - check_note_content(fields, options.selects); + let packed_note = retrieved_note.note.pack(); + check_packed_note(packed_note, options.selects); if i != 0 { - check_notes_order(prev_fields, fields, options.sorts); + check_notes_order(prev_packed_note, packed_note, options.sorts); } - prev_fields = fields; + prev_packed_note = packed_note; let note_hash_for_read_request = compute_note_hash_for_read_request(retrieved_note, storage_slot); @@ -183,7 +181,7 @@ where unconstrained fn get_note_internal(storage_slot: Field) -> RetrievedNote where - Note: NoteInterface, + Note: NoteInterface + Packable, { let placeholder_note = [Option::none()]; let placeholder_fields = [0; GET_NOTE_ORACLE_RETURN_LENGTH]; @@ -215,7 +213,7 @@ unconstrained fn get_notes_internal, ) -> [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where - Note: NoteInterface, + Note: NoteInterface + Packable, { // This function simply performs some transformations from NoteGetterOptions into the types required by the oracle. let (num_selects, select_by_indexes, select_by_offsets, select_by_lengths, select_values, select_comparators, sort_by_indexes, sort_by_offsets, sort_by_lengths, sort_order) = @@ -252,7 +250,7 @@ pub unconstrained fn view_notes( options: NoteViewerOptions, ) -> BoundedVec where - Note: NoteInterface, + Note: NoteInterface + Packable, { let (num_selects, select_by_indexes, select_by_offsets, select_by_lengths, select_values, select_comparators, sort_by_indexes, sort_by_offsets, sort_by_lengths, sort_order) = flatten_options(options.selects, options.sorts); diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr index 8ead18b196b0..66432e4f5555 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr @@ -1,5 +1,8 @@ use crate::note::{note_interface::NoteInterface, retrieved_note::RetrievedNote}; -use dep::protocol_types::{constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, traits::ToField}; +use dep::protocol_types::{ + constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, + traits::{Packable, ToField}, +}; use std::option::Option; pub struct PropertySelector { @@ -134,7 +137,7 @@ impl NoteGetterOptions NoteGetterOptions where - Note: NoteInterface, + Note: NoteInterface + Packable, { // This function initializes a NoteGetterOptions that simply returns the maximum number of notes allowed in a call. pub fn new() -> Self { @@ -154,7 +157,7 @@ where impl NoteGetterOptions where - Note: NoteInterface, + Note: NoteInterface + Packable, { // This function initializes a NoteGetterOptions with a preprocessor, which takes the notes returned from // the database and preprocessor_args as its parameters. @@ -179,7 +182,7 @@ where impl NoteGetterOptions where - Note: NoteInterface, + Note: NoteInterface + Packable, { // This function initializes a NoteGetterOptions with a filter, which takes // the notes returned from the database and filter_args as its parameters. 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 ecac68a9c168..57bc5e5d51e9 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr @@ -41,17 +41,13 @@ pub trait NullifiableNote { // docs:start:note_interface // Autogenerated by the #[note] macro -pub trait NoteInterface { - fn pack_content(self) -> [Field; N]; - - fn unpack_content(fields: [Field; N]) -> Self; - +pub trait NoteInterface { fn get_note_type_id() -> Field; /// Returns the non-siloed note hash, i.e. the inner hash computed by the contract during private execution. Note /// hashes are later siloed by contract address and nonce by the kernels before being committed to the state tree. /// - /// This should be a commitment to the note contents, including the storage slot (for indexing) and some random + /// This should be a commitment to the packed note, including the storage slot (for indexing) and some random /// value (to prevent brute force trial-hashing attacks). fn compute_note_hash(self, storage_slot: Field) -> Field; } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr b/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr index c131195d69ea..fb93b8d9498c 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr @@ -1,7 +1,7 @@ use crate::note::constants::MAX_NOTES_PER_PAGE; use crate::note::note_getter_options::{NoteStatus, PropertySelector, Select, Sort}; use crate::note::note_interface::NoteInterface; -use dep::protocol_types::traits::ToField; +use dep::protocol_types::traits::{Packable, ToField}; use std::option::Option; // docs:start:NoteViewerOptions @@ -17,7 +17,7 @@ pub struct NoteViewerOptions { impl NoteViewerOptions { pub fn new() -> NoteViewerOptions where - Note: NoteInterface, + Note: NoteInterface + Packable, { NoteViewerOptions { selects: 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 3a2762c3324c..000b187df1e9 100644 --- a/noir-projects/aztec-nr/aztec/src/note/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/note/utils.nr @@ -13,13 +13,13 @@ use dep::protocol_types::{ }, }; -pub fn compute_siloed_nullifier( +pub fn compute_siloed_nullifier( retrieved_note: RetrievedNote, storage_slot: Field, context: &mut PrivateContext, ) -> Field where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { let note_hash_for_nullify = compute_note_hash_for_nullify(retrieved_note, storage_slot); let inner_nullifier = retrieved_note.note.compute_nullifier(context, note_hash_for_nullify); @@ -28,12 +28,12 @@ where } // TODO(#7775): make this not impossible to understand -pub fn compute_note_hash_for_read_request( +pub fn compute_note_hash_for_read_request( retrieved_note: RetrievedNote, storage_slot: Field, ) -> Field where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface, { let note_hash = retrieved_note.note.compute_note_hash(storage_slot); @@ -50,13 +50,10 @@ where } // TODO(#7775): make this not impossible to understand -pub fn compute_note_hash_for_nullify_internal( +pub fn compute_note_hash_for_nullify_internal( retrieved_note: RetrievedNote, note_hash_for_read_request: Field, -) -> Field -where - Note: NoteInterface + NullifiableNote, -{ +) -> Field { if (retrieved_note.note_hash_counter != 0) & (retrieved_note.nonce != 0) { // Non-revertible note, nullified by a revertible nullifier, we need to nullify the note hash that will reach the tree let siloed_note_hash = @@ -69,7 +66,7 @@ where } // TODO(#7775): nuke this commented out code - kept it around as it contains comments which might be helpful when tackling #7775 -// pub fn compute_note_hash_for_nullify(note: Note) -> Field where Note: NoteInterface { +// pub fn compute_note_hash_for_nullify(note: Note) -> Field where Note: NoteInterface { // let header = note.get_header(); // // There are 3 cases for reading a note intended for consumption: // // 1. The note was inserted in this transaction, is revertible, or is not nullified by a revertible nullifier in @@ -110,30 +107,37 @@ where // } // } -pub fn compute_note_hash_for_nullify( +pub fn compute_note_hash_for_nullify( retrieved_note: RetrievedNote, storage_slot: Field, ) -> Field where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { let note_hash_for_read_request = compute_note_hash_for_read_request(retrieved_note, storage_slot); compute_note_hash_for_nullify_internal(retrieved_note, note_hash_for_read_request) } -pub unconstrained fn compute_note_hash_and_optionally_a_nullifier( - unpack_content: fn([Field; N]) -> T, +/// Computes the note hash and optionally a nullifier for a given note. `N` is the length of the packed note, +/// `S` is the length of the packed note with its padding array. +/// +/// Note: `packed_note_with_padding` is typically constructed by calling the `storage()` method on a `BoundedVec`. This +/// function will then extract the relevant fields from the array using the `subarray` method and the actual packed +/// note length `N`. +pub unconstrained fn compute_note_hash_and_optionally_a_nullifier( + unpack_note: fn([Field; N]) -> Note, contract_address: AztecAddress, nonce: Field, compute_nullifier: bool, storage_slot: Field, - packed_note_content: [Field; S], + packed_note_with_padding: [Field; S], ) -> [Field; 4] where - T: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote, { - let note = unpack_content(array::subarray(packed_note_content, 0)); + let packed_note = array::subarray(packed_note_with_padding, 0); + let note = unpack_note(packed_note); let note_hash = note.compute_note_hash(storage_slot); let siloed_note_hash = compute_siloed_note_hash(contract_address, note_hash); diff --git a/noir-projects/aztec-nr/aztec/src/oracle/note_discovery.nr b/noir-projects/aztec-nr/aztec/src/oracle/note_discovery.nr index 8d4c2848991b..5b1cb8bd248a 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/note_discovery.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/note_discovery.nr @@ -1,24 +1,24 @@ -use crate::note::discovery::MAX_NOTE_SERIALIZED_LEN; +use crate::note::discovery::MAX_NOTE_PACKED_LEN; use dep::protocol_types::address::AztecAddress; -/// Informs PXE of a note's existence so that it can later retrieved by the `getNotes` oracle. The note will be scoped -/// to `contract_address`, meaning other contracts will not be able to access it unless authorized. +/// Informs PXE of a note's existence so that it can later be retrieved by the `getNotes` oracle. The note will be +/// scoped to `contract_address`, meaning other contracts will not be able to access it unless authorized. /// -/// The note's `content` is what `getNotes` will later return. PXE indexes notes by `storage_slot`, so this value is -/// typically used to filter notes that correspond to different state variables. `note_hash` and `nullifier` are the -/// inner hashes, i.e. the raw hashes returned by `NoteInterface::compute_note_hash` and -/// `NullifiableNote::compute_nullifier`. PXE will verify that the siloed unique note hash was inserted into the tree at -/// `tx_hash`, and will store the nullifier to later check for nullification. +/// The packed note is what `getNotes` will later return. PXE indexes notes by `storage_slot`, so this value +/// is typically used to filter notes that correspond to different state variables. `note_hash` and `nullifier` are +/// the inner hashes, i.e. the raw hashes returned by `NoteInterface::compute_note_hash` and +/// `NullifiableNote::compute_nullifier`. PXE will verify that the siloed unique note hash was inserted into the tree +/// at `tx_hash`, and will store the nullifier to later check for nullification. /// /// `recipient` is the account to which the note was sent to. Other accounts will not be able to access this note (e.g. /// other accounts will not be able to see one another's token balance notes, even in the same PXE) unless authorized. /// -/// Returns true if the note was sucessfully delivered and added to PXE's database. +/// Returns true if the note was successfully delivered and added to PXE's database. pub unconstrained fn deliver_note( contract_address: AztecAddress, storage_slot: Field, nonce: Field, - content: BoundedVec, + packed_note: BoundedVec, note_hash: Field, nullifier: Field, tx_hash: Field, @@ -28,7 +28,7 @@ pub unconstrained fn deliver_note( contract_address, storage_slot, nonce, - content, + packed_note, note_hash, nullifier, tx_hash, @@ -41,7 +41,7 @@ unconstrained fn deliver_note_oracle( contract_address: AztecAddress, storage_slot: Field, nonce: Field, - content: BoundedVec, + packed_note: BoundedVec, note_hash: Field, nullifier: Field, tx_hash: Field, diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index fa36a763e465..c0fc57318a85 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -3,6 +3,7 @@ use crate::{note::{note_interface::NoteInterface, retrieved_note::RetrievedNote} use dep::protocol_types::{ address::AztecAddress, indexed_tagging_secret::{INDEXED_TAGGING_SECRET_LENGTH, IndexedTaggingSecret}, + traits::Packable, }; /// Notifies the simulator that a note has been created, so that it can be returned in future read requests in the same @@ -10,7 +11,7 @@ use dep::protocol_types::{ pub fn notify_created_note( storage_slot: Field, note_type_id: Field, - packed_note_content: [Field; N], + packed_note: [Field; N], note_hash: Field, counter: u32, ) { @@ -20,7 +21,7 @@ pub fn notify_created_note( notify_created_note_oracle_wrapper( storage_slot, note_type_id, - packed_note_content, + packed_note, note_hash, counter, ) @@ -45,24 +46,18 @@ pub fn notify_created_nullifier(nullifier: Field) { unconstrained fn notify_created_note_oracle_wrapper( storage_slot: Field, note_type_id: Field, - packed_note_content: [Field; N], + packed_note: [Field; N], note_hash: Field, counter: u32, ) { - let _ = notify_created_note_oracle( - storage_slot, - note_type_id, - packed_note_content, - note_hash, - counter, - ); + let _ = notify_created_note_oracle(storage_slot, note_type_id, packed_note, note_hash, counter); } #[oracle(notifyCreatedNote)] unconstrained fn notify_created_note_oracle( _storage_slot: Field, _note_type_id: Field, - _packed_note_content: [Field; N], + _packed_note: [Field; N], _note_hash: Field, _counter: u32, ) -> Field {} @@ -164,10 +159,10 @@ pub unconstrained fn get_notes>; S], // TODO: Remove it and use `limit` to initialize the note array. placeholder_fields: [Field; NS], // TODO: Remove it and use `limit` to initialize the note array. - _placeholder_note_length: [Field; N], // Turbofish hack? Compiler breaks calculating read_offset unless we add this parameter + _placeholder_note_length: [Field; N], // Turbofish hack? Compiler breaks calculating read_offset unless we add this parameter TODO(benesjan): try removing this. ) -> [Option>; S] where - Note: NoteInterface, + Note: NoteInterface + Packable, { sync_notes_oracle_wrapper(); let fields = get_notes_oracle_wrapper( @@ -198,9 +193,9 @@ where let nonce = fields[read_offset]; let note_hash_counter = fields[read_offset + 1] as u32; - let note_content = array::subarray(fields, read_offset + 2); + let packed_note = array::subarray(fields, read_offset + 2); - let note = Note::unpack_content(note_content); + let note = Note::unpack(packed_note); let retrieved_note = RetrievedNote { note, contract_address, nonce, note_hash_counter }; placeholder_opt_notes[i] = Option::some(retrieved_note); diff --git a/noir-projects/aztec-nr/aztec/src/prelude.nr b/noir-projects/aztec-nr/aztec/src/prelude.nr index bff301cdf44a..b62e61ca3588 100644 --- a/noir-projects/aztec-nr/aztec/src/prelude.nr +++ b/noir-projects/aztec-nr/aztec/src/prelude.nr @@ -6,7 +6,6 @@ pub use crate::{ note_interface::{NoteInterface, NullifiableNote}, note_viewer_options::NoteViewerOptions, retrieved_note::RetrievedNote, - utils::compute_note_hash_and_optionally_a_nullifier as utils_compute_note_hash_and_optionally_a_nullifier, }, state_vars::{ map::Map, private_immutable::PrivateImmutable, private_mutable::PrivateMutable, diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr index 58b932b12011..dab695e717c6 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr @@ -56,7 +56,7 @@ impl PrivateImmutable { // docs:start:initialize pub fn initialize(self, note: Note) -> NoteEmission where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { // Nullify the storage slot. let nullifier = self.compute_initialization_nullifier(); @@ -69,7 +69,7 @@ impl PrivateImmutable { // docs:start:get_note pub fn get_note(self) -> Note where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { let storage_slot = self.storage_slot; let retrieved_note = get_note(self.context, storage_slot).0; @@ -94,7 +94,7 @@ impl PrivateImmutable { // docs:start:view_note pub unconstrained fn view_note(self) -> Note where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { let mut options = NoteViewerOptions::new(); view_notes(self.storage_slot, options.set_limit(1)).get(0) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr index 496a7d677fab..f0164aee7ef8 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr @@ -59,7 +59,7 @@ impl PrivateMutable { impl PrivateMutable where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { // docs:start:initialize pub fn initialize(self, note: Note) -> NoteEmission { @@ -127,7 +127,7 @@ where impl PrivateMutable where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { pub unconstrained fn is_initialized(self) -> bool { let nullifier = self.compute_initialization_nullifier(); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr index 96d7a51d7ec0..33946f1f2745 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr @@ -43,7 +43,7 @@ impl PrivateSet { impl PrivateSet where - Note: NoteInterface + NullifiableNote + Eq, + Note: NoteInterface + NullifiableNote + Eq + Packable, { // docs:start:insert pub fn insert(self, note: Note) -> NoteEmission { @@ -97,7 +97,7 @@ where impl PrivateSet where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { // docs:start:view_notes pub unconstrained fn view_notes( 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 6f806b185e63..941a2243bfae 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 @@ -12,6 +12,7 @@ use crate::oracle::{ notes::notify_created_note, }; use protocol_types::constants::PUBLIC_DISPATCH_SELECTOR; +use protocol_types::traits::Packable; pub struct TestEnvironment {} @@ -157,18 +158,18 @@ impl TestEnvironment { contract_address: AztecAddress, ) where - Note: NoteInterface + NullifiableNote, + Note: NoteInterface + NullifiableNote + Packable, { let original_contract_address = get_contract_address(); cheatcodes::set_contract_address(contract_address); let note_hash_counter = cheatcodes::get_side_effects_counter(); let note_hash = note.compute_note_hash(storage_slot); - let packed_content = Note::pack_content(note); + let packed_note = Note::pack(note); notify_created_note( storage_slot, Note::get_note_type_id(), - packed_content, + packed_note, 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 fe19edfd5f39..e28dea8fc8ed 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 @@ -11,12 +11,11 @@ use dep::protocol_types::{ address::AztecAddress, constants::{GENERATOR_INDEX__NOTE_HASH, GENERATOR_INDEX__NOTE_NULLIFIER}, hash::poseidon2_hash_with_separator, + traits::Packable, utils::arrays::array_concat, }; -global MOCK_NOTE_LENGTH: u32 = 1; - -#[derive(Eq)] +#[derive(Eq, Packable)] pub(crate) struct MockNote { pub(crate) value: Field, } @@ -54,15 +53,7 @@ impl NullifiableNote for MockNote { } } -impl NoteInterface for MockNote { - fn pack_content(self) -> [Field; MOCK_NOTE_LENGTH] { - [self.value] - } - - fn unpack_content(fields: [Field; MOCK_NOTE_LENGTH]) -> Self { - Self { value: fields[0] } - } - +impl NoteInterface for MockNote { fn get_note_type_id() -> Field { // randomly chosen note type id --> has to fit within 7 bits 76 @@ -72,7 +63,7 @@ impl NoteInterface for MockNote { // We use Poseidon2 instead of multi-scalar multiplication (MSM) here since this is not a partial note // and therefore does not require MSM's additive homomorphism property. Additionally, Poseidon2 uses fewer // constraints. - let input = array_concat(self.pack_content(), [storage_slot]); + let input = array_concat(self.pack(), [storage_slot]); poseidon2_hash_with_separator(input, GENERATOR_INDEX__NOTE_HASH) } } diff --git a/noir-projects/aztec-nr/uint-note/src/uint_note.nr b/noir-projects/aztec-nr/uint-note/src/uint_note.nr index ccf3552cce54..0a8ca726aeda 100644 --- a/noir-projects/aztec-nr/uint-note/src/uint_note.nr +++ b/noir-projects/aztec-nr/uint-note/src/uint_note.nr @@ -5,13 +5,14 @@ use dep::aztec::{ oracle::random::random, prelude::{NullifiableNote, PrivateContext, RetrievedNote}, protocol_types::{ - address::AztecAddress, - constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, - traits::{Packable, Serialize}, + address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, + hash::poseidon2_hash_with_separator, traits::Serialize, }, }; +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + // docs:start:UintNote #[partial_note(quote {value})] #[derive(Eq, Serialize)] @@ -19,7 +20,7 @@ pub struct UintNote { // The amount of tokens in the note value: U128, owner: AztecAddress, - // Randomness of the note to hide its contents + // Randomness of the note to protect against note hash preimage attacks randomness: Field, } // docs:end:UintNote diff --git a/noir-projects/aztec-nr/value-note/src/value_note.nr b/noir-projects/aztec-nr/value-note/src/value_note.nr index 19978005a545..7fc36ad89643 100644 --- a/noir-projects/aztec-nr/value-note/src/value_note.nr +++ b/noir-projects/aztec-nr/value-note/src/value_note.nr @@ -9,14 +9,18 @@ use dep::aztec::{ oracle::random::random, protocol_types::{ address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, traits::Packable, + hash::poseidon2_hash_with_separator, }, }; +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + pub(crate) global VALUE_NOTE_LEN: u32 = 3; // 3 plus a header. // docs:start:value-note-def #[note] +#[derive(Eq)] pub struct ValueNote { value: Field, owner: AztecAddress, @@ -72,11 +76,3 @@ impl ValueNote { ValueNote { value, owner, randomness } } } - -impl Eq for ValueNote { - fn eq(self, other: Self) -> bool { - (self.value == other.value) - & (self.owner == other.owner) - & (self.randomness == other.randomness) - } -} diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr index 4643b25e9b4d..03dbc46e892e 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr @@ -5,17 +5,18 @@ use dep::aztec::{ note::{retrieved_note::RetrievedNote, utils::compute_note_hash_for_nullify}, oracle::random::random, prelude::{NullifiableNote, PrivateContext}, - protocol_types::{ - address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, traits::Packable, - }, + protocol_types::{address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER}, }; +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + #[note] pub struct SubscriptionNote { owner: AztecAddress, expiry_block_number: Field, remaining_txs: Field, - // Randomness of the note to hide its contents + // Randomness of the note to protect against note hash preimage attacks randomness: Field, } diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr index d46db59d0769..f582ca02473f 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr @@ -2,19 +2,21 @@ use dep::aztec::{ keys::getters::{get_nsk_app, get_public_keys}, macros::notes::note, note::utils::compute_note_hash_for_nullify, + prelude::{NullifiableNote, PrivateContext, RetrievedNote}, protocol_types::{ - address::AztecAddress, - constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, - traits::{Packable, Serialize}, + address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, + hash::poseidon2_hash_with_separator, traits::Serialize, }, }; -use dep::aztec::prelude::{NullifiableNote, PrivateContext, RetrievedNote}; + +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; // docs:start:state_vars-CardNote global CARD_NOTE_LEN: u32 = 3; // 3 plus a header. #[note] +#[derive(Eq)] pub struct CardNote { points: u8, randomness: Field, @@ -72,11 +74,3 @@ impl Serialize<3> for CardNote { } } // docs:end:serialize - -impl Eq for CardNote { - fn eq(self, other: Self) -> bool { - (self.points == other.points) - & (self.owner == other.owner) - & (self.randomness == other.randomness) - } -} 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 072b17bb8f5f..a0c507b24590 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 @@ -1,30 +1,66 @@ -use dep::aztec::prelude::{NoteInterface, NullifiableNote, PrivateContext, RetrievedNote}; +use dep::aztec::prelude::{NullifiableNote, PrivateContext, RetrievedNote}; use dep::aztec::{ note::utils::compute_note_hash_for_nullify, keys::getters::{get_nsk_app, get_public_keys}, - protocol_types::{address::AztecAddress, constants::{GENERATOR_INDEX__NOTE_NULLIFIER, GENERATOR_INDEX__NOTE_HASH}, hash::poseidon2_hash_with_separator, utils::arrays::array_concat}, - macros::notes::note_custom_interface + protocol_types::{address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator, traits::Packable}, + macros::notes::note }; -global ECDSA_PUBLIC_KEY_NOTE_LEN: u32 = 5; - // Stores an ECDSA public key composed of two 32-byte elements // TODO: Do we need to include a nonce, in case we want to read/nullify/recreate with the same pubkey value? -#[note_custom_interface] +#[note] pub struct EcdsaPublicKeyNote { x: [u8; 32], y: [u8; 32], owner: AztecAddress, } -impl NoteInterface for EcdsaPublicKeyNote { +impl NullifiableNote for EcdsaPublicKeyNote { + + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { + let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash(); + let secret = context.request_nsk_app(owner_npk_m_hash); + poseidon2_hash_with_separator( + [ + note_hash_for_nullify, + secret + ], + GENERATOR_INDEX__NOTE_NULLIFIER as Field + ) + } + + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field { + // We set the note_hash_counter to 0 as the note is assumed to be committed (and hence not transient). + let retrieved_note = RetrievedNote { note: self, contract_address, nonce: note_nonce, note_hash_counter: 0 }; + let note_hash_for_nullify = compute_note_hash_for_nullify(retrieved_note, storage_slot); + let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash(); + let secret = get_nsk_app(owner_npk_m_hash); + poseidon2_hash_with_separator( + [ + note_hash_for_nullify, + secret + ], + GENERATOR_INDEX__NOTE_NULLIFIER as Field + ) + } +} + +impl EcdsaPublicKeyNote { + pub fn new(x: [u8; 32], y: [u8; 32], owner: AztecAddress) -> Self { + EcdsaPublicKeyNote { x, y, owner } + } +} + +global ECDSA_PUBLIC_KEY_NOTE_PCKD_LEN: u32 = 5; + +impl Packable for EcdsaPublicKeyNote { // 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 pack_content(self) -> [Field; ECDSA_PUBLIC_KEY_NOTE_LEN] { + fn pack(self) -> [Field; ECDSA_PUBLIC_KEY_NOTE_PCKD_LEN] { let mut x: Field = 0; let mut y: Field = 0; let mut mul: Field = 1; @@ -44,71 +80,22 @@ impl NoteInterface for EcdsaPublicKeyNote { } // Cannot use the automatic unpacking for the aforementioned reasons - fn unpack_content(packed_content: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote { + fn unpack(packed_note: [Field; ECDSA_PUBLIC_KEY_NOTE_PCKD_LEN]) -> EcdsaPublicKeyNote { let mut x: [u8; 32] = [0; 32]; let mut y: [u8; 32] = [0; 32]; - let part_x:[u8; 32] = packed_content[0].to_be_bytes(); + let part_x:[u8; 32] = packed_note[0].to_be_bytes(); for i in 0..31 { x[i] = part_x[i + 1]; } - x[31] = packed_content[1].to_be_bytes::<32>()[31]; + x[31] = packed_note[1].to_be_bytes::<32>()[31]; - let part_y:[u8; 32] = packed_content[2].to_be_bytes(); + let part_y:[u8; 32] = packed_note[2].to_be_bytes(); for i in 0..31 { y[i] = part_y[i + 1]; } - y[31] = packed_content[3].to_be_bytes::<32>()[31]; - - EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(packed_content[4]) } - } + y[31] = packed_note[3].to_be_bytes::<32>()[31]; - fn get_note_type_id() -> Field { - // randomly chosen note type id --> has to fit within 7 bits - 76 + EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(packed_note[4]) } } - - fn compute_note_hash(self, storage_slot: Field) -> Field { - // We use Poseidon2 instead of multi-scalar multiplication (MSM) here since this is not a partial note - // and therefore does not require MSM's additive homomorphism property. Additionally, Poseidon2 uses fewer - // constraints. - let inputs = array_concat(self.pack_content(), [storage_slot]); - poseidon2_hash_with_separator(inputs, GENERATOR_INDEX__NOTE_HASH) - } -} - -impl NullifiableNote for EcdsaPublicKeyNote { - - fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { - let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash(); - let secret = context.request_nsk_app(owner_npk_m_hash); - poseidon2_hash_with_separator( - [ - note_hash_for_nullify, - secret - ], - GENERATOR_INDEX__NOTE_NULLIFIER as Field - ) - } - - unconstrained fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field { - // We set the note_hash_counter to 0 as the note is assumed to be committed (and hence not transient). - let retrieved_note = RetrievedNote { note: self, contract_address, nonce: note_nonce, note_hash_counter: 0 }; - let note_hash_for_nullify = compute_note_hash_for_nullify(retrieved_note, storage_slot); - let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash(); - let secret = get_nsk_app(owner_npk_m_hash); - poseidon2_hash_with_separator( - [ - note_hash_for_nullify, - secret - ], - GENERATOR_INDEX__NOTE_NULLIFIER as Field - ) - } -} - -impl EcdsaPublicKeyNote { - pub fn new(x: [u8; 32], y: [u8; 32], owner: AztecAddress) -> Self { - EcdsaPublicKeyNote { x, y, owner } - } -} +} \ No newline at end of file diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr index 6888a42c0c77..86913add9100 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr @@ -6,18 +6,22 @@ use dep::aztec::{ prelude::{NullifiableNote, PrivateContext, RetrievedNote}, protocol_types::{ address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, traits::Packable, + hash::poseidon2_hash_with_separator, }, }; +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + // docs:start:nft_note #[partial_note(quote { token_id})] +#[derive(Eq)] pub struct NFTNote { // ID of the token token_id: Field, // The owner of the note owner: AztecAddress, - // Randomness of the note to hide its contents + // Randomness of the note to protect against note hash preimage attacks randomness: Field, } // docs:end:nft_note @@ -67,11 +71,3 @@ impl NFTNote { NFTNote { token_id, owner, randomness } } } - -impl Eq for NFTNote { - fn eq(self, other: Self) -> bool { - (self.token_id == other.token_id) - & (self.owner == other.owner) - & (self.randomness == other.randomness) - } -} diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr index 68a468bcb399..5f3cfbfe1dea 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr @@ -5,10 +5,13 @@ use aztec::{ prelude::{NullifiableNote, PrivateContext, RetrievedNote}, protocol_types::{ address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, traits::Packable, + hash::poseidon2_hash_with_separator, }, }; +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + // Stores a public key composed of two fields // TODO: Do we need to include a nonce, in case we want to read/nullify/recreate with the same pubkey value? #[note] diff --git a/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr b/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr index 480e8e18fd5b..d18d9a99dd78 100644 --- a/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr +++ b/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr @@ -3,7 +3,9 @@ use crate::types::token_note::OwnedNote; use dep::aztec::{ context::{PrivateContext, UnconstrainedContext}, note::note_emission::OuterNoteEmission, - protocol_types::{address::AztecAddress, constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL}, + protocol_types::{ + address::AztecAddress, constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, traits::Packable, + }, }; use dep::aztec::prelude::{ NoteGetterOptions, NoteInterface, NoteViewerOptions, NullifiableNote, PrivateSet, RetrievedNote, @@ -21,19 +23,16 @@ impl BalanceSet { } impl BalanceSet { - pub unconstrained fn balance_of(self: Self) -> U128 + pub unconstrained fn balance_of(self: Self) -> U128 where - Note: NoteInterface + NullifiableNote + OwnedNote, + Note: NoteInterface + NullifiableNote + OwnedNote + Packable, { self.balance_of_with_offset(0) } - pub unconstrained fn balance_of_with_offset( - self: Self, - offset: u32, - ) -> U128 + pub unconstrained fn balance_of_with_offset(self: Self, offset: u32) -> U128 where - Note: NoteInterface + NullifiableNote + OwnedNote, + Note: NoteInterface + NullifiableNote + OwnedNote + Packable, { let mut balance = U128::from_integer(0); // docs:start:view_notes @@ -54,13 +53,9 @@ impl BalanceSet { } impl BalanceSet { - pub fn add( - self: Self, - owner: AztecAddress, - addend: U128, - ) -> OuterNoteEmission + pub fn add(self: Self, owner: AztecAddress, addend: U128) -> OuterNoteEmission where - Note: NoteInterface + NullifiableNote + OwnedNote + Eq, + Note: NoteInterface + NullifiableNote + OwnedNote + Eq + Packable, { if addend == U128::from_integer(0) { OuterNoteEmission::new(Option::none()) @@ -73,13 +68,9 @@ impl BalanceSet { } } - pub fn sub( - self: Self, - owner: AztecAddress, - amount: U128, - ) -> OuterNoteEmission + pub fn sub(self: Self, owner: AztecAddress, amount: U128) -> OuterNoteEmission where - Note: NoteInterface + NullifiableNote + OwnedNote + Eq, + Note: NoteInterface + NullifiableNote + OwnedNote + Eq + Packable, { let subtracted = self.try_sub(amount, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL); @@ -98,13 +89,9 @@ impl BalanceSet { // The `max_notes` parameter is used to fine-tune the number of constraints created by this function. The gate count // scales relatively linearly with `max_notes`, but a lower `max_notes` parameter increases the likelihood of // `try_sub` subtracting an amount smaller than `target_amount`. - pub fn try_sub( - self: Self, - target_amount: U128, - max_notes: u32, - ) -> U128 + pub fn try_sub(self: Self, target_amount: U128, max_notes: u32) -> U128 where - Note: NoteInterface + NullifiableNote + OwnedNote + Eq, + Note: NoteInterface + NullifiableNote + OwnedNote + Eq + Packable, { // We are using a preprocessor here (filter applied in an unconstrained context) instead of a filter because // we do not need to prove correct execution of the preprocessor. @@ -132,12 +119,12 @@ impl BalanceSet { // The preprocessor (a filter applied in an unconstrained context) does not check if total sum is larger or equal to // 'min_sum' - all it does is remove extra notes if it does reach that value. // Note that proper usage of this preprocessor requires for notes to be sorted in descending order. -pub fn preprocess_notes_min_sum( +pub fn preprocess_notes_min_sum( notes: [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], min_sum: U128, ) -> [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where - Note: NoteInterface + NullifiableNote + OwnedNote, + Note: NoteInterface + NullifiableNote + OwnedNote, { let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let mut sum = U128::from_integer(0); diff --git a/noir-projects/noir-contracts/contracts/spam_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/spam_contract/src/types/token_note.nr index 6f0c53baf0c0..6bf71efb3224 100644 --- a/noir-projects/noir-contracts/contracts/spam_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/spam_contract/src/types/token_note.nr @@ -6,22 +6,26 @@ use dep::aztec::{ prelude::{NullifiableNote, PrivateContext, RetrievedNote}, protocol_types::{ address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, traits::Packable, + hash::poseidon2_hash_with_separator, }, }; +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + trait OwnedNote { fn new(amount: U128, owner: AztecAddress) -> Self; fn get_amount(self) -> U128; } // docs:start:TokenNote #[note] +#[derive(Eq)] pub struct TokenNote { // The amount of tokens in the note amount: U128, // The owner of the note owner: AztecAddress, - // Randomness of the note to hide its contents + // Randomness of the note to protect against note hash preimage attacks randomness: Field, } // docs:end:TokenNote @@ -61,14 +65,6 @@ impl NullifiableNote for TokenNote { } } -impl Eq for TokenNote { - fn eq(self, other: Self) -> bool { - (self.amount == other.amount) - & (self.owner == other.owner) - & (self.randomness == other.randomness) - } -} - impl OwnedNote for TokenNote { fn new(amount: U128, owner: AztecAddress) -> Self { // Safety: We use the randomness to preserve the privacy of the note recipient by preventing brute-forcing, diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr b/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr index e988a074ca1b..ce76fcaede25 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr @@ -1,20 +1,46 @@ use dep::aztec::{ context::PrivateContext, - macros::notes::note, - note::note_interface::NullifiableNote, - protocol_types::{address::AztecAddress, traits::{Deserialize, Packable, Serialize}}, + macros::notes::note_custom_interface, + note::note_interface::{NoteInterface, NullifiableNote}, + protocol_types::{ + address::AztecAddress, + constants::GENERATOR_INDEX__NOTE_HASH, + hash::poseidon2_hash_with_separator, + traits::{Deserialize, Serialize}, + utils::arrays::array_concat, + }, }; -// A note which stores a field and is expected to be passed around using the `addNote` function. -// WARNING: This Note is not private as it does not contain randomness and hence it can be easy to perform -// serialized_note attack on it. This note has been developed purely for testing purposes so that it can easily be -// manually added to PXE. Do not use for real applications. -#[note] -#[derive(Serialize, Deserialize)] +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + +/// A note which stores a field and is expected to be passed around using the `addNote` function. +/// +/// WARNING: This Note is not private as it does not contain randomness, making it vulnerable to +/// note hash preimage attacks. This note was developed purely for testing purposes so it could be +/// easily added to PXE manually. Do not use for real applications. +/// +/// Note: We are using `#[note_custom_interface]` here even though we don't need a custom implementation, +/// just to test that the macro works (it's not used anywhere else so far). +#[note_custom_interface] +#[derive(Eq, Deserialize, Serialize)] pub struct TestNote { value: Field, } +impl NoteInterface for TestNote { + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/12012): This is broken + fn get_note_type_id() -> Field { + // id has to fit within 7 bits + 2 + } + + fn compute_note_hash(self, storage_slot: Field) -> Field { + let inputs = array_concat(self.pack(), [storage_slot]); + poseidon2_hash_with_separator(inputs, GENERATOR_INDEX__NOTE_HASH) + } +} + impl NullifiableNote for TestNote { fn compute_nullifier( @@ -42,24 +68,3 @@ impl TestNote { TestNote { value } } } - -// Note: We are not deriving Eq here because generally we don't want to include the header in the comparison. -// This is bad note design and a tech debt. Ideally derive it once the note design is fixed. -impl Eq for TestNote { - fn eq(self, other: Self) -> bool { - self.value == other.value - } -} - -// Note: We are not deriving Packable here because that would pack the whole struct including the note header -// (so the resulting field array size would be 5). -// This is bad note design and a tech debt. Ideally derive it once the note design is fixed. -impl Packable<1> for TestNote { - fn pack(self) -> [Field; 1] { - [self.value] - } - - fn unpack(fields: [Field; 1]) -> Self { - TestNote { value: fields[0] } - } -} diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr index 5f051d4daaef..b646c3bf4fbc 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr @@ -2,7 +2,7 @@ use crate::types::token_note::OwnedNote; use dep::aztec::{ context::{PrivateContext, UnconstrainedContext}, note::note_emission::OuterNoteEmission, - protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, + protocol_types::{constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, traits::Packable}, }; use dep::aztec::prelude::{ AztecAddress, Map, NoteGetterOptions, NoteInterface, NoteViewerOptions, NullifiableNote, @@ -27,23 +27,20 @@ impl BalancesMap { } impl BalancesMap { - pub unconstrained fn balance_of( - self: Self, - owner: AztecAddress, - ) -> U128 + pub unconstrained fn balance_of(self: Self, owner: AztecAddress) -> U128 where - Note: NoteInterface + NullifiableNote + OwnedNote, + Note: NoteInterface + NullifiableNote + OwnedNote + Packable, { self.balance_of_with_offset(owner, 0) } - pub unconstrained fn balance_of_with_offset( + pub unconstrained fn balance_of_with_offset( self: Self, owner: AztecAddress, offset: u32, ) -> U128 where - Note: NoteInterface + NullifiableNote + OwnedNote, + Note: NoteInterface + NullifiableNote + OwnedNote + Packable, { let mut balance = U128::from_integer(0); // docs:start:view_notes @@ -65,13 +62,9 @@ impl BalancesMap { impl BalancesMap { - pub fn add( - self: Self, - owner: AztecAddress, - addend: U128, - ) -> OuterNoteEmission + pub fn add(self: Self, owner: AztecAddress, addend: U128) -> OuterNoteEmission where - Note: NoteInterface + NullifiableNote + OwnedNote + Eq, + Note: NoteInterface + NullifiableNote + OwnedNote + Eq + Packable, { if addend == U128::from_integer(0) { OuterNoteEmission::new(Option::none()) @@ -84,13 +77,13 @@ impl BalancesMap { } } - pub fn sub( + pub fn sub( self: Self, owner: AztecAddress, subtrahend: U128, ) -> OuterNoteEmission where - Note: NoteInterface + NullifiableNote + OwnedNote + Eq, + Note: NoteInterface + NullifiableNote + OwnedNote + Eq + Packable, { let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend); let notes = self.map.at(owner).pop_notes(options); @@ -112,12 +105,12 @@ impl BalancesMap { } } -pub fn filter_notes_min_sum( +pub fn filter_notes_min_sum( retrieved_notes: [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], min_sum: U128, ) -> [Option>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where - Note: NoteInterface + OwnedNote, + Note: NoteInterface + OwnedNote, { let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let mut sum = U128::from_integer(0); diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr index a85f52989ad5..b6a6e913a995 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr @@ -6,21 +6,25 @@ use dep::aztec::{ prelude::{NullifiableNote, PrivateContext, RetrievedNote}, protocol_types::{ address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, traits::Packable, + hash::poseidon2_hash_with_separator, }, }; +// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros. +use aztec::protocol_types::traits::Packable; + trait OwnedNote { fn new(amount: U128, owner: AztecAddress) -> Self; fn get_amount(self) -> U128; } #[note] +#[derive(Eq)] pub struct TokenNote { // The amount of tokens in the note amount: U128, owner: AztecAddress, - // Randomness of the note to hide its contents + // Randomness of the note to protect against note hash preimage attacks randomness: Field, } @@ -59,14 +63,6 @@ impl NullifiableNote for TokenNote { } } -impl Eq for TokenNote { - fn eq(self, other: Self) -> bool { - (self.amount == other.amount) - & (self.owner == other.owner) - & (self.randomness == other.randomness) - } -} - impl OwnedNote for TokenNote { fn new(amount: U128, owner: AztecAddress) -> Self { // Safety: We use the randomness to preserve the privacy of the note recipient by preventing brute-forcing, diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr index 6fd549e2c846..2497ce428a30 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr @@ -1,4 +1,3 @@ -// docs:start:token_types_all use dep::aztec::{ macros::notes::note, note::utils::compute_note_hash_for_nullify, @@ -16,6 +15,7 @@ use dep::std::mem::zeroed; // Owner of the tokens provides a "secret_hash" as an argument to the public "shield" function and then the tokens // can be redeemed in private by presenting the preimage of the "secret_hash" (the secret). #[note] +#[derive(Eq)] pub struct TransparentNote { amount: Field, secret_hash: Field, @@ -62,10 +62,3 @@ impl TransparentNote { TransparentNote { amount, secret_hash } } } - -impl Eq for TransparentNote { - fn eq(self, other: Self) -> bool { - (self.amount == other.amount) & (self.secret_hash == other.secret_hash) - } -} -// docs:end:token_types_all 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 74a989931579..325ab8edf024 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -30,9 +30,7 @@ pub contract Token { storage::storage, }, oracle::random::random, - prelude::{ - AztecAddress, FunctionSelector, Map, PublicContext, PublicImmutable, PublicMutable, - }, + prelude::{AztecAddress, Map, PublicContext, PublicImmutable, PublicMutable}, protocol_types::{point::Point, traits::Serialize}, }; 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 06c30b85bcfc..c64ca823eebc 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 @@ -61,7 +61,7 @@ use super::traits::{Deserialize, Packable, Serialize}; /// # Panics /// - If the deserialization logic encounters a type it does not support. /// - If an incorrect number of fields are consumed when deserializing a string. -pub comptime fn generate_deserialize_from_fields( +pub comptime fn generate_deserialize_from_fields( name: Quoted, typ: Type, field_array_name: Quoted, @@ -453,7 +453,13 @@ pub(crate) comptime fn derive_deserialize(s: StructDefinition) -> Quoted { } } -pub(crate) comptime fn derive_packable(s: StructDefinition) -> Quoted { +/// Generates `Packable` implementation for a given struct and returns the packed length. +/// +/// Note: We are having this function separate from `derive_packable` because we use this in the note macros to get +/// the packed length of a note as well as the `Packable` implementation. We need the length to be able to register +/// the note in the global `NOTES` map. There the length is used to generate +/// `compute_note_hash_and_optionally_a_nullifier` function on contracts. +pub comptime fn derive_packable_and_get_packed_len(s: StructDefinition) -> (Quoted, u32) { let packing_enabled = true; let typ = s.as_type(); @@ -471,7 +477,8 @@ pub(crate) comptime fn derive_packable(s: StructDefinition) -> Quoted { let field_packings = fields.join(quote {,}); let packed_len = fields.len(); - quote { + ( + quote { impl Packable<$packed_len> for $typ { fn pack(self) -> [Field; $packed_len] { $aux_vars_for_packing @@ -482,7 +489,14 @@ pub(crate) comptime fn derive_packable(s: StructDefinition) -> Quoted { $unpacked } } - } + }, + packed_len, + ) +} + +pub(crate) comptime fn derive_packable(s: StructDefinition) -> Quoted { + let (packable_impl, _) = derive_packable_and_get_packed_len(s); + packable_impl } #[derive(Packable, Serialize, Deserialize, Eq)] diff --git a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts index 29b3c0b8c4f4..c07647df5021 100644 --- a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts @@ -249,6 +249,9 @@ describe('e2e_2_pxes', () => { note = notes[0]; } + // TODO(#12013): We need to do this hack because NoteDao no longer populates noteTypeId + note.noteTypeId = TestContract.notes.ValueNote.id; + // 3. Nullify the note { const receipt = await testContract.methods.call_destroy_note(noteStorageSlot).send().wait({ debug: true }); diff --git a/yarn-project/pxe/src/database/note_dao.ts b/yarn-project/pxe/src/database/note_dao.ts index 242968609db6..c6e219882b9d 100644 --- a/yarn-project/pxe/src/database/note_dao.ts +++ b/yarn-project/pxe/src/database/note_dao.ts @@ -27,7 +27,7 @@ export class NoteDao implements NoteData { // Computed values /** - * The inner hash (non-unique, non-siloed) of the note. Each contract determines how the note content is hashed. Can + * The inner hash (non-unique, non-siloed) of the note. Each contract determines how the note is hashed. Can * be used alongside contractAddress and nonce to compute the uniqueNoteHash and the siloedNoteHash. */ public noteHash: Fr, @@ -50,11 +50,11 @@ export class NoteDao implements NoteData { public l2BlockHash: string, /** The index of the leaf in the global note hash tree the note is stored at */ public index: bigint, - /** The public key with which the note content was encrypted during delivery. */ + /** The public key with which the note log was encrypted during delivery. */ public addressPoint: PublicKey, /** The note type identifier for the contract. - * TODO: remove + * TODO(#12013): remove */ public noteTypeId: NoteSelector, ) {} diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 9f12e100d621..087994eb6b99 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -775,7 +775,7 @@ export class SimulatorOracle implements DBOracle { receipt.blockHash!.toString(), uniqueNoteHashTreeIndex, await recipient.toAddressPoint(), - NoteSelector.empty(), // todo: remove + NoteSelector.empty(), // TODO(#12013): remove ); }