capture effective seed in backup workflows
What changed, and why it matters
This commit changes how COLDCARD backup, clone, and Key Teleport features handle temporary seeds and BIP-39 passphrases. Previously, users could sometimes back up the main wallet even when a passphrase or temporary seed was active. Now the device always backs up the wallet currently in effect and warns the user first. This is a security-hardening change that reduces the risk of accidentally exposing or archiving the wrong secret, but it is not a fix for an active remote exploit.
Treat as a security-hardening improvement rather than an urgent vulnerability patch. Review the updated backup documentation and ensure users understand that backups now contain the effective wallet (including passphrase wallets and temporary seeds). No immediate incident response is indicated by the commit alone.
Security signals we found
Behavioral change: backup/clone/teleport now always captures the effective seed instead of allowing fallback to main seed
Warning added before secret export when a temporary seed or BIP-39 passphrase wallet is active
Removal of `bypass_tmp` parameter that previously enabled backing up the main seed while a temporary seed was active
Passphrase-on-ephemeral combination is now explicitly handled and warned about
Tests assert no main-seed backup option is offered and that exported content matches the effective wallet
Evidence from the diff
The patch removes the bypass_tmp path in render_backup_contents() and related backup workflows. make_complete_backup(), clone_write_data(), and Key Teleport’s share_full_backup() now consistently use SensitiveValues(enforce_delta=True) so the captured secret is the effective one (master seed + optional passphrase, or ephemeral/temporary seed, including passphrase-on-ephemeral cases). A new confirm_tmp_in_effect() helper warns the user before any secret leaves the device. Tests are updated to assert that the backup/clone/teleport content matches the effective wallet and that no main-seed option is offered when a temporary seed or passphrase wallet is active.
Changed components
shared/backups.pyshared/actions.pyshared/pincodes.pyshared/pwsave.pyshared/teleport.pydocs/backup-files.mdreleases/Next-ChangeLog.mdInspect captured patch +184 / −91
### docs/backup-files.md
@@ -40,15 +40,24 @@ called `ckcc-backup.txt`, but the filename is now picked randomly.
## BIP39 Passphrase
-If BIP39 passphrase is active the default behavior is to back-up
-main wallet - not BIP39 passphrase wallet. From version `5.2.0`
-users can choose to back-up also BIP39 passphrase wallet.
+If BIP39 passphrase is active, the passphrase wallet itself is backed-up,
+as extended private key created from seed words plus passphrase. Neither
+the seed words nor the passphrase are part of such backup.
+
+Older versions defaulted to backing-up main wallet, and offered a choice
+between the two. That option is gone, because passphrase can be applied
+on top of another temporary seed, in which case main wallet is not the
+wallet the passphrase was applied to.
## Ephemeral Seeds
If ephemeral seed is active the default behavior is to always
back-up ephemeral wallet instead of the main wallet.
+This applies to every path that captures the whole device: `Backup System`,
+`Clone Coldcard` and Key Teleport's `Full COLDCARD Backup`. All of them
+capture the seed in effect, and warn about it beforehand.
+
## Limitations
- The archive file names are not encrypted. You can see there is a single
### releases/Next-ChangeLog.md
@@ -24,6 +24,7 @@ This lists the new changes that have not yet been published in a normal release.
and are invalidated by any upload, newly staged transaction, or new session.
Thanks to [@drk1wi](https://github.com/drk1wi).
- Change: When a BIP-39 passphrase is active, View Seed Words now shows only the effective extended private key instead of the underlying seed words.
+- Change: Backup System, Clone Coldcard, and Key Teleport’s Full COLDCARD Backup now capture the wallet secret currently in effect, including temporary seeds and BIP-39 passphrase wallets, and warn before export.
# Mk Specific Changes
### shared/actions.py
@@ -543,9 +543,10 @@ async def convert_ephemeral_to_master(*a):
from stash import bip39_passphrase
words = settings.get("words", True)
- _type = 'BIP-39 passphrase' if bip39_passphrase else 'temporary seed'
+ master_words = settings.master_get("words", True)
+ _type = 'BIP-39 passphrase wallet' if bip39_passphrase else 'temporary seed'
msg = 'Convert currently used %s to master seed. Old master seed' % _type
- if words or bip39_passphrase:
+ if master_words:
msg += ' words themselves are erased forever, '
else:
msg += ' is erased forever, '
@@ -712,7 +713,7 @@ async def export_seedqr(*a):
# Note: cannot reach this menu item if no words. If they are tmp, that's cool.
- with stash.SensitiveValues(bypass_tmp=False, enforce_delta=True) as sv:
+ with stash.SensitiveValues(enforce_delta=True) as sv:
if sv.mode != 'words':
raise ValueError(sv.mode)
### shared/backups.py
@@ -22,12 +22,11 @@
# - limited by size of LFS area of flash, since all settings are held there
MAX_BACKUP_FILE_SIZE = const(128*1024) # bytes
-def render_backup_contents(bypass_tmp=False):
+def render_backup_contents():
# simple text format:
# key = value
# or #comments
# but value is JSON
- current_tmp = None
rv = StringIO()
def COMMENT(val=None):
@@ -45,7 +44,7 @@ def ADD(key, val):
COMMENT('Private key details: ' + chain.name)
- with stash.SensitiveValues(bypass_tmp=bypass_tmp, enforce_delta=True) as sv:
+ with stash.SensitiveValues(enforce_delta=True) as sv:
if sv.mode == 'words':
ADD('mnemonic', bip39.b2a_words(sv.raw))
@@ -72,20 +71,6 @@ def ADD(key, val):
for k,v in pairs:
ADD(k, v)
- if bypass_tmp and pa.tmp_value:
- current_tmp = pa.tmp_value[:]
- pa.tmp_value = None
- # we also need correct settings from main seed
- if sv.mode == 'words':
- nv = stash.SecretStash.encode(seed_phrase=sv.raw)
- else:
- assert sv.mode == "xprv"
- nv = stash.SecretStash.encode(xprv=sv.node)
-
- settings.set_key(nv)
- settings.load()
- stash.blank_object(nv)
-
COMMENT('Firmware version (informational)')
date, vers, timestamp = version.get_mpy_version()[0:3]
ADD('fw_date', date)
@@ -119,13 +104,6 @@ def ADD(key, val):
rv.write('\n# EOF\n')
- if bypass_tmp and current_tmp:
- # go back to tmp secret and its settings
- stash.SensitiveValues.clear_cache()
- pa.tmp_value = current_tmp
- settings.set_key()
- settings.load()
-
return rv.getvalue()
def extract_raw_secret(vals):
@@ -404,27 +382,21 @@ def encrypt_7z_data(password, body, ext="txt"):
return zz, hdr, footer
-async def make_complete_backup(fname_pattern='backup.7z', write_sflash=False):
- from stash import bip39_passphrase
+async def confirm_tmp_in_effect(what):
+ # We always capture the seed in effect, never the master seed underneath it.
+ # Passphrase can be applied on top of another temporary seed, so we cannot
+ # offer master seed as an alternative - be clear about whose secret leaves.
+ if not pa.tmp_value:
+ return True
+
+ name = "BIP-39 passphrase" if stash.bip39_passphrase else "temporary seed"
+ return await ux_confirm("A %s is in effect, so %s will be of that seed." % (name, what))
+async def make_complete_backup(fname_pattern='backup.7z', write_sflash=False):
pwd = None
- bypass_tmp = False
-
- if bip39_passphrase and pa.tmp_value:
- # this is a BIP39 password ephemeral wallet
- msg = ("BIP39 passphrase is in effect. Backup ignores passphrases "
- "and produces backup of main seed. Press %s to back-up main wallet,"
- " press (2) to back-up BIP39 passphrase wallet "
- "(extended private key created via seed + pass)" % OK)
- ch = await ux_show_story(msg, escape="2")
- if ch == "x": return
- if ch == "y":
- bypass_tmp = True
-
- elif pa.tmp_value:
- if not await ux_confirm("A temporary seed is in effect, "
- "so backup will be of that seed."):
- return
+
+ if not await confirm_tmp_in_effect("backup"):
+ return
# first check if bkpw already defined on tmp seed settings
stored_pwd, skip_quiz = await bkpw_workflow()
@@ -450,18 +422,17 @@ async def make_complete_backup(fname_pattern='backup.7z', write_sflash=False):
settings.set('bkpw', pwd) # if on tmp save to tmp, do not update master
settings.save()
- return await write_complete_backup(pwd, fname_pattern, write_sflash=write_sflash,
- bypass_tmp=bypass_tmp)
+ return await write_complete_backup(pwd, fname_pattern, write_sflash=write_sflash)
async def write_complete_backup(pwd, fname_pattern, write_sflash=False,
- allow_copies=True, bypass_tmp=False):
+ allow_copies=True):
# Just do the writing
from glob import dis
from files import CardSlot
# Show progress:
dis.fullscreen('Encrypting...' if pwd else 'Generating...')
- body = render_backup_contents(bypass_tmp=bypass_tmp).encode()
+ body = render_backup_contents().encode()
gc.collect()
@@ -858,14 +829,17 @@ async def clone_write_data(*a):
await ux_show_story("Start this process on the other Coldcard, which will write a file onto MicroSD card as the first step.\n\nInsert that card and try again here.")
return
+ if not await confirm_tmp_in_effect("clone"):
+ return
+
# pick our own temp keys for this encryption
pair = ngu.secp256k1.keypair()
my_pubkey = pair.pubkey().to_bytes(False)
session_key = pair.ecdh_multiply(his_pubkey)
fname = b2a_hex(my_pubkey).decode() + '-ccbk.7z'
- await write_complete_backup(b2a_hex(session_key).decode(), fname, allow_copies=False, bypass_tmp=True)
+ await write_complete_backup(b2a_hex(session_key).decode(), fname, allow_copies=False)
await ux_show_story("Done.\n\nTake this MicroSD card back to other Coldcard and continue from there.")
### shared/pincodes.py
@@ -405,7 +405,6 @@ def new_main_secret(self, raw_secret=None, chain=None, bip39pw='', blank=False,
from glob import settings, dis
stash.SensitiveValues.clear_cache()
- bypass_tmp = False
stash.bip39_passphrase = bool(bip39pw)
# capture values we have already
@@ -416,7 +415,6 @@ def new_main_secret(self, raw_secret=None, chain=None, bip39pw='', blank=False,
if raw_secret is None:
assert pa.tmp_value
- bypass_tmp = True
pa.tmp_value = None
if blank:
# wipe current ephemeral secret settings slot
@@ -434,7 +432,7 @@ def new_main_secret(self, raw_secret=None, chain=None, bip39pw='', blank=False,
# Recalculate xfp/xpub values (depends both on secret and chain)
try:
- with stash.SensitiveValues(raw_secret, bypass_tmp=bypass_tmp) as sv:
+ with stash.SensitiveValues(raw_secret) as sv:
if chain is not None:
sv.chain = chain
### shared/pwsave.py
@@ -112,7 +112,7 @@ async def apply(menu, idx, item):
from seed import set_bip39_passphrase
from pincodes import pa
- bypass_tmp = True
+ bypass_tmp = bool(pa.tmp_value)
pw, expect_xfp = item.arg
if pa.tmp_value and settings.get("words", True):
xfp = settings.get("xfp", 0)
### shared/teleport.py
@@ -3,7 +3,7 @@
# teleport.py - Magically transport extremely sensitive data between the
# secure environment of two Q's.
#
-import ngu, aes256ctr, bip39, json, ndef, chains
+import ngu, aes256ctr, bip39, json, ndef, chains, stash
from utils import xfp2str, deserialize_secret
from ubinascii import unhexlify as a2b_hex
from ubinascii import hexlify as b2a_hex
@@ -16,7 +16,7 @@
from notes import NoteContentBase
from sffile import SFFile
from multisig import MultisigWallet
-from stash import SensitiveValues, SecretStash, blank_object, bip39_passphrase
+from stash import SensitiveValues, SecretStash, blank_object
# One page github-hosted static website that shows QR based on URL contents pushed by NFC
KT_DOMAIN = 'keyteleport.com'
@@ -538,7 +538,7 @@ def __init__(self, rx_pubkey):
# tmp seed, or maybe bip39 is in effect
# - share the current master secret, not the real master
msg = 'Temp Secret (words)' if word_based_seed() else (
- 'XPRV from Words+Passphrase' if bip39_passphrase else 'Temp XPRV Secret')
+ 'XPRV from Seed+Passphrase' if stash.bip39_passphrase else 'Temp XPRV Secret')
elif has_se_secrets():
# sharing real master secret
msg = 'Master Seed Words' if word_based_seed() else 'Master XPRV'
@@ -590,20 +590,29 @@ async def picked_note(self, _, _2, item):
async def share_full_backup(self, *a):
# context, and warn them
- ch = await ux_show_story("Sending complete backup, including master secret, "
- "seed vault (if any), multisig wallets, notes/passwords, and all settings! "
- "The receiving "
- "COLDCARD must already have the master seed wiped to be able to install "
- "everything, otherwise only master secret and multisig are saved into a tmp seed. "
- "OK to proceed?")
+ from pincodes import pa
+
+ if pa.tmp_value:
+ if stash.bip39_passphrase:
+ what = "BIP-39 Passphrase wallet"
+ else:
+ what = "current active temporary secret"
+ else:
+ what = "master secret, seed vault (if any)"
+
+ ch = await ux_show_story("Sending complete backup, including %s, multisig wallets,"
+ " notes/passwords, and all settings! The receiving COLDCARD"
+ " must already have the master seed wiped to be able to install"
+ " everything, otherwise only the transferred secret and multisig"
+ " wallets are saved into a temporary seed. OK to proceed?" % what)
if ch != 'y': return
from backups import render_backup_contents
dis.fullscreen("Buiding Backup...")
# renders a text file, with rather a lot of comments; strip them
- bkup = render_backup_contents(bypass_tmp=True)
+ bkup = render_backup_contents()
out = []
for ln in bkup.split('\n'):
if not ln: continue
@@ -618,7 +627,7 @@ async def share_master_secret(self, _, _2, item):
dis.fullscreen("Wait...")
- with SensitiveValues(bypass_tmp=False, enforce_delta=True) as sv:
+ with SensitiveValues(enforce_delta=True) as sv:
raw = bytearray(sv.secret)
xfp = xfp2str(sv.get_xfp())
### testing/test_backup.py
@@ -108,13 +108,9 @@ def doit(reuse_pw=None, save_pw=False, st=None, ct=False):
title, body = cap_story()
if st:
- if st == "b39pass":
- assert "BIP39 passphrase is in effect" in body
- assert "ignores passphrases and produces backup of main seed" in body
- assert "(2) to back-up BIP39 passphrase wallet" in body
- if st == "eph":
- assert "A temporary seed is in effect" in body
- assert "so backup will be of that seed" in body
+ name = "BIP-39 passphrase" if st == "b39pass" else "temporary seed"
+ assert f"A {name} is in effect" in body
+ assert "so backup will be of that seed" in body
press_select()
time.sleep(.1)
@@ -267,10 +263,8 @@ def test_make_backup(multisig, goto_home, pick_menu_item, cap_story, need_keypre
title, body = cap_story()
if st == "b39pass" and multisig:
- # correct settings switch back?
# multisig is only in main wallet
# must not be copied from main to b39pass
- # must not be available after backup done
assert not get_setting('multisig', None)
if notes:
@@ -308,7 +302,7 @@ def test_make_backup(multisig, goto_home, pick_menu_item, cap_story, need_keypre
verify_backup_file(fn)
decrypted = check_and_decrypt_backup(fn, words)
avail_settings = []
- if seedvault and (st in [None, "b39pass"]):
+ if seedvault and (st is None):
assert "seedvault" in decrypted
assert "seeds" in decrypted
avail_settings.append("seeds")
@@ -322,7 +316,8 @@ def test_make_backup(multisig, goto_home, pick_menu_item, cap_story, need_keypre
time.sleep(.01)
# test verify on device (CRC check)
- if multisig:
+ if multisig and (st != "b39pass"):
+ # multisig is in main wallet only, but backup is of the b39pass wallet
avail_settings.append("multisig")
restore_backup_cs(files[0], words, avail_settings=avail_settings,
@@ -413,7 +408,7 @@ def test_backup_ephemeral_wallet(stype, pick_menu_item, press_select, goto_home,
@pytest.mark.parametrize('seedvault', [False, True])
@pytest.mark.parametrize("passphrase", ["@coinkite rulez!!", "!@#!@", "AAAAAAAAAAA"])
-def test_backup_bip39_wallet(passphrase, set_bip39_pw, pick_menu_item, need_keypress,
+def test_backup_bip39_wallet(passphrase, set_bip39_pw, pick_menu_item, press_select,
goto_home, cap_story, pass_word_quiz, get_setting,
verify_backup_file, microsd_path, check_and_decrypt_backup,
sim_execfile, unit_test, word_menu_entry, cap_menu,
@@ -431,10 +426,9 @@ def test_backup_bip39_wallet(passphrase, set_bip39_pw, pick_menu_item, need_keyp
pick_menu_item("Backup System")
time.sleep(.1)
title, story = cap_story()
- assert "BIP39 passphrase is in effect" in story
- assert "ignores passphrases and produces backup of main seed" in story
- assert "(2) to back-up BIP39 passphrase wallet" in story
- need_keypress("2")
+ assert "A BIP-39 passphrase is in effect" in story
+ assert "so backup will be of that seed" in story
+ press_select()
time.sleep(.1)
title, story = cap_story()
if "Use same backup file password as last time?" in story:
@@ -465,7 +459,7 @@ def test_backup_bip39_wallet(passphrase, set_bip39_pw, pick_menu_item, need_keyp
assert "seeds" not in contents
assert simulator_fixed_words not in contents
assert simulator_fixed_tprv not in contents
- assert target == contents
+ assert sorted(target.splitlines()) == sorted(contents.splitlines())
seed = Mnemonic.to_seed(simulator_fixed_words, passphrase=passphrase)
expect = BIP32Node.from_master_secret(seed, netcode="XTN")
esk = expect.hwif(as_private=True)
@@ -594,6 +588,57 @@ def test_clone_start(reset_seed_words, pick_menu_item, cap_story, goto_home, src
# TODO check file made is a good backup, with correct password
+@pytest.mark.parametrize("b39pass", [False, True])
+def test_clone_start_tmp_seed(b39pass, reset_seed_words, pick_menu_item, cap_story, goto_home,
+ src_root_dir, sim_root_dir, generate_ephemeral_words, set_bip39_pw,
+ press_cancel, press_select, settings_set):
+ # clone is of the seed in effect, and says so before writing anything
+ sd_dir = f"{sim_root_dir}/MicroSD"
+ fname = "ccbk-start.json"
+ reset_seed_words()
+ settings_set("seedvault", 0)
+ sec = generate_ephemeral_words(24, from_main=True, seed_vault=False)
+ if b39pass:
+ # passphrase on top of the temporary seed - master seed is not its parent
+ set_bip39_pw("coinkite", reset=False, on_tmp=True)
+
+ goto_home()
+ shutil.copy(f"{src_root_dir}/testing/data/{fname}", sd_dir)
+ before = {i for i in os.listdir(sd_dir) if i.endswith(".7z")}
+ pick_menu_item("Advanced/Tools")
+ pick_menu_item("Backup")
+ pick_menu_item("Clone Coldcard")
+ time.sleep(.2)
+ title, story = cap_story()
+ name = "BIP-39 passphrase" if b39pass else "temporary seed"
+ assert f"A {name} is in effect" in story
+ assert "so clone will be of that seed" in story
+ assert "main seed" not in story
+
+ press_cancel()
+ time.sleep(.2)
+ # nothing written when refused (stale clone files are purged before this point)
+ after = {i for i in os.listdir(sd_dir) if i.endswith(".7z")}
+ assert not (after - before)
+
+ # accept, and the file is written from the seed in effect
+ pick_menu_item("Clone Coldcard")
+ time.sleep(.2)
+ press_select()
+ time.sleep(1)
+ title, story = cap_story()
+ assert "Done" in story
+ assert "Take this MicroSD card back to other Coldcard" in story
+ after = {i for i in os.listdir(sd_dir) if i.endswith(".7z")}
+ assert len(after - before) == 1
+
+ goto_home()
+ for fn in (after - before):
+ os.remove(f"{sd_dir}/{fn}")
+ os.remove(f"{sd_dir}/{fname}")
+ reset_seed_words()
+
+
def test_bkpw_override(reset_seed_words, override_bkpw, goto_home, pick_menu_item,
cap_story, press_select, garbage_collector, microsd_path,
restore_backup_cs, is_q1):
### testing/test_bip39pw.py
@@ -315,7 +315,7 @@ def test_lockdown_ux(stype, pick_menu_item, set_bip39_pw, goto_home, is_q1,
assert 'Make sure all duress wallets associated with previous seed are deleted' in story
assert 'carried forward without being properly generated from new master seed.' in story
if stype == "bip39pw":
- assert "Convert currently used BIP-39 passphrase to master seed" in story
+ assert "Convert currently used BIP-39 passphrase wallet to master seed" in story
assert "but the passphrase itself is erased" in story
assert "Press (4) to prove you read to the end of this message and accept all consequences" in story
@@ -334,7 +334,7 @@ def test_bip39pass_on_ephemeral_seed(generate_ephemeral_words, import_ephemeral_
reset_seed_words, goto_eph_seed_menu, stype,
enter_complex, cap_story, cap_menu,
settings_set, seed_vault, press_select,
- go_to_passphrase):
+ go_to_passphrase, press_cancel):
passphrase = "@coinkite rulez!!"
reset_seed_words()
settings_set("seedvault", 1)
@@ -409,6 +409,18 @@ def test_bip39pass_on_ephemeral_seed(generate_ephemeral_words, import_ephemeral_
press_select()
goto_home()
+ # backup must be of the wallet in effect, no offer to back-up main seed
+ pick_menu_item("Advanced/Tools")
+ pick_menu_item("Backup")
+ pick_menu_item("Backup System")
+ time.sleep(.1)
+ _, story = cap_story()
+ assert "A BIP-39 passphrase is in effect" in story
+ assert "so backup will be of that seed" in story
+ assert "main seed" not in story
+ press_cancel()
+ goto_home()
+
if seed_vault:
# check correct meta in seed vault
pick_menu_item("Seed Vault")
### testing/test_ccc.py
@@ -1417,7 +1417,7 @@ def test_ccc_challenge_qr_bad_checksum_crash(setup_ccc, goto_ccc_menu, cap_story
scan_a_qr(bad_seed_qr)
time.sleep(.5)
press_select()
-
+ time.sleep(.1)
title, story = cap_story()
assert 'Sorry, those words are incorrect' in story
### testing/test_drv_entro.py
@@ -43,7 +43,8 @@ def doit(mode, index, expect=None, entropy=None, sim_sec=None, chain="BTC"):
press_select()
time.sleep(0.1)
title, story = cap_story()
- if "You have a temporary seed active - deriving from temporary" in story:
+ if (("You have a temporary seed active - deriving from temporary" in story)
+ or ("it will be wrapped into the new secret" in story)):
press_select()
time.sleep(0.1)
@@ -121,6 +122,24 @@ def doit(mode, index, expect=None, entropy=None, sim_sec=None, chain="BTC"):
return doit
+def test_bip39_passphrase_warning(set_bip39_pw, goto_home, pick_menu_item, cap_story,
+ press_select, press_cancel, reset_seed_words, is_q1):
+ set_bip39_pw("bip85-test")
+
+ goto_home()
+ pick_menu_item('Advanced/Tools')
+ pick_menu_item('Derive Seed B85' if not is_q1 else 'Derive Seeds (BIP-85)')
+ press_select()
+ time.sleep(.1)
+
+ _, story = cap_story()
+ assert "You have a BIP-39 passphrase set right now" in story
+ assert "it will be wrapped into the new secret" in story
+
+ press_cancel()
+ reset_seed_words()
+
+
@pytest.fixture
def activate_bip85_ephemeral(need_keypress, cap_story, sim_exec, reset_seed_words,
confirm_tmp_seed):
### testing/test_teleport.py
@@ -764,7 +764,10 @@ def test_send_backup(testcase, rx_start, tx_start, cap_menu, enter_complex, pick
title, body = cap_story()
- assert 'Sending complete backup' in body
+ assert ('Sending complete backup, including master secret, seed vault (if any), '
+ 'multisig wallets' in body)
+ assert 'otherwise only the transferred secret and multisig' in body
+ assert ',,' not in body
press_select()
@@ -805,6 +808,28 @@ def test_send_backup(testcase, rx_start, tx_start, cap_menu, enter_complex, pick
settings_set('notes', [])
+def test_send_backup_tmp_story(rx_start, tx_start, pick_menu_item, cap_story, press_cancel,
+ generate_ephemeral_words, set_bip39_pw, restore_main_seed):
+ def check_story(expected):
+ code, rx_pubkey = rx_start()
+ tx_start(rx_pubkey, code)
+ pick_menu_item('Full COLDCARD Backup')
+
+ _, body = cap_story()
+ assert ('Sending complete backup, including %s, multisig wallets' % expected) in body
+ assert 'otherwise only the transferred secret and multisig' in body
+ assert ',,' not in body
+ press_cancel()
+
+ generate_ephemeral_words(num_words=12, from_main=True, seed_vault=False)
+ check_story('current active temporary secret')
+ restore_main_seed()
+
+ set_bip39_pw('teleport-test')
+ check_story('BIP-39 Passphrase wallet')
+ restore_main_seed()
+
+
def test_teleport_backup_invalid_raw_secret(grab_payload, rx_complete, goto_home,
pick_menu_item, cap_story, is_q1):
# yikes. Must instead show a clean FAILED story.Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.