diff --git a/Cargo.lock b/Cargo.lock index 61d8e913..c0864266 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5677,6 +5677,7 @@ dependencies = [ "ethereum_ssz", "futures", "serde", + "serde_json", "summit-finalizer", "summit-types", "tokio", diff --git a/example_genesis.toml b/example_genesis.toml index aa244bc0..6c747043 100644 --- a/example_genesis.toml +++ b/example_genesis.toml @@ -1,4 +1,4 @@ -eth_genesis_hash = "0x655cc1ecc77fe1eab4b1e62a1f461b7fddc9b06109b5ab3e9dc68c144b30c773" +eth_genesis_hash = "0x93068b65464eeee04a47a3b16b3123d05a5d83882032525bdb5e297d654857f0" leader_timeout_ms = 2000 notarization_timeout_ms = 4000 nullify_timeout_ms = 4000 diff --git a/node/src/args.rs b/node/src/args.rs index 15ddcb70..358573eb 100644 --- a/node/src/args.rs +++ b/node/src/args.rs @@ -192,13 +192,19 @@ impl Command { // use the context async move to spawn a new runtime let genesis_path = flags.genesis_path.clone(); + let key_store_path = flags.key_store_path.clone(); let rpc_port = flags.rpc_port; let _rpc_handle = context .with_label("rpc_genesis") .spawn(move |_context| async move { let genesis_sender = Command::check_sender(genesis_path, genesis_tx); - if let Err(e) = - start_rpc_server_for_genesis(genesis_sender, rpc_port, cloned_token).await + if let Err(e) = start_rpc_server_for_genesis( + genesis_sender, + key_store_path, + rpc_port, + cloned_token, + ) + .await { error!("RPC server failed: {}", e); } @@ -414,12 +420,18 @@ pub fn run_node_local( // use the context async move to spawn a new runtime let rpc_port = flags.rpc_port; let genesis_path = flags.genesis_path.clone(); + let key_store_path = flags.key_store_path.clone(); let _rpc_handle = context .with_label("rpc_genesis") .spawn(move |_context| async move { let genesis_sender = Command::check_sender(genesis_path, genesis_tx); - if let Err(e) = - start_rpc_server_for_genesis(genesis_sender, rpc_port, cloned_token).await + if let Err(e) = start_rpc_server_for_genesis( + genesis_sender, + key_store_path, + rpc_port, + cloned_token, + ) + .await { error!("RPC server failed: {}", e); } diff --git a/node/src/bin/genesis.rs b/node/src/bin/genesis.rs index 4f3b6301..9e3dc48d 100644 --- a/node/src/bin/genesis.rs +++ b/node/src/bin/genesis.rs @@ -1,15 +1,7 @@ use clap::Parser; -use commonware_codec::{DecodeExt, Encode as _}; -use commonware_cryptography::bls12381::{ - dkg::ops, - primitives::{poly, variant::MinPk}, -}; -use commonware_utils::{from_hex, hex, quorum}; -use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use std::fs; -use std::path::Path; -use summit_types::PublicKey; +use summit_types::GenesisValidator; const DEFAULT_GENESIS_FILE: &str = "./example_genesis.toml"; @@ -23,8 +15,7 @@ pub struct GenesisConfig { skip_timeout_views: u64, max_message_size_bytes: u64, namespace: String, - pub identity: String, - pub validators: Vec, + pub validators: Vec, } impl GenesisConfig { @@ -35,19 +26,6 @@ impl GenesisConfig { } } -#[derive(Debug, Serialize, Deserialize)] -pub struct Validator { - pub public_key: String, - pub ip_address: String, -} - -impl Validator { - pub fn ed25519_pubkey(&self) -> PublicKey { - let pubkey_bytes = from_hex(&self.public_key).unwrap(); - PublicKey::decode(&pubkey_bytes[..]).unwrap() - } -} - #[derive(Parser, Debug)] struct Args { /// input for genesis file @@ -63,14 +41,14 @@ struct Args { fn parse_validators( validators_path: &String, -) -> Result, Box> { +) -> Result, Box> { let rdr = std::fs::File::open(validators_path)?; - let mut validators: Vec = serde_json::from_reader(rdr)?; + let mut validators: Vec = serde_json::from_reader(rdr)?; // NOTE: (important!) // Sort public keys in the same order we do in summit validators.sort_by(|a, b| { - let a_pubkey = a.ed25519_pubkey(); - let b_pubkey = b.ed25519_pubkey(); + let a_pubkey = a.node_pubkey(); + let b_pubkey = b.node_pubkey(); a_pubkey.partial_cmp(&b_pubkey).unwrap() }); Ok(validators) @@ -81,32 +59,10 @@ fn main() -> Result<(), Box> { let validators = parse_validators(&args.validators_path)?; let node_count = validators.len() as u32; - let threshold = quorum(node_count); - let (polynomial, shares) = - ops::generate_shares::<_, MinPk>(&mut OsRng, None, node_count, threshold); - - println!("Network polynomial: {}", hex(&polynomial.encode())); - println!("Network pub key: {}", poly::public::(&polynomial)); - - // Read the genesis config let mut genesis_config = GenesisConfig::load(&args.genesis_in)?; - - // Update the identity with the hex of the polynomial - genesis_config.identity = hex(&polynomial.encode()); genesis_config.validators = validators; - // Write the shares we generated - for (i, _v) in genesis_config.validators.iter().enumerate() { - let node_dir = format!("{}/node{i}", args.out_dir); - fs::create_dir_all(&node_dir)?; - - let share_path = Path::new(&node_dir).join("share.pem"); - let share_hex = hex(&shares[i].encode()); - fs::write(&share_path, share_hex)?; - println!("Node {i}: wrote share to {share_path:?}"); - } - // Write the updated genesis config let updated_genesis = toml::to_string_pretty(&genesis_config)?; fs::write(format!("{}/genesis.toml", args.out_dir), updated_genesis)?; diff --git a/node/src/keys.rs b/node/src/keys.rs index 31787094..c8c3b4cc 100644 --- a/node/src/keys.rs +++ b/node/src/keys.rs @@ -1,15 +1,10 @@ -use anyhow::{Context as _, Result}; +use anyhow::Result; use clap::{Args, Subcommand}; -use commonware_codec::extensions::DecodeExt; use std::io::{self, Write}; use commonware_cryptography::bls12381::PrivateKey as BlsPrivateKey; use commonware_cryptography::{PrivateKeyExt as _, Signer}; -use commonware_utils::from_hex_formatted; -use summit_types::{PrivateKey, utils::get_expanded_path}; - -const NODE_KEY_FILENAME: &str = "node_key.pem"; -const CONSENSUS_KEY_FILENAME: &str = "consensus_key.pem"; +use summit_types::{KeyPaths, PrivateKey}; #[derive(Subcommand, PartialEq, Eq, Debug, Clone)] pub enum KeySubCmd { @@ -58,9 +53,12 @@ impl KeySubCmd { } fn generate_keys(&self, flags: &KeyFlags) { - let keystore_dir = get_expanded_path(&flags.key_store_path).expect("Invalid path"); - let node_key_path = keystore_dir.join(NODE_KEY_FILENAME); - let consensus_key_path = keystore_dir.join(CONSENSUS_KEY_FILENAME); + let key_paths = KeyPaths::new(flags.key_store_path.clone()); + let keystore_dir = key_paths.expanded().expect("Invalid --key-store-path"); + let node_key_path = key_paths.node_key_path().expect("Invalid node key path"); + let consensus_key_path = key_paths + .consensus_key_path() + .expect("Invalid consensus key path"); // Check if key files already exist let keys_exist = node_key_path.exists() || consensus_key_path.exists(); @@ -101,13 +99,13 @@ impl KeySubCmd { let node_private_key = PrivateKey::from_rng(&mut rand::thread_rng()); let node_pub_key = node_private_key.public_key(); let encoded_node_key = node_private_key.to_string(); - std::fs::write(&node_key_path, encoded_node_key).expect("Unable to write node key to disk"); + std::fs::write(node_key_path, encoded_node_key).expect("Unable to write node key to disk"); // Generate BLS consensus key let consensus_private_key = BlsPrivateKey::from_rng(&mut rand::thread_rng()); let consensus_pub_key = consensus_private_key.public_key(); let encoded_consensus_key = consensus_private_key.to_string(); - std::fs::write(&consensus_key_path, encoded_consensus_key) + std::fs::write(consensus_key_path, encoded_consensus_key) .expect("Unable to write consensus key to disk"); println!("Keys generated at {}:", keystore_dir.display()); @@ -116,43 +114,24 @@ impl KeySubCmd { } fn show_key(&self, flags: &KeyFlags) { - let keystore_dir = get_expanded_path(&flags.key_store_path).expect("Invalid path"); - let node_key_path = keystore_dir.join(NODE_KEY_FILENAME); - let consensus_key_path = keystore_dir.join(CONSENSUS_KEY_FILENAME); + let key_paths = KeyPaths::new(flags.key_store_path.clone()); - let node_pk = - read_ed_key_from_file(&node_key_path).expect("Unable to read node key from disk"); - let consensus_pk = read_bls_key_from_file(&consensus_key_path) + let node_pub_key = key_paths + .node_public_key() + .expect("Unable to read node key from disk"); + let consensus_pub_key = key_paths + .consensus_public_key() .expect("Unable to read consensus key from disk"); - println!("Node Public Key (ed25519): {}", node_pk.public_key()); - println!("Consensus Public Key (BLS): {}", consensus_pk.public_key()); - } -} - -pub fn read_bls_key_from_file(path: &std::path::Path) -> Result { - if let Err(e) = std::fs::read_to_string(path) { - println!("Failed to read BLS key: {}", e); + println!("Node Public Key (ed25519): {}", node_pub_key); + println!("Consensus Public Key (BLS): {}", consensus_pub_key); } - - let encoded_pk = std::fs::read_to_string(path)?; - let key = from_hex_formatted(&encoded_pk).context("Invalid BLS key format")?; - let pk = BlsPrivateKey::decode(&*key)?; - Ok(pk) -} - -pub fn read_ed_key_from_file(path: &std::path::Path) -> Result { - let encoded_pk = std::fs::read_to_string(path)?; - let key = from_hex_formatted(&encoded_pk).context("Invalid ed25519 key format")?; - let pk = PrivateKey::decode(&*key)?; - Ok(pk) } pub fn read_keys_from_keystore(keystore_path: &str) -> Result<(PrivateKey, BlsPrivateKey)> { - let keystore_dir = get_expanded_path(keystore_path)?; - println!("Keystore directory: {}", keystore_dir.display()); - let node_key = read_ed_key_from_file(&keystore_dir.join(NODE_KEY_FILENAME))?; - let consensus_key = read_bls_key_from_file(&keystore_dir.join(CONSENSUS_KEY_FILENAME))?; + let key_paths = KeyPaths::new(keystore_path.to_string()); + let node_key = key_paths.read_node_key_from_file()?; + let consensus_key = key_paths.read_bls_key_from_file()?; Ok((node_key, consensus_key)) } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index aa85758b..032ffef6 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -23,4 +23,5 @@ commonware-runtime = { workspace = true } ethereum_ssz.workspace = true dirs = "5.0.1" serde = { workspace = true } -tracing.workspace = true \ No newline at end of file +serde_json = { workspace = true } +tracing.workspace = true diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 5f7309b5..2550d3e5 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -11,14 +11,14 @@ use tokio::net::TcpListener; use tokio_util::sync::CancellationToken; pub struct RpcState { - key_path: String, + key_store_path: String, finalizer_mailbox: FinalizerMailbox, } impl RpcState { - pub fn new(key_path: String, finalizer_mailbox: FinalizerMailbox) -> Self { + pub fn new(key_store_path: String, finalizer_mailbox: FinalizerMailbox) -> Self { Self { - key_path, + key_store_path, finalizer_mailbox, } } @@ -26,11 +26,11 @@ impl RpcState { pub async fn start_rpc_server( finalizer_mailbox: FinalizerMailbox, - key_path: String, + key_store_path: String, port: u16, stop_signal: Signal, ) -> anyhow::Result<()> { - let state = RpcState::new(key_path, finalizer_mailbox); + let state = RpcState::new(key_store_path, finalizer_mailbox); let server = RpcRoutes::mount(state); @@ -63,20 +63,25 @@ impl PathSender { pub struct GenesisRpcState { genesis: PathSender, + key_store_path: String, } impl GenesisRpcState { - pub fn new(genesis: PathSender) -> Self { - Self { genesis } + pub fn new(genesis: PathSender, key_store_path: String) -> Self { + Self { + genesis, + key_store_path, + } } } pub async fn start_rpc_server_for_genesis( genesis: PathSender, + key_store_path: String, port: u16, cancel_token: CancellationToken, ) -> anyhow::Result<()> { - let state = GenesisRpcState::new(genesis); + let state = GenesisRpcState::new(genesis, key_store_path); let server = RpcRoutes::mount_for_genesis(state); diff --git a/rpc/src/routes.rs b/rpc/src/routes.rs index 4b628632..6fafe5ef 100644 --- a/rpc/src/routes.rs +++ b/rpc/src/routes.rs @@ -8,14 +8,20 @@ use axum::{ use commonware_codec::DecodeExt as _; use commonware_consensus::Block as ConsensusBlock; use commonware_consensus::simplex::signing_scheme::Scheme; -use commonware_cryptography::{Committable, Signer}; +use commonware_cryptography::Committable; use commonware_utils::{from_hex_formatted, hex}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use ssz::Encode; -use summit_types::{PrivateKey, PublicKey, utils::get_expanded_path}; +use summit_types::{KeyPaths, PublicKey, utils::get_expanded_path}; use crate::{GenesisRpcState, PathSender, RpcState}; +#[derive(Serialize)] +struct PublicKeysResponse { + node: String, + consensus: String, +} + #[derive(Deserialize)] struct ValidatorBalanceQuery { public_key: String, @@ -32,7 +38,7 @@ impl RpcRoutes { Router::new() .route("/health", get(Self::handle_health_check)) - .route("/get_public_key", get(Self::handle_get_pub_key::)) + .route("/get_public_keys", get(Self::handle_get_pub_keys::)) .route("/get_checkpoint", get(Self::handle_get_checkpoint::)) .route( "/get_latest_height", @@ -50,6 +56,8 @@ impl RpcRoutes { let state = Arc::new(state); Router::new() + .route("/health", get(Self::handle_health_check)) + .route("/get_public_keys", get(Self::handle_get_pub_keys_genesis)) .route("/send_genesis", post(Self::handle_send_genesis)) .with_state(state) } @@ -58,23 +66,30 @@ impl RpcRoutes { "Ok" } - async fn handle_get_pub_key( + async fn handle_get_pub_keys( State(state): State>>, ) -> Result { - let private_key = Self::read_ed_key_from_path(&state.key_path)?; + let key_paths = KeyPaths::new(state.key_store_path.clone()); + + let response = PublicKeysResponse { + node: key_paths.node_public_key()?, + consensus: key_paths.consensus_public_key()?, + }; - Ok(private_key.public_key().to_string()) + serde_json::to_string(&response).map_err(|e| format!("Failed to serialize response: {}", e)) } - fn read_ed_key_from_path(key_path: &str) -> Result { - let path = get_expanded_path(key_path).map_err(|_| "unable to get key_path")?; - let encoded_pk = - std::fs::read_to_string(path).map_err(|_| "Failed to read Private key file")?; + async fn handle_get_pub_keys_genesis( + State(state): State>, + ) -> Result { + let key_paths = KeyPaths::new(state.key_store_path.clone()); - let key = from_hex_formatted(&encoded_pk).ok_or("Invalid hex format for private key")?; - let pk = PrivateKey::decode(&*key).map_err(|_| "unable to decode private key")?; + let response = PublicKeysResponse { + node: key_paths.node_public_key()?, + consensus: key_paths.consensus_public_key()?, + }; - Ok(pk) + serde_json::to_string(&response).map_err(|e| format!("Failed to serialize response: {}", e)) } async fn handle_get_checkpoint( diff --git a/types/src/genesis.rs b/types/src/genesis.rs index 3b7f1145..252d1cc5 100644 --- a/types/src/genesis.rs +++ b/types/src/genesis.rs @@ -3,7 +3,7 @@ use alloy_primitives::Address; use anyhow::Context; use commonware_codec::DecodeExt; use commonware_cryptography::bls12381; -use commonware_utils::from_hex_formatted; +use commonware_utils::{from_hex, from_hex_formatted}; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; @@ -48,6 +48,17 @@ pub struct GenesisValidator { pub withdrawal_credentials: String, } +impl GenesisValidator { + fn ed25519_pubkey(key: &str) -> PublicKey { + let pubkey_bytes = from_hex(key).unwrap(); + PublicKey::decode(&pubkey_bytes[..]).unwrap() + } + + pub fn node_pubkey(&self) -> PublicKey { + GenesisValidator::ed25519_pubkey(&self.node_public_key) + } +} + #[derive(Debug, Clone)] pub struct Validator { pub node_public_key: PublicKey, diff --git a/types/src/key_paths.rs b/types/src/key_paths.rs new file mode 100644 index 00000000..65399ae7 --- /dev/null +++ b/types/src/key_paths.rs @@ -0,0 +1,86 @@ +use std::path::PathBuf; + +use crate::{PrivateKey, utils::get_expanded_path}; +use anyhow::{Context, Result}; +use commonware_codec::DecodeExt; +use commonware_cryptography::Signer; +use commonware_cryptography::bls12381::PrivateKey as BlsPrivateKey; +use commonware_utils::from_hex_formatted; + +/// Helper struct for managing key paths and loading keys from a key store directory. +/// +/// The key store directory should contain: +/// - `node_key.pem`: ED25519 private key for node identity (node key) +/// - `share.pem`: BLS12-381 DKG share (consensus key) +pub struct KeyPaths(String); + +impl KeyPaths { + /// Create a new KeyPaths instance from a key store path + pub fn new(key_store_path: String) -> Self { + Self(key_store_path) + } + + pub fn expanded(&self) -> anyhow::Result { + get_expanded_path(&self.0) + } + + /// Get the path to the node key file (ED25519) + pub fn node_key_path_str(&self) -> String { + format!("{}/node_key.pem", self.0) + } + + /// Get the path to the consensus key file (BLS share) + pub fn consensus_key_path_str(&self) -> String { + format!("{}/consensus_key.pem", self.0) + } + + pub fn node_key_path(&self) -> anyhow::Result { + get_expanded_path(&self.node_key_path_str()) + } + + pub fn consensus_key_path(&self) -> anyhow::Result { + get_expanded_path(&self.consensus_key_path_str()) + } + + /// Load the node private key (ED25519) from the key store + pub fn node_private_key(&self) -> Result { + self.read_node_key_from_file().map_err(|e| e.to_string()) + } + + /// Load the consensus private key (BLS) from the key store + pub fn consensus_private_key(&self) -> Result { + self.read_bls_key_from_file().map_err(|e| e.to_string()) + } + + /// Get the node public key (ED25519) as a hex string + pub fn node_public_key(&self) -> Result { + let private_key = self.node_private_key()?; + Ok(private_key.public_key().to_string()) + } + + /// Get the consensus public key (BLS) as a hex string + pub fn consensus_public_key(&self) -> Result { + let private_key = self.consensus_private_key()?; + Ok(private_key.public_key().to_string()) + } + + /// Read the node private key from file (using anyhow::Result for compatibility) + pub fn read_node_key_from_file(&self) -> Result { + let path = self.node_key_path()?; + let encoded_pk = std::fs::read_to_string(&path) + .context(format!("Failed to read node key from {:?}", path))?; + let key = from_hex_formatted(&encoded_pk).context("Invalid hex format for node key")?; + let pk = PrivateKey::decode(&*key).context("Unable to decode node private key")?; + Ok(pk) + } + + /// Read the BLS private key from file (using anyhow::Result for compatibility) + pub fn read_bls_key_from_file(&self) -> Result { + let path = self.consensus_key_path()?; + let encoded_pk = std::fs::read_to_string(&path) + .context(format!("Failed to read BLS key from {:?}", path))?; + let key = from_hex_formatted(&encoded_pk).context("Invalid hex format for BLS key")?; + let pk = BlsPrivateKey::decode(&*key).context("Unable to decode BLS private key")?; + Ok(pk) + } +} diff --git a/types/src/lib.rs b/types/src/lib.rs index b5d80c12..79644bea 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -7,6 +7,7 @@ pub mod engine_client; pub mod execution_request; pub mod genesis; pub mod header; +pub mod key_paths; pub mod keystore; pub mod network_oracle; #[cfg(feature = "e2e")] @@ -20,6 +21,7 @@ pub use block::*; pub use engine_client::*; pub use genesis::*; pub use header::*; +pub use key_paths::*; use withdrawal::PendingWithdrawal; use commonware_consensus::simplex::types::Activity as CActivity;