sal-modular/docs/rhai_architecture_plan.md
Sameh Abouelsaad 13945a8725 feat: Add WASM support and browser extension infrastructure
- Add WASM build target and dependencies for all crates.
- Implement IndexedDB-based persistent storage for WASM.
- Create browser extension infrastructure (UI, scripting, etc.).
- Integrate Rhai scripting engine for secure automation.
- Implement user stories and documentation for the extension.
2025-05-16 15:31:53 +03:00

398 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Rhai Scripting Architecture Plan
## Overview
This document describes the architecture and integration plan for Rhai scripting within the modular Rust cryptographic system. The goal is to enable secure, extensible scripting for both browser and (future) CLI environments, with the browser extension as the main user interface.
## Interfaces
- **Browser Extension**: The primary and recommended user interface for all modules, scripting, and automation.
- **CLI**: Planned as a future feature; not a primary interface.
## Vault & Scripting Capabilities
- All cryptographic operations (sign, verify, encrypt, decrypt) are exposed to Rhai scripts via the extension.
- Symmetric encryption/decryption of arbitrary messages/files is supported using a key derived from the keyspace password (see `Vault::encrypt`/`Vault::decrypt`).
- User-provided Rhai scripts can access the current session's signer (with explicit approval).
## Extension UI/UX & Workflow
### Phase 1: Local Session & Script Execution
1. **Session Management**
- User is prompted to create/unlock a keyspace and select/create a keypair.
- The session (unlocked keyspace + selected keypair) is required for all cryptographic actions and script execution.
2. **Per-Keypair Actions**
- Sign, verify
- Asymmetric encrypt/decrypt
- Symmetric encrypt/decrypt (using password-derived key)
- Send transaction, check balance (with selected provider)
- Execute user-provided Rhai script (from input box)
- Scripts have access to the session manager's current signer and can send transactions on behalf of the user, but require explicit approval per script execution.
### Phase 2: WebSocket Server Integration
1. **Connection**
- User must have an active session to connect to the server (connects using selected keypair's public key).
- Connection is persistent while the extension is open; user may lock keyspace but remain connected.
2. **Script Delivery & Approval**
- Server can send Rhai scripts to the extension, each with a title, description, and tags (e.g., `local`, `remote`).
- Extension notifies user of incoming script, displays metadata, and allows user to view the script.
- User must unlock their keyspace and select the correct keypair to approve/execute the script.
- For `remote` scripts: user signs the script hash (consent/authorization) and sends the signature to the server. The server may then execute the script.
- For `local` scripts: script executes locally, and the extension logs and reports the result back to the server.
- For user-pasted scripts (from input box): logs only; server connection not required.
## Script Permissions & Security
- **Session Password Handling**: The session password (or a derived key) is kept in memory only for the duration of the unlocked session, never persisted, and is zeroized from memory on session lock/logout. This follows best practices for cryptographic applications and browser extensions.
- **Signer Access**: Scripts can access the session's signer only after explicit user approval per execution.
- **Approval Model**: Every script execution (local or remote) requires user approval.
- **No global permissions**: Permissions are not granted globally or permanently.
## UI Framework & UX
- Any robust, modern, and fast UI framework may be used (React, Svelte, etc.).
- Dark mode is recommended.
- UI should be responsive, intuitive, and secure.
## Developer Notes
- The extension is the canonical interface for scripting and secure automation.
- CLI support and additional server features are planned for future phases.
- See also: [EVM Client Plan](evm_client_architecture_plan.md) and [README.md] for architecture overview.
## Project Goal
Build a system that allows users to write and execute Rhai scripts both:
- **Locally via a CLI**, and
- **In the browser via a browser extension**
using the same core logic (the `vault` and `evm_client` crates), powered by a Rust WebAssembly module.
---
## Requirements & Architecture
### 1. Shared Rust Libraries
- **Core Libraries:**
- `vault/`: Cryptographic vault and session management
- `evm_client/`: EVM RPC client
- **Responsibilities:**
- Implement business logic
- Expose functions to the Rhai scripting engine
- Reusable in both native CLI and WebAssembly builds
### 2. Recommended File Structure
```
rhai_sandbox_workspace/
├── Cargo.toml # Workspace manifest
├── vault/ # Shared logic + Rhai bindings
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # Public API (all core logic)
│ ├── rhai_bindings.rs# Rhai registration (shared)
│ └── utils.rs # Any helpers
├── cli/ # CLI runner
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
├── wasm/ # Wasm runner using same API
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── browser-extension/ # (optional) Extension frontend
│ ├── manifest.json
│ ├── index.html
│ └── index.js
```
### 3. Code Organization for Shared Rhai Bindings
**In `vault/src/lib.rs`:**
```rust
pub mod rhai_bindings;
pub use rhai_bindings::register_rhai_api;
pub fn fib(n: i64) -> i64 {
if n < 2 { n } else { fib(n - 1) + fib(n - 2) }
}
```
**In `vault/src/rhai_bindings.rs`:**
```rust
use rhai::{Engine, RegisterFn};
use crate::fib;
pub fn register_rhai_api(engine: &mut Engine) {
engine.register_fn("fib", fib);
}
```
**Using in CLI (`cli/src/main.rs`):**
```rust
use rhai::Engine;
use vault::register_rhai_api;
use std::env;
use std::fs;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
eprintln!("Usage: cli <script.rhai>");
return;
}
let script = fs::read_to_string(&args[1]).expect("Failed to read script");
let mut engine = Engine::new();
register_rhai_api(&mut engine);
match engine.eval::<i64>(&script) {
Ok(result) => println!("Result: {}", result),
Err(e) => eprintln!("Error: {}", e),
}
}
```
**Using in WASM (`wasm/src/lib.rs`):**
```rust
use wasm_bindgen::prelude::*;
use rhai::Engine;
use vault::register_rhai_api;
#[wasm_bindgen]
pub fn run_rhai(script: &str) -> JsValue {
let mut engine = Engine::new();
register_rhai_api(&mut engine);
match engine.eval_expression::<i64>(script) {
Ok(res) => JsValue::from_f64(res as f64),
Err(e) => JsValue::from_str(&format!("Error: {}", e)),
}
}
```
**Benefits:**
- Single source of truth for Rhai bindings (`register_rhai_api`)
- Easy to expand: add more Rust functions and register in one place
- Works seamlessly across CLI and WASM
- Encourages code reuse and maintainability
**This approach fully adheres to the principles in `architecture.md`**:
- Modular, layered design
- Code reuse across targets
- Shared business logic for both native and WASM
- Clean separation of platform-specific code
### 4. Native CLI Tool (`cli/`)
- Accepts `.rhai` script file or stdin
- Uses shared libraries to run the script via the Rhai engine
- Outputs the result to the terminal
### 5. WebAssembly Module (`wasm/`)
- Uses the same core library for Rhai logic
- Exposes a `run_rhai(script: &str) -> String` function via `wasm_bindgen`
- Usable from browser-based JS (e.g., `import { run_rhai }`)
### 4. Browser Extension (`browser_extension/`)
- UI for user to enter Rhai code after loading keyspace and selecting keypair (using SessionManager)
- Loads the WebAssembly module
- Runs user input through `run_rhai(script)`
- Displays the result or error
- **Security:**
- Only allows script input from:
- Trusted websites (via content script injection)
- Extension popup UI---
## EVM Client Integration: Pluggable Signer Pattern
---
## Cross-Platform, Multi-Network EvmClient Design
To be consistent with the rest of the project and adhere to the architecture and modularity principles, the `evm_client` crate should:
- Use async APIs and traits for all network and signing operations
- Support both native and WASM (browser) environments via conditional compilation
- Allow dynamic switching between multiple EVM networks/providers at runtime
- Avoid direct dependencies on vault/session, using the pluggable Signer trait
- Expose a clear, ergonomic API for both Rust and Rhai scripting
### 1. EvmProvider Abstraction
```rust
pub enum EvmProvider {
Http { name: String, url: String, chain_id: u64 },
// Future: WebSocket, Infura, etc.
}
```
### 2. EvmClient Struct
```rust
use std::collections::HashMap;
pub struct EvmClient<S: Signer> {
providers: HashMap<String, EvmProvider>,
current: String,
signer: S,
}
impl<S: Signer> EvmClient<S> {
pub fn new(signer: S) -> Self {
Self {
providers: HashMap::new(),
current: String::new(),
signer,
}
}
pub fn add_provider(&mut self, key: String, provider: EvmProvider) {
self.providers.insert(key, provider);
}
pub fn set_current(&mut self, key: &str) -> Result<(), EvmError> {
if self.providers.contains_key(key) {
self.current = key.to_string();
Ok(())
} else {
Err(EvmError::UnknownNetwork)
}
}
pub fn current_provider(&self) -> Option<&EvmProvider> {
self.providers.get(&self.current)
}
pub async fn send_transaction(&self, tx: Transaction) -> Result<TxHash, EvmError> {
let provider = self.current_provider().ok_or(EvmError::NoNetwork)?;
provider.send_raw_transaction(&tx, &self.signer).await
}
}
```
### 3. Provider Networking (Native + WASM)
```rust
impl EvmProvider {
pub async fn send_raw_transaction<S: Signer>(&self, tx: &Transaction, signer: &S) -> Result<TxHash, EvmError> {
let raw_tx = tx.sign(signer).await?;
let body = format!("{{\"raw\":\"{}\"}}", hex::encode(&raw_tx));
match self {
EvmProvider::Http { url, .. } => {
http_post(url, &body).await
}
}
}
}
// Cross-platform HTTP POST
pub async fn http_post(url: &str, body: &str) -> Result<TxHash, EvmError> {
#[cfg(not(target_arch = "wasm32"))]
{
let resp = reqwest::Client::new().post(url).body(body.to_owned()).send().await?;
// parse response...
Ok(parse_tx_hash(resp.text().await?))
}
#[cfg(target_arch = "wasm32")]
{
let resp = gloo_net::http::Request::post(url).body(body).send().await?;
// parse response...
Ok(parse_tx_hash(resp.text().await?))
}
}
```
### 4. Rhai Scripting Integration
- Expose `add_network`, `switch_network`, and `send_tx` functions to the Rhai engine via the shared bindings pattern.
- Example:
```rust
pub fn register_rhai_api(engine: &mut Engine) {
engine.register_type::<EvmClient<MySigner>>();
engine.register_fn("add_network", EvmClient::add_provider);
engine.register_fn("switch_network", EvmClient::set_current);
engine.register_fn("send_tx", EvmClient::send_transaction);
}
```
### 5. Consistency and Compliance
- **Async, modular, and testable:** All APIs are async and trait-based, just like kvstore/vault.
- **No direct dependencies:** EvmClient is generic over signing backend, like other crates.
- **Cross-platform:** Uses conditional compilation for networking, ensuring WASM and native support.
- **Clear separation:** Network and signing logic are independent, allowing easy extension and testing.
This design fits seamlessly with your projects architecture and modularity goals.
### 5. Web App Integration
- Enable trusted web apps to send Rhai scripts to the extension, using one or both of:
#### Option A: Direct (Client-side)
- Web apps use `window.postMessage()` or DOM events
- Extension listens via content script
- Validates origin before running the script
#### Option B: Server-based (WebSocket)
- Both extension and web app connect to a backend WebSocket server
- Web app sends script to server
- Server routes it to the right connected extension client
- Extension executes the script and returns the result
### 6. Security Considerations
- All script execution is sandboxed via Rhai + Wasm
- Only allow input from:
- Extension popup
- Approved websites or servers
- Validate origins and inputs strictly
- Do not expose internal APIs beyond `run_rhai(script)`
---
## High-Level Component Diagram
```
+-------------------+ +-------------------+
| CLI Tool | | Browser Extension|
| (cli/) | | (browser_ext/) |
+---------+---------+ +---------+---------+
| |
| +----------------+
| |
+---------v---------+
| WASM Module | <--- Shared Rust Core (vault, evm_client)
| (wasm/) |
+---------+---------+
|
+---------v---------+
| Rhai Engine |
+-------------------+
```
---
## Implementation Phases
### Phase 1: Core Library Integration
- Ensure all business logic (vault & evm_client) is accessible from both native and WASM targets.
- Expose required functions to Rhai engine.
### Phase 2: CLI Tool
- Implement CLI that loads and runs Rhai scripts using the shared core.
- Add support for stdin and file input.
### Phase 3: WASM Module
- Build WASM module exposing `run_rhai`.
- Integrate with browser JS via `wasm_bindgen`.
### Phase 4: Browser Extension
- UI for script input and result display.
- Integrate WASM module and SessionManager.
- Secure script input (popup and trusted sites only).
### Phase 5: Web App Integration
- Implement postMessage and/or WebSocket protocol for trusted web apps to send scripts.
- Validate origins and handle results.
---
## Open Questions / TODOs
- What subset of the vault/evm_client API should be exposed to Rhai?
- What are the best practices for sandboxing Rhai in WASM?
- How will user authentication/session be handled between extension and web app?
- How will error reporting and logging work across boundaries?
---
## References
- [Rhai scripting engine](https://rhai.rs/)
- [wasm-bindgen](https://rustwasm.github.io/wasm-bindgen/)
- [WebExtension APIs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions)
- [Rust + WASM Book](https://rustwasm.github.io/book/)
---
*Last updated: 2025-05-15*