Skip to content

Commit

Permalink
feat: packing note content (#11376)
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan authored Jan 29, 2025
1 parent ab2500b commit 6630e80
Show file tree
Hide file tree
Showing 20 changed files with 278 additions and 148 deletions.
10 changes: 5 additions & 5 deletions docs/docs/aztec/smart_contracts/functions/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ When a struct is annotated with `#[note]`, the Aztec macro applies a series of t

1. **NoteInterface Implementation**: The macro automatically implements most methods of the `NoteInterface` trait for the annotated struct. This includes:

- `serialize_content` and `deserialize_content`
- `pack_content` and `unpack_content`
- `get_header` and `set_header`
- `get_note_type_id`
- `compute_note_hiding_point`
Expand Down Expand Up @@ -219,14 +219,14 @@ struct CustomNote {

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

fn deserialize_content(serialized_note: [Field; NOTE_SERIALIZED_LEN]) -> Self {
fn unpack_content(packed_content: [Field; PACKED_NOTE_CONTENT_LEN]) -> Self {
CustomNote {
data: serialized_note[0] as Field,
owner: Address::from_field(serialized_note[1]),
data: packed_content[0] as Field,
owner: Address::from_field(packed_content[1]),
header: NoteHeader::empty()
}
}
Expand Down
14 changes: 7 additions & 7 deletions docs/docs/aztec/smart_contracts/functions/function_transforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ For private functions, the context creation involves hashing all input parameter

```rust
let mut args_hasher = ArgsHasher::new();
// Hash each parameter
// Hash each parameter
args_hasher.add(param1);
args_hasher.add(param2);
// add all parameters
Expand Down Expand Up @@ -71,8 +71,8 @@ The context provides methods to call other contracts:
```rust
let token_contract = TokenContract::at(token);
```
Under the hood, this creates a new instance of the contract interface with the specified address.

Under the hood, this creates a new instance of the contract interface with the specified address.

## Private and public input injection

Expand Down Expand Up @@ -102,7 +102,7 @@ This makes these inputs available to be consumed within private annotated functi

## Return value handling

Return values in Aztec contracts are processed differently from traditional smart contracts when using private functions.
Return values in Aztec contracts are processed differently from traditional smart contracts when using private functions.

### Private functions

Expand Down 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::deserialize_content,
NoteType::unpack_content,
note_header,
compute_nullifier,
serialized_note
packed_note_content
)
}
```
Expand Down Expand Up @@ -206,7 +206,7 @@ The computed function signatures are integrated into the contract interface like
- The function's parameters are extracted
- The signature hash is computed using `compute_fn_signature_hash`
- The placeholder in the contract interface is replaced with the computed hash

This process ensures that each function in the contract has a unique, deterministic signature based on its name and parameter types. They are inspired by Solidity's function selector mechanism.

## Contract artifacts
Expand Down
19 changes: 19 additions & 0 deletions docs/docs/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ The way in which logs are assembled in this "default_aes128" strategy is has als

You can remove this method from any custom notes or events that you've implemented.

### [Aztec.nr] Packing notes resulting in changes in `NoteInterface`
Note interface implementation generated by our macros now packs note content instead of serializing it
With this change notes are being less costly DA-wise to emit when some of the note struct members implements the `Packable` trait (this is typically the `UintNote` which represents `value` as `U128` that gets serialized as 2 fields but packed as 1).
This results in the following changes in the `NoteInterface`:

```diff
pub trait NoteInterface<let N: u32> {
- fn serialize_content(self) -> [Field; N];
+ fn pack_content(self) -> [Field; N];

- fn deserialize_content(fields: [Field; N]) -> Self;
+ fn unpack_content(fields: [Field; N]) -> Self;

fn get_header(self) -> NoteHeader;
fn set_header(&mut self, header: NoteHeader) -> ();
fn get_note_type_id() -> Field;
fn compute_note_hash(self) -> Field;
}
```

## 0.72.0
### Some functions in `aztec.js` and `@aztec/accounts` are now async
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ fn compute_note_plaintext_for_this_strategy<Note, let N: u32>(note: Note) -> [u8
where
Note: NoteInterface<N>,
{
let serialized_note = note.serialize_content();
let packed_note = note.pack_content();

let note_header = note.get_header();
let storage_slot = note_header.storage_slot;
Expand All @@ -237,8 +237,8 @@ where
plaintext_bytes[32 + i] = note_type_id_bytes[i];
}

for i in 0..serialized_note.len() {
let bytes: [u8; 32] = serialized_note[i].to_be_bytes();
for i in 0..packed_note.len() {
let bytes: [u8; 32] = packed_note[i].to_be_bytes();
for j in 0..32 {
plaintext_bytes[64 + i * 32 + j] = bytes[j];
}
Expand Down
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/macros/events/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ comptime fn generate_event_interface(s: StructDefinition) -> Quoted {
let name = s.name();
let typ = s.as_type();
let (serialization_fields, _) =
generate_serialize_to_fields(quote { self }, typ, &[quote {self.header}]);
generate_serialize_to_fields(quote { self }, typ, &[quote {self.header}], false);
let content_len = serialization_fields.len();

let event_type_id = compute_event_selector(s);
Expand Down
4 changes: 3 additions & 1 deletion noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ comptime fn transform_public(f: FunctionDefinition) -> Quoted {
// Public functions undergo a lot of transformations from their Aztec.nr form.
let original_params = f.parameters();
let args_len = original_params
.map(|(name, typ): (Quoted, Type)| generate_serialize_to_fields(name, typ, &[]).0.len())
.map(|(name, typ): (Quoted, Type)| {
generate_serialize_to_fields(name, typ, &[], false).0.len()
})
.fold(0, |acc: u32, val: u32| acc + val);

// Unlike in the private case, in public the `context` does not need to receive the hash of the original params.
Expand Down
28 changes: 14 additions & 14 deletions noir-projects/aztec-nr/aztec/src/macros/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ comptime fn generate_contract_interface(m: Module) -> Quoted {
}

comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted {
let mut max_note_length: u32 = 0;
let mut max_note_content_length: u32 = 0;
let notes = NOTES.entries();
let body = if notes.len() > 0 {
max_note_length = notes.fold(
max_note_content_length = notes.fold(
0,
|acc, (_, (_, len, _, _)): (Type, (StructDefinition, u32, Field, [(Quoted, u32, bool)]))| {
if len > acc {
Expand All @@ -138,7 +138,7 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted {
if_statements_list = if_statements_list.push_back(
quote {
$if_or_else_if note_type_id == $typ::get_note_type_id() {
aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::deserialize_content, note_header, compute_nullifier, serialized_note)
aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack_content, note_header, compute_nullifier, packed_note_content)
}
},
);
Expand Down Expand Up @@ -166,7 +166,7 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted {
storage_slot: Field,
note_type_id: Field,
compute_nullifier: bool,
serialized_note: [Field; $max_note_length],
packed_note_content: [Field; $max_note_content_length],
) -> pub [Field; 4] {
$body
}
Expand All @@ -184,14 +184,14 @@ comptime fn generate_process_log() -> Quoted {

// A typical implementation of the lambda looks something like this:
// ```
// |serialized_note_content: BoundedVec<Field, MAX_NOTE_SERIALIZED_LEN>, note_header: NoteHeader, note_type_id: Field| {
// |packed_note_content: BoundedVec<Field, MAX_NOTE_SERIALIZED_LEN>, note_header: NoteHeader, note_type_id: Field| {
// let hashes = if note_type_id == MyNoteType::get_note_type_id() {
// assert(serialized_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH);
// assert(packed_note_content.len() == MY_NOTE_TYPE_SERIALIZATION_LENGTH);
// dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier(
// MyNoteType::deserialize_content,
// MyNoteType::unpack_content,
// note_header,
// true,
// serialized_note_content.storage(),
// packed_note_content.storage(),
// )
// } else {
// panic(f"Unknown note type id {note_type_id}")
Expand All @@ -213,7 +213,7 @@ comptime fn generate_process_log() -> Quoted {

let mut if_note_type_id_match_statements_list = &[];
for i in 0..notes.len() {
let (typ, (_, serialized_note_length, _, _)) = notes[i];
let (typ, (_, packed_note_content_length, _, _)) = notes[i];

let if_or_else_if = if i == 0 {
quote { if }
Expand All @@ -224,17 +224,17 @@ comptime fn generate_process_log() -> Quoted {
if_note_type_id_match_statements_list = if_note_type_id_match_statements_list.push_back(
quote {
$if_or_else_if note_type_id == $typ::get_note_type_id() {
// As an extra safety check we make sure that the serialized_note_content bounded vec has the
// As an extra safety check we make sure that the packed_note_content bounded vec has the
// expected length, to avoid scenarios in which compute_note_hash_and_optionally_a_nullifier
// silently trims the end if the log were to be longer.
let expected_len = $serialized_note_length;
let actual_len = serialized_note_content.len();
let expected_len = $packed_note_content_length;
let actual_len = packed_note_content.len();
assert(
actual_len == expected_len,
f"Expected note content of length {expected_len} but got {actual_len} for note type id {note_type_id}"
);

aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::deserialize_content, note_header, true, serialized_note_content.storage())
aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::unpack_content, note_header, true, packed_note_content.storage())
}
},
);
Expand All @@ -256,7 +256,7 @@ comptime fn generate_process_log() -> Quoted {
unique_note_hashes_in_tx,
first_nullifier_in_tx,
recipient,
|serialized_note_content: BoundedVec<Field, _>, note_header, note_type_id| {
|packed_note_content: BoundedVec<Field, _>, note_header, note_type_id| {
let hashes = $if_note_type_id_match_statements
else {
panic(f"Unknown note type id {note_type_id}")
Expand Down
Loading

0 comments on commit 6630e80

Please sign in to comment.