@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/config.d.ts +1 -1
- package/dest/index.d.ts +1 -1
- package/dest/keystore_manager.d.ts +19 -7
- package/dest/keystore_manager.d.ts.map +1 -1
- package/dest/keystore_manager.js +198 -125
- package/dest/loader.d.ts +1 -1
- package/dest/loader.d.ts.map +1 -1
- package/dest/loader.js +93 -18
- package/dest/schemas.d.ts +2893 -990
- package/dest/schemas.d.ts.map +1 -1
- package/dest/schemas.js +102 -40
- package/dest/signer.d.ts +10 -17
- package/dest/signer.d.ts.map +1 -1
- package/dest/signer.js +58 -6
- package/dest/types.d.ts +46 -27
- package/dest/types.d.ts.map +1 -1
- package/dest/types.js +1 -1
- package/package.json +10 -7
- package/src/keystore_manager.ts +242 -145
- package/src/loader.ts +95 -11
- package/src/schemas.ts +135 -58
- package/src/signer.ts +84 -11
- package/src/types.ts +50 -32
package/src/loader.ts
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles loading and parsing keystore configuration files.
|
|
5
5
|
*/
|
|
6
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
|
+
import type { Hex } from '@aztec/foundation/string';
|
|
7
9
|
|
|
8
10
|
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
9
11
|
import { extname, join } from 'path';
|
|
12
|
+
import { privateKeyToAddress } from 'viem/accounts';
|
|
10
13
|
|
|
11
14
|
import { keystoreSchema } from './schemas.js';
|
|
12
15
|
import type { EthAccounts, KeyStore } from './types.js';
|
|
@@ -39,7 +42,7 @@ export function loadKeystoreFile(filePath: string): KeyStore {
|
|
|
39
42
|
const content = readFileSync(filePath, 'utf-8');
|
|
40
43
|
|
|
41
44
|
// Parse JSON and validate with Zod schema (following Aztec patterns)
|
|
42
|
-
return keystoreSchema.parse(JSON.parse(content))
|
|
45
|
+
return keystoreSchema.parse(JSON.parse(content));
|
|
43
46
|
} catch (error) {
|
|
44
47
|
if (error instanceof SyntaxError) {
|
|
45
48
|
throw new KeyStoreLoadError('Invalid JSON format', filePath, error);
|
|
@@ -205,12 +208,18 @@ export function mergeKeystores(keystores: KeyStore[]): KeyStore {
|
|
|
205
208
|
// Track attester addresses to prevent duplicates
|
|
206
209
|
const attesterAddresses = new Set<string>();
|
|
207
210
|
|
|
211
|
+
// Determine schema version: use v2 if any input is v2
|
|
212
|
+
const schemaVersion = keystores.some(ks => ks.schemaVersion === 2) ? 2 : 1;
|
|
213
|
+
|
|
208
214
|
const merged: KeyStore = {
|
|
209
|
-
schemaVersion
|
|
215
|
+
schemaVersion,
|
|
210
216
|
validators: [],
|
|
211
217
|
slasher: undefined,
|
|
212
218
|
remoteSigner: undefined,
|
|
213
219
|
prover: undefined,
|
|
220
|
+
publisher: undefined,
|
|
221
|
+
coinbase: undefined,
|
|
222
|
+
feeRecipient: undefined,
|
|
214
223
|
};
|
|
215
224
|
|
|
216
225
|
for (let i = 0; i < keystores.length; i++) {
|
|
@@ -220,8 +229,9 @@ export function mergeKeystores(keystores: KeyStore[]): KeyStore {
|
|
|
220
229
|
if (keystore.validators) {
|
|
221
230
|
for (const validator of keystore.validators) {
|
|
222
231
|
// Check for duplicate attester addresses
|
|
223
|
-
const attesterKeys =
|
|
224
|
-
for (
|
|
232
|
+
const attesterKeys = extractAttesterAddresses(validator.attester);
|
|
233
|
+
for (let key of attesterKeys) {
|
|
234
|
+
key = key.toLowerCase();
|
|
225
235
|
if (attesterAddresses.has(key)) {
|
|
226
236
|
throw new KeyStoreLoadError(
|
|
227
237
|
`Duplicate attester address ${key} found across keystore files`,
|
|
@@ -230,8 +240,18 @@ export function mergeKeystores(keystores: KeyStore[]): KeyStore {
|
|
|
230
240
|
}
|
|
231
241
|
attesterAddresses.add(key);
|
|
232
242
|
}
|
|
243
|
+
|
|
244
|
+
// When merging v1 validators into a v2+ result, preserve original fallback behavior
|
|
245
|
+
// by explicitly setting publisher/coinbase/feeRecipient if they're missing
|
|
246
|
+
if (keystore.schemaVersion !== schemaVersion) {
|
|
247
|
+
throw new KeyStoreLoadError(
|
|
248
|
+
`Cannot merge keystores with different schema versions: ${keystore.schemaVersion} and ${schemaVersion}`,
|
|
249
|
+
`keystores[${i}].schemaVersion`,
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
merged.validators!.push(validator);
|
|
253
|
+
}
|
|
233
254
|
}
|
|
234
|
-
merged.validators!.push(...keystore.validators);
|
|
235
255
|
}
|
|
236
256
|
|
|
237
257
|
// Merge slasher (accumulate all)
|
|
@@ -264,6 +284,45 @@ export function mergeKeystores(keystores: KeyStore[]): KeyStore {
|
|
|
264
284
|
}
|
|
265
285
|
merged.prover = keystore.prover;
|
|
266
286
|
}
|
|
287
|
+
|
|
288
|
+
// Merge top-level publisher (accumulate all, unless conflicting MnemonicConfigs)
|
|
289
|
+
if (keystore.publisher) {
|
|
290
|
+
if (!merged.publisher) {
|
|
291
|
+
merged.publisher = keystore.publisher;
|
|
292
|
+
} else {
|
|
293
|
+
const isMnemonic = (accounts: EthAccounts): boolean =>
|
|
294
|
+
typeof accounts === 'object' && accounts !== null && 'mnemonic' in accounts;
|
|
295
|
+
|
|
296
|
+
// If either is a mnemonic, warn and use last one (can't merge mnemonics)
|
|
297
|
+
if (isMnemonic(merged.publisher) || isMnemonic(keystore.publisher)) {
|
|
298
|
+
logger.warn(
|
|
299
|
+
'Multiple default publisher configurations found with mnemonic, using the last one (cannot merge mnemonics)',
|
|
300
|
+
);
|
|
301
|
+
merged.publisher = keystore.publisher;
|
|
302
|
+
} else {
|
|
303
|
+
// Both are non-mnemonic, accumulate them
|
|
304
|
+
const toArray = (accounts: EthAccounts): unknown[] => (Array.isArray(accounts) ? accounts : [accounts]);
|
|
305
|
+
const combined = [...toArray(merged.publisher), ...toArray(keystore.publisher)];
|
|
306
|
+
merged.publisher = combined as unknown as EthAccounts;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Merge top-level coinbase (last one wins, but warn about conflicts)
|
|
312
|
+
if (keystore.coinbase) {
|
|
313
|
+
if (merged.coinbase) {
|
|
314
|
+
logger.warn('Multiple default coinbase addresses found, using the last one');
|
|
315
|
+
}
|
|
316
|
+
merged.coinbase = keystore.coinbase;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Merge top-level feeRecipient (last one wins, but warn about conflicts)
|
|
320
|
+
if (keystore.feeRecipient) {
|
|
321
|
+
if (merged.feeRecipient) {
|
|
322
|
+
logger.warn('Multiple default feeRecipient addresses found, using the last one');
|
|
323
|
+
}
|
|
324
|
+
merged.feeRecipient = keystore.feeRecipient;
|
|
325
|
+
}
|
|
267
326
|
}
|
|
268
327
|
|
|
269
328
|
// Clean up empty arrays
|
|
@@ -284,18 +343,43 @@ export function mergeKeystores(keystores: KeyStore[]): KeyStore {
|
|
|
284
343
|
* @param attester The attester configuration in any supported shape.
|
|
285
344
|
* @returns Array of string keys used to detect duplicates.
|
|
286
345
|
*/
|
|
287
|
-
function
|
|
346
|
+
function extractAttesterAddresses(attester: unknown): string[] {
|
|
347
|
+
// String forms (private key or other) - return as-is for coarse uniqueness
|
|
288
348
|
if (typeof attester === 'string') {
|
|
289
|
-
|
|
349
|
+
if (attester.length === 66) {
|
|
350
|
+
return [privateKeyToAddress(attester as Hex<32>)];
|
|
351
|
+
} else {
|
|
352
|
+
return [attester];
|
|
353
|
+
}
|
|
290
354
|
}
|
|
291
355
|
|
|
356
|
+
// Arrays of attester items
|
|
292
357
|
if (Array.isArray(attester)) {
|
|
293
|
-
|
|
358
|
+
const keys: string[] = [];
|
|
359
|
+
for (const item of attester) {
|
|
360
|
+
keys.push(...extractAttesterAddresses(item));
|
|
361
|
+
}
|
|
362
|
+
return keys;
|
|
294
363
|
}
|
|
295
364
|
|
|
296
|
-
if (attester && typeof attester === 'object'
|
|
297
|
-
|
|
365
|
+
if (attester && typeof attester === 'object') {
|
|
366
|
+
if (attester instanceof EthAddress) {
|
|
367
|
+
return [attester.toString()];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const obj = attester as Record<string, unknown>;
|
|
371
|
+
|
|
372
|
+
// New shape: { eth: EthAccount, bls?: BLSAccount }
|
|
373
|
+
if ('eth' in obj) {
|
|
374
|
+
return extractAttesterAddresses(obj.eth);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Remote signer account object shape: { address, remoteSignerUrl?, ... }
|
|
378
|
+
if ('address' in obj) {
|
|
379
|
+
return [String((obj as any).address)];
|
|
380
|
+
}
|
|
298
381
|
}
|
|
299
382
|
|
|
300
|
-
|
|
383
|
+
// mnemonic, encrypted file just disable early duplicates checking
|
|
384
|
+
return [];
|
|
301
385
|
}
|
package/src/schemas.ts
CHANGED
|
@@ -1,98 +1,175 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Zod schemas for keystore validation using Aztec's validation functions
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { optional, schemas } from '@aztec/foundation/schemas';
|
|
5
5
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
6
6
|
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
|
|
9
|
+
import type { BLSPrivateKey, EthPrivateKey } from './types.js';
|
|
10
|
+
|
|
9
11
|
// Use Aztec's validation functions but return string types to match our TypeScript interfaces
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
+
export const ethPrivateKeySchema = z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid private key (must be 32 bytes with 0x prefix)')
|
|
15
|
+
.transform(s => s as EthPrivateKey);
|
|
16
|
+
export const blsPrivateKeySchema = z
|
|
12
17
|
.string()
|
|
13
|
-
.regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid private key (must be 32 bytes with 0x prefix)')
|
|
14
|
-
|
|
18
|
+
.regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid BLS private key (must be 32 bytes with 0x prefix)')
|
|
19
|
+
.transform(s => s as BLSPrivateKey);
|
|
15
20
|
const urlSchema = z.string().url('Invalid URL');
|
|
16
21
|
|
|
17
22
|
// Remote signer config schema
|
|
18
23
|
const remoteSignerConfigSchema = z.union([
|
|
19
24
|
urlSchema,
|
|
20
|
-
z
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
z
|
|
26
|
+
.object({
|
|
27
|
+
remoteSignerUrl: urlSchema,
|
|
28
|
+
certPath: optional(z.string()),
|
|
29
|
+
certPass: optional(z.string()),
|
|
30
|
+
})
|
|
31
|
+
.strict(),
|
|
25
32
|
]);
|
|
26
33
|
|
|
27
34
|
// Remote signer account schema
|
|
28
35
|
const remoteSignerAccountSchema = z.union([
|
|
29
|
-
|
|
30
|
-
z
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
schemas.EthAddress,
|
|
37
|
+
z
|
|
38
|
+
.object({
|
|
39
|
+
address: schemas.EthAddress,
|
|
40
|
+
remoteSignerUrl: urlSchema,
|
|
41
|
+
certPath: optional(z.string()),
|
|
42
|
+
certPass: optional(z.string()),
|
|
43
|
+
})
|
|
44
|
+
.strict(),
|
|
36
45
|
]);
|
|
37
46
|
|
|
38
|
-
// JSON V3
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
// Encrypted keystore file schema (used for both JSON V3 ETH keys and EIP-2335 BLS keys)
|
|
48
|
+
const encryptedKeyFileSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
path: z.string(),
|
|
51
|
+
password: optional(z.string()),
|
|
52
|
+
})
|
|
53
|
+
.strict();
|
|
43
54
|
|
|
44
55
|
// Mnemonic config schema
|
|
45
|
-
const mnemonicConfigSchema = z
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
const mnemonicConfigSchema = z
|
|
57
|
+
.object({
|
|
58
|
+
mnemonic: z.string().min(1, 'Mnemonic cannot be empty'),
|
|
59
|
+
addressIndex: z.number().int().min(0).default(0),
|
|
60
|
+
accountIndex: z.number().int().min(0).default(0),
|
|
61
|
+
addressCount: z.number().int().min(1).default(1),
|
|
62
|
+
accountCount: z.number().int().min(1).default(1),
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
52
65
|
|
|
53
66
|
// EthAccount schema
|
|
54
|
-
const ethAccountSchema = z.union([
|
|
55
|
-
ethPrivateKeySchema,
|
|
56
|
-
remoteSignerAccountSchema,
|
|
57
|
-
jsonKeyFileV3Schema,
|
|
58
|
-
mnemonicConfigSchema,
|
|
59
|
-
]);
|
|
67
|
+
const ethAccountSchema = z.union([ethPrivateKeySchema, remoteSignerAccountSchema, encryptedKeyFileSchema]);
|
|
60
68
|
|
|
61
69
|
// EthAccounts schema
|
|
62
|
-
const ethAccountsSchema = z.union([ethAccountSchema, z.array(ethAccountSchema)]);
|
|
70
|
+
const ethAccountsSchema = z.union([ethAccountSchema, z.array(ethAccountSchema), mnemonicConfigSchema]);
|
|
71
|
+
|
|
72
|
+
// BLSAccount schema
|
|
73
|
+
const blsAccountSchema = z.union([blsPrivateKeySchema, encryptedKeyFileSchema]);
|
|
74
|
+
|
|
75
|
+
// AttesterAccount schema: either EthAccount or { eth: EthAccount, bls?: BLSAccount }
|
|
76
|
+
const attesterAccountSchema = z.union([
|
|
77
|
+
ethAccountSchema,
|
|
78
|
+
z
|
|
79
|
+
.object({
|
|
80
|
+
eth: ethAccountSchema,
|
|
81
|
+
bls: optional(blsAccountSchema),
|
|
82
|
+
})
|
|
83
|
+
.strict(),
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
// AttesterAccounts schema: AttesterAccount | AttesterAccount[] | MnemonicConfig
|
|
87
|
+
const attesterAccountsSchema = z.union([attesterAccountSchema, z.array(attesterAccountSchema), mnemonicConfigSchema]);
|
|
63
88
|
|
|
64
89
|
// Prover keystore schema
|
|
65
90
|
const proverKeyStoreSchema = z.union([
|
|
66
91
|
ethAccountSchema,
|
|
67
|
-
z
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
z
|
|
93
|
+
.object({
|
|
94
|
+
id: schemas.EthAddress,
|
|
95
|
+
publisher: ethAccountsSchema,
|
|
96
|
+
})
|
|
97
|
+
.strict(),
|
|
71
98
|
]);
|
|
72
99
|
|
|
73
|
-
// Validator keystore schema
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
// Validator keystore schema for v1 (feeRecipient required)
|
|
101
|
+
const validatorKeyStoreSchemaV1 = z
|
|
102
|
+
.object({
|
|
103
|
+
attester: attesterAccountsSchema,
|
|
104
|
+
coinbase: optional(schemas.EthAddress),
|
|
105
|
+
publisher: optional(ethAccountsSchema),
|
|
106
|
+
feeRecipient: AztecAddress.schema,
|
|
107
|
+
remoteSigner: optional(remoteSignerConfigSchema),
|
|
108
|
+
fundingAccount: optional(ethAccountSchema),
|
|
109
|
+
})
|
|
110
|
+
.strict();
|
|
111
|
+
|
|
112
|
+
// Validator keystore schema for v2 (feeRecipient optional, can fall back to top-level)
|
|
113
|
+
const validatorKeyStoreSchemaV2 = z
|
|
114
|
+
.object({
|
|
115
|
+
attester: attesterAccountsSchema,
|
|
116
|
+
coinbase: optional(schemas.EthAddress),
|
|
117
|
+
publisher: optional(ethAccountsSchema),
|
|
118
|
+
feeRecipient: optional(AztecAddress.schema),
|
|
119
|
+
remoteSigner: optional(remoteSignerConfigSchema),
|
|
120
|
+
fundingAccount: optional(ethAccountSchema),
|
|
121
|
+
})
|
|
122
|
+
.strict();
|
|
123
|
+
|
|
124
|
+
// Schema v1 - original format
|
|
125
|
+
const keystoreSchemaV1 = z
|
|
85
126
|
.object({
|
|
86
127
|
schemaVersion: z.literal(1),
|
|
87
|
-
validators: z.array(
|
|
88
|
-
slasher: ethAccountsSchema
|
|
89
|
-
remoteSigner: remoteSignerConfigSchema
|
|
90
|
-
prover: proverKeyStoreSchema
|
|
91
|
-
fundingAccount: ethAccountSchema
|
|
128
|
+
validators: optional(z.array(validatorKeyStoreSchemaV1)),
|
|
129
|
+
slasher: optional(ethAccountsSchema),
|
|
130
|
+
remoteSigner: optional(remoteSignerConfigSchema),
|
|
131
|
+
prover: optional(proverKeyStoreSchema),
|
|
132
|
+
fundingAccount: optional(ethAccountSchema),
|
|
92
133
|
})
|
|
134
|
+
.strict()
|
|
93
135
|
.refine(data => data.validators || data.prover, {
|
|
94
136
|
message: 'Keystore must have at least validators or prover configuration',
|
|
95
137
|
path: ['root'],
|
|
96
138
|
});
|
|
97
139
|
|
|
98
|
-
|
|
140
|
+
// Schema v2 - adds top-level publisher, coinbase, feeRecipient
|
|
141
|
+
const keystoreSchemaV2 = z
|
|
142
|
+
.object({
|
|
143
|
+
schemaVersion: z.literal(2),
|
|
144
|
+
validators: optional(z.array(validatorKeyStoreSchemaV2)),
|
|
145
|
+
slasher: optional(ethAccountsSchema),
|
|
146
|
+
remoteSigner: optional(remoteSignerConfigSchema),
|
|
147
|
+
prover: optional(proverKeyStoreSchema),
|
|
148
|
+
fundingAccount: optional(ethAccountSchema),
|
|
149
|
+
publisher: optional(ethAccountsSchema),
|
|
150
|
+
coinbase: optional(schemas.EthAddress),
|
|
151
|
+
feeRecipient: optional(AztecAddress.schema),
|
|
152
|
+
})
|
|
153
|
+
.strict()
|
|
154
|
+
.refine(data => data.validators || data.prover, {
|
|
155
|
+
message: 'Keystore must have at least validators or prover configuration',
|
|
156
|
+
path: ['root'],
|
|
157
|
+
})
|
|
158
|
+
.refine(
|
|
159
|
+
data => {
|
|
160
|
+
// If validators are present, ensure each validator has a feeRecipient or there's a top-level feeRecipient
|
|
161
|
+
if (data.validators) {
|
|
162
|
+
const hasTopLevelFeeRecipient = !!data.feeRecipient;
|
|
163
|
+
const allValidatorsHaveFeeRecipient = data.validators.every(v => v.feeRecipient);
|
|
164
|
+
return hasTopLevelFeeRecipient || allValidatorsHaveFeeRecipient;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
message: 'Each validator must have a feeRecipient, or a top-level feeRecipient must be set for all validators',
|
|
170
|
+
path: ['feeRecipient'],
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Main keystore schema - accepts both v1 and v2
|
|
175
|
+
export const keystoreSchema = z.union([keystoreSchemaV1, keystoreSchemaV2]);
|
package/src/signer.ts
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Common interface for different signing backends (local, remote, encrypted)
|
|
5
5
|
*/
|
|
6
|
-
import type { EthSigner } from '@aztec/ethereum';
|
|
6
|
+
import type { EthSigner } from '@aztec/ethereum/eth-signer';
|
|
7
7
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
8
|
-
import {
|
|
8
|
+
import { randomBytes } from '@aztec/foundation/crypto/random';
|
|
9
|
+
import { Secp256k1Signer, toRecoveryBit } from '@aztec/foundation/crypto/secp256k1-signer';
|
|
9
10
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
11
|
import { Signature, type ViemTransactionSignature } from '@aztec/foundation/eth-signature';
|
|
11
12
|
import { jsonStringify } from '@aztec/foundation/json-rpc';
|
|
12
|
-
import { withHexPrefix } from '@aztec/foundation/string';
|
|
13
|
+
import { bufferToHex, withHexPrefix } from '@aztec/foundation/string';
|
|
13
14
|
|
|
14
15
|
import {
|
|
15
16
|
type TransactionSerializable,
|
|
@@ -104,6 +105,82 @@ export class RemoteSigner implements EthSigner {
|
|
|
104
105
|
private fetch: typeof globalThis.fetch = globalThis.fetch,
|
|
105
106
|
) {}
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Validates that a web3signer is accessible and that the given addresses are available.
|
|
110
|
+
* @param remoteSignerUrl - The URL of the web3signer (can be string or EthRemoteSignerConfig)
|
|
111
|
+
* @param addresses - The addresses to check for availability
|
|
112
|
+
* @param fetch - Optional fetch implementation for testing
|
|
113
|
+
* @throws Error if the web3signer is not accessible or if any address is not available
|
|
114
|
+
*/
|
|
115
|
+
static async validateAccess(
|
|
116
|
+
remoteSignerUrl: EthRemoteSignerConfig,
|
|
117
|
+
addresses: string[],
|
|
118
|
+
fetch: typeof globalThis.fetch = globalThis.fetch,
|
|
119
|
+
): Promise<void> {
|
|
120
|
+
const url = typeof remoteSignerUrl === 'string' ? remoteSignerUrl : remoteSignerUrl.remoteSignerUrl;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Check if the web3signer is reachable by calling eth_accounts
|
|
124
|
+
const response = await fetch(url, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: {
|
|
127
|
+
'Content-Type': 'application/json',
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
jsonrpc: '2.0',
|
|
131
|
+
method: 'eth_accounts',
|
|
132
|
+
params: [],
|
|
133
|
+
id: 1,
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
const errorText = await response.text();
|
|
139
|
+
throw new SignerError(
|
|
140
|
+
`Web3Signer validation failed: ${response.status} ${response.statusText} - ${errorText}`,
|
|
141
|
+
'eth_accounts',
|
|
142
|
+
url,
|
|
143
|
+
response.status,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const result = await response.json();
|
|
148
|
+
|
|
149
|
+
if (result.error) {
|
|
150
|
+
throw new SignerError(
|
|
151
|
+
`Web3Signer JSON-RPC error during validation: ${result.error.code} - ${result.error.message}`,
|
|
152
|
+
'eth_accounts',
|
|
153
|
+
url,
|
|
154
|
+
200,
|
|
155
|
+
result.error.code,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!result.result || !Array.isArray(result.result)) {
|
|
160
|
+
throw new Error('Invalid response from Web3Signer: expected array of accounts');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Normalize addresses to lowercase for comparison
|
|
164
|
+
const availableAccounts: string[] = result.result.map((addr: string) => addr.toLowerCase());
|
|
165
|
+
const requestedAddresses = addresses.map(addr => addr.toLowerCase());
|
|
166
|
+
|
|
167
|
+
// Check if all requested addresses are available
|
|
168
|
+
const missingAddresses = requestedAddresses.filter(addr => !availableAccounts.includes(addr));
|
|
169
|
+
|
|
170
|
+
if (missingAddresses.length > 0) {
|
|
171
|
+
throw new Error(`The following addresses are not available in the web3signer: ${missingAddresses.join(', ')}`);
|
|
172
|
+
}
|
|
173
|
+
} catch (error: any) {
|
|
174
|
+
if (error instanceof SignerError) {
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
if (error.code === 'ECONNREFUSED' || error.cause?.code === 'ECONNREFUSED') {
|
|
178
|
+
throw new Error(`Unable to connect to web3signer at ${url}. Please ensure it is running and accessible.`);
|
|
179
|
+
}
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
107
184
|
/**
|
|
108
185
|
* Sign a message using eth_sign via remote JSON-RPC.
|
|
109
186
|
*/
|
|
@@ -167,10 +244,6 @@ export class RemoteSigner implements EthSigner {
|
|
|
167
244
|
* Make a JSON-RPC eth_signTransaction request.
|
|
168
245
|
*/
|
|
169
246
|
private async makeJsonRpcSignTransactionRequest(tx: TransactionSerializable): Promise<Signature> {
|
|
170
|
-
if (tx.type !== 'eip1559') {
|
|
171
|
-
throw new Error('This signer does not support tx type: ' + tx.type);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
247
|
const txObject: RemoteSignerTxObject = {
|
|
175
248
|
from: this.address.toString(),
|
|
176
249
|
to: tx.to ?? null,
|
|
@@ -184,10 +257,10 @@ export class RemoteSigner implements EthSigner {
|
|
|
184
257
|
? withHexPrefix(tx.maxPriorityFeePerGas.toString(16))
|
|
185
258
|
: undefined,
|
|
186
259
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
260
|
+
maxFeePerBlobGas:
|
|
261
|
+
typeof tx.maxFeePerBlobGas !== 'undefined' ? withHexPrefix(tx.maxFeePerBlobGas.toString(16)) : undefined,
|
|
262
|
+
blobVersionedHashes: tx.blobVersionedHashes,
|
|
263
|
+
blobs: tx.blobs?.map(blob => (typeof blob === 'string' ? blob : bufferToHex(Buffer.from(blob)))),
|
|
191
264
|
};
|
|
192
265
|
|
|
193
266
|
let rawTxHex = await this.makeJsonRpcRequest('eth_signTransaction', txObject);
|
package/src/types.ts
CHANGED
|
@@ -5,21 +5,23 @@
|
|
|
5
5
|
* These types define the JSON structure for configuring validators, provers, and
|
|
6
6
|
* their associated keys and addresses.
|
|
7
7
|
*/
|
|
8
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
|
+
import type { Hex } from '@aztec/foundation/string';
|
|
10
|
+
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
8
11
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* An encrypted keystore file config points to a local file with an encrypted private key.
|
|
14
|
+
* The file may be in different formats:
|
|
15
|
+
* - JSON V3 format for ETH keys (Ethereum wallet standard)
|
|
16
|
+
* - EIP-2335 format for BLS keys (Ethereum 2.0 validator standard)
|
|
17
|
+
*/
|
|
18
|
+
export type EncryptedKeyFileConfig = { path: string; password?: string };
|
|
14
19
|
|
|
15
20
|
/** A private key is a 32-byte 0x-prefixed hex */
|
|
16
21
|
export type EthPrivateKey = Hex<32>;
|
|
17
22
|
|
|
18
|
-
/**
|
|
19
|
-
export type
|
|
20
|
-
|
|
21
|
-
/** An Aztec address is a 32-byte 0x-prefixed hex */
|
|
22
|
-
export type AztecAddressHex = Hex<32>;
|
|
23
|
+
/** A BLS private key is a 32-byte 0x-prefixed hex */
|
|
24
|
+
export type BLSPrivateKey = Hex<32>;
|
|
23
25
|
|
|
24
26
|
/** URL type for remote signers */
|
|
25
27
|
export type Url = string;
|
|
@@ -40,19 +42,19 @@ export type EthRemoteSignerConfig =
|
|
|
40
42
|
* If only the address is set, then the default remote signer config from the parent config is used.
|
|
41
43
|
*/
|
|
42
44
|
export type EthRemoteSignerAccount =
|
|
43
|
-
|
|
|
45
|
+
| EthAddress
|
|
44
46
|
| {
|
|
45
|
-
address:
|
|
46
|
-
remoteSignerUrl
|
|
47
|
+
address: EthAddress;
|
|
48
|
+
remoteSignerUrl: Url;
|
|
47
49
|
certPath?: string;
|
|
48
50
|
certPass?: string;
|
|
49
51
|
};
|
|
50
52
|
|
|
51
|
-
/** An L1 account is a private key, a remote signer configuration, or
|
|
52
|
-
export type EthAccount = EthPrivateKey | EthRemoteSignerAccount |
|
|
53
|
+
/** An L1 account is a private key, a remote signer configuration, or an encrypted keystore file (JSON V3 format) */
|
|
54
|
+
export type EthAccount = EthPrivateKey | EthRemoteSignerAccount | EncryptedKeyFileConfig;
|
|
53
55
|
|
|
54
56
|
/** A mnemonic can be used to define a set of accounts */
|
|
55
|
-
export type
|
|
57
|
+
export type MnemonicConfig = {
|
|
56
58
|
mnemonic: string;
|
|
57
59
|
addressIndex?: number;
|
|
58
60
|
accountIndex?: number;
|
|
@@ -61,37 +63,47 @@ export type EthMnemonicConfig = {
|
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
/** One or more L1 accounts */
|
|
64
|
-
export type EthAccounts = EthAccount | EthAccount[] |
|
|
66
|
+
export type EthAccounts = EthAccount | EthAccount[] | MnemonicConfig;
|
|
65
67
|
|
|
66
|
-
export type
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
export type ProverKeyStoreWithId = {
|
|
69
|
+
/** Address that identifies the prover. This address will receive the rewards. */
|
|
70
|
+
id: EthAddress;
|
|
71
|
+
/** One or more EOAs used for sending proof L1 txs. */
|
|
72
|
+
publisher: EthAccounts;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type ProverKeyStore = ProverKeyStoreWithId | EthAccount;
|
|
76
|
+
|
|
77
|
+
/** A BLS account is either a private key, or an EIP-2335 encrypted keystore file */
|
|
78
|
+
export type BLSAccount = BLSPrivateKey | EncryptedKeyFileConfig;
|
|
79
|
+
|
|
80
|
+
/** An AttesterAccount is a combined EthAccount and optional BLSAccount */
|
|
81
|
+
export type AttesterAccount = { eth: EthAccount; bls?: BLSAccount } | EthAccount;
|
|
82
|
+
|
|
83
|
+
/** One or more attester accounts combining ETH and BLS keys */
|
|
84
|
+
export type AttesterAccounts = AttesterAccount | AttesterAccount[] | MnemonicConfig;
|
|
74
85
|
|
|
75
86
|
export type ValidatorKeyStore = {
|
|
76
87
|
/**
|
|
77
88
|
* One or more validator attester keys to handle in this configuration block.
|
|
78
89
|
* An attester address may only appear once across all configuration blocks across all keystore files.
|
|
79
90
|
*/
|
|
80
|
-
attester:
|
|
91
|
+
attester: AttesterAccounts;
|
|
81
92
|
/**
|
|
82
93
|
* Coinbase address to use when proposing an L2 block as any of the validators in this configuration block.
|
|
83
|
-
* Falls back to the attester address if not set.
|
|
94
|
+
* Falls back to the keystore-level coinbase, then to the attester address if not set.
|
|
84
95
|
*/
|
|
85
|
-
coinbase?:
|
|
96
|
+
coinbase?: EthAddress;
|
|
86
97
|
/**
|
|
87
98
|
* One or more EOAs used for sending block proposal L1 txs for all validators in this configuration block.
|
|
88
|
-
* Falls back to the attester account if not set.
|
|
99
|
+
* Falls back to the keystore-level publisher, then to the attester account if not set.
|
|
89
100
|
*/
|
|
90
101
|
publisher?: EthAccounts;
|
|
91
102
|
/**
|
|
92
103
|
* Fee recipient address to use when proposing an L2 block as any of the validators in this configuration block.
|
|
104
|
+
* Falls back to the keystore-level feeRecipient if not set.
|
|
93
105
|
*/
|
|
94
|
-
feeRecipient
|
|
106
|
+
feeRecipient?: AztecAddress;
|
|
95
107
|
/**
|
|
96
108
|
* Default remote signer for all accounts in this block.
|
|
97
109
|
*/
|
|
@@ -103,8 +115,8 @@ export type ValidatorKeyStore = {
|
|
|
103
115
|
};
|
|
104
116
|
|
|
105
117
|
export type KeyStore = {
|
|
106
|
-
/** Schema version of this keystore file (
|
|
107
|
-
schemaVersion:
|
|
118
|
+
/** Schema version of this keystore file (1 or 2). */
|
|
119
|
+
schemaVersion: 1 | 2;
|
|
108
120
|
/** Validator configurations. */
|
|
109
121
|
validators?: ValidatorKeyStore[];
|
|
110
122
|
/** One or more accounts used for creating slash payloads on L1. Does not create slash payloads if not set. */
|
|
@@ -115,4 +127,10 @@ export type KeyStore = {
|
|
|
115
127
|
prover?: ProverKeyStore;
|
|
116
128
|
/** Used for automatically funding publisher accounts if there is none defined in the corresponding ValidatorKeyStore*/
|
|
117
129
|
fundingAccount?: EthAccount;
|
|
130
|
+
/** Default publisher accounts for all validators in this keystore. Can be overridden by individual validator configs. */
|
|
131
|
+
publisher?: EthAccounts;
|
|
132
|
+
/** Default coinbase address for all validators in this keystore. Can be overridden by individual validator configs. */
|
|
133
|
+
coinbase?: EthAddress;
|
|
134
|
+
/** Default fee recipient address for all validators in this keystore. Can be overridden by individual validator configs. */
|
|
135
|
+
feeRecipient?: AztecAddress;
|
|
118
136
|
};
|