Remove old files

Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
Lee Smet 2025-05-15 13:53:54 +02:00
parent 2adda10664
commit 2014c63b78
Signed by untrusted user who does not match committer: lee
GPG Key ID: 72CBFB5FDA7FE025
32 changed files with 0 additions and 3589 deletions

View File

@ -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

View File

@ -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<Self, CryptoError> {
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<Http>, _wallet: Option<&EthereumWallet>) -> Result<ethers::contract::Contract<ethers::providers::Provider<Http>>, 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<Abi, CryptoError> {
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<Http>,
function_name: &str,
args: Vec<Token>,
) -> Result<Vec<Token>, 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<Http>,
function_name: &str,
args: Vec<Token>,
) -> Result<H256, CryptoError> {
// 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<Http>,
function_name: &str,
args: Vec<Token>,
) -> Result<U256, CryptoError> {
// 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)
}

View File

@ -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<Token, String> {
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<Vec<Token>, 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(&param.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
}
}
}

View File

@ -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,
};

View File

@ -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<NetworkConfig> {
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<HashMap<&'static str, NetworkConfig>> = 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
})
}

View File

@ -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<Provider<Http>, CryptoError> {
Provider::<Http>::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<Provider<Http>, CryptoError> {
create_provider(&networks::gnosis())
}
/// Creates a provider for the Peaq network.
pub fn create_peaq_provider() -> Result<Provider<Http>, CryptoError> {
create_provider(&networks::peaq())
}
/// Creates a provider for the Agung testnet.
pub fn create_agung_provider() -> Result<Provider<Http>, CryptoError> {
create_provider(&networks::agung())
}

View File

@ -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<Mutex<HashMap<String, Vec<EthereumWallet>>>> = 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<EthereumWallet, CryptoError> {
// 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<EthereumWallet, CryptoError> {
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<EthereumWallet, CryptoError> {
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<EthereumWallet, CryptoError> {
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<EthereumWallet, CryptoError> {
get_current_ethereum_wallet_for_network("Peaq")
}
/// Gets the current Ethereum wallet for the Agung testnet.
pub fn get_current_agung_wallet() -> Result<EthereumWallet, CryptoError> {
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<EthereumWallet, CryptoError> {
// 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<EthereumWallet, CryptoError> {
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<EthereumWallet, CryptoError> {
// 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<EthereumWallet, CryptoError> {
create_ethereum_wallet_from_private_key_for_network(private_key, networks::gnosis())
}

View File

@ -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());
}

View File

@ -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.

View File

@ -1,7 +0,0 @@
//! Tests for Ethereum functionality.
mod wallet_tests;
mod network_tests;
mod transaction_tests;
mod contract_tests;
mod contract_args_tests;

View File

@ -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());
}

View File

@ -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
}

View File

@ -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();
}

View File

@ -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<Http>, address: Address) -> Result<U256, CryptoError> {
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<Http>,
to: Address,
amount: U256,
) -> Result<H256, CryptoError> {
// 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())
}

View File

@ -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<SigningKey>,
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<Self, CryptoError> {
// 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<Self, CryptoError> {
// 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<Self, CryptoError> {
// 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<String, CryptoError> {
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)
}
}

View File

@ -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<Self, CryptoError>;
// Create a keypair from an existing private key
pub fn from_private_key(private_key: &[u8]) -> Result<Self, CryptoError>;
// Get the public key
pub fn public_key(&self) -> &[u8];
// Sign a message
pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError>;
// Verify a signature
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, CryptoError>;
// Derive an Ethereum address from the public key
pub fn to_ethereum_address(&self) -> Result<String, CryptoError>;
// Export the private key (should be used with caution)
pub fn export_private_key(&self) -> Result<Vec<u8>, 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<Self, CryptoError>;
// Load a key space from disk
pub fn load(name: &str, password: &str) -> Result<Self, CryptoError>;
// 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<Vec<String>, 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::<String>());
// 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::<String>());
// 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::<String>());
// 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

View File

@ -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<S>(key: &VerifyingKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = key.to_sec1_bytes();
// Convert bytes to a Vec<u8> 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<E>(self, v: &[u8]) -> Result<Self::Value, E>
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<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
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<VerifyingKey, D::Error>
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<S>(key: &SigningKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = key.to_bytes();
// Convert bytes to a Vec<u8> 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<E>(self, v: &[u8]) -> Result<Self::Value, E>
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<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
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<SigningKey, D::Error>
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<u8> {
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<Vec<u8>, 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<u8> {
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<bool, CryptoError> {
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<bool, CryptoError> {
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<Vec<u8>, 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<Vec<u8>, 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<String, KeyPair>,
}
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<String> {
self.keypairs.keys().cloned().collect()
}
}

View File

@ -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;

View File

@ -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<KeySpace>,
pub selected_keypair: Option<String>,
}
impl Default for Session {
fn default() -> Self {
Session {
current_space: None,
selected_keypair: None,
}
}
}
/// Global session state.
pub static SESSION: Lazy<Mutex<Session>> = 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<KeySpace, CryptoError> {
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<KeyPair, CryptoError> {
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<Vec<String>, 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<Vec<u8>, 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<Vec<u8>, CryptoError> {
KeyPair::pub_key_from_private(private_key)
}
/// Signs a message with the selected keypair.
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, 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<bool, CryptoError> {
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<bool, CryptoError> {
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<Vec<u8>, 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<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
keypair.decrypt_asymmetric(ciphertext)
}

View File

@ -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.

View File

@ -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());
}
}

View File

@ -1,3 +0,0 @@
mod keypair_types_tests;
mod session_manager_tests;

View File

@ -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());
}
}

View File

@ -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<Self, CryptoError>;
// Load a store from disk
pub fn load(name: &str, password: &str) -> Result<Self, CryptoError>;
// 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<Option<String>, 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<bool, CryptoError>;
// List all keys
pub fn keys(&self) -> Result<Vec<String>, 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
```

View File

@ -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<serde_json::Error> for KvsError {
fn from(err: serde_json::Error) -> Self {
KvsError::Serialization(err.to_string())
}
}
impl From<KvsError> for crate::vault::error::CryptoError {
fn from(err: KvsError) -> Self {
crate::vault::error::CryptoError::SerializationError(err.to_string())
}
}
impl From<crate::vault::error::CryptoError> 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<T> = std::result::Result<T, KvsError>;

View File

@ -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;

View File

@ -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<Mutex<HashMap<String, String>>>,
/// Whether the store is encrypted
encrypted: bool,
/// The password for encryption (if encrypted)
password: Option<String>,
}
/// 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<KvStore> {
// 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<KvStore> {
// 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<String, String> = if is_encrypted {
// Decrypt the file content
let password = password.unwrap();
let encrypted_data: Vec<u8> = 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<Vec<String>> {
// 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<K, V>(&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<K, V>(&self, key: K) -> Result<V>
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<K>(&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<K>(&self, key: K) -> Result<bool>
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<Vec<String>> {
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
}
}

View File

@ -1 +0,0 @@
mod store_tests;

View File

@ -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<String, _> = 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);
}
}

View File

@ -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

View File

@ -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<u8>)` 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<Vec<u8>, 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<u8>)` 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<Vec<u8>, 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<u8>)` containing the ciphertext with the nonce appended.
/// * `Err(CryptoError)` if encryption fails.
pub fn encrypt_with_key(key: &[u8], message: &[u8]) -> Result<Vec<u8>, 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<u8>)` containing the decrypted message.
/// * `Err(CryptoError)` if decryption fails.
pub fn decrypt_with_key(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, 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<u8>,
}
/// 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<EncryptedKeySpace, CryptoError> {
// 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<KeySpace, CryptoError> {
// 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<String, CryptoError> {
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<EncryptedKeySpace, CryptoError> {
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()))
}
}
}

View File

@ -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
};