pycardano >=0.11 doesn't ship HDPublicKey — the v0.1 extraction imported a symbol that never existed in the installed version, which meant every address-derivation test failed at import time. Use HDWallet rooted at the account level with public-only fields set, then soft-derive receive chain (0) and staking chain (2). Hash the resulting verification keys through PaymentVerificationKey / StakeVerificationKey to compose the Shelley base address. Refresh the test-vector xpub with a real, deterministic CIP-1852 account xpub derived from the well-known "test ... junk" mnemonic so validate_xpub + derive_address actually exercise the BIP32 math. Drop the "random hex should be rejected" assertion — BIP32-ED25519 soft derivation doesn't enforce that the 32-byte pubkey half is a point on the curve, so arbitrary well-shaped hex is accepted by the underlying crypto.
71 lines
2.8 KiB
Python
71 lines
2.8 KiB
Python
"""Deterministic address-derivation smoke test.
|
|
|
|
Uses a known test-vector xpub (the one shipped in the pycardano docs)
|
|
to assert the derived addresses are stable and reproducible across SDK
|
|
versions. If this test ever changes output, we have a backwards-compat
|
|
problem that would break every merchant's receive-address history.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from cardano_checkout import addresses
|
|
|
|
|
|
# Public test vector — a CIP-1852 account extended public key.
|
|
# 64 bytes = 32 bytes Ed25519 pubkey || 32 bytes chain code, hex encoded.
|
|
#
|
|
# Derived deterministically from the well-known test mnemonic
|
|
# "test test test test test test test test test test test junk"
|
|
# at path m/1852'/1815'/0' via pycardano's HDWallet. Using a real,
|
|
# on-curve account xpub here (as opposed to random hex) is what lets
|
|
# validate_xpub + derive_address actually exercise the BIP32 math.
|
|
TEST_XPUB_HEX = (
|
|
"f2cdeef60dfc2c00cd1d4c0def0ce3f7b0328f5badd2fd771f48ff207ca7eaa8"
|
|
"500a3c3d556f995e79c4a75e64d13ab12772f46e6c05fed1d9698b7e12a533f7"
|
|
)
|
|
|
|
|
|
def test_validate_xpub_accepts_well_formed_key() -> None:
|
|
assert addresses.validate_xpub(TEST_XPUB_HEX) is True
|
|
|
|
|
|
def test_validate_xpub_rejects_empty_and_junk() -> None:
|
|
assert addresses.validate_xpub("") is False
|
|
assert addresses.validate_xpub("notreallyhex!!") is False
|
|
assert addresses.validate_xpub("deadbeef") is False # wrong length
|
|
# Note: a correct-length random-hex string IS accepted — BIP32-ED25519
|
|
# soft derivation over a 64-byte input doesn't require the public key
|
|
# half to be a point on the curve. We only catch shape errors here.
|
|
|
|
|
|
def test_derive_address_is_deterministic() -> None:
|
|
a0 = addresses.derive_address(TEST_XPUB_HEX, index=0, network="mainnet")
|
|
a0_again = addresses.derive_address(TEST_XPUB_HEX, index=0, network="mainnet")
|
|
assert a0 == a0_again
|
|
assert a0.startswith("addr1")
|
|
|
|
|
|
def test_derive_address_distinct_per_index() -> None:
|
|
a0 = addresses.derive_address(TEST_XPUB_HEX, index=0, network="mainnet")
|
|
a1 = addresses.derive_address(TEST_XPUB_HEX, index=1, network="mainnet")
|
|
a42 = addresses.derive_address(TEST_XPUB_HEX, index=42, network="mainnet")
|
|
assert a0 != a1 != a42
|
|
|
|
|
|
def test_derive_address_network_switch_changes_prefix() -> None:
|
|
mainnet = addresses.derive_address(TEST_XPUB_HEX, index=0, network="mainnet")
|
|
testnet = addresses.derive_address(TEST_XPUB_HEX, index=0, network="testnet")
|
|
assert mainnet.startswith("addr1")
|
|
assert testnet.startswith("addr_test1")
|
|
|
|
|
|
def test_derive_address_rejects_negative_index() -> None:
|
|
with pytest.raises(ValueError, match="non-negative"):
|
|
addresses.derive_address(TEST_XPUB_HEX, index=-1)
|
|
|
|
|
|
def test_derive_address_rejects_bad_network() -> None:
|
|
with pytest.raises(ValueError, match="Invalid network"):
|
|
addresses.derive_address(TEST_XPUB_HEX, index=0, network="preprod")
|