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')