read39 0.1.0

Util for securely reading a BIP39 Mnemonic from stdin
Documentation
use std::io::{self, Write};
use bip0039::{Language, Mnemonic};
use zeroize::Zeroizing;

enum ReadWord {
    Word(Zeroizing<String>),
    Break,
}

fn read_word<L: Language>(id: usize) -> ReadWord {
    loop {
        print!("({}) ", id + 1);
        io::stdout().flush().unwrap();
        let word = readpass::from_tty().unwrap();
        if word.is_empty() {
            break ReadWord::Break;
        }
        if !L::WORD_LIST.contains(&word.as_str()) {
            print!("invalid\n");
        } else {
            #[cfg(feature = "console")]
            let _ = console::Term::stdout().move_cursor_up(1);
            break ReadWord::Word(word);
        }
    }
}

fn read_words<L: Language>() -> Zeroizing<String> {
    let mut out = String::new();
    for i in 0_usize.. {
        match read_word::<L>(i) {
            ReadWord::Word(s) => {
                if !out.is_empty() {
                    out.push(' ');
                }
                out.push_str(s.as_str())
            }

            ReadWord::Break => break
        }
    }
    Zeroizing::new(out)
}

/// Consider using [read_mnemonic_until_ok] instead.
///
/// ```
/// let mnemonic = read39::read_mnemonic::<bip0039::English>().unwrap();
/// ```
pub fn read_mnemonic<L: Language>() -> Result<Zeroizing<Mnemonic<L>>, bip0039::Error> {
    let words = read_words::<L>();
    let mnem = Mnemonic::<L>::from_phrase(words.as_str())?;
    let mnem = Zeroizing::new(mnem);
    Ok(mnem)
}

/// ```
/// let mnemonic = read39::read_mnemonic_until_ok::<bip0039::English>();
/// ```
pub fn read_mnemonic_until_ok<L: Language>() -> Zeroizing<Mnemonic<L>> {
    loop {
        match read_mnemonic::<L>() {
            Ok(val) => break val,
            Err(err) => {
                println!("Error: {}\n", err.to_string());
            }
        }
    }
}