use eyre::{Result, WrapErr, eyre};
use std::{
fs,
path::{Path, PathBuf},
};
use subduction_crypto::signer::memory::MemorySigner;
#[derive(Debug, clap::Args)]
pub(crate) struct KeyArgs {
#[arg(short, long, conflicts_with_all = ["key_file", "ephemeral_key"])]
pub(crate) key_seed: Option<String>,
#[arg(long, conflicts_with_all = ["key_seed", "ephemeral_key"])]
pub(crate) key_file: Option<PathBuf>,
#[arg(long, conflicts_with_all = ["key_seed", "key_file"])]
pub(crate) ephemeral_key: bool,
}
pub(crate) fn load_signer(args: &KeyArgs) -> Result<MemorySigner> {
if let Some(hex_seed) = &args.key_seed {
let seed_bytes = crate::parse_32_bytes(hex_seed, "key seed")?;
tracing::info!("Using signing key from --key-seed");
return Ok(MemorySigner::from_bytes(&seed_bytes));
}
if let Some(key_path) = &args.key_file {
return load_key_file(key_path);
}
if args.ephemeral_key {
tracing::warn!("Using ephemeral key (will be lost on restart)");
return Ok(MemorySigner::generate());
}
Err(eyre!(
"No key source specified. Use one of:\n \
--key-file <PATH> Load key from file\n \
--key-seed <HEX> Key from hex seed\n \
--ephemeral-key Random key (lost on restart)"
))
}
fn load_key_file(path: &Path) -> Result<MemorySigner> {
let contents =
fs::read(path).wrap_err_with(|| format!("Failed to read key file: {}", path.display()))?;
let seed_bytes = parse_key_file_contents(&contents, path)?;
tracing::info!("Loaded signing key from {}", path.display());
Ok(MemorySigner::from_bytes(&seed_bytes))
}
fn parse_key_file_contents(contents: &[u8], path: &Path) -> Result<[u8; 32]> {
let trimmed = String::from_utf8_lossy(contents);
let trimmed = trimmed.trim();
if trimmed.len() == 64 && trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
let mut seed = [0u8; 32];
hex::decode_to_slice(trimmed, &mut seed)
.wrap_err_with(|| format!("Invalid hex in key file: {}", path.display()))?;
return Ok(seed);
}
if contents.len() == 32 {
let mut seed = [0u8; 32];
seed.copy_from_slice(contents);
return Ok(seed);
}
Err(eyre!(
"Key file {} must contain either 64 hex characters or 32 raw bytes (found {} bytes)",
path.display(),
contents.len()
))
}