From df2317223dbb559a027000e79fc9e3b7c80edc4b Mon Sep 17 00:00:00 2001 From: Plastic-time <247457381+Plastic-time@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:00:21 -0400 Subject: [PATCH] Reject altered leading zero bytes in BTC addresses --- .../crypto_addresses/btc_address.py | 10 +++++++--- tests/crypto_addresses/test_btc_address.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/validators/crypto_addresses/btc_address.py b/src/validators/crypto_addresses/btc_address.py index f781f3b9..c404614c 100644 --- a/src/validators/crypto_addresses/btc_address.py +++ b/src/validators/crypto_addresses/btc_address.py @@ -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] diff --git a/tests/crypto_addresses/test_btc_address.py b/tests/crypto_addresses/test_btc_address.py index 96647eab..8fcd5714 100644 --- a/tests/crypto_addresses/test_btc_address.py +++ b/tests/crypto_addresses/test_btc_address.py @@ -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 @@ -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)