@@ -6,7 +6,7 @@ import type { LogFn } from '@aztec/foundation/log';
66import type { AztecAddress } from '@aztec/stdlib/aztec-address' ;
77
88import { wordlist } from '@scure/bip39/wordlists/english.js' ;
9- import { writeFile } from 'fs/promises' ;
9+ import { readFile , writeFile } from 'fs/promises' ;
1010import { basename , dirname , join } from 'path' ;
1111import { createPublicClient , fallback , http } from 'viem' ;
1212import { generateMnemonic , mnemonicToAccount } from 'viem/accounts' ;
@@ -42,6 +42,11 @@ export type NewValidatorKeystoreOptions = {
4242 ikm ?: string ;
4343 blsPath ?: string ;
4444 password ?: string ;
45+ passwordFile ?: string ;
46+ ethPassword ?: string ;
47+ ethPasswordFile ?: string ;
48+ blsPassword ?: string ;
49+ blsPasswordFile ?: string ;
4550 encryptedKeystoreDir ?: string ;
4651 json ?: boolean ;
4752 feeRecipient : AztecAddress ;
@@ -53,6 +58,86 @@ export type NewValidatorKeystoreOptions = {
5358 l1ChainId ?: number ;
5459} ;
5560
61+ type PasswordSourceOptions = Pick <
62+ NewValidatorKeystoreOptions ,
63+ 'password' | 'passwordFile' | 'ethPassword' | 'ethPasswordFile' | 'blsPassword' | 'blsPasswordFile'
64+ > ;
65+
66+ type PasswordSource = {
67+ password ?: string ;
68+ passwordFile ?: string ;
69+ passwordOption : string ;
70+ passwordFileOption : string ;
71+ } ;
72+
73+ function validatePassword ( password : string , source : string ) : string {
74+ if ( password . length === 0 ) {
75+ throw new Error ( `${ source } cannot be empty` ) ;
76+ }
77+ return password ;
78+ }
79+
80+ function stripOneTrailingNewline ( password : string ) : string {
81+ if ( password . endsWith ( '\n' ) ) {
82+ password = password . slice ( 0 , - 1 ) ;
83+ }
84+ if ( password . endsWith ( '\r' ) ) {
85+ password = password . slice ( 0 , - 1 ) ;
86+ }
87+ return password ;
88+ }
89+
90+ async function resolvePasswordSource ( source : PasswordSource ) : Promise < string | undefined > {
91+ if ( source . password !== undefined && source . passwordFile !== undefined ) {
92+ throw new Error ( `${ source . passwordOption } and ${ source . passwordFileOption } cannot be used together` ) ;
93+ }
94+ if ( source . password !== undefined ) {
95+ return validatePassword ( source . password , source . passwordOption ) ;
96+ }
97+ if ( source . passwordFile !== undefined ) {
98+ if ( source . passwordFile . length === 0 ) {
99+ throw new Error ( `${ source . passwordFileOption } cannot be empty` ) ;
100+ }
101+ const password = stripOneTrailingNewline ( await readFile ( source . passwordFile , 'utf-8' ) ) ;
102+ return validatePassword ( password , source . passwordFileOption ) ;
103+ }
104+
105+ return undefined ;
106+ }
107+
108+ async function resolvePasswords ( options : PasswordSourceOptions ) {
109+ const sharedPassword = await resolvePasswordSource ( {
110+ password : options . password ,
111+ passwordFile : options . passwordFile ,
112+ passwordOption : '--password' ,
113+ passwordFileOption : '--password-file' ,
114+ } ) ;
115+ const ethPassword =
116+ ( await resolvePasswordSource ( {
117+ password : options . ethPassword ,
118+ passwordFile : options . ethPasswordFile ,
119+ passwordOption : '--eth-password' ,
120+ passwordFileOption : '--eth-password-file' ,
121+ } ) ) ?? sharedPassword ;
122+ const blsPassword =
123+ ( await resolvePasswordSource ( {
124+ password : options . blsPassword ,
125+ passwordFile : options . blsPasswordFile ,
126+ passwordOption : '--bls-password' ,
127+ passwordFileOption : '--bls-password-file' ,
128+ } ) ) ?? sharedPassword ;
129+
130+ if ( ethPassword === undefined && blsPassword === undefined ) {
131+ return { } ;
132+ }
133+ if ( ethPassword === undefined || blsPassword === undefined ) {
134+ throw new Error (
135+ 'Both ETH and BLS passwords are required when writing encrypted keystores. Provide --password to use one password for both, or provide both --eth-password and --bls-password.' ,
136+ ) ;
137+ }
138+ return { ethPassword, blsPassword } ;
139+ }
140+
56141export async function newValidatorKeystore ( options : NewValidatorKeystoreOptions , log : LogFn ) {
57142 // validate bls-path inputs before proceeding with key generation
58143 validateBlsPathOptions ( options ) ;
@@ -78,17 +163,31 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
78163 blsPath,
79164 ikm,
80165 mnemonic : _mnemonic ,
81- password,
166+ password : passwordOption ,
167+ passwordFile,
168+ ethPassword : ethPasswordOption ,
169+ ethPasswordFile,
170+ blsPassword : blsPasswordOption ,
171+ blsPasswordFile,
82172 encryptedKeystoreDir,
83173 stakerOutput,
84174 gseAddress,
85175 l1RpcUrls,
86176 l1ChainId,
87177 } = options ;
88178
179+ const { ethPassword, blsPassword } = await resolvePasswords ( {
180+ password : passwordOption ,
181+ passwordFile,
182+ ethPassword : ethPasswordOption ,
183+ ethPasswordFile,
184+ blsPassword : blsPasswordOption ,
185+ blsPasswordFile,
186+ } ) ;
187+ const shouldEncryptKeystores = ethPassword !== undefined && blsPassword !== undefined ;
89188 const mnemonic = _mnemonic ?? generateMnemonic ( wordlist ) ;
90189
91- if ( ! _mnemonic && ! json ) {
190+ if ( ! _mnemonic && ! json && ! shouldEncryptKeystores ) {
92191 log ( 'No mnemonic provided, generating new one...' ) ;
93192 log ( `Using new mnemonic:` ) ;
94193 log ( '' ) ;
@@ -115,11 +214,11 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
115214 } ) ;
116215
117216 // If password provided, write ETH JSON V3 and BLS BN254 keystores and replace plaintext
118- if ( password !== undefined ) {
217+ if ( shouldEncryptKeystores ) {
119218 const encryptedKeystoreOutDir =
120219 encryptedKeystoreDir && encryptedKeystoreDir . length > 0 ? encryptedKeystoreDir : keystoreOutDir ;
121- await writeEthJsonV3ToFile ( validators , { outDir : encryptedKeystoreOutDir , password } ) ;
122- await writeBlsBn254ToFile ( validators , { outDir : encryptedKeystoreOutDir , password } ) ;
220+ await writeEthJsonV3ToFile ( validators , { outDir : encryptedKeystoreOutDir , password : ethPassword } ) ;
221+ await writeBlsBn254ToFile ( validators , { outDir : encryptedKeystoreOutDir , password : blsPassword } ) ;
123222 }
124223
125224 const keystore = {
@@ -145,7 +244,7 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
145244 // Process each validator
146245 for ( let i = 0 ; i < validators . length ; i ++ ) {
147246 const validator = validators [ i ] ;
148- const outputs = await processAttesterAccounts ( validator . attester , gse , password ) ;
247+ const outputs = await processAttesterAccounts ( validator . attester , gse ) ;
149248
150249 // Collect all staker outputs
151250 for ( let j = 0 ; j < outputs . length ; j ++ ) {
@@ -160,7 +259,7 @@ export async function newValidatorKeystore(options: NewValidatorKeystoreOptions,
160259 }
161260 }
162261
163- const outputData = ! _mnemonic ? { ...keystore , generatedMnemonic : mnemonic } : keystore ;
262+ const outputData = ! _mnemonic && ! shouldEncryptKeystores ? { ...keystore , generatedMnemonic : mnemonic } : keystore ;
164263
165264 // Handle JSON output
166265 if ( json ) {
0 commit comments