@aztec/node-keystore 4.0.0-nightly.20250907 → 4.0.0-nightly.20260107

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.
package/dest/loader.js CHANGED
@@ -2,9 +2,11 @@
2
2
  * Keystore File Loader
3
3
  *
4
4
  * Handles loading and parsing keystore configuration files.
5
- */ import { createLogger } from '@aztec/foundation/log';
5
+ */ import { EthAddress } from '@aztec/foundation/eth-address';
6
+ import { createLogger } from '@aztec/foundation/log';
6
7
  import { readFileSync, readdirSync, statSync } from 'fs';
7
8
  import { extname, join } from 'path';
9
+ import { privateKeyToAddress } from 'viem/accounts';
8
10
  import { keystoreSchema } from './schemas.js';
9
11
  const logger = createLogger('node-keystore:loader');
10
12
  /**
@@ -166,12 +168,17 @@ const logger = createLogger('node-keystore:loader');
166
168
  }
167
169
  // Track attester addresses to prevent duplicates
168
170
  const attesterAddresses = new Set();
171
+ // Determine schema version: use v2 if any input is v2
172
+ const schemaVersion = keystores.some((ks)=>ks.schemaVersion === 2) ? 2 : 1;
169
173
  const merged = {
170
- schemaVersion: 1,
174
+ schemaVersion,
171
175
  validators: [],
172
176
  slasher: undefined,
173
177
  remoteSigner: undefined,
174
- prover: undefined
178
+ prover: undefined,
179
+ publisher: undefined,
180
+ coinbase: undefined,
181
+ feeRecipient: undefined
175
182
  };
176
183
  for(let i = 0; i < keystores.length; i++){
177
184
  const keystore = keystores[i];
@@ -179,15 +186,22 @@ const logger = createLogger('node-keystore:loader');
179
186
  if (keystore.validators) {
180
187
  for (const validator of keystore.validators){
181
188
  // Check for duplicate attester addresses
182
- const attesterKeys = extractAttesterKeys(validator.attester);
183
- for (const key of attesterKeys){
189
+ const attesterKeys = extractAttesterAddresses(validator.attester);
190
+ for (let key of attesterKeys){
191
+ key = key.toLowerCase();
184
192
  if (attesterAddresses.has(key)) {
185
193
  throw new KeyStoreLoadError(`Duplicate attester address ${key} found across keystore files`, `keystores[${i}].validators`);
186
194
  }
187
195
  attesterAddresses.add(key);
188
196
  }
197
+ // When merging v1 validators into a v2+ result, preserve original fallback behavior
198
+ // by explicitly setting publisher/coinbase/feeRecipient if they're missing
199
+ if (keystore.schemaVersion !== schemaVersion) {
200
+ throw new KeyStoreLoadError(`Cannot merge keystores with different schema versions: ${keystore.schemaVersion} and ${schemaVersion}`, `keystores[${i}].schemaVersion`);
201
+ } else {
202
+ merged.validators.push(validator);
203
+ }
189
204
  }
190
- merged.validators.push(...keystore.validators);
191
205
  }
192
206
  // Merge slasher (accumulate all)
193
207
  if (keystore.slasher) {
@@ -219,6 +233,43 @@ const logger = createLogger('node-keystore:loader');
219
233
  }
220
234
  merged.prover = keystore.prover;
221
235
  }
236
+ // Merge top-level publisher (accumulate all, unless conflicting MnemonicConfigs)
237
+ if (keystore.publisher) {
238
+ if (!merged.publisher) {
239
+ merged.publisher = keystore.publisher;
240
+ } else {
241
+ const isMnemonic = (accounts)=>typeof accounts === 'object' && accounts !== null && 'mnemonic' in accounts;
242
+ // If either is a mnemonic, warn and use last one (can't merge mnemonics)
243
+ if (isMnemonic(merged.publisher) || isMnemonic(keystore.publisher)) {
244
+ logger.warn('Multiple default publisher configurations found with mnemonic, using the last one (cannot merge mnemonics)');
245
+ merged.publisher = keystore.publisher;
246
+ } else {
247
+ // Both are non-mnemonic, accumulate them
248
+ const toArray = (accounts)=>Array.isArray(accounts) ? accounts : [
249
+ accounts
250
+ ];
251
+ const combined = [
252
+ ...toArray(merged.publisher),
253
+ ...toArray(keystore.publisher)
254
+ ];
255
+ merged.publisher = combined;
256
+ }
257
+ }
258
+ }
259
+ // Merge top-level coinbase (last one wins, but warn about conflicts)
260
+ if (keystore.coinbase) {
261
+ if (merged.coinbase) {
262
+ logger.warn('Multiple default coinbase addresses found, using the last one');
263
+ }
264
+ merged.coinbase = keystore.coinbase;
265
+ }
266
+ // Merge top-level feeRecipient (last one wins, but warn about conflicts)
267
+ if (keystore.feeRecipient) {
268
+ if (merged.feeRecipient) {
269
+ logger.warn('Multiple default feeRecipient addresses found, using the last one');
270
+ }
271
+ merged.feeRecipient = keystore.feeRecipient;
272
+ }
222
273
  }
223
274
  // Clean up empty arrays
224
275
  if (merged.validators.length === 0) {
@@ -235,21 +286,45 @@ const logger = createLogger('node-keystore:loader');
235
286
  *
236
287
  * @param attester The attester configuration in any supported shape.
237
288
  * @returns Array of string keys used to detect duplicates.
238
- */ function extractAttesterKeys(attester) {
289
+ */ function extractAttesterAddresses(attester) {
290
+ // String forms (private key or other) - return as-is for coarse uniqueness
239
291
  if (typeof attester === 'string') {
240
- return [
241
- attester
242
- ];
292
+ if (attester.length === 66) {
293
+ return [
294
+ privateKeyToAddress(attester)
295
+ ];
296
+ } else {
297
+ return [
298
+ attester
299
+ ];
300
+ }
243
301
  }
302
+ // Arrays of attester items
244
303
  if (Array.isArray(attester)) {
245
- return attester.map((a)=>typeof a === 'string' ? a : JSON.stringify(a));
304
+ const keys = [];
305
+ for (const item of attester){
306
+ keys.push(...extractAttesterAddresses(item));
307
+ }
308
+ return keys;
246
309
  }
247
- if (attester && typeof attester === 'object' && 'address' in attester) {
248
- return [
249
- attester.address
250
- ];
310
+ if (attester && typeof attester === 'object') {
311
+ if (attester instanceof EthAddress) {
312
+ return [
313
+ attester.toString()
314
+ ];
315
+ }
316
+ const obj = attester;
317
+ // New shape: { eth: EthAccount, bls?: BLSAccount }
318
+ if ('eth' in obj) {
319
+ return extractAttesterAddresses(obj.eth);
320
+ }
321
+ // Remote signer account object shape: { address, remoteSignerUrl?, ... }
322
+ if ('address' in obj) {
323
+ return [
324
+ String(obj.address)
325
+ ];
326
+ }
251
327
  }
252
- return [
253
- JSON.stringify(attester)
254
- ];
328
+ // mnemonic, encrypted file just disable early duplicates checking
329
+ return [];
255
330
  }