What changed, and why it matters
This commit only adjusts test scripts and the simulator's fake secure-element storage. It makes tests more reliable by waiting for screens longer, refreshing menus after settings changes, ignoring a harmless internal counter, and keeping simulator-only SE2 data in a separate pretend storage key. There is no change to real firmware security behavior.
No security action required; treat as ordinary test/simulator maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is entirely within the testing harness and the unix simulator’s SE2 mock. Changes include: (1) test_codex32.py: updating assertions after UI flow changes and excluding the volatile _age settings counter from state snapshots; (2) test_ephemeral.py: forcing top-menu refresh after seed list changes; (3) test_multisig.py: replacing a fixed sleep with a 10-second polled wait for an error screen; (4) test_ux.py: adding a codex32 Share conversion for QR expectation; (5) unix/variant/sim_se2.py: giving the simulated second secure element its own SettingsObject nvram key so it does not collide with pre-login/wallet settings in the simulator. No production firmware code is modified.
Changed components
testing/test_codex32.pytesting/test_ephemeral.pytesting/test_multisig.pytesting/test_ux.pyunix/variant/sim_se2.pyInspect captured patch +37 / −11
### testing/test_codex32.py
@@ -530,9 +530,17 @@ def test_calculate_checksum_retry_long_input(goto_codex32_menu, pick_menu_item,
time.sleep(.5)
# Editor opens with the truncated prefill; no crash, input still editable.
assert 'Enter Codex32' in cap_screen()
- press_cancel()
+ # Submit the still-invalid prefill to leave the editor on both Q and Mk.
+ press_select()
+ time.sleep(.2)
+ title, story = cap_story()
+ assert title == 'FAILED'
+ assert 'ms codex32 length' in story
+ press_select()
time.sleep(.2)
press_cancel()
+ time.sleep(.2)
+ assert ('Calculate Checksum' if is_q1 else 'Calc Checksum') in cap_screen()
assert active_secret() == before
@@ -1921,9 +1929,11 @@ def test_import_codex32_invalid_key_preserves_state(key, state, vault,
sim_exec('from stash import SecretStash; '
'pa.tmp_secret(SecretStash.encode(master_secret=bytes(range(32))))')
+ # Delayed settings saves may advance _age without changing wallet state.
snapshot = (
'RV.write(repr((bytes(pa.fetch(bypass_tmp=True)), pa.tmp_value, '
- 'pa.is_secret_blank(), settings.nvram_key, settings.current, '
+ 'pa.is_secret_blank(), settings.nvram_key, '
+ '{k: v for k, v in settings.current.items() if k != "_age"}, '
'settings.master_get("seeds", []))))')
before = sim_exec(snapshot)
value = Share.from_seed(bytes(32) + key.to_bytes(32, 'big'),
@@ -1962,8 +1972,10 @@ def test_non_secret_import_preserves_state(value, state, vault, unit_test, reset
if state == 'temporary':
sim_exec('from stash import SecretStash; '
'pa.tmp_secret(SecretStash.encode(master_secret=bytes(range(32))))')
+ # Delayed settings saves may advance _age without changing wallet state.
snapshot = ('RV.write(repr((bytes(pa.fetch(bypass_tmp=True)), pa.tmp_value, '
- 'pa.is_secret_blank(), settings.nvram_key, settings.current, '
+ 'pa.is_secret_blank(), settings.nvram_key, '
+ '{k: v for k, v in settings.current.items() if k != "_age"}, '
'settings.master_get("seeds", []))))')
before = sim_exec(snapshot)
try:
### testing/test_ephemeral.py
@@ -1794,7 +1794,8 @@ def test_home_menu_xfp(name_fits, goto_home, pick_menu_item, press_select, cap_s
sim_exec("from seed import SecretStash; from pincodes import pa; "
"encoded = SecretStash.storage_serialize(pa.tmp_value); "
"settings.master_set('seeds', %r + [(%r, encoded, 'Travel', 'test')]); "
- "settings.master_set('tsn', True)" % (original_seeds, tmp_xfp))
+ "settings.master_set('tsn', True); "
+ "from actions import goto_top_menu; goto_top_menu()" % (original_seeds, tmp_xfp))
goto_home()
assert cap_menu()[0] == "[Travel]"
@@ -1803,13 +1804,15 @@ def test_home_menu_xfp(name_fits, goto_home, pick_menu_item, press_select, cap_s
test_name = 'N' * name_width
sim_exec("from seed import SecretStash; from pincodes import pa; "
"encoded = SecretStash.storage_serialize(pa.tmp_value); "
- "settings.master_set('seeds', %r + [(%r, encoded, %r, 'test')])" %
+ "settings.master_set('seeds', %r + [(%r, encoded, %r, 'test')]); "
+ "from actions import goto_top_menu; goto_top_menu()" %
(original_seeds, tmp_xfp, test_name))
goto_home()
expected_name = "[" + test_name + "]" if name_fits else "[" + tmp_xfp + "]"
assert cap_menu()[0] == expected_name
- sim_exec("settings.master_set('seeds', %r); settings.master_set('tsn', %r)" %
+ sim_exec("settings.master_set('seeds', %r); settings.master_set('tsn', %r); "
+ "from actions import goto_top_menu; goto_top_menu()" %
(original_seeds, original_tsn))
goto_home()
assert cap_menu()[0] == "[" + tmp_xfp + "]"
### testing/test_multisig.py
@@ -1933,8 +1933,10 @@ def test_reject_oversized_airgapped_xpub_qr(goto_home, pick_menu_item, need_keyp
for part in parts:
scan_a_qr(part)
- time.sleep(.5)
- assert 'Multisig export is too large' in cap_screen()
+ deadline = time.monotonic() + 10
+ while 'Multisig export is too large' not in cap_screen():
+ assert time.monotonic() < deadline, 'Timed out waiting for oversized export rejection'
+ time.sleep(.1)
press_select()
### testing/test_ux.py
@@ -976,7 +976,7 @@ def test_seed_import_tapsigner(way, testnet, cap_menu, pick_menu_item, goto_home
def test_show_seed(mode, b39_word, goto_home, pick_menu_item, cap_story, need_keypress,
sim_exec, cap_menu, get_secrets, cap_screen_qr, set_bip39_pw,
set_encoded_secret, qr_quality_check, reset_seed_words,
- press_select, is_q1, seed_story_to_words, is_headless):
+ press_select, is_q1, seed_story_to_words, is_headless, load_shared_mod):
reset_seed_words()
if mode == 'words':
@@ -1036,6 +1036,9 @@ def test_show_seed(mode, b39_word, goto_home, pick_menu_item, cap_story, need_ke
else:
assert expect in body
qr_expect = expect
+ if mode == 'ms':
+ Share = load_shared_mod('codex32', '../shared/codex32.py').Share
+ qr_expect = Share.from_seed(bytes.fromhex(expect), 'ms', 'seed', 's', 0).to_string().upper()
if b39_word:
assert body.startswith("BIP-39 Passphrase in effect\n\n")
assert b39_word not in body
### unix/variant/sim_se2.py
@@ -9,6 +9,9 @@
# these flags are masked-out from mpy so even it can't tell they happened
TC_HIDDEN_MASK = const(0xf800)
+# Keep simulated SE2 state separate from pre-login and wallet settings.
+SE2_SETTINGS_KEY = b'\x02' * 32
+
class SecondSecureElement:
def __init__(self):
# restore state, or reconstruct some guesses
@@ -49,7 +52,8 @@ def reconstruct(self, tp):
def save(self):
from nvstore import SettingsObject
- s = SettingsObject()
+ s = SettingsObject(nvram_key=SE2_SETTINGS_KEY)
+ s.load()
s.set('_se2', [b2a_base64(i) for i in self.state.values()])
s.save()
print("saved SE2 data: " + ', '.join(str(i) for i in self.state.keys()))
@@ -68,7 +72,9 @@ def load(self):
# merging default values as they contain useful nfc,vidsk info
dv = obj.default_values()
obj.current.update(dv)
- s = obj.get('_se2', None) or []
+ se2 = SettingsObject(nvram_key=SE2_SETTINGS_KEY)
+ se2.load()
+ s = se2.get('_se2', None) or []
for record in s:
b = a2b_base64(record)Why this scored 12/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.