Skip to content

Instantly share code, notes, and snippets.

@10gic
Created March 7, 2021 14:09
Show Gist options
  • Save 10gic/66be6ed2125551bf1870e20cb2570631 to your computer and use it in GitHub Desktop.
Save 10gic/66be6ed2125551bf1870e20cb2570631 to your computer and use it in GitHub Desktop.

Revisions

  1. 10gic created this gist Mar 7, 2021.
    170 changes: 170 additions & 0 deletions addr_to_hash160.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,170 @@
    import sys
    import binascii
    import argparse

    # base58 from https://github.com/keis/base58
    BITCOIN_ALPHABET = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'


    def b58decode_int(
    v: bytes, alphabet: bytes = BITCOIN_ALPHABET
    ) -> int:
    """
    Decode a Base58 encoded string as an integer
    """
    v = v.rstrip()

    decimal = 0
    for char in v:
    decimal = decimal * 58 + alphabet.index(char)
    return decimal


    def b58decode(
    v: str, alphabet: bytes = BITCOIN_ALPHABET
    ) -> bytes:
    """
    Decode a Base58 encoded string
    """
    v = v.rstrip()
    v = v.encode('ascii') # str -> bytes

    origlen = len(v)
    v = v.lstrip(alphabet[0:1])
    newlen = len(v)

    acc = b58decode_int(v, alphabet=alphabet)

    result = []
    while acc > 0:
    acc, mod = divmod(acc, 256)
    result.append(mod)

    return b'\0' * (origlen - newlen) + bytes(reversed(result))


    """Reference implementation for Bech32 and segwit addresses."""
    # https://raw.githubusercontent.com/sipa/bech32/master/ref/python/segwit_addr.py
    CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"


    def bech32_polymod(values):
    """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):
    """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, data):
    """Verify a checksum given HRP and converted data characters."""
    return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1


    def bech32_decode(bech):
    """Validate a Bech32 string, and determine HRP and data."""
    if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
    (bech.lower() != bech and bech.upper() != bech)):
    return None, None
    bech = bech.lower()
    pos = bech.rfind('1')
    if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
    return None, None
    if not all(x in CHARSET for x in bech[pos + 1:]):
    return None, None
    hrp = bech[:pos]
    data = [CHARSET.find(x) for x in bech[pos + 1:]]
    if not bech32_verify_checksum(hrp, data):
    return None, None
    return hrp, data[:-6]


    def convertbits(data, frombits, tobits, pad=True):
    """General power-of-2 base conversion."""
    acc = 0
    bits = 0
    ret = []
    maxv = (1 << tobits) - 1
    max_acc = (1 << (frombits + tobits - 1)) - 1
    for value in data:
    if value < 0 or (value >> frombits):
    return None
    acc = ((acc << frombits) | value) & max_acc
    bits += frombits
    while bits >= tobits:
    bits -= tobits
    ret.append((acc >> bits) & maxv)
    if pad:
    if bits:
    ret.append((acc << (tobits - bits)) & maxv)
    elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
    return None
    return ret


    def decode(hrp, addr):
    """Decode a segwit address."""
    hrpgot, data = bech32_decode(addr)
    if hrpgot != hrp:
    return None, None
    decoded = convertbits(data[1:], 5, 8, False)
    if decoded is None or len(decoded) < 2 or len(decoded) > 40:
    return None, None
    if data[0] > 16:
    return None, None
    if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
    return None, None
    return data[0], decoded


    def input_args():
    parser = argparse.ArgumentParser(description='Read csv file with btc address as first column'
    ' encodes it to hash160 and writes to stdout'
    )
    parser.add_argument(
    'csvin',
    metavar='csvfile',
    type=str,
    help='path to csv file with btc address in first column, - means stdin'
    )

    a = parser.parse_args()
    return a


    def process(csvfile):
    """ Converts p2pkh or p2wpkh address to hash160, i.e. ripemd(sha256(pubkey)) """
    if csvfile == '-':
    infile = sys.stdin
    else:
    infile = open(csvfile, 'r')
    for row in infile:
    first_column = row.split(',')[0].strip()
    if len(first_column) == 0:
    print('')
    elif first_column.startswith('1') and 26 <= len(first_column) <= 35: # P2PKH
    ripemd_bin = b58decode(first_column)[1:-4]
    ripemd_encoded = binascii.hexlify(ripemd_bin)
    print(ripemd_encoded.decode())
    elif first_column.startswith('bc1') and len(first_column) == 42: # P2WPKH
    _, script_int = decode('bc', first_column.lower())
    ripemd_encoded = binascii.hexlify(bytearray(script_int))
    print(ripemd_encoded.decode())
    else:
    print("btc address not supported")
    if infile is not sys.stdin:
    infile.close()


    if __name__ == '__main__':
    args = input_args()
    process(args.csvin)