Skip to content

Commit

Permalink
refactor: note emission (#7003)
Browse files Browse the repository at this point in the history
Adds a `NoteEmission` struct that is returned when new notes are
created. The struct is the note and a `NoteHashCount` that can be used
when emitting the note to ensure that squashing is possible.

There are fairly large change to return this struct throughout, and at
the same time getting rid of the `broadcast` that we have currently been
using whenever the `create_note` life cycle have been encountered. This
also means that keys are not fed into the lifecycle! Meaning that the
aztec nr libraries are generally getting less contaminated with log
decisions.

Instead of always forcing an emit, the returned value can now be emitted
using the `emit` which can be provided a function. For ease of use, we
have also added `encrypted_note_emission.nr` which have a
`encode_and_encrypt` and `encode_and_encrypt_with_keys` functions.

These functions can be used along with the emit to encrypt the note and
then emit it. This allows us to execute it as:

```rust
    let amount = U128::from_integer(amount);
    storage.balances.sub(from, amount).emit(encode_and_encrypt(&mut context, from, from));
    storage.balances.add(to, amount).emit(encode_and_encrypt(&mut context, from, to));
```

**TODO:**
- [x] Remove `broadcast` from note interface
- [x] Remove `encrypt_and_emit_note` from private context

---

There are some build issues that need to be ironed out, but I think that
the flow is nicer than before as it is more clear when things are
emitted, and for cases where we are doing a `get_note` and then
`replace` it is easier for us to not emit the read but only the update
afterwards.
  • Loading branch information
LHerskind authored Jun 12, 2024
1 parent d025496 commit 10048da
Show file tree
Hide file tree
Showing 48 changed files with 297 additions and 374 deletions.
6 changes: 3 additions & 3 deletions boxes/boxes/react/src/contracts/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
contract BoxReact {
use dep::aztec::prelude::{AztecAddress, PrivateMutable, Map, NoteInterface, NoteHeader};
use dep::aztec::protocol_types::grumpkin_point::GrumpkinPoint;

use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_with_keys;
use dep::value_note::value_note::{ValueNote, VALUE_NOTE_LEN};

#[aztec(storage)]
Expand All @@ -20,7 +20,7 @@ contract BoxReact {
) {
let numbers = storage.numbers;
let mut new_number = ValueNote::new(number, owner_npk_m_hash);
numbers.at(owner).initialize(&mut new_number, owner_ovpk_m, owner_ivpk_m);
numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m));
}

#[aztec(private)]
Expand All @@ -33,7 +33,7 @@ contract BoxReact {
) {
let numbers = storage.numbers;
let mut new_number = ValueNote::new(number, owner_npk_m_hash);
numbers.at(owner).replace(&mut new_number, owner_ovpk_m, owner_ivpk_m);
numbers.at(owner).replace(&mut new_number).emit(encode_and_encrypt_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m));
}

unconstrained fn getNumber(owner: AztecAddress) -> pub ValueNote {
Expand Down
22 changes: 17 additions & 5 deletions boxes/boxes/vanilla/src/contracts/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
contract Vanilla {
use dep::aztec::prelude::{AztecAddress, PrivateMutable, Map, NoteInterface, NoteHeader};
use dep::aztec::protocol_types::grumpkin_point::GrumpkinPoint;

use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_with_keys;
use dep::value_note::value_note::{ValueNote, VALUE_NOTE_LEN};

#[aztec(storage)]
Expand All @@ -11,17 +11,29 @@ contract Vanilla {

#[aztec(private)]
#[aztec(initializer)]
fn constructor(number: Field, owner: AztecAddress, owner_npk_m_hash: Field, owner_ovpk_m: GrumpkinPoint, owner_ivpk_m: GrumpkinPoint) {
fn constructor(
number: Field,
owner: AztecAddress,
owner_npk_m_hash: Field,
owner_ovpk_m: GrumpkinPoint,
owner_ivpk_m: GrumpkinPoint
) {
let numbers = storage.numbers;
let mut new_number = ValueNote::new(number, owner_npk_m_hash);
numbers.at(owner).initialize(&mut new_number, owner_ovpk_m, owner_ivpk_m);
numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m));
}

#[aztec(private)]
fn setNumber(number: Field, owner: AztecAddress, owner_npk_m_hash: Field, owner_ovpk_m: GrumpkinPoint, owner_ivpk_m: GrumpkinPoint) {
fn setNumber(
number: Field,
owner: AztecAddress,
owner_npk_m_hash: Field,
owner_ovpk_m: GrumpkinPoint,
owner_ivpk_m: GrumpkinPoint
) {
let numbers = storage.numbers;
let mut new_number = ValueNote::new(number, owner_npk_m_hash);
numbers.at(owner).replace(&mut new_number, owner_ovpk_m, owner_ivpk_m);
numbers.at(owner).replace(&mut new_number).emit(encode_and_encrypt_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m));
}

unconstrained fn getNumber(owner: AztecAddress) -> pub ValueNote {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ Then to register the recipient's complete address in PXE we would call `register
If a note recipient is one of the accounts inside the PXE, we don't need to register it as a recipient because we already have the public key available. You can register a recipient as shown [here](../how_to_deploy_contract.md)
:::

### Call encrypt_and_emit_note
### Call emit

To emit encrypted logs you don't need to import any library. You call the context method `encrypt_and_emit_note`:
To emit encrypted logs you can import the `encode_and_encrypt` or `encode_and_encrypt_with_keys` functions and pass them into the `emit` function after inserting a note. An example can be seen in the reference token contract's transfer function:

#include_code encrypted /noir-projects/aztec-nr/address-note/src/address_note.nr rust
#include_code encrypted /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

Furthermore, if not emitting the note, one should explicitly `discard` the value returned from the note creation.

### Successfully process the encrypted event

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,3 @@ Beware that this hash computation is what the aztec.nr library is doing, and not
With this note structure, the contract can require that only notes sitting at specific storage slots can be used by specific operations, e.g., if transferring funds from `from` to `to`, the notes to destroy should be linked to `H(map_slot, from)` and the new notes (except the change-note) should be linked to `H(map_slot, to)`.

That way, we can have logical storage slots, without them really existing. This means that knowing the storage slot for a note is not enough to actually figure out what is in there (whereas it would be for looking up public state).

15 changes: 15 additions & 0 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ 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] emit encrypted logs

Emitting or broadcasting encrypted notes are no longer done as part of the note creation, but must explicitly be either emitted or discarded instead.

```diff
+ use dep::aztec::encrypted_logs::encrypted_note_emission::{encode_and_encrypt, encode_and_encrypt_with_keys};

- storage.balances.sub(from, amount);
+ storage.balances.sub(from, amount).emit(encode_and_encrypt_with_keys(&mut context, from, from));
+ storage.balances.sub(from, amount).emit(encode_and_encrypt_with_keys(&mut context, from_ovpk, from_ivpk));
+ storage.balances.sub(from, amount).discard();
```

## 0.42.0

### [Aztec.nr] Unconstrained Context
Expand Down
12 changes: 0 additions & 12 deletions noir-projects/aztec-nr/address-note/src/address_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,6 @@ impl NoteInterface<ADDRESS_NOTE_LEN, ADDRESS_NOTE_BYTES_LEN> for AddressNote {
GENERATOR_INDEX__NOTE_NULLIFIER as Field,
])
}

// Broadcasts the note as an encrypted log on L1.
fn broadcast(self, context: &mut PrivateContext, slot: Field, ovpk_m: GrumpkinPoint, ivpk_m: GrumpkinPoint) {
// docs:start:encrypted
context.encrypt_and_emit_note(
slot,
ovpk_m,
ivpk_m,
self,
);
// docs:end:encrypted
}
}

impl AddressNote {
Expand Down
23 changes: 0 additions & 23 deletions noir-projects/aztec-nr/aztec/src/context/private_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -351,29 +351,6 @@ impl PrivateContext {
emit_encrypted_event_log(contract_address, randomness, encrypted_log, counter);
}

pub fn encrypt_and_emit_note<Note, N, NB, M>(
&mut self,
storage_slot: Field,
ovpk_m: GrumpkinPoint,
ivpk_m: GrumpkinPoint,
note: Note
) where Note: NoteInterface<N, NB>, [Field; N]: LensForEncryptedLog<N, M> {
let note_hash_counter = note.get_header().note_hash_counter;
let note_exists_index = find_index(
self.new_note_hashes.storage,
|n: NoteHash| n.counter == note_hash_counter
);
assert(
note_exists_index as u32 != MAX_NEW_NOTE_HASHES_PER_CALL, "Can only emit a note log for an existing note."
);

let contract_address = self.this_address();
let ovsk_app = self.request_ovsk_app(ovpk_m.hash());

let encrypted_log: [u8; M] = compute_encrypted_note_log(contract_address, storage_slot, ovsk_app, ovpk_m, ivpk_m, note);
self.emit_raw_note_log(note_hash_counter, encrypted_log);
}

pub fn emit_raw_note_log<M>(&mut self, note_hash_counter: u32, encrypted_log: [u8; M]) {
let counter = self.next_counter();
let len = encrypted_log.len() as Field + 4;
Expand Down
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/encrypted_logs.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ mod header;
mod incoming_body;
mod outgoing_body;
mod payload;
mod encrypted_note_emission;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::{
context::PrivateContext, note::{note_emission::NoteEmission, note_interface::NoteInterface},
encrypted_logs::payload::compute_encrypted_note_log, oracle::logs_traits::LensForEncryptedLog
};
use dep::protocol_types::{
address::AztecAddress, grumpkin_point::GrumpkinPoint, abis::note_hash::NoteHash,
constants::MAX_NEW_NOTE_HASHES_PER_CALL, utils::arrays::find_index
};

fn emit_with_keys<Note, N, NB, M>(
context: &mut PrivateContext,
note: Note,
ovpk: GrumpkinPoint,
ivpk: GrumpkinPoint
) where Note: NoteInterface<N, NB>, [Field; N]: LensForEncryptedLog<N, M> {
let note_header = note.get_header();
let note_hash_counter = note_header.note_hash_counter;
let storage_slot = note_header.storage_slot;

let note_exists_index = find_index(
context.new_note_hashes.storage,
|n: NoteHash| n.counter == note_hash_counter
);
assert(
note_exists_index as u32 != MAX_NEW_NOTE_HASHES_PER_CALL, "Can only emit a note log for an existing note."
);

let contract_address: AztecAddress = context.this_address();
let ovsk_app: Field = context.request_ovsk_app(ovpk.hash());

let encrypted_log: [u8; M] = compute_encrypted_note_log(contract_address, storage_slot, ovsk_app, ovpk, ivpk, note);

context.emit_raw_note_log(note_hash_counter, encrypted_log);
}

pub fn encode_and_encrypt<Note, N, NB, M>(
context: &mut PrivateContext,
ov: AztecAddress,
iv: AztecAddress
) -> fn[(&mut PrivateContext, AztecAddress, AztecAddress)](NoteEmission<Note>) -> () where Note: NoteInterface<N, NB>, [Field; N]: LensForEncryptedLog<N, M> {
| e: NoteEmission<Note> | {
let header = context.get_header();
let ovpk = header.get_ovpk_m(context, ov);
let ivpk = header.get_ivpk_m(context, iv);
emit_with_keys(context, e.note, ovpk, ivpk);
}
}

pub fn encode_and_encrypt_with_keys<Note, N, NB, M>(
context: &mut PrivateContext,
ovpk: GrumpkinPoint,
ivpk: GrumpkinPoint
) -> fn[(&mut PrivateContext, GrumpkinPoint, GrumpkinPoint)](NoteEmission<Note>) -> () where Note: NoteInterface<N, NB>, [Field; N]: LensForEncryptedLog<N, M> {
| e: NoteEmission<Note> | {
emit_with_keys(context, e.note, ovpk, ivpk);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ mod test {

fn compute_nullifier_without_context(self) -> Field {1}

fn broadcast(self, context: &mut PrivateContext, slot: Field, ovpk_m: GrumpkinPoint, ivpk_m: GrumpkinPoint) {}

fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN] { [self.address.to_field(), self.owner.to_field(), self.randomness]}

fn deserialize_content(fields: [Field; ADDRESS_NOTE_LEN]) -> Self {
Expand Down
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ mod note_header;
mod note_interface;
mod note_viewer_options;
mod utils;
mod note_emission;
11 changes: 5 additions & 6 deletions noir-projects/aztec-nr/aztec/src/note/lifecycle.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ use dep::protocol_types::grumpkin_point::GrumpkinPoint;
use crate::context::{PrivateContext, PublicContext};
use crate::note::{
note_header::NoteHeader, note_interface::NoteInterface,
utils::{compute_note_hash_for_insertion, compute_note_hash_for_consumption}
utils::{compute_note_hash_for_insertion, compute_note_hash_for_consumption},
note_emission::NoteEmission
};
use crate::oracle::notes::{notify_created_note, notify_nullified_note};

pub fn create_note<Note, N, M>(
context: &mut PrivateContext,
storage_slot: Field,
note: &mut Note,
ovpk_m: GrumpkinPoint,
ivpk_m: GrumpkinPoint
) where Note: NoteInterface<N, M> {
note: &mut Note
) -> NoteEmission<Note> where Note: NoteInterface<N, M> {
let contract_address = (*context).this_address();
let note_hash_counter = context.side_effect_counter;

Expand All @@ -36,7 +35,7 @@ pub fn create_note<Note, N, M>(

context.push_new_note_hash(inner_note_hash);

Note::broadcast(*note, context, storage_slot, ovpk_m, ivpk_m);
NoteEmission::new(*note)
}

pub fn create_note_hash_from_public<Note, N, M>(
Expand Down
45 changes: 45 additions & 0 deletions noir-projects/aztec-nr/aztec/src/note/note_emission.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* A note emission struct containing the information required for emitting a note.
* The exact `emit` logic is passed in by the application code
*/
struct NoteEmission<Note> {
note: Note
}

impl<Note> NoteEmission<Note> {
pub fn new(note: Note) -> Self {
Self { note }
}

pub fn emit<Env>(self, _emit: fn[Env](Self) -> ()) {
_emit(self);
}

pub fn discard(self) {}
}

/**
* A struct wrapping note emission in `Option<T>`.
* This is the struct provided to application codes, which can be used to emit
* only when a note was actually inserted.
* It is fairly common to have cases where a function conditionally inserts,
* and this allows us to keep the same API for emission in both cases (e.g. inserting
* a change note in a token's transfer function only when there is "change" left).
*/
struct OuterNoteEmission<Note> {
emission: Option<NoteEmission<Note>>,
}

impl<Note> OuterNoteEmission<Note> {
pub fn new(emission: Option<NoteEmission<Note>>) -> Self {
Self { emission }
}

pub fn emit<Env>(self, _emit: fn[Env](NoteEmission<Note>) -> ()) {
if self.emission.is_some() {
_emit(self.emission.unwrap());
}
}

pub fn discard(self) {}
}
4 changes: 1 addition & 3 deletions noir-projects/aztec-nr/aztec/src/note/note_interface.nr
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ trait NoteInterface<N, M> {
fn compute_nullifier(self, context: &mut PrivateContext) -> Field;

fn compute_nullifier_without_context(self) -> Field;

fn broadcast(self, context: &mut PrivateContext, slot: Field, ovpk_m: GrumpkinPoint, ivpk_m: GrumpkinPoint) -> ();


// Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation
fn serialize_content(self) -> [Field; N];

Expand Down
10 changes: 4 additions & 6 deletions noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use dep::protocol_types::{
use crate::context::{PrivateContext, UnconstrainedContext};
use crate::note::{
lifecycle::create_note, note_getter::{get_note, view_notes}, note_interface::NoteInterface,
note_viewer_options::NoteViewerOptions
note_viewer_options::NoteViewerOptions, note_emission::NoteEmission
};
use crate::oracle::notes::check_nullifier_exists;
use crate::state_vars::storage::Storage;
Expand Down Expand Up @@ -46,15 +46,13 @@ impl<Note> PrivateImmutable<Note, &mut PrivateContext> {
// docs:start:initialize
pub fn initialize<N, M>(
self,
note: &mut Note,
ovpk_m: GrumpkinPoint,
ivpk_m: GrumpkinPoint
) where Note: NoteInterface<N, M> {
note: &mut Note
) -> NoteEmission<Note> where Note: NoteInterface<N, M> {
// Nullify the storage slot.
let nullifier = self.compute_initialization_nullifier();
self.context.push_new_nullifier(nullifier, 0);

create_note(self.context, self.storage_slot, note, ovpk_m, ivpk_m);
create_note(self.context, self.storage_slot, note)
}
// docs:end:initialize

Expand Down
Loading

0 comments on commit 10048da

Please sign in to comment.