feat: Update SAL Vault examples and documentation

- Renamed examples directory to `_archive` to reflect legacy status.
- Updated README.md to reflect current status of vault module,
  including migration from Sameh's implementation to Lee's.
- Temporarily disabled Rhai scripting integration for the vault.
- Added notes regarding current and future development steps.
This commit is contained in:
Mahmoud-Emad 2025-07-10 14:03:43 +03:00
parent 61f5331804
commit 6e5d9b35e8
58 changed files with 1576 additions and 278 deletions

View File

@ -1,64 +1,76 @@
# Hero Vault Cryptography Examples # SAL Vault Examples
This directory contains examples demonstrating the Hero Vault cryptography functionality integrated into the SAL project. This directory contains examples demonstrating the SAL Vault functionality.
## Overview ## Overview
Hero Vault provides cryptographic operations including: SAL Vault provides secure key management and cryptographic operations including:
- Key space management (creation, loading, encryption, decryption) - Vault creation and management
- Keypair management (creation, selection, listing) - KeySpace operations (encrypted key-value stores)
- Digital signatures (signing and verification) - Symmetric key generation and operations
- Symmetric encryption (key generation, encryption, decryption) - Asymmetric key operations (signing and verification)
- Ethereum wallet functionality - Secure key derivation from passwords
- Smart contract interactions
- Key-value store with encryption
## Example Files ## Current Status
- `example.rhai` - Basic example demonstrating key management, signing, and encryption ⚠️ **Note**: The vault module is currently being updated to use Lee's implementation.
- `advanced_example.rhai` - Advanced example with error handling, conditional logic, and more complex operations The Rhai scripting integration is temporarily disabled while we adapt the examples
- `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk to work with the new vault API.
- `load_existing_space.rhai` - Shows how to load a previously created key space and use its keypairs
- `contract_example.rhai` - Demonstrates loading a contract ABI and interacting with smart contracts
- `agung_send_transaction.rhai` - Demonstrates sending native tokens on the Agung network
- `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung
## Running the Examples ## Available Operations
You can run the examples using the `herodo` tool that comes with the SAL project: - **Vault Management**: Create and manage vault instances
- **KeySpace Operations**: Open encrypted key-value stores within vaults
- **Symmetric Encryption**: Generate keys and encrypt/decrypt data
- **Asymmetric Operations**: Create keypairs, sign messages, verify signatures
```bash ## Example Files (Legacy - Sameh's Implementation)
# Run a single example
herodo --path example.rhai
# Run all examples using the provided script ⚠️ **These examples are currently archived and use the previous vault implementation**:
./run_examples.sh
- `_archive/example.rhai` - Basic example demonstrating key management, signing, and encryption
- `_archive/advanced_example.rhai` - Advanced example with error handling and complex operations
- `_archive/key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk
- `_archive/load_existing_space.rhai` - Shows how to load a previously created key space
- `_archive/contract_example.rhai` - Demonstrates smart contract interactions (Ethereum)
- `_archive/agung_send_transaction.rhai` - Demonstrates Ethereum transactions on Agung network
- `_archive/agung_contract_with_args.rhai` - Shows contract interactions with arguments
## Current Implementation (Lee's Vault)
The current vault implementation provides:
```rust
// Create a new vault
let vault = Vault::new(&path).await?;
// Open an encrypted keyspace
let keyspace = vault.open_keyspace("my_space", "password").await?;
// Perform cryptographic operations
// (API documentation coming soon)
``` ```
## Key Space Storage ## Migration Status
Key spaces are stored in the `~/.hero-vault/key-spaces/` directory by default. Each key space is stored in a separate JSON file named after the key space (e.g., `my_space.json`). - ✅ **Vault Core**: Lee's implementation is active
- ✅ **Archive**: Sameh's implementation preserved in `vault/_archive/`
## Ethereum Functionality - ⏳ **Rhai Integration**: Being developed for Lee's implementation
- ⏳ **Examples**: Will be updated to use Lee's API
The Hero Vault module provides comprehensive Ethereum wallet functionality: - ❌ **Ethereum Features**: Not available in Lee's implementation
- Creating and managing wallets for different networks
- Sending ETH transactions
- Checking balances
- Interacting with smart contracts (read and write functions)
- Support for multiple networks (Ethereum, Gnosis, Peaq, Agung, etc.)
## Security ## Security
Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password. The encryption ensures that the key material is secure at rest. The vault uses:
## Best Practices - **ChaCha20Poly1305** for symmetric encryption
- **Password-based key derivation** for keyspace encryption
- **Secure key storage** with proper isolation
1. **Use Strong Passwords**: Since the security of your key spaces depends on the strength of your passwords, use strong, unique passwords. ## Next Steps
2. **Backup Key Spaces**: Regularly backup your key spaces directory to prevent data loss.
3. **Script Organization**: Split your scripts into logical units, with separate scripts for key creation and key usage. 1. **Rhai Integration**: Implement Rhai bindings for Lee's vault
4. **Error Handling**: Always check the return values of functions to ensure operations succeeded before proceeding. 2. **New Examples**: Create examples using Lee's simpler API
5. **Network Selection**: When working with Ethereum functionality, be explicit about which network you're targeting to avoid confusion. 3. **Documentation**: Complete API documentation for Lee's implementation
6. **Gas Management**: For Ethereum transactions, consider gas costs and set appropriate gas limits. 4. **Migration Guide**: Provide guidance for users migrating from Sameh's implementation

View File

@ -96,8 +96,9 @@ pub use sal_text::rhai::register_text_module;
// Re-export net module // Re-export net module
pub use sal_net::rhai::register_net_module; pub use sal_net::rhai::register_net_module;
// Re-export crypto module // Re-export crypto module - TEMPORARILY DISABLED
pub use sal_vault::rhai::register_crypto_module; // TODO: Implement rhai module for Lee's vault implementation
// pub use sal_vault::rhai::register_crypto_module;
// Re-export kubernetes module // Re-export kubernetes module
pub use sal_kubernetes::rhai::register_kubernetes_module; pub use sal_kubernetes::rhai::register_kubernetes_module;
@ -158,8 +159,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
// RFS module functions are now registered as part of sal_virt above // RFS module functions are now registered as part of sal_virt above
// Register Crypto module functions // Register Crypto module functions - TEMPORARILY DISABLED
register_crypto_module(engine)?; // TODO: Implement rhai module for Lee's vault implementation
// register_crypto_module(engine)?;
// Register Kubernetes module functions // Register Kubernetes module functions
register_kubernetes_module(engine)?; register_kubernetes_module(engine)?;

View File

@ -216,7 +216,7 @@ fn test_module_registration_functions() {
assert!(sal_rhai::register_os_module(&mut engine).is_ok()); assert!(sal_rhai::register_os_module(&mut engine).is_ok());
assert!(sal_rhai::register_process_module(&mut engine).is_ok()); assert!(sal_rhai::register_process_module(&mut engine).is_ok());
assert!(sal_rhai::register_git_module(&mut engine).is_ok()); assert!(sal_rhai::register_git_module(&mut engine).is_ok());
assert!(sal_rhai::register_crypto_module(&mut engine).is_ok()); // assert!(sal_rhai::register_crypto_module(&mut engine).is_ok()); // Temporarily disabled
assert!(sal_rhai::register_redisclient_module(&mut engine).is_ok()); assert!(sal_rhai::register_redisclient_module(&mut engine).is_ok());
assert!(sal_rhai::register_postgresclient_module(&mut engine).is_ok()); assert!(sal_rhai::register_postgresclient_module(&mut engine).is_ok());
assert!(sal_rhai::register_mycelium_module(&mut engine).is_ok()); assert!(sal_rhai::register_mycelium_module(&mut engine).is_ok());

View File

@ -3,45 +3,28 @@ name = "sal-vault"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["PlanetFirst <info@incubaid.com>"] authors = ["PlanetFirst <info@incubaid.com>"]
description = "SAL Vault - Cryptographic functionality including key management, digital signatures, symmetric encryption, Ethereum wallets, and encrypted key-value store" description = "SAL Vault - Secure key management and cryptographic operations"
repository = "https://git.threefold.info/herocode/sal" repository = "https://git.threefold.info/herocode/sal"
license = "Apache-2.0" license = "Apache-2.0"
keywords = ["vault", "crypto", "keys", "security", "sal"]
categories = ["cryptography", "api-bindings"]
[features]
# native = ["kv/native"]
# wasm = ["kv/web"]
# Features temporarily disabled due to external dependency issues
[dependencies] [dependencies]
# Core cryptographic dependencies getrandom = { version = "0.3.3", features = ["wasm_js"] }
rand = "0.9.1"
# We need to pull v0.2.x to enable the "js" feature for wasm32 builds
getrandom_old = { package = "getrandom", version = "0.2.16", features = ["js"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
chacha20poly1305 = "0.10.1" chacha20poly1305 = "0.10.1"
k256 = { version = "0.13.4", features = ["ecdsa", "ecdh"] } k256 = { version = "0.13.4", features = ["ecdh"] }
sha2 = "0.10.7" sha2 = "0.10.9"
rand = "0.8.5" # kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" }
# Temporarily disabled due to broken external dependencies
# Ethereum dependencies bincode = { version = "2.0.1", features = ["serde"] }
ethers = { version = "2.0.7", features = ["legacy"] } pbkdf2 = "0.12.2"
hex = "0.4"
# Serialization and data handling
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.22.1"
# Error handling
thiserror = "2.0.12"
# Async runtime and utilities
tokio = { version = "1.45.0", features = ["full"] }
once_cell = "1.18.0"
# File system utilities
dirs = "6.0.0"
# Rhai scripting support
rhai = { version = "1.12.0", features = ["sync"] }
# UUID generation
uuid = { version = "1.16.0", features = ["v4"] }
# Logging
log = "0.4"
[dev-dependencies]
tempfile = "3.5"
tokio-test = "0.4.4"

View File

@ -1,166 +1,148 @@
# SAL Vault (`sal-vault`) # SAL Vault
SAL Vault is a comprehensive cryptographic library that provides secure key management, digital signatures, symmetric encryption, Ethereum wallet functionality, and encrypted key-value storage. A secure, encrypted key-value store system for the System Abstraction Layer (SAL).
## Installation ## Overview
Add this to your `Cargo.toml`: SAL Vault provides a two-tiered encrypted storage system:
```toml 1. **Vault**: A collection of encrypted keyspaces
[dependencies] 2. **KeySpace**: An individual encrypted key-value store within a vault
sal-vault = "0.1.0"
```
## Features ## Features
### Core Cryptographic Operations - **Secure Storage**: ChaCha20Poly1305 encryption for all data
- **Symmetric Encryption**: ChaCha20Poly1305 AEAD cipher for secure data encryption - **Password-Based Encryption**: Keyspaces are encrypted using password-derived keys
- **Key Derivation**: PBKDF2-based key derivation from passwords - **Cross-Platform**: Works on both native and WASM targets
- **Digital Signatures**: ECDSA signing and verification using secp256k1 curves - **Async API**: Fully asynchronous operations
- **Key Management**: Secure keypair generation and storage - **Type Safety**: Strong typing with comprehensive error handling
### Keyspace Management ## Architecture
- **Multiple Keyspaces**: Organize keys into separate, password-protected spaces
- **Session Management**: Secure session handling with automatic cleanup
- **Keypair Organization**: Named keypairs within keyspaces for easy management
### Ethereum Integration ```
- **Wallet Functionality**: Create and manage Ethereum wallets from keypairs Vault
- **Transaction Signing**: Sign Ethereum transactions securely ├── KeySpace 1 (encrypted with password A)
- **Smart Contract Interaction**: Call read functions on smart contracts ├── KeySpace 2 (encrypted with password B)
- **Multi-Network Support**: Support for different Ethereum networks └── KeySpace N (encrypted with password N)
```
### Key-Value Store Each keyspace is independently encrypted, allowing different access controls and security boundaries.
- **Encrypted Storage**: Store key-value pairs with automatic encryption
- **Secure Persistence**: Data is encrypted before being written to disk
- **Type Safety**: Strongly typed storage and retrieval operations
### Rhai Scripting Integration
- **Complete API Exposure**: All vault functionality available in Rhai scripts
- **Session Management**: Script-accessible session and keyspace management
- **Cryptographic Operations**: Encryption, signing, and verification in scripts
## Usage ## Usage
### Basic Cryptographic Operations ### Creating a Vault
```rust ```rust
use sal_vault::symmetric::implementation::{encrypt_symmetric, decrypt_symmetric, generate_symmetric_key}; use sal_vault::{Vault, Error};
use std::path::Path;
// Generate a symmetric key #[tokio::main]
let key = generate_symmetric_key(); async fn main() -> Result<(), Error> {
// Create a new vault at the specified path
// Encrypt data let vault = Vault::new(Path::new("./my_vault")).await?;
let message = b"Hello, World!";
let encrypted = encrypt_symmetric(&key, message)?; // Open an encrypted keyspace
let keyspace = vault.open_keyspace("user_data", "secure_password").await?;
// Decrypt data
let decrypted = decrypt_symmetric(&key, &encrypted)?; // Use the keyspace for encrypted storage
``` // (KeySpace API documentation coming soon)
### Keyspace and Keypair Management Ok(())
```rust
use sal_vault::keyspace::{KeySpace, KeyPair};
// Create a new keyspace
let mut keyspace = KeySpace::new("my_keyspace");
// Add a keypair
keyspace.add_keypair("main_key")?;
// Sign data
if let Some(keypair) = keyspace.keypairs.get("main_key") {
let message = b"Important message";
let signature = keypair.sign(message);
let is_valid = keypair.verify(message, &signature)?;
} }
``` ```
### Ethereum Wallet Operations ### WASM Support
The vault also supports WASM targets with browser-compatible storage:
```rust ```rust
use sal_vault::ethereum::wallet::EthereumWallet; #[cfg(target_arch = "wasm32")]
use sal_vault::ethereum::networks::NetworkConfig; async fn wasm_example() -> Result<(), Error> {
let vault = Vault::new().await?; // No path needed for WASM
// Create wallet from keypair let keyspace = vault.open_keyspace("session_data", "password").await?;
let network = NetworkConfig::mainnet(); Ok(())
let wallet = EthereumWallet::from_keypair(&keypair, network)?; }
// Get wallet address
let address = wallet.address();
``` ```
### Rhai Scripting ## Security
```rhai ### Encryption
// Create and manage keyspaces
create_key_space("personal", "secure_password");
select_keyspace("personal");
// Create and use keypairs - **Algorithm**: ChaCha20Poly1305 (AEAD)
create_keypair("signing_key"); - **Key Derivation**: PBKDF2 with secure parameters
select_keypair("signing_key"); - **Nonce Generation**: Cryptographically secure random nonces
- **Authentication**: Built-in authentication prevents tampering
// Sign and verify data ### Best Practices
let message = "Important document";
let signature = sign(message);
let is_valid = verify(message, signature);
// Symmetric encryption 1. **Strong Passwords**: Use strong, unique passwords for each keyspace
let key = generate_key(); 2. **Secure Storage**: Store vault files in secure locations
let encrypted = encrypt(key, "secret data"); 3. **Access Control**: Limit filesystem access to vault directories
let decrypted = decrypt(key, encrypted); 4. **Backup Strategy**: Implement secure backup procedures
``` 5. **Key Rotation**: Periodically change keyspace passwords
## Security Features
- **Memory Safety**: All sensitive data is handled securely in memory
- **Secure Random Generation**: Uses cryptographically secure random number generation
- **Password-Based Encryption**: Keyspaces are protected with password-derived keys
- **Session Isolation**: Each session maintains separate state and security context
- **Constant-Time Operations**: Critical operations use constant-time implementations
## Error Handling ## Error Handling
The library provides comprehensive error handling through the `CryptoError` enum: The vault uses a comprehensive error system:
```rust ```rust
use sal_vault::error::CryptoError; use sal_vault::Error;
match some_crypto_operation() { match vault.open_keyspace("test", "password").await {
Ok(result) => println!("Success: {:?}", result), Ok(keyspace) => {
Err(CryptoError::InvalidKeyLength) => println!("Invalid key length provided"), // Success - use the keyspace
Err(CryptoError::EncryptionFailed(msg)) => println!("Encryption failed: {}", msg), }
Err(CryptoError::KeypairNotFound(name)) => println!("Keypair '{}' not found", name), Err(Error::IOError(io_err)) => {
Err(e) => println!("Other error: {}", e), // Handle I/O errors (file system issues)
}
Err(Error::CryptoError(crypto_err)) => {
// Handle cryptographic errors (wrong password, corruption)
}
Err(other) => {
// Handle other errors
}
} }
``` ```
## Testing ## Migration from Previous Implementation
The package includes comprehensive tests covering all functionality: This vault implementation replaces the previous Ethereum-focused vault. Key differences:
```bash ### What's New
# Run all tests - ✅ Simpler, more focused API
cargo test - ✅ Better cross-platform support
- ✅ Improved security model
- ✅ Cleaner error handling
# Run specific test categories ### What's Changed
cargo test crypto_tests - ❌ No Ethereum wallet functionality
cargo test rhai_integration_tests - ❌ No smart contract integration
``` - ❌ No built-in signing operations
- ⏳ Rhai scripting integration (coming soon)
**Note**: The Rhai integration tests use global state and are automatically serialized using a test mutex to prevent interference between parallel test runs. ### Archived Implementation
## Dependencies The previous implementation is preserved in `_archive/` for reference and potential feature extraction.
- `chacha20poly1305`: Symmetric encryption ## Development Status
- `k256`: Elliptic curve cryptography
- `ethers`: Ethereum functionality - ✅ **Core Vault**: Complete and functional
- `serde`: Serialization support - ✅ **KeySpace Operations**: Basic implementation ready
- `rhai`: Scripting integration - ✅ **Encryption**: Secure ChaCha20Poly1305 implementation
- `tokio`: Async runtime support - ⏳ **Rhai Integration**: In development
- ⏳ **Extended API**: Additional convenience methods planned
- ⏳ **Documentation**: API docs being completed
## Contributing
When contributing to the vault module:
1. Maintain security-first approach
2. Ensure cross-platform compatibility
3. Add comprehensive tests for new features
4. Update documentation for API changes
5. Consider WASM compatibility for new features
## License ## License
Licensed under the Apache License, Version 2.0. This module is part of the SAL project and follows the same licensing terms.

47
vault/_archive/Cargo.toml Normal file
View File

@ -0,0 +1,47 @@
[package]
name = "sal-vault"
version = "0.1.0"
edition = "2021"
authors = ["PlanetFirst <info@incubaid.com>"]
description = "SAL Vault - Cryptographic functionality including key management, digital signatures, symmetric encryption, Ethereum wallets, and encrypted key-value store"
repository = "https://git.threefold.info/herocode/sal"
license = "Apache-2.0"
[dependencies]
# Core cryptographic dependencies
chacha20poly1305 = "0.10.1"
k256 = { version = "0.13.4", features = ["ecdsa", "ecdh"] }
sha2 = "0.10.7"
rand = "0.8.5"
# Ethereum dependencies
ethers = { version = "2.0.7", features = ["legacy"] }
hex = "0.4"
# Serialization and data handling
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.22.1"
# Error handling
thiserror = "2.0.12"
# Async runtime and utilities
tokio = { version = "1.45.0", features = ["full"] }
once_cell = "1.18.0"
# File system utilities
dirs = "6.0.0"
# Rhai scripting support
rhai = { version = "1.12.0", features = ["sync"] }
# UUID generation
uuid = { version = "1.16.0", features = ["v4"] }
# Logging
log = "0.4"
[dev-dependencies]
tempfile = "3.5"
tokio-test = "0.4.4"

166
vault/_archive/README.md Normal file
View File

@ -0,0 +1,166 @@
# SAL Vault (`sal-vault`)
SAL Vault is a comprehensive cryptographic library that provides secure key management, digital signatures, symmetric encryption, Ethereum wallet functionality, and encrypted key-value storage.
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
sal-vault = "0.1.0"
```
## Features
### Core Cryptographic Operations
- **Symmetric Encryption**: ChaCha20Poly1305 AEAD cipher for secure data encryption
- **Key Derivation**: PBKDF2-based key derivation from passwords
- **Digital Signatures**: ECDSA signing and verification using secp256k1 curves
- **Key Management**: Secure keypair generation and storage
### Keyspace Management
- **Multiple Keyspaces**: Organize keys into separate, password-protected spaces
- **Session Management**: Secure session handling with automatic cleanup
- **Keypair Organization**: Named keypairs within keyspaces for easy management
### Ethereum Integration
- **Wallet Functionality**: Create and manage Ethereum wallets from keypairs
- **Transaction Signing**: Sign Ethereum transactions securely
- **Smart Contract Interaction**: Call read functions on smart contracts
- **Multi-Network Support**: Support for different Ethereum networks
### Key-Value Store
- **Encrypted Storage**: Store key-value pairs with automatic encryption
- **Secure Persistence**: Data is encrypted before being written to disk
- **Type Safety**: Strongly typed storage and retrieval operations
### Rhai Scripting Integration
- **Complete API Exposure**: All vault functionality available in Rhai scripts
- **Session Management**: Script-accessible session and keyspace management
- **Cryptographic Operations**: Encryption, signing, and verification in scripts
## Usage
### Basic Cryptographic Operations
```rust
use sal_vault::symmetric::implementation::{encrypt_symmetric, decrypt_symmetric, generate_symmetric_key};
// Generate a symmetric key
let key = generate_symmetric_key();
// Encrypt data
let message = b"Hello, World!";
let encrypted = encrypt_symmetric(&key, message)?;
// Decrypt data
let decrypted = decrypt_symmetric(&key, &encrypted)?;
```
### Keyspace and Keypair Management
```rust
use sal_vault::keyspace::{KeySpace, KeyPair};
// Create a new keyspace
let mut keyspace = KeySpace::new("my_keyspace");
// Add a keypair
keyspace.add_keypair("main_key")?;
// Sign data
if let Some(keypair) = keyspace.keypairs.get("main_key") {
let message = b"Important message";
let signature = keypair.sign(message);
let is_valid = keypair.verify(message, &signature)?;
}
```
### Ethereum Wallet Operations
```rust
use sal_vault::ethereum::wallet::EthereumWallet;
use sal_vault::ethereum::networks::NetworkConfig;
// Create wallet from keypair
let network = NetworkConfig::mainnet();
let wallet = EthereumWallet::from_keypair(&keypair, network)?;
// Get wallet address
let address = wallet.address();
```
### Rhai Scripting
```rhai
// Create and manage keyspaces
create_key_space("personal", "secure_password");
select_keyspace("personal");
// Create and use keypairs
create_keypair("signing_key");
select_keypair("signing_key");
// Sign and verify data
let message = "Important document";
let signature = sign(message);
let is_valid = verify(message, signature);
// Symmetric encryption
let key = generate_key();
let encrypted = encrypt(key, "secret data");
let decrypted = decrypt(key, encrypted);
```
## Security Features
- **Memory Safety**: All sensitive data is handled securely in memory
- **Secure Random Generation**: Uses cryptographically secure random number generation
- **Password-Based Encryption**: Keyspaces are protected with password-derived keys
- **Session Isolation**: Each session maintains separate state and security context
- **Constant-Time Operations**: Critical operations use constant-time implementations
## Error Handling
The library provides comprehensive error handling through the `CryptoError` enum:
```rust
use sal_vault::error::CryptoError;
match some_crypto_operation() {
Ok(result) => println!("Success: {:?}", result),
Err(CryptoError::InvalidKeyLength) => println!("Invalid key length provided"),
Err(CryptoError::EncryptionFailed(msg)) => println!("Encryption failed: {}", msg),
Err(CryptoError::KeypairNotFound(name)) => println!("Keypair '{}' not found", name),
Err(e) => println!("Other error: {}", e),
}
```
## Testing
The package includes comprehensive tests covering all functionality:
```bash
# Run all tests
cargo test
# Run specific test categories
cargo test crypto_tests
cargo test rhai_integration_tests
```
**Note**: The Rhai integration tests use global state and are automatically serialized using a test mutex to prevent interference between parallel test runs.
## Dependencies
- `chacha20poly1305`: Symmetric encryption
- `k256`: Elliptic curve cryptography
- `ethers`: Ethereum functionality
- `serde`: Serialization support
- `rhai`: Scripting integration
- `tokio`: Async runtime support
## License
Licensed under the Apache License, Version 2.0.

View File

@ -0,0 +1,160 @@
# Hero Vault Cryptography Module
The Hero Vault module provides comprehensive cryptographic functionality for the SAL project, including key management, digital signatures, symmetric encryption, Ethereum wallet operations, and a secure key-value store.
## Module Structure
The Hero Vault module is organized into several submodules:
- `error.rs` - Error types for cryptographic operations
- `keypair/` - ECDSA keypair management functionality
- `symmetric/` - Symmetric encryption using ChaCha20Poly1305
- `ethereum/` - Ethereum wallet and smart contract functionality
- `kvs/` - Encrypted key-value store
## Key Features
### Key Space Management
The module provides functionality for creating, loading, and managing key spaces. A key space is a secure container for cryptographic keys, which can be encrypted and stored on disk.
```rust
// Create a new key space
let space = KeySpace::new("my_space", "secure_password")?;
// Save the key space to disk
space.save()?;
// Load a key space from disk
let loaded_space = KeySpace::load("my_space", "secure_password")?;
```
### Keypair Management
The module provides functionality for creating, selecting, and using ECDSA keypairs for digital signatures.
```rust
// Create a new keypair in the active key space
let keypair = space.create_keypair("my_keypair", "secure_password")?;
// Select a keypair for use
space.select_keypair("my_keypair")?;
// List all keypairs in the active key space
let keypairs = space.list_keypairs()?;
```
### Digital Signatures
The module provides functionality for signing and verifying messages using ECDSA.
```rust
// Sign a message using the selected keypair
let signature = space.sign("This is a message to sign")?;
// Verify a signature
let is_valid = space.verify("This is a message to sign", &signature)?;
```
### Symmetric Encryption
The module provides functionality for symmetric encryption using ChaCha20Poly1305.
```rust
// Generate a new symmetric key
let key = space.generate_key()?;
// Encrypt a message
let encrypted = space.encrypt(&key, "This is a secret message")?;
// Decrypt a message
let decrypted = space.decrypt(&key, &encrypted)?;
```
### Ethereum Wallet Functionality
The module provides comprehensive Ethereum wallet functionality, including:
- Creating and managing wallets for different networks
- Sending ETH transactions
- Checking balances
- Interacting with smart contracts
```rust
// Create an Ethereum wallet
let wallet = EthereumWallet::new(keypair)?;
// Get the wallet address
let address = wallet.get_address()?;
// Send ETH
let tx_hash = wallet.send_eth("0x1234...", "1000000000000000")?;
// Check balance
let balance = wallet.get_balance("0x1234...")?;
```
### Smart Contract Interactions
The module provides functionality for interacting with smart contracts on EVM-based blockchains.
```rust
// Load a contract ABI
let contract = Contract::new(provider, "0x1234...", abi)?;
// Call a read-only function
let result = contract.call_read("balanceOf", vec!["0x5678..."])?;
// Call a write function
let tx_hash = contract.call_write("transfer", vec!["0x5678...", "1000"])?;
```
### Key-Value Store
The module provides an encrypted key-value store for securely storing sensitive data.
```rust
// Create a new store
let store = KvStore::new("my_store", "secure_password")?;
// Set a value
store.set("api_key", "secret_api_key")?;
// Get a value
let api_key = store.get("api_key")?;
```
## Error Handling
The module uses a comprehensive error type (`CryptoError`) for handling errors that can occur during cryptographic operations:
- `InvalidKeyLength` - Invalid key length
- `EncryptionFailed` - Encryption failed
- `DecryptionFailed` - Decryption failed
- `SignatureFormatError` - Signature format error
- `KeypairAlreadyExists` - Keypair already exists
- `KeypairNotFound` - Keypair not found
- `NoActiveSpace` - No active key space
- `NoKeypairSelected` - No keypair selected
- `SerializationError` - Serialization error
- `InvalidAddress` - Invalid address format
- `ContractError` - Smart contract error
## Ethereum Networks
The module supports multiple Ethereum networks, including:
- Gnosis Chain
- Peaq Network
- Agung Network
## Security Considerations
- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password
- Private keys are never stored in plaintext
- The module uses secure random number generation for key creation
- All cryptographic operations use well-established libraries and algorithms
## Examples
For examples of how to use the Hero Vault module, see the `examples/hero_vault` directory.

View File

@ -0,0 +1,53 @@
//! Error types for cryptographic operations
use thiserror::Error;
/// Errors that can occur during cryptographic operations
#[derive(Error, Debug)]
pub enum CryptoError {
/// Invalid key length
#[error("Invalid key length")]
InvalidKeyLength,
/// Encryption failed
#[error("Encryption failed: {0}")]
EncryptionFailed(String),
/// Decryption failed
#[error("Decryption failed: {0}")]
DecryptionFailed(String),
/// Signature format error
#[error("Signature format error: {0}")]
SignatureFormatError(String),
/// Keypair already exists
#[error("Keypair already exists: {0}")]
KeypairAlreadyExists(String),
/// Keypair not found
#[error("Keypair not found: {0}")]
KeypairNotFound(String),
/// No active key space
#[error("No active key space")]
NoActiveSpace,
/// No keypair selected
#[error("No keypair selected")]
NoKeypairSelected,
/// Serialization error
#[error("Serialization error: {0}")]
SerializationError(String),
/// Invalid address format
#[error("Invalid address format: {0}")]
InvalidAddress(String),
/// Smart contract error
#[error("Smart contract error: {0}")]
ContractError(String),
}
// Note: Error conversion to main SAL crate will be handled at the integration level

23
vault/_archive/src/lib.rs Normal file
View File

@ -0,0 +1,23 @@
//! SAL Vault: Cryptographic functionality for SAL
//!
//! This package provides cryptographic operations including:
//! - Key space management (creation, loading, encryption, decryption)
//! - Key pair management (ECDSA)
//! - Digital signatures (signing and verification)
//! - Symmetric encryption (ChaCha20Poly1305)
//! - Ethereum wallet functionality
//! - Key-value store with encryption
pub mod error;
pub mod ethereum;
pub mod keyspace;
pub mod kvs;
pub mod symmetric;
// Rhai integration module
pub mod rhai;
// Re-export modules
// Re-export common types for convenience
pub use error::CryptoError;
pub use keyspace::{KeyPair, KeySpace};

View File

@ -1,53 +1,109 @@
//! Error types for cryptographic operations #[derive(Debug)]
/// Errors encountered while using the vault
use thiserror::Error; pub enum Error {
/// An error during cryptographic operations
/// Errors that can occur during cryptographic operations Crypto(CryptoError),
#[derive(Error, Debug)] /// An error while performing an I/O operation
pub enum CryptoError { IOError(std::io::Error),
/// Invalid key length /// A corrupt keyspace is returned if a keyspace can't be decrypted
#[error("Invalid key length")] CorruptKeyspace,
InvalidKeyLength, /// An error in the used key value store (temporarily disabled)
// KV(kv::error::KVError),
/// Encryption failed /// An error while encoding/decoding the keyspace.
#[error("Encryption failed: {0}")] Coding,
EncryptionFailed(String),
/// Decryption failed
#[error("Decryption failed: {0}")]
DecryptionFailed(String),
/// Signature format error
#[error("Signature format error: {0}")]
SignatureFormatError(String),
/// Keypair already exists
#[error("Keypair already exists: {0}")]
KeypairAlreadyExists(String),
/// Keypair not found
#[error("Keypair not found: {0}")]
KeypairNotFound(String),
/// No active key space
#[error("No active key space")]
NoActiveSpace,
/// No keypair selected
#[error("No keypair selected")]
NoKeypairSelected,
/// Serialization error
#[error("Serialization error: {0}")]
SerializationError(String),
/// Invalid address format
#[error("Invalid address format: {0}")]
InvalidAddress(String),
/// Smart contract error
#[error("Smart contract error: {0}")]
ContractError(String),
} }
// Note: Error conversion to main SAL crate will be handled at the integration level impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::Crypto(e) => f.write_fmt(format_args!("crypto: {e}")),
Error::IOError(e) => f.write_fmt(format_args!("io: {e}")),
Error::CorruptKeyspace => f.write_str("corrupt keyspace"),
// Error::KV(e) => f.write_fmt(format_args!("kv: {e}")),
Error::Coding => f.write_str("keyspace coding failed"),
}
}
}
impl core::error::Error for Error {}
#[derive(Debug)]
/// Errors generated by the vault or keys.
///
/// These errors are intentionally vague to avoid issues such as padding oracles.
pub enum CryptoError {
/// Key size is not valid for this type of key
InvalidKeySize,
/// Something went wrong while trying to encrypt data
EncryptionFailed,
/// Something went wrong while trying to decrypt data
DecryptionFailed,
/// Something went wrong while trying to sign a message
SigningError,
/// The signature is invalid for this message and public key
SignatureFailed,
/// The signature does not have the expected size
InvalidSignatureSize,
/// Trying to load a key which is not the expected format,
InvalidKey,
}
impl core::fmt::Display for CryptoError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CryptoError::InvalidKeySize => f.write_str("provided key is not the correct size"),
CryptoError::EncryptionFailed => f.write_str("encryption failure"),
CryptoError::DecryptionFailed => f.write_str("decryption failure"),
CryptoError::SigningError => f.write_str("signature generation failure"),
CryptoError::SignatureFailed => f.write_str("signature verification failure"),
CryptoError::InvalidSignatureSize => {
f.write_str("provided signature does not have the expected size")
}
CryptoError::InvalidKey => f.write_str("the provided bytes are not a valid key"),
}
}
}
impl core::error::Error for CryptoError {}
impl From<CryptoError> for Error {
fn from(value: CryptoError) -> Self {
Self::Crypto(value)
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Self::IOError(value)
}
}
// impl From<kv::error::KVError> for Error {
// fn from(value: kv::error::KVError) -> Self {
// Self::KV(value)
// }
// }
impl From<bincode::error::DecodeError> for Error {
fn from(_: bincode::error::DecodeError) -> Self {
Self::Coding
}
}
impl From<bincode::error::EncodeError> for Error {
fn from(_: bincode::error::EncodeError) -> Self {
Self::Coding
}
}
impl From<k256::ecdsa::Error> for CryptoError {
fn from(_: k256::ecdsa::Error) -> Self {
Self::InvalidKey
}
}
impl From<k256::elliptic_curve::Error> for CryptoError {
fn from(_: k256::elliptic_curve::Error) -> Self {
Self::InvalidKey
}
}

83
vault/src/key.rs Normal file
View File

@ -0,0 +1,83 @@
use asymmetric::AsymmetricKeypair;
use serde::{Deserialize, Serialize};
use signature::SigningKeypair;
use symmetric::SymmetricKey;
pub mod asymmetric;
pub mod signature;
pub mod symmetric;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum KeyType {
/// The key can be used for symmetric key encryption
Symmetric,
/// The key can be used for asymmetric encryption
Asymmetric,
/// The key can be used for digital signatures
Signature,
}
/// Key holds generic information about a key
#[derive(Clone, Deserialize, Serialize)]
pub struct Key {
/// The mode of the key
mode: KeyType,
/// Raw bytes of the key
raw_key: Vec<u8>,
}
impl Key {
/// Try to downcast this `Key` to a [`SymmetricKey`]
pub fn as_symmetric(&self) -> Option<SymmetricKey> {
if matches!(self.mode, KeyType::Symmetric) {
SymmetricKey::from_bytes(&self.raw_key).ok()
} else {
None
}
}
/// Try to downcast this `Key` to an [`AsymmetricKeypair`]
pub fn as_asymmetric(&self) -> Option<AsymmetricKeypair> {
if matches!(self.mode, KeyType::Asymmetric) {
AsymmetricKeypair::from_bytes(&self.raw_key).ok()
} else {
None
}
}
/// Try to downcast this `Key` to a [`SigningKeypair`]
pub fn as_signing(&self) -> Option<SigningKeypair> {
if matches!(self.mode, KeyType::Signature) {
SigningKeypair::from_bytes(&self.raw_key).ok()
} else {
None
}
}
}
impl From<SymmetricKey> for Key {
fn from(value: SymmetricKey) -> Self {
Self {
mode: KeyType::Symmetric,
raw_key: Vec::from(value.as_raw_bytes()),
}
}
}
impl From<AsymmetricKeypair> for Key {
fn from(value: AsymmetricKeypair) -> Self {
Self {
mode: KeyType::Asymmetric,
raw_key: value.as_raw_private_key(),
}
}
}
impl From<SigningKeypair> for Key {
fn from(value: SigningKeypair) -> Self {
Self {
mode: KeyType::Signature,
raw_key: value.as_raw_private_key(),
}
}
}

161
vault/src/key/asymmetric.rs Normal file
View File

@ -0,0 +1,161 @@
//! An implementation of asymmetric cryptography using SECP256k1 ECDH with ChaCha20Poly1305
//! for the actual encryption.
use k256::{SecretKey, ecdh::diffie_hellman, elliptic_curve::sec1::ToEncodedPoint};
use sha2::Sha256;
use crate::{error::CryptoError, key::symmetric::SymmetricKey};
/// A keypair for use in asymmetric encryption operations.
pub struct AsymmetricKeypair {
/// Private part of the key
private: SecretKey,
/// Public part of the key
public: k256::PublicKey,
}
/// The public key part of an asymmetric keypair.
#[derive(Debug, PartialEq, Eq)]
pub struct PublicKey(k256::PublicKey);
impl AsymmetricKeypair {
/// Generates a new random keypair
pub fn new() -> Result<Self, CryptoError> {
let mut raw_private = [0u8; 32];
rand::fill(&mut raw_private);
let sk = SecretKey::from_slice(&raw_private)
.expect("Key is provided generated with fixed valid size");
let pk = sk.public_key();
Ok(Self {
private: sk,
public: pk,
})
}
/// Create a new key from existing bytes.
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
if bytes.len() == 32 {
let sk = SecretKey::from_slice(&bytes).expect("Key was checked to be a valid size");
let pk = sk.public_key();
Ok(Self {
private: sk,
public: pk,
})
} else {
Err(CryptoError::InvalidKeySize)
}
}
/// View the raw bytes of the private key of this keypair.
pub(crate) fn as_raw_private_key(&self) -> Vec<u8> {
self.private.as_scalar_primitive().to_bytes().to_vec()
}
/// Get the public part of this keypair.
pub fn public_key(&self) -> PublicKey {
PublicKey(self.public.clone())
}
/// Encrypt data for a receiver. First a shared secret is derived using the own private key and
/// the receivers public key. Then, this shared secret is used for symmetric encryption of the
/// plaintext. The receiver can decrypt this by generating the same shared secret, using his
/// own private key and our public key.
pub fn encrypt(
&self,
remote_key: &PublicKey,
plaintext: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let mut symmetric_key = [0u8; 32];
diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine())
.extract::<Sha256>(None)
.expand(&[], &mut symmetric_key)
.map_err(|_| CryptoError::InvalidKeySize)?;
let sym_key = SymmetricKey::from_bytes(&symmetric_key)?;
sym_key.encrypt(plaintext)
}
/// Decrypt data from a sender. The remote key must be the public key of the keypair used by
/// the sender to encrypt this message.
pub fn decrypt(
&self,
remote_key: &PublicKey,
ciphertext: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let mut symmetric_key = [0u8; 32];
diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine())
.extract::<Sha256>(None)
.expand(&[], &mut symmetric_key)
.map_err(|_| CryptoError::InvalidKeySize)?;
let sym_key = SymmetricKey::from_bytes(&symmetric_key)?;
sym_key.decrypt(ciphertext)
}
}
impl PublicKey {
/// Import a public key from raw bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
Ok(Self(k256::PublicKey::from_sec1_bytes(bytes)?))
}
/// Get the raw bytes of this `PublicKey`, which can be transferred to another party.
///
/// The public key is SEC-1 encoded and compressed.
pub fn as_bytes(&self) -> Box<[u8]> {
self.0.to_encoded_point(true).to_bytes()
}
}
#[cfg(test)]
mod tests {
/// Export a public key and import it later
#[test]
fn import_public_key() {
let kp = super::AsymmetricKeypair::new().expect("Can generate new keypair");
let pk1 = kp.public_key();
let pk_bytes = pk1.as_bytes();
let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key");
assert_eq!(pk1, pk2);
}
/// Make sure 2 random keypairs derive the same shared secret (and thus encryption key), by
/// encrypting a random message, decrypting it, and verifying it matches.
#[test]
fn encrypt_and_decrypt() {
let kp1 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
let kp2 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
let pk1 = kp1.public_key();
let pk2 = kp2.public_key();
let message = b"this is a random message to encrypt and decrypt";
let enc = kp1.encrypt(&pk2, message).expect("Can encrypt message");
let dec = kp2.decrypt(&pk1, &enc).expect("Can decrypt message");
assert_eq!(message.as_slice(), dec.as_slice());
}
/// Use a different public key for decrypting than the expected one, this should fail the
/// decryption process as we use AEAD encryption with the symmetric key.
#[test]
fn decrypt_with_wrong_key() {
let kp1 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
let kp2 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
let kp3 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
let pk2 = kp2.public_key();
let pk3 = kp3.public_key();
let message = b"this is a random message to encrypt and decrypt";
let enc = kp1.encrypt(&pk2, message).expect("Can encrypt message");
let dec = kp2.decrypt(&pk3, &enc);
assert!(dec.is_err());
}
}

142
vault/src/key/signature.rs Normal file
View File

@ -0,0 +1,142 @@
//! An implementation of digitial signatures using secp256k1 ECDSA.
use k256::ecdsa::{
Signature, SigningKey, VerifyingKey,
signature::{Signer, Verifier},
};
use crate::error::CryptoError;
pub struct SigningKeypair {
sk: SigningKey,
vk: VerifyingKey,
}
#[derive(Debug, PartialEq, Eq)]
pub struct PublicKey(VerifyingKey);
impl SigningKeypair {
/// Generates a new random keypair
pub fn new() -> Result<Self, CryptoError> {
let mut raw_private = [0u8; 32];
rand::fill(&mut raw_private);
let sk = SigningKey::from_slice(&raw_private)
.expect("Key is provided generated with fixed valid size");
let vk = sk.verifying_key().to_owned();
Ok(Self { sk, vk })
}
/// Create a new key from existing bytes.
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
if bytes.len() == 32 {
let sk = SigningKey::from_slice(&bytes).expect("Key was checked to be a valid size");
let vk = sk.verifying_key().to_owned();
Ok(Self { sk, vk })
} else {
Err(CryptoError::InvalidKeySize)
}
}
/// View the raw bytes of the private key of this keypair.
pub(crate) fn as_raw_private_key(&self) -> Vec<u8> {
self.sk.as_nonzero_scalar().to_bytes().to_vec()
}
/// Get the public part of this keypair.
pub fn public_key(&self) -> PublicKey {
PublicKey(self.vk)
}
/// Sign data with the private key of this `SigningKeypair`. Other parties can use the public
/// key to verify the signature. The generated signature is a detached signature.
pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError> {
let sig: Signature = self.sk.sign(message);
Ok(sig.to_vec())
}
}
impl PublicKey {
/// Import a public key from raw bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
Ok(Self(VerifyingKey::from_sec1_bytes(bytes)?))
}
/// Get the raw bytes of this `PublicKey`, which can be transferred to another party.
///
/// The public key is SEC-1 encoded and compressed.
pub fn as_bytes(&self) -> Box<[u8]> {
self.0.to_encoded_point(true).to_bytes()
}
pub fn verify_signature(&self, message: &[u8], sig: &[u8]) -> Result<(), CryptoError> {
let sig = Signature::from_slice(sig).map_err(|_| CryptoError::InvalidKeySize)?;
self.0
.verify(message, &sig)
.map_err(|_| CryptoError::SignatureFailed)
}
}
#[cfg(test)]
mod tests {
/// Generate a key, get the public key, export the bytes of said public key, import them again
/// as a public key, and verify the keys match. This make sure public keys can be exchanged.
#[test]
fn recover_public_key() {
let sk = super::SigningKeypair::new().expect("Can generate new key");
let pk = sk.public_key();
let pk_bytes = pk.as_bytes();
let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key");
assert_eq!(pk, pk2);
}
/// Sign a message and validate the signature with the public key. Together with the above test
/// this makes sure a remote system can receive our public key and validate messages we sign.
#[test]
fn validate_signature() {
let sk = super::SigningKeypair::new().expect("Can generate new key");
let pk = sk.public_key();
let message = b"this is an arbitrary message we want to sign";
let sig = sk.sign(message).expect("Message can be signed");
assert!(pk.verify_signature(message, &sig).is_ok());
}
/// Make sure a signature which is tampered with does not pass signature validation
#[test]
fn corrupt_signature_does_not_validate() {
let sk = super::SigningKeypair::new().expect("Can generate new key");
let pk = sk.public_key();
let message = b"this is an arbitrary message we want to sign";
let mut sig = sk.sign(message).expect("Message can be signed");
// Tamper with the sig
sig[0] = sig[0].wrapping_add(1);
assert!(pk.verify_signature(message, &sig).is_err());
}
/// Make sure a valid signature does not work for a message which has been modified
#[test]
fn tampered_message_does_not_validate() {
let sk = super::SigningKeypair::new().expect("Can generate new key");
let pk = sk.public_key();
let message = b"this is an arbitrary message we want to sign";
let mut message_clone = message.to_vec();
let sig = sk.sign(message).expect("Message can be signed");
// Modify the message
message_clone[0] = message[0].wrapping_add(1);
assert!(pk.verify_signature(&message_clone, &sig).is_err());
}
}

151
vault/src/key/symmetric.rs Normal file
View File

@ -0,0 +1,151 @@
//! An implementation of symmetric keys for ChaCha20Poly1305 encryption.
//!
//! The ciphertext is authenticated.
//! The 12-byte nonce is appended to the generated ciphertext.
//! Keys are 32 bytes in size.
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce, aead::Aead};
use crate::error::CryptoError;
#[derive(Debug, PartialEq, Eq)]
pub struct SymmetricKey([u8; 32]);
/// Size of a nonce in ChaCha20Poly1305.
const NONCE_SIZE: usize = 12;
impl SymmetricKey {
/// Generate a new random SymmetricKey.
pub fn new() -> Self {
let mut key = [0u8; 32];
rand::fill(&mut key);
Self(key)
}
/// Create a new key from existing bytes.
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<SymmetricKey, CryptoError> {
if bytes.len() == 32 {
let mut key = [0u8; 32];
key.copy_from_slice(bytes);
Ok(SymmetricKey(key))
} else {
Err(CryptoError::InvalidKeySize)
}
}
/// View the raw bytes of this key
pub(crate) fn as_raw_bytes(&self) -> &[u8; 32] {
&self.0
}
/// Encrypt a plaintext with the key. A nonce is generated and appended to the end of the
/// message.
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Create cipher
let cipher = ChaCha20Poly1305::new_from_slice(&self.0)
.expect("Key is a fixed 32 byte array so size is always ok");
// Generate random nonce
let mut nonce_bytes = [0u8; NONCE_SIZE];
rand::fill(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
// Encrypt message
let mut ciphertext = cipher
.encrypt(nonce, plaintext)
.map_err(|_| CryptoError::EncryptionFailed)?;
// Append nonce to ciphertext
ciphertext.extend_from_slice(&nonce_bytes);
Ok(ciphertext)
}
/// Decrypts a ciphertext with appended nonce.
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Check if ciphertext is long enough to contain a nonce
if ciphertext.len() <= NONCE_SIZE {
return Err(CryptoError::DecryptionFailed);
}
// Extract nonce from the end of ciphertext
let ciphertext_len = ciphertext.len() - NONCE_SIZE;
let nonce_bytes = &ciphertext[ciphertext_len..];
let ciphertext = &ciphertext[0..ciphertext_len];
// Create cipher
let cipher = ChaCha20Poly1305::new_from_slice(&self.0)
.expect("Key is a fixed 32 byte array so size is always ok");
let nonce = Nonce::from_slice(nonce_bytes);
// Decrypt message
cipher
.decrypt(nonce, ciphertext)
.map_err(|_| CryptoError::DecryptionFailed)
}
/// Derives a new symmetric key from a password.
///
/// Derivation is done using pbkdf2 with Sha256 hashing.
pub fn derive_from_password(password: &str) -> Self {
/// Salt to use for PBKDF2. This needs to be consistent accross runs to generate the same
/// key. Additionally, it does not really matter what this is, as long as its unique.
const SALT: &[u8; 10] = b"vault_salt";
/// Amount of rounds to use for key generation. More rounds => more cpu time. Changing this
/// also chagnes the generated keys.
const ROUNDS: u32 = 100_000;
let mut key = [0; 32];
pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password.as_bytes(), SALT, ROUNDS, &mut key);
Self(key)
}
}
#[cfg(test)]
mod tests {
/// Using the same password derives the same key
#[test]
fn same_password_derives_same_key() {
const EXPECTED_KEY: [u8; 32] = [
4, 179, 233, 202, 225, 70, 211, 200, 7, 73, 115, 1, 85, 149, 90, 42, 160, 68, 16, 106,
136, 19, 197, 195, 153, 145, 179, 21, 37, 13, 37, 90,
];
const PASSWORD: &str = "test123";
let key = super::SymmetricKey::derive_from_password(PASSWORD);
assert_eq!(key.0, EXPECTED_KEY);
}
/// Make sure an encrypted value with some key can be decrypted with the same key
#[test]
fn can_decrypt() {
let key = super::SymmetricKey::new();
let message = b"this is a message to decrypt";
let enc = key.encrypt(message).expect("Can encrypt message");
let dec = key.decrypt(&enc).expect("Can decrypt message");
assert_eq!(message.as_slice(), dec.as_slice());
}
/// Make sure a value encrypted with one key can't be decrypted with a different key. Since we
/// use AEAD encryption we will notice this when trying to decrypt
#[test]
fn different_key_cant_decrypt() {
let key1 = super::SymmetricKey::new();
let key2 = super::SymmetricKey::new();
let message = b"this is a message to decrypt";
let enc = key1.encrypt(message).expect("Can encrypt message");
let dec = key2.decrypt(&enc);
assert!(dec.is_err());
}
}

151
vault/src/keyspace.rs Normal file
View File

@ -0,0 +1,151 @@
// #[cfg(not(target_arch = "wasm32"))]
// mod fallback;
// #[cfg(target_arch = "wasm32")]
// mod wasm;
use std::collections::HashMap;
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
use crate::{
error::Error,
key::{symmetric::SymmetricKey, Key},
};
// use kv::KVStore;
/// Configuration to use for bincode en/decoding.
const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard();
// Temporarily using simple file-based storage instead of external KV store
#[cfg(not(target_arch = "wasm32"))]
use std::fs;
// #[cfg(not(target_arch = "wasm32"))]
// use kv::native::NativeStore;
// #[cfg(target_arch = "wasm32")]
// use kv::wasm::WasmStore;
const KEYSPACE_NAME: &str = "vault_keyspace";
/// A keyspace represents a group of stored cryptographic keys. The storage is encrypted, a
/// password must be provided when opening the KeySpace to decrypt the keys.
pub struct KeySpace {
/// Path to the keyspace file (native only)
#[cfg(not(target_arch = "wasm32"))]
path: std::path::PathBuf,
/// Storage name for WASM
#[cfg(target_arch = "wasm32")]
name: String,
/// A collection of all keys stored in the KeySpace, in decrypted form.
keys: HashMap<String, Key>,
/// The encryption key used to encrypt/decrypt this keyspace.
encryption_key: SymmetricKey,
}
/// Wasm32 constructor
#[cfg(target_arch = "wasm32")]
impl KeySpace {}
/// Non-wasm constructor
#[cfg(not(target_arch = "wasm32"))]
impl KeySpace {
/// Open the keyspace at the provided path using the given key for encryption.
pub async fn open(path: &Path, encryption_key: SymmetricKey) -> Result<Self, Error> {
let keyspace_file = path.join(KEYSPACE_NAME);
let mut ks = Self {
path: keyspace_file,
keys: HashMap::new(),
encryption_key,
};
ks.load_keyspace().await?;
Ok(ks)
}
}
#[cfg(target_arch = "wasm32")]
impl KeySpace {
pub async fn open(name: &str, encryption_key: SymmetricKey) -> Result<Self, Error> {
let mut ks = Self {
name: name.to_string(),
keys: HashMap::new(),
encryption_key,
};
ks.load_keyspace().await?;
Ok(ks)
}
}
/// Exposed methods, platform independant
impl KeySpace {
/// Get a [`Key`] previously stored under the provided name.
pub async fn get(&self, key: &str) -> Result<Option<Key>, Error> {
Ok(self.keys.get(key).cloned())
}
/// Store a [`Key`] under the provided name.
///
/// This overwrites the existing key if one is already stored with the same name.
pub async fn set(&mut self, key: String, value: Key) -> Result<(), Error> {
self.keys.insert(key, value);
self.save_keyspace().await
}
/// Delete the [`Key`] stored under the provided name.
pub async fn delete(&mut self, key: &str) -> Result<(), Error> {
self.keys.remove(key);
self.save_keyspace().await
}
/// Iterate over all stored [`keys`](Key) in the KeySpace
pub async fn iter(&self) -> Result<impl Iterator<Item = (&String, &Key)>, Error> {
Ok(self.keys.iter())
}
/// Encrypt all keys and save them to the underlying store
async fn save_keyspace(&self) -> Result<(), Error> {
let encoded_keys = bincode::serde::encode_to_vec(&self.keys, BINCODE_CONFIG)?;
let value = self.encryption_key.encrypt(&encoded_keys)?;
#[cfg(not(target_arch = "wasm32"))]
{
fs::write(&self.path, &value)?;
}
#[cfg(target_arch = "wasm32")]
{
// For WASM, we'll use localStorage or similar
// This is a placeholder - would need proper WASM storage implementation
log::warn!("WASM storage not yet implemented");
}
Ok(())
}
/// Loads the encrypted keyspace from the underlying storage
async fn load_keyspace(&mut self) -> Result<(), Error> {
#[cfg(not(target_arch = "wasm32"))]
{
if !self.path.exists() {
// Keyspace doesn't exist yet, nothing to do here
return Ok(());
}
let ks = fs::read(&self.path)?;
let raw = self.encryption_key.decrypt(&ks)?;
let (decoded_keys, _): (HashMap<String, Key>, _) =
bincode::serde::decode_from_slice(&raw, BINCODE_CONFIG)?;
self.keys = decoded_keys;
}
#[cfg(target_arch = "wasm32")]
{
// For WASM, we'll use localStorage or similar
// This is a placeholder - would need proper WASM storage implementation
log::warn!("WASM storage not yet implemented");
}
Ok(())
}
}

View File

@ -0,0 +1,72 @@
use std::{collections::HashMap, io::Write, path::PathBuf};
use crate::{
error::Error,
key::{Key, symmetric::SymmetricKey},
};
/// Magic value used as header in decrypted keyspace files.
const KEYSPACE_MAGIC: [u8; 14] = [
118, 97, 117, 108, 116, 95, 107, 101, 121, 115, 112, 97, 99, 101,
]; //"vault_keyspace"
/// A KeySpace using the filesystem as storage
pub struct KeySpace {
/// Path to file on disk
path: PathBuf,
/// Decrypted keys held in the store
keystore: HashMap<String, Key>,
/// The encryption key used to encrypt/decrypt the storage.
encryption_key: SymmetricKey,
}
impl KeySpace {
/// Opens the `KeySpace`. If it does not exist, it will be created. The provided encryption key
/// will be used for Encrypting and Decrypting the content of the KeySpace.
async fn open(path: PathBuf, encryption_key: SymmetricKey) -> Result<Self, Error> {
/// If the path does not exist, create it first and write the encrypted magic header
if !path.exists() {
// Since we checked path does not exist, the only errors here can be actual IO errors
// (unless something else creates the same file at the same time).
let mut file = std::fs::File::create_new(path)?;
let content = encryption_key.encrypt(&KEYSPACE_MAGIC)?;
file.write_all(&content)?;
}
// Load file, try to decrypt, verify magic header, deserialize keystore
let mut file = std::fs::File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
if buffer.len() < KEYSPACE_MAGIC.len() {
return Err(Error::CorruptKeyspace);
}
if buffer[..KEYSPACE_MAGIC.len()] != KEYSPACE_MAGIC {
return Err(Error::CorruptKeyspace);
}
// TODO: Actual deserialization
todo!();
}
/// Get a [`Key`] previously stored under the provided name.
async fn get(&self, key: &str) -> Result<Option<Key>, Error> {
todo!();
}
/// Store a [`Key`] under the provided name.
async fn set(&self, key: &str, value: Key) -> Result<(), Error> {
todo!();
}
/// Delete the [`Key`] stored under the provided name.
async fn delete(&self, key: &str) -> Result<(), Error> {
todo!();
}
/// Iterate over all stored [`keys`](Key) in the KeySpace
async fn iter(&self) -> Result<impl Iterator<Item = (String, Key)>, Error> {
todo!()
}
}

View File

@ -0,0 +1,26 @@
use crate::{error::Error, key::Key};
/// KeySpace represents an IndexDB keyspace
pub struct KeySpace {}
impl KeySpace {
/// Get a [`Key`] previously stored under the provided name.
async fn get(&self, key: &str) -> Result<Option<Key>, Error> {
todo!();
}
/// Store a [`Key`] under the provided name.
async fn set(&self, key: &str, value: Key) -> Result<(), Error> {
todo!();
}
/// Delete the [`Key`] stored under the provided name.
async fn delete(&self, key: &str) -> Result<(), Error> {
todo!();
}
/// Iterate over all stored [`keys`](Key) in the KeySpace
async fn iter(&self) -> Result<impl Iterator<Item = (String, Key)>, Error> {
todo!()
}
}

View File

@ -1,23 +1,51 @@
//! SAL Vault: Cryptographic functionality for SAL
//!
//! This package provides cryptographic operations including:
//! - Key space management (creation, loading, encryption, decryption)
//! - Key pair management (ECDSA)
//! - Digital signatures (signing and verification)
//! - Symmetric encryption (ChaCha20Poly1305)
//! - Ethereum wallet functionality
//! - Key-value store with encryption
pub mod error; pub mod error;
pub mod ethereum; pub mod key;
pub mod keyspace; pub mod keyspace;
pub mod kvs;
pub mod symmetric;
// Rhai integration module #[cfg(not(target_arch = "wasm32"))]
pub mod rhai; use std::path::{Path, PathBuf};
// Re-export modules use crate::{error::Error, key::symmetric::SymmetricKey, keyspace::KeySpace};
// Re-export common types for convenience
pub use error::CryptoError; /// Vault is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where
pub use keyspace::{KeyPair, KeySpace}; /// each [`space`](KeySpace) is itself an encrypted key-value store
pub struct Vault {
#[cfg(not(target_arch = "wasm32"))]
path: PathBuf,
}
#[cfg(not(target_arch = "wasm32"))]
impl Vault {
/// Create a new store at the given path, creating the path if it does not exist yet.
pub async fn new(path: &Path) -> Result<Self, Error> {
if path.exists() {
if !path.is_dir() {
return Err(Error::IOError(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"expected directory",
)));
}
} else {
std::fs::create_dir_all(path)?;
}
Ok(Self {
path: path.to_path_buf(),
})
}
}
impl Vault {
/// Open a keyspace with the given name
pub async fn open_keyspace(&self, name: &str, password: &str) -> Result<KeySpace, Error> {
let encryption_key = SymmetricKey::derive_from_password(password);
#[cfg(not(target_arch = "wasm32"))]
{
let path = self.path.join(name);
KeySpace::open(&path, encryption_key).await
}
#[cfg(target_arch = "wasm32")]
{
KeySpace::open(name, encryption_key).await
}
}
}