use super::{Bip39Error, Language};
use sha2::{Digest, Sha256};
use xbits::{FromBits, XBits};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Mnemonic {
words: Vec<String>,
language: Language,
}
#[allow(unused)]
impl Mnemonic {
const fn check_mask(len: usize) -> u8 {
match len {
12 => 0b1111_0000,
15 => 0b1111_1000,
18 => 0b1111_1100,
21 => 0b1111_1110,
24 => 0b1111_1111,
_ => unreachable!(),
}
}
pub fn new(entropy: &[u8], language: Language) -> Result<Self, Bip39Error> {
let length = match entropy.len() {
16 => 12,
20 => 15,
24 => 18,
28 => 21,
32 => 24,
_ => return Err(Bip39Error::InvalidLength),
};
let checksum = Sha256::digest(entropy)[0] & Mnemonic::check_mask(length);
let indices: Vec<usize> = [entropy.to_vec(), vec![checksum]]
.concat()
.bits()
.chunks(11)
.take(length)
.collect();
let words = indices
.iter()
.map(|&i| language.word_at(i).unwrap_or_default().to_string())
.collect();
Ok(Mnemonic { words, language })
}
#[inline]
pub fn language(&self) -> Language {
self.language
}
#[inline]
pub fn count(&self) -> usize {
self.words.len()
}
#[inline]
pub fn words(&self) -> impl Iterator<Item = &String> {
self.words.iter()
}
#[inline]
pub fn indices(&self) -> impl Iterator<Item = usize> {
self.words
.iter()
.map(|w| self.language.index_of(w).unwrap())
}
#[inline]
pub fn entropy(&self) -> Vec<u8> {
let mut entropy: Vec<u8> = Vec::from_bits_chunk(self.indices(), 11);
entropy.pop(); entropy
}
pub fn detect_language<T>(words: impl Iterator<Item = T>) -> Vec<Language>
where
T: AsRef<str>,
{
words
.map(|w| Language::detect(w.as_ref()))
.reduce(|mut acc, v| {
acc.retain(|x| v.contains(x));
acc
})
.unwrap_or_default()
}
pub fn verify_checksum(indices: &[usize]) -> Result<(), Bip39Error> {
if !matches!(indices.len(), 12 | 15 | 18 | 21 | 24) {
return Err(Bip39Error::InvalidLength);
}
let mut entropy = Vec::from_bits_chunk(indices.iter().copied(), 11);
let tail = entropy.pop().unwrap();
let checksum = Sha256::digest(&entropy)[0] & Mnemonic::check_mask(indices.len());
if checksum != tail {
return Err(Bip39Error::InvalidChecksum);
}
Ok(())
}
}
impl std::str::FromStr for Mnemonic {
type Err = Bip39Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let words: Vec<&str> = s.split_whitespace().collect();
if !matches!(words.len(), 12 | 15 | 18 | 21 | 24) {
return Err(Bip39Error::InvalidLength);
}
let mut languages = Mnemonic::detect_language(words.iter());
languages.retain(|&language| {
if let Ok(indices) = language.indices(words.iter()) {
Mnemonic::verify_checksum(&indices).is_ok()
} else {
false
}
});
match languages.len() {
0 => Err(Bip39Error::InvalidChecksum),
1 => Ok(Mnemonic {
words: words.into_iter().map(String::from).collect(),
language: languages.pop().unwrap(),
}),
2.. => {
use Language::*;
if languages == [ChineseSimplified, ChineseTraditional]
|| languages == [ChineseTraditional, ChineseSimplified]
{
Ok(Mnemonic {
words: words.into_iter().map(String::from).collect(),
language: ChineseSimplified,
})
} else {
Err(Bip39Error::AmbiguousLanguages(languages))
}
}
}
}
}
impl std::fmt::Display for Mnemonic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.words.join(" "))
}
}
trait Indices {
fn indices<T>(&self, words: impl Iterator<Item = T>) -> Result<Vec<usize>, Bip39Error>
where
T: AsRef<str>;
}
impl Indices for Language {
fn indices<T>(&self, words: impl Iterator<Item = T>) -> Result<Vec<usize>, Bip39Error>
where
T: AsRef<str>,
{
words
.map(|w| self.index_of(w.as_ref()))
.collect::<Option<Vec<_>>>()
.ok_or(Bip39Error::InvalidLanguage)
}
}