diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index 579dd5f98a75..f2fbe5836932 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -6,6 +6,61 @@ keywords: [sandbox, aztec, notes, migration, updating, upgrading] Aztec is in full-speed development. Literally every version breaks compatibility with the previous ones. This page attempts to target errors and difficulties you might encounter when upgrading, and how to resolve them. +### TBD + +### [Aztec.nr] Changes to `NoteInterface` +We are in a process of discontinuing `NoteHeader` from notes. +This led us to do the following changes to `NoteInterface`: + +```diff +pub trait NullifiableNote { +... +- unconstrained fn compute_nullifier_without_context(self) -> Field; ++ unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field; +} + +pub trait NoteInterface { +- fn compute_note_hash(self) -> Field; ++ fn compute_note_hash(self, storage_slot: Field) -> Field; +} +``` + +If you are using `#[note]` or `#[partial_note(...)]` macros this should not affect you as these functions are auto-generated. +If you use `#[note_custom_interface]` macro you will need to update your notes. +These are the changes that needed to be done to our `EcdsaPublicKeyNote`: + +```diff ++ use dep::aztec::protocol_types::utils::arrays::array_concat; + +impl NoteInterface for EcdsaPublicKeyNote { +... +- fn compute_note_hash(self) -> Field { ++ fn compute_note_hash(self, storage_slot: Field) -> Field { +- poseidon2_hash_with_separator(self.pack_content(), GENERATOR_INDEX__NOTE_HASH) ++ let inputs = array_concat(self.pack_content(), [storage_slot]); + poseidon2_hash_with_separator(inputs, GENERATOR_INDEX__NOTE_HASH) + } +} + +impl NullifiableNote for EcdsaPublicKeyNote { +... +- unconstrained fn compute_nullifier_without_context(self) -> Field { +- let note_hash_for_nullify = compute_note_hash_for_nullify(self); ++ unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { ++ let note_hash_for_nullify = compute_note_hash_for_nullify(self, 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 + ) + } +} +``` + ### 0.75.0 ### Changes to `TokenBridge` interface 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 f1aa4f334a8c..2617b9a3bb36 100644 --- a/noir-projects/aztec-nr/address-note/src/address_note.nr +++ b/noir-projects/aztec-nr/address-note/src/address_note.nr @@ -41,8 +41,8 @@ impl NullifiableNote for AddressNote { ) } - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, 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( 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 a76a1a4c64cf..9f5579a1fbb7 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 @@ -217,14 +217,16 @@ where /// note_id and the storage_slot) to be converted into bytes, because the aes function /// operates on bytes; not fields. /// NB: The extra `+ 64` bytes is for the note_id and the storage_slot of the note: -fn compute_note_plaintext_for_this_strategy(note: Note) -> [u8; N * 32 + 64] +fn compute_note_plaintext_for_this_strategy( + note: Note, + storage_slot: Field, +) -> [u8; N * 32 + 64] where Note: NoteInterface, { let packed_note = note.pack_content(); let note_header = note.get_header(); - let storage_slot = note_header.storage_slot; let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes(); // TODO(#10952): The following can be reduced to 7 bits @@ -250,6 +252,7 @@ where fn compute_log( context: PrivateContext, note: Note, + storage_slot: Field, recipient: AztecAddress, sender: AztecAddress, ) -> [Field; PRIVATE_LOG_SIZE_IN_FIELDS] @@ -272,7 +275,7 @@ where // Compute the plaintext // ***************************************************************************** - let final_plaintext_bytes = compute_note_plaintext_for_this_strategy(note); + let final_plaintext_bytes = compute_note_plaintext_for_this_strategy(note, storage_slot); // ***************************************************************************** // Convert the plaintext into whatever format the encryption function expects @@ -413,13 +416,14 @@ where unconstrained fn compute_log_unconstrained( context: PrivateContext, note: Note, + storage_slot: Field, recipient: AztecAddress, sender: AztecAddress, ) -> [Field; PRIVATE_LOG_SIZE_IN_FIELDS] where Note: NoteInterface, { - compute_log(context, note, recipient, sender) + compute_log(context, note, storage_slot, recipient, sender) } // This function seems to be affected by the following Noir bug: @@ -436,11 +440,12 @@ where { |e: NoteEmission| { let note = e.note; + let storage_slot = e.storage_slot; assert_note_exists(*context, note); let note_hash_counter = note.get_header().note_hash_counter; - let encrypted_log = compute_log(*context, note, recipient, sender); + let encrypted_log = compute_log(*context, note, storage_slot, recipient, sender); context.emit_raw_note_log(encrypted_log, note_hash_counter); } } @@ -459,6 +464,7 @@ where { |e: NoteEmission| { let note = e.note; + let storage_slot = e.storage_slot; assert_note_exists(*context, note); let note_hash_counter = note.get_header().note_hash_counter; @@ -473,7 +479,8 @@ where // It's important here that we do not // return the log from this function to the app, otherwise it could try to do stuff with it and then that might // be wrong. - let encrypted_log = unsafe { compute_log_unconstrained(*context, note, recipient, sender) }; + let encrypted_log = + unsafe { compute_log_unconstrained(*context, note, storage_slot, recipient, sender) }; context.emit_raw_note_log(encrypted_log, note_hash_counter); } } @@ -496,10 +503,7 @@ mod test { ); // This is an address copied to match the typescript one. let storage_slot = 42; - let note = MockNote::new(1234) - .contract_address(context.this_address()) - .storage_slot(storage_slot) - .build(); + let note = MockNote::new(1234).contract_address(context.this_address()).build(); let contract_address = context.this_address(); // All the values in this test were copied over from `encrypted_log_payload.test.ts` @@ -507,7 +511,7 @@ mod test { 0x10f48cd9eff7ae5b209c557c70de2e657ee79166868676b787e9417e19260e04, ); - let plaintext = super::compute_note_plaintext_for_this_strategy(note); + let plaintext = super::compute_note_plaintext_for_this_strategy(note, storage_slot); let eph_sk = 0x1358d15019d4639393d62b97e1588c095957ce74a1c32d6ec7d62fe6705d9538; let _ = OracleMock::mock("getRandomField").returns(eph_sk).times(1); @@ -527,7 +531,7 @@ mod test { let _ = OracleMock::mock("incrementAppTaggingSecretIndexAsSender").returns(()); - let payload = super::compute_log(context, note, recipient, sender); + let payload = super::compute_log(context, note, storage_slot, recipient, sender); // The following value was generated by `encrypted_log_payload.test.ts` // --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data. 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 87f34e3f150d..f951264c6efc 100644 --- a/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr @@ -7,17 +7,17 @@ use crate::{ }; trait ProveNoteInclusion { - fn prove_note_inclusion(header: BlockHeader, note: Note) + fn prove_note_inclusion(header: BlockHeader, note: Note, storage_slot: Field) where Note: NoteInterface + NullifiableNote; } impl ProveNoteInclusion for BlockHeader { - fn prove_note_inclusion(self, note: Note) + fn prove_note_inclusion(self, note: Note, storage_slot: Field) where Note: NoteInterface + NullifiableNote, { - let note_hash = compute_note_hash_for_nullify(note); + let note_hash = compute_note_hash_for_nullify(note, storage_slot); /// Safety: The witness is only used as a "magical value" that makes the merkle proof below pass. Hence it's safe. let witness = unsafe { 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 1eb5981024bc..1f3c35299f8d 100644 --- a/noir-projects/aztec-nr/aztec/src/history/note_validity.nr +++ b/noir-projects/aztec-nr/aztec/src/history/note_validity.nr @@ -6,6 +6,7 @@ trait ProveNoteValidity { fn prove_note_validity( header: BlockHeader, note: Note, + storage_slot: Field, context: &mut PrivateContext, ) where @@ -13,12 +14,17 @@ trait ProveNoteValidity { } impl ProveNoteValidity for BlockHeader { - fn prove_note_validity(self, note: Note, context: &mut PrivateContext) + fn prove_note_validity( + self, + note: Note, + storage_slot: Field, + context: &mut PrivateContext, + ) where Note: NoteInterface + NullifiableNote, { - self.prove_note_inclusion(note); - self.prove_note_not_nullified(note, context); + self.prove_note_inclusion(note, storage_slot); + self.prove_note_not_nullified(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 cfec177f3765..a7e1a1ace768 100644 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr @@ -42,6 +42,7 @@ trait ProveNoteIsNullified { fn prove_note_is_nullified( header: BlockHeader, note: Note, + storage_slot: Field, context: &mut PrivateContext, ) where @@ -50,11 +51,16 @@ trait ProveNoteIsNullified { impl ProveNoteIsNullified for BlockHeader { // docs:start:prove_note_is_nullified - fn prove_note_is_nullified(self, note: Note, context: &mut PrivateContext) + fn prove_note_is_nullified( + self, + note: Note, + storage_slot: Field, + context: &mut PrivateContext, + ) where Note: NoteInterface + NullifiableNote, { - let nullifier = compute_siloed_nullifier(note, context); + let nullifier = compute_siloed_nullifier(note, storage_slot, context); self.prove_nullifier_inclusion(nullifier); } 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 80d9f2ffaab5..1f444aaa7de5 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 @@ -55,6 +55,7 @@ trait ProveNoteNotNullified { fn prove_note_not_nullified( header: BlockHeader, note: Note, + storage_slot: Field, context: &mut PrivateContext, ) where @@ -63,11 +64,16 @@ trait ProveNoteNotNullified { impl ProveNoteNotNullified for BlockHeader { // docs:start:prove_note_not_nullified - fn prove_note_not_nullified(self, note: Note, context: &mut PrivateContext) + fn prove_note_not_nullified( + self, + note: Note, + storage_slot: Field, + context: &mut PrivateContext, + ) where Note: NoteInterface + NullifiableNote, { - let nullifier = compute_siloed_nullifier(note, context); + let nullifier = compute_siloed_nullifier(note, storage_slot, context); self.prove_nullifier_non_inclusion(nullifier); } diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr index 1a9acb1f9980..9e373bc88012 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/mod.nr @@ -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, note_header, compute_nullifier, packed_note_content) + aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack_content, note_header, compute_nullifier, storage_slot, packed_note_content) } }, ); @@ -147,10 +147,10 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted { let if_statements = if_statements_list.join(quote {}); quote { - let note_header = aztec::prelude::NoteHeader::new(contract_address, nonce, storage_slot); + let note_header = aztec::prelude::NoteHeader::new(contract_address, nonce); $if_statements else { - panic(f"Unknown note type ID") + panic(f"Unknown note type ID: {note_type_id}") } } } else { @@ -184,13 +184,14 @@ comptime fn generate_process_log() -> Quoted { // A typical implementation of the lambda looks something like this: // ``` - // |packed_note_content: BoundedVec, note_header: NoteHeader, note_type_id: Field| { + // |packed_note_content: BoundedVec, note_header: NoteHeader, 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); // dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier( // MyNoteType::unpack_content, // note_header, // true, + // storage_slot, // packed_note_content.storage(), // ) // } else { @@ -234,7 +235,7 @@ comptime fn generate_process_log() -> Quoted { f"Expected note content of length {expected_len} but got {actual_len} for note type id {note_type_id}" ); - aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack_content, note_header, true, packed_note_content.storage()) + aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack_content, note_header, true, storage_slot, packed_note_content.storage()) } }, ); @@ -256,7 +257,7 @@ comptime fn generate_process_log() -> Quoted { unique_note_hashes_in_tx, first_nullifier_in_tx, recipient, - |packed_note_content: BoundedVec, note_header, note_type_id| { + |packed_note_content: BoundedVec, note_header, storage_slot, note_type_id| { let hashes = $if_note_type_id_match_statements else { panic(f"Unknown note type id {note_type_id}") diff --git a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr index d631b8bf7a62..b2ba542a4003 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr @@ -54,7 +54,7 @@ comptime fn get_next_note_type_id() -> Field { /// ... /// } /// -/// fn compute_note_hash(self) -> Field { +/// fn compute_note_hash(self, storage_slot: Field) -> Field { /// ... /// } /// } @@ -123,7 +123,7 @@ comptime fn generate_note_interface( let merged_fields_len = merged_fields.len() + 1; // +1 for the storage slot appended below let new_scalars = new_scalars_list - .push_back(quote { std::hash::from_field_unsafe(self.header.storage_slot) }) + .push_back(quote { std::hash::from_field_unsafe(storage_slot) }) .push_back(quote { std::hash::from_field_unsafe($merged_fields_len) }) .join(quote {,}); @@ -151,7 +151,7 @@ comptime fn generate_note_interface( self.header } - fn compute_note_hash(self) -> Field { + fn compute_note_hash(self, storage_slot: Field) -> Field { $new_aux_vars let point = std::embedded_curve_ops::multi_scalar_mul( [$new_generators], 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 ddbcd80e9de9..7277f5b0b7e8 100644 --- a/noir-projects/aztec-nr/aztec/src/note/discovery/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/note/discovery/mod.nr @@ -37,6 +37,7 @@ pub struct NoteHashesAndNullifier { /// MyNoteType::unpack_content, /// note_header, /// true, +/// storage_slot, /// packed_note_content.storage(), /// ) /// } else { @@ -57,7 +58,7 @@ 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, NoteHeader, Field) -> Option, + compute_note_hash_and_nullifier: fn[Env](BoundedVec, NoteHeader, Field, Field) -> Option, ) { let (storage_slot, note_type_id, packed_note_content) = destructure_log_plaintext(log_plaintext); @@ -68,11 +69,16 @@ pub unconstrained fn do_process_log( |expected_unique_note_hash, i| { let candidate_nonce = compute_note_hash_nonce(first_nullifier_in_tx, i); - let header = NoteHeader::new(context.this_address(), candidate_nonce, storage_slot); + let header = NoteHeader::new(context.this_address(), candidate_nonce); // TODO(#11157): handle failed note_hash_and_nullifier computation - let hashes = - compute_note_hash_and_nullifier(packed_note_content, header, note_type_id).unwrap(); + let hashes = compute_note_hash_and_nullifier( + packed_note_content, + header, + storage_slot, + note_type_id, + ) + .unwrap(); if hashes.unique_note_hash == expected_unique_note_hash { // TODO(#10726): push these into a vec to deliver all at once instead of having one oracle call per note diff --git a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr index 2359e7a9c64f..652e2709ae4c 100644 --- a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr +++ b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr @@ -18,9 +18,9 @@ where let contract_address = (*context).this_address(); let note_hash_counter = context.side_effect_counter; - let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; + let header = NoteHeader { contract_address, nonce: 0, note_hash_counter }; note.set_header(header); - let note_hash = note.compute_note_hash(); + let note_hash = note.compute_note_hash(storage_slot); let packed_note_content = Note::pack_content(*note); notify_created_note( @@ -33,7 +33,7 @@ where context.push_note_hash(note_hash); - NoteEmission::new(*note) + NoteEmission::new(*note, storage_slot) } pub fn create_note_hash_from_public( @@ -46,19 +46,19 @@ where { let contract_address = (*context).this_address(); // Public note hashes are transient, but have no side effect counters, so we just need note_hash_counter != 0 - let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter: 1 }; + let header = NoteHeader { contract_address, nonce: 0, note_hash_counter: 1 }; note.set_header(header); - let note_hash = note.compute_note_hash(); + let note_hash = note.compute_note_hash(storage_slot); context.push_note_hash(note_hash); } // Note: This function is currently totally unused. -pub fn destroy_note(context: &mut PrivateContext, note: Note) +pub fn destroy_note(context: &mut PrivateContext, note: Note, storage_slot: Field) where Note: NoteInterface + NullifiableNote, { - let note_hash_for_read_request = compute_note_hash_for_read_request(note); + let note_hash_for_read_request = compute_note_hash_for_read_request(note, storage_slot); destroy_note_unsafe(context, note, note_hash_for_read_request) } diff --git a/noir-projects/aztec-nr/aztec/src/note/note_emission.nr b/noir-projects/aztec-nr/aztec/src/note/note_emission.nr index d6ac26bc2a97..6e2bdf8c07d6 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_emission.nr @@ -4,11 +4,12 @@ */ pub struct NoteEmission { pub note: Note, + pub storage_slot: Field, } impl NoteEmission { - pub fn new(note: Note) -> Self { - Self { note } + pub fn new(note: Note, storage_slot: Field) -> Self { + Self { note, storage_slot } } pub fn emit(self, _emit: fn[Env](Self) -> ()) { 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 560969ca1986..973276fd2c5f 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 @@ -37,14 +37,13 @@ fn extract_property_value_from_selector( value_field } -fn check_note_header(context: PrivateContext, storage_slot: Field, note: Note) +fn check_note_header(context: PrivateContext, note: Note) where Note: NoteInterface, { let header = note.get_header(); let contract_address = context.this_address(); assert(header.contract_address.eq(contract_address), "Mismatch note header contract address."); - assert(header.storage_slot == storage_slot, "Mismatch note header storage slot."); } fn check_note_content( @@ -92,9 +91,9 @@ where /// 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. let note = unsafe { get_note_internal(storage_slot) }; - check_note_header(*context, storage_slot, note); + check_note_header(*context, note); - let note_hash_for_read_request = compute_note_hash_for_read_request(note); + let note_hash_for_read_request = compute_note_hash_for_read_request(note, storage_slot); context.push_note_hash_read_request(note_hash_for_read_request); (note, note_hash_for_read_request) @@ -155,16 +154,14 @@ where if i < notes.len() { let note = notes.get_unchecked(i); let fields = note.pack_content(); - check_note_header(*context, storage_slot, note); + check_note_header(*context, note); check_note_content(fields, options.selects); if i != 0 { check_notes_order(prev_fields, fields, options.sorts); } prev_fields = fields; - let note_hash_for_read_request = compute_note_hash_for_read_request(note); - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1410): test to ensure - // failure if malicious oracle injects 0 nonce here for a "pre-existing" note. + let note_hash_for_read_request = compute_note_hash_for_read_request(note, storage_slot); context.push_note_hash_read_request(note_hash_for_read_request); note_hashes.push(note_hash_for_read_request); }; diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 48728210d4b8..5f09bc08e488 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -20,7 +20,7 @@ unconstrained fn setup() -> TestEnvironment { } unconstrained fn build_valid_note(value: Field) -> MockNote { - MockNote::new(value).contract_address(get_contract_address()).storage_slot(storage_slot).build() + MockNote::new(value).contract_address(get_contract_address()).build() } unconstrained fn assert_equivalent_vec_and_array( @@ -173,20 +173,7 @@ unconstrained fn rejects_mismatched_address() { let mut env = setup(); let mut context = env.private(); - let note = MockNote::new(1).storage_slot(storage_slot).build(); // We're not setting the right contract address - let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; - opt_notes[0] = Option::some(note); - - let mut options = NoteGetterOptions::new(); - let _ = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); -} - -#[test(should_fail_with = "Mismatch note header storage slot.")] -unconstrained fn rejects_mismatched_storage_slot() { - let mut env = setup(); - let mut context = env.private(); - - let note = MockNote::new(1).contract_address(get_contract_address()).build(); // We're not setting the right storage slot + let note = MockNote::new(1).build(); // We're not setting the right contract address let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; opt_notes[0] = Option::some(note); diff --git a/noir-projects/aztec-nr/aztec/src/note/note_header.nr b/noir-projects/aztec-nr/aztec/src/note/note_header.nr index b7cf2efed889..ab220402d175 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_header.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_header.nr @@ -5,23 +5,17 @@ use std::meta::derive; pub struct NoteHeader { pub contract_address: AztecAddress, pub nonce: Field, - pub storage_slot: Field, pub note_hash_counter: u32, // a note_hash_counter of 0 means non-transient } impl Empty for NoteHeader { fn empty() -> Self { - NoteHeader { - contract_address: AztecAddress::zero(), - nonce: 0, - storage_slot: 0, - note_hash_counter: 0, - } + NoteHeader { contract_address: AztecAddress::zero(), nonce: 0, note_hash_counter: 0 } } } impl NoteHeader { - pub fn new(contract_address: AztecAddress, nonce: Field, storage_slot: Field) -> Self { - NoteHeader { contract_address, nonce, storage_slot, note_hash_counter: 0 } + pub fn new(contract_address: AztecAddress, nonce: Field) -> Self { + NoteHeader { contract_address, nonce, note_hash_counter: 0 } } } 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 94ed825f9863..9dd14ead0d92 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr @@ -32,7 +32,7 @@ pub trait NullifiableNote { /// Same as compute_nullifier, but unconstrained. This version does not take a note hash because it'll only be /// invoked in unconstrained contexts, where there is no gate count. - unconstrained fn compute_nullifier_without_context(self) -> Field; + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field; } // docs:start:note_interface @@ -53,6 +53,6 @@ pub trait NoteInterface { /// /// This should be a commitment to the note contents, including the storage slot (for indexing) and some random /// value (to prevent brute force trial-hashing attacks). - fn compute_note_hash(self) -> Field; + fn compute_note_hash(self, storage_slot: Field) -> Field; } // docs:end:note_interface diff --git a/noir-projects/aztec-nr/aztec/src/note/utils.nr b/noir-projects/aztec-nr/aztec/src/note/utils.nr index 730540ec0210..99b666e7df14 100644 --- a/noir-projects/aztec-nr/aztec/src/note/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/note/utils.nr @@ -11,24 +11,28 @@ use dep::protocol_types::hash::{ pub fn compute_siloed_nullifier( note_with_header: Note, + storage_slot: Field, context: &mut PrivateContext, ) -> Field where Note: NoteInterface + NullifiableNote, { let header = note_with_header.get_header(); - let note_hash_for_nullify = compute_note_hash_for_nullify(note_with_header); + let note_hash_for_nullify = compute_note_hash_for_nullify(note_with_header, storage_slot); let inner_nullifier = note_with_header.compute_nullifier(context, note_hash_for_nullify); compute_siloed_nullifier_from_preimage(header.contract_address, inner_nullifier) } // TODO(#7775): make this not impossible to understand -pub fn compute_note_hash_for_read_request(note: Note) -> Field +pub fn compute_note_hash_for_read_request( + note: Note, + storage_slot: Field, +) -> Field where Note: NoteInterface + NullifiableNote, { - let note_hash = note.compute_note_hash(); + let note_hash = note.compute_note_hash(storage_slot); let header = note.get_header(); let nonce = header.nonce; let counter = header.note_hash_counter; @@ -108,11 +112,11 @@ where // } // } -pub fn compute_note_hash_for_nullify(note: Note) -> Field +pub fn compute_note_hash_for_nullify(note: Note, storage_slot: Field) -> Field where Note: NoteInterface + NullifiableNote, { - let note_hash_for_read_request = compute_note_hash_for_read_request(note); + let note_hash_for_read_request = compute_note_hash_for_read_request(note, storage_slot); compute_note_hash_for_nullify_internal(note, note_hash_for_read_request) } @@ -120,6 +124,7 @@ pub unconstrained fn compute_note_hash_and_optionally_a_nullifier T, note_header: NoteHeader, compute_nullifier: bool, + storage_slot: Field, packed_note_content: [Field; S], ) -> [Field; 4] where @@ -128,12 +133,12 @@ where let mut note = unpack_content(array::subarray(packed_note_content, 0)); note.set_header(note_header); - let note_hash = note.compute_note_hash(); + let note_hash = note.compute_note_hash(storage_slot); let siloed_note_hash = compute_siloed_note_hash(note_header.contract_address, note_hash); let unique_note_hash = compute_unique_note_hash(note_header.nonce, siloed_note_hash); let inner_nullifier = if compute_nullifier { - note.compute_nullifier_without_context() + note.compute_nullifier_without_context(storage_slot) } else { 0 }; diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index f1d779c43c49..9270173be839 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -201,7 +201,7 @@ where let note_content = array::subarray(fields, read_offset + 2); let mut note = Note::unpack_content(note_content); - note.set_header(NoteHeader { contract_address, nonce, storage_slot, note_hash_counter }); + note.set_header(NoteHeader { contract_address, nonce, note_hash_counter }); placeholder_opt_notes[i] = Option::some(note); }; diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr index 4eaae18e25ba..6db77ee586bf 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable/test.nr @@ -31,10 +31,7 @@ unconstrained fn test_initialize_or_replace_without_nullifier() { let state_var = in_private(&mut env); let value = 42; - let mut note = MockNote::new(value) - .contract_address(get_contract_address()) - .storage_slot(storage_slot) - .build(); + let mut note = MockNote::new(value).contract_address(get_contract_address()).build(); let _ = OracleMock::mock("checkNullifierExists").returns(0); state_var.initialize_or_replace(&mut note).discard(); 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 97eb7e1d300f..815184f19093 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 @@ -87,7 +87,7 @@ where /// Note that if you obtained the note via `get_notes` it's much better to use `pop_notes` as `pop_notes` results /// in significantly less constrains due to avoiding an extra hash and read request check. pub fn remove(self, note: Note) { - let note_hash = compute_note_hash_for_read_request(note); + let note_hash = compute_note_hash_for_read_request(note, self.storage_slot); let has_been_read = self.context.note_hash_read_requests.any(|r: ReadRequest| r.value == note_hash); assert(has_been_read, "Can only remove a note that has been read from the set."); 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 d4101c31cae3..66260cdb64bc 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 @@ -163,9 +163,9 @@ impl TestEnvironment { cheatcodes::set_contract_address(contract_address); let note_hash_counter = cheatcodes::get_side_effects_counter(); - let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; + let header = NoteHeader { contract_address, nonce: 0, note_hash_counter }; note.set_header(header); - let note_hash = note.compute_note_hash(); + let note_hash = note.compute_note_hash(storage_slot); let packed_content = Note::pack_content(*note); notify_created_note( storage_slot, 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 132e8bdd0540..9b3a0fb699c8 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,6 +11,7 @@ use dep::protocol_types::{ address::AztecAddress, constants::{GENERATOR_INDEX__NOTE_HASH, GENERATOR_INDEX__NOTE_NULLIFIER}, hash::poseidon2_hash_with_separator, + utils::arrays::array_concat, }; global MOCK_NOTE_LENGTH: u32 = 1; @@ -34,10 +35,10 @@ impl NullifiableNote for MockNote { ) } - unconstrained fn compute_nullifier_without_context(self) -> Field { + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { // We don't use any kind of secret here since this is only a mock note and having it here would make tests // more cumbersome - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); poseidon2_hash_with_separator( [note_hash_for_nullify], GENERATOR_INDEX__NOTE_NULLIFIER as Field, @@ -67,15 +68,12 @@ impl NoteInterface for MockNote { 76 } - fn compute_note_hash(self: Self) -> Field { - assert( - self.header.storage_slot != 0, - "Storage slot must be set before computing note hash", - ); + fn compute_note_hash(self: 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. - poseidon2_hash_with_separator(self.pack_content(), GENERATOR_INDEX__NOTE_HASH) + let input = array_concat(self.pack_content(), [storage_slot]); + poseidon2_hash_with_separator(input, GENERATOR_INDEX__NOTE_HASH) } } @@ -88,12 +86,11 @@ impl Eq for MockNote { pub(crate) struct MockNoteBuilder { value: Field, contract_address: Option, - storage_slot: Option, } impl MockNoteBuilder { pub(crate) fn new(value: Field) -> Self { - MockNoteBuilder { value, contract_address: Option::none(), storage_slot: Option::none() } + MockNoteBuilder { value, contract_address: Option::none() } } pub(crate) fn contract_address(&mut self, contract_address: AztecAddress) -> &mut Self { @@ -101,11 +98,6 @@ impl MockNoteBuilder { self } - pub(crate) fn storage_slot(&mut self, storage_slot: Field) -> &mut Self { - self.storage_slot = Option::some(storage_slot); - self - } - pub(crate) fn build(self) -> MockNote { let mut header = NoteHeader::empty(); @@ -113,10 +105,6 @@ impl MockNoteBuilder { header.contract_address = self.contract_address.unwrap(); } - if self.storage_slot.is_some() { - header.storage_slot = self.storage_slot.unwrap(); - } - MockNote { value: self.value, header } } } 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 f8ada18c0adf..7abfc3dfccaa 100644 --- a/noir-projects/aztec-nr/uint-note/src/uint_note.nr +++ b/noir-projects/aztec-nr/uint-note/src/uint_note.nr @@ -40,8 +40,8 @@ impl NullifiableNote for UintNote { } // docs:end:nullifier - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, 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( 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 8d5ebe823736..afd952cc4a73 100644 --- a/noir-projects/aztec-nr/value-note/src/value_note.nr +++ b/noir-projects/aztec-nr/value-note/src/value_note.nr @@ -44,8 +44,8 @@ impl NullifiableNote for ValueNote { // docs:end:nullifier - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); let owner_npk_m_hash: Field = get_public_keys(self.owner).npk_m.hash(); let secret = get_nsk_app(owner_npk_m_hash); poseidon2_hash_with_separator( 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 50b75a2e0d9b..ecc48c504af4 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 @@ -33,8 +33,8 @@ impl NullifiableNote for SubscriptionNote { ) } - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); let owner_npk_m_hash: Field = get_public_keys(self.owner).npk_m.hash(); let secret = get_nsk_app(owner_npk_m_hash); poseidon2_hash_with_separator( diff --git a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr index c9219815e8ad..823bf1a24536 100644 --- a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr @@ -28,7 +28,7 @@ pub contract Claim { } #[private] - fn claim(proof_note: UintNote, recipient: AztecAddress) { + fn claim(proof_note: UintNote, note_storage_slot: Field, recipient: AztecAddress) { // 1) Check that the note corresponds to the target contract and belongs to the sender let target_address = storage.target_contract.read(); assert( @@ -39,7 +39,7 @@ pub contract Claim { // 2) Prove that the note hash exists in the note hash tree let header = context.get_block_header(); - header.prove_note_inclusion(proof_note); + header.prove_note_inclusion(proof_note, note_storage_slot); // 3) Compute and emit a nullifier which is unique to the note and this contract to ensure the reward can be // claimed only once with the given note. @@ -48,7 +48,7 @@ pub contract Claim { // the address of a contract it was emitted from. // TODO(#7775): manually computing the hash and passing it to compute_nullifier func is not great as note could // handle it on its own or we could make prove_note_inclusion return note_hash_for_nullify. - let note_hash_for_nullify = compute_note_hash_for_nullify(proof_note); + let note_hash_for_nullify = compute_note_hash_for_nullify(proof_note, note_storage_slot); let nullifier = proof_note.compute_nullifier(&mut context, note_hash_for_nullify); context.push_nullifier(nullifier); diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index efa1471a26be..c371a0f75f16 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -155,13 +155,21 @@ pub contract Counter { let old_note = notes.get(0); - env.private().get_block_header().prove_note_validity(old_note, &mut env.private()); + env.private().get_block_header().prove_note_validity( + old_note, + owner_slot, + &mut env.private(), + ); - destroy_note(&mut env.private(), old_note); + destroy_note(&mut env.private(), old_note, owner_slot); env.advance_block_by(1); - env.private().get_block_header().prove_note_is_nullified(old_note, &mut env.private()); - env.private().get_block_header().prove_note_inclusion(old_note); + env.private().get_block_header().prove_note_is_nullified( + old_note, + owner_slot, + &mut env.private(), + ); + env.private().get_block_header().prove_note_inclusion(old_note, owner_slot); let notes: BoundedVec = view_notes(owner_slot, options); @@ -255,6 +263,10 @@ pub contract Counter { let old_note = notes.get(0); - env.private().get_block_header().prove_note_validity(old_note, &mut env.private()); + env.private().get_block_header().prove_note_validity( + old_note, + owner_slot, + &mut env.private(), + ); } } 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 3bd3c7a3baa7..60ba0840df6a 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 @@ -45,8 +45,8 @@ impl NullifiableNote for CardNote { ) } - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); let owner_npk_m_hash: Field = get_public_keys(self.owner).npk_m.hash(); let secret = get_nsk_app(owner_npk_m_hash); poseidon2_hash_with_separator( 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 ee073b05f47e..2885b65d0a41 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 @@ -2,7 +2,7 @@ use dep::aztec::prelude::{NoteHeader, NoteInterface, NullifiableNote, PrivateCon 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}, + 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 }; @@ -76,11 +76,12 @@ impl NoteInterface for EcdsaPublicKeyNote { self.header = header; } - fn compute_note_hash(self) -> Field { + 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. - poseidon2_hash_with_separator(self.pack_content(), GENERATOR_INDEX__NOTE_HASH) + let inputs = array_concat(self.pack_content(), [storage_slot]); + poseidon2_hash_with_separator(inputs, GENERATOR_INDEX__NOTE_HASH) } } @@ -98,8 +99,8 @@ impl NullifiableNote for EcdsaPublicKeyNote { ) } - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, 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( diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index e6a101e15238..fec16e2f03c8 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -56,6 +56,7 @@ pub contract InclusionProofs { options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); } let note = private_values.get_notes(options).get(0); + let note_storage_slot = private_values.storage_slot; // docs:end:get_note_from_pxe // docs:start:prove_note_inclusion // 2) Prove the note inclusion @@ -65,7 +66,7 @@ pub contract InclusionProofs { context.get_block_header() }; - header.prove_note_inclusion(note); + header.prove_note_inclusion(note, note_storage_slot); // docs:end:prove_note_inclusion } @@ -77,6 +78,7 @@ pub contract InclusionProofs { ) { let header = context.get_block_header(); let mut note = ValueNote::new(1, owner); + let note_storage_slot = 2; let header = if (use_block_number) { context.get_block_header_at(block_number) @@ -84,7 +86,7 @@ pub contract InclusionProofs { context.get_block_header() }; - header.prove_note_inclusion(note); + header.prove_note_inclusion(note, note_storage_slot); } // Proves that the note was not yet nullified at block `block_number`. @@ -106,6 +108,7 @@ pub contract InclusionProofs { options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); } let note = private_values.get_notes(options).get(0); + let note_storage_slot = private_values.storage_slot; let header = if (use_block_number) { context.get_block_header_at(block_number) @@ -113,7 +116,7 @@ pub contract InclusionProofs { context.get_block_header() }; // docs:start:prove_note_not_nullified - header.prove_note_not_nullified(note, &mut context); + header.prove_note_not_nullified(note, note_storage_slot, &mut context); // docs:end:prove_note_not_nullified } @@ -132,6 +135,7 @@ pub contract InclusionProofs { options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); } let note = private_values.get_notes(options).get(0); + let note_storage_slot = private_values.storage_slot; // 2) Prove the note validity let header = if (use_block_number) { @@ -140,7 +144,7 @@ pub contract InclusionProofs { context.get_block_header() }; // docs:start:prove_note_validity - header.prove_note_validity(note, &mut context); + header.prove_note_validity(note, note_storage_slot, &mut context); // docs:end:prove_note_validity } 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 94ad26e5161f..054372b3ab14 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 @@ -38,8 +38,8 @@ impl NullifiableNote for NFTNote { } // docs:end:compute_nullifier - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); let owner_npk_m_hash: Field = get_public_keys(self.owner).npk_m.hash(); let secret = get_nsk_app(owner_npk_m_hash); poseidon2_hash_with_separator( diff --git a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr index 39dbcd9b164b..2a337f2491cf 100644 --- a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr @@ -339,9 +339,15 @@ pub contract PendingNoteHashes { let mut bad_note = ValueNote::new(5, owner); // ...but we need a 'good' note header to get the context to add the note log let existing_note_header = good_note.get_header(); + let existing_note_storage_slot = owner_balance.storage_slot; + bad_note.set_header(existing_note_header); - NoteEmission::new(bad_note).emit(encode_and_encrypt_note(&mut context, owner, sender)); + NoteEmission::new(bad_note, existing_note_storage_slot).emit(encode_and_encrypt_note( + &mut context, + owner, + sender, + )); } #[contract_library_method] 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 cb3f763fc546..e5c81535811f 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 @@ -32,8 +32,8 @@ impl NullifiableNote for PublicKeyNote { ) } - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); let owner_npk_m_hash: Field = get_public_keys(self.owner).npk_m.hash(); let secret = get_nsk_app(owner_npk_m_hash); poseidon2_hash_with_separator( 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 c7c28b8ed5f5..5e9ec5bdfe73 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 @@ -42,8 +42,8 @@ impl NullifiableNote for TokenNote { } // docs:end:nullifier - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); let owner_npk_m_hash: Field = get_public_keys(self.owner).npk_m.hash(); let secret = get_nsk_app(owner_npk_m_hash); poseidon2_hash_with_separator( 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 ebc901dad879..f64169a80a51 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 @@ -26,7 +26,7 @@ impl NullifiableNote for TestNote { 0 } - unconstrained fn compute_nullifier_without_context(_self: Self) -> Field { + unconstrained fn compute_nullifier_without_context(_self: Self, _storage_slot: Field) -> Field { // This note is expected to be shared between users and for this reason can't be nullified using a secret. 0 } diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr index a5b6c1ea08cd..92409d6f9b7a 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr @@ -1,5 +1,9 @@ mod types; +// NOTE: This contract is stale and is kept around only as a reference of how to implement a blacklist. +// Don't take into consideration the interface of this contract. The TransparentNote "shield" flow is +// deprecated and has been replaced with partial notes in the standard token contract. +// // Minimal token implementation that supports `AuthWit` accounts and SharedMutable variables. // The auth message follows a similar pattern to the cross-chain message and includes a designated caller. // The designated caller is ALWAYS used here, and not based on a flag as cross-chain. 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 8ecddfb090fb..bcf0b78d5269 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 @@ -40,8 +40,8 @@ impl NullifiableNote for TokenNote { } // docs:end:nullifier - unconstrained fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); let owner_npk_m_hash: Field = get_public_keys(self.owner).npk_m.hash(); let secret = get_nsk_app(owner_npk_m_hash); poseidon2_hash_with_separator( 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 6008cc616a59..ff3ff97c301c 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 @@ -33,18 +33,18 @@ impl NullifiableNote for TransparentNote { fn compute_nullifier( self, _context: &mut PrivateContext, - _note_hash_for_nullify: Field, + note_hash_for_nullify: Field, ) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); poseidon2_hash_with_separator( [note_hash_for_nullify], GENERATOR_INDEX__NOTE_NULLIFIER as Field, ) } - unconstrained fn compute_nullifier_without_context(self) -> Field { - // compute_nullifier ignores both of its parameters so we can reuse it here - self.compute_nullifier(zeroed(), zeroed()) + unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self, storage_slot); + // compute_nullifier ignores context so we can reuse it here + self.compute_nullifier(zeroed(), note_hash_for_nullify) } } diff --git a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts index 0b7ed8c8f663..2b0382b7e0c0 100644 --- a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts +++ b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts @@ -56,6 +56,7 @@ describe('e2e_crowdfunding_and_claim', () => { let deadline: number; // end of crowdfunding period let uintNote!: any; + let uintNoteSlot!: any; beforeAll(async () => { ({ cheatCodes, teardown, logger, pxe, wallets } = await setup(3)); @@ -128,19 +129,20 @@ describe('e2e_crowdfunding_and_claim', () => { // Processes unique note such that it can be passed to a claim function of Claim contract const processUniqueNote = (uniqueNote: UniqueNote) => { return { - header: { + note: { + header: { + // eslint-disable-next-line camelcase + contract_address: uniqueNote.contractAddress, + // eslint-disable-next-line camelcase + note_hash_counter: 0, // set as 0 as note is not transient + nonce: uniqueNote.nonce, + }, + value: uniqueNote.note.items[0].toBigInt(), // We convert to bigint as Fr is not serializable to U128 // eslint-disable-next-line camelcase - contract_address: uniqueNote.contractAddress, - // eslint-disable-next-line camelcase - storage_slot: uniqueNote.storageSlot, - // eslint-disable-next-line camelcase - note_hash_counter: 0, // set as 0 as note is not transient - nonce: uniqueNote.nonce, + owner: AztecAddress.fromField(uniqueNote.note.items[1]), + randomness: uniqueNote.note.items[2], }, - value: uniqueNote.note.items[0].toBigInt(), // We convert to bigint as Fr is not serializable to U128 - // eslint-disable-next-line camelcase - owner: AztecAddress.fromField(uniqueNote.note.items[1]), - randomness: uniqueNote.note.items[2], + slot: uniqueNote.storageSlot, }; }; @@ -173,7 +175,9 @@ describe('e2e_crowdfunding_and_claim', () => { expect(filteredNotes!.length).toEqual(1); // Set the UintNote in a format which can be passed to claim function - uintNote = processUniqueNote(filteredNotes![0]); + const { note, slot } = processUniqueNote(filteredNotes![0]); + uintNote = note; + uintNoteSlot = slot; } // 3) We claim the reward token via the Claim contract @@ -183,7 +187,7 @@ describe('e2e_crowdfunding_and_claim', () => { await claimContract .withWallet(donorWallets[0]) - .methods.claim(uintNote, donorWallets[0].getAddress()) + .methods.claim(uintNote, uintNoteSlot, donorWallets[0].getAddress()) .send() .wait(); } @@ -213,7 +217,11 @@ describe('e2e_crowdfunding_and_claim', () => { it('cannot claim twice', async () => { // The first claim was executed in the previous test await expect( - claimContract.withWallet(donorWallets[0]).methods.claim(uintNote, donorWallets[0].getAddress()).send().wait(), + claimContract + .withWallet(donorWallets[0]) + .methods.claim(uintNote, uintNoteSlot, donorWallets[0].getAddress()) + .send() + .wait(), ).rejects.toThrow(); }); @@ -248,14 +256,14 @@ describe('e2e_crowdfunding_and_claim', () => { expect(filtered!.length).toEqual(1); // Set the UintNote in a format which can be passed to claim function - const anotherDonationNote = processUniqueNote(filtered![0]); + const { note: anotherDonationNote, slot: anotherDonationNoteSlot } = processUniqueNote(filtered![0]); // 3) We try to claim the reward token via the Claim contract with the unrelated wallet { await expect( claimContract .withWallet(unrelatedWallet) - .methods.claim(anotherDonationNote, donorWallet.getAddress()) + .methods.claim(anotherDonationNote, anotherDonationNoteSlot, donorWallet.getAddress()) .send() .wait(), ).rejects.toThrow('Note does not belong to the sender'); @@ -270,7 +278,7 @@ describe('e2e_crowdfunding_and_claim', () => { await expect( claimContract .withWallet(donorWallets[0]) - .methods.claim(nonExistentNote, donorWallets[0].getAddress()) + .methods.claim(nonExistentNote, uintNoteSlot, donorWallets[0].getAddress()) .send() .wait(), ).rejects.toThrow(); @@ -284,12 +292,15 @@ describe('e2e_crowdfunding_and_claim', () => { // 2) Create a note let note: any; + let noteSlot: any; { const receipt = await inclusionsProofsContract.methods.create_note(owner, 5n).send().wait({ debug: true }); await inclusionsProofsContract.methods.sync_notes().simulate(); const notes = await wallets[0].getNotes({ txHash: receipt.txHash }); expect(notes.length).toEqual(1); - note = processUniqueNote(notes[0]); + const { note: processedNote, slot } = processUniqueNote(notes[0]); + note = processedNote; + noteSlot = slot; } // 3) Test the note was included @@ -297,7 +308,11 @@ describe('e2e_crowdfunding_and_claim', () => { // 4) Finally, check that the claim process fails await expect( - claimContract.withWallet(donorWallets[0]).methods.claim(note, donorWallets[0].getAddress()).send().wait(), + claimContract + .withWallet(donorWallets[0]) + .methods.claim(note, noteSlot, donorWallets[0].getAddress()) + .send() + .wait(), ).rejects.toThrow(); });