@aztec/node-keystore 3.0.0-canary.a9708bd → 3.0.0-devnet.2-patch.1

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/src/schemas.ts CHANGED
@@ -1,96 +1,126 @@
1
1
  /**
2
2
  * Zod schemas for keystore validation using Aztec's validation functions
3
3
  */
4
- import { EthAddress } from '@aztec/foundation/eth-address';
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 ethAddressSchema = z.string().refine(EthAddress.isAddress, 'Invalid Ethereum address');
11
- const ethPrivateKeySchema = z
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
- const aztecAddressSchema = z.string().refine(AztecAddress.isAddress, 'Invalid Aztec address');
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.object({
21
- remoteSignerUrl: urlSchema,
22
- certPath: z.string().nullish(),
23
- certPass: z.string().nullish(),
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
- ethAddressSchema,
30
- z.object({
31
- address: ethAddressSchema,
32
- remoteSignerUrl: urlSchema.nullish(),
33
- certPath: z.string().nullish(),
34
- certPass: z.string().nullish(),
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 keystore schema
39
- const jsonKeyFileV3Schema = z.object({
40
- path: z.string(),
41
- password: z.string().nullish(),
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.object({
46
- mnemonic: z.string().min(1, 'Mnemonic cannot be empty'),
47
- addressIndex: z.number().int().min(0).default(0),
48
- accountIndex: z.number().int().min(0).default(0),
49
- addressCount: z.number().int().min(1).default(1),
50
- accountCount: z.number().int().min(1).default(1),
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.object({
68
- id: ethAddressSchema,
69
- publisher: ethAccountsSchema,
70
- }),
92
+ z
93
+ .object({
94
+ id: schemas.EthAddress,
95
+ publisher: ethAccountsSchema,
96
+ })
97
+ .strict(),
71
98
  ]);
72
99
 
73
100
  // Validator keystore schema
74
- const validatorKeyStoreSchema = z.object({
75
- attester: ethAccountsSchema,
76
- coinbase: ethAddressSchema.nullish(),
77
- publisher: ethAccountsSchema.nullish(),
78
- feeRecipient: aztecAddressSchema,
79
- remoteSigner: remoteSignerConfigSchema.nullish(),
80
- });
101
+ const validatorKeyStoreSchema = 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();
81
111
 
82
112
  // Main keystore schema
83
113
  export const keystoreSchema = z
84
114
  .object({
85
115
  schemaVersion: z.literal(1),
86
- validators: z.array(validatorKeyStoreSchema).nullish(),
87
- slasher: ethAccountsSchema.nullish(),
88
- remoteSigner: remoteSignerConfigSchema.nullish(),
89
- prover: proverKeyStoreSchema.nullish(),
116
+ validators: optional(z.array(validatorKeyStoreSchema)),
117
+ slasher: optional(ethAccountsSchema),
118
+ remoteSigner: optional(remoteSignerConfigSchema),
119
+ prover: optional(proverKeyStoreSchema),
120
+ fundingAccount: optional(ethAccountSchema),
90
121
  })
122
+ .strict()
91
123
  .refine(data => data.validators || data.prover, {
92
124
  message: 'Keystore must have at least validators or prover configuration',
93
125
  path: ['root'],
94
126
  });
95
-
96
- export type KeyStoreSchema = z.infer<typeof keystoreSchema>;
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 { Secp256k1Signer, randomBytes, toRecoveryBit } from '@aztec/foundation/crypto';
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
- // maxFeePerBlobGas:
188
- // typeof tx.maxFeePerBlobGas !== 'undefined' ? withHexPrefix(tx.maxFeePerBlobGas.toString(16)) : undefined,
189
- // blobVersionedHashes: tx.blobVersionedHashes,
190
- // blobs: tx.blobs?.map(blob => (typeof blob === 'string' ? blob : bufferToHex(Buffer.from(blob)))),
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
- /** Parameterized hex string type for specific byte lengths */
10
- export type Hex<TByteLength extends number> = `0x${string}` & { readonly _length: TByteLength };
11
-
12
- /** A json keystore config points to a local file with the encrypted private key, and may require a password for decrypting it */
13
- export type EthJsonKeyFileV3Config = { path: string; password?: string };
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
- /** An address is a 20-byte 0x-prefixed hex */
19
- export type EthAddressHex = Hex<20>;
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
- | EthAddressHex
45
+ | EthAddress
44
46
  | {
45
- address: EthAddressHex;
46
- remoteSignerUrl?: Url;
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 a standard json key store file */
52
- export type EthAccount = EthPrivateKey | EthRemoteSignerAccount | EthJsonKeyFileV3Config;
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 EthMnemonicConfig = {
57
+ export type MnemonicConfig = {
56
58
  mnemonic: string;
57
59
  addressIndex?: number;
58
60
  accountIndex?: number;
@@ -61,28 +63,37 @@ export type EthMnemonicConfig = {
61
63
  };
62
64
 
63
65
  /** One or more L1 accounts */
64
- export type EthAccounts = EthAccount | EthAccount[] | EthMnemonicConfig;
66
+ export type EthAccounts = EthAccount | EthAccount[] | MnemonicConfig;
65
67
 
66
- export type ProverKeyStore =
67
- | {
68
- /** Address that identifies the prover. This address will receive the rewards. */
69
- id: EthAddressHex;
70
- /** One or more EOAs used for sending proof L1 txs. */
71
- publisher: EthAccounts;
72
- }
73
- | EthAccount;
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: EthAccounts;
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
94
  * Falls back to the attester address if not set.
84
95
  */
85
- coinbase?: EthAddressHex;
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
99
  * Falls back to the attester account if not set.
@@ -91,11 +102,15 @@ export type ValidatorKeyStore = {
91
102
  /**
92
103
  * Fee recipient address to use when proposing an L2 block as any of the validators in this configuration block.
93
104
  */
94
- feeRecipient: AztecAddressHex;
105
+ feeRecipient: AztecAddress;
95
106
  /**
96
107
  * Default remote signer for all accounts in this block.
97
108
  */
98
109
  remoteSigner?: EthRemoteSignerConfig;
110
+ /**
111
+ * Used for automatically funding publisher accounts in this block.
112
+ */
113
+ fundingAccount?: EthAccount;
99
114
  };
100
115
 
101
116
  export type KeyStore = {
@@ -109,4 +124,6 @@ export type KeyStore = {
109
124
  remoteSigner?: EthRemoteSignerConfig;
110
125
  /** Prover configuration. Only one prover configuration is allowed. */
111
126
  prover?: ProverKeyStore;
127
+ /** Used for automatically funding publisher accounts if there is none defined in the corresponding ValidatorKeyStore*/
128
+ fundingAccount?: EthAccount;
112
129
  };