-
Notifications
You must be signed in to change notification settings - Fork 5.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EIP-3030: BLS Remote Signer HTTP API Standard #3030
Changes from all commits
08093ed
1b6590d
2102d09
0a39bab
c5df99f
6c30758
5b9ed19
fbf2b9c
4e8f773
61195ad
eadb63c
28cc951
cb13fd1
c145013
c625262
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,371 @@ | ||
--- | ||
eip: 3030 | ||
title: BLS Remote Signer HTTP API | ||
author: Herman Junge (@hermanjunge) | ||
discussions-to: https://ethereum-magicians.org/t/eip-3030-bls-remote-signer-http-api-standard/4810 | ||
status: Draft | ||
type: Standards Track | ||
category: Interface | ||
created: 2020-09-30 | ||
--- | ||
|
||
## Simple Summary | ||
This EIP defines a HTTP API standard for a BLS remote signer, consumed by validator clients to sign block proposals and attestations in the context of Ethereum 2.0 (eth2). | ||
|
||
## Abstract | ||
A [validator](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/validator.md) client contributes to the consensus of the Eth2 blockchain by signing proposals and attestations of blocks, using a BLS private key which must be available to this client at all times. | ||
|
||
The BLS remote signer API is designed to be consumed by validator clients, looking for a more secure avenue to store their BLS12-381 private key(s), enabling them to run in more permissive and scalable environments. | ||
|
||
## Motivation | ||
Eth2 utilizes [BLS12-381](https://github.com/cfrg/draft-irtf-cfrg-bls-signature/) signatures. | ||
|
||
Consensus on the eth2 Blockchain is achieved via the proposal and attestation of blocks from validator clients, using a BLS private key (_signing_ key) which must be available each time a message is signed: that is, at least once every epoch (6.4 minutes), during a small window of time within this epoch (a _slot_, i.e. 12 seconds), as each validator is expected to attest exactly once per epoch. | ||
|
||
The [eth2 specification](https://github.com/ethereum/eth2.0-specs) does not explicitly provide a directive on where this BLS private key must/should be stored, leaving this implementation detail to the client teams, who assume that this cryptographic secret is stored on the same host as the validator client. | ||
|
||
This assumption is sufficient in the use case where the validator client is running in a physically secure network (i.e. nobody, but the operator, has a chance to log-in into the machine hosting the validator client), as such configuration would only allow _outbound_ calls from the validator client. In this situation, only a physical security breach, or a Remote Code Execution (RCE) vulnerability can allow an attacker to either have arbitrary access to the storage or to the memory of the device. | ||
|
||
There are, however, use cases where it is required by the operator to run a validator client node in less constrained security environments, as the ones given by a cloud provider. Notwithstanding any security expectation, nothing prevents a rogue operator from gaining arbitrary access to the assets running inside a node. | ||
|
||
The situation is not better when the requirement is to execute the validators by leveraging a container orchestration solution (e.g. Kubernetes). The handling of secret keys across nodes can become a burden both from an operational as well as a security perspective. | ||
|
||
The proposed solution comprises running a specialized node with exclusive access to the secret keys, listening to a simple API (defined in the [Specification](#specification) section), and returning the requested signatures. Operators working under this schema must utilize clients with the adequate feature supporting the consumption of this API. | ||
|
||
The focus of this specification is the supply of BLS signatures _on demand_. The aspects of authentication, key management (creation, update, and deletion), pre-image validation, and transport encryption are discussed in the [Rationale](#rationale) section of this document. Moreover, the [Threat Model](#threat-model) section of this document provides a (non-exhaustive) list of threats and attack vectors, along with the suggested related mitigation strategy. | ||
|
||
## Specification | ||
|
||
### `GET /upcheck` | ||
|
||
_**Responses**_ | ||
|
||
Success | <br> | ||
--- | --- | ||
Code | `200` | ||
Content | `{"status": "OK"}` | ||
|
||
--- | ||
|
||
### `GET /keys` | ||
|
||
Returns the identifiers of the keys available to the signer. | ||
|
||
_**Responses**_ | ||
|
||
Success | <br> | ||
--- | --- | ||
Code | `200` | ||
Content | `{"keys": "[identifier]"}` | ||
|
||
--- | ||
|
||
### `POST /sign/:identifier` | ||
|
||
URL Parameter | <br> | ||
--- | --- | ||
`identifier` | `Key for which data to sign` | ||
|
||
_**Request**_ | ||
|
||
JSON Body | <br> | <br> | ||
--- | --- | --- | ||
`signingRoot` | **Required** | A string representation of a SHA256 Hash.<br>The result of the Ethereum 2.0 function [`compute_signing_root`](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_signing_root). | ||
<br> | Optional | Any other field will be ignored by the signer | ||
|
||
_**Responses**_ | ||
|
||
Success | <br> | ||
--- | --- | ||
Code | `200` | ||
Content | `{"signature": "<signature>"}` | ||
|
||
Where signature is a [BLS signature](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#bls-signatures) byte array encoded as a hexadecimal string. | ||
|
||
_or_ | ||
|
||
Error | <br> | ||
--- | --- | ||
Code | `400` | ||
Content | `{"error": "<Bad Request Error Message>"}` | ||
|
||
_or_ | ||
|
||
Error | <br> | ||
--- | --- | ||
Code | `404` | ||
Content | `{"error": "Key not found: <identifier>"}` | ||
|
||
--- | ||
|
||
## Rationale | ||
|
||
### UNIX philosophy: Simple API | ||
|
||
This API specification contains only three methods: one for **status**, one for **listing the available keys**, and one to **produce a signature**. There are no methods for authentication, pre-image validation, key management, nor transport encryption. | ||
|
||
The following subsections discuss aspects to be considered by the client implementers relative to these subjects. | ||
|
||
#### Implementation of additional features | ||
|
||
From an API pipeline view, we have two nodes: The validator client (1) that makes requests to the remote signer (2). A more sophisticated chain can be built by introducing elements between these two nodes. Either by setting up reverse proxy services, or by adding plugins to the remote signer implementation. | ||
|
||
#### Authentication | ||
|
||
Can be accomplished through the use of an HTTP Request Header. There are several ways to negotiate and issue a valid token to authenticate the communication between the validator client and the remote signer, each of them with potential drawbacks (e.g replay attacks, challenges in distributing the token to the validator client, etc.). In general, any method of authentication must be combined with transport encryption to be effective. | ||
|
||
The operator can also implement network Access Control Lists (ACLs) between the validator client's network and the remote signer's network, reducing the attack surface by requiring a potential attacker to be positioned in the same network as the validator client. | ||
|
||
#### Pre-image validation | ||
|
||
A key feature for a remote signer, pre-image validation, implies that not only the `signingRoot`, but all the required elements needed to perform complete validation of the message, are sent through the wire to obtain a signature. | ||
Comment on lines
+119
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd argue that pre-image validation is such a core feature that the post-image shouldn't even be sent to the signer. The signer should always be forced to calculate the hash-to-sign locally, given the input + info about signing request (maybe using mimetypes to signal what type of signing is requested) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this spec, as is, you cement the behaviour of a standard-compliant signer to sign blind checks. Which seems like a very bad idea, IMO There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can do the following then:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can also remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. So, in practice, what's the size of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fyi this is Prysm's definition. We have a field for the beacon object for additional verifications such as slashing prevention. https://github.com/prysmaticlabs/prysm/blob/master/proto/validator/accounts/v2/keymanager.proto#L53 It's not a mandatory field, the remote can ignore it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there s one scenario in my head where having the signingRoot together with all the needed fields to recompute it might come in handy, tamper detection. if the sent root and the calculated root mismatch then you have a big problem There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, someone is tampering with the traffic. So if they can modify one (e.g. the signingRoot, to sign something else), why wouldn't they tamper with both?
The arguments against including it are:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well, you are right of course :D thanks , |
||
|
||
A control that parses this pre-image will not be breaking this document API specification, as any other field different from `signingRoot` will be ignored by the remote signer. | ||
|
||
Implementers should address the additional requirements emerging for each specific validation, such as, slashing protection, as this entails the needs to manage a database and the mechanisms to update it. Also new threats need to be addressed and controlled, among them, attackers looking into tampering the source of data. | ||
|
||
#### Key management | ||
|
||
There are several ways to store secret keys, namely Hardware Security Modules (HSM), Secrets management applications (e.g. Hashicorp Vault), cloud storage with tight private network ACL rules, or even raw files in a directory. In general the remote signer implementers will abstract the storage medium from the HTTP API. | ||
|
||
It is in this perspective, that any procedure to create, update, or delete keys should be built separate from the client implementation. | ||
|
||
#### Transport Encryption | ||
|
||
If the operator is working with self-signed certificates, it is required that the client enhancement consuming the remote signer allows this option. | ||
|
||
## Test Cases | ||
|
||
### Test Data | ||
|
||
* BLS Pair | ||
* Public key: `0xb7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a`. | ||
* Secret key: `0x68081afeb7ad3e8d469f87010804c3e8d53ef77d393059a55132637206cc59ec`. | ||
* Signing root: `0xb6bb8f3765f93f4f1e7c7348479289c9261399a3c6906685e320071a1a13955c`. | ||
* Expected signature: `0xb5d0c01cef3b028e2c5f357c2d4b886f8e374d09dd660cd7dd14680d4f956778808b4d3b2ab743e890fc1a77ae62c3c90d613561b23c6adaeb5b0e288832304fddc08c7415080be73e556e8862a1b4d0f6aa8084e34a901544d5bb6aeed3a612`. | ||
|
||
|
||
### `GET /upcheck` | ||
|
||
```bash | ||
# Success | ||
|
||
## Request | ||
curl -v localhost:9000/upcheck | ||
|
||
## Response | ||
* Trying 127.0.0.1:9000... | ||
* TCP_NODELAY set | ||
* Connected to localhost (127.0.0.1) port 9000 (#0) | ||
> GET /upcheck HTTP/1.1 | ||
> Host: localhost:9000 | ||
> User-Agent: curl/7.68.0 | ||
> Accept: */* | ||
> | ||
* Mark bundle as not supporting multiuse | ||
< HTTP/1.1 200 OK | ||
< content-type: application/json | ||
< content-length: 15 | ||
< date: Wed, 30 Sep 2020 02:25:08 GMT | ||
< | ||
* Connection #0 to host localhost left intact | ||
{"status":"OK"} | ||
|
||
``` | ||
|
||
### `GET /keys` | ||
|
||
```bash | ||
# Success | ||
|
||
## Request | ||
curl -v localhost:9000/keys | ||
|
||
## Response | ||
* Trying 127.0.0.1:9000... | ||
* TCP_NODELAY set | ||
* Connected to localhost (127.0.0.1) port 9000 (#0) | ||
> GET /publicKeys HTTP/1.1 | ||
> Host: localhost:9000 | ||
> User-Agent: curl/7.68.0 | ||
> Accept: */* | ||
> | ||
* Mark bundle as not supporting multiuse | ||
< HTTP/1.1 200 OK | ||
< content-type: application/json | ||
< content-length: 116 | ||
< date: Wed, 30 Sep 2020 02:25:36 GMT | ||
< | ||
* Connection #0 to host localhost left intact | ||
{"keys":["b7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a"]} | ||
|
||
# Server Error | ||
|
||
## Preparation | ||
## `chmod` keys directory to the octal 311 (-wx--x--x). | ||
|
||
## Request | ||
curl -v localhost:9000/keys | ||
|
||
## Response | ||
* Trying 127.0.0.1:9000... | ||
* TCP_NODELAY set | ||
* Connected to localhost (127.0.0.1) port 9000 (#0) | ||
> GET /publicKeys HTTP/1.1 | ||
> Host: localhost:9000 | ||
> User-Agent: curl/7.68.0 | ||
> Accept: */* | ||
> | ||
* Mark bundle as not supporting multiuse | ||
< HTTP/1.1 500 Internal Server Error | ||
< content-length: 43 | ||
< date: Wed, 30 Sep 2020 02:26:09 GMT | ||
< | ||
* Connection #0 to host localhost left intact | ||
{"error":"Storage error: PermissionDenied"} | ||
|
||
|
||
``` | ||
|
||
### `POST /sign/:identifier` | ||
|
||
```bash | ||
# Success | ||
|
||
## Request | ||
curl -v -X POST -d '{"signingRoot":"0xb6bb8f3765f93f4f1e7c7348479289c9261399a3c6906685e320071a1a13955c"}' -H 'Content-Type: application/json' localhost:9000/sign/b7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a | ||
|
||
## Response | ||
Note: Unnecessary use of -X or --request, POST is already inferred. | ||
* Trying 127.0.0.1:9000... | ||
* TCP_NODELAY set | ||
* Connected to localhost (127.0.0.1) port 9000 (#0) | ||
> POST /sign/b7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a HTTP/1.1 | ||
> Host: localhost:9000 | ||
> User-Agent: curl/7.68.0 | ||
> Accept: */* | ||
> Content-Type: application/json | ||
> Content-Length: 84 | ||
> | ||
* upload completely sent off: 84 out of 84 bytes | ||
* Mark bundle as not supporting multiuse | ||
< HTTP/1.1 200 OK | ||
< content-type: application/json | ||
< content-length: 210 | ||
< date: Wed, 30 Sep 2020 02:16:02 GMT | ||
< | ||
* Connection #0 to host localhost left intact | ||
{"signature":"0xb5d0c01cef3b028e2c5f357c2d4b886f8e374d09dd660cd7dd14680d4f956778808b4d3b2ab743e890fc1a77ae62c3c90d613561b23c6adaeb5b0e288832304fddc08c7415080be73e556e8862a1b4d0f6aa8084e34a901544d5bb6aeed3a612"} | ||
|
||
# Bad Request Error | ||
|
||
## Request | ||
curl -v -X POST -d '{"signingRoot":"0xaa1"}' -H 'Content-Type: application/json' localhost:9000/sign/b7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a | ||
|
||
## Response | ||
Note: Unnecessary use of -X or --request, POST is already inferred. | ||
* Trying 127.0.0.1:9000... | ||
* TCP_NODELAY set | ||
* Connected to localhost (127.0.0.1) port 9000 (#0) | ||
> POST /sign/b7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a HTTP/1.1 | ||
> Host: localhost:9000 | ||
> User-Agent: curl/7.68.0 | ||
> Accept: */* | ||
> Content-Type: application/json | ||
> Content-Length: 23 | ||
> | ||
* upload completely sent off: 23 out of 23 bytes | ||
* Mark bundle as not supporting multiuse | ||
< HTTP/1.1 400 Bad Request | ||
< content-length: 38 | ||
< date: Wed, 30 Sep 2020 02:15:05 GMT | ||
< | ||
* Connection #0 to host localhost left intact | ||
{"error":"Invalid signingRoot: 0xaa1"} | ||
|
||
# No Keys Available | ||
|
||
## Request | ||
curl -v -X POST -d '{"signingRoot":"0xb6bb8f3765f93f4f1e7c7348479289c9261399a3c6906685e320071a1a13955c"}' -H 'Content-Type: application/json' localhost:9000/sign/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 | ||
|
||
## Response | ||
Note: Unnecessary use of -X or --request, POST is already inferred. | ||
* Trying 127.0.0.1:9000... | ||
* TCP_NODELAY set | ||
* Connected to localhost (127.0.0.1) port 9000 (#0) | ||
> POST /sign/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 HTTP/1.1 | ||
> Host: localhost:9000 | ||
> User-Agent: curl/7.68.0 | ||
> Accept: */* | ||
> Content-Type: application/json | ||
> Content-Length: 84 | ||
> | ||
* upload completely sent off: 84 out of 84 bytes | ||
* Mark bundle as not supporting multiuse | ||
< HTTP/1.1 404 Not Found | ||
< content-length: 123 | ||
< date: Wed, 30 Sep 2020 02:18:53 GMT | ||
< | ||
* Connection #0 to host localhost left intact | ||
{"error":"Key not found: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"} | ||
|
||
# Server Error | ||
|
||
## Preparation | ||
## `chmod` both keys directory and file to the octal 311 (-wx--x--x). | ||
## `chmod` back to 755 to delete them afterwards. | ||
|
||
## Request | ||
curl -v -X POST -d '{"signingRoot":"0xb6bb8f3765f93f4f1e7c7348479289c9261399a3c6906685e320071a1a13955c"}' -H 'Content-Type: application/json' localhost:9000/sign/b7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a | ||
|
||
## Response | ||
Note: Unnecessary use of -X or --request, POST is already inferred. | ||
* Trying 127.0.0.1:9000... | ||
* TCP_NODELAY set | ||
* Connected to localhost (127.0.0.1) port 9000 (#0) | ||
> POST /sign/b7354252aa5bce27ab9537fd0158515935f3c3861419e1b4b6c8219b5dbd15fcf907bddf275442f3e32f904f79807a2a HTTP/1.1 | ||
> Host: localhost:9000 | ||
> User-Agent: curl/7.68.0 | ||
> Accept: */* | ||
> Content-Type: application/json | ||
> Content-Length: 84 | ||
> | ||
* upload completely sent off: 84 out of 84 bytes | ||
* Mark bundle as not supporting multiuse | ||
< HTTP/1.1 500 Internal Server Error | ||
< content-length: 43 | ||
< date: Wed, 30 Sep 2020 02:21:08 GMT | ||
< | ||
* Connection #0 to host localhost left intact | ||
{"error":"Storage error: PermissionDenied"} | ||
``` | ||
|
||
## Implementation | ||
|
||
Repository Url | Language | Organization | Commentary | ||
--- | --- | --- | --- | ||
[BLS Remote Signer](https://github.com/sigp/rust-bls-remote-signer) | Rust | Sigma Prime | Supports proposed specification. | ||
[Web3signer](https://github.com/PegaSysEng/web3signer) | Java | PegaSys | Supports proposed specification, although with [slightly different methods](https://pegasyseng.github.io/web3signer/web3signer-eth2.html):<br>{`/sign` => `/api/v1/eth2/sign`, `/publicKeys` => `/api/v1/eth2/publicKeys`}. | ||
|
||
The Prysm client supports a [Remote Signing Wallet](https://docs.prylabs.network/docs/wallet/remote/), however its API requires using gRPC as transport. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prysm supports both gRPC and JSON over HTTP. There's a grpc<->json/http proxy. We will update our documentations |
||
|
||
## Security Considerations | ||
|
||
### Threat model | ||
|
||
Let's consider the following threats and their mitigations: | ||
|
||
Threat | Mitigation(s) | ||
--- | --- | ||
An attacker can spoof the validator client. | See the discussion at [Authentication](#authentication). | ||
An attacker can send a crafted message to the signer. | See discussion at [Pre-image validation](#pre-image-validation). | ||
An attacker can create, update, or delete secret keys. | Keys are not to be writable via any interface of the remote signer. | ||
An attacker can repudiate a sent message. | Implement logging in the signer. Enhance it by sending logs to a syslog box. | ||
An attacker can disclose the contents of a private key by retrieving the key from storage. | Storage in Hardware security module (HSM).<br>_or_<br>Storage in Secrets management applications (e.g. Hashicorp Vault). | ||
An attacker can eavesdrop on the uploading of a secret key. | Upload the keys using a secure channel, based on each storage specification. | ||
An attacker can eavesdrop on the retrieval of a key from the remote signer. | Always pass the data between storage and remote signer node using a secure channel. | ||
An attacker can dump the memory in the remote signer to disclose a secret key. | Prevent physical access to the node running the remote signer.<br>_or_<br>Prevent access to the terminal of the node running the remote signer: Logs being sent to a syslog box. Deployments triggered by a simple, non-parameterized API.<br>_or_<br>Implement zeroization of the secret key at memory.<br>_or_<br>Explore the compilation and running of the remote signer in a Trusted execution environment (TEE). | ||
An attacker can DoS the remote signer. | Implement IP filtering.<br>_or_<br>Implement Rate limiting. | ||
|
||
## Copyright | ||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need for authentication if the requestor is not privileged. And it wouldn't be privileged if you removed the need for the signer to trust the
signingRoot
.So in essence, you now force the validator to store some sealer-credential, instead of a sealing-key -- just swapping one set of secrets for another.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. This rationale should be more emphatic on the point about swapping of secrets, since it beats the purpose of this API. Please notice that the following paragraph advises the reader to explore the use of network Access Control Lists (ACLs).