async def accept_terms(*a): # do nothing if they have accepted the terms once (ever), otherwise # force them to read message... if settings.get('terms_ok'): return while 1: ch = await ux_show_story("""\ By using this product, you are accepting our Terms of Sale and Use. Read the full document at: https:// coldcardwallet .com/legal Press OK to accept terms and continue.""", escape='7') if ch == 'y': break await show_bag_number() # Note fact they accepted the terms. Annoying to do more than once. settings.set('terms_ok', 1) settings.save()
async def address_explore(*a): # explore addresses based on derivation path chosen # by proxy external index=0 address from main import settings while not settings.get('axskip', False): ch = await ux_show_story('''\ The following menu lists the first payment address \ produced by various common wallet systems. Choose the address that your desktop or mobile wallet \ has shown you as the first receive address. WARNING: Please understand that exceeding the gap limit \ of your wallet, or choosing the wrong address on the next screen \ may make it very difficult to recover your funds. Press 4 to start or 6 to hide this message forever.''', escape='46') if ch == '4': break if ch == '6': settings.set('axskip', True) break if ch == 'x': return m = AddressListMenu() await m.render() # slow the_ux.push(m)
async def start_b39_pw(menu, label, item): if not settings.get('b39skip', False): ch = await ux_show_story('''\ You may add a passphrase to your BIP39 seed words. \ This creates an entirely new wallet, for every possible passphrase. By default, the Coldcard uses an empty string as the passphrase. On the next menu, you can enter a passphrase by selecting \ individual lettters, choosing from the word list (recommended), \ or by typing numbers. Please write down the fingerprint of all your wallets, so you can \ confirm when you've got the right passphrase. (If you are writing down \ the passphrase as well, it's okay to put them together.) There is no way for \ the Coldcard to know if your password is correct, and if you have it wrong, \ you will be looking at an empty wallet. Limitations: 100 characters max length, ASCII \ characters 32-126 (0x20-0x7e) only. OK to start. X to go back. Or press 2 to hide this message forever. ''', escape='2') if ch == '2': settings.set('b39skip', True) if ch == 'x': return import seed return seed.PassphraseMenu()
def commit(self): # data to save # - important that this fails immediately when nvram overflows from main import settings obj = self.serialize() v = settings.get('multisig', []) orig = v.copy() if not v or self.storage_idx == -1: # create self.storage_idx = len(v) v.append(obj) else: # update: no provision for changing fingerprints assert sorted(k for k, v in v[self.storage_idx][2]) == self.xfps v[self.storage_idx] = obj settings.set('multisig', v) # save now, rather than in background, so we can recover # from out-of-space situation try: settings.save() except: # back out change; no longer sure of NVRAM state try: settings.set('multisig', orig) settings.save() except: pass # give up on recovery raise MultisigOutOfSpace
async def test_7z(): # test full 7z round-trip # Altho cleartext mode is not for real, if the code is written, I must test it. from backups import write_complete_backup, restore_complete_doit from sffile import SFFile import tcc from main import settings, sf, numpad today = tcc.random.uniform(1000000) import machine machine.reset = lambda: None for chain in ['BTC', 'XTN']: for words in ([], ['abc', 'def']): settings.set('check', today) settings.set('chain', chain) ll, sha = await write_complete_backup(words, None, True) result = SFFile(0, ll).read() if words: #open('debug.7z', 'wb').write(result) assert ll > 800 assert len(sha) == 32 assert result[0:6] == b"7z\xbc\xaf'\x1c" assert tcc.sha256(result).digest() == sha assert len(set(result)) >= 240 # encrypted else: sr = str(result, 'ascii') print("Backup contents:\n" + sr) assert sr[0] == '#', result assert 'Coldcard' in sr assert len(set(sr)) < 100 # cleartext, english assert ('chain = "%s"' % chain) in result # test restore # - cant wipe flash, since the backup file is there # - cant wipe all settings becuase PIN and stuff is simulated there del settings.current['check'] with SFFile(0, ll) as fd: numpad.inject('y') # for 'success' message await restore_complete_doit(fd, words) assert settings.get('check') == today, \ (settings.get('check'), '!=', today) assert settings.get('chain') == chain, \ (settings.get('chain'), '!=', chain) today += 3 import ux ux.restore_menu()
def set_chain(idx, text): val = ch[idx][0] assert ch[idx][1] == text settings.set('chain', val) try: # update xpub stored in settings import stash with stash.SensitiveValues() as sv: sv.capture_xpub() except ValueError: # no secrets yet, not an error pass
def set_it(idx, text): value = ch[idx][0] settings.set('sens', value) numpad.sensitivity = value # save also for next login time. from main import pa from nvstore import SettingsObject if not pa.is_secondary: tmp = SettingsObject() tmp.set('sens', value) tmp.save() del tmp
def delete(self): # remove saved entry # - important: not expecting more than one instance of this class in memory from main import settings assert self.storage_idx >= 0 # safety check expect_idx = self.find_match(self.M, self.N, self.xfps) assert expect_idx == self.storage_idx lst = settings.get('multisig', []) del lst[self.storage_idx] settings.set('multisig', lst) settings.save() self.storage_idx = -1
async def start_selftest(): try: await test_oled() await test_microsd() await test_numpad() await test_sflash() await test_secure_element() await test_sd_active() # add more tests here settings.set('tested', True) await ux_show_story("Selftest complete", 'PASS') except (RuntimeError, AssertionError) as e: await ux_show_story("Test failed:\n" + str(e), 'FAIL')
async def start_selftest(): import version try: await test_oled() await test_microsd() await test_numpad() await test_sflash() await test_ae508a() if version.is_mark2(): await test_sd_active() # add more tests here settings.set('tested', True) await ux_show_story("Selftest complete", 'PASS') except (RuntimeError, AssertionError) as e: await ux_show_story("Test failed:\n" + str(e), 'FAIL')
async def start_selftest(): try: await test_oled() await test_microsd() await test_touch() await test_sflash() #await test_dfu_button() # no need await test_ae508a() # TODO: # - PIN diode # add more tests here settings.set('tested', True) await ux_show_story("Selftest complete", 'PASS') except (RuntimeError, AssertionError) as e: await ux_show_story("Test failed:\n" + str(e), 'FAIL')
def add(cls, prevout, amount): # protect privacy, compress a little, and save it. # - we know it's not yet in our lists key = cls.encode_key(prevout) # memory management: can't store very much, so trim as needed depth = HISTORY_SAVED if settings.capacity > 0.8: depth //= 2 # also limit in-memory use cls.load_cache() if len(cls.runtime_cache) >= HISTORY_MAX_MEM: del cls.runtime_cache[0] # save new addition assert len(key) == ENCKEY_LEN assert amount > 0 entry = key + cls.encode_value(prevout, amount) cls.runtime_cache.append(entry) # update what we're going to save long-term settings.set(cls.KEY, cls.runtime_cache[-depth:])
def capture_xpub(self): # track my xpubkey fingerprint & value in settings (not sensitive really) # - we share this on any USB connection from main import settings #print("capture xfp/xpub/chain") settings.set('xfp', self.node.my_fingerprint()) settings.set('xpub', self.chain.serialize_public(self.node)) settings.set('chain', self.chain.ctype)
def set_it(idx, text): settings.set('sens', idx) numpad.sensitivity = idx
xfp = sys.argv[sys.argv.index('--xfp') + 1] sim_defaults['xfp'] = unpack("<I", a2b_hex(xfp))[0] print("Override XFP: " + xfp2str(sim_defaults['xfp'])) if '--seed' in sys.argv: # --xfp aabbccdd => pretend we know that key (won't be able to sign) from ustruct import unpack from utils import xfp2str from seed import set_seed_value from main import pa, settings words = sys.argv[sys.argv.index('--seed') + 1].split(' ') assert len(words) == 24, "Expected 24 space-separated words: add some quotes" pa.pin = b'12-12' set_seed_value(words) settings.set('terms_ok', 1) settings.set('_skip_pin', '12-12') settings.set('chain', 'XTN') print("Seed phrase set, resulting XFP: " + xfp2str(settings.get('xfp'))) if '-g' in sys.argv: # do login sim_defaults.pop('_skip_pin', 0) if '--nick' in sys.argv: nick = sys.argv[sys.argv.index('--nick') + 1] sim_defaults['nick'] = nick sim_defaults['terms_ok'] = 1 sim_defaults.pop('_skip_pin', 0) if '--delay' in sys.argv:
# load up the simulator w/ indicated list of seed words import tcc, main from sim_settings import sim_defaults import stash, chains from h import b2a_hex from main import settings, pa import stash from seed import set_seed_value from utils import xfp2str tn = chains.BitcoinTestnet if 1: stash.bip39_passphrase = '' settings.current = sim_defaults settings.overrides.clear() settings.set('chain', 'XTN') settings.set('words', True) settings.set('terms_ok', True) settings.set('idle_to', 3600) set_seed_value(main.WORDS) print("New key in effect: %s" % settings.get('xpub', 'MISSING')) print("Fingerprint: %s" % xfp2str(settings.get('xfp', 0)))
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()
def set_idle_timeout(idx, text): settings.set('idle_to', va[idx])
# run manually with: # execfile('../../testing/devtest/nvram.py') from ubinascii import hexlify as b2a_hex from ubinascii import unhexlify as a2b_hex import tcc, ustruct from main import settings, sf from nvstore import SLOTS # reset whatever's there sf.chip_erase() settings.load() for v in [123, 'hello', 34.56, dict(a=45)]: settings.set('abc', v) assert settings.get('abc') == v a = settings.get('_age', -1) settings.save() assert settings.get('_age') >= a + 1, [settings.get('_age'), a + 1] chk = dict(settings.current) settings.load() # some minor differences in values: bytes vs. strings, so just check keys assert sorted(list(chk)) == sorted(list(settings.current)), \ 'readback fail: \n%r != \n%r' % (chk, settings.current) if 1: # fill it up
def set(idx, text): settings.set('fee_limit', va[idx])
def set_del_psbt(idx, text): settings.set('del', idx)
def xset(idx, text): from main import settings settings.set('pms', idx)
def set_login_countdown(idx, text): settings.set('lgto', va[idx])
# load up the simulator w/ indicated list of seed words 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 seed import set_seed_value tn = chains.BitcoinTestnet if 1: settings.current = sim_defaults settings.set('chain', 'XTN') set_seed_value(main.WORDS) 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()