-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #228 from joe733/workshop
maint: refresh `btc_address` module
- Loading branch information
Showing
2 changed files
with
57 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |