Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/docs/aztec/concepts/advanced/storage/storage_slots.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ Nevertheless, the concept of a storage slot is very useful when writing applicat

If we include the storage slot, as part of the note whose commitment is stored in the note hashes tree, we can _logically link_ all the notes that make up the storage slot. For the case of a balance, we can say that the balance is the sum of all the notes that have the same storage slot - in the same way that your physical wallet balance is the sum of all the physical notes in your wallet.

Similarly to how we siloed the public storage slots, we can silo our private storage by hashing the logical storage slot together with the note content.
Similarly to how we siloed the public storage slots, we can silo our private storage by hashing the packed note together with the logical storage slot.

```rust
note_hash = H(logical_storage_slot, note_content_hash);
note_hash = H([...packed_note, logical_storage_slot]);
```

Note hash siloing is done in the application circuit, since it is not necessary for security of the network (but only the application).
Expand Down
89 changes: 42 additions & 47 deletions docs/docs/aztec/smart_contracts/functions/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,67 +218,62 @@ struct CustomNote {
### After expansion

```rust
impl CustomNote {
fn pack_content(self: CustomNote) -> [Field; PACKED_NOTE_CONTENT_LEN] {
[self.data, self.owner.to_field()]
}

fn unpack_content(packed_content: [Field; PACKED_NOTE_CONTENT_LEN]) -> Self {
CustomNote {
data: packed_content[0] as Field,
owner: Address::from_field(packed_content[1]),
header: NoteHeader::empty()
}
}

impl NoteInterface for CustomNote {
fn get_note_type_id() -> Field {
// Assigned by macros by incrementing a counter
2
}

fn get_header(note: CustomNote) -> aztec::note::note_header::NoteHeader {
note.header
fn compute_note_hash(self, storage_slot: Field) -> Field {
let inputs = array_concat(self.pack(), [storage_slot]);
poseidon2_hash_with_separator(inputs, GENERATOR_INDEX__NOTE_HASH)
}
}

fn set_header(self: &mut CustomNote, header: aztec::note::note_header::NoteHeader) {
self.header = header;
impl NullifiableNote for CustomNote {

fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field {
let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash();
let secret = context.request_nsk_app(owner_npk_m_hash);
poseidon2_hash_with_separator(
[
note_hash_for_nullify,
secret
],
GENERATOR_INDEX__NOTE_NULLIFIER as Field
)
}

fn compute_note_hiding_point(self: CustomNote) -> Point {
aztec::hash::pedersen_commitment(
self.serialize_content(),
aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_HIDING_POINT
unconstrained fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field {
// We set the note_hash_counter to 0 as the note is not transient and the concept of transient note does
// not make sense in an unconstrained context.
let retrieved_note = RetrievedNote { note: self, contract_address, nonce: note_nonce, note_hash_counter: 0 };
let note_hash_for_nullify = compute_note_hash_for_nullify(retrieved_note, storage_slot);
let owner_npk_m_hash = get_public_keys(self.owner).npk_m.hash();
let secret = get_nsk_app(owner_npk_m_hash);
poseidon2_hash_with_separator(
[
note_hash_for_nullify,
secret
],
GENERATOR_INDEX__NOTE_NULLIFIER as Field
)
}
}

fn to_be_bytes(self, storage_slot: Field) -> [u8; 128] {
assert(128 == 2 * 32 + 64, "Note byte length must be equal to (serialized_length * 32) + 64 bytes");
let serialized_note = self.serialize_content();

let mut buffer: [u8; 128] = [0; 128];

let storage_slot_bytes = storage_slot.to_be_bytes(32);
let note_type_id_bytes = CustomNote::get_note_type_id().to_be_bytes(32);

for i in 0..32 {
buffer[i] = storage_slot_bytes[i];
buffer[32 + i] = note_type_id_bytes[i];
}
impl CustomNote {
pub fn new(x: [u8; 32], y: [u8; 32], owner: AztecAddress) -> Self {
CustomNote { x, y, owner }
}
}

for i in 0..serialized_note.len() {
let bytes = serialized_note[i].to_be_bytes(32);
for j in 0..32 {
buffer[64 + i * 32 + j] = bytes[j];
}
}
buffer
}
impl Packable<2> for CustomNote {
fn pack(self) -> [Field; 2] {
[self.data, self.owner.to_field()]
}

pub fn properties() -> CustomNoteProperties {
CustomNoteProperties {
data: aztec::note::note_getter_options::PropertySelector { index: 0, offset: 0, length: 32 },
owner: aztec::note::note_getter_options::PropertySelector { index: 1, offset: 0, length: 32 }
}
fn unpack(packed_content: [Field; 2]) -> CustomNote {
CustomNote { data: packed_content[0], owner: AztecAddress { inner: packed_content[1] } }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ The function is automatically generated based on the note types defined in the c
```rust
if (note_type_id == NoteType::get_note_type_id()) {
aztec::note::utils::compute_note_hash_and_optionally_a_nullifier(
NoteType::unpack_content,
NoteType::unpack,
note_header,
compute_nullifier,
packed_note_content
packed_note
)
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ See [partial notes](../../../../../aztec/concepts/advanced/storage/partial_notes

When you send someone a note, the note hash gets added to the note hash tree. To spend the note, the receiver needs to get the note itself (the note hash preimage). There are two ways you can get a hold of your notes:

1. When sending someone a note, emit the note contents to the recipient (the function encrypts the log in such a way that only a recipient can decrypt it). PXE then tries to decrypt all the encrypted logs, and stores the successfully decrypted one. [More info here](../how_to_emit_event.md)
1. When sending someone a note, emit the note log to the recipient (the function encrypts the log in such a way that only a recipient can decrypt it). PXE then tries to decrypt all the encrypted logs, and stores the successfully decrypted one. [More info here](../how_to_emit_event.md)
2. Manually using `pxe.addNote()` - If you choose to not emit logs to save gas or when creating a note in the public domain and want to consume it in private domain (`encrypt_and_emit_note` shouldn't be called in the public domain because everything is public), like in the previous section where we created a note in public that doesn't have a designated owner.

#include_code pxe_add_note yarn-project/end-to-end/src/composed/e2e_persistence.test.ts typescript
Expand Down
44 changes: 33 additions & 11 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ The new check an indexed tree allows is non-membership of addresses of non proto
```

### [Aztec.nr] Changes to `NoteInterface`
We removed `NoteHeader` from notes and we've introduced `RetrievedNote` struct.
We removed `NoteHeader` from notes, we've introduced a `RetrievedNote` struct and instead of the `pack_content` and `unpack_content` functions we make notes implement the standard `Packable` trait.
This led us to do the following changes to `NoteInterface`:

```diff
Expand All @@ -102,11 +102,12 @@ pub trait NullifiableNote {
+ unconstrained fn fn compute_nullifier_without_context(self, storage_slot: Field, contract_address: AztecAddress, note_nonce: Field) -> Field;
}

pub trait NoteInterface<let N: u32> {
-pub trait NoteInterface<let N: u32> {
+pub trait NoteInterface {
- fn pack_content(self) -> [Field; N];
- fn unpack_content(fields: [Field; N]) -> Self;
- fn get_header(self) -> NoteHeader;

- fn set_header(&mut self, header: NoteHeader) -> ();

- fn compute_note_hash(self) -> Field;
+ fn compute_note_hash(self, storage_slot: Field) -> Field;
}
Expand All @@ -121,13 +122,15 @@ These are the changes that needed to be done to our `EcdsaPublicKeyNote`:
-use dep::aztec::prelude::{NoteHeader};
+use dep::aztec::prelude::{RetrievedNote};

impl NoteInterface<ECDSA_PUBLIC_KEY_NOTE_LEN> for EcdsaPublicKeyNote {
...
fn unpack_content(packed_content: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote {
...
- EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(packed_content[4]), header: NoteHeader::empty() }
+ EcdsaPublicKeyNote { x, y, owner: AztecAddress::from_field(packed_content[4]) }
}
- impl NoteInterface<ECDSA_PUBLIC_KEY_NOTE_LEN> for EcdsaPublicKeyNote {
+ impl NoteInterface for EcdsaPublicKeyNote {
- fn pack_content(self) -> [Field; ECDSA_PUBLIC_KEY_NOTE_LEN] {
- ...
- }

- fn unpack_content(packed_content: [Field; ECDSA_PUBLIC_KEY_NOTE_LEN]) -> EcdsaPublicKeyNote {
- ...
- }

- fn get_header(self) -> NoteHeader {
- self.header
Expand All @@ -145,6 +148,25 @@ impl NoteInterface<ECDSA_PUBLIC_KEY_NOTE_LEN> for EcdsaPublicKeyNote {
}
}

If you need to keep the custom implementation of the packing functionality, manually implement the `Packable` trait:

```diff
+ use dep::aztec::protocol_types::traits::Packable;

+impl Packable<N> for YourNote {
+ fn pack(self) -> [Field; N] {
+ ...
+ }
+
+ fn unpack(fields: [Field; N]) -> Self {
+ ...
+ }
+}
```

If you don't provide a custom implementation of the `Packable` trait, a default one will be generated.

```diff
impl NullifiableNote for EcdsaPublicKeyNote {
...
- unconstrained fn compute_nullifier_without_context(self, storage_slot: Field) -> Field {
Expand Down
19 changes: 6 additions & 13 deletions noir-projects/aztec-nr/address-note/src/address_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ use dep::aztec::{
},
oracle::random::random,
protocol_types::{
address::AztecAddress,
constants::GENERATOR_INDEX__NOTE_NULLIFIER,
hash::poseidon2_hash_with_separator,
traits::{Packable, Serialize},
address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER,
hash::poseidon2_hash_with_separator, traits::Serialize,
},
};

// TODO(#12008): Remove the need for the manual import of `Packable` trait here. This is a bug in macros.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue was already present before because we used Packable in the implementations of the pack_content and unpack_content function.s

use aztec::protocol_types::traits::Packable;

// docs:start:address_note_def
// docs:start:address_note_struct
// Stores an address
#[note]
#[derive(Serialize)]
#[derive(Eq, Serialize)]
pub struct AddressNote {
address: AztecAddress,
owner: AztecAddress,
Expand Down Expand Up @@ -71,11 +72,3 @@ impl AddressNote {
}
// docs:end:address_note_def
}

impl Eq for AddressNote {
fn eq(self, other: Self) -> bool {
(self.address == other.address)
& (self.owner == other.owner)
& (self.randomness == other.randomness)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ use crate::{
ecdh_shared_secret::derive_ecdh_shared_secret_using_aztec_address,
ephemeral::generate_ephemeral_key_pair,
},
note::{
note_emission::NoteEmission, note_interface::NoteInterface, retrieved_note::RetrievedNote,
},
note::{note_emission::NoteEmission, note_interface::NoteInterface},
oracle::{
notes::{get_app_tag_as_sender, increment_app_tagging_secret_index_as_sender},
random::random,
Expand All @@ -16,6 +14,7 @@ use crate::{
};
use dep::protocol_types::{
abis::note_hash::NoteHash, address::AztecAddress, constants::PRIVATE_LOG_SIZE_IN_FIELDS,
traits::Packable,
};
use std::aes128::aes128_encrypt;

Expand Down Expand Up @@ -219,9 +218,9 @@ fn compute_note_plaintext_for_this_strategy<Note, let N: u32>(
storage_slot: Field,
) -> [u8; N * 32 + 64]
where
Note: NoteInterface<N>,
Note: NoteInterface + Packable<N>,
{
let packed_note = note.pack_content();
let packed_note = note.pack();

let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes();

Expand Down Expand Up @@ -253,7 +252,7 @@ fn compute_log<Note, let N: u32>(
sender: AztecAddress,
) -> [Field; PRIVATE_LOG_SIZE_IN_FIELDS]
where
Note: NoteInterface<N>,
Note: NoteInterface + Packable<N>,
{
// *****************************************************************************
// Compute the shared secret
Expand Down Expand Up @@ -417,7 +416,7 @@ unconstrained fn compute_log_unconstrained<Note, let N: u32>(
sender: AztecAddress,
) -> [Field; PRIVATE_LOG_SIZE_IN_FIELDS]
where
Note: NoteInterface<N>,
Note: NoteInterface + Packable<N>,
{
compute_log(context, note, storage_slot, recipient, sender)
}
Expand All @@ -432,7 +431,7 @@ pub fn encode_and_encrypt_note<Note, let N: u32>(
sender: AztecAddress,
) -> fn[(&mut PrivateContext, AztecAddress, AztecAddress)](NoteEmission<Note>) -> ()
where
Note: NoteInterface<N>,
Note: NoteInterface + Packable<N>,
{
|e: NoteEmission<Note>| {
let note = e.note;
Expand All @@ -455,7 +454,7 @@ pub fn encode_and_encrypt_note_unconstrained<Note, let N: u32>(
sender: AztecAddress,
) -> fn[(&mut PrivateContext, AztecAddress, AztecAddress)](NoteEmission<Note>) -> ()
where
Note: NoteInterface<N>,
Note: NoteInterface + Packable<N>,
{
|e: NoteEmission<Note>| {
let note = e.note;
Expand All @@ -464,7 +463,6 @@ where

assert_note_exists(*context, note_hash_counter);

// Unconstrained logs have both their content and encryption unconstrained - it could occur that the
// Unconstrained logs have both their content and encryption unconstrained - it could occur that the
// recipient is unable to decrypt the payload.
// Regarding the note hash counter, this is used for squashing. The kernel assumes that a given note can have
Expand Down
12 changes: 4 additions & 8 deletions noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,19 @@ use crate::{
};

trait ProveNoteInclusion {
fn prove_note_inclusion<Note, let N: u32>(
fn prove_note_inclusion<Note>(
header: BlockHeader,
retrieved_note: RetrievedNote<Note>,
storage_slot: Field,
)
where
Note: NoteInterface<N> + NullifiableNote;
Note: NoteInterface + NullifiableNote;
}

impl ProveNoteInclusion for BlockHeader {
fn prove_note_inclusion<Note, let N: u32>(
self,
retrieved_note: RetrievedNote<Note>,
storage_slot: Field,
)
fn prove_note_inclusion<Note>(self, retrieved_note: RetrievedNote<Note>, storage_slot: Field)
where
Note: NoteInterface<N> + NullifiableNote,
Note: NoteInterface + NullifiableNote,
{
let note_hash = compute_note_hash_for_nullify(retrieved_note, storage_slot);

Expand Down
8 changes: 4 additions & 4 deletions noir-projects/aztec-nr/aztec/src/history/note_validity.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ use crate::{
use dep::protocol_types::block_header::BlockHeader;

trait ProveNoteValidity {
fn prove_note_validity<Note, let N: u32>(
fn prove_note_validity<Note>(
header: BlockHeader,
retrieved_note: RetrievedNote<Note>,
storage_slot: Field,
context: &mut PrivateContext,
)
where
Note: NoteInterface<N> + NullifiableNote;
Note: NoteInterface + NullifiableNote;
}

impl ProveNoteValidity for BlockHeader {
fn prove_note_validity<Note, let N: u32>(
fn prove_note_validity<Note>(
self,
retrieved_note: RetrievedNote<Note>,
storage_slot: Field,
context: &mut PrivateContext,
)
where
Note: NoteInterface<N> + NullifiableNote,
Note: NoteInterface + NullifiableNote,
{
self.prove_note_inclusion(retrieved_note, storage_slot);
self.prove_note_not_nullified(retrieved_note, storage_slot, context);
Expand Down
Loading