Export wallets with BIP 389 multipath descriptors for both receive and change branches #2534
Cirrus CI / test
failed
Feb 5, 2026 in 5m 17s
Task Summary
Instruction test failed in 03:35
Details
✅ 00:09 clone
✅ 00:05 bitcoind_installation
✅ 00:02 elementsd_installation
✅ 00:01 verify
✅ 01:06 pip
✅ 00:14 install
❌ 03:35 test
src/cryptoadvance/specter/util/requests_tools.py 37 12 68%
src/cryptoadvance/specter/util/rpcauth.py 29 18 38%
src/cryptoadvance/specter/util/setup_states.py 1 0 100%
src/cryptoadvance/specter/util/sha256sum.py 9 7 22%
src/cryptoadvance/specter/util/shell.py 41 10 76%
src/cryptoadvance/specter/util/specter_migrator.py 155 14 91%
src/cryptoadvance/specter/util/tor.py 50 33 34%
src/cryptoadvance/specter/util/tor_setup_tasks.py 42 9 79%
src/cryptoadvance/specter/util/tx.py 52 5 90%
src/cryptoadvance/specter/util/version.py 148 24 84%
src/cryptoadvance/specter/util/wallet_importer.py 225 66 71%
src/cryptoadvance/specter/util/xpub.py 9 0 100%
src/cryptoadvance/specter/wallet/__init__.py 3 0 100%
src/cryptoadvance/specter/wallet/abstract_wallet.py 15 3 80%
src/cryptoadvance/specter/wallet/addresslist.py 170 31 82%
src/cryptoadvance/specter/wallet/tx_fetcher.py 90 15 83%
src/cryptoadvance/specter/wallet/txlist.py 364 50 86%
src/cryptoadvance/specter/wallet/wallet.py 930 308 67%
src/cryptoadvance/specterext/devhelp/__init__.py 0 0 100%
src/cryptoadvance/specterext/devhelp/config.py 4 0 100%
src/cryptoadvance/specterext/devhelp/console.py 30 18 40%
src/cryptoadvance/specterext/devhelp/controller.py 62 52 16%
src/cryptoadvance/specterext/devhelp/devices/devhelpdevice.py 6 0 100%
src/cryptoadvance/specterext/devhelp/service.py 45 10 78%
src/cryptoadvance/specterext/electrum/__init__.py 0 0 100%
src/cryptoadvance/specterext/electrum/controller.py 19 3 84%
src/cryptoadvance/specterext/electrum/devices/__init__.py 0 0 100%
src/cryptoadvance/specterext/electrum/devices/electrum.py 16 3 81%
src/cryptoadvance/specterext/electrum/service.py 19 0 100%
src/cryptoadvance/specterext/notifications/__init__.py 0 0 100%
src/cryptoadvance/specterext/notifications/config.py 8 0 100%
src/cryptoadvance/specterext/notifications/controller.py 45 33 27%
src/cryptoadvance/specterext/notifications/notification_manager.py 171 90 47%
src/cryptoadvance/specterext/notifications/notifications.py 72 12 83%
src/cryptoadvance/specterext/notifications/service.py 42 14 67%
src/cryptoadvance/specterext/notifications/ui_notifications.py 103 46 55%
src/cryptoadvance/specterext/notifications/websockets_server_client.py 217 180 17%
src/cryptoadvance/specterext/spectrum/__init__.py 0 0 100%
src/cryptoadvance/specterext/spectrum/bridge_rpc.py 62 19 69%
src/cryptoadvance/specterext/spectrum/config.py 38 0 100%
src/cryptoadvance/specterext/spectrum/controller.py 106 78 26%
src/cryptoadvance/specterext/spectrum/controller_helpers.py 39 6 85%
src/cryptoadvance/specterext/spectrum/service.py 99 41 59%
src/cryptoadvance/specterext/spectrum/spectrum_node.py 141 57 60%
src/cryptoadvance/specterext/swan/__init__.py 0 0 100%
src/cryptoadvance/specterext/swan/client.py 135 39 71%
src/cryptoadvance/specterext/swan/config.py 11 0 100%
src/cryptoadvance/specterext/swan/controller.py 130 74 43%
src/cryptoadvance/specterext/swan/service.py 193 100 48%
---------------------------------------------------------------------------------------------------------------
TOTAL 20820 10038 52%
=========================== short test summary info ============================
FAILED tests/test_managers_wallet.py::test_multisig_wallet_backup_and_restore
FAILED tests/test_wallet.py::test_account_map_combined_descriptor - Assertion...
====== 2 failed, 224 passed, 6 skipped, 291 warnings in 213.08s (0:03:33) ======Annotations
Check failure on line 628 in tests/test_managers_wallet.py
cirrus-ci / test
tests/test_managers_wallet.py#L628
tests.test_managers_wallet.test_multisig_wallet_backup_and_restore
Raw output
bitcoin_regtest = <cryptoadvance.specter.process_controller.bitcoind_controller.BitcoindPlainController object at 0x7f163e5a7010>
caplog = <_pytest.logging.LogCaptureFixture object at 0x7f15c4dcbd60>
specter_regtest_configured = <cryptoadvance.specter.specter.Specter object at 0x7f15c4d3beb0>
node = <Node name=Standard node fullpath=/tmp/specter_home_tmp_jqgz8tm_/nodes/standard_node.json>
def test_multisig_wallet_backup_and_restore(
bitcoin_regtest, caplog, specter_regtest_configured, node
):
"""
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=BitcoinCore.device_type, 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=BitcoinCore.device_type, 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_multisig_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],
)
# Wallet is unfunded
address = wallet.getnewaddress()
bitcoin_regtest.testcoin_faucet(address, amount=3.3)
wallet.update_balance()
assert wallet.amount_total == 3.3
# 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, node)
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)
tests/test_managers_wallet.py:628:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/cryptoadvance/specter/util/descriptor.py:468: in parse_signers
desc_key = Key.parse_xpub(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class 'cryptoadvance.specter.key.Key'>, xpub = '1', purpose = ''
@classmethod
def parse_xpub(cls, xpub, purpose=""):
derivation = ""
arr = xpub.strip().split("]")
original = arr[-1]
if len(arr) > 1:
derivation = arr[0].replace("'", "h").lower()
xpub = arr[1]
fingerprint = ""
# just to be sure fgp/1h/2/3/ is also parsed correctly
# because we have free-form inputs
derivation = derivation.rstrip("/")
if derivation != "":
if derivation[0] != "[":
raise Exception("Missing leading [")
derivation_path = derivation[1:].split("/")
try:
fng = bytes.fromhex(
derivation_path[0].replace("-", "")
) # coldcard has hexstrings like 7c-2c-8e-1b
except Exception:
raise Exception("Fingerprint is not hex")
if len(fng) != 4:
> raise Exception("Incorrect fingerprint length")
E Exception: Incorrect fingerprint length
src/cryptoadvance/specter/key.py:89: Exception
Check failure on line 394 in tests/test_wallet.py
cirrus-ci / test
tests/test_wallet.py#L394
tests.test_wallet.test_account_map_combined_descriptor
Raw output
funded_hot_wallet_1 = <Wallet name=a_hotwallet_948909 alias=a_hotwallet_948909>
@pytest.mark.slow
def test_account_map_combined_descriptor(funded_hot_wallet_1: Wallet):
"""
Test that account_map property returns a combined descriptor with <0;1> syntax
instead of just the receive descriptor with /0/*
When exporting wallets, only the receive descriptor /0/* was included,
but it should include both receive and change addresses using multipath descriptors.
"""
wallet = funded_hot_wallet_1
# Parse the account_map JSON
account_map_dict = json.loads(wallet.account_map)
# Check that descriptor key exists
assert "descriptor" in account_map_dict
descriptor_str = account_map_dict["descriptor"]
# The descriptor should use BIP 389 multipath syntax <0;1>
# not just the receive path /0/*
> assert "<0;1>" in descriptor_str, (
f"Expected descriptor with <0;1> multipath syntax, "
f"but got: {descriptor_str}"
)
E AssertionError: Expected descriptor with <0;1> multipath syntax, but got: wpkh([915227ed/84h/1h/0h]tpubDCFZjEWPiCL57asbtmeSTMq2GW9GnBfK7f4MjKtVsRux3nC66uefXd3SPqaQYtRmPg6Aacqj2R2qQ1BwCtm6xmr9zqJSLnyhpZQgpfMCbgx/{0,1}/*)#yxak3f5f
E assert '<0;1>' in 'wpkh([915227ed/84h/1h/0h]tpubDCFZjEWPiCL57asbtmeSTMq2GW9GnBfK7f4MjKtVsRux3nC66uefXd3SPqaQYtRmPg6Aacqj2R2qQ1BwCtm6xmr9zqJSLnyhpZQgpfMCbgx/{0,1}/*)#yxak3f5f'
tests/test_wallet.py:394: AssertionError
Loading