You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2010 lines
87 KiB

# SPDX-FileCopyrightText: 2020 Foundation Devices, Inc. <hello@foundationdevices.com>
4 years ago
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2018 Coinkite, Inc. <coldcardwallet.com>
4 years ago
# SPDX-License-Identifier: GPL-3.0-only
#
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
# 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
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)
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,
4 years ago
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
4 years ago
import trezorcrypto
from seed_check_ux import SeedCheckUX
4 years ago
async def about_info(*a):
from common import system
from display import FontTiny
from utils import swab32
while True:
my_xfp = settings.get('xfp', 0)
xpub = settings.get('xpub', None)
msg = '''
Master Fingerprint:
{xfp}
4 years ago
Reversed Fingerprint:
{rev_xfp}
4 years ago
Master XPUB:
{xpub}'''.format(xfp=xfp2str(my_xfp) if my_xfp else '<No Seed Yet>',
rev_xfp=xfp2str(swab32(my_xfp)) if my_xfp else '<No Seed Yet>',
xpub=xpub if xpub != None else '<No Seed Yet>')
4 years ago
result = await ux_show_story(msg, center=True, center_vertically=True, font=FontTiny, right_btn='REGULATORY')
if result == 'y':
await regulatory_info()
else:
4 years ago
return
async def regulatory_info():
from display import FontTiny
4 years ago
msg = """\
4 years ago
Passport
4 years ago
Foundation Devices
6 Liberty Square #6018
Boston, MA 02109 USA"""
4 years ago
await ux_show_story(msg, title='Regulatory', center=True, font=FontTiny, overlay=(None, 303 - 34 - 72, 'fcc_ce_logos'))
4 years ago
# 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 = '<Unknown>'
# else:
# xfp = xfp2str(xfp)
#
# # Can only get these values if the derivation path is known
# xpub = '<Unknown>'
# path = '<Unknown>'
# 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)
4 years ago
async def rename_account(menu, label, item):
from export import auto_backup
from utils import account_exists, do_rename_account
from constants import MAX_ACCOUNT_NAME_LEN
account = common.active_account
while True:
new_name = await ux_enter_text('Rename', label="Enter account name", initial_text=account.get('name'),
right_btn='RENAME', max_length=MAX_ACCOUNT_NAME_LEN)
4 years ago
if new_name == None:
# User selected BACK
return
4 years ago
# See if an account with this name already exists
if account_exists(new_name):
result = 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='RENAME')
if result == 'x':
self.goto_prev()
else:
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()
4 years ago
return
async def delete_account(menu, label, item):
from utils import do_delete_account, make_account_name_num
from ux import the_ux
4 years ago
account = common.active_account
4 years ago
# Confirm the deletion
name_num = make_account_name_num(account.get('name'), account.get('acct_num'))
4 years ago
if not await ux_confirm('Are you sure you want to delete this account?\n\n{}'.format(name_num)):
return
4 years ago
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()
4 years ago
class VerifyAddressUX(UXStateMachine):
4 years ago
def __init__(self):
# States
self.SELECT_ACCOUNT = 1
self.SELECT_SIG_TYPE = 2
self.VERIFY_ADDRESS = 3
4 years ago
# print('VerifyAddressUX init')
super().__init__(self.SELECT_ACCOUNT)
4 years ago
self.acct_num = None
self.sig_type = None
self.multisig_wallet = None
4 years ago
# 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
4 years ago
await run_chooser(self.account_chooser, 'Account', show_checks=False)
if self.acct_num == None:
return
4 years ago
self.goto(self.SELECT_SIG_TYPE)
4 years ago
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
4 years ago
# 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...were' 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')
if address == None:
return
4 years ago
# Ensure lowercase
address = address.lower()
4 years ago
# Strip prefix if present
if address.startswith('bitcoin:'):
address = address[8:]
4 years ago
if not is_valid_address(address):
result = await ux_show_story('That is not a valid Bitcoin address.', title='Error', left_btn='BACK',
right_btn='SCAN', center=True, center_vertically=True)
if result == 'x':
if not self.goto_prev():
# Nothing to return back to, so we must have skipped one or more steps...were' done
return
continue
4 years ago
# 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)
4 years ago
# Scan addresses to see if it's valid
addr_idx = await scan_for_address(self.acct_num, address, addr_type, deriv_path, self.multisig_wallet)
if addr_idx >= 0:
# Remember where to start from next time
save_next_addr(self.acct_num, addr_type, addr_idx)
dis.fullscreen('Address Verified')
await sleep_ms(1000)
return
else:
# User asked to stop searching
return
async def verify_address(*a):
verify_address_ux = VerifyAddressUX()
await verify_address_ux.show()
4 years ago
async def update_firmware(*a):
# Upgrade via microSD card
4 years ago
# - 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
4 years ago
# Don't show any files that are pubkeys
def no_pubkeys(filename):
return not filename.endswith('-pub.bin')
4 years ago
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))
4 years ago
if not fn:
return
failed = None
system.turbo(True)
4 years ago
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
4 years ago
# Validate the header
is_valid, version, error_msg = 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
4 years ago
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)
4 years ago
# 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)
4 years ago
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
4 years ago
update_display = 0
while pos <= size + 256:
4 years ago
# 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)
4 years ago
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)
4 years ago
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.
4 years ago
# continue process...
# print("RESTARTING!")
4 years ago
# 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)
4 years ago
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
4 years ago
# TODO: Move the messaging into EnterInitialPinUX state machine
# First time they select a PIN
4 years ago
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':
4 years ago
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.
4 years ago
Please record your PIN somewhere safe.''', title='Set PIN', scroll_label='MORE')
4 years ago
if ch != 'y':
break
# Enter the PIN
from login_ux import EnterInitialPinUX
new_pin_ux = EnterInitialPinUX()
4 years ago
await new_pin_ux.show()
pin = new_pin_ux.pin
# print('pin = {}'.format(pin))
4 years ago
if pin is None:
continue
4 years ago
# New pin is being saved
dis.fullscreen("Saving PIN...")
system.show_busy_bar()
4 years ago
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()
4 years ago
return
4 years ago
async def login_countdown(minutes):
# show a countdown, which may need to
# run for multiple **days**
from common import dis
from display import FontSmall
4 years ago
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)
4 years ago
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
4 years ago
# print('pa.is_successful() = {}'.format(pa.is_successful()))
4 years ago
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
4 years ago
# print('!!!!LOGGED IN!!!!')
system.turbo(False)
4 years ago
async def create_new_seed(*a):
from ubinascii import hexlify as b2a_hex
4 years ago
import seed
system.show_busy_bar()
4 years ago
wallet_seed_bytes = await seed.create_new_wallet_seed()
# print('wallet_seed_bytes = {}'.format(b2a_hex(wallet_seed_bytes)))
4 years ago
mnemonic_str = trezorcrypto.bip39.from_data(wallet_seed_bytes)
# print('mnemonic = {}'.format(mnemonic_str))
4 years ago
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)
4 years ago
# Update xpub/xfp in settings after creating new wallet
import stash
with stash.SensitiveValues() as sv:
sv.capture_xpub()
4 years ago
system.hide_busy_bar()
4 years ago
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.
4 years ago
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
4 years ago
# 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
4 years ago
await goto_top_menu()
4 years ago
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.
4 years ago
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':
4 years ago
return
fake_it = False
if fake_it:
pass
4 years ago
else:
from seed_entry_ux import SeedEntryUX
4 years ago
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)
4 years ago
async def handle_seed_data_format(mnemonic):
4 years ago
import seed
from foundation import bip39
from common import dis, pa
from ubinascii import hexlify as b2a_hex
4 years ago
entropy = bytearray(33) # Includes and extra byte for the checksum bits
4 years ago
# 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.
4 years ago
First use Advanced > Erase Passport to remove the current seed.''', right_btn='OK')
return False
4 years ago
bip = bip39()
len = bip.mnemonic_to_entropy(mnemonic, entropy)
4 years ago
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)))
4 years ago
# 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!')
4 years ago
# Update xpub/xfp in settings after creating new wallet
import stash
with stash.SensitiveValues() as sv:
sv.capture_xpub()
4 years ago
# Show post-creation message
dis.fullscreen('Successfully Imported!')
await sleep_ms(1000)
4 years ago
await goto_top_menu()
return True
4 years ago
async def erase_wallet(menu, label, item):
4 years ago
# 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.'):
4 years ago
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'):
4 years ago
return
await seed.erase_wallet(item.arg)
4 years ago
# NOT REACHED -- reset happens
async def view_seed_words(*a):
import stash
from common import dis
4 years ago
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):
4 years ago
return
dis.fullscreen('Retrieving Seed...')
system.show_busy_bar()
4 years ago
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()
4 years ago
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
4 years ago
stash.blank_object(msg)
except Exception as e:
print('Exception: {}'.format(e))
system.hide_busy_bar()
4 years ago
# Unable to read seed!
await ux_show_story('Unable to retrieve seed.')
4 years ago
async def start_login_sequence():
# Boot up login sequence here.
#
from common import pa
4 years ago
while not pa.is_successful():
# always get a PIN and login first
await block_until_login()
async def goto_top_menu(*a):
4 years ago
# Start/restart menu system
from menu import MenuSystem
from flow import MainMenu, NoSeedMenu
4 years ago
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)
4 years ago
the_ux.reset(m)
return m
SENSITIVE_NOT_SECRET = '''
The file created is sensitive in terms of privacy, but should not \
4 years ago
compromise your funds directly.'''
PICK_ACCOUNT = '''\n\nPress 1 to enter a non-zero account number.'''
async def export_summary(*A):
4 years ago
# 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'):
4 years ago
return
# pick a semi-random file name, save it.
with imported('export') as exp:
await exp.make_summary_file()
4 years ago
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")
4 years ago
# 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')
4 years ago
async def make_microsd_backup(*A):
4 years ago
# save everything, using a password, into single encrypted file, typically on SD
with imported('export') as exp:
await exp.make_complete_backup()
4 years ago
async def verify_microsd_backup(*A):
4 years ago
# 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')
4 years ago
if fn:
# do a limited CRC-check over encrypted file
await exp.verify_backup_file(fn)
4 years ago
EMPTY_RESTORE_MSG = '''\
Before restoring from a backup, you must erase this Passport. Make sure your device is backed up.
4 years ago
Navigate to Advanced > Erase Passport.'''
4 years ago
FULL_PARTIAL_MSG = '''A wallet seed already exists.
4 years ago
Do you want to perform a FULL restore or a PARTIAL restore of accounts only?'''
4 years ago
async def restore_microsd_backup(*A):
4 years ago
from common import pa
partial_restore = False
4 years ago
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
4 years ago
# TODO: Insert step here to pick a backups-* folder when we add the XFP to the folder name
4 years ago
# 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')
4 years ago
if fn:
with imported('export') as exp:
await exp.restore_complete(fn, partial_restore)
4 years ago
async def format_sd_card(*A):
if not await ux_confirm('Erase and reformat the microSD card.', negative_btn='BACK', positive_btn='FORMAT'):
return
4 years ago
from files import format_microsd_card
4 years ago
system.turbo(True)
format_microsd_card()
system.turbo(False)
4 years ago
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
4 years ago
from utils import B2A
chk = trezorcrypto.sha256()
4 years ago
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()
4 years ago
return
basename = fn.rsplit('/', 1)[-1]
4 years ago
digest = B2A(chk.digest())
system.hide_busy_bar()
4 years ago
await ux_show_story('File:\n %s\n\n%s' % (basename, digest), title='SHA256')
4 years ago
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):
4 years ago
# 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
4 years ago
system.turbo(True)
4 years ago
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
4 years ago
files = uos.ilistdir(path)
for fn, ftype, *var in files:
# print("fn={} ftype={} var={} suffix={}".format(fn, ftype, var, suffix))
4 years ago
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
4 years ago
# 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)
4 years ago
# don't show anything if we're just gathering data
if msg is not None:
await needs_microsd()
return None
system.turbo(False)
4 years ago
if msg is None:
return choices
if not choices:
msg = none_msg or 'Unable to find an applicable file on the microSD card.'
4 years ago
if not none_msg:
if suffix:
msg += '\n\nThe filename must end in "%s".' % suffix
4 years ago
await ux_show_story(msg, center=True, center_vertically=True, title=title)
4 years ago
return
ch = await ux_show_story(msg, center=True, center_vertically=True, title=title)
4 years ago
if ch == 'x':
return
picked = []
async def clicked(_1, _2, item):
picked.append('/'.join(item.arg))
the_ux.pop()
choices.sort()
4 years ago
items = [MenuItem(label, f=clicked, arg=(path, fn))
for label, path, fn in choices]
menu = MenuSystem(items, title='Select File')
4 years ago
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
4 years ago
from public_constants import MAX_TXN_LEN
import stash
if stash.bip39_passphrase:
title = '[%s]' % xfp2str(settings.get('xfp'))
else:
title = 'Select PSBT'
4 years ago
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)
4 years ago
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)
4 years ago
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.',
4 years ago
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
4 years ago
change_pin_ux = ChangePinUX()
4 years ago
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))
4 years ago
async def show_version(*a):
# show firmware, bootloader versions
from utils import get_month_str
from utime import localtime
4 years ago
system.turbo(True)
(fw_version, fw_timestamp, boot_counter, user_signed) = system.get_software_info()
4 years ago
time = localtime(fw_timestamp)
fw_date = '{} {}, {}'.format(get_month_str(time[1]), time[2], time[0]-30)
4 years ago
msg = '''\
Current Firmware:
v{fw_version}
{fw_date}'''.format(fw_version=fw_version,
fw_date=fw_date)
4 years ago
if user_signed:
msg += '\nSigned by User'
4 years ago
msg += '''
4 years ago
Boot Counter:
{boot_counter}\
'''.format(boot_counter=boot_counter)
system.turbo(False)
4 years ago
await ux_show_story(msg, center=True, center_vertically=True)
4 years ago
async def import_multisig_from_sd(*a):
4 years ago
# 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',
4 years ago
min_size=100, max_size=20*200, taster=possible)
if not fn:
return
system.turbo(True);
4 years ago
try:
with CardSlot() as card:
with open(fn, 'rt') as fp:
data = fp.read()
except CardMissingError:
system.turbo(False);
4 years ago
await needs_microsd()
return
# print('data={}'.format(data))
4 years ago
from auth import maybe_enroll_xpub
from utils import problem_file_line, show_top_menu
from export import offer_backup
4 years ago
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:
# TOnly need to decode this for QR codes...from SD card it's already in bytes
data = data.decode('utf-8')
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
try:
possible_name = "ms"
maybe_enroll_xpub(config=data, name=possible_name)
await show_top_menu() # wait for interaction with the enroll
await offer_backup()
4 years ago
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
4 years ago
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
4 years ago
if data != None:
try:
from auth import sign_psbt_buf
# 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')
# 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
async def import_user_firmware_pubkey(*a):
from common import system, dis
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:
dis.fullscreen('Successfully Imported!')
else:
dis.fullscreen('Unable to Import')
await sleep_ms(1000)
# print('system.set_user_firmware_pubkey() = {}'.format(result))
system.turbo(False)
async def read_user_firmware_pubkey(*a):
from common import system
from ubinascii import hexlify
pubkey = bytearray(64)
system.turbo(True)
result = system.get_user_firmware_pubkey(pubkey)
# print('system.get_user_firmware_pubkey() = {}'.format(result))
# print(' len={} pubkey = {}'.format(len(pubkey), hexlify(pubkey)))
system.turbo(False)
4 years ago
async def enter_passphrase(menu, label, item):
import sys
from seed import set_bip39_passphrase
from constants import MAX_PASSPHRASE_LENGTH
4 years ago
title = item.arg
passphrase = await ux_enter_text(title, label="Enter a 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
from common import dis
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)
4 years ago
system.hide_busy_bar()
4 years ago
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))
4 years ago
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
4 years ago
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.
4 years ago
The setup guide will direct you to a page containing a QR code.
4 years ago
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')
4 years ago
if result == 'y':
while True:
# Scan a QR code
system.turbo(True)
4 years ago
qr_data = await ux_scan_qr_code('Validation')
# qr_data = 'af7e2098fd626650b342398905b82a73c835213dc1b4b1ca10f783d4e4593c95'
system.turbo(False)
4 years ago
# Generate the 4 validation words
# 4 words gives 2048 choose 4 possibilties = 730,862,190,080
4 years ago
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?')
4 years ago
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))
4 years ago
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)
4 years ago
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)
4 years ago
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()
4 years ago
# 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')
4 years ago
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
4 years ago
seed = bytearray(32)
valid = noise.random_bytes(seed, item.arg)
# print('Random bytes = {}'.format(hexlify(seed)))
4 years ago
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))
4 years ago
await sleep_ms(1000)
async def show_board_rev(*a):
from foundation import Boardrev
boardrev = Boardrev()
rev = boardrev.read()
# print('Board rev={}'.format(rev))
4 years ago
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)))
4 years ago
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):
4 years ago
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))
4 years ago
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'/0'/{account}'/{change}/{idx}"
system.turbo(True)
start_time = utime.ticks_ms()
with stash.SensitiveValues() as sv:
for idx in range(n):
subpath = path.format(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()
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()