use crate::error::CoreError;
use crate::normalize_mnemonic;
use bip39::Mnemonic;
use ed25519_dalek::SigningKey;
use multiversx_sdk::wallet;
use std::convert::TryInto;
fn parse_mnemonic(mnemonic_str: &str) -> Result<Mnemonic, CoreError> {
let mnemonic_phrase = normalize_mnemonic(mnemonic_str);
Mnemonic::parse(&mnemonic_phrase).map_err(|e| CoreError::InvalidMnemonic(e.to_string()))
}
pub fn derive_signing_key(
mnemonic_str: &str,
account: u32,
index: u32,
) -> Result<SigningKey, CoreError> {
let mnemonic = parse_mnemonic(mnemonic_str)?;
let private_key = wallet::Wallet::get_private_key_from_mnemonic(mnemonic, account, index);
let private_key_hex = private_key.to_string();
let seed_bytes = hex::decode(&private_key_hex)
.map_err(|e| CoreError::InvalidPrivateKey(format!("invalid private key hex: {e}")))?;
let seed: [u8; 32] = seed_bytes.as_slice().try_into().map_err(|_| {
CoreError::InvalidPrivateKey(format!("expected 32-byte seed, got {}", seed_bytes.len()))
})?;
Ok(SigningKey::from_bytes(&seed))
}
pub fn derive_address(mnemonic_str: &str, account: u32, index: u32) -> Result<String, CoreError> {
let mnemonic = parse_mnemonic(mnemonic_str)?;
let private_key = wallet::Wallet::get_private_key_from_mnemonic(mnemonic, account, index);
let private_key_hex = private_key.to_string();
let wallet_obj = wallet::Wallet::from_private_key(&private_key_hex)
.map_err(|e| CoreError::WalletCreation(e.to_string()))?;
Ok(wallet_obj.to_address().to_bech32_default().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
#[test]
fn test_derive_signing_key_success() {
let result = derive_signing_key(TEST_MNEMONIC, 0, 0);
assert!(result.is_ok());
let key1 = derive_signing_key(TEST_MNEMONIC, 0, 0).unwrap();
let key2 = derive_signing_key(TEST_MNEMONIC, 0, 0).unwrap();
assert_eq!(key1.to_bytes(), key2.to_bytes());
}
#[test]
fn test_derive_signing_key_invalid_mnemonic() {
let result = derive_signing_key("invalid mnemonic words that dont exist", 0, 0);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid mnemonic"));
}
#[test]
fn test_derive_signing_key_different_indices() {
let key0 = derive_signing_key(TEST_MNEMONIC, 0, 0).unwrap();
let key1 = derive_signing_key(TEST_MNEMONIC, 0, 1).unwrap();
let key2 = derive_signing_key(TEST_MNEMONIC, 1, 0).unwrap();
assert_ne!(key0.to_bytes(), key1.to_bytes());
assert_ne!(key0.to_bytes(), key2.to_bytes());
assert_ne!(key1.to_bytes(), key2.to_bytes());
}
#[test]
fn test_derive_address_success() {
let result = derive_address(TEST_MNEMONIC, 0, 0);
assert!(result.is_ok());
let address = result.unwrap();
assert!(address.starts_with("erd1"));
assert_eq!(address.len(), 62); }
#[test]
fn test_derive_address_invalid_mnemonic() {
let result = derive_address("invalid mnemonic words that dont exist", 0, 0);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid mnemonic"));
}
#[test]
fn test_derive_address_different_indices() {
let addr0 = derive_address(TEST_MNEMONIC, 0, 0).unwrap();
let addr1 = derive_address(TEST_MNEMONIC, 0, 1).unwrap();
let addr2 = derive_address(TEST_MNEMONIC, 1, 0).unwrap();
assert_ne!(addr0, addr1);
assert_ne!(addr0, addr2);
assert_ne!(addr1, addr2);
}
#[test]
fn test_derive_address_deterministic() {
let addr1 = derive_address(TEST_MNEMONIC, 0, 0).unwrap();
let addr2 = derive_address(TEST_MNEMONIC, 0, 0).unwrap();
assert_eq!(addr1, addr2);
}
#[test]
fn test_normalize_mnemonic_in_derivation() {
let mnemonic_commas = "abandon,abandon,abandon,abandon,abandon,abandon,abandon,abandon,abandon,abandon,abandon,about";
let mnemonic_spaces = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
let mnemonic_normal = TEST_MNEMONIC;
let addr_commas = derive_address(mnemonic_commas, 0, 0).unwrap();
let addr_spaces = derive_address(mnemonic_spaces, 0, 0).unwrap();
let addr_normal = derive_address(mnemonic_normal, 0, 0).unwrap();
assert_eq!(addr_commas, addr_normal);
assert_eq!(addr_spaces, addr_normal);
}
#[test]
fn test_signing_key_and_address_match() {
let signing_key = derive_signing_key(TEST_MNEMONIC, 0, 0).unwrap();
let address = derive_address(TEST_MNEMONIC, 0, 0).unwrap();
let verifying_key = signing_key.verifying_key();
let pubkey_bytes = verifying_key.to_bytes();
let (_hrp, addr_bytes) = bech32::decode(&address).unwrap();
let addr_bytes_array: [u8; 32] = addr_bytes.as_slice().try_into().unwrap();
assert_eq!(pubkey_bytes, addr_bytes_array);
}
}