use blueprint_crypto::k256::K256Ecdsa;
use blueprint_keystore::{Keystore, KeystoreConfig, backends::Backend};
use blueprint_runner::config::Protocol;
use cargo_tangle::command::keys::{
SupportedKey, export_key, generate_key, generate_mnemonic, import_key, list_keys,
prompt_for_keys,
};
use clap::Subcommand;
use color_eyre::eyre::{Result, eyre};
use std::path::PathBuf;
#[derive(Subcommand, Debug)]
pub(super) enum KeyCommands {
#[command(visible_alias = "g")]
Generate {
#[arg(short = 't', long, value_enum)]
key_type: SupportedKey,
#[arg(short = 'o', long)]
output: Option<PathBuf>,
#[arg(long)]
seed: Option<Vec<u8>>,
#[arg(short = 'v', long)]
show_secret: bool,
},
#[command(visible_alias = "i")]
Import {
#[arg(short = 't', long, value_enum)]
key_type: Option<SupportedKey>,
#[arg(short = 'x', long)]
secret: Option<String>,
#[arg(short = 'k', long)]
keystore_path: PathBuf,
#[arg(short = 'p', long, value_enum, default_value = "tangle")]
protocol: Protocol,
},
#[command(visible_alias = "e")]
Export {
#[arg(short = 't', long, value_enum)]
key_type: SupportedKey,
#[arg(short = 'p', long)]
public: String,
#[arg(short = 'k', long)]
keystore_path: PathBuf,
},
#[command(visible_alias = "l")]
List {
#[arg(short = 'k', long)]
keystore_path: PathBuf,
},
#[command(visible_alias = "m")]
GenerateMnemonic {
#[arg(short = 'w', long, value_parser = clap::value_parser!(u32).range(12..=24))]
word_count: Option<u32>,
},
}
pub(super) async fn run(command: KeyCommands) -> Result<()> {
match command {
KeyCommands::Generate {
key_type,
output,
seed,
show_secret,
} => {
let seed = seed.map(hex::decode).transpose()?;
let (public, secret) =
generate_key(key_type, output.as_ref(), seed.as_deref(), show_secret)?;
println!("Generated {key_type:?} key:");
println!("Public key: {public}");
if show_secret || output.is_none() {
println!("Private key: {}", secret.expect("Missing secret"));
}
}
KeyCommands::Import {
key_type,
secret,
keystore_path,
protocol,
} => {
if let Some(kind) = key_type {
let secret = secret.ok_or_else(|| eyre!("Secret key is required"))?;
let public = import_key(protocol, kind, &secret, &keystore_path)?;
println!("Imported {kind:?} key:");
println!("Public key: {public}");
} else {
let key_pairs = prompt_for_keys(vec![])?;
for (kind, secret) in key_pairs {
let public = import_key(protocol, kind, &secret, &keystore_path)?;
println!("Imported {kind:?} key:");
println!("Public key: {public}");
}
}
}
KeyCommands::Export {
key_type,
public,
keystore_path,
} => {
let secret = export_key(key_type, &public, &keystore_path)?;
println!("Private key: {secret}");
}
KeyCommands::List { keystore_path } => {
let keys = list_keys(&keystore_path)?;
for (kind, public) in keys {
println!("{kind:?}: {public}");
}
}
KeyCommands::GenerateMnemonic { word_count } => {
let mnemonic = generate_mnemonic(word_count)?;
println!("Mnemonic: {mnemonic}");
}
}
Ok(())
}
pub(super) fn ensure_keys(path: &PathBuf, required: &[SupportedKey]) -> Result<()> {
if !path.exists() {
std::fs::create_dir_all(path)?;
}
let keystore = Keystore::new(KeystoreConfig::new().fs_root(path))?;
let mut missing = Vec::new();
if required.contains(&SupportedKey::Ecdsa) && keystore.list_local::<K256Ecdsa>()?.is_empty() {
missing.push(SupportedKey::Ecdsa);
}
if !missing.is_empty() {
println!(
"Keystore at {} is missing required keys. Let's set them up.",
path.display()
);
let inputs = prompt_for_keys(missing)?;
for (kind, secret) in inputs {
import_key(Protocol::Tangle, kind, &secret, path)?;
}
}
Ok(())
}