Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d2a8980
feat(cli): support funding accounts in validator-keys new/set-funding…
aminsammara Jul 13, 2026
78cdd03
chore(spartan): lower mainnet inactivity slash target to 70%
aminsammara Jul 21, 2026
f4a3e63
docs(e2e): update READMEs for the consolidated suite layout (#24518)
spalladino Jul 8, 2026
febbec5
test(e2e): instrument and diagnose bot suite setup cost (#24534)
spalladino Jul 8, 2026
d64784b
perf(e2e): warp dead waits in multi-node recovery and proving tests (…
spalladino Jul 8, 2026
4737913
perf(e2e): shrink e2e slot times (#24570)
spalladino Jul 8, 2026
1db0e11
perf(e2e): seed BananaFPC fee juice at genesis instead of bridging (#…
spalladino Jul 8, 2026
915501a
perf(bot): parallelize independent bot factory setup steps (#24581)
spalladino Jul 8, 2026
a176d96
perf(e2e): seed standard contracts at genesis (#24568)
spalladino Jul 8, 2026
075327e
feat(prover): revive cancelled proving jobs from a persisted aborted …
AztecBot Jul 8, 2026
0cd9ab1
chore: add writing-e2e-tests skill (#24597)
AztecBot Jul 8, 2026
8b2dab2
perf(e2e): overlap and batch setup txs in e2e harnesses (#24569)
spalladino Jul 8, 2026
e0db408
docs: revert threat model for node (#24465) (#24610)
AztecBot Jul 8, 2026
4f980cd
docs: document initializerless accounts (v5-next backport of #24512)
AztecBot Jul 8, 2026
c0d2441
fix: tweak depositToAztec gas config (#24607)
mverzilli Jul 8, 2026
87b3e0d
feat(e2e): interactive handshake e2e (#24590)
nchamo Jul 8, 2026
77d31f8
fix(aztec.js): give waitForNode a bounded default timeout
aminsammara Jul 9, 2026
6c22fe7
refactor: cache Aztec node reads per execution (#24630)
mverzilli Jul 9, 2026
9313618
fix(prover-node): rebuild pruned checkpoint provers and recover the e…
PhilWindle Jul 10, 2026
6565898
fix(prover-node): do not abort in-flight proving jobs on a clean shut…
AztecBot Jul 13, 2026
c06ccd8
docs: fee readme improvements (#24666)
spalladino Jul 13, 2026
cb6d8e4
fix: prevent access to secrets not in scope (#24616)
nventuro Jul 13, 2026
0f18c6e
fix: prevent reception of messages too far into the future (#24645)
nventuro Jul 13, 2026
7976ace
fix(aztec-nr): reject infinity ephemeral key in message encryption (#…
nchamo Jul 14, 2026
217a85a
feat: exported in-process testing network (#24629)
Thunkar Jul 14, 2026
f733164
test: fix proof_boundary startup race (#24671)
spalladino Jul 14, 2026
d70ef04
fix(pxe): widen tracked sender tagging ranges with onchain discovery …
vezenovm Jul 14, 2026
3661764
fix(aztec-nr): tolerate malformed partial-note completion logs (#24668)
nchamo Jul 15, 2026
bc099bc
chore: move prover namespace to regular RPC server
alexghr Jul 14, 2026
ca972d6
Revert "chore: move prover namespace to regular RPC server"
alexghr Jul 14, 2026
c8474a4
fix(validator): sync world state before forking in checkpoint proposa…
spalladino Jul 16, 2026
a62f503
fix: tagging secrets not being scoped by sender (#24772)
nventuro Jul 20, 2026
e3a561d
fix(bb): assign unique secp256r1 lookup table indices
AztecBot Jul 15, 2026
b666c47
test(bb): generalize lookup table-index invariant across all tables
ledwards2225 Jul 15, 2026
0c1693a
comment cleanup
ledwards2225 Jul 20, 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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <gtest/gtest.h>
#include <unordered_map>
#include <unordered_set>

using namespace bb;

Expand Down Expand Up @@ -121,6 +122,47 @@ TEST_F(UltraCircuitBuilderLookup, DifferentTablesGetUniqueIndices)
EXPECT_EQ(builder.get_num_lookup_tables(), 3UL);
}

// Every basic table reachable through any MultiTable must receive a unique, positive circuit-local
// table_index. The LogDeriv lookup relation identifies a table solely by table_index (not BasicTableId),
// so a generator that stores anything other than the builder-assigned index silently collapses distinct tables to one
// identity. This sweeps the whole table space so any such generator is caught.
TEST_F(UltraCircuitBuilderLookup, AllMultiTableBasicTablesGetUniquePositiveIndices)
{
Builder builder;
for (size_t mt = 0; mt < static_cast<size_t>(plookup::MultiTableId::NUM_MULTI_TABLES); ++mt) {
const auto& multitable = plookup::get_multitable(static_cast<plookup::MultiTableId>(mt));
for (const auto id : multitable.basic_table_ids) {
builder.get_table(id);
}
}

std::unordered_set<size_t> seen;
for (const auto& table : builder.get_lookup_tables()) {
EXPECT_GT(table.table_index, 0UL) << "non-positive index for basic table id " << static_cast<size_t>(table.id);
EXPECT_TRUE(seen.insert(table.table_index).second)
<< "duplicate table_index " << table.table_index << " for basic table id " << static_cast<size_t>(table.id);
}
EXPECT_NO_THROW(builder.finalize_circuit());
}

TEST_F(UltraCircuitBuilderLookup, FinalizationRejectsDuplicateTableIndices)
{
Builder builder;
builder.get_table(plookup::BasicTableId::UINT_XOR_SLICE_6_ROTATE_0);
builder.get_table(plookup::BasicTableId::UINT_AND_SLICE_6_ROTATE_0);
builder.get_lookup_tables()[1].table_index = builder.get_lookup_tables()[0].table_index;

EXPECT_THROW_OR_ABORT(builder.finalize_circuit(), "Lookup table indices must be unique within a circuit");
}

TEST_F(UltraCircuitBuilderLookup, FinalizationRejectsZeroTableIndex)
{
Builder builder;
builder.get_table(plookup::BasicTableId::UINT_XOR_SLICE_6_ROTATE_0).table_index = 0;

EXPECT_THROW_OR_ABORT(builder.finalize_circuit(), "Lookup table indices must be positive");
}

// Verifies correct behavior when key_b_index is not provided (2-to-1 lookup without second index)
TEST_F(UltraCircuitBuilderLookup, NoKeyBIndex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,22 +285,22 @@ BasicTable create_basic_table(const BasicTableId id, const size_t index)
if (id_var >= static_cast<size_t>(SECP256R1_FIXED_BASE_XLO_0) &&
id_var < static_cast<size_t>(SECP256R1_FIXED_BASE_XHI_0)) {
return secp256r1_fixed_base::table::generate_basic_table_runtime<secp256r1_fixed_base::table::AXIS_XLO>(
id, id_var - static_cast<size_t>(SECP256R1_FIXED_BASE_XLO_0));
id, id_var - static_cast<size_t>(SECP256R1_FIXED_BASE_XLO_0), index);
}
if (id_var >= static_cast<size_t>(SECP256R1_FIXED_BASE_XHI_0) &&
id_var < static_cast<size_t>(SECP256R1_FIXED_BASE_YLO_0)) {
return secp256r1_fixed_base::table::generate_basic_table_runtime<secp256r1_fixed_base::table::AXIS_XHI>(
id, id_var - static_cast<size_t>(SECP256R1_FIXED_BASE_XHI_0));
id, id_var - static_cast<size_t>(SECP256R1_FIXED_BASE_XHI_0), index);
}
if (id_var >= static_cast<size_t>(SECP256R1_FIXED_BASE_YLO_0) &&
id_var < static_cast<size_t>(SECP256R1_FIXED_BASE_YHI_0)) {
return secp256r1_fixed_base::table::generate_basic_table_runtime<secp256r1_fixed_base::table::AXIS_YLO>(
id, id_var - static_cast<size_t>(SECP256R1_FIXED_BASE_YLO_0));
id, id_var - static_cast<size_t>(SECP256R1_FIXED_BASE_YLO_0), index);
}
if (id_var >= static_cast<size_t>(SECP256R1_FIXED_BASE_YHI_0) &&
id_var < static_cast<size_t>(SECP256R1_FIXED_BASE_END)) {
return secp256r1_fixed_base::table::generate_basic_table_runtime<secp256r1_fixed_base::table::AXIS_YHI>(
id, id_var - static_cast<size_t>(SECP256R1_FIXED_BASE_YHI_0));
id, id_var - static_cast<size_t>(SECP256R1_FIXED_BASE_YHI_0), index);
}
switch (id) {
case AES_SPARSE_MAP: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,18 @@ template <table::AxisIndex axis> generate_fn_ptr generate_fn_for_window(size_t w
}
} // namespace

template <table::AxisIndex axis> BasicTable table::generate_basic_table_runtime(BasicTableId id, size_t window_idx)
template <table::AxisIndex axis>
BasicTable table::generate_basic_table_runtime(BasicTableId id, size_t window_idx, size_t table_index)
{
BB_ASSERT_LT(window_idx, NUM_WINDOWS);
return generate_fn_for_window<axis>(window_idx)(id, window_idx);
return generate_fn_for_window<axis>(window_idx)(id, table_index);
}

// Explicit instantiations for the four axes.
template BasicTable table::generate_basic_table_runtime<table::AXIS_XLO>(BasicTableId, size_t);
template BasicTable table::generate_basic_table_runtime<table::AXIS_XHI>(BasicTableId, size_t);
template BasicTable table::generate_basic_table_runtime<table::AXIS_YLO>(BasicTableId, size_t);
template BasicTable table::generate_basic_table_runtime<table::AXIS_YHI>(BasicTableId, size_t);
template BasicTable table::generate_basic_table_runtime<table::AXIS_XLO>(BasicTableId, size_t, size_t);
template BasicTable table::generate_basic_table_runtime<table::AXIS_XHI>(BasicTableId, size_t, size_t);
template BasicTable table::generate_basic_table_runtime<table::AXIS_YLO>(BasicTableId, size_t, size_t);
template BasicTable table::generate_basic_table_runtime<table::AXIS_YHI>(BasicTableId, size_t, size_t);

namespace {
// Returns `&table::get_values<axis, window_idx>` for a runtime (axis, window_idx). The per-axis 32-entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,11 @@ class table : public Secp256r1FixedBaseParams {
static BasicTable generate_basic_table(BasicTableId id, size_t table_index);

/**
* @brief Runtime dispatch helper used by plookup_tables.cpp::create_basic_table. Given an axis and a
* runtime window_idx ∈ [0, NUM_WINDOWS), instantiate the corresponding generator.
* @brief Runtime dispatch helper used by plookup_tables.cpp::create_basic_table. Selects table contents using
* window_idx and assigns the independently supplied circuit-local table_index.
*/
template <AxisIndex axis> static BasicTable generate_basic_table_runtime(BasicTableId id, size_t table_index);
template <AxisIndex axis>
static BasicTable generate_basic_table_runtime(BasicTableId id, size_t window_idx, size_t table_index);

/**
* @brief Construct one of the 10 MultiTables described in the file-header docstring.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ template <typename ExecutionTrace> void UltraCircuitBuilder_<ExecutionTrace>::fi
* our circuit is finalized, and we must not to execute these functions again.
*/
if (!this->circuit_finalized) {
std::unordered_set<size_t> table_indices;
for (const auto& table : lookup_tables) {
BB_ASSERT_GT(table.table_index, 0U, "Lookup table indices must be positive");
BB_ASSERT(table_indices.insert(table.table_index).second,
"Lookup table indices must be unique within a circuit");
}

process_non_native_field_multiplications();
#ifndef ULTRA_FUZZ
this->rom_ram_logic.process_ROM_arrays(this);
Expand Down
24 changes: 22 additions & 2 deletions docs/docs-developers/docs/aztec-js/how_to_create_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,29 @@ The secret is used to derive the account's encryption keys, and the salt ensures
Save the `secret` and `salt` values securely. You need both to recover access to your account. If you lose them, you will permanently lose access to the account and any assets it holds.
:::

## Create an initializerless account

Alternatively, create an [initializerless account](../foundational-topics/accounts/deployment.md), which needs no deployment transaction at all:

```typescript
const secret = Fr.random();
const salt = Fr.random();
const account = await wallet.createSchnorrInitializerlessAccount(secret, salt);
console.log("Account address:", account.address.toString());
```

An initializerless account commits its signing public key into the address itself (through the instance's `immutables_hash`), so there is no onchain state to initialize. Creating the account registers it locally in the PXE, and it is ready to use immediately: skip the deployment section below entirely. Fees are only needed for the account's first real transaction, paid with any of the usual [payment methods](./how_to_pay_fees.md).

Two things to keep in mind:

- The signing key cannot be changed later. A new key means a new address.
- Calling `getDeployMethod()` on an initializerless account throws, since there is nothing to deploy.

See [account deployment](../foundational-topics/accounts/deployment.md) for how this works and how to choose between the two account types.

## Deploy the account

New accounts must be deployed before they can send transactions. Deployment requires paying fees.
Accounts created with `createSchnorrAccount` must be deployed before they can send transactions (initializerless accounts skip this step). Deployment requires paying fees.

### Using the Sponsored FPC

Expand Down Expand Up @@ -70,5 +90,5 @@ Confirm the account was deployed successfully. Substitute the account variable f

- [Deploy contracts](./how_to_deploy_contract.md) with your new account
- [Send transactions](./how_to_send_transaction.md) from an account
- Learn about [account abstraction](../foundational-topics/accounts/index.md)
- Learn about [account abstraction](../foundational-topics/accounts/index.md) and [account deployment](../foundational-topics/accounts/deployment.md)
- Implement [authentication witnesses](./how_to_use_authwit.md)
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ Initialization cost is completely eliminated (no constructor transaction). The p
## Getting started

For installation instructions, usage examples, and a reference implementation of an initializerless Schnorr account contract, see the [aztec-immutables-macro README](https://github.com/defi-wonderland/aztec-immutables-macro/tree/dev).

The protocol ships a built-in account contract using the same pattern: see [account deployment](../../foundational-topics/accounts/deployment.md) for how initializerless accounts work and [creating accounts](../../aztec-js/how_to_create_account.md#create-an-initializerless-account) for using them from Aztec.js.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: Account Deployment
tags: [accounts]
description: How Aztec accounts come into existence, from the standard initializer plus deployment flow to initializerless accounts that need no onchain transaction at all.
references:
[
"noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr",
"noir-projects/noir-contracts/contracts/account/schnorr_account_contract/src/main.nr",
]
---

## How accounts come into existence

Every account in Aztec is a [contract instance](../contract_creation.md), and its address is computed deterministically from its instantiation parameters. There are two ways to make an account usable:

1. **The standard flow**: create the account locally, then send a deployment transaction that runs its initializer.
2. **The initializerless flow**: create the account locally, and that is it. No deployment transaction is ever sent.

This page explains both flows and when to choose each.

## The standard flow: initialize and deploy

A regular account contract (for example the default Schnorr account) stores its signing public key in private state. Writing that state requires running the contract's constructor, a [private initializer function](../../aztec-nr/framework-description/functions/attributes.md#initializer-functions-initializer), which in turn requires sending a transaction to the network and paying [fees](../fees.md) for it.

The address commits to that pending initialization: the constructor call's selector and arguments are hashed into the instance's `initialization_hash`, which is folded into the address derivation. This is why the address can be computed, shared, and even receive funds before the deployment transaction is sent, and why the account only becomes able to send transactions after it.

## Initializerless accounts

An initializerless account removes the deployment transaction entirely. Instead of storing the signing key in private state through an initializer, the key is committed directly into the address: the instance's `immutables_hash` field is set to the hash of the signing public key, and `immutables_hash` participates in address derivation just like `initialization_hash` does.

Since the address itself is the commitment to the signing key, there is no onchain state to create:

- The account contract has no initializer. Its `constructor` is an unconstrained utility function that runs only in the PXE: it checks that the provided public key hashes to the instance's `immutables_hash` and stores the key in the PXE's local store.
- When the account later authorizes a transaction, its entrypoint loads the key from the local store and proves in the private kernel that the key's hash matches the `immutables_hash` committed in the address. Tampering with the local key is not possible without changing the address.

Creating an initializerless account is therefore a purely local operation: the wallet computes the address, registers the contract instance in the PXE, and runs the utility constructor through a simulation. No transaction is sent, no fee is paid, and the account can start transacting as soon as it has a way to pay for its first real transaction (for example a [Sponsored FPC](../fees.md#payment-methods) or a Fee Juice balance at its address).

The genesis-funded test accounts in the local network are initializerless Schnorr accounts: their addresses receive Fee Juice at genesis, and wallets materialize them locally without any deployment.

## Trade-offs

Initializerless accounts are a good default when the account's authorization logic only depends on parameters that never change:

- **No deployment cost or delay.** The account is usable the moment it is created, which makes onboarding flows and receive-only addresses cheap.
- **The signing key is fixed forever.** The key is baked into the address, so there is no way to rotate it. A new key means a new address. The default Schnorr account offers no key rotation either, but account contracts that keep keys in state can be designed to support it.
- **Local setup is still required per PXE.** A fresh PXE does not know about the account until the wallet registers the instance and runs the utility constructor again. This is a purely offline step with no fee, but it must happen in every new environment before the account can be used.
- **There is no deployment method.** Calling `getDeployMethod()` on an initializerless account throws, since there is nothing to deploy.

Use the standard flow when the account contract needs an initializer: for example when it takes arbitrary constructor arguments or stores its authorization material in private notes.

## Using initializerless accounts

- In Aztec.js, call `createSchnorrInitializerlessAccount(secret, salt)` on your wallet. See [creating accounts](../../aztec-js/how_to_create_account.md#create-an-initializerless-account).
- In the CLI, pass the account type: `aztec-wallet create-account -t schnorr_initializerless`. The command registers the account locally and returns immediately, with no deployment transaction.
- In Aztec.nr, the same address-immutables pattern can be applied to your own contracts. See [immutables](../../aztec-nr/framework-description/immutables.md).

## Next steps

- [Create an account in Aztec.js](../../aztec-js/how_to_create_account.md)
- [Understand fees and payment methods](../fees.md)
- [Contract creation and address derivation](../contract_creation.md)
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ A contract instance includes:
- `deployer`: Optional address of the contract deployer. Zero for universal deployment
- `original_contract_class_id`: Identifier of the contract class the instance was deployed with. Updating the instance to a new class via the ContractInstanceRegistry does not change this value, since it is part of the address preimage
- `initialization_hash`: Hash of the selector and arguments to the constructor
- `immutables_hash`: Hash of the contract's compile-time immutable state
- `immutables_hash`: Hash of the contract's compile-time immutable state. [Initializerless accounts](./accounts/deployment.md) use it to commit the signing key into the address
- `public_keys`: Public keys participating in address derivation (nullifier, incoming viewing, outgoing viewing, tagging, message-signing, and fallback keys). Only the incoming viewing key is held as an elliptic curve point; the other five are held as their `hash_public_key` digests.

### Instance Address
Expand Down
2 changes: 1 addition & 1 deletion docs/docs-developers/docs/foundational-topics/fees.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ In `aztec.js`, the `L1FeeJuicePortalManager` class handles the L1 side (token ap

### Payment methods

An account with Fee Juice can pay for its transactions directly. A new account can even pay for its own deployment transaction, provided Fee Juice was bridged to its address before deployment.
An account with Fee Juice can pay for its transactions directly. A new account can even pay for its own deployment transaction, provided Fee Juice was bridged to its address before deployment. [Initializerless accounts](./accounts/deployment.md) skip the deployment transaction entirely, so they only need fees once they send their first real transaction.

Alternatively, accounts can use [fee-paying contracts (FPCs)](../aztec-js/how_to_pay_fees.md#use-fee-payment-contracts) to pay for transactions. An FPC holds its own Fee Juice balance to pay the protocol, and can accept other tokens from users in exchange.

Expand Down
2 changes: 2 additions & 0 deletions docs/docs-developers/docs/foundational-topics/wallets.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ A wallet must support at least one specific account contract implementation, whi

Note that users must be able to receive funds in Aztec before deploying their account. A wallet should let a user generate a [deterministic complete address](./accounts/keys.md#address-derivation) without having to interact with the network, so they can share it with others to receive funds. This requires that the wallet pins a specific contract implementation, its initialization arguments, a deployment salt, and the user's keys. These values yield a deterministic address, so when the account contract is actually deployed, it is available at the precalculated address. Once the account contract is deployed, the user can start sending transactions using it as the transaction origin.

Some account contracts avoid deployment altogether: [initializerless accounts](./accounts/deployment.md) commit their signing key into the address itself, so the wallet only needs to register them locally before they can start transacting.

## Transaction lifecycle

Every transaction in Aztec is broadcast to the network as a zero-knowledge proof of correct execution, in order to preserve privacy. This means that transaction proofs are generated on the wallet and not on a remote node. This is one of the biggest differences with regard to EVM chain wallets.
Expand Down
8 changes: 4 additions & 4 deletions l1-contracts/scripts/forge_broadcast.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ const timeoutMs =
(isAnvil ? 120_000 : 1_200_000);

const proc = spawn(
"forge",
process.env.FORGE_BIN || "forge",
["script", ...args, "--broadcast", "--batch-size", batchSize],
{
stdio: ["ignore", "pipe", "inherit"],
},
}
);

const stdout = [];
Expand All @@ -85,14 +85,14 @@ const exitCode = await new Promise((resolve) => {
});
proc.on("close", (code) => {
clearTimeout(timeout);
resolve(timedOut ? 1 : (code ?? 1));
resolve(timedOut ? 1 : code ?? 1);
});
});

log(
exitCode === 0
? "Broadcast succeeded."
: `Broadcast failed (exit ${exitCode}).`,
: `Broadcast failed (exit ${exitCode}).`
);
const data = Buffer.concat(stdout);
if (data.length > 0) writeSync(1, data);
Expand Down
Loading
Loading