use super::{
bits::{BitWriter, IterExt},
language::Language,
};
use crate::{Error, KeyMaterial, Path, KEY_SIZE};
use alloc::string::String;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "bip39")]
use {super::seed::Seed, hmac::Hmac, 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)?);
}
let mut entropy = Zeroizing::new(bits.into_bytes());
if entropy.len() != KEY_SIZE + 1 {
return Err(Error);
}
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);
}
Ok(Self::from_entropy(
entropy.as_slice().try_into().unwrap(),
language,
))
}
pub fn entropy(&self) -> &Entropy {
&self.entropy
}
pub fn phrase(&self) -> &str {
&self.phrase
}
pub fn language(&self) -> Language {
self.language
}
pub fn derive_subkey(self, path: impl AsRef<Path>) -> KeyMaterial {
KeyMaterial::from(self).derive_subkey(path)
}
#[cfg(feature = "bip39")]
#[cfg_attr(docsrs, doc(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 From<Phrase> for KeyMaterial {
fn from(phrase: Phrase) -> KeyMaterial {
KeyMaterial::from_bytes(&phrase.entropy).unwrap()
}
}
impl Drop for Phrase {
fn drop(&mut self) {
self.phrase.zeroize();
self.entropy.zeroize();
}
}