From 2b4434584fe5836ee5bb6a484c2f2ee017ca45e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Fri, 2 Feb 2024 10:04:16 +0100 Subject: [PATCH 01/29] Install `clap` for parsing CLI args. --- Cargo.lock | 96 +++++++++++++++++++++++++++++++++++++++++++ kairos-cli/Cargo.toml | 1 + 2 files changed, 97 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 5f24967b..fe2eb6a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,54 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.79" @@ -257,6 +305,39 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "cookie" version = "0.18.0" @@ -616,6 +697,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "kairos-cli" version = "0.1.0" +dependencies = [ + "clap", +] [[package]] name = "kairos-server" @@ -1197,6 +1281,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.48" @@ -1495,6 +1585,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "valuable" version = "0.1.0" diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 4a864138..bbe8a13e 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -13,3 +13,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = "4.4.18" From 22add8878afeb400b56ebb58616f7d71b68206d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Fri, 2 Feb 2024 10:25:22 +0100 Subject: [PATCH 02/29] Define deposit/transfer/withdraw subcommands. NOTE: There are common arguments `amount` and `private_key`. --- kairos-cli/bin/main.rs | 45 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index e7a11a96..eadeee69 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -1,3 +1,46 @@ +use clap::{Arg, Command}; + +fn common_amount_arg() -> Arg { + Arg::new("amount") + .long("amount") + .short('a') + .required(true) + .value_name("NUM_MOTES") +} + +fn common_private_key_arg() -> Arg { + Arg::new("private-key") + .long("private-key") + .short('k') + .required(true) + .value_name("FILE_PATH") +} + +fn deposit_command() -> Command { + Command::new("deposit") + .arg(common_amount_arg()) + .arg(common_private_key_arg()) +} + + +fn transfer_command() -> Command { + Command::new("transfer") + .arg(Arg::new("recipient").long("recipient").short('r').required(true).value_name("PUBLIC_KEY")) + .arg(common_amount_arg()) + .arg(common_private_key_arg()) +} + +fn withdraw_command() -> Command { + Command::new("withdraw") + .arg(common_amount_arg()) + .arg(common_private_key_arg()) +} + fn main() { - println!("Hello, world!"); + let cli = Command::new("Kairos Client") + .subcommand(deposit_command()) + .subcommand(transfer_command()) + .subcommand(withdraw_command()); + cli.get_matches(); } + From 9bc033ce36ac8a44ef32157082a59246281f4466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Fri, 2 Feb 2024 12:05:32 +0100 Subject: [PATCH 03/29] Refactor subcommands and common arguments. Organized code with directories, and introduced `ClientCommand` trait. --- kairos-cli/bin/commands/deposit.rs | 21 +++++++++++++ kairos-cli/bin/commands/mod.rs | 21 +++++++++++++ kairos-cli/bin/commands/transfer.rs | 30 ++++++++++++++++++ kairos-cli/bin/commands/withdraw.rs | 21 +++++++++++++ kairos-cli/bin/common/args.rs | 25 +++++++++++++++ kairos-cli/bin/common/mod.rs | 4 +++ kairos-cli/bin/main.rs | 48 +++++------------------------ 7 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 kairos-cli/bin/commands/deposit.rs create mode 100644 kairos-cli/bin/commands/mod.rs create mode 100644 kairos-cli/bin/commands/transfer.rs create mode 100644 kairos-cli/bin/commands/withdraw.rs create mode 100644 kairos-cli/bin/common/args.rs create mode 100644 kairos-cli/bin/common/mod.rs diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/bin/commands/deposit.rs new file mode 100644 index 00000000..0b6bf920 --- /dev/null +++ b/kairos-cli/bin/commands/deposit.rs @@ -0,0 +1,21 @@ +use crate::commands::ClientCommand; +use crate::common::{amount, private_key}; +use clap::{ArgMatches, Command}; + +pub struct Deposit; + +impl ClientCommand for Deposit { + const NAME: &'static str = "deposit"; + const ABOUT: &'static str = "Deposits funds into your account"; + + fn new() -> Command { + Command::new(Self::NAME) + .about(Self::ABOUT) + .arg(amount::arg()) + .arg(private_key::arg()) + } + + fn run(matches: &ArgMatches) { + todo!(); + } +} diff --git a/kairos-cli/bin/commands/mod.rs b/kairos-cli/bin/commands/mod.rs new file mode 100644 index 00000000..542c1e2c --- /dev/null +++ b/kairos-cli/bin/commands/mod.rs @@ -0,0 +1,21 @@ +mod deposit; +mod transfer; +mod withdraw; + +pub use deposit::Deposit; +pub use transfer::Transfer; +pub use withdraw::Withdraw; + +use clap::{ArgMatches, Command}; + +pub trait ClientCommand { + const NAME: &'static str; + const ABOUT: &'static str; + + /// Constructs the clap subcommand. + fn new() -> Command; + + /// Parses the arg matches and runs the subcommand. + /// TODO: Return execution result. + fn run(matches: &ArgMatches); +} diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs new file mode 100644 index 00000000..1d967713 --- /dev/null +++ b/kairos-cli/bin/commands/transfer.rs @@ -0,0 +1,30 @@ +use crate::commands::ClientCommand; +use crate::common::{amount, private_key}; +use clap::{Arg, ArgMatches, Command}; + +pub struct Transfer; + +fn recipient_arg() -> Arg { + Arg::new("recipient") + .long("recipient") + .short('r') + .required(true) + .value_name("PUBLIC_KEY") +} + +impl ClientCommand for Transfer { + const NAME: &'static str = "transfer"; + const ABOUT: &'static str = "Transfers funds to another account"; + + fn new() -> Command { + Command::new(Self::NAME) + .about(Self::ABOUT) + .arg(recipient_arg()) + .arg(amount::arg()) + .arg(private_key::arg()) + } + + fn run(matches: &ArgMatches) { + todo!(); + } +} diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/bin/commands/withdraw.rs new file mode 100644 index 00000000..aacdb91f --- /dev/null +++ b/kairos-cli/bin/commands/withdraw.rs @@ -0,0 +1,21 @@ +use crate::commands::ClientCommand; +use crate::common::{amount, private_key}; +use clap::{ArgMatches, Command}; + +pub struct Withdraw; + +impl ClientCommand for Withdraw { + const NAME: &'static str = "withdraw"; + const ABOUT: &'static str = "Withdraws funds from your account"; + + fn new() -> Command { + Command::new(Self::NAME) + .about(Self::ABOUT) + .arg(amount::arg()) + .arg(private_key::arg()) + } + + fn run(matches: &ArgMatches) { + todo!(); + } +} diff --git a/kairos-cli/bin/common/args.rs b/kairos-cli/bin/common/args.rs new file mode 100644 index 00000000..acf53842 --- /dev/null +++ b/kairos-cli/bin/common/args.rs @@ -0,0 +1,25 @@ +use clap::Arg; + +pub mod amount { + use super::*; + + pub fn arg() -> Arg { + Arg::new("amount") + .long("amount") + .short('a') + .required(true) + .value_name("NUM_MOTES") + } +} + +pub mod private_key { + use super::*; + + pub fn arg() -> Arg { + Arg::new("private-key") + .long("private-key") + .short('k') + .required(true) + .value_name("FILE_PATH") + } +} diff --git a/kairos-cli/bin/common/mod.rs b/kairos-cli/bin/common/mod.rs new file mode 100644 index 00000000..6a85263a --- /dev/null +++ b/kairos-cli/bin/common/mod.rs @@ -0,0 +1,4 @@ +mod args; + +pub use args::amount; +pub use args::private_key; diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index eadeee69..b3e6f30a 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -1,46 +1,14 @@ -use clap::{Arg, Command}; - -fn common_amount_arg() -> Arg { - Arg::new("amount") - .long("amount") - .short('a') - .required(true) - .value_name("NUM_MOTES") -} - -fn common_private_key_arg() -> Arg { - Arg::new("private-key") - .long("private-key") - .short('k') - .required(true) - .value_name("FILE_PATH") -} - -fn deposit_command() -> Command { - Command::new("deposit") - .arg(common_amount_arg()) - .arg(common_private_key_arg()) -} +mod commands; +mod common; - -fn transfer_command() -> Command { - Command::new("transfer") - .arg(Arg::new("recipient").long("recipient").short('r').required(true).value_name("PUBLIC_KEY")) - .arg(common_amount_arg()) - .arg(common_private_key_arg()) -} - -fn withdraw_command() -> Command { - Command::new("withdraw") - .arg(common_amount_arg()) - .arg(common_private_key_arg()) -} +use clap::Command; +use commands::{ClientCommand, Deposit, Transfer, Withdraw}; fn main() { let cli = Command::new("Kairos Client") - .subcommand(deposit_command()) - .subcommand(transfer_command()) - .subcommand(withdraw_command()); + .about("CLI for interacting with Kairos") + .subcommand(Deposit::new()) + .subcommand(Transfer::new()) + .subcommand(Withdraw::new()); cli.get_matches(); } - From 41ebd06cf51db4ef10eb121cd4c1ac9e743911b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Fri, 2 Feb 2024 12:35:05 +0100 Subject: [PATCH 04/29] Run subcommand when matched by name. NOTE: If no subcommand was provided, we display help. --- kairos-cli/bin/main.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index b3e6f30a..f2263856 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -3,12 +3,32 @@ mod common; use clap::Command; use commands::{ClientCommand, Deposit, Transfer, Withdraw}; +use std::process; -fn main() { - let cli = Command::new("Kairos Client") +fn cli() -> Command { + Command::new("Kairos Client") .about("CLI for interacting with Kairos") .subcommand(Deposit::new()) .subcommand(Transfer::new()) - .subcommand(Withdraw::new()); - cli.get_matches(); + .subcommand(Withdraw::new()) +} + +fn main() { + let arg_matches = cli().get_matches(); + let (subcommand_name, matches) = arg_matches.subcommand().unwrap_or_else(|| { + // No subcommand provided by user. + let _ = cli().print_long_help(); + process::exit(1); + }); + + match subcommand_name { + Deposit::NAME => Deposit::run(matches), + Transfer::NAME => Transfer::run(matches), + Withdraw::NAME => Withdraw::run(matches), + _ => { + // This should not happen, unless we missed some subcommand. + let _ = cli().print_long_help(); + process::exit(1); + } + }; } From eb04979a9b0a729dcc4358f01d569fe92c6b167b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Fri, 2 Feb 2024 14:44:25 +0100 Subject: [PATCH 05/29] Handle subcommand output/error. Defined `CliError`, using `thiserror` library. Result is printed to the console. --- Cargo.lock | 1 + kairos-cli/Cargo.toml | 1 + kairos-cli/bin/commands/deposit.rs | 5 +++-- kairos-cli/bin/commands/mod.rs | 7 +++++-- kairos-cli/bin/commands/transfer.rs | 5 +++-- kairos-cli/bin/commands/withdraw.rs | 5 +++-- kairos-cli/bin/error.rs | 5 +++++ kairos-cli/bin/main.rs | 13 ++++++++++++- 8 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 kairos-cli/bin/error.rs diff --git a/Cargo.lock b/Cargo.lock index fe2eb6a1..0b9aae31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -699,6 +699,7 @@ name = "kairos-cli" version = "0.1.0" dependencies = [ "clap", + "thiserror", ] [[package]] diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index bbe8a13e..510c6047 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -14,3 +14,4 @@ edition = "2021" [dependencies] clap = "4.4.18" +thiserror = "1.0.56" diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/bin/commands/deposit.rs index 0b6bf920..6f92ec6a 100644 --- a/kairos-cli/bin/commands/deposit.rs +++ b/kairos-cli/bin/commands/deposit.rs @@ -1,5 +1,6 @@ -use crate::commands::ClientCommand; +use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; +use crate::error::CliError; use clap::{ArgMatches, Command}; pub struct Deposit; @@ -15,7 +16,7 @@ impl ClientCommand for Deposit { .arg(private_key::arg()) } - fn run(matches: &ArgMatches) { + fn run(matches: &ArgMatches) -> Result { todo!(); } } diff --git a/kairos-cli/bin/commands/mod.rs b/kairos-cli/bin/commands/mod.rs index 542c1e2c..71c1cfff 100644 --- a/kairos-cli/bin/commands/mod.rs +++ b/kairos-cli/bin/commands/mod.rs @@ -6,8 +6,12 @@ pub use deposit::Deposit; pub use transfer::Transfer; pub use withdraw::Withdraw; +use crate::error::CliError; use clap::{ArgMatches, Command}; +// NOTE: Temporarily we use plain output. +pub type Output = String; + pub trait ClientCommand { const NAME: &'static str; const ABOUT: &'static str; @@ -16,6 +20,5 @@ pub trait ClientCommand { fn new() -> Command; /// Parses the arg matches and runs the subcommand. - /// TODO: Return execution result. - fn run(matches: &ArgMatches); + fn run(matches: &ArgMatches) -> Result; } diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs index 1d967713..475f49c1 100644 --- a/kairos-cli/bin/commands/transfer.rs +++ b/kairos-cli/bin/commands/transfer.rs @@ -1,5 +1,6 @@ -use crate::commands::ClientCommand; +use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; +use crate::error::CliError; use clap::{Arg, ArgMatches, Command}; pub struct Transfer; @@ -24,7 +25,7 @@ impl ClientCommand for Transfer { .arg(private_key::arg()) } - fn run(matches: &ArgMatches) { + fn run(matches: &ArgMatches) -> Result { todo!(); } } diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/bin/commands/withdraw.rs index aacdb91f..d3147ecb 100644 --- a/kairos-cli/bin/commands/withdraw.rs +++ b/kairos-cli/bin/commands/withdraw.rs @@ -1,5 +1,6 @@ -use crate::commands::ClientCommand; +use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; +use crate::error::CliError; use clap::{ArgMatches, Command}; pub struct Withdraw; @@ -15,7 +16,7 @@ impl ClientCommand for Withdraw { .arg(private_key::arg()) } - fn run(matches: &ArgMatches) { + fn run(matches: &ArgMatches) -> Result { todo!(); } } diff --git a/kairos-cli/bin/error.rs b/kairos-cli/bin/error.rs new file mode 100644 index 00000000..9a2893b5 --- /dev/null +++ b/kairos-cli/bin/error.rs @@ -0,0 +1,5 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum CliError { +} diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index f2263856..542493f7 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -1,5 +1,6 @@ mod commands; mod common; +mod error; use clap::Command; use commands::{ClientCommand, Deposit, Transfer, Withdraw}; @@ -21,7 +22,7 @@ fn main() { process::exit(1); }); - match subcommand_name { + let result = match subcommand_name { Deposit::NAME => Deposit::run(matches), Transfer::NAME => Transfer::run(matches), Withdraw::NAME => Withdraw::run(matches), @@ -31,4 +32,14 @@ fn main() { process::exit(1); } }; + + match result { + Ok(output) => { + println!("{}", output) + } + Err(error) => { + println!("{}", error); + process::exit(1); + } + } } From a39b16ff37dfd67b4ad3362e7d12dd98c7c63f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Fri, 2 Feb 2024 14:45:19 +0100 Subject: [PATCH 06/29] Parse `amount` as u64. --- kairos-cli/bin/commands/deposit.rs | 2 ++ kairos-cli/bin/commands/transfer.rs | 2 ++ kairos-cli/bin/commands/withdraw.rs | 2 ++ kairos-cli/bin/common/args.rs | 28 +++++++++++++++++++++++----- kairos-cli/bin/error.rs | 6 ++++++ 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/bin/commands/deposit.rs index 6f92ec6a..8d0f709e 100644 --- a/kairos-cli/bin/commands/deposit.rs +++ b/kairos-cli/bin/commands/deposit.rs @@ -17,6 +17,8 @@ impl ClientCommand for Deposit { } fn run(matches: &ArgMatches) -> Result { + let amount = amount::get(matches)?; + todo!(); } } diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs index 475f49c1..225d702c 100644 --- a/kairos-cli/bin/commands/transfer.rs +++ b/kairos-cli/bin/commands/transfer.rs @@ -26,6 +26,8 @@ impl ClientCommand for Transfer { } fn run(matches: &ArgMatches) -> Result { + let amount = amount::get(matches)?; + todo!(); } } diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/bin/commands/withdraw.rs index d3147ecb..7b54a230 100644 --- a/kairos-cli/bin/commands/withdraw.rs +++ b/kairos-cli/bin/commands/withdraw.rs @@ -17,6 +17,8 @@ impl ClientCommand for Withdraw { } fn run(matches: &ArgMatches) -> Result { + let amount = amount::get(matches)?; + todo!(); } } diff --git a/kairos-cli/bin/common/args.rs b/kairos-cli/bin/common/args.rs index acf53842..a7f0d229 100644 --- a/kairos-cli/bin/common/args.rs +++ b/kairos-cli/bin/common/args.rs @@ -1,14 +1,32 @@ -use clap::Arg; +use crate::error::CliError; +use clap::{Arg, ArgMatches}; pub mod amount { use super::*; + const ARG_NAME: &str = "amount"; + const ARG_SHORT: char = 'a'; + const ARG_VALUE_NAME: &str = "NUM_MOTES"; + pub fn arg() -> Arg { - Arg::new("amount") - .long("amount") - .short('a') + Arg::new(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) .required(true) - .value_name("NUM_MOTES") + .value_name(ARG_VALUE_NAME) + } + + pub fn get(matches: &ArgMatches) -> Result { + let value = matches + .get_one::(ARG_NAME) + .map(String::as_str) + .ok_or_else(|| CliError::MissingArgument { context: ARG_NAME })?; + + let amount = value + .parse::() + .map_err(|_| CliError::FailedToParseU64 { context: "amount" })?; + + Ok(amount) } } diff --git a/kairos-cli/bin/error.rs b/kairos-cli/bin/error.rs index 9a2893b5..d9515766 100644 --- a/kairos-cli/bin/error.rs +++ b/kairos-cli/bin/error.rs @@ -2,4 +2,10 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum CliError { + /// Unable to find argument by name. + #[error("missing argument '{context}'")] + MissingArgument { context: &'static str }, + /// Failed to parse amount from string. + #[error("failed to parse '{context}' as u64")] + FailedToParseU64 { context: &'static str }, } From 37109b06eb7eee405a925287eaca4f560c420e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 6 Feb 2024 00:14:00 +0100 Subject: [PATCH 07/29] Parse `recipient` and `private_key` as Casper keys. NOTE: We construct CasperSigner from private_key to be sign transactions in the future. --- Cargo.lock | 545 ++++++++++++++++++++++++++- kairos-cli/Cargo.toml | 2 + kairos-cli/bin/commands/deposit.rs | 10 +- kairos-cli/bin/commands/transfer.rs | 43 ++- kairos-cli/bin/commands/withdraw.rs | 10 +- kairos-cli/bin/common/args.rs | 26 +- kairos-cli/bin/crypto/error.rs | 10 + kairos-cli/bin/crypto/mod.rs | 4 + kairos-cli/bin/crypto/private_key.rs | 16 + kairos-cli/bin/crypto/public_key.rs | 27 ++ kairos-cli/bin/crypto/signer.rs | 42 +++ kairos-cli/bin/error.rs | 4 + kairos-cli/bin/main.rs | 1 + 13 files changed, 717 insertions(+), 23 deletions(-) create mode 100644 kairos-cli/bin/crypto/error.rs create mode 100644 kairos-cli/bin/crypto/mod.rs create mode 100644 kairos-cli/bin/crypto/private_key.rs create mode 100644 kairos-cli/bin/crypto/public_key.rs create mode 100644 kairos-cli/bin/crypto/signer.rs diff --git a/Cargo.lock b/Cargo.lock index 0b9aae31..e922bc0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -195,7 +195,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -242,12 +242,36 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bit-set" version = "0.5.3" @@ -275,6 +299,17 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -284,12 +319,51 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "casper-types" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e01525b7bbae90fe9de3f1def6ffe05052a94ed7d14b1c2b38baec81eeec31b" +dependencies = [ + "base16", + "base64 0.13.1", + "bitflags 1.3.2", + "blake2", + "derp", + "ed25519-dalek", + "getrandom", + "hex", + "hex_fmt", + "humantime", + "k256", + "num", + "num-derive", + "num-integer", + "num-rational", + "num-traits", + "once_cell", + "pem", + "rand", + "serde", + "serde_bytes", + "serde_json", + "thiserror", + "uint", + "untrusted", +] + [[package]] name = "cc" version = "1.0.83" @@ -338,6 +412,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "cookie" version = "0.18.0" @@ -357,6 +437,24 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -367,6 +465,54 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -376,12 +522,30 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derp" +version = "0.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b84cfd9b6fa437e498215e5625e9e3ae3bf9bb54d623028a181c40820db169" +dependencies = [ + "untrusted", +] + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -389,7 +553,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -398,6 +564,61 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -420,6 +641,22 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + [[package]] name = "fnv" version = "1.0.7" @@ -491,6 +728,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -510,6 +748,17 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.4.1" @@ -541,7 +790,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "headers-core", "http 1.0.0", @@ -571,6 +820,27 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "http" version = "0.2.11" @@ -628,6 +898,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "1.1.0" @@ -694,11 +970,25 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + [[package]] name = "kairos-cli" version = "0.1.0" dependencies = [ + "casper-types", "clap", + "hex", "thiserror", ] @@ -827,6 +1117,84 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -862,6 +1230,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "overload" version = "0.1.1" @@ -891,6 +1265,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -914,7 +1299,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -929,6 +1314,22 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1097,6 +1498,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rust-multipart-rfc7578_2" version = "0.6.1" @@ -1119,6 +1530,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.30" @@ -1162,6 +1582,25 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + [[package]] name = "serde" version = "1.0.195" @@ -1171,6 +1610,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.195" @@ -1179,7 +1627,7 @@ checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -1201,6 +1649,7 @@ version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ + "indexmap", "itoa", "ryu", "serde", @@ -1236,7 +1685,18 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", ] [[package]] @@ -1257,6 +1717,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -1282,12 +1752,45 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.48" @@ -1335,7 +1838,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -1420,7 +1923,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -1485,7 +1988,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] @@ -1539,6 +2042,18 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -1575,6 +2090,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.5.0" @@ -1787,3 +2308,9 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 510c6047..ab4f3a6b 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -13,5 +13,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +casper-types = { version = "4.0.1", features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. clap = "4.4.18" +hex = "0.4.3" thiserror = "1.0.56" diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/bin/commands/deposit.rs index 8d0f709e..49137647 100644 --- a/kairos-cli/bin/commands/deposit.rs +++ b/kairos-cli/bin/commands/deposit.rs @@ -1,5 +1,6 @@ use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; +use crate::crypto::signer::CasperSigner; use crate::error::CliError; use clap::{ArgMatches, Command}; @@ -18,7 +19,14 @@ impl ClientCommand for Deposit { fn run(matches: &ArgMatches) -> Result { let amount = amount::get(matches)?; + let private_key = private_key::get(matches)?; - todo!(); + let signer = CasperSigner::from_key(private_key); + + // TODO: Create transaction and sign it with `signer`. + + // TODO: Send transaction to the network, using Rust SDK. + + Ok("ok".to_string()) } } diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs index 225d702c..6967fc41 100644 --- a/kairos-cli/bin/commands/transfer.rs +++ b/kairos-cli/bin/commands/transfer.rs @@ -1,16 +1,35 @@ use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; +use crate::crypto::public_key::CasperPublicKey; +use crate::crypto::signer::CasperSigner; use crate::error::CliError; use clap::{Arg, ArgMatches, Command}; pub struct Transfer; -fn recipient_arg() -> Arg { - Arg::new("recipient") - .long("recipient") - .short('r') - .required(true) - .value_name("PUBLIC_KEY") +const ARG_NAME: &str = "recipient"; +const ARG_SHORT: char = 'r'; +const ARG_VALUE_NAME: &str = "PUBLIC_KEY"; + +pub mod recipient { + use super::*; + + pub fn arg() -> Arg { + Arg::new(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) + .required(true) + .value_name(ARG_VALUE_NAME) + } + + pub fn get(matches: &ArgMatches) -> Result { + let value = matches + .get_one::("recipient") + .map(String::as_str) + .unwrap(); + + CasperPublicKey::from_hex(value).map_err(|error| CliError::CryptoError { error }) + } } impl ClientCommand for Transfer { @@ -20,14 +39,22 @@ impl ClientCommand for Transfer { fn new() -> Command { Command::new(Self::NAME) .about(Self::ABOUT) - .arg(recipient_arg()) + .arg(recipient::arg()) .arg(amount::arg()) .arg(private_key::arg()) } fn run(matches: &ArgMatches) -> Result { + let recipient = recipient::get(matches)?; let amount = amount::get(matches)?; + let private_key = private_key::get(matches)?; + + let signer = CasperSigner::from_key(private_key); + + // TODO: Create transaction and sign it with `signer`. + + // TODO: Send transaction to the network, using Rust SDK. - todo!(); + Ok("ok".to_string()) } } diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/bin/commands/withdraw.rs index 7b54a230..15deb43a 100644 --- a/kairos-cli/bin/commands/withdraw.rs +++ b/kairos-cli/bin/commands/withdraw.rs @@ -1,5 +1,6 @@ use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; +use crate::crypto::signer::CasperSigner; use crate::error::CliError; use clap::{ArgMatches, Command}; @@ -18,7 +19,14 @@ impl ClientCommand for Withdraw { fn run(matches: &ArgMatches) -> Result { let amount = amount::get(matches)?; + let private_key = private_key::get(matches)?; - todo!(); + let signer = CasperSigner::from_key(private_key); + + // TODO: Create transaction and sign it with `signer`. + + // TODO: Send transaction to the network, using Rust SDK. + + Ok("ok".to_string()) } } diff --git a/kairos-cli/bin/common/args.rs b/kairos-cli/bin/common/args.rs index a7f0d229..fcd0926f 100644 --- a/kairos-cli/bin/common/args.rs +++ b/kairos-cli/bin/common/args.rs @@ -31,13 +31,31 @@ pub mod amount { } pub mod private_key { + use crate::crypto::private_key::CasperPrivateKey; + use super::*; + const ARG_NAME: &str = "private-key"; + const ARG_SHORT: char = 'k'; + const ARG_VALUE_NAME: &str = "FILE_PATH"; + pub fn arg() -> Arg { - Arg::new("private-key") - .long("private-key") - .short('k') + Arg::new(ARG_NAME) + .long(ARG_NAME) + .short(ARG_SHORT) .required(true) - .value_name("FILE_PATH") + .value_name(ARG_VALUE_NAME) + } + + pub fn get(matches: &ArgMatches) -> Result { + let value = matches + .get_one::(ARG_NAME) + .map(String::as_str) + .ok_or_else(|| CliError::MissingArgument { context: ARG_NAME })?; + + let private_key = + CasperPrivateKey::from_file(value).map_err(|error| CliError::CryptoError { error })?; + + Ok(private_key) } } diff --git a/kairos-cli/bin/crypto/error.rs b/kairos-cli/bin/crypto/error.rs new file mode 100644 index 00000000..62ce5f66 --- /dev/null +++ b/kairos-cli/bin/crypto/error.rs @@ -0,0 +1,10 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum CryptoError { + /// Failed to parse a public key from a formatted string. + #[error("failed to parse private key")] + FailedToParseKey {}, + #[error("failed to serialize signature/key")] + Serialization {}, +} diff --git a/kairos-cli/bin/crypto/mod.rs b/kairos-cli/bin/crypto/mod.rs new file mode 100644 index 00000000..b68afd01 --- /dev/null +++ b/kairos-cli/bin/crypto/mod.rs @@ -0,0 +1,4 @@ +pub mod error; +pub mod private_key; +pub mod public_key; +pub mod signer; diff --git a/kairos-cli/bin/crypto/private_key.rs b/kairos-cli/bin/crypto/private_key.rs new file mode 100644 index 00000000..7ca479a3 --- /dev/null +++ b/kairos-cli/bin/crypto/private_key.rs @@ -0,0 +1,16 @@ +use crate::crypto::error::CryptoError; + +pub struct CasperPrivateKey(pub casper_types::SecretKey); + +impl CasperPrivateKey { + pub fn from_key(public_key: casper_types::SecretKey) -> Self { + Self(public_key) + } + + pub fn from_file(file_path: &str) -> Result { + let secret_key = casper_types::SecretKey::from_file(file_path) + .map_err(|_e| CryptoError::FailedToParseKey {})?; + let private_key = casper_types::SecretKey::from(secret_key); + Ok(Self(private_key)) + } +} diff --git a/kairos-cli/bin/crypto/public_key.rs b/kairos-cli/bin/crypto/public_key.rs new file mode 100644 index 00000000..b5d6bb66 --- /dev/null +++ b/kairos-cli/bin/crypto/public_key.rs @@ -0,0 +1,27 @@ +use crate::crypto::error::CryptoError; +use casper_types::bytesrepr::FromBytes; +use casper_types::bytesrepr::ToBytes; +use casper_types::crypto; + +#[derive(Clone)] +pub struct CasperPublicKey(pub casper_types::PublicKey); + +impl CasperPublicKey { + pub fn from_hex(hex_str: &str) -> Result { + let bytes = hex::decode(hex_str).map_err(|_e| CryptoError::Serialization {})?; + let (public_key, _) = + crypto::PublicKey::from_bytes(&bytes).map_err(|_e| CryptoError::Serialization {})?; + + Ok(Self(public_key)) + } + + pub fn from_key(public_key: casper_types::PublicKey) -> Self { + Self(public_key) + } + + fn get(&self) -> Result, CryptoError> { + self.0 + .to_bytes() + .map_err(|_e| CryptoError::Serialization {}) + } +} diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs new file mode 100644 index 00000000..d6f63b4f --- /dev/null +++ b/kairos-cli/bin/crypto/signer.rs @@ -0,0 +1,42 @@ +use super::private_key::CasperPrivateKey; +use super::public_key::CasperPublicKey; +use crate::crypto::error::CryptoError; +use casper_types::{bytesrepr::ToBytes, SecretKey}; +use casper_types::{crypto, PublicKey}; + +pub struct CasperSigner { + secret_key: CasperPrivateKey, + public_key: CasperPublicKey, +} + +impl CasperSigner { + pub fn from_key(secret_key: CasperPrivateKey) -> Self { + // Derive the public key. + let public_key = CasperPublicKey::from_key(PublicKey::from(&secret_key.0)); + + CasperSigner { + secret_key, + public_key, + } + } + + pub fn from_file(secret_key_path: &str) -> Result { + let secret_key = + SecretKey::from_file(secret_key_path).map_err(|_| CryptoError::FailedToParseKey {})?; + + Ok(Self::from_key(CasperPrivateKey::from_key(secret_key))) + } + + fn get_public_key(&self) -> CasperPublicKey { + self.public_key.clone() + } + + fn sign_message(&self, message: &[u8]) -> Result, CryptoError> { + let signature = crypto::sign(message, &self.secret_key.0, &self.public_key.0); + let bytes = signature + .to_bytes() + .map_err(|_e| CryptoError::Serialization {})?; + + Ok(bytes) + } +} diff --git a/kairos-cli/bin/error.rs b/kairos-cli/bin/error.rs index d9515766..11c01afc 100644 --- a/kairos-cli/bin/error.rs +++ b/kairos-cli/bin/error.rs @@ -1,3 +1,4 @@ +use crate::crypto::error::CryptoError; use thiserror::Error; #[derive(Error, Debug)] @@ -8,4 +9,7 @@ pub enum CliError { /// Failed to parse amount from string. #[error("failed to parse '{context}' as u64")] FailedToParseU64 { context: &'static str }, + /// Cryptography error. + #[error("cryptography error: {error}")] + CryptoError { error: CryptoError }, } diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index 542493f7..886d1624 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -1,5 +1,6 @@ mod commands; mod common; +mod crypto; mod error; use clap::Command; From 154363706d0e61ad4b9f536e957a410ff4bfffe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 6 Feb 2024 14:27:51 +0100 Subject: [PATCH 08/29] Add tests for CLI usage. --- Cargo.lock | 72 +++++++++++++ kairos-cli/Cargo.toml | 1 + kairos-cli/tests/cli_tests.rs | 100 ++++++++++++++++++ .../tests/fixtures/ed25519/public_key.pem | 3 + .../tests/fixtures/ed25519/secret_key.pem | 3 + .../tests/fixtures/secp256k1/public_key.pem | 4 + .../tests/fixtures/secp256k1/secret_key.pem | 3 + 7 files changed, 186 insertions(+) create mode 100644 kairos-cli/tests/cli_tests.rs create mode 100644 kairos-cli/tests/fixtures/ed25519/public_key.pem create mode 100644 kairos-cli/tests/fixtures/ed25519/secret_key.pem create mode 100644 kairos-cli/tests/fixtures/secp256k1/public_key.pem create mode 100644 kairos-cli/tests/fixtures/secp256k1/secret_key.pem diff --git a/Cargo.lock b/Cargo.lock index e922bc0d..e0753339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,21 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "assert_cmd" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00ad3f3a942eee60335ab4342358c161ee296829e0d16ff42fc1d6cb07815467" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "async-trait" version = "0.1.77" @@ -319,6 +334,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +dependencies = [ + "memchr", + "regex-automata 0.4.5", + "serde", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -537,6 +563,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.9.0" @@ -558,6 +590,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dotenvy" version = "0.15.7" @@ -986,6 +1024,7 @@ dependencies = [ name = "kairos-cli" version = "0.1.0" dependencies = [ + "assert_cmd", "casper-types", "clap", "hex", @@ -1342,6 +1381,33 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -1821,6 +1887,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" version = "1.0.56" diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index ab4f3a6b..201d9427 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -13,6 +13,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +assert_cmd = "2.0.13" casper-types = { version = "4.0.1", features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. clap = "4.4.18" hex = "0.4.3" diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs new file mode 100644 index 00000000..2aaf6243 --- /dev/null +++ b/kairos-cli/tests/cli_tests.rs @@ -0,0 +1,100 @@ +use assert_cmd::Command; +use std::path::PathBuf; + +// Helper function to get the path to a fixture file +fn fixture_path(relative_path: &str) -> String { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests"); + path.push("fixtures"); + path.push(relative_path); + path.to_str().unwrap().to_string() +} + +#[test] +fn deposit_successful_with_ed25519() { + let secret_key_path = fixture_path("ed25519/secret_key.pem"); + + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); + cmd.arg("deposit") + .arg("--amount") + .arg("123") + .arg("--private-key") + .arg(secret_key_path); + cmd.assert().success().stdout("ok\n"); +} + +#[test] +fn transfer_successful_with_secp256k1() { + let secret_key_path = fixture_path("secp256k1/secret_key.pem"); + let recipient = "01a26419a7d82b2263deaedea32d35eee8ae1c850bd477f62a82939f06e80df356"; // Example recipient + + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); + cmd.arg("transfer") + .arg("--recipient") + .arg(recipient) + .arg("--amount") + .arg("123") + .arg("--private-key") + .arg(secret_key_path); + cmd.assert().success().stdout("ok\n"); +} + +#[test] +fn withdraw_successful_with_ed25519() { + let secret_key_path = fixture_path("ed25519/secret_key.pem"); + + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); + cmd.arg("withdraw") + .arg("--amount") + .arg("123") + .arg("--private-key") + .arg(secret_key_path); + cmd.assert().success().stdout("ok\n"); +} + +#[test] +fn deposit_invalid_amount() { + let secret_key_path = fixture_path("ed25519/secret_key.pem"); + + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); + cmd.arg("deposit") + .arg("--amount") + .arg("foo") // Invalid amount + .arg("--private-key") + .arg(secret_key_path); + cmd.assert() + .failure() + .stdout("failed to parse 'amount' as u64\n"); +} + +#[test] +fn deposit_invalid_private_key_path() { + let secret_key_path = fixture_path("ed25519/non_existing.pem"); // Non-existing path + + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); + cmd.arg("deposit") + .arg("--amount") + .arg("123") + .arg("--private-key") + .arg(secret_key_path); + cmd.assert() + .failure() + .stdout("cryptography error: failed to parse private key\n"); +} + +#[test] +fn transfer_invalid_recipient() { + let secret_key_path = fixture_path("ed25519/secret_key.pem"); + + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); + cmd.arg("transfer") + .arg("--recipient") + .arg("foo") // Invalid recipient format + .arg("--amount") + .arg("123") + .arg("--private-key") + .arg(secret_key_path); + cmd.assert() + .failure() + .stdout("cryptography error: failed to serialize signature/key\n"); +} diff --git a/kairos-cli/tests/fixtures/ed25519/public_key.pem b/kairos-cli/tests/fixtures/ed25519/public_key.pem new file mode 100644 index 00000000..0fabc70e --- /dev/null +++ b/kairos-cli/tests/fixtures/ed25519/public_key.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA/nhOuK9V0WBgXegPtMgJXxzzrr9Cr6VXAyRVA4s9HOY= +-----END PUBLIC KEY----- diff --git a/kairos-cli/tests/fixtures/ed25519/secret_key.pem b/kairos-cli/tests/fixtures/ed25519/secret_key.pem new file mode 100644 index 00000000..c8b5e128 --- /dev/null +++ b/kairos-cli/tests/fixtures/ed25519/secret_key.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEINGZqbfpO+UnBomOhxW0Y+ynRuDPpD4354zEYVPM3hpz +-----END PRIVATE KEY----- diff --git a/kairos-cli/tests/fixtures/secp256k1/public_key.pem b/kairos-cli/tests/fixtures/secp256k1/public_key.pem new file mode 100644 index 00000000..9052f23d --- /dev/null +++ b/kairos-cli/tests/fixtures/secp256k1/public_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgADnu3WpM+rtgFyMjU9/M5Pi6npWn6zEtRy +ZnyS1LP24Ys= +-----END PUBLIC KEY----- diff --git a/kairos-cli/tests/fixtures/secp256k1/secret_key.pem b/kairos-cli/tests/fixtures/secp256k1/secret_key.pem new file mode 100644 index 00000000..24036c2c --- /dev/null +++ b/kairos-cli/tests/fixtures/secp256k1/secret_key.pem @@ -0,0 +1,3 @@ +-----BEGIN EC PRIVATE KEY----- +MC4CAQEEIKJ1twPHznYeFtbkBygA8rLf9Y3rQJ9mlSQz1WxErM5UoAcGBSuBBAAK +-----END EC PRIVATE KEY----- From 479a52bee4feecfab282b09fc61dd617c1b2bf54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 6 Feb 2024 14:52:59 +0100 Subject: [PATCH 09/29] Fix clippy warnings. --- kairos-cli/bin/commands/deposit.rs | 6 +++--- kairos-cli/bin/commands/mod.rs | 2 +- kairos-cli/bin/commands/transfer.rs | 8 ++++---- kairos-cli/bin/commands/withdraw.rs | 6 +++--- kairos-cli/bin/common/args.rs | 4 ++-- kairos-cli/bin/crypto/private_key.rs | 3 +-- kairos-cli/bin/crypto/public_key.rs | 1 + kairos-cli/bin/crypto/signer.rs | 2 ++ kairos-cli/bin/main.rs | 6 +++--- 9 files changed, 20 insertions(+), 18 deletions(-) diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/bin/commands/deposit.rs index 49137647..bc97b58b 100644 --- a/kairos-cli/bin/commands/deposit.rs +++ b/kairos-cli/bin/commands/deposit.rs @@ -10,7 +10,7 @@ impl ClientCommand for Deposit { const NAME: &'static str = "deposit"; const ABOUT: &'static str = "Deposits funds into your account"; - fn new() -> Command { + fn new_cmd() -> Command { Command::new(Self::NAME) .about(Self::ABOUT) .arg(amount::arg()) @@ -18,10 +18,10 @@ impl ClientCommand for Deposit { } fn run(matches: &ArgMatches) -> Result { - let amount = amount::get(matches)?; + let _amount = amount::get(matches)?; let private_key = private_key::get(matches)?; - let signer = CasperSigner::from_key(private_key); + let _signer = CasperSigner::from_key(private_key); // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/bin/commands/mod.rs b/kairos-cli/bin/commands/mod.rs index 71c1cfff..c6f4a61d 100644 --- a/kairos-cli/bin/commands/mod.rs +++ b/kairos-cli/bin/commands/mod.rs @@ -17,7 +17,7 @@ pub trait ClientCommand { const ABOUT: &'static str; /// Constructs the clap subcommand. - fn new() -> Command; + fn new_cmd() -> Command; /// Parses the arg matches and runs the subcommand. fn run(matches: &ArgMatches) -> Result; diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs index 6967fc41..2a9f4b92 100644 --- a/kairos-cli/bin/commands/transfer.rs +++ b/kairos-cli/bin/commands/transfer.rs @@ -36,7 +36,7 @@ impl ClientCommand for Transfer { const NAME: &'static str = "transfer"; const ABOUT: &'static str = "Transfers funds to another account"; - fn new() -> Command { + fn new_cmd() -> Command { Command::new(Self::NAME) .about(Self::ABOUT) .arg(recipient::arg()) @@ -45,11 +45,11 @@ impl ClientCommand for Transfer { } fn run(matches: &ArgMatches) -> Result { - let recipient = recipient::get(matches)?; - let amount = amount::get(matches)?; + let _recipient = recipient::get(matches)?; + let _amount = amount::get(matches)?; let private_key = private_key::get(matches)?; - let signer = CasperSigner::from_key(private_key); + let _signer = CasperSigner::from_key(private_key); // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/bin/commands/withdraw.rs index 15deb43a..b8ec133f 100644 --- a/kairos-cli/bin/commands/withdraw.rs +++ b/kairos-cli/bin/commands/withdraw.rs @@ -10,7 +10,7 @@ impl ClientCommand for Withdraw { const NAME: &'static str = "withdraw"; const ABOUT: &'static str = "Withdraws funds from your account"; - fn new() -> Command { + fn new_cmd() -> Command { Command::new(Self::NAME) .about(Self::ABOUT) .arg(amount::arg()) @@ -18,10 +18,10 @@ impl ClientCommand for Withdraw { } fn run(matches: &ArgMatches) -> Result { - let amount = amount::get(matches)?; + let _amount = amount::get(matches)?; let private_key = private_key::get(matches)?; - let signer = CasperSigner::from_key(private_key); + let _signer = CasperSigner::from_key(private_key); // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/bin/common/args.rs b/kairos-cli/bin/common/args.rs index fcd0926f..73e268ac 100644 --- a/kairos-cli/bin/common/args.rs +++ b/kairos-cli/bin/common/args.rs @@ -20,7 +20,7 @@ pub mod amount { let value = matches .get_one::(ARG_NAME) .map(String::as_str) - .ok_or_else(|| CliError::MissingArgument { context: ARG_NAME })?; + .ok_or(CliError::MissingArgument { context: ARG_NAME })?; let amount = value .parse::() @@ -51,7 +51,7 @@ pub mod private_key { let value = matches .get_one::(ARG_NAME) .map(String::as_str) - .ok_or_else(|| CliError::MissingArgument { context: ARG_NAME })?; + .ok_or(CliError::MissingArgument { context: ARG_NAME })?; let private_key = CasperPrivateKey::from_file(value).map_err(|error| CliError::CryptoError { error })?; diff --git a/kairos-cli/bin/crypto/private_key.rs b/kairos-cli/bin/crypto/private_key.rs index 7ca479a3..7ed7a911 100644 --- a/kairos-cli/bin/crypto/private_key.rs +++ b/kairos-cli/bin/crypto/private_key.rs @@ -10,7 +10,6 @@ impl CasperPrivateKey { pub fn from_file(file_path: &str) -> Result { let secret_key = casper_types::SecretKey::from_file(file_path) .map_err(|_e| CryptoError::FailedToParseKey {})?; - let private_key = casper_types::SecretKey::from(secret_key); - Ok(Self(private_key)) + Ok(Self(secret_key)) } } diff --git a/kairos-cli/bin/crypto/public_key.rs b/kairos-cli/bin/crypto/public_key.rs index b5d6bb66..f90b7fee 100644 --- a/kairos-cli/bin/crypto/public_key.rs +++ b/kairos-cli/bin/crypto/public_key.rs @@ -19,6 +19,7 @@ impl CasperPublicKey { Self(public_key) } + #[allow(unused)] fn get(&self) -> Result, CryptoError> { self.0 .to_bytes() diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs index d6f63b4f..065cd14b 100644 --- a/kairos-cli/bin/crypto/signer.rs +++ b/kairos-cli/bin/crypto/signer.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use super::private_key::CasperPrivateKey; use super::public_key::CasperPublicKey; use crate::crypto::error::CryptoError; diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index 886d1624..a0f2789c 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -10,9 +10,9 @@ use std::process; fn cli() -> Command { Command::new("Kairos Client") .about("CLI for interacting with Kairos") - .subcommand(Deposit::new()) - .subcommand(Transfer::new()) - .subcommand(Withdraw::new()) + .subcommand(Deposit::new_cmd()) + .subcommand(Transfer::new_cmd()) + .subcommand(Withdraw::new_cmd()) } fn main() { From 9bc4a717f1f4bba387af53a229a51cee05a29081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 6 Feb 2024 21:53:59 +0100 Subject: [PATCH 10/29] Use stderr for errors. --- kairos-cli/bin/main.rs | 2 +- kairos-cli/tests/cli_tests.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index a0f2789c..25c5adce 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -39,7 +39,7 @@ fn main() { println!("{}", output) } Err(error) => { - println!("{}", error); + eprintln!("{}", error); process::exit(1); } } diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 2aaf6243..c047db5b 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -64,7 +64,7 @@ fn deposit_invalid_amount() { .arg(secret_key_path); cmd.assert() .failure() - .stdout("failed to parse 'amount' as u64\n"); + .stderr("failed to parse 'amount' as u64\n"); } #[test] @@ -79,7 +79,7 @@ fn deposit_invalid_private_key_path() { .arg(secret_key_path); cmd.assert() .failure() - .stdout("cryptography error: failed to parse private key\n"); + .stderr("cryptography error: failed to parse private key\n"); } #[test] @@ -96,5 +96,5 @@ fn transfer_invalid_recipient() { .arg(secret_key_path); cmd.assert() .failure() - .stdout("cryptography error: failed to serialize signature/key\n"); + .stderr("cryptography error: failed to serialize signature/key\n"); } From caed7b8d85cd99c4dbbb3b0ad0330fcafb719764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 6 Feb 2024 21:58:41 +0100 Subject: [PATCH 11/29] Remove `ClientCommand` trait. --- kairos-cli/bin/commands/deposit.rs | 11 +++++------ kairos-cli/bin/commands/mod.rs | 17 ----------------- kairos-cli/bin/commands/transfer.rs | 11 +++++------ kairos-cli/bin/commands/withdraw.rs | 11 +++++------ kairos-cli/bin/main.rs | 2 +- 5 files changed, 16 insertions(+), 36 deletions(-) diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/bin/commands/deposit.rs index bc97b58b..17dd2f48 100644 --- a/kairos-cli/bin/commands/deposit.rs +++ b/kairos-cli/bin/commands/deposit.rs @@ -1,4 +1,3 @@ -use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; use crate::crypto::signer::CasperSigner; use crate::error::CliError; @@ -6,18 +5,18 @@ use clap::{ArgMatches, Command}; pub struct Deposit; -impl ClientCommand for Deposit { - const NAME: &'static str = "deposit"; - const ABOUT: &'static str = "Deposits funds into your account"; +impl Deposit { + pub const NAME: &'static str = "deposit"; + pub const ABOUT: &'static str = "Deposits funds into your account"; - fn new_cmd() -> Command { + pub fn new_cmd() -> Command { Command::new(Self::NAME) .about(Self::ABOUT) .arg(amount::arg()) .arg(private_key::arg()) } - fn run(matches: &ArgMatches) -> Result { + pub fn run(matches: &ArgMatches) -> Result { let _amount = amount::get(matches)?; let private_key = private_key::get(matches)?; diff --git a/kairos-cli/bin/commands/mod.rs b/kairos-cli/bin/commands/mod.rs index c6f4a61d..3c6fdceb 100644 --- a/kairos-cli/bin/commands/mod.rs +++ b/kairos-cli/bin/commands/mod.rs @@ -5,20 +5,3 @@ mod withdraw; pub use deposit::Deposit; pub use transfer::Transfer; pub use withdraw::Withdraw; - -use crate::error::CliError; -use clap::{ArgMatches, Command}; - -// NOTE: Temporarily we use plain output. -pub type Output = String; - -pub trait ClientCommand { - const NAME: &'static str; - const ABOUT: &'static str; - - /// Constructs the clap subcommand. - fn new_cmd() -> Command; - - /// Parses the arg matches and runs the subcommand. - fn run(matches: &ArgMatches) -> Result; -} diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs index 2a9f4b92..e8da7e38 100644 --- a/kairos-cli/bin/commands/transfer.rs +++ b/kairos-cli/bin/commands/transfer.rs @@ -1,4 +1,3 @@ -use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; use crate::crypto::public_key::CasperPublicKey; use crate::crypto::signer::CasperSigner; @@ -32,11 +31,11 @@ pub mod recipient { } } -impl ClientCommand for Transfer { - const NAME: &'static str = "transfer"; - const ABOUT: &'static str = "Transfers funds to another account"; +impl Transfer { + pub const NAME: &'static str = "transfer"; + pub const ABOUT: &'static str = "Transfers funds to another account"; - fn new_cmd() -> Command { + pub fn new_cmd() -> Command { Command::new(Self::NAME) .about(Self::ABOUT) .arg(recipient::arg()) @@ -44,7 +43,7 @@ impl ClientCommand for Transfer { .arg(private_key::arg()) } - fn run(matches: &ArgMatches) -> Result { + pub fn run(matches: &ArgMatches) -> Result { let _recipient = recipient::get(matches)?; let _amount = amount::get(matches)?; let private_key = private_key::get(matches)?; diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/bin/commands/withdraw.rs index b8ec133f..481cf894 100644 --- a/kairos-cli/bin/commands/withdraw.rs +++ b/kairos-cli/bin/commands/withdraw.rs @@ -1,4 +1,3 @@ -use crate::commands::{ClientCommand, Output}; use crate::common::{amount, private_key}; use crate::crypto::signer::CasperSigner; use crate::error::CliError; @@ -6,18 +5,18 @@ use clap::{ArgMatches, Command}; pub struct Withdraw; -impl ClientCommand for Withdraw { - const NAME: &'static str = "withdraw"; - const ABOUT: &'static str = "Withdraws funds from your account"; +impl Withdraw { + pub const NAME: &'static str = "withdraw"; + pub const ABOUT: &'static str = "Withdraws funds from your account"; - fn new_cmd() -> Command { + pub fn new_cmd() -> Command { Command::new(Self::NAME) .about(Self::ABOUT) .arg(amount::arg()) .arg(private_key::arg()) } - fn run(matches: &ArgMatches) -> Result { + pub fn run(matches: &ArgMatches) -> Result { let _amount = amount::get(matches)?; let private_key = private_key::get(matches)?; diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index 25c5adce..b1aa75a1 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -4,7 +4,7 @@ mod crypto; mod error; use clap::Command; -use commands::{ClientCommand, Deposit, Transfer, Withdraw}; +use commands::{Deposit, Transfer, Withdraw}; use std::process; fn cli() -> Command { From db60f27b9f9168ac716f4551f3bc34109c6b6041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 6 Feb 2024 22:02:04 +0100 Subject: [PATCH 12/29] Simplify `fixture_path()` helper. --- kairos-cli/tests/cli_tests.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index c047db5b..e0e98110 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -2,12 +2,10 @@ use assert_cmd::Command; use std::path::PathBuf; // Helper function to get the path to a fixture file -fn fixture_path(relative_path: &str) -> String { +fn fixture_path(relative_path: &str) -> PathBuf { let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("tests"); - path.push("fixtures"); - path.push(relative_path); - path.to_str().unwrap().to_string() + path.extend(["tests", "fixtures", relative_path].iter()); + path } #[test] From 70d4d9b01d47f4f4046a9aef9cfc5b6cfc568ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 6 Feb 2024 22:22:08 +0100 Subject: [PATCH 13/29] Include fixtures as part of the clean source. This allows to include static files in tests. --- flake.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index d7278be3..0974826d 100644 --- a/flake.nix +++ b/flake.nix @@ -45,7 +45,12 @@ kairosNodeAttrs = { src = lib.cleanSourceWith { src = craneLib.path ./.; - filter = path: type: craneLib.filterCargoSources path type; + filter = path: type: + # Allow static files. + (lib.hasInfix "/fixtures/" path) || + # Default filter (from crane) for .rs files. + (craneLib.filterCargoSources path type) + ; }; nativeBuildInputs = with pkgs; [ pkg-config ]; From 4e869d24e971d5c0c2461bc60dccbef4751a7438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Wed, 7 Feb 2024 13:13:59 +0100 Subject: [PATCH 14/29] Remove shallow constructor function. `CasperPrivateKey` can be created directly, as every field is public. --- kairos-cli/bin/crypto/private_key.rs | 4 ---- kairos-cli/bin/crypto/signer.rs | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/kairos-cli/bin/crypto/private_key.rs b/kairos-cli/bin/crypto/private_key.rs index 7ed7a911..7fcc937c 100644 --- a/kairos-cli/bin/crypto/private_key.rs +++ b/kairos-cli/bin/crypto/private_key.rs @@ -3,10 +3,6 @@ use crate::crypto::error::CryptoError; pub struct CasperPrivateKey(pub casper_types::SecretKey); impl CasperPrivateKey { - pub fn from_key(public_key: casper_types::SecretKey) -> Self { - Self(public_key) - } - pub fn from_file(file_path: &str) -> Result { let secret_key = casper_types::SecretKey::from_file(file_path) .map_err(|_e| CryptoError::FailedToParseKey {})?; diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs index 065cd14b..ba56c0a7 100644 --- a/kairos-cli/bin/crypto/signer.rs +++ b/kairos-cli/bin/crypto/signer.rs @@ -26,7 +26,7 @@ impl CasperSigner { let secret_key = SecretKey::from_file(secret_key_path).map_err(|_| CryptoError::FailedToParseKey {})?; - Ok(Self::from_key(CasperPrivateKey::from_key(secret_key))) + Ok(Self::from_key(CasperPrivateKey(secret_key))) } fn get_public_key(&self) -> CasperPublicKey { From e874746037aa560bafa4fad05d46d95ebe8a36d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Wed, 7 Feb 2024 13:19:50 +0100 Subject: [PATCH 15/29] Rename `CasperPublicKey::get()` into `to_bytes()`. --- kairos-cli/bin/crypto/public_key.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kairos-cli/bin/crypto/public_key.rs b/kairos-cli/bin/crypto/public_key.rs index f90b7fee..1a58b849 100644 --- a/kairos-cli/bin/crypto/public_key.rs +++ b/kairos-cli/bin/crypto/public_key.rs @@ -20,7 +20,7 @@ impl CasperPublicKey { } #[allow(unused)] - fn get(&self) -> Result, CryptoError> { + fn to_bytes(&self) -> Result, CryptoError> { self.0 .to_bytes() .map_err(|_e| CryptoError::Serialization {}) From c91cc30efba3080ef370d9bc80fea19c43f0c702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Thu, 8 Feb 2024 09:28:59 +0100 Subject: [PATCH 16/29] Move `assert_cmd` into dev dependencies. --- kairos-cli/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 201d9427..7a07e845 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -13,8 +13,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -assert_cmd = "2.0.13" casper-types = { version = "4.0.1", features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. clap = "4.4.18" hex = "0.4.3" thiserror = "1.0.56" + +[dev-dependencies] +assert_cmd = "2.0.13" From 0d154b77c9b96cf316307c1f5ac40a1afc88632a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 12 Feb 2024 18:38:34 +0100 Subject: [PATCH 17/29] Refactor CLI parsing: `clap` derive + improved errors. **Big refactoring!** As suggested, more declarative code is used to define CLI. NOTE: Errors like 'missing argument' or 'u64 parse error' are handled by `clap` natively now. --- Cargo.lock | 32 +++++++++++++ kairos-cli/Cargo.toml | 3 +- kairos-cli/bin/commands/deposit.rs | 37 ++++++--------- kairos-cli/bin/commands/mod.rs | 20 +++++--- kairos-cli/bin/commands/transfer.rs | 66 ++++++++------------------- kairos-cli/bin/commands/withdraw.rs | 37 ++++++--------- kairos-cli/bin/common/args.rs | 66 ++++----------------------- kairos-cli/bin/common/mod.rs | 5 +- kairos-cli/bin/crypto/error.rs | 12 +++-- kairos-cli/bin/crypto/private_key.rs | 11 ++++- kairos-cli/bin/crypto/public_key.rs | 19 ++++---- kairos-cli/bin/crypto/signer.rs | 26 +++++++---- kairos-cli/bin/error.rs | 22 +++++---- kairos-cli/bin/main.rs | 37 ++++++--------- kairos-cli/bin/utils.rs | 6 +++ kairos-cli/tests/cli_tests.rs | 21 +++++++-- kairos-cli/tests/fixtures/invalid.pem | 1 + 17 files changed, 206 insertions(+), 215 deletions(-) create mode 100644 kairos-cli/bin/utils.rs create mode 100644 kairos-cli/tests/fixtures/invalid.pem diff --git a/Cargo.lock b/Cargo.lock index e0753339..b03a31ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,6 +412,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -426,6 +427,18 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "clap_lex" version = "0.6.0" @@ -695,6 +708,15 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1028,6 +1050,7 @@ dependencies = [ "casper-types", "clap", "hex", + "predicates", "thiserror", ] @@ -1146,6 +1169,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1389,7 +1418,10 @@ checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" dependencies = [ "anstyle", "difflib", + "float-cmp", + "normalize-line-endings", "predicates-core", + "regex", ] [[package]] diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index 7a07e845..abf507c8 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -14,9 +14,10 @@ edition = "2021" [dependencies] casper-types = { version = "4.0.1", features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. -clap = "4.4.18" +clap = { version = "4.4.18", features = ["derive"] } hex = "0.4.3" thiserror = "1.0.56" [dev-dependencies] assert_cmd = "2.0.13" +predicates = "3.1.0" diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/bin/commands/deposit.rs index 17dd2f48..a3967e6f 100644 --- a/kairos-cli/bin/commands/deposit.rs +++ b/kairos-cli/bin/commands/deposit.rs @@ -1,31 +1,24 @@ -use crate::common::{amount, private_key}; +use crate::common::args::{AmountArg, PrivateKeyPathArg}; use crate::crypto::signer::CasperSigner; use crate::error::CliError; -use clap::{ArgMatches, Command}; -pub struct Deposit; +use clap::Parser; -impl Deposit { - pub const NAME: &'static str = "deposit"; - pub const ABOUT: &'static str = "Deposits funds into your account"; - - pub fn new_cmd() -> Command { - Command::new(Self::NAME) - .about(Self::ABOUT) - .arg(amount::arg()) - .arg(private_key::arg()) - } - - pub fn run(matches: &ArgMatches) -> Result { - let _amount = amount::get(matches)?; - let private_key = private_key::get(matches)?; +#[derive(Parser, Debug)] +pub struct Args { + #[clap(flatten)] + amount: AmountArg, + #[clap(flatten)] + private_key_path: PrivateKeyPathArg, +} - let _signer = CasperSigner::from_key(private_key); +pub fn run(args: Args) -> Result { + let _amount: u64 = args.amount.field; + let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?; - // TODO: Create transaction and sign it with `signer`. + // TODO: Create transaction and sign it with `signer`. - // TODO: Send transaction to the network, using Rust SDK. + // TODO: Send transaction to the network, using Rust SDK. - Ok("ok".to_string()) - } + Ok("ok".to_string()) } diff --git a/kairos-cli/bin/commands/mod.rs b/kairos-cli/bin/commands/mod.rs index 3c6fdceb..069a0721 100644 --- a/kairos-cli/bin/commands/mod.rs +++ b/kairos-cli/bin/commands/mod.rs @@ -1,7 +1,15 @@ -mod deposit; -mod transfer; -mod withdraw; +pub mod deposit; +pub mod transfer; +pub mod withdraw; -pub use deposit::Deposit; -pub use transfer::Transfer; -pub use withdraw::Withdraw; +use clap::Subcommand; + +#[derive(Subcommand)] +pub enum Command { + #[command(about = "Deposits funds into your account")] + Deposit(deposit::Args), + #[command(about = "Transfers funds to another account")] + Transfer(transfer::Args), + #[command(about = "Withdraws funds from your account")] + Withdraw(withdraw::Args), +} diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs index e8da7e38..74112948 100644 --- a/kairos-cli/bin/commands/transfer.rs +++ b/kairos-cli/bin/commands/transfer.rs @@ -1,59 +1,31 @@ -use crate::common::{amount, private_key}; +use crate::common::args::{AmountArg, PrivateKeyPathArg}; use crate::crypto::public_key::CasperPublicKey; use crate::crypto::signer::CasperSigner; use crate::error::CliError; -use clap::{Arg, ArgMatches, Command}; +use crate::utils::parse_hex_string; -pub struct Transfer; +use clap::Parser; -const ARG_NAME: &str = "recipient"; -const ARG_SHORT: char = 'r'; -const ARG_VALUE_NAME: &str = "PUBLIC_KEY"; +type StdVec = Vec; -pub mod recipient { - use super::*; - - pub fn arg() -> Arg { - Arg::new(ARG_NAME) - .long(ARG_NAME) - .short(ARG_SHORT) - .required(true) - .value_name(ARG_VALUE_NAME) - } - - pub fn get(matches: &ArgMatches) -> Result { - let value = matches - .get_one::("recipient") - .map(String::as_str) - .unwrap(); - - CasperPublicKey::from_hex(value).map_err(|error| CliError::CryptoError { error }) - } +#[derive(Parser)] +pub struct Args { + #[arg(long, short, value_name = "PUBLIC_KEY", value_parser = parse_hex_string)] + recipient: StdVec, + #[clap(flatten)] + amount: AmountArg, + #[clap(flatten)] + private_key_path: PrivateKeyPathArg, } -impl Transfer { - pub const NAME: &'static str = "transfer"; - pub const ABOUT: &'static str = "Transfers funds to another account"; - - pub fn new_cmd() -> Command { - Command::new(Self::NAME) - .about(Self::ABOUT) - .arg(recipient::arg()) - .arg(amount::arg()) - .arg(private_key::arg()) - } - - pub fn run(matches: &ArgMatches) -> Result { - let _recipient = recipient::get(matches)?; - let _amount = amount::get(matches)?; - let private_key = private_key::get(matches)?; - - let _signer = CasperSigner::from_key(private_key); +pub fn run(args: Args) -> Result { + let _recipient = CasperPublicKey::from_bytes(args.recipient.as_ref())?; + let _amount: u64 = args.amount.field; + let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?; - // TODO: Create transaction and sign it with `signer`. + // TODO: Create transaction and sign it with `signer`. - // TODO: Send transaction to the network, using Rust SDK. + // TODO: Send transaction to the network, using Rust SDK. - Ok("ok".to_string()) - } + Ok("ok".to_string()) } diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/bin/commands/withdraw.rs index 481cf894..bdb0c932 100644 --- a/kairos-cli/bin/commands/withdraw.rs +++ b/kairos-cli/bin/commands/withdraw.rs @@ -1,31 +1,24 @@ -use crate::common::{amount, private_key}; +use crate::common::args::{AmountArg, PrivateKeyPathArg}; use crate::crypto::signer::CasperSigner; use crate::error::CliError; -use clap::{ArgMatches, Command}; -pub struct Withdraw; +use clap::Parser; -impl Withdraw { - pub const NAME: &'static str = "withdraw"; - pub const ABOUT: &'static str = "Withdraws funds from your account"; - - pub fn new_cmd() -> Command { - Command::new(Self::NAME) - .about(Self::ABOUT) - .arg(amount::arg()) - .arg(private_key::arg()) - } - - pub fn run(matches: &ArgMatches) -> Result { - let _amount = amount::get(matches)?; - let private_key = private_key::get(matches)?; +#[derive(Parser)] +pub struct Args { + #[clap(flatten)] + amount: AmountArg, + #[clap(flatten)] + private_key_path: PrivateKeyPathArg, +} - let _signer = CasperSigner::from_key(private_key); +pub fn run(args: Args) -> Result { + let _amount: u64 = args.amount.field; + let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?; - // TODO: Create transaction and sign it with `signer`. + // TODO: Create transaction and sign it with `signer`. - // TODO: Send transaction to the network, using Rust SDK. + // TODO: Send transaction to the network, using Rust SDK. - Ok("ok".to_string()) - } + Ok("ok".to_string()) } diff --git a/kairos-cli/bin/common/args.rs b/kairos-cli/bin/common/args.rs index 73e268ac..696dfa12 100644 --- a/kairos-cli/bin/common/args.rs +++ b/kairos-cli/bin/common/args.rs @@ -1,61 +1,15 @@ -use crate::error::CliError; -use clap::{Arg, ArgMatches}; +use std::path::PathBuf; -pub mod amount { - use super::*; +use clap::Args; - const ARG_NAME: &str = "amount"; - const ARG_SHORT: char = 'a'; - const ARG_VALUE_NAME: &str = "NUM_MOTES"; - - pub fn arg() -> Arg { - Arg::new(ARG_NAME) - .long(ARG_NAME) - .short(ARG_SHORT) - .required(true) - .value_name(ARG_VALUE_NAME) - } - - pub fn get(matches: &ArgMatches) -> Result { - let value = matches - .get_one::(ARG_NAME) - .map(String::as_str) - .ok_or(CliError::MissingArgument { context: ARG_NAME })?; - - let amount = value - .parse::() - .map_err(|_| CliError::FailedToParseU64 { context: "amount" })?; - - Ok(amount) - } +#[derive(Args, Debug)] +pub struct AmountArg { + #[arg(name = "amount", long, short, value_name = "NUM_MOTES")] + pub field: u64, } -pub mod private_key { - use crate::crypto::private_key::CasperPrivateKey; - - use super::*; - - const ARG_NAME: &str = "private-key"; - const ARG_SHORT: char = 'k'; - const ARG_VALUE_NAME: &str = "FILE_PATH"; - - pub fn arg() -> Arg { - Arg::new(ARG_NAME) - .long(ARG_NAME) - .short(ARG_SHORT) - .required(true) - .value_name(ARG_VALUE_NAME) - } - - pub fn get(matches: &ArgMatches) -> Result { - let value = matches - .get_one::(ARG_NAME) - .map(String::as_str) - .ok_or(CliError::MissingArgument { context: ARG_NAME })?; - - let private_key = - CasperPrivateKey::from_file(value).map_err(|error| CliError::CryptoError { error })?; - - Ok(private_key) - } +#[derive(Args, Debug)] +pub struct PrivateKeyPathArg { + #[arg(name = "private-key", long, short = 'k', value_name = "FILE_PATH")] + pub field: PathBuf, } diff --git a/kairos-cli/bin/common/mod.rs b/kairos-cli/bin/common/mod.rs index 6a85263a..6e10f4ad 100644 --- a/kairos-cli/bin/common/mod.rs +++ b/kairos-cli/bin/common/mod.rs @@ -1,4 +1 @@ -mod args; - -pub use args::amount; -pub use args::private_key; +pub mod args; diff --git a/kairos-cli/bin/crypto/error.rs b/kairos-cli/bin/crypto/error.rs index 62ce5f66..c713903e 100644 --- a/kairos-cli/bin/crypto/error.rs +++ b/kairos-cli/bin/crypto/error.rs @@ -2,9 +2,13 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum CryptoError { - /// Failed to parse a public key from a formatted string. + /// Unable to load a file from the given path. + #[error("failed to load key from file")] + KeyLoad, + /// Failed to parse a public key from a raw data. #[error("failed to parse private key")] - FailedToParseKey {}, - #[error("failed to serialize signature/key")] - Serialization {}, + FailedToParseKey, + /// Invalid public key (hexdigest) or other encoding related error. + #[error("failed to serialize/deserialize '{context}'")] + Serialization { context: &'static str }, } diff --git a/kairos-cli/bin/crypto/private_key.rs b/kairos-cli/bin/crypto/private_key.rs index 7fcc937c..ca424199 100644 --- a/kairos-cli/bin/crypto/private_key.rs +++ b/kairos-cli/bin/crypto/private_key.rs @@ -1,11 +1,18 @@ +use casper_types::file_utils::read_file; + use crate::crypto::error::CryptoError; pub struct CasperPrivateKey(pub casper_types::SecretKey); impl CasperPrivateKey { pub fn from_file(file_path: &str) -> Result { - let secret_key = casper_types::SecretKey::from_file(file_path) - .map_err(|_e| CryptoError::FailedToParseKey {})?; + let data = read_file(file_path).map_err(|_e| CryptoError::KeyLoad)?; + let secret_key = + casper_types::SecretKey::from_pem(data).map_err(|_e| CryptoError::FailedToParseKey)?; Ok(Self(secret_key)) } } + +pub fn parse_private_key(file_path: &str) -> Result { + CasperPrivateKey::from_file(file_path) +} diff --git a/kairos-cli/bin/crypto/public_key.rs b/kairos-cli/bin/crypto/public_key.rs index 1a58b849..c4bb478a 100644 --- a/kairos-cli/bin/crypto/public_key.rs +++ b/kairos-cli/bin/crypto/public_key.rs @@ -1,17 +1,18 @@ use crate::crypto::error::CryptoError; use casper_types::bytesrepr::FromBytes; use casper_types::bytesrepr::ToBytes; -use casper_types::crypto; #[derive(Clone)] pub struct CasperPublicKey(pub casper_types::PublicKey); impl CasperPublicKey { - pub fn from_hex(hex_str: &str) -> Result { - let bytes = hex::decode(hex_str).map_err(|_e| CryptoError::Serialization {})?; - let (public_key, _) = - crypto::PublicKey::from_bytes(&bytes).map_err(|_e| CryptoError::Serialization {})?; - + pub fn from_bytes(bytes: &[u8]) -> Result { + let (public_key, _remainder) = + casper_types::PublicKey::from_bytes(bytes).map_err(|_e| { + CryptoError::Serialization { + context: "public key", + } + })?; Ok(Self(public_key)) } @@ -21,8 +22,8 @@ impl CasperPublicKey { #[allow(unused)] fn to_bytes(&self) -> Result, CryptoError> { - self.0 - .to_bytes() - .map_err(|_e| CryptoError::Serialization {}) + self.0.to_bytes().map_err(|_e| CryptoError::Serialization { + context: "public key", + }) } } diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs index ba56c0a7..08d64f28 100644 --- a/kairos-cli/bin/crypto/signer.rs +++ b/kairos-cli/bin/crypto/signer.rs @@ -1,6 +1,6 @@ -#![allow(unused)] +use std::path::PathBuf; -use super::private_key::CasperPrivateKey; +use super::private_key::{parse_private_key, CasperPrivateKey}; use super::public_key::CasperPublicKey; use crate::crypto::error::CryptoError; use casper_types::{bytesrepr::ToBytes, SecretKey}; @@ -11,8 +11,9 @@ pub struct CasperSigner { public_key: CasperPublicKey, } +#[allow(unused)] impl CasperSigner { - pub fn from_key(secret_key: CasperPrivateKey) -> Self { + fn from_key_raw(secret_key: CasperPrivateKey) -> Self { // Derive the public key. let public_key = CasperPublicKey::from_key(PublicKey::from(&secret_key.0)); @@ -24,20 +25,29 @@ impl CasperSigner { pub fn from_file(secret_key_path: &str) -> Result { let secret_key = - SecretKey::from_file(secret_key_path).map_err(|_| CryptoError::FailedToParseKey {})?; + SecretKey::from_file(secret_key_path).map_err(|_e| CryptoError::FailedToParseKey)?; - Ok(Self::from_key(CasperPrivateKey(secret_key))) + Ok(Self::from_key_raw(CasperPrivateKey(secret_key))) } - fn get_public_key(&self) -> CasperPublicKey { + pub fn from_key_pathbuf(secret_key_path: PathBuf) -> Result { + let private_key_path_str: &str = secret_key_path.to_str().ok_or(CryptoError::KeyLoad)?; + let private_key = parse_private_key(private_key_path_str)?; + + Ok(Self::from_key_raw(private_key)) + } + + pub fn get_public_key(&self) -> CasperPublicKey { self.public_key.clone() } - fn sign_message(&self, message: &[u8]) -> Result, CryptoError> { + pub fn sign_message(&self, message: &[u8]) -> Result, CryptoError> { let signature = crypto::sign(message, &self.secret_key.0, &self.public_key.0); let bytes = signature .to_bytes() - .map_err(|_e| CryptoError::Serialization {})?; + .map_err(|_e| CryptoError::Serialization { + context: "signature", + })?; Ok(bytes) } diff --git a/kairos-cli/bin/error.rs b/kairos-cli/bin/error.rs index 11c01afc..3d68c6ed 100644 --- a/kairos-cli/bin/error.rs +++ b/kairos-cli/bin/error.rs @@ -1,15 +1,21 @@ -use crate::crypto::error::CryptoError; +use hex::FromHexError; use thiserror::Error; +use crate::crypto::error::CryptoError; + #[derive(Error, Debug)] pub enum CliError { - /// Unable to find argument by name. - #[error("missing argument '{context}'")] - MissingArgument { context: &'static str }, - /// Failed to parse amount from string. - #[error("failed to parse '{context}' as u64")] - FailedToParseU64 { context: &'static str }, /// Cryptography error. #[error("cryptography error: {error}")] - CryptoError { error: CryptoError }, + CryptoError { + #[from] + error: CryptoError, + }, + // TODO: Add error for "Failed to parse hex string: {}" + /// Failed to parse hex string. + #[error("failed to parse hex string: {error}")] + ParseError { + #[from] + error: FromHexError, + }, } diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index b1aa75a1..79b20880 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -2,36 +2,27 @@ mod commands; mod common; mod crypto; mod error; +mod utils; -use clap::Command; -use commands::{Deposit, Transfer, Withdraw}; use std::process; -fn cli() -> Command { - Command::new("Kairos Client") - .about("CLI for interacting with Kairos") - .subcommand(Deposit::new_cmd()) - .subcommand(Transfer::new_cmd()) - .subcommand(Withdraw::new_cmd()) +use clap::Parser; +use commands::Command; + +#[derive(Parser)] +#[command(name = "Kairos Client", about = "CLI for interacting with Kairos")] +struct Cli { + #[command(subcommand)] + command: Command, } fn main() { - let arg_matches = cli().get_matches(); - let (subcommand_name, matches) = arg_matches.subcommand().unwrap_or_else(|| { - // No subcommand provided by user. - let _ = cli().print_long_help(); - process::exit(1); - }); + let cli = Cli::parse(); - let result = match subcommand_name { - Deposit::NAME => Deposit::run(matches), - Transfer::NAME => Transfer::run(matches), - Withdraw::NAME => Withdraw::run(matches), - _ => { - // This should not happen, unless we missed some subcommand. - let _ = cli().print_long_help(); - process::exit(1); - } + let result = match cli.command { + Command::Deposit(args) => commands::deposit::run(args), + Command::Transfer(args) => commands::transfer::run(args), + Command::Withdraw(args) => commands::withdraw::run(args), }; match result { diff --git a/kairos-cli/bin/utils.rs b/kairos-cli/bin/utils.rs new file mode 100644 index 00000000..07c23c11 --- /dev/null +++ b/kairos-cli/bin/utils.rs @@ -0,0 +1,6 @@ +use crate::error::CliError; + +/// Custom parser function to convert a hexadecimal string to a byte array. +pub fn parse_hex_string(s: &str) -> Result, CliError> { + hex::decode(s).map_err(|e| e.into()) +} diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index e0e98110..284799c7 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -62,7 +62,7 @@ fn deposit_invalid_amount() { .arg(secret_key_path); cmd.assert() .failure() - .stderr("failed to parse 'amount' as u64\n"); + .stderr(predicates::str::contains("invalid value")); } #[test] @@ -77,7 +77,22 @@ fn deposit_invalid_private_key_path() { .arg(secret_key_path); cmd.assert() .failure() - .stderr("cryptography error: failed to parse private key\n"); + .stderr(predicates::str::contains("failed to load key from file")); +} + +#[test] +fn deposit_invalid_private_key_content() { + let secret_key_path = fixture_path("invalid.pem"); // Invalid content + + let mut cmd = Command::cargo_bin("kairos-cli").unwrap(); + cmd.arg("deposit") + .arg("--amount") + .arg("123") + .arg("--private-key") + .arg(secret_key_path); + cmd.assert() + .failure() + .stderr(predicates::str::contains("failed to parse private key")); } #[test] @@ -94,5 +109,5 @@ fn transfer_invalid_recipient() { .arg(secret_key_path); cmd.assert() .failure() - .stderr("cryptography error: failed to serialize signature/key\n"); + .stderr(predicates::str::contains("failed to parse hex string")); } diff --git a/kairos-cli/tests/fixtures/invalid.pem b/kairos-cli/tests/fixtures/invalid.pem new file mode 100644 index 00000000..f9129d7a --- /dev/null +++ b/kairos-cli/tests/fixtures/invalid.pem @@ -0,0 +1 @@ +Ooops! From 0bc00c6e6bdfdb695cfe1e1b716a28419b093987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 13 Feb 2024 15:13:43 +0100 Subject: [PATCH 18/29] Replace type alias with absolute path import. Different way to workaround https://github.com/clap-rs/clap/issues/4626. --- kairos-cli/bin/commands/transfer.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs index 74112948..b2c44bda 100644 --- a/kairos-cli/bin/commands/transfer.rs +++ b/kairos-cli/bin/commands/transfer.rs @@ -6,12 +6,10 @@ use crate::utils::parse_hex_string; use clap::Parser; -type StdVec = Vec; - #[derive(Parser)] pub struct Args { #[arg(long, short, value_name = "PUBLIC_KEY", value_parser = parse_hex_string)] - recipient: StdVec, + recipient: ::std::vec::Vec, // Absolute path is required here - see https://github.com/clap-rs/clap/issues/4626#issue-1528622454. #[clap(flatten)] amount: AmountArg, #[clap(flatten)] From 13b80c99ccc682f6b71ae40f1f49907871df880b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 13 Feb 2024 15:34:48 +0100 Subject: [PATCH 19/29] Remove unneeded `prase_private_key()`. `CasperPrivateKey` can be used directly. --- kairos-cli/bin/crypto/private_key.rs | 4 ---- kairos-cli/bin/crypto/signer.rs | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/kairos-cli/bin/crypto/private_key.rs b/kairos-cli/bin/crypto/private_key.rs index ca424199..fb8b1895 100644 --- a/kairos-cli/bin/crypto/private_key.rs +++ b/kairos-cli/bin/crypto/private_key.rs @@ -12,7 +12,3 @@ impl CasperPrivateKey { Ok(Self(secret_key)) } } - -pub fn parse_private_key(file_path: &str) -> Result { - CasperPrivateKey::from_file(file_path) -} diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs index 08d64f28..bf237f72 100644 --- a/kairos-cli/bin/crypto/signer.rs +++ b/kairos-cli/bin/crypto/signer.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use super::private_key::{parse_private_key, CasperPrivateKey}; +use super::private_key::CasperPrivateKey; use super::public_key::CasperPublicKey; use crate::crypto::error::CryptoError; use casper_types::{bytesrepr::ToBytes, SecretKey}; @@ -32,7 +32,7 @@ impl CasperSigner { pub fn from_key_pathbuf(secret_key_path: PathBuf) -> Result { let private_key_path_str: &str = secret_key_path.to_str().ok_or(CryptoError::KeyLoad)?; - let private_key = parse_private_key(private_key_path_str)?; + let private_key = CasperPrivateKey::from_file(private_key_path_str)?; Ok(Self::from_key_raw(private_key)) } From 489deb1f5012fe8fb3fc8fff183a301e12e472fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 13 Feb 2024 15:47:29 +0100 Subject: [PATCH 20/29] Format imports. Used the following `rustfmt.toml`: ``` unstable_features = true imports_granularity = "Module" ``` --- kairos-cli/bin/crypto/public_key.rs | 3 +-- kairos-cli/bin/crypto/signer.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/kairos-cli/bin/crypto/public_key.rs b/kairos-cli/bin/crypto/public_key.rs index c4bb478a..42824d0f 100644 --- a/kairos-cli/bin/crypto/public_key.rs +++ b/kairos-cli/bin/crypto/public_key.rs @@ -1,6 +1,5 @@ use crate::crypto::error::CryptoError; -use casper_types::bytesrepr::FromBytes; -use casper_types::bytesrepr::ToBytes; +use casper_types::bytesrepr::{FromBytes, ToBytes}; #[derive(Clone)] pub struct CasperPublicKey(pub casper_types::PublicKey); diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs index bf237f72..1d42d058 100644 --- a/kairos-cli/bin/crypto/signer.rs +++ b/kairos-cli/bin/crypto/signer.rs @@ -3,8 +3,8 @@ use std::path::PathBuf; use super::private_key::CasperPrivateKey; use super::public_key::CasperPublicKey; use crate::crypto::error::CryptoError; -use casper_types::{bytesrepr::ToBytes, SecretKey}; -use casper_types::{crypto, PublicKey}; +use casper_types::bytesrepr::ToBytes; +use casper_types::{crypto, PublicKey, SecretKey}; pub struct CasperSigner { secret_key: CasperPrivateKey, From 1881742ea4922cdd9cbba7f447eaa6ea2792c013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 13 Feb 2024 16:39:42 +0100 Subject: [PATCH 21/29] Refactor constructors of `CasperSigner` and `CasperPrivateKey`. 1. Removed duplicated code (from_key_pathbuf()). 2. Handling paths via `AsRef`. 3. Expose Caspers's `ErrorExt` error. --- kairos-cli/bin/commands/deposit.rs | 2 +- kairos-cli/bin/commands/transfer.rs | 2 +- kairos-cli/bin/commands/withdraw.rs | 2 +- kairos-cli/bin/crypto/error.rs | 11 ++++++----- kairos-cli/bin/crypto/private_key.rs | 13 ++++++------- kairos-cli/bin/crypto/signer.rs | 26 +++++++------------------- kairos-cli/tests/cli_tests.rs | 2 +- 7 files changed, 23 insertions(+), 35 deletions(-) diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/bin/commands/deposit.rs index a3967e6f..d4e87d11 100644 --- a/kairos-cli/bin/commands/deposit.rs +++ b/kairos-cli/bin/commands/deposit.rs @@ -14,7 +14,7 @@ pub struct Args { pub fn run(args: Args) -> Result { let _amount: u64 = args.amount.field; - let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?; + let _signer = CasperSigner::from_file(args.private_key_path.field)?; // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/bin/commands/transfer.rs index b2c44bda..a5519f1a 100644 --- a/kairos-cli/bin/commands/transfer.rs +++ b/kairos-cli/bin/commands/transfer.rs @@ -19,7 +19,7 @@ pub struct Args { pub fn run(args: Args) -> Result { let _recipient = CasperPublicKey::from_bytes(args.recipient.as_ref())?; let _amount: u64 = args.amount.field; - let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?; + let _signer = CasperSigner::from_file(args.private_key_path.field)?; // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/bin/commands/withdraw.rs index bdb0c932..5c4023f5 100644 --- a/kairos-cli/bin/commands/withdraw.rs +++ b/kairos-cli/bin/commands/withdraw.rs @@ -14,7 +14,7 @@ pub struct Args { pub fn run(args: Args) -> Result { let _amount: u64 = args.amount.field; - let _signer = CasperSigner::from_key_pathbuf(args.private_key_path.field)?; + let _signer = CasperSigner::from_file(args.private_key_path.field)?; // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/bin/crypto/error.rs b/kairos-cli/bin/crypto/error.rs index c713903e..789afa5d 100644 --- a/kairos-cli/bin/crypto/error.rs +++ b/kairos-cli/bin/crypto/error.rs @@ -1,13 +1,14 @@ +use casper_types::ErrorExt; use thiserror::Error; #[derive(Error, Debug)] pub enum CryptoError { - /// Unable to load a file from the given path. - #[error("failed to load key from file")] - KeyLoad, /// Failed to parse a public key from a raw data. - #[error("failed to parse private key")] - FailedToParseKey, + #[error("failed to parse private key: {error}")] + FailedToParseKey { + #[from] + error: ErrorExt, + }, /// Invalid public key (hexdigest) or other encoding related error. #[error("failed to serialize/deserialize '{context}'")] Serialization { context: &'static str }, diff --git a/kairos-cli/bin/crypto/private_key.rs b/kairos-cli/bin/crypto/private_key.rs index fb8b1895..ece70b67 100644 --- a/kairos-cli/bin/crypto/private_key.rs +++ b/kairos-cli/bin/crypto/private_key.rs @@ -1,14 +1,13 @@ -use casper_types::file_utils::read_file; +use std::path::Path; -use crate::crypto::error::CryptoError; +use super::error::CryptoError; pub struct CasperPrivateKey(pub casper_types::SecretKey); impl CasperPrivateKey { - pub fn from_file(file_path: &str) -> Result { - let data = read_file(file_path).map_err(|_e| CryptoError::KeyLoad)?; - let secret_key = - casper_types::SecretKey::from_pem(data).map_err(|_e| CryptoError::FailedToParseKey)?; - Ok(Self(secret_key)) + pub fn from_file>(file_path: P) -> Result { + casper_types::SecretKey::from_file(file_path) + .map(Self) + .map_err(|error| error.into()) } } diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs index 1d42d058..dbfc0f79 100644 --- a/kairos-cli/bin/crypto/signer.rs +++ b/kairos-cli/bin/crypto/signer.rs @@ -1,10 +1,10 @@ -use std::path::PathBuf; +use std::path::Path; use super::private_key::CasperPrivateKey; use super::public_key::CasperPublicKey; use crate::crypto::error::CryptoError; use casper_types::bytesrepr::ToBytes; -use casper_types::{crypto, PublicKey, SecretKey}; +use casper_types::{crypto, PublicKey}; pub struct CasperSigner { secret_key: CasperPrivateKey, @@ -13,28 +13,16 @@ pub struct CasperSigner { #[allow(unused)] impl CasperSigner { - fn from_key_raw(secret_key: CasperPrivateKey) -> Self { + pub fn from_file>(file_path: P) -> Result { + let secret_key = CasperPrivateKey::from_file(file_path)?; + // Derive the public key. let public_key = CasperPublicKey::from_key(PublicKey::from(&secret_key.0)); - CasperSigner { + Ok(CasperSigner { secret_key, public_key, - } - } - - pub fn from_file(secret_key_path: &str) -> Result { - let secret_key = - SecretKey::from_file(secret_key_path).map_err(|_e| CryptoError::FailedToParseKey)?; - - Ok(Self::from_key_raw(CasperPrivateKey(secret_key))) - } - - pub fn from_key_pathbuf(secret_key_path: PathBuf) -> Result { - let private_key_path_str: &str = secret_key_path.to_str().ok_or(CryptoError::KeyLoad)?; - let private_key = CasperPrivateKey::from_file(private_key_path_str)?; - - Ok(Self::from_key_raw(private_key)) + }) } pub fn get_public_key(&self) -> CasperPublicKey { diff --git a/kairos-cli/tests/cli_tests.rs b/kairos-cli/tests/cli_tests.rs index 284799c7..2893ba7d 100644 --- a/kairos-cli/tests/cli_tests.rs +++ b/kairos-cli/tests/cli_tests.rs @@ -77,7 +77,7 @@ fn deposit_invalid_private_key_path() { .arg(secret_key_path); cmd.assert() .failure() - .stderr(predicates::str::contains("failed to load key from file")); + .stderr(predicates::str::contains("failed to parse private key")); } #[test] From a9ed198795c1f9c909aa8fcecd358f08feee7991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 13 Feb 2024 16:52:37 +0100 Subject: [PATCH 22/29] Expose signer's field `public_key` directly. --- kairos-cli/bin/crypto/signer.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs index dbfc0f79..91ff99e7 100644 --- a/kairos-cli/bin/crypto/signer.rs +++ b/kairos-cli/bin/crypto/signer.rs @@ -8,7 +8,7 @@ use casper_types::{crypto, PublicKey}; pub struct CasperSigner { secret_key: CasperPrivateKey, - public_key: CasperPublicKey, + pub public_key: CasperPublicKey, } #[allow(unused)] @@ -25,10 +25,6 @@ impl CasperSigner { }) } - pub fn get_public_key(&self) -> CasperPublicKey { - self.public_key.clone() - } - pub fn sign_message(&self, message: &[u8]) -> Result, CryptoError> { let signature = crypto::sign(message, &self.secret_key.0, &self.public_key.0); let bytes = signature From 8eb6e69ceb954775226f7fe429d5fb6a698e036b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 13 Feb 2024 16:55:54 +0100 Subject: [PATCH 23/29] Remove `CasperPublicKey::from_key()`. It can be constructed directly via public field. --- kairos-cli/bin/crypto/public_key.rs | 4 ---- kairos-cli/bin/crypto/signer.rs | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/kairos-cli/bin/crypto/public_key.rs b/kairos-cli/bin/crypto/public_key.rs index 42824d0f..f3a10045 100644 --- a/kairos-cli/bin/crypto/public_key.rs +++ b/kairos-cli/bin/crypto/public_key.rs @@ -15,10 +15,6 @@ impl CasperPublicKey { Ok(Self(public_key)) } - pub fn from_key(public_key: casper_types::PublicKey) -> Self { - Self(public_key) - } - #[allow(unused)] fn to_bytes(&self) -> Result, CryptoError> { self.0.to_bytes().map_err(|_e| CryptoError::Serialization { diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/bin/crypto/signer.rs index 91ff99e7..de35e0a8 100644 --- a/kairos-cli/bin/crypto/signer.rs +++ b/kairos-cli/bin/crypto/signer.rs @@ -17,7 +17,7 @@ impl CasperSigner { let secret_key = CasperPrivateKey::from_file(file_path)?; // Derive the public key. - let public_key = CasperPublicKey::from_key(PublicKey::from(&secret_key.0)); + let public_key = CasperPublicKey(PublicKey::from(&secret_key.0)); Ok(CasperSigner { secret_key, From 55879c2437d5cdd9d92164fdd66f6f8d934fbfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 13 Feb 2024 16:57:03 +0100 Subject: [PATCH 24/29] Remove outdated TODO. --- kairos-cli/bin/error.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/kairos-cli/bin/error.rs b/kairos-cli/bin/error.rs index 3d68c6ed..9338ee2b 100644 --- a/kairos-cli/bin/error.rs +++ b/kairos-cli/bin/error.rs @@ -11,7 +11,6 @@ pub enum CliError { #[from] error: CryptoError, }, - // TODO: Add error for "Failed to parse hex string: {}" /// Failed to parse hex string. #[error("failed to parse hex string: {error}")] ParseError { From 560645a34432520f2f6a529948ad29b2b1e40773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 13 Feb 2024 18:11:25 +0100 Subject: [PATCH 25/29] Moved core logic into `src` directory. Better separation with executable at `./bin/main.rs`. --- kairos-cli/bin/main.rs | 8 +------- kairos-cli/{bin => src}/commands/deposit.rs | 0 kairos-cli/{bin => src}/commands/mod.rs | 0 kairos-cli/{bin => src}/commands/transfer.rs | 0 kairos-cli/{bin => src}/commands/withdraw.rs | 0 kairos-cli/{bin => src}/common/args.rs | 0 kairos-cli/{bin => src}/common/mod.rs | 0 kairos-cli/{bin => src}/crypto/error.rs | 0 kairos-cli/{bin => src}/crypto/mod.rs | 0 kairos-cli/{bin => src}/crypto/private_key.rs | 0 kairos-cli/{bin => src}/crypto/public_key.rs | 0 kairos-cli/{bin => src}/crypto/signer.rs | 0 kairos-cli/{bin => src}/error.rs | 0 kairos-cli/src/lib.rs | 5 +++++ kairos-cli/{bin => src}/utils.rs | 0 15 files changed, 6 insertions(+), 7 deletions(-) rename kairos-cli/{bin => src}/commands/deposit.rs (100%) rename kairos-cli/{bin => src}/commands/mod.rs (100%) rename kairos-cli/{bin => src}/commands/transfer.rs (100%) rename kairos-cli/{bin => src}/commands/withdraw.rs (100%) rename kairos-cli/{bin => src}/common/args.rs (100%) rename kairos-cli/{bin => src}/common/mod.rs (100%) rename kairos-cli/{bin => src}/crypto/error.rs (100%) rename kairos-cli/{bin => src}/crypto/mod.rs (100%) rename kairos-cli/{bin => src}/crypto/private_key.rs (100%) rename kairos-cli/{bin => src}/crypto/public_key.rs (100%) rename kairos-cli/{bin => src}/crypto/signer.rs (100%) rename kairos-cli/{bin => src}/error.rs (100%) create mode 100644 kairos-cli/src/lib.rs rename kairos-cli/{bin => src}/utils.rs (100%) diff --git a/kairos-cli/bin/main.rs b/kairos-cli/bin/main.rs index 79b20880..0de33073 100644 --- a/kairos-cli/bin/main.rs +++ b/kairos-cli/bin/main.rs @@ -1,13 +1,7 @@ -mod commands; -mod common; -mod crypto; -mod error; -mod utils; - use std::process; use clap::Parser; -use commands::Command; +use kairos_cli::commands::{self, Command}; #[derive(Parser)] #[command(name = "Kairos Client", about = "CLI for interacting with Kairos")] diff --git a/kairos-cli/bin/commands/deposit.rs b/kairos-cli/src/commands/deposit.rs similarity index 100% rename from kairos-cli/bin/commands/deposit.rs rename to kairos-cli/src/commands/deposit.rs diff --git a/kairos-cli/bin/commands/mod.rs b/kairos-cli/src/commands/mod.rs similarity index 100% rename from kairos-cli/bin/commands/mod.rs rename to kairos-cli/src/commands/mod.rs diff --git a/kairos-cli/bin/commands/transfer.rs b/kairos-cli/src/commands/transfer.rs similarity index 100% rename from kairos-cli/bin/commands/transfer.rs rename to kairos-cli/src/commands/transfer.rs diff --git a/kairos-cli/bin/commands/withdraw.rs b/kairos-cli/src/commands/withdraw.rs similarity index 100% rename from kairos-cli/bin/commands/withdraw.rs rename to kairos-cli/src/commands/withdraw.rs diff --git a/kairos-cli/bin/common/args.rs b/kairos-cli/src/common/args.rs similarity index 100% rename from kairos-cli/bin/common/args.rs rename to kairos-cli/src/common/args.rs diff --git a/kairos-cli/bin/common/mod.rs b/kairos-cli/src/common/mod.rs similarity index 100% rename from kairos-cli/bin/common/mod.rs rename to kairos-cli/src/common/mod.rs diff --git a/kairos-cli/bin/crypto/error.rs b/kairos-cli/src/crypto/error.rs similarity index 100% rename from kairos-cli/bin/crypto/error.rs rename to kairos-cli/src/crypto/error.rs diff --git a/kairos-cli/bin/crypto/mod.rs b/kairos-cli/src/crypto/mod.rs similarity index 100% rename from kairos-cli/bin/crypto/mod.rs rename to kairos-cli/src/crypto/mod.rs diff --git a/kairos-cli/bin/crypto/private_key.rs b/kairos-cli/src/crypto/private_key.rs similarity index 100% rename from kairos-cli/bin/crypto/private_key.rs rename to kairos-cli/src/crypto/private_key.rs diff --git a/kairos-cli/bin/crypto/public_key.rs b/kairos-cli/src/crypto/public_key.rs similarity index 100% rename from kairos-cli/bin/crypto/public_key.rs rename to kairos-cli/src/crypto/public_key.rs diff --git a/kairos-cli/bin/crypto/signer.rs b/kairos-cli/src/crypto/signer.rs similarity index 100% rename from kairos-cli/bin/crypto/signer.rs rename to kairos-cli/src/crypto/signer.rs diff --git a/kairos-cli/bin/error.rs b/kairos-cli/src/error.rs similarity index 100% rename from kairos-cli/bin/error.rs rename to kairos-cli/src/error.rs diff --git a/kairos-cli/src/lib.rs b/kairos-cli/src/lib.rs new file mode 100644 index 00000000..86f168d4 --- /dev/null +++ b/kairos-cli/src/lib.rs @@ -0,0 +1,5 @@ +pub mod commands; +pub mod common; +pub mod crypto; +pub mod error; +pub mod utils; diff --git a/kairos-cli/bin/utils.rs b/kairos-cli/src/utils.rs similarity index 100% rename from kairos-cli/bin/utils.rs rename to kairos-cli/src/utils.rs From 455872d272361548a91d8f162b86867ecd760a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Fri, 16 Feb 2024 13:53:22 +0100 Subject: [PATCH 26/29] Return unwrapped `ErrorExt` in crypto constructors. More precise errors for caller, with the cost of manual casting into `CryptoError` in handlers. --- kairos-cli/src/commands/deposit.rs | 4 +++- kairos-cli/src/commands/transfer.rs | 4 +++- kairos-cli/src/commands/withdraw.rs | 4 +++- kairos-cli/src/crypto/private_key.rs | 8 +++----- kairos-cli/src/crypto/signer.rs | 4 ++-- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/kairos-cli/src/commands/deposit.rs b/kairos-cli/src/commands/deposit.rs index d4e87d11..a90650ff 100644 --- a/kairos-cli/src/commands/deposit.rs +++ b/kairos-cli/src/commands/deposit.rs @@ -1,4 +1,5 @@ use crate::common::args::{AmountArg, PrivateKeyPathArg}; +use crate::crypto::error::CryptoError; use crate::crypto::signer::CasperSigner; use crate::error::CliError; @@ -14,7 +15,8 @@ pub struct Args { pub fn run(args: Args) -> Result { let _amount: u64 = args.amount.field; - let _signer = CasperSigner::from_file(args.private_key_path.field)?; + let _signer = + CasperSigner::from_file(args.private_key_path.field).map_err(CryptoError::from)?; // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/src/commands/transfer.rs b/kairos-cli/src/commands/transfer.rs index a5519f1a..508057eb 100644 --- a/kairos-cli/src/commands/transfer.rs +++ b/kairos-cli/src/commands/transfer.rs @@ -1,4 +1,5 @@ use crate::common::args::{AmountArg, PrivateKeyPathArg}; +use crate::crypto::error::CryptoError; use crate::crypto::public_key::CasperPublicKey; use crate::crypto::signer::CasperSigner; use crate::error::CliError; @@ -19,7 +20,8 @@ pub struct Args { pub fn run(args: Args) -> Result { let _recipient = CasperPublicKey::from_bytes(args.recipient.as_ref())?; let _amount: u64 = args.amount.field; - let _signer = CasperSigner::from_file(args.private_key_path.field)?; + let _signer = + CasperSigner::from_file(args.private_key_path.field).map_err(CryptoError::from)?; // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/src/commands/withdraw.rs b/kairos-cli/src/commands/withdraw.rs index 5c4023f5..45a6b130 100644 --- a/kairos-cli/src/commands/withdraw.rs +++ b/kairos-cli/src/commands/withdraw.rs @@ -1,4 +1,5 @@ use crate::common::args::{AmountArg, PrivateKeyPathArg}; +use crate::crypto::error::CryptoError; use crate::crypto::signer::CasperSigner; use crate::error::CliError; @@ -14,7 +15,8 @@ pub struct Args { pub fn run(args: Args) -> Result { let _amount: u64 = args.amount.field; - let _signer = CasperSigner::from_file(args.private_key_path.field)?; + let _signer = + CasperSigner::from_file(args.private_key_path.field).map_err(CryptoError::from)?; // TODO: Create transaction and sign it with `signer`. diff --git a/kairos-cli/src/crypto/private_key.rs b/kairos-cli/src/crypto/private_key.rs index ece70b67..9cfd7902 100644 --- a/kairos-cli/src/crypto/private_key.rs +++ b/kairos-cli/src/crypto/private_key.rs @@ -1,13 +1,11 @@ use std::path::Path; -use super::error::CryptoError; +use casper_types::ErrorExt; pub struct CasperPrivateKey(pub casper_types::SecretKey); impl CasperPrivateKey { - pub fn from_file>(file_path: P) -> Result { - casper_types::SecretKey::from_file(file_path) - .map(Self) - .map_err(|error| error.into()) + pub fn from_file>(file_path: P) -> Result { + casper_types::SecretKey::from_file(file_path).map(Self) } } diff --git a/kairos-cli/src/crypto/signer.rs b/kairos-cli/src/crypto/signer.rs index de35e0a8..7ce62e5f 100644 --- a/kairos-cli/src/crypto/signer.rs +++ b/kairos-cli/src/crypto/signer.rs @@ -4,7 +4,7 @@ use super::private_key::CasperPrivateKey; use super::public_key::CasperPublicKey; use crate::crypto::error::CryptoError; use casper_types::bytesrepr::ToBytes; -use casper_types::{crypto, PublicKey}; +use casper_types::{crypto, ErrorExt, PublicKey}; pub struct CasperSigner { secret_key: CasperPrivateKey, @@ -13,7 +13,7 @@ pub struct CasperSigner { #[allow(unused)] impl CasperSigner { - pub fn from_file>(file_path: P) -> Result { + pub fn from_file>(file_path: P) -> Result { let secret_key = CasperPrivateKey::from_file(file_path)?; // Derive the public key. From 3775a71005d2f99a8e9dbe9d158ea16c052abab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Thu, 29 Feb 2024 11:47:02 +0100 Subject: [PATCH 27/29] Add tests for parsing Casper public keys. Public keys were taken directly from: - https://cspr.live/account/01c377281132044bd3278b039925eeb3efdb9d99dd5f46d9ec6a764add34581af7 - https://cspr.live/account/0202e99759649fa63a72c685b72e696b30c90f1deabb02d0d9b1de45eb371a73e5bb --- kairos-cli/src/crypto/public_key.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/kairos-cli/src/crypto/public_key.rs b/kairos-cli/src/crypto/public_key.rs index f3a10045..07b356d0 100644 --- a/kairos-cli/src/crypto/public_key.rs +++ b/kairos-cli/src/crypto/public_key.rs @@ -22,3 +22,32 @@ impl CasperPublicKey { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_casper_ed25519_public_key() { + // This public key has a 01 prefix indicating Ed25519. + let bytes = hex::decode("01c377281132044bd3278b039925eeb3efdb9d99dd5f46d9ec6a764add34581af7").unwrap(); + let result = CasperPublicKey::from_bytes(&bytes); + assert!(result.is_ok(), "Ed25519 public key should be parsed correctly"); + } + + #[test] + fn test_casper_secp256k1_public_key() { + // This public key has a 02 prefix indicating Secp256k1. + let bytes = hex::decode("0202e99759649fa63a72c685b72e696b30c90f1deabb02d0d9b1de45eb371a73e5bb").unwrap(); + let result = CasperPublicKey::from_bytes(&bytes); + assert!(result.is_ok(), "Secp256k1 public key should be parsed correctly"); + } + + #[test] + fn test_casper_unrecognized_prefix() { + // Using a 99 prefix which is not recognized. + let bytes = hex::decode("99c377281132044bd3278b039925eeb3efdb9d99dd5f46d9ec6a764add34581af7").unwrap(); + let result = CasperPublicKey::from_bytes(&bytes); + assert!(result.is_err(), "Unrecognized prefix should result in an error"); + } +} From 1eb91d42cd0d0dbff4e191b8f855e68a1ba57f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Mon, 4 Mar 2024 12:56:18 +0100 Subject: [PATCH 28/29] Fix formatting with `cargo fmt`. --- kairos-cli/src/crypto/public_key.rs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/kairos-cli/src/crypto/public_key.rs b/kairos-cli/src/crypto/public_key.rs index 07b356d0..fa85808f 100644 --- a/kairos-cli/src/crypto/public_key.rs +++ b/kairos-cli/src/crypto/public_key.rs @@ -30,24 +30,39 @@ mod tests { #[test] fn test_casper_ed25519_public_key() { // This public key has a 01 prefix indicating Ed25519. - let bytes = hex::decode("01c377281132044bd3278b039925eeb3efdb9d99dd5f46d9ec6a764add34581af7").unwrap(); + let bytes = + hex::decode("01c377281132044bd3278b039925eeb3efdb9d99dd5f46d9ec6a764add34581af7") + .unwrap(); let result = CasperPublicKey::from_bytes(&bytes); - assert!(result.is_ok(), "Ed25519 public key should be parsed correctly"); + assert!( + result.is_ok(), + "Ed25519 public key should be parsed correctly" + ); } #[test] fn test_casper_secp256k1_public_key() { // This public key has a 02 prefix indicating Secp256k1. - let bytes = hex::decode("0202e99759649fa63a72c685b72e696b30c90f1deabb02d0d9b1de45eb371a73e5bb").unwrap(); + let bytes = + hex::decode("0202e99759649fa63a72c685b72e696b30c90f1deabb02d0d9b1de45eb371a73e5bb") + .unwrap(); let result = CasperPublicKey::from_bytes(&bytes); - assert!(result.is_ok(), "Secp256k1 public key should be parsed correctly"); + assert!( + result.is_ok(), + "Secp256k1 public key should be parsed correctly" + ); } #[test] fn test_casper_unrecognized_prefix() { // Using a 99 prefix which is not recognized. - let bytes = hex::decode("99c377281132044bd3278b039925eeb3efdb9d99dd5f46d9ec6a764add34581af7").unwrap(); + let bytes = + hex::decode("99c377281132044bd3278b039925eeb3efdb9d99dd5f46d9ec6a764add34581af7") + .unwrap(); let result = CasperPublicKey::from_bytes(&bytes); - assert!(result.is_err(), "Unrecognized prefix should result in an error"); + assert!( + result.is_err(), + "Unrecognized prefix should result in an error" + ); } } From 2a3771802399a457f53bd5851a5b39edccdd9a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20Bro=C5=84ski?= Date: Tue, 5 Mar 2024 15:19:25 +0100 Subject: [PATCH 29/29] Make serialization error context more specific. Now it is possible to differentiate serialization and deserialization error. --- kairos-cli/src/crypto/public_key.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kairos-cli/src/crypto/public_key.rs b/kairos-cli/src/crypto/public_key.rs index fa85808f..9bf0d571 100644 --- a/kairos-cli/src/crypto/public_key.rs +++ b/kairos-cli/src/crypto/public_key.rs @@ -9,7 +9,7 @@ impl CasperPublicKey { let (public_key, _remainder) = casper_types::PublicKey::from_bytes(bytes).map_err(|_e| { CryptoError::Serialization { - context: "public key", + context: "public key serialization", } })?; Ok(Self(public_key)) @@ -18,7 +18,7 @@ impl CasperPublicKey { #[allow(unused)] fn to_bytes(&self) -> Result, CryptoError> { self.0.to_bytes().map_err(|_e| CryptoError::Serialization { - context: "public key", + context: "public key deserialization", }) } }