use std::fmt;
use failure::Error;
use unicode_normalization::UnicodeNormalization;
use crate::crypto::{gen_random_bytes, sha256_first_byte};
use crate::error::ErrorKind;
use crate::language::Language;
use crate::mnemonic_type::MnemonicType;
use crate::util::{checksum, BitWriter, IterExt};
#[derive(Clone)]
pub struct Mnemonic {
phrase: String,
lang: Language,
entropy: Vec<u8>,
}
impl Mnemonic {
pub fn new(mtype: MnemonicType, lang: Language) -> Mnemonic {
let entropy = gen_random_bytes(mtype.entropy_bits() / 8);
Mnemonic::from_entropy_unchecked(entropy, lang)
}
pub fn from_entropy(entropy: &[u8], lang: Language) -> Result<Mnemonic, Error> {
MnemonicType::for_key_size(entropy.len() * 8)?;
Ok(Self::from_entropy_unchecked(entropy, lang))
}
fn from_entropy_unchecked<E>(entropy: E, lang: Language) -> Mnemonic
where
E: Into<Vec<u8>>,
{
let entropy = entropy.into();
let wordlist = lang.wordlist();
let checksum_byte = sha256_first_byte(&entropy);
let phrase = entropy
.iter()
.chain(Some(&checksum_byte))
.bits()
.map(|bits| wordlist.get_word(bits))
.join(" ");
Mnemonic {
phrase,
lang,
entropy,
}
}
pub fn from_phrase(phrase: &str, lang: Language) -> Result<Mnemonic, Error> {
let phrase = phrase
.split_whitespace()
.map(|w| w.nfkd())
.join::<String>(" ");
let entropy = Mnemonic::phrase_to_entropy(&phrase, lang)?;
let mnemonic = Mnemonic {
phrase,
lang,
entropy,
};
Ok(mnemonic)
}
pub fn validate(phrase: &str, lang: Language) -> Result<(), Error> {
Mnemonic::phrase_to_entropy(phrase, lang)?;
Ok(())
}
fn phrase_to_entropy(phrase: &str, lang: Language) -> Result<Vec<u8>, Error> {
let wordmap = lang.wordmap();
let mut bits = BitWriter::with_capacity(264);
for word in phrase.split(" ") {
bits.push(wordmap.get_bits(&word)?);
}
let mtype = MnemonicType::for_word_count(bits.len() / 11)?;
debug_assert!(
bits.len() == mtype.total_bits(),
"Insufficient amount of bits to validate"
);
let mut entropy = bits.into_bytes();
let entropy_bytes = mtype.entropy_bits() / 8;
let actual_checksum = checksum(entropy[entropy_bytes], mtype.checksum_bits());
entropy.truncate(entropy_bytes);
let checksum_byte = sha256_first_byte(&entropy);
let expected_checksum = checksum(checksum_byte, mtype.checksum_bits());
if actual_checksum != expected_checksum {
Err(ErrorKind::InvalidChecksum)?;
}
Ok(entropy)
}
pub fn phrase(&self) -> &str {
&self.phrase
}
pub fn into_phrase(self) -> String {
self.phrase
}
pub fn entropy(&self) -> &[u8] {
&self.entropy
}
pub fn language(&self) -> Language {
self.lang
}
}
impl AsRef<str> for Mnemonic {
fn as_ref(&self) -> &str {
self.phrase()
}
}
impl fmt::Display for Mnemonic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.phrase(), f)
}
}
impl fmt::Debug for Mnemonic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.phrase(), f)
}
}
impl fmt::LowerHex for Mnemonic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
f.write_str("0x")?;
}
for byte in self.entropy() {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
impl fmt::UpperHex for Mnemonic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
f.write_str("0x")?;
}
for byte in self.entropy() {
write!(f, "{:02X}", byte)?;
}
Ok(())
}
}
impl From<Mnemonic> for String {
fn from(val: Mnemonic) -> String {
val.into_phrase()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn back_to_back() {
let m1 = Mnemonic::new(MnemonicType::Words12, Language::English);
let m2 = Mnemonic::from_phrase(m1.phrase(), Language::English).unwrap();
let m3 = Mnemonic::from_entropy(m1.entropy(), Language::English).unwrap();
assert_eq!(m1.entropy(), m2.entropy(), "Entropy must be the same");
assert_eq!(m1.entropy(), m3.entropy(), "Entropy must be the same");
assert_eq!(m1.phrase(), m2.phrase(), "Phrase must be the same");
assert_eq!(m1.phrase(), m3.phrase(), "Phrase must be the same");
}
#[test]
fn mnemonic_from_entropy() {
let entropy = &[
0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
0x6A, 0x79,
];
let phrase = "crop cash unable insane eight faith inflict route frame loud box vibrant";
let mnemonic = Mnemonic::from_entropy(entropy, Language::English).unwrap();
assert_eq!(phrase, mnemonic.phrase());
}
#[test]
fn mnemonic_from_phrase() {
let entropy = &[
0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
0x6A, 0x79,
];
let phrase = "crop cash unable insane eight faith inflict route frame loud box vibrant";
let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap();
assert_eq!(entropy, mnemonic.entropy());
}
#[test]
fn mnemonic_format() {
let mnemonic = Mnemonic::new(MnemonicType::Words15, Language::English);
assert_eq!(mnemonic.phrase(), format!("{}", mnemonic));
}
#[test]
fn mnemonic_hex_format() {
let entropy = &[
0x03, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
0x6A, 0x79,
];
let mnemonic = Mnemonic::from_entropy(entropy, Language::English).unwrap();
assert_eq!(
format!("{:x}", mnemonic),
"03e46bb13a746ea41cdde45c90846a79"
);
assert_eq!(
format!("{:X}", mnemonic),
"03E46BB13A746EA41CDDE45C90846A79"
);
assert_eq!(
format!("{:#x}", mnemonic),
"0x03e46bb13a746ea41cdde45c90846a79"
);
assert_eq!(
format!("{:#X}", mnemonic),
"0x03E46BB13A746EA41CDDE45C90846A79"
);
}
}