feat: Refactor kvstore and vault to use features and logging
- Remove hardcoded dependencies in kvstore Cargo.toml; use features instead. This allows for more flexible compilation for different targets (native vs. WASM). - Improve logging in vault crate using the `log` crate. This makes debugging easier and provides more informative output during execution. Native tests use `env_logger`, WASM tests use `console_log`. - Update README to reflect new logging best practices. - Add cfg attributes to native and wasm modules to improve clarity. - Update traits.rs to specify Send + Sync behavior expectations.
This commit is contained in:
		
							
								
								
									
										85
									
								
								docs/native-wasm-build-plan.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								docs/native-wasm-build-plan.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| # Plan: Ensuring Native and WASM Builds Work for the Vault/KVStore System | ||||
|  | ||||
| ## Purpose | ||||
| This document outlines the steps and requirements to guarantee that both native (desktop/server) and WASM (browser) builds of the `vault` and `kvstore` crates work seamlessly, securely, and efficiently. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1. Architecture Principles | ||||
| - **Async/Await Everywhere:** All APIs must be async and runtime-agnostic (no Tokio requirement in library code). | ||||
| - **KVStore Trait:** Use an async trait for storage, with platform-specific implementations (sled for native, IndexedDB/idb for WASM). | ||||
| - **Conditional Compilation:** Use `#[cfg(target_arch = "wasm32")]` and `#[cfg(not(target_arch = "wasm32"))]` to select code and dependencies. | ||||
| - **No Blocking in WASM:** All I/O and crypto operations must be async and non-blocking in browser builds. | ||||
| - **WASM-Compatible Crypto:** Only use crypto crates that compile to WASM (e.g., `aes-gcm`, `chacha20poly1305`, `k256`, `rand_core`). | ||||
| - **Separation of Concerns:** All encryption and password logic resides in `vault`, not `kvstore`. | ||||
| - **Stateless and Session APIs:** Provide both stateless (context-passing) and session-based APIs in `vault`. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Cargo.toml and Dependency Management | ||||
| - **Native:** | ||||
|   - `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]` | ||||
|     - `tokio` (with only supported features) | ||||
|     - `sled` | ||||
| - **WASM:** | ||||
|   - `[target.'cfg(target_arch = "wasm32")'.dependencies]` | ||||
|     - `idb` | ||||
|     - `wasm-bindgen`, `wasm-bindgen-futures` | ||||
| - **Crypto:** | ||||
|   - Only include crates that are WASM-compatible for both targets. | ||||
| - **No unconditional `tokio`** in `vault` or `kvstore`. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. Code Organization | ||||
| - **KVStore Trait:** | ||||
|   - Define as async trait (using `async_trait`). | ||||
|   - Implement for sled (native) and idb (WASM), using `#[cfg]`. | ||||
| - **Vault:** | ||||
|   - All persistence must go through the KVStore trait. | ||||
|   - All cryptography must be WASM-compatible. | ||||
|   - No direct file or blocking I/O in WASM. | ||||
| - **Runtime:** | ||||
|   - Only use `tokio` in binaries or native-specific code. | ||||
|   - In WASM, use `wasm-bindgen-futures::spawn_local` for async tasks. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. Platform-Specific Guidelines | ||||
| - **Native (Desktop/Server):** | ||||
|   - Use `sled` for storage. | ||||
|   - Use `tokio::task::spawn_blocking` for blocking I/O if needed. | ||||
|   - All async code should work with any runtime. | ||||
| - **WASM (Browser):** | ||||
|   - Use `idb` crate for IndexedDB storage. | ||||
|   - All code must be non-blocking and compatible with the browser event loop. | ||||
|   - Use `wasm-bindgen` and `wasm-bindgen-futures` for JS interop and async. | ||||
|   - Expose APIs with `#[wasm_bindgen]` for JS usage. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. Testing | ||||
| - **Native:** `cargo test` | ||||
| - **WASM:** `cargo test --target wasm32-unknown-unknown --release` (or use `wasm-pack test`) | ||||
| - **Separate tests** for native and WASM backends in `tests/`. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Checklist for Compliance | ||||
| - [ ] No unconditional `tokio` usage in library code | ||||
| - [ ] All dependencies are WASM-compatible (where needed) | ||||
| - [ ] All storage goes through async KVStore trait | ||||
| - [ ] No blocking I/O or native-only APIs in WASM | ||||
| - [ ] All cryptography is WASM-compatible | ||||
| - [ ] Both stateless and session APIs are available in `vault` | ||||
| - [ ] All APIs are async and runtime-agnostic | ||||
| - [ ] Native and WASM tests both pass | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 7. References | ||||
| - See `docs/Architecture.md`, `docs/kvstore-vault-architecture.md`, and `docs/vault_impl_plan.md` for architectural background and rationale. | ||||
|  | ||||
| --- | ||||
|  | ||||
| By following this plan, the codebase will be robust, portable, and secure on both native and browser platforms, and will adhere to all project architectural guidelines. | ||||
							
								
								
									
										164
									
								
								docs/vault.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								docs/vault.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,164 @@ | ||||
| 🧱 Vault Crate Architecture | ||||
| 1. VaultStore | ||||
| Purpose: Central manager for all keyspaces. | ||||
|  | ||||
| Responsibilities: | ||||
|  | ||||
| Maintain metadata about keyspaces. | ||||
|  | ||||
| Provide interfaces to create, load, and manage keyspaces. | ||||
|  | ||||
| Ensure each keyspace is encrypted with its own password. | ||||
|  | ||||
| 2. KeySpace | ||||
| Purpose: Isolated environment containing multiple keypairs. | ||||
|  | ||||
| Responsibilities: | ||||
|  | ||||
| Securely store and manage keypairs. | ||||
|  | ||||
| Provide cryptographic operations like signing and encryption. | ||||
|  | ||||
| Handle encryption/decryption using a password-derived key. | ||||
|  | ||||
| 3. KeyPair | ||||
| Purpose: Represents an individual cryptographic keypair. | ||||
|  | ||||
| Responsibilities: | ||||
|  | ||||
| Perform cryptographic operations such as signing and verification. | ||||
|  | ||||
| Support key export and import functionalities. | ||||
|  | ||||
| 4. Symmetric Encryption Module | ||||
| Purpose: Provides encryption and decryption functionalities. | ||||
|  | ||||
| Responsibilities: | ||||
|  | ||||
| Encrypt and decrypt data using algorithms like ChaCha20Poly1305. | ||||
|  | ||||
| Derive encryption keys from passwords using PBKDF2. | ||||
|  | ||||
| 5. SessionManager | ||||
| Purpose: Manages the active context for cryptographic operations, simplifying API usage. | ||||
|  | ||||
| Responsibilities: | ||||
|  | ||||
| Maintain the currently selected keypair. | ||||
|  | ||||
| Provide simplified methods for cryptographic operations without repeatedly specifying the keypair. | ||||
|  | ||||
| 🔐 Security Model | ||||
| Per-KeySpace Encryption: Each keyspace is encrypted independently using a key derived from its password. | ||||
|  | ||||
| VaultStore Metadata: Stores non-sensitive metadata about keyspaces, such as their names and creation dates. This metadata can be stored in plaintext or encrypted based on security requirements. | ||||
|  | ||||
| 🧪 API Design | ||||
| VaultStore | ||||
| rust | ||||
| Copy | ||||
| Edit | ||||
| pub struct VaultStore { | ||||
|     // Internal fields | ||||
| } | ||||
|  | ||||
| impl VaultStore { | ||||
|     pub fn new() -> Self; | ||||
|     pub fn load() -> Result<Self, VaultError>; | ||||
|     pub fn save(&self) -> Result<(), VaultError>; | ||||
|  | ||||
|     pub fn list_keyspaces(&self) -> Vec<KeyspaceMetadata>; | ||||
|     pub fn create_keyspace(&mut self, name: &str, password: &str) -> Result<(), VaultError>; | ||||
|     pub fn delete_keyspace(&mut self, name: &str) -> Result<(), VaultError>; | ||||
|     pub fn rename_keyspace(&mut self, old_name: &str, new_name: &str) -> Result<(), VaultError>; | ||||
|     pub fn load_keyspace(&self, name: &str, password: &str) -> Result<KeySpace, VaultError>; | ||||
| } | ||||
| KeySpace | ||||
| rust | ||||
| Copy | ||||
| Edit | ||||
| pub struct KeySpace { | ||||
|     // Internal fields | ||||
| } | ||||
|  | ||||
| impl KeySpace { | ||||
|     pub fn new(name: &str, password: &str) -> Result<Self, VaultError>; | ||||
|     pub fn save(&self) -> Result<(), VaultError>; | ||||
|  | ||||
|     pub fn list_keypairs(&self) -> Vec<String>; | ||||
|     pub fn create_keypair(&mut self, name: &str) -> Result<(), VaultError>; | ||||
|     pub fn delete_keypair(&mut self, name: &str) -> Result<(), VaultError>; | ||||
|     pub fn rename_keypair(&mut self, old_name: &str, new_name: &str) -> Result<(), VaultError>; | ||||
|     pub fn get_keypair(&self, name: &str) -> Result<KeyPair, VaultError>; | ||||
|  | ||||
|     pub fn sign(&self, keypair_name: &str, message: &[u8]) -> Result<Vec<u8>, VaultError>; | ||||
|     pub fn verify(&self, keypair_name: &str, message: &[u8], signature: &[u8]) -> Result<bool, VaultError>; | ||||
| } | ||||
| KeyPair | ||||
| rust | ||||
| Copy | ||||
| Edit | ||||
| pub struct KeyPair { | ||||
|     // Internal fields | ||||
| } | ||||
|  | ||||
| impl KeyPair { | ||||
|     pub fn new() -> Self; | ||||
|     pub fn from_private_key(private_key: &[u8]) -> Result<Self, VaultError>; | ||||
|  | ||||
|     pub fn public_key(&self) -> Vec<u8>; | ||||
|     pub fn private_key(&self) -> Vec<u8>; | ||||
|  | ||||
|     pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, VaultError>; | ||||
|     pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, VaultError>; | ||||
| } | ||||
| SessionManager (Optional) | ||||
| rust | ||||
| Copy | ||||
| Edit | ||||
| pub struct SessionManager { | ||||
|     keyspace: KeySpace, | ||||
|     active_keypair: String, | ||||
| } | ||||
|  | ||||
| impl SessionManager { | ||||
|     pub fn new(keyspace: KeySpace, keypair_name: &str) -> Result<Self, VaultError>; | ||||
|     pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, VaultError>; | ||||
|     pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, VaultError>; | ||||
|     pub fn switch_keypair(&mut self, keypair_name: &str) -> Result<(), VaultError>; | ||||
| } | ||||
| 📦 Storage Structure | ||||
| Copy | ||||
| Edit | ||||
| vault_store/ | ||||
| ├── metadata.json | ||||
| └── keyspaces/ | ||||
|     ├── alice.ksp | ||||
|     ├── bob.ksp | ||||
|     └── ... | ||||
| metadata.json: Contains metadata about each keyspace, such as name and creation date. | ||||
|  | ||||
| keyspaces/: Directory containing encrypted keyspace files. | ||||
|  | ||||
| 🔄 Integration with kvstore | ||||
| Native Environment | ||||
| Storage Backend: Utilize the local filesystem or a persistent database (e.g., SQLite) for storing VaultStore and KeySpace data. | ||||
|  | ||||
| Usage: | ||||
|  | ||||
| Initialize VaultStore and load existing keyspaces. | ||||
|  | ||||
| Perform cryptographic operations using KeySpace and KeyPair. | ||||
|  | ||||
| Persist changes to disk or database. | ||||
|  | ||||
| Browser Environment (WASM) | ||||
| Storage Backend: Use browser storage APIs like localStorage or IndexedDB for persisting data. | ||||
|  | ||||
| Usage: | ||||
|  | ||||
| Compile the vault crate to WebAssembly. | ||||
|  | ||||
| Interact with the vault API through JavaScript bindings. | ||||
|  | ||||
| Store and retrieve encrypted keyspaces using browser storage. | ||||
							
								
								
									
										276
									
								
								docs/vault_impl_plan.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								docs/vault_impl_plan.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,276 @@ | ||||
| # Vault Implementation Plan | ||||
|  | ||||
| > **Design Principle:** | ||||
| > **The vault crate will provide both a stateless (context-passing) API and an ergonomic session-based API.** | ||||
| > This ensures maximum flexibility for both library developers and application builders, supporting both functional and stateful usage patterns. | ||||
|  | ||||
| ## 1. Architecture Overview | ||||
|  | ||||
| The `vault` crate is a modular, async, and WASM-compatible cryptographic keystore. It manages an encrypted keyspace (multiple keypairs), provides cryptographic APIs, and persists all data via the `kvstore` trait. The design ensures all sensitive material is encrypted at rest and is portable across native and browser environments. | ||||
|  | ||||
| **Core Components:** | ||||
| - **Vault:** Main manager for encrypted keyspace and cryptographic operations. | ||||
| - **KeyPair:** Represents individual asymmetric keypairs (e.g., secp256k1, Ed25519). | ||||
| - **Symmetric Encryption Module:** Handles encryption/decryption and key derivation. | ||||
| - **SessionManager (Optional):** Maintains current context (e.g., selected keypair) for user sessions. | ||||
| - **KVStore:** Async trait for backend-agnostic persistence (sled on native, IndexedDB on WASM). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Using Both Stateless and Session-Based APIs | ||||
|  | ||||
| You can design the vault crate to support both stateless and session-based (stateful) usage patterns. This gives maximum flexibility to both library developers and application builders. | ||||
|  | ||||
| ### Stateless API | ||||
| - All operations require explicit context (unlocked keyspace, keypair, etc.) as arguments. | ||||
| - No hidden or global state; maximally testable and concurrency-friendly. | ||||
| - Example: | ||||
|   ```rust | ||||
|   let keyspace = vault.unlock_keyspace("personal", b"password").await?; | ||||
|   let signature = keyspace.sign("key1", &msg).await?; | ||||
|   ``` | ||||
|  | ||||
| ### Session Manager API | ||||
| - Maintains in-memory state of unlocked keyspaces and current selections. | ||||
| - Provides ergonomic methods for interactive apps (CLI, desktop, browser). | ||||
| - Example: | ||||
|   ```rust | ||||
|   let mut session = SessionManager::new(); | ||||
|   session.unlock_keyspace("personal", b"password", &vault)?; | ||||
|   session.select_keypair("key1"); | ||||
|   let signature = session.current_keypair().unwrap().sign(&msg)?; | ||||
|   session.logout(); // wipes all secrets from memory | ||||
|   ``` | ||||
|  | ||||
| ### How They Work Together | ||||
| - The **stateless API** is the core, always available and used internally by the session manager. | ||||
| - The **session manager** is a thin, optional layer that wraps the stateless API for convenience. | ||||
| - Applications can choose which pattern fits their needs, or even mix both (e.g., use stateless for background jobs, session manager for user sessions). | ||||
|  | ||||
| ### Benefits | ||||
| - **Flexibility:** Library users can pick the best model for their use case. | ||||
| - **Security:** Session manager can enforce auto-lock, timeouts, and secure memory wiping. | ||||
| - **Simplicity:** Stateless API is easy to test and reason about, while session manager improves UX for interactive flows. | ||||
|  | ||||
| ### Commitment: Provide Both APIs | ||||
| - **Both stateless and session-based APIs will be provided in the vault crate.** | ||||
| - Stateless API: For backend, automation, or library contexts—explicit, functional, and concurrency-friendly. | ||||
| - Session manager API: For UI/UX-focused applications—ergonomic, stateful, and user-friendly. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Data Model | ||||
|  | ||||
| ### VaultMetadata & Keyspace Model | ||||
| ```rust | ||||
| struct VaultMetadata { | ||||
|     name: String, | ||||
|     keyspaces: Vec<KeyspaceMetadata>, | ||||
|     // ... other vault-level metadata (optionally encrypted) | ||||
| } | ||||
|  | ||||
| struct KeyspaceMetadata { | ||||
|     name: String, | ||||
|     salt: [u8; 16], // Unique salt for this keyspace | ||||
|     encrypted_blob: Vec<u8>, // All keypairs & secrets, encrypted with keyspace password | ||||
|     // ... other keyspace metadata | ||||
| } | ||||
|  | ||||
| // The decrypted contents of a keyspace: | ||||
| struct KeyspaceData { | ||||
|     keypairs: Vec<KeyEntry>, | ||||
|     // ... other keyspace-level metadata | ||||
| } | ||||
|  | ||||
| struct KeyEntry { | ||||
|     id: String, | ||||
|     key_type: KeyType, | ||||
|     private_key: Vec<u8>, // Only present in memory after decryption | ||||
|     public_key: Vec<u8>, | ||||
|     metadata: Option<KeyMetadata>, | ||||
| } | ||||
|  | ||||
| enum KeyType { | ||||
|     Secp256k1, | ||||
|     Ed25519, | ||||
|     // ... | ||||
| } | ||||
| ``` | ||||
|  | ||||
| - The vault contains a list of keyspaces, each with its own salt and encrypted blob. | ||||
| - Each keyspace is unlocked independently using its password and salt. | ||||
| - Key material is never stored unencrypted; only decrypted in memory after unlocking a keyspace. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. API Design (Keyspace Model) | ||||
|  | ||||
| ### Vault | ||||
| ```rust | ||||
| impl<S: KVStore + Send + Sync> Vault<S> { | ||||
|     async fn open(store: S) -> Result<Self, VaultError>; | ||||
|     async fn list_keyspaces(&self) -> Result<Vec<KeyspaceInfo>, VaultError>; | ||||
|     async fn create_keyspace(&mut self, name: &str, password: &[u8]) -> Result<(), VaultError>; | ||||
|     async fn delete_keyspace(&mut self, name: &str) -> Result<(), VaultError>; | ||||
|     async fn unlock_keyspace(&mut self, name: &str, password: &[u8]) -> Result<(), VaultError>; | ||||
|     async fn lock_keyspace(&mut self, name: &str); | ||||
|     // ... | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Keyspace Management | ||||
| ```rust | ||||
| impl Keyspace { | ||||
|     fn is_unlocked(&self) -> bool; | ||||
|     fn name(&self) -> &str; | ||||
|     async fn create_key(&mut self, key_type: KeyType, name: &str) -> Result<String, VaultError>; | ||||
|     async fn list_keys(&self) -> Result<Vec<KeyInfo>, VaultError>; | ||||
|     async fn sign(&self, key_id: &str, msg: &[u8]) -> Result<Signature, VaultError>; | ||||
|     async fn encrypt(&self, key_id: &str, plaintext: &[u8]) -> Result<Ciphertext, VaultError>; | ||||
|     async fn decrypt(&self, key_id: &str, ciphertext: &[u8]) -> Result<Vec<u8>, VaultError>; | ||||
|     async fn change_password(&mut self, old: &[u8], new: &[u8]) -> Result<(), VaultError>; | ||||
|     // ... | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### SessionManager | ||||
| ```rust | ||||
| impl SessionManager { | ||||
|     fn select_key(&mut self, key_id: &str); | ||||
|     fn current_key(&self) -> Option<&KeyPair>; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Symmetric Encryption Module | ||||
| - Derives a master key from password and salt (e.g., PBKDF2 or scrypt). | ||||
| - Encrypts/decrypts vault data with AES-GCM or ChaCha20Poly1305. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. Implementation Plan | ||||
|  | ||||
| 1. **Define Data Structures** | ||||
|     - VaultData, KeyEntry, KeyType, KeyMetadata, etc. | ||||
| 2. **Implement Symmetric Encryption** | ||||
|     - Password-based key derivation (PBKDF2/scrypt) | ||||
|     - AES-GCM or ChaCha20Poly1305 encryption | ||||
| 3. **Vault Logic** | ||||
|     - open, unlock, encrypt/decrypt, manage keypairs | ||||
|     - persist encrypted blob in kvstore | ||||
| 4. **KeyPair Management** | ||||
|     - Generate, import, export, sign, verify | ||||
| 5. **Session Management** | ||||
|     - Track selected key/context | ||||
| 6. **Error Handling** | ||||
|     - VaultError enum for crypto, storage, and logic errors | ||||
| 7. **WASM Interop** | ||||
|     - Use wasm-bindgen to expose async APIs as JS Promises | ||||
|     - Ensure all crypto crates are WASM-compatible | ||||
| 8. **Testing** | ||||
|     - Native and WASM tests for all APIs | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Design Decisions: Old Implementation, Current Plan, Open Questions, and Recommendations | ||||
|  | ||||
| | Area | Old Implementation | Current Plan | Decision Left/Open | Recommendation & Rationale | | ||||
| |------|--------------------|--------------|--------------------|----------------------------| | ||||
| | **KDF** | PBKDF2-HMAC-SHA256 | PBKDF2 or scrypt (WASM-compatible) | Which as default? Both supported? Per-keyspace choice? | **Use scrypt as default** for new keyspaces (stronger against GPU attacks) | ||||
| | **Symmetric Encryption** | ChaCha20Poly1305 | AES-256-GCM or ChaCha20Poly1305 | Which default? Both supported? Per-keyspace choice? | **ChaCha20Poly1305 recommended** for WASM and cross-platform. | ||||
| | **Key Types** | secp256k1, Ed25519 | secp256k1, Ed25519 | Add more? Custom key types? | **Keep secp256k1 and Ed25519 as default.** | | ||||
| | **Metadata Encryption** | Unencrypted vault metadata | Unencrypted keyspace metadata | Option to encrypt metadata? | **Unencrypted vault metadata** for simplicity. | | ||||
| | **Session Manager Features** | No session manager, manual unlock | Optional session manager | Timeout, auto-lock, secure wipe, multi-user? | **Implement optional session manager with timeout and secure memory wipe**. | | ||||
| | **Password Change/Recovery** | Manual re-encrypt, no recovery | API for password change | Re-encrypt all? Recovery/MFA? | **Re-encrypt keyspace on password change.** | | ||||
| | **WASM/Native Crypto** | Native only | WASM-compatible crates | Native-only features? | **Require WASM compatibility for all core features.** | | ||||
| | **Keyspace Sharing/Export** | Manual export/import, share password | Share keyspace password | Explicit export/import flows? Auditing? | **Add explicit export/import APIs.** Log/audit sharing if privacy is a concern. | | ||||
| | **Multi-user/Access Control** | Single password per vault | Single password per keyspace | ACL, threshold unlock? | **Single password per keyspace is simplest.** | | ||||
| | **Metadata/Tagging** | Minimal metadata, no tags | Basic metadata, optional tags | Required/custom tags? Usage stats? | **Support custom tags and creation date** for keyspaces/keys. | | ||||
| | **Storage Structure** | Single JSON file (vault) | Keyspaces as blobs in vault metadata | Store as separate kvstore records? | **Recommend storing each keyspace as a separate record** in kvstore for easier backup/sync/restore. | | ||||
| | **Error Handling** | Basic error codes | VaultError enum | Granular or coarse? WASM/JS exposure? | **Define granular error types** and expose user-friendly errors for WASM/JS. | | ||||
|  | ||||
| --- | ||||
|  | ||||
| **Legend:** | ||||
| - **Old Implementation:** What was done in the previous (legacy) design. | ||||
| - **Current Plan:** What is currently proposed in this implementation plan. | ||||
| - **Decision Left/Open:** What remains to be finalized or clarified. | ||||
| - **Recommendation & Rationale:** What is recommended for the new implementation and why, especially if it differs from the old approach. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. File/Module Structure (Recommended) | ||||
|  | ||||
| ``` | ||||
| vault/ | ||||
| ├── src/ | ||||
| │   ├── lib.rs            # Vault API and main logic | ||||
| │   ├── data.rs           # Data models: VaultData, KeyEntry, etc. | ||||
| │   ├── crypto.rs         # Symmetric/asymmetric crypto, key derivation | ||||
| │   ├── session.rs        # SessionManager | ||||
| │   ├── error.rs          # VaultError and error handling | ||||
| │   └── utils.rs          # Helpers, serialization, etc. | ||||
| ├── tests/ | ||||
| │   ├── native.rs         # Native (sled) tests | ||||
| │   └── wasm.rs           # WASM (IndexedDB) tests | ||||
| └── ... | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Cryptography: Crates and Algorithms | ||||
|  | ||||
| **Crates:** | ||||
| - [`aes-gcm`](https://crates.io/crates/aes-gcm): AES-GCM authenticated encryption (WASM-compatible) | ||||
| - [`chacha20poly1305`](https://crates.io/crates/chacha20poly1305): ChaCha20Poly1305 authenticated encryption (WASM-compatible) | ||||
| - [`pbkdf2`](https://crates.io/crates/pbkdf2): Password-based key derivation (WASM-compatible) | ||||
| - [`scrypt`](https://crates.io/crates/scrypt): Alternative KDF, strong and WASM-compatible | ||||
| - [`k256`](https://crates.io/crates/k256): secp256k1 ECDSA (Ethereum keys) | ||||
| - [`ed25519-dalek`](https://crates.io/crates/ed25519-dalek): Ed25519 keypairs | ||||
| - [`rand_core`](https://crates.io/crates/rand_core): Randomness, WASM-compatible | ||||
| - [`getrandom`](https://crates.io/crates/getrandom): Platform-agnostic RNG | ||||
|  | ||||
| **Algorithm Choices:** | ||||
| - **Vault Encryption:** | ||||
|   - AES-256-GCM (default, via `aes-gcm`) | ||||
|   - Optionally ChaCha20Poly1305 (via `chacha20poly1305`) | ||||
| - **Password Key Derivation:** | ||||
|   - PBKDF2-HMAC-SHA256 (via `pbkdf2`) | ||||
|   - Optionally scrypt (via `scrypt`) | ||||
| - **Asymmetric Keypairs:** | ||||
|   - secp256k1 (via `k256`) for Ethereum/EVM | ||||
|   - Ed25519 (via `ed25519-dalek`) for general-purpose signatures | ||||
| - **Randomness:** | ||||
|   - Use `rand_core` and `getrandom` for secure RNG in both native and WASM | ||||
|  | ||||
| **Feature-to-Algorithm Mapping:** | ||||
| | Feature                | Crate(s)              | Algorithm(s)              | | ||||
| |------------------------|-----------------------|---------------------------| | ||||
| | Vault encryption       | aes-gcm, chacha20poly1305 | AES-256-GCM, ChaCha20Poly1305 | | ||||
| | Password KDF           | pbkdf2, scrypt        | PBKDF2-HMAC-SHA256, scrypt| | ||||
| | Symmetric encryption   | aes-gcm, chacha20poly1305 | AES-256-GCM, ChaCha20Poly1305 | | ||||
| | secp256k1 keypairs     | k256                  | secp256k1 ECDSA           | | ||||
| | Ed25519 keypairs       | ed25519-dalek         | Ed25519                   | | ||||
| | Randomness             | rand_core, getrandom  | OS RNG                    | | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 7. WASM & Native Considerations | ||||
| - Use only WASM-compatible crypto crates (`aes-gcm`, `chacha20poly1305`, `k256`, `ed25519-dalek`, etc). | ||||
| - Use `wasm-bindgen`/`wasm-bindgen-futures` for browser interop. | ||||
| - Use `tokio::task::spawn_blocking` for blocking crypto on native. | ||||
| - All APIs are async and runtime-agnostic. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Future Extensions | ||||
| - Multi-user vaults (multi-password, access control) | ||||
| - Hardware-backed key storage (YubiKey, WebAuthn) | ||||
| - Key rotation and auditing | ||||
| - Pluggable crypto algorithms | ||||
| - Advanced metadata and tagging | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 7. References | ||||
| - See `docs/Architecture.md` and `docs/kvstore-vault-architecture.md` for high-level design and rationale. | ||||
| - Crypto patterns inspired by industry best practices (e.g., Wire, Signal, Bitwarden). | ||||
| @@ -8,24 +8,21 @@ path = "src/lib.rs" | ||||
|  | ||||
| [dependencies] | ||||
| async-trait = "0.1" | ||||
| sled = { version = "0.34", optional = true } | ||||
| idb = { version = "0.4", optional = true } | ||||
| js-sys = "0.3" | ||||
| wasm-bindgen = "0.2" | ||||
| wasm-bindgen-futures = "0.4" | ||||
| thiserror = "1" | ||||
| tempfile = "3" | ||||
|  | ||||
| [features] | ||||
| default = [] | ||||
| native = ["sled", "tokio"] | ||||
| web = ["idb"] | ||||
|  | ||||
| [target.'cfg(not(target_arch = "wasm32"))'.dependencies] | ||||
| tokio = { version = "1.45", optional = true, default-features = false, features = ["rt-multi-thread", "macros"] } | ||||
| sled = { version = "0.34" } | ||||
| tokio = { version = "1", features = ["rt-multi-thread", "macros"] } | ||||
|  | ||||
| [target.'cfg(target_arch = "wasm32")'.dependencies] | ||||
| idb = "0.4" | ||||
|  | ||||
| [target.'cfg(target_arch = "wasm32")'.dev-dependencies] | ||||
| idb = { version = "0.4" } | ||||
| wasm-bindgen-test = "0.3" | ||||
|  | ||||
| [features] | ||||
| default = [] | ||||
| native = [] | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| //! | ||||
| //! # Runtime Requirement | ||||
| //! | ||||
| #![cfg(not(target_arch = "wasm32"))] | ||||
| //! **A Tokio runtime must be running to use this backend.** | ||||
| //! This library does not start or manage a runtime; it assumes that all async methods are called from within an existing Tokio runtime context (e.g., via `#[tokio::main]` or `tokio::test`). | ||||
| //! | ||||
| @@ -10,11 +11,18 @@ | ||||
| //! # Example | ||||
| //! | ||||
|  | ||||
| use crate::traits::KVStore; | ||||
| use crate::error::{KVError, Result}; | ||||
| //! Native backend for kvstore using sled | ||||
| //! Only compiled for non-wasm32 targets | ||||
|  | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| use crate::traits::KVStore; | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| use crate::error::{KVError, Result}; | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| use async_trait::async_trait; | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| use sled::Db; | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| use std::sync::Arc; | ||||
|  | ||||
| #[derive(Clone)] | ||||
|   | ||||
| @@ -16,6 +16,11 @@ use crate::error::Result; | ||||
| /// - contains_key (was exists) | ||||
| /// - keys | ||||
| /// - clear | ||||
| /// Async key-value store interface for both native and WASM backends. | ||||
| /// | ||||
| /// For native (non-wasm32) backends, implementers should be `Send + Sync` to support async usage. | ||||
| /// For WASM (wasm32) backends, `Send + Sync` is not required. | ||||
| #[async_trait::async_trait] | ||||
| pub trait KVStore { | ||||
|     async fn get(&self, key: &str) -> Result<Option<Vec<u8>>>; | ||||
|     async fn set(&self, key: &str, value: &[u8]) -> Result<()>; | ||||
|   | ||||
| @@ -13,12 +13,15 @@ | ||||
| //! | ||||
|  | ||||
|  | ||||
| //! WASM backend for kvstore using IndexedDB (idb crate) | ||||
| //! Only compiled for wasm32 targets | ||||
|  | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| use crate::traits::KVStore; | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| use crate::error::{KVError, Result}; | ||||
|  | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| use async_trait::async_trait; | ||||
|  | ||||
|  | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| use idb::{Database, TransactionMode, Factory}; | ||||
| #[cfg(target_arch = "wasm32")] | ||||
|   | ||||
| @@ -18,7 +18,10 @@ chacha20poly1305 = "0.10" | ||||
| k256 = { version = "0.13", features = ["ecdsa"] } | ||||
| ed25519-dalek = "2.1" | ||||
| rand_core = "0.6" | ||||
| log = "0.4" | ||||
| thiserror = "1" | ||||
| env_logger = "0.11" | ||||
| console_log = "1" | ||||
| serde = { version = "1", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| hex = "0.4" | ||||
|   | ||||
| @@ -12,22 +12,20 @@ | ||||
|  | ||||
| ## Logging Best Practices | ||||
|  | ||||
| This crate uses the [`log`](https://docs.rs/log) crate for all logging. To see logs in your application or tests, you must initialize a logger: | ||||
| This crate uses the [`log`](https://docs.rs/log) crate for logging. For native tests, use [`env_logger`](https://docs.rs/env_logger); for WASM tests, use [`console_log`](https://docs.rs/console_log). | ||||
|  | ||||
| - **Native (desktop/server):** | ||||
|   - Add `env_logger` as a dev-dependency. | ||||
|   - Initialize in your main or test: | ||||
|     ```rust | ||||
|     let _ = env_logger::builder().is_test(true).try_init(); | ||||
|     ``` | ||||
| - **WASM (browser):** | ||||
|   - Add `console_log` as a dev-dependency. | ||||
|   - Initialize in your main or test: | ||||
|     ```rust | ||||
|     console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); | ||||
|     ``` | ||||
| - Native (in tests): | ||||
|   ```rust | ||||
|   let _ = env_logger::builder().is_test(true).try_init(); | ||||
|   log::info!("test started"); | ||||
|   ``` | ||||
| - WASM (in tests): | ||||
|   ```rust | ||||
|   console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); | ||||
|   log::debug!("wasm test started"); | ||||
|   ``` | ||||
|  | ||||
| Then use logging macros (`log::debug!`, `log::info!`, `log::warn!`, `log::error!`) throughout your code and tests. | ||||
| Use `log::debug!`, `log::info!`, `log::error!`, etc., throughout the codebase for consistent and idiomatic logging. Do not prefix messages with [DEBUG], [ERROR], etc. The log level is handled by the logger. | ||||
|  | ||||
| ## Usage Example | ||||
|  | ||||
|   | ||||
							
								
								
									
										157
									
								
								vault/src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								vault/src/lib.rs
									
									
									
									
									
								
							| @@ -18,22 +18,7 @@ use crate::crypto::random_salt; | ||||
| use crate::crypto::cipher::{encrypt_chacha20, decrypt_chacha20, encrypt_aes_gcm, decrypt_aes_gcm}; | ||||
| use signature::SignatureEncoding; | ||||
| // TEMP: File-based debug logger for crypto troubleshooting | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| fn debug_log(msg: &str) { | ||||
|     use std::fs::OpenOptions; | ||||
|     use std::io::Write; | ||||
|     let mut f = OpenOptions::new() | ||||
|         .create(true) | ||||
|         .append(true) | ||||
|         .open("/tmp/vault_crypto_debug.log") | ||||
|         .unwrap(); | ||||
|     writeln!(f, "{}", msg).unwrap(); | ||||
| } | ||||
|  | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| fn debug_log(_msg: &str) { | ||||
|     // No-op in WASM | ||||
| } | ||||
| use log::{debug, info, error}; | ||||
|  | ||||
| /// Vault: Cryptographic keyspace and operations | ||||
| pub struct Vault<S: KVStore> { | ||||
| @@ -46,30 +31,30 @@ fn encrypt_with_nonce_prepended(key: &[u8], plaintext: &[u8], cipher: &str) -> R | ||||
|     use crate::crypto::random_salt; | ||||
|     use crate::crypto; | ||||
|     let nonce = random_salt(12); | ||||
|     debug_log(&format!("[DEBUG][ENCRYPT_HELPER] nonce: {}", hex::encode(&nonce))); | ||||
|     debug!("nonce: {}", hex::encode(&nonce)); | ||||
|     let (ct, _key_hex) = match cipher { | ||||
|         "chacha20poly1305" => { | ||||
|             let ct = encrypt_chacha20(key, plaintext, &nonce) | ||||
|                 .map_err(|e| VaultError::Crypto(e))?; | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ct: {}", hex::encode(&ct))); | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] key: {}", hex::encode(key))); | ||||
|             debug!("ct: {}", hex::encode(&ct)); | ||||
|             debug!("key: {}", hex::encode(key)); | ||||
|             (ct, hex::encode(key)) | ||||
|         }, | ||||
|         "aes-gcm" => { | ||||
|             let ct = encrypt_aes_gcm(key, plaintext, &nonce) | ||||
|                 .map_err(|e| VaultError::Crypto(e))?; | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ct: {}", hex::encode(&ct))); | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] key: {}", hex::encode(key))); | ||||
|             debug!("ct: {}", hex::encode(&ct)); | ||||
|             debug!("key: {}", hex::encode(key)); | ||||
|             (ct, hex::encode(key)) | ||||
|         }, | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] unsupported cipher: {}", cipher)); | ||||
|             debug!("unsupported cipher: {}", cipher); | ||||
|             return Err(VaultError::Other(format!("Unsupported cipher: {cipher}"))); | ||||
|         } | ||||
|     }; | ||||
|     let mut blob = nonce.clone(); | ||||
|     blob.extend_from_slice(&ct); | ||||
|     debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ENCRYPTED (nonce|ct): {}", hex::encode(&blob))); | ||||
|     debug!("ENCRYPTED (nonce|ct): {}", hex::encode(&blob)); | ||||
|     Ok(blob) | ||||
| } | ||||
|  | ||||
| @@ -82,50 +67,50 @@ impl<S: KVStore> Vault<S> { | ||||
|     pub async fn create_keyspace(&mut self, name: &str, password: &[u8], kdf: &str, cipher: &str, tags: Option<Vec<String>>) -> Result<(), VaultError> { | ||||
|         // Check if keyspace already exists | ||||
|         if self.storage.get(name).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?.is_some() { | ||||
|             debug_log(&format!("[DEBUG][CREATE_KEYSPACE] ERROR: keyspace '{}' already exists", name)); | ||||
|             debug!("keyspace '{}' already exists", name); | ||||
|             return Err(VaultError::Crypto("Keyspace already exists".to_string())); | ||||
|         } | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] entry: name={}", name)); | ||||
|         debug!("entry: name={}", name); | ||||
|         use crate::crypto::{random_salt, kdf}; | ||||
|         use crate::data::{KeyspaceMetadata, KeyspaceData}; | ||||
|         use serde_json; | ||||
|          | ||||
|         // 1. Generate salt | ||||
|         let salt = random_salt(16); | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] salt: {:?}", salt)); | ||||
|         debug!("salt: {:?}", salt); | ||||
|         // 2. Derive key | ||||
|         let key = match kdf { | ||||
|             "scrypt" => match kdf::derive_key_scrypt(password, &salt, 32) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][CREATE_KEYSPACE] kdf scrypt error: {}", e)); | ||||
|                     debug!("kdf scrypt error: {}", e); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             "pbkdf2" => kdf::derive_key_pbkdf2(password, &salt, 32, 10_000), | ||||
|             _ => { | ||||
|                 debug_log(&format!("[DEBUG][CREATE_KEYSPACE] unsupported KDF: {}", kdf)); | ||||
|                 debug!("unsupported KDF: {}", kdf); | ||||
|                 return Err(VaultError::Other(format!("Unsupported KDF: {kdf}"))); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] derived key: {} bytes", key.len())); | ||||
|         debug!("derived key: {} bytes", key.len()); | ||||
|         // 3. Prepare initial keyspace data | ||||
|         let keyspace_data = KeyspaceData { keypairs: vec![] }; | ||||
|         let plaintext = match serde_json::to_vec(&keyspace_data) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][CREATE_KEYSPACE] serde_json error: {}", e)); | ||||
|                 debug!("serde_json error: {}", e); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] plaintext serialized: {} bytes", plaintext.len())); | ||||
|         debug!("plaintext serialized: {} bytes", plaintext.len()); | ||||
|         // 4. Generate nonce (12 bytes for both ciphers) | ||||
|         let nonce = random_salt(12); | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] nonce: {}", hex::encode(&nonce))); | ||||
|         debug!("nonce: {}", hex::encode(&nonce)); | ||||
|         // 5. Encrypt | ||||
|         let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, cipher)?; | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] encrypted_blob: {} bytes", encrypted_blob.len())); | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] encrypted_blob (hex): {}", hex::encode(&encrypted_blob))); | ||||
|         debug!("encrypted_blob: {} bytes", encrypted_blob.len()); | ||||
|         debug!("encrypted_blob (hex): {}", hex::encode(&encrypted_blob)); | ||||
|         // 6. Compose metadata | ||||
|         let metadata = KeyspaceMetadata { | ||||
|             name: name.to_string(), | ||||
| @@ -140,12 +125,12 @@ impl<S: KVStore> Vault<S> { | ||||
|         let meta_bytes = match serde_json::to_vec(&metadata) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][CREATE_KEYSPACE] serde_json metadata error: {}", e)); | ||||
|                 debug!("serde_json metadata error: {}", e); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         self.storage.set(name, &meta_bytes).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|         debug_log("[DEBUG][CREATE_KEYSPACE] success"); | ||||
|         debug!("success"); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
| @@ -167,18 +152,15 @@ impl<S: KVStore> Vault<S> { | ||||
|  | ||||
|     /// Unlock a keyspace by name and password, returning the decrypted data | ||||
|     pub async fn unlock_keyspace(&self, name: &str, password: &[u8]) -> Result<KeyspaceData, VaultError> { | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] entry: name={} password={}", name, hex::encode(password))); | ||||
|         debug!("unlock_keyspace entry: name={}", name); | ||||
|         use crate::crypto::{kdf}; | ||||
|         use serde_json; | ||||
|         // 1. Fetch keyspace metadata | ||||
|         let meta_bytes = self.storage.get(name).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] got meta_bytes: {}", meta_bytes.as_ref().map(|v| v.len()).unwrap_or(0))); | ||||
|         let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(name.to_string()))?; | ||||
|         let metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?; | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] metadata: kdf={} cipher={} salt={:?} encrypted_blob_len={}", metadata.kdf, metadata.cipher, metadata.salt, metadata.encrypted_blob.len())); | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] ENCRYPTED_BLOB (hex): {}", hex::encode(&metadata.encrypted_blob))); | ||||
|         if metadata.salt.len() != 16 { | ||||
|             debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] ERROR: salt length {} != 16", metadata.salt.len())); | ||||
|             debug!("salt length {} != 16", metadata.salt.len()); | ||||
|             return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string())); | ||||
|         } | ||||
|         // 2. Derive key | ||||
| @@ -186,57 +168,57 @@ impl<S: KVStore> Vault<S> { | ||||
|             "scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] kdf scrypt error: {}", e)); | ||||
|                     debug!("kdf scrypt error: {}", e); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             "pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000), | ||||
|             _ => { | ||||
|                 debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] unsupported KDF: {}", metadata.kdf)); | ||||
|                 debug!("unsupported KDF: {}", metadata.kdf); | ||||
|                 return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf))); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] derived key: {} bytes", key.len())); | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] derived key (hex): {}", hex::encode(&key))); | ||||
|         debug!("derived key: {} bytes", key.len()); | ||||
|         debug!("derived key (hex): {}", hex::encode(&key)); | ||||
|         // 3. Split nonce and ciphertext | ||||
|         let ciphertext = &metadata.encrypted_blob; | ||||
|         if ciphertext.len() < 12 { | ||||
|             debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] ciphertext too short: {}", ciphertext.len())); | ||||
|             debug!("ciphertext too short: {}", ciphertext.len()); | ||||
|             return Err(VaultError::Crypto("Ciphertext too short".to_string())); | ||||
|         } | ||||
|         let (nonce, ct) = ciphertext.split_at(12); | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] nonce: {} ct: {}", hex::encode(nonce), hex::encode(ct))); | ||||
|         debug!("nonce: {}", hex::encode(nonce)); | ||||
|         // 4. Decrypt | ||||
|         let plaintext = match metadata.cipher.as_str() { | ||||
|             "chacha20poly1305" => match decrypt_chacha20(&key, ct, nonce) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] chacha20poly1305 error: {}", e)); | ||||
|                     debug!("chacha20poly1305 error: {}", e); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             "aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] aes-gcm error: {}", e)); | ||||
|                     debug!("aes-gcm error: {}", e); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             _ => { | ||||
|                 debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] unsupported cipher: {}", metadata.cipher)); | ||||
|                 debug!("unsupported cipher: {}", metadata.cipher); | ||||
|                 return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher))); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] plaintext decrypted: {} bytes", plaintext.len())); | ||||
|         debug!("plaintext decrypted: {} bytes", plaintext.len()); | ||||
|         // 4. Deserialize keyspace data | ||||
|         let keyspace_data: KeyspaceData = match serde_json::from_slice(&plaintext) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] serde_json data error: {}", e)); | ||||
|                 debug!("serde_json data error: {}", e); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug_log("[DEBUG][UNLOCK_KEYSPACE] success"); | ||||
|         debug!("success"); | ||||
|         Ok(keyspace_data) | ||||
|     } | ||||
|  | ||||
| @@ -316,17 +298,17 @@ impl<S: KVStore> Vault<S> { | ||||
|  | ||||
|     /// Save the updated keyspace data (helper) | ||||
|     async fn save_keyspace(&mut self, keyspace: &str, password: &[u8], data: &KeyspaceData) -> Result<(), VaultError> { | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] entry: keyspace={} password={}", keyspace, hex::encode(password))); | ||||
|         debug!("save_keyspace entry: keyspace={}", keyspace); | ||||
|         use crate::crypto::kdf; | ||||
|         use serde_json; | ||||
|         // 1. Fetch metadata | ||||
|         let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] got meta_bytes: {}", meta_bytes.as_ref().map(|v| v.len()).unwrap_or(0))); | ||||
|         debug!("got meta_bytes: {}", meta_bytes.as_ref().map(|v| v.len()).unwrap_or(0)); | ||||
|         let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(keyspace.to_string()))?; | ||||
|         let mut metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] metadata: kdf={} cipher={} salt={:?}", metadata.kdf, metadata.cipher, metadata.salt)); | ||||
|         debug!("metadata: kdf={} cipher={} salt={:?}", metadata.kdf, metadata.cipher, metadata.salt); | ||||
|         if metadata.salt.len() != 16 { | ||||
|             debug_log(&format!("[DEBUG][SAVE_KEYSPACE] ERROR: salt length {} != 16", metadata.salt.len())); | ||||
|             debug!("salt length {} != 16", metadata.salt.len()); | ||||
|             return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string())); | ||||
|         } | ||||
|         // 2. Derive key | ||||
| @@ -334,43 +316,43 @@ impl<S: KVStore> Vault<S> { | ||||
|             "scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][SAVE_KEYSPACE] kdf scrypt error: {}", e)); | ||||
|                     debug!("kdf scrypt error: {}", e); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             "pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000), | ||||
|             _ => { | ||||
|                 debug_log(&format!("[DEBUG][SAVE_KEYSPACE] unsupported KDF: {}", metadata.kdf)); | ||||
|                 debug!("unsupported KDF: {}", metadata.kdf); | ||||
|                 return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf))); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] derived key: {} bytes", key.len())); | ||||
|         debug!("derived key: {} bytes", key.len()); | ||||
|         // 3. Serialize plaintext | ||||
|         let plaintext = match serde_json::to_vec(data) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][SAVE_KEYSPACE] serde_json data error: {}", e)); | ||||
|                 debug!("serde_json data error: {}", e); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] plaintext serialized: {} bytes", plaintext.len())); | ||||
|         debug!("plaintext serialized: {} bytes", plaintext.len()); | ||||
|         // 4. Generate nonce | ||||
|         let nonce = random_salt(12); | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] nonce: {}", hex::encode(&nonce))); | ||||
|         debug!("nonce: {}", hex::encode(&nonce)); | ||||
|         // 5. Encrypt | ||||
|         let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, &metadata.cipher)?; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] encrypted_blob: {} bytes", encrypted_blob.len())); | ||||
|         debug!("encrypted_blob: {} bytes", encrypted_blob.len()); | ||||
|         // 6. Store new encrypted blob | ||||
|         metadata.encrypted_blob = encrypted_blob; | ||||
|         let meta_bytes = match serde_json::to_vec(&metadata) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][SAVE_KEYSPACE] serde_json metadata error: {}", e)); | ||||
|                 debug!("serde_json metadata error: {}", e); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         self.storage.set(keyspace, &meta_bytes).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|         debug_log("[DEBUG][SAVE_KEYSPACE] success"); | ||||
|         debug!("success"); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
| @@ -433,62 +415,62 @@ impl<S: KVStore> Vault<S> { | ||||
| /// Encrypt a message using the keyspace symmetric cipher | ||||
| /// (for simplicity, uses keyspace password-derived key) | ||||
| pub async fn encrypt(&self, keyspace: &str, password: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|     debug_log("[DEBUG][ENTER] encrypt"); | ||||
|     debug_log(&format!("[DEBUG][encrypt] keyspace={}", keyspace)); | ||||
|     debug!("encrypt"); | ||||
|     debug!("keyspace={}", keyspace); | ||||
|     use crate::crypto::{kdf}; | ||||
|     // 1. Load keyspace metadata | ||||
|     let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|     let meta_bytes = match meta_bytes { | ||||
|         Some(val) => val, | ||||
|         None => { | ||||
|             debug_log("[DEBUG][ERR] encrypt: keyspace not found"); | ||||
|             debug!("keyspace not found"); | ||||
|             return Err(VaultError::Other("Keyspace not found".to_string())); | ||||
|         } | ||||
|     }; | ||||
|     let meta: KeyspaceMetadata = match serde_json::from_slice(&meta_bytes) { | ||||
|         Ok(val) => val, | ||||
|         Err(e) => { | ||||
|             debug_log(&format!("[DEBUG][ERR] encrypt: serialization error: {}", e)); | ||||
|             debug!("serialization error: {}", e); | ||||
|             return Err(VaultError::Serialization(e.to_string())); | ||||
|         } | ||||
|     }; | ||||
|     debug_log(&format!("[DEBUG][encrypt] salt={:?} cipher={} (hex salt: {})", meta.salt, meta.cipher, hex::encode(&meta.salt))); | ||||
|     debug!("salt={:?} cipher={} (hex salt: {})", meta.salt, meta.cipher, hex::encode(&meta.salt)); | ||||
|     // 2. Derive key | ||||
|     let key = match meta.kdf.as_str() { | ||||
|         "scrypt" => match kdf::derive_key_scrypt(password, &meta.salt, 32) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] encrypt: kdf scrypt error: {}", e)); | ||||
|                 debug!("kdf scrypt error: {}", e); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         "pbkdf2" => kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000), | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ERR] encrypt: unsupported KDF: {}", meta.kdf)); | ||||
|             debug!("unsupported KDF: {}", meta.kdf); | ||||
|             return Err(VaultError::Other(format!("Unsupported KDF: {}", meta.kdf))); | ||||
|         } | ||||
|     }; | ||||
|     // 3. Generate nonce | ||||
|     let nonce = random_salt(12); | ||||
|     debug_log(&format!("[DEBUG][encrypt] nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce))); | ||||
|     debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce)); | ||||
|     // 4. Encrypt | ||||
|     let ciphertext = match meta.cipher.as_str() { | ||||
|         "chacha20poly1305" => match encrypt_chacha20(&key, plaintext, &nonce) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] encrypt: chacha20poly1305 error: {}", e)); | ||||
|                 debug!("chacha20poly1305 error: {}", e); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         "aes-gcm" => match encrypt_aes_gcm(&key, plaintext, &nonce) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] encrypt: aes-gcm error: {}", e)); | ||||
|                 debug!("aes-gcm error: {}", e); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ERR] encrypt: unsupported cipher: {}", meta.cipher)); | ||||
|             debug!("unsupported cipher: {}", meta.cipher); | ||||
|             return Err(VaultError::Other(format!("Unsupported cipher: {}", meta.cipher))); | ||||
|         } | ||||
|     }; | ||||
| @@ -501,58 +483,57 @@ pub async fn encrypt(&self, keyspace: &str, password: &[u8], plaintext: &[u8]) - | ||||
| /// Decrypt a message using the keyspace symmetric cipher | ||||
| /// (for simplicity, uses keyspace password-derived key) | ||||
| pub async fn decrypt(&self, keyspace: &str, password: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|     debug_log("[DEBUG][ENTER] decrypt"); | ||||
|     debug_log(&format!("[DEBUG][decrypt] keyspace={}", keyspace)); | ||||
|     debug!("decrypt"); | ||||
|     debug!("keyspace={}", keyspace); | ||||
|     use crate::crypto::{kdf}; | ||||
|     // 1. Fetch metadata | ||||
|     let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|     let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(keyspace.to_string()))?; | ||||
|     let metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?; | ||||
|     debug_log(&format!("[DEBUG][decrypt] salt={:?} cipher={} (hex salt: {})", metadata.salt, metadata.cipher, hex::encode(&metadata.salt))); | ||||
|     debug!("salt={:?} cipher={} (hex salt: {})", metadata.salt, metadata.cipher, hex::encode(&metadata.salt)); | ||||
|     // 2. Derive key | ||||
|     let key = match metadata.kdf.as_str() { | ||||
|         "scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] decrypt: storage error: {:?}", e)); | ||||
|                 debug!("storage error: {:?}", e); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         "pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000), | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ERR] decrypt: unsupported KDF: {}", metadata.kdf)); | ||||
|             debug!("unsupported KDF: {}", metadata.kdf); | ||||
|             return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf))); | ||||
|         } | ||||
|     }; | ||||
|     // 3. Split nonce and ciphertext | ||||
|     if ciphertext.len() < 12 { | ||||
|         debug_log(&format!("[DEBUG][ERR] decrypt: ciphertext too short: {}", ciphertext.len())); | ||||
|         debug!("ciphertext too short: {}", ciphertext.len()); | ||||
|         return Err(VaultError::Crypto("Ciphertext too short".to_string())); | ||||
|     } | ||||
|     let (nonce, ct) = ciphertext.split_at(12); | ||||
|     debug_log(&format!("[DEBUG][decrypt] nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce))); | ||||
|     debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce)); | ||||
|     // 4. Decrypt | ||||
|     let plaintext = match metadata.cipher.as_str() { | ||||
|         "chacha20poly1305" => match decrypt_chacha20(&key, ct, nonce) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] decrypt: chacha20poly1305 error: {}", e)); | ||||
|                 debug!("chacha20poly1305 error: {}", e); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         "aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] decrypt: aes-gcm error: {}", e)); | ||||
|                 debug!("aes-gcm error: {}", e); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ERR] decrypt: unsupported cipher: {}", metadata.cipher)); | ||||
|             debug!("unsupported cipher: {}", metadata.cipher); | ||||
|             return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher))); | ||||
|         } | ||||
|     }; | ||||
|     Ok(plaintext) | ||||
| } | ||||
|  | ||||
| } // <-- Close the impl block | ||||
|   | ||||
| @@ -3,20 +3,12 @@ | ||||
| use vault::{Vault, KeyType, KeyMetadata}; | ||||
| use kvstore::native::NativeStore; | ||||
|  | ||||
| fn debug_log(msg: &str) { | ||||
|     use std::fs::OpenOptions; | ||||
|     use std::io::Write; | ||||
|     let mut f = OpenOptions::new() | ||||
|         .create(true) | ||||
|         .append(true) | ||||
|         .open("vault_crypto_debug.log") | ||||
|         .unwrap(); | ||||
|     writeln!(f, "{}", msg).unwrap(); | ||||
| } | ||||
| use log::{debug, info, error}; | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn test_keypair_management_and_crypto() { | ||||
|     debug_log("[DEBUG][TEST] test_keypair_management_and_crypto started"); | ||||
|     let _ = env_logger::builder().is_test(true).try_init(); | ||||
|     debug!("test_keypair_management_and_crypto started"); | ||||
|     // Use NativeStore for native tests | ||||
|     #[cfg(not(target_arch = "wasm32"))] | ||||
|     let store = NativeStore::open("vault_native_test").expect("Failed to open native store"); | ||||
| @@ -27,39 +19,39 @@ async fn test_keypair_management_and_crypto() { | ||||
|     let keyspace = &format!("testspace_{}", chrono::Utc::now().timestamp_nanos()); | ||||
|     let password = b"supersecret"; | ||||
|  | ||||
|     debug_log(&format!("[DEBUG][TEST] keyspace: {} password: {}", keyspace, hex::encode(password))); | ||||
|     debug_log("[DEBUG][TEST] before create_keyspace"); | ||||
|     debug!("keyspace: {} password: {}", keyspace, hex::encode(password)); | ||||
|     debug!("before create_keyspace"); | ||||
|     vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await.unwrap(); | ||||
|  | ||||
|     debug_log(&format!("[DEBUG][TEST] after create_keyspace: keyspace={} password={}", keyspace, hex::encode(password))); | ||||
|     debug_log("[DEBUG][TEST] before add Ed25519 keypair"); | ||||
|     debug!("after create_keyspace: keyspace={} password={}", keyspace, hex::encode(password)); | ||||
|     debug!("before add Ed25519 keypair"); | ||||
|     let key_id = vault.add_keypair(keyspace, password, KeyType::Ed25519, Some(KeyMetadata { name: Some("edkey".into()), created_at: None, tags: None })).await; | ||||
|     match &key_id { | ||||
|         Ok(_) => debug_log("[DEBUG][TEST] after add Ed25519 keypair (Ok)"), | ||||
|         Err(e) => debug_log(&format!("[DEBUG][TEST] after add Ed25519 keypair (Err): {:?}", e)), | ||||
|         Ok(_) => debug!("after add Ed25519 keypair (Ok)"), | ||||
|         Err(e) => debug!("after add Ed25519 keypair (Err): {:?}", e), | ||||
|     } | ||||
|     let key_id = key_id.unwrap(); | ||||
|     debug_log("[DEBUG][TEST] before add secp256k1 keypair"); | ||||
|     debug!("before add secp256k1 keypair"); | ||||
|     let secp_id = vault.add_keypair(keyspace, password, KeyType::Secp256k1, Some(KeyMetadata { name: Some("secpkey".into()), created_at: None, tags: None })).await.unwrap(); | ||||
|  | ||||
|     debug_log("[DEBUG][TEST] before list_keypairs"); | ||||
|     debug!("before list_keypairs"); | ||||
|     let keys = vault.list_keypairs(keyspace, password).await.unwrap(); | ||||
|     assert_eq!(keys.len(), 2); | ||||
|  | ||||
|     debug_log("[DEBUG][TEST] before export Ed25519 keypair"); | ||||
|     debug!("before export Ed25519 keypair"); | ||||
|     let (priv_bytes, pub_bytes) = vault.export_keypair(keyspace, password, &key_id).await.unwrap(); | ||||
|     assert!(!priv_bytes.is_empty() && !pub_bytes.is_empty()); | ||||
|  | ||||
|     debug_log("[DEBUG][TEST] before sign Ed25519"); | ||||
|     debug!("before sign Ed25519"); | ||||
|     let msg = b"hello world"; | ||||
|     let sig = vault.sign(keyspace, password, &key_id, msg).await.unwrap(); | ||||
|     debug_log("[DEBUG][TEST] before verify Ed25519"); | ||||
|     debug!("before verify Ed25519"); | ||||
|     let ok = vault.verify(keyspace, password, &key_id, msg, &sig).await.unwrap(); | ||||
|     assert!(ok); | ||||
|  | ||||
|     debug_log("[DEBUG][TEST] before sign secp256k1"); | ||||
|     debug!("before sign secp256k1"); | ||||
|     let sig2 = vault.sign(keyspace, password, &secp_id, msg).await.unwrap(); | ||||
|     debug_log("[DEBUG][TEST] before verify secp256k1"); | ||||
|     debug!("before verify secp256k1"); | ||||
|     let ok2 = vault.verify(keyspace, password, &secp_id, msg, &sig2).await.unwrap(); | ||||
|     assert!(ok2); | ||||
|  | ||||
|   | ||||
| @@ -10,107 +10,108 @@ wasm_bindgen_test_configure!(run_in_browser); | ||||
| #[wasm_bindgen_test(async)] | ||||
| async fn wasm_test_keypair_management_and_crypto() { | ||||
|     console_error_panic_hook::set_once(); | ||||
|     console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); | ||||
|     let store = WasmStore::open("vault_idb_test").await.expect("Failed to open IndexedDB store"); | ||||
|     let mut vault = Vault::new(store); | ||||
|     let keyspace = "wasmspace"; | ||||
|     let password = b"supersecret"; | ||||
|     println!("[DEBUG] Initialized vault and IndexedDB store"); | ||||
|     log::debug!("Initialized vault and IndexedDB store"); | ||||
|  | ||||
|     // Step 1: Create keyspace | ||||
|     match vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await { | ||||
|         Ok(_) => println!("[DEBUG] Created keyspace"), | ||||
|         Err(e) => { println!("[ERROR] Failed to create keyspace: {:?}", e); return; } | ||||
|         Ok(_) => log::debug!("Created keyspace"), | ||||
|         Err(e) => { log::debug!("Failed to create keyspace: {:?}", e); return; } | ||||
|     } | ||||
|  | ||||
|     // Step 2: Add Ed25519 keypair | ||||
|     let key_id = match vault.add_keypair(keyspace, password, KeyType::Ed25519, Some(KeyMetadata { name: Some("edkey".into()), created_at: None, tags: None })).await { | ||||
|         Ok(id) => { println!("[DEBUG] Added Ed25519 keypair: {}", id); id }, | ||||
|         Err(e) => { println!("[ERROR] Failed to add Ed25519 keypair: {:?}", e); return; } | ||||
|         Ok(id) => { log::debug!("Added Ed25519 keypair: {}", id); id }, | ||||
|         Err(e) => { log::debug!("Failed to add Ed25519 keypair: {:?}", e); return; } | ||||
|     }; | ||||
|  | ||||
|     // Step 3: Add Secp256k1 keypair | ||||
|     let secp_id = match vault.add_keypair(keyspace, password, KeyType::Secp256k1, Some(KeyMetadata { name: Some("secpkey".into()), created_at: None, tags: None })).await { | ||||
|         Ok(id) => { println!("[DEBUG] Added Secp256k1 keypair: {}", id); id }, | ||||
|         Err(e) => { println!("[ERROR] Failed to add Secp256k1 keypair: {:?}", e); return; } | ||||
|         Ok(id) => { log::debug!("Added Secp256k1 keypair: {}", id); id }, | ||||
|         Err(e) => { log::debug!("Failed to add Secp256k1 keypair: {:?}", e); return; } | ||||
|     }; | ||||
|  | ||||
|     // Step 4: List keypairs | ||||
|     let keys = match vault.list_keypairs(keyspace, password).await { | ||||
|         Ok(keys) => { println!("[DEBUG] Listed keypairs: {:?}", keys); keys }, | ||||
|         Err(e) => { println!("[ERROR] Failed to list keypairs: {:?}", e); return; } | ||||
|         Ok(keys) => { log::debug!("Listed keypairs: {:?}", keys); keys }, | ||||
|         Err(e) => { log::debug!("Failed to list keypairs: {:?}", e); return; } | ||||
|     }; | ||||
|     if keys.len() != 2 { | ||||
|         println!("[ERROR] Expected 2 keypairs, got {}", keys.len()); | ||||
|         log::debug!("Expected 2 keypairs, got {}", keys.len()); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 5: Export Ed25519 keypair | ||||
|     let (priv_bytes, pub_bytes) = match vault.export_keypair(keyspace, password, &key_id).await { | ||||
|         Ok((priv_bytes, pub_bytes)) => { | ||||
|             println!("[DEBUG] Exported Ed25519 keypair, priv: {} bytes, pub: {} bytes", priv_bytes.len(), pub_bytes.len()); | ||||
|             log::debug!("Exported Ed25519 keypair, priv: {} bytes, pub: {} bytes", priv_bytes.len(), pub_bytes.len()); | ||||
|             (priv_bytes, pub_bytes) | ||||
|         }, | ||||
|         Err(e) => { println!("[ERROR] Failed to export Ed25519 keypair: {:?}", e); return; } | ||||
|         Err(e) => { log::debug!("Failed to export Ed25519 keypair: {:?}", e); return; } | ||||
|     }; | ||||
|     if priv_bytes.is_empty() || pub_bytes.is_empty() { | ||||
|         println!("[ERROR] Exported Ed25519 keypair bytes are empty"); | ||||
|         log::debug!("Exported Ed25519 keypair bytes are empty"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 6: Sign and verify with Ed25519 | ||||
|     let msg = b"hello wasm"; | ||||
|     let sig = match vault.sign(keyspace, password, &key_id, msg).await { | ||||
|         Ok(sig) => { println!("[DEBUG] Signed message with Ed25519"); sig }, | ||||
|         Err(e) => { println!("[ERROR] Failed to sign with Ed25519: {:?}", e); return; } | ||||
|         Ok(sig) => { log::debug!("Signed message with Ed25519"); sig }, | ||||
|         Err(e) => { log::debug!("Failed to sign with Ed25519: {:?}", e); return; } | ||||
|     }; | ||||
|     let ok = match vault.verify(keyspace, password, &key_id, msg, &sig).await { | ||||
|         Ok(ok) => { println!("[DEBUG] Verified Ed25519 signature: {}", ok); ok }, | ||||
|         Err(e) => { println!("[ERROR] Failed to verify Ed25519 signature: {:?}", e); return; } | ||||
|         Ok(ok) => { log::debug!("Verified Ed25519 signature: {}", ok); ok }, | ||||
|         Err(e) => { log::debug!("Failed to verify Ed25519 signature: {:?}", e); return; } | ||||
|     }; | ||||
|     if !ok { | ||||
|         println!("[ERROR] Ed25519 signature verification failed"); | ||||
|         log::debug!("Ed25519 signature verification failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 7: Sign and verify with Secp256k1 | ||||
|     let sig2 = match vault.sign(keyspace, password, &secp_id, msg).await { | ||||
|         Ok(sig) => { println!("[DEBUG] Signed message with Secp256k1"); sig }, | ||||
|         Err(e) => { println!("[ERROR] Failed to sign with Secp256k1: {:?}", e); return; } | ||||
|         Ok(sig) => { log::debug!("Signed message with Secp256k1"); sig }, | ||||
|         Err(e) => { log::debug!("Failed to sign with Secp256k1: {:?}", e); return; } | ||||
|     }; | ||||
|     let ok2 = match vault.verify(keyspace, password, &secp_id, msg, &sig2).await { | ||||
|         Ok(ok) => { println!("[DEBUG] Verified Secp256k1 signature: {}", ok); ok }, | ||||
|         Err(e) => { println!("[ERROR] Failed to verify Secp256k1 signature: {:?}", e); return; } | ||||
|         Ok(ok) => { log::debug!("Verified Secp256k1 signature: {}", ok); ok }, | ||||
|         Err(e) => { log::debug!("Failed to verify Secp256k1 signature: {:?}", e); return; } | ||||
|     }; | ||||
|     if !ok2 { | ||||
|         println!("[ERROR] Secp256k1 signature verification failed"); | ||||
|         log::debug!("Secp256k1 signature verification failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 8: Encrypt and decrypt | ||||
|     let ciphertext = match vault.encrypt(keyspace, password, msg).await { | ||||
|         Ok(ct) => { println!("[DEBUG] Encrypted message"); ct }, | ||||
|         Err(e) => { println!("[ERROR] Failed to encrypt message: {:?}", e); return; } | ||||
|         Ok(ct) => { log::debug!("Encrypted message"); ct }, | ||||
|         Err(e) => { log::debug!("Failed to encrypt message: {:?}", e); return; } | ||||
|     }; | ||||
|     let plaintext = match vault.decrypt(keyspace, password, &ciphertext).await { | ||||
|         Ok(pt) => { println!("[DEBUG] Decrypted message"); pt }, | ||||
|         Err(e) => { println!("[ERROR] Failed to decrypt message: {:?}", e); return; } | ||||
|         Ok(pt) => { log::debug!("Decrypted message"); pt }, | ||||
|         Err(e) => { log::debug!("Failed to decrypt message: {:?}", e); return; } | ||||
|     }; | ||||
|     if plaintext != msg { | ||||
|         println!("[ERROR] Decrypted message does not match original"); | ||||
|         log::debug!("Decrypted message does not match original"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 9: Remove Ed25519 keypair | ||||
|     match vault.remove_keypair(keyspace, password, &key_id).await { | ||||
|         Ok(_) => println!("[DEBUG] Removed Ed25519 keypair"), | ||||
|         Err(e) => { println!("[ERROR] Failed to remove Ed25519 keypair: {:?}", e); return; } | ||||
|         Ok(_) => log::debug!("Removed Ed25519 keypair"), | ||||
|         Err(e) => { log::debug!("Failed to remove Ed25519 keypair: {:?}", e); return; } | ||||
|     } | ||||
|     let keys = match vault.list_keypairs(keyspace, password).await { | ||||
|         Ok(keys) => { println!("[DEBUG] Listed keypairs after removal: {:?}", keys); keys }, | ||||
|         Err(e) => { println!("[ERROR] Failed to list keypairs after removal: {:?}", e); return; } | ||||
|         Ok(keys) => { log::debug!("Listed keypairs after removal: {:?}", keys); keys }, | ||||
|         Err(e) => { log::debug!("Failed to list keypairs after removal: {:?}", e); return; } | ||||
|     }; | ||||
|     if keys.len() != 1 { | ||||
|         println!("[ERROR] Expected 1 keypair after removal, got {}", keys.len()); | ||||
|         log::debug!("Expected 1 keypair after removal, got {}", keys.len()); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,121 +0,0 @@ | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error") | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error") | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error") | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error") | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error") | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error") | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace_1747303028801159410 password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace_1747303028801159410 password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Ok) | ||||
| [DEBUG][TEST] before add secp256k1 keypair | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace_1747303185421006752 password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace_1747303185421006752 password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Ok) | ||||
| [DEBUG][TEST] before add secp256k1 keypair | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace_1747303743371199079 password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace_1747303743371199079 password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Ok) | ||||
| [DEBUG][TEST] before add secp256k1 keypair | ||||
| [DEBUG][TEST] before list_keypairs | ||||
| [DEBUG][TEST] before export Ed25519 keypair | ||||
| [DEBUG][TEST] before sign Ed25519 | ||||
| [DEBUG][TEST] before verify Ed25519 | ||||
| [DEBUG][TEST] before sign secp256k1 | ||||
| [DEBUG][TEST] before verify secp256k1 | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace_1747304555613901420 password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace_1747304555613901420 password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Ok) | ||||
| [DEBUG][TEST] before add secp256k1 keypair | ||||
| [DEBUG][TEST] before list_keypairs | ||||
| [DEBUG][TEST] before export Ed25519 keypair | ||||
| [DEBUG][TEST] before sign Ed25519 | ||||
| [DEBUG][TEST] before verify Ed25519 | ||||
| [DEBUG][TEST] before sign secp256k1 | ||||
| [DEBUG][TEST] before verify secp256k1 | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace_1747310570021504019 password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace_1747310570021504019 password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Ok) | ||||
| [DEBUG][TEST] before add secp256k1 keypair | ||||
| [DEBUG][TEST] before list_keypairs | ||||
| [DEBUG][TEST] before export Ed25519 keypair | ||||
| [DEBUG][TEST] before sign Ed25519 | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace_1747310702751219893 password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace_1747310702751219893 password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Ok) | ||||
| [DEBUG][TEST] before add secp256k1 keypair | ||||
| [DEBUG][TEST] before list_keypairs | ||||
| [DEBUG][TEST] before export Ed25519 keypair | ||||
| [DEBUG][TEST] before sign Ed25519 | ||||
| [DEBUG][TEST] before verify Ed25519 | ||||
| [DEBUG][TEST] before sign secp256k1 | ||||
| [DEBUG][TEST] before verify secp256k1 | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace_1747311247795239358 password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace_1747311247795239358 password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Ok) | ||||
| [DEBUG][TEST] before add secp256k1 keypair | ||||
| [DEBUG][TEST] before list_keypairs | ||||
| [DEBUG][TEST] before export Ed25519 keypair | ||||
| [DEBUG][TEST] before sign Ed25519 | ||||
| [DEBUG][TEST] before verify Ed25519 | ||||
| [DEBUG][TEST] before sign secp256k1 | ||||
| [DEBUG][TEST] before verify secp256k1 | ||||
| [DEBUG][TEST] test_keypair_management_and_crypto started | ||||
| [DEBUG][TEST] keyspace: testspace_1747311770351800477 password: 7375706572736563726574 | ||||
| [DEBUG][TEST] before create_keyspace | ||||
| [DEBUG][TEST] after create_keyspace: keyspace=testspace_1747311770351800477 password=7375706572736563726574 | ||||
| [DEBUG][TEST] before add Ed25519 keypair | ||||
| [DEBUG][TEST] after add Ed25519 keypair (Ok) | ||||
| [DEBUG][TEST] before add secp256k1 keypair | ||||
| [DEBUG][TEST] before list_keypairs | ||||
| [DEBUG][TEST] before export Ed25519 keypair | ||||
| [DEBUG][TEST] before sign Ed25519 | ||||
| [DEBUG][TEST] before verify Ed25519 | ||||
| [DEBUG][TEST] before sign secp256k1 | ||||
| [DEBUG][TEST] before verify secp256k1 | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								vault/vault_native_test/snap.000000000000E741
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vault/vault_native_test/snap.000000000000E741
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										156
									
								
								vault_crypto_debug.log
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								vault_crypto_debug.log
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
|    Compiling vault v0.1.0 (/home/sameh/sal-modular/vault) | ||||
| warning: unused import: `KeyInit` | ||||
|  --> vault/src/crypto.rs:5:26 | ||||
|   | | ||||
| 5 | use aes_gcm::{Aes256Gcm, KeyInit as AesKeyInit}; | ||||
|   |                          ^^^^^^^ | ||||
|   | | ||||
|   = note: `#[warn(unused_imports)]` on by default | ||||
|  | ||||
| warning: unused import: `signature::SignatureEncoding` | ||||
|    --> vault/src/lib.rs:130:13 | ||||
|     | | ||||
| 130 |         use signature::SignatureEncoding; | ||||
|     |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| warning: unused import: `k256::elliptic_curve::sec1::ToEncodedPoint` | ||||
|    --> vault/src/lib.rs:148:21 | ||||
|     | | ||||
| 148 |                 use k256::elliptic_curve::sec1::ToEncodedPoint; | ||||
|     |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| warning: enum `KdfType` is never used | ||||
|   --> vault/src/crypto.rs:14:14 | ||||
|    | | ||||
| 14 |     pub enum KdfType { | ||||
|    |              ^^^^^^^ | ||||
|    | | ||||
|    = note: `#[warn(dead_code)]` on by default | ||||
|  | ||||
| warning: enum `CipherType` is never used | ||||
|   --> vault/src/crypto.rs:36:14 | ||||
|    | | ||||
| 36 |     pub enum CipherType { | ||||
|    |              ^^^^^^^^^^ | ||||
|  | ||||
| warning: struct `SessionManager` is never constructed | ||||
|  --> vault/src/session.rs:6:12 | ||||
|   | | ||||
| 6 | pub struct SessionManager { | ||||
|   |            ^^^^^^^^^^^^^^ | ||||
|  | ||||
| warning: associated function `new` is never used | ||||
|   --> vault/src/session.rs:14:12 | ||||
|    | | ||||
| 13 | impl SessionManager { | ||||
|    | ------------------- associated function in this implementation | ||||
| 14 |     pub fn new() -> Self { | ||||
|    |            ^^^ | ||||
|  | ||||
| warning: `vault` (lib) generated 7 warnings | ||||
| warning: unused import: `async_trait::async_trait` | ||||
|  --> vault/tests/mock_store.rs:2:5 | ||||
|   | | ||||
| 2 | use async_trait::async_trait; | ||||
|   |     ^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|   | | ||||
|   = note: `#[warn(unused_imports)]` on by default | ||||
|  | ||||
| warning: `vault` (test "keypair_management") generated 1 warning (run `cargo fix --test "keypair_management"` to apply 1 suggestion) | ||||
|     Finished `test` profile [unoptimized + debuginfo] target(s) in 1.87s | ||||
|      Running tests/keypair_management.rs (target/debug/deps/keypair_management-96fd8e08e64dfc60) | ||||
|  | ||||
| running 1 test | ||||
|  | ||||
| thread 'test_keypair_management_and_crypto' panicked at vault/tests/keypair_management.rs:21:160: | ||||
| called `Result::unwrap()` on an `Err` value: Crypto("decryption error: aead::Error") | ||||
| stack backtrace: | ||||
|    0: rust_begin_unwind | ||||
|              at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/std/src/panicking.rs:695:5 | ||||
|    1: core::panicking::panic_fmt | ||||
|              at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/panicking.rs:75:14 | ||||
|    2: core::result::unwrap_failed | ||||
|              at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/result.rs:1704:5 | ||||
|    3: core::result::Result<T,E>::unwrap | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:1109:23 | ||||
|    4: keypair_management::test_keypair_management_and_crypto::{{closure}} | ||||
|              at ./tests/keypair_management.rs:21:18 | ||||
|    5: <async_std::task::builder::SupportTaskLocals<F> as core::future::future::Future>::poll::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:199:17 | ||||
|    6: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/task_locals_wrapper.rs:60:13 | ||||
|    7: std::thread::local::LocalKey<T>::try_with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12 | ||||
|    8: std::thread::local::LocalKey<T>::with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15 | ||||
|    9: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/task_locals_wrapper.rs:55:9 | ||||
|   10: <async_std::task::builder::SupportTaskLocals<F> as core::future::future::Future>::poll | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:197:13 | ||||
|   11: <futures_lite::future::Or<F1,F2> as core::future::future::Future>::poll | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.0/src/future.rs:454:33 | ||||
|   12: async_executor::State::run::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:752:32 | ||||
|   13: async_executor::Executor::run::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:343:34 | ||||
|   14: async_executor::LocalExecutor::run::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:647:34 | ||||
|   15: async_io::driver::block_on::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-2.4.0/src/driver.rs:199:37 | ||||
|   16: std::thread::local::LocalKey<T>::try_with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12 | ||||
|   17: std::thread::local::LocalKey<T>::with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15 | ||||
|   18: async_io::driver::block_on | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-2.4.0/src/driver.rs:175:5 | ||||
|   19: async_global_executor::reactor::block_on::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-global-executor-2.4.1/src/reactor.rs:3:18 | ||||
|   20: async_global_executor::reactor::block_on | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-global-executor-2.4.1/src/reactor.rs:12:5 | ||||
|   21: async_global_executor::executor::block_on::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-global-executor-2.4.1/src/executor.rs:26:36 | ||||
|   22: std::thread::local::LocalKey<T>::try_with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12 | ||||
|   23: std::thread::local::LocalKey<T>::with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15 | ||||
|   24: async_global_executor::executor::block_on | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-global-executor-2.4.1/src/executor.rs:26:5 | ||||
|   25: async_std::task::builder::Builder::blocking::{{closure}}::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:171:25 | ||||
|   26: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/task_locals_wrapper.rs:60:13 | ||||
|   27: std::thread::local::LocalKey<T>::try_with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12 | ||||
|   28: std::thread::local::LocalKey<T>::with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15 | ||||
|   29: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/task_locals_wrapper.rs:55:9 | ||||
|   30: async_std::task::builder::Builder::blocking::{{closure}} | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:168:17 | ||||
|   31: std::thread::local::LocalKey<T>::try_with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12 | ||||
|   32: std::thread::local::LocalKey<T>::with | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15 | ||||
|   33: async_std::task::builder::Builder::blocking | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:161:9 | ||||
|   34: async_std::task::block_on::block_on | ||||
|              at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/block_on.rs:31:5 | ||||
|   35: keypair_management::test_keypair_management_and_crypto | ||||
|              at ./tests/keypair_management.rs:9:1 | ||||
|   36: keypair_management::test_keypair_management_and_crypto::{{closure}} | ||||
|              at ./tests/keypair_management.rs:9:19 | ||||
|   37: core::ops::function::FnOnce::call_once | ||||
|              at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5 | ||||
|   38: core::ops::function::FnOnce::call_once | ||||
|              at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/ops/function.rs:250:5 | ||||
| note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. | ||||
| test test_keypair_management_and_crypto ... FAILED | ||||
|  | ||||
| failures: | ||||
|  | ||||
| failures: | ||||
|     test_keypair_management_and_crypto | ||||
|  | ||||
| test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 32.97s | ||||
|  | ||||
| error: test failed, to rerun pass `-p vault --test keypair_management` | ||||
		Reference in New Issue
	
	Block a user