Add touch-friendly SeedQR transcription viewer - #369
Open
schnuartz-ai wants to merge 1 commit into
Open
Conversation
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.
✅ Deploy Preview for specter-diy-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
|
@al-munazzim could you review that? |
al-munazzim
approved these changes
Aug 6, 2026
al-munazzim
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the thorough implementation and test coverage. I reviewed the SeedQR viewer changes, including the pure seedqr helpers, the LVGL overview/zoom screens, the mnemonic export routing, and the QR logging/sensitive handling.
What I checked locally:
python3 -m compileall -q src testpython test/run_native_tests.pyin a fresh uv venv withtest/integration/requirements.txtinstalled: 41/41 tests passed- Diff/read-through of the security-sensitive paths that handle mnemonic / Standard SeedQR / Compact SeedQR material
The design looks sound to me:
- Standard and Compact SeedQR now route to the transcription viewer without rendering the raw digit string / entropy bytes as text.
- The viewer uses one canonical QR module matrix for overview and zoom, so the two views cannot diverge.
- Section sizing and partial trailing sections are covered by tests.
- The previous QR payload logging issue is addressed with the
sensitiveflag and the plaintext mnemonic QR path is marked sensitive too. - CI is green.
No blocking findings from my review. Approving.
schnuartz-ai
pushed a commit
to Schnuartz/specter-diy
that referenced
this pull request
Aug 9, 2026
Extends the SeedQR transcription viewer (PR cryptoadvance#369) with a verification step: after copying a Standard/Compact SeedQR by hand, the user presses Done, scans the copy with the device's existing QR scanner, and gets a clear success/mismatch/unreadable result. - src/hosts/qr.py's scanner (GM65/M3Y over UART) only ever exposes the decoded payload, never a raw module grid or image -- there is no hardware/firmware path to a true module-by-module diff of the physical drawing today. Verification is therefore payload-only: a successful scan that decodes to the expected SeedQR is reported as "contents verified", explicitly not as "every module was drawn perfectly" (QR error correction could mask real drawing mistakes). A decoded-but-wrong payload is a hard failure; no decode is reported as unreadable. - src/seedqr.py: verify_payload/verify_scanned_payload implement that payload-only check. compare_matrices/mismatch_stats/matrix_diff/ diff_grid implement true module-level comparison and are fully unit tested, but nothing currently feeds them a physically-scanned matrix (that would require regenerating a matrix from the decoded payload, silently hiding whatever error correction papered over) -- they're ready for a future scanner that can expose raw module data. - src/gui/screens/seedqr.py: the overview screen gains a Done button (only when a scanner is wired in) that starts View/copy -> Done -> Scan copied QR -> result. Success is a clear green state (with the verified matrix rendered in green); mismatch and unreadable are clearly marked failure states with Redraw/Try again actions. The scanned bytes are only ever held long enough to classify them and are never logged, displayed as text, or persisted. - src/specter.py: Specter.scan_qr() reuses the existing QRHost/get_data scanning infrastructure (host selection, progress screen, cancel handling) instead of reimplementing scanning. - src/keystore/ram.py: threads the scan_qr callable from show_mnemonic() into show_seedqr(). - test/tests_native/test_seedqr.py: payload verification (Standard/ Compact match, mismatch, truncated, wrong-type-scan), matrix comparison (identical, missing/extra modules, multiple mismatches, counts/percentages, size mismatch, invalid input, 21x21/25x25/29x29), and security regressions (no unconditional prints in the new verification code paths, no scanned-data interpolation into labels). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WfSHkd5CGgU4SsR79yrpKg
schnuartz-ai
added a commit
to Schnuartz/specter-diy
that referenced
this pull request
Aug 9, 2026
Extends the SeedQR transcription viewer (PR cryptoadvance#369) with a verification step: after copying a Standard/Compact SeedQR by hand, the user presses Done, scans the copy with the device's existing QR scanner, and gets a clear success/mismatch/unreadable result. - src/hosts/qr.py's scanner (GM65/M3Y over UART) only ever exposes the decoded payload, never a raw module grid or image -- there is no hardware/firmware path to a true module-by-module diff of the physical drawing today. Verification is therefore payload-only: a successful scan that decodes to the expected SeedQR is reported as "contents verified", explicitly not as "every module was drawn perfectly" (QR error correction could mask real drawing mistakes). A decoded-but-wrong payload is a hard failure; no decode is reported as unreadable. - src/seedqr.py: verify_payload/verify_scanned_payload implement that payload-only check. compare_matrices/mismatch_stats/matrix_diff/ diff_grid implement true module-level comparison and are fully unit tested, but nothing currently feeds them a physically-scanned matrix (that would require regenerating a matrix from the decoded payload, silently hiding whatever error correction papered over) -- they're ready for a future scanner that can expose raw module data. - src/gui/screens/seedqr.py: the overview screen gains a Done button (only when a scanner is wired in) that starts View/copy -> Done -> Scan copied QR -> result. Success is a clear green state (with the verified matrix rendered in green); mismatch and unreadable are clearly marked failure states with Redraw/Try again actions. The scanned bytes are only ever held long enough to classify them and are never logged, displayed as text, or persisted. - src/specter.py: Specter.scan_qr() reuses the existing QRHost/get_data scanning infrastructure (host selection, progress screen, cancel handling) instead of reimplementing scanning. - src/keystore/ram.py: threads the scan_qr callable from show_mnemonic() into show_seedqr(). - test/tests_native/test_seedqr.py: payload verification (Standard/ Compact match, mismatch, truncated, wrong-type-scan), matrix comparison (identical, missing/extra modules, multiple mismatches, counts/percentages, size mismatch, invalid input, 21x21/25x25/29x29), and security regressions (no unconditional prints in the new verification code paths, no scanned-data interpolation into labels).
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
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.mdfor 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 screensrender the same canonical module matrix (generated once via
seedqr.generate_matrix()), so the overview and every zoomed sectioncan never disagree about module data.
src/keystore/ram.py: routes the Standard/Compact SeedQR menu choicesto
show_seedqr(); the Plaintext QR path is unchanged except it nowpasses
sensitive=True.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:
module matrix are never printed, logged, or persisted.
gui/components/qrcode.pyI found a pre-existingunconditional
print(text)that logged the full QR payload even onreal hardware, not just the simulator. Removed it, and added a
sensitiveflag (threaded throughadd_qrcode/QRAlert) so simulatordebug logging can be suppressed per-QR; it's now set for every
mnemonic-related QR, including the pre-existing Plaintext export path.
DELETE.Companion fix (separate repo)
While building the matrix-generation helper I found a pre-existing bug in
the
f469-discosubmodule'sqrcodeusermod:_qrcode_encode()decidedtext-vs-binary mode by scanning for an embedded
0x00byte, so abytespayload with no zero byte (common for Compact SeedQR's raw entropy) fell
through to
qrcodegen_encodeText(), which expects a NUL-terminated stringand 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-discosubmodule pointerin 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, sectiongeometry, touch mapping, navigation, and print/log security
regressions for the pure
seedqrmodule).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.
overview, tapping into first/middle/last/partial sections, all four
nav directions, back-to-overview section
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