-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Base CLI parsing #17
Base CLI parsing #17
Changes from 28 commits
2b44345
22add88
9bc033c
41ebd06
eb04979
a39b16f
37109b0
1543637
479a52b
9bc4a71
caed7b8
db60f27
70d4d9b
4e869d2
e874746
c91cc30
0d154b7
0bc00c6
13b80c9
489deb1
1881742
a9ed198
8eb6e69
55879c2
560645a
455872d
3775a71
1eb91d4
2a37718
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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); | ||
} | ||
} | ||
} |
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()) | ||
} |
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), | ||
} |
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())?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So... do we have a test that public keys on https://cspr.live/ parse using this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests added in 3775a71. |
||
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()) | ||
} |
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()) | ||
} |
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, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod args; |
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 }, | ||
} |
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; |
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) | ||
} | ||
} |
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> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t make this part of the inherent implementation, just impl the FromBytes trait if you want to do this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
let (public_key, _remainder) = | ||
casper_types::PublicKey::from_bytes(bytes).map_err(|_e| { | ||
CryptoError::Serialization { | ||
context: "public key", | ||
koxu1996 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
})?; | ||
Ok(Self(public_key)) | ||
} | ||
|
||
#[allow(unused)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just implement the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... but if you aren't using it, do we need it for anything? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not yet. However, it will be useful once we have signature verification implemented - #7. |
||
fn to_bytes(&self) -> Result<Vec<u8>, CryptoError> { | ||
self.0.to_bytes().map_err(|_e| CryptoError::Serialization { | ||
context: "public key", | ||
koxu1996 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
} | ||
} | ||
|
||
#[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" | ||
); | ||
} | ||
} |
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, | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to have a public key here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Public key is necessary for signing data with Casper signer: pub fn sign<T: AsRef<[u8]>>(
message: T,
secret_key: &SecretKey,
public_key: &PublicKey
) -> Signature Since we have user's private key, we can derive public key - we could do that every time There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh... don't use that signer, it has an unused public key for some reason. Use this instead: pub fn sign<T: AsRef<[u8]>>(secret_key: &SecretKey, message: T) -> Signature {
match secret_key {
SecretKey::System => Signature::System,
SecretKey::Ed25519(secret_key) => {
let signature = secret_key.sign(message.as_ref());
Signature::Ed25519(signature)
}
SecretKey::Secp256k1(secret_key) => {
let signature = secret_key
.try_sign(message.as_ref())
.expect("should create signature");
Signature::Secp256k1(signature)
}
_ => panic!("SecretKey is marked as non-exhaustive, but this should never happen"),
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need public key anyway (for signature verification), so I think there is no reason to reimplement Casper's |
||||
} | ||||
|
||||
#[allow(unused)] | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this used though? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not used, because we have no signature verification yet:
This is about to come soon - once we establish transaction format (I am currently exploring protobuffs and ASN.1+DER). |
||||
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)); | ||||
quinn-dougherty marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
||||
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) | ||||
} | ||||
} |
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, | ||
}, | ||
} |
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; |
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()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we make a custom signer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to isolate Casper code, to make it easily replaceable in the future. I imagine we might have
Signer
trait and blockchain specific implementations for it.