diff --git a/Cargo.lock b/Cargo.lock index 1c8c6a9a..2c0220b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1502,7 +1502,6 @@ dependencies = [ "ethereum_ssz 0.8.3", "ethereum_ssz_derive", "eyre", - "k256", "pbkdf2 0.12.2", "rand 0.9.0", "reqwest", @@ -3197,7 +3196,6 @@ dependencies = [ "once_cell", "serdect", "sha2 0.10.8", - "signature", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e28c7540..27bdfb17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ alloy = { version = "0.12", features = [ "ssz", "getrandom", "providers", + "signer-local", ] } ssz_types = "0.10" ethereum_serde_utils = "0.7.0" @@ -78,7 +79,6 @@ blsful = "2.5" tree_hash = "0.9" tree_hash_derive = "0.9" eth2_keystore = { git = "https://github.com/sigp/lighthouse", rev = "8d058e4040b765a96aa4968f4167af7571292be2" } -k256 = "0.13" aes = "0.8" ctr = "0.9.2" cipher = "0.4" diff --git a/api/signer-api.yml b/api/signer-api.yml index 888b64fb..c876a3a2 100644 --- a/api/signer-api.yml +++ b/api/signer-api.yml @@ -16,7 +16,7 @@ paths: - BearerAuth: [] responses: "200": - description: "All public keys available to the module: consensus pubkeys (BLS) and proxy pubkeys (BLS and ECDSA)" + description: "All public keys available to the module: consensus pubkeys (BLS) and proxies (BLS pubkeys and ECDSA addresses)" content: application/json: schema: @@ -32,15 +32,15 @@ paths: description: Consensus validator pubkey $ref: "#/components/schemas/BlsPubkey" proxy_bls: - description: BLS proxy validator pubkeys + description: BLS proxy pubkeys type: array items: $ref: "#/components/schemas/BlsPubkey" proxy_ecdsa: - description: ECDSA proxy validator pubkeys + description: ECDSA proxy addresses type: array items: - $ref: "#/components/schemas/EcdsaPubkey" + $ref: "#/components/schemas/EcdsaAddress" "500": description: Internal error content: @@ -71,17 +71,23 @@ paths: application/json: schema: type: object - required: [type, pubkey, object_root] + required: [type, object_root] + oneOf: + - required: [pubkey] + - required: [proxy] properties: type: description: Type of the sign request type: string enum: [consensus, proxy_bls, proxy_ecdsa] pubkey: - description: Public key of the validator + description: Public key of the validator for consensus signatures + $ref: "#/components/schemas/BlsPubkey" + proxy: + description: BLS proxy pubkey or ECDSA address for proxy signatures oneOf: - $ref: "#/components/schemas/BlsPubkey" - - $ref: "#/components/schemas/EcdsaPubkey" + - $ref: "#/components/schemas/EcdsaAddress" object_root: description: The root of the object to be signed type: string @@ -97,12 +103,12 @@ paths: ProxyBls: value: type: "proxy_bls" - pubkey: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + proxy: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" ProxyEcdsa: value: type: "proxy_ecdsa" - pubkey: "0x023b2806b1b1dfa34dd90b01546906cef3e4c8e0fc0cba60480e9eb4d0a0828311" + proxy: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" responses: "200": @@ -119,7 +125,7 @@ paths: ProxyBls: value: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" ProxyEcdsa: - value: "0xe6a0c0c41a6d4af9794882c18c5280376cbfb7921453612dea02ed8f47b1208455f07931dc12c4b70c4e8ae216db0136000ec2cf17244189f012de356ac46cec" + value: "0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c" "404": description: Unknown value (pubkey, etc.) content: @@ -201,10 +207,10 @@ paths: allOf: - $ref: "#/components/schemas/BlsPubkey" proxy: - description: the generated proxy public key + description: the generated proxy identifier (BLS pubkey or ECDSA address) oneOf: - $ref: "#/components/schemas/BlsPubkey" - - $ref: "#/components/schemas/EcdsaPubkey" + - $ref: "#/components/schemas/EcdsaAddress" signature: description: The signature of the proxy delegation allOf: @@ -220,7 +226,7 @@ paths: value: message: delegator: "0xa9e9cff900de07e295a044789fd4bdb6785eb0651ad282f9e76d12afd87e75180bdd64caf2e315b815d7322bd31ab48a" - proxy: "0x023b2806b1b1dfa34dd90b01546906cef3e4c8e0fc0cba60480e9eb4d0a0828311" + proxy: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" signature: "0xb5b5b71d1701cc45086af3d3d86bf9d3c509442835e5b9f7734923edc9a6c538e743d70613cdef90b7e5b171fbbe6a29075b3f155e4bd66d81ff9dbc3b6d7fa677d169b2ceab727ffa079a31fe1fc0e478752e9da9566a9408e4db24ac6104db" "404": description: Unknown value (pubkey, etc.) @@ -281,11 +287,11 @@ components: format: hex pattern: "^0x[a-fA-F0-9]{96}$" example: "0xa9e9cff900de07e295a044789fd4bdb6785eb0651ad282f9e76d12afd87e75180bdd64caf2e315b815d7322bd31ab48a" - EcdsaPubkey: + EcdsaAddress: type: string format: hex - pattern: "^0x[a-fA-F0-9]{66}$" - example: "0x023b2806b1b1dfa34dd90b01546906cef3e4c8e0fc0cba60480e9eb4d0a0828311" + pattern: "^0x[a-fA-F0-9]{40}$" + example: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" BlsSignature: type: string format: hex @@ -294,5 +300,5 @@ components: EcdsaSignature: type: string format: hex - pattern: "^0x[a-fA-F0-9]{128}$" - example: "0xe6a0c0c41a6d4af9794882c18c5280376cbfb7921453612dea02ed8f47b1208455f07931dc12c4b70c4e8ae216db0136000ec2cf17244189f012de356ac46cec" + pattern: "^0x[a-fA-F0-9]{130}$" + example: "0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c" diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 0c401bed..126847b6 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -10,7 +10,7 @@ pub mod prelude { load_pbs_custom_config, LogsSettings, StartCommitModuleConfig, PBS_MODULE_NAME, }, pbs::{BuilderEvent, BuilderEventClient, OnBuilderApiEvent}, - signer::{BlsPublicKey, BlsSignature, EcdsaPublicKey, EcdsaSignature}, + signer::{BlsPublicKey, BlsSignature, EcdsaSignature}, types::Chain, utils::{initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, utcnow_us}, }; diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index cd21eba7..8ac207fd 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -38,7 +38,6 @@ blst.workspace = true tree_hash.workspace = true tree_hash_derive.workspace = true eth2_keystore.workspace = true -k256.workspace = true aes.workspace = true ctr.workspace = true cipher.workspace = true diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 32e8acea..8f1a4e3b 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use alloy::rpc::types::beacon::BlsSignature; +use alloy::{primitives::Address, rpc::types::beacon::BlsSignature}; use eyre::WrapErr; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use serde::Deserialize; @@ -10,12 +10,12 @@ use super::{ constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, error::SignerClientError, request::{ - EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, PublicKey, - SignConsensusRequest, SignProxyRequest, SignRequest, SignedProxyDelegation, + EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, ProxyId, SignConsensusRequest, + SignProxyRequest, SignRequest, SignedProxyDelegation, }, }; use crate::{ - signer::{BlsPublicKey, EcdsaPublicKey, EcdsaSignature}, + signer::{BlsPublicKey, EcdsaSignature}, DEFAULT_REQUEST_TIMEOUT, }; @@ -93,7 +93,7 @@ impl SignerClient { pub async fn request_proxy_signature_ecdsa( &self, - request: SignProxyRequest, + request: SignProxyRequest
, ) -> Result { self.request_signature(&request.into()).await } @@ -110,7 +110,7 @@ impl SignerClient { request: &GenerateProxyRequest, ) -> Result, SignerClientError> where - T: PublicKey + for<'de> Deserialize<'de>, + T: ProxyId + for<'de> Deserialize<'de>, { let url = self.url.join(GENERATE_PROXY_KEY_PATH)?; let res = self.client.post(url).json(&request).send().await?; @@ -144,7 +144,7 @@ impl SignerClient { pub async fn generate_proxy_key_ecdsa( &self, consensus_pubkey: BlsPublicKey, - ) -> Result, SignerClientError> { + ) -> Result, SignerClientError> { let request = GenerateProxyRequest::new(consensus_pubkey, EncryptionScheme::Ecdsa); let ecdsa_signed_proxy_delegation = self.generate_proxy_key(&request).await?; diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 36b57cd7..b8843234 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -1,57 +1,56 @@ use std::{ - fmt::{self, Debug, Display, LowerHex}, + fmt::{self, Debug, Display}, str::FromStr, }; -use alloy::{hex, rpc::types::beacon::BlsSignature}; +use alloy::{ + hex, + primitives::{Address, B256}, + rpc::types::beacon::BlsSignature, +}; use derive_more::derive::From; use serde::{Deserialize, Serialize}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - constants::COMMIT_BOOST_DOMAIN, - error::BlstErrorWrapper, - signature::verify_signed_message, - signer::{BlsPublicKey, EcdsaPublicKey}, - types::Chain, + constants::COMMIT_BOOST_DOMAIN, error::BlstErrorWrapper, signature::verify_signed_message, + signer::BlsPublicKey, types::Chain, }; -pub trait PublicKey: AsRef<[u8]> + Debug + Clone + Copy + TreeHash + Display + LowerHex {} +pub trait ProxyId: AsRef<[u8]> + Debug + Clone + Copy + TreeHash + Display {} -impl PublicKey for EcdsaPublicKey {} +impl ProxyId for Address {} -impl PublicKey for BlsPublicKey {} +impl ProxyId for BlsPublicKey {} // GENERIC PROXY DELEGATION #[derive(Debug, Clone, Copy, Serialize, Deserialize, TreeHash)] -pub struct ProxyDelegation { +pub struct ProxyDelegation { pub delegator: BlsPublicKey, pub proxy: T, } pub type ProxyDelegationBls = ProxyDelegation; -pub type ProxyDelegationEcdsa = ProxyDelegation; +pub type ProxyDelegationEcdsa = ProxyDelegation
; -impl fmt::Display for ProxyDelegation { +impl fmt::Display for ProxyDelegation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Delegator: {}\nProxy: {}", self.delegator, self.proxy) } } -// TODO: might need to adapt the SignedProxyDelegation so that it goes through -// web3 signer #[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct SignedProxyDelegation { +pub struct SignedProxyDelegation { pub message: ProxyDelegation, /// Signature of message with the delegator keypair pub signature: BlsSignature, } pub type SignedProxyDelegationBls = SignedProxyDelegation; -pub type SignedProxyDelegationEcdsa = SignedProxyDelegation; +pub type SignedProxyDelegationEcdsa = SignedProxyDelegation
; -impl SignedProxyDelegation { +impl SignedProxyDelegation { pub fn validate(&self, chain: Chain) -> Result<(), BlstErrorWrapper> { verify_signed_message( chain, @@ -63,7 +62,7 @@ impl SignedProxyDelegation { } } -impl fmt::Display for SignedProxyDelegation { +impl fmt::Display for SignedProxyDelegation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}\nSignature: {}", self.message, self.signature) } @@ -75,7 +74,7 @@ impl fmt::Display for SignedProxyDelegation { pub enum SignRequest { Consensus(SignConsensusRequest), ProxyBls(SignProxyRequest), - ProxyEcdsa(SignProxyRequest), + ProxyEcdsa(SignProxyRequest
), } impl Display for SignRequest { @@ -89,14 +88,14 @@ impl Display for SignRequest { ), SignRequest::ProxyBls(req) => write!( f, - "BLS(pubkey: {}, object_root: {})", - req.pubkey, + "BLS(proxy: {}, object_root: {})", + req.proxy, hex::encode_prefixed(req.object_root) ), SignRequest::ProxyEcdsa(req) => write!( f, - "ECDSA(pubkey: {}, object_root: {})", - req.pubkey, + "ECDSA(proxy: {}, object_root: {})", + req.proxy, hex::encode_prefixed(req.object_root) ), } @@ -106,21 +105,20 @@ impl Display for SignRequest { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignConsensusRequest { pub pubkey: BlsPublicKey, - #[serde(with = "alloy::hex::serde")] - pub object_root: [u8; 32], + pub object_root: B256, } impl SignConsensusRequest { - pub fn new(pubkey: BlsPublicKey, object_root: [u8; 32]) -> Self { + pub fn new(pubkey: BlsPublicKey, object_root: B256) -> Self { Self { pubkey, object_root } } pub fn builder(pubkey: BlsPublicKey) -> Self { - Self::new(pubkey, [0; 32]) + Self::new(pubkey, B256::ZERO) } - pub fn with_root(self, object_root: [u8; 32]) -> Self { - Self { object_root, ..self } + pub fn with_root>(self, object_root: R) -> Self { + Self { object_root: object_root.into(), ..self } } pub fn with_msg(self, msg: &impl TreeHash) -> Self { @@ -129,23 +127,22 @@ impl SignConsensusRequest { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SignProxyRequest { - pub pubkey: T, - #[serde(with = "alloy::hex::serde")] - pub object_root: [u8; 32], +pub struct SignProxyRequest { + pub proxy: T, + pub object_root: B256, } -impl SignProxyRequest { - pub fn new(pubkey: T, object_root: [u8; 32]) -> Self { - Self { pubkey, object_root } +impl SignProxyRequest { + pub fn new(proxy: T, object_root: B256) -> Self { + Self { proxy, object_root } } - pub fn builder(pubkey: T) -> Self { - Self::new(pubkey, [0; 32]) + pub fn builder(proxy: T) -> Self { + Self::new(proxy, B256::ZERO) } - pub fn with_root(self, object_root: [u8; 32]) -> Self { - Self { object_root, ..self } + pub fn with_root>(self, object_root: R) -> Self { + Self { object_root: object_root.into(), ..self } } pub fn with_msg(self, msg: &impl TreeHash) -> Self { @@ -206,7 +203,7 @@ pub struct GetPubkeysResponse { pub struct ConsensusProxyMap { pub consensus: BlsPublicKey, pub proxy_bls: Vec, - pub proxy_ecdsa: Vec, + pub proxy_ecdsa: Vec
, } impl ConsensusProxyMap { @@ -214,3 +211,106 @@ impl ConsensusProxyMap { Self { consensus, proxy_bls: vec![], proxy_ecdsa: vec![] } } } + +#[cfg(test)] +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""#; + let _: BlsSignature = serde_json::from_str(data).unwrap(); + + let data = r#""0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c""#; + let _: EcdsaSignature = serde_json::from_str(data).unwrap(); + } + + #[test] + fn test_decode_request_proxy() { + let data = r#"{ + "pubkey": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", + "scheme": "bls" + }"#; + + let request: GenerateProxyRequest = serde_json::from_str(data).unwrap(); + assert!(matches!(request, GenerateProxyRequest { scheme: EncryptionScheme::Bls, .. })); + + let data = r#"{ + "pubkey": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", + "scheme": "ecdsa" + }"#; + + let request: GenerateProxyRequest = serde_json::from_str(data).unwrap(); + assert!(matches!(request, GenerateProxyRequest { scheme: EncryptionScheme::Ecdsa, .. })); + } + + #[test] + fn test_decode_response_proxy() { + let data = r#"{ + "message": { + "delegator": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", + "proxy": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050" + }, + "signature": "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + }"#; + + let _: SignedProxyDelegationBls = serde_json::from_str(data).unwrap(); + + let data = r#"{ + "message": { + "delegator": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", + "proxy": "0x4ca9939a8311a7cab3dde201b70157285fa81a9d" + }, + "signature": "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + }"#; + + let _: SignedProxyDelegationEcdsa = serde_json::from_str(data).unwrap(); + } + + #[test] + fn test_decode_response_proxy_map() { + let data = r#"{ + "keys": [ + { + "consensus": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", + "proxy_bls": ["0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050"], + "proxy_ecdsa": ["0x4ca9939a8311a7cab3dde201b70157285fa81a9d"] + } + ] + }"#; + + let _: GetPubkeysResponse = serde_json::from_str(data).unwrap(); + } +} diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index 10b6cc83..9764aa51 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -1,7 +1,7 @@ pub use alloy::rpc::types::beacon::BlsSignature; use alloy::rpc::types::beacon::{constants::BLS_DST_SIG, BlsPublicKey as BlsPublicKeyInner}; use blst::BLST_ERROR; -use derive_more::derive::{Deref, Display, From, Into, LowerHex}; +use derive_more::derive::{Deref, Display, From, Into}; use serde::{Deserialize, Serialize}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; @@ -20,7 +20,7 @@ pub type BlsSecretKey = blst::min_pk::SecretKey; // and alloy's `BlsPublicKey` if we stick with it // std traits -#[derive(Debug, Clone, Copy, LowerHex, Display, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Default)] // serde, ssz, tree_hash #[derive(Serialize, Deserialize, TreeHash)] #[serde(transparent)] diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 07ce97d6..f84020a0 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -1,18 +1,10 @@ -use core::fmt; -use std::hash::Hash; - -use alloy::primitives::B256; -use derive_more::derive::{Deref, From, Into}; -use k256::{ - ecdsa::{Signature as EcdsaSignatureInner, VerifyingKey as EcdsaPublicKeyInner}, - elliptic_curve::generic_array::GenericArray, -}; -use serde::{Deserialize, Serialize}; -use serde_utils::hex; -use ssz_types::{ - typenum::{U33, U64}, - FixedVector, +use std::{ops::Deref, str::FromStr}; + +use alloy::{ + primitives::{Address, PrimitiveSignature, B256}, + signers::{local::PrivateKeySigner, SignerSync}, }; +use eyre::ensure; use tree_hash::TreeHash; use crate::{ @@ -21,130 +13,52 @@ use crate::{ types::Chain, }; -pub type EcdsaSecretKey = k256::ecdsa::SigningKey; - -type CompressedPublicKey = [u8; 33]; - -#[derive(Debug, Clone, Copy, From, Into, Serialize, Deserialize, PartialEq, Eq, Deref, Hash)] -#[serde(transparent)] -pub struct EcdsaPublicKey { - #[serde(with = "alloy::hex::serde")] - encoded: CompressedPublicKey, -} - -impl EcdsaPublicKey { - /// Size of the public key in bytes. We store the SEC1 encoded affine point - /// compressed, thus 33 bytes. - const SIZE: usize = 33; -} - -impl Default for EcdsaPublicKey { - fn default() -> Self { - Self { encoded: [0; Self::SIZE] } - } -} - -impl TreeHash for EcdsaPublicKey { - fn tree_hash_type() -> tree_hash::TreeHashType { - tree_hash::TreeHashType::Vector - } - - fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { - unreachable!("Vector should never be packed.") - } - - fn tree_hash_packing_factor() -> usize { - unreachable!("Vector should never be packed.") - } - - fn tree_hash_root(&self) -> tree_hash::Hash256 { - // NOTE: - // Unnecessary copying into a `FixedVector` just for its `tree_hash_root` - // implementation. If this becomes a performance issue, we could use - // `ssz_types::tree_hash::vec_tree_hash_root`, which is unfortunately - // not public. - let vec = self.encoded.to_vec(); - FixedVector::::from(vec).tree_hash_root() - } -} - -impl From for EcdsaPublicKey { - fn from(value: EcdsaPublicKeyInner) -> Self { - let encoded: [u8; Self::SIZE] = value.to_encoded_point(true).as_bytes().try_into().unwrap(); - - EcdsaPublicKey { encoded } - } -} - -impl AsRef<[u8]> for EcdsaPublicKey { - fn as_ref(&self) -> &[u8] { - &self.encoded - } -} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EcdsaSignature(PrimitiveSignature); -impl fmt::LowerHex for EcdsaPublicKey { +impl std::fmt::Display for EcdsaSignature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(self.as_ref()))?; - Ok(()) - } -} - -impl fmt::Display for EcdsaPublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:x}") - } -} - -#[derive(Clone, Deref, Serialize, Deserialize)] -#[serde(transparent)] -pub struct EcdsaSignature { - #[serde(with = "alloy::hex::serde")] - encoded: [u8; 64], -} - -impl Default for EcdsaSignature { - fn default() -> Self { - Self { encoded: [0; 64] } + write!(f, "{}", self.0) } } -impl From for EcdsaSignature { - fn from(value: EcdsaSignatureInner) -> Self { - Self { encoded: value.to_bytes().as_slice().try_into().unwrap() } +impl serde::Serialize for EcdsaSignature { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.to_string().serialize(serializer) } } -impl AsRef<[u8]> for EcdsaSignature { - fn as_ref(&self) -> &[u8] { - &self.encoded +impl<'de> serde::Deserialize<'de> for EcdsaSignature { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(Self(PrimitiveSignature::from_str(&s).map_err(serde::de::Error::custom)?)) } } -impl TryFrom<&[u8]> for EcdsaSignature { - type Error = k256::ecdsa::Error; - - fn try_from(value: &[u8]) -> std::result::Result { - Ok(EcdsaSignatureInner::from_slice(value)?.into()) +impl From for EcdsaSignature { + fn from(signature: PrimitiveSignature) -> Self { + Self(signature) } } -impl fmt::LowerHex for EcdsaSignature { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(self.as_ref()))?; - Ok(()) - } -} +impl Deref for EcdsaSignature { + type Target = PrimitiveSignature; -impl fmt::Display for EcdsaSignature { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:x}") + fn deref(&self) -> &Self::Target { + &self.0 } } // SIGNER #[derive(Clone)] pub enum EcdsaSigner { - Local(EcdsaSecretKey), + Local(PrivateKeySigner), } impl EcdsaSigner { @@ -154,13 +68,13 @@ impl EcdsaSigner { } pub fn new_from_bytes(bytes: &[u8]) -> eyre::Result { - let secret = EcdsaSecretKey::from_slice(bytes)?; + let secret = PrivateKeySigner::from_slice(bytes)?; Ok(Self::Local(secret)) } - pub fn pubkey(&self) -> EcdsaPublicKey { + pub fn address(&self) -> Address { match self { - EcdsaSigner::Local(secret) => EcdsaPublicKeyInner::from(secret).into(), + EcdsaSigner::Local(secret) => secret.address(), } } @@ -170,49 +84,61 @@ impl EcdsaSigner { } } - pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> EcdsaSignature { + pub async fn sign( + &self, + chain: Chain, + object_root: [u8; 32], + ) -> Result { match self { EcdsaSigner::Local(sk) => { let domain = compute_domain(chain, COMMIT_BOOST_DOMAIN); - let signing_root = compute_signing_root(object_root, domain); - k256::ecdsa::signature::Signer::::sign(sk, &signing_root) - .into() + let signing_root = compute_signing_root(object_root, domain).into(); + sk.sign_hash_sync(&signing_root).map(EcdsaSignature::from) } } } - pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> EcdsaSignature { + pub async fn sign_msg( + &self, + chain: Chain, + msg: &impl TreeHash, + ) -> Result { self.sign(chain, msg.tree_hash_root().0).await } } pub fn verify_ecdsa_signature( - pubkey: &EcdsaPublicKey, - msg: &[u8], + address: &Address, + msg: &[u8; 32], signature: &EcdsaSignature, -) -> Result<(), k256::ecdsa::Error> { - use k256::ecdsa::signature::Verifier; - let ecdsa_pubkey = EcdsaPublicKeyInner::from_sec1_bytes(&pubkey.encoded)?; - let ecdsa_sig = - EcdsaSignatureInner::from_bytes(GenericArray::::from_slice(signature.as_ref()))?; - ecdsa_pubkey.verify(msg, &ecdsa_sig) +) -> eyre::Result<()> { + let recovered = signature.recover_address_from_prehash(msg.into())?; + ensure!(recovered == *address, "invalid signature"); + Ok(()) } #[cfg(test)] mod test { + + use alloy::{hex, primitives::bytes}; + use super::*; #[tokio::test] async fn test_ecdsa_signer() { - let signer = EcdsaSigner::new_random(); - let pubkey = signer.pubkey(); - let object_root = B256::random().0; - let signature = signer.sign(Chain::Holesky, object_root).await; + let pk = bytes!("88bcd6672d95bcba0d52a3146494ed4d37675af4ed2206905eb161aa99a6c0d1"); + let signer = EcdsaSigner::new_from_bytes(&pk).unwrap(); + + let object_root = [1; 32]; + let signature = signer.sign(Chain::Holesky, object_root).await.unwrap(); let domain = compute_domain(Chain::Holesky, COMMIT_BOOST_DOMAIN); let msg = compute_signing_root(object_root, domain); - let verified = verify_ecdsa_signature(&pubkey, &msg, &signature); + assert_eq!(msg, hex!("219ca7a673b2cbbf67bec6c9f60f78bd051336d57b68d1540190f30667e86725")); + + let address = signer.address(); + let verified = verify_ecdsa_signature(&address, &msg, &signature); assert!(verified.is_ok()); } } diff --git a/crates/common/src/signer/store.rs b/crates/common/src/signer/store.rs index 19cfa6d2..822c0369 100644 --- a/crates/common/src/signer/store.rs +++ b/crates/common/src/signer/store.rs @@ -8,7 +8,7 @@ use std::{ use alloy::{ hex, - primitives::{Bytes, FixedBytes}, + primitives::{Address, Bytes, FixedBytes}, rpc::types::beacon::constants::BLS_SIGNATURE_BYTES_LEN, }; use eth2_keystore::{ @@ -26,17 +26,16 @@ use tracing::{trace, warn}; use super::{load_bls_signer, load_ecdsa_signer}; use crate::{ - commit::request::{EncryptionScheme, ProxyDelegation, PublicKey, SignedProxyDelegation}, + commit::request::{EncryptionScheme, ProxyDelegation, ProxyId, SignedProxyDelegation}, config::{load_env_var, PROXY_DIR_ENV, PROXY_DIR_KEYS_ENV, PROXY_DIR_SECRETS_ENV}, signer::{ - BlsProxySigner, BlsPublicKey, BlsSigner, EcdsaProxySigner, EcdsaPublicKey, EcdsaSigner, - ProxySigners, + BlsProxySigner, BlsPublicKey, BlsSigner, EcdsaProxySigner, EcdsaSigner, ProxySigners, }, types::ModuleId, }; #[derive(Debug, Serialize, Deserialize)] -struct KeyAndDelegation { +struct KeyAndDelegation { secret: Bytes, delegation: SignedProxyDelegation, } @@ -126,7 +125,7 @@ impl ProxyStore { let file_path = proxy_dir .join(module_id.to_string()) .join("ecdsa") - .join(proxy.signer.pubkey().to_string()); + .join(proxy.signer.address().to_string()); let secret = Bytes::from(proxy.signer.secret()); let to_store = KeyAndDelegation { secret, delegation: proxy.delegation }; let content = serde_json::to_vec(&to_store)?; @@ -186,14 +185,14 @@ impl ProxyStore { ) -> eyre::Result<( ProxySigners, HashMap>, - HashMap>, + HashMap>, )> { match self { ProxyStore::File { proxy_dir } => { // HashMaps to store module_id -> content mappings let mut proxy_signers = ProxySigners::default(); let mut bls_map: HashMap> = HashMap::new(); - let mut ecdsa_map: HashMap> = HashMap::new(); + let mut ecdsa_map: HashMap> = HashMap::new(); // Iterate over the entries in the base directory for entry in std::fs::read_dir(proxy_dir)? { @@ -243,12 +242,12 @@ impl ProxyStore { if path.is_file() { let file_content = read_to_string(&path)?; - let key_and_delegation: KeyAndDelegation = + let key_and_delegation: KeyAndDelegation
= serde_json::from_str(&file_content)?; let signer = EcdsaSigner::new_from_bytes( &key_and_delegation.secret, )?; - let pubkey = signer.pubkey(); + let pubkey = signer.address(); let proxy_signer = EcdsaProxySigner { signer, delegation: key_and_delegation.delegation, @@ -271,7 +270,7 @@ impl ProxyStore { ProxyStore::ERC2335 { keys_path, secrets_path } => { let mut proxy_signers = ProxySigners::default(); let mut bls_map: HashMap> = HashMap::new(); - let mut ecdsa_map: HashMap> = HashMap::new(); + let mut ecdsa_map: HashMap> = HashMap::new(); for entry in std::fs::read_dir(keys_path)? { let entry = entry?; @@ -374,7 +373,7 @@ impl ProxyStore { let signer = load_ecdsa_signer( path, secrets_path - .join(format!("{consensus_pubkey:#x}")) + .join(consensus_pubkey.to_string()) .join(&module_id) .join("ecdsa") .join(name), @@ -396,17 +395,17 @@ impl ProxyStore { delegation: SignedProxyDelegation { message: ProxyDelegation { delegator: consensus_pubkey, - proxy: signer.pubkey(), + proxy: signer.address(), }, signature: delegation_signature, }, }; - proxy_signers.ecdsa_signers.insert(signer.pubkey(), proxy_signer); + proxy_signers.ecdsa_signers.insert(signer.address(), proxy_signer); ecdsa_map .entry(ModuleId(module_id.clone())) .or_default() - .push(signer.pubkey()); + .push(signer.address()); } } } @@ -417,7 +416,7 @@ impl ProxyStore { } } -fn store_erc2335_key( +fn store_erc2335_key( module_id: &ModuleId, delegation: SignedProxyDelegation, secret: Vec, @@ -477,7 +476,7 @@ fn store_erc2335_key( }, uuid: Uuid::new_v4(), path: None, - pubkey: format!("{:x}", delegation.message.proxy), + pubkey: alloy::hex::encode(delegation.message.proxy), version: eth2_keystore::json_keystore::Version::V4, description: Some(delegation.message.proxy.to_string()), name: None, diff --git a/crates/common/src/signer/types.rs b/crates/common/src/signer/types.rs index a5888fdb..da36af5d 100644 --- a/crates/common/src/signer/types.rs +++ b/crates/common/src/signer/types.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use alloy::primitives::Bytes; +use alloy::primitives::{Address, Bytes}; use base64::{prelude::BASE64_STANDARD, Engine}; use derive_more::derive::Deref; use serde::{ @@ -8,7 +8,7 @@ use serde::{ Deserialize, Deserializer, }; -use super::{BlsPublicKey, EcdsaPublicKey, EcdsaSigner}; +use super::{BlsPublicKey, EcdsaSigner}; use crate::{ commit::request::{SignedProxyDelegationBls, SignedProxyDelegationEcdsa}, signer::BlsSigner, @@ -37,7 +37,7 @@ pub struct EcdsaProxySigner { #[derive(Default, Clone)] pub struct ProxySigners { pub bls_signers: HashMap, - pub ecdsa_signers: HashMap, + pub ecdsa_signers: HashMap, } // Prysm keystore actually has a more complex structure, but we only need diff --git a/crates/signer/src/error.rs b/crates/signer/src/error.rs index a60dd9fa..477e9e42 100644 --- a/crates/signer/src/error.rs +++ b/crates/signer/src/error.rs @@ -10,6 +10,9 @@ pub enum SignerModuleError { #[error("unauthorized")] Unauthorized, + #[error("signer error: {0}")] + SignerError(#[from] alloy::signers::Error), + #[error("unknown consensus signer: 0x{}", hex::encode(.0))] UnknownConsensusSigner(Vec), @@ -41,6 +44,7 @@ impl IntoResponse for SignerModuleError { SignerModuleError::Internal(_) => { (StatusCode::INTERNAL_SERVER_ERROR, "internal error".to_string()) } + SignerModuleError::SignerError(err) => (StatusCode::BAD_REQUEST, err.to_string()), } .into_response() } diff --git a/crates/signer/src/manager/local.rs b/crates/signer/src/manager/local.rs index 59edafa2..05f53812 100644 --- a/crates/signer/src/manager/local.rs +++ b/crates/signer/src/manager/local.rs @@ -1,14 +1,14 @@ use std::collections::HashMap; -use alloy::rpc::types::beacon::BlsSignature; +use alloy::{primitives::Address, rpc::types::beacon::BlsSignature}; use cb_common::{ commit::request::{ ConsensusProxyMap, ProxyDelegationBls, ProxyDelegationEcdsa, SignedProxyDelegationBls, SignedProxyDelegationEcdsa, }, signer::{ - BlsProxySigner, BlsPublicKey, BlsSigner, ConsensusSigner, EcdsaProxySigner, EcdsaPublicKey, - EcdsaSignature, EcdsaSigner, ProxySigners, ProxyStore, + BlsProxySigner, BlsPublicKey, BlsSigner, ConsensusSigner, EcdsaProxySigner, EcdsaSignature, + EcdsaSigner, ProxySigners, ProxyStore, }, types::{Chain, ModuleId}, }; @@ -26,7 +26,7 @@ pub struct LocalSigningManager { /// Used to retrieve the corresponding proxy signer from the signing /// manager. proxy_pubkeys_bls: HashMap>, - proxy_pubkeys_ecdsa: HashMap>, + proxy_addresses_ecdsa: HashMap>, } impl LocalSigningManager { @@ -37,14 +37,14 @@ impl LocalSigningManager { consensus_signers: Default::default(), proxy_signers: Default::default(), proxy_pubkeys_bls: Default::default(), - proxy_pubkeys_ecdsa: Default::default(), + proxy_addresses_ecdsa: Default::default(), }; if let Some(store) = &manager.proxy_store { let (proxies, bls, ecdsa) = store.load_proxies()?; manager.proxy_signers = proxies; manager.proxy_pubkeys_bls = bls; - manager.proxy_pubkeys_ecdsa = ecdsa; + manager.proxy_addresses_ecdsa = ecdsa; } Ok(manager) @@ -79,9 +79,9 @@ impl LocalSigningManager { store.store_proxy_ecdsa(&module_id, &proxy)?; } - let proxy_pubkey = proxy.pubkey(); - self.proxy_signers.ecdsa_signers.insert(proxy.pubkey(), proxy); - self.proxy_pubkeys_ecdsa.entry(module_id).or_default().push(proxy_pubkey); + let proxy_pubkey = proxy.address(); + self.proxy_signers.ecdsa_signers.insert(proxy.address(), proxy); + self.proxy_addresses_ecdsa.entry(module_id).or_default().push(proxy_pubkey); Ok(()) } @@ -111,7 +111,7 @@ impl LocalSigningManager { delegator: BlsPublicKey, ) -> Result { let signer = EcdsaSigner::new_random(); - let proxy_pubkey = signer.pubkey(); + let proxy_pubkey = signer.address(); let message = ProxyDelegationEcdsa { delegator, proxy: proxy_pubkey }; let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; @@ -156,15 +156,15 @@ impl LocalSigningManager { pub async fn sign_proxy_ecdsa( &self, - pubkey: &EcdsaPublicKey, + address: &Address, object_root: &[u8; 32], ) -> Result { let ecdsa_proxy = self .proxy_signers .ecdsa_signers - .get(pubkey) - .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; - let signature = ecdsa_proxy.sign(self.chain, *object_root).await; + .get(address) + .ok_or(SignerModuleError::UnknownProxySigner(address.to_vec()))?; + let signature = ecdsa_proxy.sign(self.chain, *object_root).await?; Ok(signature) } @@ -176,8 +176,8 @@ impl LocalSigningManager { &self.proxy_pubkeys_bls } - pub fn proxy_pubkeys_ecdsa(&self) -> &HashMap> { - &self.proxy_pubkeys_ecdsa + pub fn proxy_addresses_ecdsa(&self) -> &HashMap> { + &self.proxy_addresses_ecdsa } pub fn has_consensus(&self, pubkey: &BlsPublicKey) -> bool { @@ -193,11 +193,11 @@ impl LocalSigningManager { pub fn has_proxy_ecdsa_for_module( &self, - ecdsa_pk: &EcdsaPublicKey, + ecdsa_address: &Address, module_id: &ModuleId, ) -> bool { - match self.proxy_pubkeys_ecdsa.get(module_id) { - Some(keys) => keys.contains(ecdsa_pk), + match self.proxy_addresses_ecdsa.get(module_id) { + Some(keys) => keys.contains(ecdsa_address), None => false, } } @@ -215,13 +215,13 @@ impl LocalSigningManager { pub fn get_delegation_ecdsa( &self, - pubkey: &EcdsaPublicKey, + address: &Address, ) -> Result { self.proxy_signers .ecdsa_signers - .get(pubkey) + .get(address) .map(|x| x.delegation) - .ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec())) + .ok_or(SignerModuleError::UnknownProxySigner(address.to_vec())) } pub fn get_consensus_proxy_maps( @@ -230,7 +230,7 @@ impl LocalSigningManager { ) -> Result, SignerModuleError> { let consensus = self.consensus_pubkeys(); let proxy_bls = self.proxy_pubkeys_bls.get(module_id).cloned().unwrap_or_default(); - let proxy_ecdsa = self.proxy_pubkeys_ecdsa.get(module_id).cloned().unwrap_or_default(); + let proxy_ecdsa = self.proxy_addresses_ecdsa.get(module_id).cloned().unwrap_or_default(); let mut keys: Vec<_> = consensus.into_iter().map(ConsensusProxyMap::new).collect(); diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index a6a348ca..99f8e09e 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -123,7 +123,7 @@ async fn log_request(req: Request, next: Next) -> Result Result { - Ok((StatusCode::OK, "OK")) + Ok(StatusCode::OK) } /// Implements get_pubkeys from the Signer API @@ -164,13 +164,13 @@ async fn handle_request_signature( .sign_consensus(&pubkey, &object_root) .await .map(|sig| Json(sig).into_response()), - SignRequest::ProxyBls(SignProxyRequest { object_root, pubkey: bls_key }) => { + SignRequest::ProxyBls(SignProxyRequest { object_root, proxy: bls_key }) => { local_manager .sign_proxy_bls(&bls_key, &object_root) .await .map(|sig| Json(sig).into_response()) } - SignRequest::ProxyEcdsa(SignProxyRequest { object_root, pubkey: ecdsa_key }) => { + SignRequest::ProxyEcdsa(SignProxyRequest { object_root, proxy: ecdsa_key }) => { local_manager .sign_proxy_ecdsa(&ecdsa_key, &object_root) .await @@ -179,15 +179,13 @@ async fn handle_request_signature( }, SigningManager::Dirk(dirk_manager) => match request { SignRequest::Consensus(SignConsensusRequest { object_root, pubkey }) => dirk_manager - .request_consensus_signature(&pubkey, object_root) + .request_consensus_signature(&pubkey, *object_root) + .await + .map(|sig| Json(sig).into_response()), + SignRequest::ProxyBls(SignProxyRequest { object_root, proxy: bls_key }) => dirk_manager + .request_proxy_signature(&bls_key, *object_root) .await .map(|sig| Json(sig).into_response()), - SignRequest::ProxyBls(SignProxyRequest { object_root, pubkey: bls_key }) => { - dirk_manager - .request_proxy_signature(&bls_key, object_root) - .await - .map(|sig| Json(sig).into_response()) - } SignRequest::ProxyEcdsa(_) => { error!( event = "request_signature", @@ -272,7 +270,7 @@ async fn handle_reload( state.manager = Arc::new(RwLock::new(new_manager)); - Ok((StatusCode::OK, "OK")) + Ok(StatusCode::OK) } async fn start_manager(config: StartSignerConfig) -> eyre::Result { diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index ba59212b..b1c65338 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use alloy::primitives::Address; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; use lazy_static::lazy_static; @@ -78,7 +79,7 @@ impl DaCommitService { data: u64, pubkey: BlsPublicKey, proxy_bls: BlsPublicKey, - proxy_ecdsa: Option, + proxy_ecdsa: Option
, ) -> Result<()> { let datagram = Datagram { data };