134 lines
6.4 KiB
Python
134 lines
6.4 KiB
Python
from struct import unpack
|
|
from .functions import *
|
|
|
|
|
|
# Hierarchical Deterministic Wallets (HD Wallets)
|
|
# BIP-44 support
|
|
|
|
class Wallet():
|
|
"""
|
|
The class for creating wallet object.
|
|
|
|
:param init_vector: (optional) initialization vector should be mnemonic phrase, extended public key,
|
|
extended private key, by default None (generate new wallet).
|
|
:param compressed: (optional) if set to True private key corresponding compressed public key,
|
|
by default set to True. Recommended use only compressed public key.
|
|
:param testnet: (optional) if set to True mean that this private key for testnet Bitcoin network.
|
|
|
|
"""
|
|
def __init__(self, init_vector=None, passphrase="", language='english', word_list_dir=None, word_list=None):
|
|
if init_vector is None:
|
|
e = generate_entropy()
|
|
m = entropy_to_mnemonic(e)
|
|
self.mnemonic = m
|
|
init_vector = create_master_xprivate_key(mnemonic_to_seed(m), base58=False)
|
|
else:
|
|
if isinstance(init_vector, str):
|
|
if is_xprivate_key_valid(init_vector):
|
|
if len(init_vector) == 156:
|
|
init_vector = bytes.fromhex(init_vector)
|
|
else:
|
|
init_vector = decode_base58_with_checksum(init_vector)
|
|
elif is_xpublic_key_valid(init_vector):
|
|
if len(init_vector) == 156:
|
|
init_vector = bytes.fromhex(init_vector)
|
|
else:
|
|
init_vector = decode_base58_with_checksum(init_vector)
|
|
else:
|
|
try:
|
|
self.mnemonic = init_vector
|
|
self.passphrase = passphrase
|
|
init_vector = create_master_xprivate_key(mnemonic_to_seed(init_vector,
|
|
passphrase=passphrase),
|
|
base58=False)
|
|
except Exception as err:
|
|
raise ValueError("invalid initial vector %s" % err)
|
|
if not isinstance(init_vector, bytes):
|
|
raise ValueError("invalid initial vector")
|
|
self.accounts = dict()
|
|
self.extended_key = self.deserialize_xkey(init_vector)
|
|
|
|
def deserialize_xkey(self, xkey):
|
|
if isinstance(xkey, str):
|
|
xkey = decode_base58_with_checksum(xkey)
|
|
extended_key = dict()
|
|
extended_key['version'] = xkey[:4].hex()
|
|
extended_key['depth'] = unpack('B', xkey[4:5])[0]
|
|
extended_key['fingerprint'] = xkey[5:9].hex()
|
|
extended_key['child'] = unpack('I', xkey[9:13])[0]
|
|
extended_key['chain_code'] = xkey[13:45].hex()
|
|
info = ["Derived"] if extended_key['depth'] != 0 else ["Master"]
|
|
if xkey[:4] in [MAINNET_XPRIVATE_KEY_PREFIX, MAINNET_XPUBLIC_KEY_PREFIX]:
|
|
info.append("Mainnet")
|
|
extended_key["testnet"] = False
|
|
else:
|
|
info.append("Testnet")
|
|
extended_key["testnet"] = True
|
|
info.append("Extended")
|
|
if xkey[:4] in [MAINNET_XPRIVATE_KEY_PREFIX, TESTNET_XPRIVATE_KEY_PREFIX]:
|
|
testnet = False if xkey[:4] == MAINNET_XPRIVATE_KEY_PREFIX else True
|
|
extended_key['private_key'] = private_key_to_wif(xkey[46:78], testnet=testnet)
|
|
info.append("Private")
|
|
extended_key["type"] = "private"
|
|
else:
|
|
info.append("Public")
|
|
extended_key['public_key'] = xkey[45:78].hex()
|
|
extended_key["type"] = "public"
|
|
|
|
info.append("Key")
|
|
extended_key["info"] = " ".join(info)
|
|
extended_key["key"] = encode_base58_with_checksum(xkey)
|
|
return extended_key
|
|
|
|
def create_account(self,name, path):
|
|
self.accounts[name] = {"extended_key": self.deserialize_xkey(derive_xkey(self.extended_key["key"],
|
|
*path)),
|
|
"path": path}
|
|
|
|
def create_bip44_account(self, account=0):
|
|
if self.extended_key["depth"] != 0:
|
|
raise Exception("Create bip44 account only possible from Master private key")
|
|
if not isinstance(account, int):
|
|
raise ValueError("account should be integer")
|
|
self.create_account("%s_external" % account, [44|HARDENED_KEY, HARDENED_KEY, account, 0])
|
|
self.create_account("%s_internal" % account, [44|HARDENED_KEY, HARDENED_KEY, account, 1])
|
|
|
|
def get_bip44_address(self, i, chain="external", account_index=0, address_type="P2WPKH"):
|
|
if chain not in ("internal", "external"):
|
|
raise ValueError("chain should be inetrnal or external")
|
|
account_name = "%s_%s" % (account_index, chain)
|
|
if account_name not in self.accounts:
|
|
self.create_bip44_account(account=account_index)
|
|
return self.get_chain_address(i, account=account_name, address_type=address_type)
|
|
|
|
def get_chain_address(self, i, account=None, address_type="P2WPKH"):
|
|
if account is None:
|
|
xkey = self.extended_key["key"]
|
|
key_type = self.extended_key["type"]
|
|
testnet = self.extended_key["testnet"]
|
|
else:
|
|
xkey = self.accounts[account]["extended_key"]["key"]
|
|
key_type = self.accounts[account]["extended_key"]["type"]
|
|
testnet = self.accounts[account]["extended_key"]["testnet"]
|
|
xkey = derive_xkey(xkey, i)
|
|
if key_type == "public":
|
|
address = public_key_to_address(public_from_xpublic_key(xkey), testnet=testnet)
|
|
r = {"address": address,
|
|
"public_key": public_from_xpublic_key(xkey)}
|
|
elif key_type == "private":
|
|
private_key = private_from_xprivate_key(xkey)
|
|
if address_type == "P2WPKH":
|
|
address = public_key_to_address(private_to_public_key(private_key), testnet=testnet)
|
|
elif address_type == "P2SH_P2WPKH":
|
|
address = public_key_to_address(private_to_public_key(private_key), p2sh_p2wpkh=True,
|
|
testnet=testnet)
|
|
elif address_type == "P2PKH":
|
|
address = public_key_to_address(private_to_public_key(private_key), witness_version=None,
|
|
testnet=testnet)
|
|
r = {"address": address,
|
|
"public_key": private_to_public_key(private_key),
|
|
"private_key": private_key}
|
|
return r
|
|
|
|
|