diff --git a/bolt-cli/Cargo.lock b/bolt-cli/Cargo.lock index 38db5ad09..c514aedc4 100644 --- a/bolt-cli/Cargo.lock +++ b/bolt-cli/Cargo.lock @@ -615,6 +615,8 @@ dependencies = [ "tokio", "tonic", "tonic-build", + "tracing", + "tracing-subscriber", ] [[package]] @@ -2039,6 +2041,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2172,6 +2184,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -3054,6 +3072,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3295,6 +3322,16 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "threadpool" version = "1.8.1" @@ -3549,6 +3586,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] diff --git a/bolt-cli/Cargo.toml b/bolt-cli/Cargo.toml index 6d6308229..edf24d768 100644 --- a/bolt-cli/Cargo.toml +++ b/bolt-cli/Cargo.toml @@ -31,6 +31,8 @@ dotenvy = "0.15.7" eyre = "0.6.12" thiserror = "1.0" hex = "0.4.3" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" [dev-dependencies] tempfile = "3.13.0" diff --git a/bolt-cli/src/delegation.rs b/bolt-cli/src/delegation.rs index 15871f636..46b3cdede 100644 --- a/bolt-cli/src/delegation.rs +++ b/bolt-cli/src/delegation.rs @@ -5,12 +5,14 @@ use ethereum_consensus::crypto::{ use eyre::Result; use lighthouse_eth2_keystore::Keystore; use serde::Serialize; +use tracing::debug; use crate::{ cli::{Action, Chain}, utils::{ + dirk::Dirk, keystore::{keystore_paths, KeystoreError, KeystoreSecret}, - signing::compute_commit_boost_signing_root, + signing::{compute_commit_boost_signing_root, compute_domain_from_mask}, }, }; @@ -99,6 +101,48 @@ pub fn generate_from_keystore( Ok(signed_messages) } +/// Generate signed delegations/revocations using a remote Dirk signer +pub async fn generate_from_dirk( + dirk: &mut Dirk, + delegatee_pubkey: BlsPublicKey, + account_paths: Vec, + chain: Chain, + action: Action, +) -> Result> { + let mut signed_messages = Vec::new(); + + // first read the accounts from the remote keystore + let accounts = dirk.list_accounts(account_paths).await?; + debug!("Found {} remote accounts", accounts.len()); + + // specify the signing domain (needs to be included in the signing request) + let domain = compute_domain_from_mask(chain.fork_version()); + + for account in accounts { + // for each available pubkey we control, sign a delegation message + let pubkey = BlsPublicKey::try_from(account.public_key.as_slice())?; + + match action { + Action::Delegate => { + let message = DelegationMessage::new(pubkey.clone(), delegatee_pubkey.clone()); + let signing_root = compute_commit_boost_signing_root(message.digest(), &chain)?; + let signature = dirk.request_signature(pubkey, signing_root, domain.into()).await?; + let signed = SignedDelegation { message, signature }; + signed_messages.push(SignedMessage::Delegation(signed)); + } + Action::Revoke => { + let message = RevocationMessage::new(pubkey.clone(), delegatee_pubkey.clone()); + let signing_root = compute_commit_boost_signing_root(message.digest(), &chain)?; + let signature = dirk.request_signature(pubkey, signing_root, domain.into()).await?; + let signed = SignedRevocation { message, signature }; + signed_messages.push(SignedMessage::Revocation(signed)); + } + } + } + + Ok(signed_messages) +} + /// Event types that can be emitted by the validator pubkey to /// signal some action on the Bolt protocol. #[derive(Debug, Clone, Copy)] diff --git a/bolt-cli/src/main.rs b/bolt-cli/src/main.rs index fd72bbab4..367eaf0a3 100644 --- a/bolt-cli/src/main.rs +++ b/bolt-cli/src/main.rs @@ -13,6 +13,7 @@ mod pubkeys; /// Utility functions and helpers for the CLI. mod utils; +use tracing::debug; use utils::{dirk::Dirk, keystore::KeystoreSecret, parse_bls_public_key, write_to_file}; /// Protocol Buffers definitions generated by `prost`. @@ -21,6 +22,8 @@ mod pb; #[tokio::main] async fn main() -> Result<()> { let _ = dotenvy::dotenv(); + let _ = tracing_subscriber::fmt::try_init(); + let cli = Opts::parse(); // Init the default rustls provider for Dirk @@ -55,9 +58,20 @@ async fn main() -> Result<()> { println!("Signed delegation messages generated and saved to {}", out); } KeySource::Dirk { opts } => { - let _dirk = Dirk::connect(opts.url, opts.tls_credentials).await?; + let mut dirk = Dirk::connect(opts.url, opts.tls_credentials).await?; + let delegatee_pubkey = parse_bls_public_key(&delegatee_pubkey)?; + let signed_messages = delegation::generate_from_dirk( + &mut dirk, + delegatee_pubkey, + opts.accounts, + chain, + action, + ) + .await?; + debug!("Signed {} messages with Dirk", signed_messages.len()); - todo!("generate delegations from dirk"); + write_to_file(&out, &signed_messages)?; + println!("Signed delegation messages generated and saved to {}", out); } }, @@ -76,7 +90,7 @@ async fn main() -> Result<()> { println!("Pubkeys generated and saved to {}", out); } KeySource::Dirk { opts } => { - let dirk = Dirk::connect(opts.url, opts.tls_credentials).await?; + let mut dirk = Dirk::connect(opts.url, opts.tls_credentials).await?; let accounts = dirk.list_accounts(opts.accounts).await?; let pubkeys = pubkeys::list_from_dirk_accounts(&accounts)?; diff --git a/bolt-cli/src/utils/dirk.rs b/bolt-cli/src/utils/dirk.rs index 52f38df9c..2d161edf1 100644 --- a/bolt-cli/src/utils/dirk.rs +++ b/bolt-cli/src/utils/dirk.rs @@ -17,7 +17,8 @@ use crate::{ /// Reference: https://github.com/attestantio/dirk #[derive(Clone)] pub struct Dirk { - conn: Channel, + lister: ListerClient, + signer: SignerClient, } impl Dirk { @@ -27,33 +28,33 @@ impl Dirk { let tls_config = compose_credentials(credentials)?; let conn = Channel::builder(addr).tls_config(tls_config)?.connect().await?; - Ok(Self { conn }) + let lister = ListerClient::new(conn.clone()); + let signer = SignerClient::new(conn.clone()); + + Ok(Self { lister, signer }) } /// List all accounts in the keystore. - pub async fn list_accounts(&self, paths: Vec) -> Result> { - let mut lister = ListerClient::new(self.conn.clone()); - let accs = lister.list_accounts(ListAccountsRequest { paths }).await?; + pub async fn list_accounts(&mut self, paths: Vec) -> Result> { + let accs = self.lister.list_accounts(ListAccountsRequest { paths }).await?; Ok(accs.into_inner().accounts) } /// Request a signature from the remote signer. pub async fn request_signature( - &self, + &mut self, pubkey: BlsPublicKey, hash: B256, domain: B256, ) -> Result { - let mut signer = SignerClient::new(self.conn.clone()); - let req = SignRequest { data: hash.to_vec(), domain: domain.to_vec(), id: Some(SignRequestId::PublicKey(pubkey.to_vec())), }; - let res = signer.sign(req).await?; + let res = self.signer.sign(req).await?; let sig = res.into_inner().signature; let sig = BlsSignature::try_from(sig.as_slice()).wrap_err("Failed to parse signature")?; @@ -83,33 +84,74 @@ fn compose_credentials(creds: TlsCredentials) -> Result { #[cfg(test)] mod tests { + use std::{process::Command, time::Duration}; + use super::*; - /// Test connecting to a DIRK server + /// Test connecting to a DIRK server and listing available accounts. /// - /// This test should be run manually against a running DIRK server. - /// Eventually this could become part of the entire test setup but for now it's ignored. + /// ```shell + /// cargo test --package bolt-cli --bin bolt-cli -- utils::dirk::tests::test_dirk_connection_e2e + /// --exact --show-output --ignored + /// ``` #[tokio::test] #[ignore] - async fn test_connect_to_dirk() -> eyre::Result<()> { + async fn test_dirk_connection_e2e() -> eyre::Result<()> { // Init the default rustls provider let _ = rustls::crypto::ring::default_provider().install_default(); - let url = "https://localhost:9091".to_string(); - let test_data_dir = env!("CARGO_MANIFEST_DIR").to_string() + "/test_data/dirk"; + // Init the DIRK config file + init_dirk_config(test_data_dir.clone())?; + + // Check if dirk is installed (in $PATH) + if Command::new("dirk") + .arg("--base-dir") + .arg(&test_data_dir) + .arg("--help") + .status() + .is_err() + { + eprintln!("DIRK is not installed in $PATH"); + return Ok(()); + } + + // Start the DIRK server in the background + let mut dirk_proc = Command::new("dirk").arg("--base-dir").arg(&test_data_dir).spawn()?; + + // Wait for some time for the server to start up + tokio::time::sleep(Duration::from_secs(3)).await; + + let url = "https://localhost:9091".to_string(); + let cred = TlsCredentials { client_cert_path: test_data_dir.clone() + "/client1.crt", client_key_path: test_data_dir.clone() + "/client1.key", ca_cert_path: Some(test_data_dir.clone() + "/security/ca.crt"), }; - let dirk = Dirk::connect(url, cred).await?; + let mut dirk = Dirk::connect(url, cred).await?; let accounts = dirk.list_accounts(vec!["wallet1".to_string()]).await?; println!("Dirk Accounts: {:?}", accounts); + // make sure to stop the dirk server + dirk_proc.kill()?; + + Ok(()) + } + + fn init_dirk_config(test_data_dir: String) -> eyre::Result<()> { + // read the template json file from test_data + let template_path = test_data_dir.clone() + "/dirk.template.json"; + let template = fs::read_to_string(template_path).wrap_err("Failed to read template")?; + + // change the occurrence of $PWD to the current working directory in the template + let new_file = test_data_dir.clone() + "/dirk.json"; + let new_content = template.replace("$PWD", &test_data_dir); + fs::write(new_file, new_content).wrap_err("Failed to write dirk config file")?; + Ok(()) } } diff --git a/bolt-cli/test_data/dirk/.gitignore b/bolt-cli/test_data/dirk/.gitignore new file mode 100644 index 000000000..98368746f --- /dev/null +++ b/bolt-cli/test_data/dirk/.gitignore @@ -0,0 +1 @@ +dirk.json \ No newline at end of file diff --git a/bolt-cli/test_data/dirk/dirk.template.json b/bolt-cli/test_data/dirk/dirk.template.json new file mode 100644 index 000000000..54c954320 --- /dev/null +++ b/bolt-cli/test_data/dirk/dirk.template.json @@ -0,0 +1,23 @@ +{ + "server": { + "id": 212483780, + "name": "localhost", + "listen-address": "localhost:9091" + }, + "certificates": { + "ca-cert": "file://$PWD/security/ca.crt", + "server-cert": "file://$PWD/security/localhost.crt", + "server-key": "file://$PWD/security/localhost.key" + }, + "peers": { + "212483780": "localhost:9091" + }, + "permissions": { + "client1": { + "wallet1": "All" + }, + "localhost": { + "wallet1": "All" + } + } +} diff --git a/bolt-cli/test_data/dirk/storage/000000.vlog b/bolt-cli/test_data/dirk/storage/000000.vlog new file mode 100644 index 000000000..ff7a6f8f9 Binary files /dev/null and b/bolt-cli/test_data/dirk/storage/000000.vlog differ diff --git a/bolt-cli/test_data/dirk/storage/KEYREGISTRY b/bolt-cli/test_data/dirk/storage/KEYREGISTRY new file mode 100644 index 000000000..cd3e15ea5 --- /dev/null +++ b/bolt-cli/test_data/dirk/storage/KEYREGISTRY @@ -0,0 +1 @@ +àÝõöƒA÷¸c¾ÈÁW•Hello Badger \ No newline at end of file diff --git a/bolt-cli/test_data/dirk/storage/LOCK b/bolt-cli/test_data/dirk/storage/LOCK new file mode 100644 index 000000000..31ab16834 --- /dev/null +++ b/bolt-cli/test_data/dirk/storage/LOCK @@ -0,0 +1 @@ +33375 diff --git a/bolt-cli/test_data/dirk/storage/MANIFEST b/bolt-cli/test_data/dirk/storage/MANIFEST new file mode 100644 index 000000000..0b5596943 Binary files /dev/null and b/bolt-cli/test_data/dirk/storage/MANIFEST differ