Skip to content

Commit

Permalink
Merge pull request #228 from joe733/workshop
Browse files Browse the repository at this point in the history
maint: refresh `btc_address` module
  • Loading branch information
yozachar authored Feb 25, 2023
2 parents 5dd5ebb + f8fe2d8 commit a68ff9a
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 45 deletions.
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

0 comments on commit a68ff9a

Please sign in to comment.