keyfork: add mnemonic {from-entropy,to-seed}

This commit is contained in:
Ryan Heywood 2025-06-09 16:00:34 -04:00
parent 4714f616ea
commit fa47bc28a8
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
1 changed files with 63 additions and 2 deletions

View File

@ -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(())
},
}
}
}