//! Rhai bindings for SAL crypto functionality use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; use ethers::types::{Address, U256}; use once_cell::sync::Lazy; use rhai::{Dynamic, Engine, EvalAltResult}; use std::collections::HashMap; use std::fs; use std::path::PathBuf; use std::str::FromStr; use std::sync::Mutex; use tokio::runtime::Runtime; use crate::vault::ethereum; use crate::vault::keyspace::session_manager as keypair; use crate::vault::symmetric::implementation as symmetric_impl; // Global Tokio runtime for blocking async operations static RUNTIME: Lazy> = Lazy::new(|| Mutex::new(Runtime::new().expect("Failed to create Tokio runtime"))); // Global provider registry static PROVIDERS: Lazy< Mutex>>, > = Lazy::new(|| Mutex::new(HashMap::new())); // Key space management functions fn load_key_space(name: &str, password: &str) -> bool { // Get the key spaces directory from config let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); // Check if directory exists if !key_spaces_dir.exists() { log::error!("Key spaces directory does not exist"); return false; } // Get the key space file path let space_path = key_spaces_dir.join(format!("{}.json", name)); // Check if file exists if !space_path.exists() { log::error!("Key space file not found: {}", space_path.display()); return false; } // Read the file let serialized = match fs::read_to_string(&space_path) { Ok(data) => data, Err(e) => { log::error!("Error reading key space file: {}", e); return false; } }; // Deserialize the encrypted space let encrypted_space = match symmetric_impl::deserialize_encrypted_space(&serialized) { Ok(space) => space, Err(e) => { log::error!("Error deserializing key space: {}", e); return false; } }; // Decrypt the space let space = match symmetric_impl::decrypt_key_space(&encrypted_space, password) { Ok(space) => space, Err(e) => { log::error!("Error decrypting key space: {}", e); return false; } }; // Set as current space match keypair::set_current_space(space) { Ok(_) => true, Err(e) => { log::error!("Error setting current space: {}", e); false } } } fn create_key_space(name: &str, password: &str) -> bool { match keypair::create_space(name) { Ok(_) => { // Get the current space match keypair::get_current_space() { Ok(space) => { // Encrypt the key space let encrypted_space = match symmetric_impl::encrypt_key_space(&space, password) { Ok(encrypted) => encrypted, Err(e) => { log::error!("Error encrypting key space: {}", e); return false; } }; // Serialize the encrypted space let serialized = match symmetric_impl::serialize_encrypted_space(&encrypted_space) { Ok(json) => json, Err(e) => { log::error!("Error serializing encrypted space: {}", e); return false; } }; // Get the key spaces directory let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); // Create directory if it doesn't exist if !key_spaces_dir.exists() { match fs::create_dir_all(&key_spaces_dir) { Ok(_) => {} Err(e) => { log::error!("Error creating key spaces directory: {}", e); return false; } } } // Write to file let space_path = key_spaces_dir.join(format!("{}.json", name)); match fs::write(&space_path, serialized) { Ok(_) => { log::info!("Key space created and saved to {}", space_path.display()); true } Err(e) => { log::error!("Error writing key space file: {}", e); false } } } Err(e) => { log::error!("Error getting current space: {}", e); false } } } Err(e) => { log::error!("Error creating key space: {}", e); false } } } // Auto-save function for internal use fn auto_save_key_space(password: &str) -> bool { match keypair::get_current_space() { Ok(space) => { // Encrypt the key space let encrypted_space = match symmetric_impl::encrypt_key_space(&space, password) { Ok(encrypted) => encrypted, Err(e) => { log::error!("Error encrypting key space: {}", e); return false; } }; // Serialize the encrypted space let serialized = match symmetric_impl::serialize_encrypted_space(&encrypted_space) { Ok(json) => json, Err(e) => { log::error!("Error serializing encrypted space: {}", e); return false; } }; // Get the key spaces directory let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces"); // Create directory if it doesn't exist if !key_spaces_dir.exists() { match fs::create_dir_all(&key_spaces_dir) { Ok(_) => {} Err(e) => { log::error!("Error creating key spaces directory: {}", e); return false; } } } // Write to file let space_path = key_spaces_dir.join(format!("{}.json", space.name)); match fs::write(&space_path, serialized) { Ok(_) => { log::info!("Key space saved to {}", space_path.display()); true } Err(e) => { log::error!("Error writing key space file: {}", e); false } } } Err(e) => { log::error!("Error getting current space: {}", e); false } } } fn encrypt_key_space(password: &str) -> String { match keypair::get_current_space() { Ok(space) => match symmetric_impl::encrypt_key_space(&space, password) { Ok(encrypted_space) => match serde_json::to_string(&encrypted_space) { Ok(json) => json, Err(e) => { log::error!("Error serializing encrypted space: {}", e); String::new() } }, Err(e) => { log::error!("Error encrypting key space: {}", e); String::new() } }, Err(e) => { log::error!("Error getting current space: {}", e); String::new() } } } fn decrypt_key_space(encrypted: &str, password: &str) -> bool { match serde_json::from_str(encrypted) { Ok(encrypted_space) => { match symmetric_impl::decrypt_key_space(&encrypted_space, password) { Ok(space) => match keypair::set_current_space(space) { Ok(_) => true, Err(e) => { log::error!("Error setting current space: {}", e); false } }, Err(e) => { log::error!("Error decrypting key space: {}", e); false } } } Err(e) => { log::error!("Error parsing encrypted space: {}", e); false } } } // Keypair management functions fn create_keypair(name: &str, password: &str) -> bool { match keypair::create_keypair(name) { Ok(_) => { // Auto-save the key space after creating a keypair auto_save_key_space(password) } Err(e) => { log::error!("Error creating keypair: {}", e); false } } } fn select_keypair(name: &str) -> bool { match keypair::select_keypair(name) { Ok(_) => true, Err(e) => { log::error!("Error selecting keypair: {}", e); false } } } fn list_keypairs() -> Vec { match keypair::list_keypairs() { Ok(keypairs) => keypairs, Err(e) => { log::error!("Error listing keypairs: {}", e); Vec::new() } } } // Cryptographic operations fn sign(message: &str) -> String { let message_bytes = message.as_bytes(); match keypair::keypair_sign(message_bytes) { Ok(signature) => BASE64.encode(signature), Err(e) => { log::error!("Error signing message: {}", e); String::new() } } } fn verify(message: &str, signature: &str) -> bool { let message_bytes = message.as_bytes(); match BASE64.decode(signature) { Ok(signature_bytes) => match keypair::keypair_verify(message_bytes, &signature_bytes) { Ok(is_valid) => is_valid, Err(e) => { log::error!("Error verifying signature: {}", e); false } }, Err(e) => { log::error!("Error decoding signature: {}", e); false } } } // Symmetric encryption fn generate_key() -> String { let key = symmetric_impl::generate_symmetric_key(); BASE64.encode(key) } fn encrypt(key: &str, message: &str) -> String { match BASE64.decode(key) { Ok(key_bytes) => { let message_bytes = message.as_bytes(); match symmetric_impl::encrypt_symmetric(&key_bytes, message_bytes) { Ok(ciphertext) => BASE64.encode(ciphertext), Err(e) => { log::error!("Error encrypting message: {}", e); String::new() } } } Err(e) => { log::error!("Error decoding key: {}", e); String::new() } } } fn decrypt(key: &str, ciphertext: &str) -> String { match BASE64.decode(key) { Ok(key_bytes) => match BASE64.decode(ciphertext) { Ok(ciphertext_bytes) => { match symmetric_impl::decrypt_symmetric(&key_bytes, &ciphertext_bytes) { Ok(plaintext) => match String::from_utf8(plaintext) { Ok(text) => text, Err(e) => { log::error!("Error converting plaintext to string: {}", e); String::new() } }, Err(e) => { log::error!("Error decrypting ciphertext: {}", e); String::new() } } } Err(e) => { log::error!("Error decoding ciphertext: {}", e); String::new() } }, Err(e) => { log::error!("Error decoding key: {}", e); String::new() } } } // Ethereum operations // Gnosis Chain operations fn create_ethereum_wallet() -> bool { match ethereum::create_ethereum_wallet_for_network(ethereum::networks::gnosis()) { Ok(_) => true, Err(e) => { log::error!("Error creating Ethereum wallet: {}", e); false } } } fn get_ethereum_address() -> String { match ethereum::get_current_ethereum_wallet_for_network("Gnosis") { Ok(wallet) => wallet.address_string(), Err(e) => { log::error!("Error getting Ethereum address: {}", e); String::new() } } } // Peaq network operations fn create_peaq_wallet() -> bool { match ethereum::create_peaq_wallet() { Ok(_) => true, Err(e) => { log::error!("Error creating Peaq wallet: {}", e); false } } } fn get_peaq_address() -> String { match ethereum::get_current_peaq_wallet() { Ok(wallet) => wallet.address_string(), Err(e) => { log::error!("Error getting Peaq address: {}", e); String::new() } } } // Agung testnet operations fn create_agung_wallet() -> bool { match ethereum::create_agung_wallet() { Ok(_) => true, Err(e) => { log::error!("Error creating Agung wallet: {}", e); false } } } fn get_agung_address() -> String { match ethereum::get_current_agung_wallet() { Ok(wallet) => wallet.address_string(), Err(e) => { log::error!("Error getting Agung address: {}", e); String::new() } } } // Generic network operations fn create_wallet_for_network(network_name: &str) -> bool { let network = match ethereum::networks::get_network_by_name(network_name) { Some(network) => network, None => { log::error!("Unknown network: {}", network_name); return false; } }; match ethereum::create_ethereum_wallet_for_network(network) { Ok(_) => true, Err(e) => { log::error!("Error creating wallet for network {}: {}", network_name, e); false } } } // Get wallet address for a specific network fn get_wallet_address_for_network(network_name: &str) -> String { let network_name_proper = match ethereum::networks::get_proper_network_name(network_name) { Some(name) => name, None => { log::error!("Unknown network: {}", network_name); return String::new(); } }; match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) { Ok(wallet) => wallet.address_string(), Err(e) => { log::error!( "Error getting wallet address for network {}: {}", network_name, e ); String::new() } } } // Clear wallets for a specific network fn clear_wallets_for_network(network_name: &str) -> bool { let network_name_proper = match ethereum::networks::get_proper_network_name(network_name) { Some(name) => name, None => { log::error!("Unknown network: {}", network_name); return false; } }; ethereum::clear_ethereum_wallets_for_network(network_name_proper); true } // List supported networks fn list_supported_networks() -> rhai::Array { let mut arr = rhai::Array::new(); for name in ethereum::networks::list_network_names() { arr.push(Dynamic::from(name.to_lowercase())); } arr } // Get network token symbol fn get_network_token_symbol(network_name: &str) -> String { match ethereum::networks::get_network_by_name(network_name) { Some(network) => network.token_symbol, None => { log::error!("Unknown network: {}", network_name); String::new() } } } // Get network explorer URL fn get_network_explorer_url(network_name: &str) -> String { match ethereum::networks::get_network_by_name(network_name) { Some(network) => network.explorer_url, None => { log::error!("Unknown network: {}", network_name); String::new() } } } // Create a wallet from a private key for a specific network fn create_wallet_from_private_key_for_network(private_key: &str, network_name: &str) -> bool { let network = match ethereum::networks::get_network_by_name(network_name) { Some(network) => network, None => { log::error!("Unknown network: {}", network_name); return false; } }; match ethereum::create_ethereum_wallet_from_private_key_for_network(private_key, network) { Ok(_) => true, Err(e) => { log::error!( "Error creating wallet from private key for network {}: {}", network_name, e ); false } } } // Create a provider for the Agung network fn create_agung_provider() -> String { match ethereum::create_agung_provider() { Ok(provider) => { // Generate a unique ID for the provider let id = format!("provider_{}", uuid::Uuid::new_v4()); // Store the provider in the registry if let Ok(mut providers) = PROVIDERS.lock() { providers.insert(id.clone(), provider); return id; } log::error!("Failed to acquire provider registry lock"); String::new() } Err(e) => { log::error!("Error creating Agung provider: {}", e); String::new() } } } // Get the balance of an address on a specific network fn get_balance(network_name: &str, address: &str) -> String { // Get the runtime let rt = match RUNTIME.lock() { Ok(rt) => rt, Err(e) => { log::error!("Failed to acquire runtime lock: {}", e); return String::new(); } }; // Parse the address let addr = match Address::from_str(address) { Ok(addr) => addr, Err(e) => { log::error!("Invalid address format: {}", e); return String::new(); } }; // Get the proper network name let network_name_proper = match ethereum::networks::get_proper_network_name(network_name) { Some(name) => name, None => { log::error!("Unknown network: {}", network_name); return String::new(); } }; // Get the network config let network = match ethereum::networks::get_network_by_name(network_name_proper) { Some(n) => n, None => { log::error!("Failed to get network config for: {}", network_name_proper); return String::new(); } }; // Create a provider let provider = match ethereum::create_provider(&network) { Ok(p) => p, Err(e) => { log::error!("Failed to create provider: {}", e); return String::new(); } }; // Execute the balance query in a blocking manner match rt.block_on(async { ethereum::get_balance(&provider, addr).await }) { Ok(balance) => balance.to_string(), Err(e) => { log::error!("Failed to get balance: {}", e); String::new() } } } // Send ETH from one address to another using the blocking approach fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String { // Get the runtime let rt = match RUNTIME.lock() { Ok(rt) => rt, Err(e) => { log::error!("Failed to acquire runtime lock: {}", e); return String::new(); } }; // Parse the address let to_addr = match Address::from_str(to_address) { Ok(addr) => addr, Err(e) => { log::error!("Invalid address format: {}", e); return String::new(); } }; // Parse the amount (using string to handle large numbers) let amount = match U256::from_dec_str(amount_str) { Ok(amt) => amt, Err(e) => { log::error!("Invalid amount format: {}", e); return String::new(); } }; // Get the proper network name let network_name_proper = match ethereum::networks::get_proper_network_name(wallet_network) { Some(name) => name, None => { log::error!("Unknown network: {}", wallet_network); return String::new(); } }; // Get the wallet let wallet = match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) { Ok(w) => w, Err(e) => { log::error!("Failed to get wallet: {}", e); return String::new(); } }; // Create a provider let provider = match ethereum::create_provider(&wallet.network) { Ok(p) => p, Err(e) => { log::error!("Failed to create provider: {}", e); return String::new(); } }; // Execute the transaction in a blocking manner match rt.block_on(async { ethereum::send_eth(&wallet, &provider, to_addr, amount).await }) { Ok(tx_hash) => format!("{:?}", tx_hash), Err(e) => { log::error!("Transaction failed: {}", e); String::new() } } } // Smart contract operations // Load a contract ABI from a JSON string and create a contract instance fn load_contract_abi(network_name: &str, address: &str, abi_json: &str) -> String { // Get the network let network = match ethereum::networks::get_network_by_name(network_name) { Some(network) => network, None => { log::error!("Unknown network: {}", network_name); return String::new(); } }; // Parse the ABI let abi = match ethereum::load_abi_from_json(abi_json) { Ok(abi) => abi, Err(e) => { log::error!("Error parsing ABI: {}", e); return String::new(); } }; // Create the contract match ethereum::Contract::from_address_string(address, abi, network) { Ok(contract) => { // Serialize the contract to JSON for storage match serde_json::to_string(&contract) { Ok(json) => json, Err(e) => { log::error!("Error serializing contract: {}", e); String::new() } } } Err(e) => { log::error!("Error creating contract: {}", e); String::new() } } } // Load a contract ABI from a file fn load_contract_abi_from_file(network_name: &str, address: &str, file_path: &str) -> String { // Read the ABI file match fs::read_to_string(file_path) { Ok(abi_json) => load_contract_abi(network_name, address, &abi_json), Err(e) => { log::error!("Error reading ABI file: {}", e); String::new() } } } // Use the utility functions from the ethereum module // Call a read-only function on a contract (no arguments version) fn call_contract_read_no_args(contract_json: &str, function_name: &str) -> Dynamic { call_contract_read(contract_json, function_name, rhai::Array::new()) } // Call a read-only function on a contract with arguments fn call_contract_read(contract_json: &str, function_name: &str, args: rhai::Array) -> Dynamic { // Deserialize the contract let contract: ethereum::Contract = match serde_json::from_str(contract_json) { Ok(contract) => contract, Err(e) => { log::error!("Error deserializing contract: {}", e); return Dynamic::UNIT; } }; // Prepare the arguments let tokens = match ethereum::prepare_function_arguments(&contract.abi, function_name, &args) { Ok(tokens) => tokens, Err(e) => { log::error!("Error preparing arguments: {}", e); return Dynamic::UNIT; } }; // Get the runtime let rt = match RUNTIME.lock() { Ok(rt) => rt, Err(e) => { log::error!("Failed to acquire runtime lock: {}", e); return Dynamic::UNIT; } }; // Create a provider let provider = match ethereum::create_provider(&contract.network) { Ok(p) => p, Err(e) => { log::error!("Failed to create provider: {}", e); return Dynamic::UNIT; } }; // Execute the call in a blocking manner match rt.block_on(async { ethereum::call_read_function(&contract, &provider, function_name, tokens).await }) { Ok(result) => ethereum::convert_token_to_rhai(&result), Err(e) => { log::error!("Failed to call contract function: {}", e); Dynamic::UNIT } } } // Call a state-changing function on a contract (no arguments version) fn call_contract_write_no_args(contract_json: &str, function_name: &str) -> String { call_contract_write(contract_json, function_name, rhai::Array::new()) } // Call a state-changing function on a contract with arguments fn call_contract_write(contract_json: &str, function_name: &str, args: rhai::Array) -> String { // Deserialize the contract let contract: ethereum::Contract = match serde_json::from_str(contract_json) { Ok(contract) => contract, Err(e) => { log::error!("Error deserializing contract: {}", e); return String::new(); } }; // Prepare the arguments let tokens = match ethereum::prepare_function_arguments(&contract.abi, function_name, &args) { Ok(tokens) => tokens, Err(e) => { log::error!("Error preparing arguments: {}", e); return String::new(); } }; // Get the runtime let rt = match RUNTIME.lock() { Ok(rt) => rt, Err(e) => { log::error!("Failed to acquire runtime lock: {}", e); return String::new(); } }; // Get the wallet let network_name_proper = contract.network.name.as_str(); let wallet = match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) { Ok(w) => w, Err(e) => { log::error!("Failed to get wallet: {}", e); return String::new(); } }; // Create a provider let provider = match ethereum::create_provider(&contract.network) { Ok(p) => p, Err(e) => { log::error!("Failed to create provider: {}", e); return String::new(); } }; // Execute the transaction in a blocking manner match rt.block_on(async { ethereum::call_write_function(&contract, &wallet, &provider, function_name, tokens).await }) { Ok(tx_hash) => format!("{:?}", tx_hash), Err(e) => { // Log the error details for debugging log::debug!("\nERROR DETAILS: Transaction failed: {}", e); log::debug!("Contract address: {}", contract.address); log::debug!("Function: {}", function_name); log::debug!("Arguments: {:?}", args); log::debug!("Wallet address: {}", wallet.address); log::debug!("Network: {}", contract.network.name); log::error!("Transaction failed: {}", e); String::new() } } } /// Register crypto functions with the Rhai engine pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box> { // Register key space functions engine.register_fn("load_key_space", load_key_space); engine.register_fn("create_key_space", create_key_space); engine.register_fn("encrypt_key_space", encrypt_key_space); engine.register_fn("decrypt_key_space", decrypt_key_space); // Register keypair functions engine.register_fn("create_keypair", create_keypair); engine.register_fn("select_keypair", select_keypair); engine.register_fn("list_keypairs", list_keypairs); // Register signing/verification functions engine.register_fn("sign", sign); engine.register_fn("verify", verify); // Register symmetric encryption functions engine.register_fn("generate_key", generate_key); engine.register_fn("encrypt", encrypt); engine.register_fn("decrypt", decrypt); // Register Ethereum functions (Gnosis Chain) engine.register_fn("create_ethereum_wallet", create_ethereum_wallet); engine.register_fn("get_ethereum_address", get_ethereum_address); // Register Peaq network functions engine.register_fn("create_peaq_wallet", create_peaq_wallet); engine.register_fn("get_peaq_address", get_peaq_address); // Register Agung testnet functions engine.register_fn("create_agung_wallet", create_agung_wallet); engine.register_fn("get_agung_address", get_agung_address); // Register generic network functions engine.register_fn("create_wallet_for_network", create_wallet_for_network); engine.register_fn( "get_wallet_address_for_network", get_wallet_address_for_network, ); engine.register_fn("clear_wallets_for_network", clear_wallets_for_network); engine.register_fn("list_supported_networks", list_supported_networks); engine.register_fn("get_network_token_symbol", get_network_token_symbol); engine.register_fn("get_network_explorer_url", get_network_explorer_url); // Register new Ethereum functions for wallet creation from private key and transactions engine.register_fn( "create_wallet_from_private_key_for_network", create_wallet_from_private_key_for_network, ); engine.register_fn("create_agung_provider", create_agung_provider); engine.register_fn("send_eth", send_eth); engine.register_fn("get_balance", get_balance); // Register smart contract functions engine.register_fn("load_contract_abi", load_contract_abi); engine.register_fn("load_contract_abi_from_file", load_contract_abi_from_file); // Register the read function with different arities engine.register_fn("call_contract_read", call_contract_read_no_args); engine.register_fn("call_contract_read", call_contract_read); // Register the write function with different arities engine.register_fn("call_contract_write", call_contract_write_no_args); engine.register_fn("call_contract_write", call_contract_write); Ok(()) }