diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/custom_note.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/custom_note.md index 32edb7d5340..73939944a88 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/custom_note.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/custom_note.md @@ -25,11 +25,11 @@ You will need to implement a note interface for your note. Most of this is autom #include_code note_interface noir-projects/aztec-nr/aztec/src/note/note_interface.nr rust -You will need to implement the functions `compute_note_hash_and_nullifier()` and `compute_note_hash_and_nullifier_without_context()` which tells Aztec how to compute nullifiers for your note. +You will need to implement the functions `compute_nullifier(...)` and `compute_nullifier_without_context()` which tells Aztec how to compute nullifiers for your note. #include_code note_interface noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr rust -In this example, these functions compute the note hash by using `compute_note_hash_for_consumption` and then generate a nullifier by hashing this note hash together with a secret. +In this example, these functions compute the note hash by using `compute_note_hash_for_nullify` and then generate a nullifier by hashing this note hash together with a secret. ### Methods diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index 6b12658a854..6d364a430a6 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -8,6 +8,57 @@ Aztec is in full-speed development. Literally every version breaks compatibility ## 0.48.0 +### NoteInterface changes + +`compute_note_hash_and_nullifier*` functions were renamed as `compute_nullifier*` and the `compute_nullifier` function now takes `note_hash_for_nullify` as an argument (this allowed us to reduce gate counts and the hash was typically computed before). Also `compute_note_hash_for_consumption` function was renamed as `compute_note_hash_for_nullify`. + +```diff +impl NoteInterface for ValueNote { +- fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { +- let note_hash_for_nullify = compute_note_hash_for_consumption(self); +- let secret = context.request_nsk_app(self.npk_m_hash); +- let nullifier = poseidon2_hash_with_separator([ +- note_hash_for_nullify, +- secret, +- ], +- GENERATOR_INDEX__NOTE_NULLIFIER as Field, +- ); +- (note_hash_for_nullify, nullifier) +- } +- fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { +- let note_hash_for_nullify = compute_note_hash_for_consumption(self); +- let secret = get_nsk_app(self.npk_m_hash); +- let nullifier = poseidon2_hash_with_separator([ +- note_hash_for_nullify, +- secret, +- ], +- GENERATOR_INDEX__NOTE_NULLIFIER as Field, +- ); +- (note_hash_for_nullify, nullifier) +- } + ++ fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { ++ let secret = context.request_nsk_app(self.npk_m_hash); ++ poseidon2_hash_with_separator([ ++ note_hash_for_nullify, ++ secret ++ ], ++ GENERATOR_INDEX__NOTE_NULLIFIER as Field, ++ ) ++ } ++ fn compute_nullifier_without_context(self) -> Field { ++ let note_hash_for_nullify = compute_note_hash_for_nullify(self); ++ let secret = get_nsk_app(self.npk_m_hash); ++ poseidon2_hash_with_separator([ ++ note_hash_for_nullify, ++ secret, ++ ], ++ GENERATOR_INDEX__NOTE_NULLIFIER as Field, ++ ) ++ } +} +``` + ### Fee Juice rename The name of the canonical Gas contract has changed to Fee Juice. Update noir code: 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 cde76600727..d8bbbcbfdeb 100644 --- a/noir-projects/aztec-nr/address-note/src/address_note.nr +++ b/noir-projects/aztec-nr/address-note/src/address_note.nr @@ -3,7 +3,7 @@ use dep::aztec::{ address::AztecAddress, traits::Empty, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator }, - note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption}, + note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_nullify}, oracle::unsafe_rand::unsafe_rand, keys::getters::get_nsk_app, context::PrivateContext }; @@ -25,28 +25,25 @@ struct AddressNote { impl NoteInterface for AddressNote { - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } } diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr index ab96b9d36bc..9ad6eb71f0c 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr @@ -71,11 +71,13 @@ mod test { fn set_header(&mut self, header: NoteHeader) {self.header = header; } - fn compute_note_hash_and_nullifier(_self: Self, _context: &mut PrivateContext) -> (Field, Field) { - (1, 1) + fn compute_nullifier(_self: Self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { + 1 } - fn compute_note_hash_and_nullifier_without_context(_self: Self) -> (Field, Field) {(1,1)} + fn compute_nullifier_without_context(_self: Self) -> Field { + 1 + } fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN] { [self.address.to_field(), self.owner.to_field(), self.randomness]} 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 5f6b6b53a58..d5779411b6b 100644 --- a/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr @@ -2,7 +2,7 @@ use dep::protocol_types::merkle_tree::root::root_from_sibling_path; use dep::protocol_types::header::Header; use crate::{ - note::{utils::compute_note_hash_for_consumption, note_interface::NoteInterface}, + note::{utils::compute_note_hash_for_nullify, note_interface::NoteInterface}, oracle::get_membership_witness::get_note_hash_membership_witness }; @@ -13,7 +13,7 @@ trait ProveNoteInclusion { impl ProveNoteInclusion for Header { fn prove_note_inclusion(self, note: Note) where Note: NoteInterface { // 1) Compute note_hash - let note_hash = compute_note_hash_for_consumption(note); + let note_hash = compute_note_hash_for_nullify(note); // 2) Get the membership witness of the note in the note hash tree let witness = get_note_hash_membership_witness(self.global_variables.block_number as u32, note_hash); diff --git a/noir-projects/aztec-nr/aztec/src/initializer.nr b/noir-projects/aztec-nr/aztec/src/initializer.nr index dc612afc342..1be907c5ce9 100644 --- a/noir-projects/aztec-nr/aztec/src/initializer.nr +++ b/noir-projects/aztec-nr/aztec/src/initializer.nr @@ -1,6 +1,6 @@ use dep::protocol_types::{ - address::AztecAddress, hash::{compute_siloed_nullifier, poseidon2_hash_with_separator}, - constants::GENERATOR_INDEX__CONSTRUCTOR, abis::function_selector::FunctionSelector + address::AztecAddress, hash::poseidon2_hash_with_separator, constants::GENERATOR_INDEX__CONSTRUCTOR, + abis::function_selector::FunctionSelector }; use crate::{ diff --git a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr index 5bcabb1784e..5253b37ddd2 100644 --- a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr +++ b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr @@ -1,6 +1,7 @@ use crate::context::{PrivateContext, PublicContext}; use crate::note::{ - note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption, + note_header::NoteHeader, note_interface::NoteInterface, + utils::{compute_note_hash_for_read_request, compute_note_hash_for_nullify_internal}, note_emission::NoteEmission }; use crate::oracle::notes::{notify_created_note, notify_nullified_note}; @@ -50,14 +51,26 @@ pub fn create_note_hash_from_public( context.push_note_hash(note_hash); } +// Note: This function is currently totally unused. pub fn destroy_note( context: &mut PrivateContext, note: Note ) where Note: NoteInterface { - let (note_hash, nullifier) = note.compute_note_hash_and_nullifier(context); + let note_hash_for_read_request = compute_note_hash_for_read_request(note); + + destroy_note_unsafe(context, note, note_hash_for_read_request) +} + +pub fn destroy_note_unsafe( + context: &mut PrivateContext, + note: Note, + note_hash_for_read_request: Field +) where Note: NoteInterface { + let note_hash_for_nullify = compute_note_hash_for_nullify_internal(note, note_hash_for_read_request); + let nullifier = note.compute_nullifier(context, note_hash_for_nullify); let note_hash_counter = note.get_header().note_hash_counter; - let note_hash_for_consumption = if (note_hash_counter == 0) { + let notification_note_hash = if (note_hash_counter == 0) { // Counter is zero, so we're nullifying a settled note and we don't populate the note_hash with real value. 0 } else { @@ -66,11 +79,12 @@ pub fn destroy_note( // hash with real value to inform the kernel which note we're nullifyng so that it can either squash both // the note and the nullifier if it's an inner note hash, or check that the it matches a pending note if it's // a siloed note hash. - note_hash + note_hash_for_nullify }; let nullifier_counter = context.side_effect_counter; - assert(notify_nullified_note(nullifier, note_hash_for_consumption, nullifier_counter) == 0); + assert(notify_nullified_note(nullifier, notification_note_hash, nullifier_counter) == 0); - context.push_nullifier_for_note_hash(nullifier, note_hash_for_consumption) + context.push_nullifier_for_note_hash(nullifier, notification_note_hash) } + 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 f806dae9d27..576d05a0885 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 @@ -92,7 +92,7 @@ fn check_notes_order( pub fn get_note( context: &mut PrivateContext, storage_slot: Field -) -> Note where Note: NoteInterface { +) -> (Note, Field) where Note: NoteInterface { let note = get_note_internal(storage_slot); check_note_header(*context, storage_slot, note); @@ -100,14 +100,14 @@ pub fn get_note( let note_hash_for_read_request = compute_note_hash_for_read_request(note); context.push_note_hash_read_request(note_hash_for_read_request); - note + (note, note_hash_for_read_request) } pub fn get_notes( context: &mut PrivateContext, storage_slot: Field, options: NoteGetterOptions -) -> BoundedVec where Note: NoteInterface + Eq { +) -> (BoundedVec, BoundedVec) where Note: NoteInterface + Eq { let opt_notes = get_notes_internal(storage_slot, options); constrain_get_notes_internal(context, storage_slot, opt_notes, options) @@ -126,7 +126,7 @@ fn constrain_get_notes_internal; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], options: NoteGetterOptions -) -> BoundedVec where Note: NoteInterface + Eq { +) -> (BoundedVec, BoundedVec) where Note: NoteInterface + Eq { // 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 @@ -136,6 +136,7 @@ fn constrain_get_notes_internal = BoundedVec::new(); // We have now collapsed the sparse array of Options into a BoundedVec. This is a more ergonomic type and also // results in reduced gate counts when setting a limit value, since we guarantee that the limit is an upper bound @@ -158,10 +159,11 @@ fn constrain_get_notes_internal(storage_slot: Field) -> Note where Note: NoteInterface { 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 52ba87c15c1..0f8c81cf66f 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 @@ -47,7 +47,7 @@ fn processes_single_note() { notes_to_constrain[0] = Option::some(build_valid_note(13)); let options = NoteGetterOptions::new(); - let returned = constrain_get_notes_internal(&mut context, storage_slot, notes_to_constrain, options); + let (returned, _) = constrain_get_notes_internal(&mut context, storage_slot, notes_to_constrain, options); assert_equivalent_vec_and_array(returned, notes_to_constrain); assert_eq(context.note_hash_read_requests.len(), 1); @@ -63,7 +63,7 @@ fn processes_many_notes() { notes_to_constrain[1] = Option::some(build_valid_note(19)); let options = NoteGetterOptions::new(); - let returned = constrain_get_notes_internal(&mut context, storage_slot, notes_to_constrain, options); + let (returned, _) = constrain_get_notes_internal(&mut context, storage_slot, notes_to_constrain, options); assert_equivalent_vec_and_array(returned, notes_to_constrain); assert_eq(context.note_hash_read_requests.len(), 2); @@ -83,7 +83,7 @@ fn collapses_notes_at_the_beginning_of_the_array() { opt_notes[13] = Option::some(build_valid_note(5)); let options = NoteGetterOptions::new(); - let returned = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); + let (returned, _) = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); let mut expected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; expected[0] = Option::some(build_valid_note(0)); @@ -103,7 +103,7 @@ fn can_return_zero_notes() { let opt_notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let options = NoteGetterOptions::new(); - let returned = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); + let (returned, _) = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); assert_eq(returned.len(), 0); } @@ -145,7 +145,7 @@ fn applies_filter_before_constraining() { }; let options = NoteGetterOptions::with_filter(filter_fn, ()); - let returned = constrain_get_notes_internal(&mut context, storage_slot, notes_to_constrain, options); + let (returned, _) = constrain_get_notes_internal(&mut context, storage_slot, notes_to_constrain, options); // Only the note with value 42 should be returned, and moved to the beginning of the array. The other notes were not // constrained, and hence validation did not fail. 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 53afa05820c..7631d30da12 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr @@ -4,9 +4,16 @@ use dep::protocol_types::point::Point; // docs:start:note_interface trait NoteInterface { - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field); - - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field); + // This function MUST be called with the correct note hash for consumption! It will otherwise silently fail and + // compute an incorrect value. + // The reason why we receive this as an argument instead of computing it ourselves directly is because the + // caller will typically already have computed this note hash, and we can reuse that value to reduce the total + // gate count of the circuit. + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field; + + // Unlike compute_nullifier, this function does not take a note hash since it'll only be invoked in unconstrained + // contexts, where there is no gate count. + fn compute_nullifier_without_context(self) -> Field; // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation fn serialize_content(self) -> [Field; N]; diff --git a/noir-projects/aztec-nr/aztec/src/note/utils.nr b/noir-projects/aztec-nr/aztec/src/note/utils.nr index 8bce7674865..dcc45135411 100644 --- a/noir-projects/aztec-nr/aztec/src/note/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/note/utils.nr @@ -17,11 +17,13 @@ pub fn compute_siloed_nullifier( context: &mut PrivateContext ) -> Field where Note: NoteInterface { let header = note_with_header.get_header(); - let (_, inner_nullifier) = note_with_header.compute_note_hash_and_nullifier(context); + let note_hash_for_nullify = compute_note_hash_for_nullify(note_with_header); + 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 where Note: NoteInterface { // TODO(#7771): inject compute_note_hash(...) func to notes with macros. let note_hash = note.compute_note_hiding_point().x; @@ -35,48 +37,77 @@ pub fn compute_note_hash_for_read_request(note: No } } -pub fn compute_note_hash_for_consumption(note: Note) -> Field where Note: NoteInterface { +// TODO(#7775): make this not impossible to understand +pub fn compute_note_hash_for_nullify_internal( + note: Note, + note_hash_for_read_request: Field +) -> 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 - // the same transaction: (note_hash_counter != 0) & (nonce == 0) - // 2. The note was inserted in this transaction, is non-revertible, and is nullified by a revertible nullifier in - // the same transaction: (note_hash_counter != 0) & (nonce != 0) - // 3. The note was inserted in a previous transaction: (note_hash_counter == 0) & (nonce != 0) - - // TODO(#7771): inject compute_note_hash(...) func to notes with macros. - let note_hash = note.compute_note_hiding_point().x; - if header.nonce == 0 { - // Case 1. - // If a note is transient, we just read the note_hash (kernel will hash it with nonce and silo by contract address). - note_hash + if header.note_hash_counter != 0 { + if header.nonce == 0 { + // Case 1: Transient note + note_hash_for_read_request + } else { + // Case 2: Non-revertible note, nullified by a revertible nullifier + let unique_note_hash = compute_unique_note_hash(header.nonce, note_hash_for_read_request); + compute_siloed_note_hash(header.contract_address, unique_note_hash) + } } else { - // Case 2: If a note is non-revertible, and is nullified by a revertible nullifier, we cannot squash them in the - // private reset circuit. Because if the tx reverts, we will have to keep the note hash and throw away the - // nullifier. - // And if the tx does not revert, both will be emitted. In which case, the nullifier must be created in the app - // from the siloed note hash. - // The kernel circuit will check that a nullifier with non-zero note_nonce is linked to a note hash, whose - // siloed note hash matches the note hash specified in the nullifier. - - // Case 3: If a note is not from the current transaction, that means we are reading a settled note (from - // tree) created in a previous TX. So we need the siloed_note_hash which has already been hashed with - // nonce and then contract address. This hash will match the existing leaf in the note hash - // tree, so the kernel can just perform a membership check directly on this hash/leaf. - let unique_note_hash = compute_unique_note_hash(header.nonce, note_hash); - compute_siloed_note_hash(header.contract_address, unique_note_hash) - // IMPORTANT NOTE ON REDUNDANT SILOING BY CONTRACT ADDRESS: The note hash computed above is - // "siloed" by contract address. When a note hash is computed solely for the purpose of - // nullification, it is not strictly necessary to silo the note hash before computing - // its nullifier. In other words, it is NOT NECESSARY for protocol security that a nullifier - // be computed from a siloed note hash. After all, persistable note hashes and nullifiers are - // siloed by the kernel circuit. That being said, the siloed note hash computed above CAN be - // used for nullifier computation, and this achieves the (arguably unnecessary) property that - // nullifiers are computed from a note hash's fully-computed note hash tree leaf. + // Case 3: Note from a previous transaction + // note_hash_for_read_request is already the unique_note_hash in this case + compute_siloed_note_hash(header.contract_address, note_hash_for_read_request) } } +// 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 { +// 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 +// // the same transaction: (note_hash_counter != 0) & (nonce == 0) +// // 2. The note was inserted in this transaction, is non-revertible, and is nullified by a revertible nullifier in +// // the same transaction: (note_hash_counter != 0) & (nonce != 0) +// // 3. The note was inserted in a previous transaction: (note_hash_counter == 0) & (nonce != 0) + +// // TODO(#7771): inject compute_note_hash(...) func to notes with macros. +// let note_hash = note.compute_note_hiding_point().x; + +// if header.nonce == 0 { +// // Case 1. +// // If a note is transient, we just read the note_hash (kernel will hash it with nonce and silo by contract address). +// note_hash +// } else { +// // Case 2: If a note is non-revertible, and is nullified by a revertible nullifier, we cannot squash them in the +// // private reset circuit. Because if the tx reverts, we will have to keep the note hash and throw away the +// // nullifier. +// // And if the tx does not revert, both will be emitted. In which case, the nullifier must be created in the app +// // from the siloed note hash. +// // The kernel circuit will check that a nullifier with non-zero note_nonce is linked to a note hash, whose +// // siloed note hash matches the note hash specified in the nullifier. + +// // Case 3: If a note is not from the current transaction, that means we are reading a settled note (from +// // tree) created in a previous TX. So we need the siloed_note_hash which has already been hashed with +// // nonce and then contract address. This hash will match the existing leaf in the note hash +// // tree, so the kernel can just perform a membership check directly on this hash/leaf. +// let unique_note_hash = compute_unique_note_hash(header.nonce, note_hash); +// compute_siloed_note_hash(header.contract_address, unique_note_hash) +// // IMPORTANT NOTE ON REDUNDANT SILOING BY CONTRACT ADDRESS: The note hash computed above is +// // "siloed" by contract address. When a note hash is computed solely for the purpose of +// // nullification, it is not strictly necessary to silo the note hash before computing +// // its nullifier. In other words, it is NOT NECESSARY for protocol security that a nullifier +// // be computed from a siloed note hash. After all, persistable note hashes and nullifiers are +// // siloed by the kernel circuit. That being said, the siloed note hash computed above CAN be +// // used for nullifier computation, and this achieves the (arguably unnecessary) property that +// // nullifiers are computed from a note hash's fully-computed note hash tree leaf. +// } +// } + +pub fn compute_note_hash_for_nullify(note: Note) -> Field where Note: NoteInterface { + let note_hash_for_read_request = compute_note_hash_for_read_request(note); + compute_note_hash_for_nullify_internal(note, note_hash_for_read_request) +} + pub fn compute_note_hash_and_optionally_a_nullifier( deserialize_content: fn([Field; N]) -> T, note_header: NoteHeader, @@ -92,8 +123,7 @@ pub fn compute_note_hash_and_optionally_a_nullifier PrivateImmutable { // docs:start:get_note pub fn get_note(self) -> Note where Note: NoteInterface { let storage_slot = self.storage_slot; - get_note(self.context, storage_slot) + get_note(self.context, storage_slot).0 } // docs:end:get_note } 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 2a4ba83e383..b054ae17c8b 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 @@ -5,7 +5,7 @@ use dep::protocol_types::{ use crate::context::{PrivateContext, UnconstrainedContext}; use crate::note::{ - lifecycle::{create_note, destroy_note}, note_getter::{get_note, view_notes}, + lifecycle::{create_note, destroy_note_unsafe}, note_getter::{get_note, view_notes}, note_interface::NoteInterface, note_viewer_options::NoteViewerOptions, note_emission::NoteEmission }; use crate::oracle::notes::check_nullifier_exists; @@ -36,8 +36,8 @@ impl PrivateMutable { // Under such circumstances, such application developers might wish to _not_ use this state variable type. // This is especially dangerous for initial assignment to elements of a `Map` type (for example), because the storage slot often also identifies an actor. e.g. // the initial assignment to `my_map.at(msg.sender)` will leak: `msg.sender`, the fact that an element of `my_map` was assigned-to for the first time, and the contract_address. - // Note: subsequent nullification of this state variable, via the `replace` method will not be leaky, if the `compute_note_hash_and_nullifier()` method of the underlying note is designed to ensure privacy. - // For example, if the `compute_note_hash_and_nullifier()` method injects the secret key of a note owner into the computed nullifier's preimage. + // Note: subsequent nullification of this state variable, via the `replace` method will not be leaky, if the `compute_nullifier()` method of the underlying note is designed to ensure privacy. + // For example, if the `compute_nullifier()` method injects the secret key of a note owner into the computed nullifier's preimage. pub fn compute_initialization_nullifier(self) -> Field { poseidon2_hash_with_separator( [self.storage_slot], @@ -59,10 +59,10 @@ impl PrivateMutable where Note: NoteInter // docs:start:replace pub fn replace(self, new_note: &mut Note) -> NoteEmission { - let prev_note: Note = get_note(self.context, self.storage_slot); + let (prev_note, note_hash_for_read_request): (Note, Field) = get_note(self.context, self.storage_slot); // Nullify previous note. - destroy_note(self.context, prev_note); + destroy_note_unsafe(self.context, prev_note, note_hash_for_read_request); // Add replacement note. create_note(self.context, self.storage_slot, new_note) @@ -91,10 +91,10 @@ impl PrivateMutable where Note: NoteInter // docs:start:get_note pub fn get_note(self) -> NoteEmission { - let mut note = get_note(self.context, self.storage_slot); + let mut (note, note_hash_for_read_request) = get_note(self.context, self.storage_slot); // Nullify current note to make sure it's reading the latest note. - destroy_note(self.context, note); + destroy_note_unsafe(self.context, note, note_hash_for_read_request); // Add the same note again. // Because a nonce is added to every note in the kernel, its nullifier will be different. 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 9051cb86f85..24b0a1b39c7 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 @@ -1,7 +1,8 @@ use dep::protocol_types::{constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, abis::read_request::ReadRequest}; use crate::context::{PrivateContext, PublicContext, UnconstrainedContext}; use crate::note::{ - constants::MAX_NOTES_PER_PAGE, lifecycle::{create_note, create_note_hash_from_public, destroy_note}, + constants::MAX_NOTES_PER_PAGE, + lifecycle::{create_note, create_note_hash_from_public, destroy_note_unsafe}, note_getter::{get_notes, view_notes}, note_getter_options::NoteGetterOptions, note_header::NoteHeader, note_interface::NoteInterface, note_viewer_options::NoteViewerOptions, utils::compute_note_hash_for_read_request, note_emission::NoteEmission @@ -45,16 +46,17 @@ impl PrivateSet where N self, options: NoteGetterOptions ) -> BoundedVec { - let notes = get_notes(self.context, self.storage_slot, options); + let (notes, note_hashes) = get_notes(self.context, self.storage_slot, options); // We iterate in a range 0..options.limit instead of 0..notes.len() because options.limit is known at compile // time and hence will result in less constraints when set to a lower value than // MAX_NOTE_HASH_READ_REQUESTS_PER_CALL. for i in 0..options.limit { if i < notes.len() { let note = notes.get_unchecked(i); + let note_hash = note_hashes.get_unchecked(i); // We immediately destroy the note without doing any of the read request checks `remove` typically // performs because we know that the `get_notes` call has already placed those constraints. - destroy_note(self.context, note); + destroy_note_unsafe(self.context, note, note_hash); } } @@ -68,7 +70,7 @@ impl PrivateSet where N 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."); - destroy_note(self.context, note); + destroy_note_unsafe(self.context, note, note_hash); } /// Note that if you later on remove the note it's much better to use `pop_notes` as `pop_notes` results @@ -77,7 +79,7 @@ impl PrivateSet where N self, options: NoteGetterOptions ) -> BoundedVec { - get_notes(self.context, self.storage_slot, options) + get_notes(self.context, self.storage_slot, options).0 } } 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 85cc21f9106..0559ffb41e7 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 @@ -40,12 +40,12 @@ impl NoteInterface for MockNote { 0 } - fn compute_note_hash_and_nullifier(_self: Self, _context: &mut PrivateContext) -> (Field, Field) { - (0, 0) + fn compute_nullifier(_self: Self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { + 0 } - fn compute_note_hash_and_nullifier_without_context(_self: Self) -> (Field, Field) { - (0, 0) + fn compute_nullifier_without_context(_self: Self) -> Field { + 0 } fn to_be_bytes(self, storage_slot: Field) -> [u8; MOCK_NOTE_BYTES_LENGTH] { 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 df953c5d9d6..86d29a98219 100644 --- a/noir-projects/aztec-nr/value-note/src/value_note.nr +++ b/noir-projects/aztec-nr/value-note/src/value_note.nr @@ -3,7 +3,7 @@ use dep::aztec::{ address::AztecAddress, traits::{Deserialize, Serialize}, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator }, - note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption}, + note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_nullify}, oracle::unsafe_rand::unsafe_rand, keys::getters::get_nsk_app, context::PrivateContext }; @@ -24,30 +24,27 @@ struct ValueNote { impl NoteInterface for ValueNote { // docs:start:nullifier - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, - secret, + secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } // docs:end:nullifier - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret, ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } } 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 681dabb8760..202da79eb18 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 @@ -1,7 +1,7 @@ use dep::aztec::prelude::{AztecAddress, PrivateContext, NoteHeader, NoteInterface}; use dep::aztec::{ protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, - note::utils::compute_note_hash_for_consumption, keys::getters::get_nsk_app + note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app }; global SUBSCRIPTION_NOTE_LEN: Field = 3; @@ -19,28 +19,25 @@ struct SubscriptionNote { } impl NoteInterface for SubscriptionNote { - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, - secret, + secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret, ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } } 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 8b15ee8f04e..e39e00df441 100644 --- a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr @@ -1,5 +1,6 @@ contract Claim { use dep::aztec::{ + note::utils::compute_note_hash_for_nullify, protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress}, state_vars::SharedImmutable }; @@ -38,7 +39,10 @@ contract Claim { // Note: Only the owner of the npk_m will be able to produce the nsk_app and compute this nullifier. // The nullifier is unique to the note and THIS contract because the protocol siloes all nullifiers with // the address of a contract it was emitted from. - let (_, nullifier) = proof_note.compute_note_hash_and_nullifier(&mut context); + // 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 nullifier = proof_note.compute_nullifier(&mut context, note_hash_for_nullify); context.push_nullifier(nullifier); // 4) Finally we mint the reward token to the sender of the transaction 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 4c03ec84d0e..043d4eab2bc 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 @@ -1,6 +1,6 @@ use dep::aztec::prelude::{AztecAddress, NoteInterface, NoteHeader, PrivateContext}; use dep::aztec::{ - note::{utils::compute_note_hash_for_consumption}, keys::getters::get_nsk_app, + note::{utils::compute_note_hash_for_nullify}, keys::getters::get_nsk_app, protocol_types::{ traits::{Empty, Eq, Serialize}, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator @@ -33,28 +33,25 @@ impl CardNote { // docs:start:note_interface impl NoteInterface for CardNote { - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, - secret, + secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret, ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } } // docs:end:note_interface 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 4f03f385d0a..174627342d8 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,7 +1,7 @@ use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteHeader, NoteInterface, NoteGetterOptions, PrivateContext}; use dep::aztec::{ - note::utils::compute_note_hash_for_consumption, keys::getters::get_nsk_app, + note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app, protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} }; @@ -65,28 +65,25 @@ impl NoteInterface f EcdsaPublicKeyNote { x, y, npk_m_hash: serialized_note[4], header: NoteHeader::empty() } } - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, - secret, + secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret, ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } } 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 4e4933afddb..b9ecbe035d5 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 @@ -1,6 +1,6 @@ use dep::aztec::prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext}; use dep::aztec::{ - note::utils::compute_note_hash_for_consumption, keys::getters::get_nsk_app, + note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app, protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} }; @@ -19,28 +19,25 @@ struct PublicKeyNote { } impl NoteInterface for PublicKeyNote { - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, - secret, + secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret, ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } } diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index e021ea991e3..62e88f77c89 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -29,7 +29,7 @@ contract Test { context::inputs::private_context_inputs::PrivateContextInputs, hash::{pedersen_hash, compute_secret_hash, ArgsHasher}, note::{ - lifecycle::{create_note, destroy_note}, note_getter::{get_notes, view_notes}, + lifecycle::{create_note, destroy_note_unsafe}, note_getter::{get_notes, view_notes}, note_getter_options::NoteStatus }, deploy::deploy_contract as aztec_deploy_contract, @@ -117,7 +117,7 @@ contract Test { options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); } - let notes: BoundedVec = get_notes(&mut context, storage_slot, options); + let (notes, _): (BoundedVec, BoundedVec) = get_notes(&mut context, storage_slot, options); notes.get(0).value } @@ -133,7 +133,7 @@ contract Test { options = options.set_status(NoteStatus.ACTIVE_OR_NULLIFIED); } - let notes: BoundedVec = get_notes(&mut context, storage_slot, options); + let (notes, _): (BoundedVec, BoundedVec) = get_notes(&mut context, storage_slot, options); [notes.get(0).value, notes.get(1).value] } @@ -175,11 +175,12 @@ contract Test { ); let options = NoteGetterOptions::new(); - let notes: BoundedVec = get_notes(&mut context, storage_slot, options); + let (notes, note_hashes): (BoundedVec, BoundedVec) = get_notes(&mut context, storage_slot, options); let note = notes.get(0); + let note_hash = note_hashes.get(0); - destroy_note(&mut context, note); + destroy_note_unsafe(&mut context, note, note_hash); } #[aztec(private)] 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 f6bbbd39eae..7689d675362 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 @@ -18,14 +18,14 @@ struct TestNote { impl NoteInterface for TestNote { - fn compute_note_hash_and_nullifier(self, _context: &mut PrivateContext) -> (Field, Field) { + fn compute_nullifier(self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { // This note is expected to be shared between users and for this reason can't be nullified using a secret. - (0, 0) + 0 } - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { + fn compute_nullifier_without_context(self) -> Field { // This note is expected to be shared between users and for this reason can't be nullified using a secret. - (0, 0) + 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 275c21e141c..aebae804ec3 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 @@ -1,7 +1,7 @@ use dep::aztec::{ prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext}, protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, - note::utils::compute_note_hash_for_consumption, oracle::unsafe_rand::unsafe_rand, + note::utils::compute_note_hash_for_nullify, oracle::unsafe_rand::unsafe_rand, keys::getters::get_nsk_app }; @@ -26,29 +26,26 @@ struct TokenNote { impl NoteInterface for TokenNote { // docs:start:nullifier - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, - secret, + secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } // docs:end:nullifier - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret, ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } } 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 a7dbc06c5d1..3ac79408852 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,6 +1,6 @@ // docs:start:token_types_all use dep::aztec::{ - note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_consumption}, + note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_nullify}, prelude::{NoteHeader, NoteInterface, PrivateContext}, protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} }; @@ -40,8 +40,8 @@ impl NoteInterface for Transpa } } - fn compute_note_hash_and_nullifier(self, _context: &mut PrivateContext) -> (Field, Field) { - self.compute_note_hash_and_nullifier_without_context() + fn compute_nullifier(self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { + self.compute_nullifier_without_context() } // Computing a nullifier in a transparent note is not guarded by making secret a part of the nullifier preimage (as @@ -52,14 +52,13 @@ impl NoteInterface for Transpa // 3) the "get_notes" oracle constrains that the secret hash in the returned note matches the one computed in // circuit. // This achieves that the note can only be spent by the party that knows the secret. - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let nullifier = poseidon2_hash_with_separator([ + fn compute_nullifier_without_context(self) -> 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, - ); - (note_hash_for_nullify, nullifier) + ) } } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr index 6a2d4e94fe2..a3a42c36a43 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr @@ -1,7 +1,7 @@ use dep::aztec::{ prelude::{AztecAddress, NoteHeader, NoteInterface, PrivateContext}, protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, - note::utils::compute_note_hash_for_consumption, oracle::unsafe_rand::unsafe_rand, + note::utils::compute_note_hash_for_nullify, oracle::unsafe_rand::unsafe_rand, keys::getters::get_nsk_app }; @@ -33,29 +33,26 @@ struct TokenNote { impl NoteInterface for TokenNote { // docs:start:nullifier - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, - secret, + secret ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } // docs:end:nullifier - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([ + poseidon2_hash_with_separator([ note_hash_for_nullify, secret, ], GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ); - (note_hash_for_nullify, nullifier) + ) } // TODO(#7738): Nuke this function and have it auto-generated by macros diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr index 7a7c6204812..1e543e3d4ad 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr @@ -1,6 +1,6 @@ // docs:start:token_types_all use dep::aztec::{ - note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_consumption}, + note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_nullify}, prelude::{NoteHeader, NoteInterface, PrivateContext}, protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} }; @@ -40,8 +40,8 @@ impl NoteInterface for Transpa } } - fn compute_note_hash_and_nullifier(self, _context: &mut PrivateContext) -> (Field, Field) { - self.compute_note_hash_and_nullifier_without_context() + fn compute_nullifier(self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { + self.compute_nullifier_without_context() } // Computing a nullifier in a transparent note is not guarded by making secret a part of the nullifier preimage (as @@ -52,14 +52,13 @@ impl NoteInterface for Transpa // 3) the "get_notes" oracle constrains that the secret hash in the returned note matches the one computed in // circuit. // This achieves that the note can only be spent by the party that knows the secret. - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let nullifier = poseidon2_hash_with_separator([ + fn compute_nullifier_without_context(self) -> 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, - ); - (note_hash_for_nullify, nullifier) + ) } } diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr index f7d4666c706..4be78977dbf 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/token_note.nr @@ -5,7 +5,7 @@ use dep::aztec::{ constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::{Point, POINT_LENGTH}, scalar::Scalar, hash::poseidon2_hash_with_separator, traits::Serialize }, - note::utils::compute_note_hash_for_consumption, oracle::unsafe_rand::unsafe_rand, + note::utils::compute_note_hash_for_nullify, oracle::unsafe_rand::unsafe_rand, keys::getters::get_nsk_app }; use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe}; @@ -30,19 +30,21 @@ struct TokenNote { impl NoteInterface for TokenNote { // docs:start:nullifier - fn compute_note_hash_and_nullifier(self, context: &mut PrivateContext) -> ( Field, Field ) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([note_hash_for_nullify, secret], GENERATOR_INDEX__NOTE_NULLIFIER); - (note_hash_for_nullify, nullifier) + poseidon2_hash_with_separator([ + note_hash_for_nullify, + secret + ], + GENERATOR_INDEX__NOTE_NULLIFIER as Field, + ) } // docs:end:nullifier - fn compute_note_hash_and_nullifier_without_context(self) -> ( Field, Field ) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - let nullifier = poseidon2_hash_with_separator([note_hash_for_nullify,secret,],GENERATOR_INDEX__NOTE_NULLIFIER); - (note_hash_for_nullify, nullifier) + poseidon2_hash_with_separator([note_hash_for_nullify, secret],GENERATOR_INDEX__NOTE_NULLIFIER) } fn compute_note_hiding_point(self) -> Point { diff --git a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/transparent_note.nr index 8d0efabe993..1e543e3d4ad 100644 --- a/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/transparent_note.nr +++ b/noir-projects/noir-contracts/contracts/token_with_refunds_contract/src/types/transparent_note.nr @@ -1,6 +1,6 @@ // docs:start:token_types_all use dep::aztec::{ - note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_consumption}, + note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_nullify}, prelude::{NoteHeader, NoteInterface, PrivateContext}, protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} }; @@ -40,9 +40,8 @@ impl NoteInterface for Transpa } } - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386): Ensure nullifier collisions are prevented - fn compute_note_hash_and_nullifier(self, _context: &mut PrivateContext) -> (Field, Field) { - self.compute_note_hash_and_nullifier_without_context() + fn compute_nullifier(self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { + self.compute_nullifier_without_context() } // Computing a nullifier in a transparent note is not guarded by making secret a part of the nullifier preimage (as @@ -53,10 +52,13 @@ impl NoteInterface for Transpa // 3) the "get_notes" oracle constrains that the secret hash in the returned note matches the one computed in // circuit. // This achieves that the note can only be spent by the party that knows the secret. - fn compute_note_hash_and_nullifier_without_context(self) -> (Field, Field) { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let nullifier = poseidon2_hash_with_separator([note_hash_for_nullify], GENERATOR_INDEX__NOTE_NULLIFIER); - (note_hash_for_nullify, nullifier) + fn compute_nullifier_without_context(self) -> 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, + ) } }