"""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 operator prompts reach your terminal. Operator setup -------------- For ``test_jade_enumerate_via_specter`` and ``test_jade_extract_xpub_via_specter``: any Jade with any seed (your real one is fine — those tests only read public material). For ``test_jade_sign_psbt_via_specter``: the Jade must be in **Temporary Signer mode** with the public **BIP-39 abandon vector** seed (``abandon abandon ... about``) loaded. The simplest path: 1. Power-cycle the Jade so it's at the boot menu. 2. Choose ``Temporary Signer`` -> ``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 import pytest from cryptoadvance.specter.hwi_rpc import HWIBridge 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, 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"] def _prompt(msg: str) -> None: print(f"\n>>> {msg}") try: input(">>> Press Enter when ready... ") except EOFError: 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(skip_hwi_initialisation=True) 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(skip_hwi_initialisation=True) jades = _enumerate_jade(bridge) assert jades, "no Jade detected" fingerprint = jades[0].get("fingerprint") assert fingerprint, f"Jade enumerated without fingerprint: {jades[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}" 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 the canned abandon-vector PSBT through Jade. 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. """ 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() bridge = HWIBridge(skip_hwi_initialisation=True) jades = _enumerate_jade(bridge, chain="test") assert jades, "no Jade detected" 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"