feat: add blockstream jade hardware wallet - #765
coreyphillips wants to merge 15 commits into
Conversation
|
…wipe The foreground Trezor reconnect was launched in a task that only marked the session active once it ran, so a Jade operation taking the lock in between could dial alongside it. The reconnect now registers before the lock is released and is cancelled when the session is released. A wipe only tore down the Jade, so a Trezor reconnect finishing after the wipe could save its device back. The Trezor session is now released and its loaded devices dropped before the wipe clears storage.
addressed - reaudit confirmed
There was a problem hiding this comment.
No HIGH/MEDIUM findings, and no LOW worth posting.
Checked:
- Funds. The amount and address on
HwSendSignVieware the values passed tosignAndBroadcast. The fee shown comes from the compose that produced the PSBT. The PSBT is built app-side from the stored xpub, and the device contributes only the fingerprint. Jade results go throughfinalizePsbt(originalPsbt:signedPsbt:), and core 0.5.17's guards pin the unsigned tx and each input'sprevious_outputand runinterpreter_check. No double broadcast:pendingPaymentcaches the signed tx per request, re-sign happens only when no result reached the app, andcancel()is a no-op onceisBroadcastUnresolved. The transfer path does the same viapendingHwFundingBroadcast. - Device identity.
requireJadeSession(holding:)runs beforesignPsbt.rejectUnusableDevicefails closed on a nil efuse MAC. Known-device reconnects are efuse-checked before unlock, and the advertised name is only a hint. - Key material. No xpub, PSBT, fingerprint or PIN in logs.
HwKnownDeviceonly gainsvendorandjadeDeviceId. The backup snapshot is still{walletId: customLabel}. - Upgrade from Trezor-only state. Storage keys are unchanged. A missing
vendordecodes as Trezor unless the id has thejade:prefix, and unknown vendors are preserved on rewrite.bitkit-core0.5.14 → 0.5.17 adds only Jade code and the newfinalize_psbtguards, with no storage or migration change. - Lifecycle. A pairing failure before
addOrUpdateKnownDevicestores nothing.resetForWipecovers both vendors ahead ofremovePersistentDomain, and theconnectEpochbump stops a late connect from persisting after a wipe. Both greptile threads are fixed at head. - Parity with synonymdev/bitkit-android#1231. Signing identity check, efuse nil check, and the Receive tab not tearing down a live Trezor session: present. Activity-teardown cache and USB-attach disconnect: absent, since iOS has no activity lifecycle or USB. Locked Jade with no exit from Send:
canLeavecovers it.
Not verified: device-only behaviour, i.e. link release during on-device confirm at 30s in background, and firmware dropping the confirm screen. That needs a real Jade and iPhone (manual tests 5b/8).
There was a problem hiding this comment.
Verdict: ✅ Approve
Reaudit: diff 6 files.
Counterpart synonymdev/bitkit-android#1231: not compared.
Findings:
N/A
Audit:
Already done in comment.
QA:
Tested on sim-1 iPhone 17 Pro simulator on iOS 26.5
Test 1
Test 2
Test 3
Test 4
Test 5
Test 6
Test 7
Test 8
Test 9
Test 10
Test 11
Test 12
Test 13
Approve.
Coverage:
Unit tests: 100% - Focused tests cover synchronous reconnect activity, cross-vendor release, wipe cancellation, both-vendor reset, and loaded-device clearing.
QA: 0 of 13 manual tests passed
Reviewed by gpt-5.6-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)
This PR:
It is the iOS counterpart to bitkit-android#1231 and requires bitkit-core 0.5.17, which carries the Jade module (synonymdev/bitkit-core#153).
Description
A Jade can now be paired from Connect Hardware, unlocked with the PIN entered on the device, and used like a paired Trezor: watch-only balances, on-device receive address verification, and on-device signing for both a send and a transfer to spending. The protocol, the pinserver round trip and every deadline live in bitkit-core. This app supplies the byte transport over Bluetooth and the UI that drives the flows.
Differences from Android worth knowing:
Tab-trezorfor a Trezor wallet (unchanged) andTab-jadefor a Jade. bitkit-android#1231 renames its tag toTab-hardwarefor both, while the shared helper in bitkit-e2e-tests and Android'sReceiveHardwareFlowTeststill tapTab-trezor.Out of Scope
Bitkit/Assets.xcassets/Illustrations/jade-placeholder.imageset: the Jade illustration is a placeholder vector until design supplies the real asset.journeys/hardware-wallet: no Jade journeys, since there is no Jade emulator inbitkit-dockerand the simulator has no Bluetooth.Design
N/A: no design available.
Preview
Recording of the Bluetooth pair, verify and sign flow to be attached from a physical Jade.
QA Notes
Manual Tests
These need a physical Jade and a physical iPhone, since the simulator has no Bluetooth.
regression:Trezor → pair, Receive → Verify on Device, Send and Transfer to Spending: all work as before, and the receive tab reads "Trezor".regression:Trezor connected → background and reopen Bitkit: the Trezor reconnects.Automated Checks
JadeManagerTests.swift: pairing, unlock, account export with the taproot fallback, identity checks, reconnect by advertised name, silent reconnect, the reconnect loop, cancel and teardown ordering, the disconnect notice, and background release. It ports the Bluetooth-relevant cases of Android'sJadeRepoTest.kt.JadeTransportTests.swift,JadeBLELinkStateTests.swiftandBlockingQueueTests.swift: chunk sizing, read timeouts, close waking every waiter, error codes, the stale bond message, and that Bluetooth stays off until a Jade is used.JadeServiceTests.swift:finalizePsbtreaches core instead of recursing.JadeDeviceIdentityTests.swift: device ids, models, advertised name matching and network mapping.HwWalletManagerVendorTests.swift: vendor routing, one vendor session at a time, the Jade signing identity check, on-device verification, the Jade fingerprint on compose, passphrase refusal for a Jade, per-vendor reconnect timeouts, and foreground reconnect.HwErrorPredicateTests.swiftandHwErrorPresenterTests.swift: Jade error classification and copy.HwEngagedSessionTests.swift: the receive sheet releases only a session it used.TrezorManagerVendorIsolationTests.swift: Trezor loads, renames and forgets never touch a Jade entry.HwWalletVendorPresentationTests.swift: vendor assets, copy and the receive tab identifier.HwKnownDeviceMatchingTests.swiftandHwKnownDeviceStorageTests.swift(renamed from their Trezor names): vendor slices, migration of entries saved before this change, and entries of an unknown vendor being kept.HwConnectViewModelTests.swift: Jade discovery, cancel and unlock hint.HwFundingSignerTests.swift: when the send sheet can be left, cancel release, and the attempt counter.TransferViewModelHwTests.swift,HwWalletIdTests.swift,HwWalletNameTests.swift,HwWalletManagerTests.swiftandHwWalletManagerPassphraseTests.swift.node scripts/validate-translations.jsreports 0 errors, and SwiftFormat passes on the changed files.