Skip to content

Add touch-friendly SeedQR transcription viewer - #369

Open
schnuartz-ai wants to merge 1 commit into
cryptoadvance:masterfrom
schnuartz-ai:feature/seedqr-transcription-viewer
Open

Add touch-friendly SeedQR transcription viewer#369
schnuartz-ai wants to merge 1 commit into
cryptoadvance:masterfrom
schnuartz-ai:feature/seedqr-transcription-viewer

Conversation

@schnuartz-ai

@schnuartz-ai schnuartz-ai commented Jul 28, 2026

Copy link
Copy Markdown

Summary

Implements a guided, zone-based SeedQR transcription viewer, adapted from
SeedSigner's SeedQR transcription workflow to Specter DIY's 480x800
touchscreen (see SeedSigner's docs/seed_qr/README.md for the reference UX).

Selecting SeedQR (digits) or Compact SeedQR (binary) from the
mnemonic export menu now opens a dedicated overview screen: the complete
QR code, split into labeled sections, with a coordinate axis (row letters
/ column numbers) around it and a "Tap a section to enlarge" hint. Tapping
a section opens it hugely enlarged, with crisp square modules, visible
grid lines, and Left/Right/Up/Down navigation to neighboring sections —
making it practical to copy a SeedQR onto paper or a metal backup plate by
hand. Returning to the overview preserves the section you were last
looking at. Plaintext QR export is unchanged.

(I'll attach a few simulator screenshots to this description separately.)

Design

  • src/seedqr.py: pure matrix/geometry helpers (matrix generation +
    validation, section geometry, touch-to-section mapping, navigation) with
    no lvgl dependency, so this logic is unit-testable on a desktop
    Python interpreter without a display or firmware build.
  • src/gui/screens/seedqr.py: SeedQROverviewScreen + SeedQRZoomScreen,
    and the show_seedqr() overview<->zoom navigation loop. Both screens
    render the same canonical module matrix (generated once via
    seedqr.generate_matrix()), so the overview and every zoomed section
    can never disagree about module data.
  • src/keystore/ram.py: routes the Standard/Compact SeedQR menu choices
    to show_seedqr(); the Plaintext QR path is unchanged except it now
    passes sensitive=True.
  • Section sizing matches SeedSigner: a 21x21 QR groups into 7x7-module
    sections (3x3 grid); every larger SeedQR size groups into 5x5-module
    sections. A non-square-divisible size (e.g. 29x29) has a smaller
    trailing row/column, which is rendered at its true, smaller size (not
    padded out with filler modules).

Security

This handles wallet recovery material, so:

  • The mnemonic, SeedQR digit string, Compact SeedQR bytes, and the QR
    module matrix are never printed, logged, or persisted.
  • While reviewing gui/components/qrcode.py I found a pre-existing
    unconditional print(text) that logged the full QR payload even on
    real hardware
    , not just the simulator. Removed it, and added a
    sensitive flag (threaded through add_qrcode/QRAlert) so simulator
    debug logging can be suppressed per-QR; it's now set for every
    mnemonic-related QR, including the pre-existing Plaintext export path.
  • Both new screens drop their references to the secret matrix on LVGL
    DELETE.

Companion fix (separate repo)

While building the matrix-generation helper I found a pre-existing bug in
the f469-disco submodule's qrcode usermod: _qrcode_encode() decided
text-vs-binary mode by scanning for an embedded 0x00 byte, so a bytes
payload with no zero byte (common for Compact SeedQR's raw entropy) fell
through to qrcodegen_encodeText(), which expects a NUL-terminated string
and could read past the intended data into uninitialized stack memory.
Fixed at diybitcoinhardware/f469-disco#43 (using the actual Python object
type instead of content-sniffing). I don't have write access to that
repo's upstream, so it's a separate PR; the f469-disco submodule pointer
in this PR is unchanged (still upstream's commit) — once #43 merges,
the submodule pointer here should be bumped in a follow-up commit.

Test plan

  • python3 run_native_tests.py — 41/41 tests pass (encoding, section
    geometry, touch mapping, navigation, and print/log security
    regressions for the pure seedqr module).
  • python3 -m compileall src test — clean.
  • make unix (real firmware build, including this submodule fix) —
    builds successfully; confirmed the compiled qrcode.encode_to_string()
    produces the correct matrix size for all 4 SeedQR combinations
    (12/24-word x Standard/Compact), including entropy with no embedded
    zero byte.
  • Manually exercised all 4 combinations in the unix simulator: full
    overview, tapping into first/middle/last/partial sections, all four
    nav directions, back-to-overview section
  • Not tested on physical hardware.

This PR implements the SeedQR transcription mode requested in #212, including the sectioned overview and enlarged grid-based navigation for copying the QR to paper or metal.

Fixes #212

Screenshot 2026-07-28 112219 Screenshot 2026-07-28 112239 Screenshot 2026-07-28 114854 Screenshot 2026-07-28 122510 Screenshot 2026-07-28 122524 Screenshot 2026-07-28 122503

Selecting Standard SeedQR (digits) or Compact SeedQR (binary) from the
mnemonic export menu now opens a dedicated overview screen: the complete
QR code, split into labeled sections (matching SeedSigner's SeedQR
transcription workflow), with a coordinate axis (row letters / column
numbers) around it. Tapping a section opens it hugely enlarged, with
crisp square modules, visible grid lines, and Left/Right/Up/Down
navigation to neighboring sections -- making it practical to copy a
SeedQR onto paper or a metal backup plate by hand.

- src/seedqr.py: pure matrix/geometry helpers (no lvgl dependency), so
  section geometry, touch-to-section mapping, and navigation logic are
  unit-testable without a display or firmware build.
- src/gui/screens/seedqr.py: the two new screens (SeedQROverviewScreen,
  SeedQRZoomScreen) and the overview<->zoom navigation loop.
- src/keystore/ram.py: routes Standard/Compact SeedQR selections to the
  new viewer; Plaintext QR export is unchanged except it's now marked
  sensitive so it stops being logged in the simulator.
- src/gui/components/qrcode.py, gui/common.py, gui/screens/qralert.py:
  add a `sensitive` flag and remove an unconditional print() that leaked
  QR contents (including on real hardware, not just the simulator).
- test/tests_native/test_seedqr.py: encoding, section geometry, touch
  mapping, navigation, and security-regression tests (41 total) for the
  pure logic module.

The corresponding fix to the f469-disco submodule's qrcode usermod (a
pre-existing bug where compact/binary QR payloads could read past their
buffer into uninitialized stack memory) is proposed separately at
diybitcoinhardware/f469-disco#43, since this repo has no write access to
that submodule's upstream. Once that merges, the f469-disco submodule
pointer here should be bumped in a follow-up commit.
@netlify

netlify Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploy Preview for specter-diy-docs ready!

Name Link
🔨 Latest commit 3d48e6f
🔍 Latest deploy log https://app.netlify.com/projects/specter-diy-docs/deploys/6a6882ab0b3643000863ba93
😎 Deploy Preview https://deploy-preview-369--specter-diy-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

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.

Add transcription mode to SeedQR

1 participant