Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/cashaddr.py
Show All 9 Lines | |||||
_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" | _CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" | ||||
def _polymod(values): | def _polymod(values): | ||||
"""Internal function that computes the cashaddr checksum.""" | """Internal function that computes the cashaddr checksum.""" | ||||
c = 1 | c = 1 | ||||
for d in values: | for d in values: | ||||
c0 = c >> 35 | c0 = c >> 35 | ||||
c = ((c & 0x07ffffffff) << 5) ^ d | c = ((c & 0x07FFFFFFFF) << 5) ^ d | ||||
if (c0 & 0x01): | if c0 & 0x01: | ||||
c ^= 0x98f2bc8e61 | c ^= 0x98F2BC8E61 | ||||
if (c0 & 0x02): | if c0 & 0x02: | ||||
c ^= 0x79b76d99e2 | c ^= 0x79B76D99E2 | ||||
if (c0 & 0x04): | if c0 & 0x04: | ||||
c ^= 0xf33e5fb3c4 | c ^= 0xF33E5FB3C4 | ||||
if (c0 & 0x08): | if c0 & 0x08: | ||||
c ^= 0xae2eabe2a8 | c ^= 0xAE2EABE2A8 | ||||
if (c0 & 0x10): | if c0 & 0x10: | ||||
c ^= 0x1e4f43e470 | c ^= 0x1E4F43E470 | ||||
retval = c ^ 1 | retval = c ^ 1 | ||||
return retval | return retval | ||||
def _prefix_expand(prefix): | def _prefix_expand(prefix): | ||||
"""Expand the prefix into values for checksum computation.""" | """Expand the prefix into values for checksum computation.""" | ||||
retval = bytearray(ord(x) & 0x1f for x in prefix) | retval = bytearray(ord(x) & 0x1F for x in prefix) | ||||
# Append null separator | # Append null separator | ||||
retval.append(0) | retval.append(0) | ||||
return retval | return retval | ||||
def _create_checksum(prefix, data): | def _create_checksum(prefix, data): | ||||
"""Compute the checksum values given prefix and data.""" | """Compute the checksum values given prefix and data.""" | ||||
values = _prefix_expand(prefix) + data + bytes(8) | values = _prefix_expand(prefix) + data + bytes(8) | ||||
Show All 29 Lines | def _pack_addr_data(kind, addr_hash): | ||||
offset = 1 | offset = 1 | ||||
encoded_size = 0 | encoded_size = 0 | ||||
if len(addr_hash) >= 40: | if len(addr_hash) >= 40: | ||||
offset = 2 | offset = 2 | ||||
encoded_size |= 0x04 | encoded_size |= 0x04 | ||||
encoded_size |= (len(addr_hash) - 20 * offset) // (4 * offset) | encoded_size |= (len(addr_hash) - 20 * offset) // (4 * offset) | ||||
# invalid size? | # invalid size? | ||||
if ((len(addr_hash) - 20 * offset) % (4 * offset) != 0 | if (len(addr_hash) - 20 * offset) % (4 * offset) != 0 or not 0 <= encoded_size <= 7: | ||||
or not 0 <= encoded_size <= 7): | raise ValueError(f"invalid address hash size {addr_hash}") | ||||
raise ValueError(f'invalid address hash size {addr_hash}') | |||||
version_byte |= encoded_size | version_byte |= encoded_size | ||||
data = bytes([version_byte]) + addr_hash | data = bytes([version_byte]) + addr_hash | ||||
return _convertbits(data, 8, 5, True) | return _convertbits(data, 8, 5, True) | ||||
def _decode_payload(addr): | def _decode_payload(addr): | ||||
"""Validate a cashaddr string. | """Validate a cashaddr string. | ||||
Throws CashAddr.Error if it is invalid, otherwise returns the | Throws CashAddr.Error if it is invalid, otherwise returns the | ||||
tuple | tuple | ||||
(prefix, payload) | (prefix, payload) | ||||
without the checksum. | without the checksum. | ||||
""" | """ | ||||
lower = addr.lower() | lower = addr.lower() | ||||
if lower != addr and addr.upper() != addr: | if lower != addr and addr.upper() != addr: | ||||
raise ValueError(f'mixed case in address: {addr}') | raise ValueError(f"mixed case in address: {addr}") | ||||
parts = lower.split(':', 1) | parts = lower.split(":", 1) | ||||
if len(parts) != 2: | if len(parts) != 2: | ||||
raise ValueError(f"address missing ':' separator: {addr}") | raise ValueError(f"address missing ':' separator: {addr}") | ||||
prefix, payload = parts | prefix, payload = parts | ||||
if not prefix: | if not prefix: | ||||
raise ValueError(f'address prefix is missing: {addr}') | raise ValueError(f"address prefix is missing: {addr}") | ||||
if not all(33 <= ord(x) <= 126 for x in prefix): | if not all(33 <= ord(x) <= 126 for x in prefix): | ||||
raise ValueError(f'invalid address prefix: {prefix}') | raise ValueError(f"invalid address prefix: {prefix}") | ||||
if not (8 <= len(payload) <= 124): | if not (8 <= len(payload) <= 124): | ||||
raise ValueError(f'address payload has invalid length: {len(addr)}') | raise ValueError(f"address payload has invalid length: {len(addr)}") | ||||
try: | try: | ||||
data = bytes(_CHARSET.find(x) for x in payload) | data = bytes(_CHARSET.find(x) for x in payload) | ||||
except ValueError: | except ValueError: | ||||
raise ValueError(f'invalid characters in address: {payload}') | raise ValueError(f"invalid characters in address: {payload}") | ||||
if _polymod(_prefix_expand(prefix) + data): | if _polymod(_prefix_expand(prefix) + data): | ||||
raise ValueError(f'invalid checksum in address: {addr}') | raise ValueError(f"invalid checksum in address: {addr}") | ||||
if lower != addr: | if lower != addr: | ||||
prefix = prefix.upper() | prefix = prefix.upper() | ||||
# Drop the 40 bit checksum | # Drop the 40 bit checksum | ||||
return prefix, data[:-8] | return prefix, data[:-8] | ||||
# | # | ||||
# External Interface | # External Interface | ||||
# | # | ||||
PUBKEY_TYPE = 0 | PUBKEY_TYPE = 0 | ||||
SCRIPT_TYPE = 1 | SCRIPT_TYPE = 1 | ||||
def decode(address): | def decode(address): | ||||
'''Given a cashaddr address, return a tuple | """Given a cashaddr address, return a tuple | ||||
(prefix, kind, hash) | (prefix, kind, hash) | ||||
''' | """ | ||||
if not isinstance(address, str): | if not isinstance(address, str): | ||||
raise TypeError('address must be a string') | raise TypeError("address must be a string") | ||||
prefix, payload = _decode_payload(address) | prefix, payload = _decode_payload(address) | ||||
# Ensure there isn't extra padding | # Ensure there isn't extra padding | ||||
extrabits = len(payload) * 5 % 8 | extrabits = len(payload) * 5 % 8 | ||||
if extrabits >= 5: | if extrabits >= 5: | ||||
raise ValueError(f'excess padding in address {address}') | raise ValueError(f"excess padding in address {address}") | ||||
# Ensure extrabits are zeros | # Ensure extrabits are zeros | ||||
if payload[-1] & ((1 << extrabits) - 1): | if payload[-1] & ((1 << extrabits) - 1): | ||||
raise ValueError(f'non-zero padding in address {address}') | raise ValueError(f"non-zero padding in address {address}") | ||||
decoded = _convertbits(payload, 5, 8, False) | decoded = _convertbits(payload, 5, 8, False) | ||||
version = decoded[0] | version = decoded[0] | ||||
addr_hash = bytes(decoded[1:]) | addr_hash = bytes(decoded[1:]) | ||||
size = (version & 0x03) * 4 + 20 | size = (version & 0x03) * 4 + 20 | ||||
# Double the size, if the 3rd bit is on. | # Double the size, if the 3rd bit is on. | ||||
if version & 0x04: | if version & 0x04: | ||||
size <<= 1 | size <<= 1 | ||||
if size != len(addr_hash): | if size != len(addr_hash): | ||||
raise ValueError( | raise ValueError( | ||||
f'address hash has length {len(addr_hash)} but expected {size}') | f"address hash has length {len(addr_hash)} but expected {size}" | ||||
) | |||||
kind = version >> 3 | kind = version >> 3 | ||||
if kind not in (SCRIPT_TYPE, PUBKEY_TYPE): | if kind not in (SCRIPT_TYPE, PUBKEY_TYPE): | ||||
raise ValueError(f'unrecognised address type {kind}') | raise ValueError(f"unrecognised address type {kind}") | ||||
return prefix, kind, addr_hash | return prefix, kind, addr_hash | ||||
def encode(prefix, kind, addr_hash): | def encode(prefix, kind, addr_hash): | ||||
"""Encode a cashaddr address without prefix and separator.""" | """Encode a cashaddr address without prefix and separator.""" | ||||
if not isinstance(prefix, str): | if not isinstance(prefix, str): | ||||
raise TypeError('prefix must be a string') | raise TypeError("prefix must be a string") | ||||
if not isinstance(addr_hash, (bytes, bytearray)): | if not isinstance(addr_hash, (bytes, bytearray)): | ||||
raise TypeError('addr_hash must be binary bytes') | raise TypeError("addr_hash must be binary bytes") | ||||
if kind not in (SCRIPT_TYPE, PUBKEY_TYPE): | if kind not in (SCRIPT_TYPE, PUBKEY_TYPE): | ||||
raise ValueError(f'unrecognised address type {kind}') | raise ValueError(f"unrecognised address type {kind}") | ||||
payload = _pack_addr_data(kind, addr_hash) | payload = _pack_addr_data(kind, addr_hash) | ||||
checksum = _create_checksum(prefix, payload) | checksum = _create_checksum(prefix, payload) | ||||
return ''.join([_CHARSET[d] for d in (payload + checksum)]) | return "".join([_CHARSET[d] for d in (payload + checksum)]) | ||||
def encode_full(prefix, kind, addr_hash): | def encode_full(prefix, kind, addr_hash): | ||||
"""Encode a full cashaddr address, with prefix and separator.""" | """Encode a full cashaddr address, with prefix and separator.""" | ||||
return ':'.join([prefix, encode(prefix, kind, addr_hash)]) | return ":".join([prefix, encode(prefix, kind, addr_hash)]) |