Source code for cashscript_py.helpers.bech32

"""Minimal Bech32 helpers used by CashAddr encoding/decoding."""

import re

# The list of 32 symbols used in Bech32 encoding.
bech32_character_set = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"

# An object mapping each of the 32 symbols used in Bech32 encoding to their respective index in the character set.
bech32_character_set_index = {char: index for index, char in enumerate(bech32_character_set)}


[docs] class BitRegroupingError(ValueError): """Errors returned by bit regrouping during base conversion.""" integer_out_of_range = ( "An integer provided in the source array is out of the range of the specified source word length." ) has_disallowed_padding = "Encountered padding when padding was disallowed." requires_disallowed_padding = "Encoding requires padding while padding is disallowed."
[docs] def regroup_bits( bin: list[int], source_word_length: int, result_word_length: int, allow_padding: bool = True, ) -> list[int]: """Convert a list of integers from source_word_length to result_word_length. Args: bin: Source integers (each fits in source_word_length bits). source_word_length: Bit-width of each input value. result_word_length: Desired bit-width of each output value. allow_padding: If False, fail when padding would be required. Returns: List of regrouped integers. Raises: BitRegroupingError: If input values are out of range or padding is not allowed. """ accumulator = 0 bits = 0 result: list[int] = [] max_result_int = (1 << result_word_length) - 1 for value in bin: if value < 0 or value >> source_word_length != 0: raise BitRegroupingError(BitRegroupingError.integer_out_of_range) accumulator = (accumulator << source_word_length) | value bits += source_word_length while bits >= result_word_length: bits -= result_word_length result.append((accumulator >> bits) & max_result_int) if allow_padding: if bits > 0: result.append((accumulator << (result_word_length - bits)) & max_result_int) else: if bits >= source_word_length or (((accumulator << (result_word_length - bits)) & max_result_int) > 0): raise BitRegroupingError( BitRegroupingError.has_disallowed_padding if bits >= source_word_length else BitRegroupingError.requires_disallowed_padding ) return result
[docs] def encode_bech32(base32_integer_array: list[int]) -> str: """Encode a list of 5-bit integers to a Bech32 string.""" return "".join([bech32_character_set[i] for i in base32_integer_array])
[docs] def decode_bech32(valid_bech32: str) -> list[int]: """Decode a Bech32 string into a list of 5-bit integers. Raises: Bech32DecodingError: If the input contains characters outside of the Bech32 charset. """ if not is_bech32_character_set(valid_bech32): raise Bech32DecodingError(Bech32DecodingError.not_bech32_character_set) return [bech32_character_set_index[char] for char in valid_bech32]
non_bech32_characters = re.compile(f"[^{bech32_character_set}]")
[docs] def is_bech32_character_set(maybe_bech32: str) -> bool: """Return True if the string contains only Bech32 characters.""" return not non_bech32_characters.search(maybe_bech32)
[docs] class Bech32DecodingError(ValueError): """Bech32 decoding error messages.""" not_bech32_character_set = "Bech32 decoding error: input contains characters outside of the Bech32 character set."