Skip to content

feat: add seedqr wallet recovery - #1319

Merged
pwltr merged 1 commit into
masterfrom
codex/seedqr-import-android
Sep 22, 2026
Merged

pwltr merged 1 commit into
masterfrom
codex/seedqr-import-android

Conversation

@pwltr

@pwltr pwltr commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1277

Related to bitkit-ios#760.

This PR:

  1. Adds Standard and Compact SeedQR import to the 12-word wallet recovery flow
  2. Preserves binary QR payloads so Compact SeedQR data reaches the shared bitkit-core decoder unchanged
  3. Presents the scanner correctly during onboarding and aligns its full-screen header with the restore screen
  4. Normalizes the scan icon while preserving its established tab-bar appearance

Description

  • Adds a scan action to wallet recovery so a 12-word SeedQR can populate the existing recovery fields without transmitting or persisting seed material.
  • Uses the shared bitkit-core 0.5.18 Standard and Compact SeedQR decoders so validation behavior remains consistent across Android and iOS.
  • Carries both text and raw QR bytes through camera, gallery, paste, and send scanner paths so binary Compact SeedQR payloads are decoded without changing existing text-based scans.
  • Keeps invalid or unsupported payloads on the recovery screen and shows the existing QR error feedback without replacing entered words.
  • Hosts the restore scanner during onboarding, removes the sheet gradient in its full-screen presentation, and uses the standard app header so safe areas and back navigation remain aligned.
  • Adds optical padding to the shared scan vector so navigation and contact actions match adjacent icon proportions while retaining the current tab-bar appearance.

Out of Scope

  • SeedQR export: this PR supports import only.
  • Recovery formats: 24-word mnemonics and non-SeedQR encodings remain unsupported.
  • Simulator gallery behavior: no simulator-specific photo-picker workaround is added.

Design

  • Bitkit Wallet Figma
  • Mapped frames: Restore wallet › Restore wallet and Send (QuickPay) › Scan QR invoice Quickpay.
  • The new restore-specific scanner action and full-screen scanner presentation have no dedicated design frame.

Preview

QA Notes

Manual Tests

  • 1a. Restore Wallet → scan a valid 12-word Standard SeedQR: all 12 word fields populate in order and Restore becomes enabled.
    • 1b. Restore Wallet → scan the equivalent Compact SeedQR: the same 12 words populate and Restore becomes enabled.
  • 2. Restore Wallet → scan an invalid or unsupported QR: an error toast appears and the existing recovery words remain unchanged.
  • 3. Restore Wallet → scanner → back: the back arrow stays vertically aligned across both screens and returns to the entered recovery words.
  • 4. regression: Restore Wallet → enter or paste a valid 12-word mnemonic manually: recovery remains available without using the scanner.
  • 5. regression: Send → scan and paste text-based Bitcoin, Lightning, LNURL, and Bitkit links: existing scanner routing is unchanged.
  • 6. regression: Home tab bar and Add Contact: the scan glyph is optically aligned with adjacent icons and the tab-bar scan button retains its previous apparent size.

Automated Checks

  • Unit tests added in RestoreWalletSeedQrViewModelTest.kt: decoded mnemonics populate all recovery fields, while invalid scans preserve state and emit error feedback.
  • Unit tests modified in RestoreWalletViewModelTest.kt: existing restore behavior uses the injected SeedQR repository without changing manual mnemonic coverage.
  • Local validation: just compile, just test, and just lint pass; detekt reports only repository-baseline findings.

@greptile-apps

greptile-apps Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

The PR should not merge until mixed-barcode selection is corrected so binary SeedQR support cannot block valid text scans in existing send and sheet flows.

Findings

  1. P1 Binary Barcode Blocks Text ▶

Summary

This PR adds Standard and Compact SeedQR recovery by preserving text and binary QR representations through the scanner, decoding them through bitkit-core, and populating the existing restore-wallet state.

  • Adds a SeedQR repository and recovery-screen scan action.
  • Generalizes scanner results from text strings to text/raw-byte payloads.
  • Adds full-screen onboarding scanner presentation and adjusts the shared scan icon.
  • Updates bitkit-core to 0.5.18 and adds recovery view-model tests.
  • The generalized barcode-selection logic introduces a regression for existing text-only flows when a binary barcode precedes a valid text barcode.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Camera[Camera, gallery, or paste] --> Payload[QrCodePayload]
  Payload -->|Restore flow| SeedRepo[SeedQrRepo]
  SeedRepo -->|Standard text| Standard[decodeStandardSeedQr]
  SeedRepo -->|16 raw bytes| Compact[decodeCompactSeedQr]
  Standard --> Mnemonic[12-word mnemonic]
  Compact --> Mnemonic
  Mnemonic --> RestoreVM[RestoreWalletViewModel]
  RestoreVM --> Fields[Populate and validate recovery fields]
  Payload -->|Existing scanner flows| TextOnly[Require payload.text]
  TextOnly --> Routing[Bitcoin, Lightning, LNURL, and link routing]
Loading

Reviews (1) · Last reviewed commit: "fix: align restore scanner header"

Comment thread app/src/main/java/to/bitkit/ui/screens/scanner/QrCodeAnalyzer.kt Outdated
@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from b33de79 (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@pwltr
pwltr requested review from a team, ben-kaufman and piotr-iohk and removed request for a team September 21, 2026 18:57
Comment thread app/src/main/java/to/bitkit/ui/screens/scanner/QrScanningScreen.kt Outdated
Comment thread app/src/main/java/to/bitkit/ui/onboarding/RestoreWalletScreen.kt

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No new findings at bb92c731b. Ben's clipboard-as-Compact and system-Back threads are still open, and I have not repeated them.

Checked and clean:

  • Decode. bitkit-core 0.5.18 Standard decoding requires 48 digits, indices < 2048, and validate_mnemonic. A malformed Standard QR errors out and leaves the words untouched. replaceAllWords re-runs the BIP39 check anyway. 24-word and 32-byte payloads fall through to InvalidPayload, which matches the 12-word scope.
  • Key material. The payload and mnemonic are never logged, and no effect carries the mnemonic. A SeedQR scanned from Send or the main scanner is only logged as a redacted hash, lands in the address field, and fails decode. Nothing is persisted and nothing goes to the clipboard. scanResult is nulled right after delivery, and BlockScreenshots() covers the in-screen scanner.
  • Back and cancel. Toolbar Back in the scanner only flips isScanningSeedQr. An invalid scan keeps the words, and a valid scan replaces only the words; the passphrase is kept.
  • Payment QR regression. selectQrCodePayload still picks the first barcode with a non-null rawValue. The binary fallback applies only when acceptsBinaryPayload, and every non-restore call site leaves that false.
  • Dependency. The only change is the first-party bitkit-core pin, 0.5.14 → 0.5.18. The same pin is in synonymdev/bitkit-ios#760.

Unverified, device-only: that ML Kit rawBytes for a byte-mode Compact SeedQR is exactly the 16 payload bytes, including bytes ≥ 0x80. Scanning bitkit-core's fixture 5bbd9d71a8ec7990831aff359d426545 on a physical device should give forum undo fragile … off merit.

@pwltr
pwltr requested a review from ben-kaufman September 22, 2026 10:13

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Delta since bb92c731b (f2e57daf9): no findings. Clipboard and debug input now go through textQrCodePayload with rawBytes = null, so a 16-character paste can no longer decode as Compact entropy. The camera and gallery paths still take ML Kit rawBytes through selectQrCodePayload. BackHandler(enabled = isScanningSeedQr) closes the inline scanner on system Back and keeps the form.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

QA reviewed on f2e57da.

Since the review on bb92c73, one commit. System Back on the restore scanner returns to the form. Clipboard and debug text no longer carry raw bytes, so a 16-character paste is not treated as Compact SeedQR.

Ran on Pixel_6: typed a word, opened Scan QR Code, pressed system Back. The form and the word were still there. On the S22, a 12-word Standard SeedQR filled the words. The matching Compact SeedQR did too, after a closer hold. An invalid code and a 24-word code were rejected and left the words alone.

iPhone 17: Standard and Compact both filled the same phrase. Invalid and 24-word were rejected with the 12-word SeedQR message.

Twin synonymdev/bitkit-ios#760: same import. Its two low notes are fixed in f710485.

Findings

LOW

  • [twin] An invalid or 24-word SeedQR on Restore shows the generic "Bitkit is not able to read this QR code." The camera did read it. iOS says "This QR code does not contain a valid 12-word SeedQR." RestoreWalletScreen.kt:115 uses other__qr_error_text. iOS SeedQRCodeScannerView uses onboarding__restore_seedqr_error.
Scale

HIGH must be fixed before merge · MEDIUM good to fix here or in a follow-up · LOW non-blocking, nit
[coverage] untested path or stale test · [ids] missing/mismatched test identifier · [security] funds, auth, secrets · [twin] iOS/Android drift · [bug] defect found by reading · [device] seen on emulator/simulator

piotr-iohk
piotr-iohk previously approved these changes Sep 22, 2026
@pwltr

pwltr commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the LOW [twin] finding in f89a295. Invalid and unsupported SeedQR payloads on Restore now use the dedicated message: “This QR code does not contain a valid 12-word SeedQR.” This matches iOS and distinguishes a decoded-but-invalid payload from a camera read failure.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Delta since f2e57daf9 (f89a295e0): no findings. An invalid scan now shows onboarding__restore_seedqr_error, which is added in alphabetical order.

@piotr-iohk

Copy link
Copy Markdown
Collaborator

QA reviewed on f89a295.

Since the review on f2e57da, one commit. Invalid and unsupported SeedQR on Restore now uses "This QR code does not contain a valid 12-word SeedQR," matching iOS.

Reviewed the restore scanner toast key and the surrounding decode path; no device run this round.

Twin bitkit-ios#760: already reviewed on f710485.

No findings.

Checked and clean

  • InvalidSeedQr toast uses onboarding__restore_seedqr_error; camera-read failures still use other__qr_error_text
  • BackHandler still closes the inline scanner and keeps the form
  • Clipboard and debug input stay text-only
  • selectQrCodePayload still prefers text, then binary only when acceptsBinaryPayload

@pwltr
pwltr enabled auto-merge (squash) September 22, 2026 14:19
@pwltr
pwltr force-pushed the codex/seedqr-import-android branch from f89a295 to b33de79 Compare September 22, 2026 14:24
@pwltr

pwltr commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

Squashed commits

@pwltr
pwltr merged commit 8f123b1 into master Sep 22, 2026
19 checks passed
@pwltr
pwltr deleted the codex/seedqr-import-android branch September 22, 2026 14:52
Sign up for free to 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.

[Feature]: Import BIP39 recovery phrase via SeedQR for Bitkit

4 participants