@aztec/node-keystore 2.0.3 → 2.1.0-rc.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.
@@ -28,6 +28,11 @@ export declare class KeystoreManager {
28
28
  * @param keystore Parsed keystore configuration
29
29
  */
30
30
  constructor(keystore: KeyStore);
31
+ /**
32
+ * Validates all remote signers in the keystore are accessible and have the required addresses.
33
+ * Should be called after construction if validation is needed.
34
+ */
35
+ validateSigners(): Promise<void>;
31
36
  /**
32
37
  * Validates that attester addresses are unique across all validators
33
38
  * Only checks simple private key attesters, not JSON-V3 or mnemonic attesters,
@@ -1 +1 @@
1
- {"version":3,"file":"keystore_manager.d.ts","sourceRoot":"","sources":["../src/keystore_manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAKhD,OAAO,KAAK,EAEV,WAAW,EAIX,qBAAqB,EACrB,QAAQ,EACR,cAAc,EACd,iBAAiB,IAAI,uBAAuB,EAC7C,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGpB,KAAK,CAAC,EAAE,KAAK;gBAD7B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC;;;;OAIG;gBACS,QAAQ,EAAE,QAAQ;IAK9B;;;;;OAKG;IACH,OAAO,CAAC,+BAA+B;IAkBvC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IAoDlD;;OAEG;IACH,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAK1D;;OAEG;IACH,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAc3D,kCAAkC,IAAI,SAAS,EAAE;IAWjD;;OAEG;IACH,oBAAoB,IAAI,SAAS,EAAE;IAQnC;;OAEG;IACH,mBAAmB,IAAI;QAAE,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,SAAS;IAkCvF;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAOpD;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,UAAU;IAgBtD;;OAEG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY;IAKrD;;;OAGG;IACH,kBAAkB,IAAI,WAAW,GAAG,SAAS;IAI7C;;;OAGG;IACH,eAAe,IAAI,cAAc,GAAG,SAAS;IAI7C;;;OAGG;IACH,uCAAuC,IAAI,IAAI;IAqB/C;;OAEG;IACH,OAAO,CAAC,4BAA4B;IA+BpC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA2ClC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkD9B;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAwBxC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA8BjC;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAI3E;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAI1F;;;OAGG;IACH,8BAA8B,CAC5B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,UAAU,GAC1B,qBAAqB,GAAG,SAAS;CAkHrC"}
1
+ {"version":3,"file":"keystore_manager.d.ts","sourceRoot":"","sources":["../src/keystore_manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAKhD,OAAO,KAAK,EAEV,WAAW,EAIX,qBAAqB,EACrB,QAAQ,EACR,cAAc,EACd,iBAAiB,IAAI,uBAAuB,EAC7C,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAGpB,KAAK,CAAC,EAAE,KAAK;gBAD7B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC;;;;OAIG;gBACS,QAAQ,EAAE,QAAQ;IAK9B;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAwEtC;;;;;OAKG;IACH,OAAO,CAAC,+BAA+B;IAkBvC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IAoDlD;;OAEG;IACH,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAK1D;;OAEG;IACH,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,EAAE;IAc3D,kCAAkC,IAAI,SAAS,EAAE;IAWjD;;OAEG;IACH,oBAAoB,IAAI,SAAS,EAAE;IAQnC;;OAEG;IACH,mBAAmB,IAAI;QAAE,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,SAAS;IAkCvF;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAOpD;;OAEG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,UAAU;IAgBtD;;OAEG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY;IAKrD;;;OAGG;IACH,kBAAkB,IAAI,WAAW,GAAG,SAAS;IAI7C;;;OAGG;IACH,eAAe,IAAI,cAAc,GAAG,SAAS;IAI7C;;;OAGG;IACH,uCAAuC,IAAI,IAAI;IAqB/C;;OAEG;IACH,OAAO,CAAC,4BAA4B;IA+BpC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA2ClC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkD9B;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAwBxC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IA8BjC;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAI3E;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC;IAI1F;;;OAGG;IACH,8BAA8B,CAC5B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,UAAU,GAC1B,qBAAqB,GAAG,SAAS;CAkHrC"}
@@ -31,6 +31,70 @@ import { LocalSigner, RemoteSigner } from './signer.js';
31
31
  this.validateUniqueAttesterAddresses();
32
32
  }
33
33
  /**
34
+ * Validates all remote signers in the keystore are accessible and have the required addresses.
35
+ * Should be called after construction if validation is needed.
36
+ */ async validateSigners() {
37
+ // Collect all remote signers with their addresses grouped by URL
38
+ const remoteSignersByUrl = new Map();
39
+ // Helper to extract remote signer URL from config
40
+ const getUrl = (config)=>{
41
+ return typeof config === 'string' ? config : config.remoteSignerUrl;
42
+ };
43
+ // Helper to collect remote signers from accounts
44
+ const collectRemoteSigners = (accounts, defaultRemoteSigner)=>{
45
+ const processAccount = (account)=>{
46
+ if (typeof account === 'object' && !('path' in account) && !('mnemonic' in account)) {
47
+ // This is a remote signer account
48
+ const remoteSigner = account;
49
+ const address = 'address' in remoteSigner ? remoteSigner.address : remoteSigner;
50
+ let url;
51
+ if ('remoteSignerUrl' in remoteSigner && remoteSigner.remoteSignerUrl) {
52
+ url = remoteSigner.remoteSignerUrl;
53
+ } else if (defaultRemoteSigner) {
54
+ url = getUrl(defaultRemoteSigner);
55
+ } else {
56
+ return; // No remote signer URL available
57
+ }
58
+ if (!remoteSignersByUrl.has(url)) {
59
+ remoteSignersByUrl.set(url, new Set());
60
+ }
61
+ remoteSignersByUrl.get(url).add(address.toString());
62
+ }
63
+ };
64
+ if (Array.isArray(accounts)) {
65
+ accounts.forEach((account)=>collectRemoteSigners(account, defaultRemoteSigner));
66
+ } else if (typeof accounts === 'object' && 'mnemonic' in accounts) {
67
+ // Skip mnemonic configs
68
+ } else {
69
+ processAccount(accounts);
70
+ }
71
+ };
72
+ // Collect from validators
73
+ const validatorCount = this.getValidatorCount();
74
+ for(let i = 0; i < validatorCount; i++){
75
+ const validator = this.getValidator(i);
76
+ const remoteSigner = validator.remoteSigner || this.keystore.remoteSigner;
77
+ collectRemoteSigners(validator.attester, remoteSigner);
78
+ if (validator.publisher) {
79
+ collectRemoteSigners(validator.publisher, remoteSigner);
80
+ }
81
+ }
82
+ // Collect from slasher
83
+ if (this.keystore.slasher) {
84
+ collectRemoteSigners(this.keystore.slasher, this.keystore.remoteSigner);
85
+ }
86
+ // Collect from prover
87
+ if (this.keystore.prover && typeof this.keystore.prover === 'object' && 'publisher' in this.keystore.prover) {
88
+ collectRemoteSigners(this.keystore.prover.publisher, this.keystore.remoteSigner);
89
+ }
90
+ // Validate each remote signer URL with all its addresses
91
+ for (const [url, addresses] of remoteSignersByUrl.entries()){
92
+ if (addresses.size > 0) {
93
+ await RemoteSigner.validateAccess(url, Array.from(addresses));
94
+ }
95
+ }
96
+ }
97
+ /**
34
98
  * Validates that attester addresses are unique across all validators
35
99
  * Only checks simple private key attesters, not JSON-V3 or mnemonic attesters,
36
100
  * these are validated when decrypting the JSON-V3 keystore files
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/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.1",
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.1",
66
+ "@aztec/foundation": "2.1.0-rc.1",
67
+ "@aztec/stdlib": "2.1.0-rc.1",
68
68
  "@ethersproject/wallet": "^5.7.0",
69
69
  "tslib": "^2.4.0",
70
70
  "viem": "2.23.7",
@@ -58,6 +58,82 @@ export class KeystoreManager {
58
58
  this.validateUniqueAttesterAddresses();
59
59
  }
60
60
 
61
+ /**
62
+ * Validates all remote signers in the keystore are accessible and have the required addresses.
63
+ * Should be called after construction if validation is needed.
64
+ */
65
+ async validateSigners(): Promise<void> {
66
+ // Collect all remote signers with their addresses grouped by URL
67
+ const remoteSignersByUrl = new Map<string, Set<string>>();
68
+
69
+ // Helper to extract remote signer URL from config
70
+ const getUrl = (config: EthRemoteSignerConfig): string => {
71
+ return typeof config === 'string' ? config : config.remoteSignerUrl;
72
+ };
73
+
74
+ // Helper to collect remote signers from accounts
75
+ const collectRemoteSigners = (accounts: EthAccounts, defaultRemoteSigner?: EthRemoteSignerConfig): void => {
76
+ const processAccount = (account: EthAccount): void => {
77
+ if (typeof account === 'object' && !('path' in account) && !('mnemonic' in (account as any))) {
78
+ // This is a remote signer account
79
+ const remoteSigner = account as EthRemoteSignerAccount;
80
+ const address = 'address' in remoteSigner ? remoteSigner.address : remoteSigner;
81
+
82
+ let url: string;
83
+ if ('remoteSignerUrl' in remoteSigner && remoteSigner.remoteSignerUrl) {
84
+ url = remoteSigner.remoteSignerUrl;
85
+ } else if (defaultRemoteSigner) {
86
+ url = getUrl(defaultRemoteSigner);
87
+ } else {
88
+ return; // No remote signer URL available
89
+ }
90
+
91
+ if (!remoteSignersByUrl.has(url)) {
92
+ remoteSignersByUrl.set(url, new Set());
93
+ }
94
+ remoteSignersByUrl.get(url)!.add(address.toString());
95
+ }
96
+ };
97
+
98
+ if (Array.isArray(accounts)) {
99
+ accounts.forEach(account => collectRemoteSigners(account, defaultRemoteSigner));
100
+ } else if (typeof accounts === 'object' && 'mnemonic' in accounts) {
101
+ // Skip mnemonic configs
102
+ } else {
103
+ processAccount(accounts as EthAccount);
104
+ }
105
+ };
106
+
107
+ // Collect from validators
108
+ const validatorCount = this.getValidatorCount();
109
+ for (let i = 0; i < validatorCount; i++) {
110
+ const validator = this.getValidator(i);
111
+ const remoteSigner = validator.remoteSigner || this.keystore.remoteSigner;
112
+
113
+ collectRemoteSigners(validator.attester, remoteSigner);
114
+ if (validator.publisher) {
115
+ collectRemoteSigners(validator.publisher, remoteSigner);
116
+ }
117
+ }
118
+
119
+ // Collect from slasher
120
+ if (this.keystore.slasher) {
121
+ collectRemoteSigners(this.keystore.slasher, this.keystore.remoteSigner);
122
+ }
123
+
124
+ // Collect from prover
125
+ if (this.keystore.prover && typeof this.keystore.prover === 'object' && 'publisher' in this.keystore.prover) {
126
+ collectRemoteSigners(this.keystore.prover.publisher, this.keystore.remoteSigner);
127
+ }
128
+
129
+ // Validate each remote signer URL with all its addresses
130
+ for (const [url, addresses] of remoteSignersByUrl.entries()) {
131
+ if (addresses.size > 0) {
132
+ await RemoteSigner.validateAccess(url, Array.from(addresses));
133
+ }
134
+ }
135
+ }
136
+
61
137
  /**
62
138
  * Validates that attester addresses are unique across all validators
63
139
  * Only checks simple private key attesters, not JSON-V3 or mnemonic attesters,
package/src/signer.ts CHANGED
@@ -104,6 +104,82 @@ export class RemoteSigner implements EthSigner {
104
104
  private fetch: typeof globalThis.fetch = globalThis.fetch,
105
105
  ) {}
106
106
 
107
+ /**
108
+ * Validates that a web3signer is accessible and that the given addresses are available.
109
+ * @param remoteSignerUrl - The URL of the web3signer (can be string or EthRemoteSignerConfig)
110
+ * @param addresses - The addresses to check for availability
111
+ * @param fetch - Optional fetch implementation for testing
112
+ * @throws Error if the web3signer is not accessible or if any address is not available
113
+ */
114
+ static async validateAccess(
115
+ remoteSignerUrl: EthRemoteSignerConfig,
116
+ addresses: string[],
117
+ fetch: typeof globalThis.fetch = globalThis.fetch,
118
+ ): Promise<void> {
119
+ const url = typeof remoteSignerUrl === 'string' ? remoteSignerUrl : remoteSignerUrl.remoteSignerUrl;
120
+
121
+ try {
122
+ // Check if the web3signer is reachable by calling eth_accounts
123
+ const response = await fetch(url, {
124
+ method: 'POST',
125
+ headers: {
126
+ 'Content-Type': 'application/json',
127
+ },
128
+ body: JSON.stringify({
129
+ jsonrpc: '2.0',
130
+ method: 'eth_accounts',
131
+ params: [],
132
+ id: 1,
133
+ }),
134
+ });
135
+
136
+ if (!response.ok) {
137
+ const errorText = await response.text();
138
+ throw new SignerError(
139
+ `Web3Signer validation failed: ${response.status} ${response.statusText} - ${errorText}`,
140
+ 'eth_accounts',
141
+ url,
142
+ response.status,
143
+ );
144
+ }
145
+
146
+ const result = await response.json();
147
+
148
+ if (result.error) {
149
+ throw new SignerError(
150
+ `Web3Signer JSON-RPC error during validation: ${result.error.code} - ${result.error.message}`,
151
+ 'eth_accounts',
152
+ url,
153
+ 200,
154
+ result.error.code,
155
+ );
156
+ }
157
+
158
+ if (!result.result || !Array.isArray(result.result)) {
159
+ throw new Error('Invalid response from Web3Signer: expected array of accounts');
160
+ }
161
+
162
+ // Normalize addresses to lowercase for comparison
163
+ const availableAccounts: string[] = result.result.map((addr: string) => addr.toLowerCase());
164
+ const requestedAddresses = addresses.map(addr => addr.toLowerCase());
165
+
166
+ // Check if all requested addresses are available
167
+ const missingAddresses = requestedAddresses.filter(addr => !availableAccounts.includes(addr));
168
+
169
+ if (missingAddresses.length > 0) {
170
+ throw new Error(`The following addresses are not available in the web3signer: ${missingAddresses.join(', ')}`);
171
+ }
172
+ } catch (error: any) {
173
+ if (error instanceof SignerError) {
174
+ throw error;
175
+ }
176
+ if (error.code === 'ECONNREFUSED' || error.cause?.code === 'ECONNREFUSED') {
177
+ throw new Error(`Unable to connect to web3signer at ${url}. Please ensure it is running and accessible.`);
178
+ }
179
+ throw error;
180
+ }
181
+ }
182
+
107
183
  /**
108
184
  * Sign a message using eth_sign via remote JSON-RPC.
109
185
  */