//! BIP-39 mnemonic support for WASM. use bip39::{Language, Mnemonic as Bip39Mnemonic, MnemonicType}; use wasm_bindgen::prelude::*; /// BIP-39 mnemonic phrase wrapper. #[wasm_bindgen] pub struct Mnemonic { inner: Bip39Mnemonic, } #[wasm_bindgen] impl Mnemonic { /// Generate a new random mnemonic with the specified word count. #[wasm_bindgen(constructor)] pub fn new(word_count: u8) -> Result { let mnemonic_type = match word_count { 12 => MnemonicType::Words12, 15 => MnemonicType::Words15, 18 => MnemonicType::Words18, 21 => MnemonicType::Words21, 24 => MnemonicType::Words24, _ => { return Err(JsValue::from_str( "Invalid word count. Must be 12, 15, 18, 21, or 24", )) } }; let mnemonic = Bip39Mnemonic::new(mnemonic_type, Language::English); Ok(Mnemonic { inner: mnemonic }) } /// Generate a 24-word mnemonic. #[wasm_bindgen] pub fn generate(word_count: u8) -> Result { Mnemonic::new(word_count) } /// Parse a mnemonic from a phrase. #[wasm_bindgen(js_name = fromPhrase)] pub fn from_phrase(phrase: &str) -> Result { let mnemonic = Bip39Mnemonic::from_phrase(phrase, Language::English) .map_err(|e| JsValue::from_str(&format!("Invalid mnemonic: {:?}", e)))?; Ok(Mnemonic { inner: mnemonic }) } /// Get the mnemonic phrase as a string. #[wasm_bindgen] pub fn phrase(&self) -> String { self.inner.phrase().to_string() } /// Get the mnemonic words as an array. #[wasm_bindgen] pub fn words(&self) -> Vec { self.inner .phrase() .split_whitespace() .map(String::from) .collect() } /// Get the word count. #[wasm_bindgen(js_name = wordCount)] pub fn word_count(&self) -> usize { self.inner.phrase().split_whitespace().count() } /// Derive a 64-byte seed from the mnemonic. #[wasm_bindgen(js_name = toSeed)] pub fn to_seed(&self, passphrase: &str) -> Vec { let seed = bip39::Seed::new(&self.inner, passphrase); seed.as_bytes().to_vec() } /// Get the entropy bytes. #[wasm_bindgen] pub fn entropy(&self) -> Vec { self.inner.entropy().to_vec() } /// Validate a mnemonic phrase. #[wasm_bindgen] pub fn validate(phrase: &str) -> bool { Bip39Mnemonic::validate(phrase, Language::English).is_ok() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_mnemonic_generation() { let mnemonic = Mnemonic::new(24).unwrap(); assert_eq!(mnemonic.word_count(), 24); } #[test] fn test_mnemonic_validation() { assert!(Mnemonic::validate("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about")); assert!(!Mnemonic::validate("invalid mnemonic phrase")); } #[test] fn test_mnemonic_to_seed() { let mnemonic = Mnemonic::from_phrase( "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" ).unwrap(); let seed = mnemonic.to_seed(""); assert_eq!(seed.len(), 64); } }