From 6dead402a27e978870298faf48b950419723b383 Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Mon, 23 Jun 2025 14:56:03 +0300 Subject: [PATCH] feat: Remove herodo from monorepo and update dependencies - Removed the `herodo` binary from the monorepo. This was done as part of the monorepo conversion process. - Updated the `Cargo.toml` file to reflect the removal of `herodo` and adjust dependencies accordingly. - Updated `src/lib.rs` and `src/rhai/mod.rs` to use the new `sal-vault` crate for vault functionality. This improves the modularity and maintainability of the project. --- Cargo.toml | 5 +- src/lib.rs | 2 +- src/rhai/mod.rs | 6 +- src/vault/README.md | 160 ------------ src/vault/error.rs | 58 ----- src/vault/keyspace/mod.rs | 18 -- .../keyspace/tests/keypair_types_tests.rs | 98 -------- src/vault/keyspace/tests/mod.rs | 3 - .../keyspace/tests/session_manager_tests.rs | 112 --------- src/vault/kvs/tests/mod.rs | 1 - src/vault/kvs/tests/store_tests.rs | 104 -------- src/vault/mod.rs | 20 -- vault/.cargo/config.toml | 2 - vault/Cargo.toml | 59 +++-- vault/README.md | 155 ++++++++++++ vault/src/error.rs | 152 ++++-------- {src/vault => vault/src}/ethereum/README.md | 0 {src/vault => vault/src}/ethereum/contract.rs | 80 +++--- .../src}/ethereum/contract_utils.rs | 0 {src/vault => vault/src}/ethereum/mod.rs | 0 {src/vault => vault/src}/ethereum/networks.rs | 0 {src/vault => vault/src}/ethereum/provider.rs | 10 +- {src/vault => vault/src}/ethereum/storage.rs | 77 +++--- .../ethereum/tests/contract_args_tests.rs | 0 .../src}/ethereum/tests/contract_tests.rs | 0 .../vault => vault/src}/ethereum/tests/mod.rs | 0 .../src}/ethereum/tests/network_tests.rs | 0 .../src}/ethereum/tests/transaction_tests.rs | 0 .../src}/ethereum/tests/wallet_tests.rs | 0 .../src}/ethereum/transaction.rs | 38 ++- {src/vault => vault/src}/ethereum/wallet.rs | 6 +- vault/src/key.rs | 83 ------- vault/src/key/asymmetric.rs | 161 ------------- vault/src/key/signature.rs | 142 ----------- vault/src/key/symmetric.rs | 151 ------------ vault/src/keyspace.rs | 131 ---------- {src/vault => vault/src}/keyspace/README.md | 0 vault/src/keyspace/fallback.rs | 72 ------ .../src}/keyspace/keypair_types.rs | 4 +- vault/src/keyspace/mod.rs | 16 ++ .../src}/keyspace/session_manager.rs | 4 +- {src/vault => vault/src}/keyspace/spec.md | 0 vault/src/keyspace/wasm.rs | 26 -- {src/vault => vault/src}/kvs/README.md | 0 {src/vault => vault/src}/kvs/error.rs | 16 +- {src/vault => vault/src}/kvs/mod.rs | 7 +- {src/vault => vault/src}/kvs/store.rs | 4 +- vault/src/lib.rs | 66 ++--- src/rhai/vault.rs => vault/src/rhai.rs | 69 ++++-- {src/vault => vault/src}/symmetric/README.md | 0 .../src}/symmetric/implementation.rs | 74 +++--- {src/vault => vault/src}/symmetric/mod.rs | 0 vault/tests/crypto_tests.rs | 121 ++++++++++ vault/tests/rhai/basic_crypto.rhai | 83 +++++++ vault/tests/rhai/keyspace_management.rhai | 122 ++++++++++ vault/tests/rhai_integration_tests.rs | 227 ++++++++++++++++++ 56 files changed, 1074 insertions(+), 1671 deletions(-) delete mode 100644 src/vault/README.md delete mode 100644 src/vault/error.rs delete mode 100644 src/vault/keyspace/mod.rs delete mode 100644 src/vault/keyspace/tests/keypair_types_tests.rs delete mode 100644 src/vault/keyspace/tests/mod.rs delete mode 100644 src/vault/keyspace/tests/session_manager_tests.rs delete mode 100644 src/vault/kvs/tests/mod.rs delete mode 100644 src/vault/kvs/tests/store_tests.rs delete mode 100644 src/vault/mod.rs delete mode 100644 vault/.cargo/config.toml create mode 100644 vault/README.md rename {src/vault => vault/src}/ethereum/README.md (100%) rename {src/vault => vault/src}/ethereum/contract.rs (72%) rename {src/vault => vault/src}/ethereum/contract_utils.rs (100%) rename {src/vault => vault/src}/ethereum/mod.rs (100%) rename {src/vault => vault/src}/ethereum/networks.rs (100%) rename {src/vault => vault/src}/ethereum/provider.rs (74%) rename {src/vault => vault/src}/ethereum/storage.rs (73%) rename {src/vault => vault/src}/ethereum/tests/contract_args_tests.rs (100%) rename {src/vault => vault/src}/ethereum/tests/contract_tests.rs (100%) rename {src/vault => vault/src}/ethereum/tests/mod.rs (100%) rename {src/vault => vault/src}/ethereum/tests/network_tests.rs (100%) rename {src/vault => vault/src}/ethereum/tests/transaction_tests.rs (100%) rename {src/vault => vault/src}/ethereum/tests/wallet_tests.rs (100%) rename {src/vault => vault/src}/ethereum/transaction.rs (67%) rename {src/vault => vault/src}/ethereum/wallet.rs (96%) delete mode 100644 vault/src/key.rs delete mode 100644 vault/src/key/asymmetric.rs delete mode 100644 vault/src/key/signature.rs delete mode 100644 vault/src/key/symmetric.rs delete mode 100644 vault/src/keyspace.rs rename {src/vault => vault/src}/keyspace/README.md (100%) delete mode 100644 vault/src/keyspace/fallback.rs rename {src/vault => vault/src}/keyspace/keypair_types.rs (99%) create mode 100644 vault/src/keyspace/mod.rs rename {src/vault => vault/src}/keyspace/session_manager.rs (96%) rename {src/vault => vault/src}/keyspace/spec.md (100%) delete mode 100644 vault/src/keyspace/wasm.rs rename {src/vault => vault/src}/kvs/README.md (100%) rename {src/vault => vault/src}/kvs/error.rs (68%) rename {src/vault => vault/src}/kvs/mod.rs (63%) rename {src/vault => vault/src}/kvs/store.rs (99%) rename src/rhai/vault.rs => vault/src/rhai.rs (93%) rename {src/vault => vault/src}/symmetric/README.md (100%) rename {src/vault => vault/src}/symmetric/implementation.rs (88%) rename {src/vault => vault/src}/symmetric/mod.rs (100%) create mode 100644 vault/tests/crypto_tests.rs create mode 100644 vault/tests/rhai/basic_crypto.rhai create mode 100644 vault/tests/rhai/keyspace_management.rhai create mode 100644 vault/tests/rhai_integration_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 3259183..80ebba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ categories = ["os", "filesystem", "api-bindings"] readme = "README.md" [workspace] -members = [".", "vault", "git", "redisclient", "mycelium", "text", "os", "net", "zinit_client", "process", "virt", "postgresclient", "herodo"] +members = [".", "vault", "git", "redisclient", "mycelium", "text", "os", "net", "zinit_client", "process", "virt", "postgresclient"] [dependencies] hex = "0.4" @@ -69,6 +69,7 @@ sal-zinit-client = { path = "zinit_client" } sal-process = { path = "process" } sal-virt = { path = "virt" } sal-postgresclient = { path = "postgresclient" } +sal-vault = { path = "vault" } # Optional features for specific OS functionality [target.'cfg(unix)'.dependencies] @@ -89,4 +90,4 @@ tokio = { version = "1.28", features = [ "test-util", ] } # For async testing - +# herodo binary removed during monorepo conversion diff --git a/src/lib.rs b/src/lib.rs index e195cc0..d5775f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ pub use sal_process as process; pub use sal_redisclient as redisclient; pub mod rhai; pub use sal_text as text; -pub mod vault; +pub use sal_vault as vault; pub use sal_virt as virt; pub use sal_zinit_client as zinit_client; diff --git a/src/rhai/mod.rs b/src/rhai/mod.rs index ed4557a..55a0265 100644 --- a/src/rhai/mod.rs +++ b/src/rhai/mod.rs @@ -10,7 +10,7 @@ pub mod error; // PostgreSQL module is now provided by sal-postgresclient package // Virt modules (buildah, nerdctl, rfs) are now provided by sal-virt package -mod vault; +// vault module is now provided by sal-vault package // zinit module is now in sal-zinit-client package #[cfg(test)] @@ -97,7 +97,7 @@ pub use sal_text::rhai::register_text_module; pub use sal_net::rhai::register_net_module; // Re-export crypto module -pub use vault::register_crypto_module; +pub use sal_vault::rhai::register_crypto_module; // Rename copy functions to avoid conflicts pub use sal_os::rhai::copy as os_copy; @@ -152,7 +152,7 @@ pub fn register(engine: &mut Engine) -> Result<(), Box> { // RFS module functions are now registered as part of sal_virt above // Register Crypto module functions - vault::register_crypto_module(engine)?; + register_crypto_module(engine)?; // Register Redis client module functions sal_redisclient::rhai::register_redisclient_module(engine)?; diff --git a/src/vault/README.md b/src/vault/README.md deleted file mode 100644 index 28a3f1b..0000000 --- a/src/vault/README.md +++ /dev/null @@ -1,160 +0,0 @@ -# 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. diff --git a/src/vault/error.rs b/src/vault/error.rs deleted file mode 100644 index 2cf41f1..0000000 --- a/src/vault/error.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! 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), -} - -/// Convert CryptoError to SAL's Error type -impl From for crate::Error { - fn from(err: CryptoError) -> Self { - crate::Error::Sal(err.to_string()) - } -} diff --git a/src/vault/keyspace/mod.rs b/src/vault/keyspace/mod.rs deleted file mode 100644 index d9ea317..0000000 --- a/src/vault/keyspace/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Key pair management functionality -//! -//! This module provides functionality for creating and managing ECDSA key pairs. - -pub mod keypair_types; -pub mod session_manager; - -// Re-export public types and functions -pub use keypair_types::{KeyPair, KeySpace}; -pub use session_manager::{ - create_space, set_current_space, get_current_space, clear_session, - create_keypair, select_keypair, get_selected_keypair, list_keypairs, - keypair_pub_key, derive_public_key, keypair_sign, keypair_verify, - verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric -}; - -#[cfg(test)] -mod tests; diff --git a/src/vault/keyspace/tests/keypair_types_tests.rs b/src/vault/keyspace/tests/keypair_types_tests.rs deleted file mode 100644 index 1511dd8..0000000 --- a/src/vault/keyspace/tests/keypair_types_tests.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::vault::keyspace::keypair_types::{KeyPair, KeySpace}; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_keypair_creation() { - let keypair = KeyPair::new("test_keypair"); - assert_eq!(keypair.name, "test_keypair"); - // Basic check that keys are generated (they should have non-zero length) - assert!(!keypair.pub_key().is_empty()); - } - - #[test] - fn test_keypair_sign_and_verify() { - let keypair = KeyPair::new("test_keypair"); - let message = b"This is a test message"; - let signature = keypair.sign(message); - assert!(!signature.is_empty()); - - let is_valid = keypair - .verify(message, &signature) - .expect("Verification failed"); - assert!(is_valid); - - // Test with a wrong message - let wrong_message = b"This is a different message"; - let is_valid_wrong = keypair - .verify(wrong_message, &signature) - .expect("Verification failed with wrong message"); - assert!(!is_valid_wrong); - } - - #[test] - fn test_verify_with_public_key() { - let keypair = KeyPair::new("test_keypair"); - let message = b"Another test message"; - let signature = keypair.sign(message); - let public_key = keypair.pub_key(); - - let is_valid = KeyPair::verify_with_public_key(&public_key, message, &signature) - .expect("Verification with public key failed"); - assert!(is_valid); - - // Test with a wrong public key - let wrong_keypair = KeyPair::new("wrong_keypair"); - let wrong_public_key = wrong_keypair.pub_key(); - let is_valid_wrong_key = - KeyPair::verify_with_public_key(&wrong_public_key, message, &signature) - .expect("Verification with wrong public key failed"); - assert!(!is_valid_wrong_key); - } - - #[test] - fn test_asymmetric_encryption_decryption() { - // Sender's keypair - let sender_keypair = KeyPair::new("sender"); - let _ = sender_keypair.pub_key(); - - // Recipient's keypair - let recipient_keypair = KeyPair::new("recipient"); - let recipient_public_key = recipient_keypair.pub_key(); - - let message = b"This is a secret message"; - - // Sender encrypts for recipient - let ciphertext = sender_keypair - .encrypt_asymmetric(&recipient_public_key, message) - .expect("Encryption failed"); - assert!(!ciphertext.is_empty()); - - // Recipient decrypts - let decrypted_message = recipient_keypair - .decrypt_asymmetric(&ciphertext) - .expect("Decryption failed"); - assert_eq!(decrypted_message, message); - - // Test decryption with wrong keypair - let wrong_keypair = KeyPair::new("wrong_recipient"); - let result = wrong_keypair.decrypt_asymmetric(&ciphertext); - assert!(result.is_err()); - } - - #[test] - fn test_keyspace_add_keypair() { - let mut space = KeySpace::new("test_space"); - space - .add_keypair("keypair1") - .expect("Failed to add keypair1"); - assert_eq!(space.keypairs.len(), 1); - assert!(space.keypairs.contains_key("keypair1")); - - // Test adding a duplicate keypair - let result = space.add_keypair("keypair1"); - assert!(result.is_err()); - } -} diff --git a/src/vault/keyspace/tests/mod.rs b/src/vault/keyspace/tests/mod.rs deleted file mode 100644 index 770d0e5..0000000 --- a/src/vault/keyspace/tests/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ - -mod keypair_types_tests; -mod session_manager_tests; \ No newline at end of file diff --git a/src/vault/keyspace/tests/session_manager_tests.rs b/src/vault/keyspace/tests/session_manager_tests.rs deleted file mode 100644 index 621d9d3..0000000 --- a/src/vault/keyspace/tests/session_manager_tests.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::vault::keyspace::keypair_types::KeySpace; -use crate::vault::keyspace::session_manager::{ - clear_session, create_keypair, create_space, get_current_space, get_selected_keypair, - list_keypairs, select_keypair, set_current_space, -}; - -// Helper function to clear the session before each test -fn setup_test() { - clear_session(); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_create_and_get_space() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - let space = get_current_space().expect("Failed to get current space"); - assert_eq!(space.name, "test_space"); - } - - #[test] - fn test_set_current_space() { - setup_test(); - let space = KeySpace::new("another_space"); - set_current_space(space.clone()).expect("Failed to set current space"); - let current_space = get_current_space().expect("Failed to get current space"); - assert_eq!(current_space.name, "another_space"); - } - - #[test] - fn test_clear_session() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - clear_session(); - let result = get_current_space(); - assert!(result.is_err()); - } - - #[test] - fn test_create_and_select_keypair() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - create_keypair("test_keypair").expect("Failed to create keypair"); - let keypair = get_selected_keypair().expect("Failed to get selected keypair"); - assert_eq!(keypair.name, "test_keypair"); - - select_keypair("test_keypair").expect("Failed to select keypair"); - let selected_keypair = - get_selected_keypair().expect("Failed to get selected keypair after select"); - assert_eq!(selected_keypair.name, "test_keypair"); - } - - #[test] - fn test_list_keypairs() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - create_keypair("keypair1").expect("Failed to create keypair1"); - create_keypair("keypair2").expect("Failed to create keypair2"); - - let keypairs = list_keypairs().expect("Failed to list keypairs"); - assert_eq!(keypairs.len(), 2); - assert!(keypairs.contains(&"keypair1".to_string())); - assert!(keypairs.contains(&"keypair2".to_string())); - } - - #[test] - fn test_create_keypair_no_active_space() { - setup_test(); - let result = create_keypair("test_keypair"); - assert!(result.is_err()); - } - - #[test] - fn test_select_keypair_no_active_space() { - setup_test(); - let result = select_keypair("test_keypair"); - assert!(result.is_err()); - } - - #[test] - fn test_select_nonexistent_keypair() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - let result = select_keypair("nonexistent_keypair"); - assert!(result.is_err()); - } - - #[test] - fn test_get_selected_keypair_no_active_space() { - setup_test(); - let result = get_selected_keypair(); - assert!(result.is_err()); - } - - #[test] - fn test_get_selected_keypair_no_keypair_selected() { - setup_test(); - create_space("test_space").expect("Failed to create space"); - let result = get_selected_keypair(); - assert!(result.is_err()); - } - - #[test] - fn test_list_keypairs_no_active_space() { - setup_test(); - let result = list_keypairs(); - assert!(result.is_err()); - } -} diff --git a/src/vault/kvs/tests/mod.rs b/src/vault/kvs/tests/mod.rs deleted file mode 100644 index 668dbed..0000000 --- a/src/vault/kvs/tests/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod store_tests; \ No newline at end of file diff --git a/src/vault/kvs/tests/store_tests.rs b/src/vault/kvs/tests/store_tests.rs deleted file mode 100644 index a978bc3..0000000 --- a/src/vault/kvs/tests/store_tests.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::vault::kvs::store::{create_store, delete_store, open_store}; - -// Helper function to generate a unique store name for each test -fn generate_test_store_name() -> String { - use rand::Rng; - let random_string: String = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(10) - .map(char::from) - .collect(); - format!("test_store_{}", random_string) -} - -// Helper function to clean up test stores -fn cleanup_test_store(name: &str) { - let _ = delete_store(name); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_create_and_open_store() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - assert_eq!(store.name(), store_name); - assert!(!store.is_encrypted()); - - let opened_store = open_store(&store_name, None).expect("Failed to open store"); - assert_eq!(opened_store.name(), store_name); - assert!(!opened_store.is_encrypted()); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_set_and_get_value() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - let value: String = store.get("key1").expect("Failed to get value"); - assert_eq!(value, "value1"); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_delete_value() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - store.delete("key1").expect("Failed to delete value"); - let result: Result = store.get("key1"); - assert!(result.is_err()); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_contains_key() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - assert!(store.contains("key1").expect("Failed to check contains")); - assert!(!store.contains("key2").expect("Failed to check contains")); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_list_keys() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - store.set("key2", &"value2").expect("Failed to set value"); - - let keys = store.keys().expect("Failed to list keys"); - assert_eq!(keys.len(), 2); - assert!(keys.contains(&"key1".to_string())); - assert!(keys.contains(&"key2".to_string())); - - cleanup_test_store(&store_name); - } - - #[test] - fn test_clear_store() { - let store_name = generate_test_store_name(); - let store = create_store(&store_name, false, None).expect("Failed to create store"); - - store.set("key1", &"value1").expect("Failed to set value"); - store.set("key2", &"value2").expect("Failed to set value"); - - store.clear().expect("Failed to clear store"); - let keys = store.keys().expect("Failed to list keys after clear"); - assert!(keys.is_empty()); - - cleanup_test_store(&store_name); - } -} diff --git a/src/vault/mod.rs b/src/vault/mod.rs deleted file mode 100644 index b97a574..0000000 --- a/src/vault/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Hero Vault: Cryptographic functionality for SAL -//! -//! This module 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 keyspace; -pub mod symmetric; -pub mod ethereum; -pub mod kvs; - -// Re-export modules -// Re-export common types for convenience -pub use error::CryptoError; -pub use keyspace::{KeyPair, KeySpace}; diff --git a/vault/.cargo/config.toml b/vault/.cargo/config.toml deleted file mode 100644 index 2e07606..0000000 --- a/vault/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.wasm32-unknown-unknown] -rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] diff --git a/vault/Cargo.toml b/vault/Cargo.toml index c8b92a8..494c54b 100644 --- a/vault/Cargo.toml +++ b/vault/Cargo.toml @@ -1,22 +1,47 @@ [package] -name = "vault" +name = "sal-vault" version = "0.1.0" -edition = "2024" - -[features] -native = ["kv/native"] -wasm = ["kv/web"] +edition = "2021" +authors = ["PlanetFirst "] +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] -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" +# Core cryptographic dependencies chacha20poly1305 = "0.10.1" -k256 = { version = "0.13.4", features = ["ecdh"] } -sha2 = "0.10.9" -kv = { git = "https://git.threefold.info/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" } -bincode = { version = "2.0.1", features = ["serde"] } -pbkdf2 = "0.12.2" +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" diff --git a/vault/README.md b/vault/README.md new file mode 100644 index 0000000..6634688 --- /dev/null +++ b/vault/README.md @@ -0,0 +1,155 @@ +# 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. + +## 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 +``` + +## 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. diff --git a/vault/src/error.rs b/vault/src/error.rs index 87ff0d4..d329a4f 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -1,109 +1,53 @@ -#[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 - KV(kv::error::KVError), - /// An error while encoding/decoding the keyspace. - Coding, -} +//! Error types for cryptographic operations -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"), - } - } -} +use thiserror::Error; -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. +/// Errors that can occur during cryptographic operations +#[derive(Error, Debug)] 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, + /// 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), } -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 for Error { - fn from(value: CryptoError) -> Self { - Self::Crypto(value) - } -} - -impl From for Error { - fn from(value: std::io::Error) -> Self { - Self::IOError(value) - } -} - -impl From for Error { - fn from(value: kv::error::KVError) -> Self { - Self::KV(value) - } -} - -impl From for Error { - fn from(_: bincode::error::DecodeError) -> Self { - Self::Coding - } -} - -impl From for Error { - fn from(_: bincode::error::EncodeError) -> Self { - Self::Coding - } -} - -impl From for CryptoError { - fn from(_: k256::ecdsa::Error) -> Self { - Self::InvalidKey - } -} - -impl From for CryptoError { - fn from(_: k256::elliptic_curve::Error) -> Self { - Self::InvalidKey - } -} +// Note: Error conversion to main SAL crate will be handled at the integration level diff --git a/src/vault/ethereum/README.md b/vault/src/ethereum/README.md similarity index 100% rename from src/vault/ethereum/README.md rename to vault/src/ethereum/README.md diff --git a/src/vault/ethereum/contract.rs b/vault/src/ethereum/contract.rs similarity index 72% rename from src/vault/ethereum/contract.rs rename to vault/src/ethereum/contract.rs index 5e8749e..d9102ee 100644 --- a/src/vault/ethereum/contract.rs +++ b/vault/src/ethereum/contract.rs @@ -2,15 +2,15 @@ //! //! This module provides functionality for interacting with smart contracts on EVM-based blockchains. -use ethers::prelude::*; use ethers::abi::{Abi, Token}; -use std::sync::Arc; +use ethers::prelude::*; +use serde::{Deserialize, Serialize}; use std::str::FromStr; -use serde::{Serialize, Deserialize}; +use std::sync::Arc; -use crate::vault::error::CryptoError; -use super::wallet::EthereumWallet; use super::networks::NetworkConfig; +use super::wallet::EthereumWallet; +use crate::error::CryptoError; /// A smart contract instance. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -34,7 +34,11 @@ impl Contract { } /// Creates a new contract instance from an address string and ABI. - pub fn from_address_string(address_str: &str, abi: Abi, network: NetworkConfig) -> Result { + pub fn from_address_string( + address_str: &str, + abi: Abi, + network: NetworkConfig, + ) -> Result { let address = Address::from_str(address_str) .map_err(|e| CryptoError::InvalidAddress(format!("Invalid address format: {}", e)))?; @@ -42,12 +46,13 @@ impl Contract { } /// Creates an ethers Contract instance for interaction. - pub fn create_ethers_contract(&self, provider: Provider, _wallet: Option<&EthereumWallet>) -> Result>, CryptoError> { - let contract = ethers::contract::Contract::new( - self.address, - self.abi.clone(), - Arc::new(provider), - ); + pub fn create_ethers_contract( + &self, + provider: Provider, + _wallet: Option<&EthereumWallet>, + ) -> Result>, CryptoError> { + let contract = + ethers::contract::Contract::new(self.address, self.abi.clone(), Arc::new(provider)); Ok(contract) } @@ -70,24 +75,30 @@ pub async fn call_read_function( let _ethers_contract = contract.create_ethers_contract(provider.clone(), None)?; // Get the function from the ABI - let function = contract.abi.function(function_name) + 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)))?; + 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 + 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)))?; + let decoded = function.decode_output(&result).map_err(|e| { + CryptoError::ContractError(format!("Failed to decode function output: {}", e)) + })?; Ok(decoded) } @@ -101,18 +112,18 @@ pub async fn call_write_function( args: Vec, ) -> Result { // Create a client with the wallet - let client = SignerMiddleware::new( - provider.clone(), - wallet.wallet.clone(), - ); + let client = SignerMiddleware::new(provider.clone(), wallet.wallet.clone()); // Get the function from the ABI - let function = contract.abi.function(function_name) + 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)))?; + 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() @@ -135,12 +146,15 @@ pub async fn call_write_function( 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 Err(CryptoError::ContractError(format!( + "Failed to send transaction: {}", + e + ))); } }; @@ -157,12 +171,15 @@ pub async fn estimate_gas( args: Vec, ) -> Result { // Get the function from the ABI - let function = contract.abi.function(function_name) + 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)))?; + 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() @@ -171,7 +188,8 @@ pub async fn estimate_gas( .data(call_data); // Estimate gas - let gas = provider.estimate_gas(&tx.into(), None) + let gas = provider + .estimate_gas(&tx.into(), None) .await .map_err(|e| CryptoError::ContractError(format!("Failed to estimate gas: {}", e)))?; diff --git a/src/vault/ethereum/contract_utils.rs b/vault/src/ethereum/contract_utils.rs similarity index 100% rename from src/vault/ethereum/contract_utils.rs rename to vault/src/ethereum/contract_utils.rs diff --git a/src/vault/ethereum/mod.rs b/vault/src/ethereum/mod.rs similarity index 100% rename from src/vault/ethereum/mod.rs rename to vault/src/ethereum/mod.rs diff --git a/src/vault/ethereum/networks.rs b/vault/src/ethereum/networks.rs similarity index 100% rename from src/vault/ethereum/networks.rs rename to vault/src/ethereum/networks.rs diff --git a/src/vault/ethereum/provider.rs b/vault/src/ethereum/provider.rs similarity index 74% rename from src/vault/ethereum/provider.rs rename to vault/src/ethereum/provider.rs index 145566a..de3aed6 100644 --- a/src/vault/ethereum/provider.rs +++ b/vault/src/ethereum/provider.rs @@ -2,13 +2,17 @@ use ethers::prelude::*; -use crate::vault::error::CryptoError; use super::networks::{self, NetworkConfig}; +use crate::error::CryptoError; /// Creates a provider for a specific network. pub fn create_provider(network: &NetworkConfig) -> Result, CryptoError> { - Provider::::try_from(network.rpc_url.as_str()) - .map_err(|e| CryptoError::SerializationError(format!("Failed to create provider for {}: {}", network.name, e))) + Provider::::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. diff --git a/src/vault/ethereum/storage.rs b/vault/src/ethereum/storage.rs similarity index 73% rename from src/vault/ethereum/storage.rs rename to vault/src/ethereum/storage.rs index e74fb26..6ef4dc9 100644 --- a/src/vault/ethereum/storage.rs +++ b/vault/src/ethereum/storage.rs @@ -1,31 +1,34 @@ //! Ethereum wallet storage functionality. -use std::sync::Mutex; -use std::collections::HashMap; use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::Mutex; -use crate::vault::error::CryptoError; -use super::wallet::EthereumWallet; use super::networks::{self, NetworkConfig}; +use super::wallet::EthereumWallet; +use crate::error::CryptoError; /// Global storage for Ethereum wallets. -static ETH_WALLETS: Lazy>>> = Lazy::new(|| { - Mutex::new(HashMap::new()) -}); +static ETH_WALLETS: Lazy>>> = + 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 { +pub fn create_ethereum_wallet_for_network( + network: NetworkConfig, +) -> Result { // Get the currently selected keypair - let keypair = crate::vault::keyspace::get_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); + let network_wallets = wallets + .entry(wallet.network.name.clone()) + .or_insert_with(Vec::new); network_wallets.push(wallet.clone()); - + Ok(wallet) } @@ -40,15 +43,19 @@ pub fn create_agung_wallet() -> Result { } /// Gets the current Ethereum wallet for a specific network. -pub fn get_current_ethereum_wallet_for_network(network_name: &str) -> Result { +pub fn get_current_ethereum_wallet_for_network( + network_name: &str, +) -> Result { let wallets = ETH_WALLETS.lock().unwrap(); - - let network_wallets = wallets.get(network_name).ok_or(CryptoError::NoKeypairSelected)?; - + + 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()) } @@ -75,18 +82,23 @@ pub fn clear_ethereum_wallets_for_network(network_name: &str) { } /// 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 { +pub fn create_ethereum_wallet_from_name_for_network( + name: &str, + network: NetworkConfig, +) -> Result { // Get the currently selected keypair - let keypair = crate::vault::keyspace::get_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); + let network_wallets = wallets + .entry(wallet.network.name.clone()) + .or_insert_with(Vec::new); network_wallets.push(wallet.clone()); - + Ok(wallet) } @@ -96,19 +108,26 @@ pub fn create_ethereum_wallet_from_name(name: &str) -> Result Result { +pub fn create_ethereum_wallet_from_private_key_for_network( + private_key: &str, + network: NetworkConfig, +) -> Result { // 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); + 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 { +pub fn create_ethereum_wallet_from_private_key( + private_key: &str, +) -> Result { create_ethereum_wallet_from_private_key_for_network(private_key, networks::gnosis()) } diff --git a/src/vault/ethereum/tests/contract_args_tests.rs b/vault/src/ethereum/tests/contract_args_tests.rs similarity index 100% rename from src/vault/ethereum/tests/contract_args_tests.rs rename to vault/src/ethereum/tests/contract_args_tests.rs diff --git a/src/vault/ethereum/tests/contract_tests.rs b/vault/src/ethereum/tests/contract_tests.rs similarity index 100% rename from src/vault/ethereum/tests/contract_tests.rs rename to vault/src/ethereum/tests/contract_tests.rs diff --git a/src/vault/ethereum/tests/mod.rs b/vault/src/ethereum/tests/mod.rs similarity index 100% rename from src/vault/ethereum/tests/mod.rs rename to vault/src/ethereum/tests/mod.rs diff --git a/src/vault/ethereum/tests/network_tests.rs b/vault/src/ethereum/tests/network_tests.rs similarity index 100% rename from src/vault/ethereum/tests/network_tests.rs rename to vault/src/ethereum/tests/network_tests.rs diff --git a/src/vault/ethereum/tests/transaction_tests.rs b/vault/src/ethereum/tests/transaction_tests.rs similarity index 100% rename from src/vault/ethereum/tests/transaction_tests.rs rename to vault/src/ethereum/tests/transaction_tests.rs diff --git a/src/vault/ethereum/tests/wallet_tests.rs b/vault/src/ethereum/tests/wallet_tests.rs similarity index 100% rename from src/vault/ethereum/tests/wallet_tests.rs rename to vault/src/ethereum/tests/wallet_tests.rs diff --git a/src/vault/ethereum/transaction.rs b/vault/src/ethereum/transaction.rs similarity index 67% rename from src/vault/ethereum/transaction.rs rename to vault/src/ethereum/transaction.rs index fd9deb6..dc18c14 100644 --- a/src/vault/ethereum/transaction.rs +++ b/vault/src/ethereum/transaction.rs @@ -2,25 +2,29 @@ use ethers::prelude::*; -use crate::vault::error::CryptoError; -use super::wallet::EthereumWallet; 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) + + format!( + "{:.*} {}", + display_decimals as usize, token, network.token_symbol + ) } /// Gets the balance of an Ethereum address. pub async fn get_balance(provider: &Provider, address: Address) -> Result { - provider.get_balance(address, None) + provider + .get_balance(address, None) .await .map_err(|e| CryptoError::SerializationError(format!("Failed to get balance: {}", e))) } @@ -33,22 +37,16 @@ pub async fn send_eth( amount: U256, ) -> Result { // Create a client with the wallet - let client = SignerMiddleware::new( - provider.clone(), - wallet.wallet.clone(), - ); - + let client = SignerMiddleware::new(provider.clone(), wallet.wallet.clone()); + // Create the transaction - let tx = TransactionRequest::new() - .to(to) - .value(amount) - .gas(21000); - + 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)))?; - + 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()) } diff --git a/src/vault/ethereum/wallet.rs b/vault/src/ethereum/wallet.rs similarity index 96% rename from src/vault/ethereum/wallet.rs rename to vault/src/ethereum/wallet.rs index 58d060f..b87db84 100644 --- a/src/vault/ethereum/wallet.rs +++ b/vault/src/ethereum/wallet.rs @@ -8,8 +8,8 @@ use sha2::{Digest, Sha256}; use std::str::FromStr; use super::networks::NetworkConfig; -use crate::vault::error::CryptoError; -use crate::vault::keyspace::KeyPair; +use crate::error::CryptoError; +use crate::keyspace::KeyPair; /// An Ethereum wallet derived from a keypair. #[derive(Debug, Clone)] @@ -22,7 +22,7 @@ pub struct EthereumWallet { impl EthereumWallet { /// Creates a new Ethereum wallet from a keypair for a specific network. pub fn from_keypair( - keypair: &crate::vault::keyspace::keypair_types::KeyPair, + keypair: &crate::keyspace::keypair_types::KeyPair, network: NetworkConfig, ) -> Result { // Get the private key bytes from the keypair diff --git a/vault/src/key.rs b/vault/src/key.rs deleted file mode 100644 index 42d2529..0000000 --- a/vault/src/key.rs +++ /dev/null @@ -1,83 +0,0 @@ -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, -} - -impl Key { - /// Try to downcast this `Key` to a [`SymmetricKey`] - pub fn as_symmetric(&self) -> Option { - 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 { - 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 { - if matches!(self.mode, KeyType::Signature) { - SigningKeypair::from_bytes(&self.raw_key).ok() - } else { - None - } - } -} - -impl From for Key { - fn from(value: SymmetricKey) -> Self { - Self { - mode: KeyType::Symmetric, - raw_key: Vec::from(value.as_raw_bytes()), - } - } -} - -impl From for Key { - fn from(value: AsymmetricKeypair) -> Self { - Self { - mode: KeyType::Asymmetric, - raw_key: value.as_raw_private_key(), - } - } -} - -impl From for Key { - fn from(value: SigningKeypair) -> Self { - Self { - mode: KeyType::Signature, - raw_key: value.as_raw_private_key(), - } - } -} diff --git a/vault/src/key/asymmetric.rs b/vault/src/key/asymmetric.rs deleted file mode 100644 index ea89740..0000000 --- a/vault/src/key/asymmetric.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! 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 { - 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 { - 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 { - 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, CryptoError> { - let mut symmetric_key = [0u8; 32]; - diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine()) - .extract::(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, CryptoError> { - let mut symmetric_key = [0u8; 32]; - diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine()) - .extract::(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 { - 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()); - } -} diff --git a/vault/src/key/signature.rs b/vault/src/key/signature.rs deleted file mode 100644 index e83d364..0000000 --- a/vault/src/key/signature.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! 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 { - 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 { - 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 { - 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, 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 { - 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()); - } -} diff --git a/vault/src/key/symmetric.rs b/vault/src/key/symmetric.rs deleted file mode 100644 index 00aaa96..0000000 --- a/vault/src/key/symmetric.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! 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 { - 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, 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, 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::(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()); - } -} diff --git a/vault/src/keyspace.rs b/vault/src/keyspace.rs deleted file mode 100644 index 112be5e..0000000 --- a/vault/src/keyspace.rs +++ /dev/null @@ -1,131 +0,0 @@ -// #[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::{Key, symmetric::SymmetricKey}, -}; - -use kv::KVStore; - -/// Configuration to use for bincode en/decoding. -const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); - -// #[cfg(not(target_arch = "wasm32"))] -// use fallback::KeySpace as Ks; -// #[cfg(target_arch = "wasm32")] -// use wasm::KeySpace as Ks; - -#[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 { - // store: Ks, - #[cfg(not(target_arch = "wasm32"))] - store: NativeStore, - #[cfg(target_arch = "wasm32")] - store: WasmStore, - /// A collection of all keys stored in the KeySpace, in decrypted form. - keys: HashMap, - /// 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 { - let store = NativeStore::open(&path.display().to_string())?; - let mut ks = Self { - store, - 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 { - let store = WasmStore::open(name).await?; - let mut ks = Self { - store, - 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, 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, 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)?; - // Put in store - Ok(self.store.set(KEYSPACE_NAME, &value).await?) - } - - /// Loads the encrypted keyspace from the underlying storage - async fn load_keyspace(&mut self) -> Result<(), Error> { - let Some(ks) = self.store.get(KEYSPACE_NAME).await? else { - // Keyspace doesn't exist yet, nothing to do here - return Ok(()); - }; - - let raw = self.encryption_key.decrypt(&ks)?; - - let (decoded_keys, _): (HashMap, _) = - bincode::serde::decode_from_slice(&raw, BINCODE_CONFIG)?; - - self.keys = decoded_keys; - - Ok(()) - } -} diff --git a/src/vault/keyspace/README.md b/vault/src/keyspace/README.md similarity index 100% rename from src/vault/keyspace/README.md rename to vault/src/keyspace/README.md diff --git a/vault/src/keyspace/fallback.rs b/vault/src/keyspace/fallback.rs deleted file mode 100644 index cd8cca7..0000000 --- a/vault/src/keyspace/fallback.rs +++ /dev/null @@ -1,72 +0,0 @@ -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, - /// 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 { - /// 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, 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, Error> { - todo!() - } -} diff --git a/src/vault/keyspace/keypair_types.rs b/vault/src/keyspace/keypair_types.rs similarity index 99% rename from src/vault/keyspace/keypair_types.rs rename to vault/src/keyspace/keypair_types.rs index a91d8cd..01bc995 100644 --- a/src/vault/keyspace/keypair_types.rs +++ b/vault/src/keyspace/keypair_types.rs @@ -9,8 +9,8 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::HashMap; -use crate::vault::error::CryptoError; -use crate::vault::symmetric::implementation; +use crate::error::CryptoError; +use crate::symmetric::implementation; /// A keypair for signing and verifying messages. #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/vault/src/keyspace/mod.rs b/vault/src/keyspace/mod.rs new file mode 100644 index 0000000..10a5c29 --- /dev/null +++ b/vault/src/keyspace/mod.rs @@ -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 diff --git a/src/vault/keyspace/session_manager.rs b/vault/src/keyspace/session_manager.rs similarity index 96% rename from src/vault/keyspace/session_manager.rs rename to vault/src/keyspace/session_manager.rs index 3fa7a11..37f0ffe 100644 --- a/src/vault/keyspace/session_manager.rs +++ b/vault/src/keyspace/session_manager.rs @@ -1,8 +1,8 @@ use once_cell::sync::Lazy; use std::sync::Mutex; -use crate::vault::error::CryptoError; -use crate::vault::keyspace::keypair_types::{KeyPair, KeySpace}; // Assuming KeyPair and KeySpace will be in keypair_types.rs +use crate::error::CryptoError; +use crate::keyspace::keypair_types::{KeyPair, KeySpace}; /// Session state for the current key space and selected keypair. pub struct Session { diff --git a/src/vault/keyspace/spec.md b/vault/src/keyspace/spec.md similarity index 100% rename from src/vault/keyspace/spec.md rename to vault/src/keyspace/spec.md diff --git a/vault/src/keyspace/wasm.rs b/vault/src/keyspace/wasm.rs deleted file mode 100644 index 5c60ddf..0000000 --- a/vault/src/keyspace/wasm.rs +++ /dev/null @@ -1,26 +0,0 @@ -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, 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, Error> { - todo!() - } -} diff --git a/src/vault/kvs/README.md b/vault/src/kvs/README.md similarity index 100% rename from src/vault/kvs/README.md rename to vault/src/kvs/README.md diff --git a/src/vault/kvs/error.rs b/vault/src/kvs/error.rs similarity index 68% rename from src/vault/kvs/error.rs rename to vault/src/kvs/error.rs index bbd6eaf..b6192c5 100644 --- a/src/vault/kvs/error.rs +++ b/vault/src/kvs/error.rs @@ -44,20 +44,18 @@ impl From for KvsError { } } -impl From for crate::vault::error::CryptoError { +impl From for crate::error::CryptoError { fn from(err: KvsError) -> Self { - crate::vault::error::CryptoError::SerializationError(err.to_string()) + crate::error::CryptoError::SerializationError(err.to_string()) } } -impl From for KvsError { - fn from(err: crate::vault::error::CryptoError) -> Self { +impl From for KvsError { + fn from(err: crate::error::CryptoError) -> Self { match err { - crate::vault::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg), - crate::vault::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg), - crate::vault::error::CryptoError::SerializationError(msg) => { - KvsError::Serialization(msg) - } + 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()), } } diff --git a/src/vault/kvs/mod.rs b/vault/src/kvs/mod.rs similarity index 63% rename from src/vault/kvs/mod.rs rename to vault/src/kvs/mod.rs index 90e85b9..01cbf7f 100644 --- a/src/vault/kvs/mod.rs +++ b/vault/src/kvs/mod.rs @@ -8,10 +8,7 @@ pub mod store; // Re-export public types and functions pub use error::KvsError; pub use store::{ - KvStore, KvPair, - create_store, open_store, delete_store, - list_stores, get_store_path + create_store, delete_store, get_store_path, list_stores, open_store, KvPair, KvStore, }; -#[cfg(test)] -mod tests; +// Tests are now in the tests/ directory diff --git a/src/vault/kvs/store.rs b/vault/src/kvs/store.rs similarity index 99% rename from src/vault/kvs/store.rs rename to vault/src/kvs/store.rs index 74c9c6f..fa7ce12 100644 --- a/src/vault/kvs/store.rs +++ b/vault/src/kvs/store.rs @@ -1,7 +1,7 @@ //! Implementation of a simple key-value store using the filesystem. -use crate::vault::kvs::error::{KvsError, Result}; -use crate::vault::symmetric::implementation::{ +use crate::kvs::error::{KvsError, Result}; +use crate::symmetric::implementation::{ decrypt_symmetric, derive_key_from_password, encrypt_symmetric, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; diff --git a/vault/src/lib.rs b/vault/src/lib.rs index 1f9e834..08b677b 100644 --- a/vault/src/lib.rs +++ b/vault/src/lib.rs @@ -1,51 +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 key; +pub mod ethereum; pub mod keyspace; +pub mod kvs; +pub mod symmetric; -#[cfg(not(target_arch = "wasm32"))] -use std::path::{Path, PathBuf}; +// Rhai integration module +pub mod rhai; -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 { - 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 { - 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 - } - } -} +// Re-export modules +// Re-export common types for convenience +pub use error::CryptoError; +pub use keyspace::{KeyPair, KeySpace}; diff --git a/src/rhai/vault.rs b/vault/src/rhai.rs similarity index 93% rename from src/rhai/vault.rs rename to vault/src/rhai.rs index 51dc9c1..f04f366 100644 --- a/src/rhai/vault.rs +++ b/vault/src/rhai.rs @@ -1,4 +1,4 @@ -//! Rhai bindings for SAL crypto functionality +//! Rhai bindings for SAL vault functionality use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; @@ -13,9 +13,9 @@ use std::str::FromStr; use std::sync::Mutex; use tokio::runtime::Runtime; -use crate::vault::{ethereum, keyspace}; +use crate::{ethereum, keyspace}; -use crate::vault::symmetric::implementation as symmetric_impl; +use crate::symmetric::implementation as symmetric_impl; // Global Tokio runtime for blocking async operations static RUNTIME: Lazy> = Lazy::new(|| Mutex::new(Runtime::new().expect("Failed to create Tokio runtime"))); @@ -25,6 +25,10 @@ static PROVIDERS: Lazy< Mutex>>, > = Lazy::new(|| Mutex::new(HashMap::new())); +// Global keyspace registry for testing (stores keyspaces with their passwords) +static KEYSPACE_REGISTRY: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + // Key space management functions fn load_key_space(name: &str, password: &str) -> bool { // Get the key spaces directory from config @@ -89,6 +93,11 @@ fn create_key_space(name: &str, password: &str) -> bool { // Get the current space match keyspace::get_current_space() { Ok(space) => { + // Store in registry for testing + if let Ok(mut registry) = KEYSPACE_REGISTRY.lock() { + registry.insert(name.to_string(), (space.clone(), password.to_string())); + } + // Encrypt the key space let encrypted_space = match symmetric_impl::encrypt_key_space(&space, password) { @@ -255,7 +264,7 @@ fn decrypt_key_space(encrypted: &str, password: &str) -> bool { // keyspace management functions fn create_keyspace(name: &str, password: &str) -> bool { - match keyspace::create_keypair(name) { + match keyspace::session_manager::create_keypair(name) { Ok(_) => { // Auto-save the key space after creating a keyspace auto_save_key_space(password) @@ -268,16 +277,34 @@ fn create_keyspace(name: &str, password: &str) -> bool { } fn select_keyspace(name: &str) -> bool { - let session = crate::vault::keyspace::session_manager::SESSION - .lock() - .unwrap(); - if let Some(ref current_space_obj) = session.current_space { - if current_space_obj.name == name { - log::debug!("Keyspace '{}' is already selected.", name); - return true; + // First check if it's already the current keyspace + { + let session = crate::keyspace::session_manager::SESSION.lock().unwrap(); + if let Some(ref current_space_obj) = session.current_space { + if current_space_obj.name == name { + log::debug!("Keyspace '{}' is already selected.", name); + return true; + } } } - log::warn!("Attempted to select keyspace '{}' which is not currently active. Use 'load_key_space(name, password)' to load and select a keyspace.", name); + + // Try to get from registry first (for testing) + if let Ok(registry) = KEYSPACE_REGISTRY.lock() { + if let Some((space, _password)) = registry.get(name) { + match keyspace::session_manager::set_current_space(space.clone()) { + Ok(_) => { + log::debug!("Selected keyspace '{}' from registry", name); + return true; + } + Err(e) => { + log::error!("Error setting current space: {}", e); + return false; + } + } + } + } + + log::warn!("Keyspace '{}' not found in registry. Use 'load_key_space(name, password)' to load from disk.", name); false } @@ -342,6 +369,10 @@ fn rhai_select_keypair(name: &str) -> bool { fn rhai_clear_session() { keyspace::session_manager::clear_session(); + // Also clear the registry for testing + if let Ok(mut registry) = KEYSPACE_REGISTRY.lock() { + registry.clear(); + } } fn rhai_create_keypair(name: &str) -> bool { @@ -380,13 +411,15 @@ fn sign(message: &str) -> String { fn verify(message: &str, signature: &str) -> bool { let message_bytes = message.as_bytes(); match BASE64.decode(signature) { - Ok(signature_bytes) => match keyspace::keypair_verify(message_bytes, &signature_bytes) { - Ok(is_valid) => is_valid, - Err(e) => { - log::error!("Error verifying signature: {}", e); - false + Ok(signature_bytes) => { + match keyspace::session_manager::keypair_verify(message_bytes, &signature_bytes) { + Ok(is_valid) => is_valid, + Err(e) => { + log::error!("Error verifying signature: {}", e); + false + } } - }, + } Err(e) => { log::error!("Error decoding signature: {}", e); false diff --git a/src/vault/symmetric/README.md b/vault/src/symmetric/README.md similarity index 100% rename from src/vault/symmetric/README.md rename to vault/src/symmetric/README.md diff --git a/src/vault/symmetric/implementation.rs b/vault/src/symmetric/implementation.rs similarity index 88% rename from src/vault/symmetric/implementation.rs rename to vault/src/symmetric/implementation.rs index 2fa9520..39c5356 100644 --- a/src/vault/symmetric/implementation.rs +++ b/vault/src/symmetric/implementation.rs @@ -1,13 +1,13 @@ //! Implementation of symmetric encryption functionality. -use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce}; use chacha20poly1305::aead::Aead; +use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce}; use rand::{rngs::OsRng, RngCore}; -use serde::{Serialize, Deserialize}; -use sha2::{Sha256, Digest}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; -use crate::vault::error::CryptoError; -use crate::vault::keyspace::KeySpace; +use crate::error::CryptoError; +use crate::keyspace::KeySpace; /// The size of the nonce in bytes. const NONCE_SIZE: usize = 12; @@ -36,7 +36,7 @@ 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 @@ -58,22 +58,23 @@ pub fn derive_key_from_password(password: &str) -> [u8; 32] { /// * `Err(CryptoError::EncryptionFailed)` if encryption fails. pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, CryptoError> { // Create cipher - let cipher = ChaCha20Poly1305::new_from_slice(key) - .map_err(|_| CryptoError::InvalidKeyLength)?; - + 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) + 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) } @@ -92,22 +93,25 @@ pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, CryptoEr pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result, CryptoError> { // Check if ciphertext is long enough to contain a nonce if ciphertext_with_nonce.len() <= NONCE_SIZE { - return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string())); + 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 cipher = + ChaCha20Poly1305::new_from_slice(key).map_err(|_| CryptoError::InvalidKeyLength)?; + let nonce = Nonce::from_slice(nonce_bytes); - + // Decrypt message - cipher.decrypt(nonce, ciphertext) + cipher + .decrypt(nonce, ciphertext) .map_err(|e| CryptoError::DecryptionFailed(e.to_string())) } @@ -167,7 +171,10 @@ pub struct EncryptedKeySpace { /// /// * `Ok(EncryptedKeySpace)` containing the encrypted key space. /// * `Err(CryptoError)` if encryption fails. -pub fn encrypt_key_space(space: &KeySpace, password: &str) -> Result { +pub fn encrypt_key_space( + space: &KeySpace, + password: &str, +) -> Result { // Serialize the key space let serialized = match serde_json::to_vec(space) { Ok(data) => data, @@ -176,13 +183,13 @@ pub fn encrypt_key_space(space: &KeySpace, password: &str) -> Result Result Result Result { +pub fn decrypt_key_space( + encrypted_space: &EncryptedKeySpace, + password: &str, +) -> Result { // Derive key from password let key = derive_key_from_password(password); - + // Decrypt the data let decrypted_data = decrypt_symmetric(&key, &encrypted_space.encrypted_data)?; - + // Deserialize the key space let space: KeySpace = match serde_json::from_slice(&decrypted_data) { Ok(space) => space, @@ -226,7 +236,7 @@ pub fn decrypt_key_space(encrypted_space: &EncryptedKeySpace, password: &str) -> return Err(CryptoError::SerializationError(e.to_string())); } }; - + Ok(space) } @@ -240,7 +250,9 @@ pub fn decrypt_key_space(encrypted_space: &EncryptedKeySpace, password: &str) -> /// /// * `Ok(String)` containing the serialized encrypted key space. /// * `Err(CryptoError)` if serialization fails. -pub fn serialize_encrypted_space(encrypted_space: &EncryptedKeySpace) -> Result { +pub fn serialize_encrypted_space( + encrypted_space: &EncryptedKeySpace, +) -> Result { serde_json::to_string(encrypted_space) .map_err(|e| CryptoError::SerializationError(e.to_string())) } diff --git a/src/vault/symmetric/mod.rs b/vault/src/symmetric/mod.rs similarity index 100% rename from src/vault/symmetric/mod.rs rename to vault/src/symmetric/mod.rs diff --git a/vault/tests/crypto_tests.rs b/vault/tests/crypto_tests.rs new file mode 100644 index 0000000..aacb1ce --- /dev/null +++ b/vault/tests/crypto_tests.rs @@ -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"); +} diff --git a/vault/tests/rhai/basic_crypto.rhai b/vault/tests/rhai/basic_crypto.rhai new file mode 100644 index 0000000..0c57b53 --- /dev/null +++ b/vault/tests/rhai/basic_crypto.rhai @@ -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! ==="); diff --git a/vault/tests/rhai/keyspace_management.rhai b/vault/tests/rhai/keyspace_management.rhai new file mode 100644 index 0000000..3197a81 --- /dev/null +++ b/vault/tests/rhai/keyspace_management.rhai @@ -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! ==="); diff --git a/vault/tests/rhai_integration_tests.rs b/vault/tests/rhai_integration_tests.rs new file mode 100644 index 0000000..3179063 --- /dev/null +++ b/vault/tests/rhai_integration_tests.rs @@ -0,0 +1,227 @@ +use rhai::{Engine, EvalAltResult}; +use sal_vault::rhai::*; + +#[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 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> = engine.eval(script); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_symmetric_encryption_functions() { + 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> = engine.eval(script); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_keyspace_functions() { + 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> = engine.eval(script); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_keypair_functions() { + 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> = engine.eval(script); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_signing_functions() { + 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> = engine.eval(script); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } + + #[test] + fn test_session_management() { + 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(); + if spaces.len() < 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(); + if keypairs.len() != 1 { + throw "Should have exactly 1 keypair in space2"; + } + + true + "#; + + let result: Result> = 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 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> = engine.eval(script); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), true); + } +}