Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c0b5915
cherry-pick #20512: docs: add delayedpublicmutable apiref
AztecBot Mar 18, 2026
8d423c7
cherry-pick #20379: feat: custom message handlers in Aztec.nr
AztecBot Mar 18, 2026
4918a47
cherry-pick #20831: feat!: make unused msg disco fns private
AztecBot Mar 18, 2026
fd6179d
cherry-pick #21024: feat: add compile-time size check for events
AztecBot Mar 18, 2026
c915a28
cherry-pick #21134: chore: add warning on invalid recipients
AztecBot Mar 18, 2026
0c59791
cherry-pick #21072: feat: add aztecaddress::is_valid
AztecBot Mar 18, 2026
45d4197
cherry-pick #21186: chore: use returns true for boolean fns
AztecBot Mar 18, 2026
69ce7f5
cherry-pick #21189: feat: add note hash and nullifier helper functions
AztecBot Mar 18, 2026
2356f1b
cherry-pick #21229: docs: small delayedpubmut update
AztecBot Mar 18, 2026
a6d0b41
cherry-pick #21228: test: restore pubmut tests
AztecBot Mar 18, 2026
fb2c59b
cherry-pick #21234: fix: claim contract & improve nullif docs
AztecBot Mar 18, 2026
759f1ee
cherry-pick #21639: feat!: split compute note hash and nullifier
AztecBot Mar 18, 2026
d30ef0c
fix: resolve cherry-pick conflicts for all 12 backported PRs
AztecBot Mar 18, 2026
9c6d3f4
Update public_immutable.nr
nventuro Mar 18, 2026
23c325b
chore: run nargo fmt to fix formatting
AztecBot Mar 18, 2026
7ac1aa6
Reduce CHANGE_AUTHORIZED_DELAY from 360 to 180 seconds
nventuro Mar 18, 2026
8121f1d
fix: remove unused NOTES import in aztec.nr
AztecBot Mar 18, 2026
03843ab
fix: adapt e2e_custom_message.test.ts to v4-next API (DeployResultMin…
AztecBot Mar 18, 2026
994ac53
fix: restore missing docs:start:delayed_public_mutable_storage tag in…
AztecBot Mar 18, 2026
088bb89
fix: move invalid_event struct inside contract for v4-next compat
AztecBot Mar 19, 2026
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
2 changes: 1 addition & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@
"noirup",
"allow_phase_change",
"nophasecheck",
"nullifer",
"Nullifiable",
"offchain",
"onchain",
Expand Down Expand Up @@ -367,6 +366,7 @@
"unfinalized",
"uniquified",
"uniquify",
"unlinkability",
"unkonstrained",
"unnullify",
"unpadded",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1987,7 +1987,7 @@ Doing the changes is as straightforward as:

`UintNote` has also been updated to use the native `u128` type.

### [aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifer`
### [aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifier`

This function is no longer mandatory for contracts, and the `#[aztec]` macro no longer injects it.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2684,7 +2684,7 @@ Doing the changes is as straightforward as:

`UintNote` has also been updated to use the native `u128` type.

### [aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifer`
### [aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifier`

This function is no longer mandatory for contracts, and the `#[aztec]` macro no longer injects it.

Expand Down
73 changes: 63 additions & 10 deletions docs/docs-developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,46 @@ 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.

### [Aztec.nr] Made `compute_note_hash_for_nullification` unconstrained

This function shouldn't have been constrained in the first place, as constrained computation of `HintedNote` nullifiers is dangerous (constrained computation of nullifiers can be performed only on the `ConfirmedNote` type). If you were calling this from a constrained function, consider using `compute_confirmed_note_hash_for_nullification` instead. Unconstrained usage is safe.

### [Aztec.nr] Changes to standard note hash computation

Note hashes used to be computed with the storage slot being the last value of the preimage, it is now the first. This is to make it easier to ensure all note hashes have proper domain separation.

This change requires no input from your side unless you were testing or relying on hardcoded note hashes.

### Private initialization nullifier now includes `init_hash`

The private initialization nullifier is no longer derived from just the contract address. It is now computed as a Poseidon2 hash of `[address, init_hash]` using a dedicated domain separator. This prevents observers from determining whether a fully private contract has been initialized by simply knowing its address.
Expand All @@ -25,6 +65,7 @@ If you use `assert_contract_was_initialized_by` or `assert_contract_was_not_init
+ instance.initialization_hash,
);
```

### 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 Expand Up @@ -138,9 +179,14 @@ When using `NO_WAIT`, returns `{ txHash, offchainEffects, offchainMessages }` in
Offchain messages emitted by the transaction are available on the result:

```typescript
const { receipt, offchainMessages } = await contract.methods.foo(args).send({ from: sender });
const { receipt, offchainMessages } = await contract.methods
.foo(args)
.send({ from: sender });
for (const msg of offchainMessages) {
console.log(`Message for ${msg.recipient} from contract ${msg.contractAddress}:`, msg.payload);
console.log(
`Message for ${msg.recipient} from contract ${msg.contractAddress}:`,
msg.payload,
);
}
```

Expand Down Expand Up @@ -195,6 +241,7 @@ counter/
This enables adding multiple contracts to a single workspace. Running `aztec new <name>` inside an existing workspace (a directory with a `Nargo.toml` containing `[workspace]`) now adds a new `<name>_contract` and `<name>_test` crate pair to the workspace instead of creating a new directory.

**What changed:**

- Crate directories are now `<name>_contract/` and `<name>_test/` instead of `contract/` and `test/`.
- Contract code is now at `<name>_contract/src/main.nr` instead of `contract/src/main.nr`.
- Contract dependencies go in `<name>_contract/Nargo.toml` instead of `contract/Nargo.toml`.
Expand Down Expand Up @@ -254,6 +301,7 @@ my_project/
```

**What changed:**

- The `--contract` and `--lib` flags have been removed from `aztec new` and `aztec init`. These commands now always create a contract workspace.
- Contract code is now at `contract/src/main.nr` instead of `src/main.nr`.
- The `Nargo.toml` in the project root is now a workspace file. Contract dependencies go in `contract/Nargo.toml`.
Expand All @@ -279,7 +327,7 @@ The wallet now passes scopes to PXE, and only the `from` address is in scope by

2. **Operations that access another contract's private state** (e.g., withdrawing from an escrow contract that nullifies the contract's own token notes).

```
````

**Example: deploying a contract with private storage (e.g., `PrivateToken`)**

Expand All @@ -293,7 +341,7 @@ The wallet now passes scopes to PXE, and only the `from` address is in scope by
from: sender,
+ additionalScopes: [tokenInstance.address],
});
```
````

**Example: withdrawing from an escrow contract**

Expand Down Expand Up @@ -340,22 +388,26 @@ The `include_by_timestamp` field has been renamed to `expiration_timestamp` acro
The Aztec CLI is now installed without Docker. The installation command has changed:

**Old installation (deprecated):**

```bash
bash -i <(curl -sL https://install.aztec.network)
aztec-up <version>
```

**New installation:**

```bash
VERSION=<version> bash -i <(curl -sL https://install.aztec.network/<version>)
```

For example, to install version `#include_version_without_prefix`:

```bash
VERSION=#include_version_without_prefix bash -i <(curl -sL https://install.aztec.network/#include_version_without_prefix)
```

**Key changes:**

- Docker is no longer required to run the Aztec CLI tools
- The `VERSION` environment variable must be set in the installation command
- The version must also be included in the URL path
Expand All @@ -364,12 +416,13 @@ VERSION=#include_version_without_prefix bash -i <(curl -sL https://install.aztec

After installation, `aztec-up` functions as a version manager with the following commands:

| Command | Description |
|---------|-------------|
| Command | Description |
| ---------------------------- | ------------------------------------------- |
| `aztec-up install <version>` | Install a specific version and switch to it |
| `aztec-up use <version>` | Switch to an already installed version |
| `aztec-up list` | List all installed versions |
| `aztec-up self-update` | Update aztec-up itself |
| `aztec-up use <version>` | Switch to an already installed version |
| `aztec-up list` | List all installed versions |
| `aztec-up self-update` | Update aztec-up itself |

### `@aztec/test-wallet` replaced by `@aztec/wallets`

The `@aztec/test-wallet` package has been removed. Use `@aztec/wallets` instead, which provides `EmbeddedWallet` with a `static create()` factory:
Expand Down Expand Up @@ -3000,7 +3053,7 @@ Doing the changes is as straightforward as:

`UintNote` has also been updated to use the native `u128` type.

### [aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifer`
### [aztec-nr] Removed `compute_note_hash_and_optionally_a_nullifier`

This function is no longer mandatory for contracts, and the `#[aztec]` macro no longer injects it.

Expand Down
8 changes: 6 additions & 2 deletions noir-projects/aztec-nr/aztec/src/capsules/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ impl<T> CapsuleArray<T> {
capsules::store(self.contract_address, self.base_slot, current_length - 1);
}

/// Iterates over the entire array, calling the callback with all values and their array index. The order in which
/// values are processed is arbitrary.
/// Calls a function on each element of the array.
///
/// The function `f` is called once with each array value and its corresponding index. The order in which values
/// are processed is arbitrary.
///
/// ## Array Mutation
///
/// It is safe to delete the current element (and only the current element) from inside the callback via `remove`:
/// ```noir
Expand Down
87 changes: 36 additions & 51 deletions noir-projects/aztec-nr/aztec/src/context/private_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -349,71 +349,56 @@ impl PrivateContext {
self.note_hashes.push(Counted::new(note_hash, self.next_counter()));
}

/// Pushes a new nullifier to the Aztec blockchain's global Nullifier Tree (a state tree).
/// Creates a new [nullifier](crate::nullifier).
///
/// See also: `push_nullifier_for_note_hash`.
/// ## Safety
///
/// Low-level function: Ordinarily, smart contract developers will not need to manually call this. Aztec-nr's state
/// variables (see `../state_vars/`) are designed to understand when to create and push new nullifiers.
///
/// A nullifier can only be emitted once. Duplicate nullifier insertions are rejected by the protocol.
///
/// Generally, a nullifier is emitted to prevent an action from happening more than once, in such a way that the
/// action cannot be linked (by an observer of the blockchain) to any earlier transactions.
///
/// I.e. a nullifier is a random-looking, but deterministic record of a private, one-time action, which does not
/// leak what action has been taken, and which preserves the property of "tx unlinkability".
/// This is a low-level function that must be used with great care to avoid subtle corruption of contract state.
/// Instead of calling this function, consider using the higher-level [`crate::state_vars::SingleUseClaim`].
///
/// Usually, a nullifier will be emitted to "spend" a note (a piece of private state), without revealing which
/// specific note is being spent.
/// In particular, callers must ensure all nullifiers created by a contract are properly domain-separated, so that
/// unrelated components don't interfere with one another (e.g. a transaction nullifier accidentally marking a
/// variable as initialized). Only [`PrivateContext::push_nullifier_for_note_hash`] should be used for note
/// nullifiers, never this one.
///
/// (Important: in such cases, use the below `push_nullifier_for_note_hash`).
///
/// Sometimes, a nullifier might be emitted completely unrelated to any notes. Examples include initialization of a
/// new contract; initialization of a PrivateMutable, or signalling in Semaphore-like applications. This
/// `push_nullifier` function serves such use cases.
///
/// # Arguments
/// * `nullifier`
///
/// # Advanced
/// From here, the protocol's kernel circuits will take over and insert the nullifier into the protocol's
/// "nullifier tree" (in the Base Rollup circuit). Before insertion, the protocol will:
/// - "Silo" the `nullifier` with the contract address of this function, to yield a `siloed_nullifier`. This
/// prevents state collisions between different smart contracts.
/// - Ensure the `siloed_nullifier` is unique (the nullifier tree is an indexed merkle tree which supports
/// efficient non-membership proofs).
/// ## Advanced
///
/// The raw `nullifier` is not what is inserted into the Aztec state tree: it will be first siloed by contract
/// address via [`crate::protocol::hash::compute_siloed_nullifier`] in order to prevent accidental or malicious
/// interference of nullifiers from different contracts.
pub fn push_nullifier(&mut self, nullifier: Field) {
notify_created_nullifier(nullifier);
self.nullifiers.push(Nullifier { value: nullifier, note_hash: 0 }.count(self.next_counter()));
}

/// Pushes a nullifier that corresponds to a specific note hash.
/// Creates a new [nullifier](crate::nullifier) associated with a note.
///
/// Low-level function: Ordinarily, smart contract developers will not need to manually call this. Aztec-nr's state
/// variables (see `../state_vars/`) are designed to understand when to create and push new nullifiers.
/// This is a variant of [`PrivateContext::push_nullifier`] that is used for note nullifiers, i.e. nullifiers that
/// correspond to a note. If a note and its nullifier are created in the same transaction, then the private kernels
/// will 'squash' these values, deleting them both as if they never existed and reducing transaction fees.
///
/// This is a specialized version of `push_nullifier` that links a nullifier to the specific note hash it's
/// nullifying. This is the most common usage pattern for nullifiers. See `push_nullifier` for more explanation on
/// nullifiers.
/// The `nullification_note_hash` must be the result of calling
/// [`crate::note::utils::compute_confirmed_note_hash_for_nullification`] for pending notes, and `0` for settled
/// notes (which cannot be squashed).
///
/// # Arguments
/// * `nullifier`
/// * `nullified_note_hash` - The note hash of the note being nullified
/// ## Safety
///
/// # Advanced
///Important: usage of this function doesn't mean that the world will _see_ that this nullifier relates to the
/// given nullified_note_hash (as that would violate "tx unlinkability"); it simply informs the user's PXE about
/// the relationship (via `notify_nullified_note`). The PXE can then use this information to feed hints to the
/// kernel circuits for "squashing" purposes: If a note is nullified during the same tx which created it, we can
/// "squash" (delete) the note and nullifier (and any private logs associated with the note), to save on data
/// emission costs.
///
pub fn push_nullifier_for_note_hash(&mut self, nullifier: Field, nullified_note_hash: Field) {
/// This is a low-level function that must be used with great care to avoid subtle corruption of contract state.
/// Instead of calling this function, consider using the higher-level [`crate::note::lifecycle::destroy_note`].
///
/// The precautions listed for [`PrivateContext::push_nullifier`] apply here as well, and callers should
/// additionally ensure `nullification_note_hash` corresponds to a note emitted by this contract, with its hash
/// computed in the same transaction execution phase as the call to this function. Finally, only this function
/// should be used for note nullifiers, never [`PrivateContext::push_nullifier`].
///
/// Failure to do these things can result in unprovable contexts, accidental deletion of notes, or double-spend
/// attacks.
pub fn push_nullifier_for_note_hash(&mut self, nullifier: Field, nullification_note_hash: Field) {
let nullifier_counter = self.next_counter();
notify_nullified_note(nullifier, nullified_note_hash, nullifier_counter);
self.nullifiers.push(Nullifier { value: nullifier, note_hash: nullified_note_hash }.count(nullifier_counter));
notify_nullified_note(nullifier, nullification_note_hash, nullifier_counter);
self.nullifiers.push(Nullifier { value: nullifier, note_hash: nullification_note_hash }.count(
nullifier_counter,
));
}

/// Returns the anchor block header - the historical block header that this private function is reading from.
Expand Down Expand Up @@ -607,7 +592,7 @@ impl PrivateContext {
/// network as a whole. For example, if a contract interaction sets include-by to some publicly-known value (e.g.
/// the time when a contract upgrades), then the wallet might wish to set an even lower one to avoid revealing that
/// this tx is interacting with said contract. Ideally, all wallets should standardize on an approach in order to
/// provide users with a large anonymity set -- although the exact approach
/// provide users with a large privacy set -- although the exact approach
/// will need to be discussed. Wallets that deviate from a standard might accidentally reveal which wallet each
/// transaction originates from.
///
Expand Down
28 changes: 16 additions & 12 deletions noir-projects/aztec-nr/aztec/src/context/public_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -366,22 +366,26 @@ impl PublicContext {
unsafe { avm::emit_note_hash(note_hash) };
}

/// Adds a new nullifier to the Aztec blockchain's global Nullifier Tree.
/// Creates a new [nullifier](crate::nullifier).
///
/// Whilst nullifiers are primarily intended as a _privacy-preserving_ record of a one-time action, they can also
/// be used to efficiently record _public_ one-time actions too. Hence why you're seeing this function within the
/// PublicContext. An example is to check whether a contract has been published: we emit a nullifier that is
/// deterministic, but whose preimage is _not_ private.
/// While nullifiers are primarily intended as a _privacy-preserving_ record of a one-time action, they can also
/// be used to efficiently record _public_ one-time actions. This function allows creating nullifiers from public
/// contract functions, which behave just like those created from private functions.
///
/// # Arguments
/// * `nullifier` - A unique field element that represents the consumed state
/// ## Safety
///
/// # Advanced
/// * Nullifier is immediately added to the global nullifier tree
/// * Emitted nullifiers are immediately visible to all subsequent transactions in the same block
/// * Automatically siloed with the contract address by the protocol
/// * Used for preventing double-spending and ensuring one-time actions
/// This is a low-level function that must be used with great care to avoid subtle corruption of contract state.
///
/// In particular, callers must ensure all nullifiers created by a contract are properly domain-separated, so that
/// unrelated components don't interfere with one another (e.g. a transaction nullifier accidentally marking a
/// variable as initialized). Note nullifiers should only be created via
/// [`crate::context::PrivateContext::push_nullifier_for_note_hash`].
///
/// ## Advanced
///
/// The raw `nullifier` is not what is inserted into the Aztec state tree: it will be first siloed by contract
/// address via [`crate::protocol::hash::compute_siloed_nullifier`] in order to prevent accidental or malicious
/// interference of nullifiers from different contracts.
pub fn push_nullifier(_self: Self, nullifier: Field) {
// Safety: AVM opcodes are constrained by the AVM itself
unsafe { avm::emit_nullifier(nullifier) };
Expand Down
Loading
Loading