From 02acf3896669a10dab43acf74daeee83d2f71d39 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Fri, 9 Aug 2024 17:16:34 +0300 Subject: [PATCH 01/37] refactor(signer)!: generalize for other signature schemes than BLS Co-authored-by: reo101 --- Cargo.toml | 2 +- crates/common/src/commit/client.rs | 7 +- crates/common/src/commit/request.rs | 25 ++- crates/common/src/signature.rs | 30 ++-- crates/common/src/signer.rs | 236 +++++++++++++++++++++++++--- crates/signer/Cargo.toml | 1 + crates/signer/src/error.rs | 11 +- crates/signer/src/manager.rs | 184 +++++++++++++++------- crates/signer/src/service.rs | 18 ++- examples/da_commit/src/main.rs | 8 +- tests/src/mock_relay.rs | 4 +- 11 files changed, 402 insertions(+), 124 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b3f1e6e..76455096 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,4 +80,4 @@ 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" \ No newline at end of file +derive_more = { version = "1.0.0", features = ["from", "into", "deref", "display"] } \ No newline at end of file diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index e7c65979..6233d1ca 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -10,12 +10,12 @@ use super::{ error::SignerClientError, request::{GenerateProxyRequest, SignRequest, SignedProxyDelegation}, }; -use crate::DEFAULT_REQUEST_TIMEOUT; +use crate::{signer::GenericPubkey, DEFAULT_REQUEST_TIMEOUT}; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GetPubkeysResponse { pub consensus: Vec, - pub proxy: Vec, + pub proxy: Vec, } /// Client used by commit modules to request signatures via the Signer API @@ -90,10 +90,9 @@ impl SignerClient { pub async fn generate_proxy_key( &self, - pubkey: BlsPublicKey, + request: &GenerateProxyRequest, ) -> Result { 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(); diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 06760a13..1193831a 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -4,14 +4,14 @@ use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; -use crate::{error::BlstErrorWrapper, signature::verify_signed_builder_message, types::Chain}; +use crate::{error::BlstErrorWrapper, signature::verify_signed_builder_message, signer::GenericPubkey, 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 { pub delegator: BlsPublicKey, - pub proxy: BlsPublicKey, + pub proxy: GenericPubkey, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -32,23 +32,25 @@ impl SignedProxyDelegation { } } +// TODO(David): Consider splitting `SignRequest` into two: ConsensusSignRequest and ProxySignRequest +// for better type safety (avoid the Vec generalisation) and avoid the is_proxy flag #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignRequest { - pub pubkey: BlsPublicKey, + pub pubkey: Vec, // TODO(David): Vec might not be the most memory inefficient, think about something on the stack pub is_proxy: bool, pub object_root: [u8; 32], } impl SignRequest { pub fn new( - pubkey: BlsPublicKey, + pubkey: Vec, is_proxy: bool, object_root: [u8; 32], ) -> SignRequest { Self { pubkey, is_proxy, object_root } } - pub fn builder(pubkey: BlsPublicKey) -> Self { + pub fn builder(pubkey: Vec) -> Self { Self::new(pubkey, false, [0; 32]) } @@ -67,11 +69,18 @@ impl SignRequest { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GenerateProxyRequest { - pub pubkey: BlsPublicKey, + pub consensus_pubkey: BlsPublicKey, + pub scheme: EncryptionScheme, } impl GenerateProxyRequest { - pub fn new(pubkey: BlsPublicKey) -> Self { - GenerateProxyRequest { pubkey } + pub fn new(consensus_pubkey: BlsPublicKey, scheme: EncryptionScheme) -> Self { + GenerateProxyRequest { consensus_pubkey, scheme } } } + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum EncryptionScheme { + Bls, + Ecdsa, +} diff --git a/crates/common/src/signature.rs b/crates/common/src/signature.rs index f5262fb3..6136627b 100644 --- a/crates/common/src/signature.rs +++ b/crates/common/src/signature.rs @@ -1,6 +1,6 @@ use alloy::rpc::types::beacon::{constants::BLS_DST_SIG, BlsPublicKey, BlsSignature}; use blst::{ - min_pk::{PublicKey, SecretKey, Signature}, + min_pk::{PublicKey, SecretKey as BlsSecretKey, Signature}, BLST_ERROR, }; use rand::RngCore; @@ -9,24 +9,22 @@ use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - constants::{APPLICATION_BUILDER_DOMAIN, GENESIS_VALIDATORS_ROOT}, - error::BlstErrorWrapper, - types::Chain, - utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}, + constants::{APPLICATION_BUILDER_DOMAIN, GENESIS_VALIDATORS_ROOT}, error::BlstErrorWrapper, signer::SecretKey, types::Chain, utils::{alloy_pubkey_to_blst, alloy_sig_to_blst} }; -pub fn random_secret() -> SecretKey { +pub fn random_secret() -> BlsSecretKey { let mut rng = rand::thread_rng(); let mut ikm = [0u8; 32]; rng.fill_bytes(&mut ikm); - match SecretKey::key_gen(&ikm, &[]) { + match BlsSecretKey::key_gen(&ikm, &[]) { Ok(key) => key, // Key material is always valid (32 `u8`s), so `key_gen` can't return Err. Err(_) => unreachable!(), } } +// TODO(David): Think about removing pub fn verify_signature( pubkey: &BlsPublicKey, msg: &[u8], @@ -43,7 +41,7 @@ pub fn verify_signature( } } -pub fn sign_message(secret_key: &SecretKey, msg: &[u8]) -> BlsSignature { +pub fn sign_message(secret_key: &BlsSecretKey, msg: &[u8]) -> BlsSignature { let signature = secret_key.sign(msg, BLS_DST_SIG, &[]).to_bytes(); BlsSignature::from_slice(&signature) } @@ -91,22 +89,24 @@ pub fn verify_signed_builder_message( verify_signature(pubkey, &signing_root, signature) } -pub fn sign_builder_message( +pub fn sign_builder_message( chain: Chain, - secret_key: &SecretKey, + secret_key: &T, msg: &impl TreeHash, -) -> BlsSignature { +) -> T::Signature { sign_builder_root(chain, secret_key, msg.tree_hash_root().0) } -pub fn sign_builder_root( +// TODO(David): This utility function seems unnecessary +pub fn sign_builder_root( chain: Chain, - secret_key: &SecretKey, + secret_key: &T, object_root: [u8; 32], -) -> BlsSignature { +) -> T::Signature { let domain = chain.builder_domain(); let signing_root = compute_signing_root(object_root, domain); - sign_message(secret_key, &signing_root) + secret_key.sign(&signing_root) + // sign_message(secret_key, &signing_root) } #[cfg(test)] diff --git a/crates/common/src/signer.rs b/crates/common/src/signer.rs index 15eb5b51..aafc34e3 100644 --- a/crates/common/src/signer.rs +++ b/crates/common/src/signer.rs @@ -1,48 +1,240 @@ -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; -use blst::min_pk::SecretKey; -use eyre::Result; +use std::{any::Any, error::Error}; + +use alloy::{primitives::FixedBytes, rpc::types::beacon::{BlsPublicKey, BlsSignature}}; +use blst::min_pk::{PublicKey, SecretKey as BlsSecretKey}; +use derive_more::derive::{Deref, From}; +use eyre::{Context, Result}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; use crate::{ - error::BlstErrorWrapper, - signature::{random_secret, sign_builder_message}, - types::Chain, - utils::blst_pubkey_to_alloy, + commit::request::SignedProxyDelegation, error::BlstErrorWrapper, signature::{random_secret, sign_builder_message, sign_builder_root, sign_message, verify_signature}, types::Chain, utils::blst_pubkey_to_alloy }; +pub trait SecretKey { + type PubKey: AsRef<[u8]> + Clone; + type Signature: AsRef<[u8]> + Clone; + type VerificationError: Error; + + fn new_random() -> Self; + fn new_from_bytes(bytes: &[u8]) -> Result where Self: Sized; + fn pubkey(&self) -> Self::PubKey; + fn sign(&self, msg: &[u8; 32]) -> Self::Signature; + fn sign_msg(&self, msg: &impl TreeHash) -> Self::Signature { + self.sign(&msg.tree_hash_root().0) + } + + fn verify_signature(pubkey: &Self::PubKey, msg: &[u8], signature: &Self::Signature) -> Result<(), Self::VerificationError>; + + // UTILITY METHODS + fn to_generic_pubkey(pubkey: Self::PubKey) -> GenericPubkey; + fn to_generic_proxy_signer(proxy_signer: ProxySigner) -> GenericProxySigner where Self: Sized; +} + +impl SecretKey for BlsSecretKey { + type PubKey = BlsPublicKey; + type Signature = BlsSignature; + type VerificationError = BlstErrorWrapper; + + fn new_random() -> Self { + random_secret() + } + + fn new_from_bytes(bytes: &[u8]) -> Result { + Ok(BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?) + } + + fn pubkey(&self) -> Self::PubKey { + blst_pubkey_to_alloy(&self.sk_to_pk()) + } + + fn sign(&self, msg: &[u8; 32]) -> Self::Signature { + sign_message(self, msg) + } + + fn verify_signature( + pubkey: &Self::PubKey, + msg: &[u8], + signature: &Self::Signature, + ) -> Result<(), Self::VerificationError> { + verify_signature(pubkey, msg, signature) + } + + // UTILITY METHODS + fn to_generic_pubkey(pubkey: Self::PubKey) -> GenericPubkey { + GenericPubkey::Bls(pubkey) + } + + fn to_generic_proxy_signer(proxy_signer: ProxySigner) -> GenericProxySigner where Self: Sized { + GenericProxySigner::Bls(proxy_signer) + } +} + #[derive(Clone)] -pub enum Signer { - Local(SecretKey), +pub enum Signer { + Local(T), } -impl Signer { +impl Signer { pub fn new_random() -> Self { - Signer::Local(random_secret()) + Signer::Local(T::new_random()) } pub fn new_from_bytes(bytes: &[u8]) -> Result { - let secret_key = SecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?; - Ok(Self::Local(secret_key)) + T::new_from_bytes(bytes).map(Self::Local) } - pub fn pubkey(&self) -> BlsPublicKey { + pub fn pubkey(&self) -> T::PubKey { match self { - Signer::Local(secret) => blst_pubkey_to_alloy(&secret.sk_to_pk()), + Signer::Local(secret) => secret.pubkey(), } } - pub async fn sign(&self, chain: Chain, object_root: &[u8; 32]) -> BlsSignature { + pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> T::Signature { match self { - Signer::Local(sk) => sign_builder_message(chain, sk, object_root), + Signer::Local(sk) => sign_builder_root(chain, sk, object_root), } } - pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> BlsSignature { + pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> T::Signature { + self.sign(chain, msg.tree_hash_root().0).await + } + + pub async fn verify_signature( + pubkey: &T::PubKey, + msg: &[u8], + signature: &T::Signature, + ) -> Result<(), T::VerificationError> { + T::verify_signature(pubkey, msg, signature) + } +} + +// #[derive(Clone)] +// #[non_exhaustive] +// pub enum GenericSigner { // TODO: Name better +// Bls(Signer), +// // Ecdsa(Signer), // TODO: Add ecdsa +// } + +// impl GenericSigner { +// pub fn pubkey(&self) -> Vec { +// match self { +// GenericSigner::Bls(bls_signer) => bls_signer.pubkey().to_vec(), +// } +// } + +// pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> Vec { +// match self { +// GenericSigner::Bls(bls_signer) => bls_signer.sign(chain, object_root).await.to_vec(), +// } +// } + +// pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> Vec { +// self.sign(chain, msg.tree_hash_root().0).await +// } +// } + + +// 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, it also signs a ProxyDelegation associating the new keypair +// with its consensus pubkey When a new commit module starts, pass the +// ProxyDelegation msg and then sign all future commit messages with the proxy +// key for slashing the faulty message + proxy delegation can be used +// Signed using builder domain + +#[derive(Clone, Deref)] +pub struct ProxySigner { + #[deref] + signer: Signer, + delegation: SignedProxyDelegation, +} + +impl ProxySigner { + pub fn new(signer: Signer, delegation: SignedProxyDelegation) -> Self { + Self { signer, delegation } + } +} + +#[derive(From)] +#[non_exhaustive] +pub enum GenericProxySigner { + Bls(ProxySigner) +} + +impl GenericProxySigner { + pub fn pubkey(&self) -> /*Vec*/ GenericPubkey { + match self { + GenericProxySigner::Bls(proxy_signer) => GenericPubkey::Bls(proxy_signer.pubkey()), + } + } + + pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> Vec { + match self { + GenericProxySigner::Bls(proxy_signer) => proxy_signer.sign(chain, object_root).await.to_vec(), + } + } + + pub fn delegation(&self) -> SignedProxyDelegation { + match self { + GenericProxySigner::Bls(proxy_signer) => proxy_signer.delegation, + } + } + + pub async fn verify_signature( + &self, + msg: &[u8], + signature: &[u8], + ) -> eyre::Result<()> { + self.pubkey().verify_signature(msg, signature) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode)] +#[serde(untagged)] +#[ssz(enum_behaviour = "transparent")] +#[non_exhaustive] +pub enum GenericPubkey { + Bls(::PubKey) +} + +impl GenericPubkey { + pub fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { + match self { + GenericPubkey::Bls(bls_pubkey) => { + Ok(::verify_signature(bls_pubkey, msg, signature.try_into().context("Invalid signature length for BLS.")?)?) + }, + } + } +} + +impl AsRef<[u8]> for GenericPubkey { + fn as_ref(&self) -> &[u8] { + match self { + GenericPubkey::Bls(bls_pubkey) => bls_pubkey.as_ref(), + } + } +} + + +impl tree_hash::TreeHash for GenericPubkey { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::Container + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + unimplemented!("Enum should never be packed") + } + + fn tree_hash_packing_factor() -> usize { + unimplemented!("Enum should never be packed") + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { match self { - Signer::Local(sk) => { - let object_root = msg.tree_hash_root(); - sign_builder_message(chain, sk, &object_root.0) - } + GenericPubkey::Bls(ref inner) => inner.tree_hash_root(), } } } diff --git a/crates/signer/Cargo.toml b/crates/signer/Cargo.toml index 71205d81..4c6aa598 100644 --- a/crates/signer/Cargo.toml +++ b/crates/signer/Cargo.toml @@ -38,3 +38,4 @@ rand.workspace = true uuid.workspace = true bimap.workspace = true lazy_static.workspace = true +derive_more.workspace = true diff --git a/crates/signer/src/error.rs b/crates/signer/src/error.rs index ddf023af..1a19b4dd 100644 --- a/crates/signer/src/error.rs +++ b/crates/signer/src/error.rs @@ -1,4 +1,4 @@ -use alloy::rpc::types::beacon::BlsPublicKey; +use alloy::{hex, rpc::types::beacon::BlsPublicKey}; use axum::{ http::StatusCode, response::{IntoResponse, Response}, @@ -10,11 +10,12 @@ pub enum SignerModuleError { #[error("unauthorized")] Unauthorized, - #[error("unknown consensus signer: {0}")] - UnknownConsensusSigner(BlsPublicKey), + #[error("unknown consensus signer: 0x{}", hex::encode(.0))] + UnknownConsensusSigner(Vec), - #[error("unknown proxy signer: {0}")] - UnknownProxySigner(BlsPublicKey), + // TODO(David): Think about better formatting, maybe a custom type instead of Vec + #[error("unknown proxy signer: 0x{}", hex::encode(.0))] + UnknownProxySigner(Vec), } impl IntoResponse for SignerModuleError { diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 53e74b08..00e79405 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -1,46 +1,96 @@ use std::collections::HashMap; use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use blst::min_pk::SecretKey as BlsSecretKey; use cb_common::{ commit::request::{ProxyDelegation, SignedProxyDelegation}, - signer::Signer, + signer::{GenericProxySigner, GenericPubkey, ProxySigner, SecretKey, Signer}, types::{Chain, ModuleId}, }; +use derive_more::{Deref, Display, From, Into}; use tree_hash::TreeHash; use crate::error::SignerModuleError; -// 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, it also signs a ProxyDelegation associating the new keypair -// with its consensus pubkey When a new commit module starts, pass the -// ProxyDelegation msg and then sign all future commit messages with the proxy -// key for slashing the faulty message + proxy delegation can be used -// Signed using builder domain - -#[derive(Clone)] -pub struct ProxySigner { - signer: Signer, - delegation: SignedProxyDelegation, +struct ProxySigners { + bls_signers: HashMap>, + // ecdsa_signers: HashMap<_, _> // TODO: add +} + +impl<'a> ProxySigners { + pub fn get(&self, key: &GenericPubkey) -> Option { + match key { + GenericPubkey::Bls(bls_pubkey) => { + let proxy_signer = self.dep_get(bls_pubkey)?; + Some(GenericProxySigner::Bls(proxy_signer.clone())) + } + _ => todo!("Crypto backend not yet implemented here."), + } + } + + pub fn add(&mut self, proxy: GenericProxySigner) { + match proxy { + GenericProxySigner::Bls(bls_proxy) => { + self.bls_signers.insert(bls_proxy.pubkey(), bls_proxy) + } + _ => todo!("Crypto backend not yet implemented here."), + }; + } + + pub fn find(&'a self, pubkey: &[u8]) -> Option { + fn helper<'a, T: SecretKey>( + keys: impl IntoIterator::PubKey>, + pubkey: &[u8], + ) -> Option + where + ::PubKey: 'a, + { + keys.into_iter().find(|x| x.as_ref() == pubkey).cloned().map(T::to_generic_pubkey) + } + + helper::(self.bls_signers.keys(), pubkey) + // .or_else(|| helper::(self.ecdsa_signers.keys(), pubkey)) + } +} + +impl Default for ProxySigners { + fn default() -> Self { + Self { bls_signers: Default::default() } + } +} + +trait DepGet { + type Target; + + fn dep_get(&self, k: &Key) -> Option<&Self::Target>; +} + +impl DepGet for ProxySigners { + type Target = ProxySigner; + + fn dep_get(&self, k: &BlsPublicKey) -> Option<&Self::Target> { + self.bls_signers.get(k) + } } pub struct SigningManager { chain: Chain, consensus_signers: HashMap, - proxy_signers: HashMap, + proxy_signers: ProxySigners, // HashMap, ProxySigner>, + // proxy_delegations: /// Map of module ids to their associated proxy pubkeys. /// Used to retrieve the corresponding proxy signer from the signing /// manager. - proxy_pubkeys: HashMap>, + proxy_pubkeys: HashMap>, } impl SigningManager { pub fn new(chain: Chain) -> Self { Self { chain, - consensus_signers: HashMap::new(), - proxy_signers: HashMap::new(), - proxy_pubkeys: HashMap::new(), + consensus_signers: Default::default(), + proxy_signers: Default::default(), + proxy_pubkeys: Default::default(), } } @@ -48,22 +98,22 @@ impl SigningManager { self.consensus_signers.insert(signer.pubkey(), signer); } - pub fn add_proxy_signer(&mut self, proxy: ProxySigner) { - self.proxy_signers.insert(proxy.signer.pubkey(), proxy); + pub fn add_proxy_signer(&mut self, proxy: GenericProxySigner) { + self.proxy_signers.add(proxy); } - pub async fn create_proxy( + pub async fn create_proxy( &mut self, module_id: ModuleId, delegator: BlsPublicKey, ) -> Result { - let signer = Signer::new_random(); - let proxy_pubkey = signer.pubkey(); + let signer = Signer::::new_random(); + let proxy_pubkey = T::to_generic_pubkey(signer.pubkey()); let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; let signed_delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; - let proxy_signer = ProxySigner { signer, delegation: signed_delegation }; + let proxy_signer = T::to_generic_proxy_signer(ProxySigner::new(signer, signed_delegation)); // Add the new proxy key to the manager's internal state self.add_proxy_signer(proxy_signer); @@ -77,25 +127,34 @@ impl SigningManager { pub async fn sign_consensus( &self, pubkey: &BlsPublicKey, - msg: &[u8; 32], + object_root: &[u8; 32], ) -> Result { let signer = self .consensus_signers .get(pubkey) - .ok_or(SignerModuleError::UnknownConsensusSigner(*pubkey))?; - let signature = signer.sign(self.chain, msg).await; + .ok_or(SignerModuleError::UnknownConsensusSigner(pubkey.to_vec()))?; + let signature = signer.sign(self.chain, *object_root).await; Ok(signature) } + fn find_proxy(&self, pubkey: &[u8]) -> Option { + let generic_pubkey = self.proxy_signers.find(pubkey)?; + + let proxy_signer = self.proxy_signers.get(&generic_pubkey).expect("Unreachable!"); + + Some(proxy_signer) + } + pub async fn sign_proxy( &self, - pubkey: &BlsPublicKey, - msg: &[u8; 32], - ) -> Result { - let proxy = - self.proxy_signers.get(pubkey).ok_or(SignerModuleError::UnknownProxySigner(*pubkey))?; - let signature = proxy.signer.sign(self.chain, msg).await; + pubkey: &[u8], + object_root: &[u8; 32], + ) -> Result, SignerModuleError> { + let proxy = self.find_proxy(pubkey) + .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; + + let signature = proxy.sign(self.chain, *object_root).await; Ok(signature) } @@ -104,37 +163,33 @@ impl SigningManager { self.consensus_signers.keys().cloned().collect() } - pub fn proxy_pubkeys(&self) -> &HashMap> { + pub fn proxy_pubkeys(&self) -> &HashMap> { &self.proxy_pubkeys } - pub fn delegations(&self) -> Vec { - self.proxy_signers.values().map(|s| s.delegation).collect() - } pub fn has_consensus(&self, pubkey: &BlsPublicKey) -> bool { self.consensus_signers.contains_key(pubkey) } - pub fn has_proxy(&self, pubkey: &BlsPublicKey) -> bool { - self.proxy_signers.contains_key(pubkey) + pub fn has_proxy(&self, pubkey: &[u8]) -> bool { + self.proxy_signers.find(pubkey).is_some() } pub fn get_delegation( &self, - proxy_pubkey: &BlsPublicKey, + pubkey: &[u8], ) -> Result { - let signer = self - .proxy_signers - .get(proxy_pubkey) - .ok_or(SignerModuleError::UnknownProxySigner(*proxy_pubkey))?; - Ok(signer.delegation) + let proxy = self.find_proxy(pubkey) + .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; + + Ok(proxy.delegation()) } } #[cfg(test)] mod tests { - use cb_common::signature::verify_signed_builder_message; + use cb_common::signature::{compute_signing_root, verify_signed_builder_message}; use lazy_static::lazy_static; use tree_hash::Hash256; @@ -160,7 +215,8 @@ mod tests { async fn test_proxy_key_is_valid_proxy_for_consensus_key() { let (mut signing_manager, consensus_pk) = init_signing_manager(); - let signed_delegation = signing_manager.create_proxy(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); + let signed_delegation = + signing_manager.create_proxy::(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); let validation_result = signed_delegation.validate(*CHAIN); @@ -170,7 +226,7 @@ mod tests { ); assert!( - signing_manager.has_proxy(&signed_delegation.message.proxy), + signing_manager.has_proxy(&signed_delegation.message.proxy.as_ref()), "Newly generated proxy key must be present in the signing manager's registry." ); } @@ -179,37 +235,43 @@ mod tests { async fn test_tampered_proxy_key_is_invalid() { let (mut signing_manager, consensus_pk) = init_signing_manager(); - let mut signed_delegation = signing_manager.create_proxy(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); + let mut signed_delegation = + signing_manager.create_proxy::(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); let m = &mut signed_delegation.signature.0[0]; (*m, _) = m.overflowing_add(1); let validation_result = signed_delegation.validate(*CHAIN); - assert!( - validation_result.is_err(), - "Tampered proxy key must be invalid." - ); + assert!(validation_result.is_err(), "Tampered proxy key must be invalid."); } #[tokio::test] async fn test_proxy_key_signs_message() { let (mut signing_manager, consensus_pk) = init_signing_manager(); - let signed_delegation = signing_manager.create_proxy(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); + let signed_delegation = + signing_manager.create_proxy::(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); let proxy_pk = signed_delegation.message.proxy; let data_root = Hash256::random(); let data_root_bytes = data_root.as_fixed_bytes(); - let sig = signing_manager.sign_proxy(&proxy_pk, data_root_bytes).await.unwrap(); + let sig = signing_manager.sign_proxy(proxy_pk.as_ref(), data_root_bytes).await.unwrap(); - let validation_result = verify_signed_builder_message( - *CHAIN, - &signed_delegation.message.proxy, - &data_root_bytes, - &sig, - ); + // Verify signature + + let domain = CHAIN.builder_domain(); + let signing_root = compute_signing_root(data_root_bytes.tree_hash_root().0, domain); + + let validation_result = proxy_pk.verify_signature(&signing_root, &sig); + + // verify_signed_builder_message( + // *CHAIN, + // &signed_delegation.message.proxy, + // &data_root_bytes, + // &sig, + // ); assert!( validation_result.is_ok(), diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 1dc6fed4..4984b80c 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -10,6 +10,7 @@ use axum::{ }; use axum_extra::TypedHeader; use bimap::BiHashMap; +use blst::min_pk::SecretKey as BlsSecretKey; use cb_common::{ commit::{ client::GetPubkeysResponse, @@ -34,7 +35,8 @@ pub struct SigningService; struct SigningState { /// Mananger handling different signing methods manager: Arc>, - /// Map of JWTs to module ids. This also acts as registry of all modules running + /// Map of JWTs to module ids. This also acts as registry of all modules + /// running jwts: Arc>, } @@ -126,7 +128,12 @@ async fn handle_request_signature( let sig = if request.is_proxy { signing_manager.sign_proxy(&request.pubkey, &request.object_root).await } else { - signing_manager.sign_consensus(&request.pubkey, &request.object_root).await + let pubkey = request + .pubkey + .as_slice() + .try_into() + .map_err(|_| SignerModuleError::UnknownConsensusSigner(request.pubkey.clone()))?; + signing_manager.sign_consensus(pubkey, &request.object_root).await.map(|x| x.to_vec()) }?; Ok((StatusCode::OK, Json(sig)).into_response()) @@ -143,7 +150,12 @@ async fn handle_generate_proxy( let mut signing_manager = state.manager.write().await; - let proxy_delegation = signing_manager.create_proxy(module_id, request.pubkey).await?; + use cb_common::commit::request::EncryptionScheme; + let proxy_delegation = match request.scheme { + EncryptionScheme::Bls => signing_manager.create_proxy::(module_id, request.consensus_pubkey).await?, + EncryptionScheme::Ecdsa => todo!("Not yet implemented"), //signing_manager.create_proxy::(module_id, request.consensus_pubkey).await?,, + }; + Ok((StatusCode::OK, Json(proxy_delegation)).into_response()) } diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index 1b91099e..360ec9dc 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,6 +1,7 @@ use std::time::Duration; use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use commit::request::GenerateProxyRequest; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; use lazy_static::lazy_static; @@ -46,7 +47,8 @@ impl DaCommitService { let pubkey = pubkeys.consensus.first().ok_or_eyre("no key available")?; info!("Registered validator {pubkey}"); - let proxy_delegation = self.config.signer_client.generate_proxy_key(*pubkey).await?; + let request = GenerateProxyRequest::new(*pubkey, commit::request::EncryptionScheme::Bls); + let proxy_delegation = self.config.signer_client.generate_proxy_key(&request).await?; info!("Obtained a proxy delegation {proxy_delegation:#?}"); let mut data = 0; @@ -61,11 +63,11 @@ impl DaCommitService { pub async fn send_request(&self, data: u64, pubkey: BlsPublicKey, proxy_delegation: SignedProxyDelegation) -> Result<()> { let datagram = Datagram { data }; - let request = SignRequest::builder(pubkey) + let request = SignRequest::builder(pubkey.to_vec()) .with_msg(&datagram); let signature = self.config.signer_client.request_signature(&request); - let proxy_request = SignRequest::builder(proxy_delegation.message.proxy) + let proxy_request = SignRequest::builder(proxy_delegation.message.proxy.as_ref().to_vec()) .is_proxy() .with_msg(&datagram); let proxy_signature = self.config.signer_client.request_signature(&proxy_request); diff --git a/tests/src/mock_relay.rs b/tests/src/mock_relay.rs index b43a5b53..f53adc5d 100644 --- a/tests/src/mock_relay.rs +++ b/tests/src/mock_relay.rs @@ -83,8 +83,8 @@ async fn handle_get_header( response.data.message.header.block_hash.0[0] = 1; response.data.message.set_value(U256::from(10)); response.data.message.pubkey = state.signer.pubkey(); - response.data.signature = - state.signer.sign(state.chain, &response.data.message.tree_hash_root().0).await; + let object_root = response.data.message.tree_hash_root().0; + response.data.signature = state.signer.sign(state.chain, object_root).await; (StatusCode::OK, axum::Json(response)).into_response() } From a7c76dfa0c0657a000a56e14781f22e240f87f0d Mon Sep 17 00:00:00 2001 From: David Petrov Date: Tue, 13 Aug 2024 17:28:32 +0300 Subject: [PATCH 02/37] feat(signer)!: add support for ECDSA proxy keys Co-authored-by: reo101 --- Cargo.toml | 3 + crates/common/Cargo.toml | 3 + crates/common/src/commit/client.rs | 27 ++- crates/common/src/commit/request.rs | 52 +++-- crates/common/src/config/signer.rs | 6 +- crates/common/src/config/utils.rs | 3 +- crates/common/src/signature.rs | 19 +- crates/common/src/signer.rs | 240 --------------------- crates/common/src/signer/mod.rs | 113 ++++++++++ crates/common/src/signer/schemes/bls.rs | 56 +++++ crates/common/src/signer/schemes/ecdsa.rs | 250 ++++++++++++++++++++++ crates/common/src/signer/schemes/mod.rs | 2 + crates/common/src/signer/signers.rs | 110 ++++++++++ crates/common/src/types.rs | 5 +- crates/signer/Cargo.toml | 1 + crates/signer/src/error.rs | 3 +- crates/signer/src/manager.rs | 103 +++++---- crates/signer/src/service.rs | 18 +- examples/da_commit/src/main.rs | 24 +-- 19 files changed, 693 insertions(+), 345 deletions(-) delete mode 100644 crates/common/src/signer.rs create mode 100644 crates/common/src/signer/mod.rs create mode 100644 crates/common/src/signer/schemes/bls.rs create mode 100644 crates/common/src/signer/schemes/ecdsa.rs create mode 100644 crates/common/src/signer/schemes/mod.rs create mode 100644 crates/common/src/signer/signers.rs diff --git a/Cargo.toml b/Cargo.toml index 76455096..a26db98d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,9 @@ blst = "0.3.11" tree_hash = "0.5" tree_hash_derive = "0.5" eth2_keystore = { git = "https://github.com/sigp/lighthouse", rev = "9e12c21f268c80a3f002ae0ca27477f9f512eb6f" } +elliptic-curve = { version = "0.13", features = ["serde"] } +generic-array = { version = "1.1.0", features = ["serde"] } +k256 = "0.13" # docker docker-compose-types = "0.12.0" diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 61b295aa..0aff58f5 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -35,6 +35,9 @@ blst.workspace = true tree_hash.workspace = true tree_hash_derive.workspace = true eth2_keystore.workspace = true +elliptic-curve.workspace = true +generic-array.workspace = true +k256.workspace = true # misc thiserror.workspace = true diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 6233d1ca..c52997d3 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -1,6 +1,6 @@ -use std::sync::Arc; +use std::{fmt, sync::Arc}; -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use alloy::rpc::types::beacon::BlsPublicKey; use eyre::WrapErr; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use serde::{Deserialize, Serialize}; @@ -45,7 +45,8 @@ impl SignerClient { } /// Request a list of validator pubkeys for which signatures can be - /// requested. TODO: add more docs on how proxy keys work + /// requested. + // TODO: add more docs on how proxy keys work pub async fn get_pubkeys(&self) -> Result { let url = format!("{}{}", self.url, GET_PUBKEYS_PATH); let res = self.client.get(&url).send().await?; @@ -69,7 +70,7 @@ impl SignerClient { pub async fn request_signature( &self, request: &SignRequest, - ) -> Result { + ) -> Result { let url = format!("{}{}", self.url, REQUEST_SIGNATURE_PATH); let res = self.client.post(&url).json(&request).send().await?; @@ -83,9 +84,9 @@ impl SignerClient { }); } - let signature: BlsSignature = serde_json::from_slice(&response_bytes)?; + let signature: Vec = serde_json::from_slice(&response_bytes)?; - Ok(signature) + Ok(GenericSignature(signature)) } pub async fn generate_proxy_key( @@ -93,6 +94,7 @@ impl SignerClient { request: &GenerateProxyRequest, ) -> Result { let url = format!("{}{}", self.url, GENERATE_PROXY_KEY_PATH); + println!("{}", serde_json::to_string(&request).unwrap()); let res = self.client.post(&url).json(&request).send().await?; let status = res.status(); @@ -110,3 +112,16 @@ impl SignerClient { Ok(signed_proxy_delegation) } } + +// TODO(David): Better naming +pub struct GenericSignature(Vec); + +impl fmt::Display for GenericSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x")?; + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 1193831a..c6ccd80f 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -1,19 +1,30 @@ +use std::fmt; + use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; -use crate::{error::BlstErrorWrapper, signature::verify_signed_builder_message, signer::GenericPubkey, types::Chain}; +use crate::{ + error::BlstErrorWrapper, signature::verify_signed_builder_message, signer::GenericPubkey, + 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 { pub delegator: BlsPublicKey, pub proxy: GenericPubkey, } +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 message: ProxyDelegation, @@ -32,21 +43,26 @@ impl SignedProxyDelegation { } } -// TODO(David): Consider splitting `SignRequest` into two: ConsensusSignRequest and ProxySignRequest -// for better type safety (avoid the Vec generalisation) and avoid the is_proxy flag +impl fmt::Display for SignedProxyDelegation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}\nSignature: {}", self.message, self.signature) + } +} + +// TODO(David): Consider splitting `SignRequest` into two: ConsensusSignRequest +// and ProxySignRequest. For better type safety (to avoid the Vec +// generalisation) and avoid the is_proxy flag #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignRequest { - pub pubkey: Vec, // TODO(David): Vec might not be the most memory inefficient, think about something on the stack + // TODO(David): Vec might not be the most memory inefficient, think about something on the + // stack + pub pubkey: Vec, pub is_proxy: bool, pub object_root: [u8; 32], } impl SignRequest { - pub fn new( - pubkey: Vec, - is_proxy: bool, - object_root: [u8; 32], - ) -> SignRequest { + pub fn new(pubkey: Vec, is_proxy: bool, object_root: [u8; 32]) -> SignRequest { Self { pubkey, is_proxy, object_root } } @@ -67,6 +83,14 @@ impl SignRequest { } } +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum EncryptionScheme { + #[serde(rename = "bls")] + Bls, + #[serde(rename = "ecdsa")] + Ecdsa, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GenerateProxyRequest { pub consensus_pubkey: BlsPublicKey, @@ -78,9 +102,3 @@ impl GenerateProxyRequest { GenerateProxyRequest { consensus_pubkey, scheme } } } - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum EncryptionScheme { - Bls, - Ecdsa, -} diff --git a/crates/common/src/config/signer.rs b/crates/common/src/config/signer.rs index 988d22a2..0ed3e5fa 100644 --- a/crates/common/src/config/signer.rs +++ b/crates/common/src/config/signer.rs @@ -1,5 +1,4 @@ use bimap::BiHashMap; - use eyre::Result; use serde::{Deserialize, Serialize}; @@ -8,7 +7,10 @@ use super::{ utils::{load_env_var, load_jwts}, CommitBoostConfig, }; -use crate::{loader::SignerLoader, types::{Chain, Jwt, ModuleId}}; +use crate::{ + loader::SignerLoader, + types::{Chain, Jwt, ModuleId}, +}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SignerConfig { diff --git a/crates/common/src/config/utils.rs b/crates/common/src/config/utils.rs index ce5b810a..cd7bf927 100644 --- a/crates/common/src/config/utils.rs +++ b/crates/common/src/config/utils.rs @@ -2,9 +2,8 @@ use bimap::BiHashMap; use eyre::{Context, Result}; use serde::de::DeserializeOwned; -use crate::types::{Jwt, ModuleId}; - use super::constants::JWTS_ENV; +use crate::types::{Jwt, ModuleId}; pub fn load_env_var(env: &str) -> Result { std::env::var(env).wrap_err(format!("{env} is not set")) diff --git a/crates/common/src/signature.rs b/crates/common/src/signature.rs index 6136627b..762e0bcf 100644 --- a/crates/common/src/signature.rs +++ b/crates/common/src/signature.rs @@ -3,27 +3,18 @@ use blst::{ min_pk::{PublicKey, SecretKey as BlsSecretKey, Signature}, BLST_ERROR, }; -use rand::RngCore; use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - constants::{APPLICATION_BUILDER_DOMAIN, GENESIS_VALIDATORS_ROOT}, error::BlstErrorWrapper, signer::SecretKey, types::Chain, utils::{alloy_pubkey_to_blst, alloy_sig_to_blst} + constants::{APPLICATION_BUILDER_DOMAIN, GENESIS_VALIDATORS_ROOT}, + error::BlstErrorWrapper, + signer::SecretKey, + types::Chain, + utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}, }; -pub fn random_secret() -> BlsSecretKey { - let mut rng = rand::thread_rng(); - let mut ikm = [0u8; 32]; - rng.fill_bytes(&mut ikm); - - match BlsSecretKey::key_gen(&ikm, &[]) { - Ok(key) => key, - // Key material is always valid (32 `u8`s), so `key_gen` can't return Err. - Err(_) => unreachable!(), - } -} - // TODO(David): Think about removing pub fn verify_signature( pubkey: &BlsPublicKey, diff --git a/crates/common/src/signer.rs b/crates/common/src/signer.rs deleted file mode 100644 index aafc34e3..00000000 --- a/crates/common/src/signer.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::{any::Any, error::Error}; - -use alloy::{primitives::FixedBytes, rpc::types::beacon::{BlsPublicKey, BlsSignature}}; -use blst::min_pk::{PublicKey, SecretKey as BlsSecretKey}; -use derive_more::derive::{Deref, From}; -use eyre::{Context, Result}; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use tree_hash::TreeHash; -use tree_hash_derive::TreeHash; - -use crate::{ - commit::request::SignedProxyDelegation, error::BlstErrorWrapper, signature::{random_secret, sign_builder_message, sign_builder_root, sign_message, verify_signature}, types::Chain, utils::blst_pubkey_to_alloy -}; - -pub trait SecretKey { - type PubKey: AsRef<[u8]> + Clone; - type Signature: AsRef<[u8]> + Clone; - type VerificationError: Error; - - fn new_random() -> Self; - fn new_from_bytes(bytes: &[u8]) -> Result where Self: Sized; - fn pubkey(&self) -> Self::PubKey; - fn sign(&self, msg: &[u8; 32]) -> Self::Signature; - fn sign_msg(&self, msg: &impl TreeHash) -> Self::Signature { - self.sign(&msg.tree_hash_root().0) - } - - fn verify_signature(pubkey: &Self::PubKey, msg: &[u8], signature: &Self::Signature) -> Result<(), Self::VerificationError>; - - // UTILITY METHODS - fn to_generic_pubkey(pubkey: Self::PubKey) -> GenericPubkey; - fn to_generic_proxy_signer(proxy_signer: ProxySigner) -> GenericProxySigner where Self: Sized; -} - -impl SecretKey for BlsSecretKey { - type PubKey = BlsPublicKey; - type Signature = BlsSignature; - type VerificationError = BlstErrorWrapper; - - fn new_random() -> Self { - random_secret() - } - - fn new_from_bytes(bytes: &[u8]) -> Result { - Ok(BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?) - } - - fn pubkey(&self) -> Self::PubKey { - blst_pubkey_to_alloy(&self.sk_to_pk()) - } - - fn sign(&self, msg: &[u8; 32]) -> Self::Signature { - sign_message(self, msg) - } - - fn verify_signature( - pubkey: &Self::PubKey, - msg: &[u8], - signature: &Self::Signature, - ) -> Result<(), Self::VerificationError> { - verify_signature(pubkey, msg, signature) - } - - // UTILITY METHODS - fn to_generic_pubkey(pubkey: Self::PubKey) -> GenericPubkey { - GenericPubkey::Bls(pubkey) - } - - fn to_generic_proxy_signer(proxy_signer: ProxySigner) -> GenericProxySigner where Self: Sized { - GenericProxySigner::Bls(proxy_signer) - } -} - -#[derive(Clone)] -pub enum Signer { - Local(T), -} - -impl Signer { - pub fn new_random() -> Self { - Signer::Local(T::new_random()) - } - - pub fn new_from_bytes(bytes: &[u8]) -> Result { - T::new_from_bytes(bytes).map(Self::Local) - } - - pub fn pubkey(&self) -> T::PubKey { - match self { - Signer::Local(secret) => secret.pubkey(), - } - } - - pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> T::Signature { - match self { - Signer::Local(sk) => sign_builder_root(chain, sk, object_root), - } - } - - pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> T::Signature { - self.sign(chain, msg.tree_hash_root().0).await - } - - pub async fn verify_signature( - pubkey: &T::PubKey, - msg: &[u8], - signature: &T::Signature, - ) -> Result<(), T::VerificationError> { - T::verify_signature(pubkey, msg, signature) - } -} - -// #[derive(Clone)] -// #[non_exhaustive] -// pub enum GenericSigner { // TODO: Name better -// Bls(Signer), -// // Ecdsa(Signer), // TODO: Add ecdsa -// } - -// impl GenericSigner { -// pub fn pubkey(&self) -> Vec { -// match self { -// GenericSigner::Bls(bls_signer) => bls_signer.pubkey().to_vec(), -// } -// } - -// pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> Vec { -// match self { -// GenericSigner::Bls(bls_signer) => bls_signer.sign(chain, object_root).await.to_vec(), -// } -// } - -// pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> Vec { -// self.sign(chain, msg.tree_hash_root().0).await -// } -// } - - -// 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, it also signs a ProxyDelegation associating the new keypair -// with its consensus pubkey When a new commit module starts, pass the -// ProxyDelegation msg and then sign all future commit messages with the proxy -// key for slashing the faulty message + proxy delegation can be used -// Signed using builder domain - -#[derive(Clone, Deref)] -pub struct ProxySigner { - #[deref] - signer: Signer, - delegation: SignedProxyDelegation, -} - -impl ProxySigner { - pub fn new(signer: Signer, delegation: SignedProxyDelegation) -> Self { - Self { signer, delegation } - } -} - -#[derive(From)] -#[non_exhaustive] -pub enum GenericProxySigner { - Bls(ProxySigner) -} - -impl GenericProxySigner { - pub fn pubkey(&self) -> /*Vec*/ GenericPubkey { - match self { - GenericProxySigner::Bls(proxy_signer) => GenericPubkey::Bls(proxy_signer.pubkey()), - } - } - - pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> Vec { - match self { - GenericProxySigner::Bls(proxy_signer) => proxy_signer.sign(chain, object_root).await.to_vec(), - } - } - - pub fn delegation(&self) -> SignedProxyDelegation { - match self { - GenericProxySigner::Bls(proxy_signer) => proxy_signer.delegation, - } - } - - pub async fn verify_signature( - &self, - msg: &[u8], - signature: &[u8], - ) -> eyre::Result<()> { - self.pubkey().verify_signature(msg, signature) - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode)] -#[serde(untagged)] -#[ssz(enum_behaviour = "transparent")] -#[non_exhaustive] -pub enum GenericPubkey { - Bls(::PubKey) -} - -impl GenericPubkey { - pub fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { - match self { - GenericPubkey::Bls(bls_pubkey) => { - Ok(::verify_signature(bls_pubkey, msg, signature.try_into().context("Invalid signature length for BLS.")?)?) - }, - } - } -} - -impl AsRef<[u8]> for GenericPubkey { - fn as_ref(&self) -> &[u8] { - match self { - GenericPubkey::Bls(bls_pubkey) => bls_pubkey.as_ref(), - } - } -} - - -impl tree_hash::TreeHash for GenericPubkey { - fn tree_hash_type() -> tree_hash::TreeHashType { - tree_hash::TreeHashType::Container - } - - fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { - unimplemented!("Enum should never be packed") - } - - fn tree_hash_packing_factor() -> usize { - unimplemented!("Enum should never be packed") - } - - fn tree_hash_root(&self) -> tree_hash::Hash256 { - match self { - GenericPubkey::Bls(ref inner) => inner.tree_hash_root(), - } - } -} diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs new file mode 100644 index 00000000..20455c19 --- /dev/null +++ b/crates/common/src/signer/mod.rs @@ -0,0 +1,113 @@ +use std::{error::Error, fmt::{self, LowerHex}}; + +use eyre::{Context, Result}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; + +pub mod schemes; +pub mod signers; + +pub use schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey}; +pub use signers::{GenericProxySigner, ProxySigner, Signer}; + +pub type PubKey = ::PubKey; + +pub trait SecretKey { + type PubKey: AsRef<[u8]> + Clone; + type Signature: AsRef<[u8]> + Clone; + type VerificationError: Error; + + fn new_random() -> Self; + fn new_from_bytes(bytes: &[u8]) -> Result + where + Self: Sized; + fn pubkey(&self) -> Self::PubKey; + fn sign(&self, msg: &[u8; 32]) -> Self::Signature; + fn sign_msg(&self, msg: &impl TreeHash) -> Self::Signature { + self.sign(&msg.tree_hash_root().0) + } + + fn verify_signature( + pubkey: &Self::PubKey, + msg: &[u8], + signature: &Self::Signature, + ) -> Result<(), Self::VerificationError>; +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode)] +#[serde(untagged)] +#[ssz(enum_behaviour = "transparent")] +pub enum GenericPubkey { + Bls(PubKey), + Ecdsa(PubKey), +} + +impl GenericPubkey { + pub fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { + match self { + GenericPubkey::Bls(bls_pubkey) => Ok(::verify_signature( + bls_pubkey, + msg, + signature.try_into().context("Invalid signature length for BLS.")?, + )?), + GenericPubkey::Ecdsa(ecdsa_pubkey) => { + let sig = signature.try_into().context("Invalid signature for ECDSA.")?; + Ok(::verify_signature(ecdsa_pubkey, msg, &sig)?) + } + } + } +} + +impl AsRef<[u8]> for GenericPubkey { + fn as_ref(&self) -> &[u8] { + match self { + GenericPubkey::Bls(bls_pubkey) => bls_pubkey.as_ref(), + GenericPubkey::Ecdsa(ecdsa_pubkey) => ecdsa_pubkey.as_ref(), + } + } +} + +impl LowerHex for GenericPubkey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x")?; + + let pubkey_bytes = match self { + GenericPubkey::Bls(bls) => bls.as_ref(), + GenericPubkey::Ecdsa(ecdsa) => ecdsa.as_ref(), + }; + + for byte in pubkey_bytes { + write!(f, "{:02x}", byte)?; + } + + Ok(()) + } +} + +impl fmt::Display for GenericPubkey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:x}") + } +} + +impl tree_hash::TreeHash for GenericPubkey { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::Container + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + unimplemented!("Enum should never be packed") + } + + fn tree_hash_packing_factor() -> usize { + unimplemented!("Enum should never be packed") + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + match self { + GenericPubkey::Bls(ref inner) => inner.tree_hash_root(), + GenericPubkey::Ecdsa(ref inner) => inner.tree_hash_root(), + } + } +} diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs new file mode 100644 index 00000000..b296c500 --- /dev/null +++ b/crates/common/src/signer/schemes/bls.rs @@ -0,0 +1,56 @@ +use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; + +use crate::{ + error::BlstErrorWrapper, + signature::{sign_message, verify_signature}, + signer::{GenericPubkey, SecretKey}, + utils::blst_pubkey_to_alloy, +}; + +pub type BlsSecretKey = blst::min_pk::SecretKey; + +impl SecretKey for BlsSecretKey { + type PubKey = BlsPublicKey; + type Signature = BlsSignature; + type VerificationError = BlstErrorWrapper; + + fn new_random() -> Self { + use rand::RngCore; + + let mut rng = rand::thread_rng(); + let mut ikm = [0u8; 32]; + rng.fill_bytes(&mut ikm); + + match BlsSecretKey::key_gen(&ikm, &[]) { + Ok(key) => key, + // Key material is always valid (32 `u8`s), so `key_gen` can't return Err. + Err(_) => unreachable!(), + } + } + + fn new_from_bytes(bytes: &[u8]) -> eyre::Result { + Ok(BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?) + } + + fn pubkey(&self) -> Self::PubKey { + blst_pubkey_to_alloy(&self.sk_to_pk()) + } + + fn sign(&self, msg: &[u8; 32]) -> Self::Signature { + sign_message(self, msg) + } + + fn verify_signature( + pubkey: &Self::PubKey, + msg: &[u8], + signature: &Self::Signature, + ) -> Result<(), Self::VerificationError> { + verify_signature(pubkey, msg, signature) + } +} + +impl From for GenericPubkey { + fn from(value: BlsPublicKey) -> Self { + GenericPubkey::Bls(value) + } +} diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs new file mode 100644 index 00000000..2ec3656d --- /dev/null +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -0,0 +1,250 @@ +use std::hash::Hash; + +use derive_more::derive::Into; +use eyre::Result; +use generic_array::GenericArray; +use k256::{ + ecdsa::{Signature as EcdsaSignatureInner, VerifyingKey as EcdsaPublicKeyInner}, + AffinePoint, +}; +use serde::{Deserialize, Serialize}; +use ssz_types::{typenum::U33, FixedVector}; +use tree_hash::TreeHash; + +use crate::signer::{GenericPubkey, PubKey, SecretKey}; + +pub type EcdsaSecretKey = k256::ecdsa::SigningKey; +type EcdsaCompressedKey = GenericArray; + +#[derive(Debug, Clone, Copy, Into, Serialize, Deserialize, PartialEq, Eq)] +#[serde(from = "JSONEcdsaPublicKey")] +pub struct EcdsaPublicKey { + #[serde(skip_serializing)] + inner: EcdsaPublicKeyInner, + encoded: EcdsaCompressedKey, +} + +impl EcdsaPublicKey { + /// Size of the public key in bytes. We store the SEC1 encoded affine point + /// compressed, thus 33 bytes. + const SIZE: usize = 33; + + pub fn new(inner: EcdsaPublicKeyInner) -> Self { + use elliptic_curve::sec1::ToEncodedPoint; + + let affine: &AffinePoint = inner.as_ref(); + let encoded: [u8; Self::SIZE] = + affine.to_encoded_point(true).as_bytes().try_into().unwrap(); + + let encoded = GenericArray::from_array(encoded); + + EcdsaPublicKey { inner, encoded } + } + + pub fn new_from_bytes(encoded: Vec) -> Result { + let inner = EcdsaPublicKeyInner::from_sec1_bytes(&encoded)?; + let encoded = GenericArray::from_array::<{ Self::SIZE }>(encoded.try_into().unwrap()); + Ok(Self { inner, encoded }) + } + + fn new_from_bytes_infallible(encoded: [u8; Self::SIZE]) -> Self { + Self { + inner: EcdsaPublicKeyInner::from_sec1_bytes(&encoded).unwrap(), + encoded: GenericArray::from_array(encoded), + } + } +} + +impl Hash for EcdsaPublicKey { + fn hash(&self, state: &mut H) { + self.encoded.hash(state); + } +} + +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.into_array::<{ Self::SIZE }>().to_vec(); + FixedVector::::from(vec).tree_hash_root() + } +} + +impl ssz::Encode for EcdsaPublicKey { + #[inline] + fn is_ssz_fixed_len() -> bool { + true + } + + fn ssz_bytes_len(&self) -> usize { + Self::SIZE + } + + #[inline] + fn ssz_fixed_len() -> usize { + Self::SIZE + } + + #[inline] + fn ssz_append(&self, buf: &mut Vec) { + buf.extend_from_slice(&self.encoded) + } + + #[inline] + fn as_ssz_bytes(&self) -> Vec { + self.encoded.to_vec() + } +} + +impl ssz::Decode for EcdsaPublicKey { + fn is_ssz_fixed_len() -> bool { + true + } + + fn ssz_fixed_len() -> usize { + Self::SIZE + } + + fn from_ssz_bytes(bytes: &[u8]) -> std::result::Result { + if bytes.len() != Self::SIZE { + return Err(ssz::DecodeError::InvalidByteLength { + len: bytes.len(), + expected: Self::SIZE, + }); + } + + let mut fixed_array = [0_u8; Self::SIZE]; + fixed_array.copy_from_slice(bytes); + + Ok(EcdsaPublicKey::new_from_bytes_infallible(fixed_array)) + } +} + +impl TryFrom> for EcdsaPublicKey { + type Error = k256::ecdsa::Error; + + fn try_from(value: Vec) -> std::result::Result { + Self::new_from_bytes(value) + } +} + +impl From for EcdsaPublicKey { + fn from(value: EcdsaCompressedKey) -> Self { + Self::new_from_bytes_infallible(value.try_into().unwrap()) + } +} + +#[derive(Deserialize)] +struct JSONEcdsaPublicKey { + encoded: EcdsaCompressedKey, +} + +impl From for EcdsaPublicKey { + fn from(value: JSONEcdsaPublicKey) -> Self { + // TODO(David): think about fallible decoding of invalid keys. + // This would incorrectly panic. + Self::try_from(value.encoded).expect("Encoded ECDSA key must be valid.") + } +} + +impl From for EcdsaPublicKey { + fn from(value: EcdsaPublicKeyInner) -> Self { + EcdsaPublicKey::new(value) + } +} + +impl AsRef<[u8]> for EcdsaPublicKey { + fn as_ref(&self) -> &[u8] { + &self.encoded + } +} + +#[derive(Clone)] +pub struct EcdsaSignature { + inner: EcdsaSignatureInner, + // TODO(David): Maybe prefer `GenericArray` for explicit fixed size. + pub(in crate::signer) encoded: Vec, +} + +impl EcdsaSignature { + pub fn new(inner: EcdsaSignatureInner) -> Self { + Self { inner, encoded: inner.to_vec() } + } +} + +impl From for EcdsaSignature { + fn from(value: EcdsaSignatureInner) -> Self { + Self::new(value) + } +} + +impl AsRef<[u8]> for EcdsaSignature { + fn as_ref(&self) -> &[u8] { + &self.encoded + } +} + +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 SecretKey for EcdsaSecretKey { + type PubKey = EcdsaPublicKey; + + type Signature = EcdsaSignature; + + type VerificationError = k256::ecdsa::Error; + + fn new_random() -> Self { + EcdsaSecretKey::random(&mut rand::thread_rng()) + } + + fn new_from_bytes(bytes: &[u8]) -> Result + where + Self: Sized, + { + Ok(EcdsaSecretKey::from_slice(bytes)?) + } + + fn pubkey(&self) -> Self::PubKey { + EcdsaPublicKeyInner::from(self).into() + } + + fn sign(&self, msg: &[u8; 32]) -> Self::Signature { + k256::ecdsa::signature::Signer::::sign(self, msg).into() + } + + fn verify_signature( + pubkey: &Self::PubKey, + msg: &[u8], + signature: &Self::Signature, + ) -> Result<(), Self::VerificationError> { + use k256::ecdsa::signature::Verifier; + pubkey.inner.verify(msg, &signature.inner) + } +} + +impl From for GenericPubkey { + fn from(value: PubKey) -> Self { + GenericPubkey::Ecdsa(value) + } +} diff --git a/crates/common/src/signer/schemes/mod.rs b/crates/common/src/signer/schemes/mod.rs new file mode 100644 index 00000000..91cccfdc --- /dev/null +++ b/crates/common/src/signer/schemes/mod.rs @@ -0,0 +1,2 @@ +pub mod bls; +pub mod ecdsa; diff --git a/crates/common/src/signer/signers.rs b/crates/common/src/signer/signers.rs new file mode 100644 index 00000000..70e08db4 --- /dev/null +++ b/crates/common/src/signer/signers.rs @@ -0,0 +1,110 @@ +use derive_more::derive::{Deref, From}; +use eyre::Result; +use tree_hash::TreeHash; + +use super::{ + schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey}, + GenericPubkey, SecretKey, +}; +use crate::{commit::request::SignedProxyDelegation, signature::sign_builder_root, types::Chain}; + +// TODO(David): remove the default type arg +#[derive(Clone)] +pub enum Signer { + Local(T), +} + +impl Signer { + pub fn new_random() -> Self { + Signer::Local(T::new_random()) + } + + pub fn new_from_bytes(bytes: &[u8]) -> Result { + T::new_from_bytes(bytes).map(Self::Local) + } + + pub fn pubkey(&self) -> T::PubKey { + match self { + Signer::Local(secret) => secret.pubkey(), + } + } + + pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> T::Signature { + match self { + Signer::Local(sk) => sign_builder_root(chain, sk, object_root), + } + } + + pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> T::Signature { + self.sign(chain, msg.tree_hash_root().0).await + } + + // TODO(David): + // This doesn't need to be here. A separate `Verifier` might make sense. + // Left here for now (for convenience). + pub async fn verify_signature( + pubkey: &T::PubKey, + msg: &[u8], + signature: &T::Signature, + ) -> Result<(), T::VerificationError> { + T::verify_signature(pubkey, msg, signature) + } +} + +// 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, it also signs a ProxyDelegation associating the new keypair +// with its consensus pubkey When a new commit module starts, pass the +// ProxyDelegation msg and then sign all future commit messages with the proxy +// key for slashing the faulty message + proxy delegation can be used +// Signed using builder domain + +#[derive(Clone, Deref)] +pub struct ProxySigner { + #[deref] + signer: Signer, + delegation: SignedProxyDelegation, +} + +impl ProxySigner { + pub fn new(signer: Signer, delegation: SignedProxyDelegation) -> Self { + Self { signer, delegation } + } +} + +#[derive(From)] +pub enum GenericProxySigner { + Bls(ProxySigner), + Ecdsa(ProxySigner), +} + +impl GenericProxySigner { + pub fn pubkey(&self) -> GenericPubkey { + match self { + GenericProxySigner::Bls(proxy_signer) => GenericPubkey::Bls(proxy_signer.pubkey()), + GenericProxySigner::Ecdsa(proxy_signer) => GenericPubkey::Ecdsa(proxy_signer.pubkey()), + } + } + + pub fn delegation(&self) -> SignedProxyDelegation { + match self { + GenericProxySigner::Bls(proxy_signer) => proxy_signer.delegation, + GenericProxySigner::Ecdsa(proxy_signer) => proxy_signer.delegation, + } + } + + pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> Vec { + match self { + GenericProxySigner::Bls(proxy_signer) => { + proxy_signer.sign(chain, object_root).await.to_vec() + } + GenericProxySigner::Ecdsa(proxy_signer) => { + proxy_signer.sign(chain, object_root).await.encoded.to_vec() + } + } + } + + pub async fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { + self.pubkey().verify_signature(msg, signature) + } +} diff --git a/crates/common/src/types.rs b/crates/common/src/types.rs index 68db796e..3e9b9e35 100644 --- a/crates/common/src/types.rs +++ b/crates/common/src/types.rs @@ -1,5 +1,4 @@ -use derive_more::{Deref, From, Into, Display}; - +use derive_more::{Deref, Display, From, Into}; use serde::{Deserialize, Serialize}; use crate::constants::{ @@ -54,4 +53,4 @@ pub struct ModuleId(pub String); #[derive(Clone, Debug, Display, PartialEq, Eq, Hash, Deref, From, Into, Serialize, Deserialize)] #[into(owned, ref, ref_mut)] #[serde(transparent)] -pub struct Jwt(pub String); \ No newline at end of file +pub struct Jwt(pub String); diff --git a/crates/signer/Cargo.toml b/crates/signer/Cargo.toml index 4c6aa598..8c1fd00c 100644 --- a/crates/signer/Cargo.toml +++ b/crates/signer/Cargo.toml @@ -30,6 +30,7 @@ tracing.workspace = true blst.workspace = true tree_hash.workspace = true tree_hash_derive.workspace = true +k256.workspace = true # misc thiserror.workspace = true diff --git a/crates/signer/src/error.rs b/crates/signer/src/error.rs index 1a19b4dd..8bdbd0ef 100644 --- a/crates/signer/src/error.rs +++ b/crates/signer/src/error.rs @@ -1,4 +1,4 @@ -use alloy::{hex, rpc::types::beacon::BlsPublicKey}; +use alloy::hex; use axum::{ http::StatusCode, response::{IntoResponse, Response}, @@ -13,7 +13,6 @@ pub enum SignerModuleError { #[error("unknown consensus signer: 0x{}", hex::encode(.0))] UnknownConsensusSigner(Vec), - // TODO(David): Think about better formatting, maybe a custom type instead of Vec #[error("unknown proxy signer: 0x{}", hex::encode(.0))] UnknownProxySigner(Vec), } diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 00e79405..9d8b47f1 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -4,72 +4,84 @@ use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use blst::min_pk::SecretKey as BlsSecretKey; use cb_common::{ commit::request::{ProxyDelegation, SignedProxyDelegation}, - signer::{GenericProxySigner, GenericPubkey, ProxySigner, SecretKey, Signer}, + signer::{ + EcdsaSecretKey, GenericProxySigner, GenericPubkey, ProxySigner, PubKey, SecretKey, Signer, + }, types::{Chain, ModuleId}, }; -use derive_more::{Deref, Display, From, Into}; use tree_hash::TreeHash; use crate::error::SignerModuleError; struct ProxySigners { - bls_signers: HashMap>, - // ecdsa_signers: HashMap<_, _> // TODO: add + bls_signers: HashMap, ProxySigner>, + ecdsa_signers: HashMap, ProxySigner>, } impl<'a> ProxySigners { pub fn get(&self, key: &GenericPubkey) -> Option { match key { GenericPubkey::Bls(bls_pubkey) => { - let proxy_signer = self.dep_get(bls_pubkey)?; + let proxy_signer = self.get_proxy_signer(bls_pubkey)?; Some(GenericProxySigner::Bls(proxy_signer.clone())) } - _ => todo!("Crypto backend not yet implemented here."), + GenericPubkey::Ecdsa(ecdsa_pubkey) => { + let proxy_signer = self.get_proxy_signer(ecdsa_pubkey)?; + Some(GenericProxySigner::Ecdsa(proxy_signer.clone())) + } } } pub fn add(&mut self, proxy: GenericProxySigner) { match proxy { GenericProxySigner::Bls(bls_proxy) => { - self.bls_signers.insert(bls_proxy.pubkey(), bls_proxy) + self.bls_signers.insert(bls_proxy.pubkey(), bls_proxy); + } + GenericProxySigner::Ecdsa(ecdsa_proxy) => { + self.ecdsa_signers.insert(ecdsa_proxy.pubkey(), ecdsa_proxy); } - _ => todo!("Crypto backend not yet implemented here."), - }; + } } - pub fn find(&'a self, pubkey: &[u8]) -> Option { - fn helper<'a, T: SecretKey>( - keys: impl IntoIterator::PubKey>, + pub fn find_pubkey(&'a self, pubkey: &[u8]) -> Option { + fn find_typed<'a, T>( + keys: impl IntoIterator>, pubkey: &[u8], ) -> Option where - ::PubKey: 'a, + T: SecretKey, + PubKey: 'a + Into, { - keys.into_iter().find(|x| x.as_ref() == pubkey).cloned().map(T::to_generic_pubkey) + keys.into_iter().find(|x| x.as_ref() == pubkey).cloned().map(Into::into) } - helper::(self.bls_signers.keys(), pubkey) - // .or_else(|| helper::(self.ecdsa_signers.keys(), pubkey)) + find_typed::(self.bls_signers.keys(), pubkey) + .or_else(|| find_typed::(self.ecdsa_signers.keys(), pubkey)) } } impl Default for ProxySigners { fn default() -> Self { - Self { bls_signers: Default::default() } + Self { bls_signers: Default::default(), ecdsa_signers: Default::default() } } } -trait DepGet { - type Target; - - fn dep_get(&self, k: &Key) -> Option<&Self::Target>; +trait GetProxySigner { + fn get_proxy_signer(&self, pk: &PubKey) -> Option<&ProxySigner>; } -impl DepGet for ProxySigners { - type Target = ProxySigner; +impl GetProxySigner for ProxySigners { + fn get_proxy_signer(&self, pk: &PubKey) -> Option<&ProxySigner> { + self.bls_signers.get(pk) + } +} - fn dep_get(&self, k: &BlsPublicKey) -> Option<&Self::Target> { - self.bls_signers.get(k) +impl GetProxySigner for ProxySigners { + fn get_proxy_signer( + &self, + pk: &PubKey, + ) -> Option<&ProxySigner> { + self.ecdsa_signers.get(pk) } } @@ -106,14 +118,18 @@ impl SigningManager { &mut self, module_id: ModuleId, delegator: BlsPublicKey, - ) -> Result { + ) -> Result + where + PubKey: Into, + ProxySigner: Into, + { let signer = Signer::::new_random(); - let proxy_pubkey = T::to_generic_pubkey(signer.pubkey()); + let proxy_pubkey = signer.pubkey().into(); let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; let signed_delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; - let proxy_signer = T::to_generic_proxy_signer(ProxySigner::new(signer, signed_delegation)); + let proxy_signer = ProxySigner::new(signer, signed_delegation).into(); // Add the new proxy key to the manager's internal state self.add_proxy_signer(proxy_signer); @@ -139,7 +155,7 @@ impl SigningManager { } fn find_proxy(&self, pubkey: &[u8]) -> Option { - let generic_pubkey = self.proxy_signers.find(pubkey)?; + let generic_pubkey = self.proxy_signers.find_pubkey(pubkey)?; let proxy_signer = self.proxy_signers.get(&generic_pubkey).expect("Unreachable!"); @@ -151,7 +167,8 @@ impl SigningManager { pubkey: &[u8], object_root: &[u8; 32], ) -> Result, SignerModuleError> { - let proxy = self.find_proxy(pubkey) + let proxy = self + .find_proxy(pubkey) .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; let signature = proxy.sign(self.chain, *object_root).await; @@ -167,20 +184,20 @@ impl SigningManager { &self.proxy_pubkeys } - pub fn has_consensus(&self, pubkey: &BlsPublicKey) -> bool { self.consensus_signers.contains_key(pubkey) } pub fn has_proxy(&self, pubkey: &[u8]) -> bool { - self.proxy_signers.find(pubkey).is_some() + self.proxy_signers.find_pubkey(pubkey).is_some() } pub fn get_delegation( &self, pubkey: &[u8], ) -> Result { - let proxy = self.find_proxy(pubkey) + let proxy = self + .find_proxy(pubkey) .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; Ok(proxy.delegation()) @@ -189,7 +206,7 @@ impl SigningManager { #[cfg(test)] mod tests { - use cb_common::signature::{compute_signing_root, verify_signed_builder_message}; + use cb_common::signature::compute_signing_root; use lazy_static::lazy_static; use tree_hash::Hash256; @@ -215,8 +232,10 @@ mod tests { async fn test_proxy_key_is_valid_proxy_for_consensus_key() { let (mut signing_manager, consensus_pk) = init_signing_manager(); - let signed_delegation = - signing_manager.create_proxy::(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); + let signed_delegation = signing_manager + .create_proxy::(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); let validation_result = signed_delegation.validate(*CHAIN); @@ -235,8 +254,10 @@ mod tests { async fn test_tampered_proxy_key_is_invalid() { let (mut signing_manager, consensus_pk) = init_signing_manager(); - let mut signed_delegation = - signing_manager.create_proxy::(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); + let mut signed_delegation = signing_manager + .create_proxy::(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); let m = &mut signed_delegation.signature.0[0]; (*m, _) = m.overflowing_add(1); @@ -250,8 +271,10 @@ mod tests { async fn test_proxy_key_signs_message() { let (mut signing_manager, consensus_pk) = init_signing_manager(); - let signed_delegation = - signing_manager.create_proxy::(MODULE_ID.clone(), consensus_pk.clone()).await.unwrap(); + let signed_delegation = signing_manager + .create_proxy::(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); let proxy_pk = signed_delegation.message.proxy; let data_root = Hash256::random(); diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 4984b80c..2232033d 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -10,15 +10,12 @@ use axum::{ }; use axum_extra::TypedHeader; use bimap::BiHashMap; -use blst::min_pk::SecretKey as BlsSecretKey; use cb_common::{ commit::{ client::GetPubkeysResponse, constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, request::{GenerateProxyRequest, SignRequest}, - }, - config::StartSignerConfig, - types::{Jwt, ModuleId}, + }, config::StartSignerConfig, signer::{BlsSecretKey, EcdsaSecretKey}, types::{Jwt, ModuleId} }; use eyre::{Result, WrapErr}; use headers::{authorization::Bearer, Authorization}; @@ -152,10 +149,17 @@ async fn handle_generate_proxy( use cb_common::commit::request::EncryptionScheme; let proxy_delegation = match request.scheme { - EncryptionScheme::Bls => signing_manager.create_proxy::(module_id, request.consensus_pubkey).await?, - EncryptionScheme::Ecdsa => todo!("Not yet implemented"), //signing_manager.create_proxy::(module_id, request.consensus_pubkey).await?,, + EncryptionScheme::Bls => { + signing_manager + .create_proxy::(module_id, request.consensus_pubkey) + .await? + } + EncryptionScheme::Ecdsa => { + signing_manager + .create_proxy::(module_id, request.consensus_pubkey) + .await? + } }; - Ok((StatusCode::OK, Json(proxy_delegation)).into_response()) } diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index 360ec9dc..50dacdec 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use alloy::rpc::types::beacon::BlsPublicKey; use commit::request::GenerateProxyRequest; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; @@ -47,9 +47,9 @@ impl DaCommitService { let pubkey = pubkeys.consensus.first().ok_or_eyre("no key available")?; info!("Registered validator {pubkey}"); - let request = GenerateProxyRequest::new(*pubkey, commit::request::EncryptionScheme::Bls); + let request = GenerateProxyRequest::new(*pubkey, commit::request::EncryptionScheme::Ecdsa); let proxy_delegation = self.config.signer_client.generate_proxy_key(&request).await?; - info!("Obtained a proxy delegation {proxy_delegation:#?}"); + info!("Obtained a proxy delegation:\n{proxy_delegation}"); let mut data = 0; @@ -60,11 +60,15 @@ impl DaCommitService { } } - pub async fn send_request(&self, data: u64, pubkey: BlsPublicKey, proxy_delegation: SignedProxyDelegation) -> Result<()> { + pub async fn send_request( + &self, + data: u64, + pubkey: BlsPublicKey, + proxy_delegation: SignedProxyDelegation, + ) -> Result<()> { let datagram = Datagram { data }; - let request = SignRequest::builder(pubkey.to_vec()) - .with_msg(&datagram); + let request = SignRequest::builder(pubkey.to_vec()).with_msg(&datagram); let signature = self.config.signer_client.request_signature(&request); let proxy_request = SignRequest::builder(proxy_delegation.message.proxy.as_ref().to_vec()) @@ -77,8 +81,8 @@ impl DaCommitService { (res.0?, res.1?) }; - info!("Proposer commitment (consensus): {}", pretty_print_sig(signature)); - info!("Proposer commitment (proxy): {}", pretty_print_sig(proxy_signature)); + info!("Proposer commitment (consensus): {}", signature); + info!("Proposer commitment (proxy): {}", proxy_signature); SIG_RECEIVED_COUNTER.inc(); @@ -117,7 +121,3 @@ async fn main() -> Result<()> { } Ok(()) } - -fn pretty_print_sig(sig: BlsSignature) -> String { - format!("{}..", &sig.to_string()[..16]) -} From a5450b6443863f95ba2c7ece407ed68b8d73656a Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 14 Aug 2024 10:49:20 +0300 Subject: [PATCH 03/37] chore(clippy): infallible instead of fallible conversions --- crates/common/src/signer/mod.rs | 5 ++++- crates/common/src/signer/schemes/ecdsa.rs | 6 ++---- crates/signer/src/manager.rs | 7 +------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 20455c19..657e2db3 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -1,4 +1,7 @@ -use std::{error::Error, fmt::{self, LowerHex}}; +use std::{ + error::Error, + fmt::{self, LowerHex}, +}; use eyre::{Context, Result}; use serde::{Deserialize, Serialize}; diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 2ec3656d..f213dfc3 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -145,7 +145,7 @@ impl TryFrom> for EcdsaPublicKey { impl From for EcdsaPublicKey { fn from(value: EcdsaCompressedKey) -> Self { - Self::new_from_bytes_infallible(value.try_into().unwrap()) + Self::new_from_bytes_infallible(value.into()) } } @@ -156,9 +156,7 @@ struct JSONEcdsaPublicKey { impl From for EcdsaPublicKey { fn from(value: JSONEcdsaPublicKey) -> Self { - // TODO(David): think about fallible decoding of invalid keys. - // This would incorrectly panic. - Self::try_from(value.encoded).expect("Encoded ECDSA key must be valid.") + Self::from(value.encoded) } } diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 9d8b47f1..08035613 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -13,6 +13,7 @@ use tree_hash::TreeHash; use crate::error::SignerModuleError; +#[derive(Default)] struct ProxySigners { bls_signers: HashMap, ProxySigner>, ecdsa_signers: HashMap, ProxySigner>, @@ -60,12 +61,6 @@ impl<'a> ProxySigners { } } -impl Default for ProxySigners { - fn default() -> Self { - Self { bls_signers: Default::default(), ecdsa_signers: Default::default() } - } -} - trait GetProxySigner { fn get_proxy_signer(&self, pk: &PubKey) -> Option<&ProxySigner>; } From a9ff828488ee51a016cc4d405340b302bbada16b Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 14 Aug 2024 13:57:00 +0300 Subject: [PATCH 04/37] chore(signer): resolve TODOs - simplify internals of `EcdsaPublicKey`: no need to store the actual abstract object, just its compressed encoding - we construct it when needed - extract verification logic into a separate trait `Verifier`, not in `SecretKey` - remove some (already) obsolete utility functions from `signature.rs` --- crates/common/src/commit/request.rs | 2 - crates/common/src/signature.rs | 57 ++++----------- crates/common/src/signer/mod.rs | 31 ++++---- crates/common/src/signer/schemes/bls.rs | 32 ++++++--- crates/common/src/signer/schemes/ecdsa.rs | 86 ++++++----------------- crates/common/src/signer/signers.rs | 11 --- crates/signer/src/service.rs | 5 +- 7 files changed, 80 insertions(+), 144 deletions(-) diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index c6ccd80f..3e037d66 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -54,8 +54,6 @@ impl fmt::Display for SignedProxyDelegation { // generalisation) and avoid the is_proxy flag #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignRequest { - // TODO(David): Vec might not be the most memory inefficient, think about something on the - // stack pub pubkey: Vec, pub is_proxy: bool, pub object_root: [u8; 32], diff --git a/crates/common/src/signature.rs b/crates/common/src/signature.rs index 762e0bcf..8b61a945 100644 --- a/crates/common/src/signature.rs +++ b/crates/common/src/signature.rs @@ -1,8 +1,4 @@ -use alloy::rpc::types::beacon::{constants::BLS_DST_SIG, BlsPublicKey, BlsSignature}; -use blst::{ - min_pk::{PublicKey, SecretKey as BlsSecretKey, Signature}, - BLST_ERROR, -}; +use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; @@ -10,52 +6,29 @@ use tree_hash_derive::TreeHash; use crate::{ constants::{APPLICATION_BUILDER_DOMAIN, GENESIS_VALIDATORS_ROOT}, error::BlstErrorWrapper, - signer::SecretKey, + signer::{SecretKey, Verifier}, types::Chain, - utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}, }; -// TODO(David): Think about removing -pub fn verify_signature( - pubkey: &BlsPublicKey, - msg: &[u8], - signature: &BlsSignature, -) -> Result<(), BlstErrorWrapper> { - let pubkey: PublicKey = alloy_pubkey_to_blst(pubkey)?; - let signature: Signature = alloy_sig_to_blst(signature)?; - - let res = signature.verify(true, msg, BLS_DST_SIG, &[], &pubkey, true); - if res == BLST_ERROR::BLST_SUCCESS { - Ok(()) - } else { - Err(res.into()) +pub fn compute_signing_root(object_root: [u8; 32], signing_domain: [u8; 32]) -> [u8; 32] { + #[derive(Default, Debug, Encode, Decode, TreeHash)] + struct SigningData { + object_root: [u8; 32], + signing_domain: [u8; 32], } -} - -pub fn sign_message(secret_key: &BlsSecretKey, msg: &[u8]) -> BlsSignature { - let signature = secret_key.sign(msg, BLS_DST_SIG, &[]).to_bytes(); - BlsSignature::from_slice(&signature) -} -#[derive(Default, Debug, Encode, Decode, TreeHash)] -struct SigningData { - object_root: [u8; 32], - signing_domain: [u8; 32], -} - -pub fn compute_signing_root(object_root: [u8; 32], signing_domain: [u8; 32]) -> [u8; 32] { let signing_data = SigningData { object_root, signing_domain }; signing_data.tree_hash_root().0 } -#[derive(Debug, Encode, Decode, TreeHash)] -struct ForkData { - fork_version: [u8; 4], - genesis_validators_root: [u8; 32], -} - #[allow(dead_code)] fn compute_builder_domain(chain: Chain) -> [u8; 32] { + #[derive(Debug, Encode, Decode, TreeHash)] + struct ForkData { + fork_version: [u8; 4], + genesis_validators_root: [u8; 32], + } + let mut domain = [0u8; 32]; domain[..4].copy_from_slice(&APPLICATION_BUILDER_DOMAIN); @@ -77,7 +50,7 @@ pub fn verify_signed_builder_message( let domain = chain.builder_domain(); let signing_root = compute_signing_root(msg.tree_hash_root().0, domain); - verify_signature(pubkey, &signing_root, signature) + pubkey.verify_signature(&signing_root, signature) } pub fn sign_builder_message( @@ -88,7 +61,6 @@ pub fn sign_builder_message( sign_builder_root(chain, secret_key, msg.tree_hash_root().0) } -// TODO(David): This utility function seems unnecessary pub fn sign_builder_root( chain: Chain, secret_key: &T, @@ -97,7 +69,6 @@ pub fn sign_builder_root( let domain = chain.builder_domain(); let signing_root = compute_signing_root(object_root, domain); secret_key.sign(&signing_root) - // sign_message(secret_key, &signing_root) } #[cfg(test)] diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 657e2db3..dd4bb646 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -3,7 +3,7 @@ use std::{ fmt::{self, LowerHex}, }; -use eyre::{Context, Result}; +use eyre::Context; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; @@ -17,24 +17,30 @@ pub use signers::{GenericProxySigner, ProxySigner, Signer}; pub type PubKey = ::PubKey; pub trait SecretKey { - type PubKey: AsRef<[u8]> + Clone; + type PubKey: AsRef<[u8]> + Clone + Verifier; type Signature: AsRef<[u8]> + Clone; - type VerificationError: Error; fn new_random() -> Self; - fn new_from_bytes(bytes: &[u8]) -> Result + fn new_from_bytes(bytes: &[u8]) -> eyre::Result where Self: Sized; fn pubkey(&self) -> Self::PubKey; - fn sign(&self, msg: &[u8; 32]) -> Self::Signature; + fn sign(&self, msg: &[u8]) -> Self::Signature; fn sign_msg(&self, msg: &impl TreeHash) -> Self::Signature { self.sign(&msg.tree_hash_root().0) } +} + +pub trait Verifier +where + T: ?Sized, +{ + type VerificationError: Error; fn verify_signature( - pubkey: &Self::PubKey, + &self, msg: &[u8], - signature: &Self::Signature, + signature: &T::Signature, ) -> Result<(), Self::VerificationError>; } @@ -49,14 +55,13 @@ pub enum GenericPubkey { impl GenericPubkey { pub fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { match self { - GenericPubkey::Bls(bls_pubkey) => Ok(::verify_signature( - bls_pubkey, - msg, - signature.try_into().context("Invalid signature length for BLS.")?, - )?), + GenericPubkey::Bls(bls_pubkey) => { + let sig = signature.try_into().context("Invalid signature length for BLS.")?; + Ok(bls_pubkey.verify_signature(msg, &sig)?) + } GenericPubkey::Ecdsa(ecdsa_pubkey) => { let sig = signature.try_into().context("Invalid signature for ECDSA.")?; - Ok(::verify_signature(ecdsa_pubkey, msg, &sig)?) + Ok(ecdsa_pubkey.verify_signature(msg, &sig)?) } } } diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index b296c500..a190bcd2 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -1,9 +1,9 @@ -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use alloy::rpc::types::beacon::{constants::BLS_DST_SIG, BlsPublicKey, BlsSignature}; +use blst::BLST_ERROR; use crate::{ error::BlstErrorWrapper, - signature::{sign_message, verify_signature}, - signer::{GenericPubkey, SecretKey}, + signer::{GenericPubkey, PubKey, SecretKey, Verifier}, utils::blst_pubkey_to_alloy, }; @@ -12,7 +12,6 @@ pub type BlsSecretKey = blst::min_pk::SecretKey; impl SecretKey for BlsSecretKey { type PubKey = BlsPublicKey; type Signature = BlsSignature; - type VerificationError = BlstErrorWrapper; fn new_random() -> Self { use rand::RngCore; @@ -36,16 +35,31 @@ impl SecretKey for BlsSecretKey { blst_pubkey_to_alloy(&self.sk_to_pk()) } - fn sign(&self, msg: &[u8; 32]) -> Self::Signature { - sign_message(self, msg) + fn sign(&self, msg: &[u8]) -> Self::Signature { + let signature = self.sign(msg, BLS_DST_SIG, &[]).to_bytes(); + BlsSignature::from_slice(&signature) } +} + +impl Verifier for PubKey { + type VerificationError = BlstErrorWrapper; fn verify_signature( - pubkey: &Self::PubKey, + &self, msg: &[u8], - signature: &Self::Signature, + signature: &::Signature, ) -> Result<(), Self::VerificationError> { - verify_signature(pubkey, msg, signature) + use crate::utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}; + + let pubkey = alloy_pubkey_to_blst(self)?; + let signature = alloy_sig_to_blst(signature)?; + + let res = signature.verify(true, msg, BLS_DST_SIG, &[], &pubkey, true); + if res == BLST_ERROR::BLST_SUCCESS { + Ok(()) + } else { + Err(res.into()) + } } } diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index f213dfc3..1803da19 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -3,56 +3,26 @@ use std::hash::Hash; use derive_more::derive::Into; use eyre::Result; use generic_array::GenericArray; -use k256::{ - ecdsa::{Signature as EcdsaSignatureInner, VerifyingKey as EcdsaPublicKeyInner}, - AffinePoint, -}; +use k256::ecdsa::{Signature as EcdsaSignatureInner, VerifyingKey as EcdsaPublicKeyInner}; use serde::{Deserialize, Serialize}; use ssz_types::{typenum::U33, FixedVector}; use tree_hash::TreeHash; -use crate::signer::{GenericPubkey, PubKey, SecretKey}; +use crate::signer::{GenericPubkey, PubKey, SecretKey, Verifier}; pub type EcdsaSecretKey = k256::ecdsa::SigningKey; -type EcdsaCompressedKey = GenericArray; + +type CompressedPublicKey = GenericArray; #[derive(Debug, Clone, Copy, Into, Serialize, Deserialize, PartialEq, Eq)] -#[serde(from = "JSONEcdsaPublicKey")] pub struct EcdsaPublicKey { - #[serde(skip_serializing)] - inner: EcdsaPublicKeyInner, - encoded: EcdsaCompressedKey, + 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; - - pub fn new(inner: EcdsaPublicKeyInner) -> Self { - use elliptic_curve::sec1::ToEncodedPoint; - - let affine: &AffinePoint = inner.as_ref(); - let encoded: [u8; Self::SIZE] = - affine.to_encoded_point(true).as_bytes().try_into().unwrap(); - - let encoded = GenericArray::from_array(encoded); - - EcdsaPublicKey { inner, encoded } - } - - pub fn new_from_bytes(encoded: Vec) -> Result { - let inner = EcdsaPublicKeyInner::from_sec1_bytes(&encoded)?; - let encoded = GenericArray::from_array::<{ Self::SIZE }>(encoded.try_into().unwrap()); - Ok(Self { inner, encoded }) - } - - fn new_from_bytes_infallible(encoded: [u8; Self::SIZE]) -> Self { - Self { - inner: EcdsaPublicKeyInner::from_sec1_bytes(&encoded).unwrap(), - encoded: GenericArray::from_array(encoded), - } - } } impl Hash for EcdsaPublicKey { @@ -131,38 +101,21 @@ impl ssz::Decode for EcdsaPublicKey { let mut fixed_array = [0_u8; Self::SIZE]; fixed_array.copy_from_slice(bytes); - Ok(EcdsaPublicKey::new_from_bytes_infallible(fixed_array)) + Ok(EcdsaPublicKey { encoded: fixed_array.into() }) } } -impl TryFrom> for EcdsaPublicKey { - type Error = k256::ecdsa::Error; - - fn try_from(value: Vec) -> std::result::Result { - Self::new_from_bytes(value) - } -} - -impl From for EcdsaPublicKey { - fn from(value: EcdsaCompressedKey) -> Self { - Self::new_from_bytes_infallible(value.into()) - } -} - -#[derive(Deserialize)] -struct JSONEcdsaPublicKey { - encoded: EcdsaCompressedKey, -} - -impl From for EcdsaPublicKey { - fn from(value: JSONEcdsaPublicKey) -> Self { - Self::from(value.encoded) +impl From for EcdsaPublicKey { + fn from(value: CompressedPublicKey) -> Self { + Self { encoded: value } } } impl From for EcdsaPublicKey { fn from(value: EcdsaPublicKeyInner) -> Self { - EcdsaPublicKey::new(value) + let encoded: [u8; Self::SIZE] = value.to_encoded_point(true).as_bytes().try_into().unwrap(); + + EcdsaPublicKey { encoded: encoded.into() } } } @@ -210,8 +163,6 @@ impl SecretKey for EcdsaSecretKey { type Signature = EcdsaSignature; - type VerificationError = k256::ecdsa::Error; - fn new_random() -> Self { EcdsaSecretKey::random(&mut rand::thread_rng()) } @@ -227,17 +178,22 @@ impl SecretKey for EcdsaSecretKey { EcdsaPublicKeyInner::from(self).into() } - fn sign(&self, msg: &[u8; 32]) -> Self::Signature { + fn sign(&self, msg: &[u8]) -> Self::Signature { k256::ecdsa::signature::Signer::::sign(self, msg).into() } +} + +impl Verifier for PubKey { + type VerificationError = k256::ecdsa::Error; fn verify_signature( - pubkey: &Self::PubKey, + &self, msg: &[u8], - signature: &Self::Signature, + signature: &::Signature, ) -> Result<(), Self::VerificationError> { use k256::ecdsa::signature::Verifier; - pubkey.inner.verify(msg, &signature.inner) + let ecdsa_pubkey = EcdsaPublicKeyInner::from_sec1_bytes(&self.encoded)?; + ecdsa_pubkey.verify(msg, &signature.inner) } } diff --git a/crates/common/src/signer/signers.rs b/crates/common/src/signer/signers.rs index 70e08db4..ebab746d 100644 --- a/crates/common/src/signer/signers.rs +++ b/crates/common/src/signer/signers.rs @@ -38,17 +38,6 @@ impl Signer { pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> T::Signature { self.sign(chain, msg.tree_hash_root().0).await } - - // TODO(David): - // This doesn't need to be here. A separate `Verifier` might make sense. - // Left here for now (for convenience). - pub async fn verify_signature( - pubkey: &T::PubKey, - msg: &[u8], - signature: &T::Signature, - ) -> Result<(), T::VerificationError> { - T::verify_signature(pubkey, msg, signature) - } } // For extra safety and to avoid risking signing malicious messages, use a proxy diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 164165b3..b60dd667 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -15,7 +15,10 @@ use cb_common::{ client::GetPubkeysResponse, constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, request::{GenerateProxyRequest, SignRequest}, - }, config::StartSignerConfig, signer::{BlsSecretKey, EcdsaSecretKey}, types::{Jwt, ModuleId} + }, + config::StartSignerConfig, + signer::{BlsSecretKey, EcdsaSecretKey}, + types::{Jwt, ModuleId}, }; use eyre::{Result, WrapErr}; use headers::{authorization::Bearer, Authorization}; From 4e96742fd2ec617955a3868c8e1f40cbbb95df9f Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 14 Aug 2024 16:11:32 +0300 Subject: [PATCH 05/37] refactor(signer)!: move around - move `GenericProxySigner` from common to manager - simplify inners of `EcdsaSignature` --- Cargo.toml | 8 +-- crates/common/src/signer/mod.rs | 2 +- crates/common/src/signer/schemes/ecdsa.rs | 28 +++++----- crates/common/src/signer/signers.rs | 66 +---------------------- crates/signer/src/manager.rs | 62 +++++++++++++++++++-- 5 files changed, 78 insertions(+), 88 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd44d3b9..c74c858e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ cb-signer = { path = "crates/signer" } alloy = { version = "0.2.0", features = ["rpc-types-beacon"] } ethereum_ssz = "0.5" ethereum_ssz_derive = "0.5.3" -ssz_types = "0.5" +ssz_types = "0.6.0" ethereum_serde_utils = "0.5.2" ethereum-types = "0.14.1" @@ -59,11 +59,11 @@ prometheus = "0.13.4" # crypto blst = "0.3.11" -tree_hash = "0.5" -tree_hash_derive = "0.5" +tree_hash = "0.6.0" +tree_hash_derive = "0.6.0" eth2_keystore = { git = "https://github.com/sigp/lighthouse", rev = "9e12c21f268c80a3f002ae0ca27477f9f512eb6f" } elliptic-curve = { version = "0.13", features = ["serde"] } -generic-array = { version = "1.1.0", features = ["serde"] } +generic-array = { version = "0.14.7", features = ["serde"] } k256 = "0.13" # docker diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index dd4bb646..08cf0ee7 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -12,7 +12,7 @@ pub mod schemes; pub mod signers; pub use schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey}; -pub use signers::{GenericProxySigner, ProxySigner, Signer}; +pub use signers::Signer; pub type PubKey = ::PubKey; diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 1803da19..1edd9a4b 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -1,11 +1,14 @@ use std::hash::Hash; -use derive_more::derive::Into; +use derive_more::derive::{Deref, Into}; use eyre::Result; use generic_array::GenericArray; use k256::ecdsa::{Signature as EcdsaSignatureInner, VerifyingKey as EcdsaPublicKeyInner}; use serde::{Deserialize, Serialize}; -use ssz_types::{typenum::U33, FixedVector}; +use ssz_types::{ + typenum::{U33, U64}, + FixedVector, +}; use tree_hash::TreeHash; use crate::signer::{GenericPubkey, PubKey, SecretKey, Verifier}; @@ -14,7 +17,7 @@ pub type EcdsaSecretKey = k256::ecdsa::SigningKey; type CompressedPublicKey = GenericArray; -#[derive(Debug, Clone, Copy, Into, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Into, Serialize, Deserialize, PartialEq, Eq, Deref)] pub struct EcdsaPublicKey { encoded: CompressedPublicKey, } @@ -50,7 +53,7 @@ impl TreeHash for EcdsaPublicKey { // 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.into_array::<{ Self::SIZE }>().to_vec(); + let vec = self.encoded.to_vec(); FixedVector::::from(vec).tree_hash_root() } } @@ -125,22 +128,14 @@ impl AsRef<[u8]> for EcdsaPublicKey { } } -#[derive(Clone)] +#[derive(Clone, Deref)] pub struct EcdsaSignature { - inner: EcdsaSignatureInner, - // TODO(David): Maybe prefer `GenericArray` for explicit fixed size. - pub(in crate::signer) encoded: Vec, -} - -impl EcdsaSignature { - pub fn new(inner: EcdsaSignatureInner) -> Self { - Self { inner, encoded: inner.to_vec() } - } + encoded: GenericArray, } impl From for EcdsaSignature { fn from(value: EcdsaSignatureInner) -> Self { - Self::new(value) + Self { encoded: value.to_bytes() } } } @@ -193,7 +188,8 @@ impl Verifier for PubKey { ) -> Result<(), Self::VerificationError> { use k256::ecdsa::signature::Verifier; let ecdsa_pubkey = EcdsaPublicKeyInner::from_sec1_bytes(&self.encoded)?; - ecdsa_pubkey.verify(msg, &signature.inner) + let ecdsa_sig = EcdsaSignatureInner::from_bytes(&signature)?; + ecdsa_pubkey.verify(msg, &ecdsa_sig) } } diff --git a/crates/common/src/signer/signers.rs b/crates/common/src/signer/signers.rs index ebab746d..850c2128 100644 --- a/crates/common/src/signer/signers.rs +++ b/crates/common/src/signer/signers.rs @@ -1,12 +1,8 @@ -use derive_more::derive::{Deref, From}; use eyre::Result; use tree_hash::TreeHash; -use super::{ - schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey}, - GenericPubkey, SecretKey, -}; -use crate::{commit::request::SignedProxyDelegation, signature::sign_builder_root, types::Chain}; +use super::{schemes::bls::BlsSecretKey, SecretKey}; +use crate::{signature::sign_builder_root, types::Chain}; // TODO(David): remove the default type arg #[derive(Clone)] @@ -39,61 +35,3 @@ impl Signer { self.sign(chain, msg.tree_hash_root().0).await } } - -// 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, it also signs a ProxyDelegation associating the new keypair -// with its consensus pubkey When a new commit module starts, pass the -// ProxyDelegation msg and then sign all future commit messages with the proxy -// key for slashing the faulty message + proxy delegation can be used -// Signed using builder domain - -#[derive(Clone, Deref)] -pub struct ProxySigner { - #[deref] - signer: Signer, - delegation: SignedProxyDelegation, -} - -impl ProxySigner { - pub fn new(signer: Signer, delegation: SignedProxyDelegation) -> Self { - Self { signer, delegation } - } -} - -#[derive(From)] -pub enum GenericProxySigner { - Bls(ProxySigner), - Ecdsa(ProxySigner), -} - -impl GenericProxySigner { - pub fn pubkey(&self) -> GenericPubkey { - match self { - GenericProxySigner::Bls(proxy_signer) => GenericPubkey::Bls(proxy_signer.pubkey()), - GenericProxySigner::Ecdsa(proxy_signer) => GenericPubkey::Ecdsa(proxy_signer.pubkey()), - } - } - - pub fn delegation(&self) -> SignedProxyDelegation { - match self { - GenericProxySigner::Bls(proxy_signer) => proxy_signer.delegation, - GenericProxySigner::Ecdsa(proxy_signer) => proxy_signer.delegation, - } - } - - pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> Vec { - match self { - GenericProxySigner::Bls(proxy_signer) => { - proxy_signer.sign(chain, object_root).await.to_vec() - } - GenericProxySigner::Ecdsa(proxy_signer) => { - proxy_signer.sign(chain, object_root).await.encoded.to_vec() - } - } - } - - pub async fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { - self.pubkey().verify_signature(msg, signature) - } -} diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 08035613..c5e0ee92 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -4,15 +4,71 @@ use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use blst::min_pk::SecretKey as BlsSecretKey; use cb_common::{ commit::request::{ProxyDelegation, SignedProxyDelegation}, - signer::{ - EcdsaSecretKey, GenericProxySigner, GenericPubkey, ProxySigner, PubKey, SecretKey, Signer, - }, + signer::{EcdsaSecretKey, GenericPubkey, PubKey, SecretKey, Signer}, types::{Chain, ModuleId}, }; +use derive_more::derive::{Deref, From}; use tree_hash::TreeHash; use crate::error::SignerModuleError; +// 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, it also signs a ProxyDelegation associating the new keypair +// with its consensus pubkey When a new commit module starts, pass the +// ProxyDelegation msg and then sign all future commit messages with the proxy +// key for slashing the faulty message + proxy delegation can be used +// Signed using builder domain +#[derive(Clone, Deref)] +pub struct ProxySigner { + #[deref] + signer: Signer, + delegation: SignedProxyDelegation, +} + +impl ProxySigner { + pub fn new(signer: Signer, delegation: SignedProxyDelegation) -> Self { + Self { signer, delegation } + } +} + +#[derive(From)] +pub enum GenericProxySigner { + Bls(ProxySigner), + Ecdsa(ProxySigner), +} + +impl GenericProxySigner { + pub fn pubkey(&self) -> GenericPubkey { + match self { + GenericProxySigner::Bls(proxy_signer) => GenericPubkey::Bls(proxy_signer.pubkey()), + GenericProxySigner::Ecdsa(proxy_signer) => GenericPubkey::Ecdsa(proxy_signer.pubkey()), + } + } + + pub fn delegation(&self) -> SignedProxyDelegation { + match self { + GenericProxySigner::Bls(proxy_signer) => proxy_signer.delegation, + GenericProxySigner::Ecdsa(proxy_signer) => proxy_signer.delegation, + } + } + + pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> Vec { + match self { + GenericProxySigner::Bls(proxy_signer) => { + proxy_signer.sign(chain, object_root).await.to_vec() + } + GenericProxySigner::Ecdsa(proxy_signer) => { + proxy_signer.sign(chain, object_root).await.to_vec() + } + } + } + + pub async fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { + self.pubkey().verify_signature(msg, signature) + } +} + #[derive(Default)] struct ProxySigners { bls_signers: HashMap, ProxySigner>, From 4b283e221a90022912e6d8e246c05963f0940290 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 14 Aug 2024 16:19:31 +0300 Subject: [PATCH 06/37] chore(signer)!: rename associated type and type alias --- crates/common/src/signer/mod.rs | 10 +++++----- crates/common/src/signer/schemes/bls.rs | 8 ++++---- crates/common/src/signer/schemes/ecdsa.rs | 10 +++++----- crates/common/src/signer/signers.rs | 2 +- crates/signer/src/manager.rs | 18 +++++++++--------- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 08cf0ee7..becb4b40 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -14,17 +14,17 @@ pub mod signers; pub use schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey}; pub use signers::Signer; -pub type PubKey = ::PubKey; +pub type Pubkey = ::PublicKey; pub trait SecretKey { - type PubKey: AsRef<[u8]> + Clone + Verifier; + type PublicKey: AsRef<[u8]> + Clone + Verifier; type Signature: AsRef<[u8]> + Clone; fn new_random() -> Self; fn new_from_bytes(bytes: &[u8]) -> eyre::Result where Self: Sized; - fn pubkey(&self) -> Self::PubKey; + fn pubkey(&self) -> Self::PublicKey; fn sign(&self, msg: &[u8]) -> Self::Signature; fn sign_msg(&self, msg: &impl TreeHash) -> Self::Signature { self.sign(&msg.tree_hash_root().0) @@ -48,8 +48,8 @@ where #[serde(untagged)] #[ssz(enum_behaviour = "transparent")] pub enum GenericPubkey { - Bls(PubKey), - Ecdsa(PubKey), + Bls(Pubkey), + Ecdsa(Pubkey), } impl GenericPubkey { diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index a190bcd2..f60a12ad 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -3,14 +3,14 @@ use blst::BLST_ERROR; use crate::{ error::BlstErrorWrapper, - signer::{GenericPubkey, PubKey, SecretKey, Verifier}, + signer::{GenericPubkey, Pubkey, SecretKey, Verifier}, utils::blst_pubkey_to_alloy, }; pub type BlsSecretKey = blst::min_pk::SecretKey; impl SecretKey for BlsSecretKey { - type PubKey = BlsPublicKey; + type PublicKey = BlsPublicKey; type Signature = BlsSignature; fn new_random() -> Self { @@ -31,7 +31,7 @@ impl SecretKey for BlsSecretKey { Ok(BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?) } - fn pubkey(&self) -> Self::PubKey { + fn pubkey(&self) -> Self::PublicKey { blst_pubkey_to_alloy(&self.sk_to_pk()) } @@ -41,7 +41,7 @@ impl SecretKey for BlsSecretKey { } } -impl Verifier for PubKey { +impl Verifier for Pubkey { type VerificationError = BlstErrorWrapper; fn verify_signature( diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 1edd9a4b..b0ab8bbc 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -11,7 +11,7 @@ use ssz_types::{ }; use tree_hash::TreeHash; -use crate::signer::{GenericPubkey, PubKey, SecretKey, Verifier}; +use crate::signer::{GenericPubkey, Pubkey, SecretKey, Verifier}; pub type EcdsaSecretKey = k256::ecdsa::SigningKey; @@ -154,7 +154,7 @@ impl TryFrom<&[u8]> for EcdsaSignature { } impl SecretKey for EcdsaSecretKey { - type PubKey = EcdsaPublicKey; + type PublicKey = EcdsaPublicKey; type Signature = EcdsaSignature; @@ -169,7 +169,7 @@ impl SecretKey for EcdsaSecretKey { Ok(EcdsaSecretKey::from_slice(bytes)?) } - fn pubkey(&self) -> Self::PubKey { + fn pubkey(&self) -> Self::PublicKey { EcdsaPublicKeyInner::from(self).into() } @@ -178,7 +178,7 @@ impl SecretKey for EcdsaSecretKey { } } -impl Verifier for PubKey { +impl Verifier for Pubkey { type VerificationError = k256::ecdsa::Error; fn verify_signature( @@ -194,7 +194,7 @@ impl Verifier for PubKey { } impl From for GenericPubkey { - fn from(value: PubKey) -> Self { + fn from(value: Pubkey) -> Self { GenericPubkey::Ecdsa(value) } } diff --git a/crates/common/src/signer/signers.rs b/crates/common/src/signer/signers.rs index 850c2128..81028ca9 100644 --- a/crates/common/src/signer/signers.rs +++ b/crates/common/src/signer/signers.rs @@ -19,7 +19,7 @@ impl Signer { T::new_from_bytes(bytes).map(Self::Local) } - pub fn pubkey(&self) -> T::PubKey { + pub fn pubkey(&self) -> T::PublicKey { match self { Signer::Local(secret) => secret.pubkey(), } diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index c5e0ee92..43df29ae 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -4,7 +4,7 @@ use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use blst::min_pk::SecretKey as BlsSecretKey; use cb_common::{ commit::request::{ProxyDelegation, SignedProxyDelegation}, - signer::{EcdsaSecretKey, GenericPubkey, PubKey, SecretKey, Signer}, + signer::{EcdsaSecretKey, GenericPubkey, Pubkey, SecretKey, Signer}, types::{Chain, ModuleId}, }; use derive_more::derive::{Deref, From}; @@ -71,8 +71,8 @@ impl GenericProxySigner { #[derive(Default)] struct ProxySigners { - bls_signers: HashMap, ProxySigner>, - ecdsa_signers: HashMap, ProxySigner>, + bls_signers: HashMap, ProxySigner>, + ecdsa_signers: HashMap, ProxySigner>, } impl<'a> ProxySigners { @@ -102,12 +102,12 @@ impl<'a> ProxySigners { pub fn find_pubkey(&'a self, pubkey: &[u8]) -> Option { fn find_typed<'a, T>( - keys: impl IntoIterator>, + keys: impl IntoIterator>, pubkey: &[u8], ) -> Option where T: SecretKey, - PubKey: 'a + Into, + Pubkey: 'a + Into, { keys.into_iter().find(|x| x.as_ref() == pubkey).cloned().map(Into::into) } @@ -118,11 +118,11 @@ impl<'a> ProxySigners { } trait GetProxySigner { - fn get_proxy_signer(&self, pk: &PubKey) -> Option<&ProxySigner>; + fn get_proxy_signer(&self, pk: &Pubkey) -> Option<&ProxySigner>; } impl GetProxySigner for ProxySigners { - fn get_proxy_signer(&self, pk: &PubKey) -> Option<&ProxySigner> { + fn get_proxy_signer(&self, pk: &Pubkey) -> Option<&ProxySigner> { self.bls_signers.get(pk) } } @@ -130,7 +130,7 @@ impl GetProxySigner for ProxySigners { impl GetProxySigner for ProxySigners { fn get_proxy_signer( &self, - pk: &PubKey, + pk: &Pubkey, ) -> Option<&ProxySigner> { self.ecdsa_signers.get(pk) } @@ -171,7 +171,7 @@ impl SigningManager { delegator: BlsPublicKey, ) -> Result where - PubKey: Into, + Pubkey: Into, ProxySigner: Into, { let signer = Signer::::new_random(); From 9d70712a51d240a650e4d8c788bb54d1714836b6 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 14 Aug 2024 16:40:16 +0300 Subject: [PATCH 07/37] choer(signer): rename client `Signature` type --- crates/common/src/commit/client.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index c52997d3..d318a47d 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -70,7 +70,7 @@ impl SignerClient { pub async fn request_signature( &self, request: &SignRequest, - ) -> Result { + ) -> Result { let url = format!("{}{}", self.url, REQUEST_SIGNATURE_PATH); let res = self.client.post(&url).json(&request).send().await?; @@ -86,7 +86,7 @@ impl SignerClient { let signature: Vec = serde_json::from_slice(&response_bytes)?; - Ok(GenericSignature(signature)) + Ok(Signature(signature)) } pub async fn generate_proxy_key( @@ -113,10 +113,13 @@ impl SignerClient { } } -// TODO(David): Better naming -pub struct GenericSignature(Vec); +// NOTE(David): +// For now, this is a simple displayable wrapper around vec, serving as a +// client-side type. It can be further deliberated whether a separate +// client-side type is preferrable over re-using an SDK in `common::signer`. +pub struct Signature(Vec); -impl fmt::Display for GenericSignature { +impl fmt::Display for Signature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "0x")?; for byte in &self.0 { From 287da33504de4b4ea8a3b9cec6a1e8f01ad3c56b Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 14 Aug 2024 16:48:25 +0300 Subject: [PATCH 08/37] refactor(signer)!: remove default type parameter in `Signer` - define public alias `ConsensusSigner = Signerl` --- crates/common/src/loader.rs | 15 +++++++++------ crates/common/src/signer/mod.rs | 2 +- crates/common/src/signer/signers.rs | 7 ++++--- crates/signer/src/manager.rs | 6 +++--- tests/src/mock_relay.rs | 6 +++--- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/crates/common/src/loader.rs b/crates/common/src/loader.rs index cf5d453a..9e371edc 100644 --- a/crates/common/src/loader.rs +++ b/crates/common/src/loader.rs @@ -7,7 +7,7 @@ use serde::{de, Deserialize, Deserializer, Serialize}; use crate::{ config::{load_env_var, SIGNER_DIR_KEYS_ENV, SIGNER_DIR_SECRETS_ENV, SIGNER_KEYS_ENV}, - signer::Signer, + signer::{ConsensusSigner, Signer}, }; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -24,12 +24,12 @@ pub enum SignerLoader { } impl SignerLoader { - pub fn load_keys(self) -> eyre::Result> { + pub fn load_keys(self) -> eyre::Result> { // TODO: add flag to support also native loader self.load_from_env() } - pub fn load_from_env(self) -> eyre::Result> { + pub fn load_from_env(self) -> eyre::Result> { Ok(match self { SignerLoader::File { .. } => { let path = load_env_var(SIGNER_KEYS_ENV)?; @@ -40,7 +40,7 @@ impl SignerLoader { keys.into_iter() .map(|k| Signer::new_from_bytes(&k.secret_key)) - .collect::>>()? + .collect::>>()? } SignerLoader::ValidatorsDir { .. } => { // TODO: hacky way to load for now, we should support reading the @@ -71,7 +71,10 @@ impl<'de> Deserialize<'de> for FileKey { } } -fn load_secrets_and_keys(keys_path: String, secrets_path: String) -> eyre::Result> { +fn load_secrets_and_keys( + keys_path: String, + secrets_path: String, +) -> eyre::Result> { let entries = fs::read_dir(keys_path.clone())?; let mut signers = Vec::new(); @@ -98,7 +101,7 @@ fn load_secrets_and_keys(keys_path: String, secrets_path: String) -> eyre::Resul Ok(signers) } -fn load_one(ks_path: String, pw_path: String) -> eyre::Result { +fn load_one(ks_path: String, pw_path: String) -> eyre::Result { let keystore = Keystore::from_json_file(ks_path).map_err(|_| eyre!("failed reading json"))?; let password = fs::read(pw_path)?; let key = diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index becb4b40..4f1e8d36 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -12,7 +12,7 @@ pub mod schemes; pub mod signers; pub use schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey}; -pub use signers::Signer; +pub use signers::{ConsensusSigner, Signer}; pub type Pubkey = ::PublicKey; diff --git a/crates/common/src/signer/signers.rs b/crates/common/src/signer/signers.rs index 81028ca9..d58bc47f 100644 --- a/crates/common/src/signer/signers.rs +++ b/crates/common/src/signer/signers.rs @@ -1,12 +1,13 @@ use eyre::Result; use tree_hash::TreeHash; -use super::{schemes::bls::BlsSecretKey, SecretKey}; +use super::{BlsSecretKey, SecretKey}; use crate::{signature::sign_builder_root, types::Chain}; -// TODO(David): remove the default type arg +pub type ConsensusSigner = Signer; + #[derive(Clone)] -pub enum Signer { +pub enum Signer { Local(T), } diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 43df29ae..a5d23216 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -4,7 +4,7 @@ use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use blst::min_pk::SecretKey as BlsSecretKey; use cb_common::{ commit::request::{ProxyDelegation, SignedProxyDelegation}, - signer::{EcdsaSecretKey, GenericPubkey, Pubkey, SecretKey, Signer}, + signer::{ConsensusSigner, EcdsaSecretKey, GenericPubkey, Pubkey, SecretKey, Signer}, types::{Chain, ModuleId}, }; use derive_more::derive::{Deref, From}; @@ -138,7 +138,7 @@ impl GetProxySigner for ProxySigners { pub struct SigningManager { chain: Chain, - consensus_signers: HashMap, + consensus_signers: HashMap, proxy_signers: ProxySigners, // HashMap, ProxySigner>, // proxy_delegations: /// Map of module ids to their associated proxy pubkeys. @@ -157,7 +157,7 @@ impl SigningManager { } } - pub fn add_consensus_signer(&mut self, signer: Signer) { + pub fn add_consensus_signer(&mut self, signer: ConsensusSigner) { self.consensus_signers.insert(signer.pubkey(), signer); } diff --git a/tests/src/mock_relay.rs b/tests/src/mock_relay.rs index 86653806..79db092d 100644 --- a/tests/src/mock_relay.rs +++ b/tests/src/mock_relay.rs @@ -16,7 +16,7 @@ use cb_common::{ GetHeaderParams, GetHeaderReponse, SubmitBlindedBlockResponse, BUILDER_API_PATH, GET_HEADER_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, SUBMIT_BLOCK_PATH, }, - signer::Signer, + signer::ConsensusSigner, types::Chain, }; use tracing::debug; @@ -25,7 +25,7 @@ use tree_hash::TreeHash; pub struct MockRelayState { pub chain: Chain, pub get_header_delay_ms: u64, - pub signer: Signer, + pub signer: ConsensusSigner, received_get_header: Arc, received_get_status: Arc, received_register_validator: Arc, @@ -48,7 +48,7 @@ impl MockRelayState { } impl MockRelayState { - pub fn new(chain: Chain, signer: Signer, get_header_delay_ms: u64) -> Self { + pub fn new(chain: Chain, signer: ConsensusSigner, get_header_delay_ms: u64) -> Self { Self { chain, signer, From 431f1899d1c40232683bc717c33ea807db2e8a6b Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 14 Aug 2024 16:51:08 +0300 Subject: [PATCH 09/37] chore(clippy): remove unnecessary nested reference --- crates/common/src/signer/schemes/ecdsa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index b0ab8bbc..60d34ad6 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -188,7 +188,7 @@ impl Verifier for Pubkey { ) -> Result<(), Self::VerificationError> { use k256::ecdsa::signature::Verifier; let ecdsa_pubkey = EcdsaPublicKeyInner::from_sec1_bytes(&self.encoded)?; - let ecdsa_sig = EcdsaSignatureInner::from_bytes(&signature)?; + let ecdsa_sig = EcdsaSignatureInner::from_bytes(signature)?; ecdsa_pubkey.verify(msg, &ecdsa_sig) } } From 8041e0f76ebba52febdeb7c167f402a20570fd9e Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 15 Aug 2024 11:49:58 +0300 Subject: [PATCH 10/37] chore: remove commented code --- crates/signer/src/manager.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index a5d23216..ff9a6ec4 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -334,19 +334,11 @@ mod tests { let sig = signing_manager.sign_proxy(proxy_pk.as_ref(), data_root_bytes).await.unwrap(); // Verify signature - let domain = CHAIN.builder_domain(); let signing_root = compute_signing_root(data_root_bytes.tree_hash_root().0, domain); let validation_result = proxy_pk.verify_signature(&signing_root, &sig); - // verify_signed_builder_message( - // *CHAIN, - // &signed_delegation.message.proxy, - // &data_root_bytes, - // &sig, - // ); - assert!( validation_result.is_ok(), "Proxy keypair must produce valid signatures of messages." From 23437a24cf1a2ad8000fc716e237fca792639a4e Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 15 Aug 2024 11:50:59 +0300 Subject: [PATCH 11/37] refactor(signer/ecdsa): derive unnecessary trait impls --- crates/common/src/signer/schemes/ecdsa.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 60d34ad6..5b6bdbb6 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -1,6 +1,6 @@ use std::hash::Hash; -use derive_more::derive::{Deref, Into}; +use derive_more::derive::{Deref, From, Into}; use eyre::Result; use generic_array::GenericArray; use k256::ecdsa::{Signature as EcdsaSignatureInner, VerifyingKey as EcdsaPublicKeyInner}; @@ -17,7 +17,7 @@ pub type EcdsaSecretKey = k256::ecdsa::SigningKey; type CompressedPublicKey = GenericArray; -#[derive(Debug, Clone, Copy, Into, Serialize, Deserialize, PartialEq, Eq, Deref)] +#[derive(Debug, Clone, Copy, From, Into, Serialize, Deserialize, PartialEq, Eq, Deref, Hash)] pub struct EcdsaPublicKey { encoded: CompressedPublicKey, } @@ -28,12 +28,6 @@ impl EcdsaPublicKey { const SIZE: usize = 33; } -impl Hash for EcdsaPublicKey { - fn hash(&self, state: &mut H) { - self.encoded.hash(state); - } -} - impl TreeHash for EcdsaPublicKey { fn tree_hash_type() -> tree_hash::TreeHashType { tree_hash::TreeHashType::Vector @@ -108,12 +102,6 @@ impl ssz::Decode for EcdsaPublicKey { } } -impl From for EcdsaPublicKey { - fn from(value: CompressedPublicKey) -> Self { - Self { encoded: value } - } -} - impl From for EcdsaPublicKey { fn from(value: EcdsaPublicKeyInner) -> Self { let encoded: [u8; Self::SIZE] = value.to_encoded_point(true).as_bytes().try_into().unwrap(); From 9078e25bb0c980110e6b33fdf7f0da171537d8b9 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 15 Aug 2024 11:55:12 +0300 Subject: [PATCH 12/37] style: remove unnecessary constraint duplication --- crates/common/src/signer/schemes/ecdsa.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 5b6bdbb6..45d5083e 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -150,10 +150,7 @@ impl SecretKey for EcdsaSecretKey { EcdsaSecretKey::random(&mut rand::thread_rng()) } - fn new_from_bytes(bytes: &[u8]) -> Result - where - Self: Sized, - { + fn new_from_bytes(bytes: &[u8]) -> Result { Ok(EcdsaSecretKey::from_slice(bytes)?) } From 377aa786071ef8164ea6daf4d31e09c9d631feaa Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 15 Aug 2024 13:49:30 +0300 Subject: [PATCH 13/37] refactor(signer)!: rename module `signers` to `signer` and constrain visibility --- crates/common/src/signer/mod.rs | 5 +++-- crates/common/src/signer/{signers.rs => signer.rs} | 0 2 files changed, 3 insertions(+), 2 deletions(-) rename crates/common/src/signer/{signers.rs => signer.rs} (100%) diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 4f1e8d36..5f9b738e 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -9,10 +9,11 @@ use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; pub mod schemes; -pub mod signers; +#[allow(clippy::module_inception)] +mod signer; pub use schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey}; -pub use signers::{ConsensusSigner, Signer}; +pub use signer::{ConsensusSigner, Signer}; pub type Pubkey = ::PublicKey; diff --git a/crates/common/src/signer/signers.rs b/crates/common/src/signer/signer.rs similarity index 100% rename from crates/common/src/signer/signers.rs rename to crates/common/src/signer/signer.rs From b83841e56e567888b5f2828e62e4f5d5867de498 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 15 Aug 2024 15:01:05 +0300 Subject: [PATCH 14/37] refactor(loader): unify `ConsensusSigner` type alias usage * also, a small fix with error handling (avoid panicking) --- crates/common/src/loader.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/common/src/loader.rs b/crates/common/src/loader.rs index 9e371edc..3450506f 100644 --- a/crates/common/src/loader.rs +++ b/crates/common/src/loader.rs @@ -2,12 +2,12 @@ use std::fs; use alloy::{primitives::hex::FromHex, rpc::types::beacon::BlsPublicKey}; use eth2_keystore::Keystore; -use eyre::eyre; +use eyre::{eyre, Context}; use serde::{de, Deserialize, Deserializer, Serialize}; use crate::{ config::{load_env_var, SIGNER_DIR_KEYS_ENV, SIGNER_DIR_SECRETS_ENV, SIGNER_KEYS_ENV}, - signer::{ConsensusSigner, Signer}, + signer::ConsensusSigner, }; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -39,15 +39,16 @@ impl SignerLoader { let keys: Vec = serde_json::from_str(&file)?; keys.into_iter() - .map(|k| Signer::new_from_bytes(&k.secret_key)) - .collect::>>()? + .map(|k| ConsensusSigner::new_from_bytes(&k.secret_key)) + .collect::>() + .context("failed to load signers")? } SignerLoader::ValidatorsDir { .. } => { // TODO: hacky way to load for now, we should support reading the // definitions.yml file let keys_path = load_env_var(SIGNER_DIR_KEYS_ENV)?; let secrets_path = load_env_var(SIGNER_DIR_SECRETS_ENV)?; - load_secrets_and_keys(keys_path, secrets_path).expect("failed to load signers") + load_secrets_and_keys(keys_path, secrets_path).context("failed to load signers")? } }) } @@ -106,7 +107,7 @@ fn load_one(ks_path: String, pw_path: String) -> eyre::Result { let password = fs::read(pw_path)?; let key = keystore.decrypt_keypair(&password).map_err(|_| eyre!("failed decrypting keypair"))?; - Signer::new_from_bytes(key.sk.serialize().as_bytes()) + ConsensusSigner::new_from_bytes(key.sk.serialize().as_bytes()) } #[cfg(test)] From 7e1bf44bbda0866a8f5bff9bcdc8092922000de7 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Tue, 20 Aug 2024 13:32:55 +0300 Subject: [PATCH 15/37] chore!: remove forgotten debug --- crates/common/src/commit/client.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index d318a47d..7bc4e957 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -94,7 +94,6 @@ impl SignerClient { request: &GenerateProxyRequest, ) -> Result { let url = format!("{}{}", self.url, GENERATE_PROXY_KEY_PATH); - println!("{}", serde_json::to_string(&request).unwrap()); let res = self.client.post(&url).json(&request).send().await?; let status = res.status(); From eba97b0ca03b140bbfdc9f4b7b8ad4adf382776b Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 21 Aug 2024 13:03:10 +0300 Subject: [PATCH 16/37] refactor(signer)!: [WIP] simplify proxy keys - flatten out the code structure by removing generics - TODO: client SDK still not typed straightforward, but with enums insteand --- bin/src/lib.rs | 3 +- crates/common/src/commit/client.rs | 54 +++--- crates/common/src/commit/request.rs | 112 ++++++++++-- crates/common/src/signature.rs | 25 ++- crates/common/src/signer/mod.rs | 98 ++++++---- crates/common/src/signer/schemes/bls.rs | 135 ++++++++++---- crates/common/src/signer/schemes/ecdsa.rs | 115 +++++++++--- crates/common/src/signer/signer.rs | 72 ++++---- crates/signer/src/manager.rs | 211 +++++++++++++--------- crates/signer/src/service.rs | 63 ++++--- examples/da_commit/src/main.rs | 13 +- tests/tests/pbs_integration.rs | 13 +- 12 files changed, 606 insertions(+), 308 deletions(-) diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 81be3e30..b99953ff 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -1,7 +1,8 @@ pub mod prelude { pub use cb_common::{ commit, - commit::request::{SignRequest, SignedProxyDelegation}, + commit::request::{SignConsensusRequest, SignedProxyDelegation}, + signer::{EcdsaPublicKey, EcdsaSignature}, config::{ load_builder_module_config, load_commit_module_config, load_pbs_config, load_pbs_custom_config, StartCommitModuleConfig, diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 7bc4e957..955127b3 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -1,6 +1,6 @@ use std::{fmt, sync::Arc}; -use alloy::rpc::types::beacon::BlsPublicKey; +use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use eyre::WrapErr; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use serde::{Deserialize, Serialize}; @@ -8,9 +8,9 @@ use serde::{Deserialize, Serialize}; use super::{ constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, error::SignerClientError, - request::{GenerateProxyRequest, SignRequest, SignedProxyDelegation}, + request::{GenerateProxyRequest, SignConsensusRequest, SignProxyBlsRequest, SignProxyEcdsaRequest, SignProxyRequest, SignRequest, SignedProxyDelegation}, }; -use crate::{signer::GenericPubkey, DEFAULT_REQUEST_TIMEOUT}; +use crate::{signer::{schemes::ecdsa::{EcdsaPublicKey, EcdsaSignature}, BlsSecretKey, GenericPubkey}, DEFAULT_REQUEST_TIMEOUT}; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GetPubkeysResponse { @@ -67,10 +67,10 @@ impl SignerClient { } /// Send a signature request - pub async fn request_signature( + async fn request_signature( &self, request: &SignRequest, - ) -> Result { + ) -> Result, SignerClientError> { let url = format!("{}{}", self.url, REQUEST_SIGNATURE_PATH); let res = self.client.post(&url).json(&request).send().await?; @@ -86,7 +86,33 @@ impl SignerClient { let signature: Vec = serde_json::from_slice(&response_bytes)?; - Ok(Signature(signature)) + Ok(signature) + } + + pub async fn request_consensus_signature(&self, request: SignConsensusRequest) -> Result { + let request = SignRequest::Consensus(request); + let raw_signature = self.request_signature(&request).await?; + + let signature = BlsSignature::from_slice(&raw_signature); + + Ok(signature) + } + + async fn request_proxy_signature(&self, request: SignProxyRequest) -> Result, SignerClientError> { + let request = SignRequest::Proxy(request); + self.request_signature(&request).await + } + + pub async fn request_proxy_ecdsa_signature(&self, request: SignProxyEcdsaRequest) -> Result { + let raw_signature = self.request_proxy_signature(request.into()).await?; + let signature = EcdsaSignature::try_from(raw_signature.as_ref()).expect("unexpected invalid ECDSA signature"); + Ok(signature) + } + + pub async fn request_proxy_bls_signature(&self, request: SignProxyBlsRequest) -> Result { + let raw_signature = self.request_proxy_signature(request.into()).await?; + let signature = BlsSignature::from_slice(&raw_signature); + Ok(signature) } pub async fn generate_proxy_key( @@ -111,19 +137,3 @@ impl SignerClient { Ok(signed_proxy_delegation) } } - -// NOTE(David): -// For now, this is a simple displayable wrapper around vec, serving as a -// client-side type. It can be further deliberated whether a separate -// client-side type is preferrable over re-using an SDK in `common::signer`. -pub struct Signature(Vec); - -impl fmt::Display for Signature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0x")?; - for byte in &self.0 { - write!(f, "{:02x}", byte)?; - } - Ok(()) - } -} diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 3e037d66..9ebbee66 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -7,7 +7,7 @@ use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - error::BlstErrorWrapper, signature::verify_signed_builder_message, signer::GenericPubkey, + error::BlstErrorWrapper, signature::verify_signed_builder_message, signer::{schemes::ecdsa::EcdsaPublicKey, GenericPubkey}, types::Chain, }; @@ -49,27 +49,105 @@ impl fmt::Display for SignedProxyDelegation { } } -// TODO(David): Consider splitting `SignRequest` into two: ConsensusSignRequest -// and ProxySignRequest. For better type safety (to avoid the Vec -// generalisation) and avoid the is_proxy flag +// TODO(David): This struct shouldn't be visible in the client SDK #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SignRequest { - pub pubkey: Vec, - pub is_proxy: bool, +pub enum SignRequest { + Consensus(SignConsensusRequest), + Proxy(SignProxyRequest), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SignConsensusRequest { + pub pubkey: BlsPublicKey, + pub object_root: [u8; 32], +} + +impl SignConsensusRequest { + pub fn new(pubkey: BlsPublicKey, object_root: [u8; 32]) -> Self { + Self { pubkey, object_root } + } + + pub fn builder(pubkey: BlsPublicKey) -> Self { + Self::new(pubkey, [0; 32]) + } + + pub fn with_root(self, object_root: [u8; 32]) -> Self { + Self { object_root, ..self } + } + + pub fn with_msg(self, msg: &impl TreeHash) -> Self { + self.with_root(msg.tree_hash_root().0) + } +} + +//TODO(David): Shouldn't be visible from the client SDK +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SignProxyRequest { + pub pubkey: GenericPubkey, + pub object_root: [u8; 32], +} + +impl SignProxyRequest { + pub fn new(pubkey: GenericPubkey, object_root: [u8; 32]) -> Self { + Self { pubkey, object_root } + } + + pub fn builder(pubkey: GenericPubkey) -> Self { + Self::new(pubkey, [0; 32]) + } + + pub fn with_root(self, object_root: [u8; 32]) -> Self { + Self { object_root, ..self } + } + + pub fn with_msg(self, msg: &impl TreeHash) -> Self { + self.with_root(msg.tree_hash_root().0) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SignProxyEcdsaRequest { + pub pubkey: EcdsaPublicKey, pub object_root: [u8; 32], } -impl SignRequest { - pub fn new(pubkey: Vec, is_proxy: bool, object_root: [u8; 32]) -> SignRequest { - Self { pubkey, is_proxy, object_root } +impl SignProxyEcdsaRequest { + pub fn new(pubkey: EcdsaPublicKey, object_root: [u8; 32]) -> Self { + Self { pubkey, object_root } } - pub fn builder(pubkey: Vec) -> Self { - Self::new(pubkey, false, [0; 32]) + pub fn builder(pubkey: EcdsaPublicKey) -> Self { + Self::new(pubkey, [0; 32]) } - pub fn is_proxy(self) -> Self { - Self { is_proxy: true, ..self } + pub fn with_root(self, object_root: [u8; 32]) -> Self { + Self { object_root, ..self } + } + + pub fn with_msg(self, msg: &impl TreeHash) -> Self { + self.with_root(msg.tree_hash_root().0) + } +} + +impl From for SignProxyRequest { + fn from(value: SignProxyEcdsaRequest) -> Self { + Self { pubkey: GenericPubkey::Ecdsa(value.pubkey), object_root: value.object_root } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SignProxyBlsRequest { + pub pubkey: BlsPublicKey, + pub object_root: [u8; 32], +} + +impl SignProxyBlsRequest { + pub fn new(pubkey: BlsPublicKey, object_root: [u8; 32]) -> Self { + Self { pubkey, object_root } + } + + pub fn builder(pubkey: BlsPublicKey) -> Self { + Self::new(pubkey, [0; 32]) } pub fn with_root(self, object_root: [u8; 32]) -> Self { @@ -81,6 +159,12 @@ impl SignRequest { } } +impl From for SignProxyRequest { + fn from(value: SignProxyBlsRequest) -> Self { + Self { pubkey: GenericPubkey::Bls(value.pubkey), object_root: value.object_root } + } +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum EncryptionScheme { #[serde(rename = "bls")] diff --git a/crates/common/src/signature.rs b/crates/common/src/signature.rs index 8b61a945..2e6a40dd 100644 --- a/crates/common/src/signature.rs +++ b/crates/common/src/signature.rs @@ -1,4 +1,4 @@ -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use alloy::rpc::types::beacon::{constants::BLS_DST_SIG, BlsPublicKey, BlsSignature}; use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; @@ -6,10 +6,15 @@ use tree_hash_derive::TreeHash; use crate::{ constants::{APPLICATION_BUILDER_DOMAIN, GENESIS_VALIDATORS_ROOT}, error::BlstErrorWrapper, - signer::{SecretKey, Verifier}, + signer::{schemes::bls::verify_bls_signature, BlsSecretKey}, types::Chain, }; +pub fn sign_message(secret_key: &BlsSecretKey, msg: &[u8]) -> BlsSignature { + let signature = secret_key.sign(msg, BLS_DST_SIG, &[]).to_bytes(); + BlsSignature::from_slice(&signature) +} + pub fn compute_signing_root(object_root: [u8; 32], signing_domain: [u8; 32]) -> [u8; 32] { #[derive(Default, Debug, Encode, Decode, TreeHash)] struct SigningData { @@ -50,25 +55,25 @@ pub fn verify_signed_builder_message( let domain = chain.builder_domain(); let signing_root = compute_signing_root(msg.tree_hash_root().0, domain); - pubkey.verify_signature(&signing_root, signature) + verify_bls_signature(pubkey, &signing_root, signature) } -pub fn sign_builder_message( +pub fn sign_builder_message( chain: Chain, - secret_key: &T, + secret_key: &BlsSecretKey, msg: &impl TreeHash, -) -> T::Signature { +) -> BlsSignature { sign_builder_root(chain, secret_key, msg.tree_hash_root().0) } -pub fn sign_builder_root( +pub fn sign_builder_root( chain: Chain, - secret_key: &T, + secret_key: &BlsSecretKey, object_root: [u8; 32], -) -> T::Signature { +) -> BlsSignature { let domain = chain.builder_domain(); let signing_root = compute_signing_root(object_root, domain); - secret_key.sign(&signing_root) + sign_message(secret_key, &signing_root) } #[cfg(test)] diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 5f9b738e..11f28343 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -1,56 +1,56 @@ use std::{ - error::Error, fmt::{self, LowerHex}, }; +use alloy::rpc::types::beacon::BlsPublicKey; use eyre::Context; +use schemes::{bls::verify_bls_signature, ecdsa::verify_ecdsa_signature}; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; -use tree_hash::TreeHash; pub mod schemes; #[allow(clippy::module_inception)] mod signer; -pub use schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey}; -pub use signer::{ConsensusSigner, Signer}; - -pub type Pubkey = ::PublicKey; - -pub trait SecretKey { - type PublicKey: AsRef<[u8]> + Clone + Verifier; - type Signature: AsRef<[u8]> + Clone; - - fn new_random() -> Self; - fn new_from_bytes(bytes: &[u8]) -> eyre::Result - where - Self: Sized; - fn pubkey(&self) -> Self::PublicKey; - fn sign(&self, msg: &[u8]) -> Self::Signature; - fn sign_msg(&self, msg: &impl TreeHash) -> Self::Signature { - self.sign(&msg.tree_hash_root().0) - } -} - -pub trait Verifier -where - T: ?Sized, -{ - type VerificationError: Error; - - fn verify_signature( - &self, - msg: &[u8], - signature: &T::Signature, - ) -> Result<(), Self::VerificationError>; -} +pub use schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey, ecdsa::EcdsaPublicKey, ecdsa::EcdsaSignature}; +pub use signer::{ConsensusSigner, BlsSigner, EcdsaSigner}; + +// pub type Pubkey = ::PublicKey; + +// pub trait SecretKey { +// type PublicKey: AsRef<[u8]> + Clone + Verifier; +// type Signature: AsRef<[u8]> + Clone; + +// fn new_random() -> Self; +// fn new_from_bytes(bytes: &[u8]) -> eyre::Result +// where +// Self: Sized; +// fn pubkey(&self) -> Self::PublicKey; +// fn sign(&self, msg: &[u8]) -> Self::Signature; +// fn sign_msg(&self, msg: &impl TreeHash) -> Self::Signature { +// self.sign(&msg.tree_hash_root().0) +// } +// } + +// pub trait Verifier +// where +// T: ?Sized, +// { +// type VerificationError: Error; + +// fn verify_signature( +// &self, +// msg: &[u8], +// signature: &T::Signature, +// ) -> Result<(), Self::VerificationError>; +// } #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode)] #[serde(untagged)] #[ssz(enum_behaviour = "transparent")] pub enum GenericPubkey { - Bls(Pubkey), - Ecdsa(Pubkey), + Bls(BlsPublicKey), + Ecdsa(EcdsaPublicKey), } impl GenericPubkey { @@ -58,11 +58,11 @@ impl GenericPubkey { match self { GenericPubkey::Bls(bls_pubkey) => { let sig = signature.try_into().context("Invalid signature length for BLS.")?; - Ok(bls_pubkey.verify_signature(msg, &sig)?) + Ok(verify_bls_signature(bls_pubkey, msg, &sig)?) } GenericPubkey::Ecdsa(ecdsa_pubkey) => { let sig = signature.try_into().context("Invalid signature for ECDSA.")?; - Ok(ecdsa_pubkey.verify_signature(msg, &sig)?) + Ok(verify_ecdsa_signature(ecdsa_pubkey, msg, &sig)?) } } } @@ -77,6 +77,28 @@ impl AsRef<[u8]> for GenericPubkey { } } +impl TryFrom for BlsPublicKey { + type Error = (); + + fn try_from(value: GenericPubkey) -> Result { + match value { + GenericPubkey::Bls(bls_pubkey) => Ok(bls_pubkey), + GenericPubkey::Ecdsa(_) => Err(()), + } + } +} + +impl TryFrom for EcdsaPublicKey { + type Error = (); + + fn try_from(value: GenericPubkey) -> Result { + match value { + GenericPubkey::Bls(_) => Err(()), + GenericPubkey::Ecdsa(ecdsa_pubkey) => Ok(ecdsa_pubkey), + } + } +} + impl LowerHex for GenericPubkey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "0x")?; diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index f60a12ad..ddfc9153 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -1,68 +1,127 @@ use alloy::rpc::types::beacon::{constants::BLS_DST_SIG, BlsPublicKey, BlsSignature}; use blst::BLST_ERROR; +use tree_hash::TreeHash; use crate::{ - error::BlstErrorWrapper, - signer::{GenericPubkey, Pubkey, SecretKey, Verifier}, - utils::blst_pubkey_to_alloy, + error::BlstErrorWrapper, signature::sign_builder_root, signer::GenericPubkey, types::Chain, utils::blst_pubkey_to_alloy }; pub type BlsSecretKey = blst::min_pk::SecretKey; -impl SecretKey for BlsSecretKey { - type PublicKey = BlsPublicKey; - type Signature = BlsSignature; +#[derive(Clone)] +pub enum BlsSigner { + Local(BlsSecretKey), +} - fn new_random() -> Self { - use rand::RngCore; +impl BlsSigner { + pub fn new_random() -> Self { + Self::Local(random_secret()) + } - let mut rng = rand::thread_rng(); - let mut ikm = [0u8; 32]; - rng.fill_bytes(&mut ikm); + pub fn new_from_bytes(bytes: &[u8]) -> eyre::Result { + let secret = BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?; + Ok(Self::Local(secret)) + } - match BlsSecretKey::key_gen(&ikm, &[]) { - Ok(key) => key, - // Key material is always valid (32 `u8`s), so `key_gen` can't return Err. - Err(_) => unreachable!(), + pub fn pubkey(&self) -> BlsPublicKey { + match self { + BlsSigner::Local(secret) => blst_pubkey_to_alloy(&secret.sk_to_pk()), } } - fn new_from_bytes(bytes: &[u8]) -> eyre::Result { - Ok(BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?) + pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> BlsSignature { + match self { + BlsSigner::Local(sk) => sign_builder_root(chain, sk, object_root), + } } - fn pubkey(&self) -> Self::PublicKey { - blst_pubkey_to_alloy(&self.sk_to_pk()) + pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> BlsSignature { + self.sign(chain, msg.tree_hash_root().0).await } +} - fn sign(&self, msg: &[u8]) -> Self::Signature { - let signature = self.sign(msg, BLS_DST_SIG, &[]).to_bytes(); - BlsSignature::from_slice(&signature) +fn random_secret() -> BlsSecretKey { + use rand::RngCore; + + let mut rng = rand::thread_rng(); + let mut ikm = [0u8; 32]; + rng.fill_bytes(&mut ikm); + + match BlsSecretKey::key_gen(&ikm, &[]) { + Ok(key) => key, + // Key material is always valid (32 `u8`s), so `key_gen` can't return Err. + Err(_) => unreachable!(), } } -impl Verifier for Pubkey { - type VerificationError = BlstErrorWrapper; +// impl SecretKey for BlsSecretKey { +// // type PublicKey = BlsPublicKey; +// // type Signature = BlsSignature; - fn verify_signature( - &self, - msg: &[u8], - signature: &::Signature, - ) -> Result<(), Self::VerificationError> { - use crate::utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}; +// // fn new_random() -> Self { +// // use rand::RngCore; - let pubkey = alloy_pubkey_to_blst(self)?; - let signature = alloy_sig_to_blst(signature)?; +// // let mut rng = rand::thread_rng(); +// // let mut ikm = [0u8; 32]; +// // rng.fill_bytes(&mut ikm); - let res = signature.verify(true, msg, BLS_DST_SIG, &[], &pubkey, true); - if res == BLST_ERROR::BLST_SUCCESS { - Ok(()) - } else { - Err(res.into()) - } +// // match BlsSecretKey::key_gen(&ikm, &[]) { +// // Ok(key) => key, +// // // Key material is always valid (32 `u8`s), so `key_gen` can't return Err. +// // Err(_) => unreachable!(), +// // } +// // } + +// fn new_from_bytes(bytes: &[u8]) -> eyre::Result { +// Ok(BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?) +// } + +// fn pubkey(&self) -> Self::PublicKey { +// blst_pubkey_to_alloy(&self.sk_to_pk()) +// } + +// fn sign(&self, msg: &[u8]) -> Self::Signature { +// let signature = self.sign(msg, BLS_DST_SIG, &[]).to_bytes(); +// BlsSignature::from_slice(&signature) +// } +// } + +pub fn verify_bls_signature(pubkey: &BlsPublicKey, msg: &[u8], signature: &BlsSignature) -> Result<(), BlstErrorWrapper> { + use crate::utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}; + + let pubkey = alloy_pubkey_to_blst(pubkey)?; + let signature = alloy_sig_to_blst(signature)?; + + let res = signature.verify(true, msg, BLS_DST_SIG, &[], &pubkey, true); + if res == BLST_ERROR::BLST_SUCCESS { + Ok(()) + } else { + Err(res.into()) } } +// impl Verifier for Pubkey { +// type VerificationError = BlstErrorWrapper; + +// fn verify_signature( +// &self, +// msg: &[u8], +// signature: &::Signature, +// ) -> Result<(), Self::VerificationError> { +// use crate::utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}; + +// let pubkey = alloy_pubkey_to_blst(self)?; +// let signature = alloy_sig_to_blst(signature)?; + +// let res = signature.verify(true, msg, BLS_DST_SIG, &[], &pubkey, true); +// if res == BLST_ERROR::BLST_SUCCESS { +// Ok(()) +// } else { +// Err(res.into()) +// } +// } +// } + impl From for GenericPubkey { fn from(value: BlsPublicKey) -> Self { GenericPubkey::Bls(value) diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 45d5083e..3606702b 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::hash::Hash; use derive_more::derive::{Deref, From, Into}; @@ -11,7 +12,7 @@ use ssz_types::{ }; use tree_hash::TreeHash; -use crate::signer::{GenericPubkey, Pubkey, SecretKey, Verifier}; +use crate::{signature::compute_signing_root, signer::GenericPubkey, types::Chain}; pub type EcdsaSecretKey = k256::ecdsa::SigningKey; @@ -141,45 +142,109 @@ impl TryFrom<&[u8]> for EcdsaSignature { } } -impl SecretKey for EcdsaSecretKey { - type PublicKey = EcdsaPublicKey; +impl fmt::LowerHex for EcdsaSignature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "0x")?; - type Signature = EcdsaSignature; + let pubkey_bytes = self.as_ref(); - fn new_random() -> Self { - EcdsaSecretKey::random(&mut rand::thread_rng()) + for byte in pubkey_bytes { + write!(f, "{:02x}", byte)?; + } + + Ok(()) } +} - fn new_from_bytes(bytes: &[u8]) -> Result { - Ok(EcdsaSecretKey::from_slice(bytes)?) +impl fmt::Display for EcdsaSignature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:x}") } +} + +// SIGNER +#[derive(Clone)] +pub enum EcdsaSigner { + Local(EcdsaSecretKey), +} - fn pubkey(&self) -> Self::PublicKey { - EcdsaPublicKeyInner::from(self).into() +impl EcdsaSigner { + pub fn new_random() -> Self { + Self::Local(EcdsaSecretKey::random(&mut rand::thread_rng())) } - fn sign(&self, msg: &[u8]) -> Self::Signature { - k256::ecdsa::signature::Signer::::sign(self, msg).into() + pub fn new_from_bytes(bytes: &[u8]) -> eyre::Result { + let secret = EcdsaSecretKey::from_slice(bytes)?; + Ok(Self::Local(secret)) } -} -impl Verifier for Pubkey { - type VerificationError = k256::ecdsa::Error; + pub fn pubkey(&self) -> EcdsaPublicKey { + match self { + EcdsaSigner::Local(secret) => EcdsaPublicKeyInner::from(secret).into(), + } + } + + pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> EcdsaSignature { + match self { + EcdsaSigner::Local(sk) => /*sign_builder_root(chain, sk, object_root),*/ { + let domain = chain.builder_domain(); + let signing_root = compute_signing_root(object_root, domain); + k256::ecdsa::signature::Signer::::sign(sk, &signing_root).into() + } + } + } - fn verify_signature( - &self, - msg: &[u8], - signature: &::Signature, - ) -> Result<(), Self::VerificationError> { - use k256::ecdsa::signature::Verifier; - let ecdsa_pubkey = EcdsaPublicKeyInner::from_sec1_bytes(&self.encoded)?; - let ecdsa_sig = EcdsaSignatureInner::from_bytes(signature)?; - ecdsa_pubkey.verify(msg, &ecdsa_sig) + pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> EcdsaSignature { + self.sign(chain, msg.tree_hash_root().0).await } } +pub fn verify_ecdsa_signature(pubkey: &EcdsaPublicKey, msg: &[u8], 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(signature)?; + ecdsa_pubkey.verify(msg, &ecdsa_sig) +} + +// impl SecretKey for EcdsaSecretKey { +// type PublicKey = EcdsaPublicKey; + +// type Signature = EcdsaSignature; + +// fn new_random() -> Self { +// EcdsaSecretKey::random(&mut rand::thread_rng()) +// } + +// fn new_from_bytes(bytes: &[u8]) -> Result { +// Ok(EcdsaSecretKey::from_slice(bytes)?) +// } + +// fn pubkey(&self) -> Self::PublicKey { +// EcdsaPublicKeyInner::from(self).into() +// } + +// fn sign(&self, msg: &[u8]) -> Self::Signature { +// k256::ecdsa::signature::Signer::::sign(self, msg).into() +// } +// } + +// impl Verifier for Pubkey { +// type VerificationError = k256::ecdsa::Error; + +// fn verify_signature( +// &self, +// msg: &[u8], +// signature: &::Signature, +// ) -> Result<(), Self::VerificationError> { +// use k256::ecdsa::signature::Verifier; +// let ecdsa_pubkey = EcdsaPublicKeyInner::from_sec1_bytes(&self.encoded)?; +// let ecdsa_sig = EcdsaSignatureInner::from_bytes(signature)?; +// ecdsa_pubkey.verify(msg, &ecdsa_sig) +// } +// } + impl From for GenericPubkey { - fn from(value: Pubkey) -> Self { + fn from(value: EcdsaPublicKey) -> Self { GenericPubkey::Ecdsa(value) } } diff --git a/crates/common/src/signer/signer.rs b/crates/common/src/signer/signer.rs index d58bc47f..562a5460 100644 --- a/crates/common/src/signer/signer.rs +++ b/crates/common/src/signer/signer.rs @@ -1,38 +1,34 @@ -use eyre::Result; -use tree_hash::TreeHash; - -use super::{BlsSecretKey, SecretKey}; -use crate::{signature::sign_builder_root, types::Chain}; - -pub type ConsensusSigner = Signer; - -#[derive(Clone)] -pub enum Signer { - Local(T), -} - -impl Signer { - pub fn new_random() -> Self { - Signer::Local(T::new_random()) - } - - pub fn new_from_bytes(bytes: &[u8]) -> Result { - T::new_from_bytes(bytes).map(Self::Local) - } - - pub fn pubkey(&self) -> T::PublicKey { - match self { - Signer::Local(secret) => secret.pubkey(), - } - } - - pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> T::Signature { - match self { - Signer::Local(sk) => sign_builder_root(chain, sk, object_root), - } - } - - pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> T::Signature { - self.sign(chain, msg.tree_hash_root().0).await - } -} +pub use super::schemes::bls::BlsSigner; +pub type ConsensusSigner = BlsSigner; +pub type EcdsaSigner = super::schemes::ecdsa::EcdsaSigner; + +// #[derive(Clone)] +// pub enum Signer { +// Local(T), +// } + +// impl Signer { +// pub fn new_random() -> Self { +// Signer::Local(T::new_random()) +// } + +// pub fn new_from_bytes(bytes: &[u8]) -> Result { +// T::new_from_bytes(bytes).map(Self::Local) +// } + +// pub fn pubkey(&self) -> T::PublicKey { +// match self { +// Signer::Local(secret) => secret.pubkey(), +// } +// } + +// pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> T::Signature { +// match self { +// Signer::Local(sk) => sign_builder_root(chain, sk, object_root), +// } +// } + +// pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> T::Signature { +// self.sign(chain, msg.tree_hash_root().0).await +// } +// } diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index ff9a6ec4..77d057a9 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -1,10 +1,9 @@ use std::collections::HashMap; use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; -use blst::min_pk::SecretKey as BlsSecretKey; use cb_common::{ - commit::request::{ProxyDelegation, SignedProxyDelegation}, - signer::{ConsensusSigner, EcdsaSecretKey, GenericPubkey, Pubkey, SecretKey, Signer}, + commit::request::{EncryptionScheme, ProxyDelegation, SignedProxyDelegation}, + signer::{schemes::ecdsa::{EcdsaPublicKey, EcdsaSignature}, BlsSigner, ConsensusSigner, EcdsaSigner, GenericPubkey}, types::{Chain, ModuleId}, }; use derive_more::derive::{Deref, From}; @@ -19,23 +18,37 @@ use crate::error::SignerModuleError; // ProxyDelegation msg and then sign all future commit messages with the proxy // key for slashing the faulty message + proxy delegation can be used // Signed using builder domain +// #[derive(Clone, Deref)] +// pub struct ProxySigner { +// #[deref] +// signer: Signer, +// delegation: SignedProxyDelegation, +// } + +// impl ProxySigner { +// pub fn new(signer: Signer, delegation: SignedProxyDelegation) -> Self { +// Self { signer, delegation } +// } +// } + #[derive(Clone, Deref)] -pub struct ProxySigner { +pub struct BlsProxySigner { #[deref] - signer: Signer, + signer: BlsSigner, delegation: SignedProxyDelegation, } -impl ProxySigner { - pub fn new(signer: Signer, delegation: SignedProxyDelegation) -> Self { - Self { signer, delegation } - } +#[derive(Clone, Deref)] +pub struct EcdsaProxySigner { + #[deref] + signer: EcdsaSigner, + delegation: SignedProxyDelegation, } #[derive(From)] pub enum GenericProxySigner { - Bls(ProxySigner), - Ecdsa(ProxySigner), + Bls(BlsProxySigner), + Ecdsa(EcdsaProxySigner), } impl GenericProxySigner { @@ -71,19 +84,19 @@ impl GenericProxySigner { #[derive(Default)] struct ProxySigners { - bls_signers: HashMap, ProxySigner>, - ecdsa_signers: HashMap, ProxySigner>, + bls_signers: HashMap, + ecdsa_signers: HashMap, } -impl<'a> ProxySigners { +impl ProxySigners { pub fn get(&self, key: &GenericPubkey) -> Option { match key { GenericPubkey::Bls(bls_pubkey) => { - let proxy_signer = self.get_proxy_signer(bls_pubkey)?; + let proxy_signer = self.bls_signers.get(bls_pubkey)?; Some(GenericProxySigner::Bls(proxy_signer.clone())) } GenericPubkey::Ecdsa(ecdsa_pubkey) => { - let proxy_signer = self.get_proxy_signer(ecdsa_pubkey)?; + let proxy_signer = self.ecdsa_signers.get(ecdsa_pubkey)?; Some(GenericProxySigner::Ecdsa(proxy_signer.clone())) } } @@ -100,41 +113,48 @@ impl<'a> ProxySigners { } } - pub fn find_pubkey(&'a self, pubkey: &[u8]) -> Option { - fn find_typed<'a, T>( - keys: impl IntoIterator>, - pubkey: &[u8], - ) -> Option - where - T: SecretKey, - Pubkey: 'a + Into, - { - keys.into_iter().find(|x| x.as_ref() == pubkey).cloned().map(Into::into) + pub fn has_proxy(&self, pubkey: &GenericPubkey) -> bool { + match pubkey { + GenericPubkey::Bls(bls_pk) => self.bls_signers.contains_key(bls_pk), + GenericPubkey::Ecdsa(ecdsa_pk) => self.ecdsa_signers.contains_key(ecdsa_pk), } - - find_typed::(self.bls_signers.keys(), pubkey) - .or_else(|| find_typed::(self.ecdsa_signers.keys(), pubkey)) } -} -trait GetProxySigner { - fn get_proxy_signer(&self, pk: &Pubkey) -> Option<&ProxySigner>; -} - -impl GetProxySigner for ProxySigners { - fn get_proxy_signer(&self, pk: &Pubkey) -> Option<&ProxySigner> { - self.bls_signers.get(pk) - } + // pub fn find_pubkey<'a>(&'a self, pubkey: &[u8]) -> Option { + // fn find_typed<'a, T>( + // keys: impl IntoIterator>, + // pubkey: &[u8], + // ) -> Option + // where + // T: SecretKey, + // Pubkey: 'a + Into, + // { + // keys.into_iter().find(|x| x.as_ref() == pubkey).cloned().map(Into::into) + // } + + // find_typed::(self.bls_signers.keys(), pubkey) + // .or_else(|| find_typed::(self.ecdsa_signers.keys(), pubkey)) + // } } -impl GetProxySigner for ProxySigners { - fn get_proxy_signer( - &self, - pk: &Pubkey, - ) -> Option<&ProxySigner> { - self.ecdsa_signers.get(pk) - } -} +// trait GetProxySigner { +// fn get_proxy_signer(&self, pk: &Pubkey) -> Option<&ProxySigner>; +// } + +// impl GetProxySigner for ProxySigners { +// fn get_proxy_signer(&self, pk: &BlsPublicKey) -> Option<&ProxySigner> { +// self.bls_signers.get(pk) +// } +// } + +// impl GetProxySigner for ProxySigners { +// fn get_proxy_signer( +// &self, +// pk: &EcdsaPublicKey, +// ) -> Option<&ProxySigner> { +// self.ecdsa_signers.get(pk) +// } +// } pub struct SigningManager { chain: Chain, @@ -165,22 +185,40 @@ impl SigningManager { self.proxy_signers.add(proxy); } - pub async fn create_proxy( + pub async fn create_proxy( &mut self, module_id: ModuleId, delegator: BlsPublicKey, + encryption_scheme: EncryptionScheme, ) -> Result - where - Pubkey: Into, - ProxySigner: Into, { - let signer = Signer::::new_random(); - let proxy_pubkey = signer.pubkey().into(); - - let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; - let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; - let signed_delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; - let proxy_signer = ProxySigner::new(signer, signed_delegation).into(); + let (proxy_signer, proxy_pubkey, signed_delegation) = match encryption_scheme { + EncryptionScheme::Bls => { + let signer = BlsSigner::new_random(); + let proxy_pubkey = signer.pubkey().into(); + + let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; + let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; + let delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; + (BlsProxySigner { signer, delegation }.into(), proxy_pubkey, delegation) + }, + EncryptionScheme::Ecdsa => { + let signer = EcdsaSigner::new_random(); + let proxy_pubkey = signer.pubkey().into(); + + let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; + let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; + let delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; + (EcdsaProxySigner { signer, delegation }.into(), proxy_pubkey, delegation) + }, + }; + // let signer = Signer::::new_random(); + // let proxy_pubkey = signer.pubkey().into(); + + // let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; + // let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; + // let signed_delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; + // let proxy_signer = ProxySigner::new(signer, signed_delegation).into(); // Add the new proxy key to the manager's internal state self.add_proxy_signer(proxy_signer); @@ -205,25 +243,29 @@ impl SigningManager { Ok(signature) } - fn find_proxy(&self, pubkey: &[u8]) -> Option { - let generic_pubkey = self.proxy_signers.find_pubkey(pubkey)?; - - let proxy_signer = self.proxy_signers.get(&generic_pubkey).expect("Unreachable!"); + // pub async fn sign_proxy( + // &self, + // pubkey: &[u8], + // object_root: &[u8; 32], + // ) -> Result, SignerModuleError> { + // let proxy = self + // .find_proxy(pubkey) + // .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; - Some(proxy_signer) - } + // let signature = proxy.sign(self.chain, *object_root).await; - pub async fn sign_proxy( - &self, - pubkey: &[u8], - object_root: &[u8; 32], - ) -> Result, SignerModuleError> { - let proxy = self - .find_proxy(pubkey) - .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; + // Ok(signature) + // } - let signature = proxy.sign(self.chain, *object_root).await; + pub async fn sign_proxy_bls(&self, pubkey: &BlsPublicKey, object_root: &[u8; 32]) -> Result { + let bls_proxy = self.proxy_signers.bls_signers.get(pubkey).ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; + let signature = bls_proxy.sign(self.chain, *object_root).await; + Ok(signature) + } + pub async fn sign_proxy_ecdsa(&self, pubkey: &EcdsaPublicKey, 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; Ok(signature) } @@ -239,17 +281,16 @@ impl SigningManager { self.consensus_signers.contains_key(pubkey) } - pub fn has_proxy(&self, pubkey: &[u8]) -> bool { - self.proxy_signers.find_pubkey(pubkey).is_some() + pub fn has_proxy(&self, pubkey: &GenericPubkey) -> bool { + self.proxy_signers.has_proxy(pubkey) } pub fn get_delegation( &self, - pubkey: &[u8], + pubkey: &GenericPubkey, ) -> Result { - let proxy = self - .find_proxy(pubkey) - .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; + let proxy = self.proxy_signers.get(&pubkey) + .ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec()))?; Ok(proxy.delegation()) } @@ -271,7 +312,7 @@ mod tests { fn init_signing_manager() -> (SigningManager, BlsPublicKey) { let mut signing_manager = SigningManager::new(*CHAIN); - let consensus_signer = Signer::new_random(); + let consensus_signer = ConsensusSigner::new_random(); let consensus_pk = consensus_signer.pubkey(); signing_manager.add_consensus_signer(consensus_signer.clone()); @@ -284,7 +325,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let signed_delegation = signing_manager - .create_proxy::(MODULE_ID.clone(), consensus_pk.clone()) + .create_proxy(MODULE_ID.clone(), consensus_pk.clone(), EncryptionScheme::Bls) .await .unwrap(); @@ -296,7 +337,7 @@ mod tests { ); assert!( - signing_manager.has_proxy(&signed_delegation.message.proxy.as_ref()), + signing_manager.has_proxy(&signed_delegation.message.proxy), "Newly generated proxy key must be present in the signing manager's registry." ); } @@ -306,7 +347,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let mut signed_delegation = signing_manager - .create_proxy::(MODULE_ID.clone(), consensus_pk.clone()) + .create_proxy(MODULE_ID.clone(), consensus_pk.clone(), EncryptionScheme::Bls) .await .unwrap(); @@ -323,7 +364,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let signed_delegation = signing_manager - .create_proxy::(MODULE_ID.clone(), consensus_pk.clone()) + .create_proxy(MODULE_ID.clone(), consensus_pk.clone(), EncryptionScheme::Bls) .await .unwrap(); let proxy_pk = signed_delegation.message.proxy; @@ -331,13 +372,13 @@ mod tests { let data_root = Hash256::random(); let data_root_bytes = data_root.as_fixed_bytes(); - let sig = signing_manager.sign_proxy(proxy_pk.as_ref(), data_root_bytes).await.unwrap(); + let sig = signing_manager.sign_proxy_bls(&proxy_pk.try_into().unwrap(), data_root_bytes).await.unwrap(); // Verify signature let domain = CHAIN.builder_domain(); let signing_root = compute_signing_root(data_root_bytes.tree_hash_root().0, domain); - let validation_result = proxy_pk.verify_signature(&signing_root, &sig); + let validation_result = proxy_pk.verify_signature(&signing_root, sig.as_ref()); assert!( validation_result.is_ok(), diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index b60dd667..daaaf38f 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -14,7 +14,7 @@ use cb_common::{ commit::{ client::GetPubkeysResponse, constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, - request::{GenerateProxyRequest, SignRequest}, + request::{GenerateProxyRequest, SignConsensusRequest, SignProxyRequest, SignRequest}, }, config::StartSignerConfig, signer::{BlsSecretKey, EcdsaSecretKey}, @@ -128,17 +128,33 @@ async fn handle_request_signature( let signing_manager = state.manager.read().await; - let sig = if request.is_proxy { - signing_manager.sign_proxy(&request.pubkey, &request.object_root).await - } else { - let pubkey = request - .pubkey - .as_slice() - .try_into() - .map_err(|_| SignerModuleError::UnknownConsensusSigner(request.pubkey.clone()))?; - signing_manager.sign_consensus(pubkey, &request.object_root).await.map(|x| x.to_vec()) + let sig = match request { + SignRequest::Consensus(SignConsensusRequest { pubkey, object_root }) => { + signing_manager.sign_consensus(&pubkey, &object_root).await.map(|sig| sig.to_vec()) + }, + SignRequest::Proxy(SignProxyRequest { pubkey, object_root }) => { + match pubkey { + cb_common::signer::GenericPubkey::Bls(bls_pubkey) => { + signing_manager.sign_proxy_bls(&bls_pubkey, &object_root).await.map(|sig| sig.to_vec()) + }, + cb_common::signer::GenericPubkey::Ecdsa(ecdsa_pubkey) => { + signing_manager.sign_proxy_ecdsa(&ecdsa_pubkey, &object_root).await.map(|sig| sig.to_vec()) + }, + } + }, }?; + // let sig = if request.is_proxy { + // signing_manager.sign_proxy(&request.pubkey, &request.object_root).await + // } else { + // let pubkey = request + // .pubkey + // .as_slice() + // .try_into() + // .map_err(|_| SignerModuleError::UnknownConsensusSigner(request.pubkey.clone()))?; + // signing_manager.sign_consensus(pubkey, &request.object_root).await.map(|x| x.to_vec()) + // }?; + Ok((StatusCode::OK, Json(sig)).into_response()) } @@ -153,19 +169,20 @@ async fn handle_generate_proxy( let mut signing_manager = state.manager.write().await; - use cb_common::commit::request::EncryptionScheme; - let proxy_delegation = match request.scheme { - EncryptionScheme::Bls => { - signing_manager - .create_proxy::(module_id, request.consensus_pubkey) - .await? - } - EncryptionScheme::Ecdsa => { - signing_manager - .create_proxy::(module_id, request.consensus_pubkey) - .await? - } - }; + let proxy_delegation = signing_manager + .create_proxy(module_id, request.consensus_pubkey, request.scheme) + .await?; + + // match request.scheme { + // EncryptionScheme::Bls => { + + // } + // EncryptionScheme::Ecdsa => { + // signing_manager + // .create_proxy::(module_id, request.consensus_pubkey) + // .await? + // } + // }; Ok((StatusCode::OK, Json(proxy_delegation)).into_response()) } diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index 50dacdec..eaf4d9d1 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,7 +1,7 @@ use std::time::Duration; use alloy::rpc::types::beacon::BlsPublicKey; -use commit::request::GenerateProxyRequest; +use commit::request::{GenerateProxyRequest, SignProxyEcdsaRequest}; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; use lazy_static::lazy_static; @@ -68,13 +68,14 @@ impl DaCommitService { ) -> Result<()> { let datagram = Datagram { data }; - let request = SignRequest::builder(pubkey.to_vec()).with_msg(&datagram); - let signature = self.config.signer_client.request_signature(&request); + let request = SignConsensusRequest::builder(pubkey).with_msg(&datagram); + let signature = self.config.signer_client.request_consensus_signature(request); - let proxy_request = SignRequest::builder(proxy_delegation.message.proxy.as_ref().to_vec()) - .is_proxy() + let ecdsa_proxy: EcdsaPublicKey = proxy_delegation.message.proxy.try_into().unwrap(); + + let proxy_request = SignProxyEcdsaRequest::builder(ecdsa_proxy) .with_msg(&datagram); - let proxy_signature = self.config.signer_client.request_signature(&proxy_request); + let proxy_signature = self.config.signer_client.request_proxy_ecdsa_signature(proxy_request); let (signature, proxy_signature) = { let res = tokio::join!(signature, proxy_signature); diff --git a/tests/tests/pbs_integration.rs b/tests/tests/pbs_integration.rs index eb3cff56..2b127bfe 100644 --- a/tests/tests/pbs_integration.rs +++ b/tests/tests/pbs_integration.rs @@ -2,10 +2,7 @@ use std::{net::SocketAddr, sync::Arc, time::Duration, u64}; use alloy::primitives::U256; use cb_common::{ - config::{PbsConfig, PbsModuleConfig}, - pbs::RelayClient, - signer::Signer, - types::Chain, + config::{PbsConfig, PbsModuleConfig}, pbs::RelayClient, signer::ConsensusSigner, types::Chain }; use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use cb_tests::{ @@ -55,7 +52,7 @@ fn to_pbs_config(chain: Chain, pbs_config: PbsConfig, relays: Vec) #[tokio::test] async fn test_get_header() -> Result<()> { setup_test_env(); - let signer = Signer::new_random(); + let signer = ConsensusSigner::new_random(); let chain = Chain::Holesky; let port = 3000; @@ -83,7 +80,7 @@ async fn test_get_header() -> Result<()> { #[tokio::test] async fn test_get_status() -> Result<()> { setup_test_env(); - let signer = Signer::new_random(); + let signer = ConsensusSigner::new_random(); let chain = Chain::Holesky; let port = 3100; @@ -115,7 +112,7 @@ async fn test_get_status() -> Result<()> { #[tokio::test] async fn test_register_validators() -> Result<()> { setup_test_env(); - let signer = Signer::new_random(); + let signer = ConsensusSigner::new_random(); let chain = Chain::Holesky; let port = 3300; @@ -143,7 +140,7 @@ async fn test_register_validators() -> Result<()> { #[tokio::test] async fn test_submit_block() -> Result<()> { setup_test_env(); - let signer = Signer::new_random(); + let signer = ConsensusSigner::new_random(); let chain = Chain::Holesky; let port = 3400; From ad6d37332b68c26af7d7588ec0671d2ba7bdb37f Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 21 Aug 2024 17:32:29 +0300 Subject: [PATCH 17/37] refactor(signer)!: separate BLS and ECDSA types in client SDK - thus, conclude an implementation variant where ECDSA and BLS types are separated almost everywhere throughout the signer module (+the client SDK) --- bin/src/lib.rs | 2 +- crates/common/src/commit/client.rs | 73 ++++++-- crates/common/src/commit/request.rs | 166 ++++++++++++++++--- crates/common/src/signer/schemes/ecdsa.rs | 37 +++-- crates/signer/src/manager.rs | 192 +++++++++++++++------- crates/signer/src/service.rs | 30 ++-- examples/da_commit/src/main.rs | 9 +- 7 files changed, 386 insertions(+), 123 deletions(-) diff --git a/bin/src/lib.rs b/bin/src/lib.rs index b99953ff..79d1336f 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -1,7 +1,7 @@ pub mod prelude { pub use cb_common::{ commit, - commit::request::{SignConsensusRequest, SignedProxyDelegation}, + commit::request::{SignConsensusRequest, SignedProxyDelegation, SignedProxyDelegationEcdsa, SignedProxyDelegationBls}, signer::{EcdsaPublicKey, EcdsaSignature}, config::{ load_builder_module_config, load_commit_module_config, load_pbs_config, diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 955127b3..23e87257 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -1,4 +1,4 @@ -use std::{fmt, sync::Arc}; +use std::sync::Arc; use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use eyre::WrapErr; @@ -8,9 +8,16 @@ use serde::{Deserialize, Serialize}; use super::{ constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, error::SignerClientError, - request::{GenerateProxyRequest, SignConsensusRequest, SignProxyBlsRequest, SignProxyEcdsaRequest, SignProxyRequest, SignRequest, SignedProxyDelegation}, + request::{ + EncryptionScheme, GenerateProxyRequest, SignConsensusRequest, SignProxyBlsRequest, + SignProxyEcdsaRequest, SignProxyRequest, SignRequest, SignedProxyDelegation, + SignedProxyDelegationBls, SignedProxyDelegationEcdsa, + }, +}; +use crate::{ + signer::{schemes::ecdsa::EcdsaSignature, GenericPubkey}, + DEFAULT_REQUEST_TIMEOUT, }; -use crate::{signer::{schemes::ecdsa::{EcdsaPublicKey, EcdsaSignature}, BlsSecretKey, GenericPubkey}, DEFAULT_REQUEST_TIMEOUT}; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GetPubkeysResponse { @@ -67,10 +74,7 @@ impl SignerClient { } /// Send a signature request - async fn request_signature( - &self, - request: &SignRequest, - ) -> Result, SignerClientError> { + async fn request_signature(&self, request: &SignRequest) -> Result, SignerClientError> { let url = format!("{}{}", self.url, REQUEST_SIGNATURE_PATH); let res = self.client.post(&url).json(&request).send().await?; @@ -89,7 +93,10 @@ impl SignerClient { Ok(signature) } - pub async fn request_consensus_signature(&self, request: SignConsensusRequest) -> Result { + pub async fn request_consensus_signature( + &self, + request: SignConsensusRequest, + ) -> Result { let request = SignRequest::Consensus(request); let raw_signature = self.request_signature(&request).await?; @@ -98,24 +105,34 @@ impl SignerClient { Ok(signature) } - async fn request_proxy_signature(&self, request: SignProxyRequest) -> Result, SignerClientError> { + async fn request_proxy_signature( + &self, + request: SignProxyRequest, + ) -> Result, SignerClientError> { let request = SignRequest::Proxy(request); self.request_signature(&request).await } - pub async fn request_proxy_ecdsa_signature(&self, request: SignProxyEcdsaRequest) -> Result { + pub async fn request_proxy_ecdsa_signature( + &self, + request: SignProxyEcdsaRequest, + ) -> Result { let raw_signature = self.request_proxy_signature(request.into()).await?; - let signature = EcdsaSignature::try_from(raw_signature.as_ref()).expect("unexpected invalid ECDSA signature"); + let signature = EcdsaSignature::try_from(raw_signature.as_ref()) + .expect("requested signature should be ECDSA"); Ok(signature) } - pub async fn request_proxy_bls_signature(&self, request: SignProxyBlsRequest) -> Result { + pub async fn request_proxy_bls_signature( + &self, + request: SignProxyBlsRequest, + ) -> Result { let raw_signature = self.request_proxy_signature(request.into()).await?; let signature = BlsSignature::from_slice(&raw_signature); Ok(signature) } - pub async fn generate_proxy_key( + async fn generate_proxy_key( &self, request: &GenerateProxyRequest, ) -> Result { @@ -136,4 +153,34 @@ impl SignerClient { Ok(signed_proxy_delegation) } + + pub async fn generate_bls_proxy_key( + &self, + consensus_pubkey: BlsPublicKey, + ) -> Result { + let request = GenerateProxyRequest::new(consensus_pubkey, EncryptionScheme::Bls); + + let bls_signed_proxy_delegation = self + .generate_proxy_key(&request) + .await? + .try_into() + .expect("generated proxy delegation should be BLS"); + + Ok(bls_signed_proxy_delegation) + } + + pub async fn generate_ecdsa_proxy_key( + &self, + consensus_pubkey: BlsPublicKey, + ) -> Result { + let request = GenerateProxyRequest::new(consensus_pubkey, EncryptionScheme::Ecdsa); + + let ecdsa_signed_proxy_delegation = self + .generate_proxy_key(&request) + .await? + .try_into() + .expect("generated proxy delegation should be BLS"); + + Ok(ecdsa_signed_proxy_delegation) + } } diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 9ebbee66..e124cb69 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::fmt::{self, Debug}; use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use serde::{Deserialize, Serialize}; @@ -7,10 +7,13 @@ use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - error::BlstErrorWrapper, signature::verify_signed_builder_message, signer::{schemes::ecdsa::EcdsaPublicKey, GenericPubkey}, + error::BlstErrorWrapper, + signature::verify_signed_builder_message, + signer::{schemes::ecdsa::EcdsaPublicKey, GenericPubkey}, types::Chain, }; +// GENERIC PROXY DELEGATION #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, TreeHash)] pub struct ProxyDelegation { pub delegator: BlsPublicKey, @@ -49,6 +52,142 @@ impl fmt::Display for SignedProxyDelegation { } } +// ECDSA PROXY DELEGATION +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, TreeHash)] +pub struct ProxyDelegationEcdsa { + pub delegator: BlsPublicKey, + pub proxy: EcdsaPublicKey, +} + +impl fmt::Display for ProxyDelegationEcdsa { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Delegator: {}\nProxy: {}", self.delegator, self.proxy) + } +} + +impl From for ProxyDelegation { + fn from(value: ProxyDelegationEcdsa) -> Self { + ProxyDelegation { delegator: value.delegator, proxy: GenericPubkey::Ecdsa(value.proxy) } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct SignedProxyDelegationEcdsa { + pub message: ProxyDelegationEcdsa, + /// Signature of message with the delegator keypair + pub signature: BlsSignature, +} + +impl SignedProxyDelegationEcdsa { + pub fn validate(&self, chain: Chain) -> Result<(), BlstErrorWrapper> { + verify_signed_builder_message( + chain, + &self.message.delegator, + &self.message, + &self.signature, + ) + } +} + +impl fmt::Display for SignedProxyDelegationEcdsa { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}\nSignature: {}", self.message, self.signature) + } +} + +impl From for SignedProxyDelegation { + fn from(value: SignedProxyDelegationEcdsa) -> Self { + SignedProxyDelegation { message: value.message.into(), signature: value.signature } + } +} + +impl TryFrom for ProxyDelegationEcdsa { + type Error = (); + + fn try_from(value: ProxyDelegation) -> Result { + Ok(ProxyDelegationEcdsa { delegator: value.delegator, proxy: value.proxy.try_into()? }) + } +} + +impl TryFrom for SignedProxyDelegationEcdsa { + type Error = (); + + fn try_from(value: SignedProxyDelegation) -> Result { + Ok(SignedProxyDelegationEcdsa { + message: value.message.try_into()?, + signature: value.signature, + }) + } +} + +// BLS PROXY DELEGATION +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, TreeHash)] +pub struct ProxyDelegationBls { + pub delegator: BlsPublicKey, + pub proxy: BlsPublicKey, +} + +impl fmt::Display for ProxyDelegationBls { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Delegator: {}\nProxy: {}", self.delegator, self.proxy) + } +} + +impl From for ProxyDelegation { + fn from(value: ProxyDelegationBls) -> Self { + ProxyDelegation { delegator: value.delegator, proxy: GenericPubkey::Bls(value.proxy) } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct SignedProxyDelegationBls { + pub message: ProxyDelegationBls, + /// Signature of message with the delegator keypair + pub signature: BlsSignature, +} + +impl SignedProxyDelegationBls { + pub fn validate(&self, chain: Chain) -> Result<(), BlstErrorWrapper> { + verify_signed_builder_message( + chain, + &self.message.delegator, + &self.message, + &self.signature, + ) + } +} + +impl fmt::Display for SignedProxyDelegationBls { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}\nSignature: {}", self.message, self.signature) + } +} + +impl From for SignedProxyDelegation { + fn from(value: SignedProxyDelegationBls) -> Self { + SignedProxyDelegation { message: value.message.into(), signature: value.signature } + } +} + +impl TryFrom for ProxyDelegationBls { + type Error = (); + + fn try_from(value: ProxyDelegation) -> Result { + Ok(ProxyDelegationBls { delegator: value.delegator, proxy: value.proxy.try_into()? }) + } +} + +impl TryFrom for SignedProxyDelegationBls { + type Error = (); + + fn try_from(value: SignedProxyDelegation) -> Result { + Ok(SignedProxyDelegationBls { + message: value.message.try_into()?, + signature: value.signature, + }) + } +} + // TODO(David): This struct shouldn't be visible in the client SDK #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SignRequest { @@ -87,24 +226,6 @@ pub struct SignProxyRequest { pub object_root: [u8; 32], } -impl SignProxyRequest { - pub fn new(pubkey: GenericPubkey, object_root: [u8; 32]) -> Self { - Self { pubkey, object_root } - } - - pub fn builder(pubkey: GenericPubkey) -> Self { - Self::new(pubkey, [0; 32]) - } - - pub fn with_root(self, object_root: [u8; 32]) -> Self { - Self { object_root, ..self } - } - - pub fn with_msg(self, msg: &impl TreeHash) -> Self { - self.with_root(msg.tree_hash_root().0) - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignProxyEcdsaRequest { pub pubkey: EcdsaPublicKey, @@ -131,7 +252,7 @@ impl SignProxyEcdsaRequest { impl From for SignProxyRequest { fn from(value: SignProxyEcdsaRequest) -> Self { - Self { pubkey: GenericPubkey::Ecdsa(value.pubkey), object_root: value.object_root } + Self { pubkey: value.pubkey.into(), object_root: value.object_root } } } @@ -161,7 +282,7 @@ impl SignProxyBlsRequest { impl From for SignProxyRequest { fn from(value: SignProxyBlsRequest) -> Self { - Self { pubkey: GenericPubkey::Bls(value.pubkey), object_root: value.object_root } + Self { pubkey: value.pubkey.into(), object_root: value.object_root } } } @@ -173,6 +294,7 @@ pub enum EncryptionScheme { Ecdsa, } +// TODO(David): This struct shouldn't be visible in the client SDK #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GenerateProxyRequest { pub consensus_pubkey: BlsPublicKey, diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 3606702b..0718a57c 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -2,10 +2,10 @@ use core::fmt; use std::hash::Hash; use derive_more::derive::{Deref, From, Into}; -use eyre::Result; use generic_array::GenericArray; use k256::ecdsa::{Signature as EcdsaSignatureInner, VerifyingKey as EcdsaPublicKeyInner}; use serde::{Deserialize, Serialize}; +use serde_utils::hex; use ssz_types::{ typenum::{U33, U64}, FixedVector, @@ -117,6 +117,19 @@ impl AsRef<[u8]> for EcdsaPublicKey { } } +impl fmt::LowerHex for EcdsaPublicKey { + 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)] pub struct EcdsaSignature { encoded: GenericArray, @@ -144,14 +157,7 @@ impl TryFrom<&[u8]> for EcdsaSignature { impl fmt::LowerHex for EcdsaSignature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "0x")?; - - let pubkey_bytes = self.as_ref(); - - for byte in pubkey_bytes { - write!(f, "{:02x}", byte)?; - } - + write!(f, "{}", hex::encode(self.as_ref()))?; Ok(()) } } @@ -186,10 +192,13 @@ impl EcdsaSigner { pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> EcdsaSignature { match self { - EcdsaSigner::Local(sk) => /*sign_builder_root(chain, sk, object_root),*/ { + EcdsaSigner::Local(sk) => + /* sign_builder_root(chain, sk, object_root), */ + { let domain = chain.builder_domain(); let signing_root = compute_signing_root(object_root, domain); - k256::ecdsa::signature::Signer::::sign(sk, &signing_root).into() + k256::ecdsa::signature::Signer::::sign(sk, &signing_root) + .into() } } } @@ -199,7 +208,11 @@ impl EcdsaSigner { } } -pub fn verify_ecdsa_signature(pubkey: &EcdsaPublicKey, msg: &[u8], signature: &EcdsaSignature) -> Result<(), k256::ecdsa::Error> { +pub fn verify_ecdsa_signature( + pubkey: &EcdsaPublicKey, + msg: &[u8], + 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(signature)?; diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 77d057a9..6866276a 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -2,8 +2,14 @@ use std::collections::HashMap; use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; use cb_common::{ - commit::request::{EncryptionScheme, ProxyDelegation, SignedProxyDelegation}, - signer::{schemes::ecdsa::{EcdsaPublicKey, EcdsaSignature}, BlsSigner, ConsensusSigner, EcdsaSigner, GenericPubkey}, + commit::request::{ + ProxyDelegationBls, ProxyDelegationEcdsa, SignedProxyDelegation, SignedProxyDelegationBls, + SignedProxyDelegationEcdsa, + }, + signer::{ + schemes::ecdsa::{EcdsaPublicKey, EcdsaSignature}, + BlsSigner, ConsensusSigner, EcdsaSigner, GenericPubkey, + }, types::{Chain, ModuleId}, }; use derive_more::derive::{Deref, From}; @@ -35,14 +41,14 @@ use crate::error::SignerModuleError; pub struct BlsProxySigner { #[deref] signer: BlsSigner, - delegation: SignedProxyDelegation, + delegation: SignedProxyDelegationBls, } #[derive(Clone, Deref)] pub struct EcdsaProxySigner { #[deref] signer: EcdsaSigner, - delegation: SignedProxyDelegation, + delegation: SignedProxyDelegationEcdsa, } #[derive(From)] @@ -61,8 +67,8 @@ impl GenericProxySigner { pub fn delegation(&self) -> SignedProxyDelegation { match self { - GenericProxySigner::Bls(proxy_signer) => proxy_signer.delegation, - GenericProxySigner::Ecdsa(proxy_signer) => proxy_signer.delegation, + GenericProxySigner::Bls(proxy_signer) => proxy_signer.delegation.into(), + GenericProxySigner::Ecdsa(proxy_signer) => proxy_signer.delegation.into(), } } @@ -185,48 +191,103 @@ impl SigningManager { self.proxy_signers.add(proxy); } - pub async fn create_proxy( + fn persist_proxy_signer(&mut self, proxy: GenericProxySigner, module_id: ModuleId) { + let proxy_pubkey = proxy.pubkey(); + self.add_proxy_signer(proxy); + self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey) + } + + pub async fn create_proxy_bls( &mut self, module_id: ModuleId, delegator: BlsPublicKey, - encryption_scheme: EncryptionScheme, - ) -> Result - { - let (proxy_signer, proxy_pubkey, signed_delegation) = match encryption_scheme { - EncryptionScheme::Bls => { - let signer = BlsSigner::new_random(); - let proxy_pubkey = signer.pubkey().into(); - - let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; - let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; - let delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; - (BlsProxySigner { signer, delegation }.into(), proxy_pubkey, delegation) - }, - EncryptionScheme::Ecdsa => { - let signer = EcdsaSigner::new_random(); - let proxy_pubkey = signer.pubkey().into(); - - let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; - let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; - let delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; - (EcdsaProxySigner { signer, delegation }.into(), proxy_pubkey, delegation) - }, - }; - // let signer = Signer::::new_random(); - // let proxy_pubkey = signer.pubkey().into(); - - // let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; - // let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; - // let signed_delegation: SignedProxyDelegation = SignedProxyDelegation { signature, message }; - // let proxy_signer = ProxySigner::new(signer, signed_delegation).into(); - - // Add the new proxy key to the manager's internal state - self.add_proxy_signer(proxy_signer); - self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey); - - Ok(signed_delegation) + ) -> Result { + let signer = BlsSigner::new_random(); + let proxy_pubkey = signer.pubkey(); + + let message = ProxyDelegationBls { delegator, proxy: proxy_pubkey }; + let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; + let delegation = SignedProxyDelegationBls { signature, message }; + let proxy_signer = BlsProxySigner { signer, delegation }; + + self.persist_proxy_signer(proxy_signer.into(), module_id); + + Ok(delegation) + } + + pub async fn create_proxy_ecdsa( + &mut self, + module_id: ModuleId, + delegator: BlsPublicKey, + ) -> Result { + let signer = EcdsaSigner::new_random(); + let proxy_pubkey = signer.pubkey(); + + let message = ProxyDelegationEcdsa { delegator, proxy: proxy_pubkey }; + let signature = self.sign_consensus(&delegator, &message.tree_hash_root().0).await?; + let delegation = SignedProxyDelegationEcdsa { signature, message }; + let proxy_signer = EcdsaProxySigner { signer, delegation }; + + self.persist_proxy_signer(proxy_signer.into(), module_id); + + Ok(delegation) } + // pub async fn create_proxy( + // &mut self, + // module_id: ModuleId, + // delegator: BlsPublicKey, + // encryption_scheme: EncryptionScheme, + // ) -> Result { + // let (proxy_signer, proxy_pubkey, signed_delegation) = match + // encryption_scheme { EncryptionScheme::Bls => { + // let signer = BlsSigner::new_random(); + // let proxy_pubkey = signer.pubkey().into(); + + // let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; + // let signature = + // self.sign_consensus(&delegator, + // &message.tree_hash_root().0).await?; let delegation: + // SignedProxyDelegation = SignedProxyDelegation { + // signature, message }; ( + // BlsProxySigner { signer, delegation: + // delegation.try_into().unwrap() }.into(), proxy_pubkey, + // delegation, + // ) + // } + // EncryptionScheme::Ecdsa => { + // let signer = EcdsaSigner::new_random(); + // let proxy_pubkey = signer.pubkey().into(); + + // let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; + // let signature = + // self.sign_consensus(&delegator, + // &message.tree_hash_root().0).await?; let delegation: + // SignedProxyDelegation = SignedProxyDelegation { + // signature, message }; ( + // EcdsaProxySigner { signer, delegation: + // delegation.try_into().unwrap() }.into(), proxy_pubkey, + // delegation, + // ) + // } + // }; + + // // let signer = Signer::::new_random(); + // // let proxy_pubkey = signer.pubkey().into(); + + // // let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; + // // let signature = self.sign_consensus(&delegator, + // // &message.tree_hash_root().0).await?; let signed_delegation: + // // SignedProxyDelegation = SignedProxyDelegation { signature, message }; + // // let proxy_signer = ProxySigner::new(signer, signed_delegation).into(); + + // // Add the new proxy key to the manager's internal state + // self.add_proxy_signer(proxy_signer); + // self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey); + + // Ok(signed_delegation) + // } + // TODO: double check what we can actually sign here with different providers eg // web3 signer pub async fn sign_consensus( @@ -257,14 +318,30 @@ impl SigningManager { // Ok(signature) // } - pub async fn sign_proxy_bls(&self, pubkey: &BlsPublicKey, object_root: &[u8; 32]) -> Result { - let bls_proxy = self.proxy_signers.bls_signers.get(pubkey).ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; + pub async fn sign_proxy_bls( + &self, + pubkey: &BlsPublicKey, + object_root: &[u8; 32], + ) -> Result { + let bls_proxy = self + .proxy_signers + .bls_signers + .get(pubkey) + .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; let signature = bls_proxy.sign(self.chain, *object_root).await; Ok(signature) } - pub async fn sign_proxy_ecdsa(&self, pubkey: &EcdsaPublicKey, object_root: &[u8; 32]) -> Result { - let ecdsa_proxy = self.proxy_signers.ecdsa_signers.get(pubkey).ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; + pub async fn sign_proxy_ecdsa( + &self, + pubkey: &EcdsaPublicKey, + 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; Ok(signature) } @@ -289,7 +366,9 @@ impl SigningManager { &self, pubkey: &GenericPubkey, ) -> Result { - let proxy = self.proxy_signers.get(&pubkey) + let proxy = self + .proxy_signers + .get(&pubkey) .ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec()))?; Ok(proxy.delegation()) @@ -298,7 +377,7 @@ impl SigningManager { #[cfg(test)] mod tests { - use cb_common::signature::compute_signing_root; + use cb_common::{signature::compute_signing_root, signer::schemes::bls::verify_bls_signature}; use lazy_static::lazy_static; use tree_hash::Hash256; @@ -325,7 +404,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let signed_delegation = signing_manager - .create_proxy(MODULE_ID.clone(), consensus_pk.clone(), EncryptionScheme::Bls) + .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) .await .unwrap(); @@ -337,7 +416,7 @@ mod tests { ); assert!( - signing_manager.has_proxy(&signed_delegation.message.proxy), + signing_manager.has_proxy(&signed_delegation.message.proxy.into()), "Newly generated proxy key must be present in the signing manager's registry." ); } @@ -347,7 +426,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let mut signed_delegation = signing_manager - .create_proxy(MODULE_ID.clone(), consensus_pk.clone(), EncryptionScheme::Bls) + .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) .await .unwrap(); @@ -364,7 +443,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let signed_delegation = signing_manager - .create_proxy(MODULE_ID.clone(), consensus_pk.clone(), EncryptionScheme::Bls) + .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) .await .unwrap(); let proxy_pk = signed_delegation.message.proxy; @@ -372,13 +451,16 @@ mod tests { let data_root = Hash256::random(); let data_root_bytes = data_root.as_fixed_bytes(); - let sig = signing_manager.sign_proxy_bls(&proxy_pk.try_into().unwrap(), data_root_bytes).await.unwrap(); + let sig = signing_manager + .sign_proxy_bls(&proxy_pk.try_into().unwrap(), data_root_bytes) + .await + .unwrap(); // Verify signature let domain = CHAIN.builder_domain(); let signing_root = compute_signing_root(data_root_bytes.tree_hash_root().0, domain); - let validation_result = proxy_pk.verify_signature(&signing_root, sig.as_ref()); + let validation_result = verify_bls_signature(&proxy_pk, &signing_root, &sig); assert!( validation_result.is_ok(), diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index daaaf38f..71900c51 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -14,10 +14,12 @@ use cb_common::{ commit::{ client::GetPubkeysResponse, constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, - request::{GenerateProxyRequest, SignConsensusRequest, SignProxyRequest, SignRequest}, + request::{ + EncryptionScheme, GenerateProxyRequest, SignConsensusRequest, SignProxyRequest, + SignRequest, SignedProxyDelegation, + }, }, config::StartSignerConfig, - signer::{BlsSecretKey, EcdsaSecretKey}, types::{Jwt, ModuleId}, }; use eyre::{Result, WrapErr}; @@ -169,20 +171,18 @@ async fn handle_generate_proxy( let mut signing_manager = state.manager.write().await; - let proxy_delegation = signing_manager - .create_proxy(module_id, request.consensus_pubkey, request.scheme) - .await?; - - // match request.scheme { - // EncryptionScheme::Bls => { + // let proxy_delegation = signing_manager + // .create_proxy(module_id, request.consensus_pubkey, request.scheme) + // .await?; - // } - // EncryptionScheme::Ecdsa => { - // signing_manager - // .create_proxy::(module_id, request.consensus_pubkey) - // .await? - // } - // }; + let proxy_delegation: SignedProxyDelegation = match request.scheme { + EncryptionScheme::Bls => { + signing_manager.create_proxy_bls(module_id, request.consensus_pubkey).await?.into() + } + EncryptionScheme::Ecdsa => { + signing_manager.create_proxy_ecdsa(module_id, request.consensus_pubkey).await?.into() + } + }; Ok((StatusCode::OK, Json(proxy_delegation)).into_response()) } diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index eaf4d9d1..e533a43a 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,7 +1,7 @@ use std::time::Duration; use alloy::rpc::types::beacon::BlsPublicKey; -use commit::request::{GenerateProxyRequest, SignProxyEcdsaRequest}; +use commit::request::SignProxyEcdsaRequest; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; use lazy_static::lazy_static; @@ -47,8 +47,7 @@ impl DaCommitService { let pubkey = pubkeys.consensus.first().ok_or_eyre("no key available")?; info!("Registered validator {pubkey}"); - let request = GenerateProxyRequest::new(*pubkey, commit::request::EncryptionScheme::Ecdsa); - let proxy_delegation = self.config.signer_client.generate_proxy_key(&request).await?; + let proxy_delegation = self.config.signer_client.generate_ecdsa_proxy_key(*pubkey).await?; info!("Obtained a proxy delegation:\n{proxy_delegation}"); let mut data = 0; @@ -64,14 +63,14 @@ impl DaCommitService { &self, data: u64, pubkey: BlsPublicKey, - proxy_delegation: SignedProxyDelegation, + proxy_delegation: SignedProxyDelegationEcdsa, ) -> Result<()> { let datagram = Datagram { data }; let request = SignConsensusRequest::builder(pubkey).with_msg(&datagram); let signature = self.config.signer_client.request_consensus_signature(request); - let ecdsa_proxy: EcdsaPublicKey = proxy_delegation.message.proxy.try_into().unwrap(); + let ecdsa_proxy: EcdsaPublicKey = proxy_delegation.message.proxy; let proxy_request = SignProxyEcdsaRequest::builder(ecdsa_proxy) .with_msg(&datagram); From fede09cb95c9c8cfe41d51a5d5d26b00b471cd11 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 21 Aug 2024 17:44:41 +0300 Subject: [PATCH 18/37] chore(signer)!: reformat & clean up --- bin/src/lib.rs | 7 +- config.example.toml | 17 +-- crates/common/src/signer/mod.rs | 41 ++------ crates/common/src/signer/schemes/bls.rs | 63 ++---------- crates/common/src/signer/schemes/ecdsa.rs | 43 +------- crates/common/src/signer/signer.rs | 32 +----- crates/signer/src/manager.rs | 120 +--------------------- crates/signer/src/service.rs | 35 ++----- examples/da_commit/src/main.rs | 6 +- tests/tests/pbs_integration.rs | 5 +- 10 files changed, 49 insertions(+), 320 deletions(-) diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 79d1336f..78c8c8b9 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -1,13 +1,16 @@ pub mod prelude { pub use cb_common::{ commit, - commit::request::{SignConsensusRequest, SignedProxyDelegation, SignedProxyDelegationEcdsa, SignedProxyDelegationBls}, - signer::{EcdsaPublicKey, EcdsaSignature}, + commit::request::{ + SignConsensusRequest, SignedProxyDelegation, SignedProxyDelegationBls, + SignedProxyDelegationEcdsa, + }, config::{ load_builder_module_config, load_commit_module_config, load_pbs_config, load_pbs_custom_config, StartCommitModuleConfig, }, pbs::{BuilderEvent, BuilderEventClient, OnBuilderApiEvent}, + signer::{EcdsaPublicKey, EcdsaSignature}, utils::{ initialize_pbs_tracing_log, initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, utcnow_us, diff --git a/config.example.toml b/config.example.toml index 95633156..d067c466 100644 --- a/config.example.toml +++ b/config.example.toml @@ -1,4 +1,4 @@ -# The main configuration file for the Commit-Boost sidecar. +# The main configuration file for the Commit-Boost sidecar. # Some fields are optional and can be omitted, in which case the default value, if present, will be used. # Chain spec id. Supported values: Mainnet, Holesky, Helder @@ -18,7 +18,7 @@ port = 18550 # Whether to forward `status` calls to relays or skip and return 200 # OPTIONAL, DEFAULT: true relay_check = true -# Timeout in milliseconds for the `get_header` call to relays. Note that the CL has also a timeout (e.g. 1 second) so +# Timeout in milliseconds for the `get_header` call to relays. Note that the CL has also a timeout (e.g. 1 second) so # this should be lower than that, leaving some margin for overhead # OPTIONAL, DEFAULT: 950 timeout_get_header_ms = 950 @@ -34,10 +34,11 @@ skip_sigverify = false # Minimum bid in ETH that will be accepted from `get_header` # OPTIONAL, DEFAULT: 0.0 min_bid_eth = 0.0 +# How late in milliseconds in the slot is "late". This impacts the `get_header` requests, by shortening timeouts for `get_header` calls to # List of URLs of relay monitors to send registrations to # OPTIONAL relay_monitors = [] -# How late in milliseconds in the slot is "late". This impacts the `get_header` requests, by shortening timeouts for `get_header` calls to +# How late in milliseconds in the slot is "late". This impacts the `get_header` requests, by shortening timeouts for `get_header` calls to # relays and make sure a header is returned within this deadline. If the request from the CL comes later in the slot, then fetching headers is skipped # to force local building and miniminzing the risk of missed slots. See also the timing games section below # OPTIONAL, DEFAULT: 2000 @@ -55,12 +56,12 @@ url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb headers = { X-MyCustomHeader = "MyCustomValue" } # Whether to enable timing games, as tuned by `target_first_request_ms` and `frequency_get_header_ms`. # These values should be carefully chosen for each relay, as each relay has different latency and timing games setups. -# They should only be used by advanced users, and if mis-configured can result in unforeseen effects, e.g. fetching a lower header value, +# They should only be used by advanced users, and if mis-configured can result in unforeseen effects, e.g. fetching a lower header value, # or getting a temporary IP ban. -# +# # EXAMPLES # Assuming: timeout_get_header_ms = 950, frequency_get_header_ms = 300, target_first_request_ms = 200, late_in_slot_time_ms = 2000 -# +# # 1) CL request comes at 100ms in the slot (max timeout 1050ms in the slot), then: # - sleep for 100ms # - send request at 200ms with 850ms timeout @@ -112,13 +113,13 @@ id = "DA_COMMIT" type = "commit" # Docker image of the module docker_image = "test_da_commit" -# Additional config needed by the business logic of the module should also be set here. +# Additional config needed by the business logic of the module should also be set here. # See also `examples/da_commit/src/main.rs` for more information sleep_secs = 5 # Configuration for how metrics should be collected and scraped [metrics] -# Path to a `prometheus.yml` file to use in Prometheus. If using a custom config file, be sure to add a +# Path to a `prometheus.yml` file to use in Prometheus. If using a custom config file, be sure to add a # file discovery section as follows: # ```yml # file_sd_configs: diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 11f28343..49bd82c6 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -1,6 +1,4 @@ -use std::{ - fmt::{self, LowerHex}, -}; +use std::fmt::{self, LowerHex}; use alloy::rpc::types::beacon::BlsPublicKey; use eyre::Context; @@ -12,38 +10,11 @@ pub mod schemes; #[allow(clippy::module_inception)] mod signer; -pub use schemes::{bls::BlsSecretKey, ecdsa::EcdsaSecretKey, ecdsa::EcdsaPublicKey, ecdsa::EcdsaSignature}; -pub use signer::{ConsensusSigner, BlsSigner, EcdsaSigner}; - -// pub type Pubkey = ::PublicKey; - -// pub trait SecretKey { -// type PublicKey: AsRef<[u8]> + Clone + Verifier; -// type Signature: AsRef<[u8]> + Clone; - -// fn new_random() -> Self; -// fn new_from_bytes(bytes: &[u8]) -> eyre::Result -// where -// Self: Sized; -// fn pubkey(&self) -> Self::PublicKey; -// fn sign(&self, msg: &[u8]) -> Self::Signature; -// fn sign_msg(&self, msg: &impl TreeHash) -> Self::Signature { -// self.sign(&msg.tree_hash_root().0) -// } -// } - -// pub trait Verifier -// where -// T: ?Sized, -// { -// type VerificationError: Error; - -// fn verify_signature( -// &self, -// msg: &[u8], -// signature: &T::Signature, -// ) -> Result<(), Self::VerificationError>; -// } +pub use schemes::{ + bls::BlsSecretKey, + ecdsa::{EcdsaPublicKey, EcdsaSecretKey, EcdsaSignature}, +}; +pub use signer::{BlsSigner, ConsensusSigner, EcdsaSigner}; #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode)] #[serde(untagged)] diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index ddfc9153..cbfc66cb 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -3,7 +3,8 @@ use blst::BLST_ERROR; use tree_hash::TreeHash; use crate::{ - error::BlstErrorWrapper, signature::sign_builder_root, signer::GenericPubkey, types::Chain, utils::blst_pubkey_to_alloy + error::BlstErrorWrapper, signature::sign_builder_root, signer::GenericPubkey, types::Chain, + utils::blst_pubkey_to_alloy, }; pub type BlsSecretKey = blst::min_pk::SecretKey; @@ -54,39 +55,11 @@ fn random_secret() -> BlsSecretKey { } } -// impl SecretKey for BlsSecretKey { -// // type PublicKey = BlsPublicKey; -// // type Signature = BlsSignature; - -// // fn new_random() -> Self { -// // use rand::RngCore; - -// // let mut rng = rand::thread_rng(); -// // let mut ikm = [0u8; 32]; -// // rng.fill_bytes(&mut ikm); - -// // match BlsSecretKey::key_gen(&ikm, &[]) { -// // Ok(key) => key, -// // // Key material is always valid (32 `u8`s), so `key_gen` can't return Err. -// // Err(_) => unreachable!(), -// // } -// // } - -// fn new_from_bytes(bytes: &[u8]) -> eyre::Result { -// Ok(BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?) -// } - -// fn pubkey(&self) -> Self::PublicKey { -// blst_pubkey_to_alloy(&self.sk_to_pk()) -// } - -// fn sign(&self, msg: &[u8]) -> Self::Signature { -// let signature = self.sign(msg, BLS_DST_SIG, &[]).to_bytes(); -// BlsSignature::from_slice(&signature) -// } -// } - -pub fn verify_bls_signature(pubkey: &BlsPublicKey, msg: &[u8], signature: &BlsSignature) -> Result<(), BlstErrorWrapper> { +pub fn verify_bls_signature( + pubkey: &BlsPublicKey, + msg: &[u8], + signature: &BlsSignature, +) -> Result<(), BlstErrorWrapper> { use crate::utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}; let pubkey = alloy_pubkey_to_blst(pubkey)?; @@ -100,28 +73,6 @@ pub fn verify_bls_signature(pubkey: &BlsPublicKey, msg: &[u8], signature: &BlsSi } } -// impl Verifier for Pubkey { -// type VerificationError = BlstErrorWrapper; - -// fn verify_signature( -// &self, -// msg: &[u8], -// signature: &::Signature, -// ) -> Result<(), Self::VerificationError> { -// use crate::utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}; - -// let pubkey = alloy_pubkey_to_blst(self)?; -// let signature = alloy_sig_to_blst(signature)?; - -// let res = signature.verify(true, msg, BLS_DST_SIG, &[], &pubkey, true); -// if res == BLST_ERROR::BLST_SUCCESS { -// Ok(()) -// } else { -// Err(res.into()) -// } -// } -// } - impl From for GenericPubkey { fn from(value: BlsPublicKey) -> Self { GenericPubkey::Bls(value) diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 0718a57c..495d3c30 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -192,9 +192,7 @@ impl EcdsaSigner { pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> EcdsaSignature { match self { - EcdsaSigner::Local(sk) => - /* sign_builder_root(chain, sk, object_root), */ - { + EcdsaSigner::Local(sk) => { let domain = chain.builder_domain(); let signing_root = compute_signing_root(object_root, domain); k256::ecdsa::signature::Signer::::sign(sk, &signing_root) @@ -208,6 +206,8 @@ impl EcdsaSigner { } } +// TODO(David): Add tests for this verification, most probably not properly +// working yet pub fn verify_ecdsa_signature( pubkey: &EcdsaPublicKey, msg: &[u8], @@ -219,43 +219,6 @@ pub fn verify_ecdsa_signature( ecdsa_pubkey.verify(msg, &ecdsa_sig) } -// impl SecretKey for EcdsaSecretKey { -// type PublicKey = EcdsaPublicKey; - -// type Signature = EcdsaSignature; - -// fn new_random() -> Self { -// EcdsaSecretKey::random(&mut rand::thread_rng()) -// } - -// fn new_from_bytes(bytes: &[u8]) -> Result { -// Ok(EcdsaSecretKey::from_slice(bytes)?) -// } - -// fn pubkey(&self) -> Self::PublicKey { -// EcdsaPublicKeyInner::from(self).into() -// } - -// fn sign(&self, msg: &[u8]) -> Self::Signature { -// k256::ecdsa::signature::Signer::::sign(self, msg).into() -// } -// } - -// impl Verifier for Pubkey { -// type VerificationError = k256::ecdsa::Error; - -// fn verify_signature( -// &self, -// msg: &[u8], -// signature: &::Signature, -// ) -> Result<(), Self::VerificationError> { -// use k256::ecdsa::signature::Verifier; -// let ecdsa_pubkey = EcdsaPublicKeyInner::from_sec1_bytes(&self.encoded)?; -// let ecdsa_sig = EcdsaSignatureInner::from_bytes(signature)?; -// ecdsa_pubkey.verify(msg, &ecdsa_sig) -// } -// } - impl From for GenericPubkey { fn from(value: EcdsaPublicKey) -> Self { GenericPubkey::Ecdsa(value) diff --git a/crates/common/src/signer/signer.rs b/crates/common/src/signer/signer.rs index 562a5460..1dcb6169 100644 --- a/crates/common/src/signer/signer.rs +++ b/crates/common/src/signer/signer.rs @@ -1,34 +1,4 @@ +// TODO(David): Remove module pub use super::schemes::bls::BlsSigner; pub type ConsensusSigner = BlsSigner; pub type EcdsaSigner = super::schemes::ecdsa::EcdsaSigner; - -// #[derive(Clone)] -// pub enum Signer { -// Local(T), -// } - -// impl Signer { -// pub fn new_random() -> Self { -// Signer::Local(T::new_random()) -// } - -// pub fn new_from_bytes(bytes: &[u8]) -> Result { -// T::new_from_bytes(bytes).map(Self::Local) -// } - -// pub fn pubkey(&self) -> T::PublicKey { -// match self { -// Signer::Local(secret) => secret.pubkey(), -// } -// } - -// pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> T::Signature { -// match self { -// Signer::Local(sk) => sign_builder_root(chain, sk, object_root), -// } -// } - -// pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> T::Signature { -// self.sign(chain, msg.tree_hash_root().0).await -// } -// } diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 6866276a..ead4f2cc 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -23,20 +23,6 @@ use crate::error::SignerModuleError; // with its consensus pubkey When a new commit module starts, pass the // ProxyDelegation msg and then sign all future commit messages with the proxy // key for slashing the faulty message + proxy delegation can be used -// Signed using builder domain -// #[derive(Clone, Deref)] -// pub struct ProxySigner { -// #[deref] -// signer: Signer, -// delegation: SignedProxyDelegation, -// } - -// impl ProxySigner { -// pub fn new(signer: Signer, delegation: SignedProxyDelegation) -> Self { -// Self { signer, delegation } -// } -// } - #[derive(Clone, Deref)] pub struct BlsProxySigner { #[deref] @@ -125,43 +111,8 @@ impl ProxySigners { GenericPubkey::Ecdsa(ecdsa_pk) => self.ecdsa_signers.contains_key(ecdsa_pk), } } - - // pub fn find_pubkey<'a>(&'a self, pubkey: &[u8]) -> Option { - // fn find_typed<'a, T>( - // keys: impl IntoIterator>, - // pubkey: &[u8], - // ) -> Option - // where - // T: SecretKey, - // Pubkey: 'a + Into, - // { - // keys.into_iter().find(|x| x.as_ref() == pubkey).cloned().map(Into::into) - // } - - // find_typed::(self.bls_signers.keys(), pubkey) - // .or_else(|| find_typed::(self.ecdsa_signers.keys(), pubkey)) - // } } -// trait GetProxySigner { -// fn get_proxy_signer(&self, pk: &Pubkey) -> Option<&ProxySigner>; -// } - -// impl GetProxySigner for ProxySigners { -// fn get_proxy_signer(&self, pk: &BlsPublicKey) -> Option<&ProxySigner> { -// self.bls_signers.get(pk) -// } -// } - -// impl GetProxySigner for ProxySigners { -// fn get_proxy_signer( -// &self, -// pk: &EcdsaPublicKey, -// ) -> Option<&ProxySigner> { -// self.ecdsa_signers.get(pk) -// } -// } - pub struct SigningManager { chain: Chain, consensus_signers: HashMap, @@ -233,61 +184,6 @@ impl SigningManager { Ok(delegation) } - // pub async fn create_proxy( - // &mut self, - // module_id: ModuleId, - // delegator: BlsPublicKey, - // encryption_scheme: EncryptionScheme, - // ) -> Result { - // let (proxy_signer, proxy_pubkey, signed_delegation) = match - // encryption_scheme { EncryptionScheme::Bls => { - // let signer = BlsSigner::new_random(); - // let proxy_pubkey = signer.pubkey().into(); - - // let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; - // let signature = - // self.sign_consensus(&delegator, - // &message.tree_hash_root().0).await?; let delegation: - // SignedProxyDelegation = SignedProxyDelegation { - // signature, message }; ( - // BlsProxySigner { signer, delegation: - // delegation.try_into().unwrap() }.into(), proxy_pubkey, - // delegation, - // ) - // } - // EncryptionScheme::Ecdsa => { - // let signer = EcdsaSigner::new_random(); - // let proxy_pubkey = signer.pubkey().into(); - - // let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; - // let signature = - // self.sign_consensus(&delegator, - // &message.tree_hash_root().0).await?; let delegation: - // SignedProxyDelegation = SignedProxyDelegation { - // signature, message }; ( - // EcdsaProxySigner { signer, delegation: - // delegation.try_into().unwrap() }.into(), proxy_pubkey, - // delegation, - // ) - // } - // }; - - // // let signer = Signer::::new_random(); - // // let proxy_pubkey = signer.pubkey().into(); - - // // let message = ProxyDelegation { delegator, proxy: proxy_pubkey }; - // // let signature = self.sign_consensus(&delegator, - // // &message.tree_hash_root().0).await?; let signed_delegation: - // // SignedProxyDelegation = SignedProxyDelegation { signature, message }; - // // let proxy_signer = ProxySigner::new(signer, signed_delegation).into(); - - // // Add the new proxy key to the manager's internal state - // self.add_proxy_signer(proxy_signer); - // self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey); - - // Ok(signed_delegation) - // } - // TODO: double check what we can actually sign here with different providers eg // web3 signer pub async fn sign_consensus( @@ -304,20 +200,6 @@ impl SigningManager { Ok(signature) } - // pub async fn sign_proxy( - // &self, - // pubkey: &[u8], - // object_root: &[u8; 32], - // ) -> Result, SignerModuleError> { - // let proxy = self - // .find_proxy(pubkey) - // .ok_or(SignerModuleError::UnknownProxySigner(pubkey.to_vec()))?; - - // let signature = proxy.sign(self.chain, *object_root).await; - - // Ok(signature) - // } - pub async fn sign_proxy_bls( &self, pubkey: &BlsPublicKey, @@ -368,7 +250,7 @@ impl SigningManager { ) -> Result { let proxy = self .proxy_signers - .get(&pubkey) + .get(pubkey) .ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec()))?; Ok(proxy.delegation()) diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 71900c51..6aa9438a 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -133,30 +133,19 @@ async fn handle_request_signature( let sig = match request { SignRequest::Consensus(SignConsensusRequest { pubkey, object_root }) => { signing_manager.sign_consensus(&pubkey, &object_root).await.map(|sig| sig.to_vec()) - }, - SignRequest::Proxy(SignProxyRequest { pubkey, object_root }) => { - match pubkey { - cb_common::signer::GenericPubkey::Bls(bls_pubkey) => { - signing_manager.sign_proxy_bls(&bls_pubkey, &object_root).await.map(|sig| sig.to_vec()) - }, - cb_common::signer::GenericPubkey::Ecdsa(ecdsa_pubkey) => { - signing_manager.sign_proxy_ecdsa(&ecdsa_pubkey, &object_root).await.map(|sig| sig.to_vec()) - }, - } + } + SignRequest::Proxy(SignProxyRequest { pubkey, object_root }) => match pubkey { + cb_common::signer::GenericPubkey::Bls(bls_pubkey) => signing_manager + .sign_proxy_bls(&bls_pubkey, &object_root) + .await + .map(|sig| sig.to_vec()), + cb_common::signer::GenericPubkey::Ecdsa(ecdsa_pubkey) => signing_manager + .sign_proxy_ecdsa(&ecdsa_pubkey, &object_root) + .await + .map(|sig| sig.to_vec()), }, }?; - // let sig = if request.is_proxy { - // signing_manager.sign_proxy(&request.pubkey, &request.object_root).await - // } else { - // let pubkey = request - // .pubkey - // .as_slice() - // .try_into() - // .map_err(|_| SignerModuleError::UnknownConsensusSigner(request.pubkey.clone()))?; - // signing_manager.sign_consensus(pubkey, &request.object_root).await.map(|x| x.to_vec()) - // }?; - Ok((StatusCode::OK, Json(sig)).into_response()) } @@ -171,10 +160,6 @@ async fn handle_generate_proxy( let mut signing_manager = state.manager.write().await; - // let proxy_delegation = signing_manager - // .create_proxy(module_id, request.consensus_pubkey, request.scheme) - // .await?; - let proxy_delegation: SignedProxyDelegation = match request.scheme { EncryptionScheme::Bls => { signing_manager.create_proxy_bls(module_id, request.consensus_pubkey).await?.into() diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index e533a43a..f3b70696 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -72,9 +72,9 @@ impl DaCommitService { let ecdsa_proxy: EcdsaPublicKey = proxy_delegation.message.proxy; - let proxy_request = SignProxyEcdsaRequest::builder(ecdsa_proxy) - .with_msg(&datagram); - let proxy_signature = self.config.signer_client.request_proxy_ecdsa_signature(proxy_request); + let proxy_request = SignProxyEcdsaRequest::builder(ecdsa_proxy).with_msg(&datagram); + let proxy_signature = + self.config.signer_client.request_proxy_ecdsa_signature(proxy_request); let (signature, proxy_signature) = { let res = tokio::join!(signature, proxy_signature); diff --git a/tests/tests/pbs_integration.rs b/tests/tests/pbs_integration.rs index 2b127bfe..fc481c19 100644 --- a/tests/tests/pbs_integration.rs +++ b/tests/tests/pbs_integration.rs @@ -2,7 +2,10 @@ use std::{net::SocketAddr, sync::Arc, time::Duration, u64}; use alloy::primitives::U256; use cb_common::{ - config::{PbsConfig, PbsModuleConfig}, pbs::RelayClient, signer::ConsensusSigner, types::Chain + config::{PbsConfig, PbsModuleConfig}, + pbs::RelayClient, + signer::ConsensusSigner, + types::Chain, }; use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use cb_tests::{ From 68fce4360081781f177447d9f351ffe172d80639 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 22 Aug 2024 11:56:32 +0300 Subject: [PATCH 19/37] refactor(signer/manager): remove enum `GenericProxySigner` --- crates/signer/src/manager.rs | 112 ++++++++--------------------------- 1 file changed, 26 insertions(+), 86 deletions(-) diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index ead4f2cc..5204604d 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -12,7 +12,7 @@ use cb_common::{ }, types::{Chain, ModuleId}, }; -use derive_more::derive::{Deref, From}; +use derive_more::derive::Deref; use tree_hash::TreeHash; use crate::error::SignerModuleError; @@ -37,87 +37,16 @@ pub struct EcdsaProxySigner { delegation: SignedProxyDelegationEcdsa, } -#[derive(From)] -pub enum GenericProxySigner { - Bls(BlsProxySigner), - Ecdsa(EcdsaProxySigner), -} - -impl GenericProxySigner { - pub fn pubkey(&self) -> GenericPubkey { - match self { - GenericProxySigner::Bls(proxy_signer) => GenericPubkey::Bls(proxy_signer.pubkey()), - GenericProxySigner::Ecdsa(proxy_signer) => GenericPubkey::Ecdsa(proxy_signer.pubkey()), - } - } - - pub fn delegation(&self) -> SignedProxyDelegation { - match self { - GenericProxySigner::Bls(proxy_signer) => proxy_signer.delegation.into(), - GenericProxySigner::Ecdsa(proxy_signer) => proxy_signer.delegation.into(), - } - } - - pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> Vec { - match self { - GenericProxySigner::Bls(proxy_signer) => { - proxy_signer.sign(chain, object_root).await.to_vec() - } - GenericProxySigner::Ecdsa(proxy_signer) => { - proxy_signer.sign(chain, object_root).await.to_vec() - } - } - } - - pub async fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { - self.pubkey().verify_signature(msg, signature) - } -} - #[derive(Default)] struct ProxySigners { bls_signers: HashMap, ecdsa_signers: HashMap, } -impl ProxySigners { - pub fn get(&self, key: &GenericPubkey) -> Option { - match key { - GenericPubkey::Bls(bls_pubkey) => { - let proxy_signer = self.bls_signers.get(bls_pubkey)?; - Some(GenericProxySigner::Bls(proxy_signer.clone())) - } - GenericPubkey::Ecdsa(ecdsa_pubkey) => { - let proxy_signer = self.ecdsa_signers.get(ecdsa_pubkey)?; - Some(GenericProxySigner::Ecdsa(proxy_signer.clone())) - } - } - } - - pub fn add(&mut self, proxy: GenericProxySigner) { - match proxy { - GenericProxySigner::Bls(bls_proxy) => { - self.bls_signers.insert(bls_proxy.pubkey(), bls_proxy); - } - GenericProxySigner::Ecdsa(ecdsa_proxy) => { - self.ecdsa_signers.insert(ecdsa_proxy.pubkey(), ecdsa_proxy); - } - } - } - - pub fn has_proxy(&self, pubkey: &GenericPubkey) -> bool { - match pubkey { - GenericPubkey::Bls(bls_pk) => self.bls_signers.contains_key(bls_pk), - GenericPubkey::Ecdsa(ecdsa_pk) => self.ecdsa_signers.contains_key(ecdsa_pk), - } - } -} - pub struct SigningManager { chain: Chain, consensus_signers: HashMap, - proxy_signers: ProxySigners, // HashMap, ProxySigner>, - // proxy_delegations: + proxy_signers: ProxySigners, /// Map of module ids to their associated proxy pubkeys. /// Used to retrieve the corresponding proxy signer from the signing /// manager. @@ -138,14 +67,16 @@ impl SigningManager { self.consensus_signers.insert(signer.pubkey(), signer); } - pub fn add_proxy_signer(&mut self, proxy: GenericProxySigner) { - self.proxy_signers.add(proxy); + pub fn add_proxy_signer_bls(&mut self, proxy: BlsProxySigner, module_id: ModuleId) { + let proxy_pubkey = proxy.pubkey(); + self.proxy_signers.bls_signers.insert(proxy.pubkey(), proxy); + self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey.into()) } - fn persist_proxy_signer(&mut self, proxy: GenericProxySigner, module_id: ModuleId) { + pub fn add_proxy_signer_ecdsa(&mut self, proxy: EcdsaProxySigner, module_id: ModuleId) { let proxy_pubkey = proxy.pubkey(); - self.add_proxy_signer(proxy); - self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey) + self.proxy_signers.ecdsa_signers.insert(proxy.pubkey(), proxy); + self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey.into()) } pub async fn create_proxy_bls( @@ -161,7 +92,7 @@ impl SigningManager { let delegation = SignedProxyDelegationBls { signature, message }; let proxy_signer = BlsProxySigner { signer, delegation }; - self.persist_proxy_signer(proxy_signer.into(), module_id); + self.add_proxy_signer_bls(proxy_signer, module_id); Ok(delegation) } @@ -179,7 +110,7 @@ impl SigningManager { let delegation = SignedProxyDelegationEcdsa { signature, message }; let proxy_signer = EcdsaProxySigner { signer, delegation }; - self.persist_proxy_signer(proxy_signer.into(), module_id); + self.add_proxy_signer_ecdsa(proxy_signer, module_id); Ok(delegation) } @@ -241,19 +172,28 @@ impl SigningManager { } pub fn has_proxy(&self, pubkey: &GenericPubkey) -> bool { - self.proxy_signers.has_proxy(pubkey) + match pubkey { + GenericPubkey::Bls(bls_pk) => self.proxy_signers.bls_signers.contains_key(bls_pk), + GenericPubkey::Ecdsa(ecdsa_pk) => { + self.proxy_signers.ecdsa_signers.contains_key(ecdsa_pk) + } + } } pub fn get_delegation( &self, pubkey: &GenericPubkey, ) -> Result { - let proxy = self - .proxy_signers - .get(pubkey) - .ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec()))?; + let delegation: Option = match pubkey { + GenericPubkey::Bls(bls_pk) => { + self.proxy_signers.bls_signers.get(bls_pk).map(|x| x.delegation).map(Into::into) + } + GenericPubkey::Ecdsa(ecdsa_pk) => { + self.proxy_signers.ecdsa_signers.get(ecdsa_pk).map(|x| x.delegation).map(Into::into) + } + }; - Ok(proxy.delegation()) + delegation.ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec())) } } From a7d53a15ed13942d361008f28e0f9886516f4083 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 22 Aug 2024 12:41:23 +0300 Subject: [PATCH 20/37] refactor(signer)!: make `SignedProxyDelegation` generic in the public key type * also, introduce the `PublicKey` trait * TODO: a custom wrapper type around `BlsPublicKey` was necessary in order to implement `TreeHash` trait for it. Refine the boundaries of usage of alloy's type and our custom wrapper. --- crates/common/src/commit/client.rs | 36 +++-- crates/common/src/commit/request.rs | 172 ++++------------------ crates/common/src/pbs/types/get_header.rs | 2 +- crates/common/src/signer/mod.rs | 13 +- crates/common/src/signer/schemes/bls.rs | 31 +++- crates/signer/src/manager.rs | 53 +++++-- crates/signer/src/service.rs | 14 +- examples/da_commit/src/main.rs | 8 +- tests/src/mock_relay.rs | 2 +- tests/tests/pbs_integration.rs | 10 +- 10 files changed, 138 insertions(+), 203 deletions(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 23e87257..556d529b 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::{BlsPublicKey, BlsSignature}; +use alloy::rpc::types::beacon::BlsSignature; use eyre::WrapErr; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use serde::{Deserialize, Serialize}; @@ -9,13 +9,16 @@ use super::{ constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, error::SignerClientError, request::{ - EncryptionScheme, GenerateProxyRequest, SignConsensusRequest, SignProxyBlsRequest, - SignProxyEcdsaRequest, SignProxyRequest, SignRequest, SignedProxyDelegation, - SignedProxyDelegationBls, SignedProxyDelegationEcdsa, + EncryptionScheme, GenerateProxyRequest, PublicKey, SignConsensusRequest, + SignProxyBlsRequest, SignProxyEcdsaRequest, SignProxyRequest, SignRequest, + SignedProxyDelegation, }, }; use crate::{ - signer::{schemes::ecdsa::EcdsaSignature, GenericPubkey}, + signer::{ + schemes::{bls::BlsPublicKey, ecdsa::EcdsaSignature}, + EcdsaPublicKey, GenericPubkey, + }, DEFAULT_REQUEST_TIMEOUT, }; @@ -132,10 +135,13 @@ impl SignerClient { Ok(signature) } - async fn generate_proxy_key( + async fn generate_proxy_key( &self, request: &GenerateProxyRequest, - ) -> Result { + ) -> Result, SignerClientError> + where + T: PublicKey + for<'de> Deserialize<'de>, + { let url = format!("{}{}", self.url, GENERATE_PROXY_KEY_PATH); let res = self.client.post(&url).json(&request).send().await?; @@ -157,14 +163,10 @@ impl SignerClient { pub async fn generate_bls_proxy_key( &self, consensus_pubkey: BlsPublicKey, - ) -> Result { + ) -> Result, SignerClientError> { let request = GenerateProxyRequest::new(consensus_pubkey, EncryptionScheme::Bls); - let bls_signed_proxy_delegation = self - .generate_proxy_key(&request) - .await? - .try_into() - .expect("generated proxy delegation should be BLS"); + let bls_signed_proxy_delegation = self.generate_proxy_key(&request).await?; Ok(bls_signed_proxy_delegation) } @@ -172,14 +174,10 @@ impl SignerClient { pub async fn generate_ecdsa_proxy_key( &self, consensus_pubkey: BlsPublicKey, - ) -> Result { + ) -> Result, SignerClientError> { let request = GenerateProxyRequest::new(consensus_pubkey, EncryptionScheme::Ecdsa); - let ecdsa_signed_proxy_delegation = self - .generate_proxy_key(&request) - .await? - .try_into() - .expect("generated proxy delegation should be BLS"); + let ecdsa_signed_proxy_delegation = self.generate_proxy_key(&request).await?; Ok(ecdsa_signed_proxy_delegation) } diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index e124cb69..fe53cd04 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -1,7 +1,8 @@ -use std::fmt::{self, Debug}; +use std::fmt::{self, Debug, Display, LowerHex}; -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use alloy::rpc::types::beacon::BlsSignature; use serde::{Deserialize, Serialize}; +use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; @@ -9,144 +10,51 @@ use tree_hash_derive::TreeHash; use crate::{ error::BlstErrorWrapper, signature::verify_signed_builder_message, - signer::{schemes::ecdsa::EcdsaPublicKey, GenericPubkey}, + signer::{ + schemes::{bls::BlsPublicKey, ecdsa::EcdsaPublicKey}, + GenericPubkey, + }, types::Chain, }; -// GENERIC PROXY DELEGATION -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, TreeHash)] -pub struct ProxyDelegation { - pub delegator: BlsPublicKey, - pub proxy: GenericPubkey, -} - -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 message: ProxyDelegation, - /// Signature of message with the delegator keypair - pub signature: BlsSignature, +pub trait PublicKey: + AsRef<[u8]> + Debug + Clone + Copy + Encode + Decode + TreeHash + Display + LowerHex +{ } -impl SignedProxyDelegation { - pub fn validate(&self, chain: Chain) -> Result<(), BlstErrorWrapper> { - verify_signed_builder_message( - chain, - &self.message.delegator, - &self.message, - &self.signature, - ) - } -} +impl PublicKey for EcdsaPublicKey {} -impl fmt::Display for SignedProxyDelegation { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}\nSignature: {}", self.message, self.signature) - } -} +impl PublicKey for BlsPublicKey {} -// ECDSA PROXY DELEGATION +// GENERIC PROXY DELEGATION #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, TreeHash)] -pub struct ProxyDelegationEcdsa { +pub struct ProxyDelegation { pub delegator: BlsPublicKey, - pub proxy: EcdsaPublicKey, -} - -impl fmt::Display for ProxyDelegationEcdsa { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Delegator: {}\nProxy: {}", self.delegator, self.proxy) - } -} - -impl From for ProxyDelegation { - fn from(value: ProxyDelegationEcdsa) -> Self { - ProxyDelegation { delegator: value.delegator, proxy: GenericPubkey::Ecdsa(value.proxy) } - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct SignedProxyDelegationEcdsa { - pub message: ProxyDelegationEcdsa, - /// Signature of message with the delegator keypair - pub signature: BlsSignature, -} - -impl SignedProxyDelegationEcdsa { - pub fn validate(&self, chain: Chain) -> Result<(), BlstErrorWrapper> { - verify_signed_builder_message( - chain, - &self.message.delegator, - &self.message, - &self.signature, - ) - } -} - -impl fmt::Display for SignedProxyDelegationEcdsa { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}\nSignature: {}", self.message, self.signature) - } -} - -impl From for SignedProxyDelegation { - fn from(value: SignedProxyDelegationEcdsa) -> Self { - SignedProxyDelegation { message: value.message.into(), signature: value.signature } - } -} - -impl TryFrom for ProxyDelegationEcdsa { - type Error = (); - - fn try_from(value: ProxyDelegation) -> Result { - Ok(ProxyDelegationEcdsa { delegator: value.delegator, proxy: value.proxy.try_into()? }) - } -} - -impl TryFrom for SignedProxyDelegationEcdsa { - type Error = (); - - fn try_from(value: SignedProxyDelegation) -> Result { - Ok(SignedProxyDelegationEcdsa { - message: value.message.try_into()?, - signature: value.signature, - }) - } + pub proxy: T, } -// BLS PROXY DELEGATION -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, TreeHash)] -pub struct ProxyDelegationBls { - pub delegator: BlsPublicKey, - pub proxy: BlsPublicKey, -} +pub type ProxyDelegationBls = ProxyDelegation; +pub type ProxyDelegationEcdsa = ProxyDelegation; -impl fmt::Display for ProxyDelegationBls { +impl fmt::Display for ProxyDelegation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Delegator: {}\nProxy: {}", self.delegator, self.proxy) } } -impl From for ProxyDelegation { - fn from(value: ProxyDelegationBls) -> Self { - ProxyDelegation { delegator: value.delegator, proxy: GenericPubkey::Bls(value.proxy) } - } -} - +// TODO: might need to adapt the SignedProxyDelegation so that it goes through +// web3 signer #[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct SignedProxyDelegationBls { - pub message: ProxyDelegationBls, +pub struct SignedProxyDelegation { + pub message: ProxyDelegation, /// Signature of message with the delegator keypair pub signature: BlsSignature, } -impl SignedProxyDelegationBls { +pub type SignedProxyDelegationBls = SignedProxyDelegation; +pub type SignedProxyDelegationEcdsa = SignedProxyDelegation; + +impl SignedProxyDelegation { pub fn validate(&self, chain: Chain) -> Result<(), BlstErrorWrapper> { verify_signed_builder_message( chain, @@ -157,37 +65,12 @@ impl SignedProxyDelegationBls { } } -impl fmt::Display for SignedProxyDelegationBls { +impl fmt::Display for SignedProxyDelegation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}\nSignature: {}", self.message, self.signature) } } -impl From for SignedProxyDelegation { - fn from(value: SignedProxyDelegationBls) -> Self { - SignedProxyDelegation { message: value.message.into(), signature: value.signature } - } -} - -impl TryFrom for ProxyDelegationBls { - type Error = (); - - fn try_from(value: ProxyDelegation) -> Result { - Ok(ProxyDelegationBls { delegator: value.delegator, proxy: value.proxy.try_into()? }) - } -} - -impl TryFrom for SignedProxyDelegationBls { - type Error = (); - - fn try_from(value: SignedProxyDelegation) -> Result { - Ok(SignedProxyDelegationBls { - message: value.message.try_into()?, - signature: value.signature, - }) - } -} - // TODO(David): This struct shouldn't be visible in the client SDK #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SignRequest { @@ -219,6 +102,7 @@ impl SignConsensusRequest { } } +//TODO(David): Make generic on PublicKey? //TODO(David): Shouldn't be visible from the client SDK #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignProxyRequest { diff --git a/crates/common/src/pbs/types/get_header.rs b/crates/common/src/pbs/types/get_header.rs index 531e2dd4..c00c2404 100644 --- a/crates/common/src/pbs/types/get_header.rs +++ b/crates/common/src/pbs/types/get_header.rs @@ -117,7 +117,7 @@ mod tests { assert!(verify_signed_builder_message( Chain::Holesky, - &parsed.message.pubkey, + &parsed.message.pubkey.into(), &parsed.message, &parsed.signature, ) diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 49bd82c6..21f5eb2e 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -1,8 +1,10 @@ use std::fmt::{self, LowerHex}; -use alloy::rpc::types::beacon::BlsPublicKey; use eyre::Context; -use schemes::{bls::verify_bls_signature, ecdsa::verify_ecdsa_signature}; +use schemes::{ + bls::{verify_bls_signature, BlsPublicKey}, + ecdsa::verify_ecdsa_signature, +}; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -93,17 +95,18 @@ impl fmt::Display for GenericPubkey { } } +/// A transparent implementation impl tree_hash::TreeHash for GenericPubkey { fn tree_hash_type() -> tree_hash::TreeHashType { - tree_hash::TreeHashType::Container + tree_hash::TreeHashType::Vector } fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { - unimplemented!("Enum should never be packed") + unimplemented!("Vector should never be packed") } fn tree_hash_packing_factor() -> usize { - unimplemented!("Enum should never be packed") + unimplemented!("Vector should never be packed") } fn tree_hash_root(&self) -> tree_hash::Hash256 { diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index cbfc66cb..4ba1999f 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -1,6 +1,12 @@ -use alloy::rpc::types::beacon::{constants::BLS_DST_SIG, BlsPublicKey, BlsSignature}; +use alloy::rpc::types::beacon::{ + constants::BLS_DST_SIG, BlsPublicKey as BlsPublicKeyInner, BlsSignature, +}; use blst::BLST_ERROR; +use derive_more::derive::{Deref, Display, From, Into, LowerHex}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; use crate::{ error::BlstErrorWrapper, signature::sign_builder_root, signer::GenericPubkey, types::Chain, @@ -9,6 +15,23 @@ use crate::{ pub type BlsSecretKey = blst::min_pk::SecretKey; +// std traits +#[derive(Debug, Clone, Copy, LowerHex, Display, PartialEq, Eq, Hash)] +// serde, ssz, tree_hash +#[derive(Serialize, Deserialize, Encode, Decode, TreeHash)] +#[ssz(struct_behaviour = "transparent")] +// derive_more +#[derive(Deref, From, Into)] +pub struct BlsPublicKey { + inner: BlsPublicKeyInner, +} + +impl AsRef<[u8]> for BlsPublicKey { + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + #[derive(Clone)] pub enum BlsSigner { Local(BlsSecretKey), @@ -26,7 +49,7 @@ impl BlsSigner { pub fn pubkey(&self) -> BlsPublicKey { match self { - BlsSigner::Local(secret) => blst_pubkey_to_alloy(&secret.sk_to_pk()), + BlsSigner::Local(secret) => blst_pubkey_to_alloy(&secret.sk_to_pk()).into(), } } @@ -55,8 +78,10 @@ fn random_secret() -> BlsSecretKey { } } +// TODO(David): Refine the boundaries between our wrapper `BlsPublicKey` type +// and alloy's `BlsPublicKey`. This stinks right now... pub fn verify_bls_signature( - pubkey: &BlsPublicKey, + pubkey: &BlsPublicKeyInner, msg: &[u8], signature: &BlsSignature, ) -> Result<(), BlstErrorWrapper> { diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 5204604d..b9e8a034 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -1,13 +1,16 @@ use std::collections::HashMap; -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use alloy::rpc::types::beacon::BlsSignature; use cb_common::{ commit::request::{ - ProxyDelegationBls, ProxyDelegationEcdsa, SignedProxyDelegation, SignedProxyDelegationBls, - SignedProxyDelegationEcdsa, + ProxyDelegationBls, ProxyDelegationEcdsa, PublicKey, SignedProxyDelegation, + SignedProxyDelegationBls, SignedProxyDelegationEcdsa, }, signer::{ - schemes::ecdsa::{EcdsaPublicKey, EcdsaSignature}, + schemes::{ + bls::BlsPublicKey, + ecdsa::{EcdsaPublicKey, EcdsaSignature}, + }, BlsSigner, ConsensusSigner, EcdsaSigner, GenericPubkey, }, types::{Chain, ModuleId}, @@ -180,20 +183,38 @@ impl SigningManager { } } - pub fn get_delegation( + // The trait bound is merely an implementational detail, we don't want this + // trait to be implemented outside. + #[allow(private_bounds)] + pub fn get_delegation( &self, - pubkey: &GenericPubkey, - ) -> Result { - let delegation: Option = match pubkey { - GenericPubkey::Bls(bls_pk) => { - self.proxy_signers.bls_signers.get(bls_pk).map(|x| x.delegation).map(Into::into) - } - GenericPubkey::Ecdsa(ecdsa_pk) => { - self.proxy_signers.ecdsa_signers.get(ecdsa_pk).map(|x| x.delegation).map(Into::into) - } - }; + pubkey: &T, + ) -> Result, SignerModuleError> + where + T: PublicKey, + Self: GetDelegation, + { + >::get_delegation(self, pubkey) + .ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec())) + } +} + +trait GetDelegation { + fn get_delegation(&self, pubkey: &T) -> Option>; +} + +impl GetDelegation for SigningManager { + fn get_delegation(&self, pubkey: &BlsPublicKey) -> Option> { + self.proxy_signers.bls_signers.get(pubkey).map(|x| x.delegation) + } +} - delegation.ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec())) +impl GetDelegation for SigningManager { + fn get_delegation( + &self, + pubkey: &EcdsaPublicKey, + ) -> Option> { + self.proxy_signers.ecdsa_signers.get(pubkey).map(|x| x.delegation) } } diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 6aa9438a..71679d23 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -16,7 +16,7 @@ use cb_common::{ constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, request::{ EncryptionScheme, GenerateProxyRequest, SignConsensusRequest, SignProxyRequest, - SignRequest, SignedProxyDelegation, + SignRequest, }, }, config::StartSignerConfig, @@ -160,14 +160,18 @@ async fn handle_generate_proxy( let mut signing_manager = state.manager.write().await; - let proxy_delegation: SignedProxyDelegation = match request.scheme { + let response = match request.scheme { EncryptionScheme::Bls => { - signing_manager.create_proxy_bls(module_id, request.consensus_pubkey).await?.into() + let proxy_delegation = + signing_manager.create_proxy_bls(module_id, request.consensus_pubkey).await?; + (StatusCode::OK, Json(proxy_delegation)).into_response() } EncryptionScheme::Ecdsa => { - signing_manager.create_proxy_ecdsa(module_id, request.consensus_pubkey).await?.into() + let proxy_delegation = + signing_manager.create_proxy_ecdsa(module_id, request.consensus_pubkey).await?; + (StatusCode::OK, Json(proxy_delegation)).into_response() } }; - Ok((StatusCode::OK, Json(proxy_delegation)).into_response()) + Ok(response) } diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index f3b70696..c20c9a7b 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -44,16 +44,16 @@ impl DaCommitService { let pubkeys = self.config.signer_client.get_pubkeys().await?; info!(consensus = pubkeys.consensus.len(), proxy = pubkeys.proxy.len(), "Received pubkeys"); - let pubkey = pubkeys.consensus.first().ok_or_eyre("no key available")?; + let pubkey = *pubkeys.consensus.first().ok_or_eyre("no key available")?; info!("Registered validator {pubkey}"); - let proxy_delegation = self.config.signer_client.generate_ecdsa_proxy_key(*pubkey).await?; + let proxy_delegation = self.config.signer_client.generate_ecdsa_proxy_key(pubkey).await?; info!("Obtained a proxy delegation:\n{proxy_delegation}"); let mut data = 0; loop { - self.send_request(data, *pubkey, proxy_delegation).await?; + self.send_request(data, pubkey.into(), proxy_delegation).await?; sleep(Duration::from_secs(self.config.extra.sleep_secs)).await; data += 1; } @@ -67,7 +67,7 @@ impl DaCommitService { ) -> Result<()> { let datagram = Datagram { data }; - let request = SignConsensusRequest::builder(pubkey).with_msg(&datagram); + let request = SignConsensusRequest::builder(pubkey.into()).with_msg(&datagram); let signature = self.config.signer_client.request_consensus_signature(request); let ecdsa_proxy: EcdsaPublicKey = proxy_delegation.message.proxy; diff --git a/tests/src/mock_relay.rs b/tests/src/mock_relay.rs index 79db092d..36a5802b 100644 --- a/tests/src/mock_relay.rs +++ b/tests/src/mock_relay.rs @@ -82,7 +82,7 @@ async fn handle_get_header( response.data.message.header.parent_hash = parent_hash; response.data.message.header.block_hash.0[0] = 1; response.data.message.set_value(U256::from(10)); - response.data.message.pubkey = state.signer.pubkey(); + response.data.message.pubkey = state.signer.pubkey().into(); let object_root = response.data.message.tree_hash_root().0; response.data.signature = state.signer.sign(state.chain, object_root).await; diff --git a/tests/tests/pbs_integration.rs b/tests/tests/pbs_integration.rs index fc481c19..fd0d7eea 100644 --- a/tests/tests/pbs_integration.rs +++ b/tests/tests/pbs_integration.rs @@ -60,7 +60,7 @@ async fn test_get_header() -> Result<()> { let chain = Chain::Holesky; let port = 3000; - let mock_relay = generate_mock_relay(port + 1, signer.pubkey())?; + let mock_relay = generate_mock_relay(port + 1, *signer.pubkey())?; let mock_state = Arc::new(MockRelayState::new(chain, signer, 0)); tokio::spawn(start_mock_relay_service(mock_state.clone(), port + 1)); @@ -89,8 +89,8 @@ async fn test_get_status() -> Result<()> { let port = 3100; let relays = vec![ - generate_mock_relay(port + 1, signer.pubkey())?, - generate_mock_relay(port + 2, signer.pubkey())?, + generate_mock_relay(port + 1, *signer.pubkey())?, + generate_mock_relay(port + 2, *signer.pubkey())?, ]; let mock_state = Arc::new(MockRelayState::new(chain, signer, 0)); tokio::spawn(start_mock_relay_service(mock_state.clone(), port + 1)); @@ -120,7 +120,7 @@ async fn test_register_validators() -> Result<()> { let chain = Chain::Holesky; let port = 3300; - let relays = vec![generate_mock_relay(port + 1, signer.pubkey())?]; + let relays = vec![generate_mock_relay(port + 1, *signer.pubkey())?]; let mock_state = Arc::new(MockRelayState::new(chain, signer, 0)); tokio::spawn(start_mock_relay_service(mock_state.clone(), port + 1)); @@ -148,7 +148,7 @@ async fn test_submit_block() -> Result<()> { let chain = Chain::Holesky; let port = 3400; - let relays = vec![generate_mock_relay(port + 1, signer.pubkey())?]; + let relays = vec![generate_mock_relay(port + 1, *signer.pubkey())?]; let mock_state = Arc::new(MockRelayState::new(chain, signer, 0)); tokio::spawn(start_mock_relay_service(mock_state.clone(), port + 1)); From 780299e6b529a96ff9582494868cab559d1b87dc Mon Sep 17 00:00:00 2001 From: David Petrov Date: Fri, 23 Aug 2024 10:40:44 +0300 Subject: [PATCH 21/37] refactor(signer/request): make `SignProxyRequest` generic in the public key type thus remove `SignProxyBlsRequest` and `SignProxyEcdsaRequest` as duplicated structs --- crates/common/src/commit/client.rs | 21 +++------- crates/common/src/commit/request.rs | 63 +++++++---------------------- crates/signer/src/service.rs | 14 +++---- examples/da_commit/src/main.rs | 4 +- 4 files changed, 27 insertions(+), 75 deletions(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 556d529b..0ea64694 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -9,9 +9,8 @@ use super::{ constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, error::SignerClientError, request::{ - EncryptionScheme, GenerateProxyRequest, PublicKey, SignConsensusRequest, - SignProxyBlsRequest, SignProxyEcdsaRequest, SignProxyRequest, SignRequest, - SignedProxyDelegation, + EncryptionScheme, GenerateProxyRequest, PublicKey, SignConsensusRequest, SignProxyRequest, + SignRequest, SignedProxyDelegation, }, }; use crate::{ @@ -108,19 +107,11 @@ impl SignerClient { Ok(signature) } - async fn request_proxy_signature( - &self, - request: SignProxyRequest, - ) -> Result, SignerClientError> { - let request = SignRequest::Proxy(request); - self.request_signature(&request).await - } - pub async fn request_proxy_ecdsa_signature( &self, - request: SignProxyEcdsaRequest, + request: SignProxyRequest, ) -> Result { - let raw_signature = self.request_proxy_signature(request.into()).await?; + let raw_signature = self.request_signature(&request.into()).await?; let signature = EcdsaSignature::try_from(raw_signature.as_ref()) .expect("requested signature should be ECDSA"); Ok(signature) @@ -128,9 +119,9 @@ impl SignerClient { pub async fn request_proxy_bls_signature( &self, - request: SignProxyBlsRequest, + request: SignProxyRequest, ) -> Result { - let raw_signature = self.request_proxy_signature(request.into()).await?; + let raw_signature = self.request_signature(&request.into()).await?; let signature = BlsSignature::from_slice(&raw_signature); Ok(signature) } diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index fe53cd04..3573a8b2 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -10,10 +10,7 @@ use tree_hash_derive::TreeHash; use crate::{ error::BlstErrorWrapper, signature::verify_signed_builder_message, - signer::{ - schemes::{bls::BlsPublicKey, ecdsa::EcdsaPublicKey}, - GenericPubkey, - }, + signer::schemes::{bls::BlsPublicKey, ecdsa::EcdsaPublicKey}, types::Chain, }; @@ -75,7 +72,8 @@ impl fmt::Display for SignedProxyDelegation { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SignRequest { Consensus(SignConsensusRequest), - Proxy(SignProxyRequest), + ProxyBls(SignProxyRequest), + ProxyEcdsa(SignProxyRequest), } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -102,26 +100,18 @@ impl SignConsensusRequest { } } -//TODO(David): Make generic on PublicKey? -//TODO(David): Shouldn't be visible from the client SDK #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SignProxyRequest { - pub pubkey: GenericPubkey, +pub struct SignProxyRequest { + pub pubkey: T, pub object_root: [u8; 32], } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SignProxyEcdsaRequest { - pub pubkey: EcdsaPublicKey, - pub object_root: [u8; 32], -} - -impl SignProxyEcdsaRequest { - pub fn new(pubkey: EcdsaPublicKey, object_root: [u8; 32]) -> Self { +impl SignProxyRequest { + pub fn new(pubkey: T, object_root: [u8; 32]) -> Self { Self { pubkey, object_root } } - pub fn builder(pubkey: EcdsaPublicKey) -> Self { + pub fn builder(pubkey: T) -> Self { Self::new(pubkey, [0; 32]) } @@ -134,39 +124,14 @@ impl SignProxyEcdsaRequest { } } -impl From for SignProxyRequest { - fn from(value: SignProxyEcdsaRequest) -> Self { - Self { pubkey: value.pubkey.into(), object_root: value.object_root } +impl From> for SignRequest { + fn from(value: SignProxyRequest) -> Self { + Self::ProxyEcdsa(value) } } - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SignProxyBlsRequest { - pub pubkey: BlsPublicKey, - pub object_root: [u8; 32], -} - -impl SignProxyBlsRequest { - pub fn new(pubkey: BlsPublicKey, object_root: [u8; 32]) -> Self { - Self { pubkey, object_root } - } - - pub fn builder(pubkey: BlsPublicKey) -> Self { - Self::new(pubkey, [0; 32]) - } - - pub fn with_root(self, object_root: [u8; 32]) -> Self { - Self { object_root, ..self } - } - - pub fn with_msg(self, msg: &impl TreeHash) -> Self { - self.with_root(msg.tree_hash_root().0) - } -} - -impl From for SignProxyRequest { - fn from(value: SignProxyBlsRequest) -> Self { - Self { pubkey: value.pubkey.into(), object_root: value.object_root } +impl From> for SignRequest { + fn from(value: SignProxyRequest) -> Self { + Self::ProxyBls(value) } } diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 71679d23..7f539ee8 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -134,15 +134,11 @@ async fn handle_request_signature( SignRequest::Consensus(SignConsensusRequest { pubkey, object_root }) => { signing_manager.sign_consensus(&pubkey, &object_root).await.map(|sig| sig.to_vec()) } - SignRequest::Proxy(SignProxyRequest { pubkey, object_root }) => match pubkey { - cb_common::signer::GenericPubkey::Bls(bls_pubkey) => signing_manager - .sign_proxy_bls(&bls_pubkey, &object_root) - .await - .map(|sig| sig.to_vec()), - cb_common::signer::GenericPubkey::Ecdsa(ecdsa_pubkey) => signing_manager - .sign_proxy_ecdsa(&ecdsa_pubkey, &object_root) - .await - .map(|sig| sig.to_vec()), + SignRequest::ProxyBls(SignProxyRequest { pubkey: bls_pk, object_root }) => { + signing_manager.sign_proxy_bls(&bls_pk, &object_root).await.map(|sig| sig.to_vec()) + } + SignRequest::ProxyEcdsa(SignProxyRequest { pubkey: ecdsa_pk, object_root }) => { + signing_manager.sign_proxy_ecdsa(&ecdsa_pk, &object_root).await.map(|sig| sig.to_vec()) }, }?; diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index c20c9a7b..afa19582 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,7 +1,7 @@ use std::time::Duration; use alloy::rpc::types::beacon::BlsPublicKey; -use commit::request::SignProxyEcdsaRequest; +use commit::request::SignProxyRequest; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; use lazy_static::lazy_static; @@ -72,7 +72,7 @@ impl DaCommitService { let ecdsa_proxy: EcdsaPublicKey = proxy_delegation.message.proxy; - let proxy_request = SignProxyEcdsaRequest::builder(ecdsa_proxy).with_msg(&datagram); + let proxy_request = SignProxyRequest::builder(ecdsa_proxy).with_msg(&datagram); let proxy_signature = self.config.signer_client.request_proxy_ecdsa_signature(proxy_request); From 7ddcd3a67764367d26efac87f59509f3bc437505 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Fri, 23 Aug 2024 10:43:19 +0300 Subject: [PATCH 22/37] refactor(signer/manager)!: split generic `get_delegation` into two separte methods --- crates/signer/src/manager.rs | 44 +++++++++++++----------------------- crates/signer/src/service.rs | 2 +- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index b9e8a034..9f05953f 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use alloy::rpc::types::beacon::BlsSignature; use cb_common::{ commit::request::{ - ProxyDelegationBls, ProxyDelegationEcdsa, PublicKey, SignedProxyDelegation, - SignedProxyDelegationBls, SignedProxyDelegationEcdsa, + ProxyDelegationBls, ProxyDelegationEcdsa, SignedProxyDelegationBls, + SignedProxyDelegationEcdsa, }, signer::{ schemes::{ @@ -183,38 +183,26 @@ impl SigningManager { } } - // The trait bound is merely an implementational detail, we don't want this - // trait to be implemented outside. - #[allow(private_bounds)] - pub fn get_delegation( + pub fn get_delegation_bls( &self, - pubkey: &T, - ) -> Result, SignerModuleError> - where - T: PublicKey, - Self: GetDelegation, - { - >::get_delegation(self, pubkey) + pubkey: &BlsPublicKey, + ) -> Result { + self.proxy_signers + .bls_signers + .get(pubkey) + .map(|x| x.delegation) .ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec())) } -} - -trait GetDelegation { - fn get_delegation(&self, pubkey: &T) -> Option>; -} - -impl GetDelegation for SigningManager { - fn get_delegation(&self, pubkey: &BlsPublicKey) -> Option> { - self.proxy_signers.bls_signers.get(pubkey).map(|x| x.delegation) - } -} -impl GetDelegation for SigningManager { - fn get_delegation( + pub fn get_delegation_ecdsa( &self, pubkey: &EcdsaPublicKey, - ) -> Option> { - self.proxy_signers.ecdsa_signers.get(pubkey).map(|x| x.delegation) + ) -> Result { + self.proxy_signers + .ecdsa_signers + .get(pubkey) + .map(|x| x.delegation) + .ok_or(SignerModuleError::UnknownProxySigner(pubkey.as_ref().to_vec())) } } diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 7f539ee8..56d76c1c 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -139,7 +139,7 @@ async fn handle_request_signature( } SignRequest::ProxyEcdsa(SignProxyRequest { pubkey: ecdsa_pk, object_root }) => { signing_manager.sign_proxy_ecdsa(&ecdsa_pk, &object_root).await.map(|sig| sig.to_vec()) - }, + } }?; Ok((StatusCode::OK, Json(sig)).into_response()) From b8f228b630f1865439959b166b5a87a5a23bc0b9 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Tue, 27 Aug 2024 12:54:01 +0300 Subject: [PATCH 23/37] refactor(signer/client): remove unnecessary `Vec` deserialization makes deserialization of signatures properly fallible * also, add both proxy examples in `da_commit` --- crates/common/src/commit/client.rs | 14 ++++------ crates/common/src/signer/schemes/ecdsa.rs | 3 +- crates/signer/src/service.rs | 27 ++++++++++-------- examples/da_commit/src/main.rs | 34 +++++++++++++++-------- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 0ea64694..cc476b49 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use alloy::rpc::types::beacon::BlsSignature; +use axum::body::Bytes; use eyre::WrapErr; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use serde::{Deserialize, Serialize}; @@ -76,7 +77,7 @@ impl SignerClient { } /// Send a signature request - async fn request_signature(&self, request: &SignRequest) -> Result, SignerClientError> { + async fn request_signature(&self, request: &SignRequest) -> Result { let url = format!("{}{}", self.url, REQUEST_SIGNATURE_PATH); let res = self.client.post(&url).json(&request).send().await?; @@ -90,9 +91,7 @@ impl SignerClient { }); } - let signature: Vec = serde_json::from_slice(&response_bytes)?; - - Ok(signature) + Ok(response_bytes) } pub async fn request_consensus_signature( @@ -102,7 +101,7 @@ impl SignerClient { let request = SignRequest::Consensus(request); let raw_signature = self.request_signature(&request).await?; - let signature = BlsSignature::from_slice(&raw_signature); + let signature = serde_json::from_slice(&raw_signature)?; Ok(signature) } @@ -112,8 +111,7 @@ impl SignerClient { request: SignProxyRequest, ) -> Result { let raw_signature = self.request_signature(&request.into()).await?; - let signature = EcdsaSignature::try_from(raw_signature.as_ref()) - .expect("requested signature should be ECDSA"); + let signature = serde_json::from_slice(&raw_signature)?; Ok(signature) } @@ -122,7 +120,7 @@ impl SignerClient { request: SignProxyRequest, ) -> Result { let raw_signature = self.request_signature(&request.into()).await?; - let signature = BlsSignature::from_slice(&raw_signature); + let signature = serde_json::from_slice(&raw_signature)?; Ok(signature) } diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 495d3c30..b3986067 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -130,7 +130,8 @@ impl fmt::Display for EcdsaPublicKey { } } -#[derive(Clone, Deref)] +// TODO(David): Hex encode when Ser/De +#[derive(Clone, Deref, Serialize, Deserialize)] pub struct EcdsaSignature { encoded: GenericArray, } diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 56d76c1c..430faade 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -130,19 +130,24 @@ async fn handle_request_signature( let signing_manager = state.manager.read().await; - let sig = match request { - SignRequest::Consensus(SignConsensusRequest { pubkey, object_root }) => { - signing_manager.sign_consensus(&pubkey, &object_root).await.map(|sig| sig.to_vec()) - } - SignRequest::ProxyBls(SignProxyRequest { pubkey: bls_pk, object_root }) => { - signing_manager.sign_proxy_bls(&bls_pk, &object_root).await.map(|sig| sig.to_vec()) - } + let signature_response = match request { + SignRequest::Consensus(SignConsensusRequest { pubkey, object_root }) => signing_manager + .sign_consensus(&pubkey, &object_root) + .await + .map(|sig| Json(sig).into_response()), + SignRequest::ProxyBls(SignProxyRequest { pubkey: bls_pk, object_root }) => signing_manager + .sign_proxy_bls(&bls_pk, &object_root) + .await + .map(|sig| Json(sig).into_response()), SignRequest::ProxyEcdsa(SignProxyRequest { pubkey: ecdsa_pk, object_root }) => { - signing_manager.sign_proxy_ecdsa(&ecdsa_pk, &object_root).await.map(|sig| sig.to_vec()) + signing_manager + .sign_proxy_ecdsa(&ecdsa_pk, &object_root) + .await + .map(|sig| Json(sig).into_response()) } }?; - Ok((StatusCode::OK, Json(sig)).into_response()) + Ok(signature_response) } async fn handle_generate_proxy( @@ -160,12 +165,12 @@ async fn handle_generate_proxy( EncryptionScheme::Bls => { let proxy_delegation = signing_manager.create_proxy_bls(module_id, request.consensus_pubkey).await?; - (StatusCode::OK, Json(proxy_delegation)).into_response() + Json(proxy_delegation).into_response() } EncryptionScheme::Ecdsa => { let proxy_delegation = signing_manager.create_proxy_ecdsa(module_id, request.consensus_pubkey).await?; - (StatusCode::OK, Json(proxy_delegation)).into_response() + Json(proxy_delegation).into_response() } }; diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index afa19582..cb908fa4 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -47,13 +47,19 @@ impl DaCommitService { let pubkey = *pubkeys.consensus.first().ok_or_eyre("no key available")?; info!("Registered validator {pubkey}"); - let proxy_delegation = self.config.signer_client.generate_ecdsa_proxy_key(pubkey).await?; - info!("Obtained a proxy delegation:\n{proxy_delegation}"); + let proxy_delegation_bls = self.config.signer_client.generate_bls_proxy_key(pubkey).await?; + info!("Obtained a BLS proxy delegation:\n{proxy_delegation_bls}"); + let proxy_bls = proxy_delegation_bls.message.proxy; + + let proxy_delegation_ecdsa = + self.config.signer_client.generate_ecdsa_proxy_key(pubkey).await?; + info!("Obtained an ECDSA proxy delegation:\n{proxy_delegation_bls}"); + let proxy_ecdsa = proxy_delegation_ecdsa.message.proxy; let mut data = 0; loop { - self.send_request(data, pubkey.into(), proxy_delegation).await?; + self.send_request(data, pubkey.into(), proxy_bls.into(), proxy_ecdsa).await?; sleep(Duration::from_secs(self.config.extra.sleep_secs)).await; data += 1; } @@ -63,26 +69,30 @@ impl DaCommitService { &self, data: u64, pubkey: BlsPublicKey, - proxy_delegation: SignedProxyDelegationEcdsa, + proxy_bls: BlsPublicKey, + proxy_ecdsa: EcdsaPublicKey, ) -> Result<()> { let datagram = Datagram { data }; let request = SignConsensusRequest::builder(pubkey.into()).with_msg(&datagram); let signature = self.config.signer_client.request_consensus_signature(request); - let ecdsa_proxy: EcdsaPublicKey = proxy_delegation.message.proxy; + let proxy_request_bls = SignProxyRequest::builder(proxy_bls.into()).with_msg(&datagram); + let proxy_signature_bls = + self.config.signer_client.request_proxy_bls_signature(proxy_request_bls); - let proxy_request = SignProxyRequest::builder(ecdsa_proxy).with_msg(&datagram); - let proxy_signature = - self.config.signer_client.request_proxy_ecdsa_signature(proxy_request); + let proxy_request_ecdsa = SignProxyRequest::builder(proxy_ecdsa).with_msg(&datagram); + let proxy_signature_ecdsa = + self.config.signer_client.request_proxy_ecdsa_signature(proxy_request_ecdsa); - let (signature, proxy_signature) = { - let res = tokio::join!(signature, proxy_signature); - (res.0?, res.1?) + let (signature, proxy_signature_bls, proxy_signature_ecdsa) = { + let res = tokio::join!(signature, proxy_signature_bls, proxy_signature_ecdsa); + (res.0?, res.1?, res.2?) }; info!("Proposer commitment (consensus): {}", signature); - info!("Proposer commitment (proxy): {}", proxy_signature); + info!("Proposer commitment (proxy BLS): {}", proxy_signature_bls); + info!("Proposer commitment (proxy ECDSA): {}", proxy_signature_ecdsa); SIG_RECEIVED_COUNTER.inc(); From 635f620cf0275eff5f2bbdda4bb497a3419f2a0a Mon Sep 17 00:00:00 2001 From: David Petrov Date: Tue, 27 Aug 2024 16:06:32 +0300 Subject: [PATCH 24/37] feat(signer): add tests for ECDSA proxies --- crates/common/src/signer/schemes/ecdsa.rs | 2 - crates/signer/src/manager.rs | 183 ++++++++++++++++------ 2 files changed, 132 insertions(+), 53 deletions(-) diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index b3986067..c8a8791a 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -207,8 +207,6 @@ impl EcdsaSigner { } } -// TODO(David): Add tests for this verification, most probably not properly -// working yet pub fn verify_ecdsa_signature( pubkey: &EcdsaPublicKey, msg: &[u8], diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 9f05953f..45f8a690 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -208,7 +208,7 @@ impl SigningManager { #[cfg(test)] mod tests { - use cb_common::{signature::compute_signing_root, signer::schemes::bls::verify_bls_signature}; + use cb_common::signature::compute_signing_root; use lazy_static::lazy_static; use tree_hash::Hash256; @@ -230,72 +230,153 @@ mod tests { (signing_manager, consensus_pk) } - #[tokio::test] - async fn test_proxy_key_is_valid_proxy_for_consensus_key() { - let (mut signing_manager, consensus_pk) = init_signing_manager(); + mod test_proxy_bls { + use cb_common::signer::schemes::bls::verify_bls_signature; - let signed_delegation = signing_manager - .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) - .await - .unwrap(); + use super::*; - let validation_result = signed_delegation.validate(*CHAIN); + #[tokio::test] + async fn test_proxy_key_is_valid_proxy_for_consensus_key() { + let (mut signing_manager, consensus_pk) = init_signing_manager(); - assert!( - validation_result.is_ok(), - "Proxy delegation signature must be valid for consensus key." - ); + let signed_delegation = signing_manager + .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); - assert!( - signing_manager.has_proxy(&signed_delegation.message.proxy.into()), - "Newly generated proxy key must be present in the signing manager's registry." - ); - } + let validation_result = signed_delegation.validate(*CHAIN); + + assert!( + validation_result.is_ok(), + "Proxy delegation signature must be valid for consensus key." + ); + + assert!( + signing_manager.has_proxy(&signed_delegation.message.proxy.into()), + "Newly generated proxy key must be present in the signing manager's registry." + ); + } + + #[tokio::test] + async fn test_tampered_proxy_key_is_invalid() { + let (mut signing_manager, consensus_pk) = init_signing_manager(); + + let mut signed_delegation = signing_manager + .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); + + let m = &mut signed_delegation.signature.0[0]; + (*m, _) = m.overflowing_add(1); - #[tokio::test] - async fn test_tampered_proxy_key_is_invalid() { - let (mut signing_manager, consensus_pk) = init_signing_manager(); + let validation_result = signed_delegation.validate(*CHAIN); - let mut signed_delegation = signing_manager - .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) - .await - .unwrap(); + assert!(validation_result.is_err(), "Tampered proxy key must be invalid."); + } + + #[tokio::test] + async fn test_proxy_key_signs_message() { + let (mut signing_manager, consensus_pk) = init_signing_manager(); + + let signed_delegation = signing_manager + .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); + let proxy_pk = signed_delegation.message.proxy; + + let data_root = Hash256::random(); + let data_root_bytes = data_root.as_fixed_bytes(); - let m = &mut signed_delegation.signature.0[0]; - (*m, _) = m.overflowing_add(1); + let sig = signing_manager + .sign_proxy_bls(&proxy_pk.try_into().unwrap(), data_root_bytes) + .await + .unwrap(); - let validation_result = signed_delegation.validate(*CHAIN); + // Verify signature + let domain = CHAIN.builder_domain(); + let signing_root = compute_signing_root(data_root_bytes.tree_hash_root().0, domain); - assert!(validation_result.is_err(), "Tampered proxy key must be invalid."); + let validation_result = verify_bls_signature(&proxy_pk, &signing_root, &sig); + + assert!( + validation_result.is_ok(), + "Proxy keypair must produce valid signatures of messages." + ) + } } - #[tokio::test] - async fn test_proxy_key_signs_message() { - let (mut signing_manager, consensus_pk) = init_signing_manager(); + mod test_proxy_ecdsa { + use cb_common::signer::schemes::ecdsa::verify_ecdsa_signature; + + use super::*; - let signed_delegation = signing_manager - .create_proxy_bls(MODULE_ID.clone(), consensus_pk.clone()) - .await - .unwrap(); - let proxy_pk = signed_delegation.message.proxy; + #[tokio::test] + async fn test_proxy_key_is_valid_proxy_for_consensus_key() { + let (mut signing_manager, consensus_pk) = init_signing_manager(); - let data_root = Hash256::random(); - let data_root_bytes = data_root.as_fixed_bytes(); + let signed_delegation = signing_manager + .create_proxy_ecdsa(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); - let sig = signing_manager - .sign_proxy_bls(&proxy_pk.try_into().unwrap(), data_root_bytes) - .await - .unwrap(); + let validation_result = signed_delegation.validate(*CHAIN); - // Verify signature - let domain = CHAIN.builder_domain(); - let signing_root = compute_signing_root(data_root_bytes.tree_hash_root().0, domain); + assert!( + validation_result.is_ok(), + "Proxy delegation signature must be valid for consensus key." + ); - let validation_result = verify_bls_signature(&proxy_pk, &signing_root, &sig); + assert!( + signing_manager.has_proxy(&signed_delegation.message.proxy.into()), + "Newly generated proxy key must be present in the signing manager's registry." + ); + } + + #[tokio::test] + async fn test_tampered_proxy_key_is_invalid() { + let (mut signing_manager, consensus_pk) = init_signing_manager(); + + let mut signed_delegation = signing_manager + .create_proxy_ecdsa(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); + + let m = &mut signed_delegation.signature.0[0]; + (*m, _) = m.overflowing_add(1); + + let validation_result = signed_delegation.validate(*CHAIN); + + assert!(validation_result.is_err(), "Tampered proxy key must be invalid."); + } - assert!( - validation_result.is_ok(), - "Proxy keypair must produce valid signatures of messages." - ) + #[tokio::test] + async fn test_proxy_key_signs_message() { + let (mut signing_manager, consensus_pk) = init_signing_manager(); + + let signed_delegation = signing_manager + .create_proxy_ecdsa(MODULE_ID.clone(), consensus_pk.clone()) + .await + .unwrap(); + let proxy_pk = signed_delegation.message.proxy; + + let data_root = Hash256::random(); + let data_root_bytes = data_root.as_fixed_bytes(); + + let sig = signing_manager + .sign_proxy_ecdsa(&proxy_pk.try_into().unwrap(), data_root_bytes) + .await + .unwrap(); + + // Verify signature + let domain = CHAIN.builder_domain(); + let signing_root = compute_signing_root(data_root_bytes.tree_hash_root().0, domain); + + let validation_result = verify_ecdsa_signature(&proxy_pk, &signing_root, &sig); + + assert!( + validation_result.is_ok(), + "Proxy keypair must produce valid signatures of messages." + ) + } } } From dd864ec2d2fe7e2e3a53003c511431a51388bb1e Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 28 Aug 2024 10:24:34 +0300 Subject: [PATCH 25/37] refactor(signer/api)!: proper struct serde * replace derived Serialize/Deserialize with sane API ser/de * hex encode fields properly * simplify the inner types of `EcdsaPublicKey` and `EcdsaSignature` to native rust arrays --- crates/common/src/commit/request.rs | 3 ++ crates/common/src/signer/schemes/bls.rs | 3 +- crates/common/src/signer/schemes/ecdsa.rs | 44 ++++++++++++++--------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 3573a8b2..7b140f8d 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -70,6 +70,7 @@ impl fmt::Display for SignedProxyDelegation { // TODO(David): This struct shouldn't be visible in the client SDK #[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] pub enum SignRequest { Consensus(SignConsensusRequest), ProxyBls(SignProxyRequest), @@ -79,6 +80,7 @@ pub enum SignRequest { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignConsensusRequest { pub pubkey: BlsPublicKey, + #[serde(with = "alloy::hex::serde")] pub object_root: [u8; 32], } @@ -103,6 +105,7 @@ impl SignConsensusRequest { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignProxyRequest { pub pubkey: T, + #[serde(with = "alloy::hex::serde")] pub object_root: [u8; 32], } diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index 4ba1999f..f7c7bfba 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -16,10 +16,11 @@ use crate::{ pub type BlsSecretKey = blst::min_pk::SecretKey; // std traits -#[derive(Debug, Clone, Copy, LowerHex, Display, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, LowerHex, Display, PartialEq, Eq, Hash, Default)] // serde, ssz, tree_hash #[derive(Serialize, Deserialize, Encode, Decode, TreeHash)] #[ssz(struct_behaviour = "transparent")] +#[serde(transparent)] // derive_more #[derive(Deref, From, Into)] pub struct BlsPublicKey { diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index c8a8791a..043263d2 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -16,10 +16,12 @@ use crate::{signature::compute_signing_root, signer::GenericPubkey, types::Chain pub type EcdsaSecretKey = k256::ecdsa::SigningKey; -type CompressedPublicKey = GenericArray; +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, } @@ -29,6 +31,12 @@ impl EcdsaPublicKey { 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 @@ -88,18 +96,12 @@ impl ssz::Decode for EcdsaPublicKey { Self::SIZE } - fn from_ssz_bytes(bytes: &[u8]) -> std::result::Result { - if bytes.len() != Self::SIZE { - return Err(ssz::DecodeError::InvalidByteLength { - len: bytes.len(), - expected: Self::SIZE, - }); - } - - let mut fixed_array = [0_u8; Self::SIZE]; - fixed_array.copy_from_slice(bytes); + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let encoded = <[u8; 33]>::try_from(bytes).map_err(|_| { + ssz::DecodeError::InvalidByteLength { len: bytes.len(), expected: Self::SIZE } + })?; - Ok(EcdsaPublicKey { encoded: fixed_array.into() }) + Ok(EcdsaPublicKey { encoded }) } } @@ -107,7 +109,7 @@ 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: encoded.into() } + EcdsaPublicKey { encoded } } } @@ -130,15 +132,22 @@ impl fmt::Display for EcdsaPublicKey { } } -// TODO(David): Hex encode when Ser/De #[derive(Clone, Deref, Serialize, Deserialize)] +#[serde(transparent)] pub struct EcdsaSignature { - encoded: GenericArray, + #[serde(with = "alloy::hex::serde")] + encoded: [u8; 64], +} + +impl Default for EcdsaSignature { + fn default() -> Self { + Self { encoded: [0; 64] } + } } impl From for EcdsaSignature { fn from(value: EcdsaSignatureInner) -> Self { - Self { encoded: value.to_bytes() } + Self { encoded: value.to_bytes().as_slice().try_into().unwrap() } } } @@ -214,7 +223,8 @@ pub fn verify_ecdsa_signature( ) -> Result<(), k256::ecdsa::Error> { use k256::ecdsa::signature::Verifier; let ecdsa_pubkey = EcdsaPublicKeyInner::from_sec1_bytes(&pubkey.encoded)?; - let ecdsa_sig = EcdsaSignatureInner::from_bytes(signature)?; + let ecdsa_sig = + EcdsaSignatureInner::from_bytes(GenericArray::::from_slice(signature.as_ref()))?; ecdsa_pubkey.verify(msg, &ecdsa_sig) } From b3286d5636403ec08d0531bfe0aa31bacb816fb4 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 28 Aug 2024 11:16:19 +0300 Subject: [PATCH 26/37] refactor(signer): remove unnecessary module --- crates/common/src/signer/mod.rs | 9 ++++----- crates/common/src/signer/signer.rs | 4 ---- 2 files changed, 4 insertions(+), 9 deletions(-) delete mode 100644 crates/common/src/signer/signer.rs diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 21f5eb2e..6ac4b78c 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -9,14 +9,13 @@ use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; pub mod schemes; -#[allow(clippy::module_inception)] -mod signer; pub use schemes::{ - bls::BlsSecretKey, - ecdsa::{EcdsaPublicKey, EcdsaSecretKey, EcdsaSignature}, + bls::{BlsSecretKey, BlsSigner}, + ecdsa::{EcdsaPublicKey, EcdsaSecretKey, EcdsaSignature, EcdsaSigner}, }; -pub use signer::{BlsSigner, ConsensusSigner, EcdsaSigner}; + +pub type ConsensusSigner = BlsSigner; #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode)] #[serde(untagged)] diff --git a/crates/common/src/signer/signer.rs b/crates/common/src/signer/signer.rs deleted file mode 100644 index 1dcb6169..00000000 --- a/crates/common/src/signer/signer.rs +++ /dev/null @@ -1,4 +0,0 @@ -// TODO(David): Remove module -pub use super::schemes::bls::BlsSigner; -pub type ConsensusSigner = BlsSigner; -pub type EcdsaSigner = super::schemes::ecdsa::EcdsaSigner; From 7dedbcfd585676da19a3f5bd485b2b44457bb65e Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 28 Aug 2024 12:47:42 +0300 Subject: [PATCH 27/37] refactor(signer)!: remove `GenericPubkey` for the current scale of our needs, we can do without a common (unifying) type for both public key types (BLS and ECDSA), so we basically separate into two separate flows which have only the `PublicKey` trait in common, and that's with a really narrow scope mainly for client-side convenience. --- crates/common/src/commit/client.rs | 6 +- crates/common/src/signer/mod.rs | 109 ---------------------- crates/common/src/signer/schemes/bls.rs | 8 +- crates/common/src/signer/schemes/ecdsa.rs | 8 +- crates/signer/src/manager.rs | 37 ++++---- crates/signer/src/service.rs | 7 +- examples/da_commit/src/main.rs | 7 +- 7 files changed, 38 insertions(+), 144 deletions(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index cc476b49..3aab8d19 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -17,15 +17,17 @@ use super::{ use crate::{ signer::{ schemes::{bls::BlsPublicKey, ecdsa::EcdsaSignature}, - EcdsaPublicKey, GenericPubkey, + EcdsaPublicKey, }, DEFAULT_REQUEST_TIMEOUT, }; +// TODO(David): move to `request.rs` #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GetPubkeysResponse { pub consensus: Vec, - pub proxy: Vec, + pub proxy_bls: Vec, + pub proxy_ecdsa: Vec, } /// Client used by commit modules to request signatures via the Signer API diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index 6ac4b78c..f38a5ed7 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -1,13 +1,3 @@ -use std::fmt::{self, LowerHex}; - -use eyre::Context; -use schemes::{ - bls::{verify_bls_signature, BlsPublicKey}, - ecdsa::verify_ecdsa_signature, -}; -use serde::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; - pub mod schemes; pub use schemes::{ @@ -16,102 +6,3 @@ pub use schemes::{ }; pub type ConsensusSigner = BlsSigner; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode)] -#[serde(untagged)] -#[ssz(enum_behaviour = "transparent")] -pub enum GenericPubkey { - Bls(BlsPublicKey), - Ecdsa(EcdsaPublicKey), -} - -impl GenericPubkey { - pub fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> eyre::Result<()> { - match self { - GenericPubkey::Bls(bls_pubkey) => { - let sig = signature.try_into().context("Invalid signature length for BLS.")?; - Ok(verify_bls_signature(bls_pubkey, msg, &sig)?) - } - GenericPubkey::Ecdsa(ecdsa_pubkey) => { - let sig = signature.try_into().context("Invalid signature for ECDSA.")?; - Ok(verify_ecdsa_signature(ecdsa_pubkey, msg, &sig)?) - } - } - } -} - -impl AsRef<[u8]> for GenericPubkey { - fn as_ref(&self) -> &[u8] { - match self { - GenericPubkey::Bls(bls_pubkey) => bls_pubkey.as_ref(), - GenericPubkey::Ecdsa(ecdsa_pubkey) => ecdsa_pubkey.as_ref(), - } - } -} - -impl TryFrom for BlsPublicKey { - type Error = (); - - fn try_from(value: GenericPubkey) -> Result { - match value { - GenericPubkey::Bls(bls_pubkey) => Ok(bls_pubkey), - GenericPubkey::Ecdsa(_) => Err(()), - } - } -} - -impl TryFrom for EcdsaPublicKey { - type Error = (); - - fn try_from(value: GenericPubkey) -> Result { - match value { - GenericPubkey::Bls(_) => Err(()), - GenericPubkey::Ecdsa(ecdsa_pubkey) => Ok(ecdsa_pubkey), - } - } -} - -impl LowerHex for GenericPubkey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0x")?; - - let pubkey_bytes = match self { - GenericPubkey::Bls(bls) => bls.as_ref(), - GenericPubkey::Ecdsa(ecdsa) => ecdsa.as_ref(), - }; - - for byte in pubkey_bytes { - write!(f, "{:02x}", byte)?; - } - - Ok(()) - } -} - -impl fmt::Display for GenericPubkey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self:x}") - } -} - -/// A transparent implementation -impl tree_hash::TreeHash for GenericPubkey { - fn tree_hash_type() -> tree_hash::TreeHashType { - tree_hash::TreeHashType::Vector - } - - fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { - unimplemented!("Vector should never be packed") - } - - fn tree_hash_packing_factor() -> usize { - unimplemented!("Vector should never be packed") - } - - fn tree_hash_root(&self) -> tree_hash::Hash256 { - match self { - GenericPubkey::Bls(ref inner) => inner.tree_hash_root(), - GenericPubkey::Ecdsa(ref inner) => inner.tree_hash_root(), - } - } -} diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index f7c7bfba..bbc911cd 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -9,7 +9,7 @@ use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - error::BlstErrorWrapper, signature::sign_builder_root, signer::GenericPubkey, types::Chain, + error::BlstErrorWrapper, signature::sign_builder_root, types::Chain, utils::blst_pubkey_to_alloy, }; @@ -98,9 +98,3 @@ pub fn verify_bls_signature( Err(res.into()) } } - -impl From for GenericPubkey { - fn from(value: BlsPublicKey) -> Self { - GenericPubkey::Bls(value) - } -} diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 043263d2..1e1e70ec 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -12,7 +12,7 @@ use ssz_types::{ }; use tree_hash::TreeHash; -use crate::{signature::compute_signing_root, signer::GenericPubkey, types::Chain}; +use crate::{signature::compute_signing_root, types::Chain}; pub type EcdsaSecretKey = k256::ecdsa::SigningKey; @@ -227,9 +227,3 @@ pub fn verify_ecdsa_signature( EcdsaSignatureInner::from_bytes(GenericArray::::from_slice(signature.as_ref()))?; ecdsa_pubkey.verify(msg, &ecdsa_sig) } - -impl From for GenericPubkey { - fn from(value: EcdsaPublicKey) -> Self { - GenericPubkey::Ecdsa(value) - } -} diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 45f8a690..5353edfc 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -11,7 +11,7 @@ use cb_common::{ bls::BlsPublicKey, ecdsa::{EcdsaPublicKey, EcdsaSignature}, }, - BlsSigner, ConsensusSigner, EcdsaSigner, GenericPubkey, + BlsSigner, ConsensusSigner, EcdsaSigner, }, types::{Chain, ModuleId}, }; @@ -53,7 +53,8 @@ pub struct SigningManager { /// Map of module ids to their associated proxy pubkeys. /// Used to retrieve the corresponding proxy signer from the signing /// manager. - proxy_pubkeys: HashMap>, + proxy_pubkeys_bls: HashMap>, + proxy_pubkeys_ecdsa: HashMap>, } impl SigningManager { @@ -62,7 +63,8 @@ impl SigningManager { chain, consensus_signers: Default::default(), proxy_signers: Default::default(), - proxy_pubkeys: Default::default(), + proxy_pubkeys_bls: Default::default(), + proxy_pubkeys_ecdsa: Default::default(), } } @@ -73,13 +75,13 @@ impl SigningManager { pub fn add_proxy_signer_bls(&mut self, proxy: BlsProxySigner, module_id: ModuleId) { let proxy_pubkey = proxy.pubkey(); self.proxy_signers.bls_signers.insert(proxy.pubkey(), proxy); - self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey.into()) + self.proxy_pubkeys_bls.entry(module_id).or_default().push(proxy_pubkey) } pub fn add_proxy_signer_ecdsa(&mut self, proxy: EcdsaProxySigner, module_id: ModuleId) { let proxy_pubkey = proxy.pubkey(); self.proxy_signers.ecdsa_signers.insert(proxy.pubkey(), proxy); - self.proxy_pubkeys.entry(module_id).or_default().push(proxy_pubkey.into()) + self.proxy_pubkeys_ecdsa.entry(module_id).or_default().push(proxy_pubkey) } pub async fn create_proxy_bls( @@ -166,21 +168,24 @@ impl SigningManager { self.consensus_signers.keys().cloned().collect() } - pub fn proxy_pubkeys(&self) -> &HashMap> { - &self.proxy_pubkeys + pub fn proxy_pubkeys_bls(&self) -> &HashMap> { + &self.proxy_pubkeys_bls + } + + pub fn proxy_pubkeys_ecdsa(&self) -> &HashMap> { + &self.proxy_pubkeys_ecdsa } pub fn has_consensus(&self, pubkey: &BlsPublicKey) -> bool { self.consensus_signers.contains_key(pubkey) } - pub fn has_proxy(&self, pubkey: &GenericPubkey) -> bool { - match pubkey { - GenericPubkey::Bls(bls_pk) => self.proxy_signers.bls_signers.contains_key(bls_pk), - GenericPubkey::Ecdsa(ecdsa_pk) => { - self.proxy_signers.ecdsa_signers.contains_key(ecdsa_pk) - } - } + pub fn has_proxy_ecdsa(&self, ecdsa_pk: &EcdsaPublicKey) -> bool { + self.proxy_signers.ecdsa_signers.contains_key(ecdsa_pk) + } + + pub fn has_proxy_bls(&self, bls_pk: &BlsPublicKey) -> bool { + self.proxy_signers.bls_signers.contains_key(bls_pk) } pub fn get_delegation_bls( @@ -252,7 +257,7 @@ mod tests { ); assert!( - signing_manager.has_proxy(&signed_delegation.message.proxy.into()), + signing_manager.has_proxy_bls(&signed_delegation.message.proxy), "Newly generated proxy key must be present in the signing manager's registry." ); } @@ -327,7 +332,7 @@ mod tests { ); assert!( - signing_manager.has_proxy(&signed_delegation.message.proxy.into()), + signing_manager.has_proxy_ecdsa(&signed_delegation.message.proxy), "Newly generated proxy key must be present in the signing manager's registry." ); } diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 430faade..7da84b50 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -111,9 +111,12 @@ async fn handle_get_pubkeys( let signing_manager = state.manager.read().await; let consensus = signing_manager.consensus_pubkeys(); - let proxy = signing_manager.proxy_pubkeys().get(&module_id).cloned().unwrap_or_default(); + let proxy_bls = + signing_manager.proxy_pubkeys_bls().get(&module_id).cloned().unwrap_or_default(); + let proxy_ecdsa = + signing_manager.proxy_pubkeys_ecdsa().get(&module_id).cloned().unwrap_or_default(); - let res = GetPubkeysResponse { consensus, proxy }; + let res = GetPubkeysResponse { consensus, proxy_bls, proxy_ecdsa }; Ok((StatusCode::OK, Json(res)).into_response()) } diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index cb908fa4..34e4361d 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -42,7 +42,12 @@ impl DaCommitService { // the config has the signer_client already setup, we can use it to interact // with the Signer API let pubkeys = self.config.signer_client.get_pubkeys().await?; - info!(consensus = pubkeys.consensus.len(), proxy = pubkeys.proxy.len(), "Received pubkeys"); + info!( + consensus = pubkeys.consensus.len(), + proxy_bls = pubkeys.proxy_bls.len(), + proxy_ecdsa = pubkeys.proxy_ecdsa.len(), + "Received pubkeys" + ); let pubkey = *pubkeys.consensus.first().ok_or_eyre("no key available")?; info!("Registered validator {pubkey}"); From f075db9a05b1c1e5422ab0ccb5b1476cdedc5e58 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 28 Aug 2024 12:58:43 +0300 Subject: [PATCH 28/37] refactor(signer/request): move `GetPubkeysResponse` --- crates/common/src/commit/client.rs | 16 ++++------------ crates/common/src/commit/request.rs | 11 +++++++++-- crates/signer/src/service.rs | 5 ++--- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 3aab8d19..874c2141 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -4,14 +4,14 @@ use alloy::rpc::types::beacon::BlsSignature; use axum::body::Bytes; use eyre::WrapErr; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use super::{ constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, error::SignerClientError, request::{ - EncryptionScheme, GenerateProxyRequest, PublicKey, SignConsensusRequest, SignProxyRequest, - SignRequest, SignedProxyDelegation, + EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, PublicKey, + SignConsensusRequest, SignProxyRequest, SignRequest, SignedProxyDelegation, }, }; use crate::{ @@ -22,14 +22,6 @@ use crate::{ DEFAULT_REQUEST_TIMEOUT, }; -// TODO(David): move to `request.rs` -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct GetPubkeysResponse { - pub consensus: Vec, - pub proxy_bls: Vec, - pub proxy_ecdsa: Vec, -} - /// Client used by commit modules to request signatures via the Signer API #[derive(Debug, Clone)] pub struct SignerClient { @@ -73,7 +65,7 @@ impl SignerClient { }); } - let parsed_response: GetPubkeysResponse = serde_json::from_slice(&response_bytes)?; + let parsed_response = serde_json::from_slice(&response_bytes)?; Ok(parsed_response) } diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 7b140f8d..90fd7ae0 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -68,7 +68,7 @@ impl fmt::Display for SignedProxyDelegation { } } -// TODO(David): This struct shouldn't be visible in the client SDK +// TODO(David): This struct shouldn't be visible to module authors #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum SignRequest { @@ -146,7 +146,7 @@ pub enum EncryptionScheme { Ecdsa, } -// TODO(David): This struct shouldn't be visible in the client SDK +// TODO(David): This struct shouldn't be visible to module authors #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GenerateProxyRequest { pub consensus_pubkey: BlsPublicKey, @@ -158,3 +158,10 @@ impl GenerateProxyRequest { GenerateProxyRequest { consensus_pubkey, scheme } } } + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GetPubkeysResponse { + pub consensus: Vec, + pub proxy_bls: Vec, + pub proxy_ecdsa: Vec, +} diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index 7da84b50..c4032aa7 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -12,11 +12,10 @@ use axum_extra::TypedHeader; use bimap::BiHashMap; use cb_common::{ commit::{ - client::GetPubkeysResponse, constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, request::{ - EncryptionScheme, GenerateProxyRequest, SignConsensusRequest, SignProxyRequest, - SignRequest, + EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, SignConsensusRequest, + SignProxyRequest, SignRequest, }, }, config::StartSignerConfig, From 9c4f41205aa6d16af50515c267706f4917c792e6 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 28 Aug 2024 15:35:10 +0300 Subject: [PATCH 29/37] refactor(signer): reduce code duplication around `SignRequest` --- crates/common/src/commit/client.rs | 25 ++++++++++--------------- crates/common/src/commit/request.rs | 14 ++------------ 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 874c2141..8c2c6e4b 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use alloy::rpc::types::beacon::BlsSignature; -use axum::body::Bytes; use eyre::WrapErr; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use serde::Deserialize; @@ -71,7 +70,10 @@ impl SignerClient { } /// Send a signature request - async fn request_signature(&self, request: &SignRequest) -> Result { + async fn request_signature(&self, request: &SignRequest) -> Result + where + T: for<'de> Deserialize<'de>, + { let url = format!("{}{}", self.url, REQUEST_SIGNATURE_PATH); let res = self.client.post(&url).json(&request).send().await?; @@ -85,37 +87,30 @@ impl SignerClient { }); } - Ok(response_bytes) + let signature = serde_json::from_slice(&response_bytes)?; + + Ok(signature) } pub async fn request_consensus_signature( &self, request: SignConsensusRequest, ) -> Result { - let request = SignRequest::Consensus(request); - let raw_signature = self.request_signature(&request).await?; - - let signature = serde_json::from_slice(&raw_signature)?; - - Ok(signature) + self.request_signature(&request.into()).await } pub async fn request_proxy_ecdsa_signature( &self, request: SignProxyRequest, ) -> Result { - let raw_signature = self.request_signature(&request.into()).await?; - let signature = serde_json::from_slice(&raw_signature)?; - Ok(signature) + self.request_signature(&request.into()).await } pub async fn request_proxy_bls_signature( &self, request: SignProxyRequest, ) -> Result { - let raw_signature = self.request_signature(&request.into()).await?; - let signature = serde_json::from_slice(&raw_signature)?; - Ok(signature) + self.request_signature(&request.into()).await } async fn generate_proxy_key( diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 90fd7ae0..df6ba8e7 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -1,6 +1,7 @@ use std::fmt::{self, Debug, Display, LowerHex}; use alloy::rpc::types::beacon::BlsSignature; +use derive_more::derive::From; use serde::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; @@ -69,7 +70,7 @@ impl fmt::Display for SignedProxyDelegation { } // TODO(David): This struct shouldn't be visible to module authors -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, From)] #[serde(tag = "type", rename_all = "snake_case")] pub enum SignRequest { Consensus(SignConsensusRequest), @@ -127,17 +128,6 @@ impl SignProxyRequest { } } -impl From> for SignRequest { - fn from(value: SignProxyRequest) -> Self { - Self::ProxyEcdsa(value) - } -} -impl From> for SignRequest { - fn from(value: SignProxyRequest) -> Self { - Self::ProxyBls(value) - } -} - #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum EncryptionScheme { #[serde(rename = "bls")] From 4e652d97d33ce7dc3051db072a0f0298878ee315 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 28 Aug 2024 16:21:39 +0300 Subject: [PATCH 30/37] style/refactor(signer): reduce method size --- crates/common/src/commit/client.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 8c2c6e4b..6d3966e6 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -51,22 +51,16 @@ impl SignerClient { /// requested. // TODO: add more docs on how proxy keys work pub async fn get_pubkeys(&self) -> Result { - let url = format!("{}{}", self.url, GET_PUBKEYS_PATH); - let res = self.client.get(&url).send().await?; + let res = self.client.get(&format!("{}{}", self.url, GET_PUBKEYS_PATH)).send().await?; - let status = res.status(); - let response_bytes = res.bytes().await?; - - if !status.is_success() { + if !res.status().is_success() { return Err(SignerClientError::FailedRequest { - status: status.as_u16(), - error_msg: String::from_utf8_lossy(&response_bytes).into_owned(), + status: res.status().as_u16(), + error_msg: String::from_utf8_lossy(&res.bytes().await?).into_owned(), }); } - let parsed_response = serde_json::from_slice(&response_bytes)?; - - Ok(parsed_response) + Ok(serde_json::from_slice(&res.bytes().await?)?) } /// Send a signature request From 9907059c6266ed8d69cfe04572e87647a9d23eae Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 28 Aug 2024 17:30:09 +0300 Subject: [PATCH 31/37] refactor(deps)!: revert version updates not immediately necessary --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c74c858e..622e28b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ cb-signer = { path = "crates/signer" } alloy = { version = "0.2.0", features = ["rpc-types-beacon"] } ethereum_ssz = "0.5" ethereum_ssz_derive = "0.5.3" -ssz_types = "0.6.0" +ssz_types = "0.5" ethereum_serde_utils = "0.5.2" ethereum-types = "0.14.1" @@ -59,8 +59,8 @@ prometheus = "0.13.4" # crypto blst = "0.3.11" -tree_hash = "0.6.0" -tree_hash_derive = "0.6.0" +tree_hash = "0.5" +tree_hash_derive = "0.5" eth2_keystore = { git = "https://github.com/sigp/lighthouse", rev = "9e12c21f268c80a3f002ae0ca27477f9f512eb6f" } elliptic-curve = { version = "0.13", features = ["serde"] } generic-array = { version = "0.14.7", features = ["serde"] } From 0ff20910bf12216c0c63926c2fd0c989bfdda2bc Mon Sep 17 00:00:00 2001 From: David Petrov Date: Wed, 28 Aug 2024 17:47:23 +0300 Subject: [PATCH 32/37] chore(deps)!: remove unused deps they're reexported from the `k256` crate; were previously explicitly imported to enable `serde` feature flag, that's not necessary any longer after type simplifications around inner ECDSA pubkey/signature representation. --- Cargo.toml | 2 -- crates/common/Cargo.toml | 2 -- crates/common/src/signer/schemes/ecdsa.rs | 6 ++++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 622e28b1..203ae372 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,8 +62,6 @@ blst = "0.3.11" tree_hash = "0.5" tree_hash_derive = "0.5" eth2_keystore = { git = "https://github.com/sigp/lighthouse", rev = "9e12c21f268c80a3f002ae0ca27477f9f512eb6f" } -elliptic-curve = { version = "0.13", features = ["serde"] } -generic-array = { version = "0.14.7", features = ["serde"] } k256 = "0.13" # docker diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 0aff58f5..ec2b9a9f 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -35,8 +35,6 @@ blst.workspace = true tree_hash.workspace = true tree_hash_derive.workspace = true eth2_keystore.workspace = true -elliptic-curve.workspace = true -generic-array.workspace = true k256.workspace = true # misc diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index 1e1e70ec..359a3054 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -2,8 +2,10 @@ use core::fmt; use std::hash::Hash; use derive_more::derive::{Deref, From, Into}; -use generic_array::GenericArray; -use k256::ecdsa::{Signature as EcdsaSignatureInner, VerifyingKey as EcdsaPublicKeyInner}; +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::{ From dea0b0c926fded8c69c49cf89dadf16b831fbd68 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 29 Aug 2024 10:10:59 +0300 Subject: [PATCH 33/37] chore(signer): add TODO for `BlsPublicKey` wrapper --- crates/common/src/signer/schemes/bls.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index bbc911cd..1ee4faae 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -15,6 +15,12 @@ use crate::{ pub type BlsSecretKey = blst::min_pk::SecretKey; +// TODO(David): +// This wrapper type is potentially a temporary solution, merely to implement +// `TreeHash`. Remove when progress is made on this issue (https://github.com/sigp/tree_hash/issues/22) +// or refine the boundaries between our wrapper `BlsPublicKey` type +// and alloy's `BlsPublicKey` if we stick with it + // std traits #[derive(Debug, Clone, Copy, LowerHex, Display, PartialEq, Eq, Hash, Default)] // serde, ssz, tree_hash @@ -79,8 +85,6 @@ fn random_secret() -> BlsSecretKey { } } -// TODO(David): Refine the boundaries between our wrapper `BlsPublicKey` type -// and alloy's `BlsPublicKey`. This stinks right now... pub fn verify_bls_signature( pubkey: &BlsPublicKeyInner, msg: &[u8], From 5fc50ae598928e68a928934331bff411a612fdf5 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 29 Aug 2024 10:25:48 +0300 Subject: [PATCH 34/37] refactor(signer)!: re-export request types properly from prelude --- bin/src/lib.rs | 4 ++-- examples/da_commit/src/main.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 78c8c8b9..5318bef3 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -2,8 +2,8 @@ pub mod prelude { pub use cb_common::{ commit, commit::request::{ - SignConsensusRequest, SignedProxyDelegation, SignedProxyDelegationBls, - SignedProxyDelegationEcdsa, + SignConsensusRequest, SignProxyRequest, SignedProxyDelegation, + SignedProxyDelegationBls, SignedProxyDelegationEcdsa, }, config::{ load_builder_module_config, load_commit_module_config, load_pbs_config, diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index 34e4361d..47a7e18f 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,7 +1,6 @@ use std::time::Duration; use alloy::rpc::types::beacon::BlsPublicKey; -use commit::request::SignProxyRequest; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; use lazy_static::lazy_static; From 0bcd2d70dab320997f46862a2cfda0fd3d10cc3a Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 29 Aug 2024 14:59:23 +0300 Subject: [PATCH 35/37] feat(signer/api): update api docs --- api/signer-api.yml | 143 +++++++++++++++++++++++---------- examples/da_commit/src/main.rs | 2 +- 2 files changed, 103 insertions(+), 42 deletions(-) diff --git a/api/signer-api.yml b/api/signer-api.yml index 1cccb891..e03331e6 100644 --- a/api/signer-api.yml +++ b/api/signer-api.yml @@ -15,7 +15,7 @@ paths: - BearerAuth: [] responses: "200": - description: A list of Bls pubkeys + description: "All public keys available to the module: consensus pubkeys (BLS) and proxy pubkeys (BLS and ECDSA)" content: application/json: schema: @@ -25,18 +25,17 @@ paths: 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 + $ref: '#/components/schemas/BlsPubkey' + proxy_bls: + description: BLS proxy validator pubkeys type: array items: - type: string - format: hex - pattern: "^0x[a-fA-F0-9]{96}$" - example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + $ref: '#/components/schemas/BlsPubkey' + proxy_ecdsa: + description: ECDSA proxy validator pubkeys + type: array + items: + $ref: '#/components/schemas/EcdsaPubkey' "500": description: Internal error content: @@ -67,34 +66,55 @@ paths: application/json: schema: type: object + required: [type, pubkey, object_root] properties: - pubkey: - description: BLS public key of validator + type: + description: Type of the sign request 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 + enum: [consensus, proxy_bls, proxy_ecdsa] + pubkey: + description: Public key of the validator + oneOf: + - $ref: '#/components/schemas/BlsPubkey' + - $ref: '#/components/schemas/EcdsaPubkey' object_root: description: The root of the object to be signed 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" + pubkey: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + ProxyEcdsa: + value: + type: "proxy_ecdsa" + pubkey: "0x023b2806b1b1dfa34dd90b01546906cef3e4c8e0fc0cba60480e9eb4d0a0828311" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" responses: "200": - description: Successs + description: Success content: application/json: schema: - type: string - description: The validator signature - format: hex - pattern: "^0x[a-fA-F0-9]{192}$" - example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + oneOf: + - $ref: '#/components/schemas/BlsSignature' + - $ref: '#/components/schemas/EcdsaSignature' + examples: + Consensus: + value: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + ProxyBls: + value: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + ProxyEcdsa: + value: "0xe6a0c0c41a6d4af9794882c18c5280376cbfb7921453612dea02ed8f47b1208455f07931dc12c4b70c4e8ae216db0136000ec2cf17244189f012de356ac46cec" "404": description: Unknown value (pubkey, etc.) content: @@ -141,13 +161,25 @@ paths: application/json: schema: type: object + required: [pubkey, scheme] properties: pubkey: description: a validator BLS public key for which to generate a proxy key + allOf: + - $ref: '#/components/schemas/BlsPubkey' + scheme: + description: signature scheme to generate proxy keypair for type: string - format: hex - pattern: "^0x[a-fA-F0-9]{96}$" - example: "0xac5e059177afc33263e95d0be0690138b9a1d79a6e19018086a0362e0c30a50bf9e05a08cb44785724d0b2718c5c7118" + enum: [bls, ecdsa] + examples: + Bls: + value: + pubkey: "0xa9e9cff900de07e295a044789fd4bdb6785eb0651ad282f9e76d12afd87e75180bdd64caf2e315b815d7322bd31ab48a" + scheme: "bls" + Ecdsa: + value: + pubkey: "0xa9e9cff900de07e295a044789fd4bdb6785eb0651ad282f9e76d12afd87e75180bdd64caf2e315b815d7322bd31ab48a" + scheme: "ecdsa" responses: "200": description: Successs @@ -161,22 +193,30 @@ paths: 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" + allOf: + - $ref: '#/components/schemas/BlsPubkey' proxy: description: the generated proxy public key - type: string - format: hex - pattern: "^0x[a-fA-F0-9]{96}$" - example: "0x8a481a7a51c430a9bafa64366bc4934f5880f5f1d97646f91680936a53f2a268fdde5369430a2b4bb700c5f82cfbab3f" + oneOf: + - $ref: '#/components/schemas/BlsPubkey' + - $ref: '#/components/schemas/EcdsaPubkey' signature: description: The signature of the proxy delegation - type: string - format: hex - pattern: "^0x[a-fA-F0-9]{192}$" - example: "0xabfacf1cd17d80abfc6fa6b8e534ab25cdb1f95a855706ef604672c8695401a84c7834008e57925d4259c551b7c03d1a16f05b082294fadcba802a61a5cccfb5e96dd1dce4c9dac3f6d15254495019146346670be1f374a67cb0cda2aaf72d00" + allOf: + - $ref: '#/components/schemas/BlsSignature' + examples: + Bls: + value: + message: + delegator: "0xa9e9cff900de07e295a044789fd4bdb6785eb0651ad282f9e76d12afd87e75180bdd64caf2e315b815d7322bd31ab48a" + proxy: "0xb646318d81b7cff3f8aae5040eab11927b4a99542c02970a1ab8069a83e5b76b302705d0b5e0054831ce2af72088bf30" + signature: "0x88274f2d78d30ae429cc16f5c64657b491ccf26291c821cf953da34f16d60947d4f245decdce4a492e8d8f949482051b184aaa890d5dd97788387689335a1fee37cbe55c0227f81b073ce6e93b45f96169f497ed322d3d384d79ccaa7846d5ab" + Ecdsa: + value: + message: + delegator: "0xa9e9cff900de07e295a044789fd4bdb6785eb0651ad282f9e76d12afd87e75180bdd64caf2e315b815d7322bd31ab48a" + proxy: "0x023b2806b1b1dfa34dd90b01546906cef3e4c8e0fc0cba60480e9eb4d0a0828311" + signature: "0xb5b5b71d1701cc45086af3d3d86bf9d3c509442835e5b9f7734923edc9a6c538e743d70613cdef90b7e5b171fbbe6a29075b3f155e4bd66d81ff9dbc3b6d7fa677d169b2ceab727ffa079a31fe1fc0e478752e9da9566a9408e4db24ac6104db" "404": description: Unknown value (pubkey, etc.) content: @@ -216,3 +256,24 @@ components: type: http scheme: bearer bearerFormat: JWT + schemas: + BlsPubkey: + type: string + format: hex + pattern: "^0x[a-fA-F0-9]{96}$" + example: "0xa9e9cff900de07e295a044789fd4bdb6785eb0651ad282f9e76d12afd87e75180bdd64caf2e315b815d7322bd31ab48a" + EcdsaPubkey: + type: string + format: hex + pattern: "^0x[a-fA-F0-9]{64}$" + example: "0x023b2806b1b1dfa34dd90b01546906cef3e4c8e0fc0cba60480e9eb4d0a0828311" + BlsSignature: + type: string + format: hex + pattern: "^0x[a-fA-F0-9]{192}$" + example: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + EcdsaSignature: + type: string + format: hex + pattern: "^0x[a-fA-F0-9]{128}$" + example: "0xe6a0c0c41a6d4af9794882c18c5280376cbfb7921453612dea02ed8f47b1208455f07931dc12c4b70c4e8ae216db0136000ec2cf17244189f012de356ac46cec" diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index 47a7e18f..7ce59814 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -57,7 +57,7 @@ impl DaCommitService { let proxy_delegation_ecdsa = self.config.signer_client.generate_ecdsa_proxy_key(pubkey).await?; - info!("Obtained an ECDSA proxy delegation:\n{proxy_delegation_bls}"); + info!("Obtained an ECDSA proxy delegation:\n{proxy_delegation_ecdsa}"); let proxy_ecdsa = proxy_delegation_ecdsa.message.proxy; let mut data = 0; From f103c8d8b3c7cdd8889afb06a7e0d4ebc0868c67 Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 29 Aug 2024 15:39:03 +0300 Subject: [PATCH 36/37] refactor(signer/client): re-export bls types and rename methods for consistency --- bin/src/lib.rs | 2 +- crates/common/src/commit/client.rs | 8 ++++---- crates/common/src/signer/mod.rs | 2 +- crates/common/src/signer/schemes/bls.rs | 5 ++--- examples/da_commit/src/main.rs | 15 +++++++-------- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 5318bef3..eb9f5067 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -10,7 +10,7 @@ pub mod prelude { load_pbs_custom_config, StartCommitModuleConfig, }, pbs::{BuilderEvent, BuilderEventClient, OnBuilderApiEvent}, - signer::{EcdsaPublicKey, EcdsaSignature}, + signer::{BlsPublicKey, BlsSignature, EcdsaPublicKey, EcdsaSignature}, utils::{ initialize_pbs_tracing_log, initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, utcnow_us, diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 6d3966e6..b61fd42c 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -93,14 +93,14 @@ impl SignerClient { self.request_signature(&request.into()).await } - pub async fn request_proxy_ecdsa_signature( + pub async fn request_proxy_signature_ecdsa( &self, request: SignProxyRequest, ) -> Result { self.request_signature(&request.into()).await } - pub async fn request_proxy_bls_signature( + pub async fn request_proxy_signature_bls( &self, request: SignProxyRequest, ) -> Result { @@ -132,7 +132,7 @@ impl SignerClient { Ok(signed_proxy_delegation) } - pub async fn generate_bls_proxy_key( + pub async fn generate_proxy_key_bls( &self, consensus_pubkey: BlsPublicKey, ) -> Result, SignerClientError> { @@ -143,7 +143,7 @@ impl SignerClient { Ok(bls_signed_proxy_delegation) } - pub async fn generate_ecdsa_proxy_key( + pub async fn generate_proxy_key_ecdsa( &self, consensus_pubkey: BlsPublicKey, ) -> Result, SignerClientError> { diff --git a/crates/common/src/signer/mod.rs b/crates/common/src/signer/mod.rs index f38a5ed7..3622e1c1 100644 --- a/crates/common/src/signer/mod.rs +++ b/crates/common/src/signer/mod.rs @@ -1,7 +1,7 @@ pub mod schemes; pub use schemes::{ - bls::{BlsSecretKey, BlsSigner}, + bls::{BlsPublicKey, BlsSecretKey, BlsSignature, BlsSigner}, ecdsa::{EcdsaPublicKey, EcdsaSecretKey, EcdsaSignature, EcdsaSigner}, }; diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index 1ee4faae..00911766 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -1,6 +1,5 @@ -use alloy::rpc::types::beacon::{ - constants::BLS_DST_SIG, BlsPublicKey as BlsPublicKeyInner, BlsSignature, -}; +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 serde::{Deserialize, Serialize}; diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index 7ce59814..09abcf2c 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,6 +1,5 @@ use std::time::Duration; -use alloy::rpc::types::beacon::BlsPublicKey; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; use lazy_static::lazy_static; @@ -51,19 +50,19 @@ impl DaCommitService { let pubkey = *pubkeys.consensus.first().ok_or_eyre("no key available")?; info!("Registered validator {pubkey}"); - let proxy_delegation_bls = self.config.signer_client.generate_bls_proxy_key(pubkey).await?; + let proxy_delegation_bls = self.config.signer_client.generate_proxy_key_bls(pubkey).await?; info!("Obtained a BLS proxy delegation:\n{proxy_delegation_bls}"); let proxy_bls = proxy_delegation_bls.message.proxy; let proxy_delegation_ecdsa = - self.config.signer_client.generate_ecdsa_proxy_key(pubkey).await?; + self.config.signer_client.generate_proxy_key_ecdsa(pubkey).await?; info!("Obtained an ECDSA proxy delegation:\n{proxy_delegation_ecdsa}"); let proxy_ecdsa = proxy_delegation_ecdsa.message.proxy; let mut data = 0; loop { - self.send_request(data, pubkey.into(), proxy_bls.into(), proxy_ecdsa).await?; + self.send_request(data, pubkey, proxy_bls, proxy_ecdsa).await?; sleep(Duration::from_secs(self.config.extra.sleep_secs)).await; data += 1; } @@ -78,16 +77,16 @@ impl DaCommitService { ) -> Result<()> { let datagram = Datagram { data }; - let request = SignConsensusRequest::builder(pubkey.into()).with_msg(&datagram); + let request = SignConsensusRequest::builder(pubkey).with_msg(&datagram); let signature = self.config.signer_client.request_consensus_signature(request); - let proxy_request_bls = SignProxyRequest::builder(proxy_bls.into()).with_msg(&datagram); + let proxy_request_bls = SignProxyRequest::builder(proxy_bls).with_msg(&datagram); let proxy_signature_bls = - self.config.signer_client.request_proxy_bls_signature(proxy_request_bls); + self.config.signer_client.request_proxy_signature_bls(proxy_request_bls); let proxy_request_ecdsa = SignProxyRequest::builder(proxy_ecdsa).with_msg(&datagram); let proxy_signature_ecdsa = - self.config.signer_client.request_proxy_ecdsa_signature(proxy_request_ecdsa); + self.config.signer_client.request_proxy_signature_ecdsa(proxy_request_ecdsa); let (signature, proxy_signature_bls, proxy_signature_ecdsa) = { let res = tokio::join!(signature, proxy_signature_bls, proxy_signature_ecdsa); From da550d365a61d153ac9ff81cbadc793498e4242d Mon Sep 17 00:00:00 2001 From: David Petrov Date: Thu, 29 Aug 2024 15:41:42 +0300 Subject: [PATCH 37/37] chore(docs): update md files in book --- README.md | 4 ++-- docs/docs/developing/commit-module.md | 27 +++++++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1052ce5b..4249c4ae 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,10 @@ async fn main() { let pubkey = *pubkeys.consensus.first().unwrap(); let datagram = Datagram { data: 42 }; - let request = SignRequest::builder(pubkey).with_msg(&datagram); + let request = SignConsensusRequest::builder(pubkey).with_msg(&datagram); let signature = config .signer_client - .request_signature(&request) + .request_consensus_signature(&request) .await .unwrap(); diff --git a/docs/docs/developing/commit-module.md b/docs/docs/developing/commit-module.md index cfd388c9..f1ad147d 100644 --- a/docs/docs/developing/commit-module.md +++ b/docs/docs/developing/commit-module.md @@ -46,7 +46,7 @@ The loaded `config` also has a few other useful fields: ## Requesting signatures -At its core the Signer Module simply provides a signature on a 32-byte data digest. The signatures are currently provided with either the validator keys (BLS) or a proxy key (also BLS) for a given validator key, both on the [builder domain](https://github.com/Commit-Boost/commit-boost-client/blob/main/crates/common/src/signature.rs#L88-L96). Eventually we plan to support [alternative](https://github.com/Commit-Boost/commit-boost-client/issues/20) signing schemes, too. +At its core the Signer Module simply provides a signature on a 32-byte data digest. The signatures are currently provided with either the validator keys (BLS) or a proxy key (BLS or ECDSA) for a given validator key, both on the [builder domain](https://github.com/Commit-Boost/commit-boost-client/blob/main/crates/common/src/signature.rs#L88-L96). In the example we use `TreeHash`, already used in the CL, to create the digest from a custom struct: ```rust @@ -69,26 +69,37 @@ Then, we can request a signature either with a consensus key or with a proxy key Requesting a signature is as simple as: ```rust let datagram = Datagram { data: 1 }; -let request = SignRequest::builder(pubkey).with_msg(&datagram); -let signature = config.signer_client.request_signature(&request).await.unwrap(); +let request = SignConsensusRequest::builder(pubkey).with_msg(&datagram); +let signature = config.signer_client.request_consensus_signature(&request).await.unwrap(); ``` Where `pubkey` is the validator (consensus) public key for which the signature is requested. ### With a proxy key -You'll have to first request a proxy key be generated for a given consensus key: +You'll have to first request a proxy key be generated for a given consensus key. +We support two signature schemes for proxies: BLS or ECDSA. + +To request a proxy: ```rust -let proxy_delegation = self.config.signer_client.generate_proxy_key(pubkey).await?; +// BLS proxy +let proxy_delegation = self.config.signer_client.generate_proxy_key_bls(pubkey).await?; +let proxy_pubkey = proxy_delegation.message.proxy; + +// or ECDSA proxy +let proxy_delegation = self.config.signer_client.generate_proxy_key_ecdsa(pubkey).await?; let proxy_pubkey = proxy_delegation.message.proxy; ``` Where `pubkey` is the validator (consensus) public key for which a proxy is to be generated. -Then, the only difference from the direct approach is explicitly saying that the signature request uses a proxy key: +Then you can use the generated proxy key to request a signature: ```rust let datagram = Datagram { data: 1 }; -let request = SignRequest::builder(proxy_pubkey).is_proxy().with_msg(&datagram); -let signature = config.signer_client.request_signature(&request).await.unwrap(); +let request = SignProxyRequest::builder(proxy_pubkey).with_msg(&datagram); +// if `proxy_pubkey` is a BLS proxy +let signature = config.signer_client.request_proxy_signature_bls(&request).await.unwrap(); +// or for ECDSA proxy +let signature = config.signer_client.request_proxy_signature_ecdsa(&request).await.unwrap(); ``` ## Metrics