diff --git a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/aztec-js/how_to_create_account.md b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/aztec-js/how_to_create_account.md index cfab8ec53f70..3341e529e719 100644 --- a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/aztec-js/how_to_create_account.md +++ b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/aztec-js/how_to_create_account.md @@ -39,9 +39,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 @@ -144,5 +164,5 @@ console.log("Account deployed:", metadata.initializationStatus); - [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) diff --git a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/aztec-nr/framework-description/immutables.md b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/aztec-nr/framework-description/immutables.md index 0e93cfda9fbe..629d66a9c3c0 100644 --- a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/aztec-nr/framework-description/immutables.md +++ b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/aztec-nr/framework-description/immutables.md @@ -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. diff --git a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/accounts/deployment.md b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/accounts/deployment.md new file mode 100644 index 000000000000..d189520dfa81 --- /dev/null +++ b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/accounts/deployment.md @@ -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) diff --git a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/contract_creation.md b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/contract_creation.md index 899ece79d9cd..a8bfa6801bdf 100644 --- a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/contract_creation.md +++ b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/contract_creation.md @@ -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 diff --git a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/fees.md b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/fees.md index d2db70adb960..6edcc4f5d4b7 100644 --- a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/fees.md +++ b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/fees.md @@ -92,7 +92,7 @@ Fee Juice uses an enshrined `FeeJuicePortal` contract on Ethereum for bridging, ### 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. diff --git a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/wallets.md b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/wallets.md index 96d2b4ee45df..01ab9df87c2f 100644 --- a/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/wallets.md +++ b/docs/developer_versioned_docs/version-v5.0.0-rc.2/docs/foundational-topics/wallets.md @@ -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. diff --git a/docs/docs-developers/docs/aztec-js/how_to_create_account.md b/docs/docs-developers/docs/aztec-js/how_to_create_account.md index 5f9fcfe0a43e..4b69c16aff01 100644 --- a/docs/docs-developers/docs/aztec-js/how_to_create_account.md +++ b/docs/docs-developers/docs/aztec-js/how_to_create_account.md @@ -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 @@ -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) diff --git a/docs/docs-developers/docs/aztec-nr/framework-description/immutables.md b/docs/docs-developers/docs/aztec-nr/framework-description/immutables.md index 0e93cfda9fbe..629d66a9c3c0 100644 --- a/docs/docs-developers/docs/aztec-nr/framework-description/immutables.md +++ b/docs/docs-developers/docs/aztec-nr/framework-description/immutables.md @@ -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. diff --git a/docs/docs-developers/docs/foundational-topics/accounts/deployment.md b/docs/docs-developers/docs/foundational-topics/accounts/deployment.md new file mode 100644 index 000000000000..d189520dfa81 --- /dev/null +++ b/docs/docs-developers/docs/foundational-topics/accounts/deployment.md @@ -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) diff --git a/docs/docs-developers/docs/foundational-topics/contract_creation.md b/docs/docs-developers/docs/foundational-topics/contract_creation.md index f2fd1929df29..b4a573765db4 100644 --- a/docs/docs-developers/docs/foundational-topics/contract_creation.md +++ b/docs/docs-developers/docs/foundational-topics/contract_creation.md @@ -55,7 +55,7 @@ A contract instance includes: - `deployer`: Optional address of the contract deployer. Zero for universal deployment - `contract_class_id`: Identifier of the contract class for this instance - `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 diff --git a/docs/docs-developers/docs/foundational-topics/fees.md b/docs/docs-developers/docs/foundational-topics/fees.md index 8ff84586cdbc..adfbcaf88c81 100644 --- a/docs/docs-developers/docs/foundational-topics/fees.md +++ b/docs/docs-developers/docs/foundational-topics/fees.md @@ -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. diff --git a/docs/docs-developers/docs/foundational-topics/wallets.md b/docs/docs-developers/docs/foundational-topics/wallets.md index 895570670b5c..c299f7405c2b 100644 --- a/docs/docs-developers/docs/foundational-topics/wallets.md +++ b/docs/docs-developers/docs/foundational-topics/wallets.md @@ -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.