From dcf800a5c5fdedddf3d05593df31c1cca2ebd0e6 Mon Sep 17 00:00:00 2001 From: Dmitry Monakhov Date: Fri, 8 May 2026 11:08:29 +0200 Subject: [PATCH] Recognize BBQr Binary 7z blob as paper-backup; route to restore_complete Adds 24 lines (10 in decoders.py + 14 in ux_q1.py). No bootloader or crypto change; reuses existing PSRAM/SFFile USB-restore path. A full .7z backup is a master-seed artifact, so the restore target is chosen from device state (master when blank, temporary otherwise) via not pa.is_secret_blank() - same rule as restore_backup_dev() - rather than the menu-level tmp flag, which is always True on a running device and would dead-end at "Cannot use master seed as temporary". Pairs with a standalone offline CLI that produces printable BBQr sheets from .7z backups; the device only needs this import-side recognition. --- shared/decoders.py | 10 ++++++++++ shared/ux_q1.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/shared/decoders.py b/shared/decoders.py index d0439d91..b94975f7 100644 --- a/shared/decoders.py +++ b/shared/decoders.py @@ -149,6 +149,16 @@ def decode_qr_result(got, expect_secret=False, expect_text=False, expect_bbqr=Fa raise QRDecodeExplained("Truncated KT RX") return 'teleport', (ty, got) + + elif ty == 'B': + # Binary BBQr: today the only meaningful payload is a Coldcard + # paper-backup, identified by the 7z magic header. Reassembled + # blob lives at PSRAM offset 0 (BBQrPsramStorage). + if final_size >= 6 and bytes(got[:6]) == b'7z\xbc\xaf\x27\x1c': + return 'paper_backup', (final_size,) + msg = TYPE_LABELS.get(ty, 'Unknown FileType') + raise QRDecodeExplained("Sorry, %s not useful." % msg) + else: msg = TYPE_LABELS.get(ty, 'Unknown FileType') raise QRDecodeExplained("Sorry, %s not useful." % msg) diff --git a/shared/ux_q1.py b/shared/ux_q1.py index 579b941a..8352aa80 100644 --- a/shared/ux_q1.py +++ b/shared/ux_q1.py @@ -1025,6 +1025,20 @@ async def scan_anything(self, expect_secret=False, tmp=False): from teleport import kt_incoming await kt_incoming(*vals) + elif what == 'paper_backup': + # Encrypted Coldcard backup delivered as a printed sheet of BBQr + # codes. The reassembled .7z blob is already in PSRAM at offset 0 + # (BBQrPsramStorage default); vals[0] is its byte length. Reuse + # the existing USB-style restore_complete() path which expects + # an int length and reads from PSRAM via SFFile. + # A full backup is a master-seed artifact: restore as master on a + # blank device (the real recovery case), else as a temporary seed. + # Mirrors restore_backup_dev(); the menu-level `tmp` is ignored + # because it is always True on a running (seeded) device, which + # would dead-end at "Cannot use master seed as temporary". + from backups import restore_complete + await restore_complete(vals[0], temporary=not pa.is_secret_blank()) + else: await ux_show_story(what, title='Unhandled')