# SPDX-FileCopyrightText: 2020 Foundation Devices, Inc. # SPDX-License-Identifier: GPL-3.0-or-later # # SPDX-FileCopyrightText: 2018 Coinkite, Inc. # SPDX-License-Identifier: GPL-3.0-only # # (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard # and is covered by GPLv3 license found in COPYING. # # actions.py # # Every function here is called directly by a menu item. They should all be async. # import pyb import version import gc from files import CardMissingError, CardSlot # import main from uasyncio import sleep_ms import common from common import settings, system, noise, dis from utils import (UXStateMachine, imported, pretty_short_delay, xfp2str, to_str, truncate_string_to_width, set_next_addr, scan_for_address, get_accounts, run_chooser, make_account_name_num, is_valid_address, save_next_addr, needs_microsd, format_btc_address, is_all_zero, bytes_to_hex_str, split_to_lines, is_valid_btc_address, do_address_verify) from wallets.utils import get_export_mode, get_addr_type_from_address, get_deriv_path_from_addr_type_and_acct from ux import (the_ux, ux_confirm, ux_enter_pin, ux_enter_text, ux_scan_qr_code, ux_shutdown, ux_show_story, ux_show_story_sequence, ux_show_text_as_ur, ux_show_word_list) from se_commands import * from data_codecs.qr_type import QRType import trezorcrypto from seed_check_ux import SeedCheckUX async def about_info(*a): from common import system from display import FontTiny from utils import swab32 while True: serial = system.get_serial_number() my_xfp = settings.get('xfp', 0) xpub = settings.get('xpub', None) msg = ''' Master Fingerprint: {xfp} Reversed Fingerprint: {rev_xfp} Master XPUB: {xpub} Serial Number: {serial}'''.format(xfp=xfp2str(my_xfp) if my_xfp else '', rev_xfp=xfp2str(swab32(my_xfp)) if my_xfp else '', xpub=xpub if xpub != None else '', serial=serial) result = await ux_show_story(msg, center=True, center_vertically=True, font=FontTiny, right_btn='REGULATORY') if result == 'y': await regulatory_info() else: return async def regulatory_info(): from display import FontTiny msg = """\ Passport Foundation Devices 6 Liberty Square #6018 Boston, MA 02109 USA""" await ux_show_story(msg, title='Regulatory', center=True, font=FontTiny, overlay=(None, 303 - 34 - 72, 'fcc_ce_logos')) # async def account_info(*a): # # show the XPUB, and other useful information # import common # import stash # from display import FontTiny # # xfp = settings.get('xfp', 0) # if xfp == None: # xfp = '' # else: # xfp = xfp2str(xfp) # # # Can only get these values if the derivation path is known # xpub = '' # path = '' # if common.active_account.deriv_path: # with stash.SensitiveValues() as sv: # path = common.active_account.deriv_path # node = sv.derive_path(path) # xpub = sv.chain.serialize_public(node, common.active_account.addr_type) # print('account_info(): xpub={}'.format(xpub)) # # msg = ''' # Account Number: # {acct_num} # # Derivation Path: # {path} # # Account Fingerprint: # {xfp} # # Account XPUB: # {xpub}'''.format(acct_num=common.active_account.acct_num, # path=path, # xfp=xfp, # xpub=xpub) # # await ux_show_story( # msg, # title=common.active_account.name, # center=True, # font=FontTiny) async def rename_account(menu, label, item): from utils import account_exists, do_rename_account from constants import MAX_ACCOUNT_NAME_LEN account = common.active_account original_name = account.get('name') new_name = original_name while True: new_name = await ux_enter_text('Rename', label="Enter account name", initial_text=new_name, right_btn='RENAME', max_length=MAX_ACCOUNT_NAME_LEN) if new_name == None or new_name == original_name: # User selected BACK or selected RENAME with the same exact name return # See if an account with this name already exists if account_exists(new_name): await ux_show_story('An account with the name "{}" already exists. Please choose a different name.'.format(new_name), title='Duplicate', center=True, center_vertically=True, right_btn='EDIT') continue # Get the accounts and replace the name and save it await do_rename_account(account.get('acct_num'), new_name) # Pop so we skip over the sub-menu for the account the_ux.pop() return async def delete_account(menu, label, item): from utils import do_delete_account, make_account_name_num from ux import the_ux account = common.active_account # Confirm the deletion name_num = make_account_name_num(account.get('name'), account.get('acct_num')) if not await ux_confirm('Are you sure you want to delete this account?\n\n{}'.format(name_num)): return await do_delete_account(account.get('acct_num')) # Pop so we skip over the sub-menu for the account we just deleted the_ux.pop() class VerifyAddressUX(UXStateMachine): def __init__(self): # States self.SELECT_ACCOUNT = 1 self.SELECT_SIG_TYPE = 2 self.VERIFY_ADDRESS = 3 # print('VerifyAddressUX init') super().__init__(self.SELECT_ACCOUNT) self.acct_num = None self.sig_type = None self.multisig_wallet = None # Account chooser def account_chooser(self): choices = [] values = [] accounts = get_accounts() accounts.sort(key=lambda a: a.get('acct_num', 0)) for acct in accounts: acct_num = acct.get('acct_num') account_name_num = make_account_name_num(acct.get('name'), acct_num) choices.append(account_name_num) values.append(acct_num) def select_account(index, text): self.acct_num = values[index] return 0, choices, select_account # Select the sig type and if multisig, the specific multisig wallet def sig_type_chooser(self): from multisig import MultisigWallet choices = ['Single-sig'] values = ['single-sig'] num_multisigs = MultisigWallet.get_count() for ms_idx in range(num_multisigs): ms = MultisigWallet.get_by_idx(ms_idx) choices.append('%d/%d: %s' % (ms.M ,ms.N, ms.name)) values.append(ms) def select_sig_type(index, text): if index == 0: self.sig_type = 'single-sig' self.multisig_wallet = None else: self.sig_type = 'multisig' self.multisig_wallet = values[index] return 0, choices, select_sig_type async def show(self): while True: # print('show: state={}'.format(self.state)) if self.state == self.SELECT_ACCOUNT: self.acct_num = None accounts = get_accounts() if len(accounts) == 1: self.acct_num = 0 self.goto(self.SELECT_SIG_TYPE, save_curr=False) # Don't save this since we're skipping this state continue await run_chooser(self.account_chooser, 'Account', show_checks=False) if self.acct_num == None: return self.goto(self.SELECT_SIG_TYPE) elif self.state == self.SELECT_SIG_TYPE: # Multisig only possible for account 0, so skip this if not account 0 if self.acct_num > 0: self.sig_type = 'single-sig' self.goto(self.VERIFY_ADDRESS, save_curr=False) # Don't save this since we're skipping this state continue # Choose a wallet from the available list multisigs = settings.get('multisig', []) if len(multisigs) == 0: self.sig_type = 'single-sig' else: await run_chooser(self.sig_type_chooser, 'Type', show_checks=False) if self.sig_type == None: if not self.goto_prev(): # Nothing to return back to, so we must have skipped one or more steps...we're done return continue # print('self.sig_type={}'.format(self.sig_type)) self.goto(self.VERIFY_ADDRESS) elif self.state == self.VERIFY_ADDRESS: # Scan the address to be verified - should be a normal QR code system.turbo(True); address = await ux_scan_qr_code('Verify Address') system.turbo(False) if address == None: return address, is_valid_btc = await is_valid_btc_address(address) if is_valid_btc == False: if not self.goto_prev(): return continue # Get the address type from the address is_multisig = self.sig_type == 'multisig' # print('address={} acct_num={} is_multisig={}'.format(address, self.acct_num, is_multisig)) addr_type = get_addr_type_from_address(address, is_multisig) deriv_path = get_deriv_path_from_addr_type_and_acct(addr_type, self.acct_num, is_multisig) result = await do_address_verify(self.acct_num, address, addr_type, deriv_path, self.multisig_wallet) if result == 'x': if not self.goto_prev(): return else: # User asked to stop searching return async def verify_address(*a): verify_address_ux = VerifyAddressUX() await verify_address_ux.show() async def update_firmware(*a): # Upgrade via microSD card # - search for a particular file # - verify it lightly # - erase serial flash # - copy it over (slow) # - reboot into bootloader, which finishes install from common import sf, dis from constants import FW_HEADER_SIZE, FW_ACTUAL_HEADER_SIZE, FW_MAX_SIZE import trezorcrypto # Don't show any files that are pubkeys def no_pubkeys(filename): return not filename.endswith('-pub.bin') fn = await file_picker('On the next screen, select the firmware file you want to install.', suffix='.bin', title='Select File', taster=no_pubkeys) # print('\nselected fn = {}\n'.format(fn)) if not fn: return failed = None system.turbo(True) with CardSlot() as card: with open(fn, 'rb') as fp: import os offset = 0 s = os.stat(fn) size = s[6] if size < FW_HEADER_SIZE: await ux_show_story('Firmware file is too small.', title='Error', left_btn='BACK', right_btn='OK', center=True, center_vertically=True) return if size > FW_MAX_SIZE: await ux_show_story('Firmware file is too large.', title='Error', left_btn='BACK', right_btn='OK', center=True, center_vertically=True) return # Read the header header = fp.read(FW_HEADER_SIZE) if len(header) != FW_HEADER_SIZE: system.turbo(False) await ux_show_story('Firmware file is too small, and the system misreported its size.', title='Error', left_btn='BACK', right_btn='OK', center=True, center_vertically=True) return # Validate the header is_valid, version, error_msg, is_user_signed = system.validate_firmware_header(header) if not is_valid: system.turbo(False) await ux_show_story('Firmware header is invalid.\n\n{}'.format(error_msg), title='Error', left_btn='BACK', right_btn='OK', center=True, center_vertically=True) return if is_user_signed: pubkey_result, pubkey = read_user_firmware_pubkey() if not pubkey_result or is_all_zero(pubkey): system.turbo(False) await ux_show_story('Install a Developer PubKey before loading non-Foundation firmware.\n\n', title='Error', left_btn='BACK', right_btn='OK', center=True, center_vertically=True) return system.turbo(False) # Give the user a chance to confirm/back out if not await ux_confirm('Please make sure your Passport is backed up before proceeding.\n\n' + 'Are you sure you want to update the firmware?\n\nNew Version:\n{}'.format(version), title='Update', scroll_label='MORE'): return if not await ux_confirm('Do not remove the batteries or shutdown Passport during the firmware update.\n\nWe recommend using fresh batteries.', title='Reminder', negative_btn='CANCEL', positive_btn='OK'): return # Start the update system.turbo(True) # copy binary into serial flash fp.seek(offset) # Calculate the update request hash so that the booloader knows this was requested by the user, not # injected into SPI flash by some external attacker. # Hash the firmware header header_hash = bytearray(32) # Only hash the bytes that contain the passport_firmware_header_t to match what's hashed in the bootloader firmware_header = header[0:FW_ACTUAL_HEADER_SIZE] system.sha256(firmware_header, header_hash) system.sha256(header_hash, header_hash) # Double sha # Get the device hash device_hash = bytearray(32) system.get_device_hash(device_hash) # Combine them s = trezorcrypto.sha256() s.update(header_hash) s.update(device_hash) # Result update_hash = s.digest() # Erase first page sf.sector_erase(0) while sf.is_busy(): await sleep_ms(10) buf = bytearray(256) # must be flash page size buf[0:32] = update_hash # Copy into the buf we'll use to write to SPI flash sf.write(0, buf) # Need to write the entire page of 256 bytes # Start one page in so that we can use the first page for storing a hash. # The hash combines the firmware hash with the device hash. pos = 256 update_display = 0 while pos <= size + 256: # print('pos = {}'.format(pos)) # Update progress bar every 50 flash pages if update_display % 50 == 0: dis.splash(message='Preparing Update...', progress=(pos-256)/size) update_display += 1 here = fp.readinto(buf) if not here: break if pos % 4096 == 0: # erase here sf.sector_erase(pos) while sf.is_busy(): await sleep_ms(10) sf.write(pos, buf) # full page write: 0.6 to 3ms while sf.is_busy(): await sleep_ms(1) pos += here if failed: system.turbo(False) await ux_show_story(failed, title='Sorry!') return # Save an entry to the settings indicating that we are doing an update (curr_version, _, _, _) = system.get_software_info() settings.set('update', '{}->{}'.format(curr_version, version)) # old_version->new_version await settings.save() # NOTE: We intentionally stay in turbo mode here as we reboot to keep the final splash display fast. # Bootloader will go back to top speed anyway. # continue process... # print("RESTARTING!") # Show final progress bar at 100% and change message dis.splash(message='Restarting...', progress=1) # TODO: Make 0-100 to be consistent with progress bar system.turbo(False) await sleep_ms(1000) import machine machine.reset() async def reset_self(*a): import machine machine.soft_reset() # NOT REACHED async def initial_pin_setup(*a): from common import pa, dis, loop # TODO: Move the messaging into EnterInitialPinUX state machine # First time they select a PIN while 1: # ch = await ux_show_story('''\ # Passport uses a PIN from 6 to 12 digits long. # # There is an additional security feature that you can use. After entering 2 or more digits of your PIN, press and hold the VALIDATE \ # button and Passport will show you two Security Words unique to your device and PIN prefix. # # Remember these two words, and remember how many digits you entered before checking them. When logging in, you can \ # repeat this process and you should see the same words. # # If you see different words, then either: # # 1. You entered a different number of digits # # 2. You entered the wrong first digits of your PIN # # 3. Your Passport has been tampered with''', title='PIN Info', scroll_label='MORE') # if ch == 'y': while 1: ch = await ux_show_story('''\ Now it's time to set your 6-12 digit PIN. There is no way to recover a lost PIN or reset Passport. Please record your PIN somewhere safe.''', title='Set PIN', scroll_label='MORE') if ch != 'y': break # Enter the PIN from login_ux import EnterInitialPinUX new_pin_ux = EnterInitialPinUX() await new_pin_ux.show() pin = new_pin_ux.pin # print('pin = {}'.format(pin)) if pin is None: continue # New pin is being saved dis.fullscreen("Saving PIN...") system.show_busy_bar() 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 except Exception as e: print("Exception: {}".format(e)) finally: system.hide_busy_bar() return async def login_countdown(minutes): # show a countdown, which may need to # run for multiple **days** from common import dis from display import FontSmall sec = minutes * 60 while sec: dis.clear() y = 0 dis.text(None, y, 'Login countdown in', font=FontSmall) y += 14 dis.text(None, y, 'effect. Must wait:', font=FontSmall) y += 14 y += 5 dis.text(None, y, pretty_short_delay(sec), font=FontSmall) dis.show() dis.busy_bar(1) await sleep_ms(1000) sec -= 1 dis.busy_bar(0) async def block_until_login(*a): # # Force user to enter a valid PIN. # from login_ux import LoginUX from common import pa, loop, dis # print('pa.is_successful() = {}'.format(pa.is_successful())) while not pa.is_successful(): login_ux = LoginUX() # try: await login_ux.show() # except Exception as e: # print('ERROR when logging in: {}'.format(e)) # # not allowed! # pass # print('!!!!LOGGED IN!!!!') system.turbo(False) async def create_new_seed(*a): from ubinascii import hexlify as b2a_hex import seed system.show_busy_bar() wallet_seed_bytes = await seed.create_new_wallet_seed() # print('wallet_seed_bytes = {}'.format(b2a_hex(wallet_seed_bytes))) mnemonic_str = trezorcrypto.bip39.from_data(wallet_seed_bytes) # print('mnemonic = {}'.format(mnemonic_str)) mnemonic_words = mnemonic_str.split(' ') # Save the wallet so we can work with it (needs to be saved for backup to work) await seed.save_wallet_seed(wallet_seed_bytes) # Update xpub/xfp in settings after creating new wallet import stash with stash.SensitiveValues() as sv: sv.capture_xpub() system.hide_busy_bar() while True: ch = await ux_show_story('''Now let's create a backup of your seed. We recommend backing up Passport to the two included microSD cards. Experienced users can always view and record the 24-word seed in the Advanced settings menu.''', title='Backup') if ch == 'x': if await ux_confirm("Are you sure you want to cancel the backup?\n\nWithout a microSD backup or the seed phrase, you won't be able to recover your funds."): # Go back to the outer loop and show the selection again break # Ensure microSD card is inserted before continuing try: with CardSlot() as card: # TODO: Call the export.make_complete_backup() directly and have it return True/False to indicate if the backup completed await make_microsd_backup() break except CardMissingError: ch = await needs_microsd() if ch == 'x': continue await goto_top_menu() async def restore_wallet_from_seed(menu, label, item): result = await ux_show_story('''On the next screen you'll be able to restore your seed using predictive text input. If you'd like to enter "car" for example, type 2-2-7 and select "car" from the dropdown.''', title='Restore Seed') if result == 'x': return fake_it = False if fake_it: pass else: from seed_entry_ux import SeedEntryUX seed_phrase_entry = SeedEntryUX(seed_len=item.arg) await seed_phrase_entry.show() if not seed_phrase_entry.is_seed_valid: return # Seed is valid, so go ahead and convert the mnemonic to seed bits and save it mnemonic = ' '.join(seed_phrase_entry.words) # print('mnemonic = {}'.format(mnemonic)) await handle_seed_data_format(mnemonic) async def handle_seed_data_format(mnemonic): import seed from foundation import bip39 from common import dis, pa from ubinascii import hexlify as b2a_hex entropy = bytearray(33) # Includes and extra byte for the checksum bits # Don't let them import seed if there is already a wallet. if not pa.is_secret_blank(): await ux_show_story('''Unable to import seed phrase because this Passport is alread configured with a seed. First use Advanced > Erase Passport to remove the current seed.''', right_btn='OK') return False bip = bip39() len = bip.mnemonic_to_entropy(mnemonic, entropy) if len == 264: # 24 words x 11 bits each trim_pos = 32 elif len == 198: # 18 words x 11 bits each trim_pos = 24 elif len == 132: # 12 words x 11 bits each trim_pos = 16 entropy = entropy[:trim_pos] # Trim off the excess (including checksum bits) # print('entropy = {}'.format(b2a_hex(entropy))) # Entropy is now the right length - SecretStash.encode() adds a marker byte to indicate length of the secret # so we can decode it correctly. await seed.save_wallet_seed(entropy) # print('Seed was imported successfully!') # Update xpub/xfp in settings after creating new wallet import stash with stash.SensitiveValues() as sv: sv.capture_xpub() # Show post-creation message dis.fullscreen('Successfully Imported!') await sleep_ms(1000) await goto_top_menu() return True async def erase_wallet(menu, label, item): # Erase the seed words, and private key from this wallet! # This is super dangerous for the customer's money. import seed from common import pa if not await ux_confirm('Are you sure you want to erase this Passport? All funds will be lost if not backed up.'): return if not await ux_confirm('Without a proper backup, this action will cause you to lose all funds associated with this device.\n\n' + 'Please confirm that you understand these risks.', scroll_label='MORE', negative_btn='BACK', positive_btn='CONFIRM'): return await seed.erase_wallet(item.arg) # NOT REACHED -- reset happens async def view_seed_words(*a): import stash from common import dis if not await ux_confirm( 'The next screen will show your seed words and, if defined, your passphrase.\n\n' + 'Anyone who knows these words can control your funds.\n\n' + 'Do you want to display this sensitive information?', scroll_label='MORE', center=False): return dis.fullscreen('Retrieving Seed...') system.show_busy_bar() try: with stash.SensitiveValues() as sv: assert sv.mode == 'words' # protected by menu item predicate words = trezorcrypto.bip39.from_data(sv.raw).split(' ') msg = 'Seed words (%d):\n' % len(words) msg += '\n'.join('%2d: %s' % (i+1, w) for i, w in enumerate(words)) pw = stash.bip39_passphrase if pw: msg += '\n\nPassphrase:\n%s' % stash.bip39_passphrase system.hide_busy_bar() ch = await ux_show_story(msg, sensitive=True, right_btn='VERIFY') if ch == 'y': seed_check = SeedCheckUX(seed_words=words, title='Verify Seed') await seed_check.show() return stash.blank_object(msg) except Exception as e: print('Exception: {}'.format(e)) system.hide_busy_bar() # Unable to read seed! await ux_show_story('Unable to retrieve seed.') async def start_login_sequence(): # Boot up login sequence here. # from common import pa while not pa.is_successful(): # always get a PIN and login first await block_until_login() async def goto_top_menu(*a): # Start/restart menu system from menu import MenuSystem from flow import MainMenu, NoSeedMenu from common import pa # print('pa.is_secret_blank()={}'.format(pa.is_secret_blank())) if pa.is_secret_blank(): m = MenuSystem(NoSeedMenu) else: m = MenuSystem(MainMenu) the_ux.reset(m) return m SENSITIVE_NOT_SECRET = ''' The file created is sensitive in terms of privacy, but should not \ compromise your funds directly.''' PICK_ACCOUNT = '''\n\nPress 1 to enter a non-zero account number.''' async def export_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, title='Export', negative_btn='BACK', positive_btn='CONTINUE'): return # pick a semi-random file name, save it. with imported('export') as exp: await exp.make_summary_file() def electrum_export_story(background=False): # saves memory being in a function return ('''\ This saves a skeleton Electrum wallet file onto the microSD card. \ You can then open that file in Electrum without ever connecting this Passport to a computer.\n ''' + (background or 'Choose an address type for the wallet on the next screen.'+PICK_ACCOUNT) + SENSITIVE_NOT_SECRET) # async def electrum_skeleton(*a): # # save xpub, and some other public details into a file: NOT MULTISIG # # ch = await ux_show_story(electrum_export_story()) # # account_num = 0 # if ch == '1': # account_num = await ux_enter_number('Account Number:', 9999) # elif ch != 'y': # return # # # pick segwit or classic derivation+such # from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH # from menu import MenuSystem, MenuItem # # # Ordering and terminology from similar screen in Electrum. I prefer # # 'classic' instead of 'legacy' personally. # rv = [] # # rv.append(MenuItem("Legacy (P2PKH)", f=electrum_skeleton_step2, # arg=(AF_CLASSIC, account_num))) # rv.append(MenuItem("P2SH-Segwit", f=electrum_skeleton_step2, # arg=(AF_P2WPKH_P2SH, account_num))) # rv.append(MenuItem("Native Segwit", f=electrum_skeleton_step2, # arg=(AF_P2WPKH, account_num))) # # return MenuSystem(rv, title="Electrum") # 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 # # ch = 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 Passport to a computer.\ # ''' + PICK_ACCOUNT + SENSITIVE_NOT_SECRET) # # account_num = 0 # if ch == '1': # account_num = await ux_enter_number('Account Number:', 9999) # elif ch != 'y': # return # # # no choices to be made, just do it. # with imported('export') as exp: # dis.fullscreen('Generating...') # body = exp.make_bitcoin_core_wallet(account_num) # await write_text_file('bitcoin-core.txt', body, 'Bitcoin Core') # async def electrum_skeleton_step2(_1, _2, item): # # pick a semi-random file name, render and save it. # with imported('export') as exp: # addr_fmt, account_num = item.arg # await exp.make_json_wallet('Electrum wallet', lambda: exp.generate_electrum_wallet(addr_fmt, account_num)) # async def generic_skeleton(*a): # # like the Multisig export, make a single JSON file with # # basically all useful XPUB's in it. # # if await ux_show_story('''\ # Saves JSON file onto MicroSD card, with XPUB values that are needed to watch typical \ # single-signer UTXO associated with this Coldcard.''' + SENSITIVE_NOT_SECRET) != 'y': # return # # account_num = await ux_enter_number('Account Number:', 9999) # # # no choices to be made, just do it. # import export # await export.make_json_wallet('Generic Export', # lambda: export.generate_generic_export(account_num), # 'coldcard-export.json') # 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 # # 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 Passport to a computer.\ # ''' + SENSITIVE_NOT_SECRET) != 'y': # return # # # no choices to be made, just do it. # with imported('export') as exp: # await exp.make_json_wallet('Wasabi wallet', lambda: exp.generate_wasabi_wallet(), 'new-wasabi.json') async def make_microsd_backup(*A): # save everything, using a password, into single encrypted file, typically on SD with imported('export') as exp: await exp.make_complete_backup() async def verify_microsd_backup(*A): # check most recent backup is "good" # read 7z header, and measure checksums with imported('export') as exp: fn = await file_picker('Select the backup to verify.', suffix='.7z', max_size=exp.MAX_BACKUP_FILE_SIZE, folder_path='/sd/backups') if fn: # do a limited CRC-check over encrypted file await exp.verify_backup_file(fn) EMPTY_RESTORE_MSG = '''\ Before restoring from a backup, you must erase this Passport. Make sure your device is backed up. Navigate to Advanced > Erase Passport.''' FULL_PARTIAL_MSG = '''A wallet seed already exists. Do you want to perform a FULL restore or a PARTIAL restore of accounts only?''' async def restore_microsd_backup(*A): from common import pa partial_restore = False if not pa.is_secret_blank(): await ux_show_story(EMPTY_RESTORE_MSG) return # if not pa.is_secret_blank(): # result = await ux_show_story(FULL_PARTIAL_MSG, left_btn='FULL', right_btn='PARTIAL') # if result == 'x': # await ux_show_story(EMPTY_RESTORE_MSG) # return # else: # partial_restore = True # TODO: Insert step here to pick a backups-* folder when we add the XFP to the folder name # Choose a backup file -- must be in 7z format fn = await file_picker('Select the backup to restore and then enter the six-word password.', suffix='.7z', max_size=10000, folder_path='/sd/backups') if fn: with imported('export') as exp: await exp.restore_complete(fn, partial_restore) async def format_sd_card(*A): if not await ux_confirm('Erase and reformat the microSD card.', negative_btn='BACK', positive_btn='FORMAT'): return from files import format_microsd_card system.turbo(True) format_microsd_card() system.turbo(False) async def list_files(*a): # list files, don't do anything with them? fn = await file_picker('List all files on the microSD card. Select a file to show the SHA256 hash.', min_size=0) if not fn: return from utils import B2A chk = trezorcrypto.sha256() system.show_busy_bar() try: with CardSlot() as card: with open(fn, 'rb') as fp: while 1: data = fp.read(1024) if not data: break chk.update(data) except CardMissingError: system.hide_busy_bar() await needs_microsd() return basename = fn.rsplit('/', 1)[-1] digest = B2A(chk.digest()) system.hide_busy_bar() await ux_show_story('File:\n %s\n\n%s' % (basename, digest), title='SHA256') async def file_picker(msg, suffix=None, min_size=None, max_size=None, taster=None, choices=None, none_msg=None, title='Select', folder_path=None): # present a menu w/ a list of files... to be read # - optionally, enforce a max size, and provide a "tasting" function # - if msg==None, don't prompt, just do the search and return list # - if choices is provided; skip search process # - escape: allow these chars to skip picking process from menu import MenuSystem, MenuItem import uos from utils import get_filesize, folder_exists system.turbo(True) if choices is None: choices = [] try: with CardSlot() as card: sofar = set() if folder_path == None: folder_path = card.get_paths() else: folder_path = [folder_path] for path in folder_path: # If the folder doesn't exist, skip it (e.g., if /sd/backups/ doesn't exist) if not folder_exists(path): continue files = uos.ilistdir(path) for fn, ftype, *var in files: # print("fn={} ftype={} var={} suffix={}".format(fn, ftype, var, suffix)) if ftype == 0x4000: # ignore subdirs continue if suffix and not fn.lower().endswith(suffix): # wrong suffix continue if fn[0] == '.': continue full_fname = path + '/' + fn # Consider file size # sigh, OS/filesystem variations file_size = var[1] if len( var) == 2 else get_filesize(full_fname) if min_size is not None and file_size < min_size: continue if max_size is not None and file_size > max_size: continue if taster is not None: try: yummy = taster(full_fname) except IOError: # print("fail: %s" % full_fname) yummy = False if not yummy: continue label = fn while label in sofar: # just the file name isn't unique enough sometimes? # - shouldn't happen anymore now that we dno't support internal FS # - unless we do muliple paths label += path.split('/')[-1] + '/' + fn sofar.add(label) choices.append((label, path, fn)) except CardMissingError: system.turbo(False) # don't show anything if we're just gathering data if msg is not None: await needs_microsd() return None system.turbo(False) if msg is None: return choices if not choices: msg = none_msg or 'Unable to find an applicable file on the microSD card.' if not none_msg: if suffix: msg += '\n\nThe filename must end in "%s".' % suffix await ux_show_story(msg, center=True, center_vertically=True, title=title) return ch = await ux_show_story(msg, center=True, center_vertically=True, title=title) if ch == 'x': return picked = [] async def clicked(_1, _2, item): picked.append('/'.join(item.arg)) the_ux.pop() choices.sort() items = [MenuItem(label, f=clicked, arg=(path, fn)) for label, path, fn in choices] menu = MenuSystem(items, title='Select File') the_ux.push(menu) await menu.interact() return picked[0] if picked else None async def sign_tx_from_sd(*a): # Check if any signable in SD card, if so do it from public_constants import MAX_TXN_LEN import stash if stash.bip39_passphrase: title = '[%s]' % xfp2str(settings.get('xfp')) else: title = 'Select PSBT' def is_psbt(filename): if '-signed' in filename.lower(): return False with open(filename, 'rb') as fd: taste = fd.read(10) if taste[0:5] == b'psbt\xff': return True if taste[0:10] == b'70736274ff': # hex-encoded return True if taste[0:6] == b'cHNidP': # base64-encoded return True return False choices = await file_picker(None, suffix='psbt', min_size=50, max_size=MAX_TXN_LEN, taster=is_psbt) if not choices: await ux_show_story("""\ Copy an unsigned PSBT transaction onto the microSD card and insert it into Passport. """, title=title) return if len(choices) == 1: # skip the menu label, path, fn = choices[0] input_psbt = path + '/' + fn else: input_psbt = await file_picker('Choose a PSBT to sign.', choices=choices, title=title) if not input_psbt: return # start the process from auth import sign_psbt_file await sign_psbt_file(input_psbt) async def sign_message_on_sd(*a): # Menu item: choose a file to be signed (as a short text message) # def is_signable(filename): if '-signed' in filename.lower(): return False with open(filename, 'rt') as fd: lines = fd.readlines() return (1 <= len(lines) <= 5) fn = await file_picker('Choose text file to sign.', suffix='txt', min_size=2, max_size=500, taster=is_signable, none_msg='No suitable files found. Must be one line of text, in a .TXT file, optionally followed by a subkey derivation path on a second line.') if not fn: return # start the process from auth import sign_txt_file await sign_txt_file(fn) async def change_pin(*a): # Help user change pins with appropriate warnings. from login_ux import ChangePinUX change_pin_ux = ChangePinUX() await change_pin_ux.show() # Reset pin to blank (all zeroes) # async def set_blank_pin(*a): # from common import pa # # args = {} # old_pin = await ux_enter_pin(title='Enter Old PIN', heading='Old PIN') # args['old_pin'] = old_pin.encode() # blank_pin = [32] * 0 # args['new_pin'] = bytearray(blank_pin) # try: # pa.change(**args) # except Exception as e: # print('Exception: {}'.format(e)) async def show_version(*a): # show firmware, bootloader versions from utils import get_month_str from utime import localtime system.turbo(True) (fw_version, fw_timestamp, boot_counter, user_signed) = system.get_software_info() time = localtime(fw_timestamp) fw_date = '{} {}, {}'.format(get_month_str(time[1]), time[2], time[0]-30) msg = '''\ Current Firmware: v{fw_version} {fw_date}'''.format(fw_version=fw_version, fw_date=fw_date) if user_signed: msg += '\nSigned by User' msg += ''' Boot Counter: {boot_counter}\ '''.format(boot_counter=boot_counter) system.turbo(False) await ux_show_story(msg, center=True, center_vertically=True) async def import_multisig_from_sd(*a): # pick text file from SD card, import as multisig setup file def possible(filename): with open(filename, 'rt') as fd: for ln in fd: if 'pub' in ln: return True fn = await file_picker('Select multisig wallet file to import (.txt)', suffix='.txt', min_size=100, max_size=20*200, taster=possible) if not fn: return system.turbo(True); try: with CardSlot() as card: with open(fn, 'rt') as fp: data = fp.read() except CardMissingError: system.turbo(False); await needs_microsd() return # print('data={}'.format(data)) from auth import maybe_enroll_xpub from utils import problem_file_line, show_top_menu from export import offer_backup try: possible_name = (fn.split('/')[-1].split('.'))[0] maybe_enroll_xpub(config=data, name=possible_name) await show_top_menu() # wait for interaction with the enroll system.turbo(False); await offer_backup() except Exception as e: system.turbo(False); await ux_show_story('Unable to import multisig configuration.\n\n{}\n{}'.format(e, problem_file_line(e)), title='Error') async def import_multisig_from_qr(*a): system.turbo(True); data = await ux_scan_qr_code('Import Multisig') system.turbo(False); if data != None: await handle_import_multisig_config(data) async def handle_import_multisig_config(data): from auth import maybe_enroll_xpub from export import offer_backup from utils import show_top_menu # Coming from a UR code we need to decode from bytes if isinstance(data, (bytes, bytearray)): data = data.decode('utf-8') try: common.is_new_wallet_a_duplicate = False possible_name = "ms" maybe_enroll_xpub(config=data, name=possible_name) await show_top_menu() # wait for interaction with the enroll if not common.is_new_wallet_a_duplicate: await offer_backup() except Exception as e: await ux_show_story('Unable to import multisig configuration.\n\n'+str(e), title='Error') # Scan QR code and magically determine what to do async def magic_scan(menu, label, item): from common import dis from data_codecs.data_format import get_flow_for_data title = item.arg while True: system.turbo(True) data = await ux_scan_qr_code(title) system.turbo(False) # Run the samplers to figure out what type of data was scanned and run the corresponding flow, if any if data == None: return flow = get_flow_for_data(data) if flow == None: # Show error to user result = await ux_show_story('Unrecognized data format.', title='Error', right_btn='RETRY', center=True, center_vertically=True) if result == 'y': continue return else: # Run the flow retry = await flow(data) if retry: continue return # Handler for psbt async def handle_psbt_data_format(data): from common import dis if data != None: try: from auth import sign_psbt_buf gc.collect() # print('Available RAM: psbt 1 = {}'.format(gc.mem_free())) # The data can be a string or may already be a bytes object if isinstance(data, bytes): data_buf = data else: data_buf = bytes(data, 'utf-8') gc.collect() # Try to avoid excessive fragmentation # print('Available RAM: psbt 2 = {}'.format(gc.mem_free())) # print("data_buf={}".format(data_buf)) system.show_busy_bar() dis.fullscreen('Analyzing...') await sign_psbt_buf(data_buf) except Exception as e: # print('Signing exception:{}'.format(e)) result = await ux_show_story('Error signing transaction:\n\n{}'.format(e), title='Error', right_btn='RETRY') return result == 'y' finally: system.hide_busy_bar() return False def read_user_firmware_pubkey(): pubkey = bytearray(64) system.turbo(True) result = system.get_user_firmware_pubkey(pubkey) system.turbo(False) return result, pubkey async def enter_passphrase(menu, label, item): import sys from seed import set_bip39_passphrase from constants import MAX_PASSPHRASE_LENGTH title = item.arg passphrase = await ux_enter_text(title, label="Enter Passphrase", max_length=MAX_PASSPHRASE_LENGTH) # print("Chosen passphrase = {}".format(passphrase)) if not await ux_confirm('Are you sure you want to apply the passphrase:\n\n{}'.format(passphrase)): return # Applying the passphrase takes a bit of time so show message dis.fullscreen("Applying Passphrase...") system.show_busy_bar() result = None try: err = set_bip39_passphrase(passphrase) if err: await ux_show_story('Unable to apply passphrase: {}'.format(err)) else: result = settings.get('xpub') except BaseException as exc: sys.print_exception(exc) system.hide_busy_bar() async def enter_seed_phrase(menu, label, item): from seed_entry_ux import SeedEntryUX seed_phrase_entry = SeedEntryUX(seed_len=item.arg) await seed_phrase_entry.show() # print('seed words = {}'.format(seed_phrase_entry.words)) async def sample_stories(menu, label, item): result = await ux_show_story_sequence( [ {'msg': 'Testing 1\nTesting 1\nTesting 1\nTesting 1\nTesting 1\nTesting 1\nTesting 1\nTesting 1\nTesting 1\nTesting 1\n', 'title': 'Story 1'}, {'msg': 'Testing 2', 'title': 'Story 2'}, {'msg': 'Testing 3', 'title': 'Story 3', 'right_btn': 'ACTION'} ] ) if result == 'y': # Do some action at the end pass # TODO: Go back and reimplement this as a state machine like LoginUX async def validate_passport_hw(*a): from pincodes import PinAttempt from ubinascii import unhexlify as a2b_hex from common import system if settings.get('validated_ok'): return while True: # Explain what validation is result = await ux_show_story('''\ Next, let's make sure your Passport has not been tampered with during shipping. The setup guide will direct you to a page containing a QR code. On the next screen, scan that QR code. Your Passport will show you 4 Security Words in response. Enter those 4 words in the same order into the validation page.''', title='Validation', left_btn='SHUTDOWN', scroll_label='MORE') if result == 'y': while True: # Scan a QR code system.turbo(True) qr_data = await ux_scan_qr_code('Validation') # qr_data = 'af7e2098fd626650b342398905b82a73c835213dc1b4b1ca10f783d4e4593c95' system.turbo(False) # Generate the 4 validation words # 4 words gives 2048 choose 4 possibilties = 730,862,190,080 if qr_data == None: while True: result = await ux_show_story('''No QR code was scanned. Do you want to try again, or skip validation?\n\nIMPORTANT: If you skip validation now, there will be no way to do so in the future.''', left_btn='SKIP', right_btn='RETRY') if result == 'x': # Confirm? confirmed = await ux_confirm('Are you sure you want to permanently skip supply chain validation?') if confirmed: # 2 means validation was skipped, but we won't show it again settings.set('validated_ok', 2) return else: # Break out of the inner loop and back out the the QR validation scan loop break else: # Split the data up into the challenge and the signature parts = qr_data.split(' ') # print('parts={}'.format(parts)) if len(parts) != 2: # print('ERROR: len={}'.format(len(parts))) result = await ux_show_story('The QR code is not formatted correctly. Are you sure you are ' + 'on the correct website?', left_btn='SHUTDOWN', right_btn='RETRY') if result == 'x': await ux_shutdown() else: # Retry in the outer loop - scan the QR code again break system.turbo(True) challenge = parts[0] challenge_hash = bytearray(32) system.sha256(challenge, challenge_hash) # print('challenge: {}'.format(challenge)) # print('challenge_hash: {}'.format(challenge_hash)) signature_str = parts[1] signature = a2b_hex(signature_str) # print('signature_str: {}'.format(signature_str)) # print('signature: {}'.format(signature)) system.turbo(False) # Let's make sure that this challenge was actually signed by the Foundation server if system.verify_supply_chain_server_signature(challenge_hash, signature) == False: result = await ux_show_story('The QR code you scanned does not appear to be from the ' + 'Foundation Devices validation server.\n\nPlease ensure that you navigated to the correct website.', left_btn='SHUTDOWN', right_btn='RETRY') if result == 'x': await ux_shutdown() else: # Retry in the outer loop - scan the QR code again break await ux_show_story('The QR code you scanned does not appear to be from the ' + 'Foundation Devices validation server!\n\nPlease ensure ' + 'that you navigated to the correct website.', 'Error', right_btn='OK') return # print('CHALLENGE SIGNATURE IS VALID!!!!!') # The challenge was properly signed by the Foundation server, so now generate the response words = PinAttempt.supply_chain_validation_words(a2b_hex(challenge)) # print('Validation words={}'.format(words)) numbered_words = [] for i in range(len(words)): numbered_words.append('{}. {}'.format(i+1, words[i])) result = await ux_show_word_list( 'Validate', numbered_words, heading1='Enter these words on', heading2='the validation page.', left_aligned_center=True, left_btn='INVALID', right_btn='VALID' ) if result == 'x': while True: result = await ux_show_story('''\ If the words did not match, your Passport may have been modified after it was manufactured. Please contact us at support@foundationdevices.com.''', left_btn='SHUTDOWN', right_btn='RETRY') if result == 'x': await ux_shutdown() else: # Retry in the outer loop - scan the QR code again break elif result == 'y': # Note that they confirmed the validation words were correct settings.set('validated_ok', 1) return elif result == 'x': await ux_shutdown() async def test_ur(*a): from test import TestUR test = TestUR() test.run_tests() async def test_ur_encoder(_1, _2, item): await ux_show_text_as_ur(title='Test UR Encoding', msg='Animated UR Code', qr_text=b'Y\x01\x00\x91n\xc6\\\xf7|\xad\xf5\\\xd7\xf9\xcd\xa1\xa1\x03\x00&\xdd\xd4.\x90[w\xad\xc3nO-<\xcb\xa4O\x7f\x04\xf2\xdeD\xf4-\x84\xc3t\xa0\xe1I\x13o%\xb0\x18RTYa\xd5_\x7fz\x8c\xdem\x0e.\xc4?;-\xcbdJ"\t\xe8\xc9\xe3J\xf5\xc4ty\x84\xa5\xe8s\xc9\xcf_\x96^%\xee)\x03\x9f\xdf\x8c\xa7O\x1cv\x9f\xc0~\xb7\xeb\xae\xc4n\x06\x95\xae\xa6\xcb\xd6\x0b>\xc4\xbb\xff\x1b\x9f\xfe\x8a\x9er@\x12\x93w\xb9\xd3q\x1e\xd3\x8dA/\xbbDB%o\x1eoY^\x0f\xc5\x7f\xedE\x1f\xb0\xa0\x10\x1f\xb7k\x1f\xb1\xe1\xb8\x8c\xfd\xfd\xaa\x94b\x94\xa4}\xe8\xff\xf1s\xf0!\xc0\xe6\xf6[\x05\xc0\xa4\x94\xe5\x07\x91\'\n\x00P\xa7:\xe6\x9bg%PZ.\xc8\xa5y\x14W\xc9\x87m\xd3J\xad\xd1\x92\xa5:\xa0\xdcf\xb5V\xc0\xc2\x15\xc7\xce\xb8$\x8bq|"\x95\x1ee0[V\xa3pn>\x86\xeb\x01\xc8\x03\xbb\xf9\x15\xd8\x0e\xdc\xd6MM', qr_type=QRType.UR1, qr_args=None) async def test_num_entry(*a): num = await ux_enter_text('Enter Number', label='Enter an integer', num_only=True) dis.fullscreen('Number = {}'.format(num)) await sleep_ms(2000) async def play_snake(*a): from snake import snake_game await snake_game() async def play_stacking_sats(*a): from stacking_sats import stacking_sats_game await stacking_sats_game() # Secure Element Test Actions async def se_get_config(*a): config = bytearray(128) system.dispatch(CMD_GET_SE_CONFIG, config, 0) import ubinascii s = ubinascii.hexlify(config).decode('utf8') lines = [s[i:i+16] for i in range(0, len(s), 16)] data = '\n'.join(lines) await ux_show_story("SE Config\n\n" + data) async def gen_random(_1, _2, item): from ubinascii import hexlify seed = bytearray(32) valid = noise.random_bytes(seed, item.arg) # print('Random bytes = {}'.format(hexlify(seed))) async def show_power_monitor(*a): from foundation import Powermon powermon = Powermon() for i in range(10): (current, voltage) = powermon.read() # print('current={} voltage={}'.format(current, voltage)) await sleep_ms(1000) async def show_board_rev(*a): from foundation import Boardrev boardrev = Boardrev() rev = boardrev.read() # print('Board rev={}'.format(rev)) async def erase_user_settings(*a): confirm = await ux_confirm('Are you sure you want to erase the User Settings?\n\nWhen restarting, you will be prompted to accept terms again and go through Supply Chain Authentication again.') if confirm: settings.erase_settings_flash() async def update_xpub(*a): import stash with stash.SensitiveValues() as sv: sv.capture_xpub() async def coming_soon(*a): await ux_show_story('This feature is under development. Stay tuned!', title='Coming Soon') async def dump_settings(*a): print('Current Settings:\n{}'.format(to_str(settings.curr_dict))) async def dump_flash_cache(*a): from common import flash_cache print('Current Flash Cache:\n{}'.format(to_str(flash_cache.current))) async def test_ur1_old(*a): from ur1.decode_ur import decode_ur from ur1.encode_ur import encode_ur # Encoding data = '7e4a61385e2550981b4b5633ab178eb077a30505fbd53f107ec1081e7cf0ca3c0dc0bfea5b8bfb5e6ffc91afd104c3aa756210b5dbc5118fd12c87ee04269815ba6a9968a0d0d3b7a9b631382a36bc70ab626d5670b4b48ff843f4d9a15631aa67c7aaf0ac6ce7e3bff03b2c9643e3375e47493c4e0f8635329d66fdec41b10ce74dcbf25fc15d829e7830c325643a98561f441b40a02e8353493e6afc16192fe99d90d8ca65539af77ddeaccc8943a37563a9ba83675bd5d4da7c60c9a172cf6940cbf0ec8fe04175a629932e3512c5d2aaea3cca3246f40a21ffdc33c3987dc7b880351230eb3759fe3c7dc7b2d3a20a95996ff0b7a0dba834f96beb64c14e3426fb051a936ba41569ab99c0066a6d9c0777a49e49e6cbad24d722a4c7da112432679264b9adc0a8cff9dd1fe0ee9ee2747f6a68537c389a7303a1af23c534ee6392bc17b04cf0fbce7689e66b673a440c04a9454005b0c76664639113458eb7d0902eff04d11138ce2a8ee16a9cd7c8926514efa9bd83ae7a4c139835f0fe0f68c628e0645c8524c30dfc314e825a7aa13224d98e2f7a9d12183a999bb1f28549c99a9072d99c05c24e0c84848c4fc147a094ab7b69e9cbea86952fccf15500fbb234ffe6ee6e6ded515c8016cb017ba36fb931ef276cec4ed22c1aed1495d2df3b3ce66c03f5b9ffa8434bf0e8fb149de94e050b3da178df1f76c00a366cb2801fabdf1a1e90cd3cd45ecb7a930a40b151455f76b726d552f31c21324992da257ff8bde2923dfd5d0d6b87233fae215ffacbecd96249099e7e3427d533db56cdb09c7475b4ce3314e33f43953a7370866cc11d85f00b71b15510b46c4b4fa490c660ddfeda0ceb1b8265995f7071c155ad1b57465fdc0fa81a73f9f19ac4872029d5844c1838f732e803043673e26cbc5b51297a324ff00a2d2d4222bad556b93d27c8e376e3ff8f9d37b3073410708ebb3d4dd7473d27212310b71a3c33a5c8f87f44824640e7f8970f4eda9364195c87a91172b8f085f1773641dde1ce21938746234055bc971ce2325f814e3eec60f781dd4faf52afd5be4a6b38656f7e9739f724cb7ccd4e4d01e802add3dc7b83191f894b3ee0ed752ee514d5ec55' result = encode_ur(data) if result == [ 'ur:bytes/1of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/typjqlj2vyu9uf2snqd5k43n4vtcavrh5vzst7748ug8asggre70pj3uphqtl6jm30a4umlujxhazpxr4f6kyy94m0z3rr739jr7uppxnq2m565edzsdp5ah4xmrzwp2x678p2mzd4t8pd953luy8axe59trr2n8c740ptrvul3mlupm9jty8cehter5j0zwp7rr2v5a', 'ur:bytes/2of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/vm77csd3pnn5mjljtlq4mq570qcvxfty82v9v86yrdq2qt5r2dynu6huzcvjl6vajrvv5e2nntmhmh4vejy58gm4vw5m4qm8t02afknuvry6zuk0d9qvhu8v3lsyzadx9xfjudgjchf2463uegeydaq2y8lacv7rnp7u0wyqx5frp6eht8lrclw8ktf6yz54n9hlpdaq', 'ur:bytes/3of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/mw5rf7ttadjvzn35ymas2x5ndwjp26dtn8qqv6ndnsrh0fy7f8nvhtfy6u32f376zyjryeujvju6ms9gelua68lqa60wyarldf59xlpcnfes8gd0y0znfmnrj27p0vzv7rauua5fue4kwwjypsz2j32qqkcvwenyvwg3x3vwklgfqthlqng3zwxw928wz65u6lyfyeg5', 'ur:bytes/4of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/a75mmqaw0fxp8xp47rlq76xx9rsxghy9ynpsmlp3f6p9574pxgjdnr3002w3yxp6nxdmru59f8ye4yrjmxwqtsjwpjzgfrz0c9r6p99t0d57njl2s62jln8325q0hv35llnwumnda4g4eqqkevqhhgm0hyc77fmva38dytq6a52ft5kl8v7wvmqr7kull2zrf0cw37c5', 'ur:bytes/5of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/nh55upgt8ksh3hclwmqq5dnvk2qpl27lrg0fpnfu630vk75npfqtz529tamtwfk42te3cgfjfxfd5ftllz779y3al4ws66u8yvl6ug2llt97ektzfyyeul35yl2n8k6kekcfcar4kn8rx98r8ape2wnnwzrxesgashcqkud325gtgmztf7jfp3nqmhld5r8trwpxtx2l', 'ur:bytes/6of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/wpcuz4ddrdt5vh7up75p5ule7xdvfpeq982cgnqc8rmn96qrqsm88cnvh3d4z2t6xf8lqz3d94pz9wk426un6f7gudmw8lu0n5mmxpe5zpcgaweafht5w0f8yy33pdc68se6tj8c0azgy3jqulufwr6wm2fkgx2us753zu4c7zzlzaekg8w7rn3pjwr5vg6q2k7fw88z', 'ur:bytes/7of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/xf0czn37a3s00qwaf7h49t74he9xkwr9dalfww0hyn9hen2wf5q7sq4d60w8hqcer7y5k0hqa46jaeg56hk92xd0vz4', ]: print('encode_ur() worked!') else: print('encode_ur() failed!') # Decoding workloads = [ 'ur:bytes/7of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/xf0czn37a3s00qwaf7h49t74he9xkwr9dalfww0hyn9hen2wf5q7sq4d60w8hqcer7y5k0hqa46jaeg56hk92xd0vz4', 'ur:bytes/4of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/a75mmqaw0fxp8xp47rlq76xx9rsxghy9ynpsmlp3f6p9574pxgjdnr3002w3yxp6nxdmru59f8ye4yrjmxwqtsjwpjzgfrz0c9r6p99t0d57njl2s62jln8325q0hv35llnwumnda4g4eqqkevqhhgm0hyc77fmva38dytq6a52ft5kl8v7wvmqr7kull2zrf0cw37c5', 'ur:bytes/2of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/vm77csd3pnn5mjljtlq4mq570qcvxfty82v9v86yrdq2qt5r2dynu6huzcvjl6vajrvv5e2nntmhmh4vejy58gm4vw5m4qm8t02afknuvry6zuk0d9qvhu8v3lsyzadx9xfjudgjchf2463uegeydaq2y8lacv7rnp7u0wyqx5frp6eht8lrclw8ktf6yz54n9hlpdaq', 'ur:bytes/5of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/nh55upgt8ksh3hclwmqq5dnvk2qpl27lrg0fpnfu630vk75npfqtz529tamtwfk42te3cgfjfxfd5ftllz779y3al4ws66u8yvl6ug2llt97ektzfyyeul35yl2n8k6kekcfcar4kn8rx98r8ape2wnnwzrxesgashcqkud325gtgmztf7jfp3nqmhld5r8trwpxtx2l', 'ur:bytes/3of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/mw5rf7ttadjvzn35ymas2x5ndwjp26dtn8qqv6ndnsrh0fy7f8nvhtfy6u32f376zyjryeujvju6ms9gelua68lqa60wyarldf59xlpcnfes8gd0y0znfmnrj27p0vzv7rauua5fue4kwwjypsz2j32qqkcvwenyvwg3x3vwklgfqthlqng3zwxw928wz65u6lyfyeg5', 'ur:bytes/6of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/wpcuz4ddrdt5vh7up75p5ule7xdvfpeq982cgnqc8rmn96qrqsm88cnvh3d4z2t6xf8lqz3d94pz9wk426un6f7gudmw8lu0n5mmxpe5zpcgaweafht5w0f8yy33pdc68se6tj8c0azgy3jqulufwr6wm2fkgx2us753zu4c7zzlzaekg8w7rn3pjwr5vg6q2k7fw88z', 'ur:bytes/1of7/0jsmw5retzcecxhpgz2e6pnzw9qy98m0frafzjuwn324w4yf8xdq0h0cmw/typjqlj2vyu9uf2snqd5k43n4vtcavrh5vzst7748ug8asggre70pj3uphqtl6jm30a4umlujxhazpxr4f6kyy94m0z3rr739jr7uppxnq2m565edzsdp5ah4xmrzwp2x678p2mzd4t8pd953luy8axe59trr2n8c740ptrvul3mlupm9jty8cehter5j0zwp7rr2v5a', ] result = decode_ur(workloads) # print('test_random_part_order: result = '.format(result)) if result == '7e4a61385e2550981b4b5633ab178eb077a30505fbd53f107ec1081e7cf0ca3c0dc0bfea5b8bfb5e6ffc91afd104c3aa756210b5dbc5118fd12c87ee04269815ba6a9968a0d0d3b7a9b631382a36bc70ab626d5670b4b48ff843f4d9a15631aa67c7aaf0ac6ce7e3bff03b2c9643e3375e47493c4e0f8635329d66fdec41b10ce74dcbf25fc15d829e7830c325643a98561f441b40a02e8353493e6afc16192fe99d90d8ca65539af77ddeaccc8943a37563a9ba83675bd5d4da7c60c9a172cf6940cbf0ec8fe04175a629932e3512c5d2aaea3cca3246f40a21ffdc33c3987dc7b880351230eb3759fe3c7dc7b2d3a20a95996ff0b7a0dba834f96beb64c14e3426fb051a936ba41569ab99c0066a6d9c0777a49e49e6cbad24d722a4c7da112432679264b9adc0a8cff9dd1fe0ee9ee2747f6a68537c389a7303a1af23c534ee6392bc17b04cf0fbce7689e66b673a440c04a9454005b0c76664639113458eb7d0902eff04d11138ce2a8ee16a9cd7c8926514efa9bd83ae7a4c139835f0fe0f68c628e0645c8524c30dfc314e825a7aa13224d98e2f7a9d12183a999bb1f28549c99a9072d99c05c24e0c84848c4fc147a094ab7b69e9cbea86952fccf15500fbb234ffe6ee6e6ded515c8016cb017ba36fb931ef276cec4ed22c1aed1495d2df3b3ce66c03f5b9ffa8434bf0e8fb149de94e050b3da178df1f76c00a366cb2801fabdf1a1e90cd3cd45ecb7a930a40b151455f76b726d552f31c21324992da257ff8bde2923dfd5d0d6b87233fae215ffacbecd96249099e7e3427d533db56cdb09c7475b4ce3314e33f43953a7370866cc11d85f00b71b15510b46c4b4fa490c660ddfeda0ceb1b8265995f7071c155ad1b57465fdc0fa81a73f9f19ac4872029d5844c1838f732e803043673e26cbc5b51297a324ff00a2d2d4222bad556b93d27c8e376e3ff8f9d37b3073410708ebb3d4dd7473d27212310b71a3c33a5c8f87f44824640e7f8970f4eda9364195c87a91172b8f085f1773641dde1ce21938746234055bc971ce2325f814e3eec60f781dd4faf52afd5be4a6b38656f7e9739f724cb7ccd4e4d01e802add3dc7b83191f894b3ee0ed752ee514d5ec55': print('decode_ur() worked!') else: print('decode_ur() failed!') async def test_ur1(*a): from ur1.decode_ur import decode_ur from ur1.encode_ur import encode_ur from ubinascii import unhexlify, hexlify # Encoding data = '70736274ff01005e0200000001ec609ee4ba6cd94f9fc5aac5ec6a07cf15fa57079501ec866c77885ab8b1d44c0100000000ffffffff015802000000000000220020e62c3bf1cf1e7a78133c4ef8fb21a68b2f0a5519da30d742c4ae321180f5970200000000000100fd88010200000000010153a921bb59af10165684d1dbbb42bff6fedf1867517605319cd46d0a8e1490030000000000ffffffff029c0b000000000000220020e5f40bdb0b4938b97a940912062537bc9b717491c96a5fe949323328e8ae2cf3f1430000000000002200202e691b3a2c57d0b77d4979101dfd3bbd7737dfdf7702f3d4ce9e9d547a04be550400483045022100eacf00d9c626257a3267d4ef69fad45e4f4cefdafeee7825fa09833f39acc8ac02203628f2659b68fd4570b8c835c30225a9ef16d4f65ab7a81e26e61af50ed61e5a01473044022071ce9d40247ce983771e4db332795d92607c34a75e86427ecd936495ff5f32dd022033b162bb1204c72dfe700b965ab56ca1a5137907e2bddf06f7fa8149991849b801695221025c5d8a75673f9810802a54d387f73bcecab2ce327d2c7cb3d01e9423b81f7144210309296fc7ca56609d0bab5623bff5d315a9aaa0646a900598cc384b8f8b3f09ca210328adad6ad3627bdf5fa7562754c3ca8bf01ed742e086654848e595f43b2f14e053ae0000000001012bf1430000000000002200202e691b3a2c57d0b77d4979101dfd3bbd7737dfdf7702f3d4ce9e9d547a04be552202020696b21057f70b9476a75229c93428d0cddceaaac9488e4b2a1f37d24918fe384830450221009fb917bf041cb7e00ace7b57e40d0265ed32d09862b90a13be20db0bb6f65d22022023707aa2f23bde856b1954e7189d9695b6f983e11b0b18fedfe893eaaf76a1f901220203d9277a7c106434329ba12cb7e6a7e3059cd1d3ce675e49a6b998d8497940fc424730440220076e268c09acadf5b22872450463d40c41cb4a231b86c8375aca591a8b2eac23022050e497440a3fd3f6dded6c4b72d54dde1bd62bc3c456111c0696e6458c5236620101030401000000220603d9277a7c106434329ba12cb7e6a7e3059cd1d3ce675e49a6b998d8497940fc421c317184b630000080000000800000008002000080010000000000000022060234745d1d85a741aa0921cfd89fea4a3540c818d7599af30afb637c1960a4e9031c83bd41063000008000000080000000800200008001000000000000002206020696b21057f70b9476a75229c93428d0cddceaaac9488e4b2a1f37d24918fe381c1799c1ce3000008000000080000000800200008001000000000000000105695221020696b21057f70b9476a75229c93428d0cddceaaac9488e4b2a1f37d24918fe38210234745d1d85a741aa0921cfd89fea4a3540c818d7599af30afb637c1960a4e9032103d9277a7c106434329ba12cb7e6a7e3059cd1d3ce675e49a6b998d8497940fc4253ae0000' data = hexlify(data) # self.qr_sizes = [500, 200, 60] result = encode_ur(data, fragment_capacity=200) if result == [ 'ur:bytes/1of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/tyy9cdesxuenvv3hx3nxvvp3xqcr2efsxgcrqvpsxqcrqvt9vvmrqwt9v56xycfkvdjrjdrx89nxxdtpv93n2etrxesnqdmrvccn2enpx5mnqdeex5crzetr8qmrvcehxuursdtpvguxyvtyxs6xxvp3xqcrqvpsxqcrqenxvenxvenxvccrzdfcxqerqvpsxqcrqvps', 'ur:bytes/2of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xqcrqvpjxgcrqv3sv5mrycenvfnrzcmxx9jnwcfh8qcnxvmrx3jkvwrxvgerzcfk8p3rye3svy6n2vfev3snxvryxu6ryce5v9jnxv33xyurqe348ymnqv3sxqcrqvpsxqcrqvpsxycrqeny8qurqvfsxgcrqvpsxqcrqvpsxycrzdfnvyunyvtzvg6njctxxycrzd34', 'ur:bytes/3of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xcurgep3v33xyc35xf3xve3kvejkge338qmrwdf3xumrqdfnxyukxep5xejrqcfcv5cngwfsxqenqvpsxqcrqvpsxqcxvenxvenxvenxxqerjcesvgcrqvpsxqcrqvpsxqcrqv3jxqcryvr9x4nrgvrzv33rqc358yensc3exasnjdps8ycnyvpkxg6nxdmzvvukyde3', 'ur:bytes/4of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xu6rjvtr8ymxzdtxv5ungwfnxgenxv3cv5uxzefjvdnrxe33xsenqvpsxqcrqvpsxqcrqvpjxgcrqv3sxfjnvwf3vgekzvnrx5mkgvrzxumkgdpexuunzvp3v3nxgvmzvfjrwdenxajxverxxumnqvnxxdjrgcm989jnjep4xsmkzvp5vfjn2dfsxscrqdpcxvcrgdfs', 'ur:bytes/5of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xgerzvpsv4skxe3sxpjrjcekxgmrydfhvyenyd3hvs6x2e3k89nxzep5x4jnge35vdjkverpvejk2efh8qer2enpxqunsvenvcenjctrvvuxzcesxgerqvekxguxvv3kx5ukyd3cvejrgdfhxp3rscecxv6kxvesxger2cfev4nrzdnyx3nrvdtpvgmkzwp3v5ervefk', 'ur:bytes/6of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/x9skvdfsv4jrvvt9x4snqvf5xuenqdp5xqeryvphx93k2wtyxscrydphvdjnjwpnxumnzef5v33rxvejxuun2epexgmrqdmrxv6xzde4v5urvdpjxajkxepexvmrgwf4venr2e3nxfjxgvpjxgcrxvmzxymrycnzxyerqdrrxuexgen9xucrqc3exc6kzc34xe3kzvtp', 'ur:bytes/7of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/x5cnxdeexqmk2vnzv3jxvvpkvcmkvcfcxy6rjwfexyurgwtz8qcrzd3ex5eryvfsxg6kxdty8psnwdfkxuekvwfcxycrsvpjvy6ngepn8qmkvdenvf3k2cmpvgexxefnxgmkgvnrxa3kyvmyxqck2wf5xgekywp3vcmnzdp5xgcnqves8yerjdnxvvmkxcf4xcmrqwty', 'ur:bytes/8of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xp3xzc34xcerxcnxvc6kgve3x4snjctpvycrvdpkvyunqvp48yuxxcen8q6xywrx8p3rxe3s893kzv33xqenywrpv3skgdnpvsenvv3hvfjxvdtxvymn2d3jxu6ngcenvdsnscnxxqck2ephxsex2vpcxcmr2dpcxsux2dfex4nrgvmzxfnrzdr9xq6nxct9xqcrqvps', 'ur:bytes/9of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xqcrqvp3xqcnycnxxy6rxvpsxqcrqvpsxqcrqvpsxgerqvpjxqex2d3ex93rxcfjvv6nwepsvgmnwep58ymnjvfsx9jxvepnvf3xgdehxvmkgenyvcmnwvpjvcekgdrrv5uk2wtyx56rwcfsx33x2df4xgerqv3sxgcrvwfkvgerzvp4xanrwvrz8y6rwdnpxu6nyv3e', 'ur:bytes/10of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/vvunxdpj8pjrqcmyv33k2ctpv93njdpc8pjngc3jvyckvvehvsergwf38pnx2vecxsurxvp5x5cryv33xqcrjenz8ycnwcnxxq6rzcmzxajnqvrpvdjnwc34xajngvryxqervdt9vsenyeps8yurvvnz8ycxzvfnvfjnyvryvgcxyc3kvcmr2epjxgcryv3sxgenwvph', 'ur:bytes/11of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/v9snye3jxd3xgefcx5mxyvfex56x2de38qukgwfk8y6kydnx8yurxef3x93rqc338pnx2erxv5urjvm9v9skvdekvyckvwfsxyeryvpjxqekgwfjxumkzdmrxycrvdpnxsenywtzvycnycmzxajnvcfhv5enqdfevdjrzepnvdjnvde4v56rjcfkvgunjwry8q6rjdee', 'ur:bytes/12of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xscxvce5xg6rwvesxs6rqv3jxqcrwdn9xgmrsces89skxctyvc6kyv3j8qmnydp4xq6rvvmyxscxxdp3vd3rgcfjxvckywpkvvurxde4v93kzdfex9snsc3jv4skxv3nxqeryvp4xpjngwfhxs6rqcfnvejrxe3kv3jx2epkvv6xydejvs6ngeryv5ckyepkxf3xxvmr', 'ur:bytes/13of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xs6nvvf3x93nqd3exejnvdp48p3n2v3nxcmryvp3xqcnqvesxscrzvpsxqcrqvpjxgcrvvpnvsunydehvymkxvfsxc6rxdpnxgukycf3xf3kydm9xesnwefnxq6njcmyx9jrxcm9xcmn2ef589snvc3e8yuxgwp58ymnjdpsve3ngv33vvenzde38q6xyd3nxqcrqvps', 'ur:bytes/14of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/8qcrqvpsxqcrqwpsxqcrqvpsxqurqvpjxqcrqvpcxqcrzvpsxqcrqvpsxqcrqvpsxqcryv3sxccryve5xu6r2ep3vsur2cfhxsckzcfs8yerzcmxvsurjen9vy6xzve4xscxxwp38pjrwdfe89skvvesv9nxyd3nxa3nzwfkxpsngefexqenzcecxd3xgdp3xqmrxvps', 'ur:bytes/15of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xqcrqwpsxqcrqvpsxqurqvpsxqcrqvpcxqcryvpsxqcrsvpsxycrqvpsxqcrqvpsxqcrqvpsxgerqd3sxgcrvwfkvgerzvp4xanrwvrz8y6rwdnpxu6nyv3evvunxdpj8pjrqcmyv33k2ctpv93njdpc8pjngc3jvyckvvehvsergwf38pnx2vecx93nzdee893nzcm9', 'ur:bytes/16of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xvcrqvpsxqurqvpsxqcrqvpcxqcrqvpsxqcrsvpsxgcrqvps8qcrqvfsxqcrqvpsxqcrqvpsxqcrqvp3xq6nvwf4xgerzvpjxqmrjdnzxgcnqdfhvcmnqc3exsmnvcfhx5erywtr8yengv3cvscxxeryvdjkzctpvvungwpcv56xyvnpx9nrxdmyxg6rjvfcvejnxwpj', 'ur:bytes/17of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xycryve5xu6r2ep3vsur2cfhxsckzcfs8yerzcmxvsurjen9vy6xzve4xscxxwp38pjrwdfe89skvvesv9nxyd3nxa3nzwfkxpsngefexqenyvfsxdjrjv3hxasnwce3xqmrgve5xverjcnpxyexxc3hv5mxzdm9xvcr2wtrvsckgvmrv5mrwdt9xsukzdnz8yunsepc', 'ur:bytes/18of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xsunwwf5xpnxxdpjx5ekzefsxqcrqyyqyag' ]: print('encode_ur() worked!') else: print('encode_ur() failed!') print('result={}'.format(result)) # Decoding workloads = [ 'ur:bytes/1of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/tyy9cdesxuenvv3hx3nxvvp3xqcr2efsxgcrqvpsxqcrqvt9vvmrqwt9v56xycfkvdjrjdrx89nxxdtpv93n2etrxesnqdmrvccn2enpx5mnqdeex5crzetr8qmrvcehxuursdtpvguxyvtyxs6xxvp3xqcrqvpsxqcrqenxvenxvenxvccrzdfcxqerqvpsxqcrqvps', 'ur:bytes/2of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xqcrqvpjxgcrqv3sv5mrycenvfnrzcmxx9jnwcfh8qcnxvmrx3jkvwrxvgerzcfk8p3rye3svy6n2vfev3snxvryxu6ryce5v9jnxv33xyurqe348ymnqv3sxqcrqvpsxqcrqvpsxycrqeny8qurqvfsxgcrqvpsxqcrqvpsxycrzdfnvyunyvtzvg6njctxxycrzd34', 'ur:bytes/3of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xcurgep3v33xyc35xf3xve3kvejkge338qmrwdf3xumrqdfnxyukxep5xejrqcfcv5cngwfsxqenqvpsxqcrqvpsxqcxvenxvenxvenxxqerjcesvgcrqvpsxqcrqvpsxqcrqv3jxqcryvr9x4nrgvrzv33rqc358yensc3exasnjdps8ycnyvpkxg6nxdmzvvukyde3', 'ur:bytes/4of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xu6rjvtr8ymxzdtxv5ungwfnxgenxv3cv5uxzefjvdnrxe33xsenqvpsxqcrqvpsxqcrqvpjxgcrqv3sxfjnvwf3vgekzvnrx5mkgvrzxumkgdpexuunzvp3v3nxgvmzvfjrwdenxajxverxxumnqvnxxdjrgcm989jnjep4xsmkzvp5vfjn2dfsxscrqdpcxvcrgdfs', 'ur:bytes/5of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xgerzvpsv4skxe3sxpjrjcekxgmrydfhvyenyd3hvs6x2e3k89nxzep5x4jnge35vdjkverpvejk2efh8qer2enpxqunsvenvcenjctrvvuxzcesxgerqvekxguxvv3kx5ukyd3cvejrgdfhxp3rscecxv6kxvesxger2cfev4nrzdnyx3nrvdtpvgmkzwp3v5ervefk', 'ur:bytes/6of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/x9skvdfsv4jrvvt9x4snqvf5xuenqdp5xqeryvphx93k2wtyxscrydphvdjnjwpnxumnzef5v33rxvejxuun2epexgmrqdmrxv6xzde4v5urvdpjxajkxepexvmrgwf4venr2e3nxfjxgvpjxgcrxvmzxymrycnzxyerqdrrxuexgen9xucrqc3exc6kzc34xe3kzvtp', 'ur:bytes/7of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/x5cnxdeexqmk2vnzv3jxvvpkvcmkvcfcxy6rjwfexyurgwtz8qcrzd3ex5eryvfsxg6kxdty8psnwdfkxuekvwfcxycrsvpjvy6ngepn8qmkvdenvf3k2cmpvgexxefnxgmkgvnrxa3kyvmyxqck2wf5xgekywp3vcmnzdp5xgcnqves8yerjdnxvvmkxcf4xcmrqwty', 'ur:bytes/8of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xp3xzc34xcerxcnxvc6kgve3x4snjctpvycrvdpkvyunqvp48yuxxcen8q6xywrx8p3rxe3s893kzv33xqenywrpv3skgdnpvsenvv3hvfjxvdtxvymn2d3jxu6ngcenvdsnscnxxqck2ephxsex2vpcxcmr2dpcxsux2dfex4nrgvmzxfnrzdr9xq6nxct9xqcrqvps', 'ur:bytes/9of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xqcrqvp3xqcnycnxxy6rxvpsxqcrqvpsxqcrqvpsxgerqvpjxqex2d3ex93rxcfjvv6nwepsvgmnwep58ymnjvfsx9jxvepnvf3xgdehxvmkgenyvcmnwvpjvcekgdrrv5uk2wtyx56rwcfsx33x2df4xgerqv3sxgcrvwfkvgerzvp4xanrwvrz8y6rwdnpxu6nyv3e', 'ur:bytes/10of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/vvunxdpj8pjrqcmyv33k2ctpv93njdpc8pjngc3jvyckvvehvsergwf38pnx2vecxsurxvp5x5cryv33xqcrjenz8ycnwcnxxq6rzcmzxajnqvrpvdjnwc34xajngvryxqervdt9vsenyeps8yurvvnz8ycxzvfnvfjnyvryvgcxyc3kvcmr2epjxgcryv3sxgenwvph', 'ur:bytes/11of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/v9snye3jxd3xgefcx5mxyvfex56x2de38qukgwfk8y6kydnx8yurxef3x93rqc338pnx2erxv5urjvm9v9skvdekvyckvwfsxyeryvpjxqekgwfjxumkzdmrxycrvdpnxsenywtzvycnycmzxajnvcfhv5enqdfevdjrzepnvdjnvde4v56rjcfkvgunjwry8q6rjdee', 'ur:bytes/12of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xscxvce5xg6rwvesxs6rqv3jxqcrwdn9xgmrsces89skxctyvc6kyv3j8qmnydp4xq6rvvmyxscxxdp3vd3rgcfjxvckywpkvvurxde4v93kzdfex9snsc3jv4skxv3nxqeryvp4xpjngwfhxs6rqcfnvejrxe3kv3jx2epkvv6xydejvs6ngeryv5ckyepkxf3xxvmr', 'ur:bytes/13of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xs6nvvf3x93nqd3exejnvdp48p3n2v3nxcmryvp3xqcnqvesxscrzvpsxqcrqvpjxgcrvvpnvsunydehvymkxvfsxc6rxdpnxgukycf3xf3kydm9xesnwefnxq6njcmyx9jrxcm9xcmn2ef589snvc3e8yuxgwp58ymnjdpsve3ngv33vvenzde38q6xyd3nxqcrqvps', 'ur:bytes/14of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/8qcrqvpsxqcrqwpsxqcrqvpsxqurqvpjxqcrqvpcxqcrzvpsxqcrqvpsxqcrqvpsxqcryv3sxccryve5xu6r2ep3vsur2cfhxsckzcfs8yerzcmxvsurjen9vy6xzve4xscxxwp38pjrwdfe89skvvesv9nxyd3nxa3nzwfkxpsngefexqenzcecxd3xgdp3xqmrxvps', 'ur:bytes/15of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xqcrqwpsxqcrqvpsxqurqvpsxqcrqvpcxqcryvpsxqcrsvpsxycrqvpsxqcrqvpsxqcrqvpsxgerqd3sxgcrvwfkvgerzvp4xanrwvrz8y6rwdnpxu6nyv3evvunxdpj8pjrqcmyv33k2ctpv93njdpc8pjngc3jvyckvvehvsergwf38pnx2vecx93nzdee893nzcm9', 'ur:bytes/16of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xvcrqvpsxqurqvpsxqcrqvpcxqcrqvpsxqcrsvpsxgcrqvps8qcrqvfsxqcrqvpsxqcrqvpsxqcrqvp3xq6nvwf4xgerzvpjxqmrjdnzxgcnqdfhvcmnqc3exsmnvcfhx5erywtr8yengv3cvscxxeryvdjkzctpvvungwpcv56xyvnpx9nrxdmyxg6rjvfcvejnxwpj', 'ur:bytes/17of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xycryve5xu6r2ep3vsur2cfhxsckzcfs8yerzcmxvsurjen9vy6xzve4xscxxwp38pjrwdfe89skvvesv9nxyd3nxa3nzwfkxpsngefexqenyvfsxdjrjv3hxasnwce3xqmrgve5xverjcnpxyexxc3hv5mxzdm9xvcr2wtrvsckgvmrv5mrwdt9xsukzdnz8yunsepc', 'ur:bytes/18of18/0p90t76gsdfeachgyjsh8plf6g34lepg2r6mk2ehc0ekvwtrtphq3w3c32/xsunwwf5xpnxxdpjx5ekzefsxqcrqyyqyag' ] encoded_data = decode_ur(workloads) result = unhexlify(encoded_data).decode('utf-8') # print('test_random_part_order: result = '.format(result)) if result == '70736274ff01005e0200000001ec609ee4ba6cd94f9fc5aac5ec6a07cf15fa57079501ec866c77885ab8b1d44c0100000000ffffffff015802000000000000220020e62c3bf1cf1e7a78133c4ef8fb21a68b2f0a5519da30d742c4ae321180f5970200000000000100fd88010200000000010153a921bb59af10165684d1dbbb42bff6fedf1867517605319cd46d0a8e1490030000000000ffffffff029c0b000000000000220020e5f40bdb0b4938b97a940912062537bc9b717491c96a5fe949323328e8ae2cf3f1430000000000002200202e691b3a2c57d0b77d4979101dfd3bbd7737dfdf7702f3d4ce9e9d547a04be550400483045022100eacf00d9c626257a3267d4ef69fad45e4f4cefdafeee7825fa09833f39acc8ac02203628f2659b68fd4570b8c835c30225a9ef16d4f65ab7a81e26e61af50ed61e5a01473044022071ce9d40247ce983771e4db332795d92607c34a75e86427ecd936495ff5f32dd022033b162bb1204c72dfe700b965ab56ca1a5137907e2bddf06f7fa8149991849b801695221025c5d8a75673f9810802a54d387f73bcecab2ce327d2c7cb3d01e9423b81f7144210309296fc7ca56609d0bab5623bff5d315a9aaa0646a900598cc384b8f8b3f09ca210328adad6ad3627bdf5fa7562754c3ca8bf01ed742e086654848e595f43b2f14e053ae0000000001012bf1430000000000002200202e691b3a2c57d0b77d4979101dfd3bbd7737dfdf7702f3d4ce9e9d547a04be552202020696b21057f70b9476a75229c93428d0cddceaaac9488e4b2a1f37d24918fe384830450221009fb917bf041cb7e00ace7b57e40d0265ed32d09862b90a13be20db0bb6f65d22022023707aa2f23bde856b1954e7189d9695b6f983e11b0b18fedfe893eaaf76a1f901220203d9277a7c106434329ba12cb7e6a7e3059cd1d3ce675e49a6b998d8497940fc424730440220076e268c09acadf5b22872450463d40c41cb4a231b86c8375aca591a8b2eac23022050e497440a3fd3f6dded6c4b72d54dde1bd62bc3c456111c0696e6458c5236620101030401000000220603d9277a7c106434329ba12cb7e6a7e3059cd1d3ce675e49a6b998d8497940fc421c317184b630000080000000800000008002000080010000000000000022060234745d1d85a741aa0921cfd89fea4a3540c818d7599af30afb637c1960a4e9031c83bd41063000008000000080000000800200008001000000000000002206020696b21057f70b9476a75229c93428d0cddceaaac9488e4b2a1f37d24918fe381c1799c1ce3000008000000080000000800200008001000000000000000105695221020696b21057f70b9476a75229c93428d0cddceaaac9488e4b2a1f37d24918fe38210234745d1d85a741aa0921cfd89fea4a3540c818d7599af30afb637c1960a4e9032103d9277a7c106434329ba12cb7e6a7e3059cd1d3ce675e49a6b998d8497940fc4253ae0000': print('decode_ur() worked!') else: print('decode_ur() failed!') print('result={}'.format(result)) async def battery_mon(*a): from battery_mon import battery_mon await battery_mon() async def generate_settings_error(*a): from settings import Settings s = Settings() s.load() s.set('sats_highscore', 1234) s.save() async def generate_settings_error2(*a): from common import settings for i in range(100): settings.set('test', i) await settings.save() await sleep_ms(100) async def toggle_demo(*a): import stash import common common.demo_active = not common.demo_active # Repeatedly fetch seed values to try to make it fail (sometimes it does) async def test_fetch_seeds(*a): good = 0 bad = 0 for i in range(100): try: with stash.SensitiveValues() as sv: assert sv.mode == 'words' # protected by menu item predicate words = trezorcrypto.bip39.from_data(sv.raw).split(' ') msg = 'Seed words (%d):\n' % len(words) msg += '\n'.join('%2d: %s' % (i+1, w) for i, w in enumerate(words)) pw = stash.bip39_passphrase if pw: msg += '\n\nBIP39 Passphrase:\n%s' % stash.bip39_passphrase # print('msg={}'.format(msg)) stash.blank_object(msg) good += 1 except Exception as e: bad +=1 # print('Exception: {}'.format(e)) # print('ERROR fetching words!') # print('good={} bad={}'.format(good, bad)) async def read_ambient(*a): for i in range(10): level = system.read_ambient() # print('Ambient level = {}'.format(level)) async def test_seed_check(*a): seed_words = ['oxygen', 'weapon', 'flee', 'kite', 'bid', 'video', 'coach', 'wish', 'invest', 'river', 'vocal', 'sugar', 'help', 'delay', 'outer', 'cruise', 'pupil', 'friend', 'disease', 'afraid', 'century', 'actor', 'another', 'impact'] seed_check = SeedCheckUX(seed_words=seed_words) result = await seed_check.show() # print('seed_check.is_check_valid = {}'.format(seed_check.is_check_valid)) async def test_derive_addresses(*a): import utime import stash import chains from public_constants import AF_P2WPKH from common import system n = 100 chain = chains.current_chain() addrs = [] path = "m/84'/{coin_type}'/{account}'/{change}/{idx}" system.turbo(True) start_time = utime.ticks_ms() with stash.SensitiveValues() as sv: for idx in range(n): subpath = path.format(coin_type=chain.b44_cointype, account=0, change=0, idx=idx) node = sv.derive_path(subpath, register=False) addr = chain.address(node, AF_P2WPKH) addrs.append(addr) # print("{} => {}".format(subpath, addr)) stash.blank_object(node) end_time = utime.ticks_ms() system.turbo(False) print('Elapsed = {} secs'.format( round(float(end_time - start_time) / 1000, 2) )) idx = 0 for addr in addrs: subpath = path.format(account=0, change=0, idx=idx) print("{} => {}".format(subpath, addr)) idx += 1 # Test function only async def supply_chain_challenge(*a): from trezorcrypto import sha256 from common import noise, system from noise_source import NoiseSource from ubinascii import hexlify # Make a challenge challenge = bytearray(32) noise.random_bytes(challenge, NoiseSource.ALL) # print('challenge: {}'.format(hexlify(challenge).decode('utf-8'))) # Hash the challenge with slot 7 response = bytearray(32) if system.supply_chain_challenge(challenge, response) == False: pass # print('ERROR: Unable to complete supply chain challenge!') else: # This is the secret in the SE now # NOTE: Padded at the end with zeros? slot7 = b'\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01' # slot7 = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' expected_response = bytearray(32) system.hmac_sha256(slot7, challenge, expected_response) # print('expected_response: {}'.format(hexlify(expected_response).decode('utf-8'))) # print('response: {}\n'.format(hexlify(response).decode('utf-8'))) async def test_write_flash_cache(*a): from common import flash_cache, system system.turbo(True) flash_cache.set('utxos', {'foo': 1234, 'bar': [1,2,3,4]}) system.turbo(False) async def test_read_flash_cache(*a): from common import flash_cache, system system.turbo(True) flash_cache.load() utxos = flash_cache.get('utxos', None) # print('utxos={}'.format(utxos)) system.turbo(False) async def toggle_screenshot_mode(*a): import common common.screenshot_mode_enabled = not common.screenshot_mode_enabled if common.screenshot_mode_enabled: await ux_show_story('Press and release the aA1 key in the lower right corner of the keypad to save a screenshot to the microSD card.\n\nIf no microSD is inserted, nothing will happen.', title='Screenshots', center=True, center_vertically=True) # print('common.screenshot_mode_enabled={}'.format(common.screenshot_mode_enabled)) async def toggle_snapshot_mode(*a): import common common.snapshot_mode_enabled = not common.snapshot_mode_enabled # print('common.snapshot_mode_enabled={}'.format(common.snapshot_mode_enabled)) async def toggle_battery_mon(*a): import common common.enable_battery_mon = not common.enable_battery_mon # print('common.enable_battery_mon={}'.format(common.enable_battery_mon)) # Remove all account info and multisig info - TESTING ONLY async def clear_accts(*a): from common import settings settings.remove('multisig') settings.remove('accounts') settings.remove('wallet_prog') async def test_folders(*a): import uos import os from files import CardSlot, CardMissingError from utils import get_backups_folder_path try: with CardSlot() as card: path = get_backups_folder_path(card) try: print('Creating backups') uos.mkdir(path) except Exception as e: print('Backups folder already exists!') pass fname = '{}/passport-backup-1.bin'.format(path) with open(fname, 'wb') as fd: fd.write('{ "acb": 123, "def": 456 }') except Exception as e: print('Exception: {}'.format(e)) async def make_accounts_menu(menu, label, item): from accounts import AllAccountsMenu # List of all created accounts and ability to create a new account rv = AllAccountsMenu.construct() return AllAccountsMenu(rv, title=item.arg) async def reset_device(*a): from common import system system.reset() async def test_battery_calcs(*a): from periodic import calc_battery_percent for voltage in range(2400,3101,10): p = calc_battery_percent(0, voltage); # current is ignored for now # print('voltage={} => {}%\n'.format(voltage, p)) print('{},{}'.format(voltage, p)) async def clear_ovc(*a): from history import OutptValueCache from common import flash_cache OutptValueCache.clear() async def set_last_verified_addr(*a): from utils import save_next_addr from public_constants import AF_P2WPKH save_next_addr(0, AF_P2WPKH, 76, False) def clear_cached_pubkey(): common.cached_pubkey = None async def install_user_firmware_pubkey(*a): from ubinascii import hexlify result = await ux_show_story('''Passport allows you to compile your own firmware version and sign it \ with your private key. To enable this, you must first import your corresponding public key. On the next screen, you can select your public key and import it into Passport.''', title='Import PubKey') if result == 'x': return fn = await file_picker('Select public key file (*-pub.bin)', suffix='-pub.bin') if fn == None: return system.turbo(True) with CardSlot() as card: with open(fn, 'rb') as fd: fd.seek(24) # Skip the header pubkey = fd.read(64) # Read the pubkey # print('pubkey = {}'.format(hexlify(pubkey))) result = system.set_user_firmware_pubkey(pubkey) if result: await ux_show_story('Successfully Installed!', title='Install', center=True, center_vertically=True) else: await ux_show_story('Unable to Install.', title='Install', center=True, center_vertically=True) clear_cached_pubkey() # print('system.set_user_firmware_pubkey() = {}'.format(result)) system.turbo(False) async def view_user_firmware_pubkey(*a): pubkey_result, pubkey = read_user_firmware_pubkey() if pubkey_result: pubkey = bytes_to_hex_str(pubkey) pubkey = split_to_lines(pubkey, 16) else: pubkey = 'Unable to read Developer PubKey' result = await ux_show_story(pubkey, title='View PubKey', center=True, center_vertically=True) if result == 'x': return async def remove_user_firmware_pubkey(*a): if system.is_user_firmware_installed(): await ux_show_story('You must install official Foundation firmware before you can remove the Developer PubKey.', title='Remove', center=True, center_vertically=True) return pubkey = bytearray(64) # Confirm the user knows the potential consequences if await ux_confirm('Are you sure you want to remove the Developer PubKey?', title='Remove'): result = system.set_user_firmware_pubkey(pubkey) if result: await ux_show_story('Successfully Removed PubKey!', title='Remove', center=True, center_vertically=True) else: await ux_show_story('Unable to Remove PubKey', title='Remove', center=True, center_vertically=True) clear_cached_pubkey()