async def dump_summary(*A): # save addresses, and some other public details into a file if not await ux_confirm('''\ Saves a text file to MicroSD with a summary of the *public* details \ of your wallet. For example, this gives the XPUB (extended public key) \ that you will need to import other wallet software to track balance.''' + SENSITIVE_NOT_SECRET): return # pick a semi-random file name, save it. with imported('backups') as bk: await bk.make_summary_file()
async def start_selftest(*args): import version if len(args) and not version.is_factory_mode(): # called from inside menu, not directly if not await ux_confirm('''Selftest destroys settings on other profiles (not seeds). Requires MicroSD card and might have other consequences. Recommended only for factory.'''): return await ux_aborted() with imported('selftest') as st: await st.start_selftest() settings.save()
async def use_dice(self, *a): # Use lots of (D6) dice rolls to create privkey entropy. privkey = b'' with imported('seed') as seed: count, privkey = await seed.add_dice_rolls(0, privkey, True) if count == 0: return if privkey >= SECP256K1_ORDER or privkey == bytes(32): # lottery won! but not going to waste bytes here preparing to celebrate return return await self.doit(have_key=privkey)
async def electrum_skeleton(*A): # save xpub, and some other public details into a file if not await ux_confirm('''\ Saves a skeleton wallet file which Electrum can open, on to MicroSD. \ You can then open that file in Electrum without ever connecting the Coldcard to a computer.''' + SENSITIVE_NOT_SECRET): return # TODO: pick segwit or classic derivation+such # pick a semi-random file name, save it. with imported('backups') as bk: await bk.make_electrum_wallet(is_segwit=False)
async def verify_backup(*A): # check most recent backup is "good" # read 7z header, and measure checksums # save everything, using a password, into single encrypted file, typically on SD fn = await file_picker( 'Select file containing the backup to be verified. No password will be required.', suffix='.7z', max_size=10000) if fn: with imported('backups') as bk: await bk.verify_backup_file(fn)
async def restore_everything(*A): from main import pa if not pa.is_secret_blank(): await ux_show_story(EMPTY_RESTORE_MSG) return # restore everything, using a password, from single encrypted 7z file fn = await file_picker('Select file containing the backup to be restored, and ' 'then enter the password.', suffix='.7z', max_size=10000) if fn: with imported('backups') as bk: await bk.restore_complete(fn)
async def verify_backup(*A): # check most recent backup is "good" # read 7z header, and measure checksums with imported('backups') as bk: fn = await file_picker( 'Select file containing the backup to be verified. No password will be required.', suffix='.7z', max_size=bk.MAX_BACKUP_FILE_SIZE) if fn: # do a limited CRC-check over encrypted file await bk.verify_backup_file(fn)
async def restore_complete(fname_or_fd): from ux import the_ux with imported('seed') as seed: async def done(words): # remove all pw-picking from menu stack seed.WordNestMenu.pop_all() await restore_complete_doit(fname_or_fd, words) # give them a menu to pick from m = seed.WordNestMenu(num_words=num_pw_words, has_checksum=False, done_cb=done) the_ux.push(m)
async def wasabi_skeleton(*A): # save xpub, and some other public details into a file # - user has no choice, it's going to be bech32 with m/84'/0'/0' path import chains ch = chains.current_chain() if await ux_show_story('''\ This saves a skeleton Wasabi wallet file onto the MicroSD card. \ You can then open that file in Wasabi without ever connecting this Coldcard to a computer.\ ''' + SENSITIVE_NOT_SECRET) != 'y': return # no choices to be made, just do it. with imported('backups') as bk: await bk.make_json_wallet('Wasabi wallet', lambda: bk.generate_wasabi_wallet(), 'new-wasabi.json')
async def bitcoin_core_skeleton(*A): # save output descriptors into a file # - user has no choice, it's going to be bech32 with m/84'/{coin_type}'/0' path import chains ch = chains.current_chain() if await ux_show_story('''\ This saves a command onto the MicroSD card that includes the public keys.\ You can then run that command in Bitcoin Core without ever connecting this Coldcard to a computer.\ ''' + SENSITIVE_NOT_SECRET) != 'y': return # no choices to be made, just do it. with imported('backups') as bk: await bk.make_bitcoin_core_wallet()
async def restore_everything(*A): from main import pa if not pa.is_secret_blank(): await ux_show_story('''\ You must clear the wallet seed before restoring a backup because it replaces \ the seed value and the old seed would be lost.\n\n\ Visit the advanced menu and choose 'Destroy Seed'.''') return # save everything, using a password, into single encrypted file, typically on SD fn = await file_picker('Select file containing the backup to be restored, and ' 'then enter the password.', suffix='.7z', max_size=10000) if fn: with imported('backups') as bk: await bk.restore_complete(fn)
async def restore_everything_cleartext(*A): # Asssume no password on backup file; devs and crazy people only from main import pa if not pa.is_secret_blank(): await ux_show_story(EMPTY_RESTORE_MSG) return # restore everything, using NO password, from single text file, like would be wrapped in 7z fn = await file_picker('Select the cleartext file containing the backup to be restored.', suffix='.txt', max_size=10000) if fn: with imported('backups') as bk: prob = await bk.restore_complete_doit(fn, []) if prob: await ux_show_story(prob, title='FAILED')
def render_qr(self, msg): # Version 2 would be nice, but can't hold what we need, even at min error correction, # so we are forced into version 3 = 29x29 pixels # - see <https://www.qrcode.com/en/about/version.html> # - to display 29x29 pixels, we have to double them up: 58x58 # - not really providing enough space around it # - inverted QR (black/white swap) still readable by scanners, altho wrong from utils import imported with imported('uQR') as uqr: if self.is_alnum: # targeting 'alpha numeric' mode, typical len is 42 ec = uqr.ERROR_CORRECT_Q assert len(msg) <= 47 else: # has to be 'binary' mode, altho shorter msg, typical 34-36 ec = uqr.ERROR_CORRECT_M assert len(msg) <= 42 q = uqr.QRCode(version=3, box_size=1, border=0, mask_pattern=3, error_correction=ec) if self.is_alnum: here = uqr.QRData(msg.upper().encode('ascii'), mode=uqr.MODE_ALPHA_NUM, check_data=False) else: here = uqr.QRData(msg.encode('ascii'), mode=uqr.MODE_8BIT_BYTE, check_data=False) q.add_data(here) q.make(fit=False) self.qr_data = q.get_matrix()
async def backup_everything(*A): # save everything, using a password, into single encrypted file, typically on SD with imported('backups') as bk: await bk.make_complete_backup()
async def electrum_skeleton_step2(_1, _2, item): # pick a semi-random file name, render and save it. with imported('backups') as bk: addr_fmt = item.arg await bk.make_json_wallet( 'Electrum wallet', lambda: bk.generate_electrum_wallet(addr_fmt))
async def electrum_skeleton_step2(_1, _2, item): # pick a semi-random file name, render and save it. with imported('backups') as bk: await bk.make_electrum_wallet(addr_type=item.arg)
async def doit(self, *a, have_key=None): # make the wallet. from main import dis try: from chains import current_chain import tcc from serializations import hash160 from stash import blank_object if not have_key: # get some random bytes await ux_dramatic_pause("Picking key...", 2) privkey = tcc.secp256k1.generate_secret() else: # caller must range check this already: 0 < privkey < order privkey = have_key # calculate corresponding public key value pubkey = tcc.secp256k1.publickey(privkey, True) # always compressed style dis.fullscreen("Rendering...") # make payment address digest = hash160(pubkey) ch = current_chain() if self.is_segwit: addr = tcc.codecs.bech32_encode(ch.bech32_hrp, 0, digest) else: addr = tcc.codecs.b58_encode(ch.b58_addr + digest) wif = tcc.codecs.b58_encode(ch.b58_privkey + privkey + b'\x01') if self.can_do_qr(): with imported('uqr') as uqr: # make the QR's now, since it's slow is_alnum = self.is_segwit qr_addr = uqr.make( addr if not is_alnum else addr.upper(), min_version=4, max_version=4, encoding=(uqr.Mode_ALPHANUMERIC if is_alnum else 0)) qr_wif = uqr.make(wif, min_version=4, max_version=4, encoding=uqr.Mode_BYTE) else: qr_addr = None qr_wif = None # Use address as filename. clearly will be unique, but perhaps a bit # awkward to work with. basename = addr dis.fullscreen("Saving...") with CardSlot() as card: fname, nice_txt = card.pick_filename( basename + ('-note.txt' if self.template_fn else '.txt')) with open(fname, 'wt') as fp: self.make_txt(fp, addr, wif, privkey, qr_addr, qr_wif) if self.template_fn: fname, nice_pdf = card.pick_filename(basename + '.pdf') with open(fname, 'wb') as fp: self.make_pdf(fp, addr, wif, qr_addr, qr_wif) else: nice_pdf = '' # Half-hearted attempt to cleanup secrets-contaminated memory # - better would be force user to reboot # - and yet, we just output the WIF to SDCard anyway blank_object(privkey) blank_object(wif) del qr_wif except CardMissingError: await needs_microsd() return except Exception as e: await ux_show_story('Failed to write!\n\n\n' + str(e)) return await ux_show_story('Done! Created file(s):\n\n%s\n\n%s' % (nice_txt, nice_pdf))