Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: documenting note macros #9009

Merged
merged 8 commits into from
Oct 7, 2024
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
263 changes: 238 additions & 25 deletions noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use crate::note::{note_header::NoteHeader, note_getter_options::PropertySelector

comptime global NOTE_HEADER_TYPE = type_of(NoteHeader::empty());

// A map from note type to (note_struct_definition, serialized_note_length, note_type_id, fields).
// `fields` is an array of tuples where each tuple contains the name of the field/struct member (e.g. `amount`
// in `TokenNote`), the index of where the serialized member starts in the serialized note and a flag indicating
// whether the field is nullable or not.
/// A map from note type to (note_struct_definition, serialized_note_length, note_type_id, fields).
/// `fields` is an array of tuples where each tuple contains the name of the field/struct member (e.g. `amount`
/// in `TokenNote`), the index of where the serialized member starts in the serialized note and a flag indicating
/// whether the field is nullable or not.
comptime mut global NOTES: UHashMap<Type, (StructDefinition, u32, Field, [(Quoted, u32, bool)]), BuildHasherDefault<Poseidon2Hasher>> = UHashMap::default();

/// Computes a note type id by hashing a note name (e.g. `TokenNote`), getting the first 4 bytes of the hash
/// and returning it as a `Field`.
comptime fn compute_note_type_id(name: Quoted) -> Field {
let name_as_str_quote = name.as_str_quote();

Expand All @@ -26,6 +28,38 @@ comptime fn compute_note_type_id(name: Quoted) -> Field {
)
}

/// Generates default `NoteInterface` implementation for a given note struct `s` and returns it as quote along with
/// the length of the serialized note.
///
/// impl NoteInterface<N> for NoteStruct {
/// fn to_be_bytes(self, storage_slot: Field) -> [u8; N * 32 + 64] {
/// ...
/// }
///
/// fn deserialize_content(value: [Field; N]) -> Self {
/// ...
/// }
///
/// fn serialize_content(self) -> [Field; N] {
/// ...
/// }
///
/// fn get_note_type_id() -> Field {
/// ...
/// }
///
/// fn set_header(&mut self, header: NoteHeader) {
/// ...
/// }
///
/// fn get_header(self) -> NoteHeader {
/// ...
/// }
///
/// fn compute_note_hash(self) -> Field {
/// ...
/// }
/// }
comptime fn generate_note_interface(
s: StructDefinition,
note_type_id: Field,
Expand Down Expand Up @@ -124,6 +158,26 @@ comptime fn generate_note_interface(
}, content_len)
}

/// Generates note properties struct for a given note struct `s`.
///
/// Example:
/// ```
/// struct TokenNoteProperties {
/// amount: aztec::note::note_getter_options::PropertySelector,
/// npk_m_hash: aztec::note::note_getter_options::PropertySelector
/// randomness: aztec::note::note_getter_options::PropertySelector
/// }
///
/// impl aztec::note::note_interface::NoteProperties<TokenNoteProperties> for TokenNote {
/// fn properties() -> TokenNoteProperties {
/// Self {
/// amount: aztec::note::note_getter_options::PropertySelector { index: 0, offset: 0, length: 32 },
/// npk_m_hash: aztec::note::note_getter_options::PropertySelector { index: 1, offset: 0, length: 32 },
/// randomness: aztec::note::note_getter_options::PropertySelector { index: 2, offset: 0, length: 32 }
/// }
/// }
/// }
/// ```
comptime fn generate_note_properties(s: StructDefinition) -> Quoted {
let name = s.name();

Expand Down Expand Up @@ -164,8 +218,28 @@ comptime fn generate_note_properties(s: StructDefinition) -> Quoted {
}
}

/// Generates note export for a given note struct. The export is a global variable that contains note type id,
/// Generates note export for a given note struct `s`. The export is a global variable that contains note type id,
/// note name and information about note fields (field name, index and whether the field is nullable or not).
///
/// Example:
/// ```
/// struct TokenNoteFields_5695262104 {
/// amount: aztec::note::note_field::NoteField,
/// owner: aztec::note::note_field::NoteField
/// }
///
/// #[abi(notes)]
/// global TokenNote_EXPORTS_5695262104: (Field, str<8>, TokenNoteFields_5695262104) = (
/// 0,
/// "TokenNote",
/// TokenNoteFields_5695262104 {
/// amount: aztec::note::note_field::NoteField { index: 0, nullable: false },
/// owner: aztec::note::note_field::NoteField { index: 1, nullable: false }
/// }
/// );
///
/// Randomly looking value at the end of the export name is generated by hashing the note struct type and is included
/// to prevent naming collisions in case there are multiple notes with the same name imported in a contract.
pub(crate) comptime fn generate_note_export(
s: StructDefinition,
note_type_id: Field,
Expand Down Expand Up @@ -197,10 +271,24 @@ pub(crate) comptime fn generate_note_export(
}

#[abi(notes)]
global $global_export_name: (Field, str<$note_name_str_len>, $note_fields_name) = ($note_type_id,$note_name_as_str, $note_fields_name { $note_field_constructors });
global $global_export_name: (Field, str<$note_name_str_len>, $note_fields_name) = ($note_type_id, $note_name_as_str, $note_fields_name { $note_field_constructors });
}
}

/// Generates quotes necessary for multi-scalar multiplication of `indexed_fields` (indexed struct fields). Returns
/// a tuple containing quotes for generators, scalars, arguments and auxiliary variables. For more info on what are
/// auxiliary variables and how they are used, see `flatten_to_fields` function.
///
/// Example return values:
/// generators_list: [aztec::generators::Ga1, aztec::generators::Ga2, aztec::generators::Ga3, aztec::generators::Ga4]
/// scalars_list: [
/// std::hash::from_field_unsafe(amount.lo as Field),
/// std::hash::from_field_unsafe(amount.hi as Field),
/// std::hash::from_field_unsafe(npk_m_hash as Field),
/// std::hash::from_field_unsafe(randomness as Field)
/// ]
/// args_list: [amount: U128, npk_m_hash: Field, randomness: Field]
/// aux_vars: []
comptime fn generate_multi_scalar_mul(indexed_fields: [(Quoted, Type, u32)]) -> ([Quoted], [Quoted], [Quoted], Quoted) {
let mut generators_list = &[];
let mut scalars_list = &[];
Expand Down Expand Up @@ -230,6 +318,56 @@ comptime fn generate_multi_scalar_mul(indexed_fields: [(Quoted, Type, u32)]) ->
(generators_list, scalars_list, args_list, aux_vars)
}

/// Generates setup payload for a given note struct `s`. The setup payload contains log plaintext and hiding point.
///
/// Example:
/// ```
/// struct TokenNoteSetupPayload {
/// log_plaintext: [u8; 160],
/// hiding_point: aztec::protocol_types::point::Point
/// }
///
/// impl TokenNoteSetupPayload {
/// fn new(mut self, npk_m_hash: Field, randomness: Field, storage_slot: Field) -> TokenNoteSetupPayload {
/// let hiding_point = std::embedded_curve_ops::multi_scalar_mul(
/// [aztec::generators::Ga1, aztec::generators::Ga2, aztec::generators::G_slot],
/// [
/// std::hash::from_field_unsafe(npk_m_hash),
/// std::hash::from_field_unsafe(randomness),
/// std::hash::from_field_unsafe(storage_slot)
/// ]
/// );
///
/// let let storage_slot_bytes = storage_slot.to_be_bytes();
/// let let note_type_id_bytes = TokenNote::get_note_type_id().to_be_bytes();
///
/// for i in 0..32 {
/// log_plaintext[i] = storage_slot_bytes[i];
/// log_plaintext[32 + i] = note_type_id_bytes[i];
/// }
///
/// let serialized_note = [npk_m_hash as Field, randomness as Field];
///
/// for i in 0..serialized_note.len() {
/// let bytes: [u8; 32] = serialized_note[i].to_be_bytes();
/// for j in 0..32 {
/// log_plaintext[64 + i * 32 + j] = bytes[j];
/// }
/// }
///
/// TokenNoteSetupPayload {
/// log_plaintext,
/// hiding_point
/// }
/// }
/// }
///
/// impl aztec::protocol_types::traits::Empty for TokenNoteSetupPayload {
/// fn empty() -> Self {
/// Self { log_plaintext: [0; 160], hiding_point: aztec::protocol_types::point::Point::empty() }
/// }
/// }
/// ```
comptime fn generate_setup_payload(
s: StructDefinition,
indexed_fixed_fields: [(Quoted, Type, u32)],
Expand Down Expand Up @@ -278,6 +416,10 @@ comptime fn generate_setup_payload(
}, setup_payload_name)
}

/// Generates setup log plaintext for a given note struct `s`. The setup log plaintext is computed by serializing
/// storage slot from target function arguments, note type id from the note struct `s` and the fixed fields. The fixed
/// fields are obtained by passing the whole note struct to the `flatten_to_fields(...)` function but omitting the
/// `NoteHeader` and the nullable fields.
comptime fn get_setup_log_plaintext_body(
s: StructDefinition,
log_plaintext_length: u32,
Expand Down Expand Up @@ -322,6 +464,38 @@ comptime fn get_setup_log_plaintext_body(
}
}

/// Generates finalization payload for a given note struct `s`. The finalization payload contains log and note hash.
///
/// Example:
/// ```
/// struct TokenNoteFinalizationPayload {
/// log: [Field; 2],
/// note_hash: Field
/// }
///
/// impl TokenNoteFinalizationPayload {
/// fn new(mut self, hiding_point: aztec::protocol_types::point::Point, amount: U128) -> TokenNoteFinalizationPayload {
/// self.log = [amount.lo as Field, amount.hi as Field];
///
/// let finalization_hiding_point = std::embedded_curve_ops::multi_scalar_mul(
/// [aztec::generators::Ga3, aztec::generators::Ga4],
/// [
/// std::hash::from_field_unsafe(amount.lo),
/// std::hash::from_field_unsafe(amount.hi)
/// ]
/// ) + hiding_point;
///
/// self.note_hash = finalization_hiding_point.x;
/// self
/// }
/// }
///
/// impl aztec::protocol_types::traits::Empty for TokenNoteFinalizationPayload {
/// fn empty() -> Self {
/// Self { log: [0; 2], note_hash: 0 }
/// }
/// }
/// ```
comptime fn generate_finalization_payload(
s: StructDefinition,
indexed_fixed_fields: [(Quoted, Type, u32)],
Expand Down Expand Up @@ -385,11 +559,21 @@ comptime fn generate_finalization_payload(
}, finalization_payload_name)
}

comptime fn generate_partial_note_impl(
s: StructDefinition,
setup_payload_name: Quoted,
finalization_payload_name: Quoted
) -> Quoted {
/// Generates `PartialNote` implementation for a given note struct `s`.
///
/// Example:
/// ```
/// impl PartialNote<TokenNoteSetupPayload, TokenNoteFinalizationPayload> for TokenNote {
/// fn setup_payload() -> TokenNoteSetupPayload {
/// TokenNoteSetupPayload::empty()
/// }
///
/// fn finalization_payload() -> TokenNoteFinalizationPayload {
/// TokenNoteFinalizationPayload::empty()
/// }
/// }
/// ```
comptime fn generate_partial_note_impl(s: StructDefinition, setup_payload_name: Quoted, finalization_payload_name: Quoted) -> Quoted {
let name = s.name();
quote {
impl aztec::note::note_interface::PartialNote<$setup_payload_name, $finalization_payload_name> for $name {
Expand All @@ -404,6 +588,8 @@ comptime fn generate_partial_note_impl(
}
}

/// Registers a note struct `note` with the given `note_serialized_len`, `note_type_id`, `fixed_fields` and
/// `nullable_fields` in the global `NOTES` map.
comptime fn register_note(
note: StructDefinition,
note_serialized_len: u32,
Expand Down Expand Up @@ -453,28 +639,36 @@ comptime fn index_note_fields(
(indexed_fixed_fields, indexed_nullable_fields)
}

comptime fn common_note_annotation(s: StructDefinition) -> (Quoted, Field) {
// Automatically inject header if not present
/// Injects `NoteHeader` to the note struct if not present.
comptime fn inject_note_header(s: StructDefinition) {
let filtered_header = s.fields().filter(| (_, typ): (Quoted, Type) | typ == NOTE_HEADER_TYPE);
if (filtered_header.len() == 0) {
let new_fields = s.fields().push_back((quote { header }, NOTE_HEADER_TYPE));
s.set_fields(new_fields);
}
let note_properties = generate_note_properties(s);
let note_type_id = compute_note_type_id(s.name());

(quote {
$note_properties
}, note_type_id)
}

/// Injects `NoteHeader` to the note struct if not present and generates the following:
/// - NoteTypeProperties
/// - SetupPayload
/// - FinalizationPayload
/// - PartialNote trait implementation
/// - NoteExport
/// - NoteInterface trait implementation
/// - Registers the note in the global `NOTES` map.
///
/// For more details on the generated code, see the individual functions.
#[varargs]
pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> Quoted {
// We separate struct members into fixed ones and nullable ones and we store info about the start index of each
// member in the serialized note array.
let (indexed_fixed_fields, indexed_nullable_fields) = index_note_fields(s, nullable_fields);

let (common, note_type_id) = common_note_annotation(s);
// We inject NoteHeader if it's not present in the note struct.
inject_note_header(s);

let note_properties = generate_note_properties(s);
let note_type_id = compute_note_type_id(s.name());
let (setup_payload_impl, setup_payload_name) = generate_setup_payload(s, indexed_fixed_fields, indexed_nullable_fields);
let (finalization_payload_impl, finalization_payload_name) = generate_finalization_payload(s, indexed_fixed_fields, indexed_nullable_fields);
let (note_interface_impl, note_serialized_len) = generate_note_interface(s, note_type_id, indexed_fixed_fields, indexed_nullable_fields);
Expand All @@ -488,17 +682,28 @@ pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) ->
);

quote {
$common
$note_properties
$setup_payload_impl
$finalization_payload_impl
$note_interface_impl
$partial_note_impl
}
}

/// Injects `NoteHeader` to the note struct if not present and generates the following:
/// - NoteTypeProperties
/// - NoteInterface trait implementation
/// - Registers the note in the global `NOTES` map.
///
/// For more details on the generated code, see the individual functions.
pub comptime fn note(s: StructDefinition) -> Quoted {
let (indexed_fixed_fields, indexed_nullable_fields) = index_note_fields(s, &[]);
let (common, note_type_id) = common_note_annotation(s);

// We inject NoteHeader if it's not present in the note struct.
inject_note_header(s);

let note_properties = generate_note_properties(s);
let note_type_id = compute_note_type_id(s.name());
let (note_interface_impl, note_serialized_len) = generate_note_interface(s, note_type_id, indexed_fixed_fields, indexed_nullable_fields);
register_note(
s,
Expand All @@ -509,13 +714,21 @@ pub comptime fn note(s: StructDefinition) -> Quoted {
);

quote {
$common
$note_properties
$note_interface_impl
}
}

/// Injects `NoteHeader` to the note struct if not present and generates the following:
/// - NoteTypeProperties
///
/// For more details on the generated code, see the individual functions.
pub comptime fn note_custom_interface(s: StructDefinition) -> Quoted {
let (common, note_type_id) = common_note_annotation(s);
// We inject NoteHeader if it's not present in the note struct.
inject_note_header(s);

let note_properties = generate_note_properties(s);
let note_type_id = compute_note_type_id(s.name());
let serialized_len_type = fresh_type_variable();
let note_interface_impl = s.as_type().get_trait_impl(quote { crate::note::note_interface::NoteInterface<$serialized_len_type> }.as_trait_constraint());
let name = s.name();
Expand All @@ -532,6 +745,6 @@ pub comptime fn note_custom_interface(s: StructDefinition) -> Quoted {
);

quote {
$common
$note_properties
}
}
Loading
Loading