Remove old files
Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
parent
2adda10664
commit
2014c63b78
@ -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
|
@ -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)
|
||||
}
|
@ -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(¶m.kind)) {
|
||||
Ok(token) => tokens.push(token),
|
||||
Err(e) => return Err(format!("Error converting argument {}: {}", i, e))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
/// Convert ethers Token to Rhai Dynamic value
|
||||
pub fn convert_token_to_rhai(tokens: &[Token]) -> Dynamic {
|
||||
if tokens.is_empty() {
|
||||
return Dynamic::UNIT;
|
||||
}
|
||||
|
||||
// If there's only one return value, return it directly
|
||||
if tokens.len() == 1 {
|
||||
return token_to_dynamic(&tokens[0]);
|
||||
}
|
||||
|
||||
// If there are multiple return values, return them as an array
|
||||
let mut array = Array::new();
|
||||
for token in tokens {
|
||||
array.push(token_to_dynamic(token));
|
||||
}
|
||||
Dynamic::from(array)
|
||||
}
|
||||
|
||||
/// Convert a single token to a Dynamic value
|
||||
pub fn token_to_dynamic(token: &Token) -> Dynamic {
|
||||
match token {
|
||||
Token::Address(addr) => Dynamic::from(format!("{:?}", addr)),
|
||||
Token::Bytes(bytes) => Dynamic::from(ethers::utils::hex::encode(bytes)),
|
||||
Token::Int(i) => Dynamic::from(i.to_string()),
|
||||
Token::Uint(u) => Dynamic::from(u.to_string()),
|
||||
Token::Bool(b) => Dynamic::from(*b),
|
||||
Token::String(s) => Dynamic::from(s.clone()),
|
||||
Token::Array(arr) => {
|
||||
let mut rhai_arr = Array::new();
|
||||
for item in arr {
|
||||
rhai_arr.push(token_to_dynamic(item));
|
||||
}
|
||||
Dynamic::from(rhai_arr)
|
||||
},
|
||||
Token::Tuple(tuple) => {
|
||||
let mut rhai_arr = Array::new();
|
||||
for item in tuple {
|
||||
rhai_arr.push(token_to_dynamic(item));
|
||||
}
|
||||
Dynamic::from(rhai_arr)
|
||||
},
|
||||
// Handle other token types
|
||||
_ => {
|
||||
log::warn!("Unsupported token type: {:?}", token);
|
||||
Dynamic::UNIT
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
@ -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
|
||||
})
|
||||
}
|
@ -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())
|
||||
}
|
@ -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())
|
||||
}
|
@ -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());
|
||||
}
|
@ -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.
|
@ -1,7 +0,0 @@
|
||||
//! Tests for Ethereum functionality.
|
||||
|
||||
mod wallet_tests;
|
||||
mod network_tests;
|
||||
mod transaction_tests;
|
||||
mod contract_tests;
|
||||
mod contract_args_tests;
|
@ -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());
|
||||
}
|
@ -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
|
||||
}
|
@ -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();
|
||||
}
|
@ -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())
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
@ -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;
|
@ -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)
|
||||
}
|
@ -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.
|
@ -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());
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
|
||||
mod keypair_types_tests;
|
||||
mod session_manager_tests;
|
@ -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());
|
||||
}
|
||||
}
|
@ -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
|
||||
```
|
@ -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>;
|
@ -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;
|
@ -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
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
mod store_tests;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
};
|
Loading…
Reference in New Issue
Block a user