@aztec/node-keystore 2.0.0-nightly.20250826 → 2.0.0-nightly.20250828
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 +7 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +10 -0
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/keystore_manager.d.ts +11 -7
- package/dest/keystore_manager.d.ts.map +1 -1
- package/dest/keystore_manager.js +18 -3
- package/dest/signer.d.ts +15 -29
- package/dest/signer.d.ts.map +1 -1
- package/dest/signer.js +78 -11
- package/package.json +4 -3
- package/src/config.ts +16 -0
- package/src/index.ts +1 -0
- package/src/keystore_manager.ts +41 -19
- package/src/signer.ts +144 -37
package/dest/config.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ConfigMappingsType } from '@aztec/foundation/config';
|
|
2
|
+
export type KeyStoreConfig = {
|
|
3
|
+
keyStoreDirectory: string | undefined;
|
|
4
|
+
};
|
|
5
|
+
export declare const keyStoreConfigMappings: ConfigMappingsType<KeyStoreConfig>;
|
|
6
|
+
export declare function getKeyStoreConfigFromEnv(): KeyStoreConfig;
|
|
7
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAyB,MAAM,0BAA0B,CAAC;AAE1F,MAAM,MAAM,cAAc,GAAG;IAC3B,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,kBAAkB,CAAC,cAAc,CAKrE,CAAC;AAEF,wBAAgB,wBAAwB,IAAI,cAAc,CAEzD"}
|
package/dest/config.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getConfigFromMappings } from '@aztec/foundation/config';
|
|
2
|
+
export const keyStoreConfigMappings = {
|
|
3
|
+
keyStoreDirectory: {
|
|
4
|
+
env: 'KEY_STORE_DIRECTORY',
|
|
5
|
+
description: 'Location of key store directory'
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
export function getKeyStoreConfigFromEnv() {
|
|
9
|
+
return getConfigFromMappings(keyStoreConfigMappings);
|
|
10
|
+
}
|
package/dest/index.d.ts
CHANGED
package/dest/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC"}
|
package/dest/index.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Manages keystore configuration and delegates signing operations to appropriate signers.
|
|
5
5
|
*/
|
|
6
|
+
import type { EthSigner } from '@aztec/ethereum';
|
|
6
7
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
7
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
8
9
|
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
9
10
|
import type { TypedDataDefinition } from 'viem';
|
|
10
|
-
import { type Signer } from './signer.js';
|
|
11
11
|
import type { EthAccounts, EthRemoteSignerConfig, KeyStore, ProverKeyStore, ValidatorKeyStore as ValidatorKeystoreConfig } from './types.js';
|
|
12
12
|
/**
|
|
13
13
|
* Error thrown when keystore operations fail
|
|
@@ -42,19 +42,23 @@ export declare class KeystoreManager {
|
|
|
42
42
|
/**
|
|
43
43
|
* Create signers for validator attester accounts
|
|
44
44
|
*/
|
|
45
|
-
createAttesterSigners(validatorIndex: number):
|
|
45
|
+
createAttesterSigners(validatorIndex: number): EthSigner[];
|
|
46
46
|
/**
|
|
47
47
|
* Create signers for validator publisher accounts (falls back to attester if not specified)
|
|
48
48
|
*/
|
|
49
|
-
createPublisherSigners(validatorIndex: number):
|
|
49
|
+
createPublisherSigners(validatorIndex: number): EthSigner[];
|
|
50
|
+
createAllValidatorPublisherSigners(): EthSigner[];
|
|
50
51
|
/**
|
|
51
52
|
* Create signers for slasher accounts
|
|
52
53
|
*/
|
|
53
|
-
createSlasherSigners():
|
|
54
|
+
createSlasherSigners(): EthSigner[];
|
|
54
55
|
/**
|
|
55
56
|
* Create signers for prover accounts
|
|
56
57
|
*/
|
|
57
|
-
createProverSigners():
|
|
58
|
+
createProverSigners(): {
|
|
59
|
+
id: EthAddress | undefined;
|
|
60
|
+
signers: EthSigner[];
|
|
61
|
+
} | undefined;
|
|
58
62
|
/**
|
|
59
63
|
* Get validator configuration by index
|
|
60
64
|
*/
|
|
@@ -109,11 +113,11 @@ export declare class KeystoreManager {
|
|
|
109
113
|
/**
|
|
110
114
|
* Sign message with a specific signer
|
|
111
115
|
*/
|
|
112
|
-
signMessage(signer:
|
|
116
|
+
signMessage(signer: EthSigner, message: Buffer32): Promise<Signature>;
|
|
113
117
|
/**
|
|
114
118
|
* Sign typed data with a specific signer
|
|
115
119
|
*/
|
|
116
|
-
signTypedData(signer:
|
|
120
|
+
signTypedData(signer: EthSigner, typedData: TypedDataDefinition): Promise<Signature>;
|
|
117
121
|
/**
|
|
118
122
|
* Get the effective remote signer configuration for a specific attester address
|
|
119
123
|
* Precedence: account-level override > validator-level config > file-level default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keystore_manager.d.ts","sourceRoot":"","sources":["../src/keystore_manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,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;AAKjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;
|
|
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;AAKjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAIhD,OAAO,KAAK,EAEV,WAAW,EAKX,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;IAiElD;;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;IAiCvF;;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,MAAM;IAK/C;;;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;IA+ClC;;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;CAiIrC"}
|
package/dest/keystore_manager.js
CHANGED
|
@@ -125,6 +125,14 @@ import { LocalSigner, RemoteSigner } from './signer.js';
|
|
|
125
125
|
// Fall back to attester signers
|
|
126
126
|
return this.createAttesterSigners(validatorIndex);
|
|
127
127
|
}
|
|
128
|
+
createAllValidatorPublisherSigners() {
|
|
129
|
+
const numValidators = this.getValidatorCount();
|
|
130
|
+
const allPublishers = [];
|
|
131
|
+
for(let i = 0; i < numValidators; i++){
|
|
132
|
+
allPublishers.push(...this.createPublisherSigners(i));
|
|
133
|
+
}
|
|
134
|
+
return allPublishers;
|
|
135
|
+
}
|
|
128
136
|
/**
|
|
129
137
|
* Create signers for slasher accounts
|
|
130
138
|
*/ createSlasherSigners() {
|
|
@@ -137,11 +145,15 @@ import { LocalSigner, RemoteSigner } from './signer.js';
|
|
|
137
145
|
* Create signers for prover accounts
|
|
138
146
|
*/ createProverSigners() {
|
|
139
147
|
if (!this.keystore.prover) {
|
|
140
|
-
return
|
|
148
|
+
return undefined;
|
|
141
149
|
}
|
|
142
150
|
// Handle simple prover case (just a private key)
|
|
143
151
|
if (typeof this.keystore.prover === 'string' || 'path' in this.keystore.prover || 'address' in this.keystore.prover) {
|
|
144
|
-
|
|
152
|
+
const signers = this.createSignersFromEthAccounts(this.keystore.prover, this.keystore.remoteSigner);
|
|
153
|
+
return {
|
|
154
|
+
id: undefined,
|
|
155
|
+
signers
|
|
156
|
+
};
|
|
145
157
|
}
|
|
146
158
|
// Handle complex prover case with id and publishers
|
|
147
159
|
const proverConfig = this.keystore.prover;
|
|
@@ -150,7 +162,10 @@ import { LocalSigner, RemoteSigner } from './signer.js';
|
|
|
150
162
|
const publisherSigners = this.createSignersFromEthAccounts(publisherAccounts, this.keystore.remoteSigner);
|
|
151
163
|
signers.push(...publisherSigners);
|
|
152
164
|
}
|
|
153
|
-
return
|
|
165
|
+
return {
|
|
166
|
+
id: EthAddress.fromString(proverConfig.id),
|
|
167
|
+
signers
|
|
168
|
+
};
|
|
154
169
|
}
|
|
155
170
|
/**
|
|
156
171
|
* Get validator configuration by index
|
package/dest/signer.d.ts
CHANGED
|
@@ -3,58 +3,42 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Common interface for different signing backends (local, remote, encrypted)
|
|
5
5
|
*/
|
|
6
|
+
import type { EthSigner } from '@aztec/ethereum';
|
|
6
7
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
7
8
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
8
9
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
9
|
-
import type
|
|
10
|
+
import { type TransactionSerializable, type TypedDataDefinition } from 'viem';
|
|
10
11
|
import type { EthRemoteSignerConfig } from './types.js';
|
|
11
12
|
/**
|
|
12
13
|
* Error thrown for remote signer HTTP or JSON-RPC failures
|
|
13
14
|
*/
|
|
14
15
|
export declare class SignerError extends Error {
|
|
15
|
-
method: 'eth_sign' | 'eth_signTypedData_v4';
|
|
16
|
+
method: 'eth_sign' | 'eth_signTransaction' | 'eth_signTypedData_v4';
|
|
16
17
|
url: string;
|
|
17
18
|
statusCode?: number | undefined;
|
|
18
19
|
errorCode?: number | undefined;
|
|
19
|
-
constructor(message: string, method: 'eth_sign' | 'eth_signTypedData_v4', url: string, statusCode?: number | undefined, errorCode?: number | undefined);
|
|
20
|
+
constructor(message: string, method: 'eth_sign' | 'eth_signTransaction' | 'eth_signTypedData_v4', url: string, statusCode?: number | undefined, errorCode?: number | undefined);
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Common interface for all signer implementations
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* Abstraction for signing operations used by the node keystore.
|
|
26
|
-
*/
|
|
27
|
-
export interface Signer {
|
|
28
|
-
/** The Ethereum address for this signer */
|
|
29
|
-
readonly address: EthAddress;
|
|
30
|
-
/** Sign a message using eth_sign (with Ethereum message prefix) */
|
|
31
|
-
signMessage(message: Buffer32): Promise<Signature>;
|
|
32
|
-
/** Sign typed data using EIP-712 */
|
|
33
|
-
signTypedData(typedData: TypedDataDefinition): Promise<Signature>;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Local signer using in-memory private key
|
|
37
|
-
*/
|
|
38
22
|
/**
|
|
39
23
|
* Local signer that holds an in-memory Secp256k1 private key.
|
|
40
24
|
*/
|
|
41
|
-
export declare class LocalSigner implements
|
|
25
|
+
export declare class LocalSigner implements EthSigner {
|
|
26
|
+
private privateKey;
|
|
42
27
|
private readonly signer;
|
|
43
28
|
constructor(privateKey: Buffer32);
|
|
44
29
|
get address(): EthAddress;
|
|
45
30
|
signMessage(message: Buffer32): Promise<Signature>;
|
|
46
31
|
signTypedData(typedData: TypedDataDefinition): Promise<Signature>;
|
|
32
|
+
signTransaction(transaction: TransactionSerializable): Promise<Signature>;
|
|
47
33
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Remote signer using Web3Signer HTTP API
|
|
50
|
-
*/
|
|
51
34
|
/**
|
|
52
35
|
* Remote signer that proxies signing operations to a Web3Signer-compatible HTTP endpoint.
|
|
53
36
|
*/
|
|
54
|
-
export declare class RemoteSigner implements
|
|
37
|
+
export declare class RemoteSigner implements EthSigner {
|
|
55
38
|
readonly address: EthAddress;
|
|
56
39
|
private readonly config;
|
|
57
|
-
|
|
40
|
+
private fetch;
|
|
41
|
+
constructor(address: EthAddress, config: EthRemoteSignerConfig, fetch?: typeof globalThis.fetch);
|
|
58
42
|
/**
|
|
59
43
|
* Sign a message using eth_sign via remote JSON-RPC.
|
|
60
44
|
*/
|
|
@@ -63,6 +47,7 @@ export declare class RemoteSigner implements Signer {
|
|
|
63
47
|
* Sign typed data using eth_signTypedData_v4 via remote JSON-RPC.
|
|
64
48
|
*/
|
|
65
49
|
signTypedData(typedData: TypedDataDefinition): Promise<Signature>;
|
|
50
|
+
signTransaction(transaction: TransactionSerializable): Promise<Signature>;
|
|
66
51
|
/**
|
|
67
52
|
* Make a JSON-RPC sign request using eth_sign
|
|
68
53
|
*/
|
|
@@ -70,13 +55,14 @@ export declare class RemoteSigner implements Signer {
|
|
|
70
55
|
* Make a JSON-RPC eth_sign request.
|
|
71
56
|
*/
|
|
72
57
|
private makeJsonRpcSignRequest;
|
|
73
|
-
/**
|
|
74
|
-
* Make a JSON-RPC sign typed data request using eth_signTypedData_v4
|
|
75
|
-
*/
|
|
76
58
|
/**
|
|
77
59
|
* Make a JSON-RPC eth_signTypedData_v4 request.
|
|
78
60
|
*/
|
|
79
61
|
private makeJsonRpcSignTypedDataRequest;
|
|
62
|
+
/**
|
|
63
|
+
* Make a JSON-RPC eth_signTransaction request.
|
|
64
|
+
*/
|
|
65
|
+
private makeJsonRpcSignTransactionRequest;
|
|
80
66
|
/**
|
|
81
67
|
* Resolve the effective remote signer URL from config.
|
|
82
68
|
*/
|
package/dest/signer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,
|
|
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;AAG3F,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,UAAU,GAAG,qBAAqB,GAAG,sBAAsB;IACnE,GAAG,EAAE,MAAM;IACX,UAAU,CAAC,EAAE,MAAM;IACnB,SAAS,CAAC,EAAE,MAAM;gBAJzB,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,UAAU,GAAG,qBAAqB,GAAG,sBAAsB,EACnE,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;IAoDpC;;OAEG;YACW,+BAA+B;IAoD7C;;OAEG;YACW,iCAAiC;IAqF/C;;OAEG;IACH,OAAO,CAAC,YAAY;CAMrB"}
|
package/dest/signer.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Common interface for different signing backends (local, remote, encrypted)
|
|
5
5
|
*/ import { Buffer32 } from '@aztec/foundation/buffer';
|
|
6
|
-
import { Secp256k1Signer } from '@aztec/foundation/crypto';
|
|
6
|
+
import { Secp256k1Signer, toRecoveryBit } from '@aztec/foundation/crypto';
|
|
7
7
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
8
|
-
import {
|
|
8
|
+
import { withHexPrefix } from '@aztec/foundation/string';
|
|
9
|
+
import { hashTypedData, keccak256, parseTransaction, serializeTransaction } from 'viem';
|
|
9
10
|
/**
|
|
10
11
|
* Error thrown for remote signer HTTP or JSON-RPC failures
|
|
11
12
|
*/ export class SignerError extends Error {
|
|
@@ -19,12 +20,12 @@ import { hashTypedData } from 'viem';
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
|
-
* Local signer using in-memory private key
|
|
23
|
-
*/ /**
|
|
24
23
|
* Local signer that holds an in-memory Secp256k1 private key.
|
|
25
24
|
*/ export class LocalSigner {
|
|
25
|
+
privateKey;
|
|
26
26
|
signer;
|
|
27
27
|
constructor(privateKey){
|
|
28
|
+
this.privateKey = privateKey;
|
|
28
29
|
this.signer = new Secp256k1Signer(privateKey);
|
|
29
30
|
}
|
|
30
31
|
get address() {
|
|
@@ -37,17 +38,28 @@ import { hashTypedData } from 'viem';
|
|
|
37
38
|
const digest = hashTypedData(typedData);
|
|
38
39
|
return Promise.resolve(this.signer.sign(Buffer32.fromString(digest)));
|
|
39
40
|
}
|
|
41
|
+
signTransaction(transaction) {
|
|
42
|
+
// Taken from viem's `signTransaction` implementation
|
|
43
|
+
const tx = transaction.type === 'eip4844' ? {
|
|
44
|
+
...transaction,
|
|
45
|
+
sidecars: false
|
|
46
|
+
} : transaction;
|
|
47
|
+
const serializedTx = serializeTransaction(tx);
|
|
48
|
+
const txHash = keccak256(serializedTx);
|
|
49
|
+
const sig = this.signer.sign(Buffer32.fromString(txHash.slice(2)));
|
|
50
|
+
return Promise.resolve(new Signature(sig.r, sig.s, toRecoveryBit(sig.v)));
|
|
51
|
+
}
|
|
40
52
|
}
|
|
41
53
|
/**
|
|
42
|
-
* Remote signer using Web3Signer HTTP API
|
|
43
|
-
*/ /**
|
|
44
54
|
* Remote signer that proxies signing operations to a Web3Signer-compatible HTTP endpoint.
|
|
45
55
|
*/ export class RemoteSigner {
|
|
46
56
|
address;
|
|
47
57
|
config;
|
|
48
|
-
|
|
58
|
+
fetch;
|
|
59
|
+
constructor(address, config, fetch = globalThis.fetch){
|
|
49
60
|
this.address = address;
|
|
50
61
|
this.config = config;
|
|
62
|
+
this.fetch = fetch;
|
|
51
63
|
}
|
|
52
64
|
/**
|
|
53
65
|
* Sign a message using eth_sign via remote JSON-RPC.
|
|
@@ -59,6 +71,9 @@ import { hashTypedData } from 'viem';
|
|
|
59
71
|
*/ async signTypedData(typedData) {
|
|
60
72
|
return await this.makeJsonRpcSignTypedDataRequest(typedData);
|
|
61
73
|
}
|
|
74
|
+
signTransaction(transaction) {
|
|
75
|
+
return this.makeJsonRpcSignTransactionRequest(transaction);
|
|
76
|
+
}
|
|
62
77
|
/**
|
|
63
78
|
* Make a JSON-RPC sign request using eth_sign
|
|
64
79
|
*/ /**
|
|
@@ -74,7 +89,7 @@ import { hashTypedData } from 'viem';
|
|
|
74
89
|
],
|
|
75
90
|
id: 1
|
|
76
91
|
};
|
|
77
|
-
const response = await fetch(url, {
|
|
92
|
+
const response = await this.fetch(url, {
|
|
78
93
|
method: 'POST',
|
|
79
94
|
headers: {
|
|
80
95
|
'Content-Type': 'application/json'
|
|
@@ -99,8 +114,6 @@ import { hashTypedData } from 'viem';
|
|
|
99
114
|
return Signature.fromString(signatureHex);
|
|
100
115
|
}
|
|
101
116
|
/**
|
|
102
|
-
* Make a JSON-RPC sign typed data request using eth_signTypedData_v4
|
|
103
|
-
*/ /**
|
|
104
117
|
* Make a JSON-RPC eth_signTypedData_v4 request.
|
|
105
118
|
*/ async makeJsonRpcSignTypedDataRequest(typedData) {
|
|
106
119
|
const url = this.getSignerUrl();
|
|
@@ -113,7 +126,7 @@ import { hashTypedData } from 'viem';
|
|
|
113
126
|
],
|
|
114
127
|
id: 1
|
|
115
128
|
};
|
|
116
|
-
const response = await fetch(url, {
|
|
129
|
+
const response = await this.fetch(url, {
|
|
117
130
|
method: 'POST',
|
|
118
131
|
headers: {
|
|
119
132
|
'Content-Type': 'application/json'
|
|
@@ -138,6 +151,60 @@ import { hashTypedData } from 'viem';
|
|
|
138
151
|
return Signature.fromString(signatureHex);
|
|
139
152
|
}
|
|
140
153
|
/**
|
|
154
|
+
* Make a JSON-RPC eth_signTransaction request.
|
|
155
|
+
*/ async makeJsonRpcSignTransactionRequest(tx) {
|
|
156
|
+
if (tx.type !== 'eip1559') {
|
|
157
|
+
throw new Error('This signer does not support tx type: ' + tx.type);
|
|
158
|
+
}
|
|
159
|
+
const url = this.getSignerUrl();
|
|
160
|
+
const txObject = {
|
|
161
|
+
from: this.address.toString(),
|
|
162
|
+
to: tx.to ?? null,
|
|
163
|
+
data: tx.data,
|
|
164
|
+
value: typeof tx.value !== 'undefined' ? withHexPrefix(tx.value.toString(16)) : undefined,
|
|
165
|
+
nonce: typeof tx.nonce !== 'undefined' ? withHexPrefix(tx.nonce.toString(16)) : undefined,
|
|
166
|
+
gas: typeof tx.gas !== 'undefined' ? withHexPrefix(tx.gas.toString(16)) : undefined,
|
|
167
|
+
maxFeePerGas: typeof tx.maxFeePerGas !== 'undefined' ? withHexPrefix(tx.maxFeePerGas.toString(16)) : undefined,
|
|
168
|
+
maxPriorityFeePerGas: typeof tx.maxPriorityFeePerGas !== 'undefined' ? withHexPrefix(tx.maxPriorityFeePerGas.toString(16)) : undefined
|
|
169
|
+
};
|
|
170
|
+
const body = {
|
|
171
|
+
jsonrpc: '2.0',
|
|
172
|
+
method: 'eth_signTransaction',
|
|
173
|
+
params: [
|
|
174
|
+
txObject
|
|
175
|
+
],
|
|
176
|
+
id: 1
|
|
177
|
+
};
|
|
178
|
+
const response = await this.fetch(url, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: {
|
|
181
|
+
'Content-Type': 'application/json'
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify(body)
|
|
184
|
+
});
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
const errorText = await response.text();
|
|
187
|
+
throw new SignerError(`Web3Signer request failed for eth_signTransaction at ${url}: ${response.status} ${response.statusText} - ${errorText}`, 'eth_signTransaction', url, response.status);
|
|
188
|
+
}
|
|
189
|
+
const result = await response.json();
|
|
190
|
+
if (result.error) {
|
|
191
|
+
throw new SignerError(`Web3Signer JSON-RPC error for eth_signTransaction at ${url}: ${result.error.code} - ${result.error.message}`, 'eth_signTransaction', url, undefined, result.error.code);
|
|
192
|
+
}
|
|
193
|
+
if (!result.result) {
|
|
194
|
+
throw new Error('Invalid response from Web3Signer: no result found');
|
|
195
|
+
}
|
|
196
|
+
let rawTxHex = result.result;
|
|
197
|
+
if (!rawTxHex.startsWith('0x')) {
|
|
198
|
+
rawTxHex = '0x' + rawTxHex;
|
|
199
|
+
}
|
|
200
|
+
// we get back to whole signed tx. Deserialize it in order to read the signature
|
|
201
|
+
const parsedTxWithSignature = parseTransaction(rawTxHex);
|
|
202
|
+
if (parsedTxWithSignature.r === undefined || parsedTxWithSignature.s === undefined || parsedTxWithSignature.v === undefined) {
|
|
203
|
+
throw new Error('Tx not signed by Web3Signer');
|
|
204
|
+
}
|
|
205
|
+
return Signature.fromViemTransactionSignature(parsedTxWithSignature);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
141
208
|
* Resolve the effective remote signer URL from config.
|
|
142
209
|
*/ getSignerUrl() {
|
|
143
210
|
if (typeof this.config === 'string') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/node-keystore",
|
|
3
|
-
"version": "2.0.0-nightly.
|
|
3
|
+
"version": "2.0.0-nightly.20250828",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -62,8 +62,9 @@
|
|
|
62
62
|
]
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@aztec/
|
|
66
|
-
"@aztec/
|
|
65
|
+
"@aztec/ethereum": "2.0.0-nightly.20250828",
|
|
66
|
+
"@aztec/foundation": "2.0.0-nightly.20250828",
|
|
67
|
+
"@aztec/stdlib": "2.0.0-nightly.20250828",
|
|
67
68
|
"@ethersproject/wallet": "^5.7.0",
|
|
68
69
|
"tslib": "^2.4.0",
|
|
69
70
|
"viem": "2.23.7",
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config';
|
|
2
|
+
|
|
3
|
+
export type KeyStoreConfig = {
|
|
4
|
+
keyStoreDirectory: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const keyStoreConfigMappings: ConfigMappingsType<KeyStoreConfig> = {
|
|
8
|
+
keyStoreDirectory: {
|
|
9
|
+
env: 'KEY_STORE_DIRECTORY',
|
|
10
|
+
description: 'Location of key store directory',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function getKeyStoreConfigFromEnv(): KeyStoreConfig {
|
|
15
|
+
return getConfigFromMappings<KeyStoreConfig>(keyStoreConfigMappings);
|
|
16
|
+
}
|
package/src/index.ts
CHANGED
package/src/keystore_manager.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Manages keystore configuration and delegates signing operations to appropriate signers.
|
|
5
5
|
*/
|
|
6
|
+
import type { EthSigner } from '@aztec/ethereum';
|
|
6
7
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
7
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
8
9
|
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
@@ -13,7 +14,7 @@ import { extname, join } from 'path';
|
|
|
13
14
|
import type { TypedDataDefinition } from 'viem';
|
|
14
15
|
import { mnemonicToAccount } from 'viem/accounts';
|
|
15
16
|
|
|
16
|
-
import { LocalSigner, RemoteSigner
|
|
17
|
+
import { LocalSigner, RemoteSigner } from './signer.js';
|
|
17
18
|
import type {
|
|
18
19
|
EthAccount,
|
|
19
20
|
EthAccounts,
|
|
@@ -152,7 +153,7 @@ export class KeystoreManager {
|
|
|
152
153
|
/**
|
|
153
154
|
* Create signers for validator attester accounts
|
|
154
155
|
*/
|
|
155
|
-
createAttesterSigners(validatorIndex: number):
|
|
156
|
+
createAttesterSigners(validatorIndex: number): EthSigner[] {
|
|
156
157
|
const validator = this.getValidator(validatorIndex);
|
|
157
158
|
return this.createSignersFromEthAccounts(validator.attester, validator.remoteSigner || this.keystore.remoteSigner);
|
|
158
159
|
}
|
|
@@ -160,7 +161,7 @@ export class KeystoreManager {
|
|
|
160
161
|
/**
|
|
161
162
|
* Create signers for validator publisher accounts (falls back to attester if not specified)
|
|
162
163
|
*/
|
|
163
|
-
createPublisherSigners(validatorIndex: number):
|
|
164
|
+
createPublisherSigners(validatorIndex: number): EthSigner[] {
|
|
164
165
|
const validator = this.getValidator(validatorIndex);
|
|
165
166
|
|
|
166
167
|
if (validator.publisher) {
|
|
@@ -174,10 +175,21 @@ export class KeystoreManager {
|
|
|
174
175
|
return this.createAttesterSigners(validatorIndex);
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
createAllValidatorPublisherSigners(): EthSigner[] {
|
|
179
|
+
const numValidators = this.getValidatorCount();
|
|
180
|
+
const allPublishers = [];
|
|
181
|
+
|
|
182
|
+
for (let i = 0; i < numValidators; i++) {
|
|
183
|
+
allPublishers.push(...this.createPublisherSigners(i));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return allPublishers;
|
|
187
|
+
}
|
|
188
|
+
|
|
177
189
|
/**
|
|
178
190
|
* Create signers for slasher accounts
|
|
179
191
|
*/
|
|
180
|
-
createSlasherSigners():
|
|
192
|
+
createSlasherSigners(): EthSigner[] {
|
|
181
193
|
if (!this.keystore.slasher) {
|
|
182
194
|
return [];
|
|
183
195
|
}
|
|
@@ -188,9 +200,9 @@ export class KeystoreManager {
|
|
|
188
200
|
/**
|
|
189
201
|
* Create signers for prover accounts
|
|
190
202
|
*/
|
|
191
|
-
createProverSigners():
|
|
203
|
+
createProverSigners(): { id: EthAddress | undefined; signers: EthSigner[] } | undefined {
|
|
192
204
|
if (!this.keystore.prover) {
|
|
193
|
-
return
|
|
205
|
+
return undefined;
|
|
194
206
|
}
|
|
195
207
|
|
|
196
208
|
// Handle simple prover case (just a private key)
|
|
@@ -199,19 +211,26 @@ export class KeystoreManager {
|
|
|
199
211
|
'path' in this.keystore.prover ||
|
|
200
212
|
'address' in this.keystore.prover
|
|
201
213
|
) {
|
|
202
|
-
|
|
214
|
+
const signers = this.createSignersFromEthAccounts(this.keystore.prover as EthAccount, this.keystore.remoteSigner);
|
|
215
|
+
return {
|
|
216
|
+
id: undefined,
|
|
217
|
+
signers,
|
|
218
|
+
};
|
|
203
219
|
}
|
|
204
220
|
|
|
205
221
|
// Handle complex prover case with id and publishers
|
|
206
222
|
const proverConfig = this.keystore.prover;
|
|
207
|
-
const signers:
|
|
223
|
+
const signers: EthSigner[] = [];
|
|
208
224
|
|
|
209
225
|
for (const publisherAccounts of proverConfig.publisher) {
|
|
210
226
|
const publisherSigners = this.createSignersFromEthAccounts(publisherAccounts, this.keystore.remoteSigner);
|
|
211
227
|
signers.push(...publisherSigners);
|
|
212
228
|
}
|
|
213
229
|
|
|
214
|
-
return
|
|
230
|
+
return {
|
|
231
|
+
id: EthAddress.fromString(proverConfig.id),
|
|
232
|
+
signers,
|
|
233
|
+
};
|
|
215
234
|
}
|
|
216
235
|
|
|
217
236
|
/**
|
|
@@ -302,13 +321,16 @@ export class KeystoreManager {
|
|
|
302
321
|
/**
|
|
303
322
|
* Create signers from EthAccounts configuration
|
|
304
323
|
*/
|
|
305
|
-
private createSignersFromEthAccounts(
|
|
324
|
+
private createSignersFromEthAccounts(
|
|
325
|
+
accounts: EthAccounts,
|
|
326
|
+
defaultRemoteSigner?: EthRemoteSignerConfig,
|
|
327
|
+
): EthSigner[] {
|
|
306
328
|
if (typeof accounts === 'string') {
|
|
307
329
|
return [this.createSignerFromEthAccount(accounts, defaultRemoteSigner)];
|
|
308
330
|
}
|
|
309
331
|
|
|
310
332
|
if (Array.isArray(accounts)) {
|
|
311
|
-
const signers:
|
|
333
|
+
const signers: EthSigner[] = [];
|
|
312
334
|
for (const account of accounts) {
|
|
313
335
|
const accountSigners = this.createSignersFromEthAccounts(account, defaultRemoteSigner);
|
|
314
336
|
signers.push(...accountSigners);
|
|
@@ -333,7 +355,7 @@ export class KeystoreManager {
|
|
|
333
355
|
/**
|
|
334
356
|
* Create a signer from a single EthAccount configuration
|
|
335
357
|
*/
|
|
336
|
-
private createSignerFromEthAccount(account: EthAccount, defaultRemoteSigner?: EthRemoteSignerConfig):
|
|
358
|
+
private createSignerFromEthAccount(account: EthAccount, defaultRemoteSigner?: EthRemoteSignerConfig): EthSigner {
|
|
337
359
|
// Private key (hex string)
|
|
338
360
|
if (typeof account === 'string') {
|
|
339
361
|
if (account.startsWith('0x') && account.length === 66) {
|
|
@@ -383,14 +405,14 @@ export class KeystoreManager {
|
|
|
383
405
|
/**
|
|
384
406
|
* Create signer from JSON V3 keystore file or directory
|
|
385
407
|
*/
|
|
386
|
-
private createSignerFromJsonV3(config: EthJsonKeyFileV3Config):
|
|
408
|
+
private createSignerFromJsonV3(config: EthJsonKeyFileV3Config): EthSigner[] {
|
|
387
409
|
try {
|
|
388
410
|
const stats = statSync(config.path);
|
|
389
411
|
|
|
390
412
|
if (stats.isDirectory()) {
|
|
391
413
|
// Handle directory - load all JSON files
|
|
392
414
|
const files = readdirSync(config.path);
|
|
393
|
-
const signers:
|
|
415
|
+
const signers: EthSigner[] = [];
|
|
394
416
|
const seenAddresses = new Map<string, string>(); // address -> file name
|
|
395
417
|
|
|
396
418
|
for (const file of files) {
|
|
@@ -436,7 +458,7 @@ export class KeystoreManager {
|
|
|
436
458
|
/**
|
|
437
459
|
* Create signer from a single JSON V3 keystore file
|
|
438
460
|
*/
|
|
439
|
-
private createSignerFromSingleJsonV3File(filePath: string, password?: string):
|
|
461
|
+
private createSignerFromSingleJsonV3File(filePath: string, password?: string): EthSigner {
|
|
440
462
|
try {
|
|
441
463
|
// Read the keystore file
|
|
442
464
|
const keystoreJson = readFileSync(filePath, 'utf8');
|
|
@@ -463,9 +485,9 @@ export class KeystoreManager {
|
|
|
463
485
|
/**
|
|
464
486
|
* Create signers from mnemonic configuration using BIP44 derivation
|
|
465
487
|
*/
|
|
466
|
-
private createSignersFromMnemonic(config: EthMnemonicConfig):
|
|
488
|
+
private createSignersFromMnemonic(config: EthMnemonicConfig): EthSigner[] {
|
|
467
489
|
const { mnemonic, addressIndex = 0, accountIndex = 0, addressCount = 1, accountCount = 1 } = config;
|
|
468
|
-
const signers:
|
|
490
|
+
const signers: EthSigner[] = [];
|
|
469
491
|
|
|
470
492
|
try {
|
|
471
493
|
// Use viem's mnemonic derivation (imported at top of file)
|
|
@@ -496,14 +518,14 @@ export class KeystoreManager {
|
|
|
496
518
|
/**
|
|
497
519
|
* Sign message with a specific signer
|
|
498
520
|
*/
|
|
499
|
-
async signMessage(signer:
|
|
521
|
+
async signMessage(signer: EthSigner, message: Buffer32): Promise<Signature> {
|
|
500
522
|
return await signer.signMessage(message);
|
|
501
523
|
}
|
|
502
524
|
|
|
503
525
|
/**
|
|
504
526
|
* Sign typed data with a specific signer
|
|
505
527
|
*/
|
|
506
|
-
async signTypedData(signer:
|
|
528
|
+
async signTypedData(signer: EthSigner, typedData: TypedDataDefinition): Promise<Signature> {
|
|
507
529
|
return await signer.signTypedData(typedData);
|
|
508
530
|
}
|
|
509
531
|
|
package/src/signer.ts
CHANGED
|
@@ -3,13 +3,21 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Common interface for different signing backends (local, remote, encrypted)
|
|
5
5
|
*/
|
|
6
|
+
import type { EthSigner } from '@aztec/ethereum';
|
|
6
7
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
7
|
-
import { Secp256k1Signer } from '@aztec/foundation/crypto';
|
|
8
|
+
import { Secp256k1Signer, toRecoveryBit } from '@aztec/foundation/crypto';
|
|
8
9
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
|
-
import { Signature } from '@aztec/foundation/eth-signature';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
10
|
+
import { Signature, type ViemTransactionSignature } from '@aztec/foundation/eth-signature';
|
|
11
|
+
import { withHexPrefix } from '@aztec/foundation/string';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
type TransactionSerializable,
|
|
15
|
+
type TypedDataDefinition,
|
|
16
|
+
hashTypedData,
|
|
17
|
+
keccak256,
|
|
18
|
+
parseTransaction,
|
|
19
|
+
serializeTransaction,
|
|
20
|
+
} from 'viem';
|
|
13
21
|
|
|
14
22
|
import type { EthRemoteSignerConfig } from './types.js';
|
|
15
23
|
|
|
@@ -19,7 +27,7 @@ import type { EthRemoteSignerConfig } from './types.js';
|
|
|
19
27
|
export class SignerError extends Error {
|
|
20
28
|
constructor(
|
|
21
29
|
message: string,
|
|
22
|
-
public method: 'eth_sign' | 'eth_signTypedData_v4',
|
|
30
|
+
public method: 'eth_sign' | 'eth_signTransaction' | 'eth_signTypedData_v4',
|
|
23
31
|
public url: string,
|
|
24
32
|
public statusCode?: number,
|
|
25
33
|
public errorCode?: number,
|
|
@@ -29,33 +37,13 @@ export class SignerError extends Error {
|
|
|
29
37
|
}
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
/**
|
|
33
|
-
* Common interface for all signer implementations
|
|
34
|
-
*/
|
|
35
|
-
/**
|
|
36
|
-
* Abstraction for signing operations used by the node keystore.
|
|
37
|
-
*/
|
|
38
|
-
export interface Signer {
|
|
39
|
-
/** The Ethereum address for this signer */
|
|
40
|
-
readonly address: EthAddress;
|
|
41
|
-
|
|
42
|
-
/** Sign a message using eth_sign (with Ethereum message prefix) */
|
|
43
|
-
signMessage(message: Buffer32): Promise<Signature>;
|
|
44
|
-
|
|
45
|
-
/** Sign typed data using EIP-712 */
|
|
46
|
-
signTypedData(typedData: TypedDataDefinition): Promise<Signature>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Local signer using in-memory private key
|
|
51
|
-
*/
|
|
52
40
|
/**
|
|
53
41
|
* Local signer that holds an in-memory Secp256k1 private key.
|
|
54
42
|
*/
|
|
55
|
-
export class LocalSigner implements
|
|
43
|
+
export class LocalSigner implements EthSigner {
|
|
56
44
|
private readonly signer: Secp256k1Signer;
|
|
57
45
|
|
|
58
|
-
constructor(privateKey: Buffer32) {
|
|
46
|
+
constructor(private privateKey: Buffer32) {
|
|
59
47
|
this.signer = new Secp256k1Signer(privateKey);
|
|
60
48
|
}
|
|
61
49
|
|
|
@@ -71,18 +59,48 @@ export class LocalSigner implements Signer {
|
|
|
71
59
|
const digest = hashTypedData(typedData);
|
|
72
60
|
return Promise.resolve(this.signer.sign(Buffer32.fromString(digest)));
|
|
73
61
|
}
|
|
62
|
+
|
|
63
|
+
signTransaction(transaction: TransactionSerializable): Promise<Signature> {
|
|
64
|
+
// Taken from viem's `signTransaction` implementation
|
|
65
|
+
const tx: TransactionSerializable =
|
|
66
|
+
transaction.type === 'eip4844'
|
|
67
|
+
? {
|
|
68
|
+
...transaction,
|
|
69
|
+
sidecars: false,
|
|
70
|
+
}
|
|
71
|
+
: transaction;
|
|
72
|
+
const serializedTx = serializeTransaction(tx);
|
|
73
|
+
const txHash = keccak256(serializedTx);
|
|
74
|
+
const sig = this.signer.sign(Buffer32.fromString(txHash.slice(2)));
|
|
75
|
+
return Promise.resolve(new Signature(sig.r, sig.s, toRecoveryBit(sig.v)));
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
// reference - https://docs.web3signer.consensys.io/reference/api/json-rpc#eth_signtransaction
|
|
80
|
+
type RemoteSignerTxObject = {
|
|
81
|
+
from: string;
|
|
82
|
+
to?: string | null;
|
|
83
|
+
gas?: string;
|
|
84
|
+
maxPriorityFeePerGas?: string;
|
|
85
|
+
maxFeePerGas?: string;
|
|
86
|
+
nonce?: string;
|
|
87
|
+
value?: string;
|
|
88
|
+
data?: string;
|
|
89
|
+
|
|
90
|
+
// EIP-4844 extension - https://github.com/Consensys/web3signer/pull/1096
|
|
91
|
+
maxFeePerBlobGas?: string;
|
|
92
|
+
blobVersionedHashes?: readonly string[];
|
|
93
|
+
blobs?: readonly string[];
|
|
94
|
+
};
|
|
95
|
+
|
|
79
96
|
/**
|
|
80
97
|
* Remote signer that proxies signing operations to a Web3Signer-compatible HTTP endpoint.
|
|
81
98
|
*/
|
|
82
|
-
export class RemoteSigner implements
|
|
99
|
+
export class RemoteSigner implements EthSigner {
|
|
83
100
|
constructor(
|
|
84
101
|
public readonly address: EthAddress,
|
|
85
102
|
private readonly config: EthRemoteSignerConfig,
|
|
103
|
+
private fetch: typeof globalThis.fetch = globalThis.fetch,
|
|
86
104
|
) {}
|
|
87
105
|
|
|
88
106
|
/**
|
|
@@ -99,6 +117,10 @@ export class RemoteSigner implements Signer {
|
|
|
99
117
|
return await this.makeJsonRpcSignTypedDataRequest(typedData);
|
|
100
118
|
}
|
|
101
119
|
|
|
120
|
+
signTransaction(transaction: TransactionSerializable): Promise<Signature> {
|
|
121
|
+
return this.makeJsonRpcSignTransactionRequest(transaction);
|
|
122
|
+
}
|
|
123
|
+
|
|
102
124
|
/**
|
|
103
125
|
* Make a JSON-RPC sign request using eth_sign
|
|
104
126
|
*/
|
|
@@ -115,7 +137,7 @@ export class RemoteSigner implements Signer {
|
|
|
115
137
|
id: 1,
|
|
116
138
|
};
|
|
117
139
|
|
|
118
|
-
const response = await fetch(url, {
|
|
140
|
+
const response = await this.fetch(url, {
|
|
119
141
|
method: 'POST',
|
|
120
142
|
headers: {
|
|
121
143
|
'Content-Type': 'application/json',
|
|
@@ -157,9 +179,6 @@ export class RemoteSigner implements Signer {
|
|
|
157
179
|
return Signature.fromString(signatureHex as `0x${string}`);
|
|
158
180
|
}
|
|
159
181
|
|
|
160
|
-
/**
|
|
161
|
-
* Make a JSON-RPC sign typed data request using eth_signTypedData_v4
|
|
162
|
-
*/
|
|
163
182
|
/**
|
|
164
183
|
* Make a JSON-RPC eth_signTypedData_v4 request.
|
|
165
184
|
*/
|
|
@@ -173,7 +192,7 @@ export class RemoteSigner implements Signer {
|
|
|
173
192
|
id: 1,
|
|
174
193
|
};
|
|
175
194
|
|
|
176
|
-
const response = await fetch(url, {
|
|
195
|
+
const response = await this.fetch(url, {
|
|
177
196
|
method: 'POST',
|
|
178
197
|
headers: {
|
|
179
198
|
'Content-Type': 'application/json',
|
|
@@ -215,6 +234,94 @@ export class RemoteSigner implements Signer {
|
|
|
215
234
|
return Signature.fromString(signatureHex as `0x${string}`);
|
|
216
235
|
}
|
|
217
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Make a JSON-RPC eth_signTransaction request.
|
|
239
|
+
*/
|
|
240
|
+
private async makeJsonRpcSignTransactionRequest(tx: TransactionSerializable): Promise<Signature> {
|
|
241
|
+
if (tx.type !== 'eip1559') {
|
|
242
|
+
throw new Error('This signer does not support tx type: ' + tx.type);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const url = this.getSignerUrl();
|
|
246
|
+
|
|
247
|
+
const txObject: RemoteSignerTxObject = {
|
|
248
|
+
from: this.address.toString(),
|
|
249
|
+
to: tx.to ?? null,
|
|
250
|
+
data: tx.data,
|
|
251
|
+
value: typeof tx.value !== 'undefined' ? withHexPrefix(tx.value.toString(16)) : undefined,
|
|
252
|
+
nonce: typeof tx.nonce !== 'undefined' ? withHexPrefix(tx.nonce.toString(16)) : undefined,
|
|
253
|
+
gas: typeof tx.gas !== 'undefined' ? withHexPrefix(tx.gas.toString(16)) : undefined,
|
|
254
|
+
maxFeePerGas: typeof tx.maxFeePerGas !== 'undefined' ? withHexPrefix(tx.maxFeePerGas.toString(16)) : undefined,
|
|
255
|
+
maxPriorityFeePerGas:
|
|
256
|
+
typeof tx.maxPriorityFeePerGas !== 'undefined'
|
|
257
|
+
? withHexPrefix(tx.maxPriorityFeePerGas.toString(16))
|
|
258
|
+
: undefined,
|
|
259
|
+
|
|
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)))),
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const body = {
|
|
267
|
+
jsonrpc: '2.0',
|
|
268
|
+
method: 'eth_signTransaction',
|
|
269
|
+
params: [txObject],
|
|
270
|
+
id: 1,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const response = await this.fetch(url, {
|
|
274
|
+
method: 'POST',
|
|
275
|
+
headers: {
|
|
276
|
+
'Content-Type': 'application/json',
|
|
277
|
+
},
|
|
278
|
+
body: JSON.stringify(body),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (!response.ok) {
|
|
282
|
+
const errorText = await response.text();
|
|
283
|
+
throw new SignerError(
|
|
284
|
+
`Web3Signer request failed for eth_signTransaction at ${url}: ${response.status} ${response.statusText} - ${errorText}`,
|
|
285
|
+
'eth_signTransaction',
|
|
286
|
+
url,
|
|
287
|
+
response.status,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const result = await response.json();
|
|
292
|
+
|
|
293
|
+
if (result.error) {
|
|
294
|
+
throw new SignerError(
|
|
295
|
+
`Web3Signer JSON-RPC error for eth_signTransaction at ${url}: ${result.error.code} - ${result.error.message}`,
|
|
296
|
+
'eth_signTransaction',
|
|
297
|
+
url,
|
|
298
|
+
undefined,
|
|
299
|
+
result.error.code,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!result.result) {
|
|
304
|
+
throw new Error('Invalid response from Web3Signer: no result found');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let rawTxHex = result.result;
|
|
308
|
+
if (!rawTxHex.startsWith('0x')) {
|
|
309
|
+
rawTxHex = '0x' + rawTxHex;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// we get back to whole signed tx. Deserialize it in order to read the signature
|
|
313
|
+
const parsedTxWithSignature = parseTransaction(rawTxHex);
|
|
314
|
+
if (
|
|
315
|
+
parsedTxWithSignature.r === undefined ||
|
|
316
|
+
parsedTxWithSignature.s === undefined ||
|
|
317
|
+
parsedTxWithSignature.v === undefined
|
|
318
|
+
) {
|
|
319
|
+
throw new Error('Tx not signed by Web3Signer');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return Signature.fromViemTransactionSignature(parsedTxWithSignature as ViemTransactionSignature);
|
|
323
|
+
}
|
|
324
|
+
|
|
218
325
|
/**
|
|
219
326
|
* Resolve the effective remote signer URL from config.
|
|
220
327
|
*/
|