Skip to content

Commit 72666f8

Browse files
authored
chore(release): v5.0.1 (#24737)
Merges the current `v5-next` tree (`c11bc68`) back into `v5` and bumps the release-please manifest to `5.0.1` to cut the v5.0.1 patch release. - `.release-please-manifest.json`: `5.0.0` → `5.0.1` - Brings all commits on `v5-next` since `v5.0.0` onto `v5` (companion change: `v5-next` manifest bumped to `5.1.0` in `ee3716277a`). **Merge strategy:** this is a release-branch sync — merge so `v5` remains a true superset of the history (merge commit or fast-forward). A squash merge would collapse the whole tree into a single commit and rewrite the SHAs, so avoid it here. Tagging `v5.0.1` on the resulting release commit is a follow-up step.
2 parents 22cb085 + cb7a609 commit 72666f8

108 files changed

Lines changed: 3347 additions & 504 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "5.0.0"
2+
".": "5.0.1"
33
}

docs/docs-developers/docs/aztec-nr/framework-description/advanced/how_to_prove_history.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,18 @@ Prove a note exists in the note hash tree:
5656
To prove a note was valid (existed AND wasn't nullified) at a historical block:
5757

5858
```rust
59-
use aztec::history::note::assert_note_was_valid_by;
59+
use aztec::history::note::assert_local_note_was_valid_by;
6060

6161
let header = self.context.get_anchor_block_header();
62-
assert_note_was_valid_by(header, hinted_note, &mut self.context);
62+
assert_local_note_was_valid_by(header, hinted_note, &mut self.context);
6363
```
6464

6565
This verifies both:
6666
1. The note was included in the note hash tree
6767
2. The note's nullifier was not in the nullifier tree
6868

69+
The `assert_local_note_was_*` helpers only work for the executing contract's own notes: this is because it is not possible for a contract to retrieve the app-siloed nullifier hiding secret key that corresponds to another contract. To prove facts about another contract's notes, use `assert_note_existed_by`, which supports notes of any contract (see [Prove note inclusion](#prove-note-inclusion)).
70+
6971
## Prove at a specific historical block
7072

7173
To prove against state at a specific past block (not just the anchor block):
@@ -83,13 +85,13 @@ Using `get_block_header_at` adds ~3k constraints to prove Archive tree membershi
8385

8486
## Prove a note was nullified
8587

86-
To prove a note has been spent/nullified:
88+
To prove a note has been nullified:
8789

8890
```rust
89-
use aztec::history::note::assert_note_was_nullified_by;
91+
use aztec::history::note::assert_local_note_was_nullified_by;
9092

9193
let header = self.context.get_anchor_block_header();
92-
assert_note_was_nullified_by(header, confirmed_note, &mut self.context);
94+
assert_local_note_was_nullified_by(header, confirmed_note, &mut self.context);
9395
```
9496

9597
## Prove contract bytecode was published
@@ -137,10 +139,10 @@ The `aztec::history` module provides these functions:
137139

138140
| Function | Module | Purpose |
139141
|----------|--------|---------|
140-
| `assert_note_existed_by` | `history::note` | Prove note exists in note hash tree |
141-
| `assert_note_was_valid_by` | `history::note` | Prove note exists and is not nullified |
142-
| `assert_note_was_nullified_by` | `history::note` | Prove note's nullifier is in nullifier tree |
143-
| `assert_note_was_not_nullified_by` | `history::note` | Prove note's nullifier is not in nullifier tree |
142+
| `assert_note_existed_by` | `history::note` | Prove note exists in note hash tree (any contract) |
143+
| `assert_local_note_was_valid_by` | `history::note` | Prove own note exists and is not nullified |
144+
| `assert_local_note_was_nullified_by` | `history::note` | Prove own note's nullifier is in nullifier tree |
145+
| `assert_local_note_was_not_nullified_by` | `history::note` | Prove own note's nullifier is not in nullifier tree |
144146
| `assert_nullifier_existed_by` | `history::nullifier` | Prove a siloed nullifier exists |
145147
| `assert_nullifier_did_not_exist_by` | `history::nullifier` | Prove a siloed nullifier does not exist |
146148
| `assert_contract_bytecode_was_published_by` | `history::deployment` | Prove a contract's bytecode was published |

docs/docs-developers/docs/resources/migration_notes.md

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,40 @@ Aztec is in active development. Each version may introduce breaking changes that
99

1010
## TBD
1111

12-
### [PXE] Local PXE database is reset on upgrade
12+
### [Aztec.nr] History note nullification helpers renamed and restricted to own-contract notes
1313

14-
The persisted tagging stores now key every entry by the self-describing `<kind>:<secret>:<app>` form of `AppTaggingSecret`; unconstrained secrets previously used a two-part `<secret>:<app>` key. This bumps the PXE data schema version, and there is no forward migration for the old keys: on first open the PXE clears any database whose stored schema version differs from the current one. The wipe resets the entire PXE store, not just the tagging data, because all of it shares one backing database.
14+
The `history::note` helpers that recompute a note's nullifier have been renamed with a `local_` prefix and now assert that the note belongs to the executing contract:
1515

16-
**Impact**: On upgrade your local PXE state is reset. You must re-register accounts and re-sync from genesis. Wallets should surface a "your local state was reset, please re-register accounts and re-sync" path.
16+
| Old | New |
17+
| --- | --- |
18+
| `assert_note_was_valid_by` | `assert_local_note_was_valid_by` |
19+
| `assert_note_was_nullified_by` | `assert_local_note_was_nullified_by` |
20+
| `assert_note_was_not_nullified_by` | `assert_local_note_was_not_nullified_by` |
21+
22+
These helpers derive the note's nullifier from the executing contract's app-siloed nullifier key, and it is not possible for a contract to retrieve the app-siloed nullifier for a different contract, as this would constitute leakage of key material. These helpers incorrectly used the local contract's key siloing, which for a note of a different contract resulted in the non-inclusion check passing unconditionally and wrongly reporting a nullified note as not nullified. The helpers now assert `contract_address == context.this_address()` and fail with "Note nullification history is only supported for the executing contract's own notes" otherwise.
23+
24+
**Impact**: Update call sites to the new names. Own-contract usage (the common case) is unaffected beyond the rename. `assert_note_existed_by` is unchanged and still supports notes of any contract, since note-hash inclusion involves no keys.
25+
26+
### [Aztec.nr] `set_as_fee_payer` now asserts it is called during the setup phase
27+
28+
`PrivateContext::set_as_fee_payer` now asserts that execution is still in the setup (non-revertible) phase, i.e. that `end_setup` has not yet been called by any function in the transaction. Electing a fee payer in the revertible phase was never safe: compensation collected by the fee payer after `end_setup` can be discarded if a public call later reverts, while the protocol still debits the fee payer's fee-juice balance.
29+
30+
**Impact**: A transaction in which `set_as_fee_payer` runs after the setup phase has ended now fails with `fee payer must be elected during the setup phase`. Standard fee payment flows, which call `set_as_fee_payer` before or together with `end_setup`, are unaffected.
31+
32+
### [PXE] Stores are now selected by `(l1ChainId, rollupAddress, schemaVersion)` instead of being wiped on mismatch
33+
34+
Previously, connecting a PXE or embedded wallet to a different or redeployed rollup, or bumping the store schema version, wiped the existing on-disk store in place. That meant master account keys could be destroyed simply by pointing a wallet at a different network. PXE data stores now exist per `(l1ChainId, rollupAddress, schemaVersion)` triple, and switching networks (or upgrading) selects or creates the matching store instead of overwriting previous ones. The embedded wallet's `wallet_data` store is partitioned the same way, so accounts and aliases are per network: switching networks starts with an empty account list until accounts are re-imported, and switching back finds the originals intact.
35+
36+
**Impact**: The first start after upgrading to this version begins with a fresh, empty store; the pre-upgrade data is not deleted. On Node.js environments (lmdb-v2) pre-upgrade data stays at `<dataDirectory>/<name>` while new per-identity `pxe_data` stores live under `<dataDirectory>/<name>-stores/`. The embedded Node.js wallet previously stored data in cwd-relative, rollup-address-suffixed directories instead (`pxe_data_<rollupAddress>/pxe_data` for the PXE store, `wallet_data_<rollupAddress>/wallet_data` for the wallet store): if you used it before this release, that is where the old data lives. The embedded wallet now defaults its data root to `aztec-wallet-data/`, with per-identity wallet stores under `<dataDirectory>/wallet_data-stores/` on Node.js and OPFS store names prefixed `wallet_data_` in the browser. Browser apps can enumerate and clean up `pxe_data` and `wallet_data` stores for networks no longer in use with the new `listStores()` / `deleteStore()` utilities:
37+
38+
```ts
39+
import { deleteStore, listStores } from '@aztec/kv-store/sqlite-opfs';
40+
41+
const names = await listStores();
42+
await deleteStore(names[0]);
43+
```
44+
45+
This change also removes `createStore` from `@aztec/kv-store/sqlite-opfs` and `@aztec/kv-store/deprecated/indexeddb`: stores are now opened by name with `AztecSQLiteOPFSStore.open` / `AztecIndexedDBStore.open`.
1746

1847
### [Aztec.nr] `TestEnvironmentOptions::with_tagging_secret_strategy` replaced
1948

@@ -131,8 +160,6 @@ Registering classes and instances are now separate, unvalidated operations. `reg
131160
The new class is used automatically once the upgrade takes effect on chain; no further PXE action is needed. Registering it beforehand is harmless: until the update activates, the node still resolves the contract's current class to the previous one, so it keeps running its old code.
132161

133162
- `pxe.getContractInstance(address)` and `wallet.getContractMetadata(address).instance` now return the contract's **address preimage**, which no longer includes `currentContractClassId`.
134-
135-
136163
### [Aztec.js] `AccountWithSecretKey` removed, read account keys from the `AccountManager` or PXE
137164

138165
`AccountWithSecretKey` was a thin wrapper that bundled an account's transaction signer with its master secret key, used mainly to print or export the secret. It has been removed, and `AccountManager.getAccount()` now returns the plain `Account` signer. The wrapper's extra methods are no longer available on that value:

l1-contracts/scripts/forge_broadcast.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ const timeoutMs =
5959
(isAnvil ? 120_000 : 1_200_000);
6060

6161
const proc = spawn(
62-
"forge",
62+
process.env.FORGE_BIN || "forge",
6363
["script", ...args, "--broadcast", "--batch-size", batchSize],
6464
{
6565
stdio: ["ignore", "pipe", "inherit"],
66-
},
66+
}
6767
);
6868

6969
const stdout = [];
@@ -85,14 +85,14 @@ const exitCode = await new Promise((resolve) => {
8585
});
8686
proc.on("close", (code) => {
8787
clearTimeout(timeout);
88-
resolve(timedOut ? 1 : (code ?? 1));
88+
resolve(timedOut ? 1 : code ?? 1);
8989
});
9090
});
9191

9292
log(
9393
exitCode === 0
9494
? "Broadcast succeeded."
95-
: `Broadcast failed (exit ${exitCode}).`,
95+
: `Broadcast failed (exit ${exitCode}).`
9696
);
9797
const data = Buffer.concat(stdout);
9898
if (data.length > 0) writeSync(1, data);

noir-projects/aztec-nr/CLAUDE.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,36 @@ Follow the Rust stdlib style roughly. See `PublicImmutable` in `state_vars/publi
3434
- **Show practical patterns in examples.** Don't just show the API call — show it in context (e.g. inside a `#[external("public")]` function with realistic variable names).
3535
- **Document cost for methods.** When relevant, note which AVM opcodes are invoked and how many times (e.g. "`SLOAD` is invoked a number of times equal to `T`'s packed length").
3636

37+
### Brevity and attitude
38+
39+
Write for a caller using the API, not for an implementor or an auditor. State what the item does and any restriction a caller must respect, plus the alternative to reach for — then stop. Leave out the internal mechanism and the security rationale behind a restriction: those belong in code comments messages or design docs, not the rustdoc. A reader who hits a restriction needs to know it exists and where to go next, not the argument for why it holds.
40+
41+
- **State the restriction, not the reason it's sound.** Say "this can only be used for X", not the multi-sentence explanation of what goes wrong otherwise.
42+
- **Don't narrate the mechanism.** Key siloing, tree layouts, hash preimages, and similar internals are implementation detail; mention them only when a caller cannot use the API correctly without knowing (and then put them under `## Implementation Details`).
43+
- **Point to the alternative instead of enumerating trade-offs.** A single "use `[other_fn]` for the other case" beats a paragraph weighing options.
44+
45+
Example — documenting a helper that only works on the executing contract's own notes:
46+
47+
```rust
48+
// Too much: explains the key-siloing mechanism and the failure the guard prevents.
49+
/// The note's nullifier is recomputed using the executing contract's app-siloed nullifier key
50+
/// and then siloed with `confirmed_note.contract_address`. These only agree for the executing
51+
/// contract's own notes; otherwise the non-inclusion proof passes unconditionally and wrongly
52+
/// reports a nullified note as not nullified, so this asserts that
53+
/// `confirmed_note.contract_address == context.this_address()`.
54+
55+
// Right: states the restriction and the alternative, nothing more.
56+
/// ## Local notes only
57+
///
58+
/// This can only be used for notes of the executing contract. Use [`assert_note_existed_by`] to
59+
/// prove existence of another contract's notes.
60+
```
61+
62+
## Testing
63+
64+
- **No messages on assertions inside tests.** Write `assert_eq(actual, expected)` and `assert(cond)`, not `assert_eq(actual, expected, "values should match")`. The test's name and body already make clear what is being checked, so an assertion message is redundant noise. (Library code is the opposite: an `assert`/`panic` there must carry a message, since it explains a runtime failure to a contract developer.)
65+
- `#[test(should_fail_with = "...")]` is not an assertion message and is encouraged. The string matches a substring of the expected failure, pinning down *which* error the failing case must produce so the test can't pass for the wrong reason.
66+
3767
## Logging
3868

3969
- **Always use the prefixed logging functions** from `crate::logging` (e.g. `logging::aztecnr_debug_log!`, `logging::aztecnr_debug_log_format!`). These automatically prepend `[aztec-nr] ` to all messages at compile time.

noir-projects/aztec-nr/aztec/src/context/private_context.nr

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,22 @@ impl PrivateContext {
538538
/// Only one contract per transaction can declare itself as the fee payer, and it must have sufficient fee-juice
539539
/// balance (>= the gas limits specified in the TxContext) by the time we reach the public setup phase of the tx.
540540
///
541+
/// The fee payer must be elected during the setup (non-revertible) phase, i.e. before
542+
/// [`end_setup`](PrivateContext::end_setup) is called - this function asserts so. This is because any compensation
543+
/// collected by the fee payer during the revertible phase can be discarded if a public call later reverts, while
544+
/// the protocol still debits the fee payer's fee-juice balance. Note that `end_setup` does not need to be called
545+
/// by the electing function itself: it can be called later in the transaction (e.g. by the fee-juice contract when
546+
/// claiming fee juice that pays for the very same transaction).
541547
pub fn set_as_fee_payer(&mut self) {
548+
assert(!self.in_revertible_phase(), "fee payer must be elected during the setup phase");
542549
aztecnr_trace_log_format!("Setting {0} as fee payer")([self.this_address().to_field()]);
543550
self.is_fee_payer = true;
544551
}
545552

553+
/// Returns whether execution is currently in the revertible (app) phase of the transaction.
554+
///
555+
/// A transaction is in the revertible phase if [`end_setup`](PrivateContext::end_setup) has already been called -
556+
/// potentially by a different function of the same transaction.
546557
pub fn in_revertible_phase(&mut self) -> bool {
547558
let current_counter = self.side_effect_counter;
548559

noir-projects/aztec-nr/aztec/src/history/note.nr

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ use crate::{
1515

1616
mod test;
1717

18+
/// Asserts that a note existed in the note hash tree by the time a block was mined.
19+
///
20+
/// Proves note inclusion at `block_header` by checking membership of the note's unique note hash, and returns a
21+
/// [`ConfirmedNote`] describing it.
22+
///
23+
/// ## Cross-contract notes
24+
///
25+
/// Unlike the nullification helpers, this works for notes of **any** contract, not just the executing one: note hashes
26+
/// are siloed with the note's own contract address (carried by the [`HintedNote`]) and no secret keys are involved.
27+
///
28+
/// To reason about whether such a note has been nullified, see [`assert_local_note_was_nullified_by`] and
29+
/// [`assert_local_note_was_not_nullified_by`], which — in contrast — can only be used for the executing contract's own
30+
/// notes.
1831
pub fn assert_note_existed_by<Note>(block_header: BlockHeader, hinted_note: HintedNote<Note>) -> ConfirmedNote<Note>
1932
where
2033
Note: NoteHash,
@@ -46,7 +59,17 @@ where
4659
ConfirmedNote::new(hinted_note, unique_note_hash)
4760
}
4861

49-
pub fn assert_note_was_valid_by<Note>(
62+
/// Asserts a note existed and had not yet been nullified by a given block.
63+
///
64+
/// Combines [`assert_note_existed_by`] and [`assert_local_note_was_not_nullified_by`]: it proves both that the note
65+
/// existed at `block_header` and that it had not been nullified then. A contract typically uses this as a security gate
66+
/// before acting on a note whose validity at some past block matters.
67+
///
68+
/// ## Local notes only
69+
///
70+
/// Like [`assert_local_note_was_not_nullified_by`], this can only be used for notes of the executing contract. Use
71+
/// [`assert_note_existed_by`] on its own to prove existence of another contract's notes.
72+
pub fn assert_local_note_was_valid_by<Note>(
5073
block_header: BlockHeader,
5174
hinted_note: HintedNote<Note>,
5275
context: &mut PrivateContext,
@@ -55,17 +78,28 @@ where
5578
Note: NoteHash,
5679
{
5780
let confirmed_note = assert_note_existed_by(block_header, hinted_note);
58-
assert_note_was_not_nullified_by(block_header, confirmed_note, context);
81+
assert_local_note_was_not_nullified_by(block_header, confirmed_note, context);
5982
}
6083

61-
pub fn assert_note_was_nullified_by<Note>(
84+
/// Asserts a note had been nullified by a given block.
85+
///
86+
/// ## Local notes only
87+
///
88+
/// This can only be used for notes of the executing contract. Use [`assert_note_existed_by`] on its own to prove
89+
/// existence of another contract's notes.
90+
pub fn assert_local_note_was_nullified_by<Note>(
6291
block_header: BlockHeader,
6392
confirmed_note: ConfirmedNote<Note>,
6493
context: &mut PrivateContext,
6594
)
6695
where
6796
Note: NoteHash,
6897
{
98+
assert(
99+
confirmed_note.contract_address == context.this_address(),
100+
"Note nullification history is only supported for the executing contract's own notes",
101+
);
102+
69103
let note_hash_for_nullification = compute_confirmed_note_hash_for_nullification(confirmed_note);
70104
let inner_nullifier =
71105
confirmed_note.note.compute_nullifier(context, confirmed_note.owner, note_hash_for_nullification);
@@ -75,14 +109,25 @@ where
75109
assert_nullifier_existed_by(block_header, siloed_nullifier);
76110
}
77111

78-
pub fn assert_note_was_not_nullified_by<Note>(
112+
/// Asserts a note had **not** been nullified by a given block.
113+
///
114+
/// ## Local notes only
115+
///
116+
/// This can only be used for notes of the executing contract. Use [`assert_note_existed_by`] on its own to prove
117+
/// existence of another contract's notes.
118+
pub fn assert_local_note_was_not_nullified_by<Note>(
79119
block_header: BlockHeader,
80120
confirmed_note: ConfirmedNote<Note>,
81121
context: &mut PrivateContext,
82122
)
83123
where
84124
Note: NoteHash,
85125
{
126+
assert(
127+
confirmed_note.contract_address == context.this_address(),
128+
"Note nullification history is only supported for the executing contract's own notes",
129+
);
130+
86131
let note_hash_for_nullification = compute_confirmed_note_hash_for_nullification(confirmed_note);
87132

88133
let inner_nullifier =

0 commit comments

Comments
 (0)