from jmbitcoin.secp256k1_main import * import hmac import hashlib import struct from bitcointx.core import Hash160, Hash from bitcointx import base58 # Below code ASSUMES binary inputs and compressed pubkeys MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' TESTNET_PRIVATE = b'\x04\x35\x83\x94' TESTNET_PUBLIC = b'\x04\x35\x87\xCF' SIGNET_PRIVATE = b'\x04\x35\x83\x94' SIGNET_PUBLIC = b'\x04\x35\x87\xCF' PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE, SIGNET_PRIVATE] PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC, SIGNET_PUBLIC] privtopub = privkey_to_pubkey # BIP32 child key derivation def raw_bip32_ckd(rawtuple, i): vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple i = int(i) if vbytes in PRIVATE: priv = key pub = privtopub(key) else: pub = key if i >= 2**31: if vbytes in PUBLIC: raise Exception("Can't do private derivation on public key!") I = hmac.new(chaincode, b'\x00' + priv[:32] + struct.pack(b'>L', i), hashlib.sha512).digest() else: I = hmac.new(chaincode, pub + struct.pack(b'>L', i), hashlib.sha512).digest() if vbytes in PRIVATE: newkey = add_privkeys(I[:32] + b'\x01', priv) fingerprint = Hash160(privtopub(key))[:4] if vbytes in PUBLIC: newkey = add_pubkeys([privtopub(I[:32] + b'\x01'), key]) fingerprint = Hash160(key)[:4] return (vbytes, depth + 1, fingerprint, i, I[32:], newkey) def bip32_serialize(rawtuple): vbytes, depth, fingerprint, i, chaincode, key = rawtuple if isinstance(i, int): i = struct.pack(b'>L', i) chaincode = chaincode keydata = b'\x00' + key[:-1] if vbytes in PRIVATE else key bindata = vbytes + struct.pack(b'B',depth % 256) + fingerprint + i + chaincode + keydata return base58.encode(bindata + Hash(bindata)[:4]) def bip32_deserialize(data): dbin = base58.decode(data) if Hash(dbin[:-4])[:4] != dbin[-4:]: raise Exception("Invalid checksum") vbytes = dbin[0:4] if vbytes not in PRIVATE and vbytes not in PUBLIC: raise Exception("Invalid vbytes {}".format(vbytes)) depth = dbin[4] fingerprint = dbin[5:9] child_num = struct.unpack(b'>L',dbin[9:13])[0] if depth == 0 and (fingerprint != b'\x00'*4 or child_num != 0): raise Exception("Invalid master key, depth = {}, fingerprint = {}, child_num = {}".format(depth, fingerprint, child_num)) chaincode = dbin[13:45] key = dbin[46:78] + b'\x01' if vbytes in PRIVATE else dbin[45:78] if vbytes in PUBLIC and not is_valid_pubkey(key): raise Exception("Invalid public key") if vbytes in PRIVATE: if dbin[45] != 0: raise Exception("Invalid private key") # check for validity, this will raise exception privkey_to_pubkey(key) return (vbytes, depth, fingerprint, child_num, chaincode, key) def raw_bip32_privtopub(rawtuple): vbytes, depth, fingerprint, i, chaincode, key = rawtuple if vbytes in PUBLIC: return rawtuple newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key)) def bip32_privtopub(data): return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data))) def bip32_ckd(data, i): return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i)) def bip32_master_key(seed, vbytes=MAINNET_PRIVATE): I = hmac.new("Bitcoin seed".encode("utf-8"), seed, hashlib.sha512).digest() return bip32_serialize((vbytes, 0, b'\x00' * 4, 0, I[32:], I[:32] + b'\x01' )) def bip32_extract_key(data): return bip32_deserialize(data)[-1] def bip32_descend(*args): if len(args) == 2: key, path = args else: key, path = args[0], map(int, args[1:]) for p in path: key = bip32_ckd(key, p) return bip32_extract_key(key)