zera-sdk 0.1.0

Rust SDK for ZERA transactions, validator APIs, and bridge workflows
Documentation
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)
}