_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
def _polymod(values):
c = 1
for d in values:
c0 = c >> 35
c = ((c & 0x07FFFFFFFF) << 5) ^ d
if c0 & 0x01:
c ^= 0x98F2BC8E61
if c0 & 0x02:
c ^= 0x79B76D99E2
if c0 & 0x04:
c ^= 0xF33E5FB3C4
if c0 & 0x08:
c ^= 0xAE2EABE2A8
if c0 & 0x10:
c ^= 0x1E4F43E470
retval = c ^ 1
return retval
def _prefix_expand(prefix):
retval = bytearray(ord(x) & 0x1F for x in prefix)
retval.append(0)
return retval
def _create_checksum(prefix, data):
values = _prefix_expand(prefix) + data + bytes(8)
polymod = _polymod(values)
return bytes((polymod >> 5 * (7 - i)) & 31 for i in range(8))
def _convertbits(data, frombits, tobits, pad=True):
acc = 0
bits = 0
ret = bytearray()
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad and bits:
ret.append((acc << (tobits - bits)) & maxv)
return ret
def _pack_addr_data(kind, addr_hash):
version_byte = kind << 3
offset = 1
encoded_size = 0
if len(addr_hash) >= 40:
offset = 2
encoded_size |= 0x04
encoded_size |= (len(addr_hash) - 20 * offset) // (4 * offset)
if (len(addr_hash) - 20 * offset) % (4 * offset) != 0 or not 0 <= encoded_size <= 7:
raise ValueError(f"invalid address hash size {addr_hash}")
version_byte |= encoded_size
data = bytes([version_byte]) + addr_hash
return _convertbits(data, 8, 5, True)
def _decode_payload(addr):
lower = addr.lower()
if lower != addr and addr.upper() != addr:
raise ValueError(f"mixed case in address: {addr}")
parts = lower.split(":", 1)
if len(parts) != 2:
raise ValueError(f"address missing ':' separator: {addr}")
prefix, payload = parts
if not prefix:
raise ValueError(f"address prefix is missing: {addr}")
if not all(33 <= ord(x) <= 126 for x in prefix):
raise ValueError(f"invalid address prefix: {prefix}")
if len(payload) < 8 or len(payload) > 124:
raise ValueError(f"address payload has invalid length: {len(addr)}")
try:
data = bytes(_CHARSET.find(x) for x in payload)
except ValueError as e:
raise ValueError(f"invalid characters in address: {payload}") from e
if _polymod(_prefix_expand(prefix) + data):
raise ValueError(f"invalid checksum in address: {addr}")
if lower != addr:
prefix = prefix.upper()
return prefix, data[:-8]
PUBKEY_TYPE = 0
SCRIPT_TYPE = 1
PUBKEY_TYPE_TOKEN = 2
SCRIPT_TYPE_TOKEN = 3
def decode(address):
if not isinstance(address, str):
raise TypeError("address must be a string")
prefix, payload = _decode_payload(address)
extrabits = len(payload) * 5 % 8
if extrabits >= 5:
raise ValueError(f"excess padding in address {address}")
if payload[-1] & ((1 << extrabits) - 1):
raise ValueError(f"non-zero padding in address {address}")
decoded = _convertbits(payload, 5, 8, False)
version = decoded[0]
addr_hash = bytes(decoded[1:])
size = (version & 0x03) * 4 + 20
if version & 0x04:
size <<= 1
if size != len(addr_hash):
raise ValueError(
f"address hash has length {len(addr_hash)} but expected {size}"
)
kind = version >> 3
if kind not in (SCRIPT_TYPE, PUBKEY_TYPE, SCRIPT_TYPE_TOKEN, PUBKEY_TYPE_TOKEN):
raise ValueError(f"unrecognised address type {kind}")
return prefix, kind, addr_hash
def encode(prefix, kind, addr_hash):
if not isinstance(prefix, str):
raise TypeError("prefix must be a string")
if not isinstance(addr_hash, (bytes, bytearray)):
raise TypeError("addr_hash must be binary bytes")
if kind not in (SCRIPT_TYPE, PUBKEY_TYPE, SCRIPT_TYPE_TOKEN, PUBKEY_TYPE_TOKEN):
raise ValueError(f"unrecognised address type {kind}")
payload = _pack_addr_data(kind, addr_hash)
checksum = _create_checksum(prefix, payload)
return "".join([_CHARSET[d] for d in (payload + checksum)])
def encode_full(prefix, kind, addr_hash):
return ":".join([prefix, encode(prefix, kind, addr_hash)])