async def remember_bip39_passphrase(): # Compute current xprv and switch to using that as root secret. import stash from main import dis, pa if not stash.bip39_passphrase: if not await ux_confirm( '''You do not have a BIP39 passphrase set right now, so this command does little except forget the seed words. It does not enhance security.''' ): return dis.fullscreen('Check...') with stash.SensitiveValues() as sv: if sv.mode != 'words': # not a BIP39 derived secret, so cannot work. await ux_show_story( '''The wallet secret was not based on a seed phrase, so we cannot add a BIP39 passphrase at this time.''', title='Failed') return nv = SecretStash.encode(xprv=sv.node) dis.fullscreen('Saving...') pa.change(new_secret=nv) # re-read settings since key is now different # - also captures xfp, xpub at this point pa.new_main_secret(nv) # check and reload secret pa.reset() pa.login()
async def remember_bip39_passphrase(): # Compute current xprv and switch to using that as root secret. import stash from main import dis, pa dis.fullscreen('Check...') with stash.SensitiveValues() as sv: if sv.mode != 'words': # not a BIP39 derived secret, so cannot work. await ux_show_story('''The wallet secret was not based on a seed phrase, so we cannot add a BIP39 passphrase at this time.''', title='Failed') return nv = SecretStash.encode(xprv=sv.node) # Important: won't write new XFP to nvram if pw still set stash.bip39_passphrase = '' dis.fullscreen('Saving...') pa.change(new_secret=nv) # re-read settings since key is now different # - also captures xfp, xpub at this point pa.new_main_secret(nv) # check and reload secret pa.reset() pa.login()
async def initial_pin_setup(*a): # First time they select a PIN of any type. from login import LoginUX lll = LoginUX() pin = await lll.get_new_pin( 'Choose PIN', '''\ 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. ''') 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)
def clear_seed(): from main import dis, pa, settings import utime dis.fullscreen('Clearing...') # 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) nv = bytes(AE_SECRET_LEN) pa.change(new_secret=nv) dis.fullscreen('Reboot...') utime.sleep(1) # security: need to reboot to really be sure to clear the secrets from main memory. from machine import reset reset()
def set_seed_value(words): # Save the seed words into secure element, and reboot. BIP39 password # is not set at this point (empty string) ok = tcc.bip39.check(' '.join(words)) assert ok, "seed check: %r" % words # map words to bip39 wordlist indices data = [tcc.bip39.lookup_word(w) for w in words] # map to packed binary representation. val = 0 for v in data: val <<= 11 val |= v # remove the checksum part vlen = (len(words) * 4) // 3 val >>= (len(words) // 3) # convert to bytes seed = val.to_bytes(vlen, 'big') assert len(seed) == vlen from main import dis, pa, settings # encode it for our limited secret space nv = SecretStash.encode(seed_phrase=seed) dis.fullscreen('Applying...') pa.change(new_secret=nv) # re-read settings since key is now different # - also captures xfp, xpub at this point pa.new_main_secret(nv) # check and reload secret pa.reset() pa.login()
async def remember_bip39_passphrase(): # Compute current xprv and switch to using that as root secret. import stash from main import dis, pa dis.fullscreen('Check...') with stash.SensitiveValues() as sv: nv = SecretStash.encode(xprv=sv.node) # Important: won't write new XFP to nvram if pw still set stash.bip39_passphrase = '' dis.fullscreen('Saving...') pa.change(new_secret=nv) # re-read settings since key is now different # - also captures xfp, xpub at this point pa.new_main_secret(nv) # check and reload secret pa.reset() pa.login()
async def restore_from_dict(vals): # Restore from a dict of values. Already JSON decoded. # Reboot on success, return string on failure from main import pa, dis, settings from pincodes import AE_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(AE_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) and version.has_608: 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 version.has_fatram and ('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()
async def pin_changer(_1, _2, item): # Help them to change pins with appropriate warnings. # - forcing them to drill-down to get warning about secondary is on purpose # - the bootloader maybe lying to us about weather we are main vs. duress # - there is a duress wallet for both main/sec pins, and you need to know main pin for that # - what may look like just policy here, is in fact enforced by the bootrom code # from main import pa, dis from login import LoginUX from pincodes import BootloaderError, EPIN_OLD_AUTH_FAIL mode = item.arg warn = { 'main': ('Main PIN', 'You will be changing the main PIN used to unlock your Coldcard. ' "It's the one you just used a moment ago to get in here."), 'duress': ('Duress PIN', 'This PIN leads to a bogus wallet. Funds are recoverable ' 'from main seed backup, but not as easily.'), 'secondary': ('Second PIN', 'This PIN protects the "secondary" wallet that can be used to ' 'segregate funds or other banking purposes. This other wallet is ' 'completely independant of the primary.'), 'brickme': ('Brickme PIN', 'Use of this special PIN code at any prompt will destroy the ' 'Coldcard completely. It cannot be reused or salvaged, and ' 'the secrets it held are destroyed forever.\n\nDO NOT TEST THIS!'), } if pa.is_secondary: # secondary wallet user can only change their own password, and the secondary # duress pin... # NOTE: now excluded from menu, but keep if mode == 'main' or mode == 'brickme': await needs_primary() return if mode == 'duress' and pa.is_secret_blank(): await ux_show_story( "Please set wallet seed before creating duress wallet.") return # are we changing the pin used to login? is_login_pin = (mode == 'main') or (mode == 'secondary' and pa.is_secondary) lll = LoginUX() lll.offer_second = False title, msg = warn[mode] async def incorrect_pin(): await ux_show_story( 'You provided an incorrect value for the existing %s.' % title, title='Wrong PIN') return # standard threats for all PIN's msg += '''\n\n\ THERE IS ABSOLUTELY NO WAY TO RECOVER A FORGOTTEN PIN! Write it down. We strongly recommend all PIN codes used be unique between each other. ''' if not is_login_pin: msg += '''\nUse 999999-999999 to clear existing PIN.''' ch = await ux_show_story(msg, title=title) if ch != 'y': return args = {} need_old_pin = True if is_login_pin: # Challenge them for old password; certainly had it, because # we wouldn't be here otherwise. need_old_pin = True else: # There may be no existing PIN, and we need to learn that if mode == 'secondary': args['is_secondary'] = True elif mode == 'duress': args['is_duress'] = True elif mode == 'brickme': args['is_brickme'] = True old_pin = None try: dis.fullscreen("Check...") pa.change(old_pin=b'', new_pin=b'', **args) need_old_pin = False old_pin = '' # converts to bytes below except BootloaderError as exc: #print("(not-error) old pin was NOT blank: %s" % exc) need_old_pin = True was_blank = not need_old_pin if need_old_pin: # We need the existing pin, so prompt for that. lll.subtitle = 'Old ' + title old_pin = await lll.prompt_pin() if old_pin is None: return await ux_aborted() args['old_pin'] = old_pin.encode() # we can verify the main pin right away here. Be nice. if is_login_pin and args['old_pin'] != pa.pin: return await incorrect_pin() while 1: lll.reset() lll.subtitle = "New " + title pin = await lll.get_new_pin(title, allow_clear=True) if pin is None: return await ux_aborted() is_clear = (pin == '999999-999999') args['new_pin'] = pin.encode() if not is_clear else b'' if args['new_pin'] == pa.pin and not is_login_pin: await ux_show_story( "Your new PIN matches the existing PIN used to get here. " "It would be a bad idea to use it for another purpose.", title="Try Again") continue break # install it. dis.fullscreen("Saving...") try: pa.change(**args) except Exception as exc: code = exc.args[1] if code == EPIN_OLD_AUTH_FAIL: # likely: wrong old pin, on anything but main PIN return await incorrect_pin() else: return await ux_show_story("Unexpected low-level error: %s" % exc.args[0], title='Error') # Main pin is changed, and we use it lots, so update pa # - also to get pa.has_duress_pin() and has_brickme_pin() to be correct, need this dis.fullscreen("Verify...") pa.setup(args['new_pin'] if is_login_pin else pa.pin, pa.is_secondary) if not pa.is_successful(): # typical: do need login, but if we just cleared the main PIN, # we cannot/need not login again pa.login() if mode == 'duress': # program the duress secret now... it's derived from real wallet from stash import SensitiveValues, SecretStash, AE_SECRET_LEN if is_clear: # clear secret, using the new pin, which is empty string pa.change(is_duress=True, new_secret=b'\0' * AE_SECRET_LEN, old_pin=b'', new_pin=b'') else: with SensitiveValues() as sv: # derive required key node = sv.duress_root() d_secret = SecretStash.encode(xprv=node) sv.register(d_secret) # write it out. pa.change(is_duress=True, new_secret=d_secret, old_pin=args['new_pin'])
# load up the simulator w/ indicated encoded secret. could be xprv/words/etc. import tcc, main from sim_settings import sim_defaults import stash, chains from h import b2a_hex from main import settings, pa from stash import SecretStash, SensitiveValues from utils import xfp2str settings.current = sim_defaults settings.overrides.clear() raw = main.ENCODED_SECRET pa.change(new_secret=raw) pa.new_main_secret(raw) print("New key in effect: %s" % settings.get('xpub', 'MISSING')) print("Fingerprint: %s" % xfp2str(settings.get('xfp', 0)))
# (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()
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: dis.busy_bar(True) 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) finally: dis.busy_bar(False) # 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)
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # load up the simulator w/ indicated test master key import tcc, main from sim_settings import sim_defaults import stash, chains from h import b2a_hex from main import settings, pa from stash import SecretStash, SensitiveValues from utils import xfp2str rs = main.RAW_SECRET print("New raw secret: %s" % b2a_hex(rs)) if 1: settings.current = sim_defaults settings.overrides.clear() settings.set('chain', 'XTN') pa.change(new_secret=rs) pa.new_main_secret(rs) print("New key in effect: %s" % settings.get('xpub', 'MISSING')) print("Fingerprint: %s" % xfp2str(settings.get('xfp', 0)))