Skip to content
29 changes: 29 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,35 @@ 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**: Only 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.

### [Aztec.js] `TxReceipt` now includes `epochNumber`

Expand Down
244 changes: 179 additions & 65 deletions noir-projects/aztec-nr/aztec/src/macros/aztec.nr

Large diffs are not rendered by default.

98 changes: 61 additions & 37 deletions noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,14 @@ pub struct NoteHashAndNullifier {
pub inner_nullifier: Option<Field>,
}

/// A contract's way of computing note hashes and nullifiers.
/// A contract's way of computing note hashes.
///
/// Each contract in the network is free to compute their note's hash and nullifiers as they see fit - the hash
/// function itself and the nullifier derivation are not enshrined or standardized. Some aztec-nr functions however do
/// need to know the details of this computation (e.g. when finding new notes), which is what this type represents.
/// Each contract in the network is free to compute their note's hash as they see fit - the hash function itself is not
/// enshrined or standardized. Some aztec-nr functions however do need to know the details of this computation (e.g.
/// when finding new notes), which is what this type represents.
///
/// This function takes a note's packed content, storage slot, note type ID, address of the emitting contract,
/// randomness and note nonce, and attempts to compute its inner note hash (not siloed by address nor uniqued by nonce)
/// and inner nullifier (not siloed by address).
/// This function takes a note's packed content, storage slot, note type ID, address of the emitting contract and
/// randomness, and attempts to compute its inner note hash (not siloed by address nor uniqued by nonce).
///
/// ## Transient Notes
///
Expand All @@ -51,31 +50,16 @@ pub struct NoteHashAndNullifier {
///
/// The [`[#aztec]`](crate::macros::aztec::aztec) macro automatically creates a correct implementation of this function
/// for each contract by inspecting all note types in use and the storage layout. This injected function is a
/// `#[contract_library_method]` called `_compute_note_hash_and_nullifier`, and it looks something like this:
/// `#[contract_library_method]` called `_compute_note_hash`, and it looks something like this:
///
/// ```noir
/// |packed_note, owner, storage_slot, note_type_id, contract_address, randomness, note_nonce| {
/// |packed_note, owner, storage_slot, note_type_id, _contract_address, randomness| {
/// if note_type_id == MyNoteType::get_id() {
/// if packed_note.len() != MY_NOTE_TYPE_SERIALIZATION_LENGTH {
/// Option::none()
/// } else {
/// let note = MyNoteType::unpack(aztec::utils::array::subarray(packed_note.storage(), 0));
///
/// let note_hash = note.compute_note_hash(owner, storage_slot, randomness);
/// let note_hash_for_nullification = aztec::note::utils::compute_note_hash_for_nullification(
/// HintedNote {
/// note, contract_address, owner, randomness, storage_slot,
/// metadata: SettledNoteMetadata::new(note_nonce).into(),
/// },
/// );
///
/// let inner_nullifier = note.compute_nullifier_unconstrained(owner, note_hash_for_nullification);
///
/// Option::some(
/// aztec::messages::discovery::NoteHashAndNullifier {
/// note_hash, inner_nullifier
/// }
/// )
/// Option::some(note.compute_note_hash(owner, storage_slot, randomness))
/// }
/// } else if note_type_id == MyOtherNoteType::get_id() {
/// ... // Similar to above but calling MyOtherNoteType::unpack
Expand All @@ -84,9 +68,30 @@ pub struct NoteHashAndNullifier {
/// };
/// }
/// ```
pub type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /*
pub type ComputeNoteHash = unconstrained fn(/* packed_note */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /*
owner */ AztecAddress, /* storage_slot */ Field, /* note_type_id */ Field, /* contract_address */ AztecAddress, /*
randomness */ Field, /* note nonce */ Field) -> Option<NoteHashAndNullifier>;
randomness */ Field) -> Option<Field>;

/// A contract's way of computing note nullifiers.
///
/// Like [`ComputeNoteHash`], each contract is free to derive nullifiers as they see fit. This function takes the
/// unique note hash (used as the note hash for nullification for settled notes), plus the note's packed content and
/// metadata, and attempts to compute the inner nullifier (not siloed by address).
///
/// ## Automatic Implementation
///
/// The [`[#aztec]`](crate::macros::aztec::aztec) macro automatically creates a correct implementation of this function
/// for each contract called `_compute_note_nullifier`. It dispatches on `note_type_id` similarly to
/// [`ComputeNoteHash`], then calls the note's
/// [`compute_nullifier_unconstrained`](crate::note::note_interface::NoteHash::compute_nullifier_unconstrained) method.
pub type ComputeNoteNullifier = unconstrained fn(/* unique_note_hash */Field, /* packed_note */ BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /*
Comment thread
nventuro marked this conversation as resolved.
Outdated
* owner */ AztecAddress, /* storage_slot */ Field, /* note_type_id */ Field, /*
Comment thread
nventuro marked this conversation as resolved.
Outdated
contract_address */ AztecAddress, /* randomness */ Field) -> Option<Field>;

/// Deprecated: use [`ComputeNoteHash`] and [`ComputeNoteNullifier`] instead.
pub type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /*
* owner */ AztecAddress, /* storage_slot */ Field, /* note_type_id */ Field, /*
contract_address */ AztecAddress, /* randomness */ Field, /* note nonce */ Field) -> Option<NoteHashAndNullifier>;

/// A handler for custom messages.
///
Expand All @@ -108,9 +113,10 @@ pub type CustomMessageHandler<Env> = unconstrained fn[Env](
///
/// The private state will be synchronized up to the block that will be used for private transactions (i.e. the anchor
/// block. This will typically be close to the tip of the chain.
pub unconstrained fn do_sync_state<ComputeNoteHashAndNullifierEnv, CustomMessageHandlerEnv>(
pub unconstrained fn do_sync_state<CustomMessageHandlerEnv>(
contract_address: AztecAddress,
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<ComputeNoteHashAndNullifierEnv>,
compute_note_hash: ComputeNoteHash,
compute_note_nullifier: ComputeNoteNullifier,
process_custom_message: Option<CustomMessageHandler<CustomMessageHandlerEnv>>,
offchain_inbox_sync: Option<OffchainInboxSync<()>>,
) {
Expand All @@ -130,7 +136,8 @@ pub unconstrained fn do_sync_state<ComputeNoteHashAndNullifierEnv, CustomMessage

process_message_ciphertext(
contract_address,
compute_note_hash_and_nullifier,
compute_note_hash,
compute_note_nullifier,
process_custom_message,
message_ciphertext,
pending_tagged_log.context,
Expand All @@ -149,7 +156,8 @@ pub unconstrained fn do_sync_state<ComputeNoteHashAndNullifierEnv, CustomMessage
msgs.for_each(|i, msg| {
process_message_ciphertext(
contract_address,
compute_note_hash_and_nullifier,
compute_note_hash,
compute_note_nullifier,
process_custom_message,
msg.message_ciphertext,
msg.message_context,
Expand All @@ -162,7 +170,11 @@ pub unconstrained fn do_sync_state<ComputeNoteHashAndNullifierEnv, CustomMessage

// Then we process all pending partial notes, regardless of whether they were found in the current or previous
// executions.
partial_notes::fetch_and_process_partial_note_completion_logs(contract_address, compute_note_hash_and_nullifier);
partial_notes::fetch_and_process_partial_note_completion_logs(
contract_address,
compute_note_hash,
compute_note_nullifier,
);

// Finally we validate all notes and events that were found as part of the previous processes, resulting in them
// being added to PXE's database and retrievable via oracles (get_notes) and our TS API (PXE::getPrivateEvents).
Expand All @@ -173,7 +185,7 @@ mod test {
use crate::{
capsules::CapsuleArray,
messages::{
discovery::{CustomMessageHandler, do_sync_state, NoteHashAndNullifier},
discovery::{CustomMessageHandler, do_sync_state},
logs::note::MAX_NOTE_PACKED_LEN,
processing::{
offchain::OffchainInboxSync, pending_tagged_log::PendingTaggedLog, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT,
Expand All @@ -200,7 +212,8 @@ mod test {
let no_inbox_sync: Option<OffchainInboxSync<()>> = Option::none();
do_sync_state(
contract_address,
dummy_compute_nhnn,
dummy_compute_note_hash,
dummy_compute_note_nullifier,
no_handler,
no_inbox_sync,
);
Expand All @@ -209,15 +222,26 @@ mod test {
});
}

unconstrained fn dummy_compute_nhnn(
unconstrained fn dummy_compute_note_hash(
_packed_note: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
_owner: AztecAddress,
_storage_slot: Field,
_note_type_id: Field,
_contract_address: AztecAddress,
_randomness: Field,
) -> Option<Field> {
Option::none()
}

unconstrained fn dummy_compute_note_nullifier(
_unique_note_hash: Field,
_packed_note: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
_owner: AztecAddress,
_storage_slot: Field,
_note_type_id: Field,
_contract_address: AztecAddress,
_randomness: Field,
_note_nonce: Field,
) -> Option<NoteHashAndNullifier> {
) -> Option<Field> {
Option::none()
}
}
Loading
Loading