From 196a4269d1e9ee43e65d48f46e0478ec799ee9ef Mon Sep 17 00:00:00 2001 From: ryardley Date: Tue, 3 Dec 2024 16:53:12 +0700 Subject: [PATCH 01/22] Docs --- packages/ciphernode/Cargo.lock | 1 + packages/ciphernode/docs/user_guide.md | 100 ++++++++++++++++++ packages/ciphernode/enclave/Cargo.toml | 1 + .../ciphernode/enclave/src/commands/init.rs | 63 +++++++++++ .../ciphernode/enclave/src/commands/mod.rs | 3 + packages/ciphernode/enclave/src/main.rs | 3 +- 6 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 packages/ciphernode/docs/user_guide.md create mode 100644 packages/ciphernode/enclave/src/commands/init.rs diff --git a/packages/ciphernode/Cargo.lock b/packages/ciphernode/Cargo.lock index 580d8beb..09f1d15d 100644 --- a/packages/ciphernode/Cargo.lock +++ b/packages/ciphernode/Cargo.lock @@ -2164,6 +2164,7 @@ dependencies = [ "config", "data", "dialoguer", + "dirs", "enclave-core", "enclave_node", "hex", diff --git a/packages/ciphernode/docs/user_guide.md b/packages/ciphernode/docs/user_guide.md new file mode 100644 index 00000000..96e348c2 --- /dev/null +++ b/packages/ciphernode/docs/user_guide.md @@ -0,0 +1,100 @@ +# Running a Ciphernode + +``` +enclave init +``` + +Will setup an initial configuration + +## Configuration + +Enclave is configured using a configuration file. By default this file is located under `~/.config/enclave/config.yaml` + +Default values for this file might effectively look like: + +``` +# ~/.config/enclave/config.yaml +key_file: "{config_dir}/key" +db_file: "{data_dir}/db" +config_dir: "~/.config/enclave" +data_dir: "~/.local/share/enclave" +``` + +> Note if you set `config_dir` it will change the default location for both the config file and the `key_file` and if you specify `data_dir` it will change the default location for the `db_file` for example: +> If I run `enclave start --config ./some-config.yaml` where `./some-config.yaml` contains: +> +> ``` +> # some-config.yaml +> config_dir: "/my/config/dir" +> ``` +> +> The `enclave` binary will look for the key_file under: `/my/config/dir/key` + +### Setting a relative folder as a config dir + +You may set a relative folder for your config and data dirs. + +``` +# /path/to/config.yaml +config_dir: ./conf +data_dir: ./data +``` + +Within the above config the `key_file` location will be: `/path/to/conf/key` and the `db_file` will be `/path/to/data/db`. + +## Providing a registration address + +_NOTE: this will likely change soon as we move to using BLS signatures for Ciphernode identification_ + +Ciphernodes need a registration address to identify themselves within a committee you can specify this with the `address` field within the configuration: + +``` +# ~/.config/enclave/config.yaml +address: "0x2546BcD3c84621e976D8185a91A922aE77ECEc30" +``` + +## Setting your encryption password + +Your encryption password is required to encrypt sensitive data within the database such as keys and wallet private keys. You can set this key in two ways: + +1. Use the command line +2. Provide a key file + +## Provide your password using the commandline + +``` +> enclave password create + +Please enter a new password: +``` + +Enter your chosen password. + +``` +Please confirm your password: +``` + +Enter your password again to confirm it. + +``` +Password sucessfully set. +``` + +Assuming default settings you should now be able to find your keyfile under `~/.config/enclave/key` + +## Provide your password using a key file + +You can use a keyfile to provide your password by creating a file under `~/.config/enclave/key` and setting the file permissions to `400` + +``` +mkdir -p ~/.config/enclave && read -s password && echo -n "$password" > ~/.config/enclave/key && chmod 400 ~/.config/enclave/key +``` + +You can change the location of your keyfile by using the `key_file` option within your configuration file: + +``` +# ~/.config/enclave/config.yaml +key_file: "/path/to/enclave/key" +``` + + diff --git a/packages/ciphernode/enclave/Cargo.toml b/packages/ciphernode/enclave/Cargo.toml index f0a93ab7..74a4dde6 100644 --- a/packages/ciphernode/enclave/Cargo.toml +++ b/packages/ciphernode/enclave/Cargo.toml @@ -15,6 +15,7 @@ cipher = { path = "../cipher" } clap = { workspace = true } config = { path = "../config" } data = { path = "../data" } +dirs = { workspace = true } dialoguer = "0.11.0" enclave-core = { path = "../core" } enclave_node = { path = "../enclave_node" } diff --git a/packages/ciphernode/enclave/src/commands/init.rs b/packages/ciphernode/enclave/src/commands/init.rs new file mode 100644 index 00000000..e3a2ebd4 --- /dev/null +++ b/packages/ciphernode/enclave/src/commands/init.rs @@ -0,0 +1,63 @@ +use anyhow::Result; +use config::load_config; +use dialoguer::{theme::ColorfulTheme, Input}; +use enclave_core::get_tag; +use std::fs; +use tracing::instrument; + +use crate::commands::password::{self, PasswordCommands}; + +#[instrument(name = "app", skip_all, fields(id = get_tag()))] +pub async fn execute() -> Result<()> { + // Prompt for Ethereum address + let eth_address: String = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter your Ethereum address") + .validate_with(|input: &String| -> Result<(), &str> { + // Basic Ethereum address validation + if !input.starts_with("0x") { + return Err("Address must start with '0x'"); + } + if input.len() != 42 { + return Err("Address must be 42 characters long (including '0x')"); + } + if !input[2..].chars().all(|c| c.is_ascii_hexdigit()) { + return Err("Address must contain only hexadecimal characters"); + } + Ok(()) + }) + .interact()?; + + // Create config directory if it doesn't exist + let config_dir = dirs::home_dir() + .ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))? + .join(".config") + .join("enclave"); + fs::create_dir_all(&config_dir)?; + + // Create config file path + let config_path = config_dir.join("config.yaml"); + + // Create YAML content using indented heredoc style + let config_content = format!( + r#"--- +# Enclave Configuration File +# Ethereum Account Configuration +address: "{}" +"#, + eth_address + ); + + // Write to file + fs::write(config_path.clone(), config_content)?; + + // Load with default location + let config = load_config(Some(&config_path.display().to_string()))?; + + password::execute(PasswordCommands::Create { password: None }, config).await?; + + // password::execute(/* command */, config) + println!("Enclave configuration successfully created!"); + println!("You can start your node using `enclave start`"); + + Ok(()) +} diff --git a/packages/ciphernode/enclave/src/commands/mod.rs b/packages/ciphernode/enclave/src/commands/mod.rs index 169593af..647263ef 100644 --- a/packages/ciphernode/enclave/src/commands/mod.rs +++ b/packages/ciphernode/enclave/src/commands/mod.rs @@ -3,6 +3,7 @@ pub mod net; pub mod password; pub mod start; pub mod wallet; +pub mod init; use self::password::PasswordCommands; use aggregator::AggregatorCommands; @@ -38,4 +39,6 @@ pub enum Commands { #[command(subcommand)] command: NetCommands, }, + + Init } diff --git a/packages/ciphernode/enclave/src/main.rs b/packages/ciphernode/enclave/src/main.rs index b49df740..5d5acf96 100644 --- a/packages/ciphernode/enclave/src/main.rs +++ b/packages/ciphernode/enclave/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; use clap::Parser; -use commands::{aggregator, net, password, start, wallet, Commands}; +use commands::{aggregator, init, net, password, start, wallet, Commands}; use config::load_config; use enclave_core::{get_tag, set_tag}; use tracing::instrument; @@ -50,6 +50,7 @@ impl Cli { match self.command { Commands::Start => start::execute(config).await?, + Commands::Init => init::execute().await?, Commands::Password { command } => password::execute(command, config).await?, Commands::Aggregator { command } => aggregator::execute(command, config).await?, Commands::Wallet { command } => wallet::execute(command, config).await?, From a86618102e8e6d1f97cbc57560ddc6018a77960b Mon Sep 17 00:00:00 2001 From: ryardley Date: Fri, 6 Dec 2024 07:28:05 +1100 Subject: [PATCH 02/22] enclave init --- packages/ciphernode/Cargo.lock | 55 +++++++++++- packages/ciphernode/Cargo.toml | 1 + .../ciphernode/cipher/src/password_manager.rs | 14 ++++ packages/ciphernode/enclave/Cargo.toml | 9 +- packages/ciphernode/enclave/build.rs | 63 ++++++++++++++ .../ciphernode/enclave/src/commands/init.rs | 84 +++++++++++++++---- .../enclave/src/commands/password/create.rs | 5 ++ .../enclave/src/commands/password/delete.rs | 6 +- 8 files changed, 215 insertions(+), 22 deletions(-) create mode 100644 packages/ciphernode/enclave/build.rs diff --git a/packages/ciphernode/Cargo.lock b/packages/ciphernode/Cargo.lock index 09f1d15d..acae3568 100644 --- a/packages/ciphernode/Cargo.lock +++ b/packages/ciphernode/Cargo.lock @@ -2169,8 +2169,11 @@ dependencies = [ "enclave_node", "hex", "once_cell", + "phf", "router", "rpassword", + "serde", + "serde_json", "tokio", "tracing", "tracing-subscriber", @@ -4588,6 +4591,48 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -5576,9 +5621,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -5687,6 +5732,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" diff --git a/packages/ciphernode/Cargo.toml b/packages/ciphernode/Cargo.toml index c7df1e8c..382ac6b7 100644 --- a/packages/ciphernode/Cargo.toml +++ b/packages/ciphernode/Cargo.toml @@ -51,6 +51,7 @@ num = "0.4.3" rand_chacha = "0.3.1" rand = "0.8.5" serde = { version = "1.0.208", features = ["derive"] } +serde_json = { version = "1.0.133" } sled = "0.34.7" sha2 = "0.10.8" tokio = { version = "1.38", features = ["full"] } diff --git a/packages/ciphernode/cipher/src/password_manager.rs b/packages/ciphernode/cipher/src/password_manager.rs index 550eadf2..7f30866d 100644 --- a/packages/ciphernode/cipher/src/password_manager.rs +++ b/packages/ciphernode/cipher/src/password_manager.rs @@ -14,6 +14,7 @@ pub trait PasswordManager { async fn get_key(&self) -> Result>>; async fn delete_key(&mut self) -> Result<()>; async fn set_key(&mut self, contents: Zeroizing>) -> Result<()>; + fn is_set(&self) -> bool; } pub struct InMemPasswordManager(pub Option>>); @@ -54,6 +55,10 @@ impl PasswordManager for EnvPasswordManager { self.0 = None; Ok(()) } + + fn is_set(&self) -> bool { + self.0 == None + } } #[async_trait] @@ -73,6 +78,10 @@ impl PasswordManager for InMemPasswordManager { self.0 = None; Ok(()) } + + fn is_set(&self) -> bool { + self.0 == None + } } pub struct FilePasswordManager { @@ -149,6 +158,11 @@ impl PasswordManager for FilePasswordManager { Ok(()) } + + fn is_set(&self) -> bool { + let path = &self.path; + path.exists() + } } fn ensure_file_permissions(path: &PathBuf, perms: u32) -> Result<()> { diff --git a/packages/ciphernode/enclave/Cargo.toml b/packages/ciphernode/enclave/Cargo.toml index 74a4dde6..9df066dd 100644 --- a/packages/ciphernode/enclave/Cargo.toml +++ b/packages/ciphernode/enclave/Cargo.toml @@ -4,8 +4,7 @@ version = "0.1.0" edition = "2021" description = ": coordinates the encryption and decryption of enclave computations" repository = "https://github.com/gnosisguild/enclave/packages/ciphernode" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +build = "build.rs" [dependencies] actix = { workspace = true } @@ -24,6 +23,12 @@ once_cell = "1.20.2" router = { path = "../router" } rpassword = "7.3.1" tokio = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } zeroize = { workspace = true } +phf = { version = "0.11", features = ["macros"] } + +[build-dependencies] +serde_json = { workspace = true } diff --git a/packages/ciphernode/enclave/build.rs b/packages/ciphernode/enclave/build.rs new file mode 100644 index 00000000..01a47dd1 --- /dev/null +++ b/packages/ciphernode/enclave/build.rs @@ -0,0 +1,63 @@ +// Here we build some contract information from the EVM deployment artifacts that we can use within +// our binaries. Specifically we wbuild out a rust file that has a structure we can import and use +// within our configuration builder +use serde_json::{from_reader, Value}; +use std::env; +use std::fs::{self, File}; +use std::path::Path; + +fn main() -> std::io::Result<()> { + // Get the manifest directory (where Cargo.toml is located) + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + // Path to deployment artifacts + let deployments_path = Path::new(&manifest_dir) + .join("..") // Adjust based on your actual path structure + .join("..") + .join("evm") + .join("deployments") + .join("sepolia"); + + // Create output string for contract info + let mut contract_info = String::from( + "pub struct ContractInfo {\n pub address: &'static str,\n pub deploy_block: u64,\n}\n\n" + ); + contract_info.push_str( + "pub static CONTRACT_DEPLOYMENTS: phf::Map<&'static str, ContractInfo> = phf::phf_map! {\n", + ); + + // Process each JSON file in the deployments directory + for entry in fs::read_dir(deployments_path)? { + let entry = entry?; + let path = entry.path(); + + if path.extension().and_then(|s| s.to_str()) == Some("json") { + let contract_name = path.file_stem().and_then(|s| s.to_str()).unwrap(); + + let file = File::open(&path)?; + let json: Value = from_reader(file)?; + + // Extract address and block number + if let (Some(address), Some(deploy_block)) = ( + json["address"].as_str(), + json["receipt"]["blockNumber"].as_u64(), + ) { + contract_info.push_str(&format!( + " \"{}\" => ContractInfo {{\n address: \"{}\",\n deploy_block: {},\n }},\n", + contract_name, address, deploy_block + )); + } + } + } + + contract_info.push_str("};\n"); + + // Write the generated code to a file + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("contract_deployments.rs"); + fs::write(dest_path, contract_info)?; + + println!("cargo:rerun-if-changed=../packages/evm/deployments/sepolia"); + + Ok(()) +} diff --git a/packages/ciphernode/enclave/src/commands/init.rs b/packages/ciphernode/enclave/src/commands/init.rs index e3a2ebd4..a28eebfe 100644 --- a/packages/ciphernode/enclave/src/commands/init.rs +++ b/packages/ciphernode/enclave/src/commands/init.rs @@ -1,3 +1,6 @@ +use crate::commands::password::{self, PasswordCommands}; +use alloy::transports::http::reqwest::Url; +use anyhow::anyhow; use anyhow::Result; use config::load_config; use dialoguer::{theme::ColorfulTheme, Input}; @@ -5,57 +8,104 @@ use enclave_core::get_tag; use std::fs; use tracing::instrument; -use crate::commands::password::{self, PasswordCommands}; +// Import a built file: +// see /target/debug/enclave-xxxxxx/out/contract_deployments.rs +// also see build.rs +include!(concat!(env!("OUT_DIR"), "/contract_deployments.rs")); + +// Get the ContractInfo object +fn get_contract_info(name: &str) -> Result<&ContractInfo> { + Ok(CONTRACT_DEPLOYMENTS + .get(name) + .ok_or(anyhow!("Could not get contract info"))?) +} #[instrument(name = "app", skip_all, fields(id = get_tag()))] pub async fn execute() -> Result<()> { - // Prompt for Ethereum address - let eth_address: String = Input::with_theme(&ColorfulTheme::default()) - .with_prompt("Enter your Ethereum address") + let rpc_url = Input::::new() + .with_prompt("Enter WebSocket devnet RPC URL") + .default("wss://ethereum-sepolia-rpc.publicnode.com".to_string()) + .validate_with(|input: &String| { + if let Ok(url) = Url::parse(input) { + if url.scheme() == "wss" { + return Ok(()); + } + } + Err("Please enter a valid WSS URL") + }) + .interact_text()?; + + let eth_address: Option = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter your Ethereum address (press Enter to skip)") + .allow_empty(true) .validate_with(|input: &String| -> Result<(), &str> { - // Basic Ethereum address validation + if input.is_empty() { + return Ok(()); + } if !input.starts_with("0x") { return Err("Address must start with '0x'"); } if input.len() != 42 { return Err("Address must be 42 characters long (including '0x')"); } - if !input[2..].chars().all(|c| c.is_ascii_hexdigit()) { - return Err("Address must contain only hexadecimal characters"); + for c in input[2..].chars() { + if !c.is_ascii_hexdigit() { + return Err("Address must contain only hexadecimal characters"); + } } Ok(()) }) - .interact()?; + .interact() + .ok() + .map(|s| if s.is_empty() { None } else { Some(s) }) + .flatten(); - // Create config directory if it doesn't exist let config_dir = dirs::home_dir() .ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))? .join(".config") .join("enclave"); fs::create_dir_all(&config_dir)?; - // Create config file path let config_path = config_dir.join("config.yaml"); - // Create YAML content using indented heredoc style let config_content = format!( r#"--- # Enclave Configuration File -# Ethereum Account Configuration -address: "{}" +{} +chains: + - name: "devnet" + rpc_url: "{}" + contracts: + enclave: + address: "{}" + deploy_block: {} + ciphernode_registry: + address: "{}" + deploy_block: {} + filter_registry: + address: "{}" + deploy_block: {} "#, - eth_address + eth_address.map_or(String::new(), |addr| format!( + "# Ethereum Account Configuration\naddress: \"{}\"", + addr + )), + rpc_url, + get_contract_info("Enclave")?.address, + get_contract_info("Enclave")?.deploy_block, + get_contract_info("CiphernodeRegistryOwnable")?.address, + get_contract_info("CiphernodeRegistryOwnable")?.deploy_block, + get_contract_info("NaiveRegistryFilter")?.address, + get_contract_info("NaiveRegistryFilter")?.deploy_block, ); - // Write to file fs::write(config_path.clone(), config_content)?; // Load with default location let config = load_config(Some(&config_path.display().to_string()))?; password::execute(PasswordCommands::Create { password: None }, config).await?; - - // password::execute(/* command */, config) + println!("Enclave configuration successfully created!"); println!("You can start your node using `enclave start`"); diff --git a/packages/ciphernode/enclave/src/commands/password/create.rs b/packages/ciphernode/enclave/src/commands/password/create.rs index 5af3fa46..4a9a454e 100644 --- a/packages/ciphernode/enclave/src/commands/password/create.rs +++ b/packages/ciphernode/enclave/src/commands/password/create.rs @@ -43,6 +43,11 @@ fn get_zeroizing_pw_vec(input: Option) -> Result>> { pub async fn execute(config: &AppConfig, input: Option) -> Result<()> { let key_file = config.key_file(); let mut pm = FilePasswordManager::new(key_file); + + if pm.is_set() { + bail!("Keyfile already exists. Refusing to overwrite. Try using `enclave password overwrite` or `enclave password delete` in order to change or delete your password.") + } + let pw = get_zeroizing_pw_vec(input)?; match pm.set_key(pw).await { diff --git a/packages/ciphernode/enclave/src/commands/password/delete.rs b/packages/ciphernode/enclave/src/commands/password/delete.rs index bffdf6a0..76d2e8ae 100644 --- a/packages/ciphernode/enclave/src/commands/password/delete.rs +++ b/packages/ciphernode/enclave/src/commands/password/delete.rs @@ -29,9 +29,13 @@ pub async fn prompt_delete(config: &AppConfig, delete_mode: DeleteMode) -> Resul .interact()?; if proceed { - let mut pw_str = prompt_password("\n\nPlease enter the current password: ")?; let key_file = config.key_file(); let mut pm = FilePasswordManager::new(key_file); + if !pm.is_set() { + println!("Password is not set. Nothing to do."); + return Ok(false); + } + let mut pw_str = prompt_password("\n\nPlease enter the current password: ")?; let mut cur_pw = pm.get_key().await?; if pw_str != String::from_utf8_lossy(&cur_pw) { From 4c9931f56f29c077e81f719a5eab9db3b0265dc4 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 15:16:52 +1100 Subject: [PATCH 03/22] Reuse URL validation --- packages/ciphernode/Cargo.lock | 1 + packages/ciphernode/config/Cargo.toml | 2 + packages/ciphernode/config/src/app_config.rs | 68 ++++++++++++++++++- .../ciphernode/enclave/src/commands/init.rs | 22 +++--- .../ciphernode/enclave_node/src/aggregator.rs | 6 +- .../ciphernode/enclave_node/src/ciphernode.rs | 9 +-- packages/ciphernode/evm/src/helpers.rs | 59 +--------------- 7 files changed, 85 insertions(+), 82 deletions(-) diff --git a/packages/ciphernode/Cargo.lock b/packages/ciphernode/Cargo.lock index acae3568..33238622 100644 --- a/packages/ciphernode/Cargo.lock +++ b/packages/ciphernode/Cargo.lock @@ -1694,6 +1694,7 @@ dependencies = [ "dirs", "figment", "serde", + "url", ] [[package]] diff --git a/packages/ciphernode/config/Cargo.toml b/packages/ciphernode/config/Cargo.toml index a694ceb5..a336f7fe 100644 --- a/packages/ciphernode/config/Cargo.toml +++ b/packages/ciphernode/config/Cargo.toml @@ -9,3 +9,5 @@ anyhow = { workspace = true } serde = { workspace = true } figment = { workspace = true } alloy = { workspace = true } +url = { workspace = true } + diff --git a/packages/ciphernode/config/src/app_config.rs b/packages/ciphernode/config/src/app_config.rs index 903af725..0d5791d4 100644 --- a/packages/ciphernode/config/src/app_config.rs +++ b/packages/ciphernode/config/src/app_config.rs @@ -1,4 +1,7 @@ use alloy::primitives::Address; +use anyhow::anyhow; +use anyhow::bail; +use anyhow::Context; use anyhow::Result; use figment::{ providers::{Format, Serialized, Yaml}, @@ -9,6 +12,7 @@ use std::{ env, path::{Path, PathBuf}, }; +use url::Url; #[derive(Debug, Deserialize, Serialize, PartialEq)] #[serde(untagged)] @@ -20,6 +24,61 @@ pub enum Contract { AddressOnly(String), } +#[derive(Clone)] +pub enum RPC { + Http(String), + Https(String), + Ws(String), + Wss(String), +} + +impl RPC { + pub fn from_url(url: &str) -> Result { + let parsed = Url::parse(url).context("Invalid URL format")?; + match parsed.scheme() { + "http" => Ok(RPC::Http(url.to_string())), + "https" => Ok(RPC::Https(url.to_string())), + "ws" => Ok(RPC::Ws(url.to_string())), + "wss" => Ok(RPC::Wss(url.to_string())), + _ => bail!("Invalid protocol. Expected: http://, https://, ws://, wss://"), + } + } + + pub fn as_http_url(&self) -> String { + match self { + RPC::Http(url) | RPC::Https(url) => url.clone(), + RPC::Ws(url) | RPC::Wss(url) => { + let mut parsed = Url::parse(url).expect(&format!("Failed to parse URL: {}", url)); + parsed + .set_scheme(if self.is_secure() { "https" } else { "http" }) + .expect("http(s) are valid schemes"); + parsed.to_string() + } + } + } + + pub fn as_ws_url(&self) -> String { + match self { + RPC::Ws(url) | RPC::Wss(url) => url.clone(), + RPC::Http(url) | RPC::Https(url) => { + let mut parsed = Url::parse(url).expect(&format!("Failed to parse URL: {}", url)); + parsed + .set_scheme(if self.is_secure() { "wss" } else { "ws" }) + .expect("ws(s) are valid schemes"); + parsed.to_string() + } + } + } + + pub fn is_websocket(&self) -> bool { + matches!(self, RPC::Ws(_) | RPC::Wss(_)) + } + + pub fn is_secure(&self) -> bool { + matches!(self, RPC::Https(_) | RPC::Wss(_)) + } +} + impl Contract { pub fn address(&self) -> &String { use Contract::*; @@ -63,12 +122,19 @@ impl Default for RpcAuth { pub struct ChainConfig { pub enabled: Option, pub name: String, - pub rpc_url: String, // We may need multiple per chain for redundancy at a later point + rpc_url: String, // We may need multiple per chain for redundancy at a later point #[serde(default)] pub rpc_auth: RpcAuth, pub contracts: ContractAddresses, } +impl ChainConfig { + pub fn rpc_url(&self) -> Result { + Ok(RPC::from_url(&self.rpc_url) + .map_err(|e| anyhow!("Failed to parse RPC URL for chain {}: {}", self.name, e))?) + } +} + #[derive(Debug, Deserialize, Serialize)] pub struct AppConfig { /// The chains config diff --git a/packages/ciphernode/enclave/src/commands/init.rs b/packages/ciphernode/enclave/src/commands/init.rs index a28eebfe..926e844f 100644 --- a/packages/ciphernode/enclave/src/commands/init.rs +++ b/packages/ciphernode/enclave/src/commands/init.rs @@ -1,8 +1,10 @@ use crate::commands::password::{self, PasswordCommands}; use alloy::transports::http::reqwest::Url; use anyhow::anyhow; +use anyhow::bail; use anyhow::Result; use config::load_config; +use config::RPC; use dialoguer::{theme::ColorfulTheme, Input}; use enclave_core::get_tag; use std::fs; @@ -25,32 +27,28 @@ pub async fn execute() -> Result<()> { let rpc_url = Input::::new() .with_prompt("Enter WebSocket devnet RPC URL") .default("wss://ethereum-sepolia-rpc.publicnode.com".to_string()) - .validate_with(|input: &String| { - if let Ok(url) = Url::parse(input) { - if url.scheme() == "wss" { - return Ok(()); - } - } - Err("Please enter a valid WSS URL") + .validate_with(|input: &String| -> Result<()> { + RPC::from_url(input)?; + Ok(()) }) .interact_text()?; let eth_address: Option = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter your Ethereum address (press Enter to skip)") .allow_empty(true) - .validate_with(|input: &String| -> Result<(), &str> { + .validate_with(|input: &String| -> Result<()> { if input.is_empty() { return Ok(()); } if !input.starts_with("0x") { - return Err("Address must start with '0x'"); + bail!("Address must start with '0x'") } if input.len() != 42 { - return Err("Address must be 42 characters long (including '0x')"); + bail!("Address must be 42 characters long (including '0x')") } for c in input[2..].chars() { if !c.is_ascii_hexdigit() { - return Err("Address must contain only hexadecimal characters"); + bail!("Address must contain only hexadecimal characters") } } Ok(()) @@ -61,7 +59,7 @@ pub async fn execute() -> Result<()> { .flatten(); let config_dir = dirs::home_dir() - .ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))? + .ok_or_else(|| anyhow!("Could not determine home directory"))? .join(".config") .join("enclave"); fs::create_dir_all(&config_dir)?; diff --git a/packages/ciphernode/enclave_node/src/aggregator.rs b/packages/ciphernode/enclave_node/src/aggregator.rs index 1cfbfbdf..b8931f7b 100644 --- a/packages/ciphernode/enclave_node/src/aggregator.rs +++ b/packages/ciphernode/enclave_node/src/aggregator.rs @@ -4,7 +4,7 @@ use cipher::Cipher; use config::AppConfig; use enclave_core::EventBus; use evm::{ - helpers::{get_signer_from_repository, ProviderConfig, RPC}, + helpers::{get_signer_from_repository, ProviderConfig}, CiphernodeRegistrySol, EnclaveSol, RegistryFilterSol, }; use logger::SimpleLogger; @@ -42,9 +42,7 @@ pub async fn setup_aggregator( .iter() .filter(|chain| chain.enabled.unwrap_or(true)) { - let rpc_url = RPC::from_url(&chain.rpc_url).map_err(|e| { - anyhow::anyhow!("Failed to parse RPC URL for chain {}: {}", chain.name, e) - })?; + let rpc_url = chain.rpc_url()?; let provider_config = ProviderConfig::new(rpc_url, chain.rpc_auth.clone()); let read_provider = provider_config.create_readonly_provider().await?; let write_provider = provider_config.create_ws_signer_provider(&signer).await?; diff --git a/packages/ciphernode/enclave_node/src/ciphernode.rs b/packages/ciphernode/enclave_node/src/ciphernode.rs index 4e760614..6448e616 100644 --- a/packages/ciphernode/enclave_node/src/ciphernode.rs +++ b/packages/ciphernode/enclave_node/src/ciphernode.rs @@ -4,10 +4,7 @@ use anyhow::Result; use cipher::Cipher; use config::AppConfig; use enclave_core::{get_tag, EventBus}; -use evm::{ - helpers::{ProviderConfig, RPC}, - CiphernodeRegistrySol, EnclaveSolReader, -}; +use evm::{helpers::ProviderConfig, CiphernodeRegistrySol, EnclaveSolReader}; use logger::SimpleLogger; use net::NetworkManager; use rand::SeedableRng; @@ -44,9 +41,7 @@ pub async fn setup_ciphernode( .iter() .filter(|chain| chain.enabled.unwrap_or(true)) { - let rpc_url = RPC::from_url(&chain.rpc_url).map_err(|e| { - anyhow::anyhow!("Failed to parse RPC URL for chain {}: {}", chain.name, e) - })?; + let rpc_url = chain.rpc_url()?; let provider_config = ProviderConfig::new(rpc_url, chain.rpc_auth.clone()); let read_provider = provider_config.create_readonly_provider().await?; EnclaveSolReader::attach( diff --git a/packages/ciphernode/evm/src/helpers.rs b/packages/ciphernode/evm/src/helpers.rs index 9f6d7373..20a08706 100644 --- a/packages/ciphernode/evm/src/helpers.rs +++ b/packages/ciphernode/evm/src/helpers.rs @@ -25,67 +25,10 @@ use alloy::{ use anyhow::{bail, Context, Result}; use base64::{engine::general_purpose::STANDARD, Engine}; use cipher::Cipher; -use config::RpcAuth; +use config::{RpcAuth, RPC}; use data::Repository; use std::{env, marker::PhantomData, sync::Arc}; -use url::Url; use zeroize::Zeroizing; - -#[derive(Clone)] -pub enum RPC { - Http(String), - Https(String), - Ws(String), - Wss(String), -} - -impl RPC { - pub fn from_url(url: &str) -> Result { - let parsed = Url::parse(url).context("Invalid URL format")?; - match parsed.scheme() { - "http" => Ok(RPC::Http(url.to_string())), - "https" => Ok(RPC::Https(url.to_string())), - "ws" => Ok(RPC::Ws(url.to_string())), - "wss" => Ok(RPC::Wss(url.to_string())), - _ => bail!("Invalid protocol. Expected: http://, https://, ws://, wss://"), - } - } - - pub fn as_http_url(&self) -> String { - match self { - RPC::Http(url) | RPC::Https(url) => url.clone(), - RPC::Ws(url) | RPC::Wss(url) => { - let mut parsed = Url::parse(url).expect(&format!("Failed to parse URL: {}", url)); - parsed - .set_scheme(if self.is_secure() { "https" } else { "http" }) - .expect("http(s) are valid schemes"); - parsed.to_string() - } - } - } - - pub fn as_ws_url(&self) -> String { - match self { - RPC::Ws(url) | RPC::Wss(url) => url.clone(), - RPC::Http(url) | RPC::Https(url) => { - let mut parsed = Url::parse(url).expect(&format!("Failed to parse URL: {}", url)); - parsed - .set_scheme(if self.is_secure() { "wss" } else { "ws" }) - .expect("ws(s) are valid schemes"); - parsed.to_string() - } - } - } - - pub fn is_websocket(&self) -> bool { - matches!(self, RPC::Ws(_) | RPC::Wss(_)) - } - - pub fn is_secure(&self) -> bool { - matches!(self, RPC::Https(_) | RPC::Wss(_)) - } -} - pub trait AuthConversions { fn to_header_value(&self) -> Option; fn to_ws_auth(&self) -> Option; From 38cc61efebe3fae431a418e779f642f5f29066e8 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 16:25:26 +1100 Subject: [PATCH 04/22] Make it possible to pass in all params to init --- .../ciphernode/enclave/src/commands/init.rs | 101 ++++++++++++------ .../ciphernode/enclave/src/commands/mod.rs | 20 +++- .../enclave/src/commands/password/create.rs | 6 +- .../enclave/src/commands/password/mod.rs | 5 +- .../src/commands/password/overwrite.rs | 2 +- packages/ciphernode/enclave/src/main.rs | 7 +- 6 files changed, 101 insertions(+), 40 deletions(-) diff --git a/packages/ciphernode/enclave/src/commands/init.rs b/packages/ciphernode/enclave/src/commands/init.rs index 926e844f..908d09c2 100644 --- a/packages/ciphernode/enclave/src/commands/init.rs +++ b/packages/ciphernode/enclave/src/commands/init.rs @@ -1,5 +1,4 @@ use crate::commands::password::{self, PasswordCommands}; -use alloy::transports::http::reqwest::Url; use anyhow::anyhow; use anyhow::bail; use anyhow::Result; @@ -22,41 +21,68 @@ fn get_contract_info(name: &str) -> Result<&ContractInfo> { .ok_or(anyhow!("Could not get contract info"))?) } +fn validate_rpc_url(url: &String) -> Result<()> { + RPC::from_url(url)?; + Ok(()) +} + +fn validate_eth_address(address: &String) -> Result<()> { + if address.is_empty() { + return Ok(()); + } + if !address.starts_with("0x") { + bail!("Address must start with '0x'") + } + if address.len() != 42 { + bail!("Address must be 42 characters long (including '0x')") + } + for c in address[2..].chars() { + if !c.is_ascii_hexdigit() { + bail!("Address must contain only hexadecimal characters") + } + } + Ok(()) +} + #[instrument(name = "app", skip_all, fields(id = get_tag()))] -pub async fn execute() -> Result<()> { - let rpc_url = Input::::new() - .with_prompt("Enter WebSocket devnet RPC URL") - .default("wss://ethereum-sepolia-rpc.publicnode.com".to_string()) - .validate_with(|input: &String| -> Result<()> { - RPC::from_url(input)?; - Ok(()) - }) - .interact_text()?; +pub async fn execute( + rpc_url: Option, + eth_address: Option, + password: Option, + skip_eth: bool, +) -> Result<()> { + let rpc_url = match rpc_url { + Some(url) => { + validate_rpc_url(&url)?; + url + } + None => Input::::new() + .with_prompt("Enter WebSocket devnet RPC URL") + .default("wss://ethereum-sepolia-rpc.publicnode.com".to_string()) + .validate_with(validate_rpc_url) + .interact_text()?, + }; - let eth_address: Option = Input::with_theme(&ColorfulTheme::default()) - .with_prompt("Enter your Ethereum address (press Enter to skip)") - .allow_empty(true) - .validate_with(|input: &String| -> Result<()> { - if input.is_empty() { - return Ok(()); - } - if !input.starts_with("0x") { - bail!("Address must start with '0x'") - } - if input.len() != 42 { - bail!("Address must be 42 characters long (including '0x')") - } - for c in input[2..].chars() { - if !c.is_ascii_hexdigit() { - bail!("Address must contain only hexadecimal characters") - } + let eth_address: Option = match eth_address { + Some(address) => { + validate_eth_address(&address)?; + Some(address) + } + None => { + if skip_eth { + None + } else { + Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter your Ethereum address (press Enter to skip)") + .allow_empty(true) + .validate_with(validate_eth_address) + .interact() + .ok() + .map(|s| if s.is_empty() { None } else { Some(s) }) + .flatten() } - Ok(()) - }) - .interact() - .ok() - .map(|s| if s.is_empty() { None } else { Some(s) }) - .flatten(); + } + }; let config_dir = dirs::home_dir() .ok_or_else(|| anyhow!("Could not determine home directory"))? @@ -102,7 +128,14 @@ chains: // Load with default location let config = load_config(Some(&config_path.display().to_string()))?; - password::execute(PasswordCommands::Create { password: None }, config).await?; + password::execute( + PasswordCommands::Create { + password, + overwrite: true, + }, + config, + ) + .await?; println!("Enclave configuration successfully created!"); println!("You can start your node using `enclave start`"); diff --git a/packages/ciphernode/enclave/src/commands/mod.rs b/packages/ciphernode/enclave/src/commands/mod.rs index 647263ef..3ed2998d 100644 --- a/packages/ciphernode/enclave/src/commands/mod.rs +++ b/packages/ciphernode/enclave/src/commands/mod.rs @@ -1,9 +1,9 @@ pub mod aggregator; +pub mod init; pub mod net; pub mod password; pub mod start; pub mod wallet; -pub mod init; use self::password::PasswordCommands; use aggregator::AggregatorCommands; @@ -40,5 +40,21 @@ pub enum Commands { command: NetCommands, }, - Init + Init { + /// Testing only: A path to write the latest pubkey to + #[arg(long = "rpc-url")] + rpc_url: Option, + + /// Testing only: A path to write the latest plaintexts to + #[arg(long = "eth-address")] + eth_address: Option, + + /// The password + #[arg(short, long)] + password: Option, + + /// Skip asking for eth + #[arg(long = "skip-eth")] + skip_eth: bool, + }, } diff --git a/packages/ciphernode/enclave/src/commands/password/create.rs b/packages/ciphernode/enclave/src/commands/password/create.rs index 4a9a454e..2f1d11df 100644 --- a/packages/ciphernode/enclave/src/commands/password/create.rs +++ b/packages/ciphernode/enclave/src/commands/password/create.rs @@ -40,10 +40,14 @@ fn get_zeroizing_pw_vec(input: Option) -> Result>> { Ok(pw) } -pub async fn execute(config: &AppConfig, input: Option) -> Result<()> { +pub async fn execute(config: &AppConfig, input: Option, overwrite: bool) -> Result<()> { let key_file = config.key_file(); let mut pm = FilePasswordManager::new(key_file); + if overwrite { + pm.delete_key().await?; + } + if pm.is_set() { bail!("Keyfile already exists. Refusing to overwrite. Try using `enclave password overwrite` or `enclave password delete` in order to change or delete your password.") } diff --git a/packages/ciphernode/enclave/src/commands/password/mod.rs b/packages/ciphernode/enclave/src/commands/password/mod.rs index 8067bc1e..b6eb1127 100644 --- a/packages/ciphernode/enclave/src/commands/password/mod.rs +++ b/packages/ciphernode/enclave/src/commands/password/mod.rs @@ -12,6 +12,9 @@ pub enum PasswordCommands { /// The new password #[arg(short, long)] password: Option, + + #[arg(short, long)] + overwrite: bool }, /// Delete the current password @@ -27,7 +30,7 @@ pub enum PasswordCommands { pub async fn execute(command: PasswordCommands, config: AppConfig) -> Result<()> { match command { - PasswordCommands::Create { password } => create::execute(&config, password).await?, + PasswordCommands::Create { password, overwrite } => create::execute(&config, password, overwrite).await?, PasswordCommands::Delete => delete::execute(&config).await?, PasswordCommands::Overwrite { password } => overwrite::execute(&config, password).await?, }; diff --git a/packages/ciphernode/enclave/src/commands/password/overwrite.rs b/packages/ciphernode/enclave/src/commands/password/overwrite.rs index 301539d5..1720bb36 100644 --- a/packages/ciphernode/enclave/src/commands/password/overwrite.rs +++ b/packages/ciphernode/enclave/src/commands/password/overwrite.rs @@ -5,7 +5,7 @@ use config::AppConfig; pub async fn execute(config: &AppConfig, input: Option) -> Result<()> { if prompt_delete(config, DeleteMode::Overwrite).await? { - set_password(config, input).await?; + set_password(config, input, true).await?; } Ok(()) } diff --git a/packages/ciphernode/enclave/src/main.rs b/packages/ciphernode/enclave/src/main.rs index 5d5acf96..808b7629 100644 --- a/packages/ciphernode/enclave/src/main.rs +++ b/packages/ciphernode/enclave/src/main.rs @@ -50,7 +50,12 @@ impl Cli { match self.command { Commands::Start => start::execute(config).await?, - Commands::Init => init::execute().await?, + Commands::Init { + rpc_url, + eth_address, + password, + skip_eth, + } => init::execute(rpc_url, eth_address, password, skip_eth).await?, Commands::Password { command } => password::execute(command, config).await?, Commands::Aggregator { command } => aggregator::execute(command, config).await?, Commands::Wallet { command } => wallet::execute(command, config).await?, From 96d2757a957000885716b8874828c6b3727d3215 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 16:26:02 +1100 Subject: [PATCH 05/22] Formatting --- packages/ciphernode/enclave/src/commands/password/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ciphernode/enclave/src/commands/password/mod.rs b/packages/ciphernode/enclave/src/commands/password/mod.rs index b6eb1127..f329ec2e 100644 --- a/packages/ciphernode/enclave/src/commands/password/mod.rs +++ b/packages/ciphernode/enclave/src/commands/password/mod.rs @@ -14,7 +14,7 @@ pub enum PasswordCommands { password: Option, #[arg(short, long)] - overwrite: bool + overwrite: bool, }, /// Delete the current password @@ -30,7 +30,10 @@ pub enum PasswordCommands { pub async fn execute(command: PasswordCommands, config: AppConfig) -> Result<()> { match command { - PasswordCommands::Create { password, overwrite } => create::execute(&config, password, overwrite).await?, + PasswordCommands::Create { + password, + overwrite, + } => create::execute(&config, password, overwrite).await?, PasswordCommands::Delete => delete::execute(&config).await?, PasswordCommands::Overwrite { password } => overwrite::execute(&config, password).await?, }; From d5ead902afe4b48d903ad354e3e8dbf1c4132fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=B3=CE=BB?= Date: Sat, 7 Dec 2024 16:51:08 +1100 Subject: [PATCH 06/22] Update user_guide.md --- packages/ciphernode/docs/user_guide.md | 53 +++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/ciphernode/docs/user_guide.md b/packages/ciphernode/docs/user_guide.md index 96e348c2..3311155f 100644 --- a/packages/ciphernode/docs/user_guide.md +++ b/packages/ciphernode/docs/user_guide.md @@ -1,10 +1,59 @@ # Running a Ciphernode +_NOTE: passing an address to a node may not be required in future versions as we may be moving towards BLS keys_ + +You can use the cli to setup your node: + +``` +$ enclave init +Enter WebSocket devnet RPC URL [wss://ethereum-sepolia-rpc.publicnode.com]: wss://ethereum-sepolia-rpc.publicnode.com +✔ Enter your Ethereum address (press Enter to skip) · 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + + +Please enter a new password: +Please confirm your password: +Password sucessfully set. +Enclave configuration successfully created! +You can start your node using `enclave start` +``` + +This will setup an initial configuration: + +``` +$ cat ~/.config/enclave/config.yaml +--- +# Enclave Configuration File +# Ethereum Account Configuration +address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" +chains: + - name: "devnet" + rpc_url: "wss://ethereum-sepolia-rpc.publicnode.com" + contracts: + enclave: + address: "0xCe087F31e20E2F76b6544A2E4A74D4557C8fDf77" + deploy_block: 7073317 + ciphernode_registry: + address: "0x0952388f6028a9Eda93a5041a3B216Ea331d97Ab" + deploy_block: 7073318 + filter_registry: + address: "0xcBaCE7C360b606bb554345b20884A28e41436934" + deploy_block: 7073319 +``` + +It will also setup the nodes key_file in the following path: + ``` -enclave init +~/.config/enclave/key ``` -Will setup an initial configuration +You can now setup your wallet if you have your node configured for writing to the blockchain: + +``` +$ enclave wallet set --private-key "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +``` + +_*NOTE: the above key is known and taken from the default hardhat mnemonic._ + ## Configuration From 02d222ae3d55ce3a7c729eed89ee35e95e0f8273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=B3=CE=BB?= Date: Sat, 7 Dec 2024 16:53:27 +1100 Subject: [PATCH 07/22] Update user_guide.md --- packages/ciphernode/docs/user_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ciphernode/docs/user_guide.md b/packages/ciphernode/docs/user_guide.md index 3311155f..c7c1bb69 100644 --- a/packages/ciphernode/docs/user_guide.md +++ b/packages/ciphernode/docs/user_guide.md @@ -49,11 +49,11 @@ It will also setup the nodes key_file in the following path: You can now setup your wallet if you have your node configured for writing to the blockchain: ``` +# Example key DO NOT USE $ enclave wallet set --private-key "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" ``` -_*NOTE: the above key is known and taken from the default hardhat mnemonic._ - +_*NOTE: do not use the above private key as this is obviously public and all funds will be lost_ ## Configuration From 71ccc8f678ad0816678a342e2f410de2f6f288e5 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 16:54:19 +1100 Subject: [PATCH 08/22] Fix issue with pm and add test --- .../ciphernode/enclave/src/commands/init.rs | 22 +++++++++++++++++++ .../enclave/src/commands/password/create.rs | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/ciphernode/enclave/src/commands/init.rs b/packages/ciphernode/enclave/src/commands/init.rs index 908d09c2..65b16658 100644 --- a/packages/ciphernode/enclave/src/commands/init.rs +++ b/packages/ciphernode/enclave/src/commands/init.rs @@ -142,3 +142,25 @@ chains: Ok(()) } + +#[cfg(test)] +mod tests { + use super::validate_eth_address; + use anyhow::Result; + + #[test] + fn eth_address_validation() -> Result<()> { + assert!( + validate_eth_address(&"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".to_string()).is_ok() + ); + assert!( + validate_eth_address(&"d8dA6BF26964aF9D7eEd9e03E53415D37aA96045".to_string()).is_err() + ); + assert!(validate_eth_address(&"0x1234567890abcdef".to_string()).is_err()); + assert!( + validate_eth_address(&"0x0000000000000000000000000000000000000000".to_string()).is_ok() + ); + + Ok(()) + } +} diff --git a/packages/ciphernode/enclave/src/commands/password/create.rs b/packages/ciphernode/enclave/src/commands/password/create.rs index 2f1d11df..e809a1ad 100644 --- a/packages/ciphernode/enclave/src/commands/password/create.rs +++ b/packages/ciphernode/enclave/src/commands/password/create.rs @@ -44,7 +44,7 @@ pub async fn execute(config: &AppConfig, input: Option, overwrite: bool) let key_file = config.key_file(); let mut pm = FilePasswordManager::new(key_file); - if overwrite { + if overwrite && pm.is_set() { pm.delete_key().await?; } From 8944606ee6041a422a98dc275633f7b4db3f0707 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 17:24:24 +1100 Subject: [PATCH 09/22] Update password input to use dialoguer --- packages/ciphernode/Cargo.lock | 22 -------- packages/ciphernode/enclave/Cargo.toml | 1 - .../enclave/src/commands/password/create.rs | 7 +-- .../enclave/src/commands/password/delete.rs | 5 +- .../enclave/src/commands/password/helpers.rs | 10 ++++ .../enclave/src/commands/password/mod.rs | 2 + .../enclave/src/commands/wallet/mod.rs | 2 +- .../enclave/src/commands/wallet/set.rs | 53 ++++++++++++++++++- 8 files changed, 71 insertions(+), 31 deletions(-) create mode 100644 packages/ciphernode/enclave/src/commands/password/helpers.rs diff --git a/packages/ciphernode/Cargo.lock b/packages/ciphernode/Cargo.lock index 33238622..fde6c003 100644 --- a/packages/ciphernode/Cargo.lock +++ b/packages/ciphernode/Cargo.lock @@ -2172,7 +2172,6 @@ dependencies = [ "once_cell", "phf", "router", - "rpassword", "serde", "serde_json", "tokio", @@ -5282,17 +5281,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "rpassword" -version = "7.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" -dependencies = [ - "libc", - "rtoolbox", - "windows-sys 0.48.0", -] - [[package]] name = "rtnetlink" version = "0.10.1" @@ -5309,16 +5297,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "rtoolbox" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ruint" version = "1.12.3" diff --git a/packages/ciphernode/enclave/Cargo.toml b/packages/ciphernode/enclave/Cargo.toml index 9df066dd..d38cf579 100644 --- a/packages/ciphernode/enclave/Cargo.toml +++ b/packages/ciphernode/enclave/Cargo.toml @@ -21,7 +21,6 @@ enclave_node = { path = "../enclave_node" } hex = { workspace = true } once_cell = "1.20.2" router = { path = "../router" } -rpassword = "7.3.1" tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/packages/ciphernode/enclave/src/commands/password/create.rs b/packages/ciphernode/enclave/src/commands/password/create.rs index e809a1ad..382d87e6 100644 --- a/packages/ciphernode/enclave/src/commands/password/create.rs +++ b/packages/ciphernode/enclave/src/commands/password/create.rs @@ -1,9 +1,10 @@ use anyhow::{bail, Result}; use cipher::{FilePasswordManager, PasswordManager}; use config::AppConfig; -use rpassword::prompt_password; use zeroize::{Zeroize, Zeroizing}; +use super::prompt_password; + fn get_zeroizing_pw_vec(input: Option) -> Result>> { if let Some(mut pw_str) = input { if pw_str.trim().is_empty() { @@ -15,13 +16,13 @@ fn get_zeroizing_pw_vec(input: Option) -> Result>> { } // First password entry - let mut pw_str = prompt_password("\n\nPlease enter a new password: ")?; + let mut pw_str = prompt_password("Please enter a new password")?; if pw_str.trim().is_empty() { bail!("Password must not be blank") } // Second password entry for confirmation - let mut confirm_pw_str = prompt_password("Please confirm your password: ")?; + let mut confirm_pw_str = prompt_password("Please confirm your password")?; // Check if passwords match if pw_str.trim() != confirm_pw_str.trim() { diff --git a/packages/ciphernode/enclave/src/commands/password/delete.rs b/packages/ciphernode/enclave/src/commands/password/delete.rs index 76d2e8ae..0bf07077 100644 --- a/packages/ciphernode/enclave/src/commands/password/delete.rs +++ b/packages/ciphernode/enclave/src/commands/password/delete.rs @@ -2,9 +2,10 @@ use anyhow::*; use cipher::{FilePasswordManager, PasswordManager}; use config::AppConfig; use dialoguer::{theme::ColorfulTheme, Confirm}; -use rpassword::prompt_password; use zeroize::Zeroize; +use super::prompt_password; + pub enum DeleteMode { Delete, Overwrite, @@ -35,7 +36,7 @@ pub async fn prompt_delete(config: &AppConfig, delete_mode: DeleteMode) -> Resul println!("Password is not set. Nothing to do."); return Ok(false); } - let mut pw_str = prompt_password("\n\nPlease enter the current password: ")?; + let mut pw_str = prompt_password("Please enter the current password")?; let mut cur_pw = pm.get_key().await?; if pw_str != String::from_utf8_lossy(&cur_pw) { diff --git a/packages/ciphernode/enclave/src/commands/password/helpers.rs b/packages/ciphernode/enclave/src/commands/password/helpers.rs new file mode 100644 index 00000000..2a2a7d05 --- /dev/null +++ b/packages/ciphernode/enclave/src/commands/password/helpers.rs @@ -0,0 +1,10 @@ +use dialoguer::{theme::ColorfulTheme, Password}; +use anyhow::Result; + +pub fn prompt_password(prompt: impl Into) -> Result { + let password = Password::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact()?; + + Ok(password) +} diff --git a/packages/ciphernode/enclave/src/commands/password/mod.rs b/packages/ciphernode/enclave/src/commands/password/mod.rs index f329ec2e..3924bdda 100644 --- a/packages/ciphernode/enclave/src/commands/password/mod.rs +++ b/packages/ciphernode/enclave/src/commands/password/mod.rs @@ -1,9 +1,11 @@ mod create; mod delete; mod overwrite; +mod helpers; use anyhow::*; use clap::Subcommand; use config::AppConfig; +use helpers::*; #[derive(Subcommand, Debug)] pub enum PasswordCommands { diff --git a/packages/ciphernode/enclave/src/commands/wallet/mod.rs b/packages/ciphernode/enclave/src/commands/wallet/mod.rs index 43130405..6c41c82b 100644 --- a/packages/ciphernode/enclave/src/commands/wallet/mod.rs +++ b/packages/ciphernode/enclave/src/commands/wallet/mod.rs @@ -10,7 +10,7 @@ pub enum WalletCommands { /// The new private key - note we are leaving as hex string as it is easier to manage with /// the allow Signer coercion #[arg(long = "private-key", value_parser = ensure_hex)] - private_key: String, + private_key: Option, }, } diff --git a/packages/ciphernode/enclave/src/commands/wallet/set.rs b/packages/ciphernode/enclave/src/commands/wallet/set.rs index ae6d5773..9314595d 100644 --- a/packages/ciphernode/enclave/src/commands/wallet/set.rs +++ b/packages/ciphernode/enclave/src/commands/wallet/set.rs @@ -1,11 +1,60 @@ use actix::Actor; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use cipher::Cipher; use config::AppConfig; +use dialoguer::{theme::ColorfulTheme, Input, Password}; use enclave_core::{EventBus, GetErrors}; use enclave_node::get_repositories; -pub async fn execute(config: &AppConfig, input: String) -> Result<()> { +pub fn validate_private_key(input: &String) -> Result<()> { + // Remove 0x prefix if present + let key = if input.starts_with("0x") { + &input[2..] + } else if input.len() == 64 { + input + } else { + return Err(anyhow!( + "Invalid private key length: {}. Expected 64 characters (or 66 with '0x' prefix)", + input.len() + )); + }; + + // Check length + if key.len() != 64 { + return Err(anyhow!( + "Invalid private key length: {}. Expected 64 characters", + key.len() + )); + } + + // Validate hex characters and convert to bytes + let bytes = (0..key.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&key[i..i + 2], 16)) + .collect::, _>>() + .map_err(|e| anyhow!("Invalid hex character: {}", e))?; + + // Check if the key is in the valid range (1 <= key < secp256k1_n) + let secp256k1_n = + hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141").unwrap(); + if bytes.len() != 32 || bytes > secp256k1_n || bytes.iter().all(|&b| b == 0) { + return Err(anyhow!("Private key value is out of valid range")); + } + + Ok(()) +} +pub async fn execute(config: &AppConfig, private_key: Option) -> Result<()> { + let input = if let Some(private_key) = private_key { + private_key + } else { + Password::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter your Ethereum private key") + .validate_with(validate_private_key) + .interact()? + .trim() + .to_string() + }; + let cipher = Cipher::from_config(config).await?; let encrypted = cipher.encrypt_data(&mut input.as_bytes().to_vec())?; let bus = EventBus::new(true).start(); From 00e1bfa24d2c7f629073687646ddc9ca1a9006e0 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 17:28:02 +1100 Subject: [PATCH 10/22] Formatting --- packages/ciphernode/enclave/src/commands/password/helpers.rs | 4 ++-- packages/ciphernode/enclave/src/commands/password/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ciphernode/enclave/src/commands/password/helpers.rs b/packages/ciphernode/enclave/src/commands/password/helpers.rs index 2a2a7d05..d0a4f91d 100644 --- a/packages/ciphernode/enclave/src/commands/password/helpers.rs +++ b/packages/ciphernode/enclave/src/commands/password/helpers.rs @@ -1,10 +1,10 @@ -use dialoguer::{theme::ColorfulTheme, Password}; use anyhow::Result; +use dialoguer::{theme::ColorfulTheme, Password}; pub fn prompt_password(prompt: impl Into) -> Result { let password = Password::with_theme(&ColorfulTheme::default()) .with_prompt(prompt) .interact()?; - + Ok(password) } diff --git a/packages/ciphernode/enclave/src/commands/password/mod.rs b/packages/ciphernode/enclave/src/commands/password/mod.rs index 3924bdda..5ebaf984 100644 --- a/packages/ciphernode/enclave/src/commands/password/mod.rs +++ b/packages/ciphernode/enclave/src/commands/password/mod.rs @@ -1,7 +1,7 @@ mod create; mod delete; -mod overwrite; mod helpers; +mod overwrite; use anyhow::*; use clap::Subcommand; use config::AppConfig; From ae50d13008bfd59c245b37d2c8c3882a62b6d5ee Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:11:25 +1100 Subject: [PATCH 11/22] Remove expect --- packages/ciphernode/config/src/app_config.rs | 19 ++++++++++--------- .../ciphernode/enclave_node/src/aggregator.rs | 2 +- packages/ciphernode/evm/src/helpers.rs | 16 ++++++++-------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/ciphernode/config/src/app_config.rs b/packages/ciphernode/config/src/app_config.rs index 0d5791d4..7f646348 100644 --- a/packages/ciphernode/config/src/app_config.rs +++ b/packages/ciphernode/config/src/app_config.rs @@ -44,28 +44,29 @@ impl RPC { } } - pub fn as_http_url(&self) -> String { + pub fn as_http_url(&self) -> Result { match self { - RPC::Http(url) | RPC::Https(url) => url.clone(), + RPC::Http(url) | RPC::Https(url) => Ok(url.clone()), RPC::Ws(url) | RPC::Wss(url) => { let mut parsed = Url::parse(url).expect(&format!("Failed to parse URL: {}", url)); parsed .set_scheme(if self.is_secure() { "https" } else { "http" }) - .expect("http(s) are valid schemes"); - parsed.to_string() + .map_err(|_| anyhow!("http(s) are valid schemes"))?; + Ok(parsed.to_string()) } } } - pub fn as_ws_url(&self) -> String { + pub fn as_ws_url(&self) -> Result { match self { - RPC::Ws(url) | RPC::Wss(url) => url.clone(), + RPC::Ws(url) | RPC::Wss(url) => Ok(url.clone()), RPC::Http(url) | RPC::Https(url) => { - let mut parsed = Url::parse(url).expect(&format!("Failed to parse URL: {}", url)); + let mut parsed = + Url::parse(url).context(format!("Failed to parse URL: {}", url))?; parsed .set_scheme(if self.is_secure() { "wss" } else { "ws" }) - .expect("ws(s) are valid schemes"); - parsed.to_string() + .map_err(|_| anyhow!("ws(s) are valid schemes"))?; + Ok(parsed.to_string()) } } } diff --git a/packages/ciphernode/enclave_node/src/aggregator.rs b/packages/ciphernode/enclave_node/src/aggregator.rs index b8931f7b..4d058d33 100644 --- a/packages/ciphernode/enclave_node/src/aggregator.rs +++ b/packages/ciphernode/enclave_node/src/aggregator.rs @@ -29,7 +29,7 @@ pub async fn setup_aggregator( ) -> Result<(Addr, JoinHandle>, String)> { let bus = EventBus::new(true).start(); let rng = Arc::new(Mutex::new( - ChaCha20Rng::from_rng(OsRng).expect("Failed to create RNG"), + ChaCha20Rng::from_rng(OsRng)?, )); let store = setup_datastore(&config, &bus)?; let repositories = store.repositories(); diff --git a/packages/ciphernode/evm/src/helpers.rs b/packages/ciphernode/evm/src/helpers.rs index 20a08706..168cea26 100644 --- a/packages/ciphernode/evm/src/helpers.rs +++ b/packages/ciphernode/evm/src/helpers.rs @@ -126,7 +126,7 @@ impl ProviderConfig { async fn create_ws_provider(&self) -> Result> { Ok(ProviderBuilder::new() - .on_ws(self.create_ws_connect()) + .on_ws(self.create_ws_connect()?) .await? .boxed()) } @@ -156,7 +156,7 @@ impl ProviderConfig { let provider = ProviderBuilder::new() .with_recommended_fillers() .wallet(wallet) - .on_ws(self.create_ws_connect()) + .on_ws(self.create_ws_connect()?) .await .context("Failed to create WS signer provider")?; @@ -175,12 +175,12 @@ impl ProviderConfig { WithChainId::new(provider).await } - fn create_ws_connect(&self) -> WsConnect { - if let Some(ws_auth) = self.auth.to_ws_auth() { - WsConnect::new(self.rpc.as_ws_url()).with_auth(ws_auth) + fn create_ws_connect(&self) -> Result { + Ok(if let Some(ws_auth) = self.auth.to_ws_auth() { + WsConnect::new(self.rpc.as_ws_url()?).with_auth(ws_auth) } else { - WsConnect::new(self.rpc.as_ws_url()) - } + WsConnect::new(self.rpc.as_ws_url()?) + }) } fn create_http_client(&self) -> Result>> { @@ -192,7 +192,7 @@ impl ProviderConfig { .default_headers(headers) .build() .context("Failed to create HTTP client")?; - let http = Http::with_client(client, self.rpc.as_http_url().parse()?); + let http = Http::with_client(client, self.rpc.as_http_url()?.parse()?); Ok(RpcClient::new(http, false)) } } From ff061858a897cb374db22f7c80e8b6f2bd0e06b5 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:14:05 +1100 Subject: [PATCH 12/22] Fix tests --- packages/ciphernode/evm/src/helpers.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/ciphernode/evm/src/helpers.rs b/packages/ciphernode/evm/src/helpers.rs index 168cea26..08a5921f 100644 --- a/packages/ciphernode/evm/src/helpers.rs +++ b/packages/ciphernode/evm/src/helpers.rs @@ -225,30 +225,32 @@ mod test { use super::*; #[test] - fn test_rpc_type_conversion() { + fn test_rpc_type_conversion() -> Result<()> { // Test HTTP URLs let http = RPC::from_url("http://localhost:8545/").unwrap(); assert!(matches!(http, RPC::Http(_))); - assert_eq!(http.as_http_url(), "http://localhost:8545/"); - assert_eq!(http.as_ws_url(), "ws://localhost:8545/"); + assert_eq!(http.as_http_url()?, "http://localhost:8545/"); + assert_eq!(http.as_ws_url()?, "ws://localhost:8545/"); // Test HTTPS URLs let https = RPC::from_url("https://example.com/").unwrap(); assert!(matches!(https, RPC::Https(_))); - assert_eq!(https.as_http_url(), "https://example.com/"); - assert_eq!(https.as_ws_url(), "wss://example.com/"); + assert_eq!(https.as_http_url()?, "https://example.com/"); + assert_eq!(https.as_ws_url()?, "wss://example.com/"); // Test WS URLs let ws = RPC::from_url("ws://localhost:8545/").unwrap(); assert!(matches!(ws, RPC::Ws(_))); - assert_eq!(ws.as_http_url(), "http://localhost:8545/"); - assert_eq!(ws.as_ws_url(), "ws://localhost:8545/"); + assert_eq!(ws.as_http_url()?, "http://localhost:8545/"); + assert_eq!(ws.as_ws_url()?, "ws://localhost:8545/"); // Test WSS URLs let wss = RPC::from_url("wss://example.com/").unwrap(); assert!(matches!(wss, RPC::Wss(_))); - assert_eq!(wss.as_http_url(), "https://example.com/"); - assert_eq!(wss.as_ws_url(), "wss://example.com/"); + assert_eq!(wss.as_http_url()?, "https://example.com/"); + assert_eq!(wss.as_ws_url()?, "wss://example.com/"); + + Ok(()) } #[test] From b7c268e9f27e5bc93f63f427146007cabe0cda6b Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:14:28 +1100 Subject: [PATCH 13/22] Remove expect --- packages/ciphernode/enclave_node/src/aggregator.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/ciphernode/enclave_node/src/aggregator.rs b/packages/ciphernode/enclave_node/src/aggregator.rs index 4d058d33..b5dfba4e 100644 --- a/packages/ciphernode/enclave_node/src/aggregator.rs +++ b/packages/ciphernode/enclave_node/src/aggregator.rs @@ -28,9 +28,7 @@ pub async fn setup_aggregator( plaintext_write_path: Option<&str>, ) -> Result<(Addr, JoinHandle>, String)> { let bus = EventBus::new(true).start(); - let rng = Arc::new(Mutex::new( - ChaCha20Rng::from_rng(OsRng)?, - )); + let rng = Arc::new(Mutex::new(ChaCha20Rng::from_rng(OsRng)?)); let store = setup_datastore(&config, &bus)?; let repositories = store.repositories(); let sortition = Sortition::attach(&bus, repositories.sortition()).await?; From 41a87ba9849eda0e572e2af29439db1047e85eb2 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:15:48 +1100 Subject: [PATCH 14/22] Remove expect --- packages/ciphernode/config/src/app_config.rs | 3 ++- packages/ciphernode/enclave_node/src/ciphernode.rs | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/ciphernode/config/src/app_config.rs b/packages/ciphernode/config/src/app_config.rs index 7f646348..fd452429 100644 --- a/packages/ciphernode/config/src/app_config.rs +++ b/packages/ciphernode/config/src/app_config.rs @@ -48,7 +48,8 @@ impl RPC { match self { RPC::Http(url) | RPC::Https(url) => Ok(url.clone()), RPC::Ws(url) | RPC::Wss(url) => { - let mut parsed = Url::parse(url).expect(&format!("Failed to parse URL: {}", url)); + let mut parsed = + Url::parse(url).context(format!("Failed to parse URL: {}", url))?; parsed .set_scheme(if self.is_secure() { "https" } else { "http" }) .map_err(|_| anyhow!("http(s) are valid schemes"))?; diff --git a/packages/ciphernode/enclave_node/src/ciphernode.rs b/packages/ciphernode/enclave_node/src/ciphernode.rs index 6448e616..5b1e60d9 100644 --- a/packages/ciphernode/enclave_node/src/ciphernode.rs +++ b/packages/ciphernode/enclave_node/src/ciphernode.rs @@ -24,9 +24,7 @@ pub async fn setup_ciphernode( config: AppConfig, address: Address, ) -> Result<(Addr, JoinHandle>, String)> { - let rng = Arc::new(Mutex::new( - rand_chacha::ChaCha20Rng::from_rng(OsRng).expect("Failed to create RNG"), - )); + let rng = Arc::new(Mutex::new(rand_chacha::ChaCha20Rng::from_rng(OsRng)?)); let bus = EventBus::new(true).start(); let cipher = Arc::new(Cipher::from_config(&config).await?); let store = setup_datastore(&config, &bus)?; From 8f6b5b34ea7c3fa49c8dbf6c7aa2c0d29d7950ce Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:20:27 +1100 Subject: [PATCH 15/22] unwrap --- packages/ciphernode/config/src/app_config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ciphernode/config/src/app_config.rs b/packages/ciphernode/config/src/app_config.rs index fd452429..133c23be 100644 --- a/packages/ciphernode/config/src/app_config.rs +++ b/packages/ciphernode/config/src/app_config.rs @@ -322,10 +322,12 @@ fn expand_tilde(path: &Path) -> PathBuf { struct OsDirs; impl OsDirs { pub fn config_dir() -> PathBuf { + // TODO: handle unwrap error case dirs::config_dir().unwrap().join("enclave") } pub fn data_dir() -> PathBuf { + // TODO: handle unwrap error case dirs::data_local_dir().unwrap().join("enclave") } } From 31fda4a44b24da5d6ac2a69ac94ead90c5b326d7 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:22:43 +1100 Subject: [PATCH 16/22] Ensure consistency with other validation --- .../enclave/src/commands/wallet/set.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/ciphernode/enclave/src/commands/wallet/set.rs b/packages/ciphernode/enclave/src/commands/wallet/set.rs index 9314595d..cc5488ec 100644 --- a/packages/ciphernode/enclave/src/commands/wallet/set.rs +++ b/packages/ciphernode/enclave/src/commands/wallet/set.rs @@ -7,22 +7,20 @@ use enclave_core::{EventBus, GetErrors}; use enclave_node::get_repositories; pub fn validate_private_key(input: &String) -> Result<()> { - // Remove 0x prefix if present - let key = if input.starts_with("0x") { - &input[2..] - } else if input.len() == 64 { - input - } else { + // Require 0x prefix + if !input.starts_with("0x") { return Err(anyhow!( - "Invalid private key length: {}. Expected 64 characters (or 66 with '0x' prefix)", - input.len() + "Invalid private key format: must start with '0x' prefix" )); - }; + } + + // Remove 0x prefix + let key = &input[2..]; // Check length if key.len() != 64 { return Err(anyhow!( - "Invalid private key length: {}. Expected 64 characters", + "Invalid private key length: {}. Expected 64 characters after '0x' prefix", key.len() )); } From fd6ec6de0a2676bb07663cb2cb442b31fb2f19bd Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:23:50 +1100 Subject: [PATCH 17/22] Add docs --- packages/ciphernode/enclave/src/commands/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ciphernode/enclave/src/commands/mod.rs b/packages/ciphernode/enclave/src/commands/mod.rs index 3ed2998d..ec26de49 100644 --- a/packages/ciphernode/enclave/src/commands/mod.rs +++ b/packages/ciphernode/enclave/src/commands/mod.rs @@ -41,11 +41,11 @@ pub enum Commands { }, Init { - /// Testing only: A path to write the latest pubkey to + /// An rpc url for enclave to connect to #[arg(long = "rpc-url")] rpc_url: Option, - /// Testing only: A path to write the latest plaintexts to + /// An Ethereum address that enclave should use to identify the node #[arg(long = "eth-address")] eth_address: Option, From 609372bdf9fc7086d265a3f0eb2e64a314b56065 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:25:42 +1100 Subject: [PATCH 18/22] Fix is_set --- packages/ciphernode/cipher/src/password_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ciphernode/cipher/src/password_manager.rs b/packages/ciphernode/cipher/src/password_manager.rs index 7f30866d..8229e812 100644 --- a/packages/ciphernode/cipher/src/password_manager.rs +++ b/packages/ciphernode/cipher/src/password_manager.rs @@ -57,7 +57,7 @@ impl PasswordManager for EnvPasswordManager { } fn is_set(&self) -> bool { - self.0 == None + self.0.is_some() } } @@ -80,7 +80,7 @@ impl PasswordManager for InMemPasswordManager { } fn is_set(&self) -> bool { - self.0 == None + self.0.is_some() } } From 8b5191f346e7b236313a8cd75583872a972e2339 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 18:27:51 +1100 Subject: [PATCH 19/22] add validation --- packages/ciphernode/enclave/src/commands/wallet/set.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ciphernode/enclave/src/commands/wallet/set.rs b/packages/ciphernode/enclave/src/commands/wallet/set.rs index cc5488ec..3bf872de 100644 --- a/packages/ciphernode/enclave/src/commands/wallet/set.rs +++ b/packages/ciphernode/enclave/src/commands/wallet/set.rs @@ -43,6 +43,7 @@ pub fn validate_private_key(input: &String) -> Result<()> { } pub async fn execute(config: &AppConfig, private_key: Option) -> Result<()> { let input = if let Some(private_key) = private_key { + validate_private_key(&private_key)?; private_key } else { Password::with_theme(&ColorfulTheme::default()) From ab13135dda3d485a7543789b822096b75e777570 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 20:20:36 +1100 Subject: [PATCH 20/22] Remove spacific formatting --- packages/ciphernode/enclave/src/commands/wallet/set.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/ciphernode/enclave/src/commands/wallet/set.rs b/packages/ciphernode/enclave/src/commands/wallet/set.rs index 3bf872de..c9eb6390 100644 --- a/packages/ciphernode/enclave/src/commands/wallet/set.rs +++ b/packages/ciphernode/enclave/src/commands/wallet/set.rs @@ -2,7 +2,7 @@ use actix::Actor; use anyhow::{anyhow, bail, Result}; use cipher::Cipher; use config::AppConfig; -use dialoguer::{theme::ColorfulTheme, Input, Password}; +use dialoguer::{theme::ColorfulTheme, Password}; use enclave_core::{EventBus, GetErrors}; use enclave_node::get_repositories; @@ -32,13 +32,6 @@ pub fn validate_private_key(input: &String) -> Result<()> { .collect::, _>>() .map_err(|e| anyhow!("Invalid hex character: {}", e))?; - // Check if the key is in the valid range (1 <= key < secp256k1_n) - let secp256k1_n = - hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141").unwrap(); - if bytes.len() != 32 || bytes > secp256k1_n || bytes.iter().all(|&b| b == 0) { - return Err(anyhow!("Private key value is out of valid range")); - } - Ok(()) } pub async fn execute(config: &AppConfig, private_key: Option) -> Result<()> { From 7a7e0e4bc1679c4cece9c23747b27326212b3a3c Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 20:21:48 +1100 Subject: [PATCH 21/22] Remove named binding --- packages/ciphernode/enclave/src/commands/wallet/set.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ciphernode/enclave/src/commands/wallet/set.rs b/packages/ciphernode/enclave/src/commands/wallet/set.rs index c9eb6390..0e1faac5 100644 --- a/packages/ciphernode/enclave/src/commands/wallet/set.rs +++ b/packages/ciphernode/enclave/src/commands/wallet/set.rs @@ -26,7 +26,7 @@ pub fn validate_private_key(input: &String) -> Result<()> { } // Validate hex characters and convert to bytes - let bytes = (0..key.len()) + let _ = (0..key.len()) .step_by(2) .map(|i| u8::from_str_radix(&key[i..i + 2], 16)) .collect::, _>>() From 80425c454977f14a7cb568237431b6a630f2a278 Mon Sep 17 00:00:00 2001 From: ryardley Date: Sat, 7 Dec 2024 20:22:11 +1100 Subject: [PATCH 22/22] Formatting --- packages/ciphernode/cipher/src/password_manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ciphernode/cipher/src/password_manager.rs b/packages/ciphernode/cipher/src/password_manager.rs index 8229e812..6cfe02d8 100644 --- a/packages/ciphernode/cipher/src/password_manager.rs +++ b/packages/ciphernode/cipher/src/password_manager.rs @@ -57,7 +57,7 @@ impl PasswordManager for EnvPasswordManager { } fn is_set(&self) -> bool { - self.0.is_some() + self.0.is_some() } } @@ -80,7 +80,7 @@ impl PasswordManager for InMemPasswordManager { } fn is_set(&self) -> bool { - self.0.is_some() + self.0.is_some() } }