Browse Source

Merge pull request #4 from Foundation-Devices/v1.0.2-dev

v1.0.2 dev branch
dev-v1.0.4 v1.0.2
Ken Carpenter 3 years ago
committed by GitHub
parent
commit
3c83a9ba57
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      ports/stm32/boards/Passport/modules/actions.py
  2. 6
      ports/stm32/boards/Passport/modules/flow.py
  3. 98
      ports/stm32/boards/Passport/modules/self_test_ux.py
  4. 20
      ports/stm32/boards/Passport/modules/utils.py
  5. 98
      ports/stm32/boards/Passport/tools/provisioning/provision.py

26
ports/stm32/boards/Passport/modules/actions.py

@ -21,7 +21,7 @@ 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)
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,
@ -31,23 +31,16 @@ from data_codecs.qr_type import QRType
import trezorcrypto
from seed_check_ux import SeedCheckUX
async def needs_microsd():
# Standard msg shown if no SD card detected when we need one.
return await ux_show_story("Please insert a microSD card.", title='MicroSD', center=True, center_vertically=True)
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 = '''Serial Number:
{serial}
msg = '''
Master Fingerprint:
{xfp}
@ -55,8 +48,7 @@ Reversed Fingerprint:
{rev_xfp}
Master XPUB:
{xpub}'''.format(serial=serial,
xfp=xfp2str(my_xfp) if my_xfp else '<No Seed Yet>',
{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>')
@ -622,7 +614,7 @@ async def create_new_seed(*a):
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"):
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
@ -1019,7 +1011,7 @@ async def file_picker(msg, suffix=None, min_size=None, max_size=None, taster=Non
# - escape: allow these chars to skip picking process
from menu import MenuSystem, MenuItem
import uos
from utils import get_filesize
from utils import get_filesize, folder_exists
system.turbo(True)
@ -1035,6 +1027,10 @@ async def file_picker(msg, suffix=None, min_size=None, max_size=None, taster=Non
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))
@ -1943,6 +1939,10 @@ async def test_read_flash_cache(*a):
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):

6
ports/stm32/boards/Passport/modules/flow.py

@ -19,7 +19,7 @@ from public_constants import AF_P2WPKH
from multisig import make_multisig_menu
from wallets.utils import has_export_mode
from export import view_backup_password
from utils import is_new_wallet_in_progress, get_accounts
from utils import is_new_wallet_in_progress, get_accounts, is_screenshot_mode_enabled
from new_wallet import pair_new_wallet
from ie import show_browser
@ -184,5 +184,7 @@ ExtrasMenu = [
# MenuItem('Developer Menu', menu=DeveloperMenu),
MenuItem('Snakamoto', f=play_snake),
MenuItem('Stacking Sats', f=play_stacking_sats),
MenuItem('Internet Browser', f=show_browser)
MenuItem('Internet Browser', f=show_browser),
MenuItem('Enable Screenshots', f=toggle_screenshot_mode, predicate=lambda: not is_screenshot_mode_enabled()),
MenuItem('Disable Screenshots', f=toggle_screenshot_mode, predicate=is_screenshot_mode_enabled)
]

98
ports/stm32/boards/Passport/modules/self_test_ux.py

@ -5,48 +5,81 @@
#
from common import system
from utils import UXStateMachine
from utils import UXStateMachine, needs_microsd
from ux import ux_show_text_as_ur, ux_keypad_test, ux_scan_qr_code, ux_show_story, ux_draw_alignment_grid
from data_codecs.qr_type import QRType
async def microsd_test():
import uos
import os
from files import CardSlot, CardMissingError
from utils import file_exists
msg = 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
while True:
try:
with CardSlot() as card:
filename = '{}/microsd-test.txt'.format(card.get_sd_root())
if file_exists(filename):
os.remove(filename)
with open(filename, 'wt') as fd:
fd.write(msg)
with open(filename, 'rt') as fd:
read_msg = fd.read()
if read_msg != msg:
await ux_show_story('The text read back from the microSD card did not match that written. Read:\n\n {}'.format(read_msg), title='Error')
return False
os.remove(filename)
result = await ux_show_story('microSD card is working properly!', title='microSD Test', center=True, center_vertically=True)
if result == 'x':
return False
return True
except CardMissingError:
result = await needs_microsd()
if result == 'x':
return False
except Exception as e:
await ux_show_story('{}'.format(e), title='Exception')
return False
class SelfTestUX(UXStateMachine):
def __init__(self):
# States
self.SHOW_SERIAL_NUMBER = 1
self.KEYPAD_TEST = 2
self.CAMERA_TEST = 3
self.CAMERA_TEST_RESULT = 4
self.SCREEN_ALIGNMENT = 5
self.KEYPAD_TEST = 1
self.CAMERA_TEST = 2
self.CAMERA_TEST_RESULT = 3
self.SCREEN_ALIGNMENT = 4
self.MICROSD_TEST = 5
self.TESTS_COMPLETE = 6
self.qr_data = None
# print('SelfTestUX init')
super().__init__(self.SHOW_SERIAL_NUMBER)
super().__init__(self.KEYPAD_TEST)
async def show(self):
while True:
# print('show: state={}'.format(self.state))
if self.state == self.SHOW_SERIAL_NUMBER:
serial = system.get_serial_number()
result = await ux_show_text_as_ur(title='Serial Num.', qr_text=serial, qr_type=QRType.QR, msg=serial,
right_btn='NEXT') # If right_btn is specified, then RESIZE doesn't appear/work, which is fine here
if result == 'x':
return
else:
self.goto(self.KEYPAD_TEST)
elif self.state == self.KEYPAD_TEST:
if self.state == self.KEYPAD_TEST:
# print('Keypad Test!')
result = await ux_keypad_test()
if result == 'x':
self.goto(self.SHOW_SERIAL_NUMBER)
self.goto_prev()
else:
self.goto(self.SCREEN_ALIGNMENT)
elif self.state == self.SCREEN_ALIGNMENT:
result = await ux_draw_alignment_grid(title='Align Screen')
if result == 'x':
self.goto(self.KEYPAD_TEST)
self.goto_prev()
else:
self.goto(self.CAMERA_TEST)
@ -56,19 +89,36 @@ class SelfTestUX(UXStateMachine):
self.qr_data = await ux_scan_qr_code('Camera Test')
# print('qr_data=|{}|'.format(self.qr_data))
system.turbo(False)
self.goto(self.CAMERA_TEST_RESULT)
self.goto(self.CAMERA_TEST_RESULT, save_curr=False)
elif self.state == self.CAMERA_TEST_RESULT:
if self.qr_data == None:
result = await ux_show_story('No QR code scanned.', right_btn='RETRY')
if result == 'x':
self.goto(self.SCREEN_ALIGNMENT)
self.goto_prev()
else:
self.goto(self.CAMERA_TEST)
else:
# Show the data - The QR code used in the factory starts with "Camera Test Passed!"
result = await ux_show_story(self.qr_data, right_btn='DONE')
if result == 'x':
self.goto(self.SCREEN_ALIGNMENT)
self.goto_prev()
else:
self.goto(self.MICROSD_TEST)
elif self.state == self.MICROSD_TEST:
# Describe the microSD test
result = await ux_show_story('This test will exercise the read/write features of the microSD card.', title='microSD Test', right_btn='START', center=True, center_vertically=True)
if result == 'x':
self.goto_prev()
continue
if await microsd_test():
self.goto(self.TESTS_COMPLETE)
elif self.state == self.TESTS_COMPLETE:
result = await ux_show_story('All tests complete!', title='Complete', right_btn='SHUTDOWN', center=True, center_vertically=True)
if result == 'x':
self.goto_prev()
else:
return
system.shutdown()

20
ports/stm32/boards/Passport/modules/utils.py

@ -494,6 +494,17 @@ def file_exists(path):
except:
return False
def folder_exists(path):
import os
from stat import S_ISDIR
try:
s = os.stat(path)
mode = s[0]
return S_ISDIR(mode)
except OSError as e:
return False
# Derive addresses from the specified path until we find the address or have tried max_to_check addresses
# If single sig, we need `path`.
# If multisig, we need `ms_wallet`, but not `path`
@ -650,6 +661,10 @@ def is_new_wallet_in_progress():
ap = settings.get('wallet_prog', None)
return ap != None
def is_screenshot_mode_enabled():
from common import screenshot_mode_enabled
return screenshot_mode_enabled
async def do_rename_account(acct_num, new_name):
from common import settings
from export import auto_backup
@ -758,4 +773,9 @@ def is_alphanumeric_qr(buf):
return True
async def needs_microsd():
from ux import ux_show_story
# Standard msg shown if no SD card detected when we need one.
return await ux_show_story("Please insert a microSD card.", title='MicroSD', center=True, center_vertically=True)
# EOF

98
ports/stm32/boards/Passport/tools/provisioning/provision.py

@ -1,3 +1,4 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2021 Foundation Devices, Inc. <hello@foundationdevices.com>
# SPDX-License-Identifier: GPL-3.0-or-later
#
@ -33,10 +34,6 @@ TELNET_CMD_LINE = ['telnet', 'localhost', '4444']
telnet_proc = None
# FACTORY SETTINGS
DIAGNOSTIC_MODE = False # Set to True to get more menu options
def connect_to_telnet():
# Connect
global tn
@ -45,6 +42,27 @@ def connect_to_telnet():
# We still see the commands we send echoed from the remote side, but they are not also echoed locally now.
tn.write(b'' + telnetlib.IAC + telnetlib.DONT + telnetlib.ECHO)
def banner(s):
divider = '=' * len(s)
print(divider)
print(s)
print(divider)
# def poll_for_device():
# attempts_remaining = 10
# print('Waiting for device to be ready...')
# while attempts_remaining > 0:
# print(' {}...'.format(attempts_remaining))
# result = init_device()
# if result:
# print('Device Ready!')
# return True
#
# time.sleep(1)
# attempts_remaining -= 1
#
# print('Timeout! Device did not respond.')
# return False
# Numato 32 channel GPIO board over USB serial:
#
@ -103,19 +121,42 @@ def wait_for_prompt(timeout=None):
else:
return True
MAX_ATTEMPTS = 10
# Put device into halt state, and discard all unread data to get ready for a new command
def init_device(timeout=None):
r = tn.read_very_eager()
# print('Halting device...')
tn.write(b'reset halt\r')
return wait_for_prompt(timeout)
attempts_remaining = MAX_ATTEMPTS
if not tn:
print('Connecting to OpenOCD...')
while attempts_remaining > 0:
if not tn:
connect_to_telnet()
if tn:
r = tn.read_very_eager()
# print('Halting device...')
tn.write(b'reset halt\r')
result = wait_for_prompt(timeout)
if result:
if attempts_remaining < MAX_ATTEMPTS:
banner('Connected to device!')
return True
print(' {}...'.format(attempts_remaining))
time.sleep(1)
attempts_remaining -= 1
banner('Timeout! Device did not respond.')
return False
def random_fn_ext(l):
import os, binascii
return binascii.b2a_hex(os.urandom(l)).decode('utf-8')
def provision_device(flash_bootloader=False, flash_firmware=False, with_secrets=False):
init_device()
if not init_device(): return
# Check to see if the device was already provisioned - it will have data in its secrets
if flash_bootloader and not with_secrets:
@ -183,7 +224,7 @@ def provision_device(flash_bootloader=False, flash_firmware=False, with_secrets=
print('Complete!')
def write_supply_chain_secret():
init_device()
if not init_device(): return
# Write the supply chain secret
print('Setting Supply Chain Validation Secret...')
@ -196,19 +237,15 @@ def write_supply_chain_secret():
tn.write(bytes(cmd, 'utf-8'))
wait_for_prompt()
def test_device_connection():
tn.read_very_eager()
device_found = init_device(timeout=5)
if device_found:
print('Passport is connected and responding to commands.')
else:
print('===================================================================')
print('Unable to connect to device (Error or timeout connecting to device)')
print('===================================================================')
# def test_device_connection():
# # tn.read_very_eager()
# device_found = init_device(timeout=5)
# if device_found:
# print('Passport is connected and responding to commands.')
def read_supply_chain_secret(do_init=True):
if do_init:
init_device()
if not init_device(): return
# Read the supply chain secret to make sure the device is ready for provisioning
tn.write(bytes('mdb {} 32\r'.format(hex(SUPPLY_CHAIN_SECRET_ADDRESS)), 'utf-8'))
@ -235,7 +272,7 @@ def is_already_provisioned(secrets):
return any(map(lambda b: b != 0xFF, secrets))
def get_secrets():
init_device()
if not init_device(): return []
cmd = bytes('mdb {} 256\r'.format(hex(BL_NVROM_BASE)), 'utf-8')
tn.write(cmd)
@ -266,7 +303,7 @@ def save_secrets():
print('\nUnable to read secrets from device!')
def reset_device():
init_device()
if not init_device(): return
print('Resetting Device...')
tn.write(b'reset\r')
@ -274,7 +311,7 @@ def reset_device():
print('Done.')
def erase_all_flash():
init_device()
if not init_device(): return
print('Erasing all internal flash (bootloader, secrets, firmware, user settings)...')
tn.write(b'flash erase_address 0x8000000 0x200000\r')
@ -298,9 +335,14 @@ def erase_all_flash():
#
def main():
DIAGNOSTIC_MODE = False
if len(sys.argv) > 1:
if sys.argv[1] == '--diag':
DIAGNOSTIC_MODE = True
if DIAGNOSTIC_MODE:
options = [
'[1] Test Device Connection',
'[1] Connect to Test Device',
'[2] Provision Device',
'[3] Update Bootloader Only (with secrets.bin)',
'[4] Update Firmware Only',
@ -314,7 +356,7 @@ def main():
]
else:
options = [
'[1] Test Device Connection',
'[1] Connect to Test Device',
'[2] Provision Device',
'[3] Print Secrets',
'[4] Reset Device',
@ -331,8 +373,7 @@ def main():
if DIAGNOSTIC_MODE:
if selection == 0:
connect_to_telnet()
test_device_connection()
init_device()
elif selection == 1:
provision_device(flash_bootloader=True, flash_firmware=True)
elif selection == 2:
@ -355,8 +396,7 @@ def main():
exit = True
else:
if selection == 0:
connect_to_telnet()
test_device_connection()
init_device()
elif selection == 1:
provision_device(flash_bootloader=True, flash_firmware=True)
elif selection == 2:

Loading…
Cancel
Save