├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── README.rst ├── examples └── sweeper.py ├── multimerchant ├── __init__.py ├── _version.py ├── network.py └── wallet │ ├── __init__.py │ ├── bip32.py │ ├── keys.py │ └── utils.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── bip32_test_vector.json ├── generate_bip32_test_vectors.py ├── generate_test_keys.py ├── keys_test_vector.json ├── test_bip32.py ├── test_bip32_vector.py ├── test_key_vector.py └── test_keys.py /AUTHORS: -------------------------------------------------------------------------------- 1 | Steven Buss 2 | Michael Flaxman 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Steven Buss 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | include AUTHORS 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | Multimerchant 3 | =========== 4 | 5 | -------------- 6 | 7 | Installation 8 | ============ 9 | 10 | Install this library: 11 | 12 | .. code-block:: 13 | 14 | $ sudo pip install multimerchant 15 | 16 | 17 | Then to verify it's working: 18 | 19 | .. code-block:: python 20 | 21 | from multimerchant.wallet import Wallet 22 | 23 | w = Wallet.from_master_secret("correct horse battery staple") 24 | assert w.to_address() == "1AJ7EDxyRwyGNcL4scXfUU7XqYkmVcwHqe" 25 | 26 | BIP32 wallets 27 | ============= 28 | 29 | BIP32_ wallets are hierarchical deterministic wallets. They allow you to 30 | generate bitcoin/dogecoin/litecoin addresses without exposing your private key to a 31 | potentially insecure server. 32 | 33 | To link a user with a new bitcoin address, you just need to provide the user's 34 | ID to the ``create_new_address_for_user`` method: 35 | 36 | TL;DR 37 | ----- 38 | 39 | .. code-block:: python 40 | 41 | ## DO THIS ON AN OFFLINE MACHINE, NOT YOUR WEBSERVER 42 | from multimerchant.wallet import Wallet 43 | 44 | # Create a wallet, and a primary child wallet for your app 45 | my_wallet = Wallet.new_random_wallet() 46 | print(my_wallet.serialize_b58(private=True)) # Write this down or print it out and keep in a secure location 47 | project_0_wallet = my_wallet.get_child(0, is_prime=True) 48 | project_0_public = project_0_wallet.public_copy() 49 | print(project_0_public.serialize_b58(private=False)) # Put this in your app's settings file 50 | 51 | 52 | ## THINGS BELOW ARE PUBLIC FOR YOUR WEBSERVER 53 | 54 | # In your app's settings file, declare your public wallet: 55 | WALLET_PUBKEY = "" 56 | 57 | # Create a payment address for a user as needed: 58 | from multimerchant.wallet import Wallet 59 | from myapp.settings import WALLET_PUBKEY 60 | 61 | def get_payment_address_for_user(user): 62 | user_id = user.id 63 | assert isinstance(user_id, (int, long)) 64 | wallet = Wallet.deserialize(WALLET_PUBKEY) 65 | wallet_for_user = wallet.create_new_address_for_user(user.id) 66 | return wallet_for_user.to_address() 67 | 68 | .. _security: 69 | 70 | Security warning 71 | ---------------- 72 | 73 | 74 | BIP32 wallets have a vulnerability/bug that allows an attacker to recover the 75 | master private key when given a master public key and a publicly-derived 76 | private child. In other words: 77 | 78 | .. code-block:: python 79 | 80 | from multimerchant.wallet import Wallet 81 | 82 | w = Wallet.new_random_wallet() 83 | child = w.get_child(0, is_prime=False) # public derivation of a private child 84 | w_pub = w.public_copy() 85 | master_public_key = w_pub.serialize_b58(private=False) 86 | private_child_key = child.serialize_b58(private=True) 87 | 88 | Given ``master_public_key`` and ``private_child_key``, the steps to recover the 89 | secret master private key (``w``) are as simple as a subtraction on the 90 | elliptic curve. This has been implemented as ``Wallet.crack_private_key``, 91 | because if it's possible to do this, then anyone should be able to do it so the 92 | attack is well known: 93 | 94 | .. code-block:: python 95 | 96 | public_master = Wallet.deserialize(master_public_key) 97 | private_child = Wallet.deserialize(private_child_key) 98 | private_master = public_master.crack_private_key(private_child) 99 | assert private_master == w # :( 100 | 101 | This attack can be mitigated by these simple steps: 102 | 103 | #. NEVER give out your root master public key. 104 | #. When uploading a master public key to a webserver, always use a prime child 105 | of your master root. 106 | #. Never give out a private child key unless the user you're giving it to 107 | already has control of the parent private key (eg, for user-owned wallets). 108 | 109 | Why "always use a prime child of your master root" in step 2? Because prime 110 | children use private derivation, which means they cannot be used to recover the 111 | parent private key (no easier than brute force, anyway). 112 | 113 | Create a new wallet 114 | ------------------- 115 | 116 | If you haven't created a wallet yet, do so like this: 117 | 118 | **IMPORTANT** You must back up your wallet's private key, otherwise you won't 119 | be able to retrieve the coins sent to your public addresses. 120 | 121 | .. code-block:: python 122 | 123 | from multimerchant.wallet import Wallet 124 | 125 | my_wallet = Wallet.new_random_wallet() 126 | 127 | # Then back up your private key 128 | 129 | private_key = my_wallet.serialize() 130 | print(private_key) 131 | # Make sure that you can load your wallet successfully from this key 132 | wallet_test = Wallet.deserialize(private_key) 133 | assert my_wallet == wallet_test 134 | # If that assertion fails then open a ticket! 135 | # NOW WRITE DOWN THE PRIVATE KEY AND STORE IT IN A SECURE LOCATION 136 | 137 | Note that it's a good idea to supply some extra entropy to `new_random_wallet` 138 | in case your PRNG is compromised. You can accomplish this easily by banging on 139 | the keyboard. Here's an example, yours should be *much* longer: 140 | 141 | .. code-block:: python 142 | 143 | from multimerchant.wallet import Wallet 144 | 145 | wallet1 = Wallet.new_random_wallet('asdfasdfasdf') 146 | wallet2 = Wallet.new_random_wallet('asdfasdfasdf') 147 | assert(wallet1.get_private_key_hex() != wallet2.get_private_key_hex()) 148 | 149 | # They're completely different 150 | 151 | BIP32 wallets (or hierarchical deterministic wallets) allow you to create child 152 | wallets which can only generate public keys and don't expose a private key to 153 | an insecure server. You should create a new prime child wallet for every 154 | website you run (or a new wallet entirely), and perhaps a new prime child for 155 | each user (though that requires pre-generating a bunch of prime children 156 | offline, since you need the private key). Try to use prime children where 157 | possible (see `security`_). 158 | 159 | It's a good idea to create at least *one* prime child wallet for use on your 160 | website. The thinking being that if your website's wallet gets compromised 161 | somehow, you haven't completely lost control because your master wallet is 162 | secured on an offline machine. You can use your master wallet to move any funds 163 | in compromised child wallets to new child wallets and you'll be ok. 164 | 165 | Let's generate a new child wallet for your first website! 166 | 167 | .. code-block:: python 168 | 169 | # Lets assume you're loading a wallet from your safe private key backup 170 | my_wallet = Wallet.deserialize(private_key) 171 | 172 | # Create a new, public-only prime child wallet. Since you have the master 173 | # private key, you can recreate this child at any time in the future and don't 174 | # need to securely store its private key. 175 | # Remember to generate this as a prime child! See the security notice above. 176 | child = my_wallet.get_child(0, is_prime=True, as_private=False) 177 | 178 | # And lets export this child key 179 | public_key = my_wallet.serialize_b58(private=False) 180 | print(public_key) 181 | 182 | You can store your public key in your app's source code, as long as you never 183 | reveal any private keys. See the `security`_ notice above. 184 | 185 | Be aware that if someone gets a hold of your public key then they can generate 186 | all of your subsequent child addresses, which means they'll know exactly how 187 | many coins you have. The attacker cannot spend any coins, however, unless they 188 | are able to recover the private key (see `security`_). 189 | 190 | Generating new public addresses 191 | ------------------------------- 192 | 193 | BIP32 wallets allow you to generate public addresses without revealing your 194 | private key. Just pass in the user ID that needs a wallet: 195 | 196 | .. code-block:: python 197 | 198 | from multimerchant.wallet import Wallet 199 | from myapp.settings import WALLET_PUBKEY # Created above 200 | 201 | master_wallet = Wallet.deserialize(WALLET_PUBKEY) 202 | user_wallet = master_wallet.create_new_address_for_user(user_id) 203 | payment_address = user_wallet.to_address() 204 | 205 | This assumes that ``user_id`` is a unique positive integer and does not change 206 | for the life of the user (and is less than 2,147,483,648). Now any payments 207 | received at ``payment_address`` should be credited to the user identified by 208 | ``user_id``. 209 | 210 | Staying secure 211 | ============== 212 | 213 | Public Keys 214 | ----------- 215 | 216 | Public keys are mostly safe to keep on a public webserver. However, even though 217 | a public key does not allow an attacker to spend any of your coins, you should 218 | still try to protect the public key from hackers or curious eyes. Knowing the 219 | public key allows an attacker to generate all possible child wallets and know 220 | exactly how many coins you have. This isn't terrible, but nobody likes having 221 | their books opened up like this. 222 | 223 | As mentioned earlier, knowledge of a master public key and a non-prime private 224 | child of that key is enough to be able to recover the master private key. Never 225 | reveal private keys to users unless they already own the master private parent. 226 | 227 | Your master public key can be used to generate a virtually unlimited number of 228 | child public keys. Your users won't pay to your master public key, but instead 229 | you'll use your master public key to generate a new wallet for each user. 230 | 231 | Private Keys 232 | ------------ 233 | 234 | You must have the private key to spend any of your coins. If your private key 235 | is stolen then the hacker also has control of all of your coins. With a BIP32 236 | Wallet, generating a new master wallet is one of the only times that you need 237 | to be paranoid (and you're not being paranoid if they really *are* out to get 238 | you). Paranoia here is good because if anyone gets control of your master 239 | wallet they can spend all funds in all child wallets. 240 | 241 | You should create your wallet on a computer that is not connected to the 242 | internet. Ideally, this computer will *never* be connected to the internet 243 | after you generate your private key. The safest way to do this is to run Ubuntu 244 | on a livecd, install python and multimerchant, and generate a new wallet. 245 | 246 | Once you generate a new wallet you should write down the private key on a piece 247 | of paper (or print it out ...but can you *really* trust your printer?) and 248 | store it in a secure location. 249 | 250 | .. code-block:: bash 251 | 252 | sudo apt-get install python 253 | sudo apt-get install pip 254 | 255 | pip install multimerchant 256 | pip install ipython 257 | 258 | # Then launch the ipython shell 259 | ipython 260 | 261 | Once inside your ipython shell, generate a new wallet: 262 | 263 | .. code-block:: python 264 | 265 | from multimerchant.wallet import Wallet 266 | 267 | my_wallet = Wallet.new_random_wallet() 268 | 269 | # Then back up your private key 270 | 271 | private_key = my_wallet.serialize() 272 | print(private_key) 273 | # Write down this private key. 274 | # Double check it. 275 | # Then shut down the computer without connecting to the internet. 276 | 277 | Master private key 278 | ------------------ 279 | 280 | Your master private key allows you to spend coins sent to any of your public 281 | addresses. Guard this with your life, and never put it on a computer that's 282 | connected to the internet. 283 | 284 | Master private keys must NEVER be put on the internet. They must NEVER be 285 | located on a computer that is even *connected* to the internet. The only key 286 | that should be online is your PUBLIC key. Your private key should be written 287 | down (yes, on paper) and stored in a safe location, or on a computer that is 288 | never connected to the internet. 289 | 290 | Security wise, this is the most important part of generating secure public 291 | payment addresses. A master private key is the only way to retrieve the funds 292 | paid to a public address. You can use your master private key to generate the 293 | private keys of any child wallets, and then transfer those to a networked 294 | computer as necessary, if you want slightly smaller surface area for attacks. 295 | 296 | Forthcoming versions of multimerchant will allow you to generate transactions 297 | offline that you can safely transfer to a networked computer, allowing you to 298 | spend your child funds without ever putting a private key on a networked 299 | machine. 300 | 301 | Testing 302 | ------- 303 | 304 | All of these work, though I typically use nosetest: 305 | 306 | .. code-block:: bash 307 | 308 | python setup.py test 309 | nosetests 310 | python -m unittest discover 311 | 312 | Note 313 | ------- 314 | 315 | This repository is a fork of Steven Buss's work, Bitmerchant: https://github.com/sbuss/bitmerchant. 316 | -------------------------------------------------------------------------------- /examples/sweeper.py: -------------------------------------------------------------------------------- 1 | from multimerchant.wallet import Wallet 2 | import time 3 | from block_io import BlockIo 4 | import six 5 | import os 6 | import sys 7 | 8 | try: 9 | os.environ["HD_PRIVKEY"] 10 | except KeyError: 11 | print "Please generate an HD wallet first. See README.rst on https://github.com/blockio/multimerchant-python" 12 | print "Or do this:" 13 | print "\t $ python" 14 | print "\t >> from multimerchant.wallet import Wallet" 15 | print "\t >> print \"My HD Private Key:\", Wallet.new_generate_wallet(network=\"DOGETEST\")" 16 | print "\t >> quit()" 17 | print "\t $ HD_PRIVKEY=STRING_FROM_ABOVE python sweeper.py" 18 | print "... where sweeper.py is this file." 19 | sys.exit(1) 20 | 21 | # Please use the Dogecoin Testnet here -- you have free coins on sign up at Block.io 22 | # Dogecoin Testnet because of the static demo amount for withdrawals/sweeps below 23 | block_io = BlockIo('Your Dogecoin Testnet API Key', 'Your Secret PIN', 2) 24 | 25 | network = block_io.get_balance()['data']['network'] # extract the network of our API Key 26 | 27 | # create a wallet using a master secret -- this one is super insecure, but it's an example 28 | # don't have an HD privkey yet? Create one by using: 29 | # 30 | # $ python 31 | # >> from multimerchant.wallet import Wallet 32 | # >> hd_privkey = Wallet.new_random_wallet(network="DOGETEST").serialize() 33 | # >> print "My Super Secret HD Wallet:", hd_privkey 34 | # 35 | # The 'network' value above can be: BTC, BTCTEST, DOGE, DOGETEST, LTC, LTCTEST 36 | # Get the relevant network's API Key at Block.io for use in this example 37 | 38 | w = Wallet.deserialize(os.environ['HD_PRIVKEY'], network=network) 39 | 40 | # or generate an insecure version like this: 41 | # w = Wallet.from_master_secret("correct horse battery staple", network=network) 42 | 43 | # BIP32 wallets are children derived from a single master seed (you generated this with the instructions above) 44 | # You can specify a child by an ID. For instance, for child_id=1: 45 | 46 | # let's generate 5 wallets 47 | 48 | addresses = [] 49 | children = [] # the payment addresses we'll generate from the seed 50 | 51 | for child_id in range(1,6): 52 | child = w.get_child(child_id, is_prime=True, as_private=True) 53 | addresses.insert(len(addresses), child.to_address()) 54 | children.insert(len(children), child) 55 | 56 | six.print_("Child No.", child_id, ". Address="+child.to_address(), "PrivKey="+child.export_to_wif()) 57 | 58 | # check the balance for these addresses using Block.io 59 | all_addresses = ','.join(str(x) for x in addresses) 60 | 61 | response = block_io.get_address_balance(addresses=all_addresses) # the addresses parameter can be a comma-separated list of addresses here 62 | 63 | # NOTE: Amounts deposited into addresses through Block.io green addresses will be immediately available 64 | # even with 0 confirmations 65 | six.print_(">> Total Balance in All Addresses:", response['data']['available_balance'], network) 66 | 67 | for addrinfo in response['data']['balances']: 68 | six.print_(" >> Balances in", addrinfo['address']) 69 | six.print_(" >>> Available:", addrinfo['available_balance'], network) # either confirmed or from a green address 70 | six.print_(" >>> Pending:", addrinfo['pending_received_balance'], network) # is neither from a green address, nor is it confirmed 71 | 72 | # let's transfer some testnet coins into the first child address 73 | amounts = "500.0" # DOGETEST 74 | response = block_io.withdraw(to_addresses=children[0].to_address(), amounts=amounts) 75 | 76 | six.print_("* Depositing", amounts, network, "into", children[0].to_address()) 77 | six.print_(">> Deposit Proof Transaction ID:", response['data']['txid']) # you can view this on https://chain.so immediately 78 | 79 | time.sleep(2) # let the transaction propagate on the network for a bit 80 | 81 | # so far so good. Let's sweep the coins out of the first child, and into the second child 82 | # NOTE: While you can specify the number of confirmations required for coins to be swept, 83 | # please beware that deposits from green addresses will show as available in get_address_balance calls. 84 | # This might cause confusion when the sweep_from_address call returns an error when sweeping amounts with 85 | # confirmations > 0 86 | 87 | six.print_("* Sweeping all funds (confirmed and unconfirmed) from", children[0].to_address(), "to", children[1].to_address()) 88 | response = block_io.sweep_from_address(from_address=children[0].to_address(), private_key=children[0].export_to_wif(), to_address=children[1].to_address()) 89 | 90 | 91 | six.print_(">> Amount swept from", children[0].to_address(), "into", children[1].to_address(), "=", response['data']['amount_sent'], network) 92 | six.print_(">> Transaction ID:", response['data']['txid']) 93 | 94 | # Note: the swept amount does not need to be confirmed. In the above case, the amount was not confirmed 95 | # but was swept into the destination address immediately 96 | # You can sweep only confirmed amounts if you wish by adding "confirmations=X" to the sweep_from_address call, 97 | # where X is the number of confirmations 98 | 99 | -------------------------------------------------------------------------------- /multimerchant/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ # NOQA 2 | 3 | __all__ = [ 4 | 'network', 5 | 'wallet', 6 | ] 7 | -------------------------------------------------------------------------------- /multimerchant/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.1" 2 | -------------------------------------------------------------------------------- /multimerchant/network.py: -------------------------------------------------------------------------------- 1 | class BitcoinMainNet(object): 2 | """Bitcoin MainNet version bytes. 3 | 4 | From https://github.com/bitcoin/bitcoin/blob/v0.9.0rc1/src/chainparams.cpp 5 | """ 6 | NAME = "Bitcoin Main Net" 7 | SCRIPT_ADDRESS = 0x05 # int(0x05) = 5 8 | PUBKEY_ADDRESS = 0x00 # int(0x00) = 0 # Used to create payment addresses 9 | SECRET_KEY = 0x80 # int(0x80) = 128 # Used for WIF format 10 | EXT_PUBLIC_KEY = 0x0488B21E # Used to serialize public BIP32 addresses 11 | EXT_SECRET_KEY = 0x0488ADE4 # Used to serialize private BIP32 addresses 12 | 13 | 14 | class BitcoinTestNet(object): 15 | """Bitcoin TestNet version bytes. 16 | 17 | From https://github.com/bitcoin/bitcoin/blob/v0.9.0rc1/src/chainparams.cpp 18 | """ 19 | NAME = "Bitcoin Test Net" 20 | SCRIPT_ADDRESS = 0xc4 # int(0xc4) = 196 21 | PUBKEY_ADDRESS = 0x6f # int(0x6f) = 111 22 | SECRET_KEY = 0xEF # int(0xef) = 239 23 | EXT_PUBLIC_KEY = 0x043587CF 24 | EXT_SECRET_KEY = 0x04358394 25 | 26 | 27 | class LitecoinMainNet(object): 28 | """Litecoin MainNet version bytes 29 | 30 | Primary version bytes from: 31 | https://github.com/litecoin-project/litecoin/blob/master-0.8/src/base58.h 32 | 33 | Unofficial extended version bytes from 34 | https://bitcointalk.org/index.php?topic=453395.0 35 | """ 36 | NAME = "Litecoin Main Net" 37 | SCRIPT_ADDRESS = 0x05 # int(0x05) = 5 38 | PUBKEY_ADDRESS = 0x30 # int(0x30) = 48 39 | SECRET_KEY = PUBKEY_ADDRESS + 128 # = int(0xb0) = 176 40 | 41 | # Unofficial extended version bytes taken from 42 | # https://bitcointalk.org/index.php?topic=453395.0 43 | EXT_PUBLIC_KEY = 0x019da462 44 | EXT_SECRET_KEY = 0x019d9cfe 45 | 46 | 47 | class LitecoinTestNet(object): 48 | """Litecoin TestNet version bytes 49 | 50 | Primary version bytes from: 51 | https://github.com/litecoin-project/litecoin/blob/master-0.8/src/base58.h 52 | 53 | Unofficial extended version bytes from 54 | https://bitcointalk.org/index.php?topic=453395.0 55 | """ 56 | NAME = "Litecoin Test Net" 57 | SCRIPT_ADDRESS = 0xc4 # int(0xc4) = 196 58 | PUBKEY_ADDRESS = 0x6f # int(0x6f) = 111 59 | SECRET_KEY = PUBKEY_ADDRESS + 128 # = int(0xef) = 239 60 | 61 | # Unofficial extended version bytes taken from 62 | # https://bitcointalk.org/index.php?topic=453395.0 63 | EXT_PUBLIC_KEY = 0x0436f6e1 64 | EXT_SECRET_KEY = 0x0436ef7d 65 | 66 | 67 | class DogecoinMainNet(object): 68 | """Dogecoin MainNet version bytes 69 | 70 | Primary version bytes from: 71 | https://github.com/dogecoin/dogecoin/blob/1.5.2/src/base58.h 72 | 73 | Unofficial extended version bytes from 74 | https://bitcointalk.org/index.php?topic=409731 75 | """ 76 | NAME = "Dogecoin Main Net" 77 | SCRIPT_ADDRESS = 0x16 # int(0x16) = 22 78 | PUBKEY_ADDRESS = 0x1e # int(0x1e) = 30 79 | SECRET_KEY = PUBKEY_ADDRESS + 128 # int(0x9e) = 158 80 | 81 | # Unofficial extended version bytes taken from 82 | # https://bitcointalk.org/index.php?topic=409731 83 | EXT_PUBLIC_KEY = 0x02facafd 84 | EXT_SECRET_KEY = 0x02fac398 85 | 86 | 87 | class DogecoinTestNet(object): 88 | """Dogecoin TestNet version bytes 89 | 90 | Primary version bytes from: 91 | https://github.com/dogecoin/dogecoin/blob/1.5.2/src/base58.h 92 | 93 | Unofficial extended version bytes from 94 | https://bitcointalk.org/index.php?topic=409731 95 | """ 96 | NAME = "Dogecoin Test Net" 97 | SCRIPT_ADDRESS = 0xc4 # int(0xc4) = 196 98 | PUBKEY_ADDRESS = 0x71 # int(0x71) = 113 99 | SECRET_KEY = PUBKEY_ADDRESS + 128 # int(0xf1) = 241 100 | 101 | # Unofficial extended version bytes taken from 102 | # https://bitcointalk.org/index.php?topic=409731 103 | EXT_PUBLIC_KEY = 0x0432a9a8 104 | EXT_SECRET_KEY = 0x0432a243 105 | -------------------------------------------------------------------------------- /multimerchant/wallet/__init__.py: -------------------------------------------------------------------------------- 1 | from .bip32 import Wallet 2 | 3 | __all__ = [ 4 | 'Wallet' 5 | ] 6 | -------------------------------------------------------------------------------- /multimerchant/wallet/bip32.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify 2 | from binascii import unhexlify 3 | from hashlib import sha256 4 | from hashlib import sha512 5 | import hmac 6 | 7 | import base58 8 | from os import urandom 9 | from ecdsa import SECP256k1 10 | from ecdsa.ecdsa import Public_key as _ECDSA_Public_key 11 | from ecdsa.ellipticcurve import INFINITY 12 | import six 13 | import time 14 | 15 | # import all the networks 16 | from ..network import BitcoinMainNet 17 | from ..network import BitcoinTestNet 18 | from ..network import DogecoinMainNet 19 | from ..network import DogecoinTestNet 20 | from ..network import LitecoinMainNet 21 | from ..network import LitecoinTestNet 22 | 23 | from .keys import incompatible_network_exception_factory 24 | from .keys import PrivateKey 25 | from .keys import PublicKey 26 | from .keys import PublicPair 27 | from .utils import chr_py2 28 | from .utils import ensure_bytes 29 | from .utils import ensure_str 30 | from .utils import hash160 31 | from .utils import is_hex_string 32 | from .utils import long_or_int 33 | from .utils import long_to_hex 34 | from .utils import memoize 35 | 36 | 37 | class Wallet(object): 38 | """A BIP32 wallet is made up of Wallet nodes. 39 | 40 | A Private node contains both a public and private key, while a public 41 | node contains only a public key. 42 | 43 | **WARNING**: 44 | 45 | When creating a NEW wallet you MUST back up the private key. If 46 | you don't then any coins sent to your address will be LOST FOREVER. 47 | 48 | You need to save the private key somewhere. It is OK to just write 49 | it down on a piece of paper! Don't share this key with anyone! 50 | 51 | >>> my_wallet = Wallet.from_master_secret( 52 | ... key='correct horse battery staple') 53 | >>> private = my_wallet.serialize(private=True) 54 | >>> private # doctest: +ELLIPSIS 55 | u'xprv9s21ZrQH143K2mDJW8vDeFwbyDbFv868mM2Zr87rJSTj8q16Unkaq1pryiV...' 56 | 57 | If you want to use this wallet on your website to accept bitcoin or 58 | altcoin payments, you should first create a primary child. 59 | 60 | BIP32 Hierarchical Deterministic Wallets are described in this BIP: 61 | https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki 62 | """ 63 | def __init__(self, 64 | chain_code, 65 | depth=0, 66 | parent_fingerprint=0, 67 | child_number=0, 68 | private_exponent=None, 69 | private_key=None, 70 | public_pair=None, 71 | public_key=None, 72 | network="bitcoin_testnet"): 73 | """Construct a new BIP32 compliant wallet. 74 | 75 | You probably don't want to use this init methd. Instead use one 76 | of the 'from_master_secret' or 'deserialize' cosntructors. 77 | """ 78 | 79 | if (not (private_exponent or private_key) and 80 | not (public_pair or public_key)): 81 | raise InsufficientKeyDataError( 82 | "You must supply one of private_exponent or public_pair") 83 | 84 | network = Wallet.get_network(network) 85 | self.private_key = None 86 | self.public_key = None 87 | if private_key: 88 | if not isinstance(private_key, PrivateKey): 89 | raise InvalidPrivateKeyError( 90 | "private_key must be of type " 91 | "bitmerchant.wallet.keys.PrivateKey") 92 | self.private_key = private_key 93 | elif private_exponent: 94 | self.private_key = PrivateKey( 95 | private_exponent, network=network) 96 | 97 | if public_key: 98 | if not isinstance(public_key, PublicKey): 99 | raise InvalidPublicKeyError( 100 | "public_key must be of type " 101 | "bitmerchant.wallet.keys.PublicKey") 102 | self.public_key = public_key 103 | elif public_pair: 104 | self.public_key = PublicKey.from_public_pair( 105 | public_pair, network=network) 106 | else: 107 | self.public_key = self.private_key.get_public_key() 108 | 109 | if (self.private_key and self.private_key.get_public_key() != 110 | self.public_key): 111 | raise KeyMismatchError( 112 | "Provided private and public values do not match") 113 | 114 | def h(val, hex_len): 115 | if isinstance(val, six.integer_types): 116 | return long_to_hex(val, hex_len) 117 | elif (isinstance(val, six.string_types) or 118 | isinstance(val, six.binary_type)) and is_hex_string(val): 119 | val = ensure_bytes(val) 120 | if len(val) != hex_len: 121 | raise ValueError("Invalid parameter length") 122 | return val 123 | else: 124 | raise ValueError("Invalid parameter type") 125 | 126 | def l(val): 127 | if isinstance(val, six.integer_types): 128 | return long_or_int(val) 129 | elif (isinstance(val, six.string_types) or 130 | isinstance(val, six.binary_type)): 131 | val = ensure_bytes(val) 132 | if not is_hex_string(val): 133 | val = hexlify(val) 134 | return long_or_int(val, 16) 135 | else: 136 | raise ValueError("parameter must be an int or long") 137 | 138 | self.network = network 139 | self.depth = l(depth) 140 | if (isinstance(parent_fingerprint, six.string_types) or 141 | isinstance(parent_fingerprint, six.binary_type)): 142 | val = ensure_bytes(parent_fingerprint) 143 | if val.startswith(b"0x"): 144 | parent_fingerprint = val[2:] 145 | self.parent_fingerprint = b"0x" + h(parent_fingerprint, 8) 146 | self.child_number = l(child_number) 147 | self.chain_code = h(chain_code, 64) 148 | 149 | def get_private_key_hex(self): 150 | """ 151 | Get the hex-encoded (I guess SEC1?) representation of the private key. 152 | 153 | DO NOT share this private key with anyone. 154 | """ 155 | return ensure_bytes(self.private_key.get_key()) 156 | 157 | def get_public_key_hex(self, compressed=True): 158 | """Get the sec1 representation of the public key.""" 159 | return ensure_bytes(self.public_key.get_key(compressed)) 160 | 161 | @property 162 | def identifier(self): 163 | """Get the identifier for this node. 164 | 165 | Extended keys can be identified by the Hash160 (RIPEMD160 after SHA256) 166 | of the public key's `key`. This corresponds exactly to the data used in 167 | traditional Bitcoin addresses. It is not advised to represent this data 168 | in base58 format though, as it may be interpreted as an address that 169 | way (and wallet software is not required to accept payment to the chain 170 | key itself). 171 | """ 172 | key = self.get_public_key_hex() 173 | return ensure_bytes(hexlify(hash160(unhexlify(key)))) 174 | 175 | @property 176 | def fingerprint(self): 177 | """The first 32 bits of the identifier are called the fingerprint.""" 178 | # 32 bits == 4 Bytes == 8 hex characters 179 | return b'0x' + self.identifier[:8] 180 | 181 | def create_new_address_for_user(self, user_id): 182 | """Create a new bitcoin address to accept payments for a User. 183 | 184 | This is a convenience wrapper around `get_child` that helps you do 185 | the right thing. This method always creates a public, non-prime 186 | address that can be generated from a BIP32 public key on an 187 | insecure server.""" 188 | max_id = 0x80000000 189 | if user_id < 0 or user_id > max_id: 190 | raise ValueError( 191 | "Invalid UserID. Must be between 0 and %s" % max_id) 192 | return self.get_child(user_id, is_prime=False, as_private=False) 193 | 194 | def get_child_for_path(self, path): 195 | """Get a child for a given path. 196 | 197 | Rather than repeated calls to get_child, children can be found 198 | by a derivation path. Paths look like: 199 | 200 | m/0/1'/10 201 | 202 | Which is the same as 203 | 204 | self.get_child(0).get_child(-1).get_child(10) 205 | 206 | Or, in other words, the 10th publicly derived child of the 1st 207 | privately derived child of the 0th publicly derived child of master. 208 | 209 | You can use either ' or p to denote a prime (that is, privately 210 | derived) child. 211 | 212 | A child that has had its private key stripped can be requested by 213 | either passing a capital M or appending '.pub' to the end of the path. 214 | These three paths all give the same child that has had its private 215 | key scrubbed: 216 | 217 | M/0/1 218 | m/0/1.pub 219 | M/0/1.pub 220 | """ 221 | path = ensure_str(path) 222 | 223 | if not path: 224 | raise InvalidPathError("%s is not a valid path" % path) 225 | 226 | # Figure out public/private derivation 227 | as_private = True 228 | if path.startswith("M"): 229 | as_private = False 230 | if path.endswith(".pub"): 231 | as_private = False 232 | path = path[:-4] 233 | 234 | parts = path.split("/") 235 | if len(parts) == 0: 236 | raise InvalidPathError() 237 | 238 | child = self 239 | for part in parts: 240 | if part.lower() == "m": 241 | continue 242 | is_prime = None # Let primeness be figured out by the child number 243 | if part[-1] in "'p": 244 | is_prime = True 245 | part = part.replace("'", "").replace("p", "") 246 | try: 247 | child_number = long_or_int(part) 248 | except ValueError: 249 | raise InvalidPathError("%s is not a valid path" % path) 250 | child = child.get_child(child_number, is_prime) 251 | if not as_private: 252 | return child.public_copy() 253 | return child 254 | 255 | @memoize 256 | def get_child(self, child_number, is_prime=None, as_private=True): 257 | """Derive a child key. 258 | 259 | :param child_number: The number of the child key to compute 260 | :type child_number: int 261 | :param is_prime: If True, the child is calculated via private 262 | derivation. If False, then public derivation is used. If None, 263 | then it is figured out from the value of child_number. 264 | :type is_prime: bool, defaults to None 265 | :param as_private: If True, strips private key from the result. 266 | Defaults to False. If there is no private key present, this is 267 | ignored. 268 | :type as_private: bool 269 | 270 | Positive child_numbers (less than 2,147,483,648) produce publicly 271 | derived children. 272 | 273 | Negative numbers (greater than -2,147,483,648) uses private derivation. 274 | 275 | NOTE: Python can't do -0, so if you want the privately derived 0th 276 | child you need to manually set is_prime=True. 277 | 278 | NOTE: negative numbered children are provided as a convenience 279 | because nobody wants to remember the above numbers. Negative numbers 280 | are considered 'prime children', which is described in the BIP32 spec 281 | as a leading 1 in a 32 bit unsigned int. 282 | 283 | This derivation is fully described at 284 | https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-functions # nopep8 285 | """ 286 | boundary = 0x80000000 287 | 288 | if abs(child_number) >= boundary: 289 | raise ValueError("Invalid child number %s" % child_number) 290 | 291 | # If is_prime isn't set, then we can infer it from the child_number 292 | if is_prime is None: 293 | # Prime children are either < 0 or > 0x80000000 294 | if child_number < 0: 295 | child_number = abs(child_number) 296 | is_prime = True 297 | elif child_number >= boundary: 298 | child_number -= boundary 299 | is_prime = True 300 | else: 301 | is_prime = False 302 | else: 303 | # Otherwise is_prime is set so the child_number should be between 304 | # 0 and 0x80000000 305 | if child_number < 0 or child_number >= boundary: 306 | raise ValueError( 307 | "Invalid child number. Must be between 0 and %s" % 308 | boundary) 309 | 310 | if not self.private_key and is_prime: 311 | raise ValueError( 312 | "Cannot compute a prime child without a private key") 313 | 314 | if is_prime: 315 | # Even though we take child_number as an int < boundary, the 316 | # internal derivation needs it to be the larger number. 317 | child_number = child_number + boundary 318 | child_number_hex = long_to_hex(child_number, 8) 319 | 320 | if is_prime: 321 | # Let data = concat(0x00, self.key, child_number) 322 | data = b'00' + self.private_key.get_key() 323 | else: 324 | data = self.get_public_key_hex() 325 | data += child_number_hex 326 | 327 | # Compute a 64 Byte I that is the HMAC-SHA512, using self.chain_code 328 | # as the seed, and data as the message. 329 | I = hmac.new( 330 | unhexlify(self.chain_code), 331 | msg=unhexlify(data), 332 | digestmod=sha512).digest() 333 | # Split I into its 32 Byte components. 334 | I_L, I_R = I[:32], I[32:] 335 | 336 | if long_or_int(hexlify(I_L), 16) >= SECP256k1.order: 337 | raise InvalidPrivateKeyError("The derived key is too large.") 338 | 339 | c_i = hexlify(I_R) 340 | private_exponent = None 341 | public_pair = None 342 | if self.private_key: 343 | # Use private information for derivation 344 | # I_L is added to the current key's secret exponent (mod n), where 345 | # n is the order of the ECDSA curve in use. 346 | private_exponent = ( 347 | (long_or_int(hexlify(I_L), 16) + 348 | long_or_int(self.private_key.get_key(), 16)) 349 | % SECP256k1.order) 350 | # I_R is the child's chain code 351 | else: 352 | # Only use public information for this derivation 353 | g = SECP256k1.generator 354 | I_L_long = long_or_int(hexlify(I_L), 16) 355 | point = (_ECDSA_Public_key(g, g * I_L_long).point + 356 | self.public_key.to_point()) 357 | # I_R is the child's chain code 358 | public_pair = PublicPair(point.x(), point.y()) 359 | 360 | child = self.__class__( 361 | chain_code=c_i, 362 | depth=self.depth + 1, # we have to go deeper... 363 | parent_fingerprint=self.fingerprint, 364 | child_number=child_number_hex, 365 | private_exponent=private_exponent, 366 | public_pair=public_pair, 367 | network=self.network) 368 | if child.public_key.to_point() == INFINITY: 369 | raise InfinityPointException("The point at infinity is invalid.") 370 | if not as_private: 371 | return child.public_copy() 372 | return child 373 | 374 | def public_copy(self): 375 | """Clone this wallet and strip it of its private information.""" 376 | return self.__class__( 377 | chain_code=self.chain_code, 378 | depth=self.depth, 379 | parent_fingerprint=self.parent_fingerprint, 380 | child_number=self.child_number, 381 | public_pair=self.public_key.to_public_pair(), 382 | network=self.network) 383 | 384 | def crack_private_key(self, child_private_key): 385 | """Crack the parent private key given a child private key. 386 | 387 | BIP32 has a vulnerability/feature that allows you to recover the 388 | master private key if you're given a master public key and any of its 389 | publicly-derived child private keys. This is a pretty serious security 390 | vulnerability that looks as innocuous as this: 391 | 392 | >>> w = Wallet.new_random_wallet() 393 | >>> child = w.get_child(0, is_prime=False) 394 | >>> w_pub = w.public_copy() 395 | >>> assert w_pub.private_key is None 396 | >>> master_public_key = w_pub.serialize_b58(private=False) 397 | >>> # Now you put master_public_key on your website 398 | >>> # and give somebody a private key 399 | >>> public_master = Wallet.deserialize(master_public_key) 400 | >>> cracked_private_master = public_master.crack_private_key(child) 401 | >>> assert w == cracked_private_master # :( 402 | 403 | Implementation details from http://bitcoinmagazine.com/8396/deterministic-wallets-advantages-flaw/ # nopep8 404 | """ 405 | if self.private_key: 406 | raise AssertionError("You already know the private key") 407 | if child_private_key.parent_fingerprint != self.fingerprint: 408 | raise ValueError("This is not a valid child") 409 | if child_private_key.child_number >= 0x80000000: 410 | raise ValueError( 411 | "Cannot crack private keys from private derivation") 412 | 413 | # Duplicate the public child derivation 414 | child_number_hex = long_to_hex(child_private_key.child_number, 8) 415 | data = self.get_public_key_hex() + child_number_hex 416 | I = hmac.new( 417 | unhexlify(self.chain_code), 418 | msg=unhexlify(data), 419 | digestmod=sha512).digest() 420 | I_L, I_R = I[:32], I[32:] 421 | # Public derivation is the same as private derivation plus some offset 422 | # knowing the child's private key allows us to find this offset just 423 | # by subtracting the child's private key from the parent I_L data 424 | privkey = PrivateKey(long_or_int(hexlify(I_L), 16), 425 | network=self.network) 426 | parent_private_key = child_private_key.private_key - privkey 427 | return self.__class__( 428 | chain_code=self.chain_code, 429 | depth=self.depth, 430 | parent_fingerprint=self.parent_fingerprint, 431 | child_number=self.child_number, 432 | private_key=parent_private_key, 433 | network=self.network) 434 | 435 | def export_to_wif(self): 436 | """Export a key to WIF. 437 | 438 | See https://en.bitcoin.it/wiki/Wallet_import_format for a full 439 | description. 440 | """ 441 | # Add the network byte, creating the "extended key" 442 | extended_key_hex = self.private_key.get_extended_key() 443 | # BIP32 wallets have a trailing \01 byte 444 | extended_key_bytes = unhexlify(extended_key_hex) + b'\01' 445 | # And return the base58-encoded result with a checksum 446 | return base58.b58encode_check(extended_key_bytes) 447 | 448 | def serialize(self, private=True): 449 | """Serialize this key. 450 | 451 | :param private: Whether or not the serialized key should contain 452 | private information. Set to False for a public-only representation 453 | that cannot spend funds but can create children. You want 454 | private=False if you are, for example, running an e-commerce 455 | website and want to accept bitcoin payments. See the README 456 | for more information. 457 | :type private: bool, defaults to True 458 | 459 | See the spec in `deserialize` for more details. 460 | """ 461 | if private and not self.private_key: 462 | raise ValueError("Cannot serialize a public key as private") 463 | 464 | if private: 465 | network_version = long_to_hex( 466 | self.network.EXT_SECRET_KEY, 8) 467 | else: 468 | network_version = long_to_hex( 469 | self.network.EXT_PUBLIC_KEY, 8) 470 | depth = long_to_hex(self.depth, 2) 471 | parent_fingerprint = self.parent_fingerprint[2:] # strip leading 0x 472 | child_number = long_to_hex(self.child_number, 8) 473 | chain_code = self.chain_code 474 | ret = (network_version + depth + parent_fingerprint + child_number + 475 | chain_code) 476 | # Private and public serializations are slightly different 477 | if private: 478 | ret += b'00' + self.private_key.get_key() 479 | else: 480 | ret += self.get_public_key_hex(compressed=True) 481 | return ensure_bytes(ret.lower()) 482 | 483 | def serialize_b58(self, private=True): 484 | """Encode the serialized node in base58.""" 485 | return ensure_str( 486 | base58.b58encode_check(unhexlify(self.serialize(private)))) 487 | 488 | def to_address(self): 489 | """Create a public address from this Wallet. 490 | 491 | Public addresses can accept payments. 492 | 493 | https://en.bitcoin.it/wiki/Technical_background_of_Bitcoin_addresses 494 | """ 495 | key = unhexlify(self.get_public_key_hex()) 496 | # First get the hash160 of the key 497 | hash160_bytes = hash160(key) 498 | # Prepend the network address byte 499 | network_hash160_bytes = \ 500 | chr_py2(self.network.PUBKEY_ADDRESS) + hash160_bytes 501 | # Return a base58 encoded address with a checksum 502 | return ensure_str(base58.b58encode_check(network_hash160_bytes)) 503 | 504 | @classmethod 505 | @memoize 506 | def deserialize(cls, key, network="bitcoin_testnet"): 507 | """Load the ExtendedBip32Key from a hex key. 508 | 509 | The key consists of 510 | 511 | * 4 byte version bytes (network key) 512 | * 1 byte depth: 513 | - 0x00 for master nodes, 514 | - 0x01 for level-1 descendants, .... 515 | * 4 byte fingerprint of the parent's key (0x00000000 if master key) 516 | * 4 byte child number. This is the number i in x_i = x_{par}/i, 517 | with x_i the key being serialized. This is encoded in MSB order. 518 | (0x00000000 if master key) 519 | * 32 bytes: the chain code 520 | * 33 bytes: the public key or private key data 521 | (0x02 + X or 0x03 + X for public keys, 0x00 + k for private keys) 522 | (Note that this also supports 0x04 + X + Y uncompressed points, 523 | but this is totally non-standard and this library won't even 524 | generate such data.) 525 | """ 526 | 527 | network = Wallet.get_network(network) 528 | 529 | if len(key) in [78, (78 + 32)]: 530 | # we have a byte array, so pass 531 | pass 532 | else: 533 | key = ensure_bytes(key) 534 | if len(key) in [78 * 2, (78 + 32) * 2]: 535 | # we have a hexlified non-base58 key, continue! 536 | key = unhexlify(key) 537 | elif len(key) == 111: 538 | # We have a base58 encoded string 539 | key = base58.b58decode_check(key) 540 | # Now that we double checkd the values, convert back to bytes because 541 | # they're easier to slice 542 | version, depth, parent_fingerprint, child, chain_code, key_data = ( 543 | key[:4], key[4], key[5:9], key[9:13], key[13:45], key[45:]) 544 | 545 | version_long = long_or_int(hexlify(version), 16) 546 | exponent = None 547 | pubkey = None 548 | point_type = key_data[0] 549 | if not isinstance(point_type, six.integer_types): 550 | point_type = ord(point_type) 551 | if point_type == 0: 552 | # Private key 553 | if version_long != network.EXT_SECRET_KEY: 554 | raise incompatible_network_exception_factory( 555 | network.NAME, network.EXT_SECRET_KEY, 556 | version) 557 | exponent = key_data[1:] 558 | elif point_type in [2, 3, 4]: 559 | # Compressed public coordinates 560 | if version_long != network.EXT_PUBLIC_KEY: 561 | raise incompatible_network_exception_factory( 562 | network.NAME, network.EXT_PUBLIC_KEY, 563 | version) 564 | pubkey = PublicKey.from_hex_key(key_data, network=network) 565 | # Even though this was generated from a compressed pubkey, we 566 | # want to store it as an uncompressed pubkey 567 | pubkey.compressed = False 568 | else: 569 | raise ValueError("Invalid key_data prefix, got %s" % point_type) 570 | 571 | def l(byte_seq): 572 | if byte_seq is None: 573 | return byte_seq 574 | elif isinstance(byte_seq, six.integer_types): 575 | return byte_seq 576 | return long_or_int(hexlify(byte_seq), 16) 577 | 578 | return cls(depth=l(depth), 579 | parent_fingerprint=l(parent_fingerprint), 580 | child_number=l(child), 581 | chain_code=l(chain_code), 582 | private_exponent=l(exponent), 583 | public_key=pubkey, 584 | network=network) 585 | 586 | @classmethod 587 | def from_master_secret(cls, seed, network="bitcoin_testnet"): 588 | """Generate a new PrivateKey from a secret key. 589 | 590 | :param seed: The key to use to generate this wallet. It may be a long 591 | string. Do not use a phrase from a book or song, as that will 592 | be guessed and is not secure. My advice is to not supply this 593 | argument and let me generate a new random key for you. 594 | 595 | See https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format # nopep8 596 | """ 597 | network = Wallet.get_network(network) 598 | seed = ensure_bytes(seed) 599 | # Given a seed S of at least 128 bits, but 256 is advised 600 | # Calculate I = HMAC-SHA512(key="Bitcoin seed", msg=S) 601 | I = hmac.new(b"Bitcoin seed", msg=seed, digestmod=sha512).digest() 602 | # Split I into two 32-byte sequences, IL and IR. 603 | I_L, I_R = I[:32], I[32:] 604 | # Use IL as master secret key, and IR as master chain code. 605 | return cls(private_exponent=long_or_int(hexlify(I_L), 16), 606 | chain_code=long_or_int(hexlify(I_R), 16), 607 | network=network) 608 | 609 | @classmethod 610 | def from_master_secret_slow(cls, password, network=BitcoinMainNet): 611 | """ 612 | Generate a new key from a password using 50,000 rounds of HMAC-SHA256. 613 | 614 | This should generate the same result as bip32.org. 615 | 616 | WARNING: The security of this method has not been evaluated. 617 | """ 618 | # Make sure the password string is bytes 619 | key = ensure_bytes(password) 620 | data = unhexlify(b"0" * 64) # 256-bit 0 621 | for i in range(50000): 622 | data = hmac.new(key, msg=data, digestmod=sha256).digest() 623 | return cls.from_master_secret(data, network) 624 | 625 | def __eq__(self, other): 626 | attrs = [ 627 | 'chain_code', 628 | 'depth', 629 | 'parent_fingerprint', 630 | 'child_number', 631 | 'private_key', 632 | 'public_key', 633 | 'network', 634 | ] 635 | return other and all( 636 | getattr(self, attr) == getattr(other, attr) for attr in attrs) 637 | 638 | def __ne__(self, other): 639 | return not self == other 640 | 641 | __hash__ = object.__hash__ 642 | 643 | @classmethod 644 | def get_network(self, network): 645 | # returns a network class object 646 | 647 | response = None 648 | if network == "bitcoin_testnet" or network == "BTCTEST": 649 | response = BitcoinTestNet 650 | elif network == "bitcoin" or network == "BTC": 651 | response = BitcoinMainNet 652 | elif network == "dogecoin" or network == "DOGE": 653 | response = DogecoinMainNet 654 | elif network == "dogecoin_testnet" or network == "DOGETEST": 655 | response = DogecoinTestNet 656 | elif network == "litecoin" or network == "LTC": 657 | response = LitecoinMainNet 658 | elif network == "litecoin_testnet" or network == "LTCTEST": 659 | response = LitecoinTestNet 660 | else: 661 | response = network 662 | 663 | return response 664 | 665 | @classmethod 666 | def new_random_wallet(cls, user_entropy=None, network=BitcoinMainNet): 667 | """ 668 | Generate a new wallet using a randomly generated 512 bit seed. 669 | 670 | Args: 671 | user_entropy: Optional user-supplied entropy which is combined 672 | combined with the random seed, to help counteract compromised 673 | PRNGs. 674 | 675 | You are encouraged to add an optional `user_entropy` string to protect 676 | against a compromised CSPRNG. This will be combined with the output 677 | from the CSPRNG. Note that if you do supply this value it only adds 678 | additional entropy and will not be sufficient to recover the random 679 | wallet. If you're even saving `user_entropy` at all, you're doing it 680 | wrong. 681 | """ 682 | seed = str(urandom(64)) # 512/8 683 | # weak extra protection inspired by pybitcointools implementation: 684 | seed += str(int(time.time()*10**6)) 685 | if user_entropy: 686 | user_entropy = str(user_entropy) # allow for int/long 687 | seed += user_entropy 688 | return cls.from_master_secret(seed, network=network) 689 | 690 | 691 | class InvalidPathError(Exception): 692 | pass 693 | 694 | 695 | class InsufficientKeyDataError(ValueError): 696 | pass 697 | 698 | 699 | class InvalidPrivateKeyError(ValueError): 700 | pass 701 | 702 | 703 | class InvalidPublicKeyError(ValueError): 704 | pass 705 | 706 | 707 | class KeyMismatchError(ValueError): 708 | pass 709 | 710 | 711 | class InfinityPointException(Exception): 712 | pass 713 | -------------------------------------------------------------------------------- /multimerchant/wallet/keys.py: -------------------------------------------------------------------------------- 1 | from binascii import hexlify 2 | from binascii import unhexlify 3 | from collections import namedtuple 4 | from hashlib import sha256 5 | 6 | import base58 7 | from ecdsa import SigningKey 8 | from ecdsa import VerifyingKey 9 | from ecdsa import SECP256k1 10 | from ecdsa.ellipticcurve import Point as _ECDSA_Point 11 | from ecdsa.numbertheory import square_root_mod_prime 12 | import six 13 | 14 | from ..network import BitcoinMainNet 15 | from .utils import chr_py2 16 | from .utils import ensure_bytes 17 | from .utils import ensure_str 18 | from .utils import hash160 19 | from .utils import is_hex_string 20 | from .utils import long_or_int 21 | from .utils import long_to_hex 22 | from .utils import memoize 23 | 24 | 25 | PublicPair = namedtuple("PublicPair", ["x", "y"]) 26 | 27 | 28 | class Key(object): 29 | def __init__(self, network, compressed=False): 30 | """Construct a Key.""" 31 | # Set network first because set_key needs it 32 | self.network = network 33 | self.compressed = compressed 34 | 35 | def __eq__(self, other): 36 | return (other and 37 | self.network == other.network and 38 | type(self) == type(other)) 39 | 40 | def __ne__(self, other): 41 | return not self == other 42 | 43 | __hash__ = object.__hash__ 44 | 45 | def get_key(self): 46 | raise NotImplementedError() 47 | 48 | 49 | class PrivateKey(Key): 50 | def __init__(self, secret_exponent, network=BitcoinMainNet, 51 | *args, **kwargs): 52 | if not isinstance(secret_exponent, six.integer_types): 53 | raise ValueError("secret_exponent must be a long") 54 | super(PrivateKey, self).__init__(network=network, *args, **kwargs) 55 | self._private_key = SigningKey.from_secret_exponent( 56 | secret_exponent, curve=SECP256k1) 57 | 58 | def get_key(self): 59 | """Get the key - a hex formatted private exponent for the curve.""" 60 | return ensure_bytes(hexlify(self._private_key.to_string())) 61 | 62 | @memoize 63 | def get_public_key(self): 64 | """Get the PublicKey for this PrivateKey.""" 65 | return PublicKey.from_verifying_key( 66 | self._private_key.get_verifying_key(), 67 | network=self.network, compressed=self.compressed) 68 | 69 | def get_extended_key(self): 70 | """Get the extended key. 71 | 72 | Extended keys contain the network bytes and the public or private 73 | key. 74 | """ 75 | network_hex_chars = hexlify( 76 | chr_py2(self.network.SECRET_KEY)) 77 | return ensure_bytes(network_hex_chars + self.get_key()) 78 | 79 | def export_to_wif(self, compressed=None): 80 | """Export a key to WIF. 81 | 82 | :param compressed: False if you want a standard WIF export (the most 83 | standard option). True if you want the compressed form (Note that 84 | not all clients will accept this form). Defaults to None, which 85 | in turn uses the self.compressed attribute. 86 | :type compressed: bool 87 | See https://en.bitcoin.it/wiki/Wallet_import_format for a full 88 | description. 89 | """ 90 | # Add the network byte, creating the "extended key" 91 | extended_key_hex = self.get_extended_key() 92 | extended_key_bytes = unhexlify(extended_key_hex) 93 | if compressed is None: 94 | compressed = self.compressed 95 | if compressed: 96 | extended_key_bytes += '\01' 97 | # And return the base58-encoded result with a checksum 98 | return ensure_str(base58.b58encode_check(extended_key_bytes)) 99 | 100 | def _public_child(child_number): 101 | raise NotImplementedError() 102 | 103 | @classmethod 104 | def from_wif(cls, wif, network=BitcoinMainNet): 105 | """Import a key in WIF format. 106 | 107 | WIF is Wallet Import Format. It is a base58 encoded checksummed key. 108 | See https://en.bitcoin.it/wiki/Wallet_import_format for a full 109 | description. 110 | 111 | This supports compressed WIFs - see this for an explanation: 112 | http://bitcoin.stackexchange.com/questions/7299/when-importing-private-keys-will-compressed-or-uncompressed-format-be-used # nopep8 113 | (specifically http://bitcoin.stackexchange.com/a/7958) 114 | """ 115 | # Decode the base58 string and ensure the checksum is valid 116 | wif = ensure_str(wif) 117 | try: 118 | extended_key_bytes = base58.b58decode_check(wif) 119 | except ValueError as e: 120 | # Invalid checksum! 121 | raise ChecksumException(e) 122 | 123 | # Verify we're on the right network 124 | network_bytes = extended_key_bytes[0] 125 | # py3k interprets network_byte as an int already 126 | if not isinstance(network_bytes, six.integer_types): 127 | network_bytes = ord(network_bytes) 128 | if (network_bytes != network.SECRET_KEY): 129 | raise incompatible_network_exception_factory( 130 | network_name=network.NAME, 131 | expected_prefix=network.SECRET_KEY, 132 | given_prefix=network_bytes) 133 | 134 | # Drop the network bytes 135 | extended_key_bytes = extended_key_bytes[1:] 136 | 137 | # Check for comprssed public key 138 | # This only affects the way in which addresses are generated. 139 | compressed = False 140 | if len(extended_key_bytes) == 33: 141 | # We are supposed to use compressed form! 142 | extended_key_bytes = extended_key_bytes[:-1] 143 | compressed = True 144 | 145 | # And we should finally have a valid key 146 | return cls(long_or_int(hexlify(extended_key_bytes), 16), network, 147 | compressed=compressed) 148 | 149 | @classmethod 150 | def from_hex_key(cls, key, network=BitcoinMainNet): 151 | if len(key) == 32: 152 | # Oh! we have bytes instead of a hex string 153 | key = hexlify(key) 154 | key = ensure_bytes(key) 155 | if not is_hex_string(key) or len(key) != 64: 156 | raise ValueError("Invalid hex key") 157 | return cls(long_or_int(key, 16), network) 158 | 159 | @classmethod 160 | def from_master_password(cls, password, network=BitcoinMainNet): 161 | """Generate a new key from a master password. 162 | 163 | This password is hashed via a single round of sha256 and is highly 164 | breakable, but it's the standard brainwallet approach. 165 | 166 | See `PrivateKey.from_master_password_slow` for a slightly more 167 | secure generation method (which will still be subject to a rainbow 168 | table attack :\) 169 | """ 170 | password = ensure_bytes(password) 171 | key = sha256(password).hexdigest() 172 | return cls.from_hex_key(key, network) 173 | 174 | __hash__ = Key.__hash__ 175 | 176 | def __eq__(self, other): 177 | return (super(PrivateKey, self).__eq__(other) and 178 | self._private_key.curve == other._private_key.curve and 179 | (self._private_key.to_string() == 180 | other._private_key.to_string()) and 181 | (self._private_key.privkey.secret_multiplier == 182 | other._private_key.privkey.secret_multiplier) and 183 | self.get_public_key() == other.get_public_key()) 184 | 185 | def __sub__(self, other): 186 | assert isinstance(other, self.__class__) 187 | assert self.network == other.network 188 | k1 = self._private_key.privkey.secret_multiplier 189 | k2 = other._private_key.privkey.secret_multiplier 190 | result = (k1 - k2) % SECP256k1.order 191 | return self.__class__(result, network=self.network) 192 | 193 | 194 | class PublicKey(Key): 195 | def __init__(self, verifying_key, network=BitcoinMainNet, *args, **kwargs): 196 | """Create a public key. 197 | 198 | :param verifying_key: The ECDSA VerifyingKey corresponding to this 199 | public key. 200 | :type verifying_key: ecdsa.VerifyingKey 201 | :param network: The network you want (Networks just define certain 202 | constants, like byte-prefixes on public addresses). 203 | :type network: See `bitmerchant.wallet.network` 204 | """ 205 | super(PublicKey, self).__init__(network=network, *args, **kwargs) 206 | self._verifying_key = verifying_key 207 | self.x = verifying_key.pubkey.point.x() 208 | self.y = verifying_key.pubkey.point.y() 209 | 210 | def get_key(self, compressed=None): 211 | """Get the hex-encoded key. 212 | 213 | :param compressed: False if you want a standard 65 Byte key (the most 214 | standard option). True if you want the compressed 33 Byte form. 215 | Defaults to None, which in turn uses the self.compressed attribute. 216 | :type compressed: bool 217 | 218 | PublicKeys consist of an ID byte, the x, and the y coordinates 219 | on the elliptic curve. 220 | 221 | In the case of uncompressed keys, the ID byte is 04. 222 | Compressed keys use the SEC1 format: 223 | If Y is odd: id_byte = 03 224 | else: id_byte = 02 225 | 226 | Note that I pieced this algorithm together from the pycoin source. 227 | 228 | This is documented in http://www.secg.org/collateral/sec1_final.pdf 229 | but, honestly, it's pretty confusing. 230 | 231 | I guess this is a pretty big warning that I'm not *positive* this 232 | will do the right thing in all cases. The tests pass, and this does 233 | exactly what pycoin does, but I'm not positive pycoin works either! 234 | """ 235 | if compressed is None: 236 | compressed = self.compressed 237 | if compressed: 238 | parity = 2 + (self.y & 1) # 0x02 even, 0x03 odd 239 | return ensure_bytes( 240 | long_to_hex(parity, 2) + 241 | long_to_hex(self.x, 64)) 242 | else: 243 | return ensure_bytes( 244 | b'04' + 245 | long_to_hex(self.x, 64) + 246 | long_to_hex(self.y, 64)) 247 | 248 | @classmethod 249 | def from_hex_key(cls, key, network=BitcoinMainNet): 250 | """Load the PublicKey from a compressed or uncompressed hex key. 251 | 252 | This format is defined in PublicKey.get_key() 253 | """ 254 | if len(key) == 130 or len(key) == 66: 255 | # It might be a hexlified byte array 256 | try: 257 | key = unhexlify(key) 258 | except TypeError: 259 | pass 260 | key = ensure_bytes(key) 261 | 262 | compressed = False 263 | id_byte = key[0] 264 | if not isinstance(id_byte, six.integer_types): 265 | id_byte = ord(id_byte) 266 | if id_byte == 4: 267 | # Uncompressed public point 268 | # 1B ID + 32B x coord + 32B y coord = 65 B 269 | if len(key) != 65: 270 | raise KeyParseError("Invalid key length") 271 | public_pair = PublicPair( 272 | long_or_int(hexlify(key[1:33]), 16), 273 | long_or_int(hexlify(key[33:]), 16)) 274 | elif id_byte in [2, 3]: 275 | # Compressed public point! 276 | compressed = True 277 | if len(key) != 33: 278 | raise KeyParseError("Invalid key length") 279 | y_odd = bool(id_byte & 0x01) # 0 even, 1 odd 280 | x = long_or_int(hexlify(key[1:]), 16) 281 | # The following x-to-pair algorithm was lifted from pycoin 282 | # I still need to sit down an understand it. It is also described 283 | # in http://www.secg.org/collateral/sec1_final.pdf 284 | curve = SECP256k1.curve 285 | p = curve.p() 286 | # For SECP256k1, curve.a() is 0 and curve.b() is 7, so this is 287 | # effectively (x ** 3 + 7) % p, but the full equation is kept 288 | # for just-in-case-the-curve-is-broken future-proofing 289 | alpha = (pow(x, 3, p) + curve.a() * x + curve.b()) % p 290 | beta = square_root_mod_prime(alpha, p) 291 | y_even = not y_odd 292 | if y_even == bool(beta & 1): 293 | public_pair = PublicPair(x, p - beta) 294 | else: 295 | public_pair = PublicPair(x, beta) 296 | else: 297 | raise KeyParseError("The given key is not in a known format.") 298 | return cls.from_public_pair(public_pair, network=network, 299 | compressed=compressed) 300 | 301 | @memoize 302 | def create_point(self, x, y): 303 | """Create an ECDSA point on the SECP256k1 curve with the given coords. 304 | 305 | :param x: The x coordinate on the curve 306 | :type x: long 307 | :param y: The y coodinate on the curve 308 | :type y: long 309 | """ 310 | if (not isinstance(x, six.integer_types) or 311 | not isinstance(y, six.integer_types)): 312 | raise ValueError("The coordinates must be longs.") 313 | return _ECDSA_Point(SECP256k1.curve, x, y) 314 | 315 | def to_point(self): 316 | return self._verifying_key.pubkey.point 317 | 318 | @classmethod 319 | def from_point(cls, point, network=BitcoinMainNet, **kwargs): 320 | """Create a PublicKey from a point on the SECP256k1 curve. 321 | 322 | :param point: A point on the SECP256k1 curve. 323 | :type point: SECP256k1.point 324 | """ 325 | verifying_key = VerifyingKey.from_public_point(point, curve=SECP256k1) 326 | return cls.from_verifying_key(verifying_key, network=network, **kwargs) 327 | 328 | @classmethod 329 | def from_verifying_key( 330 | cls, verifying_key, network=BitcoinMainNet, **kwargs): 331 | return cls(verifying_key, network=network, **kwargs) 332 | 333 | def to_address(self, compressed=None): 334 | """Create a public address from this key. 335 | 336 | :param compressed: False if you want a normal uncompressed address 337 | (the most standard option). True if you want the compressed form. 338 | Note that most clients will not accept compressed addresses. 339 | Defaults to None, which in turn uses the self.compressed attribute. 340 | :type compressed: bool 341 | 342 | https://en.bitcoin.it/wiki/Technical_background_of_Bitcoin_addresses 343 | """ 344 | key = unhexlify(self.get_key(compressed)) 345 | # First get the hash160 of the key 346 | hash160_bytes = hash160(key) 347 | # Prepend the network address byte 348 | network_hash160_bytes = \ 349 | chr_py2(self.network.PUBKEY_ADDRESS) + hash160_bytes 350 | # Return a base58 encoded address with a checksum 351 | return ensure_str(base58.b58encode_check(network_hash160_bytes)) 352 | 353 | def to_public_pair(self): 354 | return PublicPair(self.x, self.y) 355 | 356 | @classmethod 357 | def from_public_pair(cls, pair, network=BitcoinMainNet, **kwargs): 358 | point = _ECDSA_Point(SECP256k1.curve, pair.x, pair.y) 359 | return cls.from_point(point, network=network, **kwargs) 360 | 361 | def __eq__(self, other): 362 | return (super(PublicKey, self).__eq__(other) and 363 | self.x == other.x and 364 | self.y == other.y) 365 | 366 | __hash__ = Key.__hash__ 367 | 368 | 369 | class KeyParseError(Exception): 370 | pass 371 | 372 | 373 | def incompatible_network_exception_factory( 374 | network_name, expected_prefix, given_prefix): 375 | return IncompatibleNetworkException( 376 | "Incorrect network. {net_name} expects a byte prefix of " 377 | "{expected_prefix}, but you supplied {given_prefix}".format( 378 | net_name=network_name, 379 | expected_prefix=expected_prefix, 380 | given_prefix=given_prefix)) 381 | 382 | 383 | class ChecksumException(Exception): 384 | pass 385 | 386 | 387 | class IncompatibleNetworkException(Exception): 388 | pass 389 | 390 | 391 | class InvalidChildException(Exception): 392 | pass 393 | -------------------------------------------------------------------------------- /multimerchant/wallet/utils.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import hashlib 3 | from hashlib import sha256 4 | import re 5 | 6 | import six 7 | 8 | if six.PY3: 9 | long = int 10 | 11 | 12 | def ensure_bytes(data): 13 | if not isinstance(data, six.binary_type): 14 | return data.encode('utf-8') 15 | return data 16 | 17 | 18 | def ensure_str(data): 19 | if isinstance(data, six.binary_type): 20 | return data.decode('utf-8') 21 | elif not isinstance(data, six.string_types): 22 | raise ValueError("Invalid value for string") 23 | return data 24 | 25 | 26 | def chr_py2(num): 27 | """Ensures that python3's chr behavior matches python2.""" 28 | if six.PY3: 29 | return bytes([num]) 30 | return chr(num) 31 | 32 | 33 | def hash160(data): 34 | """Return ripemd160(sha256(data))""" 35 | rh = hashlib.new('ripemd160', sha256(data).digest()) 36 | return rh.digest() 37 | 38 | 39 | def is_hex_string(string): 40 | """Check if the string is only composed of hex characters.""" 41 | pattern = re.compile(r'[A-Fa-f0-9]+') 42 | if isinstance(string, six.binary_type): 43 | string = str(string) 44 | return pattern.match(string) is not None 45 | 46 | 47 | def long_to_hex(l, size): 48 | """Encode a long value as a hex string, 0-padding to size. 49 | 50 | Note that size is the size of the resulting hex string. So, for a 32Byte 51 | long size should be 64 (two hex characters per byte".""" 52 | f_str = "{0:0%sx}" % size 53 | return ensure_bytes(f_str.format(l).lower()) 54 | 55 | 56 | def long_or_int(val, *args): 57 | return long(val, *args) 58 | 59 | 60 | def memoize(f): 61 | """Memoization decorator for a function taking one or more arguments.""" 62 | def _c(*args, **kwargs): 63 | if not hasattr(f, 'cache'): 64 | f.cache = dict() 65 | key = (args, tuple(kwargs)) 66 | if key not in f.cache: 67 | f.cache[key] = f(*args, **kwargs) 68 | return f.cache[key] 69 | return wraps(f)(_c) 70 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup 4 | 5 | 6 | def load_readme(): 7 | PROJECT_DIR = os.path.dirname(__file__) 8 | readme_file = "README.rst" 9 | try: 10 | return open(os.path.join(PROJECT_DIR, readme_file), 'r').read() 11 | except Exception: 12 | raise RuntimeError("Cannot find readme file {fname}.".format( 13 | fname=readme_file)) 14 | 15 | 16 | def load_version(): 17 | """Open and parse out the version number from the _version.py module. 18 | 19 | Inspired by http://stackoverflow.com/a/7071358 20 | """ 21 | import re 22 | version_file = "multimerchant/_version.py" 23 | version_line = open(version_file).read().rstrip() 24 | vre = re.compile(r'__version__ = "([^"]+)"') 25 | matches = vre.findall(version_line) 26 | if matches and len(matches) > 0: 27 | return matches[0] 28 | else: 29 | raise RuntimeError( 30 | "Cannot find version string in {version_file}.".format( 31 | version_file=version_file)) 32 | 33 | version = load_version() 34 | long_description = load_readme() 35 | 36 | setup( 37 | name='multimerchant', 38 | version=version, 39 | description="Bitcoin/Dogecoin/Litecoin merchant tools that work with Block.io", 40 | long_description=long_description, 41 | author='Steven Buss', 42 | author_email='steven.buss@gmail.com', 43 | url='https://github.com/blockio/multimerchant-python', 44 | download_url=( 45 | 'https://github.com/blockio/multimerchant-python/tarball/v%s' % version), 46 | classifiers=[ 47 | "Development Status :: 3 - Alpha", 48 | "Intended Audience :: Developers", 49 | "License :: OSI Approved :: MIT License", 50 | "Operating System :: OS Independent", 51 | "Programming Language :: Python", 52 | "Programming Language :: Python :: 2.6", 53 | "Programming Language :: Python :: 2.7", 54 | "Programming Language :: Python :: 3", 55 | "Programming Language :: Python :: 3.3", 56 | ], 57 | packages=[ 58 | 'multimerchant', 59 | 'multimerchant.wallet', 60 | ], 61 | package_data={'': ['AUTHORS', 'LICENSE']}, 62 | include_package_data=True, 63 | license='MIT License', 64 | test_suite="tests", 65 | install_requires=[ 66 | 'base58==0.2.1', 67 | 'ecdsa==0.11', 68 | 'six>=1.8.0', 69 | 'block-io>=1.1.2' 70 | ], 71 | ) 72 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockIo/multimerchant-python/a1a8d48fc49aaaa46449f5aef1d9beb8e49f1c4d/tests/__init__.py -------------------------------------------------------------------------------- /tests/generate_bip32_test_vectors.py: -------------------------------------------------------------------------------- 1 | """Generate BIP32 wallet test vectors from pycoin""" 2 | import argparse 3 | from binascii import hexlify 4 | from hashlib import sha256 5 | import json 6 | import random 7 | 8 | from pycoin.wallet import Wallet 9 | 10 | 11 | def dump_node(node): 12 | return { 13 | 'private_key': node.wallet_key(True), 14 | 'wif': node.wif(), 15 | 'public_key': node.wallet_key(False), 16 | 'chain_code': hexlify(node.chain_code), 17 | 'fingerprint': hexlify(node.fingerprint()), 18 | 'depth': node.depth, 19 | 'secret_exponent': node.secret_exponent, 20 | } 21 | 22 | 23 | def get_new_address(wallet_num): 24 | passphrase = sha256(b"%s" % random.randint(0, 2**30)).hexdigest() 25 | wallet = Wallet.from_master_secret(passphrase) 26 | ret = dump_node(wallet) 27 | children = [] 28 | # Now build up some random paths 29 | # Just go five deep 30 | path = "m" 31 | for depth in range(5): 32 | child_number = random.randint(0, 0x80000000) 33 | path = "%s/%s" % (path, child_number) 34 | prime = random.choice([True, False]) 35 | if prime: 36 | path += "'" 37 | children.append( 38 | {"path": path, 39 | "child": dump_node(wallet.subkey_for_path(path[2:]))}) 40 | ret['children'] = children 41 | return ret 42 | 43 | 44 | def generate_address_vector(outfile, num_addresses, seed): 45 | if args.seed: 46 | random.seed(args.seed) 47 | with open(outfile, 'w') as f: 48 | f.write("[\n") 49 | for i in range(num_addresses): 50 | f.write(json.dumps(get_new_address(i))) 51 | if i < (num_addresses - 1): 52 | f.write(",") 53 | f.write("\n") 54 | f.write("]") 55 | 56 | 57 | if __name__ == "__main__": 58 | parser = argparse.ArgumentParser( 59 | description="Generate test vectors for pub/private key validation") 60 | parser.add_argument("-o", "--output", help="output file path", 61 | default="tests/bip32_test_vector.json") 62 | parser.add_argument("-n", "--num-keys", type=int, default=100, 63 | help="Number of keys to generate") 64 | parser.add_argument("-s", "--seed", type=int, default=1234, 65 | help="The random seed for random wallets. Optional.") 66 | args = parser.parse_args() 67 | generate_address_vector( 68 | outfile=args.output, num_addresses=args.num_keys, seed=args.seed) 69 | -------------------------------------------------------------------------------- /tests/generate_test_keys.py: -------------------------------------------------------------------------------- 1 | """Generate a JSON dump of keys for validating multimerchant.wallet.keys. 2 | 3 | First install bitcoind and run 4 | 5 | $ touch fakewallet.dat 6 | $ bitcoind --rpcuser=bitcoinrpc --rpcpassword=multimerchanttest --server --wallet=fakewallet.dat 7 | $ python generate_test_keys.py -o test_keys.json 8 | """ 9 | import argparse 10 | import json 11 | import pyjsonrpc 12 | 13 | client = pyjsonrpc.HttpClient( 14 | 'http://localhost:8332', 15 | username='bitcoinrpc', 16 | password='multimerchanttest') 17 | 18 | 19 | def get_new_address(): 20 | address = client.getnewaddress() 21 | private_key = client.dumpprivkey(address) 22 | address_info = client.validateaddress(address) 23 | assert address_info['isvalid'] 24 | data = { 25 | 'private_key': private_key, 26 | 'iscompressed': address_info['iscompressed'], 27 | 'address': address, 28 | 'pubkey': address_info['pubkey'], 29 | } 30 | return data 31 | 32 | 33 | def generate_address_vector(outfile, num_addresses): 34 | with open(outfile, 'w') as f: 35 | f.write("[\n") 36 | for i in range(num_addresses): 37 | f.write(json.dumps(get_new_address())) 38 | if i < (num_addresses - 1): 39 | f.write(",") 40 | f.write("\n") 41 | f.write("]") 42 | 43 | 44 | if __name__ == "__main__": 45 | parser = argparse.ArgumentParser( 46 | description="Generate test vectors for pub/private key validation") 47 | parser.add_argument("-o", "--output", help="output file path", 48 | default="tests/keys_test_vector.json") 49 | parser.add_argument("-n", "--num-keys", type=int, default=1000, 50 | help="Number of keys to generate") 51 | args = parser.parse_args() 52 | generate_address_vector(outfile=args.output, num_addresses=args.num_keys) 53 | -------------------------------------------------------------------------------- /tests/keys_test_vector.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"private_key": "L35T5UPHzyhFQwEKSar2ZhK9KPAw9WE5tHuYcuv8qorAtUutjPT2", "iscompressed": true, "pubkey": "0291a16d88f90816295ad7cc66e1c59f92d7ecc808f53919f205f1dee73a61216f", "address": "16MmVyVy3ppBwpyPoCDQMkXwFjyJXQaWCf"}, 3 | {"private_key": "L45RRUdNcQhw2diZ6MUtiip9S4WDT2byGJUvKwy5byrZkBA1GAp6", "iscompressed": true, "pubkey": "030d465480a3b14f23d0b0cb5bd1e1b238b221009da818260aab6f975a93ceaff9", "address": "1KBCqrwmFFdPbJACDjnaaTXZC5AGJYJdMy"}, 4 | {"private_key": "L4RG1Dimjgmt4XsgKqpbKJxRqQP6NsneEpHaeK7k16NdM52h42aN", "iscompressed": true, "pubkey": "034bddeb6a5e81ddfcce67549161697c1c2075f3acc9f96e5f5b45c210e6cb14ca", "address": "17UpNAtT3bTUJXRgjGymSXYezgkDNrUS5w"}, 5 | {"private_key": "KzvyXhLHNWkgTJgGzveEYiz83WxzzUg2quosH5MWPrJZSoRb8S7C", "iscompressed": true, "pubkey": "03816fcf5a4af65ddefdef8ee2e489544de3ce02889f33d36241c23731e38adf04", "address": "1PWwNeQTA23cnYHgDgZgwrWqMUqnmSXTvK"}, 6 | {"private_key": "KzEG8F2Ge6tjQ8aYsVDFwZtzRsdbHqevpyZRqh97Pqm8axe3jQ2C", "iscompressed": true, "pubkey": "03c4965093b05cfa15d9c3db53f6e60eece906437249858a07f065ddf406b13822", "address": "1FNWbpLgeFm2JnCM8hZADAbuAeWUitPyVa"}, 7 | {"private_key": "Kypp9qEkeRdJ1APSYFRpwtb8mK9p5GyPWSwY6EZQsJ3sidGfwVfN", "iscompressed": true, "pubkey": "03f6d1ec6f72e93aae79b5a3d4d0e2e6799521a2502e49b6622040f47a80009b2d", "address": "1GRu7Fwrp9xUeJw7LcuyeAn2xUk9FABnzJ"}, 8 | {"private_key": "L1cGmqQhLBKZGo6xkp3Gdkkre1mvDb2VGf8jK1MN56Nxjz71QUKn", "iscompressed": true, "pubkey": "03791ad2860fba25fb029929e5d917a2b6a69ea4117f44bc5b82c64b9a1ef3aca0", "address": "1FgAukSZRWqFMLwxctYKTBFfFi2rrkMPPc"}, 9 | {"private_key": "L2KNPpcCtC8LxgXuKTcsoDAYx3YYDRMLkTzAz6dUVyULYNsxD17e", "iscompressed": true, "pubkey": "0319d15dd885c1cd82ae587f879070a52b6ab63ec360fb7824f6af5784caa73683", "address": "12nm8kSNTrg7etVmWvYJtSzkSVwF3BERa3"}, 10 | {"private_key": "L4v5wAv8S255jRGFj3KhiAGp9wPVubnWNEFYQMCHLZvu6KzbEiev", "iscompressed": true, "pubkey": "021158989d9872d5892fab347f19fff57d970f213c2223bb52baa534f61e2b13e5", "address": "1H2diCv84bS35B1yxBdW2rGJqMVa9HDteA"}, 11 | {"private_key": "KxtoopvquMzMfkaExVWqMFyqa8oBqMV7TDGAqLYZ2MUWG57659kU", "iscompressed": true, "pubkey": "02b362b8156a9ef2e737d59f88fcd309b4f437f58a56bdcb167def9130bca984f6", "address": "1GFqCLBTZf96yyhM1Quk1MN68PNxK3xtJc"}, 12 | {"private_key": "L4yzGcgEcRAqA7JPRsmV4VPTNAuGx9iSogSHZKPkc3gsRTfJPwCE", "iscompressed": true, "pubkey": "02fba8987ed8c5b5a385c18d099b44b017c402c0888eb4f667a764acfac00b4fbe", "address": "15pJRx5zaUzEz88L6pEKoh7Qg9SfRN9fYv"}, 13 | {"private_key": "L25fokTXh1oZ1qcbFUevjVVe7YsnxGZmnoecbnzG3d3qq3i8Kn5S", "iscompressed": true, "pubkey": "0276a0e281d2c1873868a15df74773e940e735bd0392189f6624d45a1a11852dbd", "address": "1KTJsWdMXU7EfMnEmhh46ML5bWxKr8PU9r"}, 14 | {"private_key": "L1vexhCaMcP8CJNRo9dszkDuQB4mtYYzc5DR8iZAab3FTfoqGN3g", "iscompressed": true, "pubkey": "022ea30bb8ac22883b5474644fe58e762ea03f063adaeeb0d90dc8ab4eb4a0115d", "address": "1Afwvd6tbJk5t8U2LM7e4qp3jKiCqqVzKF"}, 15 | {"private_key": "L2PxwCkV3XZ13Jb24DszsepuKgGngHTtcx6cdHvw76CFSAAopdox", "iscompressed": true, "pubkey": "0295c4618db4caa1c20f2b605a560a852301ad942c48168ca806131351a4b682c6", "address": "1LDNdcp254CF513i6LNkJXJ7L8aC6zMyC3"}, 16 | {"private_key": "Kws5Ybx8gPPXxYCxenpEBfjDfmwSNLhxn2MBimDV9hbEYraDhBYR", "iscompressed": true, "pubkey": "036416efa4e84c79525dd4436c5a728e66a07a3cc76a4284b32b71beeb0f15fd70", "address": "1CJaTbstUszdwbdbTQ4Bq3eJYLZDEWgKUM"}, 17 | {"private_key": "L4sa1zTCmakt7BLRzMn2HyedRJcWYJaVeLnw21vB1N855gFCexgT", "iscompressed": true, "pubkey": "037df6cd4d139615cf8344c3e513b96c9b979bdfb3e6c94acf8cb4d306efdfb21b", "address": "1ATY3TyCnuTSX7Akojs8rokhvmjxqejzEW"}, 18 | {"private_key": "L2HJYwtjGNqrpzEZNu7SqXssq86N5wV4tjmoZMWvDRRmf1VhkvCM", "iscompressed": true, "pubkey": "02f37a14ad62316579b440e319707709c5cf484a17681ef38b1e1feb83deb59420", "address": "1FoBoYFBsMFZpZE1nkJ2wwQXgWGoE3F8b9"}, 19 | {"private_key": "L1H2PSppESpUpPSVBAEk8881CRRFoJVyHaPr8j1UnbRBLwZ6G2Hx", "iscompressed": true, "pubkey": "038e93ee920d25daf2dcfb742304d04834400641aa32940d11773946e787c3890a", "address": "1LWCmqx5XSEnVMtdH9LfMVqDxa1Hcms6Bt"}, 20 | {"private_key": "L2L5fPQ29yiyBDt8Mh2XdsTc9EWf3ABTk5N9dZmhifJmjgGwhYpH", "iscompressed": true, "pubkey": "021b9cc01ca164a94e515f878b371a1a88041666b7217da3b62d45d8ebcf2bdb95", "address": "15w9oj6bK5zVwd4XLSBA2WT8qEyP4HCmME"}, 21 | {"private_key": "KyLfDgPTby735AnXQD9td4QBBNiJJu7bPiiLQX3qAH9xKCWXZUhd", "iscompressed": true, "pubkey": "03fa060b4f01b813bdeef0666520d800a767c1c7d45ca34d8c67cf5e3a084b3d7d", "address": "1NGprAdJnERDwZmJj4jxd56R5Y8bewP6Xq"}, 22 | {"private_key": "L4d7XP9p9kSbtTd5yp2bjDzV2YxzhbeUhkP1gnDDFYRrPoAxFb7d", "iscompressed": true, "pubkey": "03d8508b14b8f481ca42d8687544ede677b40609a984d40222414311fd1831d6fa", "address": "1ANuxsKuiegKVGuQBE2EoGnPWWAcugW3qt"}, 23 | {"private_key": "L3stGFUByJodozRmP4yLMDKz497f7YxNgkhos3R4gDDoY8sL9sMY", "iscompressed": true, "pubkey": "024868f8a7b93f4655b8f7ab74305acd082ed6f18ac18db0051e0a320c933ba94b", "address": "19EfYVgkvjYCen5hXkFXwYY5kwvUZ4zWrJ"}, 24 | {"private_key": "KxQv9FrRkATNiUDJZ3r61TWbc5bmBw25RxxGYMDJ11ygX1PCYUXb", "iscompressed": true, "pubkey": "02ee3bc794fba41d3a66ef3b1790a1a5e7e9e32773cc3afae7e9e3cae954a85334", "address": "1MkAdAfjCN65mMjH1AzXW6Bmz32eDhLhUq"}, 25 | {"private_key": "KxHcZWHVKknFbasnJNjujxEZPWqnh5HYvcj1BmMZzVSX3et6aX6H", "iscompressed": true, "pubkey": "037471c82220ae1fb6302664aebeb05109688448d2eb6945ca9521803742e2421d", "address": "1ANXjhZtqFxaUYHGrPntvXFWx2hD96wpLq"}, 26 | {"private_key": "Kxx9LXEe9VMhqhhdNZgVXZMcjtC3hxM6EavLGw6b9oy6E8R7yRvC", "iscompressed": true, "pubkey": "03df4b0ad2feda68363d31d5421ccceb983151f4d0373b93acbc7b5a5fe627f85b", "address": "1PZbKZx6bvTS3iyzG5uSYwGyNQen351x1y"}, 27 | {"private_key": "L2bBaKU6CVms2Q7GbkZN7vfYJ2YoMYLGFTMB1nBsS9CcipMGSDNN", "iscompressed": true, "pubkey": "0308bd3d6dcae9c10bc562eb63df1031791a36ffdbe385755da9e1a0e6c80deed7", "address": "1A4N9v6AW2GvxxUQMw1YjT64WfC9WgGmiD"}, 28 | {"private_key": "Kx4Hbx5BGHpc2TPW5SJpouy8iEk5jXvmsbWDorfZwGp3m3Ev8jGm", "iscompressed": true, "pubkey": "028920ea6aceba6da1d6e958e01b86d0502e754c6bbdefbf308c082cea8f22d567", "address": "1NFJtiJuMtNpZo4g59K94kR8Qk9Uw9WgXX"}, 29 | {"private_key": "KxittvKdmuKtxsY6VxcMtrZMd5AaXWTAUhN3a2W4JnTSc8AmJem8", "iscompressed": true, "pubkey": "024e68002561f3f6bb3a712715b5e82807b460b46f264ea50bc1123e194b4184cf", "address": "1GYktr6hvE3uBrDxuxdoSkKx7Y3tkyz512"}, 30 | {"private_key": "L4recHbfwb6wJUX6unAqcGfBccxXHdfCMGmjaxD1RidsWSQWdfNs", "iscompressed": true, "pubkey": "0389640128408689cebde50f9bce6fb2a684fb8d531e681fa828bc904d70b97283", "address": "1BVXJ67rsreKkCRkTx6ADHP7ybs7hgXMqT"}, 31 | {"private_key": "L1vbXPPTGgPJPas27NAA3JA9rGLpm5h8QPEsCBosgtqKGxWB6Yru", "iscompressed": true, "pubkey": "02e696c71dc77df1fbe98dced5bf039111a8f91db02dfb3c2804e116dcdbe064c5", "address": "18kvD13n7H7tHH3cbj6pAm5ryRenhnfnfa"}, 32 | {"private_key": "L3pStyG8HfnqmDPR3ZriuyEuTR3kqRFDKnVVwFq4MuwsGzDgJvfs", "iscompressed": true, "pubkey": "02507723cbab5cb674ef807a7b5d60ae1277432740ade329c1dcd6449a0ff395fa", "address": "1LggFbwCdi39FgZXFXof12JZiqb9keYuVa"}, 33 | {"private_key": "L5dszdzeNHaJXhpjnsK3igk28XbxC77KbrLkLo3Hyu36AmkSNUzZ", "iscompressed": true, "pubkey": "0313e4658d9c175e7c4c1019f98b05cce137654a91c1703efe16a6bf5b2240fc2c", "address": "1HnabXg5rXRFgP8U6LPiQmC4sMGWUZx3Py"}, 34 | {"private_key": "Kyo7wnJRzbhvKaZBSYh88iaF98b3HEFTuTDZ1jPvLi7nUcXFJPxz", "iscompressed": true, "pubkey": "03021a20aff4bdabbb121ca5a9775fbd864a7da040d2eb73a6c934a21f6b19ecca", "address": "1Aaznfu6hXPkfwF5iD3odKyZ2vruQDPjZD"}, 35 | {"private_key": "Kzu8pMuKJ93g5LQK6d4aFFqixp87DmrdmtmMc8onRN9LSjw2FxLJ", "iscompressed": true, "pubkey": "0394d65f286611e4cf45a8f350441771fad3c0f7fa0e6804bfa108756f4bbe58ff", "address": "19MqgHBB6eU3jEPeydsfXscUew4aDvUgGV"}, 36 | {"private_key": "Kzb3jrZm72feuU6soSoHXqXKquQpHaSZoUnGpE2K2Rdty3cBFoKN", "iscompressed": true, "pubkey": "03fe0dfb58d787db97e7182a8a49c4e2882c9f47fd7e34aa52016fa8cd0c55b3c0", "address": "1EzhwJqLN9WwhxfM2dQ1f2bjMwWXgL3rW9"}, 37 | {"private_key": "KwGJCH7DbL3iA2WShiR9ggDfRREWQLoUu7EWjToXZisXUVSf3dCf", "iscompressed": true, "pubkey": "02df594fcf744835a72feac1dd6531422655e1bb4aec9feef150dd6d5ce08c3b0f", "address": "1AwLseTMokJwz8fHe23eBetJV9xyevrvmB"}, 38 | {"private_key": "KyxQGQ1bUFBxsn3YPXC7hC3LhmS3kiJ6ZQ3CgHzFxD41f5kyDnaJ", "iscompressed": true, "pubkey": "0288df2895c414e61a02c6ccb93d9597a6c4b37524eb89bd58e1b6ded5455c7e66", "address": "162hmUeJUwt8qPgg4LCbfyNrGXpS1SbV8i"}, 39 | {"private_key": "L1xQKjsvtdguSgSyo5QtYcFYegW9qFXpJqFnFnT8UxC7B6tHdMCo", "iscompressed": true, "pubkey": "030858aa85ed6854fc7db1cffb5764c49e17f765896a2d26fab17dfee2937e29d3", "address": "1MxmKngKaLk4CRasqH4Kcsqpq1Rd9zYZeK"}, 40 | {"private_key": "Ky6kBvM1TPfBRmDX8Xb1bMuioEfeJQ6mYDQWkkWXgvBdXDx8aPz3", "iscompressed": true, "pubkey": "0226f5cb6a94b8be7b6797bcd1464142b5d88552a4c61606140adaddb2bf6d27cd", "address": "1254FAV2z6xDNNWTk96tRS6ShW9dxFZRVL"}, 41 | {"private_key": "KyR3KpqkBtBPTsaZ6MTahW5XUzuA9WbhNcKP6kP9xGtPppWU2gGQ", "iscompressed": true, "pubkey": "0266c14e53eda4c9ca8e89a6245c821fa57c2beea42dde0102a024d9e23a4b23c4", "address": "15pMbz7sAb8HBZuCM3diHAjsVZHjcJ7M4n"}, 42 | {"private_key": "L4cPW8eqjmQaA46agjGKZFoRQmq7LeN4C882mNAKyns6qvCueh4A", "iscompressed": true, "pubkey": "02731e67989c4b351e8c8ccaf99f57ae8e632fc7fc4fd07c89a75dd6a34fde0e6a", "address": "1MPwx5TRB6JBFYEoqbcR83riNpjbszodqr"}, 43 | {"private_key": "L24akxqx11BiQ2C1755cgt8QFbxLbRwb8Zey4NSXur6qY7STYERR", "iscompressed": true, "pubkey": "0372000d399442c65c5bec71f25c48c2bb9fd747dbfa4b8ba3d29d5810020830a1", "address": "15fXGd6zzzBr8fHgmYHDhWi7CyVgsfqr3G"}, 44 | {"private_key": "KzbucYwjz4jAoZwqAEjSKQQihqCW34GLHnU1ngkVqevr2ejEtxL7", "iscompressed": true, "pubkey": "0302cb23077bd58e76f17f19a9799473a12fe83fafe799e595b9da142f77866b74", "address": "1Wc5qjKLNwGWi32WhAWXs3NS1tHnPLQXi"}, 45 | {"private_key": "L3Ub2Ws8PUfSCfEp5qGfwk4hA2jYKwZA7gtQSfpaoKrAyLch8n9M", "iscompressed": true, "pubkey": "02ed70a1970913ca768983ad413d5cbd957855c6c3f39b13c2d4094cb570b49e4a", "address": "1FViBhq6HAQogpK97bLhUesrkV8uXzHsAz"}, 46 | {"private_key": "KwQ1wXUjLmPbhtSCtNQ97fHti7ouUhydxfUkSQQ4ax9YUFnCHrch", "iscompressed": true, "pubkey": "027cd687214da765937320554bace2b180bbdb7679c2b709c98deefbb5cf4fcb96", "address": "1AjK6cW4yfF7W54kGfiQTg58rdZwEesS3L"}, 47 | {"private_key": "L39m4mXnHVTSoZwVmvrLN21NnHG84tXCmjJDBp9G2hsXw2g5u4hR", "iscompressed": true, "pubkey": "03c155850303e3287faec6898a8574de943a3036187bd119a5313b95e8c30df780", "address": "16pneKQbbhV9hcp9YEs2s9UzQGTPLmN7Y5"}, 48 | {"private_key": "L1L2qn2bBEVLzsLEXwDFSaQdDEbNYzTJRdQtBnyr3KaEHiJVxN1m", "iscompressed": true, "pubkey": "03cf83b0a802f05c04b1066b0016c964ad2fc43be77d97364e42832b6864829436", "address": "1BnSw5cPdRhYmqyJStXNZXSDxdoogSWVFi"}, 49 | {"private_key": "KwTQVShEf59t3NvpBAh6jNbNiAXH6S3UFjtVwLHqgLHRpwnrSxhY", "iscompressed": true, "pubkey": "0264409e9356635c3c350da664240f54ee40e07e2dbd807649137126ebd1844477", "address": "19inRqp8ipdzwaj26je22Gcakj661ckrF1"}, 50 | {"private_key": "L2uhLo67UsWetiKfwAZ382JAWMfuJ3scuFfntcFrmc5zA3xYKK8o", "iscompressed": true, "pubkey": "031e5b77f959c684326a135bdb9585dad19e8ff4a7849bd193bebbc39ffba6a1ee", "address": "1DLhm1RvqV2rjScykLg6pydk4nRrUd2ibX"}, 51 | {"private_key": "KyxeQbhFiFXhsaWS6usJmDsK7iN7Z8YtCk2ChtsuTv58TMspcSuq", "iscompressed": true, "pubkey": "037388dab542bb559dbcdfd357063e8689fd2086ba35841a262f99ea184fe2baf0", "address": "1FGaSyWJ9pw1zrMBMVtS3G2xwbxVaT4jym"}, 52 | {"private_key": "L45MmWx1Q27v9bp8FvDJxJpvGPykXXYyXHWfaqiwmFome7dQ6mZW", "iscompressed": true, "pubkey": "03caacfefa26638f05370fe7b946726f589ea966f31af97a1932c219668f9460f2", "address": "1AQivXTEKkEMEtajWyqyNnZo86eqH3A5pq"}, 53 | {"private_key": "L4pzbgkFBBEaAaaFCPWd7M9cWKG9iEg6MjDDrrJ3c7xJujjv7tUi", "iscompressed": true, "pubkey": "03d68648758f0dbcd5024431cc442504f08bbf58f99c6364860923c9ba25ca0f8c", "address": "13kLV9cq4CLxzAUyCSufBcjyT4kKy8Wxjs"}, 54 | {"private_key": "L1zHg5EitiGemn4A19664xLqC1CWKgZ9oRt7aSeKBrH2X5rZH1cn", "iscompressed": true, "pubkey": "03b3adbe4db68a109b836f89fed9d15395c60c5dbebc4f64546315157699a99b7f", "address": "1Dqn2EGunEvVNjTwFYmBCeTTSVrdS6XpdW"}, 55 | {"private_key": "L2YNRfF8NeBGRekcKZiPHkFQKwKDqVz3qYDD3hATiBNSMXdLBgYi", "iscompressed": true, "pubkey": "0386e60a48e6ae4cf2e87cbd632ec0b6378ee9199b4dbd04fc43af80a8e5902c14", "address": "13YRqY2kKZsoXE8aYsqTjcUuhMhFDJoUtU"}, 56 | {"private_key": "KwaWQpiksYvmuWoWaS56vadjSHAQRL8wvaUTs275JJW3VAn3jTMY", "iscompressed": true, "pubkey": "0206fce99ccc5d5516ae7782fe0f7ee9921a8aaf4312d9f1fbb15c2ba6dc0e3ef6", "address": "1LwdvCRESnffQs49rgvtD98wYnNQHrTV8c"}, 57 | {"private_key": "Kx8riAreHw47oSdRQoYvvM5sVbpGyb5qRGR61xzK6gxWdAy2mw6T", "iscompressed": true, "pubkey": "02e7691016ebae535093c49f081cc5bc9ec9ef99341738a7861e8b1a79df6bc2f1", "address": "19622VBHAMdPNvp5ewMr9iG7cHMFEc6qGb"}, 58 | {"private_key": "KwPii39mLWNVKJ6iNkEQ6S8cKz1wr8muDeodarc3KR8yZ4PS5UH8", "iscompressed": true, "pubkey": "02c564558cd794966252a66afd07eb5a40fe65738be61306687babf6ed0ab94a68", "address": "1KWMCXjnE5VQkXrwKAAL42AMRmGPDma659"}, 59 | {"private_key": "L4rCcpW6aDjeS599t6rou4VW8GR3jqtpzWBn4qLvh6JgH3kbz4gj", "iscompressed": true, "pubkey": "034097c139162227835e3a97e9d73c775226bfd5c32ab0b598287e4205b7bb8c19", "address": "18jACxLpCC9iF3s1NyMF2QtoZZGrrRmmyQ"}, 60 | {"private_key": "L1vBdULiV1EeQikZ4Fz7Fi81LMMZzMoW7xgPov6DLdPNyzqDsXjs", "iscompressed": true, "pubkey": "0203ce5df17bcef2d8772214075d199d0e3cc586541d57f2a50a21ff68df76f5ee", "address": "15mS53RcUczwPvpckqJmmToA3i7CGNQ7Yi"}, 61 | {"private_key": "KzC8gQRV2MxQPfactHnsab9W4XeHVikxvoFrBG6B8nQzQERTVydA", "iscompressed": true, "pubkey": "027bf5b2037d1055b483e4b10918abfdc3e28b965d3db8ad5dee49b87cd1059ae0", "address": "1FaGYuZLJk13j5khu2srFox4jSk3TYByBr"}, 62 | {"private_key": "L4N4cVftGnokNGT3E38yqg2qcp2CztYHUXNyLTvwyzWcFABmTeLd", "iscompressed": true, "pubkey": "0205140e099ea40d7450a5ff99a0cfe561d76e9c3c9b3381c375940b7fff9b8f3f", "address": "1G9GLpmaUHMaKxH55w5Lm2UH6WKRvfYzpE"}, 63 | {"private_key": "L4HBVJYAkoiwoAjDjxjxVcDasZNhonG3aWqfmD6GxUaT1YrNqvJv", "iscompressed": true, "pubkey": "031a610f7383a202f33b5b54430b0265d974554da013a2846efe4afcf3f967564f", "address": "1GLapM5mkduikeMkbK8yoczpT6my2VmV2v"}, 64 | {"private_key": "L3iGU9HpJtG1Se1kdWXkXSumY8WSMr8ESC5aYM6nx1CaJPoo7nY5", "iscompressed": true, "pubkey": "0213862b88baa4309cb6276f3668eab34013336ea097200bbeed571a4205eef5f3", "address": "1JMEabzSH4HZhkWMVNusWNVmUvVyaoEAQM"}, 65 | {"private_key": "L4PuC6BKwMZtUqnA8cFnDaxNnuWyqrKmasch5Ct6JgM5b21VGNXV", "iscompressed": true, "pubkey": "02e04957fc125cfb3cb9c40991a408dd74f13f8528a5556aedf3425564014d10c8", "address": "13b9ACTVFPae9JZ9EecffioPii25R4eY3Z"}, 66 | {"private_key": "L3DvafJbAiGnoceukKTqKrf3bMgZFomuDAoVtJwt1NmtKJtUVEKK", "iscompressed": true, "pubkey": "022b662f10c89becfdc69c15b342bd096fe99f66cc7b2c0f31e5f8812f4bd66de4", "address": "1ERizVCHrTDQyMfMbLmXzZmEUdueGwbHXS"}, 67 | {"private_key": "L47DSA1Mus6X6MXYzQmsEicQwT22LdVfgcMNg1MHDM5CALxpdnqG", "iscompressed": true, "pubkey": "03fda23f44a6be570c21ae55cb594fbf07b318da03a3fb820ece93bb18c9dc7e57", "address": "1PvWjRQg4A3wkE4tquchVqDB7oMUhFpZJw"}, 68 | {"private_key": "KzouoT2F94vjWhYGp6Jbu5FWaJ47Mt8TGobTWh5WPr1gYcwymP9d", "iscompressed": true, "pubkey": "03752dbb1eac53e7b1e8ae624cd6370e80aa1ca60ad93ad3ed5a3551111de65e43", "address": "1HgXaE3pPvmLDSdgM9agdcnLX6dUyRAasq"}, 69 | {"private_key": "L1HJPtYRy1uuXcsCFnLAygUuk3AP9vAsYZiQi1CRrtcMUfvfWra4", "iscompressed": true, "pubkey": "030138d26971e13e145bee9d426213a551472e40756780e481e49d61f0bcb3e408", "address": "1Bq3VuPif8ijsdNYTGNb6Xip9feWfbmsrd"}, 70 | {"private_key": "KxznakSC571aVrYN9hTLsac3qJsnzJGf6ChV6vyJiWXSJu6hc9ic", "iscompressed": true, "pubkey": "032b6b779c8a0fc699dd4c4102b049f740a0ff7eb8974946833e1a225cb4a41ed4", "address": "12ujdoiVsVLSyDhPCxifjQD2cFPwpVuqFV"}, 71 | {"private_key": "KzZ7XKGEufRATpBcwvMXxacmvqiaB3fHUVv6yZqnF5GBTEfxmz2r", "iscompressed": true, "pubkey": "036a6a18c2644b04a7192f9ee32317be9f5b03179098e21bfb23061623c70aa8aa", "address": "1AXcC4XCHHAu982Vp1CGSFkb9h18zdkMni"}, 72 | {"private_key": "Kx8JoYFg6TjvjBfGMCjKGeRmkxbC4ivLm4FWhw2kWb6gBbgA67mn", "iscompressed": true, "pubkey": "028230793216827347916373dc725574a810d48b4f19fbf24e3f8b6d345f62076a", "address": "1NZyaSsbVSNRa4UA97WRx58tuhNktaDyPR"}, 73 | {"private_key": "KxS1WVdoercFMLSBPQD2zmxgzqESo6bWyjXYq2edVKoHfqRTupwx", "iscompressed": true, "pubkey": "03411cb7d7c066758d6664624b3f21a9b7b597dc5e51050dceb86c4b245ec02fdd", "address": "159qrDnuwpCKfXiHF9rMjyxvqgPMEM2Jdg"}, 74 | {"private_key": "L4LcC6ZKtUgTqxghgR9PsyMF4bLXWFqMPz68SY2HSARd2DFcYRwW", "iscompressed": true, "pubkey": "03df474b2eb3ad48976ee695de657086570b9917fd6ffa55518c935049984c94a3", "address": "19V95umvkwt7xaXkr8fVLxr5LKnJ88msWN"}, 75 | {"private_key": "L1H969ahaijAG85udU8tcje3BmEsXrKTc7TMozVxhJP3XZhdhH91", "iscompressed": true, "pubkey": "030501c8451e24cded44ddf9f480e69fbb990a3158eeb61b4adbfcfea828971855", "address": "1KqqZKHaDkFEPkj69UnMQu5Ttmwpdkj8o7"}, 76 | {"private_key": "L2iTa58wyv9HZW6EJriagKU15opjjwYziJkbcpouNyeBWnJEUHXM", "iscompressed": true, "pubkey": "020933ae99c5e228b72ebc9fbe5c4ac569b2de0796a4d6de08aa2e2e82a7012171", "address": "1AgXKgQE7yZHuabNibkZSNsWSGKkJQvPbw"}, 77 | {"private_key": "L4T6RgEu8ZUK1mjKZcvqstKp8htxddrFwjtwbw2DKxym5CeSMQ3B", "iscompressed": true, "pubkey": "03af3bcce9831a95b85ddb224f8b4c3f2f013f4b74a4a974845dc94ebc7d5a7b7e", "address": "1L9t83dVeffFybiFfHSz9CkeAdgVJTjjYM"}, 78 | {"private_key": "L2Qfti76bSiZVyokUvUA5FddK9kgjzY5NMu6jyMMQyRykPLz4rE7", "iscompressed": true, "pubkey": "022abd19e9e59891df821a63450dfc458701268382736657c727182e2920491f89", "address": "1CoHMqHwsnxUAyUsuAKnfTTcGvfJ78i1ub"}, 79 | {"private_key": "KwJ61TyWCjfcdJ8FaY14iV1PbxiJx15PdgdhYNwrmXYT7LfXQFKN", "iscompressed": true, "pubkey": "027e743af26c1354f1e5e296dd6ecad64224e628a6af3db1db28fab12a8ff62f1d", "address": "1HidcEM2jczMHXR1ZrsAimsTKzAEpoV6Lg"}, 80 | {"private_key": "KyBkbUGxAuPSefnfFPhR4kF53KzS1ZrdxQUCwZYitCMsfQbHVKi9", "iscompressed": true, "pubkey": "0216390d13e5e53884f378ce05a331fe31eeb8798b6c1d5ed2dca2911cb0f1a56b", "address": "1JufftwUYeR7c85ge5DFTEpSGDKBoTrmdj"}, 81 | {"private_key": "L53zgt9RyGV2K6it6rKipzjrqicC2QYRH9rNUpNvVrd7tikCqSQk", "iscompressed": true, "pubkey": "02b814b73c703c573b85bbf30d95e106a67f3e98d5964e3cc2b40234ceaee1dade", "address": "17YjTU2heXCatmrdwgofW1LHFBW7GLATXR"}, 82 | {"private_key": "KybL4xMuKvD6mM4qsQs468LYhnTu3GJpwusfEWkQEcLwxzG2LmhP", "iscompressed": true, "pubkey": "02ab6a928d7721a2acd26d8a59b17417c5737a00e58979663ff37ffa986f6c70d8", "address": "17zVA2pG1nMHFXdQ8tD5QCNad33WNNUao4"}, 83 | {"private_key": "L5hXBv2dGrEYiYoaVJ2DrqkUz9abuBxuHhD1DF6fZCe55Gzy5mcV", "iscompressed": true, "pubkey": "02ef36ed31158fbf1d9b1310beb1595a4a9f0644e5cd43f972eacbfaa1869a4a5a", "address": "1866KnmudMJQXk9mjtUqSJpoYJu5KEcqK5"}, 84 | {"private_key": "L27HSHheACmUaghenrxHJVPdAHbJjvoER2dNvnpmp3oDrkPn1AYL", "iscompressed": true, "pubkey": "024fd1573a61736ea5391b4d64f029f20287d3761b7e9aa2573a6f22472ae35c67", "address": "19KNCxuWE3r8vptTU2NLhnFQUqcw4Lqco7"}, 85 | {"private_key": "KzZ2aRu2Buwm4STKZr2WGie299zpfX8ajNNcT8Quef8i7QoRCxL1", "iscompressed": true, "pubkey": "0271fdd0a7bd86ffe16b6cd64fb32a833e0695566afaca42d76b7668bc6fbb40e9", "address": "168M1DhbvWf1F6Vezys5zzhELUTwwuBtxE"}, 86 | {"private_key": "L1mDUbc6agySzEQYrWH58thbeGhA2C8SpgPgh7ZYigg5He5Vq5tt", "iscompressed": true, "pubkey": "02ac99c52893840ea7d3f65da007fd3d4e780aa6b909a481528bd4fa26201d74ff", "address": "13SimnH7PTu6P92TRcBPVFYRMZXtRefEmU"}, 87 | {"private_key": "L1jXvpgEtmdEDmMKiZikXN3nY6mnz3nUZCjt8aeDnQLGTKEijbUc", "iscompressed": true, "pubkey": "03600e7e7a153f6b5aceeb8cedb084e1e7b5816c6d031d4d94d7c6fbf9ed5cffea", "address": "1GbLMPUqG6VkSq9pYhiKS68jqBjutCgJ6J"}, 88 | {"private_key": "L1w1dLFEKFYqMZ7Jh1sq42sz3hzdeT7vkieYGjmMVBKDnNMxnRCD", "iscompressed": true, "pubkey": "03753960c4d9eea1b1fc450bd6ab9f49bbb189ce0d10d39e7626d6fdb9e269c0ab", "address": "1QLGXwyrbjf9zTrndmhhUZea1hUHBvEzzA"}, 89 | {"private_key": "Kzg35DfrxiW7eCWPdWNVy71qFobgT88Zc329WT5VHdoUDAHuhupH", "iscompressed": true, "pubkey": "03cc42b76f4645c6277cb4baed2622d0c814eaf911c13b2906e8c27f4a1a127fa8", "address": "1LUr6U9UvYcxo7KrrmPhoAHbfianrJu2p4"}, 90 | {"private_key": "L1EVcZdRkRPTNhCPeYYAjz5GUVFWuosPPz7HDctMCpAv5xJRDdcY", "iscompressed": true, "pubkey": "024659c96d27cd85c6a4b168812043d61387436023a94a3a8fe277e60cd255d3b4", "address": "1EoD2nvkFWa7ct6STFwS3zr3VftcJkkZPJ"}, 91 | {"private_key": "L2RyujiuW869KrU9hUiBBUZhCjwBV5UsRqH1VGosFXHE5syyn79x", "iscompressed": true, "pubkey": "025fca1a1daafc810022d85129b08497fcc3f5e62e381fb550d544e40d5f0ebf1b", "address": "1Q1BV35zGBVptUHoJy7dpbFhL4TsQXbZ3M"}, 92 | {"private_key": "L5gZqGKvcaS6VTSRUZpmrZdDjH6HnXmxEdRekKxdsKbMSJWTXSTT", "iscompressed": true, "pubkey": "03330497d71716154d8c986fe65982714ada950e590cb0b77518005783b20f3335", "address": "1MxmssoFS73xQTfL8BNPamHjbcBZKuzjKN"}, 93 | {"private_key": "L3nsfveNFcTnJYbg7tR6o9CmhcnjJ9xa8oWEcLkY9yQBagowYiUv", "iscompressed": true, "pubkey": "027d335012467c3ad1dd0d7eedcfd5f619bcafbf6557fd9fed9b41cedd0bf81eb6", "address": "1FR5R4aQvM5154qrgfkDgRaFn4VL1vvAtk"}, 94 | {"private_key": "KxCHCFvPHA38PpSWDVdYqpZg3JrCQenztCsdzVbBYmBmdW5XzcNZ", "iscompressed": true, "pubkey": "02837321e958da639343dc848838a47d8644eedf506e6a6d03f1c5d390f9fe8f4d", "address": "134JcmSZ8igzJ2hWaW4ZFqNzc3LChBSeXt"}, 95 | {"private_key": "KyGujU95Hw2PxacsxChgJyCeLgVUn4RFjBfUd8u8QJsTEiHePX6H", "iscompressed": true, "pubkey": "0332caae9c90df8b4038c9790481a26c50668799ceae0656323518cb90d7adce86", "address": "1FcvFVKdw45v4WCcg5uHHqfE8cW5xzuF92"}, 96 | {"private_key": "KwQ3np7qF25csL5zWeoG1ZAYRfjkjYnZsP1Afjtx3SyE2F3VbVRo", "iscompressed": true, "pubkey": "03589a938381711af8cc596c3e5edd146a78dd085e642ddeff56e5412c899630df", "address": "1Ae318iNSeKjfutTcPywE2QnpDzkiNhLmk"}, 97 | {"private_key": "KwfUAhCas4eopx81DSx5EDP4BEgMg2W6Sc2NhrApaLmhnQX5aCuT", "iscompressed": true, "pubkey": "03c70c938a1d0271b7eab9f9e8c8674aee02dc44f48a19039410a066df876aef7d", "address": "1PUKxbG7ntbmurJcPJkdzS3TNnchbw8qvW"}, 98 | {"private_key": "Kwzg5L1s5VVfp4ST15nBj4Am1GWCr3AQL5JTevqNBhGSdKnRvbCV", "iscompressed": true, "pubkey": "02534f540b0d0025bcfe562613eb05bab4e1a53ea036f5ad35735d12569d7184db", "address": "19ybArGC3SxiXYKKnmwDG5BdkPNSPon29B"}, 99 | {"private_key": "L43w7WLcLMgbMRRPUYZCWWB7cpeg9B4ebf1B5SSnK8hYYoZ1NJxn", "iscompressed": true, "pubkey": "03e00678841e7ad5d77f1eacbf1c84494c3f27447b4a7c87548238e2c20c2fd30f", "address": "185ANtABZPUpsB7FcXiusp2JXdDSc12Ej6"}, 100 | {"private_key": "L5fbmAQMCVQghySThhK78F6sP1FFvycybbWeYYkf5sTRaRUhT2uW", "iscompressed": true, "pubkey": "033bb3551575cac18637f45431123366a95f7ae7f772e7a56fe11407de2f433686", "address": "13y5ByQYUDvFuk4ttyGvDFwKJptm7BgSCK"}, 101 | {"private_key": "L1TjitdrnhvQdkmkC8DbRV6H91r5zpHaTggs5PMz4HuQfnadFzbb", "iscompressed": true, "pubkey": "036cc318f9d3ddc6b8b6d37cc72028f2ce37fc2ae38b73cf426ca812e7da46bf24", "address": "16gJsrvNFUCa2RtuUxgPXtbMAE2BnvGLBG"} 102 | ] -------------------------------------------------------------------------------- /tests/test_bip32.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | from mock import patch 3 | import time 4 | from unittest import TestCase 5 | 6 | from ecdsa import SECP256k1 7 | from ecdsa.ellipticcurve import INFINITY 8 | 9 | from multimerchant.network import BitcoinMainNet 10 | from multimerchant.network import BitcoinTestNet 11 | from multimerchant.network import DogecoinMainNet 12 | from multimerchant.network import LitecoinMainNet 13 | from multimerchant.wallet import Wallet 14 | from multimerchant.wallet.bip32 import InfinityPointException 15 | from multimerchant.wallet.bip32 import InsufficientKeyDataError 16 | from multimerchant.wallet.bip32 import InvalidPathError 17 | from multimerchant.wallet.bip32 import InvalidPrivateKeyError 18 | from multimerchant.wallet.bip32 import InvalidPublicKeyError 19 | from multimerchant.wallet.bip32 import KeyMismatchError 20 | from multimerchant.wallet.keys import IncompatibleNetworkException 21 | from multimerchant.wallet.utils import ensure_bytes 22 | from multimerchant.wallet.utils import long_to_hex 23 | 24 | 25 | class TestWallet(TestCase): 26 | @classmethod 27 | def setUpClass(cls): 28 | cls.expected_key = ensure_bytes( 29 | "0488ade4" # BitcoinMainNet version 30 | "00" # depth 31 | "00000000" # parent fingerprint 32 | "00000000" # child_number 33 | # chain_code 34 | "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508" 35 | "00" # key identifier 36 | # private exponent 37 | "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35") 38 | cls.master_key = Wallet.deserialize(cls.expected_key) 39 | 40 | def test_serialize_master_key(self): 41 | self.assertEqual(self.expected_key, self.master_key.serialize()) 42 | 43 | def test_from_master_secret(self): 44 | secret = binascii.unhexlify(b'000102030405060708090a0b0c0d0e0f') 45 | self.assertEqual(Wallet.from_master_secret(secret), 46 | self.master_key) 47 | 48 | def test_from_master_secret_slow(self): 49 | """Verified against bip32.org""" 50 | password = "correct horse battery staple" 51 | w = Wallet.from_master_secret_slow(password) 52 | self.assertEqual( 53 | w.serialize_b58(private=True), 54 | "xprv9s21ZrQH143K3JDqHk5kEb6o2w8pEwm3cmt8qaSw9coaHCYJFtaybzUob6d4" 55 | "WyJDf8uspZkBAt7DcEVhvCDRBHZEavVJg51HZEGdVH2uXLK") 56 | self.assertEqual(w.depth, 0) 57 | self.assertEqual(w.parent_fingerprint, b"0x00000000") 58 | self.assertEqual(w.child_number, 0) 59 | self.assertEqual( 60 | w.chain_code, 61 | (b'7c73c15c623128246dcf37d439be2a9d' 62 | b'da5fb33b2aec18e66a806d10a236b5c9')) 63 | self.assertEqual( 64 | w.export_to_wif(), 65 | 'KxTFZmNVYgAupo2w8QUNpfDjSEMhGN7RaQ6rhNRvsSHBggASpEr1') 66 | child = w.get_child(0, is_prime=False) 67 | self.assertEqual( 68 | child.serialize_b58(private=True), 69 | "xprv9vExvbix4MQgazj3vovZ4UEwmLSEQrktY8yZAVhFAB7W7xzqS9RXH8ZaNEdw" 70 | "KoQzbPixY3YSVjK58S3K5h4ktjVEpHrfjUarsiUfKDe6A4i") 71 | self.assertEqual( 72 | child.export_to_wif(), 73 | 'L3LA3KxJELbwCyVjFaSrvvUsnfKcZ9TPmGXbq4s6zmK5kaBVja29') 74 | self.assertEqual( 75 | child.serialize_b58(private=False), 76 | "xpub69EKL7FqtixyoUoX2qTZRcBgKNGipKUjuMu9xt6riWeUzmKyygjmpvt4DXaL" 77 | "U2vyoVqYtpqyuDYDHsxbzzReQmou1PtwVthP3SJkjcHEEg4") 78 | self.assertEqual( 79 | child.get_public_key_hex(), 80 | (b"03b18ba94530690859a3f6ebb2b866d1" 81 | b"51f8499b3164d027ba5b464e4ed71329aa")) 82 | self.assertEqual( 83 | child.to_address(), 84 | "1MfJvR28iULUb8AwtY7hp7xpc1A8Wg1ojX") 85 | 86 | def test_invalid_network_prefix(self): 87 | key = self.expected_key 88 | key = (long_to_hex(BitcoinTestNet.EXT_SECRET_KEY, 8) + 89 | self.expected_key[8:]) 90 | self.assertRaises(IncompatibleNetworkException, 91 | Wallet.deserialize, key, BitcoinMainNet) 92 | self.assertTrue(Wallet.deserialize(key, BitcoinTestNet)) 93 | 94 | def test_public_export(self): 95 | """Export a node as public.""" 96 | child = self.master_key.get_child(0, as_private=False) 97 | self.assertEqual(child.private_key, None) 98 | key = child.serialize(private=False) 99 | self.assertTrue( 100 | long_to_hex(BitcoinMainNet.EXT_PUBLIC_KEY, 8) in key) 101 | self.assertEqual(Wallet.deserialize(key), child) 102 | 103 | def test_public_export_mismatch(self): 104 | """Can't export a public node as private.""" 105 | child = self.master_key.get_child(0, as_private=False) 106 | self.assertEqual(child.private_key, None) 107 | self.assertRaises(ValueError, child.serialize) 108 | 109 | def test_random_wallet(self): 110 | w = Wallet.new_random_wallet() 111 | self.assertTrue(Wallet.deserialize(w.serialize()), w) 112 | self.assertEqual(w.depth, 0) 113 | self.assertEqual(w.parent_fingerprint, b'0x' + long_to_hex(0, 8)) 114 | self.assertEqual(w.child_number, 0) 115 | 116 | w2 = Wallet.new_random_wallet() 117 | self.assertNotEqual(w.get_private_key_hex(), w2.get_private_key_hex()) 118 | 119 | def test_random_wallet_with_entropy(self): 120 | """Ensure that the user_entropy value actually adds entropy.""" 121 | test_time = time.time() 122 | with patch('multimerchant.wallet.bip32.urandom', return_value=b'0'*64): 123 | with patch('multimerchant.wallet.bip32.time') as mock_time: 124 | mock_time.time.return_value = test_time 125 | self.assertEqual( 126 | Wallet.new_random_wallet('entropy'), 127 | Wallet.new_random_wallet('entropy')) 128 | self.assertNotEqual( 129 | Wallet.new_random_wallet('entropy'), 130 | Wallet.new_random_wallet('foo')) 131 | 132 | def test_insuffient_key_data(self): 133 | self.assertRaises(InsufficientKeyDataError, Wallet, 134 | chain_code=self.master_key.chain_code, 135 | private_exponent=None, 136 | private_key=None, 137 | public_pair=None, 138 | public_key=None) 139 | 140 | def test_private_exponent(self): 141 | """Ensure we can create a wallet with just a private exponent.""" 142 | Wallet(chain_code='0' * 64, 143 | private_exponent=(self.master_key.private_key._private_key 144 | .privkey.secret_multiplier)) 145 | 146 | def test_private_key(self): 147 | """Ensure a private key is sufficient to create a wallet.""" 148 | Wallet(chain_code='0' * 64, 149 | private_key=self.master_key.private_key) 150 | 151 | def test_private_key_type(self): 152 | """Must be a multimerchant private key""" 153 | self.assertRaises( 154 | InvalidPrivateKeyError, Wallet, 155 | chain_code='0' * 64, 156 | private_key=self.master_key.private_key._private_key) 157 | 158 | def test_public_pair(self): 159 | Wallet(chain_code=b'0' * 64, 160 | public_pair=self.master_key.public_key.to_public_pair()) 161 | 162 | def test_public_key(self): 163 | Wallet(chain_code=b'0' * 64, 164 | public_key=self.master_key.public_key) 165 | 166 | def test_public_key_type(self): 167 | self.assertRaises( 168 | InvalidPublicKeyError, Wallet, 169 | chain_code=b'0' * 64, 170 | public_key=self.master_key.public_key._verifying_key) 171 | 172 | def test_mismatch_public_private(self): 173 | w = Wallet.new_random_wallet() 174 | self.assertRaises( 175 | KeyMismatchError, Wallet, 176 | chain_code=b'0' * 64, 177 | private_key=self.master_key.private_key, 178 | public_key=w.public_key) 179 | 180 | 181 | class TestInvalidChildren(TestCase): 182 | def test_key_too_large(self): 183 | w = Wallet.new_random_wallet() 184 | order = binascii.unhexlify(long_to_hex(SECP256k1.order, 64)) 185 | return_value = order + order 186 | with patch('hmac.HMAC.digest', return_value=return_value): 187 | self.assertRaises( 188 | InvalidPrivateKeyError, 189 | w.get_child, 190 | 1) 191 | 192 | def test_infinity_point(self): 193 | w = Wallet.new_random_wallet() 194 | with patch('multimerchant.wallet.keys.PublicKey.to_point', 195 | return_value=INFINITY): 196 | self.assertRaises( 197 | InfinityPointException, 198 | w.get_child, 199 | 1) 200 | 201 | 202 | class TestNewAddressForUser(TestCase): 203 | def setUp(self): 204 | self.w = Wallet.new_random_wallet() 205 | 206 | def test_invalid_user_id(self): 207 | self.assertRaises( 208 | ValueError, 209 | self.w.create_new_address_for_user, 210 | -10) 211 | self.assertRaises( 212 | ValueError, 213 | self.w.create_new_address_for_user, 214 | 0x80000000 + 1) 215 | 216 | def test_new_address(self): 217 | child = self.w.create_new_address_for_user(10) 218 | self.assertEqual( 219 | self.w.get_child(10, as_private=False), child) 220 | 221 | 222 | class TestCrackPrivateKey(TestCase): 223 | def setUp(self): 224 | self.w = Wallet.new_random_wallet() 225 | self.pub_derived_private_child = self.w.get_child(100) 226 | self.wpub = self.w.public_copy() 227 | self.assertTrue(self.wpub.private_key is None) 228 | 229 | def test_already_have_private(self): 230 | self.assertRaises(AssertionError, 231 | self.w.crack_private_key, 232 | self.pub_derived_private_child) 233 | 234 | def test_invalid_fingerprint(self): 235 | child = self.pub_derived_private_child.get_child(10) 236 | self.assertRaises(ValueError, self.wpub.crack_private_key, child) 237 | 238 | def test_invalid_prime(self): 239 | child = self.w.get_child(-100) 240 | self.assertRaises(ValueError, self.wpub.crack_private_key, child) 241 | 242 | def test_crack_child(self): 243 | cracked = self.wpub.crack_private_key(self.pub_derived_private_child) 244 | self.assertEqual(cracked, self.w) 245 | self.assertEqual(cracked.get_child(100), 246 | self.pub_derived_private_child) 247 | self.assertEqual(cracked.get_child(-100), self.w.get_child(-100)) 248 | 249 | 250 | class TestSubkeyPath(TestCase): 251 | """Tests for get_child_for_path not covered by TestVectors.""" 252 | @classmethod 253 | def setUpClass(cls): 254 | """ 255 | This particular key was found by accident to cause the public 256 | deserialized wallet to have a bad public key point! 257 | 258 | There was a bug that did not properly handle restoring a key from 259 | a compressed point that had an odd beta parameter. 260 | (see PublicKey.from_hex_key) 261 | """ 262 | cls.wallet = Wallet.deserialize( 263 | u'xprv9s21ZrQH143K319oTMcEt2n2g51StkEnXq23t52ajHM4zFX7cyPqaHShDod' 264 | 'cHAqorNQuDW82jUhXJLomy5A8kM36y8HntnosgCvc1szPJ6x') 265 | 266 | def assert_public(self, node): 267 | self.assertEqual(node.private_key, None) 268 | 269 | def test_strip_private_key(self): 270 | self.assert_public(self.wallet.public_copy()) 271 | self.assertNotEqual(self.wallet.private_key, None) 272 | 273 | def test_export_as_public(self): 274 | self.assert_public(self.wallet.get_child(0, as_private=False)) 275 | 276 | def test_path_as_public(self): 277 | self.assert_public(self.wallet.get_child_for_path("M/0")) 278 | self.assert_public(self.wallet.get_child_for_path("M/0.pub")) 279 | self.assert_public(self.wallet.get_child_for_path("m/0.pub")) 280 | self.assert_public(self.wallet.get_child_for_path("M")) 281 | self.assert_public(self.wallet.get_child_for_path("m.pub")) 282 | 283 | def test_public_final_with_prime(self): 284 | self.assert_public(self.wallet.get_child_for_path("M/0/1'/2/3'.pub")) 285 | 286 | def test_public_child_restore(self): 287 | pub_child = self.wallet.get_child_for_path("M/0") 288 | self.assert_public(pub_child) 289 | loaded = Wallet.deserialize(pub_child.serialize(False)) 290 | self.assertEqual(pub_child, loaded) 291 | n1 = pub_child.get_child_for_path("m/1") 292 | n2 = loaded.get_child_for_path("m/1") 293 | self.assertEqual(n1, n2) 294 | 295 | def test_invalid_path(self): 296 | self.assertRaises( 297 | ValueError, 298 | self.wallet.get_child_for_path, 299 | None) 300 | self.assertRaises( 301 | InvalidPathError, 302 | self.wallet.get_child_for_path, 303 | "") 304 | self.assertRaises( 305 | InvalidPathError, 306 | self.wallet.get_child_for_path, 307 | "m/foo") 308 | self.assertRaises( 309 | InvalidPathError, 310 | self.wallet.get_child_for_path, 311 | "M/1234/4567m") 312 | 313 | def test_child_too_small(self): 314 | self.assertRaises( 315 | ValueError, 316 | self.wallet.get_child, 317 | -(0x80000000 + 1)) 318 | 319 | def test_child_too_big(self): 320 | self.assertRaises( 321 | ValueError, 322 | self.wallet.get_child, 323 | 0xFFFFFFFF + 1) 324 | 325 | def test_path_bigger_than_boundary(self): 326 | child_number = 0x80000000 327 | self.assertRaises( 328 | ValueError, 329 | self.wallet.get_child_for_path, "m/%s" % child_number) 330 | self.assertRaises( 331 | ValueError, 332 | self.wallet.get_child_for_path, "m/%s" % (child_number + 1)) 333 | self.assertNotEqual( 334 | self.wallet.get_child_for_path("m/%s'" % (child_number - 1)), 335 | self.wallet.get_child_for_path("m/%s" % (child_number - 1))) 336 | 337 | def test_child_bigger_than_boundary(self): 338 | child_number = 0x80000000 339 | self.assertRaises( 340 | ValueError, self.wallet.get_child, -1, is_prime=True) 341 | self.assertRaises( 342 | ValueError, self.wallet.get_child, -1, is_prime=False) 343 | self.assertRaises( 344 | ValueError, self.wallet.get_child, child_number, is_prime=True) 345 | self.assertRaises( 346 | ValueError, self.wallet.get_child, child_number, is_prime=False) 347 | 348 | 349 | class TestSerialize(TestCase): 350 | network = BitcoinMainNet 351 | 352 | @classmethod 353 | def setUpClass(cls): 354 | cls.wallet = Wallet.new_random_wallet(network=cls.network) 355 | 356 | def test_serialize_private(self): 357 | prv = self.wallet.serialize(private=True) 358 | w = Wallet.deserialize(prv, network=self.network) 359 | self.assertTrue(w.private_key) 360 | self.assertEqual(w, self.wallet) 361 | 362 | prv = self.wallet.serialize_b58(private=True) 363 | w = Wallet.deserialize(prv, network=self.network) 364 | self.assertTrue(w.private_key) 365 | self.assertEqual(w, self.wallet) 366 | 367 | def test_serialize_public(self): 368 | pub = self.wallet.serialize(private=False) 369 | w = Wallet.deserialize(pub, network=self.network) 370 | self.assertFalse(w.private_key) 371 | 372 | pub = self.wallet.serialize_b58(private=False) 373 | w = Wallet.deserialize(pub, network=self.network) 374 | self.assertFalse(w.private_key) 375 | 376 | def test_deserialize_byte_array(self): 377 | key = binascii.unhexlify(self.wallet.serialize()) 378 | w = Wallet.deserialize(key, network=self.network) 379 | self.assertEqual(w, self.wallet) 380 | 381 | 382 | class TestSerializeDogecoin(TestSerialize): 383 | network = DogecoinMainNet 384 | 385 | 386 | class TestSerializeLitecoin(TestSerialize): 387 | network = LitecoinMainNet 388 | 389 | 390 | class _TestWalletVectors(TestCase): 391 | def _test_vector(self, key, id_hex, fingerprint, address, 392 | secret_key_hex, secret_key_wif, 393 | pubkey_hex, chaincode_hex, 394 | pubkey_serialized_hex, private_serialized_hex, 395 | pubkey_base58, private_base58, 396 | include_private=True 397 | ): 398 | self.assertEqual(key.identifier, ensure_bytes(id_hex)) 399 | self.assertEqual(key.fingerprint, ensure_bytes(fingerprint)) 400 | self.assertEqual(key.to_address(), address) 401 | self.assertEqual(key.get_public_key_hex(), ensure_bytes(pubkey_hex)) 402 | self.assertEqual(key.chain_code, ensure_bytes(chaincode_hex)) 403 | self.assertEqual(key.serialize(private=False), 404 | ensure_bytes(pubkey_serialized_hex)) 405 | self.assertEqual(key.serialize_b58(private=False), pubkey_base58) 406 | 407 | if include_private: 408 | self.assertEqual(key.get_private_key_hex(), 409 | ensure_bytes(secret_key_hex)) 410 | self.assertEqual(key.export_to_wif(), secret_key_wif) 411 | self.assertEqual(key.serialize(), 412 | ensure_bytes(private_serialized_hex)) 413 | self.assertEqual(key.serialize_b58(), private_base58) 414 | 415 | def _test_deserialize(self, child, *vector): 416 | self._test_vector( 417 | Wallet.deserialize(child.serialize(private=True)), 418 | *vector) 419 | self._test_vector( 420 | Wallet.deserialize(child.serialize(private=False)), 421 | *vector, include_private=False) 422 | 423 | 424 | class TestWalletVectors1(_TestWalletVectors): 425 | @classmethod 426 | def setUpClass(cls): 427 | cls.master_key = Wallet.from_master_secret( 428 | binascii.unhexlify('000102030405060708090a0b0c0d0e0f')) 429 | 430 | def test_m(self): 431 | """[Chain m]""" 432 | vector = [ 433 | '3442193e1bb70916e914552172cd4e2dbc9df811', 434 | '0x3442193e', 435 | '15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma', 436 | 'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35', 437 | 'L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW', 438 | '0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2', # nopep8 439 | '873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508', 440 | '0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2', # nopep8 441 | '0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35', # nopep8 442 | 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8', # nopep8 443 | 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi', # nopep8 444 | ] 445 | self._test_vector(self.master_key, *vector) 446 | self._test_vector(self.master_key.get_child_for_path("m"), *vector) 447 | self._test_deserialize(self.master_key, *vector) 448 | 449 | def test_m_0p(self): 450 | vector = [ 451 | '5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7', 452 | '0x5c1bd648', 453 | '19Q2WoS5hSS6T8GjhK8KZLMgmWaq4neXrh', 454 | 'edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea', 455 | 'L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT', 456 | '035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56', # nopep8 457 | '47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141', 458 | '0488b21e013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56', # nopep8 459 | '0488ade4013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae623614100edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea', # nopep8 460 | 'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw', # nopep8 461 | 'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7', # nopep8 462 | ] 463 | child = self.master_key.get_child(0, is_prime=True) 464 | self._test_vector(child, *vector) 465 | self._test_vector(self.master_key.get_child_for_path("m/0'"), *vector) 466 | self._test_vector(self.master_key.get_child_for_path("m/0p"), *vector) 467 | self._test_deserialize(child, *vector) 468 | 469 | def test_m_0p_1(self): 470 | vector = [ 471 | 'bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe', 472 | '0xbef5a2f9', 473 | '1JQheacLPdM5ySCkrZkV66G2ApAXe1mqLj', 474 | '3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368', 475 | 'KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM', 476 | '03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c', # nopep8 477 | '2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19', 478 | '0488b21e025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c1903501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c', # nopep8 479 | '0488ade4025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19003c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368', # nopep8 480 | 'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ', # nopep8 481 | 'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs', # nopep8 482 | ] 483 | m0 = self.master_key.get_child(0, is_prime=True) 484 | child = m0.get_child(1, is_prime=False) 485 | self._test_vector(child, *vector) 486 | self._test_vector( 487 | self.master_key.get_child_for_path("m/0'/1"), *vector) 488 | self._test_vector( 489 | self.master_key.get_child_for_path("m/0p/1"), *vector) 490 | self._test_deserialize(child, *vector) 491 | 492 | def test_m_0p_1_2p(self): 493 | vector = [ 494 | 'ee7ab90cde56a8c0e2bb086ac49748b8db9dce72', 495 | '0xee7ab90c', 496 | '1NjxqbA9aZWnh17q1UW3rB4EPu79wDXj7x', 497 | 'cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca', 498 | 'L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU', 499 | '0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2', # nopep8 500 | '04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f', 501 | '0488b21e03bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2', # nopep8 502 | '0488ade403bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f00cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca', # nopep8 503 | 'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5', # nopep8 504 | 'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM', # nopep8 505 | ] 506 | child = self.master_key.get_child(0, True).get_child(1).get_child(-2) 507 | self._test_vector(child, *vector) 508 | self._test_vector( 509 | self.master_key.get_child_for_path("m/0'/1/2'"), *vector) 510 | self._test_vector( 511 | self.master_key.get_child_for_path("m/0p/1/2p"), *vector) 512 | self._test_deserialize(child, *vector) 513 | 514 | def test_m_0p_1_2p_2(self): 515 | vector = [ 516 | 'd880d7d893848509a62d8fb74e32148dac68412f', 517 | '0xd880d7d8', 518 | '1LjmJcdPnDHhNTUgrWyhLGnRDKxQjoxAgt', 519 | '0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4', 520 | 'KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR', 521 | '02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29', # nopep8 522 | 'cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd', 523 | '0488b21e04ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29', # nopep8 524 | '0488ade404ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd000f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4', # nopep8 525 | 'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV', # nopep8 526 | 'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334', # nopep8 527 | ] 528 | node = self.master_key.get_child(0, True).get_child(1).get_child(-2) 529 | child = node.get_child(2) 530 | self._test_vector(child, *vector) 531 | self._test_vector( 532 | self.master_key.get_child_for_path("m/0'/1/2'/2"), *vector) 533 | self._test_vector( 534 | self.master_key.get_child_for_path("m/0p/1/2p/2"), *vector) 535 | self._test_deserialize(child, *vector) 536 | 537 | def test_m_0p_1_2p_2_1000000000(self): 538 | vector = [ 539 | 'd69aa102255fed74378278c7812701ea641fdf32', 540 | '0xd69aa102', 541 | '1LZiqrop2HGR4qrH1ULZPyBpU6AUP49Uam', 542 | '471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8', 543 | 'Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs', 544 | '022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011', # nopep8 545 | 'c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e', 546 | '0488b21e05d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011', # nopep8 547 | '0488ade405d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e00471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8', # nopep8 548 | 'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy', # nopep8 549 | 'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76', # nopep8 550 | ] 551 | child = (self.master_key.get_child(0, True) 552 | .get_child(1).get_child(-2).get_child(2) 553 | .get_child(1000000000)) 554 | self._test_vector(child, *vector) 555 | self._test_vector( 556 | self.master_key.get_child_for_path("m/0'/1/2'/2/1000000000"), 557 | *vector) 558 | self._test_vector( 559 | self.master_key.get_child_for_path("m/0p/1/2p/2/1000000000"), 560 | *vector) 561 | self._test_deserialize(child, *vector) 562 | 563 | 564 | class TestWalletVectors2(_TestWalletVectors): 565 | @classmethod 566 | def setUpClass(cls): 567 | cls.master_key = Wallet.from_master_secret(binascii.unhexlify( 568 | 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a2' 569 | '9f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542' 570 | )) 571 | 572 | def test_m(self): 573 | vector = [ 574 | 'bd16bee53961a47d6ad888e29545434a89bdfe95', 575 | '0xbd16bee5', 576 | '1JEoxevbLLG8cVqeoGKQiAwoWbNYSUyYjg', 577 | '4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e', 578 | 'KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy', 579 | '03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7', # nopep8 580 | '60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689', 581 | '0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7', # nopep8 582 | '0488ade400000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689004b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e', # nopep8 583 | 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB', # nopep8 584 | 'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U', # nopep8 585 | ] 586 | self._test_vector(self.master_key, *vector) 587 | self._test_deserialize(self.master_key, *vector) 588 | 589 | def test_m_0(self): 590 | vector = [ 591 | '5a61ff8eb7aaca3010db97ebda76121610b78096', 592 | '0x5a61ff8e', 593 | '19EuDJdgfRkwCmRzbzVBHZWQG9QNWhftbZ', 594 | 'abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e', 595 | 'L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj', 596 | '02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea', # nopep8 597 | 'f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c', 598 | '0488b21e01bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea', # nopep8 599 | '0488ade401bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c00abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e', # nopep8 600 | 'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH', # nopep8 601 | 'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt', # nopep8 602 | ] 603 | child = self.master_key.get_child(0) 604 | self._test_vector(child, *vector) 605 | self._test_deserialize(child, *vector) 606 | 607 | def test_m_0_2147483647p(self): 608 | vector = [ 609 | 'd8ab493736da02f11ed682f88339e720fb0379d1', 610 | '0xd8ab4937', 611 | '1Lke9bXGhn5VPrBuXgN12uGUphrttUErmk', 612 | '877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93', 613 | 'L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr', 614 | '03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b', # nopep8 615 | 'be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9', 616 | '0488b21e025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d903c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b', # nopep8 617 | '0488ade4025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d900877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93', # nopep8 618 | 'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a', # nopep8 619 | 'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9', # nopep8 620 | ] 621 | child = self.master_key.get_child(0).get_child(2147483647, True) 622 | self._test_vector(child, *vector) 623 | self._test_vector(self.master_key.get_child(0) 624 | .get_child(-2147483647), *vector) 625 | self._test_deserialize(child, *vector) 626 | 627 | def test_m_0_2147483647p_1(self): 628 | vector = [ 629 | '78412e3a2296a40de124307b6485bd19833e2e34', 630 | '0x78412e3a', 631 | '1BxrAr2pHpeBheusmd6fHDP2tSLAUa3qsW', 632 | '704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7', 633 | 'KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2', 634 | '03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9', # nopep8 635 | 'f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb', 636 | '0488b21e03d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9', # nopep8 637 | '0488ade403d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb00704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7', # nopep8 638 | 'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon', # nopep8 639 | 'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef', # nopep8 640 | ] 641 | child = (self.master_key.get_child(0) 642 | .get_child(2147483647, True) 643 | .get_child(1)) 644 | self._test_vector(child, *vector) 645 | self._test_deserialize(child, *vector) 646 | 647 | def test_m_0_2147483647p_1_2147483646p(self): 648 | vector = [ 649 | '31a507b815593dfc51ffc7245ae7e5aee304246e', 650 | '0x31a507b8', 651 | '15XVotxCAV7sRx1PSCkQNsGw3W9jT9A94R', 652 | 'f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d', 653 | 'L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF', 654 | '02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0', # nopep8 655 | '637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29', 656 | '0488b21e0478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2902d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0', # nopep8 657 | '0488ade40478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2900f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d', # nopep8 658 | 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL', # nopep8 659 | 'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc', # nopep8 660 | ] 661 | child = (self.master_key.get_child(0) 662 | .get_child(2147483647, True) 663 | .get_child(1) 664 | .get_child(2147483646, True)) 665 | self._test_vector(child, *vector) 666 | self._test_deserialize(child, *vector) 667 | 668 | def test_m_0_2147483647p_1_2147483646p_2(self): 669 | vector = [ 670 | '26132fdbe7bf89cbc64cf8dafa3f9f88b8666220', 671 | '0x26132fdb', 672 | '14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt', 673 | 'bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23', 674 | 'L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK', 675 | '024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c', # nopep8 676 | '9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271', 677 | '0488b21e0531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c', # nopep8 678 | '0488ade40531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed27100bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23', # nopep8 679 | 'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt', # nopep8 680 | 'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j', # nopep8 681 | ] 682 | child = (self.master_key.get_child(0) 683 | .get_child(2147483647, True) 684 | .get_child(1) 685 | .get_child(2147483646, True) 686 | .get_child(2)) 687 | self._test_vector(child, *vector) 688 | self._test_vector(self.master_key.get_child(0) 689 | .get_child(-2147483647) 690 | .get_child(1) 691 | .get_child(-2147483646) 692 | .get_child(2), *vector) 693 | self._test_deserialize(child, *vector) 694 | 695 | 696 | class _TestWalletVectorsBip32org(TestCase): 697 | """Test vectors generated with bip32.org""" 698 | def _test(self, key, private_key_b58, private_key_wif, 699 | pubkey_b58, pubkey_hex, address, include_private=True): 700 | if include_private: 701 | self.assertEqual(key.serialize_b58(), private_key_b58) 702 | self.assertEqual(key.export_to_wif(), private_key_wif) 703 | self.assertEqual(key.serialize_b58(private=False), pubkey_b58) 704 | self.assertEqual(key.get_public_key_hex(), ensure_bytes(pubkey_hex)) 705 | self.assertEqual(key.to_address(), address) 706 | 707 | def _test_deserialize(self, child, *vector): 708 | self._test( 709 | Wallet.deserialize( 710 | child.serialize(private=True), network=self.network), 711 | *vector) 712 | self._test( 713 | Wallet.deserialize( 714 | child.serialize(private=False), network=self.network), 715 | *vector, include_private=False) 716 | 717 | 718 | class _TestWalletVectorsDogecoin(_TestWalletVectorsBip32org): 719 | network = DogecoinMainNet 720 | """ 721 | This is a reduced test because Dogecoin doesn't have official vectors. 722 | 723 | I generated these test values using http://bip32.org 724 | """ 725 | @classmethod 726 | def setUpClass(cls): 727 | cls.master_key = Wallet.deserialize( 728 | 'dgpv51eADS3spNJh8qd8KgFeT3V2QZBDSkYUqbaKDwZpDN4jd3uLcR7i6CruVDsb' 729 | 'acyx3NL2puToxM9MQYhZSsD8tBkXeQkm5btsKxpZawwPQND', 730 | cls.network 731 | ) 732 | 733 | 734 | class TestWalletVectorsDogecoin1(_TestWalletVectorsDogecoin): 735 | def test_m_0p(self): 736 | vector = [ 737 | 'dgpv54rTeYviMxmUs9cNrWWrvqJZ5C6bfH7yV66f1k9p6EBtFPSiGe8X3zP9e3YyarxzcYHWgbuuc3PcNFynEYyDFNS7yNWbisqdU9nYy2bZGPD', # nopep8 738 | 'QTndqZdNU46ndUrbHzMC3rqSP5PWdE3vfEeRrUDZxEHXveLwbpta', 739 | 'dgub8ojUzErbv7RpA1GXtk8q3gr9XkUEVQ9gmgssArYntMEtoSZQgQgHhHnoDJ8Wp4swrdBSmQs7WZWp5q96TjgW8k1HpqyyfpqEvq4MD6cNMgn', # nopep8 740 | '037379173b8d4a681c2dfe1d4ea4c0961f3087f7e52380e0d20d617ba175ba18ce', # nopep8 741 | 'DCJCTZdCiddc47n23zoaJ1cWCXpkYLfyYJ' 742 | ] 743 | key = self.master_key.get_child(0, True) 744 | self._test(key, *vector) 745 | self._test_deserialize(key, *vector) 746 | 747 | def test_m_0p_1(self): 748 | vector = [ 749 | 'dgpv55yu5Hmd9XBBe1UNqhzUuy77eWQyBiyBGHxKrUoZGFGe3foc9AuJxVQ5e8K6C3LogwyGkEmJVwZ9kWCdg8vd61WRXpcJ6fqosi7Q69teU9r', # nopep8 750 | 'QUf7sx5yK5Jw6a9rHuMsRwYv3WrdzfMfwX7mwb6MG6CZ4T1TYcBW', 751 | 'dgub8prvQyhWhfqWvs8XswcT2pei74nc1qztYtjY1bCY4NKebivJYwT5bnojDMRLqR7pnWY46yChRSoeYYCLxGrQiWWbhvBWi6WSR6kQaabSGdN', # nopep8 752 | '02cbcaa03b355646ac834df6bd24744f1fbe801a9168744604171b6e228f44d4b4', # nopep8 753 | 'DF1kyuBcTfUwx5rvEXWLBwjUDJZVXqyNkD' 754 | ] 755 | key = (self.master_key.get_child(0, True) 756 | .get_child(1)) 757 | self._test(key, *vector) 758 | self._test_deserialize(key, *vector) 759 | 760 | def test_m_0p_1_2p(self): 761 | vector = [ 762 | 'dgpv585jjaM2m4VAQHfu9TQ9iGEiyeRbJbiwF3mqxoC7ER8FDc5rCtmVckFRQXH5XpwBiLduR5PjB85s2n1DBLqYAXkhuXC6AMmfw1mF9MkkiqJ', # nopep8 763 | 'QWuyvRUVSzGsPcV6r7wZD51GK8tHJ5CSHpccAdr5ojFaLSQmHXqu', 764 | 'dgub8rxm5GGvKD9Vh9L4Bh27q7nKSCoE8ikeXeZ47ub62YBFmfCYcfKGG3f4yijWNXbZDuss3HKmJAZsjkFcC63SiPUYHxLXLnXaGg2Etq6UdSD', # nopep8 765 | '0235881bfba654b68153c3e781588d2c161defeb273ff6bd333b1075f0102c8cd7', # nopep8 766 | 'D6VUtfV7L874S9c5Vcxmek3aRV3hDS6eqR' 767 | ] 768 | key = (self.master_key.get_child(0, True) 769 | .get_child(1) 770 | .get_child(2, True)) 771 | self._test(key, *vector) 772 | self._test_deserialize(key, *vector) 773 | 774 | def test_m_0p_1_2p_2(self): 775 | vector = [ 776 | 'dgpv59H2Cgx4gUkZwbZH44mDznT18YtBstwstes8dR5H3fVLzXzo91dwGDFRhTHY637rus3akpLUe1EQ54rsBqxGj5ZJRrxxZc7GSw2bBX9FJWi', # nopep8 777 | 'QQMEq6rxPz7ZToTtfkGbshrNzn13kXLUUG6733rrqArzpaWCnYv5', 778 | 'dgub8tA3YNsxEdQuETDS6JPC7dzbb7Fpi1ybBFeLnXUFqnYMYb7VYnBhuWf5GgFYWN8B4vSqfSfpEgdR97QSBgMAyp3w2JHyzPCxP41nRXAMdno', # nopep8 779 | '0238859645107ce894071e0ba4243b512d4d0fcd9fc49c0d1f1fe98ab86afe2179', # nopep8 780 | 'D5zp6rHbVHWepGxonaSG1CKAmSY7fgRZdW' 781 | ] 782 | key = (self.master_key.get_child(0, True) 783 | .get_child(1) 784 | .get_child(2, True) 785 | .get_child(2)) 786 | self._test(key, *vector) 787 | self._test_deserialize(key, *vector) 788 | 789 | def test_m_0p_1_2p_2_1000000000(self): 790 | vector = [ 791 | 'dgpv5B7qzmM1EoGC3RtP2wNxfAxTZsZ8ULrAeTNgTxABfSsdUSGkwpskeHaixWSW4urESb5ATNA49QhJK39RR8wzbXJrkLbBvMiv2MzTCTB3wJR', # nopep8 792 | 'QW8D8EFkCa5JqLg4zeDwBj7iuk3jyGgEyFmwV6kUSQQhjwZk3nBv', 793 | 'dgub8uzsLTGtnwvXLHYY5Azvn2W42RvmJTssw49td4ZATZve2VPTMbRXHazNXiqqASTcaTsxoPRhsZAeiY3XA9gaJH6dJkZNUw3LwRvbVAYtuEL', # nopep8 794 | '029aa4dc7df5d6b55058ab29b7e4020dbb7253aec3ecfa31fcd3170d2b26ec61b1', # nopep8 795 | 'DTRNwCes9k4xqLb9iD9kpSUpLpfsnQk2uw' 796 | ] 797 | key = (self.master_key.get_child(0, True) 798 | .get_child(1) 799 | .get_child(2, True) 800 | .get_child(2) 801 | .get_child(1000000000)) 802 | self._test(key, *vector) 803 | self._test_deserialize(key, *vector) 804 | 805 | 806 | class TestWalletVectorsDogecoin2(_TestWalletVectorsDogecoin): 807 | def test_m(self): 808 | vector = [ 809 | 'dgpv51eADS3spNJh8qd8KgFeT3V2QZBDSkYUqbaKDwZpDN4jd3uLcR7i6CruVDsbacyx3NL2puToxM9MQYhZSsD8tBkXeQkm5btsKxpZawwPQND', # nopep8 810 | 'QPNHZTWZzk2tdNknJqkP5SS4jwqjHwsDA4i4oPcsQ1abCck5dZzx', 811 | 'dgub8kXBZ7ymNWy2RhHHMuscZu2cs7YrGsaC8CMXP3xo1V7kB7232BfUjWGZ4VS8wHCPDNWmJdCZjo81gbpm1Co2pLyNSjpqDJYmMTGKeyAGuo9', # nopep8 812 | '0371070e700787e78e1810b2843c0723fcf25643f9de9bb90fca95ca2941dd485c', # nopep8 813 | 'DMeAv9o4rFgDTFDhSYupoRHEwNmE98FDDi' 814 | ] 815 | self._test(self.master_key, *vector) 816 | self._test_deserialize(self.master_key, *vector) 817 | self._test_deserialize(self.master_key, *vector) 818 | 819 | def test_m_0(self): 820 | vector = [ 821 | 'dgpv54rTeYva2JEWgt3hvAU6ukxYEgeKwe9Nh5CNhYkdvRDL2SrhuWHurhsER1cbNuHrtUcRVrSgJ3so8PX7V2Bn6KLhYzq9GZickzbrsavazMV', # nopep8 822 | 'QSk4UkgRmxH6XDBofiUZad7grkSKj4NQsxyKWniaoun4Az3fmmdE', 823 | 'dgub8ojUzErTaStqyjhrxQ652cW8hF1xmmB5yfyarf9ciYGLaVyQKGqgW1GszEmFjCQkXWGV9SkCpjUfNpc4mQW1EcoBBsQoe4RV61QJC4G9X3d', # nopep8 824 | '029820c6a6046cebadb9a40c717326b004257f4cc111010b571daacfe58b542565', # nopep8 825 | 'D6DLgbqjac4JqFQj7TkU2q5uqVAKFvAUHz' 826 | ] 827 | key = self.master_key.get_child(0) 828 | self._test(key, *vector) 829 | self._test_deserialize(key, *vector) 830 | 831 | def test_m_0_2147483647p(self): 832 | vector = [ 833 | 'dgpv55VT18PENZdLnv1jMvtZXELhZCfzsFmFG9Qoe5ktw4oLSirqapnwx3x9nHL6jCZSRkYMKwcziQAZmKgBGbttC6kFbwoTfGWtNWnF5hFQwsk', # nopep8 834 | 'QVvKXa4BZKMok5WLJVPY9mF6YkhWaS5BJeM8pMfGBufKUKyjiwoL', 835 | 'dgub8pNULpK7viHg5mftQAWXe5tJ1m3dhNnxYkC1oC9sjBrLzmyXzbLibMMoMYSUC34ETAQM3BVUHbdgvuFFvCGLTLifbqLeiR67x2rfcndnNC8', # nopep8 836 | '03f91b9fe3110c8cbe885666e9a86237114faf54d6e124a20320a10b7847ad8e7c', # nopep8 837 | 'DFLc2YseDUQkS9gChgzjuFA7MNUo2Kz2ch' 838 | ] 839 | key = self.master_key.get_child(0).get_child(2147483647, True) 840 | self._test(key, *vector) 841 | self._test_deserialize(key, *vector) 842 | 843 | def test_m_0_2147483647p_1(self): 844 | vector = [ 845 | 'dgpv587FrzGt8WwxqYnVBDDrEmfR5TVjBaH6p2js6YQ6S7arktrwK9Pt4AqzcLMuUnbrCJyUnomeYHHfxp9qDrtJLUJPaEyjg263PfzYgCwXnjH', # nopep8 846 | 'QQrZo8xssr5ryo8PBUsu2ukzbGEYu8khaZnTYYDDA8p8xwqe8Ub4', 847 | 'dgub8rzHCgCmgfcJ8QSeDSqpMdD1Y1sN1hJp6dX5Feo5EEdsJwydiuwehUFeBcBPyyTMTCToL5E3DsWtMpDLD7ZxwXDHA5Ty7kxnLgK4SxsMmbg', # nopep8 848 | '03bf5c6222396af17f76f577d5b4f1ab291ef051ae538eab1db8586f5de6112aa7', # nopep8 849 | 'DRwmLE3MgfPigYkeZ8nJArb37eCrrubVqM' 850 | ] 851 | key = (self.master_key.get_child(0). 852 | get_child(2147483647, True). 853 | get_child(1)) 854 | self._test(key, *vector) 855 | self._test_deserialize(key, *vector) 856 | 857 | def test_m_0_2147483647p_1_2147483646p(self): 858 | vector = [ 859 | 'dgpv5AqzQ1J7t4unSJCG7GhNnvSEnicPLNVdjB1xXseffVjc7HnkCBAJ7vDvZdqYUX3xnhhPDXqTKdwLGkuiLqMjvFoPcSbATYxEgCMYsqexwQB', # nopep8 860 | 'QP3TK4n8NaHY35rkzSYFCPX2zMXxjshjVUXcqpStspQ6HE1qsbXX', 861 | 'dgub8uj1jhE1SDa7j9rR9WKLumyqFGz2AVXM1moAgz3eTcncfLuSbwi4mDda8uKTuyiw7K66K2CwXY7KPMCGk7rQD7V6CtZ2yr4EVoBPMTqt4Bd', # nopep8 862 | '035cd9e4427e59a367b04ca0b34be7a78968d713004bcf1917fcc11c94c04e4477', # nopep8 863 | 'DK6Rf67LRccSVPA8ew1jtDsMV2W6deokqg' 864 | ] 865 | key = (self.master_key.get_child(0) 866 | .get_child(2147483647, True) 867 | .get_child(1) 868 | .get_child(2147483646, True)) 869 | self._test(key, *vector) 870 | self._test_deserialize(key, *vector) 871 | 872 | def test_m_0_2147483647p_1_2147483646p_2(self): 873 | vector = [ 874 | 'dgpv5CB6BEhuLSCN16LBqxDgpLZMtAmu88f6c376sK4FVds1PYgSyAy8dB4oMXwxmDiuodEVKEMoWzaDci3fmpi2yE9eE2jQjHcvQ2ojqQLLcrH', # nopep8 875 | 'QWLvYMin1VjHqQuS33nq23CMmACUxBxAfYLmZXYaaGuJHjp5T5Lx', 876 | 'dgub8w47WvdntarhHwzLtBqewC6xLj9XxFgotdtK2RTEHkv1wbo9NwWuGUUSvnSy57u82XKNZkxzfv6iNTcakj8VLzhtnhCpKLrdW4spZB7eosx', # nopep8 877 | '03b5770cca42dd6159a22113a4f1970794d5db993a46a45bcd1c4ee6399003d394', # nopep8 878 | 'DNoz4kLEcUENEjUceiugpw6hGPgmFJoc7C' 879 | ] 880 | key = (self.master_key.get_child(0) 881 | .get_child(2147483647, True) 882 | .get_child(1) 883 | .get_child(2147483646, True) 884 | .get_child(2)) 885 | self._test(key, *vector) 886 | self._test_deserialize(key, *vector) 887 | 888 | 889 | class _TestWalletVectorsLitecoin(_TestWalletVectorsBip32org): 890 | network = LitecoinMainNet 891 | """ 892 | This is a reduced test because Litecoin doesn't have official vectors. 893 | 894 | I generated these test values using http://bip32.org 895 | """ 896 | @classmethod 897 | def setUpClass(cls): 898 | cls.master_key = Wallet.deserialize( 899 | 'Ltpv71G8qDifUiNetGsQje8NP1KYECbgKwSP2kugo4qN9GPfJ1KB8XMXxqBTYsA5' 900 | 'PgwQaFV9u5PhKxosbjcnKKCPpPvaYVSUz24KMctXYig39Te', 901 | cls.network 902 | ) 903 | 904 | 905 | class TestWalletVectorsLitecoin1(_TestWalletVectorsLitecoin): 906 | def test_m_0p(self): 907 | vector = [ 908 | 'Ltpv74V6By3UsgGzZw27UtyGEkYeGUUyP8DeDLwnVNwrkaUHxnai5mJbmAG6JHaKSnZhZMxXyhQXU4NTqqygJxKiNt1MdKgr7jEuDZ4uagqrKDa', # nopep8 909 | 'TAqpVhaoeiN17bd7keFxKc4nAhAXFaEVuXQcyRQvBh51LxPLkAAX', 910 | 'Ltub2VfRnkU27poxBoiwjWTeKLNri3BATNnJHs3pAMi9gmFtZ9mnQgM2mmNMYdmG16ksFsF3NURRQBirSkAnNTr4gm7Mq85EBCUNCopnJieQvAr', # nopep8 911 | '027bd1f86dcd5bab63040f8f334e56d206959031df9291e4721e018e7206dcf8a2', # nopep8 912 | 'Lf9q6hjcHBcyqLbFvUAeW8XrAVtmjksK2x' 913 | ] 914 | key = self.master_key.get_child(0, True) 915 | self._test(key, *vector) 916 | self._test_deserialize(key, *vector) 917 | 918 | def test_m_0p_1(self): 919 | vector = [ 920 | 'Ltpv76dbn3DT9k1QZk4jobJ9U4i32rWgwg361gCWCBw6YpEmWd6njuHBnqD9GsP96ZuvPzohf53SwM2WCpd9tRAyxKZ98PHehciT676FoTUJFjG', # nopep8 921 | 'T3mHwrvvGQAQYgw6NkEAbvnEW7DuVhHAoHxbrvdrRnCH9ugE3d5W', 922 | 'Ltub2XowNpdzPtYNBcma4CnXYeYFURCt1vbk6CJXsAhPV12N6zHs4pKcoSKQXHHb7cTDY9gUxQ95EBYxTTYE2cqjZmvw64uvXAqgx5f5i8DTtnd', # nopep8 923 | '0391f78495549245157979b19b8c6ddad42f4092602819d85278ad22db87cb6730', # nopep8 924 | 'LPJZvWyD3i6JSMnwJtcLEMX7kYbPLbTqzE' 925 | ] 926 | key = (self.master_key.get_child(0, True) 927 | .get_child(1)) 928 | self._test(key, *vector) 929 | self._test_deserialize(key, *vector) 930 | 931 | def test_m_0p_1_2p(self): 932 | vector = [ 933 | 'Ltpv77EctZnoc1SgaSffnPGVABbenBaeYBsiiNquoszDNKphMw3i4AHfJ2NaKRgKV5mUjw9qez5qRF1HTZxXegUE3W1ebdbWVETdKDMxa587Gnk', # nopep8 934 | 'T9VTp2qTW18DYLN6Rc6a2XP2cWy2mfLqNAQZDLJm27TdsZUG9jL7', 935 | 'Ltub2YQxVMDLr9yeCKNW2zksEmRsDkGqcSSNntwwUrkWJWcHxJEnP5L6JdUqZpdzFr7ijwz4xH3fL9E9jn5246F2nNHhPsSpWrk8Bu5zYDb5LTY', # nopep8 936 | '03c0767a6c05d488b79465e973604eeeb008bfc8646877afb6237e483937beb788', # nopep8 937 | 'LdkUqtqarziHXTnC6borMadsfQesVzEJXx' 938 | ] 939 | key = (self.master_key.get_child(0, True) 940 | .get_child(1) 941 | .get_child(2, True)) 942 | self._test(key, *vector) 943 | self._test_deserialize(key, *vector) 944 | 945 | def test_m_0p_1_2p_2(self): 946 | vector = [ 947 | 'Ltpv7AHK5coBDoEZFnWC7WtcSJq58fWbJuM2FrmWVitWz9HoeQijqcW48v1N3aYHp8hkgYKHbzvqydDdPu6Lv6MykvxWhCfifdP5yAJQzHqWKeg', # nopep8 948 | 'T9tMP13KhuheJkJGFXaAM2REkFCDfHh5ebF1EHYDShTFG6t775gp', 949 | 'Ltub2bTegQDiTwmWsfD2N8NzWtfHaECnP9ugLNsYAheovL5QEmupAXYV9X7dHvJmMptrs9dHRkfPxY7iTYz5Mp8nAPkR26GzqCMVkiV7bVPCn9b', # nopep8 950 | '0227bbb5af873704535c17e7cf3cbd087760d0f4027ec5a44aa35afaba6e7d0266', # nopep8 951 | 'LgML9sstrXSnbxEv5UT9VYgp2dE7Je64FH' 952 | ] 953 | key = (self.master_key.get_child(0, True) 954 | .get_child(1) 955 | .get_child(2, True) 956 | .get_child(2)) 957 | self._test(key, *vector) 958 | self._test_deserialize(key, *vector) 959 | 960 | def test_m_0p_1_2p_2_1000000000(self): 961 | vector = [ 962 | 'Ltpv7CNcMprvXc2ZzLAXxhuYeKL5N62NnpDsHNnS3gxFr7eJndZL3vKrXrvyMR2miDk7LSrRKQ7gSKWQwhCMLxuazuRFuCCZNbM5NXUBTrwAJwb', # nopep8 963 | 'TAefeF5TJVCTFRozizQtVJ9ku6gs1EdBKko3yea23o6w3ESVhQZY', 964 | 'Ltub2dYwxcHTmkZXcCsNDKPviuAHoeiZs4nXMttTifiYnJRuNzkQNqNHYU3EbkUg3oz1WvkLs9vnNNGPa5vgHbeBjWZZ7YQbERi8E9GnAzXnVHw', # nopep8 965 | '02145bf57dcfe571710c61143adb44e80dd2ca44910b89406862962545fa567c96', # nopep8 966 | 'LQWUfR2ybmJGyLSps2fVSTCa9zmr9p9RQi' 967 | ] 968 | key = (self.master_key.get_child(0, True) 969 | .get_child(1) 970 | .get_child(2, True) 971 | .get_child(2) 972 | .get_child(1000000000)) 973 | self._test(key, *vector) 974 | self._test_deserialize(key, *vector) 975 | 976 | 977 | class TestWalletVectorsLitecoin2(_TestWalletVectorsLitecoin): 978 | def test_m(self): 979 | vector = [ 980 | 'Ltpv71G8qDifUiNetGsQje8NP1KYECbgKwSP2kugo4qN9GPfJ1KB8XMXxqBTYsA5PgwQaFV9u5PhKxosbjcnKKCPpPvaYVSUz24KMctXYig39Te', # nopep8 981 | 'T4HX1Wffx49Wbdfog3RF31m7P611LT8KPc17ZB4USQTMCZhazwNn', 982 | 'Ltub2SSUS19CirucW9aEzFckTb9kfmHsQC137H1iU3bf5TBFtNWFTSPxySHioHCHEtCb3NPSZn1FJM6joFKevvxx6vV4ggaQcKiYzaNucXpRyY8', # nopep8 983 | '03b3204919fa92d16d869fc39f3510e0bc7b2ce53c1bf6124448f2cbbbaf29db38', # nopep8 984 | 'Lbs921f129AWWyb5kfdtSefUgreidPwqAP' 985 | ] 986 | self._test(self.master_key, *vector) 987 | self._test_deserialize(self.master_key, *vector) 988 | self._test_deserialize(self.master_key, *vector) 989 | 990 | def test_m_0(self): 991 | vector = [ 992 | 'Ltpv74V6By3LY1k2RyNdNhyBCtTgxbu6VrSDx8177z5g9phb8mmiJnC5dyGEL1AxCX4BWWJEcZBxep1j7wAPUp3jXqramror3Rdtg76ZNfwqeMr', # nopep8 993 | 'T9ARV5FUdiaXyp8boDPj9H2FjWxvdEUHaURrgsVeZjVFGTesNfku', 994 | 'Ltub2VfRnkTsnAGz3r5TdKTZHUHuQAbHa6zt2e78nxqy61VBj8xndhEWeaNVaQ6N8SLEPiF8UxEXkFBbPyKiutX6FhbKJXnQAaTRXw56e8zm2qs', # nopep8 995 | '03b18ba94530690859a3f6ebb2b866d151f8499b3164d027ba5b464e4ed71329aa', # nopep8 996 | 'LftGBdKxo8aXqvs74g71692apDXQaPz17Z' 997 | ] 998 | key = self.master_key.get_child(0) 999 | self._test(key, *vector) 1000 | self._test_deserialize(key, *vector) 1001 | 1002 | def test_m_0_2147483647p(self): 1003 | vector = [ 1004 | 'Ltpv76h2CpMgsQfUbzphcJBPEBDEeP8U9WizJtRsxwTzq8LeSE6eVhZ5nzvPPxK8HifWH3GCpys6qWnzpBeFVVBaKQRki3tEN82PJUi3SdBQZcr', # nopep8 1005 | 'T3kqK41soU9GEWrDYERqqHo8kcQvVhu9J8tmk8RRqshTi5FWUtBw', 1006 | 'Ltub2XsMobnE7ZCSDsXXrufmJm3T5wpfDmHePQXudvEHmK8F2bHipcbWoc2eeNsqqjXRCgLQgkV1jS83mAZBtHqyaqPu1hkyUU1bhoQ9Pd2Fry2', # nopep8 1007 | '03e89f5654eb8489c71bb68f9df7d28c6f48a0f46c6fc2bef5ae11bb5536cebbb7', # nopep8 1008 | 'LbMKq3XN8iUCJnCfxM8SgC6YGjoMazcrz3' 1009 | ] 1010 | key = self.master_key.get_child(0).get_child(2147483647, True) 1011 | self._test(key, *vector) 1012 | self._test_deserialize(key, *vector) 1013 | 1014 | def test_m_0_2147483647p_1(self): 1015 | vector = [ 1016 | 'Ltpv78CxVKndj3P8PDBogzwnrxY7Pxtzn8vgExPZFVHeM42MGGNQ9wUJ2isvXPDHLGehGi8DdeFgNPQTGRMEKH72242jutfwB7PFbeKMhSA3pqk', # nopep8 1017 | 'T3MWHKyN3BVcQ67wLR5H7rsJx3bhmbichmz3p38FeCrTjXE9Xbup', 1018 | 'Ltub2ZPJ67DAyBv615tdwcSAwYNKqXbBrPVLKUVavU3wHEowrdZUUrWj3KzBmoW21GQSDZBNjMs2SZ2bdBREZLH3HcHT7W2DT6DZiNsNtsEZfqF', # nopep8 1019 | '03b855e07eb1837015cbd465b921fb476f99b38cf575f61ec7f594f839f42b5057', # nopep8 1020 | 'Lbqhtn3eQTiyYKWvktnxGsgSa3bEa4kNrH' 1021 | ] 1022 | key = (self.master_key.get_child(0). 1023 | get_child(2147483647, True). 1024 | get_child(1)) 1025 | self._test(key, *vector) 1026 | self._test_deserialize(key, *vector) 1027 | 1028 | def test_m_0_2147483647p_1_2147483646p(self): 1029 | vector = [ 1030 | 'Ltpv7A8P4c3YUGWoxQAsSeGJfm45zJW5fRcVqcvo9a1RifrR2z2EokC4JUnfb23mZDG1a5obbdA3KyfriaxfxdeiCCszjUwy4Tnx4NyZJhg2TXZ', # nopep8 1031 | 'T3JRr9ymVwWqVhsed96q3hcmKVgmqw2SKGQWepCkCTEmS8bDGRiL', 1032 | 'Ltub2bJifPU5iR3maGshhFkgkLtJRsCGjgB9v92ppYmiere1dMDK8fEVK5tvqR9xBf37tXswdRY7T92jbT9L1borcpBXMhYe2cxiKB3HTCtKJf4', # nopep8 1033 | '031b189497b7661fb452af508ee3e014aaac34b366fc4ea178573bf9263a824bc6', # nopep8 1034 | 'LPeSxqQM6qyFVAutnGwRDZbABLSMK7gtDD' 1035 | ] 1036 | key = (self.master_key.get_child(0) 1037 | .get_child(2147483647, True) 1038 | .get_child(1) 1039 | .get_child(2147483646, True)) 1040 | self._test(key, *vector) 1041 | self._test_deserialize(key, *vector) 1042 | 1043 | def test_m_0_2147483647p_1_2147483646p_2(self): 1044 | vector = [ 1045 | 'Ltpv7B2Va6jguSVYkxsVaYVykvfVURqLTNQVn2pYs6MHFzVtFHPUAgCqbCMChCFPmvjDKfFJZQBmyztATaZTeLpaSvpP6zcaY5DJD5Qcr66MjTW', # nopep8 1046 | 'T6u7ZdbVd4B8KWuiTdirhwJ5NNrHG73WWVatXtktmGayPERcFMMa', 1047 | 'Ltub2cCqAtAE9b2WNqaKq9zMqWVhuzXXXcy9rYvaY57aCBHUqeaYVbFGboTTwbhrJc6SezAA3mrUEKi3qey31HZHDnFfcwXYtkD3dbswWCRyKQu', # nopep8 1048 | '03b47c7d3f7eb51023206f636276fe6c3a0c51752360b12ec556b86849ca47b3fe', # nopep8 1049 | 'Ld5QMVg5tych8UKBBs1Q2LxbbVFASfv3tf' 1050 | ] 1051 | key = (self.master_key.get_child(0) 1052 | .get_child(2147483647, True) 1053 | .get_child(1) 1054 | .get_child(2147483646, True) 1055 | .get_child(2)) 1056 | self._test(key, *vector) 1057 | self._test_deserialize(key, *vector) 1058 | -------------------------------------------------------------------------------- /tests/test_bip32_vector.py: -------------------------------------------------------------------------------- 1 | import json 2 | from unittest import TestCase 3 | 4 | from multimerchant.network import BitcoinMainNet 5 | from multimerchant.wallet import Wallet 6 | from multimerchant.wallet.utils import ensure_bytes 7 | 8 | 9 | class TestBIP32(TestCase): 10 | def _test_wallet(self, wallet, data): 11 | self.assertEqual( 12 | wallet.serialize_b58(private=True), data['private_key']) 13 | self.assertEqual( 14 | wallet.serialize_b58(private=False), data['public_key']) 15 | self.assertEqual(wallet.export_to_wif(), data['wif']) 16 | self.assertEqual(wallet.chain_code, ensure_bytes(data['chain_code'])) 17 | fingerprint = ensure_bytes(data['fingerprint']) 18 | if not fingerprint.startswith(b'0x'): 19 | fingerprint = b'0x' + fingerprint 20 | self.assertEqual(wallet.fingerprint, fingerprint) 21 | self.assertEqual(wallet.depth, data['depth']) 22 | self.assertEqual( 23 | wallet.private_key._private_key.privkey.secret_multiplier, 24 | data['secret_exponent']) 25 | 26 | def test_bip32(self): 27 | with open("tests/bip32_test_vector.json", 'r') as f: 28 | vectors = json.loads(f.read()) 29 | for wallet_data in vectors: 30 | wallet = Wallet.deserialize( 31 | wallet_data['private_key'], network=BitcoinMainNet) 32 | self._test_wallet(wallet, wallet_data) 33 | for child_data in wallet_data['children']: 34 | child = wallet.get_child_for_path(child_data['path']) 35 | self._test_wallet(child, child_data['child']) 36 | -------------------------------------------------------------------------------- /tests/test_key_vector.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from unittest import TestCase 4 | 5 | from multimerchant.network import BitcoinMainNet 6 | from multimerchant.wallet.keys import PrivateKey 7 | from multimerchant.wallet.keys import PublicKey 8 | 9 | 10 | class TestKeys(TestCase): 11 | def test_keys(self): 12 | with open("tests/keys_test_vector.json", 'r') as f: 13 | vectors = json.loads(f.read()) 14 | for vector in vectors: 15 | private_key = PrivateKey.from_wif( 16 | vector['private_key'], network=BitcoinMainNet) 17 | public_key = PublicKey.from_hex_key( 18 | vector['pubkey'], network=BitcoinMainNet) 19 | self.assertEqual(private_key.get_public_key(), public_key) 20 | self.assertEqual(public_key.to_address(), vector['address']) 21 | -------------------------------------------------------------------------------- /tests/test_keys.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | from unittest import TestCase 3 | 4 | import base58 5 | 6 | from multimerchant.network import BitcoinMainNet 7 | from multimerchant.network import BitcoinTestNet 8 | from multimerchant.network import DogecoinMainNet 9 | from multimerchant.network import LitecoinMainNet 10 | from multimerchant.wallet.keys import ChecksumException 11 | from multimerchant.wallet.keys import IncompatibleNetworkException 12 | from multimerchant.wallet.keys import KeyParseError # TODO test this 13 | from multimerchant.wallet.keys import PrivateKey 14 | from multimerchant.wallet.keys import PublicKey 15 | from multimerchant.wallet.utils import ensure_bytes 16 | from multimerchant.wallet.utils import long_or_int 17 | 18 | 19 | class _TestPrivateKeyBase(TestCase): 20 | @classmethod 21 | def setUpClass(cls): 22 | # This private key chosen from the bitcoin docs: 23 | # https://en.bitcoin.it/wiki/Wallet_import_format 24 | cls.expected_key = \ 25 | b"0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d" 26 | cls.key = PrivateKey(long_or_int(cls.expected_key, 16)) 27 | 28 | 29 | class _TestPublicKeyBase(TestCase): 30 | @classmethod 31 | def setUpClass(cls): 32 | # This private key chosen from the bitcoin docs: 33 | # https://en.bitcoin.it/wiki/Wallet_import_format 34 | cls.expected_private_key = \ 35 | b"18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725" 36 | cls.private_key = PrivateKey( 37 | long_or_int(cls.expected_private_key, 16)) 38 | cls.public_key = PublicKey.from_hex_key( 39 | "04" 40 | "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" 41 | "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6") 42 | 43 | 44 | class TestPrivateKey(_TestPrivateKeyBase): 45 | def test_raw_key_hex(self): 46 | exp = self.key._private_key.privkey.secret_multiplier 47 | self.assertEqual(PrivateKey(exp), self.key) 48 | 49 | def test_raw_key_hex_bytes(self): 50 | key = binascii.unhexlify(self.key.get_key()) 51 | self.assertEqual(PrivateKey.from_hex_key(key), self.key) 52 | 53 | def test_from_master_password(self): 54 | password = "correct horse battery staple" 55 | expected_wif = "5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS" 56 | expected_pub_address = "1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T" 57 | 58 | key = PrivateKey.from_master_password(password) 59 | self.assertEqual(key.export_to_wif(), expected_wif) 60 | self.assertEqual( 61 | key.get_public_key().to_address(), expected_pub_address) 62 | 63 | def test_invalid_exponent(self): 64 | self.assertRaises(ValueError, PrivateKey, 'abcd') 65 | 66 | 67 | class TestWIF(_TestPrivateKeyBase): 68 | @classmethod 69 | def setUpClass(cls): 70 | super(TestWIF, cls).setUpClass() 71 | cls.expected_wif = \ 72 | '5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ' 73 | 74 | def test_export_to_wif(self): 75 | self.assertEqual( 76 | self.key.export_to_wif(), 77 | self.expected_wif) 78 | 79 | def test_import_wif(self): 80 | key = PrivateKey.from_wif(self.expected_wif) 81 | self.assertEqual(key, self.key) 82 | 83 | def test_import_wif_invalid_network(self): 84 | self.assertRaises( 85 | IncompatibleNetworkException, PrivateKey.from_wif, 86 | self.key.export_to_wif(), BitcoinTestNet) 87 | 88 | def test_import_wif_network(self): 89 | # Make a wif for bitcoin testnet: 90 | testnet_key = PrivateKey( 91 | self.key._private_key.privkey.secret_multiplier, 92 | network=BitcoinTestNet) 93 | testnet_wif = testnet_key.export_to_wif() 94 | # We should be able to load it properly 95 | key = PrivateKey.from_wif(testnet_wif, BitcoinTestNet) 96 | self.assertEqual(testnet_key, key) 97 | 98 | def test_bad_checksum(self): 99 | wif = self.key.export_to_wif() 100 | bad_checksum = base58.b58encode(binascii.unhexlify('FFFFFFFF')) 101 | wif = wif[:-8] + bad_checksum 102 | self.assertRaises(ChecksumException, PrivateKey.from_wif, wif) 103 | 104 | 105 | class TestPublicKey(_TestPublicKeyBase): 106 | def test_leading_zeros(self): 107 | """This zero-leading x coordinate generated by: 108 | 109 | pvk = '18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725' # nopep8 110 | from ecdsa import SECP256k1 111 | from ecdsa.ecdsa import Public_key 112 | from multimerchant.wallet.utils import long_to_hex 113 | 114 | pubkey = Public_key( 115 | SECP256k1.generator, 116 | SECP256k1.generator * long(pvk, 16)) 117 | for i in range(1, 10000): 118 | p = pubkey.point * i 119 | x = p.x() 120 | k = long_to_hex(x, 64) 121 | if k.startswith('0'): 122 | print(i) 123 | print(long_to_hex(p.x(), 64)) 124 | print(long_to_hex(p.y(), 64)) 125 | break 126 | """ 127 | expected_key = ensure_bytes( 128 | "04" 129 | "02cbfd5410fd04973c096a4275bf75070955ebd689f316a6fbd449980ba7b756" 130 | "c559764e5c367c03e002751aaf4ef8ec40fe97cda9b2d3f14fdd4cd244e8fcd2") 131 | public_key = PublicKey.from_hex_key(expected_key) 132 | self.assertEqual(public_key.get_key(), expected_key) 133 | 134 | def test_address(self): 135 | expected_address = "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM" 136 | actual_address = self.public_key.to_address() 137 | self.assertEqual(expected_address, actual_address) 138 | 139 | def test_private_to_public(self): 140 | self.assertEqual( 141 | self.private_key.get_public_key(), 142 | self.public_key) 143 | 144 | def test_unhexlified_key(self): 145 | key_bytes = binascii.unhexlify(self.public_key.get_key()) 146 | self.assertEqual( 147 | PublicKey.from_hex_key(key_bytes), 148 | self.public_key) 149 | 150 | def test_bad_key(self): 151 | self.assertRaises(KeyParseError, PublicKey.from_hex_key, 'badkey') 152 | 153 | def test_bad_network_key(self): 154 | key = self.public_key.get_key() 155 | # Change the network constant 156 | key = b"00" + key[2:] 157 | self.assertRaises(KeyParseError, 158 | PublicKey.from_hex_key, key) 159 | 160 | def test_compressed(self): 161 | compressed_key = self.public_key.get_key(compressed=True) 162 | self.assertEqual(len(compressed_key), 66) 163 | self.assertEqual( 164 | PublicKey.from_hex_key(compressed_key), self.public_key) 165 | 166 | def test_point(self): 167 | self.assertEqual(PublicKey.from_point(self.public_key.to_point()), 168 | self.public_key) 169 | 170 | def test_public_pair(self): 171 | self.assertEqual( 172 | PublicKey.from_public_pair(self.public_key.to_public_pair()), 173 | self.public_key) 174 | 175 | 176 | class TestVectors(TestCase): 177 | """Test vectors 178 | from https://github.com/bitcoin/bitcoin/blob/master/src/test/key_tests.cpp 179 | """ 180 | def _test(self, network, secret, address, compressed): 181 | key = PrivateKey.from_wif(secret, network=network) 182 | self.assertEqual(key.compressed, compressed) 183 | self.assertEqual(address, key.get_public_key().to_address()) 184 | 185 | def test_1(self): 186 | secret = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj" 187 | address = "1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ" 188 | self._test(BitcoinMainNet, secret, address, False) 189 | 190 | def test_2(self): 191 | secret = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3" 192 | address = "1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ" 193 | self._test(BitcoinMainNet, secret, address, False) 194 | 195 | def test_3(self): 196 | secret = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw" 197 | address = "1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs" 198 | self._test(BitcoinMainNet, secret, address, True) 199 | 200 | def test_4(self): 201 | secret = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g" 202 | address = "1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs" 203 | self._test(BitcoinMainNet, secret, address, True) 204 | 205 | # https://github.com/dogecoin/dogecoin/blob/master-1.5/src/test/key_tests.cpp # nopep8 206 | def test_dogecoin_1(self): 207 | secret = "6JFPe8b4jbpup7petSB98M8tcaqXCigji8fGrC8bEbbDQxQkQ68" 208 | address = "DSpgzjPyfQB6ZzeSbMWpaZiTTxGf2oBCs4" 209 | self._test(DogecoinMainNet, secret, address, False) 210 | 211 | def test_dogecoin_2(self): 212 | secret = "6KLE6U3w8x3rM7nA1ZQxR4KnyEzeirPEt4YaXWdY4roF7Tt96rq" 213 | address = "DR9VqfbWgEHZhNst34KQnABQXpPWXeLAJD" 214 | self._test(DogecoinMainNet, secret, address, False) 215 | 216 | def test_dogecoin_3(self): 217 | secret = "QP8WvtVMV2iU6y7LE27ksRspp4MAJizPWYovx88W71g1nfSdAhkV" 218 | address = "D8jZ6R8uuyQwiybupiVs3eDCedKdZ5bYV3" 219 | self._test(DogecoinMainNet, secret, address, True) 220 | 221 | def test_dogecoin_4(self): 222 | secret = "QTuro8Pwx5yaonvJmU4jbBfwuEmTViyAGNeNyfnG82o7HWJmnrLj" 223 | address = "DP7rGcDbpAvMb1dKup981zNt1heWUuVLP7" 224 | self._test(DogecoinMainNet, secret, address, True) 225 | 226 | # https://github.com/litecoin-project/litecoin/blob/master-0.8/src/test/key_tests.cpp # nopep8 227 | def test_litecoin_1(self): 228 | secret = "6uu5bsZLA2Lm6yCxgwxDxHyZmhYeqBMLQT83Fyq738YhYucQPQf" 229 | address = "LWaFezDtucfCA4xcVEfs3R3xfgGWjSwcZr" 230 | self._test(LitecoinMainNet, secret, address, False) 231 | 232 | def test_litecoin_2(self): 233 | secret = "6vZDRwYgTNidWzmKs9x8QzQGeWCqbdUtNRpEKZMaP67ZSn8XMjb" 234 | address = "LXwHM6mRd432EzLJYwuKQMPhTzrgr7ur9K" 235 | self._test(LitecoinMainNet, secret, address, False) 236 | 237 | def test_litecoin_3(self): 238 | secret = "T6UsJv9hYpvDfM5noKYkB3vfeHxhyegkeWJ4y7qKeQJuyXMK11XX" 239 | address = "LZWK8h7C166niP6GmpUmiGrvn4oxPqQgFV" 240 | self._test(LitecoinMainNet, secret, address, True) 241 | 242 | def test_litecoin_4(self): 243 | secret = "T9PBs5kq9QrkBPxeGNWKitMi4XuFVr25jaXTnuopLVZxCUAJbixA" 244 | address = "Lgb6tdqmdW3n5E12johSuEAqRMt4kAr7yu" 245 | self._test(LitecoinMainNet, secret, address, True) 246 | --------------------------------------------------------------------------------