├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── crypto_scripts ├── __init__.py ├── broadcast.py ├── convert_private_key.py ├── create_private_key.py ├── cryptosend.py ├── explorer.py ├── get_block_sizes.py ├── subscribe.py └── view_private_key_addresses.py ├── cryptos ├── __init__.py ├── blocks.py ├── cashaddr.py ├── coins │ ├── __init__.py │ ├── base.py │ ├── bitcoin.py │ ├── bitcoin_cash.py │ ├── dash.py │ ├── dogecoin.py │ └── litecoin.py ├── coins_async │ ├── __init__.py │ ├── base.py │ ├── bitcoin.py │ ├── bitcoin_cash.py │ ├── dash.py │ ├── dogecoin.py │ └── litecoin.py ├── composite.py ├── constants.py ├── deterministic.py ├── electrumx_client │ ├── __init__.py │ ├── client.py │ ├── servers │ │ ├── bitcoin.json │ │ ├── bitcoin_cash.json │ │ ├── bitcoin_cash_testnet.json │ │ ├── bitcoin_testnet.json │ │ ├── dash.json │ │ ├── dash_testnet.json │ │ ├── doge.json │ │ ├── doge_testnet.json │ │ ├── litecoin.json │ │ ├── litecoin_testnet.json │ │ └── testing.json │ └── types.py ├── english.txt ├── explorers │ ├── __init__.py │ ├── base_insight.py │ ├── bitpay.py │ ├── blockchain.py │ ├── blockcypher.py │ ├── blockdozer.py │ ├── btg_explorer.py │ ├── dash_siampm.py │ ├── sochain.py │ └── utils.py ├── keystore.py ├── main.py ├── mnemonic.py ├── opcodes.py ├── py3specials.py ├── ripemd.py ├── script_utils.py ├── segwit_addr.py ├── stealth.py ├── testing │ ├── __init__.py │ ├── testcases.py │ └── testcases_async.py ├── transaction.py ├── types.py ├── utils.py ├── wallet.py └── wallet_utils.py ├── cryptotool ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── electrum_subscribe_mock_server.py ├── test_coins ├── __init__.py └── test_bitcoin_testnet.py ├── test_coins_async ├── __init__.py ├── test_bitcoin.py ├── test_bitcoin_cash.py ├── test_bitcoin_cash_testnet.py ├── test_bitcoin_testnet.py ├── test_dash.py ├── test_dash_testnet.py ├── test_dogecoin.py ├── test_dogecoin_testnet.py ├── test_litecoin.py └── test_litecoin_testnet.py ├── test_electrum_client.py ├── test_general.py ├── test_stealth.py └── test_wallet.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This code is public domain. Everyone has the right to do whatever they want 2 | with it for any purpose. 3 | 4 | In case your jurisdiction does not consider the above disclaimer valid or 5 | enforceable, here's an MIT license for you: 6 | 7 | The MIT License (MIT) 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include cryptos/english.txt 2 | include cryptos/electrumx_client/servers/*.json 3 | include LICENSE 4 | include README.md -------------------------------------------------------------------------------- /crypto_scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primal100/pybitcointools/e7c96bfe1f4be08a9f3c540e598a73dc20ca2462/crypto_scripts/__init__.py -------------------------------------------------------------------------------- /crypto_scripts/broadcast.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | from cryptos.script_utils import get_coin, coin_list 4 | from cryptos.transaction import deserialize 5 | from pprint import pprint 6 | 7 | 8 | async def broadcast_tx(tx: str, coin_symbol: str, testnet: bool): 9 | c = get_coin(coin_symbol, testnet=testnet) 10 | try: 11 | print('Broadcasting transaction:') 12 | pprint(deserialize(tx)) 13 | await c.pushtx(tx) 14 | finally: 15 | await c.close() 16 | 17 | 18 | def main(): 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument("tx", help="Transaction Hex", type=str) 21 | parser.add_argument("-x", "--coin", help="Coin", choices=coin_list, default="btc") 22 | parser.add_argument("-t", "--testnet", help="For testnet", action="store_true") 23 | args = parser.parse_args() 24 | asyncio.run(broadcast_tx(args.tx, coin_symbol=args.coin, testnet=args.testnet)) 25 | 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /crypto_scripts/convert_private_key.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from cryptos.coins_async.base import BaseCoin 3 | from cryptos.main import privtopub, compress, decompress 4 | from cryptos.script_utils import coin_list, get_coin 5 | 6 | 7 | def main(): 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("priv", help="Private Key") 10 | parser.add_argument("output_format", help="Output format", choices=['decimal', 'bin', 'bin_compressed', 'hex', 11 | 'hex_compressed', 'wif', 'wif_compressed']) 12 | parser.add_argument("-s", "--script_type", help="Output format", 13 | choices=BaseCoin.wif_script_types.keys(), default="p2pkh") 14 | parser.add_argument("-x", "--coin", help="Coin", choices=coin_list, default="btc") 15 | parser.add_argument("-t", "--testnet", help="For testnet", action="store_true") 16 | 17 | args = parser.parse_args() 18 | 19 | coin = get_coin(args.coin, testnet=args.testnet) 20 | priv = args.priv 21 | script_type = args.script_type 22 | output_format = args.output_format 23 | encoded_priv_key = coin.encode_privkey(priv, output_format, script_type=script_type) 24 | output_format_str = output_format.replace("_", " ") 25 | print(f'Private key {output_format_str} format: {encoded_priv_key}') 26 | public_key = privtopub(encoded_priv_key) 27 | print(f'Public key: {public_key}') 28 | if script_type == "p2pkh": 29 | address = coin.pubtoaddr(public_key) 30 | print(f'P2PKH Address: {address}') 31 | elif script_type == "p2wpkh" and coin.segwit_supported: 32 | native_segwit_address = coin.pubtosegwitaddress(public_key) 33 | print(f'P2WPKH Native Segwit address: {native_segwit_address}') 34 | elif script_type == "p2wpkh-p2sh" and coin.segwit_supported: 35 | p2pkhw_p2sh = coin.pubtop2wpkh_p2sh(public_key) 36 | print(f'P2PKHW_P2SH Address: {p2pkhw_p2sh}') 37 | 38 | 39 | if __name__ == "__main__": 40 | main() 41 | -------------------------------------------------------------------------------- /crypto_scripts/create_private_key.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from cryptos.main import generate_private_key, encode_privkey 3 | from cryptos.script_utils import get_coin, coin_list 4 | 5 | 6 | def main(): 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("-x", "--coin", help="Coin", choices=coin_list, default="btc") 9 | parser.add_argument("-t", "--testnet", help="For testnet", action="store_true") 10 | args = parser.parse_args() 11 | 12 | coin = get_coin(args.coin, testnet=args.testnet) 13 | 14 | private_key = generate_private_key() 15 | private_key_compressed = encode_privkey(private_key, formt="hex_compressed") 16 | print(f'Private key: {private_key_compressed}') 17 | private_key_p2pkh = coin.encode_privkey(private_key, formt="wif_compressed", script_type="p2pkh") 18 | print(f'WIF P2PKH: {private_key_p2pkh}') 19 | print(f'P2PKH Address: {coin.privtoaddr(private_key_p2pkh)}') 20 | if coin.segwit_supported: 21 | private_key_p2wpkh_p2sh = coin.encode_privkey(private_key, formt="wif_compressed", script_type="p2wpkh-p2sh") 22 | print(f'WIF P2WPKH-P2SH: {private_key_p2wpkh_p2sh}') 23 | print(f'P2WPKH-P2SH Segwit Address: {coin.privtop2wpkh_p2sh(private_key_p2wpkh_p2sh)}') 24 | private_key_p2wpkh = coin.encode_privkey(private_key, formt="wif_compressed", script_type="p2wpkh") 25 | print(f'WIF Native Segwit P2WPKH: {private_key_p2wpkh}') 26 | print(f'Native Segwit P2WPKH Address: {coin.privtosegwitaddress(private_key_p2wpkh)}') 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /crypto_scripts/cryptosend.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import aiorpcx 3 | import asyncio 4 | import sys 5 | from getpass import getpass 6 | from cryptos.main import privtopub, compress 7 | from cryptos.transaction import serialize 8 | from typing import Callable, Any, Optional 9 | from cryptos.script_utils import get_coin, coin_list 10 | 11 | 12 | async def run_in_executor(func: Callable, *args) -> Any: 13 | return await asyncio.get_running_loop().run_in_executor(None, func, *args) 14 | 15 | 16 | async def get_confirmation() -> bool: 17 | result = await run_in_executor(input, "Send this transaction? (Y/N): ") 18 | return any(r == result.lower() for r in ("y", "yes")) 19 | 20 | 21 | async def send(coin: str, testnet: bool, addr: str, to: str, amount: int, 22 | fee: float = None, change_addr: Optional[str] = None, privkey: Optional[str] = None): 23 | coin = get_coin(coin, testnet=testnet) 24 | tx = await coin.preparetx(addr, to, amount, fee=fee, change_addr=change_addr) 25 | print(serialize(tx)) 26 | print(tx) 27 | privkey = privkey or await run_in_executor(getpass, "Enter private key to sign this transaction") 28 | if coin.is_native_segwit(addr): 29 | expected_addr = coin.privtosegwitaddress(privkey) 30 | elif coin.is_p2sh(addr): 31 | expected_addr = coin.privtop2wpkh_p2sh(privkey) 32 | elif coin.is_p2pkh(addr): 33 | expected_addr = coin.privtoaddr(privkey) 34 | elif len(addr) == 66: 35 | expected_addr = compress(privtopub(privkey)) 36 | else: 37 | expected_addr = privtopub(privkey) 38 | try: 39 | assert expected_addr == addr 40 | except AssertionError: 41 | raise AssertionError(f'Private key is for address {expected_addr}, not {addr}') 42 | tx = coin.signall(tx, privkey) 43 | print(serialize(tx)) 44 | print(tx) 45 | if args.yes or await get_confirmation(): 46 | try: 47 | result = await coin.pushtx(tx) 48 | print(f'Transaction broadcasted successfully {result}') 49 | except (aiorpcx.jsonrpc.RPCError, aiorpcx.jsonrpc.ProtocolError) as e: 50 | sys.stderr.write(e.message.upper()) 51 | else: 52 | print('Transaction was cancelled') 53 | 54 | 55 | def main(): 56 | parser = argparse.ArgumentParser() 57 | parser.add_argument("addr", help="Send from this address") 58 | parser.add_argument("to", help="Send to this address") 59 | parser.add_argument("amount", help="Amount to send", type=int) 60 | parser.add_argument("-c", "--change", help="Address for change, otherwise from address") 61 | parser.add_argument("-f", "--fee", help="Fee", type=float) 62 | parser.add_argument("-x", "--coin", help="Coin", choices=coin_list, default="btc") 63 | parser.add_argument("-t", "--testnet", help="For testnet", action="store_true") 64 | parser.add_argument("-p", "--privkey", help="Private Key") 65 | parser.add_argument("-y", "--yes", help="Confirm", action="store_true") 66 | args = parser.parse_args() 67 | asyncio.run(send(args.coin, args.testnet, args.addr, args.to, args.amount, 68 | args.fee, args.change, args.privkey)) 69 | 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /crypto_scripts/explorer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import binascii 3 | import datetime 4 | import asyncio 5 | import sys 6 | from cryptos.coins_async import BaseCoin 7 | from cryptos.constants import SATOSHI_PER_BTC 8 | from cryptos.main import safe_hexlify, is_pubkey 9 | from cryptos.transaction import json_changebase, deserialize_script 10 | from pprint import pprint 11 | from typing import Callable, Any, Union, Optional, Dict, Tuple 12 | from cryptos.script_utils import get_coin, coin_list 13 | 14 | 15 | async def run_in_executor(func: Callable, *args) -> Any: 16 | return await asyncio.get_running_loop().run_in_executor(None, func, *args) 17 | 18 | 19 | def is_block_height(coin: BaseCoin, obj_id: Union[str, int]) -> Optional[int]: 20 | try: 21 | return int(obj_id) 22 | except ValueError: 23 | return None 24 | 25 | 26 | def is_tx(coin: BaseCoin, obj_id: str) -> Optional[str]: 27 | try: 28 | tx_id = binascii.unhexlify(obj_id) 29 | if len(tx_id) == coin.txid_bytes_len: 30 | return obj_id 31 | return None 32 | except binascii.Error: 33 | return None 34 | 35 | 36 | def is_address(coin: BaseCoin, obj_id: str) -> Optional[str]: 37 | return obj_id if coin.is_address(obj_id) else None 38 | 39 | 40 | def script_pubkey_is_pubkey(scriptPubKey: Dict[str, Any], pubkey: str) -> bool: 41 | return scriptPubKey['type'] == "pubkey" and deserialize_script(scriptPubKey['hex'])[0] == pubkey 42 | 43 | 44 | def output_belongs_to_address(coin: BaseCoin, out: Dict[str, Any], address: str) -> bool: 45 | address_variations = coin.get_address_variations(address) 46 | scriptPubKey = out['scriptPubKey'] 47 | return any( 48 | scriptPubKey.get('address') == address or script_pubkey_is_pubkey(scriptPubKey, address) or address in scriptPubKey.get('addresses', []) 49 | for address in address_variations 50 | ) 51 | 52 | 53 | def script_sig_pubkey(scriptSig: str) -> Optional[str]: 54 | try: 55 | return deserialize_script(scriptSig)[1] 56 | except IndexError: 57 | return None 58 | 59 | 60 | def script_sig_script(scriptSig: str) -> Optional[str]: 61 | try: 62 | return deserialize_script(scriptSig)[-1] 63 | except IndexError: 64 | return None 65 | 66 | 67 | async def input_belongs_to_address(coin: BaseCoin, inp: Dict[str, Any], address: str, received: Dict[str, int]) -> bool: 68 | if witness := inp.get('txinwitness'): 69 | pubkey_or_script = witness[-1] 70 | if coin.is_p2wsh(address): 71 | return coin.p2sh_segwit_addr(pubkey_or_script) == address 72 | elif coin.is_segwit_or_p2sh(address): 73 | if is_pubkey(pubkey_or_script): 74 | return any(addr == address for addr in ( # P2W 75 | coin.pubtosegwitaddress(pubkey_or_script), 76 | coin.pubtop2wpkh_p2sh(pubkey_or_script), 77 | )) 78 | return False 79 | elif scriptSig := inp.get('scriptSig', {}).get('hex'): 80 | if scriptSig.startswith('00'): 81 | script = script_sig_script(scriptSig) 82 | if coin.is_p2sh(address): 83 | return coin.p2sh_scriptaddr(script) == address 84 | if coin.is_cash_address(address): 85 | return coin.p2sh_cash_addr(script) == address 86 | return False 87 | elif pubkey := script_sig_pubkey(scriptSig): 88 | return coin.pub_is_for_p2pkh_addr(pubkey, address) # P2PKH 89 | elif is_pubkey(address): 90 | txid = inp['txid'] # P2PK 91 | outno = inp['vout'] 92 | outpoint = f'{txid}:{outno}' 93 | if outpoint in received: 94 | return True 95 | prev = await coin.get_verbose_tx(txid) 96 | out = prev['vout'][outno] 97 | script_pub_key = out['scriptPubKey'] 98 | if script_pubkey_is_pubkey(script_pub_key, address): 99 | received[outpoint] = out['value'] 100 | return True 101 | return False 102 | 103 | 104 | async def print_item(obj_id: str, coin_symbol: str = "btc", testnet: bool = False) -> None: 105 | coin = get_coin(coin_symbol, testnet=testnet) 106 | try: 107 | if tx_id := is_tx(coin, obj_id): 108 | tx = await coin.get_verbose_tx(tx_id) 109 | pprint(tx) 110 | elif address := is_address(coin, obj_id): 111 | history, unspent, balances = await asyncio.gather(coin.history(address), 112 | coin.unspent(address), 113 | coin.get_balance(address)) 114 | print('HISTORY:') 115 | if len(history) > 20: 116 | print('Last 20 transactions only') 117 | verbose_history = await asyncio.gather(*[coin.get_verbose_tx(h['tx_hash']) for h in history[-21:-1]]) 118 | received = {} 119 | coinbase = False 120 | for h in verbose_history: 121 | if _time := h.get('time'): 122 | timestamp = datetime.datetime.fromtimestamp(_time) 123 | else: 124 | timestamp = '' 125 | spent_value = 0 126 | for inp in h['vin']: 127 | coinbase = inp.get('coinbase') 128 | if not coinbase and await input_belongs_to_address(coin, inp, address, received): 129 | in_txid = inp["txid"] 130 | outno = inp["vout"] 131 | try: 132 | outpoint = f'{in_txid}:{inp["vout"]}' 133 | value = received[outpoint] 134 | spent_value += value 135 | except KeyError: 136 | tx = await coin.get_verbose_tx(in_txid) 137 | for out in tx['vout']: 138 | if output_belongs_to_address(coin, out, address): 139 | value = out['value'] 140 | current_outno = out["n"] 141 | outpoint = f'{in_txid}:{current_outno}' 142 | received[outpoint] = value 143 | if outno == current_outno: 144 | spent_value += value 145 | out_value = 0 146 | for out in h['vout']: 147 | if output_belongs_to_address(coin, out, address): 148 | value = out['value'] 149 | outpoint = f'{h["txid"]}:{out["n"]}' 150 | received[outpoint] = value 151 | out_value += value 152 | total = int((out_value - spent_value) * SATOSHI_PER_BTC) 153 | if total > 0: 154 | desc = f"Received {total}{' COINBASE' if coinbase else ''}" 155 | else: 156 | desc = f"Spent { 0 - total }" 157 | print(f'{timestamp}{" " if timestamp else ""}{h["txid"]} {desc}') 158 | print(f'\nUNSPENTS') 159 | for u in unspent: 160 | u['confirmations'] = await coin.confirmations(u['height']) 161 | print(' '.join([f"{k}: {v}" for k, v in u.items()])) 162 | print('\n') 163 | for k, v in balances.items(): 164 | print(f'{k.capitalize()} Balance: {v}') 165 | len_history = len(history) 166 | len_unspent = len(unspent) 167 | plural_history = '' if len_history == 1 else 's' 168 | plural_unspent = '' if len_unspent == 1 else 's' 169 | print(f'\nThis address was found in {len_history} transaction{plural_history} and has {len_unspent} unspent{plural_unspent}.') 170 | elif block_height := is_block_height(coin, obj_id): 171 | header = await coin.block_header(block_height) 172 | header = json_changebase(header, lambda x: safe_hexlify(x)) 173 | pprint(header) 174 | else: 175 | coin_other_net = get_coin(coin_symbol, testnet=not testnet) 176 | try: 177 | if coin_other_net.is_address(obj_id): 178 | if testnet: 179 | message = f"{obj_id} is a mainnet address. Try again without --testnet" 180 | else: 181 | message = f"{obj_id} is a testnet address. Try again with --testnet" 182 | else: 183 | message = f"{obj_id} is not a block, transaction or address for {coin.display_name}" 184 | print(message, file=sys.stderr) 185 | finally: 186 | await coin_other_net.close() 187 | sys.exit(1) 188 | finally: 189 | await coin.close() 190 | await asyncio.sleep(1) 191 | 192 | 193 | def main(): 194 | parser = argparse.ArgumentParser() 195 | parser.add_argument("obj", help="Object to search for, either a transaction ID, block height or address") 196 | parser.add_argument("-x", "--coin", help="Coin", choices=coin_list, default="btc") 197 | parser.add_argument("-t", "--testnet", help="For testnet", action="store_true") 198 | args = parser.parse_args() 199 | asyncio.run(print_item(args.obj, coin_symbol=args.coin, testnet=args.testnet)) 200 | 201 | 202 | if __name__ == "__main__": 203 | main() 204 | -------------------------------------------------------------------------------- /crypto_scripts/get_block_sizes.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | from cryptos.script_utils import get_coin, coin_list 4 | 5 | 6 | async def print_block_bits(start: int, end: int, coin_symbol: str, testnet: bool): 7 | c = get_coin(coin_symbol, testnet=testnet) 8 | try: 9 | for i in range(start, end): 10 | block = await c.block_header(i) 11 | bits = block['bits'] 12 | text = f'Block {i}: {bits}' 13 | if bits != 486604799: 14 | text = f'\033[1;3m{text}\033[0m' 15 | print(text) 16 | finally: 17 | await c.close() 18 | 19 | 20 | def main(): 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument("start", help="First block height", type=int) 23 | parser.add_argument("end", help="Final block height", type=int) 24 | parser.add_argument("-x", "--coin", help="Coin", choices=coin_list, default="btc") 25 | parser.add_argument("-t", "--testnet", help="For testnet", action="store_true") 26 | args = parser.parse_args() 27 | asyncio.run(print_block_bits(args.start, args.end, coin_symbol=args.coin, testnet=args.testnet)) 28 | 29 | 30 | if __name__ == "__main__": 31 | main() 32 | -------------------------------------------------------------------------------- /crypto_scripts/subscribe.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import argparse 3 | from cryptos.types import BlockHeader, ElectrumXTx 4 | from functools import partial 5 | import logging 6 | import sys 7 | from typing import List, Union, Tuple 8 | from cryptos.coins_async.base import BaseCoin 9 | from cryptos.script_utils import get_coin, coin_list 10 | 11 | 12 | logger = logging.getLogger("subscriptions") 13 | logger.setLevel(logging.INFO) 14 | handler = logging.StreamHandler(stream=sys.stdout) 15 | formatter = logging.Formatter(fmt="%(asctime)s - %(message)s") 16 | handler.setFormatter(formatter) 17 | logger.addHandler(handler) 18 | 19 | 20 | async def log_unspents(coin: BaseCoin, address: str, unspent: List[ElectrumXTx]) -> None: 21 | for u in unspent: 22 | u['confirmations'] = await coin.confirmations(u['height']) 23 | logger.info("%s - Unspents: %s", address, unspent) 24 | 25 | 26 | async def print_balances(coin: BaseCoin, addr: str) -> None: 27 | balances, merkle_proven, unspents = await asyncio.gather( 28 | coin.get_balance(addr), coin.balance_merkle_proven(addr), coin.unspent(addr)) 29 | balances['merkle_proven'] = merkle_proven 30 | logger.info("%s - %s", addr, balances) 31 | await log_unspents(coin, addr, unspents) 32 | 33 | 34 | async def on_new_block(start_block: Tuple[Union[int, str, BlockHeader]], addresses: List[str], coin: BaseCoin, 35 | height: int, hex_header: str, header: BlockHeader) -> None: 36 | if start_block[0] != height: 37 | logger.info("New Block at height: %s: %s", height, header) 38 | else: 39 | start_block += (height, hex_header, header) 40 | logger.info("Current Block is at height: %s: %s", height, header) 41 | await asyncio.wait([asyncio.create_task(print_balances(coin, addr)) for addr in addresses]) 42 | 43 | 44 | async def on_address_change(coin: BaseCoin, address: str, new_txs: List[ElectrumXTx], newly_confirmed: List[ElectrumXTx], 45 | history: List[ElectrumXTx], unspent: List[ElectrumXTx], 46 | confirmed_balance: int, unconfirmed_balance: int, proven: int) -> None: 47 | balances = f'Confirmed: {confirmed_balance} Unconfirmed: {unconfirmed_balance} Proven Balance: {proven}' 48 | if new_txs or newly_confirmed: 49 | logger.info("%s - Changed: %s", address, balances) 50 | else: 51 | logger.info("%s - Current status is: %s", address, balances) 52 | for tx in new_txs: 53 | logger.info("%s - New TX: %s", address, tx) 54 | for tx in newly_confirmed: 55 | logger.info("%s -TX has been confirmed: %s", address, tx) 56 | await log_unspents(coin, address, unspent) 57 | 58 | 59 | async def subscribe_to_addresses(addresses: List[str], coin_symbol: str, testnet: bool): 60 | coin = get_coin(coin_symbol, testnet=testnet) 61 | initial_block = await coin.block 62 | await coin.subscribe_to_block_headers(partial(on_new_block, initial_block, addresses, coin)) 63 | await asyncio.wait([asyncio.create_task( 64 | coin.subscribe_to_address_transactions(partial(on_address_change, coin), a)) for a in addresses] 65 | ) 66 | fut = asyncio.Future() 67 | await fut 68 | 69 | 70 | def main(): 71 | parser = argparse.ArgumentParser() 72 | parser.add_argument("addresses", help="Address to subscribe to", nargs="*") 73 | parser.add_argument("-x", "--coin", help="Coin", choices=coin_list, default="btc") 74 | parser.add_argument("-t", "--testnet", help="For testnet", action="store_true") 75 | args = parser.parse_args() 76 | asyncio.run(subscribe_to_addresses(args.addresses, coin_symbol=args.coin, testnet=args.testnet)) 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /crypto_scripts/view_private_key_addresses.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from cryptos.main import privtopub, encode_privkey, compress, decompress 3 | from cryptos.script_utils import coin_list, get_coin 4 | 5 | 6 | def main(): 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("priv", help="Private Key") 9 | parser.add_argument("-x", "--coin", help="Coin", choices=coin_list, default="btc") 10 | parser.add_argument("-t", "--testnet", help="For testnet", action="store_true") 11 | 12 | args = parser.parse_args() 13 | 14 | coin = get_coin(args.coin, testnet=args.testnet) 15 | priv = args.priv 16 | print(f'Private key: {encode_privkey(priv, formt="hex")}') 17 | priv_compressed = encode_privkey(priv, formt="hex_compressed") 18 | print(f'Private key compressed: {priv_compressed}') 19 | public_key = decompress(privtopub(priv)) 20 | print(f'Public key: {public_key}') 21 | compressed_public_key = compress(public_key) 22 | print(f'Public key Compressed: {compressed_public_key}') 23 | p2pkh_wif = coin.encode_privkey(priv, "wif") 24 | p2pkh_wif_compressed = coin.encode_privkey(priv, "wif_compressed") 25 | print(f'P2PKH wif: {p2pkh_wif}') 26 | print(f'P2PKH wif compressed: {p2pkh_wif_compressed}') 27 | assert encode_privkey(p2pkh_wif, formt="hex_compressed") == priv_compressed 28 | p2pkh_address = coin.privtop2pkh(p2pkh_wif) 29 | p2pkh_compressed_address = coin.privtop2pkh(p2pkh_wif_compressed) 30 | assert p2pkh_address == coin.pubtoaddr(public_key) 31 | assert p2pkh_compressed_address == coin.pubtoaddr(compressed_public_key) 32 | assert privtopub(p2pkh_wif) == public_key 33 | assert privtopub(p2pkh_wif_compressed) == compressed_public_key 34 | assert p2pkh_address == coin.privtoaddr(p2pkh_wif) 35 | assert p2pkh_compressed_address == coin.privtoaddr(p2pkh_wif_compressed) 36 | print(f'P2PKH Address: {p2pkh_address}') 37 | print(f'P2PKH Compressed Public Key Address: {p2pkh_compressed_address}') 38 | if coin.segwit_supported: 39 | p2wpkh_p2sh_wif_compressed = coin.encode_privkey(priv, "wif_compressed", script_type="p2wpkh-p2sh") 40 | print(f'P2WPKH-P2SH wif compressed: {p2wpkh_p2sh_wif_compressed}') 41 | p2wpkh_p2sh_compressed_address = coin.privtop2wpkh_p2sh(p2wpkh_p2sh_wif_compressed) 42 | p2wpkh_p2sh_address2 = coin.pubtop2wpkh_p2sh(public_key) 43 | p2wpkh_p2sh_compressed_address2 = coin.pubtop2wpkh_p2sh(compressed_public_key) 44 | 45 | assert privtopub(p2wpkh_p2sh_wif_compressed) == compressed_public_key 46 | 47 | assert p2wpkh_p2sh_compressed_address == p2wpkh_p2sh_address2 == p2wpkh_p2sh_compressed_address2 == coin.privtoaddr(p2wpkh_p2sh_wif_compressed) 48 | 49 | p2wpkh_wif = coin.encode_privkey(priv, "wif", script_type="p2wpkh") 50 | p2wpkh_wif_compressed = coin.encode_privkey(priv, "wif_compressed", script_type="p2wpkh") 51 | print(f'P2WPKH Native Segwit wif compressed: {p2wpkh_wif_compressed}') 52 | native_segwit_address = coin.privtosegwitaddress(p2wpkh_wif) 53 | native_segwit_compressed_address = coin.privtosegwitaddress(p2wpkh_wif_compressed) 54 | 55 | print(f'P2WPKH Native Segwit Address: {native_segwit_address}') 56 | 57 | assert native_segwit_address == native_segwit_compressed_address 58 | 59 | native_segwit_address2 = coin.pubtosegwitaddress(public_key) 60 | native_segwit_compressed_address2 = coin.pubtosegwitaddress(compressed_public_key) 61 | 62 | assert native_segwit_address2 == native_segwit_compressed_address2 63 | 64 | assert native_segwit_address == native_segwit_address2 65 | 66 | assert native_segwit_address == coin.privtoaddr(p2wpkh_wif) 67 | assert native_segwit_address == coin.privtoaddr(p2wpkh_wif_compressed) 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /cryptos/__init__.py: -------------------------------------------------------------------------------- 1 | from .blocks import * 2 | from .composite import * 3 | from .deterministic import * 4 | from .main import * 5 | from .mnemonic import * 6 | from .py3specials import * 7 | from .stealth import * 8 | from .transaction import * 9 | from .coins import * 10 | from .keystore import * 11 | from .wallet import * 12 | -------------------------------------------------------------------------------- /cryptos/blocks.py: -------------------------------------------------------------------------------- 1 | from .main import * 2 | from .transaction import deserialize 3 | from .types import BlockHeader, MerkleProof 4 | from binascii import hexlify 5 | 6 | 7 | def serialize_header(inp: BlockHeader) -> bytes: 8 | o = encode(inp['version'], 256, 4)[::-1] + \ 9 | inp['prevhash'].decode('hex')[::-1] + \ 10 | inp['merkle_root'].decode('hex')[::-1] + \ 11 | encode(inp['timestamp'], 256, 4)[::-1] + \ 12 | encode(inp['bits'], 256, 4)[::-1] + \ 13 | encode(inp['nonce'], 256, 4)[::-1] 14 | h = bin_sha256(bin_sha256(o))[::-1].encode('hex') 15 | assert h == inp['hash'], (sha256(o), inp['hash']) 16 | return o.encode('hex') 17 | 18 | 19 | def deserialize_header(inp: bytes) -> BlockHeader: 20 | return { 21 | "version": decode(inp[:4][::-1], 256), 22 | "prevhash": hexlify(inp[4:36][::-1]), 23 | "merkle_root": hexlify(inp[36:68][::-1]), 24 | "timestamp": decode(inp[68:72][::-1], 256), 25 | "bits": decode(inp[72:76][::-1], 256), 26 | "nonce": decode(inp[76:80][::-1], 256), 27 | "hash": bin_sha256(bin_sha256(inp))[::-1] 28 | } 29 | 30 | 31 | def mk_merkle_proof(merkle_root: bytes, hashes: List[str], index: int) -> MerkleProof: 32 | """ 33 | This function requires all transaction hashes in a block to be provided 34 | """ 35 | tx_hash = hashes[index] 36 | try: 37 | nodes = [safe_from_hex(h)[::-1] for h in hashes] 38 | if len(nodes) % 2 and len(nodes) > 2: 39 | nodes.append(nodes[-1]) 40 | layers = [nodes] 41 | while len(nodes) > 1: 42 | newnodes = [] 43 | for i in range(0, len(nodes) - 1, 2): 44 | newnodes.append(bin_sha256(bin_sha256(nodes[i] + nodes[i+1]))) 45 | if len(newnodes) % 2 and len(newnodes) > 2: 46 | newnodes.append(newnodes[-1]) 47 | nodes = newnodes 48 | layers.append(nodes) 49 | # Sanity check, make sure merkle root is valid 50 | assert bytes_to_hex_string(nodes[0][::-1]) == merkle_root 51 | merkle_siblings = \ 52 | [layers[i][(index >> i) ^ 1] for i in range(len(layers)-1)] 53 | return { 54 | "tx_hash": tx_hash, 55 | "siblings": [bytes_to_hex_string(x[::-1]) for x in merkle_siblings], 56 | 'proven': True 57 | } 58 | except: 59 | return { 60 | "tx_hash": tx_hash, 61 | "siblings": [], 62 | 'proven': False 63 | } 64 | 65 | 66 | def verify_merkle_proof(tx_hash: str, merkle_root: bytes, hashes: List[str], index: int) -> MerkleProof: 67 | h = safe_from_hex(tx_hash)[::-1] 68 | nodes = [safe_from_hex(h)[::-1] for h in hashes] 69 | proven = True 70 | for item in nodes: 71 | inner_node = (item + h) if (index & 1) else (h + item) 72 | try: 73 | deserialize(inner_node) 74 | except Exception as e: 75 | pass 76 | else: 77 | proven = False # If a node serializes as a transaction, could be an attack 78 | break 79 | h = bin_sha256(bin_sha256(inner_node)) 80 | index >>= 1 81 | if index != 0: 82 | proven = False 83 | h = bytes_to_hex_string(h[::-1]).encode() 84 | return { 85 | "tx_hash": tx_hash, 86 | 'proven': proven and h == merkle_root 87 | } 88 | -------------------------------------------------------------------------------- /cryptos/cashaddr.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Pieter Wuille 2 | # Copyright (c) 2017 Shammah Chancellor, Neil Booth 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | _CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 23 | 24 | def _polymod(values): 25 | """Internal function that computes the cashaddr checksum.""" 26 | c = 1 27 | for d in values: 28 | c0 = c >> 35 29 | c = ((c & 0x07ffffffff) << 5) ^ d 30 | if (c0 & 0x01): 31 | c ^= 0x98f2bc8e61 32 | if (c0 & 0x02): 33 | c ^= 0x79b76d99e2 34 | if (c0 & 0x04): 35 | c ^= 0xf33e5fb3c4 36 | if (c0 & 0x08): 37 | c ^= 0xae2eabe2a8 38 | if (c0 & 0x10): 39 | c ^= 0x1e4f43e470 40 | retval= c ^ 1 41 | return retval 42 | 43 | def _prefix_expand(prefix): 44 | """Expand the prefix into values for checksum computation.""" 45 | retval = bytearray(ord(x) & 0x1f for x in prefix) 46 | # Append null separator 47 | retval.append(0) 48 | return retval 49 | 50 | def _create_checksum(prefix, data): 51 | """Compute the checksum values given prefix and data.""" 52 | values = _prefix_expand(prefix) + data + bytes(8) 53 | polymod = _polymod(values) 54 | # Return the polymod expanded into eight 5-bit elements 55 | return bytes((polymod >> 5 * (7 - i)) & 31 for i in range(8)) 56 | 57 | def _convertbits(data, frombits, tobits, pad=True): 58 | """General power-of-2 base conversion.""" 59 | acc = 0 60 | bits = 0 61 | ret = bytearray() 62 | maxv = (1 << tobits) - 1 63 | max_acc = (1 << (frombits + tobits - 1)) - 1 64 | for value in data: 65 | acc = ((acc << frombits) | value ) & max_acc 66 | bits += frombits 67 | while bits >= tobits: 68 | bits -= tobits 69 | ret.append((acc >> bits) & maxv) 70 | 71 | if pad and bits: 72 | ret.append((acc << (tobits - bits)) & maxv) 73 | 74 | return ret 75 | 76 | def _pack_addr_data(kind, addr_hash): 77 | """Pack addr data with version byte""" 78 | version_byte = kind << 3 79 | 80 | offset = 1 81 | encoded_size = 0 82 | if len(addr_hash) >= 40: 83 | offset = 2 84 | encoded_size |= 0x04 85 | encoded_size |= (len(addr_hash) - 20 * offset) // (4 * offset) 86 | 87 | # invalid size? 88 | if ((len(addr_hash) - 20 * offset) % (4 * offset) != 0 89 | or not 0 <= encoded_size <= 7): 90 | raise ValueError('invalid address hash size {}'.format(addr_hash)) 91 | 92 | version_byte |= encoded_size 93 | 94 | data = bytes([version_byte]) + addr_hash 95 | return _convertbits(data, 8, 5, True) 96 | 97 | 98 | def _decode_payload(addr): 99 | """Validate a cashaddr string. 100 | 101 | Throws CashAddr.Error if it is invalid, otherwise returns the 102 | triple 103 | 104 | (prefix, payload) 105 | 106 | without the checksum. 107 | """ 108 | lower = addr.lower() 109 | if lower != addr and addr.upper() != addr: 110 | raise ValueError('mixed case in address: {}'.format(addr)) 111 | 112 | parts = lower.split(':', 1) 113 | if len(parts) != 2: 114 | raise ValueError("address missing ':' separator: {}".format(addr)) 115 | 116 | prefix, payload = parts 117 | if not prefix: 118 | raise ValueError('address prefix is missing: {}'.format(addr)) 119 | if not all(33 <= ord(x) <= 126 for x in prefix): 120 | raise ValueError('invalid address prefix: {}'.format(prefix)) 121 | if not (8 <= len(payload) <= 124): 122 | raise ValueError('address payload has invalid length: {}' 123 | .format(len(addr))) 124 | try: 125 | data = bytes(_CHARSET.find(x) for x in payload) 126 | except ValueError: 127 | raise ValueError('invalid characters in address: {}' 128 | .format(payload)) 129 | 130 | if _polymod(_prefix_expand(prefix) + data): 131 | raise ValueError('invalid checksum in address: {}'.format(addr)) 132 | 133 | if lower != addr: 134 | prefix = prefix.upper() 135 | 136 | # Drop the 40 bit checksum 137 | return prefix, data[:-8] 138 | 139 | # 140 | # External Interface 141 | # 142 | 143 | PUBKEY_TYPE = 0 144 | SCRIPT_TYPE = 1 145 | 146 | def decode(address): 147 | '''Given a cashaddr address, return a triple 148 | 149 | (prefix, kind, hash) 150 | ''' 151 | if not isinstance(address, str): 152 | raise TypeError('address must be a string') 153 | 154 | prefix, payload = _decode_payload(address) 155 | 156 | # Ensure there isn't extra padding 157 | extrabits = len(payload) * 5 % 8 158 | if extrabits >= 5: 159 | raise ValueError('excess padding in address {}'.format(address)) 160 | 161 | # Ensure extrabits are zeros 162 | if payload[-1] & ((1 << extrabits) - 1): 163 | raise ValueError('non-zero padding in address {}'.format(address)) 164 | 165 | decoded = _convertbits(payload, 5, 8, False) 166 | version = decoded[0] 167 | addr_hash = bytes(decoded[1:]) 168 | size = (version & 0x03) * 4 + 20 169 | # Double the size, if the 3rd bit is on. 170 | if version & 0x04: 171 | size <<= 1 172 | if size != len(addr_hash): 173 | raise ValueError('address hash has length {} but expected {}' 174 | .format(len(addr_hash), size)) 175 | 176 | kind = version >> 3 177 | if kind not in (SCRIPT_TYPE, PUBKEY_TYPE): 178 | raise ValueError('unrecognised address type {}'.format(kind)) 179 | 180 | return prefix, kind, addr_hash 181 | 182 | 183 | def encode(prefix, kind, addr_hash): 184 | """Encode a cashaddr address without prefix and separator.""" 185 | if not isinstance(prefix, str): 186 | raise TypeError('prefix must be a string') 187 | 188 | if not isinstance(addr_hash, (bytes, bytearray)): 189 | raise TypeError('addr_hash must be binary bytes') 190 | 191 | if kind not in (SCRIPT_TYPE, PUBKEY_TYPE): 192 | raise ValueError('unrecognised address type {}'.format(kind)) 193 | 194 | payload = _pack_addr_data(kind, addr_hash) 195 | checksum = _create_checksum(prefix, payload) 196 | return ''.join([_CHARSET[d] for d in (payload + checksum)]) 197 | 198 | 199 | def encode_full(prefix, kind, addr_hash): 200 | """Encode a full cashaddr address, with prefix and separator.""" 201 | return ':'.join([prefix, encode(prefix, kind, addr_hash)]) 202 | -------------------------------------------------------------------------------- /cryptos/coins/__init__.py: -------------------------------------------------------------------------------- 1 | from .bitcoin import Bitcoin 2 | from .bitcoin_cash import BitcoinCash 3 | from .dash import Dash 4 | from .dogecoin import Doge 5 | from .litecoin import Litecoin 6 | -------------------------------------------------------------------------------- /cryptos/coins/bitcoin.py: -------------------------------------------------------------------------------- 1 | from cryptos.coins_async.bitcoin import Bitcoin as AsyncBitcoin 2 | from .base import BaseSyncCoin 3 | 4 | 5 | class Bitcoin(BaseSyncCoin): 6 | coin_class = AsyncBitcoin 7 | -------------------------------------------------------------------------------- /cryptos/coins/bitcoin_cash.py: -------------------------------------------------------------------------------- 1 | from cryptos.coins_async.bitcoin_cash import BitcoinCash as AsyncBitcoinCash 2 | from .base import BaseSyncCoin 3 | 4 | 5 | class BitcoinCash(BaseSyncCoin): 6 | coin_class = AsyncBitcoinCash 7 | -------------------------------------------------------------------------------- /cryptos/coins/dash.py: -------------------------------------------------------------------------------- 1 | from cryptos.coins_async.dash import Dash as AsyncDash 2 | from .base import BaseSyncCoin 3 | 4 | 5 | class Dash(BaseSyncCoin): 6 | coin_class = AsyncDash 7 | -------------------------------------------------------------------------------- /cryptos/coins/dogecoin.py: -------------------------------------------------------------------------------- 1 | from cryptos.coins_async.dogecoin import Doge as AsyncDoge 2 | from .base import BaseSyncCoin 3 | 4 | 5 | class Doge(BaseSyncCoin): 6 | coin_class = AsyncDoge 7 | -------------------------------------------------------------------------------- /cryptos/coins/litecoin.py: -------------------------------------------------------------------------------- 1 | from cryptos.coins_async.litecoin import Litecoin as AsyncLitecoin 2 | from .base import BaseSyncCoin 3 | 4 | 5 | class Litecoin(BaseSyncCoin): 6 | coin_class = AsyncLitecoin 7 | -------------------------------------------------------------------------------- /cryptos/coins_async/__init__.py: -------------------------------------------------------------------------------- 1 | from .bitcoin import * 2 | from .bitcoin_cash import * 3 | from .dash import * 4 | from .dogecoin import * 5 | from .litecoin import * 6 | -------------------------------------------------------------------------------- /cryptos/coins_async/bitcoin.py: -------------------------------------------------------------------------------- 1 | from ..explorers import blockchain 2 | from .base import BaseCoin 3 | 4 | 5 | class Bitcoin(BaseCoin): 6 | coin_symbol = "BTC" 7 | display_name = "Bitcoin" 8 | segwit_supported = True 9 | magicbyte = 0 10 | script_magicbyte = 5 11 | minimum_fee = 450 12 | segwit_hrp = "bc" 13 | wif_prefix: int = 0x80 14 | client_kwargs = { 15 | 'server_file': 'bitcoin.json', 16 | } 17 | 18 | testnet_overrides = { 19 | 'display_name': "Bitcoin Testnet", 20 | 'coin_symbol': "BTCTEST", 21 | 'magicbyte': 111, 22 | 'script_magicbyte': 196, 23 | 'segwit_hrp': 'tb', 24 | 'hd_path': 1, 25 | 'wif_prefix': 0xef, 26 | 'minimum_fee': 1000, 27 | 'client_kwargs': { 28 | 'server_file': 'bitcoin_testnet.json', 29 | 'use_ssl': False 30 | }, 31 | 'electrum_pkey_format': 'wif', 32 | 'xprv_headers': { 33 | 'p2pkh': 0x04358394, 34 | 'p2wpkh-p2sh': 0x044a4e28, 35 | 'p2wsh-p2sh': 0x295b005, 36 | 'p2wpkh': 0x04358394, 37 | 'p2wsh': 0x2aa7a99 38 | }, 39 | 'xpub_headers': { 40 | 'p2pkh': 0x043587cf, 41 | 'p2wpkh-p2sh': 0x044a5262, 42 | 'p2wsh-p2sh': 0x295b43f, 43 | 'p2wpkh': 0x043587cf, 44 | 'p2wsh': 0x2aa7ed3 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /cryptos/coins_async/bitcoin_cash.py: -------------------------------------------------------------------------------- 1 | from .base import BaseCoin 2 | from typing import Dict 3 | from ..transaction import SIGHASH_ALL, SIGHASH_FORKID 4 | 5 | 6 | class BitcoinCash(BaseCoin): 7 | coin_symbol = "BCH" 8 | display_name = "Bitcoin Cash" 9 | segwit_supported = False 10 | cash_address_supported = True 11 | magicbyte = 0 12 | script_magicbyte = 5 13 | wif_prefix = 0x80 14 | wif_script_types: Dict[str, int] = { 15 | 'p2pkh': 0, 16 | 'p2sh': 5, 17 | } 18 | hd_path = 145 19 | cash_hrp = "bitcoincash" 20 | hashcode = SIGHASH_ALL | SIGHASH_FORKID 21 | client_kwargs = { 22 | 'server_file': 'bitcoin_cash.json', 23 | } 24 | minimum_fee = 500 25 | testnet_overrides = { 26 | 'display_name': "Bitcoin Cash Testnet", 27 | 'coin_symbol': "tbcc", 28 | 'magicbyte': 111, 29 | 'script_magicbyte': 196, 30 | 'wif_prefix': 0xef, 31 | 'cash_hrp': "bchtest", 32 | 'xprv_headers': { 33 | 'p2pkh': 0x04358394, 34 | }, 35 | 'xpub_headers': { 36 | 'p2pkh': 0x043587cf, 37 | }, 38 | 'hd_path': 1, 39 | 'client_kwargs': { 40 | 'server_file': 'bitcoin_cash_testnet.json', 41 | 'use_ssl': False 42 | }, 43 | } 44 | 45 | def __init__(self, legacy=False, testnet=False, **kwargs): 46 | super(BitcoinCash, self).__init__(testnet=testnet, **kwargs) 47 | self.hd_path = 0 if legacy and testnet else self.hd_path 48 | -------------------------------------------------------------------------------- /cryptos/coins_async/dash.py: -------------------------------------------------------------------------------- 1 | from .base import BaseCoin 2 | from ..explorers import dash_siampm 3 | 4 | 5 | class Dash(BaseCoin): 6 | coin_symbol = "DASH" 7 | display_name = "Dash" 8 | segwit_supported = False 9 | magicbyte = 0x4c 10 | script_magicbyte = 0x10 11 | wif_prefix = 0xcc 12 | hd_path = 5 13 | client_kwargs = { 14 | 'server_file': 'dash.json', 15 | } 16 | testnet_overrides = { 17 | 'display_name': "Dash Testnet", 18 | 'coin_symbol': "DASHTEST", 19 | 'magicbyte': 140, 20 | 'script_magicbyte': 19, 21 | 'wif_prefix': 0xef, 22 | 'hd_path': 1, 23 | 'client_kwargs': { 24 | 'server_file': 'dash_testnet.json', 25 | 'use_ssl': True 26 | }, 27 | 'xpriv_prefix': 0x04358394, 28 | 'xpub_prefix': 0x043587cf 29 | } 30 | -------------------------------------------------------------------------------- /cryptos/coins_async/dogecoin.py: -------------------------------------------------------------------------------- 1 | from .bitcoin import BaseCoin 2 | 3 | class Doge(BaseCoin): 4 | coin_symbol = "DOGE" 5 | display_name = "Dogecoin" 6 | segwit_supported = False 7 | magicbyte = 0x1e 8 | minimum_fee = 300000 9 | script_magicbyte = 0x16 10 | wif_prefix: int = 0x9e 11 | segwit_hrp = "doge" 12 | hd_path = 3 13 | client_kwargs = { 14 | 'server_file': 'doge.json', 15 | 'use_ssl': False 16 | } 17 | xpriv_prefix = 0x02facafd 18 | xpub_prefix = 0x02fac398 19 | testnet_overrides = { 20 | 'display_name': "Dogecoin Testnet", 21 | 'coin_symbol': "Dogecoin", 22 | 'magicbyte': 0x71, 23 | 'script_magicbyte': 0xc4, 24 | 'hd_path': 1, 25 | 'wif_prefix': 0xf1, 26 | 'segwit_hrp': 'xdoge', 27 | 'minimum_fee': 300000, 28 | 'client_kwargs': { 29 | 'server_file': 'doge_testnet.json', 30 | 'use_ssl': False 31 | }, 32 | 'xpriv_prefix': 0x04358394, 33 | 'xpub_prefix': 0x043587cf 34 | } 35 | -------------------------------------------------------------------------------- /cryptos/coins_async/litecoin.py: -------------------------------------------------------------------------------- 1 | from .base import BaseCoin 2 | 3 | 4 | class Litecoin(BaseCoin): 5 | coin_symbol: str = "LTC" 6 | display_name: str = "Litecoin" 7 | segwit_supported: bool = True 8 | magicbyte: int = 48 9 | script_magicbyte = 50 10 | # script_magicbyte: int = 5 # Old magicbyte 11 | minimum_fee: int = 1000 12 | wif_prefix: int = 0xb0 13 | segwit_hrp: str = "ltc" 14 | hd_path: int = 2 15 | client_kwargs = { 16 | 'server_file': 'litecoin.json', 17 | } 18 | testnet_overrides = { 19 | 'display_name': "Litecoin Testnet", 20 | 'coin_symbol': "LTCTEST", 21 | 'magicbyte': 111, 22 | 'script_magicbyte': 58, 23 | #'script_magicbyte': 196, # Old magicbyte, 24 | 'wif_prefix': 0xbf, 25 | 'segwit_hrp': "tltc", 26 | 'minimum_fee': 1000, 27 | 'hd_path': 1, 28 | 'client_kwargs': { 29 | 'server_file': 'litecoin_testnet.json', 30 | 'use_ssl': False 31 | }, 32 | 'xpriv_prefix': 0x04358394, 33 | 'xpub_prefix': 0x043587cf 34 | } 35 | -------------------------------------------------------------------------------- /cryptos/composite.py: -------------------------------------------------------------------------------- 1 | from .deterministic import * 2 | from .transaction import * 3 | from .main import * 4 | 5 | 6 | # BIP32 hierarchical deterministic multisig script 7 | def bip32_hdm_script(*args): 8 | if len(args) == 3: 9 | keys, req, path = args 10 | else: 11 | i, keys, path = 0, [], [] 12 | while len(args[i]) > 40: 13 | keys.append(args[i]) 14 | i += 1 15 | req = int(args[i]) 16 | path = map(int, args[i+1:]) 17 | pubs = sorted(map(lambda x: bip32_descend(x, path), keys)) 18 | return mk_multisig_script(pubs, req) 19 | 20 | 21 | # BIP32 hierarchical deterministic multisig address 22 | def bip32_hdm_addr(*args): 23 | return scriptaddr(bip32_hdm_script(*args)) 24 | 25 | 26 | # Setup a coinvault transaction 27 | def setup_coinvault_tx(tx, script): 28 | txobj = deserialize(tx) 29 | N = deserialize_script(script)[-2] 30 | for inp in txobj["ins"]: 31 | inp["script"] = serialize_script([None] * (N+1) + [script]) 32 | return serialize(txobj) 33 | 34 | 35 | # Sign a coinvault transaction 36 | def sign_coinvault_tx(tx, priv): 37 | pub = privtopub(priv) 38 | txobj = deserialize(tx) 39 | subscript = deserialize_script(txobj['ins'][0]['script']) 40 | oscript = deserialize_script(subscript[-1]) 41 | k, pubs = oscript[0], oscript[1:-2] 42 | for j in range(len(txobj['ins'])): 43 | scr = deserialize_script(txobj['ins'][j]['script']) 44 | for i, p in enumerate(pubs): 45 | if p == pub: 46 | scr[i+1] = multisign(tx, j, subscript[-1], priv) 47 | if len(filter(lambda x: x, scr[1:-1])) >= k: 48 | scr = [None] + filter(lambda x: x, scr[1:-1])[:k] + [scr[-1]] 49 | txobj['ins'][j]['script'] = serialize_script(scr) 50 | return serialize(txobj) 51 | -------------------------------------------------------------------------------- /cryptos/constants.py: -------------------------------------------------------------------------------- 1 | ELECTRUM_VERSION = '3.0.5' # version of the client package 2 | PROTOCOL_VERSION = '1.4' # protocol version requested 3 | 4 | # The hash of the mnemonic seed must begin with this 5 | SEED_PREFIX = '01' # Standard wallet 6 | SEED_PREFIX_2FA = '101' # Two-factor authentication 7 | SEED_PREFIX_SW = '100' # Segwit wallet 8 | 9 | CLIENT_NAME = 'pybitcointools_cryptos' 10 | 11 | SATOSHI_PER_BTC = 100000000 12 | -------------------------------------------------------------------------------- /cryptos/deterministic.py: -------------------------------------------------------------------------------- 1 | from .main import * 2 | import hmac 3 | import hashlib 4 | 5 | # Electrum wallets 6 | 7 | 8 | def electrum_stretch(seed): 9 | return slowsha(seed) 10 | 11 | # Accepts seed or stretched seed, returns master public key 12 | 13 | 14 | def electrum_mpk(seed): 15 | if len(seed) == 32: 16 | seed = electrum_stretch(seed) 17 | return privkey_to_pubkey(seed)[2:] 18 | 19 | # Accepts (seed or stretched seed), index and secondary index 20 | # (conventionally 0 for ordinary address_derivations, 1 for change) , returns privkey 21 | 22 | 23 | def electrum_privkey(seed, n, for_change=0): 24 | if len(seed) == 32: 25 | seed = electrum_stretch(seed) 26 | mpk = electrum_mpk(seed) 27 | offset = dbl_sha256(from_int_representation_to_bytes(n)+b':'+from_int_representation_to_bytes(for_change)+b':'+binascii.unhexlify(mpk)) 28 | return add_privkeys(seed, offset) 29 | 30 | # Accepts (seed or stretched seed or master pubkey), index and secondary index 31 | # (conventionally 0 for ordinary address_derivations, 1 for change) , returns pubkey 32 | 33 | 34 | def electrum_pubkey(masterkey: AnyStr, n: int, for_change: int = 0) -> AnyStr: 35 | if len(masterkey) == 32: 36 | mpk = electrum_mpk(electrum_stretch(masterkey)) 37 | elif len(masterkey) == 64: 38 | mpk = electrum_mpk(masterkey) 39 | else: 40 | mpk = masterkey 41 | bin_mpk = encode_pubkey(mpk, 'bin_electrum') 42 | offset = bin_dbl_sha256(from_int_representation_to_bytes(n)+b':'+from_int_representation_to_bytes(for_change)+b':'+bin_mpk) 43 | return add_pubkeys('04'+mpk, privtopub(offset)) 44 | 45 | # seed/stretched seed/pubkey -> address (convenience method) 46 | 47 | 48 | def electrum_address(masterkey, n, for_change=0, magicbyte=0): 49 | return pubkey_to_address(electrum_pubkey(masterkey, n, for_change), magicbyte) 50 | 51 | # Given a master public key, a private key from that wallet and its index, 52 | # cracks the secret exponent which can be used to generate all other private 53 | # keys in the wallet 54 | 55 | 56 | def crack_electrum_wallet(mpk, pk, n, for_change=0): 57 | bin_mpk = encode_pubkey(mpk, 'bin_electrum') 58 | offset = dbl_sha256(str(n)+':'+str(for_change)+':'+bin_mpk) 59 | return subtract_privkeys(pk, offset) 60 | 61 | # Below code ASSUMES binary inputs and compressed pubkeys 62 | MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' 63 | MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' 64 | TESTNET_PRIVATE = b'\x04\x35\x83\x94' 65 | TESTNET_PUBLIC = b'\x04\x35\x87\xCF' 66 | PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE] 67 | PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC] 68 | DEFAULT = (MAINNET_PRIVATE, MAINNET_PUBLIC) 69 | 70 | # BIP32 child key derivation 71 | 72 | 73 | def raw_bip32_ckd(rawtuple, i, prefixes=DEFAULT): 74 | vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple 75 | i = int(i) 76 | 77 | private = vbytes == prefixes[0] 78 | 79 | if private: 80 | priv = key 81 | pub = privtopub(key) 82 | else: 83 | priv = None 84 | pub = key 85 | 86 | if i >= 2**31: 87 | if not priv: 88 | raise Exception("Can't do private derivation on public key!") 89 | I = hmac.new(chaincode, b'\x00'+priv[:32]+encode(i, 256, 4), hashlib.sha512).digest() 90 | else: 91 | I = hmac.new(chaincode, pub+encode(i, 256, 4), hashlib.sha512).digest() 92 | if private: 93 | newkey = add_privkeys(I[:32]+B'\x01', priv) 94 | fingerprint = bin_hash160(privtopub(key))[:4] 95 | else: 96 | newkey = add_pubkeys(compress(privtopub(I[:32])), key) 97 | fingerprint = bin_hash160(key)[:4] 98 | 99 | return (vbytes, depth + 1, fingerprint, i, I[32:], newkey) 100 | 101 | 102 | def bip32_serialize(rawtuple, prefixes=DEFAULT): 103 | vbytes, depth, fingerprint, i, chaincode, key = rawtuple 104 | i = encode(i, 256, 4) 105 | chaincode = encode(hash_to_int(chaincode), 256, 32) 106 | keydata = b'\x00'+key[:-1] if vbytes == prefixes[0] else key 107 | bindata = vbytes + from_int_to_byte(depth % 256) + fingerprint + i + chaincode + keydata 108 | return changebase(bindata+bin_dbl_sha256(bindata)[:4], 256, 58) 109 | 110 | 111 | def bip32_deserialize(data, prefixes=DEFAULT): 112 | dbin = changebase(data, 58, 256) 113 | if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]: 114 | raise Exception("Invalid checksum") 115 | vbytes = dbin[0:4] 116 | depth = from_byte_to_int(dbin[4]) 117 | fingerprint = dbin[5:9] 118 | i = decode(dbin[9:13], 256) 119 | chaincode = dbin[13:45] 120 | key = dbin[46:78]+b'\x01' if vbytes == prefixes[0] else dbin[45:78] 121 | return (vbytes, depth, fingerprint, i, chaincode, key) 122 | 123 | 124 | def is_xprv(text, prefixes=DEFAULT): 125 | try: 126 | vbytes, depth, fingerprint, i, chaincode, key = bip32_deserialize(text, prefixes) 127 | return vbytes == prefixes[0] 128 | except: 129 | return False 130 | 131 | 132 | def is_xpub(text, prefixes=DEFAULT): 133 | try: 134 | vbytes, depth, fingerprint, i, chaincode, key = bip32_deserialize(text, prefixes) 135 | return vbytes == prefixes[1] 136 | except: 137 | return False 138 | 139 | def raw_bip32_privtopub(rawtuple, prefixes=DEFAULT): 140 | vbytes, depth, fingerprint, i, chaincode, key = rawtuple 141 | newvbytes = prefixes[1] 142 | return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key)) 143 | 144 | 145 | def bip32_privtopub(data, prefixes=DEFAULT): 146 | return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data, prefixes), prefixes), prefixes) 147 | 148 | 149 | def bip32_ckd(key, path, prefixes=DEFAULT, public=False): 150 | if isinstance(path, (list, tuple)): 151 | pathlist = map(str, path) 152 | else: 153 | path = str(path) 154 | pathlist = parse_bip32_path(path) 155 | for i, p in enumerate(pathlist): 156 | key = bip32_serialize(raw_bip32_ckd(bip32_deserialize(key, prefixes), p, prefixes), prefixes) 157 | return key if not public else bip32_privtopub(key) 158 | 159 | def bip32_master_key(seed, prefixes=DEFAULT): 160 | I = hmac.new( 161 | from_string_to_bytes("Bitcoin seed"), 162 | from_string_to_bytes(seed), 163 | hashlib.sha512 164 | ).digest() 165 | return bip32_serialize((prefixes[0], 0, b'\x00'*4, 0, I[32:], I[:32]+b'\x01'), prefixes) 166 | 167 | 168 | def bip32_bin_extract_key(data, prefixes=DEFAULT): 169 | return bip32_deserialize(data, prefixes)[-1] 170 | 171 | 172 | def bip32_extract_key(data, prefixes=DEFAULT): 173 | return safe_hexlify(bip32_deserialize(data, prefixes)[-1]) 174 | 175 | 176 | def bip32_derive_key(key, path, prefixes=DEFAULT, **kwargs): 177 | return bip32_extract_key(bip32_ckd(key, path, prefixes, **kwargs), prefixes) 178 | 179 | # Exploits the same vulnerability as above in Electrum wallets 180 | # Takes a BIP32 pubkey and one of the child privkeys of its corresponding 181 | # privkey and returns the BIP32 privkey associated with that pubkey 182 | 183 | 184 | def raw_crack_bip32_privkey(parent_pub, priv, prefixes=DEFAULT): 185 | vbytes, depth, fingerprint, i, chaincode, key = priv 186 | pvbytes, pdepth, pfingerprint, pi, pchaincode, pkey = parent_pub 187 | i = int(i) 188 | 189 | if i >= 2**31: 190 | raise Exception("Can't crack private derivation!") 191 | 192 | I = hmac.new(pchaincode, pkey+encode(i, 256, 4), hashlib.sha512).digest() 193 | 194 | pprivkey = subtract_privkeys(key, I[:32]+b'\x01') 195 | 196 | newvbytes = prefixes[0] 197 | return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey) 198 | 199 | 200 | def crack_bip32_privkey(parent_pub, priv, prefixes=DEFAULT): 201 | dsppub = bip32_deserialize(parent_pub, prefixes) 202 | dspriv = bip32_deserialize(priv, prefixes) 203 | return bip32_serialize(raw_crack_bip32_privkey(dsppub, dspriv, prefixes), prefixes) 204 | 205 | 206 | def coinvault_pub_to_bip32(*args, prefixes=DEFAULT): 207 | if len(args) == 1: 208 | args = args[0].split(' ') 209 | vals = map(int, args[34:]) 210 | I1 = ''.join(map(chr, vals[:33])) 211 | I2 = ''.join(map(chr, vals[35:67])) 212 | return bip32_serialize((prefixes[1], 0, b'\x00'*4, 0, I2, I1)) 213 | 214 | 215 | def coinvault_priv_to_bip32(*args, prefixes=DEFAULT): 216 | if len(args) == 1: 217 | args = args[0].split(' ') 218 | vals = map(int, args[34:]) 219 | I2 = ''.join(map(chr, vals[35:67])) 220 | I3 = ''.join(map(chr, vals[72:104])) 221 | return bip32_serialize((prefixes[0], 0, b'\x00'*4, 0, I2, I3+b'\x01')) 222 | 223 | 224 | def bip32_descend(*args, prefixes=DEFAULT): 225 | """Descend masterkey and return privkey""" 226 | if len(args) == 2 and isinstance(args[1], list): 227 | key, path = args 228 | elif len(args) == 2 and isinstance(args[1], string_types): 229 | key = args[0] 230 | path = map(int, str(args[1]).lstrip("mM/").split('/')) 231 | elif len(args): 232 | key, path = args[0], map(int, args[1:]) 233 | for p in path: 234 | key = bip32_ckd(key, p, prefixes) 235 | return bip32_extract_key(key, prefixes) 236 | 237 | def parse_bip32_path(path): 238 | """Takes bip32 path, "m/0'/2H" or "m/0H/1/2H/2/1000000000.pub", returns list of ints """ 239 | path = path.lstrip("m/").rstrip(".pub") 240 | if not path: 241 | return [] 242 | #elif path.endswith("/"): incorrect for electrum segwit 243 | # path += "0" 244 | patharr = [] 245 | for v in path.split('/'): 246 | if not v: 247 | continue 248 | elif v[-1] in ("'H"): # hardened path 249 | v = int(v[:-1]) | 0x80000000 250 | else: # non-hardened path 251 | v = int(v) & 0x7fffffff 252 | patharr.append(v) 253 | return patharr 254 | -------------------------------------------------------------------------------- /cryptos/electrumx_client/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import ElectrumXClient 2 | -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/bitcoin_cash.json: -------------------------------------------------------------------------------- 1 | { 2 | "bch.crypto.mldlabs.com": { 3 | "pruning": "-", 4 | "s": "50002", 5 | "t": "50001", 6 | "version": "1.4.2" 7 | }, 8 | "bch.imaginary.cash": { 9 | "pruning": "-", 10 | "s": "50002", 11 | "t": "50001", 12 | "version": "1.4.1" 13 | }, 14 | "bch0.kister.net": { 15 | "pruning": "-", 16 | "s": "50002", 17 | "t": "50001", 18 | "version": "1.4.1" 19 | }, 20 | "bch.loping.net": { 21 | "pruning": "-", 22 | "s": "50002", 23 | "t": "50001", 24 | "version": "1.4.1" 25 | }, 26 | "j2tjfxntnsqpojaamnndgmfrc6lh3thattnlpc2xx53h2ojoi7agccid.onion": { 27 | "pruning": "-", 28 | "s": "50002", 29 | "t": "50001", 30 | "version": "1.4.1", 31 | "display": "bch.loping.net" 32 | }, 33 | "bch.soul-dev.com": { 34 | "pruning": "-", 35 | "s": "50002", 36 | "version": "1.4.1" 37 | }, 38 | "blackie.c3-soft.com": { 39 | "pruning": "-", 40 | "s": "50002", 41 | "t": "50001", 42 | "version": "1.4.1" 43 | }, 44 | "electron.jochen-hoenicke.de": { 45 | "pruning": "-", 46 | "s": "51002", 47 | "t": "51001", 48 | "version": "1.4.1" 49 | }, 50 | "electroncash.dk": { 51 | "pruning": "-", 52 | "s": "50002", 53 | "t": "50001", 54 | "version": "1.4.2" 55 | }, 56 | "bch2.electroncash.dk": { 57 | "pruning": "-", 58 | "s": "50002", 59 | "t": "50001", 60 | "version": "1.4.3" 61 | }, 62 | "electrum.imaginary.cash": { 63 | "pruning": "-", 64 | "s": "50002", 65 | "t": "50001", 66 | "version": "1.4.3" 67 | }, 68 | "electrumx-cash.1209k.com": { 69 | "pruning": "-", 70 | "s": "50002", 71 | "t": "50001", 72 | "version": "1.4.2" 73 | }, 74 | "7nshufncf3nmp7pa42oqhnj6whsjgo2eok4jveex62tczuhvqur5ciad.onion": { 75 | "pruning": "-", 76 | "t": "50001", 77 | "version": "1.4.1", 78 | "display": "electrum.imaginary.cash" 79 | }, 80 | "kisternet5tgeekwidrj7r7yd3n2l5j7y72b74y6xu3q2b6xdjrte6id.onion": { 81 | "pruning": "-", 82 | "t": "50001", 83 | "version": "1.4.1" 84 | }, 85 | "jh3jgcrwweh6yvmprtjnp72u2hqn34nlftlg3msrr4vmlapft4yvt2id.onion": { 86 | "pruning": "-", 87 | "s": "50002", 88 | "t": "50001", 89 | "version": "1.4" 90 | }, 91 | "electroncash.de": { 92 | "pruning": "-", 93 | "s": "50002", 94 | "t": "50001", 95 | "version": "1.4" 96 | }, 97 | "electrs.electroncash.de": { 98 | "pruning": "-", 99 | "s": "40002", 100 | "version": "1.4" 101 | }, 102 | "jktsologn7uprtwn7gsgmwuddj6rxsqmwc2vaug7jwcwzm2bxqnfpwad.onion": { 103 | "pruning": "-", 104 | "s": "50002", 105 | "t": "50001", 106 | "version": "1.4", 107 | "display": "electroncash.de" 108 | }, 109 | "fulcrum.fountainhead.cash": { 110 | "pruning": "-", 111 | "s": "50002", 112 | "t": "50001", 113 | "version": "1.4" 114 | }, 115 | "electrs.bitcoinunlimited.info": { 116 | "pruning": "-", 117 | "s": "50002", 118 | "version": "1.4" 119 | }, 120 | "bch.cyberbits.eu": { 121 | "pruning": "-", 122 | "s": "50002", 123 | "version": "1.4.2" 124 | }, 125 | "bitcoincash.network": { 126 | "pruning": "-", 127 | "s": "50002", 128 | "version": "1.4.1" 129 | }, 130 | "electrumx-bch.cryptonermal.net": { 131 | "pruning": "-", 132 | "s": "50002", 133 | "t": "50001", 134 | "version": "1.4.2" 135 | }, 136 | "electrum.bitcoinverde.org": { 137 | "pruning": "-", 138 | "s": "50002", 139 | "t": "50001", 140 | "version": "1.4.4" 141 | }, 142 | "bch.ninja": { 143 | "pruning": "-", 144 | "s": "50002", 145 | "t": "50001", 146 | "version": "1.4.4" 147 | } 148 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/bitcoin_cash_testnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "bch0.kister.net": { 3 | "s": "51002" 4 | }, 5 | "kisternet5tgeekwidrj7r7yd3n2l5j7y72b74y6xu3q2b6xdjrte6id.onion": { 6 | "s": "51002", 7 | "t": "51001", 8 | "display": "bch0.kister.net" 9 | }, 10 | "blackie.c3-soft.com": { 11 | "s": "60002" 12 | }, 13 | "testnet.imaginary.cash": { 14 | "s": "50002" 15 | }, 16 | "testnet.bitcoincash.network": { 17 | "s": "60002" 18 | }, 19 | "ebf56u6xk2e2fjlqyuz4zjj2gsrrnavfronrerh7qcvzllknytdgmmyd.onion": { 20 | "s": "50004", 21 | "t": "50003" 22 | }, 23 | "jktsologn7uprtwn7gsgmwuddj6rxsqmwc2vaug7jwcwzm2bxqnfpwad.onion": { 24 | "s": "50004", 25 | "t": "50003", 26 | "display": "electroncash.de" 27 | }, 28 | "electroncash.de": { 29 | "s": "50004", 30 | "t": "50003" 31 | }, 32 | "electrs.electroncash.de": { 33 | "s": "60002" 34 | }, 35 | "tbch.loping.net": { 36 | "s": "60002", 37 | "t": "60001" 38 | }, 39 | "scgjgc67226l65u52wyvulioxixv34p5dth73oj35ej7ham2zavdtxid.onion": { 40 | "s": "60002", 41 | "t": "60001", 42 | "display": "tbch.loping.net" 43 | } 44 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/bitcoin_testnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "blackie.c3-soft.com": { 3 | "pruning": "-", 4 | "s": "57006", 5 | "t": "57005", 6 | "version": "1.4.5" 7 | }, 8 | "blockstream.info": { 9 | "pruning": "-", 10 | "s": "993", 11 | "t": "143", 12 | "version": "1.4" 13 | }, 14 | "explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion": { 15 | "pruning": "-", 16 | "t": "143", 17 | "version": "1.4" 18 | }, 19 | "testnet.aranguren.org": { 20 | "pruning": "-", 21 | "s": "51002", 22 | "t": "51001", 23 | "version": "1.4.2" 24 | }, 25 | "testnet.hsmiths.com": { 26 | "pruning": "-", 27 | "s": "53012", 28 | "version": "1.4.2" 29 | }, 30 | "testnet.qtornado.com": { 31 | "pruning": "-", 32 | "s": "51002", 33 | "t": "51001", 34 | "version": "1.5" 35 | }, 36 | "tn.not.fyi": { 37 | "pruning": "-", 38 | "s": "55002", 39 | "t": "55001", 40 | "version": "1.4" 41 | } 42 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/dash.json: -------------------------------------------------------------------------------- 1 | { 2 | "electrumx-mainnet.dash.org": { 3 | "pruning": "-", 4 | "s": "50002", 5 | "version": "1.4" 6 | }, 7 | "drk.p2pay.com": { 8 | "pruning": "-", 9 | "s": "50002", 10 | "version": "1.4" 11 | }, 12 | "electrum1.cipig.net": { 13 | "pruning": "-", 14 | "t": "10061", 15 | "version": "1.4" 16 | }, 17 | "electrum2.cipig.net": { 18 | "pruning": "-", 19 | "t": "10061", 20 | "version": "1.4" 21 | }, 22 | "electrum3.cipig.net": { 23 | "pruning": "-", 24 | "t": "10061", 25 | "version": "1.4" 26 | }, 27 | "hbhux3mfytxeywes5qesx7nzoesukihwseqkw7qa7oprc5nqqxj3jiid.onion": { 28 | "pruning": "-", 29 | "s": "50002", 30 | "version": "1.4" 31 | } 32 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/dash_testnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "electrumx-testnet.dash.org": { 3 | "pruning": "-", 4 | "s": "50002", 5 | "version": "1.4" 6 | }, 7 | "dword.ga": { 8 | "pruning": "-", 9 | "s": "51002", 10 | "version": "1.4" 11 | }, 12 | "hbhux3mfytxeywes5qesx7nzoesukihwseqkw7qa7oprc5nqqxj3jiid.onion": { 13 | "pruning": "-", 14 | "s": "60002", 15 | "version": "1.4" 16 | }, 17 | "dasht.random.re": { 18 | "pruning": "-", 19 | "s": "54002", 20 | "version": "1.4" 21 | } 22 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/doge.json: -------------------------------------------------------------------------------- 1 | { 2 | "electrum1.cipig.net": { 3 | "pruning": "-", 4 | "t": "10060", 5 | "version": "1.4" 6 | }, 7 | "electrum2.cipig.net": { 8 | "pruning": "-", 9 | "t": "10060", 10 | "version": "1.4" 11 | }, 12 | "electrum3.cipig.net": { 13 | "pruning": "-", 14 | "t": "10060", 15 | "version": "1.4" 16 | } 17 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/doge_testnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "vmi1078963.contaboserver.net": { 3 | "pruning": "-", 4 | "t": "51001", 5 | "s": "51002", 6 | "version": "1.4" 7 | }, 8 | "194.163.143.28": { 9 | "pruning": "-", 10 | "t": "51001", 11 | "version": "1.4" 12 | } 13 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/litecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "46.101.3.154": { 3 | "pruning": "-", 4 | "s": "50002", 5 | "t": "50001", 6 | "version": "1.4.2" 7 | }, 8 | "backup.electrum-ltc.org": { 9 | "pruning": "-", 10 | "s": "443", 11 | "t": "50001", 12 | "version": "1.4.2" 13 | }, 14 | "electrum-ltc.bysh.me": { 15 | "pruning": "-", 16 | "s": "50002", 17 | "t": "50001", 18 | "version": "1.4.2" 19 | }, 20 | "electrum.ltc.xurious.com": { 21 | "pruning": "-", 22 | "s": "50002", 23 | "t": "50001", 24 | "version": "1.4.2" 25 | }, 26 | "electrum.privateservers.network": { 27 | "pruning": "-", 28 | "s": "50005", 29 | "t": "50004", 30 | "version": "1.4.2" 31 | }, 32 | "electrum1.cipig.net": { 33 | "pruning": "-", 34 | "s": "20063", 35 | "t": "10063", 36 | "version": "1.4.2" 37 | }, 38 | "ltc.rentonisk.com": { 39 | "pruning": "-", 40 | "s": "50002", 41 | "t": "50001", 42 | "version": "1.4.2" 43 | } 44 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/litecoin_testnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "electrum-ltc.bysh.me": { 3 | "pruning": "-", 4 | "s": "51002", 5 | "t": "51001", 6 | "version": "1.4.2" 7 | }, 8 | "electrum.ltc.xurious.com": { 9 | "pruning": "-", 10 | "s": "51002", 11 | "t": "51001", 12 | "version": "1.4.2" 13 | } 14 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/servers/testing.json: -------------------------------------------------------------------------------- 1 | { 2 | "127.0.0.1": { 3 | "pruning": "-", 4 | "t": "44444", 5 | "s": "44445", 6 | "version": "1.4.2" 7 | }, 8 | "127.0.0.2": { 9 | "pruning": "-", 10 | "t": "44444", 11 | "s": "44445", 12 | "version": "1.4.2" 13 | } 14 | } -------------------------------------------------------------------------------- /cryptos/electrumx_client/types.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict, Union, Callable, List, Dict, Any, Literal, Awaitable 2 | from typing_extensions import NotRequired 3 | 4 | 5 | class ElectrumXBlockCPResponse(TypedDict): 6 | branch: str 7 | header: str 8 | root: str 9 | 10 | 11 | ElectrumXBlockResponse = Union[str, ElectrumXBlockCPResponse] 12 | 13 | 14 | class ElectrumXBlockHeadersResponse(TypedDict): 15 | count: int 16 | hex: str 17 | max: int 18 | root: NotRequired[str] 19 | branch: NotRequired[str] 20 | 21 | 22 | class ElectrumXBlockHeaderNotification(TypedDict): 23 | height: int 24 | hex: str 25 | 26 | 27 | BlockHeaderNotificationCallback = Callable[[ElectrumXBlockHeaderNotification], Awaitable[None]] 28 | 29 | 30 | class ElectrumXBalanceResponse(TypedDict): 31 | confirmed: int 32 | unconfirmed: int 33 | 34 | 35 | class ElectrumXMultiBalanceResponse(TypedDict): 36 | confirmed: int 37 | unconfirmed: int 38 | address: str 39 | 40 | 41 | class ElectrumXTx(TypedDict): 42 | height: int 43 | tx_hash: str 44 | fee: NotRequired[int] 45 | tx_pos: NotRequired[int] 46 | value: NotRequired[int] 47 | address: NotRequired[str] 48 | 49 | 50 | ElectrumXHistoryResponse = List[ElectrumXTx] 51 | 52 | 53 | ElectrumXMempoolResponse = List[ElectrumXTx] 54 | 55 | 56 | ElectrumXUnspentResponse = List[ElectrumXTx] 57 | 58 | 59 | class ElectrumXTxAddress(TypedDict): 60 | height: int 61 | tx_hash: str 62 | fee: NotRequired[int] 63 | tx_pos: NotRequired[int] 64 | value: NotRequired[int] 65 | address: str 66 | 67 | 68 | ElectrumXMultiTxResponse = List[ElectrumXTxAddress] 69 | 70 | 71 | class ElectrumXScripthashNotification(TypedDict): 72 | scripthash: str 73 | status: str 74 | 75 | 76 | AddressNotificationCallback = Callable[[ElectrumXScripthashNotification], Awaitable[None]] 77 | 78 | 79 | class ElectrumXVerboseTX(TypedDict): 80 | blockhash: str 81 | blocktime: int 82 | confirmations: int 83 | hash: str 84 | hex: str 85 | locktime: int 86 | size: int 87 | time: int 88 | txid: str 89 | version: int 90 | vin: List[Dict[str, Any]] 91 | vout: List[Dict[str, Any]] 92 | vsize: int 93 | weight: int 94 | 95 | 96 | ElectrumXGetTxResponse = Union[str, ElectrumXVerboseTX] 97 | 98 | 99 | class ElectrumXMerkleResponse(TypedDict): 100 | block_height: int 101 | merkle: List[str] 102 | pos: int 103 | 104 | 105 | TxidOrTx = Literal['txid', 'tx'] 106 | TargetType = Literal['block_hash', 'block_header', "merkle_root"] 107 | 108 | 109 | class ElectrumXTSCMerkleResponse(TypedDict): 110 | composite: bool 111 | index: int 112 | nodes: List[str] 113 | proofType: Literal['branch', 'tree'] 114 | target: str 115 | targetType: TargetType 116 | txOrId: str 117 | -------------------------------------------------------------------------------- /cryptos/explorers/__init__.py: -------------------------------------------------------------------------------- 1 | from . import base_insight, bitpay, blockcypher, blockdozer, dash_siampm, sochain 2 | 3 | def parse_addr_args(*args): 4 | # Valid input formats: unspent([addr1, addr2, addr3]) 5 | # unspent([addr1, addr2, addr3], coin_symbol="btc") 6 | # unspent(addr1, addr2, addr3) 7 | # unspent(addr1, addr2, addr3, coin_symbol="btc") 8 | addr_args = args 9 | if len(args) == 0: 10 | return [] 11 | if len(addr_args) == 1 and isinstance(addr_args[0], list): 12 | addr_args = addr_args[0] 13 | elif isinstance(addr_args, tuple): 14 | addr_args = list(addr_args) 15 | return addr_args -------------------------------------------------------------------------------- /cryptos/explorers/base_insight.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | import datetime 4 | from .utils import parse_addr_args 5 | 6 | #Base module for all insight-based explorers 7 | 8 | sendtx_url = "%s/tx/send" 9 | address_url = "%s/addrs/%s/txs" 10 | utxo_url = "%s/addrs/%s/utxo" 11 | fetchtx_url = "%s/tx/%s" 12 | current_block_height_url = "%s/status?q=getInfo" 13 | block_hash_by_height_url = "%s/block-index/%s" 14 | block_info_url = "%s/block/%s" 15 | 16 | def unspent(base_url, *args): 17 | 18 | addrs = parse_addr_args(*args) 19 | 20 | if len(addrs) == 0: 21 | return [] 22 | 23 | url = utxo_url % (base_url, ','.join(addrs)) 24 | 25 | response = requests.get(url) 26 | txs = response.json() 27 | for i, tx in enumerate(txs): 28 | if 'satoshis' in tx.keys(): 29 | txs[i] = { 30 | 'output': "%s:%s" % (tx['txid'], tx['vout']), 31 | 'value': tx['satoshis'], 32 | } 33 | else: 34 | txs[i] = { 35 | 'output': "%s:%s" % (tx['txid'], tx['vout']), 36 | 'value': int(tx['amount'] * 100000000), 37 | } 38 | return txs 39 | 40 | def fetchtx(base_url, txhash): 41 | url = fetchtx_url % (base_url, txhash) 42 | response = requests.get(url) 43 | return response.json() 44 | 45 | def txinputs(base_url, txhash): 46 | result = fetchtx(base_url, txhash) 47 | inputs = result['vin'] 48 | unspents = [{'output': "%s:%s" % (i['txid'], i['vout']), 'value': i['valueSat']} for i in inputs] 49 | return unspents 50 | 51 | def pushtx(base_url, network, tx): 52 | if not re.match('^[0-9a-fA-F]*$', tx): 53 | tx = tx.encode('hex') 54 | 55 | url = sendtx_url % base_url 56 | response = requests.post(url, {'rawtx': tx}) 57 | if response.status_code == 200: 58 | result = response.json() 59 | return {'status': 'success', 60 | 'data': { 61 | 'txid': result['txid'], 62 | 'network': network 63 | } 64 | } 65 | return response 66 | 67 | # Gets the transaction output history of a given set of address_derivations, 68 | # including whether or not they have been spent 69 | def history(base_url, *args): 70 | # Valid input formats: history([addr1, addr2,addr3]) 71 | # history(addr1, addr2, addr3) 72 | 73 | addrs = parse_addr_args(*args) 74 | 75 | if len(addrs) == 0: 76 | return [] 77 | 78 | url = address_url % (base_url, ','.join(addrs)) 79 | response = requests.get(url) 80 | return response.json() 81 | 82 | def block_height(base_url, txhash): 83 | tx = fetchtx(base_url, txhash) 84 | return tx.get('blockheight', None) or tx.get('height', None) 85 | 86 | def block_info(base_url, height): 87 | url = block_hash_by_height_url % (base_url, height) 88 | response = requests.get(url) 89 | blockhash = response.json()['blockHash'] 90 | url = block_info_url % (base_url, blockhash) 91 | response = requests.get(url) 92 | data = response.json() 93 | return { 94 | 'version': data['version'], 95 | 'hash': data['hash'], 96 | 'prevhash': data['previousblockhash'], 97 | 'timestamp': data['time'], 98 | 'merkle_root': data['merkleroot'], 99 | 'bits': data['bits'], 100 | 'nonce': data['nonce'], 101 | 'tx_hashes': data['tx'] 102 | } 103 | 104 | 105 | def current_block_height(base_url): 106 | url = current_block_height_url % base_url 107 | response = requests.get(url) 108 | result = response.json() 109 | return result['info']['blocks'] -------------------------------------------------------------------------------- /cryptos/explorers/bitpay.py: -------------------------------------------------------------------------------- 1 | from . import base_insight as insight 2 | 3 | #Docs: https://github.com/bitpay/insight-api 4 | 5 | base_url = "https://bch-insight.bitpay.com/api" 6 | 7 | def unspent(*args): 8 | return insight.unspent(base_url, *args) 9 | 10 | def pushtx(*args): 11 | return insight.pushtx(base_url, *args) 12 | 13 | def history(*args): 14 | return insight.history(base_url, *args) -------------------------------------------------------------------------------- /cryptos/explorers/blockchain.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | from cryptos.transaction import public_txhash 4 | from .utils import parse_addr_args 5 | 6 | def get_url(coin_symbol): 7 | if coin_symbol == "BTC": 8 | return "https://blockchain.info" 9 | return "https://testnet.blockchain.info" 10 | 11 | sendtx_url = "%s/pushtx" 12 | address_url = "%s/address/%s?format=json" 13 | utxo_url = "%s/unspent?active=%s&limit=1000&format=json" 14 | fetchtx_url = "%s/rawtx/%s?format=json" 15 | block_height_url = "%s/block-height/%s?format=json" 16 | latest_block_url = "%s/latestblock" 17 | block_info_url = "%s/rawblock/%s" 18 | 19 | def unspent(*args, coin_symbol="BTC"): 20 | 21 | addrs = parse_addr_args(*args) 22 | 23 | if len(addrs) == 0: 24 | return [] 25 | 26 | base_url = get_url(coin_symbol) 27 | url = utxo_url % (base_url, '|'.join(addrs)) 28 | response = requests.get(url) 29 | if response.text == "No free outputs to spend": 30 | return [] 31 | try: 32 | 33 | outputs = response.json()['unspent_outputs'] 34 | for i, o in enumerate(outputs): 35 | outputs[i] = { 36 | "output": o['tx_hash_big_endian']+':'+str(o['tx_output_n']), 37 | "value": o['value'] 38 | } 39 | return outputs 40 | except (ValueError, KeyError): 41 | raise Exception("Unable to decode JSON from result: %s" % response.text) 42 | 43 | def fetchtx(txhash, coin_symbol="BTC"): 44 | base_url = get_url(coin_symbol) 45 | url = fetchtx_url % (base_url, txhash) 46 | response = requests.get(url) 47 | try: 48 | return response.json() 49 | except ValueError: 50 | raise Exception("Unable to decode JSON from result: %s" % response.text) 51 | 52 | def tx_hash_from_index(index, coin_symbol="BTC"): 53 | result = fetchtx(index, coin_symbol=coin_symbol) 54 | return result['hash'] 55 | 56 | def txinputs(txhash, coin_symbol="BTC"): 57 | result = fetchtx(txhash, coin_symbol=coin_symbol) 58 | inputs = result['inputs'] 59 | unspents = [{'output': "%s:%s" % ( 60 | tx_hash_from_index(i["prev_out"]['tx_index'], coin_symbol=coin_symbol), i["prev_out"]['n']), 61 | 'value': i["prev_out"]['value']} for i in inputs] 62 | return unspents 63 | 64 | def pushtx(tx, coin_symbol="BTC"): 65 | if not re.match('^[0-9a-fA-F]*$', tx): 66 | tx = tx.encode('hex') 67 | 68 | base_url = get_url(coin_symbol) 69 | url = sendtx_url % base_url 70 | hash = public_txhash(tx) 71 | response = requests.post(url, {'tx': tx}) 72 | if response.status_code == 200: 73 | return {'status': 'success', 74 | 'data': { 75 | 'txid': hash, 76 | 'network': coin_symbol 77 | } 78 | } 79 | return response 80 | 81 | # Gets the transaction output history of a given set of address_derivations, 82 | # including whether or not they have been spent 83 | def history(*args, coin_symbol="BTC"): 84 | # Valid input formats: history([addr1, addr2,addr3]) 85 | # history(addr1, addr2, addr3) 86 | 87 | addrs = parse_addr_args(*args) 88 | 89 | if len(addrs) == 0: 90 | return [] 91 | 92 | base_url = get_url(coin_symbol) 93 | url = address_url % (base_url, '|'.join(addrs)) 94 | response = requests.get(url) 95 | return response.json() 96 | 97 | def block_height(txhash, coin_symbol="BTC"): 98 | tx = fetchtx(txhash,coin_symbol=coin_symbol) 99 | return tx['block_height'] 100 | 101 | def block_info(height, coin_symbol="BTC"): 102 | base_url = get_url(coin_symbol) 103 | url = block_height_url % (base_url, height) 104 | response = requests.get(url) 105 | blocks = response.json()['blocks'] 106 | data = list(filter(lambda d: d['main_chain'], blocks))[0] 107 | return { 108 | 'version': data['ver'], 109 | 'hash': data['hash'], 110 | 'prevhash': data['prev_block'], 111 | 'timestamp': data['time'], 112 | 'merkle_root': data['mrkl_root'], 113 | 'bits': data['bits'], 114 | 'nonce': data['nonce'], 115 | 'tx_hashes': [t['hash'] for t in data['tx']] 116 | } 117 | 118 | def current_block_height(coin_symbol="BTC"): 119 | base_url = get_url(coin_symbol) 120 | url = latest_block_url % base_url 121 | response = requests.get(url) 122 | return response.json()["height"] -------------------------------------------------------------------------------- /cryptos/explorers/blockcypher.py: -------------------------------------------------------------------------------- 1 | import re 2 | #from blockcypher import api 3 | from .utils import parse_addr_args 4 | 5 | def unspent(*addrs, coin_symbol=None, api_key=None, **kwargs): 6 | 7 | if len(addrs) == 0: 8 | return [] 9 | 10 | addrs = parse_addr_args(*addrs) 11 | 12 | if len(addrs) == 1: 13 | txs = api.get_address_details(addrs[0], coin_symbol=coin_symbol, unspent_only=True, **kwargs)['txrefs'] 14 | for tx in txs: 15 | tx['output'] = "%s:%s" % (tx['tx_hash'], tx['tx_output_n']) 16 | return txs 17 | result = api.get_addresses_details(addrs, coin_symbol=coin_symbol, api_key=None, unspent_only=True, **kwargs)['txrefs'] 18 | return result 19 | 20 | def pushtx(tx, coin_symbol=None, api_key=None): 21 | if not re.match('^[0-9a-fA-F]*$', tx): 22 | tx = tx.encode('hex') 23 | return api.pushtx(tx, coin_symbol=coin_symbol, api_key=None) 24 | 25 | # Gets the transaction output history of an address, 26 | # including whether or not they have been spent 27 | def history(addr, coin_symbol=None, api_key=None, **kwargs): 28 | return api.get_address_full(addr, coin_symbol=coin_symbol, api_key=None, **kwargs) -------------------------------------------------------------------------------- /cryptos/explorers/blockdozer.py: -------------------------------------------------------------------------------- 1 | from . import base_insight as insight 2 | 3 | base_url = "https://%s.blockdozer.com/insight-api" 4 | 5 | def unspent(*args, coin_symbol="bcc"): 6 | base_url_for_coin = base_url % coin_symbol 7 | return insight.unspent(base_url_for_coin, *args) 8 | 9 | def pushtx(tx, coin_symbol="bcc"): 10 | base_url_for_coin = base_url % coin_symbol 11 | return insight.pushtx(base_url_for_coin, coin_symbol, tx) 12 | 13 | def fetchtx(txhash, coin_symbol="bcc"): 14 | base_url_for_coin = base_url % coin_symbol 15 | return insight.fetchtx(base_url_for_coin, txhash) 16 | 17 | def txinputs(txhash, coin_symbol="bcc"): 18 | base_url_for_coin = base_url % coin_symbol 19 | return insight.txinputs(base_url_for_coin, txhash) 20 | 21 | def history(*args, coin_symbol="bcc"): 22 | base_url_for_coin = base_url % coin_symbol 23 | return insight.history(base_url_for_coin, *args) 24 | 25 | def block_height(tx, coin_symbol="bcc"): 26 | base_url_for_coin = base_url % coin_symbol 27 | return insight.block_height(base_url_for_coin, tx) 28 | 29 | def current_block_height(coin_symbol="bcc"): 30 | base_url_for_coin = base_url % coin_symbol 31 | return insight.current_block_height(base_url_for_coin) 32 | 33 | def block_info(height, coin_symbol="bcc"): 34 | base_url_for_coin = base_url % coin_symbol 35 | return insight.block_info(base_url_for_coin, height) -------------------------------------------------------------------------------- /cryptos/explorers/btg_explorer.py: -------------------------------------------------------------------------------- 1 | from . import base_insight as insight 2 | 3 | def get_url(coin_symbol): 4 | if coin_symbol == "btg": 5 | return "https://btgexplorer.com/api" 6 | raise NotImplementedError("Explorer unavailable for Bitcoin Gold testnet") 7 | 8 | def unspent(*args, coin_symbol="btg"): 9 | base_url = get_url(coin_symbol) 10 | return insight.unspent(base_url, *args) 11 | 12 | def fetchtx(txhash, coin_symbol="btg"): 13 | base_url = get_url(coin_symbol="btg") 14 | return insight.fetchtx(base_url, txhash) 15 | 16 | def txinputs(txhash, coin_symbol="btg"): 17 | base_url = get_url(coin_symbol) 18 | return insight.txinputs(base_url, txhash) 19 | 20 | def pushtx(tx, coin_symbol="btg"): 21 | base_url = get_url(coin_symbol) 22 | return insight.pushtx(base_url, coin_symbol.upper(), tx) 23 | 24 | def history(*args, coin_symbol="btg"): 25 | base_url = get_url(coin_symbol) 26 | return insight.history(base_url, *args) 27 | 28 | def block_height(tx, coin_symbol="btg"): 29 | base_url = get_url(coin_symbol) 30 | return insight.block_height(base_url, tx) 31 | 32 | def current_block_height(coin_symbol="btg"): 33 | base_url = get_url(coin_symbol) 34 | return insight.current_block_height(base_url) 35 | 36 | def block_info(height, coin_symbol="btg"): 37 | base_url = get_url(coin_symbol) 38 | return insight.block_info(base_url, height) -------------------------------------------------------------------------------- /cryptos/explorers/dash_siampm.py: -------------------------------------------------------------------------------- 1 | from . import base_insight as insight 2 | 3 | def get_url(coin_symbol): 4 | if coin_symbol == "DASH": 5 | return "https://insight.dash.siampm.com/api" 6 | return "https://test.insight.dash.siampm.com/api" 7 | 8 | def unspent(*args, coin_symbol="DASH"): 9 | base_url = get_url(coin_symbol) 10 | return insight.unspent(base_url, *args) 11 | 12 | def fetchtx(txhash, coin_symbol="DASH"): 13 | base_url = get_url(coin_symbol) 14 | return insight.fetchtx(base_url, txhash) 15 | 16 | def txinputs(txhash, coin_symbol="DASH"): 17 | base_url = get_url(coin_symbol) 18 | return insight.txinputs(base_url, txhash) 19 | 20 | def pushtx(tx, coin_symbol="DASH"): 21 | base_url = get_url(coin_symbol) 22 | return insight.pushtx(base_url, coin_symbol, tx) 23 | 24 | def history(*args, coin_symbol="DASH"): 25 | base_url = get_url(coin_symbol) 26 | return insight.history(base_url, *args) 27 | 28 | def block_height(tx, coin_symbol="DASH"): 29 | base_url = get_url(coin_symbol) 30 | return insight.block_height(base_url, tx) 31 | 32 | def current_block_height(coin_symbol="DASH"): 33 | base_url = get_url(coin_symbol) 34 | return insight.current_block_height(base_url) 35 | 36 | def block_info(height, coin_symbol="DASH"): 37 | base_url = get_url(coin_symbol) 38 | return insight.block_info(base_url, height) -------------------------------------------------------------------------------- /cryptos/explorers/sochain.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | 4 | #Docs: https://chain.so/api 5 | 6 | base_url = "https://chain.so/api/v2/" 7 | sendtx_url = base_url + "send_tx/%s" 8 | address_url = base_url + "address/%s/%s" 9 | utxo_url = base_url + "get_tx_unspent/%s/%s" 10 | tx_url = base_url + "get_tx/%s/%s" 11 | tx_details_url = base_url + "tx/%s/%s" 12 | tx_inputs_url = base_url + "get_tx_inputs/%s/%s" 13 | network_url = base_url + "get_info/%s" 14 | block_url = base_url + "block/%s/%s" 15 | 16 | def unspent(addr, coin_symbol="LTC"): 17 | url = utxo_url % (coin_symbol, addr) 18 | response = requests.get(url) 19 | try: 20 | result = response.json() 21 | if 'data' in result.keys() and 'txs' in result['data'].keys(): 22 | txs = response.json()['data']['txs'] 23 | for i, tx in enumerate(txs): 24 | txs[i] = { 25 | 'output': "%s:%s" % (tx['txid'], tx['output_no']), 26 | 'value': int(tx['value'].replace('.', '')), 27 | } 28 | return txs 29 | else: 30 | raise Exception(response.text) 31 | except ValueError: 32 | raise Exception("Unable to decode JSON from result: %s" % response.text) 33 | 34 | def fetchtx(txhash, coin_symbol="LTC"): 35 | url = tx_url % (coin_symbol, txhash) 36 | response = requests.get(url) 37 | result = response.json() 38 | return result['data'] 39 | 40 | def gettxdetails(txhash, coin_symbol="LTC"): 41 | url = tx_details_url % (coin_symbol, txhash) 42 | response = requests.get(url) 43 | result = response.json() 44 | return result['data'] 45 | 46 | def txinputs(txhash, coin_symbol="LTC"): 47 | url = tx_inputs_url % (coin_symbol, txhash) 48 | response = requests.get(url) 49 | result = response.json() 50 | inputs = result['data']['inputs'] 51 | unspents = [{'output': (i['from_output']['txid'] + ":" + str(i['from_output']['output_no'])), 'value': int(float(i['value']) * 100000000)} for i in inputs] 52 | return unspents 53 | 54 | def pushtx(tx, coin_symbol="LTC"): 55 | if not re.match('^[0-9a-fA-F]*$', tx): 56 | tx = tx.encode('hex') 57 | url = sendtx_url % coin_symbol 58 | response = requests.post(url, {'tx_hex': tx}) 59 | return response.json() 60 | 61 | def history(addr, coin_symbol="LTC"): 62 | url = address_url % (coin_symbol, addr) 63 | response = requests.get(url) 64 | return response.json() 65 | 66 | def block_height(txhash, coin_symbol="LTC"): 67 | tx = gettxdetails(txhash,coin_symbol=coin_symbol) 68 | return tx['block_no'] 69 | 70 | def block_info(height, coin_symbol="LTC"): 71 | url = block_url % (coin_symbol, height) 72 | response = requests.get(url) 73 | data = response.json()['data'] 74 | return { 75 | 'version': data['version'], 76 | 'hash': data['blockhash'], 77 | 'prevhash': data['previous_blockhash'], 78 | 'timestamp': data['time'], 79 | 'merkle_root': data['merkleroot'], 80 | 'bits': data['bits'], 81 | 'nonce': data['nonce'], 82 | 'tx_hashes': [t['txid'] for t in data['txs']] 83 | } 84 | 85 | 86 | def current_block_height(coin_symbol="LTC"): 87 | url = network_url % coin_symbol 88 | response = requests.get(url) 89 | return response.json()['data']['blocks'] 90 | -------------------------------------------------------------------------------- /cryptos/explorers/utils.py: -------------------------------------------------------------------------------- 1 | def parse_addr_args(*args): 2 | # Valid input formats: unspent([addr1, addr2, addr3]) 3 | # unspent([addr1, addr2, addr3], coin_symbol="btc") 4 | # unspent(addr1, addr2, addr3) 5 | # unspent(addr1, addr2, addr3, coin_symbol="btc") 6 | addr_args = args 7 | if len(args) == 0: 8 | return [] 9 | if len(addr_args) == 1 and isinstance(addr_args[0], list): 10 | addr_args = addr_args[0] 11 | elif isinstance(addr_args, tuple): 12 | addr_args = list(addr_args) 13 | return addr_args -------------------------------------------------------------------------------- /cryptos/mnemonic.py: -------------------------------------------------------------------------------- 1 | import random 2 | from pbkdf2 import PBKDF2 3 | import hmac 4 | from .py3specials import * 5 | from .wallet_utils import is_new_seed 6 | from bisect import bisect_left 7 | import unicodedata 8 | 9 | wordlist_english=[word.strip() for word in list(open(os.path.join(os.path.dirname(os.path.realpath(__file__)),'english.txt'),'r'))] 10 | 11 | ELECTRUM_VERSION = '3.0.5' # version of the client package 12 | PROTOCOL_VERSION = '1.1' # protocol version requested 13 | 14 | # The hash of the mnemonic seed must begin with this 15 | SEED_PREFIX = '01' # Standard wallet 16 | SEED_PREFIX_2FA = '101' # Two-factor authentication 17 | SEED_PREFIX_SW = '100' # Segwit wallet 18 | 19 | whitespace = ' \t\n\r\v\f' 20 | 21 | # http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html 22 | CJK_INTERVALS = [ 23 | (0x4E00, 0x9FFF, 'CJK Unified Ideographs'), 24 | (0x3400, 0x4DBF, 'CJK Unified Ideographs Extension A'), 25 | (0x20000, 0x2A6DF, 'CJK Unified Ideographs Extension B'), 26 | (0x2A700, 0x2B73F, 'CJK Unified Ideographs Extension C'), 27 | (0x2B740, 0x2B81F, 'CJK Unified Ideographs Extension D'), 28 | (0xF900, 0xFAFF, 'CJK Compatibility Ideographs'), 29 | (0x2F800, 0x2FA1D, 'CJK Compatibility Ideographs Supplement'), 30 | (0x3190, 0x319F , 'Kanbun'), 31 | (0x2E80, 0x2EFF, 'CJK Radicals Supplement'), 32 | (0x2F00, 0x2FDF, 'CJK Radicals'), 33 | (0x31C0, 0x31EF, 'CJK Strokes'), 34 | (0x2FF0, 0x2FFF, 'Ideographic Description Characters'), 35 | (0xE0100, 0xE01EF, 'Variation Selectors Supplement'), 36 | (0x3100, 0x312F, 'Bopomofo'), 37 | (0x31A0, 0x31BF, 'Bopomofo Extended'), 38 | (0xFF00, 0xFFEF, 'Halfwidth and Fullwidth Forms'), 39 | (0x3040, 0x309F, 'Hiragana'), 40 | (0x30A0, 0x30FF, 'Katakana'), 41 | (0x31F0, 0x31FF, 'Katakana Phonetic Extensions'), 42 | (0x1B000, 0x1B0FF, 'Kana Supplement'), 43 | (0xAC00, 0xD7AF, 'Hangul Syllables'), 44 | (0x1100, 0x11FF, 'Hangul Jamo'), 45 | (0xA960, 0xA97F, 'Hangul Jamo Extended A'), 46 | (0xD7B0, 0xD7FF, 'Hangul Jamo Extended B'), 47 | (0x3130, 0x318F, 'Hangul Compatibility Jamo'), 48 | (0xA4D0, 0xA4FF, 'Lisu'), 49 | (0x16F00, 0x16F9F, 'Miao'), 50 | (0xA000, 0xA48F, 'Yi Syllables'), 51 | (0xA490, 0xA4CF, 'Yi Radicals'), 52 | ] 53 | def is_CJK(c): 54 | n = ord(c) 55 | for imin,imax,name in CJK_INTERVALS: 56 | if n>=imin and n<=imax: return True 57 | return False 58 | 59 | def normalize_text(seed): 60 | # normalize 61 | seed = unicodedata.normalize('NFKD', seed) 62 | # lower 63 | seed = seed.lower() 64 | # remove accents 65 | seed = u''.join([c for c in seed if not unicodedata.combining(c)]) 66 | # normalize whitespaces 67 | seed = u' '.join(seed.split()) 68 | # remove whitespaces between CJK 69 | seed = u''.join([seed[i] for i in range(len(seed)) if not (seed[i] in whitespace and is_CJK(seed[i-1]) and is_CJK(seed[i+1]))]) 70 | return seed 71 | 72 | 73 | def eint_to_bytes(entint,entbits): 74 | a=hex(entint)[2:].rstrip('L').zfill(32) 75 | print(a) 76 | return binascii.unhexlify(a) 77 | 78 | def mnemonic_int_to_words(mint,mint_num_words,wordlist=wordlist_english): 79 | backwords=[wordlist[(mint >> (11*x)) & 0x7FF].strip() for x in range(mint_num_words)] 80 | return ' '.join((backwords[::-1])) 81 | 82 | def entropy_cs(entbytes): 83 | entropy_size=8*len(entbytes) 84 | checksum_size=entropy_size//32 85 | hd=hashlib.sha256(entbytes).hexdigest() 86 | csint=int(hd,16) >> (256-checksum_size) 87 | return csint,checksum_size 88 | 89 | def entropy_to_words(entbytes,wordlist=wordlist_english): 90 | if(len(entbytes) < 4 or len(entbytes) % 4 != 0): 91 | raise ValueError("The size of the entropy must be a multiple of 4 bytes (multiple of 32 bits)") 92 | entropy_size=8*len(entbytes) 93 | csint,checksum_size = entropy_cs(entbytes) 94 | entint=int(binascii.hexlify(entbytes),16) 95 | mint=(entint << checksum_size) | csint 96 | mint_num_words=(entropy_size+checksum_size)//11 97 | 98 | return mnemonic_int_to_words(mint,mint_num_words,wordlist) 99 | 100 | def words_bisect(word,wordlist=wordlist_english): 101 | lo=bisect_left(wordlist,word) 102 | hi=len(wordlist)-bisect_left(wordlist[:lo:-1],word) 103 | 104 | return lo,hi 105 | 106 | def words_split(wordstr,wordlist=wordlist_english): 107 | def popword(wordstr,wordlist): 108 | for fwl in range(1,9): 109 | w=wordstr[:fwl].strip() 110 | lo,hi=words_bisect(w,wordlist) 111 | if (hi-lo == 1): 112 | return w,wordstr[fwl:].lstrip() 113 | wordlist=wordlist[lo:hi] 114 | raise Exception("Wordstr %s not found in list" %(w)) 115 | 116 | words=[] 117 | tail=wordstr 118 | while(len(tail)): 119 | head,tail=popword(tail,wordlist) 120 | words.append(head) 121 | return words 122 | 123 | def words_to_mnemonic_int(words,wordlist=wordlist_english): 124 | if(isinstance(words,str)): 125 | words = words.split() 126 | return sum([wordlist.index(w) << (11*x) for x,w in enumerate(words[::-1])]) 127 | 128 | def words_verify(words,wordlist=wordlist_english): 129 | if(isinstance(words,str)): 130 | words=words_split(words,wordlist) 131 | 132 | mint = words_to_mnemonic_int(words,wordlist) 133 | mint_bits=len(words)*11 134 | cs_bits=mint_bits//32 135 | entropy_bits=mint_bits-cs_bits 136 | eint=mint >> cs_bits 137 | csint=mint & ((1 << cs_bits)-1) 138 | ebytes = eint_to_bytes(eint,entropy_bits) 139 | return csint == entropy_cs(ebytes) 140 | 141 | def bip39_normalize_passphrase(passphrase): 142 | return unicodedata.normalize('NFKD', passphrase or '') 143 | 144 | # returns tuple (is_checksum_valid, is_wordlist_valid) 145 | def bip39_is_checksum_valid(mnemonic): 146 | words = [ unicodedata.normalize('NFKD', word) for word in mnemonic.split() ] 147 | words_len = len(words) 148 | n = len(wordlist_english) 149 | checksum_length = 11*words_len//33 150 | entropy_length = 32*checksum_length 151 | i = 0 152 | words.reverse() 153 | while words: 154 | w = words.pop() 155 | try: 156 | k = wordlist_english.index(w) 157 | except ValueError: 158 | return False, False 159 | i = i*n + k 160 | if words_len not in [12, 15, 18, 21, 24]: 161 | return False, True 162 | entropy = i >> checksum_length 163 | checksum = i % 2**checksum_length 164 | h = '{:x}'.format(entropy) 165 | while len(h) < entropy_length/4: 166 | h = '0'+h 167 | b = bytearray.fromhex(h) 168 | hashed = int(safe_hexlify(hashlib.sha256(b).digest()), 16) 169 | calculated_checksum = hashed >> (256 - checksum_length) 170 | return checksum == calculated_checksum, True 171 | 172 | def mnemonic_to_seed(mnemonic_phrase, passphrase='', passphrase_prefix=b"mnemonic"): 173 | passphrase = bip39_normalize_passphrase(passphrase) 174 | passphrase = from_string_to_bytes(passphrase) 175 | if isinstance(mnemonic_phrase, (list, tuple)): 176 | mnemonic_phrase = ' '.join(mnemonic_phrase) 177 | mnemonic = unicodedata.normalize('NFKD', ' '.join(mnemonic_phrase.split())) 178 | mnemonic = from_string_to_bytes(mnemonic) 179 | return PBKDF2(mnemonic, passphrase_prefix + passphrase, iterations=2048, macmodule=hmac, digestmodule=hashlib.sha512).read(64) 180 | 181 | def bip39_mnemonic_to_seed(mnemonic_phrase, passphrase=''): 182 | if not bip39_is_checksum_valid(mnemonic_phrase)[1]: 183 | raise Exception("BIP39 Checksum is invalid for this mnemonic") 184 | return mnemonic_to_seed(mnemonic_phrase, passphrase=passphrase, passphrase_prefix=b"mnemonic") 185 | 186 | def electrum_mnemonic_to_seed(mnemonic_phrase, passphrase='', ): 187 | return mnemonic_to_seed(mnemonic_phrase, passphrase=passphrase, passphrase_prefix=b"electrum") 188 | 189 | def is_old_seed(seed): 190 | return False 191 | 192 | def seed_prefix(seed_type): 193 | if seed_type == 'standard': 194 | return SEED_PREFIX 195 | elif seed_type == 'segwit': 196 | return SEED_PREFIX_SW 197 | elif seed_type == '2fa': 198 | return SEED_PREFIX_2FA 199 | 200 | def seed_type(x): 201 | if is_old_seed(x): 202 | return 'old' 203 | elif is_new_seed(x): 204 | return 'standard' 205 | elif is_new_seed(x, SEED_PREFIX_SW): 206 | return 'segwit' 207 | elif is_new_seed(x, SEED_PREFIX_2FA): 208 | return '2fa' 209 | return '' 210 | 211 | is_seed = lambda x: bool(seed_type(x)) 212 | 213 | 214 | 215 | def words_mine(prefix,entbits,satisfunction,wordlist=wordlist_english,randombits=random.getrandbits): 216 | prefix_bits=len(prefix)*11 217 | mine_bits=entbits-prefix_bits 218 | pint=words_to_mnemonic_int(prefix,wordlist) 219 | pint<<=mine_bits 220 | dint=randombits(mine_bits) 221 | count=0 222 | while(not satisfunction(entropy_to_words(eint_to_bytes(pint+dint,entbits)))): 223 | dint=randombits(mine_bits) 224 | if((count & 0xFFFF) == 0): 225 | print("Searched %f percent of the space" % (float(count)/float(1 << mine_bits))) 226 | 227 | return entropy_to_words(eint_to_bytes(pint+dint,entbits)) 228 | 229 | -------------------------------------------------------------------------------- /cryptos/opcodes.py: -------------------------------------------------------------------------------- 1 | # Taken from electrum 2 | from enum import IntEnum 3 | 4 | 5 | class opcodes(IntEnum): 6 | # push value 7 | OP_0 = 0x00 8 | OP_FALSE = OP_0 9 | OP_PUSHDATA1 = 0x4c 10 | OP_PUSHDATA2 = 0x4d 11 | OP_PUSHDATA4 = 0x4e 12 | OP_1NEGATE = 0x4f 13 | OP_RESERVED = 0x50 14 | OP_1 = 0x51 15 | OP_TRUE = OP_1 16 | OP_2 = 0x52 17 | OP_3 = 0x53 18 | OP_4 = 0x54 19 | OP_5 = 0x55 20 | OP_6 = 0x56 21 | OP_7 = 0x57 22 | OP_8 = 0x58 23 | OP_9 = 0x59 24 | OP_10 = 0x5a 25 | OP_11 = 0x5b 26 | OP_12 = 0x5c 27 | OP_13 = 0x5d 28 | OP_14 = 0x5e 29 | OP_15 = 0x5f 30 | OP_16 = 0x60 31 | 32 | # control 33 | OP_NOP = 0x61 34 | OP_VER = 0x62 35 | OP_IF = 0x63 36 | OP_NOTIF = 0x64 37 | OP_VERIF = 0x65 38 | OP_VERNOTIF = 0x66 39 | OP_ELSE = 0x67 40 | OP_ENDIF = 0x68 41 | OP_VERIFY = 0x69 42 | OP_RETURN = 0x6a 43 | 44 | # stack ops 45 | OP_TOALTSTACK = 0x6b 46 | OP_FROMALTSTACK = 0x6c 47 | OP_2DROP = 0x6d 48 | OP_2DUP = 0x6e 49 | OP_3DUP = 0x6f 50 | OP_2OVER = 0x70 51 | OP_2ROT = 0x71 52 | OP_2SWAP = 0x72 53 | OP_IFDUP = 0x73 54 | OP_DEPTH = 0x74 55 | OP_DROP = 0x75 56 | OP_DUP = 0x76 57 | OP_NIP = 0x77 58 | OP_OVER = 0x78 59 | OP_PICK = 0x79 60 | OP_ROLL = 0x7a 61 | OP_ROT = 0x7b 62 | OP_SWAP = 0x7c 63 | OP_TUCK = 0x7d 64 | 65 | # splice ops 66 | OP_CAT = 0x7e 67 | OP_SUBSTR = 0x7f 68 | OP_LEFT = 0x80 69 | OP_RIGHT = 0x81 70 | OP_SIZE = 0x82 71 | 72 | # bit logic 73 | OP_INVERT = 0x83 74 | OP_AND = 0x84 75 | OP_OR = 0x85 76 | OP_XOR = 0x86 77 | OP_EQUAL = 0x87 78 | OP_EQUALVERIFY = 0x88 79 | OP_RESERVED1 = 0x89 80 | OP_RESERVED2 = 0x8a 81 | 82 | # numeric 83 | OP_1ADD = 0x8b 84 | OP_1SUB = 0x8c 85 | OP_2MUL = 0x8d 86 | OP_2DIV = 0x8e 87 | OP_NEGATE = 0x8f 88 | OP_ABS = 0x90 89 | OP_NOT = 0x91 90 | OP_0NOTEQUAL = 0x92 91 | 92 | OP_ADD = 0x93 93 | OP_SUB = 0x94 94 | OP_MUL = 0x95 95 | OP_DIV = 0x96 96 | OP_MOD = 0x97 97 | OP_LSHIFT = 0x98 98 | OP_RSHIFT = 0x99 99 | 100 | OP_BOOLAND = 0x9a 101 | OP_BOOLOR = 0x9b 102 | OP_NUMEQUAL = 0x9c 103 | OP_NUMEQUALVERIFY = 0x9d 104 | OP_NUMNOTEQUAL = 0x9e 105 | OP_LESSTHAN = 0x9f 106 | OP_GREATERTHAN = 0xa0 107 | OP_LESSTHANOREQUAL = 0xa1 108 | OP_GREATERTHANOREQUAL = 0xa2 109 | OP_MIN = 0xa3 110 | OP_MAX = 0xa4 111 | 112 | OP_WITHIN = 0xa5 113 | 114 | # crypto 115 | OP_RIPEMD160 = 0xa6 116 | OP_SHA1 = 0xa7 117 | OP_SHA256 = 0xa8 118 | OP_HASH160 = 0xa9 119 | OP_HASH256 = 0xaa 120 | OP_CODESEPARATOR = 0xab 121 | OP_CHECKSIG = 0xac 122 | OP_CHECKSIGVERIFY = 0xad 123 | OP_CHECKMULTISIG = 0xae 124 | OP_CHECKMULTISIGVERIFY = 0xaf 125 | 126 | # expansion 127 | OP_NOP1 = 0xb0 128 | OP_CHECKLOCKTIMEVERIFY = 0xb1 129 | OP_NOP2 = OP_CHECKLOCKTIMEVERIFY 130 | OP_CHECKSEQUENCEVERIFY = 0xb2 131 | OP_NOP3 = OP_CHECKSEQUENCEVERIFY 132 | OP_NOP4 = 0xb3 133 | OP_NOP5 = 0xb4 134 | OP_NOP6 = 0xb5 135 | OP_NOP7 = 0xb6 136 | OP_NOP8 = 0xb7 137 | OP_NOP9 = 0xb8 138 | OP_NOP10 = 0xb9 139 | 140 | OP_INVALIDOPCODE = 0xff 141 | 142 | def hex(self) -> str: 143 | return bytes([self]).hex() 144 | -------------------------------------------------------------------------------- /cryptos/py3specials.py: -------------------------------------------------------------------------------- 1 | import os 2 | import binascii 3 | import hashlib 4 | from typing import AnyStr, Union, List 5 | 6 | 7 | string_types = (str) 8 | string_or_bytes_types = (str, bytes) 9 | int_types = (int, float) 10 | # Base switching 11 | code_strings = { 12 | 2: '01', 13 | 10: '0123456789', 14 | 16: '0123456789abcdef', 15 | 32: 'abcdefghijklmnopqrstuvwxyz234567', 16 | 58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', 17 | 256: ''.join([chr(x) for x in range(256)]) 18 | } 19 | 20 | def bin_dbl_sha256(s): 21 | bytes_to_hash = from_string_to_bytes(s) 22 | return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() 23 | 24 | def lpad(msg, symbol, length): 25 | if len(msg) >= length: 26 | return msg 27 | return symbol * (length - len(msg)) + msg 28 | 29 | def get_code_string(base): 30 | if base in code_strings: 31 | return code_strings[base] 32 | else: 33 | raise ValueError("Invalid base!") 34 | 35 | 36 | def changebase(string, frm, to, minlen=0): 37 | if frm == to: 38 | return lpad(string, get_code_string(frm)[0], minlen) 39 | return encode(decode(string, frm), to, minlen) 40 | 41 | 42 | def bin_to_b58check(inp: bytes, magicbyte: int = 0) -> str: 43 | if magicbyte == 0: 44 | inp = from_int_to_byte(0) + inp 45 | while magicbyte > 0: 46 | inp = from_int_to_byte(magicbyte % 256) + inp 47 | magicbyte //= 256 48 | 49 | leadingzbytes = 0 50 | for x in inp: 51 | if x != 0: 52 | break 53 | leadingzbytes += 1 54 | 55 | checksum = bin_dbl_sha256(inp)[:4] 56 | return '1' * leadingzbytes + changebase(inp+checksum, 256, 58) 57 | 58 | 59 | def bytes_to_hex_string(b: Union[int, bytes, List[str], List[int]]) -> str: 60 | if isinstance(b, str): 61 | return b 62 | 63 | return ''.join('{:02x}'.format(y) for y in b) 64 | 65 | 66 | def safe_from_hex(s: str) -> bytes: 67 | return bytes.fromhex(s) 68 | 69 | 70 | def from_int_representation_to_bytes(a: int) -> bytes: 71 | return bytes(str(a), 'utf-8') 72 | 73 | 74 | def from_int_to_byte(a: int) -> bytes: 75 | return bytes([a]) 76 | 77 | 78 | def from_byte_to_int(a: bytes) -> int: 79 | return a 80 | 81 | 82 | def from_string_to_bytes(a: AnyStr) -> bytes: 83 | return a if isinstance(a, bytes) else bytes(a, 'utf-8') 84 | 85 | 86 | def safe_hexlify(a: bytes) -> str: 87 | return str(binascii.hexlify(a), 'utf-8') 88 | 89 | 90 | def encode(val, base, minlen=0): 91 | base, minlen = int(base), int(minlen) 92 | code_string = get_code_string(base) 93 | result_bytes = bytes() 94 | while val > 0: 95 | curcode = code_string[val % base] 96 | result_bytes = bytes([ord(curcode)]) + result_bytes 97 | val //= base 98 | 99 | pad_size = minlen - len(result_bytes) 100 | 101 | padding_element = b'\x00' if base == 256 else b'1' \ 102 | if base == 58 else b'0' 103 | if (pad_size > 0): 104 | result_bytes = padding_element*pad_size + result_bytes 105 | 106 | result_string = ''.join([chr(y) for y in result_bytes]) 107 | result = result_bytes if base == 256 else result_string 108 | 109 | return result 110 | 111 | 112 | def decode(string, base): 113 | if base == 256 and isinstance(string, str): 114 | string = bytes(bytearray.fromhex(string)) 115 | base = int(base) 116 | code_string = get_code_string(base) 117 | result = 0 118 | if base == 256: 119 | def extract(d, cs): 120 | return d 121 | else: 122 | def extract(d, cs): 123 | return cs.find(d if isinstance(d, str) else chr(d)) 124 | 125 | if base == 16: 126 | string = string.lower() 127 | while len(string) > 0: 128 | result *= base 129 | result += extract(string[0], code_string) 130 | string = string[1:] 131 | return result 132 | 133 | 134 | def random_string(x): 135 | return str(os.urandom(x)) 136 | -------------------------------------------------------------------------------- /cryptos/script_utils.py: -------------------------------------------------------------------------------- 1 | from cryptos.coins_async import BaseCoin, Bitcoin, BitcoinCash, Dash, Litecoin, Doge 2 | 3 | 4 | coins = {c.coin_symbol: c for c in (Bitcoin, Litecoin, BitcoinCash, Dash, Doge)} 5 | 6 | 7 | def get_coin(coin_symbol: str, testnet: bool) -> BaseCoin: 8 | symbol = coin_symbol.upper() 9 | return coins[symbol](testnet=testnet) 10 | 11 | 12 | coin_list = [c.lower() for c in coins.keys()] 13 | -------------------------------------------------------------------------------- /cryptos/segwit_addr.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) 2017 Pieter Wuille 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | """Reference implementation for Bech32/Bech32m and segwit addresses.""" 23 | 24 | from enum import Enum 25 | from typing import Tuple, Optional, Sequence, NamedTuple, List 26 | 27 | CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 28 | _CHARSET_INVERSE = {x: CHARSET.find(x) for x in CHARSET} 29 | 30 | BECH32_CONST = 1 31 | BECH32M_CONST = 0x2bc830a3 32 | 33 | 34 | class Encoding(Enum): 35 | """Enumeration type to list the various supported encodings.""" 36 | BECH32 = 1 37 | BECH32M = 2 38 | 39 | 40 | class DecodedBech32(NamedTuple): 41 | encoding: Optional[Encoding] 42 | hrp: Optional[str] 43 | data: Optional[Sequence[int]] # 5-bit ints 44 | 45 | 46 | def bech32_polymod(values): 47 | """Internal function that computes the Bech32 checksum.""" 48 | generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] 49 | chk = 1 50 | for value in values: 51 | top = chk >> 25 52 | chk = (chk & 0x1ffffff) << 5 ^ value 53 | for i in range(5): 54 | chk ^= generator[i] if ((top >> i) & 1) else 0 55 | return chk 56 | 57 | 58 | def bech32_hrp_expand(hrp): 59 | """Expand the HRP into values for checksum computation.""" 60 | return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] 61 | 62 | 63 | def bech32_verify_checksum(hrp, data): 64 | """Verify a checksum given HRP and converted data characters.""" 65 | check = bech32_polymod(bech32_hrp_expand(hrp) + data) 66 | if check == BECH32_CONST: 67 | return Encoding.BECH32 68 | elif check == BECH32M_CONST: 69 | return Encoding.BECH32M 70 | else: 71 | return None 72 | 73 | 74 | def bech32_create_checksum(encoding: Encoding, hrp: str, data: List[int]) -> List[int]: 75 | """Compute the checksum values given HRP and data.""" 76 | values = bech32_hrp_expand(hrp) + data 77 | const = BECH32M_CONST if encoding == Encoding.BECH32M else BECH32_CONST 78 | polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const 79 | return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] 80 | 81 | 82 | def bech32_encode(encoding: Encoding, hrp: str, data: List[int]) -> str: 83 | """Compute a Bech32 or Bech32m string given HRP and data values.""" 84 | combined = data + bech32_create_checksum(encoding, hrp, data) 85 | return hrp + '1' + ''.join([CHARSET[d] for d in combined]) 86 | 87 | 88 | def bech32_decode(bech: str, *, ignore_long_length=False) -> DecodedBech32: 89 | """Validate a Bech32/Bech32m string, and determine HRP and data.""" 90 | bech_lower = bech.lower() 91 | if bech_lower != bech and bech.upper() != bech: 92 | return DecodedBech32(None, None, None) 93 | pos = bech.rfind('1') 94 | if pos < 1 or pos + 7 > len(bech) or (not ignore_long_length and len(bech) > 90): 95 | return DecodedBech32(None, None, None) 96 | # check that HRP only consists of sane ASCII chars 97 | if any(ord(x) < 33 or ord(x) > 126 for x in bech[:pos+1]): 98 | return DecodedBech32(None, None, None) 99 | bech = bech_lower 100 | hrp = bech[:pos] 101 | try: 102 | data = [_CHARSET_INVERSE[x] for x in bech[pos+1:]] 103 | except KeyError: 104 | return DecodedBech32(None, None, None) 105 | encoding = bech32_verify_checksum(hrp, data) 106 | if encoding is None: 107 | return DecodedBech32(None, None, None) 108 | return DecodedBech32(encoding=encoding, hrp=hrp, data=data[:-6]) 109 | 110 | 111 | def convertbits(data, frombits, tobits, pad=True): 112 | """General power-of-2 base conversion.""" 113 | acc = 0 114 | bits = 0 115 | ret = [] 116 | maxv = (1 << tobits) - 1 117 | max_acc = (1 << (frombits + tobits - 1)) - 1 118 | for value in data: 119 | if value < 0 or (value >> frombits): 120 | return None 121 | acc = ((acc << frombits) | value) & max_acc 122 | bits += frombits 123 | while bits >= tobits: 124 | bits -= tobits 125 | ret.append((acc >> bits) & maxv) 126 | if pad: 127 | if bits: 128 | ret.append((acc << (tobits - bits)) & maxv) 129 | elif bits >= frombits or ((acc << (tobits - bits)) & maxv): 130 | return None 131 | return ret 132 | 133 | 134 | def decode_segwit_address(hrp: str, addr: Optional[str]) -> Tuple[Optional[int], Optional[Sequence[int]]]: 135 | """Decode a segwit address.""" 136 | if addr is None: 137 | return (None, None) 138 | encoding, hrpgot, data = bech32_decode(addr) 139 | if hrpgot != hrp: 140 | return (None, None) 141 | decoded = convertbits(data[1:], 5, 8, False) 142 | if decoded is None or len(decoded) < 2 or len(decoded) > 40: 143 | return (None, None) 144 | if data[0] > 16: 145 | return (None, None) 146 | if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: 147 | return (None, None) 148 | if (data[0] == 0 and encoding != Encoding.BECH32) or (data[0] != 0 and encoding != Encoding.BECH32M): 149 | return (None, None) 150 | return (data[0], decoded) 151 | 152 | 153 | def encode_segwit_address(hrp: str, witver: int, witprog: bytes) -> Optional[str]: 154 | """Encode a segwit address.""" 155 | encoding = Encoding.BECH32 if witver == 0 else Encoding.BECH32M 156 | ret = bech32_encode(encoding, hrp, [witver] + convertbits(witprog, 8, 5)) 157 | if decode_segwit_address(hrp, ret) == (None, None): 158 | return None 159 | return ret 160 | -------------------------------------------------------------------------------- /cryptos/stealth.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | 3 | # Shared secrets and uncovering pay keys 4 | 5 | 6 | def shared_secret_sender(scan_pubkey, ephem_privkey): 7 | shared_point = main.multiply(scan_pubkey, ephem_privkey) 8 | shared_secret = main.sha256(main.encode_pubkey(shared_point, 'bin_compressed')) 9 | return shared_secret 10 | 11 | 12 | def shared_secret_receiver(ephem_pubkey, scan_privkey): 13 | shared_point = main.multiply(ephem_pubkey, scan_privkey) 14 | shared_secret = main.sha256(main.encode_pubkey(shared_point, 'bin_compressed')) 15 | return shared_secret 16 | 17 | 18 | def uncover_pay_pubkey_sender(scan_pubkey, spend_pubkey, ephem_privkey): 19 | shared_secret = shared_secret_sender(scan_pubkey, ephem_privkey) 20 | return main.add_pubkeys(spend_pubkey, main.privtopub(shared_secret)) 21 | 22 | 23 | def uncover_pay_pubkey_receiver(scan_privkey, spend_pubkey, ephem_pubkey): 24 | shared_secret = shared_secret_receiver(ephem_pubkey, scan_privkey) 25 | return main.add_pubkeys(spend_pubkey, main.privtopub(shared_secret)) 26 | 27 | 28 | def uncover_pay_privkey(scan_privkey, spend_privkey, ephem_pubkey): 29 | shared_secret = shared_secret_receiver(ephem_pubkey, scan_privkey) 30 | return main.add_privkeys(spend_privkey, shared_secret) 31 | 32 | # Address encoding 33 | 34 | # Functions for basic stealth address_derivations, 35 | # i.e. one scan key, one spend key, no prefix 36 | 37 | 38 | def pubkeys_to_basic_stealth_address(scan_pubkey, spend_pubkey, magic_byte=42): 39 | # magic_byte = 42 for mainnet, 43 for testnet. 40 | hex_scankey = main.encode_pubkey(scan_pubkey, 'hex_compressed') 41 | hex_spendkey = main.encode_pubkey(spend_pubkey, 'hex_compressed') 42 | hex_data = '00{0:066x}01{1:066x}0100'.format(int(hex_scankey, 16), int(hex_spendkey, 16)) 43 | addr = main.hex_to_b58check(hex_data, magic_byte) 44 | return addr 45 | 46 | 47 | def basic_stealth_address_to_pubkeys(stealth_address): 48 | magicbyte, hex_data = main.b58check_to_hex(stealth_address) 49 | if len(hex_data) != 140: 50 | raise Exception('Stealth address is not of basic type (one scan key, one spend key, no prefix)') 51 | 52 | scan_pubkey = hex_data[2:68] 53 | spend_pubkey = hex_data[70:136] 54 | return scan_pubkey, spend_pubkey 55 | 56 | # Sending stealth payments 57 | 58 | 59 | def mk_stealth_metadata_script(ephem_pubkey, nonce): 60 | op_return = '6a' 61 | msg_size = '26' 62 | version = '06' 63 | return op_return + msg_size + version + '{0:08x}'.format(nonce) + main.encode_pubkey(ephem_pubkey, 'hex_compressed') 64 | 65 | 66 | def mk_stealth_tx_outputs(stealth_addr, value, ephem_privkey, nonce, network='btc'): 67 | 68 | scan_pubkey, spend_pubkey = basic_stealth_address_to_pubkeys(stealth_addr) 69 | 70 | if network == 'btc': 71 | btc_magic_byte = 42 72 | if stealth_addr != pubkeys_to_basic_stealth_address(scan_pubkey, spend_pubkey, btc_magic_byte): 73 | raise Exception('Invalid btc mainnet stealth address: ' + stealth_addr) 74 | magic_byte_addr = 0 75 | 76 | elif network == 'testnet': 77 | testnet_magic_byte = 43 78 | if stealth_addr != pubkeys_to_basic_stealth_address(scan_pubkey, spend_pubkey, testnet_magic_byte): 79 | raise Exception('Invalid testnet stealth address: ' + stealth_addr) 80 | magic_byte_addr = 111 81 | 82 | ephem_pubkey = main.privkey_to_pubkey(ephem_privkey) 83 | output0 = {'script': mk_stealth_metadata_script(ephem_pubkey, nonce), 84 | 'value': 0} 85 | 86 | pay_pubkey = uncover_pay_pubkey_sender(scan_pubkey, spend_pubkey, ephem_privkey) 87 | pay_addr = main.pubkey_to_address(pay_pubkey, magic_byte_addr) 88 | output1 = {'address': pay_addr, 89 | 'value': value} 90 | 91 | return [output0, output1] 92 | 93 | # Receiving stealth payments 94 | 95 | 96 | def ephem_pubkey_from_tx_script(stealth_tx_script): 97 | if len(stealth_tx_script) != 80: 98 | raise Exception('Wrong format for stealth tx output') 99 | return stealth_tx_script[14:] 100 | -------------------------------------------------------------------------------- /cryptos/testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primal100/pybitcointools/e7c96bfe1f4be08a9f3c540e598a73dc20ca2462/cryptos/testing/__init__.py -------------------------------------------------------------------------------- /cryptos/types.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict, Dict, Any, AnyStr 2 | from typing import List, AnyStr, Union, Callable, Awaitable 3 | from typing_extensions import NotRequired 4 | from .electrumx_client.types import ElectrumXTx 5 | 6 | 7 | class TxInput(TypedDict): 8 | tx_hash: NotRequired[str] 9 | tx_pos: NotRequired[int] 10 | output: NotRequired[str] 11 | script: NotRequired[AnyStr] 12 | sequence: NotRequired[int] 13 | value: NotRequired[int] 14 | address: NotRequired[str] 15 | 16 | 17 | class TxOut(TypedDict): 18 | value: int 19 | address: NotRequired[str] 20 | script: NotRequired[str] 21 | 22 | 23 | class Witness(TypedDict): 24 | number: int 25 | scriptCode: AnyStr 26 | 27 | 28 | class Tx(TypedDict): 29 | ins: List[TxInput] 30 | outs: List[TxOut] 31 | version: str 32 | marker: NotRequired[str] 33 | flag: NotRequired[str] 34 | witness: NotRequired[List[Witness]] 35 | tx_hash: NotRequired[str] 36 | locktime: int 37 | 38 | 39 | class BlockHeader(TypedDict): 40 | version: int 41 | prevhash: bytes 42 | merkle_root: bytes 43 | timestamp: int 44 | bits: int 45 | nonce: int 46 | hash: bytes 47 | 48 | 49 | class MerkleProof(TypedDict): 50 | tx_hash: str 51 | siblings: NotRequired[List[str]] 52 | proven: bool 53 | 54 | 55 | class AddressBalance(TypedDict): 56 | address: str 57 | balance: int 58 | 59 | 60 | class AddressStatusUpdate(TypedDict): 61 | address: str 62 | status: str 63 | 64 | 65 | BlockHeaderCallbackSync = Callable[[int, str, BlockHeader], None] 66 | BlockHeaderCallbackAsync = Callable[[int, str, BlockHeader], Awaitable[None]] 67 | BlockHeaderCallback = Union[BlockHeaderCallbackSync, BlockHeaderCallbackAsync] 68 | 69 | 70 | AddressCallbackSync = Callable[[str, str], None] 71 | AddressCallbackAsync = Callable[[str, str], Awaitable[None]] 72 | AddressCallback = Union[AddressCallbackSync, AddressCallbackAsync] 73 | 74 | 75 | AddressTXCallbackSync = Callable[[str, List[ElectrumXTx], List[ElectrumXTx], List[ElectrumXTx], List[ElectrumXTx], int, int, int], None] 76 | AddressTXCallbackAsync = Callable[[str, List[ElectrumXTx], List[ElectrumXTx], List[ElectrumXTx], List[ElectrumXTx], int, int, int], Awaitable[None]] 77 | AddressTXCallback = Union[AddressTXCallbackSync, AddressTXCallbackAsync] 78 | 79 | 80 | # Either a single private key or a mapping of addresses to private keys 81 | PrivkeyType = Union[int, str, bytes] 82 | PrivateKeySignAllType = Union[Dict[str, PrivkeyType], PrivkeyType] 83 | PubKeyType = Union[list, tuple, str, bytes] 84 | 85 | 86 | class TXInspectType(TypedDict): 87 | ins: Dict[str, TxInput] 88 | outs: List[Dict[str, Any]] 89 | fee: int 90 | -------------------------------------------------------------------------------- /cryptos/utils.py: -------------------------------------------------------------------------------- 1 | # Electrum - lightweight Bitcoin client 2 | # Copyright (C) 2011 Thomas Voegtlin 3 | # 4 | # Permission is hereby granted, free of charge, to any person 5 | # obtaining a copy of this software and associated documentation files 6 | # (the "Software"), to deal in the Software without restriction, 7 | # including without limitation the rights to use, copy, modify, merge, 8 | # publish, distribute, sublicense, and/or sell copies of the Software, 9 | # 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 13 | # included in all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | import os 25 | import re 26 | from typing import AsyncGenerator, List, Any, Optional, AnyStr, Match 27 | 28 | 29 | def user_dir(appname: str) -> Optional[str]: 30 | if os.name == 'posix': 31 | return os.path.join(os.environ["HOME"], ".%s" % appname.lower()) 32 | elif "APPDATA" in os.environ: 33 | return os.path.join(os.environ["APPDATA"], appname.capitalize()) 34 | elif "LOCALAPPDATA" in os.environ: 35 | return os.path.join(os.environ["LOCALAPPDATA"], appname.capitalize()) 36 | else: 37 | # raise Exception("No home directory found in environment variables.") 38 | return 39 | 40 | 41 | async def alist(generator: AsyncGenerator[Any, None]) -> List[Any]: 42 | return [i async for i in generator] 43 | 44 | 45 | def is_hex(text: str) -> Optional[Match[AnyStr]]: 46 | regex = '^[0-9a-fA-F]*$' 47 | if isinstance(text, bytes): 48 | regex = regex.encode() 49 | return re.match(regex, text) 50 | -------------------------------------------------------------------------------- /cryptos/wallet_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Electrum - lightweight Bitcoin client 4 | # Copyright (C) 2011 thomasv@gitorious with changes by pycryptools developers 5 | # 6 | # Permission is hereby granted, free of charge, to any person 7 | # obtaining a copy of this software and associated documentation files 8 | # (the "Software"), to deal in the Software without restriction, 9 | # including without limitation the rights to use, copy, modify, merge, 10 | # publish, distribute, sublicense, and/or sell copies of the Software, 11 | # and to permit persons to whom the Software is furnished to do so, 12 | # subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be 15 | # included in all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | 27 | from .main import * 28 | from .py3specials import * 29 | from . import constants as version 30 | 31 | # Version numbers for BIP32 extended keys 32 | # standard: xprv, xpub 33 | # segwit in p2sh: yprv, ypub 34 | # native segwit: zprv, zpub 35 | XPRV_HEADERS = { 36 | 'standard': 0x0488ade4, 37 | 'p2wpkh-p2sh': 0x049d7878, 38 | 'p2wsh-p2sh': 0x295b005, 39 | 'p2wpkh': 0x4b2430c, 40 | 'p2wsh': 0x2aa7a99 41 | } 42 | XPUB_HEADERS = { 43 | 'standard': 0x0488b21e, 44 | 'p2wpkh-p2sh': 0x049d7cb2, 45 | 'p2wsh-p2sh': 0x295b43f, 46 | 'p2wpkh': 0x4b24746, 47 | 'p2wsh': 0x2aa7ed3 48 | } 49 | 50 | bh2u = safe_hexlify 51 | hfu = binascii.hexlify 52 | bfh = safe_from_hex 53 | hmac_sha_512 = lambda x, y: hmac.new(x, y, hashlib.sha512).digest() 54 | 55 | def rev_hex(s): 56 | return bh2u(bfh(s)[::-1]) 57 | 58 | def int_to_hex(i, length=1): 59 | assert isinstance(i, int) 60 | s = hex(i)[2:].rstrip('L') 61 | s = "0"*(2*length - len(s)) + s 62 | return rev_hex(s) 63 | 64 | class InvalidPassword(Exception): 65 | def __str__(self): 66 | return "Incorrect password" 67 | 68 | try: 69 | from Cryptodome.Cipher import AES 70 | except: 71 | AES = None 72 | 73 | class InvalidPasswordException(Exception): 74 | pass 75 | 76 | class InvalidPadding(Exception): 77 | pass 78 | 79 | def assert_bytes(*args): 80 | """ 81 | porting helper, assert args type 82 | """ 83 | try: 84 | for x in args: 85 | assert isinstance(x, (bytes, bytearray)) 86 | except: 87 | print('assert bytes failed', list(map(type, args))) 88 | raise 89 | 90 | def append_PKCS7_padding(data): 91 | assert_bytes(data) 92 | padlen = 16 - (len(data) % 16) 93 | return data + bytes([padlen]) * padlen 94 | 95 | 96 | def strip_PKCS7_padding(data): 97 | assert_bytes(data) 98 | if len(data) % 16 != 0 or len(data) == 0: 99 | raise InvalidPadding("invalid length") 100 | padlen = data[-1] 101 | if padlen > 16: 102 | raise InvalidPadding("invalid padding byte (large)") 103 | for i in data[-padlen:]: 104 | if i != padlen: 105 | raise InvalidPadding("invalid padding byte (inconsistent)") 106 | return data[0:-padlen] 107 | 108 | def aes_encrypt_with_iv(key, iv, data): 109 | assert_bytes(key, iv, data) 110 | data = append_PKCS7_padding(data) 111 | if AES: 112 | e = AES.new(key, AES.MODE_CBC, iv).encrypt(data) 113 | else: 114 | aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv) 115 | aes = pyaes.Encrypter(aes_cbc, padding=pyaes.PADDING_NONE) 116 | e = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer 117 | return e 118 | 119 | 120 | def aes_decrypt_with_iv(key, iv, data): 121 | assert_bytes(key, iv, data) 122 | if AES: 123 | cipher = AES.new(key, AES.MODE_CBC, iv) 124 | data = cipher.decrypt(data) 125 | else: 126 | aes_cbc = pyaes.AESModeOfOperationCBC(key, iv=iv) 127 | aes = pyaes.Decrypter(aes_cbc, padding=pyaes.PADDING_NONE) 128 | data = aes.feed(data) + aes.feed() # empty aes.feed() flushes buffer 129 | try: 130 | return strip_PKCS7_padding(data) 131 | except InvalidPadding: 132 | raise InvalidPassword() 133 | 134 | def EncodeAES(secret, s): 135 | assert_bytes(s) 136 | iv = bytes(os.urandom(16)) 137 | ct = aes_encrypt_with_iv(secret, iv, s) 138 | e = iv + ct 139 | return base64.b64encode(e) 140 | 141 | def DecodeAES(secret, e): 142 | e = bytes(base64.b64decode(e)) 143 | iv, e = e[:16], e[16:] 144 | s = aes_decrypt_with_iv(secret, iv, e) 145 | return s 146 | 147 | def pw_encode(s, password): 148 | if password: 149 | secret = Hash(password) 150 | return EncodeAES(secret, to_bytes(s, "utf8")).decode('utf8') 151 | else: 152 | return s 153 | 154 | def pw_decode(s, password): 155 | if password is not None: 156 | secret = Hash(password) 157 | try: 158 | d = to_string(DecodeAES(secret, s), "utf8") 159 | except Exception: 160 | raise InvalidPassword() 161 | return d 162 | else: 163 | return s 164 | 165 | def is_new_seed(x, prefix=version.SEED_PREFIX): 166 | from . import mnemonic 167 | x = mnemonic.normalize_text(x) 168 | s = bh2u(hmac_sha_512(b"Seed version", x.encode('utf8'))) 169 | return s.startswith(prefix) 170 | 171 | def seed_type(x): 172 | if is_new_seed(x): 173 | return 'standard' 174 | elif is_new_seed(x, version.SEED_PREFIX_SW): 175 | return 'segwit' 176 | elif is_new_seed(x, version.SEED_PREFIX_2FA): 177 | return '2fa' 178 | return '' 179 | 180 | is_seed = lambda x: bool(seed_type(x)) 181 | 182 | SCRIPT_TYPES = { 183 | 'p2pkh':0, 184 | 'p2wpkh':1, 185 | 'p2wpkh-p2sh':2, 186 | 'p2sh':5, 187 | 'p2wsh':6, 188 | 'p2wsh-p2sh':7 189 | } 190 | 191 | __b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 192 | assert len(__b58chars) == 58 193 | 194 | __b43chars = b'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:' 195 | assert len(__b43chars) == 43 196 | 197 | def inv_dict(d): 198 | return {v: k for k, v in d.items()} 199 | 200 | def is_minikey(text): 201 | # Minikeys are typically 22 or 30 characters, but this routine 202 | # permits any length of 20 or more provided the minikey is valid. 203 | # A valid minikey must begin with an 'S', be in base58, and when 204 | # suffixed with '?' have its SHA256 hash begin with a zero byte. 205 | # They are widely used in Casascius physical bitcoins. 206 | return (len(text) >= 20 and text[0] == 'S' 207 | and all(ord(c) in __b58chars for c in text) 208 | and sha256(text + '?')[0] == 0x00) 209 | 210 | def minikey_to_private_key(text): 211 | return sha256(text) 212 | 213 | 214 | ###################################### BIP32 ############################## 215 | 216 | BIP32_PRIME = 0x80000000 217 | 218 | 219 | def get_pubkeys_from_secret(secret): 220 | # public key 221 | pubkey = compress(privtopub(secret)) 222 | return pubkey, True 223 | 224 | def xprv_header(xtype): 225 | return bfh("%08x" % XPRV_HEADERS[xtype]) 226 | 227 | 228 | def xpub_header(xtype): 229 | return bfh("%08x" % XPUB_HEADERS[xtype]) -------------------------------------------------------------------------------- /cryptotool: -------------------------------------------------------------------------------- 1 | git#!/usr/bin/python 2 | from cryptos import * 3 | import json 4 | 5 | coins = { 6 | 'bch': BitcoinCash, 7 | 'btc': Bitcoin, 8 | 'dash': Dash, 9 | 'doge': Doge, 10 | 'ltc': Litecoin 11 | } 12 | 13 | if len(sys.argv) == 1: 14 | print("pybtctool ...") 15 | else: 16 | coin = Bitcoin 17 | testnet = False 18 | cmdargs, preargs, kwargs = [], [], {} 19 | i = 2 20 | # Process first arg tag 21 | if sys.argv[1] == '-s': 22 | preargs.extend(re.findall(r'\S\S*', sys.stdin.read())) 23 | elif sys.argv[1] == '-B': 24 | preargs.extend([sys.stdin.read()]) 25 | elif sys.argv[1] == '-b': 26 | preargs.extend([sys.stdin.read()[:-1]]) 27 | elif sys.argv[1] == '-j': 28 | preargs.extend([json.loads(sys.stdin.read())]) 29 | elif sys.argv[1] == '-J': 30 | preargs.extend(json.loads(sys.stdin.read())) 31 | else: 32 | i = 1 33 | while i < len(sys.argv): 34 | if sys.argv[i] == '--testnet': 35 | testnet = True 36 | i += 1 37 | elif sys.argv[i] == '--coin': 38 | coinname = sys.argv[i+1].lower() 39 | try: 40 | coin = coins[coinname] 41 | except KeyError: 42 | raise Exception('Coin %s not recognised. Choose from: %s ' % (coin, ','.join(coins.keys()))) 43 | i += 2 44 | elif sys.argv[i][:2] == '--': 45 | kwargs[sys.argv[i][2:]] = sys.argv[i+1] 46 | i += 2 47 | else: 48 | cmdargs.append(sys.argv[i]) 49 | i += 1 50 | cmd = cmdargs[0] 51 | args = preargs + cmdargs[1:] 52 | try: 53 | c = coin(testnet=testnet) 54 | o = getattr(c, cmd)(*args, **kwargs) 55 | except AttributeError: 56 | o = vars()[cmd](*args, **kwargs) 57 | if isinstance(o, (list, dict)): 58 | print(json.dumps(o)) 59 | else: 60 | print(o) 61 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | pbkdf2 3 | pycryptodomex 4 | aiorpcx 5 | certifi 6 | janus 7 | packaging 8 | typing_extensions -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup, find_packages 3 | 4 | setup(name='cryptos', 5 | version='2.0.9', 6 | description='Python Crypto Coin Tools', 7 | long_description=open('README.md').read(), 8 | long_description_content_type='text/markdown', 9 | author='Paul Martin', 10 | author_email='greatestloginnameever@gmail.com', 11 | url='http://github.com/primal100/pybitcointools', 12 | packages=find_packages(), 13 | include_package_data=True, 14 | install_requires=[ 15 | "requests", 16 | "pbkdf2", 17 | "pycryptodomex", 18 | "aiorpcx", 19 | "certifi", 20 | "janus", 21 | "packaging", 22 | "typing_extensions" 23 | ], 24 | classifiers=[ 25 | 'Development Status :: 5 - Production/Stable', 26 | 'Intended Audience :: Developers', 27 | 'Intended Audience :: Education', 28 | 'License :: OSI Approved :: MIT License', 29 | 'Operating System :: OS Independent', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 3', 32 | 'Topic :: Security :: Cryptography', 33 | ], 34 | entry_points=''' 35 | [console_scripts] 36 | broadcast=crypto_scripts.broadcast:main 37 | convert_private_key=crypto_scripts.convert_private_key:main 38 | create_private_key=crypto_scripts.create_private_key:main 39 | cryptosend=crypto_scripts.cryptosend:main 40 | explorer=crypto_scripts.explorer:main 41 | get_block_sizes=crypto_scripts.get_block_sizes:main 42 | subscribe=crypto_scripts.subscribe:main 43 | view_private_key_addresses=crypto_scripts.view_private_key_addresses:main 44 | ''' 45 | ) 46 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primal100/pybitcointools/e7c96bfe1f4be08a9f3c540e598a73dc20ca2462/tests/__init__.py -------------------------------------------------------------------------------- /tests/electrum_subscribe_mock_server.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import itertools 4 | import threading 5 | from concurrent.futures import Future 6 | from ssl import create_default_context, Purpose 7 | from typing import Dict, Union 8 | 9 | 10 | scripthash_status1 = "e1969d52d5c94cdc9f3839ef720eec70282ce4c76d3634d2bdf138e24b223dc8" 11 | scripthash_status2 = "e1969d52d5c94cdc9f3839ef720eec70282ce4c76d3634d2bdf138e24b223d44" 12 | scripthash_status3 = "e1969d52d5c94cdc9f3839ef720eec70282ce4c76d3634d2bdf138e24b223dxy" 13 | 14 | first_conn_scripthash = [scripthash_status1, scripthash_status2] 15 | second_conn_scripthash = [scripthash_status2, scripthash_status3] 16 | 17 | 18 | block1 = {'height': 2350325, 'hex': "0000ff3f7586812b8a8677342ceef85916c2667b63468a8d19d0604c2e000000000000005292d8eba79db851be100996f48147df69386b43bf7fcb5e3361cf46f9ea8ed8a3214463ffff001d51c56337"} 19 | block2 = {'height': 2350326, 'hex': "00004a2920c2d8311e12d3e35b8da48ad29b1254e0a0d2be1623d717f69000000000000001aaf14e207eea7f36cdc1cf92a8d43a5db2ac1ce22925e104ccedca3b5d2d26892544630194331933c10227"} 20 | block3 = {'height': 2350327, 'hex': "0000552920c2d8311e12d3e35b8da48ad29b1254e0a0d2be1623d717f69000000000000001aaf14e207eea7f36cdc1cf92a8d43a5db2ac1ce22925e104ccedca3b5d2d26892544630194331933c10227"} 21 | 22 | first_conn_blocks = [block1, block2] 23 | second_conn_blocks = [block2, block3] 24 | 25 | 26 | cycles = {} 27 | 28 | 29 | def reset_cycles() -> None: 30 | cycles['scripthash'] = itertools.cycle([first_conn_scripthash, second_conn_scripthash]) 31 | cycles['block'] = itertools.cycle([first_conn_blocks, second_conn_blocks]) 32 | 33 | 34 | reset_cycles() 35 | 36 | 37 | async def send_message(writer, data: Dict[str, Union[int, str, dict]]) -> None: 38 | data['jsonrpc'] = "2.0" 39 | 40 | out_message = (json.dumps(data) + "\n").encode() 41 | 42 | print('sending', out_message) 43 | 44 | writer.write(out_message) 45 | await writer.drain() 46 | 47 | 48 | async def handle_rpc(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): 49 | while not writer.transport.is_closing(): 50 | print('Server Reading data') 51 | msg = await reader.readline() 52 | print('Server received data', msg) 53 | if msg: 54 | data = msg.decode() 55 | 56 | data = json.loads(data) 57 | print("Server json data", data) 58 | msg_id = data['id'] 59 | method = data['method'] 60 | params = data.get('params', []) 61 | print("Server received method", method) 62 | if method == "server.version": 63 | response = {"result": ["ElectrumXMock 1.16.0", "1.4"], "id": msg_id} 64 | await send_message(writer, response) 65 | elif method == "server.ping": 66 | response = {"result": None, "id": msg_id} 67 | await send_message(writer, response) 68 | elif method == "blockchain.scripthash.subscribe": 69 | scripthash_status = next(cycles['scripthash']) 70 | response = { 71 | "result": scripthash_status[0], 72 | "id": msg_id 73 | } 74 | await send_message(writer, response) 75 | await asyncio.sleep(2) 76 | response = { 77 | "method": method, 78 | "params": params + [scripthash_status[1]] 79 | } 80 | await send_message(writer, response) 81 | elif method == "blockchain.headers.subscribe": 82 | blocks = next(cycles['block']) 83 | response = { 84 | "result": blocks[0], 85 | "id": msg_id} 86 | await send_message(writer, response) 87 | await asyncio.sleep(2) 88 | response = { 89 | "method": method, 90 | "params": [blocks[1]] 91 | } 92 | await send_message(writer, response) 93 | else: 94 | response = {"error": {"code": -32601, "message": f"Method {method} is not supported by this mock server", "id": msg_id}} 95 | await send_message(writer, response) 96 | else: 97 | print('Closing writer') 98 | writer.close() 99 | print('Server stopped while loop') 100 | 101 | 102 | async def get_server(fut: Union[asyncio.Future, Future], host: str = "127.0.0.1", ssl: bool = False): 103 | port = 44445 if ssl else 44444 104 | context = create_default_context(purpose=Purpose.SERVER_AUTH) if ssl else None 105 | server = await asyncio.start_server( 106 | handle_rpc, host, port, ssl=context) 107 | 108 | sock = server.sockets[0] 109 | listening_on = sock.getsockname() 110 | 111 | asyncio.get_running_loop().call_soon(fut.set_result, listening_on) 112 | 113 | return server 114 | 115 | 116 | async def run_server(fut: asyncio.Future, host: str = "127.0.0.1", ssl: bool = False): 117 | server = await get_server(fut, host=host, ssl=ssl) 118 | async with server: 119 | await server.serve_forever() 120 | 121 | 122 | async def run_server_until_cancelled(fut: Future, stop_fut: Future, host: str = "127.0.0.1", ssl: bool = False): 123 | stop_fut_async = asyncio.wrap_future(stop_fut) 124 | server = await get_server(fut, host=host, ssl=ssl) 125 | async with server: 126 | await asyncio.wait([asyncio.create_task(server.serve_forever()), stop_fut_async], return_when=asyncio.FIRST_COMPLETED) 127 | 128 | 129 | def run_in_loop(fut: Future, stop_fut: Future, host: str = '127.0.0.1', ssl: bool = False): 130 | asyncio.run(run_server_until_cancelled(fut, stop_fut, host=host, ssl=ssl)) 131 | 132 | 133 | def run_server_in_thread(stop_fut: Future, host: str = '127.0.0.1', ssl: bool = False): 134 | fut = Future() 135 | thread = threading.Thread(target=run_in_loop, args=(fut, stop_fut), kwargs={'host': host, 'ssl': ssl}, daemon=True) 136 | thread.start() 137 | return fut.result(10) 138 | -------------------------------------------------------------------------------- /tests/test_coins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primal100/pybitcointools/e7c96bfe1f4be08a9f3c540e598a73dc20ca2462/tests/test_coins/__init__.py -------------------------------------------------------------------------------- /tests/test_coins_async/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primal100/pybitcointools/e7c96bfe1f4be08a9f3c540e598a73dc20ca2462/tests/test_coins_async/__init__.py -------------------------------------------------------------------------------- /tests/test_coins_async/test_bitcoin_cash.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from binascii import unhexlify 3 | from cryptos import coins_async 4 | from cryptos import cashaddr 5 | from cryptos.testing.testcases_async import BaseAsyncCoinTestCase 6 | from cryptos.electrumx_client.types import ElectrumXTx, ElectrumXMultiBalanceResponse 7 | from cryptos.main import privtopub 8 | from cryptos.types import TxInput 9 | from typing import List, Type 10 | from unittest import mock 11 | 12 | 13 | class TestBitcoinCash(BaseAsyncCoinTestCase): 14 | name: str = "Bitcoin Cash" 15 | coin: Type[coins_async.BaseCoin] = coins_async.BitcoinCash 16 | addresses = ["1MhTCMUjM1TEQ7RSwoCMVZy7ARa9aAP82Z", "" 17 | "18DEbpqpdmfNaosxwQhCNHDAChZYCNG836", 18 | "175MvCJkNZT3zSdCntXj9vK7L6XKDWjLnD"] # n2DQVQZiA2tVBDu4fNAjKVBS2RArWhfncv 19 | cash_addresses = ["bitcoincash:qr3sjptscfm7kqry6s67skm5dgsudwkmcsfvmsq7c6", 20 | "bitcoincash:qp83jwvlc8clct6vpskr8jhyayr8u7ynhqf8z4glc3", 21 | "bitcoincash:qpp28cg6sze9la3myp6v28ghg5fjhn9m5ynaj2uu6x"] 22 | multisig_addresses = ["35D72hVBzYXqNkyN63z28FHmSyPKuJh9Q2", "32tuh24PcKWQWfWitfp9NVhRuYjDKG7vCH"] 23 | cash_multisig_addresses = ["bitcoincash:pqnfj8jmtpj30fnjgc2gy0gs4l6sptdyhc84ukmr52", 24 | "bitcoincash:pqxn06syr9twx9ecx892alre33yuuwn2gu7z0p7lzz"] 25 | privkeys: List[str] = ["098ddf01ebb71ead01fc52cb4ad1f5cafffb5f2d052dd233b3cad18e255e1db1", 26 | "0861e1bb62504f5e9f03b59308005a6f2c12c34df108c6f7c52e5e712a08e91401", 27 | "c396c62dfdc529645b822dc4eaa7b9ddc97dd8424de09ca19decce61e6732f71"] 28 | privkey_standard_wifs: List[str] = ['5HtVdWeLBUpSyqqSnnJoQnCiuc33Kg86i8V1gc1tKK13tw1Cqrg', 29 | "KwW1FKxkfefDyVStxvKH9qCCb9qaiFXBFZUy2mPLvTMap2f5YaXR", 30 | "5KJRe1vYXbE4hhvNjJjPX6iS1tqpksNKHChrQjzyYVDgh9Z8H5o"] 31 | fee: int = 500 32 | max_fee: int = fee 33 | testnet: bool = False 34 | unspent_addresses: List[str] = ["1KomPE4JdF7P4tBzb12cyqbBfrVp4WYxNS"] 35 | unspent_cash_addresses: List[str] = ["bitcoincash:qr8y5u55y3j9lsyk0rsmsvnm03udrnplcg6jpj24wk"] 36 | balance: ElectrumXMultiBalanceResponse = {'confirmed': 249077026, 'unconfirmed': 0} 37 | balances: List[ElectrumXMultiBalanceResponse] = [{'address': unspent_addresses[0]} | dict(balance)] 38 | unspent: List[ElectrumXTx] = [{'address': '1KomPE4JdF7P4tBzb12cyqbBfrVp4WYxNS', 39 | 'height': 508381, 40 | 'tx_hash': 'e3ead2c8e6ad22b38f49abd5ae7a29105f0f64d19865fd8ccb0f8d5b2665f476', 41 | 'tx_pos': 1, 42 | 'value': 249077026}] 43 | unspents: List[ElectrumXTx] = unspent 44 | history: List[ElectrumXTx] = [{'height': 508381, 'tx_hash': 'e3ead2c8e6ad22b38f49abd5ae7a29105f0f64d19865fd8ccb0f8d5b2665f476'}] 45 | histories: List[ElectrumXTx] = [{'address': unspent_addresses[0]} | dict(history[0])] 46 | min_latest_height: int = 129055 47 | txheight: int = 509045 48 | block_hash: str = "0000000000000000006d011e3ab462725dad9d4e8d1a7398bcc2895defd1fa3f" 49 | txinputs: List[TxInput] = [{'output': 'b3105972beef05e88cf112fd9718d32c270773462d62e4659dc9b4a2baafc038:0', 'value': 1157763509}] 50 | txid: str = "e3ead2c8e6ad22b38f49abd5ae7a29105f0f64d19865fd8ccb0f8d5b2665f476" 51 | raw_tx: str = "0200000001ab293b56edcc8d99b665ebe6265132408df132ecf7a1948d68bee425cef7bb63010000006b483045022100fca8f51dc515e85862cd087729136656e4f73f76eb1ce9d4ce90b092e4b9efea02204943929a08bab03e95dad6781e128f49a3d35af055a146a9c8e4aec3a4c90db54121039b190dc5e0bcea42cec072f7aebf097f379691b3dfcc67fd587dddc1d004eaa4feffffff0222ec4377000000001976a9149119c4f8dc64fde6e9d6f59ae9273993b858c03388ac229dd80e000000001976a914ce4a729424645fc09678e1b8327b7c78d1cc3fc288acdac10700" 52 | 53 | def test_cash_addr(self): 54 | # https://reference.cash/protocol/blockchain/encoding/cashaddr 55 | public_key_hash = unhexlify("211b74ca4686f81efda5641767fc84ef16dafe0b") 56 | addr = cashaddr.encode_full(self._coin.cash_hrp, 0, public_key_hash) 57 | self.assertEqual(addr, "bitcoincash:qqs3kax2g6r0s8ha54jpwelusnh3dkh7pvu23rzrru") 58 | 59 | def test_address_conversion(self): 60 | for addr, cashaddr in zip(self.addresses, self.cash_addresses): 61 | convert_cashaddr = self._coin.legacy_addr_to_cash_address(addr) 62 | self.assertEqual(convert_cashaddr, cashaddr) 63 | convert_addr = self._coin.cash_address_to_legacy_addr(cashaddr) 64 | self.assertEqual(addr, convert_addr) 65 | 66 | def test_cash_address_multisig_ok(self): 67 | pubs = [privtopub(priv) for priv in self.privkeys] 68 | script1, address1 = self._coin.mk_multsig_cash_address(*pubs, num_required=2) 69 | self.assertEqual(address1, self.cash_multisig_addresses[0]) 70 | pubs2 = pubs[0:2] 71 | script2, address2 = self._coin.mk_multsig_cash_address(*pubs2) 72 | self.assertEqual(address2, self.cash_multisig_addresses[1]) 73 | 74 | def test_address_conversion_multisig(self): 75 | for addr, cashaddr in zip(self.multisig_addresses, self.cash_multisig_addresses): 76 | convert_cashaddr = self._coin.legacy_addr_to_cash_address(addr) 77 | self.assertEqual(convert_cashaddr, cashaddr) 78 | convert_addr = self._coin.cash_address_to_legacy_addr(cashaddr) 79 | self.assertEqual(addr, convert_addr) 80 | 81 | def test_standard_wif_ok(self): 82 | self.assertStandardWifOK() 83 | for privkey, addr in zip(self.privkeys, self.cash_addresses): 84 | cash_addr = self._coin.privtocashaddress(privkey) 85 | self.assertEqual(cash_addr, addr) 86 | 87 | @unittest.skip("Unreliable") 88 | async def test_balance(self): 89 | await self.assertBalanceOK() 90 | 91 | @unittest.skip("Address needs updating") 92 | async def test_balance_cash_address(self): 93 | result = await self._coin.get_balance(self.unspent_cash_addresses[0]) 94 | self.assertEqual(self.balance, result) 95 | 96 | async def test_balances(self): 97 | await self.assertBalancesOK() 98 | 99 | async def test_unspent(self): 100 | await self.assertUnspentOK() 101 | 102 | @unittest.skip("Unreliable") 103 | async def test_unspents(self): 104 | await self.assertUnspentsOK() 105 | 106 | async def test_merkle_proof(self): 107 | await self.assertMerkleProofOK() 108 | 109 | @unittest.skip("Intermittent Failures") 110 | async def test_history(self): 111 | await self.assertHistoryOK() 112 | 113 | async def test_histories(self): 114 | await self.assertHistoriesOK() 115 | 116 | @unittest.skip('Out of range error returned, not sure why') 117 | async def test_block_header(self): 118 | await self.assertBlockHeaderOK() 119 | 120 | async def test_block_headers(self): 121 | await self.assertBlockHeadersOK() 122 | 123 | async def test_gettx(self): 124 | await self.assertGetTXOK() 125 | 126 | @unittest.skip('Transaction not found') 127 | async def test_getverbosetx(self): 128 | await self.assertGetVerboseTXOK() 129 | 130 | async def test_gettxs(self): 131 | await self.assertTxsOK() 132 | 133 | @unittest.skip("Test address needs updating") 134 | async def test_balance_merkle_proven(self): 135 | await self.assertBalanceMerkleProvenOK() 136 | 137 | async def test_balances_merkle_proven(self): 138 | await self.assertBalancesMerkleProvenOK() 139 | 140 | async def test_transaction(self): 141 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 142 | side_effect=self.mock_electrumx_send_request): 143 | await self.assertTransactionOK("744e0a28ed754ceb551a4fc3a45f57f0b3bd34ff415c3724a2ce774ccc24eff9") 144 | 145 | async def test_transaction_cash_address(self): 146 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 147 | side_effect=self.mock_electrumx_send_request): 148 | await self.assertCashAddressTransactionOK( 149 | "744e0a28ed754ceb551a4fc3a45f57f0b3bd34ff415c3724a2ce774ccc24eff9") 150 | 151 | async def test_transaction_multisig(self): 152 | # 010000000176f465265b8d0fcb8cfd6598d1640f5f10297aaed5ab498fb322ade6c8d2eae301000000fd3d01004830450221009a556d396ad8ceec4cbb1cacda67933818deac5210a96a445ac13e97fe8c476102202682d3d25c62e23865b00e1c3ea36c53a2691c64dd43b04de76a35ecd64e5364414730440220045fd2daec62375ffe538c8758ab1461b4d7cc430ec24c57883a6048006daa1a022025d1dec4c9a36000539c283fb1be428b317b66e71ca29fa964efa4d287032f1b414ca9524104de476e251a827e58199ed4d6d7c2177f0a97a2dda150d7a9e59fc5682519eb94d37bc387edff66e7b0f16e92dd045fe968d63e1f203613b76ad733e5cdf8e818210391ed6bf1e0842997938ea2706480a7085b8bb253268fd12ea83a68509602b6e0410415991434e628402bebcbaa3261864309d2c6fd10c850462b9ef0258832822d35aa26e62e629d2337e3716784ca6c727c73e9600436ded7417d957318dc7a41eb53aeffffffff02914e6c070000000017a9140d37ea041956e3173831caaefc798c49ce3a6a4787514d6c070000000017a91426991e5b586517a6724614823d10aff500ada4be8700000000 153 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 154 | side_effect=self.mock_electrumx_send_request): 155 | await self.assertMultiSigTransactionOK("9a44b27e7d03361ab66dff6bb9ce49a743b1e91123610dca94099ce92c50eeed") 156 | 157 | async def test_transaction_multisig_cash(self): 158 | # 010000000176f465265b8d0fcb8cfd6598d1640f5f10297aaed5ab498fb322ade6c8d2eae301000000fd3d0100473044022055dc9295816ce57fc78cd5e5b2e49d7811073e31532508a887e5acc562752e3a0220304788647824903c9fe0d10df3e0379391c85f572e15e966eac6744f3623473f41483045022100fa2a6222842a8102ab944cf9e0e282e907c3699921487f7da350a8879b45064c02202ab2a4eca7adb31c40b4210b8ff42171fad87370a8f84adbae5ceb694607f62e414ca9524104de476e251a827e58199ed4d6d7c2177f0a97a2dda150d7a9e59fc5682519eb94d37bc387edff66e7b0f16e92dd045fe968d63e1f203613b76ad733e5cdf8e818210391ed6bf1e0842997938ea2706480a7085b8bb253268fd12ea83a68509602b6e0410415991434e628402bebcbaa3261864309d2c6fd10c850462b9ef0258832822d35aa26e62e629d2337e3716784ca6c727c73e9600436ded7417d957318dc7a41eb53aeffffffff02914e6c070000000017a9140d37ea041956e3173831caaefc798c49ce3a6a4787117a6b070000000017a91426991e5b586517a6724614823d10aff500ada4be8700000000 159 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 160 | side_effect=self.mock_electrumx_send_request): 161 | await self.assertCashAddressMultiSigTransactionOK("9a44b27e7d03361ab66dff6bb9ce49a743b1e91123610dca94099ce92c50eeed") 162 | 163 | 164 | async def test_sendmulti_recipient_tx(self): 165 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 166 | side_effect=self.mock_electrumx_send_request): 167 | await self.assertSendMultiRecipientsTXOK("2f5046ed857685a08ffb642a8e352b7cdb5cc6b981d690086fb337b868f26dcd") 168 | 169 | async def test_send(self): 170 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 171 | side_effect=self.mock_electrumx_send_request): 172 | await self.assertSendOK("9fea356516a2cb97b2242fb6b468f4f9264c26977e456b643cc787dc0f3480c0") 173 | 174 | async def test_subscribe_block_headers(self): 175 | await self.assertSubscribeBlockHeadersOK() 176 | 177 | async def test_subscribe_block_headers_sync(self): 178 | await self.assertSubscribeBlockHeadersSyncCallbackOK() 179 | 180 | async def test_latest_block(self): 181 | await self.assertLatestBlockOK() 182 | 183 | async def test_confirmations(self): 184 | await self.assertConfirmationsOK() 185 | -------------------------------------------------------------------------------- /tests/test_coins_async/test_bitcoin_cash_testnet.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cryptos import coins_async 4 | from cryptos.main import privtopub 5 | from cryptos.testing.testcases_async import BaseAsyncCoinTestCase 6 | from cryptos.types import ElectrumXTx, TxOut 7 | from cryptos.electrumx_client.types import ElectrumXMultiBalanceResponse 8 | from typing import List, Type 9 | 10 | 11 | class TestBitcoinCashTestnet(BaseAsyncCoinTestCase): 12 | name = "Bitcoin Cash Testnet" 13 | coin: Type[coins_async.BaseCoin] = coins_async.BitcoinCash 14 | addresses: List[str] = ["n2DQVQZiA2tVBDu4fNAjKVBS2RArWhfncv", 15 | "mnjBtsvoSo6dMvMaeyfaCCRV4hAF8WA2cu", 16 | "mmbKDFPjBatJmZ6pWTW6yqXSC6826YLBX6"] 17 | cash_addresses = ["bchtest:qr3sjptscfm7kqry6s67skm5dgsudwkmcsd7lhzflx", 18 | "bchtest:qp83jwvlc8clct6vpskr8jhyayr8u7ynhqd4xj2gld", 19 | "bchtest:qpp28cg6sze9la3myp6v28ghg5fjhn9m5yh0kd7ta6"] 20 | privkeys: List[str] = [ 21 | "098ddf01ebb71ead01fc52cb4ad1f5cafffb5f2d052dd233b3cad18e255e1db1", 22 | "0861e1bb62504f5e9f03b59308005a6f2c12c34df108c6f7c52e5e712a08e91401", 23 | "c396c62dfdc529645b822dc4eaa7b9ddc97dd8424de09ca19decce61e6732f71"] # Private keys for above address_derivations in same order 24 | privkey_standard_wifs: List[str] = ['91f8DFTsmhtawuLjR8CiHNkgZGPkUqfJ45LxmENPf3k6fuX1m4N', 25 | 'cMrziExc6iMV8vvAML8QX9hGDP8zNhcsKbdS9BqrRa1b4mhKvK6f', 26 | "9354Dkk67pJCfmRfMedJPhGPfZCXv2uWd9ZoVNMUtDxjUBbCVZK"] 27 | multisig_addresses: List[str] = ["2MvmK6SRDc13BaYbumBbtkCH2fKbViC5XEv", "2MtT7kkzRDn1kiT9GZoS1zSgh7twP145Qif"] 28 | cash_multisig_addresses: List[str] = ["bchtest:pqnfj8jmtpj30fnjgc2gy0gs4l6sptdyhcr8c3e5nk", 29 | "bchtest:pqxn06syr9twx9ecx892alre33yuuwn2gu6stxug97"] 30 | fee: int = 1000 31 | max_fee: int = 3500 32 | testnet: bool = True 33 | 34 | min_latest_height: int = 1524427 35 | unspent_addresses: List[str] = ["ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA"] 36 | unspent_cash_addresses: List[str] = ['bchtest:qpl9sk4pjy70zt5efr5s7ecc3m5j2r242c4czjmhfy'] 37 | unspent: List[ElectrumXTx] = [{'address': 'ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA', 38 | 'height': 1196454, 39 | 'tx_hash': '80700e6d1125deafa22b307f6c7c99e75771f9fc05517fc795a1344eca7c8472', 40 | 'tx_pos': 0, 41 | 'value': 550000000}] 42 | unspents: List[ElectrumXTx] = unspent 43 | balance: ElectrumXMultiBalanceResponse = {'confirmed': 550000000, 'unconfirmed': 0} 44 | balances: List[ElectrumXMultiBalanceResponse] = [{'address': unspent_addresses[0]} | dict(balance)] 45 | history: List[ElectrumXTx] = [{'height': 1196454, 'tx_hash': '80700e6d1125deafa22b307f6c7c99e75771f9fc05517fc795a1344eca7c8472'}] 46 | histories: List[ElectrumXTx] = [{'address': unspent_addresses[0]} | dict(history[0])] 47 | txid: str = "b4dd5908cca851d861b9d2ca267a901bb6f581f2bb096fbf42a28cc2d98e866a" 48 | txheight: int = 1196454 49 | block_hash: str = "000000002bab447cbd0c60829a80051e320aa6308d578db3369eb85b2ebb9f46" 50 | txinputs: List[TxOut] = [{'output': "cbd43131ee11bc9e05f36f55088ede26ab5fb160cc3ff11785ce9cc653aa414b:1", 'value': 96190578808}] 51 | raw_tx: str = "01000000014b41aa53c69cce8517f13fcc60b15fab26de8e08556ff3059ebc11ee3131d4cb010000006b483045022100b9050a1d58f36a771c4e0869900fb0474b809b134fdad566742e5b3a0ed7580d022065b80e9cc2bc9b921a9b0aad12228d9967345959b021214dbe60b3ffa44dbf0e412102ae83c12f8e2a686fb6ebb25a9ebe39fcd71d981cc6c172fedcdd042536a328f2ffffffff0200ab9041000000001976a914c384950342cb6f8df55175b48586838b03130fad88acd88ed523160000001976a9143479daa7de5c6d8dad24535e648861d4e7e3f7e688ac00000000" 52 | 53 | def test_address_conversion(self): 54 | for addr, cashaddr in zip(self.addresses, self.cash_addresses): 55 | convert_cashaddr = self._coin.legacy_addr_to_cash_address(addr) 56 | self.assertEqual(convert_cashaddr, cashaddr) 57 | convert_addr = self._coin.cash_address_to_legacy_addr(cashaddr) 58 | self.assertEqual(addr, convert_addr) 59 | 60 | def test_cash_address_multisig_ok(self): 61 | pubs = [privtopub(priv) for priv in self.privkeys] 62 | script1, address1 = self._coin.mk_multsig_cash_address(*pubs, num_required=2) 63 | self.assertEqual(address1, self.cash_multisig_addresses[0]) 64 | pubs2 = pubs[0:2] 65 | script2, address2 = self._coin.mk_multsig_cash_address(*pubs2) 66 | self.assertEqual(address2, self.cash_multisig_addresses[1]) 67 | 68 | def test_address_conversion_multisig(self): 69 | for addr, cashaddr in zip(self.multisig_addresses, self.cash_multisig_addresses): 70 | convert_cashaddr = self._coin.legacy_addr_to_cash_address(addr) 71 | self.assertEqual(convert_cashaddr, cashaddr) 72 | convert_addr = self._coin.cash_address_to_legacy_addr(cashaddr) 73 | self.assertEqual(addr, convert_addr) 74 | 75 | def test_standard_wif_ok(self): 76 | self.assertStandardWifOK() 77 | for privkey, addr in zip(self.privkeys, self.cash_addresses): 78 | cash_addr = self._coin.privtocashaddress(privkey) 79 | self.assertEqual(cash_addr, addr) 80 | 81 | async def test_balance(self): 82 | await self.assertBalanceOK() 83 | 84 | async def test_balance_cash_address(self): 85 | result = await self._coin.get_balance(self.unspent_cash_addresses[0]) 86 | self.assertEqual(self.balance, result) 87 | 88 | async def test_balances(self): 89 | await self.assertBalancesOK() 90 | 91 | async def test_unspent(self): 92 | await self.assertUnspentOK() 93 | 94 | async def test_unspents(self): 95 | await self.assertUnspentsOK() 96 | 97 | async def test_merkle_proof(self): 98 | await self.assertMerkleProofOK() 99 | 100 | async def test_history(self): 101 | await self.assertHistoryOK() 102 | 103 | async def test_histories(self): 104 | await self.assertHistoriesOK() 105 | 106 | async def test_block_header(self): 107 | await self.assertBlockHeaderOK() 108 | 109 | async def test_block_headers(self): 110 | await self.assertBlockHeadersOK() 111 | 112 | async def test_gettx(self): 113 | await self.assertGetTXOK() 114 | 115 | @unittest.skip('Intermittent failures') 116 | async def test_getverbosetx(self): 117 | await self.assertGetVerboseTXOK() 118 | 119 | async def test_gettxs(self): 120 | await self.assertGetTxsOK() 121 | 122 | async def test_balance_merkle_proven(self): 123 | await self.assertBalanceMerkleProvenOK() 124 | 125 | async def test_balances_merkle_proven(self): 126 | await self.assertBalancesMerkleProvenOK() 127 | 128 | async def test_transaction(self): 129 | """ 130 | Sample transaction: 131 | TxID: d4e8e93ba458c675270a5e6ac6772e35356ec95c37f8de6eb4a7a74103ecac8a 132 | """ 133 | await self.assertTransactionOK() 134 | 135 | async def test_transaction_cash_address(self): 136 | """ 137 | Sample transaction: 138 | TxID: 1ec96ce25a0104cda556f16d0d630768308a6b14dd35363dabe62fa96aa3237a 139 | """ 140 | await self.assertCashAddressTransactionOK() 141 | 142 | async def test_transaction_multisig(self): 143 | """ 144 | Sample transaction: 145 | TxID: c8987d59357f108fff46837b9309b28a1dc91d0fa4daa2c2f515107f61943a05 146 | """ 147 | await self.assertMultiSigTransactionOK() 148 | 149 | async def test_transaction_multisig_cash_address(self): 150 | """ 151 | Sample transaction: 152 | TxID: eb8a0ad9434786bfe69c992f286cbe391c4845058ae130f8a35e205e3280c6d4 153 | """ 154 | await self.assertCashAddressMultiSigTransactionOK() 155 | 156 | async def test_sendmulti_recipient_tx(self): 157 | """ 158 | Sample transaction: 159 | TxID: 49a731c4eaae1c6570590cd3eb5f4af4d4f6b282186b368b574169e9a7d576ab 160 | """ 161 | await self.assertSendMultiRecipientsTXOK() 162 | 163 | async def test_send(self): 164 | """ 165 | Sample transaction: 166 | TxID: ae9dd16e61521791659080c58299937a5d0ac2d20608e8bc1f494a08f6d9f5fb 167 | """ 168 | await self.assertSendOK() 169 | 170 | async def test_subscribe_block_headers(self): 171 | await self.assertSubscribeBlockHeadersOK() 172 | 173 | async def test_subscribe_block_headers_sync(self): 174 | await self.assertSubscribeBlockHeadersSyncCallbackOK() 175 | 176 | async def test_latest_block(self): 177 | await self.assertLatestBlockOK() 178 | 179 | async def test_confirmations(self): 180 | await self.assertConfirmationsOK() 181 | 182 | async def test_subscribe_address(self): 183 | await self.assertSubscribeAddressOK() 184 | 185 | async def test_subscribe_address_sync(self): 186 | await self.assertSubscribeAddressSyncCallbackOK() 187 | 188 | async def test_subscribe_address_transactions(self): 189 | await self.assertSubscribeAddressTransactionsOK() 190 | 191 | async def test_subscribe_address_transactions_sync(self): 192 | await self.assertSubscribeAddressTransactionsSyncOK() 193 | -------------------------------------------------------------------------------- /tests/test_coins_async/test_bitcoin_testnet.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cryptos import coins_async 4 | from cryptos.testing.testcases_async import BaseAsyncCoinTestCase 5 | from cryptos.electrumx_client.types import ElectrumXTx, ElectrumXMultiBalanceResponse 6 | from typing import List, Type 7 | 8 | 9 | class TestBitcoinTestnet(BaseAsyncCoinTestCase): 10 | name: str = "Bitcoin Testnet" 11 | coin: Type[coins_async.BaseCoin] = coins_async.Bitcoin 12 | addresses: List[str] = ["n2DQVQZiA2tVBDu4fNAjKVBS2RArWhfncv", 13 | "mnjBtsvoSo6dMvMaeyfaCCRV4hAF8WA2cu", 14 | "mmbKDFPjBatJmZ6pWTW6yqXSC6826YLBX6"] 15 | segwit_addresses: List[str] = ["2N74sauceDn2qeHFJuNfJ3c1anxPcDRrVtz", 16 | "2NDpBxpK4obuGiFodKtYe3dXx14aPwDBPGU", 17 | "2Mt2f4knFtjLZz9CW2979Hw3tYiAYd6WcA1"] 18 | native_segwit_addresses: List[str] = ["tb1q95cgql39zvtc57g4vn8ytzmlvtt43skngdq0ue", 19 | "tb1qfuvnn87p787z7nqv9seu4e8fqel83yacg7yf2r", 20 | "tb1qst3pkm860tjt9y70ugnaluqyqnfa7h54ekyj66"] 21 | multisig_addresses: List[str] = ["2MvmK6SRDc13BaYbumBbtkCH2fKbViC5XEv", 22 | "2MtT7kkzRDn1kiT9GZoS1zSgh7twP145Qif"] 23 | native_segwit_multisig_addresses: List[str] = [ 24 | "tb1q7e42a8gmgp5d7kw8myrruj5qnxp8edp7v5y0y95hrjz6t530uehqkj0tl2", 25 | "tb1qu7fz4uku8kh6tg7qghj7rnf88g858lal258gzfu85kx7vc5h0qpsyxrfnp" 26 | ] 27 | privkeys: List[str] = ["098ddf01ebb71ead01fc52cb4ad1f5cafffb5f2d052dd233b3cad18e255e1db1", 28 | "0861e1bb62504f5e9f03b59308005a6f2c12c34df108c6f7c52e5e712a08e91401", 29 | "c396c62dfdc529645b822dc4eaa7b9ddc97dd8424de09ca19decce61e6732f71"] 30 | public_keys: List[str] = [ 31 | "04de476e251a827e58199ed4d6d7c2177f0a97a2dda150d7a9e59fc5682519eb94d37bc387edff66e7b0f16e92dd045fe968d63e1f203613b76ad733e5cdf8e818", 32 | "0391ed6bf1e0842997938ea2706480a7085b8bb253268fd12ea83a68509602b6e0", 33 | "0415991434e628402bebcbaa3261864309d2c6fd10c850462b9ef0258832822d35aa26e62e629d2337e3716784ca6c727c73e9600436ded7417d957318dc7a41eb" 34 | ] 35 | privkey_standard_wifs: List[str] = ['91f8DFTsmhtawuLjR8CiHNkgZGPkUqfJ45LxmENPf3k6fuX1m4N', 36 | 'cMrziExc6iMV8vvAML8QX9hGDP8zNhcsKbdS9BqrRa1b4mhKvK6f', 37 | "9354Dkk67pJCfmRfMedJPhGPfZCXv2uWd9ZoVNMUtDxjUBbCVZK"] 38 | privkey_segwit_wifs: List[str] = ['cf4XyPZRSdJxnN3dKdaUodCn6HLiaiKUbTRNF8hbytcebJFytuLg', 39 | 'cf2FrZgQT2Bm5xwhTUvC7VtEiFYJ3YAKeUshG6Y3QXX1dSAZ9s9h', 40 | "cmJAMcnywPbCT97PQHs1MWuuY3XD7uxE5S43K7wb43iR1Axqeupz"] 41 | privkey_native_segwit_wifs: List[str] = ["cWUuQECXGUPpor2rmZBb1T7Hqr94kJ3kS1oEggMWVQrwJy3wWMF4", 42 | "cWSdHQKWGsGd7SvvuQXJKKnkTpLeD7tbV3FZheBwv3mJM6yc95xc", 43 | "cciXnTS5mEg4Ud6crDU7ZLpRHcKZHVgVuzRukfbVZZxhiqfSyfBH"] 44 | fee: int = 1500 45 | max_fee: int = 3500 46 | testnet: bool = True 47 | min_latest_height: int = 1258030 48 | 49 | unspent_addresses: List[str] = ["ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA", "2MwHtiGJJqcFgNnbCu1REVy5ooDEeAAFXMy", 50 | "tb1qjap2aae2tsky3ctlh48yltev0sjdmx92yk76wq"] 51 | unspent: List[ElectrumXTx] = [{'tx_hash': '1d69dd7a23f18d86f514ff7d8ef85894ad00c61fb29f3f7597e9834ac2569c8c', 52 | 'tx_pos': 0, 'height': 1238008, 'value': 180000000, 53 | 'address': 'ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA'}] 54 | unspents: List[ElectrumXTx] = [ 55 | {'tx_hash': '1d69dd7a23f18d86f514ff7d8ef85894ad00c61fb29f3f7597e9834ac2569c8c', 'tx_pos': 0, 'height': 1238008, 56 | 'value': 180000000, 'address': 'ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA'}, 57 | {'tx_hash': '70bd4ce0e4cf2977ab53e767865da21483977cdb94b1a36eb68d30829c9c392f', 'tx_pos': 1, 'height': 1275633, 58 | 'value': 173980000, 'address': '2MwHtiGJJqcFgNnbCu1REVy5ooDEeAAFXMy'}, 59 | {'tx_hash': '70bd4ce0e4cf2977ab53e767865da21483977cdb94b1a36eb68d30829c9c392f', 'tx_pos': 0, 'height': 1275633, 60 | 'value': 6000000, 'address': 'tb1qjap2aae2tsky3ctlh48yltev0sjdmx92yk76wq'}] 61 | balance: ElectrumXMultiBalanceResponse = {'confirmed': 180000000, 'unconfirmed': 0} 62 | balances: List[ElectrumXMultiBalanceResponse] = [ 63 | {'confirmed': 180000000, 'unconfirmed': 0, 'address': 'ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA'}, 64 | {'confirmed': 173980000, 'unconfirmed': 0, 'address': '2MwHtiGJJqcFgNnbCu1REVy5ooDEeAAFXMy'}, 65 | {'confirmed': 6000000, 'unconfirmed': 0, 'address': 'tb1qjap2aae2tsky3ctlh48yltev0sjdmx92yk76wq'}] 66 | history: List[ElectrumXTx] = [ 67 | {'tx_hash': '1d69dd7a23f18d86f514ff7d8ef85894ad00c61fb29f3f7597e9834ac2569c8c', 'height': 1238008}] 68 | histories: List[ElectrumXTx] = [ 69 | {'tx_hash': '1d69dd7a23f18d86f514ff7d8ef85894ad00c61fb29f3f7597e9834ac2569c8c', 'height': 1238008, 70 | 'address': 'ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA'}, 71 | {'tx_hash': 'e25d8f4036e44159b0364b45867e08ae47a57dda68ba800ba8abe1fb2dc54a40', 'height': 1275633, 72 | 'address': '2MwHtiGJJqcFgNnbCu1REVy5ooDEeAAFXMy'}, 73 | {'tx_hash': '70bd4ce0e4cf2977ab53e767865da21483977cdb94b1a36eb68d30829c9c392f', 'height': 1275633, 74 | 'address': '2MwHtiGJJqcFgNnbCu1REVy5ooDEeAAFXMy'}, 75 | {'tx_hash': '70bd4ce0e4cf2977ab53e767865da21483977cdb94b1a36eb68d30829c9c392f', 'height': 1275633, 76 | 'address': 'tb1qjap2aae2tsky3ctlh48yltev0sjdmx92yk76wq'}] 77 | txid: str = "1d69dd7a23f18d86f514ff7d8ef85894ad00c61fb29f3f7597e9834ac2569c8c" 78 | txheight: int = 1238008 79 | block_hash: str = "00000000000ac694c157a56de45e2f985adefda11d3e2d7375905a03950852df" 80 | raw_tx: str = "01000000000101333ae299a6c6353d88e6540c67d8c82281259bc29b3313bcbc9b62a9a7e78a1b0100000017160014ffe21a1b058e7f8dedfcc3f1526f82303cff4fc7ffffffff020095ba0a000000001976a9147e585aa1913cf12e9948e90f67188ee9250d555688acfcb92b4d2c00000017a914e223701f10c2a5e7782ef6e10a2560f4c6e968a2870247304402207f2aa4118eee2ef231eab3afcbf6b01b4c1ca3672bd87a3864cf405741bd2c1d02202ab7502cbc50126f68cb2b366e5b3799d3ec0a3359c6a895a730a6891c7bcae10121023c13734016f27089393f9fc79736e4dca1f27725c68e720e1855202f3fbf037e00000000" 81 | 82 | def test_standard_wif_ok(self): 83 | self.assertStandardWifOK() 84 | 85 | def test_p2wpkh_p2sh_wif_ok(self): 86 | self.assertP2WPKH_P2SH_WifOK() 87 | 88 | def test_p2wpkh_wif_ok(self): 89 | self.assertP2WPKH_WIFOK() 90 | 91 | async def test_balance(self): 92 | await self.assertBalanceOK() 93 | 94 | async def test_balances(self): 95 | await self.assertBalancesOK() 96 | 97 | async def test_unspent(self): 98 | await self.assertUnspentOK() 99 | 100 | async def test_unspents(self): 101 | await self.assertUnspentsOK() 102 | 103 | async def test_merkle_proof(self): 104 | await self.assertMerkleProofOK() 105 | 106 | async def test_history(self): 107 | await self.assertHistoryOK() 108 | 109 | async def test_histories(self): 110 | await self.assertHistoriesOK() 111 | 112 | async def test_balance_merkle_proven(self): 113 | await self.assertBalanceMerkleProvenOK() 114 | 115 | async def test_balances_merkle_proven(self): 116 | await self.assertBalancesMerkleProvenOK() 117 | 118 | async def test_block_header(self): 119 | await self.assertBlockHeaderOK() 120 | 121 | async def test_block_headers(self): 122 | await self.assertBlockHeadersOK() 123 | 124 | async def test_gettx(self): 125 | await self.assertGetSegwitTXOK() 126 | 127 | @unittest.skip('Intermittent failures') 128 | async def test_getverbosetx(self): 129 | await self.assertGetVerboseTXOK() 130 | 131 | async def test_gettxs(self): 132 | await self.assertGetSegwitTxsOK() 133 | 134 | async def test_transaction(self): 135 | """ 136 | Sample transaction: 137 | TxID: ef60508ebe9f684fa881ad41ba278365f238a5fca36af78f3f106c30f7020eca 138 | """ 139 | await self.assertTransactionOK() 140 | 141 | async def test_transaction_p2pk(self): 142 | """ 143 | Sample transaction: 144 | TxID: 145 | """ 146 | await self.assertTransactionToPKOK() 147 | 148 | async def test_transaction_segwit(self): 149 | """ 150 | Sample transaction: 151 | TxID: 0494107dea76e27658b245744ffda3766010af347c3dd68abb2686ca080d1cd5 152 | """ 153 | await self.assertSegwitTransactionOK() 154 | 155 | async def test_transaction_native_segwit(self): 156 | """ 157 | Sample transaction: 158 | TxID: b368c55cd8895c561f176645ce418b3e3ac7e3c98aa74f4a7bcf286e53c86515 159 | """ 160 | await self.assertNativeSegwitTransactionOK() 161 | 162 | async def test_transaction_mixed_segwit(self): 163 | """ 164 | Sample transaction: 165 | TxID: 15470f798231153ce9d6427cd223c8bd591b2ebf6c059e19347db4df038d6e86 166 | """ 167 | await self.assertMixedSegwitTransactionOK() 168 | 169 | async def test_transaction_multisig(self): 170 | """ 171 | Sample transaction: 172 | TxID: a555123e8e49e32e8d462705f278c5cd5a3d08ea5e7e738ecc286dae1a1eac38 173 | """ 174 | await self.assertMultiSigTransactionOK() 175 | 176 | async def test_transaction_native_segwit_multisig(self): 177 | """ 178 | Sample transaction: 179 | TxID: b710704a7939e3e0c82e643faaa3a602549c416a2004feba13e2ef7a9e95dddb 180 | """ 181 | await self.assertNativeSegwitMultiSigTransactionOK() 182 | 183 | async def test_sendmulti_recipient_tx(self): 184 | """ 185 | Sample transaction: 186 | TxID: 8b364c9337998c2586350804553f0f66f97372fe99ed0506a279e4e344495fb8 187 | """ 188 | await self.assertSendMultiRecipientsTXOK() 189 | 190 | async def test_send(self): 191 | """ 192 | Sample transaction: 193 | TxID: 21fdea144f28d0cb1da99c5fe7c96268aaa98ddef3a14fd627b44ea31ce0be3e 194 | """ 195 | await self.assertSendOK() 196 | 197 | async def test_subscribe_block_headers(self): 198 | await self.assertSubscribeBlockHeadersOK() 199 | 200 | async def test_subscribe_block_headers_sync(self): 201 | await self.assertSubscribeBlockHeadersSyncCallbackOK() 202 | 203 | async def test_latest_block(self): 204 | await self.assertLatestBlockOK() 205 | 206 | async def test_confirmations(self): 207 | await self.assertConfirmationsOK() 208 | 209 | @unittest.skip('Intermittent failures') 210 | async def test_subscribe_address(self): 211 | await self.assertSubscribeAddressOK() 212 | 213 | async def test_subscribe_address_sync(self): 214 | await self.assertSubscribeAddressSyncCallbackOK() 215 | 216 | async def test_subscribe_address_transactions(self): 217 | await self.assertSubscribeAddressTransactionsOK() 218 | 219 | async def test_subscribe_address_transactions_sync(self): 220 | await self.assertSubscribeAddressTransactionsSyncOK() 221 | -------------------------------------------------------------------------------- /tests/test_coins_async/test_dash.py: -------------------------------------------------------------------------------- 1 | from cryptos import coins_async 2 | from cryptos.testing.testcases_async import BaseAsyncCoinTestCase 3 | from cryptos.electrumx_client.types import ElectrumXMultiBalanceResponse 4 | from typing import List 5 | from unittest import mock 6 | 7 | 8 | class TestDash(BaseAsyncCoinTestCase): 9 | name = "Dash" 10 | coin = coins_async.Dash 11 | 12 | addresses = ["XwPJ2c8dJifpZ422ogWaM6etzm9qZ7RyBz", 13 | "Xhu5S5VibUsxjkUYoJ1RDotx339EEL1qGH", 14 | "XgmCkSxeLGfe9PDnemqx1SzuAS71BPYDoY"] 15 | multisig_addresses: List[str] = ["7VvjrtmMoXdUNXWKMfeXTdHRNXDhhs16sQ", "7TcYXDLZRJc3WS3gAHUehsh5q6Zb4KSUic"] 16 | privkeys = ["098ddf01ebb71ead01fc52cb4ad1f5cafffb5f2d052dd233b3cad18e255e1db1", 17 | "0861e1bb62504f5e9f03b59308005a6f2c12c34df108c6f7c52e5e712a08e91401", 18 | "c396c62dfdc529645b822dc4eaa7b9ddc97dd8424de09ca19decce61e6732f71"] #Private keys for above address_derivations in same order 19 | privkey_standard_wifs: List[str] = ["7qd538kkwpDfEC13gqUju9maWGqcjCAeWhsCDzJ1s7WXQCRhBqM", 20 | "XBZvhbM7yLHg2pTGzgK9f4PDWB7AA48Rd8psZHiYEpdgDBikbLbe", 21 | "7s313e2yHvdGx45ydMuL1UHHcZeQAPQs5n62x8H76HjACPvsS4x"] 22 | unspent_addresses = ["XtuVUju4Baaj7YXShQu4QbLLR7X2aw9Gc8"] 23 | unspent = [ 24 | {'address': 'XtuVUju4Baaj7YXShQu4QbLLR7X2aw9Gc8', 'height': 1086570, 25 | 'tx_hash': '47266dc659f7271d26dd2b10369895c26d7b16b7db7cd577b60896c7c6cc1974', 'tx_pos': 0, 26 | 'value': 99808}] 27 | unspents = unspent 28 | balance: ElectrumXMultiBalanceResponse = {'confirmed': 99808, 'unconfirmed': 0} 29 | balances: List[ElectrumXMultiBalanceResponse] =[{'address': unspent_addresses[0]} | dict(balance)] 30 | history = [{'height': 1086570, 'tx_hash': '47266dc659f7271d26dd2b10369895c26d7b16b7db7cd577b60896c7c6cc1974'}] 31 | histories = [{'address': 'XtuVUju4Baaj7YXShQu4QbLLR7X2aw9Gc8', 'height': 1086570, 32 | 'tx_hash': '47266dc659f7271d26dd2b10369895c26d7b16b7db7cd577b60896c7c6cc1974'}] 33 | fee: int = 500 34 | max_fee: int = 3500 35 | testnet = False 36 | min_latest_height = 801344 37 | txheight: int = 509045 38 | block_hash = "0134634c7f95ebc31f3929d30a334f7d5c87a41625b64e26bf680028fa47fc63" 39 | txid = "e7a607c5152863209f33cec4cc0baed973f7cfd75ae28130e623c099fde7072c" 40 | raw_tx = "0100000001b8f99a18e65963d07b239c77528ac0521b607e10ae3d82d2d417283fd61273ac000000006b483045022100dd5bce7f3898d754813e3b77f25fb9731a3d4e3fa7877d9313db9d728a5b28df02203be0bc1166c7a5669d0f8134b9dcc3a2c5fcf69e4a9106780f9ea3b6c9c60edd01210301fb572fa47ec4de2c8cecc1a38fe4fedc2c533dc3a9ae126fb8a225c8d895f5ffffffff02a5d93d10000000001976a9144ce676449e20eb2e329173d34b752fd70f02299088ac00ef1c0d000000001976a914561a5126d9674f51688677c7d19127bd6386795988ac00000000" 41 | expected_tx_verbose_keys: List[str] = ['blockhash', 'blocktime', 'chainlock', 'confirmations', 'hex', 'instantlock', 'instantlock_internal', 'locktime', 'size', 'time', 'txid', 'type', 'version', 'vin', 'vout'] 42 | 43 | def test_standard_wif_ok(self): 44 | self.assertStandardWifOK() 45 | 46 | async def test_balance(self): 47 | await self.assertBalanceOK() 48 | 49 | async def test_balances(self): 50 | await self.assertBalancesOK() 51 | 52 | async def test_unspent(self): 53 | await self.assertUnspentOK() 54 | 55 | async def test_unspents(self): 56 | await self.assertUnspentsOK() 57 | 58 | async def test_merkle_proof(self): 59 | await self.assertMerkleProofOK() 60 | 61 | async def test_history(self): 62 | await self.assertHistoryOK() 63 | 64 | async def test_histories(self): 65 | await self.assertHistoriesOK() 66 | 67 | async def test_block_header(self): 68 | await self.assertBlockHeaderOK() 69 | 70 | async def test_block_headers(self): 71 | await self.assertBlockHeadersOK() 72 | 73 | async def test_gettx(self): 74 | await self.assertGetTXOK() 75 | 76 | async def test_getverbosetx(self): 77 | await self.assertGetVerboseTXOK() 78 | 79 | async def test_gettxs(self): 80 | await self.assertTxsOK() 81 | 82 | async def test_balance_merkle_proven(self): 83 | await self.assertBalanceMerkleProvenOK() 84 | 85 | async def test_balances_merkle_proven(self): 86 | await self.assertBalancesMerkleProvenOK() 87 | 88 | async def test_transaction(self): 89 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 90 | side_effect=self.mock_electrumx_send_request): 91 | await self.assertTransactionOK( 92 | "f6e42d4bae97868779ce7377fd34e973c17b38e9029b37e961ae75dd82a9e34f") 93 | 94 | async def test_transaction_multisig(self): 95 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 96 | side_effect=self.mock_electrumx_send_request): 97 | await self.assertMultiSigTransactionOK( 98 | "d786997a1d0faa3f17b9d597a631822e0e3f29780fbeb4259545f77c40fd7d25") 99 | 100 | async def test_sendmulti_recipient_tx(self): 101 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 102 | side_effect=self.mock_electrumx_send_request): 103 | await self.assertSendMultiRecipientsTXOK("29433a1fed7577398e5414b0c887a59e5a4cfd5a2a26bbd6d5c495bfd46c3061") 104 | 105 | async def test_send(self): 106 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 107 | side_effect=self.mock_electrumx_send_request): 108 | await self.assertSendOK("66b37a63f0bfdc9b9a09543966a7ba22aab9f5c614267ca75ccdc1a85ffcc5a3") 109 | 110 | async def test_subscribe_block_headers(self): 111 | await self.assertSubscribeBlockHeadersOK() 112 | 113 | async def test_subscribe_block_headers_sync(self): 114 | await self.assertSubscribeBlockHeadersSyncCallbackOK() 115 | 116 | async def test_latest_block(self): 117 | await self.assertLatestBlockOK() 118 | 119 | async def test_confirmations(self): 120 | await self.assertConfirmationsOK() 121 | -------------------------------------------------------------------------------- /tests/test_coins_async/test_dash_testnet.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import skip 3 | from cryptos import coins_async 4 | from cryptos.testing.testcases_async import BaseAsyncCoinTestCase 5 | from typing import List 6 | 7 | 8 | class TestDashTestnet(BaseAsyncCoinTestCase): 9 | name = "Dash Testnet" 10 | coin = coins_async.Dash 11 | addresses = ["yh1u3ZD4kGKttnwaNXpyP85FH3eD8E99vP", 12 | "yTXgT2aA32Y35VQ6N9KpFqKJKKdbidgKeZ", 13 | "ySPomQ35mpKiV89LDdAM3URFSibNiXEC4J"] 14 | multisig_addresses: List[str] = ["8hwYpDfDw526ppvaRveUv16nG2zXwp1Z7X", "8fdMUYERYqzfxjTwEYUcAFWSicLRBA7rxn"] 15 | privkeys = ["098ddf01ebb71ead01fc52cb4ad1f5cafffb5f2d052dd233b3cad18e255e1db1", 16 | "0861e1bb62504f5e9f03b59308005a6f2c12c34df108c6f7c52e5e712a08e91401", 17 | "c396c62dfdc529645b822dc4eaa7b9ddc97dd8424de09ca19decce61e6732f71"] #Private keys for above address_derivations in same order 18 | privkey_standard_wifs: List[str] = ["91f8DFTsmhtawuLjR8CiHNkgZGPkUqfJ45LxmENPf3k6fuX1m4N", 19 | "cMrziExc6iMV8vvAML8QX9hGDP8zNhcsKbdS9BqrRa1b4mhKvK6f", 20 | "9354Dkk67pJCfmRfMedJPhGPfZCXv2uWd9ZoVNMUtDxjUBbCVZK"] 21 | fee: int = 5000 22 | max_fee: int = 100000 23 | testnet: bool = True 24 | min_latest_height = 830385 25 | unspent_addresses = ["ye9FSaGnHH5A2cjJ9s2y9XTgyJZefB5huz"] 26 | unspent = [{'address': 'ye9FSaGnHH5A2cjJ9s2y9XTgyJZefB5huz', 27 | 'height': 830413, 28 | 'tx_hash': 'a9f58c5bf785528edcda59a1e1f679a690be2f2c036e194bb2bc3f6ed0b5a9a5', 29 | 'tx_pos': 0, 30 | 'value': 378260000}] 31 | unspents = unspent 32 | balance = {'confirmed': 378260000, 'unconfirmed': 0} 33 | balances = [{'address': unspent_addresses[0]} | dict(balance)] 34 | history = [ 35 | {'height': 830413, 36 | 'tx_hash': 'a9f58c5bf785528edcda59a1e1f679a690be2f2c036e194bb2bc3f6ed0b5a9a5'} 37 | ] 38 | histories = [{'address': unspent_addresses[0]} | dict(history[0])] 39 | txid = 'a9f58c5bf785528edcda59a1e1f679a690be2f2c036e194bb2bc3f6ed0b5a9a5' 40 | txheight = 45550 41 | block_hash: str = "5a353b1bc4974919fc49c0b15f6c2226e27dfdef0fe1809cd90f67eb50e6b478" 42 | raw_tx: str = "020000000112f461b7fbc2607586659722ec82d81e568ea5e66308e2ea76b0b284fabefb1f010000006a47304402201fcd36ce719006af4278f0624c922df5e275be767a468fdf66c72a0de8b6bb8202206cb4fc48f0b91be14b1135856fd37c0ba9c6b670199131ea3d8150979f652322012103c54188322b2e2639a465f63c9a30ee620c3bd3af157e1c5781d34d2f779dca9ffeffffff0220ca8b16000000001976a914c384950342cb6f8df55175b48586838b03130fad88ac1c181c50000000001976a9146fd4df7d158c66f9f00c1247ad64a9e7d6a6c33388acc5ab0c00" 43 | expected_tx_verbose_keys: List[str] = ['blockhash', 'blocktime', 'chainlock', 'confirmations', 'hex', 'instantlock', 'instantlock_internal', 'locktime', 'size', 'time', 'txid', 'type', 'version', 'vin', 'vout'] 44 | 45 | def test_standard_wif_ok(self): 46 | self.assertStandardWifOK() 47 | 48 | async def test_balance(self): 49 | await self.assertBalanceOK() 50 | 51 | async def test_balances(self): 52 | await self.assertBalancesOK() 53 | 54 | async def test_unspent(self): 55 | await self.assertUnspentOK() 56 | 57 | async def test_unspents(self): 58 | await self.assertUnspentsOK() 59 | 60 | async def test_merkle_proof(self): 61 | await self.assertMerkleProofOK() 62 | 63 | async def test_history(self): 64 | await self.assertHistoryOK() 65 | 66 | async def test_histories(self): 67 | await self.assertHistoriesOK() 68 | 69 | async def test_balance_merkle_proven(self): 70 | await self.assertBalanceMerkleProvenOK() 71 | 72 | async def test_balances_merkle_proven(self): 73 | await self.assertBalancesMerkleProvenOK() 74 | 75 | async def test_block_header(self): 76 | await self.assertBlockHeaderOK() 77 | 78 | async def test_block_headers(self): 79 | await self.assertBlockHeadersOK() 80 | 81 | async def test_gettx(self): 82 | await self.assertGetTXOK() 83 | 84 | async def test_getverbosetx(self): 85 | await self.assertGetVerboseTXOK() 86 | 87 | async def test_gettxs(self): 88 | await self.assertGetTXOK() 89 | 90 | async def test_transaction(self): 91 | """ 92 | Sample transaction: 93 | TxID: 23ebe2519c10803ff4d3cab0013d80b63e2ba90103c98084aacbc79c78dd736f 94 | """ 95 | await self.assertTransactionOK() 96 | 97 | @unittest.skip("Unspents unrecognized") 98 | async def test_transaction_multisig(self): 99 | """ 100 | Sample transaction: 101 | TxID: 102 | """ 103 | await self.assertMultiSigTransactionOK() 104 | 105 | @unittest.skip("Intermittent failure") 106 | async def test_sendmulti_recipient_tx(self): 107 | """ 108 | Sample transaction: 109 | TxID: 6fb0071bd94dff8de1f9784fd30aadeebd2d8ca27f5451b7725e05c868e98593 110 | """ 111 | await self.assertSendMultiRecipientsTXOK() 112 | 113 | async def test_send(self): 114 | """ 115 | Sample transaction: 116 | TxID: 81a22353f6dc45ae9b32a8fdea26b74fa71050c6993959cb07ce7092b26b1287 117 | """ 118 | await self.assertSendOK() 119 | 120 | async def test_subscribe_block_headers(self): 121 | await self.assertSubscribeBlockHeadersOK() 122 | 123 | async def test_subscribe_block_headers_sync(self): 124 | await self.assertSubscribeBlockHeadersSyncCallbackOK() 125 | 126 | async def test_latest_block(self): 127 | await self.assertLatestBlockOK() 128 | 129 | async def test_confirmations(self): 130 | await self.assertConfirmationsOK() 131 | 132 | async def test_subscribe_address(self): 133 | await self.assertSubscribeAddressOK() 134 | 135 | async def test_subscribe_address_sync(self): 136 | await self.assertSubscribeAddressSyncCallbackOK() 137 | 138 | async def test_subscribe_address_transactions(self): 139 | await self.assertSubscribeAddressTransactionsOK() 140 | 141 | async def test_subscribe_address_transactions_sync(self): 142 | await self.assertSubscribeAddressTransactionsSyncOK() 143 | -------------------------------------------------------------------------------- /tests/test_coins_async/test_dogecoin.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cryptos import coins_async 4 | from cryptos.testing.testcases_async import BaseAsyncCoinTestCase 5 | from typing import List 6 | from unittest import mock 7 | 8 | 9 | class TestDoge(BaseAsyncCoinTestCase): 10 | name = "Dogecoin" 11 | coin = coins_async.Doge 12 | addresses = ['DRqYjcRNeRMWw7c3gPBv3L8i3ZJSqbm6PV', 13 | 'DCML95nTwBZf7p4Zfzgkv3Nm5qHqY1g17S', 14 | 'DBDTTTFPfyMLXSooXUXHhgUiDEFcTRnvFf'] 15 | multisig_addresses: List[str] = ["9uxMmYZ64cQjH8LqWBeSNNv99YmMznrBuN", 16 | "9seARs8HgPPJR2tCJoUZcdKoc87FNnHZhg"] 17 | privkeys: List[str] = ["098ddf01ebb71ead01fc52cb4ad1f5cafffb5f2d052dd233b3cad18e255e1db1", 18 | "0861e1bb62504f5e9f03b59308005a6f2c12c34df108c6f7c52e5e712a08e91401", 19 | "c396c62dfdc529645b822dc4eaa7b9ddc97dd8424de09ca19decce61e6732f71"] 20 | privkey_standard_wifs: List[str] = ['6JCpvU7HkF7F2TGbzAvn2QBxP2WapoQNKJcF16en8yCghYr1m7W', 21 | 'QNtvQAmjvG9MD1qwZCA5342p4Bs9koez8pAspPnBeoxwFxbqYcre', 22 | '6KckvyPW6MWrkKMXvhMN8ihfVKKNFzeatNq5jEdsN9RKVnJrynQ'] 23 | fee: int = 300000 24 | max_fee: int = 600000 25 | testnet = False 26 | 27 | min_latest_height = 4464523 28 | unspent_addresses = ["DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8"] 29 | 30 | balance = {'confirmed': 730696900, 'unconfirmed': 0} 31 | balances = [{'address': unspent_addresses[0]} | dict(balance)] 32 | 33 | unspent = [{'address': 'DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8', 34 | 'height': 3101154, 35 | 'tx_hash': '770e6cb667a07c74a73ed74950224cb536ceed34dc678182a0d86c7a7703ed9c', 36 | 'tx_pos': 0, 37 | 'value': 100000000}, 38 | {'address': 'DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8', 39 | 'height': 3133347, 40 | 'tx_hash': '976a9e6cd24d44cce4098401398a37663973a297ab60c2f42db7dc46640ca410', 41 | 'tx_pos': 0, 42 | 'value': 69696900}, 43 | {'address': 'DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8', 44 | 'height': 3311542, 45 | 'tx_hash': 'd19cd7b01a6e7320f3b5efb90dc2cc4505ac8bf32b2e6b6fae87a87615752976', 46 | 'tx_pos': 0, 47 | 'value': 420000000}, 48 | {'address': 'DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8', 49 | 'height': 4073469, 50 | 'tx_hash': 'aa5cb5cccf8dadd5a42d039134b732f76226b6c09aca629cbf0489a5974578e6', 51 | 'tx_pos': 0, 52 | 'value': 141000000}] 53 | unspents = unspent 54 | history = [ 55 | {'height': 3101154, 56 | 'tx_hash': '770e6cb667a07c74a73ed74950224cb536ceed34dc678182a0d86c7a7703ed9c'}, 57 | {'height': 3133347, 58 | 'tx_hash': '976a9e6cd24d44cce4098401398a37663973a297ab60c2f42db7dc46640ca410'}, 59 | {'height': 3311542, 60 | 'tx_hash': 'd19cd7b01a6e7320f3b5efb90dc2cc4505ac8bf32b2e6b6fae87a87615752976'}, 61 | {'height': 4073469, 62 | 'tx_hash': 'aa5cb5cccf8dadd5a42d039134b732f76226b6c09aca629cbf0489a5974578e6'}] 63 | histories = [{'address': 'DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8', 64 | 'height': 3101154, 65 | 'tx_hash': '770e6cb667a07c74a73ed74950224cb536ceed34dc678182a0d86c7a7703ed9c'}, 66 | {'address': 'DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8', 67 | 'height': 3133347, 68 | 'tx_hash': '976a9e6cd24d44cce4098401398a37663973a297ab60c2f42db7dc46640ca410'}, 69 | {'address': 'DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8', 70 | 'height': 4073469, 71 | 'tx_hash': 'aa5cb5cccf8dadd5a42d039134b732f76226b6c09aca629cbf0489a5974578e6'}, 72 | {'address': 'DLAznsPDLDRgsVcTFWRMYMG5uH6GddDtv8', 73 | 'height': 3311542, 74 | 'tx_hash': 'd19cd7b01a6e7320f3b5efb90dc2cc4505ac8bf32b2e6b6fae87a87615752976'}] 75 | txid: str = "345c28885d265edbf8565f553f9491c511b6549d3923a1d63fe158b8000bbee2" 76 | txheight: int = 2046470 77 | block_hash: str = "404646bb8958730e133c2a363b79ef1c9c300db4134988060c6c9215e9a6694a" 78 | raw_tx: str = "010000000185229c0ab8804cb1e4bf465d9e3e0bcbc0a40481a5892eb03d61d7411f1fee72000000006a47304402202da8ad356685fe8f3337cd14c7a20861fc332b79a49581c062fe2f648e4b8d0b0220106fed579b94c0bf1b6bdb199c8147deb65b40f6618bab89c00262090124b70d012102960791fd0876b1b00ae11ea858d4f3f44a3184d8d4bff0336417b969b9f11809feffffff02928eccfd280000001976a91466f79e401b78766cbea8791ec76c8d6b2cf6441788ac75969b6e2b0300001976a914f594b41cfd73d290bbbb298d71e63d1c613d35ee88acf8391f00" 79 | 80 | def test_standard_wif_ok(self): 81 | self.assertStandardWifOK() 82 | 83 | async def test_balance(self): 84 | await self.assertBalanceOK() 85 | 86 | async def test_balances(self): 87 | await self.assertBalancesOK() 88 | 89 | async def test_unspent(self): 90 | await self.assertUnspentOK() 91 | 92 | async def test_unspents(self): 93 | await self.assertUnspentsOK() 94 | 95 | async def test_merkle_proof(self): 96 | await self.assertMerkleProofOK() 97 | 98 | async def test_history(self): 99 | await self.assertHistoryOK() 100 | 101 | async def test_histories(self): 102 | await self.assertHistoriesOK() 103 | 104 | async def test_balance_merkle_proven(self): 105 | await self.assertBalanceMerkleProvenOK() 106 | 107 | async def test_balances_merkle_proven(self): 108 | await self.assertBalancesMerkleProvenOK() 109 | 110 | async def test_block_header(self): 111 | await self.assertBlockHeaderOK() 112 | 113 | async def test_block_headers(self): 114 | await self.assertBlockHeadersOK() 115 | 116 | async def test_gettx(self): 117 | await self.assertGetTXOK() 118 | 119 | @unittest.skip("Intermittent failure") 120 | async def test_getverbosetx(self): 121 | await self.assertGetVerboseTXOK() 122 | 123 | async def test_gettxs(self): 124 | await self.assertGetTxsOK() 125 | 126 | async def test_transaction(self): 127 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 128 | side_effect=self.mock_electrumx_send_request): 129 | await self.assertTransactionOK("812f055cc55515c2e1aa7a6aa38adc890d3c5d39781d38fb2e5f647b84b6b3fa") 130 | 131 | async def test_transaction_multisig(self): 132 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 133 | side_effect=self.mock_electrumx_send_request): 134 | await self.assertMultiSigTransactionOK("a9b8184a05f317ba18f3e01482305bc6ba667f4a674e097de515d31381d902c1") 135 | 136 | async def test_sendmulti_recipient_tx(self): 137 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 138 | side_effect=self.mock_electrumx_send_request): 139 | await self.assertSendMultiRecipientsTXOK("4646a5b2f143d98f474e3a60f745627a5ddbbccc7c1fceeeeeaa391c4c0b263d") 140 | 141 | async def test_send(self): 142 | with mock.patch('cryptos.electrumx_client.client.NotificationSession.send_request', 143 | side_effect=self.mock_electrumx_send_request): 144 | await self.assertSendOK("01d0cb59eaaeaafc6d8e09d18bcd2b7beaf856936b41570819c4475d2dcdcd0e") 145 | 146 | async def test_subscribe_block_headers(self): 147 | await self.assertSubscribeBlockHeadersOK() 148 | 149 | async def test_subscribe_block_headers_sync(self): 150 | await self.assertSubscribeBlockHeadersSyncCallbackOK() 151 | 152 | async def test_latest_block(self): 153 | await self.assertLatestBlockOK() 154 | 155 | async def test_confirmations(self): 156 | await self.assertConfirmationsOK() 157 | 158 | -------------------------------------------------------------------------------- /tests/test_coins_async/test_dogecoin_testnet.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cryptos import coins_async 4 | from cryptos.testing.testcases_async import BaseAsyncCoinTestCase 5 | from typing import List 6 | 7 | 8 | @unittest.skip('Only Doge Electrumx server is often down') 9 | class TestDogeTestnet(BaseAsyncCoinTestCase): 10 | name = "Doge Testnet" 11 | coin = coins_async.Doge 12 | addresses = ['nptcTdAHaPpEp6BEiCqNHjj1HRgjtFELjM', 13 | 'nbQPs6XNsA2NzndkhpLDASy4Khg8ZfhUfj', 14 | 'naGXBTzJbwp4QRNzZJAjx651T6duZy2kgV'] 15 | multisig_addresses: List[str] = ["2MvmK6SRDc13BaYbumBbtkCH2fKbViC5XEv", 16 | "2MtT7kkzRDn1kiT9GZoS1zSgh7twP145Qif"] 17 | privkeys: List[str] = ["098ddf01ebb71ead01fc52cb4ad1f5cafffb5f2d052dd233b3cad18e255e1db1", 18 | "0861e1bb62504f5e9f03b59308005a6f2c12c34df108c6f7c52e5e712a08e91401", 19 | "c396c62dfdc529645b822dc4eaa7b9ddc97dd8424de09ca19decce61e6732f71"] 20 | privkey_standard_wifs: List[str] = ['95YcZiMwUZF3DYW9EHSiC1xEPVdBnFM3dMVBFXkBeMksf8k8F53', 21 | 'cf2FrZgQT2Bm5xwhTUvC7VtEiFYJ3YAKeUshG6Y3QXX1dSAZ9s9h', 22 | '96xYaDe9pfeewQb5AosJJLTwVnRyDSbGCRi1yfjGsXyWTNCJpxv'] 23 | fee: int = 300000 24 | max_fee: int = 1000000 25 | testnet = True 26 | min_latest_height = 4109697 27 | 28 | unspent_addresses = ["ncst7MzasMBQFwx2LuCwj8Z6F4pCRppzBP"] 29 | unspent = [{'address': 'ncst7MzasMBQFwx2LuCwj8Z6F4pCRppzBP', 30 | 'height': 4107911, 31 | 'tx_hash': '268bff71633310c974e262a67a057a618f6d6596bfa95e45692d6c9269a13873', 32 | 'tx_pos': 0, 33 | 'value': 10000000000}] 34 | unspents = unspent 35 | balance = {'confirmed': 10000000000, 'unconfirmed': 0} 36 | balances = [{'address': unspent_addresses[0]} | dict(balance)] 37 | history = [{'height': 4107911, 38 | 'tx_hash': '268bff71633310c974e262a67a057a618f6d6596bfa95e45692d6c9269a13873'}] 39 | histories = [{'address': unspent_addresses[0]} | dict(history[0])] 40 | txid: str = "268bff71633310c974e262a67a057a618f6d6596bfa95e45692d6c9269a13873" 41 | txheight: int = 4107911 42 | block_hash: str = "de125596a7937dcccdb49bb55ea50b776fbe32f8bfa9afcda236dcdc6b9880e3" 43 | raw_tx: str = "0100000001502100e4c8a3c0bb1aabc6698b128ffdaf06be518f126fe3400880920d680600010000006b483045022100918045061097ed4e3ae63d3f5a0fa5aa188a0e1f8f0041fb0693a0c05cb3ce8b02206efcb89204e4924a71704e105b1d8c7cc8a701a70262efb91dc426cdb97c40670121030d78465ee4506029a720bd691c883705391271d9a8f428abae5c577ce44841f1feffffff0200e40b54020000001976a9145f44291007a8a4000b9b8424843793f402638e9388acaae442dc310000001976a914a7106a8a40e93b097a383446d30ce8d1f7af1db288ac86ae3e00" 44 | 45 | def test_standard_wif_ok(self): 46 | self.assertStandardWifOK() 47 | 48 | async def test_balance(self): 49 | await self.assertBalanceOK() 50 | 51 | async def test_balances(self): 52 | await self.assertBalancesOK() 53 | 54 | async def test_unspent(self): 55 | await self.assertUnspentOK() 56 | 57 | async def test_unspents(self): 58 | await self.assertUnspentsOK() 59 | 60 | async def test_merkle_proof(self): 61 | await self.assertMerkleProofOK() 62 | 63 | async def test_history(self): 64 | await self.assertHistoryOK() 65 | 66 | async def test_histories(self): 67 | await self.assertHistoriesOK() 68 | 69 | async def test_balance_merkle_proven(self): 70 | await self.assertBalanceMerkleProvenOK() 71 | 72 | async def test_balances_merkle_proven(self): 73 | await self.assertBalancesMerkleProvenOK() 74 | 75 | async def test_block_header(self): 76 | await self.assertBlockHeaderOK() 77 | 78 | async def test_block_headers(self): 79 | await self.assertBlockHeadersOK() 80 | 81 | async def test_gettx(self): 82 | await self.assertGetTXOK() 83 | 84 | async def test_getverbosetx(self): 85 | await self.assertGetVerboseTXOK() 86 | 87 | async def test_gettxs(self): 88 | await self.assertGetTxsOK() 89 | 90 | async def test_transaction(self): 91 | """ 92 | Sample transaction: 93 | TxID: 4d859f797f678f0cbe608490e0ba5bc7f1fa670b34192bbbf6f468fb2ed00fe4 94 | """ 95 | await self.assertTransactionOK() 96 | 97 | async def test_transaction_multisig(self): 98 | """ 99 | Sample transaction: 100 | TxID: 092df25d3bb39bb82e1d00faf08a0aec2255a3214bab2a1a7c6437dd77b6281b 101 | """ 102 | await self.assertMultiSigTransactionOK() 103 | 104 | @unittest.skip("Intermittent failure") 105 | async def test_sendmulti_recipient_tx(self): 106 | """ 107 | Sample transaction: 108 | TxID: 5723b84848f00d2b315086ce564e535bd6d75fa13225ff75fce0d9ed7cd306a9 109 | """ 110 | await self.assertSendMultiRecipientsTXOK() 111 | 112 | async def test_send(self): 113 | """ 114 | Sample transaction: 115 | TxID: 21fdea144f28d0cb1da99c5fe7c96268aaa98ddef3a14fd627b44ea31ce0be3e 116 | """ 117 | await self.assertSendOK() 118 | 119 | async def test_subscribe_block_headers(self): 120 | await self.assertSubscribeBlockHeadersOK() 121 | 122 | async def test_subscribe_block_headers_sync(self): 123 | await self.assertSubscribeBlockHeadersSyncCallbackOK() 124 | 125 | async def test_latest_block(self): 126 | await self.assertLatestBlockOK() 127 | 128 | async def test_confirmations(self): 129 | await self.assertConfirmationsOK() 130 | 131 | async def test_subscribe_address(self): 132 | await self.assertSubscribeAddressOK() 133 | 134 | async def test_subscribe_address_sync(self): 135 | await self.assertSubscribeAddressSyncCallbackOK() 136 | 137 | async def test_subscribe_address_transactions(self): 138 | await self.assertSubscribeAddressTransactionsOK() 139 | 140 | async def test_subscribe_address_transactions_sync(self): 141 | await self.assertSubscribeAddressTransactionsSyncOK() 142 | 143 | -------------------------------------------------------------------------------- /tests/test_coins_async/test_litecoin_testnet.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cryptos import coins_async 4 | from cryptos.main import privtopub, compress 5 | from cryptos.testing.testcases_async import BaseAsyncCoinTestCase 6 | from cryptos.types import ElectrumXTx, TxOut 7 | from cryptos.electrumx_client.types import ElectrumXMultiBalanceResponse 8 | from typing import List 9 | 10 | 11 | class TestLitecoinTestnet(BaseAsyncCoinTestCase): 12 | name = "Litecoin Testnet" 13 | coin = coins_async.Litecoin 14 | addresses: List[str] = ["n2DQVQZiA2tVBDu4fNAjKVBS2RArWhfncv", 15 | "mnjBtsvoSo6dMvMaeyfaCCRV4hAF8WA2cu", 16 | "mmbKDFPjBatJmZ6pWTW6yqXSC6826YLBX6"] 17 | segwit_addresses: List[str] = ["QaRdhvUtEt5vnU1MXUhK8JT1wLqS7Y29Ua", 18 | "QhAx5qBJphxMrSZfwzaf8KyP9T2DrAMbiC", 19 | "QMPRBmeVuqPf8KxYeF9ANdVKh6cNTePk7W"] 20 | native_segwit_addresses: List[str] = [ 21 | "tltc1q95cgql39zvtc57g4vn8ytzmlvtt43skn39z3vs", 22 | "tltc1qfuvnn87p787z7nqv9seu4e8fqel83yac3kxh62", 23 | "tltc1qst3pkm860tjt9y70ugnaluqyqnfa7h54q7xv2n"] 24 | multisig_addresses: List[str] = ["QQ85DTHTd76GijMxPHduptiToi3KbZFpcw", 25 | "QMossmrfEt4qrduKBuU35988GHPCtUY4ZQ"] 26 | native_segwit_multisig_addresses: List[str] = [ 27 | "tltc1q7e42a8gmgp5d7kw8myrruj5qnxp8edp7v5y0y95hrjz6t530uehqf3n2q4", 28 | "tltc1qu7fz4uku8kh6tg7qghj7rnf88g858lal258gzfu85kx7vc5h0qpsm9lgv7" 29 | ] 30 | # Private keys for above addresses in same order 31 | privkeys = ["098ddf01ebb71ead01fc52cb4ad1f5cafffb5f2d052dd233b3cad18e255e1db1", 32 | "0861e1bb62504f5e9f03b59308005a6f2c12c34df108c6f7c52e5e712a08e91401", 33 | "c396c62dfdc529645b822dc4eaa7b9ddc97dd8424de09ca19decce61e6732f71"] 34 | privkey_standard_wifs: List[str] = ['7QMPk7vLsHRiUXSsuJQkVyyWbnqHH3DGHPwo43MMwbRUz7o9yLi', 35 | 'VG2jGVfRhLNtN6HHohBYJo9tGXVgJcbyWPjBHPDJrbqRYtCsGKDb', 36 | "7RmKkdCZDPqLCPXoqpqLcJVDi5e4iETUrUAdnBLTAme7nJDsayK"] 37 | privkey_segwit_wifs: List[str] = ['VZEGXeGF3FLN1XQkmzdcbGfQ9RhQWdJanFX7PL54QvSV5Qcy4Stv', 38 | 'VZBzQpPE3eDAK8JpuqyKu9LrmPtyyT9RqGySQHuVqZLr7YZusmsc', 39 | "VfTtusVoY1cbgJUWrev99ANXbBsu3pwLGE9nTKK3V5YFVHKwMvh4"] 40 | privkey_native_segwit_wifs: List[str] = ["VQedxUuLs6RE31PzDvEio6ZutzVkgD2rcotypsixvSgmo5QjrRhM", 41 | "VQcMqf2KsVJ2LcJ4MmaS6yFNWxhL92shfqMJqqZQM5b8qDNnGqBj", 42 | "VWtGLi8uMrhThnTkJaXFLzH3LkgFDQfc6nXetrxwzbnYCxAXw4MK"] 43 | fee: int = 2000 44 | max_fee: int = 4000 45 | testnet: bool = True 46 | 47 | min_latest_height: int = 2391524 48 | unspent_addresses: List[str] = ["ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA"] 49 | unspent: List[ElectrumXTx] = [{'address': 'ms31HApa3jvv3crqvZ3sJj7tC5TCs61GSA', 50 | 'height': 296481, 51 | 'tx_hash': '3f7a5460983cdfdf8118a1ab6bc84c28e536e83971532d4910c26bd21153de19', 52 | 'tx_pos': 1, 53 | 'value': 100000000}] 54 | unspents: List[ElectrumXTx] = unspent 55 | balance: ElectrumXMultiBalanceResponse = {'confirmed': 100000000, 'unconfirmed': 0} 56 | balances: List[ElectrumXMultiBalanceResponse] = [{'address': unspent_addresses[0]} | dict(balance)] 57 | history: List[ElectrumXTx] = [{'height': 296481, 'tx_hash': '3f7a5460983cdfdf8118a1ab6bc84c28e536e83971532d4910c26bd21153de19'}] 58 | histories: List[ElectrumXTx] = [{'address': unspent_addresses[0]} | dict(history[0])] 59 | txid: str = "2a288547460ebe410e98fe63a1900b6452d95ec318efb0d58a5584ac67f27d93" 60 | txheight: int = 296568 61 | block_hash: str = "9c557ffb695078e9f79d92b449fc0e61d82c331692258eb38495013aaf636218" 62 | txinputs: List[TxOut] = [] 63 | raw_tx: str = "010000000384c9a749888ec0b7372a5740dfe8ba059f67d7b48cb1110060a4e666b42ea383000000006b483045022100c7081d2329334a78cde23359da1d9684d60b7fdb3e396c9d2633c419f9ad30ff022058e7cd031df6b7c7208b3140887e9ba012c81e4f300fcf388256f2636b0682e401210391ed6bf1e0842997938ea2706480a7085b8bb253268fd12ea83a68509602b6e0ffffffff84c9a749888ec0b7372a5740dfe8ba059f67d7b48cb1110060a4e666b42ea383010000006a47304402207ceb8ca2179fc4ff975ebc3a95b6b1ddc5ce0c280203576d8a1d53948c7138ac02201157f68003220b7f6c3abc7756e7838e062b81ed511f6caff66aa1a73525efa301210391ed6bf1e0842997938ea2706480a7085b8bb253268fd12ea83a68509602b6e0ffffffff7183a5bf996607a10ee0566716012a587adb9e43778c1a019deb3e43b9537af2010000006b483045022100a95b8b36d08f944949b7fa2dca32f5e44e568339dcde11a8713e4676ed3bc77202204d117c91053b667714b1496583583bf8633b7fb189a800d08fdaaefd3f1ef49301210391ed6bf1e0842997938ea2706480a7085b8bb253268fd12ea83a68509602b6e0ffffffff020cb82d01000000001976a91442a3e11a80b25ff63b2074c51d1745132bccbba188ac74789b0a000000001976a914c384950342cb6f8df55175b48586838b03130fad88ac00000000" 64 | 65 | def test_standard_wif_ok(self): 66 | self.assertStandardWifOK() 67 | 68 | def test_p2wpkh_p2sh_wif_ok(self): 69 | self.assertP2WPKH_P2SH_WifOK() 70 | 71 | def test_p2wpkh_wif_ok(self): 72 | self.assertP2WPKH_WIFOK() 73 | 74 | async def test_balance(self): 75 | await self.assertBalanceOK() 76 | 77 | async def test_balances(self): 78 | await self.assertBalancesOK() 79 | 80 | async def test_unspent(self): 81 | await self.assertUnspentOK() 82 | 83 | async def test_unspents(self): 84 | await self.assertUnspentsOK() 85 | 86 | async def test_merkle_proof(self): 87 | await self.assertMerkleProofOK() 88 | 89 | async def test_history(self): 90 | await self.assertHistoryOK() 91 | 92 | async def test_histories(self): 93 | await self.assertHistoriesOK() 94 | 95 | async def test_block_header(self): 96 | await self.assertBlockHeaderOK() 97 | 98 | async def test_block_headers(self): 99 | await self.assertBlockHeadersOK() 100 | 101 | async def test_gettx(self): 102 | await self.assertGetTXOK() 103 | 104 | @unittest.skip("Intermittent Failure") 105 | async def test_getverbosetx(self): 106 | await self.assertGetVerboseTXOK() 107 | 108 | async def test_gettxs(self): 109 | await self.assertGetTXOK() 110 | 111 | async def test_balance_merkle_proven(self): 112 | await self.assertBalanceMerkleProvenOK() 113 | 114 | async def test_balances_merkle_proven(self): 115 | await self.assertBalancesMerkleProvenOK() 116 | 117 | @unittest.skip("Intermittent Failure") 118 | async def test_transaction(self): 119 | """ 120 | Sample transaction: 121 | TxID: 319107640d6dbbbf0da7c6341074410862f61e167c49094477e10f80ecf41b20 122 | """ 123 | await self.assertTransactionOK() 124 | 125 | async def test_transaction_segwit(self): 126 | """ 127 | Sample transaction: 128 | TxID: cc1aba794b8f7bb0176906e724594ce6c79f598cd904c1483de4b43331b25cc5 129 | """ 130 | await self.assertSegwitTransactionOK() 131 | 132 | @unittest.skip("Intermittent Failure") 133 | async def test_transaction_native_segwit(self): 134 | """ 135 | Sample transaction: 136 | TxID: 8dbaffbd6206147389e0bb9c30c84e9f0a5dedb9a1e15563c950c7470b3c90c5 137 | """ 138 | await self.assertNativeSegwitTransactionOK() 139 | 140 | async def test_transaction_mixed_segwit(self): 141 | """ 142 | Sample transaction: 143 | TxID: 3dc675d5addfad9d142f51411372ed2ad9700a19019114aab775048effadf7da 144 | """ 145 | await self.assertMixedSegwitTransactionOK() 146 | 147 | async def test_transaction_multisig(self): 148 | """ 149 | Sample transaction: 150 | TxID: 6d68cdc662fc92164b371aba385aec9afc99058a6ce6dfaa5c5766705e099f85 151 | """ 152 | await self.assertMultiSigTransactionOK() 153 | 154 | @unittest.skip("Intermittent Failure") 155 | async def test_transaction_native_segwit_multisig(self): 156 | """ 157 | Sample transaction: 158 | TxID: 4076340f359668b325808ec98888863402c0fb829f0f1d15e2e8ab816841d749 159 | """ 160 | await self.assertNativeSegwitMultiSigTransactionOK() 161 | 162 | @unittest.skip("Intermittent Failure") 163 | async def test_sendmulti_recipient_tx(self): 164 | """ 165 | Sample transaction: 166 | TxID: da7c7f99f2f1968bfd53dc7a817b4a9f3f3f596727194c4df57fac3f0e68e777 167 | """ 168 | await self.assertSendMultiRecipientsTXOK() 169 | 170 | async def test_send(self): 171 | """ 172 | Sample transaction: 173 | TxID: 779221217a57596149d1ea4bf017066df6a488d1afe730d8d805bbaa6b6b0deb 174 | """ 175 | await self.assertSendOK() 176 | 177 | async def test_subscribe_block_headers(self): 178 | await self.assertSubscribeBlockHeadersOK() 179 | 180 | async def test_subscribe_block_headers_sync(self): 181 | await self.assertSubscribeBlockHeadersSyncCallbackOK() 182 | 183 | async def test_latest_block(self): 184 | await self.assertLatestBlockOK() 185 | 186 | async def test_confirmations(self): 187 | await self.assertConfirmationsOK() 188 | 189 | async def test_subscribe_address(self): 190 | await self.assertSubscribeAddressOK() 191 | 192 | async def test_subscribe_address_sync(self): 193 | await self.assertSubscribeAddressSyncCallbackOK() 194 | 195 | async def test_subscribe_address_transactions(self): 196 | await self.assertSubscribeAddressTransactionsOK() 197 | 198 | async def test_subscribe_address_transactions_sync(self): 199 | await self.assertSubscribeAddressTransactionsSyncOK() 200 | -------------------------------------------------------------------------------- /tests/test_stealth.py: -------------------------------------------------------------------------------- 1 | import cryptos as bc 2 | import sys 3 | import unittest 4 | 5 | class TestStealth(unittest.TestCase): 6 | 7 | def setUp(self): 8 | 9 | if sys.getrecursionlimit() < 1000: 10 | sys.setrecursionlimit(1000) 11 | 12 | self.addr = 'vJmtjxSDxNPXL4RNapp9ARdqKz3uJyf1EDGjr1Fgqs9c8mYsVH82h8wvnA4i5rtJ57mr3kor1EVJrd4e5upACJd588xe52yXtzumxj' 13 | self.scan_pub = '025e58a31122b38c86abc119b9379fe247410aee87a533f9c07b189aef6c3c1f52' 14 | self.scan_priv = '3e49e7257cb31db997edb1cf8299af0f37e2663e2260e4b8033e49d39a6d02f2' 15 | self.spend_pub = '03616562c98e7d7b74be409a787cec3a912122f3fb331a9bee9b0b73ce7b9f50af' 16 | self.spend_priv = 'aa3db0cfb3edc94de4d10f873f8190843f2a17484f6021a95a7742302c744748' 17 | self.ephem_pub = '03403d306ec35238384c7e340393335f9bc9bb4a2e574eb4e419452c4ea19f14b0' 18 | self.ephem_priv = '9e63abaf8dcd5ea3919e6de0b6c544e00bf51bf92496113a01d6e369944dc091' 19 | self.shared_secret = 'a4047ee231f4121e3a99a3a3378542e34a384b865a9917789920e1f13ffd91c6' 20 | self.pay_pub = '02726112ad39cb6bf848b1b1ef30b88e35286bf99f746c2be575f96c0e02a9357c' 21 | self.pay_priv = '4e422fb1e5e1db6c1f6ab32a7706d368ceb385e7fab098e633c5c5949c3b97cd' 22 | 23 | self.testnet_addr = 'waPUuLLykSnY3itzf1AyrQZm42F7KyB7SR5zpfqmnzPXWhx9kXLzV3EcyqzDdpTwngiyCCMUqztS9S1d7XJs3JMt3MsHPDpBCudvx9' 24 | 25 | def test_address_encoding(self): 26 | 27 | sc_pub, sp_pub = bc.basic_stealth_address_to_pubkeys(self.addr) 28 | self.assertEqual(sc_pub, self.scan_pub) 29 | self.assertEqual(sp_pub, self.spend_pub) 30 | 31 | stealth_addr2 = bc.pubkeys_to_basic_stealth_address(sc_pub, sp_pub) 32 | self.assertEqual(stealth_addr2, self.addr) 33 | 34 | magic_byte_testnet = 43 35 | sc_pub, sp_pub = bc.basic_stealth_address_to_pubkeys(self.testnet_addr) 36 | self.assertEqual(sc_pub, self.scan_pub) 37 | self.assertEqual(sp_pub, self.spend_pub) 38 | 39 | stealth_addr2 = bc.pubkeys_to_basic_stealth_address(sc_pub, sp_pub, magic_byte_testnet) 40 | self.assertEqual(stealth_addr2, self.testnet_addr) 41 | 42 | def test_shared_secret(self): 43 | 44 | sh_sec = bc.shared_secret_sender(self.scan_pub, self.ephem_priv) 45 | self.assertEqual(sh_sec, self.shared_secret) 46 | 47 | sh_sec2 = bc.shared_secret_receiver(self.ephem_pub, self.scan_priv) 48 | self.assertEqual(sh_sec2, self.shared_secret) 49 | 50 | def test_uncover_pay_keys(self): 51 | 52 | pub = bc.uncover_pay_pubkey_sender(self.scan_pub, self.spend_pub, self.ephem_priv) 53 | pub2 = bc.uncover_pay_pubkey_receiver(self.scan_priv, self.spend_pub, self.ephem_pub) 54 | self.assertEqual(pub, self.pay_pub) 55 | self.assertEqual(pub2, self.pay_pub) 56 | 57 | priv = bc.uncover_pay_privkey(self.scan_priv, self.spend_priv, self.ephem_pub) 58 | self.assertEqual(priv, self.pay_priv) 59 | 60 | def test_stealth_metadata_script(self): 61 | 62 | nonce = int('deadbeef', 16) 63 | script = bc.mk_stealth_metadata_script(self.ephem_pub, nonce) 64 | self.assertEqual(script[6:], 'deadbeef' + self.ephem_pub) 65 | 66 | eph_pub = bc.ephem_pubkey_from_tx_script(script) 67 | self.assertEqual(eph_pub, self.ephem_pub) 68 | 69 | def test_stealth_tx_outputs(self): 70 | 71 | nonce = int('deadbeef', 16) 72 | value = 10**8 73 | outputs = bc.mk_stealth_tx_outputs(self.addr, value, self.ephem_priv, nonce) 74 | 75 | self.assertEqual(outputs[0]['value'], 0) 76 | self.assertEqual(outputs[0]['script'], '6a2606deadbeef' + self.ephem_pub) 77 | self.assertEqual(outputs[1]['address'], bc.pubkey_to_address(self.pay_pub)) 78 | self.assertEqual(outputs[1]['value'], value) 79 | 80 | outputs = bc.mk_stealth_tx_outputs(self.testnet_addr, value, self.ephem_priv, nonce, 'testnet') 81 | 82 | self.assertEqual(outputs[0]['value'], 0) 83 | self.assertEqual(outputs[0]['script'], '6a2606deadbeef' + self.ephem_pub) 84 | self.assertEqual(outputs[1]['address'], bc.pubkey_to_address(self.pay_pub, 111)) 85 | self.assertEqual(outputs[1]['value'], value) 86 | 87 | self.assertRaises(Exception, bc.mk_stealth_tx_outputs, self.testnet_addr, value, self.ephem_priv, nonce, 'btc') 88 | 89 | self.assertRaises(Exception, bc.mk_stealth_tx_outputs, self.addr, value, self.ephem_priv, nonce, 'testnet') 90 | 91 | if __name__ == '__main__': 92 | unittest.main() 93 | --------------------------------------------------------------------------------