├── .gitignore ├── AUTHORS ├── DEPRECATED.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── circle.yml ├── pkg ├── arch.txt ├── deps-deb.txt ├── description.txt ├── license.txt ├── maintainer.txt ├── name.txt ├── url.txt └── version.txt ├── pybitcoin ├── __init__.py ├── address.py ├── b58check.py ├── constants.py ├── errors.py ├── formatcheck.py ├── hash.py ├── keypair.py ├── merkle.py ├── passphrases │ ├── __init__.py │ ├── english_words.py │ ├── legacy.py │ └── passphrase.py ├── privatekey.py ├── publickey.py ├── rpc │ ├── README.md │ ├── __init__.py │ ├── bitcoind_client.py │ ├── config.py │ ├── namecoind_client.py │ └── namecoind_cluster.py ├── services │ ├── __init__.py │ ├── bitcoind.py │ ├── blockchain_client.py │ ├── blockchain_info.py │ ├── blockcypher.py │ └── chain_com.py ├── transactions │ ├── __init__.py │ ├── network.py │ ├── opcodes.py │ ├── outputs.py │ ├── scripts.py │ ├── serialize.py │ └── utils.py └── wallet.py ├── requirements.txt ├── settings.py ├── setup.cfg ├── setup.py ├── tests └── test_rpc.py └── unit_tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | # Mac Hidden File 39 | .DS_Store 40 | 41 | # Virtual Environment 42 | /venv/ 43 | 44 | ignore 45 | tests/secrets* 46 | secrets.py 47 | 48 | # swap files 49 | *.swp 50 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | pybitcoin is maintained by Halfmoon Labs and various contributors. 2 | 3 | Development Leads 4 | ```````````````` 5 | 6 | - Ryan Shea (https://github.com/rxl) 7 | - Muneeb Ali (https://github.com/muneeb-ali) 8 | 9 | 10 | Contributors 11 | ```````````````` 12 | 13 | - Michael Flaxman (https://github.com/mflaxman) 14 | - Adam Brown (https://github.com/DeftNerd) 15 | -------------------------------------------------------------------------------- /DEPRECATED.md: -------------------------------------------------------------------------------- 1 | ### Keypairs 2 | 3 | #### Random keypairs 4 | 5 | ```python 6 | >>> from pybitcoin import BitcoinKeypair 7 | >>> keypair = BitcoinKeypair() 8 | >>> keypair.private_key() 9 | '91149ee24f1ee9a6f42c3dd64c2287781c8c57a6e8e929c80976e586d5322a3d' 10 | >>> keypair.public_key() 11 | '042c6b7e6da7633c8f226891cc7fa8e5ec84f8eacc792a46786efc869a408d29539a5e6f8de3f71c0014e8ea71691c7b41f45c083a074fef7ab5c321753ba2b3fe' 12 | >>> keypair.wif_pk() 13 | '5JvBUBPzU42Y7BHD7thTnySXQXMk8XEJGGQGcyBw7CCkw8RAH7m' 14 | >>> keypair.address() 15 | '13mtgVARiB1HiRyCHnKTi6rEwyje5TYKBW' 16 | ``` 17 | 18 | #### Custom keypairs 19 | 20 | ```python 21 | >>> hex_private_key = '91149ee24f1ee9a6f42c3dd64c2287781c8c57a6e8e929c80976e586d5322a3d' 22 | >>> keypair = BitcoinKeypair(hex_private_key) 23 | ``` 24 | 25 | ### Utilities 26 | 27 | #### Random passphrases 28 | 29 | ```python 30 | >>> from pybitcoin import random_160bit_passphrase 31 | >>> random_160bit_passphrase() 32 | 'shepherd mais pack rate enamel horace diva filesize maximum really roar mall' 33 | ``` 34 | 35 | #### Random brain wallet keypairs 36 | 37 | ```python 38 | >>> keypair = BitcoinKeypair.from_passphrase() 39 | >>> keypair.passphrase() 40 | 'shepherd mais pack rate enamel horace diva filesize maximum really roar mall' 41 | >>> keypair.address() 42 | '13mtgVARiB1HiRyCHnKTi6rEwyje5TYKBW' 43 | ``` 44 | 45 | #### Custom brain wallet keypairs 46 | 47 | ```python 48 | >>> passphrase = 'shepherd mais pack rate enamel horace diva filesize maximum really roar mall' 49 | >>> keypair = BitcoinKeypair.from_passphrase(passphrase) 50 | ``` 51 | 52 | #### Altcoin keypairs 53 | 54 | ```python 55 | >>> from pybitcoin import LitecoinKeypair 56 | >>> litecoin_keypair = LitecoinKeypair() 57 | >>> litecoin_keypair.address() 58 | 'LMzqwhUFnqFLyEfMTvJkz7v1AC6v8N9Qcd' 59 | ``` 60 | 61 | ### Wallets 62 | 63 | #### Sequential Deterministic Wallets 64 | 65 | ```python 66 | >>> from pybitcoin import SDWallet, BitcoinKeypair 67 | >>> passphrase = 'shepherd mais pack rate enamel horace diva filesize maximum really roar mall' 68 | >>> wallet = SDWallet(passphrase) 69 | >>> bitcoin_keypair_1 = wallet.keypair(1, BitcoinKeypair) 70 | >>> bitcoin_keypair_1.passphrase() 71 | 'shepherd mais pack rate enamel horace diva filesize maximum really roar mall bitcoin1' 72 | >>> bitcoin_keypair_1.address() 73 | '1DS2vmsqTwtXp1DfmDHi55Aqc6w4LBUC9k' 74 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Halfmoon Labs Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include scripts/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pybitcoin 2 | ===== 3 | 4 | ## This code is deprecated and should not be used 5 | 6 | Python library with tools for Bitcoin and other cryptocurrencies. 7 | 8 | ## Usage 9 | 10 | ### Private Keys 11 | 12 | ```python 13 | >>> from pybitcoin import BitcoinPrivateKey 14 | >>> private_key = BitcoinPrivateKey() 15 | >>> private_key.to_hex() 16 | '91149ee24f1ee9a6f42c3dd64c2287781c8c57a6e8e929c80976e586d5322a3d' 17 | >>> private_key.to_wif() 18 | '5JvBUBPzU42Y7BHD7thTnySXQXMk8XEJGGQGcyBw7CCkw8RAH7m' 19 | >>> private_key_2 = BitcoinPrivateKey('91149ee24f1ee9a6f42c3dd64c2287781c8c57a6e8e929c80976e586d5322a3d') 20 | >>> print private_key.to_wif() == private_key_2.to_wif() 21 | True 22 | ``` 23 | 24 | ### Public Keys 25 | 26 | ```python 27 | >>> public_key = private_key.public_key() 28 | >>> public_key.to_hex() 29 | '042c6b7e6da7633c8f226891cc7fa8e5ec84f8eacc792a46786efc869a408d29539a5e6f8de3f71c0014e8ea71691c7b41f45c083a074fef7ab5c321753ba2b3fe' 30 | >>> public_key_2 = BitcoinPublicKey(public_key.to_hex()) 31 | >>> print public_key.to_hex() == public_key_2.to_hex() 32 | True 33 | ``` 34 | 35 | ### Addresses 36 | 37 | ```python 38 | >>> public_key.address() 39 | '13mtgVARiB1HiRyCHnKTi6rEwyje5TYKBW' 40 | >>> public_key.hash160() 41 | '1e6db1e09b5e307847e5734864a79ea0113d0083' 42 | ``` 43 | 44 | ### Brainwallet-based Private Keys 45 | 46 | ```python 47 | >>> private_key = BitcoinPrivateKey.from_passphrase() 48 | >>> private_key.passphrase() 49 | 'shepherd mais pack rate enamel horace diva filesize maximum really roar mall' 50 | >>> private_key.to_hex() 51 | '91149ee24f1ee9a6f42c3dd64c2287781c8c57a6e8e929c80976e586d5322a3d' 52 | >>> priv2 = BitcoinPrivateKey.from_passphrase(priv2.passphrase()) 53 | >>> print private_key.to_hex() == priv2.to_hex() 54 | True 55 | ``` 56 | 57 | ### Sending Transactions to Addresses 58 | 59 | ```python 60 | >>> from pybitcoin import BlockcypherClient 61 | >>> recipient_address = '1EEwLZVZMc2EhMf3LXDARbp4mA3qAwhBxu' 62 | >>> blockchain_client = BlockcypherClient(BLOCKCYPHER_API_KEY) 63 | >>> send_to_address(recipient_address, 10000, private_key.to_hex(), blockchain_client) 64 | ``` 65 | 66 | ### Sending OP_RETURN Transactions 67 | 68 | ```python 69 | >>> from pybitcoin import make_op_return_tx 70 | >>> data = '00' * 80 71 | >>> tx = make_op_return_tx(data, private_key.to_hex(), blockchain_client, fee=10000, format='bin') 72 | >>> broadcast_transaction(tx, blockchain_client) 73 | {"success": True} 74 | ``` 75 | 76 | ### Altcoins 77 | 78 | ```python 79 | >>> class NamecoinPrivateKey(BitcoinPrivateKey): 80 | >>> _pubkeyhash_version_byte = 52 81 | >>> namecoin_private_key = NamecoinPrivateKey(private_key.to_hex()) 82 | >>> namecoin_private_key.to_wif() 83 | '73zteEjenBCK7qVtG2yRPeco2TP5w93qBW5sJkxYoGYvbWwAbXv' 84 | >>> namecoin_public_key = namecoin_private_key.public_key() 85 | >>> namecoin_public_key.address() 86 | 'MyMFt8fQdZ6rEyDhZbe2vd19gD8gzagr7Z' 87 | ``` 88 | 89 | ## Supported currencies 90 | 91 | Litecoin, Namecoin, Peercoin, Primecoin, Testnet, Worldcoin, Megacoin, Feathercoin, Terracoin, Novacoin, Dogecoin, Anoncoin, Protoshares, Ixcoin, Memorycoin, Infinitecoin, Cryptogenic Bullion, Quarkcoin, Netcoin, Earthcoin, Reddcoin, (insert your favorite cryptocurrency here) 92 | 93 | ## Developers 94 | 95 | **Q:** Can I contribute to pybitcoin? 96 | 97 | **A:** Of course! Any and all are encouraged to contribute. Just fork a copy of the repo and get started on something that you think would improve the current offering. 98 | 99 | **Q:** What should I work on? 100 | 101 | **A:** That's up to you! For a quick project, consider adding support for a new cryptocurrency (should only require two lines of code, not including the unit tests). 102 | 103 | Meanwhile, for something a bit more ambitious, check the issues section for outstanding feature requests. 104 | 105 | ## Notice 106 | 107 | pybitcoin is still in beta. Developers using pybitcoin are encouraged to inspect the code for themselves and perform their own tests. We are committed to ensuring that this library behaves exactly as it is supposed to under all conditions, and have plans to ramp up our testing efforts going forward. 108 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | python: 3 | version: 2.7.10 4 | 5 | test: 6 | override: 7 | - python unit_tests.py 8 | -------------------------------------------------------------------------------- /pkg/arch.txt: -------------------------------------------------------------------------------- 1 | all 2 | -------------------------------------------------------------------------------- /pkg/deps-deb.txt: -------------------------------------------------------------------------------- 1 | python2.7 2 | python-requests 3 | python-ecdsa 4 | python-commontools 5 | python-utilitybelt 6 | python-bitcoinrpc 7 | python-keychain 8 | python-bitcoin 9 | -------------------------------------------------------------------------------- /pkg/description.txt: -------------------------------------------------------------------------------- 1 | Library for Bitcoin & other cryptocurrencies. Tools are provided for blockchain transactions, RPC calls, and private keys, public keys, and addresses. 2 | -------------------------------------------------------------------------------- /pkg/license.txt: -------------------------------------------------------------------------------- 1 | MIT 2 | -------------------------------------------------------------------------------- /pkg/maintainer.txt: -------------------------------------------------------------------------------- 1 | Jude Nelson (jude@onename.com) 2 | -------------------------------------------------------------------------------- /pkg/name.txt: -------------------------------------------------------------------------------- 1 | python-pybitcoin 2 | -------------------------------------------------------------------------------- /pkg/url.txt: -------------------------------------------------------------------------------- 1 | https://github.com/blockstack/pybitcoin 2 | -------------------------------------------------------------------------------- /pkg/version.txt: -------------------------------------------------------------------------------- 1 | 0.9.8 2 | -------------------------------------------------------------------------------- /pybitcoin/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import services 11 | from .services import * 12 | 13 | import transactions 14 | from .transactions import * 15 | 16 | import passphrases 17 | from .passphrases import create_passphrase 18 | from .passphrases.legacy import ( 19 | random_160bit_passphrase, random_256bit_passphrase 20 | ) 21 | from .b58check import ( 22 | b58check_encode, b58check_decode, b58check_unpack, 23 | b58check_version_byte, is_b58check 24 | ) 25 | from .hash import ( 26 | bin_hash160, hex_hash160, reverse_hash, bin_double_sha256, 27 | hex_to_bin_reversed, bin_to_hex_reversed 28 | ) 29 | from .formatcheck import ( 30 | is_secret_exponent, is_256bit_hex_string, is_wif_pk, 31 | is_b58check_address, is_hex_ecdsa_pubkey, is_binary_ecdsa_pubkey 32 | ) 33 | from .wallet import SDWallet 34 | from .address import ( 35 | bin_hash160_to_address, hex_hash160_to_address, script_hex_to_address, 36 | address_to_bin_hash160, address_to_hex_hash160, 37 | address_to_new_cryptocurrency 38 | ) 39 | from .merkle import MerkleTree, calculate_merkle_pairs, calculate_merkle_root 40 | 41 | from .keypair import * 42 | from .privatekey import * 43 | from .publickey import * 44 | from .constants import * 45 | 46 | from keychain import PrivateKeychain, PublicKeychain 47 | -------------------------------------------------------------------------------- /pybitcoin/address.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from binascii import unhexlify, hexlify 11 | from .b58check import b58check_encode, b58check_decode 12 | 13 | 14 | def bin_hash160_to_address(bin_hash160, version_byte=0): 15 | return b58check_encode(bin_hash160, version_byte=version_byte) 16 | 17 | 18 | def hex_hash160_to_address(hash160, version_byte=0): 19 | return bin_hash160_to_address( 20 | unhexlify(hash160), version_byte=version_byte) 21 | 22 | 23 | def script_hex_to_address(script, version_byte=0): 24 | if script[0:6] == '76a914' and script[-4:] == '88ac': 25 | bin_hash160 = unhexlify(script[6:-4]) 26 | return bin_hash160_to_address(bin_hash160, version_byte=version_byte) 27 | return None 28 | 29 | 30 | def address_to_bin_hash160(address): 31 | return b58check_decode(address) 32 | 33 | 34 | def address_to_hex_hash160(address): 35 | return hexlify(address_to_bin_hash160(address)) 36 | 37 | 38 | def address_to_new_cryptocurrency(address, new_version_number): 39 | bin_hash160 = address_to_bin_hash160(address) 40 | return bin_hash160_to_address(bin_hash160, new_version_number) 41 | -------------------------------------------------------------------------------- /pybitcoin/b58check.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import re 11 | from binascii import hexlify, unhexlify 12 | from hashlib import sha256 13 | from utilitybelt import change_charset 14 | from .hash import bin_checksum 15 | 16 | HEX_KEYSPACE = "0123456789abcdef" 17 | B58_KEYSPACE = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 18 | 19 | 20 | def b58check_encode(bin_s, version_byte=0): 21 | """ Takes in a binary string and converts it to a base 58 check string. """ 22 | # append the version byte to the beginning 23 | bin_s = chr(int(version_byte)) + bin_s 24 | # calculate the number of leading zeros 25 | num_leading_zeros = len(re.match(r'^\x00*', bin_s).group(0)) 26 | # add in the checksum add the end 27 | bin_s = bin_s + bin_checksum(bin_s) 28 | # convert from b2 to b16 29 | hex_s = hexlify(bin_s) 30 | # convert from b16 to b58 31 | b58_s = change_charset(hex_s, HEX_KEYSPACE, B58_KEYSPACE) 32 | 33 | return B58_KEYSPACE[0] * num_leading_zeros + b58_s 34 | 35 | 36 | def b58check_unpack(b58_s): 37 | """ Takes in a base 58 check string and returns: the version byte, the 38 | original encoded binary string, and the checksum. 39 | """ 40 | num_leading_zeros = len(re.match(r'^1*', b58_s).group(0)) 41 | # convert from b58 to b16 42 | hex_s = change_charset(b58_s, B58_KEYSPACE, HEX_KEYSPACE) 43 | # if an odd number of hex characters are present, add a zero to the front 44 | if len(hex_s) % 2 == 1: 45 | hex_s = "0" + hex_s 46 | # convert from b16 to b2 47 | bin_s = unhexlify(hex_s) 48 | # add in the leading zeros 49 | bin_s = '\x00' * num_leading_zeros + bin_s 50 | # make sure the newly calculated checksum equals the embedded checksum 51 | newly_calculated_checksum = bin_checksum(bin_s[:-4]) 52 | embedded_checksum = bin_s[-4:] 53 | if not (newly_calculated_checksum == embedded_checksum): 54 | raise ValueError('b58check value has an invalid checksum') 55 | # return values 56 | version_byte = bin_s[:1] 57 | encoded_value = bin_s[1:-4] 58 | checksum = bin_s[-4:] 59 | return version_byte, encoded_value, checksum 60 | 61 | 62 | def b58check_decode(b58_s): 63 | """ Takes in a base 58 check string and returns the original encoded binary 64 | string. 65 | """ 66 | version_byte, encoded_value, checksum = b58check_unpack(b58_s) 67 | return encoded_value 68 | 69 | 70 | def b58check_version_byte(b58_s): 71 | """ Takes in a base 58 check string and returns the version byte as an 72 | integer. """ 73 | version_byte, encoded_value, checksum = b58check_unpack(b58_s) 74 | return ord(version_byte) 75 | 76 | 77 | def is_b58check(b58_s): 78 | version_byte, binary_s, checksum = b58check_unpack(b58_s) 79 | return (b58_s == b58check_encode( 80 | binary_s, version_byte=ord(version_byte))) 81 | -------------------------------------------------------------------------------- /pybitcoin/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | # data structure constants 11 | UINT_MAX = 2**32-1 12 | 13 | # protocol constants 14 | SATOSHIS_PER_COIN = 10**8 15 | MAX_BYTES_AFTER_OP_RETURN = 80 16 | 17 | # fee defaults 18 | STANDARD_FEE = 1000 # 1000 satoshis = 10 bits = .01 mbits = .00001 BTC 19 | OP_RETURN_FEE = 10000 # 10k satoshis = .0001 BTC 20 | -------------------------------------------------------------------------------- /pybitcoin/errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | _errors = { 11 | "EXPONENT_OUTSIDE_CURVE_ORDER": ( 12 | "Secret exponent is outside of the" 13 | "valid range. Must be >= 1 and < the curve order."), 14 | "PHRASE_YIELDS_INVALID_EXPONENT": ( 15 | "Invalid passphrase. The hash of this " 16 | "passphrase exceeds the curve order. Please try another passphrase."), 17 | "MUST_BE_VALID_PRIVKEY_FORMAT": ( 18 | "Format must be bin, hex, wif, or " 19 | "b58check."), 20 | "MUST_BE_VALID_PUBKEY_FORMAT": ("Format must be bin or hex"), 21 | "MUST_BE_VALID_HASH160_FORMAT": ( 22 | "format must be bin, hex or " 23 | "b58check."), 24 | "NOT_A_BRAIN_WALLET": ( 25 | "No passphrase! This isn't a brain wallet address!"), 26 | "MUST_BE_A_HEX_STRING": ("Must be a hex string"), 27 | "IMPROPER_PUBLIC_KEY_FORMAT": ("Public key is not in proper format"), 28 | } 29 | -------------------------------------------------------------------------------- /pybitcoin/formatcheck.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import os 11 | import re 12 | import random 13 | import binascii 14 | from utilitybelt import is_int, is_hex 15 | 16 | from .b58check import is_b58check 17 | 18 | 19 | def is_secret_exponent(val, curve_order): 20 | return (isinstance(val, (int, long)) and val >= 1 and val < curve_order) 21 | 22 | 23 | def is_256bit_hex_string(val): 24 | return (isinstance(val, str) and len(val) == 64 and is_hex(val)) 25 | 26 | 27 | def is_wif_pk(val): 28 | return (len(val) >= 51 and len(val) <= 52 and is_b58check(val)) 29 | 30 | 31 | def is_b58check_address(val): 32 | return is_b58check(val) 33 | # return (len(val) >= 27 and len(val) <= 34 and is_b58check(val)) 34 | 35 | 36 | def is_hex_ecdsa_pubkey(val): 37 | return (is_hex(val) and len(val) == 128) 38 | 39 | 40 | def is_binary_ecdsa_pubkey(val): 41 | return (isinstance(val, str) and len(val) == 64) 42 | -------------------------------------------------------------------------------- /pybitcoin/hash.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import hashlib 11 | from hashlib import sha256 12 | from binascii import hexlify, unhexlify 13 | from utilitybelt import is_hex 14 | 15 | 16 | def bin_sha256(bin_s): 17 | return sha256(bin_s).digest() 18 | 19 | 20 | def bin_checksum(bin_s): 21 | """ Takes in a binary string and returns a checksum. """ 22 | return bin_sha256(bin_sha256(bin_s))[:4] 23 | 24 | 25 | def bin_double_sha256(bin_s): 26 | return bin_sha256(bin_sha256(bin_s)) 27 | 28 | 29 | def bin_hash160(s, hex_format=False): 30 | """ s is in hex or binary format 31 | """ 32 | if hex_format and is_hex(s): 33 | s = unhexlify(s) 34 | return hashlib.new('ripemd160', bin_sha256(s)).digest() 35 | 36 | 37 | def hex_hash160(s, hex_format=False): 38 | """ s is in hex or binary format 39 | """ 40 | if hex_format and is_hex(s): 41 | s = unhexlify(s) 42 | return hexlify(bin_hash160(s)) 43 | 44 | 45 | def reverse_hash(hash, hex_format=True): 46 | """ hash is in hex or binary format 47 | """ 48 | if not hex_format: 49 | hash = hexlify(hash) 50 | return "".join(reversed([hash[i:i+2] for i in range(0, len(hash), 2)])) 51 | 52 | 53 | def hex_to_bin_reversed(s): 54 | return unhexlify(s.encode('utf8'))[::-1] 55 | 56 | 57 | def bin_to_hex_reversed(s): 58 | return hexlify(s[::-1]) 59 | -------------------------------------------------------------------------------- /pybitcoin/keypair.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import os 11 | import json 12 | import binascii 13 | import ecdsa 14 | import hashlib 15 | 16 | from .privatekey import random_secret_exponent 17 | from .b58check import b58check_encode, b58check_decode, b58check_unpack, \ 18 | b58check_version_byte 19 | from .errors import _errors 20 | from .hash import bin_hash160 21 | from .formatcheck import is_int, is_256bit_hex_string, is_wif_pk, \ 22 | is_secret_exponent 23 | from .passphrases import create_passphrase 24 | 25 | 26 | class BitcoinKeypair(): 27 | """ NOTE: This object has been replaced by the BitcoinPrivateKey and 28 | BitcoinPublicKey objects and is set to be deprecated at a future date. 29 | """ 30 | 31 | _curve = ecdsa.curves.SECP256k1 32 | _hash_function = hashlib.sha256 33 | _pubkeyhash_version_byte = 0 34 | 35 | @classmethod 36 | def version_byte(cls, type='pubkey_hash'): 37 | if type == 'pubkey_hash': 38 | return cls._pubkeyhash_version_byte 39 | elif type == 'private_key': 40 | return (cls._pubkeyhash_version_byte + 128) % 256 41 | else: 42 | raise Exception("type must be 'pubkey_hash' or 'privatekey'") 43 | 44 | def __init__(self, private_key=None): 45 | """ Takes in a private key/secret exponent. 46 | """ 47 | if not private_key: 48 | secret_exponent = random_secret_exponent(self._curve.order) 49 | elif is_int(private_key): 50 | secret_exponent = private_key 51 | elif is_256bit_hex_string(private_key): 52 | secret_exponent = int(private_key, 16) 53 | elif is_wif_pk(private_key): 54 | secret_exponent = int( 55 | binascii.hexlify(b58check_decode(private_key)), 16) 56 | 57 | # make sure that: 1 <= secret_exponent < curve_order 58 | if not is_secret_exponent(secret_exponent, self._curve.order): 59 | raise IndexError(_errors["EXPONENT_OUTSIDE_CURVE_ORDER"]) 60 | 61 | self._ecdsa_private_key = ecdsa.keys.SigningKey.from_secret_exponent( 62 | secret_exponent, self._curve, self._hash_function 63 | ) 64 | self._ecdsa_public_key = self._ecdsa_private_key.get_verifying_key() 65 | 66 | @classmethod 67 | def from_private_key(cls, private_key=None): 68 | return cls(private_key) 69 | 70 | @classmethod 71 | def from_passphrase(cls, passphrase=None): 72 | """ Create keypair from a passphrase input (a brain wallet keypair).""" 73 | if not passphrase: 74 | # run a rejection sampling algorithm to ensure the private key is 75 | # less than the curve order 76 | while True: 77 | passphrase = create_passphrase(bits_of_entropy=160) 78 | hex_private_key = hashlib.sha256(passphrase).hexdigest() 79 | if int(hex_private_key, 16) < cls._curve.order: 80 | break 81 | else: 82 | hex_private_key = hashlib.sha256(passphrase).hexdigest() 83 | if not (int(hex_private_key, 16) < cls._curve.order): 84 | raise ValueError(_errors["PHRASE_YIELDS_INVALID_EXPONENT"]) 85 | 86 | keypair = cls(hex_private_key) 87 | keypair._passphrase = passphrase 88 | return keypair 89 | 90 | def _bin_private_key(self): 91 | return self._ecdsa_private_key.to_string() 92 | 93 | def _bin_public_key(self, prefix=True): 94 | ecdsa_public_key = self._ecdsa_public_key.to_string() 95 | if prefix: 96 | return '\x04' + ecdsa_public_key 97 | return ecdsa_public_key 98 | 99 | def _bin_hash160(self): 100 | return bin_hash160(self._bin_public_key()) 101 | 102 | def private_key(self, format='hex'): 103 | if format == 'bin': 104 | return self._bin_private_key() 105 | elif format == 'hex': 106 | return binascii.hexlify(self._bin_private_key()) 107 | elif format == 'wif' or format == 'b58check': 108 | return b58check_encode( 109 | self._bin_private_key(), 110 | version_byte=self.version_byte('private_key')) 111 | else: 112 | raise ValueError(_errors["MUST_BE_VALID_PRIVKEY_FORMAT"]) 113 | 114 | def public_key(self, format='hex'): 115 | if format == 'bin': 116 | return self._bin_public_key() 117 | elif format == 'hex': 118 | return binascii.hexlify(self._bin_public_key()) 119 | else: 120 | raise ValueError(_errors["MUST_BE_VALID_PUBKEY_FORMAT"]) 121 | 122 | def hash160(self, format='hex'): 123 | if format == 'bin': 124 | return self._bin_hash160() 125 | elif format == 'hex': 126 | return binascii.hexlify(self._bin_hash160()) 127 | elif format == 'b58check': 128 | return b58check_encode( 129 | self._bin_hash160(), 130 | version_byte=self.version_byte('pubkey_hash')) 131 | else: 132 | raise ValueError(_errors["MUST_BE_VALID_HASH160_FORMAT"]) 133 | 134 | def secret_exponent(self): 135 | """ The secret exponent is the private key in int or hex format. """ 136 | return self.private_key('hex') 137 | 138 | def wif_pk(self): 139 | """ The "wif pk" is the private key in wallet import format. """ 140 | return self.private_key('wif') 141 | 142 | def address(self): 143 | """ The address is the hash160 in b58check format. """ 144 | return self.hash160('b58check') 145 | 146 | """ Brain wallet address methods """ 147 | 148 | def passphrase(self): 149 | if hasattr(self, '_passphrase'): 150 | return self._passphrase 151 | else: 152 | raise Exception(_errors["NOT_A_BRAIN_WALLET"]) 153 | 154 | 155 | class LitecoinKeypair(BitcoinKeypair): 156 | _pubkeyhash_version_byte = 48 157 | 158 | 159 | class NamecoinKeypair(BitcoinKeypair): 160 | _pubkeyhash_version_byte = 52 161 | 162 | 163 | class PeercoinKeypair(BitcoinKeypair): 164 | _pubkeyhash_version_byte = 55 165 | 166 | 167 | class PrimecoinKeypair(BitcoinKeypair): 168 | _pubkeyhash_version_byte = 23 169 | 170 | 171 | class DogecoinKeypair(BitcoinKeypair): 172 | _pubkeyhash_version_byte = 30 173 | 174 | 175 | class WorldcoinKeypair(BitcoinKeypair): 176 | _pubkeyhash_version_byte = 73 177 | 178 | 179 | class FeathercoinKeypair(BitcoinKeypair): 180 | _pubkeyhash_version_byte = 14 181 | 182 | 183 | class TerracoinKeypair(BitcoinKeypair): 184 | _pubkeyhash_version_byte = 0 185 | 186 | 187 | class NovacoinKeypair(BitcoinKeypair): 188 | _pubkeyhash_version_byte = 8 189 | 190 | 191 | class IxcoinKeypair(BitcoinKeypair): 192 | _pubkeyhash_version_byte = 138 193 | 194 | 195 | class TestnetKeypair(BitcoinKeypair): 196 | _pubkeyhash_version_byte = 111 197 | 198 | 199 | class ProtosharesKeypair(BitcoinKeypair): 200 | _pubkeyhash_version_byte = 56 201 | 202 | 203 | class MemorycoinKeypair(BitcoinKeypair): 204 | _pubkeyhash_version_byte = 50 205 | 206 | 207 | class QuarkcoinKeypair(BitcoinKeypair): 208 | _pubkeyhash_version_byte = 58 209 | 210 | 211 | class InfinitecoinKeypair(BitcoinKeypair): 212 | _pubkeyhash_version_byte = 102 213 | 214 | 215 | class CryptogenicbullionKeypair(BitcoinKeypair): 216 | _pubkeyhash_version_byte = 11 217 | 218 | 219 | class AnoncoinKeypair(BitcoinKeypair): 220 | _pubkeyhash_version_byte = 23 221 | 222 | 223 | class MegacoinKeypair(BitcoinKeypair): 224 | _pubkeyhash_version_byte = 50 225 | 226 | 227 | class EarthcoinKeypair(BitcoinKeypair): 228 | _pubkeyhash_version_byte = 93 229 | 230 | 231 | class NetcoinKeypair(BitcoinKeypair): 232 | _pubkeyhash_version_byte = 112 233 | 234 | 235 | class HuntercoinKeypair(BitcoinKeypair): 236 | _pubkeyhash_version_byte = 40 237 | 238 | 239 | class VertcoinKeypair(BitcoinKeypair): 240 | _pubkeyhash_version_byte = 71 241 | 242 | 243 | class ReddcoinKeypair(BitcoinKeypair): 244 | _pubkeyhash_version_byte = 61 245 | 246 | # TO DO: 247 | # auroracoin 248 | # counterparty 249 | # darkcoin 250 | # ybcoin 251 | # maxcoin 252 | # mintcoin 253 | # devcoin 254 | # tickets 255 | # freicoin 256 | # zetacoin 257 | # digitalcoin 258 | # copperlark 259 | # applecoin 260 | # unobtanium 261 | # fedoracoin 262 | # cachecoin 263 | # mincoin 264 | # ultracoin 265 | # colossuscoin 266 | # blackcoin 267 | # securecoin 268 | # gridcoin 269 | # billioncoin 270 | # kittehcoin 271 | # karmacoin 272 | # mooncoin 273 | # sexcoin 274 | -------------------------------------------------------------------------------- /pybitcoin/merkle.py: -------------------------------------------------------------------------------- 1 | from .hash import bin_double_sha256, bin_to_hex_reversed, hex_to_bin_reversed 2 | 3 | 4 | def hex_to_bin_reversed_hashes(hex_hashes): 5 | return [hex_to_bin_reversed(h) for h in hex_hashes] 6 | 7 | 8 | def calculate_merkle_pairs(bin_hashes, hash_function=bin_double_sha256): 9 | """ takes in a list of binary hashes, returns a binary hash 10 | """ 11 | hashes = list(bin_hashes) 12 | # if there are an odd number of hashes, double up the last one 13 | if len(hashes) % 2 == 1: 14 | hashes.append(hashes[-1]) 15 | # build the new list of hashes 16 | new_hashes = [] 17 | for i in range(0, len(hashes), 2): 18 | new_hashes.append(hash_function(hashes[i] + hashes[i+1])) 19 | # return the new list of hashes 20 | return new_hashes 21 | 22 | 23 | def calculate_merkle_root(hashes, hash_function=bin_double_sha256, 24 | hex_format=True): 25 | """ takes in a list of binary hashes, returns a binary hash 26 | """ 27 | if hex_format: 28 | hashes = hex_to_bin_reversed_hashes(hashes) 29 | # keep moving up the merkle tree, constructing one row at a time 30 | while len(hashes) > 1: 31 | hashes = calculate_merkle_pairs(hashes, hash_function) 32 | # get the merkle root 33 | merkle_root = hashes[0] 34 | # if the user wants the merkle root in hex format, convert it 35 | if hex_format: 36 | return bin_to_hex_reversed(merkle_root) 37 | # return the binary merkle root 38 | return merkle_root 39 | 40 | 41 | class MerkleTree(): 42 | def __init__(self, hashes, hex_format=True, 43 | hash_function=bin_double_sha256): 44 | if not len(hashes) > 0: 45 | raise ValueError("At least one hash is required.") 46 | 47 | self.rows = [] 48 | 49 | # if the hashes are in hex format, first convert them to binary 50 | if hex_format: 51 | hashes = hex_to_bin_reversed_hashes(hashes) 52 | 53 | # build the rows of the merkle tree 54 | self.rows.append(hashes) 55 | while len(hashes) > 1: 56 | hashes = calculate_merkle_pairs(hashes, hash_function) 57 | self.rows.append(hashes) 58 | 59 | def get(self, row_index, column_index): 60 | # check to make sure there are enough rows 61 | if row_index + 1 > len(self.rows): 62 | raise ValueError("There aren't that many rows.") 63 | row = self.rows(row_index) 64 | # check to make sure there are enough items in the row 65 | if column_index + 1 > len(row): 66 | raise ValueError("There aren't that many items in that row.") 67 | # return the requested item 68 | return row[column_index] 69 | 70 | def root(self, hex_format=True): 71 | # return the merkle root 72 | bin_merkle_root = self.rows[-1][0] 73 | if hex_format: 74 | return bin_to_hex_reversed(bin_merkle_root) 75 | -------------------------------------------------------------------------------- /pybitcoin/passphrases/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from .passphrase import * 11 | -------------------------------------------------------------------------------- /pybitcoin/passphrases/legacy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from utilitybelt import dev_random_entropy 11 | from math import pow, ceil, log 12 | from .passphrase import pick_random_words_from_wordlist 13 | from .english_words import english_words_google 14 | 15 | def random_passphrase_from_wordlist(phrase_length, wordlist): 16 | """ An extremely entropy efficient passphrase generator. 17 | 18 | This function: 19 | -Pulls entropy from the safer alternative to /dev/urandom: /dev/random 20 | -Doesn't rely on random.seed (words are selected right from the entropy) 21 | -Only requires 2 entropy bytes/word for word lists of up to 65536 words 22 | """ 23 | 24 | passphrase_words = [] 25 | 26 | numbytes_of_entropy = phrase_length * 2 27 | entropy = list(dev_random_entropy(numbytes_of_entropy, fallback_to_urandom=True)) 28 | 29 | bytes_per_word = int(ceil(log(len(wordlist), 2) / 8)) 30 | 31 | if (phrase_length * bytes_per_word > 64): 32 | raise Exception("Error! This operation requires too much entropy. \ 33 | Try a shorter phrase length or word list.") 34 | 35 | for i in range(phrase_length): 36 | current_entropy = entropy[i*bytes_per_word:(i+1)*bytes_per_word] 37 | index = int(''.join(current_entropy).encode('hex'), 16) % len(wordlist) 38 | word = wordlist[index] 39 | passphrase_words.append(word) 40 | 41 | return " ".join(passphrase_words) 42 | 43 | def random_160bit_passphrase(): 44 | return random_passphrase_from_wordlist(english_words_google[0:10500], 12) 45 | 46 | def random_256bit_passphrase(): 47 | return random_passphrase_from_wordlist(english_words_google[0:65536], 16) 48 | -------------------------------------------------------------------------------- /pybitcoin/passphrases/passphrase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import random, math 11 | from .english_words import english_words_bip39, english_words_wiktionary, \ 12 | english_words_google 13 | 14 | system_random = random.SystemRandom() 15 | 16 | def get_wordlist(language, word_source): 17 | """ Takes in a language and a word source and returns a matching wordlist, 18 | if it exists. 19 | Valid languages: ['english'] 20 | Valid word sources: ['bip39', 'wiktionary', 'google'] 21 | """ 22 | try: 23 | wordlist_string = eval(language + '_words_' + word_source) 24 | except NameError: 25 | raise Exception("No wordlist could be found for the word source and language provided.") 26 | wordlist = wordlist_string.split(',') 27 | return wordlist 28 | 29 | def get_num_words_with_entropy(bits_of_entropy, wordlist): 30 | """ Gets the number of words randomly selected from a given wordlist that 31 | would result in the number of bits of entropy specified. 32 | """ 33 | entropy_per_word = math.log(len(wordlist))/math.log(2) 34 | num_words = int(math.ceil(bits_of_entropy/entropy_per_word)) 35 | return num_words 36 | 37 | def pick_random_words_from_wordlist(wordlist, num_words_to_choose): 38 | """ Picks words randomly from a wordlist. 39 | """ 40 | return [system_random.choice(wordlist) for i in range(num_words_to_choose)] 41 | 42 | def create_passphrase(bits_of_entropy=None, num_words=None, 43 | language='english', word_source='wiktionary'): 44 | """ Creates a passphrase that has a certain number of bits of entropy OR 45 | a certain number of words. 46 | """ 47 | wordlist = get_wordlist(language, word_source) 48 | 49 | if not num_words: 50 | if not bits_of_entropy: 51 | bits_of_entropy = 80 52 | num_words = get_num_words_with_entropy(bits_of_entropy, wordlist) 53 | 54 | return ' '.join(pick_random_words_from_wordlist(wordlist, num_words)) 55 | 56 | """ 57 | def create_bip39_passphrase(bits_of_entropy): 58 | return create_passphrase(bits_of_entropy=bits_of_entropy, word_source='bip39') 59 | 60 | def create_wiktionary_passphrase(bits_of_entropy): 61 | return create_passphrase(bits_of_entropy=bits_of_entropy, word_source='wiktionary') 62 | 63 | def create_passphrase_with_num_words(num_words, language='english', 64 | word_source='bip39'): 65 | wordlist = get_wordlist(language, word_source) 66 | return ' '.join(pick_random_words_from_wordlist(wordlist, num_words)) 67 | 68 | def create_simple_passphrase(): 69 | return create_passphrase_with_entropy(num_words=40, word_source='bip39') 70 | 71 | def create_128bit_passphrase(): 72 | return create_passphrase_with_entropy(128, word_source='bip39') 73 | 74 | def create_160bit_passphrase(): 75 | return create_passphrase_with_entropy(160, word_source='wiktionary') 76 | 77 | def create_256bit_passphrase(): 78 | return create_passphrase_with_entropy(256, word_source='wiktionary') 79 | """ 80 | -------------------------------------------------------------------------------- /pybitcoin/privatekey.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import os 11 | import json 12 | import hashlib 13 | import ecdsa 14 | from binascii import hexlify, unhexlify 15 | from ecdsa.keys import SigningKey 16 | from utilitybelt import is_int, dev_random_entropy, dev_urandom_entropy 17 | from bitcoin import compress, encode_privkey, get_privkey_format 18 | 19 | from .errors import _errors 20 | from .formatcheck import * 21 | from .b58check import b58check_encode, b58check_decode 22 | from .publickey import BitcoinPublicKey, PUBKEY_MAGIC_BYTE 23 | from .passphrases import create_passphrase 24 | 25 | 26 | def random_secret_exponent(curve_order): 27 | """ Generates a random secret exponent. """ 28 | # run a rejection sampling algorithm to ensure the random int is less 29 | # than the curve order 30 | while True: 31 | # generate a random 256 bit hex string 32 | random_hex = hexlify(dev_random_entropy(32)) 33 | random_int = int(random_hex, 16) 34 | if random_int >= 1 and random_int < curve_order: 35 | break 36 | return random_int 37 | 38 | 39 | class BitcoinPrivateKey(): 40 | _curve = ecdsa.curves.SECP256k1 41 | _hash_function = hashlib.sha256 42 | _pubkeyhash_version_byte = 0 43 | 44 | @classmethod 45 | def wif_version_byte(cls): 46 | if hasattr(cls, '_wif_version_byte'): 47 | return cls._wif_version_byte 48 | return (cls._pubkeyhash_version_byte + 128) % 256 49 | 50 | def __init__(self, private_key=None, compressed=False): 51 | """ Takes in a private key/secret exponent. 52 | """ 53 | self._compressed = compressed 54 | if not private_key: 55 | secret_exponent = random_secret_exponent(self._curve.order) 56 | else: 57 | secret_exponent = encode_privkey(private_key, 'decimal') 58 | if get_privkey_format(private_key).endswith('compressed'): 59 | self._compressed = True 60 | 61 | # make sure that: 1 <= secret_exponent < curve_order 62 | if not is_secret_exponent(secret_exponent, self._curve.order): 63 | raise IndexError(_errors["EXPONENT_OUTSIDE_CURVE_ORDER"]) 64 | 65 | self._ecdsa_private_key = ecdsa.keys.SigningKey.from_secret_exponent( 66 | secret_exponent, self._curve, self._hash_function 67 | ) 68 | 69 | @classmethod 70 | def from_passphrase(cls, passphrase=None): 71 | """ Create keypair from a passphrase input (a brain wallet keypair).""" 72 | if not passphrase: 73 | # run a rejection sampling algorithm to ensure the private key is 74 | # less than the curve order 75 | while True: 76 | passphrase = create_passphrase(bits_of_entropy=160) 77 | hex_private_key = hashlib.sha256(passphrase).hexdigest() 78 | if int(hex_private_key, 16) < cls._curve.order: 79 | break 80 | else: 81 | hex_private_key = hashlib.sha256(passphrase).hexdigest() 82 | if not (int(hex_private_key, 16) < cls._curve.order): 83 | raise ValueError(_errors["CURVE_ORDER_EXCEEDED"]) 84 | 85 | keypair = cls(hex_private_key) 86 | keypair._passphrase = passphrase 87 | return keypair 88 | 89 | def to_bin(self): 90 | if self._compressed: 91 | return encode_privkey( 92 | self._ecdsa_private_key.to_string(), 'bin_compressed') 93 | else: 94 | return self._ecdsa_private_key.to_string() 95 | 96 | def to_hex(self): 97 | if self._compressed: 98 | return encode_privkey( 99 | self._ecdsa_private_key.to_string(), 'hex_compressed') 100 | else: 101 | return hexlify(self.to_bin()) 102 | 103 | def to_wif(self): 104 | if self._compressed: 105 | return encode_privkey( 106 | self._ecdsa_private_key.to_string(), 'wif_compressed') 107 | else: 108 | return b58check_encode( 109 | self.to_bin(), version_byte=self.wif_version_byte()) 110 | 111 | def to_pem(self): 112 | return self._ecdsa_private_key.to_pem() 113 | 114 | def to_der(self): 115 | return hexlify(self._ecdsa_private_key.to_der()) 116 | 117 | def public_key(self): 118 | # lazily calculate and set the public key 119 | if not hasattr(self, '_public_key'): 120 | ecdsa_public_key = self._ecdsa_private_key.get_verifying_key() 121 | 122 | bin_public_key_string = PUBKEY_MAGIC_BYTE + \ 123 | ecdsa_public_key.to_string() 124 | 125 | if self._compressed: 126 | bin_public_key_string = compress(bin_public_key_string) 127 | 128 | # create the public key object from the public key string 129 | self._public_key = BitcoinPublicKey( 130 | bin_public_key_string, 131 | version_byte=self._pubkeyhash_version_byte) 132 | 133 | # return the public key object 134 | return self._public_key 135 | 136 | def passphrase(self): 137 | if hasattr(self, '_passphrase'): 138 | return self._passphrase 139 | else: 140 | raise Exception(_errors["NOT_A_BRAIN_WALLET"]) 141 | 142 | 143 | class LitecoinPrivateKey(BitcoinPrivateKey): 144 | _pubkeyhash_version_byte = 48 145 | 146 | 147 | class NamecoinPrivateKey(BitcoinPrivateKey): 148 | _pubkeyhash_version_byte = 52 149 | -------------------------------------------------------------------------------- /pybitcoin/publickey.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import os 11 | import json 12 | import hashlib 13 | import ecdsa 14 | from binascii import hexlify, unhexlify 15 | from ecdsa.keys import VerifyingKey 16 | from bitcoin import decompress, compress, pubkey_to_address 17 | from utilitybelt import is_hex 18 | 19 | from .errors import _errors 20 | from .hash import bin_hash160 as get_bin_hash160 21 | from .formatcheck import is_hex_ecdsa_pubkey, is_binary_ecdsa_pubkey 22 | from .b58check import b58check_encode 23 | from .address import bin_hash160_to_address 24 | 25 | PUBKEY_MAGIC_BYTE = '\x04' 26 | 27 | 28 | class CharEncoding(): 29 | hex = 16 30 | bin = 256 31 | 32 | 33 | class PubkeyType(): 34 | ecdsa = 1 35 | uncompressed = 2 36 | compressed = 3 37 | 38 | 39 | def get_public_key_format(public_key_string): 40 | if not isinstance(public_key_string, str): 41 | raise ValueError('Public key must be a string.') 42 | 43 | if len(public_key_string) == 64: 44 | return CharEncoding.bin, PubkeyType.ecdsa 45 | 46 | if (len(public_key_string) == 65 and 47 | public_key_string[0] == PUBKEY_MAGIC_BYTE): 48 | return CharEncoding.bin, PubkeyType.uncompressed 49 | 50 | if len(public_key_string) == 33: 51 | return CharEncoding.bin, PubkeyType.compressed 52 | 53 | if is_hex(public_key_string): 54 | if len(public_key_string) == 128: 55 | return CharEncoding.hex, PubkeyType.ecdsa 56 | 57 | if (len(public_key_string) == 130 and 58 | public_key_string[0:2] == hexlify(PUBKEY_MAGIC_BYTE)): 59 | return CharEncoding.hex, PubkeyType.uncompressed 60 | 61 | if len(public_key_string) == 66: 62 | return CharEncoding.hex, PubkeyType.compressed 63 | 64 | raise ValueError(_errors['IMPROPER_PUBLIC_KEY_FORMAT']) 65 | 66 | 67 | def extract_bin_ecdsa_pubkey(public_key): 68 | key_charencoding, key_type = get_public_key_format(public_key) 69 | 70 | if key_charencoding == CharEncoding.hex: 71 | bin_public_key = unhexlify(public_key) 72 | elif key_charencoding == CharEncoding.bin: 73 | bin_public_key = public_key 74 | else: 75 | raise ValueError(_errors['IMPROPER_PUBLIC_KEY_FORMAT']) 76 | 77 | if key_type == PubkeyType.ecdsa: 78 | return bin_public_key 79 | elif key_type == PubkeyType.uncompressed: 80 | return bin_public_key[1:] 81 | elif key_type == PubkeyType.compressed: 82 | return decompress(bin_public_key)[1:] 83 | else: 84 | raise ValueError(_errors['IMPROPER_PUBLIC_KEY_FORMAT']) 85 | 86 | 87 | def extract_bin_bitcoin_pubkey(public_key): 88 | key_charencoding, key_type = get_public_key_format(public_key) 89 | 90 | if key_charencoding == CharEncoding.hex: 91 | bin_public_key = unhexlify(public_key) 92 | elif key_charencoding == CharEncoding.bin: 93 | bin_public_key = public_key 94 | else: 95 | raise ValueError(_errors['IMPROPER_PUBLIC_KEY_FORMAT']) 96 | 97 | if key_type == PubkeyType.ecdsa: 98 | return PUBKEY_MAGIC_BYTE + bin_public_key 99 | elif key_type == PubkeyType.uncompressed: 100 | return bin_public_key 101 | elif key_type == PubkeyType.compressed: 102 | return bin_public_key 103 | else: 104 | raise ValueError(_errors['IMPROPER_PUBLIC_KEY_FORMAT']) 105 | 106 | 107 | class BitcoinPublicKey(): 108 | _curve = ecdsa.curves.SECP256k1 109 | _version_byte = 0 110 | 111 | @classmethod 112 | def version_byte(cls): 113 | return cls._version_byte 114 | 115 | def __init__(self, public_key_string, version_byte=None, verify=True): 116 | """ Takes in a public key in hex format. 117 | """ 118 | # set the version byte 119 | if version_byte: 120 | self._version_byte = version_byte 121 | 122 | self._charencoding, self._type = get_public_key_format( 123 | public_key_string) 124 | 125 | # extract the binary bitcoin key (compressed/uncompressed w magic byte) 126 | self._bin_public_key = extract_bin_bitcoin_pubkey(public_key_string) 127 | 128 | # extract the bin ecdsa public key (uncompressed, w/out a magic byte) 129 | bin_ecdsa_public_key = extract_bin_ecdsa_pubkey(public_key_string) 130 | if verify: 131 | try: 132 | # create the ecdsa key object 133 | self._ecdsa_public_key = VerifyingKey.from_string( 134 | bin_ecdsa_public_key, self._curve) 135 | except AssertionError as e: 136 | raise ValueError(_errors['IMPROPER_PUBLIC_KEY_FORMAT']) 137 | 138 | def to_bin(self): 139 | return self._bin_public_key 140 | 141 | def to_hex(self): 142 | return hexlify(self.to_bin()) 143 | 144 | def to_pem(self): 145 | return self._ecdsa_public_key.to_pem() 146 | 147 | def to_der(self): 148 | return hexlify(self._ecdsa_public_key.to_der()) 149 | 150 | def bin_hash160(self): 151 | if not hasattr(self, '_bin_hash160'): 152 | self._bin_hash160 = get_bin_hash160(self.to_bin()) 153 | return self._bin_hash160 154 | 155 | def hash160(self): 156 | return hexlify(self.bin_hash160()) 157 | 158 | def address(self): 159 | if self._type == PubkeyType.compressed: 160 | bin_hash160 = get_bin_hash160(compress(self.to_bin())) 161 | return bin_hash160_to_address( 162 | bin_hash160, version_byte=self._version_byte) 163 | 164 | return bin_hash160_to_address(self.bin_hash160(), 165 | version_byte=self._version_byte) 166 | 167 | 168 | class LitecoinPublicKey(BitcoinPublicKey): 169 | _version_byte = 48 170 | 171 | 172 | class NamecoinPublicKey(BitcoinPublicKey): 173 | _version_byte = 52 174 | -------------------------------------------------------------------------------- /pybitcoin/rpc/README.md: -------------------------------------------------------------------------------- 1 | rpc 2 | ======= 3 | 4 | Pybitcoin provides support for RPC calls to namecoind and bitcoind. Under the hood it uses [AuthProxy](https://github.com/jgarzik/python-bitcoinrpc). Pybitcoin makes the following changes/additions to directly using the underlying RPC: 5 | 6 | For Namecoind: 7 | 8 | 1. Support for managing a cluster of namecoind servers 9 | 2. Adds get_full_profile() that can fetch an Openname profile from a linked-list of key:value entries 10 | 3. Fixes the bug where value > 520 bytes can cause a key to become unusable 11 | 4. Logically separates name_transfer from name_update 12 | 5. Adds reasonable default values for certain calls e.g., 100 as timeout for unlocking wallet 13 | 6. Adds calls for checking if a user is registered (True/False) and if a wallet is unlocked (True/False) 14 | 7. Better error handling 15 | 16 | ## Installation: 17 | 18 | For the latest version: 19 | 20 | ``` 21 | pip install git+ssh://git@github.com/openname/pybitcoin.git/@rpc 22 | ``` 23 | 24 | By default bitcoind is turned off and the configuration of a public namecoind server is used. Custom namecoind/bitcoind servers can be used by setting the appropriate ENV VARIABLES (see [config.py](coinrpc/config.py)) e.g., by sourcing the following scripts: 25 | 26 | ``` 27 | source /scripts/setup_namecoind.sh 28 | source /scripts/setup_bitcoind.sh 29 | ``` 30 | 31 | ## Usage: 32 | 33 | ``` 34 | from pybitcoin.rpc import namecoind 35 | print namecoind.blocks() 36 | ``` 37 | -------------------------------------------------------------------------------- /pybitcoin/rpc/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2015 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from .config import NAMECOIND_ENABLED, BITCOIND_ENABLED 11 | 12 | if NAMECOIND_ENABLED: 13 | 14 | from .namecoind_client import NamecoindClient 15 | namecoind = NamecoindClient() # start with default server settings 16 | 17 | 18 | if BITCOIND_ENABLED: 19 | 20 | from .bitcoind_client import BitcoindClient 21 | bitcoind = BitcoindClient() # start with default server settings 22 | -------------------------------------------------------------------------------- /pybitcoin/rpc/bitcoind_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2016 by Halfmoon Labs, Inc. 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException 11 | from commontools import log, error_reply 12 | from .. import SATOSHIS_PER_COIN 13 | from .. import script_hex_to_address 14 | 15 | from .config import BITCOIND_SERVER, BITCOIND_PORT, BITCOIND_USER 16 | from .config import BITCOIND_PASSWD, BITCOIND_WALLET_PASSPHRASE 17 | from .config import BITCOIND_USE_HTTPS 18 | 19 | 20 | class BitcoindClient(object): 21 | 22 | def __init__(self, server=BITCOIND_SERVER, port=BITCOIND_PORT, 23 | user=BITCOIND_USER, passwd=BITCOIND_PASSWD, 24 | use_https=BITCOIND_USE_HTTPS, 25 | passphrase=BITCOIND_WALLET_PASSPHRASE, version_byte=0): 26 | 27 | self.passphrase = passphrase 28 | self.server = server 29 | 30 | self.type = 'bitcoind' 31 | self.version_byte = version_byte 32 | 33 | if use_https: 34 | http_string = 'https://' 35 | else: 36 | http_string = 'http://' 37 | 38 | self.obj = AuthServiceProxy(http_string + user + ':' + passwd + 39 | '@' + server + ':' + str(port)) 40 | 41 | def __getattr__(self, name): 42 | """ changes the behavior of underlying authproxy to return the error 43 | from bitcoind instead of raising JSONRPCException 44 | """ 45 | func = getattr(self.__dict__['obj'], name) 46 | if callable(func): 47 | def my_wrapper(*args, **kwargs): 48 | try: 49 | ret = func(*args, **kwargs) 50 | except JSONRPCException as e: 51 | return e.error 52 | else: 53 | return ret 54 | return my_wrapper 55 | else: 56 | return func 57 | 58 | def blocks(self): 59 | 60 | reply = self.obj.getinfo() 61 | 62 | if 'blocks' in reply: 63 | return reply['blocks'] 64 | 65 | return None 66 | 67 | def unlock_wallet(self, timeout=120): 68 | 69 | try: 70 | info = self.obj.walletpassphrase(self.passphrase, timeout) 71 | 72 | if info is None: 73 | return True 74 | except: 75 | pass 76 | 77 | return False 78 | 79 | def sendtoaddress(self, bitcoinaddress, amount): 80 | 81 | self.unlock_wallet() 82 | 83 | try: 84 | # ISSUE: should not be float, needs fix 85 | status = self.obj.sendtoaddress(bitcoinaddress, float(amount)) 86 | return status 87 | except Exception as e: 88 | return error_reply(str(e)) 89 | 90 | def validateaddress(self, bitcoinaddress): 91 | 92 | try: 93 | status = self.obj.validateaddress(bitcoinaddress) 94 | return status 95 | except Exception as e: 96 | return error_reply(str(e)) 97 | 98 | def importprivkey(self, bitcoinprivkey, label='import', rescan=False): 99 | 100 | self.unlock_wallet() 101 | 102 | try: 103 | status = self.obj.importprivkey(bitcoinprivkey, label, rescan) 104 | return status 105 | except Exception as e: 106 | return error_reply(str(e)) 107 | 108 | def format_unspents(self, unspents): 109 | return [{ 110 | "transaction_hash": s["txid"], 111 | "output_index": s["vout"], 112 | "value": int(round(s["amount"]*SATOSHIS_PER_COIN)), 113 | "script_hex": s["scriptPubKey"], 114 | "confirmations": s["confirmations"] 115 | } 116 | for s in unspents 117 | ] 118 | 119 | def get_unspents(self, address): 120 | """ Get the spendable transaction outputs, also known as UTXOs or 121 | unspent transaction outputs. 122 | 123 | NOTE: this will only return unspents if the address provided is 124 | present in the bitcoind server. Use the chain, blockchain, 125 | or blockcypher API to grab the unspents for arbitrary addresses. 126 | """ 127 | 128 | addresses = [] 129 | addresses.append(str(address)) 130 | min_confirmations = 0 131 | max_confirmation = 2000000000 # just a very large number for max 132 | 133 | unspents = self.obj.listunspent(min_confirmations, max_confirmation, 134 | addresses) 135 | 136 | return self.format_unspents(unspents) 137 | 138 | def broadcast_transaction(self, hex_tx): 139 | """ Dispatch a raw transaction to the network. 140 | """ 141 | 142 | resp = self.obj.sendrawtransaction(hex_tx) 143 | if len(resp) > 0: 144 | return {'transaction_hash': resp, 'success': True} 145 | else: 146 | return error_reply('Invalid response from bitcoind.') 147 | -------------------------------------------------------------------------------- /pybitcoin/rpc/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2015 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import os 11 | 12 | NAMECOIND_ENABLED = False 13 | BITCOIND_ENABLED = True 14 | 15 | DEBUG = True 16 | 17 | VALUE_MAX_LIMIT = 520 18 | 19 | # -------------------------------------------------- 20 | if NAMECOIND_ENABLED: 21 | 22 | NAMECOIND_USE_HTTPS = True 23 | 24 | try: 25 | NAMECOIND_PORT = os.environ['NAMECOIND_PORT'] 26 | NAMECOIND_SERVER = os.environ['NAMECOIND_SERVER'] 27 | NAMECOIND_USER = os.environ['NAMECOIND_USER'] 28 | NAMECOIND_PASSWD = os.environ['NAMECOIND_PASSWD'] 29 | except: 30 | # default settings with a public server 31 | NAMECOIND_PORT = 8332 32 | NAMECOIND_SERVER = 'nmcd.onename.com' 33 | NAMECOIND_USER = 'opennamesystem' 34 | NAMECOIND_PASSWD = 'opennamesystem' 35 | 36 | try: 37 | NAMECOIND_WALLET_PASSPHRASE = os.environ['NAMECOIND_WALLET_PASSPHRASE'] 38 | except: 39 | NAMECOIND_WALLET_PASSPHRASE = '' 40 | 41 | try: 42 | from .config_local import MAIN_SERVER, LOAD_SERVERS 43 | except: 44 | MAIN_SERVER = NAMECOIND_SERVER 45 | LOAD_SERVERS = [] 46 | 47 | # -------------------------------------------------- 48 | if BITCOIND_ENABLED: 49 | 50 | BITCOIND_USE_HTTPS = True 51 | 52 | try: 53 | BITCOIND_PORT = os.environ['BITCOIND_PORT'] 54 | BITCOIND_SERVER = os.environ['BITCOIND_SERVER'] 55 | BITCOIND_USER = os.environ['BITCOIND_USER'] 56 | BITCOIND_PASSWD = os.environ['BITCOIND_PASSWD'] 57 | BITCOIND_WALLET_PASSPHRASE = os.environ['BITCOIND_WALLET_PASSPHRASE'] 58 | 59 | except: 60 | BITCOIND_SERVER = 'btcd.onename.com' 61 | BITCOIND_PORT = 8332 62 | BITCOIND_USER = 'openname' 63 | BITCOIND_PASSWD = 'opennamesystem' 64 | 65 | try: 66 | BITCOIND_WALLET_PASSPHRASE = os.environ['BITCOIND_WALLET_PASSPHRASE'] 67 | except: 68 | BITCOIND_WALLET_PASSPHRASE = '' 69 | -------------------------------------------------------------------------------- /pybitcoin/rpc/namecoind_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2015 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from commontools import utf8len, error_reply, get_json 11 | import json 12 | 13 | from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException 14 | from .config import NAMECOIND_SERVER, NAMECOIND_PORT, NAMECOIND_USER 15 | from .config import NAMECOIND_PASSWD, NAMECOIND_WALLET_PASSPHRASE 16 | from .config import NAMECOIND_USE_HTTPS, VALUE_MAX_LIMIT 17 | 18 | import ssl 19 | import httplib 20 | 21 | create_ssl_authproxy = False 22 | do_wrap_socket = False 23 | 24 | if hasattr( ssl, "_create_unverified_context" ): 25 | #opt-out for verifying self-signed certificates (typically used in namecoin/bitcoind) 26 | ssl._create_default_https_context = ssl._create_unverified_context 27 | create_ssl_authproxy = True 28 | 29 | if not hasattr( ssl, "create_default_context" ): 30 | create_ssl_authproxy = False 31 | do_wrap_socket = True 32 | 33 | 34 | class NamecoindConnection( httplib.HTTPSConnection ): 35 | """ 36 | Wrapped SSL connection, if we can't use SSLContext. 37 | """ 38 | 39 | def __init__(self, host, port, timeout=None ): 40 | 41 | httplib.HTTPSConnection.__init__(self, host, port ) 42 | self.timeout = timeout 43 | 44 | def connect( self ): 45 | 46 | sock = socket.create_connection((self.host, self.port), self.timeout) 47 | if self._tunnel_host: 48 | self.sock = sock 49 | self._tunnel() 50 | 51 | self.sock = ssl.wrap_socket( sock, cert_reqs=ssl.CERT_NONE ) 52 | 53 | 54 | class NamecoindClient(object): 55 | 56 | def __init__(self, server=NAMECOIND_SERVER, port=NAMECOIND_PORT, 57 | user=NAMECOIND_USER, passwd=NAMECOIND_PASSWD, 58 | use_https=NAMECOIND_USE_HTTPS, 59 | passphrase=NAMECOIND_WALLET_PASSPHRASE): 60 | 61 | global create_ssl_authproxy, do_wrap_socket 62 | 63 | if use_https: 64 | http_string = 'https://' 65 | else: 66 | http_string = 'http://' 67 | 68 | authproxy_config_uri = http_string + user + ':' + passwd + '@' + server + ':' + str(port) 69 | 70 | self.passphrase = passphrase 71 | self.server = server 72 | 73 | if do_wrap_socket: 74 | # ssl._create_unverified_context and ssl.create_default_context are not supported. 75 | # wrap the socket directly 76 | connection = NamecoindConnection( server, int(port) ) 77 | self.obj = AuthServiceProxy(authproxy_config_uri, connection=connection) 78 | 79 | elif create_ssl_authproxy: 80 | # ssl has _create_unverified_context, so we're good to go 81 | self.obj = AuthServiceProxy(authproxy_config_uri) 82 | 83 | else: 84 | # have to set up an unverified context ourselves 85 | ssl_ctx = ssl.create_default_context() 86 | ssl_ctx.check_hostname = False 87 | ssl_ctx.verify_mode = ssl.CERT_NONE 88 | connection = httplib.HTTPSConnection( server, int(port), context=ssl_ctx ) 89 | self.obj = AuthServiceProxy(authproxy_config_uri, connection=connection) 90 | 91 | 92 | def __getattr__(self, name): 93 | """ changes the behavior of underlying authproxy to return the error 94 | from namecoind instead of raising JSONRPCException 95 | """ 96 | func = getattr(self.__dict__['obj'], name) 97 | if callable(func): 98 | def my_wrapper(*args, **kwargs): 99 | try: 100 | ret = func(*args, **kwargs) 101 | except JSONRPCException as e: 102 | return e.error 103 | else: 104 | return ret 105 | return my_wrapper 106 | else: 107 | return func 108 | 109 | # ----------------------------------- 110 | def blocks(self): 111 | 112 | reply = self.getinfo() 113 | 114 | if 'blocks' in reply: 115 | return reply['blocks'] 116 | 117 | return None 118 | 119 | # ----------------------------------- 120 | def name_filter(self, regex, check_blocks=36000, 121 | show_from=0, num_results=0): 122 | 123 | try: 124 | reply = self.obj.name_filter(regex, check_blocks, 125 | show_from, num_results) 126 | except JSONRPCException as e: 127 | return e.error 128 | 129 | return reply 130 | 131 | # ----------------------------------- 132 | # Step-1 for registrering new names 133 | def name_new(self, key, value): 134 | 135 | # check if this key already exists 136 | # namecoind lets you do name_new on keys that exist 137 | if self.check_registration(key): 138 | return error_reply("This key already exists") 139 | 140 | if not self.unlock_wallet(self.passphrase): 141 | return error_reply("Error unlocking wallet", 403) 142 | 143 | # create new name 144 | # returns a list of [tx, rand] 145 | try: 146 | reply = self.obj.name_new(key) 147 | except JSONRPCException as e: 148 | return e.error 149 | 150 | return reply 151 | 152 | # ---------------------------------------------- 153 | # step-2 for registering 154 | def firstupdate(self, key, rand, value, tx=None): 155 | 156 | if utf8len(value) > VALUE_MAX_LIMIT: 157 | return error_reply("value larger than " + str(VALUE_MAX_LIMIT)) 158 | 159 | if not self.unlock_wallet(self.passphrase): 160 | error_reply("Error unlocking wallet", 403) 161 | 162 | try: 163 | if tx is not None: 164 | reply = self.obj.name_firstupdate(key, rand, tx, value) 165 | else: 166 | reply = self.obj.name_firstupdate(key, rand, value) 167 | except JSONRPCException as e: 168 | return e.error 169 | 170 | return reply 171 | 172 | # ----------------------------------- 173 | def name_update(self, key, value): 174 | 175 | if utf8len(value) > VALUE_MAX_LIMIT: 176 | return error_reply("value larger than " + str(VALUE_MAX_LIMIT)) 177 | 178 | if not self.unlock_wallet(self.passphrase): 179 | error_reply("Error unlocking wallet", 403) 180 | 181 | try: 182 | # update the 'value' 183 | reply = self.obj.name_update(key, value) 184 | except JSONRPCException as e: 185 | return e.error 186 | 187 | return reply 188 | 189 | # ----------------------------------- 190 | def name_transfer(self, key, new_address, value=None): 191 | """ Check if this name exists and if it does, find the value field 192 | note that update command needs an arg of . 193 | in case we're simply transferring, need to obtain old value first 194 | """ 195 | 196 | key_details = self.name_show(key) 197 | 198 | if 'code' in key_details and key_details.get('code') == -4: 199 | return error_reply("Key does not exist") 200 | 201 | # get new 'value' if given, otherwise use the old 'value' 202 | if value is None: 203 | value = json.dumps(key_details['value']) 204 | 205 | if not self.unlock_wallet(self.passphrase): 206 | error_reply("Error unlocking wallet", 403) 207 | 208 | # transfer the name (underlying call is still name_update) 209 | try: 210 | # update the 'value' 211 | reply = self.obj.name_update(key, value, new_address) 212 | except JSONRPCException as e: 213 | return e.error 214 | 215 | return reply 216 | 217 | # ----------------------------------- 218 | def check_registration(self, key): 219 | 220 | reply = self.name_show(key) 221 | 222 | if 'code' in reply and reply.get('code') == -4: 223 | return False 224 | elif 'expired' in reply and reply.get('expired') == 1: 225 | return False 226 | else: 227 | return True 228 | 229 | # ----------------------------------- 230 | def validate_address(self, address): 231 | 232 | reply = self.validateaddress(address) 233 | 234 | reply['server'] = self.server 235 | 236 | return reply 237 | 238 | # ----------------------------------- 239 | def get_full_profile(self, key): 240 | 241 | check_profile = self.name_show(key) 242 | 243 | try: 244 | check_profile = check_profile['value'] 245 | except: 246 | return check_profile 247 | 248 | if 'next' in check_profile: 249 | try: 250 | child_data = self.get_full_profile(check_profile['next']) 251 | except: 252 | return check_profile 253 | 254 | del check_profile['next'] 255 | 256 | merged_data = {key: value for (key, value) in ( 257 | check_profile.items() + child_data.items())} 258 | return merged_data 259 | 260 | else: 261 | return check_profile 262 | 263 | # ----------------------------------- 264 | def name_show(self, input_key): 265 | 266 | try: 267 | reply = self.obj.name_show(input_key) 268 | except JSONRPCException as e: 269 | return e.error 270 | 271 | reply['value'] = get_json(reply['value']) 272 | 273 | return reply 274 | 275 | # ----------------------------------- 276 | def unlock_wallet(self, passphrase, timeout=100): 277 | 278 | try: 279 | reply = self.walletpassphrase(passphrase, timeout) 280 | except JSONRPCException as e: 281 | if 'code' in reply: 282 | if reply['code'] == -17: 283 | return True 284 | else: 285 | return False 286 | 287 | return True 288 | 289 | # ----------------------------------- 290 | def importprivkey(self, namecoinprivkey, label='import', rescan=False): 291 | 292 | if not self.unlock_wallet(self.passphrase): 293 | error_reply("Error unlocking wallet", 403) 294 | 295 | try: 296 | reply = self.obj.importprivkey(namecoinprivkey, label, rescan) 297 | except JSONRPCException as e: 298 | return e.error 299 | return reply 300 | -------------------------------------------------------------------------------- /pybitcoin/rpc/namecoind_cluster.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2015 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import os 11 | import sys 12 | import json 13 | 14 | from .config import NAMECOIND_SERVER 15 | 16 | 17 | from .namecoind_client import NamecoindClient 18 | 19 | from .config import NAMECOIND_SERVER, NAMECOIND_PORT, NAMECOIND_USER 20 | from .config import NAMECOIND_PASSWD 21 | from .config import MAIN_SERVER, LOAD_SERVERS 22 | from .config import NAMECOIND_WALLET_PASSPHRASE 23 | 24 | from multiprocessing.pool import ThreadPool 25 | from commontools import log 26 | from commontools import pretty_print as pprint 27 | 28 | # IN NMC 29 | MIN_BALANCE = 25 30 | RELOAD_AMOUNT = 5 31 | 32 | # ----------------------------------- 33 | def pending_transactions(server): 34 | 35 | """ get the no. of pending transactions (0 confirmations) on a server 36 | """ 37 | 38 | namecoind = NamecoindClient(server, NAMECOIND_PORT, 39 | NAMECOIND_USER, NAMECOIND_PASSWD) 40 | 41 | reply = namecoind.listtransactions("", 10000) 42 | 43 | counter = 0 44 | 45 | for i in reply: 46 | if i['confirmations'] == 0: 47 | counter += 1 48 | 49 | return counter 50 | 51 | 52 | # ----------------------------------- 53 | def check_address(address, server=MAIN_SERVER, servers=LOAD_SERVERS): 54 | 55 | reply = {} 56 | reply["server"] = None 57 | reply["ismine"] = False 58 | reply['registered'] = True 59 | 60 | # -------------------------- 61 | def check_address_inner(server): 62 | 63 | try: 64 | namecoind = NamecoindClient(server, NAMECOIND_PORT, 65 | NAMECOIND_USER, NAMECOIND_PASSWD) 66 | 67 | info = namecoind.validate_address(address) 68 | except Exception as e: 69 | log.debug("Error in server %s", server) 70 | log.debug(e) 71 | return 72 | 73 | if info['ismine'] is True: 74 | reply['server'] = server 75 | reply['ismine'] = True 76 | 77 | # first check the main server 78 | check_address_inner(server) 79 | 80 | if reply['ismine'] is True: 81 | return reply 82 | 83 | # if not main server, check others 84 | pool = ThreadPool(len(servers)) 85 | 86 | pool.map(check_address_inner, servers) 87 | pool.close() 88 | pool.join() 89 | 90 | return reply 91 | 92 | 93 | # ----------------------------------- 94 | def get_server(key, server=MAIN_SERVER, servers=LOAD_SERVERS): 95 | 96 | """ given a key, get the IP address of the server that has the pvt key that 97 | owns the name/key 98 | """ 99 | 100 | namecoind = NamecoindClient(NAMECOIND_SERVER, NAMECOIND_PORT, 101 | NAMECOIND_USER, NAMECOIND_PASSWD) 102 | 103 | info = namecoind.name_show(key) 104 | 105 | if 'address' in info: 106 | return check_address(info['address'], server, servers) 107 | 108 | response = {} 109 | response["registered"] = False 110 | response["server"] = None 111 | response["ismine"] = False 112 | return response 113 | 114 | 115 | # ----------------------------------- 116 | def clean_wallet(server, clean_wallet): 117 | 118 | namecoind = NamecoindClient(server) 119 | 120 | reply = namecoind.listtransactions("", 10000) 121 | 122 | counter = 0 123 | counter_total = 0 124 | 125 | track_confirmations = 1000 126 | 127 | for i in reply: 128 | counter_total += 1 129 | 130 | if i['confirmations'] == 0: 131 | 132 | counter += 1 133 | 134 | if clean_wallet: 135 | log.debug(namecoind.deletetransaction(i['txid'])) 136 | 137 | elif i['confirmations'] < track_confirmations: 138 | track_confirmations = i['confirmations'] 139 | 140 | log.debug("%s: %s pending tx, %s confirmations (last tx), %s total tx" 141 | % (server, counter, track_confirmations, counter_total)) 142 | 143 | 144 | # ----------------------------------- 145 | def rebroadcast_tx(server, raw_tx): 146 | 147 | namecoind = NamecoindClient(server) 148 | 149 | print namecoind.sendrawtransaction(raw_tx) 150 | 151 | 152 | # ----------------------------------- 153 | def check_servers(servers, clean=False): 154 | 155 | for server in servers: 156 | clean_wallet(server, clean) 157 | 158 | 159 | # ----------------------------------- 160 | def get_confirmations(server, tx): 161 | 162 | namecoind = NamecoindClient(server) 163 | 164 | reply = namecoind.listtransactions("",10000) 165 | 166 | for entry in reply: 167 | 168 | if entry['txid'] == tx: 169 | return int(entry['confirmations']) 170 | 171 | return 0 172 | 173 | 174 | # ----------------------------------- 175 | def get_receiver_address(server): 176 | 177 | reply = {} 178 | 179 | namecoind = NamecoindClient(server) 180 | 181 | info = namecoind.listreceivedbyaddress() 182 | 183 | address = info[0]['address'] 184 | 185 | info = namecoind.validateaddress(address) 186 | 187 | if info['ismine'] is not True: 188 | msg = "something went wrong" 189 | print msg 190 | reply['error'] = msg 191 | else: 192 | reply['address'] = address 193 | 194 | return address 195 | 196 | 197 | # ----------------------------------- 198 | def check_if_needs_reload(server, min_balance=MIN_BALANCE): 199 | 200 | reply = {} 201 | 202 | namecoind = NamecoindClient(server) 203 | 204 | info = namecoind.getinfo() 205 | balance = float(info['balance']) 206 | 207 | if balance < min_balance: 208 | print "%s needs reloading" % server 209 | return True 210 | 211 | 212 | # ----------------------------------- 213 | def send_payment(server, payments): 214 | 215 | reply = {} 216 | 217 | namecoind = NamecoindClient(server) 218 | 219 | namecoind.unlock_wallet(NAMECOIND_WALLET_PASSPHRASE) 220 | for payment in payments: 221 | print namecoind.sendtoaddress(payment['address'], payment['amount']) 222 | 223 | 224 | # ----------------------------------- 225 | def reload_wallets(main_server, slave_servers=LOAD_SERVERS): 226 | 227 | payments = [] 228 | 229 | for server in LOAD_SERVERS: 230 | #print get_receiver_address(server) 231 | if check_if_needs_reload(server): 232 | reload_tx = {} 233 | reload_tx['amount'] = RELOAD_AMOUNT 234 | reload_tx['address'] = get_receiver_address(server) 235 | payments.append(reload_tx) 236 | 237 | send_payment(main_server, payments) 238 | 239 | # ----------------------------------- 240 | if __name__ == '__main__': 241 | 242 | try: 243 | option = sys.argv[1] 244 | if(option == '--clean'): 245 | check_servers(LOAD_SERVERS, clean=True) 246 | exit(0) 247 | except: 248 | pass 249 | 250 | check_servers(LOAD_SERVERS, clean=False) 251 | -------------------------------------------------------------------------------- /pybitcoin/services/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from .blockchain_client import BlockchainClient 11 | from blockcypher import BlockcypherClient 12 | from blockchain_info import BlockchainInfoClient 13 | from chain_com import ChainComClient 14 | from bitcoind import BitcoindClient, create_bitcoind_service_proxy 15 | 16 | import blockcypher 17 | import blockchain_info 18 | import chain_com 19 | import bitcoind 20 | -------------------------------------------------------------------------------- /pybitcoin/services/bitcoind.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import httplib 11 | 12 | from bitcoinrpc.authproxy import AuthServiceProxy 13 | 14 | from ..constants import SATOSHIS_PER_COIN 15 | 16 | from ..address import script_hex_to_address 17 | 18 | from .blockchain_client import BlockchainClient 19 | 20 | 21 | def create_bitcoind_service_proxy( 22 | rpc_username, rpc_password, server='127.0.0.1', port=8332, use_https=False): 23 | """ create a bitcoind service proxy 24 | """ 25 | protocol = 'https' if use_https else 'http' 26 | uri = '%s://%s:%s@%s:%s' % (protocol, rpc_username, rpc_password, 27 | server, port) 28 | return AuthServiceProxy(uri) 29 | 30 | 31 | class BitcoindClient(BlockchainClient): 32 | def __init__(self, rpc_username, rpc_password, use_https=False, 33 | server='127.0.0.1', port=8332, version_byte=0): 34 | self.type = 'bitcoind' 35 | self.auth = (rpc_username, rpc_password) 36 | self.bitcoind = create_bitcoind_service_proxy(rpc_username, 37 | rpc_password, use_https=use_https, server=server, port=port) 38 | self.version_byte = version_byte 39 | 40 | 41 | def format_unspents(unspents): 42 | return [{ 43 | "transaction_hash": s["txid"], 44 | "output_index": s["vout"], 45 | "value": int(round(s["amount"]*SATOSHIS_PER_COIN)), 46 | "script_hex": s["scriptPubKey"], 47 | "confirmations": s["confirmations"] 48 | } 49 | for s in unspents 50 | ] 51 | 52 | 53 | def get_unspents(address, blockchain_client): 54 | """ Get the spendable transaction outputs, also known as UTXOs or 55 | unspent transaction outputs. 56 | 57 | NOTE: this will only return unspents if the address provided is present 58 | in the bitcoind server. Use the chain, blockchain, or blockcypher API 59 | to grab the unspents for arbitrary addresses. 60 | """ 61 | if isinstance(blockchain_client, BitcoindClient): 62 | bitcoind = blockchain_client.bitcoind 63 | version_byte = blockchain_client.version_byte 64 | elif isinstance(blockchain_client, AuthServiceProxy): 65 | bitcoind = blockchain_client 66 | version_byte = 0 67 | else: 68 | raise Exception('A BitcoindClient object is required') 69 | 70 | addresses = [] 71 | addresses.append(str(address)) 72 | min_confirmations = 0 73 | max_confirmation = 2000000000 # just a very large number for max 74 | unspents = bitcoind.listunspent(min_confirmations, max_confirmation, 75 | addresses) 76 | 77 | return format_unspents(unspents) 78 | 79 | 80 | def broadcast_transaction(hex_tx, blockchain_client): 81 | """ Dispatch a raw transaction to the network. 82 | """ 83 | if isinstance(blockchain_client, BitcoindClient): 84 | bitcoind = blockchain_client.bitcoind 85 | elif isinstance(blockchain_client, AuthServiceProxy): 86 | bitcoind = blockchain_client 87 | else: 88 | raise Exception('A BitcoindClient object is required') 89 | 90 | try: 91 | resp = bitcoind.sendrawtransaction(hex_tx) 92 | except httplib.BadStatusLine: 93 | raise Exception('Invalid HTTP status code from bitcoind.') 94 | 95 | if len(resp) > 0: 96 | return {'tx_hash': resp, 'success': True} 97 | else: 98 | raise Exception('Invalid response from bitcoind.') 99 | -------------------------------------------------------------------------------- /pybitcoin/services/blockchain_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | class BlockchainClient(object): 11 | """ Type parameter can be 'bitcoind', 'blockchain.info', 'chain.com', 12 | 'blockcypher.com', etc. 13 | Auth object is a two item tuple. 14 | """ 15 | def __init__(self, type, auth=None): 16 | self.type = type 17 | if isinstance(auth, tuple) and len(auth) == 2: 18 | self.auth = auth 19 | else: 20 | raise Exception('auth must be a two-item tuple') 21 | -------------------------------------------------------------------------------- /pybitcoin/services/blockchain_info.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import json, requests, traceback 11 | from ..hash import reverse_hash 12 | 13 | BLOCKCHAIN_API_BASE_URL = "https://blockchain.info" 14 | 15 | from .blockchain_client import BlockchainClient 16 | 17 | class BlockchainInfoClient(BlockchainClient): 18 | def __init__(self, api_key=None): 19 | self.type = 'blockchain.info' 20 | if api_key: 21 | self.auth = (api_key, '') 22 | else: 23 | self.auth = None 24 | 25 | def format_unspents(unspents): 26 | return [{ 27 | "transaction_hash": reverse_hash(s["tx_hash"]), 28 | "output_index": s["tx_output_n"], 29 | "value": s["value"], 30 | "script_hex": s["script"], 31 | "confirmations": s["confirmations"] 32 | } 33 | for s in unspents 34 | ] 35 | 36 | def get_unspents(address, blockchain_client=BlockchainInfoClient()): 37 | """ Get the spendable transaction outputs, also known as UTXOs or 38 | unspent transaction outputs. 39 | """ 40 | if not isinstance(blockchain_client, BlockchainInfoClient): 41 | raise Exception('A BlockchainInfoClient object is required') 42 | 43 | url = BLOCKCHAIN_API_BASE_URL + "/unspent?format=json&active=" + address 44 | 45 | auth = blockchain_client.auth 46 | if auth and len(auth) == 2 and isinstance(auth[0], str): 47 | url = url + "&api_code=" + auth[0] 48 | 49 | r = requests.get(url, auth=auth) 50 | try: 51 | unspents = r.json()["unspent_outputs"] 52 | except ValueError, e: 53 | raise Exception('Invalid response from blockchain.info.') 54 | 55 | return format_unspents(unspents) 56 | 57 | def broadcast_transaction(hex_tx, blockchain_client=BlockchainInfoClient()): 58 | """ Dispatch a raw transaction to the network. 59 | """ 60 | url = BLOCKCHAIN_API_BASE_URL + '/pushtx' 61 | payload = {'tx': hex_tx} 62 | r = requests.post(url, data=payload, auth=blockchain_client.auth) 63 | 64 | if 'submitted' in r.text.lower(): 65 | return {'success': True} 66 | else: 67 | raise Exception('Invalid response from blockchain.info.') 68 | 69 | 70 | -------------------------------------------------------------------------------- /pybitcoin/services/blockcypher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | 11 | import json 12 | import requests 13 | 14 | BLOCKCYPHER_BASE_URL = 'https://api.blockcypher.com/v1/btc/main' 15 | 16 | from .blockchain_client import BlockchainClient 17 | 18 | 19 | class BlockcypherClient(BlockchainClient): 20 | def __init__(self, api_key=None): 21 | self.type = 'blockcypher.com' 22 | if api_key: 23 | self.auth = (api_key, '') 24 | else: 25 | self.auth = None 26 | 27 | 28 | def format_unspents(unspents): 29 | 30 | # sandowhich confirmed and unconfiremd unspents 31 | all_unspents = unspents.get('txrefs', []) + unspents.get('unconfirmed_txrefs', []) 32 | 33 | return [{ 34 | "transaction_hash": s["tx_hash"], 35 | "output_index": s["tx_output_n"], 36 | "value": s["value"], 37 | "script_hex": s.get("script"), 38 | "confirmations": s["confirmations"], 39 | } 40 | for s in all_unspents 41 | ] 42 | 43 | 44 | def get_unspents(address, blockchain_client=BlockcypherClient()): 45 | """ Get the spendable transaction outputs, also known as UTXOs or 46 | unspent transaction outputs. 47 | """ 48 | if not isinstance(blockchain_client, BlockcypherClient): 49 | raise Exception('A BlockcypherClient object is required') 50 | 51 | url = '%s/addrs/%s?unspentOnly=true&includeScript=true' % ( 52 | BLOCKCYPHER_BASE_URL, address) 53 | 54 | if blockchain_client.auth: 55 | r = requests.get(url + '&token=' + blockchain_client.auth[0]) 56 | else: 57 | r = requests.get(url) 58 | 59 | try: 60 | unspents = r.json() 61 | except ValueError: 62 | raise Exception('Received non-JSON response from blockcypher.com.') 63 | 64 | # sandwich unconfirmed and confirmed unspents 65 | 66 | return format_unspents(unspents) 67 | 68 | 69 | def broadcast_transaction(hex_tx, blockchain_client): 70 | """ Dispatch a raw hex transaction to the network. 71 | """ 72 | if not isinstance(blockchain_client, BlockcypherClient): 73 | raise Exception('A BlockcypherClient object is required') 74 | 75 | url = '%s/txs/push' % (BLOCKCYPHER_BASE_URL) 76 | payload = json.dumps({'tx': hex_tx}) 77 | r = requests.post(url, data=payload) 78 | 79 | try: 80 | data = r.json() 81 | except ValueError: 82 | raise Exception('Received non-JSON from blockcypher.com.') 83 | 84 | if 'tx' in data: 85 | reply = {} 86 | reply['tx_hash'] = data['tx']['hash'] 87 | reply['success'] = True 88 | return reply 89 | else: 90 | err_str = 'Tx hash missing from blockcypher response: ' + str(data) 91 | raise Exception(err_str) 92 | -------------------------------------------------------------------------------- /pybitcoin/services/chain_com.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import json, requests, traceback 11 | 12 | CHAIN_API_BASE_URL = 'https://api.chain.com/v2' 13 | 14 | from .blockchain_client import BlockchainClient 15 | 16 | class ChainComClient(BlockchainClient): 17 | def __init__(self, api_key_id=None, api_key_secret=None): 18 | self.type = 'chain.com' 19 | if api_key_id and api_key_secret: 20 | self.auth = (api_key_id, api_key_secret) 21 | else: 22 | self.auth = None 23 | 24 | def format_unspents(unspents): 25 | return [{ 26 | "transaction_hash": s["transaction_hash"], 27 | "output_index": s["output_index"], 28 | "value": s["value"], 29 | "script_opcodes": s["script"], 30 | "script_hex": s["script_hex"], 31 | "script_type": s["script_type"], 32 | "confirmations": s["confirmations"] 33 | } 34 | for s in unspents 35 | ] 36 | 37 | def get_unspents(address, blockchain_client=ChainComClient()): 38 | """ Get the spendable transaction outputs, also known as UTXOs or 39 | unspent transaction outputs. 40 | """ 41 | if not isinstance(blockchain_client, ChainComClient): 42 | raise Exception('A ChainComClient object is required') 43 | 44 | url = CHAIN_API_BASE_URL + '/bitcoin/addresses/' + address + '/unspents' 45 | 46 | auth = blockchain_client.auth 47 | if auth: 48 | r = requests.get(url, auth=auth) 49 | else: 50 | r = requests.get(url + '?api-key-id=DEMO-4a5e1e4') 51 | 52 | try: 53 | unspents = r.json() 54 | except ValueError, e: 55 | raise Exception('Received non-JSON response from chain.com.') 56 | 57 | return format_unspents(unspents) 58 | 59 | def broadcast_transaction(hex_tx, blockchain_client): 60 | """ Dispatch a raw hex transaction to the network. 61 | """ 62 | if not isinstance(blockchain_client, ChainComClient): 63 | raise Exception('A ChainComClient object is required') 64 | 65 | auth = blockchain_client.auth 66 | if not auth or len(auth) != 2: 67 | raise Exception('ChainComClient object must have auth credentials.') 68 | 69 | url = CHAIN_API_BASE_URL + '/bitcoin/transactions/send' 70 | payload = json.dumps({ 'signed_hex': hex_tx }) 71 | r = requests.post(url, data=payload, auth=auth) 72 | 73 | try: 74 | data = r.json() 75 | except ValueError, e: 76 | raise Exception('Received non-JSON from chain.com.') 77 | 78 | if 'transaction_hash' in data: 79 | reply = {} 80 | reply['tx_hash'] = data['transaction_hash'] 81 | reply['success'] = True 82 | return reply 83 | else: 84 | raise Exception('Tx hash missing from chain.com response: ' + str(data) + '\noriginal: ' + str(payload)) 85 | 86 | -------------------------------------------------------------------------------- /pybitcoin/transactions/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014-2016 by Halfmoon Labs, Inc. 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import opcodes 11 | 12 | from .network import broadcast_transaction, send_to_address, get_unspents, \ 13 | embed_data_in_blockchain, make_send_to_address_tx, make_op_return_tx, \ 14 | analyze_private_key, serialize_sign_and_broadcast, \ 15 | sign_all_unsigned_inputs 16 | 17 | from .scripts import make_pay_to_address_script, make_op_return_script, \ 18 | script_to_hex 19 | 20 | from .serialize import serialize_input, serialize_output, \ 21 | serialize_transaction, deserialize_transaction 22 | 23 | from .outputs import make_op_return_outputs, make_pay_to_address_outputs 24 | from .utils import flip_endian, variable_length_int 25 | -------------------------------------------------------------------------------- /pybitcoin/transactions/network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from binascii import hexlify, unhexlify 11 | from bitcoin import sign as sign_transaction 12 | 13 | from ..services import blockchain_info, chain_com, bitcoind, blockcypher 14 | from ..privatekey import BitcoinPrivateKey 15 | from .serialize import serialize_transaction, deserialize_transaction 16 | from .outputs import make_pay_to_address_outputs, make_op_return_outputs 17 | from ..constants import STANDARD_FEE, OP_RETURN_FEE 18 | 19 | """ Note: for functions that take in an auth object, here are some examples 20 | for the various APIs available: 21 | 22 | blockcypher.com: auth=(api_key, None) or None 23 | blockchain.info: auth=(api_key, None) 24 | chain.com: auth=(api_key_id, api_key_secret) 25 | """ 26 | 27 | from ..services import (ChainComClient, BlockchainInfoClient, BitcoindClient, 28 | BlockcypherClient, BlockchainClient) 29 | from bitcoinrpc.authproxy import AuthServiceProxy 30 | 31 | 32 | def get_unspents(address, blockchain_client=BlockchainInfoClient()): 33 | """ Gets the unspent outputs for a given address. 34 | """ 35 | if isinstance(blockchain_client, BlockcypherClient): 36 | return blockcypher.get_unspents(address, blockchain_client) 37 | elif isinstance(blockchain_client, BlockchainInfoClient): 38 | return blockchain_info.get_unspents(address, blockchain_client) 39 | elif isinstance(blockchain_client, ChainComClient): 40 | return chain_com.get_unspents(address, blockchain_client) 41 | elif isinstance(blockchain_client, (BitcoindClient, AuthServiceProxy)): 42 | return bitcoind.get_unspents(address, blockchain_client) 43 | elif hasattr(blockchain_client, "get_unspents"): 44 | return blockchain_client.get_unspents( address ) 45 | elif isinstance(blockchain_client, BlockchainClient): 46 | raise Exception('That blockchain interface is not supported.') 47 | else: 48 | raise Exception('A BlockchainClient object is required') 49 | 50 | 51 | def broadcast_transaction(hex_tx, blockchain_client): 52 | """ Dispatches a raw hex transaction to the network. 53 | """ 54 | if isinstance(blockchain_client, BlockcypherClient): 55 | return blockcypher.broadcast_transaction(hex_tx, blockchain_client) 56 | elif isinstance(blockchain_client, BlockchainInfoClient): 57 | return blockchain_info.broadcast_transaction(hex_tx, blockchain_client) 58 | elif isinstance(blockchain_client, ChainComClient): 59 | return chain_com.broadcast_transaction(hex_tx, blockchain_client) 60 | elif isinstance(blockchain_client, (BitcoindClient, AuthServiceProxy)): 61 | return bitcoind.broadcast_transaction(hex_tx, blockchain_client) 62 | elif hasattr(blockchain_client, "broadcast_transaction"): 63 | return blockchain_client.broadcast_transaction( hex_tx ) 64 | elif isinstance(blockchain_client, BlockchainClient): 65 | raise Exception('That blockchain interface is not supported.') 66 | else: 67 | raise Exception('A BlockchainClient object is required') 68 | 69 | 70 | def get_private_key_obj(private_key): 71 | if isinstance(private_key, BitcoinPrivateKey): 72 | return private_key 73 | else: 74 | return BitcoinPrivateKey(private_key) 75 | 76 | 77 | def analyze_private_key(private_key, blockchain_client): 78 | private_key_obj = get_private_key_obj(private_key) 79 | # determine the address associated with the supplied private key 80 | from_address = private_key_obj.public_key().address() 81 | # get the unspent outputs corresponding to the given address 82 | inputs = get_unspents(from_address, blockchain_client) 83 | # return the inputs 84 | return private_key_obj, from_address, inputs 85 | 86 | 87 | def make_send_to_address_tx(recipient_address, amount, private_key, 88 | blockchain_client=BlockchainInfoClient(), fee=STANDARD_FEE, 89 | change_address=None): 90 | """ Builds and signs a "send to address" transaction. 91 | """ 92 | # get out the private key object, sending address, and inputs 93 | private_key_obj, from_address, inputs = analyze_private_key(private_key, 94 | blockchain_client) 95 | # get the change address 96 | if not change_address: 97 | change_address = from_address 98 | # create the outputs 99 | outputs = make_pay_to_address_outputs(recipient_address, amount, inputs, 100 | change_address, fee=fee) 101 | # serialize the transaction 102 | unsigned_tx = serialize_transaction(inputs, outputs) 103 | 104 | # generate a scriptSig for each input 105 | for i in xrange(0, len(inputs)): 106 | signed_tx = sign_transaction(unsigned_tx, i, private_key_obj.to_hex()) 107 | unsigned_tx = signed_tx 108 | 109 | # return the signed tx 110 | return signed_tx 111 | 112 | 113 | def make_op_return_tx(data, private_key, 114 | blockchain_client=BlockchainInfoClient(), fee=OP_RETURN_FEE, 115 | change_address=None, format='bin'): 116 | """ Builds and signs an OP_RETURN transaction. 117 | """ 118 | # get out the private key object, sending address, and inputs 119 | private_key_obj, from_address, inputs = analyze_private_key(private_key, 120 | blockchain_client) 121 | # get the change address 122 | if not change_address: 123 | change_address = from_address 124 | # create the outputs 125 | outputs = make_op_return_outputs(data, inputs, change_address, 126 | fee=fee, format=format) 127 | # serialize the transaction 128 | unsigned_tx = serialize_transaction(inputs, outputs) 129 | 130 | # generate a scriptSig for each input 131 | for i in xrange(0, len(inputs)): 132 | signed_tx = sign_transaction(unsigned_tx, i, private_key_obj.to_hex()) 133 | unsigned_tx = signed_tx 134 | 135 | # return the signed tx 136 | return signed_tx 137 | 138 | 139 | def send_to_address(recipient_address, amount, private_key, 140 | blockchain_client=BlockchainInfoClient(), fee=STANDARD_FEE, 141 | change_address=None): 142 | """ Builds, signs, and dispatches a "send to address" transaction. 143 | """ 144 | # build and sign the tx 145 | signed_tx = make_send_to_address_tx(recipient_address, amount, 146 | private_key, blockchain_client, fee=fee, 147 | change_address=change_address) 148 | # dispatch the signed transction to the network 149 | response = broadcast_transaction(signed_tx, blockchain_client) 150 | # return the response 151 | return response 152 | 153 | 154 | def embed_data_in_blockchain(data, private_key, 155 | blockchain_client=BlockchainInfoClient(), fee=OP_RETURN_FEE, 156 | change_address=None, format='bin'): 157 | """ Builds, signs, and dispatches an OP_RETURN transaction. 158 | """ 159 | # build and sign the tx 160 | signed_tx = make_op_return_tx(data, private_key, blockchain_client, 161 | fee=fee, change_address=change_address, format=format) 162 | # dispatch the signed transction to the network 163 | response = broadcast_transaction(signed_tx, blockchain_client) 164 | # return the response 165 | return response 166 | 167 | 168 | def serialize_sign_and_broadcast(inputs, outputs, private_key, 169 | blockchain_client=BlockchainInfoClient()): 170 | # extract the private key object 171 | private_key_obj = get_private_key_obj(private_key) 172 | 173 | # serialize the transaction 174 | unsigned_tx = serialize_transaction(inputs, outputs) 175 | 176 | # generate a scriptSig for each input 177 | for i in xrange(0, len(inputs)): 178 | signed_tx = sign_transaction(unsigned_tx, i, private_key_obj.to_hex()) 179 | unsigned_tx = signed_tx 180 | 181 | # dispatch the signed transaction to the network 182 | response = broadcast_transaction(signed_tx, blockchain_client) 183 | # return the response 184 | return response 185 | 186 | 187 | def sign_all_unsigned_inputs(hex_privkey, unsigned_tx_hex): 188 | """ 189 | Sign a serialized transaction's unsigned inputs 190 | 191 | @hex_privkey: private key that should sign inputs 192 | @unsigned_tx_hex: hex transaction with unsigned inputs 193 | 194 | Returns: signed hex transaction 195 | """ 196 | inputs, outputs, locktime, version = deserialize_transaction(unsigned_tx_hex) 197 | tx_hex = unsigned_tx_hex 198 | for index in xrange(0, len(inputs)): 199 | if len(inputs[index]['script_sig']) == 0: 200 | 201 | # tx with index i signed with privkey 202 | tx_hex = sign_transaction(str(unsigned_tx_hex), index, hex_privkey) 203 | unsigned_tx_hex = tx_hex 204 | 205 | return tx_hex 206 | -------------------------------------------------------------------------------- /pybitcoin/transactions/opcodes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | OP_0 = 0 11 | OP_PUSHDATA1 = 76 # 0x4c 12 | OP_PUSHDATA2 = 77 # 0x4d 13 | OP_PUSHDATA4 = 78 # 0x4e 14 | OP_1NEGATE = 79 15 | OP_RESERVED = 80 16 | OP_1 = 81 17 | OP_2 = 82 18 | OP_3 = 83 19 | OP_4 = 84 20 | OP_5 = 85 21 | OP_6 = 86 22 | OP_7 = 87 23 | OP_8 = 88 24 | OP_9 = 89 25 | OP_10 = 90 26 | OP_11 = 91 27 | OP_12 = 92 28 | OP_13 = 93 29 | OP_14 = 94 30 | OP_15 = 95 31 | OP_16 = 96 32 | OP_NOP = 97 33 | OP_VER = 98 34 | OP_IF = 99 35 | OP_NOTIF = 100 36 | OP_VERIF = 101 37 | OP_VERNOTIF = 102 38 | OP_ELSE = 103 39 | OP_ENDIF = 104 40 | OP_VERIFY = 105 # 0x69 41 | OP_RETURN = 106 # 0x6a 42 | OP_TOALTSTACK = 107 43 | OP_FROMALTSTACK = 108 44 | OP_2DROP = 109 45 | OP_2DUP = 110 46 | OP_3DUP = 111 47 | OP_2OVER = 112 48 | OP_2ROT = 113 49 | OP_2SWAP = 114 50 | OP_IFDUP = 115 51 | OP_DEPTH = 116 52 | OP_DROP = 117 53 | OP_DUP = 118 54 | OP_NIP = 119 55 | OP_OVER = 120 56 | OP_PICK = 121 57 | OP_ROLL = 122 58 | OP_ROT = 123 59 | OP_SWAP = 124 60 | OP_TUCK = 125 61 | OP_CAT = 126 62 | OP_SUBSTR = 127 63 | OP_LEFT = 128 64 | OP_RIGHT = 129 65 | OP_SIZE = 130 66 | OP_INVERT = 131 67 | OP_AND = 132 68 | OP_OR = 133 69 | OP_XOR = 134 70 | OP_EQUAL = 135 # 0x87 71 | OP_EQUALVERIFY = 136 # 0x88 72 | OP_RESERVED1 = 137 73 | OP_RESERVED2 = 138 74 | OP_1ADD = 139 75 | OP_1SUB = 140 76 | OP_2MUL = 141 77 | OP_2DIV = 142 78 | OP_NEGATE = 143 79 | OP_ABS = 144 80 | OP_NOT = 145 81 | OP_0NOTEQUAL = 146 82 | OP_ADD = 147 83 | OP_SUB = 148 84 | OP_MUL = 149 85 | OP_DIV = 150 86 | OP_MOD = 151 87 | OP_LSHIFT = 152 88 | OP_RSHIFT = 153 89 | OP_BOOLAND = 154 90 | OP_BOOLOR = 155 91 | OP_NUMEQUAL = 156 92 | OP_NUMEQUALVERIFY = 157 93 | OP_NUMNOTEQUAL = 158 94 | OP_LESSTHAN = 159 95 | OP_GREATERTHAN = 160 96 | OP_LESSTHANOREQUAL = 161 97 | OP_GREATERTHANOREQUAL = 162 98 | OP_MIN = 163 99 | OP_MAX = 164 100 | OP_WITHIN = 165 101 | OP_RIPEMD160 = 166 102 | OP_SHA1 = 167 103 | OP_SHA256 = 168 104 | OP_HASH160 = 169 # 0xa9 105 | OP_HASH256 = 170 106 | OP_CODESEPARATOR = 171 107 | OP_CHECKSIG = 172 # 0xac 108 | OP_CHECKSIGVERIFY = 173 # 0xad 109 | OP_CHECKMULTISIG = 174 # 0xae 110 | OP_CHECKMULTISIGVERIFY = 175 # 0xaf 111 | OP_NOP1 = 176 112 | OP_NOP2 = 177 113 | OP_NOP3 = 178 114 | OP_NOP4 = 179 115 | OP_NOP5 = 180 116 | OP_NOP6 = 181 117 | OP_NOP7 = 182 118 | OP_NOP8 = 183 119 | OP_NOP9 = 184 120 | OP_NOP10 = 185 121 | OP_PUBKEYHASH = 253 # 0xfd 122 | OP_PUBKEY = 254 # 0xfe 123 | OP_INVALIDOPCODE = 255 124 | -------------------------------------------------------------------------------- /pybitcoin/transactions/outputs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from .scripts import make_pay_to_address_script, make_op_return_script 11 | from ..constants import STANDARD_FEE, OP_RETURN_FEE 12 | 13 | def calculate_change_amount(inputs, send_amount, fee): 14 | # calculate the total amount coming into the transaction from the inputs 15 | total_amount_in = sum([input['value'] for input in inputs]) 16 | # change = whatever is left over from the amount sent & the transaction fee 17 | change_amount = total_amount_in - send_amount - fee 18 | # check to ensure the change amount is a non-negative value and return it 19 | if change_amount < 0: 20 | raise ValueError('Not enough inputs for transaction (total: %s, to spend: %s, fee: %s).' % (total_amount_in, send_amount, fee)) 21 | return change_amount 22 | 23 | def make_pay_to_address_outputs(to_address, send_amount, inputs, change_address, 24 | fee=STANDARD_FEE): 25 | """ Builds the outputs for a "pay to address" transaction. 26 | """ 27 | return [ 28 | # main output 29 | { "script_hex": make_pay_to_address_script(to_address), "value": send_amount }, 30 | # change output 31 | { "script_hex": make_pay_to_address_script(change_address), 32 | "value": calculate_change_amount(inputs, send_amount, fee) 33 | } 34 | ] 35 | 36 | def make_op_return_outputs(data, inputs, change_address, fee=OP_RETURN_FEE, 37 | send_amount=0, format='bin'): 38 | """ Builds the outputs for an OP_RETURN transaction. 39 | """ 40 | return [ 41 | # main output 42 | { "script_hex": make_op_return_script(data, format=format), "value": send_amount }, 43 | # change output 44 | { "script_hex": make_pay_to_address_script(change_address), 45 | "value": calculate_change_amount(inputs, send_amount, fee) 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /pybitcoin/transactions/scripts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | from .opcodes import * 11 | from .utils import count_bytes 12 | from ..constants import MAX_BYTES_AFTER_OP_RETURN 13 | from ..b58check import b58check_decode, b58check_encode 14 | from binascii import hexlify, unhexlify 15 | from utilitybelt import is_hex 16 | 17 | def script_to_hex(script): 18 | """ Parse the string representation of a script and return the hex version. 19 | Example: "OP_DUP OP_HASH160 c629...a6db OP_EQUALVERIFY OP_CHECKSIG" 20 | """ 21 | hex_script = '' 22 | parts = script.split(' ') 23 | for part in parts: 24 | if part[0:3] == 'OP_': 25 | try: 26 | hex_script += '%0.2x' % eval(part) 27 | except: 28 | raise Exception('Invalid opcode: %s' % part) 29 | elif isinstance(part, (int)): 30 | hex_script += '%0.2x' % part 31 | elif is_hex(part): 32 | hex_script += '%0.2x' % count_bytes(part) + part 33 | else: 34 | raise Exception('Invalid script - only opcodes and hex characters allowed.') 35 | return hex_script 36 | 37 | def make_pay_to_address_script(address): 38 | """ Takes in an address and returns the script 39 | """ 40 | hash160 = hexlify(b58check_decode(address)) 41 | script_string = 'OP_DUP OP_HASH160 %s OP_EQUALVERIFY OP_CHECKSIG' % hash160 42 | return script_to_hex(script_string) 43 | 44 | def make_op_return_script(data, format='bin'): 45 | """ Takes in raw ascii data to be embedded and returns a script. 46 | """ 47 | if format == 'hex': 48 | assert(is_hex(data)) 49 | hex_data = data 50 | elif format == 'bin': 51 | hex_data = hexlify(data) 52 | else: 53 | raise Exception("Format must be either 'hex' or 'bin'") 54 | 55 | num_bytes = count_bytes(hex_data) 56 | if num_bytes > MAX_BYTES_AFTER_OP_RETURN: 57 | raise Exception('Data is %i bytes - must not exceed 40.' % num_bytes) 58 | 59 | script_string = 'OP_RETURN %s' % hex_data 60 | return script_to_hex(script_string) 61 | 62 | -------------------------------------------------------------------------------- /pybitcoin/transactions/serialize.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014-2016 by Halfmoon Labs, Inc. 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import bitcoin 11 | import struct 12 | 13 | from binascii import hexlify, unhexlify 14 | 15 | from .utils import flip_endian, variable_length_int 16 | from ..constants import UINT_MAX 17 | from utilitybelt import is_hex 18 | 19 | 20 | def serialize_input(input, signature_script_hex=''): 21 | """ Serializes a transaction input. 22 | """ 23 | if not (isinstance(input, dict) and 'transaction_hash' in input \ 24 | and 'output_index' in input): 25 | raise Exception('Required parameters: transaction_hash, output_index') 26 | 27 | if is_hex(str(input['transaction_hash'])) and len(str(input['transaction_hash'])) != 64: 28 | raise Exception("Transaction hash '%s' must be 32 bytes" % input['transaction_hash']) 29 | 30 | elif not is_hex(str(input['transaction_hash'])) and len(str(input['transaction_hash'])) != 32: 31 | raise Exception("Transaction hash '%s' must be 32 bytes" % hexlify(input['transaction_hash'])) 32 | 33 | if not 'sequence' in input: 34 | input['sequence'] = UINT_MAX 35 | 36 | return ''.join([ 37 | flip_endian(input['transaction_hash']), 38 | hexlify(struct.pack(' 0 22 | and cls.__bases__[0].__name__ == 'BitcoinKeypair'): 23 | return True 24 | 25 | _messages = { 26 | "SHORT_PASSPHRASE": "Warning! Passphrase must be at least %s characters.", 27 | "INVALID_KEYPAIR_CLASS": "Class must be a valid currency keypair class.", 28 | } 29 | 30 | 31 | class SDWallet(): 32 | """ A sequential deterministic wallet. 33 | """ 34 | 35 | def __init__(self, passphrase=None): 36 | """ Create wallet from a passphrase input. """ 37 | if not passphrase: 38 | passphrase = create_passphrase(bits_of_entropy=160) 39 | 40 | self._passphrase = passphrase 41 | 42 | def passphrase(self): 43 | return self._passphrase 44 | 45 | def keypair(self, i, keypair_class): 46 | """ Return the keypair that corresponds to the provided sequence number 47 | and keypair class (BitcoinKeypair, etc.). 48 | """ 49 | 50 | # Make sure keypair_class is a valid cryptocurrency keypair 51 | if not is_cryptocurrency_keypair_class(keypair_class): 52 | raise Exception(_messages["INVALID_KEYPAIR_CLASS"]) 53 | 54 | currency_name = keypair_class.__name__.lower().replace('keypair', '') 55 | 56 | k = keypair_class.from_passphrase( 57 | self._passphrase + " " + currency_name + str(i)) 58 | 59 | return k 60 | 61 | 62 | class HDWallet(): 63 | """ A hierarchical deterministic wallet in accordance with BIP 32. 64 | """ 65 | 66 | def __init__(self): 67 | raise NotImplementedError() 68 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.4.3 2 | ecdsa>=0.13 3 | commontools==0.1.0 4 | utilitybelt>=0.2.1 5 | python-bitcoinrpc==0.1 6 | keychain>=0.1.4 7 | bitcoin>=1.1.42 8 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | secrets_list = [ 4 | 'CHAIN_API_ID', 'CHAIN_API_SECRET', 'BITCOIN_PRIVATE_KEY', \ 5 | 'BITCOIN_PRIVATE_KEY_2', 'BITCOIND_RPC_USERNAME', 'BITCOIND_RPC_PASSWORD', 6 | 'NAMECOIN_PRIVATE_KEY', 'BLOCKCHAIN_API_KEY', 'BLOCKCYPHER_API_KEY' 7 | ] 8 | 9 | for env_variable in os.environ: 10 | if env_variable in secrets_list: 11 | env_value = os.environ[env_variable] 12 | exec(env_variable + " = \"\"\"" + env_value + "\"\"\"") -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pep8] 2 | ignore = E123,E128,E501 3 | 4 | [flake8] 5 | ignore = E123,E128,E501 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | pybitcoin 4 | ============== 5 | 6 | """ 7 | 8 | from setuptools import setup, find_packages 9 | 10 | setup( 11 | name='pybitcoin', 12 | version='0.9.9', 13 | url='https://github.com/blockstack/pybitcoin', 14 | license='MIT', 15 | author='Blockstack Developers', 16 | author_email='hello@onename.com', 17 | description="""Library for Bitcoin & other cryptocurrencies. Tools are provided for blockchain transactions, RPC calls, and private keys, public keys, and addresses.""", 18 | keywords='bitcoin btc litecoin namecoin dogecoin cryptocurrency', 19 | packages=find_packages(), 20 | zip_safe=False, 21 | install_requires=[ 22 | 'requests>=2.4.3', 23 | 'ecdsa>=0.13', 24 | 'commontools==0.1.0', 25 | 'utilitybelt>=0.2.6', 26 | 'python-bitcoinrpc==0.1', 27 | 'keychain>=0.1.4', 28 | 'bitcoin>=1.1.42' 29 | ], 30 | classifiers=[ 31 | 'Intended Audience :: Developers', 32 | 'License :: OSI Approved :: MIT License', 33 | 'Operating System :: OS Independent', 34 | 'Programming Language :: Python', 35 | 'Topic :: Internet', 36 | 'Topic :: Security :: Cryptography', 37 | 'Topic :: Software Development :: Libraries :: Python Modules', 38 | ], 39 | ) 40 | -------------------------------------------------------------------------------- /tests/test_rpc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | 7 | # Hack around absolute paths 8 | current_dir = os.path.abspath(os.path.dirname(__file__)) 9 | parent_dir = os.path.abspath(current_dir + "/../") 10 | 11 | sys.path.insert(0, parent_dir) 12 | 13 | import unittest 14 | from pybitcoin.rpc import BitcoindClient, NamecoindClient 15 | 16 | bitcoind = BitcoindClient() 17 | namecoind = NamecoindClient() 18 | 19 | 20 | class NamecoindTestCase(unittest.TestCase): 21 | 22 | def test_connectivity(self): 23 | """ Check if can connect to namecoind 24 | """ 25 | 26 | blocks = namecoind.blocks() 27 | self.assertIsNotNone(blocks, msg='Namecoind is not responding') 28 | 29 | def test_full_profile(self): 30 | """ Check if can connect to namecoind 31 | """ 32 | 33 | key = 'u/muneeb' 34 | 35 | profile = namecoind.get_full_profile(key) 36 | self.assertIsInstance(profile, dict, msg='Could not get full profile') 37 | 38 | 39 | class BitcoindTestCase(unittest.TestCase): 40 | 41 | def test_connectivity(self): 42 | """ Check if can connect to bitcoind 43 | """ 44 | 45 | blocks = bitcoind.blocks() 46 | self.assertIsNotNone(blocks, msg='Bitcoind is not responding') 47 | 48 | if __name__ == '__main__': 49 | 50 | unittest.main() 51 | -------------------------------------------------------------------------------- /unit_tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | pybitcoin 4 | ~~~~~ 5 | 6 | :copyright: (c) 2014 by Halfmoon Labs 7 | :license: MIT, see LICENSE for more details. 8 | """ 9 | 10 | import json 11 | import traceback 12 | import unittest 13 | from test import test_support 14 | 15 | from pybitcoin.publickey import BitcoinPublicKey 16 | from pybitcoin.privatekey import BitcoinPrivateKey, NamecoinPrivateKey 17 | from pybitcoin.keypair import BitcoinKeypair 18 | from pybitcoin.rpc import BitcoindClient 19 | from pybitcoin.wallet import SDWallet 20 | from pybitcoin.merkle import MerkleTree, calculate_merkle_root 21 | 22 | from pybitcoin.b58check import b58check_encode, b58check_decode, b58check_unpack 23 | from pybitcoin.formatcheck import is_b58check_address, is_256bit_hex_string, \ 24 | is_wif_pk 25 | 26 | from pybitcoin.transactions import analyze_private_key 27 | from pybitcoin.transactions.network import make_send_to_address_tx, \ 28 | make_op_return_tx, send_to_address, broadcast_transaction 29 | 30 | from pybitcoin.services import blockcypher 31 | from pybitcoin.services import blockchain_info 32 | from pybitcoin.services.bitcoind import create_bitcoind_service_proxy 33 | 34 | from pybitcoin import PrivateKeychain, PublicKeychain 35 | 36 | get_class = lambda x: globals()[x] 37 | 38 | from settings import BITCOIND_RPC_PASSWORD, BITCOIND_RPC_USERNAME, \ 39 | BLOCKCHAIN_API_KEY, BLOCKCYPHER_API_KEY, \ 40 | BITCOIN_PRIVATE_KEY, BITCOIN_PRIVATE_KEY_2 41 | 42 | bitcoind_client = BitcoindClient( 43 | server='btcd.onename.com', port=8332, user=BITCOIND_RPC_USERNAME, 44 | passwd=BITCOIND_RPC_PASSWORD, use_https=True) 45 | 46 | 47 | _reference_info = { 48 | 'passphrase': 'correct horse battery staple', 49 | 'bin_private_key': '\xc4\xbb\xcb\x1f\xbe\xc9\x9de\xbfY\xd8\\\x8c\xb6.\xe2\xdb\x96?\x0f\xe1\x06\xf4\x83\xd9\xaf\xa7;\xd4\xe3\x9a\x8a', 50 | 'hex_private_key': 'c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a', 51 | 'hex_public_key': '0478d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455', 52 | 'hex_hash160': 'c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827', 53 | 'wif_private_key': '5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS', 54 | 'address': '1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T', 55 | 'wif_version_byte': 128, 56 | 'pem_private_key': '-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIMS7yx++yZ1lv1nYXIy2LuLblj8P4Qb0g9mvpzvU45qKoAcGBSuBBAAK\noUQDQgAEeNQwJ0+MXsEyEzgVHp8n9MZ2oAi9+GONB8C2vpqzXHGhUYBjJDrNTf6W\ntm4/LsgBPI4HLNCbODShn4H2Wcw0VQ==\n-----END EC PRIVATE KEY-----\n', 57 | 'pem_public_key': '-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEeNQwJ0+MXsEyEzgVHp8n9MZ2oAi9+GON\nB8C2vpqzXHGhUYBjJDrNTf6Wtm4/LsgBPI4HLNCbODShn4H2Wcw0VQ==\n-----END PUBLIC KEY-----\n', 58 | 'der_private_key': '30740201010420c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8aa00706052b8104000aa1440342000478d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455', 59 | 'der_public_key': '3056301006072a8648ce3d020106052b8104000a0342000478d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455' 60 | } 61 | 62 | 63 | class BitcoinUncompressedPublicKeyTest(unittest.TestCase): 64 | reference = _reference_info 65 | 66 | def setUp(self): 67 | self.public_key = BitcoinPublicKey(self.reference['hex_public_key']) 68 | 69 | def tearDown(self): 70 | pass 71 | 72 | def test_address(self): 73 | self.assertEqual(self.public_key.address(), self.reference['address']) 74 | 75 | def test_hex_hash160(self): 76 | self.assertEqual( 77 | self.public_key.hash160(), self.reference['hex_hash160']) 78 | 79 | def test_hex_public_key(self): 80 | self.assertEqual( 81 | self.public_key.to_hex(), self.reference['hex_public_key']) 82 | 83 | def test_pem_public_key(self): 84 | self.assertEqual( 85 | self.public_key.to_pem(), self.reference['pem_public_key']) 86 | 87 | def test_der_public_key(self): 88 | self.assertEqual( 89 | self.public_key.to_der(), self.reference['der_public_key']) 90 | 91 | 92 | class BitcoinCompressedPublicKeyTest(unittest.TestCase): 93 | def setUp(self): 94 | self.reference = { 95 | 'hex_public_key': '02068fd9d47283fb310e6dfb66b141dd78fbabc76d073d48cddc770ffb2bd262d7', 96 | 'bin_public_key': '\x02\x06\x8f\xd9\xd4r\x83\xfb1\x0em\xfbf\xb1A\xddx\xfb\xab\xc7m\x07=H\xcd\xdcw\x0f\xfb+\xd2b\xd7', 97 | 'hex_hash160': '25488b0d3bb770d6e0ef07e1f19d33ab59931dee', 98 | 'bin_hash160': '%H\x8b\r;\xb7p\xd6\xe0\xef\x07\xe1\xf1\x9d3\xabY\x93\x1d\xee', 99 | 'address': '14Q8uVAX29RUMvqPGXL5sg6NiwwMRFCm8C', 100 | } 101 | self.public_key = BitcoinPublicKey(self.reference['hex_public_key']) 102 | 103 | def tearDown(self): 104 | pass 105 | 106 | def test_address(self): 107 | self.assertEqual(self.public_key.address(), self.reference['address']) 108 | 109 | def test_bin_hash160(self): 110 | self.assertEqual( 111 | self.public_key.bin_hash160(), self.reference['bin_hash160']) 112 | 113 | def test_hex_hash160(self): 114 | self.assertEqual( 115 | self.public_key.hash160(), self.reference['hex_hash160']) 116 | 117 | def test_bin_public_key(self): 118 | self.assertEqual( 119 | self.public_key.to_bin(), self.reference['bin_public_key']) 120 | 121 | def test_hex_public_key(self): 122 | self.assertEqual( 123 | self.public_key.to_hex(), self.reference['hex_public_key']) 124 | 125 | 126 | class BitcoinPublicKeyCreationTest(unittest.TestCase): 127 | def setUp(self): 128 | self.address_compressed = '14Q8uVAX29RUMvqPGXL5sg6NiwwMRFCm8C' 129 | self.address_uncompressed = '1AuZor1RVzG22wqbH2sG2j5WRDZsbw1tip' 130 | 131 | def tearDown(self): 132 | pass 133 | 134 | def test_create_pubkey_from_hex_uncompressed_format(self): 135 | public_key_string = '04068fd9d47283fb310e6dfb66b141dd78fbabc76d073d48cddc770ffb2bd262d7b2832f87f683100b89c2e95314deeeacbc6409af1e36c3ae3fd8c5f2f243cfec' 136 | self.assertEqual(self.address_uncompressed, BitcoinPublicKey( 137 | public_key_string).address()) 138 | 139 | def test_create_pubkey_from_bin_uncompressed_format(self): 140 | public_key_string = '\x04\x06\x8f\xd9\xd4r\x83\xfb1\x0em\xfbf\xb1A\xddx\xfb\xab\xc7m\x07=H\xcd\xdcw\x0f\xfb+\xd2b\xd7\xb2\x83/\x87\xf6\x83\x10\x0b\x89\xc2\xe9S\x14\xde\xee\xac\xbcd\t\xaf\x1e6\xc3\xae?\xd8\xc5\xf2\xf2C\xcf\xec' 141 | self.assertEqual(self.address_uncompressed, BitcoinPublicKey( 142 | public_key_string).address()) 143 | 144 | def test_create_pubkey_from_hex_ecdsa_format(self): 145 | public_key_string = '068fd9d47283fb310e6dfb66b141dd78fbabc76d073d48cddc770ffb2bd262d7b2832f87f683100b89c2e95314deeeacbc6409af1e36c3ae3fd8c5f2f243cfec' 146 | self.assertEqual(self.address_uncompressed, BitcoinPublicKey( 147 | public_key_string).address()) 148 | 149 | def test_create_pubkey_from_bin_ecdsa_format(self): 150 | public_key_string = '\x06\x8f\xd9\xd4r\x83\xfb1\x0em\xfbf\xb1A\xddx\xfb\xab\xc7m\x07=H\xcd\xdcw\x0f\xfb+\xd2b\xd7\xb2\x83/\x87\xf6\x83\x10\x0b\x89\xc2\xe9S\x14\xde\xee\xac\xbcd\t\xaf\x1e6\xc3\xae?\xd8\xc5\xf2\xf2C\xcf\xec' 151 | self.assertEqual(self.address_uncompressed, BitcoinPublicKey( 152 | public_key_string).address()) 153 | 154 | def test_create_pubkey_from_hex_compressed_format(self): 155 | public_key_string = '02068fd9d47283fb310e6dfb66b141dd78fbabc76d073d48cddc770ffb2bd262d7' 156 | self.assertEqual(self.address_compressed, BitcoinPublicKey( 157 | public_key_string).address()) 158 | 159 | def test_create_pubkey_from_bin_compressed_format(self): 160 | public_key_string = '\x02\x06\x8f\xd9\xd4r\x83\xfb1\x0em\xfbf\xb1A\xddx\xfb\xab\xc7m\x07=H\xcd\xdcw\x0f\xfb+\xd2b\xd7' 161 | self.assertEqual(self.address_compressed, BitcoinPublicKey( 162 | public_key_string).address()) 163 | 164 | 165 | class BitcoinPrivateKeyToPublicKeyTest(unittest.TestCase): 166 | reference = _reference_info 167 | 168 | def setUp(self): 169 | pass 170 | 171 | def tearDown(self): 172 | pass 173 | 174 | def test_private_key_to_public_key_conversion(self): 175 | priv = BitcoinPrivateKey(self.reference['hex_private_key']) 176 | pub = priv.public_key() 177 | self.assertEqual(pub.to_hex(), self.reference['hex_public_key']) 178 | self.assertEqual(pub.address(), self.reference['address']) 179 | 180 | 181 | class BitcoinPrivateKeyTest(unittest.TestCase): 182 | reference = _reference_info 183 | 184 | def setUp(self): 185 | self.private_key = BitcoinPrivateKey(self.reference['hex_private_key']) 186 | 187 | def tearDown(self): 188 | pass 189 | 190 | def test_private_key_from_wif(self): 191 | self.private_key_from_wif = BitcoinPrivateKey( 192 | self.reference['wif_private_key']) 193 | self.assertEqual( 194 | self.private_key.to_hex(), self.private_key_from_wif.to_hex()) 195 | 196 | def test_hex_private_key(self): 197 | self.assertEqual( 198 | self.private_key.to_hex(), self.reference['hex_private_key']) 199 | 200 | def test_wif_private_key(self): 201 | self.assertEqual( 202 | self.private_key.to_wif(), self.reference['wif_private_key']) 203 | 204 | def test_pem_private_key(self): 205 | self.assertEqual( 206 | self.private_key.to_pem(), self.reference['pem_private_key']) 207 | 208 | def test_der_private_key(self): 209 | self.assertEqual( 210 | self.private_key.to_der(), self.reference['der_private_key']) 211 | 212 | 213 | class BitcoinKeypairTest(unittest.TestCase): 214 | reference = _reference_info 215 | 216 | def setUp(self): 217 | self.keypair = BitcoinKeypair(self.reference['hex_private_key']) 218 | 219 | def tearDown(self): 220 | pass 221 | 222 | def test_hex_private_key(self): 223 | self.assertEqual( 224 | self.keypair.private_key(), self.reference['hex_private_key']) 225 | 226 | def test_wif_private_key(self): 227 | self.assertEqual( 228 | self.keypair.wif_pk(), self.reference['wif_private_key']) 229 | 230 | def test_address(self): 231 | self.assertEqual( 232 | self.keypair.address(), self.reference['address']) 233 | 234 | def test_hex_hash160(self): 235 | self.assertEqual(self.keypair.hash160(), self.reference['hex_hash160']) 236 | 237 | def test_public_key(self): 238 | self.assertEqual( 239 | self.keypair.public_key(), self.reference['hex_public_key']) 240 | 241 | 242 | class AltcoinKeypairTest(unittest.TestCase): 243 | coin_names = [ 244 | 'bitcoin', 'litecoin', 'namecoin', 'peercoin', 'primecoin', 245 | 'dogecoin', 'worldcoin', 'feathercoin', 'terracoin', 'novacoin', 246 | 'testnet', 'protoshares', 'memorycoin', 'quarkcoin', 'infinitecoin', 247 | 'cryptogenicbullion', 'ixcoin', 'anoncoin', 'megacoin' 248 | ] 249 | 250 | reference = { 251 | 'passphrase': 'correct horse battery staple', 252 | 'hex_private_key': 'c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a', 253 | 'hex_public_key': '78d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455', 254 | 'hex_hash160': 'c4c5d791fcb4654a1ef5e03fe0ad3d9c598f9827', 255 | ('bitcoin', 'wif'): '5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS', 256 | ('bitcoin', 'address'): '1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T', 257 | ('litecoin', 'wif'): '6vcfLvDpYnHdbVxoQa6Lmo3k9iR5xVjKwwf3dp4XgmQT3QJywYi', 258 | ('litecoin', 'address'): 'LdAPi7uXrLLmeh7u57pzkZc3KovxEDYRJq', 259 | ('namecoin', 'wif'): '74Pe3r1wxUzY8nHd2taLb5SqpAsxZK6q6VwUcQp7fPS11tYZd9P', 260 | ('namecoin', 'address'): 'NEWoeZ6gh4CGvRgFAoAGh4hBqpxizGT6gZ', 261 | ('peercoin', 'wif'): '7ADsaYN3Wm2DYF2jkdSLT3FAZWj7WRdTTR9oLrsoeMTAVgq1Mho', 262 | ('peercoin', 'address'): 'PSXcbszYpbauNj6WF4AE9SWYjLjZArBajH', 263 | ('primecoin', 'wif'): '6623w812F9NyDzSAk5aMvn4PFs28htfSGxtMY4s7qPEkhoV8sQS', 264 | ('primecoin', 'address'): 'AZiK6QTL6pksCrdjTdW2dRoNbCVNQ7zRs6', 265 | ('dogecoin', 'wif'): '6KdGAk9FD87ZAjW768vMc2FoffLAFpZZnSP7F7gPnyHUA9ttj7B', 266 | ('dogecoin', 'address'): 'DP5XzAYM55zzvtcLdZqG2JhszjHyNnvW8i', 267 | ('worldcoin', 'wif'): '7mDGkiScrRCHy1VS54cKcp373Zp3D6oDcvRjjZFwY9a9NushHNZ', 268 | ('worldcoin', 'address'): 'WgcUKqMjbqvg6Xc4gc9xshQi4RNY1S38TD', 269 | ('feathercoin', 'wif'): '5nXMM2xjaKHw1cCparzNLtfR1qUfrZ5ZCDFPLig3tVBGGBK2QwG', 270 | ('feathercoin', 'address'): '6wftERmjiCayqxNxErWAGJMHvfAt4RZZbn', 271 | ('terracoin', 'wif'): '5KJvsngHeMpm884wtkJNzQGaCErckhHJBGFsvd3VyK5qMZXj3hS', 272 | ('terracoin', 'address'): '1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T', 273 | ('novacoin', 'wif'): '5artHeGYTmEaCgib9PGNcy4mX9nMxL2JUNpjspYfvZ8wJWQjuBJ', 274 | ('novacoin', 'address'): '4XeGKmz1T7oiwMYS6LWFMYia9ddDoT6ajT', 275 | ('ixcoin', 'wif'): 'Mw64RiX6A23DKVivM4USZXC8nBt3bqyKquB8wsifzJ589JYYDF', 276 | ('ixcoin', 'address'): 'xqagKtjTka3dFhfhGsogPr6qyD7rAzGQKQ', 277 | ('testnet', 'wif'): '935ZTXVqEatu6BaEX6CHrzpXquDKurpVXD7q1FQ1K3pt8VwmG2L', 278 | ('testnet', 'address'): 'myTPjxggahXyAzuMcYp5JTkbybANyLsYBW', 279 | ('protoshares', 'wif'): '7CAckmp5NBhSg4cSfD4LQMqwUdLqA8ULF4Dub1Zhe1TYzJcerWL', 280 | ('protoshares', 'address'): 'PqsDazHqXn3nCAEbGUVYdZnLMqzVqdmE9z', 281 | ('memorycoin', 'wif'): '6zW9hP7tFde5s98DDjLLgSFHyweXFuR5XDoG87SKg5RE2dHMpaF', 282 | ('memorycoin', 'address'): 'MRqbgLW7GhGXHZQ57xVdip9capSqZatiut', 283 | ('quarkcoin', 'wif'): '7G477Ei9533twhmrUNJLK13VJraGTYA5pLN85JwVdKUKyd6oDz6', 284 | ('quarkcoin', 'address'): 'QeYRZCtQx8yXq2WmKKABbpKucrWPFn2Z8g', 285 | ('infinitecoin', 'wif'): '8jarsSTYZkorsoLtMscJH7RZbsfs4XEcSTUrouCwN9mPgw1j4iq', 286 | ('infinitecoin', 'address'): 'iMQxsz16C5N5p6eaPmpCwLJXK3qtXZuvoh', 287 | ('cryptogenicbullion', 'wif'): '5gh7pLce23GFc9Ths88NUvs6GVdWuSYvqJ34cGcMuXA6nPooqdc', 288 | ('cryptogenicbullion', 'address'): '5jf5H6ssafCMPexhAbWCovXw39Q3ryw5ic', 289 | ('anoncoin', 'wif'): '6623w812F9NyDzSAk5aMvn4PFs28htfSGxtMY4s7qPEkhoV8sQS', 290 | ('anoncoin', 'address'): 'AZiK6QTL6pksCrdjTdW2dRoNbCVNQ7zRs6', 291 | ('megacoin', 'wif'): '6zW9hP7tFde5s98DDjLLgSFHyweXFuR5XDoG87SKg5RE2dHMpaF', 292 | ('megacoin', 'address'): 'MRqbgLW7GhGXHZQ57xVdip9capSqZatiut', 293 | } 294 | 295 | def setUp(self): 296 | pass 297 | 298 | def tearDown(self): 299 | pass 300 | 301 | 302 | class BitcoinBrainWalletKeypairTest(BitcoinKeypairTest): 303 | def setUp(self): 304 | BitcoinKeypairTest.setUp(self) 305 | self.keypair = BitcoinKeypair.from_passphrase( 306 | self.reference['passphrase']) 307 | 308 | def test_passphrase(self): 309 | self.assertEqual( 310 | self.keypair.passphrase(), self.reference['passphrase']) 311 | 312 | def test_random_passphrase_length(self): 313 | random_keypair = BitcoinKeypair.from_passphrase() 314 | self.assertTrue(len(random_keypair.passphrase().split()) > 1) 315 | 316 | 317 | class BitcoinKeypairFromWIFTest(BitcoinKeypairTest): 318 | def setUp(self): 319 | BitcoinKeypairTest.setUp(self) 320 | self.keypair = BitcoinKeypair.from_private_key( 321 | self.reference['wif_private_key']) 322 | 323 | 324 | class RandomBitcoinKeypairsTest(unittest.TestCase): 325 | def setUp(self): 326 | self.keypair = BitcoinKeypair() 327 | self.brainwallet_keypair = BitcoinKeypair.from_passphrase() 328 | 329 | def tearDown(self): 330 | pass 331 | 332 | def test_keypair(self): 333 | # self.assertTrue(is_256bit_hex_string(self.keypair.private_key())) 334 | # self.assertTrue(is_wif_pk(self.keypair.wif_pk())) 335 | self.assertTrue(is_b58check_address(self.keypair.address())) 336 | 337 | def test_brainwallet_keypair(self): 338 | self.assertTrue(len(self.brainwallet_keypair.passphrase().split()) > 1) 339 | 340 | 341 | class BitcoinB58CheckTest(unittest.TestCase): 342 | reference = _reference_info 343 | 344 | def setUp(self): 345 | pass 346 | 347 | def tearDown(self): 348 | pass 349 | 350 | def test_b58check_encode_then_decode(self): 351 | bin_private_key = self.reference['hex_private_key'].decode('hex') 352 | wif_private_key = b58check_encode( 353 | bin_private_key, version_byte=self.reference['wif_version_byte']) 354 | self.assertEqual(self.reference['wif_private_key'], wif_private_key) 355 | bin_private_key_verification = b58check_decode(wif_private_key) 356 | self.assertEqual(bin_private_key_verification, bin_private_key) 357 | 358 | def test_b58check_unpack_then_encode(self): 359 | version_byte, bin_private_key, checksum = b58check_unpack( 360 | self.reference['wif_private_key']) 361 | self.assertTrue( 362 | ord(version_byte) == self.reference['wif_version_byte']) 363 | wif_private_key = b58check_encode( 364 | bin_private_key, version_byte=ord(version_byte)) 365 | self.assertEqual(self.reference['wif_private_key'], wif_private_key) 366 | 367 | 368 | class BitcoinFormatCheckTest(unittest.TestCase): 369 | reference = _reference_info 370 | 371 | def setUp(self): 372 | pass 373 | 374 | def tearDown(self): 375 | pass 376 | 377 | def test_is_wif_private_key(self): 378 | self.assertTrue(is_wif_pk(self.reference['wif_private_key'])) 379 | 380 | def test_is_hex_private_key(self): 381 | self.assertTrue( 382 | is_256bit_hex_string(self.reference['hex_private_key'])) 383 | 384 | 385 | class SequentialWalletTest(unittest.TestCase): 386 | reference = { 387 | 'passphrase': ('shepherd mais pack rate enamel horace diva filesize' 388 | ' maximum really roar mall'), 389 | 'bitcoin_keypair_1': { 390 | 'address': '1DS2vmsqTwtXp1DfmDHi55Aqc6w4LBUC9k', 391 | } 392 | } 393 | 394 | def setUp(self): 395 | self.wallet = SDWallet(self.reference['passphrase']) 396 | 397 | def tearDown(self): 398 | pass 399 | 400 | def test_bitcoin_keypairs(self): 401 | bitcoin_keypair_1 = self.wallet.keypair(1, BitcoinKeypair) 402 | self.assertEqual(self.reference['bitcoin_keypair_1']['address'], 403 | bitcoin_keypair_1.address()) 404 | self.assertEqual(bitcoin_keypair_1.passphrase(), 405 | self.reference['passphrase'] + ' bitcoin1') 406 | 407 | UNSPENTS_DICT = { 408 | '691d1645dc6f9431fe2ef414aaa88887efb5fed9354bf53ed01349595bf725ed': { 409 | 'script_hex': '76a9148eac3d867e1f92f47da40217c3cbd3d75d05701388ac', 410 | 'output_index': 1, 411 | 'transaction_hash': ("691d1645dc6f9431fe2ef414aaa88887efb5fed9354bf" 412 | "53ed01349595bf725ed"), 413 | 'confirmations': 6146, 414 | 'value': 55366270 415 | } 416 | } 417 | 418 | 419 | class ServicesGetUnspentsTest(unittest.TestCase): 420 | def setUp(self): 421 | self.address = '1E1PHi525xnHvSU4BqnV8aqwZwN8zoaHFv' 422 | self.total_unspent_value = 55366270 423 | self.unspents_dict = UNSPENTS_DICT 424 | 425 | def tearDown(self): 426 | pass 427 | 428 | def compare_unspents(self, unspents): 429 | for unspent in unspents: 430 | if unspent['transaction_hash'] in self.unspents_dict: 431 | ref_unspent = self.unspents_dict[unspent['transaction_hash']] 432 | self.assertEqual( 433 | unspent['transaction_hash'], 434 | ref_unspent['transaction_hash']) 435 | self.assertEqual( 436 | unspent['output_index'], ref_unspent['output_index']) 437 | self.assertEqual( 438 | unspent['script_hex'], ref_unspent['script_hex']) 439 | self.assertEqual(unspent['value'], ref_unspent['value']) 440 | else: 441 | continue 442 | 443 | def compare_total_value(self, unspents): 444 | total_value = sum([u['value'] for u in unspents]) 445 | self.assertEqual(total_value, self.total_unspent_value) 446 | 447 | def test_blockcypher_get_unspents(self): 448 | client = blockcypher.BlockcypherClient() 449 | unspents = blockcypher.get_unspents(self.address, client) 450 | self.compare_total_value(unspents) 451 | self.compare_unspents(unspents) 452 | 453 | """ 454 | def test_blockchain_info_get_unspents(self): 455 | client = blockchain_info.BlockchainInfoClient( 456 | BLOCKCHAIN_API_KEY) 457 | unspents = blockchain_info.get_unspents(self.address, client) 458 | self.compare_total_value(unspents) 459 | self.compare_unspents(unspents) 460 | """ 461 | 462 | """ 463 | def test_bitcoind_get_unspents(self): 464 | client = bitcoind_client 465 | unspents = client.get_unspents(self.address) 466 | self.compare_total_value(unspents) 467 | self.compare_unspents(unspents) 468 | """ 469 | 470 | 471 | class TransactionNetworkFunctionsTest(unittest.TestCase): 472 | def setUp(self): 473 | self.private_key = BITCOIN_PRIVATE_KEY 474 | 475 | def tearDown(self): 476 | pass 477 | 478 | def test_analyze_private_key(self): 479 | client = blockcypher.BlockcypherClient(BLOCKCYPHER_API_KEY) 480 | private_key_obj, from_address, inputs = analyze_private_key( 481 | self.private_key, client) 482 | self.assertTrue(isinstance(private_key_obj, BitcoinPrivateKey)) 483 | 484 | 485 | class ServicesSendTransactionTest(unittest.TestCase): 486 | def setUp(self): 487 | self.recipient_address = '1EEwLZVZMc2EhMf3LXDARbp4mA3qAwhBxu' 488 | self.private_key = BitcoinPrivateKey(BITCOIN_PRIVATE_KEY) 489 | self.send_amount = 1000 490 | 491 | self.blockcypher_client = blockcypher.BlockcypherClient( 492 | BLOCKCYPHER_API_KEY) 493 | self.blockchain_info_client = blockchain_info.BlockchainInfoClient( 494 | BLOCKCHAIN_API_KEY) 495 | self.bitcoind_client = bitcoind_client 496 | self.bitcoind = create_bitcoind_service_proxy( 497 | BITCOIND_RPC_USERNAME, BITCOIND_RPC_PASSWORD) 498 | 499 | self.signed_tx = make_send_to_address_tx( 500 | self.recipient_address, self.send_amount, 501 | self.private_key, self.blockcypher_client) 502 | 503 | def tearDown(self): 504 | pass 505 | 506 | def broadcast_with_client(self, tx, client): 507 | return broadcast_transaction(tx, client) 508 | 509 | def send_to_address_with_client(self, client): 510 | return send_to_address( 511 | self.recipient_address, 1000, self.private_key, client) 512 | 513 | """ 514 | def test_send_transaction_blockcypher_com(self): 515 | resp = self.broadcast_with_client( 516 | self.signed_tx, self.blockcypher_client) 517 | self.assertTrue(resp.get('success')) 518 | 519 | def test_send_transaction_blockchain_info(self): 520 | resp = self.broadcast_with_client( 521 | self.signed_tx, self.blockchain_info_client) 522 | self.assertTrue(resp.get('success')) 523 | 524 | def test_send_transaction_bitcoind(self): 525 | resp = self.broadcast_with_client(self.signed_tx, self.bitcoind) 526 | self.assertTrue(resp.get('success')) 527 | """ 528 | 529 | """ 530 | def test_build_transaction(self): 531 | signed_tx = make_send_to_address_tx( 532 | recipient_address, 1000, private_key, client) 533 | #print signed_tx 534 | self.assertTrue(True) 535 | """ 536 | 537 | 538 | class ServicesSendOpReturnTransactionTest(unittest.TestCase): 539 | def setUp(self): 540 | self.private_key = BITCOIN_PRIVATE_KEY_2 541 | 542 | self.blockcypher_client = blockcypher.BlockcypherClient( 543 | BLOCKCYPHER_API_KEY) 544 | self.blockchain_info_client = blockchain_info.BlockchainInfoClient( 545 | BLOCKCHAIN_API_KEY) 546 | self.bitcoind_client = bitcoind_client 547 | 548 | self.fee = 10000 549 | 550 | def tearDown(self): 551 | pass 552 | 553 | def run_op_return_tx_building(self, data, client, format='bin'): 554 | return make_op_return_tx( 555 | data, self.private_key, client, fee=self.fee, format=format) 556 | 557 | def run_tx_broadcasting(self, tx, client): 558 | return broadcast_transaction(tx, client) 559 | 560 | """ 561 | def run_data_embedding(self, data, client): 562 | resp = embed_data_in_blockchain(data, self.private_key, 563 | client, format='hex', fee=100000) 564 | return resp 565 | """ 566 | 567 | def test_hex_op_return_tx(self): 568 | data = '00' * 80 569 | signed_tx = self.run_op_return_tx_building( 570 | data, self.blockcypher_client, format='hex') 571 | resp = self.run_tx_broadcasting(signed_tx, self.blockcypher_client) 572 | self.assertTrue(resp.get('success')) 573 | 574 | """ 575 | def test_short_hex_op_return_tx(self): 576 | resp = embed_data('0'*2) 577 | self.assertTrue(resp.get('success')) 578 | 579 | def test_bin_op_return_tx(self): 580 | resp = embed_data("Hello, Blockchain!") 581 | self.assertTrue(resp.get('success')) 582 | """ 583 | 584 | 585 | class MerkleTest(unittest.TestCase): 586 | def setUp(self): 587 | self.hashes = [ 588 | 'f484b014c55a43b409a59de3177d49a88149b4473f9a7b81ea9e3535d4b7a301', 589 | '7b5636e9bc6ec910157e88702699bc7892675e8b489632c9166764341a4d4cfe', 590 | 'f8b02b8bf25cb6008e38eb5453a22c502f37e76375a86a0f0cfaa3c301aa1209' 591 | ] 592 | self.merkle_root = ("4f4c8c201e85a64a410cc7272c77f443d8b8df3289c67af" 593 | "9dab1e87d9e61985e") 594 | 595 | def tearDown(self): 596 | pass 597 | 598 | def test_merkle_tree(self): 599 | merkle_tree = MerkleTree(self.hashes) 600 | self.assertEqual(merkle_tree.root(), self.merkle_root) 601 | 602 | def test_calculate_merkle_root(self): 603 | merkle_root = calculate_merkle_root(self.hashes) 604 | self.assertEqual(merkle_root, self.merkle_root) 605 | 606 | 607 | class KeychainTest(unittest.TestCase): 608 | def setUp(self): 609 | self.private_keychains = { 610 | "root": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", 611 | "0H": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", 612 | "0H/1": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", 613 | "0H/1/2H": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", 614 | "0H/1/2H/2": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", 615 | "0H/1/2H/2/1000000000": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" 616 | } 617 | self.public_keychains = { 618 | "root": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", 619 | "0H": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", 620 | "0H/1": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", 621 | "0H/1/2H": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", 622 | "0H/1/2H/2": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", 623 | "0H/1/2H/2/1000000000": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" 624 | } 625 | self.root_private_keychain = PrivateKeychain(self.private_keychains["root"]) 626 | 627 | def tearDown(self): 628 | pass 629 | 630 | def test_root_private_to_public(self): 631 | public_keychain = self.root_private_keychain.public_keychain() 632 | self.assertEqual(str(public_keychain), str(self.public_keychains["root"])) 633 | 634 | def test_hardened_child_0H(self): 635 | private_keychain = self.root_private_keychain.hardened_child(0) 636 | self.assertEqual(str(private_keychain), str(self.private_keychains["0H"])) 637 | self.assertEqual(str(private_keychain.public_keychain()), str(self.public_keychains["0H"])) 638 | 639 | def test_unhardened_child_0H_1(self): 640 | private_keychain = self.root_private_keychain.hardened_child(0).child(1) 641 | self.assertEqual(str(private_keychain), str(self.private_keychains["0H/1"])) 642 | public_keychain = private_keychain.public_keychain() 643 | self.assertEqual(str(public_keychain), str(self.public_keychains["0H/1"])) 644 | public_keychain_2 = self.root_private_keychain.hardened_child(0).public_keychain().child(1) 645 | self.assertEqual(str(public_keychain), str(public_keychain_2)) 646 | 647 | def test_5_step_derivation(self): 648 | private_keychain = self.root_private_keychain.hardened_child(0).child(1).hardened_child(2).child(2).child(1000000000) 649 | self.assertEqual(str(private_keychain), str(self.private_keychains["0H/1/2H/2/1000000000"])) 650 | public_keychain = private_keychain.public_keychain() 651 | self.assertEqual(str(public_keychain), str(self.public_keychains["0H/1/2H/2/1000000000"])) 652 | 653 | 654 | def test_main(): 655 | def altcoin_test_generator(coin_name): 656 | def generate(self): 657 | keypair = get_class(coin_name.title() + 'Keypair') 658 | private_key = self.reference['hex_private_key'] 659 | keypair = keypair.from_private_key(private_key) 660 | 661 | wif_private_key = keypair.wif_pk() 662 | reference_wif_private_key = self.reference[(coin_name, 'wif')] 663 | self.assertEqual(wif_private_key, reference_wif_private_key) 664 | 665 | address = keypair.address() 666 | reference_address = self.reference[(coin_name, 'address')] 667 | self.assertEqual(address, reference_address) 668 | 669 | return generate 670 | 671 | # generate altcoin tests 672 | for coin_name in AltcoinKeypairTest.coin_names: 673 | test_name = 'test_%s' % coin_name 674 | test = altcoin_test_generator(coin_name) 675 | setattr(AltcoinKeypairTest, test_name, test) 676 | 677 | test_support.run_unittest( 678 | MerkleTest, 679 | BitcoinUncompressedPublicKeyTest, 680 | BitcoinCompressedPublicKeyTest, 681 | BitcoinPublicKeyCreationTest, 682 | BitcoinPrivateKeyTest, 683 | BitcoinPrivateKeyToPublicKeyTest, 684 | BitcoinKeypairTest, 685 | # AltcoinKeypairTest, 686 | BitcoinBrainWalletKeypairTest, 687 | BitcoinKeypairFromWIFTest, 688 | RandomBitcoinKeypairsTest, 689 | BitcoinB58CheckTest, 690 | BitcoinFormatCheckTest, 691 | SequentialWalletTest, 692 | KeychainTest 693 | ) 694 | 695 | 696 | def test_transactions(): 697 | test_support.run_unittest( 698 | ServicesGetUnspentsTest, 699 | TransactionNetworkFunctionsTest, 700 | ServicesSendTransactionTest, 701 | ServicesSendOpReturnTransactionTest 702 | ) 703 | 704 | if __name__ == '__main__': 705 | test_main() 706 | # test_transactions() 707 | --------------------------------------------------------------------------------