From cea2d7e655dfd29f2be558fa6e7f0ebd8fa21b8c Mon Sep 17 00:00:00 2001 From: Sameh Abouelsaad Date: Thu, 15 May 2025 16:42:19 +0300 Subject: [PATCH] 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. --- docs/native-wasm-build-plan.md | 85 ++++++ docs/vault.md | 164 +++++++++++ docs/vault_impl_plan.md | 276 ++++++++++++++++++ kvstore/Cargo.toml | 17 +- kvstore/src/native.rs | 12 +- kvstore/src/traits.rs | 5 + kvstore/src/wasm.rs | 9 +- vault/Cargo.toml | 3 + vault/README.md | 26 +- vault/src/lib.rs | 157 +++++----- vault/tests/keypair_management.rs | 40 +-- vault/tests/wasm_keypair_management.rs | 67 ++--- vault/vault_crypto_debug.log | 121 -------- vault/vault_native_test/db | Bin 524287 -> 524287 bytes vault/vault_native_test/snap.00000000000022A1 | Bin 124 -> 0 bytes vault/vault_native_test/snap.000000000000E741 | Bin 0 -> 73 bytes vault_crypto_debug.log | 156 ++++++++++ 17 files changed, 843 insertions(+), 295 deletions(-) create mode 100644 docs/native-wasm-build-plan.md create mode 100644 docs/vault.md create mode 100644 docs/vault_impl_plan.md delete mode 100644 vault/vault_crypto_debug.log delete mode 100644 vault/vault_native_test/snap.00000000000022A1 create mode 100644 vault/vault_native_test/snap.000000000000E741 create mode 100644 vault_crypto_debug.log diff --git a/docs/native-wasm-build-plan.md b/docs/native-wasm-build-plan.md new file mode 100644 index 0000000..efbe266 --- /dev/null +++ b/docs/native-wasm-build-plan.md @@ -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. diff --git a/docs/vault.md b/docs/vault.md new file mode 100644 index 0000000..574b034 --- /dev/null +++ b/docs/vault.md @@ -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; + pub fn save(&self) -> Result<(), VaultError>; + + pub fn list_keyspaces(&self) -> Vec; + 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 +rust +Copy +Edit +pub struct KeySpace { + // Internal fields +} + +impl KeySpace { + pub fn new(name: &str, password: &str) -> Result; + pub fn save(&self) -> Result<(), VaultError>; + + pub fn list_keypairs(&self) -> Vec; + 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; + + pub fn sign(&self, keypair_name: &str, message: &[u8]) -> Result, VaultError>; + pub fn verify(&self, keypair_name: &str, message: &[u8], signature: &[u8]) -> Result; +} +KeyPair +rust +Copy +Edit +pub struct KeyPair { + // Internal fields +} + +impl KeyPair { + pub fn new() -> Self; + pub fn from_private_key(private_key: &[u8]) -> Result; + + pub fn public_key(&self) -> Vec; + pub fn private_key(&self) -> Vec; + + pub fn sign(&self, message: &[u8]) -> Result, VaultError>; + pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result; +} +SessionManager (Optional) +rust +Copy +Edit +pub struct SessionManager { + keyspace: KeySpace, + active_keypair: String, +} + +impl SessionManager { + pub fn new(keyspace: KeySpace, keypair_name: &str) -> Result; + pub fn sign(&self, message: &[u8]) -> Result, VaultError>; + pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result; + 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. \ No newline at end of file diff --git a/docs/vault_impl_plan.md b/docs/vault_impl_plan.md new file mode 100644 index 0000000..141034a --- /dev/null +++ b/docs/vault_impl_plan.md @@ -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, + // ... other vault-level metadata (optionally encrypted) +} + +struct KeyspaceMetadata { + name: String, + salt: [u8; 16], // Unique salt for this keyspace + encrypted_blob: Vec, // All keypairs & secrets, encrypted with keyspace password + // ... other keyspace metadata +} + +// The decrypted contents of a keyspace: +struct KeyspaceData { + keypairs: Vec, + // ... other keyspace-level metadata +} + +struct KeyEntry { + id: String, + key_type: KeyType, + private_key: Vec, // Only present in memory after decryption + public_key: Vec, + metadata: Option, +} + +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 Vault { + async fn open(store: S) -> Result; + async fn list_keyspaces(&self) -> Result, 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; + async fn list_keys(&self) -> Result, VaultError>; + async fn sign(&self, key_id: &str, msg: &[u8]) -> Result; + async fn encrypt(&self, key_id: &str, plaintext: &[u8]) -> Result; + async fn decrypt(&self, key_id: &str, ciphertext: &[u8]) -> Result, 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). diff --git a/kvstore/Cargo.toml b/kvstore/Cargo.toml index 8bcdcd1..b302484 100644 --- a/kvstore/Cargo.toml +++ b/kvstore/Cargo.toml @@ -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 = [] diff --git a/kvstore/src/native.rs b/kvstore/src/native.rs index 4256097..e0ccd40 100644 --- a/kvstore/src/native.rs +++ b/kvstore/src/native.rs @@ -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)] diff --git a/kvstore/src/traits.rs b/kvstore/src/traits.rs index 1e3b7b4..d5f4372 100644 --- a/kvstore/src/traits.rs +++ b/kvstore/src/traits.rs @@ -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>>; async fn set(&self, key: &str, value: &[u8]) -> Result<()>; diff --git a/kvstore/src/wasm.rs b/kvstore/src/wasm.rs index c84159b..f68efc6 100644 --- a/kvstore/src/wasm.rs +++ b/kvstore/src/wasm.rs @@ -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")] diff --git a/vault/Cargo.toml b/vault/Cargo.toml index 01b96f3..d1d6523 100644 --- a/vault/Cargo.toml +++ b/vault/Cargo.toml @@ -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" diff --git a/vault/README.md b/vault/README.md index 40e5f5f..f746899 100644 --- a/vault/README.md +++ b/vault/README.md @@ -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 diff --git a/vault/src/lib.rs b/vault/src/lib.rs index 79e899a..a23f031 100644 --- a/vault/src/lib.rs +++ b/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 { @@ -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 Vault { pub async fn create_keyspace(&mut self, name: &str, password: &[u8], kdf: &str, cipher: &str, tags: Option>) -> 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 Vault { 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 Vault { /// Unlock a keyspace by name and password, returning the decrypted data pub async fn unlock_keyspace(&self, name: &str, password: &[u8]) -> Result { - 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 Vault { "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 Vault { /// 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 Vault { "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 Vault { /// 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, 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, 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 diff --git a/vault/tests/keypair_management.rs b/vault/tests/keypair_management.rs index 5b6a2ae..c1fae5e 100644 --- a/vault/tests/keypair_management.rs +++ b/vault/tests/keypair_management.rs @@ -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); diff --git a/vault/tests/wasm_keypair_management.rs b/vault/tests/wasm_keypair_management.rs index c6e8e6d..b16dc87 100644 --- a/vault/tests/wasm_keypair_management.rs +++ b/vault/tests/wasm_keypair_management.rs @@ -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; } } diff --git a/vault/vault_crypto_debug.log b/vault/vault_crypto_debug.log deleted file mode 100644 index 332a075..0000000 --- a/vault/vault_crypto_debug.log +++ /dev/null @@ -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 diff --git a/vault/vault_native_test/db b/vault/vault_native_test/db index fa04fefea71f6e0a220296186d87f7c220f8edca..0ff59d75c8f55eaf28ae106a23ffb2426b46dd35 100644 GIT binary patch literal 524287 zcmeF)OUSNime%>578N8RQaEs^PyrtK2K)Q=xe&zzD2QM;Jy4`cva-Tb%+5?Jv#?Y| z@c@E|B|;&sw6rsuMtkI-6jT%)=s-cii3g+y4NcRAf??>O{Qa-{-I*B`J1PQlpc|gd z{q6UCpXWZTb**b%Yu(TLMa0ki^gsN8pE>{gBahDi{^+m$Bmd;TyyN}v`z!z8C%*p+ zkG}Be|NX%~{=a|mi;sTZZNQ@s-uvd)zxeK>?>nEr@P$V|?;riKpZ+uN{K7|%e&9=A z`sP>P|B)|!=||rGU0?q3uYT+M{`%9u`S;%a;3v)f#ov1Wo8S88H@^Ip_rG-i;^~Vg z_xBG^U%Ytr?BU6)C(mAf@by3R-q*hTzr6q6554zuj(hL!y>EW`tKWL>hkob%%g1*Q zPaf_b9^c=+c(}WN@#60O<Qy}N(%>h9tG@!gYW4ga6- z-ut~j^1JNxjUV><&(Sn=Xdsd{=6GA?aA{yH@p1m?%DIZ zS1<3LGx&jNk6nJo+WUvQR~CA8ch8OcCw?A3+`Y`q1~%`(H7nddbHREKkDuK2_W9jQ z7oXg{e9WD%{Mh?n&cZJP<~!itz4xsz|L8a0`=PJ>_*cLBhkx}i`~AQ5-4DO?nSuE5 z*Ss}>fbzYFitmf(!TUtK1p*l!o`$-BJT~NoS8zRb*Pt#x1*ss4LHLLItL8j*^VQue zH=ntCe}DJvSy11<5a#X`GHX4-JJfox@5`5WFYk*}Yu6kfOnYpFdHUvl+nNe6sq}AaLJKHq88dW6b@thDSs1 zxs;9f1~WLoOgHu6saL+@r*0&&%9rf$>6IaPsqBnsXs-h|i$0SC~8o(CqKpRT^VD^rRVsEmkcrC!7qk64^gx$PoWCn>=V&y5SO`;*N-bYrAb*?{*8Y$Snh2p-#gy)IeOcaVk~uX;>Ix#V9&FW*@`N zY%`|L#+0(?Oc33+E$kn%GAHxF?k)i{TZ%}jcQ53X-5YT%5h@H`$~4OuvS&(=#Jap_ zmELD*>}LC6G^7s&S-s#J)WDLcl~Y7)DXlEZFw+pNUHU20#iw#W?Q<|(@^v{5PSI}v zvZF|w#uOa2EpI6iT9?|$^opZLte`q4W-{MJ;~`?3W*)09wtOzhAIc(F?; zaTN51L{>y8=<&s%fl0jLWVJUEM*9_65|U_@G12jaNCFoULM*f^6_Y}fA~RA5k-#do zAuoTI&lm!%A53@)l@2tYx!7yQsW`9d0j24FcaN; z*JCl-g2_q-CQT`g)QvY$b662Ayz=Yv?ePWE_U9mxm- zSR{XTTsZU5Zo*dCDdpT!BLOU^D<3Y?vSa?D^d zRl>Ox>-P9>@fI+rYjn>hU;AG0`|vDk@w>UC`P90#Ip>vN;8{;8gr9Q>YVE z)1+5v0hlrZ+6BZr%d~HP(CY#*Y00<}qO(v{us}1rmOXulXs@@*D2z)L3RjFx4~(dX zL?EcFwn4jW<_C(^DujKzniy2(wT4Ntzl==j@Kj2LY3WlJQ_@kk6;)ZZng{FP7W=|) zQRz|uDsX&u;qXq+CLYqkI&Hr;V*xM6qnK4t!>AnIKDVkE|H}hQ34c)nm4)vssM#S% zwQ(zIb|2%->HOnk-45~K9@EBME7-K@tKe3uEisHnFb2jDdkD|fS>T+~%T=DDuN$;n5Ey^6xs$>i9S~57?&LEuZj2t8C zPQZp`jFmAh6KDJgTYhiDG8aeJ=0bx)6rlVIK_eqed!_^FS2Yn3)vR1me7jRJ?oR}e&XqEfpc!Qwp^nIacZI-8u8RbyUU3vGo7$-wM2@R?v)yeS&YpI*i>y`-!R)-HQYuz(|JnNWp2b% zqe^|=rhP6J=GYJ+bKC|sQC%^%DcR)}0Ci!ACofHe(C~E)I%c=vqy)a*w7hNlad+Cf z+~$K&#SmEXn{-zJ`O$KGOKX%VsQKDHEt(jZx=T&^&BOHD4E_A7tKyB|IJe?I&4{pcUOJ*V#^p-h8%$`9c* z9m>KJMr8&7cPfdcBYT;I)CR|mNW1JTzhYaWgR`lH?sSn7+u|yj4%uxGrK>^dT_mc~ zmz8#i18Wa#h#wY7284U(;R60Td@N}~A|5N6SOMs`kx49GLzzz)w9?As_|K52(4u9w z1g#1u$+??Lr#^8qO{rz=EW%jhR>#n14}H$Gj_IlHp3xd?`}KPANk$^zDnj=yU*_fV zTHY2XulF5t44bKpOE|!3SPqxbKCWRf!6(UIDVVj{C{qY35jEy)G8~RQR;U_DiM}!t zH0mzwKw;kEu=a1+)ouhOV0*5SMAvTHBbN$~sIfSZ}+EII_qd7X_Cwg}!hV3O6gB1$)n0 zRELCJ{a^197FNQ6$(m6UwQ5K+dbR={2*8Sn zg7%i9 z7EXus{D%c?pZcd%+KtIdiGMS?Vv^mh6TgGcPVpl5K(!>B;W5T+CN@H zG4`9CmfVof5E*YW=ddhZWV^CBPnLi?cgCAmVUpnR+NAh#Ln;()D_lD$#qS858ZkOy zu}4zF9utF6QIFoP)b=h7^3b@F-rvkD{1Uo04jL|;H49a&kV2w$qGpC6QXoOl8riuX zV}116f^>}FucL2Q3orw^ZP`XWo~R4(G3V=n+>%CtBC+$&E@=k_x{^qMZ;Dc1URi)n z1!|p{5uqC$Q)DCHVzFiB@%^NAcOfTn3-p>yo=KjW&lqdSo=Su*ET753MFCtkO=Q)I zBsu*`c?D!;VTa??2VbLth!p=D+{sg`&a!j1V4nNfRb!vMVjSIUuWWI?nv#i8E`3>w zqMP*%+%q>zS^$q5#1)qOW9>dxoGj}SD6rHo6S1Ihs*6?Y*4XA2i)oLz}o(5CbGtE z_LXM|Su9*4a6i-2xt2_+N|quWVb2z8oU0n3^oK&6r`txgJ>1nDhWtp8`qM=kg8x-1Q5a7s8>x&)WzMEsmXzzc`V=A-0dDfSid?_ zh(r*ft2&0?IGIE29vJO{B5q zv!9-!yTO>ThbF0A?2D(T{+X5+O|LY!vIOqD8GadO%gpYkLFugQiIF*1U&o{K(11Kt zDid1Nj;SzbdC7?TtIXD}9GM=51%irnC)r3A&ncl(Yx~~-Vn*$@9kT(V)$ZycXYwsu ztEyGy+Wo>gW6Fh{>X{u%rJGA2Sg&DuVcIhT1p!hW#!F2_*O++%c^4b*PubTd*(XNv zrTx5?kUM5C;SCjm6bTZspm0_ka$7RoY z&e*`WZ$zuTiQH}kR8(3Iu{vdD0gO~EthuenS*Xu573ffZPK#4<=J(dkUV8|o3^7du zwUZ0~>{3srhn&!^@Mgf2gID2UrxL8HTB$LmFgJ?c;=_4Vx<^Eeo6_4c^2Z}Qkk_?d zPy|c%w`$|+uQR4KOPIna8z&KL-P0}#ARX3g^PyZx^~_?Tcm5Et6!HAd6LT25-3xCp z*ln%R4JxTtR_-gP`Nss?$@hu~VTDCJr?G@V!vV%2rQ zQ8v6yjhOroA7@Y3OA#{)Ku(f88(ednG^p znZ@>_cmBd#Qf(uAk%6D!R2*wdiFSMts6A?o&fZ6=lD8TaMMPxNiz8NfpLqM3=r+Ke zigA=H|JRkDuGL+`ZRO2)Et@P`T`x4^f~XOw%3WfxVTTx_qER%R9TBUo=(32b8Dw?k zuw(MX#dQz>t-U> z6eGJVY{{7Sy3i;aX*{i{C(I*lbm{hGrC}u$^&#~v z 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 z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 tV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DQZ2Lf-R{civO delta 45 ucmezWU;h7p`3)wn?TTECK+FWh%s|Wn#H>Kfwq22n{odE@23(A9j{pE~Jr4u` diff --git a/vault/vault_native_test/snap.00000000000022A1 b/vault/vault_native_test/snap.00000000000022A1 deleted file mode 100644 index 4a79f6f6597d3adc114e2929d6e1325bd1bff246..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124 zcmZ3a!~h12jP4Ky1B1IHqYR8K!^x@&W2*`zz?caiXTD^Faz6h2p5e#|<@{s_{K?k0 aUjoMEKmU{A6Qe0ipQ%s*lzD2llsW*N9unCA diff --git a/vault/vault_native_test/snap.000000000000E741 b/vault/vault_native_test/snap.000000000000E741 new file mode 100644 index 0000000000000000000000000000000000000000..4702c0f75b6aa14fd80c765e9b8975a1b1778b67 GIT binary patch literal 73 ycmZ>I&Hx6CjP4Ky1B1IHqYR8K!^!w(E0ptdEzeJlP)1XjoT-p4lvyY`JqZ9WPYEah literal 0 HcmV?d00001 diff --git a/vault_crypto_debug.log b/vault_crypto_debug.log new file mode 100644 index 0000000..5eabe85 --- /dev/null +++ b/vault_crypto_debug.log @@ -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::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: 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::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::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: 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: 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::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::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::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::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::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::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::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::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`