Compare commits
No commits in common. "8aa2b2da261796ba52f56be9d41f6d477c27c974" and "516d0177e7ec151d3444a71c71514081ce3e35a0" have entirely different histories.
8aa2b2da26
...
516d0177e7
20
Cargo.toml
20
Cargo.toml
@ -29,28 +29,12 @@ serde_json = "1.0" # For JSON handling
|
|||||||
glob = "0.3.1" # For file pattern matching
|
glob = "0.3.1" # For file pattern matching
|
||||||
tempfile = "3.5" # For temporary file operations
|
tempfile = "3.5" # For temporary file operations
|
||||||
log = "0.4" # Logging facade
|
log = "0.4" # Logging facade
|
||||||
env_logger = "0.10.0" # Logger implementation
|
|
||||||
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
|
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
|
||||||
rand = "0.8.5" # Random number generation
|
rand = "0.8.5" # Random number generation
|
||||||
clap = "2.33" # Command-line argument parsing
|
clap = "2.33" # Command-line argument parsing
|
||||||
r2d2 = "0.8.10"
|
r2d2 = "0.8.10"
|
||||||
r2d2_postgres = "0.18.2"
|
r2d2_postgres = "0.18.2"
|
||||||
|
|
||||||
# Crypto dependencies
|
|
||||||
base64 = "0.21.0" # Base64 encoding/decoding
|
|
||||||
k256 = { version = "0.13.1", features = ["ecdsa"] } # Elliptic curve cryptography
|
|
||||||
once_cell = "1.18.0" # Lazy static initialization
|
|
||||||
sha2 = "0.10.7" # SHA-2 hash functions
|
|
||||||
chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher
|
|
||||||
ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library
|
|
||||||
dirs = "5.0.1" # Directory paths
|
|
||||||
uuid = { version = "1.16.0", features = ["v4"] }
|
|
||||||
tokio-test = "0.4.4"
|
|
||||||
zinit-client = { git = "https://github.com/threefoldtech/zinit", branch = "json_rpc", package = "zinit-client" }
|
|
||||||
anyhow = "1.0.98"
|
|
||||||
jsonrpsee = "0.25.1"
|
|
||||||
tokio = "1.45.0"
|
|
||||||
|
|
||||||
# Optional features for specific OS functionality
|
# Optional features for specific OS functionality
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
nix = "0.26" # Unix-specific functionality
|
nix = "0.26" # Unix-specific functionality
|
||||||
@ -63,9 +47,7 @@ windows = { version = "0.48", features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.5" # For tests that need temporary files/directories
|
tempfile = "3.5" # For tests that need temporary files/directories
|
||||||
tokio = { version = "1.28", features = ["full", "test-util"] } # For async testing
|
|
||||||
mockall = "0.11.4" # For mocking in tests
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "herodo"
|
name = "herodo"
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
# Hero Vault Cryptography Examples
|
|
||||||
|
|
||||||
This directory contains examples demonstrating the Hero Vault cryptography functionality integrated into the SAL project.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Hero Vault provides cryptographic operations including:
|
|
||||||
|
|
||||||
- Key space management (creation, loading, encryption, decryption)
|
|
||||||
- Keypair management (creation, selection, listing)
|
|
||||||
- Digital signatures (signing and verification)
|
|
||||||
- Symmetric encryption (key generation, encryption, decryption)
|
|
||||||
- Ethereum wallet functionality
|
|
||||||
- Smart contract interactions
|
|
||||||
- Key-value store with encryption
|
|
||||||
|
|
||||||
## Example Files
|
|
||||||
|
|
||||||
- `example.rhai` - Basic example demonstrating key management, signing, and encryption
|
|
||||||
- `advanced_example.rhai` - Advanced example with error handling, conditional logic, and more complex operations
|
|
||||||
- `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk
|
|
||||||
- `load_existing_space.rhai` - Shows how to load a previously created key space and use its keypairs
|
|
||||||
- `contract_example.rhai` - Demonstrates loading a contract ABI and interacting with smart contracts
|
|
||||||
- `agung_send_transaction.rhai` - Demonstrates sending native tokens on the Agung network
|
|
||||||
- `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung
|
|
||||||
|
|
||||||
## Running the Examples
|
|
||||||
|
|
||||||
You can run the examples using the `herodo` tool that comes with the SAL project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run a single example
|
|
||||||
herodo --path example.rhai
|
|
||||||
|
|
||||||
# Run all examples using the provided script
|
|
||||||
./run_examples.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Space Storage
|
|
||||||
|
|
||||||
Key spaces are stored in the `~/.hero-vault/key-spaces/` directory by default. Each key space is stored in a separate JSON file named after the key space (e.g., `my_space.json`).
|
|
||||||
|
|
||||||
## Ethereum Functionality
|
|
||||||
|
|
||||||
The Hero Vault module provides comprehensive Ethereum wallet functionality:
|
|
||||||
|
|
||||||
- Creating and managing wallets for different networks
|
|
||||||
- Sending ETH transactions
|
|
||||||
- Checking balances
|
|
||||||
- Interacting with smart contracts (read and write functions)
|
|
||||||
- Support for multiple networks (Ethereum, Gnosis, Peaq, Agung, etc.)
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password. The encryption ensures that the key material is secure at rest.
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Use Strong Passwords**: Since the security of your key spaces depends on the strength of your passwords, use strong, unique passwords.
|
|
||||||
2. **Backup Key Spaces**: Regularly backup your key spaces directory to prevent data loss.
|
|
||||||
3. **Script Organization**: Split your scripts into logical units, with separate scripts for key creation and key usage.
|
|
||||||
4. **Error Handling**: Always check the return values of functions to ensure operations succeeded before proceeding.
|
|
||||||
5. **Network Selection**: When working with Ethereum functionality, be explicit about which network you're targeting to avoid confusion.
|
|
||||||
6. **Gas Management**: For Ethereum transactions, consider gas costs and set appropriate gas limits.
|
|
@ -1,233 +0,0 @@
|
|||||||
// Advanced Rhai script example for Hero Vault Cryptography Module
|
|
||||||
// This script demonstrates conditional logic, error handling, and more complex operations
|
|
||||||
|
|
||||||
// Function to create a key space with error handling
|
|
||||||
fn setup_key_space(name, password) {
|
|
||||||
print("Attempting: Create key space: " + name);
|
|
||||||
let result = create_key_space(name, password);
|
|
||||||
|
|
||||||
if result {
|
|
||||||
print("✅ Create key space succeeded!");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
print("❌ Create key space failed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to create and select a keypair
|
|
||||||
fn setup_keypair(name, password) {
|
|
||||||
print("Attempting: Create keypair: " + name);
|
|
||||||
let result = create_keypair(name, password);
|
|
||||||
|
|
||||||
if result {
|
|
||||||
print("✅ Create keypair succeeded!");
|
|
||||||
|
|
||||||
print("Attempting: Select keypair: " + name);
|
|
||||||
let selected = select_keypair(name);
|
|
||||||
|
|
||||||
if selected {
|
|
||||||
print("✅ Select keypair succeeded!");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
print("❌ Select keypair failed!");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("❌ Create keypair failed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to sign multiple messages
|
|
||||||
fn sign_messages(messages) {
|
|
||||||
let signatures = [];
|
|
||||||
|
|
||||||
for message in messages {
|
|
||||||
print("Signing message: " + message);
|
|
||||||
print("Attempting: Sign message");
|
|
||||||
let signature = sign(message);
|
|
||||||
|
|
||||||
if signature != "" {
|
|
||||||
print("✅ Sign message succeeded!");
|
|
||||||
signatures.push(#{
|
|
||||||
message: message,
|
|
||||||
signature: signature
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
print("❌ Sign message failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return signatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to verify signatures
|
|
||||||
fn verify_signatures(signed_messages) {
|
|
||||||
let results = [];
|
|
||||||
|
|
||||||
for item in signed_messages {
|
|
||||||
let message = item.message;
|
|
||||||
let signature = item.signature;
|
|
||||||
|
|
||||||
print("Verifying signature for: " + message);
|
|
||||||
print("Attempting: Verify signature");
|
|
||||||
let is_valid = verify(message, signature);
|
|
||||||
|
|
||||||
if is_valid {
|
|
||||||
print("✅ Verify signature succeeded!");
|
|
||||||
} else {
|
|
||||||
print("❌ Verify signature failed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push(#{
|
|
||||||
message: message,
|
|
||||||
valid: is_valid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to encrypt multiple messages
|
|
||||||
fn encrypt_messages(messages) {
|
|
||||||
// Generate a symmetric key
|
|
||||||
print("Attempting: Generate symmetric key");
|
|
||||||
let key = generate_key();
|
|
||||||
|
|
||||||
if key == "" {
|
|
||||||
print("❌ Generate symmetric key failed!");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
print("✅ Generate symmetric key succeeded!");
|
|
||||||
print("Using key: " + key);
|
|
||||||
let encrypted_messages = [];
|
|
||||||
|
|
||||||
for message in messages {
|
|
||||||
print("Encrypting message: " + message);
|
|
||||||
print("Attempting: Encrypt message");
|
|
||||||
let encrypted = encrypt(key, message);
|
|
||||||
|
|
||||||
if encrypted != "" {
|
|
||||||
print("✅ Encrypt message succeeded!");
|
|
||||||
encrypted_messages.push(#{
|
|
||||||
original: message,
|
|
||||||
encrypted: encrypted,
|
|
||||||
key: key
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
print("❌ Encrypt message failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return encrypted_messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to decrypt messages
|
|
||||||
fn decrypt_messages(encrypted_messages) {
|
|
||||||
let decrypted_messages = [];
|
|
||||||
|
|
||||||
for item in encrypted_messages {
|
|
||||||
let encrypted = item.encrypted;
|
|
||||||
let key = item.key;
|
|
||||||
let original = item.original;
|
|
||||||
|
|
||||||
print("Decrypting message...");
|
|
||||||
print("Attempting: Decrypt message");
|
|
||||||
let decrypted = decrypt(key, encrypted);
|
|
||||||
|
|
||||||
if decrypted != false {
|
|
||||||
let success = decrypted == original;
|
|
||||||
|
|
||||||
decrypted_messages.push(#{
|
|
||||||
decrypted: decrypted,
|
|
||||||
original: original,
|
|
||||||
success: success
|
|
||||||
});
|
|
||||||
|
|
||||||
if success {
|
|
||||||
print("Decryption matched original ✅");
|
|
||||||
} else {
|
|
||||||
print("Decryption did not match original ❌");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return decrypted_messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main script execution
|
|
||||||
print("=== Advanced Cryptography Script ===");
|
|
||||||
|
|
||||||
// Set up key space
|
|
||||||
let space_name = "advanced_space";
|
|
||||||
let password = "secure_password123";
|
|
||||||
|
|
||||||
if setup_key_space(space_name, password) {
|
|
||||||
print("\n--- Key space setup complete ---\n");
|
|
||||||
|
|
||||||
// Set up keypair
|
|
||||||
if setup_keypair("advanced_keypair", password) {
|
|
||||||
print("\n--- Keypair setup complete ---\n");
|
|
||||||
|
|
||||||
// Define messages to sign
|
|
||||||
let messages = [
|
|
||||||
"This is the first message to sign",
|
|
||||||
"Here's another message that needs signing",
|
|
||||||
"And a third message for good measure"
|
|
||||||
];
|
|
||||||
|
|
||||||
// Sign messages
|
|
||||||
print("\n--- Signing Messages ---\n");
|
|
||||||
let signed_messages = sign_messages(messages);
|
|
||||||
|
|
||||||
// Verify signatures
|
|
||||||
print("\n--- Verifying Signatures ---\n");
|
|
||||||
let verification_results = verify_signatures(signed_messages);
|
|
||||||
|
|
||||||
// Count successful verifications
|
|
||||||
let successful_verifications = verification_results.filter(|r| r.valid).len();
|
|
||||||
print("Successfully verified " + successful_verifications + " out of " + verification_results.len() + " signatures");
|
|
||||||
|
|
||||||
// Encrypt messages
|
|
||||||
print("\n--- Encrypting Messages ---\n");
|
|
||||||
let encrypted_messages = encrypt_messages(messages);
|
|
||||||
|
|
||||||
// Decrypt messages
|
|
||||||
print("\n--- Decrypting Messages ---\n");
|
|
||||||
let decryption_results = decrypt_messages(encrypted_messages);
|
|
||||||
|
|
||||||
// Count successful decryptions
|
|
||||||
let successful_decryptions = decryption_results.filter(|r| r.success).len();
|
|
||||||
print("Successfully decrypted " + successful_decryptions + " out of " + decryption_results.len() + " messages");
|
|
||||||
|
|
||||||
// Create Ethereum wallet
|
|
||||||
print("\n--- Creating Ethereum Wallet ---\n");
|
|
||||||
print("Attempting: Create Ethereum wallet");
|
|
||||||
let wallet_created = create_ethereum_wallet();
|
|
||||||
|
|
||||||
if wallet_created {
|
|
||||||
print("✅ Create Ethereum wallet succeeded!");
|
|
||||||
|
|
||||||
print("Attempting: Get Ethereum address");
|
|
||||||
let address = get_ethereum_address();
|
|
||||||
|
|
||||||
if address != "" {
|
|
||||||
print("✅ Get Ethereum address succeeded!");
|
|
||||||
print("Ethereum wallet address: " + address);
|
|
||||||
} else {
|
|
||||||
print("❌ Get Ethereum address failed!");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("❌ Create Ethereum wallet failed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\n=== Script execution completed successfully! ===");
|
|
||||||
} else {
|
|
||||||
print("Failed to set up keypair. Aborting script.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("Failed to set up key space. Aborting script.");
|
|
||||||
}
|
|
@ -1,152 +0,0 @@
|
|||||||
// Example Rhai script for testing contract functions with arguments on Agung network
|
|
||||||
// This script demonstrates how to use call_contract_read and call_contract_write with arguments
|
|
||||||
|
|
||||||
// Step 1: Set up wallet and network
|
|
||||||
let space_name = "agung_contract_args_demo";
|
|
||||||
let password = "secure_password123";
|
|
||||||
let private_key = "51c194d20bcd25360a3aa94426b3b60f738007e42f22e1bc97821c65c353e6d2";
|
|
||||||
let network_name = "agung";
|
|
||||||
|
|
||||||
print("=== Testing Contract Functions With Arguments on Agung Network ===\n");
|
|
||||||
|
|
||||||
// Create a key space
|
|
||||||
print("Creating key space: " + space_name);
|
|
||||||
if create_key_space(space_name, password) {
|
|
||||||
print("✓ Key space created successfully");
|
|
||||||
|
|
||||||
// Create a keypair
|
|
||||||
print("\nCreating keypair...");
|
|
||||||
if create_keypair("contract_key", password) {
|
|
||||||
print("✓ Created contract keypair");
|
|
||||||
|
|
||||||
// Create a wallet from the private key for the Agung network
|
|
||||||
print("\nCreating wallet from private key for Agung network...");
|
|
||||||
if create_wallet_from_private_key_for_network(private_key, network_name) {
|
|
||||||
print("✓ Wallet created successfully");
|
|
||||||
|
|
||||||
// Get the wallet address
|
|
||||||
let wallet_address = get_wallet_address_for_network(network_name);
|
|
||||||
print("Wallet address: " + wallet_address);
|
|
||||||
|
|
||||||
// Check wallet balance
|
|
||||||
print("\nChecking wallet balance...");
|
|
||||||
let balance = get_balance(network_name, wallet_address);
|
|
||||||
if balance != "" {
|
|
||||||
print("Wallet balance: " + balance + " wei");
|
|
||||||
|
|
||||||
// Define a simple ERC-20 token contract ABI (partial)
|
|
||||||
let token_abi = `[
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "name",
|
|
||||||
"outputs": [{"name": "", "type": "string"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "symbol",
|
|
||||||
"outputs": [{"name": "", "type": "string"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "decimals",
|
|
||||||
"outputs": [{"name": "", "type": "uint8"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [{"name": "_owner", "type": "address"}],
|
|
||||||
"name": "balanceOf",
|
|
||||||
"outputs": [{"name": "balance", "type": "uint256"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}],
|
|
||||||
"name": "transfer",
|
|
||||||
"outputs": [{"name": "", "type": "bool"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
]`;
|
|
||||||
|
|
||||||
// For this example, we'll use a test token contract on Agung
|
|
||||||
let token_address = "0x7267B587E4416537060C6bF0B06f6Fd421106650";
|
|
||||||
|
|
||||||
print("\nLoading contract ABI...");
|
|
||||||
let contract = load_contract_abi(network_name, token_address, token_abi);
|
|
||||||
|
|
||||||
if contract != "" {
|
|
||||||
print("✓ Contract loaded successfully");
|
|
||||||
|
|
||||||
// First, let's try to read some data from the contract
|
|
||||||
print("\nReading contract data...");
|
|
||||||
|
|
||||||
// Try to get token name (no arguments)
|
|
||||||
let token_name = call_contract_read(contract, "name");
|
|
||||||
print("Token name: " + token_name);
|
|
||||||
|
|
||||||
// Try to get token symbol (no arguments)
|
|
||||||
let token_symbol = call_contract_read(contract, "symbol");
|
|
||||||
print("Token symbol: " + token_symbol);
|
|
||||||
|
|
||||||
// Try to get token decimals (no arguments)
|
|
||||||
let token_decimals = call_contract_read(contract, "decimals");
|
|
||||||
print("Token decimals: " + token_decimals);
|
|
||||||
|
|
||||||
// Try to get token balance (with address argument)
|
|
||||||
print("\nCalling balanceOf with address argument...");
|
|
||||||
let balance = call_contract_read(contract, "balanceOf", [wallet_address]);
|
|
||||||
print("Token balance: " + balance);
|
|
||||||
|
|
||||||
// Now, let's try to execute a write function with arguments
|
|
||||||
print("\nExecuting contract write function with arguments...");
|
|
||||||
|
|
||||||
// Define a recipient address and amount for the transfer
|
|
||||||
// Using a random valid address on the network
|
|
||||||
let recipient = "0xEEdf3468E8F232A7a03D49b674bA44740C8BD8Be";
|
|
||||||
let amount = 1000000; // Changed from string to number for uint256 compatibility
|
|
||||||
|
|
||||||
print("Attempting to transfer " + amount + " tokens to " + recipient);
|
|
||||||
|
|
||||||
// Call the transfer function with arguments
|
|
||||||
let tx_hash = call_contract_write(contract, "transfer", [recipient, amount]);
|
|
||||||
|
|
||||||
if tx_hash != "" {
|
|
||||||
print("✓ Transaction sent successfully");
|
|
||||||
print("Transaction hash: " + tx_hash);
|
|
||||||
print("You can view the transaction at: " + get_network_explorer_url(network_name) + "/tx/" + tx_hash);
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to send transaction");
|
|
||||||
print("This could be due to insufficient funds, contract issues, or other errors.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to load contract");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to get wallet balance");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create wallet from private key");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create keypair");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create key space");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\nContract function with arguments test completed");
|
|
@ -1,104 +0,0 @@
|
|||||||
// Script to create an Agung wallet from a private key and send tokens
|
|
||||||
// This script demonstrates how to create a wallet from a private key and send tokens
|
|
||||||
|
|
||||||
// Define the private key and recipient address
|
|
||||||
let private_key = "0x9ecfd58eca522b0e7c109bf945966ee208cd6d593b1dc3378aedfdc60b64f512";
|
|
||||||
let recipient_address = "0xf400f9c3F7317e19523a5DB698Ce67e7a7E083e2";
|
|
||||||
|
|
||||||
print("=== Agung Wallet Transaction Demo ===");
|
|
||||||
print(`From private key: ${private_key}`);
|
|
||||||
print(`To address: ${recipient_address}`);
|
|
||||||
|
|
||||||
// First, create a key space and keypair (required for the wallet infrastructure)
|
|
||||||
let space_name = "agung_transaction_demo";
|
|
||||||
let password = "demo_password";
|
|
||||||
|
|
||||||
// Create a new key space
|
|
||||||
if !create_key_space(space_name, password) {
|
|
||||||
print("Failed to create key space");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a keypair
|
|
||||||
if !create_keypair("demo_keypair", password) {
|
|
||||||
print("Failed to create keypair");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select the keypair
|
|
||||||
if !select_keypair("demo_keypair") {
|
|
||||||
print("Failed to select keypair");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\nCreated and selected keypair successfully");
|
|
||||||
|
|
||||||
// Clear any existing Agung wallets to avoid conflicts
|
|
||||||
if clear_wallets_for_network("agung") {
|
|
||||||
print("Cleared existing Agung wallets");
|
|
||||||
} else {
|
|
||||||
print("Failed to clear existing Agung wallets");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a wallet from the private key directly
|
|
||||||
print("\n=== Creating Wallet from Private Key ===");
|
|
||||||
|
|
||||||
// Create a wallet from the private key for the Agung network
|
|
||||||
if create_wallet_from_private_key_for_network(private_key, "agung") {
|
|
||||||
print("Successfully created wallet from private key for Agung network");
|
|
||||||
|
|
||||||
// Get the wallet address
|
|
||||||
let wallet_address = get_wallet_address_for_network("agung");
|
|
||||||
print(`Wallet address: ${wallet_address}`);
|
|
||||||
|
|
||||||
// Create a provider for the Agung network
|
|
||||||
let provider_id = create_agung_provider();
|
|
||||||
if provider_id != "" {
|
|
||||||
print("Successfully created Agung provider");
|
|
||||||
|
|
||||||
// Check the wallet balance first
|
|
||||||
let wallet_address = get_wallet_address_for_network("agung");
|
|
||||||
let balance_wei = get_balance("agung", wallet_address);
|
|
||||||
|
|
||||||
if balance_wei == "" {
|
|
||||||
print("Failed to get wallet balance");
|
|
||||||
print("This could be due to network issues or other errors.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
print(`Current wallet balance: ${balance_wei} wei`);
|
|
||||||
|
|
||||||
// Convert 1 AGNG to wei (1 AGNG = 10^18 wei)
|
|
||||||
// Use string representation for large numbers
|
|
||||||
let amount_wei_str = "1000000000000000000"; // 1 AGNG in wei as a string
|
|
||||||
|
|
||||||
// Check if we have enough balance
|
|
||||||
if parse_int(balance_wei) < parse_int(amount_wei_str) {
|
|
||||||
print(`Insufficient balance to send ${amount_wei_str} wei (1 AGNG)`);
|
|
||||||
print(`Current balance: ${balance_wei} wei`);
|
|
||||||
print("Please fund the wallet before attempting to send a transaction");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
print(`Attempting to send ${amount_wei_str} wei (1 AGNG) to ${recipient_address}`);
|
|
||||||
|
|
||||||
// Send the transaction using the blocking implementation
|
|
||||||
let tx_hash = send_eth("agung", recipient_address, amount_wei_str);
|
|
||||||
|
|
||||||
if tx_hash != "" {
|
|
||||||
print(`Transaction sent with hash: ${tx_hash}`);
|
|
||||||
print(`You can view the transaction at: ${get_network_explorer_url("agung")}/tx/${tx_hash}`);
|
|
||||||
} else {
|
|
||||||
print("Transaction failed");
|
|
||||||
print("This could be due to insufficient funds, network issues, or other errors.");
|
|
||||||
print("Check the logs for more details.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("Failed to create Agung provider");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("Failed to create wallet from private key");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\nAgung transaction demo completed");
|
|
@ -1,98 +0,0 @@
|
|||||||
// Example Rhai script for interacting with smart contracts using Hero Vault
|
|
||||||
// This script demonstrates loading a contract ABI and interacting with a contract
|
|
||||||
|
|
||||||
// Step 1: Set up wallet and network
|
|
||||||
let space_name = "contract_demo_space";
|
|
||||||
let password = "secure_password123";
|
|
||||||
|
|
||||||
print("Creating key space: " + space_name);
|
|
||||||
if create_key_space(space_name, password) {
|
|
||||||
print("✓ Key space created successfully");
|
|
||||||
|
|
||||||
// Create a keypair
|
|
||||||
print("\nCreating keypair...");
|
|
||||||
if create_keypair("contract_key", password) {
|
|
||||||
print("✓ Created contract keypair");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Create an Ethereum wallet for Gnosis Chain
|
|
||||||
print("\nCreating Ethereum wallet...");
|
|
||||||
if create_ethereum_wallet() {
|
|
||||||
print("✓ Ethereum wallet created");
|
|
||||||
|
|
||||||
let address = get_ethereum_address();
|
|
||||||
print("Ethereum address: " + address);
|
|
||||||
|
|
||||||
// Step 3: Define a simple ERC-20 ABI (partial)
|
|
||||||
let erc20_abi = `[
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "name",
|
|
||||||
"outputs": [{"name": "", "type": "string"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "symbol",
|
|
||||||
"outputs": [{"name": "", "type": "string"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "decimals",
|
|
||||||
"outputs": [{"name": "", "type": "uint8"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [{"name": "owner", "type": "address"}],
|
|
||||||
"name": "balanceOf",
|
|
||||||
"outputs": [{"name": "", "type": "uint256"}],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
]`;
|
|
||||||
|
|
||||||
// Step 4: Load the contract ABI
|
|
||||||
print("\nLoading contract ABI...");
|
|
||||||
let contract = load_contract_abi("Gnosis", "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", erc20_abi);
|
|
||||||
if contract != "" {
|
|
||||||
print("✓ Contract loaded successfully");
|
|
||||||
|
|
||||||
// Step 5: Call read-only functions
|
|
||||||
print("\nCalling read-only functions...");
|
|
||||||
|
|
||||||
// Get token name
|
|
||||||
let token_name = call_contract_read(contract, "name");
|
|
||||||
print("Token name: " + token_name);
|
|
||||||
|
|
||||||
// Get token symbol
|
|
||||||
let token_symbol = call_contract_read(contract, "symbol");
|
|
||||||
print("Token symbol: " + token_symbol);
|
|
||||||
|
|
||||||
// Get token decimals
|
|
||||||
let token_decimals = call_contract_read(contract, "decimals");
|
|
||||||
print("Token decimals: " + token_decimals);
|
|
||||||
|
|
||||||
// For now, we're just demonstrating the basic structure
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to load contract");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create Ethereum wallet");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create key space");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\nContract example completed");
|
|
@ -1,85 +0,0 @@
|
|||||||
// Example Rhai script for Hero Vault Cryptography Module
|
|
||||||
// This script demonstrates key management, signing, and encryption
|
|
||||||
|
|
||||||
// Step 1: Create and manage a key space
|
|
||||||
let space_name = "demo_space";
|
|
||||||
let password = "secure_password123";
|
|
||||||
|
|
||||||
print("Creating key space: " + space_name);
|
|
||||||
if create_key_space(space_name, password) {
|
|
||||||
print("✓ Key space created successfully");
|
|
||||||
|
|
||||||
// Step 2: Create and use keypairs
|
|
||||||
print("\nCreating keypairs...");
|
|
||||||
if create_keypair("signing_key", password) {
|
|
||||||
print("✓ Created signing keypair");
|
|
||||||
}
|
|
||||||
|
|
||||||
if create_keypair("encryption_key", password) {
|
|
||||||
print("✓ Created encryption keypair");
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all keypairs
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
print("Available keypairs: " + keypairs);
|
|
||||||
|
|
||||||
// Step 3: Sign a message
|
|
||||||
print("\nPerforming signing operations...");
|
|
||||||
if select_keypair("signing_key") {
|
|
||||||
print("✓ Selected signing keypair");
|
|
||||||
|
|
||||||
let message = "This is a secure message that needs to be signed";
|
|
||||||
print("Message: " + message);
|
|
||||||
|
|
||||||
let signature = sign(message);
|
|
||||||
print("Signature: " + signature);
|
|
||||||
|
|
||||||
// Verify the signature
|
|
||||||
let is_valid = verify(message, signature);
|
|
||||||
if is_valid {
|
|
||||||
print("Signature verification: ✓ Valid");
|
|
||||||
} else {
|
|
||||||
print("Signature verification: ✗ Invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Encrypt and decrypt data
|
|
||||||
print("\nPerforming encryption operations...");
|
|
||||||
|
|
||||||
// Generate a symmetric key
|
|
||||||
let sym_key = generate_key();
|
|
||||||
print("Generated symmetric key: " + sym_key);
|
|
||||||
|
|
||||||
// Encrypt a message
|
|
||||||
let secret = "This is a top secret message that must be encrypted";
|
|
||||||
print("Original message: " + secret);
|
|
||||||
|
|
||||||
let encrypted_data = encrypt(sym_key, secret);
|
|
||||||
print("Encrypted data: " + encrypted_data);
|
|
||||||
|
|
||||||
// Decrypt the message
|
|
||||||
let decrypted_data = decrypt(sym_key, encrypted_data);
|
|
||||||
print("Decrypted message: " + decrypted_data);
|
|
||||||
|
|
||||||
// Verify decryption was successful
|
|
||||||
if decrypted_data == secret {
|
|
||||||
print("✓ Encryption/decryption successful");
|
|
||||||
} else {
|
|
||||||
print("✗ Encryption/decryption failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Create an Ethereum wallet
|
|
||||||
print("\nCreating Ethereum wallet...");
|
|
||||||
if select_keypair("encryption_key") {
|
|
||||||
print("✓ Selected keypair for Ethereum wallet");
|
|
||||||
|
|
||||||
if create_ethereum_wallet() {
|
|
||||||
print("✓ Ethereum wallet created");
|
|
||||||
|
|
||||||
let address = get_ethereum_address();
|
|
||||||
print("Ethereum address: " + address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\nScript execution completed successfully!");
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
// Example Rhai script demonstrating key space persistence for Hero Vault
|
|
||||||
// This script shows how to create, save, and load key spaces
|
|
||||||
|
|
||||||
// Step 1: Create a key space
|
|
||||||
let space_name = "persistent_space";
|
|
||||||
let password = "secure_password123";
|
|
||||||
|
|
||||||
print("Creating key space: " + space_name);
|
|
||||||
if create_key_space(space_name, password) {
|
|
||||||
print("✓ Key space created successfully");
|
|
||||||
|
|
||||||
// Step 2: Create keypairs in this space
|
|
||||||
print("\nCreating keypairs...");
|
|
||||||
if create_keypair("persistent_key1", password) {
|
|
||||||
print("✓ Created first keypair");
|
|
||||||
}
|
|
||||||
|
|
||||||
if create_keypair("persistent_key2", password) {
|
|
||||||
print("✓ Created second keypair");
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all keypairs
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
print("Available keypairs: " + keypairs);
|
|
||||||
|
|
||||||
// Step 3: Clear the session (simulate closing and reopening the CLI)
|
|
||||||
print("\nClearing session (simulating restart)...");
|
|
||||||
// Note: In a real script, you would exit here and run a new script
|
|
||||||
// For demonstration purposes, we'll continue in the same script
|
|
||||||
|
|
||||||
// Step 4: Load the key space from disk
|
|
||||||
print("\nLoading key space from disk...");
|
|
||||||
if load_key_space(space_name, password) {
|
|
||||||
print("✓ Key space loaded successfully");
|
|
||||||
|
|
||||||
// Verify the keypairs are still available
|
|
||||||
let loaded_keypairs = list_keypairs();
|
|
||||||
print("Keypairs after loading: " + loaded_keypairs);
|
|
||||||
|
|
||||||
// Step 5: Use a keypair from the loaded space
|
|
||||||
print("\nSelecting and using a keypair...");
|
|
||||||
if select_keypair("persistent_key1") {
|
|
||||||
print("✓ Selected keypair");
|
|
||||||
|
|
||||||
let message = "This message was signed using a keypair from a loaded key space";
|
|
||||||
let signature = sign(message);
|
|
||||||
print("Message: " + message);
|
|
||||||
print("Signature: " + signature);
|
|
||||||
|
|
||||||
// Verify the signature
|
|
||||||
let is_valid = verify(message, signature);
|
|
||||||
if is_valid {
|
|
||||||
print("Signature verification: ✓ Valid");
|
|
||||||
} else {
|
|
||||||
print("Signature verification: ✗ Invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to load key space");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create key space");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\nScript execution completed!");
|
|
@ -1,65 +0,0 @@
|
|||||||
// Example Rhai script demonstrating loading an existing key space for Hero Vault
|
|
||||||
// This script shows how to load a previously created key space and use its keypairs
|
|
||||||
|
|
||||||
// Define the key space name and password
|
|
||||||
let space_name = "persistent_space";
|
|
||||||
let password = "secure_password123";
|
|
||||||
|
|
||||||
print("Loading existing key space: " + space_name);
|
|
||||||
|
|
||||||
// Load the key space from disk
|
|
||||||
if load_key_space(space_name, password) {
|
|
||||||
print("✓ Key space loaded successfully");
|
|
||||||
|
|
||||||
// List available keypairs
|
|
||||||
let keypairs = list_keypairs();
|
|
||||||
print("Available keypairs: " + keypairs);
|
|
||||||
|
|
||||||
// Use both keypairs to sign different messages
|
|
||||||
if select_keypair("persistent_key1") {
|
|
||||||
print("\nUsing persistent_key1:");
|
|
||||||
let message1 = "Message signed with the first keypair";
|
|
||||||
let signature1 = sign(message1);
|
|
||||||
print("Message: " + message1);
|
|
||||||
print("Signature: " + signature1);
|
|
||||||
|
|
||||||
let is_valid1 = verify(message1, signature1);
|
|
||||||
if is_valid1 {
|
|
||||||
print("Verification: ✓ Valid");
|
|
||||||
} else {
|
|
||||||
print("Verification: ✗ Invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if select_keypair("persistent_key2") {
|
|
||||||
print("\nUsing persistent_key2:");
|
|
||||||
let message2 = "Message signed with the second keypair";
|
|
||||||
let signature2 = sign(message2);
|
|
||||||
print("Message: " + message2);
|
|
||||||
print("Signature: " + signature2);
|
|
||||||
|
|
||||||
let is_valid2 = verify(message2, signature2);
|
|
||||||
if is_valid2 {
|
|
||||||
print("Verification: ✓ Valid");
|
|
||||||
} else {
|
|
||||||
print("Verification: ✗ Invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an Ethereum wallet using one of the keypairs
|
|
||||||
print("\nCreating Ethereum wallet from persistent keypair:");
|
|
||||||
if select_keypair("persistent_key1") {
|
|
||||||
if create_ethereum_wallet() {
|
|
||||||
print("✓ Ethereum wallet created");
|
|
||||||
|
|
||||||
let address = get_ethereum_address();
|
|
||||||
print("Ethereum address: " + address);
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to create Ethereum wallet");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print("✗ Failed to load key space. Make sure you've run key_persistence_example.rhai first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("\nScript execution completed!");
|
|
@ -1,87 +0,0 @@
|
|||||||
// Basic example of using the Zinit client in Rhai
|
|
||||||
|
|
||||||
// Socket path for Zinit
|
|
||||||
let socket_path = "/var/run/zinit.sock";
|
|
||||||
|
|
||||||
// List all services
|
|
||||||
print("Listing all services:");
|
|
||||||
let services = zinit_list(socket_path);
|
|
||||||
|
|
||||||
if services.is_empty() {
|
|
||||||
print("No services found.");
|
|
||||||
} else {
|
|
||||||
// Iterate over the keys of the map
|
|
||||||
for name in services.keys() {
|
|
||||||
let state = services[name];
|
|
||||||
print(`${name}: ${state}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get status of a specific service
|
|
||||||
let service_name = "test";
|
|
||||||
print(`Getting status for ${service_name}:`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let status = zinit_status(socket_path, service_name);
|
|
||||||
print(`Service: ${status.name}`);
|
|
||||||
print(`PID: ${status.pid}`);
|
|
||||||
print(`State: ${status.state}`);
|
|
||||||
print(`Target: ${status.target}`);
|
|
||||||
print("Dependencies:");
|
|
||||||
|
|
||||||
for (dep, state) in status.after.keys() {
|
|
||||||
print(` ${dep}: ${state}`);
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
print(`Error getting status: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new service
|
|
||||||
print("\nCreating a new service:");
|
|
||||||
let new_service = "rhai-test-service";
|
|
||||||
let exec_command = "echo 'Hello from Rhai'";
|
|
||||||
let oneshot = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let result = zinit_create_service(socket_path, new_service, exec_command, oneshot);
|
|
||||||
print(`Service created: ${result}`);
|
|
||||||
|
|
||||||
// Monitor the service
|
|
||||||
print("\nMonitoring the service:");
|
|
||||||
let monitor_result = zinit_monitor(socket_path, new_service);
|
|
||||||
print(`Service monitored: ${monitor_result}`);
|
|
||||||
|
|
||||||
// Start the service
|
|
||||||
print("\nStarting the service:");
|
|
||||||
let start_result = zinit_start(socket_path, new_service);
|
|
||||||
print(`Service started: ${start_result}`);
|
|
||||||
|
|
||||||
// Get logs for a specific service
|
|
||||||
print("\nGetting logs:");
|
|
||||||
let logs = zinit_logs(socket_path, new_service);
|
|
||||||
|
|
||||||
for log in logs {
|
|
||||||
print(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Or to get all logs (uncomment if needed)
|
|
||||||
// print("\nGetting all logs:");
|
|
||||||
// let all_logs = zinit_logs_all(socket_path);
|
|
||||||
//
|
|
||||||
// for log in all_logs {
|
|
||||||
// print(log);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
print("\nCleaning up:");
|
|
||||||
let stop_result = zinit_stop(socket_path, new_service);
|
|
||||||
print(`Service stopped: ${stop_result}`);
|
|
||||||
|
|
||||||
let forget_result = zinit_forget(socket_path, new_service);
|
|
||||||
print(`Service forgotten: ${forget_result}`);
|
|
||||||
|
|
||||||
let delete_result = zinit_delete_service(socket_path, new_service);
|
|
||||||
print(`Service deleted: ${delete_result}`);
|
|
||||||
} catch(err) {
|
|
||||||
print(`Error: ${err}`);
|
|
||||||
}
|
|
@ -4,12 +4,8 @@
|
|||||||
//! It parses command line arguments and calls into the implementation in the cmd module.
|
//! It parses command line arguments and calls into the implementation in the cmd module.
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use env_logger;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Initialize the logger
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
let matches = App::new("herodo")
|
let matches = App::new("herodo")
|
||||||
.version("0.1.0")
|
.version("0.1.0")
|
||||||
@ -31,4 +27,4 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
// Call the run function from the cmd module
|
// Call the run function from the cmd module
|
||||||
sal::cmd::herodo::run(script_path)
|
sal::cmd::herodo::run(script_path)
|
||||||
}
|
}
|
@ -1,160 +0,0 @@
|
|||||||
# Hero Vault Cryptography Module
|
|
||||||
|
|
||||||
The Hero Vault module provides comprehensive cryptographic functionality for the SAL project, including key management, digital signatures, symmetric encryption, Ethereum wallet operations, and a secure key-value store.
|
|
||||||
|
|
||||||
## Module Structure
|
|
||||||
|
|
||||||
The Hero Vault module is organized into several submodules:
|
|
||||||
|
|
||||||
- `error.rs` - Error types for cryptographic operations
|
|
||||||
- `keypair/` - ECDSA keypair management functionality
|
|
||||||
- `symmetric/` - Symmetric encryption using ChaCha20Poly1305
|
|
||||||
- `ethereum/` - Ethereum wallet and smart contract functionality
|
|
||||||
- `kvs/` - Encrypted key-value store
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### Key Space Management
|
|
||||||
|
|
||||||
The module provides functionality for creating, loading, and managing key spaces. A key space is a secure container for cryptographic keys, which can be encrypted and stored on disk.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a new key space
|
|
||||||
let space = KeySpace::new("my_space", "secure_password")?;
|
|
||||||
|
|
||||||
// Save the key space to disk
|
|
||||||
space.save()?;
|
|
||||||
|
|
||||||
// Load a key space from disk
|
|
||||||
let loaded_space = KeySpace::load("my_space", "secure_password")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Keypair Management
|
|
||||||
|
|
||||||
The module provides functionality for creating, selecting, and using ECDSA keypairs for digital signatures.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a new keypair in the active key space
|
|
||||||
let keypair = space.create_keypair("my_keypair", "secure_password")?;
|
|
||||||
|
|
||||||
// Select a keypair for use
|
|
||||||
space.select_keypair("my_keypair")?;
|
|
||||||
|
|
||||||
// List all keypairs in the active key space
|
|
||||||
let keypairs = space.list_keypairs()?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Digital Signatures
|
|
||||||
|
|
||||||
The module provides functionality for signing and verifying messages using ECDSA.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Sign a message using the selected keypair
|
|
||||||
let signature = space.sign("This is a message to sign")?;
|
|
||||||
|
|
||||||
// Verify a signature
|
|
||||||
let is_valid = space.verify("This is a message to sign", &signature)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Symmetric Encryption
|
|
||||||
|
|
||||||
The module provides functionality for symmetric encryption using ChaCha20Poly1305.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Generate a new symmetric key
|
|
||||||
let key = space.generate_key()?;
|
|
||||||
|
|
||||||
// Encrypt a message
|
|
||||||
let encrypted = space.encrypt(&key, "This is a secret message")?;
|
|
||||||
|
|
||||||
// Decrypt a message
|
|
||||||
let decrypted = space.decrypt(&key, &encrypted)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ethereum Wallet Functionality
|
|
||||||
|
|
||||||
The module provides comprehensive Ethereum wallet functionality, including:
|
|
||||||
|
|
||||||
- Creating and managing wallets for different networks
|
|
||||||
- Sending ETH transactions
|
|
||||||
- Checking balances
|
|
||||||
- Interacting with smart contracts
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create an Ethereum wallet
|
|
||||||
let wallet = EthereumWallet::new(keypair)?;
|
|
||||||
|
|
||||||
// Get the wallet address
|
|
||||||
let address = wallet.get_address()?;
|
|
||||||
|
|
||||||
// Send ETH
|
|
||||||
let tx_hash = wallet.send_eth("0x1234...", "1000000000000000")?;
|
|
||||||
|
|
||||||
// Check balance
|
|
||||||
let balance = wallet.get_balance("0x1234...")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Smart Contract Interactions
|
|
||||||
|
|
||||||
The module provides functionality for interacting with smart contracts on EVM-based blockchains.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Load a contract ABI
|
|
||||||
let contract = Contract::new(provider, "0x1234...", abi)?;
|
|
||||||
|
|
||||||
// Call a read-only function
|
|
||||||
let result = contract.call_read("balanceOf", vec!["0x5678..."])?;
|
|
||||||
|
|
||||||
// Call a write function
|
|
||||||
let tx_hash = contract.call_write("transfer", vec!["0x5678...", "1000"])?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key-Value Store
|
|
||||||
|
|
||||||
The module provides an encrypted key-value store for securely storing sensitive data.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a new store
|
|
||||||
let store = KvStore::new("my_store", "secure_password")?;
|
|
||||||
|
|
||||||
// Set a value
|
|
||||||
store.set("api_key", "secret_api_key")?;
|
|
||||||
|
|
||||||
// Get a value
|
|
||||||
let api_key = store.get("api_key")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
The module uses a comprehensive error type (`CryptoError`) for handling errors that can occur during cryptographic operations:
|
|
||||||
|
|
||||||
- `InvalidKeyLength` - Invalid key length
|
|
||||||
- `EncryptionFailed` - Encryption failed
|
|
||||||
- `DecryptionFailed` - Decryption failed
|
|
||||||
- `SignatureFormatError` - Signature format error
|
|
||||||
- `KeypairAlreadyExists` - Keypair already exists
|
|
||||||
- `KeypairNotFound` - Keypair not found
|
|
||||||
- `NoActiveSpace` - No active key space
|
|
||||||
- `NoKeypairSelected` - No keypair selected
|
|
||||||
- `SerializationError` - Serialization error
|
|
||||||
- `InvalidAddress` - Invalid address format
|
|
||||||
- `ContractError` - Smart contract error
|
|
||||||
|
|
||||||
## Ethereum Networks
|
|
||||||
|
|
||||||
The module supports multiple Ethereum networks, including:
|
|
||||||
|
|
||||||
- Gnosis Chain
|
|
||||||
- Peaq Network
|
|
||||||
- Agung Network
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password
|
|
||||||
- Private keys are never stored in plaintext
|
|
||||||
- The module uses secure random number generation for key creation
|
|
||||||
- All cryptographic operations use well-established libraries and algorithms
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
For examples of how to use the Hero Vault module, see the `examples/hero_vault` directory.
|
|
@ -1,58 +0,0 @@
|
|||||||
//! Error types for cryptographic operations
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Errors that can occur during cryptographic operations
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum CryptoError {
|
|
||||||
/// Invalid key length
|
|
||||||
#[error("Invalid key length")]
|
|
||||||
InvalidKeyLength,
|
|
||||||
|
|
||||||
/// Encryption failed
|
|
||||||
#[error("Encryption failed: {0}")]
|
|
||||||
EncryptionFailed(String),
|
|
||||||
|
|
||||||
/// Decryption failed
|
|
||||||
#[error("Decryption failed: {0}")]
|
|
||||||
DecryptionFailed(String),
|
|
||||||
|
|
||||||
/// Signature format error
|
|
||||||
#[error("Signature format error: {0}")]
|
|
||||||
SignatureFormatError(String),
|
|
||||||
|
|
||||||
/// Keypair already exists
|
|
||||||
#[error("Keypair already exists: {0}")]
|
|
||||||
KeypairAlreadyExists(String),
|
|
||||||
|
|
||||||
/// Keypair not found
|
|
||||||
#[error("Keypair not found: {0}")]
|
|
||||||
KeypairNotFound(String),
|
|
||||||
|
|
||||||
/// No active key space
|
|
||||||
#[error("No active key space")]
|
|
||||||
NoActiveSpace,
|
|
||||||
|
|
||||||
/// No keypair selected
|
|
||||||
#[error("No keypair selected")]
|
|
||||||
NoKeypairSelected,
|
|
||||||
|
|
||||||
/// Serialization error
|
|
||||||
#[error("Serialization error: {0}")]
|
|
||||||
SerializationError(String),
|
|
||||||
|
|
||||||
/// Invalid address format
|
|
||||||
#[error("Invalid address format: {0}")]
|
|
||||||
InvalidAddress(String),
|
|
||||||
|
|
||||||
/// Smart contract error
|
|
||||||
#[error("Smart contract error: {0}")]
|
|
||||||
ContractError(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert CryptoError to SAL's Error type
|
|
||||||
impl From<CryptoError> for crate::Error {
|
|
||||||
fn from(err: CryptoError) -> Self {
|
|
||||||
crate::Error::Sal(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,160 +0,0 @@
|
|||||||
# Hero Vault Ethereum Module
|
|
||||||
|
|
||||||
The Ethereum module provides functionality for creating and managing Ethereum wallets and interacting with smart contracts on EVM-based blockchains.
|
|
||||||
|
|
||||||
## Module Structure
|
|
||||||
|
|
||||||
The Ethereum module is organized into several components:
|
|
||||||
|
|
||||||
- `wallet.rs` - Core Ethereum wallet implementation
|
|
||||||
- `networks.rs` - Network registry and configuration
|
|
||||||
- `provider.rs` - Provider creation and management
|
|
||||||
- `transaction.rs` - Transaction-related functionality
|
|
||||||
- `storage.rs` - Wallet storage functionality
|
|
||||||
- `contract.rs` - Smart contract interaction functionality
|
|
||||||
- `contract_utils.rs` - Utilities for contract interactions
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### Wallet Management
|
|
||||||
|
|
||||||
The module provides functionality for creating and managing Ethereum wallets:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a new Ethereum wallet for a specific network
|
|
||||||
let wallet = create_ethereum_wallet_for_network("Ethereum")?;
|
|
||||||
|
|
||||||
// Create a wallet for specific networks
|
|
||||||
let peaq_wallet = create_peaq_wallet()?;
|
|
||||||
let agung_wallet = create_agung_wallet()?;
|
|
||||||
|
|
||||||
// Create a wallet with a specific name
|
|
||||||
let named_wallet = create_ethereum_wallet_from_name_for_network("my_wallet", "Gnosis")?;
|
|
||||||
|
|
||||||
// Create a wallet from a private key
|
|
||||||
let imported_wallet = create_ethereum_wallet_from_private_key("0x...")?;
|
|
||||||
|
|
||||||
// Get the current wallet for a network
|
|
||||||
let current_wallet = get_current_ethereum_wallet_for_network("Ethereum")?;
|
|
||||||
|
|
||||||
// Clear wallets
|
|
||||||
clear_ethereum_wallets()?;
|
|
||||||
clear_ethereum_wallets_for_network("Gnosis")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Network Management
|
|
||||||
|
|
||||||
The module supports multiple Ethereum networks and provides functionality for managing network configurations:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Get a network configuration by name
|
|
||||||
let network = get_network_by_name("Ethereum")?;
|
|
||||||
|
|
||||||
// Get the proper network name (normalized)
|
|
||||||
let name = get_proper_network_name("eth")?; // Returns "Ethereum"
|
|
||||||
|
|
||||||
// List all available network names
|
|
||||||
let networks = list_network_names()?;
|
|
||||||
|
|
||||||
// Get all network configurations
|
|
||||||
let all_networks = get_all_networks()?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Provider Management
|
|
||||||
|
|
||||||
The module provides functionality for creating and managing Ethereum providers:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a provider for a specific network
|
|
||||||
let provider = create_provider("Ethereum")?;
|
|
||||||
|
|
||||||
// Create providers for specific networks
|
|
||||||
let gnosis_provider = create_gnosis_provider()?;
|
|
||||||
let peaq_provider = create_peaq_provider()?;
|
|
||||||
let agung_provider = create_agung_provider()?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Transaction Management
|
|
||||||
|
|
||||||
The module provides functionality for managing Ethereum transactions:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Get the balance of an address
|
|
||||||
let balance = get_balance("Ethereum", "0x...")?;
|
|
||||||
|
|
||||||
// Send ETH to an address
|
|
||||||
let tx_hash = send_eth("Ethereum", "0x...", "1000000000000000")?;
|
|
||||||
|
|
||||||
// Format a balance for display
|
|
||||||
let formatted = format_balance(balance, 18)?; // Convert wei to ETH
|
|
||||||
```
|
|
||||||
|
|
||||||
### Smart Contract Interactions
|
|
||||||
|
|
||||||
The module provides functionality for interacting with smart contracts:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Load a contract ABI from JSON
|
|
||||||
let abi = load_abi_from_json(json_string)?;
|
|
||||||
|
|
||||||
// Create a contract instance
|
|
||||||
let contract = Contract::new(provider, "0x...", abi)?;
|
|
||||||
|
|
||||||
// Call a read-only function
|
|
||||||
let result = call_read_function(contract, "balanceOf", vec!["0x..."])?;
|
|
||||||
|
|
||||||
// Call a write function
|
|
||||||
let tx_hash = call_write_function(contract, "transfer", vec!["0x...", "1000"])?;
|
|
||||||
|
|
||||||
// Estimate gas for a function call
|
|
||||||
let gas = estimate_gas(contract, "transfer", vec!["0x...", "1000"])?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Contract Utilities
|
|
||||||
|
|
||||||
The module provides utilities for working with contract function arguments and return values:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Convert Rhai values to Ethereum tokens
|
|
||||||
let token = convert_rhai_to_token(value)?;
|
|
||||||
|
|
||||||
// Prepare function arguments
|
|
||||||
let args = prepare_function_arguments(function, vec![arg1, arg2])?;
|
|
||||||
|
|
||||||
// Convert Ethereum tokens to Rhai values
|
|
||||||
let rhai_value = convert_token_to_rhai(token)?;
|
|
||||||
|
|
||||||
// Convert a token to a dynamic value
|
|
||||||
let dynamic = token_to_dynamic(token)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Supported Networks
|
|
||||||
|
|
||||||
The module supports multiple Ethereum networks, including:
|
|
||||||
|
|
||||||
- Gnosis Chain
|
|
||||||
- Peaq Network
|
|
||||||
- Agung Network
|
|
||||||
|
|
||||||
Each network has its own configuration, including:
|
|
||||||
|
|
||||||
- RPC URL
|
|
||||||
- Chain ID
|
|
||||||
- Explorer URL
|
|
||||||
- Native currency symbol and decimals
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
The module uses the `CryptoError` type for handling errors that can occur during Ethereum operations:
|
|
||||||
|
|
||||||
- `InvalidAddress` - Invalid Ethereum address format
|
|
||||||
- `ContractError` - Smart contract interaction error
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
For examples of how to use the Ethereum module, see the `examples/hero_vault` directory, particularly:
|
|
||||||
|
|
||||||
- `contract_example.rhai` - Demonstrates loading a contract ABI and interacting with smart contracts
|
|
||||||
- `agung_simple_transfer.rhai` - Shows how to perform a simple ETH transfer on the Agung network
|
|
||||||
- `agung_send_transaction.rhai` - Demonstrates sending transactions on the Agung network
|
|
||||||
- `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung
|
|
@ -1,179 +0,0 @@
|
|||||||
//! Smart contract interaction functionality.
|
|
||||||
//!
|
|
||||||
//! This module provides functionality for interacting with smart contracts on EVM-based blockchains.
|
|
||||||
|
|
||||||
use ethers::prelude::*;
|
|
||||||
use ethers::abi::{Abi, Token};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
use crate::hero_vault::error::CryptoError;
|
|
||||||
use super::wallet::EthereumWallet;
|
|
||||||
use super::networks::NetworkConfig;
|
|
||||||
|
|
||||||
/// A smart contract instance.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Contract {
|
|
||||||
/// The contract address
|
|
||||||
pub address: Address,
|
|
||||||
/// The contract ABI
|
|
||||||
pub abi: Abi,
|
|
||||||
/// The network the contract is deployed on
|
|
||||||
pub network: NetworkConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Contract {
|
|
||||||
/// Creates a new contract instance.
|
|
||||||
pub fn new(address: Address, abi: Abi, network: NetworkConfig) -> Self {
|
|
||||||
Contract {
|
|
||||||
address,
|
|
||||||
abi,
|
|
||||||
network,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new contract instance from an address string and ABI.
|
|
||||||
pub fn from_address_string(address_str: &str, abi: Abi, network: NetworkConfig) -> Result<Self, CryptoError> {
|
|
||||||
let address = Address::from_str(address_str)
|
|
||||||
.map_err(|e| CryptoError::InvalidAddress(format!("Invalid address format: {}", e)))?;
|
|
||||||
|
|
||||||
Ok(Contract::new(address, abi, network))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an ethers Contract instance for interaction.
|
|
||||||
pub fn create_ethers_contract(&self, provider: Provider<Http>, _wallet: Option<&EthereumWallet>) -> Result<ethers::contract::Contract<ethers::providers::Provider<Http>>, CryptoError> {
|
|
||||||
let contract = ethers::contract::Contract::new(
|
|
||||||
self.address,
|
|
||||||
self.abi.clone(),
|
|
||||||
Arc::new(provider),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(contract)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads a contract ABI from a JSON string.
|
|
||||||
pub fn load_abi_from_json(json_str: &str) -> Result<Abi, CryptoError> {
|
|
||||||
serde_json::from_str(json_str)
|
|
||||||
.map_err(|e| CryptoError::SerializationError(format!("Failed to parse ABI JSON: {}", e)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls a read-only function on a contract.
|
|
||||||
pub async fn call_read_function(
|
|
||||||
contract: &Contract,
|
|
||||||
provider: &Provider<Http>,
|
|
||||||
function_name: &str,
|
|
||||||
args: Vec<Token>,
|
|
||||||
) -> Result<Vec<Token>, CryptoError> {
|
|
||||||
// Create the ethers contract (not used directly but kept for future extensions)
|
|
||||||
let _ethers_contract = contract.create_ethers_contract(provider.clone(), None)?;
|
|
||||||
|
|
||||||
// Get the function from the ABI
|
|
||||||
let function = contract.abi.function(function_name)
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?;
|
|
||||||
|
|
||||||
// Encode the function call
|
|
||||||
let call_data = function.encode_input(&args)
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?;
|
|
||||||
|
|
||||||
// Make the call
|
|
||||||
let tx = TransactionRequest::new()
|
|
||||||
.to(contract.address)
|
|
||||||
.data(call_data);
|
|
||||||
|
|
||||||
let result = provider.call(&tx.into(), None).await
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Contract call failed: {}", e)))?;
|
|
||||||
|
|
||||||
// Decode the result
|
|
||||||
let decoded = function.decode_output(&result)
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Failed to decode function output: {}", e)))?;
|
|
||||||
|
|
||||||
Ok(decoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes a state-changing function on a contract.
|
|
||||||
pub async fn call_write_function(
|
|
||||||
contract: &Contract,
|
|
||||||
wallet: &EthereumWallet,
|
|
||||||
provider: &Provider<Http>,
|
|
||||||
function_name: &str,
|
|
||||||
args: Vec<Token>,
|
|
||||||
) -> Result<H256, CryptoError> {
|
|
||||||
// Create a client with the wallet
|
|
||||||
let client = SignerMiddleware::new(
|
|
||||||
provider.clone(),
|
|
||||||
wallet.wallet.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the function from the ABI
|
|
||||||
let function = contract.abi.function(function_name)
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?;
|
|
||||||
|
|
||||||
// Encode the function call
|
|
||||||
let call_data = function.encode_input(&args)
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?;
|
|
||||||
|
|
||||||
// Create the transaction request with gas limit
|
|
||||||
let tx = TransactionRequest::new()
|
|
||||||
.to(contract.address)
|
|
||||||
.data(call_data)
|
|
||||||
.gas(U256::from(300000)); // Set a reasonable gas limit
|
|
||||||
|
|
||||||
// Send the transaction using the client directly
|
|
||||||
log::info!("Sending transaction to contract at {}", contract.address);
|
|
||||||
log::info!("Function: {}, Args: {:?}", function_name, args);
|
|
||||||
|
|
||||||
// Log detailed information about the transaction
|
|
||||||
log::debug!("Sending transaction to contract at {}", contract.address);
|
|
||||||
log::debug!("Function: {}, Args: {:?}", function_name, args);
|
|
||||||
log::debug!("From address: {}", wallet.address);
|
|
||||||
log::debug!("Gas limit: {:?}", tx.gas);
|
|
||||||
|
|
||||||
let pending_tx = match client.send_transaction(tx, None).await {
|
|
||||||
Ok(pending_tx) => {
|
|
||||||
log::debug!("Transaction sent successfully: {:?}", pending_tx.tx_hash());
|
|
||||||
log::info!("Transaction sent successfully: {:?}", pending_tx.tx_hash());
|
|
||||||
pending_tx
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
// Log the error for debugging
|
|
||||||
log::error!("Failed to send transaction: {}", e);
|
|
||||||
log::error!("ERROR DETAILS: {:?}", e);
|
|
||||||
return Err(CryptoError::ContractError(format!("Failed to send transaction: {}", e)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the transaction hash
|
|
||||||
Ok(pending_tx.tx_hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Estimates gas for a contract function call.
|
|
||||||
pub async fn estimate_gas(
|
|
||||||
contract: &Contract,
|
|
||||||
wallet: &EthereumWallet,
|
|
||||||
provider: &Provider<Http>,
|
|
||||||
function_name: &str,
|
|
||||||
args: Vec<Token>,
|
|
||||||
) -> Result<U256, CryptoError> {
|
|
||||||
// Get the function from the ABI
|
|
||||||
let function = contract.abi.function(function_name)
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Function not found in ABI: {}", e)))?;
|
|
||||||
|
|
||||||
// Encode the function call
|
|
||||||
let call_data = function.encode_input(&args)
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Failed to encode function call: {}", e)))?;
|
|
||||||
|
|
||||||
// Create the transaction request
|
|
||||||
let tx = TransactionRequest::new()
|
|
||||||
.from(wallet.address)
|
|
||||||
.to(contract.address)
|
|
||||||
.data(call_data);
|
|
||||||
|
|
||||||
// Estimate gas
|
|
||||||
let gas = provider.estimate_gas(&tx.into(), None)
|
|
||||||
.await
|
|
||||||
.map_err(|e| CryptoError::ContractError(format!("Failed to estimate gas: {}", e)))?;
|
|
||||||
|
|
||||||
Ok(gas)
|
|
||||||
}
|
|
@ -1,183 +0,0 @@
|
|||||||
//! Utility functions for smart contract interactions.
|
|
||||||
|
|
||||||
use ethers::abi::{Abi, Token, ParamType};
|
|
||||||
use ethers::types::{Address, U256};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use rhai::{Dynamic, Array};
|
|
||||||
|
|
||||||
/// Convert Rhai Dynamic values to ethers Token types
|
|
||||||
pub fn convert_rhai_to_token(value: &Dynamic, expected_type: Option<&ParamType>) -> Result<Token, String> {
|
|
||||||
match value {
|
|
||||||
// Handle integers
|
|
||||||
v if v.is_int() => {
|
|
||||||
let i = v.as_int().unwrap();
|
|
||||||
if let Some(param_type) = expected_type {
|
|
||||||
match param_type {
|
|
||||||
ParamType::Uint(_) => Ok(Token::Uint(U256::from(i as u64))),
|
|
||||||
ParamType::Int(_) => {
|
|
||||||
// Convert to I256 - in a real implementation, we would handle this properly
|
|
||||||
// For now, we'll just use U256 for both types
|
|
||||||
Ok(Token::Uint(U256::from(i as u64)))
|
|
||||||
},
|
|
||||||
_ => Err(format!("Expected {}, got integer", param_type))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Default to Uint256 if no type info
|
|
||||||
Ok(Token::Uint(U256::from(i as u64)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Handle strings and addresses
|
|
||||||
v if v.is_string() => {
|
|
||||||
let s = v.to_string();
|
|
||||||
if let Some(param_type) = expected_type {
|
|
||||||
match param_type {
|
|
||||||
ParamType::Address => {
|
|
||||||
match Address::from_str(&s) {
|
|
||||||
Ok(addr) => Ok(Token::Address(addr)),
|
|
||||||
Err(e) => Err(format!("Invalid address format: {}", e))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ParamType::String => Ok(Token::String(s)),
|
|
||||||
ParamType::Bytes => {
|
|
||||||
// Handle hex string conversion to bytes
|
|
||||||
if s.starts_with("0x") {
|
|
||||||
match ethers::utils::hex::decode(&s[2..]) {
|
|
||||||
Ok(bytes) => Ok(Token::Bytes(bytes)),
|
|
||||||
Err(e) => Err(format!("Invalid hex string: {}", e))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Token::Bytes(s.as_bytes().to_vec()))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => Err(format!("Expected {}, got string", param_type))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Try to detect type from string format
|
|
||||||
if s.starts_with("0x") && s.len() == 42 {
|
|
||||||
// Likely an address
|
|
||||||
match Address::from_str(&s) {
|
|
||||||
Ok(addr) => Ok(Token::Address(addr)),
|
|
||||||
Err(_) => Ok(Token::String(s))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Token::String(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Handle booleans
|
|
||||||
v if v.is_bool() => {
|
|
||||||
let b = v.as_bool().unwrap();
|
|
||||||
if let Some(param_type) = expected_type {
|
|
||||||
if matches!(param_type, ParamType::Bool) {
|
|
||||||
Ok(Token::Bool(b))
|
|
||||||
} else {
|
|
||||||
Err(format!("Expected {}, got boolean", param_type))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Token::Bool(b))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Handle arrays
|
|
||||||
v if v.is_array() => {
|
|
||||||
let arr = v.clone().into_array().unwrap();
|
|
||||||
if let Some(ParamType::Array(inner_type)) = expected_type {
|
|
||||||
let mut tokens = Vec::new();
|
|
||||||
for item in arr.iter() {
|
|
||||||
match convert_rhai_to_token(item, Some(inner_type)) {
|
|
||||||
Ok(token) => tokens.push(token),
|
|
||||||
Err(e) => return Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Token::Array(tokens))
|
|
||||||
} else {
|
|
||||||
Err("Array type mismatch or no type information available".to_string())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Handle other types or return error
|
|
||||||
_ => Err(format!("Unsupported Rhai type: {:?}", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate and convert arguments based on function ABI
|
|
||||||
pub fn prepare_function_arguments(
|
|
||||||
abi: &Abi,
|
|
||||||
function_name: &str,
|
|
||||||
args: &Array
|
|
||||||
) -> Result<Vec<Token>, String> {
|
|
||||||
// Get the function from the ABI
|
|
||||||
let function = abi.function(function_name)
|
|
||||||
.map_err(|e| format!("Function not found in ABI: {}", e))?;
|
|
||||||
|
|
||||||
// Check if number of arguments matches
|
|
||||||
if function.inputs.len() != args.len() {
|
|
||||||
return Err(format!(
|
|
||||||
"Wrong number of arguments for function '{}': expected {}, got {}",
|
|
||||||
function_name, function.inputs.len(), args.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert each argument according to the expected type
|
|
||||||
let mut tokens = Vec::new();
|
|
||||||
for (i, (param, arg)) in function.inputs.iter().zip(args.iter()).enumerate() {
|
|
||||||
match convert_rhai_to_token(arg, Some(¶m.kind)) {
|
|
||||||
Ok(token) => tokens.push(token),
|
|
||||||
Err(e) => return Err(format!("Error converting argument {}: {}", i, e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert ethers Token to Rhai Dynamic value
|
|
||||||
pub fn convert_token_to_rhai(tokens: &[Token]) -> Dynamic {
|
|
||||||
if tokens.is_empty() {
|
|
||||||
return Dynamic::UNIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's only one return value, return it directly
|
|
||||||
if tokens.len() == 1 {
|
|
||||||
return token_to_dynamic(&tokens[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are multiple return values, return them as an array
|
|
||||||
let mut array = Array::new();
|
|
||||||
for token in tokens {
|
|
||||||
array.push(token_to_dynamic(token));
|
|
||||||
}
|
|
||||||
Dynamic::from(array)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a single token to a Dynamic value
|
|
||||||
pub fn token_to_dynamic(token: &Token) -> Dynamic {
|
|
||||||
match token {
|
|
||||||
Token::Address(addr) => Dynamic::from(format!("{:?}", addr)),
|
|
||||||
Token::Bytes(bytes) => Dynamic::from(ethers::utils::hex::encode(bytes)),
|
|
||||||
Token::Int(i) => Dynamic::from(i.to_string()),
|
|
||||||
Token::Uint(u) => Dynamic::from(u.to_string()),
|
|
||||||
Token::Bool(b) => Dynamic::from(*b),
|
|
||||||
Token::String(s) => Dynamic::from(s.clone()),
|
|
||||||
Token::Array(arr) => {
|
|
||||||
let mut rhai_arr = Array::new();
|
|
||||||
for item in arr {
|
|
||||||
rhai_arr.push(token_to_dynamic(item));
|
|
||||||
}
|
|
||||||
Dynamic::from(rhai_arr)
|
|
||||||
},
|
|
||||||
Token::Tuple(tuple) => {
|
|
||||||
let mut rhai_arr = Array::new();
|
|
||||||
for item in tuple {
|
|
||||||
rhai_arr.push(token_to_dynamic(item));
|
|
||||||
}
|
|
||||||
Dynamic::from(rhai_arr)
|
|
||||||
},
|
|
||||||
// Handle other token types
|
|
||||||
_ => {
|
|
||||||
log::warn!("Unsupported token type: {:?}", token);
|
|
||||||
Dynamic::UNIT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
//! Ethereum wallet functionality
|
|
||||||
//!
|
|
||||||
//! This module provides functionality for creating and managing Ethereum wallets
|
|
||||||
//! and interacting with smart contracts on EVM-based blockchains.
|
|
||||||
//!
|
|
||||||
//! The module is organized into several components:
|
|
||||||
//! - `wallet.rs`: Core Ethereum wallet implementation
|
|
||||||
//! - `networks.rs`: Network registry and configuration
|
|
||||||
//! - `provider.rs`: Provider creation and management
|
|
||||||
//! - `transaction.rs`: Transaction-related functionality
|
|
||||||
//! - `storage.rs`: Wallet storage functionality
|
|
||||||
//! - `contract.rs`: Smart contract interaction functionality
|
|
||||||
|
|
||||||
mod wallet;
|
|
||||||
mod provider;
|
|
||||||
mod transaction;
|
|
||||||
mod storage;
|
|
||||||
mod contract;
|
|
||||||
mod contract_utils;
|
|
||||||
pub mod networks;
|
|
||||||
#[cfg(test)]
|
|
||||||
pub mod tests;
|
|
||||||
|
|
||||||
// Re-export public types and functions
|
|
||||||
pub use wallet::EthereumWallet;
|
|
||||||
pub use networks::NetworkConfig;
|
|
||||||
|
|
||||||
// Re-export wallet creation functions
|
|
||||||
pub use storage::{
|
|
||||||
create_ethereum_wallet_for_network,
|
|
||||||
create_peaq_wallet,
|
|
||||||
create_agung_wallet,
|
|
||||||
create_ethereum_wallet_from_name_for_network,
|
|
||||||
create_ethereum_wallet_from_name,
|
|
||||||
create_ethereum_wallet_from_private_key_for_network,
|
|
||||||
create_ethereum_wallet_from_private_key,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Re-export wallet management functions
|
|
||||||
pub use storage::{
|
|
||||||
get_current_ethereum_wallet_for_network,
|
|
||||||
get_current_peaq_wallet,
|
|
||||||
get_current_agung_wallet,
|
|
||||||
clear_ethereum_wallets,
|
|
||||||
clear_ethereum_wallets_for_network,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Re-export provider functions
|
|
||||||
pub use provider::{
|
|
||||||
create_provider,
|
|
||||||
create_gnosis_provider,
|
|
||||||
create_peaq_provider,
|
|
||||||
create_agung_provider,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Re-export transaction functions
|
|
||||||
pub use transaction::{
|
|
||||||
get_balance,
|
|
||||||
send_eth,
|
|
||||||
format_balance,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Re-export network registry functions
|
|
||||||
pub use networks::{
|
|
||||||
get_network_by_name,
|
|
||||||
get_proper_network_name,
|
|
||||||
list_network_names,
|
|
||||||
get_all_networks,
|
|
||||||
names,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Re-export contract functions
|
|
||||||
pub use contract::{
|
|
||||||
Contract,
|
|
||||||
load_abi_from_json,
|
|
||||||
call_read_function,
|
|
||||||
call_write_function,
|
|
||||||
estimate_gas,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Re-export contract utility functions
|
|
||||||
pub use contract_utils::{
|
|
||||||
convert_rhai_to_token,
|
|
||||||
prepare_function_arguments,
|
|
||||||
convert_token_to_rhai,
|
|
||||||
token_to_dynamic,
|
|
||||||
};
|
|
@ -1,102 +0,0 @@
|
|||||||
//! Ethereum network registry
|
|
||||||
//!
|
|
||||||
//! This module provides a centralized registry of Ethereum networks and utilities
|
|
||||||
//! to work with them.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
/// Configuration for an EVM-compatible network
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct NetworkConfig {
|
|
||||||
pub name: String,
|
|
||||||
pub chain_id: u64,
|
|
||||||
pub rpc_url: String,
|
|
||||||
pub explorer_url: String,
|
|
||||||
pub token_symbol: String,
|
|
||||||
pub decimals: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Network name constants
|
|
||||||
pub mod names {
|
|
||||||
pub const GNOSIS: &str = "Gnosis";
|
|
||||||
pub const PEAQ: &str = "Peaq";
|
|
||||||
pub const AGUNG: &str = "Agung";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the Gnosis Chain network configuration
|
|
||||||
pub fn gnosis() -> NetworkConfig {
|
|
||||||
NetworkConfig {
|
|
||||||
name: names::GNOSIS.to_string(),
|
|
||||||
chain_id: 100,
|
|
||||||
rpc_url: "https://rpc.gnosischain.com".to_string(),
|
|
||||||
explorer_url: "https://gnosisscan.io".to_string(),
|
|
||||||
token_symbol: "xDAI".to_string(),
|
|
||||||
decimals: 18,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the Peaq Network configuration
|
|
||||||
pub fn peaq() -> NetworkConfig {
|
|
||||||
NetworkConfig {
|
|
||||||
name: names::PEAQ.to_string(),
|
|
||||||
chain_id: 3338,
|
|
||||||
rpc_url: "https://peaq.api.onfinality.io/public".to_string(),
|
|
||||||
explorer_url: "https://peaq.subscan.io/".to_string(),
|
|
||||||
token_symbol: "PEAQ".to_string(),
|
|
||||||
decimals: 18,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the Agung Testnet configuration
|
|
||||||
pub fn agung() -> NetworkConfig {
|
|
||||||
NetworkConfig {
|
|
||||||
name: names::AGUNG.to_string(),
|
|
||||||
chain_id: 9990,
|
|
||||||
rpc_url: "https://wss-async.agung.peaq.network".to_string(),
|
|
||||||
explorer_url: "https://agung-testnet.subscan.io/".to_string(),
|
|
||||||
token_symbol: "AGNG".to_string(),
|
|
||||||
decimals: 18,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a network by its name (case-insensitive)
|
|
||||||
pub fn get_network_by_name(name: &str) -> Option<NetworkConfig> {
|
|
||||||
let name_lower = name.to_lowercase();
|
|
||||||
match name_lower.as_str() {
|
|
||||||
"gnosis" => Some(gnosis()),
|
|
||||||
"peaq" => Some(peaq()),
|
|
||||||
"agung" => Some(agung()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the proper capitalization of a network name
|
|
||||||
pub fn get_proper_network_name(name: &str) -> Option<&'static str> {
|
|
||||||
let name_lower = name.to_lowercase();
|
|
||||||
match name_lower.as_str() {
|
|
||||||
"gnosis" => Some(names::GNOSIS),
|
|
||||||
"peaq" => Some(names::PEAQ),
|
|
||||||
"agung" => Some(names::AGUNG),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a list of all supported network names
|
|
||||||
pub fn list_network_names() -> Vec<&'static str> {
|
|
||||||
vec![names::GNOSIS, names::PEAQ, names::AGUNG]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a map of all networks
|
|
||||||
pub fn get_all_networks() -> &'static HashMap<&'static str, NetworkConfig> {
|
|
||||||
static NETWORKS: OnceLock<HashMap<&'static str, NetworkConfig>> = OnceLock::new();
|
|
||||||
|
|
||||||
NETWORKS.get_or_init(|| {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
map.insert(names::GNOSIS, gnosis());
|
|
||||||
map.insert(names::PEAQ, peaq());
|
|
||||||
map.insert(names::AGUNG, agung());
|
|
||||||
map
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
//! Ethereum provider functionality.
|
|
||||||
|
|
||||||
use ethers::prelude::*;
|
|
||||||
|
|
||||||
use crate::hero_vault::error::CryptoError;
|
|
||||||
use super::networks::{self, NetworkConfig};
|
|
||||||
|
|
||||||
/// Creates a provider for a specific network.
|
|
||||||
pub fn create_provider(network: &NetworkConfig) -> Result<Provider<Http>, CryptoError> {
|
|
||||||
Provider::<Http>::try_from(network.rpc_url.as_str())
|
|
||||||
.map_err(|e| CryptoError::SerializationError(format!("Failed to create provider for {}: {}", network.name, e)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a provider for the Gnosis Chain.
|
|
||||||
pub fn create_gnosis_provider() -> Result<Provider<Http>, CryptoError> {
|
|
||||||
create_provider(&networks::gnosis())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a provider for the Peaq network.
|
|
||||||
pub fn create_peaq_provider() -> Result<Provider<Http>, CryptoError> {
|
|
||||||
create_provider(&networks::peaq())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a provider for the Agung testnet.
|
|
||||||
pub fn create_agung_provider() -> Result<Provider<Http>, CryptoError> {
|
|
||||||
create_provider(&networks::agung())
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
//! Ethereum wallet storage functionality.
|
|
||||||
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use crate::hero_vault::error::CryptoError;
|
|
||||||
use super::wallet::EthereumWallet;
|
|
||||||
use super::networks::{self, NetworkConfig};
|
|
||||||
|
|
||||||
/// Global storage for Ethereum wallets.
|
|
||||||
static ETH_WALLETS: Lazy<Mutex<HashMap<String, Vec<EthereumWallet>>>> = Lazy::new(|| {
|
|
||||||
Mutex::new(HashMap::new())
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Creates an Ethereum wallet from the currently selected keypair for a specific network.
|
|
||||||
pub fn create_ethereum_wallet_for_network(network: NetworkConfig) -> Result<EthereumWallet, CryptoError> {
|
|
||||||
// Get the currently selected keypair
|
|
||||||
let keypair = crate::hero_vault::keypair::get_selected_keypair()?;
|
|
||||||
|
|
||||||
// Create an Ethereum wallet from the keypair
|
|
||||||
let wallet = EthereumWallet::from_keypair(&keypair, network)?;
|
|
||||||
|
|
||||||
// Store the wallet
|
|
||||||
let mut wallets = ETH_WALLETS.lock().unwrap();
|
|
||||||
let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new);
|
|
||||||
network_wallets.push(wallet.clone());
|
|
||||||
|
|
||||||
Ok(wallet)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an Ethereum wallet from the currently selected keypair for the Peaq network.
|
|
||||||
pub fn create_peaq_wallet() -> Result<EthereumWallet, CryptoError> {
|
|
||||||
create_ethereum_wallet_for_network(networks::peaq())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an Ethereum wallet from the currently selected keypair for the Agung testnet.
|
|
||||||
pub fn create_agung_wallet() -> Result<EthereumWallet, CryptoError> {
|
|
||||||
create_ethereum_wallet_for_network(networks::agung())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the current Ethereum wallet for a specific network.
|
|
||||||
pub fn get_current_ethereum_wallet_for_network(network_name: &str) -> Result<EthereumWallet, CryptoError> {
|
|
||||||
let wallets = ETH_WALLETS.lock().unwrap();
|
|
||||||
|
|
||||||
let network_wallets = wallets.get(network_name).ok_or(CryptoError::NoKeypairSelected)?;
|
|
||||||
|
|
||||||
if network_wallets.is_empty() {
|
|
||||||
return Err(CryptoError::NoKeypairSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(network_wallets.last().unwrap().clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the current Ethereum wallet for the Peaq network.
|
|
||||||
pub fn get_current_peaq_wallet() -> Result<EthereumWallet, CryptoError> {
|
|
||||||
get_current_ethereum_wallet_for_network("Peaq")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the current Ethereum wallet for the Agung testnet.
|
|
||||||
pub fn get_current_agung_wallet() -> Result<EthereumWallet, CryptoError> {
|
|
||||||
get_current_ethereum_wallet_for_network("Agung")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears all Ethereum wallets.
|
|
||||||
pub fn clear_ethereum_wallets() {
|
|
||||||
let mut wallets = ETH_WALLETS.lock().unwrap();
|
|
||||||
wallets.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears Ethereum wallets for a specific network.
|
|
||||||
pub fn clear_ethereum_wallets_for_network(network_name: &str) {
|
|
||||||
let mut wallets = ETH_WALLETS.lock().unwrap();
|
|
||||||
wallets.remove(network_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an Ethereum wallet from a name and the currently selected keypair for a specific network.
|
|
||||||
pub fn create_ethereum_wallet_from_name_for_network(name: &str, network: NetworkConfig) -> Result<EthereumWallet, CryptoError> {
|
|
||||||
// Get the currently selected keypair
|
|
||||||
let keypair = crate::hero_vault::keypair::get_selected_keypair()?;
|
|
||||||
|
|
||||||
// Create an Ethereum wallet from the name and keypair
|
|
||||||
let wallet = EthereumWallet::from_name_and_keypair(name, &keypair, network)?;
|
|
||||||
|
|
||||||
// Store the wallet
|
|
||||||
let mut wallets = ETH_WALLETS.lock().unwrap();
|
|
||||||
let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new);
|
|
||||||
network_wallets.push(wallet.clone());
|
|
||||||
|
|
||||||
Ok(wallet)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an Ethereum wallet from a name and the currently selected keypair for the Gnosis network.
|
|
||||||
pub fn create_ethereum_wallet_from_name(name: &str) -> Result<EthereumWallet, CryptoError> {
|
|
||||||
create_ethereum_wallet_from_name_for_network(name, networks::gnosis())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an Ethereum wallet from a private key for a specific network.
|
|
||||||
pub fn create_ethereum_wallet_from_private_key_for_network(private_key: &str, network: NetworkConfig) -> Result<EthereumWallet, CryptoError> {
|
|
||||||
// Create an Ethereum wallet from the private key
|
|
||||||
let wallet = EthereumWallet::from_private_key(private_key, network)?;
|
|
||||||
|
|
||||||
// Store the wallet
|
|
||||||
let mut wallets = ETH_WALLETS.lock().unwrap();
|
|
||||||
let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new);
|
|
||||||
network_wallets.push(wallet.clone());
|
|
||||||
|
|
||||||
Ok(wallet)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an Ethereum wallet from a private key for the Gnosis network.
|
|
||||||
pub fn create_ethereum_wallet_from_private_key(private_key: &str) -> Result<EthereumWallet, CryptoError> {
|
|
||||||
create_ethereum_wallet_from_private_key_for_network(private_key, networks::gnosis())
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
//! Tests for smart contract argument handling functionality.
|
|
||||||
|
|
||||||
use ethers::types::Address;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::hero_vault::ethereum::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_contract_creation() {
|
|
||||||
// Create a simple ABI
|
|
||||||
let abi_json = r#"[
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getValue",
|
|
||||||
"outputs": [{"type": "uint256", "name": ""}],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [{"type": "uint256", "name": "newValue"}],
|
|
||||||
"name": "setValue",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
]"#;
|
|
||||||
|
|
||||||
// Parse the ABI
|
|
||||||
let abi = load_abi_from_json(abi_json).unwrap();
|
|
||||||
|
|
||||||
// Create a contract address
|
|
||||||
let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
|
|
||||||
|
|
||||||
// Create a network config
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
// Create a contract
|
|
||||||
let contract = Contract::new(address, abi, network);
|
|
||||||
|
|
||||||
// Verify the contract was created correctly
|
|
||||||
assert_eq!(contract.address, address);
|
|
||||||
assert_eq!(contract.network.name, "Gnosis");
|
|
||||||
|
|
||||||
// Verify the ABI contains the expected functions
|
|
||||||
assert!(contract.abi.function("getValue").is_ok());
|
|
||||||
assert!(contract.abi.function("setValue").is_ok());
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
//! Tests for smart contract functionality.
|
|
||||||
|
|
||||||
use ethers::types::Address;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::hero_vault::ethereum::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_contract_creation() {
|
|
||||||
// Create a simple ABI
|
|
||||||
let abi_json = r#"[
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getValue",
|
|
||||||
"outputs": [{"type": "uint256", "name": ""}],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [{"type": "uint256", "name": "newValue"}],
|
|
||||||
"name": "setValue",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
]"#;
|
|
||||||
|
|
||||||
// Parse the ABI
|
|
||||||
let abi = load_abi_from_json(abi_json).unwrap();
|
|
||||||
|
|
||||||
// Create a contract address
|
|
||||||
let address = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
|
|
||||||
|
|
||||||
// Create a network config
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
// Create a contract
|
|
||||||
let contract = Contract::new(address, abi, network);
|
|
||||||
|
|
||||||
// Verify the contract was created correctly
|
|
||||||
assert_eq!(contract.address, address);
|
|
||||||
assert_eq!(contract.network.name, "Gnosis");
|
|
||||||
|
|
||||||
// Verify the ABI contains the expected functions
|
|
||||||
assert!(contract.abi.function("getValue").is_ok());
|
|
||||||
assert!(contract.abi.function("setValue").is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_contract_from_address_string() {
|
|
||||||
// Create a simple ABI
|
|
||||||
let abi_json = r#"[
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "getValue",
|
|
||||||
"outputs": [{"type": "uint256", "name": ""}],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
]"#;
|
|
||||||
|
|
||||||
// Parse the ABI
|
|
||||||
let abi = load_abi_from_json(abi_json).unwrap();
|
|
||||||
|
|
||||||
// Create a network config
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
// Create a contract from an address string
|
|
||||||
let address_str = "0x1234567890123456789012345678901234567890";
|
|
||||||
let contract = Contract::from_address_string(address_str, abi, network).unwrap();
|
|
||||||
|
|
||||||
// Verify the contract was created correctly
|
|
||||||
assert_eq!(contract.address, Address::from_str(address_str).unwrap());
|
|
||||||
|
|
||||||
// Test with an invalid address
|
|
||||||
let invalid_address = "0xinvalid";
|
|
||||||
let result = Contract::from_address_string(invalid_address, contract.abi.clone(), contract.network.clone());
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: We can't easily test the actual contract calls in unit tests without mocking
|
|
||||||
// the provider, which would be complex. These would be better tested in integration tests
|
|
||||||
// with a local blockchain or testnet.
|
|
@ -1,7 +0,0 @@
|
|||||||
//! Tests for Ethereum functionality.
|
|
||||||
|
|
||||||
mod wallet_tests;
|
|
||||||
mod network_tests;
|
|
||||||
mod transaction_tests;
|
|
||||||
mod contract_tests;
|
|
||||||
mod contract_args_tests;
|
|
@ -1,74 +0,0 @@
|
|||||||
//! Tests for Ethereum network functionality.
|
|
||||||
|
|
||||||
use crate::hero_vault::ethereum::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_network_config() {
|
|
||||||
let gnosis = networks::gnosis();
|
|
||||||
assert_eq!(gnosis.name, "Gnosis");
|
|
||||||
assert_eq!(gnosis.chain_id, 100);
|
|
||||||
assert_eq!(gnosis.token_symbol, "xDAI");
|
|
||||||
|
|
||||||
let peaq = networks::peaq();
|
|
||||||
assert_eq!(peaq.name, "Peaq");
|
|
||||||
assert_eq!(peaq.chain_id, 3338);
|
|
||||||
assert_eq!(peaq.token_symbol, "PEAQ");
|
|
||||||
|
|
||||||
let agung = networks::agung();
|
|
||||||
assert_eq!(agung.name, "Agung");
|
|
||||||
assert_eq!(agung.chain_id, 9990);
|
|
||||||
assert_eq!(agung.token_symbol, "AGNG");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_network_registry() {
|
|
||||||
let network_names = networks::list_network_names();
|
|
||||||
assert!(network_names.iter().any(|&name| name == "Gnosis"));
|
|
||||||
assert!(network_names.iter().any(|&name| name == "Peaq"));
|
|
||||||
assert!(network_names.iter().any(|&name| name == "Agung"));
|
|
||||||
|
|
||||||
let gnosis_proper = networks::get_proper_network_name("gnosis");
|
|
||||||
assert_eq!(gnosis_proper, Some("Gnosis"));
|
|
||||||
|
|
||||||
let peaq_proper = networks::get_proper_network_name("peaq");
|
|
||||||
assert_eq!(peaq_proper, Some("Peaq"));
|
|
||||||
|
|
||||||
let agung_proper = networks::get_proper_network_name("agung");
|
|
||||||
assert_eq!(agung_proper, Some("Agung"));
|
|
||||||
|
|
||||||
let unknown = networks::get_proper_network_name("unknown");
|
|
||||||
assert_eq!(unknown, None);
|
|
||||||
|
|
||||||
let gnosis_config = networks::get_network_by_name("Gnosis");
|
|
||||||
assert!(gnosis_config.is_some());
|
|
||||||
assert_eq!(gnosis_config.unwrap().chain_id, 100);
|
|
||||||
|
|
||||||
let unknown_config = networks::get_network_by_name("Unknown");
|
|
||||||
assert!(unknown_config.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_provider() {
|
|
||||||
let gnosis = networks::gnosis();
|
|
||||||
let peaq = networks::peaq();
|
|
||||||
let agung = networks::agung();
|
|
||||||
|
|
||||||
// Create providers
|
|
||||||
let gnosis_provider = create_provider(&gnosis);
|
|
||||||
let peaq_provider = create_provider(&peaq);
|
|
||||||
let agung_provider = create_provider(&agung);
|
|
||||||
|
|
||||||
// They should all succeed
|
|
||||||
assert!(gnosis_provider.is_ok());
|
|
||||||
assert!(peaq_provider.is_ok());
|
|
||||||
assert!(agung_provider.is_ok());
|
|
||||||
|
|
||||||
// The convenience functions should also work
|
|
||||||
let gnosis_provider2 = create_gnosis_provider();
|
|
||||||
let peaq_provider2 = create_peaq_provider();
|
|
||||||
let agung_provider2 = create_agung_provider();
|
|
||||||
|
|
||||||
assert!(gnosis_provider2.is_ok());
|
|
||||||
assert!(peaq_provider2.is_ok());
|
|
||||||
assert!(agung_provider2.is_ok());
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
//! Tests for Ethereum transaction functionality.
|
|
||||||
|
|
||||||
use crate::hero_vault::ethereum::*;
|
|
||||||
use crate::hero_vault::keypair::KeyPair;
|
|
||||||
use ethers::types::U256;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_format_balance() {
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
// Test with 0
|
|
||||||
let balance = U256::from(0);
|
|
||||||
let formatted = format_balance(balance, &network);
|
|
||||||
assert_eq!(formatted, "0.000000 xDAI");
|
|
||||||
|
|
||||||
// Test with 1 wei
|
|
||||||
let balance = U256::from(1);
|
|
||||||
let formatted = format_balance(balance, &network);
|
|
||||||
assert_eq!(formatted, "0.000000 xDAI");
|
|
||||||
|
|
||||||
// Test with 1 gwei (10^9 wei)
|
|
||||||
let balance = U256::from(1_000_000_000u64);
|
|
||||||
let formatted = format_balance(balance, &network);
|
|
||||||
assert_eq!(formatted, "0.000000 xDAI");
|
|
||||||
|
|
||||||
// Test with 1 ETH (10^18 wei)
|
|
||||||
let balance = U256::from_dec_str("1000000000000000000").unwrap();
|
|
||||||
let formatted = format_balance(balance, &network);
|
|
||||||
assert_eq!(formatted, "1.000000 xDAI");
|
|
||||||
|
|
||||||
// Test with a larger amount
|
|
||||||
let balance = U256::from_dec_str("123456789000000000000").unwrap();
|
|
||||||
let formatted = format_balance(balance, &network);
|
|
||||||
assert_eq!(formatted, "123.456789 xDAI");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_balance() {
|
|
||||||
// This is a mock test since we can't actually query the blockchain in a unit test
|
|
||||||
// In a real test, we would use a local blockchain or mock the provider
|
|
||||||
|
|
||||||
// Create a provider
|
|
||||||
let network = networks::gnosis();
|
|
||||||
let provider_result = create_provider(&network);
|
|
||||||
|
|
||||||
// The provider creation should succeed
|
|
||||||
assert!(provider_result.is_ok());
|
|
||||||
|
|
||||||
// We can't actually test get_balance without a blockchain
|
|
||||||
// In a real test, we would mock the provider and test the function
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_send_eth() {
|
|
||||||
// This is a mock test since we can't actually send transactions in a unit test
|
|
||||||
// In a real test, we would use a local blockchain or mock the provider
|
|
||||||
|
|
||||||
// Create a wallet
|
|
||||||
let keypair = KeyPair::new("test_keypair6");
|
|
||||||
let network = networks::gnosis();
|
|
||||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
|
||||||
|
|
||||||
// Create a provider
|
|
||||||
let provider_result = create_provider(&network);
|
|
||||||
assert!(provider_result.is_ok());
|
|
||||||
|
|
||||||
// We can't actually test send_eth without a blockchain
|
|
||||||
// In a real test, we would mock the provider and test the function
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
//! Tests for Ethereum wallet functionality.
|
|
||||||
|
|
||||||
use crate::hero_vault::ethereum::*;
|
|
||||||
use crate::hero_vault::keypair::KeyPair;
|
|
||||||
use ethers::utils::hex;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ethereum_wallet_from_keypair() {
|
|
||||||
let keypair = KeyPair::new("test_keypair");
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(wallet.network.name, "Gnosis");
|
|
||||||
assert_eq!(wallet.network.chain_id, 100);
|
|
||||||
|
|
||||||
// The address should be a valid Ethereum address
|
|
||||||
assert!(wallet.address_string().starts_with("0x"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ethereum_wallet_from_name_and_keypair() {
|
|
||||||
let keypair = KeyPair::new("test_keypair2");
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
let wallet = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(wallet.network.name, "Gnosis");
|
|
||||||
assert_eq!(wallet.network.chain_id, 100);
|
|
||||||
|
|
||||||
// The address should be a valid Ethereum address
|
|
||||||
assert!(wallet.address_string().starts_with("0x"));
|
|
||||||
|
|
||||||
// Creating another wallet with the same name and keypair should yield the same address
|
|
||||||
let wallet2 = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap();
|
|
||||||
assert_eq!(wallet.address, wallet2.address);
|
|
||||||
|
|
||||||
// Creating a wallet with a different name should yield a different address
|
|
||||||
let wallet3 = EthereumWallet::from_name_and_keypair("test2", &keypair, network.clone()).unwrap();
|
|
||||||
assert_ne!(wallet.address, wallet3.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ethereum_wallet_from_private_key() {
|
|
||||||
let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
let wallet = EthereumWallet::from_private_key(private_key, network.clone()).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(wallet.network.name, "Gnosis");
|
|
||||||
assert_eq!(wallet.network.chain_id, 100);
|
|
||||||
|
|
||||||
// The address should be a valid Ethereum address
|
|
||||||
assert!(wallet.address_string().starts_with("0x"));
|
|
||||||
|
|
||||||
// The address should be deterministic based on the private key
|
|
||||||
let wallet2 = EthereumWallet::from_private_key(private_key, network.clone()).unwrap();
|
|
||||||
assert_eq!(wallet.address, wallet2.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wallet_management() {
|
|
||||||
// Clear any existing wallets
|
|
||||||
clear_ethereum_wallets();
|
|
||||||
|
|
||||||
// Create a key space and keypair
|
|
||||||
crate::hero_vault::keypair::create_space("test_space").unwrap();
|
|
||||||
crate::hero_vault::keypair::create_keypair("test_keypair3").unwrap();
|
|
||||||
|
|
||||||
// Create wallets for different networks
|
|
||||||
let gnosis_wallet = create_ethereum_wallet_for_network(networks::gnosis()).unwrap();
|
|
||||||
let peaq_wallet = create_ethereum_wallet_for_network(networks::peaq()).unwrap();
|
|
||||||
let agung_wallet = create_ethereum_wallet_for_network(networks::agung()).unwrap();
|
|
||||||
|
|
||||||
// Get the current wallets
|
|
||||||
let current_gnosis = get_current_ethereum_wallet_for_network("Gnosis").unwrap();
|
|
||||||
let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap();
|
|
||||||
let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap();
|
|
||||||
|
|
||||||
// Check that they match
|
|
||||||
assert_eq!(gnosis_wallet.address, current_gnosis.address);
|
|
||||||
assert_eq!(peaq_wallet.address, current_peaq.address);
|
|
||||||
assert_eq!(agung_wallet.address, current_agung.address);
|
|
||||||
|
|
||||||
// Clear wallets for a specific network
|
|
||||||
clear_ethereum_wallets_for_network("Gnosis");
|
|
||||||
|
|
||||||
// Check that the wallet is gone
|
|
||||||
let result = get_current_ethereum_wallet_for_network("Gnosis");
|
|
||||||
assert!(result.is_err());
|
|
||||||
|
|
||||||
// But the others should still be there
|
|
||||||
let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap();
|
|
||||||
let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap();
|
|
||||||
assert_eq!(peaq_wallet.address, current_peaq.address);
|
|
||||||
assert_eq!(agung_wallet.address, current_agung.address);
|
|
||||||
|
|
||||||
// Clear all wallets
|
|
||||||
clear_ethereum_wallets();
|
|
||||||
|
|
||||||
// Check that all wallets are gone
|
|
||||||
let result1 = get_current_ethereum_wallet_for_network("Gnosis");
|
|
||||||
let result2 = get_current_ethereum_wallet_for_network("Peaq");
|
|
||||||
let result3 = get_current_ethereum_wallet_for_network("Agung");
|
|
||||||
assert!(result1.is_err());
|
|
||||||
assert!(result2.is_err());
|
|
||||||
assert!(result3.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sign_message() {
|
|
||||||
let keypair = KeyPair::new("test_keypair4");
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
|
||||||
|
|
||||||
// Create a tokio runtime for the async test
|
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
||||||
|
|
||||||
// Sign a message
|
|
||||||
let message = b"Hello, world!";
|
|
||||||
let signature = rt.block_on(wallet.sign_message(message)).unwrap();
|
|
||||||
|
|
||||||
// The signature should be a non-empty string
|
|
||||||
assert!(!signature.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_private_key_hex() {
|
|
||||||
let keypair = KeyPair::new("test_keypair5");
|
|
||||||
let network = networks::gnosis();
|
|
||||||
|
|
||||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
|
||||||
|
|
||||||
// Get the private key as hex
|
|
||||||
let private_key_hex = wallet.private_key_hex();
|
|
||||||
|
|
||||||
// The private key should be a 64-character hex string (32 bytes)
|
|
||||||
assert_eq!(private_key_hex.len(), 64);
|
|
||||||
|
|
||||||
// It should be possible to parse it as hex
|
|
||||||
let _bytes = hex::decode(private_key_hex).unwrap();
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
//! Ethereum transaction functionality.
|
|
||||||
|
|
||||||
use ethers::prelude::*;
|
|
||||||
|
|
||||||
use crate::hero_vault::error::CryptoError;
|
|
||||||
use super::wallet::EthereumWallet;
|
|
||||||
use super::networks::NetworkConfig;
|
|
||||||
|
|
||||||
/// Formats a token balance for display.
|
|
||||||
pub fn format_balance(balance: U256, network: &NetworkConfig) -> String {
|
|
||||||
let wei = balance.as_u128();
|
|
||||||
let divisor = 10u128.pow(network.decimals as u32) as f64;
|
|
||||||
let token = wei as f64 / divisor;
|
|
||||||
|
|
||||||
// Display with the appropriate number of decimal places
|
|
||||||
let display_decimals = std::cmp::min(6, network.decimals);
|
|
||||||
|
|
||||||
format!("{:.*} {}", display_decimals as usize, token, network.token_symbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the balance of an Ethereum address.
|
|
||||||
pub async fn get_balance(provider: &Provider<Http>, address: Address) -> Result<U256, CryptoError> {
|
|
||||||
provider.get_balance(address, None)
|
|
||||||
.await
|
|
||||||
.map_err(|e| CryptoError::SerializationError(format!("Failed to get balance: {}", e)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends Ethereum from one address to another.
|
|
||||||
pub async fn send_eth(
|
|
||||||
wallet: &EthereumWallet,
|
|
||||||
provider: &Provider<Http>,
|
|
||||||
to: Address,
|
|
||||||
amount: U256,
|
|
||||||
) -> Result<H256, CryptoError> {
|
|
||||||
// Create a client with the wallet
|
|
||||||
let client = SignerMiddleware::new(
|
|
||||||
provider.clone(),
|
|
||||||
wallet.wallet.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create the transaction
|
|
||||||
let tx = TransactionRequest::new()
|
|
||||||
.to(to)
|
|
||||||
.value(amount)
|
|
||||||
.gas(21000);
|
|
||||||
|
|
||||||
// Send the transaction
|
|
||||||
let pending_tx = client.send_transaction(tx, None)
|
|
||||||
.await
|
|
||||||
.map_err(|e| CryptoError::SerializationError(format!("Failed to send transaction: {}", e)))?;
|
|
||||||
|
|
||||||
// Return the transaction hash instead of waiting for the receipt
|
|
||||||
Ok(pending_tx.tx_hash())
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
//! Ethereum wallet implementation.
|
|
||||||
|
|
||||||
use ethers::prelude::*;
|
|
||||||
use ethers::signers::{LocalWallet, Signer, Wallet};
|
|
||||||
use ethers::utils::hex;
|
|
||||||
use k256::ecdsa::SigningKey;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use sha2::{Sha256, Digest};
|
|
||||||
|
|
||||||
use crate::hero_vault::error::CryptoError;
|
|
||||||
use crate::hero_vault::keypair::KeyPair;
|
|
||||||
use super::networks::NetworkConfig;
|
|
||||||
|
|
||||||
/// An Ethereum wallet derived from a keypair.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct EthereumWallet {
|
|
||||||
pub address: Address,
|
|
||||||
pub wallet: Wallet<SigningKey>,
|
|
||||||
pub network: NetworkConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EthereumWallet {
|
|
||||||
/// Creates a new Ethereum wallet from a keypair for a specific network.
|
|
||||||
pub fn from_keypair(keypair: &KeyPair, network: NetworkConfig) -> Result<Self, CryptoError> {
|
|
||||||
// Get the private key bytes from the keypair
|
|
||||||
let private_key_bytes = keypair.signing_key.to_bytes();
|
|
||||||
|
|
||||||
// Convert to a hex string (without 0x prefix)
|
|
||||||
let private_key_hex = hex::encode(private_key_bytes);
|
|
||||||
|
|
||||||
// Create an Ethereum wallet from the private key
|
|
||||||
let wallet = LocalWallet::from_str(&private_key_hex)
|
|
||||||
.map_err(|_e| CryptoError::InvalidKeyLength)?
|
|
||||||
.with_chain_id(network.chain_id);
|
|
||||||
|
|
||||||
// Get the Ethereum address
|
|
||||||
let address = wallet.address();
|
|
||||||
|
|
||||||
Ok(EthereumWallet {
|
|
||||||
address,
|
|
||||||
wallet,
|
|
||||||
network,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new Ethereum wallet from a name and keypair (deterministic derivation) for a specific network.
|
|
||||||
pub fn from_name_and_keypair(name: &str, keypair: &KeyPair, network: NetworkConfig) -> Result<Self, CryptoError> {
|
|
||||||
// Get the private key bytes from the keypair
|
|
||||||
let private_key_bytes = keypair.signing_key.to_bytes();
|
|
||||||
|
|
||||||
// Create a deterministic seed by combining name and private key
|
|
||||||
let mut hasher = Sha256::default();
|
|
||||||
hasher.update(name.as_bytes());
|
|
||||||
hasher.update(&private_key_bytes);
|
|
||||||
let seed = hasher.finalize();
|
|
||||||
|
|
||||||
// Use the seed as a private key
|
|
||||||
let private_key_hex = hex::encode(seed);
|
|
||||||
|
|
||||||
// Create an Ethereum wallet from the derived private key
|
|
||||||
let wallet = LocalWallet::from_str(&private_key_hex)
|
|
||||||
.map_err(|_e| CryptoError::InvalidKeyLength)?
|
|
||||||
.with_chain_id(network.chain_id);
|
|
||||||
|
|
||||||
// Get the Ethereum address
|
|
||||||
let address = wallet.address();
|
|
||||||
|
|
||||||
Ok(EthereumWallet {
|
|
||||||
address,
|
|
||||||
wallet,
|
|
||||||
network,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new Ethereum wallet from a private key for a specific network.
|
|
||||||
pub fn from_private_key(private_key: &str, network: NetworkConfig) -> Result<Self, CryptoError> {
|
|
||||||
// Remove 0x prefix if present
|
|
||||||
let private_key_clean = private_key.trim_start_matches("0x");
|
|
||||||
|
|
||||||
// Create an Ethereum wallet from the private key
|
|
||||||
let wallet = LocalWallet::from_str(private_key_clean)
|
|
||||||
.map_err(|_e| CryptoError::InvalidKeyLength)?
|
|
||||||
.with_chain_id(network.chain_id);
|
|
||||||
|
|
||||||
// Get the Ethereum address
|
|
||||||
let address = wallet.address();
|
|
||||||
|
|
||||||
Ok(EthereumWallet {
|
|
||||||
address,
|
|
||||||
wallet,
|
|
||||||
network,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the Ethereum address as a string.
|
|
||||||
pub fn address_string(&self) -> String {
|
|
||||||
format!("{:?}", self.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signs a message with the Ethereum wallet.
|
|
||||||
pub async fn sign_message(&self, message: &[u8]) -> Result<String, CryptoError> {
|
|
||||||
let signature = self.wallet.sign_message(message)
|
|
||||||
.await
|
|
||||||
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(signature.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the private key as a hex string.
|
|
||||||
pub fn private_key_hex(&self) -> String {
|
|
||||||
let bytes = self.wallet.signer().to_bytes();
|
|
||||||
hex::encode(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
# Hero Vault Keypair Module
|
|
||||||
|
|
||||||
The Keypair module provides functionality for creating, managing, and using ECDSA keypairs for digital signatures and other cryptographic operations.
|
|
||||||
|
|
||||||
## Module Structure
|
|
||||||
|
|
||||||
The Keypair module is organized into:
|
|
||||||
|
|
||||||
- `implementation.rs` - Core implementation of the KeyPair and KeySpace types
|
|
||||||
- `mod.rs` - Module exports and public interface
|
|
||||||
|
|
||||||
## Key Types
|
|
||||||
|
|
||||||
### KeyPair
|
|
||||||
|
|
||||||
The `KeyPair` type represents an ECDSA keypair used for digital signatures and other cryptographic operations.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub struct KeyPair {
|
|
||||||
// Private fields
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyPair {
|
|
||||||
// Create a new random keypair
|
|
||||||
pub fn new() -> Result<Self, CryptoError>;
|
|
||||||
|
|
||||||
// Create a keypair from an existing private key
|
|
||||||
pub fn from_private_key(private_key: &[u8]) -> Result<Self, CryptoError>;
|
|
||||||
|
|
||||||
// Get the public key
|
|
||||||
pub fn public_key(&self) -> &[u8];
|
|
||||||
|
|
||||||
// Sign a message
|
|
||||||
pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError>;
|
|
||||||
|
|
||||||
// Verify a signature
|
|
||||||
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, CryptoError>;
|
|
||||||
|
|
||||||
// Derive an Ethereum address from the public key
|
|
||||||
pub fn to_ethereum_address(&self) -> Result<String, CryptoError>;
|
|
||||||
|
|
||||||
// Export the private key (should be used with caution)
|
|
||||||
pub fn export_private_key(&self) -> Result<Vec<u8>, CryptoError>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### KeySpace
|
|
||||||
|
|
||||||
The `KeySpace` type represents a secure container for multiple keypairs, which can be encrypted and stored on disk.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub struct KeySpace {
|
|
||||||
// Private fields
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeySpace {
|
|
||||||
// Create a new key space
|
|
||||||
pub fn new(name: &str, password: &str) -> Result<Self, CryptoError>;
|
|
||||||
|
|
||||||
// Load a key space from disk
|
|
||||||
pub fn load(name: &str, password: &str) -> Result<Self, CryptoError>;
|
|
||||||
|
|
||||||
// Save the key space to disk
|
|
||||||
pub fn save(&self) -> Result<(), CryptoError>;
|
|
||||||
|
|
||||||
// Create a new keypair in the key space
|
|
||||||
pub fn create_keypair(&mut self, name: &str, password: &str) -> Result<&KeyPair, CryptoError>;
|
|
||||||
|
|
||||||
// Select a keypair for use
|
|
||||||
pub fn select_keypair(&mut self, name: &str) -> Result<&KeyPair, CryptoError>;
|
|
||||||
|
|
||||||
// Get the currently selected keypair
|
|
||||||
pub fn current_keypair(&self) -> Result<&KeyPair, CryptoError>;
|
|
||||||
|
|
||||||
// List all keypairs in the key space
|
|
||||||
pub fn list_keypairs(&self) -> Result<Vec<String>, CryptoError>;
|
|
||||||
|
|
||||||
// Get a keypair by name
|
|
||||||
pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError>;
|
|
||||||
|
|
||||||
// Remove a keypair from the key space
|
|
||||||
pub fn remove_keypair(&mut self, name: &str) -> Result<(), CryptoError>;
|
|
||||||
|
|
||||||
// Rename a keypair
|
|
||||||
pub fn rename_keypair(&mut self, old_name: &str, new_name: &str) -> Result<(), CryptoError>;
|
|
||||||
|
|
||||||
// Get the name of the key space
|
|
||||||
pub fn name(&self) -> &str;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### Key Space Management
|
|
||||||
|
|
||||||
The module provides functionality for creating, loading, and managing key spaces:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a new key space
|
|
||||||
let mut space = KeySpace::new("my_space", "secure_password")?;
|
|
||||||
|
|
||||||
// Save the key space to disk
|
|
||||||
space.save()?;
|
|
||||||
|
|
||||||
// Load a key space from disk
|
|
||||||
let mut loaded_space = KeySpace::load("my_space", "secure_password")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Keypair Management
|
|
||||||
|
|
||||||
The module provides functionality for creating, selecting, and using keypairs:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a new keypair in the key space
|
|
||||||
let keypair = space.create_keypair("my_keypair", "secure_password")?;
|
|
||||||
|
|
||||||
// Select a keypair for use
|
|
||||||
space.select_keypair("my_keypair")?;
|
|
||||||
|
|
||||||
// Get the currently selected keypair
|
|
||||||
let current = space.current_keypair()?;
|
|
||||||
|
|
||||||
// List all keypairs in the key space
|
|
||||||
let keypairs = space.list_keypairs()?;
|
|
||||||
|
|
||||||
// Get a keypair by name
|
|
||||||
let keypair = space.get_keypair("my_keypair")?;
|
|
||||||
|
|
||||||
// Remove a keypair from the key space
|
|
||||||
space.remove_keypair("my_keypair")?;
|
|
||||||
|
|
||||||
// Rename a keypair
|
|
||||||
space.rename_keypair("my_keypair", "new_name")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Digital Signatures
|
|
||||||
|
|
||||||
The module provides functionality for signing and verifying messages using ECDSA:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Sign a message using the selected keypair
|
|
||||||
let keypair = space.current_keypair()?;
|
|
||||||
let signature = keypair.sign("This is a message to sign".as_bytes())?;
|
|
||||||
|
|
||||||
// Verify a signature
|
|
||||||
let is_valid = keypair.verify("This is a message to sign".as_bytes(), &signature)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ethereum Address Derivation
|
|
||||||
|
|
||||||
The module provides functionality for deriving Ethereum addresses from keypairs:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Derive an Ethereum address from a keypair
|
|
||||||
let keypair = space.current_keypair()?;
|
|
||||||
let address = keypair.to_ethereum_address()?;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password
|
|
||||||
- Private keys are never stored in plaintext
|
|
||||||
- The module uses secure random number generation for key creation
|
|
||||||
- All cryptographic operations use well-established libraries and algorithms
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
The module uses the `CryptoError` type for handling errors that can occur during keypair operations:
|
|
||||||
|
|
||||||
- `InvalidKeyLength` - Invalid key length
|
|
||||||
- `SignatureFormatError` - Signature format error
|
|
||||||
- `KeypairAlreadyExists` - Keypair already exists
|
|
||||||
- `KeypairNotFound` - Keypair not found
|
|
||||||
- `NoActiveSpace` - No active key space
|
|
||||||
- `NoKeypairSelected` - No keypair selected
|
|
||||||
- `SerializationError` - Serialization error
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
For examples of how to use the Keypair module, see the `examples/hero_vault` directory, particularly:
|
|
||||||
|
|
||||||
- `example.rhai` - Basic example demonstrating key management and signing
|
|
||||||
- `advanced_example.rhai` - Advanced example with error handling
|
|
||||||
- `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk
|
|
||||||
- `load_existing_space.rhai` - Shows how to load a previously created key space
|
|
@ -1,467 +0,0 @@
|
|||||||
//! Implementation of keypair functionality.
|
|
||||||
|
|
||||||
use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature};
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use sha2::{Sha256, Digest};
|
|
||||||
|
|
||||||
use crate::hero_vault::error::CryptoError;
|
|
||||||
|
|
||||||
/// A keypair for signing and verifying messages.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct KeyPair {
|
|
||||||
pub name: String,
|
|
||||||
#[serde(with = "verifying_key_serde")]
|
|
||||||
pub verifying_key: VerifyingKey,
|
|
||||||
#[serde(with = "signing_key_serde")]
|
|
||||||
pub signing_key: SigningKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialization helpers for VerifyingKey
|
|
||||||
mod verifying_key_serde {
|
|
||||||
use super::*;
|
|
||||||
use serde::{Serializer, Deserializer};
|
|
||||||
use serde::de::{self, Visitor};
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
pub fn serialize<S>(key: &VerifyingKey, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let bytes = key.to_sec1_bytes();
|
|
||||||
// Convert bytes to a Vec<u8> and serialize that instead
|
|
||||||
serializer.collect_seq(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VerifyingKeyVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for VerifyingKeyVisitor {
|
|
||||||
type Value = VerifyingKey;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
formatter.write_str("a byte array representing a verifying key")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
VerifyingKey::from_sec1_bytes(v).map_err(|e| {
|
|
||||||
log::error!("Error deserializing verifying key: {:?}", e);
|
|
||||||
E::custom(format!("invalid verifying key: {:?}", e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: de::SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
// Collect all bytes from the sequence
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
while let Some(byte) = seq.next_element()? {
|
|
||||||
bytes.push(byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
VerifyingKey::from_sec1_bytes(&bytes).map_err(|e| {
|
|
||||||
log::error!("Error deserializing verifying key from seq: {:?}", e);
|
|
||||||
de::Error::custom(format!("invalid verifying key from seq: {:?}", e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<VerifyingKey, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
// Try to deserialize as bytes first, then as a sequence
|
|
||||||
deserializer.deserialize_any(VerifyingKeyVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialization helpers for SigningKey
|
|
||||||
mod signing_key_serde {
|
|
||||||
use super::*;
|
|
||||||
use serde::{Serializer, Deserializer};
|
|
||||||
use serde::de::{self, Visitor};
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
pub fn serialize<S>(key: &SigningKey, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let bytes = key.to_bytes();
|
|
||||||
// Convert bytes to a Vec<u8> and serialize that instead
|
|
||||||
serializer.collect_seq(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SigningKeyVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for SigningKeyVisitor {
|
|
||||||
type Value = SigningKey;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
formatter.write_str("a byte array representing a signing key")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
SigningKey::from_bytes(v.into()).map_err(|e| {
|
|
||||||
log::error!("Error deserializing signing key: {:?}", e);
|
|
||||||
E::custom(format!("invalid signing key: {:?}", e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: de::SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
// Collect all bytes from the sequence
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
while let Some(byte) = seq.next_element()? {
|
|
||||||
bytes.push(byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
SigningKey::from_bytes(bytes.as_slice().into()).map_err(|e| {
|
|
||||||
log::error!("Error deserializing signing key from seq: {:?}", e);
|
|
||||||
de::Error::custom(format!("invalid signing key from seq: {:?}", e))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<SigningKey, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
// Try to deserialize as bytes first, then as a sequence
|
|
||||||
deserializer.deserialize_any(SigningKeyVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyPair {
|
|
||||||
/// Creates a new keypair with the given name.
|
|
||||||
pub fn new(name: &str) -> Self {
|
|
||||||
let signing_key = SigningKey::random(&mut OsRng);
|
|
||||||
let verifying_key = VerifyingKey::from(&signing_key);
|
|
||||||
|
|
||||||
KeyPair {
|
|
||||||
name: name.to_string(),
|
|
||||||
verifying_key,
|
|
||||||
signing_key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the public key bytes.
|
|
||||||
pub fn pub_key(&self) -> Vec<u8> {
|
|
||||||
self.verifying_key.to_sec1_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Derives a public key from a private key.
|
|
||||||
pub fn pub_key_from_private(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
let signing_key = SigningKey::from_bytes(private_key.into())
|
|
||||||
.map_err(|_| CryptoError::InvalidKeyLength)?;
|
|
||||||
let verifying_key = VerifyingKey::from(&signing_key);
|
|
||||||
Ok(verifying_key.to_sec1_bytes().to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signs a message.
|
|
||||||
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
|
|
||||||
let signature: Signature = self.signing_key.sign(message);
|
|
||||||
signature.to_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verifies a message signature.
|
|
||||||
pub fn verify(&self, message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
|
|
||||||
let signature = Signature::from_bytes(signature_bytes.into())
|
|
||||||
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
|
|
||||||
|
|
||||||
match self.verifying_key.verify(message, &signature) {
|
|
||||||
Ok(_) => Ok(true),
|
|
||||||
Err(_) => Ok(false), // Verification failed, but operation was successful
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verifies a message signature using only a public key.
|
|
||||||
pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
|
|
||||||
let verifying_key = VerifyingKey::from_sec1_bytes(public_key)
|
|
||||||
.map_err(|_| CryptoError::InvalidKeyLength)?;
|
|
||||||
|
|
||||||
let signature = Signature::from_bytes(signature_bytes.into())
|
|
||||||
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
|
|
||||||
|
|
||||||
match verifying_key.verify(message, &signature) {
|
|
||||||
Ok(_) => Ok(true),
|
|
||||||
Err(_) => Ok(false), // Verification failed, but operation was successful
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypts a message using the recipient's public key.
|
|
||||||
/// This implements ECIES (Elliptic Curve Integrated Encryption Scheme):
|
|
||||||
/// 1. Generate an ephemeral keypair
|
|
||||||
/// 2. Derive a shared secret using ECDH
|
|
||||||
/// 3. Derive encryption key from the shared secret
|
|
||||||
/// 4. Encrypt the message using symmetric encryption
|
|
||||||
/// 5. Return the ephemeral public key and the ciphertext
|
|
||||||
pub fn encrypt_asymmetric(&self, recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
// Parse recipient's public key
|
|
||||||
let recipient_key = VerifyingKey::from_sec1_bytes(recipient_public_key)
|
|
||||||
.map_err(|_| CryptoError::InvalidKeyLength)?;
|
|
||||||
|
|
||||||
// Generate ephemeral keypair
|
|
||||||
let ephemeral_signing_key = SigningKey::random(&mut OsRng);
|
|
||||||
let ephemeral_public_key = VerifyingKey::from(&ephemeral_signing_key);
|
|
||||||
|
|
||||||
// Derive shared secret (this is a simplified ECDH)
|
|
||||||
// In a real implementation, we would use proper ECDH, but for this example:
|
|
||||||
let shared_point = recipient_key.to_encoded_point(false);
|
|
||||||
let shared_secret = {
|
|
||||||
let mut hasher = Sha256::default();
|
|
||||||
hasher.update(ephemeral_signing_key.to_bytes());
|
|
||||||
hasher.update(shared_point.as_bytes());
|
|
||||||
hasher.finalize().to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Encrypt the message using the derived key
|
|
||||||
let ciphertext = crate::hero_vault::symmetric::encrypt_with_key(&shared_secret, message)
|
|
||||||
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
|
|
||||||
|
|
||||||
// Format: ephemeral_public_key || ciphertext
|
|
||||||
let mut result = ephemeral_public_key.to_sec1_bytes().to_vec();
|
|
||||||
result.extend_from_slice(&ciphertext);
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts a message using the recipient's private key.
|
|
||||||
/// This is the counterpart to encrypt_asymmetric.
|
|
||||||
pub fn decrypt_asymmetric(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
// The first 33 or 65 bytes (depending on compression) are the ephemeral public key
|
|
||||||
// For simplicity, we'll assume uncompressed keys (65 bytes)
|
|
||||||
if ciphertext.len() <= 65 {
|
|
||||||
return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract ephemeral public key and actual ciphertext
|
|
||||||
let ephemeral_public_key = &ciphertext[..65];
|
|
||||||
let actual_ciphertext = &ciphertext[65..];
|
|
||||||
|
|
||||||
// Parse ephemeral public key
|
|
||||||
let sender_key = VerifyingKey::from_sec1_bytes(ephemeral_public_key)
|
|
||||||
.map_err(|_| CryptoError::InvalidKeyLength)?;
|
|
||||||
|
|
||||||
// Derive shared secret (simplified ECDH)
|
|
||||||
let shared_point = sender_key.to_encoded_point(false);
|
|
||||||
let shared_secret = {
|
|
||||||
let mut hasher = Sha256::default();
|
|
||||||
hasher.update(self.signing_key.to_bytes());
|
|
||||||
hasher.update(shared_point.as_bytes());
|
|
||||||
hasher.finalize().to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Decrypt the message using the derived key
|
|
||||||
crate::hero_vault::symmetric::decrypt_with_key(&shared_secret, actual_ciphertext)
|
|
||||||
.map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A collection of keypairs.
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
||||||
pub struct KeySpace {
|
|
||||||
pub name: String,
|
|
||||||
pub keypairs: HashMap<String, KeyPair>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeySpace {
|
|
||||||
/// Creates a new key space with the given name.
|
|
||||||
pub fn new(name: &str) -> Self {
|
|
||||||
KeySpace {
|
|
||||||
name: name.to_string(),
|
|
||||||
keypairs: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new keypair to the space.
|
|
||||||
pub fn add_keypair(&mut self, name: &str) -> Result<(), CryptoError> {
|
|
||||||
if self.keypairs.contains_key(name) {
|
|
||||||
return Err(CryptoError::KeypairAlreadyExists(name.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let keypair = KeyPair::new(name);
|
|
||||||
self.keypairs.insert(name.to_string(), keypair);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a keypair by name.
|
|
||||||
pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError> {
|
|
||||||
self.keypairs.get(name).ok_or(CryptoError::KeypairNotFound(name.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lists all keypair names in the space.
|
|
||||||
pub fn list_keypairs(&self) -> Vec<String> {
|
|
||||||
self.keypairs.keys().cloned().collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Session state for the current key space and selected keypair.
|
|
||||||
pub struct Session {
|
|
||||||
pub current_space: Option<KeySpace>,
|
|
||||||
pub selected_keypair: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Session {
|
|
||||||
fn default() -> Self {
|
|
||||||
Session {
|
|
||||||
current_space: None,
|
|
||||||
selected_keypair: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Global session state.
|
|
||||||
static SESSION: Lazy<Mutex<Session>> = Lazy::new(|| {
|
|
||||||
Mutex::new(Session::default())
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Creates a new key space with the given name.
|
|
||||||
pub fn create_space(name: &str) -> Result<(), CryptoError> {
|
|
||||||
let mut session = SESSION.lock().unwrap();
|
|
||||||
|
|
||||||
// Create a new space
|
|
||||||
let space = KeySpace::new(name);
|
|
||||||
|
|
||||||
// Set as current space
|
|
||||||
session.current_space = Some(space);
|
|
||||||
session.selected_keypair = None;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the current key space.
|
|
||||||
pub fn set_current_space(space: KeySpace) -> Result<(), CryptoError> {
|
|
||||||
let mut session = SESSION.lock().unwrap();
|
|
||||||
session.current_space = Some(space);
|
|
||||||
session.selected_keypair = None;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the current key space.
|
|
||||||
pub fn get_current_space() -> Result<KeySpace, CryptoError> {
|
|
||||||
let session = SESSION.lock().unwrap();
|
|
||||||
session.current_space.clone().ok_or(CryptoError::NoActiveSpace)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the current session (logout).
|
|
||||||
pub fn clear_session() {
|
|
||||||
let mut session = SESSION.lock().unwrap();
|
|
||||||
session.current_space = None;
|
|
||||||
session.selected_keypair = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new keypair in the current space.
|
|
||||||
pub fn create_keypair(name: &str) -> Result<(), CryptoError> {
|
|
||||||
let mut session = SESSION.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(ref mut space) = session.current_space {
|
|
||||||
if space.keypairs.contains_key(name) {
|
|
||||||
return Err(CryptoError::KeypairAlreadyExists(name.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let keypair = KeyPair::new(name);
|
|
||||||
space.keypairs.insert(name.to_string(), keypair);
|
|
||||||
|
|
||||||
// Automatically select the new keypair
|
|
||||||
session.selected_keypair = Some(name.to_string());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(CryptoError::NoActiveSpace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Selects a keypair for use.
|
|
||||||
pub fn select_keypair(name: &str) -> Result<(), CryptoError> {
|
|
||||||
let mut session = SESSION.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(ref space) = session.current_space {
|
|
||||||
if !space.keypairs.contains_key(name) {
|
|
||||||
return Err(CryptoError::KeypairNotFound(name.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
session.selected_keypair = Some(name.to_string());
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(CryptoError::NoActiveSpace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the currently selected keypair.
|
|
||||||
pub fn get_selected_keypair() -> Result<KeyPair, CryptoError> {
|
|
||||||
let session = SESSION.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(ref space) = session.current_space {
|
|
||||||
if let Some(ref keypair_name) = session.selected_keypair {
|
|
||||||
if let Some(keypair) = space.keypairs.get(keypair_name) {
|
|
||||||
return Ok(keypair.clone());
|
|
||||||
}
|
|
||||||
return Err(CryptoError::KeypairNotFound(keypair_name.clone()));
|
|
||||||
}
|
|
||||||
return Err(CryptoError::NoKeypairSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(CryptoError::NoActiveSpace)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lists all keypair names in the current space.
|
|
||||||
pub fn list_keypairs() -> Result<Vec<String>, CryptoError> {
|
|
||||||
let session = SESSION.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(ref space) = session.current_space {
|
|
||||||
Ok(space.keypairs.keys().cloned().collect())
|
|
||||||
} else {
|
|
||||||
Err(CryptoError::NoActiveSpace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the public key of the selected keypair.
|
|
||||||
pub fn keypair_pub_key() -> Result<Vec<u8>, CryptoError> {
|
|
||||||
let keypair = get_selected_keypair()?;
|
|
||||||
Ok(keypair.pub_key())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Derives a public key from a private key.
|
|
||||||
pub fn derive_public_key(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
KeyPair::pub_key_from_private(private_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signs a message with the selected keypair.
|
|
||||||
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
let keypair = get_selected_keypair()?;
|
|
||||||
Ok(keypair.sign(message))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verifies a message signature with the selected keypair.
|
|
||||||
pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
|
|
||||||
let keypair = get_selected_keypair()?;
|
|
||||||
keypair.verify(message, signature_bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verifies a message signature with a public key.
|
|
||||||
pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
|
|
||||||
KeyPair::verify_with_public_key(public_key, message, signature_bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypts a message for a recipient using their public key.
|
|
||||||
pub fn encrypt_asymmetric(recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
let keypair = get_selected_keypair()?;
|
|
||||||
keypair.encrypt_asymmetric(recipient_public_key, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts a message that was encrypted with the current keypair's public key.
|
|
||||||
pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
let keypair = get_selected_keypair()?;
|
|
||||||
keypair.decrypt_asymmetric(ciphertext)
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
//! Key pair management functionality
|
|
||||||
//!
|
|
||||||
//! This module provides functionality for creating and managing ECDSA key pairs.
|
|
||||||
|
|
||||||
mod implementation;
|
|
||||||
|
|
||||||
// Re-export public types and functions
|
|
||||||
pub use implementation::{
|
|
||||||
KeyPair, KeySpace,
|
|
||||||
create_space, set_current_space, get_current_space, clear_session,
|
|
||||||
create_keypair, select_keypair, get_selected_keypair, list_keypairs,
|
|
||||||
keypair_pub_key, derive_public_key, keypair_sign, keypair_verify,
|
|
||||||
verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric
|
|
||||||
};
|
|
@ -1,167 +0,0 @@
|
|||||||
# Hero Vault Key-Value Store Module
|
|
||||||
|
|
||||||
The Key-Value Store (KVS) module provides an encrypted key-value store for securely storing sensitive data.
|
|
||||||
|
|
||||||
## Module Structure
|
|
||||||
|
|
||||||
The KVS module is organized into:
|
|
||||||
|
|
||||||
- `store.rs` - Core implementation of the key-value store
|
|
||||||
- `error.rs` - Error types specific to the KVS module
|
|
||||||
- `mod.rs` - Module exports and public interface
|
|
||||||
|
|
||||||
## Key Types
|
|
||||||
|
|
||||||
### KvStore
|
|
||||||
|
|
||||||
The `KvStore` type represents an encrypted key-value store:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub struct KvStore {
|
|
||||||
// Private fields
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KvStore {
|
|
||||||
// Create a new store
|
|
||||||
pub fn new(name: &str, password: &str) -> Result<Self, CryptoError>;
|
|
||||||
|
|
||||||
// Load a store from disk
|
|
||||||
pub fn load(name: &str, password: &str) -> Result<Self, CryptoError>;
|
|
||||||
|
|
||||||
// Save the store to disk
|
|
||||||
pub fn save(&self) -> Result<(), CryptoError>;
|
|
||||||
|
|
||||||
// Set a value
|
|
||||||
pub fn set(&mut self, key: &str, value: &str) -> Result<(), CryptoError>;
|
|
||||||
|
|
||||||
// Get a value
|
|
||||||
pub fn get(&self, key: &str) -> Result<Option<String>, CryptoError>;
|
|
||||||
|
|
||||||
// Delete a value
|
|
||||||
pub fn delete(&mut self, key: &str) -> Result<(), CryptoError>;
|
|
||||||
|
|
||||||
// Check if a key exists
|
|
||||||
pub fn has(&self, key: &str) -> Result<bool, CryptoError>;
|
|
||||||
|
|
||||||
// List all keys
|
|
||||||
pub fn keys(&self) -> Result<Vec<String>, CryptoError>;
|
|
||||||
|
|
||||||
// Clear all values
|
|
||||||
pub fn clear(&mut self) -> Result<(), CryptoError>;
|
|
||||||
|
|
||||||
// Get the name of the store
|
|
||||||
pub fn name(&self) -> &str;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### Store Management
|
|
||||||
|
|
||||||
The module provides functionality for creating, loading, and managing key-value stores:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a new store
|
|
||||||
let mut store = KvStore::new("my_store", "secure_password")?;
|
|
||||||
|
|
||||||
// Save the store to disk
|
|
||||||
store.save()?;
|
|
||||||
|
|
||||||
// Load a store from disk
|
|
||||||
let mut loaded_store = KvStore::load("my_store", "secure_password")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Value Management
|
|
||||||
|
|
||||||
The module provides functionality for managing values in the store:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Set a value
|
|
||||||
store.set("api_key", "secret_api_key")?;
|
|
||||||
|
|
||||||
// Get a value
|
|
||||||
let api_key = store.get("api_key")?;
|
|
||||||
|
|
||||||
// Delete a value
|
|
||||||
store.delete("api_key")?;
|
|
||||||
|
|
||||||
// Check if a key exists
|
|
||||||
let exists = store.has("api_key")?;
|
|
||||||
|
|
||||||
// List all keys
|
|
||||||
let keys = store.keys()?;
|
|
||||||
|
|
||||||
// Clear all values
|
|
||||||
store.clear()?;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
### Encryption
|
|
||||||
|
|
||||||
The KVS module uses the Symmetric Encryption module to encrypt all values stored in the key-value store. This ensures that sensitive data is protected at rest.
|
|
||||||
|
|
||||||
The encryption process:
|
|
||||||
|
|
||||||
1. A master key is derived from the provided password using PBKDF2
|
|
||||||
2. Each value is encrypted using ChaCha20Poly1305 with a unique key derived from the master key and the value's key
|
|
||||||
3. The encrypted values are stored in a JSON file on disk
|
|
||||||
|
|
||||||
### Storage Format
|
|
||||||
|
|
||||||
The key-value store is stored in a JSON file with the following structure:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "my_store",
|
|
||||||
"salt": "base64-encoded-salt",
|
|
||||||
"values": {
|
|
||||||
"key1": "base64-encoded-encrypted-value",
|
|
||||||
"key2": "base64-encoded-encrypted-value",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The file is stored in the `~/.hero-vault/stores/` directory by default.
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
- Use strong passwords to protect the key-value store
|
|
||||||
- The security of the store depends on the strength of the password
|
|
||||||
- Consider the security implications of storing sensitive data on disk
|
|
||||||
- Regularly backup the store to prevent data loss
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
The module uses the `CryptoError` type for handling errors that can occur during key-value store operations:
|
|
||||||
|
|
||||||
- `EncryptionFailed` - Encryption failed
|
|
||||||
- `DecryptionFailed` - Decryption failed
|
|
||||||
- `SerializationError` - Serialization error
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
For examples of how to use the KVS module, see the `examples/hero_vault` directory. While there may not be specific examples for the KVS module, the general pattern of usage is similar to the key space management examples.
|
|
||||||
|
|
||||||
A basic usage example:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a new store
|
|
||||||
let mut store = KvStore::new("my_store", "secure_password")?;
|
|
||||||
|
|
||||||
// Set some values
|
|
||||||
store.set("api_key", "secret_api_key")?;
|
|
||||||
store.set("access_token", "secret_access_token")?;
|
|
||||||
|
|
||||||
// Save the store to disk
|
|
||||||
store.save()?;
|
|
||||||
|
|
||||||
// Later, load the store
|
|
||||||
let loaded_store = KvStore::load("my_store", "secure_password")?;
|
|
||||||
|
|
||||||
// Get a value
|
|
||||||
let api_key = loaded_store.get("api_key")?;
|
|
||||||
println!("API Key: {}", api_key.unwrap_or_default());
|
|
||||||
```
|
|
@ -1,66 +0,0 @@
|
|||||||
//! Error types for the key-value store.
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Errors that can occur when using the key-value store.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum KvsError {
|
|
||||||
/// I/O error
|
|
||||||
#[error("I/O error: {0}")]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
|
|
||||||
/// Key not found
|
|
||||||
#[error("Key not found: {0}")]
|
|
||||||
KeyNotFound(String),
|
|
||||||
|
|
||||||
/// Store not found
|
|
||||||
#[error("Store not found: {0}")]
|
|
||||||
StoreNotFound(String),
|
|
||||||
|
|
||||||
/// Serialization error
|
|
||||||
#[error("Serialization error: {0}")]
|
|
||||||
Serialization(String),
|
|
||||||
|
|
||||||
/// Deserialization error
|
|
||||||
#[error("Deserialization error: {0}")]
|
|
||||||
Deserialization(String),
|
|
||||||
|
|
||||||
/// Encryption error
|
|
||||||
#[error("Encryption error: {0}")]
|
|
||||||
Encryption(String),
|
|
||||||
|
|
||||||
/// Decryption error
|
|
||||||
#[error("Decryption error: {0}")]
|
|
||||||
Decryption(String),
|
|
||||||
|
|
||||||
/// Other error
|
|
||||||
#[error("Error: {0}")]
|
|
||||||
Other(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_json::Error> for KvsError {
|
|
||||||
fn from(err: serde_json::Error) -> Self {
|
|
||||||
KvsError::Serialization(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<KvsError> for crate::hero_vault::error::CryptoError {
|
|
||||||
fn from(err: KvsError) -> Self {
|
|
||||||
crate::hero_vault::error::CryptoError::SerializationError(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<crate::hero_vault::error::CryptoError> for KvsError {
|
|
||||||
fn from(err: crate::hero_vault::error::CryptoError) -> Self {
|
|
||||||
match err {
|
|
||||||
crate::hero_vault::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg),
|
|
||||||
crate::hero_vault::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg),
|
|
||||||
crate::hero_vault::error::CryptoError::SerializationError(msg) => KvsError::Serialization(msg),
|
|
||||||
_ => KvsError::Other(err.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Result type for key-value store operations.
|
|
||||||
pub type Result<T> = std::result::Result<T, KvsError>;
|
|
@ -1,14 +0,0 @@
|
|||||||
//! Key-Value Store functionality
|
|
||||||
//!
|
|
||||||
//! This module provides a simple key-value store with encryption support.
|
|
||||||
|
|
||||||
mod error;
|
|
||||||
mod store;
|
|
||||||
|
|
||||||
// Re-export public types and functions
|
|
||||||
pub use error::KvsError;
|
|
||||||
pub use store::{
|
|
||||||
KvStore, KvPair,
|
|
||||||
create_store, open_store, delete_store,
|
|
||||||
list_stores, get_store_path
|
|
||||||
};
|
|
@ -1,362 +0,0 @@
|
|||||||
//! Implementation of a simple key-value store using the filesystem.
|
|
||||||
|
|
||||||
use crate::hero_vault::kvs::error::{KvsError, Result};
|
|
||||||
use crate::hero_vault::symmetric;
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
/// A key-value pair.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct KvPair {
|
|
||||||
pub key: String,
|
|
||||||
pub value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A simple key-value store.
|
|
||||||
///
|
|
||||||
/// This implementation uses the filesystem to store key-value pairs.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct KvStore {
|
|
||||||
/// The name of the store
|
|
||||||
name: String,
|
|
||||||
/// The path to the store file
|
|
||||||
path: PathBuf,
|
|
||||||
/// In-memory cache of the store data
|
|
||||||
data: Arc<Mutex<HashMap<String, String>>>,
|
|
||||||
/// Whether the store is encrypted
|
|
||||||
encrypted: bool,
|
|
||||||
/// The password for encryption (if encrypted)
|
|
||||||
password: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the path to the key-value store directory.
|
|
||||||
pub fn get_store_path() -> PathBuf {
|
|
||||||
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
|
|
||||||
home_dir.join(".hero-vault").join("kvs")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new key-value store with the given name.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `name` - The name of the store
|
|
||||||
/// * `encrypted` - Whether to encrypt the store
|
|
||||||
/// * `password` - The password for encryption (required if encrypted is true)
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A new `KvStore` instance
|
|
||||||
pub fn create_store(name: &str, encrypted: bool, password: Option<&str>) -> Result<KvStore> {
|
|
||||||
// Check if password is provided when encryption is enabled
|
|
||||||
if encrypted && password.is_none() {
|
|
||||||
return Err(KvsError::Other("Password required for encrypted store".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the store directory if it doesn't exist
|
|
||||||
let store_dir = get_store_path();
|
|
||||||
if !store_dir.exists() {
|
|
||||||
fs::create_dir_all(&store_dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the store file path
|
|
||||||
let store_path = store_dir.join(format!("{}.json", name));
|
|
||||||
|
|
||||||
// Create an empty store
|
|
||||||
let store = KvStore {
|
|
||||||
name: name.to_string(),
|
|
||||||
path: store_path,
|
|
||||||
data: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
encrypted,
|
|
||||||
password: password.map(|s| s.to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save the empty store
|
|
||||||
store.save()?;
|
|
||||||
|
|
||||||
Ok(store)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Opens an existing key-value store with the given name.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `name` - The name of the store
|
|
||||||
/// * `password` - The password for decryption (required if the store is encrypted)
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// The opened `KvStore` instance
|
|
||||||
pub fn open_store(name: &str, password: Option<&str>) -> Result<KvStore> {
|
|
||||||
// Get the store file path
|
|
||||||
let store_dir = get_store_path();
|
|
||||||
let store_path = store_dir.join(format!("{}.json", name));
|
|
||||||
|
|
||||||
// Check if the store exists
|
|
||||||
if !store_path.exists() {
|
|
||||||
return Err(KvsError::StoreNotFound(name.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the store file
|
|
||||||
let file_content = fs::read_to_string(&store_path)?;
|
|
||||||
|
|
||||||
// Check if the file is encrypted (simple heuristic)
|
|
||||||
let is_encrypted = !file_content.starts_with('{');
|
|
||||||
|
|
||||||
// If encrypted, we need a password
|
|
||||||
if is_encrypted && password.is_none() {
|
|
||||||
return Err(KvsError::Other("Password required for encrypted store".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the store data
|
|
||||||
let data: HashMap<String, String> = if is_encrypted {
|
|
||||||
// Decrypt the file content
|
|
||||||
let password = password.unwrap();
|
|
||||||
let encrypted_data: Vec<u8> = serde_json::from_str(&file_content)?;
|
|
||||||
let key = symmetric::derive_key_from_password(password);
|
|
||||||
let decrypted_data = symmetric::decrypt_symmetric(&key, &encrypted_data)?;
|
|
||||||
let decrypted_str = String::from_utf8(decrypted_data)
|
|
||||||
.map_err(|e| KvsError::Deserialization(e.to_string()))?;
|
|
||||||
serde_json::from_str(&decrypted_str)?
|
|
||||||
} else {
|
|
||||||
serde_json::from_str(&file_content)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the store
|
|
||||||
let store = KvStore {
|
|
||||||
name: name.to_string(),
|
|
||||||
path: store_path,
|
|
||||||
data: Arc::new(Mutex::new(data)),
|
|
||||||
encrypted: is_encrypted,
|
|
||||||
password: password.map(|s| s.to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(store)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes a key-value store with the given name.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `name` - The name of the store to delete
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// `Ok(())` if the operation was successful
|
|
||||||
pub fn delete_store(name: &str) -> Result<()> {
|
|
||||||
// Get the store file path
|
|
||||||
let store_dir = get_store_path();
|
|
||||||
let store_path = store_dir.join(format!("{}.json", name));
|
|
||||||
|
|
||||||
// Check if the store exists
|
|
||||||
if !store_path.exists() {
|
|
||||||
return Err(KvsError::StoreNotFound(name.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the store file
|
|
||||||
fs::remove_file(store_path)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lists all available key-value stores.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A vector of store names
|
|
||||||
pub fn list_stores() -> Result<Vec<String>> {
|
|
||||||
// Get the store directory
|
|
||||||
let store_dir = get_store_path();
|
|
||||||
if !store_dir.exists() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all JSON files in the directory
|
|
||||||
let mut stores = Vec::new();
|
|
||||||
for entry in fs::read_dir(store_dir)? {
|
|
||||||
let entry = entry?;
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_file() && path.extension().map_or(false, |ext| ext == "json") {
|
|
||||||
if let Some(name) = path.file_stem() {
|
|
||||||
if let Some(name_str) = name.to_str() {
|
|
||||||
stores.push(name_str.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(stores)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KvStore {
|
|
||||||
/// Saves the store to disk.
|
|
||||||
fn save(&self) -> Result<()> {
|
|
||||||
// Get the store data
|
|
||||||
let data = self.data.lock().unwrap();
|
|
||||||
|
|
||||||
// Serialize the data
|
|
||||||
let serialized = serde_json::to_string(&*data)?;
|
|
||||||
|
|
||||||
// Write to file
|
|
||||||
if self.encrypted {
|
|
||||||
if let Some(password) = &self.password {
|
|
||||||
// Encrypt the data
|
|
||||||
let key = symmetric::derive_key_from_password(password);
|
|
||||||
let encrypted_data = symmetric::encrypt_symmetric(&key, serialized.as_bytes())?;
|
|
||||||
let encrypted_json = serde_json::to_string(&encrypted_data)?;
|
|
||||||
fs::write(&self.path, encrypted_json)?;
|
|
||||||
} else {
|
|
||||||
return Err(KvsError::Other("Password required for encrypted store".to_string()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fs::write(&self.path, serialized)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a value with the given key.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `key` - The key to store the value under
|
|
||||||
/// * `value` - The value to store
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// `Ok(())` if the operation was successful
|
|
||||||
pub fn set<K, V>(&self, key: K, value: &V) -> Result<()>
|
|
||||||
where
|
|
||||||
K: ToString,
|
|
||||||
V: Serialize,
|
|
||||||
{
|
|
||||||
let key_str = key.to_string();
|
|
||||||
let serialized = serde_json::to_string(value)?;
|
|
||||||
|
|
||||||
// Update in-memory data
|
|
||||||
{
|
|
||||||
let mut data = self.data.lock().unwrap();
|
|
||||||
data.insert(key_str, serialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to disk
|
|
||||||
self.save()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves a value for the given key.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `key` - The key to retrieve the value for
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// The value if found, or `Err(KvsError::KeyNotFound)` if not found
|
|
||||||
pub fn get<K, V>(&self, key: K) -> Result<V>
|
|
||||||
where
|
|
||||||
K: ToString,
|
|
||||||
V: DeserializeOwned,
|
|
||||||
{
|
|
||||||
let key_str = key.to_string();
|
|
||||||
let data = self.data.lock().unwrap();
|
|
||||||
|
|
||||||
match data.get(&key_str) {
|
|
||||||
Some(serialized) => {
|
|
||||||
let value = serde_json::from_str(serialized)?;
|
|
||||||
Ok(value)
|
|
||||||
},
|
|
||||||
None => Err(KvsError::KeyNotFound(key_str)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes a value for the given key.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `key` - The key to delete
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// `Ok(())` if the operation was successful
|
|
||||||
pub fn delete<K>(&self, key: K) -> Result<()>
|
|
||||||
where
|
|
||||||
K: ToString,
|
|
||||||
{
|
|
||||||
let key_str = key.to_string();
|
|
||||||
|
|
||||||
// Update in-memory data
|
|
||||||
{
|
|
||||||
let mut data = self.data.lock().unwrap();
|
|
||||||
if data.remove(&key_str).is_none() {
|
|
||||||
return Err(KvsError::KeyNotFound(key_str));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to disk
|
|
||||||
self.save()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a key exists in the store.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `key` - The key to check
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// `true` if the key exists, `false` otherwise
|
|
||||||
pub fn contains<K>(&self, key: K) -> Result<bool>
|
|
||||||
where
|
|
||||||
K: ToString,
|
|
||||||
{
|
|
||||||
let key_str = key.to_string();
|
|
||||||
let data = self.data.lock().unwrap();
|
|
||||||
|
|
||||||
Ok(data.contains_key(&key_str))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lists all keys in the store.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A vector of keys as strings
|
|
||||||
pub fn keys(&self) -> Result<Vec<String>> {
|
|
||||||
let data = self.data.lock().unwrap();
|
|
||||||
|
|
||||||
Ok(data.keys().cloned().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears all key-value pairs from the store.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// `Ok(())` if the operation was successful
|
|
||||||
pub fn clear(&self) -> Result<()> {
|
|
||||||
// Update in-memory data
|
|
||||||
{
|
|
||||||
let mut data = self.data.lock().unwrap();
|
|
||||||
data.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to disk
|
|
||||||
self.save()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the name of the store.
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets whether the store is encrypted.
|
|
||||||
pub fn is_encrypted(&self) -> bool {
|
|
||||||
self.encrypted
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
//! Hero Vault: Cryptographic functionality for SAL
|
|
||||||
//!
|
|
||||||
//! This module provides cryptographic operations including:
|
|
||||||
//! - Key space management (creation, loading, encryption, decryption)
|
|
||||||
//! - Key pair management (ECDSA)
|
|
||||||
//! - Digital signatures (signing and verification)
|
|
||||||
//! - Symmetric encryption (ChaCha20Poly1305)
|
|
||||||
//! - Ethereum wallet functionality
|
|
||||||
//! - Key-value store with encryption
|
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
pub mod keypair;
|
|
||||||
pub mod symmetric;
|
|
||||||
pub mod ethereum;
|
|
||||||
pub mod kvs;
|
|
||||||
|
|
||||||
// Re-export common types for convenience
|
|
||||||
pub use error::CryptoError;
|
|
||||||
pub use keypair::{KeyPair, KeySpace};
|
|
@ -1,98 +0,0 @@
|
|||||||
# Hero Vault Symmetric Encryption Module
|
|
||||||
|
|
||||||
The Symmetric Encryption module provides functionality for symmetric encryption and decryption using the ChaCha20Poly1305 algorithm.
|
|
||||||
|
|
||||||
## Module Structure
|
|
||||||
|
|
||||||
The Symmetric Encryption module is organized into:
|
|
||||||
|
|
||||||
- `implementation.rs` - Core implementation of symmetric encryption functionality
|
|
||||||
- `mod.rs` - Module exports and public interface
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### Key Generation
|
|
||||||
|
|
||||||
The module provides functionality for generating secure symmetric keys:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Generate a new symmetric key
|
|
||||||
let key = generate_key()?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Encryption
|
|
||||||
|
|
||||||
The module provides functionality for encrypting data using ChaCha20Poly1305:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Encrypt data
|
|
||||||
let encrypted = encrypt(&key, "This is a secret message")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Decryption
|
|
||||||
|
|
||||||
The module provides functionality for decrypting data encrypted with ChaCha20Poly1305:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Decrypt data
|
|
||||||
let decrypted = decrypt(&key, &encrypted)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Password-Based Key Derivation
|
|
||||||
|
|
||||||
The module provides functionality for deriving encryption keys from passwords:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Derive a key from a password
|
|
||||||
let key = derive_key_from_password(password, salt)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
### ChaCha20Poly1305
|
|
||||||
|
|
||||||
The module uses the ChaCha20Poly1305 authenticated encryption with associated data (AEAD) algorithm, which provides both confidentiality and integrity protection.
|
|
||||||
|
|
||||||
ChaCha20 is a stream cipher designed by Daniel J. Bernstein, which is combined with the Poly1305 message authentication code to provide authenticated encryption.
|
|
||||||
|
|
||||||
Key features of ChaCha20Poly1305:
|
|
||||||
|
|
||||||
- 256-bit key
|
|
||||||
- 96-bit nonce (used once)
|
|
||||||
- Authentication tag to verify integrity
|
|
||||||
- High performance on modern processors
|
|
||||||
- Resistance to timing attacks
|
|
||||||
|
|
||||||
### Key Derivation
|
|
||||||
|
|
||||||
For password-based encryption, the module uses the PBKDF2 (Password-Based Key Derivation Function 2) algorithm to derive encryption keys from passwords.
|
|
||||||
|
|
||||||
Key features of PBKDF2:
|
|
||||||
|
|
||||||
- Configurable iteration count to increase computational cost
|
|
||||||
- Salt to prevent rainbow table attacks
|
|
||||||
- Configurable output key length
|
|
||||||
- Uses HMAC-SHA256 as the underlying pseudorandom function
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
- Always use a unique key for each encryption operation
|
|
||||||
- Never reuse nonces with the same key
|
|
||||||
- Store keys securely
|
|
||||||
- Use strong passwords for password-based encryption
|
|
||||||
- Consider the security implications of storing encrypted data
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
The module uses the `CryptoError` type for handling errors that can occur during symmetric encryption operations:
|
|
||||||
|
|
||||||
- `InvalidKeyLength` - Invalid key length
|
|
||||||
- `EncryptionFailed` - Encryption failed
|
|
||||||
- `DecryptionFailed` - Decryption failed
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
For examples of how to use the Symmetric Encryption module, see the `examples/hero_vault` directory, particularly:
|
|
||||||
|
|
||||||
- `example.rhai` - Basic example demonstrating symmetric encryption
|
|
||||||
- `advanced_example.rhai` - Advanced example with error handling
|
|
@ -1,266 +0,0 @@
|
|||||||
//! Implementation of symmetric encryption functionality.
|
|
||||||
|
|
||||||
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce};
|
|
||||||
use chacha20poly1305::aead::Aead;
|
|
||||||
use rand::{rngs::OsRng, RngCore};
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use sha2::{Sha256, Digest};
|
|
||||||
|
|
||||||
use crate::hero_vault::error::CryptoError;
|
|
||||||
use crate::hero_vault::keypair::KeySpace;
|
|
||||||
|
|
||||||
/// The size of the nonce in bytes.
|
|
||||||
const NONCE_SIZE: usize = 12;
|
|
||||||
|
|
||||||
/// Generates a random 32-byte symmetric key.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A 32-byte array containing the random key.
|
|
||||||
pub fn generate_symmetric_key() -> [u8; 32] {
|
|
||||||
let mut key = [0u8; 32];
|
|
||||||
OsRng.fill_bytes(&mut key);
|
|
||||||
key
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Derives a 32-byte key from a password.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `password` - The password to derive the key from.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A 32-byte array containing the derived key.
|
|
||||||
pub fn derive_key_from_password(password: &str) -> [u8; 32] {
|
|
||||||
let mut hasher = Sha256::default();
|
|
||||||
hasher.update(password.as_bytes());
|
|
||||||
let result = hasher.finalize();
|
|
||||||
|
|
||||||
let mut key = [0u8; 32];
|
|
||||||
key.copy_from_slice(&result);
|
|
||||||
key
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypts data using ChaCha20Poly1305 with an internally generated nonce.
|
|
||||||
///
|
|
||||||
/// The nonce is appended to the ciphertext so it can be extracted during decryption.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `key` - The encryption key (should be 32 bytes).
|
|
||||||
/// * `message` - The message to encrypt.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended.
|
|
||||||
/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
|
|
||||||
/// * `Err(CryptoError::EncryptionFailed)` if encryption fails.
|
|
||||||
pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
// Create cipher
|
|
||||||
let cipher = ChaCha20Poly1305::new_from_slice(key)
|
|
||||||
.map_err(|_| CryptoError::InvalidKeyLength)?;
|
|
||||||
|
|
||||||
// Generate random nonce
|
|
||||||
let mut nonce_bytes = [0u8; NONCE_SIZE];
|
|
||||||
OsRng.fill_bytes(&mut nonce_bytes);
|
|
||||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
|
||||||
|
|
||||||
// Encrypt message
|
|
||||||
let ciphertext = cipher.encrypt(nonce, message)
|
|
||||||
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
|
|
||||||
|
|
||||||
// Append nonce to ciphertext
|
|
||||||
let mut result = ciphertext;
|
|
||||||
result.extend_from_slice(&nonce_bytes);
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts data using ChaCha20Poly1305, extracting the nonce from the ciphertext.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `key` - The decryption key (should be 32 bytes).
|
|
||||||
/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Ok(Vec<u8>)` containing the decrypted message.
|
|
||||||
/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
|
|
||||||
/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short.
|
|
||||||
pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
// Check if ciphertext is long enough to contain a nonce
|
|
||||||
if ciphertext_with_nonce.len() <= NONCE_SIZE {
|
|
||||||
return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract nonce from the end of ciphertext
|
|
||||||
let ciphertext_len = ciphertext_with_nonce.len() - NONCE_SIZE;
|
|
||||||
let ciphertext = &ciphertext_with_nonce[0..ciphertext_len];
|
|
||||||
let nonce_bytes = &ciphertext_with_nonce[ciphertext_len..];
|
|
||||||
|
|
||||||
// Create cipher
|
|
||||||
let cipher = ChaCha20Poly1305::new_from_slice(key)
|
|
||||||
.map_err(|_| CryptoError::InvalidKeyLength)?;
|
|
||||||
|
|
||||||
let nonce = Nonce::from_slice(nonce_bytes);
|
|
||||||
|
|
||||||
// Decrypt message
|
|
||||||
cipher.decrypt(nonce, ciphertext)
|
|
||||||
.map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypts data using a key directly (for internal use).
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `key` - The encryption key.
|
|
||||||
/// * `message` - The message to encrypt.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended.
|
|
||||||
/// * `Err(CryptoError)` if encryption fails.
|
|
||||||
pub fn encrypt_with_key(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
encrypt_symmetric(key, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts data using a key directly (for internal use).
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `key` - The decryption key.
|
|
||||||
/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Ok(Vec<u8>)` containing the decrypted message.
|
|
||||||
/// * `Err(CryptoError)` if decryption fails.
|
|
||||||
pub fn decrypt_with_key(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
||||||
decrypt_symmetric(key, ciphertext_with_nonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Metadata for an encrypted key space.
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct EncryptedKeySpaceMetadata {
|
|
||||||
pub name: String,
|
|
||||||
pub created_at: u64,
|
|
||||||
pub last_accessed: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An encrypted key space with metadata.
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct EncryptedKeySpace {
|
|
||||||
pub metadata: EncryptedKeySpaceMetadata,
|
|
||||||
pub encrypted_data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypts a key space using a password.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `space` - The key space to encrypt.
|
|
||||||
/// * `password` - The password to encrypt with.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Ok(EncryptedKeySpace)` containing the encrypted key space.
|
|
||||||
/// * `Err(CryptoError)` if encryption fails.
|
|
||||||
pub fn encrypt_key_space(space: &KeySpace, password: &str) -> Result<EncryptedKeySpace, CryptoError> {
|
|
||||||
// Serialize the key space
|
|
||||||
let serialized = match serde_json::to_vec(space) {
|
|
||||||
Ok(data) => data,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Serialization error during encryption: {}", e);
|
|
||||||
return Err(CryptoError::SerializationError(e.to_string()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Derive key from password
|
|
||||||
let key = derive_key_from_password(password);
|
|
||||||
|
|
||||||
// Encrypt the serialized data
|
|
||||||
let encrypted_data = encrypt_symmetric(&key, &serialized)?;
|
|
||||||
|
|
||||||
// Create metadata
|
|
||||||
let now = std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_millis() as u64;
|
|
||||||
let metadata = EncryptedKeySpaceMetadata {
|
|
||||||
name: space.name.clone(),
|
|
||||||
created_at: now,
|
|
||||||
last_accessed: now,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(EncryptedKeySpace {
|
|
||||||
metadata,
|
|
||||||
encrypted_data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts a key space using a password.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `encrypted_space` - The encrypted key space.
|
|
||||||
/// * `password` - The password to decrypt with.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Ok(KeySpace)` containing the decrypted key space.
|
|
||||||
/// * `Err(CryptoError)` if decryption fails.
|
|
||||||
pub fn decrypt_key_space(encrypted_space: &EncryptedKeySpace, password: &str) -> Result<KeySpace, CryptoError> {
|
|
||||||
// Derive key from password
|
|
||||||
let key = derive_key_from_password(password);
|
|
||||||
|
|
||||||
// Decrypt the data
|
|
||||||
let decrypted_data = decrypt_symmetric(&key, &encrypted_space.encrypted_data)?;
|
|
||||||
|
|
||||||
// Deserialize the key space
|
|
||||||
let space: KeySpace = match serde_json::from_slice(&decrypted_data) {
|
|
||||||
Ok(space) => space,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Deserialization error: {}", e);
|
|
||||||
return Err(CryptoError::SerializationError(e.to_string()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(space)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serializes an encrypted key space to a JSON string.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `encrypted_space` - The encrypted key space to serialize.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Ok(String)` containing the serialized encrypted key space.
|
|
||||||
/// * `Err(CryptoError)` if serialization fails.
|
|
||||||
pub fn serialize_encrypted_space(encrypted_space: &EncryptedKeySpace) -> Result<String, CryptoError> {
|
|
||||||
serde_json::to_string(encrypted_space)
|
|
||||||
.map_err(|e| CryptoError::SerializationError(e.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserializes an encrypted key space from a JSON string.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `serialized` - The serialized encrypted key space.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Ok(EncryptedKeySpace)` containing the deserialized encrypted key space.
|
|
||||||
/// * `Err(CryptoError)` if deserialization fails.
|
|
||||||
pub fn deserialize_encrypted_space(serialized: &str) -> Result<EncryptedKeySpace, CryptoError> {
|
|
||||||
match serde_json::from_str(serialized) {
|
|
||||||
Ok(space) => Ok(space),
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Error deserializing encrypted space: {}", e);
|
|
||||||
Err(CryptoError::SerializationError(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
//! Symmetric encryption functionality
|
|
||||||
//!
|
|
||||||
//! This module provides functionality for symmetric encryption using ChaCha20Poly1305.
|
|
||||||
|
|
||||||
mod implementation;
|
|
||||||
|
|
||||||
// Re-export public types and functions
|
|
||||||
pub use implementation::{
|
|
||||||
generate_symmetric_key, derive_key_from_password,
|
|
||||||
encrypt_symmetric, decrypt_symmetric,
|
|
||||||
encrypt_with_key, decrypt_with_key,
|
|
||||||
encrypt_key_space, decrypt_key_space,
|
|
||||||
serialize_encrypted_space, deserialize_encrypted_space,
|
|
||||||
EncryptedKeySpace, EncryptedKeySpaceMetadata
|
|
||||||
};
|
|
@ -12,7 +12,6 @@
|
|||||||
//! - System information
|
//! - System information
|
||||||
//! - Network operations
|
//! - Network operations
|
||||||
//! - Environment variables
|
//! - Environment variables
|
||||||
//! - Cryptographic operations
|
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -46,10 +45,6 @@ pub mod redisclient;
|
|||||||
pub mod rhai;
|
pub mod rhai;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod virt;
|
pub mod virt;
|
||||||
pub mod hero_vault;
|
|
||||||
pub mod rhai;
|
|
||||||
pub mod cmd;
|
|
||||||
pub mod zinit_client;
|
|
||||||
|
|
||||||
// Version information
|
// Version information
|
||||||
/// Returns the version of the SAL library
|
/// Returns the version of the SAL library
|
||||||
|
@ -1,943 +0,0 @@
|
|||||||
//! Rhai bindings for SAL crypto functionality
|
|
||||||
|
|
||||||
use rhai::{Engine, Dynamic, EvalAltResult};
|
|
||||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
use ethers::types::{Address, U256};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::hero_vault::{keypair, symmetric, ethereum};
|
|
||||||
use crate::hero_vault::ethereum::{prepare_function_arguments, convert_token_to_rhai};
|
|
||||||
|
|
||||||
// Global Tokio runtime for blocking async operations
|
|
||||||
static RUNTIME: Lazy<Mutex<Runtime>> = Lazy::new(|| {
|
|
||||||
Mutex::new(Runtime::new().expect("Failed to create Tokio runtime"))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Global provider registry
|
|
||||||
static PROVIDERS: Lazy<Mutex<HashMap<String, ethers::providers::Provider<ethers::providers::Http>>>> = 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::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::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::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::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::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::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::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::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<String> {
|
|
||||||
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::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::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::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 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) => 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 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<EvalAltResult>> {
|
|
||||||
// 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(())
|
|
||||||
}
|
|
@ -12,9 +12,7 @@ mod postgresclient;
|
|||||||
mod process;
|
mod process;
|
||||||
mod redisclient;
|
mod redisclient;
|
||||||
mod rfs;
|
mod rfs;
|
||||||
mod hero_vault; // This module now uses hero_vault internally
|
|
||||||
mod text;
|
mod text;
|
||||||
mod zinit;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
@ -92,9 +90,6 @@ pub use rfs::register as register_rfs_module;
|
|||||||
pub use crate::git::{GitRepo, GitTree};
|
pub use crate::git::{GitRepo, GitTree};
|
||||||
pub use git::register_git_module;
|
pub use git::register_git_module;
|
||||||
|
|
||||||
// Re-export zinit module
|
|
||||||
pub use zinit::register_zinit_module;
|
|
||||||
|
|
||||||
// Re-export text module
|
// Re-export text module
|
||||||
pub use text::register_text_module;
|
pub use text::register_text_module;
|
||||||
// Re-export text functions directly from text module
|
// Re-export text functions directly from text module
|
||||||
@ -110,9 +105,6 @@ pub use crate::text::{
|
|||||||
// Re-export TextReplacer functions
|
// Re-export TextReplacer functions
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|
||||||
// Re-export crypto module
|
|
||||||
pub use hero_vault::register_crypto_module;
|
|
||||||
|
|
||||||
// Rename copy functions to avoid conflicts
|
// Rename copy functions to avoid conflicts
|
||||||
pub use os::copy as os_copy;
|
pub use os::copy as os_copy;
|
||||||
|
|
||||||
@ -151,19 +143,11 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
|||||||
// Register Git module functions
|
// Register Git module functions
|
||||||
git::register_git_module(engine)?;
|
git::register_git_module(engine)?;
|
||||||
|
|
||||||
|
|
||||||
// Register Zinit module functions
|
|
||||||
zinit::register_zinit_module(engine)?;
|
|
||||||
|
|
||||||
// Register Text module functions
|
// Register Text module functions
|
||||||
text::register_text_module(engine)?;
|
text::register_text_module(engine)?;
|
||||||
|
|
||||||
// Register RFS module functions
|
// Register RFS module functions
|
||||||
rfs::register(engine)?;
|
rfs::register(engine)?;
|
||||||
|
|
||||||
// Register Crypto module functions
|
|
||||||
hero_vault::register_crypto_module(engine)?;
|
|
||||||
|
|
||||||
|
|
||||||
// Register Redis client module functions
|
// Register Redis client module functions
|
||||||
redisclient::register_redisclient_module(engine)?;
|
redisclient::register_redisclient_module(engine)?;
|
||||||
|
@ -1,345 +0,0 @@
|
|||||||
//! Rhai wrappers for Zinit client module functions
|
|
||||||
//!
|
|
||||||
//! This module provides Rhai wrappers for the functions in the Zinit client module.
|
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, Array, Dynamic, Map};
|
|
||||||
use crate::zinit_client as client;
|
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
use crate::rhai::error::ToRhaiError;
|
|
||||||
|
|
||||||
/// Register Zinit module functions with the Rhai engine
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `engine` - The Rhai engine to register the functions with
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
|
||||||
pub fn register_zinit_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
|
||||||
// Register Zinit client functions
|
|
||||||
engine.register_fn("zinit_list", zinit_list);
|
|
||||||
engine.register_fn("zinit_status", zinit_status);
|
|
||||||
engine.register_fn("zinit_start", zinit_start);
|
|
||||||
engine.register_fn("zinit_stop", zinit_stop);
|
|
||||||
engine.register_fn("zinit_restart", zinit_restart);
|
|
||||||
engine.register_fn("zinit_monitor", zinit_monitor);
|
|
||||||
engine.register_fn("zinit_forget", zinit_forget);
|
|
||||||
engine.register_fn("zinit_kill", zinit_kill);
|
|
||||||
engine.register_fn("zinit_create_service", zinit_create_service);
|
|
||||||
engine.register_fn("zinit_delete_service", zinit_delete_service);
|
|
||||||
engine.register_fn("zinit_get_service", zinit_get_service);
|
|
||||||
engine.register_fn("zinit_logs", zinit_logs);
|
|
||||||
engine.register_fn("zinit_logs_all", zinit_logs_all);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ToRhaiError<T> for Result<T, zinit_client::ClientError> {
|
|
||||||
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> {
|
|
||||||
self.map_err(|e| {
|
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
format!("Zinit error: {}", e).into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to get a runtime
|
|
||||||
fn get_runtime() -> Result<Runtime, Box<EvalAltResult>> {
|
|
||||||
tokio::runtime::Runtime::new().map_err(|e| {
|
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
format!("Failed to create Tokio runtime: {}", e).into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Zinit Client Function Wrappers
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::list
|
|
||||||
///
|
|
||||||
/// Lists all services managed by Zinit.
|
|
||||||
pub fn zinit_list(socket_path: &str) -> Result<Map, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::list(socket_path).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let services = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert HashMap<String, String> to Rhai Map
|
|
||||||
let mut map = Map::new();
|
|
||||||
for (name, state) in services {
|
|
||||||
map.insert(name.into(), Dynamic::from(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::status
|
|
||||||
///
|
|
||||||
/// Gets the status of a specific service.
|
|
||||||
pub fn zinit_status(socket_path: &str, name: &str) -> Result<Map, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::status(socket_path, name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let status = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert Status to Rhai Map
|
|
||||||
let mut map = Map::new();
|
|
||||||
map.insert("name".into(), Dynamic::from(status.name));
|
|
||||||
map.insert("pid".into(), Dynamic::from(status.pid));
|
|
||||||
map.insert("state".into(), Dynamic::from(status.state));
|
|
||||||
map.insert("target".into(), Dynamic::from(status.target));
|
|
||||||
|
|
||||||
// Convert dependencies
|
|
||||||
let mut deps_map = Map::new();
|
|
||||||
for (dep, state) in status.after {
|
|
||||||
deps_map.insert(dep.into(), Dynamic::from(state));
|
|
||||||
}
|
|
||||||
map.insert("after".into(), Dynamic::from_map(deps_map));
|
|
||||||
|
|
||||||
Ok(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::start
|
|
||||||
///
|
|
||||||
/// Starts a service.
|
|
||||||
pub fn zinit_start(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::start(socket_path, name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::stop
|
|
||||||
///
|
|
||||||
/// Stops a service.
|
|
||||||
pub fn zinit_stop(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::stop(socket_path, name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::restart
|
|
||||||
///
|
|
||||||
/// Restarts a service.
|
|
||||||
pub fn zinit_restart(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
client::restart(socket_path, name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::monitor
|
|
||||||
///
|
|
||||||
/// Starts monitoring a service.
|
|
||||||
pub fn zinit_monitor(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.monitor(name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::forget
|
|
||||||
///
|
|
||||||
/// Stops monitoring a service.
|
|
||||||
pub fn zinit_forget(socket_path: &str, name: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.forget(name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::kill
|
|
||||||
///
|
|
||||||
/// Sends a signal to a service.
|
|
||||||
pub fn zinit_kill(socket_path: &str, name: &str, signal: &str) -> Result<bool, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.kill(name, signal).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::create_service
|
|
||||||
///
|
|
||||||
/// Creates a new service.
|
|
||||||
pub fn zinit_create_service(socket_path: &str, name: &str, exec: &str, oneshot: bool) -> Result<String, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
// Create service configuration
|
|
||||||
let content = serde_json::from_value(json!({
|
|
||||||
"exec": exec,
|
|
||||||
"oneshot": oneshot
|
|
||||||
})).map_err(|e| {
|
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
format!("Failed to create service configuration: {}", e).into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.create_service(name, content).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::delete_service
|
|
||||||
///
|
|
||||||
/// Deletes a service.
|
|
||||||
pub fn zinit_delete_service(socket_path: &str, name: &str) -> Result<String, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.delete_service(name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
result.to_rhai_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::get_service
|
|
||||||
///
|
|
||||||
/// Gets a service configuration.
|
|
||||||
pub fn zinit_get_service(socket_path: &str, name: &str) -> Result<Dynamic, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.get_service(name).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let value = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert Value to Dynamic
|
|
||||||
match value {
|
|
||||||
Value::Object(map) => {
|
|
||||||
let mut rhai_map = Map::new();
|
|
||||||
for (k, v) in map {
|
|
||||||
rhai_map.insert(k.into(), value_to_dynamic(v));
|
|
||||||
}
|
|
||||||
Ok(Dynamic::from_map(rhai_map))
|
|
||||||
},
|
|
||||||
_ => Err(Box::new(EvalAltResult::ErrorRuntime(
|
|
||||||
"Expected object from get_service".into(),
|
|
||||||
rhai::Position::NONE
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::logs with a filter
|
|
||||||
///
|
|
||||||
/// Gets logs for a specific service.
|
|
||||||
pub fn zinit_logs(socket_path: &str, filter: &str) -> Result<Array, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let filter_string = Some(filter.to_string());
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.logs(filter_string).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let logs = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert Vec<String> to Rhai Array
|
|
||||||
let mut array = Array::new();
|
|
||||||
for log in logs {
|
|
||||||
array.push(Dynamic::from(log));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(array)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for zinit_client::logs without a filter
|
|
||||||
///
|
|
||||||
/// Gets all logs.
|
|
||||||
pub fn zinit_logs_all(socket_path: &str) -> Result<Array, Box<EvalAltResult>> {
|
|
||||||
let rt = get_runtime()?;
|
|
||||||
|
|
||||||
let result = rt.block_on(async {
|
|
||||||
let client = client::get_zinit_client(socket_path).await?;
|
|
||||||
client.logs(None).await
|
|
||||||
});
|
|
||||||
|
|
||||||
let logs = result.to_rhai_error()?;
|
|
||||||
|
|
||||||
// Convert Vec<String> to Rhai Array
|
|
||||||
let mut array = Array::new();
|
|
||||||
for log in logs {
|
|
||||||
array.push(Dynamic::from(log));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(array)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to convert serde_json::Value to rhai::Dynamic
|
|
||||||
fn value_to_dynamic(value: Value) -> Dynamic {
|
|
||||||
match value {
|
|
||||||
Value::Null => Dynamic::UNIT,
|
|
||||||
Value::Bool(b) => Dynamic::from(b),
|
|
||||||
Value::Number(n) => {
|
|
||||||
if let Some(i) = n.as_i64() {
|
|
||||||
Dynamic::from(i)
|
|
||||||
} else if let Some(f) = n.as_f64() {
|
|
||||||
Dynamic::from(f)
|
|
||||||
} else {
|
|
||||||
Dynamic::from(n.to_string())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Value::String(s) => Dynamic::from(s),
|
|
||||||
Value::Array(arr) => {
|
|
||||||
let mut rhai_arr = Array::new();
|
|
||||||
for item in arr {
|
|
||||||
rhai_arr.push(value_to_dynamic(item));
|
|
||||||
}
|
|
||||||
Dynamic::from(rhai_arr)
|
|
||||||
},
|
|
||||||
Value::Object(map) => {
|
|
||||||
let mut rhai_map = Map::new();
|
|
||||||
for (k, v) in map {
|
|
||||||
rhai_map.insert(k.into(), value_to_dynamic(v));
|
|
||||||
}
|
|
||||||
Dynamic::from_map(rhai_map)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,203 +0,0 @@
|
|||||||
use std::sync::{Arc, Mutex, Once};
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use zinit_client::{Client as ZinitClient, ClientError, Status};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use serde_json::{Map, Value};
|
|
||||||
|
|
||||||
// Global Zinit client instance using lazy_static
|
|
||||||
lazy_static! {
|
|
||||||
static ref ZINIT_CLIENT: Mutex<Option<Arc<ZinitClientWrapper>>> = Mutex::new(None);
|
|
||||||
static ref INIT: Once = Once::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper for Zinit client to handle connection
|
|
||||||
pub struct ZinitClientWrapper {
|
|
||||||
client: ZinitClient,
|
|
||||||
initialized: AtomicBool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ZinitClientWrapper {
|
|
||||||
// Create a new Zinit client wrapper
|
|
||||||
fn new(client: ZinitClient) -> Self {
|
|
||||||
ZinitClientWrapper {
|
|
||||||
client,
|
|
||||||
initialized: AtomicBool::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the client
|
|
||||||
async fn initialize(&self) -> Result<(), ClientError> {
|
|
||||||
if self.initialized.load(Ordering::Relaxed) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to list services to check if the connection works
|
|
||||||
let _ = self.client.list().await.map_err(|e| {
|
|
||||||
eprintln!("Failed to initialize Zinit client: {}", e);
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
self.initialized.store(true, Ordering::Relaxed);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all services
|
|
||||||
pub async fn list(&self) -> Result<HashMap<String, String>, ClientError> {
|
|
||||||
self.client.list().await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get status of a service
|
|
||||||
pub async fn status(&self, name: &str) -> Result<Status, ClientError> {
|
|
||||||
self.client.status(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a service
|
|
||||||
pub async fn start(&self, name: &str) -> Result<(), ClientError> {
|
|
||||||
self.client.start(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop a service
|
|
||||||
pub async fn stop(&self, name: &str) -> Result<(), ClientError> {
|
|
||||||
self.client.stop(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart a service
|
|
||||||
pub async fn restart(&self, name: &str) -> Result<(), ClientError> {
|
|
||||||
self.client.restart(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monitor a service
|
|
||||||
pub async fn monitor(&self, name: &str) -> Result<(), ClientError> {
|
|
||||||
self.client.monitor(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forget a service
|
|
||||||
pub async fn forget(&self, name: &str) -> Result<(), ClientError> {
|
|
||||||
self.client.forget(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a signal to a service
|
|
||||||
pub async fn kill(&self, name: &str, signal: &str) -> Result<(), ClientError> {
|
|
||||||
self.client.kill(name, signal).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new service
|
|
||||||
pub async fn create_service(&self, name: &str, content: Map<String, Value>) -> Result<String, ClientError> {
|
|
||||||
self.client.create_service(name, content).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a service
|
|
||||||
pub async fn delete_service(&self, name: &str) -> Result<String, ClientError> {
|
|
||||||
self.client.delete_service(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a service configuration
|
|
||||||
pub async fn get_service(&self, name: &str) -> Result<Value, ClientError> {
|
|
||||||
self.client.get_service(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown the system
|
|
||||||
pub async fn shutdown(&self) -> Result<(), ClientError> {
|
|
||||||
self.client.shutdown().await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reboot the system
|
|
||||||
pub async fn reboot(&self) -> Result<(), ClientError> {
|
|
||||||
self.client.reboot().await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start HTTP server
|
|
||||||
pub async fn start_http_server(&self, address: &str) -> Result<String, ClientError> {
|
|
||||||
self.client.start_http_server(address).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop HTTP server
|
|
||||||
pub async fn stop_http_server(&self) -> Result<(), ClientError> {
|
|
||||||
self.client.stop_http_server().await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get logs
|
|
||||||
pub async fn logs(&self, filter: Option<String>) -> Result<Vec<String>, ClientError> {
|
|
||||||
self.client.logs(filter).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the Zinit client instance
|
|
||||||
pub async fn get_zinit_client(socket_path: &str) -> Result<Arc<ZinitClientWrapper>, ClientError> {
|
|
||||||
// Check if we already have a client
|
|
||||||
{
|
|
||||||
let guard = ZINIT_CLIENT.lock().unwrap();
|
|
||||||
if let Some(ref client) = &*guard {
|
|
||||||
return Ok(Arc::clone(client));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new client
|
|
||||||
let client = create_zinit_client(socket_path).await?;
|
|
||||||
|
|
||||||
// Store the client globally
|
|
||||||
{
|
|
||||||
let mut guard = ZINIT_CLIENT.lock().unwrap();
|
|
||||||
*guard = Some(Arc::clone(&client));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new Zinit client
|
|
||||||
async fn create_zinit_client(socket_path: &str) -> Result<Arc<ZinitClientWrapper>, ClientError> {
|
|
||||||
// Connect via Unix socket
|
|
||||||
let client = ZinitClient::unix_socket(socket_path).await?;
|
|
||||||
let wrapper = Arc::new(ZinitClientWrapper::new(client));
|
|
||||||
|
|
||||||
// Initialize the client
|
|
||||||
wrapper.initialize().await?;
|
|
||||||
|
|
||||||
Ok(wrapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the Zinit client
|
|
||||||
pub async fn reset(socket_path: &str) -> Result<(), ClientError> {
|
|
||||||
// Clear the existing client
|
|
||||||
{
|
|
||||||
let mut client_guard = ZINIT_CLIENT.lock().unwrap();
|
|
||||||
*client_guard = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new client, only return error if it fails
|
|
||||||
get_zinit_client(socket_path).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience functions for common operations
|
|
||||||
|
|
||||||
// List all services
|
|
||||||
pub async fn list(socket_path: &str) -> Result<HashMap<String, String>, ClientError> {
|
|
||||||
let client = get_zinit_client(socket_path).await?;
|
|
||||||
client.list().await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get status of a service
|
|
||||||
pub async fn status(socket_path: &str, name: &str) -> Result<Status, ClientError> {
|
|
||||||
let client = get_zinit_client(socket_path).await?;
|
|
||||||
client.status(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a service
|
|
||||||
pub async fn start(socket_path: &str, name: &str) -> Result<(), ClientError> {
|
|
||||||
let client = get_zinit_client(socket_path).await?;
|
|
||||||
client.start(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop a service
|
|
||||||
pub async fn stop(socket_path: &str, name: &str) -> Result<(), ClientError> {
|
|
||||||
let client = get_zinit_client(socket_path).await?;
|
|
||||||
client.stop(name).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart a service
|
|
||||||
pub async fn restart(socket_path: &str, name: &str) -> Result<(), ClientError> {
|
|
||||||
let client = get_zinit_client(socket_path).await?;
|
|
||||||
client.restart(name).await
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user