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
예제 #2
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