-
Notifications
You must be signed in to change notification settings - Fork 946
[Merged by Bors] - [Remote signer] Add signer consumer lib #1763
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
Closed
Closed
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| [package] | ||
| name = "remote_signer_consumer" | ||
| version = "0.2.0" | ||
| authors = ["Herman Junge <herman@sigmaprime.io>"] | ||
| edition = "2018" | ||
|
|
||
| [dev-dependencies] | ||
| rand = "0.7.3" | ||
| remote_signer_test = { path = "../../testing/remote_signer_test" } | ||
|
|
||
| [dependencies] | ||
| reqwest = { version = "0.10.8", features = ["json"] } | ||
| serde = { version = "1.0.116", features = ["derive"] } | ||
| tokio = { version = "0.2.22", features = ["time"] } | ||
| types = { path = "../../consensus/types" } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| use crate::{ | ||
| Error, RemoteSignerObject, RemoteSignerRequestBody, RemoteSignerResponseBodyError, | ||
| RemoteSignerResponseBodyOK, | ||
| }; | ||
| use reqwest::StatusCode; | ||
| pub use reqwest::Url; | ||
| use types::{Domain, Fork, Hash256}; | ||
|
|
||
| /// A wrapper around `reqwest::Client` which provides convenience methods | ||
| /// to interface with a BLS Remote Signer. | ||
| pub struct RemoteSignerHttpConsumer { | ||
| client: reqwest::Client, | ||
| server: Url, | ||
| } | ||
|
|
||
| impl RemoteSignerHttpConsumer { | ||
| pub fn from_components(server: Url, client: reqwest::Client) -> Self { | ||
| Self { client, server } | ||
| } | ||
|
|
||
| /// `POST /sign/:public-key` | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `public_key` - Goes within the url to identify the key we want to use as signer. | ||
| /// * `bls_domain` - BLS Signature domain. Supporting `BeaconProposer`, `BeaconAttester`,`Randao`. | ||
| /// * `data` - A `BeaconBlock`, `AttestationData`, or `Epoch`. | ||
| /// * `fork` - A `Fork` object containing previous and current versions. | ||
| /// * `genesis_validators_root` - A `Hash256` for domain separation and chain versioning. | ||
| /// | ||
| /// It sends through the wire a serialized `RemoteSignerRequestBody`. | ||
| pub async fn sign<R: RemoteSignerObject>( | ||
| &self, | ||
| public_key: &str, | ||
| bls_domain: Domain, | ||
| data: R, | ||
| fork: Fork, | ||
| genesis_validators_root: Hash256, | ||
| ) -> Result<String, Error> { | ||
| if public_key.is_empty() { | ||
| return Err(Error::InvalidParameter( | ||
| "Empty parameter public_key".to_string(), | ||
| )); | ||
| } | ||
|
|
||
| let mut path = self.server.clone(); | ||
| path.path_segments_mut() | ||
| .map_err(|()| Error::InvalidUrl(self.server.clone()))? | ||
| .push("sign") | ||
| .push(public_key); | ||
|
|
||
| let bls_domain = match bls_domain { | ||
| Domain::BeaconProposer => data.validate_object(bls_domain), | ||
| Domain::BeaconAttester => data.validate_object(bls_domain), | ||
| Domain::Randao => data.validate_object(bls_domain), | ||
| _ => Err(Error::InvalidParameter(format!( | ||
| "Unsupported BLS Domain: {:?}", | ||
| bls_domain | ||
| ))), | ||
| }?; | ||
|
|
||
| let body = RemoteSignerRequestBody { | ||
| bls_domain, | ||
| data, | ||
| fork, | ||
| genesis_validators_root, | ||
| }; | ||
|
|
||
| let response = self | ||
| .client | ||
| .post(path) | ||
| .json(&body) | ||
| .send() | ||
| .await | ||
| .map_err(Error::Reqwest)?; | ||
|
|
||
| match response.status() { | ||
| StatusCode::OK => match response.json::<RemoteSignerResponseBodyOK>().await { | ||
| Ok(resp_json) => Ok(resp_json.signature), | ||
| Err(e) => Err(Error::Reqwest(e)), | ||
| }, | ||
| _ => match response.json::<RemoteSignerResponseBodyError>().await { | ||
| Ok(resp_json) => Err(Error::ServerMessage(resp_json.error)), | ||
| Err(e) => Err(Error::Reqwest(e)), | ||
| }, | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| //! Enables the [Lighthouse Ethereum 2.0 Client] to consume signatures from the | ||
| //! [BLS Remote Signer]. | ||
| //! | ||
| //! ## About | ||
| //! | ||
| //! The lighthouse client needs to include this crate, and implement the | ||
| //! adequate bypasses and CLI flags needed to find the remote signer and perform | ||
| //! the HTTP requests. | ||
| //! | ||
| //! As defined by the [EIP-3030] specification, this crate will take the | ||
| //! received object data and parameters, and send them to the remote signer | ||
| //! for the production of a signing root hash and signature (the latter if the | ||
| //! signer has in storage the key identified at request). | ||
| //! | ||
| //! ## Usage | ||
| //! | ||
| //! ### RemoteSignerHttpConsumer | ||
| //! | ||
| //! Just provide an `Url` and a timeout | ||
| //! | ||
| //! ``` | ||
| //! use remote_signer_consumer::RemoteSignerHttpConsumer; | ||
| //! use reqwest::{ClientBuilder, Url}; | ||
| //! use tokio::time::Duration; | ||
| //! | ||
| //! let url: Url = "http://127.0.0.1:9000".parse().unwrap(); | ||
| //! let reqwest_client = ClientBuilder::new() | ||
| //! .timeout(Duration::from_secs(2)) | ||
| //! .build() | ||
| //! .unwrap(); | ||
| //! | ||
| //! let signer = RemoteSignerHttpConsumer::from_components(url, reqwest_client); | ||
| //! | ||
| //! ``` | ||
| //! | ||
| //! ## sign API | ||
| //! | ||
| //! `POST /sign/:identifier` | ||
| //! | ||
| //! ### Arguments | ||
| //! | ||
| //! #### `public_key` | ||
| //! | ||
| //! Goes within the url to identify the key we want to use as signer. | ||
| //! | ||
| //! #### `bls_domain` | ||
| //! | ||
| //! [BLS Signature domain]. Supporting `BeaconProposer`, `BeaconAttester`, | ||
| //! `Randao`. | ||
| //! | ||
| //! #### `data` | ||
| //! | ||
| //! A `BeaconBlock`, `AttestationData`, or `Epoch`. | ||
| //! | ||
| //! #### `fork` | ||
| //! | ||
| //! A [`Fork`] object, containing previous and current versions. | ||
| //! | ||
| //! #### `genesis_validators_root` | ||
| //! | ||
| //! A [`Hash256`] for domain separation and chain versioning. | ||
| //! | ||
| //! ### Behavior | ||
| //! | ||
| //! Upon receiving and validating the parameters, the signer sends through the | ||
| //! wire a serialized `RemoteSignerRequestBody`. Receiving a `200` message with | ||
| //! the `signature` field inside a JSON payload, or an error. | ||
| //! | ||
| //! ## How it works | ||
| //! | ||
| //! The production of a _local_ signature (i.e. inside the Lighthouse client) | ||
| //! has slight variations among the kind of objects (block, attestation, | ||
| //! randao). | ||
| //! | ||
| //! To sign a message, the following procedures are needed: | ||
| //! | ||
| //! * Get the `fork_version` - From the objects `Fork` and `Epoch`. | ||
| //! * Compute the [`fork_data_root`] - From the `fork_version` and the | ||
| //! `genesis_validators_root`. | ||
| //! * Compute the [`domain`] - From the `fork_data_root` and the `bls_domain`. | ||
| //! * With the `domain`, the object (or `epoch` in the case of [`randao`]) | ||
| //! can be merkelized into its [`signing_root`] to be signed. | ||
| //! | ||
| //! In short, to obtain a signature from the remote signer, we need to produce | ||
| //! (and serialize) the following objects: | ||
| //! | ||
| //! * `bls_domain`. | ||
| //! * `data` of the object, if this is a block proposal, an attestation, or an epoch. | ||
| //! * `epoch`, obtained from the object. | ||
| //! * `fork`. | ||
| //! * `genesis_validators_root`. | ||
| //! | ||
| //! And, of course, the identifier of the secret key, the `public_key`. | ||
| //! | ||
| //! ## Future Work | ||
| //! | ||
| //! ### EIP-3030 | ||
| //! | ||
| //! Work is being done to [standardize the API of the remote signers]. | ||
| //! | ||
| //! [`domain`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_domain | ||
| //! [`Epoch`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types | ||
| //! [`fork_data_root`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_fork_data_root | ||
| //! [`Fork`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#fork | ||
| //! [`Hash256`]: https://docs.rs/ethereum-types/0.9.2/ethereum_types/struct.H256.html | ||
| //! [`randao`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#randao | ||
| //! [`signing_root`]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#compute_signing_root | ||
| //! [BLS Remote Signer]: https://github.com/sigp/rust-bls-remote-signer | ||
| //! [BLS Signature domain]: https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/beacon-chain.md#domain-types | ||
| //! [EIP-3030]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3030.md | ||
| //! [Lighthouse Ethereum 2.0 Client]: https://github.com/sigp/lighthouse | ||
| //! [standardize the API of the remote signers]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3030.md | ||
|
|
||
| mod http_client; | ||
|
|
||
| pub use http_client::RemoteSignerHttpConsumer; | ||
| pub use reqwest::Url; | ||
| use serde::{Deserialize, Serialize}; | ||
| use types::{AttestationData, BeaconBlock, Domain, Epoch, EthSpec, Fork, Hash256, SignedRoot}; | ||
|
|
||
| #[derive(Debug)] | ||
| pub enum Error { | ||
| /// The `reqwest` client raised an error. | ||
| Reqwest(reqwest::Error), | ||
| /// The server returned an error message where the body was able to be parsed. | ||
| ServerMessage(String), | ||
| /// The supplied URL is badly formatted. It should look something like `http://127.0.0.1:5052`. | ||
| InvalidUrl(Url), | ||
| /// The supplied parameter is invalid. | ||
| InvalidParameter(String), | ||
| } | ||
|
|
||
| #[derive(Serialize)] | ||
| struct RemoteSignerRequestBody<T> { | ||
| /// BLS Signature domain. Supporting `BeaconProposer`, `BeaconAttester`,`Randao`. | ||
| bls_domain: String, | ||
|
|
||
| /// A `BeaconBlock`, `AttestationData`, or `Epoch`. | ||
| data: T, | ||
|
|
||
| /// A `Fork` object containing previous and current versions. | ||
| fork: Fork, | ||
|
|
||
| /// A `Hash256` for domain separation and chain versioning. | ||
| genesis_validators_root: Hash256, | ||
| } | ||
|
|
||
| #[derive(Deserialize)] | ||
| struct RemoteSignerResponseBodyOK { | ||
| signature: String, | ||
| } | ||
|
|
||
| #[derive(Deserialize)] | ||
| struct RemoteSignerResponseBodyError { | ||
| error: String, | ||
| } | ||
|
|
||
| /// Allows the verification of the BeaconBlock and AttestationData objects | ||
| /// to be sent through the wire, against their BLS Domains. | ||
| pub trait RemoteSignerObject: SignedRoot + Serialize { | ||
| fn validate_object(&self, domain: Domain) -> Result<String, Error>; | ||
| fn get_epoch(&self) -> Epoch; | ||
| } | ||
|
|
||
| impl<E: EthSpec> RemoteSignerObject for BeaconBlock<E> { | ||
| fn validate_object(&self, domain: Domain) -> Result<String, Error> { | ||
| match domain { | ||
| Domain::BeaconProposer => Ok("beacon_proposer".to_string()), | ||
| _ => Err(Error::InvalidParameter(format!( | ||
| "Domain mismatch for the BeaconBlock object. Expected BeaconProposer, got {:?}", | ||
| domain | ||
| ))), | ||
| } | ||
| } | ||
|
|
||
| fn get_epoch(&self) -> Epoch { | ||
| self.epoch() | ||
| } | ||
| } | ||
|
|
||
| impl RemoteSignerObject for AttestationData { | ||
| fn validate_object(&self, domain: Domain) -> Result<String, Error> { | ||
| match domain { | ||
| Domain::BeaconAttester => Ok("beacon_attester".to_string()), | ||
| _ => Err(Error::InvalidParameter(format!( | ||
| "Domain mismatch for the AttestationData object. Expected BeaconAttester, got {:?}", | ||
| domain | ||
| ))), | ||
| } | ||
| } | ||
|
|
||
| fn get_epoch(&self) -> Epoch { | ||
| self.target.epoch | ||
| } | ||
| } | ||
|
|
||
| impl RemoteSignerObject for Epoch { | ||
| fn validate_object(&self, domain: Domain) -> Result<String, Error> { | ||
| match domain { | ||
| Domain::Randao => Ok("randao".to_string()), | ||
| _ => Err(Error::InvalidParameter(format!( | ||
| "Domain mismatch for the Epoch object. Expected Randao, got {:?}", | ||
| domain | ||
| ))), | ||
| } | ||
| } | ||
|
|
||
| fn get_epoch(&self) -> Epoch { | ||
| *self | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.