async def xor_restore_start(*a): # shown on import menu when no seed of any kind yet # - or operational system ch = await ux_show_story('''\ To import a seed split using XOR, you must import all the parts. It does not matter the order (A/B/C or C/A/B) and the Coldcard cannot determine when you have all the parts. You may stop at any time and you will have a valid wallet.''') if ch == 'x': return global import_xor_parts import_xor_parts.clear() from pincodes import pa if not pa.is_secret_blank(): msg = "Since you have a seed already on this Coldcard, the reconstructed XOR seed will be temporary and not saved. Wipe the seed first if you want to commit the new value into the secure element." if settings.get('words', True): msg += '''\n Press (1) to include this Coldcard's seed words into the XOR seed set, or OK to continue without.''' ch = await ux_show_story(msg, escape='1') if ch == 'x': return elif ch == '1': with stash.SensitiveValues() as sv: if sv.mode == 'words': words = bip39.b2a_words(sv.raw).split(' ') import_xor_parts.append(words) return XORWordNestMenu(num_words=24)
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 __init__(self, secret=None, bypass_pw=False): if secret is None: # fetch the secret from bootloader/atecc508a from pincodes import pa if pa.is_secret_blank(): raise ValueError('no secrets yet') self.secret = pa.fetch() self.spots = [self.secret] else: # sometimes we already know it #assert set(secret) != {0} self.secret = secret self.spots = [] # backup during volatile bip39 encryption: do not use passphrase self._bip39pw = '' if bypass_pw else str(bip39_passphrase)
def set_key(self, new_secret=None): # System settings (not secrets) are stored in SPI Flash, encrypted with this # key that is derived from main wallet secret. Call this method when the secret # is first loaded, or changes for some reason. from pincodes import pa from stash import blank_object key = None mine = False if not new_secret: if not pa.is_successful() or pa.is_secret_blank(): # simple fixed key allows us to store a few things when logged out key = b'\0' * 32 else: # read secret and use it. new_secret = pa.fetch() mine = True if new_secret: # hash up the secret... without decoding it or similar assert len(new_secret) >= 32 s = sha256(new_secret) for round in range(5): s.update('pad') s = sha256(s.digest()) key = s.digest() if mine: blank_object(new_secret) # for restore from backup case, or when changing (created) the seed self.nvram_key = key
def has_secrets(): from pincodes import pa return not pa.is_secret_blank()
# (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 glob import numpad, dis from pincodes import pa from nvstore import settings 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()