def test_wif(self): # https://en.bitcoin.it/wiki/Wallet_import_format prvkey = 0xC28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D uncompressedKey = b'\x80' + prvkey.to_bytes(32, byteorder='big') uncompressedWIF = b'5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ' wif = b58encode_check(uncompressedKey) self.assertEqual(wif, uncompressedWIF) key = b58decode_check(uncompressedWIF) self.assertEqual(key, uncompressedKey) compressedKey = b'\x80' + prvkey.to_bytes(32, byteorder='big') + b'\x01' compressedWIF = b'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617' wif = b58encode_check(compressedKey) self.assertEqual(wif, compressedWIF) key = b58decode_check(compressedWIF) self.assertEqual(key, compressedKey) # string compressedWIF = 'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617' key = b58decode_check(compressedWIF) self.assertEqual(key, compressedKey) # string with leading space compressedWIF = ' KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617' b58decode_check(compressedWIF) self.assertEqual(key, compressedKey) # string with trailing space compressedWIF = 'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617 ' b58decode_check(compressedWIF) self.assertEqual(key, compressedKey)
def wif_from_prvkey(prvkey: int, compressed: bool) -> bytes: """private key to Wallet Import Format""" payload = b'\x80' + int2octets(prvkey, ec.psize) if compressed: payload += b'\x01' return b58encode_check(payload)
def bip32_crack(parent_xpub: bytes, child_xprv: bytes) -> bytes: parent_xpub = b58decode_check(parent_xpub, 78) assert parent_xpub[45] in (2, 3), "extended parent key is not a public one" child_xprv = b58decode_check(child_xprv, 78) assert child_xprv[45] == 0, "extended child key is not a private one" # check depth assert child_xprv[4] == parent_xpub[4]+1, "wrong child/parent depth relation" # check fingerprint Parent_bytes = parent_xpub[45: ] assert child_xprv[ 5: 9] == h160(Parent_bytes)[:4], "not a child for the provided parent" # check normal derivation child_index = child_xprv[ 9:13] assert child_index[0] < 0x80, "hardened derivation" parent_xprv = child_xprv[ : 4] # version parent_xprv += parent_xpub[ 4: 5] # depth parent_xprv += parent_xpub[ 5: 9] # parent pubkey fingerprint parent_xprv += parent_xpub[ 9:13] # child index parent_chain_code = parent_xpub[13:45] parent_xprv += parent_chain_code # chain code h = HMAC(parent_chain_code, Parent_bytes + child_index, sha512).digest() offset = int.from_bytes(h[:32], 'big') child = int.from_bytes(child_xprv[46:], 'big') parent = (child - offset) % ec.n parent_bytes = b'\x00' + parent.to_bytes(32, 'big') parent_xprv += parent_bytes # private key return b58encode_check(parent_xprv)
def address_from_pubkey(Q: Point, compressed: bool, version: bytes = b'\x00') -> bytes: """Public key to (bytes) address""" pubkey = point2octets(ec, Q, compressed) # FIXME: this is mainnet only vh160 = version + h160(pubkey) return b58encode_check(vh160)
def test_b58_exceptions(self): # int is not hex-string or bytes self.assertRaises(TypeError, b58encode_check, 3) encoded = b58encode_check(b"test") # unexpected decoded length wrong_length = len(encoded) - 1 self.assertRaises(ValueError, b58decode_check, encoded, wrong_length) # checksum is invalid invalidChecksum = encoded[:-4] + b'1111' self.assertRaises(ValueError, b58decode_check, invalidChecksum, 4)
def test_bip32_exceptions(self): mprv = b'xppp9s21ZrQH143K2oxHiQ5f7D7WYgXD9h6HAXDBuMoozDGGiYHWsq7TLBj2yvGuHTLSPCaFmUyN1v3fJRiY2A4YuNSrqQMPVLZKt76goL6LP7L' self.assertRaises(ValueError, bip32_ckd, mprv, 'invalid index') self.assertRaises(ValueError, bip32_ckd, mprv, 0x80000000) self.assertRaises(ValueError, bip32_ckd, mprv, "800000") self.assertRaises(ValueError, bip32_derive, mprv, '/1') self.assertRaises(TypeError, bip32_derive, mprv, 1) mprv = b'xprv9s21ZrQH143K2oxHiQ5f7D7WYgXD9h6HAXDBuMoozDGGiYHWsq7TLBj2yvGuHTLSPCaFmUyN1v3fJRiY2A4YuNSrqQMPVLZKt76goL6LP7L' self.assertRaises(ValueError, bip32_child_index, mprv) xkey = b'\x04\x88\xAD\xE5' # invalid version xkey += b'\x00' * 74 xkey = b58encode_check(xkey) self.assertRaises(ValueError, bip32_ckd, xkey, 0x80000000)
def bip32_master_prvkey_from_seed(bip32_seed: Union[str, bytes], version: bytes) -> bytes: """derive the master extended private key from the seed""" if type(bip32_seed) == str: # hex string bip32_seed = bytes.fromhex(bip32_seed) assert version in PRIVATE, "wrong version, master key must be private" # serialization data xmprv = version # version xmprv += b'\x00' # depth xmprv += b'\x00\x00\x00\x00' # parent pubkey fingerprint xmprv += b'\x00\x00\x00\x00' # child index # actual extended key (key + chain code) derivation hashValue = HMAC(b"Bitcoin seed", bip32_seed, sha512).digest() mprv = int_from_Scalar(ec, hashValue[:32]) xmprv += hashValue[32:] # chain code xmprv += b'\x00' + mprv.to_bytes(32, 'big') # private key return b58encode_check(xmprv)
def bip32_mprv_from_seed(seed: octets, version: octets) -> bytes: """derive the master extended private key from the seed""" if isinstance(version, str): # hex string version = bytes.fromhex(version) assert version in PRIVATE, "wrong version, master key must be private" # serialization data xmprv = version # version xmprv += b'\x00' # depth xmprv += b'\x00\x00\x00\x00' # parent pubkey fingerprint xmprv += b'\x00\x00\x00\x00' # child index # actual extended key (key + chain code) derivation if isinstance(seed, str): # hex string seed = bytes.fromhex(seed) hd = HMAC(b"Bitcoin seed", seed, sha512).digest() mprv = octets2int(hd[:32]) xmprv += hd[32:] # chain code xmprv += b'\x00' + mprv.to_bytes(32, 'big') # private key return b58encode_check(xmprv)
def bip32_xpub_from_xprv(xprv: bytes) -> bytes: """Neutered Derivation (ND) Computation of the extended public key corresponding to an extended private key (“neutered” as it removes the ability to sign transactions) """ xprv = b58decode_check(xprv, 78) assert xprv[45] == 0, "extended key is not a private one" i = PRIVATE.index(xprv[:4]) # serialization data xpub = PUBLIC[i] # version # unchanged serialization data xpub += xprv[4:5] # depth xpub += xprv[5:9] # parent pubkey fingerprint xpub += xprv[9:13] # child index xpub += xprv[13:45] # chain code p = xprv[46:] P = pointMultiply(ec, p, ec.G) xpub += bytes_from_Point(ec, P, True) # public key return b58encode_check(xpub)
def bip32_ckd(xparentkey: octets, index: Union[octets, int]) -> bytes: """Child Key Derivation (CDK) Key derivation is normal if the extended parent key is public or child_index is less than 0x80000000. Key derivation is hardened if the extended parent key is private and child_index is not less than 0x80000000. """ if isinstance(index, int): index = index.to_bytes(4, 'big') elif isinstance(index, str): # hex string index = bytes.fromhex(index) if len(index) != 4: raise ValueError(f"a 4 bytes int is required, not {len(index)}") xparent = b58decode_check(xparentkey, 78) version = xparent[:4] # serialization data xkey = version # version xkey += (xparent[4] + 1).to_bytes(1, 'big') # (increased) depth if (version in PUBLIC): if xparent[45] not in (2, 3): # not a compressed public key raise ValueError("version/key mismatch in extended parent key") Parent_bytes = xparent[45:] Parent = octets2point(ec, Parent_bytes) xkey += h160(Parent_bytes)[:4] # parent pubkey fingerprint if index[0] >= 0x80: raise ValueError("no private/hardened derivation from pubkey") xkey += index # child index parent_chain_code = xparent[13:45] # normal derivation # actual extended key (key + chain code) derivation h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest() offset = int.from_bytes(h[:32], 'big') Offset = pointMult(ec, offset, ec.G) Child = ec.add(Parent, Offset) Child_bytes = point2octets(ec, Child, True) xkey += h[32:] # chain code xkey += Child_bytes # public key elif (version in PRIVATE): if xparent[45] != 0: # not a private key raise ValueError("version/key mismatch in extended parent key") parent = int.from_bytes(xparent[46:], 'big') Parent = pointMult(ec, parent, ec.G) Parent_bytes = point2octets(ec, Parent, True) xkey += h160(Parent_bytes)[:4] # parent pubkey fingerprint xkey += index # child index # actual extended key (key + chain code) derivation parent_chain_code = xparent[13:45] if (index[0] < 0x80): # normal derivation h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest() else: # hardened derivation h = HMAC(parent_chain_code, xparent[45:] + index, sha512).digest() offset = int.from_bytes(h[:32], 'big') child = (parent + offset) % ec.n child_bytes = b'\x00' + child.to_bytes(32, 'big') xkey += h[32:] # chain code xkey += child_bytes # private key else: raise ValueError("invalid extended key version") return b58encode_check(xkey)
def bip32_ckd(xparentkey: bytes, index: Union[bytes, int]) -> bytes: """Child Key Derivation (CDK) Key derivation is normal if the extended parent key is public or child_index is less than 0x80000000. Key derivation is hardened if the extended parent key is private and child_index is not less than 0x80000000. """ if isinstance(index, int): index = index.to_bytes(4, 'big') elif isinstance(index, bytes): assert len(index) == 4 else: raise TypeError("a 4 bytes int is required") xparent = b58decode_check(xparentkey, 78) version = xparent[:4] # serialization data xkey = version # version xkey += (xparent[4] + 1).to_bytes(1, 'big') # (increased) depth if (version in PUBLIC): assert xparent[45] in (2, 3), \ "version/key mismatch in extended parent key" Parent_bytes = xparent[45:] Parent = tuple_from_Point(ec, Parent_bytes) xkey += h160(Parent_bytes)[:4] # parent pubkey fingerprint assert index[0] < 0x80, \ "no private/hardened derivation from pubkey" xkey += index # child index parent_chain_code = xparent[13:45] ## normal derivation # actual extended key (key + chain code) derivation h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest() offset = int.from_bytes(h[:32], 'big') Offset = ec.pointMultiply(offset, ec.G) Child = ec.pointAdd(Parent, Offset) Child_bytes = bytes_from_Point(ec, Child, True) xkey += h[32:] # chain code xkey += Child_bytes # public key elif (version in PRIVATE): assert xparent[45] == 0, "version/key mismatch in extended parent key" parent = int.from_bytes(xparent[46:], 'big') Parent = ec.pointMultiply(parent, ec.G) Parent_bytes = bytes_from_Point(ec, Parent, True) xkey += h160(Parent_bytes)[:4] # parent pubkey fingerprint xkey += index # child index # actual extended key (key + chain code) derivation parent_chain_code = xparent[13:45] if (index[0] < 0x80): ## normal derivation h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest() else: ## hardened derivation h = HMAC(parent_chain_code, xparent[45:] + index, sha512).digest() offset = int.from_bytes(h[:32], 'big') child = (parent + offset) % ec.order child_bytes = b'\x00' + child.to_bytes(32, 'big') xkey += h[32:] # chain code xkey += child_bytes # private key else: raise ValueError("invalid extended key version") return b58encode_check(xkey)
def address_from_pubkey(pubkey: bytes, version: bytes = b'\x00') -> bytes: """Public key to (bytes) address""" # FIXME: this is mainnet only vh160 = version + h160(pubkey) return b58encode_check(vh160)
def wif_from_prvkey(prvkey: PrivateKey, compressed: bool = True) -> bytes: """private key to Wallet Import Format""" payload = b'\x80' + bytes_from_Scalar(ec, prvkey) if compressed: payload += b'\x01' return b58encode_check(payload)
def address_from_pubkey_bytes(inp, version=b'\x00'): vh160 = version + hash160(inp) return b58encode_check(vh160)
print( "\n*** [5] First 4 bytes of the second SHA-256 hash used as address checksum:" ) print(h2[:4].hex()) print("\n*** [6] checksum added at the end of extended key:") addr = ExtKey + h2[:4] print(addr.hex()) print("\n*** [7] Base58 encoding") wif = b58encode(addr) print(wif) assert wif == b'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617', "failure" assert b58encode_check( ExtKey ) == b'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617', "failure" print("\n****** WIF to private key ******") print("\n*** [1] Base58 WIF") print(wif) compressed = len(wif) - 51 print("compressed" if (compressed == 1) else "uncompressed") print("\n*** [2] Base58 decoding") addr = b58decode(wif) print(addr.hex()) print("\n*** [3] Extended key (checksum verified)") ExtKey, checksum = addr[:-4], addr[-4:]
print( "\n*** [5] First 4 bytes of the second SHA-256 hash used as address checksum:" ) print(h2[:4].hex()) print("\n*** [6] checksum added at the end of extended key:") addr = ExtKey + h2[:4] print(addr.hex()) print("\n*** [7] Base58 encoding") wif = b58encode(addr) print(wif) assert wif == b'5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ', "failure" assert b58encode_check( ExtKey ) == b'5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ', "failure" print("\n****** WIF to private key ******") print("\n*** [1] Base58 WIF") print(wif) compressed = len(wif) - 51 print("compressed" if (compressed == 1) else "uncompressed") print("\n*** [2] Base58 decoding") addr = b58decode(wif) print(addr.hex()) print("\n*** [3] Extended key (checksum verified)") ExtKey, checksum = addr[:-4], addr[-4:]
child_number = b'\x00\x00\x00\x00' # the fingerprint of the parent's public key (0x00000000 if master key) fingerprint = b'\x00\x00\x00\x00' idf = depth + fingerprint + child_number # master private key, master public key, chain code hashValue = HMAC(b"Bitcoin seed", seed.to_bytes(seed_bytes, byteorder='big'), sha512).digest() p_bytes = hashValue[:32] p = int(p_bytes.hex(), 16) % ec.order p_bytes = b'\x00' + p.to_bytes(32, byteorder='big') P = ec.pointMultiply(p, ec.G) P_bytes = bytes_from_Point(ec, P, True) chain_code = hashValue[32:] #extended keys ext_prv = b58encode_check(xprv + idf + chain_code + p_bytes) print("\nm") print(ext_prv) ext_pub = b58encode_check(xpub + idf + chain_code + P_bytes) print("M") print(ext_pub) assert ext_prv == b"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", "failure" assert ext_pub == b"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", "failure" # ==first (0) hardened child== depth = b'\x01' child_number = 0 + 0x80000000 #hardened child_number = child_number.to_bytes(4, byteorder='big') fingerprint = h160(P_bytes)[:4] idf = depth + fingerprint + child_number
def test_mainnet(self): # bitcoin core derivation style mprv = b'xprv9s21ZrQH143K2ZP8tyNiUtgoezZosUkw9hhir2JFzDhcUWKz8qFYk3cxdgSFoCMzt8E2Ubi1nXw71TLhwgCfzqFHfM5Snv4zboSebePRmLS' # m/0'/0'/463' addr1 = b'1DyfBWxhVLmrJ7keyiHeMbt7N3UdeGU4G5' indexes = [0x80000000, 0x80000000, 0x80000000 + 463] addr = address_from_xpub( bip32_xpub_from_xprv(bip32_derive(mprv, indexes))) self.assertEqual(addr, addr1) path = "m/0'/0'/463'" addr = address_from_xpub(bip32_xpub_from_xprv(bip32_derive(mprv, path))) self.assertEqual(addr, addr1) # m/0'/0'/267' addr2 = b'11x2mn59Qy43DjisZWQGRResjyQmgthki' indexes = [0x80000000, 0x80000000, 0x80000000 + 267] addr = address_from_xpub( bip32_xpub_from_xprv(bip32_derive(mprv, indexes))) self.assertEqual(addr, addr2) path = "m/0'/0'/267'" addr = address_from_xpub(bip32_xpub_from_xprv(bip32_derive(mprv, path))) self.assertEqual(addr, addr2) xkey_version = PRIVATE[0] seed = "bfc4cbaad0ff131aa97fa30a48d09ae7df914bcc083af1e07793cd0a7c61a03f65d622848209ad3366a419f4718a80ec9037df107d8d12c19b83202de00a40ad" seed = bytes.fromhex(seed) xprv = bip32_mprv_from_seed(seed, xkey_version) xpub = b'xpub661MyMwAqRbcFMYjmw8C6dJV97a4oLss6hb3v9wTQn2X48msQB61RCaLGtNhzgPCWPaJu7SvuB9EBSFCL43kTaFJC3owdaMka85uS154cEh' self.assertEqual(bip32_xpub_from_xprv(xprv), xpub) ind = [0, 0] addr = address_from_xpub(bip32_xpub_from_xprv(bip32_derive(xprv, ind))) self.assertEqual(addr, b'1FcfDbWwGs1PmyhMVpCAhoTfMnmSuptH6g') ind = [0, 1] addr = address_from_xpub(bip32_xpub_from_xprv(bip32_derive(xprv, ind))) self.assertEqual(addr, b'1K5GjYkZnPFvMDTGaQHTrVnd8wjmrtfR5x') ind = [0, 2] addr = address_from_xpub(bip32_xpub_from_xprv(bip32_derive(xprv, ind))) self.assertEqual(addr, b'1PQYX2uN7NYFd7Hq22ECMzfDcKhtrHmkfi') ind = [1, 0] addr = address_from_xpub(bip32_xpub_from_xprv(bip32_derive(xprv, ind))) self.assertEqual(addr, b'1BvSYpojWoWUeaMLnzbkK55v42DbizCoyq') ind = [1, 1] addr = address_from_xpub(bip32_xpub_from_xprv(bip32_derive(xprv, ind))) self.assertEqual(addr, b'1NXB59hF4QzYpFrB7o6usLBjbk2D3ZqxAL') ind = [1, 2] addr = address_from_xpub(bip32_xpub_from_xprv(bip32_derive(xprv, ind))) self.assertEqual(addr, b'16NLYkKtvYhW1Jp86tbocku3gxWcvitY1w') # version/key mismatch in extended parent key bmprv = b58decode_check(mprv) bad_mprv = b58encode_check(bmprv[0:45] + b'\x01' + bmprv[46:]) self.assertRaises(ValueError, bip32_ckd, bad_mprv, 1) #bip32_ckd(bad_mprv, 1) # version/key mismatch in extended parent key mpub = bip32_xpub_from_xprv(mprv) bmpub = b58decode_check(mpub) bad_mpub = b58encode_check(bmpub[0:45] + b'\x00' + bmpub[46:]) self.assertRaises(ValueError, bip32_ckd, bad_mpub, 1) #bip32_ckd(bad_mpub, 1) # no private/hardened derivation from pubkey self.assertRaises(ValueError, bip32_ckd, mpub, 0x80000000)