Skip to content

Commit

Permalink
feat(signer)!: add proxy keys (#39)
Browse files Browse the repository at this point in the history
* feat(signer)!: add proxy keys

- add `generate_proxy_keys` endpoint to the Signer API. Modify the signer client accordingly.

- add a `.dockerignore` file

- remove the need for modules to provide their id in requests. A module's JWT is now solely sufficient to identify the module.
  * `SigningService` now contains jwt <-> module_id in a bidirectional hashmap

- add authentication middleware to the signer service instead of manual auth in the handlers

- introduce `ModuleId` and `Jwt` wrapper types around strings to improve semantics (useful after a couple of mishaps with key <-> value directions across the different maps)
  * see `common::types`

- add example proxy key generation request in `da_commit` module

- small misc changes

- small reformatting

* chore(clippy): remove unnecessary `clone`

* chore(signer): remove `SignerModuleError::UnknownModuleId`

not needed anymore since JWTs now uniquely identify the module

* feat(docs)!: update with proxy keys

* chore(da_commit): add proxy key example

* chore: resolve TODO

* also, add `rust-toolchain.toml`

* chore: fix typo

* chore: move dependency to workspace

* chore: sync rust version in docs

* chore: reexport `SignedProxyDelegation` from prelude

* chore: use `fmt::Display` of `ModuleId` in traces

* chore: rename field back to `message`

* chore: make log level `error`

* chore: remove TODO

* fix: error log on module startup
  • Loading branch information
David-Petrov authored Aug 7, 2024
1 parent 4fac191 commit 511a5b0
Show file tree
Hide file tree
Showing 26 changed files with 416 additions and 102 deletions.
6 changes: 5 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
target
target
Dockerfile
.dockerignore
.git
.gitignore
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,5 @@ rand = "0.8.5"
dotenvy = "0.15.7"
indexmap = "2.2.6"
lazy_static = "1.5.0"
bimap = { version = "0.6.3", features = ["serde"] }
derive_more = "0.99.18"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A new Ethereum validator sidecar focused on standardizing the last mile of communication between validators and third-party protocols.

[Docs](https://commit-boost.github.io/commit-boost-client/) |
[Docs](https://commit-boost.github.io/commit-boost-client/) |
[Twitter](https://x.com/Commit_Boost)

## Overview
Expand Down Expand Up @@ -51,7 +51,7 @@ async fn main() {
let pubkey = *pubkeys.consensus.first().unwrap();

let datagram = Datagram { data: 42 };
let request = SignRequest::builder(config.id, pubkey).with_msg(&datagram);
let request = SignRequest::builder(pubkey).with_msg(&datagram);
let signature = config
.signer_client
.request_signature(&request)
Expand Down
92 changes: 86 additions & 6 deletions api/signer-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ paths:
summary: Get a list of public keys for which signatures may be requested
tags:
- Signer
security:
- BearerAuth: []
responses:
"200":
description: A list of Bls pubkeys
Expand All @@ -35,7 +37,6 @@ paths:
format: hex
pattern: "^0x[a-fA-F0-9]{96}$"
example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989"

"500":
description: Internal error
content:
Expand Down Expand Up @@ -67,10 +68,6 @@ paths:
schema:
type: object
properties:
id:
description: The module ID
type: string
example: "MY_MODULE_ID"
pubkey:
description: BLS public key of validator
type: string
Expand Down Expand Up @@ -99,7 +96,7 @@ paths:
pattern: "^0x[a-fA-F0-9]{192}$"
example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989"
"404":
description: Unknown value (pubkey, module id)
description: Unknown value (pubkey, etc.)
content:
application/json:
schema:
Expand Down Expand Up @@ -130,6 +127,89 @@ paths:
message:
type: string
example: "Internal error"

/signer/v1/generate_proxy_key:
post:
summary: Request a proxy key be generated for a specific consensus pubkey
tags:
- Signer
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
pubkey:
description: a validator BLS public key for which to generate a proxy key
type: string
format: hex
pattern: "^0x[a-fA-F0-9]{96}$"
example: "0xac5e059177afc33263e95d0be0690138b9a1d79a6e19018086a0362e0c30a50bf9e05a08cb44785724d0b2718c5c7118"
responses:
"200":
description: Successs
content:
application/json:
schema:
type: object
properties:
message:
type: object
properties:
delegator:
description: the validator BLS public key for which the proxy key was generated (the same one as requested)
type: string
format: hex
pattern: "^0x[a-fA-F0-9]{96}$"
example: "0xac5e059177afc33263e95d0be0690138b9a1d79a6e19018086a0362e0c30a50bf9e05a08cb44785724d0b2718c5c7118"
proxy:
description: the generated proxy public key
type: string
format: hex
pattern: "^0x[a-fA-F0-9]{96}$"
example: "0x8a481a7a51c430a9bafa64366bc4934f5880f5f1d97646f91680936a53f2a268fdde5369430a2b4bb700c5f82cfbab3f"
signature:
description: The signature of the proxy delegation
type: string
format: hex
pattern: "^0x[a-fA-F0-9]{192}$"
example: "0xabfacf1cd17d80abfc6fa6b8e534ab25cdb1f95a855706ef604672c8695401a84c7834008e57925d4259c551b7c03d1a16f05b082294fadcba802a61a5cccfb5e96dd1dce4c9dac3f6d15254495019146346670be1f374a67cb0cda2aaf72d00"
"404":
description: Unknown value (pubkey, etc.)
content:
application/json:
schema:
type: object
required:
- code
- message
properties:
code:
type: number
example: 404
message:
type: string
example: "Unknown pubkey"
"500":
description: Internal error
content:
application/json:
schema:
type: object
required:
- code
- message
properties:
code:
type: number
example: 500
message:
type: string
example: "Internal error"

components:
securitySchemes:
BearerAuth:
Expand Down
2 changes: 1 addition & 1 deletion bin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod prelude {
pub use cb_common::{
commit,
commit::request::SignRequest,
commit::request::{SignRequest, SignedProxyDelegation},
config::{load_builder_module_config, load_commit_module_config, StartCommitModuleConfig},
pbs::{BuilderEvent, BuilderEventClient, OnBuilderApiEvent},
utils::{initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, utcnow_us},
Expand Down
2 changes: 2 additions & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ thiserror.workspace = true
eyre.workspace = true
url.workspace = true
rand.workspace = true
bimap.workspace = true
derive_more.workspace = true
27 changes: 25 additions & 2 deletions crates/common/src/commit/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use serde::{Deserialize, Serialize};

use super::{
constants::{GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH},
constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH},
error::SignerClientError,
request::SignRequest,
request::{GenerateProxyRequest, SignRequest, SignedProxyDelegation},
};
use crate::DEFAULT_REQUEST_TIMEOUT;

Expand Down Expand Up @@ -87,4 +87,27 @@ impl SignerClient {

Ok(signature)
}

pub async fn generate_proxy_key(
&self,
pubkey: BlsPublicKey,
) -> Result<SignedProxyDelegation, SignerClientError> {
let url = format!("{}{}", self.url, GENERATE_PROXY_KEY_PATH);
let request = GenerateProxyRequest::new(pubkey);
let res = self.client.post(&url).json(&request).send().await?;

let status = res.status();
let response_bytes = res.bytes().await?;

if !status.is_success() {
return Err(SignerClientError::FailedRequest {
status: status.as_u16(),
error_msg: String::from_utf8_lossy(&response_bytes).into_owned(),
});
}

let signed_proxy_delegation = serde_json::from_slice(&response_bytes)?;

Ok(signed_proxy_delegation)
}
}
1 change: 1 addition & 0 deletions crates/common/src/commit/constants.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub const GET_PUBKEYS_PATH: &str = "/signer/v1/get_pubkeys";
pub const REQUEST_SIGNATURE_PATH: &str = "/signer/v1/request_signature";
pub const GENERATE_PROXY_KEY_PATH: &str = "/signer/v1/generate_proxy_key";
2 changes: 1 addition & 1 deletion crates/common/src/commit/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub enum SignerClientError {
#[error("invalid header value: {0}")]
InvalidHeader(#[from] reqwest::header::InvalidHeaderValue),

#[error("failed request: status {status} msg {error_msg}")]
#[error("failed request: status {status}; message: \"{error_msg}\"")]
FailedRequest { status: u16, error_msg: String },

#[error("serde decode error: {0}")]
Expand Down
21 changes: 15 additions & 6 deletions crates/common/src/commit/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,22 @@ impl SignedProxyDelegation {

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignRequest {
pub id: String,
pub pubkey: BlsPublicKey,
pub is_proxy: bool,
pub object_root: [u8; 32],
}

impl SignRequest {
pub fn new(
id: impl Into<String>,
pubkey: BlsPublicKey,
is_proxy: bool,
object_root: [u8; 32],
) -> SignRequest {
Self { id: id.into(), pubkey, is_proxy, object_root }
Self { pubkey, is_proxy, object_root }
}

pub fn builder(id: impl Into<String>, pubkey: BlsPublicKey) -> Self {
Self::new(id, pubkey, false, [0; 32])
pub fn builder(pubkey: BlsPublicKey) -> Self {
Self::new(pubkey, false, [0; 32])
}

pub fn is_proxy(self) -> Self {
Expand All @@ -63,6 +61,17 @@ impl SignRequest {
}

pub fn with_msg(self, msg: &impl TreeHash) -> Self {
Self { object_root: msg.tree_hash_root().0, ..self }
self.with_root(msg.tree_hash_root().0)
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerateProxyRequest {
pub pubkey: BlsPublicKey,
}

impl GenerateProxyRequest {
pub fn new(pubkey: BlsPublicKey) -> Self {
GenerateProxyRequest { pubkey }
}
}
14 changes: 7 additions & 7 deletions crates/common/src/config/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
utils::load_file_from_env,
BUILDER_SERVER_ENV,
},
types::Chain,
types::{Chain, Jwt, ModuleId},
};

#[derive(Debug, Deserialize, Serialize)]
Expand All @@ -25,7 +25,7 @@ pub enum ModuleKind {
#[derive(Debug, Deserialize, Serialize)]
pub struct StaticModuleConfig {
/// Unique id of the module
pub id: String,
pub id: ModuleId,
/// Docker image of the module
pub docker_image: String,
/// Type of the module
Expand All @@ -37,7 +37,7 @@ pub struct StaticModuleConfig {
#[derive(Debug)]
pub struct StartCommitModuleConfig<T = ()> {
/// Unique id of the module
pub id: String,
pub id: ModuleId,
/// Chain spec
pub chain: Chain,
/// Signer client to call Signer API
Expand All @@ -52,8 +52,8 @@ pub struct StartCommitModuleConfig<T = ()> {
/// - [MODULE_JWT_ENV] - the jwt token for the module
// TODO: add metrics url here
pub fn load_commit_module_config<T: DeserializeOwned>() -> Result<StartCommitModuleConfig<T>> {
let module_id = load_env_var(MODULE_ID_ENV)?;
let module_jwt = load_env_var(MODULE_JWT_ENV)?;
let module_id = ModuleId(load_env_var(MODULE_ID_ENV)?);
let module_jwt = Jwt(load_env_var(MODULE_JWT_ENV)?);
let signer_server_address = load_env_var(SIGNER_SERVER_ENV)?;

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -111,7 +111,7 @@ pub fn load_commit_module_config<T: DeserializeOwned>() -> Result<StartCommitMod
#[derive(Debug)]
pub struct StartBuilderModuleConfig<T> {
/// Unique id of the module
pub id: String,
pub id: ModuleId,
/// Chain spec
pub chain: Chain,
/// Where to listen for Builder events
Expand All @@ -122,7 +122,7 @@ pub struct StartBuilderModuleConfig<T> {

pub fn load_builder_module_config<T: DeserializeOwned>() -> eyre::Result<StartBuilderModuleConfig<T>>
{
let module_id = load_env_var(MODULE_ID_ENV)?;
let module_id = ModuleId(load_env_var(MODULE_ID_ENV)?);
let builder_events_port: u16 = load_env_var(BUILDER_SERVER_ENV)?.parse()?;

#[derive(Debug, Deserialize)]
Expand Down
6 changes: 3 additions & 3 deletions crates/common/src/config/signer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use bimap::BiHashMap;

use eyre::Result;
use serde::{Deserialize, Serialize};
Expand All @@ -8,7 +8,7 @@ use super::{
utils::{load_env_var, load_jwts},
CommitBoostConfig,
};
use crate::{loader::SignerLoader, types::Chain};
use crate::{loader::SignerLoader, types::{Chain, Jwt, ModuleId}};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SignerConfig {
Expand All @@ -28,7 +28,7 @@ pub struct StartSignerConfig {
pub chain: Chain,
pub loader: SignerLoader,
pub server_port: u16,
pub jwts: HashMap<String, String>,
pub jwts: BiHashMap<ModuleId, Jwt>,
}

impl StartSignerConfig {
Expand Down
9 changes: 5 additions & 4 deletions crates/common/src/config/utils.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::collections::HashMap;

use bimap::BiHashMap;
use eyre::{Context, Result};
use serde::de::DeserializeOwned;

use crate::types::{Jwt, ModuleId};

use super::constants::JWTS_ENV;

pub fn load_env_var(env: &str) -> Result<String> {
Expand All @@ -20,8 +21,8 @@ pub fn load_file_from_env<T: DeserializeOwned>(env: &str) -> Result<T> {
load_from_file(&path)
}

/// Loads a map of module id -> jwt token from a json env
pub fn load_jwts() -> Result<HashMap<String, String>> {
/// Loads a bidirectional map of module id <-> jwt token from a json env
pub fn load_jwts() -> Result<BiHashMap<ModuleId, Jwt>> {
let jwts = std::env::var(JWTS_ENV).wrap_err(format!("{JWTS_ENV} is not set"))?;
serde_json::from_str(&jwts).wrap_err("could not deserialize json from string")
}
Loading

0 comments on commit 511a5b0

Please sign in to comment.