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.
Converts p2pkh or p2wpkh address to hash160, i.e. ripemd(sha256(pubkey))
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment