2026-03-09 11:11:41 +01:00
|
|
|
import sys
|
|
|
|
|
import os
|
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
|
|
|
|
|
|
|
|
from src.p2tr import generate_p2tr_address
|
|
|
|
|
|
|
|
|
|
SECRETSCAN_URL = "https://secretscan.org/Bitcoin?address={}"
|
|
|
|
|
|
|
|
|
|
NETWORKS = ['mainnet', 'testnet', 'regtest']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_fields():
|
|
|
|
|
result = generate_p2tr_address('mainnet')
|
|
|
|
|
assert set(result.keys()) == {'network', 'script_type', 'private_key_hex', 'private_key_wif', 'internal_pubkey_x_hex', 'address'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_script_type():
|
|
|
|
|
result = generate_p2tr_address('mainnet')
|
|
|
|
|
assert result['script_type'] == 'p2tr'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_address_mainnet():
|
|
|
|
|
result = generate_p2tr_address('mainnet')
|
|
|
|
|
assert result['address'].startswith('bc1p')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_address_testnet():
|
|
|
|
|
result = generate_p2tr_address('testnet')
|
|
|
|
|
assert result['address'].startswith('tb1p')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_address_regtest():
|
|
|
|
|
result = generate_p2tr_address('regtest')
|
|
|
|
|
assert result['address'].startswith('bcrt1p')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_private_key_length():
|
|
|
|
|
result = generate_p2tr_address('mainnet')
|
|
|
|
|
assert len(result['private_key_hex']) == 64
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_internal_pubkey_length():
|
|
|
|
|
result = generate_p2tr_address('mainnet')
|
|
|
|
|
assert len(result['internal_pubkey_x_hex']) == 64 # x-only pubkey, 32 bytes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_unique_addresses():
|
|
|
|
|
r1 = generate_p2tr_address('mainnet')
|
|
|
|
|
r2 = generate_p2tr_address('mainnet')
|
|
|
|
|
assert r1['address'] != r2['address']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_print_secretscan(capsys):
|
|
|
|
|
result = generate_p2tr_address('mainnet')
|
|
|
|
|
url = SECRETSCAN_URL.format(result['address'])
|
|
|
|
|
print(f"\n[P2TR] Address: {result['address']}")
|
|
|
|
|
print(f"[P2TR] Verify on SecretScan: {url}")
|
|
|
|
|
captured = capsys.readouterr()
|
|
|
|
|
assert 'secretscan.org' in captured.out
|
2026-03-10 09:40:01 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Error cases ---
|
|
|
|
|
|
|
|
|
|
def test_p2tr_invalid_network_raises():
|
|
|
|
|
import pytest
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
generate_p2tr_address('invalid_net')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Taproot tweak: output key differs from internal key ---
|
|
|
|
|
|
|
|
|
|
def test_p2tr_tweaked_key_differs_from_internal():
|
|
|
|
|
result = generate_p2tr_address('mainnet')
|
|
|
|
|
# The address encodes the tweaked output key (x-only).
|
|
|
|
|
# Derive internal key x-only from the result and verify they differ.
|
|
|
|
|
internal_x = result['internal_pubkey_x_hex']
|
|
|
|
|
# Decode the bech32m address: find separator (last '1'), skip witness version
|
|
|
|
|
from src.p2tr import convertbits, _BECH32_CHARSET
|
|
|
|
|
addr = result['address']
|
|
|
|
|
sep = addr.rfind('1')
|
|
|
|
|
data_chars = addr[sep + 1:] # data + checksum (6 chars)
|
|
|
|
|
decoded = [_BECH32_CHARSET.index(c) for c in data_chars[:-6]] # strip checksum
|
|
|
|
|
# decoded[0] = witness version (1 for Taproot), decoded[1:] = 32-byte prog in 5-bit groups
|
|
|
|
|
output_prog = bytes(convertbits(decoded[1:], 5, 8, False))
|
|
|
|
|
output_x_hex = output_prog.hex()
|
|
|
|
|
# In key-path-only Taproot, the tweak is non-zero so output != internal
|
|
|
|
|
assert output_x_hex != internal_x
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- WIF format ---
|
|
|
|
|
|
|
|
|
|
def test_p2tr_wif_mainnet_compressed_starts_k_or_l():
|
|
|
|
|
result = generate_p2tr_address('mainnet')
|
|
|
|
|
assert result['private_key_wif'][0] in ('K', 'L')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p2tr_wif_testnet_starts_c():
|
|
|
|
|
result = generate_p2tr_address('testnet')
|
|
|
|
|
assert result['private_key_wif'][0] == 'c'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- All networks produce correct network field ---
|
|
|
|
|
|
|
|
|
|
def test_p2tr_all_networks():
|
|
|
|
|
for network in NETWORKS:
|
|
|
|
|
result = generate_p2tr_address(network)
|
|
|
|
|
assert result['network'] == network
|