def make_menu(self): from menu import MenuItem, MenuSystem from actions import goto_top_menu from ux import ux_show_story from seed import set_bip39_passphrase import pyb # Very quick check for card not present case. if not pyb.SDCard().present(): return None # Read file, decrypt and make a menu to show; OR return None # if any error hit. try: with CardSlot() as card: self._calc_key(card) if not self.key: return None data = self._read(card) if not data: return None except CardMissingError: # not an error: they just aren't using feature return None # We have a list of xfp+pw fields. Make a menu. # Challenge: we need to hint at which is which, but don't want to # show the password on-screen. # - simple algo: # - show either first N or last N chars only # - pick which set which is all-unique, if neither, try N+1 # pws = [] for i in data: p = i.get('pw') if p not in pws: pws.append(p) for N in range(1, 8): parts = [ i[0:N] + ('*' * (len(i) - N if len(i) > N else 0)) for i in pws ] if len(set(parts)) == len(pws): break parts = [('*' * (len(i) - N if len(i) > N else 0)) + i[-N:] for i in pws] if len(set(parts)) == len(pws): break else: # give up: show it all! parts = pws async def doit(menu, idx, item): # apply the password immediately and drop them at top menu set_bip39_passphrase(data[idx]['pw']) from main 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() return MenuSystem( (MenuItem(label or '(empty)', f=doit) for label in parts))
async def file_picker(msg, suffix=None, min_size=1, max_size=1000000, taster=None, choices=None, escape=None, none_msg=None): # present a menu w/ a list of files... to be read # - optionally, enforce a max size, and provide a "tasting" function # - if msg==None, don't prompt, just do the search and return list # - if choices is provided; skip search process # - escape: allow these chars to skip picking process from menu import MenuSystem, MenuItem import uos from utils import get_filesize if choices is None: choices = [] try: with CardSlot() as card: sofar = set() for path in card.get_paths(): for fn, ftype, *var in uos.ilistdir(path): if ftype == 0x4000: # ignore subdirs continue if suffix and not fn.lower().endswith(suffix): # wrong suffix continue if fn[0] == '.': continue full_fname = path + '/' + fn # Conside file size # sigh, OS/filesystem variations file_size = var[1] if len(var) == 2 else get_filesize( full_fname) if not (min_size <= file_size <= max_size): continue if taster is not None: try: yummy = taster(full_fname) except IOError: #print("fail: %s" % full_fname) yummy = False if not yummy: continue label = fn while label in sofar: # just the file name isn't unique enough sometimes? # - shouldn't happen anymore now that we dno't support internal FS # - unless we do muliple paths label += path.split('/')[-1] + '/' + fn sofar.add(label) choices.append((label, path, fn)) except CardMissingError: # don't show anything if we're just gathering data if msg is not None: await needs_microsd() return None if msg is None: return choices if not choices: msg = none_msg or 'Unable to find any suitable files for this operation. ' if not none_msg: if suffix: msg += 'The filename must end in "%s". ' % suffix msg += '\n\nMaybe insert (another) SD card and try again?' await ux_show_story(msg) return # tell them they need to pick; can quit here too, but that's obvious. if len(choices) != 1: msg += '\n\nThere are %d files to pick from.' % len(choices) else: msg += '\n\nThere is only one file to pick from.' ch = await ux_show_story(msg, escape=escape) if escape and ch in escape: return ch if ch == 'x': return picked = [] async def clicked(_1, _2, item): picked.append('/'.join(item.arg)) the_ux.pop() items = [ MenuItem(label, f=clicked, arg=(path, fn)) for label, path, fn in choices ] if 0: # don't like; and now showing count on previous page if len(choices) == 1: # if only one choice, we could make the choice for them ... except very confusing items.append(MenuItem(' (one file)', f=None)) else: items.append(MenuItem(' (%d files)' % len(choices), f=None)) menu = MenuSystem(items) the_ux.push(menu) await menu.interact() return picked[0] if picked else None
async def initial_pin_setup(*a): # First time they select a PIN of any type. from login import LoginUX lll = LoginUX() title = 'Choose PIN' ch = await ux_show_story('''\ Pick the main wallet's PIN code now. Be more clever, but an example: 123-4567 It has two parts: prefix (123-) and suffix (-4567). \ Each part must between 2 to 6 digits long. Total length \ can be as long as 12 digits. The prefix part determines the anti-phishing words you will \ see each time you login. Your new PIN protects access to \ this Coldcard device and is not a factor in the wallet's \ seed words or private keys. THERE IS ABSOLUTELY NO WAY TO RECOVER A FORGOTTEN PIN! Write it down. ''', title=title) if ch != 'y': return while 1: ch = await ux_show_story('''\ There is ABSOLUTELY NO WAY to 'reset the PIN' or 'factory reset' the Coldcard if you forget the PIN. DO NOT FORGET THE PIN CODE. Press 6 to prove you read to the end of this message.''', title='WARNING', escape='6') if ch == 'x': return if ch == '6': break # do the actual picking pin = await lll.get_new_pin(title) del lll if pin is None: return # A new pin is to be set! from main import pa, dis, settings, loop dis.fullscreen("Saving...") try: assert pa.is_blank() pa.change(new_pin=pin) # check it? kinda, but also get object into normal "logged in" state pa.setup(pin) ok = pa.login() assert ok # must re-read settings after login, because they are encrypted # with a key derived from the main secret. settings.set_key() settings.load() except Exception as e: print("Exception: %s" % e) # Allow USB protocol, now that we are auth'ed from usb import enable_usb enable_usb(loop, False) from menu import MenuSystem from flow import EmptyWallet return MenuSystem(EmptyWallet)