KeepKey hardware wallet integration — Phase 1 (account setup + Orchard PCZT signing)#1
Draft
pastaghost wants to merge 18 commits into
Draft
KeepKey hardware wallet integration — Phase 1 (account setup + Orchard PCZT signing)#1pastaghost wants to merge 18 commits into
pastaghost wants to merge 18 commits into
Conversation
Adds the build infrastructure, USB HID transport implementation, and KeepKeyAccount model needed for KeepKey hardware wallet integration. - Add com.google.protobuf Gradle plugin (v0.9.4) and protobuf-kotlin-lite (v4.28.2) to settings.gradle.kts and ui-lib/build.gradle.kts - Create ui-lib/src/main/proto/ with README listing required firmware proto files (messages-zcash.proto, messages.proto, types.proto) - Implement KeepKeyTransportProviderImpl: 64-byte HID framing (0x3F header), writePackets/readPackets, USB endpoint discovery, GetFeatures call to read firmware version - Add KeepKeyAccount to the WalletAccount sealed interface hierarchy; stores seedFingerprint for per-session device binding - Add ic_item_keepkey.xml drawable (light + dark), keepkey_account_name and connect/error string resources, keepkey_device_filter.xml - Wire USB_DEVICE_ATTACHED intent filter and meta-data into MainActivity - Register KeepKeyTransportProviderImpl in Koin ProviderModule Note: proto files must be copied from keepkey-firmware/deps/device-protocol/ at commit de297b7a before the build will generate message bindings. Lockfiles will need updating after first Gradle sync.
Copies messages-zcash.proto, messages.proto, and types.proto from keepkey/device-protocol release/7.14.1-device-protocol into the Gradle protobuf source set. Enables generateProto to produce Kotlin lite bindings for all ZCash KeepKey messages (IDs 1300–1309).
Implements the complete account-setup UI for KeepKey Phase 1: - ConnectKeepKeyUseCase: sends ZcashGetOrchardFVK (msg 1304), decodes ZcashOrchardFVK (msg 1305), encodes the UFVK via ZIP-316 + Bech32m, derives a stable seed fingerprint, imports the account and navigates. - Blake2b (pure Kotlin, API-27 safe) and OrchardUfvkEncoder (F4Jumble + Bech32m) in the new crypto package. - connectkeepkey/connect screen: KeepKeyConnectState/VM/View/Screen - connectkeepkey/connected screen: success confirmation after pairing - GetKeepKeyStatusUseCase: ENABLED when no KeepKey account exists - IntegrationsVM: adds KeepKey list item (UNAVAILABLE hides it) - AccountListVM: "Connect Hardware Wallet" button now routes to IntegrationsArgs and hides when any HW account (Keystone or KeepKey) is present - KeepOpenFlow.KEEPKEY + KeepOpenVM strings for the sync-wait screen - DI: factoryOf(ConnectKeepKey/GetKeepKeyStatus), viewModelOf(KeepKeyConnectVM) - Nav graph: ConnectKeepKeyArgs and KeepKeyConnectedArgs routes - String resources (en + es) for connect, connected, keep-open, and integrations screens - ic_integrations_keepkey drawable (light + dark)
…type addition
- Add exhaustive `is KeepKeyAccount` branches to all `when` expressions
across use cases, VMs, and repositories (Phase 2 paths throw; display
paths use KeepKey icon/string/enum; proposal flows return flowOf(null))
- Fix protobuf Kotlin DSL: id("java"/"kotlin") → create("java"/"kotlin")
- Fix BLAKE2b IV literals: hex constants that overflow signed Long now use
java.lang.Long.parseUnsignedLong for correct two's-complement bit patterns
- Fix AccountDataSource: seedFingerprint read from sdkAccount.seedFingerprint
directly (not via .purpose which does not exist on Account)
- Remove @throws annotation from ConnectKeepKeyUseCase class level (invalid target)
- Fix duplicate keepkey_account_name string resource
- Add importKeepKeyAccount stub to FakeAccountDataSource in unit tests
- Build verified: compileZcashmainnetInternalDebugKotlin succeeds
- Tests verified: testZcashmainnetInternalDebugUnitTest passes
Add requestPermission() to KeepKeyTransportProvider interface and impl. Uses a one-shot BroadcastReceiver with suspendCancellableCoroutine to suspend until the user responds to the system permission dialog, then unregisters the receiver. ConnectKeepKeyUseCase now calls requestPermission() before connect(), throwing on denial.
Implements the proposal lifecycle for KeepKey USB signing: - createProposal / createExactInput/OutputSwapProposal / createZip321Proposal / createShieldProposal - signAndSubmit(): createPczt → addProofs → redactForSigner → USB signing exchange → broadcast - Full ZcashSignPCZT → ZcashPCZTAction × N → ZcashSignedPCZT message loop - Registered as a Koin singleton in RepositoryModule Two SDK gaps are documented with TODO(sdk) comments and stubs that throw UnsupportedOperationException at runtime until resolved: 1. Extracting per-action fields (n_actions, digests, alpha, cv_net, …) from a Pczt 2. Inserting RedPallas signatures back into the Pczt (Synchronizer.addSpendAuthSigsToPczt)
State/VM/View/Screen for the USB signing confirmation flow: - "Confirm on KeepKey" title + subtitle from string resources - CircularProgressIndicator while signAndSubmit() is in-flight - Success navigates to TransactionProgressArgs via replace() - Failure surfaces error message; cancel/back calls CancelProposalFlowUseCase - SignKeepKeyTransactionVM registered in ViewModelModule (Koin) - SignKeepKeyTransactionArgs registered in WalletNavGraph - Spanish string translations added alongside English
Replace error() stubs in CreateProposal, GetProposal, ObserveProposal, SubmitProposal, and CancelProposalFlow with real KeepKeyProposalRepository calls. SubmitProposal now navigates to SignKeepKeyTransactionArgs for KeepKey accounts. CancelProposalFlow clears the KeepKey repo on cancel.
Add new-or-active, date, estimation, and height sub-screens under connectkeepkey/ mirroring the Keystone birthday flow. Update KeepKeyConnectVM to navigate to KeepKeyNewOrActiveScreen instead of running the full use case; the actual USB connect + FVK fetch happens when the user confirms their device type or birthday choice. Wire all new routes in WalletNavGraph and register VMs in ViewModelModule.
Generalize DisconnectUseCase and DisconnectVM from KeystoneAccount to WalletAccount so KeepKey accounts can also be disconnected via the same UI flow. getKeystoneAccount() renamed to getHardwareWalletAccount(). Also update TODO.md to mark ZA-44–46, ZA-63–65 complete.
Pulled buildKeepKeyPackets / parseKeepKeyPackets out of KeepKeyTransportProvider into KeepKeyFraming.kt (internal visibility) so JVM unit tests can reach them without Android SDK dependencies. KeepKeyTransportProvider now delegates to those functions. 14 tests cover boundary sizes (0, 57, 58, 120, 121, 200 bytes), big-endian type-ID / length encoding, per-packet invariants, round-trips at every boundary, and both bad-marker error paths. All 137 tasks pass.
Pulled the ZcashSignPCZT → ZcashPCZTAction × N → ZcashSignedPCZT exchange out of KeepKeyProposalRepository into KeepKeySigningProtocol (internal class, pure Kotlin, no Android/ZCash SDK types). The repository now delegates to it. 11 tests in KeepKeySigningProtocolTest cover zero/single/multi-action happy paths, MSG_FAILURE on both init and action messages, wrong response type, out-of-order action index, account index encoding, and verbatim PCZT byte forwarding. All 137 unit test tasks pass.
… unit tests combine(isLoading, errorMessage) replaces map so the UI re-emits when errorMessage changes independently of isLoading; previously error was swallowed because the map only fired on isLoading changes. Blake2bTest: correct RFC 7693 vector for "a" (hash[1]=0x3f, hash[3]=0x4e), add "abc" vector — implementation was already correct.
KeepKeyEmulatorTransportProvider implements KeepKeyTransportProvider using the Flask HTTP bridge (bridge.py) in front of the Docker emulator's UDP sockets, making the full signing protocol testable without physical hardware. KeepKeyEmulatorDebugLink wraps the debug link interface to load a known mnemonic and simulate button presses in test setup. KeepKeyEmulatorIntegrationTest drives ZcashGetOrchardFVK against the emulator and asserts golden FVK vectors from test_msg_zcash_orchard.py. Tests self-skip via Assume when the bridge is unreachable so CI without the Docker stack is unaffected.
…ning tests OrchardActionData carries alpha + sighash (legacy mode) per Orchard action. KeepKeySigningProtocol.sign() now forwards these into ZcashPCZTAction and also sets address_n, n_actions, total_amount, and fee in ZcashSignPCZT. Existing production call site (nActions=0, actions=emptyList) is unchanged. KeepKeyEmulatorIntegrationTest gains 7 signing tests: single-action signature size/non-zero/count, 3-action count, different-sighash produces different sig, different-account produces different sig, zero-actions empty. All skip automatically when the Docker emulator is unreachable.
KeepKeyConnectViewTest: 10 tests covering idle, loading, error, and callback states for KeepKeyConnectView and KeepKeyConnectedView. SignKeepKeyTransactionViewTest: 12 tests covering title/subtitle display, button visibility in loading state, error message show/hide, positive and negative callbacks, and disabled-button enforcement. All tests build state directly — no ViewModel, transport, or emulator.
…Case Replace the monolithic ConnectKeepKeyUseCase with two focused use cases: - GetKeepKeyOrchardFVKUseCase: USB permission + connect + FVK export + UFVK encode + seed fingerprint derivation + unified address derivation - ImportKeepKeyAccountUseCase: account import + navigation Add selectkeepkeyaccount/ package (State, View, ViewModel, Screen, Args) that mirrors the Keystone account selection screen. The three birthday-aware VMs (KeepKeyNewOrActiveVM, KeepKeyEstimationVM, KeepKeyHeightVM) now call GetKeepKeyOrchardFVKUseCase and navigate to SelectKeepKeyAccountArgs instead of importing directly. The selection screen shows the abbreviated unified address derived from the FVK and lets the user confirm before ImportKeepKeyAccountUseCase writes the account to the SDK and navigates to the connected/resync flow.
- detekt: add @Suppress("TooManyFunctions") to KeepKeyTransportProviderImpl and KeepKeyEmulatorTransportProvider (both exceed the 11-function threshold matching the same pattern used by KeystoneProposalRepository and AccountDataSource) - detekt: extract ZIP32_PURPOSE, ZCASH_COIN_TYPE, ORCHARD_FIELD_BYTES constants in KeepKeySigningProtocol to eliminate MagicNumber violations on derivation path - detekt: add @Suppress("MagicNumber") to byte-manipulation functions (readVarint, buildKeepKeyPackets, parseKeepKeyPackets, fromHex, httpPost) where raw bit masks are the clearest expression of the protocol spec - detekt: replace bare TODO(sdk) comments in KeepKeyProposalRepository with TODO [#2]: format referencing the new GitHub issue tracking the ZCash SDK gap (addSpendAuthSigsToPczt not yet available); open issue #2 in keepkey/zodl-android - detekt: fix MaxLineLength in KeepKeyTransportProvider (wrap throw into block) and KeepKeyProposalRepository (split long Suppress annotation and shorten comments) - ktlint: run ktlintFormat to fix formatting across all KeepKey files and any existing files we modified as part of the integration (block wrapping, chain method continuation, no-multi-spaces, multiline-expression-wrapping)
8a8cab6 to
12d712e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
What's included
Transport & Protocol
KeepKeyTransportProvider— USB HID framing (buildKeepKeyPackets/parseKeepKeyPackets), 64-byte packets, AndroidUsbManagerintegrationKeepKeyEmulatorTransportProvider— HTTP bridge transport for thekktech/kkemuDocker emulator (integration testing without physical hardware)KeepKeyEmulatorDebugLink— debug link forLoadDevice/ button automation in emulator testsKeepKeySigningProtocol—ZcashSignPCZT → ZcashPCZTAction × N → ZcashSignedPCZTstate machineOrchardUfvkEncoder— ZIP-316 Bech32m encoding for UFVK stringsBlake2b— pure-Kotlin BLAKE2b-256 for seed fingerprint derivationUse Cases
GetKeepKeyOrchardFVKUseCase— USB permission + connect +ZcashGetOrchardFVK+ UFVK encode + seed fingerprint + unified address derivationImportKeepKeyAccountUseCase— SDK account import + navigationKeepKeyProposalRepository— proposal lifecycle, PCZT creation, signing orchestration (mirrorsKeystoneProposalRepository)Screens
connectkeepkey/— connect, connected, date, estimation, height, neworactive sub-screens (mirrorsconnectkeystone/)selectkeepkeyaccount/— account confirmation screen showing derived unified address before importsignkeepkeytransaction/— signing screen with progress indicator, error/cancel handlingTesting
KeepKeyFramingTest)KeepKeySigningProtocolTest)Blake2bTest)KeepKeyConnectViewTest)SignKeepKeyTransactionViewTest)kktech/kkemuDocker image — auto-skip when emulator is unreachable (KeepKeyEmulatorIntegrationTest)Test plan
./gradlew :ui-lib:compileZcashmainnetInternalDebugKotlin— must be clean./gradlew :ui-lib:testZcashmainnetInternalDebugUnitTest— all unit tests passcd keepkey-firmware/scripts/emulator && docker-compose up) and run instrumented integration testsDerivation path
m/32'/133'/0'm/44'/133'/0'/0/0Not included in this PR (deferred)
ZcashTransparentInput/ZcashTransparentSig)🤖 Generated with Claude Code