Skip to content

Instantly share code, notes, and snippets.

@parodyBit
Last active August 17, 2022 14:17
Show Gist options
  • Save parodyBit/3a19e357403c85d31cfe4543d808c891 to your computer and use it in GitHub Desktop.
Save parodyBit/3a19e357403c85d31cfe4543d808c891 to your computer and use it in GitHub Desktop.
bech32 encoder
# This is a bech32 encoder
# run with command line arguments
# $ python3 bech32_encoder.py <prefix> <address>
# $ python3 bech32_encoder.py wit 0x000000000000000000000000000000000000dead
import sys
from typing import List, AnyStr
import unicodedata
from enum import Enum
def bytes_to_int(bts: bytes) -> int:
return int.from_bytes(bts, 'big')
def int_to_bytes(i: int) -> bytes:
length = max(1, (i.bit_length() + 7) // 8)
return i.to_bytes(length, 'big')
Bytes = List[int]
class BaseConversionError(Exception):
pass
def convert_bits(data: Bytes, from_bits: int, to_bits: int, pad=True) -> Bytes:
"""General power-of-2 base conversion."""
acc = 0
bits = 0
ret = []
max_v = (1 << to_bits) - 1
max_acc = (1 << (from_bits + to_bits - 1)) - 1
for value in data:
if value < 0 or (value >> from_bits):
raise BaseConversionError
acc = ((acc << from_bits) | value) & max_acc
bits += from_bits
while bits >= to_bits:
bits -= to_bits
ret.append((acc >> bits) & max_v)
if pad:
if bits:
ret.append((acc << (to_bits - bits)) & max_v)
elif bits >= from_bits or ((acc << (to_bits - bits)) & max_v):
raise BaseConversionError
return ret
def normalize_string(txt: AnyStr) -> str:
if isinstance(txt, bytes):
utxt = txt.decode('utf8')
elif isinstance(txt, str):
utxt = txt
else:
raise TypeError('String value expected')
return unicodedata.normalize('NFKD', utxt)
ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
BASE = len(ALPHABET)
class Bech32Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
BECH32M = 2
class Bech32DecodeError(Exception):
pass
def bech32_poly_mod(values) -> int:
"""Internal function that computes the Bech32 checksum."""
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk = 1
for value in values:
top = chk >> 25
chk = (chk & 0x1ffffff) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
return chk
def bech32_hrp_expand(hrp: str) -> List[int]:
"""Expand the HRP into values for checksum computation."""
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
def bech32_verify_checksum(hrp: str, data) -> bool:
"""Verify a checksum given HRP and converted data characters."""
return bech32_poly_mod(bech32_hrp_expand(hrp) + data) == 1
def bech32_create_checksum(hrp: str, data):
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
polymod = bech32_poly_mod(values + [0, 0, 0, 0, 0, 0]) ^ 1
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
def bech32_decode_address(bech: str):
bech, separator = bech.lower(), bech.find('1')
hrp_ = bech[:separator]
data = [CHARSET.find(x) for x in bech[separator + 1:]]
decoded = data[:-6]
try:
if any(ord(x) < 33 or ord(x) > 126 for x in bech):
raise Bech32DecodeError('Character outside US-ASCII [33-126] range')
if (bech.lower() != bech) and (bech.upper() != bech):
raise Bech32DecodeError('Mixed upper and lower case')
if separator == 0:
raise Bech32DecodeError('Empty human readable part')
elif separator == -1:
raise Bech32DecodeError('No separator character')
elif separator + 7 > len(bech):
raise Bech32DecodeError('Checksum too short')
if not all(x in CHARSET for x in bech[separator + 1:]):
raise Bech32DecodeError('Character not in charset')
if not bech32_verify_checksum(hrp_, data):
raise Bech32DecodeError('Invalid checksum')
if decoded is None or len(decoded) < 2:
raise Bech32DecodeError('Witness program too short')
except Bech32DecodeError as error:
print(error)
b256 = convert_bits(decoded, from_bits=5, to_bits=8, pad=True)
return b256
def bech32_encode_address(hrp: str, data: str) -> str:
hex_bytes = [b for b in bytes.fromhex(data)]
data = convert_bits(data=hex_bytes, from_bits=8, to_bits=5, pad=False)
checksum = bech32_create_checksum(hrp=hrp, data=data)
combined = data + checksum
return normalize_string(hrp + '1' + ''.join([CHARSET[i] for i in combined]))
if __name__ == '__main__':
public_key = sys.argv[2]
prefix = sys.argv[1]
if public_key.startswith('0x'):
public_key = public_key[2:]
address = bech32_encode_address(prefix, public_key)
print(address)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment