async def handle_upload(self, offset, total_size, data): from main import dis, sf, hsm_active from utils import check_firmware_hdr from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE # maintain a running SHA256 over what's received if offset == 0: self.file_checksum = tcc.sha256() assert offset % 256 == 0, 'alignment' assert offset + len(data) <= total_size <= MAX_UPLOAD_LEN, 'long' if hsm_active: # additional restrictions in HSM mode assert offset + len(data) <= total_size <= MAX_TXN_LEN, 'psbt' if offset == 0: assert data[0:5] == b'psbt\xff', 'psbt' for pos in range(offset, offset + len(data), 256): if pos % 4096 == 0: # erase here dis.fullscreen("Receiving...", offset / total_size) sf.sector_erase(pos) while sf.is_busy(): await sleep_ms(10) # write up to 256 bytes here = data[pos - offset:pos - offset + 256] self.file_checksum.update(here) # Very special case for firmware upgrades: intercept and modify # header contents on the fly, and also fail faster if wouldn't work # on this specific hardware. # - workaround: ckcc-protocol upgrade process understates the file # length and appends hdr, but that's kinda a bug, so support both if (pos == (FW_HEADER_OFFSET & ~255) or pos == (total_size - FW_HEADER_SIZE) or pos == total_size): prob = check_firmware_hdr(memoryview(here)[-128:], None, bad_magic_ok=True) if prob: raise ValueError(prob) sf.write(pos, here) # full page write: 0.6 to 3ms while sf.is_busy(): await sleep_ms(1) if offset + len(data) >= total_size and not hsm_active: # probably done dis.progress_bar_show(1.0) ux.restore_menu() return offset
async def test_7z(): # test full 7z round-trip # Altho cleartext mode is not for real, if the code is written, I must test it. from backups import write_complete_backup, restore_complete_doit from sffile import SFFile import tcc from main import settings, sf, numpad today = tcc.random.uniform(1000000) import machine machine.reset = lambda: None for chain in ['BTC', 'XTN']: for words in ([], ['abc', 'def']): settings.set('check', today) settings.set('chain', chain) ll, sha = await write_complete_backup(words, None, True) result = SFFile(0, ll).read() if words: #open('debug.7z', 'wb').write(result) assert ll > 800 assert len(sha) == 32 assert result[0:6] == b"7z\xbc\xaf'\x1c" assert tcc.sha256(result).digest() == sha assert len(set(result)) >= 240 # encrypted else: sr = str(result, 'ascii') print("Backup contents:\n" + sr) assert sr[0] == '#', result assert 'Coldcard' in sr assert len(set(sr)) < 100 # cleartext, english assert ('chain = "%s"' % chain) in result # test restore # - cant wipe flash, since the backup file is there # - cant wipe all settings becuase PIN and stuff is simulated there del settings.current['check'] with SFFile(0, ll) as fd: numpad.inject('y') # for 'success' message await restore_complete_doit(fd, words) assert settings.get('check') == today, \ (settings.get('check'), '!=', today) assert settings.get('chain') == chain, \ (settings.get('chain'), '!=', chain) today += 3 import ux ux.restore_menu()
def pop_menu(self): # drop them back into menu system, but try not to affect # menu position. self.ux_done = True from actions import goto_top_menu from ux import the_ux, restore_menu if the_ux.top_of_stack() == self: empty = the_ux.pop() if empty: goto_top_menu() restore_menu()
async def handle_upload(self, offset, total_size, data): from main import dis, sf # maintain a running SHA256 over what's received if offset == 0: self.file_checksum = tcc.sha256() assert offset % 256 == 0, 'alignment' assert offset + len(data) <= total_size <= MAX_UPLOAD_LEN, 'long' rb = bytearray(256) for pos in range(offset, offset + len(data), 256): if pos % 4096 == 0: # erase here sf.sector_erase(pos) dis.fullscreen("Receiving...") dis.progress_bar_show(offset / total_size) while sf.is_busy(): await sleep_ms(10) # write up to 256 bytes here = data[pos - offset:pos - offset + 256] sf.write(pos, here) # full page write: 0.6 to 3ms while sf.is_busy(): await sleep_ms(1) # use actual read back for verify sf.read(pos, rb) self.file_checksum.update(rb[0:len(here)]) if offset + len(data) >= total_size: # probably done dis.progress_bar_show(1.0) ux.restore_menu() return offset
async def test_7z(): # test full 7z round-trip # Altho cleartext mode is not for real, if the code is written, I must test it. from backups import write_complete_backup, restore_complete_doit from sffile import SFFile import ngu, version, uos from glob import numpad from pincodes import pa from nvstore import settings if version.has_fatram: import hsm had_policy = hsm.hsm_policy_available() else: had_policy = False today = ngu.random.uniform(1000000) import machine machine.reset = lambda: None for chain in ['BTC', 'XTN']: for words in ([], ['abc', 'def']): settings.set('check', today) settings.set('chain', chain) if version.has_608: ls = b'%416d' % today pa.ls_change(ls) ll, sha = await write_complete_backup(words, None, True) result = SFFile(0, ll).read() if words: #open('debug.7z', 'wb').write(result) assert ll > 800 assert len(sha) == 32 assert result[0:6] == b"7z\xbc\xaf'\x1c" assert ngu.hash.sha256s(result) == sha assert len(set(result)) >= 240 # encrypted else: sr = str(result, 'ascii') print("Backup contents:\n" + sr) assert sr[0] == '#', result assert 'Coldcard' in sr assert len(set(sr)) < 100 # cleartext, english assert ('chain = "%s"' % chain) in result # test restore # - cant wipe flash, since the backup file is there # - cant wipe all settings becuase PIN and stuff is simulated there del settings.current['check'] if had_policy: from hsm import POLICY_FNAME uos.unlink(POLICY_FNAME) assert not hsm.hsm_policy_available() with SFFile(0, ll) as fd: numpad.inject('y') # for 'success' message await restore_complete_doit(fd, words) assert settings.get('check') == today, \ (settings.get('check'), '!=', today) assert settings.get('chain') == chain, \ (settings.get('chain'), '!=', chain) if version.has_608: assert pa.ls_fetch() == ls if had_policy: assert had_policy == hsm.hsm_policy_available() today += 3 import ux ux.restore_menu()
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # quickly clear all multisig wallets installed from nvstore import settings from ux import restore_menu if settings.get('multisig'): del settings.current['multisig'] settings.save() print("cleared multisigs") restore_menu()
async def handle_upload(self, offset, total_size, data): from sflash import SF from glob import dis, hsm_active from utils import check_firmware_hdr from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE, FW_HEADER_MAGIC # maintain a running SHA256 over what's received if offset == 0: self.file_checksum = sha256() self.is_fw_upgrade = False assert offset % 256 == 0, 'alignment' assert offset + len(data) <= total_size <= MAX_UPLOAD_LEN, 'long' if hsm_active: # additional restrictions in HSM mode assert offset + len(data) <= total_size <= MAX_TXN_LEN, 'psbt' if offset == 0: assert data[0:5] == b'psbt\xff', 'psbt' for pos in range(offset, offset + len(data), 256): if pos % 4096 == 0: # erase here dis.fullscreen("Receiving...", offset / total_size) 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) # Very special case for firmware upgrades: intercept and modify # header contents on the fly, and also fail faster if wouldn't work # on this specific hardware. # - workaround: ckcc-protocol upgrade process understates the file # length and appends hdr, but that's kinda a bug, so support both is_trailer = (pos == (total_size - FW_HEADER_SIZE) or pos == total_size) if pos == (FW_HEADER_OFFSET & ~255): hdr = memoryview(here)[-128:] magic, = unpack_from('<I', hdr[0:4]) if magic == FW_HEADER_MAGIC: self.is_fw_upgrade = bytes(hdr) prob = check_firmware_hdr(hdr, total_size) if prob: raise ValueError(prob) if is_trailer and self.is_fw_upgrade: # expect the trailer to exactly match the original one assert len(here) == 128 # == FW_HEADER_SIZE hdr = memoryview(here)[-128:] assert hdr == self.is_fw_upgrade # indicates hacking # but don't write it, instead offer user a chance to abort from auth import authorize_upgrade authorize_upgrade(self.is_fw_upgrade, pos) # pretend we wrote it, so ckcc-protocol or whatever gives normal feedback return offset SF.write(pos, here) # full page write: 0.6 to 3ms while SF.is_busy(): await sleep_ms(1) if offset + len(data) >= total_size and not hsm_active: # probably done dis.progress_bar_show(1.0) ux.restore_menu() return offset