Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions api/signer-api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
openapi: "3.0.2"
info:
title: Signer API
version: "0.1.0"
description: API that allows commit modules to request generic signatures from validators
tags:
- name: Signer
paths:
/signer/v1/pubkeys:
get:
summary: Get a list of public keys for which signatures may be requested
tags:
- Signer
responses:
"200":
description: A list of Bls pubkeys
content:
application/json:
schema:
type: object
properties:
consensus:
description: Consensus validator pubkeys
type: array
items:
type: string
format: hex
pattern: "^0x[a-fA-F0-9]{96}$"
example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989"
proxy:
description: Proxy validator pubkeys
type: array
items:
type: string
format: hex
pattern: "^0x[a-fA-F0-9]{96}$"
example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989"

"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"

/signer/v1/request_signature:
post:
summary: Send a signature request
tags:
- Signer
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
id:
description: The module ID
type: string
example: "MY_MODULE_ID"
pubkey:
description: BLS public key of validator
type: string
format: hex
pattern: "^0x[a-fA-F0-9]{96}$"
example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989"
is_proxy:
description: Whether the request is for a proxy pubkey
type: boolean
example: false
object_root:
description: The root of the object to be signed
type: string
format: hex
pattern: "^0x[a-fA-F0-9]{64}$"
example: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9"
responses:
"200":
description: Successs
content:
application/json:
schema:
type: string
description: The validator signature
format: hex
pattern: "^0x[a-fA-F0-9]{192}$"
example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989"
"404":
description: Unknown value (pubkey, module id)
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:
type: http
scheme: bearer
bearerFormat: JWT
3 changes: 3 additions & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ blst.workspace = true
tree_hash.workspace = true
tree_hash_derive.workspace = true
rand.workspace = true

reqwest.workspace = true
thiserror.workspace = true
80 changes: 80 additions & 0 deletions crates/common/src/commit/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use alloy_rpc_types_beacon::{BlsPublicKey, BlsSignature};
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use serde::{Deserialize, Serialize};

use super::{error::SignerClientError, request::SignRequest};

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GetPubkeysResponse {
pub consensus: Vec<BlsPublicKey>,
pub proxy: Vec<BlsPublicKey>,
}

const GET_PUBKEYS_PATH: &str = "/signer/v1/pubkeys";
const REQUEST_SIGNATURE_PATH: &str = "/signer/v1/request_signature";

/// Client used by commit modules to request signatures via the Signer API to the Signer Module
pub struct SignerClient {
/// Url endpoint of the Signer Module
url: String,
client: reqwest::Client,
}

impl SignerClient {
/// Create a new SignerClient
pub fn new(url: String, jwt: &str) -> Result<Self, SignerClientError> {
let mut headers = HeaderMap::new();

let mut auth_value = HeaderValue::from_str(&format!("Bearer {}", jwt))?;
auth_value.set_sensitive(true);
headers.insert(AUTHORIZATION, auth_value);

let client = reqwest::ClientBuilder::new().default_headers(headers).build()?;

Ok(Self { url, client })
}

/// Request a list of validator pubkeys for which signatures can be requested.
/// TODO: add more docs on how proxy keys work
pub async fn get_pubkeys(&self) -> Result<GetPubkeysResponse, SignerClientError> {
let url = format!("{}{}", self.url, GET_PUBKEYS_PATH);
let res = self.client.get(&url).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 parsed_response: GetPubkeysResponse = serde_json::from_slice(&response_bytes)?;

Ok(parsed_response)
}

/// Send a signature request to the Signer Module
pub async fn request_signature(
&self,
request: &SignRequest,
) -> Result<BlsSignature, SignerClientError> {
let url = format!("{}{}", self.url, REQUEST_SIGNATURE_PATH);
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 signature: BlsSignature = serde_json::from_slice(&response_bytes)?;

Ok(signature)
}
}
14 changes: 14 additions & 0 deletions crates/common/src/commit/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#[derive(Debug, thiserror::Error)]
pub enum SignerClientError {
#[error("reqwest error: {0}")]
ReqwestError(#[from] reqwest::Error),

#[error("invalid header value: {0}")]
InvalidHeader(#[from] reqwest::header::InvalidHeaderValue),

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

#[error("serde decode error: {0}")]
SerdeDecodeError(#[from] serde_json::Error),
}
3 changes: 3 additions & 0 deletions crates/common/src/commit/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod client;
pub mod error;
pub mod request;
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use alloy_rpc_types_beacon::{BlsPublicKey, BlsSignature};
use blst::BLST_ERROR;
use cb_common::{signature::verify_signed_builder_message, types::Chain};
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;

use crate::{signature::verify_signed_builder_message, types::Chain};

// TODO: might need to adapt the SignedProxyDelegation so that it goes through web3 signer
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct ProxyDelegation {
Expand Down
1 change: 1 addition & 0 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod commit;
pub mod config;
pub mod constants;
pub mod pbs;
Expand Down
1 change: 0 additions & 1 deletion crates/crypto/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub mod error;
pub mod manager;
pub mod service;
pub mod types;
11 changes: 6 additions & 5 deletions crates/crypto/src/manager.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::collections::HashMap;

use alloy_rpc_types_beacon::{BlsPublicKey, BlsSignature};
use cb_common::{signer::Signer, types::Chain};
use cb_common::{
commit::request::{ProxyDelegation, SignedProxyDelegation},
signer::Signer,
types::Chain,
};
use tree_hash::TreeHash;

use crate::{
error::SignError,
types::{ProxyDelegation, SignedProxyDelegation},
};
use crate::error::SignError;

// For extra safety and to avoid risking signing malicious messages, use a proxy setup:
// proposer creates a new ephemeral keypair which will be used to sign commit messages,
Expand Down
3 changes: 2 additions & 1 deletion crates/crypto/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use axum::{
Json,
};
use cb_common::{
commit::request::SignRequest,
config::SignerConfig,
pbs::{COMMIT_BOOST_API, PUBKEYS_PATH, SIGN_REQUEST_PATH},
types::Chain,
Expand All @@ -16,7 +17,7 @@ use tokio::net::TcpListener;
use tracing::{error, info};
use uuid::Uuid;

use crate::{error::SignError, manager::SigningManager, types::SignRequest};
use crate::{error::SignError, manager::SigningManager};

pub struct SigningService;

Expand Down
2 changes: 1 addition & 1 deletion examples/da_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use std::time::Duration;

use alloy_rpc_types_beacon::{BlsPublicKey, BlsSignature};
use cb_common::{
commit::request::SignRequest,
config::{load_module_config, ModuleConfig},
pbs::{COMMIT_BOOST_API, PUBKEYS_PATH, SIGN_REQUEST_PATH},
utils::initialize_tracing_log,
};
use cb_crypto::types::SignRequest;
use serde::Deserialize;
use tokio::time::sleep;
use tracing::{error, info};
Expand Down