synor/crates/synor-crypto-wasm/src/mnemonic_wasm.rs
Gulshan Yadav b22c1b89f0 feat: Phase 7 production readiness improvements
- Add SYNOR_BOOTSTRAP_PEERS env var for runtime seed node configuration
- Implement secrets provider abstraction for faucet wallet key security
  (supports file-based secrets in /run/secrets for production)
- Create WASM crypto crate foundation for web wallet (Ed25519, BIP-39)
- Add DEPLOYMENT.md guide for testnet deployment
- Add SECURITY_AUDIT_SCOPE.md for external security audit preparation
- Document seed node deployment process in synor-network

Security improvements:
- Faucet now auto-detects /run/secrets for secure key storage
- CORS already defaults to specific origins (https://faucet.synor.cc)
- Bootstrap peers now configurable at runtime without recompilation
2026-01-08 07:21:14 +05:30

115 lines
3.4 KiB
Rust

//! 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<Mnemonic, JsValue> {
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, JsValue> {
Mnemonic::new(word_count)
}
/// Parse a mnemonic from a phrase.
#[wasm_bindgen(js_name = fromPhrase)]
pub fn from_phrase(phrase: &str) -> Result<Mnemonic, JsValue> {
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<String> {
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<u8> {
let seed = bip39::Seed::new(&self.inner, passphrase);
seed.as_bytes().to_vec()
}
/// Get the entropy bytes.
#[wasm_bindgen]
pub fn entropy(&self) -> Vec<u8> {
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::*;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_mnemonic_generation() {
let mnemonic = Mnemonic::new(24).unwrap();
assert_eq!(mnemonic.word_count(), 24);
}
#[wasm_bindgen_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"));
}
#[wasm_bindgen_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);
}
}