Skip to content

Commit

Permalink
Merge pull request #17 from cspr-rad/feature/base-cli-parsing
Browse files Browse the repository at this point in the history
Base CLI parsing
  • Loading branch information
koxu1996 authored Mar 15, 2024
2 parents 2ef83df + 2a37718 commit 948f15a
Show file tree
Hide file tree
Showing 24 changed files with 1,188 additions and 11 deletions.
746 changes: 737 additions & 9 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 ];

Expand Down
8 changes: 8 additions & 0 deletions kairos-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@ 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 = { 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"
30 changes: 29 additions & 1 deletion kairos-cli/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
use std::process;

use clap::Parser;
use kairos_cli::commands::{self, Command};

#[derive(Parser)]
#[command(name = "Kairos Client", about = "CLI for interacting with Kairos")]
struct Cli {
#[command(subcommand)]
command: Command,
}

fn main() {
println!("Hello, world!");
let cli = Cli::parse();

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 {
Ok(output) => {
println!("{}", output)
}
Err(error) => {
eprintln!("{}", error);
process::exit(1);
}
}
}
26 changes: 26 additions & 0 deletions kairos-cli/src/commands/deposit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::common::args::{AmountArg, PrivateKeyPathArg};
use crate::crypto::error::CryptoError;
use crate::crypto::signer::CasperSigner;
use crate::error::CliError;

use clap::Parser;

#[derive(Parser, Debug)]
pub struct Args {
#[clap(flatten)]
amount: AmountArg,
#[clap(flatten)]
private_key_path: PrivateKeyPathArg,
}

pub fn run(args: Args) -> Result<String, CliError> {
let _amount: u64 = args.amount.field;
let _signer =
CasperSigner::from_file(args.private_key_path.field).map_err(CryptoError::from)?;

// TODO: Create transaction and sign it with `signer`.

// TODO: Send transaction to the network, using Rust SDK.

Ok("ok".to_string())
}
15 changes: 15 additions & 0 deletions kairos-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pub mod deposit;
pub mod transfer;
pub mod 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),
}
31 changes: 31 additions & 0 deletions kairos-cli/src/commands/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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;
use crate::utils::parse_hex_string;

use clap::Parser;

#[derive(Parser)]
pub struct Args {
#[arg(long, short, value_name = "PUBLIC_KEY", value_parser = parse_hex_string)]
recipient: ::std::vec::Vec<u8>, // Absolute path is required here - see https://github.com/clap-rs/clap/issues/4626#issue-1528622454.
#[clap(flatten)]
amount: AmountArg,
#[clap(flatten)]
private_key_path: PrivateKeyPathArg,
}

pub fn run(args: Args) -> Result<String, CliError> {
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).map_err(CryptoError::from)?;

// TODO: Create transaction and sign it with `signer`.

// TODO: Send transaction to the network, using Rust SDK.

Ok("ok".to_string())
}
26 changes: 26 additions & 0 deletions kairos-cli/src/commands/withdraw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::common::args::{AmountArg, PrivateKeyPathArg};
use crate::crypto::error::CryptoError;
use crate::crypto::signer::CasperSigner;
use crate::error::CliError;

use clap::Parser;

#[derive(Parser)]
pub struct Args {
#[clap(flatten)]
amount: AmountArg,
#[clap(flatten)]
private_key_path: PrivateKeyPathArg,
}

pub fn run(args: Args) -> Result<String, CliError> {
let _amount: u64 = args.amount.field;
let _signer =
CasperSigner::from_file(args.private_key_path.field).map_err(CryptoError::from)?;

// TODO: Create transaction and sign it with `signer`.

// TODO: Send transaction to the network, using Rust SDK.

Ok("ok".to_string())
}
15 changes: 15 additions & 0 deletions kairos-cli/src/common/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use std::path::PathBuf;

use clap::Args;

#[derive(Args, Debug)]
pub struct AmountArg {
#[arg(name = "amount", long, short, value_name = "NUM_MOTES")]
pub field: u64,
}

#[derive(Args, Debug)]
pub struct PrivateKeyPathArg {
#[arg(name = "private-key", long, short = 'k', value_name = "FILE_PATH")]
pub field: PathBuf,
}
1 change: 1 addition & 0 deletions kairos-cli/src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod args;
15 changes: 15 additions & 0 deletions kairos-cli/src/crypto/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use casper_types::ErrorExt;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum CryptoError {
/// Failed to parse a public key from a raw data.
#[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 },
}
4 changes: 4 additions & 0 deletions kairos-cli/src/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod error;
pub mod private_key;
pub mod public_key;
pub mod signer;
11 changes: 11 additions & 0 deletions kairos-cli/src/crypto/private_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::path::Path;

use casper_types::ErrorExt;

pub struct CasperPrivateKey(pub casper_types::SecretKey);

impl CasperPrivateKey {
pub fn from_file<P: AsRef<Path>>(file_path: P) -> Result<Self, ErrorExt> {
casper_types::SecretKey::from_file(file_path).map(Self)
}
}
68 changes: 68 additions & 0 deletions kairos-cli/src/crypto/public_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::crypto::error::CryptoError;
use casper_types::bytesrepr::{FromBytes, ToBytes};

#[derive(Clone)]
pub struct CasperPublicKey(pub casper_types::PublicKey);

impl CasperPublicKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
let (public_key, _remainder) =
casper_types::PublicKey::from_bytes(bytes).map_err(|_e| {
CryptoError::Serialization {
context: "public key serialization",
}
})?;
Ok(Self(public_key))
}

#[allow(unused)]
fn to_bytes(&self) -> Result<Vec<u8>, CryptoError> {
self.0.to_bytes().map_err(|_e| CryptoError::Serialization {
context: "public key deserialization",
})
}
}

#[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"
);
}
}
38 changes: 38 additions & 0 deletions kairos-cli/src/crypto/signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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, ErrorExt, PublicKey};

pub struct CasperSigner {
secret_key: CasperPrivateKey,
pub public_key: CasperPublicKey,
}

#[allow(unused)]
impl CasperSigner {
pub fn from_file<P: AsRef<Path>>(file_path: P) -> Result<Self, ErrorExt> {
let secret_key = CasperPrivateKey::from_file(file_path)?;

// Derive the public key.
let public_key = CasperPublicKey(PublicKey::from(&secret_key.0));

Ok(CasperSigner {
secret_key,
public_key,
})
}

pub fn sign_message(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError> {
let signature = crypto::sign(message, &self.secret_key.0, &self.public_key.0);
let bytes = signature
.to_bytes()
.map_err(|_e| CryptoError::Serialization {
context: "signature",
})?;

Ok(bytes)
}
}
20 changes: 20 additions & 0 deletions kairos-cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use hex::FromHexError;
use thiserror::Error;

use crate::crypto::error::CryptoError;

#[derive(Error, Debug)]
pub enum CliError {
/// Cryptography error.
#[error("cryptography error: {error}")]
CryptoError {
#[from]
error: CryptoError,
},
/// Failed to parse hex string.
#[error("failed to parse hex string: {error}")]
ParseError {
#[from]
error: FromHexError,
},
}
5 changes: 5 additions & 0 deletions kairos-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod commands;
pub mod common;
pub mod crypto;
pub mod error;
pub mod utils;
6 changes: 6 additions & 0 deletions kairos-cli/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u8>, CliError> {
hex::decode(s).map_err(|e| e.into())
}
Loading

0 comments on commit 948f15a

Please sign in to comment.