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 common 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()
def save_wallet_seed(seed_bits): from common import dis, pa, settings print('save_wallet_seed 1') # encode it for our limited secret space nv = SecretStash.encode(seed_bits=seed_bits) print('save_wallet_seed 2: nv={}'.format(b2a_hex(nv))) dis.fullscreen('Saving Wallet...') pa.change(new_secret=nv) print('save_wallet_seed 3') # re-read settings since key is now different # - also captures xfp, xpub at this point pa.new_main_secret(nv) print('save_wallet_seed 4') # check and reload secret pa.reset() print('save_wallet_seed 5') pa.login() print('save_wallet_seed 6')
def set_seed_value(words=None, encoded=None): # Save the seed words into secure element, and reboot. BIP-39 password # is not set at this point (empty string) if words: bip39.a2b_words(words) # checksum check # map words to bip39 wordlist indices data = [bip39.wordlist_en.index(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 # encode it for our limited secret space nv = SecretStash.encode(seed_phrase=seed) else: nv = encoded from glob import dis 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()
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 import_xprv(*A): # read an XPRV from a text file and use it. import tcc, chains, ure from main import pa from stash import SecretStash from ubinascii import hexlify as b2a_hex from backups import restore_from_dict assert pa.is_secret_blank() # "must not have secret" def contains_xprv(fname): # just check if likely to be valid; not full check try: with open(fname, 'rt') as fd: for ln in fd: # match tprv and xprv, plus y/zprv etc if 'prv' in ln: return True return False except OSError: # directories? return False # pick a likely-looking file. fn = await file_picker('Select file containing the XPRV to be imported.', min_size=50, max_size=2000, taster=contains_xprv) if not fn: return node, chain, addr_fmt = None, None, None # open file and do it pat = ure.compile(r'.prv[A-Za-z0-9]+') with CardSlot() as card: with open(fn, 'rt') as fd: for ln in fd.readlines(): if 'prv' not in ln: continue found = pat.search(ln) if not found: continue found = found.group(0) for ch in chains.AllChains: for kk in ch.slip132: if found[0] == ch.slip132[kk].hint: try: node = tcc.bip32.deserialize( found, ch.slip132[kk].pub, ch.slip132[kk].priv) chain = ch addr_fmt = kk break except ValueError: pass if node: break if not node: # unable await ux_show_story('''\ Sorry, wasn't able to find an extended private key to import. It should be at \ the start of a line, and probably starts with "xprv".''', title="FAILED") return # encode it in our style d = dict(chain=chain.ctype, raw_secret=b2a_hex(SecretStash.encode(xprv=node))) node.blank() # TODO: capture the address format implied by SLIP32 version bytes #addr_fmt = # restore as if it was a backup (code reuse) await restore_from_dict(d)
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 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 tn = chains.BitcoinTestnet node = tcc.bip32.deserialize(main.TPRV, tn.b32_version_pub, tn.b32_version_priv) assert node if settings.get('xfp') == node.my_fingerprint(): print("right xfp already") else: settings.current = sim_defaults settings.set('chain', 'XTN') raw = SecretStash.encode(xprv=node) pa.change(new_secret=raw) pa.new_main_secret(raw) print("New key in effect: %s" % settings.get('xpub', 'MISSING')) print("Fingerprint: 0x%08x" % settings.get('xfp', 0)) assert settings.get('xfp', 0) == node.my_fingerprint()