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

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.
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,eAAO,MAAM,mBAAmB,kDAGK,CAAC;AAiEtC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYvB,CAAC"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG/D,eAAO,MAAM,mBAAmB,kDAGK,CAAC;AACtC,eAAO,MAAM,mBAAmB,kDAGK,CAAC;AAgFtC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYvB,CAAC"}
package/dest/schemas.js CHANGED
@@ -5,6 +5,7 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address';
5
5
  import { z } from 'zod';
6
6
  // Use Aztec's validation functions but return string types to match our TypeScript interfaces
7
7
  export const ethPrivateKeySchema = z.string().regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid private key (must be 32 bytes with 0x prefix)').transform((s)=>s);
8
+ export const blsPrivateKeySchema = z.string().regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid BLS private key (must be 32 bytes with 0x prefix)').transform((s)=>s);
8
9
  const urlSchema = z.string().url('Invalid URL');
9
10
  // Remote signer config schema
10
11
  const remoteSignerConfigSchema = z.union([
@@ -20,7 +21,7 @@ const remoteSignerAccountSchema = z.union([
20
21
  schemas.EthAddress,
21
22
  z.object({
22
23
  address: schemas.EthAddress,
23
- remoteSignerUrl: optional(urlSchema),
24
+ remoteSignerUrl: urlSchema,
24
25
  certPath: optional(z.string()),
25
26
  certPass: optional(z.string())
26
27
  })
@@ -50,6 +51,25 @@ const ethAccountsSchema = z.union([
50
51
  z.array(ethAccountSchema),
51
52
  mnemonicConfigSchema
52
53
  ]);
54
+ // BLSAccount schema
55
+ const blsAccountSchema = z.union([
56
+ blsPrivateKeySchema,
57
+ jsonKeyFileV3Schema
58
+ ]);
59
+ // AttesterAccount schema: either EthAccount or { eth: EthAccount, bls?: BLSAccount }
60
+ const attesterAccountSchema = z.union([
61
+ ethAccountSchema,
62
+ z.object({
63
+ eth: ethAccountSchema,
64
+ bls: optional(blsAccountSchema)
65
+ })
66
+ ]);
67
+ // AttesterAccounts schema: AttesterAccount | AttesterAccount[] | MnemonicConfig
68
+ const attesterAccountsSchema = z.union([
69
+ attesterAccountSchema,
70
+ z.array(attesterAccountSchema),
71
+ mnemonicConfigSchema
72
+ ]);
53
73
  // Prover keystore schema
54
74
  const proverKeyStoreSchema = z.union([
55
75
  ethAccountSchema,
@@ -60,7 +80,7 @@ const proverKeyStoreSchema = z.union([
60
80
  ]);
61
81
  // Validator keystore schema
62
82
  const validatorKeyStoreSchema = z.object({
63
- attester: ethAccountsSchema,
83
+ attester: attesterAccountsSchema,
64
84
  coinbase: optional(schemas.EthAddress),
65
85
  publisher: optional(ethAccountsSchema),
66
86
  feeRecipient: AztecAddress.schema,
package/dest/signer.d.ts CHANGED
@@ -39,6 +39,14 @@ export declare class RemoteSigner implements EthSigner {
39
39
  private readonly config;
40
40
  private fetch;
41
41
  constructor(address: EthAddress, config: EthRemoteSignerConfig, fetch?: typeof globalThis.fetch);
42
+ /**
43
+ * Validates that a web3signer is accessible and that the given addresses are available.
44
+ * @param remoteSignerUrl - The URL of the web3signer (can be string or EthRemoteSignerConfig)
45
+ * @param addresses - The addresses to check for availability
46
+ * @param fetch - Optional fetch implementation for testing
47
+ * @throws Error if the web3signer is not accessible or if any address is not available
48
+ */
49
+ static validateAccess(remoteSignerUrl: EthRemoteSignerConfig, addresses: string[], fetch?: typeof globalThis.fetch): Promise<void>;
42
50
  /**
43
51
  * Sign a message using eth_sign via remote JSON-RPC.
44
52
  */
@@ -1 +1 @@
1
- {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAiC,MAAM,iCAAiC,CAAC;AAI3F,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EAKzB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IAG3B,MAAM,EAAE,MAAM;IACd,GAAG,EAAE,MAAM;IACX,UAAU,CAAC,EAAE,MAAM;IACnB,SAAS,CAAC,EAAE,MAAM;gBAJzB,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,SAAS,CAAC,EAAE,MAAM,YAAA;CAK5B;AAED;;GAEG;AACH,qBAAa,WAAY,YAAW,SAAS;IAG/B,OAAO,CAAC,UAAU;IAF9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAErB,UAAU,EAAE,QAAQ;IAIxC,IAAI,OAAO,IAAI,UAAU,CAExB;IAED,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIlD,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAKjE,eAAe,CAAC,WAAW,EAAE,uBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC;CAc1E;AAmBD;;GAEG;AACH,qBAAa,YAAa,YAAW,SAAS;aAE1B,OAAO,EAAE,UAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,KAAK;gBAFG,OAAO,EAAE,UAAU,EAClB,MAAM,EAAE,qBAAqB,EACtC,KAAK,GAAE,OAAO,UAAU,CAAC,KAAwB;IAG3D;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIxD;;OAEG;IACG,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIvE,eAAe,CAAC,WAAW,EAAE,uBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIzE;;OAEG;IACH;;OAEG;YACW,sBAAsB;IAcpC;;OAEG;YACW,+BAA+B;IAkB7C;;OAEG;YACW,iCAAiC;IA+C/C;;OAEG;YACW,kBAAkB;IAyChC;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,UAAU;CAGnB"}
1
+ {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAiC,MAAM,iCAAiC,CAAC;AAI3F,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EAKzB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAK;IAG3B,MAAM,EAAE,MAAM;IACd,GAAG,EAAE,MAAM;IACX,UAAU,CAAC,EAAE,MAAM;IACnB,SAAS,CAAC,EAAE,MAAM;gBAJzB,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,SAAS,CAAC,EAAE,MAAM,YAAA;CAK5B;AAED;;GAEG;AACH,qBAAa,WAAY,YAAW,SAAS;IAG/B,OAAO,CAAC,UAAU;IAF9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAErB,UAAU,EAAE,QAAQ;IAIxC,IAAI,OAAO,IAAI,UAAU,CAExB;IAED,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIlD,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAKjE,eAAe,CAAC,WAAW,EAAE,uBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC;CAc1E;AAmBD;;GAEG;AACH,qBAAa,YAAa,YAAW,SAAS;aAE1B,OAAO,EAAE,UAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,KAAK;gBAFG,OAAO,EAAE,UAAU,EAClB,MAAM,EAAE,qBAAqB,EACtC,KAAK,GAAE,OAAO,UAAU,CAAC,KAAwB;IAG3D;;;;;;OAMG;WACU,cAAc,CACzB,eAAe,EAAE,qBAAqB,EACtC,SAAS,EAAE,MAAM,EAAE,EACnB,KAAK,GAAE,OAAO,UAAU,CAAC,KAAwB,GAChD,OAAO,CAAC,IAAI,CAAC;IAiEhB;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAIxD;;OAEG;IACG,aAAa,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIvE,eAAe,CAAC,WAAW,EAAE,uBAAuB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIzE;;OAEG;IACH;;OAEG;YACW,sBAAsB;IAcpC;;OAEG;YACW,+BAA+B;IAkB7C;;OAEG;YACW,iCAAiC;IA+C/C;;OAEG;YACW,kBAAkB;IAyChC;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,UAAU;CAGnB"}
package/dest/signer.js CHANGED
@@ -63,6 +63,57 @@ import { hashTypedData, keccak256, parseTransaction, serializeTransaction } from
63
63
  this.fetch = fetch;
64
64
  }
65
65
  /**
66
+ * Validates that a web3signer is accessible and that the given addresses are available.
67
+ * @param remoteSignerUrl - The URL of the web3signer (can be string or EthRemoteSignerConfig)
68
+ * @param addresses - The addresses to check for availability
69
+ * @param fetch - Optional fetch implementation for testing
70
+ * @throws Error if the web3signer is not accessible or if any address is not available
71
+ */ static async validateAccess(remoteSignerUrl, addresses, fetch = globalThis.fetch) {
72
+ const url = typeof remoteSignerUrl === 'string' ? remoteSignerUrl : remoteSignerUrl.remoteSignerUrl;
73
+ try {
74
+ // Check if the web3signer is reachable by calling eth_accounts
75
+ const response = await fetch(url, {
76
+ method: 'POST',
77
+ headers: {
78
+ 'Content-Type': 'application/json'
79
+ },
80
+ body: JSON.stringify({
81
+ jsonrpc: '2.0',
82
+ method: 'eth_accounts',
83
+ params: [],
84
+ id: 1
85
+ })
86
+ });
87
+ if (!response.ok) {
88
+ const errorText = await response.text();
89
+ throw new SignerError(`Web3Signer validation failed: ${response.status} ${response.statusText} - ${errorText}`, 'eth_accounts', url, response.status);
90
+ }
91
+ const result = await response.json();
92
+ if (result.error) {
93
+ throw new SignerError(`Web3Signer JSON-RPC error during validation: ${result.error.code} - ${result.error.message}`, 'eth_accounts', url, 200, result.error.code);
94
+ }
95
+ if (!result.result || !Array.isArray(result.result)) {
96
+ throw new Error('Invalid response from Web3Signer: expected array of accounts');
97
+ }
98
+ // Normalize addresses to lowercase for comparison
99
+ const availableAccounts = result.result.map((addr)=>addr.toLowerCase());
100
+ const requestedAddresses = addresses.map((addr)=>addr.toLowerCase());
101
+ // Check if all requested addresses are available
102
+ const missingAddresses = requestedAddresses.filter((addr)=>!availableAccounts.includes(addr));
103
+ if (missingAddresses.length > 0) {
104
+ throw new Error(`The following addresses are not available in the web3signer: ${missingAddresses.join(', ')}`);
105
+ }
106
+ } catch (error) {
107
+ if (error instanceof SignerError) {
108
+ throw error;
109
+ }
110
+ if (error.code === 'ECONNREFUSED' || error.cause?.code === 'ECONNREFUSED') {
111
+ throw new Error(`Unable to connect to web3signer at ${url}. Please ensure it is running and accessible.`);
112
+ }
113
+ throw error;
114
+ }
115
+ }
116
+ /**
66
117
  * Sign a message using eth_sign via remote JSON-RPC.
67
118
  */ async signMessage(message) {
68
119
  return await this.makeJsonRpcSignRequest(message);
package/dest/types.d.ts CHANGED
@@ -12,12 +12,14 @@ export type Hex<TByteLength extends number> = `0x${string}` & {
12
12
  readonly _length: TByteLength;
13
13
  };
14
14
  /** A json keystore config points to a local file with the encrypted private key, and may require a password for decrypting it */
15
- export type EthJsonKeyFileV3Config = {
15
+ export type JsonKeyFileV3Config = {
16
16
  path: string;
17
17
  password?: string;
18
18
  };
19
19
  /** A private key is a 32-byte 0x-prefixed hex */
20
20
  export type EthPrivateKey = Hex<32>;
21
+ /** A BLS private key is a 32-byte 0x-prefixed hex */
22
+ export type BLSPrivateKey = Hex<32>;
21
23
  /** URL type for remote signers */
22
24
  export type Url = string;
23
25
  /**
@@ -34,14 +36,14 @@ export type EthRemoteSignerConfig = Url | {
34
36
  */
35
37
  export type EthRemoteSignerAccount = EthAddress | {
36
38
  address: EthAddress;
37
- remoteSignerUrl?: Url;
39
+ remoteSignerUrl: Url;
38
40
  certPath?: string;
39
41
  certPass?: string;
40
42
  };
41
43
  /** An L1 account is a private key, a remote signer configuration, or a standard json key store file */
42
- export type EthAccount = EthPrivateKey | EthRemoteSignerAccount | EthJsonKeyFileV3Config;
44
+ export type EthAccount = EthPrivateKey | EthRemoteSignerAccount | JsonKeyFileV3Config;
43
45
  /** A mnemonic can be used to define a set of accounts */
44
- export type EthMnemonicConfig = {
46
+ export type MnemonicConfig = {
45
47
  mnemonic: string;
46
48
  addressIndex?: number;
47
49
  accountIndex?: number;
@@ -49,7 +51,7 @@ export type EthMnemonicConfig = {
49
51
  accountCount?: number;
50
52
  };
51
53
  /** One or more L1 accounts */
52
- export type EthAccounts = EthAccount | EthAccount[] | EthMnemonicConfig;
54
+ export type EthAccounts = EthAccount | EthAccount[] | MnemonicConfig;
53
55
  export type ProverKeyStoreWithId = {
54
56
  /** Address that identifies the prover. This address will receive the rewards. */
55
57
  id: EthAddress;
@@ -57,12 +59,21 @@ export type ProverKeyStoreWithId = {
57
59
  publisher: EthAccounts;
58
60
  };
59
61
  export type ProverKeyStore = ProverKeyStoreWithId | EthAccount;
62
+ /** A BLS account is either a private key, or a standard json key store file */
63
+ export type BLSAccount = BLSPrivateKey | JsonKeyFileV3Config;
64
+ /** An AttesterAccount is a combined EthAccount and optional BLSAccount */
65
+ export type AttesterAccount = {
66
+ eth: EthAccount;
67
+ bls?: BLSAccount;
68
+ } | EthAccount;
69
+ /** One or more attester accounts combining ETH and BLS keys */
70
+ export type AttesterAccounts = AttesterAccount | AttesterAccount[] | MnemonicConfig;
60
71
  export type ValidatorKeyStore = {
61
72
  /**
62
73
  * One or more validator attester keys to handle in this configuration block.
63
74
  * An attester address may only appear once across all configuration blocks across all keystore files.
64
75
  */
65
- attester: EthAccounts;
76
+ attester: AttesterAccounts;
66
77
  /**
67
78
  * Coinbase address to use when proposing an L2 block as any of the validators in this configuration block.
68
79
  * Falls back to the attester address if not set.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,8DAA8D;AAC9D,MAAM,MAAM,GAAG,CAAC,WAAW,SAAS,MAAM,IAAI,KAAK,MAAM,EAAE,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAEhG,iIAAiI;AACjI,MAAM,MAAM,sBAAsB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,iDAAiD;AACjD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;AAEpC,kCAAkC;AAClC,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC;AAEzB;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAC7B,GAAG,GACH;IACE,eAAe,EAAE,GAAG,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEN;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAC9B,UAAU,GACV;IACE,OAAO,EAAE,UAAU,CAAC;IACpB,eAAe,CAAC,EAAE,GAAG,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEN,uGAAuG;AACvG,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAEzF,yDAAyD;AACzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,8BAA8B;AAC9B,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,EAAE,GAAG,iBAAiB,CAAC;AAExE,MAAM,MAAM,oBAAoB,GAAG;IACjC,iFAAiF;IACjF,EAAE,EAAE,UAAU,CAAC;IACf,sDAAsD;IACtD,SAAS,EAAE,WAAW,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,oBAAoB,GAAG,UAAU,CAAC;AAE/D,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,QAAQ,EAAE,WAAW,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB;;OAEG;IACH,YAAY,EAAE,YAAY,CAAC;IAC3B;;OAEG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC;;OAEG;IACH,cAAc,CAAC,EAAE,UAAU,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACjC,8GAA8G;IAC9G,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC,sEAAsE;IACtE,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,wHAAwH;IACxH,cAAc,CAAC,EAAE,UAAU,CAAC;CAC7B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/node-keystore",
3
- "version": "2.0.3",
3
+ "version": "2.1.0-rc.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -62,9 +62,9 @@
62
62
  ]
63
63
  },
64
64
  "dependencies": {
65
- "@aztec/ethereum": "2.0.3",
66
- "@aztec/foundation": "2.0.3",
67
- "@aztec/stdlib": "2.0.3",
65
+ "@aztec/ethereum": "2.1.0-rc.2",
66
+ "@aztec/foundation": "2.1.0-rc.2",
67
+ "@aztec/stdlib": "2.1.0-rc.2",
68
68
  "@ethersproject/wallet": "^5.7.0",
69
69
  "tslib": "^2.4.0",
70
70
  "viem": "2.23.7",
@@ -18,13 +18,14 @@ import { mnemonicToAccount } from 'viem/accounts';
18
18
  import { ethPrivateKeySchema } from './schemas.js';
19
19
  import { LocalSigner, RemoteSigner } from './signer.js';
20
20
  import type {
21
+ AttesterAccounts,
21
22
  EthAccount,
22
23
  EthAccounts,
23
- EthJsonKeyFileV3Config,
24
- EthMnemonicConfig,
25
24
  EthRemoteSignerAccount,
26
25
  EthRemoteSignerConfig,
26
+ JsonKeyFileV3Config,
27
27
  KeyStore,
28
+ MnemonicConfig,
28
29
  ProverKeyStore,
29
30
  ValidatorKeyStore as ValidatorKeystoreConfig,
30
31
  } from './types.js';
@@ -58,6 +59,82 @@ export class KeystoreManager {
58
59
  this.validateUniqueAttesterAddresses();
59
60
  }
60
61
 
62
+ /**
63
+ * Validates all remote signers in the keystore are accessible and have the required addresses.
64
+ * Should be called after construction if validation is needed.
65
+ */
66
+ async validateSigners(): Promise<void> {
67
+ // Collect all remote signers with their addresses grouped by URL
68
+ const remoteSignersByUrl = new Map<string, Set<string>>();
69
+
70
+ // Helper to extract remote signer URL from config
71
+ const getUrl = (config: EthRemoteSignerConfig): string => {
72
+ return typeof config === 'string' ? config : config.remoteSignerUrl;
73
+ };
74
+
75
+ // Helper to collect remote signers from accounts
76
+ const collectRemoteSigners = (accounts: EthAccounts, defaultRemoteSigner?: EthRemoteSignerConfig): void => {
77
+ const processAccount = (account: EthAccount): void => {
78
+ if (typeof account === 'object' && !('path' in account) && !('mnemonic' in (account as any))) {
79
+ // This is a remote signer account
80
+ const remoteSigner = account as EthRemoteSignerAccount;
81
+ const address = 'address' in remoteSigner ? remoteSigner.address : remoteSigner;
82
+
83
+ let url: string;
84
+ if ('remoteSignerUrl' in remoteSigner && remoteSigner.remoteSignerUrl) {
85
+ url = remoteSigner.remoteSignerUrl;
86
+ } else if (defaultRemoteSigner) {
87
+ url = getUrl(defaultRemoteSigner);
88
+ } else {
89
+ return; // No remote signer URL available
90
+ }
91
+
92
+ if (!remoteSignersByUrl.has(url)) {
93
+ remoteSignersByUrl.set(url, new Set());
94
+ }
95
+ remoteSignersByUrl.get(url)!.add(address.toString());
96
+ }
97
+ };
98
+
99
+ if (Array.isArray(accounts)) {
100
+ accounts.forEach(account => collectRemoteSigners(account, defaultRemoteSigner));
101
+ } else if (typeof accounts === 'object' && 'mnemonic' in accounts) {
102
+ // Skip mnemonic configs
103
+ } else {
104
+ processAccount(accounts as EthAccount);
105
+ }
106
+ };
107
+
108
+ // Collect from validators
109
+ const validatorCount = this.getValidatorCount();
110
+ for (let i = 0; i < validatorCount; i++) {
111
+ const validator = this.getValidator(i);
112
+ const remoteSigner = validator.remoteSigner || this.keystore.remoteSigner;
113
+
114
+ collectRemoteSigners(this.extractEthAccountsFromAttester(validator.attester), remoteSigner);
115
+ if (validator.publisher) {
116
+ collectRemoteSigners(validator.publisher, remoteSigner);
117
+ }
118
+ }
119
+
120
+ // Collect from slasher
121
+ if (this.keystore.slasher) {
122
+ collectRemoteSigners(this.keystore.slasher, this.keystore.remoteSigner);
123
+ }
124
+
125
+ // Collect from prover
126
+ if (this.keystore.prover && typeof this.keystore.prover === 'object' && 'publisher' in this.keystore.prover) {
127
+ collectRemoteSigners(this.keystore.prover.publisher, this.keystore.remoteSigner);
128
+ }
129
+
130
+ // Validate each remote signer URL with all its addresses
131
+ for (const [url, addresses] of remoteSignersByUrl.entries()) {
132
+ if (addresses.size > 0) {
133
+ await RemoteSigner.validateAccess(url, Array.from(addresses));
134
+ }
135
+ }
136
+ }
137
+
61
138
  /**
62
139
  * Validates that attester addresses are unique across all validators
63
140
  * Only checks simple private key attesters, not JSON-V3 or mnemonic attesters,
@@ -86,38 +163,38 @@ export class KeystoreManager {
86
163
  * Best-effort address extraction that avoids decryption/derivation (no JSON-V3 or mnemonic processing).
87
164
  * This is used at construction time to check for obvious duplicates without throwing for invalid inputs.
88
165
  */
89
- private extractAddressesWithoutSensitiveOperations(accounts: EthAccounts): EthAddress[] {
166
+ private extractAddressesWithoutSensitiveOperations(accounts: AttesterAccounts): EthAddress[] {
167
+ const ethAccounts = this.extractEthAccountsFromAttester(accounts);
168
+ return this.extractAddressesFromEthAccountsNonSensitive(ethAccounts);
169
+ }
170
+
171
+ /**
172
+ * Extract addresses from EthAccounts without sensitive operations (no decryption/derivation).
173
+ */
174
+ private extractAddressesFromEthAccountsNonSensitive(accounts: EthAccounts): EthAddress[] {
90
175
  const results: EthAddress[] = [];
91
176
 
92
177
  const handleAccount = (account: EthAccount): void => {
93
- // String cases: private key or address or remote signer address
94
178
  if (typeof account === 'string') {
95
179
  if (account.startsWith('0x') && account.length === 66) {
96
- // Private key -> derive address locally without external deps
97
180
  try {
98
181
  const signer = new LocalSigner(Buffer32.fromString(ethPrivateKeySchema.parse(account)));
99
182
  results.push(signer.address);
100
183
  } catch {
101
- // Ignore invalid private key at construction time
184
+ // ignore invalid private key at construction time
102
185
  }
103
- return;
104
186
  }
105
-
106
- // Any other string cannot be confidently resolved here
107
187
  return;
108
188
  }
109
189
 
110
- // JSON V3 keystore: skip (requires decryption)
111
190
  if ('path' in account) {
112
191
  return;
113
192
  }
114
193
 
115
- // Mnemonic: skip (requires derivation and may throw on invalid mnemonics)
116
194
  if ('mnemonic' in (account as any)) {
117
195
  return;
118
196
  }
119
197
 
120
- // Remote signer account. If it contains 'address' then extract, otherwise it IS the address
121
198
  const remoteSigner: EthRemoteSignerAccount = account;
122
199
  if ('address' in remoteSigner) {
123
200
  results.push(remoteSigner.address);
@@ -128,12 +205,15 @@ export class KeystoreManager {
128
205
 
129
206
  if (Array.isArray(accounts)) {
130
207
  for (const account of accounts) {
131
- const subResults = this.extractAddressesWithoutSensitiveOperations(account);
132
- results.push(...subResults);
208
+ handleAccount(account as EthAccount);
133
209
  }
134
210
  return results;
135
211
  }
136
212
 
213
+ if (typeof accounts === 'object' && accounts !== null && 'mnemonic' in (accounts as any)) {
214
+ return results;
215
+ }
216
+
137
217
  handleAccount(accounts as EthAccount);
138
218
  return results;
139
219
  }
@@ -143,7 +223,8 @@ export class KeystoreManager {
143
223
  */
144
224
  createAttesterSigners(validatorIndex: number): EthSigner[] {
145
225
  const validator = this.getValidator(validatorIndex);
146
- return this.createSignersFromEthAccounts(validator.attester, validator.remoteSigner || this.keystore.remoteSigner);
226
+ const ethAccounts = this.extractEthAccountsFromAttester(validator.attester);
227
+ return this.createSignersFromEthAccounts(ethAccounts, validator.remoteSigner || this.keystore.remoteSigner);
147
228
  }
148
229
 
149
230
  /**
@@ -210,7 +291,6 @@ export class KeystoreManager {
210
291
  if ('id' in this.keystore.prover) {
211
292
  const id = this.keystore.prover.id;
212
293
  const signers = this.createSignersFromEthAccounts(this.keystore.prover.publisher, this.keystore.remoteSigner);
213
-
214
294
  return { id, signers };
215
295
  }
216
296
 
@@ -292,7 +372,7 @@ export class KeystoreManager {
292
372
  for (let validatorIndex = 0; validatorIndex < validatorCount; validatorIndex++) {
293
373
  const validator = this.getValidator(validatorIndex);
294
374
  const signers = this.createSignersFromEthAccounts(
295
- validator.attester,
375
+ this.extractEthAccountsFromAttester(validator.attester),
296
376
  validator.remoteSigner || this.keystore.remoteSigner,
297
377
  );
298
378
  for (const signer of signers) {
@@ -390,7 +470,7 @@ export class KeystoreManager {
390
470
  /**
391
471
  * Create signer from JSON V3 keystore file or directory
392
472
  */
393
- private createSignerFromJsonV3(config: EthJsonKeyFileV3Config): EthSigner[] {
473
+ private createSignerFromJsonV3(config: JsonKeyFileV3Config): EthSigner[] {
394
474
  try {
395
475
  const stats = statSync(config.path);
396
476
 
@@ -470,7 +550,7 @@ export class KeystoreManager {
470
550
  /**
471
551
  * Create signers from mnemonic configuration using BIP44 derivation
472
552
  */
473
- private createSignersFromMnemonic(config: EthMnemonicConfig): EthSigner[] {
553
+ private createSignersFromMnemonic(config: MnemonicConfig): EthSigner[] {
474
554
  const { mnemonic, addressIndex = 0, accountIndex = 0, addressCount = 1, accountCount = 1 } = config;
475
555
  const signers: EthSigner[] = [];
476
556
 
@@ -601,38 +681,56 @@ export class KeystoreManager {
601
681
  return validator.remoteSigner || this.keystore.remoteSigner;
602
682
  };
603
683
 
604
- // Check the attester configuration
605
- const { attester } = validator;
684
+ // Normalize attester to EthAccounts and search
685
+ const normalized = this.extractEthAccountsFromAttester(validator.attester);
686
+
687
+ const findInEthAccounts = (accs: EthAccounts): EthRemoteSignerConfig | undefined => {
688
+ if (typeof accs === 'string') {
689
+ return checkAccount(accs);
690
+ }
691
+ if (Array.isArray(accs)) {
692
+ for (const a of accs as EthAccount[]) {
693
+ const res = checkAccount(a);
694
+ if (res !== undefined) {
695
+ return res;
696
+ }
697
+ }
698
+ return undefined;
699
+ }
700
+ if (typeof accs === 'object' && accs !== null && 'mnemonic' in accs) {
701
+ // mnemonic-derived keys are local signers; no remote signer config
702
+ return undefined;
703
+ }
704
+ return checkAccount(accs as EthAccount);
705
+ };
706
+
707
+ return findInEthAccounts(normalized);
708
+ }
606
709
 
710
+ /** Extract ETH accounts from AttesterAccounts */
711
+ private extractEthAccountsFromAttester(attester: AttesterAccounts): EthAccounts {
607
712
  if (typeof attester === 'string') {
608
- const result = checkAccount(attester);
609
- return result === undefined ? undefined : result;
713
+ return attester;
610
714
  }
611
-
612
715
  if (Array.isArray(attester)) {
613
- for (const account of attester) {
614
- const result = checkAccount(account);
615
- if (result !== undefined) {
616
- return result;
716
+ const out: EthAccount[] = [];
717
+ for (const item of attester) {
718
+ if (typeof item === 'string') {
719
+ out.push(item);
720
+ } else if ('eth' in (item as any)) {
721
+ out.push((item as any).eth as EthAccount);
722
+ } else if (!('mnemonic' in (item as any))) {
723
+ out.push(item as EthAccount);
617
724
  }
618
725
  }
619
- return undefined;
726
+ return out;
620
727
  }
621
-
622
- // Mnemonic configuration
623
- if ('mnemonic' in attester) {
624
- try {
625
- const signers = this.createSignersFromMnemonic(attester);
626
- const matches = signers.some(s => s.address.equals(attesterAddress));
627
- // Mnemonic-derived keys are local signers
628
- return matches ? undefined : undefined;
629
- } catch {
630
- return undefined;
631
- }
728
+ if ('mnemonic' in (attester as any)) {
729
+ return attester as any;
632
730
  }
633
-
634
- // Single account object
635
- const result = checkAccount(attester);
636
- return result === undefined ? undefined : result;
731
+ if ('eth' in (attester as any)) {
732
+ return (attester as any).eth as EthAccount;
733
+ }
734
+ return attester as any;
637
735
  }
638
736
  }
package/src/loader.ts CHANGED
@@ -285,17 +285,37 @@ export function mergeKeystores(keystores: KeyStore[]): KeyStore {
285
285
  * @returns Array of string keys used to detect duplicates.
286
286
  */
287
287
  function extractAttesterKeys(attester: unknown): string[] {
288
+ // String forms (private key or other) - return as-is for coarse uniqueness
288
289
  if (typeof attester === 'string') {
289
290
  return [attester];
290
291
  }
291
292
 
293
+ // Arrays of attester items
292
294
  if (Array.isArray(attester)) {
293
- return attester.map(a => (typeof a === 'string' ? a : JSON.stringify(a)));
295
+ const keys: string[] = [];
296
+ for (const item of attester) {
297
+ keys.push(...extractAttesterKeys(item));
298
+ }
299
+ return keys;
294
300
  }
295
301
 
296
- if (attester && typeof attester === 'object' && 'address' in attester) {
297
- return [(attester as { address: string }).address];
302
+ if (attester && typeof attester === 'object') {
303
+ const obj = attester as Record<string, unknown>;
304
+
305
+ // New shape: { eth: EthAccount, bls?: BLSAccount }
306
+ if ('eth' in obj) {
307
+ return extractAttesterKeys(obj.eth);
308
+ }
309
+
310
+ // Remote signer account object shape: { address, remoteSignerUrl?, ... }
311
+ if ('address' in obj) {
312
+ return [String((obj as any).address)];
313
+ }
314
+
315
+ // Mnemonic or other object shapes: stringify
316
+ return [JSON.stringify(attester)];
298
317
  }
299
318
 
319
+ // Fallback stringify for anything else (null/undefined)
300
320
  return [JSON.stringify(attester)];
301
321
  }
package/src/schemas.ts CHANGED
@@ -6,13 +6,17 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address';
6
6
 
7
7
  import { z } from 'zod';
8
8
 
9
- import type { EthPrivateKey } from './types.js';
9
+ import type { BLSPrivateKey, EthPrivateKey } from './types.js';
10
10
 
11
11
  // Use Aztec's validation functions but return string types to match our TypeScript interfaces
12
12
  export const ethPrivateKeySchema = z
13
13
  .string()
14
14
  .regex(/^0x[0-9a-fA-F]{64}$/, 'Invalid private key (must be 32 bytes with 0x prefix)')
15
15
  .transform(s => s as EthPrivateKey);
16
+ export const blsPrivateKeySchema = z
17
+ .string()
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);
16
20
  const urlSchema = z.string().url('Invalid URL');
17
21
 
18
22
  // Remote signer config schema
@@ -30,7 +34,7 @@ const remoteSignerAccountSchema = z.union([
30
34
  schemas.EthAddress,
31
35
  z.object({
32
36
  address: schemas.EthAddress,
33
- remoteSignerUrl: optional(urlSchema),
37
+ remoteSignerUrl: urlSchema,
34
38
  certPath: optional(z.string()),
35
39
  certPass: optional(z.string()),
36
40
  }),
@@ -57,6 +61,21 @@ const ethAccountSchema = z.union([ethPrivateKeySchema, remoteSignerAccountSchema
57
61
  // EthAccounts schema
58
62
  const ethAccountsSchema = z.union([ethAccountSchema, z.array(ethAccountSchema), mnemonicConfigSchema]);
59
63
 
64
+ // BLSAccount schema
65
+ const blsAccountSchema = z.union([blsPrivateKeySchema, jsonKeyFileV3Schema]);
66
+
67
+ // AttesterAccount schema: either EthAccount or { eth: EthAccount, bls?: BLSAccount }
68
+ const attesterAccountSchema = z.union([
69
+ ethAccountSchema,
70
+ z.object({
71
+ eth: ethAccountSchema,
72
+ bls: optional(blsAccountSchema),
73
+ }),
74
+ ]);
75
+
76
+ // AttesterAccounts schema: AttesterAccount | AttesterAccount[] | MnemonicConfig
77
+ const attesterAccountsSchema = z.union([attesterAccountSchema, z.array(attesterAccountSchema), mnemonicConfigSchema]);
78
+
60
79
  // Prover keystore schema
61
80
  const proverKeyStoreSchema = z.union([
62
81
  ethAccountSchema,
@@ -68,7 +87,7 @@ const proverKeyStoreSchema = z.union([
68
87
 
69
88
  // Validator keystore schema
70
89
  const validatorKeyStoreSchema = z.object({
71
- attester: ethAccountsSchema,
90
+ attester: attesterAccountsSchema,
72
91
  coinbase: optional(schemas.EthAddress),
73
92
  publisher: optional(ethAccountsSchema),
74
93
  feeRecipient: AztecAddress.schema,