use crate::crypto::{gen_random_bytes, sha256_first_byte};
use crate::error::ErrorKind;
use crate::keyphrase_type::KeyPhraseType;
use crate::language::{Language, WordList, WordMap};
use crate::util::{checksum, BitWriter, Bits11, IterExt};
use failure::Error;
use std::fmt;
#[derive(Clone)]
pub struct KeyPhrase {
phrase: String,
lang: Language,
entropy: Vec<u8>,
}
impl KeyPhrase {
pub fn new(keyphrase_type: KeyPhraseType, lang: Language) -> KeyPhrase {
let entropy: Vec<u8> = gen_random_bytes(keyphrase_type.entropy_bits() / 8);
KeyPhrase::from_entropy_unchecked(entropy, lang)
}
pub fn from_entropy(entropy: &[u8], lang: Language) -> Result<KeyPhrase, Error> {
KeyPhraseType::for_key_size(entropy.len() * 8)?;
Ok(Self::from_entropy_unchecked(entropy, lang))
}
fn from_entropy_unchecked<E>(entropy: E, lang: Language) -> KeyPhrase
where
E: Into<Vec<u8>>,
{
let entropy: Vec<u8> = entropy.into();
let wordlist: &WordList = lang.wordlist();
let checksum_byte: u8 = sha256_first_byte(&entropy);
let phrase: String = entropy
.iter()
.chain(Some(&checksum_byte))
.bits()
.map(|bits: Bits11| wordlist.get_word(bits))
.join(" ");
KeyPhrase {
phrase,
lang,
entropy,
}
}
pub fn from_phrase<S>(phrase: S, lang: Language) -> Result<KeyPhrase, Error>
where
S: Into<String>,
{
let phrase: String = phrase.into();
let entropy: Vec<u8> = KeyPhrase::phrase_to_entropy(&phrase, lang)?;
let keyphrase: KeyPhrase = KeyPhrase {
phrase,
lang,
entropy,
};
Ok(keyphrase)
}
pub fn validate(phrase: &str, lang: Language) -> Result<(), Error> {
KeyPhrase::phrase_to_entropy(phrase, lang)?;
Ok(())
}
fn phrase_to_entropy(phrase: &str, lang: Language) -> Result<Vec<u8>, Error> {
let wordmap: &WordMap = lang.wordmap();
let mut bits = BitWriter::with_capacity(264);
for word in phrase.split(" ") {
bits.push(wordmap.get_bits(&word)?);
}
let mtype: KeyPhraseType = KeyPhraseType::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: usize = mtype.entropy_bits() / 8;
let actual_checksum: u8 = checksum(entropy[entropy_bytes], mtype.checksum_bits());
entropy.truncate(entropy_bytes);
let checksum_byte: u8 = sha256_first_byte(&entropy);
let expected_checksum: u8 = 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 KeyPhrase {
fn as_ref(&self) -> &str {
self.phrase()
}
}
impl fmt::Display for KeyPhrase {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.phrase(), f)
}
}
impl fmt::Debug for KeyPhrase {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.phrase(), f)
}
}
impl fmt::LowerHex for KeyPhrase {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
f.write_str("0x")?;
}
for byte in self.entropy() {
write!(f, "{:x}", byte)?;
}
Ok(())
}
}
impl fmt::UpperHex for KeyPhrase {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
f.write_str("0x")?;
}
for byte in self.entropy() {
write!(f, "{:X}", byte)?;
}
Ok(())
}
}
impl From<KeyPhrase> for String {
fn from(val: KeyPhrase) -> String {
val.into_phrase()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn back_to_back() {
let m1: KeyPhrase = KeyPhrase::new(KeyPhraseType::Words12, Language::English);
let m2: KeyPhrase = KeyPhrase::from_phrase(m1.phrase(), Language::English).unwrap();
let m3: KeyPhrase = KeyPhrase::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 keyphrase_from_entropy() {
let entropy: &[u8; 16] = &[
0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
0x6A, 0x79,
];
let phrase: &str =
"crop cash unable insane eight faith inflict route frame loud box vibrant";
let keyphrase: KeyPhrase = KeyPhrase::from_entropy(entropy, Language::English).unwrap();
assert_eq!(phrase, keyphrase.phrase());
}
#[test]
fn keyphrase_from_phrase() {
let entropy: &[u8; 16] = &[
0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
0x6A, 0x79,
];
let phrase: &str =
"crop cash unable insane eight faith inflict route frame loud box vibrant";
let keyphrase: KeyPhrase = KeyPhrase::from_phrase(phrase, Language::English).unwrap();
assert_eq!(entropy, keyphrase.entropy());
}
#[test]
fn keyphrase_format() {
let keyphrase: KeyPhrase = KeyPhrase::new(KeyPhraseType::Words15, Language::English);
assert_eq!(keyphrase.phrase(), format!("{}", keyphrase));
}
#[test]
fn keyphrase_hex_format() {
let entropy: &[u8; 16] = &[
0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
0x6A, 0x79,
];
let keyphrase: KeyPhrase = KeyPhrase::from_entropy(entropy, Language::English).unwrap();
assert_eq!(
format!("{:x}", keyphrase),
"33e46bb13a746ea41cdde45c90846a79"
);
assert_eq!(
format!("{:X}", keyphrase),
"33E46BB13A746EA41CDDE45C90846A79"
);
assert_eq!(
format!("{:#x}", keyphrase),
"0x33e46bb13a746ea41cdde45c90846a79"
);
assert_eq!(
format!("{:#X}", keyphrase),
"0x33E46BB13A746EA41CDDE45C90846A79"
);
}
}