Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/validators/crypto_addresses/btc_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@


def _decode_base58(addr: str):
"""Decode base58."""
"""Decode base58, preserving each leading one as a zero byte."""
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
return sum((58**enm) * alphabet.index(idx) for enm, idx in enumerate(addr[::-1]))
decoded = sum((58**enm) * alphabet.index(idx) for enm, idx in enumerate(addr[::-1]))
zero_bytes = len(addr) - len(addr.lstrip("1"))
return b"\0" * zero_bytes + decoded.to_bytes((decoded.bit_length() + 7) // 8, "big")


def _validate_old_btc_address(addr: str):
"""Validate P2PKH and P2SH type address."""
if len(addr) not in range(25, 35):
return False
decoded_bytes = _decode_base58(addr).to_bytes(25, "big")
decoded_bytes = _decode_base58(addr)
if len(decoded_bytes) != 25:
return False
header, checksum = decoded_bytes[:-4], decoded_bytes[-4:]
return checksum == sha256(sha256(header).digest()).digest()[:4]

Expand Down
19 changes: 19 additions & 0 deletions tests/crypto_addresses/test_btc_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
[
# P2PKH (Pay-to-PubkeyHash) type
"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2",
# P2PKH with zero bytes in the payload, encoded as leading ones
"1111111111111111111114oLvT2",
# Legacy testnet P2PKH
"mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn",
# P2SH (Pay to script hash) type
"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy",
# Bech32/segwit type
Expand Down Expand Up @@ -41,3 +45,18 @@ def test_returns_true_on_valid_btc_address(value: str):
def test_returns_failed_validation_on_invalid_btc_address(value: str):
"""Test returns failed validation on invalid btc address."""
assert isinstance(btc_address(value), ValidationError)


@pytest.mark.parametrize(
"value",
[
# Removing the version byte's leading one must not be silently padded.
"BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2",
# Both remain within the existing character-count bounds.
"111111111111111111114oLvT2",
"11111111111111111111114oLvT2",
],
)
def test_rejects_changed_leading_zero_bytes(value: str):
"""Leading ones encode bytes and cannot be added or removed."""
assert isinstance(btc_address(value), ValidationError)