From 98ab2e1536626b39a2595026fa7a9688cd64a403 Mon Sep 17 00:00:00 2001 From: Sameh Abouelsaad Date: Thu, 8 May 2025 17:39:39 +0300 Subject: [PATCH] feat: introduce hero_vault --- Cargo.toml | 9 + examples/hero_vault/README.md | 49 ++ examples/hero_vault/advanced_example.rhai | 233 +++++++++ examples/hero_vault/example.rhai | 85 ++++ .../hero_vault/key_persistence_example.rhai | 65 +++ examples/hero_vault/load_existing_space.rhai | 65 +++ src/crypto/error.rs | 50 ++ src/crypto/ethereum/implementation.rs | 228 +++++++++ src/crypto/ethereum/mod.rs | 12 + src/crypto/keypair/implementation.rs | 467 ++++++++++++++++++ src/crypto/keypair/mod.rs | 14 + src/crypto/kvs/error.rs | 66 +++ src/crypto/kvs/mod.rs | 14 + src/crypto/kvs/store.rs | 362 ++++++++++++++ src/crypto/mod.rs | 17 + src/crypto/symmetric/implementation.rs | 266 ++++++++++ src/crypto/symmetric/mod.rs | 15 + src/lib.rs | 2 + src/rhai/crypto.rs | 416 ++++++++++++++++ src/rhai/mod.rs | 7 + 20 files changed, 2442 insertions(+) create mode 100644 examples/hero_vault/README.md create mode 100644 examples/hero_vault/advanced_example.rhai create mode 100644 examples/hero_vault/example.rhai create mode 100644 examples/hero_vault/key_persistence_example.rhai create mode 100644 examples/hero_vault/load_existing_space.rhai create mode 100644 src/crypto/error.rs create mode 100644 src/crypto/ethereum/implementation.rs create mode 100644 src/crypto/ethereum/mod.rs create mode 100644 src/crypto/keypair/implementation.rs create mode 100644 src/crypto/keypair/mod.rs create mode 100644 src/crypto/kvs/error.rs create mode 100644 src/crypto/kvs/mod.rs create mode 100644 src/crypto/kvs/store.rs create mode 100644 src/crypto/mod.rs create mode 100644 src/crypto/symmetric/implementation.rs create mode 100644 src/crypto/symmetric/mod.rs create mode 100644 src/rhai/crypto.rs diff --git a/Cargo.toml b/Cargo.toml index d607ded..0d48d90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,15 @@ rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language rand = "0.8.5" # Random number generation clap = "2.33" # Command-line argument parsing +# Crypto dependencies +base64 = "0.21.0" # Base64 encoding/decoding +k256 = { version = "0.13.1", features = ["ecdsa"] } # Elliptic curve cryptography +once_cell = "1.18.0" # Lazy static initialization +sha2 = "0.10.7" # SHA-2 hash functions +chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher +ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library +dirs = "5.0.1" # Directory paths + # Optional features for specific OS functionality [target.'cfg(unix)'.dependencies] nix = "0.26" # Unix-specific functionality diff --git a/examples/hero_vault/README.md b/examples/hero_vault/README.md new file mode 100644 index 0000000..140b858 --- /dev/null +++ b/examples/hero_vault/README.md @@ -0,0 +1,49 @@ +# Hero Vault Cryptography Examples + +This directory contains examples demonstrating the Hero Vault cryptography functionality integrated into the SAL project. + +## Overview + +Hero Vault provides cryptographic operations including: + +- Key space management (creation, loading, encryption, decryption) +- Keypair management (creation, selection, listing) +- Digital signatures (signing and verification) +- Symmetric encryption (key generation, encryption, decryption) +- Ethereum wallet functionality + +## Directory Structure + +- `rhai/` - Rhai script examples demonstrating Hero Vault functionality + - `example.rhai` - Basic example demonstrating key management, signing, and encryption + - `advanced_example.rhai` - Advanced example with error handling and more complex operations + - `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 and use its keypairs + - `README.md` - Documentation for the Rhai scripting API + +## Running the Examples + +You can run the examples using the `herodo` tool that comes with the SAL project: + +```bash +# Run a single example +herodo --path rhai/example.rhai + +# Run all examples using the provided script +./run_examples.sh +``` + +## Key Space Storage + +Key spaces are stored in the `~/.hero-vault/key-spaces/` directory by default. Each key space is stored in a separate JSON file named after the key space (e.g., `my_space.json`). + +## Security + +Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password. The encryption ensures that the key material is secure at rest. + +## Best Practices + +1. **Use Strong Passwords**: Since the security of your key spaces depends on the strength of your passwords, use strong, unique passwords. +2. **Backup Key Spaces**: Regularly backup your key spaces directory to prevent data loss. +3. **Script Organization**: Split your scripts into logical units, with separate scripts for key creation and key usage. +4. **Error Handling**: Always check the return values of functions to ensure operations succeeded before proceeding. \ No newline at end of file diff --git a/examples/hero_vault/advanced_example.rhai b/examples/hero_vault/advanced_example.rhai new file mode 100644 index 0000000..ea90498 --- /dev/null +++ b/examples/hero_vault/advanced_example.rhai @@ -0,0 +1,233 @@ +// Advanced Rhai script example for Hero Vault Cryptography Module +// This script demonstrates conditional logic, error handling, and more complex operations + +// Function to create a key space with error handling +fn setup_key_space(name, password) { + print("Attempting: Create key space: " + name); + let result = create_key_space(name, password); + + if result { + print("✅ Create key space succeeded!"); + return true; + } else { + print("❌ Create key space failed!"); + } + + return false; +} + +// Function to create and select a keypair +fn setup_keypair(name, password) { + print("Attempting: Create keypair: " + name); + let result = create_keypair(name, password); + + if result { + print("✅ Create keypair succeeded!"); + + print("Attempting: Select keypair: " + name); + let selected = select_keypair(name); + + if selected { + print("✅ Select keypair succeeded!"); + return true; + } else { + print("❌ Select keypair failed!"); + } + } else { + print("❌ Create keypair failed!"); + } + + return false; +} + +// Function to sign multiple messages +fn sign_messages(messages) { + let signatures = []; + + for message in messages { + print("Signing message: " + message); + print("Attempting: Sign message"); + let signature = sign(message); + + if signature != "" { + print("✅ Sign message succeeded!"); + signatures.push(#{ + message: message, + signature: signature + }); + } else { + print("❌ Sign message failed!"); + } + } + + return signatures; +} + +// Function to verify signatures +fn verify_signatures(signed_messages) { + let results = []; + + for item in signed_messages { + let message = item.message; + let signature = item.signature; + + print("Verifying signature for: " + message); + print("Attempting: Verify signature"); + let is_valid = verify(message, signature); + + if is_valid { + print("✅ Verify signature succeeded!"); + } else { + print("❌ Verify signature failed!"); + } + + results.push(#{ + message: message, + valid: is_valid + }); + } + + return results; +} + +// Function to encrypt multiple messages +fn encrypt_messages(messages) { + // Generate a symmetric key + print("Attempting: Generate symmetric key"); + let key = generate_key(); + + if key == "" { + print("❌ Generate symmetric key failed!"); + return []; + } + + print("✅ Generate symmetric key succeeded!"); + print("Using key: " + key); + let encrypted_messages = []; + + for message in messages { + print("Encrypting message: " + message); + print("Attempting: Encrypt message"); + let encrypted = encrypt(key, message); + + if encrypted != "" { + print("✅ Encrypt message succeeded!"); + encrypted_messages.push(#{ + original: message, + encrypted: encrypted, + key: key + }); + } else { + print("❌ Encrypt message failed!"); + } + } + + return encrypted_messages; +} + +// Function to decrypt messages +fn decrypt_messages(encrypted_messages) { + let decrypted_messages = []; + + for item in encrypted_messages { + let encrypted = item.encrypted; + let key = item.key; + let original = item.original; + + print("Decrypting message..."); + print("Attempting: Decrypt message"); + let decrypted = decrypt(key, encrypted); + + if decrypted != false { + let success = decrypted == original; + + decrypted_messages.push(#{ + decrypted: decrypted, + original: original, + success: success + }); + + if success { + print("Decryption matched original ✅"); + } else { + print("Decryption did not match original ❌"); + } + } + } + + return decrypted_messages; +} + +// Main script execution +print("=== Advanced Cryptography Script ==="); + +// Set up key space +let space_name = "advanced_space"; +let password = "secure_password123"; + +if setup_key_space(space_name, password) { + print("\n--- Key space setup complete ---\n"); + + // Set up keypair + if setup_keypair("advanced_keypair", password) { + print("\n--- Keypair setup complete ---\n"); + + // Define messages to sign + let messages = [ + "This is the first message to sign", + "Here's another message that needs signing", + "And a third message for good measure" + ]; + + // Sign messages + print("\n--- Signing Messages ---\n"); + let signed_messages = sign_messages(messages); + + // Verify signatures + print("\n--- Verifying Signatures ---\n"); + let verification_results = verify_signatures(signed_messages); + + // Count successful verifications + let successful_verifications = verification_results.filter(|r| r.valid).len(); + print("Successfully verified " + successful_verifications + " out of " + verification_results.len() + " signatures"); + + // Encrypt messages + print("\n--- Encrypting Messages ---\n"); + let encrypted_messages = encrypt_messages(messages); + + // Decrypt messages + print("\n--- Decrypting Messages ---\n"); + let decryption_results = decrypt_messages(encrypted_messages); + + // Count successful decryptions + let successful_decryptions = decryption_results.filter(|r| r.success).len(); + print("Successfully decrypted " + successful_decryptions + " out of " + decryption_results.len() + " messages"); + + // Create Ethereum wallet + print("\n--- Creating Ethereum Wallet ---\n"); + print("Attempting: Create Ethereum wallet"); + let wallet_created = create_ethereum_wallet(); + + if wallet_created { + print("✅ Create Ethereum wallet succeeded!"); + + print("Attempting: Get Ethereum address"); + let address = get_ethereum_address(); + + if address != "" { + print("✅ Get Ethereum address succeeded!"); + print("Ethereum wallet address: " + address); + } else { + print("❌ Get Ethereum address failed!"); + } + } else { + print("❌ Create Ethereum wallet failed!"); + } + + print("\n=== Script execution completed successfully! ==="); + } else { + print("Failed to set up keypair. Aborting script."); + } +} else { + print("Failed to set up key space. Aborting script."); +} \ No newline at end of file diff --git a/examples/hero_vault/example.rhai b/examples/hero_vault/example.rhai new file mode 100644 index 0000000..179546e --- /dev/null +++ b/examples/hero_vault/example.rhai @@ -0,0 +1,85 @@ +// Example Rhai script for Hero Vault Cryptography Module +// This script demonstrates key management, signing, and encryption + +// Step 1: Create and manage a key space +let space_name = "demo_space"; +let password = "secure_password123"; + +print("Creating key space: " + space_name); +if create_key_space(space_name, password) { + print("✓ Key space created successfully"); + + // Step 2: Create and use keypairs + print("\nCreating keypairs..."); + if create_keypair("signing_key", password) { + print("✓ Created signing keypair"); + } + + if create_keypair("encryption_key", password) { + print("✓ Created encryption keypair"); + } + + // List all keypairs + let keypairs = list_keypairs(); + print("Available keypairs: " + keypairs); + + // Step 3: Sign a message + print("\nPerforming signing operations..."); + if select_keypair("signing_key") { + print("✓ Selected signing keypair"); + + let message = "This is a secure message that needs to be signed"; + print("Message: " + message); + + let signature = sign(message); + print("Signature: " + signature); + + // Verify the signature + let is_valid = verify(message, signature); + if is_valid { + print("Signature verification: ✓ Valid"); + } else { + print("Signature verification: ✗ Invalid"); + } + } + + // Step 4: Encrypt and decrypt data + print("\nPerforming encryption operations..."); + + // Generate a symmetric key + let sym_key = generate_key(); + print("Generated symmetric key: " + sym_key); + + // Encrypt a message + let secret = "This is a top secret message that must be encrypted"; + print("Original message: " + secret); + + let encrypted_data = encrypt(sym_key, secret); + print("Encrypted data: " + encrypted_data); + + // Decrypt the message + let decrypted_data = decrypt(sym_key, encrypted_data); + print("Decrypted message: " + decrypted_data); + + // Verify decryption was successful + if decrypted_data == secret { + print("✓ Encryption/decryption successful"); + } else { + print("✗ Encryption/decryption failed"); + } + + // Step 5: Create an Ethereum wallet + print("\nCreating Ethereum wallet..."); + if select_keypair("encryption_key") { + print("✓ Selected keypair for Ethereum wallet"); + + if create_ethereum_wallet() { + print("✓ Ethereum wallet created"); + + let address = get_ethereum_address(); + print("Ethereum address: " + address); + } + } + + print("\nScript execution completed successfully!"); +} \ No newline at end of file diff --git a/examples/hero_vault/key_persistence_example.rhai b/examples/hero_vault/key_persistence_example.rhai new file mode 100644 index 0000000..7b77d85 --- /dev/null +++ b/examples/hero_vault/key_persistence_example.rhai @@ -0,0 +1,65 @@ +// Example Rhai script demonstrating key space persistence for Hero Vault +// This script shows how to create, save, and load key spaces + +// Step 1: Create a key space +let space_name = "persistent_space"; +let password = "secure_password123"; + +print("Creating key space: " + space_name); +if create_key_space(space_name, password) { + print("✓ Key space created successfully"); + + // Step 2: Create keypairs in this space + print("\nCreating keypairs..."); + if create_keypair("persistent_key1", password) { + print("✓ Created first keypair"); + } + + if create_keypair("persistent_key2", password) { + print("✓ Created second keypair"); + } + + // List all keypairs + let keypairs = list_keypairs(); + print("Available keypairs: " + keypairs); + + // Step 3: Clear the session (simulate closing and reopening the CLI) + print("\nClearing session (simulating restart)..."); + // Note: In a real script, you would exit here and run a new script + // For demonstration purposes, we'll continue in the same script + + // Step 4: Load the key space from disk + print("\nLoading key space from disk..."); + if load_key_space(space_name, password) { + print("✓ Key space loaded successfully"); + + // Verify the keypairs are still available + let loaded_keypairs = list_keypairs(); + print("Keypairs after loading: " + loaded_keypairs); + + // Step 5: Use a keypair from the loaded space + print("\nSelecting and using a keypair..."); + if select_keypair("persistent_key1") { + print("✓ Selected keypair"); + + let message = "This message was signed using a keypair from a loaded key space"; + let signature = sign(message); + print("Message: " + message); + print("Signature: " + signature); + + // Verify the signature + let is_valid = verify(message, signature); + if is_valid { + print("Signature verification: ✓ Valid"); + } else { + print("Signature verification: ✗ Invalid"); + } + } + } else { + print("✗ Failed to load key space"); + } +} else { + print("✗ Failed to create key space"); +} + +print("\nScript execution completed!"); \ No newline at end of file diff --git a/examples/hero_vault/load_existing_space.rhai b/examples/hero_vault/load_existing_space.rhai new file mode 100644 index 0000000..52c1fac --- /dev/null +++ b/examples/hero_vault/load_existing_space.rhai @@ -0,0 +1,65 @@ +// Example Rhai script demonstrating loading an existing key space for Hero Vault +// This script shows how to load a previously created key space and use its keypairs + +// Define the key space name and password +let space_name = "persistent_space"; +let password = "secure_password123"; + +print("Loading existing key space: " + space_name); + +// Load the key space from disk +if load_key_space(space_name, password) { + print("✓ Key space loaded successfully"); + + // List available keypairs + let keypairs = list_keypairs(); + print("Available keypairs: " + keypairs); + + // Use both keypairs to sign different messages + if select_keypair("persistent_key1") { + print("\nUsing persistent_key1:"); + let message1 = "Message signed with the first keypair"; + let signature1 = sign(message1); + print("Message: " + message1); + print("Signature: " + signature1); + + let is_valid1 = verify(message1, signature1); + if is_valid1 { + print("Verification: ✓ Valid"); + } else { + print("Verification: ✗ Invalid"); + } + } + + if select_keypair("persistent_key2") { + print("\nUsing persistent_key2:"); + let message2 = "Message signed with the second keypair"; + let signature2 = sign(message2); + print("Message: " + message2); + print("Signature: " + signature2); + + let is_valid2 = verify(message2, signature2); + if is_valid2 { + print("Verification: ✓ Valid"); + } else { + print("Verification: ✗ Invalid"); + } + } + + // Create an Ethereum wallet using one of the keypairs + print("\nCreating Ethereum wallet from persistent keypair:"); + if select_keypair("persistent_key1") { + if create_ethereum_wallet() { + print("✓ Ethereum wallet created"); + + let address = get_ethereum_address(); + print("Ethereum address: " + address); + } else { + print("✗ Failed to create Ethereum wallet"); + } + } +} else { + print("✗ Failed to load key space. Make sure you've run key_persistence_example.rhai first."); +} + +print("\nScript execution completed!"); \ No newline at end of file diff --git a/src/crypto/error.rs b/src/crypto/error.rs new file mode 100644 index 0000000..edab358 --- /dev/null +++ b/src/crypto/error.rs @@ -0,0 +1,50 @@ +//! Error types for cryptographic operations + +use thiserror::Error; + +/// Errors that can occur during cryptographic operations +#[derive(Error, Debug)] +pub enum CryptoError { + /// Invalid key length + #[error("Invalid key length")] + InvalidKeyLength, + + /// Encryption failed + #[error("Encryption failed: {0}")] + EncryptionFailed(String), + + /// Decryption failed + #[error("Decryption failed: {0}")] + DecryptionFailed(String), + + /// Signature format error + #[error("Signature format error: {0}")] + SignatureFormatError(String), + + /// Keypair already exists + #[error("Keypair already exists: {0}")] + KeypairAlreadyExists(String), + + /// Keypair not found + #[error("Keypair not found: {0}")] + KeypairNotFound(String), + + /// No active key space + #[error("No active key space")] + NoActiveSpace, + + /// No keypair selected + #[error("No keypair selected")] + NoKeypairSelected, + + /// Serialization error + #[error("Serialization error: {0}")] + SerializationError(String), +} + +/// Convert CryptoError to SAL's Error type +impl From for crate::Error { + fn from(err: CryptoError) -> Self { + crate::Error::Sal(err.to_string()) + } +} \ No newline at end of file diff --git a/src/crypto/ethereum/implementation.rs b/src/crypto/ethereum/implementation.rs new file mode 100644 index 0000000..59ec7ff --- /dev/null +++ b/src/crypto/ethereum/implementation.rs @@ -0,0 +1,228 @@ +//! Implementation of Ethereum functionality. + +use ethers::prelude::*; +use ethers::signers::{LocalWallet, Signer, Wallet}; +use ethers::utils::hex; +use k256::ecdsa::SigningKey; +use std::str::FromStr; +use std::sync::Mutex; +use once_cell::sync::Lazy; +use sha2::{Sha256, Digest}; + +use crate::crypto::error::CryptoError; +use crate::crypto::keypair::KeyPair; + +// Gnosis Chain configuration +pub const GNOSIS_CHAIN_ID: u64 = 100; +pub const GNOSIS_RPC_URL: &str = "https://rpc.gnosis.gateway.fm"; +pub const GNOSIS_EXPLORER: &str = "https://gnosisscan.io"; + +/// An Ethereum wallet derived from a keypair. +#[derive(Debug, Clone)] +pub struct EthereumWallet { + pub address: Address, + pub wallet: Wallet, +} + +impl EthereumWallet { + /// Creates a new Ethereum wallet from a keypair. + pub fn from_keypair(keypair: &KeyPair) -> 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(GNOSIS_CHAIN_ID); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + }) + } + + /// Creates a new Ethereum wallet from a name and keypair (deterministic derivation). + pub fn from_name_and_keypair(name: &str, keypair: &KeyPair) -> 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(GNOSIS_CHAIN_ID); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + }) + } + + /// Creates a new Ethereum wallet from a private key. + pub fn from_private_key(private_key: &str) -> 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(GNOSIS_CHAIN_ID); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + }) + } + + /// 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) + } +} + +/// Global storage for Ethereum wallets. +static ETH_WALLETS: Lazy>> = Lazy::new(|| { + Mutex::new(Vec::new()) +}); + +/// Creates an Ethereum wallet from the currently selected keypair. +pub fn create_ethereum_wallet() -> Result { + // Get the currently selected keypair + let keypair = crate::crypto::keypair::get_selected_keypair()?; + + // Create an Ethereum wallet from the keypair + let wallet = EthereumWallet::from_keypair(&keypair)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + wallets.push(wallet.clone()); + + Ok(wallet) +} + +/// Gets the current Ethereum wallet. +pub fn get_current_ethereum_wallet() -> Result { + let wallets = ETH_WALLETS.lock().unwrap(); + + if wallets.is_empty() { + return Err(CryptoError::NoKeypairSelected); + } + + Ok(wallets.last().unwrap().clone()) +} + +/// Clears all Ethereum wallets. +pub fn clear_ethereum_wallets() { + let mut wallets = ETH_WALLETS.lock().unwrap(); + wallets.clear(); +} + +/// Formats an Ethereum balance for display. +pub fn format_eth_balance(balance: U256) -> String { + let wei = balance.as_u128(); + let eth = wei as f64 / 1_000_000_000_000_000_000.0; + format!("{:.6} ETH", eth) +} + +/// 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()) +} + +/// Creates a provider for the Gnosis Chain. +pub fn create_gnosis_provider() -> Result, CryptoError> { + Provider::::try_from(GNOSIS_RPC_URL) + .map_err(|e| CryptoError::SerializationError(format!("Failed to create Gnosis provider: {}", e))) +} + +/// Creates an Ethereum wallet from a name and the currently selected keypair. +pub fn create_ethereum_wallet_from_name(name: &str) -> Result { + // Get the currently selected keypair + let keypair = crate::crypto::keypair::get_selected_keypair()?; + + // Create an Ethereum wallet from the name and keypair + let wallet = EthereumWallet::from_name_and_keypair(name, &keypair)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + wallets.push(wallet.clone()); + + Ok(wallet) +} + +/// Creates an Ethereum wallet from a private key. +pub fn create_ethereum_wallet_from_private_key(private_key: &str) -> Result { + // Create an Ethereum wallet from the private key + let wallet = EthereumWallet::from_private_key(private_key)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + wallets.push(wallet.clone()); + + Ok(wallet) +} \ No newline at end of file diff --git a/src/crypto/ethereum/mod.rs b/src/crypto/ethereum/mod.rs new file mode 100644 index 0000000..4764b38 --- /dev/null +++ b/src/crypto/ethereum/mod.rs @@ -0,0 +1,12 @@ +//! Ethereum wallet functionality +//! +//! This module provides functionality for creating and managing Ethereum wallets. + +mod implementation; + +// Re-export public types and functions +pub use implementation::{ + EthereumWallet, + create_ethereum_wallet, + get_current_ethereum_wallet +}; \ No newline at end of file diff --git a/src/crypto/keypair/implementation.rs b/src/crypto/keypair/implementation.rs new file mode 100644 index 0000000..591c87b --- /dev/null +++ b/src/crypto/keypair/implementation.rs @@ -0,0 +1,467 @@ +//! Implementation of keypair functionality. + +use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature}; +use rand::rngs::OsRng; +use serde::{Serialize, Deserialize}; +use std::collections::HashMap; +use once_cell::sync::Lazy; +use std::sync::Mutex; +use sha2::{Sha256, Digest}; + +use crate::crypto::error::CryptoError; + +/// 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::{Serializer, Deserializer}; + use serde::de::{self, Visitor}; + 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::{Serializer, Deserializer}; + use serde::de::{self, Visitor}; + 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 (this is a simplified ECDH) + // In a real implementation, we would use proper ECDH, but for this example: + let shared_point = recipient_key.to_encoded_point(false); + let shared_secret = { + let mut hasher = Sha256::default(); + hasher.update(ephemeral_signing_key.to_bytes()); + hasher.update(shared_point.as_bytes()); + hasher.finalize().to_vec() + }; + + // Encrypt the message using the derived key + let ciphertext = crate::crypto::symmetric::encrypt_with_key(&shared_secret, message) + .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?; + + // Format: ephemeral_public_key || ciphertext + let mut result = ephemeral_public_key.to_sec1_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 (simplified ECDH) + let shared_point = sender_key.to_encoded_point(false); + let shared_secret = { + let mut hasher = Sha256::default(); + hasher.update(self.signing_key.to_bytes()); + hasher.update(shared_point.as_bytes()); + hasher.finalize().to_vec() + }; + + // Decrypt the message using the derived key + crate::crypto::symmetric::decrypt_with_key(&shared_secret, 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() + } +} + +/// 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. +static SESSION: Lazy> = Lazy::new(|| { + Mutex::new(Session::default()) +}); + +/// 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) +} \ No newline at end of file diff --git a/src/crypto/keypair/mod.rs b/src/crypto/keypair/mod.rs new file mode 100644 index 0000000..fde0f35 --- /dev/null +++ b/src/crypto/keypair/mod.rs @@ -0,0 +1,14 @@ +//! Key pair management functionality +//! +//! This module provides functionality for creating and managing ECDSA key pairs. + +mod implementation; + +// Re-export public types and functions +pub use implementation::{ + KeyPair, KeySpace, + 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 +}; \ No newline at end of file diff --git a/src/crypto/kvs/error.rs b/src/crypto/kvs/error.rs new file mode 100644 index 0000000..9b0527d --- /dev/null +++ b/src/crypto/kvs/error.rs @@ -0,0 +1,66 @@ +//! Error types for the key-value store. + +use std::fmt; +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::crypto::error::CryptoError { + fn from(err: KvsError) -> Self { + crate::crypto::error::CryptoError::SerializationError(err.to_string()) + } +} + +impl From for KvsError { + fn from(err: crate::crypto::error::CryptoError) -> Self { + match err { + crate::crypto::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg), + crate::crypto::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg), + crate::crypto::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; \ No newline at end of file diff --git a/src/crypto/kvs/mod.rs b/src/crypto/kvs/mod.rs new file mode 100644 index 0000000..9171032 --- /dev/null +++ b/src/crypto/kvs/mod.rs @@ -0,0 +1,14 @@ +//! Key-Value Store functionality +//! +//! This module provides a simple key-value store with encryption support. + +mod error; +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 +}; \ No newline at end of file diff --git a/src/crypto/kvs/store.rs b/src/crypto/kvs/store.rs new file mode 100644 index 0000000..be70caa --- /dev/null +++ b/src/crypto/kvs/store.rs @@ -0,0 +1,362 @@ +//! Implementation of a simple key-value store using the filesystem. + +use crate::crypto::kvs::error::{KvsError, Result}; +use crate::crypto::symmetric; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::path::{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 = symmetric::derive_key_from_password(password); + let decrypted_data = symmetric::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 = symmetric::derive_key_from_password(password); + let encrypted_data = symmetric::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 = 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/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..43cb5bf --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,17 @@ +//! Cryptographic functionality for SAL +//! +//! This module provides cryptographic operations including: +//! - Key pair management (ECDSA) +//! - Symmetric encryption (ChaCha20Poly1305) +//! - Ethereum wallet functionality +//! - Key-value store with encryption + +pub mod error; +pub mod keypair; +pub mod symmetric; +pub mod ethereum; +pub mod kvs; + +// Re-export common types for convenience +pub use error::CryptoError; +pub use keypair::{KeyPair, KeySpace}; \ No newline at end of file diff --git a/src/crypto/symmetric/implementation.rs b/src/crypto/symmetric/implementation.rs new file mode 100644 index 0000000..65eff19 --- /dev/null +++ b/src/crypto/symmetric/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::crypto::error::CryptoError; +use crate::crypto::keypair::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())) + } + } +} \ No newline at end of file diff --git a/src/crypto/symmetric/mod.rs b/src/crypto/symmetric/mod.rs new file mode 100644 index 0000000..aa2c313 --- /dev/null +++ b/src/crypto/symmetric/mod.rs @@ -0,0 +1,15 @@ +//! Symmetric encryption functionality +//! +//! This module provides functionality for symmetric encryption using ChaCha20Poly1305. + +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 +}; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ceda467..0152f94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ //! - System information //! - Network operations //! - Environment variables +//! - Cryptographic operations use std::io; use thiserror::Error; @@ -44,6 +45,7 @@ pub mod text; pub mod virt; pub mod rhai; pub mod cmd; +pub mod crypto; // Version information /// Returns the version of the SAL library diff --git a/src/rhai/crypto.rs b/src/rhai/crypto.rs new file mode 100644 index 0000000..30e13ae --- /dev/null +++ b/src/rhai/crypto.rs @@ -0,0 +1,416 @@ +//! Rhai bindings for SAL crypto functionality + +use rhai::{Engine, Dynamic, FnPtr, EvalAltResult}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; +use std::fs; +use std::path::PathBuf; + +use crate::crypto::{keypair, symmetric, ethereum}; +use crate::crypto::error::CryptoError; +use crate::crypto::kvs; + +// Key space management functions +fn load_key_space(name: &str, password: &str) -> bool { + // Get the key spaces directory from config + let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); + + // Check if directory exists + if !key_spaces_dir.exists() { + log::error!("Key spaces directory does not exist"); + return false; + } + + // Get the key space file path + let space_path = key_spaces_dir.join(format!("{}.json", name)); + + // Check if file exists + if !space_path.exists() { + log::error!("Key space file not found: {}", space_path.display()); + return false; + } + + // Read the file + let serialized = match fs::read_to_string(&space_path) { + Ok(data) => data, + Err(e) => { + log::error!("Error reading key space file: {}", e); + return false; + } + }; + + // Deserialize the encrypted space + let encrypted_space = match symmetric::deserialize_encrypted_space(&serialized) { + Ok(space) => space, + Err(e) => { + log::error!("Error deserializing key space: {}", e); + return false; + } + }; + + // Decrypt the space + let space = match symmetric::decrypt_key_space(&encrypted_space, password) { + Ok(space) => space, + Err(e) => { + log::error!("Error decrypting key space: {}", e); + return false; + } + }; + + // Set as current space + match keypair::set_current_space(space) { + Ok(_) => true, + Err(e) => { + log::error!("Error setting current space: {}", e); + false + } + } +} + +fn create_key_space(name: &str, password: &str) -> bool { + match keypair::create_space(name) { + Ok(_) => { + // Get the current space + match keypair::get_current_space() { + Ok(space) => { + // Encrypt the key space + let encrypted_space = match symmetric::encrypt_key_space(&space, password) { + Ok(encrypted) => encrypted, + Err(e) => { + log::error!("Error encrypting key space: {}", e); + return false; + } + }; + + // Serialize the encrypted space + let serialized = match symmetric::serialize_encrypted_space(&encrypted_space) { + Ok(json) => json, + Err(e) => { + log::error!("Error serializing encrypted space: {}", e); + return false; + } + }; + + // Get the key spaces directory + let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); + + // Create directory if it doesn't exist + if !key_spaces_dir.exists() { + match fs::create_dir_all(&key_spaces_dir) { + Ok(_) => {}, + Err(e) => { + log::error!("Error creating key spaces directory: {}", e); + return false; + } + } + } + + // Write to file + let space_path = key_spaces_dir.join(format!("{}.json", name)); + match fs::write(&space_path, serialized) { + Ok(_) => { + log::info!("Key space created and saved to {}", space_path.display()); + true + }, + Err(e) => { + log::error!("Error writing key space file: {}", e); + false + } + } + }, + Err(e) => { + log::error!("Error getting current space: {}", e); + false + } + } + }, + Err(e) => { + log::error!("Error creating key space: {}", e); + false + } + } +} + +// Auto-save function for internal use +fn auto_save_key_space(password: &str) -> bool { + match keypair::get_current_space() { + Ok(space) => { + // Encrypt the key space + let encrypted_space = match symmetric::encrypt_key_space(&space, password) { + Ok(encrypted) => encrypted, + Err(e) => { + log::error!("Error encrypting key space: {}", e); + return false; + } + }; + + // Serialize the encrypted space + let serialized = match symmetric::serialize_encrypted_space(&encrypted_space) { + Ok(json) => json, + Err(e) => { + log::error!("Error serializing encrypted space: {}", e); + return false; + } + }; + + // Get the key spaces directory + let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); + + // Create directory if it doesn't exist + if !key_spaces_dir.exists() { + match fs::create_dir_all(&key_spaces_dir) { + Ok(_) => {}, + Err(e) => { + log::error!("Error creating key spaces directory: {}", e); + return false; + } + } + } + + // Write to file + let space_path = key_spaces_dir.join(format!("{}.json", space.name)); + match fs::write(&space_path, serialized) { + Ok(_) => { + log::info!("Key space saved to {}", space_path.display()); + true + }, + Err(e) => { + log::error!("Error writing key space file: {}", e); + false + } + } + }, + Err(e) => { + log::error!("Error getting current space: {}", e); + false + } + } +} + +fn encrypt_key_space(password: &str) -> String { + match keypair::get_current_space() { + Ok(space) => { + match symmetric::encrypt_key_space(&space, password) { + Ok(encrypted_space) => { + match serde_json::to_string(&encrypted_space) { + Ok(json) => json, + Err(e) => { + log::error!("Error serializing encrypted space: {}", e); + String::new() + } + } + }, + Err(e) => { + log::error!("Error encrypting key space: {}", e); + String::new() + } + } + }, + Err(e) => { + log::error!("Error getting current space: {}", e); + String::new() + } + } +} + +fn decrypt_key_space(encrypted: &str, password: &str) -> bool { + match serde_json::from_str(encrypted) { + Ok(encrypted_space) => { + match symmetric::decrypt_key_space(&encrypted_space, password) { + Ok(space) => { + match keypair::set_current_space(space) { + Ok(_) => true, + Err(e) => { + log::error!("Error setting current space: {}", e); + false + } + } + }, + Err(e) => { + log::error!("Error decrypting key space: {}", e); + false + } + } + }, + Err(e) => { + log::error!("Error parsing encrypted space: {}", e); + false + } + } +} + +// Keypair management functions +fn create_keypair(name: &str, password: &str) -> bool { + match keypair::create_keypair(name) { + Ok(_) => { + // Auto-save the key space after creating a keypair + auto_save_key_space(password) + }, + Err(e) => { + log::error!("Error creating keypair: {}", e); + false + } + } +} + +fn select_keypair(name: &str) -> bool { + match keypair::select_keypair(name) { + Ok(_) => true, + Err(e) => { + log::error!("Error selecting keypair: {}", e); + false + } + } +} + +fn list_keypairs() -> Vec { + match keypair::list_keypairs() { + Ok(keypairs) => keypairs, + Err(e) => { + log::error!("Error listing keypairs: {}", e); + Vec::new() + } + } +} + +// Cryptographic operations +fn sign(message: &str) -> String { + let message_bytes = message.as_bytes(); + match keypair::keypair_sign(message_bytes) { + Ok(signature) => BASE64.encode(signature), + Err(e) => { + log::error!("Error signing message: {}", e); + String::new() + } + } +} + +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(is_valid) => is_valid, + Err(e) => { + log::error!("Error verifying signature: {}", e); + false + } + } + }, + Err(e) => { + log::error!("Error decoding signature: {}", e); + false + } + } +} + +// Symmetric encryption +fn generate_key() -> String { + let key = symmetric::generate_symmetric_key(); + BASE64.encode(key) +} + +fn encrypt(key: &str, message: &str) -> String { + match BASE64.decode(key) { + Ok(key_bytes) => { + let message_bytes = message.as_bytes(); + match symmetric::encrypt_symmetric(&key_bytes, message_bytes) { + Ok(ciphertext) => BASE64.encode(ciphertext), + Err(e) => { + log::error!("Error encrypting message: {}", e); + String::new() + } + } + }, + Err(e) => { + log::error!("Error decoding key: {}", e); + String::new() + } + } +} + +fn decrypt(key: &str, ciphertext: &str) -> String { + match BASE64.decode(key) { + Ok(key_bytes) => { + match BASE64.decode(ciphertext) { + Ok(ciphertext_bytes) => { + match symmetric::decrypt_symmetric(&key_bytes, &ciphertext_bytes) { + Ok(plaintext) => { + match String::from_utf8(plaintext) { + Ok(text) => text, + Err(e) => { + log::error!("Error converting plaintext to string: {}", e); + String::new() + } + } + }, + Err(e) => { + log::error!("Error decrypting ciphertext: {}", e); + String::new() + } + } + }, + Err(e) => { + log::error!("Error decoding ciphertext: {}", e); + String::new() + } + } + }, + Err(e) => { + log::error!("Error decoding key: {}", e); + String::new() + } + } +} + +// Ethereum operations +fn create_ethereum_wallet() -> bool { + match ethereum::create_ethereum_wallet() { + Ok(_) => true, + Err(e) => { + log::error!("Error creating Ethereum wallet: {}", e); + false + } + } +} + +fn get_ethereum_address() -> String { + match ethereum::get_current_ethereum_wallet() { + Ok(wallet) => wallet.address_string(), + Err(e) => { + log::error!("Error getting Ethereum address: {}", e); + String::new() + } + } +} + +/// Register crypto functions with the Rhai engine +pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box> { + // Register key space functions + engine.register_fn("load_key_space", load_key_space); + engine.register_fn("create_key_space", create_key_space); + engine.register_fn("encrypt_key_space", encrypt_key_space); + engine.register_fn("decrypt_key_space", decrypt_key_space); + + // Register keypair functions + engine.register_fn("create_keypair", create_keypair); + engine.register_fn("select_keypair", select_keypair); + engine.register_fn("list_keypairs", list_keypairs); + + // Register signing/verification functions + engine.register_fn("sign", sign); + engine.register_fn("verify", verify); + + // Register symmetric encryption functions + engine.register_fn("generate_key", generate_key); + engine.register_fn("encrypt", encrypt); + engine.register_fn("decrypt", decrypt); + + // Register Ethereum functions + engine.register_fn("create_ethereum_wallet", create_ethereum_wallet); + engine.register_fn("get_ethereum_address", get_ethereum_address); + + Ok(()) +} \ No newline at end of file diff --git a/src/rhai/mod.rs b/src/rhai/mod.rs index d63565e..49439a7 100644 --- a/src/rhai/mod.rs +++ b/src/rhai/mod.rs @@ -11,6 +11,7 @@ mod nerdctl; mod git; mod text; mod rfs; +mod crypto; #[cfg(test)] mod tests; @@ -73,6 +74,9 @@ pub use crate::text::{ // Re-export TextReplacer functions pub use text::*; +// Re-export crypto module +pub use crypto::register_crypto_module; + // Rename copy functions to avoid conflicts pub use os::copy as os_copy; @@ -116,6 +120,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box> { // Register RFS module functions rfs::register(engine)?; + // Register Crypto module functions + crypto::register_crypto_module(engine)?; + // Future modules can be registered here