What changed, and why it matters
This commit improves the Unix simulator's fake version of the COLDCARD's second secure element (SE2). It fixes a small bug where logging in with a 'trick PIN' could leave the wrong wallet loaded when later doing a normal login, and it makes the simulator better at rebuilding trick-PIN state from saved settings. These changes only affect the software simulator used for development/testing, not real COLDCARD hardware.
No immediate action required. This is a simulator-only quality improvement. Reviewers may want to confirm the self.wallet = None fix correctly covers the noted SP-unlock case and that reconstructed trick-PIN state in the simulator remains consistent with real hardware behavior for testing purposes.
Security signals we found
Bugfix in simulator trick-PIN handling: clearing self.wallet after non-trick PIN lookup to avoid stale wallet state
Refactoring of simulator SE2 state reconstruction logic
Addition of debug print statements for simulator trick-PIN slots
Comment noting a pre-existing simulator limitation with saved _skip_pin setting
Evidence from the diff
The patch touches only the Unix simulator variants (unix/variant/sim_se2.py and unix/variant/sim_settings.py). It refactors SE2 state reconstruction into a separate reconstruct() method, makes the simulator rebuild trick-PIN state from the ‘tp’ settings key when no persisted ‘_se2’ data exists, and adds a bugfix in get_by_pin() that clears self.wallet when a provided PIN is not a trick PIN. The latter prevents a stale wallet reference from persisting after a trick-login scenario (specifically noted as the ‘SP unlock case’). A minor comment is added in sim_settings.py about the ‘-g’ login path not working if ‘_skip_pin’ was saved. No real hardware secure-element code is modified.
Changed components
unix/variant/sim_se2.pyunix/variant/sim_settings.pyInspect captured patch +37 / −30
diff --git a/unix/variant/sim_se2.py b/unix/variant/sim_se2.py
index a03e2b0..450ba94 100644
--- a/unix/variant/sim_se2.py
+++ b/unix/variant/sim_se2.py
@@ -15,30 +15,34 @@ class SecondSecureElement:
self.wallet = None
self.load()
- if not self.state:
- # reconstruct based on user-space understanding of SE2 content
- # - can't work with duress wallet cases here (no data)
- # - mostly here so sim_settings works w/ non-empty defaults
- print("SIM SE2: found no state, trying to reconstruct")
- from glob import settings
- from trick_pins import TC_FAKE_OUT, TC_WORD_WALLET, TC_XPRV_WALLET
- from trick_pins import TC_DELTA_MODE, make_slot, TRICK_SLOT_LAYOUT
-
- for pin, (slot_num, tc_flags, tc_arg) in settings.get('tp', {}).items():
- if (tc_flags & (TC_DELTA_MODE | TC_WORD_WALLET | TC_XPRV_WALLET)):
- print("cant do duress cases")
- continue
- #assert not (tc_flags & (TC_DELTA_MODE | TC_WORD_WALLET | TC_XPRV_WALLET)), \
- #'unhandled simulated case: 0x%x' % tc_flags
-
- b, s = make_slot()
- s.pin_len = len(pin)
- s.pin[:s.pin_len] = pin.encode()
- s.tc_flags = tc_flags
- s.tc_arg = tc_arg
- s.slot_num = slot_num
-
- self.state[slot_num] = bytes(b)
+ def reconstruct(self, tp):
+ # reconstruct based on user-space understanding of SE2 content
+ # - can't work with duress wallet cases here (no data)
+ # - mostly here so sim_settings works w/ non-empty defaults
+ print("SIM SE2: found no state, trying to reconstruct")
+ from glob import settings
+ from trick_pins import TC_FAKE_OUT, TC_WORD_WALLET, TC_XPRV_WALLET
+ from trick_pins import TC_DELTA_MODE, make_slot, TRICK_SLOT_LAYOUT
+
+ print(" .. tp = %r" % tp)
+ if not tp: return
+
+ for pin, (slot_num, tc_flags, tc_arg) in tp.items():
+ if (tc_flags & (TC_DELTA_MODE | TC_WORD_WALLET | TC_XPRV_WALLET)):
+ print("cant do duress cases")
+ continue
+ #assert not (tc_flags & (TC_DELTA_MODE | TC_WORD_WALLET | TC_XPRV_WALLET)), \
+ #'unhandled simulated case: 0x%x' % tc_flags
+
+ b, s = make_slot()
+ s.pin_len = len(pin)
+ s.pin[:s.pin_len] = pin.encode()
+ s.tc_flags = tc_flags
+ s.tc_arg = tc_arg
+ s.slot_num = slot_num
+
+ self.state[slot_num] = bytes(b)
+ print("slot[%d] <= flags=0x%x arg=0x%x" % (slot_num, tc_flags, tc_arg))
# Storage: base64 encoded binary for all the slot numbers in a dict
@@ -64,16 +68,18 @@ class SecondSecureElement:
# merging default values as they contain useful nfc,vidsk info
dv = obj.default_values()
obj.current.update(dv)
- s = obj.get('_se2', None)
- if not s:
- print("no SE2 data")
- return
+ s = obj.get('_se2', None) or []
for record in s:
b = a2b_base64(record)
slot = uctypes.struct(uctypes.addressof(b), TRICK_SLOT_LAYOUT)
self.state[slot.slot_num] = b
print("SE2 slot %d is populated" % slot.slot_num)
+ else:
+ print("no SE2 data")
+
+ if not self.state:
+ self.reconstruct(obj.get('tp'))
def callgate(self, buf_io, arg2):
# ckcc.callgate(22, ...)
@@ -149,11 +155,12 @@ class SecondSecureElement:
# similar to stm32/mk4-bootloader/se2.c se2_test_trick_pin(safety_mode=False)
xs = self.get_by_pin(pin.encode(), num_fails)
if not xs:
+ self.wallet = None # bugfix: normal login after trick login (SP unlock case)
return None
- print("PIN %s is a TRICK!" % pin)
tc_flags = xs.tc_flags
tc_arg = xs.tc_arg
+ print("PIN %s is a TRICK! flags=0x%x arg=%d" % (pin, tc_flags, tc_arg))
from trick_pins import TC_WIPE, TC_BRICK, TC_REBOOT, TC_FAKE_OUT
from trick_pins import TC_WORD_WALLET, TC_XPRV_WALLET, TC_DELTA_MODE
diff --git a/unix/variant/sim_settings.py b/unix/variant/sim_settings.py
index 2706fb4..7823545 100644
--- a/unix/variant/sim_settings.py
+++ b/unix/variant/sim_settings.py
@@ -141,7 +141,7 @@ if '--secret' in sys.argv:
if '-g' in sys.argv:
- # do login
+ # do login.. but does not work if _skip_pin got saved into settings already
sim_defaults.pop('_skip_pin', 0)
if '--nick' in sys.argv:
Why this scored 19/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.