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
14 changes: 7 additions & 7 deletions noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr
Original file line number Diff line number Diff line change
Expand Up @@ -467,13 +467,13 @@ impl<let N: u32> CallInterface<N> for PublicStaticVoidCallInterface<N> {
}

pub struct PublicStaticVoidCallInterface<let N: u32> {
target_contract: AztecAddress,
selector: FunctionSelector,
name: str<N>,
args: [Field],
return_type: (),
is_static: bool,
gas_opts: GasOpts,
pub target_contract: AztecAddress,
pub selector: FunctionSelector,
pub name: str<N>,
pub args: [Field],
pub return_type: (),
pub is_static: bool,
pub gas_opts: GasOpts,
}

impl<let N: u32> PublicStaticVoidCallInterface<N> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fn extract_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_pose
bytes
}

// TODO(#10537): Consider nuking this function.
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.

Unrelated change. Linked the issue here when I was doing old issues cleanup.

fn extract_close_to_uniformly_random_256_bits_from_ecdh_shared_secret_using_sha256(
shared_secret: Point,
) -> [u8; 32] {
Expand Down Expand Up @@ -57,6 +58,7 @@ fn derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret(
(sym_key, iv)
}

// TODO(#10537): Consider nuking this function.
pub fn derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_sha256(
shared_secret: Point,
) -> ([u8; 16], [u8; 16]) {
Expand All @@ -66,6 +68,7 @@ pub fn derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_sha256(
)
}

// TODO(#10537): This function is currently unused. Consider using it instead of the sha256 one.
pub fn derive_aes_symmetric_key_and_iv_from_ecdh_shared_secret_using_poseidon2(
shared_secret: Point,
) -> ([u8; 16], [u8; 16]) {
Expand Down
14 changes: 11 additions & 3 deletions noir-projects/aztec-nr/aztec/src/macros/functions/interfaces.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::macros::utils::{
add_to_field_slice, compute_fn_selector, get_fn_visibility, is_fn_private, is_fn_public,
is_fn_view,
add_to_field_slice, compute_fn_selector, get_fn_visibility, get_trait_impl_method,
is_fn_private, is_fn_public, is_fn_view,
};
use std::{
collections::umap::UHashMap,
Expand Down Expand Up @@ -126,10 +126,18 @@ pub comptime fn stub_fn(f: FunctionDefinition) -> Quoted {
quote {}
};

let function_selector_typ =
quote { protocol_types::abis::function_selector::FunctionSelector }.as_type();
let from_field = get_trait_impl_method(
function_selector_typ,
quote { protocol_types::traits::FromField },
quote { from_field },
);

quote {
pub fn $fn_name(self, $fn_parameters_list) -> $call_interface_name<$call_interface_generics> {
$args
let selector = dep::aztec::protocol_types::abis::function_selector::FunctionSelector::from_field($fn_selector);
let selector = $from_field($fn_selector);
$call_interface_name {
target_contract: self.target_contract,
selector,
Expand Down
160 changes: 96 additions & 64 deletions noir-projects/aztec-nr/aztec/src/macros/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use storage::STORAGE_LAYOUT_NAME;

use dispatch::generate_public_dispatch;
use functions::transform_unconstrained;
use utils::module_has_storage;
use utils::{get_trait_impl_method, module_has_storage};

/// Marks a contract as an Aztec contract, generating the interfaces for its functions and notes, as well as injecting
/// the `compute_note_hash_and_optionally_a_nullifier` function PXE requires in order to validate notes.
Expand Down Expand Up @@ -114,7 +114,7 @@ comptime fn generate_contract_interface(m: Module) -> Quoted {
comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted {
let mut max_note_packed_len: u32 = 0;
let notes = NOTES.entries();
let body = if notes.len() > 0 {
if notes.len() > 0 {
max_note_packed_len = notes.fold(
0,
|acc, (_, (_, len, _, _)): (Type, (StructDefinition, u32, Field, [(Quoted, u32, bool)]))| {
Expand All @@ -130,15 +130,27 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted {

for i in 0..notes.len() {
let (typ, (_, _, _, _)) = notes[i];

let get_note_type_id = get_trait_impl_method(
typ,
quote { crate::note::note_interface::NoteInterface },
quote { get_note_type_id },
);
let unpack = get_trait_impl_method(
typ,
quote { crate::protocol_types::traits::Packable<_> },
quote { unpack },
);

let if_or_else_if = if i == 0 {
quote { if }
} else {
quote { else if }
};
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::unpack, contract_address, nonce, compute_nullifier, storage_slot, packed_note)
$if_or_else_if note_type_id == $get_note_type_id() {
aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($unpack, contract_address, nonce, compute_nullifier, storage_slot, packed_note)
}
},
);
Expand All @@ -147,27 +159,32 @@ comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted {
let if_statements = if_statements_list.join(quote {});

quote {
$if_statements
else {
panic(f"Unknown note type ID: {note_type_id}")
unconstrained fn compute_note_hash_and_optionally_a_nullifier(
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.

Had to do this change because if we returned a body from the if and then return the final quote from the function later we would get warnings in cases where there are no notes as the function args would be unused.

contract_address: aztec::protocol_types::address::AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
compute_nullifier: bool,
packed_note: [Field; $max_note_packed_len],
) -> pub [Field; 4] {
$if_statements
else {
panic(f"Unknown note type ID: {note_type_id}")
}
}
}
} else {
quote {
panic(f"No notes defined")
}
};

quote {
unconstrained fn compute_note_hash_and_optionally_a_nullifier(
contract_address: aztec::protocol_types::address::AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
compute_nullifier: bool,
packed_note: [Field; $max_note_packed_len],
) -> pub [Field; 4] {
$body
unconstrained fn compute_note_hash_and_optionally_a_nullifier(
_contract_address: aztec::protocol_types::address::AztecAddress,
_nonce: Field,
_storage_slot: Field,
_note_type_id: Field,
_compute_nullifier: bool,
_packed_note: [Field; $max_note_packed_len],
) -> pub [Field; 4] {
panic(f"No notes defined")
}
}
}
}
Expand Down Expand Up @@ -216,6 +233,17 @@ comptime fn generate_process_log() -> Quoted {
for i in 0..notes.len() {
let (typ, (_, packed_note_length, _, _)) = notes[i];

let get_note_type_id = get_trait_impl_method(
typ,
quote { crate::note::note_interface::NoteInterface },
quote { get_note_type_id },
);
let unpack = get_trait_impl_method(
typ,
quote { crate::protocol_types::traits::Packable<_> },
quote { unpack },
);

let if_or_else_if = if i == 0 {
quote { if }
} else {
Expand All @@ -224,7 +252,7 @@ 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() {
$if_or_else_if note_type_id == $get_note_type_id() {
// As an extra safety check we make sure that the packed_note 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.
Expand All @@ -235,59 +263,63 @@ comptime fn generate_process_log() -> Quoted {
f"Expected packed note 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::unpack, contract_address, nonce, true, storage_slot, packed_note.storage())
aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($unpack, contract_address, nonce, true, storage_slot, packed_note.storage())
}
},
);
}

let if_note_type_id_match_statements = if_note_type_id_match_statements_list.join(quote {});

let body = if notes.len() > 0 {
if notes.len() > 0 {
quote {
// Because this unconstrained function is injected after the contract is processed by the macros, it'll not
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.

Had to do this change because if we returned a body from the if and then return the final quote from the function later we would get warnings in cases where there are no notes as the function args would be unused.

// be modified by the macros that alter unconstrained functions. As such, we need to manually inject the
// unconstrained execution context since it will not be available otherwise.
let context = dep::aztec::context::unconstrained_context::UnconstrainedContext::new();

dep::aztec::note::discovery::do_process_log(
context,
log_plaintext,
tx_hash,
unique_note_hashes_in_tx,
first_nullifier_in_tx,
recipient,
|packed_note: BoundedVec<Field, _>, contract_address, nonce, storage_slot, note_type_id| {
let hashes = $if_note_type_id_match_statements
else {
panic(f"Unknown note type id {note_type_id}")
};

Option::some(
dep::aztec::note::discovery::NoteHashesAndNullifier {
note_hash: hashes[0],
unique_note_hash: hashes[1],
inner_nullifier: hashes[3],
},
)
}
);
unconstrained fn process_log(
log_plaintext: BoundedVec<Field, dep::aztec::protocol_types::constants::PRIVATE_LOG_SIZE_IN_FIELDS>,
tx_hash: Field,
unique_note_hashes_in_tx: BoundedVec<Field, dep::aztec::protocol_types::constants::MAX_NOTE_HASHES_PER_TX>,
first_nullifier_in_tx: Field,
recipient: aztec::protocol_types::address::AztecAddress,
) {
// Because this unconstrained function is injected after the contract is processed by the macros, it'll not
// be modified by the macros that alter unconstrained functions. As such, we need to manually inject the
// unconstrained execution context since it will not be available otherwise.
let context = dep::aztec::context::unconstrained_context::UnconstrainedContext::new();

dep::aztec::note::discovery::do_process_log(
context,
log_plaintext,
tx_hash,
unique_note_hashes_in_tx,
first_nullifier_in_tx,
recipient,
|packed_note: BoundedVec<Field, _>, contract_address, nonce, storage_slot, note_type_id| {
let hashes = $if_note_type_id_match_statements
else {
panic(f"Unknown note type id {note_type_id}")
};

Option::some(
dep::aztec::note::discovery::NoteHashesAndNullifier {
note_hash: hashes[0],
unique_note_hash: hashes[1],
inner_nullifier: hashes[3],
},
)
}
);
}
}
} else {
quote {
panic(f"No notes defined")
}
};

quote {
unconstrained fn process_log(
log_plaintext: BoundedVec<Field, dep::aztec::protocol_types::constants::PRIVATE_LOG_SIZE_IN_FIELDS>,
tx_hash: Field,
unique_note_hashes_in_tx: BoundedVec<Field, dep::aztec::protocol_types::constants::MAX_NOTE_HASHES_PER_TX>,
first_nullifier_in_tx: Field,
recipient: aztec::protocol_types::address::AztecAddress,
) {
$body
unconstrained fn process_log(
_log_plaintext: BoundedVec<Field, dep::aztec::protocol_types::constants::PRIVATE_LOG_SIZE_IN_FIELDS>,
_tx_hash: Field,
_unique_note_hashes_in_tx: BoundedVec<Field, dep::aztec::protocol_types::constants::MAX_NOTE_HASHES_PER_TX>,
_first_nullifier_in_tx: Field,
_recipient: aztec::protocol_types::address::AztecAddress,
) {
panic(f"No notes defined")
}
}
}
}
Expand Down
21 changes: 19 additions & 2 deletions noir-projects/aztec-nr/aztec/src/macros/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,16 @@ pub(crate) comptime fn add_to_field_slice(slice_name: Quoted, name: Quoted, typ:
if typ.is_field() | typ.as_integer().is_some() | typ.is_bool() {
quote { $slice_name = $slice_name.push_back($name as Field); }
} else if typ.as_struct().is_some() {
quote { $slice_name = $slice_name.append($name.serialize()); }
// We invoke serialize as a static trait function rather than calling $name.serialize() directly in the quote
// to avoid "trait not in scope" compiler warnings.
quote { $slice_name = $slice_name.append(aztec::protocol_types::traits::Serialize::serialize($name)); }
} else if typ.as_array().is_some() {
let (element_type, _) = typ.as_array().unwrap();
let serialized_name = f"{name}_serialized".quoted_contents();
// We invoke serialize as a static trait function rather than calling x.serialize() directly in the quote
// to avoid "trait not in scope" compiler warnings.
quote {
let $serialized_name = $name.map(|x: $element_type | x.serialize());
let $serialized_name = $name.map(|x: $element_type | aztec::protocol_types::traits::Serialize::serialize(x));
for i in 0..$name.len() {
$slice_name = $slice_name.append($serialized_name[i].as_slice());
}
Expand Down Expand Up @@ -251,3 +255,16 @@ pub(crate) comptime fn is_note(typ: Type) -> bool {
| def.has_named_attribute("note_custom_interface")
})
}

/// Returns the typed expression of a trait method implementation. This is preferred over directly inlining with
/// `$typ::target_method()` in a quote, as direct inlining would result in missing import warnings in the generated
/// code (specifically, warnings that the trait implementation is not in scope).
pub(crate) comptime fn get_trait_impl_method(
typ: Type,
target_trait: Quoted,
target_method: Quoted,
) -> TypedExpr {
let trait_constraint = target_trait.as_trait_constraint();
typ.get_trait_impl(trait_constraint).unwrap().methods().filter(|m| m.name() == target_method)[0]
.as_typed_expr()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ mod utils;

use dep::aztec::macros::aztec;

/// The purpose of this contract is to perform a check in public without revealing what contract enqued the public
/// call. This is achieved by having a private function on this contract that enques the public call and hence
/// The purpose of this contract is to perform a check in public without revealing what contract enqueued the public
/// call. This is achieved by having a private function on this contract that enqueues the public call and hence
/// the `msg_sender` in the public call is the address of this contract.
#[aztec]
pub contract Router {
Expand Down