Created
March 7, 2021 14:09
-
-
Save 10gic/66be6ed2125551bf1870e20cb2570631 to your computer and use it in GitHub Desktop.
Converts p2pkh or p2wpkh address to hash160, i.e. ripemd(sha256(pubkey))
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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