Files
easy-wallet/tests/test_hd_wallet.py

235 lines
7.9 KiB
Python
Raw Normal View History

import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from src.hd_wallet import generate_hd_wallet
SECRETSCAN_URL = "https://secretscan.org/Bitcoin?address={}"
TEST_MNEMONIC = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
BIP_TYPES = ['bip44', 'bip49', 'bip84', 'bip86']
# --- Electrum top-level structure ---
def test_hd_top_level_fields():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
assert set(result.keys()) == {'keystore', 'wallet_type', 'use_encryption', 'seed_type', 'seed_version', 'addresses'}
def test_hd_electrum_metadata():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
assert result['wallet_type'] == 'standard'
assert result['use_encryption'] is False
assert result['seed_type'] == 'bip39'
assert result['seed_version'] == 17
# --- Keystore structure ---
def test_hd_keystore_fields():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
ks = result['keystore']
assert set(ks.keys()) == {'type', 'xpub', 'xprv', 'seed', 'passphrase', 'derivation', 'root_fingerprint'}
def test_hd_keystore_type():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
assert result['keystore']['type'] == 'bip32'
def test_hd_keystore_seed():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
assert result['keystore']['seed'] == TEST_MNEMONIC
def test_hd_keystore_root_fingerprint_length():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
assert len(result['keystore']['root_fingerprint']) == 8 # 4 bytes hex
def test_hd_keystore_same_root_fingerprint_across_bip_types():
# Root fingerprint derives from master key, same for all BIP types with same mnemonic
fingerprints = {
bip: generate_hd_wallet('mainnet', bip, TEST_MNEMONIC)['keystore']['root_fingerprint']
for bip in BIP_TYPES
}
assert len(set(fingerprints.values())) == 1
# --- xpub/xprv prefixes per BIP type ---
def test_hd_bip44_xpub_prefix():
ks = generate_hd_wallet('mainnet', 'bip44', TEST_MNEMONIC)['keystore']
assert ks['xpub'].startswith('xpub')
assert ks['xprv'].startswith('xprv')
def test_hd_bip49_xpub_prefix():
ks = generate_hd_wallet('mainnet', 'bip49', TEST_MNEMONIC)['keystore']
assert ks['xpub'].startswith('ypub')
assert ks['xprv'].startswith('yprv')
def test_hd_bip84_xpub_prefix():
ks = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)['keystore']
assert ks['xpub'].startswith('zpub')
assert ks['xprv'].startswith('zprv')
def test_hd_bip86_xpub_prefix():
ks = generate_hd_wallet('mainnet', 'bip86', TEST_MNEMONIC)['keystore']
assert ks['xpub'].startswith('xpub')
assert ks['xprv'].startswith('xprv')
# --- Derivation paths ---
def test_hd_derivation_bip44_mainnet():
ks = generate_hd_wallet('mainnet', 'bip44', TEST_MNEMONIC)['keystore']
assert ks['derivation'] == "m/44'/0'/0'"
def test_hd_derivation_bip49_mainnet():
ks = generate_hd_wallet('mainnet', 'bip49', TEST_MNEMONIC)['keystore']
assert ks['derivation'] == "m/49'/0'/0'"
def test_hd_derivation_bip84_mainnet():
ks = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)['keystore']
assert ks['derivation'] == "m/84'/0'/0'"
def test_hd_derivation_bip86_mainnet():
ks = generate_hd_wallet('mainnet', 'bip86', TEST_MNEMONIC)['keystore']
assert ks['derivation'] == "m/86'/0'/0'"
def test_hd_derivation_testnet_coin_type():
ks = generate_hd_wallet('testnet', 'bip84', TEST_MNEMONIC)['keystore']
assert ks['derivation'] == "m/84'/1'/0'"
# --- Addresses structure ---
def test_hd_addresses_keys():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
assert set(result['addresses'].keys()) == {'receiving', 'change'}
assert result['addresses']['change'] == []
def test_hd_address_entry_fields():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=1)
entry = result['addresses']['receiving'][0]
assert set(entry.keys()) == {'index', 'path', 'address', 'public_key', 'private_key_wif', 'private_key_hex'}
def test_hd_address_path_format():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=3)
for i, entry in enumerate(result['addresses']['receiving']):
assert entry['path'] == f"m/84'/0'/0'/0/{i}"
assert entry['index'] == i
# --- Address prefixes mainnet ---
def test_hd_bip44_address_mainnet():
result = generate_hd_wallet('mainnet', 'bip44', TEST_MNEMONIC, num_addresses=1)
assert result['addresses']['receiving'][0]['address'].startswith('1')
def test_hd_bip49_address_mainnet():
result = generate_hd_wallet('mainnet', 'bip49', TEST_MNEMONIC, num_addresses=1)
assert result['addresses']['receiving'][0]['address'].startswith('3')
def test_hd_bip84_address_mainnet():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=1)
assert result['addresses']['receiving'][0]['address'].startswith('bc1q')
def test_hd_bip86_address_mainnet():
result = generate_hd_wallet('mainnet', 'bip86', TEST_MNEMONIC, num_addresses=1)
assert result['addresses']['receiving'][0]['address'].startswith('bc1p')
# --- Address prefixes testnet ---
def test_hd_bip44_address_testnet():
result = generate_hd_wallet('testnet', 'bip44', TEST_MNEMONIC, num_addresses=1)
assert result['addresses']['receiving'][0]['address'][0] in ('m', 'n')
def test_hd_bip84_address_testnet():
result = generate_hd_wallet('testnet', 'bip84', TEST_MNEMONIC, num_addresses=1)
assert result['addresses']['receiving'][0]['address'].startswith('tb1q')
def test_hd_bip86_address_testnet():
result = generate_hd_wallet('testnet', 'bip86', TEST_MNEMONIC, num_addresses=1)
assert result['addresses']['receiving'][0]['address'].startswith('tb1p')
# --- Determinism ---
def test_hd_same_mnemonic_same_addresses():
r1 = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=3)
r2 = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=3)
addrs1 = [a['address'] for a in r1['addresses']['receiving']]
addrs2 = [a['address'] for a in r2['addresses']['receiving']]
assert addrs1 == addrs2
def test_hd_same_mnemonic_same_xpub():
r1 = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
r2 = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
assert r1['keystore']['xpub'] == r2['keystore']['xpub']
def test_hd_different_mnemonic_different_xpub():
r1 = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC)
r2 = generate_hd_wallet('mainnet', 'bip84') # random mnemonic
assert r1['keystore']['xpub'] != r2['keystore']['xpub']
# --- num_addresses ---
def test_hd_num_addresses():
for n in [1, 3, 10]:
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=n)
assert len(result['addresses']['receiving']) == n
# --- Key format ---
def test_hd_private_key_length():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=1)
assert len(result['addresses']['receiving'][0]['private_key_hex']) == 64
def test_hd_public_key_compressed():
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=1)
pub = result['addresses']['receiving'][0]['public_key']
assert len(pub) == 66
assert pub[:2] in ('02', '03')
# --- Mnemonic generation ---
def test_hd_generates_mnemonic_when_none():
result = generate_hd_wallet('mainnet', 'bip84')
assert len(result['keystore']['seed'].split()) == 12
# --- SecretScan ---
def test_hd_print_secretscan(capsys):
result = generate_hd_wallet('mainnet', 'bip84', TEST_MNEMONIC, num_addresses=3)
for a in result['addresses']['receiving']:
url = SECRETSCAN_URL.format(a['address'])
print(f"\n[HD BIP84] [{a['index']}] {a['address']}")
print(f"[HD BIP84] Verify on SecretScan: {url}")
captured = capsys.readouterr()
assert captured.out.count('secretscan.org') == 3