├── .gitignore ├── CHANGES.txt ├── LICENCE.txt ├── MANIFEST.in ├── README.rst ├── py_hd_wallet ├── __init__.py ├── mw.py ├── ripple.py └── wallet.py ├── setup.py └── test ├── __init__.py └── test_py_hdwallet.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | dist 4 | *.pyc 5 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nthtson/py_hd_wallet/783293771e9ee98f806652f352e8ce3458841c68/CHANGES.txt -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Nguyen Tran Ho Thanh Son 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nthtson/py_hd_wallet/783293771e9ee98f806652f352e8ce3458841c68/MANIFEST.in -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | py_hd_wallet 2 | =========== 3 | 4 | ** A multi crypto-currencies HD wallet implemented by Python. ** 5 | 6 | BIP32 (or HD for "hierarchical deterministic") wallets allow you to create 7 | child wallets which can only generate public keys and don't expose a 8 | private key to an insecure server. 9 | The implementation is based on the proposal BIP32, BIP39 and is currently in audit mode. 10 | Please do not use in production yet. Testing welcome. 11 | 12 | This library simplify the process of creating new wallets for the 13 | BTC, BTG, BCH, ETH, LTC, DASH, DOGE, ZEC, XRP, ZCASH and XLM. 14 | 15 | Most of the code here is copied from: 16 | 17 | - Ran Aroussi's `pywallet ` 18 | - Devrandom's `pymultiwallet ` 19 | - Reverbel's `seed-phrases-for-stellar ` 20 | 21 | I simply added support for a few more crypto-currencies. 22 | 23 | -------------- 24 | 25 | Installation 26 | ------------- 27 | 28 | Install via PiP: 29 | 30 | .. code:: bash 31 | 32 | $ sudo pip install py_hd_wallet 33 | 34 | 35 | Example code: 36 | ============= 37 | 38 | Create HD Wallet 39 | ---------------- 40 | 41 | The following code creates a new Bitcoin HD wallet: 42 | 43 | .. code:: python 44 | 45 | # create_btc_wallet.py 46 | 47 | from py_hd_wallet import wallet 48 | 49 | # generate 12 word mnemonic seed 50 | seed = wallet.generate_mnemonic() 51 | 52 | # create bitcoin wallet 53 | w = wallet.create_wallet(network="bitcoin", seed=seed, children=1) 54 | print(w) 55 | 56 | # wallets = wallet.create_wallet(network="BTC", seed=seed, children=1) 57 | # wallets = wallet.create_wallet(network="ETH", seed=seed, children=1) 58 | # wallets = wallet.create_wallet(network="XRP", seed=seed, children=1) 59 | # wallets = wallet.create_wallet(network="ZCASH", seed=seed, children=1) 60 | # wallets = wallet.create_wallet(network="XLM", seed=seed, children=1) 61 | # wallets = wallet.create_wallet(network="stellar_testnet", seed=seed, children=1) 62 | 63 | Output looks like this: 64 | 65 | .. code:: bash 66 | 67 | $ python create_btc_wallet.py 68 | 69 | { 70 | "coin": "BTC", 71 | "seed": "guess tiny intact poet process segment pelican bright assume avocado view lazy", 72 | "address": "1HwPm2tcdakwkTTWU286crWQqTnbEkD7av", 73 | "xprivate_key": "xprv9s21ZrQH143K2Dizn667UCo9oYPdTPSMWq7D5t929aXf1kfnmW79CryavzBxqbWfrYzw8jbyTKvsiuFNwr1JL2qfrUy2Kbwq4WbBPfxYGbg", 74 | "xpublic_key": "xpub661MyMwAqRbcEhoTt7d7qLjtMaE7rrACt42otGYdhv4dtYzwK3RPkfJ4nEjpFQDdT8JjT3VwQ3ZKjJaeuEdpWmyw16sY9SsoY68PoXaJvfU", 75 | "wif": "L1EnVJviG6jR2oovFbfxZoMp1JknTACKLzsTKqDNUwATCWpY1Fp4", 76 | "children": [{ 77 | "address": "1nDWAr2v1wNv6ZkjQ3GJCZq1HUHCHm1wZ", 78 | "address": "1nDWAr2v1wNv6ZkjQ3GJCZq1HUHCHm1wZ", 79 | "path": "m/0", 80 | "wif": "KysRDiwJNkS9VPzy1UH76DrCDizsWKtEooSzikich792RVzcUaJP" 81 | }] 82 | } 83 | 84 | 85 | \* Valid options for `network` are: BTC, BTG, BCH, LTC, DASH, DOGE, XRP, ZCASH and XLM 86 | 87 | 88 | -------------------------------------------------------------------------------- /py_hd_wallet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nthtson/py_hd_wallet/783293771e9ee98f806652f352e8ce3458841c68/py_hd_wallet/__init__.py -------------------------------------------------------------------------------- /py_hd_wallet/mw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import hashlib 3 | import os 4 | import sys 5 | from binascii import hexlify 6 | from getpass import getpass 7 | from optparse import OptionParser 8 | 9 | import hashprint 10 | import sha3 11 | from mnemonic.mnemonic import Mnemonic 12 | from pycoin.contrib.segwit_addr import bech32_encode, convertbits 13 | from pycoin.encoding import b2a_hashed_base58, to_bytes_32 14 | from pycoin.key.BIP32Node import BIP32Node 15 | 16 | # from .colorize import colorize 17 | from .ripple import RippleBaseDecoder 18 | 19 | # mw 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' TREZOR 20 | # > seed c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04 21 | # ku H:$SEED 22 | # > master xprv9s21ZrQH143K3h3fDYiay8mocZ3afhfULfb5GX8kCBdno77K4HiA15Tg23wpbeF1pLfs1c5SPmYHrEpTuuRhxMwvKDwqdKiGJS9XFKzUsAF 23 | # ku -s "44'/0'/0'/0/0" H:$SEED 24 | # > 1PEha8dk5Me5J1rZWpgqSt5F4BroTBLS5y 25 | VISUALIZATION_PATH = "9999'/9999'" 26 | 27 | ripple_decoder = RippleBaseDecoder() 28 | 29 | 30 | def btc_to_address(prefix, subkey): 31 | return b2a_hashed_base58(prefix + subkey.hash160()) 32 | 33 | 34 | def btc_to_private(exponent): 35 | return b2a_hashed_base58(b'\x80' + to_bytes_32(exponent) + b'\01') 36 | 37 | 38 | def eth_to_address(prefix, subkey): 39 | hasher = sha3.keccak_256() 40 | hasher.update(subkey.sec(True)[1:]) 41 | return hexlify(hasher.digest()[-20:]).decode() 42 | 43 | 44 | def eth_to_private(exponent): 45 | return hexlify(to_bytes_32(exponent)).decode() 46 | 47 | 48 | def xrp_to_address(prefix, subkey): 49 | return ripple_decoder.encode(subkey.hash160()) 50 | 51 | 52 | def xrp_to_private(exponent): 53 | return hexlify(to_bytes_32(exponent)).decode() 54 | 55 | 56 | def cosmos_to_address(prefix, subkey): 57 | return bech32_encode(prefix.decode(), convertbits(subkey.hash160(), 8, 5)) 58 | 59 | 60 | def cosmos_to_private(exponent): 61 | return hexlify(to_bytes_32(exponent)).decode() 62 | 63 | 64 | coin_map = { 65 | "btc": (b'\0', "44'/0'/0'/0", btc_to_address, btc_to_private), 66 | "zcash": (b'\x1c\xb8', "44'/1893'/0'/0", btc_to_address, btc_to_private), 67 | "eth": (b'', "44'/60'/0'/0", eth_to_address, eth_to_private), 68 | "rop": (b'', "44'/1'/0'/0", eth_to_address, eth_to_private), 69 | "xrp": (b'', "44'/144'/0'/0", xrp_to_address, xrp_to_private), 70 | "txrp": (b'', "44'/1'/0'/0", xrp_to_address, xrp_to_private), 71 | "cosmos": (b'cosmos', "44'/118'/0'/0", cosmos_to_address, cosmos_to_private), 72 | } 73 | 74 | coins = list(coin_map.keys()) 75 | 76 | coin_list = ",".join(coins) 77 | 78 | 79 | def mnemonic_to_master(mnemonic, passphrase): 80 | seed = Mnemonic.to_seed(mnemonic, passphrase=passphrase) 81 | master = BIP32Node.from_master_secret(seed) 82 | return seed, master 83 | 84 | 85 | def compute_address(coin, master, i): 86 | (address_prefix, coin_derivation, to_address, to_private) = coin_map[coin] 87 | path = coin_derivation + "/%d"%(i,) 88 | subkey = next(master.subkeys(path)) 89 | private = to_private(subkey.secret_exponent()) 90 | address = to_address(address_prefix, subkey) 91 | return address, private, path 92 | 93 | 94 | def generate(data=None): 95 | if data is None: 96 | data = os.urandom(16) 97 | return Mnemonic('english').to_mnemonic(data) 98 | 99 | 100 | def hash_entropy(entropy_string): 101 | ee = hashlib.sha256(entropy_string.encode('utf-8')) 102 | return ee.digest()[0:16] 103 | 104 | 105 | def visual(master): 106 | subkey = next(master.subkeys(VISUALIZATION_PATH)) 107 | return hashprint.pformat(list(bytearray(subkey.hash160()))) -------------------------------------------------------------------------------- /py_hd_wallet/ripple.py: -------------------------------------------------------------------------------- 1 | from hashlib import sha256 2 | from binascii import hexlify 3 | import six 4 | 5 | class RippleBaseDecoder(object): 6 | """Decodes Ripple's base58 alphabet. 7 | This is what ripple-lib does in ``base.js``. 8 | """ 9 | 10 | alphabet = 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz' 11 | 12 | @classmethod 13 | def decode(cls, *a, **kw): 14 | """Apply base58 decode, verify checksum, return payload. 15 | """ 16 | decoded = cls.decode_base(*a, **kw) 17 | assert cls.verify_checksum(decoded) 18 | payload = decoded[:-4] # remove the checksum 19 | payload = payload[1:] # remove first byte, a version number 20 | return payload 21 | 22 | @classmethod 23 | def decode_base(cls, encoded, pad_length=None): 24 | """Decode a base encoded string with the Ripple alphabet.""" 25 | n = 0 26 | base = len(cls.alphabet) 27 | for char in encoded: 28 | n = n * base + cls.alphabet.index(char) 29 | return to_bytes(n, pad_length, 'big') 30 | 31 | @classmethod 32 | def verify_checksum(cls, bytes): 33 | """These ripple byte sequences have a checksum builtin. 34 | """ 35 | valid = bytes[-4:] == sha256(sha256(bytes[:-4]).digest()).digest()[:4] 36 | return valid 37 | 38 | @staticmethod 39 | def as_ints(bytes): 40 | return list([ord(c) for c in bytes]) 41 | 42 | @classmethod 43 | def encode(cls, data): 44 | """Apply base58 encode including version, checksum.""" 45 | version = b'\x00' 46 | bytes = version + data 47 | bytes += sha256(sha256(bytes).digest()).digest()[:4] # checksum 48 | return cls.encode_base(bytes) 49 | 50 | @classmethod 51 | def encode_seed(cls, data): 52 | """Apply base58 encode including version, checksum.""" 53 | version = bytearray([33]) 54 | bytes = version + data 55 | bytes += sha256(sha256(bytes).digest()).digest()[:4] # checksum 56 | return cls.encode_base(bytes) 57 | 58 | @classmethod 59 | def encode_base(cls, data): 60 | # https://github.com/jgarzik/python-bitcoinlib/blob/master/bitcoin/base58.py 61 | # Convert big-endian bytes to integer 62 | n = int(hexlify(data).decode('utf8'), 16) 63 | 64 | # Divide that integer into base58 65 | res = [] 66 | while n > 0: 67 | n, r = divmod(n, len(cls.alphabet)) 68 | res.append(cls.alphabet[r]) 69 | res = ''.join(res[::-1]) 70 | 71 | # Encode leading zeros as base58 zeros 72 | czero = 0 if six.PY3 else b'\x00' 73 | pad = 0 74 | for c in data: 75 | if c == czero: 76 | pad += 1 77 | else: 78 | break 79 | return cls.alphabet[0] * pad + res -------------------------------------------------------------------------------- /py_hd_wallet/wallet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from datetime import datetime 5 | from pywallet.utils import ( 6 | Wallet, HDPrivateKey, HDKey 7 | ) 8 | from pywallet.network import * 9 | import inspect 10 | from .mw import compute_address, mnemonic_to_master 11 | from seed_phrases_for_stellar.seed_phrase_to_stellar_keys import to_binary_seed 12 | from seed_phrases_for_stellar.key_derivation import account_keypair 13 | 14 | my_language = 'english' 15 | STELLAR_ACCOUNT_PATH_FORMAT = "m/44'/148'/%d'" 16 | public_stellar_passphrase = 'Public Global Stellar Network ; September 2015' 17 | test_stellar_passphrase = 'Test SDF Network ; September 2015' 18 | 19 | 20 | def generate_mnemonic(strength=128): 21 | """ 22 | 23 | :param strength: 24 | :return: 25 | """ 26 | _, seed = HDPrivateKey.master_key_from_entropy(strength=strength) 27 | return seed 28 | 29 | 30 | def generate_child_id(): 31 | """ 32 | 33 | :return: 34 | """ 35 | now = datetime.now() 36 | seconds_since_midnight = (now - now.replace( 37 | hour=0, minute=0, second=0, microsecond=0)).total_seconds() 38 | return int((int(now.strftime( 39 | '%y%m%d')) + seconds_since_midnight*1000000) // 100) 40 | 41 | 42 | def create_address(network='btctest', xpub=None, child=None, path=0): 43 | """ 44 | 45 | :param network: 46 | :param xpub: 47 | :param child: 48 | :param path: 49 | :return: 50 | """ 51 | assert xpub is not None 52 | 53 | if child is None: 54 | child = generate_child_id() 55 | 56 | if network == 'ethereum' or network.upper() == 'ETH': 57 | acct_pub_key = HDKey.from_b58check(xpub) 58 | 59 | keys = HDKey.from_path( 60 | acct_pub_key, '{change}/{index}'.format(change=path, index=child)) 61 | 62 | res = { 63 | "path": "m/" + str(acct_pub_key.index) + "/" + str(keys[-1].index), 64 | "bip32_path": "m/44'/60'/0'/" + str(acct_pub_key.index) + "/" + str(keys[-1].index), 65 | "address": keys[-1].address(), 66 | "private_key": keys[-1]._key.to_hex() 67 | } 68 | 69 | if inspect.stack()[1][3] == "create_wallet": 70 | res["xpublic_key"] = keys[-1].to_b58check() 71 | 72 | return res 73 | 74 | # else ... 75 | wallet_obj = Wallet.deserialize(xpub, network=network.upper()) 76 | child_wallet = wallet_obj.get_child(child, is_prime=False) 77 | 78 | net = get_network(network) 79 | 80 | return { 81 | "path": "m/" + str(wallet_obj.child_number) + "/" +str(child_wallet.child_number), 82 | "bip32_path": net.BIP32_PATH + str(wallet_obj.child_number) + "/" +str(child_wallet.child_number), 83 | "address": child_wallet.to_address(), 84 | "private_key": child_wallet.get_private_key_hex(), 85 | # "xpublic_key": child_wallet.serialize_b58(private=False), 86 | # "wif": child_wallet.export_to_wif() # needs private key 87 | } 88 | 89 | 90 | def get_network(network='btctest'): 91 | """ 92 | 93 | :param network: 94 | :return: 95 | """ 96 | network = network.lower() 97 | 98 | if network == "bitcoin_testnet" or network == "btctest": 99 | return BitcoinTestNet 100 | elif network == "bitcoin" or network == "btc": 101 | return BitcoinMainNet 102 | elif network == "dogecoin" or network == "doge": 103 | return DogecoinMainNet 104 | elif network == "dogecoin_testnet" or network == "dogetest": 105 | return DogecoinTestNet 106 | elif network == "litecoin" or network == "ltc": 107 | return LitecoinMainNet 108 | elif network == "litecoin_testnet" or network == "ltctest": 109 | return LitecoinTestNet 110 | elif network == "bitcoin_cash" or network == "bch": 111 | return BitcoinCashMainNet 112 | elif network == "bitcoin_gold" or network == "btg": 113 | return BitcoinGoldMainNet 114 | elif network == "dash": 115 | return DashMainNet 116 | elif network == "dash_testnet" or network == 'dashtest': 117 | return DashTestNet 118 | elif network == 'omni': 119 | return OmniMainNet 120 | elif network == 'omni_testnet': 121 | return OmniTestNet 122 | elif network == "feathercoin" or network == 'ftc': 123 | return FeathercoinMainNet 124 | elif network == "qtum": 125 | return QtumMainNet 126 | elif network == "qtum_testnet" or network == "qtumtest": 127 | return QtumTestNet 128 | 129 | return BitcoinTestNet 130 | 131 | 132 | def create_wallet(network='btctest', seed=None, children=1): 133 | """ 134 | 135 | :param network: 136 | :param seed: 137 | :param children: 138 | :return: 139 | """ 140 | if seed is None: 141 | seed = generate_mnemonic() 142 | 143 | net = get_network(network) 144 | wallet = { 145 | "coin": net.COIN, 146 | "seed": seed, 147 | "private_key": "", 148 | "public_key": "", 149 | "xprivate_key": "", 150 | "xpublic_key": "", 151 | "address": "", 152 | "wif": "", 153 | "children": [] 154 | } 155 | 156 | if network == 'ethereum' or network.upper() == 'ETH': 157 | wallet["coin"] = "ETH" 158 | 159 | master_key = HDPrivateKey.master_key_from_mnemonic(seed) 160 | root_keys = HDKey.from_path(master_key, "m/44'/60'/0'") 161 | 162 | acct_priv_key = root_keys[-1] 163 | acct_pub_key = acct_priv_key.public_key 164 | 165 | wallet["private_key"] = acct_priv_key.to_hex() 166 | wallet["public_key"] = acct_pub_key.to_hex() 167 | wallet["xprivate_key"] = acct_priv_key.to_b58check() 168 | wallet["xpublic_key"] = acct_pub_key.to_b58check() 169 | 170 | child_wallet = create_address( 171 | network=network.upper(), xpub=wallet["xpublic_key"], 172 | child=0, path=0) 173 | wallet["address"] = child_wallet["address"] 174 | wallet["xpublic_key_prime"] = child_wallet["xpublic_key"] 175 | 176 | # get public info from first prime child 177 | for child in range(children): 178 | child_wallet = create_address( 179 | network=network.upper(), xpub=wallet["xpublic_key"], 180 | child=child, path=0 181 | ) 182 | wallet["children"].append({ 183 | "address": child_wallet["address"], 184 | "private_key": child_wallet["private_key"], 185 | "xpublic_key": child_wallet["xpublic_key"], 186 | "path": "m/" + str(child), 187 | "bip32_path": "m/44'/60'/0'/" + str(child), 188 | }) 189 | elif network == 'ripple' or network.upper() == 'XRP': 190 | wallet["coin"] = "XRP" 191 | (seed, master) = mnemonic_to_master(seed, '') 192 | (address, private_key, path) = compute_address('xrp', master, 0) 193 | 194 | wallet["private_key"] = private_key 195 | wallet["public_key"] = address 196 | wallet["address"] = address 197 | 198 | # get public info from first prime child 199 | for child in range(children): 200 | (child_address, child_private_key, child_path) = compute_address('xrp', master, child) 201 | wallet["children"].append({ 202 | "address": child_address, 203 | "private_key": child_private_key, 204 | # "xpublic_key": child_wallet["xpublic_key"], 205 | "path": "m/" + str(child), 206 | "bip32_path": child_path, 207 | }) 208 | elif network == 'zcash' or network.upper() == 'ZCASH': 209 | wallet["coin"] = "ZCASH" 210 | (seed, master) = mnemonic_to_master(seed, '') 211 | (address, private_key, path) = compute_address('zcash', master, 0) 212 | 213 | wallet["private_key"] = private_key 214 | wallet["public_key"] = address 215 | wallet["address"] = address 216 | 217 | # get public info from first prime child 218 | for child in range(children): 219 | (child_address, child_private_key, child_path) = compute_address('zcash', master, child) 220 | wallet["children"].append({ 221 | "address": child_address, 222 | "private_key": child_private_key, 223 | # "xpublic_key": child_wallet["xpublic_key"], 224 | "path": "m/" + str(child), 225 | "bip32_path": child_path, 226 | }) 227 | elif network == 'stellar_testnet': 228 | (binary_seed, seed_phrase_type) = to_binary_seed(seed, test_stellar_passphrase, my_language) 229 | keypair = account_keypair(binary_seed, 0) 230 | wallet["private_key"] = keypair.seed().decode("utf-8") 231 | wallet["public_key"] = keypair.address().decode("utf-8") 232 | wallet["address"] = keypair.address().decode("utf-8") 233 | # get public info from first prime child 234 | for child in range(children): 235 | keypair = account_keypair(binary_seed, child) 236 | wallet["children"].append({ 237 | "address": keypair.address().decode("utf-8"), 238 | "private_key": keypair.seed().decode("utf-8"), 239 | "path": "m/" + str(child), 240 | "bip39_path": STELLAR_ACCOUNT_PATH_FORMAT % child, 241 | }) 242 | elif network == 'stellar' or network.upper() == 'XLM': 243 | (binary_seed, seed_phrase_type) = to_binary_seed(seed, public_stellar_passphrase, my_language) 244 | keypair = account_keypair(binary_seed, 0) 245 | wallet["private_key"] = keypair.seed().decode("utf-8") 246 | wallet["public_key"] = keypair.address().decode("utf-8") 247 | wallet["address"] = keypair.address().decode("utf-8") 248 | # get public info from first prime child 249 | for child in range(children): 250 | keypair = account_keypair(binary_seed, child) 251 | wallet["children"].append({ 252 | "address": keypair.address().decode("utf-8"), 253 | "private_key": keypair.seed().decode("utf-8"), 254 | "path": "m/" + str(child), 255 | "bip39_path": STELLAR_ACCOUNT_PATH_FORMAT % child, 256 | }) 257 | else: 258 | my_wallet = Wallet.from_master_secret( 259 | network=network.upper(), seed=seed) 260 | 261 | # account level 262 | wallet["private_key"] = my_wallet.private_key.get_key().decode() 263 | wallet["public_key"] = my_wallet.public_key.get_key().decode() 264 | wallet["xprivate_key"] = my_wallet.serialize_b58(private=True) 265 | wallet["xpublic_key"] = my_wallet.serialize_b58(private=False) 266 | wallet["address"] = my_wallet.to_address() 267 | wallet["wif"] = my_wallet.export_to_wif() 268 | 269 | prime_child_wallet = my_wallet.get_child(0, is_prime=True) 270 | wallet["xpublic_key_prime"] = prime_child_wallet.serialize_b58(private=False) 271 | 272 | # prime children 273 | for child in range(children): 274 | child_wallet = my_wallet.get_child(child, is_prime=False, as_private=True) 275 | wallet["children"].append({ 276 | "xpublic_key": child_wallet.serialize_b58(private=False), 277 | "xprivate_key": child_wallet.serialize_b58(private=True), 278 | "address": child_wallet.to_address(), 279 | "private_key": child_wallet.get_private_key_hex(), 280 | "path": "m/" + str(child), 281 | "bip32_path": net.BIP32_PATH + str(child_wallet.child_number), 282 | }) 283 | 284 | return wallet 285 | 286 | 287 | def create_wallet(network='btctest', seed=None, children=1): 288 | """ 289 | 290 | :param network: 291 | :param seed: 292 | :param children: 293 | :return: 294 | """ 295 | if seed is None: 296 | seed = generate_mnemonic() 297 | 298 | net = get_network(network) 299 | wallet = { 300 | "coin": net.COIN, 301 | "seed": seed, 302 | "private_key": "", 303 | "public_key": "", 304 | "xprivate_key": "", 305 | "xpublic_key": "", 306 | "address": "", 307 | "wif": "", 308 | "children": [] 309 | } 310 | 311 | if network == 'ethereum' or network.upper() == 'ETH': 312 | wallet["coin"] = "ETH" 313 | 314 | master_key = HDPrivateKey.master_key_from_mnemonic(seed) 315 | root_keys = HDKey.from_path(master_key, "m/44'/60'/0'") 316 | 317 | acct_priv_key = root_keys[-1] 318 | acct_pub_key = acct_priv_key.public_key 319 | 320 | wallet["private_key"] = acct_priv_key.to_hex() 321 | wallet["public_key"] = acct_pub_key.to_hex() 322 | wallet["xprivate_key"] = acct_priv_key.to_b58check() 323 | wallet["xpublic_key"] = acct_pub_key.to_b58check() 324 | 325 | child_wallet = create_address( 326 | network=network.upper(), xpub=wallet["xpublic_key"], 327 | child=0, path=0) 328 | wallet["address"] = child_wallet["address"] 329 | wallet["xpublic_key_prime"] = child_wallet["xpublic_key"] 330 | 331 | # get public info from first prime child 332 | for child in range(children): 333 | child_wallet = create_address( 334 | network=network.upper(), xpub=wallet["xpublic_key"], 335 | child=child, path=0 336 | ) 337 | wallet["children"].append({ 338 | "address": child_wallet["address"], 339 | "private_key": child_wallet["private_key"], 340 | "xpublic_key": child_wallet["xpublic_key"], 341 | "path": "m/" + str(child), 342 | "bip32_path": "m/44'/60'/0'/" + str(child), 343 | }) 344 | elif network == 'ripple' or network.upper() == 'XRP': 345 | wallet["coin"] = "XRP" 346 | (seed, master) = mnemonic_to_master(seed, '') 347 | (address, private_key, path) = compute_address('xrp', master, 0) 348 | 349 | wallet["private_key"] = private_key 350 | wallet["public_key"] = address 351 | wallet["address"] = address 352 | 353 | # get public info from first prime child 354 | for child in range(children): 355 | (child_address, child_private_key, child_path) = compute_address('xrp', master, child) 356 | wallet["children"].append({ 357 | "address": child_address, 358 | "private_key": child_private_key, 359 | # "xpublic_key": child_wallet["xpublic_key"], 360 | "path": "m/" + str(child), 361 | "bip32_path": child_path, 362 | }) 363 | elif network == 'zcash' or network.upper() == 'ZCASH': 364 | wallet["coin"] = "ZCASH" 365 | (seed, master) = mnemonic_to_master(seed, '') 366 | (address, private_key, path) = compute_address('zcash', master, 0) 367 | 368 | wallet["private_key"] = private_key 369 | wallet["public_key"] = address 370 | wallet["address"] = address 371 | 372 | # get public info from first prime child 373 | for child in range(children): 374 | (child_address, child_private_key, child_path) = compute_address('zcash', master, child) 375 | wallet["children"].append({ 376 | "address": child_address, 377 | "private_key": child_private_key, 378 | # "xpublic_key": child_wallet["xpublic_key"], 379 | "path": "m/" + str(child), 380 | "bip32_path": child_path, 381 | }) 382 | elif network == 'stellar_testnet': 383 | (binary_seed, seed_phrase_type) = to_binary_seed(seed, test_stellar_passphrase, my_language) 384 | keypair = account_keypair(binary_seed, 0) 385 | wallet["private_key"] = keypair.seed().decode("utf-8") 386 | wallet["public_key"] = keypair.address().decode("utf-8") 387 | wallet["address"] = keypair.address().decode("utf-8") 388 | # get public info from first prime child 389 | for child in range(children): 390 | keypair = account_keypair(binary_seed, child) 391 | wallet["children"].append({ 392 | "address": keypair.address().decode("utf-8"), 393 | "private_key": keypair.seed().decode("utf-8"), 394 | "path": "m/" + str(child), 395 | "bip39_path": STELLAR_ACCOUNT_PATH_FORMAT % child, 396 | }) 397 | elif network == 'stellar' or network.upper() == 'XLM': 398 | (binary_seed, seed_phrase_type) = to_binary_seed(seed, public_stellar_passphrase, my_language) 399 | keypair = account_keypair(binary_seed, 0) 400 | wallet["private_key"] = keypair.seed().decode("utf-8") 401 | wallet["public_key"] = keypair.address().decode("utf-8") 402 | wallet["address"] = keypair.address().decode("utf-8") 403 | # get public info from first prime child 404 | for child in range(children): 405 | keypair = account_keypair(binary_seed, child) 406 | wallet["children"].append({ 407 | "address": keypair.address().decode("utf-8"), 408 | "private_key": keypair.seed().decode("utf-8"), 409 | "path": "m/" + str(child), 410 | "bip39_path": STELLAR_ACCOUNT_PATH_FORMAT % child, 411 | }) 412 | else: 413 | my_wallet = Wallet.from_master_secret( 414 | network=network.upper(), seed=seed) 415 | 416 | # account level 417 | wallet["private_key"] = my_wallet.private_key.get_key().decode() 418 | wallet["public_key"] = my_wallet.public_key.get_key().decode() 419 | wallet["xprivate_key"] = my_wallet.serialize_b58(private=True) 420 | wallet["xpublic_key"] = my_wallet.serialize_b58(private=False) 421 | wallet["address"] = my_wallet.to_address() 422 | wallet["wif"] = my_wallet.export_to_wif() 423 | 424 | prime_child_wallet = my_wallet.get_child(0, is_prime=True) 425 | wallet["xpublic_key_prime"] = prime_child_wallet.serialize_b58(private=False) 426 | 427 | # prime children 428 | for child in range(children): 429 | child_wallet = my_wallet.get_child(child, is_prime=False, as_private=True) 430 | wallet["children"].append({ 431 | "xpublic_key": child_wallet.serialize_b58(private=False), 432 | "xprivate_key": child_wallet.serialize_b58(private=True), 433 | "address": child_wallet.to_address(), 434 | "private_key": child_wallet.get_private_key_hex(), 435 | "path": "m/" + str(child), 436 | "bip32_path": net.BIP32_PATH + str(child_wallet.child_number), 437 | }) 438 | 439 | return wallet 440 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.rst", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | 8 | name='py_hd_wallet', 9 | 10 | version='0.1', 11 | 12 | packages=['py_hd_wallet'], 13 | 14 | license='LICENSE.txt', 15 | 16 | author="Nguyen Tran Ho Thanh Son", 17 | 18 | author_email="nguyentranhothanhson@gmail.com", 19 | 20 | description="A multi-cryptocurrency HD wallet implemented by Python", 21 | 22 | long_description=long_description, 23 | long_description_content_type='text/x-rst', 24 | 25 | url="https://github.com/nthtson/py_hd_wallet", 26 | 27 | install_requires=[ 28 | 'pywallet==0.1.0', 29 | 'pycoin==0.80', 30 | 'mnemonic==0.18', 31 | 'pysha3==1.0.2', 32 | 'pycryptodome==3.19.1', 33 | 'hashprint==1.0.1', 34 | 'pysha3==1.0.2', 35 | 'pycoin==0.80', 36 | 'seed-phrases-for-stellar==0.2', 37 | 'toml==0.10.0' 38 | ] 39 | 40 | ) 41 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nthtson/py_hd_wallet/783293771e9ee98f806652f352e8ce3458841c68/test/__init__.py -------------------------------------------------------------------------------- /test/test_py_hdwallet.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | from os.path import dirname, join, abspath 3 | sys.path.insert(0, abspath(join(dirname(__file__), '..'))) 4 | from py_hd_wallet import wallet 5 | 6 | # generate 12 word mnemonic seed 7 | # seed = wallet.generate_mnemonic() 8 | seed = 'guess tiny intact poet process segment pelican bright assume avocado view lazy' 9 | print(seed) 10 | 11 | # create bitcoin wallet 12 | wallets = wallet.create_wallet(network="BTC", seed=seed, children=1) 13 | print(wallets) 14 | # 15 | # # create ethereum wallet 16 | wallets = wallet.create_wallet(network="ETH", seed=seed, children=1) 17 | print(wallets) 18 | # 19 | # # create ripple wallet 20 | wallets = wallet.create_wallet(network="XRP", seed=seed, children=1) 21 | print(wallets) 22 | 23 | # create zcash wallet 24 | wallets = wallet.create_wallet(network="ZCASH", seed=seed, children=1) 25 | print(wallets) 26 | 27 | # # create stellar wallet 28 | wallets = wallet.create_wallet(network="XLM", seed=seed, children=1) 29 | print(wallets) 30 | 31 | # # create stellar wallet 32 | wallets = wallet.create_wallet(network="stellar_testnet", seed=seed, children=1) 33 | print(wallets) 34 | --------------------------------------------------------------------------------