def test_WalletImporter_integration(specter_regtest_configured, bitcoin_regtest): """ WalletImporter can load a wallet from a backup json with unknown devices and initialize a watch-only wallet that can receive funds and update its balance. """ specter = specter_regtest_configured someuser: User = specter.user_manager.add_user( User.from_json( { "id": "someuser", "username": "******", "password": "******", "config": {}, "is_admin": False, }, specter, )) specter.user_manager.save() specter.check() # Create a Wallet wallet_json = '{"label": "another_simple_wallet", "blockheight": 0, "descriptor": "wpkh([1ef4e492/84h/1h/0h]tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc/0/*)#xp8lv5nr", "devices": [{"type": "trezor", "label": "trezor"}]} ' wallet_importer = WalletImporter(wallet_json, specter, device_manager=someuser.device_manager) wallet_importer.create_nonexisting_signers( someuser.device_manager, { "unknown_cosigner_0_name": "trezor", "unknown_cosigner_0_type": "trezor" }, ) dm: DeviceManager = someuser.device_manager wallet = wallet_importer.create_wallet(someuser.wallet_manager) # fund it with some coins bitcoin_regtest.testcoin_faucet(address=wallet.getnewaddress(), confirm_payment=False) # There can be a delay in the node generating the faucet deposit tx so keep # rechecking until it's done (or we timeout). for i in range(0, 15): wallet.update() if wallet.update_balance()["untrusted_pending"] != 0: break else: time.sleep(2) wallet = someuser.wallet_manager.get_by_alias("another_simple_wallet") assert wallet.update_balance()["untrusted_pending"] == 20
def test_multisig_wallet_backup_and_restore(caplog, specter_regtest_configured): """ Multisig wallets should be able to be backed up and re-imported with or without the "devices" attr in the json backup. """ caplog.set_level(logging.INFO) device_manager = specter_regtest_configured.device_manager wallet_manager = specter_regtest_configured.wallet_manager device = device_manager.get_by_alias("trezor") device_type = device.device_type # Get the multisig 'wsh' testnet key for key in device.keys: if key.key_type == "wsh" and key.xpub.startswith("tpub"): break # Create a pair of hot wallet signers hot_wallet_1_device = device_manager.add_device( name="hot_key_1", device_type=DeviceTypes.BITCOINCORE, keys=[] ) hot_wallet_1_device.setup_device(file_password=None, wallet_manager=wallet_manager) hot_wallet_1_device.add_hot_wallet_keys( mnemonic=generate_mnemonic(strength=128), passphrase="", paths=["m/48h/1h/0h/2h"], file_password=None, wallet_manager=wallet_manager, testnet=True, keys_range=[0, 1000], keys_purposes=[], ) hot_wallet_2_device = device_manager.add_device( name="hot_key_2", device_type=DeviceTypes.BITCOINCORE, keys=[] ) hot_wallet_2_device.setup_device(file_password=None, wallet_manager=wallet_manager) hot_wallet_2_device.add_hot_wallet_keys( mnemonic=generate_mnemonic(strength=128), passphrase="", paths=["m/48h/1h/0h/2h"], file_password=None, wallet_manager=wallet_manager, testnet=True, keys_range=[0, 1000], keys_purposes=[], ) # create the multisig wallet wallet = wallet_manager.create_wallet( name="my_test_wallet", sigs_required=2, key_type=key.key_type, keys=[key, hot_wallet_1_device.keys[0], hot_wallet_2_device.keys[0]], devices=[device, hot_wallet_1_device, hot_wallet_2_device], ) # Fund the wallet address = wallet.getnewaddress() wallet.rpc.generatetoaddress(101, address) # update the wallet data balance = wallet.get_balance() assert balance["trusted"] > 0.0 # Save the json backup wallet_backup = json.loads(wallet.account_map.replace("\\\\", "").replace("'", "h")) assert "devices" in wallet_backup # Clear everything out as if we've never seen this wallet or device before wallet_manager.delete_wallet(wallet) device_manager.remove_device(device, wallet_manager=wallet_manager) assert wallet.name not in wallet_manager.wallets_names assert device.name not in device_manager.devices_names # Parse the backed up wallet (code adapted from the new_wallet endpoint) ( wallet_name, recv_descriptor, cosigners_types, ) = WalletImporter.parse_wallet_data_import(wallet_backup) descriptor = Descriptor.parse( AddChecksum(recv_descriptor.split("#")[0]), testnet=is_testnet(specter_regtest_configured.chain), ) ( keys, cosigners, unknown_cosigners, unknown_cosigners_types, ) = descriptor.parse_signers(device_manager.devices, cosigners_types) assert cosigners_types[0]["label"] == "Trezor" assert cosigners_types[0]["type"] == device_type assert cosigners_types[1]["label"] == "hot_key_1" assert cosigners_types[1]["type"] == DeviceTypes.BITCOINCORE assert cosigners_types[2]["label"] == "hot_key_2" assert cosigners_types[2]["type"] == DeviceTypes.BITCOINCORE # Re-create the Trezor device new_device = device_manager.add_device( name=unknown_cosigners[0][1], device_type=unknown_cosigners_types[0], keys=[unknown_cosigners[0][0]], ) keys.append(unknown_cosigners[0][0]) cosigners.append(new_device) wallet = wallet_manager.create_wallet( name=wallet_name, sigs_required=descriptor.multisig_M, key_type=descriptor.address_type, keys=keys, devices=cosigners, ) # Sync the new wallet in bitcoincore to its existing utxos wallet.rpc.rescanblockchain(0) # We restored the wallet's utxos assert wallet.get_balance()["trusted"] > 0.0 # Now do it again, but without the newer "devices" attr del wallet_backup["devices"] # Clear everything out as if we've never seen this wallet or device before wallet_manager.delete_wallet(wallet) for device_names in device_manager.devices: device = device_manager.devices[device_names] device_manager.remove_device(device, wallet_manager=wallet_manager) assert wallet.name not in wallet_manager.wallets_names assert device.name not in device_manager.devices_names # Parse the backed up wallet (code adapted from the new_wallet endpoint) ( wallet_name, recv_descriptor, cosigners_types, ) = WalletImporter.parse_wallet_data_import(wallet_backup) descriptor = Descriptor.parse( AddChecksum(recv_descriptor.split("#")[0]), testnet=is_testnet(specter_regtest_configured.chain), ) ( keys, cosigners, unknown_cosigners, unknown_cosigners_types, ) = descriptor.parse_signers(device_manager.devices, cosigners_types) # Now we don't know any of the cosigners' types assert len(cosigners_types) == 0 assert unknown_cosigners_types[0] == DeviceTypes.GENERICDEVICE # Re-create all three devices for i, (unknown_cosigner_key, label) in enumerate(unknown_cosigners): # 'label' will be unknown assert label is None new_device = device_manager.add_device( name=f"{wallet_name} signer {i + 1}", device_type=unknown_cosigners_types[i], keys=[unknown_cosigner_key], ) keys.append(unknown_cosigner_key) cosigners.append(new_device) wallet = wallet_manager.create_wallet( name=wallet_name, sigs_required=descriptor.multisig_M, key_type=descriptor.address_type, keys=keys, devices=cosigners, ) # Sync the new wallet in bitcoincore to its existing utxos wallet.rpc.rescanblockchain(0) # We restored the wallet's utxos assert wallet.get_balance()["trusted"] > 0.0
def test_singlesig_wallet_backup_and_restore(caplog, specter_regtest_configured): """ Single-sig wallets should be able to be backed up and re-imported with or without the "devices" attr in the json backup. """ caplog.set_level(logging.INFO) device_manager = specter_regtest_configured.device_manager wallet_manager = specter_regtest_configured.wallet_manager device = device_manager.get_by_alias("trezor") device_type = device.device_type # Get the 'wkph' testnet key for key in device.keys: if key.key_type == "wpkh" and key.xpub.startswith("tpub"): break # create a wallet wallet = wallet_manager.create_wallet( name="my_test_wallet", sigs_required=1, key_type=key.key_type, keys=[key], devices=[device], ) # Fund the wallet address = wallet.getnewaddress() wallet.rpc.generatetoaddress(101, address) # update the wallet data balance = wallet.get_balance() assert balance["trusted"] > 0.0 # Save the json backup wallet_backup = json.loads(wallet.account_map) assert "devices" in wallet_backup # Clear everything out as if we've never seen this wallet or device before wallet_manager.delete_wallet(wallet) device_manager.remove_device(device, wallet_manager=wallet_manager) assert wallet.name not in wallet_manager.wallets_names assert device.name not in device_manager.devices_names # Parse the backed up wallet (code adapted from the new_wallet endpoint) ( wallet_name, recv_descriptor, cosigners_types, ) = WalletImporter.parse_wallet_data_import(wallet_backup) descriptor = Descriptor.parse( AddChecksum(recv_descriptor.split("#")[0]), testnet=is_testnet(specter_regtest_configured.chain), ) ( keys, cosigners, unknown_cosigners, unknown_cosigners_types, ) = descriptor.parse_signers(device_manager.devices, cosigners_types) device_name = cosigners_types[0]["label"] assert device_name == "Trezor" assert unknown_cosigners_types[0] == device_type # Re-create the device new_device = device_manager.add_device( name=device_name, device_type=unknown_cosigners_types[0], keys=[unknown_cosigners[0][0]], ) keys.append(unknown_cosigners[0][0]) cosigners.append(new_device) wallet = wallet_manager.create_wallet( name=wallet_name, sigs_required=descriptor.multisig_M, key_type=descriptor.address_type, keys=keys, devices=cosigners, ) # Sync the new wallet in bitcoincore to its existing utxos. wallet.rpc.rescanblockchain(0) # We restored the wallet's utxos assert wallet.get_balance()["trusted"] > 0.0 # Now do it again, but without the newer "devices" attr del wallet_backup["devices"] # Clear everything out as if we've never seen this wallet or device before wallet_manager.delete_wallet(wallet) device_manager.remove_device(device, wallet_manager=wallet_manager) assert wallet.name not in wallet_manager.wallets_names assert device.name not in device_manager.devices_names # Parse the backed up wallet (code adapted from the new_wallet endpoint) ( wallet_name, recv_descriptor, cosigners_types, ) = WalletImporter.parse_wallet_data_import(wallet_backup) descriptor = Descriptor.parse( AddChecksum(recv_descriptor.split("#")[0]), testnet=is_testnet(specter_regtest_configured.chain), ) ( keys, cosigners, unknown_cosigners, unknown_cosigners_types, ) = descriptor.parse_signers(device_manager.devices, cosigners_types) assert len(cosigners_types) == 0 assert unknown_cosigners_types[0] == DeviceTypes.GENERICDEVICE # Re-create the device new_device = device_manager.add_device( name=device_name, device_type=unknown_cosigners_types[0], keys=[unknown_cosigners[0][0]], ) keys.append(unknown_cosigners[0][0]) cosigners.append(new_device) wallet = wallet_manager.create_wallet( name=wallet_name, sigs_required=descriptor.multisig_M, key_type=descriptor.address_type, keys=keys, devices=cosigners, ) # Sync the new wallet in bitcoincore to its existing utxos wallet.rpc.rescanblockchain(0) # We restored the wallet's utxos assert wallet.get_balance()["trusted"] > 0.0
def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder): assert bitcoin_regtest.get_rpc().test_connection() config = { "rpc": { "autodetect": False, "datadir": "", "user": bitcoin_regtest.rpcconn.rpcuser, "password": bitcoin_regtest.rpcconn.rpcpassword, "port": bitcoin_regtest.rpcconn.rpcport, "host": bitcoin_regtest.rpcconn.ipaddress, "protocol": "http", }, "auth": { "method": "rpcpasswordaspin", }, } specter = Specter(data_folder=devices_filled_data_folder, config=config) assert specter.chain == "regtest" # Create a User someuser = specter.user_manager.add_user( User.from_json( user_dict={ "id": "someuser", "username": "******", "password": hash_password("somepassword"), "config": {}, "is_admin": False, "services": None, }, specter=specter, )) specter.user_manager.save() specter.check() assert not someuser.wallet_manager.working_folder is None # Create a Wallet wallet_json = '{"label": "a_simple_wallet", "blockheight": 0, "descriptor": "wpkh([1ef4e492/84h/1h/0h]tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc/0/*)#xp8lv5nr", "devices": [{"type": "trezor", "label": "trezor"}]} ' wallet_importer = WalletImporter(wallet_json, specter, device_manager=someuser.device_manager) wallet_importer.create_nonexisting_signers( someuser.device_manager, { "unknown_cosigner_0_name": "trezor", "unknown_cosigner_0_type": "trezor" }, ) dm: DeviceManager = someuser.device_manager wallet = wallet_importer.create_wallet(someuser.wallet_manager) try: # fund it with some coins bitcoin_regtest.testcoin_faucet(address=wallet.getnewaddress()) # make sure it's confirmed bitcoin_regtest.mine() # Realize that the wallet has funds: wallet.update() except SpecterError as se: if str(se).startswith("Timeout"): pytest.fail( "We got a Bitcoin-RPC timeout while setting up the test, minting some coins. Test Error! Check cpu/mem utilastion and btc/elem logs!" ) return else: raise se assert wallet.fullbalance >= 20 assert not specter.wallet_manager.working_folder is None try: yield specter finally: # Deleting all Wallets (this will also purge them on core) for user in specter.user_manager.users: for wallet in list(user.wallet_manager.wallets.values()): user.wallet_manager.delete_wallet( wallet, bitcoin_datadir=bitcoin_regtest.datadir, chain="regtest")
def test_WalletImporter_unit(): specter_mock = MagicMock() specter_mock.chain = "regtest" # coldcard, trezor import wallet_json = """ {"label": "MyTestMultisig", "blockheight": 0, "descriptor": "wsh(sortedmulti(1,[fb7c1f11/48h/1h/0h/2h]tpubDExnGppazLhZPNadP8Q5Vgee2QcvbyAf9GvGaEY7ALVJREaG2vdTqv1MHRoDtPaYP3y1DGVx7wrKKhsLhs26GY263uE6Wi3qNbi71AHZ6p7/0/*,[1ef4e492/48h/1h/0h/2h]tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ/0/*))#s0jemlck", "devices": [ {"type": "coldcard", "label": "MyColdcard"}, {"type": "trezor", "label": "MyTestTrezor"} ] } """ wallet_importer = WalletImporter(wallet_json, specter_mock) assert wallet_importer.wallet_name == "MyTestMultisig" assert ( str(wallet_importer.descriptor) == "wsh(sortedmulti(1,[fb7c1f11/48h/1h/0h/2h]tpubDExnGppazLhZPNadP8Q5Vgee2QcvbyAf9GvGaEY7ALVJREaG2vdTqv1MHRoDtPaYP3y1DGVx7wrKKhsLhs26GY263uE6Wi3qNbi71AHZ6p7/0/*,[1ef4e492/48h/1h/0h/2h]tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ/0/*))" ) assert wallet_importer.cosigners_types == [ { "label": "MyColdcard", "type": "coldcard" }, { "label": "MyTestTrezor", "type": "trezor" }, ] # The descriptor (very briefly) assert [ key.origin.fingerprint.hex() for key in wallet_importer.descriptor.keys ] == ["fb7c1f11", "1ef4e492"] assert len(wallet_importer.keys) == 0 assert len(wallet_importer.cosigners) == 0 assert len(wallet_importer.unknown_cosigners) == 2 # it's a tuple: assert isinstance(wallet_importer.unknown_cosigners[0][0], Key) assert wallet_importer.unknown_cosigners[0][1] == "MyColdcard" assert isinstance(wallet_importer.unknown_cosigners[1][0], Key) assert wallet_importer.unknown_cosigners[1][1] == "MyTestTrezor" assert len(wallet_importer.unknown_cosigners_types) == 2 assert wallet_importer.unknown_cosigners_types == ["coldcard", "trezor"] request_form = { "unknown_cosigner_0_name": "MyColdcard", "unknown_cosigner_1_name": "MyTestTrezor", } wallet_importer.create_nonexisting_signers(MagicMock(), request_form) # The keys has been created assert len(wallet_importer.keys) == 2 # now we have 2 cosigners assert len(wallet_importer.cosigners) == 2 # But still 2 unknown cosigners assert len(wallet_importer.unknown_cosigners) == 2 wm_mock = MagicMock() wallet_mock = MagicMock() wm_mock.create_wallet.return_value = wallet_mock wallet_importer.create_wallet(wm_mock) assert wm_mock.create_wallet.called wm_mock.create_wallet.assert_called_once assert wallet_mock.keypoolrefill.called # electrum single-sig and multisig import (Not BIP39, but electrum seeds), Focus is here only on the correct descriptor singlesig_json = """ { "keystore": { "derivation": "m/0'", "pw_hash_version": 1, "root_fingerprint": "1f0a071c", "seed": "castle flight vessel game mushroom stumble noise list scheme episode sheriff squeeze", "seed_type": "segwit", "type": "bip32", "xprv": "vprv9FTrHquAzV9x9rNoJNA65YTwqyAjJHzs9kvPSfQqoPYKDPiAugDe6sCqrbkut2k3JEeXbmRN9aWMsdJZD1nGhdsCZbbmHrszvifAxo7oVjA", "xpub": "vpub5UTChMS4priFNLTGQPh6SgQgQ11DhkiiWyqzF3pTMj5J6C3KTDXtefXKhrrRPuF2TsEuoU9w6NvydP9axJQKuof6gzdvt1eDJSRZzy3WDzV" }, "use_encryption": false, "wallet_type": "standard" } """ singlesig_importer = WalletImporter(singlesig_json, specter_mock) assert ( str(singlesig_importer.descriptor) == "wpkh([1f0a071c/0h]vpub5UTChMS4priFNLTGQPh6SgQgQ11DhkiiWyqzF3pTMj5J6C3KTDXtefXKhrrRPuF2TsEuoU9w6NvydP9axJQKuof6gzdvt1eDJSRZzy3WDzV/0/*)" ) multisig_json = """ { "use_encryption": false, "wallet_type": "1of2", "x1/": { "derivation": "m/1'", "pw_hash_version": 1, "root_fingerprint": "9f09087f", "seed": "bicycle master jacket bring tornado faint bachelor violin delay equip february frog", "seed_type": "segwit", "type": "bip32", "xprv": "Vprv19bpWVjGnupHR8JU9p9RU4gWrUAYProMHQ5MxPw14b1Rtk8mB6V3vQSywnxwJ9GoJwt3Jgf4Qe2DcU9JzYoFj1FNZsuqY3kYt3Rt2JvG4wG", "xpub": "Vpub5gHreumWwHVxJbLG3Tp1sULdRVgUZBzFrhg9EuyJUropjEPXoVJe6u4r7y2EpwyVXHgehJ2PNn2R3kDxXLBGNnKmbicdibuoGmNjhCfyEZF" }, "x2/": { "derivation": "m/1'", "pw_hash_version": 1, "root_fingerprint": "1f0a071c", "seed": "castle flight vessel game mushroom stumble noise list scheme episode sheriff squeeze", "seed_type": "segwit", "type": "bip32", "xprv": "Vprv18fFgB8GFSawxQYZFf4q8umBXkVGg799uBeuAp1wXSkxX9dgvpwZnjCFwaffHauq1LZHZQPpei13HuMoR7kjTJg8HKWcmqbPTTzbVqUKj5Y", "xpub": "Vpub5fMHpbAWPpGcqsaM9JjRYKRJ6n1CqSL4UVFgTL4EwiZMMdtTZDm9yDp87ko2WA1N9dWLuM48H9oiPDE4nGPfpxFNe7ZkcSqc2L2ncuEXu4Z" } } """ multisig_importer = WalletImporter(multisig_json, specter_mock) assert ( str(multisig_importer.descriptor) == "wsh(sortedmulti(1,[9f09087f/1h]Vpub5gHreumWwHVxJbLG3Tp1sULdRVgUZBzFrhg9EuyJUropjEPXoVJe6u4r7y2EpwyVXHgehJ2PNn2R3kDxXLBGNnKmbicdibuoGmNjhCfyEZF/0/*,[1f0a071c/1h]Vpub5fMHpbAWPpGcqsaM9JjRYKRJ6n1CqSL4UVFgTL4EwiZMMdtTZDm9yDp87ko2WA1N9dWLuM48H9oiPDE4nGPfpxFNe7ZkcSqc2L2ncuEXu4Z/0/*))" )
def test_WalletImporter_unit(): specter_mock = MagicMock() specter_mock.chain = "regtest" wallet_json = """ {"label": "MyTestMultisig", "blockheight": 0, "descriptor": "wsh(sortedmulti(1,[fb7c1f11/48h/1h/0h/2h]tpubDExnGppazLhZPNadP8Q5Vgee2QcvbyAf9GvGaEY7ALVJREaG2vdTqv1MHRoDtPaYP3y1DGVx7wrKKhsLhs26GY263uE6Wi3qNbi71AHZ6p7/0/*,[1ef4e492/48h/1h/0h/2h]tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ/0/*))#s0jemlck", "devices": [ {"type": "coldcard", "label": "MyColdcard"}, {"type": "trezor", "label": "MyTestTrezor"} ] } """ wallet_importer = WalletImporter(wallet_json, specter_mock) assert wallet_importer.wallet_name == "MyTestMultisig" assert ( str(wallet_importer.descriptor) == "wsh(sortedmulti(1,[fb7c1f11/48h/1h/0h/2h]tpubDExnGppazLhZPNadP8Q5Vgee2QcvbyAf9GvGaEY7ALVJREaG2vdTqv1MHRoDtPaYP3y1DGVx7wrKKhsLhs26GY263uE6Wi3qNbi71AHZ6p7/0/*,[1ef4e492/48h/1h/0h/2h]tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ/0/*))" ) assert wallet_importer.cosigners_types == [ { "label": "MyColdcard", "type": "coldcard" }, { "label": "MyTestTrezor", "type": "trezor" }, ] # The descriptor (very briefly) assert [ key.origin.fingerprint.hex() for key in wallet_importer.descriptor.keys ] == ["fb7c1f11", "1ef4e492"] assert len(wallet_importer.keys) == 0 assert len(wallet_importer.cosigners) == 0 assert len(wallet_importer.unknown_cosigners) == 2 # it's a tuple: assert isinstance(wallet_importer.unknown_cosigners[0][0], Key) assert wallet_importer.unknown_cosigners[0][1] == "MyColdcard" assert isinstance(wallet_importer.unknown_cosigners[1][0], Key) assert wallet_importer.unknown_cosigners[1][1] == "MyTestTrezor" assert len(wallet_importer.unknown_cosigners_types) == 2 assert wallet_importer.unknown_cosigners_types == ["coldcard", "trezor"] request_form = { "unknown_cosigner_0_name": "MyColdcard", "unknown_cosigner_1_name": "MyTestTrezor", } wallet_importer.create_nonexisting_signers(MagicMock(), request_form) # The keys has been created assert len(wallet_importer.keys) == 2 # now we have 2 cosigners assert len(wallet_importer.cosigners) == 2 # But still 2 unknown cosigners assert len(wallet_importer.unknown_cosigners) == 2 wm_mock = MagicMock() wallet_mock = MagicMock() wm_mock.create_wallet.return_value = wallet_mock wallet_importer.create_wallet(wm_mock) assert wm_mock.create_wallet.called wm_mock.create_wallet.assert_called_once assert wallet_mock.keypoolrefill.called
def new_wallet(wallet_type): wallet_types = ["simple", "multisig", "import_wallet"] if wallet_type not in wallet_types: flash(_("Unknown wallet type requested"), "error") return redirect(url_for("wallets_endpoint.new_wallet_type")) err = None if request.method == "POST": action = request.form["action"] if action == "importwallet": try: wallet_importer: WalletImporter = WalletImporter( request.form["wallet_data"], app.specter, ) except SpecterError as se: flash(str(se), "error") return redirect(url_for("wallets_endpoint.new_wallet_type")) createwallet = "createwallet" in request.form if createwallet: # User might have renamed it wallet_importer.wallet_name = request.form["wallet_name"] wallet_importer.create_nonexisting_signers( app.specter.device_manager, request.form) try: wallet_importer.create_wallet(app.specter.wallet_manager) except SpecterError as se: flash(str(se), "error") return redirect( url_for("wallets_endpoint.new_wallet_type")) flash(_("Wallet imported successfully"), "info") try: wallet_importer.rescan_as_needed(app.specter) except SpecterError as se: flash(str(se), "error") return redirect( url_for( "wallets_endpoint.receive", wallet_alias=wallet_importer.wallet.alias, ) + "?newwallet=true") else: return render_template( "wallet/new_wallet/import_wallet.jinja", wallet_data=wallet_importer.wallet_json, wallet_type=wallet_importer.wallet_type, wallet_name=wallet_importer.wallet_name, cosigners=wallet_importer.cosigners, unknown_cosigners=wallet_importer.unknown_cosigners, unknown_cosigners_types=wallet_importer. unknown_cosigners_types, sigs_required=wallet_importer.sigs_required, sigs_total=wallet_importer.sigs_total, specter=app.specter, rand=rand, ) if action == "device": cosigners = [ app.specter.device_manager.get_by_alias(alias) for alias in request.form.getlist("devices") ] if not cosigners: return render_template( "wallet/new_wallet/new_wallet.jinja", wallet_type=wallet_type, error= _("No device was selected. Please select a device to create the wallet for." ), specter=app.specter, rand=rand, ) devices = get_devices_with_keys_by_type(app, cosigners, wallet_type) for device in devices: if len(device.keys) == 0: err = _( "Device {} doesn't have keys matching this wallet type" ).format(device.name) break name = wallet_type.title() wallet_name = name i = 2 while wallet_name in app.specter.wallet_manager.wallets_names: wallet_name = "%s %d" % (name, i) i += 1 return render_template( "wallet/new_wallet/new_wallet_keys.jinja", cosigners=devices, wallet_type=wallet_type, sigs_total=len(devices), sigs_required=max(len(devices) * 2 // 3, 1), error=err, specter=app.specter, rand=rand, ) if action == "key" and err is None: wallet_name = request.form["wallet_name"] address_type = request.form["type"] sigs_total = int(request.form.get("sigs_total", 1)) sigs_required = int(request.form.get("sigs_required", 1)) if wallet_name in app.specter.wallet_manager.wallets_names: err = _("Wallet already exists") if err: devices = [ app.specter.device_manager.get_by_alias( request.form.get("cosigner{}".format(i))) for i in range(0, sigs_total) ] return render_template( "wallet/new_wallet/new_wallet_keys.jinja", cosigners=devices, wallet_type=wallet_type, sigs_total=len(devices), sigs_required=max(len(devices) * 2 // 3, 1), error=err, specter=app.specter, rand=rand, ) keys = [] cosigners = [] devices = [] for i in range(sigs_total): try: key = request.form["key%d" % i] cosigner_name = request.form["cosigner%d" % i] cosigner = app.specter.device_manager.get_by_alias( cosigner_name) cosigners.append(cosigner) for k in cosigner.keys: if k.original == key: keys.append(k) break except: pass if len(keys) != sigs_total or len(cosigners) != sigs_total: err = _( "No keys were selected for device, please try adding keys first" ) devices = [ app.specter.device_manager.get_by_alias( request.form.get("cosigner{}".format(i))) for i in range(0, sigs_total) ] return render_template( "wallet/new_wallet/new_wallet_keys.jinja", cosigners=devices, wallet_type=wallet_type, sigs_total=len(devices), sigs_required=max(len(devices) * 2 // 3, 1), error=err, specter=app.specter, rand=rand, ) # create a wallet here try: wallet = app.specter.wallet_manager.create_wallet( wallet_name, sigs_required, address_type, keys, cosigners) except Exception as e: handle_exception(e) err = _("Failed to create wallet. Error: {}").format(e) return render_template( "wallet/new_wallet/new_wallet_keys.jinja", cosigners=cosigners, wallet_type=wallet_type, sigs_total=len(devices), sigs_required=max(len(devices) * 2 // 3, 1), error=err, specter=app.specter, rand=rand, ) app.logger.info("Created Wallet %s" % wallet_name) rescan_blockchain = "rescanblockchain" in request.form if rescan_blockchain: # old wallet - import more addresses wallet.keypoolrefill(0, wallet.IMPORT_KEYPOOL, change=False) wallet.keypoolrefill(0, wallet.IMPORT_KEYPOOL, change=True) if "utxo" in request.form.get("full_rescan_option"): explorer = None if "use_explorer" in request.form: if request.form["explorer"] == "CUSTOM": explorer = request.form["custom_explorer"] else: explorer = app.config["EXPLORERS_LIST"][ request.form["explorer"]]["url"] wallet.rescanutxo( explorer, app.specter.requests_session(explorer), app.specter.only_tor, ) app.specter.info["utxorescan"] = 1 app.specter.utxorescanwallet = wallet.alias else: app.logger.info("Rescanning Blockchain ...") startblock = int(request.form["startblock"]) try: wallet.rpc.rescanblockchain(startblock, no_wait=True) except Exception as e: handle_exception(e) err = "%r" % e wallet.getdata() return redirect( url_for("wallets_endpoint.receive", wallet_alias=wallet.alias) + "?newwallet=true") if action == "preselected_device": return render_template( "wallet/new_wallet/new_wallet_keys.jinja", cosigners=[ app.specter.device_manager.get_by_alias( request.form["device"]) ], wallet_type="simple", sigs_total=1, sigs_required=1, error=err, specter=app.specter, rand=rand, ) return render_template( "wallet/new_wallet/new_wallet.jinja", wallet_type=wallet_type, error=err, specter=app.specter, rand=rand, )