...
This commit is contained in:
30
packages/crypt/vault/Cargo.toml
Normal file
30
packages/crypt/vault/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "sal-vault"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["PlanetFirst <info@incubaid.com>"]
|
||||
description = "SAL Vault - Secure key management and cryptographic operations"
|
||||
repository = "https://git.threefold.info/herocode/sal"
|
||||
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]
|
||||
getrandom = { version = "0.3.3", features = ["wasm_js"] }
|
||||
rand = "0.9.1"
|
||||
# We need to pull v0.2.x to enable the "js" feature for wasm32 builds
|
||||
getrandom_old = { package = "getrandom", version = "0.2.16", features = ["js"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
k256 = { version = "0.13.4", features = ["ecdh"] }
|
||||
sha2 = "0.10.9"
|
||||
# kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" }
|
||||
# Temporarily disabled due to broken external dependencies
|
||||
bincode = { version = "2.0.1", features = ["serde"] }
|
||||
pbkdf2 = "0.12.2"
|
148
packages/crypt/vault/README.md
Normal file
148
packages/crypt/vault/README.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# SAL Vault
|
||||
|
||||
A secure, encrypted key-value store system for the System Abstraction Layer (SAL).
|
||||
|
||||
## Overview
|
||||
|
||||
SAL Vault provides a two-tiered encrypted storage system:
|
||||
|
||||
1. **Vault**: A collection of encrypted keyspaces
|
||||
2. **KeySpace**: An individual encrypted key-value store within a vault
|
||||
|
||||
## Features
|
||||
|
||||
- **Secure Storage**: ChaCha20Poly1305 encryption for all data
|
||||
- **Password-Based Encryption**: Keyspaces are encrypted using password-derived keys
|
||||
- **Cross-Platform**: Works on both native and WASM targets
|
||||
- **Async API**: Fully asynchronous operations
|
||||
- **Type Safety**: Strong typing with comprehensive error handling
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Vault
|
||||
├── KeySpace 1 (encrypted with password A)
|
||||
├── KeySpace 2 (encrypted with password B)
|
||||
└── KeySpace N (encrypted with password N)
|
||||
```
|
||||
|
||||
Each keyspace is independently encrypted, allowing different access controls and security boundaries.
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating a Vault
|
||||
|
||||
```rust
|
||||
use sal_vault::{Vault, Error};
|
||||
use std::path::Path;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
// Create a new vault at the specified path
|
||||
let vault = Vault::new(Path::new("./my_vault")).await?;
|
||||
|
||||
// Open an encrypted keyspace
|
||||
let keyspace = vault.open_keyspace("user_data", "secure_password").await?;
|
||||
|
||||
// Use the keyspace for encrypted storage
|
||||
// (KeySpace API documentation coming soon)
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### WASM Support
|
||||
|
||||
The vault also supports WASM targets with browser-compatible storage:
|
||||
|
||||
```rust
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn wasm_example() -> Result<(), Error> {
|
||||
let vault = Vault::new().await?; // No path needed for WASM
|
||||
let keyspace = vault.open_keyspace("session_data", "password").await?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Encryption
|
||||
|
||||
- **Algorithm**: ChaCha20Poly1305 (AEAD)
|
||||
- **Key Derivation**: PBKDF2 with secure parameters
|
||||
- **Nonce Generation**: Cryptographically secure random nonces
|
||||
- **Authentication**: Built-in authentication prevents tampering
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Strong Passwords**: Use strong, unique passwords for each keyspace
|
||||
2. **Secure Storage**: Store vault files in secure locations
|
||||
3. **Access Control**: Limit filesystem access to vault directories
|
||||
4. **Backup Strategy**: Implement secure backup procedures
|
||||
5. **Key Rotation**: Periodically change keyspace passwords
|
||||
|
||||
## Error Handling
|
||||
|
||||
The vault uses a comprehensive error system:
|
||||
|
||||
```rust
|
||||
use sal_vault::Error;
|
||||
|
||||
match vault.open_keyspace("test", "password").await {
|
||||
Ok(keyspace) => {
|
||||
// Success - use the keyspace
|
||||
}
|
||||
Err(Error::IOError(io_err)) => {
|
||||
// Handle I/O errors (file system issues)
|
||||
}
|
||||
Err(Error::CryptoError(crypto_err)) => {
|
||||
// Handle cryptographic errors (wrong password, corruption)
|
||||
}
|
||||
Err(other) => {
|
||||
// Handle other errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migration from Previous Implementation
|
||||
|
||||
This vault implementation replaces the previous Ethereum-focused vault. Key differences:
|
||||
|
||||
### What's New
|
||||
- ✅ Simpler, more focused API
|
||||
- ✅ Better cross-platform support
|
||||
- ✅ Improved security model
|
||||
- ✅ Cleaner error handling
|
||||
|
||||
### What's Changed
|
||||
- ❌ No Ethereum wallet functionality
|
||||
- ❌ No smart contract integration
|
||||
- ❌ No built-in signing operations
|
||||
- ⏳ Rhai scripting integration (coming soon)
|
||||
|
||||
### Archived Implementation
|
||||
|
||||
The previous implementation is preserved in `_archive/` for reference and potential feature extraction.
|
||||
|
||||
## Development Status
|
||||
|
||||
- ✅ **Core Vault**: Complete and functional
|
||||
- ✅ **KeySpace Operations**: Basic implementation ready
|
||||
- ✅ **Encryption**: Secure ChaCha20Poly1305 implementation
|
||||
- ⏳ **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
|
||||
|
||||
This module is part of the SAL project and follows the same licensing terms.
|
47
packages/crypt/vault/_archive/Cargo.toml
Normal file
47
packages/crypt/vault/_archive/Cargo.toml
Normal 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
packages/crypt/vault/_archive/README.md
Normal file
166
packages/crypt/vault/_archive/README.md
Normal 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.
|
160
packages/crypt/vault/_archive/src/README.md
Normal file
160
packages/crypt/vault/_archive/src/README.md
Normal 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.
|
53
packages/crypt/vault/_archive/src/error.rs
Normal file
53
packages/crypt/vault/_archive/src/error.rs
Normal 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
|
160
packages/crypt/vault/_archive/src/ethereum/README.md
Normal file
160
packages/crypt/vault/_archive/src/ethereum/README.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Hero Vault Ethereum Module
|
||||
|
||||
The Ethereum module provides functionality for creating and managing Ethereum wallets and interacting with smart contracts on EVM-based blockchains.
|
||||
|
||||
## Module Structure
|
||||
|
||||
The Ethereum module is organized into several components:
|
||||
|
||||
- `wallet.rs` - Core Ethereum wallet implementation
|
||||
- `networks.rs` - Network registry and configuration
|
||||
- `provider.rs` - Provider creation and management
|
||||
- `transaction.rs` - Transaction-related functionality
|
||||
- `storage.rs` - Wallet storage functionality
|
||||
- `contract.rs` - Smart contract interaction functionality
|
||||
- `contract_utils.rs` - Utilities for contract interactions
|
||||
|
||||
## Key Features
|
||||
|
||||
### Wallet Management
|
||||
|
||||
The module provides functionality for creating and managing Ethereum wallets:
|
||||
|
||||
```rust
|
||||
// Create a new Ethereum wallet for a specific network
|
||||
let wallet = create_ethereum_wallet_for_network("Ethereum")?;
|
||||
|
||||
// Create a wallet for specific networks
|
||||
let peaq_wallet = create_peaq_wallet()?;
|
||||
let agung_wallet = create_agung_wallet()?;
|
||||
|
||||
// Create a wallet with a specific name
|
||||
let named_wallet = create_ethereum_wallet_from_name_for_network("my_wallet", "Gnosis")?;
|
||||
|
||||
// Create a wallet from a private key
|
||||
let imported_wallet = create_ethereum_wallet_from_private_key("0x...")?;
|
||||
|
||||
// Get the current wallet for a network
|
||||
let current_wallet = get_current_ethereum_wallet_for_network("Ethereum")?;
|
||||
|
||||
// Clear wallets
|
||||
clear_ethereum_wallets()?;
|
||||
clear_ethereum_wallets_for_network("Gnosis")?;
|
||||
```
|
||||
|
||||
### Network Management
|
||||
|
||||
The module supports multiple Ethereum networks and provides functionality for managing network configurations:
|
||||
|
||||
```rust
|
||||
// Get a network configuration by name
|
||||
let network = get_network_by_name("Ethereum")?;
|
||||
|
||||
// Get the proper network name (normalized)
|
||||
let name = get_proper_network_name("eth")?; // Returns "Ethereum"
|
||||
|
||||
// List all available network names
|
||||
let networks = list_network_names()?;
|
||||
|
||||
// Get all network configurations
|
||||
let all_networks = get_all_networks()?;
|
||||
```
|
||||
|
||||
### Provider Management
|
||||
|
||||
The module provides functionality for creating and managing Ethereum providers:
|
||||
|
||||
```rust
|
||||
// Create a provider for a specific network
|
||||
let provider = create_provider("Ethereum")?;
|
||||
|
||||
// Create providers for specific networks
|
||||
let gnosis_provider = create_gnosis_provider()?;
|
||||
let peaq_provider = create_peaq_provider()?;
|
||||
let agung_provider = create_agung_provider()?;
|
||||
```
|
||||
|
||||
### Transaction Management
|
||||
|
||||
The module provides functionality for managing Ethereum transactions:
|
||||
|
||||
```rust
|
||||
// Get the balance of an address
|
||||
let balance = get_balance("Ethereum", "0x...")?;
|
||||
|
||||
// Send ETH to an address
|
||||
let tx_hash = send_eth("Ethereum", "0x...", "1000000000000000")?;
|
||||
|
||||
// Format a balance for display
|
||||
let formatted = format_balance(balance, 18)?; // Convert wei to ETH
|
||||
```
|
||||
|
||||
### Smart Contract Interactions
|
||||
|
||||
The module provides functionality for interacting with smart contracts:
|
||||
|
||||
```rust
|
||||
// Load a contract ABI from JSON
|
||||
let abi = load_abi_from_json(json_string)?;
|
||||
|
||||
// Create a contract instance
|
||||
let contract = Contract::new(provider, "0x...", abi)?;
|
||||
|
||||
// Call a read-only function
|
||||
let result = call_read_function(contract, "balanceOf", vec!["0x..."])?;
|
||||
|
||||
// Call a write function
|
||||
let tx_hash = call_write_function(contract, "transfer", vec!["0x...", "1000"])?;
|
||||
|
||||
// Estimate gas for a function call
|
||||
let gas = estimate_gas(contract, "transfer", vec!["0x...", "1000"])?;
|
||||
```
|
||||
|
||||
### Contract Utilities
|
||||
|
||||
The module provides utilities for working with contract function arguments and return values:
|
||||
|
||||
```rust
|
||||
// Convert Rhai values to Ethereum tokens
|
||||
let token = convert_rhai_to_token(value)?;
|
||||
|
||||
// Prepare function arguments
|
||||
let args = prepare_function_arguments(function, vec![arg1, arg2])?;
|
||||
|
||||
// Convert Ethereum tokens to Rhai values
|
||||
let rhai_value = convert_token_to_rhai(token)?;
|
||||
|
||||
// Convert a token to a dynamic value
|
||||
let dynamic = token_to_dynamic(token)?;
|
||||
```
|
||||
|
||||
## Supported Networks
|
||||
|
||||
The module supports multiple Ethereum networks, including:
|
||||
|
||||
- Gnosis Chain
|
||||
- Peaq Network
|
||||
- Agung Network
|
||||
|
||||
Each network has its own configuration, including:
|
||||
|
||||
- RPC URL
|
||||
- Chain ID
|
||||
- Explorer URL
|
||||
- Native currency symbol and decimals
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module uses the `CryptoError` type for handling errors that can occur during Ethereum operations:
|
||||
|
||||
- `InvalidAddress` - Invalid Ethereum address format
|
||||
- `ContractError` - Smart contract interaction error
|
||||
|
||||
## Examples
|
||||
|
||||
For examples of how to use the Ethereum module, see the `examples/hero_vault` directory, particularly:
|
||||
|
||||
- `contract_example.rhai` - Demonstrates loading a contract ABI and interacting with smart contracts
|
||||
- `agung_simple_transfer.rhai` - Shows how to perform a simple ETH transfer on the Agung network
|
||||
- `agung_send_transaction.rhai` - Demonstrates sending transactions on the Agung network
|
||||
- `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung
|
197
packages/crypt/vault/_archive/src/ethereum/contract.rs
Normal file
197
packages/crypt/vault/_archive/src/ethereum/contract.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! Smart contract interaction functionality.
|
||||
//!
|
||||
//! This module provides functionality for interacting with smart contracts on EVM-based blockchains.
|
||||
|
||||
use ethers::abi::{Abi, Token};
|
||||
use ethers::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::networks::NetworkConfig;
|
||||
use super::wallet::EthereumWallet;
|
||||
use crate::error::CryptoError;
|
||||
|
||||
/// 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)
|
||||
}
|
187
packages/crypt/vault/_archive/src/ethereum/contract_utils.rs
Normal file
187
packages/crypt/vault/_archive/src/ethereum/contract_utils.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
//! Utility functions for smart contract interactions.
|
||||
|
||||
use ethers::abi::{Abi, ParamType, Token};
|
||||
use ethers::types::{Address, U256};
|
||||
use rhai::{Array, Dynamic};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
}
|
59
packages/crypt/vault/_archive/src/ethereum/mod.rs
Normal file
59
packages/crypt/vault/_archive/src/ethereum/mod.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
//! 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 contract;
|
||||
pub mod contract_utils;
|
||||
pub mod networks;
|
||||
mod provider;
|
||||
mod storage;
|
||||
mod transaction;
|
||||
mod wallet;
|
||||
// Re-export public types and functions
|
||||
pub use networks::NetworkConfig;
|
||||
pub use wallet::EthereumWallet;
|
||||
|
||||
// Re-export wallet creation functions
|
||||
pub use storage::{
|
||||
create_agung_wallet, create_ethereum_wallet_for_network, create_ethereum_wallet_from_name,
|
||||
create_ethereum_wallet_from_name_for_network, create_ethereum_wallet_from_private_key,
|
||||
create_ethereum_wallet_from_private_key_for_network, create_peaq_wallet,
|
||||
};
|
||||
|
||||
// Re-export wallet management functions
|
||||
pub use storage::{
|
||||
clear_ethereum_wallets, clear_ethereum_wallets_for_network, get_current_agung_wallet,
|
||||
get_current_ethereum_wallet_for_network, get_current_peaq_wallet,
|
||||
};
|
||||
|
||||
// Re-export provider functions
|
||||
pub use provider::{
|
||||
create_agung_provider, create_gnosis_provider, create_peaq_provider, create_provider,
|
||||
};
|
||||
|
||||
// Re-export transaction functions
|
||||
pub use transaction::{format_balance, get_balance, send_eth};
|
||||
|
||||
// Re-export network registry functions
|
||||
pub use networks::{
|
||||
get_all_networks, get_network_by_name, get_proper_network_name, list_network_names, names,
|
||||
};
|
||||
|
||||
// Re-export contract functions
|
||||
pub use contract::{
|
||||
call_read_function, call_write_function, estimate_gas, load_abi_from_json, Contract,
|
||||
};
|
||||
|
||||
// Re-export contract utility functions
|
||||
pub use contract_utils::{
|
||||
convert_rhai_to_token, convert_token_to_rhai, prepare_function_arguments, token_to_dynamic,
|
||||
};
|
102
packages/crypt/vault/_archive/src/ethereum/networks.rs
Normal file
102
packages/crypt/vault/_archive/src/ethereum/networks.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! Ethereum network registry
|
||||
//!
|
||||
//! This module provides a centralized registry of Ethereum networks and utilities
|
||||
//! to work with them.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// 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
|
||||
})
|
||||
}
|
31
packages/crypt/vault/_archive/src/ethereum/provider.rs
Normal file
31
packages/crypt/vault/_archive/src/ethereum/provider.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
//! Ethereum provider functionality.
|
||||
|
||||
use ethers::prelude::*;
|
||||
|
||||
use super::networks::{self, NetworkConfig};
|
||||
use crate::error::CryptoError;
|
||||
|
||||
/// 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())
|
||||
}
|
133
packages/crypt/vault/_archive/src/ethereum/storage.rs
Normal file
133
packages/crypt/vault/_archive/src/ethereum/storage.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
//! Ethereum wallet storage functionality.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::networks::{self, NetworkConfig};
|
||||
use super::wallet::EthereumWallet;
|
||||
use crate::error::CryptoError;
|
||||
|
||||
/// 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::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::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())
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
//! Tests for smart contract argument handling functionality.
|
||||
|
||||
use ethers::types::Address;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::vault::ethereum::*;
|
||||
|
||||
#[test]
|
||||
fn test_contract_creation() {
|
||||
// Create a simple ABI
|
||||
let abi_json = r#"[
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getValue",
|
||||
"outputs": [{"type": "uint256", "name": ""}],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{"type": "uint256", "name": "newValue"}],
|
||||
"name": "setValue",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]"#;
|
||||
|
||||
// Parse the ABI
|
||||
let abi = load_abi_from_json(abi_json).unwrap();
|
||||
|
||||
// Create a contract address
|
||||
let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
|
||||
|
||||
// Create a network config
|
||||
let network = networks::gnosis();
|
||||
|
||||
// Create a contract
|
||||
let contract = Contract::new(address, abi, network);
|
||||
|
||||
// Verify the contract was created correctly
|
||||
assert_eq!(contract.address, address);
|
||||
assert_eq!(contract.network.name, "Gnosis");
|
||||
|
||||
// Verify the ABI contains the expected functions
|
||||
assert!(contract.abi.function("getValue").is_ok());
|
||||
assert!(contract.abi.function("setValue").is_ok());
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
//! Tests for smart contract functionality.
|
||||
|
||||
use ethers::types::Address;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::vault::ethereum::*;
|
||||
|
||||
#[test]
|
||||
fn test_contract_creation() {
|
||||
// Create a simple ABI
|
||||
let abi_json = r#"[
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getValue",
|
||||
"outputs": [{"type": "uint256", "name": ""}],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{"type": "uint256", "name": "newValue"}],
|
||||
"name": "setValue",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]"#;
|
||||
|
||||
// Parse the ABI
|
||||
let abi = load_abi_from_json(abi_json).unwrap();
|
||||
|
||||
// Create a contract address
|
||||
let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
|
||||
|
||||
// Create a network config
|
||||
let network = networks::gnosis();
|
||||
|
||||
// Create a contract
|
||||
let contract = Contract::new(address, abi, network);
|
||||
|
||||
// Verify the contract was created correctly
|
||||
assert_eq!(contract.address, address);
|
||||
assert_eq!(contract.network.name, "Gnosis");
|
||||
|
||||
// Verify the ABI contains the expected functions
|
||||
assert!(contract.abi.function("getValue").is_ok());
|
||||
assert!(contract.abi.function("setValue").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contract_from_address_string() {
|
||||
// Create a simple ABI
|
||||
let abi_json = r#"[
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getValue",
|
||||
"outputs": [{"type": "uint256", "name": ""}],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]"#;
|
||||
|
||||
// Parse the ABI
|
||||
let abi = load_abi_from_json(abi_json).unwrap();
|
||||
|
||||
// Create a network config
|
||||
let network = networks::gnosis();
|
||||
|
||||
// Create a contract from an address string
|
||||
let address_str = "0x1234567890123456789012345678901234567890";
|
||||
let contract = Contract::from_address_string(address_str, abi, network).unwrap();
|
||||
|
||||
// Verify the contract was created correctly
|
||||
assert_eq!(contract.address, Address::from_str(address_str).unwrap());
|
||||
|
||||
// Test with an invalid address
|
||||
let invalid_address = "0xinvalid";
|
||||
let result = Contract::from_address_string(invalid_address, contract.abi.clone(), contract.network.clone());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// Note: We can't easily test the actual contract calls in unit tests without mocking
|
||||
// the provider, which would be complex. These would be better tested in integration tests
|
||||
// with a local blockchain or testnet.
|
7
packages/crypt/vault/_archive/src/ethereum/tests/mod.rs
Normal file
7
packages/crypt/vault/_archive/src/ethereum/tests/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
//! Tests for Ethereum functionality.
|
||||
|
||||
mod wallet_tests;
|
||||
mod network_tests;
|
||||
mod transaction_tests;
|
||||
mod contract_tests;
|
||||
mod contract_args_tests;
|
@@ -0,0 +1,74 @@
|
||||
//! Tests for Ethereum network functionality.
|
||||
|
||||
use crate::vault::ethereum::*;
|
||||
|
||||
#[test]
|
||||
fn test_network_config() {
|
||||
let gnosis = networks::gnosis();
|
||||
assert_eq!(gnosis.name, "Gnosis");
|
||||
assert_eq!(gnosis.chain_id, 100);
|
||||
assert_eq!(gnosis.token_symbol, "xDAI");
|
||||
|
||||
let peaq = networks::peaq();
|
||||
assert_eq!(peaq.name, "Peaq");
|
||||
assert_eq!(peaq.chain_id, 3338);
|
||||
assert_eq!(peaq.token_symbol, "PEAQ");
|
||||
|
||||
let agung = networks::agung();
|
||||
assert_eq!(agung.name, "Agung");
|
||||
assert_eq!(agung.chain_id, 9990);
|
||||
assert_eq!(agung.token_symbol, "AGNG");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_network_registry() {
|
||||
let network_names = networks::list_network_names();
|
||||
assert!(network_names.iter().any(|&name| name == "Gnosis"));
|
||||
assert!(network_names.iter().any(|&name| name == "Peaq"));
|
||||
assert!(network_names.iter().any(|&name| name == "Agung"));
|
||||
|
||||
let gnosis_proper = networks::get_proper_network_name("gnosis");
|
||||
assert_eq!(gnosis_proper, Some("Gnosis"));
|
||||
|
||||
let peaq_proper = networks::get_proper_network_name("peaq");
|
||||
assert_eq!(peaq_proper, Some("Peaq"));
|
||||
|
||||
let agung_proper = networks::get_proper_network_name("agung");
|
||||
assert_eq!(agung_proper, Some("Agung"));
|
||||
|
||||
let unknown = networks::get_proper_network_name("unknown");
|
||||
assert_eq!(unknown, None);
|
||||
|
||||
let gnosis_config = networks::get_network_by_name("Gnosis");
|
||||
assert!(gnosis_config.is_some());
|
||||
assert_eq!(gnosis_config.unwrap().chain_id, 100);
|
||||
|
||||
let unknown_config = networks::get_network_by_name("Unknown");
|
||||
assert!(unknown_config.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_provider() {
|
||||
let gnosis = networks::gnosis();
|
||||
let peaq = networks::peaq();
|
||||
let agung = networks::agung();
|
||||
|
||||
// Create providers
|
||||
let gnosis_provider = create_provider(&gnosis);
|
||||
let peaq_provider = create_provider(&peaq);
|
||||
let agung_provider = create_provider(&agung);
|
||||
|
||||
// They should all succeed
|
||||
assert!(gnosis_provider.is_ok());
|
||||
assert!(peaq_provider.is_ok());
|
||||
assert!(agung_provider.is_ok());
|
||||
|
||||
// The convenience functions should also work
|
||||
let gnosis_provider2 = create_gnosis_provider();
|
||||
let peaq_provider2 = create_peaq_provider();
|
||||
let agung_provider2 = create_agung_provider();
|
||||
|
||||
assert!(gnosis_provider2.is_ok());
|
||||
assert!(peaq_provider2.is_ok());
|
||||
assert!(agung_provider2.is_ok());
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
//! Tests for Ethereum transaction functionality.
|
||||
|
||||
use crate::vault::ethereum::*;
|
||||
use crate::vault::keypair::implementation::KeyPair;
|
||||
use ethers::types::U256;
|
||||
// use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_format_balance() {
|
||||
let network = networks::gnosis();
|
||||
|
||||
// Test with 0
|
||||
let balance = U256::from(0);
|
||||
let formatted = format_balance(balance, &network);
|
||||
assert_eq!(formatted, "0.000000 xDAI");
|
||||
|
||||
// Test with 1 wei
|
||||
let balance = U256::from(1);
|
||||
let formatted = format_balance(balance, &network);
|
||||
assert_eq!(formatted, "0.000000 xDAI");
|
||||
|
||||
// Test with 1 gwei (10^9 wei)
|
||||
let balance = U256::from(1_000_000_000u64);
|
||||
let formatted = format_balance(balance, &network);
|
||||
assert_eq!(formatted, "0.000000 xDAI");
|
||||
|
||||
// Test with 1 ETH (10^18 wei)
|
||||
let balance = U256::from_dec_str("1000000000000000000").unwrap();
|
||||
let formatted = format_balance(balance, &network);
|
||||
assert_eq!(formatted, "1.000000 xDAI");
|
||||
|
||||
// Test with a larger amount
|
||||
let balance = U256::from_dec_str("123456789000000000000").unwrap();
|
||||
let formatted = format_balance(balance, &network);
|
||||
assert_eq!(formatted, "123.456789 xDAI");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_balance() {
|
||||
// This is a mock test since we can't actually query the blockchain in a unit test
|
||||
// In a real test, we would use a local blockchain or mock the provider
|
||||
|
||||
// Create a provider
|
||||
let network = networks::gnosis();
|
||||
let provider_result = create_provider(&network);
|
||||
|
||||
// The provider creation should succeed
|
||||
assert!(provider_result.is_ok());
|
||||
|
||||
// We can't actually test get_balance without a blockchain
|
||||
// In a real test, we would mock the provider and test the function
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_eth() {
|
||||
// This is a mock test since we can't actually send transactions in a unit test
|
||||
// In a real test, we would use a local blockchain or mock the provider
|
||||
|
||||
// Create a wallet
|
||||
let keypair = KeyPair::new("test_keypair6");
|
||||
let network = networks::gnosis();
|
||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
||||
|
||||
// Create a provider
|
||||
let provider_result = create_provider(&network);
|
||||
assert!(provider_result.is_ok());
|
||||
|
||||
// We can't actually test send_eth without a blockchain
|
||||
// In a real test, we would mock the provider and test the function
|
||||
}
|
143
packages/crypt/vault/_archive/src/ethereum/tests/wallet_tests.rs
Normal file
143
packages/crypt/vault/_archive/src/ethereum/tests/wallet_tests.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
//! Tests for Ethereum wallet functionality.
|
||||
|
||||
use crate::vault::ethereum::*;
|
||||
use crate::vault::keypair::implementation::KeyPair;
|
||||
use ethers::utils::hex;
|
||||
|
||||
#[test]
|
||||
fn test_ethereum_wallet_from_keypair() {
|
||||
let keypair = KeyPair::new("test_keypair");
|
||||
let network = networks::gnosis();
|
||||
|
||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
||||
|
||||
assert_eq!(wallet.network.name, "Gnosis");
|
||||
assert_eq!(wallet.network.chain_id, 100);
|
||||
|
||||
// The address should be a valid Ethereum address
|
||||
assert!(wallet.address_string().starts_with("0x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ethereum_wallet_from_name_and_keypair() {
|
||||
let keypair = KeyPair::new("test_keypair2");
|
||||
let network = networks::gnosis();
|
||||
|
||||
let wallet = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap();
|
||||
|
||||
assert_eq!(wallet.network.name, "Gnosis");
|
||||
assert_eq!(wallet.network.chain_id, 100);
|
||||
|
||||
// The address should be a valid Ethereum address
|
||||
assert!(wallet.address_string().starts_with("0x"));
|
||||
|
||||
// Creating another wallet with the same name and keypair should yield the same address
|
||||
let wallet2 = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap();
|
||||
assert_eq!(wallet.address, wallet2.address);
|
||||
|
||||
// Creating a wallet with a different name should yield a different address
|
||||
let wallet3 = EthereumWallet::from_name_and_keypair("test2", &keypair, network.clone()).unwrap();
|
||||
assert_ne!(wallet.address, wallet3.address);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ethereum_wallet_from_private_key() {
|
||||
let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
let network = networks::gnosis();
|
||||
|
||||
let wallet = EthereumWallet::from_private_key(private_key, network.clone()).unwrap();
|
||||
|
||||
assert_eq!(wallet.network.name, "Gnosis");
|
||||
assert_eq!(wallet.network.chain_id, 100);
|
||||
|
||||
// The address should be a valid Ethereum address
|
||||
assert!(wallet.address_string().starts_with("0x"));
|
||||
|
||||
// The address should be deterministic based on the private key
|
||||
let wallet2 = EthereumWallet::from_private_key(private_key, network.clone()).unwrap();
|
||||
assert_eq!(wallet.address, wallet2.address);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_management() {
|
||||
// Clear any existing wallets
|
||||
clear_ethereum_wallets();
|
||||
|
||||
// Create a key space and keypair
|
||||
crate::vault::keypair::session_manager::create_space("test_space").unwrap();
|
||||
crate::vault::keypair::create_keypair("test_keypair3").unwrap();
|
||||
|
||||
// Create wallets for different networks
|
||||
let gnosis_wallet = create_ethereum_wallet_for_network(networks::gnosis()).unwrap();
|
||||
let peaq_wallet = create_ethereum_wallet_for_network(networks::peaq()).unwrap();
|
||||
let agung_wallet = create_ethereum_wallet_for_network(networks::agung()).unwrap();
|
||||
|
||||
// Get the current wallets
|
||||
let current_gnosis = get_current_ethereum_wallet_for_network("Gnosis").unwrap();
|
||||
let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap();
|
||||
let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap();
|
||||
|
||||
// Check that they match
|
||||
assert_eq!(gnosis_wallet.address, current_gnosis.address);
|
||||
assert_eq!(peaq_wallet.address, current_peaq.address);
|
||||
assert_eq!(agung_wallet.address, current_agung.address);
|
||||
|
||||
// Clear wallets for a specific network
|
||||
clear_ethereum_wallets_for_network("Gnosis");
|
||||
|
||||
// Check that the wallet is gone
|
||||
let result = get_current_ethereum_wallet_for_network("Gnosis");
|
||||
assert!(result.is_err());
|
||||
|
||||
// But the others should still be there
|
||||
let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap();
|
||||
let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap();
|
||||
assert_eq!(peaq_wallet.address, current_peaq.address);
|
||||
assert_eq!(agung_wallet.address, current_agung.address);
|
||||
|
||||
// Clear all wallets
|
||||
clear_ethereum_wallets();
|
||||
|
||||
// Check that all wallets are gone
|
||||
let result1 = get_current_ethereum_wallet_for_network("Gnosis");
|
||||
let result2 = get_current_ethereum_wallet_for_network("Peaq");
|
||||
let result3 = get_current_ethereum_wallet_for_network("Agung");
|
||||
assert!(result1.is_err());
|
||||
assert!(result2.is_err());
|
||||
assert!(result3.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_message() {
|
||||
let keypair = KeyPair::new("test_keypair4");
|
||||
let network = networks::gnosis();
|
||||
|
||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
||||
|
||||
// Create a tokio runtime for the async test
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
// Sign a message
|
||||
let message = b"Hello, world!";
|
||||
let signature = rt.block_on(wallet.sign_message(message)).unwrap();
|
||||
|
||||
// The signature should be a non-empty string
|
||||
assert!(!signature.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_private_key_hex() {
|
||||
let keypair = KeyPair::new("test_keypair5");
|
||||
let network = networks::gnosis();
|
||||
|
||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
||||
|
||||
// Get the private key as hex
|
||||
let private_key_hex = wallet.private_key_hex();
|
||||
|
||||
// The private key should be a 64-character hex string (32 bytes)
|
||||
assert_eq!(private_key_hex.len(), 64);
|
||||
|
||||
// It should be possible to parse it as hex
|
||||
let _bytes = hex::decode(private_key_hex).unwrap();
|
||||
}
|
52
packages/crypt/vault/_archive/src/ethereum/transaction.rs
Normal file
52
packages/crypt/vault/_archive/src/ethereum/transaction.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
//! Ethereum transaction functionality.
|
||||
|
||||
use ethers::prelude::*;
|
||||
|
||||
use super::networks::NetworkConfig;
|
||||
use super::wallet::EthereumWallet;
|
||||
use crate::error::CryptoError;
|
||||
|
||||
/// 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())
|
||||
}
|
126
packages/crypt/vault/_archive/src/ethereum/wallet.rs
Normal file
126
packages/crypt/vault/_archive/src/ethereum/wallet.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
//! 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::error::CryptoError;
|
||||
use crate::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: &crate::keyspace::keypair_types::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)
|
||||
}
|
||||
}
|
271
packages/crypt/vault/_archive/src/keyspace/README.md
Normal file
271
packages/crypt/vault/_archive/src/keyspace/README.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Hero Vault Keypair Module
|
||||
|
||||
The Keypair module provides functionality for creating, managing, and using ECDSA keypairs for digital signatures and other cryptographic operations.
|
||||
|
||||
## Module Structure
|
||||
|
||||
The Keypair module is organized into:
|
||||
|
||||
- `keypair_types.rs` - Defines the KeyPair and related types.
|
||||
- `session_manager.rs` - Implements the core logic for managing keypairs and key spaces.
|
||||
- `mod.rs` - Module exports and public interface.
|
||||
|
||||
## Key Types
|
||||
|
||||
### KeyPair
|
||||
|
||||
The `KeyPair` type represents an ECDSA keypair used for digital signatures and other cryptographic operations.
|
||||
|
||||
```rust
|
||||
pub struct KeyPair {
|
||||
// Private fields
|
||||
// ...
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
// Create a new random keypair
|
||||
pub fn new() -> Result<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
|
332
packages/crypt/vault/_archive/src/keyspace/keypair_types.rs
Normal file
332
packages/crypt/vault/_archive/src/keyspace/keypair_types.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
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::error::CryptoError;
|
||||
use crate::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(recipient_public_key);
|
||||
// Use a fixed salt for testing purposes
|
||||
hasher.update(b"fixed_salt_for_testing");
|
||||
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(self.verifying_key.to_sec1_bytes());
|
||||
// Use the same fixed salt as in encryption
|
||||
hasher.update(b"fixed_salt_for_testing");
|
||||
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()
|
||||
}
|
||||
}
|
16
packages/crypt/vault/_archive/src/keyspace/mod.rs
Normal file
16
packages/crypt/vault/_archive/src/keyspace/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
//! 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::{
|
||||
clear_session, create_keypair, create_space, decrypt_asymmetric, derive_public_key,
|
||||
encrypt_asymmetric, get_current_space, get_selected_keypair, keypair_pub_key, keypair_sign,
|
||||
keypair_verify, list_keypairs, select_keypair, set_current_space, verify_with_public_key,
|
||||
};
|
||||
|
||||
// Tests are now in the tests/ directory
|
174
packages/crypt/vault/_archive/src/keyspace/session_manager.rs
Normal file
174
packages/crypt/vault/_archive/src/keyspace/session_manager.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::error::CryptoError;
|
||||
use crate::keyspace::keypair_types::{KeyPair, KeySpace};
|
||||
|
||||
/// 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)
|
||||
}
|
36
packages/crypt/vault/_archive/src/keyspace/spec.md
Normal file
36
packages/crypt/vault/_archive/src/keyspace/spec.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Keyspace Module Specification
|
||||
|
||||
This document explains the purpose and functionality of the `keyspace` module within the Hero Vault.
|
||||
|
||||
## Purpose of the Module
|
||||
|
||||
The `keyspace` module provides a secure and organized way to manage cryptographic keypairs. It allows for the creation, storage, loading, and utilization of keypairs within designated containers called keyspaces. This module is essential for handling sensitive cryptographic material securely.
|
||||
|
||||
## What is a Keyspace?
|
||||
|
||||
A keyspace is a logical container designed to hold multiple cryptographic keypairs. It is represented by the `KeySpace` struct in the code. Keyspaces can be encrypted and persisted to disk, providing a secure method for storing collections of keypairs. Each keyspace is identified by a unique name.
|
||||
|
||||
## What is a Keypair?
|
||||
|
||||
A keypair, represented by the `KeyPair` struct, is a fundamental cryptographic element consisting of a mathematically linked pair of keys: a public key and a private key. In this module, ECDSA (Elliptic Curve Digital Signature Algorithm) keypairs are used.
|
||||
|
||||
* **Private Key:** This key is kept secret and is used for operations like signing data or decrypting messages intended for the keypair's owner.
|
||||
* **Public Key:** This key can be shared openly and is used to verify signatures created by the corresponding private key or to encrypt messages that can only be decrypted by the private key.
|
||||
|
||||
## How Many Keypairs Per Space?
|
||||
|
||||
A keyspace can hold multiple keypairs. The `KeySpace` struct uses a `HashMap` to store keypairs, where each keypair is associated with a unique string name. There is no inherent, fixed limit on the number of keypairs a keyspace can contain, beyond the practical limitations of system memory.
|
||||
|
||||
## How Do We Load Them?
|
||||
|
||||
Keyspaces are loaded from persistent storage (disk) using the `KeySpace::load` function, which requires the keyspace name and a password for decryption. Once a `KeySpace` object is loaded into memory, it can be set as the currently active keyspace for the session using the `session_manager::set_current_space` function. Individual keypairs within the loaded keyspace are then accessed by their names using functions like `session_manager::select_keypair` and `session_manager::get_selected_keypair`.
|
||||
|
||||
## What Do They Do?
|
||||
|
||||
Keypairs within a keyspace are used to perform various cryptographic operations. The `KeyPair` struct provides methods for:
|
||||
|
||||
* **Digital Signatures:** Signing messages with the private key (`KeyPair::sign`) and verifying those signatures with the public key (`KeyPair::verify`).
|
||||
* **Ethereum Address Derivation:** Generating an Ethereum address from the public key (`KeyPair::to_ethereum_address`).
|
||||
* **Asymmetric Encryption/Decryption:** Encrypting data using a recipient's public key (`KeyPair::encrypt_asymmetric`) and decrypting data encrypted with the keypair's public key using the private key (`KeyPair::decrypt_asymmetric`).
|
||||
|
||||
The `session_manager` module provides functions that utilize the currently selected keypair to perform these operations within the context of the active session.
|
173
packages/crypt/vault/_archive/src/kvs/README.md
Normal file
173
packages/crypt/vault/_archive/src/kvs/README.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Hero Vault Key-Value Store Module
|
||||
|
||||
The Key-Value Store (KVS) module provides an encrypted key-value store for securely storing sensitive data.
|
||||
|
||||
## Module Structure
|
||||
|
||||
The KVS module is organized into:
|
||||
|
||||
- `store.rs` - Core implementation of the key-value store
|
||||
- `error.rs` - Error types specific to the KVS module
|
||||
- `mod.rs` - Module exports and public interface
|
||||
|
||||
## Key Types
|
||||
|
||||
### KvStore
|
||||
|
||||
The `KvStore` type represents an encrypted key-value store:
|
||||
|
||||
```rust
|
||||
pub struct KvStore {
|
||||
// Private fields
|
||||
// ...
|
||||
}
|
||||
|
||||
impl KvStore {
|
||||
// Create a new store
|
||||
pub fn new(name: &str, password: &str) -> Result<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
|
||||
```
|
65
packages/crypt/vault/_archive/src/kvs/error.rs
Normal file
65
packages/crypt/vault/_archive/src/kvs/error.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
//! 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::error::CryptoError {
|
||||
fn from(err: KvsError) -> Self {
|
||||
crate::error::CryptoError::SerializationError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::error::CryptoError> for KvsError {
|
||||
fn from(err: crate::error::CryptoError) -> Self {
|
||||
match err {
|
||||
crate::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg),
|
||||
crate::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg),
|
||||
crate::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>;
|
14
packages/crypt/vault/_archive/src/kvs/mod.rs
Normal file
14
packages/crypt/vault/_archive/src/kvs/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! 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::{
|
||||
create_store, delete_store, get_store_path, list_stores, open_store, KvPair, KvStore,
|
||||
};
|
||||
|
||||
// Tests are now in the tests/ directory
|
370
packages/crypt/vault/_archive/src/kvs/store.rs
Normal file
370
packages/crypt/vault/_archive/src/kvs/store.rs
Normal file
@@ -0,0 +1,370 @@
|
||||
//! Implementation of a simple key-value store using the filesystem.
|
||||
|
||||
use crate::kvs::error::{KvsError, Result};
|
||||
use crate::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
|
||||
}
|
||||
}
|
23
packages/crypt/vault/_archive/src/lib.rs
Normal file
23
packages/crypt/vault/_archive/src/lib.rs
Normal 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};
|
1093
packages/crypt/vault/_archive/src/rhai.rs
Normal file
1093
packages/crypt/vault/_archive/src/rhai.rs
Normal file
File diff suppressed because it is too large
Load Diff
98
packages/crypt/vault/_archive/src/symmetric/README.md
Normal file
98
packages/crypt/vault/_archive/src/symmetric/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Hero Vault Symmetric Encryption Module
|
||||
|
||||
The Symmetric Encryption module provides functionality for symmetric encryption and decryption using the ChaCha20Poly1305 algorithm.
|
||||
|
||||
## Module Structure
|
||||
|
||||
The Symmetric Encryption module is organized into:
|
||||
|
||||
- `implementation.rs` - Core implementation of symmetric encryption functionality
|
||||
- `mod.rs` - Module exports and public interface
|
||||
|
||||
## Key Features
|
||||
|
||||
### Key Generation
|
||||
|
||||
The module provides functionality for generating secure symmetric keys:
|
||||
|
||||
```rust
|
||||
// Generate a new symmetric key
|
||||
let key = generate_key()?;
|
||||
```
|
||||
|
||||
### Encryption
|
||||
|
||||
The module provides functionality for encrypting data using ChaCha20Poly1305:
|
||||
|
||||
```rust
|
||||
// Encrypt data
|
||||
let encrypted = encrypt(&key, "This is a secret message")?;
|
||||
```
|
||||
|
||||
### Decryption
|
||||
|
||||
The module provides functionality for decrypting data encrypted with ChaCha20Poly1305:
|
||||
|
||||
```rust
|
||||
// Decrypt data
|
||||
let decrypted = decrypt(&key, &encrypted)?;
|
||||
```
|
||||
|
||||
### Password-Based Key Derivation
|
||||
|
||||
The module provides functionality for deriving encryption keys from passwords:
|
||||
|
||||
```rust
|
||||
// Derive a key from a password
|
||||
let key = derive_key_from_password(password, salt)?;
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### ChaCha20Poly1305
|
||||
|
||||
The module uses the ChaCha20Poly1305 authenticated encryption with associated data (AEAD) algorithm, which provides both confidentiality and integrity protection.
|
||||
|
||||
ChaCha20 is a stream cipher designed by Daniel J. Bernstein, which is combined with the Poly1305 message authentication code to provide authenticated encryption.
|
||||
|
||||
Key features of ChaCha20Poly1305:
|
||||
|
||||
- 256-bit key
|
||||
- 96-bit nonce (used once)
|
||||
- Authentication tag to verify integrity
|
||||
- High performance on modern processors
|
||||
- Resistance to timing attacks
|
||||
|
||||
### Key Derivation
|
||||
|
||||
For password-based encryption, the module uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to derive encryption keys from passwords.
|
||||
|
||||
Key features of PBKDF2:
|
||||
|
||||
- Configurable iteration count to increase computational cost
|
||||
- Salt to prevent rainbow table attacks
|
||||
- Configurable output key length
|
||||
- Uses HMAC-SHA256 as the underlying pseudorandom function
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Always use a unique key for each encryption operation
|
||||
- Never reuse nonces with the same key
|
||||
- Store keys securely
|
||||
- Use strong passwords for password-based encryption
|
||||
- Consider the security implications of storing encrypted data
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module uses the `CryptoError` type for handling errors that can occur during symmetric encryption operations:
|
||||
|
||||
- `InvalidKeyLength` - Invalid key length
|
||||
- `EncryptionFailed` - Encryption failed
|
||||
- `DecryptionFailed` - Decryption failed
|
||||
|
||||
## Examples
|
||||
|
||||
For examples of how to use the Symmetric Encryption module, see the `examples/hero_vault` directory, particularly:
|
||||
|
||||
- `example.rhai` - Basic example demonstrating symmetric encryption
|
||||
- `advanced_example.rhai` - Advanced example with error handling
|
278
packages/crypt/vault/_archive/src/symmetric/implementation.rs
Normal file
278
packages/crypt/vault/_archive/src/symmetric/implementation.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
//! Implementation of symmetric encryption functionality.
|
||||
|
||||
use chacha20poly1305::aead::Aead;
|
||||
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce};
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::error::CryptoError;
|
||||
use crate::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()))
|
||||
}
|
||||
}
|
||||
}
|
13
packages/crypt/vault/_archive/src/symmetric/mod.rs
Normal file
13
packages/crypt/vault/_archive/src/symmetric/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
//! Symmetric encryption functionality
|
||||
//!
|
||||
//! This module provides functionality for symmetric encryption using ChaCha20Poly1305.
|
||||
|
||||
pub mod implementation;
|
||||
|
||||
// Re-export public types and functions
|
||||
pub use implementation::{
|
||||
decrypt_key_space, decrypt_symmetric, decrypt_with_key, derive_key_from_password,
|
||||
deserialize_encrypted_space, encrypt_key_space, encrypt_symmetric, encrypt_with_key,
|
||||
generate_symmetric_key, serialize_encrypted_space, EncryptedKeySpace,
|
||||
EncryptedKeySpaceMetadata,
|
||||
};
|
121
packages/crypt/vault/_archive/tests/crypto_tests.rs
Normal file
121
packages/crypt/vault/_archive/tests/crypto_tests.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use sal_vault::error::CryptoError;
|
||||
use sal_vault::keyspace::{KeyPair, KeySpace};
|
||||
use sal_vault::symmetric::implementation::{
|
||||
decrypt_symmetric, encrypt_symmetric, generate_symmetric_key,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_symmetric_key_generation() {
|
||||
let key1 = generate_symmetric_key();
|
||||
let key2 = generate_symmetric_key();
|
||||
|
||||
// Keys should be different
|
||||
assert_ne!(key1, key2);
|
||||
|
||||
// Keys should be 32 bytes
|
||||
assert_eq!(key1.len(), 32);
|
||||
assert_eq!(key2.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symmetric_encryption_decryption() {
|
||||
let key = generate_symmetric_key();
|
||||
let message = b"Hello, World!";
|
||||
|
||||
// Encrypt the message
|
||||
let encrypted = encrypt_symmetric(&key, message).expect("Encryption should succeed");
|
||||
|
||||
// Encrypted data should be different from original
|
||||
assert_ne!(encrypted.as_slice(), message);
|
||||
|
||||
// Decrypt the message
|
||||
let decrypted = decrypt_symmetric(&key, &encrypted).expect("Decryption should succeed");
|
||||
|
||||
// Decrypted data should match original
|
||||
assert_eq!(decrypted.as_slice(), message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symmetric_encryption_with_wrong_key() {
|
||||
let key1 = generate_symmetric_key();
|
||||
let key2 = generate_symmetric_key();
|
||||
let message = b"Secret message";
|
||||
|
||||
// Encrypt with key1
|
||||
let encrypted = encrypt_symmetric(&key1, message).expect("Encryption should succeed");
|
||||
|
||||
// Try to decrypt with key2 (should fail)
|
||||
let result = decrypt_symmetric(&key2, &encrypted);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyspace_creation() {
|
||||
let mut keyspace = KeySpace::new("test_space");
|
||||
|
||||
assert_eq!(keyspace.name, "test_space");
|
||||
assert!(keyspace.keypairs.is_empty());
|
||||
|
||||
// Add a keypair
|
||||
keyspace
|
||||
.add_keypair("test_key")
|
||||
.expect("Adding keypair should succeed");
|
||||
|
||||
assert_eq!(keyspace.keypairs.len(), 1);
|
||||
assert!(keyspace.keypairs.contains_key("test_key"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keypair_creation() {
|
||||
let keypair = KeyPair::new("test_keypair");
|
||||
|
||||
// Test that we can get the public key
|
||||
let public_key = keypair.pub_key();
|
||||
assert!(!public_key.is_empty());
|
||||
|
||||
// Test signing and verification
|
||||
let message = b"test message";
|
||||
let signature = keypair.sign(message);
|
||||
|
||||
let is_valid = keypair
|
||||
.verify(message, &signature)
|
||||
.expect("Verification should succeed");
|
||||
assert!(is_valid);
|
||||
|
||||
// Test with wrong message
|
||||
let wrong_message = b"wrong message";
|
||||
let is_valid = keypair
|
||||
.verify(wrong_message, &signature)
|
||||
.expect("Verification should succeed");
|
||||
assert!(!is_valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyspace_serialization() {
|
||||
let mut keyspace = KeySpace::new("test_space");
|
||||
keyspace
|
||||
.add_keypair("test_key")
|
||||
.expect("Adding keypair should succeed");
|
||||
|
||||
// Serialize
|
||||
let serialized = serde_json::to_string(&keyspace).expect("Serialization should succeed");
|
||||
|
||||
// Deserialize
|
||||
let deserialized: KeySpace =
|
||||
serde_json::from_str(&serialized).expect("Deserialization should succeed");
|
||||
|
||||
assert_eq!(deserialized.name, keyspace.name);
|
||||
assert_eq!(deserialized.keypairs.len(), keyspace.keypairs.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_types() {
|
||||
let error = CryptoError::InvalidKeyLength;
|
||||
assert_eq!(error.to_string(), "Invalid key length");
|
||||
|
||||
let error = CryptoError::EncryptionFailed("test error".to_string());
|
||||
assert_eq!(error.to_string(), "Encryption failed: test error");
|
||||
|
||||
let error = CryptoError::KeypairNotFound("test_key".to_string());
|
||||
assert_eq!(error.to_string(), "Keypair not found: test_key");
|
||||
}
|
83
packages/crypt/vault/_archive/tests/rhai/basic_crypto.rhai
Normal file
83
packages/crypt/vault/_archive/tests/rhai/basic_crypto.rhai
Normal file
@@ -0,0 +1,83 @@
|
||||
// basic_crypto.rhai
|
||||
// Basic cryptographic operations test
|
||||
|
||||
print("=== Testing Basic Cryptographic Operations ===");
|
||||
|
||||
// Test symmetric encryption
|
||||
print("Testing symmetric encryption...");
|
||||
let key = generate_key();
|
||||
let message = "Hello, World!";
|
||||
|
||||
let encrypted = encrypt(key, message);
|
||||
let decrypted = decrypt(key, encrypted);
|
||||
|
||||
if decrypted != message {
|
||||
throw "Symmetric encryption/decryption failed";
|
||||
}
|
||||
print("✓ Symmetric encryption works correctly");
|
||||
|
||||
// Test keyspace creation
|
||||
print("Testing keyspace creation...");
|
||||
clear_session();
|
||||
|
||||
let created = create_key_space("test_space", "secure_password");
|
||||
if !created {
|
||||
throw "Failed to create keyspace";
|
||||
}
|
||||
print("✓ Keyspace created successfully");
|
||||
|
||||
// Test keyspace selection
|
||||
print("Testing keyspace selection...");
|
||||
let selected = select_keyspace("test_space");
|
||||
if !selected {
|
||||
throw "Failed to select keyspace";
|
||||
}
|
||||
print("✓ Keyspace selected successfully");
|
||||
|
||||
// Test keypair creation
|
||||
print("Testing keypair creation...");
|
||||
let keypair_created = create_keypair("test_keypair");
|
||||
if !keypair_created {
|
||||
throw "Failed to create keypair";
|
||||
}
|
||||
print("✓ Keypair created successfully");
|
||||
|
||||
// Test keypair selection
|
||||
print("Testing keypair selection...");
|
||||
let keypair_selected = select_keypair("test_keypair");
|
||||
if !keypair_selected {
|
||||
throw "Failed to select keypair";
|
||||
}
|
||||
print("✓ Keypair selected successfully");
|
||||
|
||||
// Test public key retrieval
|
||||
print("Testing public key retrieval...");
|
||||
let pub_key = keypair_pub_key();
|
||||
if pub_key == "" {
|
||||
throw "Failed to get public key";
|
||||
}
|
||||
print("✓ Public key retrieved: " + pub_key);
|
||||
|
||||
// Test signing and verification
|
||||
print("Testing digital signatures...");
|
||||
let test_message = "This is a test message for signing";
|
||||
let signature = sign(test_message);
|
||||
|
||||
if signature == "" {
|
||||
throw "Failed to sign message";
|
||||
}
|
||||
|
||||
let is_valid = verify(test_message, signature);
|
||||
if !is_valid {
|
||||
throw "Signature verification failed";
|
||||
}
|
||||
print("✓ Digital signature works correctly");
|
||||
|
||||
// Test with wrong message
|
||||
let wrong_valid = verify("Wrong message", signature);
|
||||
if wrong_valid {
|
||||
throw "Signature should not be valid for wrong message";
|
||||
}
|
||||
print("✓ Signature correctly rejects wrong message");
|
||||
|
||||
print("=== All basic crypto tests passed! ===");
|
@@ -0,0 +1,122 @@
|
||||
// keyspace_management.rhai
|
||||
// Advanced keyspace and keypair management test
|
||||
|
||||
print("=== Testing Keyspace Management ===");
|
||||
|
||||
// Clear any existing session
|
||||
clear_session();
|
||||
|
||||
// Test creating multiple keyspaces
|
||||
print("Creating multiple keyspaces...");
|
||||
let space1_created = create_key_space("personal", "personal_password");
|
||||
let space2_created = create_key_space("business", "business_password");
|
||||
let space3_created = create_key_space("testing", "testing_password");
|
||||
|
||||
if !space1_created || !space2_created || !space3_created {
|
||||
throw "Failed to create one or more keyspaces";
|
||||
}
|
||||
print("✓ Multiple keyspaces created successfully");
|
||||
|
||||
// Test listing keyspaces
|
||||
print("Testing keyspace listing...");
|
||||
let spaces = list_keyspaces();
|
||||
if spaces.len() < 3 {
|
||||
throw "Should have at least 3 keyspaces";
|
||||
}
|
||||
print("✓ Keyspaces listed: " + spaces.len() + " found");
|
||||
|
||||
// Test working with personal keyspace
|
||||
print("Working with personal keyspace...");
|
||||
select_keyspace("personal");
|
||||
|
||||
// Create multiple keypairs in personal space
|
||||
create_keypair("main_key");
|
||||
create_keypair("backup_key");
|
||||
create_keypair("signing_key");
|
||||
|
||||
let personal_keypairs = list_keypairs();
|
||||
if personal_keypairs.len() != 3 {
|
||||
throw "Personal keyspace should have 3 keypairs";
|
||||
}
|
||||
print("✓ Personal keyspace has " + personal_keypairs.len() + " keypairs");
|
||||
|
||||
// Test working with business keyspace
|
||||
print("Working with business keyspace...");
|
||||
select_keyspace("business");
|
||||
|
||||
// Create keypairs in business space
|
||||
create_keypair("company_key");
|
||||
create_keypair("contract_key");
|
||||
|
||||
let business_keypairs = list_keypairs();
|
||||
if business_keypairs.len() != 2 {
|
||||
throw "Business keyspace should have 2 keypairs";
|
||||
}
|
||||
print("✓ Business keyspace has " + business_keypairs.len() + " keypairs");
|
||||
|
||||
// Test switching between keypairs
|
||||
print("Testing keypair switching...");
|
||||
select_keypair("company_key");
|
||||
let company_pubkey = keypair_pub_key();
|
||||
|
||||
select_keypair("contract_key");
|
||||
let contract_pubkey = keypair_pub_key();
|
||||
|
||||
if company_pubkey == contract_pubkey {
|
||||
throw "Different keypairs should have different public keys";
|
||||
}
|
||||
print("✓ Keypair switching works correctly");
|
||||
|
||||
// Test signing with different keypairs
|
||||
print("Testing signatures with different keypairs...");
|
||||
let message = "Business contract data";
|
||||
|
||||
select_keypair("company_key");
|
||||
let company_signature = sign(message);
|
||||
|
||||
select_keypair("contract_key");
|
||||
let contract_signature = sign(message);
|
||||
|
||||
if company_signature == contract_signature {
|
||||
throw "Different keypairs should produce different signatures";
|
||||
}
|
||||
print("✓ Different keypairs produce different signatures");
|
||||
|
||||
// Test cross-verification (should fail)
|
||||
select_keypair("company_key");
|
||||
let company_valid = verify(message, contract_signature);
|
||||
if company_valid {
|
||||
throw "Company key should not verify contract key signature";
|
||||
}
|
||||
print("✓ Cross-verification correctly fails");
|
||||
|
||||
// Test correct verification
|
||||
let correct_valid = verify(message, company_signature);
|
||||
if !correct_valid {
|
||||
throw "Company key should verify its own signature";
|
||||
}
|
||||
print("✓ Self-verification works correctly");
|
||||
|
||||
// Test session isolation
|
||||
print("Testing session isolation...");
|
||||
select_keyspace("testing");
|
||||
let testing_keypairs = list_keypairs();
|
||||
if testing_keypairs.len() != 0 {
|
||||
throw "Testing keyspace should be empty";
|
||||
}
|
||||
print("✓ Keyspaces are properly isolated");
|
||||
|
||||
// Test error handling
|
||||
print("Testing error handling...");
|
||||
let invalid_select = select_keyspace("non_existent");
|
||||
if invalid_select {
|
||||
throw "Should not be able to select non-existent keyspace";
|
||||
}
|
||||
|
||||
let invalid_keypair = select_keypair("non_existent");
|
||||
if invalid_keypair {
|
||||
throw "Should not be able to select non-existent keypair";
|
||||
}
|
||||
print("✓ Error handling works correctly");
|
||||
|
||||
print("=== All keyspace management tests passed! ===");
|
243
packages/crypt/vault/_archive/tests/rhai_integration_tests.rs
Normal file
243
packages/crypt/vault/_archive/tests/rhai_integration_tests.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use sal_vault::rhai::*;
|
||||
use std::sync::Mutex;
|
||||
|
||||
// NOTE: These tests use global state (SESSION and KEYSPACE_REGISTRY) and are automatically
|
||||
// serialized using a global mutex to prevent test interference during parallel execution.
|
||||
|
||||
// Global test mutex to ensure tests run sequentially
|
||||
static TEST_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
#[cfg(test)]
|
||||
mod rhai_integration_tests {
|
||||
use super::*;
|
||||
|
||||
fn create_test_engine() -> Engine {
|
||||
let mut engine = Engine::new();
|
||||
register_crypto_module(&mut engine).expect("Failed to register crypto module");
|
||||
engine
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rhai_module_registration() {
|
||||
let _guard = TEST_MUTEX.lock().unwrap();
|
||||
let engine = create_test_engine();
|
||||
|
||||
// Test that the functions are registered by checking if they exist
|
||||
let script = r#"
|
||||
// Test that all crypto functions are available
|
||||
let functions_exist = true;
|
||||
|
||||
// We can't actually call these without proper setup, but we can verify they're registered
|
||||
// by checking that the engine doesn't throw "function not found" errors
|
||||
functions_exist
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symmetric_encryption_functions() {
|
||||
let _guard = TEST_MUTEX.lock().unwrap();
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
// Test symmetric encryption functions
|
||||
let key = generate_key();
|
||||
let message = "Hello, World!";
|
||||
|
||||
let encrypted = encrypt(key, message);
|
||||
let decrypted = decrypt(key, encrypted);
|
||||
|
||||
decrypted == message
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyspace_functions() {
|
||||
let _guard = TEST_MUTEX.lock().unwrap();
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
// Test keyspace functions
|
||||
clear_session();
|
||||
|
||||
let created = create_key_space("test_space", "password123");
|
||||
if !created {
|
||||
throw "Failed to create key space";
|
||||
}
|
||||
|
||||
let selected = select_keyspace("test_space");
|
||||
if !selected {
|
||||
throw "Failed to select keyspace";
|
||||
}
|
||||
|
||||
true
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keypair_functions() {
|
||||
let _guard = TEST_MUTEX.lock().unwrap();
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
// Test keypair functions
|
||||
clear_session();
|
||||
|
||||
// Create and select keyspace
|
||||
create_key_space("test_space", "password123");
|
||||
select_keyspace("test_space");
|
||||
|
||||
// Create keypair
|
||||
let created = create_keypair("test_keypair");
|
||||
if !created {
|
||||
throw "Failed to create keypair";
|
||||
}
|
||||
|
||||
// Select keypair
|
||||
let selected = select_keypair("test_keypair");
|
||||
if !selected {
|
||||
throw "Failed to select keypair";
|
||||
}
|
||||
|
||||
// Get public key
|
||||
let pub_key = keypair_pub_key();
|
||||
if pub_key == "" {
|
||||
throw "Failed to get public key";
|
||||
}
|
||||
|
||||
true
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signing_functions() {
|
||||
let _guard = TEST_MUTEX.lock().unwrap();
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
// Test signing and verification functions
|
||||
clear_session();
|
||||
|
||||
// Setup keyspace and keypair
|
||||
create_key_space("test_space", "password123");
|
||||
select_keyspace("test_space");
|
||||
create_keypair("test_keypair");
|
||||
select_keypair("test_keypair");
|
||||
|
||||
// Test signing and verification
|
||||
let message = "test message";
|
||||
let signature = sign(message);
|
||||
|
||||
if signature == "" {
|
||||
throw "Failed to sign message";
|
||||
}
|
||||
|
||||
let is_valid = verify(message, signature);
|
||||
if !is_valid {
|
||||
throw "Signature verification failed";
|
||||
}
|
||||
|
||||
// Test with wrong message
|
||||
let wrong_is_valid = verify("wrong message", signature);
|
||||
if wrong_is_valid {
|
||||
throw "Signature should not be valid for wrong message";
|
||||
}
|
||||
|
||||
true
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_session_management() {
|
||||
let _guard = TEST_MUTEX.lock().unwrap();
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
// Test session management
|
||||
clear_session();
|
||||
|
||||
// Create multiple keyspaces
|
||||
create_key_space("space1", "password1");
|
||||
create_key_space("space2", "password2");
|
||||
|
||||
// Test listing keyspaces
|
||||
let spaces = list_keyspaces();
|
||||
let space_count = count_keyspaces();
|
||||
if space_count < 2 {
|
||||
throw "Should have at least 2 keyspaces";
|
||||
}
|
||||
|
||||
// Test selecting different keyspaces
|
||||
select_keyspace("space1");
|
||||
create_keypair("keypair1");
|
||||
|
||||
select_keyspace("space2");
|
||||
create_keypair("keypair2");
|
||||
|
||||
// Test listing keypairs in current space
|
||||
let keypairs = list_keypairs();
|
||||
let keypair_count = count_keypairs();
|
||||
if keypair_count != 1 {
|
||||
throw "Should have exactly 1 keypair in space2";
|
||||
}
|
||||
|
||||
true
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
if let Err(ref e) = result {
|
||||
println!("Script error: {}", e);
|
||||
}
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_handling() {
|
||||
let _guard = TEST_MUTEX.lock().unwrap();
|
||||
let engine = create_test_engine();
|
||||
|
||||
let script = r#"
|
||||
// Test error handling
|
||||
clear_session();
|
||||
|
||||
// Try to select non-existent keyspace
|
||||
let selected = select_keyspace("non_existent");
|
||||
if selected {
|
||||
throw "Should not be able to select non-existent keyspace";
|
||||
}
|
||||
|
||||
// Try to create keypair without keyspace
|
||||
let created = create_keypair("test_keypair");
|
||||
if created {
|
||||
throw "Should not be able to create keypair without keyspace";
|
||||
}
|
||||
|
||||
true
|
||||
"#;
|
||||
|
||||
let result: Result<bool, Box<EvalAltResult>> = engine.eval(script);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), true);
|
||||
}
|
||||
}
|
160
packages/crypt/vault/src/README.md
Normal file
160
packages/crypt/vault/src/README.md
Normal 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.
|
109
packages/crypt/vault/src/error.rs
Normal file
109
packages/crypt/vault/src/error.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
#[derive(Debug)]
|
||||
/// Errors encountered while using the vault
|
||||
pub enum Error {
|
||||
/// An error during cryptographic operations
|
||||
Crypto(CryptoError),
|
||||
/// An error while performing an I/O operation
|
||||
IOError(std::io::Error),
|
||||
/// A corrupt keyspace is returned if a keyspace can't be decrypted
|
||||
CorruptKeyspace,
|
||||
/// An error in the used key value store (temporarily disabled)
|
||||
// KV(kv::error::KVError),
|
||||
/// An error while encoding/decoding the keyspace.
|
||||
Coding,
|
||||
}
|
||||
|
||||
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
packages/crypt/vault/src/key.rs
Normal file
83
packages/crypt/vault/src/key.rs
Normal 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
packages/crypt/vault/src/key/asymmetric.rs
Normal file
161
packages/crypt/vault/src/key/asymmetric.rs
Normal 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
packages/crypt/vault/src/key/signature.rs
Normal file
142
packages/crypt/vault/src/key/signature.rs
Normal 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
packages/crypt/vault/src/key/symmetric.rs
Normal file
151
packages/crypt/vault/src/key/symmetric.rs
Normal 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
packages/crypt/vault/src/keyspace.rs
Normal file
151
packages/crypt/vault/src/keyspace.rs
Normal 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(())
|
||||
}
|
||||
}
|
72
packages/crypt/vault/src/keyspace/fallback.rs
Normal file
72
packages/crypt/vault/src/keyspace/fallback.rs
Normal 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!()
|
||||
}
|
||||
}
|
26
packages/crypt/vault/src/keyspace/wasm.rs
Normal file
26
packages/crypt/vault/src/keyspace/wasm.rs
Normal 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!()
|
||||
}
|
||||
}
|
51
packages/crypt/vault/src/lib.rs
Normal file
51
packages/crypt/vault/src/lib.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
pub mod error;
|
||||
pub mod key;
|
||||
pub mod keyspace;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::{error::Error, key::symmetric::SymmetricKey, keyspace::KeySpace};
|
||||
|
||||
/// Vault is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where
|
||||
/// each [`space`](KeySpace) is itself an encrypted key-value store
|
||||
pub struct Vault {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Vault {
|
||||
/// Create a new store at the given path, creating the path if it does not exist yet.
|
||||
pub async fn new(path: &Path) -> Result<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
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user