Skip to main content

External Signer

This section explains how the external signer works. If you don't plan to run the validator in an unsafe environment (such as the cloud) you probably don't need it.

To avoid users exposing their private keys the validator program has a simple communication protocol implemented via HTTP requests for remote/external signing.

HTTP Protocol

The external signer must implement a simple HTTP server that waits for POST requests on an endpoint of the form <signer_address>/sign. When initializing the validator the <signer_address> should be specified in its configuration (e.g. specifying --signer-url flag).

Request Format

The validator will make POST request with all the transaction data to sign:

{
"transaction": {
"type": "INVOKE",
"sender_address": "0x11efbf2806a9f6fe043c91c176ed88c38907379e59d2d3413a00eeeef08aa7e",
"calldata": [
"0x1",
"0x4862e05d00f2d0981c4a912269c21ad99438598ab86b6e70d1cee267caaa78d",
"0x37446750a403c1b4014436073cf8d08ceadc5b156ac1c8b7b0ca41a0c9c1c54",
"0x1",
"0x6521dd8f51f893a8580baedc249f1afaf7fd999c88722e607787970697dd76"
],
"version": "0x3",
"signature": [
"0x6711bfe51870a9874af883ca974b94fef85200d5772db5792013644dc9dd16a",
"0x23e345b39ffc43a92ab6735bf3f11e7dac1aa931d8c9647a1a2957f759b8baa"
],
"nonce": "0x194",
"resource_bounds": {
"l1_gas": {
"max_amount": "0x0",
"max_price_per_unit": "0x57e48bc504e79"
},
"l1_data_gas": {
"max_amount": "0x450",
"max_price_per_unit": "0xa54"
},
"l2_gas": {
"max_amount": "0xc92ca0",
"max_price_per_unit": "0x1b5aea1cb"
}
},
"tip": "0x0",
"paymaster_data": [],
"account_deployment_data": [],
"nonce_data_availability_mode": "L1",
"fee_data_availability_mode": "L1"
},
"chain_id": "0x534e5f5345504f4c4941"
}

Response Format

It will wait for ECDSA signature values r and s in an array:

{
"signature": [
"0xabc",
"0xdef"
]
}

We have provided an already functional implementation for you to use or take as an example to implement your own.

Example

This example simulates the interaction between validator and remote signer using our own implemented signer. Start by compiling the remote signer:

make signer

Then set a private key which will be used to sign transactions and the HTTP address where the signer will receive POST requests from the validator program. For example using private key 0x123:

SIGNER_PRIVATE_KEY="0x123" ./build/signer \
--address localhost:8080

This will start the program and will remain there listening for requests.

On a separate terminal, send a transaction data and request its signing. For example:

curl -X POST http://localhost:8080/sign \
-H "Content-Type: application/json" \
-d '{
"transaction": {
"type": "INVOKE",
"sender_address": "0x11efbf2806a9f6fe043c91c176ed88c38907379e59d2d3413a00eeeef08aa7e",
"calldata": [
"0x1",
"0x4862e05d00f2d0981c4a912269c21ad99438598ab86b6e70d1cee267caaa78d",
"0x37446750a403c1b4014436073cf8d08ceadc5b156ac1c8b7b0ca41a0c9c1c54",
"0x1",
"0x6521dd8f51f893a8580baedc249f1afaf7fd999c88722e607787970697dd76"
],
"version": "0x3",
"signature": [
"0x6711bfe51870a9874af883ca974b94fef85200d5772db5792013644dc9dd16a",
"0x23e345b39ffc43a92ab6735bf3f11e7dac1aa931d8c9647a1a2957f759b8baa"
],
"nonce": "0x194",
"resource_bounds": {
"l1_gas": {
"max_amount": "0x0",
"max_price_per_unit": "0x57e48bc504e79"
},
"l1_data_gas": {
"max_amount": "0x450",
"max_price_per_unit": "0xa54"
},
"l2_gas": {
"max_amount": "0xc92ca0",
"max_price_per_unit": "0x1b5aea1cb"
}
},
"tip": "0x0",
"paymaster_data": [],
"account_deployment_data": [],
"nonce_data_availability_mode": "L1",
"fee_data_availability_mode": "L1"
},
"chain_id": "0x534e5f5345504f4c4941"
}'

You should immediately get the following answer provided you used the same private key and transaction data shown as an example:

{
"signature": [
"0x12bf16c46782eb88570942ce126b2284bfb46b21c4b071a116bc0a6cffff35e",
"0x69abdfe5ba5b24dbbb2b9ccc3c02b03f46c505d3aa8b37d3a4bb3d6b1a81ded"
]
}

This communication is what will happen behind the curtains when using the validator and an external signer each time there is an attestation required. Notice that the validator program remains completely agnostic to the private key since only the remote signer knows it.