async def startup(): print("startup()") from actions import accept_terms, validate_passport_hw await accept_terms() # We will come here again if the device is shutdown, but the validation # words have not been confirmed (assuming the terms were accepted). await validate_passport_hw() # Setup first PIN if it's blank from common import pa from actions import initial_pin_setup if pa.is_blank(): await initial_pin_setup() # Prompt for PIN and then pick appropriate top-level menu, # based on contents of secure chip (ie. is there # a wallet defined) from actions import start_login_sequence await start_login_sequence() # from actions import test_normal_menu # await test_normal_menu() from actions import goto_top_menu goto_top_menu() from common import loop loop.create_task(main())
async def done_apply(self, *a): # apply the passphrase. # - important to work on empty string here too. from stash import bip39_passphrase old_pw = str(bip39_passphrase) err = set_bip39_passphrase(pp_sofar) if err: # kinda very late: but if not BIP39 based key, ends up here. return await ux_show_story(err, title="Fail") from main import settings xfp = settings.get('xfp') ch = await ux_show_story( '''Above is the master key fingerprint of the new wallet. Press X to abort and keep editing passphrase. OK to use the new wallet.''', title="[%s]" % xfp2str(xfp)) if ch == 'x': # go back! set_bip39_passphrase(old_pw) return goto_top_menu()
async def done_apply(self, *a): # apply the passphrase. # - important to work on empty string here too. from stash import bip39_passphrase old_pw = str(bip39_passphrase) set_bip39_passphrase(pp_sofar) from main import settings xfp = settings.get('xfp') msg = '''Above is the master key fingerprint of the new wallet. Press X to abort and keep editing passphrase, OK to use the new wallet and 1 to save to MicroSD''' ch = await ux_show_story(msg, title="[%s]" % xfp2str(xfp), escape='1') if ch == 'x': # go back! set_bip39_passphrase(old_pw) return if ch == '1': await PassphraseSaver().append(xfp, pp_sofar) goto_top_menu()
async def done_cancel(self, *a): global pp_sofar if len(pp_sofar) > 3: if not await ux_confirm("What you have entered will be forgotten."): return goto_top_menu()
async def all_done(new_words): # save the new seed value set_seed_value(new_words) # clear menu stack goto_top_menu() return None
async def approve_word_list(seed): # Force the user to write the seeds words down, give a quiz, then save them. # LESSON LEARNED: if the user is writting down the words, as we have # vividly instructed, then it's a big deal to lose those words and have to start # over. So confirm that action, and don't volunteer it. words = tcc.bip39.from_data(seed).split(' ') assert len(words) == 24 while 1: # show the seed words ch = await show_words(words, escape='46', extra='\n\nPress 4 to add some dice rolls into the mix.') if ch == 'x': # user abort, but confirm it! if await ux_confirm("Throw away those words and stop this process?"): return else: continue if ch == '4': # dice roll mode count, new_seed = await add_dice_rolls(0, seed, False) if count: seed = new_seed words = tcc.bip39.from_data(seed).split(' ') continue if ch == '6': # wants to skip the quiz (undocumented) if await ux_confirm("Skipping the quiz means you might have " "recorded the seed wrong and will be crying later."): break # Perform a test, to check they wrote them down ch = await word_quiz(words) if ch == 'x': # user abort quiz if await ux_confirm("Throw away those words and stop this process? Press X to see the word list again and restart the quiz."): return # show the words again, but don't change them continue # quiz passed break # Done! set_seed_value(words) # send them to home menu, now with a wallet enabled goto_top_menu()
async def all_done(new_words): # So we have another part, might be done or not. global import_xor_parts assert len(new_words) == 24 import_xor_parts.append(new_words) XORWordNestMenu.pop_all() num_parts = len(import_xor_parts) seed = xor32(*(bip39.a2b_words(w) for w in import_xor_parts)) msg = "You've entered %d parts so far.\n\n" % num_parts if num_parts >= 2: chk_word = bip39.b2a_words(seed).split(' ')[-1] msg += "If you stop now, the 24th word of the XOR-combined seed phrase\nwill be:\n\n" msg += "24: %s\n\n" % chk_word if all((not x) for x in seed): # zero seeds are never right. msg += "ZERO WARNING\nProvided seed works out to all zeros "\ "right now. You may have doubled a part or made some other mistake.\n\n" msg += "Press (1) to enter next list of words, or (2) if done with all words." ch = await ux_show_story(msg, strict_escape=True, escape='12x', sensitive=True) if ch == 'x': # give up import_xor_parts.clear() # concern: we are contaminated w/ secrets return None elif ch == '1': # do another list of words nxt = XORWordNestMenu(num_words=24) the_ux.push(nxt) elif ch == '2': # done; import on temp basis, or be the main secret from pincodes import pa enc = stash.SecretStash.encode(seed_phrase=seed) if pa.is_secret_blank(): # save it since they have no other secret set_seed_value(encoded=enc) # update menu contents now that wallet defined goto_top_menu() else: pa.tmp_secret(enc) await ux_show_story( "New master key in effect until next power down.") return None
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 doit(menu, idx, item): # apply the password immediately and drop them at top menu set_bip39_passphrase(data[idx]['pw']) from nvstore import settings from utils import xfp2str xfp = settings.get('xfp') # verification step; I don't see any way for this to go wrong assert xfp == data[idx]['xfp'] # feedback that it worked await ux_show_story("Passphrase restored.", title="[%s]" % xfp2str(xfp)) goto_top_menu()
async def mainline(): # Mainline of program, after startup # # - Do not add to this function, its vars are # in memory forever; instead, extend more_setup above. from actions import goto_top_menu from ux import the_ux goto_top_menu() gc.collect() #print("Free mem: %d" % gc.mem_free()) while 1: await the_ux.interact()
def done(self, redraw=True): # drop them back into menu system, but at top. self.ux_done = True from actions import goto_top_menu m = goto_top_menu() if redraw: m.show()
async def interact(self): import main from main import numpad, is_devmode from actions import login_now from uasyncio import sleep_ms # Replace some drawing functions orig_fullscreen = main.dis.fullscreen orig_progress_bar = main.dis.progress_bar orig_progress_bar_show = main.dis.progress_bar_show main.dis.fullscreen = self.hack_fullscreen main.dis.progress_bar = self.hack_progress_bar main.dis.progress_bar_show = self.hack_progress_bar # get ready ourselves main.dis.set_brightness(0) # dimest, but still readable self.draw_background() # Kill time, waiting for user input self.digits = '' self.test_restart = False while not self.test_restart: self.show() gc.collect() try: # Poll for an event, no block ch = numpad.get_nowait() if ch == 'x': self.digits = '' elif ch == 'y': if len(self.digits) == LOCAL_PIN_LENGTH: main.hsm_active.local_pin_entered(self.digits) self.digits = '' elif ch == numpad.ABORT_KEY: # important to eat these and fully suppress them pass elif ch: if len(self.digits) < LOCAL_PIN_LENGTH: # allow only 6 digits self.digits += ch[0] if len(self.digits) == LOCAL_PIN_LENGTH: # send it, even if they didn't press OK yet main.hsm_active.local_pin_entered(self.digits) # do immediate screen update continue except QueueEmpty: await sleep_ms(100) except BaseException as exc: # just in case, keep going sys.print_exception(exc) continue # do the interactions, but don't let user actually press anything req = UserAuthorizedAction.active_request if req and not req.ux_done: try: await req.interact() except AbortInteraction: pass # This code only reachable on the simulator and modified devices under test, # and when the "boot_to_hsm" feature is used and successfully unlock near # boottime. from actions import goto_top_menu main.hsm_active = None goto_top_menu() # restore normal operation of UX from display import Display main.dis.fullscreen = orig_fullscreen main.dis.progress_bar = orig_progress_bar main.dis.progress_bar_show = orig_progress_bar_show return
async def make_new_wallet(): # pick a new random seed, and force them to # write it down, then save it. from main import dis from uasyncio import sleep_ms # CONCERN: memory is really contaminated with secrets in this process, much more so # than during normal operation. Maybe we should block USB and force a reboot as well? # LESSON LEARNED: if the user is writting down the words, as we have # vividly instructed, then it's a big deal to lose those words and have to start # over. So confirm that action, and don't volunteer it. # dramatic pause await ux_dramatic_pause('Generating...', 4) # always full 24-word (256 bit) entropy seed = bytearray(32) rng_bytes(seed) assert len(set(seed)) > 4, "impossible luck?" # hash to mitigate bias in TRNG seed = tcc.sha256(seed).digest() words = tcc.bip39.from_data(seed).split(' ') assert len(words) == 24 #print('words: ' + ' '.join(words)) while 1: # show the seed words ch = await show_words(words, escape='6') if ch == 'x': # user abort if await ux_confirm("Throw away those words and stop this process?" ): return else: continue if ch == '6': # wants to skip the quiz (undocumented) if await ux_confirm( "Skipping the quiz means you might have " "recorded the seed wrong and will be crying later."): break # Perform a test, to check they wrote them down ch = await word_quiz(words) if ch == 'x': # user abort quiz if await ux_confirm( "Throw away those words and stop this process? Press X to see the word list again and restart the quiz." ): return # show the words again, but don't change them continue # quiz passed break # Done! set_seed_value(words) # send them to home menu, now with a wallet enabled goto_top_menu()
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # quickly main wipe seed; don't install anything new from main import pa, settings, numpad, dis from pincodes import AE_SECRET_LEN, PA_IS_BLANK if not pa.is_secret_blank(): # clear settings associated with this key, since it will be no more settings.blank() # save a blank secret (all zeros is a special case, detected by bootloader) dis.fullscreen('Wipe Seed!') nv = bytes(AE_SECRET_LEN) pa.change(new_secret=nv) rv = pa.setup(pa.pin) pa.login() assert pa.is_secret_blank() # reset top menu and go there from actions import goto_top_menu goto_top_menu() numpad.abort_ux()