1"""BTC Address."""
2
3# standard
4from hashlib import sha256
5import re
6
7# local
8from validators.utils import validator
9
10
11def _decode_base58(addr: str):
12 """Decode base58."""
13 alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
14 return sum((58**enm) * alphabet.index(idx) for enm, idx in enumerate(addr[::-1]))
15
16
17def _validate_old_btc_address(addr: str):
18 """Validate P2PKH and P2SH type address."""
19 if len(addr) not in range(25, 35):
20 return False
21 decoded_bytes = _decode_base58(addr).to_bytes(25, "big")
22 header, checksum = decoded_bytes[:-4], decoded_bytes[-4:]
23 return checksum == sha256(sha256(header).digest()).digest()[:4]
24
25
26@validator
27def btc_address(value: str, /):
28 """Return whether or not given value is a valid bitcoin address.
29
30 Full validation is implemented for P2PKH and P2SH addresses.
31 For segwit addresses a regexp is used to provide a reasonable
32 estimate on whether the address is valid.
33
34 Examples:
35 >>> btc_address('3Cwgr2g7vsi1bXDUkpEnVoRLA9w4FZfC69')
36 True
37 >>> btc_address('1BvBMsEYstWetqTFn5Au4m4GFg7xJaNVN2')
38 ValidationError(func=btc_address, args={'value': '1BvBMsEYstWetqTFn5Au4m4GFg7xJaNVN2'})
39
40 Args:
41 value:
42 Bitcoin address string to validate.
43
44 Returns:
45 (Literal[True]): If `value` is a valid bitcoin address.
46 (ValidationError): If `value` is an invalid bitcoin address.
47 """
48 if not value:
49 return False
50
51 return (
52 # segwit pattern
53 re.compile(r"^(bc|tc)[0-3][02-9ac-hj-np-z]{14,74}$").match(value)
54 if value[:2] in ("bc", "tb")
55 else _validate_old_btc_address(value)
56 )