55 */
66import type { EthSigner } from '@aztec/ethereum' ;
77import { Buffer32 } from '@aztec/foundation/buffer' ;
8- import { Secp256k1Signer , toRecoveryBit } from '@aztec/foundation/crypto' ;
8+ import { Secp256k1Signer , randomBytes , toRecoveryBit } from '@aztec/foundation/crypto' ;
99import type { EthAddress } from '@aztec/foundation/eth-address' ;
1010import { Signature , type ViemTransactionSignature } from '@aztec/foundation/eth-signature' ;
11+ import { jsonStringify } from '@aztec/foundation/json-rpc' ;
1112import { withHexPrefix } from '@aztec/foundation/string' ;
1213
1314import {
@@ -27,7 +28,7 @@ import type { EthRemoteSignerConfig } from './types.js';
2728export class SignerError extends Error {
2829 constructor (
2930 message : string ,
30- public method : 'eth_sign' | 'eth_signTransaction' | 'eth_signTypedData_v4' ,
31+ public method : string ,
3132 public url : string ,
3233 public statusCode ?: number ,
3334 public errorCode ?: number ,
@@ -128,50 +129,12 @@ export class RemoteSigner implements EthSigner {
128129 * Make a JSON-RPC eth_sign request.
129130 */
130131 private async makeJsonRpcSignRequest ( data : Buffer32 ) : Promise < Signature > {
131- const url = this . getSignerUrl ( ) ;
132-
133- const body = {
134- jsonrpc : '2.0' ,
135- method : 'eth_sign' ,
136- params : [ this . address . toString ( ) , data . toString ( ) ] ,
137- id : 1 ,
138- } ;
139-
140- const response = await this . fetch ( url , {
141- method : 'POST' ,
142- headers : {
143- 'Content-Type' : 'application/json' ,
144- } ,
145- body : JSON . stringify ( body ) ,
146- } ) ;
132+ let signatureHex = await this . makeJsonRpcRequest ( 'eth_sign' , this . address . toString ( ) , data . toString ( ) ) ;
147133
148- if ( ! response . ok ) {
149- const errorText = await response . text ( ) ;
150- throw new SignerError (
151- `Web3Signer request failed for eth_sign at ${ url } : ${ response . status } ${ response . statusText } - ${ errorText } ` ,
152- 'eth_sign' ,
153- url ,
154- response . status ,
155- ) ;
134+ if ( typeof signatureHex !== 'string' ) {
135+ throw new Error ( 'Invalid signature' ) ;
156136 }
157137
158- const result = await response . json ( ) ;
159-
160- if ( result . error ) {
161- throw new SignerError (
162- `Web3Signer JSON-RPC error for eth_sign at ${ url } : ${ result . error . code } - ${ result . error . message } ` ,
163- 'eth_sign' ,
164- url ,
165- undefined ,
166- result . error . code ,
167- ) ;
168- }
169-
170- if ( ! result . result ) {
171- throw new Error ( 'Invalid response from Web3Signer: no result found' ) ;
172- }
173-
174- let signatureHex = result . result ;
175138 if ( ! signatureHex . startsWith ( '0x' ) ) {
176139 signatureHex = '0x' + signatureHex ;
177140 }
@@ -183,50 +146,16 @@ export class RemoteSigner implements EthSigner {
183146 * Make a JSON-RPC eth_signTypedData_v4 request.
184147 */
185148 private async makeJsonRpcSignTypedDataRequest ( typedData : TypedDataDefinition ) : Promise < Signature > {
186- const url = this . getSignerUrl ( ) ;
187-
188- const body = {
189- jsonrpc : '2.0' ,
190- method : 'eth_signTypedData_v4' ,
191- params : [ this . address . toString ( ) , typedData ] ,
192- id : 1 ,
193- } ;
194-
195- const response = await this . fetch ( url , {
196- method : 'POST' ,
197- headers : {
198- 'Content-Type' : 'application/json' ,
199- } ,
200- body : JSON . stringify ( body ) ,
201- } ) ;
202-
203- if ( ! response . ok ) {
204- const errorText = await response . text ( ) ;
205- throw new SignerError (
206- `Web3Signer request failed for eth_signTypedData_v4 at ${ url } : ${ response . status } ${ response . statusText } - ${ errorText } ` ,
207- 'eth_signTypedData_v4' ,
208- url ,
209- response . status ,
210- ) ;
149+ let signatureHex = await this . makeJsonRpcRequest (
150+ 'eth_signTypedData' ,
151+ this . address . toString ( ) ,
152+ jsonStringify ( typedData ) ,
153+ ) ;
154+
155+ if ( typeof signatureHex !== 'string' ) {
156+ throw new Error ( 'Invalid signature' ) ;
211157 }
212158
213- const result = await response . json ( ) ;
214-
215- if ( result . error ) {
216- throw new SignerError (
217- `Web3Signer JSON-RPC error for eth_signTypedData_v4 at ${ url } : ${ result . error . code } - ${ result . error . message } ` ,
218- 'eth_signTypedData_v4' ,
219- url ,
220- undefined ,
221- result . error . code ,
222- ) ;
223- }
224-
225- if ( ! result . result ) {
226- throw new Error ( 'Invalid response from Web3Signer: no result found' ) ;
227- }
228-
229- let signatureHex = result . result ;
230159 if ( ! signatureHex . startsWith ( '0x' ) ) {
231160 signatureHex = '0x' + signatureHex ;
232161 }
@@ -242,8 +171,6 @@ export class RemoteSigner implements EthSigner {
242171 throw new Error ( 'This signer does not support tx type: ' + tx . type ) ;
243172 }
244173
245- const url = this . getSignerUrl ( ) ;
246-
247174 const txObject : RemoteSignerTxObject = {
248175 from : this . address . toString ( ) ,
249176 to : tx . to ?? null ,
@@ -263,26 +190,49 @@ export class RemoteSigner implements EthSigner {
263190 // blobs: tx.blobs?.map(blob => (typeof blob === 'string' ? blob : bufferToHex(Buffer.from(blob)))),
264191 } ;
265192
266- const body = {
267- jsonrpc : '2.0' ,
268- method : 'eth_signTransaction' ,
269- params : [ txObject ] ,
270- id : 1 ,
271- } ;
193+ let rawTxHex = await this . makeJsonRpcRequest ( 'eth_signTransaction' , txObject ) ;
194+
195+ if ( typeof rawTxHex !== 'string' ) {
196+ throw new Error ( 'Invalid signed tx' ) ;
197+ }
198+
199+ if ( ! rawTxHex . startsWith ( '0x' ) ) {
200+ rawTxHex = '0x' + rawTxHex ;
201+ }
202+
203+ // we get back to whole signed tx. Deserialize it in order to read the signature
204+ const parsedTxWithSignature = parseTransaction ( rawTxHex ) ;
205+ if (
206+ parsedTxWithSignature . r === undefined ||
207+ parsedTxWithSignature . s === undefined ||
208+ parsedTxWithSignature . v === undefined
209+ ) {
210+ throw new Error ( 'Tx not signed by Web3Signer' ) ;
211+ }
212+
213+ return Signature . fromViemTransactionSignature ( parsedTxWithSignature as ViemTransactionSignature ) ;
214+ }
215+
216+ /**
217+ * Sends a JSON-RPC request and returns its result
218+ */
219+ private async makeJsonRpcRequest ( method : string , ...params : any [ ] ) : Promise < any > {
220+ const url = this . getSignerUrl ( ) ;
221+ const id = this . generateId ( ) ;
272222
273223 const response = await this . fetch ( url , {
274224 method : 'POST' ,
275225 headers : {
276226 'Content-Type' : 'application/json' ,
277227 } ,
278- body : JSON . stringify ( body ) ,
228+ body : jsonStringify ( { jsonrpc : '2.0' , method , params , id } ) ,
279229 } ) ;
280230
281231 if ( ! response . ok ) {
282232 const errorText = await response . text ( ) ;
283233 throw new SignerError (
284- `Web3Signer request failed for eth_signTransaction at ${ url } : ${ response . status } ${ response . statusText } - ${ errorText } ` ,
285- 'eth_signTransaction' ,
234+ `Web3Signer request failed for ${ method } at ${ url } : ${ response . status } ${ response . statusText } - ${ errorText } ` ,
235+ method ,
286236 url ,
287237 response . status ,
288238 ) ;
@@ -292,10 +242,10 @@ export class RemoteSigner implements EthSigner {
292242
293243 if ( result . error ) {
294244 throw new SignerError (
295- `Web3Signer JSON-RPC error for eth_signTransaction at ${ url } : ${ result . error . code } - ${ result . error . message } ` ,
296- 'eth_signTransaction' ,
245+ `Web3Signer JSON-RPC error for ${ method } at ${ url } : ${ result . error . code } - ${ result . error . message } ` ,
246+ method ,
297247 url ,
298- undefined ,
248+ response . status ,
299249 result . error . code ,
300250 ) ;
301251 }
@@ -304,22 +254,7 @@ export class RemoteSigner implements EthSigner {
304254 throw new Error ( 'Invalid response from Web3Signer: no result found' ) ;
305255 }
306256
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 ) ;
257+ return result . result ;
323258 }
324259
325260 /**
@@ -331,4 +266,11 @@ export class RemoteSigner implements EthSigner {
331266 }
332267 return this . config . remoteSignerUrl ;
333268 }
269+
270+ /**
271+ * Generate an id to use for a JSON-RPC call
272+ */
273+ private generateId ( ) : string {
274+ return randomBytes ( 4 ) . toString ( 'hex' ) ;
275+ }
334276}
0 commit comments