Feat: simplify vault API and update docs
This commit is contained in:
parent
cea2d7e655
commit
791752c3a5
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@
|
||||
# Ignore IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Ignore test databases
|
||||
/vault/vault_native_test/
|
85
README.md
85
README.md
@ -33,25 +33,92 @@ cd kvstore
|
||||
wasm-pack test --headless --firefox --features web
|
||||
```
|
||||
|
||||
# Rhai Scripting System
|
||||
|
||||
A unified system for writing and executing [Rhai](https://rhai.rs/) scripts, powered by shared Rust core logic. Supports both local CLI execution and secure browser extension use, with the same business logic compiled to WebAssembly.
|
||||
|
||||
---
|
||||
|
||||
## Project Goals
|
||||
- **Write and run Rhai scripts** both locally (CLI) and in the browser (extension).
|
||||
- **Reuse the same Rust core logic** (vault, evm_client) across all platforms.
|
||||
- **Sandboxed, secure script execution** in both native and WASM environments.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
- **Shared Rust Crates:**
|
||||
- `vault/` and `evm_client/` implement business logic and expose APIs to Rhai.
|
||||
- All logic is reusable in both native and WASM builds.
|
||||
- **CLI Tool (`cli/`):**
|
||||
- Runs Rhai scripts from files or stdin using the shared core.
|
||||
- Outputs results to the terminal.
|
||||
- **WebAssembly Module (`wasm/`):**
|
||||
- Exposes `run_rhai(script: &str) -> String` via `wasm-bindgen`.
|
||||
- Usable from browser JS and the extension.
|
||||
- **Browser Extension (`browser_extension/`):**
|
||||
- UI for entering and running Rhai scripts securely in the browser.
|
||||
- Loads the WASM module and displays results.
|
||||
- **Web App Integration:**
|
||||
- Trusted web apps can send scripts to the extension for execution (via postMessage or WebSocket, with strict origin checks).
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### CLI
|
||||
```
|
||||
sal-cli run my_script.rhai
|
||||
# or
|
||||
cat my_script.rhai | sal-cli run
|
||||
```
|
||||
|
||||
### Browser/Extension
|
||||
- Enter Rhai code in the extension popup or trusted website.
|
||||
- Extension loads the WASM module and calls `run_rhai(script)`.
|
||||
- Result is displayed in the UI.
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
- All script execution is sandboxed via Rhai + WASM.
|
||||
- Only accepts input from:
|
||||
- Extension popup UI
|
||||
- Approved websites (via content script)
|
||||
- Trusted backend server (if using WebSocket)
|
||||
- Strict origin and input validation.
|
||||
- No internal APIs exposed beyond `run_rhai(script)`.
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
```
|
||||
.
|
||||
├── kvstore/ # Key-value store trait and backends
|
||||
├── vault/ # Cryptographic vault
|
||||
├── evm_client/ # EVM RPC client
|
||||
├── cli_app/ # CLI (planned)
|
||||
├── web_app/ # Web app (planned)
|
||||
├── docs/ # Architecture docs
|
||||
├── vault/ # Cryptographic vault (shared core)
|
||||
├── evm_client/ # EVM RPC client (shared core)
|
||||
├── cli/ # Command-line tool for Rhai scripts
|
||||
├── wasm/ # WebAssembly module for browser/extension
|
||||
├── browser_extension/ # Extension source
|
||||
├── docs/ # Architecture & usage docs
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
- [x] Unified async trait for key-value storage
|
||||
- [x] Native and WASM backends for kvstore
|
||||
- [ ] Cryptographic vault with password-protected keyspace
|
||||
- [ ] EVM client with vault integration
|
||||
- [ ] CLI and web app targets
|
||||
- [ ] Full end-to-end integration
|
||||
- [x] Shared Rust core for vault and evm_client
|
||||
- [x] WASM module exposing `run_rhai`
|
||||
- [ ] CLI tool for local Rhai script execution
|
||||
- [ ] Browser extension for secure script execution
|
||||
- [ ] Web app integration (postMessage/WebSocket)
|
||||
- [ ] Full end-to-end integration and security review
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
MIT OR Apache-2.0
|
||||
|
||||
|
@ -1,384 +0,0 @@
|
||||
# Architecture and Implementation Plan for the Rust Modular System
|
||||
|
||||
The system is organized into three core Rust crates (`kvstore`, `vault`, `evm_client`) plus two front‐end targets (a CLI and a WASM web app). The **`kvstore`** crate defines an async `KVStore` trait and provides two implementations: on native platforms it uses **sled**, while in WASM/browser it uses IndexedDB via the `idb` crate (selected by Cargo feature flags or `#[cfg(target_arch = "wasm32")]`). For example, Wire’s core-crypto keystore uses IndexedDB with AES-GCM for WASM and SQLCipher on native platforms. The **`vault`** crate manages an encrypted keyspace of multiple keypairs (password-protected), performing cryptographic operations (sign/verify, sym/asym encryption) and persisting data through `kvstore`. The **`evm_client`** crate handles EVM RPC calls (using `alloy`), depending on `vault` to sign transactions with stored keys. A Rust **CLI** binary ties these together with a Rhai scripting engine: Rhai scripts invoke async APIs via a message-passing pattern. The **browser target** compiles to Wasm (with `wasm-bindgen`); it exposes the same APIs to JavaScript or to Rhai compiled for Wasm.
|
||||
|
||||
## Crate and Module Structure
|
||||
|
||||
* **Cargo workspace**: top-level `Cargo.toml` lists members `kvstore/`, `vault/`, `evm_client/`, `cli_app/`, `web_app/`. Common dev-dependencies and CI config are shared at the workspace root.
|
||||
* **Features & cfg**: In `kvstore`, define Cargo features or use `#[cfg]` to toggle backends. E.g. `cfg(not(target_arch = "wasm32"))` for sled, and `cfg(target_arch = "wasm32")` for IndexedDB. Use `async_trait` for the `KVStore` trait so implementations can be async. Similar conditional compilation applies to any platform-specific code (e.g. using WebCrypto APIs only under WASM).
|
||||
* **Dependencies**:
|
||||
|
||||
* `kvstore` depends on `sled` (native) and `idb` (WASM), and defines `async fn` methods. Blocking DB calls (sled) must be offloaded via a `spawn_blocking` provided by the caller.
|
||||
* `vault` depends on `kvstore` and various crypto crates (e.g. `aes-gcm` or `chacha20poly1305` for symmetric encryption; `k256`/`rust-crypto` for signatures). For WASM compatibility, ensure chosen crypto crates support `wasm32-unknown-unknown`. Keys are encrypted at rest with a password-derived key (AES-256-GCM or similar).
|
||||
* `evm_client` depends on `vault` (for signing) and an Ethereum library (e.g. `alloy` with an async HTTP provider). On WASM, use `wasm-bindgen-futures` to call JavaScript fetch or use a crate like `reqwest` with the `wasm` feature.
|
||||
* The **CLI** (binary) depends on Rhai (`rhai` crate), `tokio` or similar for async execution, and the above libraries. It sets up an async runtime (e.g. Tokio) to run tasks.
|
||||
* The **web\_app** (WASM target) depends on `wasm-bindgen`/`wasm-bindgen-futures` and `vault`/`evm_client`. It uses `wasm-bindgen` to expose Rust functions to JS. Rhai can also be compiled to WASM for scripting in-browser, but must be integrated via the same message-passing pattern (see below).
|
||||
|
||||
## `kvstore` Crate Design
|
||||
|
||||
The `kvstore` crate defines:
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait KVStore {
|
||||
async fn get(&self, key: &str) -> Option<Vec<u8>>;
|
||||
async fn put(&self, key: &str, value: &[u8]) -> ();
|
||||
async fn delete(&self, key: &str) -> ();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
It then provides two modules implementing this trait:
|
||||
|
||||
* **Native backend (sled)**: A wrapper around `sled::Db`. Since `sled` I/O is blocking, each call should be executed in a blocking context (e.g. using `tokio::task::spawn_blocking`) so as not to block the async runtime.
|
||||
* **WASM/browser backend (IndexedDB)**: Uses the `idb` crate (or `web-sys`/`gloo`) to store data in the browser’s IndexedDB. This implementation is inherently async (Promise-based) and works in `wasm32-unknown-unknown`. On compilation, one can use Cargo features like `default-features = false` and `features = ["native", "wasm"]`, or simply `#[cfg]` to select the correct backend.
|
||||
|
||||
Citing best practice: the pattern of having an encrypted keystore use IndexedDB on WASM is standard (e.g. Wire’s core-crypto keystore). We will mirror that by encrypting data before `put`-ting it. The `kvstore` implementation will automatically be runtime-agnostic (using only `std::future::Future` in its APIs).
|
||||
|
||||
## `vault` Crate Design
|
||||
|
||||
The `vault` crate implements a WebAssembly-compatible cryptographic keystore. It manages:
|
||||
|
||||
* **Encrypted keyspace**: A password protects all key material. On open, derive an encryption key (e.g. via scrypt or PBKDF2) and decrypt the stored vault (a blob in `kvstore`). Inside, multiple keypairs (e.g. Ethereum secp256k1 keys, Ed25519 keys, etc.) are stored.
|
||||
* **Crypto APIs**: Expose async functions to create new keys, list keys, and to perform crypto operations: e.g. `async fn sign_transaction(&self, key_id: &str, tx: &Transaction) -> Signature`, `async fn verify(&self, ...) -> bool`, `async fn encrypt(&self, ...)->Ciphertext`, etc.
|
||||
* **Storage**: Internally uses the `kvstore::KVStore` trait to persist the encrypted vault. For example, on each change, it re-encrypts the whole keyspace and `put`s it under a fixed ID key.
|
||||
* **WASM Compatibility**: All operations must compile to Wasm. Use Rust crypto crates compatible with `no_std`/WASM (e.g. `aes-gcm`, `k256`, `rand_core` with `getrandom` support). Alternatively, one could use the browser’s WebCrypto via `wasm-bindgen` for symmetric operations, but for simplicity we can rely on Rust crates (AES-GCM implementations that compile to WASM).
|
||||
|
||||
Internally, `vault` ensures all operations return `Future`s. It will not assume any particular async runtime – for example, file I/O or crypto is fast in memory, but if any blocking work is needed (like PBKDF2 hashing), it should be done via a provided `spawn_blocking` (as recommended by forum answers). On WASM, such heavy work would yield to the JS event loop via `spawn_local` (see below).
|
||||
|
||||
When open, `vault` authenticates the user’s password, loads (via `kvstore`) the encrypted blob of keys, and allows operations. Fig. above illustrates a cryptographic network: keys stored securely (vault) are used for signing on behalf of the user. Internally, best practice is to use an authenticated cipher (e.g. AES-256-GCM) with a strong KDF, as noted in existing systems.
|
||||
|
||||
## `evm_client` Crate Design
|
||||
|
||||
The `evm_client` crate provides async interfaces to interact with an EVM blockchain:
|
||||
|
||||
* **Dependencies**: It uses the `alloy` crate for building transactions, ABI encoding, and an async HTTP provider for RPC calls.
|
||||
* **Signing**: It calls into `vault` when a transaction must be signed. For example, `evm_client.sign_and_send(tx)` will invoke `vault.sign(key_id, tx_bytes)` to get a signature.
|
||||
* **Async RPC**: All RPC calls (`eth_sendRawTransaction`, `eth_call`, etc.) are `async fn`s returning `Future`s. These futures must be runtime-agnostic: they use standard `async/await` and do not tie to Tokio specifically. For HTTP, on native targets use `reqwest` with Tokio, while on WASM use `reqwest` with its `wasm` feature or `gloo-net` with `wasm-bindgen-futures`.
|
||||
* **Configuration**: Provide a flexible config (e.g. chain ID, gas price options) via plain structs. Errors should use a common error enum or `thiserror` crate.
|
||||
* **Features**: Could have a feature flag to choose between `alloy` and `ethers`. Both are fully async.
|
||||
|
||||
The `evm_client` crate itself should be purely async and not block. It will typically run on Tokio in the CLI, and on the browser’s single-threaded event loop with `spawn_local` in the web app.
|
||||
|
||||
## CLI Binary (Rhai Scripting)
|
||||
|
||||
The CLI binary (`cli_app`) binds everything with a user interface. Its design:
|
||||
|
||||
* **Command loop**: On startup it spawns a Rhai `Engine` in a separate OS thread. The main thread runs a Tokio async runtime (or other) to handle network and I/O.
|
||||
* **Message-passing**: Use two MPSC channels: one for messages *to* the engine, and one for replies *from* the engine. According to Rhai’s multi-threaded pattern, we register API functions in the engine that send commands via channel to the main thread. The main thread processes commands (e.g. “sign this tx”, “send transaction”, etc.) using `vault`/`evm_client`, then sends back results.
|
||||
* **Blocking calls**: In Rhai, all calls are blocking from the script’s perspective. Under the hood, the registered API calls serialize the request (e.g. to JSON) and send it on the command channel. The Rhai engine will block until a reply arrives on the reply channel. This pattern ensures the script can call async Rust code seamlessly (step 6–8 in Rhai docs).
|
||||
* **Example flow**: A Rhai script calls `let res = send_tx(data)`. The `send_tx` function (registered in the engine) captures the channel handles, packages `data` into a message, and sends it. The engine thread blocks. The main thread’s async runtime reads the message, calls `evm_client.send_transaction(data).await`, then sends the result back. The Rhai engine thread receives it and returns it to the script.
|
||||
|
||||
This design follows Rhai’s recommended “blocking/async” pattern. It keeps the library usage runtime-agnostic, while allowing user-defined scripts to trigger asynchronous operations.
|
||||
|
||||
## Browser Application (WASM)
|
||||
|
||||
The browser target (`web_app`) is compiled with `wasm-bindgen` to Wasm. It provides the same core functionality via a JS API (or Rhai in WASM). Key points:
|
||||
|
||||
* **Exports**: Use `#[wasm_bindgen]` to expose async functions to JavaScript. For example, expose `async fn create_key(name: String) -> JsValue` that returns a JavaScript `Promise`. The `wasm-bindgen-futures` crate will convert Rust `Future`s into JS Promises automatically.
|
||||
* **Async runtime**: WebAssembly runs on the browser’s single thread. To perform async Rust code, we use `wasm_bindgen_futures::spawn_local` to drive futures on the JS event loop. For example, in an exported function we might do `spawn_local(async move { /* call vault, evm_client */ })`. According to docs, `spawn_local` “runs a Rust `Future` on the current thread” and schedules it as a microtask. This lets our async functions execute to completion without blocking the event loop.
|
||||
* **Promises and interop**: Return types must be `JsValue` or types convertible by `wasm_bindgen`. Complex data (e.g. byte arrays) can be passed as `Uint8Array` or encoded (e.g. hex).
|
||||
* **Rhai in WASM**: Optionally, we can compile Rhai to WebAssembly as well. In that case, we would run the Rhai engine in a WebWorker (since WASM threads are limited) and use `MessageChannel` for communication. The same message-passing pattern applies: a script call in the worker posts a message to the main thread with request data, and awaits a message back. The main thread (browser UI) handles the request using the exposed Rust APIs. This is analogous to the CLI pattern but using Web APIs. (Implementation note: enabling threading in WASM requires `wasm-bindgen` with the `--target bundler` or using `web-sys` `Worker` APIs.)
|
||||
* **Integration tips**: Use the `wasm-bindgen` guide to share data types (strings, structs) between JS and Rust. For async tests, `wasm-bindgen-futures` has examples.
|
||||
|
||||
In summary, the web app compiles the same crates to Wasm and exposes them. The figure above (a network on a globe) conceptually represents the global connectivity: the browser connects to EVM nodes via WebAssembly modules, invoking Rust code. All async boundaries are handled with `spawn_local` and JS Promises (as `wasm-bindgen-futures` outlines).
|
||||
|
||||
## Async and Runtime-Agnostic Best Practices
|
||||
|
||||
Throughout all crates we adhere to runtime-agnostic async principles:
|
||||
|
||||
* **Use `std::future::Future`** in public APIs, not a specific runtime’s types. Internally, any async work (I/O, network) should be done with `async/await`.
|
||||
* **Feature-gate runtime-specific code**: If we need to call `tokio::spawn` or `async-std`, isolate that behind `#[cfg(feature = "tokio")]` or similar. Initially, one can pick one runtime (e.g. Tokio) and make the library depend on it, then add cfg-features later.
|
||||
* **Blocking calls**: Any blocking work (file I/O, heavy crypto) is executed via a passed-in executor (e.g. require a `spawn_blocking: Fn(Box<dyn FnOnce() + Send>)` callback), as recommended by Rust forum advice. This way the library never forces a specific thread pool. For example, in `kvstore`’s sled backend, all operations are done in `spawn_blocking`.
|
||||
* **Testing**: Include tests for both native and WASM targets (using `wasm-pack test` or headless browser tests) to catch platform differences.
|
||||
* **Error handling**: Use `Result` types, with a shared error enum. Avoid panic paths – return errors across FFI boundaries.
|
||||
|
||||
By decoupling logic from the runtime (using channels for Rhai, spawn\_local for WASM, cfg-features for backends), the libraries remain flexible. As one Rust discussion notes, “using `cfg(feature = "...")` to isolate the pieces that have to be runtime specific” is key. We ensure all public async APIs are `async fn` so they can be `await`ed in any context.
|
||||
|
||||
## Workspace Layout and Features
|
||||
|
||||
The recommended workspace layout is:
|
||||
|
||||
```
|
||||
/Cargo.toml # workspace manifest
|
||||
/kvstore/Cargo.toml # kvstore crate
|
||||
/vault/Cargo.toml # vault crate
|
||||
/evm_client/Cargo.toml
|
||||
/cli_app/Cargo.toml # binary (depends on kvstore, vault, evm_client, rhai)
|
||||
/web_app/Cargo.toml # cdylib (wasm) crate (depends on kvstore, vault, evm_client, wasm-bindgen)
|
||||
```
|
||||
|
||||
Each crate’s `Cargo.toml` lists its dependencies. For `kvstore`, an example feature setup:
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default = ["native"]
|
||||
native = ["sled"]
|
||||
web = ["idb"]
|
||||
```
|
||||
|
||||
In code:
|
||||
|
||||
```rust
|
||||
#[cfg(feature = "native")]
|
||||
mod sled_backend;
|
||||
#[cfg(feature = "web")]
|
||||
mod indexeddb_backend;
|
||||
```
|
||||
|
||||
One could also omit features and just use `#[cfg(target_arch = "wasm32")]` for the web backend. The `wasm-bindgen` crate is included under the `web_app` for browser integration.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This plan lays out a clear, modular architecture. Diagrams (above) conceptually show how the crates interact: `kvstore` underlies `vault`, which together support `evm_client`; the CLI and WASM targets invoke them asynchronously. We use message-passing (channels) to bridge Rhai scripts with async Rust code, and `spawn_local` in the browser to schedule futures. By following Rust async best practices (runtime-agnostic Futures, careful use of `cfg` and spawn-blocking) and wasm-bindgen conventions, the system will work seamlessly both on the desktop/CLI and in the browser.
|
||||
|
||||
**Sources:** Concepts and patterns are drawn from Rust async and WASM guidelines. For example, using IndexedDB with AES-GCM in WASM keystores is inspired by existing systems. These sources guided the design of a flexible, secure architecture.
|
||||
|
||||
|
||||
|
||||
## 🔐 `kvstore` Crate: Pluggable Key-Value Storage Layer
|
||||
|
||||
**Purpose**: Provide an abstraction for key-value storage with async-compatible traits, supporting both native and WASM environments.
|
||||
|
||||
### Public API
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait KVStore {
|
||||
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, KVError>;
|
||||
async fn set(&self, key: &str, value: &[u8]) -> Result<(), KVError>;
|
||||
async fn delete(&self, key: &str) -> Result<(), KVError>;
|
||||
async fn exists(&self, key: &str) -> Result<bool, KVError>;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Backends**:
|
||||
|
||||
* **Native**: [`sled`](https://crates.io/crates/sled)
|
||||
* **WASM**: [`idb`](https://crates.io/crates/idb) (IndexedDB)
|
||||
|
||||
**Features**:
|
||||
|
||||
* Compile-time target detection via `#[cfg(target_arch = "wasm32")]`
|
||||
* Enables usage in both CLI and browser environments
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ `vault` Crate: Core Cryptography Module
|
||||
|
||||
**Purpose**: Manage secure key storage, cryptographic operations, and password-protected keyspaces.
|
||||
|
||||
### Public API
|
||||
|
||||
```rust
|
||||
pub struct HeroVault;
|
||||
|
||||
impl HeroVault {
|
||||
pub async fn create_keyspace(name: &str, password: &str) -> Result<(), VaultError>;
|
||||
pub async fn load_keyspace(name: &str, password: &str) -> Result<(), VaultError>;
|
||||
pub async fn logout() -> Result<(), VaultError>;
|
||||
|
||||
pub async fn create_keypair(label: &str) -> Result<(), VaultError>;
|
||||
pub async fn select_keypair(label: &str) -> Result<(), VaultError>;
|
||||
pub async fn list_keypairs() -> Result<Vec<String>, VaultError>;
|
||||
pub async fn get_public_key(label: &str) -> Result<Vec<u8>, VaultError>;
|
||||
|
||||
pub async fn sign_message(message: &[u8]) -> Result<Vec<u8>, VaultError>;
|
||||
pub async fn verify_signature(message: &[u8], signature: &[u8], public_key: &[u8]) -> Result<bool, VaultError>;
|
||||
|
||||
pub async fn encrypt(data: &[u8], password: &str) -> Result<Vec<u8>, VaultError>;
|
||||
pub async fn decrypt(data: &[u8], password: &str) -> Result<Vec<u8>, VaultError>;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Security**:
|
||||
|
||||
* All sensitive data encrypted at rest using AES-GCM or ChaCha20-Poly1305
|
||||
* Passwords stretched via Argon2id or PBKDF2
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ `evm_client` Crate: EVM Integration Layer
|
||||
|
||||
**Purpose**: Interact with Ethereum-compatible chains using key material from `vault`.
|
||||
|
||||
### Public API
|
||||
|
||||
```rust
|
||||
pub struct EvmClient;
|
||||
|
||||
impl EvmClient {
|
||||
pub async fn connect(rpc_url: &str) -> Result<Self, EvmError>;
|
||||
pub async fn get_balance(&self, address: &str) -> Result<U256, EvmError>;
|
||||
pub async fn send_transaction(&self, to: &str, value: U256, data: &[u8]) -> Result<TxHash, EvmError>;
|
||||
pub async fn call_contract(&self, to: &str, data: &[u8]) -> Result<Vec<u8>, EvmError>;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Options**:
|
||||
|
||||
* `ethers-rs` (default, mature)
|
||||
* `alloy` (alternative, lightweight and WASM-friendly)([Stack Overflow][1])
|
||||
|
||||
**Usage**:
|
||||
|
||||
* Transaction signing using vault keys
|
||||
* Account management and EIP-1559 support
|
||||
* Modular pluggability to support multiple networks([Medium][2])
|
||||
|
||||
---
|
||||
|
||||
## 🧰 CLI Interface
|
||||
|
||||
**Purpose**: Provide a command-line interface for interacting with the `vault` and `evm_client` crates, with scripting capabilities via Rhai.
|
||||
|
||||
### Features
|
||||
|
||||
* Built with `rhai` scripting engine for dynamic workflows
|
||||
* Thin wrapper over `vault` and `evm_client`
|
||||
* Exposes custom functions to Rhai:
|
||||
|
||||
```rust
|
||||
fn sign_tx(...) -> Result<String, Box<EvalAltResult>>;
|
||||
fn create_keyspace(...) -> ...;
|
||||
```
|
||||
|
||||
|
||||
|
||||
* Asynchronous operations managed via `tokio` or `async-std`
|
||||
|
||||
---
|
||||
|
||||
## 🌐 WebAssembly (Browser) Target
|
||||
|
||||
**Purpose**: Provide a browser-compatible interface for the `vault` and `evm_client` crates, compiled to WebAssembly.
|
||||
|
||||
### Features
|
||||
|
||||
* Exposed using `wasm-bindgen`
|
||||
* No Rhai scripting in browser due to native-only dependencies
|
||||
* Interaction model:
|
||||
|
||||
* Expose WebAssembly bindings (async `Promise`-compatible)
|
||||
* Front-end (e.g., React) calls functions via JS bridge
|
||||
* Keyspace and signing operations run within WASM memory
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Rhai Integration Strategy
|
||||
|
||||
* Only used in CLI
|
||||
* Bind only synchronous APIs
|
||||
* Asynchronous work handled by sending commands to a background task([Deno][3])
|
||||
|
||||
```rust
|
||||
rhai.register_fn("sign", move |input: String| -> String {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
command_sender.send(VaultCommand::SignMessage { input, resp: tx });
|
||||
rx.blocking_recv().unwrap()
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Runtime Strategy
|
||||
|
||||
* **Library (`vault`, `kvstore`, `evm_client`)**:
|
||||
|
||||
* Must be async-runtime agnostic
|
||||
* No global runtime should be spawned
|
||||
* Use `async-trait`, `Send + 'static` futures
|
||||
|
||||
* **CLI & Web Targets**:
|
||||
|
||||
* CLI: Use `tokio` or `async-std`
|
||||
* WASM: Use `wasm-bindgen-futures` and `spawn_local`
|
||||
|
||||
---
|
||||
|
||||
## 📐 Architecture Diagram
|
||||
|
||||
```
|
||||
[ CLI (Rhai) ] [ Browser (WASM) ]
|
||||
| |
|
||||
[ Scripts ] [ JS / TS ]
|
||||
| |
|
||||
[ Runtime ] [ wasm-bindgen ]
|
||||
| |
|
||||
[ vault (async) ] [ vault (wasm32) ]
|
||||
| |
|
||||
[ kvstore (sled) ] [ kvstore (idb) ]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Dependency Overview
|
||||
|
||||
| Crate | Key Deps | WASM Support |
|
||||
| ----------- | --------------------- | ---------------------------- |
|
||||
| kvstore | sled, idb | ✅ |
|
||||
| hero\_vault | aes-gcm, argon2, rand | ✅ |
|
||||
| evm\_client | alloy | ✅ |
|
||||
| CLI | rhai, tokio | ❌ |
|
||||
| Web Target | wasm-bindgen, idb | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Plan
|
||||
|
||||
1. **Scaffold Crates**:
|
||||
|
||||
* `kvstore`
|
||||
* `vault`
|
||||
* `evm_client`
|
||||
|
||||
2. **Implement `KVStore` Trait**:
|
||||
|
||||
* Implement `sled` backend for native
|
||||
* Implement `idb` backend for WASM
|
||||
|
||||
3. **Develop `vault`**:
|
||||
|
||||
* Implement password-based encrypted keyspaces
|
||||
* Integrate with `kvstore` for persistence
|
||||
* Implement cryptographic operations (signing, encryption, etc.)([GitHub][4])
|
||||
|
||||
4. **Develop `evm_client`**:
|
||||
|
||||
* Integrate with `alloy`
|
||||
* Implement transaction signing using `vault` keys
|
||||
* Implement account management and contract interaction
|
||||
|
||||
5. **Develop CLI Interface**:
|
||||
|
||||
* Integrate `rhai` scripting engine
|
||||
* Expose `vault` and `evm_client` functionalities
|
||||
* Implement message-passing for async operations
|
||||
|
||||
6. **Develop WebAssembly Target**:
|
||||
|
||||
* Compile `vault` and `evm_client` to WASM using `wasm-bindgen`
|
||||
* Expose functionalities to JavaScript
|
||||
* Implement frontend interface (e.g., React)
|
||||
|
||||
7. **Testing and Documentation**:
|
||||
|
||||
* Write unit and integration tests for all functionalities
|
||||
* Document public APIs and usage examples
|
||||
|
||||
---
|
||||
|
||||
This comprehensive plan ensures a modular, secure, and cross-platform cryptographic system, drawing inspiration from the `herocode/webassembly` project. The design facilitates both command-line and browser-based applications, providing
|
||||
|
||||
[1]: https://stackoverflow.com/questions/78979955/how-encrypt-on-blazor-wasm-wpa-using-aes-and-rfc2898?utm_source=chatgpt.com "how encrypt on blazor wasm wpa using Aes and Rfc2898"
|
||||
[2]: https://medium.com/coderhack-com/coderhack-cryptography-libraries-and-uses-in-rust-31957242299f?utm_source=chatgpt.com "Cryptography with rust | by Amay B | CoderHack.com - Medium"
|
||||
[3]: https://deno.com/blog/v1.12?utm_source=chatgpt.com "Deno 1.12 Release Notes"
|
||||
[4]: https://github.com/matrix-org/matrix-rust-sdk-crypto-wasm/releases?utm_source=chatgpt.com "Releases · matrix-org/matrix-rust-sdk-crypto-wasm - GitHub"
|
79
docs/architecture.md
Normal file
79
docs/architecture.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Architecture Overview
|
||||
|
||||
This document describes the architecture and design rationale for the modular Rust system, including the `kvstore`, `vault`, and `evm_client` crates, as well as the CLI and WASM/web-app targets.
|
||||
|
||||
## Table of Contents
|
||||
- [Summary](#summary)
|
||||
- [Crate and Module Structure](#crate-and-module-structure)
|
||||
- [Design Rationale](#design-rationale)
|
||||
- [Vault Crate Design](#vault-crate-design)
|
||||
- [Async, WASM, and Integration](#async-wasm-and-integration)
|
||||
- [Diagrams & References](#diagrams--references)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
This system is organized as a Rust workspace with three core crates:
|
||||
- `kvstore`: Abstract, async key-value storage for both native and WASM environments.
|
||||
- `vault`: Manages encrypted keyspaces and cryptographic operations, using `kvstore` for persistence.
|
||||
- `evm_client`: Ethereum RPC client, signs transactions using keys from `vault`.
|
||||
|
||||
Front-end targets:
|
||||
- CLI (with Rhai scripting)
|
||||
- WASM/web-app (with JS interop)
|
||||
|
||||
---
|
||||
|
||||
## Crate and Module Structure
|
||||
- **Workspace:** Top-level `Cargo.toml` lists all member crates.
|
||||
- **Features & cfg:** Use Cargo features and `#[cfg]` attributes for platform-specific code (e.g., `sled` for native, `idb` for WASM).
|
||||
- **Dependencies:**
|
||||
- `kvstore` uses `sled` (native) and `idb` (WASM).
|
||||
- `vault` uses `kvstore` and crypto crates (`chacha20poly1305`, `pbkdf2`, etc.).
|
||||
- `evm_client` uses `vault` and Ethereum libraries (e.g., `alloy`).
|
||||
- CLI uses Rhai and async runtime (e.g., Tokio).
|
||||
- Web app uses `wasm-bindgen` and exposes Rust APIs to JS.
|
||||
|
||||
---
|
||||
|
||||
## Design Rationale
|
||||
### Improvements in the New Implementation
|
||||
- **Async/Await API:** All operations are async, supporting non-blocking I/O for both WASM and native environments.
|
||||
- **Backend Abstraction:** The `KVStore` trait abstracts over multiple storage backends, enabling cross-platform support and easier testing.
|
||||
- **Separation of Concerns:**
|
||||
- `kvstore` handles only storage.
|
||||
- `vault` is responsible for encryption, decryption, and password management.
|
||||
- **WASM and Native Support:** Out-of-the-box support for both browser (IndexedDB) and native (sled) environments.
|
||||
- **Cleaner, Testable Design:** Each layer is independently testable and mockable.
|
||||
|
||||
### Why Encryption and Password Protection are in Vault
|
||||
- **Single Responsibility:** `kvstore` is for storage; `vault` handles security.
|
||||
- **Flexibility:** Encryption algorithms and policies can evolve in `vault` without changing storage.
|
||||
- **Security:** Cryptography is isolated in `vault`, reducing attack surface and easing audits.
|
||||
- **Cross-Platform Consistency:** Same vault logic regardless of storage backend.
|
||||
|
||||
---
|
||||
|
||||
## Vault Crate Design
|
||||
- **Encrypted Keyspace:** Password-protected, supports multiple keypairs (e.g., secp256k1, Ed25519).
|
||||
- **Crypto APIs:** Async functions for key management, signing, encryption/decryption.
|
||||
- **Storage:** Uses `kvstore` for persistence; re-encrypts and stores the keyspace as a blob.
|
||||
- **WASM Compatibility:** Uses Rust crypto crates that support `wasm32-unknown-unknown`.
|
||||
|
||||
---
|
||||
|
||||
## Async, WASM, and Integration
|
||||
- **Exports:** Use `#[wasm_bindgen]` to expose async functions to JS.
|
||||
- **Async runtime:** Use `wasm_bindgen_futures::spawn_local` in the browser.
|
||||
- **Interop:** JS and Rust communicate via Promises and `JsValue`.
|
||||
- **CLI:** Uses Rhai scripting and async runtime.
|
||||
|
||||
---
|
||||
|
||||
## Diagrams & References
|
||||
- See included diagrams for crate relationships and message-passing patterns.
|
||||
- Design patterns and best practices are drawn from Rust async and WASM guidelines.
|
||||
|
||||
---
|
||||
|
||||
*This document merges and replaces content from the previous `Architecture.md` and `kvstore-vault-architecture.md`. For further details on implementation, see `vault_impl_plan.md`.*
|
@ -60,7 +60,7 @@ This document outlines the steps and requirements to guarantee that both native
|
||||
|
||||
## 5. Testing
|
||||
- **Native:** `cargo test`
|
||||
- **WASM:** `cargo test --target wasm32-unknown-unknown --release` (or use `wasm-pack test`)
|
||||
- **WASM:** `wasm-pack test --headless --firefox` (or `--chrome`) inside the crate directory
|
||||
- **Separate tests** for native and WASM backends in `tests/`.
|
||||
|
||||
---
|
@ -1,73 +0,0 @@
|
||||
# What’s Improved in the New Implementation
|
||||
|
||||
_and why Encryption and Password Protection should be implemented in the Vault crate_
|
||||
|
||||
---
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
This document compares the old and new designs of the key-value store (kvstore) module, highlights improvements in the new implementation, and explains the architectural decision to move encryption and password protection to the vault crate.
|
||||
|
||||
---
|
||||
|
||||
## 2. Improvements in the New Implementation
|
||||
|
||||
### a. **Async/Await API**
|
||||
- All operations are asynchronous, enabling non-blocking I/O.
|
||||
- Essential for WASM/browser and scalable server environments.
|
||||
|
||||
### b. **Backend Abstraction**
|
||||
- The `KVStore` trait abstracts over multiple storage backends (native and WASM).
|
||||
- Enables cross-platform support and easier testing.
|
||||
|
||||
### c. **Separation of Concerns**
|
||||
- The storage layer (`kvstore`) is now focused solely on key-value persistence.
|
||||
- No longer mixes storage with cryptography or user authentication.
|
||||
|
||||
### d. **WASM and Native Support**
|
||||
- Out-of-the-box support for both browser (IndexedDB) and native (sled) environments.
|
||||
- Easy to extend with new backends in the future.
|
||||
|
||||
### e. **Cleaner, More Testable Design**
|
||||
- Each layer is independently testable and mockable.
|
||||
- Simpler to reason about and maintain.
|
||||
|
||||
---
|
||||
|
||||
## 3. Why Encryption and Password Protection Belong in the Vault Crate
|
||||
|
||||
### a. **Single Responsibility Principle**
|
||||
- `kvstore` should only handle storage, not cryptographic operations or user authentication.
|
||||
- `vault` is responsible for security: encryption, decryption, password management.
|
||||
|
||||
### b. **Flexibility and Extensibility**
|
||||
- Different applications may require different encryption schemes or policies.
|
||||
- By implementing encryption in `vault`, you can easily swap algorithms, add multi-user support, or support new crypto features without touching the storage backend.
|
||||
|
||||
### c. **Security Best Practices**
|
||||
- Keeping cryptography separate from storage reduces the attack surface and risk of subtle bugs.
|
||||
- All key material and secrets are encrypted before being handed to the storage layer.
|
||||
|
||||
### d. **Cross-Platform Consistency**
|
||||
- The same vault logic can be used regardless of storage backend (sled, IndexedDB, etc).
|
||||
- Ensures consistent encryption and password handling on all platforms.
|
||||
|
||||
### e. **Easier Upgrades and Auditing**
|
||||
- Security code is isolated in one place (`vault`), making it easier to audit and upgrade.
|
||||
|
||||
---
|
||||
|
||||
## 4. Summary Table
|
||||
|
||||
| Layer | Responsibility | Encryption | Passwords | Storage Backend |
|
||||
|-----------|------------------------|------------|-----------|----------------|
|
||||
| kvstore | Persistence/Storage | ❌ | ❌ | sled, IndexedDB|
|
||||
| vault | Security, Key Mgmt | ✅ | ✅ | Uses kvstore |
|
||||
|
||||
---
|
||||
|
||||
## 5. Conclusion
|
||||
|
||||
- The new design is more modular, secure, and maintainable.
|
||||
- Encryption and password logic in `vault` enables strong, flexible security while keeping storage simple and robust.
|
||||
|
123
docs/kvstore.md
Normal file
123
docs/kvstore.md
Normal file
@ -0,0 +1,123 @@
|
||||
# kvstore Crate Overview
|
||||
|
||||
`kvstore` is a runtime-agnostic, async key-value storage crate designed for both native (using `sled`) and WASM/browser (using IndexedDB via the `idb` crate) environments. It provides a unified API for all platforms, enabling seamless storage abstraction for Rust applications.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
- [Summary](#summary)
|
||||
- [Main Components](#main-components)
|
||||
- [Supported Environments](#supported-environments)
|
||||
- [Quickstart & Usage Examples](#quickstart--usage-examples)
|
||||
- [API Reference](#api-reference)
|
||||
- [More Information](#more-information)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
The `kvstore` crate defines an async trait for key-value storage, with robust implementations for native and browser environments. It is the storage backend for higher-level crates such as `vault`.
|
||||
|
||||
---
|
||||
|
||||
## Main Components
|
||||
- **KVStore Trait**: Async interface for key-value operations (`get`, `set`, `remove`, `contains_key`, `keys`, `clear`).
|
||||
- **NativeStore**: Native backend using `sled` (requires Tokio runtime).
|
||||
- **WasmStore**: WASM/browser backend using IndexedDB via the `idb` crate.
|
||||
- **KVError**: Error type covering I/O, serialization, encryption, and backend-specific issues.
|
||||
|
||||
---
|
||||
|
||||
## Supported Environments
|
||||
- **Native:** Uses `sled` for fast, embedded storage. Blocking I/O is offloaded to background threads using Tokio.
|
||||
- **Browser (WASM):** Uses IndexedDB via the `idb` crate. Fully async and Promise-based.
|
||||
|
||||
---
|
||||
|
||||
## Quickstart & Usage Examples
|
||||
|
||||
### Native Example
|
||||
```rust
|
||||
use kvstore::{KVStore, NativeStore};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let store = NativeStore::open("/tmp/mydb").unwrap();
|
||||
store.set("foo", b"bar").await.unwrap();
|
||||
let val = store.get("foo").await.unwrap();
|
||||
println!("Got: {:?}", val);
|
||||
}
|
||||
```
|
||||
|
||||
### WASM/Browser Example
|
||||
```rust
|
||||
// In a browser/WASM environment:
|
||||
use kvstore::{KVStore, WasmStore};
|
||||
|
||||
// Must be called from an async context (e.g., JS Promise)
|
||||
let store = WasmStore::open("mydb").await.unwrap();
|
||||
store.set("foo", b"bar").await.unwrap();
|
||||
let val = store.get("foo").await.unwrap();
|
||||
// Use the value as needed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### KVStore Trait
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait KVStore {
|
||||
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>>;
|
||||
async fn set(&self, key: &str, value: &[u8]) -> Result<()>;
|
||||
async fn remove(&self, key: &str) -> Result<()>;
|
||||
async fn contains_key(&self, key: &str) -> Result<bool>;
|
||||
async fn keys(&self) -> Result<Vec<String>>;
|
||||
async fn clear(&self) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
### NativeStore
|
||||
```rust
|
||||
pub struct NativeStore { /* ... */ }
|
||||
|
||||
impl NativeStore {
|
||||
pub fn open(path: &str) -> Result<Self>;
|
||||
// Implements KVStore trait
|
||||
}
|
||||
```
|
||||
|
||||
### WasmStore
|
||||
```rust
|
||||
pub struct WasmStore { /* ... */ }
|
||||
|
||||
impl WasmStore {
|
||||
pub async fn open(name: &str) -> Result<Self>;
|
||||
// Implements KVStore trait
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```rust
|
||||
#[derive(Debug, Error)]
|
||||
pub enum KVError {
|
||||
Io(std::io::Error),
|
||||
KeyNotFound(String),
|
||||
StoreNotFound(String),
|
||||
Serialization(String),
|
||||
Deserialization(String),
|
||||
Encryption(String),
|
||||
Decryption(String),
|
||||
Other(String),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## More Information
|
||||
- For architecture and design, see [`architecture.md`](architecture.md)
|
||||
- For integration examples, see [`build_instructions.md`](build_instructions.md)
|
||||
|
||||
---
|
||||
|
||||
*For advanced usage, backend customization, and WASM integration, see the linked documents above.*
|
166
docs/vault.md
166
docs/vault.md
@ -1,103 +1,92 @@
|
||||
🧱 Vault Crate Architecture
|
||||
1. VaultStore
|
||||
Purpose: Central manager for all keyspaces.
|
||||
# Vault Crate Overview
|
||||
|
||||
Responsibilities:
|
||||
Welcome to the Vault crate! This document provides a high-level overview, usage examples, and a guide to the main components of the Vault system. For deeper technical details, see [`architecture.md`](architecture.md) and [`vault_impl_plan.md`](vault_impl_plan.md).
|
||||
|
||||
Maintain metadata about keyspaces.
|
||||
---
|
||||
|
||||
Provide interfaces to create, load, and manage keyspaces.
|
||||
## Table of Contents
|
||||
- [Summary](#summary)
|
||||
- [Main Components](#main-components)
|
||||
- [Security Model](#security-model)
|
||||
- [Quickstart & Usage Examples](#quickstart--usage-examples)
|
||||
- [More Information](#more-information)
|
||||
|
||||
Ensure each keyspace is encrypted with its own password.
|
||||
---
|
||||
|
||||
2. KeySpace
|
||||
Purpose: Isolated environment containing multiple keypairs.
|
||||
## Summary
|
||||
The Vault crate is a modular, async, and WASM-compatible cryptographic keystore. It manages encrypted keyspaces (each with multiple keypairs), provides cryptographic APIs, and persists all data via a pluggable key-value store. All sensitive material is always encrypted at rest.
|
||||
|
||||
Responsibilities:
|
||||
---
|
||||
|
||||
Securely store and manage keypairs.
|
||||
## Main Components
|
||||
- **VaultStore**: Central manager for all keyspaces. Handles creation, loading, and metadata.
|
||||
- **KeySpace**: Isolated environment containing multiple keypairs, encrypted with its own password.
|
||||
- **KeyPair**: Represents an individual cryptographic keypair (e.g., secp256k1, Ed25519).
|
||||
- **Symmetric Encryption Module**: Handles encryption/decryption using ChaCha20Poly1305 and PBKDF2.
|
||||
- **SessionManager** (optional): Manages active context for ergonomic API usage (not required for stateless usage).
|
||||
|
||||
Provide cryptographic operations like signing and encryption.
|
||||
---
|
||||
|
||||
Handle encryption/decryption using a password-derived key.
|
||||
## Security Model
|
||||
- **Per-KeySpace Encryption:** Each keyspace is encrypted independently using a password-derived key.
|
||||
- **VaultStore Metadata:** Stores non-sensitive metadata about keyspaces (names, creation dates, etc.).
|
||||
- **Zero-Knowledge:** Passwords and keys are never stored in plaintext; all cryptographic operations are performed in memory.
|
||||
|
||||
3. KeyPair
|
||||
Purpose: Represents an individual cryptographic keypair.
|
||||
---
|
||||
|
||||
Responsibilities:
|
||||
## Quickstart & Usage Examples
|
||||
|
||||
Perform cryptographic operations such as signing and verification.
|
||||
```rust
|
||||
// Create a new VaultStore
|
||||
let mut vault = VaultStore::new();
|
||||
|
||||
Support key export and import functionalities.
|
||||
// Create a new keyspace
|
||||
vault.create_keyspace("personal", "password123")?;
|
||||
|
||||
4. Symmetric Encryption Module
|
||||
Purpose: Provides encryption and decryption functionalities.
|
||||
// List available keyspaces
|
||||
let keyspaces = vault.list_keyspaces();
|
||||
|
||||
Responsibilities:
|
||||
// Unlock a keyspace
|
||||
let keyspace = vault.load_keyspace("personal", "password123")?;
|
||||
|
||||
Encrypt and decrypt data using algorithms like ChaCha20Poly1305.
|
||||
// Create a new keypair
|
||||
let key_id = keyspace.create_key(KeyType::Secp256k1, "mykey")?;
|
||||
|
||||
Derive encryption keys from passwords using PBKDF2.
|
||||
// Sign a message
|
||||
let signature = keyspace.sign(&key_id, b"hello world")?;
|
||||
```
|
||||
|
||||
5. SessionManager
|
||||
Purpose: Manages the active context for cryptographic operations, simplifying API usage.
|
||||
---
|
||||
|
||||
Responsibilities:
|
||||
## More Information
|
||||
- **Architecture & Design:** See [`architecture.md`](architecture.md)
|
||||
- **Implementation Details:** See [`vault_impl_plan.md`](vault_impl_plan.md)
|
||||
- **Build Instructions:** See [`build_instructions.md`](build_instructions.md)
|
||||
|
||||
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
|
||||
*For advanced usage (stateless/session APIs, custom backends, WASM integration), see the linked documents above.*
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
### KeySpace API Example
|
||||
|
||||
```rust
|
||||
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
|
||||
```
|
||||
|
||||
### KeyPair API Example
|
||||
|
||||
```rust
|
||||
pub struct KeyPair {
|
||||
// Internal fields
|
||||
}
|
||||
@ -105,17 +94,16 @@ pub struct KeyPair {
|
||||
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
|
||||
```
|
||||
|
||||
### SessionManager API Example (Optional)
|
||||
|
||||
```rust
|
||||
pub struct SessionManager {
|
||||
keyspace: KeySpace,
|
||||
active_keypair: String,
|
||||
@ -127,38 +115,24 @@ impl SessionManager {
|
||||
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
|
||||
```
|
||||
|
||||
### Storage Structure
|
||||
|
||||
```text
|
||||
vault_store/
|
||||
├── metadata.json
|
||||
└── keyspaces/
|
||||
├── alice.ksp
|
||||
├── bob.ksp
|
||||
└── ...
|
||||
metadata.json: Contains metadata about each keyspace, such as name and creation date.
|
||||
```
|
||||
- `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.
|
||||
## Supported Environments
|
||||
|
||||
Usage:
|
||||
- **Native:** Uses filesystem or a database (e.g., SQLite) for storage.
|
||||
- **Browser (WASM):** Uses IndexedDB or localStorage via the kvstore abstraction.
|
||||
|
||||
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.
|
||||
For full build and integration instructions, see [build_instructions.md](build_instructions.md).
|
||||
|
@ -1,10 +1,23 @@
|
||||
# Vault Implementation Plan
|
||||
# Vault Implementation Plan (Technical Appendix)
|
||||
|
||||
This document is a technical reference for contributors and maintainers of the Vault crate. It covers advanced implementation details, design rationale, and data models. For a high-level overview and usage, see [`vault.md`](vault.md) and [`architecture.md`](architecture.md).
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
- [Design Principle: Stateless & Session APIs](#design-principle-stateless--session-apis)
|
||||
- [Data Model](#data-model)
|
||||
- [Module & File Structure](#module--file-structure)
|
||||
- [Advanced Notes](#advanced-notes)
|
||||
|
||||
---
|
||||
|
||||
|
||||
> **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
|
||||
## Design Principle: Stateless & Session APIs
|
||||
|
||||
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.
|
||||
|
||||
@ -15,9 +28,7 @@ The `vault` crate is a modular, async, and WASM-compatible cryptographic keystor
|
||||
- **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.
|
||||
|
||||
@ -59,7 +70,7 @@ You can design the vault crate to support both stateless and session-based (stat
|
||||
|
||||
---
|
||||
|
||||
## 2. Data Model
|
||||
## Data Model
|
||||
|
||||
### VaultMetadata & Keyspace Model
|
||||
```rust
|
||||
@ -141,64 +152,6 @@ impl SessionManager {
|
||||
}
|
||||
```
|
||||
|
||||
### 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/
|
||||
@ -217,7 +170,12 @@ vault/
|
||||
|
||||
---
|
||||
|
||||
## 6. Cryptography: Crates and Algorithms
|
||||
## Advanced Notes
|
||||
|
||||
- For further context on cryptographic choices, async patterns, and WASM compatibility, see `architecture.md`.
|
||||
- This appendix is intended for developers extending or maintaining the Vault implementation.
|
||||
|
||||
### Cryptography: Crates and Algorithms
|
||||
|
||||
**Crates:**
|
||||
- [`aes-gcm`](https://crates.io/crates/aes-gcm): AES-GCM authenticated encryption (WASM-compatible)
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
//! Crypto utilities for the vault crate
|
||||
use chacha20poly1305::{ChaCha20Poly1305, KeyInit as ChaChaKeyInit, aead::{Aead, generic_array::GenericArray}};
|
||||
use aes_gcm::Aes256Gcm;
|
||||
|
||||
use pbkdf2::pbkdf2_hmac;
|
||||
use scrypt::{scrypt, Params as ScryptParams};
|
||||
|
||||
use sha2::Sha256;
|
||||
use rand_core::{RngCore, OsRng as RandOsRng};
|
||||
|
||||
@ -12,12 +12,7 @@ pub mod kdf {
|
||||
use super::*;
|
||||
|
||||
|
||||
pub fn derive_key_scrypt(password: &[u8], salt: &[u8], key_len: usize) -> Result<Vec<u8>, String> {
|
||||
let params = ScryptParams::recommended();
|
||||
let mut key = vec![0u8; key_len];
|
||||
scrypt(password, salt, ¶ms, &mut key).map_err(|e| e.to_string())?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
|
||||
pub fn derive_key_pbkdf2(password: &[u8], salt: &[u8], key_len: usize, iterations: u32) -> Vec<u8> {
|
||||
let mut key = vec![0u8; key_len];
|
||||
@ -42,17 +37,9 @@ pub mod cipher {
|
||||
.map_err(|e| format!("decryption error: {e}"))
|
||||
}
|
||||
|
||||
pub fn encrypt_aes_gcm(key: &[u8], plaintext: &[u8], nonce: &[u8]) -> Result<Vec<u8>, String> {
|
||||
let cipher = Aes256Gcm::new(GenericArray::from_slice(key));
|
||||
cipher.encrypt(GenericArray::from_slice(nonce), plaintext)
|
||||
.map_err(|e| format!("encryption error: {e}"))
|
||||
}
|
||||
|
||||
pub fn decrypt_aes_gcm(key: &[u8], ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>, String> {
|
||||
let cipher = Aes256Gcm::new(GenericArray::from_slice(key));
|
||||
cipher.decrypt(GenericArray::from_slice(nonce), ciphertext)
|
||||
.map_err(|e| format!("decryption error: {e}"))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
pub fn random_salt(len: usize) -> Vec<u8> {
|
||||
|
@ -11,8 +11,6 @@ pub struct VaultMetadata {
|
||||
pub struct KeyspaceMetadata {
|
||||
pub name: String,
|
||||
pub salt: [u8; 16], // Unique salt for this keyspace
|
||||
pub kdf: String, // e.g. "scrypt" or "pbkdf2"
|
||||
pub cipher: String, // e.g. "chacha20poly1305" or "aes-gcm"
|
||||
pub encrypted_blob: Vec<u8>,
|
||||
pub created_at: Option<u64>, // Unix timestamp
|
||||
pub tags: Option<Vec<String>>,
|
||||
|
231
vault/src/lib.rs
231
vault/src/lib.rs
@ -14,11 +14,12 @@ use kvstore::KVStore;
|
||||
use data::*;
|
||||
use error::VaultError;
|
||||
use crate::crypto::random_salt;
|
||||
use crate::crypto::kdf;
|
||||
|
||||
use crate::crypto::cipher::{encrypt_chacha20, decrypt_chacha20, encrypt_aes_gcm, decrypt_aes_gcm};
|
||||
use crate::crypto::cipher::{encrypt_chacha20, decrypt_chacha20};
|
||||
use signature::SignatureEncoding;
|
||||
// TEMP: File-based debug logger for crypto troubleshooting
|
||||
use log::{debug, info, error};
|
||||
use log::{debug};
|
||||
|
||||
/// Vault: Cryptographic keyspace and operations
|
||||
pub struct Vault<S: KVStore> {
|
||||
@ -27,31 +28,16 @@ pub struct Vault<S: KVStore> {
|
||||
}
|
||||
|
||||
/// Helper to encrypt and prepend nonce to ciphertext for keyspace storage
|
||||
fn encrypt_with_nonce_prepended(key: &[u8], plaintext: &[u8], cipher: &str) -> Result<Vec<u8>, VaultError> {
|
||||
use crate::crypto::random_salt;
|
||||
use crate::crypto;
|
||||
/// Helper to encrypt and prepend nonce to ciphertext for keyspace storage
|
||||
/// Always uses ChaCha20Poly1305.
|
||||
fn encrypt_with_nonce_prepended(key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, VaultError> {
|
||||
let nonce = random_salt(12);
|
||||
debug!("nonce: {}", hex::encode(&nonce));
|
||||
let (ct, _key_hex) = match cipher {
|
||||
"chacha20poly1305" => {
|
||||
// Always use ChaCha20Poly1305 for encryption
|
||||
let ct = encrypt_chacha20(key, plaintext, &nonce)
|
||||
.map_err(|e| VaultError::Crypto(e))?;
|
||||
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!("ct: {}", hex::encode(&ct));
|
||||
debug!("key: {}", hex::encode(key));
|
||||
(ct, hex::encode(key))
|
||||
},
|
||||
_ => {
|
||||
debug!("unsupported cipher: {}", cipher);
|
||||
return Err(VaultError::Other(format!("Unsupported cipher: {cipher}")));
|
||||
}
|
||||
};
|
||||
let mut blob = nonce.clone();
|
||||
blob.extend_from_slice(&ct);
|
||||
debug!("ENCRYPTED (nonce|ct): {}", hex::encode(&blob));
|
||||
@ -64,7 +50,8 @@ impl<S: KVStore> Vault<S> {
|
||||
}
|
||||
|
||||
/// Create a new keyspace with the given name, password, and options.
|
||||
pub async fn create_keyspace(&mut self, name: &str, password: &[u8], kdf: &str, cipher: &str, tags: Option<Vec<String>>) -> Result<(), VaultError> {
|
||||
/// Create a new keyspace with the given name and password. Always uses PBKDF2 and ChaCha20Poly1305.
|
||||
pub async fn create_keyspace(&mut self, name: &str, password: &[u8], 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!("keyspace '{}' already exists", name);
|
||||
@ -79,20 +66,8 @@ impl<S: KVStore> Vault<S> {
|
||||
let salt = random_salt(16);
|
||||
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!("kdf scrypt error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &salt, 32, 10_000),
|
||||
_ => {
|
||||
debug!("unsupported KDF: {}", kdf);
|
||||
return Err(VaultError::Other(format!("Unsupported KDF: {kdf}")));
|
||||
}
|
||||
};
|
||||
// Always use PBKDF2 for key derivation
|
||||
let key = kdf::derive_key_pbkdf2(password, &salt, 32, 10_000);
|
||||
debug!("derived key: {} bytes", key.len());
|
||||
// 3. Prepare initial keyspace data
|
||||
let keyspace_data = KeyspaceData { keypairs: vec![] };
|
||||
@ -108,15 +83,14 @@ impl<S: KVStore> Vault<S> {
|
||||
let nonce = random_salt(12);
|
||||
debug!("nonce: {}", hex::encode(&nonce));
|
||||
// 5. Encrypt
|
||||
let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, cipher)?;
|
||||
// Always use ChaCha20Poly1305 for encryption
|
||||
let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext)?;
|
||||
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(),
|
||||
salt: salt.try_into().unwrap_or([0u8; 16]),
|
||||
kdf: kdf.to_string(),
|
||||
cipher: cipher.to_string(),
|
||||
encrypted_blob,
|
||||
created_at: Some(crate::utils::now()),
|
||||
tags,
|
||||
@ -151,9 +125,11 @@ impl<S: KVStore> Vault<S> {
|
||||
}
|
||||
|
||||
/// Unlock a keyspace by name and password, returning the decrypted data
|
||||
/// Unlock a keyspace by name and password, returning the decrypted data
|
||||
/// Always uses PBKDF2 and ChaCha20Poly1305.
|
||||
pub async fn unlock_keyspace(&self, name: &str, password: &[u8]) -> Result<KeyspaceData, VaultError> {
|
||||
debug!("unlock_keyspace entry: name={}", name);
|
||||
use crate::crypto::{kdf};
|
||||
// use crate::crypto::kdf; // removed if not needed
|
||||
use serde_json;
|
||||
// 1. Fetch keyspace metadata
|
||||
let meta_bytes = self.storage.get(name).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
||||
@ -164,51 +140,18 @@ impl<S: KVStore> Vault<S> {
|
||||
return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string()));
|
||||
}
|
||||
// 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!("kdf scrypt error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
||||
_ => {
|
||||
debug!("unsupported KDF: {}", metadata.kdf);
|
||||
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
||||
}
|
||||
};
|
||||
let key = kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000);
|
||||
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!("ciphertext too short: {}", ciphertext.len());
|
||||
return Err(VaultError::Crypto("Ciphertext too short".to_string()));
|
||||
}
|
||||
|
||||
let (nonce, ct) = ciphertext.split_at(12);
|
||||
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!("chacha20poly1305 error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
"aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
debug!("aes-gcm error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
debug!("unsupported cipher: {}", metadata.cipher);
|
||||
return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher)));
|
||||
}
|
||||
};
|
||||
let plaintext = decrypt_chacha20(&key, ct, nonce).map_err(VaultError::Crypto)?;
|
||||
debug!("plaintext decrypted: {} bytes", plaintext.len());
|
||||
// 4. Deserialize keyspace data
|
||||
let keyspace_data: KeyspaceData = match serde_json::from_slice(&plaintext) {
|
||||
@ -231,7 +174,9 @@ impl<S: KVStore> Vault<S> {
|
||||
// --- Keypair Management APIs ---
|
||||
|
||||
/// Add a new keypair to a keyspace (generates and stores a new keypair)
|
||||
pub async fn add_keypair(&mut self, keyspace: &str, password: &[u8], key_type: KeyType, metadata: Option<KeyMetadata>) -> Result<String, VaultError> {
|
||||
/// Add a new keypair to a keyspace (generates and stores a new keypair)
|
||||
/// If key_type is None, defaults to Secp256k1.
|
||||
pub async fn add_keypair(&mut self, keyspace: &str, password: &[u8], key_type: Option<KeyType>, metadata: Option<KeyMetadata>) -> Result<String, VaultError> {
|
||||
use crate::data::KeyEntry;
|
||||
use rand_core::OsRng;
|
||||
use rand_core::RngCore;
|
||||
@ -239,6 +184,7 @@ impl<S: KVStore> Vault<S> {
|
||||
// 1. Unlock keyspace
|
||||
let mut data = self.unlock_keyspace(keyspace, password).await?;
|
||||
// 2. Generate keypair
|
||||
let key_type = key_type.unwrap_or(KeyType::Secp256k1);
|
||||
let (private_key, public_key, id) = match key_type {
|
||||
KeyType::Ed25519 => {
|
||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||
@ -301,31 +247,18 @@ impl<S: KVStore> Vault<S> {
|
||||
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!("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!("metadata: kdf={} cipher={} salt={:?}", metadata.kdf, metadata.cipher, metadata.salt);
|
||||
debug!("metadata: salt={:?}", metadata.salt);
|
||||
if metadata.salt.len() != 16 {
|
||||
debug!("salt length {} != 16", metadata.salt.len());
|
||||
return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string()));
|
||||
}
|
||||
// 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!("kdf scrypt error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
||||
_ => {
|
||||
debug!("unsupported KDF: {}", metadata.kdf);
|
||||
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
||||
}
|
||||
};
|
||||
let key = kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000);
|
||||
debug!("derived key: {} bytes", key.len());
|
||||
// 3. Serialize plaintext
|
||||
let plaintext = match serde_json::to_vec(data) {
|
||||
@ -340,7 +273,7 @@ impl<S: KVStore> Vault<S> {
|
||||
let nonce = random_salt(12);
|
||||
debug!("nonce: {}", hex::encode(&nonce));
|
||||
// 5. Encrypt
|
||||
let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, &metadata.cipher)?;
|
||||
let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext)?;
|
||||
debug!("encrypted_blob: {} bytes", encrypted_blob.len());
|
||||
// 6. Store new encrypted blob
|
||||
metadata.encrypted_blob = encrypted_blob;
|
||||
@ -411,13 +344,11 @@ 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!("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 {
|
||||
@ -434,47 +365,14 @@ pub async fn encrypt(&self, keyspace: &str, password: &[u8], plaintext: &[u8]) -
|
||||
return Err(VaultError::Serialization(e.to_string()));
|
||||
}
|
||||
};
|
||||
debug!("salt={:?} cipher={} (hex salt: {})", meta.salt, meta.cipher, hex::encode(&meta.salt));
|
||||
debug!("salt={:?} (hex salt: {})", meta.salt, 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!("kdf scrypt error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000),
|
||||
_ => {
|
||||
debug!("unsupported KDF: {}", meta.kdf);
|
||||
return Err(VaultError::Other(format!("Unsupported KDF: {}", meta.kdf)));
|
||||
}
|
||||
};
|
||||
let key = kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000);
|
||||
// 3. Generate nonce
|
||||
let nonce = random_salt(12);
|
||||
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!("chacha20poly1305 error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
"aes-gcm" => match encrypt_aes_gcm(&key, plaintext, &nonce) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
debug!("aes-gcm error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
debug!("unsupported cipher: {}", meta.cipher);
|
||||
return Err(VaultError::Other(format!("Unsupported cipher: {}", meta.cipher)));
|
||||
}
|
||||
};
|
||||
// 5. Prepend nonce to ciphertext
|
||||
let ciphertext = encrypt_chacha20(&key, plaintext, &nonce).map_err(VaultError::Crypto)?;
|
||||
let mut out = nonce;
|
||||
out.extend_from_slice(&ciphertext);
|
||||
Ok(out)
|
||||
@ -484,56 +382,31 @@ pub async fn encrypt(&self, keyspace: &str, password: &[u8], plaintext: &[u8]) -
|
||||
/// (for simplicity, uses keyspace password-derived key)
|
||||
pub async fn decrypt(&self, keyspace: &str, password: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, VaultError> {
|
||||
debug!("decrypt");
|
||||
debug!("keyspace={}", keyspace);
|
||||
use crate::crypto::{kdf};
|
||||
// 1. Fetch metadata
|
||||
|
||||
// 1. Load keyspace 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!("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!("storage error: {:?}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
||||
_ => {
|
||||
debug!("unsupported KDF: {}", metadata.kdf);
|
||||
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
||||
let meta_bytes = match meta_bytes {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
debug!("keyspace not found");
|
||||
return Err(VaultError::Other("Keyspace not found".to_string()));
|
||||
}
|
||||
};
|
||||
// 3. Split nonce and ciphertext
|
||||
if ciphertext.len() < 12 {
|
||||
debug!("ciphertext too short: {}", ciphertext.len());
|
||||
return Err(VaultError::Crypto("Ciphertext too short".to_string()));
|
||||
let meta: KeyspaceMetadata = match serde_json::from_slice(&meta_bytes) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
debug!("serialization error: {}", e);
|
||||
return Err(VaultError::Serialization(e.to_string()));
|
||||
}
|
||||
let (nonce, ct) = ciphertext.split_at(12);
|
||||
};
|
||||
debug!("salt={:?} (hex salt: {})", meta.salt, hex::encode(&meta.salt));
|
||||
// 2. Derive key
|
||||
let key = kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000);
|
||||
// 3. Extract nonce
|
||||
let nonce = &ciphertext[..12];
|
||||
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!("chacha20poly1305 error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
"aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
debug!("aes-gcm error: {}", e);
|
||||
return Err(VaultError::Crypto(e));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
debug!("unsupported cipher: {}", metadata.cipher);
|
||||
return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher)));
|
||||
}
|
||||
};
|
||||
let plaintext = decrypt_chacha20(&key, &ciphertext[12..], nonce).map_err(VaultError::Crypto)?;
|
||||
Ok(plaintext)
|
||||
}
|
||||
} // <-- Close the impl block
|
||||
}
|
@ -1,22 +1,4 @@
|
||||
//! Session manager for the vault crate (optional)
|
||||
|
||||
use crate::data::KeyspaceData;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct SessionManager {
|
||||
unlocked_keyspaces: HashMap<String, KeyspaceData>,
|
||||
current_keyspace: Option<String>,
|
||||
current_keypair: Option<String>,
|
||||
// ...
|
||||
}
|
||||
|
||||
impl SessionManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
unlocked_keyspaces: HashMap::new(),
|
||||
current_keyspace: None,
|
||||
current_keypair: None,
|
||||
}
|
||||
}
|
||||
// ... methods for unlock, lock, select, timeout, etc.
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ pub fn now() -> u64 {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
// Use JS Date.now() in milliseconds, convert to seconds
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
let date = js_sys::Date::new_0();
|
||||
(date.get_time() / 1000.0) as u64
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
use vault::{Vault, KeyType, KeyMetadata};
|
||||
use kvstore::native::NativeStore;
|
||||
|
||||
use log::{debug, info, error};
|
||||
use log::debug;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_keypair_management_and_crypto() {
|
||||
@ -16,23 +16,23 @@ async fn test_keypair_management_and_crypto() {
|
||||
let mut vault = Vault::new(store);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
compile_error!("This test is not intended for wasm32 targets");
|
||||
let keyspace = &format!("testspace_{}", chrono::Utc::now().timestamp_nanos());
|
||||
let keyspace = &format!("testspace_{}", chrono::Utc::now().timestamp_nanos_opt().unwrap());
|
||||
let password = b"supersecret";
|
||||
|
||||
debug!("keyspace: {} password: {}", keyspace, hex::encode(password));
|
||||
debug!("before create_keyspace");
|
||||
vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await.unwrap();
|
||||
vault.create_keyspace(keyspace, password, None).await.unwrap();
|
||||
|
||||
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;
|
||||
let key_id = vault.add_keypair(keyspace, password, Some(KeyType::Ed25519), Some(KeyMetadata { name: Some("edkey".into()), created_at: None, tags: None })).await;
|
||||
match &key_id {
|
||||
Ok(_) => debug!("after add Ed25519 keypair (Ok)"),
|
||||
Err(e) => debug!("after add Ed25519 keypair (Err): {:?}", e),
|
||||
}
|
||||
let key_id = key_id.unwrap();
|
||||
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();
|
||||
let secp_id = vault.add_keypair(keyspace, password, None, Some(KeyMetadata { name: Some("secpkey".into()), created_at: None, tags: None })).await.unwrap();
|
||||
|
||||
debug!("before list_keypairs");
|
||||
let keys = vault.list_keypairs(keyspace, password).await.unwrap();
|
||||
|
@ -18,19 +18,19 @@ async fn wasm_test_keypair_management_and_crypto() {
|
||||
log::debug!("Initialized vault and IndexedDB store");
|
||||
|
||||
// Step 1: Create keyspace
|
||||
match vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await {
|
||||
match vault.create_keyspace(keyspace, password, None).await {
|
||||
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 {
|
||||
let key_id = match vault.add_keypair(keyspace, password, Some(KeyType::Ed25519), Some(KeyMetadata { name: Some("edkey".into()), created_at: None, tags: None })).await {
|
||||
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 {
|
||||
let secp_id = match vault.add_keypair(keyspace, password, None, Some(KeyMetadata { name: Some("secpkey".into()), created_at: None, tags: None })).await {
|
||||
Ok(id) => { log::debug!("Added Secp256k1 keypair: {}", id); id },
|
||||
Err(e) => { log::debug!("Failed to add Secp256k1 keypair: {:?}", e); return; }
|
||||
};
|
||||
|
@ -1,4 +0,0 @@
|
||||
segment_size: 524288
|
||||
use_compression: false
|
||||
version: 0.34
|
||||
vQÁ
|
Binary file not shown.
Binary file not shown.
@ -1,156 +0,0 @@
|
||||
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`
|
Loading…
Reference in New Issue
Block a user