From 4c4e225a29e1b05a10b83ac5a0460509a78f75ed Mon Sep 17 00:00:00 2001 From: Marcos Nicolau Date: Mon, 12 Jan 2026 16:29:58 -0300 Subject: [PATCH] feat: keystore flag in cli --- aggregation_mode/Cargo.lock | 22 ++++++++++++++ aggregation_mode/cli/Cargo.toml | 1 + aggregation_mode/cli/src/commands/deposit.rs | 12 ++++---- aggregation_mode/cli/src/commands/helpers.rs | 31 +++++++++++++++++++- aggregation_mode/cli/src/commands/submit.rs | 18 +++++++----- 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/aggregation_mode/Cargo.lock b/aggregation_mode/Cargo.lock index ac5145dd5..f59e6d191 100644 --- a/aggregation_mode/Cargo.lock +++ b/aggregation_mode/Cargo.lock @@ -320,6 +320,7 @@ dependencies = [ "alloy", "bincode", "clap", + "rpassword", "serde", "sp1-sdk", "tokio", @@ -7540,6 +7541,17 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rpassword" +version = "7.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.59.0", +] + [[package]] name = "rrs-lib" version = "0.1.0" @@ -7581,6 +7593,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rtoolbox" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cc970b249fbe527d6e02e0a227762c9108b2f49d81094fe357ffc6d14d7f6f" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "ruint" version = "1.17.0" diff --git a/aggregation_mode/cli/Cargo.toml b/aggregation_mode/cli/Cargo.toml index 44797cc27..d21411363 100644 --- a/aggregation_mode/cli/Cargo.toml +++ b/aggregation_mode/cli/Cargo.toml @@ -13,3 +13,4 @@ alloy = { workspace = true } agg_mode_sdk = { path = "../sdk"} sp1-sdk = "5.0.0" clap = { version = "4.5.4", features = ["derive"] } +rpassword = "7.3.1" diff --git a/aggregation_mode/cli/src/commands/deposit.rs b/aggregation_mode/cli/src/commands/deposit.rs index eb2a5f856..4434ff1cc 100644 --- a/aggregation_mode/cli/src/commands/deposit.rs +++ b/aggregation_mode/cli/src/commands/deposit.rs @@ -3,20 +3,18 @@ use alloy::{ network::{EthereumWallet, TransactionBuilder}, primitives::{Address, U256}, providers::{Provider, ProviderBuilder}, - signers::local::LocalSigner, }; use clap::{self, Args}; -use std::str::FromStr; -use crate::commands::helpers::parse_network; +use crate::commands::helpers::{parse_network, PrivateKeyType}; const PAYMENT_AMOUNT: &str = "1"; // ether /// Send 1 ether to the aggregation mode payment service to fund proof submissions #[derive(Debug, Clone, Args)] pub struct SendPaymentArgs { - #[arg(long = "private-key")] - private_key: String, + #[command(flatten)] + private_key_type: PrivateKeyType, #[arg(short = 'n', long = "network", default_value = "devnet", value_parser = parse_network)] network: Network, #[arg(long = "rpc-url")] @@ -29,10 +27,10 @@ pub async fn run(args: SendPaymentArgs) { args.network ); - let signer = match LocalSigner::from_str(args.private_key.trim()) { + let signer = match args.private_key_type.into_signer() { Ok(s) => s, Err(e) => { - tracing::error!("Failed to parse private key: {e}"); + tracing::error!("{e}"); return; } }; diff --git a/aggregation_mode/cli/src/commands/helpers.rs b/aggregation_mode/cli/src/commands/helpers.rs index a9214d9d0..36b27cb9c 100644 --- a/aggregation_mode/cli/src/commands/helpers.rs +++ b/aggregation_mode/cli/src/commands/helpers.rs @@ -1,4 +1,6 @@ -use clap::{self, ValueEnum}; +use alloy::signers::local::{LocalSigner, PrivateKeySigner}; +use clap::{self, Args, ValueEnum}; +use std::path::PathBuf; use std::str::FromStr; use agg_mode_sdk::types::Network; @@ -14,3 +16,30 @@ pub enum ProvingSystemArg { #[clap(name = "Risc0")] Risc0, } + +#[derive(Args, Debug, Clone)] +#[group(required = true, multiple = false)] +pub struct PrivateKeyType { + #[arg(name = "keystore_path", long = "keystore-path")] + pub keystore_path: Option, + #[arg(name = "private_key", long = "private-key")] + pub private_key: Option, +} + +impl PrivateKeyType { + /// Creates a LocalSigner from either a keystore file or a raw private key. + /// If a keystore path is provided, prompts for the password interactively. + pub fn into_signer(self) -> Result { + if let Some(keystore_path) = self.keystore_path { + let password = rpassword::prompt_password("Please enter your keystore password: ") + .map_err(|e| format!("Failed to read password: {e}"))?; + LocalSigner::decrypt_keystore(&keystore_path, password) + .map_err(|e| format!("Failed to decrypt keystore: {e}")) + } else if let Some(private_key) = self.private_key { + LocalSigner::from_str(private_key.trim()) + .map_err(|e| format!("Failed to parse private key: {e}")) + } else { + Err("Either --keystore-path or --private-key must be provided".to_string()) + } + } +} diff --git a/aggregation_mode/cli/src/commands/submit.rs b/aggregation_mode/cli/src/commands/submit.rs index 41b29d66d..a1705ed56 100644 --- a/aggregation_mode/cli/src/commands/submit.rs +++ b/aggregation_mode/cli/src/commands/submit.rs @@ -1,10 +1,9 @@ use agg_mode_sdk::{gateway::provider::AggregationModeGatewayProvider, types::Network}; -use alloy::signers::local::LocalSigner; use clap::{command, Args, Subcommand}; use sp1_sdk::{SP1ProofWithPublicValues, SP1VerifyingKey}; -use std::{path::PathBuf, str::FromStr}; +use std::path::PathBuf; -use crate::commands::helpers::parse_network; +use crate::commands::helpers::{parse_network, PrivateKeyType}; #[derive(Debug, Subcommand)] pub enum SubmitCommand { @@ -18,8 +17,8 @@ pub struct SubmitSP1Args { proof_path: PathBuf, #[arg(long = "vk")] verifying_key_path: PathBuf, - #[arg(long = "private-key")] - private_key: String, + #[command(flatten)] + private_key_type: PrivateKeyType, #[arg(short = 'n', long = "network", default_value = "devnet", value_parser = parse_network)] network: Network, } @@ -30,8 +29,13 @@ pub async fn run(args: SubmitSP1Args) { let proof = load_proof(&args.proof_path).expect("Valid proof"); let vk = load_vk(&args.verifying_key_path).expect("Valid vk"); - let signer = - LocalSigner::from_str(args.private_key.trim()).expect("failed to parse private key: {e}"); + let signer = match args.private_key_type.into_signer() { + Ok(s) => s, + Err(e) => { + tracing::error!("{e}"); + return; + } + }; let provider = AggregationModeGatewayProvider::new_with_signer(args.network.clone(), signer) .expect("failed to initialize gateway client: {e:?}");