def test_import(): m = mock_provider m.reset_mocks() keys = HDKey.from_path(master, "m/44'/0'/0'") m.hd_master_key = master m.set_num_used_accounts(0) m.set_txn_side_effect_for_hd_discovery() wallet = Two1Wallet.import_from_mnemonic( data_provider=m, mnemonic=master_seed, passphrase=passphrase, account_type="BIP44BitcoinMainnet") assert wallet._root_keys[1].to_b58check() == keys[1].to_b58check() assert wallet._root_keys[2].to_b58check() == keys[2].to_b58check() assert wallet._accounts[0].key.to_b58check() == keys[3].to_b58check() assert len(wallet._accounts) == 1 # Now test where the first account has transactions m.reset_mocks() m.set_num_used_accounts(2) # Set this to 2 so that we get the side effects m.set_num_used_addresses(account_index=0, n=10, change=0) m.set_num_used_addresses(account_index=0, n=20, change=1) m.set_txn_side_effect_for_hd_discovery() wallet = Two1Wallet.import_from_mnemonic( data_provider=m, mnemonic=master_seed, passphrase=passphrase, account_type="BIP44BitcoinMainnet") assert len(wallet._accounts) == 1 assert wallet._accounts[0].has_txns() # Test where multiple accounts have transactions m.reset_mocks() m.set_num_used_accounts(5) for i in range(4): m.set_num_used_addresses(account_index=i, n=1, change=0) m.set_num_used_addresses(account_index=i, n=2, change=1) m.set_txn_side_effect_for_hd_discovery() wallet = Two1Wallet.import_from_mnemonic( data_provider=m, mnemonic=master_seed, passphrase=passphrase, account_type="BIP44BitcoinMainnet") assert len(wallet._accounts) == 4 for i in range(4): assert wallet._accounts[i].has_txns()
def restore(ctx): """ Restore a wallet from a mnemonic \b If you accidently deleted your wallet file or the file became corrupted, use this command to restore your wallet. You must have your 12 word phrase (mnemonic) that was displayed when you created your wallet. """ # Stop daemon if it's running. d = None try: d = get_daemonizer() except OSError as e: pass if d: try: d.stop() except exceptions.DaemonizerError as e: click.echo("ERROR: Couldn't stop daemon: %s" % e) ctx.exit(code=4) # Check to see if the current wallet path exists if os.path.exists(ctx.obj['wallet_path']): if click.confirm("Wallet file already exists and may have a balance. Do you want to delete it?"): os.remove(ctx.obj['wallet_path']) else: click.echo("Not continuing.") ctx.exit(code=4) # Ask for mnemonic mnemonic = click.prompt("Please enter the wallet's 12 word mnemonic").strip() # Sanity check the mnemonic def check_mnemonic(mnemonic): try: return Mnemonic(language='english').check(mnemonic) except ConfigurationError: return False if not check_mnemonic(mnemonic): click.echo("ERROR: Invalid mnemonic.") ctx.exit(code=5) # Try creating the wallet click.echo("\nRestoring...") wallet = Two1Wallet.import_from_mnemonic( data_provider=ctx.obj['data_provider'], mnemonic=mnemonic, ) wallet.to_file(ctx.obj['wallet_path']) if Two1Wallet.check_wallet_file(ctx.obj['wallet_path']): click.echo("Wallet successfully restored. Run '21 login' to connect this wallet to your 21 account.") else: click.echo("Wallet not restored.") ctx.exit(code=6)
def test_import(): m = mock_provider m.reset_mocks() keys = HDKey.from_path(master, "m/44'/0'/0'") m.hd_master_key = master m.set_num_used_accounts(0) m.set_txn_side_effect_for_hd_discovery() wallet = Two1Wallet.import_from_mnemonic(data_provider=m, mnemonic=master_seed, passphrase=passphrase, account_type="BIP44BitcoinMainnet") assert wallet._root_keys[1].to_b58check() == keys[1].to_b58check() assert wallet._root_keys[2].to_b58check() == keys[2].to_b58check() assert wallet._accounts[0].key.to_b58check() == keys[3].to_b58check() assert len(wallet._accounts) == 1 # Now test where the first account has transactions m.reset_mocks() m.set_num_used_accounts(2) # Set this to 2 so that we get the side effects m.set_num_used_addresses(account_index=0, n=10, change=0) m.set_num_used_addresses(account_index=0, n=20, change=1) m.set_txn_side_effect_for_hd_discovery() wallet = Two1Wallet.import_from_mnemonic(data_provider=m, mnemonic=master_seed, passphrase=passphrase, account_type="BIP44BitcoinMainnet") assert len(wallet._accounts) == 1 assert wallet._accounts[0].has_txns() # Test where multiple accounts have transactions m.reset_mocks() m.set_num_used_accounts(5) for i in range(4): m.set_num_used_addresses(account_index=i, n=1, change=0) m.set_num_used_addresses(account_index=i, n=2, change=1) m.set_txn_side_effect_for_hd_discovery() wallet = Two1Wallet.import_from_mnemonic(data_provider=m, mnemonic=master_seed, passphrase=passphrase, account_type="BIP44BitcoinMainnet") assert len(wallet._accounts) == 4 for i in range(4): assert wallet._accounts[i].has_txns()
def create(ctx, account_type, testnet): """ Creates a new wallet """ # txn_data_provider and related params come from the # global context. passphrase = "" if ctx.obj['passphrase']: # Let's prompt for a passphrase conf = "a" i = 0 while passphrase != conf and i < 3: passphrase = getpass.getpass("Enter desired passphrase: ") conf = getpass.getpass("Confirm passphrase: ") i += 1 if passphrase != conf: ctx.fail("Passphrases don't match. Quitting.") options = { "account_type": account_type, "passphrase": passphrase, "data_provider": ctx.obj['data_provider'], "testnet": testnet, "wallet_path": ctx.obj['wallet_path'] } logger.info("Creating wallet with options: %r" % options) created = Two1Wallet.configure(options) if created: # Make sure it opens logger.info("Wallet created.") try: wallet = Two1Wallet(params_or_file=ctx.obj['wallet_path'], data_provider=ctx.obj['data_provider'], passphrase=passphrase) click.echo("Wallet successfully created!") adder = " (and your passphrase) " if passphrase else " " click.echo( "Your wallet can be recovered using the following set of words (in that order)." ) click.echo("Please store them%ssafely." % adder) click.echo("\n%s\n" % wallet._orig_params['master_seed']) except Exception as e: logger.debug("Error opening created wallet: %s" % e) click.echo("Wallet was not created properly.") ctx.exit(code=3) else: ctx.fail("Wallet was not created.")
def restore(ctx): """ Restore a wallet from a mnemonic \b If you accidently deleted your wallet file or the file became corrupted, use this command to restore your wallet. You must have your 12 word phrase (mnemonic) that was displayed when you created your wallet. """ # Check to see if the current wallet path exists if os.path.exists(ctx.obj['wallet_path']): if click.confirm( "Wallet file already exists and may have a balance. Do you want to delete it?" ): os.remove(ctx.obj['wallet_path']) else: click.echo("Not continuing.") ctx.exit(code=4) # Ask for mnemonic mnemonic = click.prompt( "Please enter the wallet's 12 word mnemonic").strip() # Sanity check the mnemonic def check_mnemonic(mnemonic): try: return Mnemonic(language='english').check(mnemonic) except ConfigurationError: return False if not check_mnemonic(mnemonic): click.echo("ERROR: Invalid mnemonic.") ctx.exit(code=5) # Try creating the wallet click.echo("\nRestoring...") wallet = Two1Wallet.import_from_mnemonic( data_provider=ctx.obj['data_provider'], mnemonic=mnemonic, ) wallet.to_file(ctx.obj['wallet_path']) if Two1Wallet.check_wallet_file(ctx.obj['wallet_path']): click.echo( "Wallet successfully restored. Run '21 login' to connect this wallet to your 21 account." ) else: click.echo("Wallet not restored.") ctx.exit(code=6)
def login_21(): """ Restore wallet to disk and log in to 21. """ mnemonic = os.environ["TWO1_WALLET_MNEMONIC"] provider = TwentyOneProvider() wallet = Two1Wallet.import_from_mnemonic(provider, mnemonic) if not os.path.exists(os.path.dirname(Two1Wallet.DEFAULT_WALLET_PATH)): os.makedirs(os.path.dirname(Two1Wallet.DEFAULT_WALLET_PATH)) wallet.to_file(Two1Wallet.DEFAULT_WALLET_PATH) # login config = Config() machine_auth = machine_auth_wallet.MachineAuthWallet(wallet) username = os.environ["TWO1_USERNAME"] password = os.environ["TWO1_PASSWORD"] rest_client = _rest_client.TwentyOneRestClient(two1.TWO1_HOST, machine_auth, username) machine_auth_pubkey_b64 = base64.b64encode( machine_auth.public_key.compressed_bytes).decode() payout_address = machine_auth.wallet.current_address rest_client.login(payout_address=payout_address, password=password) config.set("username", username) config.set("mining_auth_pubkey", machine_auth_pubkey_b64) config.save()
def login_21(): """ Restore wallet to disk and log in to 21. """ mnemonic = os.environ["TWO1_WALLET_MNEMONIC"] provider = TwentyOneProvider() wallet = Two1Wallet.import_from_mnemonic(provider, mnemonic) if not os.path.exists(os.path.dirname(Two1Wallet.DEFAULT_WALLET_PATH)): os.makedirs(os.path.dirname(Two1Wallet.DEFAULT_WALLET_PATH)) wallet.to_file(Two1Wallet.DEFAULT_WALLET_PATH) # login config = Config() machine_auth = machine_auth_wallet.MachineAuthWallet(wallet) username = os.environ["TWO1_USERNAME"] password = os.environ["TWO1_PASSWORD"] rest_client = _rest_client.TwentyOneRestClient(two1.TWO1_HOST, machine_auth, username) machine_auth_pubkey_b64 = base64.b64encode(machine_auth.public_key.compressed_bytes).decode() payout_address = machine_auth.wallet.current_address rest_client.login(payout_address=payout_address, password=password) config.set("username", username) config.set("mining_auth_pubkey", machine_auth_pubkey_b64) config.save()
def test_encrypt_decrypt(): mkey_enc, mseed_enc = Two1Wallet.encrypt(master_key=config['master_key'], master_seed=config['master_seed'], passphrase=passphrase, key_salt=enc_key_salt) mkey, mseed = Two1Wallet.decrypt(master_key_enc=mkey_enc, master_seed_enc=mseed_enc, passphrase=passphrase, key_salt=enc_key_salt) assert mkey == config['master_key'] assert mseed == config['master_seed'] for i in range(1000): s = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(random.randint(1, 200))) key = rand_bytes(Two1Wallet.AES_BLOCK_SIZE) enc = Two1Wallet._encrypt_str(s, key) dec = Two1Wallet._decrypt_str(enc, key) assert dec == s
def test_encrypt_decrypt(): mkey_enc, mseed_enc = Two1Wallet.encrypt(master_key=config['master_key'], master_seed=config['master_seed'], passphrase=passphrase, key_salt=enc_key_salt) mkey, mseed = Two1Wallet.decrypt(master_key_enc=mkey_enc, master_seed_enc=mseed_enc, passphrase=passphrase, key_salt=enc_key_salt) assert mkey == config['master_key'] assert mseed == config['master_seed'] for i in range(1000): s = ''.join( random.choice(string.ascii_letters + string.digits) for _ in range(random.randint(1, 200))) key = rand_bytes(Two1Wallet.AES_BLOCK_SIZE) enc = Two1Wallet._encrypt_str(s, key) dec = Two1Wallet._decrypt_str(enc, key) assert dec == s
def startdaemon(ctx): """ Starts the daemon """ # Check to sere if we're in a venv and don't do anything if we are if os.environ.get("VIRTUAL_ENV"): click.echo( "Not starting daemon while inside a virtualenv. It can be manually " + "started by doing 'walletd' and backgrounding the process.") return # Check if the wallet path exists if not Two1Wallet.check_wallet_file(ctx.obj['wallet_path']): click.echo("ERROR: Wallet does not exist! Not starting daemon.") ctx.exit(code=7) try: d = get_daemonizer() except OSError as e: logger.debug(str(e)) click.echo("Error: %s" % e) return if d.started(): click.echo("walletd already running.") return if not d.installed(): if isinstance(ctx.obj['data_provider'], TwentyOneProvider): dpo = dict(provider='twentyone') try: d.install(dpo) except exceptions.DaemonizerError as e: logger.debug(str(e)) click.echo("Error: %s" % e) return msg = "" try: if d.start(): msg = "walletd successfully started." else: msg = "walletd not started." except exceptions.DaemonizerError as e: msg = "Error: %s" % e logger.debug(msg) click.echo(msg)
def startdaemon(ctx): """ Starts the daemon """ # Check to sere if we're in a venv and don't do anything if we are if os.environ.get("VIRTUAL_ENV"): click.echo("Not starting daemon while inside a virtualenv. It can be manually " + "started by doing 'walletd' and backgrounding the process.") return # Check if the wallet path exists if not Two1Wallet.check_wallet_file(ctx.obj['wallet_path']): click.echo("ERROR: Wallet does not exist! Not starting daemon.") ctx.exit(code=7) try: d = get_daemonizer() except OSError as e: logger.debug(str(e)) click.echo("Error: %s" % e) return if d.started(): click.echo("walletd already running.") return if not d.installed(): if isinstance(ctx.obj['data_provider'], TwentyOneProvider): dpo = dict(provider='twentyone') try: d.install(dpo) except exceptions.DaemonizerError as e: logger.debug(str(e)) click.echo("Error: %s" % e) return msg = "" try: if d.start(): msg = "walletd successfully started." else: msg = "walletd not started." except exceptions.DaemonizerError as e: msg = "Error: %s" % e logger.debug(msg) click.echo(msg)
def create(ctx, account_type, testnet): """ Creates a new wallet """ # txn_data_provider and related params come from the # global context. passphrase = "" if ctx.obj['passphrase']: # Let's prompt for a passphrase conf = "a" i = 0 while passphrase != conf and i < 3: passphrase = getpass.getpass("Enter desired passphrase: ") conf = getpass.getpass("Confirm passphrase: ") i += 1 if passphrase != conf: ctx.fail("Passphrases don't match. Quitting.") options = {"account_type": account_type, "passphrase": passphrase, "data_provider": ctx.obj['data_provider'], "testnet": testnet, "wallet_path": ctx.obj['wallet_path']} logger.info("Creating wallet with options: %r" % options) created = Two1Wallet.configure(options) if created: # Make sure it opens logger.info("Wallet created.") try: wallet = Two1Wallet(params_or_file=ctx.obj['wallet_path'], data_provider=ctx.obj['data_provider'], passphrase=passphrase) click.echo("Wallet successfully created!") adder = " (and your passphrase) " if passphrase else " " click.echo("Your wallet can be recovered using the following set of words (in that order).") click.echo("Please store them%ssafely." % adder) click.echo("\n%s\n" % wallet._orig_params['master_seed']) except Exception as e: logger.debug("Error opening created wallet: %s" % e) click.echo("Wallet was not created properly.") ctx.exit(code=3) else: ctx.fail("Wallet was not created.")
def test_create(): # Here we just check to see that the config was created properly, # there is only 1 account associated w/the wallet and that there # are no txns, etc. mock_provider.set_txn_side_effect_for_hd_discovery() wallet, _ = Two1Wallet.create(data_provider=mock_provider, passphrase=passphrase) assert len(wallet._accounts) == 1 assert wallet._accounts[0].name == "default" assert wallet._accounts[0].index == 0x80000000 wallet_config = wallet.to_dict() assert wallet_config['account_map'] == {"default": 0} assert wallet_config['accounts'][0]['last_payout_index'] == -1 assert wallet_config['accounts'][0]['last_change_index'] == -1
def load_wallet(wallet_path, data_provider, passphrase): """ Loads a wallet. Args: wallet_path (str): The path to the wallet to be loaded. data_provider (BaseProvider): A blockchain data provider object. passphrase (str): Passphrase to use to unlock the wallet if the wallet requires unlocking. """ global wallet try: logger.debug("In load_wallet...") logger.debug("\twallet_path = %s" % wallet_path) logger.debug("\tdata_provider = %r" % data_provider) logger.debug("\tpassphrase = %r" % bool(passphrase)) wallet['obj'] = Two1Wallet(params_or_file=wallet_path, data_provider=data_provider, passphrase=passphrase) wallet['locked'] = False except Exception as e: raise WalletNotLoadedError("Wallet loading failed: %s" % e)
from two1.bitcoin.crypto import HDKey, HDPrivateKey from two1.bitcoin.utils import bytes_to_str from two1.bitcoin.utils import rand_bytes from two1.blockchain.mock_provider import MockProvider from two1.wallet import exceptions from two1.wallet.two1_wallet import Two1Wallet enc_key_salt = b'\xaa\xbb\xcc\xdd' passphrase = "test_wallet" passphrase_hash = PBKDF2.crypt(passphrase) master_key = "xprv9s21ZrQH143K2dUcTctuNw8oV8e7gi4ZbHFGAnyGJtWwmKbKTbLGtx48DQGzioGDdhVn8zFhJe8hbDdfDnK19ykxjwXLzd6EpxnTqi4zQGN" # nopep8 master_seed = "tuna object element cancel hard nose faculty noble swear net subway offer" mkey_enc, mseed_enc = Two1Wallet.encrypt(master_key=master_key, master_seed=master_seed, passphrase=passphrase, key_salt=enc_key_salt) config = {'master_key': mkey_enc, 'master_seed': mseed_enc, 'locked': True, 'passphrase_hash': passphrase_hash, 'key_salt': bytes_to_str(enc_key_salt), 'account_type': "BIP44BitcoinMainnet", 'accounts': [{'public_key': "xpub6CNX3TRAXGpoV1a3ai3Hs9R85t63V3k6BGsTaxZZMJJ4DL6kRY8riYA1r6hxyeuxgeb33FfBgrJrV6wxv6VXEVHAfPGJNw8ZzbEJHgsbmpz", # nopep8 'last_payout_index': 2, 'last_change_index': 1}], 'account_map': {'default': 0}} master = HDPrivateKey.master_key_from_mnemonic(master_seed, passphrase) mock_provider = MockProvider("BIP44BitcoinMainnet", master)
def main(ctx, wallet_path, blockchain_data_provider, insight_url, insight_api_path, data_update_interval, debug): """ Two1 Wallet daemon """ global DEF_WALLET_UPDATE_INTERVAL wp = Path(wallet_path) # Initialize some logging handlers ch = logging.handlers.TimedRotatingFileHandler(wp.dirname().joinpath("walletd.log"), when='midnight', backupCount=5) ch_formatter = logging.Formatter( '%(asctime)s %(levelname)s %(name)-8s: %(message)s') ch.setFormatter(ch_formatter) ch.setLevel(logging.DEBUG if debug else logging.INFO) logging.getLogger().addHandler(ch) console = logging.StreamHandler() console.setLevel(logging.CRITICAL) logging.getLogger().addHandler(console) logging.getLogger().setLevel(logging.DEBUG) global wallet wallet['path'] = wallet_path if not Two1Wallet.check_wallet_file(wallet['path']): logger.critical("Wallet file does not exist or have the right parameters.") sys.exit(-1) wallet['data_provider'] = ctx.obj['data_provider'] if data_update_interval is not None: DEF_WALLET_UPDATE_INTERVAL = data_update_interval wallet['update_info']['interval'] = data_update_interval logger.info("Starting daemon for wallet %s" % wallet_path) logger.info("Blockchain data provider: %s" % ctx.obj['data_provider'].__class__.__name__) logger.info("Update interval: %ds" % data_update_interval) # Check whether the wallet is locked if Two1Wallet.is_locked(wallet_path): wallet['locked'] = True logger.info("Wallet is locked.") else: logger.info("Wallet unlocked. Loading ...") try: load_wallet(wallet_path=wallet_path, data_provider=ctx.obj['data_provider'], passphrase="") logger.info("... loading complete.") except WalletNotLoadedError as e: logger.error(str(e)) logger.info("Terminating.") sys.exit(-1) create_daemon_methods() # Setup a signal handler signal.signal(signal.SIGINT, sig_handler) signal.signal(signal.SIGTERM, sig_handler) server_thread = threading.Thread(target=rpc_server.serve_forever, daemon=True) update_thread = threading.Thread(target=data_updater, daemon=True) server_thread.start() update_thread.start() logger.info("Daemon started.") server_thread.join() rpc_server.server_close() try: _check_wallet_loaded() wallet['obj'].sync_wallet_file(force_cache_write=True) except: pass sys.exit(0)
import random from two1.blockchain.twentyone_provider import TwentyOneProvider from two1.wallet.two1_wallet import Two1Wallet mnemonics = ['absent paddle capable spell bag reflect rally there swear swallow cook rubber', 'stairs art mirror spoon clap talk exclude tuna absurd exact grape relief', 'poem into dune liar already rain swear thunder spread kangaroo monster wise', 'business lyrics news image duty stone clerk salad harvest shallow follow evoke', 'another student leg ladder jeans hello cluster type network wrist before sense'] cp = TwentyOneProvider() # Create wallet objects wallets = [Two1Wallet.import_from_mnemonic(data_provider=cp, mnemonic=m, account_type='BIP32') for m in mnemonics] max_balance_index = -1 max_balance = 0 for i, w in enumerate(wallets): balance = w.balances if balance['confirmed'] > max_balance: max_balance = balance['confirmed'] max_balance_index = i print("\nWallet %d:" % i) print("----------") print("Num accounts: %d" % (len(w._accounts)))
def main(ctx, wallet_path, passphrase, blockchain_data_provider, insight_url, insight_api_path, debug): """ Command-line Interface for the Two1 Wallet """ if wallet_path is None: try: config = two1_config.Config(config_file) wallet_path = config.wallet_path except two1exceptions.FileDecodeError as e: raise click.ClickException( uxstring.UxString.Error.file_decode.format((str(e)))) wp = Path(wallet_path) # Initialize some logging handlers ch = logging.StreamHandler() ch_formatter = logging.Formatter('%(levelname)s: %(message)s') ch.setFormatter(ch_formatter) if not os.path.exists(wp.dirname()): os.makedirs(wp.dirname()) fh = logging.handlers.TimedRotatingFileHandler( wp.dirname().joinpath("wallet_cli.log"), when='midnight', backupCount=5) fh_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s') fh.setFormatter(fh_formatter) logger.addHandler(ch) logger.addHandler(fh) fh.setLevel(logging.DEBUG if debug else logging.INFO) ch.setLevel(logging.DEBUG if debug else logging.WARNING) logger.setLevel(logging.DEBUG if debug else logging.INFO) logger.info("Wallet client started.") if ctx.obj is None: ctx.obj = {} ctx.obj['wallet_path'] = wallet_path ctx.obj['passphrase'] = passphrase if ctx.invoked_subcommand not in ['create', 'restore']: # Check that the wallet path exists if not Two1Wallet.check_wallet_file(ctx.obj['wallet_path']): click.echo("ERROR: Wallet file does not exist or is corrupt.") ctx.exit(code=7) p = get_passphrase() if passphrase else '' try: logger.info("Loading wallet %s ..." % (wp)) ctx.obj['wallet'] = Wallet(wallet_path=wallet_path, data_provider=ctx.obj['data_provider'], passphrase=p) logger.info("... loading complete.") except exceptions.PassphraseError as e: click.echo(str(e)) ctx.exit(code=1) except (TypeError, ValueError) as e: logger.error("Internal wallet error. Please report this as a bug.") logger.debug("".join(traceback.format_tb(e.__traceback__))) ctx.exit(code=2) def _on_close(): try: ctx.obj['wallet'].sync_wallet_file() except: pass ctx.call_on_close(_on_close)
#!/usr/bin/env python3 from two1.wallet.two1_wallet import Two1Wallet from two1.blockchain.twentyone_provider import TwentyOneProvider from two1.bitcoin import utils from two1.bitcoin import Script from two1.bitcoin import Transaction, TransactionInput, TransactionOutput # Create application objects wallet = Two1Wallet(Two1Wallet.DEFAULT_WALLET_PATH, TwentyOneProvider()) def write_ew_message(msg): """Write a message to the blockchain.""" print("write_ew_message({})" % msg) # Create a bitcoin script object with our message if len(msg) > 72: raise Exception('Message is too long and may not be accepted.') msg = "EW " + msg message_script = Script('OP_RETURN 0x{}'.format(utils.bytes_to_str(msg.encode()))) # Define the fee we're willing to pay for the tx tx_fee = 11000 # Get the first UTXO from our set that can cover the fee utxo = None for utxo_addr, utxos in wallet.get_utxos().items(): for u in utxos: if u.value > tx_fee:
from two1.bitcoin.crypto import HDKey, HDPrivateKey from two1.bitcoin.utils import bytes_to_str from two1.bitcoin.utils import rand_bytes from two1.blockchain.mock_provider import MockProvider from two1.wallet import exceptions from two1.wallet.two1_wallet import Two1Wallet enc_key_salt = b'\xaa\xbb\xcc\xdd' passphrase = "test_wallet" passphrase_hash = PBKDF2.crypt(passphrase) master_key = "xprv9s21ZrQH143K2dUcTctuNw8oV8e7gi4ZbHFGAnyGJtWwmKbKTbLGtx48DQGzioGDdhVn8zFhJe8hbDdfDnK19ykxjwXLzd6EpxnTqi4zQGN" # nopep8 master_seed = "tuna object element cancel hard nose faculty noble swear net subway offer" mkey_enc, mseed_enc = Two1Wallet.encrypt(master_key=master_key, master_seed=master_seed, passphrase=passphrase, key_salt=enc_key_salt) config = { 'master_key': mkey_enc, 'master_seed': mseed_enc, 'locked': True, 'passphrase_hash': passphrase_hash, 'key_salt': bytes_to_str(enc_key_salt), 'account_type': "BIP44BitcoinMainnet",
def test_rest(): m = mock_provider m.hd_master_key = master m.reset_mocks() m.set_num_used_accounts(1) m.set_num_used_addresses(account_index=0, n=1, change=0) m.set_num_used_addresses(account_index=0, n=2, change=1) m.set_txn_side_effect_for_hd_discovery() wallet = Two1Wallet(params_or_file=config, data_provider=m, passphrase=passphrase) # First 5 internal addresses of account 0 # These can be gotten from https://dcpos.github.io/bip39/ ext_addrs = [ "1Kv1QLXekeE42rKhvZ41kHS1auE7R3t21o", "1CYhVFaBwmTQRQwdyLc4rq9HwaxdqtQ68G", "18KCKKB5MGs4Rqu4t8jL9Bkt9SAp7NpUvm", "1FqUrpUpqWfHoPVga4uMKYCPHHoApvNiPa", "12zb1hJP5WEHCSKz5LyoPM9iaCwXtTthRc" ] int_addrs = [ "1Hiv6LroFmqcaVV9rhY6eNUjnFQh4y6kL7", "1GTUuNbgk4sv7LPQd2WqSP9PiinzeuBmay", "14fpkEZZ6QP3QEcQnfSjH7adkC2RsMuiZw", "1LPNyqqX6RU5b4oPumePR72tFfwiNUho4s", "1FqvNKJb8au82PhtGP8D1odXWVC1Ae4jN9" ] bad_addrs = [ "1CEDwjjtYjCQUoRZQW9RUXHH5Ao7PWYKf", "1CbHFUNsyCzZSDu7hYae7HHqgzMjBfqoP9" ] paths = wallet.find_addresses(int_addrs + ext_addrs + bad_addrs) assert len(paths.keys()) == 10 for i in range(5): # Hardened account key derivation, thus 0x80000000 assert paths[ext_addrs[i]] == (0x80000000, 0, i) assert paths[int_addrs[i]] == (0x80000000, 1, i) # Check address belongs assert wallet.address_belongs(ext_addrs[i]) == "m/44'/0'/0'/0/%d" % i assert wallet.address_belongs(int_addrs[i]) == "m/44'/0'/0'/1/%d" % i for b in bad_addrs: assert b not in paths assert wallet.address_belongs(b) is None # Check that there's an account name assert wallet.get_account_name(0) == "default" # Check the balance assert wallet.balances == {'confirmed': 100000, 'total': 200000} # Check that we can get a new payout address ext_addr = wallet.get_payout_address("default") assert ext_addr == ext_addrs[1] assert wallet.accounts[0].last_indices[0] == 0 # Check the balance again - should be the same m.set_num_used_addresses(0, 1, 0) assert wallet.balances == {'confirmed': 100000, 'total': 200000} # Try sending below the dust limit with pytest.raises(ValueError): wallet.send_to(address="14ocdLGpBp7Yv3gsPDszishSJUv3cpLqUM", amount=544) # Try sending a non-integer amount (mistaken user tries to send in btc # instead of satoshi) with pytest.raises(exceptions.SatoshiUnitsError): wallet.send_to(address="14ocdLGpBp7Yv3gsPDszishSJUv3cpLqUM", amount=0.0001) # Try sending more than we have and make sure it raises an exception with pytest.raises(exceptions.WalletBalanceError): wallet.send_to(address="14ocdLGpBp7Yv3gsPDszishSJUv3cpLqUM", amount=10000000) # Should still fail when using unconfirmed if amount is greater # than unconfirmed balance with pytest.raises(exceptions.WalletBalanceError): wallet.send_to(address="14ocdLGpBp7Yv3gsPDszishSJUv3cpLqUM", use_unconfirmed=True, amount=10000000) # Should fail when not using unconfirmed txns and # confirmed < amount < unconfirmed. with pytest.raises(exceptions.WalletBalanceError): wallet.send_to(address="14ocdLGpBp7Yv3gsPDszishSJUv3cpLqUM", use_unconfirmed=False, amount=150000) # Should get past checking balance but raise a NotImplementedError with pytest.raises(NotImplementedError): wallet.send_to(address="14ocdLGpBp7Yv3gsPDszishSJUv3cpLqUM", use_unconfirmed=False, amount=7700) # Should get past checking balance but raise a signing error with pytest.raises(NotImplementedError): wallet.send_to(address="14ocdLGpBp7Yv3gsPDszishSJUv3cpLqUM", use_unconfirmed=True, amount=12581) # test number of addresses in spread_utxos with pytest.raises(ValueError): wallet.spread_utxos(threshold=500000, num_addresses=0, accounts=[]) with pytest.raises(ValueError): wallet.spread_utxos(threshold=500000, num_addresses=101, accounts=[]) # test units for spread_utxos with pytest.raises(exceptions.SatoshiUnitsError): wallet.spread_utxos(threshold=0.0001, num_addresses=1, accounts=[]) # Finally check storing to a file params = {} with tempfile.NamedTemporaryFile(delete=True) as tf: wallet.to_file(tf) # Read it back tf.seek(0, 0) params = json.loads(tf.read().decode('utf-8')) # Check that the params match expected assert params['master_key'] == config['master_key'] assert params['master_seed'] == config['master_seed'] assert params['locked'] assert params['key_salt'] == bytes_to_str(enc_key_salt) assert params['passphrase_hash'] == passphrase_hash assert params['account_type'] == "BIP44BitcoinMainnet" assert params['account_map']['default'] == 0 assert len(params['accounts']) == 1 assert params['accounts'][0]['last_payout_index'] == 0 assert params['accounts'][0]['last_change_index'] == 1 assert params['accounts'][0]['public_key'] == config['accounts'][0][ 'public_key'] # Now create the wallet from the file with pytest.raises(exceptions.PassphraseError): w2 = Two1Wallet(params_or_file=tf.name, data_provider=m, passphrase='wrong_pass') # Now do with the correct passphrase m.set_txn_side_effect_for_hd_discovery() w2 = Two1Wallet(params_or_file=tf.name, data_provider=m, passphrase=passphrase) assert len(w2.accounts) == 1 assert not w2._testnet acct = w2.accounts[0] assert acct.last_indices[0] == 0 assert acct.last_indices[1] == 1
def main(ctx, wallet_path, passphrase, blockchain_data_provider, insight_url, insight_api_path, debug): """ Command-line Interface for the Two1 Wallet """ if wallet_path is None: try: config = two1_config.Config(config_file) wallet_path = config.wallet_path except two1exceptions.FileDecodeError as e: raise click.ClickException(uxstring.UxString.Error.file_decode.format((str(e)))) wp = Path(wallet_path) # Initialize some logging handlers ch = logging.StreamHandler() ch_formatter = logging.Formatter( '%(levelname)s: %(message)s') ch.setFormatter(ch_formatter) if not os.path.exists(wp.dirname()): os.makedirs(wp.dirname()) fh = logging.handlers.TimedRotatingFileHandler(wp.dirname().joinpath("wallet_cli.log"), when='midnight', backupCount=5) fh_formatter = logging.Formatter( '%(asctime)s %(levelname)s: %(message)s') fh.setFormatter(fh_formatter) logger.addHandler(ch) logger.addHandler(fh) fh.setLevel(logging.DEBUG if debug else logging.INFO) ch.setLevel(logging.DEBUG if debug else logging.WARNING) logger.setLevel(logging.DEBUG if debug else logging.INFO) logger.info("Wallet client started.") if ctx.obj is None: ctx.obj = {} ctx.obj['wallet_path'] = wallet_path ctx.obj['passphrase'] = passphrase if ctx.invoked_subcommand not in ['create', 'restore', 'startdaemon', 'stopdaemon', 'uninstalldaemon']: # Check that the wallet path exists if not Two1Wallet.check_wallet_file(ctx.obj['wallet_path']): click.echo("ERROR: Wallet file does not exist or is corrupt.") ctx.exit(code=7) p = get_passphrase() if passphrase else '' try: logger.info("Loading wallet %s ..." % (wp)) ctx.obj['wallet'] = Wallet(wallet_path=wallet_path, data_provider=ctx.obj['data_provider'], passphrase=p) logger.info("... loading complete.") except exceptions.PassphraseError as e: click.echo(str(e)) ctx.exit(code=1) except (TypeError, ValueError) as e: logger.error("Internal wallet error. Please report this as a bug.") logger.debug("".join(traceback.format_tb(e.__traceback__))) ctx.exit(code=2) def _on_close(): try: ctx.obj['wallet'].sync_wallet_file() except: pass ctx.call_on_close(_on_close)