Skip to content

Commit

Permalink
refactor,feat(sidecar): SignerBLS is now Enum, sign with keystore wip
Browse files Browse the repository at this point in the history
  • Loading branch information
thedevbirb committed Oct 9, 2024
1 parent 5ff71a9 commit 697db44
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 169 deletions.
136 changes: 4 additions & 132 deletions bolt-sidecar/src/crypto/bls.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::fmt::Debug;

use alloy::primitives::FixedBytes;
use alloy::{primitives::FixedBytes, rpc::types::beacon::constants::BLS_PUBLIC_KEY_BYTES_LEN};
use blst::{min_pk::Signature, BLST_ERROR};
use ethereum_consensus::{crypto::PublicKey as BlsPublicKey, deneb::compute_signing_root};
use rand::RngCore;

pub use blst::min_pk::{PublicKey, SecretKey as BlsSecretKey};
pub use ethereum_consensus::deneb::BlsSignature;

use crate::ChainConfig;
use crate::{ChainConfig, CommitBoostSigner};

/// The BLS Domain Separator used in Ethereum 2.0.
pub const BLS_DST_PREFIX: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
Expand All @@ -35,134 +35,6 @@ pub trait SignerBLS: Send + Debug {
async fn sign_commit_boost_root(&self, data: &[u8; 32]) -> eyre::Result<BLSSig>;
}

/// A BLS signer that can sign any type that implements the [`SignableBLS`] trait.
#[derive(Clone)]
pub struct Signer {
chain: ChainConfig,
key: BlsSecretKey,
}

impl Debug for Signer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Signer")
.field("pubkey", &self.pubkey())
.field("chain", &self.chain.name())
.finish()
}
}

impl Signer {
/// Create a new signer with the given BLS secret key.
pub fn new(key: BlsSecretKey, chain: ChainConfig) -> Self {
Self { key, chain }
}

/// Create a signer with a random BLS key configured for Mainnet for testing.
#[cfg(test)]
pub fn random() -> Self {
Self { key: random_bls_secret(), chain: ChainConfig::mainnet() }
}

/// Get the public key of the signer.
pub fn pubkey(&self) -> BlsPublicKey {
let pk = self.key.sk_to_pk();
BlsPublicKey::try_from(pk.to_bytes().as_ref()).unwrap()
}

/// Sign an SSZ object root with the Application Builder domain.
pub fn sign_application_builder_root(&self, root: [u8; 32]) -> eyre::Result<BLSSig> {
self.sign_root(root, self.chain.application_builder_domain())
}

/// Sign an SSZ object root with the Commit Boost domain.
pub fn sign_commit_boost_root(&self, root: [u8; 32]) -> eyre::Result<BLSSig> {
self.sign_root(root, self.chain.commit_boost_domain())
}

/// Sign an SSZ object root with the given domain.
pub fn sign_root(&self, root: [u8; 32], domain: [u8; 32]) -> eyre::Result<BLSSig> {
let signing_root = compute_signing_root(&root, domain)?;
let sig = self.key.sign(signing_root.as_slice(), BLS_DST_PREFIX, &[]);
Ok(BLSSig::from_slice(&sig.to_bytes()))
}

/// Verify the signature with the public key of the signer using the Application Builder domain.
pub fn verify_application_builder_root(
&self,
root: [u8; 32],
signature: &Signature,
) -> eyre::Result<()> {
self.verify_root(root, signature, self.chain.application_builder_domain())
}

/// Verify the signature with the public key of the signer using the Commit Boost domain.
pub fn verify_commit_boost_root(
&self,
root: [u8; 32],
signature: &Signature,
) -> eyre::Result<()> {
self.verify_root(root, signature, self.chain.commit_boost_domain())
}

/// Verify the signature of the object with the given public key.
pub fn verify_root(
&self,
root: [u8; 32],
signature: &Signature,
domain: [u8; 32],
) -> eyre::Result<()> {
let signing_root = compute_signing_root(&root, domain)?;
let pk = blst::min_pk::PublicKey::from_bytes(self.pubkey().as_ref()).unwrap();

let res = signature.verify(true, signing_root.as_ref(), BLS_DST_PREFIX, &[], &pk, true);
if res == BLST_ERROR::BLST_SUCCESS {
Ok(())
} else {
eyre::bail!(format!("Invalid signature: {:?}", res))
}
}
}

#[async_trait::async_trait]
impl SignerBLS for Signer {
fn pubkey(&self) -> BlsPublicKey {
self.pubkey()
}

async fn sign_commit_boost_root(&self, data: &[u8; 32]) -> eyre::Result<BLSSig> {
self.sign_commit_boost_root(*data)
}
}

/// Generate a random BLS secret key.
pub fn random_bls_secret() -> BlsSecretKey {
let mut rng = rand::thread_rng();
let mut ikm = [0u8; 32];
rng.fill_bytes(&mut ikm);
BlsSecretKey::key_gen(&ikm, &[]).unwrap()
}

#[cfg(test)]
mod tests {
use crate::{
crypto::bls::{SignableBLS, Signer},
test_util::TestSignableData,
};

use rand::Rng;

#[tokio::test]
async fn test_bls_signer() {
let signer = Signer::random();

// Generate random data for the test
let mut rng = rand::thread_rng();
let mut data = [0u8; 32];
rng.fill(&mut data);
let msg = TestSignableData { data };

let signature = signer.sign_commit_boost_root(msg.digest()).unwrap();
let sig = blst::min_pk::Signature::from_bytes(signature.as_ref()).unwrap();
assert!(signer.verify_commit_boost_root(msg.digest(), &sig).is_ok());
}
pub fn cl_public_key_to_arr(pubkey: BlsPublicKey) -> [u8; BLS_PUBLIC_KEY_BYTES_LEN] {
pubkey.as_ref().try_into().expect("BLS keys are 48 bytes")
}
71 changes: 46 additions & 25 deletions bolt-sidecar/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::time::{Duration, Instant};

use alloy::{rpc::types::beacon::events::HeadEvent, signers::local::PrivateKeySigner};
use beacon_api_client::mainnet::Client as BeaconClient;
use cb_common::signer::BlsSigner;
use ethereum_consensus::{
clock::{self, SlotStream, SystemTimeProvider},
phase0::mainnet::SLOTS_PER_EPOCH,
Expand All @@ -16,23 +17,28 @@ use crate::{
server::{CommitmentsApiServer, Event as CommitmentEvent},
spec::Error as CommitmentError,
},
crypto::{bls::Signer as BlsSigner, SignableBLS, SignerBLS, SignerECDSA},
crypto::{
bls::{cl_public_key_to_arr, Signer as BlsSigner},
SignableBLS, SignerBLS, SignerECDSA,
},
primitives::{
CommitmentRequest, ConstraintsMessage, FetchPayloadRequest, LocalPayloadFetcher,
SignedConstraints, TransactionExt,
},
signer::local::Signer,
start_builder_proxy_server,
state::{fetcher::StateFetcher, ConsensusState, ExecutionState, HeadTracker, StateClient},
telemetry::ApiMetrics,
BuilderProxyConfig, CommitBoostSigner, ConstraintsApi, ConstraintsClient, LocalBuilder, Opts,
SignerBLSEnum,
};

/// The driver for the sidecar, responsible for managing the main event loop.
pub struct SidecarDriver<C, BLS, ECDSA> {
pub struct SidecarDriver<C, ECDSA> {
head_tracker: HeadTracker,
execution: ExecutionState<C>,
consensus: ConsensusState,
constraint_signer: BLS,
constraint_signer: SignerBLSEnum,
commitment_signer: ECDSA,
local_builder: LocalBuilder,
constraints_client: ConstraintsClient,
Expand All @@ -58,15 +64,17 @@ impl<B: SignerBLS> fmt::Debug for SidecarDriver<StateClient, B, PrivateKeySigner
}
}

impl SidecarDriver<StateClient, BlsSigner, PrivateKeySigner> {
impl SidecarDriver<StateClient, PrivateKeySigner> {
/// Create a new sidecar driver with the given [Config] and private key signer.
pub async fn with_local_signer(opts: &Opts) -> eyre::Result<Self> {
// The default state client simply uses the execution API URL to fetch state updates.
let state_client = StateClient::new(opts.execution_api_url.clone());

// Constraints are signed with a BLS private key
let constraint_signer =
BlsSigner::new(opts.signing.private_key.clone().expect("local signer").0, opts.chain);
let constraint_signer = SignerBLSEnum::Local(Signer::new(
opts.signing.private_key.clone().expect("local signer").0,
opts.chain,
));

// Commitment responses are signed with a regular Ethereum wallet private key.
// This is now generated randomly because slashing is not yet implemented.
Expand All @@ -76,7 +84,7 @@ impl SidecarDriver<StateClient, BlsSigner, PrivateKeySigner> {
}
}

impl SidecarDriver<StateClient, CommitBoostSigner, CommitBoostSigner> {
impl SidecarDriver<StateClient, CommitBoostSigner> {
/// Create a new sidecar driver with the given [Config] and commit-boost signer.
pub async fn with_commit_boost_signer(opts: &Opts) -> eyre::Result<Self> {
// The default state client simply uses the execution API URL to fetch state updates.
Expand All @@ -91,21 +99,20 @@ impl SidecarDriver<StateClient, CommitBoostSigner, CommitBoostSigner> {
)
.await?;

// Constraints are signed with commit-boost signer
let constraint_signer = commit_boost_signer.clone();
let cb_bls_signer = SignerBLSEnum::CommitBoost(commit_boost_signer.clone());

// Commitment responses are signed with commit-boost signer
let commitment_signer = commit_boost_signer.clone();

Self::from_components(opts, constraint_signer, commitment_signer, state_client).await
Self::from_components(opts, cb_bls_signer, commitment_signer, state_client).await
}
}

impl<C: StateFetcher, BLS: SignerBLS, ECDSA: SignerECDSA> SidecarDriver<C, BLS, ECDSA> {
impl<C: StateFetcher, ECDSA: SignerECDSA> SidecarDriver<C, ECDSA> {
/// Create a new sidecar driver with the given components
pub async fn from_components(
opts: &Opts,
constraint_signer: BLS,
constraint_signer: SignerBLSEnum,
commitment_signer: ECDSA,
fetcher: C,
) -> eyre::Result<Self> {
Expand Down Expand Up @@ -198,7 +205,7 @@ impl<C: StateFetcher, BLS: SignerBLS, ECDSA: SignerECDSA> SidecarDriver<C, BLS,

// TODO(nico): currently we don't use the validator pubkey to sign. this will be re-added
// later to support different signing setups.
let _validator_pubkey = match self.consensus.validate_request(&request) {
let validator_pubkey = match self.consensus.validate_request(&request) {
Ok(index) => index,
Err(err) => {
error!(?err, "Consensus: failed to validate request");
Expand Down Expand Up @@ -227,24 +234,38 @@ impl<C: StateFetcher, BLS: SignerBLS, ECDSA: SignerECDSA> SidecarDriver<C, BLS,
// parse the request into constraints and sign them
let slot = inclusion_request.slot;

let pubkey = match self.constraint_signer {
SignerBLSEnum::Local(ref signer) => signer.pubkey(),
SignerBLSEnum::CommitBoost(ref signer) => signer.pubkey(),
SignerBLSEnum::Keystore(_) => validator_pubkey.clone(),
};

// NOTE: we iterate over the transactions in the request and generate a signed constraint
// for each one. This is because the transactions in the commitment request are not
// supposed to be treated as a relative-ordering bundle, but a batch
// with no ordering guarantees.
for tx in inclusion_request.txs {
let tx_type = tx.tx_type();
let pubkey = self.constraint_signer.pubkey();
let message = ConstraintsMessage::from_transaction(pubkey, slot, tx);

let signed_constraints =
match self.constraint_signer.sign_commit_boost_root(&message.digest()).await {
Ok(signature) => SignedConstraints { message, signature },
Err(err) => {
error!(?err, "Failed to sign constraints");
let _ = response.send(Err(CommitmentError::Internal));
return;
}
};
let message = ConstraintsMessage::from_transaction(pubkey.clone(), slot, tx);
let digest = message.digest();

let signature = match self.constraint_signer {
SignerBLSEnum::Local(ref signer) => signer.sign_commit_boost_root(digest),
SignerBLSEnum::CommitBoost(ref signer) => {
signer.sign_commit_boost_root(digest).await
}
SignerBLSEnum::Keystore(ref signer) => {
signer.sign_commit_boost_root(digest, cl_public_key_to_arr(pubkey))
}
};
let signed_constraints = match signature {
Ok(signature) => SignedConstraints { message, signature },
Err(e) => {
error!(?e, "Failed to sign constraints");
let _ = response.send(Err(CommitmentError::Internal));
return;
}
};

ApiMetrics::increment_transactions_preconfirmed(tx_type);
self.execution.add_constraint(slot, signed_constraints);
Expand Down
2 changes: 1 addition & 1 deletion bolt-sidecar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub mod state;

/// The signers available to the sidecar
mod signer;
pub use signer::commit_boost::CommitBoostSigner;
pub use signer::{commit_boost::CommitBoostSigner, SignerBLSEnum};

/// Utilities for testing
#[cfg(test)]
Expand Down
9 changes: 4 additions & 5 deletions bolt-sidecar/src/signer/commit_boost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,18 @@ impl CommitBoostSigner {
}
}

#[async_trait::async_trait]
impl SignerBLS for CommitBoostSigner {
fn pubkey(&self) -> BlsPublicKey {
impl CommitBoostSigner {
pub fn pubkey(&self) -> BlsPublicKey {
self.get_consensus_pubkey()
}

async fn sign_commit_boost_root(&self, data: &[u8; 32]) -> eyre::Result<BlsSignature> {
pub async fn sign_commit_boost_root(&self, data: [u8; 32]) -> eyre::Result<BlsSignature> {
// convert the pubkey from ethereum_consensus to commit-boost format
let pubkey = cb_common::signer::BlsPublicKey::from(
alloy::rpc::types::beacon::BlsPublicKey::from_slice(self.pubkey().as_ref()),
);

let request = SignConsensusRequest { pubkey, object_root: *data };
let request = SignConsensusRequest { pubkey, object_root: data };

debug!(?request, "Requesting signature from commit_boost");

Expand Down
16 changes: 14 additions & 2 deletions bolt-sidecar/src/signer/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@ use eyre::eyre;
use lighthouse_bls::Keypair;
use lighthouse_eth2_keystore::Keystore;

use crate::{config::signing::BlsSecretKey, crypto::SignerBLS};
use crate::{
config::signing::BlsSecretKey,
crypto::{bls::BLSSig, SignerBLS},
};

#[derive(Clone)]
pub struct KeystoreSigner {
keypairs: Vec<Keypair>,
}

impl KeystoreSigner {
fn new(keys_path: Option<&str>, password: &[u8]) -> eyre::Result<Self> {
pub fn new(keys_path: Option<&str>, password: &[u8]) -> eyre::Result<Self> {
let keystores_paths = keystore_paths(keys_path)?;
let mut keypairs = Vec::with_capacity(keystores_paths.len());

Expand All @@ -42,6 +46,14 @@ impl KeystoreSigner {

Ok(Self { keypairs })
}

pub fn sign_commit_boost_root(
&self,
root: [u8; 32],
public_key: [u8; 64],
) -> eyre::Result<BLSSig> {
todo!()
}
}

/// Returns the paths of all the keystore files provided an optional `keys_path`, which defaults to `keys`.
Expand Down
Loading

0 comments on commit 697db44

Please sign in to comment.