diff --git a/Cargo.lock b/Cargo.lock index 1e244a81..edfd16a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1696,10 +1696,20 @@ dependencies = [ "casper-types 4.0.1", "clap", "hex", + "kairos-crypto", "predicates", "thiserror", ] +[[package]] +name = "kairos-crypto" +version = "0.1.0" +dependencies = [ + "casper-types 4.0.1", + "hex", + "thiserror", +] + [[package]] name = "kairos-server" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 6d28d1fd..9bdf86b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "kairos-cli", + "kairos-crypto", "kairos-server", "kairos-tx", "kairos-test-utils", diff --git a/kairos-cli/Cargo.toml b/kairos-cli/Cargo.toml index a7c381bd..df53204e 100644 --- a/kairos-cli/Cargo.toml +++ b/kairos-cli/Cargo.toml @@ -18,6 +18,7 @@ casper-types = { version = "4.0.1", features = ["std"] } # TODO: Change `std` -> clap = { version = "4.5", features = ["derive", "deprecated"] } hex = "0.4" thiserror = "1" +kairos-crypto = { path = "../kairos-crypto" } [dev-dependencies] assert_cmd = "2" diff --git a/kairos-cli/src/commands/deposit.rs b/kairos-cli/src/commands/deposit.rs index a90650ff..b4b95c99 100644 --- a/kairos-cli/src/commands/deposit.rs +++ b/kairos-cli/src/commands/deposit.rs @@ -1,8 +1,10 @@ use crate::common::args::{AmountArg, PrivateKeyPathArg}; -use crate::crypto::error::CryptoError; -use crate::crypto::signer::CasperSigner; use crate::error::CliError; +use kairos_crypto::error::CryptoError; +use kairos_crypto::implementations::Signer; +use kairos_crypto::CryptoSigner; + use clap::Parser; #[derive(Parser, Debug)] @@ -16,7 +18,7 @@ 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).map_err(CryptoError::from)?; + Signer::from_private_key_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 508057eb..d45cd954 100644 --- a/kairos-cli/src/commands/transfer.rs +++ b/kairos-cli/src/commands/transfer.rs @@ -1,10 +1,11 @@ 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 kairos_crypto::error::CryptoError; +use kairos_crypto::implementations::Signer; +use kairos_crypto::CryptoSigner; + use clap::Parser; #[derive(Parser)] @@ -18,10 +19,10 @@ pub struct Args { } pub fn run(args: Args) -> Result { - let _recipient = CasperPublicKey::from_bytes(args.recipient.as_ref())?; + let _recipient = Signer::from_public_key(args.recipient)?.to_public_key()?; let _amount: u64 = args.amount.field; let _signer = - CasperSigner::from_file(args.private_key_path.field).map_err(CryptoError::from)?; + Signer::from_private_key_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 45a6b130..13490eb2 100644 --- a/kairos-cli/src/commands/withdraw.rs +++ b/kairos-cli/src/commands/withdraw.rs @@ -1,8 +1,10 @@ use crate::common::args::{AmountArg, PrivateKeyPathArg}; -use crate::crypto::error::CryptoError; -use crate::crypto::signer::CasperSigner; use crate::error::CliError; +use kairos_crypto::error::CryptoError; +use kairos_crypto::implementations::Signer; +use kairos_crypto::CryptoSigner; + use clap::Parser; #[derive(Parser)] @@ -16,7 +18,7 @@ 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).map_err(CryptoError::from)?; + Signer::from_private_key_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/error.rs b/kairos-cli/src/crypto/error.rs deleted file mode 100644 index 789afa5d..00000000 --- a/kairos-cli/src/crypto/error.rs +++ /dev/null @@ -1,15 +0,0 @@ -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 }, -} diff --git a/kairos-cli/src/crypto/mod.rs b/kairos-cli/src/crypto/mod.rs deleted file mode 100644 index b68afd01..00000000 --- a/kairos-cli/src/crypto/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod error; -pub mod private_key; -pub mod public_key; -pub mod signer; diff --git a/kairos-cli/src/crypto/private_key.rs b/kairos-cli/src/crypto/private_key.rs deleted file mode 100644 index 9cfd7902..00000000 --- a/kairos-cli/src/crypto/private_key.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::path::Path; - -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) - } -} diff --git a/kairos-cli/src/crypto/public_key.rs b/kairos-cli/src/crypto/public_key.rs deleted file mode 100644 index 9bf0d571..00000000 --- a/kairos-cli/src/crypto/public_key.rs +++ /dev/null @@ -1,68 +0,0 @@ -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 { - 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, 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" - ); - } -} diff --git a/kairos-cli/src/crypto/signer.rs b/kairos-cli/src/crypto/signer.rs deleted file mode 100644 index 7ce62e5f..00000000 --- a/kairos-cli/src/crypto/signer.rs +++ /dev/null @@ -1,38 +0,0 @@ -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>(file_path: P) -> Result { - 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, 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) - } -} diff --git a/kairos-cli/src/error.rs b/kairos-cli/src/error.rs index 9338ee2b..19558e20 100644 --- a/kairos-cli/src/error.rs +++ b/kairos-cli/src/error.rs @@ -1,7 +1,7 @@ use hex::FromHexError; use thiserror::Error; -use crate::crypto::error::CryptoError; +use kairos_crypto::error::CryptoError; #[derive(Error, Debug)] pub enum CliError { diff --git a/kairos-cli/src/lib.rs b/kairos-cli/src/lib.rs index 86f168d4..80647025 100644 --- a/kairos-cli/src/lib.rs +++ b/kairos-cli/src/lib.rs @@ -1,5 +1,4 @@ pub mod commands; pub mod common; -pub mod crypto; pub mod error; pub mod utils; diff --git a/kairos-crypto/Cargo.toml b/kairos-crypto/Cargo.toml new file mode 100644 index 00000000..e7b26536 --- /dev/null +++ b/kairos-crypto/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "kairos-crypto" +version.workspace = true +edition.workspace = true + +[features] +default = ["crypto-casper"] +crypto-casper = ["casper-types"] + +[lib] + +[dependencies] +hex = "0.4" +thiserror = "1" + +# Casper signer implementation. +casper-types = { version = "4", optional = true, features = ["std"] } # TODO: Change `std` -> `std-fs-io` in the future version. diff --git a/kairos-crypto/src/error.rs b/kairos-crypto/src/error.rs new file mode 100644 index 00000000..885720af --- /dev/null +++ b/kairos-crypto/src/error.rs @@ -0,0 +1,20 @@ +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 { error: String }, + /// Encoding related error. + #[error("failed to serialize '{context}'")] + Serialization { context: &'static str }, + /// Invalid public key (hexdigest) or other decoding related error. + #[error("failed to deserialize '{context}'")] + Deserialization { context: &'static str }, + /// Signature verification failure. + #[error("signature verification failed")] + InvalidSignature, + /// Private key is not provided. + #[error("private key is not provided")] + MissingPrivateKey, +} diff --git a/kairos-crypto/src/implementations/casper.rs b/kairos-crypto/src/implementations/casper.rs new file mode 100644 index 00000000..9b0fcc57 --- /dev/null +++ b/kairos-crypto/src/implementations/casper.rs @@ -0,0 +1,132 @@ +use casper_types::bytesrepr::{FromBytes, ToBytes}; +use casper_types::{crypto, PublicKey, SecretKey, Signature}; +use std::path::Path; + +use crate::CryptoError; +use crate::CryptoSigner; + +pub struct Signer { + private_key: Option, + public_key: PublicKey, +} + +impl CryptoSigner for Signer { + fn from_private_key_file>(file: P) -> Result + where + Self: Sized, + { + let private_key = + SecretKey::from_file(file).map_err(|e| CryptoError::FailedToParseKey { + error: e.to_string(), + })?; + let public_key = PublicKey::from(&private_key); + + Ok(Self { + private_key: Some(private_key), + public_key, + }) + } + + fn from_public_key>(bytes: T) -> Result + where + Self: Sized, + { + let (public_key, _remainder) = casper_types::PublicKey::from_bytes(bytes.as_ref()) + .map_err(|_e| CryptoError::Deserialization { + context: "public key", + })?; + + Ok(Self { + private_key: None, + public_key, + }) + } + + fn sign>(&self, data: T) -> Result, CryptoError> { + let private_key = self + .private_key + .as_ref() + .ok_or(CryptoError::MissingPrivateKey)?; + let signature = crypto::sign(data, private_key, &self.public_key); + let signature_bytes = signature + .to_bytes() + .map_err(|_e| CryptoError::Serialization { + context: "signature", + })?; + + Ok(signature_bytes) + } + + fn verify, U: AsRef<[u8]>>( + &self, + data: T, + signature_bytes: U, + ) -> Result<(), CryptoError> { + let (signature, _remainder) = + Signature::from_bytes(signature_bytes.as_ref()).map_err(|_e| { + CryptoError::Deserialization { + context: "signature", + } + })?; + crypto::verify(data, &signature, &self.public_key) + .map_err(|_e| CryptoError::InvalidSignature)?; + + Ok(()) + } + + fn to_public_key(&self) -> Result, CryptoError> { + let public_key = + self.public_key + .clone() + .into_bytes() + .map_err(|_e| CryptoError::Serialization { + context: "public_key", + })?; + + Ok(public_key) + } +} + +#[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 = Signer::from_public_key(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 = Signer::from_public_key(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 = Signer::from_public_key(bytes); + assert!( + result.is_err(), + "Unrecognized prefix should result in an error" + ); + } +} diff --git a/kairos-crypto/src/implementations/mod.rs b/kairos-crypto/src/implementations/mod.rs new file mode 100644 index 00000000..c2e0d82e --- /dev/null +++ b/kairos-crypto/src/implementations/mod.rs @@ -0,0 +1,9 @@ +mod casper; + +#[cfg(feature = "crypto-casper")] +pub type Signer = casper::Signer; + +// Alternative crypto implementations can be provided, for example: +// +//#[cfg(feature = "crypto-kairos-a")] +//pub type Signer = kairos_a::Signer; diff --git a/kairos-crypto/src/lib.rs b/kairos-crypto/src/lib.rs new file mode 100644 index 00000000..a8f31aeb --- /dev/null +++ b/kairos-crypto/src/lib.rs @@ -0,0 +1,24 @@ +pub mod error; +pub mod implementations; + +use std::path::Path; + +use error::CryptoError; + +pub trait CryptoSigner { + fn from_private_key_file>(file: P) -> Result + where + Self: Sized; + fn from_public_key>(bytes: T) -> Result + where + Self: Sized; + + fn sign>(&self, data: T) -> Result, CryptoError>; + fn verify, U: AsRef<[u8]>>( + &self, + data: T, + signature_bytes: U, + ) -> Result<(), CryptoError>; + + fn to_public_key(&self) -> Result, CryptoError>; +}