feat: pair trezor passphrase hidden wallets - #1142

Merged
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets
Aug 14, 2026
Merged

feat: pair trezor passphrase hidden wallets#1142
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets

Conversation

@jvsena42

@jvsena42jvsena42 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes#1060

This PR:

  1. Adds an optional Passphrase path to the Connect Hardware flow, so the hidden wallets of a paired Trezor can be watched alongside its standard wallet
  2. Tracks each passphrase-derived account set as its own hardware wallet identity, with its own home tile, funds label, activity scope and removal
  3. Asks for the passphrase again before signing a transfer when the Trezor session that held it is gone, and refuses to sign if it opens a different wallet
  4. Lets the connect flow offer an already paired device, so passphrase wallets can be added after the initial pairing
  5. Fixes a Trezor Bridge session bug that blocked every passphrase pairing after the first on the emulator

Description

Trezor keeps no record of its hidden wallets and forgets a passphrase with the session, so Bitkit watches each one by its extended public keys and asks for the passphrase again whenever a session has to be rebuilt. The passphrase is never persisted or logged, and both entry screens block screenshots and screen recording the same way the wallet's own passphrase screens do.

A device previously mapped to a single wallet. It now maps to one identity per passphrase, keyed by the wallet id derived from its accounts rather than by the transport id, which two identities on the same device share. Watchers, balances, activity, labels and removal all follow that identity, and removing one hidden wallet leaves the others paired.

Pairing a hidden wallet adds an entry next to the standard one instead of replacing it. The device is matched by shared key material, so re-reading accounts updates the right entry while a different passphrase creates a new one. Only the identity that currently holds the device session shows as connected, since only that one can sign.

Before signing a transfer, Bitkit reopens the hidden wallet and accepts the session only if its accounts resolve back to the wallet being spent from. A wrong passphrase is not rejected by the device — it silently derives another wallet — so the mismatch is caught, the stray wallet that reading its accounts created is dropped, and the transfer fails instead of signing from the wrong wallet. A session that is still open signs with no prompt.

Discovery skips devices that are already paired, which left the Add Hardware Wallet button searching forever and made the passphrase step unreachable after the first pairing. It now falls back to offering a known reachable device once no new one is found.

Separately, the Bridge transport used for the emulator offered a released session id as the previous one when re-acquiring, and the bridge answered wrong previous session. Since switching to a passphrase wallet closes and reopens the session, this blocked every passphrase pairing after the first. This only affects builds with the Bridge enabled; USB and BLE are unaffected.

Preview

no-wallet-add-with-passphrase.webm
Add.multiple.passphrase.wallets.webm
add-repeated-passphrase-wallet.webm
passphrase-disabled.webm
remove-wallet.webm
rename-wallet.webm
transfer-current-session.webm
transfer-other-section.mp4

QA Notes

Needs a Trezor with passphrase protection enabled. On the emulator start it with TREZOR_PASSPHRASE_PROTECTION=true ../bitkit-docker/scripts/trezor-emulator start and confirm on the device for every account a passphrase session reads, otherwise the call blocks until it times out.

Manual Tests

  • 1a. Settings → Hardware Wallets → Add → Found → Connect: Device Connected shows Passphrase and Finish.
    • 1b. Passphrase → enter a passphrase → Continue: Passphrase Funds Found shows the balance and Label Funds prefilled with the device name.
    • 1c. Rename the label → Finish: home shows a second hardware tile under that name.
  • 2. Home: headline total includes both hardware balances, and only the identity holding the device session shows a green connection indicator.
  • 3. Paired or Passphrase Funds Found → Passphrase → re-enter a passphrase already watched: toast reports it is already added, no tile is added.
  • 3b. Device with passphrase protection turned off in Trezor Suite (the factory default) → Paired → Passphrase → enter any passphrase: error says to enable passphrase protection in Trezor Suite, no wallet is added, and the standard wallet is not left marked as passphrase-protected (Settings ▸ Hardware Wallets → Transfer To Spending on it still signs without asking for a passphrase).
  • 4a. Settings → Hardware Wallets: one row per identity, each with its own balance.
    • 4b. Rename one row: only that identity is renamed.
    • 4c. Delete the hidden wallet row: the standard wallet keeps working and the device stays paired, no re-pairing needed.
  • 5. Settings → Hardware Wallets → Add on an already paired device: reaches Found and then Device Connected, instead of searching forever.
  • 6a. Hidden wallet tile → Transfer To Spending → Amount → Sign → Open Trezor Connect while its session is still open: signs with no passphrase prompt.
    • 6b. Force-stop and relaunch the app, then repeat: the passphrase sheet opens before signing.
    • 6c. Enter a wrong passphrase: error says it opens a different wallet, nothing is signed or broadcast, and no extra tile appears.
    • 6d. Enter the correct passphrase: signing continues normally.
  • 7.regression: Pair a device without using the Passphrase path: home tile, detail screen, rename, remove and Transfer To Spending behave as before.
  • 8. Both passphrase screens: screenshots and screen recording are blocked, and the passphrase is absent from files/logs/ and files/datastore/ (release build; blocking is disabled in debug).

Automated Checks

  • Unit tests added: cover a passphrase wallet being added next to the standard one, the flag surviving reconnects, and credentials surviving a scoped removal in TrezorRepoTest.kt.
  • Unit tests added: cover two identities on one device, per-identity balances, connected state, funding account, scoped removal, and connectWithPassphrase including the already-watched case and a device with passphrase protection off in HwWalletRepoTest.kt.
  • Unit tests added: cover the passphrase step effects, the duplicate toast, the label prefill, the paired step following the right identity, and the fallback to an already paired device in HwConnectViewModelTest.kt.
  • Unit tests added: cover signing with a live session, prompting once it is gone, and refusing a passphrase that opens another wallet in TransferViewModelTest.kt.
  • Unit tests added: cover re-acquiring after a release without the stale session in TrezorBridgeTransportTest.kt.
  • Unit tests modified: existing hardware tests moved to wallet-id keying in HwWalletRepoTest.kt, TransferViewModelTest.kt and HwConnectViewModelTest.kt.
  • Journeys added: passphrase-pairing.xml, passphrase-duplicate.xml, passphrase-settings-remove.xml and passphrase-transfer-to-spending.xml, including checks that the passphrase never reaches the logs or datastore.
  • Verified on a Pixel emulator against the Trezor emulator: pairing two hidden wallets alongside the standard one, per-identity labels, three home tiles, the connected indicator, and no passphrase in the logs. The duplicate, settings-remove and transfer journeys were not executed; the transfer one additionally needs regtest funds on a hidden wallet.
  • CI: standard compile, unit test and detekt checks run by the PR bot.

Known gaps

  • A hidden wallet can persist with only part of its accounts when a read fails with a device error that is not classified as transient, and its wallet id would change once the missing accounts are read. This is a pre-existing gap in the partial-save guard that passphrase sessions make easier to hit.
  • Two hidden wallets on the same device default to the same name until renamed, since the default label is the device name.

@jvsena42jvsena42 self-assigned this Aug 6, 2026
Comment threadapp/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt Fixed
…eId in it.deviceIds } and unconditionally writes pairedWalletId = wallet.id, so a wallets emission that does not yet contain the just-added hidden wallet resets the paired identity back to the standard wallet.
…se POST, so a failed release still leaves the stale session id cached.
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 10:05
@greptile-apps

greptile-appsBot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds independently tracked Trezor hidden-wallet identities and passphrase-based session restoration while preserving wallet-scoped balances, activity, labels, signing, and removal.

  • Keys hardware-wallet state and watchers by derived wallet identity instead of transport ID.
  • Adds passphrase pairing and signing-reconnection flows with wallet-identity verification.
  • Keeps sibling identities and transport credentials when removing one hidden wallet.
  • Refreshes stale Trezor Bridge sessions before retrying acquisition.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
app/src/main/java/to/bitkit/repositories/HwWalletRepo.ktMoves hardware-wallet aggregation, watcher lifecycle, signing, and removal to wallet-identity scope; the previously reported multi-transport removal issue is addressed.
app/src/main/java/to/bitkit/repositories/TrezorRepo.ktPersists multiple identities per physical device and manages passphrase-bound sessions while atomically filtering scoped removals.
app/src/main/java/to/bitkit/services/TrezorBridgeTransport.ktClears released sessions and retries acquisition after refreshing a stale Bridge session.
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.ktIntegrates passphrase restoration and identity verification into hardware-wallet signing.
app/src/main/java/to/bitkit/ui/sheets/hardware/HwConnectViewModel.ktAdds passphrase-wallet pairing, duplicate handling, and known-device discovery fallback.
app/src/main/java/to/bitkit/ui/screens/transfer/hardware/HwPassphrasePromptSheet.ktAdds the intentionally unmasked, screenshot-protected passphrase prompt used to restore signing sessions.

Sequence Diagram

sequenceDiagram
participant UI as Hardware Wallet UI
participant HW as HwWalletRepo
participant TR as TrezorRepo
participant Device as Trezor
participant Store as HwWalletStore
UI->>HW: connectWithPassphrase(deviceId, passphrase)
HW->>TR: set/connect passphrase wallet mode
TR->>Device: Open fresh passphrase session
Device-->>TR: Features and account keys
TR->>Store: Persist identity by derived wallet ID
TR-->>HW: Connected wallet identity
HW-->>UI: Add or select hidden-wallet tile
UI->>HW: Sign from wallet ID
alt matching session is live
HW->>Device: Sign transaction
else session is absent or belongs to another identity
UI->>HW: reconnectWithPassphrase(walletId, passphrase)
HW->>TR: Open fresh passphrase session
TR->>Device: Read account keys
alt derived wallet ID matches
HW->>Device: Sign transaction
else derived wallet ID differs
HW->>TR: Disconnect stray session
HW-->>UI: Reject passphrase mismatch
end
end
Loading

Reviews (3): Last reviewed commit: "fix: passphrase should not gate pending ..." | Re-trigger Greptile

Comment threadapp/src/main/java/to/bitkit/repositories/TrezorRepo.kt
@jvsena42
jvsena42 marked this pull request as draft August 10, 2026 10:11
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 11:33
@jvsena42
jvsena42 requested a review from ovitrifAugust 10, 2026 11:55
@jvsena42

jvsena42 commented Aug 10, 2026

Copy link
Copy Markdown
MemberAuthor

checking CI failures

Edit: both are backend failures

@jvsena42
jvsena42 requested review from ovitrif and removed request for ovitrifAugust 10, 2026 16:21
@jvsena42jvsena42 added this to the 2.5.0 milestone Aug 11, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as draft August 12, 2026 17:17
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 removed the request for review from ovitrifAugust 12, 2026 17:17
@jvsena42
jvsena42 marked this pull request as ready for review August 13, 2026 09:54
@jvsena42

Copy link
Copy Markdown
MemberAuthor

All test passing. Re-runned manual tests checklist with journeys

@jvsena42
jvsena42 requested a review from ovitrifAugust 13, 2026 11:01

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, passphrase identities stay scoped to their own tiles, session, and removal without replacing the standard wallet.

@jvsena42
jvsena42 merged commit 92c085a into masterAug 14, 2026
27 checks passed
@jvsena42
jvsena42 deleted the feat/hw-passphrase-wallets branch August 14, 2026 10:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware wallet: passphrase (hidden) wallet pairing

3 participants

@jvsena42@ovitrif@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: pair trezor passphrase hidden wallets - #1142

Merged
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets
Aug 14, 2026
Merged

feat: pair trezor passphrase hidden wallets#1142
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets

Conversation

@jvsena42

@jvsena42jvsena42 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes#1060

This PR:

  1. Adds an optional Passphrase path to the Connect Hardware flow, so the hidden wallets of a paired Trezor can be watched alongside its standard wallet
  2. Tracks each passphrase-derived account set as its own hardware wallet identity, with its own home tile, funds label, activity scope and removal
  3. Asks for the passphrase again before signing a transfer when the Trezor session that held it is gone, and refuses to sign if it opens a different wallet
  4. Lets the connect flow offer an already paired device, so passphrase wallets can be added after the initial pairing
  5. Fixes a Trezor Bridge session bug that blocked every passphrase pairing after the first on the emulator

Description

Trezor keeps no record of its hidden wallets and forgets a passphrase with the session, so Bitkit watches each one by its extended public keys and asks for the passphrase again whenever a session has to be rebuilt. The passphrase is never persisted or logged, and both entry screens block screenshots and screen recording the same way the wallet's own passphrase screens do.

A device previously mapped to a single wallet. It now maps to one identity per passphrase, keyed by the wallet id derived from its accounts rather than by the transport id, which two identities on the same device share. Watchers, balances, activity, labels and removal all follow that identity, and removing one hidden wallet leaves the others paired.

Pairing a hidden wallet adds an entry next to the standard one instead of replacing it. The device is matched by shared key material, so re-reading accounts updates the right entry while a different passphrase creates a new one. Only the identity that currently holds the device session shows as connected, since only that one can sign.

Before signing a transfer, Bitkit reopens the hidden wallet and accepts the session only if its accounts resolve back to the wallet being spent from. A wrong passphrase is not rejected by the device — it silently derives another wallet — so the mismatch is caught, the stray wallet that reading its accounts created is dropped, and the transfer fails instead of signing from the wrong wallet. A session that is still open signs with no prompt.

Discovery skips devices that are already paired, which left the Add Hardware Wallet button searching forever and made the passphrase step unreachable after the first pairing. It now falls back to offering a known reachable device once no new one is found.

Separately, the Bridge transport used for the emulator offered a released session id as the previous one when re-acquiring, and the bridge answered wrong previous session. Since switching to a passphrase wallet closes and reopens the session, this blocked every passphrase pairing after the first. This only affects builds with the Bridge enabled; USB and BLE are unaffected.

Preview

no-wallet-add-with-passphrase.webm
Add.multiple.passphrase.wallets.webm
add-repeated-passphrase-wallet.webm
passphrase-disabled.webm
remove-wallet.webm
rename-wallet.webm
transfer-current-session.webm
transfer-other-section.mp4

QA Notes

Needs a Trezor with passphrase protection enabled. On the emulator start it with TREZOR_PASSPHRASE_PROTECTION=true ../bitkit-docker/scripts/trezor-emulator start and confirm on the device for every account a passphrase session reads, otherwise the call blocks until it times out.

Manual Tests

  • 1a. Settings → Hardware Wallets → Add → Found → Connect: Device Connected shows Passphrase and Finish.
    • 1b. Passphrase → enter a passphrase → Continue: Passphrase Funds Found shows the balance and Label Funds prefilled with the device name.
    • 1c. Rename the label → Finish: home shows a second hardware tile under that name.
  • 2. Home: headline total includes both hardware balances, and only the identity holding the device session shows a green connection indicator.
  • 3. Paired or Passphrase Funds Found → Passphrase → re-enter a passphrase already watched: toast reports it is already added, no tile is added.
  • 3b. Device with passphrase protection turned off in Trezor Suite (the factory default) → Paired → Passphrase → enter any passphrase: error says to enable passphrase protection in Trezor Suite, no wallet is added, and the standard wallet is not left marked as passphrase-protected (Settings ▸ Hardware Wallets → Transfer To Spending on it still signs without asking for a passphrase).
  • 4a. Settings → Hardware Wallets: one row per identity, each with its own balance.
    • 4b. Rename one row: only that identity is renamed.
    • 4c. Delete the hidden wallet row: the standard wallet keeps working and the device stays paired, no re-pairing needed.
  • 5. Settings → Hardware Wallets → Add on an already paired device: reaches Found and then Device Connected, instead of searching forever.
  • 6a. Hidden wallet tile → Transfer To Spending → Amount → Sign → Open Trezor Connect while its session is still open: signs with no passphrase prompt.
    • 6b. Force-stop and relaunch the app, then repeat: the passphrase sheet opens before signing.
    • 6c. Enter a wrong passphrase: error says it opens a different wallet, nothing is signed or broadcast, and no extra tile appears.
    • 6d. Enter the correct passphrase: signing continues normally.
  • 7.regression: Pair a device without using the Passphrase path: home tile, detail screen, rename, remove and Transfer To Spending behave as before.
  • 8. Both passphrase screens: screenshots and screen recording are blocked, and the passphrase is absent from files/logs/ and files/datastore/ (release build; blocking is disabled in debug).

Automated Checks

  • Unit tests added: cover a passphrase wallet being added next to the standard one, the flag surviving reconnects, and credentials surviving a scoped removal in TrezorRepoTest.kt.
  • Unit tests added: cover two identities on one device, per-identity balances, connected state, funding account, scoped removal, and connectWithPassphrase including the already-watched case and a device with passphrase protection off in HwWalletRepoTest.kt.
  • Unit tests added: cover the passphrase step effects, the duplicate toast, the label prefill, the paired step following the right identity, and the fallback to an already paired device in HwConnectViewModelTest.kt.
  • Unit tests added: cover signing with a live session, prompting once it is gone, and refusing a passphrase that opens another wallet in TransferViewModelTest.kt.
  • Unit tests added: cover re-acquiring after a release without the stale session in TrezorBridgeTransportTest.kt.
  • Unit tests modified: existing hardware tests moved to wallet-id keying in HwWalletRepoTest.kt, TransferViewModelTest.kt and HwConnectViewModelTest.kt.
  • Journeys added: passphrase-pairing.xml, passphrase-duplicate.xml, passphrase-settings-remove.xml and passphrase-transfer-to-spending.xml, including checks that the passphrase never reaches the logs or datastore.
  • Verified on a Pixel emulator against the Trezor emulator: pairing two hidden wallets alongside the standard one, per-identity labels, three home tiles, the connected indicator, and no passphrase in the logs. The duplicate, settings-remove and transfer journeys were not executed; the transfer one additionally needs regtest funds on a hidden wallet.
  • CI: standard compile, unit test and detekt checks run by the PR bot.

Known gaps

  • A hidden wallet can persist with only part of its accounts when a read fails with a device error that is not classified as transient, and its wallet id would change once the missing accounts are read. This is a pre-existing gap in the partial-save guard that passphrase sessions make easier to hit.
  • Two hidden wallets on the same device default to the same name until renamed, since the default label is the device name.

@jvsena42jvsena42 self-assigned this Aug 6, 2026
Comment threadapp/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt Fixed
…eId in it.deviceIds } and unconditionally writes pairedWalletId = wallet.id, so a wallets emission that does not yet contain the just-added hidden wallet resets the paired identity back to the standard wallet.
…se POST, so a failed release still leaves the stale session id cached.
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 10:05
@greptile-apps

greptile-appsBot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds independently tracked Trezor hidden-wallet identities and passphrase-based session restoration while preserving wallet-scoped balances, activity, labels, signing, and removal.

  • Keys hardware-wallet state and watchers by derived wallet identity instead of transport ID.
  • Adds passphrase pairing and signing-reconnection flows with wallet-identity verification.
  • Keeps sibling identities and transport credentials when removing one hidden wallet.
  • Refreshes stale Trezor Bridge sessions before retrying acquisition.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
app/src/main/java/to/bitkit/repositories/HwWalletRepo.ktMoves hardware-wallet aggregation, watcher lifecycle, signing, and removal to wallet-identity scope; the previously reported multi-transport removal issue is addressed.
app/src/main/java/to/bitkit/repositories/TrezorRepo.ktPersists multiple identities per physical device and manages passphrase-bound sessions while atomically filtering scoped removals.
app/src/main/java/to/bitkit/services/TrezorBridgeTransport.ktClears released sessions and retries acquisition after refreshing a stale Bridge session.
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.ktIntegrates passphrase restoration and identity verification into hardware-wallet signing.
app/src/main/java/to/bitkit/ui/sheets/hardware/HwConnectViewModel.ktAdds passphrase-wallet pairing, duplicate handling, and known-device discovery fallback.
app/src/main/java/to/bitkit/ui/screens/transfer/hardware/HwPassphrasePromptSheet.ktAdds the intentionally unmasked, screenshot-protected passphrase prompt used to restore signing sessions.

Sequence Diagram

sequenceDiagram
participant UI as Hardware Wallet UI
participant HW as HwWalletRepo
participant TR as TrezorRepo
participant Device as Trezor
participant Store as HwWalletStore
UI->>HW: connectWithPassphrase(deviceId, passphrase)
HW->>TR: set/connect passphrase wallet mode
TR->>Device: Open fresh passphrase session
Device-->>TR: Features and account keys
TR->>Store: Persist identity by derived wallet ID
TR-->>HW: Connected wallet identity
HW-->>UI: Add or select hidden-wallet tile
UI->>HW: Sign from wallet ID
alt matching session is live
HW->>Device: Sign transaction
else session is absent or belongs to another identity
UI->>HW: reconnectWithPassphrase(walletId, passphrase)
HW->>TR: Open fresh passphrase session
TR->>Device: Read account keys
alt derived wallet ID matches
HW->>Device: Sign transaction
else derived wallet ID differs
HW->>TR: Disconnect stray session
HW-->>UI: Reject passphrase mismatch
end
end
Loading

Reviews (3): Last reviewed commit: "fix: passphrase should not gate pending ..." | Re-trigger Greptile

Comment threadapp/src/main/java/to/bitkit/repositories/TrezorRepo.kt
@jvsena42
jvsena42 marked this pull request as draft August 10, 2026 10:11
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 11:33
@jvsena42
jvsena42 requested a review from ovitrifAugust 10, 2026 11:55
@jvsena42

jvsena42 commented Aug 10, 2026

Copy link
Copy Markdown
MemberAuthor

checking CI failures

Edit: both are backend failures

@jvsena42
jvsena42 requested review from ovitrif and removed request for ovitrifAugust 10, 2026 16:21
@jvsena42jvsena42 added this to the 2.5.0 milestone Aug 11, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as draft August 12, 2026 17:17
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 removed the request for review from ovitrifAugust 12, 2026 17:17
@jvsena42
jvsena42 marked this pull request as ready for review August 13, 2026 09:54
@jvsena42

Copy link
Copy Markdown
MemberAuthor

All test passing. Re-runned manual tests checklist with journeys

@jvsena42
jvsena42 requested a review from ovitrifAugust 13, 2026 11:01

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, passphrase identities stay scoped to their own tiles, session, and removal without replacing the standard wallet.

@jvsena42
jvsena42 merged commit 92c085a into masterAug 14, 2026
27 checks passed
@jvsena42
jvsena42 deleted the feat/hw-passphrase-wallets branch August 14, 2026 10:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware wallet: passphrase (hidden) wallet pairing

3 participants

@jvsena42@ovitrif@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: pair trezor passphrase hidden wallets - #1142

Merged
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets
Aug 14, 2026
Merged

feat: pair trezor passphrase hidden wallets#1142
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets

Conversation

@jvsena42

@jvsena42jvsena42 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes#1060

This PR:

  1. Adds an optional Passphrase path to the Connect Hardware flow, so the hidden wallets of a paired Trezor can be watched alongside its standard wallet
  2. Tracks each passphrase-derived account set as its own hardware wallet identity, with its own home tile, funds label, activity scope and removal
  3. Asks for the passphrase again before signing a transfer when the Trezor session that held it is gone, and refuses to sign if it opens a different wallet
  4. Lets the connect flow offer an already paired device, so passphrase wallets can be added after the initial pairing
  5. Fixes a Trezor Bridge session bug that blocked every passphrase pairing after the first on the emulator

Description

Trezor keeps no record of its hidden wallets and forgets a passphrase with the session, so Bitkit watches each one by its extended public keys and asks for the passphrase again whenever a session has to be rebuilt. The passphrase is never persisted or logged, and both entry screens block screenshots and screen recording the same way the wallet's own passphrase screens do.

A device previously mapped to a single wallet. It now maps to one identity per passphrase, keyed by the wallet id derived from its accounts rather than by the transport id, which two identities on the same device share. Watchers, balances, activity, labels and removal all follow that identity, and removing one hidden wallet leaves the others paired.

Pairing a hidden wallet adds an entry next to the standard one instead of replacing it. The device is matched by shared key material, so re-reading accounts updates the right entry while a different passphrase creates a new one. Only the identity that currently holds the device session shows as connected, since only that one can sign.

Before signing a transfer, Bitkit reopens the hidden wallet and accepts the session only if its accounts resolve back to the wallet being spent from. A wrong passphrase is not rejected by the device — it silently derives another wallet — so the mismatch is caught, the stray wallet that reading its accounts created is dropped, and the transfer fails instead of signing from the wrong wallet. A session that is still open signs with no prompt.

Discovery skips devices that are already paired, which left the Add Hardware Wallet button searching forever and made the passphrase step unreachable after the first pairing. It now falls back to offering a known reachable device once no new one is found.

Separately, the Bridge transport used for the emulator offered a released session id as the previous one when re-acquiring, and the bridge answered wrong previous session. Since switching to a passphrase wallet closes and reopens the session, this blocked every passphrase pairing after the first. This only affects builds with the Bridge enabled; USB and BLE are unaffected.

Preview

no-wallet-add-with-passphrase.webm
Add.multiple.passphrase.wallets.webm
add-repeated-passphrase-wallet.webm
passphrase-disabled.webm
remove-wallet.webm
rename-wallet.webm
transfer-current-session.webm
transfer-other-section.mp4

QA Notes

Needs a Trezor with passphrase protection enabled. On the emulator start it with TREZOR_PASSPHRASE_PROTECTION=true ../bitkit-docker/scripts/trezor-emulator start and confirm on the device for every account a passphrase session reads, otherwise the call blocks until it times out.

Manual Tests

  • 1a. Settings → Hardware Wallets → Add → Found → Connect: Device Connected shows Passphrase and Finish.
    • 1b. Passphrase → enter a passphrase → Continue: Passphrase Funds Found shows the balance and Label Funds prefilled with the device name.
    • 1c. Rename the label → Finish: home shows a second hardware tile under that name.
  • 2. Home: headline total includes both hardware balances, and only the identity holding the device session shows a green connection indicator.
  • 3. Paired or Passphrase Funds Found → Passphrase → re-enter a passphrase already watched: toast reports it is already added, no tile is added.
  • 3b. Device with passphrase protection turned off in Trezor Suite (the factory default) → Paired → Passphrase → enter any passphrase: error says to enable passphrase protection in Trezor Suite, no wallet is added, and the standard wallet is not left marked as passphrase-protected (Settings ▸ Hardware Wallets → Transfer To Spending on it still signs without asking for a passphrase).
  • 4a. Settings → Hardware Wallets: one row per identity, each with its own balance.
    • 4b. Rename one row: only that identity is renamed.
    • 4c. Delete the hidden wallet row: the standard wallet keeps working and the device stays paired, no re-pairing needed.
  • 5. Settings → Hardware Wallets → Add on an already paired device: reaches Found and then Device Connected, instead of searching forever.
  • 6a. Hidden wallet tile → Transfer To Spending → Amount → Sign → Open Trezor Connect while its session is still open: signs with no passphrase prompt.
    • 6b. Force-stop and relaunch the app, then repeat: the passphrase sheet opens before signing.
    • 6c. Enter a wrong passphrase: error says it opens a different wallet, nothing is signed or broadcast, and no extra tile appears.
    • 6d. Enter the correct passphrase: signing continues normally.
  • 7.regression: Pair a device without using the Passphrase path: home tile, detail screen, rename, remove and Transfer To Spending behave as before.
  • 8. Both passphrase screens: screenshots and screen recording are blocked, and the passphrase is absent from files/logs/ and files/datastore/ (release build; blocking is disabled in debug).

Automated Checks

  • Unit tests added: cover a passphrase wallet being added next to the standard one, the flag surviving reconnects, and credentials surviving a scoped removal in TrezorRepoTest.kt.
  • Unit tests added: cover two identities on one device, per-identity balances, connected state, funding account, scoped removal, and connectWithPassphrase including the already-watched case and a device with passphrase protection off in HwWalletRepoTest.kt.
  • Unit tests added: cover the passphrase step effects, the duplicate toast, the label prefill, the paired step following the right identity, and the fallback to an already paired device in HwConnectViewModelTest.kt.
  • Unit tests added: cover signing with a live session, prompting once it is gone, and refusing a passphrase that opens another wallet in TransferViewModelTest.kt.
  • Unit tests added: cover re-acquiring after a release without the stale session in TrezorBridgeTransportTest.kt.
  • Unit tests modified: existing hardware tests moved to wallet-id keying in HwWalletRepoTest.kt, TransferViewModelTest.kt and HwConnectViewModelTest.kt.
  • Journeys added: passphrase-pairing.xml, passphrase-duplicate.xml, passphrase-settings-remove.xml and passphrase-transfer-to-spending.xml, including checks that the passphrase never reaches the logs or datastore.
  • Verified on a Pixel emulator against the Trezor emulator: pairing two hidden wallets alongside the standard one, per-identity labels, three home tiles, the connected indicator, and no passphrase in the logs. The duplicate, settings-remove and transfer journeys were not executed; the transfer one additionally needs regtest funds on a hidden wallet.
  • CI: standard compile, unit test and detekt checks run by the PR bot.

Known gaps

  • A hidden wallet can persist with only part of its accounts when a read fails with a device error that is not classified as transient, and its wallet id would change once the missing accounts are read. This is a pre-existing gap in the partial-save guard that passphrase sessions make easier to hit.
  • Two hidden wallets on the same device default to the same name until renamed, since the default label is the device name.

@jvsena42jvsena42 self-assigned this Aug 6, 2026
Comment threadapp/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt Fixed
…eId in it.deviceIds } and unconditionally writes pairedWalletId = wallet.id, so a wallets emission that does not yet contain the just-added hidden wallet resets the paired identity back to the standard wallet.
…se POST, so a failed release still leaves the stale session id cached.
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 10:05
@greptile-apps

greptile-appsBot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds independently tracked Trezor hidden-wallet identities and passphrase-based session restoration while preserving wallet-scoped balances, activity, labels, signing, and removal.

  • Keys hardware-wallet state and watchers by derived wallet identity instead of transport ID.
  • Adds passphrase pairing and signing-reconnection flows with wallet-identity verification.
  • Keeps sibling identities and transport credentials when removing one hidden wallet.
  • Refreshes stale Trezor Bridge sessions before retrying acquisition.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
app/src/main/java/to/bitkit/repositories/HwWalletRepo.ktMoves hardware-wallet aggregation, watcher lifecycle, signing, and removal to wallet-identity scope; the previously reported multi-transport removal issue is addressed.
app/src/main/java/to/bitkit/repositories/TrezorRepo.ktPersists multiple identities per physical device and manages passphrase-bound sessions while atomically filtering scoped removals.
app/src/main/java/to/bitkit/services/TrezorBridgeTransport.ktClears released sessions and retries acquisition after refreshing a stale Bridge session.
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.ktIntegrates passphrase restoration and identity verification into hardware-wallet signing.
app/src/main/java/to/bitkit/ui/sheets/hardware/HwConnectViewModel.ktAdds passphrase-wallet pairing, duplicate handling, and known-device discovery fallback.
app/src/main/java/to/bitkit/ui/screens/transfer/hardware/HwPassphrasePromptSheet.ktAdds the intentionally unmasked, screenshot-protected passphrase prompt used to restore signing sessions.

Sequence Diagram

sequenceDiagram
participant UI as Hardware Wallet UI
participant HW as HwWalletRepo
participant TR as TrezorRepo
participant Device as Trezor
participant Store as HwWalletStore
UI->>HW: connectWithPassphrase(deviceId, passphrase)
HW->>TR: set/connect passphrase wallet mode
TR->>Device: Open fresh passphrase session
Device-->>TR: Features and account keys
TR->>Store: Persist identity by derived wallet ID
TR-->>HW: Connected wallet identity
HW-->>UI: Add or select hidden-wallet tile
UI->>HW: Sign from wallet ID
alt matching session is live
HW->>Device: Sign transaction
else session is absent or belongs to another identity
UI->>HW: reconnectWithPassphrase(walletId, passphrase)
HW->>TR: Open fresh passphrase session
TR->>Device: Read account keys
alt derived wallet ID matches
HW->>Device: Sign transaction
else derived wallet ID differs
HW->>TR: Disconnect stray session
HW-->>UI: Reject passphrase mismatch
end
end
Loading

Reviews (3): Last reviewed commit: "fix: passphrase should not gate pending ..." | Re-trigger Greptile

Comment threadapp/src/main/java/to/bitkit/repositories/TrezorRepo.kt
@jvsena42
jvsena42 marked this pull request as draft August 10, 2026 10:11
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 11:33
@jvsena42
jvsena42 requested a review from ovitrifAugust 10, 2026 11:55
@jvsena42

jvsena42 commented Aug 10, 2026

Copy link
Copy Markdown
MemberAuthor

checking CI failures

Edit: both are backend failures

@jvsena42
jvsena42 requested review from ovitrif and removed request for ovitrifAugust 10, 2026 16:21
@jvsena42jvsena42 added this to the 2.5.0 milestone Aug 11, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as draft August 12, 2026 17:17
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 removed the request for review from ovitrifAugust 12, 2026 17:17
@jvsena42
jvsena42 marked this pull request as ready for review August 13, 2026 09:54
@jvsena42

Copy link
Copy Markdown
MemberAuthor

All test passing. Re-runned manual tests checklist with journeys

@jvsena42
jvsena42 requested a review from ovitrifAugust 13, 2026 11:01

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, passphrase identities stay scoped to their own tiles, session, and removal without replacing the standard wallet.

@jvsena42
jvsena42 merged commit 92c085a into masterAug 14, 2026
27 checks passed
@jvsena42
jvsena42 deleted the feat/hw-passphrase-wallets branch August 14, 2026 10:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware wallet: passphrase (hidden) wallet pairing

3 participants

@jvsena42@ovitrif@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: pair trezor passphrase hidden wallets - #1142

Merged
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets
Aug 14, 2026
Merged

feat: pair trezor passphrase hidden wallets#1142
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets

Conversation

@jvsena42

@jvsena42jvsena42 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes#1060

This PR:

  1. Adds an optional Passphrase path to the Connect Hardware flow, so the hidden wallets of a paired Trezor can be watched alongside its standard wallet
  2. Tracks each passphrase-derived account set as its own hardware wallet identity, with its own home tile, funds label, activity scope and removal
  3. Asks for the passphrase again before signing a transfer when the Trezor session that held it is gone, and refuses to sign if it opens a different wallet
  4. Lets the connect flow offer an already paired device, so passphrase wallets can be added after the initial pairing
  5. Fixes a Trezor Bridge session bug that blocked every passphrase pairing after the first on the emulator

Description

Trezor keeps no record of its hidden wallets and forgets a passphrase with the session, so Bitkit watches each one by its extended public keys and asks for the passphrase again whenever a session has to be rebuilt. The passphrase is never persisted or logged, and both entry screens block screenshots and screen recording the same way the wallet's own passphrase screens do.

A device previously mapped to a single wallet. It now maps to one identity per passphrase, keyed by the wallet id derived from its accounts rather than by the transport id, which two identities on the same device share. Watchers, balances, activity, labels and removal all follow that identity, and removing one hidden wallet leaves the others paired.

Pairing a hidden wallet adds an entry next to the standard one instead of replacing it. The device is matched by shared key material, so re-reading accounts updates the right entry while a different passphrase creates a new one. Only the identity that currently holds the device session shows as connected, since only that one can sign.

Before signing a transfer, Bitkit reopens the hidden wallet and accepts the session only if its accounts resolve back to the wallet being spent from. A wrong passphrase is not rejected by the device — it silently derives another wallet — so the mismatch is caught, the stray wallet that reading its accounts created is dropped, and the transfer fails instead of signing from the wrong wallet. A session that is still open signs with no prompt.

Discovery skips devices that are already paired, which left the Add Hardware Wallet button searching forever and made the passphrase step unreachable after the first pairing. It now falls back to offering a known reachable device once no new one is found.

Separately, the Bridge transport used for the emulator offered a released session id as the previous one when re-acquiring, and the bridge answered wrong previous session. Since switching to a passphrase wallet closes and reopens the session, this blocked every passphrase pairing after the first. This only affects builds with the Bridge enabled; USB and BLE are unaffected.

Preview

no-wallet-add-with-passphrase.webm
Add.multiple.passphrase.wallets.webm
add-repeated-passphrase-wallet.webm
passphrase-disabled.webm
remove-wallet.webm
rename-wallet.webm
transfer-current-session.webm
transfer-other-section.mp4

QA Notes

Needs a Trezor with passphrase protection enabled. On the emulator start it with TREZOR_PASSPHRASE_PROTECTION=true ../bitkit-docker/scripts/trezor-emulator start and confirm on the device for every account a passphrase session reads, otherwise the call blocks until it times out.

Manual Tests

  • 1a. Settings → Hardware Wallets → Add → Found → Connect: Device Connected shows Passphrase and Finish.
    • 1b. Passphrase → enter a passphrase → Continue: Passphrase Funds Found shows the balance and Label Funds prefilled with the device name.
    • 1c. Rename the label → Finish: home shows a second hardware tile under that name.
  • 2. Home: headline total includes both hardware balances, and only the identity holding the device session shows a green connection indicator.
  • 3. Paired or Passphrase Funds Found → Passphrase → re-enter a passphrase already watched: toast reports it is already added, no tile is added.
  • 3b. Device with passphrase protection turned off in Trezor Suite (the factory default) → Paired → Passphrase → enter any passphrase: error says to enable passphrase protection in Trezor Suite, no wallet is added, and the standard wallet is not left marked as passphrase-protected (Settings ▸ Hardware Wallets → Transfer To Spending on it still signs without asking for a passphrase).
  • 4a. Settings → Hardware Wallets: one row per identity, each with its own balance.
    • 4b. Rename one row: only that identity is renamed.
    • 4c. Delete the hidden wallet row: the standard wallet keeps working and the device stays paired, no re-pairing needed.
  • 5. Settings → Hardware Wallets → Add on an already paired device: reaches Found and then Device Connected, instead of searching forever.
  • 6a. Hidden wallet tile → Transfer To Spending → Amount → Sign → Open Trezor Connect while its session is still open: signs with no passphrase prompt.
    • 6b. Force-stop and relaunch the app, then repeat: the passphrase sheet opens before signing.
    • 6c. Enter a wrong passphrase: error says it opens a different wallet, nothing is signed or broadcast, and no extra tile appears.
    • 6d. Enter the correct passphrase: signing continues normally.
  • 7.regression: Pair a device without using the Passphrase path: home tile, detail screen, rename, remove and Transfer To Spending behave as before.
  • 8. Both passphrase screens: screenshots and screen recording are blocked, and the passphrase is absent from files/logs/ and files/datastore/ (release build; blocking is disabled in debug).

Automated Checks

  • Unit tests added: cover a passphrase wallet being added next to the standard one, the flag surviving reconnects, and credentials surviving a scoped removal in TrezorRepoTest.kt.
  • Unit tests added: cover two identities on one device, per-identity balances, connected state, funding account, scoped removal, and connectWithPassphrase including the already-watched case and a device with passphrase protection off in HwWalletRepoTest.kt.
  • Unit tests added: cover the passphrase step effects, the duplicate toast, the label prefill, the paired step following the right identity, and the fallback to an already paired device in HwConnectViewModelTest.kt.
  • Unit tests added: cover signing with a live session, prompting once it is gone, and refusing a passphrase that opens another wallet in TransferViewModelTest.kt.
  • Unit tests added: cover re-acquiring after a release without the stale session in TrezorBridgeTransportTest.kt.
  • Unit tests modified: existing hardware tests moved to wallet-id keying in HwWalletRepoTest.kt, TransferViewModelTest.kt and HwConnectViewModelTest.kt.
  • Journeys added: passphrase-pairing.xml, passphrase-duplicate.xml, passphrase-settings-remove.xml and passphrase-transfer-to-spending.xml, including checks that the passphrase never reaches the logs or datastore.
  • Verified on a Pixel emulator against the Trezor emulator: pairing two hidden wallets alongside the standard one, per-identity labels, three home tiles, the connected indicator, and no passphrase in the logs. The duplicate, settings-remove and transfer journeys were not executed; the transfer one additionally needs regtest funds on a hidden wallet.
  • CI: standard compile, unit test and detekt checks run by the PR bot.

Known gaps

  • A hidden wallet can persist with only part of its accounts when a read fails with a device error that is not classified as transient, and its wallet id would change once the missing accounts are read. This is a pre-existing gap in the partial-save guard that passphrase sessions make easier to hit.
  • Two hidden wallets on the same device default to the same name until renamed, since the default label is the device name.

@jvsena42jvsena42 self-assigned this Aug 6, 2026
Comment threadapp/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt Fixed
…eId in it.deviceIds } and unconditionally writes pairedWalletId = wallet.id, so a wallets emission that does not yet contain the just-added hidden wallet resets the paired identity back to the standard wallet.
…se POST, so a failed release still leaves the stale session id cached.
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 10:05
@greptile-apps

greptile-appsBot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds independently tracked Trezor hidden-wallet identities and passphrase-based session restoration while preserving wallet-scoped balances, activity, labels, signing, and removal.

  • Keys hardware-wallet state and watchers by derived wallet identity instead of transport ID.
  • Adds passphrase pairing and signing-reconnection flows with wallet-identity verification.
  • Keeps sibling identities and transport credentials when removing one hidden wallet.
  • Refreshes stale Trezor Bridge sessions before retrying acquisition.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
app/src/main/java/to/bitkit/repositories/HwWalletRepo.ktMoves hardware-wallet aggregation, watcher lifecycle, signing, and removal to wallet-identity scope; the previously reported multi-transport removal issue is addressed.
app/src/main/java/to/bitkit/repositories/TrezorRepo.ktPersists multiple identities per physical device and manages passphrase-bound sessions while atomically filtering scoped removals.
app/src/main/java/to/bitkit/services/TrezorBridgeTransport.ktClears released sessions and retries acquisition after refreshing a stale Bridge session.
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.ktIntegrates passphrase restoration and identity verification into hardware-wallet signing.
app/src/main/java/to/bitkit/ui/sheets/hardware/HwConnectViewModel.ktAdds passphrase-wallet pairing, duplicate handling, and known-device discovery fallback.
app/src/main/java/to/bitkit/ui/screens/transfer/hardware/HwPassphrasePromptSheet.ktAdds the intentionally unmasked, screenshot-protected passphrase prompt used to restore signing sessions.

Sequence Diagram

sequenceDiagram
participant UI as Hardware Wallet UI
participant HW as HwWalletRepo
participant TR as TrezorRepo
participant Device as Trezor
participant Store as HwWalletStore
UI->>HW: connectWithPassphrase(deviceId, passphrase)
HW->>TR: set/connect passphrase wallet mode
TR->>Device: Open fresh passphrase session
Device-->>TR: Features and account keys
TR->>Store: Persist identity by derived wallet ID
TR-->>HW: Connected wallet identity
HW-->>UI: Add or select hidden-wallet tile
UI->>HW: Sign from wallet ID
alt matching session is live
HW->>Device: Sign transaction
else session is absent or belongs to another identity
UI->>HW: reconnectWithPassphrase(walletId, passphrase)
HW->>TR: Open fresh passphrase session
TR->>Device: Read account keys
alt derived wallet ID matches
HW->>Device: Sign transaction
else derived wallet ID differs
HW->>TR: Disconnect stray session
HW-->>UI: Reject passphrase mismatch
end
end
Loading

Reviews (3): Last reviewed commit: "fix: passphrase should not gate pending ..." | Re-trigger Greptile

Comment threadapp/src/main/java/to/bitkit/repositories/TrezorRepo.kt
@jvsena42
jvsena42 marked this pull request as draft August 10, 2026 10:11
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 11:33
@jvsena42
jvsena42 requested a review from ovitrifAugust 10, 2026 11:55
@jvsena42

jvsena42 commented Aug 10, 2026

Copy link
Copy Markdown
MemberAuthor

checking CI failures

Edit: both are backend failures

@jvsena42
jvsena42 requested review from ovitrif and removed request for ovitrifAugust 10, 2026 16:21
@jvsena42jvsena42 added this to the 2.5.0 milestone Aug 11, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as draft August 12, 2026 17:17
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 removed the request for review from ovitrifAugust 12, 2026 17:17
@jvsena42
jvsena42 marked this pull request as ready for review August 13, 2026 09:54
@jvsena42

Copy link
Copy Markdown
MemberAuthor

All test passing. Re-runned manual tests checklist with journeys

@jvsena42
jvsena42 requested a review from ovitrifAugust 13, 2026 11:01

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, passphrase identities stay scoped to their own tiles, session, and removal without replacing the standard wallet.

@jvsena42
jvsena42 merged commit 92c085a into masterAug 14, 2026
27 checks passed
@jvsena42
jvsena42 deleted the feat/hw-passphrase-wallets branch August 14, 2026 10:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware wallet: passphrase (hidden) wallet pairing

3 participants

@jvsena42@ovitrif@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: pair trezor passphrase hidden wallets - #1142

Merged
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets
Aug 14, 2026
Merged

feat: pair trezor passphrase hidden wallets#1142
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets

Conversation

@jvsena42

@jvsena42jvsena42 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes#1060

This PR:

  1. Adds an optional Passphrase path to the Connect Hardware flow, so the hidden wallets of a paired Trezor can be watched alongside its standard wallet
  2. Tracks each passphrase-derived account set as its own hardware wallet identity, with its own home tile, funds label, activity scope and removal
  3. Asks for the passphrase again before signing a transfer when the Trezor session that held it is gone, and refuses to sign if it opens a different wallet
  4. Lets the connect flow offer an already paired device, so passphrase wallets can be added after the initial pairing
  5. Fixes a Trezor Bridge session bug that blocked every passphrase pairing after the first on the emulator

Description

Trezor keeps no record of its hidden wallets and forgets a passphrase with the session, so Bitkit watches each one by its extended public keys and asks for the passphrase again whenever a session has to be rebuilt. The passphrase is never persisted or logged, and both entry screens block screenshots and screen recording the same way the wallet's own passphrase screens do.

A device previously mapped to a single wallet. It now maps to one identity per passphrase, keyed by the wallet id derived from its accounts rather than by the transport id, which two identities on the same device share. Watchers, balances, activity, labels and removal all follow that identity, and removing one hidden wallet leaves the others paired.

Pairing a hidden wallet adds an entry next to the standard one instead of replacing it. The device is matched by shared key material, so re-reading accounts updates the right entry while a different passphrase creates a new one. Only the identity that currently holds the device session shows as connected, since only that one can sign.

Before signing a transfer, Bitkit reopens the hidden wallet and accepts the session only if its accounts resolve back to the wallet being spent from. A wrong passphrase is not rejected by the device — it silently derives another wallet — so the mismatch is caught, the stray wallet that reading its accounts created is dropped, and the transfer fails instead of signing from the wrong wallet. A session that is still open signs with no prompt.

Discovery skips devices that are already paired, which left the Add Hardware Wallet button searching forever and made the passphrase step unreachable after the first pairing. It now falls back to offering a known reachable device once no new one is found.

Separately, the Bridge transport used for the emulator offered a released session id as the previous one when re-acquiring, and the bridge answered wrong previous session. Since switching to a passphrase wallet closes and reopens the session, this blocked every passphrase pairing after the first. This only affects builds with the Bridge enabled; USB and BLE are unaffected.

Preview

no-wallet-add-with-passphrase.webm
Add.multiple.passphrase.wallets.webm
add-repeated-passphrase-wallet.webm
passphrase-disabled.webm
remove-wallet.webm
rename-wallet.webm
transfer-current-session.webm
transfer-other-section.mp4

QA Notes

Needs a Trezor with passphrase protection enabled. On the emulator start it with TREZOR_PASSPHRASE_PROTECTION=true ../bitkit-docker/scripts/trezor-emulator start and confirm on the device for every account a passphrase session reads, otherwise the call blocks until it times out.

Manual Tests

  • 1a. Settings → Hardware Wallets → Add → Found → Connect: Device Connected shows Passphrase and Finish.
    • 1b. Passphrase → enter a passphrase → Continue: Passphrase Funds Found shows the balance and Label Funds prefilled with the device name.
    • 1c. Rename the label → Finish: home shows a second hardware tile under that name.
  • 2. Home: headline total includes both hardware balances, and only the identity holding the device session shows a green connection indicator.
  • 3. Paired or Passphrase Funds Found → Passphrase → re-enter a passphrase already watched: toast reports it is already added, no tile is added.
  • 3b. Device with passphrase protection turned off in Trezor Suite (the factory default) → Paired → Passphrase → enter any passphrase: error says to enable passphrase protection in Trezor Suite, no wallet is added, and the standard wallet is not left marked as passphrase-protected (Settings ▸ Hardware Wallets → Transfer To Spending on it still signs without asking for a passphrase).
  • 4a. Settings → Hardware Wallets: one row per identity, each with its own balance.
    • 4b. Rename one row: only that identity is renamed.
    • 4c. Delete the hidden wallet row: the standard wallet keeps working and the device stays paired, no re-pairing needed.
  • 5. Settings → Hardware Wallets → Add on an already paired device: reaches Found and then Device Connected, instead of searching forever.
  • 6a. Hidden wallet tile → Transfer To Spending → Amount → Sign → Open Trezor Connect while its session is still open: signs with no passphrase prompt.
    • 6b. Force-stop and relaunch the app, then repeat: the passphrase sheet opens before signing.
    • 6c. Enter a wrong passphrase: error says it opens a different wallet, nothing is signed or broadcast, and no extra tile appears.
    • 6d. Enter the correct passphrase: signing continues normally.
  • 7.regression: Pair a device without using the Passphrase path: home tile, detail screen, rename, remove and Transfer To Spending behave as before.
  • 8. Both passphrase screens: screenshots and screen recording are blocked, and the passphrase is absent from files/logs/ and files/datastore/ (release build; blocking is disabled in debug).

Automated Checks

  • Unit tests added: cover a passphrase wallet being added next to the standard one, the flag surviving reconnects, and credentials surviving a scoped removal in TrezorRepoTest.kt.
  • Unit tests added: cover two identities on one device, per-identity balances, connected state, funding account, scoped removal, and connectWithPassphrase including the already-watched case and a device with passphrase protection off in HwWalletRepoTest.kt.
  • Unit tests added: cover the passphrase step effects, the duplicate toast, the label prefill, the paired step following the right identity, and the fallback to an already paired device in HwConnectViewModelTest.kt.
  • Unit tests added: cover signing with a live session, prompting once it is gone, and refusing a passphrase that opens another wallet in TransferViewModelTest.kt.
  • Unit tests added: cover re-acquiring after a release without the stale session in TrezorBridgeTransportTest.kt.
  • Unit tests modified: existing hardware tests moved to wallet-id keying in HwWalletRepoTest.kt, TransferViewModelTest.kt and HwConnectViewModelTest.kt.
  • Journeys added: passphrase-pairing.xml, passphrase-duplicate.xml, passphrase-settings-remove.xml and passphrase-transfer-to-spending.xml, including checks that the passphrase never reaches the logs or datastore.
  • Verified on a Pixel emulator against the Trezor emulator: pairing two hidden wallets alongside the standard one, per-identity labels, three home tiles, the connected indicator, and no passphrase in the logs. The duplicate, settings-remove and transfer journeys were not executed; the transfer one additionally needs regtest funds on a hidden wallet.
  • CI: standard compile, unit test and detekt checks run by the PR bot.

Known gaps

  • A hidden wallet can persist with only part of its accounts when a read fails with a device error that is not classified as transient, and its wallet id would change once the missing accounts are read. This is a pre-existing gap in the partial-save guard that passphrase sessions make easier to hit.
  • Two hidden wallets on the same device default to the same name until renamed, since the default label is the device name.

@jvsena42jvsena42 self-assigned this Aug 6, 2026
Comment threadapp/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt Fixed
…eId in it.deviceIds } and unconditionally writes pairedWalletId = wallet.id, so a wallets emission that does not yet contain the just-added hidden wallet resets the paired identity back to the standard wallet.
…se POST, so a failed release still leaves the stale session id cached.
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 10:05
@greptile-apps

greptile-appsBot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds independently tracked Trezor hidden-wallet identities and passphrase-based session restoration while preserving wallet-scoped balances, activity, labels, signing, and removal.

  • Keys hardware-wallet state and watchers by derived wallet identity instead of transport ID.
  • Adds passphrase pairing and signing-reconnection flows with wallet-identity verification.
  • Keeps sibling identities and transport credentials when removing one hidden wallet.
  • Refreshes stale Trezor Bridge sessions before retrying acquisition.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
app/src/main/java/to/bitkit/repositories/HwWalletRepo.ktMoves hardware-wallet aggregation, watcher lifecycle, signing, and removal to wallet-identity scope; the previously reported multi-transport removal issue is addressed.
app/src/main/java/to/bitkit/repositories/TrezorRepo.ktPersists multiple identities per physical device and manages passphrase-bound sessions while atomically filtering scoped removals.
app/src/main/java/to/bitkit/services/TrezorBridgeTransport.ktClears released sessions and retries acquisition after refreshing a stale Bridge session.
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.ktIntegrates passphrase restoration and identity verification into hardware-wallet signing.
app/src/main/java/to/bitkit/ui/sheets/hardware/HwConnectViewModel.ktAdds passphrase-wallet pairing, duplicate handling, and known-device discovery fallback.
app/src/main/java/to/bitkit/ui/screens/transfer/hardware/HwPassphrasePromptSheet.ktAdds the intentionally unmasked, screenshot-protected passphrase prompt used to restore signing sessions.

Sequence Diagram

sequenceDiagram
participant UI as Hardware Wallet UI
participant HW as HwWalletRepo
participant TR as TrezorRepo
participant Device as Trezor
participant Store as HwWalletStore
UI->>HW: connectWithPassphrase(deviceId, passphrase)
HW->>TR: set/connect passphrase wallet mode
TR->>Device: Open fresh passphrase session
Device-->>TR: Features and account keys
TR->>Store: Persist identity by derived wallet ID
TR-->>HW: Connected wallet identity
HW-->>UI: Add or select hidden-wallet tile
UI->>HW: Sign from wallet ID
alt matching session is live
HW->>Device: Sign transaction
else session is absent or belongs to another identity
UI->>HW: reconnectWithPassphrase(walletId, passphrase)
HW->>TR: Open fresh passphrase session
TR->>Device: Read account keys
alt derived wallet ID matches
HW->>Device: Sign transaction
else derived wallet ID differs
HW->>TR: Disconnect stray session
HW-->>UI: Reject passphrase mismatch
end
end
Loading

Reviews (3): Last reviewed commit: "fix: passphrase should not gate pending ..." | Re-trigger Greptile

Comment threadapp/src/main/java/to/bitkit/repositories/TrezorRepo.kt
@jvsena42
jvsena42 marked this pull request as draft August 10, 2026 10:11
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 11:33
@jvsena42
jvsena42 requested a review from ovitrifAugust 10, 2026 11:55
@jvsena42

jvsena42 commented Aug 10, 2026

Copy link
Copy Markdown
MemberAuthor

checking CI failures

Edit: both are backend failures

@jvsena42
jvsena42 requested review from ovitrif and removed request for ovitrifAugust 10, 2026 16:21
@jvsena42jvsena42 added this to the 2.5.0 milestone Aug 11, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as draft August 12, 2026 17:17
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 removed the request for review from ovitrifAugust 12, 2026 17:17
@jvsena42
jvsena42 marked this pull request as ready for review August 13, 2026 09:54
@jvsena42

Copy link
Copy Markdown
MemberAuthor

All test passing. Re-runned manual tests checklist with journeys

@jvsena42
jvsena42 requested a review from ovitrifAugust 13, 2026 11:01

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, passphrase identities stay scoped to their own tiles, session, and removal without replacing the standard wallet.

@jvsena42
jvsena42 merged commit 92c085a into masterAug 14, 2026
27 checks passed
@jvsena42
jvsena42 deleted the feat/hw-passphrase-wallets branch August 14, 2026 10:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware wallet: passphrase (hidden) wallet pairing

3 participants

@jvsena42@ovitrif@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: pair trezor passphrase hidden wallets - #1142

Merged
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets
Aug 14, 2026
Merged

feat: pair trezor passphrase hidden wallets#1142
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets

Conversation

@jvsena42

@jvsena42jvsena42 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes#1060

This PR:

  1. Adds an optional Passphrase path to the Connect Hardware flow, so the hidden wallets of a paired Trezor can be watched alongside its standard wallet
  2. Tracks each passphrase-derived account set as its own hardware wallet identity, with its own home tile, funds label, activity scope and removal
  3. Asks for the passphrase again before signing a transfer when the Trezor session that held it is gone, and refuses to sign if it opens a different wallet
  4. Lets the connect flow offer an already paired device, so passphrase wallets can be added after the initial pairing
  5. Fixes a Trezor Bridge session bug that blocked every passphrase pairing after the first on the emulator

Description

Trezor keeps no record of its hidden wallets and forgets a passphrase with the session, so Bitkit watches each one by its extended public keys and asks for the passphrase again whenever a session has to be rebuilt. The passphrase is never persisted or logged, and both entry screens block screenshots and screen recording the same way the wallet's own passphrase screens do.

A device previously mapped to a single wallet. It now maps to one identity per passphrase, keyed by the wallet id derived from its accounts rather than by the transport id, which two identities on the same device share. Watchers, balances, activity, labels and removal all follow that identity, and removing one hidden wallet leaves the others paired.

Pairing a hidden wallet adds an entry next to the standard one instead of replacing it. The device is matched by shared key material, so re-reading accounts updates the right entry while a different passphrase creates a new one. Only the identity that currently holds the device session shows as connected, since only that one can sign.

Before signing a transfer, Bitkit reopens the hidden wallet and accepts the session only if its accounts resolve back to the wallet being spent from. A wrong passphrase is not rejected by the device — it silently derives another wallet — so the mismatch is caught, the stray wallet that reading its accounts created is dropped, and the transfer fails instead of signing from the wrong wallet. A session that is still open signs with no prompt.

Discovery skips devices that are already paired, which left the Add Hardware Wallet button searching forever and made the passphrase step unreachable after the first pairing. It now falls back to offering a known reachable device once no new one is found.

Separately, the Bridge transport used for the emulator offered a released session id as the previous one when re-acquiring, and the bridge answered wrong previous session. Since switching to a passphrase wallet closes and reopens the session, this blocked every passphrase pairing after the first. This only affects builds with the Bridge enabled; USB and BLE are unaffected.

Preview

no-wallet-add-with-passphrase.webm
Add.multiple.passphrase.wallets.webm
add-repeated-passphrase-wallet.webm
passphrase-disabled.webm
remove-wallet.webm
rename-wallet.webm
transfer-current-session.webm
transfer-other-section.mp4

QA Notes

Needs a Trezor with passphrase protection enabled. On the emulator start it with TREZOR_PASSPHRASE_PROTECTION=true ../bitkit-docker/scripts/trezor-emulator start and confirm on the device for every account a passphrase session reads, otherwise the call blocks until it times out.

Manual Tests

  • 1a. Settings → Hardware Wallets → Add → Found → Connect: Device Connected shows Passphrase and Finish.
    • 1b. Passphrase → enter a passphrase → Continue: Passphrase Funds Found shows the balance and Label Funds prefilled with the device name.
    • 1c. Rename the label → Finish: home shows a second hardware tile under that name.
  • 2. Home: headline total includes both hardware balances, and only the identity holding the device session shows a green connection indicator.
  • 3. Paired or Passphrase Funds Found → Passphrase → re-enter a passphrase already watched: toast reports it is already added, no tile is added.
  • 3b. Device with passphrase protection turned off in Trezor Suite (the factory default) → Paired → Passphrase → enter any passphrase: error says to enable passphrase protection in Trezor Suite, no wallet is added, and the standard wallet is not left marked as passphrase-protected (Settings ▸ Hardware Wallets → Transfer To Spending on it still signs without asking for a passphrase).
  • 4a. Settings → Hardware Wallets: one row per identity, each with its own balance.
    • 4b. Rename one row: only that identity is renamed.
    • 4c. Delete the hidden wallet row: the standard wallet keeps working and the device stays paired, no re-pairing needed.
  • 5. Settings → Hardware Wallets → Add on an already paired device: reaches Found and then Device Connected, instead of searching forever.
  • 6a. Hidden wallet tile → Transfer To Spending → Amount → Sign → Open Trezor Connect while its session is still open: signs with no passphrase prompt.
    • 6b. Force-stop and relaunch the app, then repeat: the passphrase sheet opens before signing.
    • 6c. Enter a wrong passphrase: error says it opens a different wallet, nothing is signed or broadcast, and no extra tile appears.
    • 6d. Enter the correct passphrase: signing continues normally.
  • 7.regression: Pair a device without using the Passphrase path: home tile, detail screen, rename, remove and Transfer To Spending behave as before.
  • 8. Both passphrase screens: screenshots and screen recording are blocked, and the passphrase is absent from files/logs/ and files/datastore/ (release build; blocking is disabled in debug).

Automated Checks

  • Unit tests added: cover a passphrase wallet being added next to the standard one, the flag surviving reconnects, and credentials surviving a scoped removal in TrezorRepoTest.kt.
  • Unit tests added: cover two identities on one device, per-identity balances, connected state, funding account, scoped removal, and connectWithPassphrase including the already-watched case and a device with passphrase protection off in HwWalletRepoTest.kt.
  • Unit tests added: cover the passphrase step effects, the duplicate toast, the label prefill, the paired step following the right identity, and the fallback to an already paired device in HwConnectViewModelTest.kt.
  • Unit tests added: cover signing with a live session, prompting once it is gone, and refusing a passphrase that opens another wallet in TransferViewModelTest.kt.
  • Unit tests added: cover re-acquiring after a release without the stale session in TrezorBridgeTransportTest.kt.
  • Unit tests modified: existing hardware tests moved to wallet-id keying in HwWalletRepoTest.kt, TransferViewModelTest.kt and HwConnectViewModelTest.kt.
  • Journeys added: passphrase-pairing.xml, passphrase-duplicate.xml, passphrase-settings-remove.xml and passphrase-transfer-to-spending.xml, including checks that the passphrase never reaches the logs or datastore.
  • Verified on a Pixel emulator against the Trezor emulator: pairing two hidden wallets alongside the standard one, per-identity labels, three home tiles, the connected indicator, and no passphrase in the logs. The duplicate, settings-remove and transfer journeys were not executed; the transfer one additionally needs regtest funds on a hidden wallet.
  • CI: standard compile, unit test and detekt checks run by the PR bot.

Known gaps

  • A hidden wallet can persist with only part of its accounts when a read fails with a device error that is not classified as transient, and its wallet id would change once the missing accounts are read. This is a pre-existing gap in the partial-save guard that passphrase sessions make easier to hit.
  • Two hidden wallets on the same device default to the same name until renamed, since the default label is the device name.

@jvsena42jvsena42 self-assigned this Aug 6, 2026
Comment threadapp/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt Fixed
…eId in it.deviceIds } and unconditionally writes pairedWalletId = wallet.id, so a wallets emission that does not yet contain the just-added hidden wallet resets the paired identity back to the standard wallet.
…se POST, so a failed release still leaves the stale session id cached.
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 10:05
@greptile-apps

greptile-appsBot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds independently tracked Trezor hidden-wallet identities and passphrase-based session restoration while preserving wallet-scoped balances, activity, labels, signing, and removal.

  • Keys hardware-wallet state and watchers by derived wallet identity instead of transport ID.
  • Adds passphrase pairing and signing-reconnection flows with wallet-identity verification.
  • Keeps sibling identities and transport credentials when removing one hidden wallet.
  • Refreshes stale Trezor Bridge sessions before retrying acquisition.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
app/src/main/java/to/bitkit/repositories/HwWalletRepo.ktMoves hardware-wallet aggregation, watcher lifecycle, signing, and removal to wallet-identity scope; the previously reported multi-transport removal issue is addressed.
app/src/main/java/to/bitkit/repositories/TrezorRepo.ktPersists multiple identities per physical device and manages passphrase-bound sessions while atomically filtering scoped removals.
app/src/main/java/to/bitkit/services/TrezorBridgeTransport.ktClears released sessions and retries acquisition after refreshing a stale Bridge session.
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.ktIntegrates passphrase restoration and identity verification into hardware-wallet signing.
app/src/main/java/to/bitkit/ui/sheets/hardware/HwConnectViewModel.ktAdds passphrase-wallet pairing, duplicate handling, and known-device discovery fallback.
app/src/main/java/to/bitkit/ui/screens/transfer/hardware/HwPassphrasePromptSheet.ktAdds the intentionally unmasked, screenshot-protected passphrase prompt used to restore signing sessions.

Sequence Diagram

sequenceDiagram
participant UI as Hardware Wallet UI
participant HW as HwWalletRepo
participant TR as TrezorRepo
participant Device as Trezor
participant Store as HwWalletStore
UI->>HW: connectWithPassphrase(deviceId, passphrase)
HW->>TR: set/connect passphrase wallet mode
TR->>Device: Open fresh passphrase session
Device-->>TR: Features and account keys
TR->>Store: Persist identity by derived wallet ID
TR-->>HW: Connected wallet identity
HW-->>UI: Add or select hidden-wallet tile
UI->>HW: Sign from wallet ID
alt matching session is live
HW->>Device: Sign transaction
else session is absent or belongs to another identity
UI->>HW: reconnectWithPassphrase(walletId, passphrase)
HW->>TR: Open fresh passphrase session
TR->>Device: Read account keys
alt derived wallet ID matches
HW->>Device: Sign transaction
else derived wallet ID differs
HW->>TR: Disconnect stray session
HW-->>UI: Reject passphrase mismatch
end
end
Loading

Reviews (3): Last reviewed commit: "fix: passphrase should not gate pending ..." | Re-trigger Greptile

Comment threadapp/src/main/java/to/bitkit/repositories/TrezorRepo.kt
@jvsena42
jvsena42 marked this pull request as draft August 10, 2026 10:11
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 11:33
@jvsena42
jvsena42 requested a review from ovitrifAugust 10, 2026 11:55
@jvsena42

jvsena42 commented Aug 10, 2026

Copy link
Copy Markdown
MemberAuthor

checking CI failures

Edit: both are backend failures

@jvsena42
jvsena42 requested review from ovitrif and removed request for ovitrifAugust 10, 2026 16:21
@jvsena42jvsena42 added this to the 2.5.0 milestone Aug 11, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as draft August 12, 2026 17:17
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 removed the request for review from ovitrifAugust 12, 2026 17:17
@jvsena42
jvsena42 marked this pull request as ready for review August 13, 2026 09:54
@jvsena42

Copy link
Copy Markdown
MemberAuthor

All test passing. Re-runned manual tests checklist with journeys

@jvsena42
jvsena42 requested a review from ovitrifAugust 13, 2026 11:01

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, passphrase identities stay scoped to their own tiles, session, and removal without replacing the standard wallet.

@jvsena42
jvsena42 merged commit 92c085a into masterAug 14, 2026
27 checks passed
@jvsena42
jvsena42 deleted the feat/hw-passphrase-wallets branch August 14, 2026 10:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware wallet: passphrase (hidden) wallet pairing

3 participants

@jvsena42@ovitrif@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: pair trezor passphrase hidden wallets - #1142

Merged
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets
Aug 14, 2026
Merged

feat: pair trezor passphrase hidden wallets#1142
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets

Conversation

@jvsena42

@jvsena42jvsena42 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes#1060

This PR:

  1. Adds an optional Passphrase path to the Connect Hardware flow, so the hidden wallets of a paired Trezor can be watched alongside its standard wallet
  2. Tracks each passphrase-derived account set as its own hardware wallet identity, with its own home tile, funds label, activity scope and removal
  3. Asks for the passphrase again before signing a transfer when the Trezor session that held it is gone, and refuses to sign if it opens a different wallet
  4. Lets the connect flow offer an already paired device, so passphrase wallets can be added after the initial pairing
  5. Fixes a Trezor Bridge session bug that blocked every passphrase pairing after the first on the emulator

Description

Trezor keeps no record of its hidden wallets and forgets a passphrase with the session, so Bitkit watches each one by its extended public keys and asks for the passphrase again whenever a session has to be rebuilt. The passphrase is never persisted or logged, and both entry screens block screenshots and screen recording the same way the wallet's own passphrase screens do.

A device previously mapped to a single wallet. It now maps to one identity per passphrase, keyed by the wallet id derived from its accounts rather than by the transport id, which two identities on the same device share. Watchers, balances, activity, labels and removal all follow that identity, and removing one hidden wallet leaves the others paired.

Pairing a hidden wallet adds an entry next to the standard one instead of replacing it. The device is matched by shared key material, so re-reading accounts updates the right entry while a different passphrase creates a new one. Only the identity that currently holds the device session shows as connected, since only that one can sign.

Before signing a transfer, Bitkit reopens the hidden wallet and accepts the session only if its accounts resolve back to the wallet being spent from. A wrong passphrase is not rejected by the device — it silently derives another wallet — so the mismatch is caught, the stray wallet that reading its accounts created is dropped, and the transfer fails instead of signing from the wrong wallet. A session that is still open signs with no prompt.

Discovery skips devices that are already paired, which left the Add Hardware Wallet button searching forever and made the passphrase step unreachable after the first pairing. It now falls back to offering a known reachable device once no new one is found.

Separately, the Bridge transport used for the emulator offered a released session id as the previous one when re-acquiring, and the bridge answered wrong previous session. Since switching to a passphrase wallet closes and reopens the session, this blocked every passphrase pairing after the first. This only affects builds with the Bridge enabled; USB and BLE are unaffected.

Preview

no-wallet-add-with-passphrase.webm
Add.multiple.passphrase.wallets.webm
add-repeated-passphrase-wallet.webm
passphrase-disabled.webm
remove-wallet.webm
rename-wallet.webm
transfer-current-session.webm
transfer-other-section.mp4

QA Notes

Needs a Trezor with passphrase protection enabled. On the emulator start it with TREZOR_PASSPHRASE_PROTECTION=true ../bitkit-docker/scripts/trezor-emulator start and confirm on the device for every account a passphrase session reads, otherwise the call blocks until it times out.

Manual Tests

  • 1a. Settings → Hardware Wallets → Add → Found → Connect: Device Connected shows Passphrase and Finish.
    • 1b. Passphrase → enter a passphrase → Continue: Passphrase Funds Found shows the balance and Label Funds prefilled with the device name.
    • 1c. Rename the label → Finish: home shows a second hardware tile under that name.
  • 2. Home: headline total includes both hardware balances, and only the identity holding the device session shows a green connection indicator.
  • 3. Paired or Passphrase Funds Found → Passphrase → re-enter a passphrase already watched: toast reports it is already added, no tile is added.
  • 3b. Device with passphrase protection turned off in Trezor Suite (the factory default) → Paired → Passphrase → enter any passphrase: error says to enable passphrase protection in Trezor Suite, no wallet is added, and the standard wallet is not left marked as passphrase-protected (Settings ▸ Hardware Wallets → Transfer To Spending on it still signs without asking for a passphrase).
  • 4a. Settings → Hardware Wallets: one row per identity, each with its own balance.
    • 4b. Rename one row: only that identity is renamed.
    • 4c. Delete the hidden wallet row: the standard wallet keeps working and the device stays paired, no re-pairing needed.
  • 5. Settings → Hardware Wallets → Add on an already paired device: reaches Found and then Device Connected, instead of searching forever.
  • 6a. Hidden wallet tile → Transfer To Spending → Amount → Sign → Open Trezor Connect while its session is still open: signs with no passphrase prompt.
    • 6b. Force-stop and relaunch the app, then repeat: the passphrase sheet opens before signing.
    • 6c. Enter a wrong passphrase: error says it opens a different wallet, nothing is signed or broadcast, and no extra tile appears.
    • 6d. Enter the correct passphrase: signing continues normally.
  • 7.regression: Pair a device without using the Passphrase path: home tile, detail screen, rename, remove and Transfer To Spending behave as before.
  • 8. Both passphrase screens: screenshots and screen recording are blocked, and the passphrase is absent from files/logs/ and files/datastore/ (release build; blocking is disabled in debug).

Automated Checks

  • Unit tests added: cover a passphrase wallet being added next to the standard one, the flag surviving reconnects, and credentials surviving a scoped removal in TrezorRepoTest.kt.
  • Unit tests added: cover two identities on one device, per-identity balances, connected state, funding account, scoped removal, and connectWithPassphrase including the already-watched case and a device with passphrase protection off in HwWalletRepoTest.kt.
  • Unit tests added: cover the passphrase step effects, the duplicate toast, the label prefill, the paired step following the right identity, and the fallback to an already paired device in HwConnectViewModelTest.kt.
  • Unit tests added: cover signing with a live session, prompting once it is gone, and refusing a passphrase that opens another wallet in TransferViewModelTest.kt.
  • Unit tests added: cover re-acquiring after a release without the stale session in TrezorBridgeTransportTest.kt.
  • Unit tests modified: existing hardware tests moved to wallet-id keying in HwWalletRepoTest.kt, TransferViewModelTest.kt and HwConnectViewModelTest.kt.
  • Journeys added: passphrase-pairing.xml, passphrase-duplicate.xml, passphrase-settings-remove.xml and passphrase-transfer-to-spending.xml, including checks that the passphrase never reaches the logs or datastore.
  • Verified on a Pixel emulator against the Trezor emulator: pairing two hidden wallets alongside the standard one, per-identity labels, three home tiles, the connected indicator, and no passphrase in the logs. The duplicate, settings-remove and transfer journeys were not executed; the transfer one additionally needs regtest funds on a hidden wallet.
  • CI: standard compile, unit test and detekt checks run by the PR bot.

Known gaps

  • A hidden wallet can persist with only part of its accounts when a read fails with a device error that is not classified as transient, and its wallet id would change once the missing accounts are read. This is a pre-existing gap in the partial-save guard that passphrase sessions make easier to hit.
  • Two hidden wallets on the same device default to the same name until renamed, since the default label is the device name.

@jvsena42jvsena42 self-assigned this Aug 6, 2026
Comment threadapp/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt Fixed
…eId in it.deviceIds } and unconditionally writes pairedWalletId = wallet.id, so a wallets emission that does not yet contain the just-added hidden wallet resets the paired identity back to the standard wallet.
…se POST, so a failed release still leaves the stale session id cached.
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 10:05
@greptile-apps

greptile-appsBot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds independently tracked Trezor hidden-wallet identities and passphrase-based session restoration while preserving wallet-scoped balances, activity, labels, signing, and removal.

  • Keys hardware-wallet state and watchers by derived wallet identity instead of transport ID.
  • Adds passphrase pairing and signing-reconnection flows with wallet-identity verification.
  • Keeps sibling identities and transport credentials when removing one hidden wallet.
  • Refreshes stale Trezor Bridge sessions before retrying acquisition.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
app/src/main/java/to/bitkit/repositories/HwWalletRepo.ktMoves hardware-wallet aggregation, watcher lifecycle, signing, and removal to wallet-identity scope; the previously reported multi-transport removal issue is addressed.
app/src/main/java/to/bitkit/repositories/TrezorRepo.ktPersists multiple identities per physical device and manages passphrase-bound sessions while atomically filtering scoped removals.
app/src/main/java/to/bitkit/services/TrezorBridgeTransport.ktClears released sessions and retries acquisition after refreshing a stale Bridge session.
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.ktIntegrates passphrase restoration and identity verification into hardware-wallet signing.
app/src/main/java/to/bitkit/ui/sheets/hardware/HwConnectViewModel.ktAdds passphrase-wallet pairing, duplicate handling, and known-device discovery fallback.
app/src/main/java/to/bitkit/ui/screens/transfer/hardware/HwPassphrasePromptSheet.ktAdds the intentionally unmasked, screenshot-protected passphrase prompt used to restore signing sessions.

Sequence Diagram

sequenceDiagram
participant UI as Hardware Wallet UI
participant HW as HwWalletRepo
participant TR as TrezorRepo
participant Device as Trezor
participant Store as HwWalletStore
UI->>HW: connectWithPassphrase(deviceId, passphrase)
HW->>TR: set/connect passphrase wallet mode
TR->>Device: Open fresh passphrase session
Device-->>TR: Features and account keys
TR->>Store: Persist identity by derived wallet ID
TR-->>HW: Connected wallet identity
HW-->>UI: Add or select hidden-wallet tile
UI->>HW: Sign from wallet ID
alt matching session is live
HW->>Device: Sign transaction
else session is absent or belongs to another identity
UI->>HW: reconnectWithPassphrase(walletId, passphrase)
HW->>TR: Open fresh passphrase session
TR->>Device: Read account keys
alt derived wallet ID matches
HW->>Device: Sign transaction
else derived wallet ID differs
HW->>TR: Disconnect stray session
HW-->>UI: Reject passphrase mismatch
end
end
Loading

Reviews (3): Last reviewed commit: "fix: passphrase should not gate pending ..." | Re-trigger Greptile

Comment threadapp/src/main/java/to/bitkit/repositories/TrezorRepo.kt
@jvsena42
jvsena42 marked this pull request as draft August 10, 2026 10:11
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 11:33
@jvsena42
jvsena42 requested a review from ovitrifAugust 10, 2026 11:55
@jvsena42

jvsena42 commented Aug 10, 2026

Copy link
Copy Markdown
MemberAuthor

checking CI failures

Edit: both are backend failures

@jvsena42
jvsena42 requested review from ovitrif and removed request for ovitrifAugust 10, 2026 16:21
@jvsena42jvsena42 added this to the 2.5.0 milestone Aug 11, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as draft August 12, 2026 17:17
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 removed the request for review from ovitrifAugust 12, 2026 17:17
@jvsena42
jvsena42 marked this pull request as ready for review August 13, 2026 09:54
@jvsena42

Copy link
Copy Markdown
MemberAuthor

All test passing. Re-runned manual tests checklist with journeys

@jvsena42
jvsena42 requested a review from ovitrifAugust 13, 2026 11:01

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, passphrase identities stay scoped to their own tiles, session, and removal without replacing the standard wallet.

@jvsena42
jvsena42 merged commit 92c085a into masterAug 14, 2026
27 checks passed
@jvsena42
jvsena42 deleted the feat/hw-passphrase-wallets branch August 14, 2026 10:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware wallet: passphrase (hidden) wallet pairing

3 participants

@jvsena42@ovitrif@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: pair trezor passphrase hidden wallets - #1142

Merged
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets
Aug 14, 2026
Merged

feat: pair trezor passphrase hidden wallets#1142
jvsena42 merged 36 commits into
masterfrom
feat/hw-passphrase-wallets

Conversation

@jvsena42

@jvsena42jvsena42 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Closes#1060

This PR:

  1. Adds an optional Passphrase path to the Connect Hardware flow, so the hidden wallets of a paired Trezor can be watched alongside its standard wallet
  2. Tracks each passphrase-derived account set as its own hardware wallet identity, with its own home tile, funds label, activity scope and removal
  3. Asks for the passphrase again before signing a transfer when the Trezor session that held it is gone, and refuses to sign if it opens a different wallet
  4. Lets the connect flow offer an already paired device, so passphrase wallets can be added after the initial pairing
  5. Fixes a Trezor Bridge session bug that blocked every passphrase pairing after the first on the emulator

Description

Trezor keeps no record of its hidden wallets and forgets a passphrase with the session, so Bitkit watches each one by its extended public keys and asks for the passphrase again whenever a session has to be rebuilt. The passphrase is never persisted or logged, and both entry screens block screenshots and screen recording the same way the wallet's own passphrase screens do.

A device previously mapped to a single wallet. It now maps to one identity per passphrase, keyed by the wallet id derived from its accounts rather than by the transport id, which two identities on the same device share. Watchers, balances, activity, labels and removal all follow that identity, and removing one hidden wallet leaves the others paired.

Pairing a hidden wallet adds an entry next to the standard one instead of replacing it. The device is matched by shared key material, so re-reading accounts updates the right entry while a different passphrase creates a new one. Only the identity that currently holds the device session shows as connected, since only that one can sign.

Before signing a transfer, Bitkit reopens the hidden wallet and accepts the session only if its accounts resolve back to the wallet being spent from. A wrong passphrase is not rejected by the device — it silently derives another wallet — so the mismatch is caught, the stray wallet that reading its accounts created is dropped, and the transfer fails instead of signing from the wrong wallet. A session that is still open signs with no prompt.

Discovery skips devices that are already paired, which left the Add Hardware Wallet button searching forever and made the passphrase step unreachable after the first pairing. It now falls back to offering a known reachable device once no new one is found.

Separately, the Bridge transport used for the emulator offered a released session id as the previous one when re-acquiring, and the bridge answered wrong previous session. Since switching to a passphrase wallet closes and reopens the session, this blocked every passphrase pairing after the first. This only affects builds with the Bridge enabled; USB and BLE are unaffected.

Preview

no-wallet-add-with-passphrase.webm
Add.multiple.passphrase.wallets.webm
add-repeated-passphrase-wallet.webm
passphrase-disabled.webm
remove-wallet.webm
rename-wallet.webm
transfer-current-session.webm
transfer-other-section.mp4

QA Notes

Needs a Trezor with passphrase protection enabled. On the emulator start it with TREZOR_PASSPHRASE_PROTECTION=true ../bitkit-docker/scripts/trezor-emulator start and confirm on the device for every account a passphrase session reads, otherwise the call blocks until it times out.

Manual Tests

  • 1a. Settings → Hardware Wallets → Add → Found → Connect: Device Connected shows Passphrase and Finish.
    • 1b. Passphrase → enter a passphrase → Continue: Passphrase Funds Found shows the balance and Label Funds prefilled with the device name.
    • 1c. Rename the label → Finish: home shows a second hardware tile under that name.
  • 2. Home: headline total includes both hardware balances, and only the identity holding the device session shows a green connection indicator.
  • 3. Paired or Passphrase Funds Found → Passphrase → re-enter a passphrase already watched: toast reports it is already added, no tile is added.
  • 3b. Device with passphrase protection turned off in Trezor Suite (the factory default) → Paired → Passphrase → enter any passphrase: error says to enable passphrase protection in Trezor Suite, no wallet is added, and the standard wallet is not left marked as passphrase-protected (Settings ▸ Hardware Wallets → Transfer To Spending on it still signs without asking for a passphrase).
  • 4a. Settings → Hardware Wallets: one row per identity, each with its own balance.
    • 4b. Rename one row: only that identity is renamed.
    • 4c. Delete the hidden wallet row: the standard wallet keeps working and the device stays paired, no re-pairing needed.
  • 5. Settings → Hardware Wallets → Add on an already paired device: reaches Found and then Device Connected, instead of searching forever.
  • 6a. Hidden wallet tile → Transfer To Spending → Amount → Sign → Open Trezor Connect while its session is still open: signs with no passphrase prompt.
    • 6b. Force-stop and relaunch the app, then repeat: the passphrase sheet opens before signing.
    • 6c. Enter a wrong passphrase: error says it opens a different wallet, nothing is signed or broadcast, and no extra tile appears.
    • 6d. Enter the correct passphrase: signing continues normally.
  • 7.regression: Pair a device without using the Passphrase path: home tile, detail screen, rename, remove and Transfer To Spending behave as before.
  • 8. Both passphrase screens: screenshots and screen recording are blocked, and the passphrase is absent from files/logs/ and files/datastore/ (release build; blocking is disabled in debug).

Automated Checks

  • Unit tests added: cover a passphrase wallet being added next to the standard one, the flag surviving reconnects, and credentials surviving a scoped removal in TrezorRepoTest.kt.
  • Unit tests added: cover two identities on one device, per-identity balances, connected state, funding account, scoped removal, and connectWithPassphrase including the already-watched case and a device with passphrase protection off in HwWalletRepoTest.kt.
  • Unit tests added: cover the passphrase step effects, the duplicate toast, the label prefill, the paired step following the right identity, and the fallback to an already paired device in HwConnectViewModelTest.kt.
  • Unit tests added: cover signing with a live session, prompting once it is gone, and refusing a passphrase that opens another wallet in TransferViewModelTest.kt.
  • Unit tests added: cover re-acquiring after a release without the stale session in TrezorBridgeTransportTest.kt.
  • Unit tests modified: existing hardware tests moved to wallet-id keying in HwWalletRepoTest.kt, TransferViewModelTest.kt and HwConnectViewModelTest.kt.
  • Journeys added: passphrase-pairing.xml, passphrase-duplicate.xml, passphrase-settings-remove.xml and passphrase-transfer-to-spending.xml, including checks that the passphrase never reaches the logs or datastore.
  • Verified on a Pixel emulator against the Trezor emulator: pairing two hidden wallets alongside the standard one, per-identity labels, three home tiles, the connected indicator, and no passphrase in the logs. The duplicate, settings-remove and transfer journeys were not executed; the transfer one additionally needs regtest funds on a hidden wallet.
  • CI: standard compile, unit test and detekt checks run by the PR bot.

Known gaps

  • A hidden wallet can persist with only part of its accounts when a read fails with a device error that is not classified as transient, and its wallet id would change once the missing accounts are read. This is a pre-existing gap in the partial-save guard that passphrase sessions make easier to hit.
  • Two hidden wallets on the same device default to the same name until renamed, since the default label is the device name.

@jvsena42jvsena42 self-assigned this Aug 6, 2026
Comment threadapp/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt Fixed
…eId in it.deviceIds } and unconditionally writes pairedWalletId = wallet.id, so a wallets emission that does not yet contain the just-added hidden wallet resets the paired identity back to the standard wallet.
…se POST, so a failed release still leaves the stale session id cached.
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 10:05
@greptile-apps

greptile-appsBot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds independently tracked Trezor hidden-wallet identities and passphrase-based session restoration while preserving wallet-scoped balances, activity, labels, signing, and removal.

  • Keys hardware-wallet state and watchers by derived wallet identity instead of transport ID.
  • Adds passphrase pairing and signing-reconnection flows with wallet-identity verification.
  • Keeps sibling identities and transport credentials when removing one hidden wallet.
  • Refreshes stale Trezor Bridge sessions before retrying acquisition.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
app/src/main/java/to/bitkit/repositories/HwWalletRepo.ktMoves hardware-wallet aggregation, watcher lifecycle, signing, and removal to wallet-identity scope; the previously reported multi-transport removal issue is addressed.
app/src/main/java/to/bitkit/repositories/TrezorRepo.ktPersists multiple identities per physical device and manages passphrase-bound sessions while atomically filtering scoped removals.
app/src/main/java/to/bitkit/services/TrezorBridgeTransport.ktClears released sessions and retries acquisition after refreshing a stale Bridge session.
app/src/main/java/to/bitkit/viewmodels/TransferViewModel.ktIntegrates passphrase restoration and identity verification into hardware-wallet signing.
app/src/main/java/to/bitkit/ui/sheets/hardware/HwConnectViewModel.ktAdds passphrase-wallet pairing, duplicate handling, and known-device discovery fallback.
app/src/main/java/to/bitkit/ui/screens/transfer/hardware/HwPassphrasePromptSheet.ktAdds the intentionally unmasked, screenshot-protected passphrase prompt used to restore signing sessions.

Sequence Diagram

sequenceDiagram
participant UI as Hardware Wallet UI
participant HW as HwWalletRepo
participant TR as TrezorRepo
participant Device as Trezor
participant Store as HwWalletStore
UI->>HW: connectWithPassphrase(deviceId, passphrase)
HW->>TR: set/connect passphrase wallet mode
TR->>Device: Open fresh passphrase session
Device-->>TR: Features and account keys
TR->>Store: Persist identity by derived wallet ID
TR-->>HW: Connected wallet identity
HW-->>UI: Add or select hidden-wallet tile
UI->>HW: Sign from wallet ID
alt matching session is live
HW->>Device: Sign transaction
else session is absent or belongs to another identity
UI->>HW: reconnectWithPassphrase(walletId, passphrase)
HW->>TR: Open fresh passphrase session
TR->>Device: Read account keys
alt derived wallet ID matches
HW->>Device: Sign transaction
else derived wallet ID differs
HW->>TR: Disconnect stray session
HW-->>UI: Reject passphrase mismatch
end
end
Loading

Reviews (3): Last reviewed commit: "fix: passphrase should not gate pending ..." | Re-trigger Greptile

Comment threadapp/src/main/java/to/bitkit/repositories/TrezorRepo.kt
@jvsena42
jvsena42 marked this pull request as draft August 10, 2026 10:11
@jvsena42
jvsena42 marked this pull request as ready for review August 10, 2026 11:33
@jvsena42
jvsena42 requested a review from ovitrifAugust 10, 2026 11:55
@jvsena42

jvsena42 commented Aug 10, 2026

Copy link
Copy Markdown
MemberAuthor

checking CI failures

Edit: both are backend failures

@jvsena42
jvsena42 requested review from ovitrif and removed request for ovitrifAugust 10, 2026 16:21
@jvsena42jvsena42 added this to the 2.5.0 milestone Aug 11, 2026
@jvsena42

This comment was marked as resolved.

@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 marked this pull request as draft August 12, 2026 17:17
@jvsena42

This comment was marked as resolved.

@jvsena42
jvsena42 removed the request for review from ovitrifAugust 12, 2026 17:17
@jvsena42
jvsena42 marked this pull request as ready for review August 13, 2026 09:54
@jvsena42

Copy link
Copy Markdown
MemberAuthor

All test passing. Re-runned manual tests checklist with journeys

@jvsena42
jvsena42 requested a review from ovitrifAugust 13, 2026 11:01

@ovitrifovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, passphrase identities stay scoped to their own tiles, session, and removal without replacing the standard wallet.

@jvsena42
jvsena42 merged commit 92c085a into masterAug 14, 2026
27 checks passed
@jvsena42
jvsena42 deleted the feat/hw-passphrase-wallets branch August 14, 2026 10:12
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware wallet: passphrase (hidden) wallet pairing

3 participants

@jvsena42@ovitrif@github-advanced-security