diff --git a/Cargo.lock b/Cargo.lock index 3afd9e63..539d458b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1487,6 +1487,7 @@ dependencies = [ "blst", "bytes", "cipher 0.4.4", + "const_format", "ctr 0.9.2", "derive_more 2.0.1", "docker-image", diff --git a/Cargo.toml b/Cargo.toml index 3955e9cc..d3a6d7d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ cb-signer = { path = "crates/signer" } cipher = "0.4" clap = { version = "4.5.4", features = ["derive", "env"] } color-eyre = "0.6.3" +const_format = "0.2.34" ctr = "0.9.2" derive_more = { version = "2.0.1", features = ["deref", "display", "from", "into"] } docker-compose-types = "0.16.0" diff --git a/api/signer-api.yml b/api/signer-api.yml index 69239e38..a6c427a4 100644 --- a/api/signer-api.yml +++ b/api/signer-api.yml @@ -1,7 +1,7 @@ -openapi: "3.0.2" +openapi: "3.1.1" info: title: Signer API - version: "0.1.0" + version: "0.2.0" description: API that allows commit modules to request generic signatures from validators tags: - name: Signer @@ -58,9 +58,9 @@ paths: type: string example: "Internal error" - /signer/v1/request_signature: + /signer/v1/request_signature/bls: post: - summary: Request a signature for a 32-byte blob of data (typically a hash), signed by the requested BLS or ECDSA key. + summary: Request a signature for a 32-byte blob of data (typically a hash), signed by the BLS private key for the requested public key. tags: - Signer security: @@ -71,65 +71,309 @@ paths: application/json: schema: type: object - required: [type, object_root] - oneOf: - - required: [pubkey] - - required: [proxy] + required: [pubkey, object_root] properties: - type: - description: Type of the sign request - type: string - enum: [consensus, proxy_bls, proxy_ecdsa] pubkey: description: The 48-byte BLS public key, with optional `0x` prefix, of the proposer key that you want to request a signature from. $ref: "#/components/schemas/BlsPubkey" + object_root: + description: The 32-byte data you want to sign, with optional `0x` prefix. + $ref: "#/components/schemas/B256" + example: + pubkey: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + responses: + "200": + description: A successful signature response. + content: + application/json: + schema: + $ref: "#/components/schemas/BlsSignatureResponse" + example: + pubkey: "0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4" + object_root: "0x0123456789012345678901234567890123456789012345678901234567890123" + module_signing_id: "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" + signature: "0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115" + "400": + description: | + This can occur in several scenarios: + - The Commit-Boost configuration file does not contain a signing ID for the module that made the request. + - You requested an operation while using the Dirk signer mode instead of locally-managed signer mode, but Dirk doesn't support that operation. + - Something went wrong while preparing your request; the error text will provide more information. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 400 + message: + type: string + example: "Bad request: Invalid pubkey format" + "401": + description: The requesting module did not provide a JWT string in the request's authorization header, or the JWT string was not configured in the signer service's configuration file as belonging to the module. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 401 + message: + type: string + example: "Unauthorized" + + "404": + description: You either requested a route that doesn't exist, or you requested a signature from a key that does not exist. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 404 + message: + type: string + example: "Unknown pubkey" + "429": + description: Your module attempted and failed JWT authentication too many times recently, and is currently timed out. It cannot make any more requests until the timeout ends. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 429 + message: + type: string + example: "Too many requests" + "500": + description: Your request was valid, but something went wrong internally that prevented it from being fulfilled. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 500 + message: + type: string + example: "Internal error" + "502": + description: The signer service is running in Dirk signer mode, but Dirk could not be reached. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 502 + message: + type: string + example: "Bad gateway: Dirk signer service is unreachable" + + /signer/v1/request_signature/proxy-bls: + post: + summary: Request a signature for a 32-byte blob of data (typically a hash), signed by the BLS private key for the requested proxy public key. + tags: + - Signer + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [proxy, object_root] + properties: proxy: description: The 48-byte BLS public key (for `proxy_bls` mode) or the 20-byte Ethereum address (for `proxy_ecdsa` mode), with optional `0x` prefix, of the proxy key that you want to request a signature from. - oneOf: - - $ref: "#/components/schemas/BlsPubkey" - - $ref: "#/components/schemas/EcdsaAddress" + $ref: "#/components/schemas/BlsPubkey" object_root: description: The 32-byte data you want to sign, with optional `0x` prefix. - type: string - format: hex - pattern: "^0x[a-fA-F0-9]{64}$" - example: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" - examples: - Consensus: - value: - type: "consensus" - pubkey: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" - object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" - ProxyBls: - value: - type: "proxy_bls" - proxy: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" - object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" - ProxyEcdsa: - value: - type: "proxy_ecdsa" - proxy: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" - object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + $ref: "#/components/schemas/B256" + example: + pubkey: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" responses: "200": - description: A successful signature response. The returned signature is the Merkle root hash of the provided `object_root` field and the requesting module's Signing ID as specified in the Commit-Boost configuration. For details on this signature, see the [signature structure documentation](https://commit-boost.github.io/commit-boost-client/developing/prop-commit-signing.md#structure-of-a-signature). + description: A successful signature response. content: application/json: schema: - oneOf: - - $ref: "#/components/schemas/BlsSignature" - - $ref: "#/components/schemas/EcdsaSignature" - examples: - Consensus: - value: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" - ProxyBls: - value: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" - ProxyEcdsa: - value: "0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c" + $ref: "#/components/schemas/BlsSignatureResponse" + example: + pubkey: "0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4" + object_root: "0x0123456789012345678901234567890123456789012345678901234567890123" + module_signing_id: "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" + signature: "0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115" + "400": + description: | + This can occur in several scenarios: + - The Commit-Boost configuration file does not contain a signing ID for the module that made the request. + - You requested an operation while using the Dirk signer mode instead of locally-managed signer mode, but Dirk doesn't support that operation. + - Something went wrong while preparing your request; the error text will provide more information. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 400 + message: + type: string + example: "Bad request: Invalid pubkey format" + "401": + description: The requesting module did not provide a JWT string in the request's authorization header, or the JWT string was not configured in the signer service's configuration file as belonging to the module. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 401 + message: + type: string + example: "Unauthorized" + + "404": + description: You either requested a route that doesn't exist, or you requested a signature from a key that does not exist. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 404 + message: + type: string + example: "Unknown pubkey" + "429": + description: Your module attempted and failed JWT authentication too many times recently, and is currently timed out. It cannot make any more requests until the timeout ends. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 429 + message: + type: string + example: "Too many requests" + "500": + description: Your request was valid, but something went wrong internally that prevented it from being fulfilled. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 500 + message: + type: string + example: "Internal error" + "502": + description: The signer service is running in Dirk signer mode, but Dirk could not be reached. + content: + application/json: + schema: + type: object + required: + - code + - message + properties: + code: + type: number + example: 502 + message: + type: string + example: "Bad gateway: Dirk signer service is unreachable" + + /signer/v1/request_signature/proxy-ecdsa: + post: + summary: Request a signature for a 32-byte blob of data (typically a hash), signed by the ECDSA private key for the requested proxy Ethereum address. + tags: + - Signer + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [proxy, object_root] + properties: + proxy: + description: The 20-byte Ethereum address, with optional `0x` prefix, of the proxy key that you want to request a signature from. + $ref: "#/components/schemas/EcdsaAddress" + object_root: + description: The 32-byte data you want to sign, with optional `0x` prefix. + $ref: "#/components/schemas/B256" + example: + proxy: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + responses: + "200": + description: A successful signature response. + content: + application/json: + schema: + $ref: "#/components/schemas/EcdsaSignatureResponse" + example: + address: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + module_signing_id: "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" + signature: "0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c" "400": description: | This can occur in several scenarios: - + - The Commit-Boost configuration file does not contain a signing ID for the module that made the request. - You requested an operation while using the Dirk signer mode instead of locally-managed signer mode, but Dirk doesn't support that operation. - Something went wrong while preparing your request; the error text will provide more information. content: @@ -351,6 +595,11 @@ components: scheme: bearer bearerFormat: JWT schemas: + B256: + type: string + format: hex + pattern: "^0x[a-fA-F0-9]{64}$" + example: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" BlsPubkey: type: string format: hex @@ -371,3 +620,33 @@ components: format: hex pattern: "^0x[a-fA-F0-9]{130}$" example: "0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c" + BlsSignatureResponse: + type: object + properties: + pubkey: + description: The BLS public key corresponding to the private key that was used to sign the request + $ref: "#/components/schemas/BlsPubkey" + object_root: + description: The 32-byte data that was signed, with `0x` prefix + $ref: "#/components/schemas/B256" + signing_id: + description: The signing ID of the module that requested the signature, as specified in the Commit-Boost configuration + $ref: "#/components/schemas/B256" + signature: + description: The BLS signature of the Merkle root hash of the provided `object_root` field and the requesting module's Signing ID. For details on this signature, see the [signature structure documentation](https://commit-boost.github.io/commit-boost-client/developing/prop-commit-signing.md#structure-of-a-signature). + $ref: "#/components/schemas/BlsSignature" + EcdsaSignatureResponse: + type: object + properties: + address: + description: The ECDSA address corresponding to the private key that was used to sign the request + $ref: "#/components/schemas/EcdsaAddress" + object_root: + description: The 32-byte data that was signed, with `0x` prefix + $ref: "#/components/schemas/B256" + module_signing_id: + description: The signing ID of the module that requested the signature, as specified in the Commit-Boost configuration + $ref: "#/components/schemas/B256" + signature: + description: The ECDSA signature (in Ethereum RSV format) of the Merkle root hash of the provided `object_root` field and the requesting module's Signing ID. For details on this signature, see the [signature structure documentation](https://commit-boost.github.io/commit-boost-client/developing/prop-commit-signing.md#structure-of-a-signature). + $ref: "#/components/schemas/EcdsaSignature" \ No newline at end of file diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index fe2f9aec..8653ea44 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -15,6 +15,7 @@ bimap.workspace = true blst.workspace = true bytes.workspace = true cipher.workspace = true +const_format.workspace = true ctr.workspace = true derive_more.workspace = true docker-image.workspace = true diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 34413b65..6945b4d2 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -1,22 +1,29 @@ use std::time::{Duration, Instant}; -use alloy::{primitives::Address, rpc::types::beacon::BlsSignature}; +use alloy::primitives::Address; use eyre::WrapErr; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use url::Url; use super::{ - constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, + constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH}, error::SignerClientError, request::{ EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, ProxyId, SignConsensusRequest, - SignProxyRequest, SignRequest, SignedProxyDelegation, + SignProxyRequest, SignedProxyDelegation, }, }; use crate::{ + commit::{ + constants::{ + REQUEST_SIGNATURE_BLS_PATH, REQUEST_SIGNATURE_PROXY_BLS_PATH, + REQUEST_SIGNATURE_PROXY_ECDSA_PATH, + }, + response::{BlsSignResponse, EcdsaSignResponse}, + }, constants::SIGNER_JWT_EXPIRATION, - signer::{BlsPublicKey, EcdsaSignature}, + signer::BlsPublicKey, types::{Jwt, ModuleId}, utils::create_jwt, DEFAULT_REQUEST_TIMEOUT, @@ -99,13 +106,18 @@ impl SignerClient { } /// Send a signature request - async fn request_signature(&mut self, request: &SignRequest) -> Result + async fn request_signature( + &mut self, + route: &str, + request: &Q, + ) -> Result where + Q: Serialize, T: for<'de> Deserialize<'de>, { self.refresh_jwt()?; - let url = self.url.join(REQUEST_SIGNATURE_PATH)?; + let url = self.url.join(route)?; let res = self.client.post(url).json(&request).send().await?; let status = res.status(); @@ -126,22 +138,22 @@ impl SignerClient { pub async fn request_consensus_signature( &mut self, request: SignConsensusRequest, - ) -> Result { - self.request_signature(&request.into()).await + ) -> Result { + self.request_signature(REQUEST_SIGNATURE_BLS_PATH, &request).await } pub async fn request_proxy_signature_ecdsa( &mut self, request: SignProxyRequest
, - ) -> Result { - self.request_signature(&request.into()).await + ) -> Result { + self.request_signature(REQUEST_SIGNATURE_PROXY_ECDSA_PATH, &request).await } pub async fn request_proxy_signature_bls( &mut self, request: SignProxyRequest, - ) -> Result { - self.request_signature(&request.into()).await + ) -> Result { + self.request_signature(REQUEST_SIGNATURE_PROXY_BLS_PATH, &request).await } async fn generate_proxy_key( diff --git a/crates/common/src/commit/constants.rs b/crates/common/src/commit/constants.rs index ea9cd9bb..f2d5e94c 100644 --- a/crates/common/src/commit/constants.rs +++ b/crates/common/src/commit/constants.rs @@ -1,5 +1,12 @@ +use const_format::concatcp; + pub const GET_PUBKEYS_PATH: &str = "/signer/v1/get_pubkeys"; -pub const REQUEST_SIGNATURE_PATH: &str = "/signer/v1/request_signature"; +pub const REQUEST_SIGNATURE_BASE_PATH: &str = "/signer/v1/request_signature"; +pub const REQUEST_SIGNATURE_BLS_PATH: &str = concatcp!(REQUEST_SIGNATURE_BASE_PATH, "/bls"); +pub const REQUEST_SIGNATURE_PROXY_BLS_PATH: &str = + concatcp!(REQUEST_SIGNATURE_BASE_PATH, "/proxy-bls"); +pub const REQUEST_SIGNATURE_PROXY_ECDSA_PATH: &str = + concatcp!(REQUEST_SIGNATURE_BASE_PATH, "/proxy-ecdsa"); pub const GENERATE_PROXY_KEY_PATH: &str = "/signer/v1/generate_proxy_key"; pub const STATUS_PATH: &str = "/status"; pub const RELOAD_PATH: &str = "/reload"; diff --git a/crates/common/src/commit/mod.rs b/crates/common/src/commit/mod.rs index 205785ff..193db630 100644 --- a/crates/common/src/commit/mod.rs +++ b/crates/common/src/commit/mod.rs @@ -2,3 +2,4 @@ pub mod client; pub mod constants; pub mod error; pub mod request; +pub mod response; diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 5bc3a14b..5e101092 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -9,7 +9,6 @@ use alloy::{ primitives::{aliases::B32, Address, B256}, rpc::types::beacon::BlsSignature, }; -use derive_more::derive::From; use serde::{Deserialize, Deserializer, Serialize}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; @@ -74,40 +73,6 @@ impl fmt::Display for SignedProxyDelegation { } } -// TODO(David): This struct shouldn't be visible to module authors -#[derive(Debug, Clone, Serialize, Deserialize, From)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum SignRequest { - Consensus(SignConsensusRequest), - ProxyBls(SignProxyRequest), - ProxyEcdsa(SignProxyRequest
), -} - -impl Display for SignRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SignRequest::Consensus(req) => write!( - f, - "Consensus(pubkey: {}, object_root: {})", - req.pubkey, - hex::encode_prefixed(req.object_root) - ), - SignRequest::ProxyBls(req) => write!( - f, - "BLS(proxy: {}, object_root: {})", - req.proxy, - hex::encode_prefixed(req.object_root) - ), - SignRequest::ProxyEcdsa(req) => write!( - f, - "ECDSA(proxy: {}, object_root: {})", - req.proxy, - hex::encode_prefixed(req.object_root) - ), - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignConsensusRequest { pub pubkey: BlsPublicKey, @@ -132,6 +97,17 @@ impl SignConsensusRequest { } } +impl Display for SignConsensusRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Consensus(pubkey: {}, object_root: {})", + self.pubkey, + hex::encode_prefixed(self.object_root) + ) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignProxyRequest { pub proxy: T, @@ -156,6 +132,28 @@ impl SignProxyRequest { } } +impl Display for SignProxyRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "BLS(proxy: {}, object_root: {})", + self.proxy, + hex::encode_prefixed(self.object_root) + ) + } +} + +impl Display for SignProxyRequest
{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "ECDSA(proxy: {}, object_root: {})", + self.proxy, + hex::encode_prefixed(self.object_root) + ) + } +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum EncryptionScheme { #[serde(rename = "bls")] @@ -249,36 +247,6 @@ mod tests { use super::*; use crate::signer::EcdsaSignature; - #[test] - fn test_decode_request_signature() { - let data = r#"{ - "type": "consensus", - "pubkey": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", - "object_root": "0x5c89913beafa0472168e0ec05e349b4ceb9985d25ab9fa8de53a60208c85b3a5" - }"#; - - let request: SignRequest = serde_json::from_str(data).unwrap(); - assert!(matches!(request, SignRequest::Consensus(..))); - - let data = r#"{ - "type": "proxy_bls", - "proxy": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", - "object_root": "0x5c89913beafa0472168e0ec05e349b4ceb9985d25ab9fa8de53a60208c85b3a5" - }"#; - - let request: SignRequest = serde_json::from_str(data).unwrap(); - assert!(matches!(request, SignRequest::ProxyBls(..))); - - let data = r#"{ - "type": "proxy_ecdsa", - "proxy": "0x4ca9939a8311a7cab3dde201b70157285fa81a9d", - "object_root": "0x5c89913beafa0472168e0ec05e349b4ceb9985d25ab9fa8de53a60208c85b3a5" - }"#; - - let request: SignRequest = serde_json::from_str(data).unwrap(); - assert!(matches!(request, SignRequest::ProxyEcdsa(..))); - } - #[test] fn test_decode_response_signature() { let data = r#""0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989""#; diff --git a/crates/common/src/commit/response.rs b/crates/common/src/commit/response.rs new file mode 100644 index 00000000..543fb6fc --- /dev/null +++ b/crates/common/src/commit/response.rs @@ -0,0 +1,45 @@ +use alloy::{ + primitives::{Address, B256}, + rpc::types::beacon::BlsSignature, +}; +use serde::{Deserialize, Serialize}; + +use crate::signer::{BlsPublicKey, EcdsaSignature}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct BlsSignResponse { + pub pubkey: BlsPublicKey, + pub object_root: B256, + pub module_signing_id: B256, + pub signature: BlsSignature, +} + +impl BlsSignResponse { + pub fn new( + pubkey: BlsPublicKey, + object_root: B256, + module_signing_id: B256, + signature: BlsSignature, + ) -> Self { + Self { pubkey, object_root, module_signing_id, signature } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct EcdsaSignResponse { + pub address: Address, + pub object_root: B256, + pub module_signing_id: B256, + pub signature: EcdsaSignature, +} + +impl EcdsaSignResponse { + pub fn new( + address: Address, + object_root: B256, + module_signing_id: B256, + signature: EcdsaSignature, + ) -> Self { + Self { address, object_root, module_signing_id, signature } + } +} diff --git a/crates/signer/src/constants.rs b/crates/signer/src/constants.rs index 268cd2e2..e5884d27 100644 --- a/crates/signer/src/constants.rs +++ b/crates/signer/src/constants.rs @@ -1,3 +1,5 @@ pub const GET_PUBKEYS_ENDPOINT_TAG: &str = "get_pubkeys"; pub const GENERATE_PROXY_KEY_ENDPOINT_TAG: &str = "generate_proxy_key"; -pub const REQUEST_SIGNATURE_ENDPOINT_TAG: &str = "request_signature"; +pub const REQUEST_SIGNATURE_BLS_ENDPOINT_TAG: &str = "request_signature_bls"; +pub const REQUEST_SIGNATURE_PROXY_BLS_ENDPOINT_TAG: &str = "request_signature_proxy_bls"; +pub const REQUEST_SIGNATURE_PROXY_ECDSA_ENDPOINT_TAG: &str = "request_signature_proxy_ecdsa"; diff --git a/crates/signer/src/metrics.rs b/crates/signer/src/metrics.rs index 85fa02b2..f7711d69 100644 --- a/crates/signer/src/metrics.rs +++ b/crates/signer/src/metrics.rs @@ -2,13 +2,15 @@ use axum::http::Uri; use cb_common::commit::constants::{ - GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH, + GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_BLS_PATH, + REQUEST_SIGNATURE_PROXY_BLS_PATH, REQUEST_SIGNATURE_PROXY_ECDSA_PATH, }; use lazy_static::lazy_static; use prometheus::{register_int_counter_vec_with_registry, IntCounterVec, Registry}; use crate::constants::{ - GENERATE_PROXY_KEY_ENDPOINT_TAG, GET_PUBKEYS_ENDPOINT_TAG, REQUEST_SIGNATURE_ENDPOINT_TAG, + GENERATE_PROXY_KEY_ENDPOINT_TAG, GET_PUBKEYS_ENDPOINT_TAG, REQUEST_SIGNATURE_BLS_ENDPOINT_TAG, + REQUEST_SIGNATURE_PROXY_BLS_ENDPOINT_TAG, REQUEST_SIGNATURE_PROXY_ECDSA_ENDPOINT_TAG, }; lazy_static! { @@ -28,7 +30,9 @@ pub fn uri_to_tag(uri: &Uri) -> &str { match uri.path() { GET_PUBKEYS_PATH => GET_PUBKEYS_ENDPOINT_TAG, GENERATE_PROXY_KEY_PATH => GENERATE_PROXY_KEY_ENDPOINT_TAG, - REQUEST_SIGNATURE_PATH => REQUEST_SIGNATURE_ENDPOINT_TAG, + REQUEST_SIGNATURE_BLS_PATH => REQUEST_SIGNATURE_BLS_ENDPOINT_TAG, + REQUEST_SIGNATURE_PROXY_BLS_PATH => REQUEST_SIGNATURE_PROXY_BLS_ENDPOINT_TAG, + REQUEST_SIGNATURE_PROXY_ECDSA_PATH => REQUEST_SIGNATURE_PROXY_ECDSA_ENDPOINT_TAG, _ => "unknown endpoint", } } diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 4ecf5e75..f69eb650 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -5,6 +5,10 @@ use std::{ time::{Duration, Instant}, }; +use alloy::{ + primitives::{Address, B256}, + rpc::types::beacon::BlsPublicKey, +}; use axum::{ extract::{ConnectInfo, Request, State}, http::StatusCode, @@ -17,13 +21,15 @@ use axum_extra::TypedHeader; use cb_common::{ commit::{ constants::{ - GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, RELOAD_PATH, REQUEST_SIGNATURE_PATH, + GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, RELOAD_PATH, REQUEST_SIGNATURE_BLS_PATH, + REQUEST_SIGNATURE_PROXY_BLS_PATH, REQUEST_SIGNATURE_PROXY_ECDSA_PATH, REVOKE_MODULE_PATH, STATUS_PATH, }, request::{ EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, ReloadRequest, - RevokeModuleRequest, SignConsensusRequest, SignProxyRequest, SignRequest, + RevokeModuleRequest, SignConsensusRequest, SignProxyRequest, }, + response::{BlsSignResponse, EcdsaSignResponse}, }, config::{ModuleSigningConfig, StartSignerConfig}, constants::{COMMIT_BOOST_COMMIT, COMMIT_BOOST_VERSION}, @@ -119,7 +125,9 @@ impl SigningService { SigningService::init_metrics(config.chain)?; let signer_app = axum::Router::new() - .route(REQUEST_SIGNATURE_PATH, post(handle_request_signature)) + .route(REQUEST_SIGNATURE_BLS_PATH, post(handle_request_signature_bls)) + .route(REQUEST_SIGNATURE_PROXY_BLS_PATH, post(handle_request_signature_proxy_bls)) + .route(REQUEST_SIGNATURE_PROXY_ECDSA_PATH, post(handle_request_signature_proxy_ecdsa)) .route(GET_PUBKEYS_PATH, get(handle_get_pubkeys)) .route(GENERATE_PROXY_KEY_PATH, post(handle_generate_proxy)) .route_layer(middleware::from_fn_with_state(state.clone(), jwt_auth)) @@ -292,73 +300,136 @@ async fn handle_get_pubkeys( Ok((StatusCode::OK, Json(res)).into_response()) } -/// Implements request_signature from the Signer API -async fn handle_request_signature( +/// Validates a BLS key signature request and returns the signature +async fn handle_request_signature_bls( Extension(module_id): Extension, State(state): State, - Json(request): Json, + Json(request): Json, ) -> Result { let req_id = Uuid::new_v4(); + debug!(event = "bls_request_signature", ?module_id, %request, ?req_id, "New request"); + handle_request_signature_bls_impl( + &module_id, + &state, + &req_id, + false, + &request.pubkey, + &request.object_root, + ) + .await +} - let Some(signing_id) = state.jwts.read().get(&module_id).map(|m| m.signing_id) else { - error!(event = "request_signature", ?module_id, ?req_id, "Module signing ID not found"); +/// Validates a BLS key signature request using a proxy key and returns the +/// signature +async fn handle_request_signature_proxy_bls( + Extension(module_id): Extension, + State(state): State, + Json(request): Json>, +) -> Result { + let req_id = Uuid::new_v4(); + debug!(event = "proxy_bls_request_signature", ?module_id, %request, ?req_id, "New request"); + handle_request_signature_bls_impl( + &module_id, + &state, + &req_id, + true, + &request.proxy, + &request.object_root, + ) + .await +} + +/// Implementation for handling a BLS signature request +async fn handle_request_signature_bls_impl( + module_id: &ModuleId, + state: &SigningState, + req_id: &Uuid, + is_proxy: bool, + signing_pubkey: &BlsPublicKey, + object_root: &B256, +) -> Result { + let Some(signing_id) = state.jwts.read().get(module_id).map(|m| m.signing_id) else { + error!( + event = "proxy_bls_request_signature", + ?module_id, + ?req_id, + "Module signing ID not found" + ); return Err(SignerModuleError::RequestError("Module signing ID not found".to_string())); }; - debug!(event = "request_signature", ?module_id, %request, ?req_id, "New request"); - - let manager = state.manager.read().await; - let res = match &*manager { - SigningManager::Local(local_manager) => match request { - SignRequest::Consensus(SignConsensusRequest { ref object_root, ref pubkey }) => { - local_manager - .sign_consensus(pubkey, object_root, Some(&signing_id)) - .await - .map(|sig| Json(sig).into_response()) - } - SignRequest::ProxyBls(SignProxyRequest { ref object_root, proxy: ref bls_key }) => { - local_manager - .sign_proxy_bls(bls_key, object_root, Some(&signing_id)) - .await - .map(|sig| Json(sig).into_response()) - } - SignRequest::ProxyEcdsa(SignProxyRequest { ref object_root, proxy: ref ecdsa_key }) => { - local_manager - .sign_proxy_ecdsa(ecdsa_key, object_root, Some(&signing_id)) - .await - .map(|sig| Json(sig).into_response()) + match &*state.manager.read().await { + SigningManager::Local(local_manager) => { + if is_proxy { + local_manager.sign_proxy_bls(signing_pubkey, object_root, Some(&signing_id)).await + } else { + local_manager.sign_consensus(signing_pubkey, object_root, Some(&signing_id)).await } - }, - SigningManager::Dirk(dirk_manager) => match request { - SignRequest::Consensus(SignConsensusRequest { ref object_root, ref pubkey }) => { + } + SigningManager::Dirk(dirk_manager) => { + if is_proxy { dirk_manager - .request_consensus_signature(pubkey, object_root, Some(&signing_id)) + .request_proxy_signature(signing_pubkey, object_root, Some(&signing_id)) .await - .map(|sig| Json(sig).into_response()) - } - SignRequest::ProxyBls(SignProxyRequest { ref object_root, proxy: ref bls_key }) => { + } else { dirk_manager - .request_proxy_signature(bls_key, object_root, Some(&signing_id)) + .request_consensus_signature(signing_pubkey, object_root, Some(&signing_id)) .await - .map(|sig| Json(sig).into_response()) - } - SignRequest::ProxyEcdsa(_) => { - error!( - event = "request_signature", - ?module_id, - ?req_id, - "ECDSA proxy sign request not supported with Dirk" - ); - Err(SignerModuleError::DirkNotSupported) } - }, + } + } + .map(|sig| { + Json(BlsSignResponse::new(*signing_pubkey, *object_root, signing_id, sig)).into_response() + }) + .map_err(|err| { + error!(event = "request_signature", ?module_id, ?req_id, "{err}"); + err + }) +} + +/// Validates an ECDSA key signature request using a proxy key and returns the +/// signature +async fn handle_request_signature_proxy_ecdsa( + Extension(module_id): Extension, + State(state): State, + Json(request): Json>, +) -> Result { + let req_id = Uuid::new_v4(); + let Some(signing_id) = state.jwts.read().get(&module_id).map(|m| m.signing_id) else { + error!( + event = "proxy_ecdsa_request_signature", + ?module_id, + ?req_id, + "Module signing ID not found" + ); + return Err(SignerModuleError::RequestError("Module signing ID not found".to_string())); }; + debug!(event = "proxy_ecdsa_request_signature", ?module_id, %request, ?req_id, "New request"); - if let Err(err) = &res { - error!(event = "request_signature", ?module_id, ?req_id, "{err}"); + match &*state.manager.read().await { + SigningManager::Local(local_manager) => { + local_manager + .sign_proxy_ecdsa(&request.proxy, &request.object_root, Some(&signing_id)) + .await + } + SigningManager::Dirk(_) => { + error!( + event = "request_signature", + ?module_id, + ?req_id, + "ECDSA proxy sign request not supported with Dirk" + ); + Err(SignerModuleError::DirkNotSupported) + } } - - res + .map(|sig| { + Json(EcdsaSignResponse::new(request.proxy, request.object_root, signing_id, sig)) + .into_response() + }) + .map_err(|err| { + error!(event = "request_signature", ?module_id, ?req_id, "{err}"); + err + }) } async fn handle_generate_proxy( diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index c73b2191..8360d07d 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -92,13 +92,13 @@ impl DaCommitService { // Request a signature directly from a BLS key let request = SignConsensusRequest::builder(pubkey).with_msg(&datagram); - let signature = self.config.signer_client.request_consensus_signature(request).await?; - info!("Proposer commitment (consensus): {}", signature); + let response = self.config.signer_client.request_consensus_signature(request).await?; + info!("Proposer commitment (consensus): {}", response.signature); match verify_proposer_commitment_signature_bls( self.config.chain, &pubkey, &datagram, - &signature, + &response.signature, &DA_COMMIT_SIGNING_ID, ) { Ok(_) => info!("Signature verified successfully"), @@ -107,14 +107,14 @@ impl DaCommitService { // Request a signature from a proxy BLS key let proxy_request_bls = SignProxyRequest::builder(proxy_bls).with_msg(&datagram); - let proxy_signature_bls = + let proxy_response_bls = self.config.signer_client.request_proxy_signature_bls(proxy_request_bls).await?; - info!("Proposer commitment (proxy BLS): {}", proxy_signature_bls); + info!("Proposer commitment (proxy BLS): {}", proxy_response_bls.signature); match verify_proposer_commitment_signature_bls( self.config.chain, &proxy_bls, &datagram, - &proxy_signature_bls, + &proxy_response_bls.signature, &DA_COMMIT_SIGNING_ID, ) { Ok(_) => info!("Signature verified successfully"), @@ -124,17 +124,17 @@ impl DaCommitService { // If ECDSA keys are enabled, request a signature from a proxy ECDSA key if let Some(proxy_ecdsa) = proxy_ecdsa { let proxy_request_ecdsa = SignProxyRequest::builder(proxy_ecdsa).with_msg(&datagram); - let proxy_signature_ecdsa = self + let proxy_response_ecdsa = self .config .signer_client .request_proxy_signature_ecdsa(proxy_request_ecdsa) .await?; - info!("Proposer commitment (proxy ECDSA): {}", proxy_signature_ecdsa); + info!("Proposer commitment (proxy ECDSA): {}", proxy_response_ecdsa.signature); match verify_proposer_commitment_signature_ecdsa( self.config.chain, &proxy_ecdsa, &datagram, - &proxy_signature_ecdsa, + &proxy_response_ecdsa.signature, &DA_COMMIT_SIGNING_ID, ) { Ok(_) => info!("Signature verified successfully"), diff --git a/provisioning/grafana/signer_public_dashboard.json b/provisioning/grafana/signer_public_dashboard.json index 327d48bb..4b904b1a 100644 --- a/provisioning/grafana/signer_public_dashboard.json +++ b/provisioning/grafana/signer_public_dashboard.json @@ -539,13 +539,19 @@ "list": [ { "current": { - "text": "$__all", + "selected": true, + "text": "All", "value": "$__all" }, "description": "SignerAPI endpoint", "includeAll": true, "name": "endpoint", "options": [ + { + "selected": true, + "text": "All", + "value": "$__all" + }, { "selected": false, "text": "get_pubkeys", @@ -558,11 +564,21 @@ }, { "selected": false, - "text": "request_signature", - "value": "request_signature" + "text": "request_signature_bls", + "value": "request_signature_bls" + }, + { + "selected": false, + "text": "request_signature_proxy_bls", + "value": "request_signature_proxy_bls" + }, + { + "selected": false, + "text": "request_signature_proxy_ecdsa", + "value": "request_signature_proxy_ecdsa" } ], - "query": "get_pubkeys, generate_proxy_key, request_signature", + "query": "get_pubkeys, generate_proxy_key, request_signature_bls, request_signature_proxy_bls, request_signature_proxy_ecdsa", "type": "custom" } ] diff --git a/tests/tests/signer_request_sig.rs b/tests/tests/signer_request_sig.rs index 868a1f71..f5c4e6c1 100644 --- a/tests/tests/signer_request_sig.rs +++ b/tests/tests/signer_request_sig.rs @@ -1,15 +1,17 @@ use std::collections::HashMap; use alloy::{ - hex, - primitives::{b256, FixedBytes}, + hex::FromHex, + primitives::{b256, hex, FixedBytes}, + rpc::types::beacon::BlsPublicKey, }; use cb_common::{ commit::{ - constants::REQUEST_SIGNATURE_PATH, - request::{SignConsensusRequest, SignRequest}, + constants::REQUEST_SIGNATURE_BLS_PATH, request::SignConsensusRequest, + response::BlsSignResponse, }, config::{load_module_signing_configs, ModuleSigningConfig}, + signer::BlsSignature, types::ModuleId, utils::create_jwt, }; @@ -60,22 +62,23 @@ async fn test_signer_sign_request_good() -> Result<()> { // Send a signing request let object_root = b256!("0x0123456789012345678901234567890123456789012345678901234567890123"); - let request = - SignRequest::Consensus(SignConsensusRequest { pubkey: FixedBytes(PUBKEY_1), object_root }); + let request = SignConsensusRequest { pubkey: FixedBytes(PUBKEY_1), object_root }; let jwt = create_jwt(&module_id, &jwt_config.jwt_secret)?; let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, REQUEST_SIGNATURE_PATH); + let url = format!("http://{}{}", start_config.endpoint, REQUEST_SIGNATURE_BLS_PATH); let response = client.post(&url).json(&request).bearer_auth(&jwt).send().await?; // Verify the response is successful assert!(response.status() == StatusCode::OK); // Verify the signature is returned - let signature = response.text().await?; - assert!(!signature.is_empty(), "Signature should not be empty"); - - let expected_signature = "\"0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115\""; - assert_eq!(signature, expected_signature, "Signature does not match expected value"); + let sig_response = response.json::().await?; + let expected = BlsSignResponse::new( + BlsPublicKey::from(PUBKEY_1), + object_root, + mod_cfgs.get(&module_id).unwrap().signing_id, + BlsSignature::from_hex("0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115").unwrap()); + assert_eq!(sig_response, expected, "Signature response does not match expected value"); Ok(()) } @@ -92,22 +95,32 @@ async fn test_signer_sign_request_different_module() -> Result<()> { // Send a signing request let object_root = b256!("0x0123456789012345678901234567890123456789012345678901234567890123"); - let request = - SignRequest::Consensus(SignConsensusRequest { pubkey: FixedBytes(PUBKEY_1), object_root }); + let request = SignConsensusRequest { pubkey: FixedBytes(PUBKEY_1), object_root }; let jwt = create_jwt(&module_id, &jwt_config.jwt_secret)?; let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, REQUEST_SIGNATURE_PATH); + let url = format!("http://{}{}", start_config.endpoint, REQUEST_SIGNATURE_BLS_PATH); let response = client.post(&url).json(&request).bearer_auth(&jwt).send().await?; // Verify the response is successful assert!(response.status() == StatusCode::OK); // Verify the signature is returned - let signature = response.text().await?; - assert!(!signature.is_empty(), "Signature should not be empty"); - - let incorrect_signature = "\"0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115\""; - assert_ne!(signature, incorrect_signature, "Signature does not match expected value"); + let sig_response = response.json::().await?; + assert_eq!( + sig_response.pubkey, + BlsPublicKey::from(PUBKEY_1), + "Public key does not match expected value" + ); + assert_eq!(sig_response.object_root, object_root, "Object root does not match expected value"); + assert_eq!( + sig_response.module_signing_id, + mod_cfgs.get(&module_id).unwrap().signing_id, + "Module signing ID does not match expected value" + ); + assert_ne!( + sig_response.signature, BlsSignature::from_hex("0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115").unwrap(), + "Signature matches the reference signature, which should not happen" + ); Ok(()) }