Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

maint: refresh btc_address module #228

Merged
merged 1 commit into from
Feb 25, 2023
Merged
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
30 changes: 18 additions & 12 deletions tests/test_btc_address.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
"""Test BTC address."""
# -*- coding: utf-8 -*-

# standard
import pytest

# local
from validators import btc_address, ValidationFailure


@pytest.mark.parametrize(
'value',
"value",
[
# P2PKH (Pay-to-PubkeyHash) type
'1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2',
"1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2",
# P2SH (Pay to script hash) type
'3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy',
"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy",
# Bech32/segwit type
'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq',
'bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9',
"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
"bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9",
],
)
def test_returns_true_on_valid_btc_address(value):
def test_returns_true_on_valid_btc_address(value: str):
"""Test returns true on valid btc address."""
assert btc_address(value)


@pytest.mark.parametrize(
'value',
"value",
[
'ff3Cwgr2g7vsi1bXDUkpEnVoRLA9w4FZfC69',
'b3Cgwgr2g7vsi1bXyjyDUkphEnVoRLA9w4FZfC69',
"ff3Cwgr2g7vsi1bXDUkpEnVoRLA9w4FZfC69",
"b3Cgwgr2g7vsi1bXyjyDUkphEnVoRLA9w4FZfC69",
# incorrect header
'1BvBMsEYstWetqTFn5Au4m4GFg7xJaNVN2',
"1BvBMsEYstWetqTFn5Au4m4GFg7xJaNVN2",
# incorrect checksum
'3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLz',
"3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLz",
],
)
def test_returns_failed_validation_on_invalid_btc_address(value):
def test_returns_failed_validation_on_invalid_btc_address(value: str):
"""Test returns failed validation on invalid btc address."""
assert isinstance(btc_address(value), ValidationFailure)
72 changes: 39 additions & 33 deletions validators/btc_address.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,61 @@
import re
"""BTC Address."""
# -*- coding: utf-8 -*-

# standard
from hashlib import sha256
import re

# local
from .utils import validator

segwit_pattern = re.compile(
r'^(bc|tc)[0-3][02-9ac-hj-np-z]{14,74}$')


def validate_segwit_address(addr):
return segwit_pattern.match(addr)
_segwit_pattern = re.compile(r"^(bc|tc)[0-3][02-9ac-hj-np-z]{14,74}$")


def decode_base58(addr):
def _decode_base58(addr: str):
"""Decode base58."""
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
return sum([
(58 ** e) * alphabet.index(i)
for e, i in enumerate(addr[::-1])
])
return sum((58**enm) * alphabet.index(idx) for enm, idx in enumerate(addr[::-1]))


def validate_old_btc_address(addr):
"Validate P2PKH and P2SH type address"
if not len(addr) in range(25, 35):
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")
header = decoded_bytes[:-4]
checksum = decoded_bytes[-4:]
decoded_bytes = _decode_base58(addr).to_bytes(25, "big")
header, checksum = decoded_bytes[:-4], decoded_bytes[-4:]
return checksum == sha256(sha256(header).digest()).digest()[:4]


@validator
def btc_address(value):
"""
Return whether or not given value is a valid bitcoin address.

If the value is valid bitcoin address this function returns ``True``,
otherwise :class:`~validators.utils.ValidationFailure`.
def btc_address(value: str, /):
"""Return whether or not given value is a valid bitcoin address.

Full validation is implemented for P2PKH and P2SH addresses.
For segwit addresses a regexp is used to provide a reasonable estimate
on whether the address is valid.
For segwit addresses a regexp is used to provide a reasonable
estimate on whether the address is valid.

Examples::

>>> btc_address('3Cwgr2g7vsi1bXDUkpEnVoRLA9w4FZfC69')
True
# Output: True
>>> btc_address('1BvBMsEYstWetqTFn5Au4m4GFg7xJaNVN2')
# Output: ValidationFailure(func=btc_address, args=...)

Args:
`value`:
[Required] Bitcoin address string to validate.

Returns:
`True`:
If the value is valid bitcoin address.
`ValidationFailure`:
If the value is an invalid bitcoin address.

:param value: Bitcoin address string to validate
"""
if not value or not isinstance(value, str):
return False
if value[:2] in ("bc", "tb"):
return validate_segwit_address(value)
return validate_old_btc_address(value)
if value and type(value) is str:
return (
_segwit_pattern.match(value)
if value[:2] in ("bc", "tb")
else _validate_old_btc_address(value)
)
return False