Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 70 additions & 96 deletions src/commands/config.rs
Original file line number Diff line number Diff line change
@@ -1,120 +1,94 @@
use anyhow::{bail, Result};
use crate::utils::{config, crypto, print as p};
use anyhow::Result;
use clap::Subcommand;
use colored::*;

#[derive(Subcommand)]
pub enum ConfigCommands {
/// Get a configuration value
Get {
/// Configuration key to retrieve (e.g., telemetry, network, wallet)
key: String,
},
/// Set a configuration value
Set {
/// Configuration key to set (e.g., telemetry)
key: String,
/// Configuration value (e.g., true, false)
value: String,
},
/// Show all configuration settings
/// Show current global configuration
Show,
/// Set global wallet encryption parameters (Argon2id)
SetEncryption {
/// Argon2 memory cost in KiB (e.g. 65536)
#[arg(long)]
mem: Option<u32>,
/// Argon2 iteration count (e.g. 3)
#[arg(long)]
iterations: Option<u32>,
/// Argon2 parallelism factor (e.g. 4)
#[arg(long)]
parallelism: Option<u32>,
/// Reset to library defaults
#[arg(long, default_value = "false")]
reset: bool,
},
}

pub fn handle_config(cmd: ConfigCommands) -> Result<()> {
pub fn handle(cmd: ConfigCommands) -> Result<()> {
match cmd {
ConfigCommands::Get { key } => handle_get(&key),
ConfigCommands::Set { key, value } => handle_set(&key, &value),
ConfigCommands::Show => handle_show(),
ConfigCommands::Show => show(),
ConfigCommands::SetEncryption {
mem,
iterations,
parallelism,
reset,
} => set_encryption(mem, iterations, parallelism, reset),
}
}

fn handle_get(key: &str) -> Result<()> {
let cfg = crate::utils::config::load()?;
fn show() -> Result<()> {
let cfg = config::load()?;
p::header("StarForge Configuration");
p::separator();

match key.to_lowercase().as_str() {
"telemetry" => {
let enabled = cfg.telemetry_enabled.unwrap_or(true);
println!("{}: {}", key.cyan(), if enabled { "enabled" } else { "disabled" });
Ok(())
}
"network" => {
println!("{}: {}", key.cyan(), cfg.network);
Ok(())
}
_ => {
bail!(
"Unknown configuration key: '{}'\n\nAvailable keys:\n - telemetry\n - network",
key
);
}
}
}

fn handle_set(key: &str, value: &str) -> Result<()> {
let mut cfg = crate::utils::config::load()?;
p::kv("Config file", &config::config_path().display().to_string());
p::kv("Active network", &cfg.network);
p::kv("Telemetry", if cfg.telemetry_enabled.unwrap_or(false) { "enabled" } else { "disabled" });

match key.to_lowercase().as_str() {
"telemetry" => {
let enabled = match value.to_lowercase().as_str() {
"true" | "on" | "enabled" | "yes" => true,
"false" | "off" | "disabled" | "no" => false,
_ => {
bail!(
"Invalid value for telemetry: '{}'\n\nUse 'true'/'enabled'/'on'/'yes' or 'false'/'disabled'/'off'/'no'.",
value
);
}
};
cfg.telemetry_enabled = Some(enabled);
crate::utils::config::save(&cfg)?;
println!(
"✓ {} set to {}",
"telemetry".green(),
if enabled { "enabled" } else { "disabled" }
);
Ok(())
}
"network" => {
crate::utils::config::validate_network(value)?;
cfg.network = value.to_string();
crate::utils::config::save(&cfg)?;
println!("✓ {} set to {}", "network".green(), value);
Ok(())
}
_ => {
bail!(
"Unknown configuration key: '{}'\n\nAvailable keys:\n - telemetry\n - network",
key
);
}
println!();
p::header("Wallet Encryption (Argon2id)");
if let Some(kdf) = &cfg.wallet_encryption {
p::kv("Memory cost", &format!("{} KiB", kdf.mem.unwrap_or(32768)));
p::kv("Iterations", &kdf.iterations.unwrap_or(3).to_string());
p::kv("Parallelism", &kdf.parallelism.unwrap_or(1).to_string());
} else {
p::info("Using default Argon2id parameters:");
p::kv("Memory cost", "32768 KiB (default)");
p::kv("Iterations", "3 (default)");
p::kv("Parallelism", "1 (default)");
}
}

fn handle_show() -> Result<()> {
let cfg = crate::utils::config::load()?;

println!("\n{}", "=== StarForge Configuration ===".bold());
println!();
println!(" {}: {}", "Network".cyan(), cfg.network);
p::separator();
Ok(())
}

let telemetry_status = cfg.telemetry_enabled.unwrap_or(true);
println!(
" {}: {}",
"Telemetry".cyan(),
if telemetry_status { "enabled" } else { "disabled" }
);
fn set_encryption(
mem: Option<u32>,
iterations: Option<u32>,
parallelism: Option<u32>,
reset: bool,
) -> Result<()> {
let mut cfg = config::load()?;

println!("\n{}", "Configuration file:".cyan());
if let Ok(cfg_path) = crate::utils::config::get_config_path() {
println!(" {}", cfg_path.display());
if reset {
cfg.wallet_encryption = None;
config::save(&cfg)?;
p::success("Wallet encryption parameters reset to defaults.");
return Ok(());
}

println!("\n{}", "Data directory:".cyan());
if let Ok(data_dir) = crate::utils::config::get_data_dir() {
println!(" {}", data_dir.display());
if mem.is_none() && iterations.is_none() && parallelism.is_none() {
anyhow::bail!("Provide at least one parameter to set (e.g. --mem 65536) or use --reset");
}

println!();
let mut kdf = cfg.wallet_encryption.unwrap_or_default();
if let Some(m) = mem { kdf.mem = Some(m); }
if let Some(i) = iterations { kdf.iterations = Some(i); }
if let Some(p) = parallelism { kdf.parallelism = Some(p); }

cfg.wallet_encryption = Some(kdf);
config::save(&cfg)?;

p::success("Global wallet encryption parameters updated.");
show()?;
Ok(())
}
8 changes: 7 additions & 1 deletion src/commands/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,13 @@ fn update(name: Option<String>, yes: bool) -> Result<()> {

match status {
Ok(s) if s.success() => {
registry::install_plugin(&pl.name, std::path::Path::new(&pl.path), &pl.source, &pl.starforge_version, &pl.plugin_version)?;
registry::install_plugin(
&pl.name,
std::path::Path::new(&pl.path),
&pl.source,
&pl.starforge_version,
&pl.plugin_version,
)?;
p::success(&format!(" '{}' updated via cargo install", pl.name));
updated += 1;
}
Expand Down
92 changes: 64 additions & 28 deletions src/commands/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,27 @@ use stellar_strkey::ed25519::{PrivateKey as StellarPrivateKey, PublicKey as Stel

const WALLET_BACKUP_VERSION: &str = "1";

fn kdf_options(mem: Option<u32>, iterations: Option<u32>) -> Option<crypto::KdfOptions> {
if mem.is_none() && iterations.is_none() {
None
} else {
Some(crypto::KdfOptions { mem, iterations })
fn kdf_options(
mem: Option<u32>,
iterations: Option<u32>,
parallelism: Option<u32>,
config_default: Option<&crypto::KdfOptions>,
) -> Option<crypto::KdfOptions> {
if mem.is_none() && iterations.is_none() && parallelism.is_none() && config_default.is_none() {
return None;
}

let mut options = config_default.cloned().unwrap_or_default();
if let Some(m) = mem {
options.mem = Some(m);
}
if let Some(i) = iterations {
options.iterations = Some(i);
}
if let Some(p) = parallelism {
options.parallelism = Some(p);
}
Some(options)
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -79,6 +94,15 @@ pub enum WalletCommands {
/// Account index for SEP-0005 path m/44'/148'/index' (requires --mnemonic)
#[arg(long, default_value = "0", requires = "mnemonic")]
account_index: u32,
/// Argon2 memory cost in KiB (requires --encrypt)
#[arg(long, requires = "encrypt")]
mem: Option<u32>,
/// Argon2 iteration count (requires --encrypt)
#[arg(long, requires = "encrypt")]
iterations: Option<u32>,
/// Argon2 parallelism factor (requires --encrypt)
#[arg(long, requires = "encrypt")]
parallelism: Option<u32>,
},
/// List all saved wallets
List,
Expand Down Expand Up @@ -139,21 +163,9 @@ pub enum WalletCommands {
/// Argon2 iteration count (requires --encrypt)
#[arg(long, requires = "encrypt")]
iterations: Option<u32>,
/// Write a pre-rotation backup snapshot to this file before generating
/// the new key. The snapshot is an encrypted JSON file (same format as
/// `wallet export`) containing the current keypair and full rotation
/// history. The previous secret key is also preserved in the in-config
/// rotation history entry so `wallet history` can display it later.
#[arg(long)]
backup: Option<PathBuf>,
},
/// Show the full rotation history for a wallet
History {
/// Wallet name
name: String,
/// Reveal previous secret keys stored in the rotation history
#[arg(long, default_value = "false")]
reveal: bool,
/// Argon2 parallelism factor (requires --encrypt)
#[arg(long, requires = "encrypt")]
parallelism: Option<u32>,
},
/// Export a wallet to a JSON backup file
Export {
Expand Down Expand Up @@ -295,6 +307,9 @@ pub fn handle(cmd: WalletCommands) -> Result<()> {
mnemonic: use_mnemonic,
words,
account_index,
mem,
iterations,
parallelism,
} => create(
name,
fund,
Expand All @@ -304,6 +319,9 @@ pub fn handle(cmd: WalletCommands) -> Result<()> {
use_mnemonic,
words,
account_index,
mem,
iterations,
parallelism,
),
WalletCommands::List => list(),
WalletCommands::Show { name, reveal } => show(name, reveal),
Expand All @@ -324,9 +342,8 @@ pub fn handle(cmd: WalletCommands) -> Result<()> {
encrypt,
mem,
iterations,
backup,
} => rotate_wallet(name, fund, network, encrypt, mem, iterations, backup),
WalletCommands::History { name, reveal } => wallet_history(name, reveal),
parallelism,
} => rotate_wallet(name, fund, network, encrypt, mem, iterations, parallelism),
WalletCommands::Export { name, all, output } => export_wallet(name, all, output),
WalletCommands::Import {
name,
Expand Down Expand Up @@ -496,6 +513,9 @@ fn create(
use_mnemonic: bool,
words: String,
account_index: u32,
mem: Option<u32>,
iterations: Option<u32>,
parallelism: Option<u32>,
) -> Result<()> {
let mut cfg = config::load()?;

Expand Down Expand Up @@ -547,7 +567,11 @@ fn create(
}
println!();
let pwd = crypto::prompt_passphrase("Set a passphrase to encrypt this wallet", strict)?;
crypto::encrypt_secret(&pwd, &secret_key, None)?
crypto::encrypt_secret(
&pwd,
&secret_key,
kdf_options(mem, iterations, parallelism, cfg.wallet_encryption.as_ref()).as_ref(),
)?
} else {
secret_key.clone()
};
Expand Down Expand Up @@ -1020,7 +1044,7 @@ fn rotate_wallet(
encrypt: bool,
mem: Option<u32>,
iterations: Option<u32>,
backup: Option<PathBuf>,
parallelism: Option<u32>,
) -> Result<()> {
config::validate_wallet_name(&name)?;
let mut cfg = config::load()?;
Expand Down Expand Up @@ -1079,7 +1103,11 @@ fn rotate_wallet(
"Set a secure passphrase to encrypt the rotated wallet",
true,
)?;
crypto::encrypt_secret(&pwd, &secret_key, kdf_options(mem, iterations).as_ref())?
crypto::encrypt_secret(
&pwd,
&secret_key,
kdf_options(mem, iterations, parallelism, cfg.wallet_encryption.as_ref()).as_ref(),
)?
} else {
secret_key.clone()
};
Expand Down Expand Up @@ -1238,7 +1266,11 @@ fn export_wallet(name_opt: Option<String>, all: bool, output: PathBuf) -> Result
let json = serde_json::to_string_pretty(&backup)
.with_context(|| "Failed to serialize wallet backup")?;
let passphrase = crypto::prompt_passphrase("Enter passphrase to encrypt backup", false)?;
let encrypted = crypto::encrypt_secret(&passphrase, &json, None)?;
let encrypted = crypto::encrypt_secret(
&passphrase,
&json,
kdf_options(None, None, None, cfg.wallet_encryption.as_ref()).as_ref(),
)?;
fs::write(&output, encrypted)
.with_context(|| format!("Failed to write {}", output.display()))?;

Expand Down Expand Up @@ -1311,7 +1343,11 @@ fn import_from_mnemonic(
let secret_to_store = if encrypt {
println!();
let pwd = crypto::prompt_passphrase("Set a passphrase to encrypt this wallet", false)?;
crypto::encrypt_secret(&pwd, &secret_key, None)?
crypto::encrypt_secret(
&pwd,
&secret_key,
kdf_options(None, None, None, cfg.wallet_encryption.as_ref()).as_ref(),
)?
} else {
secret_key
};
Expand Down
Loading