keyfork: impl basic `recover mnemonic`

This commit is contained in:
Ryan Heywood 2024-01-17 21:21:11 -05:00
parent d2965745e8
commit c5d1a6d62c
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
2 changed files with 67 additions and 0 deletions

View File

@ -21,9 +21,14 @@ pub enum RecoverSubcommands {
/// Combine remotely decrypted shards. The shards should be sent using the command `keyfork
/// shard transport`.
RemoteShard {},
/// Read a mnemonic from input.
Mnemonic {},
}
impl RecoverSubcommands {
/// Return the 128-bit or 256-bit entropy for a bip39 mnemonic. This is _not_ the same as the
/// 512-bit seed used by bip32.
fn handle(&self) -> Result<Vec<u8>> {
match self {
RecoverSubcommands::Shard {
@ -51,6 +56,27 @@ impl RecoverSubcommands {
remote_decrypt(&mut seed)?;
Ok(seed)
}
RecoverSubcommands::Mnemonic {} => {
use keyfork_prompt::{
default_terminal,
validators::{
mnemonic::{MnemonicChoiceValidator, WordLength},
Validator,
},
PromptHandler,
};
let mut term = default_terminal()?;
let validator = MnemonicChoiceValidator {
word_lengths: [WordLength::Count(12), WordLength::Count(24)],
};
let mnemonic = term.prompt_validated_wordlist(
"Mnemonic: ",
&Default::default(),
3,
validator.to_fn(),
)?;
Ok(mnemonic.entropy())
}
}
}
}

View File

@ -142,6 +142,47 @@ pub mod mnemonic {
}
}
/// A mnemonic of a given choice of lengths. For example, a 128-bit or 256-bit bip32 seed.
#[derive(thiserror::Error, Debug)]
pub enum MnemonicChoiceValidationError {
/// The provided mnemonic did not match any of the valid ranges.
#[error("Invalid word length: {0} was not in any {1:?}")]
InvalidLength(usize, Vec<WordLength>),
/// A mnemonic could not be parsed from the provided mnemonic.
#[error("{0}")]
MnemonicFromStrError(#[from] MnemonicFromStrError),
}
/// Validate a single mnemonic against a set of possible word lengths.
#[derive(Clone)]
pub struct MnemonicChoiceValidator<const N: usize> {
/// The accepted [`WordLength`] of the mnemonic.
pub word_lengths: [WordLength; N],
}
impl<const N: usize> Validator for MnemonicChoiceValidator<N> {
type Output = Mnemonic;
type Error = MnemonicChoiceValidationError;
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Self::Output, Self::Error>> {
let word_lengths = self.word_lengths.clone();
Box::new(move |s: String| {
let count = s.split_whitespace().count();
for word_length in &word_lengths {
if word_length.matches(count) {
let m = Mnemonic::from_str(&s)?;
return Ok(m);
}
}
Err(MnemonicChoiceValidationError::InvalidLength(
count,
word_lengths.to_vec(),
))
})
}
}
/// A mnemonic in the set of mnemonics could not be validated from the given inputs.
#[derive(thiserror::Error, Debug)]
pub enum MnemonicSetValidationError {