use super::{
bits::{BitWriter, IterExt},
language::Language,
};
use crate::{Error, KEY_SIZE};
use alloc::{format, string::String};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "bip39")]
use {super::seed::Seed, sha2::Sha512};
#[cfg(feature = "bip39")]
const PBKDF2_ROUNDS: u32 = 2048;
pub type Entropy = [u8; KEY_SIZE];
#[derive(Clone)]
pub struct Phrase {
language: Language,
entropy: Entropy,
phrase: String,
}
impl Phrase {
pub fn random(mut rng: impl RngCore + CryptoRng, language: Language) -> Self {
let mut entropy = Entropy::default();
rng.fill_bytes(&mut entropy);
Self::from_entropy(entropy, language)
}
pub fn from_entropy(entropy: Entropy, language: Language) -> Self {
let wordlist = language.wordlist();
let checksum_byte = Sha256::digest(entropy.as_ref()).as_slice()[0];
let phrase = entropy
.iter()
.chain(Some(&checksum_byte))
.bits()
.map(|bits| wordlist.get_word(bits))
.join(" ");
Phrase {
language,
entropy,
phrase,
}
}
pub fn new<S>(phrase: S, language: Language) -> Result<Self, Error>
where
S: AsRef<str>,
{
let phrase = phrase.as_ref();
let wordmap = language.wordmap();
let mut bits = BitWriter::with_capacity(264);
for word in phrase.split(' ') {
bits.push(wordmap.get_bits(word).ok_or(Error::Bip39)?);
}
let mut entropy = Zeroizing::new(bits.into_bytes());
if entropy.len() != KEY_SIZE + 1 {
return Err(Error::Bip39);
}
let actual_checksum = entropy[KEY_SIZE];
entropy.truncate(KEY_SIZE);
let expected_checksum = Sha256::digest(&entropy).as_slice()[0];
if actual_checksum != expected_checksum {
return Err(Error::Bip39);
}
Ok(Self::from_entropy(
entropy.as_slice().try_into().map_err(|_| Error::Bip39)?,
language,
))
}
pub fn entropy(&self) -> &Entropy {
&self.entropy
}
pub fn phrase(&self) -> &str {
&self.phrase
}
pub fn language(&self) -> Language {
self.language
}
#[cfg(feature = "bip39")]
pub fn to_seed(&self, password: &str) -> Seed {
let salt = Zeroizing::new(format!("mnemonic{}", password));
let mut seed = [0u8; Seed::SIZE];
pbkdf2::pbkdf2_hmac::<Sha512>(
self.phrase.as_bytes(),
salt.as_bytes(),
PBKDF2_ROUNDS,
&mut seed,
);
Seed(seed)
}
}
impl Drop for Phrase {
fn drop(&mut self) {
self.phrase.zeroize();
self.entropy.zeroize();
}
}