@aztec/cli 3.0.0-nightly.20251115 → 3.0.0-nightly.20251119

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/add.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAQnD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAU5D,MAAM,MAAM,uBAAuB,GAAG,2BAA2B,CAAC;AAElE,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,EAAE,GAAG,EAAE,KAAK,iBAyFpG"}
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/add.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAQnD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAW5D,MAAM,MAAM,uBAAuB,GAAG,2BAA2B,CAAC;AAElE,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,EAAE,GAAG,EAAE,KAAK,iBAkGpG"}
@@ -2,9 +2,11 @@ import { loadKeystoreFile } from '@aztec/node-keystore/loader';
2
2
  import { wordlist } from '@scure/bip39/wordlists/english.js';
3
3
  import { dirname, isAbsolute, join } from 'path';
4
4
  import { generateMnemonic } from 'viem/accounts';
5
- import { buildValidatorEntries, logValidatorSummaries, maybePrintJson, writeBlsBn254ToFile, writeEthJsonV3ToFile, writeKeystoreFile } from './shared.js';
5
+ import { buildValidatorEntries, logValidatorSummaries, maybePrintJson, validateBlsPathOptions, writeBlsBn254ToFile, writeEthJsonV3ToFile, writeKeystoreFile } from './shared.js';
6
6
  export async function addValidatorKeys(existing, options, log) {
7
- const { dataDir, file, count, publisherCount = 0, mnemonic, accountIndex = 0, addressIndex, ikm, blsPath, json, feeRecipient: feeRecipientOpt, coinbase: coinbaseOpt, fundingAccount: fundingAccountOpt, remoteSigner: remoteSignerOpt, password, outDir } = options;
7
+ // validate bls-path inputs before proceeding with key generation
8
+ validateBlsPathOptions(options);
9
+ const { dataDir, file, count, publisherCount = 0, mnemonic, accountIndex = 0, addressIndex, ikm, blsPath, json, feeRecipient: feeRecipientOpt, coinbase: coinbaseOpt, fundingAccount: fundingAccountOpt, remoteSigner: remoteSignerOpt, password, encryptedKeystoreDir } = options;
8
10
  const validatorCount = typeof count === 'number' && Number.isFinite(count) && count > 0 ? Math.floor(count) : 1;
9
11
  const baseAddressIndex = addressIndex ?? 0;
10
12
  const keystore = loadKeystoreFile(existing);
@@ -40,14 +42,22 @@ export async function addValidatorKeys(existing, options, log) {
40
42
  keystore.validators.push(...validators);
41
43
  // If password provided, write ETH JSON V3 and BLS BN254 keystores and replace plaintext
42
44
  if (password !== undefined) {
43
- const targetDir = outDir && outDir.length > 0 ? outDir : dataDir && dataDir.length > 0 ? dataDir : dirname(existing);
45
+ let targetDir;
46
+ if (encryptedKeystoreDir && encryptedKeystoreDir.length > 0) {
47
+ targetDir = encryptedKeystoreDir;
48
+ } else if (dataDir && dataDir.length > 0) {
49
+ targetDir = dataDir;
50
+ } else {
51
+ targetDir = dirname(existing);
52
+ }
44
53
  await writeEthJsonV3ToFile(keystore.validators, {
45
54
  outDir: targetDir,
46
55
  password
47
56
  });
48
57
  await writeBlsBn254ToFile(keystore.validators, {
49
58
  outDir: targetDir,
50
- password
59
+ password,
60
+ blsPath
51
61
  });
52
62
  }
53
63
  let outputPath = existing;
@@ -1,9 +1,9 @@
1
1
  import { deriveBlsPrivateKey } from '@aztec/foundation/crypto';
2
2
  import { writeFile } from 'fs/promises';
3
- import { computeBlsPublicKeyCompressed, withValidatorIndex } from './shared.js';
3
+ import { computeBlsPublicKeyCompressed, defaultBlsPath, withValidatorIndex } from './shared.js';
4
4
  export async function generateBlsKeypair(options, log) {
5
5
  const { mnemonic, ikm, blsPath, compressed = true, json, out } = options;
6
- const path = withValidatorIndex(blsPath ?? 'm/12381/3600/0/0/0', 0);
6
+ const path = withValidatorIndex(blsPath ?? defaultBlsPath, 0);
7
7
  const priv = deriveBlsPrivateKey(mnemonic, ikm, path);
8
8
  const pub = await computeBlsPublicKeyCompressed(priv);
9
9
  const result = {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,WAqH1D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,WAsH1D"}
@@ -1,14 +1,15 @@
1
1
  import { parseAztecAddress, parseEthereumAddress, parseHex, parseOptionalInteger } from '../../utils/commands.js';
2
+ import { defaultBlsPath } from './shared.js';
2
3
  export function injectCommands(program, log) {
3
4
  const group = program.command('validator-keys').aliases([
4
5
  'valKeys',
5
6
  'valkeys'
6
7
  ]).description('Manage validator keystores for node operators');
7
- group.command('new').summary('Generate a new validator keystore JSON').description('Generates a new validator keystore with ETH secp256k1 accounts and optional BLS accounts').option('--data-dir <path>', 'Directory to store keystore(s). Defaults to ~/.aztec/keystore').option('--file <name>', 'Keystore file name. Defaults to key1.json (or keyN.json if key1.json exists)').option('--count <N>', 'Number of validators to generate', parseOptionalInteger).option('--publisher-count <N>', 'Number of publisher accounts per validator (default 1)', (value)=>parseOptionalInteger(value, 0)).option('--mnemonic <mnemonic>', 'Mnemonic for ETH/BLS derivation').option('--passphrase <str>', 'Optional passphrase for mnemonic').option('--account-index <N>', 'Base account index for ETH derivation', parseOptionalInteger).option('--address-index <N>', 'Base address index for ETH derivation', parseOptionalInteger).option('--coinbase <address>', 'Coinbase ETH address to use when proposing', parseEthereumAddress).option('--funding-account <address>', 'ETH account to fund publishers', parseEthereumAddress).option('--remote-signer <url>', 'Default remote signer URL for accounts in this file').option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', (value)=>parseHex(value, 32)).option('--bls-path <path>', 'EIP-2334 path (default m/12381/3600/0/0/0)').option('--password <str>', 'Password for writing keystore files (ETH JSON V3 and BLS EIP-2335). Empty string allowed').option('--out-dir <dir>', 'Output directory for generated keystore file(s)').option('--json', 'Echo resulting JSON to stdout').option('--staker-output', 'Generate staker output JSON files for each attester').option('--gse-address <address>', 'GSE contract address (required with --staker-output)', parseEthereumAddress).option('--l1-rpc-urls <urls>', 'L1 RPC URLs (comma-separated, required with --staker-output)', (value)=>value.split(',')).option('-c, --l1-chain-id <number>', 'L1 chain ID (required with --staker-output)', (value)=>parseInt(value), 31337).requiredOption('--fee-recipient <address>', 'Aztec address that will receive fees', parseAztecAddress).action(async (options)=>{
8
+ group.command('new').summary('Generate a new validator keystore JSON').description('Generates a new validator keystore with ETH secp256k1 accounts and optional BLS accounts').option('--data-dir <path>', 'Directory to store keystore(s). Defaults to ~/.aztec/keystore').option('--file <name>', 'Keystore file name. Defaults to key1.json (or keyN.json if key1.json exists)').option('--count <N>', 'Number of validators to generate', parseOptionalInteger).option('--publisher-count <N>', 'Number of publisher accounts per validator (default 1)', (value)=>parseOptionalInteger(value, 0)).option('--mnemonic <mnemonic>', 'Mnemonic for ETH/BLS derivation').option('--passphrase <str>', 'Optional passphrase for mnemonic').option('--account-index <N>', 'Base account index for ETH/BLS derivation', parseOptionalInteger).option('--address-index <N>', 'Base address index for ETH/BLS derivation', parseOptionalInteger).option('--coinbase <address>', 'Coinbase ETH address to use when proposing', parseEthereumAddress).option('--funding-account <address>', 'ETH account to fund publishers', parseEthereumAddress).option('--remote-signer <url>', 'Default remote signer URL for accounts in this file').option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', (value)=>parseHex(value, 32)).option('--bls-path <path>', `EIP-2334 path (default ${defaultBlsPath})`).option('--password <str>', 'Password for writing keystore files (ETH JSON V3 and BLS EIP-2335). Empty string allowed').option('--encrypted-keystore-dir <dir>', 'Output directory for encrypted keystore file(s)').option('--json', 'Echo resulting JSON to stdout').option('--staker-output', 'Generate a single staker output JSON file with an array of validator entries').option('--gse-address <address>', 'GSE contract address (required with --staker-output)', parseEthereumAddress).option('--l1-rpc-urls <urls>', 'L1 RPC URLs (comma-separated, required with --staker-output)', (value)=>value.split(',')).option('-c, --l1-chain-id <number>', 'L1 chain ID (required with --staker-output)', (value)=>parseInt(value), 31337).requiredOption('--fee-recipient <address>', 'Aztec address that will receive fees', parseAztecAddress).action(async (options)=>{
8
9
  const { newValidatorKeystore } = await import('./new.js');
9
10
  await newValidatorKeystore(options, log);
10
11
  });
11
- group.command('add').summary('Augment an existing validator keystore JSON').description('Adds attester/publisher/BLS entries to an existing keystore using the same flags as new').argument('<existing>', 'Path to existing keystore JSON').option('--data-dir <path>', 'Directory where keystore(s) live').option('--file <name>', 'Override output file name').option('--count <N>', 'Number of validators to add', parseOptionalInteger).option('--publisher-count <N>', 'Number of publisher accounts per validator (default 1)', (value)=>parseOptionalInteger(value, 0)).option('--mnemonic <mnemonic>', 'Mnemonic for ETH/BLS derivation').option('--passphrase <str>', 'Optional passphrase for mnemonic').option('--account-index <N>', 'Base account index for ETH derivation', parseOptionalInteger).option('--address-index <N>', 'Base address index for ETH derivation', parseOptionalInteger).option('--coinbase <address>', 'Coinbase ETH address to use when proposing', parseEthereumAddress).option('--funding-account <address>', 'ETH account to fund publishers', parseEthereumAddress).option('--remote-signer <url>', 'Default remote signer URL for accounts in this file').option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', (value)=>parseHex(value, 32)).option('--bls-path <path>', 'EIP-2334 path (default m/12381/3600/0/0/0)').option('--empty', 'Generate an empty skeleton without keys').option('--password <str>', 'Password for writing keystore files (ETH JSON V3 and BLS EIP-2335). Empty string allowed').option('--out-dir <dir>', 'Output directory for generated keystore file(s)').option('--json', 'Echo resulting JSON to stdout').requiredOption('--fee-recipient <address>', 'Aztec address that will receive fees', parseAztecAddress).action(async (existing, options)=>{
12
+ group.command('add').summary('Augment an existing validator keystore JSON').description('Adds attester/publisher/BLS entries to an existing keystore using the same flags as new').argument('<existing>', 'Path to existing keystore JSON').option('--data-dir <path>', 'Directory where keystore(s) live. (default: ~/.aztec/keystore)').option('--file <name>', 'Override output file name. (default: key<N>.json)').option('--count <N>', 'Number of validators to add. (default: 1)', parseOptionalInteger).option('--publisher-count <N>', 'Number of publisher accounts per validator (default 1)', (value)=>parseOptionalInteger(value, 0)).option('--mnemonic <mnemonic>', 'Mnemonic for ETH/BLS derivation').option('--passphrase <str>', 'Optional passphrase for mnemonic').option('--account-index <N>', 'Base account index for ETH/BLS derivation', parseOptionalInteger).option('--address-index <N>', 'Base address index for ETH/BLS derivation', parseOptionalInteger).option('--coinbase <address>', 'Coinbase ETH address to use when proposing', parseEthereumAddress).option('--funding-account <address>', 'ETH account to fund publishers', parseEthereumAddress).option('--remote-signer <url>', 'Default remote signer URL for accounts in this file').option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', (value)=>parseHex(value, 32)).option('--bls-path <path>', `EIP-2334 path (default ${defaultBlsPath})`).option('--empty', 'Generate an empty skeleton without keys').option('--password <str>', 'Password for writing keystore files (ETH JSON V3 and BLS EIP-2335). Empty string allowed').option('--encrypted-keystore-dir <dir>', 'Output directory for encrypted keystore file(s)').option('--json', 'Echo resulting JSON to stdout').requiredOption('--fee-recipient <address>', 'Aztec address that will receive fees', parseAztecAddress).action(async (existing, options)=>{
12
13
  const { addValidatorKeys } = await import('./add.js');
13
14
  await addValidatorKeys(existing, options, log);
14
15
  });
@@ -19,7 +20,7 @@ export function injectCommands(program, log) {
19
20
  await generateStakerJson(options, log);
20
21
  });
21
22
  // top-level convenience: aztec generate-bls-keypair
22
- program.command('generate-bls-keypair').description('Generate a BLS keypair with convenience flags').option('--mnemonic <mnemonic>', 'Mnemonic for BLS derivation').option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', (value)=>parseHex(value, 32)).option('--bls-path <path>', 'EIP-2334 path (default m/12381/3600/0/0/0)').option('--g2', 'Derive on G2 subgroup').option('--compressed', 'Output compressed public key').option('--json', 'Print JSON output to stdout').option('--out <file>', 'Write output to file').action(async (options)=>{
23
+ program.command('generate-bls-keypair').description('Generate a BLS keypair with convenience flags').option('--mnemonic <mnemonic>', 'Mnemonic for BLS derivation').option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', (value)=>parseHex(value, 32)).option('--bls-path <path>', `EIP-2334 path (default ${defaultBlsPath})`).option('--g2', 'Derive on G2 subgroup').option('--compressed', 'Output compressed public key').option('--json', 'Print JSON output to stdout').option('--out <file>', 'Write output to file').action(async (options)=>{
23
24
  const { generateBlsKeypair } = await import('./generate_bls_keypair.js');
24
25
  await generateBlsKeypair(options, log);
25
26
  });
@@ -14,7 +14,7 @@ export type NewValidatorKeystoreOptions = {
14
14
  ikm?: string;
15
15
  blsPath?: string;
16
16
  password?: string;
17
- outDir?: string;
17
+ encryptedKeystoreDir?: string;
18
18
  json?: boolean;
19
19
  feeRecipient: AztecAddress;
20
20
  coinbase?: EthAddress;
@@ -1 +1 @@
1
- {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/new.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAmBhE,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,EAAE,GAAG,EAAE,KAAK,iBA+J1F"}
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/new.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAoBhE,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,EAAE,GAAG,EAAE,KAAK,iBAoK1F"}
@@ -5,10 +5,12 @@ import { writeFile } from 'fs/promises';
5
5
  import { basename, dirname, join } from 'path';
6
6
  import { createPublicClient, fallback, http } from 'viem';
7
7
  import { generateMnemonic, mnemonicToAccount } from 'viem/accounts';
8
- import { buildValidatorEntries, logValidatorSummaries, maybePrintJson, resolveKeystoreOutputPath, writeBlsBn254ToFile, writeEthJsonV3ToFile, writeKeystoreFile } from './shared.js';
8
+ import { buildValidatorEntries, logValidatorSummaries, maybePrintJson, resolveKeystoreOutputPath, validateBlsPathOptions, writeBlsBn254ToFile, writeEthJsonV3ToFile, writeKeystoreFile } from './shared.js';
9
9
  import { processAttesterAccounts } from './staker.js';
10
10
  export async function newValidatorKeystore(options, log) {
11
- const { dataDir, file, count, publisherCount = 0, json, coinbase, accountIndex = 0, addressIndex = 0, feeRecipient, remoteSigner, fundingAccount, blsPath, ikm, mnemonic: _mnemonic, password, outDir, stakerOutput, gseAddress, l1RpcUrls, l1ChainId } = options;
11
+ // validate bls-path inputs before proceeding with key generation
12
+ validateBlsPathOptions(options);
13
+ const { dataDir, file, count, publisherCount = 0, json, coinbase, accountIndex = 0, addressIndex = 0, feeRecipient, remoteSigner, fundingAccount, blsPath, ikm, mnemonic: _mnemonic, password, encryptedKeystoreDir, stakerOutput, gseAddress, l1RpcUrls, l1ChainId } = options;
12
14
  // Validate staker output requirements
13
15
  if (stakerOutput) {
14
16
  if (!gseAddress) {
@@ -34,6 +36,7 @@ export async function newValidatorKeystore(options, log) {
34
36
  }
35
37
  const validatorCount = typeof count === 'number' && Number.isFinite(count) && count > 0 ? Math.floor(count) : 1;
36
38
  const { outputPath } = await resolveKeystoreOutputPath(dataDir, file);
39
+ const keystoreOutDir = dirname(outputPath);
37
40
  const { validators, summaries } = await buildValidatorEntries({
38
41
  validatorCount,
39
42
  publisherCount,
@@ -49,13 +52,13 @@ export async function newValidatorKeystore(options, log) {
49
52
  });
50
53
  // If password provided, write ETH JSON V3 and BLS BN254 keystores and replace plaintext
51
54
  if (password !== undefined) {
52
- const keystoreOutDir = outDir && outDir.length > 0 ? outDir : dirname(outputPath);
55
+ const encryptedKeystoreOutDir = encryptedKeystoreDir && encryptedKeystoreDir.length > 0 ? encryptedKeystoreDir : keystoreOutDir;
53
56
  await writeEthJsonV3ToFile(validators, {
54
- outDir: keystoreOutDir,
57
+ outDir: encryptedKeystoreOutDir,
55
58
  password
56
59
  });
57
60
  await writeBlsBn254ToFile(validators, {
58
- outDir: keystoreOutDir,
61
+ outDir: encryptedKeystoreOutDir,
59
62
  password
60
63
  });
61
64
  }
@@ -73,21 +76,22 @@ export async function newValidatorKeystore(options, log) {
73
76
  transport: fallback(l1RpcUrls.map((url)=>http(url)))
74
77
  });
75
78
  const gse = new GSEContract(publicClient, gseAddress);
76
- const keystoreOutDir = outDir && outDir.length > 0 ? outDir : dirname(outputPath);
77
79
  // Extract keystore base name without extension for unique staker output filenames
78
80
  const keystoreBaseName = basename(outputPath, '.json');
79
81
  // Process each validator
80
82
  for(let i = 0; i < validators.length; i++){
81
83
  const validator = validators[i];
82
84
  const outputs = await processAttesterAccounts(validator.attester, gse, password);
83
- // Save each attester's staker output
85
+ // Collect all staker outputs
84
86
  for(let j = 0; j < outputs.length; j++){
85
- const attesterIndex = i + 1;
86
- const stakerOutputPath = join(keystoreOutDir, `${keystoreBaseName}_attester${attesterIndex}_staker_output.json`);
87
- await writeFile(stakerOutputPath, prettyPrintJSON(outputs[j]), 'utf-8');
88
87
  allStakerOutputs.push(outputs[j]);
89
88
  }
90
89
  }
90
+ // Write a single JSON file with all staker outputs
91
+ if (allStakerOutputs.length > 0) {
92
+ const stakerOutputPath = join(keystoreOutDir, `${keystoreBaseName}_staker_output.json`);
93
+ await writeFile(stakerOutputPath, prettyPrintJSON(allStakerOutputs), 'utf-8');
94
+ }
91
95
  }
92
96
  const outputData = !_mnemonic ? {
93
97
  ...keystore,
@@ -107,8 +111,9 @@ export async function newValidatorKeystore(options, log) {
107
111
  } else {
108
112
  log(`Wrote validator keystore to ${outputPath}`);
109
113
  if (stakerOutput && allStakerOutputs.length > 0) {
110
- const keystoreOutDir = outDir && outDir.length > 0 ? outDir : dirname(outputPath);
111
- log(`Wrote ${allStakerOutputs.length} staker output file(s) to ${keystoreOutDir}`);
114
+ const keystoreBaseName = basename(outputPath, '.json');
115
+ const stakerOutputPath = join(keystoreOutDir, `${keystoreBaseName}_staker_output.json`);
116
+ log(`Wrote staker output for ${allStakerOutputs.length} validator(s) to ${stakerOutputPath}`);
112
117
  log('');
113
118
  }
114
119
  }
@@ -20,7 +20,16 @@ export type BuildValidatorsInput = {
20
20
  remoteSigner?: string;
21
21
  fundingAccount?: EthAddress;
22
22
  };
23
- export declare function withValidatorIndex(path: string, index: number): string;
23
+ export declare const defaultBlsPath = "m/12381/3600/0/0/0";
24
+ export declare function validateBlsPathOptions(options: {
25
+ count?: number;
26
+ publisherCount?: number;
27
+ accountIndex?: number;
28
+ addressIndex?: number;
29
+ blsPath?: string;
30
+ ikm?: string;
31
+ }): void;
32
+ export declare function withValidatorIndex(path: string, accountIndex?: number, addressIndex?: number): string;
24
33
  /**
25
34
  * Compute a compressed BN254 G1 public key from a private key.
26
35
  * @param privateKeyHex - Private key as 0x-prefixed hex string
@@ -56,6 +65,7 @@ export declare function writeBn254BlsKeystore(outDir: string, fileNameBase: stri
56
65
  export declare function writeBlsBn254ToFile(validators: ValidatorKeyStore[], options: {
57
66
  outDir: string;
58
67
  password: string;
68
+ blsPath?: string;
59
69
  }): Promise<void>;
60
70
  /** Writes an Ethereum JSON V3 keystore using ethers, returns absolute path */
61
71
  export declare function writeEthJsonV3Keystore(outDir: string, fileNameBase: string, password: string, privateKeyHex: string): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/shared.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/shared.ts"],"names":[],"mappings":"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"}
@@ -7,10 +7,34 @@ import { access, writeFile } from 'fs/promises';
7
7
  import { homedir } from 'os';
8
8
  import { dirname, isAbsolute, join } from 'path';
9
9
  import { mnemonicToAccount } from 'viem/accounts';
10
- export function withValidatorIndex(path, index) {
10
+ export const defaultBlsPath = 'm/12381/3600/0/0/0';
11
+ export function validateBlsPathOptions(options) {
12
+ if (options.blsPath && options.blsPath !== defaultBlsPath) {
13
+ if (options.count && options.count !== 1 || options.publisherCount && options.publisherCount > 0 || options.accountIndex && options.accountIndex !== 0 || options.addressIndex && options.addressIndex !== 0) {
14
+ throw new Error('--bls-path cannot be used with --count, --publisher-count, --account-index, or --address-index');
15
+ }
16
+ }
17
+ }
18
+ export function withValidatorIndex(path, accountIndex = 0, addressIndex = 0) {
19
+ // NOTE: The legacy BLS CLI is to allow users who generated keys in 2.1.4 to be able to use the same command
20
+ // to re-generate their keys. In 2.1.5 we switched how we append addresses to the path so this is to maintain backwards compatibility.
21
+ const useLegacyBlsCli = [
22
+ 'true',
23
+ '1',
24
+ 'yes',
25
+ 'y'
26
+ ].includes(process.env.LEGACY_BLS_CLI ?? '');
27
+ const defaultBlsPathParts = defaultBlsPath.split('/');
11
28
  const parts = path.split('/');
12
- if (parts.length >= 4 && parts[0] === 'm' && parts[1] === '12381' && parts[2] === '3600') {
13
- parts[3] = String(index);
29
+ if (parts.length == defaultBlsPathParts.length && parts.every((part, index)=>part === defaultBlsPathParts[index])) {
30
+ if (useLegacyBlsCli) {
31
+ // In 2.1.4, we were using address-index in parts[3] and did NOT use account-index, check lines 32 & 84
32
+ // https://github.com/AztecProtocol/aztec-packages/blob/v2.1.4/yarn-project/cli/src/cmds/validator_keys/shared.ts
33
+ parts[3] = String(addressIndex);
34
+ } else {
35
+ parts[3] = String(accountIndex);
36
+ parts[5] = String(addressIndex);
37
+ }
14
38
  return parts.join('/');
15
39
  }
16
40
  return path;
@@ -34,14 +58,13 @@ export function deriveEthAttester(mnemonic, baseAccountIndex, addressIndex, remo
34
58
  }
35
59
  export async function buildValidatorEntries(input) {
36
60
  const { validatorCount, publisherCount = 0, accountIndex, baseAddressIndex, mnemonic, ikm, blsPath, feeRecipient, coinbase, remoteSigner, fundingAccount } = input;
37
- const defaultBlsPath = 'm/12381/3600/0/0/0';
38
61
  const summaries = [];
39
62
  const validators = await Promise.all(Array.from({
40
63
  length: validatorCount
41
64
  }, async (_unused, i)=>{
42
65
  const addressIndex = baseAddressIndex + i;
43
66
  const basePath = blsPath ?? defaultBlsPath;
44
- const perValidatorPath = withValidatorIndex(basePath, addressIndex);
67
+ const perValidatorPath = withValidatorIndex(basePath, accountIndex, addressIndex);
45
68
  const blsPrivKey = ikm || mnemonic ? deriveBlsPrivateKey(mnemonic, ikm, perValidatorPath) : undefined;
46
69
  const blsPubCompressed = blsPrivKey ? await computeBlsPublicKeyCompressed(blsPrivKey) : undefined;
47
70
  const ethAttester = deriveEthAttester(mnemonic, accountIndex, addressIndex, remoteSigner);
@@ -190,7 +213,7 @@ export function maybePrintJson(log, jsonFlag, obj) {
190
213
  continue;
191
214
  }
192
215
  const pub = await computeBlsPublicKeyCompressed(blsKey);
193
- const path = 'm/12381/3600/0/0/0';
216
+ const path = options.blsPath ?? defaultBlsPath;
194
217
  const fileBase = `${String(i + 1)}_${pub.slice(2, 18)}`;
195
218
  const keystorePath = await writeBn254BlsKeystore(options.outDir, fileBase, options.password, blsKey, pub, path);
196
219
  if (typeof att === 'object') {
@@ -1 +1 @@
1
- {"version":3,"file":"staker.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/staker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAuB,MAAM,iBAAiB,CAAC;AAGnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,KAAK,EAEV,gBAAgB,EAKjB,MAAM,4BAA4B,CAAC;AAOpC,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE;QACX,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;IACF,WAAW,EAAE;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,iBAAiB,EAAE;QACjB,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;CACH,CAAC;AA2KF;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,gBAAgB,EAAE,gBAAgB,EAClC,GAAG,EAAE,WAAW,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,EAAE,CAAC,CAqBzB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C1F"}
1
+ {"version":3,"file":"staker.d.ts","sourceRoot":"","sources":["../../../src/cmds/validator_keys/staker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAuB,MAAM,iBAAiB,CAAC;AAGnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,KAAK,EAEV,gBAAgB,EAKjB,MAAM,4BAA4B,CAAC;AAQpC,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE;QACX,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;IACF,WAAW,EAAE;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,iBAAiB,EAAE;QACjB,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;CACH,CAAC;AA2KF;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,gBAAgB,EAAE,gBAAgB,EAClC,GAAG,EAAE,WAAW,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,EAAE,CAAC,CAqBzB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C1F"}
@@ -6,6 +6,7 @@ import { Fr } from '@aztec/foundation/fields';
6
6
  import { loadKeystoreFile } from '@aztec/node-keystore/loader';
7
7
  import { Wallet } from '@ethersproject/wallet';
8
8
  import { readFileSync, writeFileSync } from 'fs';
9
+ import { basename, dirname, join } from 'path';
9
10
  import { createPublicClient, fallback, http } from 'viem';
10
11
  import { privateKeyToAddress } from 'viem/accounts';
11
12
  /**
@@ -186,21 +187,21 @@ import { privateKeyToAddress } from 'viem/accounts';
186
187
  transport: fallback(l1RpcUrls.map((url)=>http(url)))
187
188
  });
188
189
  const gse = new GSEContract(publicClient, gseAddress);
189
- // Process each validator
190
- for (const validator of keystore.validators){
190
+ const keystoreBaseName = basename(from, '.json');
191
+ const outputDir = output ? output : dirname(from);
192
+ for(let i = 0; i < keystore.validators.length; i++){
193
+ const validator = keystore.validators[i];
191
194
  const outputs = await processAttesterAccounts(validator.attester, gse, password);
192
- allOutputs.push(...outputs);
195
+ for(let j = 0; j < outputs.length; j++){
196
+ allOutputs.push(outputs[j]);
197
+ }
193
198
  }
194
199
  if (allOutputs.length === 0) {
195
200
  log('No attesters with BLS keys found (skipping mnemonics and encrypted keystores without password)');
196
201
  return;
197
202
  }
198
- const jsonOutput = prettyPrintJSON(allOutputs);
199
- // Write to file if output is specified, otherwise log to stdout
200
- if (output) {
201
- writeFileSync(output, jsonOutput, 'utf-8');
202
- log(`Wrote staking data to ${output}`);
203
- } else {
204
- log(jsonOutput);
205
- }
203
+ // Write a single JSON file with all staker outputs
204
+ const stakerOutputPath = join(outputDir, `${keystoreBaseName}_staker_output.json`);
205
+ writeFileSync(stakerOutputPath, prettyPrintJSON(allOutputs), 'utf-8');
206
+ log(`Wrote staker output for ${allOutputs.length} validator(s) to ${stakerOutputPath}`);
206
207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/cli",
3
- "version": "3.0.0-nightly.20251115",
3
+ "version": "3.0.0-nightly.20251119",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./contracts": "./dest/cmds/contracts/index.js",
@@ -71,21 +71,21 @@
71
71
  ]
72
72
  },
73
73
  "dependencies": {
74
- "@aztec/accounts": "3.0.0-nightly.20251115",
75
- "@aztec/archiver": "3.0.0-nightly.20251115",
76
- "@aztec/aztec.js": "3.0.0-nightly.20251115",
77
- "@aztec/constants": "3.0.0-nightly.20251115",
78
- "@aztec/entrypoints": "3.0.0-nightly.20251115",
79
- "@aztec/ethereum": "3.0.0-nightly.20251115",
80
- "@aztec/foundation": "3.0.0-nightly.20251115",
81
- "@aztec/l1-artifacts": "3.0.0-nightly.20251115",
82
- "@aztec/node-keystore": "3.0.0-nightly.20251115",
83
- "@aztec/node-lib": "3.0.0-nightly.20251115",
84
- "@aztec/p2p": "3.0.0-nightly.20251115",
85
- "@aztec/protocol-contracts": "3.0.0-nightly.20251115",
86
- "@aztec/stdlib": "3.0.0-nightly.20251115",
87
- "@aztec/test-wallet": "3.0.0-nightly.20251115",
88
- "@aztec/world-state": "3.0.0-nightly.20251115",
74
+ "@aztec/accounts": "3.0.0-nightly.20251119",
75
+ "@aztec/archiver": "3.0.0-nightly.20251119",
76
+ "@aztec/aztec.js": "3.0.0-nightly.20251119",
77
+ "@aztec/constants": "3.0.0-nightly.20251119",
78
+ "@aztec/entrypoints": "3.0.0-nightly.20251119",
79
+ "@aztec/ethereum": "3.0.0-nightly.20251119",
80
+ "@aztec/foundation": "3.0.0-nightly.20251119",
81
+ "@aztec/l1-artifacts": "3.0.0-nightly.20251119",
82
+ "@aztec/node-keystore": "3.0.0-nightly.20251119",
83
+ "@aztec/node-lib": "3.0.0-nightly.20251119",
84
+ "@aztec/p2p": "3.0.0-nightly.20251119",
85
+ "@aztec/protocol-contracts": "3.0.0-nightly.20251119",
86
+ "@aztec/stdlib": "3.0.0-nightly.20251119",
87
+ "@aztec/test-wallet": "3.0.0-nightly.20251119",
88
+ "@aztec/world-state": "3.0.0-nightly.20251119",
89
89
  "@ethersproject/wallet": "^5.8.0",
90
90
  "@iarna/toml": "^2.2.5",
91
91
  "@libp2p/peer-id-factory": "^3.0.4",
@@ -99,9 +99,9 @@
99
99
  "viem": "npm:@spalladino/viem@2.38.2-eip7594.0"
100
100
  },
101
101
  "devDependencies": {
102
- "@aztec/aztec-node": "3.0.0-nightly.20251115",
103
- "@aztec/kv-store": "3.0.0-nightly.20251115",
104
- "@aztec/telemetry-client": "3.0.0-nightly.20251115",
102
+ "@aztec/aztec-node": "3.0.0-nightly.20251119",
103
+ "@aztec/kv-store": "3.0.0-nightly.20251119",
104
+ "@aztec/telemetry-client": "3.0.0-nightly.20251119",
105
105
  "@jest/globals": "^30.0.0",
106
106
  "@types/jest": "^30.0.0",
107
107
  "@types/lodash.chunk": "^4.2.9",
@@ -117,20 +117,21 @@
117
117
  "typescript": "^5.3.3"
118
118
  },
119
119
  "peerDependencies": {
120
- "@aztec/accounts": "3.0.0-nightly.20251115",
121
- "@aztec/bb-prover": "3.0.0-nightly.20251115",
122
- "@aztec/ethereum": "3.0.0-nightly.20251115",
123
- "@aztec/l1-artifacts": "3.0.0-nightly.20251115",
124
- "@aztec/noir-contracts.js": "3.0.0-nightly.20251115",
125
- "@aztec/noir-protocol-circuits-types": "3.0.0-nightly.20251115",
126
- "@aztec/noir-test-contracts.js": "3.0.0-nightly.20251115",
127
- "@aztec/protocol-contracts": "3.0.0-nightly.20251115",
128
- "@aztec/stdlib": "3.0.0-nightly.20251115"
120
+ "@aztec/accounts": "3.0.0-nightly.20251119",
121
+ "@aztec/bb-prover": "3.0.0-nightly.20251119",
122
+ "@aztec/ethereum": "3.0.0-nightly.20251119",
123
+ "@aztec/l1-artifacts": "3.0.0-nightly.20251119",
124
+ "@aztec/noir-contracts.js": "3.0.0-nightly.20251119",
125
+ "@aztec/noir-protocol-circuits-types": "3.0.0-nightly.20251119",
126
+ "@aztec/noir-test-contracts.js": "3.0.0-nightly.20251119",
127
+ "@aztec/protocol-contracts": "3.0.0-nightly.20251119",
128
+ "@aztec/stdlib": "3.0.0-nightly.20251119"
129
129
  },
130
130
  "files": [
131
131
  "dest",
132
132
  "src",
133
- "!*.test.*"
133
+ "!*.test.*",
134
+ "public_include_metric_prefixes.json"
134
135
  ],
135
136
  "types": "./dest/index.d.ts",
136
137
  "engines": {
@@ -0,0 +1 @@
1
+ ["aztec.validator", "aztec.tx_collector", "aztec.mempool", "aztec.p2p.gossip.agg_", "aztec.ivc_verifier.agg_"]
@@ -12,6 +12,7 @@ import {
12
12
  buildValidatorEntries,
13
13
  logValidatorSummaries,
14
14
  maybePrintJson,
15
+ validateBlsPathOptions,
15
16
  writeBlsBn254ToFile,
16
17
  writeEthJsonV3ToFile,
17
18
  writeKeystoreFile,
@@ -20,6 +21,9 @@ import {
20
21
  export type AddValidatorKeysOptions = NewValidatorKeystoreOptions;
21
22
 
22
23
  export async function addValidatorKeys(existing: string, options: AddValidatorKeysOptions, log: LogFn) {
24
+ // validate bls-path inputs before proceeding with key generation
25
+ validateBlsPathOptions(options);
26
+
23
27
  const {
24
28
  dataDir,
25
29
  file,
@@ -36,7 +40,7 @@ export async function addValidatorKeys(existing: string, options: AddValidatorKe
36
40
  fundingAccount: fundingAccountOpt,
37
41
  remoteSigner: remoteSignerOpt,
38
42
  password,
39
- outDir,
43
+ encryptedKeystoreDir,
40
44
  } = options;
41
45
 
42
46
  const validatorCount = typeof count === 'number' && Number.isFinite(count) && count > 0 ? Math.floor(count) : 1;
@@ -84,10 +88,16 @@ export async function addValidatorKeys(existing: string, options: AddValidatorKe
84
88
 
85
89
  // If password provided, write ETH JSON V3 and BLS BN254 keystores and replace plaintext
86
90
  if (password !== undefined) {
87
- const targetDir =
88
- outDir && outDir.length > 0 ? outDir : dataDir && dataDir.length > 0 ? dataDir : dirname(existing);
91
+ let targetDir: string;
92
+ if (encryptedKeystoreDir && encryptedKeystoreDir.length > 0) {
93
+ targetDir = encryptedKeystoreDir;
94
+ } else if (dataDir && dataDir.length > 0) {
95
+ targetDir = dataDir;
96
+ } else {
97
+ targetDir = dirname(existing);
98
+ }
89
99
  await writeEthJsonV3ToFile(keystore.validators, { outDir: targetDir, password });
90
- await writeBlsBn254ToFile(keystore.validators, { outDir: targetDir, password });
100
+ await writeBlsBn254ToFile(keystore.validators, { outDir: targetDir, password, blsPath });
91
101
  }
92
102
 
93
103
  let outputPath = existing;
@@ -3,7 +3,7 @@ import type { LogFn } from '@aztec/foundation/log';
3
3
 
4
4
  import { writeFile } from 'fs/promises';
5
5
 
6
- import { computeBlsPublicKeyCompressed, withValidatorIndex } from './shared.js';
6
+ import { computeBlsPublicKeyCompressed, defaultBlsPath, withValidatorIndex } from './shared.js';
7
7
 
8
8
  export type GenerateBlsKeypairOptions = {
9
9
  mnemonic?: string;
@@ -17,7 +17,7 @@ export type GenerateBlsKeypairOptions = {
17
17
 
18
18
  export async function generateBlsKeypair(options: GenerateBlsKeypairOptions, log: LogFn) {
19
19
  const { mnemonic, ikm, blsPath, compressed = true, json, out } = options;
20
- const path = withValidatorIndex(blsPath ?? 'm/12381/3600/0/0/0', 0);
20
+ const path = withValidatorIndex(blsPath ?? defaultBlsPath, 0);
21
21
  const priv = deriveBlsPrivateKey(mnemonic, ikm, path);
22
22
  const pub = await computeBlsPublicKeyCompressed(priv);
23
23
  const result = { path, privateKey: priv, publicKey: pub, format: compressed ? 'compressed' : 'uncompressed' };
@@ -3,6 +3,7 @@ import type { LogFn } from '@aztec/foundation/log';
3
3
  import { Command } from 'commander';
4
4
 
5
5
  import { parseAztecAddress, parseEthereumAddress, parseHex, parseOptionalInteger } from '../../utils/commands.js';
6
+ import { defaultBlsPath } from './shared.js';
6
7
 
7
8
  export function injectCommands(program: Command, log: LogFn) {
8
9
  const group = program
@@ -22,20 +23,20 @@ export function injectCommands(program: Command, log: LogFn) {
22
23
  )
23
24
  .option('--mnemonic <mnemonic>', 'Mnemonic for ETH/BLS derivation')
24
25
  .option('--passphrase <str>', 'Optional passphrase for mnemonic')
25
- .option('--account-index <N>', 'Base account index for ETH derivation', parseOptionalInteger)
26
- .option('--address-index <N>', 'Base address index for ETH derivation', parseOptionalInteger)
26
+ .option('--account-index <N>', 'Base account index for ETH/BLS derivation', parseOptionalInteger)
27
+ .option('--address-index <N>', 'Base address index for ETH/BLS derivation', parseOptionalInteger)
27
28
  .option('--coinbase <address>', 'Coinbase ETH address to use when proposing', parseEthereumAddress)
28
29
  .option('--funding-account <address>', 'ETH account to fund publishers', parseEthereumAddress)
29
30
  .option('--remote-signer <url>', 'Default remote signer URL for accounts in this file')
30
31
  .option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', value => parseHex(value, 32))
31
- .option('--bls-path <path>', 'EIP-2334 path (default m/12381/3600/0/0/0)')
32
+ .option('--bls-path <path>', `EIP-2334 path (default ${defaultBlsPath})`)
32
33
  .option(
33
34
  '--password <str>',
34
35
  'Password for writing keystore files (ETH JSON V3 and BLS EIP-2335). Empty string allowed',
35
36
  )
36
- .option('--out-dir <dir>', 'Output directory for generated keystore file(s)')
37
+ .option('--encrypted-keystore-dir <dir>', 'Output directory for encrypted keystore file(s)')
37
38
  .option('--json', 'Echo resulting JSON to stdout')
38
- .option('--staker-output', 'Generate staker output JSON files for each attester')
39
+ .option('--staker-output', 'Generate a single staker output JSON file with an array of validator entries')
39
40
  .option('--gse-address <address>', 'GSE contract address (required with --staker-output)', parseEthereumAddress)
40
41
  .option('--l1-rpc-urls <urls>', 'L1 RPC URLs (comma-separated, required with --staker-output)', value =>
41
42
  value.split(','),
@@ -49,6 +50,7 @@ export function injectCommands(program: Command, log: LogFn) {
49
50
  .requiredOption('--fee-recipient <address>', 'Aztec address that will receive fees', parseAztecAddress)
50
51
  .action(async options => {
51
52
  const { newValidatorKeystore } = await import('./new.js');
53
+
52
54
  await newValidatorKeystore(options, log);
53
55
  });
54
56
 
@@ -57,27 +59,27 @@ export function injectCommands(program: Command, log: LogFn) {
57
59
  .summary('Augment an existing validator keystore JSON')
58
60
  .description('Adds attester/publisher/BLS entries to an existing keystore using the same flags as new')
59
61
  .argument('<existing>', 'Path to existing keystore JSON')
60
- .option('--data-dir <path>', 'Directory where keystore(s) live')
61
- .option('--file <name>', 'Override output file name')
62
- .option('--count <N>', 'Number of validators to add', parseOptionalInteger)
62
+ .option('--data-dir <path>', 'Directory where keystore(s) live. (default: ~/.aztec/keystore)')
63
+ .option('--file <name>', 'Override output file name. (default: key<N>.json)')
64
+ .option('--count <N>', 'Number of validators to add. (default: 1)', parseOptionalInteger)
63
65
  .option('--publisher-count <N>', 'Number of publisher accounts per validator (default 1)', value =>
64
66
  parseOptionalInteger(value, 0),
65
67
  )
66
68
  .option('--mnemonic <mnemonic>', 'Mnemonic for ETH/BLS derivation')
67
69
  .option('--passphrase <str>', 'Optional passphrase for mnemonic')
68
- .option('--account-index <N>', 'Base account index for ETH derivation', parseOptionalInteger)
69
- .option('--address-index <N>', 'Base address index for ETH derivation', parseOptionalInteger)
70
+ .option('--account-index <N>', 'Base account index for ETH/BLS derivation', parseOptionalInteger)
71
+ .option('--address-index <N>', 'Base address index for ETH/BLS derivation', parseOptionalInteger)
70
72
  .option('--coinbase <address>', 'Coinbase ETH address to use when proposing', parseEthereumAddress)
71
73
  .option('--funding-account <address>', 'ETH account to fund publishers', parseEthereumAddress)
72
74
  .option('--remote-signer <url>', 'Default remote signer URL for accounts in this file')
73
75
  .option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', value => parseHex(value, 32))
74
- .option('--bls-path <path>', 'EIP-2334 path (default m/12381/3600/0/0/0)')
76
+ .option('--bls-path <path>', `EIP-2334 path (default ${defaultBlsPath})`)
75
77
  .option('--empty', 'Generate an empty skeleton without keys')
76
78
  .option(
77
79
  '--password <str>',
78
80
  'Password for writing keystore files (ETH JSON V3 and BLS EIP-2335). Empty string allowed',
79
81
  )
80
- .option('--out-dir <dir>', 'Output directory for generated keystore file(s)')
82
+ .option('--encrypted-keystore-dir <dir>', 'Output directory for encrypted keystore file(s)')
81
83
  .option('--json', 'Echo resulting JSON to stdout')
82
84
  .requiredOption('--fee-recipient <address>', 'Aztec address that will receive fees', parseAztecAddress)
83
85
  .action(async (existing: string, options) => {
@@ -110,7 +112,7 @@ export function injectCommands(program: Command, log: LogFn) {
110
112
  .description('Generate a BLS keypair with convenience flags')
111
113
  .option('--mnemonic <mnemonic>', 'Mnemonic for BLS derivation')
112
114
  .option('--ikm <hex>', 'Initial keying material for BLS (alternative to mnemonic)', value => parseHex(value, 32))
113
- .option('--bls-path <path>', 'EIP-2334 path (default m/12381/3600/0/0/0)')
115
+ .option('--bls-path <path>', `EIP-2334 path (default ${defaultBlsPath})`)
114
116
  .option('--g2', 'Derive on G2 subgroup')
115
117
  .option('--compressed', 'Output compressed public key')
116
118
  .option('--json', 'Print JSON output to stdout')
@@ -15,6 +15,7 @@ import {
15
15
  logValidatorSummaries,
16
16
  maybePrintJson,
17
17
  resolveKeystoreOutputPath,
18
+ validateBlsPathOptions,
18
19
  writeBlsBn254ToFile,
19
20
  writeEthJsonV3ToFile,
20
21
  writeKeystoreFile,
@@ -34,7 +35,7 @@ export type NewValidatorKeystoreOptions = {
34
35
  ikm?: string;
35
36
  blsPath?: string;
36
37
  password?: string;
37
- outDir?: string;
38
+ encryptedKeystoreDir?: string;
38
39
  json?: boolean;
39
40
  feeRecipient: AztecAddress;
40
41
  coinbase?: EthAddress;
@@ -47,6 +48,9 @@ export type NewValidatorKeystoreOptions = {
47
48
  };
48
49
 
49
50
  export async function newValidatorKeystore(options: NewValidatorKeystoreOptions, log: LogFn) {
51
+ // validate bls-path inputs before proceeding with key generation
52
+ validateBlsPathOptions(options);
53
+
50
54
  const {
51
55
  dataDir,
52
56
  file,
@@ -63,7 +67,7 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
63
67
  ikm,
64
68
  mnemonic: _mnemonic,
65
69
  password,
66
- outDir,
70
+ encryptedKeystoreDir,
67
71
  stakerOutput,
68
72
  gseAddress,
69
73
  l1RpcUrls,
@@ -101,6 +105,7 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
101
105
 
102
106
  const validatorCount = typeof count === 'number' && Number.isFinite(count) && count > 0 ? Math.floor(count) : 1;
103
107
  const { outputPath } = await resolveKeystoreOutputPath(dataDir, file);
108
+ const keystoreOutDir = dirname(outputPath);
104
109
 
105
110
  const { validators, summaries } = await buildValidatorEntries({
106
111
  validatorCount,
@@ -118,9 +123,10 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
118
123
 
119
124
  // If password provided, write ETH JSON V3 and BLS BN254 keystores and replace plaintext
120
125
  if (password !== undefined) {
121
- const keystoreOutDir = outDir && outDir.length > 0 ? outDir : dirname(outputPath);
122
- await writeEthJsonV3ToFile(validators, { outDir: keystoreOutDir, password });
123
- await writeBlsBn254ToFile(validators, { outDir: keystoreOutDir, password });
126
+ const encryptedKeystoreOutDir =
127
+ encryptedKeystoreDir && encryptedKeystoreDir.length > 0 ? encryptedKeystoreDir : keystoreOutDir;
128
+ await writeEthJsonV3ToFile(validators, { outDir: encryptedKeystoreOutDir, password });
129
+ await writeBlsBn254ToFile(validators, { outDir: encryptedKeystoreOutDir, password });
124
130
  }
125
131
 
126
132
  const keystore = {
@@ -140,7 +146,6 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
140
146
  });
141
147
  const gse = new GSEContract(publicClient, gseAddress);
142
148
 
143
- const keystoreOutDir = outDir && outDir.length > 0 ? outDir : dirname(outputPath);
144
149
  // Extract keystore base name without extension for unique staker output filenames
145
150
  const keystoreBaseName = basename(outputPath, '.json');
146
151
 
@@ -149,17 +154,17 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
149
154
  const validator = validators[i];
150
155
  const outputs = await processAttesterAccounts(validator.attester, gse, password);
151
156
 
152
- // Save each attester's staker output
157
+ // Collect all staker outputs
153
158
  for (let j = 0; j < outputs.length; j++) {
154
- const attesterIndex = i + 1;
155
- const stakerOutputPath = join(
156
- keystoreOutDir,
157
- `${keystoreBaseName}_attester${attesterIndex}_staker_output.json`,
158
- );
159
- await writeFile(stakerOutputPath, prettyPrintJSON(outputs[j]), 'utf-8');
160
159
  allStakerOutputs.push(outputs[j]);
161
160
  }
162
161
  }
162
+
163
+ // Write a single JSON file with all staker outputs
164
+ if (allStakerOutputs.length > 0) {
165
+ const stakerOutputPath = join(keystoreOutDir, `${keystoreBaseName}_staker_output.json`);
166
+ await writeFile(stakerOutputPath, prettyPrintJSON(allStakerOutputs), 'utf-8');
167
+ }
163
168
  }
164
169
 
165
170
  const outputData = !_mnemonic ? { ...keystore, generatedMnemonic: mnemonic } : keystore;
@@ -178,8 +183,9 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
178
183
  } else {
179
184
  log(`Wrote validator keystore to ${outputPath}`);
180
185
  if (stakerOutput && allStakerOutputs.length > 0) {
181
- const keystoreOutDir = outDir && outDir.length > 0 ? outDir : dirname(outputPath);
182
- log(`Wrote ${allStakerOutputs.length} staker output file(s) to ${keystoreOutDir}`);
186
+ const keystoreBaseName = basename(outputPath, '.json');
187
+ const stakerOutputPath = join(keystoreOutDir, `${keystoreBaseName}_staker_output.json`);
188
+ log(`Wrote staker output for ${allStakerOutputs.length} validator(s) to ${stakerOutputPath}`);
183
189
  log('');
184
190
  }
185
191
  }
@@ -29,10 +29,46 @@ export type BuildValidatorsInput = {
29
29
  fundingAccount?: EthAddress;
30
30
  };
31
31
 
32
- export function withValidatorIndex(path: string, index: number) {
32
+ export const defaultBlsPath = 'm/12381/3600/0/0/0';
33
+
34
+ export function validateBlsPathOptions(options: {
35
+ count?: number;
36
+ publisherCount?: number;
37
+ accountIndex?: number;
38
+ addressIndex?: number;
39
+ blsPath?: string;
40
+ ikm?: string;
41
+ }) {
42
+ if (options.blsPath && options.blsPath !== defaultBlsPath) {
43
+ if (
44
+ (options.count && options.count !== 1) ||
45
+ (options.publisherCount && options.publisherCount > 0) ||
46
+ (options.accountIndex && options.accountIndex !== 0) ||
47
+ (options.addressIndex && options.addressIndex !== 0)
48
+ ) {
49
+ throw new Error('--bls-path cannot be used with --count, --publisher-count, --account-index, or --address-index');
50
+ }
51
+ }
52
+ }
53
+
54
+ export function withValidatorIndex(path: string, accountIndex: number = 0, addressIndex: number = 0) {
55
+ // NOTE: The legacy BLS CLI is to allow users who generated keys in 2.1.4 to be able to use the same command
56
+ // to re-generate their keys. In 2.1.5 we switched how we append addresses to the path so this is to maintain backwards compatibility.
57
+ const useLegacyBlsCli = ['true', '1', 'yes', 'y'].includes(process.env.LEGACY_BLS_CLI ?? '');
58
+
59
+ const defaultBlsPathParts = defaultBlsPath.split('/');
60
+
33
61
  const parts = path.split('/');
34
- if (parts.length >= 4 && parts[0] === 'm' && parts[1] === '12381' && parts[2] === '3600') {
35
- parts[3] = String(index);
62
+ if (parts.length == defaultBlsPathParts.length && parts.every((part, index) => part === defaultBlsPathParts[index])) {
63
+ if (useLegacyBlsCli) {
64
+ // In 2.1.4, we were using address-index in parts[3] and did NOT use account-index, check lines 32 & 84
65
+ // https://github.com/AztecProtocol/aztec-packages/blob/v2.1.4/yarn-project/cli/src/cmds/validator_keys/shared.ts
66
+
67
+ parts[3] = String(addressIndex);
68
+ } else {
69
+ parts[3] = String(accountIndex);
70
+ parts[5] = String(addressIndex);
71
+ }
36
72
  return parts.join('/');
37
73
  }
38
74
  return path;
@@ -74,14 +110,13 @@ export async function buildValidatorEntries(input: BuildValidatorsInput) {
74
110
  fundingAccount,
75
111
  } = input;
76
112
 
77
- const defaultBlsPath = 'm/12381/3600/0/0/0';
78
113
  const summaries: ValidatorSummary[] = [];
79
114
 
80
115
  const validators = await Promise.all(
81
116
  Array.from({ length: validatorCount }, async (_unused, i) => {
82
117
  const addressIndex = baseAddressIndex + i;
83
118
  const basePath = blsPath ?? defaultBlsPath;
84
- const perValidatorPath = withValidatorIndex(basePath, addressIndex);
119
+ const perValidatorPath = withValidatorIndex(basePath, accountIndex, addressIndex);
85
120
 
86
121
  const blsPrivKey = ikm || mnemonic ? deriveBlsPrivateKey(mnemonic, ikm, perValidatorPath) : undefined;
87
122
  const blsPubCompressed = blsPrivKey ? await computeBlsPublicKeyCompressed(blsPrivKey) : undefined;
@@ -221,7 +256,7 @@ export async function writeBn254BlsKeystore(
221
256
  /** Replace plaintext BLS keys in validators with { path, password } pointing to BN254 keystore files. */
222
257
  export async function writeBlsBn254ToFile(
223
258
  validators: ValidatorKeyStore[],
224
- options: { outDir: string; password: string },
259
+ options: { outDir: string; password: string; blsPath?: string },
225
260
  ): Promise<void> {
226
261
  for (let i = 0; i < validators.length; i++) {
227
262
  const v = validators[i];
@@ -237,7 +272,7 @@ export async function writeBlsBn254ToFile(
237
272
  }
238
273
 
239
274
  const pub = await computeBlsPublicKeyCompressed(blsKey);
240
- const path = 'm/12381/3600/0/0/0';
275
+ const path = options.blsPath ?? defaultBlsPath;
241
276
  const fileBase = `${String(i + 1)}_${pub.slice(2, 18)}`;
242
277
  const keystorePath = await writeBn254BlsKeystore(options.outDir, fileBase, options.password, blsKey, pub, path);
243
278
 
@@ -17,6 +17,7 @@ import type {
17
17
 
18
18
  import { Wallet } from '@ethersproject/wallet';
19
19
  import { readFileSync, writeFileSync } from 'fs';
20
+ import { basename, dirname, join } from 'path';
20
21
  import { createPublicClient, fallback, http } from 'viem';
21
22
  import { privateKeyToAddress } from 'viem/accounts';
22
23
 
@@ -275,10 +276,16 @@ export async function generateStakerJson(options: StakerOptions, log: LogFn): Pr
275
276
  });
276
277
  const gse = new GSEContract(publicClient, gseAddress);
277
278
 
278
- // Process each validator
279
- for (const validator of keystore.validators) {
279
+ const keystoreBaseName = basename(from, '.json');
280
+ const outputDir = output ? output : dirname(from);
281
+
282
+ for (let i = 0; i < keystore.validators.length; i++) {
283
+ const validator = keystore.validators[i];
280
284
  const outputs = await processAttesterAccounts(validator.attester, gse, password);
281
- allOutputs.push(...outputs);
285
+
286
+ for (let j = 0; j < outputs.length; j++) {
287
+ allOutputs.push(outputs[j]);
288
+ }
282
289
  }
283
290
 
284
291
  if (allOutputs.length === 0) {
@@ -286,13 +293,8 @@ export async function generateStakerJson(options: StakerOptions, log: LogFn): Pr
286
293
  return;
287
294
  }
288
295
 
289
- const jsonOutput = prettyPrintJSON(allOutputs);
290
-
291
- // Write to file if output is specified, otherwise log to stdout
292
- if (output) {
293
- writeFileSync(output, jsonOutput, 'utf-8');
294
- log(`Wrote staking data to ${output}`);
295
- } else {
296
- log(jsonOutput);
297
- }
296
+ // Write a single JSON file with all staker outputs
297
+ const stakerOutputPath = join(outputDir, `${keystoreBaseName}_staker_output.json`);
298
+ writeFileSync(stakerOutputPath, prettyPrintJSON(allOutputs), 'utf-8');
299
+ log(`Wrote staker output for ${allOutputs.length} validator(s) to ${stakerOutputPath}`);
298
300
  }