Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -867,17 +867,17 @@ void ContentAddressedCachedTreeStore<LeafValueType>::advance_finalized_block(con
ReadTransactionPtr readTx = create_read_transaction();
get_meta(uncommittedMeta);
get_meta(committedMeta, *readTx, false);
// do nothing if the block is already finalized
if (committedMeta.finalizedBlockHeight >= blockNumber) {
return;
}
if (!dataStore_->read_block_data(blockNumber, blockPayload, *readTx)) {
throw std::runtime_error(format("Unable to advance finalized block: ",
blockNumber,
". Failed to read block data. Tree name: ",
forkConstantData_.name_));
}
}
// do nothing if the block is already finalized
if (committedMeta.finalizedBlockHeight >= blockNumber) {
return;
}

// can currently only finalize up to the unfinalized block height
if (committedMeta.finalizedBlockHeight > committedMeta.unfinalizedBlockHeight) {
Expand Down
30 changes: 30 additions & 0 deletions docs/docs-developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@ Aztec is in active development. Each version may introduce breaking changes that

## TBD

### [Aztec.nr] `attempt_note_discovery` now takes two separate functions instead of one

The `attempt_note_discovery` function (and related discovery functions like `do_sync_state`, `process_message_ciphertext`) now takes separate `compute_note_hash` and `compute_note_nullifier` arguments instead of a single combined `compute_note_hash_and_nullifier`. The corresponding type aliases are now `ComputeNoteHash` and `ComputeNoteNullifier` (instead of `ComputeNoteHashAndNullifier`).

This split improves performance during nonce discovery: the note hash only needs to be computed once, while the old combined function recomputed it for every candidate nonce.

Most contracts are not affected, as the macro-generated `sync_state` and `process_message` functions handle this automatically. Only contracts that call `attempt_note_discovery` directly need to update.

**Migration:**

```diff
attempt_note_discovery(
contract_address,
tx_hash,
unique_note_hashes_in_tx,
first_nullifier_in_tx,
recipient,
- _compute_note_hash_and_nullifier,
+ _compute_note_hash,
+ _compute_note_nullifier,
owner,
storage_slot,
randomness,
note_type_id,
packed_note,
);
```

**Impact**: Contracts that call `attempt_note_discovery` or related discovery functions directly with a custom `_compute_note_hash_and_nullifier` argument. The old combined function is still generated (deprecated) but is no longer used by the framework. Additionally, if you had a custom `_compute_note_hash_and_nullifier` function then compilation will now fail as you'll need to also produce the corresponding `_compute_note_hash` and `_compute_note_nullifier` functions.

### Two separate init nullifiers for private and public

Contract initialization now emits two separate nullifiers instead of one: a **private init nullifier** and a **public init nullifier**. Each nullifier gates its respective execution domain:
Expand Down
177 changes: 16 additions & 161 deletions noir-projects/aztec-nr/aztec/src/macros/aztec.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod compute_note_hash_and_nullifier;

use crate::macros::{
calls_generation::{
external_functions::{generate_external_function_calls, generate_external_function_self_calls_structs},
Expand All @@ -6,14 +8,12 @@ use crate::macros::{
dispatch::generate_public_dispatch,
emit_public_init_nullifier::generate_emit_public_init_nullifier,
internals_functions_generation::{create_fn_abi_exports, process_functions},
notes::NOTES,
storage::STORAGE_LAYOUT_NAME,
utils::{
get_trait_impl_method, is_fn_contract_library_method, is_fn_external, is_fn_internal, is_fn_test,
module_has_storage,
},
utils::{is_fn_contract_library_method, is_fn_external, is_fn_internal, is_fn_test, module_has_storage},
};

use compute_note_hash_and_nullifier::generate_contract_library_methods_compute_note_hash_and_nullifier;

/// Marks a contract as an Aztec contract, generating the interfaces for its functions and notes, as well as injecting
/// the `sync_state` utility function.
///
Expand All @@ -34,12 +34,16 @@ pub comptime fn aztec(m: Module) -> Quoted {
// We generate ABI exports for all the external functions in the contract.
let fn_abi_exports = create_fn_abi_exports(m);

// We generate `_compute_note_hash_and_nullifier`, `sync_state` and `process_message` functions only if they are
// not already implemented. If they are implemented we just insert empty quotes.
// We generate `_compute_note_hash`, `_compute_note_nullifier` (and the deprecated
// `_compute_note_hash_and_nullifier` wrapper), `sync_state` and `process_message` functions only if they are not
// already implemented. If they are implemented we just insert empty quotes.
let contract_library_method_compute_note_hash_and_nullifier = if !m.functions().any(|f| {
// Note that we don't test for `_compute_note_hash` or `_compute_note_nullifier` in order to make this simpler
// - users must either implement all three or none.
// Down the line we'll remove this check and use `AztecConfig`.
f.name() == quote { _compute_note_hash_and_nullifier }
}) {
generate_contract_library_method_compute_note_hash_and_nullifier()
generate_contract_library_methods_compute_note_hash_and_nullifier()
} else {
quote {}
};
Expand Down Expand Up @@ -143,157 +147,6 @@ comptime fn generate_contract_interface(m: Module) -> Quoted {
}
}

/// Generates a contract library method called `_compute_note_hash_and_nullifier` which is used for note discovery (to
/// create the `aztec::messages::discovery::ComputeNoteHashAndNullifier` function) and to implement the
/// `compute_note_hash_and_nullifier` unconstrained contract function.
comptime fn generate_contract_library_method_compute_note_hash_and_nullifier() -> Quoted {
if NOTES.len() > 0 {
// Contracts that do define notes produce an if-else chain where `note_type_id` is matched against the
// `get_note_type_id()` function of each note type that we know of, in order to identify the note type. Once we
// know it we call we correct `unpack` method from the `Packable` trait to obtain the underlying note type, and
// compute the note hash (non-siloed) and inner nullifier (also non-siloed).

let mut if_note_type_id_match_statements_list = @[];
for i in 0..NOTES.len() {
let typ = NOTES.get(i);

let get_note_type_id = get_trait_impl_method(
typ,
quote { crate::note::note_interface::NoteType },
quote { get_id },
);
let unpack = get_trait_impl_method(
typ,
quote { crate::protocol::traits::Packable },
quote { unpack },
);

let compute_note_hash = get_trait_impl_method(
typ,
quote { crate::note::note_interface::NoteHash },
quote { compute_note_hash },
);

let compute_nullifier_unconstrained = get_trait_impl_method(
typ,
quote { crate::note::note_interface::NoteHash },
quote { compute_nullifier_unconstrained },
);

let if_or_else_if = if i == 0 {
quote { if }
} else {
quote { else if }
};

if_note_type_id_match_statements_list = if_note_type_id_match_statements_list.push_back(
quote {
$if_or_else_if note_type_id == $get_note_type_id() {
// As an extra safety check we make sure that the packed_note BoundedVec has the expected
// length, since we're about to interpret its raw storage as a fixed-size array by calling the
// unpack function on it.
let expected_len = <$typ as $crate::protocol::traits::Packable>::N;
let actual_len = packed_note.len();
if actual_len != expected_len {
aztec::protocol::logging::warn_log_format(
"[aztec-nr] Packed note length mismatch for note type id {2}: expected {0} fields, got {1}. Skipping note.",
[expected_len as Field, actual_len as Field, note_type_id],
);
Option::none()
} else {
let note = $unpack(aztec::utils::array::subarray(packed_note.storage(), 0));

let note_hash = $compute_note_hash(note, owner, storage_slot, randomness);

// The message discovery process finds settled notes, that is, notes that were created in
// prior transactions and are therefore already part of the note hash tree. We therefore
// compute the nullification note hash by treating the note as a settled note with the
// provided note nonce.
let note_hash_for_nullification =
aztec::note::utils::compute_note_hash_for_nullification(
aztec::note::HintedNote {
note,
contract_address,
owner,
randomness,
storage_slot,
metadata:
aztec::note::note_metadata::SettledNoteMetadata::new(
note_nonce,
)
.into(),
},
);

let inner_nullifier = $compute_nullifier_unconstrained(
note,
owner,
note_hash_for_nullification,
);

Option::some(
aztec::messages::discovery::NoteHashAndNullifier {
note_hash,
inner_nullifier,
},
)
}
}
},
);
}

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

quote {
/// Unpacks an array into a note corresponding to `note_type_id` and then computes its note hash (non-siloed) and inner nullifier (non-siloed) assuming the note has been inserted into the note hash tree with `note_nonce`.
///
/// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteHashAndNullifier` type, and so it can be used to call functions from that module such as `do_sync_state` and `attempt_note_discovery`.
///
/// This function is automatically injected by the `#[aztec]` macro.
#[contract_library_method]
unconstrained fn _compute_note_hash_and_nullifier(
packed_note: BoundedVec<Field, aztec::messages::logs::note::MAX_NOTE_PACKED_LEN>,
owner: aztec::protocol::address::AztecAddress,
storage_slot: Field,
note_type_id: Field,
contract_address: aztec::protocol::address::AztecAddress,
randomness: Field,
note_nonce: Field,
) -> Option<aztec::messages::discovery::NoteHashAndNullifier> {
$if_note_type_id_match_statements
else {
aztec::protocol::logging::warn_log_format(
"[aztec-nr] Unknown note type id {0}. Skipping note.",
[note_type_id],
);
Option::none()
}
}
}
} else {
// Contracts with no notes still implement this function to avoid having special-casing, the implementation
// simply throws immediately.
quote {
/// This contract does not use private notes, so this function should never be called as it will unconditionally fail.
///
/// This function is automatically injected by the `#[aztec]` macro.
#[contract_library_method]
unconstrained fn _compute_note_hash_and_nullifier(
_packed_note: BoundedVec<Field, aztec::messages::logs::note::MAX_NOTE_PACKED_LEN>,
_owner: aztec::protocol::address::AztecAddress,
_storage_slot: Field,
_note_type_id: Field,
_contract_address: aztec::protocol::address::AztecAddress,
_randomness: Field,
_nonce: Field,
) -> Option<aztec::messages::discovery::NoteHashAndNullifier> {
panic(f"This contract does not use private notes")
}
}
}
}

/// Generates the `sync_state` utility function that performs message discovery.
comptime fn generate_sync_state() -> Quoted {
quote {
Expand All @@ -309,7 +162,8 @@ comptime fn generate_sync_state() -> Quoted {
let address = aztec::context::UtilityContext::new().this_address();
aztec::messages::discovery::do_sync_state(
address,
_compute_note_hash_and_nullifier,
_compute_note_hash,
_compute_note_nullifier,
Option::some(aztec::messages::processing::offchain::sync_inbox),
);
}
Expand Down Expand Up @@ -337,7 +191,8 @@ comptime fn generate_process_message() -> Quoted {

aztec::messages::discovery::process_message::process_message_ciphertext(
address,
_compute_note_hash_and_nullifier,
_compute_note_hash,
_compute_note_nullifier,
message_ciphertext,
message_context,
);
Expand Down
Loading
Loading