This commit is contained in:
Peter D. Gray 2025-09-09 12:18:15 -04:00 committed by doc-hex
parent 8d84979ddf
commit 76cc136a9e
6 changed files with 122 additions and 24 deletions

View File

@ -991,14 +991,13 @@ def make_top_menu():
elif pa.is_blank():
# let them play a little before picking a PIN first time
m = MenuSystem(VirginSystem, should_cont=lambda: pa.is_blank())
elif pa.hobbled_mode:
# let them do a few things, but not all the things.
m = MenuSystem(HobbledTopMenu)
else:
assert pa.is_successful(), "nonblank but wrong pin"
if pa.has_secrets():
_cls = NormalSystem[:]
# let them do a few things, but not all the things, when "hobbled"
_cls = HobbledTopMenu[:] if pa.hobbled_mode else NormalSystem[:]
if pa.tmp_value or settings.get("hmx", False):
active_xfp = settings.get("xfp", 0)
sl, sr = ("[", "]") if pa.tmp_value else ("<", ">")

View File

@ -1194,7 +1194,7 @@ class SSSPConfigMenu(MenuSystem):
menu=lambda *a: SpendingPolicyMenu.be_a_submenu(SSSPFeature.get_policy())),
SSSPCheckedMenuItem('Word Check', 'words', 'To change Spending Policy, in addition to special PIN, you must provide the first and last seed words.'),
SSSPCheckedMenuItem('Allow Notes', 'notes', 'Allow (read-only) access to secure notes and passwords? Otherwise, they are inaccessible.'),
SSSPCheckedMenuItem('Related Keys', 'okeys', 'Allow access to BIP-39 passphrase wallets based on master seed, or Seed Vault (if any). Same spending Policy applies to all.'),
SSSPCheckedMenuItem('Related Keys', 'okeys', 'Allow access to BIP-39 passphrase wallets based on master seed, and Seed Vault (read-only). Single Spending Policy applies to all.'),
#MenuItem('Test Word Challenge', f=sssp_word_challenge), # XXX test only?
]

View File

@ -519,6 +519,8 @@ HobbledAdvancedMenu = [
MenuItem('Export Wallet', menu=WalletExportMenu, shortcut='x'), # also inside FileMgmt
MenuItem('Teleport Multisig PSBT', predicate=qr_and_ms, f=kt_send_file_psbt),
MenuItem("View Identity", f=view_ident),
MenuItem("Temporary Seed", menu=make_ephemeral_seed_menu,
predicate=lambda: sssp_spending_policy('okeys')),
MenuItem('Paper Wallets', f=make_paper_wallet),
MenuItem('NFC Tools', predicate=nfc_enabled, menu=HobbledNFCToolsMenu, shortcut=KEY_NFC),
MenuItem("Destroy Seed", f=clear_seed),

View File

@ -493,6 +493,10 @@ async def add_seed_to_vault(encoded, origin=None, label=None):
if in_seed_vault(encoded):
return
# stay "read only" in hobbled mode
if pa.hobbled_mode:
return
main_xfp = settings.master_get("xfp", 0)
# parse encoded
@ -531,7 +535,7 @@ async def add_seed_to_vault(encoded, origin=None, label=None):
async def set_ephemeral_seed(encoded, chain=None, summarize_ux=True, bip39pw='',
is_restore=False, origin=None, label=None):
# Capture tmp seed into vault, if so enabled, and regardless apply it as new tmp.
if not is_restore:
if not is_restore and not pa.hobbled_mode:
await add_seed_to_vault(encoded, origin=origin, label=label)
dis.fullscreen("Wait...")
@ -1034,11 +1038,11 @@ class SeedVaultMenu(MenuSystem):
seeds = list(seed_vault_iter())
if not seeds:
assert not pa.hobbled_mode
rv.append(MenuItem('(none saved yet)'))
if pa.tmp_value:
rv.append(add_current_tmp)
rv.append(MenuItem("Temporary Seed", menu=make_ephemeral_seed_menu))
if not pa.hobbled_mode:
if pa.tmp_value:
rv.append(add_current_tmp)
rv.append(MenuItem("Temporary Seed", menu=make_ephemeral_seed_menu))
else:
wipe_if_deltamode()
@ -1162,7 +1166,7 @@ class EphemeralSeedMenu(MenuSystem):
]
rv = [
MenuItem("Generate Words", menu=gen_ephemeral_menu),
MenuItem("Generate Words", menu=gen_ephemeral_menu, predicate=not pa.hobbled_mode),
MenuItem('Import from QR Scan', predicate=version.has_qr,
shortcut=KEY_QR, f=scan_any_qr, arg=(True, True)),
MenuItem("Import Words", menu=import_ephemeral_menu),
@ -1175,7 +1179,6 @@ class EphemeralSeedMenu(MenuSystem):
async def make_ephemeral_seed_menu(*a):
assert not pa.hobbled_mode
if (not pa.tmp_value) and (not settings.master_get("seedvault", False)):
# force a warning on them, unless they are already doing it.

View File

@ -221,10 +221,14 @@ def restore_main_seed(goto_home, pick_menu_item, cap_story, cap_menu,
@pytest.fixture
def confirm_tmp_seed(need_keypress, cap_story, press_select):
def doit(seedvault=False, expect_xfp=None):
def doit(seedvault=False, expect_xfp=None, check_sv_not_offered=False):
time.sleep(0.3)
title, story = cap_story()
if check_sv_not_offered:
assert "to store temporary seed into Seed Vault" not in story
if "Press (1) to store temporary seed into Seed Vault" in story:
if seedvault:
need_keypress("1") # store it

View File

@ -11,15 +11,12 @@ from bbqr import join_qrs
from charcodes import KEY_QR, KEY_NFC
from base64 import b32encode
from constants import *
from test_ephemeral import SEEDVAULT_TEST_DATA
from test_notes import need_some_notes
from test_ephemeral import SEEDVAULT_TEST_DATA, WORDLISTS
from test_ephemeral import confirm_tmp_seed, verify_ephemeral_secret_ui
from test_ux import word_menu_entry
'''TODO
When hobbled...
- temp seeds are read-only: no create, no rename, etc.
- seed vault can be accessed tho
'''
TODO -- When hobbled...
- login sequence
1) system has lgto value: should get bypass pin, main pin, delay, then main pin again
@ -128,6 +125,9 @@ def test_menu_contents(set_hobble, pick_menu_item, cap_menu, en_okeys, en_notes,
if en_nfc:
adv_expect.add('NFC Tools')
if en_okeys:
adv_expect.add('Temporary Seed')
m = cap_menu()
assert set(m) == adv_expect, "Adv menu wrong"
@ -209,7 +209,7 @@ def test_h_seedvault(sv_empty, set_hobble, pick_menu_item, cap_menu, settings_se
else:
settings_set('seeds', [])
xfp, enc = SEEDVAULT_TEST_DATA[0][0:2]
settings_set("seeds", [(xfp, '80'+enc, f"Menu Label", "meta")])
settings_set("seeds", [(xfp, '80'+enc, f"Menu Label", "meta-source")])
set_hobble(True, {'okeys'})
@ -228,7 +228,7 @@ def test_h_seedvault(sv_empty, set_hobble, pick_menu_item, cap_menu, settings_se
pick_menu_item(m[0])
title, story = cap_story()
assert 'Origin:\nmeta' in story
assert 'Origin:\nmeta-source' in story
press_cancel()
pick_menu_item('Use This Seed')
@ -263,7 +263,97 @@ def test_h_seedvault(sv_empty, set_hobble, pick_menu_item, cap_menu, settings_se
m = cap_menu()
assert 'Seed Vault' not in m
# BIP-39 passphrases
@pytest.mark.parametrize('mode', [ 'words', 'qr', 'xprv', 'tapsigner', 'coldcard' ])
def test_h_tempseeds(mode, set_hobble, pick_menu_item, cap_menu, settings_set, is_q1, sim_exec, settings_remove, restore_main_seed, settings_get, press_cancel, press_select, cap_story, word_menu_entry, confirm_tmp_seed, verify_ephemeral_secret_ui, scan_a_qr, tapsigner_encrypted_backup, need_keypress, enter_hex, open_microsd):
'''
- can import and use a key for signing
- NOT offered chance to save into seedvault
'''
if not is_q1 and mode == 'qr': return
settings_set('seedvault', True)
settings_set('seeds', [])
set_hobble(True, {'okeys'})
pick_menu_item("Advanced/Tools")
pick_menu_item('Temporary Seed')
m = cap_menu()
assert 'Generate Words' not in m
assert all(i.startswith("Import ") or i.endswith(' Backup') for i in m), m
words, expect_xfp = WORDLISTS[12]
if mode == 'words':
# just quick tests here, not in-depth
# - from test_ephemeral_seed_import_words()
pick_menu_item("Import Words")
pick_menu_item(f"12 Words")
time.sleep(0.1)
word_menu_entry(words.split())
elif mode == 'qr':
pick_menu_item("Import from QR Scan")
val = ' '.join(words.split()).upper()
scan_a_qr(val)
time.sleep(0.1)
elif mode == 'tapsigner':
# like test_ephemeral_seed_import_tapsigner()
fname, backup_key_hex, node = tapsigner_encrypted_backup('sd', testnet=True)
expect_xfp = node.fingerprint().hex().upper()
pick_menu_item("Tapsigner Backup")
time.sleep(0.1)
need_keypress('1')
time.sleep(0.1)
pick_menu_item(fname)
time.sleep(0.1)
_, story = cap_story()
assert "your TAPSIGNER" in story
press_select() # yes I have backup key
enter_hex(backup_key_hex)
elif mode == 'coldcard':
# like test_temporary_from_backup()
# - but skip making new bk file
fn = 'data/tip-index-famous-embark-tobacco-rice-attitude-interest-mask-random-amazing-initial.7z'
pw = fn[5:-3].split('-')
contents = open(fn, 'rb').read()
with open_microsd('example.7z', 'wb') as fd:
fd.write(contents)
pick_menu_item("Coldcard Backup")
time.sleep(0.1)
need_keypress('1')
time.sleep(0.1)
pick_menu_item('example.7z')
word_menu_entry(pw, has_checksum=False)
title, story = cap_story()
assert title == 'FAILED'
assert 'successfully tested recovery' in story
press_select()
return
elif mode == 'xprv':
# meh todo ... XPRV case from file
pick_menu_item("Import XPRV")
press_cancel()
return
else:
raise pytest.fail(f'{mode} not done')
confirm_tmp_seed(seedvault=False, check_sv_not_offered=True)
verify_ephemeral_secret_ui(expected_xfp=expect_xfp, mnemonic=None, seed_vault=False)
pick_menu_item("Restore Master")
press_select()
# TODO: BIP-39 passphrases and temp seeds
# TODO: test usb commands are blocked
# EOF