keyfork-prompt: add mnemonic validators
This commit is contained in:
parent
09c3e1bda8
commit
0c4fc16285
|
@ -1,19 +1,36 @@
|
|||
use std::{
|
||||
io::{stdin, stdout},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::io::{stdin, stdout};
|
||||
|
||||
use keyfork_mnemonic_util::Mnemonic;
|
||||
use keyfork_prompt::{qrencode, Message, PromptManager};
|
||||
use keyfork_prompt::{
|
||||
validators::{mnemonic, Validator},
|
||||
PromptManager,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut mgr = PromptManager::new(stdin(), stdout())?;
|
||||
mgr.prompt_passphrase("Passphrase: ")?;
|
||||
let string = mgr.prompt_wordlist("Mnemonic: ", &Default::default())?;
|
||||
let mnemonic = Mnemonic::from_str(&string).unwrap();
|
||||
let entropy = mnemonic.entropy();
|
||||
mgr.prompt_message(&Message::Text(format!("Your entropy is: {entropy:X?}")))?;
|
||||
let qrcode = qrencode::qrencode(&string)?;
|
||||
mgr.prompt_message(&Message::Data(qrcode))?;
|
||||
let transport_validator = mnemonic::MnemonicSetValidator {
|
||||
word_lengths: [9, 24],
|
||||
};
|
||||
let combine_validator = mnemonic::MnemonicSetValidator {
|
||||
word_lengths: [24, 48],
|
||||
};
|
||||
|
||||
let mnemonics = mgr.prompt_validated_wordlist(
|
||||
"Enter a 9-word and 24-word mnemonic: ",
|
||||
&Default::default(),
|
||||
3,
|
||||
transport_validator.to_fn(),
|
||||
)?;
|
||||
assert_eq!(mnemonics[0].entropy().len(), 12);
|
||||
assert_eq!(mnemonics[1].entropy().len(), 32);
|
||||
|
||||
let mnemonics = mgr.prompt_validated_wordlist(
|
||||
"Enter a 24 and 48-word mnemonic: ",
|
||||
&Default::default(),
|
||||
3,
|
||||
combine_validator.to_fn(),
|
||||
)?;
|
||||
assert_eq!(mnemonics[0].entropy().len(), 32);
|
||||
assert_eq!(mnemonics[1].entropy().len(), 64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -155,7 +155,6 @@ where
|
|||
}
|
||||
Event::Key(k) => match k.code {
|
||||
KeyCode::Enter => {
|
||||
input.push('\n');
|
||||
break;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
|
@ -302,7 +301,6 @@ where
|
|||
}
|
||||
Event::Key(k) => match k.code {
|
||||
KeyCode::Enter => {
|
||||
passphrase.push('\n');
|
||||
break;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![allow(clippy::type_complexity)]
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
pub trait Validator {
|
||||
type Output;
|
||||
|
@ -19,11 +20,12 @@ pub enum PinError {
|
|||
InvalidCharacters(char, usize),
|
||||
}
|
||||
|
||||
/// Validate that a PIN is of a certain length and matches a range of characters.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct PinValidator {
|
||||
pub min_length: Option<usize>,
|
||||
pub max_length: Option<usize>,
|
||||
pub range: Option<std::ops::RangeInclusive<char>>,
|
||||
pub range: Option<RangeInclusive<char>>,
|
||||
}
|
||||
|
||||
impl Validator for PinValidator {
|
||||
|
@ -52,3 +54,117 @@ impl Validator for PinValidator {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mnemonic")]
|
||||
pub mod mnemonic {
|
||||
use std::{mem::MaybeUninit, ops::Range, str::FromStr};
|
||||
|
||||
use super::Validator;
|
||||
|
||||
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum MnemonicValidationError {
|
||||
#[error("Invalid word length: {0} does not match {1:?}")]
|
||||
InvalidLength(usize, WordLength),
|
||||
|
||||
#[error("{0}")]
|
||||
MnemonicFromStrError(#[from] MnemonicFromStrError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum WordLength {
|
||||
Range(Range<usize>),
|
||||
Count(usize),
|
||||
}
|
||||
|
||||
impl WordLength {
|
||||
fn matches(&self, word_count: usize) -> bool {
|
||||
match self {
|
||||
WordLength::Range(r) => r.contains(&word_count),
|
||||
WordLength::Count(c) => c == &word_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a mnemonic of a range of word lengths or a specific length.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct MnemonicValidator {
|
||||
pub word_length: Option<WordLength>,
|
||||
}
|
||||
|
||||
impl Validator for MnemonicValidator {
|
||||
type Output = Mnemonic;
|
||||
type Error = MnemonicValidationError;
|
||||
|
||||
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Mnemonic, Self::Error>> {
|
||||
let word_length = self.word_length.clone();
|
||||
Box::new(move |s: String| match word_length.as_ref() {
|
||||
Some(wl) => {
|
||||
let count = s.split_whitespace().count();
|
||||
if !wl.matches(count) {
|
||||
return Err(Self::Error::InvalidLength(count, wl.clone()));
|
||||
}
|
||||
let m = Mnemonic::from_str(&s)?;
|
||||
Ok(m)
|
||||
}
|
||||
None => {
|
||||
let m = Mnemonic::from_str(&s)?;
|
||||
Ok(m)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum MnemonicSetValidationError {
|
||||
#[error("Invalid word length in set {0}: {1} != expected {2}")]
|
||||
InvalidSetLength(usize, usize, usize),
|
||||
|
||||
#[error("Error parsing mnemonic set {0}: {1}")]
|
||||
MnemonicFromStrError(usize, MnemonicFromStrError),
|
||||
}
|
||||
|
||||
/// Validate a set of mnemonics of a specific word length.
|
||||
#[derive(Clone)]
|
||||
pub struct MnemonicSetValidator<const N: usize> {
|
||||
pub word_lengths: [usize; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Validator for MnemonicSetValidator<N> {
|
||||
type Output = [Mnemonic; N];
|
||||
type Error = MnemonicSetValidationError;
|
||||
|
||||
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Self::Output, Self::Error>> {
|
||||
let word_lengths = self.word_lengths;
|
||||
Box::new(move |s: String| {
|
||||
let mut counter: usize = 0;
|
||||
let mut output = Vec::with_capacity(N);
|
||||
for (word_set, word_length) in word_lengths.into_iter().enumerate() {
|
||||
let words = s
|
||||
.split_whitespace()
|
||||
.skip(counter)
|
||||
.take(word_length)
|
||||
.collect::<Vec<_>>();
|
||||
if words.len() != word_length {
|
||||
return Err(MnemonicSetValidationError::InvalidSetLength(
|
||||
word_set,
|
||||
words.len(),
|
||||
word_length,
|
||||
));
|
||||
}
|
||||
let mnemonic = match Mnemonic::from_str(&words.join(" ")) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return Err(Self::Error::MnemonicFromStrError(word_set, e)),
|
||||
};
|
||||
output.push(mnemonic);
|
||||
counter += word_length;
|
||||
}
|
||||
|
||||
Ok(output
|
||||
.try_into()
|
||||
.expect("vec with capacity of const N was not filled"))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue