Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ services:
environment:
AZTEC_NODE_URL: http://node:8080
NODE_NO_WARNINGS: 1
SECRET_KEY:
SIGNING_KEY:
ETHEREUM_HOSTS:
profiles:
- cli
Expand Down
8 changes: 4 additions & 4 deletions docs/docs-developers/docs/aztec-js/how_to_create_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ yarn add @aztec/aztec.js@#include_version_without_prefix @aztec/wallets@#include

## Create a new account

Using the [`wallet` from the connection guide](./how_to_connect_to_local_network.md), call `createSchnorrAccount` to create a new account with a random secret and salt:
Using the [`wallet` from the connection guide](./how_to_connect_to_local_network.md), call `createSchnorrAccount` to create a new account with a random secret, salt, and signing key:

#include_code create_account /docs/examples/ts/aztecjs_connection/index.ts typescript

The secret is used to derive the account's encryption keys, and the salt ensures address uniqueness. The signing key is automatically derived from the secret.
The secret derives the account's encryption keys, the signing key authenticates its transactions, and the salt ensures address uniqueness. The signing key is provided independently and is not derived from the secret: it is an ownership key, so keep it separate from the encryption secret that your PXE holds.

:::warning Store your secret and salt
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.
:::warning Store your secret, salt, and signing key
Save the `secret`, `salt`, and `signingKey` values securely. You need all three to recover access to your account. If you lose them, you will permanently lose access to the account and any assets it holds.
:::

## Deploy the account
Expand Down
42 changes: 42 additions & 0 deletions docs/docs-developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ Aztec is in active development. Each version may introduce breaking changes that

## TBD

### [Aztec.js] Account signing keys are no longer derived from the privacy secret

Schnorr account signing keys used to be derived from the account's privacy secret (via the now-removed `deriveSigningKey`), which meant the ownership key could be reconstructed from a value the PXE holds. The relationship is now reversed: the signing key is the root, and the privacy secret is derived from it with `deriveSecretKeyFromSigningKey` (exported from `@aztec/accounts/utils`).

As a result:

- `deriveSigningKey` is removed.
- `getSchnorrAccountContractAddress` and `getSchnorrInitializerlessAccountContractAddress` now take the signing key first and an optional secret: `(signingPrivateKey, salt, secretKey?)`. When `secretKey` is omitted it is derived from the signing key.
- The embedded wallet's `createSchnorrAccount` and `createSchnorrInitializerlessAccount` now require an explicit signing key argument.

**Migration:**

```diff
- import { deriveSigningKey } from '@aztec/stdlib/keys';
- const signingKey = deriveSigningKey(secret);
- const address = await getSchnorrAccountContractAddress(secret, salt, signingKey);
+ import { GrumpkinScalar } from '@aztec/aztec.js/fields';
+ const signingKey = GrumpkinScalar.random(); // supply your own signing key
+ const address = await getSchnorrAccountContractAddress(signingKey, salt, secret);
```

**Impact**: Account addresses change, since both the signing key and the privacy secret feed the address. Code that derived the signing key from the secret, or passed the secret first to the address helpers, no longer compiles.

### [Aztec.nr] `MessageContext` removed: message processing uses `ResolvedTx`

`aztec::messages::processing::MessageContext` has been removed in favor of the new `ResolvedTx`, which carries the same `tx_hash`, `unique_note_hashes_in_tx`, and `first_nullifier_in_tx`, plus `block_number` and `block_hash`. The offchain handoff type `OffchainMessageWithContext` is likewise renamed to `OffchainMessageWithTx`.
Expand Down Expand Up @@ -61,6 +84,7 @@ Registering classes and instances are now separate, unvalidated operations. `reg

- `pxe.getContractInstance(address)` and `wallet.getContractMetadata(address).instance` now return the contract's **address preimage**, which no longer includes `currentContractClassId`.


### [Aztec.js] `AccountWithSecretKey` removed, read account keys from the `AccountManager` or PXE

`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:
Expand All @@ -85,6 +109,24 @@ The PXE never receives the seed nor the message-signing and fallback secret keys

**Impact**: Importing `AccountWithSecretKey`, or calling `getSecretKey()`/`getEncryptionSecret()` on the result of `getAccount()`, no longer compiles. The signer `getAccount()` returns is otherwise unchanged, and passing a single `Fr` or a `MasterSecretKeys` to `wallet.registerContract` keeps working.

### [CLI] `aztec-wallet` `--secret-key` is renamed to `--signing-key`

`aztec-wallet` accounts are now rooted on their signing key rather than on a privacy secret. The `--secret-key` option (on `create-account` and `simulate`) is renamed to `--signing-key`, and the `SECRET_KEY` environment variable to `SIGNING_KEY`. When creating an account, a random signing key is generated by default and the privacy secret is derived from it.

**Migration:**

```diff
- aztec-wallet create-account --secret-key 0x...
+ aztec-wallet create-account --signing-key 0x...
```

**Impact**: The value you save and restore for an account is now its signing key, and account addresses change, so existing wallet databases and funded addresses are not carried over.

### [Bot] `senderPrivateKey` is now the account signing key

The transaction bot previously derived its account's signing key from the configured `senderPrivateKey`. That value is now used directly as the signing key, with the privacy secret derived from it.

**Impact**: The bot's account address changes for a given `senderPrivateKey`, so the account must be re-funded at its new address.

### [PXE] Unconstrained delivery defaults to a non-interactive handshake for external recipients

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,27 @@ An Aztec account has:

## Key Derivation

The wallet uses the standard derivation from `@aztec/stdlib/keys`:
The wallet generates a random signing key as the account's root and derives the privacy secret from it:

```typescript
import { deriveSigningKey } from '@aztec/stdlib/keys';
import { GrumpkinScalar } from '@aztec/aztec.js/fields';
import { deriveSecretKeyFromSigningKey } from '@aztec/accounts/utils';

// Generate a random secret
const secret = Fr.random();

// Derive the signing key
const signingKey = deriveSigningKey(secret);
// The signing key is the account's root; the privacy secret is derived from it
const signingKey = GrumpkinScalar.random();
const secret = await deriveSecretKeyFromSigningKey(signingKey);
```

The derivation uses SHA-512 with domain separators to derive different keys:
The nullifier, viewing, and tagging keys are then derived from that secret with SHA-512 and domain separators, while the signing key is supplied to the account contract directly:

```typescript
// From stdlib/src/keys/derivation.ts
export function deriveSigningKey(secretKey: Fr): GrumpkinScalar {
return sha512ToGrumpkinScalar([secretKey, GeneratorIndex.IVSK_M]);
export function deriveMasterIncomingViewingSecretKey(secretKey: Fr): GrumpkinScalar {
Comment thread
nventuro marked this conversation as resolved.
return sha512ToGrumpkinScalar([secretKey, DomainSeparator.IVSK_M]);
}

export function deriveMasterNullifierHidingSecretKey(secretKey: Fr): GrumpkinScalar {
return sha512ToGrumpkinScalar([secretKey, GeneratorIndex.NHK_M]);
return sha512ToGrumpkinScalar([secretKey, DomainSeparator.NHK_M]);
}
```

Expand Down
7 changes: 5 additions & 2 deletions docs/examples/ts/aztecjs_connection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ console.log(`Alice's fee juice balance: ${aliceBalance}`);
// docs:end:check_fee_juice

// docs:start:create_account
import { Fr } from "@aztec/aztec.js/fields";
import { Fr, GrumpkinScalar } from "@aztec/aztec.js/fields";

const secret = Fr.random();
const salt = Fr.random();
const newAccount = await wallet.createSchnorrAccount(secret, salt);
const signingKey = GrumpkinScalar.random();
const newAccount = await wallet.createSchnorrAccount(secret, salt, signingKey);
console.log("New account address:", newAccount.address.toString());
// docs:end:create_account

Expand Down Expand Up @@ -87,9 +88,11 @@ await deployMethod.send({
// can coexist in one example; in your own code, pick whichever name fits.
const feeJuiceSecret = Fr.random();
const feeJuiceSalt = Fr.random();
const feeJuiceSigningKey = GrumpkinScalar.random();
const feeJuiceAccount = await wallet.createSchnorrAccount(
feeJuiceSecret,
feeJuiceSalt,
feeJuiceSigningKey,
);
// docs:end:create_fee_juice_account

Expand Down
12 changes: 10 additions & 2 deletions docs/examples/ts/aztecjs_getting_started/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080";
const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true });

const [alice, bob] = await getInitialTestAccountsData();
await wallet.createSchnorrInitializerlessAccount(alice.secret, alice.salt);
await wallet.createSchnorrInitializerlessAccount(bob.secret, bob.salt);
await wallet.createSchnorrInitializerlessAccount(
alice.secret,
alice.salt,
alice.signingKey,
);
await wallet.createSchnorrInitializerlessAccount(
bob.secret,
bob.salt,
bob.signingKey,
);
// docs:end:setup

// docs:start:deploy
Expand Down
3 changes: 3 additions & 0 deletions docs/examples/ts/bob_token_contract/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,18 @@ async function main() {
const giggleAccountManager = await wallet.createSchnorrInitializerlessAccount(
giggleWalletData.secret,
giggleWalletData.salt,
giggleWalletData.signingKey,
);
const aliceAccountManager = await wallet.createSchnorrInitializerlessAccount(
aliceWalletData.secret,
aliceWalletData.salt,
aliceWalletData.signingKey,
);
const bobClinicAccountManager =
await wallet.createSchnorrInitializerlessAccount(
bobClinicWalletData.secret,
bobClinicWalletData.salt,
bobClinicWalletData.signingKey,
);

const giggleAddress = giggleAccountManager.address;
Expand Down
8 changes: 6 additions & 2 deletions docs/examples/ts/recursive_verification/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC";
import { ValueNotEqualContract } from "./artifacts/ValueNotEqual.js";
import { EmbeddedWallet } from "@aztec/wallets/embedded";
import { NO_FROM } from "@aztec/aztec.js/account";
import { Fr } from "@aztec/aztec.js/fields";
import { Fr, GrumpkinScalar } from "@aztec/aztec.js/fields";
import assert from "node:assert";
import fs from "node:fs";

Expand Down Expand Up @@ -47,7 +47,11 @@ async function main() {
// Step 1: Setup wallet and create account
// Accounts in Aztec are smart contracts (account abstraction)
const wallet = await setupWallet();
const manager = await wallet.createSchnorrAccount(Fr.random(), Fr.random());
const manager = await wallet.createSchnorrAccount(
Fr.random(),
Fr.random(),
GrumpkinScalar.random(),
);

// Deploy the account contract
const deployMethod = await manager.getDeployMethod();
Expand Down
1 change: 1 addition & 0 deletions docs/examples/ts/token_bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const [accData] = await getInitialTestAccountsData();
const account = await aztecWallet.createSchnorrInitializerlessAccount(
accData.secret,
accData.salt,
accData.signingKey,
);
console.log(`Account: ${account.address.toString()}\n`);

Expand Down
18 changes: 12 additions & 6 deletions docs/examples/webapp-tutorial/scripts/deploy-and-interact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080";
const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true });

const [alice, bob] = await getInitialTestAccountsData();
await wallet.createSchnorrAccount(alice.secret, alice.salt);
await wallet.createSchnorrAccount(bob.secret, bob.salt);
console.log("Accounts ready:", alice.address.toString(), bob.address.toString());
await wallet.createSchnorrAccount(alice.secret, alice.salt, alice.signingKey);
await wallet.createSchnorrAccount(bob.secret, bob.salt, bob.signingKey);
console.log(
"Accounts ready:",
alice.address.toString(),
bob.address.toString(),
);
// docs:end:script-setup

// docs:start:script-deploy
const { contract } = await PodRacingContract.deploy(wallet, alice.address).send({
from: alice.address,
});
const { contract } = await PodRacingContract.deploy(wallet, alice.address).send(
{
from: alice.address,
},
);
console.log("Contract deployed at:", contract.address.toString());
// docs:end:script-deploy

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* account creation, deployment, and registration.
*/

import { getAztecCore } from './aztec-imports';
import { getAztecCore } from "./aztec-imports";

/**
* Derives keys and instantiates a Schnorr account contract from a secret and salt.
Expand All @@ -21,17 +21,18 @@ import { getAztecCore } from './aztec-imports';
export async function instantiateAccount(secret: string, salt: string) {
const {
Fr,
GrumpkinScalar,
deriveKeys,
deriveSigningKey,
deriveSecretKeyFromSigningKey,
SchnorrAccountContract,
getContractInstanceFromInstantiationParams,
} = await getAztecCore();

const secretFr = Fr.fromString(secret);
const signingKey = GrumpkinScalar.fromString(secret);
const saltFr = Fr.fromString(salt);

const secretFr = await deriveSecretKeyFromSigningKey(signingKey);
const { publicKeys } = await deriveKeys(secretFr);
const signingKey = deriveSigningKey(secretFr);
const accountContract = new SchnorrAccountContract(signingKey);

const initInfo = await accountContract.getInitializationFunctionAndArgs();
Expand Down
71 changes: 38 additions & 33 deletions docs/examples/webapp-tutorial/test-extension/src/aztec-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,30 @@

/** Core imports needed for account operations (key derivation, contract setup) */
export interface AztecCoreImports {
Fr: typeof import('@aztec/aztec.js/fields').Fr;
AztecAddress: typeof import('@aztec/aztec.js/addresses').AztecAddress;
deriveKeys: typeof import('@aztec/stdlib/keys').deriveKeys;
deriveSigningKey: typeof import('@aztec/stdlib/keys').deriveSigningKey;
SchnorrAccountContract: typeof import('@aztec/accounts/schnorr/lazy').SchnorrAccountContract;
getContractInstanceFromInstantiationParams: typeof import('@aztec/aztec.js/contracts').getContractInstanceFromInstantiationParams;
AccountManager: typeof import('@aztec/aztec.js/wallet').AccountManager;
Fr: typeof import("@aztec/aztec.js/fields").Fr;
GrumpkinScalar: typeof import("@aztec/aztec.js/fields").GrumpkinScalar;
AztecAddress: typeof import("@aztec/aztec.js/addresses").AztecAddress;
deriveKeys: typeof import("@aztec/stdlib/keys").deriveKeys;
deriveSecretKeyFromSigningKey: typeof import("@aztec/accounts/utils").deriveSecretKeyFromSigningKey;
SchnorrAccountContract: typeof import("@aztec/accounts/schnorr/lazy").SchnorrAccountContract;
getContractInstanceFromInstantiationParams: typeof import("@aztec/aztec.js/contracts").getContractInstanceFromInstantiationParams;
AccountManager: typeof import("@aztec/aztec.js/wallet").AccountManager;
}

/** Additional imports for the wallet runtime (BaseWallet, serialization) */
export interface AztecWalletImports extends AztecCoreImports {
BaseWallet: typeof import('@aztec/wallet-sdk/base-wallet').BaseWallet;
SignerlessAccount: typeof import('@aztec/aztec.js/account').SignerlessAccount;
WalletSchema: typeof import('@aztec/aztec.js/wallet').WalletSchema;
jsonStringify: typeof import('@aztec/foundation/json-rpc').jsonStringify;
schemaHasMethod: typeof import('@aztec/foundation/schemas').schemaHasMethod;
BaseWallet: typeof import("@aztec/wallet-sdk/base-wallet").BaseWallet;
SignerlessAccount: typeof import("@aztec/aztec.js/account").SignerlessAccount;
WalletSchema: typeof import("@aztec/aztec.js/wallet").WalletSchema;
jsonStringify: typeof import("@aztec/foundation/json-rpc").jsonStringify;
schemaHasMethod: typeof import("@aztec/foundation/schemas").schemaHasMethod;
}

/** Deploy-specific imports (fee payment, SponsoredFPC) */
export interface AztecDeployImports extends AztecCoreImports {
SponsoredFeePaymentMethod: typeof import('@aztec/aztec.js/fee').SponsoredFeePaymentMethod;
SponsoredFPCContract: typeof import('@aztec/noir-contracts.js/SponsoredFPC').SponsoredFPCContract;
SPONSORED_FPC_SALT: typeof import('@aztec/constants').SPONSORED_FPC_SALT;
SponsoredFeePaymentMethod: typeof import("@aztec/aztec.js/fee").SponsoredFeePaymentMethod;
SponsoredFPCContract: typeof import("@aztec/noir-contracts.js/SponsoredFPC").SponsoredFPCContract;
SPONSORED_FPC_SALT: typeof import("@aztec/constants").SPONSORED_FPC_SALT;
}

let coreCache: AztecCoreImports | null = null;
Expand All @@ -50,22 +51,26 @@ let deployCache: AztecDeployImports | null = null;
export async function getAztecCore(): Promise<AztecCoreImports> {
if (coreCache) return coreCache;

const [fields, addresses, keys, schnorr, contracts, wallet] = await Promise.all([
import('@aztec/aztec.js/fields'),
import('@aztec/aztec.js/addresses'),
import('@aztec/stdlib/keys'),
import('@aztec/accounts/schnorr/lazy'),
import('@aztec/aztec.js/contracts'),
import('@aztec/aztec.js/wallet'),
]);
const [fields, addresses, keys, accountUtils, schnorr, contracts, wallet] =
await Promise.all([
import("@aztec/aztec.js/fields"),
import("@aztec/aztec.js/addresses"),
import("@aztec/stdlib/keys"),
import("@aztec/accounts/utils"),
import("@aztec/accounts/schnorr/lazy"),
import("@aztec/aztec.js/contracts"),
import("@aztec/aztec.js/wallet"),
]);

coreCache = {
Fr: fields.Fr,
GrumpkinScalar: fields.GrumpkinScalar,
AztecAddress: addresses.AztecAddress,
deriveKeys: keys.deriveKeys,
deriveSigningKey: keys.deriveSigningKey,
deriveSecretKeyFromSigningKey: accountUtils.deriveSecretKeyFromSigningKey,
SchnorrAccountContract: schnorr.SchnorrAccountContract,
getContractInstanceFromInstantiationParams: contracts.getContractInstanceFromInstantiationParams,
getContractInstanceFromInstantiationParams:
contracts.getContractInstanceFromInstantiationParams,
AccountManager: wallet.AccountManager,
};

Expand All @@ -81,11 +86,11 @@ export async function getAztecWallet(): Promise<AztecWalletImports> {

const [core, bw, account, walletMod, jsonRpc, schemas] = await Promise.all([
getAztecCore(),
import('@aztec/wallet-sdk/base-wallet'),
import('@aztec/aztec.js/account'),
import('@aztec/aztec.js/wallet'),
import('@aztec/foundation/json-rpc'),
import('@aztec/foundation/schemas'),
import("@aztec/wallet-sdk/base-wallet"),
import("@aztec/aztec.js/account"),
import("@aztec/aztec.js/wallet"),
import("@aztec/foundation/json-rpc"),
import("@aztec/foundation/schemas"),
]);

walletCache = {
Expand All @@ -109,9 +114,9 @@ export async function getAztecDeploy(): Promise<AztecDeployImports> {

const [core, fee, sponsoredFpc, constants] = await Promise.all([
getAztecCore(),
import('@aztec/aztec.js/fee'),
import('@aztec/noir-contracts.js/SponsoredFPC'),
import('@aztec/constants'),
import("@aztec/aztec.js/fee"),
import("@aztec/noir-contracts.js/SponsoredFPC"),
import("@aztec/constants"),
]);

deployCache = {
Expand Down
Loading
Loading