From f41827c80023816c47432bcbb374609858c2ba3a Mon Sep 17 00:00:00 2001 From: k9ert <117085+k9ert@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:38:14 +0200 Subject: [PATCH 1/4] deps: bump hwi 2.4.0 -> 3.1.0; require Python >=3.9,<3.13 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings: Trezor Safe 5 support, Ledger Nano model IDs, tr() single-leaf descriptor parsing fix, Debian Buster Linux build compat. requires-python lifted from >=3.7,<4.0 to >=3.9,<3.13 — HWI 3.x drops 3.7/3.8 (and caps at <3.13). Drops support for those Python versions. API impact on Specter (verified): all hwilib symbols Specter imports (hwwclient, errors, key, psbt, _script, descriptor, common, bitbox02_lib.util, trezorlib.{transport,messages,protobuf}) are unchanged 2.4 -> 3.1. enumerate() gained allow_emulators=False kwarg (default ignores emulators). Specter's own simulator paths in vendored jade/keepkey/specter_diy clients are unaffected; the only behavioral change is Trezor emulator no longer auto-discovered through Specter (no callers observed in tests/). cbor2 stays at 5.9.0 — HWI 3.1's cap is <6.0.0, no conflict. (HWI 3.2 introduces a <5.8 cap that conflicts with the GHSA-3c37-wwvx-h642 fix floor; relax PR filed upstream as bitcoin-core/HWI#832, prerequisite for a later 3.2/3.3 bump.) Note: only mechanical add'n to requirements.txt is the new pip-tools 7 boilerplate text; cbor2 and all other transitive deps unchanged. Refs: - https://github.com/cryptoadvance/specter-desktop/issues/2597 (BitBox02 Nova; deferred to 3.2/3.3 bump after upstream cap relax merges) - https://github.com/bitcoin-core/HWI/pull/832 --- pyproject.toml | 5 +++-- requirements.in | 2 +- requirements.txt | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c822bb7db..df8c7953d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ readme = "README.md" license = {file = "LICENSE"} -requires-python = ">=3.7,<4.0" +requires-python = ">=3.9,<3.13" dynamic = ["dependencies", "version"] @@ -52,7 +52,8 @@ markers = [ "slow: mark test as slow.", "elm: mark test as elementsd dependent", "bottleneck: mark a test as so ressource intensive that it can create a bottleneck where the test just fails due to a lack of ressources", - "threading: test needs threading to work" + "threading: test needs threading to work", + "jade_hardware: requires a real Jade attached and an operator; opt-in only via --run-jade-hardware" ] filterwarnings = [ diff --git a/requirements.in b/requirements.in index 86990f404..35282fa9e 100644 --- a/requirements.in +++ b/requirements.in @@ -6,7 +6,7 @@ Flask-Cors==6.0.0 Flask-Login==0.6.3 Flask-RESTful==0.3.10 Flask-HTTPAuth==4.8.0 -hwi==2.4.0 +hwi==3.1.0 python-dotenv==0.21.1 requests==2.31.0 pysocks==1.7.1 diff --git a/requirements.txt b/requirements.txt index 704474bb5..3d622e9b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -432,9 +432,9 @@ hidapi==0.14.0 \ --hash=sha256:fb4e94e45f6dddb20d59501187721e5d3b02e6cc8a59d261dd5cac739008582a \ --hash=sha256:fc9ec2321bf3b0b4953910aa87c0c8ab5f93b1f113a9d3d4f18845ce54708d13 # via hwi -hwi==2.4.0 \ - --hash=sha256:3eaa7593f1ab360569eacdd9507dab75532bb58e8cd991d8ad72f5c4fcb67997 \ - --hash=sha256:7cb7ef2a4db4bc434815374d9bad43c6425491f77828314a2d2898d3e86d3f04 +hwi==3.1.0 \ + --hash=sha256:21ba92bb06e2f805e2806c686f2c50d02db6826a363b01e44052415755504d6f \ + --hash=sha256:42e875cbb616a91638fb90679cad93edb5075bf375e92fc1709be9b2a3dfd59c # via -r requirements.in idna==3.7 \ --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ @@ -921,5 +921,6 @@ wtforms==3.1.2 \ # via flask-wtf # WARNING: The following packages were not pinned, but pip requires them to be -# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. +# pinned when the requirements file includes hashes and the requirement is not +# satisfied by a package already installed. Consider using the --allow-unsafe flag. # setuptools From fe66a9940e74a1786afe144f36289ff4ab99336c Mon Sep 17 00:00:00 2001 From: k9ert <117085+k9ert@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:22:51 +0200 Subject: [PATCH 2/4] test: opt-in Jade-hardware test suite Adds tests/test_jade_hardware.py with three tests exercising HWIBridge against a real Blockstream Jade: - enumerate (fingerprint + path) - extract_xpub at m/84h/0h/0h - sign_tx with canned PSBT (skipped without fixture) Gated by --run-jade-hardware flag in tests/conftest.py via the jade_hardware marker (registered in pyproject.toml). Default behaviour is skip-and-pass so GitHub Actions remain green without any workflow change. Run locally with: pytest --run-jade-hardware tests/test_jade_hardware.py -s Verified: 3 skipped without flag; 3 collected with flag; unrelated tests untouched by the modifyitems hook. --- tests/conftest.py | 15 ++++++ tests/test_jade_hardware.py | 104 ++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 tests/test_jade_hardware.py diff --git a/tests/conftest.py b/tests/conftest.py index a7368138a..cac7f7e22 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -89,9 +89,24 @@ def pytest_addoption(parser): default="master", help="Version of elementsd (something which works with git checkout ...)", ) + parser.addoption( + "--run-jade-hardware", + action="store_true", + default=False, + help="Run tests marked jade_hardware (real Jade attached + operator).", + ) listen() +def pytest_collection_modifyitems(config, items): + if config.getoption("--run-jade-hardware"): + return + skip = pytest.mark.skip(reason="opt-in via --run-jade-hardware") + for item in items: + if "jade_hardware" in item.keywords: + item.add_marker(skip) + + def pytest_generate_tests(metafunc): # ToDo: use custom compiled version of bitcoind # E.g. test again bitcoind version [currentRelease] + master-branch diff --git a/tests/test_jade_hardware.py b/tests/test_jade_hardware.py new file mode 100644 index 000000000..6ad758557 --- /dev/null +++ b/tests/test_jade_hardware.py @@ -0,0 +1,104 @@ +"""Opt-in tests requiring a physical Blockstream Jade attached + operator. + +Skipped by default. Enable with:: + + pytest --run-jade-hardware tests/test_jade_hardware.py -s + +The ``-s`` is required so that operator prompts reach your terminal. + +Operator setup +-------------- +- Connect the Jade over USB and unlock it. +- For the signing test, the Jade must be initialised with a known seed + matching ``tests/fixtures/jade_hardware.psbt`` (see fixture for the + derivation paths used). If the fixture is absent the signing test + skips with a hint. +""" + +from pathlib import Path + +import pytest + +from cryptoadvance.specter.hwi_rpc import HWIBridge + + +FIXTURE_DIR = Path(__file__).parent / "fixtures" +PSBT_FIXTURE = FIXTURE_DIR / "jade_hardware.psbt" + + +def _enumerate_jade(bridge: HWIBridge): + devs = bridge.enumerate() + return [d for d in devs if d.get("type") == "jade"] + + +def _prompt(msg: str) -> None: + print(f"\n>>> {msg}") + try: + input(">>> Press Enter when ready... ") + except EOFError: + # Non-interactive runner: continue without blocking. + pass + + +@pytest.mark.jade_hardware +def test_jade_enumerate_via_specter(): + """Jade is detected by Specter's HWIBridge and reports a fingerprint.""" + _prompt("Connect and unlock the Jade.") + bridge = HWIBridge() + jades = _enumerate_jade(bridge) + assert jades, "no Jade detected — connect, unlock, and rerun" + jade = jades[0] + assert jade.get("fingerprint"), f"Jade enumerated without fingerprint: {jade}" + assert jade.get("path"), f"Jade enumerated without path: {jade}" + + +@pytest.mark.jade_hardware +def test_jade_extract_xpub_via_specter(): + """Specter can pull an xpub at a known derivation from Jade.""" + _prompt("Unlock the Jade. You may be asked to confirm the xpub export.") + bridge = HWIBridge() + jades = _enumerate_jade(bridge) + assert jades, "no Jade detected" + fingerprint = jades[0]["fingerprint"] + + # BIP84 native segwit, mainnet account 0 + xpub_line = bridge.extract_xpub( + derivation="m/84h/0h/0h", + device_type="jade", + fingerprint=fingerprint, + ) + assert xpub_line, "extract_xpub returned empty" + assert xpub_line.startswith("["), f"unexpected format: {xpub_line!r}" + assert "]" in xpub_line, f"unexpected format: {xpub_line!r}" + body = xpub_line.split("]", 1)[1].strip() + assert body.startswith(("xpub", "zpub", "ypub")), f"unexpected xpub: {body[:8]}" + + +@pytest.mark.jade_hardware +def test_jade_sign_psbt_via_specter(): + """End-to-end: Specter signs a canned PSBT through Jade. + + Requires ``tests/fixtures/jade_hardware.psbt`` whose input derivations + match the seed loaded on the attached Jade. Skipped if absent. + """ + if not PSBT_FIXTURE.exists(): + pytest.skip( + f"missing fixture {PSBT_FIXTURE}; create one whose input paths " + "match the seed on the test Jade" + ) + + psbt_b64 = PSBT_FIXTURE.read_text().strip() + + _prompt("Unlock the Jade. You will be asked to confirm the transaction on device.") + bridge = HWIBridge() + jades = _enumerate_jade(bridge) + assert jades, "no Jade detected" + fingerprint = jades[0]["fingerprint"] + + signed = bridge.sign_tx( + psbt=psbt_b64, + device_type="jade", + fingerprint=fingerprint, + ) + assert signed, "sign_tx returned empty" + assert signed != psbt_b64, "PSBT was returned unsigned" From 0ffdeb1e413f5ca6cbd4c9a58fd0840dbe7c6dd8 Mon Sep 17 00:00:00 2001 From: k9ert <117085+k9ert@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:14:42 +0200 Subject: [PATCH 3/4] test(jade_hardware): full sign path + fixtures + docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pass explicit chain= to enumerate / extract_xpub / sign_tx; HWIBridge default chain="" makes Jade's _network() raise BadArgumentError before extract_xpub's post-init override fires. (Pre-existing Specter bug, separate issue.) - Sign test exercises end-to-end PSBT signing via Jade in Temporary Signer mode loaded from a SeedQR of the public BIP-39 abandon vector (xfp 73c5da0a). Pre-checks the connected fingerprint and fails fast with a clear hint if the wrong seed is loaded. - Fixtures (committed): tests/fixtures/jade_hardware.psbt — testnet PSBT spending m/84'/1'/0'/0/0; carries both witness_utxo AND non_witness_utxo so Jade firmware can verify the input amount per the SegWit fee-spoof mitigation. Built with embit; psbt_faker can't emit non_witness_utxo. tests/fixtures/jade_seedqr_abandon.png — Standard SeedQR (48-digit BIP-39 indices) for scanning into Jade's Temp Signer. ASCII variant in jade_seedqr_abandon.txt. - docs/development.md gains a "Hardware-attended Jade tests" subsection covering the operator setup procedure, expected fingerprint, network choice, and what the device prompts for. Verified end-to-end against HWI 3.1.0 + cbor2 5.9.0 + a real Jade. --- docs/development.md | 31 ++++++++++ tests/fixtures/jade_hardware.psbt | 1 + tests/fixtures/jade_seedqr_abandon.png | Bin 0 -> 354 bytes tests/fixtures/jade_seedqr_abandon.txt | 15 +++++ tests/test_jade_hardware.py | 80 ++++++++++++++++++------- 5 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 tests/fixtures/jade_hardware.psbt create mode 100644 tests/fixtures/jade_seedqr_abandon.png create mode 100644 tests/fixtures/jade_seedqr_abandon.txt diff --git a/docs/development.md b/docs/development.md index 251ee8f64..bb0fcc955 100644 --- a/docs/development.md +++ b/docs/development.md @@ -14,6 +14,7 @@ - [Set up virtualenv](#set-up-virtualenv) - [If `pip install` fails on `cryptography==3.4.x`](#if-pip-install-fails-on-cryptography34x) - [How to run the tests](#how-to-run-the-tests) + - [Hardware-attended Jade tests](#hardware-attended-jade-tests) - [Code-Style](#code-style) - [Developing on tests](#developing-on-tests) - [bitcoin-specific stuff](#bitcoin-specific-stuff) @@ -209,6 +210,36 @@ Print the logging output live to the terminal: pytest --capture=no --log-cli-level=DEBUG ``` +### Hardware-attended Jade tests + +`tests/test_jade_hardware.py` exercises Specter's HWI integration end-to-end against a physical Blockstream Jade. It is gated by `--run-jade-hardware` and skipped by default, so GitHub Actions ignore it without any workflow change. + +Run with `-s` so operator prompts reach the terminal: +``` +pytest --run-jade-hardware tests/test_jade_hardware.py -s +``` + +Three tests, increasing operator effort: + +| Test | What it does | Operator action | +|---|---|---| +| `test_jade_enumerate_via_specter` | `HWIBridge.enumerate()` finds the Jade and returns a fingerprint | Connect Jade, unlock | +| `test_jade_extract_xpub_via_specter` | Pulls xpub at `m/84h/0h/0h` (mainnet) | Confirm xpub export on device | +| `test_jade_sign_psbt_via_specter` | Signs a canned testnet PSBT through Specter's sign path | Boot Jade in Temporary Signer mode, scan SeedQR, confirm tx | + +The signing test uses the public **BIP-39 abandon vector** (`abandon abandon ... about`) so the PSBT fixture matches anyone's Jade once they load that seed. Setup procedure: + +1. Power-cycle the Jade so it shows the boot menu. +2. Choose **Temporary Signer** -> **Scan SeedQR**. +3. Display `tests/fixtures/jade_seedqr_abandon.png` (or `cat tests/fixtures/jade_seedqr_abandon.txt` for the ASCII version) and scan it with the Jade camera. +4. When Jade asks for the network, select **TESTNET**. +5. Press Enter at the test prompt. +6. Confirm the transaction on the Jade screen when it pops up (~99,500 sats to a testnet bech32 output, ~99,500 change auto-validated, 1,000 fee). + +Temporary Signer state is held in RAM only and wiped on power-cycle/USB-unplug — your real seed is not affected. Expected master fingerprint for the abandon vector is `73c5da0a`; the test fails fast with a clear hint if the loaded seed is wrong. + +The fixture PSBT (`tests/fixtures/jade_hardware.psbt`) was generated with embit against m/84'/1'/0'/0/0 of the abandon vector, including a synthetic `non_witness_utxo` so Jade can verify the input amount per the SegWit fee-spoof mitigation. + Get the log-output of bitcoind side by side with the test-output. For sure you will only see the logs if the test fails. ``` pytest --bitcoind-log-stdout diff --git a/tests/fixtures/jade_hardware.psbt b/tests/fixtures/jade_hardware.psbt new file mode 100644 index 000000000..3a68aa829 --- /dev/null +++ b/tests/fixtures/jade_hardware.psbt @@ -0,0 +1 @@ +cHNidP8BAHECAAAAAYz9aGwGnHSzsZXBuDlKg9hNl6KeLvo8czy6WHaPTGwGAAAAAAD/////AqyEAQAAAAAAFgAUb6AWUAo8anN+uyYOLdyni6kjRVishAEAAAAAABYAFC80qhzwClOwVaKRoDp9RfCmmItSAAAAAAABAFMCAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wFR/////wFADQMAAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhAAAAAAEBH0ANAwAAAAAAFgAU0MSj7wnpl7bpnjl+UY/j5BoRjKEiBgLnqyU3tdSelwMJquBunknzbOHJ/rvUTsjg0cygtPnDGRhzxdoKVAAAgAEAAIAAAACAAAAAAAAAAAAAACICA11J7M1U0AmeQ2did8em1GJdYR2oil30m/lReneRp3elGHPF2gpUAACAAQAAgAAAAIABAAAAAAAAAAA= diff --git a/tests/fixtures/jade_seedqr_abandon.png b/tests/fixtures/jade_seedqr_abandon.png new file mode 100644 index 0000000000000000000000000000000000000000..9ec31d505b9853c4cfeee5ef2fa10a7b45f29fc0 GIT binary patch literal 354 zcmV-o0iFJdP)-v^hyX>cot}JQToH_Y=1x(Y$2F+JyGFN=RewZflNQxie{->TR=jh1c-XRMh} zO`FFC%5_@l3f=ttQkjY1Y%QRtbd|Mv?4ly12y8#UV$iN^^9HJ!eQEU-*Q{*wmR`vV zNq9whkgdLY#ZOhKEIK@}oARYEn9{ZAHjQV^N2e^@F`maE86ncq9sFxU*#Y~9rvBwu zeFaZUvGnzC44v*cbc6d1Dl|I#q^A8KgEKvdMGtJ&wRv2F%6w~;bF}?Mtok2HuPK|? z&%>7Njo=>6?5qCw8#t ``Scan SeedQR``. + 3. Scan ``tests/fixtures/jade_seedqr_abandon.png`` (or display + ``tests/fixtures/jade_seedqr_abandon.txt`` and scan from screen). + 4. Confirm the **testnet** network on Jade. + 5. The PSBT at ``tests/fixtures/jade_hardware.psbt`` is fabricated by + Coldcard's psbt_faker against this exact seed. + +Temporary Signer state lives in RAM only; it's wiped on power-cycle. Your +real seed is not affected. """ from pathlib import Path @@ -24,10 +37,17 @@ FIXTURE_DIR = Path(__file__).parent / "fixtures" PSBT_FIXTURE = FIXTURE_DIR / "jade_hardware.psbt" +SEEDQR_PNG = FIXTURE_DIR / "jade_seedqr_abandon.png" +SEEDQR_TXT = FIXTURE_DIR / "jade_seedqr_abandon.txt" +ABANDON_FINGERPRINT = "73c5da0a" -def _enumerate_jade(bridge: HWIBridge): - devs = bridge.enumerate() + +def _enumerate_jade(bridge: HWIBridge, chain: str = "main"): + # HWIBridge.enumerate defaults chain="" which Chain.argparse passes + # through unchanged; Jade's enumerate then fails with + # "Unhandled network: ". Pass an explicit chain. + devs = bridge.enumerate(chain=chain) return [d for d in devs if d.get("type") == "jade"] @@ -36,7 +56,6 @@ def _prompt(msg: str) -> None: try: input(">>> Press Enter when ready... ") except EOFError: - # Non-interactive runner: continue without blocking. pass @@ -59,13 +78,17 @@ def test_jade_extract_xpub_via_specter(): bridge = HWIBridge() jades = _enumerate_jade(bridge) assert jades, "no Jade detected" - fingerprint = jades[0]["fingerprint"] + fingerprint = jades[0].get("fingerprint") + assert fingerprint, f"Jade enumerated without fingerprint: {jades[0]}" - # BIP84 native segwit, mainnet account 0 + # chain must be passed explicitly: HWIBridge.extract_xpub default is + # chain="" which Specter's JadeClient.__init__ rejects via _network() + # before extract_xpub's post-init override can apply. xpub_line = bridge.extract_xpub( derivation="m/84h/0h/0h", device_type="jade", fingerprint=fingerprint, + chain="main", ) assert xpub_line, "extract_xpub returned empty" assert xpub_line.startswith("["), f"unexpected format: {xpub_line!r}" @@ -76,29 +99,42 @@ def test_jade_extract_xpub_via_specter(): @pytest.mark.jade_hardware def test_jade_sign_psbt_via_specter(): - """End-to-end: Specter signs a canned PSBT through Jade. + """End-to-end: Specter signs the canned abandon-vector PSBT through Jade. - Requires ``tests/fixtures/jade_hardware.psbt`` whose input derivations - match the seed loaded on the attached Jade. Skipped if absent. + Requires Jade in Temporary Signer mode with the abandon-vector seed + (see module docstring). The fixture PSBT was generated by Coldcard's + psbt_faker against m/84'/1'/0' on testnet; xfp is 73c5da0a. """ - if not PSBT_FIXTURE.exists(): - pytest.skip( - f"missing fixture {PSBT_FIXTURE}; create one whose input paths " - "match the seed on the test Jade" - ) + assert PSBT_FIXTURE.exists(), f"missing fixture: {PSBT_FIXTURE}" + + seedqr_hint = ( + f"\n PNG: {SEEDQR_PNG}\n" + f" ASCII: cat {SEEDQR_TXT}" + ) + _prompt( + "Put Jade in Temporary Signer mode -> Scan SeedQR -> select TESTNET." + f"\n SeedQR for the BIP-39 abandon-vector lives at:{seedqr_hint}\n" + " Then confirm the transaction on device when prompted." + ) psbt_b64 = PSBT_FIXTURE.read_text().strip() - _prompt("Unlock the Jade. You will be asked to confirm the transaction on device.") bridge = HWIBridge() - jades = _enumerate_jade(bridge) + jades = _enumerate_jade(bridge, chain="test") assert jades, "no Jade detected" - fingerprint = jades[0]["fingerprint"] + fingerprint = jades[0].get("fingerprint") + assert fingerprint, f"Jade enumerated without fingerprint: {jades[0]}" + assert fingerprint.lower() == ABANDON_FINGERPRINT, ( + f"connected Jade fingerprint is {fingerprint}; " + f"expected {ABANDON_FINGERPRINT} (abandon-vector). " + "Are you in Temporary Signer mode with the right SeedQR?" + ) signed = bridge.sign_tx( psbt=psbt_b64, device_type="jade", fingerprint=fingerprint, + chain="test", ) assert signed, "sign_tx returned empty" assert signed != psbt_b64, "PSBT was returned unsigned" From 4b5b69f10d98bdfd15e523b701e17df7fdeceadd Mon Sep 17 00:00:00 2001 From: k9ert <117085+k9ert@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:42:31 +0200 Subject: [PATCH 4/4] test(jade_hardware): tighten marker gate + skip implicit HWIBridge init Address Copilot review on #2616: - conftest: use item.get_closest_marker("jade_hardware") instead of string-match against item.keywords. keywords includes fixture names and sub-keywords, so the substring match could in principle skip an unrelated test that happened to use a "jade_hardware"-named fixture. - tests: pass skip_hwi_initialisation=True to HWIBridge(). The default ctor calls self.enumerate() with chain="" which is the very pre- existing bug the tests are working around (#2615). Skipping the implicit init avoids triggering it three times per session and is also faster. --- tests/conftest.py | 2 +- tests/test_jade_hardware.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index cac7f7e22..6be08fab0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -103,7 +103,7 @@ def pytest_collection_modifyitems(config, items): return skip = pytest.mark.skip(reason="opt-in via --run-jade-hardware") for item in items: - if "jade_hardware" in item.keywords: + if item.get_closest_marker("jade_hardware") is not None: item.add_marker(skip) diff --git a/tests/test_jade_hardware.py b/tests/test_jade_hardware.py index cdce35254..3bdb947be 100644 --- a/tests/test_jade_hardware.py +++ b/tests/test_jade_hardware.py @@ -63,7 +63,7 @@ def _prompt(msg: str) -> None: def test_jade_enumerate_via_specter(): """Jade is detected by Specter's HWIBridge and reports a fingerprint.""" _prompt("Connect and unlock the Jade.") - bridge = HWIBridge() + bridge = HWIBridge(skip_hwi_initialisation=True) jades = _enumerate_jade(bridge) assert jades, "no Jade detected — connect, unlock, and rerun" jade = jades[0] @@ -75,7 +75,7 @@ def test_jade_enumerate_via_specter(): def test_jade_extract_xpub_via_specter(): """Specter can pull an xpub at a known derivation from Jade.""" _prompt("Unlock the Jade. You may be asked to confirm the xpub export.") - bridge = HWIBridge() + bridge = HWIBridge(skip_hwi_initialisation=True) jades = _enumerate_jade(bridge) assert jades, "no Jade detected" fingerprint = jades[0].get("fingerprint") @@ -119,7 +119,7 @@ def test_jade_sign_psbt_via_specter(): psbt_b64 = PSBT_FIXTURE.read_text().strip() - bridge = HWIBridge() + bridge = HWIBridge(skip_hwi_initialisation=True) jades = _enumerate_jade(bridge, chain="test") assert jades, "no Jade detected" fingerprint = jades[0].get("fingerprint")