class BasicKeychainTest(unittest.TestCase): def setUp(self): self.private_keychains = { "root": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", "0H": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", "0H/1": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", "0H/1/2H": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", "0H/1/2H/2": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", "0H/1/2H/2/1000000000": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" } self.public_keychains = { "root": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", "0H": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", "0H/1": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", "0H/1/2H": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", "0H/1/2H/2": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", "0H/1/2H/2/1000000000": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" } self.root_private_keychain = PrivateKeychain(self.private_keychains["root"]) def tearDown(self): pass def test_root_private_to_public(self): public_keychain = self.root_private_keychain.public_keychain() self.assertEqual(str(public_keychain), str(self.public_keychains["root"])) def test_hardened_child_0H(self): private_keychain = self.root_private_keychain.hardened_child(0) self.assertEqual(str(private_keychain), str(self.private_keychains["0H"])) self.assertEqual(str(private_keychain.public_keychain()), str(self.public_keychains["0H"])) def test_unhardened_child_0H_1(self): private_keychain = self.root_private_keychain.hardened_child(0).child(1) self.assertEqual(str(private_keychain), str(self.private_keychains["0H/1"])) public_keychain = private_keychain.public_keychain() self.assertEqual(str(public_keychain), str(self.public_keychains["0H/1"])) public_keychain_2 = self.root_private_keychain.hardened_child(0).public_keychain().child(1) self.assertEqual(str(public_keychain), str(public_keychain_2)) def test_5_step_derivation(self): private_keychain = self.root_private_keychain.hardened_child(0).child(1).hardened_child(2).child(2).child(1000000000) self.assertEqual(str(private_keychain), str(self.private_keychains["0H/1/2H/2/1000000000"])) public_keychain = private_keychain.public_keychain() self.assertEqual(str(public_keychain), str(self.public_keychains["0H/1/2H/2/1000000000"])) def test_private_key(self): root_private_key = self.root_private_keychain.private_key() self.assertTrue(len(root_private_key) == 66) def test_address(self): address = self.root_private_keychain.public_keychain().address() self.assertTrue(address[0] == '1')
class HDWallet(object): """ Initialize a hierarchical deterministic wallet with hex_privkey and get child addresses and private keys """ def __init__(self, hex_privkey=None, config_path=CONFIG_PATH): """ If @hex_privkey is given, use that to derive keychain otherwise, use a new random seed """ if hex_privkey: self.priv_keychain = PrivateKeychain.from_private_key(hex_privkey) else: log.debug("No privatekey given, starting new wallet") self.priv_keychain = PrivateKeychain() self.master_address = self.get_master_address() self.child_addresses = None self.config_path = config_path def get_master_privkey(self): return self.priv_keychain.private_key() def get_child_privkey(self, index=0): """ @index is the child index Returns: child privkey for given @index """ child = self.priv_keychain.hardened_child(index) return child.private_key() def get_master_address(self): hex_privkey = self.get_master_privkey() return get_address_from_privkey(hex_privkey) def get_child_address(self, index=0): """ @index is the child index Returns: child address for given @index """ if self.child_addresses is not None: return self.child_addresses[index] hex_privkey = self.get_child_privkey(index) return get_address_from_privkey(hex_privkey) def get_child_keypairs(self, count=1, offset=0, include_privkey=False): """ Returns (privkey, address) keypairs Returns: returns child keypairs @include_privkey: toggles between option to return privkeys along with addresses or not """ keypairs = [] for index in range(offset, offset+count): address = self.get_child_address(index) if include_privkey: hex_privkey = self.get_child_privkey(index) keypairs.append((address, hex_privkey)) else: keypairs.append(address) return keypairs def get_next_keypair(self, count=1, config_path=None): """ Get next payment address that is ready to use Returns (payment_address, hex_privkey) """ if config_path is None: config_path = self.config_path addresses = self.get_child_keypairs(count=count) index = 0 for payment_address in addresses: # find an address that can be used for payment if not is_address_usable(payment_address, config_path=config_path): log.debug("Pending tx on address: %s" % payment_address) balance = get_balance( payment_address, config_path=config_path ) if balance < MINIMUM_BALANCE: log.debug("Underfunded address: %s" % payment_address) else: return payment_address, self.get_child_privkey(index) index += 1 log.debug("No valid address available.") return None, None def get_privkey_from_address(self, target_address, count=1): """ Given a child address, return priv key of that address """ addresses = self.get_child_keypairs(count=count) index = 0 for address in addresses: if address == target_address: return self.get_child_privkey(index) index += 1 return None
class HDWallet(object): """ Initialize a hierarchical deterministic wallet with hex_privkey and get child addresses and private keys """ def __init__(self, hex_privkey=None, enable_cache=False): """ If @hex_privkey is given, use that to derive keychain otherwise, use a new random seed """ if hex_privkey: self.priv_keychain = PrivateKeychain.from_private_key(hex_privkey) else: #log.debug("No privatekey given, starting new wallet") self.priv_keychain = PrivateKeychain() self.master_address = self.get_master_address() self.child_addresses = None if enable_cache: cache = self.get_cache() if cache is not None: if cache['master_address'] == self.master_address: self.child_addresses = cache['child_addresses'] else: log.debug("Wallet already exists with master address: %s" % cache['master_address']) else: #log.debug("Creating cache of HD wallet addresses ...") self.create_addresses_cache() def create_addresses_cache(self, count=DEFAULT_CHILD_ADDRESSES): if self.get_cache() is not None: return True child_addresses = [] for index in range(0, count): hex_privkey = self.get_child_privkey(index) address = self.get_child_address(index) child_addresses.append(address) if not os.path.exists(LOCAL_DIR): os.makedirs(LOCAL_DIR) with open(CACHE_FILE_FULLPATH, 'w') as cache_file: data = {'child_addresses': child_addresses} data['master_address'] = self.get_master_address() cache_file.write(json.dumps(data)) return True def get_cache(self): if not os.path.isfile(CACHE_FILE_FULLPATH): return None try: with open(CACHE_FILE_FULLPATH, 'r') as cache_file: data = json.loads(cache_file.read()) return data except: return None def get_master_privkey(self): return self.priv_keychain.private_key() def get_child_privkey(self, index=0): """ @index is the child index Returns: child privkey for given @index """ child = self.priv_keychain.hardened_child(index) return child.private_key() def get_master_address(self): hex_privkey = self.get_master_privkey() return get_address_from_privkey(hex_privkey) def get_child_address(self, index=0): """ @index is the child index Returns: child address for given @index """ if self.child_addresses is not None: return self.child_addresses[index] hex_privkey = self.get_child_privkey(index) return get_address_from_privkey(hex_privkey) def get_child_keypairs(self, count=1, offset=0, include_privkey=False): """ Returns (privkey, address) keypairs Returns: returns child keypairs @include_privkey: toggles between option to return privkeys along with addresses or not """ keypairs = [] for index in range(offset, offset + count): address = self.get_child_address(index) if include_privkey: hex_privkey = self.get_child_privkey(index) keypairs.append((address, hex_privkey)) else: keypairs.append(address) return keypairs def get_next_keypair(self, count=DEFAULT_CHILD_ADDRESSES): """ Get next payment address that is ready to use Returns (payment_address, hex_privkey) """ addresses = self.get_child_keypairs(count=count) index = 0 for payment_address in addresses: # find an address that can be used for payment if dontuseAddress(payment_address): log.debug("Pending tx on address: %s" % payment_address) elif underfundedAddress(payment_address): log.debug("Underfunded address: %s" % payment_address) else: return payment_address, self.get_child_privkey(index) index += 1 log.debug("No valid address available.") return None, None def get_privkey_from_address(self, target_address, count=DEFAULT_CHILD_ADDRESSES): """ Given a child address, return priv key of that address """ addresses = self.get_child_keypairs(count=count) index = 0 for address in addresses: if address == target_address: return self.get_child_privkey(index) index += 1 return None
class HDWallet(object): """ Initialize a hierarchical deterministic wallet with hex_privkey and get child addresses and private keys """ def __init__(self, hex_privkey=None, enable_cache=False): """ If @hex_privkey is given, use that to derive keychain otherwise, use a new random seed """ if hex_privkey: self.priv_keychain = PrivateKeychain.from_private_key(hex_privkey) else: #log.debug("No privatekey given, starting new wallet") self.priv_keychain = PrivateKeychain() self.master_address = self.get_master_address() self.child_addresses = None if enable_cache: cache = self.get_cache() if cache is not None: if cache['master_address'] == self.master_address: self.child_addresses = cache['child_addresses'] else: log.debug("Wallet already exists with master address: %s" % cache['master_address']) else: #log.debug("Creating cache of HD wallet addresses ...") self.create_addresses_cache() def create_addresses_cache(self, count=DEFAULT_CHILD_ADDRESSES): if self.get_cache() is not None: return True child_addresses = [] for index in range(0, count): hex_privkey = self.get_child_privkey(index) address = self.get_child_address(index) child_addresses.append(address) if not os.path.exists(LOCAL_DIR): os.makedirs(LOCAL_DIR) with open(CACHE_FILE_FULLPATH, 'w') as cache_file: data = {'child_addresses': child_addresses} data['master_address'] = self.get_master_address() cache_file.write(json.dumps(data)) return True def get_cache(self): if not os.path.isfile(CACHE_FILE_FULLPATH): return None try: with open(CACHE_FILE_FULLPATH, 'r') as cache_file: data = json.loads(cache_file.read()) return data except: return None def get_master_privkey(self): return self.priv_keychain.private_key() def get_child_privkey(self, index=0): """ @index is the child index Returns: child privkey for given @index """ child = self.priv_keychain.hardened_child(index) return child.private_key() def get_master_address(self): hex_privkey = self.get_master_privkey() return get_address_from_privkey(hex_privkey) def get_child_address(self, index=0): """ @index is the child index Returns: child address for given @index """ if self.child_addresses is not None: return self.child_addresses[index] hex_privkey = self.get_child_privkey(index) return get_address_from_privkey(hex_privkey) def get_child_keypairs(self, count=1, offset=0, include_privkey=False): """ Returns (privkey, address) keypairs Returns: returns child keypairs @include_privkey: toggles between option to return privkeys along with addresses or not """ keypairs = [] for index in range(offset, offset+count): address = self.get_child_address(index) if include_privkey: hex_privkey = self.get_child_privkey(index) keypairs.append((address, hex_privkey)) else: keypairs.append(address) return keypairs def get_next_keypair(self, count=DEFAULT_CHILD_ADDRESSES): """ Get next payment address that is ready to use Returns (payment_address, hex_privkey) """ addresses = self.get_child_keypairs(count=count) index = 0 for payment_address in addresses: # find an address that can be used for payment if dontuseAddress(payment_address): log.debug("Pending tx on address: %s" % payment_address) elif underfundedAddress(payment_address): log.debug("Underfunded address: %s" % payment_address) else: return payment_address, self.get_child_privkey(index) index += 1 log.debug("No valid address available.") return None, None def get_privkey_from_address(self, target_address, count=DEFAULT_CHILD_ADDRESSES): """ Given a child address, return priv key of that address """ addresses = self.get_child_keypairs(count=count) index = 0 for address in addresses: if address == target_address: return self.get_child_privkey(index) index += 1 return None
class HDWallet(object): """ Initialize a hierarchical deterministic wallet with hex_privkey and get child addresses and private keys """ def __init__(self, hex_privkey=None): """ If @hex_privkey is given, use that to derive keychain otherwise, use a new random seed """ if hex_privkey: self.priv_keychain = PrivateKeychain.from_private_key(hex_privkey) else: log.debug("No privatekey given, starting new wallet") self.priv_keychain = PrivateKeychain() self.master_address = self.get_master_address() self.child_addresses = None def get_master_privkey(self): return self.priv_keychain.private_key() def get_child_privkey(self, index=0): """ @index is the child index Returns: child privkey for given @index """ child = self.priv_keychain.hardened_child(index) return child.private_key() def get_master_address(self): hex_privkey = self.get_master_privkey() return get_address_from_privkey(hex_privkey) def get_child_address(self, index=0): """ @index is the child index Returns: child address for given @index """ if self.child_addresses is not None: return self.child_addresses[index] hex_privkey = self.get_child_privkey(index) return get_address_from_privkey(hex_privkey) def get_child_keypairs(self, count=1, offset=0, include_privkey=False): """ Returns (privkey, address) keypairs Returns: returns child keypairs @include_privkey: toggles between option to return privkeys along with addresses or not """ keypairs = [] for index in range(offset, offset + count): address = self.get_child_address(index) if include_privkey: hex_privkey = self.get_child_privkey(index) keypairs.append((address, hex_privkey)) else: keypairs.append(address) return keypairs def get_privkey_from_address(self, target_address, count=1): """ Given a child address, return priv key of that address """ addresses = self.get_child_keypairs(count=count) index = 0 for address in addresses: if address == target_address: return self.get_child_privkey(index) index += 1 return None
class HDWallet(object): """ Initialize a hierarchical deterministic wallet with hex_privkey and get child addresses and private keys """ def __init__(self, hex_privkey=None): """ If @hex_privkey is given, use that to derive keychain otherwise, use a new random seed """ if hex_privkey: self.priv_keychain = PrivateKeychain.from_private_key(hex_privkey) else: self.priv_keychain = PrivateKeychain() def get_privkey(self, index=None): """ @index is the child index Returns: master/root privkey by default or child privkey for given @index """ if index is None: return self.priv_keychain.private_key() child = self.priv_keychain.hardened_child(index) return child.private_key() def get_address(self, index=None): """ @index is the child index Returns: master/root address by default or child address for given @index """ if index is None: hex_privkey = self.get_privkey() return get_address_from_privkey(hex_privkey) hex_privkey = self.get_privkey(index) return get_address_from_privkey(hex_privkey) def get_keypairs(self, count=None, include_privkey=False): """ Returns (privkey, address) keypairs Returns: master/root pair by default if count is given, then returns child keypairs @include_privkey: toggles between option to return privkeys along with addresses or not """ keypairs = [] if count is None: if include_privkey: keypairs.append((self.get_address(), self.get_privkey())) else: keypairs.append(self.get_address()) return keypairs for index in range(count): hex_privkey = self.get_privkey(index) address = self.get_address(index) if include_privkey: keypairs.append((address, hex_privkey)) else: keypairs.append(address) return keypairs def get_next_keypair(self, count=DEFAULT_CHILD_ADDRESSES): """ Get next payment address that is ready to use Returns (payment_address, hex_privkey) """ addresses = self.get_keypairs(count=count) index = 0 for payment_address in addresses: # find an address that can be used for payment if dontuseAddress(payment_address): log.debug("Pending tx on address: %s" % payment_address) elif underfundedAddress(payment_address): log.debug("Underfunded address: %s" % payment_address) else: return payment_address, self.get_privkey(index) index += 1 log.debug("No valid address available.") return None, None