From 440cfd5b826981d8172ff364819d573c2105ecea Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 7 Oct 2024 14:56:33 +0100 Subject: [PATCH] feat(jstzd): show address --- Cargo.lock | 1 + crates/jstzd/Cargo.toml | 1 + crates/jstzd/src/task/octez_client.rs | 66 +++++++++++++++++++++++++ crates/jstzd/tests/octez_client_test.rs | 49 ++++++++++++++++++ 4 files changed, 117 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 18ac1f60..9ed12783 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2642,6 +2642,7 @@ dependencies = [ "bollard", "futures-util", "http 1.1.0", + "jstz_crypto", "octez", "rand 0.8.5", "reqwest", diff --git a/crates/jstzd/Cargo.toml b/crates/jstzd/Cargo.toml index 153f7c84..e14ee313 100644 --- a/crates/jstzd/Cargo.toml +++ b/crates/jstzd/Cargo.toml @@ -18,6 +18,7 @@ serde.workspace = true serde_json.workspace = true tempfile.workspace = true tokio.workspace = true +jstz_crypto = { path = "../jstz_crypto" } [dev-dependencies] rand.workspace = true diff --git a/crates/jstzd/src/task/octez_client.rs b/crates/jstzd/src/task/octez_client.rs index 0208952c..7737d157 100644 --- a/crates/jstzd/src/task/octez_client.rs +++ b/crates/jstzd/src/task/octez_client.rs @@ -1,6 +1,10 @@ use super::{directory::Directory, endpoint::Endpoint, octez_node::DEFAULT_RPC_ENDPOINT}; use anyhow::{anyhow, bail, Result}; use http::Uri; +use jstz_crypto::public_key::PublicKey; +use jstz_crypto::public_key_hash::PublicKeyHash; +use jstz_crypto::secret_key::SecretKey; +use jstz_crypto::{public_key, public_key_hash, secret_key}; use std::path::Path; use std::{ffi::OsStr, fmt, path::PathBuf, str::FromStr}; use tempfile::tempdir; @@ -97,6 +101,55 @@ impl fmt::Display for Signature { } } +#[derive(Debug)] +pub struct Address { + pub hash: public_key_hash::PublicKeyHash, + pub public_key: public_key::PublicKey, + pub secret_key: Option, +} + +impl Address { + const HASH: &'static str = "Hash"; + const PUBLIC_KEY: &'static str = "Public Key"; + const SECRET_KEY: &'static str = "Secret Key"; +} + +impl TryFrom for Address { + type Error = anyhow::Error; + fn try_from(stdout: StdOut) -> Result { + if !stdout.starts_with(Self::HASH) { + bail!("Invalid format for address:, {:?}", stdout); + } + let mut hash = None; + let mut public_key = None; + let mut secret_key = None; + for line in stdout.lines() { + if let Some((key, mut value)) = line.split_once(": ") { + match key { + Self::HASH => { + hash = Some(PublicKeyHash::from_base58(value)?); + } + Self::PUBLIC_KEY => { + public_key = Some(PublicKey::from_base58(value)?); + } + Self::SECRET_KEY => { + if value.starts_with("unencrypted") { + value = value.split(':').nth(1).unwrap(); + } + secret_key = Some(SecretKey::from_base58(value)?); + } + _ => bail!("Invalid key: {:?}", key), + } + } + } + Ok(Address { + hash: hash.ok_or(anyhow!("Missing hash"))?, + public_key: public_key.ok_or(anyhow!("Missing public key"))?, + secret_key, + }) + } +} + #[derive(Debug)] pub struct OctezClient { binary_path: PathBuf, @@ -178,6 +231,19 @@ impl OctezClient { Ok(()) } + pub async fn show_address( + &self, + alias: &str, + include_secret_key: bool, + ) -> Result
{ + let mut args = vec!["show", "address", alias]; + if include_secret_key { + args.push("--show-secret"); + } + let stdout = self.spawn_and_wait_command(args).await?; + Address::try_from(stdout) + } + pub async fn import_secret_key(&self, alias: &str, secret_key: &str) -> Result<()> { self.spawn_and_wait_command(["import", "secret", "key", alias, secret_key]) .await?; diff --git a/crates/jstzd/tests/octez_client_test.rs b/crates/jstzd/tests/octez_client_test.rs index 3ec17430..958062e3 100644 --- a/crates/jstzd/tests/octez_client_test.rs +++ b/crates/jstzd/tests/octez_client_test.rs @@ -61,6 +61,55 @@ async fn generates_keys() { assert_eq!(secret_keys["name"], alias); } +#[tokio::test] +async fn show_address() { + let temp_dir = TempDir::new().unwrap(); + let base_dir = temp_dir.path().to_path_buf(); + let octez_client = OctezClientBuilder::new() + .set_base_dir(base_dir.clone()) + .build() + .unwrap(); + let alias = "test_alias".to_string(); + let _ = octez_client.gen_keys(&alias, None).await; + let res = octez_client.show_address(&alias, false).await; + + assert!(res.is_ok_and(|addr| { + addr.hash.to_string().starts_with("tz1") + && addr.public_key.to_string().starts_with("edpk") + })); +} + +#[tokio::test] +async fn show_address_with_secret_key() { + let temp_dir = TempDir::new().unwrap(); + let base_dir = temp_dir.path().to_path_buf(); + let octez_client = OctezClientBuilder::new() + .set_base_dir(base_dir.clone()) + .build() + .unwrap(); + let alias = "test_alias".to_string(); + let _ = octez_client.gen_keys(&alias, None).await; + let res = octez_client.show_address(&alias, true).await; + println!("{:?}", res); + assert!(res.is_ok_and(|addr| addr + .secret_key + .is_some_and(|sk| sk.to_string().starts_with("edsk")))); +} + +#[tokio::test] +async fn show_address_fails_for_non_existing_alias() { + let temp_dir = TempDir::new().unwrap(); + let base_dir = temp_dir.path().to_path_buf(); + let octez_client = OctezClientBuilder::new() + .set_base_dir(base_dir.clone()) + .build() + .unwrap(); + let res = octez_client.show_address("test_alias", true).await; + assert!(res.is_err_and(|e| e + .to_string() + .contains("no public key hash alias named test_alias"))) +} + #[tokio::test] async fn generates_keys_with_custom_signature() { let temp_dir = TempDir::new().unwrap();