From dfe6c912734da5ed57ad406e7ae8a02bbdb53f84 Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Tue, 13 May 2025 11:45:06 +0200 Subject: [PATCH 01/11] Fix build issues Signed-off-by: Lee Smet --- src/rhai/vault.rs | 50 +++++++-------- src/vault/ethereum/wallet.rs | 57 +++++++++-------- src/vault/keyspace/keypair_types.rs | 96 +++++++++++++++++------------ src/vault/kvs/store.rs | 2 +- 4 files changed, 115 insertions(+), 90 deletions(-) diff --git a/src/rhai/vault.rs b/src/rhai/vault.rs index 42f68d4..2286afa 100644 --- a/src/rhai/vault.rs +++ b/src/rhai/vault.rs @@ -12,7 +12,7 @@ use std::sync::Mutex; use tokio::runtime::Runtime; use crate::vault::ethereum::contract_utils::{convert_token_to_rhai, prepare_function_arguments}; -use crate::vault::{ethereum, keypair}; +use crate::vault::{ethereum, keyspace}; use crate::vault::symmetric::implementation as symmetric_impl; // Global Tokio runtime for blocking async operations @@ -73,7 +73,7 @@ fn load_key_space(name: &str, password: &str) -> bool { }; // Set as current space - match keypair::set_current_space(space) { + match keyspace::set_current_space(space) { Ok(_) => true, Err(e) => { log::error!("Error setting current space: {}", e); @@ -83,10 +83,10 @@ fn load_key_space(name: &str, password: &str) -> bool { } fn create_key_space(name: &str, password: &str) -> bool { - match keypair::session_manager::create_space(name) { + match keyspace::session_manager::create_space(name) { Ok(_) => { // Get the current space - match keypair::get_current_space() { + match keyspace::get_current_space() { Ok(space) => { // Encrypt the key space let encrypted_space = match symmetric_impl::encrypt_key_space(&space, password) @@ -151,7 +151,7 @@ fn create_key_space(name: &str, password: &str) -> bool { // Auto-save function for internal use fn auto_save_key_space(password: &str) -> bool { - match keypair::get_current_space() { + match keyspace::get_current_space() { Ok(space) => { // Encrypt the key space let encrypted_space = match symmetric_impl::encrypt_key_space(&space, password) { @@ -207,7 +207,7 @@ fn auto_save_key_space(password: &str) -> bool { } fn encrypt_key_space(password: &str) -> String { - match keypair::get_current_space() { + match keyspace::get_current_space() { Ok(space) => match symmetric_impl::encrypt_key_space(&space, password) { Ok(encrypted_space) => match serde_json::to_string(&encrypted_space) { Ok(json) => json, @@ -232,7 +232,7 @@ fn decrypt_key_space(encrypted: &str, password: &str) -> bool { match serde_json::from_str(encrypted) { Ok(encrypted_space) => { match symmetric_impl::decrypt_key_space(&encrypted_space, password) { - Ok(space) => match keypair::set_current_space(space) { + Ok(space) => match keyspace::set_current_space(space) { Ok(_) => true, Err(e) => { log::error!("Error setting current space: {}", e); @@ -252,35 +252,35 @@ fn decrypt_key_space(encrypted: &str, password: &str) -> bool { } } -// Keypair management functions -fn create_keypair(name: &str, password: &str) -> bool { - match keypair::create_keypair(name) { +// keyspace management functions +fn create_keyspace(name: &str, password: &str) -> bool { + match keyspace::create_keypair(name) { Ok(_) => { - // Auto-save the key space after creating a keypair + // Auto-save the key space after creating a keyspace auto_save_key_space(password) } Err(e) => { - log::error!("Error creating keypair: {}", e); + log::error!("Error creating keyspace: {}", e); false } } } -fn select_keypair(name: &str) -> bool { - match keypair::select_keypair(name) { +fn select_keyspace(name: &str) -> bool { + match keyspace::select_keypair(name) { Ok(_) => true, Err(e) => { - log::error!("Error selecting keypair: {}", e); + log::error!("Error selecting keyspace: {}", e); false } } } -fn list_keypairs() -> Vec { - match keypair::list_keypairs() { - Ok(keypairs) => keypairs, +fn list_keyspaces() -> Vec { + match keyspace::list_keypairs() { + Ok(keyspaces) => keyspaces, Err(e) => { - log::error!("Error listing keypairs: {}", e); + log::error!("Error listing keyspaces: {}", e); Vec::new() } } @@ -289,7 +289,7 @@ fn list_keypairs() -> Vec { // Cryptographic operations fn sign(message: &str) -> String { let message_bytes = message.as_bytes(); - match keypair::keypair_sign(message_bytes) { + match keyspace::keypair_sign(message_bytes) { Ok(signature) => BASE64.encode(signature), Err(e) => { log::error!("Error signing message: {}", e); @@ -301,7 +301,7 @@ fn sign(message: &str) -> String { fn verify(message: &str, signature: &str) -> bool { let message_bytes = message.as_bytes(); match BASE64.decode(signature) { - Ok(signature_bytes) => match keypair::keypair_verify(message_bytes, &signature_bytes) { + Ok(signature_bytes) => match keyspace::keypair_verify(message_bytes, &signature_bytes) { Ok(is_valid) => is_valid, Err(e) => { log::error!("Error verifying signature: {}", e); @@ -881,10 +881,10 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box Result { // Get the private key bytes from the keypair let private_key_bytes = keypair.signing_key.to_bytes(); - + // Convert to a hex string (without 0x prefix) let private_key_hex = hex::encode(private_key_bytes); - + // Create an Ethereum wallet from the private key let wallet = LocalWallet::from_str(&private_key_hex) .map_err(|_e| CryptoError::InvalidKeyLength)? .with_chain_id(network.chain_id); - + // Get the Ethereum address let address = wallet.address(); - + Ok(EthereumWallet { address, wallet, network, }) } - + /// Creates a new Ethereum wallet from a name and keypair (deterministic derivation) for a specific network. - pub fn from_name_and_keypair(name: &str, keypair: &KeyPair, network: NetworkConfig) -> Result { + pub fn from_name_and_keypair( + name: &str, + keypair: &KeyPair, + network: NetworkConfig, + ) -> Result { // Get the private key bytes from the keypair let private_key_bytes = keypair.signing_key.to_bytes(); - + // Create a deterministic seed by combining name and private key let mut hasher = Sha256::default(); hasher.update(name.as_bytes()); hasher.update(&private_key_bytes); let seed = hasher.finalize(); - + // Use the seed as a private key let private_key_hex = hex::encode(seed); - + // Create an Ethereum wallet from the derived private key let wallet = LocalWallet::from_str(&private_key_hex) .map_err(|_e| CryptoError::InvalidKeyLength)? .with_chain_id(network.chain_id); - + // Get the Ethereum address let address = wallet.address(); - + Ok(EthereumWallet { address, wallet, network, }) } - + /// Creates a new Ethereum wallet from a private key for a specific network. - pub fn from_private_key(private_key: &str, network: NetworkConfig) -> Result { + pub fn from_private_key( + private_key: &str, + network: NetworkConfig, + ) -> Result { // Remove 0x prefix if present let private_key_clean = private_key.trim_start_matches("0x"); - + // Create an Ethereum wallet from the private key let wallet = LocalWallet::from_str(private_key_clean) .map_err(|_e| CryptoError::InvalidKeyLength)? .with_chain_id(network.chain_id); - + // Get the Ethereum address let address = wallet.address(); - + Ok(EthereumWallet { address, wallet, network, }) } - + /// Gets the Ethereum address as a string. pub fn address_string(&self) -> String { format!("{:?}", self.address) } - + /// Signs a message with the Ethereum wallet. pub async fn sign_message(&self, message: &[u8]) -> Result { - let signature = self.wallet.sign_message(message) + let signature = self + .wallet + .sign_message(message) .await .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; - + Ok(signature.to_string()) } - + /// Gets the private key as a hex string. pub fn private_key_hex(&self) -> String { let bytes = self.wallet.signer().to_bytes(); diff --git a/src/vault/keyspace/keypair_types.rs b/src/vault/keyspace/keypair_types.rs index 5dc174b..4f3fe1c 100644 --- a/src/vault/keyspace/keypair_types.rs +++ b/src/vault/keyspace/keypair_types.rs @@ -1,14 +1,16 @@ -/// Implementation of keypair functionality. - -use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature}; use k256::ecdh::EphemeralSecret; +/// Implementation of keypair functionality. +use k256::ecdsa::{ + signature::{Signer, Verifier}, + Signature, SigningKey, VerifyingKey, +}; use rand::rngs::OsRng; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use std::collections::HashMap; -use sha2::{Sha256, Digest}; -use crate::vault::symmetric::implementation; use crate::vault::error::CryptoError; +use crate::vault::symmetric::implementation; /// A keypair for signing and verifying messages. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -23,8 +25,8 @@ pub struct KeyPair { // Serialization helpers for VerifyingKey mod verifying_key_serde { use super::*; - use serde::{Serializer, Deserializer}; use serde::de::{self, Visitor}; + use serde::{Deserializer, Serializer}; use std::fmt; pub fn serialize(key: &VerifyingKey, serializer: S) -> Result @@ -64,7 +66,7 @@ mod verifying_key_serde { while let Some(byte) = seq.next_element()? { bytes.push(byte); } - + VerifyingKey::from_sec1_bytes(&bytes).map_err(|e| { log::error!("Error deserializing verifying key from seq: {:?}", e); de::Error::custom(format!("invalid verifying key from seq: {:?}", e)) @@ -84,8 +86,8 @@ mod verifying_key_serde { // Serialization helpers for SigningKey mod signing_key_serde { use super::*; - use serde::{Serializer, Deserializer}; use serde::de::{self, Visitor}; + use serde::{Deserializer, Serializer}; use std::fmt; pub fn serialize(key: &SigningKey, serializer: S) -> Result @@ -125,7 +127,7 @@ mod signing_key_serde { while let Some(byte) = seq.next_element()? { bytes.push(byte); } - + SigningKey::from_bytes(bytes.as_slice().into()).map_err(|e| { log::error!("Error deserializing signing key from seq: {:?}", e); de::Error::custom(format!("invalid signing key from seq: {:?}", e)) @@ -147,7 +149,7 @@ impl KeyPair { pub fn new(name: &str) -> Self { let signing_key = SigningKey::random(&mut OsRng); let verifying_key = VerifyingKey::from(&signing_key); - + KeyPair { name: name.to_string(), verifying_key, @@ -159,7 +161,7 @@ impl KeyPair { pub fn pub_key(&self) -> Vec { self.verifying_key.to_sec1_bytes().to_vec() } - + /// Derives a public key from a private key. pub fn pub_key_from_private(private_key: &[u8]) -> Result, CryptoError> { let signing_key = SigningKey::from_bytes(private_key.into()) @@ -178,27 +180,31 @@ impl KeyPair { pub fn verify(&self, message: &[u8], signature_bytes: &[u8]) -> Result { let signature = Signature::from_bytes(signature_bytes.into()) .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; - + match self.verifying_key.verify(message, &signature) { Ok(_) => Ok(true), Err(_) => Ok(false), // Verification failed, but operation was successful } } - + /// Verifies a message signature using only a public key. - pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result { - let verifying_key = VerifyingKey::from_sec1_bytes(public_key) - .map_err(|_| CryptoError::InvalidKeyLength)?; - + pub fn verify_with_public_key( + public_key: &[u8], + message: &[u8], + signature_bytes: &[u8], + ) -> Result { + let verifying_key = + VerifyingKey::from_sec1_bytes(public_key).map_err(|_| CryptoError::InvalidKeyLength)?; + let signature = Signature::from_bytes(signature_bytes.into()) .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; - + match verifying_key.verify(message, &signature) { Ok(_) => Ok(true), Err(_) => Ok(false), // Verification failed, but operation was successful } } - + /// Encrypts a message using the recipient's public key. /// This implements ECIES (Elliptic Curve Integrated Encryption Scheme): /// 1. Generate an ephemeral keypair @@ -206,19 +212,23 @@ impl KeyPair { /// 3. Derive encryption key from the shared secret /// 4. Encrypt the message using symmetric encryption /// 5. Return the ephemeral public key and the ciphertext - pub fn encrypt_asymmetric(&self, recipient_public_key: &[u8], message: &[u8]) -> Result, CryptoError> { + pub fn encrypt_asymmetric( + &self, + recipient_public_key: &[u8], + message: &[u8], + ) -> Result, CryptoError> { // Parse recipient's public key let recipient_key = VerifyingKey::from_sec1_bytes(recipient_public_key) .map_err(|_| CryptoError::InvalidKeyLength)?; - + // Generate ephemeral keypair let ephemeral_signing_key = SigningKey::random(&mut OsRng); let ephemeral_public_key = VerifyingKey::from(&ephemeral_signing_key); - + // Derive shared secret using ECDH let ephemeral_secret = EphemeralSecret::random(&mut OsRng); - let shared_secret = ephemeral_secret.diffie_hellman(&recipient_key.to_public_key()); - + let shared_secret = ephemeral_secret.diffie_hellman(&recipient_key.into()); + // Derive encryption key from the shared secret (e.g., using HKDF or hashing) // For simplicity, we'll hash the shared secret here let encryption_key = { @@ -226,46 +236,51 @@ impl KeyPair { hasher.update(shared_secret.raw_secret_bytes()); hasher.finalize().to_vec() }; - + // Encrypt the message using the derived key let ciphertext = implementation::encrypt_with_key(&encryption_key, message) .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?; - + // Format: ephemeral_public_key || ciphertext - let mut result = ephemeral_public_key.to_encoded_point(false).as_bytes().to_vec(); + let mut result = ephemeral_public_key + .to_encoded_point(false) + .as_bytes() + .to_vec(); result.extend_from_slice(&ciphertext); - + Ok(result) } - + /// Decrypts a message using the recipient's private key. /// This is the counterpart to encrypt_asymmetric. pub fn decrypt_asymmetric(&self, ciphertext: &[u8]) -> Result, CryptoError> { // The first 33 or 65 bytes (depending on compression) are the ephemeral public key // For simplicity, we'll assume uncompressed keys (65 bytes) if ciphertext.len() <= 65 { - return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string())); + return Err(CryptoError::DecryptionFailed( + "Ciphertext too short".to_string(), + )); } - + // Extract ephemeral public key and actual ciphertext let ephemeral_public_key = &ciphertext[..65]; let actual_ciphertext = &ciphertext[65..]; - + // Parse ephemeral public key let sender_key = VerifyingKey::from_sec1_bytes(ephemeral_public_key) .map_err(|_| CryptoError::InvalidKeyLength)?; - + // Derive shared secret using ECDH let recipient_secret = EphemeralSecret::random(&mut OsRng); - let shared_secret = recipient_secret.diffie_hellman(&sender_key.to_public_key()); - + let shared_secret = recipient_secret.diffie_hellman(&sender_key.into()); + // Derive decryption key from the shared secret (using the same method as encryption) let decryption_key = { let mut hasher = Sha256::default(); hasher.update(shared_secret.raw_secret_bytes()); hasher.finalize().to_vec() }; - + // Decrypt the message using the derived key implementation::decrypt_with_key(&decryption_key, actual_ciphertext) .map_err(|e| CryptoError::DecryptionFailed(e.to_string())) @@ -293,7 +308,7 @@ impl KeySpace { if self.keypairs.contains_key(name) { return Err(CryptoError::KeypairAlreadyExists(name.to_string())); } - + let keypair = KeyPair::new(name); self.keypairs.insert(name.to_string(), keypair); Ok(()) @@ -301,7 +316,9 @@ impl KeySpace { /// Gets a keypair by name. pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError> { - self.keypairs.get(name).ok_or(CryptoError::KeypairNotFound(name.to_string())) + self.keypairs + .get(name) + .ok_or(CryptoError::KeypairNotFound(name.to_string())) } /// Lists all keypair names in the space. @@ -309,4 +326,3 @@ impl KeySpace { self.keypairs.keys().cloned().collect() } } - diff --git a/src/vault/kvs/store.rs b/src/vault/kvs/store.rs index f30ab85..74c9c6f 100644 --- a/src/vault/kvs/store.rs +++ b/src/vault/kvs/store.rs @@ -355,7 +355,7 @@ impl KvStore { // Save to disk self.save()?; - Ok(()) + Ok(()) } /// Gets the name of the store. From e44ee83e74629cbe36534ffff663475302e8d846 Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Tue, 13 May 2025 17:37:33 +0200 Subject: [PATCH 02/11] Implement proper key types Signed-off-by: Lee Smet --- Cargo.toml | 23 +- vault/.cargo/config.toml | 2 + vault/Cargo.toml | 15 + vault/src/README.md | 160 ++++++++ vault/src/error.rs | 35 ++ vault/src/ethereum.bak/README.md | 160 ++++++++ vault/src/ethereum.bak/contract.rs | 179 +++++++++ vault/src/ethereum.bak/contract_utils.rs | 183 +++++++++ vault/src/ethereum.bak/mod.rs | 84 ++++ vault/src/ethereum.bak/networks.rs | 102 +++++ vault/src/ethereum.bak/provider.rs | 27 ++ vault/src/ethereum.bak/storage.rs | 114 ++++++ .../ethereum.bak/tests/contract_args_tests.rs | 47 +++ .../src/ethereum.bak/tests/contract_tests.rs | 83 ++++ vault/src/ethereum.bak/tests/mod.rs | 7 + vault/src/ethereum.bak/tests/network_tests.rs | 74 ++++ .../ethereum.bak/tests/transaction_tests.rs | 70 ++++ vault/src/ethereum.bak/tests/wallet_tests.rs | 143 +++++++ vault/src/ethereum.bak/transaction.rs | 54 +++ vault/src/ethereum.bak/wallet.rs | 123 ++++++ vault/src/key.rs | 83 ++++ vault/src/key/asymmetric.rs | 112 ++++++ vault/src/key/signature.rs | 76 ++++ vault/src/key/symmetric.rs | 86 ++++ vault/src/keyspace.bak/README.md | 271 +++++++++++++ vault/src/keyspace.bak/keypair_types.rs | 328 ++++++++++++++++ vault/src/keyspace.bak/mod.rs | 18 + vault/src/keyspace.bak/session_manager.rs | 174 ++++++++ vault/src/keyspace.bak/spec.md | 36 ++ .../keyspace.bak/tests/keypair_types_tests.rs | 86 ++++ vault/src/keyspace.bak/tests/mod.rs | 3 + .../tests/session_manager_tests.rs | 111 ++++++ vault/src/kvs.bak/README.md | 173 ++++++++ vault/src/kvs.bak/error.rs | 67 ++++ vault/src/kvs.bak/mod.rs | 17 + vault/src/kvs.bak/store.rs | 370 ++++++++++++++++++ vault/src/kvs.bak/tests/mod.rs | 1 + vault/src/kvs.bak/tests/store_tests.rs | 105 +++++ vault/src/kvs.rs | 3 + vault/src/lib.rs | 3 + vault/src/symmetric.bak/README.md | 98 +++++ vault/src/symmetric.bak/implementation.rs | 266 +++++++++++++ vault/src/symmetric.bak/mod.rs | 15 + 43 files changed, 4180 insertions(+), 7 deletions(-) create mode 100644 vault/.cargo/config.toml create mode 100644 vault/Cargo.toml create mode 100644 vault/src/README.md create mode 100644 vault/src/error.rs create mode 100644 vault/src/ethereum.bak/README.md create mode 100644 vault/src/ethereum.bak/contract.rs create mode 100644 vault/src/ethereum.bak/contract_utils.rs create mode 100644 vault/src/ethereum.bak/mod.rs create mode 100644 vault/src/ethereum.bak/networks.rs create mode 100644 vault/src/ethereum.bak/provider.rs create mode 100644 vault/src/ethereum.bak/storage.rs create mode 100644 vault/src/ethereum.bak/tests/contract_args_tests.rs create mode 100644 vault/src/ethereum.bak/tests/contract_tests.rs create mode 100644 vault/src/ethereum.bak/tests/mod.rs create mode 100644 vault/src/ethereum.bak/tests/network_tests.rs create mode 100644 vault/src/ethereum.bak/tests/transaction_tests.rs create mode 100644 vault/src/ethereum.bak/tests/wallet_tests.rs create mode 100644 vault/src/ethereum.bak/transaction.rs create mode 100644 vault/src/ethereum.bak/wallet.rs create mode 100644 vault/src/key.rs create mode 100644 vault/src/key/asymmetric.rs create mode 100644 vault/src/key/signature.rs create mode 100644 vault/src/key/symmetric.rs create mode 100644 vault/src/keyspace.bak/README.md create mode 100644 vault/src/keyspace.bak/keypair_types.rs create mode 100644 vault/src/keyspace.bak/mod.rs create mode 100644 vault/src/keyspace.bak/session_manager.rs create mode 100644 vault/src/keyspace.bak/spec.md create mode 100644 vault/src/keyspace.bak/tests/keypair_types_tests.rs create mode 100644 vault/src/keyspace.bak/tests/mod.rs create mode 100644 vault/src/keyspace.bak/tests/session_manager_tests.rs create mode 100644 vault/src/kvs.bak/README.md create mode 100644 vault/src/kvs.bak/error.rs create mode 100644 vault/src/kvs.bak/mod.rs create mode 100644 vault/src/kvs.bak/store.rs create mode 100644 vault/src/kvs.bak/tests/mod.rs create mode 100644 vault/src/kvs.bak/tests/store_tests.rs create mode 100644 vault/src/kvs.rs create mode 100644 vault/src/lib.rs create mode 100644 vault/src/symmetric.bak/README.md create mode 100644 vault/src/symmetric.bak/implementation.rs create mode 100644 vault/src/symmetric.bak/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 8d362cb..ba1cde8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,18 +10,24 @@ keywords = ["system", "os", "abstraction", "platform", "filesystem"] categories = ["os", "filesystem", "api-bindings"] readme = "README.md" +[workspace] +members = [".", "vault"] + [dependencies] anyhow = "1.0.98" -base64 = "0.22.1" # Base64 encoding/decoding +base64 = "0.22.1" # Base64 encoding/decoding cfg-if = "1.0" chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher clap = "2.34.0" # Command-line argument parsing -dirs = "6.0.0" # Directory paths +dirs = "6.0.0" # Directory paths env_logger = "0.11.8" # Logger implementation ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library glob = "0.3.1" # For file pattern matching jsonrpsee = "0.25.1" -k256 = { version = "0.13.4", features = ["ecdsa", "ecdh"] } # Elliptic curve cryptography +k256 = { version = "0.13.4", features = [ + "ecdsa", + "ecdh", +] } # Elliptic curve cryptography lazy_static = "1.4.0" # For lazy initialization of static variables libc = "0.2" log = "0.4" # Logging facade @@ -38,7 +44,7 @@ serde = { version = "1.0", features = [ "derive", ] } # For serialization/deserialization serde_json = "1.0" # For JSON handling -sha2 = "0.10.7" # SHA-2 hash functions +sha2 = "0.10.7" # SHA-2 hash functions tempfile = "3.5" # For temporary file operations tera = "1.19.0" # Template engine for text rendering thiserror = "2.0.12" # For error handling @@ -46,7 +52,7 @@ tokio = "1.45.0" tokio-postgres = "0.7.8" # Async PostgreSQL client tokio-test = "0.4.4" uuid = { version = "1.16.0", features = ["v4"] } -zinit-client = { git = "https://github.com/threefoldtech/zinit", branch = "json_rpc", package = "zinit-client" } +zinit-client = { git = "https://github.com/threefoldtech/zinit", branch = "json_rpc", package = "zinit-client" } # Optional features for specific OS functionality [target.'cfg(unix)'.dependencies] nix = "0.30.1" # Unix-specific functionality @@ -60,8 +66,11 @@ windows = { version = "0.61.1", features = [ [dev-dependencies] mockall = "0.13.1" # For mocking in tests -tempfile = "3.5" # For tests that need temporary files/directories -tokio = { version = "1.28", features = ["full", "test-util"] } # For async testing +tempfile = "3.5" # For tests that need temporary files/directories +tokio = { version = "1.28", features = [ + "full", + "test-util", +] } # For async testing [[bin]] name = "herodo" diff --git a/vault/.cargo/config.toml b/vault/.cargo/config.toml new file mode 100644 index 0000000..2e07606 --- /dev/null +++ b/vault/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] diff --git a/vault/Cargo.toml b/vault/Cargo.toml new file mode 100644 index 0000000..4033186 --- /dev/null +++ b/vault/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "vault" +version = "0.1.0" +edition = "2024" + +[dependencies] +getrandom = { version = "0.3.3", features = ["wasm_js"] } +rand = "0.9.1" +# We need to pull v0.2.x to enable the "js" feature for wasm32 builds +getrandom_old = { package = "getrandom", version = "0.2.16", features = ["js"] } +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +chacha20poly1305 = "0.10.1" +k256 = { version = "0.13.4", features = ["ecdh"] } +sha2 = "0.10.9" diff --git a/vault/src/README.md b/vault/src/README.md new file mode 100644 index 0000000..28a3f1b --- /dev/null +++ b/vault/src/README.md @@ -0,0 +1,160 @@ +# Hero Vault Cryptography Module + +The Hero Vault module provides comprehensive cryptographic functionality for the SAL project, including key management, digital signatures, symmetric encryption, Ethereum wallet operations, and a secure key-value store. + +## Module Structure + +The Hero Vault module is organized into several submodules: + +- `error.rs` - Error types for cryptographic operations +- `keypair/` - ECDSA keypair management functionality +- `symmetric/` - Symmetric encryption using ChaCha20Poly1305 +- `ethereum/` - Ethereum wallet and smart contract functionality +- `kvs/` - Encrypted key-value store + +## Key Features + +### Key Space Management + +The module provides functionality for creating, loading, and managing key spaces. A key space is a secure container for cryptographic keys, which can be encrypted and stored on disk. + +```rust +// Create a new key space +let space = KeySpace::new("my_space", "secure_password")?; + +// Save the key space to disk +space.save()?; + +// Load a key space from disk +let loaded_space = KeySpace::load("my_space", "secure_password")?; +``` + +### Keypair Management + +The module provides functionality for creating, selecting, and using ECDSA keypairs for digital signatures. + +```rust +// Create a new keypair in the active key space +let keypair = space.create_keypair("my_keypair", "secure_password")?; + +// Select a keypair for use +space.select_keypair("my_keypair")?; + +// List all keypairs in the active key space +let keypairs = space.list_keypairs()?; +``` + +### Digital Signatures + +The module provides functionality for signing and verifying messages using ECDSA. + +```rust +// Sign a message using the selected keypair +let signature = space.sign("This is a message to sign")?; + +// Verify a signature +let is_valid = space.verify("This is a message to sign", &signature)?; +``` + +### Symmetric Encryption + +The module provides functionality for symmetric encryption using ChaCha20Poly1305. + +```rust +// Generate a new symmetric key +let key = space.generate_key()?; + +// Encrypt a message +let encrypted = space.encrypt(&key, "This is a secret message")?; + +// Decrypt a message +let decrypted = space.decrypt(&key, &encrypted)?; +``` + +### Ethereum Wallet Functionality + +The module provides comprehensive Ethereum wallet functionality, including: + +- Creating and managing wallets for different networks +- Sending ETH transactions +- Checking balances +- Interacting with smart contracts + +```rust +// Create an Ethereum wallet +let wallet = EthereumWallet::new(keypair)?; + +// Get the wallet address +let address = wallet.get_address()?; + +// Send ETH +let tx_hash = wallet.send_eth("0x1234...", "1000000000000000")?; + +// Check balance +let balance = wallet.get_balance("0x1234...")?; +``` + +### Smart Contract Interactions + +The module provides functionality for interacting with smart contracts on EVM-based blockchains. + +```rust +// Load a contract ABI +let contract = Contract::new(provider, "0x1234...", abi)?; + +// Call a read-only function +let result = contract.call_read("balanceOf", vec!["0x5678..."])?; + +// Call a write function +let tx_hash = contract.call_write("transfer", vec!["0x5678...", "1000"])?; +``` + +### Key-Value Store + +The module provides an encrypted key-value store for securely storing sensitive data. + +```rust +// Create a new store +let store = KvStore::new("my_store", "secure_password")?; + +// Set a value +store.set("api_key", "secret_api_key")?; + +// Get a value +let api_key = store.get("api_key")?; +``` + +## Error Handling + +The module uses a comprehensive error type (`CryptoError`) for handling errors that can occur during cryptographic operations: + +- `InvalidKeyLength` - Invalid key length +- `EncryptionFailed` - Encryption failed +- `DecryptionFailed` - Decryption failed +- `SignatureFormatError` - Signature format error +- `KeypairAlreadyExists` - Keypair already exists +- `KeypairNotFound` - Keypair not found +- `NoActiveSpace` - No active key space +- `NoKeypairSelected` - No keypair selected +- `SerializationError` - Serialization error +- `InvalidAddress` - Invalid address format +- `ContractError` - Smart contract error + +## Ethereum Networks + +The module supports multiple Ethereum networks, including: + +- Gnosis Chain +- Peaq Network +- Agung Network + +## Security Considerations + +- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password +- Private keys are never stored in plaintext +- The module uses secure random number generation for key creation +- All cryptographic operations use well-established libraries and algorithms + +## Examples + +For examples of how to use the Hero Vault module, see the `examples/hero_vault` directory. diff --git a/vault/src/error.rs b/vault/src/error.rs new file mode 100644 index 0000000..24311b5 --- /dev/null +++ b/vault/src/error.rs @@ -0,0 +1,35 @@ +#[derive(Debug)] +/// Errors generated by the vault or keys. +/// +/// These errors are intentionally vague to avoid issues such as padding oracles. +pub enum CryptoError { + /// Key size is not valid for this type of key + InvalidKeySize, + /// Something went wrong while trying to encrypt data + EncryptionFailed, + /// Something went wrong while trying to decrypt data + DecryptionFailed, + /// Something went wrong while trying to sign a message + SigningError, + /// The signature is invalid for this message and public key + SignatureFailed, + /// The signature does not have the expected size + InvalidSignatureSize, +} + +impl core::fmt::Display for CryptoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CryptoError::InvalidKeySize => f.write_str("provided key is not the correct size"), + CryptoError::EncryptionFailed => f.write_str("encryption failure"), + CryptoError::DecryptionFailed => f.write_str("decryption failure"), + CryptoError::SigningError => f.write_str("signature generation failure"), + CryptoError::SignatureFailed => f.write_str("signature verification failure"), + CryptoError::InvalidSignatureSize => { + f.write_str("provided signature does not have the expected size") + } + } + } +} + +impl core::error::Error for CryptoError {} diff --git a/vault/src/ethereum.bak/README.md b/vault/src/ethereum.bak/README.md new file mode 100644 index 0000000..34fb2dd --- /dev/null +++ b/vault/src/ethereum.bak/README.md @@ -0,0 +1,160 @@ +# Hero Vault Ethereum Module + +The Ethereum module provides functionality for creating and managing Ethereum wallets and interacting with smart contracts on EVM-based blockchains. + +## Module Structure + +The Ethereum module is organized into several components: + +- `wallet.rs` - Core Ethereum wallet implementation +- `networks.rs` - Network registry and configuration +- `provider.rs` - Provider creation and management +- `transaction.rs` - Transaction-related functionality +- `storage.rs` - Wallet storage functionality +- `contract.rs` - Smart contract interaction functionality +- `contract_utils.rs` - Utilities for contract interactions + +## Key Features + +### Wallet Management + +The module provides functionality for creating and managing Ethereum wallets: + +```rust +// Create a new Ethereum wallet for a specific network +let wallet = create_ethereum_wallet_for_network("Ethereum")?; + +// Create a wallet for specific networks +let peaq_wallet = create_peaq_wallet()?; +let agung_wallet = create_agung_wallet()?; + +// Create a wallet with a specific name +let named_wallet = create_ethereum_wallet_from_name_for_network("my_wallet", "Gnosis")?; + +// Create a wallet from a private key +let imported_wallet = create_ethereum_wallet_from_private_key("0x...")?; + +// Get the current wallet for a network +let current_wallet = get_current_ethereum_wallet_for_network("Ethereum")?; + +// Clear wallets +clear_ethereum_wallets()?; +clear_ethereum_wallets_for_network("Gnosis")?; +``` + +### Network Management + +The module supports multiple Ethereum networks and provides functionality for managing network configurations: + +```rust +// Get a network configuration by name +let network = get_network_by_name("Ethereum")?; + +// Get the proper network name (normalized) +let name = get_proper_network_name("eth")?; // Returns "Ethereum" + +// List all available network names +let networks = list_network_names()?; + +// Get all network configurations +let all_networks = get_all_networks()?; +``` + +### Provider Management + +The module provides functionality for creating and managing Ethereum providers: + +```rust +// Create a provider for a specific network +let provider = create_provider("Ethereum")?; + +// Create providers for specific networks +let gnosis_provider = create_gnosis_provider()?; +let peaq_provider = create_peaq_provider()?; +let agung_provider = create_agung_provider()?; +``` + +### Transaction Management + +The module provides functionality for managing Ethereum transactions: + +```rust +// Get the balance of an address +let balance = get_balance("Ethereum", "0x...")?; + +// Send ETH to an address +let tx_hash = send_eth("Ethereum", "0x...", "1000000000000000")?; + +// Format a balance for display +let formatted = format_balance(balance, 18)?; // Convert wei to ETH +``` + +### Smart Contract Interactions + +The module provides functionality for interacting with smart contracts: + +```rust +// Load a contract ABI from JSON +let abi = load_abi_from_json(json_string)?; + +// Create a contract instance +let contract = Contract::new(provider, "0x...", abi)?; + +// Call a read-only function +let result = call_read_function(contract, "balanceOf", vec!["0x..."])?; + +// Call a write function +let tx_hash = call_write_function(contract, "transfer", vec!["0x...", "1000"])?; + +// Estimate gas for a function call +let gas = estimate_gas(contract, "transfer", vec!["0x...", "1000"])?; +``` + +### Contract Utilities + +The module provides utilities for working with contract function arguments and return values: + +```rust +// Convert Rhai values to Ethereum tokens +let token = convert_rhai_to_token(value)?; + +// Prepare function arguments +let args = prepare_function_arguments(function, vec![arg1, arg2])?; + +// Convert Ethereum tokens to Rhai values +let rhai_value = convert_token_to_rhai(token)?; + +// Convert a token to a dynamic value +let dynamic = token_to_dynamic(token)?; +``` + +## Supported Networks + +The module supports multiple Ethereum networks, including: + +- Gnosis Chain +- Peaq Network +- Agung Network + +Each network has its own configuration, including: + +- RPC URL +- Chain ID +- Explorer URL +- Native currency symbol and decimals + +## Error Handling + +The module uses the `CryptoError` type for handling errors that can occur during Ethereum operations: + +- `InvalidAddress` - Invalid Ethereum address format +- `ContractError` - Smart contract interaction error + +## Examples + +For examples of how to use the Ethereum module, see the `examples/hero_vault` directory, particularly: + +- `contract_example.rhai` - Demonstrates loading a contract ABI and interacting with smart contracts +- `agung_simple_transfer.rhai` - Shows how to perform a simple ETH transfer on the Agung network +- `agung_send_transaction.rhai` - Demonstrates sending transactions on the Agung network +- `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung diff --git a/vault/src/ethereum.bak/contract.rs b/vault/src/ethereum.bak/contract.rs new file mode 100644 index 0000000..5e8749e --- /dev/null +++ b/vault/src/ethereum.bak/contract.rs @@ -0,0 +1,179 @@ +//! Smart contract interaction functionality. +//! +//! This module provides functionality for interacting with smart contracts on EVM-based blockchains. + +use ethers::prelude::*; +use ethers::abi::{Abi, Token}; +use std::sync::Arc; +use std::str::FromStr; +use serde::{Serialize, Deserialize}; + +use crate::vault::error::CryptoError; +use super::wallet::EthereumWallet; +use super::networks::NetworkConfig; + +/// A smart contract instance. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Contract { + /// The contract address + pub address: Address, + /// The contract ABI + pub abi: Abi, + /// The network the contract is deployed on + pub network: NetworkConfig, +} + +impl Contract { + /// Creates a new contract instance. + pub fn new(address: Address, abi: Abi, network: NetworkConfig) -> Self { + Contract { + address, + abi, + network, + } + } + + /// Creates a new contract instance from an address string and ABI. + pub fn from_address_string(address_str: &str, abi: Abi, network: NetworkConfig) -> Result { + let address = Address::from_str(address_str) + .map_err(|e| CryptoError::InvalidAddress(format!("Invalid address format: {}", e)))?; + + Ok(Contract::new(address, abi, network)) + } + + /// Creates an ethers Contract instance for interaction. + pub fn create_ethers_contract(&self, provider: Provider, _wallet: Option<&EthereumWallet>) -> Result>, CryptoError> { + let contract = ethers::contract::Contract::new( + self.address, + self.abi.clone(), + Arc::new(provider), + ); + + Ok(contract) + } +} + +/// Loads a contract ABI from a JSON string. +pub fn load_abi_from_json(json_str: &str) -> Result { + serde_json::from_str(json_str) + .map_err(|e| CryptoError::SerializationError(format!("Failed to parse ABI JSON: {}", e))) +} + +/// Calls a read-only function on a contract. +pub async fn call_read_function( + contract: &Contract, + provider: &Provider, + function_name: &str, + args: Vec, +) -> Result, CryptoError> { + // Create the ethers contract (not used directly but kept for future extensions) + let _ethers_contract = contract.create_ethers_contract(provider.clone(), None)?; + + // Get the function from the ABI + let function = contract.abi.function(function_name) + .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; + + // Encode the function call + let call_data = function.encode_input(&args) + .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; + + // Make the call + let tx = TransactionRequest::new() + .to(contract.address) + .data(call_data); + + let result = provider.call(&tx.into(), None).await + .map_err(|e| CryptoError::ContractError(format!("Contract call failed: {}", e)))?; + + // Decode the result + let decoded = function.decode_output(&result) + .map_err(|e| CryptoError::ContractError(format!("Failed to decode function output: {}", e)))?; + + Ok(decoded) +} + +/// Executes a state-changing function on a contract. +pub async fn call_write_function( + contract: &Contract, + wallet: &EthereumWallet, + provider: &Provider, + function_name: &str, + args: Vec, +) -> Result { + // Create a client with the wallet + let client = SignerMiddleware::new( + provider.clone(), + wallet.wallet.clone(), + ); + + // Get the function from the ABI + let function = contract.abi.function(function_name) + .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; + + // Encode the function call + let call_data = function.encode_input(&args) + .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; + + // Create the transaction request with gas limit + let tx = TransactionRequest::new() + .to(contract.address) + .data(call_data) + .gas(U256::from(300000)); // Set a reasonable gas limit + + // Send the transaction using the client directly + log::info!("Sending transaction to contract at {}", contract.address); + log::info!("Function: {}, Args: {:?}", function_name, args); + + // Log detailed information about the transaction + log::debug!("Sending transaction to contract at {}", contract.address); + log::debug!("Function: {}, Args: {:?}", function_name, args); + log::debug!("From address: {}", wallet.address); + log::debug!("Gas limit: {:?}", tx.gas); + + let pending_tx = match client.send_transaction(tx, None).await { + Ok(pending_tx) => { + log::debug!("Transaction sent successfully: {:?}", pending_tx.tx_hash()); + log::info!("Transaction sent successfully: {:?}", pending_tx.tx_hash()); + pending_tx + }, + Err(e) => { + // Log the error for debugging + log::error!("Failed to send transaction: {}", e); + log::error!("ERROR DETAILS: {:?}", e); + return Err(CryptoError::ContractError(format!("Failed to send transaction: {}", e))); + } + }; + + // Return the transaction hash + Ok(pending_tx.tx_hash()) +} + +/// Estimates gas for a contract function call. +pub async fn estimate_gas( + contract: &Contract, + wallet: &EthereumWallet, + provider: &Provider, + function_name: &str, + args: Vec, +) -> Result { + // Get the function from the ABI + let function = contract.abi.function(function_name) + .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; + + // Encode the function call + let call_data = function.encode_input(&args) + .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; + + // Create the transaction request + let tx = TransactionRequest::new() + .from(wallet.address) + .to(contract.address) + .data(call_data); + + // Estimate gas + let gas = provider.estimate_gas(&tx.into(), None) + .await + .map_err(|e| CryptoError::ContractError(format!("Failed to estimate gas: {}", e)))?; + + Ok(gas) +} diff --git a/vault/src/ethereum.bak/contract_utils.rs b/vault/src/ethereum.bak/contract_utils.rs new file mode 100644 index 0000000..d40c23d --- /dev/null +++ b/vault/src/ethereum.bak/contract_utils.rs @@ -0,0 +1,183 @@ +//! Utility functions for smart contract interactions. + +use ethers::abi::{Abi, Token, ParamType}; +use ethers::types::{Address, U256}; +use std::str::FromStr; +use rhai::{Dynamic, Array}; + +/// Convert Rhai Dynamic values to ethers Token types +pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>) -> Result { + match value { + // Handle integers + v if v.is_int() => { + let i = v.as_int().unwrap(); + if let Some(param_type) = expected_type { + match param_type { + ParamType::Uint(_) => Ok(Token::Uint(U256::from(i as u64))), + ParamType::Int(_) => { + // Convert to I256 - in a real implementation, we would handle this properly + // For now, we'll just use U256 for both types + Ok(Token::Uint(U256::from(i as u64))) + }, + _ => Err(format!("Expected {}, got integer", param_type)) + } + } else { + // Default to Uint256 if no type info + Ok(Token::Uint(U256::from(i as u64))) + } + }, + + // Handle strings and addresses + v if v.is_string() => { + let s = v.to_string(); + if let Some(param_type) = expected_type { + match param_type { + ParamType::Address => { + match Address::from_str(&s) { + Ok(addr) => Ok(Token::Address(addr)), + Err(e) => Err(format!("Invalid address format: {}", e)) + } + }, + ParamType::String => Ok(Token::String(s)), + ParamType::Bytes => { + // Handle hex string conversion to bytes + if s.starts_with("0x") { + match ethers::utils::hex::decode(&s[2..]) { + Ok(bytes) => Ok(Token::Bytes(bytes)), + Err(e) => Err(format!("Invalid hex string: {}", e)) + } + } else { + Ok(Token::Bytes(s.as_bytes().to_vec())) + } + }, + _ => Err(format!("Expected {}, got string", param_type)) + } + } else { + // Try to detect type from string format + if s.starts_with("0x") && s.len() == 42 { + // Likely an address + match Address::from_str(&s) { + Ok(addr) => Ok(Token::Address(addr)), + Err(_) => Ok(Token::String(s)) + } + } else { + Ok(Token::String(s)) + } + } + }, + + // Handle booleans + v if v.is_bool() => { + let b = v.as_bool().unwrap(); + if let Some(param_type) = expected_type { + if matches!(param_type, ParamType::Bool) { + Ok(Token::Bool(b)) + } else { + Err(format!("Expected {}, got boolean", param_type)) + } + } else { + Ok(Token::Bool(b)) + } + }, + + // Handle arrays + v if v.is_array() => { + let arr = v.clone().into_array().unwrap(); + if let Some(ParamType::Array(inner_type)) = expected_type { + let mut tokens = Vec::new(); + for item in arr.iter() { + match convert_rhai_to_token(item, Some(inner_type)) { + Ok(token) => tokens.push(token), + Err(e) => return Err(e) + } + } + Ok(Token::Array(tokens)) + } else { + Err("Array type mismatch or no type information available".to_string()) + } + }, + + // Handle other types or return error + _ => Err(format!("Unsupported Rhai type: {:?}", value)) + } +} + +/// Validate and convert arguments based on function ABI +pub fn prepare_function_arguments( + abi: &Abi, + function_name: &str, + args: &Array +) -> Result, String> { + // Get the function from the ABI + let function = abi.function(function_name) + .map_err(|e| format!("Function not found in ABI: {}", e))?; + + // Check if number of arguments matches + if function.inputs.len() != args.len() { + return Err(format!( + "Wrong number of arguments for function '{}': expected {}, got {}", + function_name, function.inputs.len(), args.len() + )); + } + + // Convert each argument according to the expected type + let mut tokens = Vec::new(); + for (i, (param, arg)) in function.inputs.iter().zip(args.iter()).enumerate() { + match convert_rhai_to_token(arg, Some(¶m.kind)) { + Ok(token) => tokens.push(token), + Err(e) => return Err(format!("Error converting argument {}: {}", i, e)) + } + } + + Ok(tokens) +} + +/// Convert ethers Token to Rhai Dynamic value +pub fn convert_token_to_rhai(tokens: &[Token]) -> Dynamic { + if tokens.is_empty() { + return Dynamic::UNIT; + } + + // If there's only one return value, return it directly + if tokens.len() == 1 { + return token_to_dynamic(&tokens[0]); + } + + // If there are multiple return values, return them as an array + let mut array = Array::new(); + for token in tokens { + array.push(token_to_dynamic(token)); + } + Dynamic::from(array) +} + +/// Convert a single token to a Dynamic value +pub fn token_to_dynamic(token: &Token) -> Dynamic { + match token { + Token::Address(addr) => Dynamic::from(format!("{:?}", addr)), + Token::Bytes(bytes) => Dynamic::from(ethers::utils::hex::encode(bytes)), + Token::Int(i) => Dynamic::from(i.to_string()), + Token::Uint(u) => Dynamic::from(u.to_string()), + Token::Bool(b) => Dynamic::from(*b), + Token::String(s) => Dynamic::from(s.clone()), + Token::Array(arr) => { + let mut rhai_arr = Array::new(); + for item in arr { + rhai_arr.push(token_to_dynamic(item)); + } + Dynamic::from(rhai_arr) + }, + Token::Tuple(tuple) => { + let mut rhai_arr = Array::new(); + for item in tuple { + rhai_arr.push(token_to_dynamic(item)); + } + Dynamic::from(rhai_arr) + }, + // Handle other token types + _ => { + log::warn!("Unsupported token type: {:?}", token); + Dynamic::UNIT + } + } +} diff --git a/vault/src/ethereum.bak/mod.rs b/vault/src/ethereum.bak/mod.rs new file mode 100644 index 0000000..7ec8d32 --- /dev/null +++ b/vault/src/ethereum.bak/mod.rs @@ -0,0 +1,84 @@ +//! Ethereum wallet functionality +//! +//! This module provides functionality for creating and managing Ethereum wallets +//! and interacting with smart contracts on EVM-based blockchains. +//! +//! The module is organized into several components: +//! - `wallet.rs`: Core Ethereum wallet implementation +//! - `networks.rs`: Network registry and configuration +//! - `provider.rs`: Provider creation and management +//! - `transaction.rs`: Transaction-related functionality +//! - `storage.rs`: Wallet storage functionality +//! - `contract.rs`: Smart contract interaction functionality + +mod wallet; +mod provider; +mod transaction; +mod storage; +mod contract; +pub mod contract_utils; +pub mod networks; +// Re-export public types and functions +pub use wallet::EthereumWallet; +pub use networks::NetworkConfig; + +// Re-export wallet creation functions +pub use storage::{ + create_ethereum_wallet_for_network, + create_peaq_wallet, + create_agung_wallet, + create_ethereum_wallet_from_name_for_network, + create_ethereum_wallet_from_name, + create_ethereum_wallet_from_private_key_for_network, + create_ethereum_wallet_from_private_key, +}; + +// Re-export wallet management functions +pub use storage::{ + get_current_ethereum_wallet_for_network, + get_current_peaq_wallet, + get_current_agung_wallet, + clear_ethereum_wallets, + clear_ethereum_wallets_for_network, +}; + +// Re-export provider functions +pub use provider::{ + create_provider, + create_gnosis_provider, + create_peaq_provider, + create_agung_provider, +}; + +// Re-export transaction functions +pub use transaction::{ + get_balance, + send_eth, + format_balance, +}; + +// Re-export network registry functions +pub use networks::{ + get_network_by_name, + get_proper_network_name, + list_network_names, + get_all_networks, + names, +}; + +// Re-export contract functions +pub use contract::{ + Contract, + load_abi_from_json, + call_read_function, + call_write_function, + estimate_gas, +}; + +// Re-export contract utility functions +pub use contract_utils::{ + convert_rhai_to_token, + prepare_function_arguments, + convert_token_to_rhai, + token_to_dynamic, +}; diff --git a/vault/src/ethereum.bak/networks.rs b/vault/src/ethereum.bak/networks.rs new file mode 100644 index 0000000..4e81655 --- /dev/null +++ b/vault/src/ethereum.bak/networks.rs @@ -0,0 +1,102 @@ +//! Ethereum network registry +//! +//! This module provides a centralized registry of Ethereum networks and utilities +//! to work with them. + +use std::collections::HashMap; +use std::sync::OnceLock; +use serde::{Serialize, Deserialize}; + +/// Configuration for an EVM-compatible network +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetworkConfig { + pub name: String, + pub chain_id: u64, + pub rpc_url: String, + pub explorer_url: String, + pub token_symbol: String, + pub decimals: u8, +} + +/// Network name constants +pub mod names { + pub const GNOSIS: &str = "Gnosis"; + pub const PEAQ: &str = "Peaq"; + pub const AGUNG: &str = "Agung"; +} + +/// Get the Gnosis Chain network configuration +pub fn gnosis() -> NetworkConfig { + NetworkConfig { + name: names::GNOSIS.to_string(), + chain_id: 100, + rpc_url: "https://rpc.gnosischain.com".to_string(), + explorer_url: "https://gnosisscan.io".to_string(), + token_symbol: "xDAI".to_string(), + decimals: 18, + } +} + +/// Get the Peaq Network configuration +pub fn peaq() -> NetworkConfig { + NetworkConfig { + name: names::PEAQ.to_string(), + chain_id: 3338, + rpc_url: "https://peaq.api.onfinality.io/public".to_string(), + explorer_url: "https://peaq.subscan.io/".to_string(), + token_symbol: "PEAQ".to_string(), + decimals: 18, + } +} + +/// Get the Agung Testnet configuration +pub fn agung() -> NetworkConfig { + NetworkConfig { + name: names::AGUNG.to_string(), + chain_id: 9990, + rpc_url: "https://wss-async.agung.peaq.network".to_string(), + explorer_url: "https://agung-testnet.subscan.io/".to_string(), + token_symbol: "AGNG".to_string(), + decimals: 18, + } +} + +/// Get a network by its name (case-insensitive) +pub fn get_network_by_name(name: &str) -> Option { + let name_lower = name.to_lowercase(); + match name_lower.as_str() { + "gnosis" => Some(gnosis()), + "peaq" => Some(peaq()), + "agung" => Some(agung()), + _ => None, + } +} + +/// Get the proper capitalization of a network name +pub fn get_proper_network_name(name: &str) -> Option<&'static str> { + let name_lower = name.to_lowercase(); + match name_lower.as_str() { + "gnosis" => Some(names::GNOSIS), + "peaq" => Some(names::PEAQ), + "agung" => Some(names::AGUNG), + _ => None, + } +} + +/// Get a list of all supported network names +pub fn list_network_names() -> Vec<&'static str> { + vec![names::GNOSIS, names::PEAQ, names::AGUNG] +} + +/// Get a map of all networks +pub fn get_all_networks() -> &'static HashMap<&'static str, NetworkConfig> { + static NETWORKS: OnceLock> = OnceLock::new(); + + NETWORKS.get_or_init(|| { + let mut map = HashMap::new(); + map.insert(names::GNOSIS, gnosis()); + map.insert(names::PEAQ, peaq()); + map.insert(names::AGUNG, agung()); + map + }) +} diff --git a/vault/src/ethereum.bak/provider.rs b/vault/src/ethereum.bak/provider.rs new file mode 100644 index 0000000..145566a --- /dev/null +++ b/vault/src/ethereum.bak/provider.rs @@ -0,0 +1,27 @@ +//! Ethereum provider functionality. + +use ethers::prelude::*; + +use crate::vault::error::CryptoError; +use super::networks::{self, NetworkConfig}; + +/// Creates a provider for a specific network. +pub fn create_provider(network: &NetworkConfig) -> Result, CryptoError> { + Provider::::try_from(network.rpc_url.as_str()) + .map_err(|e| CryptoError::SerializationError(format!("Failed to create provider for {}: {}", network.name, e))) +} + +/// Creates a provider for the Gnosis Chain. +pub fn create_gnosis_provider() -> Result, CryptoError> { + create_provider(&networks::gnosis()) +} + +/// Creates a provider for the Peaq network. +pub fn create_peaq_provider() -> Result, CryptoError> { + create_provider(&networks::peaq()) +} + +/// Creates a provider for the Agung testnet. +pub fn create_agung_provider() -> Result, CryptoError> { + create_provider(&networks::agung()) +} diff --git a/vault/src/ethereum.bak/storage.rs b/vault/src/ethereum.bak/storage.rs new file mode 100644 index 0000000..e74fb26 --- /dev/null +++ b/vault/src/ethereum.bak/storage.rs @@ -0,0 +1,114 @@ +//! Ethereum wallet storage functionality. + +use std::sync::Mutex; +use std::collections::HashMap; +use once_cell::sync::Lazy; + +use crate::vault::error::CryptoError; +use super::wallet::EthereumWallet; +use super::networks::{self, NetworkConfig}; + +/// Global storage for Ethereum wallets. +static ETH_WALLETS: Lazy>>> = Lazy::new(|| { + Mutex::new(HashMap::new()) +}); + +/// Creates an Ethereum wallet from the currently selected keypair for a specific network. +pub fn create_ethereum_wallet_for_network(network: NetworkConfig) -> Result { + // Get the currently selected keypair + let keypair = crate::vault::keyspace::get_selected_keypair()?; + + // Create an Ethereum wallet from the keypair + let wallet = EthereumWallet::from_keypair(&keypair, network)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); + network_wallets.push(wallet.clone()); + + Ok(wallet) +} + +/// Creates an Ethereum wallet from the currently selected keypair for the Peaq network. +pub fn create_peaq_wallet() -> Result { + create_ethereum_wallet_for_network(networks::peaq()) +} + +/// Creates an Ethereum wallet from the currently selected keypair for the Agung testnet. +pub fn create_agung_wallet() -> Result { + create_ethereum_wallet_for_network(networks::agung()) +} + +/// Gets the current Ethereum wallet for a specific network. +pub fn get_current_ethereum_wallet_for_network(network_name: &str) -> Result { + let wallets = ETH_WALLETS.lock().unwrap(); + + let network_wallets = wallets.get(network_name).ok_or(CryptoError::NoKeypairSelected)?; + + if network_wallets.is_empty() { + return Err(CryptoError::NoKeypairSelected); + } + + Ok(network_wallets.last().unwrap().clone()) +} + +/// Gets the current Ethereum wallet for the Peaq network. +pub fn get_current_peaq_wallet() -> Result { + get_current_ethereum_wallet_for_network("Peaq") +} + +/// Gets the current Ethereum wallet for the Agung testnet. +pub fn get_current_agung_wallet() -> Result { + get_current_ethereum_wallet_for_network("Agung") +} + +/// Clears all Ethereum wallets. +pub fn clear_ethereum_wallets() { + let mut wallets = ETH_WALLETS.lock().unwrap(); + wallets.clear(); +} + +/// Clears Ethereum wallets for a specific network. +pub fn clear_ethereum_wallets_for_network(network_name: &str) { + let mut wallets = ETH_WALLETS.lock().unwrap(); + wallets.remove(network_name); +} + +/// Creates an Ethereum wallet from a name and the currently selected keypair for a specific network. +pub fn create_ethereum_wallet_from_name_for_network(name: &str, network: NetworkConfig) -> Result { + // Get the currently selected keypair + let keypair = crate::vault::keyspace::get_selected_keypair()?; + + // Create an Ethereum wallet from the name and keypair + let wallet = EthereumWallet::from_name_and_keypair(name, &keypair, network)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); + network_wallets.push(wallet.clone()); + + Ok(wallet) +} + +/// Creates an Ethereum wallet from a name and the currently selected keypair for the Gnosis network. +pub fn create_ethereum_wallet_from_name(name: &str) -> Result { + create_ethereum_wallet_from_name_for_network(name, networks::gnosis()) +} + +/// Creates an Ethereum wallet from a private key for a specific network. +pub fn create_ethereum_wallet_from_private_key_for_network(private_key: &str, network: NetworkConfig) -> Result { + // Create an Ethereum wallet from the private key + let wallet = EthereumWallet::from_private_key(private_key, network)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); + network_wallets.push(wallet.clone()); + + Ok(wallet) +} + +/// Creates an Ethereum wallet from a private key for the Gnosis network. +pub fn create_ethereum_wallet_from_private_key(private_key: &str) -> Result { + create_ethereum_wallet_from_private_key_for_network(private_key, networks::gnosis()) +} diff --git a/vault/src/ethereum.bak/tests/contract_args_tests.rs b/vault/src/ethereum.bak/tests/contract_args_tests.rs new file mode 100644 index 0000000..b7cb76a --- /dev/null +++ b/vault/src/ethereum.bak/tests/contract_args_tests.rs @@ -0,0 +1,47 @@ +//! Tests for smart contract argument handling functionality. + +use ethers::types::Address; +use std::str::FromStr; + +use crate::vault::ethereum::*; + +#[test] +fn test_contract_creation() { + // Create a simple ABI + let abi_json = r#"[ + { + "inputs": [], + "name": "getValue", + "outputs": [{"type": "uint256", "name": ""}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"type": "uint256", "name": "newValue"}], + "name": "setValue", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]"#; + + // Parse the ABI + let abi = load_abi_from_json(abi_json).unwrap(); + + // Create a contract address + let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap(); + + // Create a network config + let network = networks::gnosis(); + + // Create a contract + let contract = Contract::new(address, abi, network); + + // Verify the contract was created correctly + assert_eq!(contract.address, address); + assert_eq!(contract.network.name, "Gnosis"); + + // Verify the ABI contains the expected functions + assert!(contract.abi.function("getValue").is_ok()); + assert!(contract.abi.function("setValue").is_ok()); +} diff --git a/vault/src/ethereum.bak/tests/contract_tests.rs b/vault/src/ethereum.bak/tests/contract_tests.rs new file mode 100644 index 0000000..d37dde3 --- /dev/null +++ b/vault/src/ethereum.bak/tests/contract_tests.rs @@ -0,0 +1,83 @@ +//! Tests for smart contract functionality. + +use ethers::types::Address; +use std::str::FromStr; + +use crate::vault::ethereum::*; + +#[test] +fn test_contract_creation() { + // Create a simple ABI + let abi_json = r#"[ + { + "inputs": [], + "name": "getValue", + "outputs": [{"type": "uint256", "name": ""}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"type": "uint256", "name": "newValue"}], + "name": "setValue", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]"#; + + // Parse the ABI + let abi = load_abi_from_json(abi_json).unwrap(); + + // Create a contract address + let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap(); + + // Create a network config + let network = networks::gnosis(); + + // Create a contract + let contract = Contract::new(address, abi, network); + + // Verify the contract was created correctly + assert_eq!(contract.address, address); + assert_eq!(contract.network.name, "Gnosis"); + + // Verify the ABI contains the expected functions + assert!(contract.abi.function("getValue").is_ok()); + assert!(contract.abi.function("setValue").is_ok()); +} + +#[test] +fn test_contract_from_address_string() { + // Create a simple ABI + let abi_json = r#"[ + { + "inputs": [], + "name": "getValue", + "outputs": [{"type": "uint256", "name": ""}], + "stateMutability": "view", + "type": "function" + } + ]"#; + + // Parse the ABI + let abi = load_abi_from_json(abi_json).unwrap(); + + // Create a network config + let network = networks::gnosis(); + + // Create a contract from an address string + let address_str = "0x1234567890123456789012345678901234567890"; + let contract = Contract::from_address_string(address_str, abi, network).unwrap(); + + // Verify the contract was created correctly + assert_eq!(contract.address, Address::from_str(address_str).unwrap()); + + // Test with an invalid address + let invalid_address = "0xinvalid"; + let result = Contract::from_address_string(invalid_address, contract.abi.clone(), contract.network.clone()); + assert!(result.is_err()); +} + +// Note: We can't easily test the actual contract calls in unit tests without mocking +// the provider, which would be complex. These would be better tested in integration tests +// with a local blockchain or testnet. diff --git a/vault/src/ethereum.bak/tests/mod.rs b/vault/src/ethereum.bak/tests/mod.rs new file mode 100644 index 0000000..2c5c097 --- /dev/null +++ b/vault/src/ethereum.bak/tests/mod.rs @@ -0,0 +1,7 @@ +//! Tests for Ethereum functionality. + +mod wallet_tests; +mod network_tests; +mod transaction_tests; +mod contract_tests; +mod contract_args_tests; diff --git a/vault/src/ethereum.bak/tests/network_tests.rs b/vault/src/ethereum.bak/tests/network_tests.rs new file mode 100644 index 0000000..a66bd4a --- /dev/null +++ b/vault/src/ethereum.bak/tests/network_tests.rs @@ -0,0 +1,74 @@ +//! Tests for Ethereum network functionality. + +use crate::vault::ethereum::*; + +#[test] +fn test_network_config() { + let gnosis = networks::gnosis(); + assert_eq!(gnosis.name, "Gnosis"); + assert_eq!(gnosis.chain_id, 100); + assert_eq!(gnosis.token_symbol, "xDAI"); + + let peaq = networks::peaq(); + assert_eq!(peaq.name, "Peaq"); + assert_eq!(peaq.chain_id, 3338); + assert_eq!(peaq.token_symbol, "PEAQ"); + + let agung = networks::agung(); + assert_eq!(agung.name, "Agung"); + assert_eq!(agung.chain_id, 9990); + assert_eq!(agung.token_symbol, "AGNG"); +} + +#[test] +fn test_network_registry() { + let network_names = networks::list_network_names(); + assert!(network_names.iter().any(|&name| name == "Gnosis")); + assert!(network_names.iter().any(|&name| name == "Peaq")); + assert!(network_names.iter().any(|&name| name == "Agung")); + + let gnosis_proper = networks::get_proper_network_name("gnosis"); + assert_eq!(gnosis_proper, Some("Gnosis")); + + let peaq_proper = networks::get_proper_network_name("peaq"); + assert_eq!(peaq_proper, Some("Peaq")); + + let agung_proper = networks::get_proper_network_name("agung"); + assert_eq!(agung_proper, Some("Agung")); + + let unknown = networks::get_proper_network_name("unknown"); + assert_eq!(unknown, None); + + let gnosis_config = networks::get_network_by_name("Gnosis"); + assert!(gnosis_config.is_some()); + assert_eq!(gnosis_config.unwrap().chain_id, 100); + + let unknown_config = networks::get_network_by_name("Unknown"); + assert!(unknown_config.is_none()); +} + +#[test] +fn test_create_provider() { + let gnosis = networks::gnosis(); + let peaq = networks::peaq(); + let agung = networks::agung(); + + // Create providers + let gnosis_provider = create_provider(&gnosis); + let peaq_provider = create_provider(&peaq); + let agung_provider = create_provider(&agung); + + // They should all succeed + assert!(gnosis_provider.is_ok()); + assert!(peaq_provider.is_ok()); + assert!(agung_provider.is_ok()); + + // The convenience functions should also work + let gnosis_provider2 = create_gnosis_provider(); + let peaq_provider2 = create_peaq_provider(); + let agung_provider2 = create_agung_provider(); + + assert!(gnosis_provider2.is_ok()); + assert!(peaq_provider2.is_ok()); + assert!(agung_provider2.is_ok()); +} diff --git a/vault/src/ethereum.bak/tests/transaction_tests.rs b/vault/src/ethereum.bak/tests/transaction_tests.rs new file mode 100644 index 0000000..1fcc01c --- /dev/null +++ b/vault/src/ethereum.bak/tests/transaction_tests.rs @@ -0,0 +1,70 @@ +//! Tests for Ethereum transaction functionality. + +use crate::vault::ethereum::*; +use crate::vault::keypair::implementation::KeyPair; +use ethers::types::U256; +// use std::str::FromStr; + +#[test] +fn test_format_balance() { + let network = networks::gnosis(); + + // Test with 0 + let balance = U256::from(0); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "0.000000 xDAI"); + + // Test with 1 wei + let balance = U256::from(1); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "0.000000 xDAI"); + + // Test with 1 gwei (10^9 wei) + let balance = U256::from(1_000_000_000u64); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "0.000000 xDAI"); + + // Test with 1 ETH (10^18 wei) + let balance = U256::from_dec_str("1000000000000000000").unwrap(); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "1.000000 xDAI"); + + // Test with a larger amount + let balance = U256::from_dec_str("123456789000000000000").unwrap(); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "123.456789 xDAI"); +} + +#[test] +fn test_get_balance() { + // This is a mock test since we can't actually query the blockchain in a unit test + // In a real test, we would use a local blockchain or mock the provider + + // Create a provider + let network = networks::gnosis(); + let provider_result = create_provider(&network); + + // The provider creation should succeed + assert!(provider_result.is_ok()); + + // We can't actually test get_balance without a blockchain + // In a real test, we would mock the provider and test the function +} + +#[test] +fn test_send_eth() { + // This is a mock test since we can't actually send transactions in a unit test + // In a real test, we would use a local blockchain or mock the provider + + // Create a wallet + let keypair = KeyPair::new("test_keypair6"); + let network = networks::gnosis(); + let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); + + // Create a provider + let provider_result = create_provider(&network); + assert!(provider_result.is_ok()); + + // We can't actually test send_eth without a blockchain + // In a real test, we would mock the provider and test the function +} diff --git a/vault/src/ethereum.bak/tests/wallet_tests.rs b/vault/src/ethereum.bak/tests/wallet_tests.rs new file mode 100644 index 0000000..eb6502a --- /dev/null +++ b/vault/src/ethereum.bak/tests/wallet_tests.rs @@ -0,0 +1,143 @@ +//! Tests for Ethereum wallet functionality. + +use crate::vault::ethereum::*; +use crate::vault::keypair::implementation::KeyPair; +use ethers::utils::hex; + +#[test] +fn test_ethereum_wallet_from_keypair() { + let keypair = KeyPair::new("test_keypair"); + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); + + assert_eq!(wallet.network.name, "Gnosis"); + assert_eq!(wallet.network.chain_id, 100); + + // The address should be a valid Ethereum address + assert!(wallet.address_string().starts_with("0x")); +} + +#[test] +fn test_ethereum_wallet_from_name_and_keypair() { + let keypair = KeyPair::new("test_keypair2"); + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap(); + + assert_eq!(wallet.network.name, "Gnosis"); + assert_eq!(wallet.network.chain_id, 100); + + // The address should be a valid Ethereum address + assert!(wallet.address_string().starts_with("0x")); + + // Creating another wallet with the same name and keypair should yield the same address + let wallet2 = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap(); + assert_eq!(wallet.address, wallet2.address); + + // Creating a wallet with a different name should yield a different address + let wallet3 = EthereumWallet::from_name_and_keypair("test2", &keypair, network.clone()).unwrap(); + assert_ne!(wallet.address, wallet3.address); +} + +#[test] +fn test_ethereum_wallet_from_private_key() { + let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_private_key(private_key, network.clone()).unwrap(); + + assert_eq!(wallet.network.name, "Gnosis"); + assert_eq!(wallet.network.chain_id, 100); + + // The address should be a valid Ethereum address + assert!(wallet.address_string().starts_with("0x")); + + // The address should be deterministic based on the private key + let wallet2 = EthereumWallet::from_private_key(private_key, network.clone()).unwrap(); + assert_eq!(wallet.address, wallet2.address); +} + +#[test] +fn test_wallet_management() { + // Clear any existing wallets + clear_ethereum_wallets(); + + // Create a key space and keypair + crate::vault::keypair::session_manager::create_space("test_space").unwrap(); + crate::vault::keypair::create_keypair("test_keypair3").unwrap(); + + // Create wallets for different networks + let gnosis_wallet = create_ethereum_wallet_for_network(networks::gnosis()).unwrap(); + let peaq_wallet = create_ethereum_wallet_for_network(networks::peaq()).unwrap(); + let agung_wallet = create_ethereum_wallet_for_network(networks::agung()).unwrap(); + + // Get the current wallets + let current_gnosis = get_current_ethereum_wallet_for_network("Gnosis").unwrap(); + let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap(); + let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap(); + + // Check that they match + assert_eq!(gnosis_wallet.address, current_gnosis.address); + assert_eq!(peaq_wallet.address, current_peaq.address); + assert_eq!(agung_wallet.address, current_agung.address); + + // Clear wallets for a specific network + clear_ethereum_wallets_for_network("Gnosis"); + + // Check that the wallet is gone + let result = get_current_ethereum_wallet_for_network("Gnosis"); + assert!(result.is_err()); + + // But the others should still be there + let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap(); + let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap(); + assert_eq!(peaq_wallet.address, current_peaq.address); + assert_eq!(agung_wallet.address, current_agung.address); + + // Clear all wallets + clear_ethereum_wallets(); + + // Check that all wallets are gone + let result1 = get_current_ethereum_wallet_for_network("Gnosis"); + let result2 = get_current_ethereum_wallet_for_network("Peaq"); + let result3 = get_current_ethereum_wallet_for_network("Agung"); + assert!(result1.is_err()); + assert!(result2.is_err()); + assert!(result3.is_err()); +} + +#[test] +fn test_sign_message() { + let keypair = KeyPair::new("test_keypair4"); + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); + + // Create a tokio runtime for the async test + let rt = tokio::runtime::Runtime::new().unwrap(); + + // Sign a message + let message = b"Hello, world!"; + let signature = rt.block_on(wallet.sign_message(message)).unwrap(); + + // The signature should be a non-empty string + assert!(!signature.is_empty()); +} + +#[test] +fn test_private_key_hex() { + let keypair = KeyPair::new("test_keypair5"); + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); + + // Get the private key as hex + let private_key_hex = wallet.private_key_hex(); + + // The private key should be a 64-character hex string (32 bytes) + assert_eq!(private_key_hex.len(), 64); + + // It should be possible to parse it as hex + let _bytes = hex::decode(private_key_hex).unwrap(); +} diff --git a/vault/src/ethereum.bak/transaction.rs b/vault/src/ethereum.bak/transaction.rs new file mode 100644 index 0000000..fd9deb6 --- /dev/null +++ b/vault/src/ethereum.bak/transaction.rs @@ -0,0 +1,54 @@ +//! Ethereum transaction functionality. + +use ethers::prelude::*; + +use crate::vault::error::CryptoError; +use super::wallet::EthereumWallet; +use super::networks::NetworkConfig; + +/// Formats a token balance for display. +pub fn format_balance(balance: U256, network: &NetworkConfig) -> String { + let wei = balance.as_u128(); + let divisor = 10u128.pow(network.decimals as u32) as f64; + let token = wei as f64 / divisor; + + // Display with the appropriate number of decimal places + let display_decimals = std::cmp::min(6, network.decimals); + + format!("{:.*} {}", display_decimals as usize, token, network.token_symbol) +} + +/// Gets the balance of an Ethereum address. +pub async fn get_balance(provider: &Provider, address: Address) -> Result { + provider.get_balance(address, None) + .await + .map_err(|e| CryptoError::SerializationError(format!("Failed to get balance: {}", e))) +} + +/// Sends Ethereum from one address to another. +pub async fn send_eth( + wallet: &EthereumWallet, + provider: &Provider, + to: Address, + amount: U256, +) -> Result { + // Create a client with the wallet + let client = SignerMiddleware::new( + provider.clone(), + wallet.wallet.clone(), + ); + + // Create the transaction + let tx = TransactionRequest::new() + .to(to) + .value(amount) + .gas(21000); + + // Send the transaction + let pending_tx = client.send_transaction(tx, None) + .await + .map_err(|e| CryptoError::SerializationError(format!("Failed to send transaction: {}", e)))?; + + // Return the transaction hash instead of waiting for the receipt + Ok(pending_tx.tx_hash()) +} diff --git a/vault/src/ethereum.bak/wallet.rs b/vault/src/ethereum.bak/wallet.rs new file mode 100644 index 0000000..8209cdb --- /dev/null +++ b/vault/src/ethereum.bak/wallet.rs @@ -0,0 +1,123 @@ +//! Ethereum wallet implementation. + +use ethers::prelude::*; +use ethers::signers::{LocalWallet, Signer, Wallet}; +use ethers::utils::hex; +use k256::ecdsa::SigningKey; +use sha2::{Digest, Sha256}; +use std::str::FromStr; + +use super::networks::NetworkConfig; +use crate::vault::error::CryptoError; +use crate::vault::keyspace::KeyPair; + +/// An Ethereum wallet derived from a keypair. +#[derive(Debug, Clone)] +pub struct EthereumWallet { + pub address: Address, + pub wallet: Wallet, + pub network: NetworkConfig, +} + +impl EthereumWallet { + /// Creates a new Ethereum wallet from a keypair for a specific network. + pub fn from_keypair(keypair: &KeyPair, network: NetworkConfig) -> Result { + // Get the private key bytes from the keypair + let private_key_bytes = keypair.signing_key.to_bytes(); + + // Convert to a hex string (without 0x prefix) + let private_key_hex = hex::encode(private_key_bytes); + + // Create an Ethereum wallet from the private key + let wallet = LocalWallet::from_str(&private_key_hex) + .map_err(|_e| CryptoError::InvalidKeyLength)? + .with_chain_id(network.chain_id); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + network, + }) + } + + /// Creates a new Ethereum wallet from a name and keypair (deterministic derivation) for a specific network. + pub fn from_name_and_keypair( + name: &str, + keypair: &KeyPair, + network: NetworkConfig, + ) -> Result { + // Get the private key bytes from the keypair + let private_key_bytes = keypair.signing_key.to_bytes(); + + // Create a deterministic seed by combining name and private key + let mut hasher = Sha256::default(); + hasher.update(name.as_bytes()); + hasher.update(&private_key_bytes); + let seed = hasher.finalize(); + + // Use the seed as a private key + let private_key_hex = hex::encode(seed); + + // Create an Ethereum wallet from the derived private key + let wallet = LocalWallet::from_str(&private_key_hex) + .map_err(|_e| CryptoError::InvalidKeyLength)? + .with_chain_id(network.chain_id); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + network, + }) + } + + /// Creates a new Ethereum wallet from a private key for a specific network. + pub fn from_private_key( + private_key: &str, + network: NetworkConfig, + ) -> Result { + // Remove 0x prefix if present + let private_key_clean = private_key.trim_start_matches("0x"); + + // Create an Ethereum wallet from the private key + let wallet = LocalWallet::from_str(private_key_clean) + .map_err(|_e| CryptoError::InvalidKeyLength)? + .with_chain_id(network.chain_id); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + network, + }) + } + + /// Gets the Ethereum address as a string. + pub fn address_string(&self) -> String { + format!("{:?}", self.address) + } + + /// Signs a message with the Ethereum wallet. + pub async fn sign_message(&self, message: &[u8]) -> Result { + let signature = self + .wallet + .sign_message(message) + .await + .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; + + Ok(signature.to_string()) + } + + /// Gets the private key as a hex string. + pub fn private_key_hex(&self) -> String { + let bytes = self.wallet.signer().to_bytes(); + hex::encode(bytes) + } +} diff --git a/vault/src/key.rs b/vault/src/key.rs new file mode 100644 index 0000000..42d2529 --- /dev/null +++ b/vault/src/key.rs @@ -0,0 +1,83 @@ +use asymmetric::AsymmetricKeypair; +use serde::{Deserialize, Serialize}; +use signature::SigningKeypair; +use symmetric::SymmetricKey; + +pub mod asymmetric; +pub mod signature; +pub mod symmetric; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub enum KeyType { + /// The key can be used for symmetric key encryption + Symmetric, + /// The key can be used for asymmetric encryption + Asymmetric, + /// The key can be used for digital signatures + Signature, +} + +/// Key holds generic information about a key +#[derive(Clone, Deserialize, Serialize)] +pub struct Key { + /// The mode of the key + mode: KeyType, + /// Raw bytes of the key + raw_key: Vec, +} + +impl Key { + /// Try to downcast this `Key` to a [`SymmetricKey`] + pub fn as_symmetric(&self) -> Option { + if matches!(self.mode, KeyType::Symmetric) { + SymmetricKey::from_bytes(&self.raw_key).ok() + } else { + None + } + } + + /// Try to downcast this `Key` to an [`AsymmetricKeypair`] + pub fn as_asymmetric(&self) -> Option { + if matches!(self.mode, KeyType::Asymmetric) { + AsymmetricKeypair::from_bytes(&self.raw_key).ok() + } else { + None + } + } + + /// Try to downcast this `Key` to a [`SigningKeypair`] + pub fn as_signing(&self) -> Option { + if matches!(self.mode, KeyType::Signature) { + SigningKeypair::from_bytes(&self.raw_key).ok() + } else { + None + } + } +} + +impl From for Key { + fn from(value: SymmetricKey) -> Self { + Self { + mode: KeyType::Symmetric, + raw_key: Vec::from(value.as_raw_bytes()), + } + } +} + +impl From for Key { + fn from(value: AsymmetricKeypair) -> Self { + Self { + mode: KeyType::Asymmetric, + raw_key: value.as_raw_private_key(), + } + } +} + +impl From for Key { + fn from(value: SigningKeypair) -> Self { + Self { + mode: KeyType::Signature, + raw_key: value.as_raw_private_key(), + } + } +} diff --git a/vault/src/key/asymmetric.rs b/vault/src/key/asymmetric.rs new file mode 100644 index 0000000..b58e5ab --- /dev/null +++ b/vault/src/key/asymmetric.rs @@ -0,0 +1,112 @@ +//! An implementation of asymmetric cryptography using SECP256k1 ECDH with ChaCha20Poly1305 +//! for the actual encryption. + +use k256::{ + SecretKey, + ecdh::diffie_hellman, +}; +use sha2::Sha256; + +use crate::{error::CryptoError, key::symmetric::SymmetricKey}; + +/// A keypair for use in asymmetric encryption operations. +pub struct AsymmetricKeypair { + /// Private part of the key + private: SecretKey, + /// Public part of the key + public: k256::PublicKey, +} + +/// The public key part of an asymmetric keypair. +pub struct PublicKey(k256::PublicKey); + +impl AsymmetricKeypair { + /// Generates a new random keypair + pub fn new() -> Result { + let mut raw_private = [0u8; 32]; + rand::fill(&mut raw_private); + let sk = SecretKey::from_slice(&raw_private) + .expect("Key is provided generated with fixed valid size"); + let pk = sk.public_key(); + + Ok(Self { + private: sk, + public: pk, + }) + } + + /// Create a new key from existing bytes. + pub(crate) fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() == 32 { + let sk = SecretKey::from_slice(&bytes).expect("Key was checked to be a valid size"); + let pk = sk.public_key(); + Ok(Self { + private: sk, + public: pk, + }) + } else { + Err(CryptoError::InvalidKeySize) + } + } + + /// View the raw bytes of the private key of this keypair. + pub(crate) fn as_raw_private_key(&self) -> Vec { + self.private.as_scalar_primitive().to_bytes().to_vec() + } + + /// Get the public part of this keypair. + pub fn public_key(&self) -> PublicKey { + PublicKey(self.public.clone()) + } + + /// Encrypt data for a receiver. First a shared secret is derived using the own private key and + /// the receivers public key. Then, this shared secret is used for symmetric encryption of the + /// plaintext. The receiver can decrypt this by generating the same shared secret, using his + /// own private key and our public key. + pub fn encrypt( + &self, + remote_key: &PublicKey, + plaintext: &[u8], + ) -> Result, CryptoError> { + let mut symmetric_key = [0u8; 32]; + diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine()) + .extract::(None) + .expand(&[], &mut symmetric_key) + .map_err(|_| CryptoError::InvalidKeySize)?; + + let sym_key = SymmetricKey::from_bytes(&symmetric_key)?; + + sym_key.encrypt(plaintext) + } + + /// Decrypt data from a sender. The remote key must be the public key of the keypair used by + /// the sender to encrypt this message. + pub fn decrypt( + &self, + remote_key: &PublicKey, + ciphertext: &[u8], + ) -> Result, CryptoError> { + let mut symmetric_key = [0u8; 32]; + diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine()) + .extract::(None) + .expand(&[], &mut symmetric_key) + .map_err(|_| CryptoError::InvalidKeySize)?; + + let sym_key = SymmetricKey::from_bytes(&symmetric_key)?; + + sym_key.decrypt(ciphertext) + } +} + +impl PublicKey { + /// Import a public key from raw bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() == 64 { + Ok(Self( + k256::PublicKey::from_sec1_bytes(bytes).expect("Key is of valid size"), + )) + } else { + Err(CryptoError::InvalidKeySize) + } + } +} diff --git a/vault/src/key/signature.rs b/vault/src/key/signature.rs new file mode 100644 index 0000000..029f47f --- /dev/null +++ b/vault/src/key/signature.rs @@ -0,0 +1,76 @@ +//! An implementation of digitial signatures using secp256k1 ECDSA. + +use k256::ecdsa::{ + Signature, SigningKey, VerifyingKey, + signature::{Signer, Verifier}, +}; + +use crate::error::CryptoError; + +pub struct SigningKeypair { + sk: SigningKey, + vk: VerifyingKey, +} + +pub struct PublicKey(VerifyingKey); + +impl SigningKeypair { + /// Generates a new random keypair + pub fn new() -> Result { + let mut raw_private = [0u8; 32]; + rand::fill(&mut raw_private); + let sk = SigningKey::from_slice(&raw_private) + .expect("Key is provided generated with fixed valid size"); + let vk = sk.verifying_key().to_owned(); + + Ok(Self { sk, vk }) + } + + /// Create a new key from existing bytes. + pub(crate) fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() == 32 { + let sk = SigningKey::from_slice(&bytes).expect("Key was checked to be a valid size"); + let vk = sk.verifying_key().to_owned(); + Ok(Self { sk, vk }) + } else { + Err(CryptoError::InvalidKeySize) + } + } + + /// View the raw bytes of the private key of this keypair. + pub(crate) fn as_raw_private_key(&self) -> Vec { + self.sk.as_nonzero_scalar().to_bytes().to_vec() + } + + /// Get the public part of this keypair. + pub fn public_key(&self) -> PublicKey { + PublicKey(self.vk) + } + + /// Sign data with the private key of this `SigningKeypair`. Other parties can use the public + /// key to verify the signature. The generated signature is a detached signature. + pub fn sign(&self, message: &[u8]) -> Result, CryptoError> { + let sig: Signature = self.sk.sign(message); + Ok(sig.to_vec()) + } +} + +impl PublicKey { + /// Import a public key from raw bytes + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() == 64 { + Ok(Self( + VerifyingKey::from_sec1_bytes(bytes).expect("Key is of valid size"), + )) + } else { + Err(CryptoError::InvalidKeySize) + } + } + + pub fn verify_signature(&self, message: &[u8], sig: &[u8]) -> Result<(), CryptoError> { + let sig = Signature::from_slice(sig).map_err(|_| CryptoError::InvalidKeySize)?; + self.0 + .verify(message, &sig) + .map_err(|_| CryptoError::SignatureFailed) + } +} diff --git a/vault/src/key/symmetric.rs b/vault/src/key/symmetric.rs new file mode 100644 index 0000000..ec11a77 --- /dev/null +++ b/vault/src/key/symmetric.rs @@ -0,0 +1,86 @@ +//! An implementation of symmetric keys for ChaCha20Poly1305 encryption. +//! +//! The ciphertext is authenticated. +//! The 12-byte nonce is appended to the generated ciphertext. +//! Keys are 32 bytes in size. + +use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce, aead::Aead}; + +use crate::error::CryptoError; + +pub struct SymmetricKey([u8; 32]); + +/// Size of a nonce in ChaCha20Poly1305. +const NONCE_SIZE: usize = 12; + +impl SymmetricKey { + /// Generate a new random SymmetricKey. + pub fn new() -> Self { + let mut key = [0u8; 32]; + rand::fill(&mut key); + Self(key) + } + + /// Create a new key from existing bytes. + pub(crate) fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() == 32 { + let mut key = [0u8; 32]; + key.copy_from_slice(bytes); + Ok(SymmetricKey(key)) + } else { + Err(CryptoError::InvalidKeySize) + } + } + + /// View the raw bytes of this key + pub(crate) fn as_raw_bytes(&self) -> &[u8; 32] { + &self.0 + } + + /// Encrypt a plaintext with the key. A nonce is generated and appended to the end of the + /// message. + pub fn encrypt(&self, plaintext: &[u8]) -> Result, CryptoError> { + // Create cipher + let cipher = ChaCha20Poly1305::new_from_slice(&self.0) + .expect("Key is a fixed 32 byte array so size is always ok"); + + // Generate random nonce + let mut nonce_bytes = [0u8; NONCE_SIZE]; + rand::fill(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + + // Encrypt message + let mut ciphertext = cipher + .encrypt(nonce, plaintext) + .map_err(|_| CryptoError::EncryptionFailed)?; + + // Append nonce to ciphertext + ciphertext.extend_from_slice(&nonce_bytes); + + Ok(ciphertext) + } + + /// Decrypts a ciphertext with appended nonce. + pub fn decrypt(&self, ciphertext: &[u8]) -> Result, CryptoError> { + // Check if ciphertext is long enough to contain a nonce + if ciphertext.len() <= NONCE_SIZE { + return Err(CryptoError::DecryptionFailed); + } + + // Extract nonce from the end of ciphertext + let ciphertext_len = ciphertext.len() - NONCE_SIZE; + let ciphertext = &ciphertext[0..ciphertext_len]; + let nonce_bytes = &ciphertext[ciphertext_len..]; + + // Create cipher + let cipher = ChaCha20Poly1305::new_from_slice(&self.0) + .expect("Key is a fixed 32 byte array so size is always ok"); + + let nonce = Nonce::from_slice(nonce_bytes); + + // Decrypt message + cipher + .decrypt(nonce, ciphertext) + .map_err(|_| CryptoError::DecryptionFailed) + } +} diff --git a/vault/src/keyspace.bak/README.md b/vault/src/keyspace.bak/README.md new file mode 100644 index 0000000..4cfb15d --- /dev/null +++ b/vault/src/keyspace.bak/README.md @@ -0,0 +1,271 @@ +# Hero Vault Keypair Module + +The Keypair module provides functionality for creating, managing, and using ECDSA keypairs for digital signatures and other cryptographic operations. + +## Module Structure + +The Keypair module is organized into: + +- `keypair_types.rs` - Defines the KeyPair and related types. +- `session_manager.rs` - Implements the core logic for managing keypairs and key spaces. +- `mod.rs` - Module exports and public interface. + +## Key Types + +### KeyPair + +The `KeyPair` type represents an ECDSA keypair used for digital signatures and other cryptographic operations. + +```rust +pub struct KeyPair { + // Private fields + // ... +} + +impl KeyPair { + // Create a new random keypair + pub fn new() -> Result; + + // Create a keypair from an existing private key + pub fn from_private_key(private_key: &[u8]) -> Result; + + // Get the public key + pub fn public_key(&self) -> &[u8]; + + // Sign a message + pub fn sign(&self, message: &[u8]) -> Result, CryptoError>; + + // Verify a signature + pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result; + + // Derive an Ethereum address from the public key + pub fn to_ethereum_address(&self) -> Result; + + // Export the private key (should be used with caution) + pub fn export_private_key(&self) -> Result, CryptoError>; +} +``` + +### KeySpace + +The `KeySpace` type represents a secure container for multiple keypairs, which can be encrypted and stored on disk. + +```rust +pub struct KeySpace { + // Private fields + // ... +} + +impl KeySpace { + // Create a new key space + pub fn new(name: &str, password: &str) -> Result; + + // Load a key space from disk + pub fn load(name: &str, password: &str) -> Result; + + // Save the key space to disk + pub fn save(&self) -> Result<(), CryptoError>; + + // Create a new keypair in the key space + pub fn create_keypair(&mut self, name: &str, password: &str) -> Result<&KeyPair, CryptoError>; + + // Select a keypair for use + pub fn select_keypair(&mut self, name: &str) -> Result<&KeyPair, CryptoError>; + + // Get the currently selected keypair + pub fn current_keypair(&self) -> Result<&KeyPair, CryptoError>; + + // List all keypairs in the key space + pub fn list_keypairs(&self) -> Result, CryptoError>; + + // Get a keypair by name + pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError>; + + // Remove a keypair from the key space + pub fn remove_keypair(&mut self, name: &str) -> Result<(), CryptoError>; + + // Rename a keypair + pub fn rename_keypair(&mut self, old_name: &str, new_name: &str) -> Result<(), CryptoError>; + + // Get the name of the key space + pub fn name(&self) -> &str; +} +``` + +## Key Features + +### Key Space Management + +The module provides functionality for creating, loading, and managing key spaces: + +```rust +// Create a new key space +let mut space = KeySpace::new("my_space", "secure_password")?; + +// Save the key space to disk +space.save()?; + +// Load a key space from disk +let mut loaded_space = KeySpace::load("my_space", "secure_password")?; +``` + +### Keypair Management + +The module provides functionality for creating, selecting, and using keypairs: + +```rust +use crate::vault::keypair::{KeySpace, KeyPair}; +use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error + +fn demonstrate_keypair_management() -> Result<(), CryptoError> { + // Create a new key space + let mut space = KeySpace::new("my_space", "secure_password")?; + + // Create a new keypair in the key space + let keypair = space.create_keypair("my_keypair", "secure_password")?; + println!("Created keypair: {}", keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::()); + + // Select a keypair for use + space.select_keypair("my_keypair")?; + println!("Selected keypair: {}", space.current_keypair()?.public_key().iter().map(|b| format!("{:02x}", b)).collect::()); + + // List all keypairs in the key space + let keypairs = space.list_keypairs()?; + println!("Keypairs in space: {:?}", keypairs); + + // Get a keypair by name + let retrieved_keypair = space.get_keypair("my_keypair")?; + println!("Retrieved keypair: {}", retrieved_keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::()); + + // Rename a keypair + space.rename_keypair("my_keypair", "new_name")?; + println!("Renamed keypair to new_name"); + let keypairs_after_rename = space.list_keypairs()?; + println!("Keypairs in space after rename: {:?}", keypairs_after_rename); + + + // Remove a keypair from the key space + space.remove_keypair("new_name")?; + println!("Removed keypair new_name"); + let keypairs_after_remove = space.list_keypairs()?; + println!("Keypairs in space after removal: {:?}", keypairs_after_remove); + + Ok(()) +} +``` + +### Digital Signatures + +The module provides functionality for signing and verifying messages using ECDSA: + +```rust +use crate::vault::keypair::KeySpace; +use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error + +fn demonstrate_digital_signatures() -> Result<(), CryptoError> { + // Assuming a key space and selected keypair exist + // let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space + let mut space = KeySpace::new("temp_space_for_demo", "password")?; // Or create a new one for demo + space.create_keypair("my_signing_key", "key_password")?; + space.select_keypair("my_signing_key")?; + + + // Sign a message using the selected keypair + let keypair = space.current_keypair()?; + let message = "This is a message to sign".as_bytes(); + let signature = keypair.sign(message)?; + println!("Message signed. Signature: {:?}", signature); + + // Verify a signature + let is_valid = keypair.verify(message, &signature)?; + println!("Signature valid: {}", is_valid); + + // Example of invalid signature verification + let invalid_signature = vec![0u8; signature.len()]; // A dummy invalid signature + let is_valid_invalid = keypair.verify(message, &invalid_signature)?; + println!("Invalid signature valid: {}", is_valid_invalid); + + + Ok(()) +} +``` + +### Ethereum Address Derivation + +The module provides functionality for deriving Ethereum addresses from keypairs: + +```rust +use crate::vault::keypair::KeySpace; +use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error + +fn demonstrate_ethereum_address_derivation() -> Result<(), CryptoError> { + // Assuming a key space and selected keypair exist + // let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space + let mut space = KeySpace::new("temp_space_for_eth_demo", "password")?; // Or create a new one for demo + space.create_keypair("my_eth_key", "key_password")?; + space.select_keypair("my_eth_key")?; + + // Derive an Ethereum address from a keypair + let keypair = space.current_keypair()?; + let address = keypair.to_ethereum_address()?; + println!("Derived Ethereum address: {}", address); + + Ok(()) +} +``` + +## Including in Your Project + +To include the Hero Vault Keypair module in your Rust project, add the following to your `Cargo.toml` file: + +```toml +[dependencies] +hero_vault = "0.1.0" # Replace with the actual version +``` + +Then, you can import and use the module in your Rust code: + +```rust +use hero_vault::vault::keypair::{KeySpace, KeyPair}; +use hero_vault::vault::error::CryptoError; +``` + +## Testing + +Tests for the Keypair module are included within the source files, likely in `session_manager.rs` or `mod.rs` as inline tests. + +To run the tests, navigate to the root directory of the project in your terminal and execute the following command: + +```bash +cargo test --lib vault::keypair +``` + +This command will run all tests specifically within the `vault::keypair` module. + +## Security Considerations + +- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password +- Private keys are never stored in plaintext +- The module uses secure random number generation for key creation +- All cryptographic operations use well-established libraries and algorithms + +## Error Handling + +The module uses the `CryptoError` type for handling errors that can occur during keypair operations: + +- `InvalidKeyLength` - Invalid key length +- `SignatureFormatError` - Signature format error +- `KeypairAlreadyExists` - Keypair already exists +- `KeypairNotFound` - Keypair not found +- `NoActiveSpace` - No active key space +- `NoKeypairSelected` - No keypair selected +- `SerializationError` - Serialization error + +## Examples + +For examples of how to use the Keypair module, see the `examples/hero_vault` directory, particularly: + +- `example.rhai` - Basic example demonstrating key management and signing +- `advanced_example.rhai` - Advanced example with error handling +- `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk +- `load_existing_space.rhai` - Shows how to load a previously created key space diff --git a/vault/src/keyspace.bak/keypair_types.rs b/vault/src/keyspace.bak/keypair_types.rs new file mode 100644 index 0000000..4f3fe1c --- /dev/null +++ b/vault/src/keyspace.bak/keypair_types.rs @@ -0,0 +1,328 @@ +use k256::ecdh::EphemeralSecret; +/// Implementation of keypair functionality. +use k256::ecdsa::{ + signature::{Signer, Verifier}, + Signature, SigningKey, VerifyingKey, +}; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; + +use crate::vault::error::CryptoError; +use crate::vault::symmetric::implementation; + +/// A keypair for signing and verifying messages. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KeyPair { + pub name: String, + #[serde(with = "verifying_key_serde")] + pub verifying_key: VerifyingKey, + #[serde(with = "signing_key_serde")] + pub signing_key: SigningKey, +} + +// Serialization helpers for VerifyingKey +mod verifying_key_serde { + use super::*; + use serde::de::{self, Visitor}; + use serde::{Deserializer, Serializer}; + use std::fmt; + + pub fn serialize(key: &VerifyingKey, serializer: S) -> Result + where + S: Serializer, + { + let bytes = key.to_sec1_bytes(); + // Convert bytes to a Vec and serialize that instead + serializer.collect_seq(bytes) + } + + struct VerifyingKeyVisitor; + + impl<'de> Visitor<'de> for VerifyingKeyVisitor { + type Value = VerifyingKey; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array representing a verifying key") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + VerifyingKey::from_sec1_bytes(v).map_err(|e| { + log::error!("Error deserializing verifying key: {:?}", e); + E::custom(format!("invalid verifying key: {:?}", e)) + }) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + // Collect all bytes from the sequence + let mut bytes = Vec::new(); + while let Some(byte) = seq.next_element()? { + bytes.push(byte); + } + + VerifyingKey::from_sec1_bytes(&bytes).map_err(|e| { + log::error!("Error deserializing verifying key from seq: {:?}", e); + de::Error::custom(format!("invalid verifying key from seq: {:?}", e)) + }) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Try to deserialize as bytes first, then as a sequence + deserializer.deserialize_any(VerifyingKeyVisitor) + } +} + +// Serialization helpers for SigningKey +mod signing_key_serde { + use super::*; + use serde::de::{self, Visitor}; + use serde::{Deserializer, Serializer}; + use std::fmt; + + pub fn serialize(key: &SigningKey, serializer: S) -> Result + where + S: Serializer, + { + let bytes = key.to_bytes(); + // Convert bytes to a Vec and serialize that instead + serializer.collect_seq(bytes) + } + + struct SigningKeyVisitor; + + impl<'de> Visitor<'de> for SigningKeyVisitor { + type Value = SigningKey; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array representing a signing key") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + SigningKey::from_bytes(v.into()).map_err(|e| { + log::error!("Error deserializing signing key: {:?}", e); + E::custom(format!("invalid signing key: {:?}", e)) + }) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'de>, + { + // Collect all bytes from the sequence + let mut bytes = Vec::new(); + while let Some(byte) = seq.next_element()? { + bytes.push(byte); + } + + SigningKey::from_bytes(bytes.as_slice().into()).map_err(|e| { + log::error!("Error deserializing signing key from seq: {:?}", e); + de::Error::custom(format!("invalid signing key from seq: {:?}", e)) + }) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Try to deserialize as bytes first, then as a sequence + deserializer.deserialize_any(SigningKeyVisitor) + } +} + +impl KeyPair { + /// Creates a new keypair with the given name. + pub fn new(name: &str) -> Self { + let signing_key = SigningKey::random(&mut OsRng); + let verifying_key = VerifyingKey::from(&signing_key); + + KeyPair { + name: name.to_string(), + verifying_key, + signing_key, + } + } + + /// Gets the public key bytes. + pub fn pub_key(&self) -> Vec { + self.verifying_key.to_sec1_bytes().to_vec() + } + + /// Derives a public key from a private key. + pub fn pub_key_from_private(private_key: &[u8]) -> Result, CryptoError> { + let signing_key = SigningKey::from_bytes(private_key.into()) + .map_err(|_| CryptoError::InvalidKeyLength)?; + let verifying_key = VerifyingKey::from(&signing_key); + Ok(verifying_key.to_sec1_bytes().to_vec()) + } + + /// Signs a message. + pub fn sign(&self, message: &[u8]) -> Vec { + let signature: Signature = self.signing_key.sign(message); + signature.to_bytes().to_vec() + } + + /// Verifies a message signature. + pub fn verify(&self, message: &[u8], signature_bytes: &[u8]) -> Result { + let signature = Signature::from_bytes(signature_bytes.into()) + .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; + + match self.verifying_key.verify(message, &signature) { + Ok(_) => Ok(true), + Err(_) => Ok(false), // Verification failed, but operation was successful + } + } + + /// Verifies a message signature using only a public key. + pub fn verify_with_public_key( + public_key: &[u8], + message: &[u8], + signature_bytes: &[u8], + ) -> Result { + let verifying_key = + VerifyingKey::from_sec1_bytes(public_key).map_err(|_| CryptoError::InvalidKeyLength)?; + + let signature = Signature::from_bytes(signature_bytes.into()) + .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; + + match verifying_key.verify(message, &signature) { + Ok(_) => Ok(true), + Err(_) => Ok(false), // Verification failed, but operation was successful + } + } + + /// Encrypts a message using the recipient's public key. + /// This implements ECIES (Elliptic Curve Integrated Encryption Scheme): + /// 1. Generate an ephemeral keypair + /// 2. Derive a shared secret using ECDH + /// 3. Derive encryption key from the shared secret + /// 4. Encrypt the message using symmetric encryption + /// 5. Return the ephemeral public key and the ciphertext + pub fn encrypt_asymmetric( + &self, + recipient_public_key: &[u8], + message: &[u8], + ) -> Result, CryptoError> { + // Parse recipient's public key + let recipient_key = VerifyingKey::from_sec1_bytes(recipient_public_key) + .map_err(|_| CryptoError::InvalidKeyLength)?; + + // Generate ephemeral keypair + let ephemeral_signing_key = SigningKey::random(&mut OsRng); + let ephemeral_public_key = VerifyingKey::from(&ephemeral_signing_key); + + // Derive shared secret using ECDH + let ephemeral_secret = EphemeralSecret::random(&mut OsRng); + let shared_secret = ephemeral_secret.diffie_hellman(&recipient_key.into()); + + // Derive encryption key from the shared secret (e.g., using HKDF or hashing) + // For simplicity, we'll hash the shared secret here + let encryption_key = { + let mut hasher = Sha256::default(); + hasher.update(shared_secret.raw_secret_bytes()); + hasher.finalize().to_vec() + }; + + // Encrypt the message using the derived key + let ciphertext = implementation::encrypt_with_key(&encryption_key, message) + .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?; + + // Format: ephemeral_public_key || ciphertext + let mut result = ephemeral_public_key + .to_encoded_point(false) + .as_bytes() + .to_vec(); + result.extend_from_slice(&ciphertext); + + Ok(result) + } + + /// Decrypts a message using the recipient's private key. + /// This is the counterpart to encrypt_asymmetric. + pub fn decrypt_asymmetric(&self, ciphertext: &[u8]) -> Result, CryptoError> { + // The first 33 or 65 bytes (depending on compression) are the ephemeral public key + // For simplicity, we'll assume uncompressed keys (65 bytes) + if ciphertext.len() <= 65 { + return Err(CryptoError::DecryptionFailed( + "Ciphertext too short".to_string(), + )); + } + + // Extract ephemeral public key and actual ciphertext + let ephemeral_public_key = &ciphertext[..65]; + let actual_ciphertext = &ciphertext[65..]; + + // Parse ephemeral public key + let sender_key = VerifyingKey::from_sec1_bytes(ephemeral_public_key) + .map_err(|_| CryptoError::InvalidKeyLength)?; + + // Derive shared secret using ECDH + let recipient_secret = EphemeralSecret::random(&mut OsRng); + let shared_secret = recipient_secret.diffie_hellman(&sender_key.into()); + + // Derive decryption key from the shared secret (using the same method as encryption) + let decryption_key = { + let mut hasher = Sha256::default(); + hasher.update(shared_secret.raw_secret_bytes()); + hasher.finalize().to_vec() + }; + + // Decrypt the message using the derived key + implementation::decrypt_with_key(&decryption_key, actual_ciphertext) + .map_err(|e| CryptoError::DecryptionFailed(e.to_string())) + } +} + +/// A collection of keypairs. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct KeySpace { + pub name: String, + pub keypairs: HashMap, +} + +impl KeySpace { + /// Creates a new key space with the given name. + pub fn new(name: &str) -> Self { + KeySpace { + name: name.to_string(), + keypairs: HashMap::new(), + } + } + + /// Adds a new keypair to the space. + pub fn add_keypair(&mut self, name: &str) -> Result<(), CryptoError> { + if self.keypairs.contains_key(name) { + return Err(CryptoError::KeypairAlreadyExists(name.to_string())); + } + + let keypair = KeyPair::new(name); + self.keypairs.insert(name.to_string(), keypair); + Ok(()) + } + + /// Gets a keypair by name. + pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError> { + self.keypairs + .get(name) + .ok_or(CryptoError::KeypairNotFound(name.to_string())) + } + + /// Lists all keypair names in the space. + pub fn list_keypairs(&self) -> Vec { + self.keypairs.keys().cloned().collect() + } +} diff --git a/vault/src/keyspace.bak/mod.rs b/vault/src/keyspace.bak/mod.rs new file mode 100644 index 0000000..d9ea317 --- /dev/null +++ b/vault/src/keyspace.bak/mod.rs @@ -0,0 +1,18 @@ +//! Key pair management functionality +//! +//! This module provides functionality for creating and managing ECDSA key pairs. + +pub mod keypair_types; +pub mod session_manager; + +// Re-export public types and functions +pub use keypair_types::{KeyPair, KeySpace}; +pub use session_manager::{ + create_space, set_current_space, get_current_space, clear_session, + create_keypair, select_keypair, get_selected_keypair, list_keypairs, + keypair_pub_key, derive_public_key, keypair_sign, keypair_verify, + verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric +}; + +#[cfg(test)] +mod tests; diff --git a/vault/src/keyspace.bak/session_manager.rs b/vault/src/keyspace.bak/session_manager.rs new file mode 100644 index 0000000..3fa7a11 --- /dev/null +++ b/vault/src/keyspace.bak/session_manager.rs @@ -0,0 +1,174 @@ +use once_cell::sync::Lazy; +use std::sync::Mutex; + +use crate::vault::error::CryptoError; +use crate::vault::keyspace::keypair_types::{KeyPair, KeySpace}; // Assuming KeyPair and KeySpace will be in keypair_types.rs + +/// Session state for the current key space and selected keypair. +pub struct Session { + pub current_space: Option, + pub selected_keypair: Option, +} + +impl Default for Session { + fn default() -> Self { + Session { + current_space: None, + selected_keypair: None, + } + } +} + +/// Global session state. +pub static SESSION: Lazy> = Lazy::new(|| Mutex::new(Session::default())); + +// Session management and selected keypair operation functions will be added here +/// Creates a new key space with the given name. +pub fn create_space(name: &str) -> Result<(), CryptoError> { + let mut session = SESSION.lock().unwrap(); + + // Create a new space + let space = KeySpace::new(name); + + // Set as current space + session.current_space = Some(space); + session.selected_keypair = None; + + Ok(()) +} + +/// Sets the current key space. +pub fn set_current_space(space: KeySpace) -> Result<(), CryptoError> { + let mut session = SESSION.lock().unwrap(); + session.current_space = Some(space); + session.selected_keypair = None; + Ok(()) +} + +/// Gets the current key space. +pub fn get_current_space() -> Result { + let session = SESSION.lock().unwrap(); + session + .current_space + .clone() + .ok_or(CryptoError::NoActiveSpace) +} + +/// Clears the current session (logout). +pub fn clear_session() { + let mut session = SESSION.lock().unwrap(); + session.current_space = None; + session.selected_keypair = None; +} + +/// Creates a new keypair in the current space. +pub fn create_keypair(name: &str) -> Result<(), CryptoError> { + let mut session = SESSION.lock().unwrap(); + + if let Some(ref mut space) = session.current_space { + if space.keypairs.contains_key(name) { + return Err(CryptoError::KeypairAlreadyExists(name.to_string())); + } + + let keypair = KeyPair::new(name); + space.keypairs.insert(name.to_string(), keypair); + + // Automatically select the new keypair + session.selected_keypair = Some(name.to_string()); + + Ok(()) + } else { + Err(CryptoError::NoActiveSpace) + } +} + +/// Selects a keypair for use. +pub fn select_keypair(name: &str) -> Result<(), CryptoError> { + let mut session = SESSION.lock().unwrap(); + + if let Some(ref space) = session.current_space { + if !space.keypairs.contains_key(name) { + return Err(CryptoError::KeypairNotFound(name.to_string())); + } + + session.selected_keypair = Some(name.to_string()); + Ok(()) + } else { + Err(CryptoError::NoActiveSpace) + } +} + +/// Gets the currently selected keypair. +pub fn get_selected_keypair() -> Result { + let session = SESSION.lock().unwrap(); + + if let Some(ref space) = session.current_space { + if let Some(ref keypair_name) = session.selected_keypair { + if let Some(keypair) = space.keypairs.get(keypair_name) { + return Ok(keypair.clone()); + } + return Err(CryptoError::KeypairNotFound(keypair_name.clone())); + } + return Err(CryptoError::NoKeypairSelected); + } + + Err(CryptoError::NoActiveSpace) +} + +/// Lists all keypair names in the current space. +pub fn list_keypairs() -> Result, CryptoError> { + let session = SESSION.lock().unwrap(); + + if let Some(ref space) = session.current_space { + Ok(space.keypairs.keys().cloned().collect()) + } else { + Err(CryptoError::NoActiveSpace) + } +} + +/// Gets the public key of the selected keypair. +pub fn keypair_pub_key() -> Result, CryptoError> { + let keypair = get_selected_keypair()?; + Ok(keypair.pub_key()) +} + +/// Derives a public key from a private key. +pub fn derive_public_key(private_key: &[u8]) -> Result, CryptoError> { + KeyPair::pub_key_from_private(private_key) +} + +/// Signs a message with the selected keypair. +pub fn keypair_sign(message: &[u8]) -> Result, CryptoError> { + let keypair = get_selected_keypair()?; + Ok(keypair.sign(message)) +} + +/// Verifies a message signature with the selected keypair. +pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result { + let keypair = get_selected_keypair()?; + keypair.verify(message, signature_bytes) +} + +/// Verifies a message signature with a public key. +pub fn verify_with_public_key( + public_key: &[u8], + message: &[u8], + signature_bytes: &[u8], +) -> Result { + KeyPair::verify_with_public_key(public_key, message, signature_bytes) +} + +/// Encrypts a message for a recipient using their public key. +pub fn encrypt_asymmetric( + recipient_public_key: &[u8], + message: &[u8], +) -> Result, CryptoError> { + let keypair = get_selected_keypair()?; + keypair.encrypt_asymmetric(recipient_public_key, message) +} + +/// Decrypts a message that was encrypted with the current keypair's public key. +pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result, CryptoError> { + let keypair = get_selected_keypair()?; + keypair.decrypt_asymmetric(ciphertext) +} diff --git a/vault/src/keyspace.bak/spec.md b/vault/src/keyspace.bak/spec.md new file mode 100644 index 0000000..add2d42 --- /dev/null +++ b/vault/src/keyspace.bak/spec.md @@ -0,0 +1,36 @@ +# Keyspace Module Specification + +This document explains the purpose and functionality of the `keyspace` module within the Hero Vault. + +## Purpose of the Module + +The `keyspace` module provides a secure and organized way to manage cryptographic keypairs. It allows for the creation, storage, loading, and utilization of keypairs within designated containers called keyspaces. This module is essential for handling sensitive cryptographic material securely. + +## What is a Keyspace? + +A keyspace is a logical container designed to hold multiple cryptographic keypairs. It is represented by the `KeySpace` struct in the code. Keyspaces can be encrypted and persisted to disk, providing a secure method for storing collections of keypairs. Each keyspace is identified by a unique name. + +## What is a Keypair? + +A keypair, represented by the `KeyPair` struct, is a fundamental cryptographic element consisting of a mathematically linked pair of keys: a public key and a private key. In this module, ECDSA (Elliptic Curve Digital Signature Algorithm) keypairs are used. + +* **Private Key:** This key is kept secret and is used for operations like signing data or decrypting messages intended for the keypair's owner. +* **Public Key:** This key can be shared openly and is used to verify signatures created by the corresponding private key or to encrypt messages that can only be decrypted by the private key. + +## How Many Keypairs Per Space? + +A keyspace can hold multiple keypairs. The `KeySpace` struct uses a `HashMap` to store keypairs, where each keypair is associated with a unique string name. There is no inherent, fixed limit on the number of keypairs a keyspace can contain, beyond the practical limitations of system memory. + +## How Do We Load Them? + +Keyspaces are loaded from persistent storage (disk) using the `KeySpace::load` function, which requires the keyspace name and a password for decryption. Once a `KeySpace` object is loaded into memory, it can be set as the currently active keyspace for the session using the `session_manager::set_current_space` function. Individual keypairs within the loaded keyspace are then accessed by their names using functions like `session_manager::select_keypair` and `session_manager::get_selected_keypair`. + +## What Do They Do? + +Keypairs within a keyspace are used to perform various cryptographic operations. The `KeyPair` struct provides methods for: + +* **Digital Signatures:** Signing messages with the private key (`KeyPair::sign`) and verifying those signatures with the public key (`KeyPair::verify`). +* **Ethereum Address Derivation:** Generating an Ethereum address from the public key (`KeyPair::to_ethereum_address`). +* **Asymmetric Encryption/Decryption:** Encrypting data using a recipient's public key (`KeyPair::encrypt_asymmetric`) and decrypting data encrypted with the keypair's public key using the private key (`KeyPair::decrypt_asymmetric`). + +The `session_manager` module provides functions that utilize the currently selected keypair to perform these operations within the context of the active session. \ No newline at end of file diff --git a/vault/src/keyspace.bak/tests/keypair_types_tests.rs b/vault/src/keyspace.bak/tests/keypair_types_tests.rs new file mode 100644 index 0000000..01752b6 --- /dev/null +++ b/vault/src/keyspace.bak/tests/keypair_types_tests.rs @@ -0,0 +1,86 @@ + +use crate::vault::keyspace::keypair_types::{KeyPair, KeySpace}; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_keypair_creation() { + let keypair = KeyPair::new("test_keypair"); + assert_eq!(keypair.name, "test_keypair"); + // Basic check that keys are generated (they should have non-zero length) + assert!(!keypair.pub_key().is_empty()); + } + + #[test] + fn test_keypair_sign_and_verify() { + let keypair = KeyPair::new("test_keypair"); + let message = b"This is a test message"; + let signature = keypair.sign(message); + assert!(!signature.is_empty()); + + let is_valid = keypair.verify(message, &signature).expect("Verification failed"); + assert!(is_valid); + + // Test with a wrong message + let wrong_message = b"This is a different message"; + let is_valid_wrong = keypair.verify(wrong_message, &signature).expect("Verification failed with wrong message"); + assert!(!is_valid_wrong); + } + + #[test] + fn test_verify_with_public_key() { + let keypair = KeyPair::new("test_keypair"); + let message = b"Another test message"; + let signature = keypair.sign(message); + let public_key = keypair.pub_key(); + + let is_valid = KeyPair::verify_with_public_key(&public_key, message, &signature).expect("Verification with public key failed"); + assert!(is_valid); + + // Test with a wrong public key + let wrong_keypair = KeyPair::new("wrong_keypair"); + let wrong_public_key = wrong_keypair.pub_key(); + let is_valid_wrong_key = KeyPair::verify_with_public_key(&wrong_public_key, message, &signature).expect("Verification with wrong public key failed"); + assert!(!is_valid_wrong_key); + } + + #[test] + fn test_asymmetric_encryption_decryption() { + // Sender's keypair + let sender_keypair = KeyPair::new("sender"); + let sender_public_key = sender_keypair.pub_key(); + + // Recipient's keypair + let recipient_keypair = KeyPair::new("recipient"); + let recipient_public_key = recipient_keypair.pub_key(); + + let message = b"This is a secret message"; + + // Sender encrypts for recipient + let ciphertext = sender_keypair.encrypt_asymmetric(&recipient_public_key, message).expect("Encryption failed"); + assert!(!ciphertext.is_empty()); + + // Recipient decrypts + let decrypted_message = recipient_keypair.decrypt_asymmetric(&ciphertext).expect("Decryption failed"); + assert_eq!(decrypted_message, message); + + // Test decryption with wrong keypair + let wrong_keypair = KeyPair::new("wrong_recipient"); + let result = wrong_keypair.decrypt_asymmetric(&ciphertext); + assert!(result.is_err()); + } + + #[test] + fn test_keyspace_add_keypair() { + let mut space = KeySpace::new("test_space"); + space.add_keypair("keypair1").expect("Failed to add keypair1"); + assert_eq!(space.keypairs.len(), 1); + assert!(space.keypairs.contains_key("keypair1")); + + // Test adding a duplicate keypair + let result = space.add_keypair("keypair1"); + assert!(result.is_err()); + } +} \ No newline at end of file diff --git a/vault/src/keyspace.bak/tests/mod.rs b/vault/src/keyspace.bak/tests/mod.rs new file mode 100644 index 0000000..770d0e5 --- /dev/null +++ b/vault/src/keyspace.bak/tests/mod.rs @@ -0,0 +1,3 @@ + +mod keypair_types_tests; +mod session_manager_tests; \ No newline at end of file diff --git a/vault/src/keyspace.bak/tests/session_manager_tests.rs b/vault/src/keyspace.bak/tests/session_manager_tests.rs new file mode 100644 index 0000000..66ba44f --- /dev/null +++ b/vault/src/keyspace.bak/tests/session_manager_tests.rs @@ -0,0 +1,111 @@ +use crate::vault::keyspace::session_manager::{ + clear_session, create_keypair, create_space, get_current_space, get_selected_keypair, + list_keypairs, select_keypair, set_current_space, SESSION, +}; +use crate::vault::keyspace::keypair_types::KeySpace; + +// Helper function to clear the session before each test +fn setup_test() { + clear_session(); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_and_get_space() { + setup_test(); + create_space("test_space").expect("Failed to create space"); + let space = get_current_space().expect("Failed to get current space"); + assert_eq!(space.name, "test_space"); + } + + #[test] + fn test_set_current_space() { + setup_test(); + let space = KeySpace::new("another_space"); + set_current_space(space.clone()).expect("Failed to set current space"); + let current_space = get_current_space().expect("Failed to get current space"); + assert_eq!(current_space.name, "another_space"); + } + + #[test] + fn test_clear_session() { + setup_test(); + create_space("test_space").expect("Failed to create space"); + clear_session(); + let result = get_current_space(); + assert!(result.is_err()); + } + + #[test] + fn test_create_and_select_keypair() { + setup_test(); + create_space("test_space").expect("Failed to create space"); + create_keypair("test_keypair").expect("Failed to create keypair"); + let keypair = get_selected_keypair().expect("Failed to get selected keypair"); + assert_eq!(keypair.name, "test_keypair"); + + select_keypair("test_keypair").expect("Failed to select keypair"); + let selected_keypair = get_selected_keypair().expect("Failed to get selected keypair after select"); + assert_eq!(selected_keypair.name, "test_keypair"); + } + + #[test] + fn test_list_keypairs() { + setup_test(); + create_space("test_space").expect("Failed to create space"); + create_keypair("keypair1").expect("Failed to create keypair1"); + create_keypair("keypair2").expect("Failed to create keypair2"); + + let keypairs = list_keypairs().expect("Failed to list keypairs"); + assert_eq!(keypairs.len(), 2); + assert!(keypairs.contains(&"keypair1".to_string())); + assert!(keypairs.contains(&"keypair2".to_string())); + } + + #[test] + fn test_create_keypair_no_active_space() { + setup_test(); + let result = create_keypair("test_keypair"); + assert!(result.is_err()); + } + + #[test] + fn test_select_keypair_no_active_space() { + setup_test(); + let result = select_keypair("test_keypair"); + assert!(result.is_err()); + } + + #[test] + fn test_select_nonexistent_keypair() { + setup_test(); + create_space("test_space").expect("Failed to create space"); + let result = select_keypair("nonexistent_keypair"); + assert!(result.is_err()); + } + + #[test] + fn test_get_selected_keypair_no_active_space() { + setup_test(); + let result = get_selected_keypair(); + assert!(result.is_err()); + } + + #[test] + fn test_get_selected_keypair_no_keypair_selected() { + setup_test(); + create_space("test_space").expect("Failed to create space"); + let result = get_selected_keypair(); + assert!(result.is_err()); + } + + #[test] + fn test_list_keypairs_no_active_space() { + setup_test(); + let result = list_keypairs(); + assert!(result.is_err()); + } +} diff --git a/vault/src/kvs.bak/README.md b/vault/src/kvs.bak/README.md new file mode 100644 index 0000000..431a24c --- /dev/null +++ b/vault/src/kvs.bak/README.md @@ -0,0 +1,173 @@ +# Hero Vault Key-Value Store Module + +The Key-Value Store (KVS) module provides an encrypted key-value store for securely storing sensitive data. + +## Module Structure + +The KVS module is organized into: + +- `store.rs` - Core implementation of the key-value store +- `error.rs` - Error types specific to the KVS module +- `mod.rs` - Module exports and public interface + +## Key Types + +### KvStore + +The `KvStore` type represents an encrypted key-value store: + +```rust +pub struct KvStore { + // Private fields + // ... +} + +impl KvStore { + // Create a new store + pub fn new(name: &str, password: &str) -> Result; + + // Load a store from disk + pub fn load(name: &str, password: &str) -> Result; + + // Save the store to disk + pub fn save(&self) -> Result<(), CryptoError>; + + // Set a value + pub fn set(&mut self, key: &str, value: &str) -> Result<(), CryptoError>; + + // Get a value + pub fn get(&self, key: &str) -> Result, CryptoError>; + + // Delete a value + pub fn delete(&mut self, key: &str) -> Result<(), CryptoError>; + + // Check if a key exists + pub fn has(&self, key: &str) -> Result; + + // List all keys + pub fn keys(&self) -> Result, CryptoError>; + + // Clear all values + pub fn clear(&mut self) -> Result<(), CryptoError>; + + // Get the name of the store + pub fn name(&self) -> &str; +} +``` + +## Key Features + +### Store Management + +The module provides functionality for creating, loading, and managing key-value stores: + +```rust +// Create a new store +let mut store = KvStore::new("my_store", "secure_password")?; + +// Save the store to disk +store.save()?; + +// Load a store from disk +let mut loaded_store = KvStore::load("my_store", "secure_password")?; +``` + +### Value Management + +The module provides functionality for managing values in the store: + +```rust +// Set a value +store.set("api_key", "secret_api_key")?; + +// Get a value +let api_key = store.get("api_key")?; + +// Delete a value +store.delete("api_key")?; + +// Check if a key exists +let exists = store.has("api_key")?; + +// List all keys +let keys = store.keys()?; + +// Clear all values +store.clear()?; +``` + +## Technical Details + +### Encryption + +The KVS module uses the Symmetric Encryption module to encrypt all values stored in the key-value store. This ensures that sensitive data is protected at rest. + +The encryption process: + +1. A master key is derived from the provided password using PBKDF2 +2. Each value is encrypted using ChaCha20Poly1305 with a unique key derived from the master key and the value's key +3. The encrypted values are stored in a JSON file on disk + +### Storage Format + +The key-value store is stored in a JSON file with the following structure: + +```json +{ + "name": "my_store", + "salt": "base64-encoded-salt", + "values": { + "key1": "base64-encoded-encrypted-value", + "key2": "base64-encoded-encrypted-value", + ... + } +} +``` + +The file is stored in the `~/.hero-vault/stores/` directory by default. + +## Security Considerations + +- Use strong passwords to protect the key-value store +- The security of the store depends on the strength of the password +- Consider the security implications of storing sensitive data on disk +- Regularly backup the store to prevent data loss + +## Error Handling + +The module uses the `CryptoError` type for handling errors that can occur during key-value store operations: + +- `EncryptionFailed` - Encryption failed +- `DecryptionFailed` - Decryption failed +- `SerializationError` - Serialization error + +## Examples + +For examples of how to use the KVS module, see the `examples/hero_vault` directory. While there may not be specific examples for the KVS module, the general pattern of usage is similar to the key space management examples. + +A basic usage example: + +```rust +// Create a new store +let mut store = KvStore::new("my_store", "secure_password")?; + +// Set some values +store.set("api_key", "secret_api_key")?; +store.set("access_token", "secret_access_token")?; + +// Save the store to disk +store.save()?; + +// Later, load the store +let loaded_store = KvStore::load("my_store", "secure_password")?; + +// Get a value +let api_key = loaded_store.get("api_key")?; +println!("API Key: {}", api_key.unwrap_or_default()); +``` + +## to test + +```bash +cargo test --lib vault::keypair +``` \ No newline at end of file diff --git a/vault/src/kvs.bak/error.rs b/vault/src/kvs.bak/error.rs new file mode 100644 index 0000000..bbd6eaf --- /dev/null +++ b/vault/src/kvs.bak/error.rs @@ -0,0 +1,67 @@ +//! Error types for the key-value store. + +use thiserror::Error; + +/// Errors that can occur when using the key-value store. +#[derive(Debug, Error)] +pub enum KvsError { + /// I/O error + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + /// Key not found + #[error("Key not found: {0}")] + KeyNotFound(String), + + /// Store not found + #[error("Store not found: {0}")] + StoreNotFound(String), + + /// Serialization error + #[error("Serialization error: {0}")] + Serialization(String), + + /// Deserialization error + #[error("Deserialization error: {0}")] + Deserialization(String), + + /// Encryption error + #[error("Encryption error: {0}")] + Encryption(String), + + /// Decryption error + #[error("Decryption error: {0}")] + Decryption(String), + + /// Other error + #[error("Error: {0}")] + Other(String), +} + +impl From for KvsError { + fn from(err: serde_json::Error) -> Self { + KvsError::Serialization(err.to_string()) + } +} + +impl From for crate::vault::error::CryptoError { + fn from(err: KvsError) -> Self { + crate::vault::error::CryptoError::SerializationError(err.to_string()) + } +} + +impl From for KvsError { + fn from(err: crate::vault::error::CryptoError) -> Self { + match err { + crate::vault::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg), + crate::vault::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg), + crate::vault::error::CryptoError::SerializationError(msg) => { + KvsError::Serialization(msg) + } + _ => KvsError::Other(err.to_string()), + } + } +} + +/// Result type for key-value store operations. +pub type Result = std::result::Result; diff --git a/vault/src/kvs.bak/mod.rs b/vault/src/kvs.bak/mod.rs new file mode 100644 index 0000000..90e85b9 --- /dev/null +++ b/vault/src/kvs.bak/mod.rs @@ -0,0 +1,17 @@ +//! Key-Value Store functionality +//! +//! This module provides a simple key-value store with encryption support. + +pub mod error; +pub mod store; + +// Re-export public types and functions +pub use error::KvsError; +pub use store::{ + KvStore, KvPair, + create_store, open_store, delete_store, + list_stores, get_store_path +}; + +#[cfg(test)] +mod tests; diff --git a/vault/src/kvs.bak/store.rs b/vault/src/kvs.bak/store.rs new file mode 100644 index 0000000..74c9c6f --- /dev/null +++ b/vault/src/kvs.bak/store.rs @@ -0,0 +1,370 @@ +//! Implementation of a simple key-value store using the filesystem. + +use crate::vault::kvs::error::{KvsError, Result}; +use crate::vault::symmetric::implementation::{ + decrypt_symmetric, derive_key_from_password, encrypt_symmetric, +}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +/// A key-value pair. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KvPair { + pub key: String, + pub value: String, +} + +/// A simple key-value store. +/// +/// This implementation uses the filesystem to store key-value pairs. +#[derive(Clone)] +pub struct KvStore { + /// The name of the store + name: String, + /// The path to the store file + path: PathBuf, + /// In-memory cache of the store data + data: Arc>>, + /// Whether the store is encrypted + encrypted: bool, + /// The password for encryption (if encrypted) + password: Option, +} + +/// Gets the path to the key-value store directory. +pub fn get_store_path() -> PathBuf { + let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + home_dir.join(".hero-vault").join("kvs") +} + +/// Creates a new key-value store with the given name. +/// +/// # Arguments +/// +/// * `name` - The name of the store +/// * `encrypted` - Whether to encrypt the store +/// * `password` - The password for encryption (required if encrypted is true) +/// +/// # Returns +/// +/// A new `KvStore` instance +pub fn create_store(name: &str, encrypted: bool, password: Option<&str>) -> Result { + // Check if password is provided when encryption is enabled + if encrypted && password.is_none() { + return Err(KvsError::Other( + "Password required for encrypted store".to_string(), + )); + } + + // Create the store directory if it doesn't exist + let store_dir = get_store_path(); + if !store_dir.exists() { + fs::create_dir_all(&store_dir)?; + } + + // Create the store file path + let store_path = store_dir.join(format!("{}.json", name)); + + // Create an empty store + let store = KvStore { + name: name.to_string(), + path: store_path, + data: Arc::new(Mutex::new(HashMap::new())), + encrypted, + password: password.map(|s| s.to_string()), + }; + + // Save the empty store + store.save()?; + + Ok(store) +} + +/// Opens an existing key-value store with the given name. +/// +/// # Arguments +/// +/// * `name` - The name of the store +/// * `password` - The password for decryption (required if the store is encrypted) +/// +/// # Returns +/// +/// The opened `KvStore` instance +pub fn open_store(name: &str, password: Option<&str>) -> Result { + // Get the store file path + let store_dir = get_store_path(); + let store_path = store_dir.join(format!("{}.json", name)); + + // Check if the store exists + if !store_path.exists() { + return Err(KvsError::StoreNotFound(name.to_string())); + } + + // Read the store file + let file_content = fs::read_to_string(&store_path)?; + + // Check if the file is encrypted (simple heuristic) + let is_encrypted = !file_content.starts_with('{'); + + // If encrypted, we need a password + if is_encrypted && password.is_none() { + return Err(KvsError::Other( + "Password required for encrypted store".to_string(), + )); + } + + // Parse the store data + let data: HashMap = if is_encrypted { + // Decrypt the file content + let password = password.unwrap(); + let encrypted_data: Vec = serde_json::from_str(&file_content)?; + let key = derive_key_from_password(password); + let decrypted_data = decrypt_symmetric(&key, &encrypted_data)?; + let decrypted_str = String::from_utf8(decrypted_data) + .map_err(|e| KvsError::Deserialization(e.to_string()))?; + serde_json::from_str(&decrypted_str)? + } else { + serde_json::from_str(&file_content)? + }; + + // Create the store + let store = KvStore { + name: name.to_string(), + path: store_path, + data: Arc::new(Mutex::new(data)), + encrypted: is_encrypted, + password: password.map(|s| s.to_string()), + }; + + Ok(store) +} + +/// Deletes a key-value store with the given name. +/// +/// # Arguments +/// +/// * `name` - The name of the store to delete +/// +/// # Returns +/// +/// `Ok(())` if the operation was successful +pub fn delete_store(name: &str) -> Result<()> { + // Get the store file path + let store_dir = get_store_path(); + let store_path = store_dir.join(format!("{}.json", name)); + + // Check if the store exists + if !store_path.exists() { + return Err(KvsError::StoreNotFound(name.to_string())); + } + + // Delete the store file + fs::remove_file(store_path)?; + + Ok(()) +} + +/// Lists all available key-value stores. +/// +/// # Returns +/// +/// A vector of store names +pub fn list_stores() -> Result> { + // Get the store directory + let store_dir = get_store_path(); + if !store_dir.exists() { + return Ok(Vec::new()); + } + + // List all JSON files in the directory + let mut stores = Vec::new(); + for entry in fs::read_dir(store_dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() && path.extension().map_or(false, |ext| ext == "json") { + if let Some(name) = path.file_stem() { + if let Some(name_str) = name.to_str() { + stores.push(name_str.to_string()); + } + } + } + } + + Ok(stores) +} + +impl KvStore { + /// Saves the store to disk. + fn save(&self) -> Result<()> { + // Get the store data + let data = self.data.lock().unwrap(); + + // Serialize the data + let serialized = serde_json::to_string(&*data)?; + + // Write to file + if self.encrypted { + if let Some(password) = &self.password { + // Encrypt the data + let key = derive_key_from_password(password); + let encrypted_data = encrypt_symmetric(&key, serialized.as_bytes())?; + let encrypted_json = serde_json::to_string(&encrypted_data)?; + fs::write(&self.path, encrypted_json)?; + } else { + return Err(KvsError::Other( + "Password required for encrypted store".to_string(), + )); + } + } else { + fs::write(&self.path, serialized)?; + } + + Ok(()) + } + + /// Stores a value with the given key. + /// + /// # Arguments + /// + /// * `key` - The key to store the value under + /// * `value` - The value to store + /// + /// # Returns + /// + /// `Ok(())` if the operation was successful + pub fn set(&self, key: K, value: &V) -> Result<()> + where + K: ToString, + V: Serialize, + { + let key_str = key.to_string(); + let serialized = serde_json::to_string(value)?; + + // Update in-memory data + { + let mut data = self.data.lock().unwrap(); + data.insert(key_str, serialized); + } + + // Save to disk + self.save()?; + + Ok(()) + } + + /// Retrieves a value for the given key. + /// + /// # Arguments + /// + /// * `key` - The key to retrieve the value for + /// + /// # Returns + /// + /// The value if found, or `Err(KvsError::KeyNotFound)` if not found + pub fn get(&self, key: K) -> Result + where + K: ToString, + V: DeserializeOwned, + { + let key_str = key.to_string(); + let data = self.data.lock().unwrap(); + + match data.get(&key_str) { + Some(serialized) => { + let value: V = serde_json::from_str(serialized)?; + Ok(value) + } + None => Err(KvsError::KeyNotFound(key_str)), + } + } + + /// Deletes a value for the given key. + /// + /// # Arguments + /// + /// * `key` - The key to delete + /// + /// # Returns + /// + /// `Ok(())` if the operation was successful + pub fn delete(&self, key: K) -> Result<()> + where + K: ToString, + { + let key_str = key.to_string(); + + // Update in-memory data + { + let mut data = self.data.lock().unwrap(); + if data.remove(&key_str).is_none() { + return Err(KvsError::KeyNotFound(key_str)); + } + } + + // Save to disk + self.save()?; + + Ok(()) + } + + /// Checks if a key exists in the store. + /// + /// # Arguments + /// + /// * `key` - The key to check + /// + /// # Returns + /// + /// `true` if the key exists, `false` otherwise + pub fn contains(&self, key: K) -> Result + where + K: ToString, + { + let key_str = key.to_string(); + let data = self.data.lock().unwrap(); + + Ok(data.contains_key(&key_str)) + } + + /// Lists all keys in the store. + /// + /// # Returns + /// + /// A vector of keys as strings + pub fn keys(&self) -> Result> { + let data = self.data.lock().unwrap(); + + Ok(data.keys().cloned().collect()) + } + + /// Clears all key-value pairs from the store. + /// + /// # Returns + /// + /// `Ok(())` if the operation was successful + pub fn clear(&self) -> Result<()> { + // Update in-memory data + { + let mut data = self.data.lock().unwrap(); + data.clear(); + } + + // Save to disk + self.save()?; + + Ok(()) + } + + /// Gets the name of the store. + pub fn name(&self) -> &str { + &self.name + } + + /// Gets whether the store is encrypted. + pub fn is_encrypted(&self) -> bool { + self.encrypted + } +} diff --git a/vault/src/kvs.bak/tests/mod.rs b/vault/src/kvs.bak/tests/mod.rs new file mode 100644 index 0000000..668dbed --- /dev/null +++ b/vault/src/kvs.bak/tests/mod.rs @@ -0,0 +1 @@ +mod store_tests; \ No newline at end of file diff --git a/vault/src/kvs.bak/tests/store_tests.rs b/vault/src/kvs.bak/tests/store_tests.rs new file mode 100644 index 0000000..5a972bf --- /dev/null +++ b/vault/src/kvs.bak/tests/store_tests.rs @@ -0,0 +1,105 @@ +use crate::vault::kvs::store::{create_store, delete_store, open_store, KvStore}; +use std::path::PathBuf; + +// Helper function to generate a unique store name for each test +fn generate_test_store_name() -> String { + use rand::Rng; + let random_string: String = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(10) + .map(char::from) + .collect(); + format!("test_store_{}", random_string) +} + +// Helper function to clean up test stores +fn cleanup_test_store(name: &str) { + let _ = delete_store(name); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_and_open_store() { + let store_name = generate_test_store_name(); + let store = create_store(&store_name, false, None).expect("Failed to create store"); + assert_eq!(store.name(), store_name); + assert!(!store.is_encrypted()); + + let opened_store = open_store(&store_name, None).expect("Failed to open store"); + assert_eq!(opened_store.name(), store_name); + assert!(!opened_store.is_encrypted()); + + cleanup_test_store(&store_name); + } + + #[test] + fn test_set_and_get_value() { + let store_name = generate_test_store_name(); + let store = create_store(&store_name, false, None).expect("Failed to create store"); + + store.set("key1", &"value1").expect("Failed to set value"); + let value: String = store.get("key1").expect("Failed to get value"); + assert_eq!(value, "value1"); + + cleanup_test_store(&store_name); + } + + #[test] + fn test_delete_value() { + let store_name = generate_test_store_name(); + let store = create_store(&store_name, false, None).expect("Failed to create store"); + + store.set("key1", &"value1").expect("Failed to set value"); + store.delete("key1").expect("Failed to delete value"); + let result: Result = store.get("key1"); + assert!(result.is_err()); + + cleanup_test_store(&store_name); + } + + #[test] + fn test_contains_key() { + let store_name = generate_test_store_name(); + let store = create_store(&store_name, false, None).expect("Failed to create store"); + + store.set("key1", &"value1").expect("Failed to set value"); + assert!(store.contains("key1").expect("Failed to check contains")); + assert!(!store.contains("key2").expect("Failed to check contains")); + + cleanup_test_store(&store_name); + } + + #[test] + fn test_list_keys() { + let store_name = generate_test_store_name(); + let store = create_store(&store_name, false, None).expect("Failed to create store"); + + store.set("key1", &"value1").expect("Failed to set value"); + store.set("key2", &"value2").expect("Failed to set value"); + + let keys = store.keys().expect("Failed to list keys"); + assert_eq!(keys.len(), 2); + assert!(keys.contains(&"key1".to_string())); + assert!(keys.contains(&"key2".to_string())); + + cleanup_test_store(&store_name); + } + + #[test] + fn test_clear_store() { + let store_name = generate_test_store_name(); + let store = create_store(&store_name, false, None).expect("Failed to create store"); + + store.set("key1", &"value1").expect("Failed to set value"); + store.set("key2", &"value2").expect("Failed to set value"); + + store.clear().expect("Failed to clear store"); + let keys = store.keys().expect("Failed to list keys after clear"); + assert!(keys.is_empty()); + + cleanup_test_store(&store_name); + } +} \ No newline at end of file diff --git a/vault/src/kvs.rs b/vault/src/kvs.rs new file mode 100644 index 0000000..4ff117e --- /dev/null +++ b/vault/src/kvs.rs @@ -0,0 +1,3 @@ +/// Store is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where +/// each [`space`](KeySpace) is itself an encrypted key-value store +pub struct Store {} diff --git a/vault/src/lib.rs b/vault/src/lib.rs new file mode 100644 index 0000000..7183a6f --- /dev/null +++ b/vault/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod key; +pub mod kvs; diff --git a/vault/src/symmetric.bak/README.md b/vault/src/symmetric.bak/README.md new file mode 100644 index 0000000..18359ec --- /dev/null +++ b/vault/src/symmetric.bak/README.md @@ -0,0 +1,98 @@ +# Hero Vault Symmetric Encryption Module + +The Symmetric Encryption module provides functionality for symmetric encryption and decryption using the ChaCha20Poly1305 algorithm. + +## Module Structure + +The Symmetric Encryption module is organized into: + +- `implementation.rs` - Core implementation of symmetric encryption functionality +- `mod.rs` - Module exports and public interface + +## Key Features + +### Key Generation + +The module provides functionality for generating secure symmetric keys: + +```rust +// Generate a new symmetric key +let key = generate_key()?; +``` + +### Encryption + +The module provides functionality for encrypting data using ChaCha20Poly1305: + +```rust +// Encrypt data +let encrypted = encrypt(&key, "This is a secret message")?; +``` + +### Decryption + +The module provides functionality for decrypting data encrypted with ChaCha20Poly1305: + +```rust +// Decrypt data +let decrypted = decrypt(&key, &encrypted)?; +``` + +### Password-Based Key Derivation + +The module provides functionality for deriving encryption keys from passwords: + +```rust +// Derive a key from a password +let key = derive_key_from_password(password, salt)?; +``` + +## Technical Details + +### ChaCha20Poly1305 + +The module uses the ChaCha20Poly1305 authenticated encryption with associated data (AEAD) algorithm, which provides both confidentiality and integrity protection. + +ChaCha20 is a stream cipher designed by Daniel J. Bernstein, which is combined with the Poly1305 message authentication code to provide authenticated encryption. + +Key features of ChaCha20Poly1305: + +- 256-bit key +- 96-bit nonce (used once) +- Authentication tag to verify integrity +- High performance on modern processors +- Resistance to timing attacks + +### Key Derivation + +For password-based encryption, the module uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to derive encryption keys from passwords. + +Key features of PBKDF2: + +- Configurable iteration count to increase computational cost +- Salt to prevent rainbow table attacks +- Configurable output key length +- Uses HMAC-SHA256 as the underlying pseudorandom function + +## Security Considerations + +- Always use a unique key for each encryption operation +- Never reuse nonces with the same key +- Store keys securely +- Use strong passwords for password-based encryption +- Consider the security implications of storing encrypted data + +## Error Handling + +The module uses the `CryptoError` type for handling errors that can occur during symmetric encryption operations: + +- `InvalidKeyLength` - Invalid key length +- `EncryptionFailed` - Encryption failed +- `DecryptionFailed` - Decryption failed + +## Examples + +For examples of how to use the Symmetric Encryption module, see the `examples/hero_vault` directory, particularly: + +- `example.rhai` - Basic example demonstrating symmetric encryption +- `advanced_example.rhai` - Advanced example with error handling diff --git a/vault/src/symmetric.bak/implementation.rs b/vault/src/symmetric.bak/implementation.rs new file mode 100644 index 0000000..2fa9520 --- /dev/null +++ b/vault/src/symmetric.bak/implementation.rs @@ -0,0 +1,266 @@ +//! Implementation of symmetric encryption functionality. + +use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce}; +use chacha20poly1305::aead::Aead; +use rand::{rngs::OsRng, RngCore}; +use serde::{Serialize, Deserialize}; +use sha2::{Sha256, Digest}; + +use crate::vault::error::CryptoError; +use crate::vault::keyspace::KeySpace; + +/// The size of the nonce in bytes. +const NONCE_SIZE: usize = 12; + +/// Generates a random 32-byte symmetric key. +/// +/// # Returns +/// +/// A 32-byte array containing the random key. +pub fn generate_symmetric_key() -> [u8; 32] { + let mut key = [0u8; 32]; + OsRng.fill_bytes(&mut key); + key +} + +/// Derives a 32-byte key from a password. +/// +/// # Arguments +/// +/// * `password` - The password to derive the key from. +/// +/// # Returns +/// +/// A 32-byte array containing the derived key. +pub fn derive_key_from_password(password: &str) -> [u8; 32] { + let mut hasher = Sha256::default(); + hasher.update(password.as_bytes()); + let result = hasher.finalize(); + + let mut key = [0u8; 32]; + key.copy_from_slice(&result); + key +} + +/// Encrypts data using ChaCha20Poly1305 with an internally generated nonce. +/// +/// The nonce is appended to the ciphertext so it can be extracted during decryption. +/// +/// # Arguments +/// +/// * `key` - The encryption key (should be 32 bytes). +/// * `message` - The message to encrypt. +/// +/// # Returns +/// +/// * `Ok(Vec)` containing the ciphertext with the nonce appended. +/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. +/// * `Err(CryptoError::EncryptionFailed)` if encryption fails. +pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, CryptoError> { + // Create cipher + let cipher = ChaCha20Poly1305::new_from_slice(key) + .map_err(|_| CryptoError::InvalidKeyLength)?; + + // Generate random nonce + let mut nonce_bytes = [0u8; NONCE_SIZE]; + OsRng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + + // Encrypt message + let ciphertext = cipher.encrypt(nonce, message) + .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?; + + // Append nonce to ciphertext + let mut result = ciphertext; + result.extend_from_slice(&nonce_bytes); + + Ok(result) +} + +/// Decrypts data using ChaCha20Poly1305, extracting the nonce from the ciphertext. +/// +/// # Arguments +/// +/// * `key` - The decryption key (should be 32 bytes). +/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended. +/// +/// # Returns +/// +/// * `Ok(Vec)` containing the decrypted message. +/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. +/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short. +pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result, CryptoError> { + // Check if ciphertext is long enough to contain a nonce + if ciphertext_with_nonce.len() <= NONCE_SIZE { + return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string())); + } + + // Extract nonce from the end of ciphertext + let ciphertext_len = ciphertext_with_nonce.len() - NONCE_SIZE; + let ciphertext = &ciphertext_with_nonce[0..ciphertext_len]; + let nonce_bytes = &ciphertext_with_nonce[ciphertext_len..]; + + // Create cipher + let cipher = ChaCha20Poly1305::new_from_slice(key) + .map_err(|_| CryptoError::InvalidKeyLength)?; + + let nonce = Nonce::from_slice(nonce_bytes); + + // Decrypt message + cipher.decrypt(nonce, ciphertext) + .map_err(|e| CryptoError::DecryptionFailed(e.to_string())) +} + +/// Encrypts data using a key directly (for internal use). +/// +/// # Arguments +/// +/// * `key` - The encryption key. +/// * `message` - The message to encrypt. +/// +/// # Returns +/// +/// * `Ok(Vec)` containing the ciphertext with the nonce appended. +/// * `Err(CryptoError)` if encryption fails. +pub fn encrypt_with_key(key: &[u8], message: &[u8]) -> Result, CryptoError> { + encrypt_symmetric(key, message) +} + +/// Decrypts data using a key directly (for internal use). +/// +/// # Arguments +/// +/// * `key` - The decryption key. +/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended. +/// +/// # Returns +/// +/// * `Ok(Vec)` containing the decrypted message. +/// * `Err(CryptoError)` if decryption fails. +pub fn decrypt_with_key(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result, CryptoError> { + decrypt_symmetric(key, ciphertext_with_nonce) +} + +/// Metadata for an encrypted key space. +#[derive(Serialize, Deserialize, Debug)] +pub struct EncryptedKeySpaceMetadata { + pub name: String, + pub created_at: u64, + pub last_accessed: u64, +} + +/// An encrypted key space with metadata. +#[derive(Serialize, Deserialize, Debug)] +pub struct EncryptedKeySpace { + pub metadata: EncryptedKeySpaceMetadata, + pub encrypted_data: Vec, +} + +/// Encrypts a key space using a password. +/// +/// # Arguments +/// +/// * `space` - The key space to encrypt. +/// * `password` - The password to encrypt with. +/// +/// # Returns +/// +/// * `Ok(EncryptedKeySpace)` containing the encrypted key space. +/// * `Err(CryptoError)` if encryption fails. +pub fn encrypt_key_space(space: &KeySpace, password: &str) -> Result { + // Serialize the key space + let serialized = match serde_json::to_vec(space) { + Ok(data) => data, + Err(e) => { + log::error!("Serialization error during encryption: {}", e); + return Err(CryptoError::SerializationError(e.to_string())); + } + }; + + // Derive key from password + let key = derive_key_from_password(password); + + // Encrypt the serialized data + let encrypted_data = encrypt_symmetric(&key, &serialized)?; + + // Create metadata + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + let metadata = EncryptedKeySpaceMetadata { + name: space.name.clone(), + created_at: now, + last_accessed: now, + }; + + Ok(EncryptedKeySpace { + metadata, + encrypted_data, + }) +} + +/// Decrypts a key space using a password. +/// +/// # Arguments +/// +/// * `encrypted_space` - The encrypted key space. +/// * `password` - The password to decrypt with. +/// +/// # Returns +/// +/// * `Ok(KeySpace)` containing the decrypted key space. +/// * `Err(CryptoError)` if decryption fails. +pub fn decrypt_key_space(encrypted_space: &EncryptedKeySpace, password: &str) -> Result { + // Derive key from password + let key = derive_key_from_password(password); + + // Decrypt the data + let decrypted_data = decrypt_symmetric(&key, &encrypted_space.encrypted_data)?; + + // Deserialize the key space + let space: KeySpace = match serde_json::from_slice(&decrypted_data) { + Ok(space) => space, + Err(e) => { + log::error!("Deserialization error: {}", e); + return Err(CryptoError::SerializationError(e.to_string())); + } + }; + + Ok(space) +} + +/// Serializes an encrypted key space to a JSON string. +/// +/// # Arguments +/// +/// * `encrypted_space` - The encrypted key space to serialize. +/// +/// # Returns +/// +/// * `Ok(String)` containing the serialized encrypted key space. +/// * `Err(CryptoError)` if serialization fails. +pub fn serialize_encrypted_space(encrypted_space: &EncryptedKeySpace) -> Result { + serde_json::to_string(encrypted_space) + .map_err(|e| CryptoError::SerializationError(e.to_string())) +} + +/// Deserializes an encrypted key space from a JSON string. +/// +/// # Arguments +/// +/// * `serialized` - The serialized encrypted key space. +/// +/// # Returns +/// +/// * `Ok(EncryptedKeySpace)` containing the deserialized encrypted key space. +/// * `Err(CryptoError)` if deserialization fails. +pub fn deserialize_encrypted_space(serialized: &str) -> Result { + match serde_json::from_str(serialized) { + Ok(space) => Ok(space), + Err(e) => { + log::error!("Error deserializing encrypted space: {}", e); + Err(CryptoError::SerializationError(e.to_string())) + } + } +} diff --git a/vault/src/symmetric.bak/mod.rs b/vault/src/symmetric.bak/mod.rs new file mode 100644 index 0000000..1d63e3e --- /dev/null +++ b/vault/src/symmetric.bak/mod.rs @@ -0,0 +1,15 @@ +//! Symmetric encryption functionality +//! +//! This module provides functionality for symmetric encryption using ChaCha20Poly1305. + +pub mod implementation; + +// Re-export public types and functions +pub use implementation::{ + generate_symmetric_key, derive_key_from_password, + encrypt_symmetric, decrypt_symmetric, + encrypt_with_key, decrypt_with_key, + encrypt_key_space, decrypt_key_space, + serialize_encrypted_space, deserialize_encrypted_space, + EncryptedKeySpace, EncryptedKeySpaceMetadata +}; From 78c0fd7871d4f72cc5d8c1c4ee1506ce75d9fdb2 Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Wed, 14 May 2025 11:08:37 +0200 Subject: [PATCH 03/11] Define the global KeySpace interface Signed-off-by: Lee Smet --- vault/src/error.rs | 25 ++++++++++++++++- vault/src/keyspace.rs | 50 ++++++++++++++++++++++++++++++++++ vault/src/keyspace/fallback.rs | 2 ++ vault/src/keyspace/wasm.rs | 2 ++ vault/src/lib.rs | 1 + 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 vault/src/keyspace.rs create mode 100644 vault/src/keyspace/fallback.rs create mode 100644 vault/src/keyspace/wasm.rs diff --git a/vault/src/error.rs b/vault/src/error.rs index 24311b5..56df3f5 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -1,3 +1,20 @@ +#[derive(Debug)] +/// Errors encountered while using the vault +pub enum Error { + /// An error during cryptographic operations + Crypto(CryptoError), +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Error::Crypto(e) => f.write_fmt(format_args!("crypto: {e}")), + } + } +} + +impl core::error::Error for Error {} + #[derive(Debug)] /// Errors generated by the vault or keys. /// @@ -18,7 +35,7 @@ pub enum CryptoError { } impl core::fmt::Display for CryptoError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { CryptoError::InvalidKeySize => f.write_str("provided key is not the correct size"), CryptoError::EncryptionFailed => f.write_str("encryption failure"), @@ -33,3 +50,9 @@ impl core::fmt::Display for CryptoError { } impl core::error::Error for CryptoError {} + +impl From for Error { + fn from(value: CryptoError) -> Self { + Self::Crypto(value) + } +} diff --git a/vault/src/keyspace.rs b/vault/src/keyspace.rs new file mode 100644 index 0000000..d121a55 --- /dev/null +++ b/vault/src/keyspace.rs @@ -0,0 +1,50 @@ +#[cfg(target_arch = "wasm32")] +mod wasm; + +#[cfg(not(target_arch = "wasm32"))] +mod fallback; + +#[cfg(target_arch = "wasm32")] +use wasm::KeySpace as KS; + +#[cfg(not(target_arch = "wasm32"))] +use fallback::KeySpace as KS; + +use crate::{error::Error, key::Key}; + +/// A keyspace represents a group of stored cryptographic keys. The storage is encrypted, a +/// password must be provided when opening the KeySpace to decrypt the keys. +pub struct KeySpace { + store: KS, +} + +/// Wasm32 constructor +#[cfg(target_arch = "wasm32")] +impl KeySpace {} + +/// Non-wasm constructor +#[cfg(not(target_arch = "wasm32"))] +impl KeySpace {} + +/// Exposed methods, platform independant +impl KeySpace { + /// Get a [`Key`] previously stored under the provided name. + async fn get(&self, key: &str) -> Result, Error> { + todo!(); + } + + /// Store a [`Key`] under the provided name. + async fn set(&self, key: &str, value: Key) -> Result<(), Error> { + todo!(); + } + + /// Delete the [`Key`] stored under the provided name. + async fn delete(&self, key: &str) -> Result<(), Error> { + todo!(); + } + + /// Iterate over all stored [`keys`](Key) in the KeySpace + async fn iter(&self) -> Result, Error> { + todo!() + } +} diff --git a/vault/src/keyspace/fallback.rs b/vault/src/keyspace/fallback.rs new file mode 100644 index 0000000..851097f --- /dev/null +++ b/vault/src/keyspace/fallback.rs @@ -0,0 +1,2 @@ +/// A KeySpace using the filesystem as storage +pub mod KeySpace {} diff --git a/vault/src/keyspace/wasm.rs b/vault/src/keyspace/wasm.rs new file mode 100644 index 0000000..cc9ad8e --- /dev/null +++ b/vault/src/keyspace/wasm.rs @@ -0,0 +1,2 @@ +/// KeySpace represents an IndexDB keyspace +pub struct KeySpace {} diff --git a/vault/src/lib.rs b/vault/src/lib.rs index 7183a6f..b0e8ef9 100644 --- a/vault/src/lib.rs +++ b/vault/src/lib.rs @@ -1,3 +1,4 @@ pub mod error; pub mod key; +pub mod keyspace; pub mod kvs; From e9b867a36e9da6747d2173f79153d0d596510d3e Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Wed, 14 May 2025 11:49:36 +0200 Subject: [PATCH 04/11] Individiual methods for keystores Signed-off-by: Lee Smet --- vault/src/error.rs | 12 ++++++ vault/src/keyspace.rs | 6 +-- vault/src/keyspace/fallback.rs | 72 +++++++++++++++++++++++++++++++++- vault/src/keyspace/wasm.rs | 24 ++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) diff --git a/vault/src/error.rs b/vault/src/error.rs index 56df3f5..1c2366b 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -3,12 +3,18 @@ pub enum Error { /// An error during cryptographic operations Crypto(CryptoError), + /// An error while performing an I/O operation + IOError(std::io::Error), + /// A corrupt keyspace is returned if a keyspace can't be decrypted + CorruptKeyspace, } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Error::Crypto(e) => f.write_fmt(format_args!("crypto: {e}")), + Error::IOError(e) => f.write_fmt(format_args!("io: {e}")), + Error::CorruptKeyspace => f.write_str("corrupt keyspace"), } } } @@ -56,3 +62,9 @@ impl From for Error { Self::Crypto(value) } } + +impl From for Error { + fn from(value: std::io::Error) -> Self { + Self::IOError(value) + } +} diff --git a/vault/src/keyspace.rs b/vault/src/keyspace.rs index d121a55..86208a6 100644 --- a/vault/src/keyspace.rs +++ b/vault/src/keyspace.rs @@ -5,17 +5,17 @@ mod wasm; mod fallback; #[cfg(target_arch = "wasm32")] -use wasm::KeySpace as KS; +use wasm::KeySpace as Ks; #[cfg(not(target_arch = "wasm32"))] -use fallback::KeySpace as KS; +use fallback::KeySpace as Ks; use crate::{error::Error, key::Key}; /// A keyspace represents a group of stored cryptographic keys. The storage is encrypted, a /// password must be provided when opening the KeySpace to decrypt the keys. pub struct KeySpace { - store: KS, + store: Ks, } /// Wasm32 constructor diff --git a/vault/src/keyspace/fallback.rs b/vault/src/keyspace/fallback.rs index 851097f..cd8cca7 100644 --- a/vault/src/keyspace/fallback.rs +++ b/vault/src/keyspace/fallback.rs @@ -1,2 +1,72 @@ +use std::{collections::HashMap, io::Write, path::PathBuf}; + +use crate::{ + error::Error, + key::{Key, symmetric::SymmetricKey}, +}; + +/// Magic value used as header in decrypted keyspace files. +const KEYSPACE_MAGIC: [u8; 14] = [ + 118, 97, 117, 108, 116, 95, 107, 101, 121, 115, 112, 97, 99, 101, +]; //"vault_keyspace" + /// A KeySpace using the filesystem as storage -pub mod KeySpace {} +pub struct KeySpace { + /// Path to file on disk + path: PathBuf, + /// Decrypted keys held in the store + keystore: HashMap, + /// The encryption key used to encrypt/decrypt the storage. + encryption_key: SymmetricKey, +} + +impl KeySpace { + /// Opens the `KeySpace`. If it does not exist, it will be created. The provided encryption key + /// will be used for Encrypting and Decrypting the content of the KeySpace. + async fn open(path: PathBuf, encryption_key: SymmetricKey) -> Result { + /// If the path does not exist, create it first and write the encrypted magic header + if !path.exists() { + // Since we checked path does not exist, the only errors here can be actual IO errors + // (unless something else creates the same file at the same time). + let mut file = std::fs::File::create_new(path)?; + let content = encryption_key.encrypt(&KEYSPACE_MAGIC)?; + file.write_all(&content)?; + } + + // Load file, try to decrypt, verify magic header, deserialize keystore + let mut file = std::fs::File::open(path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + if buffer.len() < KEYSPACE_MAGIC.len() { + return Err(Error::CorruptKeyspace); + } + + if buffer[..KEYSPACE_MAGIC.len()] != KEYSPACE_MAGIC { + return Err(Error::CorruptKeyspace); + } + + // TODO: Actual deserialization + + todo!(); + } + + /// Get a [`Key`] previously stored under the provided name. + async fn get(&self, key: &str) -> Result, Error> { + todo!(); + } + + /// Store a [`Key`] under the provided name. + async fn set(&self, key: &str, value: Key) -> Result<(), Error> { + todo!(); + } + + /// Delete the [`Key`] stored under the provided name. + async fn delete(&self, key: &str) -> Result<(), Error> { + todo!(); + } + + /// Iterate over all stored [`keys`](Key) in the KeySpace + async fn iter(&self) -> Result, Error> { + todo!() + } +} diff --git a/vault/src/keyspace/wasm.rs b/vault/src/keyspace/wasm.rs index cc9ad8e..5c60ddf 100644 --- a/vault/src/keyspace/wasm.rs +++ b/vault/src/keyspace/wasm.rs @@ -1,2 +1,26 @@ +use crate::{error::Error, key::Key}; + /// KeySpace represents an IndexDB keyspace pub struct KeySpace {} + +impl KeySpace { + /// Get a [`Key`] previously stored under the provided name. + async fn get(&self, key: &str) -> Result, Error> { + todo!(); + } + + /// Store a [`Key`] under the provided name. + async fn set(&self, key: &str, value: Key) -> Result<(), Error> { + todo!(); + } + + /// Delete the [`Key`] stored under the provided name. + async fn delete(&self, key: &str) -> Result<(), Error> { + todo!(); + } + + /// Iterate over all stored [`keys`](Key) in the KeySpace + async fn iter(&self) -> Result, Error> { + todo!() + } +} From 7b1908b6766590f45e7e6682aaf953614877146d Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Wed, 14 May 2025 19:21:02 +0200 Subject: [PATCH 05/11] Use kvstore as backing Signed-off-by: Lee Smet --- vault/Cargo.toml | 5 +++ vault/src/error.rs | 9 ++++ vault/src/keyspace.rs | 100 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/vault/Cargo.toml b/vault/Cargo.toml index 4033186..e847f9f 100644 --- a/vault/Cargo.toml +++ b/vault/Cargo.toml @@ -3,6 +3,10 @@ name = "vault" version = "0.1.0" edition = "2024" +[features] +native = ["kv/native"] +wasm = ["kv/web"] + [dependencies] getrandom = { version = "0.3.3", features = ["wasm_js"] } rand = "0.9.1" @@ -13,3 +17,4 @@ serde_json = "1.0.140" chacha20poly1305 = "0.10.1" k256 = { version = "0.13.4", features = ["ecdh"] } sha2 = "0.10.9" +kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" } diff --git a/vault/src/error.rs b/vault/src/error.rs index 1c2366b..5613f7e 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -7,6 +7,8 @@ pub enum Error { IOError(std::io::Error), /// A corrupt keyspace is returned if a keyspace can't be decrypted CorruptKeyspace, + /// An error in the used key value store + KV(kv::error::KVError), } impl core::fmt::Display for Error { @@ -15,6 +17,7 @@ impl core::fmt::Display for Error { Error::Crypto(e) => f.write_fmt(format_args!("crypto: {e}")), Error::IOError(e) => f.write_fmt(format_args!("io: {e}")), Error::CorruptKeyspace => f.write_str("corrupt keyspace"), + Error::KV(e) => f.write_fmt(format_args!("kv: {e}")), } } } @@ -68,3 +71,9 @@ impl From for Error { Self::IOError(value) } } + +impl From for Error { + fn from(value: kv::error::KVError) -> Self { + Self::KV(value) + } +} diff --git a/vault/src/keyspace.rs b/vault/src/keyspace.rs index 86208a6..775b400 100644 --- a/vault/src/keyspace.rs +++ b/vault/src/keyspace.rs @@ -1,21 +1,39 @@ -#[cfg(target_arch = "wasm32")] -mod wasm; +// #[cfg(not(target_arch = "wasm32"))] +// mod fallback; +// #[cfg(target_arch = "wasm32")] +// mod wasm; + +use std::{collections::HashMap, path::Path}; + +use crate::{ + error::Error, + key::{Key, symmetric::SymmetricKey}, +}; + +// #[cfg(not(target_arch = "wasm32"))] +// use fallback::KeySpace as Ks; +// #[cfg(target_arch = "wasm32")] +// use wasm::KeySpace as Ks; #[cfg(not(target_arch = "wasm32"))] -mod fallback; - +use kv::native::NativeStore; #[cfg(target_arch = "wasm32")] -use wasm::KeySpace as Ks; +use kv::wasm::WasmStore; -#[cfg(not(target_arch = "wasm32"))] -use fallback::KeySpace as Ks; - -use crate::{error::Error, key::Key}; +const KEYSPACE_NAME: &str = "vault_keyspace"; /// A keyspace represents a group of stored cryptographic keys. The storage is encrypted, a /// password must be provided when opening the KeySpace to decrypt the keys. pub struct KeySpace { - store: Ks, + // store: Ks, + #[cfg(not(target_arch = "wasm32"))] + store: NativeStore, + #[cfg(target_arch = "wasm32")] + store: WasmStore, + /// A collection of all keys stored in the KeySpace, in decrypted form. + keys: HashMap, + /// The encryption key used to encrypt/decrypt this keyspace. + encryption_key: SymmetricKey, } /// Wasm32 constructor @@ -24,27 +42,71 @@ impl KeySpace {} /// Non-wasm constructor #[cfg(not(target_arch = "wasm32"))] -impl KeySpace {} +impl KeySpace { + /// Open the keyspace at the provided path using the given key for encryption. + pub fn open(path: &Path, encryption_key: SymmetricKey) -> Result { + let store = NativeStore::open(&path.display().to_string())?; + let mut ks = Self { + store, + keys: HashMap::new(), + encryption_key, + }; + ks.load_keyspace()?; + Ok(ks) + } +} + +#[cfg(target_arch = "wasm32")] +impl KeySpace { + pub async fn open(name: &str, encryption_key: SymmetricKey) -> Result { + let store = WasmStore::open(name).await?; + todo!(); + // Ok(Self { store }) + } +} /// Exposed methods, platform independant impl KeySpace { /// Get a [`Key`] previously stored under the provided name. - async fn get(&self, key: &str) -> Result, Error> { - todo!(); + pub async fn get(&self, key: &str) -> Result, Error> { + Ok(self.keys.get(key).cloned()) } /// Store a [`Key`] under the provided name. - async fn set(&self, key: &str, value: Key) -> Result<(), Error> { - todo!(); + /// + /// This overwrites the existing key if one is already stored with the same name. + pub async fn set(&mut self, key: String, value: Key) -> Result<(), Error> { + self.keys.insert(key, value); + self.save_keyspace() } /// Delete the [`Key`] stored under the provided name. - async fn delete(&self, key: &str) -> Result<(), Error> { - todo!(); + pub async fn delete(&mut self, key: &str) -> Result<(), Error> { + self.keys.remove(key); + self.save_keyspace() } /// Iterate over all stored [`keys`](Key) in the KeySpace - async fn iter(&self) -> Result, Error> { - todo!() + pub async fn iter(&self) -> Result, Error> { + Ok(self.keys.iter()) + } + + /// Encrypt all keys and save them to the underlying store + async fn save_keyspace(&self) -> Result<(), Error> { + // Bincode encode keys + // + // Put in store + } + + /// Loads the encrypted keyspace from the underlying storage + async fn load_keyspace(&mut self) -> Result<(), Error> { + let Some(ks) = self.store.get(KEYSPACE_NAME).await? else { + // Keyspace doesn't exist yet, nothing to do here + return Ok(()); + }; + + // TODO: bincode decode + + todo!(); } } From 2adda10664a0a8abef5d0d2e320009df7b93a98c Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Thu, 15 May 2025 13:53:16 +0200 Subject: [PATCH 06/11] Basic API Signed-off-by: Lee Smet --- vault/Cargo.toml | 2 ++ vault/src/error.rs | 15 +++++++++++++ vault/src/key/symmetric.rs | 18 +++++++++++++++ vault/src/keyspace.rs | 41 ++++++++++++++++++++++++--------- vault/src/kvs.rs | 46 +++++++++++++++++++++++++++++++++++++- 5 files changed, 110 insertions(+), 12 deletions(-) diff --git a/vault/Cargo.toml b/vault/Cargo.toml index e847f9f..5285312 100644 --- a/vault/Cargo.toml +++ b/vault/Cargo.toml @@ -18,3 +18,5 @@ chacha20poly1305 = "0.10.1" k256 = { version = "0.13.4", features = ["ecdh"] } sha2 = "0.10.9" kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" } +bincode = { version = "2.0.1", features = ["serde"] } +pbkdf2 = "0.12.2" diff --git a/vault/src/error.rs b/vault/src/error.rs index 5613f7e..4f4f502 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -9,6 +9,8 @@ pub enum Error { CorruptKeyspace, /// An error in the used key value store KV(kv::error::KVError), + /// An error while encoding/decoding the keyspace. + Coding, } impl core::fmt::Display for Error { @@ -18,6 +20,7 @@ impl core::fmt::Display for Error { Error::IOError(e) => f.write_fmt(format_args!("io: {e}")), Error::CorruptKeyspace => f.write_str("corrupt keyspace"), Error::KV(e) => f.write_fmt(format_args!("kv: {e}")), + Error::Coding => f.write_str("keyspace coding failed"), } } } @@ -77,3 +80,15 @@ impl From for Error { Self::KV(value) } } + +impl From for Error { + fn from(_: bincode::error::DecodeError) -> Self { + Self::Coding + } +} + +impl From for Error { + fn from(_: bincode::error::EncodeError) -> Self { + Self::Coding + } +} diff --git a/vault/src/key/symmetric.rs b/vault/src/key/symmetric.rs index ec11a77..c73a31b 100644 --- a/vault/src/key/symmetric.rs +++ b/vault/src/key/symmetric.rs @@ -83,4 +83,22 @@ impl SymmetricKey { .decrypt(nonce, ciphertext) .map_err(|_| CryptoError::DecryptionFailed) } + + /// Derives a new symmetric key from a password. + /// + /// Derivation is done using pbkdf2 with Sha256 hashing. + pub fn derive_from_password(password: &str) -> Self { + /// Salt to use for PBKDF2. This needs to be consistent accross runs to generate the same + /// key. Additionally, it does not really matter what this is, as long as its unique. + const SALT: &[u8; 10] = b"vault_salt"; + /// Amount of rounds to use for key generation. More rounds => more cpu time. Changing this + /// also chagnes the generated keys. + const ROUNDS: u32 = 100_000; + + let mut key = [0; 32]; + + pbkdf2::pbkdf2_hmac::(password.as_bytes(), SALT, ROUNDS, &mut key); + + Self(key) + } } diff --git a/vault/src/keyspace.rs b/vault/src/keyspace.rs index 775b400..112be5e 100644 --- a/vault/src/keyspace.rs +++ b/vault/src/keyspace.rs @@ -3,13 +3,21 @@ // #[cfg(target_arch = "wasm32")] // mod wasm; -use std::{collections::HashMap, path::Path}; +use std::collections::HashMap; + +#[cfg(not(target_arch = "wasm32"))] +use std::path::Path; use crate::{ error::Error, key::{Key, symmetric::SymmetricKey}, }; +use kv::KVStore; + +/// Configuration to use for bincode en/decoding. +const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); + // #[cfg(not(target_arch = "wasm32"))] // use fallback::KeySpace as Ks; // #[cfg(target_arch = "wasm32")] @@ -44,14 +52,14 @@ impl KeySpace {} #[cfg(not(target_arch = "wasm32"))] impl KeySpace { /// Open the keyspace at the provided path using the given key for encryption. - pub fn open(path: &Path, encryption_key: SymmetricKey) -> Result { + pub async fn open(path: &Path, encryption_key: SymmetricKey) -> Result { let store = NativeStore::open(&path.display().to_string())?; let mut ks = Self { store, keys: HashMap::new(), encryption_key, }; - ks.load_keyspace()?; + ks.load_keyspace().await?; Ok(ks) } } @@ -60,8 +68,13 @@ impl KeySpace { impl KeySpace { pub async fn open(name: &str, encryption_key: SymmetricKey) -> Result { let store = WasmStore::open(name).await?; - todo!(); - // Ok(Self { store }) + let mut ks = Self { + store, + keys: HashMap::new(), + encryption_key, + }; + ks.load_keyspace().await?; + Ok(ks) } } @@ -77,13 +90,13 @@ impl KeySpace { /// This overwrites the existing key if one is already stored with the same name. pub async fn set(&mut self, key: String, value: Key) -> Result<(), Error> { self.keys.insert(key, value); - self.save_keyspace() + self.save_keyspace().await } /// Delete the [`Key`] stored under the provided name. pub async fn delete(&mut self, key: &str) -> Result<(), Error> { self.keys.remove(key); - self.save_keyspace() + self.save_keyspace().await } /// Iterate over all stored [`keys`](Key) in the KeySpace @@ -93,9 +106,10 @@ impl KeySpace { /// Encrypt all keys and save them to the underlying store async fn save_keyspace(&self) -> Result<(), Error> { - // Bincode encode keys - // + let encoded_keys = bincode::serde::encode_to_vec(&self.keys, BINCODE_CONFIG)?; + let value = self.encryption_key.encrypt(&encoded_keys)?; // Put in store + Ok(self.store.set(KEYSPACE_NAME, &value).await?) } /// Loads the encrypted keyspace from the underlying storage @@ -105,8 +119,13 @@ impl KeySpace { return Ok(()); }; - // TODO: bincode decode + let raw = self.encryption_key.decrypt(&ks)?; - todo!(); + let (decoded_keys, _): (HashMap, _) = + bincode::serde::decode_from_slice(&raw, BINCODE_CONFIG)?; + + self.keys = decoded_keys; + + Ok(()) } } diff --git a/vault/src/kvs.rs b/vault/src/kvs.rs index 4ff117e..2052afa 100644 --- a/vault/src/kvs.rs +++ b/vault/src/kvs.rs @@ -1,3 +1,47 @@ +#[cfg(not(target_arch = "wasm32"))] +use std::path::{Path, PathBuf}; + +use crate::{error::Error, key::symmetric::SymmetricKey, keyspace::KeySpace}; + /// Store is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where /// each [`space`](KeySpace) is itself an encrypted key-value store -pub struct Store {} +pub struct Store { + #[cfg(not(target_arch = "wasm32"))] + path: PathBuf, +} + +#[cfg(not(target_arch = "wasm32"))] +impl Store { + /// Create a new store at the given path, creating the path if it does not exist yet. + pub async fn new(path: &Path) -> Result { + if path.exists() { + if !path.is_dir() { + return Err(Error::IOError(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "expected directory", + ))); + } + } else { + std::fs::create_dir_all(path)?; + } + Ok(Self { + path: path.to_path_buf(), + }) + } +} + +impl Store { + /// Open a keyspace with the given name + pub async fn open_keyspace(&self, name: &str, password: &str) -> Result { + let encryption_key = SymmetricKey::derive_from_password(password); + #[cfg(not(target_arch = "wasm32"))] + { + let path = self.path.join(name); + KeySpace::open(&path, encryption_key).await + } + #[cfg(target_arch = "wasm32")] + { + KeySpace::open(name, encryption_key).await + } + } +} From 2014c63b780a199c500fa60304ab8cab079864f6 Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Thu, 15 May 2025 13:53:54 +0200 Subject: [PATCH 07/11] Remove old files Signed-off-by: Lee Smet --- vault/src/ethereum.bak/README.md | 160 -------- vault/src/ethereum.bak/contract.rs | 179 --------- vault/src/ethereum.bak/contract_utils.rs | 183 --------- vault/src/ethereum.bak/mod.rs | 84 ---- vault/src/ethereum.bak/networks.rs | 102 ----- vault/src/ethereum.bak/provider.rs | 27 -- vault/src/ethereum.bak/storage.rs | 114 ------ .../ethereum.bak/tests/contract_args_tests.rs | 47 --- .../src/ethereum.bak/tests/contract_tests.rs | 83 ---- vault/src/ethereum.bak/tests/mod.rs | 7 - vault/src/ethereum.bak/tests/network_tests.rs | 74 ---- .../ethereum.bak/tests/transaction_tests.rs | 70 ---- vault/src/ethereum.bak/tests/wallet_tests.rs | 143 ------- vault/src/ethereum.bak/transaction.rs | 54 --- vault/src/ethereum.bak/wallet.rs | 123 ------ vault/src/keyspace.bak/README.md | 271 ------------- vault/src/keyspace.bak/keypair_types.rs | 328 ---------------- vault/src/keyspace.bak/mod.rs | 18 - vault/src/keyspace.bak/session_manager.rs | 174 -------- vault/src/keyspace.bak/spec.md | 36 -- .../keyspace.bak/tests/keypair_types_tests.rs | 86 ---- vault/src/keyspace.bak/tests/mod.rs | 3 - .../tests/session_manager_tests.rs | 111 ------ vault/src/kvs.bak/README.md | 173 -------- vault/src/kvs.bak/error.rs | 67 ---- vault/src/kvs.bak/mod.rs | 17 - vault/src/kvs.bak/store.rs | 370 ------------------ vault/src/kvs.bak/tests/mod.rs | 1 - vault/src/kvs.bak/tests/store_tests.rs | 105 ----- vault/src/symmetric.bak/README.md | 98 ----- vault/src/symmetric.bak/implementation.rs | 266 ------------- vault/src/symmetric.bak/mod.rs | 15 - 32 files changed, 3589 deletions(-) delete mode 100644 vault/src/ethereum.bak/README.md delete mode 100644 vault/src/ethereum.bak/contract.rs delete mode 100644 vault/src/ethereum.bak/contract_utils.rs delete mode 100644 vault/src/ethereum.bak/mod.rs delete mode 100644 vault/src/ethereum.bak/networks.rs delete mode 100644 vault/src/ethereum.bak/provider.rs delete mode 100644 vault/src/ethereum.bak/storage.rs delete mode 100644 vault/src/ethereum.bak/tests/contract_args_tests.rs delete mode 100644 vault/src/ethereum.bak/tests/contract_tests.rs delete mode 100644 vault/src/ethereum.bak/tests/mod.rs delete mode 100644 vault/src/ethereum.bak/tests/network_tests.rs delete mode 100644 vault/src/ethereum.bak/tests/transaction_tests.rs delete mode 100644 vault/src/ethereum.bak/tests/wallet_tests.rs delete mode 100644 vault/src/ethereum.bak/transaction.rs delete mode 100644 vault/src/ethereum.bak/wallet.rs delete mode 100644 vault/src/keyspace.bak/README.md delete mode 100644 vault/src/keyspace.bak/keypair_types.rs delete mode 100644 vault/src/keyspace.bak/mod.rs delete mode 100644 vault/src/keyspace.bak/session_manager.rs delete mode 100644 vault/src/keyspace.bak/spec.md delete mode 100644 vault/src/keyspace.bak/tests/keypair_types_tests.rs delete mode 100644 vault/src/keyspace.bak/tests/mod.rs delete mode 100644 vault/src/keyspace.bak/tests/session_manager_tests.rs delete mode 100644 vault/src/kvs.bak/README.md delete mode 100644 vault/src/kvs.bak/error.rs delete mode 100644 vault/src/kvs.bak/mod.rs delete mode 100644 vault/src/kvs.bak/store.rs delete mode 100644 vault/src/kvs.bak/tests/mod.rs delete mode 100644 vault/src/kvs.bak/tests/store_tests.rs delete mode 100644 vault/src/symmetric.bak/README.md delete mode 100644 vault/src/symmetric.bak/implementation.rs delete mode 100644 vault/src/symmetric.bak/mod.rs diff --git a/vault/src/ethereum.bak/README.md b/vault/src/ethereum.bak/README.md deleted file mode 100644 index 34fb2dd..0000000 --- a/vault/src/ethereum.bak/README.md +++ /dev/null @@ -1,160 +0,0 @@ -# Hero Vault Ethereum Module - -The Ethereum module provides functionality for creating and managing Ethereum wallets and interacting with smart contracts on EVM-based blockchains. - -## Module Structure - -The Ethereum module is organized into several components: - -- `wallet.rs` - Core Ethereum wallet implementation -- `networks.rs` - Network registry and configuration -- `provider.rs` - Provider creation and management -- `transaction.rs` - Transaction-related functionality -- `storage.rs` - Wallet storage functionality -- `contract.rs` - Smart contract interaction functionality -- `contract_utils.rs` - Utilities for contract interactions - -## Key Features - -### Wallet Management - -The module provides functionality for creating and managing Ethereum wallets: - -```rust -// Create a new Ethereum wallet for a specific network -let wallet = create_ethereum_wallet_for_network("Ethereum")?; - -// Create a wallet for specific networks -let peaq_wallet = create_peaq_wallet()?; -let agung_wallet = create_agung_wallet()?; - -// Create a wallet with a specific name -let named_wallet = create_ethereum_wallet_from_name_for_network("my_wallet", "Gnosis")?; - -// Create a wallet from a private key -let imported_wallet = create_ethereum_wallet_from_private_key("0x...")?; - -// Get the current wallet for a network -let current_wallet = get_current_ethereum_wallet_for_network("Ethereum")?; - -// Clear wallets -clear_ethereum_wallets()?; -clear_ethereum_wallets_for_network("Gnosis")?; -``` - -### Network Management - -The module supports multiple Ethereum networks and provides functionality for managing network configurations: - -```rust -// Get a network configuration by name -let network = get_network_by_name("Ethereum")?; - -// Get the proper network name (normalized) -let name = get_proper_network_name("eth")?; // Returns "Ethereum" - -// List all available network names -let networks = list_network_names()?; - -// Get all network configurations -let all_networks = get_all_networks()?; -``` - -### Provider Management - -The module provides functionality for creating and managing Ethereum providers: - -```rust -// Create a provider for a specific network -let provider = create_provider("Ethereum")?; - -// Create providers for specific networks -let gnosis_provider = create_gnosis_provider()?; -let peaq_provider = create_peaq_provider()?; -let agung_provider = create_agung_provider()?; -``` - -### Transaction Management - -The module provides functionality for managing Ethereum transactions: - -```rust -// Get the balance of an address -let balance = get_balance("Ethereum", "0x...")?; - -// Send ETH to an address -let tx_hash = send_eth("Ethereum", "0x...", "1000000000000000")?; - -// Format a balance for display -let formatted = format_balance(balance, 18)?; // Convert wei to ETH -``` - -### Smart Contract Interactions - -The module provides functionality for interacting with smart contracts: - -```rust -// Load a contract ABI from JSON -let abi = load_abi_from_json(json_string)?; - -// Create a contract instance -let contract = Contract::new(provider, "0x...", abi)?; - -// Call a read-only function -let result = call_read_function(contract, "balanceOf", vec!["0x..."])?; - -// Call a write function -let tx_hash = call_write_function(contract, "transfer", vec!["0x...", "1000"])?; - -// Estimate gas for a function call -let gas = estimate_gas(contract, "transfer", vec!["0x...", "1000"])?; -``` - -### Contract Utilities - -The module provides utilities for working with contract function arguments and return values: - -```rust -// Convert Rhai values to Ethereum tokens -let token = convert_rhai_to_token(value)?; - -// Prepare function arguments -let args = prepare_function_arguments(function, vec![arg1, arg2])?; - -// Convert Ethereum tokens to Rhai values -let rhai_value = convert_token_to_rhai(token)?; - -// Convert a token to a dynamic value -let dynamic = token_to_dynamic(token)?; -``` - -## Supported Networks - -The module supports multiple Ethereum networks, including: - -- Gnosis Chain -- Peaq Network -- Agung Network - -Each network has its own configuration, including: - -- RPC URL -- Chain ID -- Explorer URL -- Native currency symbol and decimals - -## Error Handling - -The module uses the `CryptoError` type for handling errors that can occur during Ethereum operations: - -- `InvalidAddress` - Invalid Ethereum address format -- `ContractError` - Smart contract interaction error - -## Examples - -For examples of how to use the Ethereum module, see the `examples/hero_vault` directory, particularly: - -- `contract_example.rhai` - Demonstrates loading a contract ABI and interacting with smart contracts -- `agung_simple_transfer.rhai` - Shows how to perform a simple ETH transfer on the Agung network -- `agung_send_transaction.rhai` - Demonstrates sending transactions on the Agung network -- `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung diff --git a/vault/src/ethereum.bak/contract.rs b/vault/src/ethereum.bak/contract.rs deleted file mode 100644 index 5e8749e..0000000 --- a/vault/src/ethereum.bak/contract.rs +++ /dev/null @@ -1,179 +0,0 @@ -//! Smart contract interaction functionality. -//! -//! This module provides functionality for interacting with smart contracts on EVM-based blockchains. - -use ethers::prelude::*; -use ethers::abi::{Abi, Token}; -use std::sync::Arc; -use std::str::FromStr; -use serde::{Serialize, Deserialize}; - -use crate::vault::error::CryptoError; -use super::wallet::EthereumWallet; -use super::networks::NetworkConfig; - -/// A smart contract instance. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Contract { - /// The contract address - pub address: Address, - /// The contract ABI - pub abi: Abi, - /// The network the contract is deployed on - pub network: NetworkConfig, -} - -impl Contract { - /// Creates a new contract instance. - pub fn new(address: Address, abi: Abi, network: NetworkConfig) -> Self { - Contract { - address, - abi, - network, - } - } - - /// Creates a new contract instance from an address string and ABI. - pub fn from_address_string(address_str: &str, abi: Abi, network: NetworkConfig) -> Result { - let address = Address::from_str(address_str) - .map_err(|e| CryptoError::InvalidAddress(format!("Invalid address format: {}", e)))?; - - Ok(Contract::new(address, abi, network)) - } - - /// Creates an ethers Contract instance for interaction. - pub fn create_ethers_contract(&self, provider: Provider, _wallet: Option<&EthereumWallet>) -> Result>, CryptoError> { - let contract = ethers::contract::Contract::new( - self.address, - self.abi.clone(), - Arc::new(provider), - ); - - Ok(contract) - } -} - -/// Loads a contract ABI from a JSON string. -pub fn load_abi_from_json(json_str: &str) -> Result { - serde_json::from_str(json_str) - .map_err(|e| CryptoError::SerializationError(format!("Failed to parse ABI JSON: {}", e))) -} - -/// Calls a read-only function on a contract. -pub async fn call_read_function( - contract: &Contract, - provider: &Provider, - function_name: &str, - args: Vec, -) -> Result, CryptoError> { - // Create the ethers contract (not used directly but kept for future extensions) - let _ethers_contract = contract.create_ethers_contract(provider.clone(), None)?; - - // Get the function from the ABI - let function = contract.abi.function(function_name) - .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; - - // Encode the function call - let call_data = function.encode_input(&args) - .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; - - // Make the call - let tx = TransactionRequest::new() - .to(contract.address) - .data(call_data); - - let result = provider.call(&tx.into(), None).await - .map_err(|e| CryptoError::ContractError(format!("Contract call failed: {}", e)))?; - - // Decode the result - let decoded = function.decode_output(&result) - .map_err(|e| CryptoError::ContractError(format!("Failed to decode function output: {}", e)))?; - - Ok(decoded) -} - -/// Executes a state-changing function on a contract. -pub async fn call_write_function( - contract: &Contract, - wallet: &EthereumWallet, - provider: &Provider, - function_name: &str, - args: Vec, -) -> Result { - // Create a client with the wallet - let client = SignerMiddleware::new( - provider.clone(), - wallet.wallet.clone(), - ); - - // Get the function from the ABI - let function = contract.abi.function(function_name) - .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; - - // Encode the function call - let call_data = function.encode_input(&args) - .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; - - // Create the transaction request with gas limit - let tx = TransactionRequest::new() - .to(contract.address) - .data(call_data) - .gas(U256::from(300000)); // Set a reasonable gas limit - - // Send the transaction using the client directly - log::info!("Sending transaction to contract at {}", contract.address); - log::info!("Function: {}, Args: {:?}", function_name, args); - - // Log detailed information about the transaction - log::debug!("Sending transaction to contract at {}", contract.address); - log::debug!("Function: {}, Args: {:?}", function_name, args); - log::debug!("From address: {}", wallet.address); - log::debug!("Gas limit: {:?}", tx.gas); - - let pending_tx = match client.send_transaction(tx, None).await { - Ok(pending_tx) => { - log::debug!("Transaction sent successfully: {:?}", pending_tx.tx_hash()); - log::info!("Transaction sent successfully: {:?}", pending_tx.tx_hash()); - pending_tx - }, - Err(e) => { - // Log the error for debugging - log::error!("Failed to send transaction: {}", e); - log::error!("ERROR DETAILS: {:?}", e); - return Err(CryptoError::ContractError(format!("Failed to send transaction: {}", e))); - } - }; - - // Return the transaction hash - Ok(pending_tx.tx_hash()) -} - -/// Estimates gas for a contract function call. -pub async fn estimate_gas( - contract: &Contract, - wallet: &EthereumWallet, - provider: &Provider, - function_name: &str, - args: Vec, -) -> Result { - // Get the function from the ABI - let function = contract.abi.function(function_name) - .map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?; - - // Encode the function call - let call_data = function.encode_input(&args) - .map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?; - - // Create the transaction request - let tx = TransactionRequest::new() - .from(wallet.address) - .to(contract.address) - .data(call_data); - - // Estimate gas - let gas = provider.estimate_gas(&tx.into(), None) - .await - .map_err(|e| CryptoError::ContractError(format!("Failed to estimate gas: {}", e)))?; - - Ok(gas) -} diff --git a/vault/src/ethereum.bak/contract_utils.rs b/vault/src/ethereum.bak/contract_utils.rs deleted file mode 100644 index d40c23d..0000000 --- a/vault/src/ethereum.bak/contract_utils.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! Utility functions for smart contract interactions. - -use ethers::abi::{Abi, Token, ParamType}; -use ethers::types::{Address, U256}; -use std::str::FromStr; -use rhai::{Dynamic, Array}; - -/// Convert Rhai Dynamic values to ethers Token types -pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>) -> Result { - match value { - // Handle integers - v if v.is_int() => { - let i = v.as_int().unwrap(); - if let Some(param_type) = expected_type { - match param_type { - ParamType::Uint(_) => Ok(Token::Uint(U256::from(i as u64))), - ParamType::Int(_) => { - // Convert to I256 - in a real implementation, we would handle this properly - // For now, we'll just use U256 for both types - Ok(Token::Uint(U256::from(i as u64))) - }, - _ => Err(format!("Expected {}, got integer", param_type)) - } - } else { - // Default to Uint256 if no type info - Ok(Token::Uint(U256::from(i as u64))) - } - }, - - // Handle strings and addresses - v if v.is_string() => { - let s = v.to_string(); - if let Some(param_type) = expected_type { - match param_type { - ParamType::Address => { - match Address::from_str(&s) { - Ok(addr) => Ok(Token::Address(addr)), - Err(e) => Err(format!("Invalid address format: {}", e)) - } - }, - ParamType::String => Ok(Token::String(s)), - ParamType::Bytes => { - // Handle hex string conversion to bytes - if s.starts_with("0x") { - match ethers::utils::hex::decode(&s[2..]) { - Ok(bytes) => Ok(Token::Bytes(bytes)), - Err(e) => Err(format!("Invalid hex string: {}", e)) - } - } else { - Ok(Token::Bytes(s.as_bytes().to_vec())) - } - }, - _ => Err(format!("Expected {}, got string", param_type)) - } - } else { - // Try to detect type from string format - if s.starts_with("0x") && s.len() == 42 { - // Likely an address - match Address::from_str(&s) { - Ok(addr) => Ok(Token::Address(addr)), - Err(_) => Ok(Token::String(s)) - } - } else { - Ok(Token::String(s)) - } - } - }, - - // Handle booleans - v if v.is_bool() => { - let b = v.as_bool().unwrap(); - if let Some(param_type) = expected_type { - if matches!(param_type, ParamType::Bool) { - Ok(Token::Bool(b)) - } else { - Err(format!("Expected {}, got boolean", param_type)) - } - } else { - Ok(Token::Bool(b)) - } - }, - - // Handle arrays - v if v.is_array() => { - let arr = v.clone().into_array().unwrap(); - if let Some(ParamType::Array(inner_type)) = expected_type { - let mut tokens = Vec::new(); - for item in arr.iter() { - match convert_rhai_to_token(item, Some(inner_type)) { - Ok(token) => tokens.push(token), - Err(e) => return Err(e) - } - } - Ok(Token::Array(tokens)) - } else { - Err("Array type mismatch or no type information available".to_string()) - } - }, - - // Handle other types or return error - _ => Err(format!("Unsupported Rhai type: {:?}", value)) - } -} - -/// Validate and convert arguments based on function ABI -pub fn prepare_function_arguments( - abi: &Abi, - function_name: &str, - args: &Array -) -> Result, String> { - // Get the function from the ABI - let function = abi.function(function_name) - .map_err(|e| format!("Function not found in ABI: {}", e))?; - - // Check if number of arguments matches - if function.inputs.len() != args.len() { - return Err(format!( - "Wrong number of arguments for function '{}': expected {}, got {}", - function_name, function.inputs.len(), args.len() - )); - } - - // Convert each argument according to the expected type - let mut tokens = Vec::new(); - for (i, (param, arg)) in function.inputs.iter().zip(args.iter()).enumerate() { - match convert_rhai_to_token(arg, Some(¶m.kind)) { - Ok(token) => tokens.push(token), - Err(e) => return Err(format!("Error converting argument {}: {}", i, e)) - } - } - - Ok(tokens) -} - -/// Convert ethers Token to Rhai Dynamic value -pub fn convert_token_to_rhai(tokens: &[Token]) -> Dynamic { - if tokens.is_empty() { - return Dynamic::UNIT; - } - - // If there's only one return value, return it directly - if tokens.len() == 1 { - return token_to_dynamic(&tokens[0]); - } - - // If there are multiple return values, return them as an array - let mut array = Array::new(); - for token in tokens { - array.push(token_to_dynamic(token)); - } - Dynamic::from(array) -} - -/// Convert a single token to a Dynamic value -pub fn token_to_dynamic(token: &Token) -> Dynamic { - match token { - Token::Address(addr) => Dynamic::from(format!("{:?}", addr)), - Token::Bytes(bytes) => Dynamic::from(ethers::utils::hex::encode(bytes)), - Token::Int(i) => Dynamic::from(i.to_string()), - Token::Uint(u) => Dynamic::from(u.to_string()), - Token::Bool(b) => Dynamic::from(*b), - Token::String(s) => Dynamic::from(s.clone()), - Token::Array(arr) => { - let mut rhai_arr = Array::new(); - for item in arr { - rhai_arr.push(token_to_dynamic(item)); - } - Dynamic::from(rhai_arr) - }, - Token::Tuple(tuple) => { - let mut rhai_arr = Array::new(); - for item in tuple { - rhai_arr.push(token_to_dynamic(item)); - } - Dynamic::from(rhai_arr) - }, - // Handle other token types - _ => { - log::warn!("Unsupported token type: {:?}", token); - Dynamic::UNIT - } - } -} diff --git a/vault/src/ethereum.bak/mod.rs b/vault/src/ethereum.bak/mod.rs deleted file mode 100644 index 7ec8d32..0000000 --- a/vault/src/ethereum.bak/mod.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Ethereum wallet functionality -//! -//! This module provides functionality for creating and managing Ethereum wallets -//! and interacting with smart contracts on EVM-based blockchains. -//! -//! The module is organized into several components: -//! - `wallet.rs`: Core Ethereum wallet implementation -//! - `networks.rs`: Network registry and configuration -//! - `provider.rs`: Provider creation and management -//! - `transaction.rs`: Transaction-related functionality -//! - `storage.rs`: Wallet storage functionality -//! - `contract.rs`: Smart contract interaction functionality - -mod wallet; -mod provider; -mod transaction; -mod storage; -mod contract; -pub mod contract_utils; -pub mod networks; -// Re-export public types and functions -pub use wallet::EthereumWallet; -pub use networks::NetworkConfig; - -// Re-export wallet creation functions -pub use storage::{ - create_ethereum_wallet_for_network, - create_peaq_wallet, - create_agung_wallet, - create_ethereum_wallet_from_name_for_network, - create_ethereum_wallet_from_name, - create_ethereum_wallet_from_private_key_for_network, - create_ethereum_wallet_from_private_key, -}; - -// Re-export wallet management functions -pub use storage::{ - get_current_ethereum_wallet_for_network, - get_current_peaq_wallet, - get_current_agung_wallet, - clear_ethereum_wallets, - clear_ethereum_wallets_for_network, -}; - -// Re-export provider functions -pub use provider::{ - create_provider, - create_gnosis_provider, - create_peaq_provider, - create_agung_provider, -}; - -// Re-export transaction functions -pub use transaction::{ - get_balance, - send_eth, - format_balance, -}; - -// Re-export network registry functions -pub use networks::{ - get_network_by_name, - get_proper_network_name, - list_network_names, - get_all_networks, - names, -}; - -// Re-export contract functions -pub use contract::{ - Contract, - load_abi_from_json, - call_read_function, - call_write_function, - estimate_gas, -}; - -// Re-export contract utility functions -pub use contract_utils::{ - convert_rhai_to_token, - prepare_function_arguments, - convert_token_to_rhai, - token_to_dynamic, -}; diff --git a/vault/src/ethereum.bak/networks.rs b/vault/src/ethereum.bak/networks.rs deleted file mode 100644 index 4e81655..0000000 --- a/vault/src/ethereum.bak/networks.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Ethereum network registry -//! -//! This module provides a centralized registry of Ethereum networks and utilities -//! to work with them. - -use std::collections::HashMap; -use std::sync::OnceLock; -use serde::{Serialize, Deserialize}; - -/// Configuration for an EVM-compatible network -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NetworkConfig { - pub name: String, - pub chain_id: u64, - pub rpc_url: String, - pub explorer_url: String, - pub token_symbol: String, - pub decimals: u8, -} - -/// Network name constants -pub mod names { - pub const GNOSIS: &str = "Gnosis"; - pub const PEAQ: &str = "Peaq"; - pub const AGUNG: &str = "Agung"; -} - -/// Get the Gnosis Chain network configuration -pub fn gnosis() -> NetworkConfig { - NetworkConfig { - name: names::GNOSIS.to_string(), - chain_id: 100, - rpc_url: "https://rpc.gnosischain.com".to_string(), - explorer_url: "https://gnosisscan.io".to_string(), - token_symbol: "xDAI".to_string(), - decimals: 18, - } -} - -/// Get the Peaq Network configuration -pub fn peaq() -> NetworkConfig { - NetworkConfig { - name: names::PEAQ.to_string(), - chain_id: 3338, - rpc_url: "https://peaq.api.onfinality.io/public".to_string(), - explorer_url: "https://peaq.subscan.io/".to_string(), - token_symbol: "PEAQ".to_string(), - decimals: 18, - } -} - -/// Get the Agung Testnet configuration -pub fn agung() -> NetworkConfig { - NetworkConfig { - name: names::AGUNG.to_string(), - chain_id: 9990, - rpc_url: "https://wss-async.agung.peaq.network".to_string(), - explorer_url: "https://agung-testnet.subscan.io/".to_string(), - token_symbol: "AGNG".to_string(), - decimals: 18, - } -} - -/// Get a network by its name (case-insensitive) -pub fn get_network_by_name(name: &str) -> Option { - let name_lower = name.to_lowercase(); - match name_lower.as_str() { - "gnosis" => Some(gnosis()), - "peaq" => Some(peaq()), - "agung" => Some(agung()), - _ => None, - } -} - -/// Get the proper capitalization of a network name -pub fn get_proper_network_name(name: &str) -> Option<&'static str> { - let name_lower = name.to_lowercase(); - match name_lower.as_str() { - "gnosis" => Some(names::GNOSIS), - "peaq" => Some(names::PEAQ), - "agung" => Some(names::AGUNG), - _ => None, - } -} - -/// Get a list of all supported network names -pub fn list_network_names() -> Vec<&'static str> { - vec![names::GNOSIS, names::PEAQ, names::AGUNG] -} - -/// Get a map of all networks -pub fn get_all_networks() -> &'static HashMap<&'static str, NetworkConfig> { - static NETWORKS: OnceLock> = OnceLock::new(); - - NETWORKS.get_or_init(|| { - let mut map = HashMap::new(); - map.insert(names::GNOSIS, gnosis()); - map.insert(names::PEAQ, peaq()); - map.insert(names::AGUNG, agung()); - map - }) -} diff --git a/vault/src/ethereum.bak/provider.rs b/vault/src/ethereum.bak/provider.rs deleted file mode 100644 index 145566a..0000000 --- a/vault/src/ethereum.bak/provider.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Ethereum provider functionality. - -use ethers::prelude::*; - -use crate::vault::error::CryptoError; -use super::networks::{self, NetworkConfig}; - -/// Creates a provider for a specific network. -pub fn create_provider(network: &NetworkConfig) -> Result, CryptoError> { - Provider::::try_from(network.rpc_url.as_str()) - .map_err(|e| CryptoError::SerializationError(format!("Failed to create provider for {}: {}", network.name, e))) -} - -/// Creates a provider for the Gnosis Chain. -pub fn create_gnosis_provider() -> Result, CryptoError> { - create_provider(&networks::gnosis()) -} - -/// Creates a provider for the Peaq network. -pub fn create_peaq_provider() -> Result, CryptoError> { - create_provider(&networks::peaq()) -} - -/// Creates a provider for the Agung testnet. -pub fn create_agung_provider() -> Result, CryptoError> { - create_provider(&networks::agung()) -} diff --git a/vault/src/ethereum.bak/storage.rs b/vault/src/ethereum.bak/storage.rs deleted file mode 100644 index e74fb26..0000000 --- a/vault/src/ethereum.bak/storage.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Ethereum wallet storage functionality. - -use std::sync::Mutex; -use std::collections::HashMap; -use once_cell::sync::Lazy; - -use crate::vault::error::CryptoError; -use super::wallet::EthereumWallet; -use super::networks::{self, NetworkConfig}; - -/// Global storage for Ethereum wallets. -static ETH_WALLETS: Lazy>>> = Lazy::new(|| { - Mutex::new(HashMap::new()) -}); - -/// Creates an Ethereum wallet from the currently selected keypair for a specific network. -pub fn create_ethereum_wallet_for_network(network: NetworkConfig) -> Result { - // Get the currently selected keypair - let keypair = crate::vault::keyspace::get_selected_keypair()?; - - // Create an Ethereum wallet from the keypair - let wallet = EthereumWallet::from_keypair(&keypair, network)?; - - // Store the wallet - let mut wallets = ETH_WALLETS.lock().unwrap(); - let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); - network_wallets.push(wallet.clone()); - - Ok(wallet) -} - -/// Creates an Ethereum wallet from the currently selected keypair for the Peaq network. -pub fn create_peaq_wallet() -> Result { - create_ethereum_wallet_for_network(networks::peaq()) -} - -/// Creates an Ethereum wallet from the currently selected keypair for the Agung testnet. -pub fn create_agung_wallet() -> Result { - create_ethereum_wallet_for_network(networks::agung()) -} - -/// Gets the current Ethereum wallet for a specific network. -pub fn get_current_ethereum_wallet_for_network(network_name: &str) -> Result { - let wallets = ETH_WALLETS.lock().unwrap(); - - let network_wallets = wallets.get(network_name).ok_or(CryptoError::NoKeypairSelected)?; - - if network_wallets.is_empty() { - return Err(CryptoError::NoKeypairSelected); - } - - Ok(network_wallets.last().unwrap().clone()) -} - -/// Gets the current Ethereum wallet for the Peaq network. -pub fn get_current_peaq_wallet() -> Result { - get_current_ethereum_wallet_for_network("Peaq") -} - -/// Gets the current Ethereum wallet for the Agung testnet. -pub fn get_current_agung_wallet() -> Result { - get_current_ethereum_wallet_for_network("Agung") -} - -/// Clears all Ethereum wallets. -pub fn clear_ethereum_wallets() { - let mut wallets = ETH_WALLETS.lock().unwrap(); - wallets.clear(); -} - -/// Clears Ethereum wallets for a specific network. -pub fn clear_ethereum_wallets_for_network(network_name: &str) { - let mut wallets = ETH_WALLETS.lock().unwrap(); - wallets.remove(network_name); -} - -/// Creates an Ethereum wallet from a name and the currently selected keypair for a specific network. -pub fn create_ethereum_wallet_from_name_for_network(name: &str, network: NetworkConfig) -> Result { - // Get the currently selected keypair - let keypair = crate::vault::keyspace::get_selected_keypair()?; - - // Create an Ethereum wallet from the name and keypair - let wallet = EthereumWallet::from_name_and_keypair(name, &keypair, network)?; - - // Store the wallet - let mut wallets = ETH_WALLETS.lock().unwrap(); - let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); - network_wallets.push(wallet.clone()); - - Ok(wallet) -} - -/// Creates an Ethereum wallet from a name and the currently selected keypair for the Gnosis network. -pub fn create_ethereum_wallet_from_name(name: &str) -> Result { - create_ethereum_wallet_from_name_for_network(name, networks::gnosis()) -} - -/// Creates an Ethereum wallet from a private key for a specific network. -pub fn create_ethereum_wallet_from_private_key_for_network(private_key: &str, network: NetworkConfig) -> Result { - // Create an Ethereum wallet from the private key - let wallet = EthereumWallet::from_private_key(private_key, network)?; - - // Store the wallet - let mut wallets = ETH_WALLETS.lock().unwrap(); - let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); - network_wallets.push(wallet.clone()); - - Ok(wallet) -} - -/// Creates an Ethereum wallet from a private key for the Gnosis network. -pub fn create_ethereum_wallet_from_private_key(private_key: &str) -> Result { - create_ethereum_wallet_from_private_key_for_network(private_key, networks::gnosis()) -} diff --git a/vault/src/ethereum.bak/tests/contract_args_tests.rs b/vault/src/ethereum.bak/tests/contract_args_tests.rs deleted file mode 100644 index b7cb76a..0000000 --- a/vault/src/ethereum.bak/tests/contract_args_tests.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Tests for smart contract argument handling functionality. - -use ethers::types::Address; -use std::str::FromStr; - -use crate::vault::ethereum::*; - -#[test] -fn test_contract_creation() { - // Create a simple ABI - let abi_json = r#"[ - { - "inputs": [], - "name": "getValue", - "outputs": [{"type": "uint256", "name": ""}], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{"type": "uint256", "name": "newValue"}], - "name": "setValue", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ]"#; - - // Parse the ABI - let abi = load_abi_from_json(abi_json).unwrap(); - - // Create a contract address - let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap(); - - // Create a network config - let network = networks::gnosis(); - - // Create a contract - let contract = Contract::new(address, abi, network); - - // Verify the contract was created correctly - assert_eq!(contract.address, address); - assert_eq!(contract.network.name, "Gnosis"); - - // Verify the ABI contains the expected functions - assert!(contract.abi.function("getValue").is_ok()); - assert!(contract.abi.function("setValue").is_ok()); -} diff --git a/vault/src/ethereum.bak/tests/contract_tests.rs b/vault/src/ethereum.bak/tests/contract_tests.rs deleted file mode 100644 index d37dde3..0000000 --- a/vault/src/ethereum.bak/tests/contract_tests.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Tests for smart contract functionality. - -use ethers::types::Address; -use std::str::FromStr; - -use crate::vault::ethereum::*; - -#[test] -fn test_contract_creation() { - // Create a simple ABI - let abi_json = r#"[ - { - "inputs": [], - "name": "getValue", - "outputs": [{"type": "uint256", "name": ""}], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{"type": "uint256", "name": "newValue"}], - "name": "setValue", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ]"#; - - // Parse the ABI - let abi = load_abi_from_json(abi_json).unwrap(); - - // Create a contract address - let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap(); - - // Create a network config - let network = networks::gnosis(); - - // Create a contract - let contract = Contract::new(address, abi, network); - - // Verify the contract was created correctly - assert_eq!(contract.address, address); - assert_eq!(contract.network.name, "Gnosis"); - - // Verify the ABI contains the expected functions - assert!(contract.abi.function("getValue").is_ok()); - assert!(contract.abi.function("setValue").is_ok()); -} - -#[test] -fn test_contract_from_address_string() { - // Create a simple ABI - let abi_json = r#"[ - { - "inputs": [], - "name": "getValue", - "outputs": [{"type": "uint256", "name": ""}], - "stateMutability": "view", - "type": "function" - } - ]"#; - - // Parse the ABI - let abi = load_abi_from_json(abi_json).unwrap(); - - // Create a network config - let network = networks::gnosis(); - - // Create a contract from an address string - let address_str = "0x1234567890123456789012345678901234567890"; - let contract = Contract::from_address_string(address_str, abi, network).unwrap(); - - // Verify the contract was created correctly - assert_eq!(contract.address, Address::from_str(address_str).unwrap()); - - // Test with an invalid address - let invalid_address = "0xinvalid"; - let result = Contract::from_address_string(invalid_address, contract.abi.clone(), contract.network.clone()); - assert!(result.is_err()); -} - -// Note: We can't easily test the actual contract calls in unit tests without mocking -// the provider, which would be complex. These would be better tested in integration tests -// with a local blockchain or testnet. diff --git a/vault/src/ethereum.bak/tests/mod.rs b/vault/src/ethereum.bak/tests/mod.rs deleted file mode 100644 index 2c5c097..0000000 --- a/vault/src/ethereum.bak/tests/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Tests for Ethereum functionality. - -mod wallet_tests; -mod network_tests; -mod transaction_tests; -mod contract_tests; -mod contract_args_tests; diff --git a/vault/src/ethereum.bak/tests/network_tests.rs b/vault/src/ethereum.bak/tests/network_tests.rs deleted file mode 100644 index a66bd4a..0000000 --- a/vault/src/ethereum.bak/tests/network_tests.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Tests for Ethereum network functionality. - -use crate::vault::ethereum::*; - -#[test] -fn test_network_config() { - let gnosis = networks::gnosis(); - assert_eq!(gnosis.name, "Gnosis"); - assert_eq!(gnosis.chain_id, 100); - assert_eq!(gnosis.token_symbol, "xDAI"); - - let peaq = networks::peaq(); - assert_eq!(peaq.name, "Peaq"); - assert_eq!(peaq.chain_id, 3338); - assert_eq!(peaq.token_symbol, "PEAQ"); - - let agung = networks::agung(); - assert_eq!(agung.name, "Agung"); - assert_eq!(agung.chain_id, 9990); - assert_eq!(agung.token_symbol, "AGNG"); -} - -#[test] -fn test_network_registry() { - let network_names = networks::list_network_names(); - assert!(network_names.iter().any(|&name| name == "Gnosis")); - assert!(network_names.iter().any(|&name| name == "Peaq")); - assert!(network_names.iter().any(|&name| name == "Agung")); - - let gnosis_proper = networks::get_proper_network_name("gnosis"); - assert_eq!(gnosis_proper, Some("Gnosis")); - - let peaq_proper = networks::get_proper_network_name("peaq"); - assert_eq!(peaq_proper, Some("Peaq")); - - let agung_proper = networks::get_proper_network_name("agung"); - assert_eq!(agung_proper, Some("Agung")); - - let unknown = networks::get_proper_network_name("unknown"); - assert_eq!(unknown, None); - - let gnosis_config = networks::get_network_by_name("Gnosis"); - assert!(gnosis_config.is_some()); - assert_eq!(gnosis_config.unwrap().chain_id, 100); - - let unknown_config = networks::get_network_by_name("Unknown"); - assert!(unknown_config.is_none()); -} - -#[test] -fn test_create_provider() { - let gnosis = networks::gnosis(); - let peaq = networks::peaq(); - let agung = networks::agung(); - - // Create providers - let gnosis_provider = create_provider(&gnosis); - let peaq_provider = create_provider(&peaq); - let agung_provider = create_provider(&agung); - - // They should all succeed - assert!(gnosis_provider.is_ok()); - assert!(peaq_provider.is_ok()); - assert!(agung_provider.is_ok()); - - // The convenience functions should also work - let gnosis_provider2 = create_gnosis_provider(); - let peaq_provider2 = create_peaq_provider(); - let agung_provider2 = create_agung_provider(); - - assert!(gnosis_provider2.is_ok()); - assert!(peaq_provider2.is_ok()); - assert!(agung_provider2.is_ok()); -} diff --git a/vault/src/ethereum.bak/tests/transaction_tests.rs b/vault/src/ethereum.bak/tests/transaction_tests.rs deleted file mode 100644 index 1fcc01c..0000000 --- a/vault/src/ethereum.bak/tests/transaction_tests.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Tests for Ethereum transaction functionality. - -use crate::vault::ethereum::*; -use crate::vault::keypair::implementation::KeyPair; -use ethers::types::U256; -// use std::str::FromStr; - -#[test] -fn test_format_balance() { - let network = networks::gnosis(); - - // Test with 0 - let balance = U256::from(0); - let formatted = format_balance(balance, &network); - assert_eq!(formatted, "0.000000 xDAI"); - - // Test with 1 wei - let balance = U256::from(1); - let formatted = format_balance(balance, &network); - assert_eq!(formatted, "0.000000 xDAI"); - - // Test with 1 gwei (10^9 wei) - let balance = U256::from(1_000_000_000u64); - let formatted = format_balance(balance, &network); - assert_eq!(formatted, "0.000000 xDAI"); - - // Test with 1 ETH (10^18 wei) - let balance = U256::from_dec_str("1000000000000000000").unwrap(); - let formatted = format_balance(balance, &network); - assert_eq!(formatted, "1.000000 xDAI"); - - // Test with a larger amount - let balance = U256::from_dec_str("123456789000000000000").unwrap(); - let formatted = format_balance(balance, &network); - assert_eq!(formatted, "123.456789 xDAI"); -} - -#[test] -fn test_get_balance() { - // This is a mock test since we can't actually query the blockchain in a unit test - // In a real test, we would use a local blockchain or mock the provider - - // Create a provider - let network = networks::gnosis(); - let provider_result = create_provider(&network); - - // The provider creation should succeed - assert!(provider_result.is_ok()); - - // We can't actually test get_balance without a blockchain - // In a real test, we would mock the provider and test the function -} - -#[test] -fn test_send_eth() { - // This is a mock test since we can't actually send transactions in a unit test - // In a real test, we would use a local blockchain or mock the provider - - // Create a wallet - let keypair = KeyPair::new("test_keypair6"); - let network = networks::gnosis(); - let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); - - // Create a provider - let provider_result = create_provider(&network); - assert!(provider_result.is_ok()); - - // We can't actually test send_eth without a blockchain - // In a real test, we would mock the provider and test the function -} diff --git a/vault/src/ethereum.bak/tests/wallet_tests.rs b/vault/src/ethereum.bak/tests/wallet_tests.rs deleted file mode 100644 index eb6502a..0000000 --- a/vault/src/ethereum.bak/tests/wallet_tests.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Tests for Ethereum wallet functionality. - -use crate::vault::ethereum::*; -use crate::vault::keypair::implementation::KeyPair; -use ethers::utils::hex; - -#[test] -fn test_ethereum_wallet_from_keypair() { - let keypair = KeyPair::new("test_keypair"); - let network = networks::gnosis(); - - let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); - - assert_eq!(wallet.network.name, "Gnosis"); - assert_eq!(wallet.network.chain_id, 100); - - // The address should be a valid Ethereum address - assert!(wallet.address_string().starts_with("0x")); -} - -#[test] -fn test_ethereum_wallet_from_name_and_keypair() { - let keypair = KeyPair::new("test_keypair2"); - let network = networks::gnosis(); - - let wallet = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap(); - - assert_eq!(wallet.network.name, "Gnosis"); - assert_eq!(wallet.network.chain_id, 100); - - // The address should be a valid Ethereum address - assert!(wallet.address_string().starts_with("0x")); - - // Creating another wallet with the same name and keypair should yield the same address - let wallet2 = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap(); - assert_eq!(wallet.address, wallet2.address); - - // Creating a wallet with a different name should yield a different address - let wallet3 = EthereumWallet::from_name_and_keypair("test2", &keypair, network.clone()).unwrap(); - assert_ne!(wallet.address, wallet3.address); -} - -#[test] -fn test_ethereum_wallet_from_private_key() { - let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - let network = networks::gnosis(); - - let wallet = EthereumWallet::from_private_key(private_key, network.clone()).unwrap(); - - assert_eq!(wallet.network.name, "Gnosis"); - assert_eq!(wallet.network.chain_id, 100); - - // The address should be a valid Ethereum address - assert!(wallet.address_string().starts_with("0x")); - - // The address should be deterministic based on the private key - let wallet2 = EthereumWallet::from_private_key(private_key, network.clone()).unwrap(); - assert_eq!(wallet.address, wallet2.address); -} - -#[test] -fn test_wallet_management() { - // Clear any existing wallets - clear_ethereum_wallets(); - - // Create a key space and keypair - crate::vault::keypair::session_manager::create_space("test_space").unwrap(); - crate::vault::keypair::create_keypair("test_keypair3").unwrap(); - - // Create wallets for different networks - let gnosis_wallet = create_ethereum_wallet_for_network(networks::gnosis()).unwrap(); - let peaq_wallet = create_ethereum_wallet_for_network(networks::peaq()).unwrap(); - let agung_wallet = create_ethereum_wallet_for_network(networks::agung()).unwrap(); - - // Get the current wallets - let current_gnosis = get_current_ethereum_wallet_for_network("Gnosis").unwrap(); - let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap(); - let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap(); - - // Check that they match - assert_eq!(gnosis_wallet.address, current_gnosis.address); - assert_eq!(peaq_wallet.address, current_peaq.address); - assert_eq!(agung_wallet.address, current_agung.address); - - // Clear wallets for a specific network - clear_ethereum_wallets_for_network("Gnosis"); - - // Check that the wallet is gone - let result = get_current_ethereum_wallet_for_network("Gnosis"); - assert!(result.is_err()); - - // But the others should still be there - let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap(); - let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap(); - assert_eq!(peaq_wallet.address, current_peaq.address); - assert_eq!(agung_wallet.address, current_agung.address); - - // Clear all wallets - clear_ethereum_wallets(); - - // Check that all wallets are gone - let result1 = get_current_ethereum_wallet_for_network("Gnosis"); - let result2 = get_current_ethereum_wallet_for_network("Peaq"); - let result3 = get_current_ethereum_wallet_for_network("Agung"); - assert!(result1.is_err()); - assert!(result2.is_err()); - assert!(result3.is_err()); -} - -#[test] -fn test_sign_message() { - let keypair = KeyPair::new("test_keypair4"); - let network = networks::gnosis(); - - let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); - - // Create a tokio runtime for the async test - let rt = tokio::runtime::Runtime::new().unwrap(); - - // Sign a message - let message = b"Hello, world!"; - let signature = rt.block_on(wallet.sign_message(message)).unwrap(); - - // The signature should be a non-empty string - assert!(!signature.is_empty()); -} - -#[test] -fn test_private_key_hex() { - let keypair = KeyPair::new("test_keypair5"); - let network = networks::gnosis(); - - let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); - - // Get the private key as hex - let private_key_hex = wallet.private_key_hex(); - - // The private key should be a 64-character hex string (32 bytes) - assert_eq!(private_key_hex.len(), 64); - - // It should be possible to parse it as hex - let _bytes = hex::decode(private_key_hex).unwrap(); -} diff --git a/vault/src/ethereum.bak/transaction.rs b/vault/src/ethereum.bak/transaction.rs deleted file mode 100644 index fd9deb6..0000000 --- a/vault/src/ethereum.bak/transaction.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Ethereum transaction functionality. - -use ethers::prelude::*; - -use crate::vault::error::CryptoError; -use super::wallet::EthereumWallet; -use super::networks::NetworkConfig; - -/// Formats a token balance for display. -pub fn format_balance(balance: U256, network: &NetworkConfig) -> String { - let wei = balance.as_u128(); - let divisor = 10u128.pow(network.decimals as u32) as f64; - let token = wei as f64 / divisor; - - // Display with the appropriate number of decimal places - let display_decimals = std::cmp::min(6, network.decimals); - - format!("{:.*} {}", display_decimals as usize, token, network.token_symbol) -} - -/// Gets the balance of an Ethereum address. -pub async fn get_balance(provider: &Provider, address: Address) -> Result { - provider.get_balance(address, None) - .await - .map_err(|e| CryptoError::SerializationError(format!("Failed to get balance: {}", e))) -} - -/// Sends Ethereum from one address to another. -pub async fn send_eth( - wallet: &EthereumWallet, - provider: &Provider, - to: Address, - amount: U256, -) -> Result { - // Create a client with the wallet - let client = SignerMiddleware::new( - provider.clone(), - wallet.wallet.clone(), - ); - - // Create the transaction - let tx = TransactionRequest::new() - .to(to) - .value(amount) - .gas(21000); - - // Send the transaction - let pending_tx = client.send_transaction(tx, None) - .await - .map_err(|e| CryptoError::SerializationError(format!("Failed to send transaction: {}", e)))?; - - // Return the transaction hash instead of waiting for the receipt - Ok(pending_tx.tx_hash()) -} diff --git a/vault/src/ethereum.bak/wallet.rs b/vault/src/ethereum.bak/wallet.rs deleted file mode 100644 index 8209cdb..0000000 --- a/vault/src/ethereum.bak/wallet.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Ethereum wallet implementation. - -use ethers::prelude::*; -use ethers::signers::{LocalWallet, Signer, Wallet}; -use ethers::utils::hex; -use k256::ecdsa::SigningKey; -use sha2::{Digest, Sha256}; -use std::str::FromStr; - -use super::networks::NetworkConfig; -use crate::vault::error::CryptoError; -use crate::vault::keyspace::KeyPair; - -/// An Ethereum wallet derived from a keypair. -#[derive(Debug, Clone)] -pub struct EthereumWallet { - pub address: Address, - pub wallet: Wallet, - pub network: NetworkConfig, -} - -impl EthereumWallet { - /// Creates a new Ethereum wallet from a keypair for a specific network. - pub fn from_keypair(keypair: &KeyPair, network: NetworkConfig) -> Result { - // Get the private key bytes from the keypair - let private_key_bytes = keypair.signing_key.to_bytes(); - - // Convert to a hex string (without 0x prefix) - let private_key_hex = hex::encode(private_key_bytes); - - // Create an Ethereum wallet from the private key - let wallet = LocalWallet::from_str(&private_key_hex) - .map_err(|_e| CryptoError::InvalidKeyLength)? - .with_chain_id(network.chain_id); - - // Get the Ethereum address - let address = wallet.address(); - - Ok(EthereumWallet { - address, - wallet, - network, - }) - } - - /// Creates a new Ethereum wallet from a name and keypair (deterministic derivation) for a specific network. - pub fn from_name_and_keypair( - name: &str, - keypair: &KeyPair, - network: NetworkConfig, - ) -> Result { - // Get the private key bytes from the keypair - let private_key_bytes = keypair.signing_key.to_bytes(); - - // Create a deterministic seed by combining name and private key - let mut hasher = Sha256::default(); - hasher.update(name.as_bytes()); - hasher.update(&private_key_bytes); - let seed = hasher.finalize(); - - // Use the seed as a private key - let private_key_hex = hex::encode(seed); - - // Create an Ethereum wallet from the derived private key - let wallet = LocalWallet::from_str(&private_key_hex) - .map_err(|_e| CryptoError::InvalidKeyLength)? - .with_chain_id(network.chain_id); - - // Get the Ethereum address - let address = wallet.address(); - - Ok(EthereumWallet { - address, - wallet, - network, - }) - } - - /// Creates a new Ethereum wallet from a private key for a specific network. - pub fn from_private_key( - private_key: &str, - network: NetworkConfig, - ) -> Result { - // Remove 0x prefix if present - let private_key_clean = private_key.trim_start_matches("0x"); - - // Create an Ethereum wallet from the private key - let wallet = LocalWallet::from_str(private_key_clean) - .map_err(|_e| CryptoError::InvalidKeyLength)? - .with_chain_id(network.chain_id); - - // Get the Ethereum address - let address = wallet.address(); - - Ok(EthereumWallet { - address, - wallet, - network, - }) - } - - /// Gets the Ethereum address as a string. - pub fn address_string(&self) -> String { - format!("{:?}", self.address) - } - - /// Signs a message with the Ethereum wallet. - pub async fn sign_message(&self, message: &[u8]) -> Result { - let signature = self - .wallet - .sign_message(message) - .await - .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; - - Ok(signature.to_string()) - } - - /// Gets the private key as a hex string. - pub fn private_key_hex(&self) -> String { - let bytes = self.wallet.signer().to_bytes(); - hex::encode(bytes) - } -} diff --git a/vault/src/keyspace.bak/README.md b/vault/src/keyspace.bak/README.md deleted file mode 100644 index 4cfb15d..0000000 --- a/vault/src/keyspace.bak/README.md +++ /dev/null @@ -1,271 +0,0 @@ -# Hero Vault Keypair Module - -The Keypair module provides functionality for creating, managing, and using ECDSA keypairs for digital signatures and other cryptographic operations. - -## Module Structure - -The Keypair module is organized into: - -- `keypair_types.rs` - Defines the KeyPair and related types. -- `session_manager.rs` - Implements the core logic for managing keypairs and key spaces. -- `mod.rs` - Module exports and public interface. - -## Key Types - -### KeyPair - -The `KeyPair` type represents an ECDSA keypair used for digital signatures and other cryptographic operations. - -```rust -pub struct KeyPair { - // Private fields - // ... -} - -impl KeyPair { - // Create a new random keypair - pub fn new() -> Result; - - // Create a keypair from an existing private key - pub fn from_private_key(private_key: &[u8]) -> Result; - - // Get the public key - pub fn public_key(&self) -> &[u8]; - - // Sign a message - pub fn sign(&self, message: &[u8]) -> Result, CryptoError>; - - // Verify a signature - pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result; - - // Derive an Ethereum address from the public key - pub fn to_ethereum_address(&self) -> Result; - - // Export the private key (should be used with caution) - pub fn export_private_key(&self) -> Result, CryptoError>; -} -``` - -### KeySpace - -The `KeySpace` type represents a secure container for multiple keypairs, which can be encrypted and stored on disk. - -```rust -pub struct KeySpace { - // Private fields - // ... -} - -impl KeySpace { - // Create a new key space - pub fn new(name: &str, password: &str) -> Result; - - // Load a key space from disk - pub fn load(name: &str, password: &str) -> Result; - - // Save the key space to disk - pub fn save(&self) -> Result<(), CryptoError>; - - // Create a new keypair in the key space - pub fn create_keypair(&mut self, name: &str, password: &str) -> Result<&KeyPair, CryptoError>; - - // Select a keypair for use - pub fn select_keypair(&mut self, name: &str) -> Result<&KeyPair, CryptoError>; - - // Get the currently selected keypair - pub fn current_keypair(&self) -> Result<&KeyPair, CryptoError>; - - // List all keypairs in the key space - pub fn list_keypairs(&self) -> Result, CryptoError>; - - // Get a keypair by name - pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError>; - - // Remove a keypair from the key space - pub fn remove_keypair(&mut self, name: &str) -> Result<(), CryptoError>; - - // Rename a keypair - pub fn rename_keypair(&mut self, old_name: &str, new_name: &str) -> Result<(), CryptoError>; - - // Get the name of the key space - pub fn name(&self) -> &str; -} -``` - -## Key Features - -### Key Space Management - -The module provides functionality for creating, loading, and managing key spaces: - -```rust -// Create a new key space -let mut space = KeySpace::new("my_space", "secure_password")?; - -// Save the key space to disk -space.save()?; - -// Load a key space from disk -let mut loaded_space = KeySpace::load("my_space", "secure_password")?; -``` - -### Keypair Management - -The module provides functionality for creating, selecting, and using keypairs: - -```rust -use crate::vault::keypair::{KeySpace, KeyPair}; -use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error - -fn demonstrate_keypair_management() -> Result<(), CryptoError> { - // Create a new key space - let mut space = KeySpace::new("my_space", "secure_password")?; - - // Create a new keypair in the key space - let keypair = space.create_keypair("my_keypair", "secure_password")?; - println!("Created keypair: {}", keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::()); - - // Select a keypair for use - space.select_keypair("my_keypair")?; - println!("Selected keypair: {}", space.current_keypair()?.public_key().iter().map(|b| format!("{:02x}", b)).collect::()); - - // List all keypairs in the key space - let keypairs = space.list_keypairs()?; - println!("Keypairs in space: {:?}", keypairs); - - // Get a keypair by name - let retrieved_keypair = space.get_keypair("my_keypair")?; - println!("Retrieved keypair: {}", retrieved_keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::()); - - // Rename a keypair - space.rename_keypair("my_keypair", "new_name")?; - println!("Renamed keypair to new_name"); - let keypairs_after_rename = space.list_keypairs()?; - println!("Keypairs in space after rename: {:?}", keypairs_after_rename); - - - // Remove a keypair from the key space - space.remove_keypair("new_name")?; - println!("Removed keypair new_name"); - let keypairs_after_remove = space.list_keypairs()?; - println!("Keypairs in space after removal: {:?}", keypairs_after_remove); - - Ok(()) -} -``` - -### Digital Signatures - -The module provides functionality for signing and verifying messages using ECDSA: - -```rust -use crate::vault::keypair::KeySpace; -use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error - -fn demonstrate_digital_signatures() -> Result<(), CryptoError> { - // Assuming a key space and selected keypair exist - // let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space - let mut space = KeySpace::new("temp_space_for_demo", "password")?; // Or create a new one for demo - space.create_keypair("my_signing_key", "key_password")?; - space.select_keypair("my_signing_key")?; - - - // Sign a message using the selected keypair - let keypair = space.current_keypair()?; - let message = "This is a message to sign".as_bytes(); - let signature = keypair.sign(message)?; - println!("Message signed. Signature: {:?}", signature); - - // Verify a signature - let is_valid = keypair.verify(message, &signature)?; - println!("Signature valid: {}", is_valid); - - // Example of invalid signature verification - let invalid_signature = vec![0u8; signature.len()]; // A dummy invalid signature - let is_valid_invalid = keypair.verify(message, &invalid_signature)?; - println!("Invalid signature valid: {}", is_valid_invalid); - - - Ok(()) -} -``` - -### Ethereum Address Derivation - -The module provides functionality for deriving Ethereum addresses from keypairs: - -```rust -use crate::vault::keypair::KeySpace; -use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error - -fn demonstrate_ethereum_address_derivation() -> Result<(), CryptoError> { - // Assuming a key space and selected keypair exist - // let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space - let mut space = KeySpace::new("temp_space_for_eth_demo", "password")?; // Or create a new one for demo - space.create_keypair("my_eth_key", "key_password")?; - space.select_keypair("my_eth_key")?; - - // Derive an Ethereum address from a keypair - let keypair = space.current_keypair()?; - let address = keypair.to_ethereum_address()?; - println!("Derived Ethereum address: {}", address); - - Ok(()) -} -``` - -## Including in Your Project - -To include the Hero Vault Keypair module in your Rust project, add the following to your `Cargo.toml` file: - -```toml -[dependencies] -hero_vault = "0.1.0" # Replace with the actual version -``` - -Then, you can import and use the module in your Rust code: - -```rust -use hero_vault::vault::keypair::{KeySpace, KeyPair}; -use hero_vault::vault::error::CryptoError; -``` - -## Testing - -Tests for the Keypair module are included within the source files, likely in `session_manager.rs` or `mod.rs` as inline tests. - -To run the tests, navigate to the root directory of the project in your terminal and execute the following command: - -```bash -cargo test --lib vault::keypair -``` - -This command will run all tests specifically within the `vault::keypair` module. - -## Security Considerations - -- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password -- Private keys are never stored in plaintext -- The module uses secure random number generation for key creation -- All cryptographic operations use well-established libraries and algorithms - -## Error Handling - -The module uses the `CryptoError` type for handling errors that can occur during keypair operations: - -- `InvalidKeyLength` - Invalid key length -- `SignatureFormatError` - Signature format error -- `KeypairAlreadyExists` - Keypair already exists -- `KeypairNotFound` - Keypair not found -- `NoActiveSpace` - No active key space -- `NoKeypairSelected` - No keypair selected -- `SerializationError` - Serialization error - -## Examples - -For examples of how to use the Keypair module, see the `examples/hero_vault` directory, particularly: - -- `example.rhai` - Basic example demonstrating key management and signing -- `advanced_example.rhai` - Advanced example with error handling -- `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk -- `load_existing_space.rhai` - Shows how to load a previously created key space diff --git a/vault/src/keyspace.bak/keypair_types.rs b/vault/src/keyspace.bak/keypair_types.rs deleted file mode 100644 index 4f3fe1c..0000000 --- a/vault/src/keyspace.bak/keypair_types.rs +++ /dev/null @@ -1,328 +0,0 @@ -use k256::ecdh::EphemeralSecret; -/// Implementation of keypair functionality. -use k256::ecdsa::{ - signature::{Signer, Verifier}, - Signature, SigningKey, VerifyingKey, -}; -use rand::rngs::OsRng; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use std::collections::HashMap; - -use crate::vault::error::CryptoError; -use crate::vault::symmetric::implementation; - -/// A keypair for signing and verifying messages. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KeyPair { - pub name: String, - #[serde(with = "verifying_key_serde")] - pub verifying_key: VerifyingKey, - #[serde(with = "signing_key_serde")] - pub signing_key: SigningKey, -} - -// Serialization helpers for VerifyingKey -mod verifying_key_serde { - use super::*; - use serde::de::{self, Visitor}; - use serde::{Deserializer, Serializer}; - use std::fmt; - - pub fn serialize(key: &VerifyingKey, serializer: S) -> Result - where - S: Serializer, - { - let bytes = key.to_sec1_bytes(); - // Convert bytes to a Vec and serialize that instead - serializer.collect_seq(bytes) - } - - struct VerifyingKeyVisitor; - - impl<'de> Visitor<'de> for VerifyingKeyVisitor { - type Value = VerifyingKey; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a byte array representing a verifying key") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: de::Error, - { - VerifyingKey::from_sec1_bytes(v).map_err(|e| { - log::error!("Error deserializing verifying key: {:?}", e); - E::custom(format!("invalid verifying key: {:?}", e)) - }) - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: de::SeqAccess<'de>, - { - // Collect all bytes from the sequence - let mut bytes = Vec::new(); - while let Some(byte) = seq.next_element()? { - bytes.push(byte); - } - - VerifyingKey::from_sec1_bytes(&bytes).map_err(|e| { - log::error!("Error deserializing verifying key from seq: {:?}", e); - de::Error::custom(format!("invalid verifying key from seq: {:?}", e)) - }) - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - // Try to deserialize as bytes first, then as a sequence - deserializer.deserialize_any(VerifyingKeyVisitor) - } -} - -// Serialization helpers for SigningKey -mod signing_key_serde { - use super::*; - use serde::de::{self, Visitor}; - use serde::{Deserializer, Serializer}; - use std::fmt; - - pub fn serialize(key: &SigningKey, serializer: S) -> Result - where - S: Serializer, - { - let bytes = key.to_bytes(); - // Convert bytes to a Vec and serialize that instead - serializer.collect_seq(bytes) - } - - struct SigningKeyVisitor; - - impl<'de> Visitor<'de> for SigningKeyVisitor { - type Value = SigningKey; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a byte array representing a signing key") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: de::Error, - { - SigningKey::from_bytes(v.into()).map_err(|e| { - log::error!("Error deserializing signing key: {:?}", e); - E::custom(format!("invalid signing key: {:?}", e)) - }) - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: de::SeqAccess<'de>, - { - // Collect all bytes from the sequence - let mut bytes = Vec::new(); - while let Some(byte) = seq.next_element()? { - bytes.push(byte); - } - - SigningKey::from_bytes(bytes.as_slice().into()).map_err(|e| { - log::error!("Error deserializing signing key from seq: {:?}", e); - de::Error::custom(format!("invalid signing key from seq: {:?}", e)) - }) - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - // Try to deserialize as bytes first, then as a sequence - deserializer.deserialize_any(SigningKeyVisitor) - } -} - -impl KeyPair { - /// Creates a new keypair with the given name. - pub fn new(name: &str) -> Self { - let signing_key = SigningKey::random(&mut OsRng); - let verifying_key = VerifyingKey::from(&signing_key); - - KeyPair { - name: name.to_string(), - verifying_key, - signing_key, - } - } - - /// Gets the public key bytes. - pub fn pub_key(&self) -> Vec { - self.verifying_key.to_sec1_bytes().to_vec() - } - - /// Derives a public key from a private key. - pub fn pub_key_from_private(private_key: &[u8]) -> Result, CryptoError> { - let signing_key = SigningKey::from_bytes(private_key.into()) - .map_err(|_| CryptoError::InvalidKeyLength)?; - let verifying_key = VerifyingKey::from(&signing_key); - Ok(verifying_key.to_sec1_bytes().to_vec()) - } - - /// Signs a message. - pub fn sign(&self, message: &[u8]) -> Vec { - let signature: Signature = self.signing_key.sign(message); - signature.to_bytes().to_vec() - } - - /// Verifies a message signature. - pub fn verify(&self, message: &[u8], signature_bytes: &[u8]) -> Result { - let signature = Signature::from_bytes(signature_bytes.into()) - .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; - - match self.verifying_key.verify(message, &signature) { - Ok(_) => Ok(true), - Err(_) => Ok(false), // Verification failed, but operation was successful - } - } - - /// Verifies a message signature using only a public key. - pub fn verify_with_public_key( - public_key: &[u8], - message: &[u8], - signature_bytes: &[u8], - ) -> Result { - let verifying_key = - VerifyingKey::from_sec1_bytes(public_key).map_err(|_| CryptoError::InvalidKeyLength)?; - - let signature = Signature::from_bytes(signature_bytes.into()) - .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; - - match verifying_key.verify(message, &signature) { - Ok(_) => Ok(true), - Err(_) => Ok(false), // Verification failed, but operation was successful - } - } - - /// Encrypts a message using the recipient's public key. - /// This implements ECIES (Elliptic Curve Integrated Encryption Scheme): - /// 1. Generate an ephemeral keypair - /// 2. Derive a shared secret using ECDH - /// 3. Derive encryption key from the shared secret - /// 4. Encrypt the message using symmetric encryption - /// 5. Return the ephemeral public key and the ciphertext - pub fn encrypt_asymmetric( - &self, - recipient_public_key: &[u8], - message: &[u8], - ) -> Result, CryptoError> { - // Parse recipient's public key - let recipient_key = VerifyingKey::from_sec1_bytes(recipient_public_key) - .map_err(|_| CryptoError::InvalidKeyLength)?; - - // Generate ephemeral keypair - let ephemeral_signing_key = SigningKey::random(&mut OsRng); - let ephemeral_public_key = VerifyingKey::from(&ephemeral_signing_key); - - // Derive shared secret using ECDH - let ephemeral_secret = EphemeralSecret::random(&mut OsRng); - let shared_secret = ephemeral_secret.diffie_hellman(&recipient_key.into()); - - // Derive encryption key from the shared secret (e.g., using HKDF or hashing) - // For simplicity, we'll hash the shared secret here - let encryption_key = { - let mut hasher = Sha256::default(); - hasher.update(shared_secret.raw_secret_bytes()); - hasher.finalize().to_vec() - }; - - // Encrypt the message using the derived key - let ciphertext = implementation::encrypt_with_key(&encryption_key, message) - .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?; - - // Format: ephemeral_public_key || ciphertext - let mut result = ephemeral_public_key - .to_encoded_point(false) - .as_bytes() - .to_vec(); - result.extend_from_slice(&ciphertext); - - Ok(result) - } - - /// Decrypts a message using the recipient's private key. - /// This is the counterpart to encrypt_asymmetric. - pub fn decrypt_asymmetric(&self, ciphertext: &[u8]) -> Result, CryptoError> { - // The first 33 or 65 bytes (depending on compression) are the ephemeral public key - // For simplicity, we'll assume uncompressed keys (65 bytes) - if ciphertext.len() <= 65 { - return Err(CryptoError::DecryptionFailed( - "Ciphertext too short".to_string(), - )); - } - - // Extract ephemeral public key and actual ciphertext - let ephemeral_public_key = &ciphertext[..65]; - let actual_ciphertext = &ciphertext[65..]; - - // Parse ephemeral public key - let sender_key = VerifyingKey::from_sec1_bytes(ephemeral_public_key) - .map_err(|_| CryptoError::InvalidKeyLength)?; - - // Derive shared secret using ECDH - let recipient_secret = EphemeralSecret::random(&mut OsRng); - let shared_secret = recipient_secret.diffie_hellman(&sender_key.into()); - - // Derive decryption key from the shared secret (using the same method as encryption) - let decryption_key = { - let mut hasher = Sha256::default(); - hasher.update(shared_secret.raw_secret_bytes()); - hasher.finalize().to_vec() - }; - - // Decrypt the message using the derived key - implementation::decrypt_with_key(&decryption_key, actual_ciphertext) - .map_err(|e| CryptoError::DecryptionFailed(e.to_string())) - } -} - -/// A collection of keypairs. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct KeySpace { - pub name: String, - pub keypairs: HashMap, -} - -impl KeySpace { - /// Creates a new key space with the given name. - pub fn new(name: &str) -> Self { - KeySpace { - name: name.to_string(), - keypairs: HashMap::new(), - } - } - - /// Adds a new keypair to the space. - pub fn add_keypair(&mut self, name: &str) -> Result<(), CryptoError> { - if self.keypairs.contains_key(name) { - return Err(CryptoError::KeypairAlreadyExists(name.to_string())); - } - - let keypair = KeyPair::new(name); - self.keypairs.insert(name.to_string(), keypair); - Ok(()) - } - - /// Gets a keypair by name. - pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError> { - self.keypairs - .get(name) - .ok_or(CryptoError::KeypairNotFound(name.to_string())) - } - - /// Lists all keypair names in the space. - pub fn list_keypairs(&self) -> Vec { - self.keypairs.keys().cloned().collect() - } -} diff --git a/vault/src/keyspace.bak/mod.rs b/vault/src/keyspace.bak/mod.rs deleted file mode 100644 index d9ea317..0000000 --- a/vault/src/keyspace.bak/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Key pair management functionality -//! -//! This module provides functionality for creating and managing ECDSA key pairs. - -pub mod keypair_types; -pub mod session_manager; - -// Re-export public types and functions -pub use keypair_types::{KeyPair, KeySpace}; -pub use session_manager::{ - create_space, set_current_space, get_current_space, clear_session, - create_keypair, select_keypair, get_selected_keypair, list_keypairs, - keypair_pub_key, derive_public_key, keypair_sign, keypair_verify, - verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric -}; - -#[cfg(test)] -mod tests; diff --git a/vault/src/keyspace.bak/session_manager.rs b/vault/src/keyspace.bak/session_manager.rs deleted file mode 100644 index 3fa7a11..0000000 --- a/vault/src/keyspace.bak/session_manager.rs +++ /dev/null @@ -1,174 +0,0 @@ -use once_cell::sync::Lazy; -use std::sync::Mutex; - -use crate::vault::error::CryptoError; -use crate::vault::keyspace::keypair_types::{KeyPair, KeySpace}; // Assuming KeyPair and KeySpace will be in keypair_types.rs - -/// Session state for the current key space and selected keypair. -pub struct Session { - pub current_space: Option, - pub selected_keypair: Option, -} - -impl Default for Session { - fn default() -> Self { - Session { - current_space: None, - selected_keypair: None, - } - } -} - -/// Global session state. -pub static SESSION: Lazy> = Lazy::new(|| Mutex::new(Session::default())); - -// Session management and selected keypair operation functions will be added here -/// Creates a new key space with the given name. -pub fn create_space(name: &str) -> Result<(), CryptoError> { - let mut session = SESSION.lock().unwrap(); - - // Create a new space - let space = KeySpace::new(name); - - // Set as current space - session.current_space = Some(space); - session.selected_keypair = None; - - Ok(()) -} - -/// Sets the current key space. -pub fn set_current_space(space: KeySpace) -> Result<(), CryptoError> { - let mut session = SESSION.lock().unwrap(); - session.current_space = Some(space); - session.selected_keypair = None; - Ok(()) -} - -/// Gets the current key space. -pub fn get_current_space() -> Result { - let session = SESSION.lock().unwrap(); - session - .current_space - .clone() - .ok_or(CryptoError::NoActiveSpace) -} - -/// Clears the current session (logout). -pub fn clear_session() { - let mut session = SESSION.lock().unwrap(); - session.current_space = None; - session.selected_keypair = None; -} - -/// Creates a new keypair in the current space. -pub fn create_keypair(name: &str) -> Result<(), CryptoError> { - let mut session = SESSION.lock().unwrap(); - - if let Some(ref mut space) = session.current_space { - if space.keypairs.contains_key(name) { - return Err(CryptoError::KeypairAlreadyExists(name.to_string())); - } - - let keypair = KeyPair::new(name); - space.keypairs.insert(name.to_string(), keypair); - - // Automatically select the new keypair - session.selected_keypair = Some(name.to_string()); - - Ok(()) - } else { - Err(CryptoError::NoActiveSpace) - } -} - -/// Selects a keypair for use. -pub fn select_keypair(name: &str) -> Result<(), CryptoError> { - let mut session = SESSION.lock().unwrap(); - - if let Some(ref space) = session.current_space { - if !space.keypairs.contains_key(name) { - return Err(CryptoError::KeypairNotFound(name.to_string())); - } - - session.selected_keypair = Some(name.to_string()); - Ok(()) - } else { - Err(CryptoError::NoActiveSpace) - } -} - -/// Gets the currently selected keypair. -pub fn get_selected_keypair() -> Result { - let session = SESSION.lock().unwrap(); - - if let Some(ref space) = session.current_space { - if let Some(ref keypair_name) = session.selected_keypair { - if let Some(keypair) = space.keypairs.get(keypair_name) { - return Ok(keypair.clone()); - } - return Err(CryptoError::KeypairNotFound(keypair_name.clone())); - } - return Err(CryptoError::NoKeypairSelected); - } - - Err(CryptoError::NoActiveSpace) -} - -/// Lists all keypair names in the current space. -pub fn list_keypairs() -> Result, CryptoError> { - let session = SESSION.lock().unwrap(); - - if let Some(ref space) = session.current_space { - Ok(space.keypairs.keys().cloned().collect()) - } else { - Err(CryptoError::NoActiveSpace) - } -} - -/// Gets the public key of the selected keypair. -pub fn keypair_pub_key() -> Result, CryptoError> { - let keypair = get_selected_keypair()?; - Ok(keypair.pub_key()) -} - -/// Derives a public key from a private key. -pub fn derive_public_key(private_key: &[u8]) -> Result, CryptoError> { - KeyPair::pub_key_from_private(private_key) -} - -/// Signs a message with the selected keypair. -pub fn keypair_sign(message: &[u8]) -> Result, CryptoError> { - let keypair = get_selected_keypair()?; - Ok(keypair.sign(message)) -} - -/// Verifies a message signature with the selected keypair. -pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result { - let keypair = get_selected_keypair()?; - keypair.verify(message, signature_bytes) -} - -/// Verifies a message signature with a public key. -pub fn verify_with_public_key( - public_key: &[u8], - message: &[u8], - signature_bytes: &[u8], -) -> Result { - KeyPair::verify_with_public_key(public_key, message, signature_bytes) -} - -/// Encrypts a message for a recipient using their public key. -pub fn encrypt_asymmetric( - recipient_public_key: &[u8], - message: &[u8], -) -> Result, CryptoError> { - let keypair = get_selected_keypair()?; - keypair.encrypt_asymmetric(recipient_public_key, message) -} - -/// Decrypts a message that was encrypted with the current keypair's public key. -pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result, CryptoError> { - let keypair = get_selected_keypair()?; - keypair.decrypt_asymmetric(ciphertext) -} diff --git a/vault/src/keyspace.bak/spec.md b/vault/src/keyspace.bak/spec.md deleted file mode 100644 index add2d42..0000000 --- a/vault/src/keyspace.bak/spec.md +++ /dev/null @@ -1,36 +0,0 @@ -# Keyspace Module Specification - -This document explains the purpose and functionality of the `keyspace` module within the Hero Vault. - -## Purpose of the Module - -The `keyspace` module provides a secure and organized way to manage cryptographic keypairs. It allows for the creation, storage, loading, and utilization of keypairs within designated containers called keyspaces. This module is essential for handling sensitive cryptographic material securely. - -## What is a Keyspace? - -A keyspace is a logical container designed to hold multiple cryptographic keypairs. It is represented by the `KeySpace` struct in the code. Keyspaces can be encrypted and persisted to disk, providing a secure method for storing collections of keypairs. Each keyspace is identified by a unique name. - -## What is a Keypair? - -A keypair, represented by the `KeyPair` struct, is a fundamental cryptographic element consisting of a mathematically linked pair of keys: a public key and a private key. In this module, ECDSA (Elliptic Curve Digital Signature Algorithm) keypairs are used. - -* **Private Key:** This key is kept secret and is used for operations like signing data or decrypting messages intended for the keypair's owner. -* **Public Key:** This key can be shared openly and is used to verify signatures created by the corresponding private key or to encrypt messages that can only be decrypted by the private key. - -## How Many Keypairs Per Space? - -A keyspace can hold multiple keypairs. The `KeySpace` struct uses a `HashMap` to store keypairs, where each keypair is associated with a unique string name. There is no inherent, fixed limit on the number of keypairs a keyspace can contain, beyond the practical limitations of system memory. - -## How Do We Load Them? - -Keyspaces are loaded from persistent storage (disk) using the `KeySpace::load` function, which requires the keyspace name and a password for decryption. Once a `KeySpace` object is loaded into memory, it can be set as the currently active keyspace for the session using the `session_manager::set_current_space` function. Individual keypairs within the loaded keyspace are then accessed by their names using functions like `session_manager::select_keypair` and `session_manager::get_selected_keypair`. - -## What Do They Do? - -Keypairs within a keyspace are used to perform various cryptographic operations. The `KeyPair` struct provides methods for: - -* **Digital Signatures:** Signing messages with the private key (`KeyPair::sign`) and verifying those signatures with the public key (`KeyPair::verify`). -* **Ethereum Address Derivation:** Generating an Ethereum address from the public key (`KeyPair::to_ethereum_address`). -* **Asymmetric Encryption/Decryption:** Encrypting data using a recipient's public key (`KeyPair::encrypt_asymmetric`) and decrypting data encrypted with the keypair's public key using the private key (`KeyPair::decrypt_asymmetric`). - -The `session_manager` module provides functions that utilize the currently selected keypair to perform these operations within the context of the active session. \ No newline at end of file diff --git a/vault/src/keyspace.bak/tests/keypair_types_tests.rs b/vault/src/keyspace.bak/tests/keypair_types_tests.rs deleted file mode 100644 index 01752b6..0000000 --- a/vault/src/keyspace.bak/tests/keypair_types_tests.rs +++ /dev/null @@ -1,86 +0,0 @@ - -use crate::vault::keyspace::keypair_types::{KeyPair, KeySpace}; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_keypair_creation() { - let keypair = KeyPair::new("test_keypair"); - assert_eq!(keypair.name, "test_keypair"); - // Basic check that keys are generated (they should have non-zero length) - assert!(!keypair.pub_key().is_empty()); - } - - #[test] - fn test_keypair_sign_and_verify() { - let keypair = KeyPair::new("test_keypair"); - let message = b"This is a test message"; - let signature = keypair.sign(message); - assert!(!signature.is_empty()); - - let is_valid = keypair.verify(message, &signature).expect("Verification failed"); - assert!(is_valid); - - // Test with a wrong message - let wrong_message = b"This is a different message"; - let is_valid_wrong = keypair.verify(wrong_message, &signature).expect("Verification failed with wrong message"); - assert!(!is_valid_wrong); - } - - #[test] - fn test_verify_with_public_key() { - let keypair = KeyPair::new("test_keypair"); - let message = b"Another test message"; - let signature = keypair.sign(message); - let public_key = keypair.pub_key(); - - let is_valid = KeyPair::verify_with_public_key(&public_key, message, &signature).expect("Verification with public key failed"); - assert!(is_valid); - - // Test with a wrong public key - let wrong_keypair = KeyPair::new("wrong_keypair"); - let wrong_public_key = wrong_keypair.pub_key(); - let is_valid_wrong_key = KeyPair::verify_with_public_key(&wrong_public_key, message, &signature).expect("Verification with wrong public key failed"); - assert!(!is_valid_wrong_key); - } - - #[test] - fn test_asymmetric_encryption_decryption() { - // Sender's keypair - let sender_keypair = KeyPair::new("sender"); - let sender_public_key = sender_keypair.pub_key(); - - // Recipient's keypair - let recipient_keypair = KeyPair::new("recipient"); - let recipient_public_key = recipient_keypair.pub_key(); - - let message = b"This is a secret message"; - - // Sender encrypts for recipient - let ciphertext = sender_keypair.encrypt_asymmetric(&recipient_public_key, message).expect("Encryption failed"); - assert!(!ciphertext.is_empty()); - - // Recipient decrypts - let decrypted_message = recipient_keypair.decrypt_asymmetric(&ciphertext).expect("Decryption failed"); - assert_eq!(decrypted_message, message); - - // Test decryption with wrong keypair - let wrong_keypair = KeyPair::new("wrong_recipient"); - let result = wrong_keypair.decrypt_asymmetric(&ciphertext); - assert!(result.is_err()); - } - - #[test] - fn test_keyspace_add_keypair() { - let mut space = KeySpace::new("test_space"); - space.add_keypair("keypair1").expect("Failed to add keypair1"); - assert_eq!(space.keypairs.len(), 1); - assert!(space.keypairs.contains_key("keypair1")); - - // Test adding a duplicate keypair - let result = space.add_keypair("keypair1"); - assert!(result.is_err()); - } -} \ No newline at end of file diff --git a/vault/src/keyspace.bak/tests/mod.rs b/vault/src/keyspace.bak/tests/mod.rs deleted file mode 100644 index 770d0e5..0000000 --- a/vault/src/keyspace.bak/tests/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ - -mod keypair_types_tests; -mod session_manager_tests; \ No newline at end of file diff --git a/vault/src/keyspace.bak/tests/session_manager_tests.rs b/vault/src/keyspace.bak/tests/session_manager_tests.rs deleted file mode 100644 index 66ba44f..0000000 --- a/vault/src/keyspace.bak/tests/session_manager_tests.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::vault::keyspace::session_manager::{ - clear_session, create_keypair, create_space, get_current_space, get_selected_keypair, - list_keypairs, select_keypair, set_current_space, SESSION, -}; -use crate::vault::keyspace::keypair_types::KeySpace; - -// Helper function to clear the session before each test -fn setup_test() { - clear_session(); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_create_and_get_space() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - let space = get_current_space().expect("Failed to get current space"); - assert_eq!(space.name, "test_space"); - } - - #[test] - fn test_set_current_space() { - setup_test(); - let space = KeySpace::new("another_space"); - set_current_space(space.clone()).expect("Failed to set current space"); - let current_space = get_current_space().expect("Failed to get current space"); - assert_eq!(current_space.name, "another_space"); - } - - #[test] - fn test_clear_session() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - clear_session(); - let result = get_current_space(); - assert!(result.is_err()); - } - - #[test] - fn test_create_and_select_keypair() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - create_keypair("test_keypair").expect("Failed to create keypair"); - let keypair = get_selected_keypair().expect("Failed to get selected keypair"); - assert_eq!(keypair.name, "test_keypair"); - - select_keypair("test_keypair").expect("Failed to select keypair"); - let selected_keypair = get_selected_keypair().expect("Failed to get selected keypair after select"); - assert_eq!(selected_keypair.name, "test_keypair"); - } - - #[test] - fn test_list_keypairs() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - create_keypair("keypair1").expect("Failed to create keypair1"); - create_keypair("keypair2").expect("Failed to create keypair2"); - - let keypairs = list_keypairs().expect("Failed to list keypairs"); - assert_eq!(keypairs.len(), 2); - assert!(keypairs.contains(&"keypair1".to_string())); - assert!(keypairs.contains(&"keypair2".to_string())); - } - - #[test] - fn test_create_keypair_no_active_space() { - setup_test(); - let result = create_keypair("test_keypair"); - assert!(result.is_err()); - } - - #[test] - fn test_select_keypair_no_active_space() { - setup_test(); - let result = select_keypair("test_keypair"); - assert!(result.is_err()); - } - - #[test] - fn test_select_nonexistent_keypair() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - let result = select_keypair("nonexistent_keypair"); - assert!(result.is_err()); - } - - #[test] - fn test_get_selected_keypair_no_active_space() { - setup_test(); - let result = get_selected_keypair(); - assert!(result.is_err()); - } - - #[test] - fn test_get_selected_keypair_no_keypair_selected() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - let result = get_selected_keypair(); - assert!(result.is_err()); - } - - #[test] - fn test_list_keypairs_no_active_space() { - setup_test(); - let result = list_keypairs(); - assert!(result.is_err()); - } -} diff --git a/vault/src/kvs.bak/README.md b/vault/src/kvs.bak/README.md deleted file mode 100644 index 431a24c..0000000 --- a/vault/src/kvs.bak/README.md +++ /dev/null @@ -1,173 +0,0 @@ -# Hero Vault Key-Value Store Module - -The Key-Value Store (KVS) module provides an encrypted key-value store for securely storing sensitive data. - -## Module Structure - -The KVS module is organized into: - -- `store.rs` - Core implementation of the key-value store -- `error.rs` - Error types specific to the KVS module -- `mod.rs` - Module exports and public interface - -## Key Types - -### KvStore - -The `KvStore` type represents an encrypted key-value store: - -```rust -pub struct KvStore { - // Private fields - // ... -} - -impl KvStore { - // Create a new store - pub fn new(name: &str, password: &str) -> Result; - - // Load a store from disk - pub fn load(name: &str, password: &str) -> Result; - - // Save the store to disk - pub fn save(&self) -> Result<(), CryptoError>; - - // Set a value - pub fn set(&mut self, key: &str, value: &str) -> Result<(), CryptoError>; - - // Get a value - pub fn get(&self, key: &str) -> Result, CryptoError>; - - // Delete a value - pub fn delete(&mut self, key: &str) -> Result<(), CryptoError>; - - // Check if a key exists - pub fn has(&self, key: &str) -> Result; - - // List all keys - pub fn keys(&self) -> Result, CryptoError>; - - // Clear all values - pub fn clear(&mut self) -> Result<(), CryptoError>; - - // Get the name of the store - pub fn name(&self) -> &str; -} -``` - -## Key Features - -### Store Management - -The module provides functionality for creating, loading, and managing key-value stores: - -```rust -// Create a new store -let mut store = KvStore::new("my_store", "secure_password")?; - -// Save the store to disk -store.save()?; - -// Load a store from disk -let mut loaded_store = KvStore::load("my_store", "secure_password")?; -``` - -### Value Management - -The module provides functionality for managing values in the store: - -```rust -// Set a value -store.set("api_key", "secret_api_key")?; - -// Get a value -let api_key = store.get("api_key")?; - -// Delete a value -store.delete("api_key")?; - -// Check if a key exists -let exists = store.has("api_key")?; - -// List all keys -let keys = store.keys()?; - -// Clear all values -store.clear()?; -``` - -## Technical Details - -### Encryption - -The KVS module uses the Symmetric Encryption module to encrypt all values stored in the key-value store. This ensures that sensitive data is protected at rest. - -The encryption process: - -1. A master key is derived from the provided password using PBKDF2 -2. Each value is encrypted using ChaCha20Poly1305 with a unique key derived from the master key and the value's key -3. The encrypted values are stored in a JSON file on disk - -### Storage Format - -The key-value store is stored in a JSON file with the following structure: - -```json -{ - "name": "my_store", - "salt": "base64-encoded-salt", - "values": { - "key1": "base64-encoded-encrypted-value", - "key2": "base64-encoded-encrypted-value", - ... - } -} -``` - -The file is stored in the `~/.hero-vault/stores/` directory by default. - -## Security Considerations - -- Use strong passwords to protect the key-value store -- The security of the store depends on the strength of the password -- Consider the security implications of storing sensitive data on disk -- Regularly backup the store to prevent data loss - -## Error Handling - -The module uses the `CryptoError` type for handling errors that can occur during key-value store operations: - -- `EncryptionFailed` - Encryption failed -- `DecryptionFailed` - Decryption failed -- `SerializationError` - Serialization error - -## Examples - -For examples of how to use the KVS module, see the `examples/hero_vault` directory. While there may not be specific examples for the KVS module, the general pattern of usage is similar to the key space management examples. - -A basic usage example: - -```rust -// Create a new store -let mut store = KvStore::new("my_store", "secure_password")?; - -// Set some values -store.set("api_key", "secret_api_key")?; -store.set("access_token", "secret_access_token")?; - -// Save the store to disk -store.save()?; - -// Later, load the store -let loaded_store = KvStore::load("my_store", "secure_password")?; - -// Get a value -let api_key = loaded_store.get("api_key")?; -println!("API Key: {}", api_key.unwrap_or_default()); -``` - -## to test - -```bash -cargo test --lib vault::keypair -``` \ No newline at end of file diff --git a/vault/src/kvs.bak/error.rs b/vault/src/kvs.bak/error.rs deleted file mode 100644 index bbd6eaf..0000000 --- a/vault/src/kvs.bak/error.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Error types for the key-value store. - -use thiserror::Error; - -/// Errors that can occur when using the key-value store. -#[derive(Debug, Error)] -pub enum KvsError { - /// I/O error - #[error("I/O error: {0}")] - Io(#[from] std::io::Error), - - /// Key not found - #[error("Key not found: {0}")] - KeyNotFound(String), - - /// Store not found - #[error("Store not found: {0}")] - StoreNotFound(String), - - /// Serialization error - #[error("Serialization error: {0}")] - Serialization(String), - - /// Deserialization error - #[error("Deserialization error: {0}")] - Deserialization(String), - - /// Encryption error - #[error("Encryption error: {0}")] - Encryption(String), - - /// Decryption error - #[error("Decryption error: {0}")] - Decryption(String), - - /// Other error - #[error("Error: {0}")] - Other(String), -} - -impl From for KvsError { - fn from(err: serde_json::Error) -> Self { - KvsError::Serialization(err.to_string()) - } -} - -impl From for crate::vault::error::CryptoError { - fn from(err: KvsError) -> Self { - crate::vault::error::CryptoError::SerializationError(err.to_string()) - } -} - -impl From for KvsError { - fn from(err: crate::vault::error::CryptoError) -> Self { - match err { - crate::vault::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg), - crate::vault::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg), - crate::vault::error::CryptoError::SerializationError(msg) => { - KvsError::Serialization(msg) - } - _ => KvsError::Other(err.to_string()), - } - } -} - -/// Result type for key-value store operations. -pub type Result = std::result::Result; diff --git a/vault/src/kvs.bak/mod.rs b/vault/src/kvs.bak/mod.rs deleted file mode 100644 index 90e85b9..0000000 --- a/vault/src/kvs.bak/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Key-Value Store functionality -//! -//! This module provides a simple key-value store with encryption support. - -pub mod error; -pub mod store; - -// Re-export public types and functions -pub use error::KvsError; -pub use store::{ - KvStore, KvPair, - create_store, open_store, delete_store, - list_stores, get_store_path -}; - -#[cfg(test)] -mod tests; diff --git a/vault/src/kvs.bak/store.rs b/vault/src/kvs.bak/store.rs deleted file mode 100644 index 74c9c6f..0000000 --- a/vault/src/kvs.bak/store.rs +++ /dev/null @@ -1,370 +0,0 @@ -//! Implementation of a simple key-value store using the filesystem. - -use crate::vault::kvs::error::{KvsError, Result}; -use crate::vault::symmetric::implementation::{ - decrypt_symmetric, derive_key_from_password, encrypt_symmetric, -}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs; -use std::path::PathBuf; -use std::sync::{Arc, Mutex}; - -/// A key-value pair. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KvPair { - pub key: String, - pub value: String, -} - -/// A simple key-value store. -/// -/// This implementation uses the filesystem to store key-value pairs. -#[derive(Clone)] -pub struct KvStore { - /// The name of the store - name: String, - /// The path to the store file - path: PathBuf, - /// In-memory cache of the store data - data: Arc>>, - /// Whether the store is encrypted - encrypted: bool, - /// The password for encryption (if encrypted) - password: Option, -} - -/// Gets the path to the key-value store directory. -pub fn get_store_path() -> PathBuf { - let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); - home_dir.join(".hero-vault").join("kvs") -} - -/// Creates a new key-value store with the given name. -/// -/// # Arguments -/// -/// * `name` - The name of the store -/// * `encrypted` - Whether to encrypt the store -/// * `password` - The password for encryption (required if encrypted is true) -/// -/// # Returns -/// -/// A new `KvStore` instance -pub fn create_store(name: &str, encrypted: bool, password: Option<&str>) -> Result { - // Check if password is provided when encryption is enabled - if encrypted && password.is_none() { - return Err(KvsError::Other( - "Password required for encrypted store".to_string(), - )); - } - - // Create the store directory if it doesn't exist - let store_dir = get_store_path(); - if !store_dir.exists() { - fs::create_dir_all(&store_dir)?; - } - - // Create the store file path - let store_path = store_dir.join(format!("{}.json", name)); - - // Create an empty store - let store = KvStore { - name: name.to_string(), - path: store_path, - data: Arc::new(Mutex::new(HashMap::new())), - encrypted, - password: password.map(|s| s.to_string()), - }; - - // Save the empty store - store.save()?; - - Ok(store) -} - -/// Opens an existing key-value store with the given name. -/// -/// # Arguments -/// -/// * `name` - The name of the store -/// * `password` - The password for decryption (required if the store is encrypted) -/// -/// # Returns -/// -/// The opened `KvStore` instance -pub fn open_store(name: &str, password: Option<&str>) -> Result { - // Get the store file path - let store_dir = get_store_path(); - let store_path = store_dir.join(format!("{}.json", name)); - - // Check if the store exists - if !store_path.exists() { - return Err(KvsError::StoreNotFound(name.to_string())); - } - - // Read the store file - let file_content = fs::read_to_string(&store_path)?; - - // Check if the file is encrypted (simple heuristic) - let is_encrypted = !file_content.starts_with('{'); - - // If encrypted, we need a password - if is_encrypted && password.is_none() { - return Err(KvsError::Other( - "Password required for encrypted store".to_string(), - )); - } - - // Parse the store data - let data: HashMap = if is_encrypted { - // Decrypt the file content - let password = password.unwrap(); - let encrypted_data: Vec = serde_json::from_str(&file_content)?; - let key = derive_key_from_password(password); - let decrypted_data = decrypt_symmetric(&key, &encrypted_data)?; - let decrypted_str = String::from_utf8(decrypted_data) - .map_err(|e| KvsError::Deserialization(e.to_string()))?; - serde_json::from_str(&decrypted_str)? - } else { - serde_json::from_str(&file_content)? - }; - - // Create the store - let store = KvStore { - name: name.to_string(), - path: store_path, - data: Arc::new(Mutex::new(data)), - encrypted: is_encrypted, - password: password.map(|s| s.to_string()), - }; - - Ok(store) -} - -/// Deletes a key-value store with the given name. -/// -/// # Arguments -/// -/// * `name` - The name of the store to delete -/// -/// # Returns -/// -/// `Ok(())` if the operation was successful -pub fn delete_store(name: &str) -> Result<()> { - // Get the store file path - let store_dir = get_store_path(); - let store_path = store_dir.join(format!("{}.json", name)); - - // Check if the store exists - if !store_path.exists() { - return Err(KvsError::StoreNotFound(name.to_string())); - } - - // Delete the store file - fs::remove_file(store_path)?; - - Ok(()) -} - -/// Lists all available key-value stores. -/// -/// # Returns -/// -/// A vector of store names -pub fn list_stores() -> Result> { - // Get the store directory - let store_dir = get_store_path(); - if !store_dir.exists() { - return Ok(Vec::new()); - } - - // List all JSON files in the directory - let mut stores = Vec::new(); - for entry in fs::read_dir(store_dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_file() && path.extension().map_or(false, |ext| ext == "json") { - if let Some(name) = path.file_stem() { - if let Some(name_str) = name.to_str() { - stores.push(name_str.to_string()); - } - } - } - } - - Ok(stores) -} - -impl KvStore { - /// Saves the store to disk. - fn save(&self) -> Result<()> { - // Get the store data - let data = self.data.lock().unwrap(); - - // Serialize the data - let serialized = serde_json::to_string(&*data)?; - - // Write to file - if self.encrypted { - if let Some(password) = &self.password { - // Encrypt the data - let key = derive_key_from_password(password); - let encrypted_data = encrypt_symmetric(&key, serialized.as_bytes())?; - let encrypted_json = serde_json::to_string(&encrypted_data)?; - fs::write(&self.path, encrypted_json)?; - } else { - return Err(KvsError::Other( - "Password required for encrypted store".to_string(), - )); - } - } else { - fs::write(&self.path, serialized)?; - } - - Ok(()) - } - - /// Stores a value with the given key. - /// - /// # Arguments - /// - /// * `key` - The key to store the value under - /// * `value` - The value to store - /// - /// # Returns - /// - /// `Ok(())` if the operation was successful - pub fn set(&self, key: K, value: &V) -> Result<()> - where - K: ToString, - V: Serialize, - { - let key_str = key.to_string(); - let serialized = serde_json::to_string(value)?; - - // Update in-memory data - { - let mut data = self.data.lock().unwrap(); - data.insert(key_str, serialized); - } - - // Save to disk - self.save()?; - - Ok(()) - } - - /// Retrieves a value for the given key. - /// - /// # Arguments - /// - /// * `key` - The key to retrieve the value for - /// - /// # Returns - /// - /// The value if found, or `Err(KvsError::KeyNotFound)` if not found - pub fn get(&self, key: K) -> Result - where - K: ToString, - V: DeserializeOwned, - { - let key_str = key.to_string(); - let data = self.data.lock().unwrap(); - - match data.get(&key_str) { - Some(serialized) => { - let value: V = serde_json::from_str(serialized)?; - Ok(value) - } - None => Err(KvsError::KeyNotFound(key_str)), - } - } - - /// Deletes a value for the given key. - /// - /// # Arguments - /// - /// * `key` - The key to delete - /// - /// # Returns - /// - /// `Ok(())` if the operation was successful - pub fn delete(&self, key: K) -> Result<()> - where - K: ToString, - { - let key_str = key.to_string(); - - // Update in-memory data - { - let mut data = self.data.lock().unwrap(); - if data.remove(&key_str).is_none() { - return Err(KvsError::KeyNotFound(key_str)); - } - } - - // Save to disk - self.save()?; - - Ok(()) - } - - /// Checks if a key exists in the store. - /// - /// # Arguments - /// - /// * `key` - The key to check - /// - /// # Returns - /// - /// `true` if the key exists, `false` otherwise - pub fn contains(&self, key: K) -> Result - where - K: ToString, - { - let key_str = key.to_string(); - let data = self.data.lock().unwrap(); - - Ok(data.contains_key(&key_str)) - } - - /// Lists all keys in the store. - /// - /// # Returns - /// - /// A vector of keys as strings - pub fn keys(&self) -> Result> { - let data = self.data.lock().unwrap(); - - Ok(data.keys().cloned().collect()) - } - - /// Clears all key-value pairs from the store. - /// - /// # Returns - /// - /// `Ok(())` if the operation was successful - pub fn clear(&self) -> Result<()> { - // Update in-memory data - { - let mut data = self.data.lock().unwrap(); - data.clear(); - } - - // Save to disk - self.save()?; - - Ok(()) - } - - /// Gets the name of the store. - pub fn name(&self) -> &str { - &self.name - } - - /// Gets whether the store is encrypted. - pub fn is_encrypted(&self) -> bool { - self.encrypted - } -} diff --git a/vault/src/kvs.bak/tests/mod.rs b/vault/src/kvs.bak/tests/mod.rs deleted file mode 100644 index 668dbed..0000000 --- a/vault/src/kvs.bak/tests/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod store_tests; \ No newline at end of file diff --git a/vault/src/kvs.bak/tests/store_tests.rs b/vault/src/kvs.bak/tests/store_tests.rs deleted file mode 100644 index 5a972bf..0000000 --- a/vault/src/kvs.bak/tests/store_tests.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::vault::kvs::store::{create_store, delete_store, open_store, KvStore}; -use std::path::PathBuf; - -// Helper function to generate a unique store name for each test -fn generate_test_store_name() -> String { - use rand::Rng; - let random_string: String = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(10) - .map(char::from) - .collect(); - format!("test_store_{}", random_string) -} - -// Helper function to clean up test stores -fn cleanup_test_store(name: &str) { - let _ = delete_store(name); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_create_and_open_store() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - assert_eq!(store.name(), store_name); - assert!(!store.is_encrypted()); - - let opened_store = open_store(&store_name, None).expect("Failed to open store"); - assert_eq!(opened_store.name(), store_name); - assert!(!opened_store.is_encrypted()); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_set_and_get_value() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - let value: String = store.get("key1").expect("Failed to get value"); - assert_eq!(value, "value1"); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_delete_value() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - store.delete("key1").expect("Failed to delete value"); - let result: Result = store.get("key1"); - assert!(result.is_err()); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_contains_key() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - assert!(store.contains("key1").expect("Failed to check contains")); - assert!(!store.contains("key2").expect("Failed to check contains")); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_list_keys() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - store.set("key2", &"value2").expect("Failed to set value"); - - let keys = store.keys().expect("Failed to list keys"); - assert_eq!(keys.len(), 2); - assert!(keys.contains(&"key1".to_string())); - assert!(keys.contains(&"key2".to_string())); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_clear_store() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - store.set("key2", &"value2").expect("Failed to set value"); - - store.clear().expect("Failed to clear store"); - let keys = store.keys().expect("Failed to list keys after clear"); - assert!(keys.is_empty()); - - cleanup_test_store(&store_name); - } -} \ No newline at end of file diff --git a/vault/src/symmetric.bak/README.md b/vault/src/symmetric.bak/README.md deleted file mode 100644 index 18359ec..0000000 --- a/vault/src/symmetric.bak/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# Hero Vault Symmetric Encryption Module - -The Symmetric Encryption module provides functionality for symmetric encryption and decryption using the ChaCha20Poly1305 algorithm. - -## Module Structure - -The Symmetric Encryption module is organized into: - -- `implementation.rs` - Core implementation of symmetric encryption functionality -- `mod.rs` - Module exports and public interface - -## Key Features - -### Key Generation - -The module provides functionality for generating secure symmetric keys: - -```rust -// Generate a new symmetric key -let key = generate_key()?; -``` - -### Encryption - -The module provides functionality for encrypting data using ChaCha20Poly1305: - -```rust -// Encrypt data -let encrypted = encrypt(&key, "This is a secret message")?; -``` - -### Decryption - -The module provides functionality for decrypting data encrypted with ChaCha20Poly1305: - -```rust -// Decrypt data -let decrypted = decrypt(&key, &encrypted)?; -``` - -### Password-Based Key Derivation - -The module provides functionality for deriving encryption keys from passwords: - -```rust -// Derive a key from a password -let key = derive_key_from_password(password, salt)?; -``` - -## Technical Details - -### ChaCha20Poly1305 - -The module uses the ChaCha20Poly1305 authenticated encryption with associated data (AEAD) algorithm, which provides both confidentiality and integrity protection. - -ChaCha20 is a stream cipher designed by Daniel J. Bernstein, which is combined with the Poly1305 message authentication code to provide authenticated encryption. - -Key features of ChaCha20Poly1305: - -- 256-bit key -- 96-bit nonce (used once) -- Authentication tag to verify integrity -- High performance on modern processors -- Resistance to timing attacks - -### Key Derivation - -For password-based encryption, the module uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to derive encryption keys from passwords. - -Key features of PBKDF2: - -- Configurable iteration count to increase computational cost -- Salt to prevent rainbow table attacks -- Configurable output key length -- Uses HMAC-SHA256 as the underlying pseudorandom function - -## Security Considerations - -- Always use a unique key for each encryption operation -- Never reuse nonces with the same key -- Store keys securely -- Use strong passwords for password-based encryption -- Consider the security implications of storing encrypted data - -## Error Handling - -The module uses the `CryptoError` type for handling errors that can occur during symmetric encryption operations: - -- `InvalidKeyLength` - Invalid key length -- `EncryptionFailed` - Encryption failed -- `DecryptionFailed` - Decryption failed - -## Examples - -For examples of how to use the Symmetric Encryption module, see the `examples/hero_vault` directory, particularly: - -- `example.rhai` - Basic example demonstrating symmetric encryption -- `advanced_example.rhai` - Advanced example with error handling diff --git a/vault/src/symmetric.bak/implementation.rs b/vault/src/symmetric.bak/implementation.rs deleted file mode 100644 index 2fa9520..0000000 --- a/vault/src/symmetric.bak/implementation.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! Implementation of symmetric encryption functionality. - -use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce}; -use chacha20poly1305::aead::Aead; -use rand::{rngs::OsRng, RngCore}; -use serde::{Serialize, Deserialize}; -use sha2::{Sha256, Digest}; - -use crate::vault::error::CryptoError; -use crate::vault::keyspace::KeySpace; - -/// The size of the nonce in bytes. -const NONCE_SIZE: usize = 12; - -/// Generates a random 32-byte symmetric key. -/// -/// # Returns -/// -/// A 32-byte array containing the random key. -pub fn generate_symmetric_key() -> [u8; 32] { - let mut key = [0u8; 32]; - OsRng.fill_bytes(&mut key); - key -} - -/// Derives a 32-byte key from a password. -/// -/// # Arguments -/// -/// * `password` - The password to derive the key from. -/// -/// # Returns -/// -/// A 32-byte array containing the derived key. -pub fn derive_key_from_password(password: &str) -> [u8; 32] { - let mut hasher = Sha256::default(); - hasher.update(password.as_bytes()); - let result = hasher.finalize(); - - let mut key = [0u8; 32]; - key.copy_from_slice(&result); - key -} - -/// Encrypts data using ChaCha20Poly1305 with an internally generated nonce. -/// -/// The nonce is appended to the ciphertext so it can be extracted during decryption. -/// -/// # Arguments -/// -/// * `key` - The encryption key (should be 32 bytes). -/// * `message` - The message to encrypt. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the ciphertext with the nonce appended. -/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. -/// * `Err(CryptoError::EncryptionFailed)` if encryption fails. -pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, CryptoError> { - // Create cipher - let cipher = ChaCha20Poly1305::new_from_slice(key) - .map_err(|_| CryptoError::InvalidKeyLength)?; - - // Generate random nonce - let mut nonce_bytes = [0u8; NONCE_SIZE]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - - // Encrypt message - let ciphertext = cipher.encrypt(nonce, message) - .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?; - - // Append nonce to ciphertext - let mut result = ciphertext; - result.extend_from_slice(&nonce_bytes); - - Ok(result) -} - -/// Decrypts data using ChaCha20Poly1305, extracting the nonce from the ciphertext. -/// -/// # Arguments -/// -/// * `key` - The decryption key (should be 32 bytes). -/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the decrypted message. -/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. -/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short. -pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result, CryptoError> { - // Check if ciphertext is long enough to contain a nonce - if ciphertext_with_nonce.len() <= NONCE_SIZE { - return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string())); - } - - // Extract nonce from the end of ciphertext - let ciphertext_len = ciphertext_with_nonce.len() - NONCE_SIZE; - let ciphertext = &ciphertext_with_nonce[0..ciphertext_len]; - let nonce_bytes = &ciphertext_with_nonce[ciphertext_len..]; - - // Create cipher - let cipher = ChaCha20Poly1305::new_from_slice(key) - .map_err(|_| CryptoError::InvalidKeyLength)?; - - let nonce = Nonce::from_slice(nonce_bytes); - - // Decrypt message - cipher.decrypt(nonce, ciphertext) - .map_err(|e| CryptoError::DecryptionFailed(e.to_string())) -} - -/// Encrypts data using a key directly (for internal use). -/// -/// # Arguments -/// -/// * `key` - The encryption key. -/// * `message` - The message to encrypt. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the ciphertext with the nonce appended. -/// * `Err(CryptoError)` if encryption fails. -pub fn encrypt_with_key(key: &[u8], message: &[u8]) -> Result, CryptoError> { - encrypt_symmetric(key, message) -} - -/// Decrypts data using a key directly (for internal use). -/// -/// # Arguments -/// -/// * `key` - The decryption key. -/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the decrypted message. -/// * `Err(CryptoError)` if decryption fails. -pub fn decrypt_with_key(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result, CryptoError> { - decrypt_symmetric(key, ciphertext_with_nonce) -} - -/// Metadata for an encrypted key space. -#[derive(Serialize, Deserialize, Debug)] -pub struct EncryptedKeySpaceMetadata { - pub name: String, - pub created_at: u64, - pub last_accessed: u64, -} - -/// An encrypted key space with metadata. -#[derive(Serialize, Deserialize, Debug)] -pub struct EncryptedKeySpace { - pub metadata: EncryptedKeySpaceMetadata, - pub encrypted_data: Vec, -} - -/// Encrypts a key space using a password. -/// -/// # Arguments -/// -/// * `space` - The key space to encrypt. -/// * `password` - The password to encrypt with. -/// -/// # Returns -/// -/// * `Ok(EncryptedKeySpace)` containing the encrypted key space. -/// * `Err(CryptoError)` if encryption fails. -pub fn encrypt_key_space(space: &KeySpace, password: &str) -> Result { - // Serialize the key space - let serialized = match serde_json::to_vec(space) { - Ok(data) => data, - Err(e) => { - log::error!("Serialization error during encryption: {}", e); - return Err(CryptoError::SerializationError(e.to_string())); - } - }; - - // Derive key from password - let key = derive_key_from_password(password); - - // Encrypt the serialized data - let encrypted_data = encrypt_symmetric(&key, &serialized)?; - - // Create metadata - let now = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_millis() as u64; - let metadata = EncryptedKeySpaceMetadata { - name: space.name.clone(), - created_at: now, - last_accessed: now, - }; - - Ok(EncryptedKeySpace { - metadata, - encrypted_data, - }) -} - -/// Decrypts a key space using a password. -/// -/// # Arguments -/// -/// * `encrypted_space` - The encrypted key space. -/// * `password` - The password to decrypt with. -/// -/// # Returns -/// -/// * `Ok(KeySpace)` containing the decrypted key space. -/// * `Err(CryptoError)` if decryption fails. -pub fn decrypt_key_space(encrypted_space: &EncryptedKeySpace, password: &str) -> Result { - // Derive key from password - let key = derive_key_from_password(password); - - // Decrypt the data - let decrypted_data = decrypt_symmetric(&key, &encrypted_space.encrypted_data)?; - - // Deserialize the key space - let space: KeySpace = match serde_json::from_slice(&decrypted_data) { - Ok(space) => space, - Err(e) => { - log::error!("Deserialization error: {}", e); - return Err(CryptoError::SerializationError(e.to_string())); - } - }; - - Ok(space) -} - -/// Serializes an encrypted key space to a JSON string. -/// -/// # Arguments -/// -/// * `encrypted_space` - The encrypted key space to serialize. -/// -/// # Returns -/// -/// * `Ok(String)` containing the serialized encrypted key space. -/// * `Err(CryptoError)` if serialization fails. -pub fn serialize_encrypted_space(encrypted_space: &EncryptedKeySpace) -> Result { - serde_json::to_string(encrypted_space) - .map_err(|e| CryptoError::SerializationError(e.to_string())) -} - -/// Deserializes an encrypted key space from a JSON string. -/// -/// # Arguments -/// -/// * `serialized` - The serialized encrypted key space. -/// -/// # Returns -/// -/// * `Ok(EncryptedKeySpace)` containing the deserialized encrypted key space. -/// * `Err(CryptoError)` if deserialization fails. -pub fn deserialize_encrypted_space(serialized: &str) -> Result { - match serde_json::from_str(serialized) { - Ok(space) => Ok(space), - Err(e) => { - log::error!("Error deserializing encrypted space: {}", e); - Err(CryptoError::SerializationError(e.to_string())) - } - } -} diff --git a/vault/src/symmetric.bak/mod.rs b/vault/src/symmetric.bak/mod.rs deleted file mode 100644 index 1d63e3e..0000000 --- a/vault/src/symmetric.bak/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Symmetric encryption functionality -//! -//! This module provides functionality for symmetric encryption using ChaCha20Poly1305. - -pub mod implementation; - -// Re-export public types and functions -pub use implementation::{ - generate_symmetric_key, derive_key_from_password, - encrypt_symmetric, decrypt_symmetric, - encrypt_with_key, decrypt_with_key, - encrypt_key_space, decrypt_key_space, - serialize_encrypted_space, deserialize_encrypted_space, - EncryptedKeySpace, EncryptedKeySpaceMetadata -}; From d29a8fbb6740b03b0b584c1dc52bf94297a256ec Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Fri, 16 May 2025 11:24:27 +0200 Subject: [PATCH 08/11] Rename Store to Vault and move to lib root Signed-off-by: Lee Smet --- vault/src/kvs.rs | 47 ---------------------------------------------- vault/src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 48 deletions(-) delete mode 100644 vault/src/kvs.rs diff --git a/vault/src/kvs.rs b/vault/src/kvs.rs deleted file mode 100644 index 2052afa..0000000 --- a/vault/src/kvs.rs +++ /dev/null @@ -1,47 +0,0 @@ -#[cfg(not(target_arch = "wasm32"))] -use std::path::{Path, PathBuf}; - -use crate::{error::Error, key::symmetric::SymmetricKey, keyspace::KeySpace}; - -/// Store is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where -/// each [`space`](KeySpace) is itself an encrypted key-value store -pub struct Store { - #[cfg(not(target_arch = "wasm32"))] - path: PathBuf, -} - -#[cfg(not(target_arch = "wasm32"))] -impl Store { - /// Create a new store at the given path, creating the path if it does not exist yet. - pub async fn new(path: &Path) -> Result { - if path.exists() { - if !path.is_dir() { - return Err(Error::IOError(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "expected directory", - ))); - } - } else { - std::fs::create_dir_all(path)?; - } - Ok(Self { - path: path.to_path_buf(), - }) - } -} - -impl Store { - /// Open a keyspace with the given name - pub async fn open_keyspace(&self, name: &str, password: &str) -> Result { - let encryption_key = SymmetricKey::derive_from_password(password); - #[cfg(not(target_arch = "wasm32"))] - { - let path = self.path.join(name); - KeySpace::open(&path, encryption_key).await - } - #[cfg(target_arch = "wasm32")] - { - KeySpace::open(name, encryption_key).await - } - } -} diff --git a/vault/src/lib.rs b/vault/src/lib.rs index b0e8ef9..1f9e834 100644 --- a/vault/src/lib.rs +++ b/vault/src/lib.rs @@ -1,4 +1,51 @@ pub mod error; pub mod key; pub mod keyspace; -pub mod kvs; + +#[cfg(not(target_arch = "wasm32"))] +use std::path::{Path, PathBuf}; + +use crate::{error::Error, key::symmetric::SymmetricKey, keyspace::KeySpace}; + +/// Vault is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where +/// each [`space`](KeySpace) is itself an encrypted key-value store +pub struct Vault { + #[cfg(not(target_arch = "wasm32"))] + path: PathBuf, +} + +#[cfg(not(target_arch = "wasm32"))] +impl Vault { + /// Create a new store at the given path, creating the path if it does not exist yet. + pub async fn new(path: &Path) -> Result { + if path.exists() { + if !path.is_dir() { + return Err(Error::IOError(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "expected directory", + ))); + } + } else { + std::fs::create_dir_all(path)?; + } + Ok(Self { + path: path.to_path_buf(), + }) + } +} + +impl Vault { + /// Open a keyspace with the given name + pub async fn open_keyspace(&self, name: &str, password: &str) -> Result { + let encryption_key = SymmetricKey::derive_from_password(password); + #[cfg(not(target_arch = "wasm32"))] + { + let path = self.path.join(name); + KeySpace::open(&path, encryption_key).await + } + #[cfg(target_arch = "wasm32")] + { + KeySpace::open(name, encryption_key).await + } + } +} From 365814b424b70e7f9a6a27c691daf4ed0f14d428 Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Fri, 16 May 2025 14:37:10 +0200 Subject: [PATCH 09/11] Fix signature key import/export, add tests Signed-off-by: Lee Smet --- vault/src/error.rs | 9 +++++ vault/src/key/signature.rs | 80 ++++++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/vault/src/error.rs b/vault/src/error.rs index 4f4f502..60ec0fd 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -44,6 +44,8 @@ pub enum CryptoError { SignatureFailed, /// The signature does not have the expected size InvalidSignatureSize, + /// Trying to load a key which is not the expected format, + InvalidKey, } impl core::fmt::Display for CryptoError { @@ -57,6 +59,7 @@ impl core::fmt::Display for CryptoError { CryptoError::InvalidSignatureSize => { f.write_str("provided signature does not have the expected size") } + CryptoError::InvalidKey => f.write_str("the provided bytes are not a valid key"), } } } @@ -92,3 +95,9 @@ impl From for Error { Self::Coding } } + +impl From for CryptoError { + fn from(_: k256::ecdsa::Error) -> Self { + Self::InvalidKey + } +} diff --git a/vault/src/key/signature.rs b/vault/src/key/signature.rs index 029f47f..e83d364 100644 --- a/vault/src/key/signature.rs +++ b/vault/src/key/signature.rs @@ -12,6 +12,7 @@ pub struct SigningKeypair { vk: VerifyingKey, } +#[derive(Debug, PartialEq, Eq)] pub struct PublicKey(VerifyingKey); impl SigningKeypair { @@ -58,13 +59,14 @@ impl SigningKeypair { impl PublicKey { /// Import a public key from raw bytes pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() == 64 { - Ok(Self( - VerifyingKey::from_sec1_bytes(bytes).expect("Key is of valid size"), - )) - } else { - Err(CryptoError::InvalidKeySize) - } + Ok(Self(VerifyingKey::from_sec1_bytes(bytes)?)) + } + + /// Get the raw bytes of this `PublicKey`, which can be transferred to another party. + /// + /// The public key is SEC-1 encoded and compressed. + pub fn as_bytes(&self) -> Box<[u8]> { + self.0.to_encoded_point(true).to_bytes() } pub fn verify_signature(&self, message: &[u8], sig: &[u8]) -> Result<(), CryptoError> { @@ -74,3 +76,67 @@ impl PublicKey { .map_err(|_| CryptoError::SignatureFailed) } } + +#[cfg(test)] +mod tests { + + /// Generate a key, get the public key, export the bytes of said public key, import them again + /// as a public key, and verify the keys match. This make sure public keys can be exchanged. + #[test] + fn recover_public_key() { + let sk = super::SigningKeypair::new().expect("Can generate new key"); + let pk = sk.public_key(); + let pk_bytes = pk.as_bytes(); + + let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key"); + + assert_eq!(pk, pk2); + } + + /// Sign a message and validate the signature with the public key. Together with the above test + /// this makes sure a remote system can receive our public key and validate messages we sign. + #[test] + fn validate_signature() { + let sk = super::SigningKeypair::new().expect("Can generate new key"); + let pk = sk.public_key(); + + let message = b"this is an arbitrary message we want to sign"; + + let sig = sk.sign(message).expect("Message can be signed"); + + assert!(pk.verify_signature(message, &sig).is_ok()); + } + + /// Make sure a signature which is tampered with does not pass signature validation + #[test] + fn corrupt_signature_does_not_validate() { + let sk = super::SigningKeypair::new().expect("Can generate new key"); + let pk = sk.public_key(); + + let message = b"this is an arbitrary message we want to sign"; + + let mut sig = sk.sign(message).expect("Message can be signed"); + + // Tamper with the sig + sig[0] = sig[0].wrapping_add(1); + + assert!(pk.verify_signature(message, &sig).is_err()); + } + + /// Make sure a valid signature does not work for a message which has been modified + #[test] + fn tampered_message_does_not_validate() { + let sk = super::SigningKeypair::new().expect("Can generate new key"); + let pk = sk.public_key(); + + let message = b"this is an arbitrary message we want to sign"; + let mut message_clone = message.to_vec(); + + let sig = sk.sign(message).expect("Message can be signed"); + + // Modify the message + message_clone[0] = message[0].wrapping_add(1); + + assert!(pk.verify_signature(&message_clone, &sig).is_err()); + } +} From 7f55cf4fba86de1607128ca9bd96986140768e1e Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Fri, 16 May 2025 15:05:45 +0200 Subject: [PATCH 10/11] Add tests for asymmetric keys, add public key export Signed-off-by: Lee Smet --- vault/src/error.rs | 6 ++++ vault/src/key/asymmetric.rs | 71 +++++++++++++++++++++++++++++++------ vault/src/key/symmetric.rs | 2 +- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/vault/src/error.rs b/vault/src/error.rs index 60ec0fd..87ff0d4 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -101,3 +101,9 @@ impl From for CryptoError { Self::InvalidKey } } + +impl From for CryptoError { + fn from(_: k256::elliptic_curve::Error) -> Self { + Self::InvalidKey + } +} diff --git a/vault/src/key/asymmetric.rs b/vault/src/key/asymmetric.rs index b58e5ab..ea89740 100644 --- a/vault/src/key/asymmetric.rs +++ b/vault/src/key/asymmetric.rs @@ -1,10 +1,7 @@ //! An implementation of asymmetric cryptography using SECP256k1 ECDH with ChaCha20Poly1305 //! for the actual encryption. -use k256::{ - SecretKey, - ecdh::diffie_hellman, -}; +use k256::{SecretKey, ecdh::diffie_hellman, elliptic_curve::sec1::ToEncodedPoint}; use sha2::Sha256; use crate::{error::CryptoError, key::symmetric::SymmetricKey}; @@ -18,6 +15,7 @@ pub struct AsymmetricKeypair { } /// The public key part of an asymmetric keypair. +#[derive(Debug, PartialEq, Eq)] pub struct PublicKey(k256::PublicKey); impl AsymmetricKeypair { @@ -101,12 +99,63 @@ impl AsymmetricKeypair { impl PublicKey { /// Import a public key from raw bytes pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() == 64 { - Ok(Self( - k256::PublicKey::from_sec1_bytes(bytes).expect("Key is of valid size"), - )) - } else { - Err(CryptoError::InvalidKeySize) - } + Ok(Self(k256::PublicKey::from_sec1_bytes(bytes)?)) + } + + /// Get the raw bytes of this `PublicKey`, which can be transferred to another party. + /// + /// The public key is SEC-1 encoded and compressed. + pub fn as_bytes(&self) -> Box<[u8]> { + self.0.to_encoded_point(true).to_bytes() + } +} + +#[cfg(test)] +mod tests { + /// Export a public key and import it later + #[test] + fn import_public_key() { + let kp = super::AsymmetricKeypair::new().expect("Can generate new keypair"); + let pk1 = kp.public_key(); + let pk_bytes = pk1.as_bytes(); + let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key"); + + assert_eq!(pk1, pk2); + } + /// Make sure 2 random keypairs derive the same shared secret (and thus encryption key), by + /// encrypting a random message, decrypting it, and verifying it matches. + #[test] + fn encrypt_and_decrypt() { + let kp1 = super::AsymmetricKeypair::new().expect("Can generate new keypair"); + let kp2 = super::AsymmetricKeypair::new().expect("Can generate new keypair"); + + let pk1 = kp1.public_key(); + let pk2 = kp2.public_key(); + + let message = b"this is a random message to encrypt and decrypt"; + + let enc = kp1.encrypt(&pk2, message).expect("Can encrypt message"); + let dec = kp2.decrypt(&pk1, &enc).expect("Can decrypt message"); + + assert_eq!(message.as_slice(), dec.as_slice()); + } + + /// Use a different public key for decrypting than the expected one, this should fail the + /// decryption process as we use AEAD encryption with the symmetric key. + #[test] + fn decrypt_with_wrong_key() { + let kp1 = super::AsymmetricKeypair::new().expect("Can generate new keypair"); + let kp2 = super::AsymmetricKeypair::new().expect("Can generate new keypair"); + let kp3 = super::AsymmetricKeypair::new().expect("Can generate new keypair"); + + let pk2 = kp2.public_key(); + let pk3 = kp3.public_key(); + + let message = b"this is a random message to encrypt and decrypt"; + + let enc = kp1.encrypt(&pk2, message).expect("Can encrypt message"); + let dec = kp2.decrypt(&pk3, &enc); + + assert!(dec.is_err()); } } diff --git a/vault/src/key/symmetric.rs b/vault/src/key/symmetric.rs index c73a31b..1caff50 100644 --- a/vault/src/key/symmetric.rs +++ b/vault/src/key/symmetric.rs @@ -69,8 +69,8 @@ impl SymmetricKey { // Extract nonce from the end of ciphertext let ciphertext_len = ciphertext.len() - NONCE_SIZE; - let ciphertext = &ciphertext[0..ciphertext_len]; let nonce_bytes = &ciphertext[ciphertext_len..]; + let ciphertext = &ciphertext[0..ciphertext_len]; // Create cipher let cipher = ChaCha20Poly1305::new_from_slice(&self.0) From e5a4a1b634fa4de10e8d9d694c936da4d6e88bf9 Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Fri, 16 May 2025 15:19:45 +0200 Subject: [PATCH 11/11] Add tests for symmetric keys Signed-off-by: Lee Smet --- vault/src/key/symmetric.rs | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/vault/src/key/symmetric.rs b/vault/src/key/symmetric.rs index 1caff50..00aaa96 100644 --- a/vault/src/key/symmetric.rs +++ b/vault/src/key/symmetric.rs @@ -8,6 +8,7 @@ use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce, aead::Aead}; use crate::error::CryptoError; +#[derive(Debug, PartialEq, Eq)] pub struct SymmetricKey([u8; 32]); /// Size of a nonce in ChaCha20Poly1305. @@ -102,3 +103,49 @@ impl SymmetricKey { Self(key) } } + +#[cfg(test)] +mod tests { + + /// Using the same password derives the same key + #[test] + fn same_password_derives_same_key() { + const EXPECTED_KEY: [u8; 32] = [ + 4, 179, 233, 202, 225, 70, 211, 200, 7, 73, 115, 1, 85, 149, 90, 42, 160, 68, 16, 106, + 136, 19, 197, 195, 153, 145, 179, 21, 37, 13, 37, 90, + ]; + const PASSWORD: &str = "test123"; + + let key = super::SymmetricKey::derive_from_password(PASSWORD); + + assert_eq!(key.0, EXPECTED_KEY); + } + + /// Make sure an encrypted value with some key can be decrypted with the same key + #[test] + fn can_decrypt() { + let key = super::SymmetricKey::new(); + + let message = b"this is a message to decrypt"; + + let enc = key.encrypt(message).expect("Can encrypt message"); + let dec = key.decrypt(&enc).expect("Can decrypt message"); + + assert_eq!(message.as_slice(), dec.as_slice()); + } + + /// Make sure a value encrypted with one key can't be decrypted with a different key. Since we + /// use AEAD encryption we will notice this when trying to decrypt + #[test] + fn different_key_cant_decrypt() { + let key1 = super::SymmetricKey::new(); + let key2 = super::SymmetricKey::new(); + + let message = b"this is a message to decrypt"; + + let enc = key1.encrypt(message).expect("Can encrypt message"); + let dec = key2.decrypt(&enc); + + assert!(dec.is_err()); + } +}