@aztec-labs/cli 6.0.0-rc.3 → 6.0.0

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.
Files changed (89) hide show
  1. package/dest/cmds/infrastructure/index.d.ts +1 -1
  2. package/dest/cmds/infrastructure/index.d.ts.map +1 -1
  3. package/dest/cmds/infrastructure/index.js +3 -2
  4. package/dest/cmds/infrastructure/sequencers.d.ts +4 -1
  5. package/dest/cmds/infrastructure/sequencers.d.ts.map +1 -1
  6. package/dest/cmds/infrastructure/sequencers.js +10 -3
  7. package/dest/cmds/l1/deploy_l1_contracts_cmd.d.ts +1 -1
  8. package/dest/cmds/l1/deploy_l1_contracts_cmd.d.ts.map +1 -1
  9. package/dest/cmds/l1/deploy_l1_contracts_cmd.js +1 -0
  10. package/dest/cmds/l1/index.d.ts +1 -1
  11. package/dest/cmds/l1/index.d.ts.map +1 -1
  12. package/dest/cmds/l1/index.js +2 -1
  13. package/dest/cmds/l1/l1_rpc_test_utils.d.ts +10 -2
  14. package/dest/cmds/l1/l1_rpc_test_utils.d.ts.map +1 -1
  15. package/dest/cmds/l1/l1_rpc_test_utils.js +35 -1
  16. package/dest/cmds/l1/prover_stats.d.ts +1 -1
  17. package/dest/cmds/l1/prover_stats.d.ts.map +1 -1
  18. package/dest/cmds/l1/prover_stats.js +3 -4
  19. package/dest/cmds/l1/update_l1_validators.d.ts +3 -2
  20. package/dest/cmds/l1/update_l1_validators.d.ts.map +1 -1
  21. package/dest/cmds/l1/update_l1_validators.js +6 -4
  22. package/dest/cmds/misc/generate_p2p_private_key.d.ts +1 -1
  23. package/dest/cmds/misc/generate_p2p_private_key.d.ts.map +1 -1
  24. package/dest/cmds/misc/generate_p2p_private_key.js +1 -0
  25. package/dest/cmds/validator_keys/add.d.ts +1 -1
  26. package/dest/cmds/validator_keys/add.d.ts.map +1 -1
  27. package/dest/cmds/validator_keys/add.js +14 -4
  28. package/dest/cmds/validator_keys/bls_key_derivation.d.ts +89 -0
  29. package/dest/cmds/validator_keys/bls_key_derivation.d.ts.map +1 -0
  30. package/dest/cmds/validator_keys/bls_key_derivation.js +122 -0
  31. package/dest/cmds/validator_keys/generate_bls_keypair.d.ts +3 -2
  32. package/dest/cmds/validator_keys/generate_bls_keypair.d.ts.map +1 -1
  33. package/dest/cmds/validator_keys/generate_bls_keypair.js +16 -3
  34. package/dest/cmds/validator_keys/index.d.ts +1 -1
  35. package/dest/cmds/validator_keys/index.d.ts.map +1 -1
  36. package/dest/cmds/validator_keys/index.js +14 -3
  37. package/dest/cmds/validator_keys/new.d.ts +3 -2
  38. package/dest/cmds/validator_keys/new.d.ts.map +1 -1
  39. package/dest/cmds/validator_keys/new.js +47 -33
  40. package/dest/cmds/validator_keys/shared.d.ts +28 -3
  41. package/dest/cmds/validator_keys/shared.d.ts.map +1 -1
  42. package/dest/cmds/validator_keys/shared.js +48 -7
  43. package/dest/cmds/validator_keys/staker.d.ts +6 -1
  44. package/dest/cmds/validator_keys/staker.d.ts.map +1 -1
  45. package/dest/cmds/validator_keys/staker.js +32 -0
  46. package/dest/config/chain_l2_config.d.ts +1 -1
  47. package/dest/config/chain_l2_config.d.ts.map +1 -1
  48. package/dest/config/chain_l2_config.js +1 -1
  49. package/dest/config/enrich_env.d.ts +1 -1
  50. package/dest/config/enrich_env.d.ts.map +1 -1
  51. package/dest/config/enrich_env.js +1 -1
  52. package/dest/config/get_l1_config.d.ts +1 -1
  53. package/dest/config/get_l1_config.d.ts.map +1 -1
  54. package/dest/config/get_l1_config.js +2 -1
  55. package/dest/config/network_config.d.ts +1 -1
  56. package/dest/config/network_config.d.ts.map +1 -1
  57. package/dest/config/network_config.js +4 -2
  58. package/dest/utils/aztec.d.ts +1 -1
  59. package/dest/utils/aztec.d.ts.map +1 -1
  60. package/dest/utils/aztec.js +1 -0
  61. package/dest/utils/commands.d.ts +1 -1
  62. package/dest/utils/commands.d.ts.map +1 -1
  63. package/dest/utils/commands.js +3 -1
  64. package/dest/utils/encoding.d.ts +1 -1
  65. package/dest/utils/encoding.d.ts.map +1 -1
  66. package/dest/utils/encoding.js +1 -0
  67. package/package.json +25 -25
  68. package/src/cmds/infrastructure/index.ts +13 -1
  69. package/src/cmds/infrastructure/sequencers.ts +14 -4
  70. package/src/cmds/l1/deploy_l1_contracts_cmd.ts +1 -0
  71. package/src/cmds/l1/index.ts +6 -0
  72. package/src/cmds/l1/l1_rpc_test_utils.ts +25 -2
  73. package/src/cmds/l1/prover_stats.ts +3 -2
  74. package/src/cmds/l1/update_l1_validators.ts +10 -4
  75. package/src/cmds/misc/generate_p2p_private_key.ts +1 -0
  76. package/src/cmds/validator_keys/add.ts +15 -2
  77. package/src/cmds/validator_keys/bls_key_derivation.ts +209 -0
  78. package/src/cmds/validator_keys/generate_bls_keypair.ts +26 -5
  79. package/src/cmds/validator_keys/index.ts +32 -0
  80. package/src/cmds/validator_keys/new.ts +50 -32
  81. package/src/cmds/validator_keys/shared.ts +70 -8
  82. package/src/cmds/validator_keys/staker.ts +56 -0
  83. package/src/config/chain_l2_config.ts +1 -0
  84. package/src/config/enrich_env.ts +1 -0
  85. package/src/config/get_l1_config.ts +2 -1
  86. package/src/config/network_config.ts +4 -2
  87. package/src/utils/aztec.ts +1 -0
  88. package/src/utils/commands.ts +3 -1
  89. package/src/utils/encoding.ts +1 -0
@@ -10,9 +10,12 @@ import { basename, dirname, join } from 'path';
10
10
  import { createPublicClient, fallback, http } from 'viem';
11
11
  import { generateMnemonic, mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
12
12
 
13
+ import { type BlsKeyDerivationOptions, resolveBlsKeyDerivationPolicy } from './bls_key_derivation.js';
13
14
  import {
14
15
  buildValidatorEntries,
15
16
  encryptFundingAccountToFile,
17
+ getBlsDerivationPaths,
18
+ getBlsKeyCandidates,
16
19
  logValidatorSummaries,
17
20
  maybePrintJson,
18
21
  resolveFundingAccount,
@@ -21,7 +24,7 @@ import {
21
24
  writeEthJsonV3ToFile,
22
25
  writeKeystoreFile,
23
26
  } from './shared.js';
24
- import { processAttesterAccounts } from './staker.js';
27
+ import { type StakerOutput, assertWithinProofOfPossessionGasLimit, processAttesterAccounts } from './staker.js';
25
28
  import {
26
29
  validateBlsPathOptions,
27
30
  validateFundingAccountOptions,
@@ -59,7 +62,7 @@ export type NewValidatorKeystoreOptions = {
59
62
  gseAddress?: EthAddress;
60
63
  l1RpcUrls?: string[];
61
64
  l1ChainId?: number;
62
- };
65
+ } & BlsKeyDerivationOptions;
63
66
 
64
67
  type PasswordSourceOptions = Pick<
65
68
  NewValidatorKeystoreOptions,
@@ -152,6 +155,7 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
152
155
  validateRemoteSignerOptions(options);
153
156
  // validate funding account option
154
157
  validateFundingAccountOptions(options);
158
+ const blsKeyDerivation = resolveBlsKeyDerivationPolicy(options);
155
159
 
156
160
  const {
157
161
  dataDir,
@@ -192,8 +196,9 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
192
196
  });
193
197
  const shouldEncryptKeystores = ethPassword !== undefined && blsPassword !== undefined;
194
198
  const mnemonic = _mnemonic ?? generateMnemonic(wordlist);
199
+ const showGeneratedMnemonic = !_mnemonic && !json && !shouldEncryptKeystores;
195
200
 
196
- if (!_mnemonic && !json && !shouldEncryptKeystores) {
201
+ if (showGeneratedMnemonic) {
197
202
  log('No mnemonic provided, generating new one...');
198
203
  log(`Using new mnemonic:`);
199
204
  log('');
@@ -217,8 +222,35 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
217
222
  feeRecipient,
218
223
  coinbase,
219
224
  remoteSigner,
225
+ blsKeyDerivation,
226
+ generatedMnemonic: _mnemonic ? undefined : showGeneratedMnemonic ? 'shown' : 'hidden',
220
227
  });
221
228
 
229
+ // Generate staker outputs if requested, from the plaintext keys and before writing anything, so that keys the GSE
230
+ // would reject leave no files behind.
231
+ const allStakerOutputs: StakerOutput[] = [];
232
+ if (stakerOutput && gseAddress && l1RpcUrls && l1ChainId !== undefined) {
233
+ const chain = createEthereumChain(l1RpcUrls, l1ChainId);
234
+ const publicClient = createPublicClient({
235
+ chain: chain.chainInfo,
236
+ transport: fallback(l1RpcUrls.map(url => http(url, { batch: false }))),
237
+ });
238
+ const gse = new GSEContract(publicClient, gseAddress);
239
+
240
+ // Process each validator
241
+ for (let i = 0; i < validators.length; i++) {
242
+ const validator = validators[i];
243
+ const outputs = await processAttesterAccounts(validator.attester, gse);
244
+
245
+ // Collect all staker outputs
246
+ for (let j = 0; j < outputs.length; j++) {
247
+ allStakerOutputs.push(outputs[j]);
248
+ }
249
+ }
250
+
251
+ await assertWithinProofOfPossessionGasLimit(gse, allStakerOutputs);
252
+ }
253
+
222
254
  let resolvedFundingAccount = fundingAccount ? resolveFundingAccount(fundingAccount, remoteSigner) : undefined;
223
255
 
224
256
  // If password provided, write ETH JSON V3 and BLS BN254 keystores and replace plaintext
@@ -226,7 +258,11 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
226
258
  const encryptedKeystoreOutDir =
227
259
  encryptedKeystoreDir && encryptedKeystoreDir.length > 0 ? encryptedKeystoreDir : keystoreOutDir;
228
260
  await writeEthJsonV3ToFile(validators, { outDir: encryptedKeystoreOutDir, password: ethPassword });
229
- await writeBlsBn254ToFile(validators, { outDir: encryptedKeystoreOutDir, password: blsPassword });
261
+ await writeBlsBn254ToFile(validators, {
262
+ outDir: encryptedKeystoreOutDir,
263
+ password: blsPassword,
264
+ derivationPaths: getBlsDerivationPaths(summaries),
265
+ });
230
266
  if (resolvedFundingAccount) {
231
267
  resolvedFundingAccount = await encryptFundingAccountToFile(resolvedFundingAccount, {
232
268
  outDir: encryptedKeystoreOutDir,
@@ -243,38 +279,20 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
243
279
 
244
280
  await writeKeystoreFile(outputPath, keystore);
245
281
 
246
- // Generate staker outputs if requested
247
- const allStakerOutputs: any[] = [];
248
- if (stakerOutput && gseAddress && l1RpcUrls && l1ChainId !== undefined) {
249
- const chain = createEthereumChain(l1RpcUrls, l1ChainId);
250
- const publicClient = createPublicClient({
251
- chain: chain.chainInfo,
252
- transport: fallback(l1RpcUrls.map(url => http(url, { batch: false }))),
253
- });
254
- const gse = new GSEContract(publicClient, gseAddress);
255
-
282
+ // Write a single JSON file with all staker outputs
283
+ if (allStakerOutputs.length > 0) {
256
284
  // Extract keystore base name without extension for unique staker output filenames
257
285
  const keystoreBaseName = basename(outputPath, '.json');
258
-
259
- // Process each validator
260
- for (let i = 0; i < validators.length; i++) {
261
- const validator = validators[i];
262
- const outputs = await processAttesterAccounts(validator.attester, gse);
263
-
264
- // Collect all staker outputs
265
- for (let j = 0; j < outputs.length; j++) {
266
- allStakerOutputs.push(outputs[j]);
267
- }
268
- }
269
-
270
- // Write a single JSON file with all staker outputs
271
- if (allStakerOutputs.length > 0) {
272
- const stakerOutputPath = join(keystoreOutDir, `${keystoreBaseName}_staker_output.json`);
273
- await writeFile(stakerOutputPath, prettyPrintJSON(allStakerOutputs), 'utf-8');
274
- }
286
+ const stakerOutputPath = join(keystoreOutDir, `${keystoreBaseName}_staker_output.json`);
287
+ await writeFile(stakerOutputPath, prettyPrintJSON(allStakerOutputs), 'utf-8');
275
288
  }
276
289
 
277
- const outputData = !_mnemonic && !shouldEncryptKeystores ? { ...keystore, generatedMnemonic: mnemonic } : keystore;
290
+ const blsKeyCandidates = getBlsKeyCandidates(summaries);
291
+ const outputData = {
292
+ ...keystore,
293
+ ...(!_mnemonic && !shouldEncryptKeystores ? { generatedMnemonic: mnemonic } : {}),
294
+ ...(blsKeyCandidates.length > 0 ? { blsKeyCandidates } : {}),
295
+ };
278
296
 
279
297
  // Handle JSON output
280
298
  if (json) {
@@ -1,6 +1,6 @@
1
1
  import { prettyPrintJSON } from '@aztec-labs/cli/utils';
2
2
  import { asyncPool } from '@aztec-labs/foundation/async-pool';
3
- import { deriveBlsPrivateKey } from '@aztec-labs/foundation/crypto/bls';
3
+ import { getEnv } from '@aztec-labs/foundation/config/env';
4
4
  import { createBn254Keystore } from '@aztec-labs/foundation/crypto/bls/bn254_keystore';
5
5
  import { computeBn254G1PublicKeyCompressed } from '@aztec-labs/foundation/crypto/bn254';
6
6
  import { EthAddress } from '@aztec-labs/foundation/eth-address';
@@ -8,6 +8,7 @@ import type { LogFn } from '@aztec-labs/foundation/log';
8
8
  import type { EthAccount, EthPrivateKey, ValidatorKeyStore } from '@aztec-labs/node-keystore/types';
9
9
  import type { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
10
10
  import { Wallet } from '@ethersproject/wallet';
11
+ import { Buffer } from 'buffer';
11
12
  import { constants as fsConstants, mkdirSync } from 'fs';
12
13
  import { access, writeFile } from 'fs/promises';
13
14
  import { availableParallelism, homedir } from 'os';
@@ -15,6 +16,12 @@ import { dirname, isAbsolute, join } from 'path';
15
16
  import { mnemonicToAccount } from 'viem/accounts';
16
17
  import { Worker } from 'worker_threads';
17
18
 
19
+ import {
20
+ type BlsKeyDerivationPolicy,
21
+ type GeneratedMnemonic,
22
+ resolveBlsKeyDerivationPolicy,
23
+ selectBlsKey,
24
+ } from './bls_key_derivation.js';
18
25
  import { defaultBlsPath } from './utils.js';
19
26
 
20
27
  type EthJsonV3WorkerResult = { json: string } | { error: { message: string; name?: string; stack?: string } };
@@ -59,13 +66,23 @@ function encryptEthJsonV3InWorker(privateKeyHex: string, password: string): Prom
59
66
  }
60
67
 
61
68
  async function encryptEthJsonV3(privateKeyHex: string, password: string): Promise<string> {
62
- if (process.env.JEST_WORKER_ID !== undefined) {
69
+ if (getEnv().JEST_WORKER_ID !== undefined) {
63
70
  return await new Wallet(privateKeyHex).encrypt(password);
64
71
  }
65
72
  return await encryptEthJsonV3InWorker(privateKeyHex, password);
66
73
  }
67
74
 
68
- export type ValidatorSummary = { attesterEth?: string; attesterBls?: string; publisherEth?: string[] };
75
+ export type ValidatorSummary = {
76
+ attesterEth?: string;
77
+ attesterBls?: string;
78
+ /** Derivation path of the BLS key, including the candidate component when the candidate is not 0. */
79
+ blsPath?: string;
80
+ /** Candidate selected by the BLS key derivation policy; 0 unless a retry picked another one. */
81
+ blsCandidate?: number;
82
+ /** The `--bls-key-derivation-max-gas` budget, when it took part in selecting a candidate other than 0. */
83
+ blsMaxGas?: number;
84
+ publisherEth?: string[];
85
+ };
69
86
 
70
87
  export type BuildValidatorsInput = {
71
88
  validatorCount: number;
@@ -79,12 +96,16 @@ export type BuildValidatorsInput = {
79
96
  feeRecipient: AztecAddress;
80
97
  coinbase?: EthAddress;
81
98
  remoteSigner?: string;
99
+ /** Defaults to the policy used when no BLS key derivation flag is given. */
100
+ blsKeyDerivation?: BlsKeyDerivationPolicy;
101
+ /** Set when the CLI generated `mnemonic` in this run. */
102
+ generatedMnemonic?: GeneratedMnemonic;
82
103
  };
83
104
 
84
105
  export function withValidatorIndex(path: string, accountIndex: number = 0, addressIndex: number = 0) {
85
106
  // NOTE: The legacy BLS CLI is to allow users who generated keys in 2.1.4 to be able to use the same command
86
107
  // 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.
87
- const useLegacyBlsCli = ['true', '1', 'yes', 'y'].includes(process.env.LEGACY_BLS_CLI ?? '');
108
+ const useLegacyBlsCli = ['true', '1', 'yes', 'y'].includes(getEnv().LEGACY_BLS_CLI ?? '');
88
109
 
89
110
  const defaultBlsPathParts = defaultBlsPath.split('/');
90
111
 
@@ -153,6 +174,8 @@ export async function buildValidatorEntries(input: BuildValidatorsInput) {
153
174
  feeRecipient,
154
175
  coinbase,
155
176
  remoteSigner,
177
+ blsKeyDerivation = resolveBlsKeyDerivationPolicy({}),
178
+ generatedMnemonic,
156
179
  } = input;
157
180
 
158
181
  const summaries: ValidatorSummary[] = [];
@@ -163,7 +186,11 @@ export async function buildValidatorEntries(input: BuildValidatorsInput) {
163
186
  const basePath = blsPath ?? defaultBlsPath;
164
187
  const perValidatorPath = withValidatorIndex(basePath, accountIndex, addressIndex);
165
188
 
166
- const blsPrivKey = ikm || mnemonic ? deriveBlsPrivateKey(mnemonic, ikm, perValidatorPath) : undefined;
189
+ const blsKey =
190
+ ikm || mnemonic
191
+ ? selectBlsKey(blsKeyDerivation, { mnemonic, ikm, perValidatorPath, generatedMnemonic })
192
+ : undefined;
193
+ const blsPrivKey = blsKey?.privateKey;
167
194
  const blsPubCompressed = blsPrivKey ? await computeBlsPublicKeyCompressed(blsPrivKey) : undefined;
168
195
 
169
196
  const ethAttester = deriveEthAttester(mnemonic, accountIndex, addressIndex, remoteSigner);
@@ -198,6 +225,9 @@ export async function buildValidatorEntries(input: BuildValidatorsInput) {
198
225
  summaries.push({
199
226
  attesterEth: attesterEthAddress,
200
227
  attesterBls: blsPubCompressed,
228
+ blsPath: blsKey?.path,
229
+ blsCandidate: blsKey?.candidate,
230
+ blsMaxGas: blsKey?.maxGas,
201
231
  publisherEth: publisherAddresses.length > 0 ? publisherAddresses : undefined,
202
232
  });
203
233
 
@@ -252,6 +282,10 @@ export function logValidatorSummaries(log: LogFn, summaries: ValidatorSummary[])
252
282
  if (v.attesterBls) {
253
283
  lines.push(` bls: ${v.attesterBls}`);
254
284
  }
285
+ if (v.blsCandidate) {
286
+ const maxGas = v.blsMaxGas !== undefined ? `, max gas ${v.blsMaxGas}` : '';
287
+ lines.push(` bls derivation path: ${v.blsPath} (candidate ${v.blsCandidate}${maxGas})`);
288
+ }
255
289
  if (v.publisherEth && v.publisherEth.length > 0) {
256
290
  lines.push(` publisher:`);
257
291
  for (const addr of v.publisherEth) {
@@ -259,11 +293,35 @@ export function logValidatorSummaries(log: LogFn, summaries: ValidatorSummary[])
259
293
  }
260
294
  }
261
295
  }
296
+ if (summaries.some(v => v.blsCandidate)) {
297
+ lines.push(
298
+ 'Some BLS keys were derived from a retry candidate (see "bls derivation path" above).',
299
+ 'To regenerate them from the mnemonic, re-run with --bls-key-derivation-v6 and the same --bls-key-derivation-max-gas, if any.',
300
+ );
301
+ }
262
302
  if (lines.length > 0) {
263
303
  log(lines.join('\n'));
264
304
  }
265
305
  }
266
306
 
307
+ /** BLS keys in `summaries` that came from a retry candidate, for reporting alongside JSON output. */
308
+ export function getBlsKeyCandidates(summaries: ValidatorSummary[]) {
309
+ return summaries
310
+ .filter(v => v.blsCandidate)
311
+ .map(v => ({
312
+ attesterEth: v.attesterEth,
313
+ bls: v.attesterBls,
314
+ path: v.blsPath,
315
+ candidate: v.blsCandidate,
316
+ ...(v.blsMaxGas !== undefined ? { maxGas: v.blsMaxGas } : {}),
317
+ }));
318
+ }
319
+
320
+ /** Derivation path of each BLS key in `summaries`, keyed by its compressed public key. */
321
+ export function getBlsDerivationPaths(summaries: ValidatorSummary[]): Map<string, string> {
322
+ return new Map(summaries.flatMap(v => (v.attesterBls && v.blsPath ? [[v.attesterBls, v.blsPath] as const] : [])));
323
+ }
324
+
267
325
  export function maybePrintJson(log: LogFn, jsonFlag: boolean | undefined, obj: unknown) {
268
326
  if (jsonFlag) {
269
327
  log(prettyPrintJSON(obj as Record<string, any>));
@@ -300,10 +358,14 @@ export async function writeBn254BlsKeystore(
300
358
  return outPath;
301
359
  }
302
360
 
303
- /** Replace plaintext BLS keys in validators with { path, password } pointing to BN254 keystore files. */
361
+ /**
362
+ * Replace plaintext BLS keys in validators with { path, password } pointing to BN254 keystore files. Each file records
363
+ * the key's derivation path from `derivationPaths` (keyed by compressed public key), or an empty path for keys missing
364
+ * from it, such as keys that were already in a keystore file, whose origin is unknown.
365
+ */
304
366
  export async function writeBlsBn254ToFile(
305
367
  validators: ValidatorKeyStore[],
306
- options: { outDir: string; password: string; blsPath?: string },
368
+ options: { outDir: string; password: string; derivationPaths?: Map<string, string> },
307
369
  ): Promise<void> {
308
370
  await Promise.all(
309
371
  validators.map(async (v, i) => {
@@ -319,7 +381,7 @@ export async function writeBlsBn254ToFile(
319
381
  }
320
382
 
321
383
  const pub = await computeBlsPublicKeyCompressed(blsKey);
322
- const path = options.blsPath ?? defaultBlsPath;
384
+ const path = options.derivationPaths?.get(pub) ?? '';
323
385
  const fileBase = `${String(i + 1)}_${pub.slice(2, 18)}`;
324
386
  const keystorePath = await writeBn254BlsKeystore(options.outDir, fileBase, options.password, blsKey, pub, path);
325
387
 
@@ -1,4 +1,11 @@
1
+ import { GSEAbi } from '@aztec-foundation/l1-artifacts/GSEAbi';
2
+
1
3
  import { prettyPrintJSON } from '@aztec-labs/cli/utils';
4
+ import {
5
+ AMSTERDAM_PROOF_OF_POSSESSION_GAS_MODEL,
6
+ computeBn254RegistrationDigest,
7
+ estimateProofOfPossessionGas,
8
+ } from '@aztec-labs/ethereum/bn254';
2
9
  import { createEthereumChain } from '@aztec-labs/ethereum/chain';
3
10
  import { GSEContract } from '@aztec-labs/ethereum/contracts';
4
11
  import { decryptBn254Keystore } from '@aztec-labs/foundation/crypto/bls/bn254_keystore';
@@ -21,6 +28,8 @@ import { basename, dirname, join } from 'path';
21
28
  import { createPublicClient, fallback, http } from 'viem';
22
29
  import { privateKeyToAddress } from 'viem/accounts';
23
30
 
31
+ import { LATEST_BLS_KEY_DERIVATION } from './bls_key_derivation.js';
32
+
24
33
  export type StakerOptions = {
25
34
  from: string;
26
35
  password?: string;
@@ -247,6 +256,51 @@ export async function processAttesterAccounts(
247
256
  return result ? [result] : [];
248
257
  }
249
258
 
259
+ /**
260
+ * Throws when registering any of `outputs` could fail because the proof-of-possession check of its BLS key is estimated
261
+ * to need more gas than the GSE gives it. Governance sets that limit and can change it, so it is read from L1.
262
+ */
263
+ export async function assertWithinProofOfPossessionGasLimit(gse: GSEContract, outputs: StakerOutput[]): Promise<void> {
264
+ const gasLimit = Number(
265
+ await gse.client.readContract({
266
+ address: gse.address.toString(),
267
+ abi: GSEAbi,
268
+ functionName: 'proofOfPossessionGasLimit',
269
+ }),
270
+ );
271
+
272
+ const overLimit = outputs
273
+ .map(output => {
274
+ const { attempts, sqrtCalls } = computeBn254RegistrationDigest({
275
+ x: BigInt(output.publicKeyG1.x),
276
+ y: BigInt(output.publicKeyG1.y),
277
+ });
278
+ return {
279
+ attester: output.attester,
280
+ estimatedGas: estimateProofOfPossessionGas(AMSTERDAM_PROOF_OF_POSSESSION_GAS_MODEL, attempts, sqrtCalls),
281
+ };
282
+ })
283
+ .filter(({ estimatedGas }) => estimatedGas > gasLimit);
284
+ if (overLimit.length === 0) {
285
+ return;
286
+ }
287
+
288
+ // Leave the same 10% margin below the limit that the derivation version budgets leave below the cap they target.
289
+ const { version, budget } = LATEST_BLS_KEY_DERIVATION;
290
+ const maxGas = Math.floor(gasLimit * 0.9);
291
+ const flags =
292
+ maxGas < budget
293
+ ? `--bls-key-derivation-v${version} --bls-key-derivation-max-gas ${maxGas}`
294
+ : `--bls-key-derivation-v${version}`;
295
+ throw new Error(
296
+ `Registering these validators could fail: the GSE at ${gse.address} gives each proof-of-possession check ` +
297
+ `${gasLimit} gas, and the BLS keys of these attesters are estimated to need more: ` +
298
+ `${overLimit.map(({ attester, estimatedGas }) => `${attester} (estimated at ${estimatedGas} gas)`).join(', ')}. ` +
299
+ `To replace their BLS keys and keep their ETH keys, regenerate them with validator-keys new, the same mnemonic ` +
300
+ `or IKM and indices, and ${flags}.`,
301
+ );
302
+ }
303
+
250
304
  /**
251
305
  * Main staker command function
252
306
  */
@@ -293,6 +347,8 @@ export async function generateStakerJson(options: StakerOptions, log: LogFn): Pr
293
347
  return;
294
348
  }
295
349
 
350
+ await assertWithinProofOfPossessionGasLimit(gse, allOutputs);
351
+
296
352
  // Write a single JSON file with all staker outputs
297
353
  const stakerOutputPath = join(outputDir, `${keystoreBaseName}_staker_output.json`);
298
354
  writeFileSync(stakerOutputPath, prettyPrintJSON(allOutputs), 'utf-8');
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-restricted-properties -- this module sets variables of the Node.js process */
1
2
  import type { NetworkNames } from '@aztec-labs/foundation/config';
2
3
  import { createLogger } from '@aztec-labs/foundation/log';
3
4
  import { type ConsensusEnvVar, checkConsensusEnvOverrides } from '@aztec-labs/stdlib/config';
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-restricted-properties -- this module sets variables of the Node.js process */
1
2
  import { EthAddress } from '@aztec-labs/aztec.js/addresses';
2
3
  import type { EnvVar } from '@aztec-labs/foundation/config';
3
4
 
@@ -2,6 +2,7 @@ import { getPublicClient } from '@aztec-labs/ethereum/client';
2
2
  import { RegistryContract } from '@aztec-labs/ethereum/contracts';
3
3
  import type { L1ContractAddresses } from '@aztec-labs/ethereum/l1-contract-addresses';
4
4
  import { getL1ContractsConfig } from '@aztec-labs/ethereum/queries';
5
+ import { getEnv } from '@aztec-labs/foundation/config/env';
5
6
  import { EthAddress } from '@aztec-labs/foundation/eth-address';
6
7
 
7
8
  export async function getL1Config(
@@ -22,7 +23,7 @@ export async function getL1Config(
22
23
  }
23
24
 
24
25
  export async function getL1RollupAddressFromEnv(l1RpcUrls: string[], l1ChainId: number) {
25
- const registryAddress = process.env.REGISTRY_CONTRACT_ADDRESS;
26
+ const registryAddress = getEnv().REGISTRY_CONTRACT_ADDRESS;
26
27
  if (!registryAddress || !EthAddress.isAddress(registryAddress)) {
27
28
  throw new Error(`Failed to extract registry address`);
28
29
  }
@@ -1,4 +1,5 @@
1
1
  import { type NetworkConfig, NetworkConfigMapSchema, type NetworkNames } from '@aztec-labs/foundation/config';
2
+ import { getEnv } from '@aztec-labs/foundation/config/env';
2
3
  import { readFile } from 'fs/promises';
3
4
  import { join } from 'path';
4
5
 
@@ -23,7 +24,7 @@ export async function getNetworkConfig(
23
24
  cacheDir?: string,
24
25
  ): Promise<NetworkConfig | undefined> {
25
26
  // Try with the primary URL (env var or default)
26
- const configLocation = process.env.NETWORK_CONFIG_LOCATION || DEFAULT_CONFIG_URL;
27
+ const configLocation = getEnv().NETWORK_CONFIG_LOCATION || DEFAULT_CONFIG_URL;
27
28
 
28
29
  let primaryError: Error | undefined;
29
30
  let config: NetworkConfig | undefined;
@@ -119,7 +120,8 @@ export async function enrichEnvironmentWithNetworkConfig(networkName: NetworkNam
119
120
  return; // No remote config for local development
120
121
  }
121
122
 
122
- const cacheDir = process.env.DATA_DIRECTORY ? join(process.env.DATA_DIRECTORY, 'cache') : undefined;
123
+ const { DATA_DIRECTORY } = getEnv();
124
+ const cacheDir = DATA_DIRECTORY ? join(DATA_DIRECTORY, 'cache') : undefined;
123
125
  const networkConfig = await getNetworkConfig(networkName, cacheDir);
124
126
 
125
127
  if (!networkConfig) {
@@ -15,6 +15,7 @@ import { type LogFn, createLogger } from '@aztec-labs/foundation/log';
15
15
  import type { NoirPackageConfig } from '@aztec-labs/foundation/noir';
16
16
  import { protocolContractsHash } from '@aztec-labs/protocol-contracts';
17
17
  import TOML from '@iarna/toml';
18
+ import { Buffer } from 'buffer';
18
19
  import { readFile } from 'fs/promises';
19
20
  import type { HDAccount, Hex, PrivateKeyAccount } from 'viem';
20
21
 
@@ -1,3 +1,4 @@
1
+ import { getEnv } from '@aztec-labs/foundation/config/env';
1
2
  import { Fr } from '@aztec-labs/foundation/curves/bn254';
2
3
  import { GrumpkinScalar } from '@aztec-labs/foundation/curves/grumpkin';
3
4
  import { EthAddress } from '@aztec-labs/foundation/eth-address';
@@ -8,6 +9,7 @@ import { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
8
9
  import { PublicKeys } from '@aztec-labs/stdlib/keys';
9
10
  import { LogCursor, Tag } from '@aztec-labs/stdlib/logs';
10
11
  import { TxHash } from '@aztec-labs/stdlib/tx/tx-hash';
12
+ import { Buffer } from 'buffer';
11
13
  import { type Command, CommanderError, InvalidArgumentError, Option } from 'commander';
12
14
  import { lookup } from 'dns/promises';
13
15
  import { rename, writeFile } from 'fs/promises';
@@ -24,7 +26,7 @@ export const getLocalhost = () =>
24
26
  .catch(() => 'localhost');
25
27
 
26
28
  export const LOCALHOST = await getLocalhost();
27
- export const { ETHEREUM_HOSTS = `http://${LOCALHOST}:8545`, PRIVATE_KEY, MNEMONIC, API_KEY, CLI_VERSION } = process.env;
29
+ export const { ETHEREUM_HOSTS = `http://${LOCALHOST}:8545`, PRIVATE_KEY, MNEMONIC, API_KEY, CLI_VERSION } = getEnv();
28
30
 
29
31
  export function addOptions(program: Command, options: Option[]) {
30
32
  options.forEach(option => program.addOption(option));
@@ -1,5 +1,6 @@
1
1
  import { Fr } from '@aztec-labs/foundation/curves/bn254';
2
2
  import type { ABIParameter, AbiType, StructType } from '@aztec-labs/stdlib/abi';
3
+ import { Buffer } from 'buffer';
3
4
 
4
5
  /**
5
6
  * Parses a hex string into an ABI struct type.