#![cfg(feature = "compat")]
use bsv_rs::compat::base58;
use bsv_rs::compat::bip32::{
generate_hd_key_from_mnemonic, ExtendedKey, Network, HARDENED_KEY_START,
};
use bsv_rs::compat::bip39::{Language, Mnemonic, WordCount};
use bsv_rs::compat::bsm;
use bsv_rs::compat::ecies;
use bsv_rs::primitives::{from_hex, PrivateKey};
#[test]
fn test_full_hd_wallet_generation_flow() {
let mnemonic = Mnemonic::new(WordCount::Words12).expect("Failed to generate mnemonic");
assert_eq!(mnemonic.words().len(), 12);
let seed = mnemonic.to_seed("");
assert_eq!(seed.len(), 64);
let master =
ExtendedKey::new_master(&seed, Network::Mainnet).expect("Failed to create master key");
assert!(master.is_private());
assert!(master.to_string().starts_with("xprv"));
let derived = master
.derive_path("m/44'/236'/0'/0/0")
.expect("Failed to derive path");
let address = derived.address(true).expect("Failed to get address");
assert!(address.starts_with('1')); }
#[test]
fn test_mnemonic_to_hd_key_helper() {
let mnemonic = Mnemonic::new(WordCount::Words24).expect("Failed to generate mnemonic");
let master = generate_hd_key_from_mnemonic(&mnemonic, "", Network::Mainnet)
.expect("Failed to generate HD key from mnemonic");
assert!(master.is_private());
let xpub = master.neuter().expect("Failed to neuter");
assert!(!xpub.is_private());
assert!(xpub.to_string().starts_with("xpub"));
}
#[test]
fn test_testnet_hd_key_derivation() {
let seed = [0u8; 32];
let master =
ExtendedKey::new_master(&seed, Network::Testnet).expect("Failed to create testnet master");
assert!(master.to_string().starts_with("tprv"));
let xpub = master.neuter().expect("Failed to neuter");
assert!(xpub.to_string().starts_with("tpub"));
let address = master
.address(false)
.expect("Failed to get testnet address");
assert!(address.starts_with('m') || address.starts_with('n'));
}
#[test]
fn test_hardened_derivation() {
let seed = [0u8; 32];
let master = ExtendedKey::new_master(&seed, Network::Mainnet).unwrap();
let child_0h = master.derive_child(HARDENED_KEY_START).unwrap();
let child_0h_path = master.derive_path("m/0'").unwrap();
assert_eq!(
child_0h.to_string(),
child_0h_path.to_string(),
"Hardened derivation mismatch"
);
}
#[test]
fn test_public_key_derivation_only() {
let seed = [0u8; 32];
let master = ExtendedKey::new_master(&seed, Network::Mainnet).unwrap();
let xpub = master.neuter().unwrap();
let pub_child_0 = xpub.derive_child(0).unwrap();
let priv_child_0 = master.derive_child(0).unwrap().neuter().unwrap();
assert_eq!(
pub_child_0.to_string(),
priv_child_0.to_string(),
"Public derivation should match private derivation result"
);
let result = xpub.derive_child(HARDENED_KEY_START);
assert!(result.is_err(), "Hardened from public should fail");
}
#[test]
fn test_extended_key_serialization_roundtrip() {
let seed = [0x42u8; 32];
let master = ExtendedKey::new_master(&seed, Network::Mainnet).unwrap();
let xprv_str = master.to_string();
let parsed = ExtendedKey::from_string(&xprv_str).unwrap();
assert_eq!(master.to_string(), parsed.to_string());
assert_eq!(master.depth(), parsed.depth());
assert_eq!(master.child_number(), parsed.child_number());
assert_eq!(master.chain_code(), parsed.chain_code());
}
#[test]
fn test_bsm_sign_verify_roundtrip() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let message = b"Hello, Bitcoin!";
let signature = bsm::sign_message(&key, message).expect("Failed to sign");
assert_eq!(signature.len(), 65);
let valid = bsm::verify_message(&address, &signature, message).expect("Verification failed");
assert!(valid, "Signature should be valid");
}
#[test]
fn test_bsm_compressed_and_uncompressed() {
let key = PrivateKey::random();
let message = b"Test message";
let sig_compressed = bsm::sign_message_with_compression(&key, message, true).unwrap();
let recovered_comp = bsm::recover_public_key_from_signature(&sig_compressed, message).unwrap();
assert!(recovered_comp.1, "Should be compressed");
let sig_uncompressed = bsm::sign_message_with_compression(&key, message, false).unwrap();
let recovered_uncomp =
bsm::recover_public_key_from_signature(&sig_uncompressed, message).unwrap();
assert!(!recovered_uncomp.1, "Should be uncompressed");
assert_eq!(
recovered_comp.0.to_compressed(),
recovered_uncomp.0.to_compressed(),
"Recovered public keys should match"
);
}
#[test]
fn test_bsm_different_messages_different_signatures() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let message1 = b"Message 1";
let message2 = b"Message 2";
let sig1 = bsm::sign_message(&key, message1).unwrap();
let sig2 = bsm::sign_message(&key, message2).unwrap();
assert_ne!(sig1, sig2);
let cross1 = bsm::verify_message(&address, &sig1, message2).unwrap();
let cross2 = bsm::verify_message(&address, &sig2, message1).unwrap();
assert!(!cross1, "Signature 1 should not verify message 2");
assert!(!cross2, "Signature 2 should not verify message 1");
}
#[test]
fn test_bsm_public_key_recovery() {
let key = PrivateKey::random();
let original_pubkey = key.public_key();
let message = b"Recover me";
let signature = bsm::sign_message(&key, message).unwrap();
let (recovered, _compressed) =
bsm::recover_public_key_from_signature(&signature, message).unwrap();
assert_eq!(
recovered.to_compressed(),
original_pubkey.to_compressed(),
"Recovered public key should match original"
);
}
#[test]
fn test_bsm_empty_and_long_messages() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let empty_sig = bsm::sign_message(&key, b"").unwrap();
assert!(bsm::verify_message(&address, &empty_sig, b"").unwrap());
let long_message = vec![0x42u8; 1024];
let long_sig = bsm::sign_message(&key, &long_message).unwrap();
assert!(bsm::verify_message(&address, &long_sig, &long_message).unwrap());
}
#[test]
fn test_electrum_ecies_roundtrip() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let message = b"Secret message from Alice to Bob";
let encrypted = ecies::electrum_encrypt(message, &bob.public_key(), &alice, false)
.expect("Encryption failed");
assert!(encrypted.len() > message.len());
assert!(encrypted.starts_with(b"BIE1"));
let decrypted = ecies::electrum_decrypt(&encrypted, &bob, Some(&alice.public_key()))
.expect("Decryption failed");
assert_eq!(decrypted, message);
}
#[test]
fn test_electrum_ecies_no_key_mode() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let message = b"Anonymous message";
let encrypted = ecies::electrum_encrypt(message, &bob.public_key(), &alice, true)
.expect("Encryption failed");
let decrypted = ecies::electrum_decrypt(&encrypted, &bob, Some(&alice.public_key()))
.expect("Decryption failed");
assert_eq!(decrypted, message);
let result = ecies::electrum_decrypt(&encrypted, &bob, None);
assert!(
result.is_err(),
"Should fail without sender's public key when no_key=true"
);
}
#[test]
fn test_bitcore_ecies_roundtrip() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let message = b"Bitcore-style encrypted message";
let encrypted = ecies::bitcore_encrypt(message, &bob.public_key(), &alice, None)
.expect("Encryption failed");
let decrypted = ecies::bitcore_decrypt(&encrypted, &bob).expect("Decryption failed");
assert_eq!(decrypted, message);
}
#[test]
fn test_bitcore_ecies_with_fixed_iv() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let message = b"Deterministic encryption test";
let iv = [0x42u8; 16];
let encrypted1 = ecies::bitcore_encrypt(message, &bob.public_key(), &alice, Some(&iv)).unwrap();
let encrypted2 = ecies::bitcore_encrypt(message, &bob.public_key(), &alice, Some(&iv)).unwrap();
assert_eq!(encrypted1, encrypted2);
let decrypted = ecies::bitcore_decrypt(&encrypted1, &bob).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_ecies_self_encryption() {
let key = PrivateKey::random();
let message = b"Self-encrypted message";
let encrypted = ecies::encrypt_single(message, &key).expect("Self-encryption failed");
let decrypted = ecies::decrypt_single(&encrypted, &key).expect("Self-decryption failed");
assert_eq!(decrypted, message);
}
#[test]
fn test_ecies_wrong_key_fails() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let charlie = PrivateKey::random(); let message = b"This should not be readable by Charlie";
let encrypted = ecies::electrum_encrypt(message, &bob.public_key(), &alice, false).unwrap();
let result = ecies::electrum_decrypt(&encrypted, &charlie, Some(&alice.public_key()));
assert!(result.is_err(), "Decryption with wrong key should fail");
}
#[test]
fn test_ecies_empty_and_large_messages() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let empty_encrypted = ecies::electrum_encrypt(b"", &bob.public_key(), &alice, false).unwrap();
let empty_decrypted =
ecies::electrum_decrypt(&empty_encrypted, &bob, Some(&alice.public_key())).unwrap();
assert_eq!(empty_decrypted, b"");
let large_message = vec![0xAB_u8; 65536];
let large_encrypted =
ecies::bitcore_encrypt(&large_message, &bob.public_key(), &alice, None).unwrap();
let large_decrypted = ecies::bitcore_decrypt(&large_encrypted, &bob).unwrap();
assert_eq!(large_decrypted, large_message);
}
#[test]
fn test_base58_roundtrip() {
let test_data = vec![
vec![0x00],
vec![0x00, 0x00, 0x01],
vec![0x00, 0x00, 0x00, 0x28, 0x7f, 0xb4, 0xcd],
vec![0xFF; 32],
(0..100).collect::<Vec<u8>>(),
];
for data in test_data {
let encoded = base58::encode(&data);
let decoded = base58::decode(&encoded).expect("Decode failed");
assert_eq!(
decoded,
data,
"Roundtrip failed for data of length {}",
data.len()
);
}
}
#[test]
fn test_base58_leading_zeros() {
let data = [0x00, 0x00, 0x00, 0x01];
let encoded = base58::encode(&data);
assert!(
encoded.starts_with("111"),
"Expected leading 1s for leading zeros"
);
let decoded = base58::decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_base58_known_values() {
let genesis_hash =
from_hex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").unwrap();
let encoded = base58::encode(&genesis_hash);
let decoded = base58::decode(&encoded).unwrap();
assert_eq!(decoded, genesis_hash);
}
#[test]
fn test_mnemonic_to_signed_message() {
let mnemonic = Mnemonic::new(WordCount::Words12).unwrap();
let master = generate_hd_key_from_mnemonic(&mnemonic, "", Network::Mainnet).unwrap();
let derived = master.derive_path("m/44'/0'/0'/0/0").unwrap();
let private_key = derived.private_key().unwrap();
let address = derived.address(true).unwrap();
let message = b"Signed with HD key";
let signature = bsm::sign_message(&private_key, message).unwrap();
assert!(bsm::verify_message(&address, &signature, message).unwrap());
}
#[test]
fn test_mnemonic_to_ecies_encryption() {
let mnemonic_alice = Mnemonic::new(WordCount::Words12).unwrap();
let mnemonic_bob = Mnemonic::new(WordCount::Words12).unwrap();
let alice_master =
generate_hd_key_from_mnemonic(&mnemonic_alice, "", Network::Mainnet).unwrap();
let bob_master = generate_hd_key_from_mnemonic(&mnemonic_bob, "", Network::Mainnet).unwrap();
let alice_key = alice_master
.derive_path("m/44'/0'/0'/0/0")
.unwrap()
.private_key()
.unwrap();
let bob_key = bob_master
.derive_path("m/44'/0'/0'/0/0")
.unwrap()
.private_key()
.unwrap();
let message = b"Secret from Alice to Bob using HD keys";
let encrypted =
ecies::electrum_encrypt(message, &bob_key.public_key(), &alice_key, false).unwrap();
let decrypted =
ecies::electrum_decrypt(&encrypted, &bob_key, Some(&alice_key.public_key())).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_invalid_mnemonic_phrase() {
let result = Mnemonic::from_phrase("invalid word here abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about");
assert!(result.is_err());
let result = Mnemonic::from_phrase("abandon abandon");
assert!(result.is_err());
let result = Mnemonic::from_phrase("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon");
assert!(result.is_err());
}
#[test]
fn test_invalid_extended_key_string() {
let result = ExtendedKey::from_string("invalid_xprv_string");
assert!(result.is_err());
let result = ExtendedKey::from_string("xprv123");
assert!(result.is_err());
let result = ExtendedKey::from_string("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHj");
assert!(result.is_err());
}
#[test]
fn test_invalid_derivation_path() {
let seed = [0u8; 32];
let master = ExtendedKey::new_master(&seed, Network::Mainnet).unwrap();
let result = master.derive_path("not/a/path");
assert!(result.is_err());
let result = master.derive_path("m/-1");
assert!(result.is_err());
}
#[test]
fn test_invalid_bsm_signature() {
let key = PrivateKey::random();
let address = key.public_key().to_address();
let result = bsm::verify_message(&address, &[0u8; 64], b"test");
assert!(result.is_err());
let result = bsm::verify_message(&address, &[0u8; 66], b"test");
assert!(result.is_err());
}
#[test]
fn test_invalid_ecies_ciphertext() {
let key = PrivateKey::random();
let result = ecies::electrum_decrypt(&[0u8; 100], &key, None);
assert!(result.is_err());
let result = ecies::bitcore_decrypt(&[0u8; 32], &key);
assert!(result.is_err());
}
#[test]
fn test_word_count_enum() {
assert_eq!(WordCount::Words12.word_count(), 12);
assert_eq!(WordCount::Words15.word_count(), 15);
assert_eq!(WordCount::Words18.word_count(), 18);
assert_eq!(WordCount::Words21.word_count(), 21);
assert_eq!(WordCount::Words24.word_count(), 24);
assert_eq!(WordCount::Words12.entropy_bytes(), 16);
assert_eq!(WordCount::Words24.entropy_bytes(), 32);
}
#[test]
fn test_language_enum() {
let lang = Language::English;
assert!(matches!(lang, Language::English));
}
#[test]
fn test_network_enum() {
let mainnet = Network::Mainnet;
let testnet = Network::Testnet;
assert!(matches!(mainnet, Network::Mainnet));
assert!(matches!(testnet, Network::Testnet));
}