@aztec-labs/validator-client 6.0.0-nightly.20260829

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 (62) hide show
  1. package/README.md +327 -0
  2. package/dest/checkpoint_builder.d.ts +92 -0
  3. package/dest/checkpoint_builder.d.ts.map +1 -0
  4. package/dest/checkpoint_builder.js +272 -0
  5. package/dest/config.d.ts +17 -0
  6. package/dest/config.d.ts.map +1 -0
  7. package/dest/config.js +102 -0
  8. package/dest/duties/validation_service.d.ts +65 -0
  9. package/dest/duties/validation_service.d.ts.map +1 -0
  10. package/dest/duties/validation_service.js +128 -0
  11. package/dest/factory.d.ts +41 -0
  12. package/dest/factory.d.ts.map +1 -0
  13. package/dest/factory.js +19 -0
  14. package/dest/index.d.ts +7 -0
  15. package/dest/index.d.ts.map +1 -0
  16. package/dest/index.js +6 -0
  17. package/dest/key_store/ha_key_store.d.ts +99 -0
  18. package/dest/key_store/ha_key_store.d.ts.map +1 -0
  19. package/dest/key_store/ha_key_store.js +208 -0
  20. package/dest/key_store/index.d.ts +6 -0
  21. package/dest/key_store/index.d.ts.map +1 -0
  22. package/dest/key_store/index.js +5 -0
  23. package/dest/key_store/interface.d.ts +104 -0
  24. package/dest/key_store/interface.d.ts.map +1 -0
  25. package/dest/key_store/interface.js +4 -0
  26. package/dest/key_store/local_key_store.d.ts +63 -0
  27. package/dest/key_store/local_key_store.d.ts.map +1 -0
  28. package/dest/key_store/local_key_store.js +83 -0
  29. package/dest/key_store/node_keystore_adapter.d.ts +151 -0
  30. package/dest/key_store/node_keystore_adapter.d.ts.map +1 -0
  31. package/dest/key_store/node_keystore_adapter.js +330 -0
  32. package/dest/key_store/web3signer_key_store.d.ts +74 -0
  33. package/dest/key_store/web3signer_key_store.d.ts.map +1 -0
  34. package/dest/key_store/web3signer_key_store.js +147 -0
  35. package/dest/metrics.d.ts +31 -0
  36. package/dest/metrics.d.ts.map +1 -0
  37. package/dest/metrics.js +101 -0
  38. package/dest/proposal_handler.d.ts +188 -0
  39. package/dest/proposal_handler.d.ts.map +1 -0
  40. package/dest/proposal_handler.js +1438 -0
  41. package/dest/streaming_inbox_checks.d.ts +103 -0
  42. package/dest/streaming_inbox_checks.d.ts.map +1 -0
  43. package/dest/streaming_inbox_checks.js +112 -0
  44. package/dest/validator.d.ts +137 -0
  45. package/dest/validator.d.ts.map +1 -0
  46. package/dest/validator.js +771 -0
  47. package/package.json +110 -0
  48. package/src/checkpoint_builder.ts +449 -0
  49. package/src/config.ts +130 -0
  50. package/src/duties/validation_service.ts +224 -0
  51. package/src/factory.ts +95 -0
  52. package/src/index.ts +6 -0
  53. package/src/key_store/ha_key_store.ts +268 -0
  54. package/src/key_store/index.ts +5 -0
  55. package/src/key_store/interface.ts +120 -0
  56. package/src/key_store/local_key_store.ts +104 -0
  57. package/src/key_store/node_keystore_adapter.ts +397 -0
  58. package/src/key_store/web3signer_key_store.ts +188 -0
  59. package/src/metrics.ts +150 -0
  60. package/src/proposal_handler.ts +1598 -0
  61. package/src/streaming_inbox_checks.ts +198 -0
  62. package/src/validator.ts +1125 -0
@@ -0,0 +1,120 @@
1
+ import type { Buffer32 } from '@aztec-labs/foundation/buffer';
2
+ import type { EthAddress } from '@aztec-labs/foundation/eth-address';
3
+ import type { Signature } from '@aztec-labs/foundation/eth-signature';
4
+ import type { EthRemoteSignerConfig } from '@aztec-labs/node-keystore';
5
+ import type { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
6
+ import type { SigningContext } from '@aztec-labs/validator-ha-signer/types';
7
+ import type { TypedDataDefinition } from 'viem';
8
+
9
+ /** Key Store
10
+ *
11
+ * A keystore interface that can be replaced with a local keystore / remote signer service
12
+ */
13
+ export interface ValidatorKeyStore {
14
+ /**
15
+ * Get the address of a signer by index
16
+ *
17
+ * @param index - The index of the signer
18
+ * @returns the address
19
+ */
20
+ getAddress(index: number): EthAddress;
21
+
22
+ /**
23
+ * Get all addresses
24
+ *
25
+ * @returns all addresses
26
+ */
27
+ getAddresses(): EthAddress[];
28
+
29
+ /**
30
+ * Sign typed data with all keystore private keys
31
+ * @param typedData - The complete EIP-712 typed data structure
32
+ * @param context - Signing context for HA slashing protection
33
+ * @returns signatures (when context provided with HA, only successfully claimed signatures are returned)
34
+ */
35
+ signTypedData(typedData: TypedDataDefinition, context: SigningContext): Promise<Signature[]>;
36
+
37
+ /**
38
+ * Sign typed data with a specific address's private key
39
+ * @param address - The address of the signer to use
40
+ * @param typedData - The complete EIP-712 typed data structure
41
+ * @param context - Signing context for HA slashing protection
42
+ * @returns signature
43
+ */
44
+ signTypedDataWithAddress(
45
+ address: EthAddress,
46
+ typedData: TypedDataDefinition,
47
+ context: SigningContext,
48
+ ): Promise<Signature>;
49
+
50
+ /**
51
+ * Flavor of sign message that followed EIP-712 eth signed message prefix
52
+ * Note: this is only required when we are using ecdsa signatures over secp256k1
53
+ *
54
+ * @param message - The message to sign.
55
+ * @param context - Signing context for HA slashing protection
56
+ * @returns The signatures (when context provided with HA, only successfully claimed signatures are returned).
57
+ */
58
+ signMessage(message: Buffer32, context: SigningContext): Promise<Signature[]>;
59
+
60
+ /**
61
+ * Sign a message with a specific address's private key
62
+ * @param address - The address of the signer to use
63
+ * @param message - The message to sign
64
+ * @param context - Signing context for HA slashing protection
65
+ * @returns signature
66
+ */
67
+ signMessageWithAddress(address: EthAddress, message: Buffer32, context: SigningContext): Promise<Signature>;
68
+ }
69
+
70
+ /**
71
+ * Extended ValidatorKeyStore interface that supports the new keystore configuration model
72
+ * with role-based address management (attester, coinbase, publisher, fee recipient)
73
+ */
74
+ export interface ExtendedValidatorKeyStore extends ValidatorKeyStore {
75
+ /**
76
+ * Get all attester addresses (maps to existing getAddresses())
77
+ * @returns all attester addresses
78
+ */
79
+ getAttesterAddresses(): EthAddress[];
80
+
81
+ /**
82
+ * Get the coinbase address for a specific attester
83
+ * Falls back to the attester address if not set
84
+ * @param attesterAddress - The attester address to find the coinbase for
85
+ * @returns the coinbase address
86
+ */
87
+ getCoinbaseAddress(attesterAddress: EthAddress): EthAddress;
88
+
89
+ /**
90
+ * Get all publisher addresses for a specific attester (EOAs used for sending block proposal L1 txs)
91
+ * Falls back to the attester addresses if not set
92
+ * @param attesterAddress - The attester address to find the publishers for
93
+ * @returns all publisher addresses for this validator
94
+ */
95
+ getPublisherAddresses(attesterAddress: EthAddress): EthAddress[];
96
+
97
+ /**
98
+ * Get the fee recipient address for a specific attester
99
+ * @param attesterAddress - The attester address to find the fee recipient for
100
+ * @returns the fee recipient address
101
+ */
102
+ getFeeRecipient(attesterAddress: EthAddress): AztecAddress;
103
+
104
+ /**
105
+ * Get the remote signer configuration for a specific attester if available
106
+ * @param attesterAddress - The attester address to find the remote signer config for
107
+ * @returns the remote signer configuration or undefined
108
+ */
109
+ getRemoteSignerConfig(attesterAddress: EthAddress): EthRemoteSignerConfig | undefined;
110
+
111
+ /**
112
+ * Start the key store
113
+ */
114
+ start(): Promise<void>;
115
+
116
+ /**
117
+ * Stop the key store
118
+ */
119
+ stop(): Promise<void>;
120
+ }
@@ -0,0 +1,104 @@
1
+ import { Buffer32 } from '@aztec-labs/foundation/buffer';
2
+ import { Secp256k1Signer } from '@aztec-labs/foundation/crypto/secp256k1-signer';
3
+ import type { EthAddress } from '@aztec-labs/foundation/eth-address';
4
+ import type { Signature } from '@aztec-labs/foundation/eth-signature';
5
+ import type { SigningContext } from '@aztec-labs/validator-ha-signer/types';
6
+ import { type TypedDataDefinition, hashTypedData } from 'viem';
7
+
8
+ import type { ValidatorKeyStore } from './interface.js';
9
+
10
+ /**
11
+ * Local Key Store
12
+ *
13
+ * An implementation of the Key store using in memory private keys.
14
+ */
15
+ export class LocalKeyStore implements ValidatorKeyStore {
16
+ private signers: Secp256k1Signer[];
17
+ private signersByAddress: Map<`0x${string}`, Secp256k1Signer>;
18
+
19
+ constructor(privateKeys: Buffer32[]) {
20
+ this.signers = privateKeys.map(privateKey => new Secp256k1Signer(privateKey));
21
+ this.signersByAddress = new Map(this.signers.map(signer => [signer.address.toString(), signer]));
22
+ }
23
+
24
+ /**
25
+ * Get the address of a signer by index
26
+ *
27
+ * @param index - The index of the signer
28
+ * @returns the address
29
+ */
30
+ public getAddress(index: number): EthAddress {
31
+ if (index >= this.signers.length) {
32
+ throw new Error(`Index ${index} is out of bounds.`);
33
+ }
34
+ return this.signers[index].address;
35
+ }
36
+
37
+ /**
38
+ * Get the addresses of all signers
39
+ *
40
+ * @returns the addresses
41
+ */
42
+ public getAddresses(): EthAddress[] {
43
+ return this.signers.map(signer => signer.address);
44
+ }
45
+
46
+ /**
47
+ * Sign a message with all keystore private keys
48
+ * @param typedData - The complete EIP-712 typed data structure (domain, types, primaryType, message)
49
+ * @param _context - Signing context (ignored by LocalKeyStore, used for HA protection)
50
+ * @return signature
51
+ */
52
+ public signTypedData(typedData: TypedDataDefinition, _context: SigningContext): Promise<Signature[]> {
53
+ const digest = hashTypedData(typedData);
54
+ return Promise.all(this.signers.map(signer => signer.sign(Buffer32.fromString(digest))));
55
+ }
56
+
57
+ /**
58
+ * Sign a message with a specific address's private key
59
+ * @param address - The address of the signer to use
60
+ * @param typedData - The complete EIP-712 typed data structure (domain, types, primaryType, message)
61
+ * @param _context - Signing context (ignored by LocalKeyStore, used for HA protection)
62
+ * @returns signature for the specified address
63
+ * @throws Error if the address is not found in the keystore
64
+ */
65
+ public signTypedDataWithAddress(
66
+ address: EthAddress,
67
+ typedData: TypedDataDefinition,
68
+ _context: SigningContext,
69
+ ): Promise<Signature> {
70
+ const signer = this.signersByAddress.get(address.toString());
71
+ if (!signer) {
72
+ throw new Error(`No signer found for address ${address.toString()}`);
73
+ }
74
+ const digest = hashTypedData(typedData);
75
+ return Promise.resolve(signer.sign(Buffer32.fromString(digest)));
76
+ }
77
+
78
+ /**
79
+ * Sign a message using eth_sign with all keystore private keys
80
+ *
81
+ * @param message - The message to sign
82
+ * @param _context - Signing context (ignored by LocalKeyStore, used for HA protection)
83
+ * @return signatures
84
+ */
85
+ public signMessage(message: Buffer32, _context: SigningContext): Promise<Signature[]> {
86
+ return Promise.all(this.signers.map(signer => signer.signMessage(message)));
87
+ }
88
+
89
+ /**
90
+ * Sign a message using eth_sign with a specific address's private key
91
+ * @param address - The address of the signer to use
92
+ * @param message - The message to sign
93
+ * @param _context - Signing context (ignored by LocalKeyStore, used for HA protection)
94
+ * @returns signature for the specified address
95
+ * @throws Error if the address is not found in the keystore
96
+ */
97
+ public signMessageWithAddress(address: EthAddress, message: Buffer32, _context: SigningContext): Promise<Signature> {
98
+ const signer = this.signersByAddress.get(address.toString());
99
+ if (!signer) {
100
+ throw new Error(`No signer found for address ${address.toString()}`);
101
+ }
102
+ return Promise.resolve(signer.signMessage(message));
103
+ }
104
+ }
@@ -0,0 +1,397 @@
1
+ import type { EthSigner } from '@aztec-labs/ethereum/eth-signer';
2
+ import type { Buffer32 } from '@aztec-labs/foundation/buffer';
3
+ import { EthAddress } from '@aztec-labs/foundation/eth-address';
4
+ import type { Signature } from '@aztec-labs/foundation/eth-signature';
5
+ import { KeystoreManager, loadKeystoreFile } from '@aztec-labs/node-keystore';
6
+ import type { EthRemoteSignerConfig } from '@aztec-labs/node-keystore';
7
+ import { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
8
+ import { InvalidValidatorPrivateKeyError } from '@aztec-labs/stdlib/validators';
9
+ import type { SigningContext } from '@aztec-labs/validator-ha-signer/types';
10
+ import type { TypedDataDefinition } from 'viem';
11
+ import { privateKeyToAccount } from 'viem/accounts';
12
+
13
+ import type { ExtendedValidatorKeyStore } from './interface.js';
14
+
15
+ type AddressHex = string;
16
+ type ValidatorIndex = number;
17
+
18
+ interface ValidatorCache {
19
+ attesters: EthSigner[];
20
+ publishers: EthSigner[];
21
+ all: EthSigner[];
22
+ byAddress: Map<AddressHex, EthSigner>; // all signers, any role
23
+ attesterSet: Set<AddressHex>; // attester addresses only
24
+ }
25
+
26
+ export class NodeKeystoreAdapter implements ExtendedValidatorKeyStore {
27
+ private readonly keystoreManager: KeystoreManager;
28
+
29
+ // Per-validator cache (lazy)
30
+ private readonly validators = new Map<ValidatorIndex, ValidatorCache>();
31
+
32
+ private readonly addressIndex = new Map<AddressHex, { signer: EthSigner; validatorIndex: ValidatorIndex }>();
33
+
34
+ private constructor(keystoreManager: KeystoreManager) {
35
+ this.keystoreManager = keystoreManager;
36
+ }
37
+
38
+ /**
39
+ * Create an adapter from a keystore JSON file on disk.
40
+ * @param keystoreFilePath Absolute or relative path to a keystore JSON file
41
+ * @returns A configured NodeKeystoreAdapter instance
42
+ * @throws Error when the file fails schema validation or cannot be read
43
+ */
44
+ static fromKeystoreFile(keystoreFilePath: string): NodeKeystoreAdapter {
45
+ const keystoreConfig = loadKeystoreFile(keystoreFilePath);
46
+ return NodeKeystoreAdapter.fromKeystoreConfig(keystoreConfig);
47
+ }
48
+
49
+ /**
50
+ * Create an adapter from an in-memory keystore-like object.
51
+ * Validates resolved duplicate attester addresses across validators and sources.
52
+ * @param keystoreConfig Parsed config object (typically result of loadKeystoreFile)
53
+ * @returns A configured NodeKeystoreAdapter instance
54
+ * @throws Error when resolved duplicate attester addresses are detected
55
+ */
56
+ static fromKeystoreConfig(keystoreConfig: unknown): NodeKeystoreAdapter {
57
+ const keystoreManager = new KeystoreManager(keystoreConfig as any);
58
+ // Validate resolved attester addresses (covers JSON V3 and mnemonic duplicates across validators)
59
+ keystoreManager.validateResolvedUniqueAttesterAddresses();
60
+ return new NodeKeystoreAdapter(keystoreManager);
61
+ }
62
+
63
+ /**
64
+ * Build an adapter directly from a list of validator private keys.
65
+ * Each key becomes a separate validator using the same key for attester and publisher,
66
+ * coinbase defaults to the derived EOA address, and feeRecipient is a 32-byte padded address.
67
+ * Note: Fee recipient is a temporary placeholder, replace with actual fee recipient when implemented.
68
+ */
69
+ static fromPrivateKeys(privateKeys: string[]): NodeKeystoreAdapter {
70
+ // Minimal validation: 0x + 64 hex
71
+ const isPk = (s: string) => /^0x[0-9a-fA-F]{64}$/.test(s);
72
+ for (const pk of privateKeys) {
73
+ if (!isPk(pk)) {
74
+ throw new InvalidValidatorPrivateKeyError();
75
+ }
76
+ }
77
+
78
+ const validators = privateKeys.map(pk => {
79
+ const account = privateKeyToAccount(pk as `0x${string}`);
80
+ const ethAddress = account.address as `0x${string}`;
81
+ // TODO: Temporary fee recipient, replace with actual fee recipient when implemented
82
+ const feeRecipient = `0x${ethAddress.slice(2).padStart(64, '0')}` as `0x${string}`;
83
+ return {
84
+ attester: pk as `0x${string}`,
85
+ publisher: pk as `0x${string}`,
86
+ coinbase: ethAddress,
87
+ feeRecipient,
88
+ };
89
+ });
90
+
91
+ const cfg = { schemaVersion: 1, validators } as const;
92
+ return NodeKeystoreAdapter.fromKeystoreConfig(cfg);
93
+ }
94
+
95
+ /**
96
+ * Build an adapter for a Web3Signer setup by providing the signer URL and the EOA addresses.
97
+ * Each address becomes a separate validator; attester and publisher point to the same remote signer entry.
98
+ * Note: Fee recipient is a temporary placeholder, replace with actual fee recipient when implemented.
99
+ */
100
+ static fromWeb3Signer(web3SignerUrl: string, addresses: EthAddress[]): NodeKeystoreAdapter {
101
+ const validators = addresses.map(address => {
102
+ const ethAddress = address.toString() as `0x${string}`;
103
+ // TODO: Temporary fee recipient, replace with actual fee recipient when implemented
104
+ const feeRecipient = `0x${ethAddress.slice(2).padStart(64, '0')}` as `0x${string}`;
105
+ return {
106
+ attester: { address: ethAddress, remoteSignerUrl: web3SignerUrl },
107
+ publisher: { address: ethAddress, remoteSignerUrl: web3SignerUrl },
108
+ coinbase: ethAddress,
109
+ feeRecipient,
110
+ };
111
+ });
112
+
113
+ const cfg = { schemaVersion: 1, validators } as const;
114
+ return NodeKeystoreAdapter.fromKeystoreConfig(cfg);
115
+ }
116
+
117
+ static fromKeyStoreManager(manager: KeystoreManager): NodeKeystoreAdapter {
118
+ return new NodeKeystoreAdapter(manager);
119
+ }
120
+
121
+ /**
122
+ * Normalize address keys to lowercase hex strings for map/set usage.
123
+ */
124
+ private static key(addr: EthAddress | AddressHex): AddressHex {
125
+ return typeof addr === 'string' ? addr.toLowerCase() : addr.toString().toLowerCase();
126
+ }
127
+
128
+ /**
129
+ * Ensure per-validator signer cache exists; build it by creating
130
+ * attester/publisher signers and populating indices when missing.
131
+ * @param validatorIndex Index of the validator in the keystore
132
+ * @returns The cached validator entry
133
+ */
134
+ private ensureValidator(validatorIndex: number): ValidatorCache {
135
+ const cached = this.validators.get(validatorIndex);
136
+ if (cached) {
137
+ return cached;
138
+ }
139
+
140
+ const attesters = this.keystoreManager.createAttesterSigners(validatorIndex);
141
+ const publishers = this.keystoreManager.createPublisherSigners(validatorIndex);
142
+
143
+ // Build 'all' + indices
144
+ const byAddress = new Map<AddressHex, EthSigner>();
145
+ const attesterSet = new Set<AddressHex>();
146
+
147
+ for (const s of attesters) {
148
+ const k = NodeKeystoreAdapter.key(s.address);
149
+ byAddress.set(k, s);
150
+ attesterSet.add(k);
151
+ }
152
+ for (const s of publishers) {
153
+ const k = NodeKeystoreAdapter.key(s.address);
154
+ if (!byAddress.has(k)) {
155
+ byAddress.set(k, s);
156
+ }
157
+ }
158
+
159
+ const all = Array.from(byAddress.values());
160
+
161
+ // Populate global index
162
+ for (const [k, signer] of byAddress.entries()) {
163
+ this.addressIndex.set(k, { signer, validatorIndex });
164
+ }
165
+
166
+ const built: ValidatorCache = { attesters, publishers, all, byAddress, attesterSet };
167
+ this.validators.set(validatorIndex, built);
168
+ return built;
169
+ }
170
+
171
+ /**
172
+ * Iterate all validator indices in the keystore manager.
173
+ */
174
+ private *validatorIndices(): Iterable<number> {
175
+ const n = this.keystoreManager.getValidatorCount();
176
+ for (let i = 0; i < n; i++) {
177
+ yield i;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Find the validator index that contains the given attester address.
183
+ * @param attesterAddress Address to locate
184
+ * @returns Validator index
185
+ * @throws Error when no validator contains the attester
186
+ */
187
+ private findValidatorIndexForAttester(attesterAddress: EthAddress): number {
188
+ const key = NodeKeystoreAdapter.key(attesterAddress);
189
+
190
+ // Fast path: if we’ve already cached any validator that includes this as attester
191
+ for (const i of this.validatorIndices()) {
192
+ const v = this.ensureValidator(i);
193
+ if (v.attesterSet.has(key)) {
194
+ return i;
195
+ }
196
+ }
197
+
198
+ throw new Error(`Attester address ${attesterAddress.toString()} not found in any validator configuration`);
199
+ }
200
+
201
+ /**
202
+ * Get attester address by flat index across all validators' attester sets.
203
+ * @param index Zero-based flat index across all attesters
204
+ * @returns EthAddress for the indexed attester
205
+ * @throws Error when index is out of bounds
206
+ */
207
+ getAddress(index: number): EthAddress {
208
+ const all = this.getAddresses();
209
+ if (index < 0 || index >= all.length) {
210
+ throw new Error(`Index ${index} is out of bounds (0..${all.length - 1}).`);
211
+ }
212
+ return all[index];
213
+ }
214
+
215
+ /**
216
+ * Get all attester addresses across validators (legacy-compatible view).
217
+ */
218
+ getAddresses(): EthAddress[] {
219
+ const out: EthAddress[] = [];
220
+ for (const i of this.validatorIndices()) {
221
+ const v = this.ensureValidator(i);
222
+ // attester addresses only for backward compatibility
223
+ for (const s of v.attesters) {
224
+ out.push(s.address);
225
+ }
226
+ }
227
+ return out;
228
+ }
229
+
230
+ /**
231
+ * Sign typed data with all attester signers across validators.
232
+ * @param typedData EIP-712 typed data
233
+ * @param _context Signing context (ignored by NodeKeystoreAdapter, used for HA protection)
234
+ * @returns Array of signatures in validator order, flattened
235
+ */
236
+ async signTypedData(typedData: TypedDataDefinition, _context: SigningContext): Promise<Signature[]> {
237
+ const jobs: Promise<Signature>[] = [];
238
+ for (const i of this.validatorIndices()) {
239
+ const v = this.ensureValidator(i);
240
+ for (const s of v.attesters) {
241
+ jobs.push(this.keystoreManager.signTypedData(s, typedData));
242
+ }
243
+ }
244
+ return await Promise.all(jobs);
245
+ }
246
+
247
+ /**
248
+ * Sign a message with all attester signers across validators.
249
+ * @param message 32-byte message (already hashed/padded as needed)
250
+ * @param _context Signing context (ignored by NodeKeystoreAdapter, used for HA protection)
251
+ * @returns Array of signatures in validator order, flattened
252
+ */
253
+ async signMessage(message: Buffer32, _context: SigningContext): Promise<Signature[]> {
254
+ const jobs: Promise<Signature>[] = [];
255
+ for (const i of this.validatorIndices()) {
256
+ const v = this.ensureValidator(i);
257
+ for (const s of v.attesters) {
258
+ jobs.push(this.keystoreManager.signMessage(s, message));
259
+ }
260
+ }
261
+ return await Promise.all(jobs);
262
+ }
263
+
264
+ /**
265
+ * Sign typed data with a signer identified by address (any role).
266
+ * Hydrates caches on-demand when the address is first seen.
267
+ * @param address Address to sign with
268
+ * @param typedData EIP-712 typed data
269
+ * @param _context Signing context (ignored by NodeKeystoreAdapter, used for HA protection)
270
+ * @returns Signature from the signer matching the address
271
+ * @throws Error when no signer exists for the address
272
+ */
273
+ async signTypedDataWithAddress(
274
+ address: EthAddress,
275
+ typedData: TypedDataDefinition,
276
+ _context: SigningContext,
277
+ ): Promise<Signature> {
278
+ const entry = this.addressIndex.get(NodeKeystoreAdapter.key(address));
279
+ if (entry) {
280
+ return await this.keystoreManager.signTypedData(entry.signer, typedData);
281
+ }
282
+
283
+ // If not in global index yet, lazily hydrate all validators once and retry
284
+ for (const i of this.validatorIndices()) {
285
+ this.ensureValidator(i);
286
+ }
287
+ const second = this.addressIndex.get(NodeKeystoreAdapter.key(address));
288
+ if (second) {
289
+ return await this.keystoreManager.signTypedData(second.signer, typedData);
290
+ }
291
+
292
+ throw new Error(`No signer found for address ${address.toString()}`);
293
+ }
294
+
295
+ /**
296
+ * Sign a message with a signer identified by address (any role).
297
+ * Hydrates caches on-demand when the address is first seen.
298
+ * @param address Address to sign with
299
+ * @param message 32-byte message
300
+ * @param _context Signing context (ignored by NodeKeystoreAdapter, used for HA protection)
301
+ * @returns Signature from the signer matching the address
302
+ * @throws Error when no signer exists for the address
303
+ */
304
+ async signMessageWithAddress(address: EthAddress, message: Buffer32, _context: SigningContext): Promise<Signature> {
305
+ const entry = this.addressIndex.get(NodeKeystoreAdapter.key(address));
306
+ if (entry) {
307
+ return await this.keystoreManager.signMessage(entry.signer, message);
308
+ }
309
+
310
+ for (const i of this.validatorIndices()) {
311
+ this.ensureValidator(i);
312
+ }
313
+ const second = this.addressIndex.get(NodeKeystoreAdapter.key(address));
314
+ if (second) {
315
+ return await this.keystoreManager.signMessage(second.signer, message);
316
+ }
317
+
318
+ throw new Error(`No signer found for address ${address.toString()}`);
319
+ }
320
+
321
+ /**
322
+ * Get all attester addresses across validators (alias of getAddresses).
323
+ */
324
+ getAttesterAddresses(): EthAddress[] {
325
+ return this.getAddresses();
326
+ }
327
+
328
+ /**
329
+ * Get the effective coinbase address for the validator that contains the given attester.
330
+ * @param attesterAddress Address of an attester belonging to the validator
331
+ * @returns Coinbase EthAddress
332
+ */
333
+ getCoinbaseAddress(attesterAddress: EthAddress): EthAddress {
334
+ const validatorIndex = this.findValidatorIndexForAttester(attesterAddress);
335
+ return this.keystoreManager.getCoinbaseAddress(validatorIndex, attesterAddress);
336
+ }
337
+
338
+ /**
339
+ * Get the publisher addresses for the validator that contains the given attester.
340
+ * @param attesterAddress Address of an attester belonging to the validator
341
+ * @returns Array of publisher addresses
342
+ */
343
+ getPublisherAddresses(attesterAddress: EthAddress): EthAddress[] {
344
+ const validatorIndex = this.findValidatorIndexForAttester(attesterAddress);
345
+ const v = this.ensureValidator(validatorIndex);
346
+ return v.publishers.map(s => s.address);
347
+ }
348
+
349
+ getAttestorForPublisher(publisherAddress: EthAddress): EthAddress {
350
+ const attestorAddresses = this.getAttesterAddresses();
351
+ for (const attestor of attestorAddresses) {
352
+ const publishers = this.getPublisherAddresses(attestor);
353
+ const found = publishers.some(publisher => publisher.equals(publisherAddress));
354
+ if (found) {
355
+ return attestor;
356
+ }
357
+ }
358
+ // Could not find an attestor for this publisher
359
+ throw new Error(`Failed to find attestor for publisher ${publisherAddress.toString()}`);
360
+ }
361
+
362
+ /**
363
+ * Get the fee recipient for the validator that contains the given attester.
364
+ * @param attesterAddress Address of an attester belonging to the validator
365
+ * @returns Fee recipient as AztecAddress
366
+ */
367
+ getFeeRecipient(attesterAddress: EthAddress): AztecAddress {
368
+ const validatorIndex = this.findValidatorIndexForAttester(attesterAddress);
369
+ return this.keystoreManager.getFeeRecipient(validatorIndex);
370
+ }
371
+
372
+ /**
373
+ * Get the effective remote signer configuration for the attester.
374
+ * Precedence: account-level override > validator-level override > file-level default.
375
+ * Returns undefined for local signers (private key / JSON-V3 / mnemonic).
376
+ * @param attesterAddress Address of an attester belonging to the validator
377
+ * @returns Effective remote signer configuration or undefined
378
+ */
379
+ getRemoteSignerConfig(attesterAddress: EthAddress): EthRemoteSignerConfig | undefined {
380
+ const validatorIndex = this.findValidatorIndexForAttester(attesterAddress);
381
+ return this.keystoreManager.getEffectiveRemoteSignerConfig(validatorIndex, attesterAddress);
382
+ }
383
+
384
+ /**
385
+ * Start the key store - no-op
386
+ */
387
+ start(): Promise<void> {
388
+ return Promise.resolve();
389
+ }
390
+
391
+ /**
392
+ * Stop the key store - no-op
393
+ */
394
+ stop(): Promise<void> {
395
+ return Promise.resolve();
396
+ }
397
+ }