diff --git a/docs/ephemeral.md b/docs/ephemeral.md index 8de5046d..939ea30b 100644 --- a/docs/ephemeral.md +++ b/docs/ephemeral.md @@ -1,8 +1,9 @@ # Ephemeral Seeds -Ephemeral seed is temporary secret stored only in Coldcard volatile -memory (RAM). It only survives single boot, meaning after Coldcard -restart it is gone. Ephemeral seeds *completely* defeats the design +Ephemeral seed is temporary secret mostly stored only in Coldcard volatile +memory (RAM). Ephemeral seed can also be stored in `Seed Vault` (5.2.0+). +It only survives single boot, meaning after Coldcard +restart it is gone. Ephemeral seeds *completely* defeats the design of Coldcard's security model, based on secure elements. Make sure you know what you're doing! diff --git a/shared/actions.py b/shared/actions.py index ab44efbc..975493d2 100644 --- a/shared/actions.py +++ b/shared/actions.py @@ -978,7 +978,7 @@ def make_top_menu(): if active_xfp: ui_xfp = "[" + xfp2str(active_xfp) + "]" _cls.insert(0, MenuItem(ui_xfp, f=ready2sign)) - _cls.append(MenuItem("Restore Seed", f=restore_main_secret)) + _cls.append(MenuItem("Restore Master", f=restore_main_secret)) else: _cls = EmptyWallet diff --git a/shared/seed.py b/shared/seed.py index 2adc055f..f1255ebb 100644 --- a/shared/seed.py +++ b/shared/seed.py @@ -480,7 +480,7 @@ async def set_ephemeral_seed(encoded, chain=None, summarize_ux=True, bip39pw='', return if summarize_ux: - await ux_show_story(title=xfp, msg="New ephemeral master key in effect until next power down.") + await ux_show_story(title=xfp, msg="New ephemeral master key is in effect now.") return applied @@ -914,6 +914,7 @@ class SeedVaultMenu(MenuSystem): cur_xfp = xfp2str(settings.get("xfp", 0)) if not seeds: rv.append(MenuItem('(none saved yet)')) + rv.append(MenuItem("Ephemeral Seed", menu=make_ephemeral_seed_menu)) else: for i, (xfp_str, encoded, name, meta) in enumerate(seeds): current_active = cur_xfp == xfp_str @@ -938,6 +939,10 @@ class SeedVaultMenu(MenuSystem): rv.append(item) + if pa.tmp_value: + from actions import restore_main_secret + rv.append(MenuItem("Restore Master", f=restore_main_secret)) + return rv def update_contents(self): @@ -995,12 +1000,15 @@ class EphemeralSeedMenu(MenuSystem): async def make_ephemeral_seed_menu(*a): - if not pa.tmp_value: + if (not pa.tmp_value) or (not settings.get("seedvault", False)): # force a warning on them, unless they are already doing it. ch = await ux_show_story( - "Ephemeral seed is a temporary secret stored solely in device RAM, persisted for only a single boot. " - "This defeats all of the benefits of Coldcard's secure element design." - "\n\nPress (4) to prove you read to the end of this message and accept all consequences.", + "Ephemeral seed is a temporary secret mostly stored " + "only in device RAM, but can be saved to Seed Vault. " + "Ephemeral seed is persisted for only a single boot. " + "Above defeats all of the benefits of Coldcard's secure " + "element design.\n\nPress (4) to prove you read to the end" + " of this message and accept all consequences.", title="WARNING", escape="4" ) diff --git a/testing/test_backup.py b/testing/test_backup.py index f13747e7..95744139 100644 --- a/testing/test_backup.py +++ b/testing/test_backup.py @@ -34,7 +34,7 @@ def test_make_backup(multisig, goto_home, pick_menu_item, cap_story, need_keypre elif st == "eph": eph_seed = generate_ephemeral_words(num_words=24, dice=False, from_main=True) _, story = cap_story() - assert "New ephemeral master key in effect" in story + assert "New ephemeral master key is in effect now." in story need_keypress("y") if multisig: diff --git a/testing/test_drv_entro.py b/testing/test_drv_entro.py index ebcd8cf6..bcf81112 100644 --- a/testing/test_drv_entro.py +++ b/testing/test_drv_entro.py @@ -139,7 +139,7 @@ def activate_bip85_ephemeral(need_keypress, cap_story, sim_exec, reset_seed_word time.sleep(0.1) title, story = cap_story() - assert 'master key in effect' in story + assert 'ephemeral master key is in effect now' in story encoded = sim_exec('from pincodes import pa; RV.write(repr(pa.fetch()))') print(encoded) diff --git a/testing/test_ephemeral.py b/testing/test_ephemeral.py index 2410bcef..cd020a8a 100644 --- a/testing/test_ephemeral.py +++ b/testing/test_ephemeral.py @@ -134,9 +134,9 @@ def restore_main_seed(goto_home, pick_menu_item, cap_story, cap_menu, prev = len(settings_slots()) goto_home() menu = cap_menu() - assert menu[-1] == "Restore Seed" + assert menu[-1] == "Restore Master" assert (menu[0][0] == "[") and (menu[0][-1] == "]") - pick_menu_item("Restore Seed") + pick_menu_item("Restore Master") time.sleep(.1) title, story = cap_story() ch = "y" @@ -156,7 +156,7 @@ def restore_main_seed(goto_home, pick_menu_item, cap_story, cap_menu, time.sleep(.3) menu = cap_menu() - assert menu[-1] != "Restore Seed" + assert menu[-1] != "Restore Master" assert (menu[0][0] != "[") and (menu[0][-1] != "]") after = len(settings_slots()) @@ -180,14 +180,14 @@ def verify_ephemeral_secret_ui(cap_story, need_keypress, cap_menu, dev, fake_txn in_effect_xfp = title[1:-1] if expected_xfp is not None: assert in_effect_xfp == expected_xfp - assert "key in effect until next power down." in story + assert 'ephemeral master key is in effect now' in story in story need_keypress("y") # just confirm new master key message menu = cap_menu() assert expected_xfp in menu[0] if expected_xfp else True assert menu[1] == "Ready To Sign" # returned to main menu - assert menu[-1] == "Restore Seed" # restore main from ephemeral + assert menu[-1] == "Restore Master" # restore main from ephemeral if seed_vault: pick_menu_item("Seed Vault") @@ -199,6 +199,7 @@ def verify_ephemeral_secret_ui(cap_story, need_keypress, cap_menu, dev, fake_txn m = cap_menu() assert "Delete" in m assert "Rename" in m + assert "Restore Master" in m assert len(m) == 4 # xfp is top item (works same as "Use this seed") if "Use This Seed" in m: pick_menu_item("Use This Seed") @@ -229,6 +230,7 @@ def verify_ephemeral_secret_ui(cap_story, need_keypress, cap_menu, dev, fake_txn time.sleep(.1) m = cap_menu() assert item not in m + assert "Restore Master" not in m else: # Seed Vault disabled m = cap_menu() @@ -709,7 +711,7 @@ def test_activate_current_tmp_secret(reset_seed_words, goto_eph_seed_menu, need_keypress("y") time.sleep(0.2) title, story = cap_story() - assert "key in effect until next power down." in story + assert 'ephemeral master key is in effect now' in story in_effect_xfp = title[1:-1] need_keypress("y") goto_eph_seed_menu() @@ -808,7 +810,7 @@ def test_seed_vault_menus(dev, data, settings_set, settings_get, pick_menu_item, time.sleep(.1) title, story = cap_story() assert xfp in title - assert "key in effect until next power down." in story + assert 'ephemeral master key is in effect now' in story need_keypress("y") active = get_seed_value_ux() if mnemonic: diff --git a/testing/test_se2.py b/testing/test_se2.py index 99e43415..f2349844 100644 --- a/testing/test_se2.py +++ b/testing/test_se2.py @@ -557,7 +557,7 @@ def test_ux_duress_choices(with_wipe, subchoice, expect, xflags, xargs, time.sleep(.1) if stop_after_activated: return _, story = cap_story() - assert 'New ephemeral master key in effect' in story + assert 'ephemeral master key is in effect now' in story xp = repl.eval("settings.get('xpub')") assert xp == wallet.hwif(as_private=False) @@ -845,7 +845,7 @@ def build_duress_wallets(request, seed_vault=False): need_keypress('y') time.sleep(0.1) _, story = cap_story() - assert 'master key in effect until next power down' in story + assert 'ephemeral master key is in effect now' in story need_keypress("y") # re-login to reset to normal seed