diff --git a/crates/keyfork/src/cli/mnemonic.rs b/crates/keyfork/src/cli/mnemonic.rs index ce4b6e1..bde5c59 100644 --- a/crates/keyfork/src/cli/mnemonic.rs +++ b/crates/keyfork/src/cli/mnemonic.rs @@ -168,6 +168,26 @@ fn context_stub(path: &Path) -> impl Fn(std::io::Error) -> Error + use<'_> { |e| Error::IOContext(e, path.to_path_buf()) } +/// Options on how to parse a mnemonic input. +#[derive(ValueEnum, Clone, Debug)] +pub enum MnemonicInputOptions { + /// Parse the input as hex-encoded. + Hex, + + /// Parse the input as a wordlist. + Wordlist, +} + +impl Display for MnemonicInputOptions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MnemonicInputOptions::Hex => f.write_str("hex"), + MnemonicInputOptions::Wordlist => f.write_str("wordlist"), + } + } +} + +#[allow(clippy::large_enum_variant)] #[derive(Subcommand, Clone, Debug)] pub enum MnemonicSubcommands { /// Generate a mnemonic using a given entropy source. @@ -292,6 +312,16 @@ pub enum MnemonicSubcommands { #[arg(long, requires = "provision", default_value_t = Options::default())] provision_config: Options, }, + + /// Convert the provided data to a mnemonic wordlist. + FromEntropy, + + /// Convert a mnemonic (wordlist or hex encoded) to a seed. + ToSeed { + /// The format of the mnemonic. + #[arg(long, default_value_t = MnemonicInputOptions::Wordlist)] + format: MnemonicInputOptions, + }, } // NOTE: This function defaults to `.asc` in the event no extension is found. @@ -318,7 +348,10 @@ fn is_extension_armored(path: &Path) -> bool { Some("pgp" | "gpg") => false, Some("asc") => true, _ => { - eprintln!("unable to determine whether to armor file: {path}", path = path.display()); + eprintln!( + "unable to determine whether to armor file: {path}", + path = path.display() + ); eprintln!("use .gpg, .pgp, or .asc extension, or `armor=true`"); eprintln!("defaulting to armored"); true @@ -460,7 +493,9 @@ fn do_shard( return Err(MissingThresholdOrMax.into()); } - let (threshold, max) = if let Some(t) = threshold.zip(max) { t } else { + let (threshold, max) = if let Some(t) = threshold.zip(max) { + t + } else { let len = u8::try_from(certs.len())?; (len, len) }; @@ -942,6 +977,32 @@ impl MnemonicSubcommands { } Ok(()) } + MnemonicSubcommands::FromEntropy => { + let stdin = std::io::stdin(); + let hex = std::io::read_to_string(stdin)?; + let trimmed = hex.trim(); + let decoded = smex::decode(trimmed)?; + let mnemonic = keyfork_mnemonic::Mnemonic::try_from_slice(&decoded)?; + println!("{mnemonic}"); + Ok(()) + }, + MnemonicSubcommands::ToSeed { format } => { + let stdin = std::io::stdin(); + let input = std::io::read_to_string(stdin)?; + let trimmed = input.trim(); + let mnemonic = match format { + MnemonicInputOptions::Hex => { + let decoded = smex::decode(trimmed)?; + keyfork_mnemonic::Mnemonic::try_from_slice(&decoded)? + }, + MnemonicInputOptions::Wordlist => { + keyfork_mnemonic::Mnemonic::from_str(trimmed)? + }, + }; + println!("{}", smex::encode(mnemonic.generate_seed(None))); + + Ok(()) + }, } } }