def make_msg(start): msg = '' if start == 0: msg = "Press 1 to save to MicroSD." msg += '\n\n' msg += "Addresses %d..%d:\n\n" % (start, start + n - 1) addrs = [] chain = chains.current_chain() dis.fullscreen('Loading...') with stash.SensitiveValues() as sv: for idx in range(start, start + n): subpath = path.format(account=0, change=0, idx=idx) node = sv.derive_path(subpath, register=False) addr = chain.address(node, addr_fmt) addr1 = addr[:16] addr2 = addr[16:] addrs.append(addr) msg += "%s =>\n %s\n %s\n\n" % (subpath, addr1, addr2) dis.progress_bar_show(idx / n) stash.blank_object(node) msg += "Press 9 to see next group.\nPress 7 to see prev. group." return msg, addrs
async def write_text_file(fname_pattern, body, title, total_parts=72): # - total_parts does need not be precise from common import dis, pa, settings from files import CardSlot, CardMissingError from actions import needs_microsd # choose a filename try: with CardSlot() as card: fname, nice = card.pick_filename(fname_pattern) # do actual write with open(fname, 'wb') as fd: for idx, part in enumerate(body): dis.progress_bar_show(idx / total_parts) fd.write(part.encode()) except CardMissingError: await needs_microsd() return except Exception as e: await ux_show_story('Failed to write!\n\n\n' + str(e)) return msg = '''%s file written:\n\n%s''' % (title, nice) await ux_show_story(msg)
async def choose_first_address(*a): # Choose from a truncated list of index 0 common addresses, remember # the last address the user selected and use it as the default from common import settings, dis chain = chains.current_chain() dis.fullscreen('Loading...') with stash.SensitiveValues() as sv: def truncate_address(addr): # Truncates address to width of screen, replacing middle chars middle = "-" leftover = SCREEN_CHAR_WIDTH - len(middle) start = addr[0:(leftover + 1) // 2] end = addr[len(addr) - (leftover // 2):] return start + middle + end # Create list of choices (address_index_0, path, addr_fmt) choices = [] for name, path, addr_fmt in chains.CommonDerivations: if '{coin_type}' in path: path = path.replace('{coin_type}', str(chain.b44_cointype)) subpath = path.format(account=0, change=0, idx=0) node = sv.derive_path(subpath, register=False) address = chain.address(node, addr_fmt) choices.append((truncate_address(address), path, addr_fmt)) dis.progress_bar_show(len(choices) / len(chains.CommonDerivations)) stash.blank_object(node) picked = None async def clicked(_1, _2, item): if picked is None: picked = item.arg the_ux.pop() items = [ MenuItem(address, f=clicked, arg=i) for i, (address, path, addr_fmt) in enumerate(choices) ] menu = MenuSystem(items, title='Address List') menu.goto_idx(settings.get('axi', 0)) the_ux.push(menu) await menu.interact() if picked is None: return None # update last clicked address settings.set('axi', picked) address, path, addr_fmt = choices[picked] return (path, addr_fmt)
def __init__(self, text, subpath, addr_fmt, approved_cb=None): super().__init__() self.text = text self.subpath = subpath self.approved_cb = approved_cb from common import dis dis.fullscreen('Wait...') with stash.SensitiveValues() as sv: node = sv.derive_path(subpath) self.address = sv.chain.address(node, addr_fmt) dis.progress_bar_show(1)
def wipe_most(self): # erase everything except settings: takes 5 seconds at least from nvstore import SLOTS end = SLOTS[0] from common 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
async def erase(self): # must be used by caller before writing any bytes assert not self.readonly assert self.length == 0 # 'already wrote?' for i in range(0, self.max_size, blksize): self.sf.block_erase(self.start + i) if i and self.message: from common import dis dis.progress_bar_show(i / self.max_size) # expect block erase to take up to 2 seconds while self.sf.is_busy(): await sleep_ms(50)
def wipe_microsd_card(): # Erase and re-format SD card. Not secure erase, because that is too slow. import callgate import pyb from common import dis try: os.umount('/sd') except: pass sd = pyb.SDCard() assert sd if not sd.present(): return # power cycle so card details (like size) are re-read from current card sd.power(0) sd.power(1) dis.fullscreen('Part Erase...') cutoff = 1024 # arbitrary blk = bytearray(512) for bnum in range(cutoff): callgate.fill_random(blk) sd.writeblocks(bnum, blk) dis.progress_bar_show(bnum / cutoff) dis.fullscreen('Formating...') # remount, with newfs option os.mount(sd, '/sd', readonly=0, mkfs=1) # done, cleanup os.umount('/sd') # important: turn off power sd = pyb.SDCard() sd.power(0)
async def make_address_summary_file(path, addr_fmt, fname_pattern='addresses.txt'): # write addresses into a text file on the microSD from common import dis from files import CardSlot, CardMissingError from actions import needs_microsd # simple: always set number of addresses. # - takes 60 seconds, to write 250 addresses on actual hardware count = 250 dis.fullscreen('Saving 0-%d' % count) # generator function body = generate_address_csv(path, addr_fmt, count) # pick filename and write try: with CardSlot() as card: fname, nice = card.pick_filename(fname_pattern) # do actual write with open(fname, 'wb') as fd: for idx, part in enumerate(body): fd.write(part.encode()) if idx % 5 == 0: dis.progress_bar_show(idx / count) except CardMissingError: await needs_microsd() return except Exception as e: await ux_show_story('Failed to write!\n\n\n' + str(e)) return msg = '''Address summary file written:\n\n%s''' % nice await ux_show_story(msg)
def read(self, ll=None): if ll == 0: return b'' elif ll is None: ll = self.length - self.pos else: ll = min(ll, self.length - self.pos) if ll <= 0: # at EOF return b'' rv = bytearray(ll) self.sf.read(self.start + self.pos, rv) self.pos += ll if self.message and ll > 1: from common import dis dis.progress_bar_show(self.pos / self.length) # altho tempting to return a bytearray (which we already have) many # callers expect return to be bytes and have those methods, like "find" return bytes(rv)
def sign_message_digest(digest, subpath, prompt): # do the signature itself! from common import dis if prompt: dis.fullscreen(prompt, percent=.25) with stash.SensitiveValues() as sv: dis.progress_bar_show(.50) node = sv.derive_path(subpath) pk = node.private_key() sv.register(pk) dis.progress_bar_show(.75) rv = trezorcrypto.secp256k1.sign(pk, digest) dis.progress_bar_show(1) return rv
async def restore_from_dict(vals): # Restore from a dict of values. Already JSON decoded. # Reboot on success, return string on failure from common import pa, dis, settings from pincodes import SE_SECRET_LEN #print("Restoring from: %r" % vals) # step1: the private key # - prefer raw_secret over other values # - TODO: fail back to other values try: chain = chains.get_chain(vals.get('chain', 'BTC')) assert 'raw_secret' in vals raw = bytearray(SE_SECRET_LEN) rs = vals.pop('raw_secret') if len(rs) % 2: rs += '0' x = a2b_hex(rs) raw[0:len(x)] = x # check we can decode this right (might be different firmare) opmode, bits, node = stash.SecretStash.decode(raw) assert node # verify against xprv value (if we have it) if 'xprv' in vals: check_xprv = chain.serialize_private(node) assert check_xprv == vals['xprv'], 'xprv mismatch' except Exception as e: return ('Unable to decode raw_secret and ' 'restore the seed value!\n\n\n' + str(e)) ls = None if 'long_secret' in vals: try: ls = a2b_hex(vals.pop('long_secret')) except Exception as exc: sys.print_exception(exc) # but keep going. dis.fullscreen("Saving...") dis.progress_bar_show(.25) # clear (in-memory) settings and change also nvram key # - also captures xfp, xpub at this point pa.change(new_secret=raw) # force the right chain pa.new_main_secret(raw, chain) # updates xfp/xpub # NOTE: don't fail after this point... they can muddle thru w/ just right seed if ls is not None: try: pa.ls_change(ls) except Exception as exc: sys.print_exception(exc) # but keep going # restore settings from backup file for idx, k in enumerate(vals): dis.progress_bar_show(idx / len(vals)) if not k.startswith('setting.'): continue if k == 'xfp' or k == 'xpub': continue settings.set(k[8:], vals[k]) # write out settings.save() if ('hsm_policy' in vals): import hsm hsm.restore_backup(vals['hsm_policy']) await ux_show_story( 'Everything has been successfully restored. ' 'We must now reboot to install the ' 'updated settings and/or seed.', title='Success!') from machine import reset reset()
def sign_psbt_buf(psbt_buf): # sign a PSBT file found on a microSD card from uio import BytesIO from common import dis from sram4 import tmp_buf from utils import HexStreamer, Base64Streamer, HexWriter, Base64Writer UserAuthorizedAction.cleanup() # copy buffer into SPI Flash # - accepts hex or base64 encoding, but binary prefered with BytesIO(psbt_buf) as fd: dis.fullscreen('Reading...') # see how long it is psbt_len = fd.seek(0, 2) fd.seek(0) # determine encoding used, altho we prefer binary taste = fd.read(10) fd.seek(0) if taste[0:5] == b'psbt\xff': print('tastes like text PSBT') decoder = None def output_encoder(x): return x elif taste[0:10] == b'70736274ff': print('tastes like binary PSBT') decoder = HexStreamer() output_encoder = HexWriter psbt_len //= 2 elif taste[0:6] == b'cHNidP': print('tastes like Base64 PSBT') decoder = Base64Streamer() output_encoder = Base64Writer psbt_len = (psbt_len * 3 // 4) + 10 else: return total = 0 with SFFile(TXN_INPUT_OFFSET, max_size=psbt_len) as out: print('sign 1') # blank flash await out.erase() print('sign 2') while 1: n = fd.readinto(tmp_buf) print('sign copy to SPI flash 1: n={}'.format(n)) if not n: break if n == len(tmp_buf): abuf = tmp_buf else: abuf = memoryview(tmp_buf)[0:n] if not decoder: out.write(abuf) total += n else: for here in decoder.more(abuf): out.write(here) total += len(here) print('sign copy to SPI flash 2: {}/{} = {}'.format(total, psbt_len, total/psbt_len)) dis.progress_bar_show(total / psbt_len) print('sign 3') # might have been whitespace inflating initial estimate of PSBT size assert total <= psbt_len psbt_len = total print('sign 4') # Create a new BytesIO() to hold the result async def done(psbt): print('sign 5: done') signed_bytes = None with BytesIO() as bfd: with output_encoder(bfd) as fd: print('sign 6: done') if psbt.is_complete(): print('sign 7: done') psbt.finalize(fd) print('sign 8: done') else: print('sign 9: done') psbt.serialize(fd) print('sign 10: done') bfd.seek(0) signed_bytes = bfd.read() print('signed_bytes={}'.format(signed_bytes)) print('sign 11: done') gc.collect() from ur1.encode_ur import encode_ur from ubinascii import hexlify signed_str = hexlify(signed_bytes) print('signed_str={}'.format(signed_str)) from ux import DisplayURCode o = DisplayURCode('Signed Txn', 'Scan to Wallet', signed_str) await o.interact_bare() UserAuthorizedAction.cleanup() print('sign 12: done') UserAuthorizedAction.active_request = ApproveTransaction(psbt_len, approved_cb=done) print('sign 13: done') # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request) print('sign 14: done')
def sign_psbt_file(filename): # sign a PSBT file found on a microSD card from files import CardSlot, CardMissingError from common import dis from sram4 import tmp_buf from utils import HexStreamer, Base64Streamer, HexWriter, Base64Writer UserAuthorizedAction.cleanup() #print("sign: %s" % filename) # copy file into our spiflash # - 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() as card: with open(filename, 'rb') as fd: dis.fullscreen('Reading...') # see how long it is psbt_len = fd.seek(0, 2) fd.seek(0) # determine encoding used, altho we prefer binary taste = fd.read(10) fd.seek(0) if taste[0:5] == b'psbt\xff': decoder = None def output_encoder(x): return x elif taste[0:10] == b'70736274ff': decoder = HexStreamer() output_encoder = HexWriter psbt_len //= 2 elif taste[0:6] == b'cHNidP': decoder = Base64Streamer() output_encoder = Base64Writer psbt_len = (psbt_len * 3 // 4) + 10 total = 0 with SFFile(TXN_INPUT_OFFSET, max_size=psbt_len) as out: # blank flash await out.erase() while 1: n = fd.readinto(tmp_buf) if not n: break if n == len(tmp_buf): abuf = tmp_buf else: abuf = memoryview(tmp_buf)[0:n] if not decoder: out.write(abuf) total += n else: for here in decoder.more(abuf): out.write(here) total += len(here) dis.progress_bar_show(total / psbt_len) # might have been whitespace inflating initial estimate of PSBT size assert total <= psbt_len psbt_len = total async def done(psbt): orig_path, basename = filename.rsplit('/', 1) orig_path += '/' base = basename.rsplit('.', 1)[0] out2_fn = None out_fn = None while 1: # try to put back into same spot, but also do top-of-card is_comp = psbt.is_complete() if not is_comp: # keep the filename under control during multiple passes target_fname = base.replace('-part', '')+'-part.psbt' else: # add -signed to end. We won't offer to sign again. target_fname = base+'-signed.psbt' for path in [orig_path, None]: try: with CardSlot() as card: out_full, out_fn = card.pick_filename( target_fname, path) out_path = path if out_full: break except CardMissingError: prob = 'Missing card.\n\n' out_fn = None if not out_fn: # need them to insert a card prob = '' else: # attempt write-out try: with CardSlot() as card: with output_encoder(open(out_full, 'wb')) as fd: # save as updated PSBT psbt.serialize(fd) if is_comp: # write out as hex too, if it's final out2_full, out2_fn = card.pick_filename( base+'-final.txn', out_path) if out2_full: with HexWriter(open(out2_full, 'wt')) as fd: # save transaction, in hex psbt.finalize(fd) # success and done! break except OSError as exc: prob = 'Failed to write!\n\n%s\n\n' % exc sys.print_exception(exc) # fall thru to try again # prompt them to input another card? ch = await ux_show_story(prob+"Please insert an SDCard to receive signed transaction, " "and press OK.", title="Need Card") if ch == 'x': return # done. msg = "Updated PSBT is:\n\n%s" % out_fn if out2_fn: msg += '\n\nFinalized transaction (ready for broadcast):\n\n%s' % out2_fn await ux_show_story(msg, title='PSBT Signed') UserAuthorizedAction.cleanup() UserAuthorizedAction.active_request = ApproveTransaction( psbt_len, approved_cb=done) # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request)
def __exit__(self, exc_type, exc_val, exc_tb): if self.message: from common import dis dis.progress_bar_show(1) return False