Skip to content

Commit

Permalink
feat: destroy_note_unsafe (#7891)
Browse files Browse the repository at this point in the history
Fixes #7831

Gate diff: 82803 - 78619 = **4184** gates saved
  • Loading branch information
benesjan authored Aug 14, 2024
1 parent 83f33a1 commit 5cda7ba
Show file tree
Hide file tree
Showing 29 changed files with 313 additions and 222 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
51 changes: 51 additions & 0 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<VALUE_NOTE_LEN, VALUE_NOTE_BYTES_LEN> 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:
Expand Down
19 changes: 8 additions & 11 deletions noir-projects/aztec-nr/address-note/src/address_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand All @@ -25,28 +25,25 @@ struct AddressNote {

impl NoteInterface<ADDRESS_NOTE_LEN, ADDRESS_NOTE_BYTES_LEN> 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)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]}

Expand Down
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand All @@ -13,7 +13,7 @@ trait ProveNoteInclusion {
impl ProveNoteInclusion for Header {
fn prove_note_inclusion<Note, N, M>(self, note: Note) where Note: NoteInterface<N, M> {
// 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);
Expand Down
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/aztec/src/initializer.nr
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down
26 changes: 20 additions & 6 deletions noir-projects/aztec-nr/aztec/src/note/lifecycle.nr
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -50,14 +51,26 @@ pub fn create_note_hash_from_public<Note, N, M>(
context.push_note_hash(note_hash);
}

// Note: This function is currently totally unused.
pub fn destroy_note<Note, N, M>(
context: &mut PrivateContext,
note: Note
) where Note: NoteInterface<N, M> {
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<Note, N, M>(
context: &mut PrivateContext,
note: Note,
note_hash_for_read_request: Field
) where Note: NoteInterface<N, M> {
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 {
Expand All @@ -66,11 +79,12 @@ pub fn destroy_note<Note, N, M>(
// 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)
}

12 changes: 7 additions & 5 deletions noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,22 @@ fn check_notes_order<let N: u32>(
pub fn get_note<Note, let N: u32, let M: u32>(
context: &mut PrivateContext,
storage_slot: Field
) -> Note where Note: NoteInterface<N, M> {
) -> (Note, Field) where Note: NoteInterface<N, M> {
let note = get_note_internal(storage_slot);

check_note_header(*context, storage_slot, 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<Note, let N: u32, let M: u32, PREPROCESSOR_ARGS, FILTER_ARGS>(
context: &mut PrivateContext,
storage_slot: Field,
options: NoteGetterOptions<Note, N, M, PREPROCESSOR_ARGS, FILTER_ARGS>
) -> BoundedVec<Note, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL> where Note: NoteInterface<N, M> + Eq {
) -> (BoundedVec<Note, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>, BoundedVec<Field, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>) where Note: NoteInterface<N, M> + Eq {
let opt_notes = get_notes_internal(storage_slot, options);

constrain_get_notes_internal(context, storage_slot, opt_notes, options)
Expand All @@ -126,7 +126,7 @@ fn constrain_get_notes_internal<Note, let N: u32, let M: u32, PREPROCESSOR_ARGS,
storage_slot: Field,
opt_notes: [Option<Note>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL],
options: NoteGetterOptions<Note, N, M, PREPROCESSOR_ARGS, FILTER_ARGS>
) -> BoundedVec<Note, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL> where Note: NoteInterface<N, M> + Eq {
) -> (BoundedVec<Note, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>, BoundedVec<Field, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL>) where Note: NoteInterface<N, M> + 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
Expand All @@ -136,6 +136,7 @@ fn constrain_get_notes_internal<Note, let N: u32, let M: u32, PREPROCESSOR_ARGS,
let filtered_notes = filter_fn(opt_notes, filter_args);

let notes = crate::utils::collapse(filtered_notes);
let mut note_hashes: BoundedVec<Field, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL> = 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
Expand All @@ -158,10 +159,11 @@ fn constrain_get_notes_internal<Note, let N: u32, let M: u32, PREPROCESSOR_ARGS,
// 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.
context.push_note_hash_read_request(note_hash_for_read_request);
note_hashes.push(note_hash_for_read_request);
};
}

notes
(notes, note_hashes)
}

unconstrained fn get_note_internal<Note, let N: u32, let M: u32>(storage_slot: Field) -> Note where Note: NoteInterface<N, M> {
Expand Down
10 changes: 5 additions & 5 deletions noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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));
Expand All @@ -103,7 +103,7 @@ fn can_return_zero_notes() {
let opt_notes: [Option<MockNote>; 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);
}

Expand Down Expand Up @@ -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.
Expand Down
13 changes: 10 additions & 3 deletions noir-projects/aztec-nr/aztec/src/note/note_interface.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ use dep::protocol_types::point::Point;

// docs:start:note_interface
trait NoteInterface<let N: u32, let M: u32> {
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];
Expand Down
Loading

0 comments on commit 5cda7ba

Please sign in to comment.