@aztec/node-keystore 2.0.3 → 2.1.0-rc.10

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.
@@ -8,7 +8,7 @@ import { Buffer32 } from '@aztec/foundation/buffer';
8
8
  import { EthAddress } from '@aztec/foundation/eth-address';
9
9
  import type { Signature } from '@aztec/foundation/eth-signature';
10
10
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
- import type { TypedDataDefinition } from 'viem';
11
+ import type { TypedDataDefinition } from '@spalladino/viem';
12
12
  import type { EthAccounts, EthRemoteSignerConfig, KeyStore, ProverKeyStore, ValidatorKeyStore as ValidatorKeystoreConfig } from './types.js';
13
13
  /**
14
14
  * Error thrown when keystore operations fail
@@ -28,6 +28,11 @@ export declare class KeystoreManager {
28
28
  * @param keystore Parsed keystore configuration
29
29
  */
30
30
  constructor(keystore: KeyStore);
31
+ /**
32
+ * Validates all remote signers in the keystore are accessible and have the required addresses.
33
+ * Should be called after construction if validation is needed.
34
+ */
35
+ validateSigners(): Promise<void>;
31
36
  /**
32
37
  * Validates that attester addresses are unique across all validators
33
38
  * Only checks simple private key attesters, not JSON-V3 or mnemonic attesters,
@@ -40,6 +45,10 @@ export declare class KeystoreManager {
40
45
  * This is used at construction time to check for obvious duplicates without throwing for invalid inputs.
41
46
  */
42
47
  private extractAddressesWithoutSensitiveOperations;
48
+ /**
49
+ * Extract addresses from EthAccounts without sensitive operations (no decryption/derivation).
50
+ */
51
+ private extractAddressesFromEthAccountsNonSensitive;
43
52
  /**
44
53
  * Create signers for validator attester accounts
45
54
  */
@@ -69,9 +78,9 @@ export declare class KeystoreManager {
69
78
  */
70
79
  getValidatorCount(): number;
71
80
  /**
72
- * Get coinbase address for validator (falls back to first attester address)
81
+ * Get coinbase address for validator (falls back to the specific attester address)
73
82
  */
74
- getCoinbaseAddress(validatorIndex: number): EthAddress;
83
+ getCoinbaseAddress(validatorIndex: number, attesterAddress: EthAddress): EthAddress;
75
84
  /**
76
85
  * Get fee recipient for validator
77
86
  */
@@ -124,5 +133,7 @@ export declare class KeystoreManager {
124
133
  * Precedence: account-level override > validator-level config > file-level default
125
134
  */
126
135
  getEffectiveRemoteSignerConfig(validatorIndex: number, attesterAddress: EthAddress): EthRemoteSignerConfig | undefined;
136
+ /** Extract ETH accounts from AttesterAccounts */
137
+ private extractEthAccountsFromAttester;
127
138
  }
128
139
  //# sourceMappingURL=keystore_manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"keystore_manager.d.ts","sourceRoot":"","sources":["../src/keystore_manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAKhD,OAAO,KAAK,EAEV,WAAW,EAIX,qBAAqB,EACrB,QAAQ,EACR,cAAc,EACd,iBAAiB,IAAI,uBAAuB,EAC7C,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGpB,KAAK,CAAC,EAAE,KAAK;gBAD7B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC;;;;OAIG;gBACS,QAAQ,EAAE,QAAQ;IAK9B;;;;;OAKG;IACH,OAAO,CAAC,+BAA+B;IAkBvC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IAoDlD;;OAEG;IACH,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAK1D;;OAEG;IACH,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAc3D,kCAAkC,IAAI,SAAS,EAAE;IAWjD;;OAEG;IACH,oBAAoB,IAAI,SAAS,EAAE;IAQnC;;OAEG;IACH,mBAAmB,IAAI;QAAE,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,SAAS;IAkCvF;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAOpD;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,UAAU;IAgBtD;;OAEG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY;IAKrD;;;OAGG;IACH,kBAAkB,IAAI,WAAW,GAAG,SAAS;IAI7C;;;OAGG;IACH,eAAe,IAAI,cAAc,GAAG,SAAS;IAI7C;;;OAGG;IACH,uCAAuC,IAAI,IAAI;IAqB/C;;OAEG;IACH,OAAO,CAAC,4BAA4B;IA+BpC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA2ClC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkD9B;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAwBxC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA8BjC;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAI3E;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAI1F;;;OAGG;IACH,8BAA8B,CAC5B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,UAAU,GAC1B,qBAAqB,GAAG,SAAS;CAkHrC"}
1
+ {"version":3,"file":"keystore_manager.d.ts","sourceRoot":"","sources":["../src/keystore_manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAO5D,OAAO,KAAK,EAGV,WAAW,EAEX,qBAAqB,EAErB,QAAQ,EAER,cAAc,EACd,iBAAiB,IAAI,uBAAuB,EAC7C,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGpB,KAAK,CAAC,EAAE,KAAK;gBAD7B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC;;;;OAIG;gBACS,QAAQ,EAAE,QAAQ;IAK9B;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAwEtC;;;;;OAKG;IACH,OAAO,CAAC,+BAA+B;IAkBvC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IAKlD;;OAEG;IACH,OAAO,CAAC,2CAA2C;IA+CnD;;OAEG;IACH,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAM1D;;OAEG;IACH,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAc3D,kCAAkC,IAAI,SAAS,EAAE;IAWjD;;OAEG;IACH,oBAAoB,IAAI,SAAS,EAAE;IAQnC;;OAEG;IACH,mBAAmB,IAAI;QAAE,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,SAAS;IAiCvF;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAOpD;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,UAAU,GAAG,UAAU;IAWnF;;OAEG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY;IAKrD;;;OAGG;IACH,kBAAkB,IAAI,WAAW,GAAG,SAAS;IAI7C;;;OAGG;IACH,eAAe,IAAI,cAAc,GAAG,SAAS;IAI7C;;;OAGG;IACH,uCAAuC,IAAI,IAAI;IAqB/C;;OAEG;IACH,OAAO,CAAC,4BAA4B;IA+BpC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA2ClC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkD9B;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAwBxC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA8BjC;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAI3E;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAI1F;;;OAGG;IACH,8BAA8B,CAC5B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,UAAU,GAC1B,qBAAqB,GAAG,SAAS;IA0GpC,iDAAiD;IACjD,OAAO,CAAC,8BAA8B;CAyBvC"}
@@ -4,9 +4,9 @@
4
4
  * Manages keystore configuration and delegates signing operations to appropriate signers.
5
5
  */ import { Buffer32 } from '@aztec/foundation/buffer';
6
6
  import { Wallet } from '@ethersproject/wallet';
7
+ import { mnemonicToAccount } from '@spalladino/viem/accounts';
7
8
  import { readFileSync, readdirSync, statSync } from 'fs';
8
9
  import { extname, join } from 'path';
9
- import { mnemonicToAccount } from 'viem/accounts';
10
10
  import { ethPrivateKeySchema } from './schemas.js';
11
11
  import { LocalSigner, RemoteSigner } from './signer.js';
12
12
  /**
@@ -31,6 +31,70 @@ import { LocalSigner, RemoteSigner } from './signer.js';
31
31
  this.validateUniqueAttesterAddresses();
32
32
  }
33
33
  /**
34
+ * Validates all remote signers in the keystore are accessible and have the required addresses.
35
+ * Should be called after construction if validation is needed.
36
+ */ async validateSigners() {
37
+ // Collect all remote signers with their addresses grouped by URL
38
+ const remoteSignersByUrl = new Map();
39
+ // Helper to extract remote signer URL from config
40
+ const getUrl = (config)=>{
41
+ return typeof config === 'string' ? config : config.remoteSignerUrl;
42
+ };
43
+ // Helper to collect remote signers from accounts
44
+ const collectRemoteSigners = (accounts, defaultRemoteSigner)=>{
45
+ const processAccount = (account)=>{
46
+ if (typeof account === 'object' && !('path' in account) && !('mnemonic' in account)) {
47
+ // This is a remote signer account
48
+ const remoteSigner = account;
49
+ const address = 'address' in remoteSigner ? remoteSigner.address : remoteSigner;
50
+ let url;
51
+ if ('remoteSignerUrl' in remoteSigner && remoteSigner.remoteSignerUrl) {
52
+ url = remoteSigner.remoteSignerUrl;
53
+ } else if (defaultRemoteSigner) {
54
+ url = getUrl(defaultRemoteSigner);
55
+ } else {
56
+ return; // No remote signer URL available
57
+ }
58
+ if (!remoteSignersByUrl.has(url)) {
59
+ remoteSignersByUrl.set(url, new Set());
60
+ }
61
+ remoteSignersByUrl.get(url).add(address.toString());
62
+ }
63
+ };
64
+ if (Array.isArray(accounts)) {
65
+ accounts.forEach((account)=>collectRemoteSigners(account, defaultRemoteSigner));
66
+ } else if (typeof accounts === 'object' && 'mnemonic' in accounts) {
67
+ // Skip mnemonic configs
68
+ } else {
69
+ processAccount(accounts);
70
+ }
71
+ };
72
+ // Collect from validators
73
+ const validatorCount = this.getValidatorCount();
74
+ for(let i = 0; i < validatorCount; i++){
75
+ const validator = this.getValidator(i);
76
+ const remoteSigner = validator.remoteSigner || this.keystore.remoteSigner;
77
+ collectRemoteSigners(this.extractEthAccountsFromAttester(validator.attester), remoteSigner);
78
+ if (validator.publisher) {
79
+ collectRemoteSigners(validator.publisher, remoteSigner);
80
+ }
81
+ }
82
+ // Collect from slasher
83
+ if (this.keystore.slasher) {
84
+ collectRemoteSigners(this.keystore.slasher, this.keystore.remoteSigner);
85
+ }
86
+ // Collect from prover
87
+ if (this.keystore.prover && typeof this.keystore.prover === 'object' && 'publisher' in this.keystore.prover) {
88
+ collectRemoteSigners(this.keystore.prover.publisher, this.keystore.remoteSigner);
89
+ }
90
+ // Validate each remote signer URL with all its addresses
91
+ for (const [url, addresses] of remoteSignersByUrl.entries()){
92
+ if (addresses.size > 0) {
93
+ await RemoteSigner.validateAccess(url, Array.from(addresses));
94
+ }
95
+ }
96
+ }
97
+ /**
34
98
  * Validates that attester addresses are unique across all validators
35
99
  * Only checks simple private key attesters, not JSON-V3 or mnemonic attesters,
36
100
  * these are validated when decrypting the JSON-V3 keystore files
@@ -54,32 +118,31 @@ import { LocalSigner, RemoteSigner } from './signer.js';
54
118
  * Best-effort address extraction that avoids decryption/derivation (no JSON-V3 or mnemonic processing).
55
119
  * This is used at construction time to check for obvious duplicates without throwing for invalid inputs.
56
120
  */ extractAddressesWithoutSensitiveOperations(accounts) {
121
+ const ethAccounts = this.extractEthAccountsFromAttester(accounts);
122
+ return this.extractAddressesFromEthAccountsNonSensitive(ethAccounts);
123
+ }
124
+ /**
125
+ * Extract addresses from EthAccounts without sensitive operations (no decryption/derivation).
126
+ */ extractAddressesFromEthAccountsNonSensitive(accounts) {
57
127
  const results = [];
58
128
  const handleAccount = (account)=>{
59
- // String cases: private key or address or remote signer address
60
129
  if (typeof account === 'string') {
61
130
  if (account.startsWith('0x') && account.length === 66) {
62
- // Private key -> derive address locally without external deps
63
131
  try {
64
132
  const signer = new LocalSigner(Buffer32.fromString(ethPrivateKeySchema.parse(account)));
65
133
  results.push(signer.address);
66
134
  } catch {
67
- // Ignore invalid private key at construction time
135
+ // ignore invalid private key at construction time
68
136
  }
69
- return;
70
137
  }
71
- // Any other string cannot be confidently resolved here
72
138
  return;
73
139
  }
74
- // JSON V3 keystore: skip (requires decryption)
75
140
  if ('path' in account) {
76
141
  return;
77
142
  }
78
- // Mnemonic: skip (requires derivation and may throw on invalid mnemonics)
79
143
  if ('mnemonic' in account) {
80
144
  return;
81
145
  }
82
- // Remote signer account. If it contains 'address' then extract, otherwise it IS the address
83
146
  const remoteSigner = account;
84
147
  if ('address' in remoteSigner) {
85
148
  results.push(remoteSigner.address);
@@ -89,11 +152,13 @@ import { LocalSigner, RemoteSigner } from './signer.js';
89
152
  };
90
153
  if (Array.isArray(accounts)) {
91
154
  for (const account of accounts){
92
- const subResults = this.extractAddressesWithoutSensitiveOperations(account);
93
- results.push(...subResults);
155
+ handleAccount(account);
94
156
  }
95
157
  return results;
96
158
  }
159
+ if (typeof accounts === 'object' && accounts !== null && 'mnemonic' in accounts) {
160
+ return results;
161
+ }
97
162
  handleAccount(accounts);
98
163
  return results;
99
164
  }
@@ -101,7 +166,8 @@ import { LocalSigner, RemoteSigner } from './signer.js';
101
166
  * Create signers for validator attester accounts
102
167
  */ createAttesterSigners(validatorIndex) {
103
168
  const validator = this.getValidator(validatorIndex);
104
- return this.createSignersFromEthAccounts(validator.attester, validator.remoteSigner || this.keystore.remoteSigner);
169
+ const ethAccounts = this.extractEthAccountsFromAttester(validator.attester);
170
+ return this.createSignersFromEthAccounts(ethAccounts, validator.remoteSigner || this.keystore.remoteSigner);
105
171
  }
106
172
  /**
107
173
  * Create signers for validator publisher accounts (falls back to attester if not specified)
@@ -173,18 +239,14 @@ import { LocalSigner, RemoteSigner } from './signer.js';
173
239
  return this.keystore.validators?.length || 0;
174
240
  }
175
241
  /**
176
- * Get coinbase address for validator (falls back to first attester address)
177
- */ getCoinbaseAddress(validatorIndex) {
242
+ * Get coinbase address for validator (falls back to the specific attester address)
243
+ */ getCoinbaseAddress(validatorIndex, attesterAddress) {
178
244
  const validator = this.getValidator(validatorIndex);
179
245
  if (validator.coinbase) {
180
246
  return validator.coinbase;
181
247
  }
182
- // Fall back to first attester address
183
- const attesterSigners = this.createAttesterSigners(validatorIndex);
184
- if (attesterSigners.length === 0) {
185
- throw new KeystoreError(`No attester signers found for validator ${validatorIndex}`);
186
- }
187
- return attesterSigners[0].address;
248
+ // Fall back to the specific attester address
249
+ return attesterAddress;
188
250
  }
189
251
  /**
190
252
  * Get fee recipient for validator
@@ -212,7 +274,7 @@ import { LocalSigner, RemoteSigner } from './signer.js';
212
274
  const validatorCount = this.getValidatorCount();
213
275
  for(let validatorIndex = 0; validatorIndex < validatorCount; validatorIndex++){
214
276
  const validator = this.getValidator(validatorIndex);
215
- const signers = this.createSignersFromEthAccounts(validator.attester, validator.remoteSigner || this.keystore.remoteSigner);
277
+ const signers = this.createSignersFromEthAccounts(this.extractEthAccountsFromAttester(validator.attester), validator.remoteSigner || this.keystore.remoteSigner);
216
278
  for (const signer of signers){
217
279
  const address = signer.address.toString().toLowerCase();
218
280
  if (seenAddresses.has(address)) {
@@ -466,34 +528,52 @@ import { LocalSigner, RemoteSigner } from './signer.js';
466
528
  // Just an address, use defaults
467
529
  return validator.remoteSigner || this.keystore.remoteSigner;
468
530
  };
469
- // Check the attester configuration
470
- const { attester } = validator;
531
+ // Normalize attester to EthAccounts and search
532
+ const normalized = this.extractEthAccountsFromAttester(validator.attester);
533
+ const findInEthAccounts = (accs)=>{
534
+ if (typeof accs === 'string') {
535
+ return checkAccount(accs);
536
+ }
537
+ if (Array.isArray(accs)) {
538
+ for (const a of accs){
539
+ const res = checkAccount(a);
540
+ if (res !== undefined) {
541
+ return res;
542
+ }
543
+ }
544
+ return undefined;
545
+ }
546
+ if (typeof accs === 'object' && accs !== null && 'mnemonic' in accs) {
547
+ // mnemonic-derived keys are local signers; no remote signer config
548
+ return undefined;
549
+ }
550
+ return checkAccount(accs);
551
+ };
552
+ return findInEthAccounts(normalized);
553
+ }
554
+ /** Extract ETH accounts from AttesterAccounts */ extractEthAccountsFromAttester(attester) {
471
555
  if (typeof attester === 'string') {
472
- const result = checkAccount(attester);
473
- return result === undefined ? undefined : result;
556
+ return attester;
474
557
  }
475
558
  if (Array.isArray(attester)) {
476
- for (const account of attester){
477
- const result = checkAccount(account);
478
- if (result !== undefined) {
479
- return result;
559
+ const out = [];
560
+ for (const item of attester){
561
+ if (typeof item === 'string') {
562
+ out.push(item);
563
+ } else if ('eth' in item) {
564
+ out.push(item.eth);
565
+ } else if (!('mnemonic' in item)) {
566
+ out.push(item);
480
567
  }
481
568
  }
482
- return undefined;
569
+ return out;
483
570
  }
484
- // Mnemonic configuration
485
571
  if ('mnemonic' in attester) {
486
- try {
487
- const signers = this.createSignersFromMnemonic(attester);
488
- const matches = signers.some((s)=>s.address.equals(attesterAddress));
489
- // Mnemonic-derived keys are local signers
490
- return matches ? undefined : undefined;
491
- } catch {
492
- return undefined;
493
- }
572
+ return attester;
573
+ }
574
+ if ('eth' in attester) {
575
+ return attester.eth;
494
576
  }
495
- // Single account object
496
- const result = checkAccount(attester);
497
- return result === undefined ? undefined : result;
577
+ return attester;
498
578
  }
499
579
  }
package/dest/loader.js CHANGED
@@ -236,19 +236,38 @@ const logger = createLogger('node-keystore:loader');
236
236
  * @param attester The attester configuration in any supported shape.
237
237
  * @returns Array of string keys used to detect duplicates.
238
238
  */ function extractAttesterKeys(attester) {
239
+ // String forms (private key or other) - return as-is for coarse uniqueness
239
240
  if (typeof attester === 'string') {
240
241
  return [
241
242
  attester
242
243
  ];
243
244
  }
245
+ // Arrays of attester items
244
246
  if (Array.isArray(attester)) {
245
- return attester.map((a)=>typeof a === 'string' ? a : JSON.stringify(a));
247
+ const keys = [];
248
+ for (const item of attester){
249
+ keys.push(...extractAttesterKeys(item));
250
+ }
251
+ return keys;
246
252
  }
247
- if (attester && typeof attester === 'object' && 'address' in attester) {
253
+ if (attester && typeof attester === 'object') {
254
+ const obj = attester;
255
+ // New shape: { eth: EthAccount, bls?: BLSAccount }
256
+ if ('eth' in obj) {
257
+ return extractAttesterKeys(obj.eth);
258
+ }
259
+ // Remote signer account object shape: { address, remoteSignerUrl?, ... }
260
+ if ('address' in obj) {
261
+ return [
262
+ String(obj.address)
263
+ ];
264
+ }
265
+ // Mnemonic or other object shapes: stringify
248
266
  return [
249
- attester.address
267
+ JSON.stringify(attester)
250
268
  ];
251
269
  }
270
+ // Fallback stringify for anything else (null/undefined)
252
271
  return [
253
272
  JSON.stringify(attester)
254
273
  ];