use bip39::Mnemonic;
use rand::rngs::OsRng;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use crate::error::ZincError;
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct ZincMnemonic {
#[zeroize(skip)]
inner: Mnemonic,
}
impl ZincMnemonic {
pub fn generate(word_count: u8) -> Result<Self, ZincError> {
let entropy_len = match word_count {
12 => 16, 24 => 32, _ => return Err(ZincError::InvalidWordCount(word_count)),
};
let mut entropy = Zeroizing::new(vec![0u8; entropy_len]);
rand::RngCore::fill_bytes(&mut OsRng, &mut entropy);
let mnemonic = Mnemonic::from_entropy(&entropy)
.map_err(|e| ZincError::MnemonicError(e.to_string()))?;
Ok(Self { inner: mnemonic })
}
pub fn parse(phrase: &str) -> Result<Self, ZincError> {
let mnemonic = Mnemonic::parse_in(bip39::Language::English, phrase)
.map_err(|e| ZincError::MnemonicError(e.to_string()))?;
Ok(Self { inner: mnemonic })
}
pub fn words(&self) -> Vec<String> {
self.inner.words().map(|w: &str| w.to_string()).collect()
}
pub fn phrase(&self) -> String {
self.inner.to_string()
}
pub fn to_seed(&self, passphrase: &str) -> Zeroizing<[u8; 64]> {
Zeroizing::new(self.inner.to_seed(passphrase))
}
#[allow(dead_code)]
pub(crate) fn inner(&self) -> &Mnemonic {
&self.inner
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn generate_12_word_mnemonic() {
let m = ZincMnemonic::generate(12).unwrap();
assert_eq!(m.words().len(), 12);
}
#[test]
fn generate_24_word_mnemonic() {
let m = ZincMnemonic::generate(24).unwrap();
assert_eq!(m.words().len(), 24);
}
#[test]
fn invalid_word_count_fails() {
let result = ZincMnemonic::generate(15);
assert!(result.is_err());
}
#[test]
fn parse_valid_mnemonic() {
let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
let m = ZincMnemonic::parse(phrase).unwrap();
assert_eq!(m.words().len(), 12);
assert_eq!(m.phrase(), phrase);
}
#[test]
fn parse_invalid_mnemonic_fails() {
let result = ZincMnemonic::parse("invalid mnemonic phrase");
assert!(result.is_err());
}
#[test]
fn seed_derivation_works() {
let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
let m = ZincMnemonic::parse(phrase).unwrap();
let seed = m.to_seed("");
assert_eq!(seed.len(), 64);
}
#[test]
fn passphrase_changes_seed() {
let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
let m = ZincMnemonic::parse(phrase).unwrap();
let seed1 = m.to_seed("");
let seed2 = m.to_seed("password");
assert_ne!(&seed1[..], &seed2[..]);
}
}