Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions shared/decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions shared/ux_q1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down