Q becomes calculator rather than e-waste

This commit is contained in:
Peter D. Gray 2025-09-22 10:47:01 -04:00
parent 038199a3e2
commit 4457576ad4
No known key found for this signature in database
GPG Key ID: A2DCD558C2BE5D7C
9 changed files with 39 additions and 16 deletions

View File

@ -44,6 +44,8 @@ Spending policies for "Single Signers" adds new spending policy options:
## 1.3.4Q - 2025-09-2x
- Enhancement: Enter "forever calculator" mode when Q would otherwise be e-waste: after 13
PIN failures, when device is bricked.
- Bugfix: Correct line positioning when 24 seed words displayed.

View File

@ -8,8 +8,9 @@ import utime, ngu, re
from utils import B2A, word_wrap
from ux_q1 import ux_input_text
async def login_repl(allow_login=True):
async def login_repl():
from glob import dis
from pincodes import pa
NUM_LINES = 7 # 10 - title - 2 for prompt
@ -64,12 +65,11 @@ Example Commands:
elif ln in ('help', 'cls', 'rand'):
# no need for () for these commands
ans = state[ln]()
elif allow_login and re_pin.match(ln) and (len(ln) <= 13):
elif pa.attempts_left and re_pin.match(ln) and (len(ln) <= 13):
# try login
m = re_pin.match(ln)
ln = m.group(1)+ '-' + m.group(2)
print(ln)
from pincodes import pa
try:
pa.setup(ln)
ok = pa.login()
@ -83,7 +83,7 @@ Example Commands:
else:
ans = 'Error: ' + repr(exc.args)
elif allow_login and re_prefix.match(ln) and (len(ln) <= 7):
elif re_prefix.match(ln) and (len(ln) <= 7):
# show words
from pincodes import pa
ans = pa.prefix_words(ln[:-1].encode())

View File

@ -181,14 +181,22 @@ class LoginUX:
async def we_are_ewaste(self, num_fails):
msg = '''After %d failed PIN attempts this Coldcard is locked forever. \
By design, there is no way to reset or recover the secure element, and its contents \
are now forever inaccessible.
are now forever inaccessible.\n\n''' % num_fails
Restore your seed words onto a new Coldcard.''' % num_fails
if has_qwerty:
msg += 'Calculator mode starts now.'
else:
msg += 'Restore your seed words onto a new Coldcard.'
while 1:
ch = await ux_show_story(msg, title='I Am Brick!', escape='6')
if ch == '6': break
if has_qwerty:
from calc import login_repl
await login_repl()
async def confirm_attempt(self, attempts_left, value):
ch = await ux_show_story('''You have %d attempts left before this Coldcard BRICKS \

View File

@ -90,7 +90,7 @@ async def more_setup():
from pincodes import pa
# check for bricked system early
# bricked CC not going past this point
pa.enforce_brick()
await pa.enforce_brick()
pa.setup(b'') # just to see where we stand.
is_blank = pa.is_blank()

View File

@ -130,9 +130,10 @@ class PinAttempt:
# seed, we are not going to let them see it, nor sign things we dont like, etc.
self.hobbled_mode = False
assert MAX_PIN_LEN == 32 # update FMT otherwise
assert ustruct.calcsize(PIN_ATTEMPT_FMT_V1) == PIN_ATTEMPT_SIZE_V1
assert ustruct.calcsize(PIN_ATTEMPT_FMT_V2_ADDITIONS) == PIN_ATTEMPT_SIZE - PIN_ATTEMPT_SIZE_V1
#assert MAX_PIN_LEN == 32 # update FMT otherwise
#assert ustruct.calcsize(PIN_ATTEMPT_FMT_V1) == PIN_ATTEMPT_SIZE_V1
#assert ustruct.calcsize(PIN_ATTEMPT_FMT_V2_ADDITIONS) \
# == PIN_ATTEMPT_SIZE - PIN_ATTEMPT_SIZE_V1
def __repr__(self):
return '<PinAttempt: fails/left=%d/%d tc_flag/arg=0x%x/0x%x>' % (
@ -535,11 +536,10 @@ class PinAttempt:
# check for bricked system early
if get_is_bricked():
try:
# regardless of settings.calc forever calculator after brickage
# for Q models fom version 5.X.X
if version.has_qwerty:
# regardless of settings, become a forever calculator after brickage.
while version.has_qwerty:
from calc import login_repl
await login_repl(allow_login=False)
await login_repl()
finally:
# die right away if it's not going to work
enter_dfu(3)

View File

@ -53,7 +53,7 @@ wallet (on testnet, always with the same seed). But there are other options:
- `--deriv` => go to the Derive Entropy menu inside settings, also loads XPRV from BIP
- `--secret 01abababab...` => directly set contents of SE secret, see SecretStash.encode()
- `--eject` => pretend no (simulated) SD Card is inserted
- `--eff` => (mk4) wipe setttings at startup, use simulator defaults
- `--eff` => wipe setttings at startup, use simulator defaults, save nothing.
- `--seq 1234yx34` => after start, enter those keypresses to get you to some submenu
- `--seq 2ENTER` => (Q) press 2 then ENTER, does QR at startup
- `--bootup-movie` => begin a movie on startup, to capture boot sequence
@ -61,6 +61,8 @@ wallet (on testnet, always with the same seed). But there are other options:
- `--battery` => (Q) assume the USB cable is NOT connected (ie. on battery power)
- `--early-usb` => start simulated USB interface even before user is login (useful for login testing)
- `--segregate` => scroll down to `Running simulators in parallel` section
- `--bricked` => simulate a system w/ bricked SE1: no more pin tries, etc.
- `--fails N` => simulate N wrong PIN attempts before login, where (1 <= N <= 13)
See `variant/sim_settings.py` for the details of settings-related options.

View File

@ -98,6 +98,9 @@ def gate(method, buf_io, arg2):
if method == 5:
# are we a brick? No.
if '--bricked' in sys.argv:
# if SE1 has pairing secret rotated; wont be able to do much
return 1
return 0
if method == 6:

View File

@ -155,6 +155,7 @@ if '--enter' in sys.argv:
# keep at end of file: extra enter to confirm something from above
numpad.inject('y')
# not best place for this
import hsm
hsm.POLICY_FNAME = hsm.POLICY_FNAME.replace('/flash/', '')

View File

@ -144,6 +144,13 @@ if '-g' in sys.argv:
# do login.. but does not work if _skip_pin got saved into settings already
sim_defaults.pop('_skip_pin', 0)
if '--fails' in sys.argv:
# fast-forward as if N PIN failures have already happened.
count = int(sys.argv[sys.argv.index('--fails') + 1])
import ckcc
ckcc.SE_STATE.force_fails(count)
sim_defaults.pop('_skip_pin', 0)
if '--nick' in sys.argv:
nick = sys.argv[sys.argv.index('--nick') + 1]
sim_defaults['nick'] = nick