Skip to content

Commit

Permalink
feat(jstzd): show address
Browse files Browse the repository at this point in the history
  • Loading branch information
ryutamago committed Oct 7, 2024
1 parent f48a8d1 commit da7cf4d
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/jstzd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
105 changes: 105 additions & 0 deletions crates/jstzd/src/task/octez_client.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -97,6 +101,61 @@ 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<secret_key::SecretKey>,
}

impl Address {
const HASH: &'static str = "Hash";
const PUBLIC_KEY: &'static str = "Public Key";
const SECRET_KEY: &'static str = "Secret Key";
}

impl TryFrom<StdOut> for Address {
type Error = anyhow::Error;
// the output of `show address` command is expected to be in the following format:
/*
* Hash: tz1..
* Public Key: edpk..
* Secret Key: unencrypted:edsk..
*/
fn try_from(stdout: StdOut) -> Result<Self> {
if !stdout.starts_with(Self::HASH) {
bail!("Invalid format:, {:?}", 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,
Expand Down Expand Up @@ -178,6 +237,19 @@ impl OctezClient {
Ok(())
}

pub async fn show_address(
&self,
alias: &str,
include_secret_key: bool,
) -> Result<Address> {
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?;
Expand Down Expand Up @@ -316,4 +388,37 @@ mod test {
assert_eq!(actual_program, expected_program);
assert_eq!(actual_args, expected_args);
}

#[test]
fn address_try_from() {
let input_text = "Hash: tz1d5aeTJZ89RxAcuFduWRmyRUwYXfZSBVSB\nPublic Key: edpkutoN27QVVbshDg2iWTGAPDN3jywvAhzxuWm3D4Nqbn7aF8fhka";
let res = Address::try_from(input_text.to_string());
println!("{:?}", res);
assert!(res.is_ok_and(|addr| {
addr.hash.to_base58() == "tz1d5aeTJZ89RxAcuFduWRmyRUwYXfZSBVSB"
&& addr.public_key.to_base58()
== "edpkutoN27QVVbshDg2iWTGAPDN3jywvAhzxuWm3D4Nqbn7aF8fhka"
}));
}

#[test]
fn address_try_from_fails_on_invalid_input() {
let input_text = "Wrong format";
let res = Address::try_from(input_text.to_owned());
assert!(res.is_err_and(|e| e.to_string().contains("Invalid format")));
}

#[test]
fn address_try_from_fails_on_invalid_key() {
let input_text = "Hash: tz1d5aeTJZ89RxAcuFduWRmyRUwYXfZSBVSB\nPublicKey: edpkutoN27QVVbshDg2iWTGAPDN3jywvAhzxuWm3D4Nqbn7aF8fhka";
let res = Address::try_from(input_text.to_owned());
assert!(res.is_err_and(|e| e.to_string().contains("Invalid key")));
}

#[test]
fn address_try_from_fails_on_missing_public_key() {
let input_text = "Hash: tz1d5aeTJZ89RxAcuFduWRmyRUwYXfZSBVSB";
let res = Address::try_from(input_text.to_owned());
assert!(res.is_err_and(|e| e.to_string().contains("Missing public key")));
}
}
49 changes: 49 additions & 0 deletions crates/jstzd/tests/octez_client_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit da7cf4d

Please sign in to comment.