remove obsolete Mk2/Mk3 code paths from firmware
(cherry picked from commit 6655238409)
This commit is contained in:
parent
615c0a5064
commit
342af8f78e
@ -5,6 +5,7 @@
|
||||
Only needed for Mk 2-3 where SPI flash was external chip,
|
||||
easily observed, but now that's different. New simpler and less storage
|
||||
wasteful plausible deniability.
|
||||
- Enhancement: Remove obsolete Mk2/Mk3 code-paths from master branch
|
||||
|
||||
## 5.1.4 - 2023-09-08
|
||||
|
||||
|
||||
@ -116,9 +116,6 @@ Extended Master Key:
|
||||
if bn:
|
||||
msg += '\nShipping Bag:\n %s\n' % bn
|
||||
|
||||
if not version.has_fatram:
|
||||
# can't support on mk2
|
||||
xpub = None
|
||||
if xpub:
|
||||
msg += '\nPress (3) to show QR code of xpub.'
|
||||
|
||||
@ -228,6 +225,9 @@ async def microsd_upgrade(menu, label, item):
|
||||
# - erase serial flash
|
||||
# - copy it over (slow)
|
||||
# - reboot into bootloader, which finishes install
|
||||
from glob import dis, PSRAM
|
||||
from files import dfu_parse
|
||||
from utils import check_firmware_hdr
|
||||
from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE, FW_MAX_LENGTH_MK4
|
||||
|
||||
force_vdisk = item.arg
|
||||
@ -238,18 +238,8 @@ async def microsd_upgrade(menu, label, item):
|
||||
if not fn: return
|
||||
|
||||
failed = None
|
||||
|
||||
with CardSlot(force_vdisk=force_vdisk) as card:
|
||||
with card.open(fn, 'rb') as fp:
|
||||
from version import has_psram
|
||||
if has_psram:
|
||||
from glob import PSRAM as SF
|
||||
else:
|
||||
from sflash import SF
|
||||
from glob import dis
|
||||
from files import dfu_parse
|
||||
from utils import check_firmware_hdr
|
||||
|
||||
offset, size = dfu_parse(fp)
|
||||
|
||||
# we also put a copy of special signed heaer at the end of the flash
|
||||
@ -264,12 +254,12 @@ async def microsd_upgrade(menu, label, item):
|
||||
failed = check_firmware_hdr(hdr, size)
|
||||
|
||||
if not failed:
|
||||
# copy binary into serial flash / PSRAM
|
||||
# copy binary into PSRAM
|
||||
fp.seek(offset)
|
||||
|
||||
dis.fullscreen("Loading...")
|
||||
|
||||
buf = bytearray(256 if not has_psram else 0x20000)
|
||||
buf = bytearray(0x20000)
|
||||
pos = 0
|
||||
while pos < size:
|
||||
dis.progress_bar_show(pos/size)
|
||||
@ -277,21 +267,7 @@ async def microsd_upgrade(menu, label, item):
|
||||
here = fp.readinto(buf)
|
||||
if not here: break
|
||||
|
||||
if has_psram:
|
||||
SF.write(pos, buf)
|
||||
else:
|
||||
if pos % 4096 == 0:
|
||||
# erase here
|
||||
SF.sector_erase(pos)
|
||||
while SF.is_busy():
|
||||
await sleep_ms(10)
|
||||
|
||||
SF.write(pos, buf)
|
||||
|
||||
# full page write: 0.6 to 3ms
|
||||
while SF.is_busy():
|
||||
await sleep_ms(1)
|
||||
|
||||
PSRAM.write(pos, buf)
|
||||
pos += here
|
||||
|
||||
if failed:
|
||||
@ -300,7 +276,7 @@ async def microsd_upgrade(menu, label, item):
|
||||
|
||||
# continue process...
|
||||
from auth import FirmwareUpgradeRequest
|
||||
m = FirmwareUpgradeRequest(hdr, size, psram_offset=(0 if has_psram else None))
|
||||
m = FirmwareUpgradeRequest(hdr, size, psram_offset=0)
|
||||
the_ux.push(m)
|
||||
|
||||
async def start_dfu(*a):
|
||||
@ -429,9 +405,6 @@ async def block_until_login():
|
||||
from login import LoginUX
|
||||
from ux import AbortInteraction
|
||||
|
||||
# mk4 does differently, as a "trick pin"
|
||||
cd_pin = settings.get('cd_pin', None) if not version.has_se2 else None
|
||||
|
||||
# do they want a randomized (shuffled) keypad?
|
||||
rnd_keypad = settings.get('rngk', 0)
|
||||
|
||||
@ -444,7 +417,7 @@ async def block_until_login():
|
||||
lll = LoginUX(rnd_keypad, kill_btn)
|
||||
|
||||
try:
|
||||
rv = await lll.try_login(bypass_pin=cd_pin)
|
||||
rv = await lll.try_login(bypass_pin=None)
|
||||
if rv: break
|
||||
except AbortInteraction:
|
||||
# not allowed!
|
||||
@ -609,16 +582,20 @@ async def clear_seed(*a):
|
||||
import seed
|
||||
|
||||
if pa.has_duress_pin():
|
||||
await ux_show_story('''Please empty the duress wallet, and clear the duress PIN before clearing main seed.''')
|
||||
await ux_show_story('Please empty the duress wallet, and clear '
|
||||
'the duress PIN before clearing main seed.')
|
||||
return
|
||||
|
||||
if version.has_se2:
|
||||
from trick_pins import tp
|
||||
if any(tp.get_duress_pins()):
|
||||
await ux_show_story('''You have one or more duress wallets defined under Trick PINs. Please empty them, and clear associated Trick PINs before clearing main seed.''')
|
||||
return
|
||||
from trick_pins import tp
|
||||
if any(tp.get_duress_pins()):
|
||||
await ux_show_story('You have one or more duress wallets defined '
|
||||
'under Trick PINs. Please empty them, and clear '
|
||||
'associated Trick PINs before clearing main seed.')
|
||||
return
|
||||
|
||||
if not await ux_confirm('''Wipe seed words and reset wallet. All funds will be lost. You better have a backup of the seed words.'''):
|
||||
if not await ux_confirm('Wipe seed words and reset wallet. '
|
||||
'All funds will be lost. '
|
||||
'You better have a backup of the seed words.'):
|
||||
return await ux_aborted()
|
||||
|
||||
ch = await ux_show_story('''Are you REALLY sure though???\n\n\
|
||||
@ -687,8 +664,7 @@ async def view_seed_words(*a):
|
||||
dis.busy_bar(False)
|
||||
msg, qr, qr_alnum = render_master_secrets(sv.mode, sv.raw, sv.node)
|
||||
|
||||
if version.has_fatram:
|
||||
msg += '\n\nPress (1) to view as QR Code.'
|
||||
msg += '\n\nPress (1) to view as QR Code.'
|
||||
|
||||
while 1:
|
||||
ch = await ux_show_story(msg, sensitive=True, escape='1')
|
||||
@ -761,10 +737,9 @@ async def version_migration():
|
||||
|
||||
async def version_migration_prelogin():
|
||||
# same, but for setting before login
|
||||
if version.has_se2:
|
||||
# these have moved into SE2 for Mk4 and so can be removed
|
||||
for n in [ 'cd_lgto', 'cd_mode', 'cd_pin' ]:
|
||||
settings.remove_key(n)
|
||||
# these have moved into SE2 for Mk4 and so can be removed
|
||||
for n in [ 'cd_lgto', 'cd_mode', 'cd_pin' ]:
|
||||
settings.remove_key(n)
|
||||
|
||||
async def start_login_sequence():
|
||||
# Boot up login sequence here.
|
||||
@ -786,12 +761,6 @@ async def start_login_sequence():
|
||||
|
||||
if pa.is_blank():
|
||||
# Blank devices, with no PIN set all, can continue w/o login
|
||||
|
||||
# Do green-light set immediately after firmware upgrade [not after mk3]
|
||||
if version.is_fresh_version() and version.mk_num <=3:
|
||||
pa.greenlight_firmware()
|
||||
dis.show()
|
||||
|
||||
goto_top_menu()
|
||||
return
|
||||
|
||||
@ -826,16 +795,11 @@ async def start_login_sequence():
|
||||
|
||||
# Do we need to do countdown delay? (real or otherwise)
|
||||
delay = 0
|
||||
if wants_countdown:
|
||||
# Mk3 and earlier
|
||||
await damage_myself()
|
||||
delay = settings.get('cd_lgto', 60)
|
||||
elif version.has_se2:
|
||||
# Mk4 approach:
|
||||
# - wiping has already occured if that was picked
|
||||
# - delay is variable, stored in tc_arg
|
||||
from trick_pins import tp
|
||||
delay = tp.was_countdown_pin()
|
||||
# Mk4 approach:
|
||||
# - wiping has already occured if that was picked
|
||||
# - delay is variable, stored in tc_arg
|
||||
from trick_pins import tp
|
||||
delay = tp.was_countdown_pin()
|
||||
|
||||
# Maybe they do know the right PIN, but do a delay anyway, because they wanted that
|
||||
if not delay:
|
||||
@ -846,23 +810,10 @@ async def start_login_sequence():
|
||||
pa.reset()
|
||||
await login_countdown(delay * (60 if not version.is_devmode else 1))
|
||||
|
||||
if version.has_se2:
|
||||
# keep it simple for Mk4+: just challenge again for any PIN
|
||||
# - if it's the same countdown pin, it will be accepted and they
|
||||
# get in (as most trick pins would do)
|
||||
await block_until_login()
|
||||
else:
|
||||
# second PIN challenge; but only if first one was actually legit
|
||||
wants_countdown = await block_until_login()
|
||||
|
||||
# whenever they use the countdown pin on second screen, kill ourselves
|
||||
if wants_countdown:
|
||||
await damage_myself()
|
||||
|
||||
if wants_countdown:
|
||||
# crash
|
||||
dis.fullscreen("ERROR")
|
||||
callgate.show_logout(1)
|
||||
# keep it simple for Mk4+: just challenge again for any PIN
|
||||
# - if it's the same countdown pin, it will be accepted and they
|
||||
# get in (as most trick pins would do)
|
||||
await block_until_login()
|
||||
|
||||
except BaseException as exc:
|
||||
# Robustness: any logic errors/bugs in above will brick the Coldcard
|
||||
@ -901,13 +852,6 @@ async def start_login_sequence():
|
||||
from imptask import IMPT
|
||||
IMPT.start_task('idle', idle_logout())
|
||||
|
||||
# Do green-light set immediately after firmware upgrade
|
||||
# - mk4 doesn't work this way, light will already be green
|
||||
if version.mk_num <= 3:
|
||||
if version.is_fresh_version() and not pa.is_secondary:
|
||||
pa.greenlight_firmware()
|
||||
dis.show()
|
||||
|
||||
# Populate xfp/xpub values, if missing.
|
||||
# - can happen for first-time login of duress wallet
|
||||
# - may indicate lost settings, which we can easily recover from
|
||||
@ -934,18 +878,17 @@ async def start_login_sequence():
|
||||
|
||||
# If HSM policy file is available, offer to start that,
|
||||
# **before** the USB is even enabled.
|
||||
if version.has_fatram:
|
||||
# do not offer HSM if wallet is blank -> HSM needs secret
|
||||
if not pa.is_secret_blank():
|
||||
try:
|
||||
import hsm, hsm_ux
|
||||
# do not offer HSM if wallet is blank -> HSM needs secret
|
||||
if not pa.is_secret_blank():
|
||||
try:
|
||||
import hsm, hsm_ux
|
||||
|
||||
if hsm.hsm_policy_available():
|
||||
settings.put("hsmcmd", True)
|
||||
ar = await hsm_ux.start_hsm_approval(usb_mode=False, startup_mode=True)
|
||||
if ar:
|
||||
await ar.interact()
|
||||
except: pass
|
||||
if hsm.hsm_policy_available():
|
||||
settings.put("hsmcmd", True)
|
||||
ar = await hsm_ux.start_hsm_approval(usb_mode=False, startup_mode=True)
|
||||
if ar:
|
||||
await ar.interact()
|
||||
except: pass
|
||||
|
||||
if version.mk_num >= 4:
|
||||
if version.has_nfc and settings.get('nfc', 0):
|
||||
@ -2073,14 +2016,7 @@ async def show_version(*a):
|
||||
bl = callgate.get_bl_version()[0]
|
||||
chk = str(b2a_hex(callgate.get_bl_checksum(0))[-8:], 'ascii')
|
||||
|
||||
if version.has_se2:
|
||||
se = '\n '.join(callgate.get_se_parts())
|
||||
else:
|
||||
se = 'ATECC'
|
||||
if version.has_608:
|
||||
se += '608B' if callgate.has_608b() else '608A'
|
||||
else:
|
||||
se += '508A'
|
||||
se = '\n '.join(callgate.get_se_parts())
|
||||
|
||||
# exposed over USB interface:
|
||||
serial = version.serial_number()
|
||||
@ -2110,12 +2046,12 @@ Serial:
|
||||
Hardware:
|
||||
{hw}
|
||||
|
||||
Secure Element{ses}:
|
||||
Secure Elements:
|
||||
{se}
|
||||
'''
|
||||
|
||||
await ux_show_story(msg.format(rel=rel, built=built, bl=bl, chk=chk, se=se,
|
||||
ser=serial, hw=hw, ses='s' if version.has_se2 else ''))
|
||||
ser=serial, hw=hw))
|
||||
|
||||
async def ship_wo_bag(*a):
|
||||
# Factory command: for dev and test units that have no bag number, and never will.
|
||||
|
||||
@ -265,7 +265,7 @@ Press (3) if you really understand and accept these risks.
|
||||
return _cache[(change, start, n)]
|
||||
|
||||
export_msg = "Press (1) to save Address summary file to SD Card."
|
||||
if version.has_fatram and not ms_wallet:
|
||||
if not ms_wallet:
|
||||
export_msg += " Press (2) to view QR Codes."
|
||||
if NFC:
|
||||
export_msg += " Press (3) to share via NFC."
|
||||
@ -346,8 +346,7 @@ Press (3) if you really understand and accept these risks.
|
||||
|
||||
elif ch == '2':
|
||||
# switch into a mode that shows them as QR codes
|
||||
if not version.has_fatram or ms_wallet:
|
||||
# requires mk3 and not multisig
|
||||
if ms_wallet:
|
||||
continue
|
||||
|
||||
from ux import show_qr_codes
|
||||
|
||||
@ -18,7 +18,7 @@ from utils import HexWriter, xfp2str, problem_file_line, cleanup_deriv_path
|
||||
from utils import B2A, parse_addr_fmt_str
|
||||
from psbt import psbtObject, FatalPSBTIssue, FraudulentChangeOutput
|
||||
from exceptions import HSMDenied
|
||||
from version import has_psram, has_fatram, MAX_TXN_LEN
|
||||
from version import MAX_TXN_LEN
|
||||
|
||||
# Where in SPI flash/PSRAM the two PSBT files are (in and out)
|
||||
TXN_INPUT_OFFSET = 0
|
||||
@ -634,7 +634,7 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
# Prompt user w/ details and get approval
|
||||
from glob import dis, hsm_active
|
||||
|
||||
# step 1: parse PSBT from sflash into in-memory objects.
|
||||
# step 1: parse PSBT from PSRAM into in-memory objects.
|
||||
|
||||
try:
|
||||
with SFFile(TXN_INPUT_OFFSET, length=self.psbt_len, message='Reading...') as fd:
|
||||
@ -801,16 +801,14 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
while 1:
|
||||
# Show txid when we can; advisory
|
||||
# - maybe even as QR, hex-encoded in alnum mode
|
||||
tmsg = txid + '\n\n'
|
||||
tmsg = txid + '\n\nPress (1) for QR Code of TXID. '
|
||||
|
||||
if has_fatram:
|
||||
tmsg += 'Press (1) for QR Code of TXID. '
|
||||
if NFC:
|
||||
tmsg += 'Press (3) to share signed txn via NFC.'
|
||||
|
||||
ch = await ux_show_story(tmsg, "Final TXID", escape='13')
|
||||
|
||||
if ch=='1' and has_fatram:
|
||||
if ch == '1':
|
||||
await show_qr_code(txid, True)
|
||||
continue
|
||||
|
||||
@ -955,7 +953,7 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
|
||||
|
||||
def sign_transaction(psbt_len, flags=0x0, psbt_sha=None):
|
||||
# transaction (binary) loaded into sflash/PSRAM already, checksum checked
|
||||
# transaction (binary) loaded into PSRAM already, checksum checked
|
||||
UserAuthorizedAction.check_busy(ApproveTransaction)
|
||||
UserAuthorizedAction.active_request = ApproveTransaction(psbt_len, flags, psbt_sha=psbt_sha)
|
||||
|
||||
@ -989,9 +987,10 @@ async def sign_psbt_file(filename, force_vdisk=False):
|
||||
from files import CardSlot, CardMissingError
|
||||
from glob import dis
|
||||
from ux import the_ux
|
||||
from sram2 import tmp_buf
|
||||
|
||||
# copy file into our spiflash
|
||||
tmp_buf = bytearray(1024)
|
||||
|
||||
# copy file into PSRAM
|
||||
# - can't work in-place on the card because we want to support writing out to different card
|
||||
# - accepts hex or base64 encoding, but binary prefered
|
||||
with CardSlot(force_vdisk, readonly=True) as card:
|
||||
@ -1270,13 +1269,13 @@ class ShowAddressBase(UserAuthorizedAction):
|
||||
msg += '\n\nCompare this payment address to the one shown on your other, less-trusted, software.'
|
||||
if NFC:
|
||||
msg += ' Press (3) to share via NFC.'
|
||||
if has_fatram:
|
||||
msg += ' Press (4) to view QR Code.'
|
||||
|
||||
msg += ' Press (4) to view QR Code.'
|
||||
|
||||
while 1:
|
||||
ch = await ux_show_story(msg, title=self.title, escape='34')
|
||||
|
||||
if ch == '4' and has_fatram:
|
||||
if ch == '4':
|
||||
await show_qr_code(self.address, (self.addr_fmt & AFC_BECH32))
|
||||
continue
|
||||
if ch == '3' and NFC:
|
||||
@ -1517,26 +1516,15 @@ Binary checksum and signature will be further verified before any changes are ma
|
||||
# - reboot to start process
|
||||
from glob import dis
|
||||
dis.fullscreen('Upgrading...', percent=1)
|
||||
|
||||
if not has_psram:
|
||||
from sflash import SF
|
||||
import callgate
|
||||
SF.write(self.length, self.hdr)
|
||||
|
||||
callgate.show_logout(2)
|
||||
else:
|
||||
# Mk4 copies from PSRAM to flash inside bootrom, we have
|
||||
# nothing to do here except start that process.
|
||||
from pincodes import pa
|
||||
pa.firmware_upgrade(self.psram_offset, self.length)
|
||||
# not reached, unless issue?
|
||||
raise RuntimeError("bootrom fail")
|
||||
# Mk4 copies from PSRAM to flash inside bootrom, we have
|
||||
# nothing to do here except start that process.
|
||||
from pincodes import pa
|
||||
pa.firmware_upgrade(self.psram_offset, self.length)
|
||||
# not reached, unless issue?
|
||||
raise RuntimeError("bootrom fail")
|
||||
else:
|
||||
# they don't want to!
|
||||
self.refused = True
|
||||
if not has_psram:
|
||||
from sflash import SF
|
||||
SF.block_erase(0) # just in case, but not required
|
||||
await ux_dramatic_pause("Refused.", 2)
|
||||
|
||||
except BaseException as exc:
|
||||
|
||||
@ -65,21 +65,13 @@ def render_backup_contents():
|
||||
ADD('long_secret', b2a_hex(pa.ls_fetch()))
|
||||
|
||||
# Duress wallets (somewhat optional, since derived)
|
||||
if version.mk_num <= 3:
|
||||
if pa.has_duress_pin():
|
||||
COMMENT('Duress Wallet (informational)')
|
||||
dpk, p = sv.duress_root()
|
||||
COMMENT('path = %s' % p)
|
||||
ADD('duress_xprv', chain.serialize_private(dpk))
|
||||
ADD('duress_xpub', chain.serialize_public(dpk))
|
||||
else:
|
||||
from trick_pins import tp
|
||||
for label, path, pairs in tp.backup_duress_wallets(sv):
|
||||
COMMENT()
|
||||
COMMENT(label + ' (informational)')
|
||||
COMMENT(path)
|
||||
for k,v in pairs:
|
||||
ADD(k, v)
|
||||
from trick_pins import tp
|
||||
for label, path, pairs in tp.backup_duress_wallets(sv):
|
||||
COMMENT()
|
||||
COMMENT(label + ' (informational)')
|
||||
COMMENT(path)
|
||||
for k,v in pairs:
|
||||
ADD(k, v)
|
||||
|
||||
COMMENT('Firmware version (informational)')
|
||||
date, vers, timestamp = version.get_mpy_version()[0:3]
|
||||
@ -100,10 +92,9 @@ def render_backup_contents():
|
||||
if k == 'words': continue # words length is recalculated from secret
|
||||
ADD('setting.' + k, v)
|
||||
|
||||
if version.has_fatram:
|
||||
import hsm
|
||||
if hsm.hsm_policy_available():
|
||||
ADD('hsm_policy', hsm.capture_backup())
|
||||
import hsm
|
||||
if hsm.hsm_policy_available():
|
||||
ADD('hsm_policy', hsm.capture_backup())
|
||||
|
||||
rv.write('\n# EOF\n')
|
||||
|
||||
@ -205,7 +196,7 @@ def restore_from_dict_ll(vals):
|
||||
# write out
|
||||
settings.save()
|
||||
|
||||
if version.has_fatram and ('hsm_policy' in vals):
|
||||
if 'hsm_policy' in vals:
|
||||
import hsm
|
||||
hsm.restore_backup(vals['hsm_policy'])
|
||||
|
||||
|
||||
@ -7,11 +7,12 @@ from ssd1306 import SSD1306_SPI
|
||||
from version import is_devmode, is_edge
|
||||
import framebuf
|
||||
from graphics import Graphics
|
||||
from sram2 import display2_buf
|
||||
|
||||
# we support 4 fonts
|
||||
from zevvpeep import FontSmall, FontLarge, FontTiny
|
||||
FontFixed = object() # ugly 8x8 PET font
|
||||
display2_buf = bytearray(1024)
|
||||
|
||||
|
||||
class Display:
|
||||
|
||||
|
||||
@ -188,7 +188,7 @@ async def drv_entro_step2(_1, picked, _2):
|
||||
prompt += ', (2) to switch to derived secret'
|
||||
elif s_mode == 'pw':
|
||||
prompt += ', (2) to type password over USB'
|
||||
if (qr is not None) and version.has_fatram:
|
||||
if qr is not None:
|
||||
prompt += ', (3) to view as QR code'
|
||||
if glob.NFC:
|
||||
prompt += ', (4) to share via NFC'
|
||||
@ -226,7 +226,7 @@ async def drv_entro_step2(_1, picked, _2):
|
||||
story = "Filename is:\n\n%s" % out_fn
|
||||
story += "\n\nSignature filename is:\n\n%s" % sig_nice
|
||||
await ux_show_story(story, title='Saved')
|
||||
elif ch == '3' and version.has_fatram:
|
||||
elif ch == '3':
|
||||
from ux import show_qr_code
|
||||
await show_qr_code(qr, qr_alnum)
|
||||
continue
|
||||
|
||||
@ -56,8 +56,7 @@ def wipe_flash_filesystem():
|
||||
# erase and re-format the flash filesystem (/flash/**)
|
||||
import ckcc, pyb
|
||||
from glob import dis
|
||||
from version import mk_num
|
||||
|
||||
|
||||
dis.fullscreen('Erasing...')
|
||||
os.umount('/flash')
|
||||
|
||||
@ -81,16 +80,10 @@ def wipe_flash_filesystem():
|
||||
|
||||
# rebuild and mount /flash
|
||||
dis.fullscreen('Rebuilding...')
|
||||
|
||||
if mk_num == 4:
|
||||
# no need to erase, we just put new FS on top
|
||||
import mk4
|
||||
mk4.make_flash_fs()
|
||||
else:
|
||||
ckcc.wipe_fs()
|
||||
|
||||
# remount it
|
||||
os.mount(fl, '/flash')
|
||||
# no need to erase, we just put new FS on top
|
||||
import mk4
|
||||
mk4.make_flash_fs()
|
||||
|
||||
# re-store current settings
|
||||
from glob import settings
|
||||
|
||||
@ -29,7 +29,7 @@ transfer data easily via NFC.''' + COMMON
|
||||
|
||||
# Disabled for now, because limited audience and
|
||||
# extra barrier to "just getting started"
|
||||
if 0: # version.has_psram and not settings.get('vidsk', 0):
|
||||
if 0: # if not settings.get('vidsk', 0):
|
||||
msg = '''Enable USB Drive?\n\n\
|
||||
Connect your COLDCARD directly as a USB flash drive \
|
||||
to your phone or desktop. You will be able to drag-n-drop or \
|
||||
|
||||
@ -682,12 +682,6 @@ class HSMPolicy:
|
||||
with open(POLICY_FNAME, 'w+t') as f:
|
||||
ujson.dump(self.save(), f)
|
||||
|
||||
if version.mk_num <= 3:
|
||||
# that changes the flash, so need to update
|
||||
# the hash stored in SE (Mk3 and earlier)
|
||||
pa.greenlight_firmware()
|
||||
dis.show()
|
||||
|
||||
if self.set_sl:
|
||||
self.save_storage_locker()
|
||||
|
||||
|
||||
@ -39,21 +39,17 @@ glob.dis = dis
|
||||
# slowish imports, some with side-effects
|
||||
import ckcc, uasyncio
|
||||
|
||||
if version.mk_num >= 4:
|
||||
# early setup code needed on Mk4
|
||||
try:
|
||||
import mk4
|
||||
mk4.init0()
|
||||
# early setup code needed on Mk4
|
||||
try:
|
||||
import mk4
|
||||
mk4.init0()
|
||||
|
||||
from psram import PSRAMWrapper
|
||||
glob.PSRAM = PSRAMWrapper()
|
||||
from psram import PSRAMWrapper
|
||||
glob.PSRAM = PSRAMWrapper()
|
||||
|
||||
except BaseException as exc:
|
||||
sys.print_exception(exc)
|
||||
# continue tho
|
||||
else:
|
||||
# Serial Flash memory
|
||||
from sflash import SF
|
||||
except BaseException as exc:
|
||||
sys.print_exception(exc)
|
||||
# continue tho
|
||||
|
||||
# Setup membrane numpad (mark 2+)
|
||||
from mempad import MembraneNumpad
|
||||
|
||||
@ -21,6 +21,7 @@ freeze_as_mpy('', [
|
||||
'export.py',
|
||||
'files.py',
|
||||
'flow.py',
|
||||
'ftux.py',
|
||||
'glob.py',
|
||||
'history.py',
|
||||
'hsm.py',
|
||||
@ -56,7 +57,6 @@ freeze_as_mpy('', [
|
||||
'ux.py',
|
||||
'version.py',
|
||||
'xor_seed.py',
|
||||
'ftux.py',
|
||||
], opt=0)
|
||||
|
||||
# Optimize data-like files, since no need to debug them.
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# Mk3 and earlier only files; would not be needed on Mk4 or later
|
||||
freeze_as_mpy('', [
|
||||
'sflash.py',
|
||||
], opt=0)
|
||||
|
||||
'sflash.py',
|
||||
], opt=0)
|
||||
@ -10,4 +10,4 @@ freeze_as_mpy('', [
|
||||
|
||||
freeze_as_mpy('', [
|
||||
'graphics_mk4.py',
|
||||
], opt=3)
|
||||
], opt=3)
|
||||
@ -73,16 +73,11 @@ class PaperWalletMaker:
|
||||
self.update_menu()
|
||||
return self.atype()[0], ['Classic P2PKH', 'Segwit P2WPKH', 'Taproot P2TR'], set
|
||||
|
||||
@staticmethod
|
||||
def can_do_qr():
|
||||
import version
|
||||
return version.has_fatram
|
||||
|
||||
def update_menu(self):
|
||||
# Reconstruct the menu contents based on our state.
|
||||
self.my_menu.replace_items([
|
||||
MenuItem("Don't make PDF" if not self.template_fn else 'Making PDF',
|
||||
f=self.pick_template, predicate=self.can_do_qr),
|
||||
f=self.pick_template),
|
||||
MenuItem(self.atype()[1], chooser=self.addr_format_chooser),
|
||||
MenuItem('Use Dice', f=self.use_dice),
|
||||
MenuItem('GENERATE WALLET', f=self.doit),
|
||||
@ -122,18 +117,14 @@ class PaperWalletMaker:
|
||||
|
||||
wif = ngu.codecs.b58_encode(ch.b58_privkey + privkey + b'\x01')
|
||||
|
||||
if self.can_do_qr():
|
||||
with imported('uqr') as uqr:
|
||||
# make the QR's now, since it's slow
|
||||
is_alnum = self.is_segwit
|
||||
qr_addr = uqr.make(addr if not is_alnum else addr.upper(),
|
||||
min_version=4, max_version=4,
|
||||
encoding=(uqr.Mode_ALPHANUMERIC if is_alnum else 0))
|
||||
with imported('uqr') as uqr:
|
||||
# make the QR's now, since it's slow
|
||||
is_alnum = self.is_segwit
|
||||
qr_addr = uqr.make(addr if not is_alnum else addr.upper(),
|
||||
min_version=4, max_version=4,
|
||||
encoding=(uqr.Mode_ALPHANUMERIC if is_alnum else 0))
|
||||
|
||||
qr_wif = uqr.make(wif, min_version=4, max_version=4, encoding=uqr.Mode_BYTE)
|
||||
else:
|
||||
qr_addr = None
|
||||
qr_wif = None
|
||||
qr_wif = uqr.make(wif, min_version=4, max_version=4, encoding=uqr.Mode_BYTE)
|
||||
|
||||
# Use address as filename. clearly will be unique, but perhaps a bit
|
||||
# awkward to work with.
|
||||
@ -308,7 +299,8 @@ class PaperWalletMaker:
|
||||
|
||||
async def make_paper_wallet(*a):
|
||||
|
||||
msg = background_msg.format(can_qr=('\nIf you have a special PDF template file, it can also make a pretty version of the same data.' if PaperWalletMaker.can_do_qr() else ''))
|
||||
msg = background_msg.format(can_qr='\nIf you have a special PDF template file, '
|
||||
'it can also make a pretty version of the same data.')
|
||||
|
||||
if await ux_show_story(msg) != 'y':
|
||||
return
|
||||
|
||||
@ -472,9 +472,6 @@ class PinAttempt:
|
||||
|
||||
def is_deltamode(self):
|
||||
# (mk4 only) are we operating w/ a slightly wrong PIN code?
|
||||
if version.mk_num < 4:
|
||||
return False
|
||||
|
||||
from trick_pins import TC_DELTA_MODE
|
||||
return bool(self.delay_required & TC_DELTA_MODE)
|
||||
|
||||
|
||||
@ -11,8 +11,8 @@ from uio import BytesIO
|
||||
from chains import taptweak, tapleaf_hash
|
||||
from sffile import SizerFile
|
||||
from sram2 import psbt_tmp256
|
||||
from multisig import MultisigWallet, disassemble_multisig_mn
|
||||
from miniscript import MiniScriptWallet
|
||||
from multisig import MultisigWallet, disassemble_multisig, disassemble_multisig_mn
|
||||
from exceptions import FatalPSBTIssue, FraudulentChangeOutput
|
||||
from serializations import ser_compact_size, deser_compact_size, hash160
|
||||
from serializations import CTxIn, CTxInWitness, CTxOut, ser_string
|
||||
@ -32,6 +32,8 @@ from public_constants import (
|
||||
TAPROOT_LEAF_TAPSCRIPT, TAPROOT_LEAF_MASK
|
||||
)
|
||||
|
||||
psbt_tmp256 = bytearray(256)
|
||||
|
||||
# PSBT proprietary keytype
|
||||
PSBT_PROPRIETARY = const(0xFC)
|
||||
|
||||
|
||||
@ -2,11 +2,10 @@
|
||||
#
|
||||
# qrs.py - QR Display related UX
|
||||
#
|
||||
import framebuf, math, uqr
|
||||
import framebuf, uqr
|
||||
from ux import UserInteraction, ux_wait_keyup, the_ux
|
||||
from utils import word_wrap
|
||||
from version import has_fatram
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
|
||||
|
||||
class QRDisplaySingle(UserInteraction):
|
||||
# Show a single QR code for (typically) a list of addresses, or a single value.
|
||||
|
||||
@ -275,10 +275,8 @@ async def show_words(words, prompt=None, escape=None, extra='', ephemeral=False)
|
||||
# user can skip quiz for ephemeral secrets
|
||||
msg += " There will be a test!"
|
||||
|
||||
|
||||
if version.has_fatram:
|
||||
escape = (escape or '') + '1'
|
||||
extra += 'Press (1) to view as QR Code. '
|
||||
escape = (escape or '') + '1'
|
||||
extra += 'Press (1) to view as QR Code. '
|
||||
|
||||
if extra:
|
||||
msg += '\n\n'
|
||||
@ -637,21 +635,9 @@ def clear_seed():
|
||||
# clear settings associated with this key, since it will be no more
|
||||
settings.blank()
|
||||
|
||||
if version.mk_num >= 4:
|
||||
callgate.fast_wipe(True)
|
||||
# NOT REACHED
|
||||
else:
|
||||
# save a blank secret (all zeros is a special case, detected by bootloader)
|
||||
nv = bytes(AE_SECRET_LEN)
|
||||
pa.change(new_secret=nv)
|
||||
callgate.fast_wipe(True)
|
||||
# NOT REACHED
|
||||
|
||||
if version.has_608:
|
||||
# wipe the long secret too
|
||||
nv = bytes(AE_LONG_SECRET_LEN)
|
||||
pa.ls_change(nv)
|
||||
|
||||
dis.busy_bar(False)
|
||||
dis.fullscreen('Reboot...')
|
||||
utime.sleep(1)
|
||||
|
||||
# security: need to reboot to really be sure to clear the secrets from main memory.
|
||||
|
||||
@ -56,11 +56,7 @@ async def test_secure_element():
|
||||
assert not get_is_bricked() # bricked already
|
||||
|
||||
# test right chips installed
|
||||
if version.has_fatram:
|
||||
assert version.has_608 # expect 608
|
||||
else:
|
||||
assert not version.has_608 # expect 508a
|
||||
assert version.hw_label == 'mk2'
|
||||
assert version.has_608 # expect 608
|
||||
|
||||
if ckcc.is_simulator(): return
|
||||
|
||||
@ -115,9 +111,6 @@ async def test_sd_active():
|
||||
|
||||
async def test_usb_light():
|
||||
# Mk4's new USB activity light (right by connector)
|
||||
|
||||
if version.mk_num < 4: return
|
||||
|
||||
from machine import Pin
|
||||
p = Pin('USB_ACTIVE', Pin.OUT)
|
||||
|
||||
@ -139,8 +132,6 @@ async def test_nfc():
|
||||
await NFCHandler.selftest()
|
||||
|
||||
async def test_psram():
|
||||
if not version.has_psram: return
|
||||
|
||||
from glob import PSRAM
|
||||
from ustruct import pack
|
||||
import ngu
|
||||
@ -168,54 +159,6 @@ async def test_psram():
|
||||
assert chk == rnd, "RB bad @ 0x%x" % pos
|
||||
dis.progress_bar_show((PSRAM.length + pos) / test_len)
|
||||
|
||||
async def test_sflash():
|
||||
if version.has_psram: return
|
||||
|
||||
dis.clear()
|
||||
dis.text(None, 18, 'Serial Flash')
|
||||
dis.show()
|
||||
|
||||
from sflash import SF
|
||||
from ustruct import pack
|
||||
import ngu
|
||||
|
||||
msize = 1024*1024
|
||||
SF.chip_erase()
|
||||
|
||||
for phase in [0, 1]:
|
||||
steps = 7*4
|
||||
for i in range(steps):
|
||||
dis.progress_bar(i/steps)
|
||||
dis.show()
|
||||
await sleep_ms(250)
|
||||
if not SF.is_busy(): break
|
||||
|
||||
assert not SF.is_busy() # "didn't finish"
|
||||
|
||||
# leave chip blank
|
||||
if phase == 1: break
|
||||
|
||||
|
||||
buf = bytearray(32)
|
||||
for addr in range(0, msize, 1024):
|
||||
SF.read(addr, buf)
|
||||
assert set(buf) == {255} # "not blank"
|
||||
|
||||
rnd = ngu.hash.sha256s(pack('I', addr))
|
||||
SF.write(addr, rnd)
|
||||
SF.wait_done()
|
||||
SF.read(addr, buf)
|
||||
assert buf == rnd # "write failed"
|
||||
|
||||
dis.progress_bar_show(addr/msize)
|
||||
|
||||
# check no aliasing, also right size part
|
||||
for addr in range(0, msize, 1024):
|
||||
expect = ngu.hash.sha256s(pack('I', addr))
|
||||
SF.read(addr, buf)
|
||||
assert buf == expect # "readback failed"
|
||||
|
||||
dis.progress_bar_show(addr/msize)
|
||||
|
||||
async def test_oled():
|
||||
# all on/off tests
|
||||
@ -306,7 +249,6 @@ async def start_selftest():
|
||||
await test_oled()
|
||||
await test_psram()
|
||||
await test_nfc()
|
||||
await test_sflash()
|
||||
await test_microsd()
|
||||
await test_numpad()
|
||||
await test_secure_element()
|
||||
|
||||
127
shared/sffile.py
127
shared/sffile.py
@ -9,29 +9,16 @@
|
||||
# - the offset is the file name
|
||||
# - (<Mk3) last 64k of memory reserved for settings
|
||||
#
|
||||
from uasyncio import sleep_ms
|
||||
from uio import BytesIO
|
||||
from uhashlib import sha256
|
||||
from version import has_psram
|
||||
|
||||
if not has_psram:
|
||||
# Use SPI flash chip
|
||||
from sflash import SF
|
||||
|
||||
# this code works on large "blocks" defined by the chip as 64k
|
||||
blksize = 65536
|
||||
# Use PSRAM chip
|
||||
from glob import PSRAM
|
||||
blksize = 4
|
||||
PADOUT = lambda n: n
|
||||
|
||||
def PADOUT(n):
|
||||
# rounds up
|
||||
return (n + blksize - 1) & ~(blksize-1)
|
||||
else:
|
||||
# Use PSRAM chip
|
||||
from glob import PSRAM
|
||||
blksize = 4
|
||||
PADOUT = lambda n: n
|
||||
|
||||
def ALIGN4(n):
|
||||
return n & ~0x3
|
||||
def ALIGN4(n):
|
||||
return n & ~0x3
|
||||
|
||||
class SFFile:
|
||||
def __init__(self, start, length=0, max_size=None, message=None, pre_erased=False):
|
||||
@ -49,10 +36,9 @@ class SFFile:
|
||||
self.readonly = False
|
||||
self.checksum = sha256()
|
||||
|
||||
if has_psram:
|
||||
# up to 3 bytes that haven't been written-out yet
|
||||
self.runt = bytearray()
|
||||
self._pos = 0
|
||||
# up to 3 bytes that haven't been written-out yet
|
||||
self.runt = bytearray()
|
||||
self._pos = 0
|
||||
else:
|
||||
# Read
|
||||
self.readonly = True
|
||||
@ -89,19 +75,7 @@ class SFFile:
|
||||
# must be used by caller before writing any bytes
|
||||
assert not self.readonly
|
||||
assert self.length == 0 # 'already wrote?'
|
||||
|
||||
if has_psram: return
|
||||
|
||||
for i in range(0, self.max_size, blksize):
|
||||
SF.block_erase(self.start + i)
|
||||
|
||||
if i and self.message:
|
||||
from glob import dis
|
||||
dis.progress_bar_show(i/self.max_size)
|
||||
|
||||
# expect block erase to take up to 2 seconds
|
||||
while SF.is_busy():
|
||||
await sleep_ms(50)
|
||||
return
|
||||
|
||||
def __enter__(self):
|
||||
if self.message:
|
||||
@ -118,12 +92,6 @@ class SFFile:
|
||||
|
||||
return False
|
||||
|
||||
def wait_writable(self):
|
||||
if has_psram: return
|
||||
|
||||
while SF.is_busy():
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
# PSRAM might leave a little behind
|
||||
if self.runt:
|
||||
@ -144,58 +112,24 @@ class SFFile:
|
||||
|
||||
left = len(b)
|
||||
|
||||
if has_psram:
|
||||
# Mk4: memory-mapped, but can only do word-aligned writes
|
||||
self.checksum.update(b)
|
||||
# Mk4: memory-mapped, but can only do word-aligned writes
|
||||
self.checksum.update(b)
|
||||
|
||||
self.runt.extend(b)
|
||||
here = ALIGN4(len(self.runt))
|
||||
if here:
|
||||
PSRAM.write(self.start + self._pos, self.runt[0:here])
|
||||
self._pos += here
|
||||
self.runt = self.runt[here:]
|
||||
self.runt.extend(b)
|
||||
here = ALIGN4(len(self.runt))
|
||||
if here:
|
||||
PSRAM.write(self.start + self._pos, self.runt[0:here])
|
||||
self._pos += here
|
||||
self.runt = self.runt[here:]
|
||||
|
||||
self.pos += left
|
||||
self.length = self.pos
|
||||
|
||||
self.pos += left
|
||||
self.length = self.pos
|
||||
if self.message:
|
||||
from glob import dis
|
||||
dis.progress_sofar(self.pos, self.length)
|
||||
|
||||
if self.message:
|
||||
from glob import dis
|
||||
dis.progress_sofar(self.pos, self.length)
|
||||
|
||||
return left
|
||||
|
||||
else:
|
||||
# must perform page-aligned (256) writes, but can start
|
||||
# anywhere in the page, and can write just one byte
|
||||
sofar = 0
|
||||
|
||||
while left:
|
||||
if (self.pos + sofar) % 256 != 0:
|
||||
# start is unaligned, do a partial write to align
|
||||
assert sofar == 0 #, (sofar, (self.pos+sofar)) # can only happen on first page
|
||||
runt = min(left, 256 - (self.pos % 256))
|
||||
here = memoryview(b)[0:runt]
|
||||
assert len(here) == runt
|
||||
else:
|
||||
# write full pages, or final runt
|
||||
here = memoryview(b)[sofar:sofar+256]
|
||||
assert 1 <= len(here) <= 256
|
||||
|
||||
self.wait_writable()
|
||||
|
||||
SF.write(self.start + self.pos + sofar, here)
|
||||
|
||||
left -= len(here)
|
||||
sofar += len(here)
|
||||
self.checksum.update(here)
|
||||
|
||||
assert left >= 0
|
||||
|
||||
assert sofar == len(b)
|
||||
self.pos += sofar
|
||||
self.length = self.pos
|
||||
return sofar
|
||||
return left
|
||||
|
||||
def read(self, ll=None):
|
||||
if ll == 0:
|
||||
@ -210,10 +144,7 @@ class SFFile:
|
||||
return b''
|
||||
|
||||
rv = bytearray(ll)
|
||||
if has_psram:
|
||||
PSRAM.read(self.start + self.pos, rv)
|
||||
else:
|
||||
SF.read(self.start + self.pos, rv)
|
||||
PSRAM.read(self.start + self.pos, rv)
|
||||
|
||||
self.pos += ll
|
||||
|
||||
@ -227,10 +158,7 @@ class SFFile:
|
||||
if actual <= 0:
|
||||
return 0
|
||||
|
||||
if has_psram:
|
||||
PSRAM.read(self.start + self.pos, b)
|
||||
else:
|
||||
SF.read(self.start + self.pos, b)
|
||||
PSRAM.read(self.start + self.pos, b)
|
||||
|
||||
self.pos += actual
|
||||
|
||||
@ -252,9 +180,6 @@ class SizerFile(SFFile):
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
return False
|
||||
|
||||
def wait_writable(self):
|
||||
return
|
||||
|
||||
def write(self, b):
|
||||
# immediate write, no buffering
|
||||
assert self.pos == self.length # "can only append"
|
||||
|
||||
137
shared/sflash.py
137
shared/sflash.py
@ -1,137 +0,0 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# sflash.py - SPI Flash on rev D and up boards. Simple serial SPI flash on SPI2 port.
|
||||
#
|
||||
# see also ../external/micropython/drivers/memory/spiflash.c
|
||||
# but not using that, because:
|
||||
# - not exposed as python objects
|
||||
# - it wants to waste 4k on a buffer
|
||||
#
|
||||
# Layout for project:
|
||||
# - 384k PSBT incoming (MAX_TXN_LEN)
|
||||
# - 384k PSBT outgoing (MAX_TXN_LEN)
|
||||
# - 128k nvram settings (32 slots of 4k each)
|
||||
#
|
||||
# During firmware updates, entire flash, starting at zero may be used.
|
||||
#
|
||||
import machine
|
||||
from version import mk_num
|
||||
|
||||
CMD_WRSR = const(0x01)
|
||||
CMD_WRITE = const(0x02)
|
||||
CMD_READ = const(0x03)
|
||||
CMD_FAST_READ = const(0x0b)
|
||||
CMD_RDSR = const(0x05)
|
||||
CMD_WREN = const(0x06)
|
||||
CMD_RDCR = const(0x35)
|
||||
CMD_RD_DEVID = const(0x9f)
|
||||
CMD_SEC_ERASE = const(0x20) # 4k unit, 40-200ms
|
||||
CMD_BLK_ERASE = const(0xd8) # 64k, 0.4 - 2s
|
||||
CMD_CHIP_ERASE = const(0xc7) # 1MB, 3.5 - 6s
|
||||
CMD_C4READ = const(0xeb)
|
||||
|
||||
class SPIFlash:
|
||||
# must write with this page size granulatity
|
||||
PAGE_SIZE = 256
|
||||
# must erase with one of these size granulatity!
|
||||
SECTOR_SIZE = 4096
|
||||
BLOCK_SIZE = 65536
|
||||
|
||||
def __init__(self):
|
||||
from machine import Pin
|
||||
|
||||
# chip can do 80Mhz, but very limited prescaler-only baudrate generation, so
|
||||
# sysclk/2 or /4 will happen depending on Mk3 vs. 4
|
||||
# - Mk4: 120Mhz => 60Mhz result (div 2)
|
||||
# - Mk3: 80Mhz => 40Mhz result (div 2)
|
||||
self.spi = machine.SPI(2, baudrate=80_000_000)
|
||||
self.cs = Pin('SF_CS', Pin.OUT)
|
||||
|
||||
def cmd(self, cmd, addr=None, complete=True, pad=False):
|
||||
if addr is not None:
|
||||
buf = bytes([cmd, (addr>>16) & 0xff, (addr >> 8) & 0xff, addr & 0xff])
|
||||
else:
|
||||
buf = bytes([cmd])
|
||||
|
||||
if pad:
|
||||
buf = buf + b'\0'
|
||||
|
||||
self.cs.low()
|
||||
self.spi.write(buf)
|
||||
if complete:
|
||||
self.cs.high()
|
||||
|
||||
def read(self, address, buf, cmd=CMD_FAST_READ):
|
||||
# random read (fast mode, because why wouldn't we?!)
|
||||
self.cmd(cmd, address, complete=False, pad=True)
|
||||
self.spi.readinto(buf)
|
||||
self.cs.high()
|
||||
|
||||
def write(self, address, buf):
|
||||
# 'page program', must already be erased
|
||||
assert 1 <= len(buf) <= 256 # "max 256"
|
||||
assert address & ~0xff == (address+len(buf)-1) & ~0xff # "page boundary"
|
||||
|
||||
self.cmd(CMD_WREN)
|
||||
self.cmd(CMD_WRITE, address, complete=False)
|
||||
self.spi.write(buf)
|
||||
self.cs.high()
|
||||
|
||||
def read_reg(self, cmd, length=3):
|
||||
# read register
|
||||
rv = bytearray(length)
|
||||
self.cmd(cmd, 0, complete=False)
|
||||
self.spi.readinto(rv)
|
||||
self.cs.high()
|
||||
|
||||
return rv
|
||||
|
||||
def is_busy(self):
|
||||
# return status of WIP = Write In Progress bit
|
||||
r = self.read_reg(CMD_RDSR, 1)
|
||||
return bool(r[0] & 0x01)
|
||||
|
||||
def wait_done(self):
|
||||
# wait until write done; could be fancier
|
||||
while 1:
|
||||
if not self.is_busy():
|
||||
return
|
||||
|
||||
def chip_erase(self):
|
||||
# can take up to 6 seconds, so poll is_busy()
|
||||
self.cmd(CMD_WREN)
|
||||
self.cmd(CMD_CHIP_ERASE)
|
||||
|
||||
def sector_erase(self, address):
|
||||
# erase 4k. 40-200ms delay; poll is_busy()
|
||||
assert address % 4096 == 0 # "not sector start"
|
||||
|
||||
self.cmd(CMD_WREN)
|
||||
self.cmd(CMD_SEC_ERASE, address)
|
||||
|
||||
def block_erase(self, address):
|
||||
# erase 64k at once
|
||||
assert address % 65536 == 0 # "not block start"
|
||||
self.cmd(CMD_WREN)
|
||||
self.cmd(CMD_BLK_ERASE, address)
|
||||
|
||||
def wipe_most(self):
|
||||
# erase everything except settings: takes 5 seconds at least
|
||||
from glob import dis
|
||||
|
||||
assert mk_num <= 3 # obsolete in mk4
|
||||
|
||||
from nvstore import SLOTS
|
||||
end = SLOTS[0]
|
||||
|
||||
for addr in range(0, end, self.BLOCK_SIZE):
|
||||
self.block_erase(addr)
|
||||
dis.progress_bar_show(addr/end)
|
||||
|
||||
while self.is_busy():
|
||||
pass
|
||||
|
||||
# singleton
|
||||
SF = SPIFlash()
|
||||
|
||||
# EOF
|
||||
@ -1,46 +0,0 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# sram2.py - Jam some larger, long-lived objects into the SRAM2 area, which isn't used enough.
|
||||
#
|
||||
# Cautions/Notes:
|
||||
# - mpy heap does not include SRAM2, so doing manual memory alloc here.
|
||||
# - top 8k reserved for bootloader, which will wipe it on each entry
|
||||
# - top page of that is specially marked to cause reset if any attempt to change
|
||||
# - 2k at bottom reserved for code in `flashbdev.c` to use as cache data for flash writing
|
||||
# - keep this file in sync with simulated version
|
||||
# - none of the above is true anymore on mk4
|
||||
#
|
||||
import uctypes, ckcc
|
||||
from version import mk_num
|
||||
|
||||
if mk_num < 4:
|
||||
# see stm32/COLDCARD/layout.ld where this is effectively defined
|
||||
SRAM2_START = const(0x10000800)
|
||||
SRAM2_LENGTH = const(0x5800)
|
||||
|
||||
_start = SRAM2_START
|
||||
|
||||
def _alloc(ln):
|
||||
global _start
|
||||
rv = uctypes.bytearray_at(_start, ln)
|
||||
_start += ln
|
||||
return rv
|
||||
else:
|
||||
# Mk4 has tons of memory, so just use it
|
||||
def _alloc(ln):
|
||||
return bytearray(ln)
|
||||
|
||||
nvstore_buf = _alloc(4096-32)
|
||||
display_buf = _alloc(1024)
|
||||
display2_buf = _alloc(1024)
|
||||
usb_buf = _alloc(2048+12) # 2060 @ 0x10001be0
|
||||
tmp_buf = _alloc(1024)
|
||||
psbt_tmp256 = _alloc(256)
|
||||
|
||||
if mk_num < 4:
|
||||
assert _start <= 0x10006000
|
||||
|
||||
# observed: about 22k on Mk2
|
||||
ckcc.stack_limit(SRAM2_LENGTH - (_start - SRAM2_START))
|
||||
|
||||
# EOF
|
||||
@ -37,8 +37,7 @@ class SSD1306(framebuf.FrameBuffer):
|
||||
|
||||
#self.buffer = bytearray(self.pages * self.width)
|
||||
|
||||
from sram2 import display_buf
|
||||
self.buffer = display_buf
|
||||
self.buffer = bytearray(1024)
|
||||
assert len(self.buffer) == self.pages * self.width
|
||||
|
||||
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
||||
|
||||
152
shared/usb.py
152
shared/usb.py
@ -10,7 +10,7 @@ from public_constants import STXN_FLAGS_MASK
|
||||
from ustruct import pack, unpack_from
|
||||
from ckcc import watchpoint, is_simulator
|
||||
from utils import problem_file_line, call_later_ms
|
||||
from version import has_fatram, is_devmode, has_psram, MAX_TXN_LEN, MAX_UPLOAD_LEN
|
||||
from version import is_devmode, MAX_TXN_LEN, MAX_UPLOAD_LEN
|
||||
from exceptions import FramingError, CCBusyError, HSMDenied, HSMCMDDisabled
|
||||
|
||||
# Unofficial, unpermissioned... numbers
|
||||
@ -90,7 +90,7 @@ def enable_usb():
|
||||
else:
|
||||
# subclass, protocol, max packet length, polling interval, report descriptor
|
||||
hid_info = (0x0, 0x0, 64, 5, hid_descp)
|
||||
classes = 'VCP+HID' if not has_psram else 'VCP+MSC+HID'
|
||||
classes = 'VCP+MSC+HID'
|
||||
pyb.usb_mode(classes, vid=COINKITE_VID, pid=CKCC_PID, hid=hid_info)
|
||||
|
||||
global handler
|
||||
@ -129,8 +129,7 @@ class USBHandler:
|
||||
# handle simulator
|
||||
self.blockable = getattr(self.dev, 'pipe', self.dev)
|
||||
|
||||
from sram2 import usb_buf
|
||||
self.msg = usb_buf
|
||||
self.msg = bytearray(2048+12)
|
||||
assert len(self.msg) == MAX_MSG_LEN
|
||||
|
||||
self.encrypted_req = False
|
||||
@ -586,66 +585,64 @@ class USBHandler:
|
||||
if cmd == 'bagi':
|
||||
return self.handle_bag_number(args)
|
||||
|
||||
if has_fatram:
|
||||
# HSM and user-related features only supported on larger-memory Mk3
|
||||
# HSM and user-related features only supported on larger-memory Mk3
|
||||
|
||||
if cmd == 'hsms':
|
||||
# HSM mode "start" -- requires user approval
|
||||
if args:
|
||||
file_len, file_sha = unpack_from('<I32s', args)
|
||||
if file_sha != self.file_checksum.digest():
|
||||
return b'err_Checksum'
|
||||
assert 2 <= file_len <= (200*1000), "badlen"
|
||||
else:
|
||||
file_len = 0
|
||||
if cmd == 'hsms':
|
||||
# HSM mode "start" -- requires user approval
|
||||
if args:
|
||||
file_len, file_sha = unpack_from('<I32s', args)
|
||||
if file_sha != self.file_checksum.digest():
|
||||
return b'err_Checksum'
|
||||
assert 2 <= file_len <= (200*1000), "badlen"
|
||||
else:
|
||||
file_len = 0
|
||||
|
||||
# Start an UX interaction but return (mostly) immediately here
|
||||
from hsm_ux import start_hsm_approval
|
||||
await start_hsm_approval(sf_len=file_len, usb_mode=True)
|
||||
# Start an UX interaction but return (mostly) immediately here
|
||||
from hsm_ux import start_hsm_approval
|
||||
await start_hsm_approval(sf_len=file_len, usb_mode=True)
|
||||
|
||||
return None
|
||||
|
||||
if cmd == 'hsts':
|
||||
# can always query HSM mode
|
||||
from hsm import hsm_status_report
|
||||
import ujson
|
||||
return b'asci' + ujson.dumps(hsm_status_report())
|
||||
|
||||
if cmd == 'gslr':
|
||||
# get the value held in the Storage Locker
|
||||
assert hsm_active, 'need hsm'
|
||||
return b'biny' + hsm_active.fetch_storage_locker()
|
||||
|
||||
# User Mgmt
|
||||
if cmd == 'nwur': # new user
|
||||
from users import Users
|
||||
auth_mode, ul, sl = unpack_from('<BBB', args)
|
||||
username = bytes(args[3:3+ul]).decode('ascii')
|
||||
secret = bytes(args[3+ul:3+ul+sl])
|
||||
|
||||
return b'asci' + Users.create(username, auth_mode, secret).encode('ascii')
|
||||
|
||||
if cmd == 'rmur': # delete user
|
||||
from users import Users
|
||||
ul, = unpack_from('<B', args)
|
||||
username = bytes(args[1:1+ul]).decode('ascii')
|
||||
|
||||
return Users.delete(username)
|
||||
|
||||
if cmd == 'user': # auth user (HSM mode)
|
||||
from users import Users
|
||||
totp_time, ul, tl = unpack_from('<IBB', args)
|
||||
username = bytes(args[6:6+ul]).decode('ascii')
|
||||
token = bytes(args[6+ul:6+ul+tl])
|
||||
|
||||
if hsm_active:
|
||||
# just queues these details, can't be checked until PSBT on-hand
|
||||
hsm_active.usb_auth_user(username, token, totp_time)
|
||||
return None
|
||||
|
||||
if cmd == 'hsts':
|
||||
# can always query HSM mode
|
||||
from hsm import hsm_status_report
|
||||
import ujson
|
||||
return b'asci' + ujson.dumps(hsm_status_report())
|
||||
|
||||
if cmd == 'gslr':
|
||||
# get the value held in the Storage Locker
|
||||
assert hsm_active, 'need hsm'
|
||||
return b'biny' + hsm_active.fetch_storage_locker()
|
||||
|
||||
|
||||
# User Mgmt
|
||||
if cmd == 'nwur': # new user
|
||||
from users import Users
|
||||
auth_mode, ul, sl = unpack_from('<BBB', args)
|
||||
username = bytes(args[3:3+ul]).decode('ascii')
|
||||
secret = bytes(args[3+ul:3+ul+sl])
|
||||
|
||||
return b'asci' + Users.create(username, auth_mode, secret).encode('ascii')
|
||||
|
||||
if cmd == 'rmur': # delete user
|
||||
from users import Users
|
||||
ul, = unpack_from('<B', args)
|
||||
username = bytes(args[1:1+ul]).decode('ascii')
|
||||
|
||||
return Users.delete(username)
|
||||
|
||||
if cmd == 'user': # auth user (HSM mode)
|
||||
from users import Users
|
||||
totp_time, ul, tl = unpack_from('<IBB', args)
|
||||
username = bytes(args[6:6+ul]).decode('ascii')
|
||||
token = bytes(args[6+ul:6+ul+tl])
|
||||
|
||||
if hsm_active:
|
||||
# just queues these details, can't be checked until PSBT on-hand
|
||||
hsm_active.usb_auth_user(username, token, totp_time)
|
||||
return None
|
||||
else:
|
||||
# dryrun/testing purposes: validate only, doesn't unlock nothing
|
||||
return b'asci' + Users.auth_okay(username, token, totp_time).encode('ascii')
|
||||
else:
|
||||
# dryrun/testing purposes: validate only, doesn't unlock nothing
|
||||
return b'asci' + Users.auth_okay(username, token, totp_time).encode('ascii')
|
||||
|
||||
#print("USB garbage: %s +[%d]" % (cmd, len(args)))
|
||||
|
||||
@ -736,23 +733,16 @@ class USBHandler:
|
||||
buf = memoryview(resp)[4:]
|
||||
|
||||
pos = (MAX_TXN_LEN * file_number) + offset
|
||||
|
||||
if has_psram:
|
||||
from glob import PSRAM
|
||||
PSRAM.read(pos, buf)
|
||||
else:
|
||||
from sflash import SF
|
||||
SF.read(pos, buf)
|
||||
|
||||
from glob import PSRAM
|
||||
PSRAM.read(pos, buf)
|
||||
|
||||
self.file_checksum.update(buf)
|
||||
|
||||
return resp
|
||||
|
||||
async def handle_upload(self, offset, total_size, data):
|
||||
if has_psram:
|
||||
from glob import PSRAM
|
||||
else:
|
||||
from sflash import SF
|
||||
from glob import PSRAM
|
||||
from glob import dis, hsm_active
|
||||
from utils import check_firmware_hdr
|
||||
from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE, FW_HEADER_MAGIC
|
||||
@ -775,15 +765,6 @@ class USBHandler:
|
||||
if pos % 4096 == 0:
|
||||
dis.fullscreen("Receiving...", offset/total_size)
|
||||
|
||||
if not has_psram:
|
||||
# erase here
|
||||
SF.sector_erase(pos)
|
||||
|
||||
# expect 10-22 ms delay here
|
||||
await sleep_ms(12)
|
||||
while SF.is_busy():
|
||||
await sleep_ms(2)
|
||||
|
||||
# write up to 256 bytes
|
||||
here = data[pos-offset:pos-offset+256]
|
||||
self.file_checksum.update(here)
|
||||
@ -817,15 +798,8 @@ class USBHandler:
|
||||
# pretend we wrote it, so ckcc-protocol or whatever gives normal feedback
|
||||
return offset
|
||||
|
||||
# write to SPI Flash / PSRAM
|
||||
if has_psram:
|
||||
PSRAM.write(pos, here)
|
||||
else:
|
||||
SF.write(pos, here)
|
||||
|
||||
# full page write: 0.6 to 3ms
|
||||
while SF.is_busy():
|
||||
await sleep_ms(1)
|
||||
# write to PSRAM
|
||||
PSRAM.write(pos, here)
|
||||
|
||||
if offset+len(data) >= total_size and not hsm_active:
|
||||
# probably done
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
# items imported here may be useful to EVAL and EXEC commands, which test cases depend on.
|
||||
import uio, sys, version, nvstore, glob, callgate
|
||||
try:
|
||||
from sflash import SF
|
||||
except: pass
|
||||
try:
|
||||
import sim_display
|
||||
except: pass
|
||||
|
||||
@ -392,17 +392,11 @@ def clean_shutdown(style=0):
|
||||
settings.save_if_dirty()
|
||||
|
||||
try:
|
||||
from glob import dis
|
||||
from glob import dis, NFC
|
||||
dis.fullscreen("Cleanup...")
|
||||
|
||||
if not version.has_psram:
|
||||
from sflash import SF
|
||||
SF.wipe_most()
|
||||
else:
|
||||
from glob import NFC
|
||||
|
||||
if NFC:
|
||||
uasyncio.run(NFC.wipe(True))
|
||||
if NFC:
|
||||
uasyncio.run(NFC.wipe(True))
|
||||
|
||||
except: pass
|
||||
|
||||
|
||||
@ -21,12 +21,12 @@ def decode_firmware_header(hdr):
|
||||
|
||||
def get_fw_header():
|
||||
# located in our own flash
|
||||
from sigheader import FLASH_HEADER_BASE, FLASH_HEADER_BASE_MK4, FW_HEADER_SIZE
|
||||
from sigheader import FLASH_HEADER_BASE_MK4, FW_HEADER_SIZE
|
||||
import uctypes
|
||||
|
||||
global mk_num
|
||||
|
||||
return uctypes.bytes_at(FLASH_HEADER_BASE_MK4 if mk_num == 4 else FLASH_HEADER_BASE,
|
||||
return uctypes.bytes_at(FLASH_HEADER_BASE_MK4,
|
||||
FW_HEADER_SIZE)
|
||||
|
||||
def get_mpy_version():
|
||||
@ -58,37 +58,8 @@ def nfc_presence_check():
|
||||
|
||||
def get_is_devmode():
|
||||
# what firmware signing key did we boot with? are we in dev mode?
|
||||
|
||||
if mk_num == 4:
|
||||
# mk4: are we built differently?
|
||||
import ckcc
|
||||
return ckcc.is_debug_build()
|
||||
|
||||
from sigheader import RAM_HEADER_BASE, FWH_PK_NUM_OFFSET
|
||||
import stm
|
||||
|
||||
# Important? Use the RAM version of this, not flash version!
|
||||
kn = stm.mem32[RAM_HEADER_BASE + FWH_PK_NUM_OFFSET]
|
||||
|
||||
# For now, all keys are "production" except number zero, which will be made public
|
||||
# - some other keys may be de-authorized and so on in the future
|
||||
is_devmode = (kn == 0)
|
||||
|
||||
return is_devmode
|
||||
|
||||
|
||||
def is_fresh_version():
|
||||
# Did we just boot into a new firmware for the first time?
|
||||
|
||||
# - mk4+ does not use this approach, light will be solid green during upgrade
|
||||
if mk_num >= 4: return False
|
||||
|
||||
from sigheader import RAM_BOOT_FLAGS, RBF_FRESH_VERSION
|
||||
import stm
|
||||
|
||||
flags = stm.mem32[RAM_BOOT_FLAGS]
|
||||
|
||||
return bool(flags & RBF_FRESH_VERSION)
|
||||
import ckcc
|
||||
return ckcc.is_debug_build()
|
||||
|
||||
|
||||
def serial_number():
|
||||
@ -103,49 +74,25 @@ def serial_number():
|
||||
|
||||
def probe_system():
|
||||
# run-once code to determine what hardware we are running on
|
||||
global hw_label, has_608, has_fatram, is_factory_mode, is_devmode, has_psram
|
||||
global has_se2, mk_num, has_nfc, is_edge
|
||||
global hw_label, has_608, is_factory_mode
|
||||
global mk_num, has_nfc, is_devmode, is_edge
|
||||
global MAX_UPLOAD_LEN, MAX_TXN_LEN
|
||||
|
||||
from sigheader import RAM_BOOT_FLAGS, RBF_FACTORY_MODE
|
||||
import ckcc, callgate, stm
|
||||
from machine import Pin
|
||||
import ckcc, callgate
|
||||
|
||||
# NOTE: mk1 not supported anymore.
|
||||
# PA10 is pulled-down in Mark2, open in previous revs
|
||||
#mark2 = (Pin('MARK2', Pin.IN, pull=Pin.PULL_UP).value() == 0)
|
||||
|
||||
hw_label = 'mk2'
|
||||
has_fatram = False
|
||||
has_psram = False
|
||||
hw_label = 'mk4'
|
||||
has_608 = True
|
||||
has_se2 = False
|
||||
has_nfc = False # hardware present; they might not be using it
|
||||
mk_num = 2
|
||||
has_nfc = nfc_presence_check() # hardware present; they might not be using it
|
||||
mk_num = 4
|
||||
|
||||
cpuid = ckcc.get_cpu_id()
|
||||
if cpuid == 0x461: # STM32L496RG6
|
||||
hw_label = 'mk3'
|
||||
has_fatram = True
|
||||
mk_num = 3
|
||||
elif cpuid == 0x470: # STM32L4S5VI
|
||||
hw_label = 'mk4'
|
||||
has_fatram = True
|
||||
has_psram = True
|
||||
has_se2 = True
|
||||
mk_num = 4
|
||||
has_nfc = nfc_presence_check()
|
||||
else:
|
||||
# mark 2
|
||||
has_608 = callgate.has_608()
|
||||
assert cpuid == 0x470 # STM32L4S5VI
|
||||
|
||||
# Boot loader needs to tell us stuff about how we were booted, sometimes:
|
||||
# - did we just install a new version, for example (obsolete in mk4)
|
||||
# - are we running in "factory mode" with flash un-secured?
|
||||
if mk_num < 4:
|
||||
is_factory_mode = bool(stm.mem32[RAM_BOOT_FLAGS] & RBF_FACTORY_MODE)
|
||||
else:
|
||||
is_factory_mode = callgate.get_factory_mode()
|
||||
is_factory_mode = callgate.get_factory_mode()
|
||||
|
||||
bn = callgate.get_bag_number()
|
||||
if bn:
|
||||
@ -159,10 +106,9 @@ def probe_system():
|
||||
is_edge = (get_mpy_version()[1][-1] == 'X')
|
||||
|
||||
# increase size limits for mk4
|
||||
if has_psram:
|
||||
from public_constants import MAX_TXN_LEN_MK4, MAX_UPLOAD_LEN_MK4
|
||||
MAX_UPLOAD_LEN = MAX_UPLOAD_LEN_MK4
|
||||
MAX_TXN_LEN = MAX_TXN_LEN_MK4
|
||||
from public_constants import MAX_TXN_LEN_MK4, MAX_UPLOAD_LEN_MK4
|
||||
MAX_UPLOAD_LEN = MAX_UPLOAD_LEN_MK4
|
||||
MAX_TXN_LEN = MAX_TXN_LEN_MK4
|
||||
|
||||
probe_system()
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ freeze_as_mpy('', [
|
||||
'mock.py',
|
||||
'os.py',
|
||||
'pyb.py',
|
||||
'sflash.py',
|
||||
'sim_mk4.py',
|
||||
'sim_nfc.py',
|
||||
'sim_psram.py',
|
||||
@ -16,7 +15,6 @@ freeze_as_mpy('', [
|
||||
'sim_se2.py',
|
||||
'sim_settings.py',
|
||||
'sim_vdisk.py',
|
||||
'sram2.py',
|
||||
'ssd1306.py',
|
||||
'stm.py',
|
||||
'struct.py',
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
# replacement for serial flash stuff, just enough to pass selftest
|
||||
#
|
||||
# see real deal at ../shared/sflash.py
|
||||
from version import mk_num
|
||||
|
||||
if mk_num < 4:
|
||||
_SIZE = 1024*1024
|
||||
|
||||
class SPIFlash:
|
||||
PAGE_SIZE = 256
|
||||
SECTOR_SIZE = 4096
|
||||
BLOCK_SIZE = 65536
|
||||
|
||||
array = bytearray(_SIZE)
|
||||
|
||||
def read(self, address, buf, **kw):
|
||||
# random read
|
||||
buf[0:len(buf)] = self.array[address:address+len(buf)]
|
||||
|
||||
def write(self, address, buf):
|
||||
# 'page program', must already be erased
|
||||
assert 1 <= len(buf) <= 256, "max 256"
|
||||
assert address & ~0xff == (address+len(buf)-1) & ~0xff, \
|
||||
"page aligned only: addr=0x%x len=0x%x" % (address, len(buf))
|
||||
|
||||
#self.array[address:address+len(buf)] = buf
|
||||
# emulate flash memory: can only go from 1=>0
|
||||
for i in range(len(buf)):
|
||||
self.array[address+i] &= buf[i]
|
||||
|
||||
def is_busy(self):
|
||||
# always instant
|
||||
return False
|
||||
|
||||
def wait_done(self):
|
||||
return
|
||||
|
||||
def chip_erase(self):
|
||||
for i in range(_SIZE):
|
||||
self.array[i] = 0xff
|
||||
|
||||
def sector_erase(self, address):
|
||||
for i in range(self.SECTOR_SIZE):
|
||||
self.array[address+i] = 0xff
|
||||
|
||||
def block_erase(self, address):
|
||||
# erase 64k at once
|
||||
assert address % 65536 == 0, "not block start"
|
||||
for i in range(self.BLOCK_SIZE):
|
||||
self.array[address+i] = 0xff
|
||||
|
||||
def wipe_most(self):
|
||||
# XXX ux here is bad
|
||||
# erase everything except settings: takes 5 seconds at least
|
||||
from nvstore import SLOTS
|
||||
end = SLOTS[0]
|
||||
|
||||
from main import dis
|
||||
dis.fullscreen("Cleanup...")
|
||||
|
||||
for addr in range(0, end, self.BLOCK_SIZE):
|
||||
self.block_erase(addr)
|
||||
dis.progress_bar_show(addr/end)
|
||||
|
||||
while self.is_busy():
|
||||
pass
|
||||
|
||||
SF = SPIFlash()
|
||||
|
||||
# EOF
|
||||
@ -1,8 +0,0 @@
|
||||
# see shared/sram2.py
|
||||
|
||||
nvstore_buf = bytearray(4096-32)
|
||||
display_buf = bytearray(1024)
|
||||
display2_buf = bytearray(1024)
|
||||
usb_buf = bytearray(2048+12)
|
||||
tmp_buf = bytearray(1024)
|
||||
psbt_tmp256 = bytearray(256)
|
||||
Loading…
Reference in New Issue
Block a user