# CLI and Rhai Scripting Implementation Plan This document outlines the technical implementation plan for adding CLI and Rhai scripting capabilities to the WebAssembly Cryptography Module. ## 1. Project Structure Updates ### 1.1 Directory Structure ``` webassembly/ ├── Cargo.toml (updated) ├── src/ │ ├── lib.rs (existing WebAssembly exports) │ ├── main.rs (new CLI entry point) │ ├── core/ (existing cryptographic core) │ ├── api/ (existing API layer) │ ├── cli/ (new CLI module) │ │ ├── commands.rs │ │ ├── config.rs │ │ ├── error.rs │ │ └── mod.rs │ ├── scripting/ (new Rhai scripting module) │ │ ├── engine.rs │ │ ├── api.rs │ │ ├── sandbox.rs │ │ └── mod.rs │ └── messaging/ (new messaging module) │ ├── mycelium.rs (or nats.rs) │ ├── error.rs │ └── mod.rs ├── scripts/ (example Rhai scripts) └── www/ (existing WebAssembly frontend) ``` ### 1.2 Cargo.toml Updates ```toml [package] name = "webassembly" version = "0.1.0" edition = "2021" authors = ["Your Name "] description = "Cryptographic module with CLI, Rhai scripting, and WebAssembly support" [lib] crate-type = ["cdylib", "rlib"] [[bin]] name = "crypto-cli" path = "src/main.rs" [dependencies] # Existing dependencies wasm-bindgen = "0.2" js-sys = "0.3" web-sys = { version = "0.3", features = ["console"] } console_error_panic_hook = "0.1" k256 = { version = "0.13", features = ["ecdsa", "serde"] } chacha20poly1305 = "0.10" rand = "0.8" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" once_cell = "1.17" sha2 = "0.10" ethers = { version = "2.0", features = ["legacy"] } # New dependencies for CLI clap = { version = "4.3", features = ["derive"] } colored = "2.0" dirs = "5.0" rustyline = "11.0" log = "0.4" env_logger = "0.10" rpassword = "7.2" # Rhai scripting rhai = { version = "1.14", features = ["sync", "serde"] } # Messaging system (choose one) # Option 1: Mycelium mycelium = "0.1" # Option 2: NATS async-nats = "0.29" tokio = { version = "1.28", features = ["full"] } [features] default = ["cli", "wasm"] cli = [] wasm = [] mycelium = [] nats = [] ``` ## 2. CLI Implementation ### 2.1 Main Entry Point (src/main.rs) ```rust use clap::Parser; use colored::Colorize; use log::info; mod core; mod api; mod cli; mod scripting; mod messaging; use cli::{Cli, Commands}; #[tokio::main] async fn main() -> Result<(), Box> { // Initialize logger env_logger::init(); // Parse command line arguments let cli = Cli::parse(); // Set up verbose logging if requested if cli.verbose { std::env::set_var("RUST_LOG", "debug"); } // Execute the appropriate command match &cli.command { Commands::Key { command } => { cli::commands::execute_key_command(command)?; }, Commands::Crypto { command } => { cli::commands::execute_crypto_command(command)?; }, Commands::Ethereum { command } => { cli::commands::execute_ethereum_command(command)?; }, Commands::Script { path, inline } => { let mut engine = scripting::ScriptEngine::new(); if let Some(script_path) = path { info!("Executing script from file: {}", script_path); engine.eval_file(script_path)?; } else if let Some(script) = inline { info!("Executing inline script"); engine.eval(script)?; } else { println!("Error: No script provided"); return Ok(()); } }, Commands::Listen { server, subject } => { // Implementation depends on chosen messaging system #[cfg(feature = "mycelium")] { let listener = messaging::mycelium::MyceliumNetwork::new().await?; listener.listen().await?; } #[cfg(feature = "nats")] { let listener = messaging::nats::NatsListener::new(server, subject).await?; listener.listen().await?; } }, Commands::Shell => { cli::shell::run_interactive_shell()?; }, } Ok(()) } ``` ### 2.2 CLI Module (src/cli/mod.rs) ```rust pub mod commands; pub mod config; pub mod error; pub mod shell; use clap::{Parser, Subcommand}; #[derive(Parser)] #[command(name = "crypto-cli")] #[command(about = "Cryptographic operations CLI with Rhai scripting support", long_about = None)] pub struct Cli { #[command(subcommand)] pub command: Commands, #[arg(short, long, help = "Enable verbose output")] pub verbose: bool, #[arg(short, long, help = "Config file path")] pub config: Option, } #[derive(Subcommand)] pub enum Commands { /// Key management commands Key { #[command(subcommand)] command: KeyCommands, }, /// Encryption/decryption commands Crypto { #[command(subcommand)] command: CryptoCommands, }, /// Ethereum wallet commands Ethereum { #[command(subcommand)] command: EthereumCommands, }, /// Execute Rhai script Script { #[arg(help = "Path to Rhai script file")] path: Option, #[arg(short, long, help = "Execute script from string")] inline: Option, }, /// Start listener for scripts Listen { #[arg(short, long, help = "Server URL", default_value = "localhost")] server: String, #[arg(short, long, help = "Subject to subscribe to", default_value = "crypto.scripts")] subject: String, }, /// Interactive shell Shell, } // Define subcommands for each category #[derive(Subcommand)] pub enum KeyCommands { // Key management commands CreateSpace { name: String, password: Option }, ListSpaces, CreateKeypair { name: String }, ListKeypairs, Export { name: String, output: Option }, Import { name: String, input: Option }, } #[derive(Subcommand)] pub enum CryptoCommands { // Cryptographic operation commands Sign { message: Option, input: Option, keypair: String, output: Option }, Verify { message: Option, input: Option, signature: String, keypair: Option, pubkey: Option }, Encrypt { data: Option, input: Option, recipient: String, output: Option }, Decrypt { data: Option, input: Option, keypair: String, output: Option }, } #[derive(Subcommand)] pub enum EthereumCommands { // Ethereum wallet commands Create { keypair: String }, Address { keypair: String }, Balance { address: Option, network: String }, } ``` ### 2.3 CLI Error Handling (src/cli/error.rs) ```rust use std::fmt; use std::io; #[derive(Debug)] pub enum CliError { IoError(String), CryptoError(crate::core::error::CryptoError), ScriptError(String), MessagingError(String), ConfigError(String), NotImplemented, } impl fmt::Display for CliError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CliError::IoError(msg) => write!(f, "I/O Error: {}", msg), CliError::CryptoError(err) => write!(f, "Crypto Error: {}", err), CliError::ScriptError(msg) => write!(f, "Script Error: {}", msg), CliError::MessagingError(msg) => write!(f, "Messaging Error: {}", msg), CliError::ConfigError(msg) => write!(f, "Configuration Error: {}", msg), CliError::NotImplemented => write!(f, "Command not implemented yet"), } } } impl From for CliError { fn from(err: io::Error) -> Self { CliError::IoError(err.to_string()) } } impl From for CliError { fn from(err: crate::core::error::CryptoError) -> Self { CliError::CryptoError(err) } } impl From for CliError { fn from(err: rhai::EvalAltResult) -> Self { CliError::ScriptError(err.to_string()) } } ``` ## 3. Rhai Scripting Implementation ### 3.1 Scripting Engine (src/scripting/engine.rs) ```rust use rhai::{Engine, AST, Scope, EvalAltResult}; use std::path::Path; use std::fs; use crate::scripting::api::register_crypto_api; use crate::cli::error::CliError; pub struct ScriptEngine { engine: Engine, scope: Scope<'static>, } impl ScriptEngine { pub fn new() -> Self { let mut engine = Engine::new(); // Set up sandboxing engine.set_max_operations(100_000); engine.set_max_modules(10); engine.set_max_string_size(10_000); engine.set_max_array_size(1_000); engine.set_max_map_size(1_000); // Disable potentially dangerous operations engine.disable_symbol("eval"); engine.disable_symbol("source"); // Register crypto API let mut scope = Scope::new(); register_crypto_api(&mut engine, &mut scope); ScriptEngine { engine, scope } } pub fn eval_file>(&mut self, path: P) -> Result<(), CliError> { let script = fs::read_to_string(path) .map_err(|e| CliError::IoError(format!("Failed to read script file: {}", e)))?; self.eval(&script) } pub fn eval(&mut self, script: &str) -> Result<(), CliError> { self.engine.eval_with_scope::<()>(&mut self.scope, script) .map_err(|e| CliError::ScriptError(e.to_string())) } } ``` ### 3.2 Scripting API (src/scripting/api.rs) ```rust use rhai::{Engine, Scope, Dynamic, FnPtr}; use crate::api::{keypair, symmetric, ethereum}; pub fn register_crypto_api(engine: &mut Engine, scope: &mut Scope) { // Register key space functions engine.register_fn("create_key_space", |name: &str| -> bool { keypair::create_space(name).is_ok() }); engine.register_fn("encrypt_key_space", |password: &str| -> Dynamic { match keypair::encrypt_space(password) { Ok(encrypted) => Dynamic::from(encrypted), Err(_) => Dynamic::UNIT, } }); engine.register_fn("decrypt_key_space", |encrypted: &str, password: &str| -> bool { keypair::decrypt_space(encrypted, password).is_ok() }); // Register keypair functions engine.register_fn("create_keypair", |name: &str| -> bool { keypair::create_keypair(name).is_ok() }); engine.register_fn("select_keypair", |name: &str| -> bool { keypair::select_keypair(name).is_ok() }); engine.register_fn("list_keypairs", || -> Dynamic { match keypair::list_keypairs() { Ok(keypairs) => { let array: Vec = keypairs.into_iter() .map(Dynamic::from) .collect(); Dynamic::from(array) }, Err(_) => Dynamic::from(Vec::::new()), } }); // Register signing/verification functions engine.register_fn("sign", |message: &str| -> Dynamic { let message_bytes = message.as_bytes(); match keypair::sign(message_bytes) { Ok(signature) => { // Convert to hex string for easier handling in scripts let hex = signature.iter() .map(|b| format!("{:02x}", b)) .collect::(); Dynamic::from(hex) }, Err(_) => Dynamic::UNIT, } }); engine.register_fn("verify", |message: &str, signature_hex: &str| -> bool { let message_bytes = message.as_bytes(); // Convert hex string back to bytes let signature_bytes = hex_to_bytes(signature_hex); if signature_bytes.is_empty() { return false; } match keypair::verify(message_bytes, &signature_bytes) { Ok(result) => result, Err(_) => false, } }); // Register symmetric encryption functions engine.register_fn("generate_key", || -> Dynamic { let key = symmetric::generate_key(); let hex = key.iter() .map(|b| format!("{:02x}", b)) .collect::(); Dynamic::from(hex) }); engine.register_fn("encrypt", |key_hex: &str, message: &str| -> Dynamic { let key = hex_to_bytes(key_hex); if key.is_empty() { return Dynamic::UNIT; } let message_bytes = message.as_bytes(); match symmetric::encrypt(&key, message_bytes) { Ok(ciphertext) => { let hex = ciphertext.iter() .map(|b| format!("{:02x}", b)) .collect::(); Dynamic::from(hex) }, Err(_) => Dynamic::UNIT, } }); engine.register_fn("decrypt", |key_hex: &str, ciphertext_hex: &str| -> Dynamic { let key = hex_to_bytes(key_hex); let ciphertext = hex_to_bytes(ciphertext_hex); if key.is_empty() || ciphertext.is_empty() { return Dynamic::UNIT; } match symmetric::decrypt(&key, &ciphertext) { Ok(plaintext) => { match String::from_utf8(plaintext) { Ok(text) => Dynamic::from(text), Err(_) => Dynamic::UNIT, } }, Err(_) => Dynamic::UNIT, } }); // Register Ethereum functions engine.register_fn("create_ethereum_wallet", || -> bool { ethereum::create_ethereum_wallet().is_ok() }); engine.register_fn("get_ethereum_address", || -> Dynamic { match ethereum::get_ethereum_address() { Ok(address) => Dynamic::from(address), Err(_) => Dynamic::UNIT, } }); } // Helper function to convert hex string to bytes fn hex_to_bytes(hex: &str) -> Vec { let mut bytes = Vec::new(); let mut chars = hex.chars(); while let (Some(a), Some(b)) = (chars.next(), chars.next()) { if let (Some(high), Some(low)) = (a.to_digit(16), b.to_digit(16)) { bytes.push(((high << 4) | low) as u8); } else { return Vec::new(); } } bytes } ``` ## 4. Messaging System Implementation ### 4.1 Mycelium Implementation (src/messaging/mycelium.rs) ```rust use mycelium::{Node, Identity, Message}; use std::time::Duration; use crate::scripting::ScriptEngine; use crate::cli::error::CliError; pub struct MyceliumNetwork { node: Node, identity: Identity, } impl MyceliumNetwork { pub async fn new() -> Result { let identity = Identity::random(); let node = Node::new(identity.clone()) .map_err(|e| CliError::MessagingError(format!("Failed to create Mycelium node: {}", e)))?; Ok(MyceliumNetwork { node, identity, }) } pub async fn listen(&self) -> Result<(), CliError> { println!("Listening for scripts on Mycelium network"); let mut receiver = self.node.subscribe("crypto.scripts") .map_err(|e| CliError::MessagingError(format!("Failed to subscribe: {}", e)))?; while let Some(msg) = receiver.recv().await { let script = String::from_utf8_lossy(&msg.payload); println!("Received script: {}", script); let mut engine = ScriptEngine::new(); match engine.eval(&script) { Ok(_) => { println!("Script executed successfully"); self.node.publish( "crypto.results", msg.sender.clone(), "Script executed successfully".as_bytes().to_vec(), ).await.map_err(|e| CliError::MessagingError(format!("Failed to send result: {}", e)))?; }, Err(e) => { println!("Script execution failed: {}", e); self.node.publish( "crypto.results", msg.sender.clone(), format!("Script execution failed: {}", e).as_bytes().to_vec(), ).await.map_err(|e| CliError::MessagingError(format!("Failed to send result: {}", e)))?; }, } } Ok(()) } } ``` ### 4.2 NATS Implementation (src/messaging/nats.rs) ```rust use async_nats::Client; use tokio::sync::mpsc; use std::time::Duration; use crate::scripting::ScriptEngine; use crate::cli::error::CliError; pub struct NatsListener { client: Client, subject: String, } impl NatsListener { pub async fn new(server: &str, subject: &str) -> Result { let client = async_nats::connect(server) .await .map_err(|e| CliError::MessagingError(format!("Failed to connect to NATS: {}", e)))?; Ok(NatsListener { client, subject: subject.to_string(), }) } pub async fn listen(&self) -> Result<(), CliError> { println!("Listening for scripts on subject: {}", self.subject); let mut subscriber = self.client.subscribe(self.subject.clone()) .await .map_err(|e| CliError::MessagingError(format!("Failed to subscribe: {}", e)))?; while let Some(msg) = subscriber.next().await { let script = String::from_utf8_lossy(&msg.payload); println!("Received script: {}", script); let mut engine = ScriptEngine::new(); let result = match engine.eval(&script) { Ok(_) => { println!("Script executed successfully"); "Script executed successfully" }, Err(e) => { println!("Script execution failed: {}", e); &format!("Script execution failed: {}", e) }, }; if let Some(reply) = msg.reply { self.client.publish(reply, result.into()) .await .map_err(|e| CliError::MessagingError(format!("Failed to send result: {}", e)))?; } } Ok(()) } } ``` ## 5. Example Rhai Scripts ### 5.1 Key Management Script (scripts/key_management.rhai) ```rhai // Create a key space let space_name = "test_space"; let password = "secure_password"; print("Creating key space: " + space_name); if create_key_space(space_name) { print("Key space created successfully"); // Encrypt the key space let encrypted = encrypt_key_space(password); print("Encrypted key space: " + encrypted); // Create a keypair if create_keypair("test_keypair") { print("Keypair created successfully"); // List keypairs let keypairs = list_keypairs(); print("Available keypairs: " + keypairs); // Select the keypair if select_keypair("test_keypair") { print("Keypair selected successfully"); } } } ``` ### 5.2 Signing Script (scripts/signing.rhai) ```rhai // Select a keypair if select_keypair("test_keypair") { print("Keypair selected successfully"); // Sign a message let message = "Hello, this is a test message"; let signature = sign(message); print("Message: " + message); print("Signature: " + signature); // Verify the signature let is_valid = verify(message, signature); print("Signature valid: " + is_valid); } ``` ### 5.3 Encryption Script (scripts/encryption.rhai) ```rhai // Generate a symmetric key let key = generate_key(); print("Generated key: " + key); // Encrypt a message let message = "This is a secret message"; let encrypted = encrypt(key, message); print("Encrypted: " + encrypted); // Decrypt the message let decrypted = decrypt(key, encrypted); print("Decrypted: " + decrypted); // Verify the decryption worked if decrypted == message { print("Encryption/decryption successful!"); } else { print("Encryption/decryption failed!"); } ``` ## 6. Implementation Steps 1. **Update Cargo.toml** - Add new dependencies - Configure features - Add binary target 2. **Create CLI Structure** - Implement CLI module - Define commands and subcommands - Set up error handling 3. **Implement Rhai Scripting** - Create scripting engine - Register API functions - Implement sandboxing 4. **Implement Messaging System** - Choose between Mycelium and NATS - Implement listener - Set up script execution 5. **Create Example Scripts** - Key management scripts - Signing scripts - Encryption scripts 6. **Testing** - Unit tests for CLI commands - Integration tests for script execution - End-to-end tests for messaging 7. **Documentation** - Update README.md - Add CLI help text - Document script API ## 7. Testing Strategy ### 7.1 Unit Tests ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_cli_key_commands() { // Test key management commands } #[test] fn test_cli_crypto_commands() { // Test cryptographic operation commands } #[test] fn test_script_engine() { // Test script execution } } ``` ### 7.2 Integration Tests ```rust #[cfg(test)] mod integration_tests { use super::*; #[test] fn test_script_execution() { // Test executing a script file } #[test] fn test_cli_workflow() { // Test a complete CLI workflow } } ``` ## 8. Conclusion This implementation plan provides a detailed roadmap for adding CLI and Rhai scripting capabilities to the WebAssembly Cryptography Module. By following this plan, the module will be transformed into a versatile cryptographic toolkit that can operate across multiple contexts while maintaining its existing WebAssembly functionality. The choice between Mycelium and NATS for the messaging system will depend on specific requirements for decentralization, security, and deployment complexity. Both options are included in this plan to provide flexibility.