use bip39::{Language, Mnemonic};
use rand::RngCore;
use crate::error::{Result, ZeraError};
use crate::wallet::constants::{validate_slip0010_path, HdOptions, MNEMONIC_LENGTHS, ZERA_TYPE};
pub fn generate_mnemonic_phrase(length: usize) -> Result<String> {
if !MNEMONIC_LENGTHS.contains(&length) {
return Err(ZeraError::Validation(format!(
"Invalid mnemonic length. Must be one of: {}",
MNEMONIC_LENGTHS
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ")
)));
}
let entropy_bits = match length {
12 => 128,
15 => 160,
18 => 192,
21 => 224,
24 => 256,
_ => unreachable!(),
};
let mut entropy = vec![0_u8; entropy_bits / 8];
rand::rngs::OsRng.fill_bytes(&mut entropy);
let mnemonic = Mnemonic::from_entropy_in(Language::English, &entropy)
.map_err(|e| ZeraError::Validation(format!("failed to generate mnemonic: {e}")))?;
Ok(mnemonic.to_string())
}
pub fn validate_mnemonic_phrase(mnemonic: &str) -> bool {
if mnemonic.is_empty() {
return false;
}
Mnemonic::parse_in_normalized(Language::English, mnemonic).is_ok()
}
pub fn generate_seed(mnemonic: &str, passphrase: &str) -> Result<Vec<u8>> {
let parsed = Mnemonic::parse_in_normalized(Language::English, mnemonic)
.map_err(|_| ZeraError::Validation("Invalid BIP39 mnemonic format".into()))?;
Ok(parsed.to_seed(passphrase).to_vec())
}
pub fn build_derivation_path(options: HdOptions) -> Result<String> {
if options.change_index != 0 && options.change_index != 1 {
return Err(ZeraError::Validation(
"changeIndex must be 0 or 1".to_string(),
));
}
let path = format!(
"m/44'/{ZERA_TYPE}'/{}'/{}'/{}'",
options.account_index, options.change_index, options.address_index
);
if !validate_slip0010_path(&path) {
return Err(ZeraError::Validation(
"Invalid SLIP-0010 path format".into(),
));
}
Ok(path)
}