From ffb03a206d84ae743f28e7e5c75f944a0b338497 Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Tue, 20 Jun 2023 23:22:54 -0700 Subject: [PATCH 001/239] SFT-23444: Fix GitHub Action deprecations Update to latest dependencies --- .github/workflows/validate_and_build.yaml | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 382d45a02..00ae93dc1 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -17,13 +17,13 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 with: driver-opts: network=host - - uses: docker/build-push-action@v2 + - uses: docker/build-push-action@v4 with: push: true context: . @@ -52,13 +52,13 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 with: driver-opts: network=host - - uses: docker/build-push-action@v2 + - uses: docker/build-push-action@v4 with: push: true context: . @@ -109,13 +109,13 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 with: driver-opts: network=host - - uses: docker/build-push-action@v2 + - uses: docker/build-push-action@v4 with: push: true context: . @@ -152,13 +152,13 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 with: driver-opts: network=host - - uses: docker/build-push-action@v2 + - uses: docker/build-push-action@v4 with: push: true context: . @@ -183,13 +183,13 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v2 with: driver-opts: network=host - - uses: docker/build-push-action@v2 + - uses: docker/build-push-action@v4 with: push: true context: . From 3e760dac4d346b927f1b61fa43c6abec3b80ab37 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 15 Jun 2023 15:26:08 -0500 Subject: [PATCH 002/239] SFT-2121: start search for backups in root of microsd --- .../boards/Passport/modules/flows/restore_backup_flow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py index c2d673ee1..9b11ae621 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py @@ -17,7 +17,7 @@ SuccessPage, YesNoChooserPage ) -from utils import get_backups_folder_path, spinner_task, get_backup_code_as_password +from utils import spinner_task, get_backup_code_as_password from tasks import restore_backup_task, get_backup_code_task from errors import Error import common @@ -52,8 +52,7 @@ async def check_if_erased(self): self.goto(self.choose_file) async def choose_file(self): - backups_path = get_backups_folder_path() - result = await FilePickerFlow(initial_path=backups_path, suffix='.7z', show_folders=True).run() + result = await FilePickerFlow(suffix='.7z', show_folders=True).run() if result is None: # No file chosen, so go back to menu self.set_result(False) From 535afd80298d958bad9c02a54232a5442c3d2b63 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 21 Jun 2023 18:31:51 -0500 Subject: [PATCH 003/239] SFT-2121: allowed recovery from root directory, only if a backup is present --- .../Passport/modules/flows/restore_backup_flow.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py index 9b11ae621..aba000a82 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py @@ -52,7 +52,18 @@ async def check_if_erased(self): self.goto(self.choose_file) async def choose_file(self): - result = await FilePickerFlow(suffix='.7z', show_folders=True).run() + from utils import get_file_list, get_backups_folder_path + + suffix = '.7z' + files = get_file_list(suffix=suffix) + if len(files) == 0: + initial_path = get_backups_folder_path() + else: + initial_path = None + + result = await FilePickerFlow(initial_path=initial_path, + suffix=suffix, + show_folders=True).run() if result is None: # No file chosen, so go back to menu self.set_result(False) From 87afff311a456ae35de18b87e607470aca5445f4 Mon Sep 17 00:00:00 2001 From: Jean-Pierre De Jesus DIAZ Date: Tue, 20 Jun 2023 12:43:10 +0200 Subject: [PATCH 004/239] SFT-2333: Update Cargo dependencies Signed-off-by: Jean-Pierre De Jesus DIAZ --- extmod/foundation-rust/Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extmod/foundation-rust/Cargo.lock b/extmod/foundation-rust/Cargo.lock index b035f3c68..1098bfac2 100644 --- a/extmod/foundation-rust/Cargo.lock +++ b/extmod/foundation-rust/Cargo.lock @@ -166,9 +166,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -219,9 +219,9 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -264,9 +264,9 @@ checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" dependencies = [ "atomic-polyfill", "critical-section", @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "uuid" From 88698373b0f6a9910bfb6bf7556050747be0f38a Mon Sep 17 00:00:00 2001 From: Jean-Pierre De Jesus DIAZ Date: Thu, 6 Jul 2023 13:26:53 +0200 Subject: [PATCH 005/239] SFT-2383: Rename bootloader splash.c file Signed-off-by: Jean-Pierre De Jesus DIAZ --- ports/stm32/boards/Passport/bootloader/Makefile | 2 +- ports/stm32/boards/Passport/bootloader/factory-test.c | 2 +- ports/stm32/boards/Passport/bootloader/main.c | 2 +- .../stm32/boards/Passport/bootloader/{splash.c => ui-splash.c} | 2 +- .../stm32/boards/Passport/bootloader/{splash.h => ui-splash.h} | 0 ports/stm32/boards/Passport/bootloader/update.c | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename ports/stm32/boards/Passport/bootloader/{splash.c => ui-splash.c} (92%) rename ports/stm32/boards/Passport/bootloader/{splash.h => ui-splash.h} (100%) diff --git a/ports/stm32/boards/Passport/bootloader/Makefile b/ports/stm32/boards/Passport/bootloader/Makefile index fccef0971..23ebf136f 100644 --- a/ports/stm32/boards/Passport/bootloader/Makefile +++ b/ports/stm32/boards/Passport/bootloader/Makefile @@ -52,10 +52,10 @@ SOURCES = startup_stm32h753xx.c SOURCES += startup.c SOURCES += main.c SOURCES += flash.c -SOURCES += splash.c SOURCES += update.c SOURCES += se-atecc608a.c SOURCES += ui.c +SOURCES += ui-splash.c SOURCES += verify.c SOURCES += version_info.c SOURCES += sd.c diff --git a/ports/stm32/boards/Passport/bootloader/factory-test.c b/ports/stm32/boards/Passport/bootloader/factory-test.c index 30a441465..f2185dd28 100644 --- a/ports/stm32/boards/Passport/bootloader/factory-test.c +++ b/ports/stm32/boards/Passport/bootloader/factory-test.c @@ -19,7 +19,7 @@ #include "ui.h" #include "display.h" #include "spiflash.h" -#include "splash.h" +#include "ui-splash.h" #include "se.h" #include "adc.h" #include "noise.h" diff --git a/ports/stm32/boards/Passport/bootloader/main.c b/ports/stm32/boards/Passport/bootloader/main.c index 0fa1d7ae8..6460d8c80 100644 --- a/ports/stm32/boards/Passport/bootloader/main.c +++ b/ports/stm32/boards/Passport/bootloader/main.c @@ -32,7 +32,7 @@ #include "gpio.h" #include "hash.h" #include "secresult.h" -#include "splash.h" +#include "ui-splash.h" #include "ui.h" #include "version_info.h" #include "images.h" diff --git a/ports/stm32/boards/Passport/bootloader/splash.c b/ports/stm32/boards/Passport/bootloader/ui-splash.c similarity index 92% rename from ports/stm32/boards/Passport/bootloader/splash.c rename to ports/stm32/boards/Passport/bootloader/ui-splash.c index 811bd5303..e00f17d5c 100644 --- a/ports/stm32/boards/Passport/bootloader/splash.c +++ b/ports/stm32/boards/Passport/bootloader/ui-splash.c @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2020 Foundation Devices, Inc. // SPDX-License-Identifier: GPL-3.0-or-later // -// splash.c - Splash screen shown at during initialization +// Splash screen shown at during initialization #include "display.h" #include "ui.h" diff --git a/ports/stm32/boards/Passport/bootloader/splash.h b/ports/stm32/boards/Passport/bootloader/ui-splash.h similarity index 100% rename from ports/stm32/boards/Passport/bootloader/splash.h rename to ports/stm32/boards/Passport/bootloader/ui-splash.h diff --git a/ports/stm32/boards/Passport/bootloader/update.c b/ports/stm32/boards/Passport/bootloader/update.c index d6112b500..173a1a2e7 100644 --- a/ports/stm32/boards/Passport/bootloader/update.c +++ b/ports/stm32/boards/Passport/bootloader/update.c @@ -23,7 +23,7 @@ #include "se.h" #include "sha256.h" #include "spiflash.h" -#include "splash.h" +#include "ui-splash.h" #include "utils.h" #include "firmware-keys.h" From df98570e4aea874caa1b498f4ad99544f6fde14b Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Tue, 16 May 2023 16:55:03 -0700 Subject: [PATCH 006/239] First pass - scrolling still needs to be added --- lib/lv_bindings/lv_conf.h | 2 +- ports/stm32/boards/Passport/manifest.py | 1 + .../modules/pages/file_picker_page.py | 149 ++++++++++------- ports/stm32/boards/Passport/modules/utils.py | 17 +- .../boards/Passport/modules/views/__init__.py | 1 + .../boards/Passport/modules/views/table.py | 157 ++++++++++++++++++ 6 files changed, 259 insertions(+), 68 deletions(-) create mode 100644 ports/stm32/boards/Passport/modules/views/table.py diff --git a/lib/lv_bindings/lv_conf.h b/lib/lv_bindings/lv_conf.h index 412145e1f..7804156fb 100644 --- a/lib/lv_bindings/lv_conf.h +++ b/lib/lv_bindings/lv_conf.h @@ -448,7 +448,7 @@ e.g. "stm32f769xx.h" or "stm32f429xx.h"*/ # define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/ #endif -#define LV_USE_TABLE 0 +#define LV_USE_TABLE 1 #define LV_USE_QRCODE 1 diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index d1d3e35ba..800efb1be 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -325,6 +325,7 @@ 'views/statusbar.py', 'views/switch.py', 'views/symbol_picker.py', + 'views/table.py', 'views/text_area.py', 'views/text_input.py', 'views/view.py')) diff --git a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py index e81113083..78ea8a0eb 100644 --- a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py +++ b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py @@ -6,11 +6,12 @@ import lvgl as lv import microns -from styles.colors import TEXT_GREY, SCROLLBAR_BG_COLOR +from styles.colors import TEXT_GREY, WHITE, FD_BLUE from styles import Stylize from pages import Page -from views import FileItem, View +from views import Table from micropython import const +from constants import MENU_ITEM_CORNER_RADIUS import passport MAX_FILE_DISPLAY = const(15) if passport.IS_COLOR else const(10) @@ -33,93 +34,123 @@ def __init__(self, default.flex_fill() default.flex_align(main=lv.FLEX_ALIGN.CENTER, cross=lv.FLEX_ALIGN.CENTER, track=lv.FLEX_ALIGN.CENTER) default.pad_row(0) + default.bg_color(WHITE) # Set non-style props self.set_width(lv.pct(100)) self.set_no_scroll() + self.table = Table(self.files) + self.table.set_width(lv.pct(100)) + self.table.set_height(lv.pct(100)) + self.table.set_scroll_dir(lv.DIR.VER) + + with Stylize(self.table, lv.PART.MAIN) as default: + default.bg_color(WHITE) + default.radius(4) + default.border_width(0) + + with Stylize(self.table, lv.PART.ITEMS) as items: + items.bg_color(WHITE) + items.text_color(TEXT_GREY) + items.border_width(0) + items.radius(MENU_ITEM_CORNER_RADIUS) + + with Stylize(self.table, lv.PART.ITEMS | lv.STATE.FOCUS_KEY) as focused: + focused.bg_color(FD_BLUE) + + + self.add_child(self.table) + # Add a scroll container for the list items, but disable scrollbars until attached - self.scroll_container = View(flex_flow=lv.FLEX_FLOW.COLUMN) - self.scroll_container.set_no_scroll() - self.scroll_container.set_width(lv.pct(100)) + # self.scroll_container = View(flex_flow=lv.FLEX_FLOW.COLUMN) + # self.scroll_container.set_no_scroll() + # self.scroll_container.set_width(lv.pct(100)) - with Stylize(self.scroll_container) as default: - default.flex_fill() - default.pad_row(0) + # with Stylize(self.scroll_container) as default: + # default.flex_fill() + # default.pad_row(0) - # Adjust scrollbar position - with Stylize(self.scroll_container, selector=lv.PART.SCROLLBAR) as scrollbar: - scrollbar.pad(right=0) - if not passport.IS_COLOR: - scrollbar.bg_color(SCROLLBAR_BG_COLOR) + # # Adjust scrollbar position + # with Stylize(self.scroll_container, selector=lv.PART.SCROLLBAR) as scrollbar: + # scrollbar.pad(right=0) # Add the file items to the scroll container - num_files = min(MAX_FILE_DISPLAY, len(self.files)) - for index in range(num_files): - filename, _full_path, is_folder = self.files[index] - self.scroll_container.add_child( - FileItem(filename=filename, is_folder=is_folder)) + # num_files = min(MAX_FILE_DISPLAY, len(self.files)) + # for index in range(num_files): + # filename, _full_path, is_folder = self.files[index] + # self.scroll_container.add_child( + # FileItem(filename=filename, is_folder=is_folder)) - self.add_child(self.scroll_container) - async def display(self, auto_close_timeout=None): - from pages import ErrorPage + # async def display(self, auto_close_timeout=None): + # from pages import ErrorPage - if len(self.files) > MAX_FILE_DISPLAY: - await ErrorPage(text="Unable to display all files. Displaying the first " - "{} files alphabetically.".format(MAX_FILE_DISPLAY)).show() + # if len(self.files) > MAX_FILE_DISPLAY: + # await ErrorPage(text="Unable to display all files. Displaying the first " + # "{} files alphabetically.".format(MAX_FILE_DISPLAY)).show() - await super().display() + # await super().display() def attach(self, group): super().attach(group) + group.add_obj(self.table.lvgl_root) # IMPORTANT: Add this to the group AFTER setting up gridnav - # Ensure scrollbars are enabled again - self.scroll_container.set_scroll_dir(lv.DIR.VER) + # # Ensure scrollbars are enabled again + # # self.scroll_container.set_scroll_dir(lv.DIR.VER) - # Setup gridnav for the layout - lv.gridnav_add(self.scroll_container.lvgl_root, lv.GRIDNAV_CTRL.NONE) - group.add_obj(self.scroll_container.lvgl_root) # IMPORTANT: Add this to the group AFTER setting up gridnav - def detach(self): - lv.group_remove_obj(self.scroll_container.lvgl_root) + # # Setup gridnav for the layout + # # lv.gridnav_add(self.scroll_container.lvgl_root, lv.GRIDNAV_CTRL.NONE) + # # group.add_obj(self.scroll_container.lvgl_root) # IMPORTANT: Add this to the group AFTER setting up gridnav - # Hide scrollbars during transitions - self.scroll_container.set_no_scroll() + def detach(self): + self.table.set_no_scroll() super().detach() - def get_selected_option_index_by_value(self, value): - for index in range(len(self.options)): - entry = self.options[index] - if entry.get('value') == value: - return index + # # Hide scrollbars during transitions + # self.scroll_container.set_no_scroll() + # super().detach() + + # def get_selected_option_index_by_value(self, value): + # for index in range(len(self.options)): + # entry = self.options[index] + # if entry.get('value') == value: + # return index - return 0 + # return 0 - def get_focused_item_index(self): - if self.is_mounted(): - focused_item = lv.gridnav_get_focused(self.scroll_container.lvgl_root) + # def get_focused_item_index(self): + # if self.is_mounted(): + # focused_item = lv.gridnav_get_focused(self.scroll_container.lvgl_root) - # Look through the children to find what index the selected one is at - for index in range(len(self.scroll_container.children)): - item = self.scroll_container.children[index] - if item.lvgl_root == focused_item: - return index + # # Look through the children to find what index the selected one is at + # for index in range(len(self.scroll_container.children)): + # item = self.scroll_container.children[index] + # if item.lvgl_root == focused_item: + # return index - # Should never happen - assert(False) - return None + # # Should never happen + # assert(False) + # return None def left_action(self, is_pressed): if not is_pressed: self.set_result(None) def right_action(self, is_pressed): - if not is_pressed: - try: - selected_option_idx = self.get_focused_item_index() - selected_file = self.files[selected_option_idx] - # print('Selected file: {}'.format(selected_file)) - self.set_result(selected_file) - except Exception as e: - assert(False, '{}'.format(e)) + selected_idx = self.table.get_selected_row() + + print('selected: {}'.format(selected_idx)) + + # if not is_pressed: + # self.set_result(None) + + # if not is_pressed: + # try: + # selected_option_idx = self.get_focused_item_index() + # selected_file = self.files[selected_option_idx] + # # print('Selected file: {}'.format(selected_file)) + # self.set_result(selected_file) + # except Exception as e: + # assert(False, '{}'.format(e)) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 3c2ba2e26..e1871d3b1 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1184,14 +1184,15 @@ def restore_sd_cb(): prev_sd_card_cb = CardSlot.get_sd_card_change_cb() CardSlot.set_sd_card_change_cb(sd_card_cb) - try: - await page.display() - except Exception as e: - page.unmount() - restore_sd_cb() - await on_result(None) - await ErrorPage(text='Unable to display page.').show() - return + # try: + await page.display() + # except Exception as e: + # print(e) + # page.unmount() + # restore_sd_cb() + # await on_result(None) + # await ErrorPage(text='Unable to display page.').show() + # return g = page.poll_for_done() while True: diff --git a/ports/stm32/boards/Passport/modules/views/__init__.py b/ports/stm32/boards/Passport/modules/views/__init__.py index f9932094d..58a6d6acb 100644 --- a/ports/stm32/boards/Passport/modules/views/__init__.py +++ b/ports/stm32/boards/Passport/modules/views/__init__.py @@ -32,6 +32,7 @@ from .card_nav import * from .slider import * from .switch import * +from .table import * from .color_picker import * from .pin_input import * from .symbol_picker import * diff --git a/ports/stm32/boards/Passport/modules/views/table.py b/ports/stm32/boards/Passport/modules/views/table.py new file mode 100644 index 000000000..ef6843489 --- /dev/null +++ b/ports/stm32/boards/Passport/modules/views/table.py @@ -0,0 +1,157 @@ +# SPDX-FileCopyrightText: © 2022 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# +# table.py - An LVGL table component wrapper + +import lvgl as lv +# from styles import Stylize, LocalStyle +from views import View +from styles.colors import WHITE, TEXT_GREY + +class Table(View): + def __init__(self, items=[], col_widths=[212]): + super().__init__() + self.items = items + self.col_widths = col_widths + + def set_src(self, src): + self.src = src + self.update() + + def change_event_cb(self, e): + print('change event!') + # obj = e.get_target() + row = lv.C_Pointer() + col = lv.C_Pointer() + self.lvgl_root.get_selected_cell(row, col) + print("row: ",row.uint_val) + + # chk = table.has_cell_ctrl(row.uint_val, 0, lv.table.CELL_CTRL.CUSTOM_1) + # if chk: + # table.clear_cell_ctrl(row.uint_val, 0, lv.table.CELL_CTRL.CUSTOM_1) + # else: + # table.add_cell_ctrl(row.uint_val, 0, lv.table.CELL_CTRL.CUSTOM + + def draw_part_begin_event_cb(self, e): + dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) + + # If the cells are drawn... + if dsc.part == lv.PART.ITEMS: + label_dsc = lv.draw_label_dsc_t.__cast__(dsc.label_dsc) + # print("draw 3: dir rect_dsc={}".format(dir(label_dsc))) + label_dsc.ofs_x += 28 + + def draw_part_end_event_cb(self, e): + print("Draw part end 1") + dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) + + # If the cells are drawn... + if dsc.part == lv.PART.ITEMS: + + # # If the cells are drawn... + # print("Draw part end 3") + # if dsc.part == lv.PART.ITEMS: + # Draw the icon + obj = e.get_target() + is_folder = obj.has_cell_ctrl(dsc.id, 0, lv.table.CELL_CTRL.CUSTOM_1) + + icon_area = lv.area_t() + icon = None + if is_folder: + print("FOLDER-----------------------------------------") + icon = lv.ICON_FOLDER + else: + print("FILE-------------------------------------------") + icon= lv.ICON_FILE + + # print("dir(dsc)={}".format(dir(dsc))) + print("p1={} p2={}".format(dsc.p1, dsc.p2)) + + selected_row = self.get_selected_row() + col_count = self.lvgl_root.get_row_cnt() + print("--> id={}".format(dsc.id)) + draw_row = dsc.id % col_count + draw_col = dsc.id // col_count + + print("--> selected_row={}".format(selected_row)) + print("--> draw_row={}".format(draw_row)) + print("--> draw_col={}".format(draw_col)) + selected = draw_row == selected_row + if selected: + print("Draw WHITE icon") + icon_color = WHITE + else: + print("Draw grey icon") + icon_color = TEXT_GREY + + + icon_img_dsc = lv.draw_img_dsc_t() + icon_img_dsc.init() + icon_img_dsc.recolor = icon_color + icon_img_dsc.recolor_opa=255 + + print("draw_area: x1={} x2={} y1={} y2={}".format( + dsc.draw_area.x1, + dsc.draw_area.x2, + dsc.draw_area.y1, + dsc.draw_area.y2 + )) + + # Setup the draw area + icon_area.x1 = 10 + dsc.draw_area.x1 + icon_area.y1 = dsc.draw_area.y1 + ((dsc.draw_area.y2 - dsc.draw_area.y1) - icon.header.h) // 2 + icon_area.x2 = icon_area.x1 + icon.header.w - 1 + icon_area.y2 = icon_area.y1 + icon.header.h - 1 + print("icon_area: x1={} x2={} y1={} y2={}".format( + icon_area.x1, + icon_area.x2, + icon_area.y1, + icon_area.y2 + )) + + print("clip_area: x1={} x2={} y1={} y2={}".format( + dsc.clip_area.x1, + dsc.clip_area.x2, + dsc.clip_area.y1, + dsc.clip_area.y2 + )) + # lv.draw_rect(icon_area, dsc.clip_area, rect_dsc) + lv.draw_img(icon_area, dsc.clip_area, icon, icon_img_dsc) + print("===============================================") + + + def create_lvgl_root(self, lvgl_parent): + table = lv.table(lvgl_parent) + + # root.set_row_cnt(len(self.items)) + # root.set_col_cnt(len(self.col_widths)) + + # Don't make the cell pressed, we will draw something different in the event + # table.remove_style(None, lv.PART.ITEMS | lv.STATE.PRESSED) + + for i, item in enumerate(self.items): + # print("data: {}: {}".format(i, item)) + (filename, _full_path, is_folder) = item + # root.set_cell_value(i, 0, ">" if is_folder else "F") + table.set_cell_value(i,0, filename) + if is_folder: + table.add_cell_ctrl(i, 0, lv.table.CELL_CTRL.CUSTOM_1) + else: + table.clear_cell_ctrl(i, 0, lv.table.CELL_CTRL.CUSTOM_1) + + for i, width in enumerate(self.col_widths): + # print("width: {}: {}".format(i, width)) + table.set_col_width(i, width) + + # Add an event callback to apply some custom drawing + table.add_event_cb(self.draw_part_begin_event_cb, lv.EVENT.DRAW_PART_BEGIN, None) + table.add_event_cb(self.draw_part_end_event_cb, lv.EVENT.DRAW_PART_END, None) + table.add_event_cb(self.change_event_cb, lv.EVENT.VALUE_CHANGED, None) + return table + + def get_selected_row(self): + row = lv.C_Pointer() + _col = lv.C_Pointer() + self.lvgl_root.get_selected_cell(row, _col) + print("row: {}".format(row.uint_val)) + return row.uint_val \ No newline at end of file From f7ec7f802cac01b72b39151c95d4e4ace1682505 Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Thu, 25 May 2023 09:47:47 -0400 Subject: [PATCH 007/239] Make scrolling to selected cell work Backported the v8.3 lv_table code for scrolling to selected cell Remaining issues: - Last item is clipped by 1-2 pixels, so cell height calculation may be incorrect - Need to implement key handling to select the active cell --- lib/lv_bindings/lvgl/src/widgets/lv_table.c | 247 +++++++++++++++----- 1 file changed, 184 insertions(+), 63 deletions(-) diff --git a/lib/lv_bindings/lvgl/src/widgets/lv_table.c b/lib/lv_bindings/lvgl/src/widgets/lv_table.c index a07c3249b..cf040c84d 100644 --- a/lib/lv_bindings/lvgl/src/widgets/lv_table.c +++ b/lib/lv_bindings/lvgl/src/widgets/lv_table.c @@ -36,8 +36,16 @@ static void draw_main(lv_event_t * e); static lv_coord_t get_row_height(lv_obj_t * obj, uint16_t row_id, const lv_font_t * font, lv_coord_t letter_space, lv_coord_t line_space, lv_coord_t cell_left, lv_coord_t cell_right, lv_coord_t cell_top, lv_coord_t cell_bottom); -static void refr_size(lv_obj_t * obj, uint32_t strat_row); -static lv_res_t get_pressed_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col); +static void refr_size_form_row(lv_obj_t *obj, uint32_t strat_row); +static void refr_cell_size(lv_obj_t *obj, uint32_t row, uint32_t col); +static lv_res_t get_pressed_cell(lv_obj_t *obj, uint16_t *row, uint16_t *col); +static void get_cell_area(lv_obj_t *obj, uint16_t row, uint16_t col, lv_area_t *area); +static void scroll_to_selected_cell(lv_obj_t *obj); + +static inline bool is_cell_empty(void *cell) +{ + return cell == NULL; +} /********************** * STATIC VARIABLES @@ -106,9 +114,7 @@ void lv_table_set_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col, const c #endif table->cell_data[cell][0] = ctrl; - refr_size(obj, row); - - lv_obj_invalidate(obj); + refr_cell_size(obj, row, col); } void lv_table_set_cell_value_fmt(lv_obj_t * obj, uint16_t row, uint16_t col, const char * fmt, ...) @@ -235,7 +241,7 @@ void lv_table_set_row_cnt(lv_obj_t * obj, uint16_t row_cnt) lv_memset_00(&table->cell_data[old_cell_cnt], (new_cell_cnt - old_cell_cnt) * sizeof(table->cell_data[0])); } - refr_size(obj, 0) ; + refr_size_form_row(obj, 0) ; } void lv_table_set_col_cnt(lv_obj_t * obj, uint16_t col_cnt) @@ -289,8 +295,7 @@ void lv_table_set_col_cnt(lv_obj_t * obj, uint16_t col_cnt) lv_mem_free(table->cell_data); table->cell_data = new_cell_data; - - refr_size(obj, 0) ; + refr_size_form_row(obj, 0); } void lv_table_set_col_width(lv_obj_t * obj, uint16_t col_id, lv_coord_t w) @@ -303,7 +308,7 @@ void lv_table_set_col_width(lv_obj_t * obj, uint16_t col_id, lv_coord_t w) if(col_id >= table->col_cnt) lv_table_set_col_cnt(obj, col_id + 1); table->col_w[col_id] = w; - refr_size(obj, 0) ; + refr_size_form_row(obj, 0); } void lv_table_add_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl) @@ -481,7 +486,7 @@ static void lv_table_event(const lv_obj_class_t * class_p, lv_event_t * e) lv_table_t * table = (lv_table_t *)obj; if(code == LV_EVENT_STYLE_CHANGED) { - refr_size(obj, 0); + refr_size_form_row(obj, 0); } else if(code == LV_EVENT_GET_SELF_SIZE) { lv_point_t * p = lv_event_get_param(e); @@ -531,6 +536,7 @@ static void lv_table_event(const lv_obj_class_t * class_p, lv_event_t * e) if(col == LV_TABLE_CELL_NONE || row == LV_TABLE_CELL_NONE) { table->col_act = 0; table->row_act = 0; + scroll_to_selected_cell(obj); lv_obj_invalidate(obj); return; } @@ -575,6 +581,7 @@ static void lv_table_event(const lv_obj_class_t * class_p, lv_event_t * e) table->row_act = row; lv_obj_invalidate(obj); + scroll_to_selected_cell(obj); res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); if(res != LV_RES_OK) return; } @@ -773,85 +780,137 @@ static void draw_main(lv_event_t * e) } } -static void refr_size(lv_obj_t * obj, uint32_t strat_row) +/* Refreshes size of the table starting from @start_row row */ +static void refr_size_form_row(lv_obj_t *obj, uint32_t start_row) { - lv_table_t * table = (lv_table_t *)obj; + const lv_coord_t cell_pad_left = lv_obj_get_style_pad_left(obj, LV_PART_ITEMS); + const lv_coord_t cell_pad_right = lv_obj_get_style_pad_right(obj, LV_PART_ITEMS); + const lv_coord_t cell_pad_top = lv_obj_get_style_pad_top(obj, LV_PART_ITEMS); + const lv_coord_t cell_pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_ITEMS); + + lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_ITEMS); + lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_ITEMS); + const lv_font_t *font = lv_obj_get_style_text_font(obj, LV_PART_ITEMS); + const lv_coord_t minh = lv_obj_get_style_min_height(obj, LV_PART_ITEMS); + const lv_coord_t maxh = lv_obj_get_style_max_height(obj, LV_PART_ITEMS); + + lv_table_t *table = (lv_table_t *)obj; uint32_t i; + for (i = start_row; i < table->row_cnt; i++) + { + lv_coord_t calculated_height = get_row_height(obj, i, font, letter_space, line_space, + cell_pad_left, cell_pad_right, cell_pad_top, cell_pad_bottom); + table->row_h[i] = LV_CLAMP(minh, calculated_height, maxh); + } - lv_coord_t cell_left = lv_obj_get_style_pad_left(obj, LV_PART_ITEMS); - lv_coord_t cell_right = lv_obj_get_style_pad_right(obj, LV_PART_ITEMS); - lv_coord_t cell_top = lv_obj_get_style_pad_top(obj, LV_PART_ITEMS); - lv_coord_t cell_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_ITEMS); + lv_obj_refresh_self_size(obj); + lv_obj_invalidate(obj); +} + +static void refr_cell_size(lv_obj_t *obj, uint32_t row, uint32_t col) +{ + const lv_coord_t cell_pad_left = lv_obj_get_style_pad_left(obj, LV_PART_ITEMS); + const lv_coord_t cell_pad_right = lv_obj_get_style_pad_right(obj, LV_PART_ITEMS); + const lv_coord_t cell_pad_top = lv_obj_get_style_pad_top(obj, LV_PART_ITEMS); + const lv_coord_t cell_pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_ITEMS); lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_ITEMS); lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_ITEMS); - const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_ITEMS); + const lv_font_t *font = lv_obj_get_style_text_font(obj, LV_PART_ITEMS); - lv_coord_t minh = lv_obj_get_style_min_height(obj, LV_PART_ITEMS); - lv_coord_t maxh = lv_obj_get_style_max_height(obj, LV_PART_ITEMS); + const lv_coord_t minh = lv_obj_get_style_min_height(obj, LV_PART_ITEMS); + const lv_coord_t maxh = lv_obj_get_style_max_height(obj, LV_PART_ITEMS); - for(i = strat_row; i < table->row_cnt; i++) { - table->row_h[i] = get_row_height(obj, i, font, letter_space, line_space, - cell_left, cell_right, cell_top, cell_bottom); - table->row_h[i] = LV_CLAMP(minh, table->row_h[i], maxh); - } + lv_table_t *table = (lv_table_t *)obj; + lv_coord_t calculated_height = get_row_height(obj, row, font, letter_space, line_space, + cell_pad_left, cell_pad_right, cell_pad_top, cell_pad_bottom); - lv_obj_refresh_self_size(obj) ; + lv_coord_t prev_row_size = table->row_h[row]; + table->row_h[row] = LV_CLAMP(minh, calculated_height, maxh); + + /*If the row height havn't changed invalidate only this cell*/ + if (prev_row_size == table->row_h[row]) + { + lv_area_t cell_area; + get_cell_area(obj, row, col, &cell_area); + lv_area_move(&cell_area, obj->coords.x1, obj->coords.y1); + lv_obj_invalidate_area(obj, &cell_area); + } + else + { + lv_obj_refresh_self_size(obj); + lv_obj_invalidate(obj); + } } -static lv_coord_t get_row_height(lv_obj_t * obj, uint16_t row_id, const lv_font_t * font, +static lv_coord_t get_row_height(lv_obj_t *obj, uint16_t row_id, const lv_font_t *font, lv_coord_t letter_space, lv_coord_t line_space, lv_coord_t cell_left, lv_coord_t cell_right, lv_coord_t cell_top, lv_coord_t cell_bottom) { - lv_table_t * table = (lv_table_t *)obj; - lv_point_t txt_size; - lv_coord_t txt_w; + lv_table_t *table = (lv_table_t *)obj; + lv_coord_t h_max = lv_font_get_line_height(font) + cell_top + cell_bottom; + /* Calculate the cell_data index where to start */ uint16_t row_start = row_id * table->col_cnt; + + /* Traverse the cells in the row_id row */ uint16_t cell; uint16_t col; - lv_coord_t h_max = lv_font_get_line_height(font) + cell_top + cell_bottom; + for (cell = row_start, col = 0; cell < row_start + table->col_cnt; cell++, col++) + { + char *cell_data = table->cell_data[cell]; - for(cell = row_start, col = 0; cell < row_start + table->col_cnt; cell++, col++) { - if(table->cell_data[cell] != NULL) { - txt_w = table->col_w[col]; - uint16_t col_merge = 0; - for(col_merge = 0; col_merge + col < table->col_cnt - 1; col_merge++) { + if (is_cell_empty(cell_data)) + { + continue; + } - if(table->cell_data[cell + col_merge] != NULL) { - lv_table_cell_ctrl_t ctrl = 0; - char * next_cell_data = table->cell_data[cell + col_merge]; - if(next_cell_data) ctrl = next_cell_data[0]; - if(ctrl & LV_TABLE_CELL_CTRL_MERGE_RIGHT) - txt_w += table->col_w[col + col_merge + 1]; - else - break; - } - else { - break; - } - } + lv_coord_t txt_w = table->col_w[col]; - lv_table_cell_ctrl_t ctrl = 0; - if(table->cell_data[cell]) ctrl = table->cell_data[cell][0]; + /* Traverse the current row from the first until the penultimate column. + * Increment the text width if the cell has the LV_TABLE_CELL_CTRL_MERGE_RIGHT control, + * exit the traversal when the current cell control is not LV_TABLE_CELL_CTRL_MERGE_RIGHT */ + uint16_t col_merge = 0; + for (col_merge = 0; col_merge + col < table->col_cnt - 1; col_merge++) + { + char *next_cell_data = table->cell_data[cell + col_merge]; - /*With text crop assume 1 line*/ - if(ctrl & LV_TABLE_CELL_CTRL_TEXT_CROP) { - h_max = LV_MAX(lv_font_get_line_height(font) + cell_top + cell_bottom, - h_max); + if (is_cell_empty(next_cell_data)) + break; + + lv_table_cell_ctrl_t ctrl = (lv_table_cell_ctrl_t)next_cell_data[0]; + if (ctrl & LV_TABLE_CELL_CTRL_MERGE_RIGHT) + { + txt_w += table->col_w[col + col_merge + 1]; } - /*Without text crop calculate the height of the text in the cell*/ - else { - txt_w -= cell_left + cell_right; + else + { + break; + } + } - lv_txt_get_size(&txt_size, table->cell_data[cell] + 1, font, - letter_space, line_space, txt_w, LV_TEXT_FLAG_NONE); + lv_table_cell_ctrl_t ctrl = (lv_table_cell_ctrl_t)cell_data[0]; - h_max = LV_MAX(txt_size.y + cell_top + cell_bottom, h_max); - cell += col_merge; - col += col_merge; - } + /*When cropping the text we can assume the row height is equal to the line height*/ + if (ctrl & LV_TABLE_CELL_CTRL_TEXT_CROP) + { + h_max = LV_MAX(lv_font_get_line_height(font) + cell_top + cell_bottom, + h_max); + } + /*Else we have to calculate the height of the cell text*/ + else + { + lv_point_t txt_size; + txt_w -= cell_left + cell_right; + + lv_txt_get_size(&txt_size, table->cell_data[cell] + 1, font, + letter_space, line_space, txt_w, LV_TEXT_FLAG_NONE); + + h_max = LV_MAX(txt_size.y + cell_top + cell_bottom, h_max); + /*Skip until one element after the last merged column*/ + cell += col_merge; + col += col_merge; } } @@ -909,5 +968,67 @@ static lv_res_t get_pressed_cell(lv_obj_t * obj, uint16_t * row, uint16_t * col) return LV_RES_OK; } +static void get_cell_area(lv_obj_t *obj, uint16_t row, uint16_t col, lv_area_t *area) +{ + lv_table_t *table = (lv_table_t *)obj; + + uint32_t c; + area->x1 = 0; + for (c = 0; c < col; c++) + { + area->x1 += table->col_w[c]; + } + + bool rtl = lv_obj_get_style_base_dir(obj, LV_PART_MAIN) == LV_BASE_DIR_RTL; + if (rtl) + { + area->x1 += lv_obj_get_scroll_x(obj); + lv_coord_t w = lv_obj_get_width(obj); + area->x2 = w - area->x1 - lv_obj_get_style_pad_right(obj, 0); + area->x1 = area->x2 - table->col_w[col]; + } + else + { + area->x1 -= lv_obj_get_scroll_x(obj); + area->x1 += lv_obj_get_style_pad_left(obj, 0); + area->x2 = area->x1 + table->col_w[col] - 1; + } + + uint32_t r; + area->y1 = 0; + for (r = 0; r < row; r++) + { + area->y1 += table->row_h[r]; + } + + area->y1 += lv_obj_get_style_pad_top(obj, 0); + area->y1 -= lv_obj_get_scroll_y(obj); + area->y2 = area->y1 + table->row_h[row] - 1; +} + +static void scroll_to_selected_cell(lv_obj_t *obj) +{ + lv_table_t *table = (lv_table_t *)obj; + + lv_area_t a; + get_cell_area(obj, table->row_act, table->col_act, &a); + if (a.x1 < 0) + { + lv_obj_scroll_by_bounded(obj, -a.x1, 0, LV_ANIM_ON); + } + else if (a.x2 > lv_obj_get_width(obj)) + { + lv_obj_scroll_by_bounded(obj, lv_obj_get_width(obj) - a.x2, 0, LV_ANIM_ON); + } + + if (a.y1 < 0) + { + lv_obj_scroll_by_bounded(obj, 0, -a.y1, LV_ANIM_ON); + } + else if (a.y2 > lv_obj_get_height(obj)) + { + lv_obj_scroll_by_bounded(obj, 0, lv_obj_get_height(obj) - a.y2, LV_ANIM_ON); + } +} #endif From b6c82cf4b236d961eb3c89b6ea2d30f6b7c31d6e Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Tue, 6 Jun 2023 23:18:31 -0700 Subject: [PATCH 008/239] Make table code more generic Add back file/folder selection --- lib/lv_bindings/lvgl/src/widgets/lv_table.c | 4 +- .../modules/pages/file_picker_page.py | 35 ++--- .../boards/Passport/modules/views/table.py | 135 ++++++++---------- 3 files changed, 82 insertions(+), 92 deletions(-) diff --git a/lib/lv_bindings/lvgl/src/widgets/lv_table.c b/lib/lv_bindings/lvgl/src/widgets/lv_table.c index cf040c84d..3412cf152 100644 --- a/lib/lv_bindings/lvgl/src/widgets/lv_table.c +++ b/lib/lv_bindings/lvgl/src/widgets/lv_table.c @@ -745,7 +745,7 @@ static void draw_main(lv_event_t * e) if(table->cell_data[cell]) { txt_area.x1 = cell_area.x1 + cell_left; - txt_area.x2 = cell_area.x2 - cell_right; + txt_area.x2 = cell_area.x2 - cell_right - 28; // FOUNDATION txt_area.y1 = cell_area.y1 + cell_top; txt_area.y2 = cell_area.y2 - cell_bottom; @@ -1027,7 +1027,7 @@ static void scroll_to_selected_cell(lv_obj_t *obj) } else if (a.y2 > lv_obj_get_height(obj)) { - lv_obj_scroll_by_bounded(obj, 0, lv_obj_get_height(obj) - a.y2, LV_ANIM_ON); + lv_obj_scroll_by_bounded(obj, 0, lv_obj_get_height(obj) - a.y2 - 10, LV_ANIM_ON); } } diff --git a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py index 78ea8a0eb..1af865a1e 100644 --- a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py +++ b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py @@ -16,6 +16,13 @@ MAX_FILE_DISPLAY = const(15) if passport.IS_COLOR else const(10) +# Returns a tuple where the first value is the string to show in the list and the second value is +# whether to show the default icon or the alt icon. +# NOTE: Doesn't support showing arbitrary icons at this time. +def get_file_info(item): + (filename, _full_path, is_folder) = item + return (filename, is_folder) + class FilePickerPage(Page): def __init__(self, @@ -40,7 +47,7 @@ def __init__(self, self.set_width(lv.pct(100)) self.set_no_scroll() - self.table = Table(self.files) + self.table = Table(items=self.files, get_cell_info=get_file_info) self.table.set_width(lv.pct(100)) self.table.set_height(lv.pct(100)) self.table.set_scroll_dir(lv.DIR.VER) @@ -139,18 +146,14 @@ def left_action(self, is_pressed): self.set_result(None) def right_action(self, is_pressed): - selected_idx = self.table.get_selected_row() - - print('selected: {}'.format(selected_idx)) - - # if not is_pressed: - # self.set_result(None) - - # if not is_pressed: - # try: - # selected_option_idx = self.get_focused_item_index() - # selected_file = self.files[selected_option_idx] - # # print('Selected file: {}'.format(selected_file)) - # self.set_result(selected_file) - # except Exception as e: - # assert(False, '{}'.format(e)) + print('Right Action!') + if not is_pressed: + try: + selected_idx = self.table.get_selected_row() + print('selected: {}'.format(selected_idx)) + selected_file = self.files[selected_idx] + print('Selected file: {}'.format(selected_file)) + self.set_result(selected_file) + except Exception as e: + print("Exception: {}".format(e)) + assert(False, '{}'.format(e)) \ No newline at end of file diff --git a/ports/stm32/boards/Passport/modules/views/table.py b/ports/stm32/boards/Passport/modules/views/table.py index ef6843489..8c8b79312 100644 --- a/ports/stm32/boards/Passport/modules/views/table.py +++ b/ports/stm32/boards/Passport/modules/views/table.py @@ -4,33 +4,33 @@ # table.py - An LVGL table component wrapper import lvgl as lv -# from styles import Stylize, LocalStyle from views import View from styles.colors import WHITE, TEXT_GREY class Table(View): - def __init__(self, items=[], col_widths=[212]): + def __init__(self, + items=[], + col_width=212, + default_icon=lv.ICON_FILE, + alt_icon=lv.ICON_FOLDER, + get_cell_info=None): super().__init__() self.items = items - self.col_widths = col_widths + self.col_width = col_width + self.default_icon = default_icon + self.alt_icon = alt_icon + self.get_cell_info = get_cell_info def set_src(self, src): self.src = src self.update() - def change_event_cb(self, e): - print('change event!') - # obj = e.get_target() - row = lv.C_Pointer() - col = lv.C_Pointer() - self.lvgl_root.get_selected_cell(row, col) - print("row: ",row.uint_val) - - # chk = table.has_cell_ctrl(row.uint_val, 0, lv.table.CELL_CTRL.CUSTOM_1) - # if chk: - # table.clear_cell_ctrl(row.uint_val, 0, lv.table.CELL_CTRL.CUSTOM_1) - # else: - # table.add_cell_ctrl(row.uint_val, 0, lv.table.CELL_CTRL.CUSTOM + # def change_event_cb(self, e): + # print('change event!') + # # obj = e.get_target() + # row = lv.C_Pointer() + # col = lv.C_Pointer() + # self.lvgl_root.get_selected_cell(row, col) def draw_part_begin_event_cb(self, e): dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) @@ -38,50 +38,41 @@ def draw_part_begin_event_cb(self, e): # If the cells are drawn... if dsc.part == lv.PART.ITEMS: label_dsc = lv.draw_label_dsc_t.__cast__(dsc.label_dsc) - # print("draw 3: dir rect_dsc={}".format(dir(label_dsc))) label_dsc.ofs_x += 28 def draw_part_end_event_cb(self, e): - print("Draw part end 1") + # print("Draw part end 1") dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) - # If the cells are drawn... + # If the cells are being drawn... if dsc.part == lv.PART.ITEMS: - # # If the cells are drawn... - # print("Draw part end 3") - # if dsc.part == lv.PART.ITEMS: # Draw the icon obj = e.get_target() - is_folder = obj.has_cell_ctrl(dsc.id, 0, lv.table.CELL_CTRL.CUSTOM_1) + is_alt_icon = obj.has_cell_ctrl(dsc.id, 0, lv.table.CELL_CTRL.CUSTOM_1) icon_area = lv.area_t() icon = None - if is_folder: - print("FOLDER-----------------------------------------") - icon = lv.ICON_FOLDER + if is_alt_icon: + icon = self.alt_icon else: - print("FILE-------------------------------------------") - icon= lv.ICON_FILE - - # print("dir(dsc)={}".format(dir(dsc))) - print("p1={} p2={}".format(dsc.p1, dsc.p2)) + icon= self.default_icon selected_row = self.get_selected_row() col_count = self.lvgl_root.get_row_cnt() - print("--> id={}".format(dsc.id)) + # print("--> id={}".format(dsc.id)) draw_row = dsc.id % col_count draw_col = dsc.id // col_count - print("--> selected_row={}".format(selected_row)) - print("--> draw_row={}".format(draw_row)) - print("--> draw_col={}".format(draw_col)) + # print("--> selected_row={}".format(selected_row)) + # print("--> draw_row={}".format(draw_row)) + # print("--> draw_col={}".format(draw_col)) selected = draw_row == selected_row if selected: - print("Draw WHITE icon") + # print("Draw WHITE icon") icon_color = WHITE else: - print("Draw grey icon") + # print("Draw grey icon") icon_color = TEXT_GREY @@ -90,68 +81,64 @@ def draw_part_end_event_cb(self, e): icon_img_dsc.recolor = icon_color icon_img_dsc.recolor_opa=255 - print("draw_area: x1={} x2={} y1={} y2={}".format( - dsc.draw_area.x1, - dsc.draw_area.x2, - dsc.draw_area.y1, - dsc.draw_area.y2 - )) + # print("draw_area: x1={} x2={} y1={} y2={}".format( + # dsc.draw_area.x1, + # dsc.draw_area.x2, + # dsc.draw_area.y1, + # dsc.draw_area.y2 + # )) # Setup the draw area icon_area.x1 = 10 + dsc.draw_area.x1 icon_area.y1 = dsc.draw_area.y1 + ((dsc.draw_area.y2 - dsc.draw_area.y1) - icon.header.h) // 2 icon_area.x2 = icon_area.x1 + icon.header.w - 1 icon_area.y2 = icon_area.y1 + icon.header.h - 1 - print("icon_area: x1={} x2={} y1={} y2={}".format( - icon_area.x1, - icon_area.x2, - icon_area.y1, - icon_area.y2 - )) - - print("clip_area: x1={} x2={} y1={} y2={}".format( - dsc.clip_area.x1, - dsc.clip_area.x2, - dsc.clip_area.y1, - dsc.clip_area.y2 - )) - # lv.draw_rect(icon_area, dsc.clip_area, rect_dsc) + + # print("icon_area: x1={} x2={} y1={} y2={}".format( + # icon_area.x1, + # icon_area.x2, + # icon_area.y1, + # icon_area.y2 + # )) + + # print("clip_area: x1={} x2={} y1={} y2={}".format( + # dsc.clip_area.x1, + # dsc.clip_area.x2, + # dsc.clip_area.y1, + # dsc.clip_area.y2 + # )) + lv.draw_img(icon_area, dsc.clip_area, icon, icon_img_dsc) - print("===============================================") + # print("===============================================") def create_lvgl_root(self, lvgl_parent): table = lv.table(lvgl_parent) - # root.set_row_cnt(len(self.items)) - # root.set_col_cnt(len(self.col_widths)) - - # Don't make the cell pressed, we will draw something different in the event - # table.remove_style(None, lv.PART.ITEMS | lv.STATE.PRESSED) - for i, item in enumerate(self.items): - # print("data: {}: {}".format(i, item)) - (filename, _full_path, is_folder) = item - # root.set_cell_value(i, 0, ">" if is_folder else "F") - table.set_cell_value(i,0, filename) - if is_folder: + if self.get_cell_info is not None: + (label, is_alt_icon) = self.get_cell_info(item) + else: + label = item + is_alt_icon = False + + table.set_cell_value(i,0, label) + if is_alt_icon: table.add_cell_ctrl(i, 0, lv.table.CELL_CTRL.CUSTOM_1) else: table.clear_cell_ctrl(i, 0, lv.table.CELL_CTRL.CUSTOM_1) - for i, width in enumerate(self.col_widths): - # print("width: {}: {}".format(i, width)) - table.set_col_width(i, width) + table.set_col_width(0, self.col_width) # Add an event callback to apply some custom drawing table.add_event_cb(self.draw_part_begin_event_cb, lv.EVENT.DRAW_PART_BEGIN, None) table.add_event_cb(self.draw_part_end_event_cb, lv.EVENT.DRAW_PART_END, None) - table.add_event_cb(self.change_event_cb, lv.EVENT.VALUE_CHANGED, None) + # table.add_event_cb(self.change_event_cb, lv.EVENT.VALUE_CHANGED, None) return table def get_selected_row(self): row = lv.C_Pointer() _col = lv.C_Pointer() self.lvgl_root.get_selected_cell(row, _col) - print("row: {}".format(row.uint_val)) + # print("row: {}".format(row.uint_val)) return row.uint_val \ No newline at end of file From 390bcabd7b2ae855ed8cfdca8ef2f3d2b037908a Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Thu, 8 Jun 2023 18:40:38 -0700 Subject: [PATCH 009/239] Fix lint warnings --- .../modules/pages/file_picker_page.py | 76 +------------------ .../boards/Passport/modules/views/table.py | 53 ++----------- 2 files changed, 10 insertions(+), 119 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py index 1af865a1e..bfbba207c 100644 --- a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py +++ b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py @@ -16,9 +16,7 @@ MAX_FILE_DISPLAY = const(15) if passport.IS_COLOR else const(10) -# Returns a tuple where the first value is the string to show in the list and the second value is -# whether to show the default icon or the alt icon. -# NOTE: Doesn't support showing arbitrary icons at this time. + def get_file_info(item): (filename, _full_path, is_folder) = item return (filename, is_folder) @@ -66,94 +64,26 @@ def __init__(self, with Stylize(self.table, lv.PART.ITEMS | lv.STATE.FOCUS_KEY) as focused: focused.bg_color(FD_BLUE) - self.add_child(self.table) - # Add a scroll container for the list items, but disable scrollbars until attached - # self.scroll_container = View(flex_flow=lv.FLEX_FLOW.COLUMN) - # self.scroll_container.set_no_scroll() - # self.scroll_container.set_width(lv.pct(100)) - - # with Stylize(self.scroll_container) as default: - # default.flex_fill() - # default.pad_row(0) - - # # Adjust scrollbar position - # with Stylize(self.scroll_container, selector=lv.PART.SCROLLBAR) as scrollbar: - # scrollbar.pad(right=0) - - # Add the file items to the scroll container - # num_files = min(MAX_FILE_DISPLAY, len(self.files)) - # for index in range(num_files): - # filename, _full_path, is_folder = self.files[index] - # self.scroll_container.add_child( - # FileItem(filename=filename, is_folder=is_folder)) - - - # async def display(self, auto_close_timeout=None): - # from pages import ErrorPage - - # if len(self.files) > MAX_FILE_DISPLAY: - # await ErrorPage(text="Unable to display all files. Displaying the first " - # "{} files alphabetically.".format(MAX_FILE_DISPLAY)).show() - - # await super().display() - def attach(self, group): super().attach(group) - group.add_obj(self.table.lvgl_root) # IMPORTANT: Add this to the group AFTER setting up gridnav - - # # Ensure scrollbars are enabled again - # # self.scroll_container.set_scroll_dir(lv.DIR.VER) - - - # # Setup gridnav for the layout - # # lv.gridnav_add(self.scroll_container.lvgl_root, lv.GRIDNAV_CTRL.NONE) - # # group.add_obj(self.scroll_container.lvgl_root) # IMPORTANT: Add this to the group AFTER setting up gridnav + group.add_obj(self.table.lvgl_root) def detach(self): self.table.set_no_scroll() super().detach() - # # Hide scrollbars during transitions - # self.scroll_container.set_no_scroll() - # super().detach() - - # def get_selected_option_index_by_value(self, value): - # for index in range(len(self.options)): - # entry = self.options[index] - # if entry.get('value') == value: - # return index - - # return 0 - - # def get_focused_item_index(self): - # if self.is_mounted(): - # focused_item = lv.gridnav_get_focused(self.scroll_container.lvgl_root) - - # # Look through the children to find what index the selected one is at - # for index in range(len(self.scroll_container.children)): - # item = self.scroll_container.children[index] - # if item.lvgl_root == focused_item: - # return index - - # # Should never happen - # assert(False) - # return None - def left_action(self, is_pressed): if not is_pressed: self.set_result(None) def right_action(self, is_pressed): - print('Right Action!') if not is_pressed: try: selected_idx = self.table.get_selected_row() - print('selected: {}'.format(selected_idx)) selected_file = self.files[selected_idx] - print('Selected file: {}'.format(selected_file)) self.set_result(selected_file) except Exception as e: print("Exception: {}".format(e)) - assert(False, '{}'.format(e)) \ No newline at end of file + assert(False, '{}'.format(e)) diff --git a/ports/stm32/boards/Passport/modules/views/table.py b/ports/stm32/boards/Passport/modules/views/table.py index 8c8b79312..7dee54f05 100644 --- a/ports/stm32/boards/Passport/modules/views/table.py +++ b/ports/stm32/boards/Passport/modules/views/table.py @@ -7,6 +7,7 @@ from views import View from styles.colors import WHITE, TEXT_GREY + class Table(View): def __init__(self, items=[], @@ -25,13 +26,6 @@ def set_src(self, src): self.src = src self.update() - # def change_event_cb(self, e): - # print('change event!') - # # obj = e.get_target() - # row = lv.C_Pointer() - # col = lv.C_Pointer() - # self.lvgl_root.get_selected_cell(row, col) - def draw_part_begin_event_cb(self, e): dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) @@ -39,9 +33,8 @@ def draw_part_begin_event_cb(self, e): if dsc.part == lv.PART.ITEMS: label_dsc = lv.draw_label_dsc_t.__cast__(dsc.label_dsc) label_dsc.ofs_x += 28 - + def draw_part_end_event_cb(self, e): - # print("Draw part end 1") dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) # If the cells are being drawn... @@ -56,37 +49,22 @@ def draw_part_end_event_cb(self, e): if is_alt_icon: icon = self.alt_icon else: - icon= self.default_icon + icon = self.default_icon - selected_row = self.get_selected_row() + selected_row = self.get_selected_row() col_count = self.lvgl_root.get_row_cnt() - # print("--> id={}".format(dsc.id)) draw_row = dsc.id % col_count - draw_col = dsc.id // col_count - # print("--> selected_row={}".format(selected_row)) - # print("--> draw_row={}".format(draw_row)) - # print("--> draw_col={}".format(draw_col)) selected = draw_row == selected_row if selected: - # print("Draw WHITE icon") icon_color = WHITE else: - # print("Draw grey icon") icon_color = TEXT_GREY - icon_img_dsc = lv.draw_img_dsc_t() icon_img_dsc.init() icon_img_dsc.recolor = icon_color - icon_img_dsc.recolor_opa=255 - - # print("draw_area: x1={} x2={} y1={} y2={}".format( - # dsc.draw_area.x1, - # dsc.draw_area.x2, - # dsc.draw_area.y1, - # dsc.draw_area.y2 - # )) + icon_img_dsc.recolor_opa = 255 # Setup the draw area icon_area.x1 = 10 + dsc.draw_area.x1 @@ -94,23 +72,7 @@ def draw_part_end_event_cb(self, e): icon_area.x2 = icon_area.x1 + icon.header.w - 1 icon_area.y2 = icon_area.y1 + icon.header.h - 1 - # print("icon_area: x1={} x2={} y1={} y2={}".format( - # icon_area.x1, - # icon_area.x2, - # icon_area.y1, - # icon_area.y2 - # )) - - # print("clip_area: x1={} x2={} y1={} y2={}".format( - # dsc.clip_area.x1, - # dsc.clip_area.x2, - # dsc.clip_area.y1, - # dsc.clip_area.y2 - # )) - lv.draw_img(icon_area, dsc.clip_area, icon, icon_img_dsc) - # print("===============================================") - def create_lvgl_root(self, lvgl_parent): table = lv.table(lvgl_parent) @@ -122,7 +84,7 @@ def create_lvgl_root(self, lvgl_parent): label = item is_alt_icon = False - table.set_cell_value(i,0, label) + table.set_cell_value(i, 0, label) if is_alt_icon: table.add_cell_ctrl(i, 0, lv.table.CELL_CTRL.CUSTOM_1) else: @@ -140,5 +102,4 @@ def get_selected_row(self): row = lv.C_Pointer() _col = lv.C_Pointer() self.lvgl_root.get_selected_cell(row, _col) - # print("row: {}".format(row.uint_val)) - return row.uint_val \ No newline at end of file + return row.uint_val From 6e5c20d1880aa8e6414744b46fbcc36e48f1125e Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Fri, 9 Jun 2023 17:27:29 -0700 Subject: [PATCH 010/239] Make the item size match normal menus --- ports/stm32/boards/Passport/modules/views/table.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/views/table.py b/ports/stm32/boards/Passport/modules/views/table.py index 7dee54f05..0263dacc0 100644 --- a/ports/stm32/boards/Passport/modules/views/table.py +++ b/ports/stm32/boards/Passport/modules/views/table.py @@ -6,7 +6,7 @@ import lvgl as lv from views import View from styles.colors import WHITE, TEXT_GREY - +from styles import Stylize class Table(View): def __init__(self, @@ -22,6 +22,9 @@ def __init__(self, self.alt_icon = alt_icon self.get_cell_info = get_cell_info + with Stylize(self, lv.PART.ITEMS) as default: + default.pad(bottom=11, top=12) + def set_src(self, src): self.src = src self.update() @@ -103,3 +106,4 @@ def get_selected_row(self): _col = lv.C_Pointer() self.lvgl_root.get_selected_cell(row, _col) return row.uint_val + From df20931d90f10a82294f61e98573212858f7baf2 Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Fri, 9 Jun 2023 17:48:11 -0700 Subject: [PATCH 011/239] Fix lint --- ports/stm32/boards/Passport/modules/views/table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/views/table.py b/ports/stm32/boards/Passport/modules/views/table.py index 0263dacc0..6399332e8 100644 --- a/ports/stm32/boards/Passport/modules/views/table.py +++ b/ports/stm32/boards/Passport/modules/views/table.py @@ -8,6 +8,7 @@ from styles.colors import WHITE, TEXT_GREY from styles import Stylize + class Table(View): def __init__(self, items=[], @@ -106,4 +107,3 @@ def get_selected_row(self): _col = lv.C_Pointer() self.lvgl_root.get_selected_cell(row, _col) return row.uint_val - From c60762161dadecdc65048c7ab4499f8bcc84fd53 Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Fri, 9 Jun 2023 22:13:52 -0700 Subject: [PATCH 012/239] Adjust scrollbar position & padding --- .../boards/Passport/modules/pages/file_picker_page.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py index bfbba207c..d78a7bb2a 100644 --- a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py +++ b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py @@ -50,6 +50,14 @@ def __init__(self, self.table.set_height(lv.pct(100)) self.table.set_scroll_dir(lv.DIR.VER) + with Stylize(self.table) as default: + default.flex_fill() + default.pad_row(0) + + # Adjust scrollbar position + with Stylize(self.table, selector=lv.PART.SCROLLBAR) as scrollbar: + scrollbar.pad(right=0) + with Stylize(self.table, lv.PART.MAIN) as default: default.bg_color(WHITE) default.radius(4) From c9fed1f794c74a284d21690876fec5d2b57f980a Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Mon, 12 Jun 2023 17:36:03 -0700 Subject: [PATCH 013/239] Fix visual glitch on last table item Add back code that was temporarily commented out --- .../extra/themes/default/lv_theme_default.c | 3 ++- lib/lv_bindings/lvgl/src/widgets/lv_table.c | 5 +++-- ports/stm32/boards/Passport/modules/utils.py | 18 +++++++++--------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/lv_bindings/lvgl/src/extra/themes/default/lv_theme_default.c b/lib/lv_bindings/lvgl/src/extra/themes/default/lv_theme_default.c index df4174460..33899692b 100644 --- a/lib/lv_bindings/lvgl/src/extra/themes/default/lv_theme_default.c +++ b/lib/lv_bindings/lvgl/src/extra/themes/default/lv_theme_default.c @@ -211,7 +211,8 @@ static lv_color_t grey_filter_cb(const lv_color_filter_dsc_t *f, lv_color_t colo static void style_init(void) { static const lv_style_prop_t trans_props[] = { - // FOUNDATION: Comment out the following line to have no transitions for button background color changes + // FOUNDATION CHANGE ============================================================== + // Comment out the following line to have no transitions for button background color changes // LV_STYLE_BG_OPA, LV_STYLE_BG_COLOR, LV_STYLE_TRANSFORM_WIDTH, LV_STYLE_TRANSFORM_HEIGHT, LV_STYLE_TRANSLATE_Y, LV_STYLE_TRANSLATE_X, diff --git a/lib/lv_bindings/lvgl/src/widgets/lv_table.c b/lib/lv_bindings/lvgl/src/widgets/lv_table.c index 3412cf152..9e2f46f34 100644 --- a/lib/lv_bindings/lvgl/src/widgets/lv_table.c +++ b/lib/lv_bindings/lvgl/src/widgets/lv_table.c @@ -498,7 +498,7 @@ static void lv_table_event(const lv_obj_class_t * class_p, lv_event_t * e) for(i = 0; i < table->row_cnt; i++) h += table->row_h[i]; p->x = w - 1; - p->y = h - 1; + p->y = h; // FOUNDATION CHANGE - last pixel was being cut off on last table item } else if(code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING) { uint16_t col; @@ -745,7 +745,8 @@ static void draw_main(lv_event_t * e) if(table->cell_data[cell]) { txt_area.x1 = cell_area.x1 + cell_left; - txt_area.x2 = cell_area.x2 - cell_right - 28; // FOUNDATION + txt_area.x2 = cell_area.x2 - cell_right - 28; // FOUNDATION CHANGE =================================== + txt_area.y1 = cell_area.y1 + cell_top; txt_area.y2 = cell_area.y2 - cell_bottom; diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index e1871d3b1..b37f0d71f 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1184,15 +1184,15 @@ def restore_sd_cb(): prev_sd_card_cb = CardSlot.get_sd_card_change_cb() CardSlot.set_sd_card_change_cb(sd_card_cb) - # try: - await page.display() - # except Exception as e: - # print(e) - # page.unmount() - # restore_sd_cb() - # await on_result(None) - # await ErrorPage(text='Unable to display page.').show() - # return + try: + await page.display() + except Exception as e: + print(e) + page.unmount() + restore_sd_cb() + await on_result(None) + await ErrorPage(text='Unable to display page.').show() + return g = page.poll_for_done() while True: From e5a7cebe7e038c5cea16aa19c4188f9394c0ff63 Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Tue, 20 Jun 2023 12:30:19 -0700 Subject: [PATCH 014/239] SFT-932: Fixes for mono/FE devices --- lib/lv_bindings/lvgl/src/widgets/lv_table.c | 5 +++- .../modules/pages/file_picker_page.py | 5 ++-- .../boards/Passport/modules/views/table.py | 24 +++++++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/lv_bindings/lvgl/src/widgets/lv_table.c b/lib/lv_bindings/lvgl/src/widgets/lv_table.c index 9e2f46f34..42215a7f7 100644 --- a/lib/lv_bindings/lvgl/src/widgets/lv_table.c +++ b/lib/lv_bindings/lvgl/src/widgets/lv_table.c @@ -745,8 +745,11 @@ static void draw_main(lv_event_t * e) if(table->cell_data[cell]) { txt_area.x1 = cell_area.x1 + cell_left; +#ifdef SCREEN_MODE_COLOR txt_area.x2 = cell_area.x2 - cell_right - 28; // FOUNDATION CHANGE =================================== - +#else + txt_area.x2 = cell_area.x2 - cell_right - 18; // FOUNDATION CHANGE =================================== +#endif txt_area.y1 = cell_area.y1 + cell_top; txt_area.y2 = cell_area.y2 - cell_bottom; diff --git a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py index d78a7bb2a..cc9afa465 100644 --- a/ports/stm32/boards/Passport/modules/pages/file_picker_page.py +++ b/ports/stm32/boards/Passport/modules/pages/file_picker_page.py @@ -6,7 +6,7 @@ import lvgl as lv import microns -from styles.colors import TEXT_GREY, WHITE, FD_BLUE +from styles.colors import TEXT_GREY, FOCUSED_LIST_ITEM_BG, FOCUSED_LIST_ITEM_TEXT, WHITE from styles import Stylize from pages import Page from views import Table @@ -70,7 +70,8 @@ def __init__(self, items.radius(MENU_ITEM_CORNER_RADIUS) with Stylize(self.table, lv.PART.ITEMS | lv.STATE.FOCUS_KEY) as focused: - focused.bg_color(FD_BLUE) + focused.bg_color(FOCUSED_LIST_ITEM_BG) + focused.text_color(FOCUSED_LIST_ITEM_TEXT) self.add_child(self.table) diff --git a/ports/stm32/boards/Passport/modules/views/table.py b/ports/stm32/boards/Passport/modules/views/table.py index 6399332e8..958db8300 100644 --- a/ports/stm32/boards/Passport/modules/views/table.py +++ b/ports/stm32/boards/Passport/modules/views/table.py @@ -5,20 +5,28 @@ import lvgl as lv from views import View -from styles.colors import WHITE, TEXT_GREY +from styles.colors import FOCUSED_LIST_ITEM_TEXT, TEXT_GREY from styles import Stylize +import passport class Table(View): def __init__(self, items=[], - col_width=212, + col_width=None, default_icon=lv.ICON_FILE, alt_icon=lv.ICON_FOLDER, get_cell_info=None): super().__init__() self.items = items + + if col_width is None: + if passport.IS_COLOR: + col_width = 212 + else: + col_width = 202 self.col_width = col_width + self.default_icon = default_icon self.alt_icon = alt_icon self.get_cell_info = get_cell_info @@ -36,7 +44,10 @@ def draw_part_begin_event_cb(self, e): # If the cells are drawn... if dsc.part == lv.PART.ITEMS: label_dsc = lv.draw_label_dsc_t.__cast__(dsc.label_dsc) - label_dsc.ofs_x += 28 + if passport.IS_COLOR: + label_dsc.ofs_x += 28 + else: + label_dsc.ofs_x += 18 def draw_part_end_event_cb(self, e): dsc = lv.obj_draw_part_dsc_t.__cast__(e.get_param()) @@ -61,7 +72,7 @@ def draw_part_end_event_cb(self, e): selected = draw_row == selected_row if selected: - icon_color = WHITE + icon_color = FOCUSED_LIST_ITEM_TEXT else: icon_color = TEXT_GREY @@ -71,7 +82,10 @@ def draw_part_end_event_cb(self, e): icon_img_dsc.recolor_opa = 255 # Setup the draw area - icon_area.x1 = 10 + dsc.draw_area.x1 + if passport.IS_COLOR: + icon_area.x1 = 10 + dsc.draw_area.x1 + else: + icon_area.x1 = 8 + dsc.draw_area.x1 icon_area.y1 = dsc.draw_area.y1 + ((dsc.draw_area.y2 - dsc.draw_area.y1) - icon.header.h) // 2 icon_area.x2 = icon_area.x1 + icon.header.w - 1 icon_area.y2 = icon_area.y1 + icon.header.h - 1 From d488e006d3f6633b9a91b16c357ab82d024d977e Mon Sep 17 00:00:00 2001 From: Jean-Pierre De Jesus DIAZ Date: Tue, 20 Jun 2023 14:05:43 +0200 Subject: [PATCH 015/239] SFT-2271: Use foundation-rs crates Signed-off-by: Jean-Pierre De Jesus DIAZ --- extmod/foundation-rust/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extmod/foundation-rust/Cargo.lock b/extmod/foundation-rust/Cargo.lock index 1098bfac2..0a0421699 100644 --- a/extmod/foundation-rust/Cargo.lock +++ b/extmod/foundation-rust/Cargo.lock @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "uuid" From d29dfefff51a6072732c000dd6aa6321b62272e6 Mon Sep 17 00:00:00 2001 From: Jean-Pierre De Jesus DIAZ Date: Fri, 14 Jul 2023 15:32:56 +0200 Subject: [PATCH 016/239] SFT-2449: Allow to build firmware with Podman. Signed-off-by: Jean-Pierre De Jesus DIAZ --- Justfile | 8 +++++--- REPRODUCIBILITY.md | 26 +++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Justfile b/Justfile index 791b57354..0cee790fc 100644 --- a/Justfile +++ b/Justfile @@ -4,10 +4,13 @@ # Justfile - Root-level Justfile for Passport export DOCKER_IMAGE := env_var_or_default('DOCKER_IMAGE', 'foundation-devices/passport2:latest') +export DOCKER_CMD := env_var_or_default('DOCKER_CMD', 'docker') + +DOCKER_RUN := if DOCKER_CMD == 'docker' { 'docker run -u $(id -u):$(id -g)' } else { 'podman run' } # Build the docker image build-docker: - docker build -t ${DOCKER_IMAGE} . + $DOCKER_CMD build -t ${DOCKER_IMAGE} . # Build the firmware inside docker. build-firmware screen="mono": mpy-cross (run-in-docker ("just ports/stm32/build " + screen)) @@ -76,8 +79,7 @@ mpy-cross: (run-in-docker "make -C mpy-cross PROG=mpy-cross-docker BUILD=build-d [private] run-in-docker command: - docker run --rm -v "$PWD":/workspace \ - -u $(id -u):$(id -g) \ + {{DOCKER_RUN}} --rm \ -v $(pwd):/workspace \ -w /workspace \ -e MPY_CROSS="/workspace/mpy-cross/mpy-cross-docker" \ diff --git a/REPRODUCIBILITY.md b/REPRODUCIBILITY.md index a8e7b9c41..70b97f834 100644 --- a/REPRODUCIBILITY.md +++ b/REPRODUCIBILITY.md @@ -26,7 +26,7 @@ In order to build and verify the reproducibility of Passport firmware, you will - Get the source code - Install the dependencies - - [Docker](https://docs.docker.com/desktop/) + - [Docker](https://docs.docker.com/desktop/) or [Podman](https://podman.io/). - [Just](https://github.com/casey/just#installation) - Build the reproducible binaries - Verify the binaries match the: @@ -54,6 +54,8 @@ Several tools are required for building and verifying Passport’s firmware. ### Install Docker +:warning: Docker requires to add your user to the `docker` group which is root-equivalent and may pose a security risk for you. Consider using Podman if you don't want to add your user to the `docker` group. Building with `sudo` and Docker is not supported. + The installation of Docker is most easily achieved by installing Docker Desktop on your given platform using the official docs linked below. Follow those directions, launch Docker Desktop, and accept the terms before proceeding: - [Windows](https://docs.docker.com/desktop/install/windows-install/) @@ -61,6 +63,20 @@ The installation of Docker is most easily achieved by installing Docker Desktop - [Linux](https://docs.docker.com/desktop/install/linux-install/) - If you don’t want to require using `sudo` when running the `just` commands below, follow the [post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user) to grant your user Docker permissions on Linux +### Install Podman (optional) + +This step is optional if you already have Docker installed and your user is on the `docker` group. + +Podman does not require root or adding your user to another group, so this option is recommended for non-developer users that want to verify the reproducibility of the firmware only. + +- [Windows](https://podman.io/docs/installation#windows) +- [MacOS](https://podman.io/docs/installation#macos) +- [Linux](https://podman.io/docs/installation#installing-on-linux) + +Also, the following configuration files might need to be created after installation: + +- [Configuration files](https://podman.io/docs/installation#policyjson) + ### Install Just Just is a powerful tool that allows us to provide scripts to perform all the necessary steps of building and verification. In order to use Just, you will need to install it using the following instructions for your given operating system: @@ -99,6 +115,14 @@ just build-docker This command will take some time to run as it creates the image, including downloading and installing every tool necessary for the build process. As we use a Docker image here, not only will this ensure the binaries are always the same for a given version, but it also allows you to easily clean up after verifying the firmware and leave your system uncluttered. +If you want to opt to use Docker instead of Podman, then you can prepend set the `DOCKER_CMD` environment variable to `podman`, for example: + +```bash +DOCKER_CMD=podman just build-docker +``` + +This applies to other commands shown here as well that would normally require Docker in order to run. + If you’d like to validate exactly how the `build-docker` Justfile command functions, you can find the relevant source code here: - [passport2/Justfile#L8-L10](https://github.com/Foundation-Devices/passport2/blob/6c6249e2c15f52c59db56b12b5f84213806a6533/Justfile#L8-L10) From ef950aa88c227a2da5ea540e73904721f1f4771e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 10 Aug 2023 17:00:43 -0500 Subject: [PATCH 017/239] SFT-906: only verify addresses of multisig that contain the current xfp --- .../modules/pages/singlesig_multisig_chooser_page.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py index 458d90d69..aa06d33fd 100644 --- a/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py @@ -10,12 +10,18 @@ class SinglesigMultisigChooserPage(ChooserPage): def __init__(self, card_header={'title': 'Single/Multisig'}, initial_value=None): from multisig_wallet import MultisigWallet + from common import settings options = [{'label': 'Single-sig', 'value': ('single-sig', None)}] + xfp = settings.get('xfp') + for ms in MultisigWallet.get_all(): - label = '%d/%d: %s' % (ms.M, ms.N, ms.name) - options.append({'label': label, 'value': ('multisig', ms)}) + for xpub in ms.xpubs: + if xfp == xpub[0]: # XFP entry in the multisig's xpub tuple + label = '%d/%d: %s' % (ms.M, ms.N, ms.name) + options.append({'label': label, 'value': ('multisig', ms)}) + continue super().__init__( card_header=card_header, From e8090abd3adb7082be0295bc839efbf911d1f7c4 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 9 Aug 2023 14:16:26 -0500 Subject: [PATCH 018/239] SFT-2253: removed select/delete option for the update firmware flow --- .../modules/flows/file_picker_flow.py | 19 +++++++++++++++---- .../modules/flows/update_firmware_flow.py | 5 ++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/file_picker_flow.py b/ports/stm32/boards/Passport/modules/flows/file_picker_flow.py index 6aa51830b..930ac06d9 100644 --- a/ports/stm32/boards/Passport/modules/flows/file_picker_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/file_picker_flow.py @@ -27,7 +27,8 @@ def __init__( enable_parent_nav=False, suffix=None, filter_fn=None, - select_text="Select"): + select_text="Select", + allow_delete=False): from files import CardSlot from utils import bind, show_card_missing @@ -47,6 +48,7 @@ def __init__( self.status_page = None self.empty_result = None self.finished = False + self.allow_delete = allow_delete bind(self, show_card_missing) @@ -73,6 +75,11 @@ def on_file_sd_card_change(self, sd_card_present): self.goto(self.show_card_missing) return True + def finalize(self, result): + common.page_transition_dir = TRANSITION_DIR_POP + self.set_result(result) + self.finished = True + async def on_file_result(self, res): # No file selected - go back to previous page if res is None: @@ -90,11 +97,15 @@ async def on_file_result(self, res): common.page_transition_dir = TRANSITION_DIR_PUSH self.paths.append(full_path) return True + + if not self.allow_delete: + self.finalize((_filename, full_path, is_folder)) + return True + result = await SelectedFileFlow(_filename, full_path, is_folder, self.select_text).run() + if result is not None: - common.page_transition_dir = TRANSITION_DIR_POP - self.set_result(result) - self.finished = True + self.finalize(result) return True diff --git a/ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py b/ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py index 3cafaf29d..5d6987f41 100644 --- a/ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py @@ -31,7 +31,10 @@ async def on_done(self, error=None): async def choose_file(self): root_path = CardSlot.get_sd_root() - result = await FilePickerFlow(initial_path=root_path, suffix='-passport.bin', show_folders=True).run() + result = await FilePickerFlow(initial_path=root_path, + suffix='-passport.bin', + show_folders=True, + allow_delete=False).run() if result is None: self.set_result(False) return From ef50392fe6e180d276eca57735d30640396441f9 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 9 Aug 2023 13:38:24 -0500 Subject: [PATCH 019/239] SFT-2458: first pass at new max sequence text --- ports/stm32/boards/Passport/modules/errors.py | 1 + .../boards/Passport/modules/flows/scan_qr_flow.py | 6 ++++++ .../boards/Passport/modules/flows/sign_psbt_qr_flow.py | 10 +++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/errors.py b/ports/stm32/boards/Passport/modules/errors.py index e99f12b93..7fd5c3213 100644 --- a/ports/stm32/boards/Passport/modules/errors.py +++ b/ports/stm32/boards/Passport/modules/errors.py @@ -25,4 +25,5 @@ 'USER_SETTINGS_FULL', 'PSBT_TOO_LARGE', 'PSBT_OVERSIZED', + 'QR_TOO_LARGE', ) diff --git a/ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py b/ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py index 9c1476dd6..b0a69afe2 100644 --- a/ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py @@ -18,6 +18,7 @@ def __init__(self, data_description=None, max_frames=None, failure_message=None): + pass_error=False): """ Initialize the scan QR flow. @@ -42,6 +43,7 @@ def __init__(self, self.data = None self.max_frames = max_frames self.failure_message = failure_message or 'Unable to scan QR code.\n\n{}' + self.pass_error = pass_error if len(self.qr_types) == 0: raise ValueError('At least one QR type must be provided') @@ -64,6 +66,10 @@ async def scan(self): return if result.is_failure(): + if self.pass_error: + self.set_result(Error.QR_TOO_LARGE) + return + await LongErrorPage(text=self.failure_message.format(result.error)).show() self.set_result(None) return diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py index e2cd50d14..7aeca5db5 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py @@ -26,7 +26,7 @@ async def scan_transaction(self): from data_codecs.qr_type import QRType from flows import ScanQRFlow, SignPsbtMicroSDFlow from errors import Error - from pages import YesNoChooserPage + from pages import YesNoChooserPage, ErrorPage import microns import passport @@ -36,6 +36,7 @@ async def scan_transaction(self): max_frames=self.max_frames, failure_message="Unable to Scan QR code, \ try signing using the microSD card.\n\n{}").run() + pass_error=True).run() if result is None: # User canceled the scan self.set_result(False) @@ -60,6 +61,13 @@ async def scan_transaction(self): self.set_result(result) return # Run it again with no max frames if the user wants + if result == Error.QR_TOO_LARGE: + await ErrorPage("This QR sequence is too large for Passport to handle. \ +\nSpend fewer coins in a single transaction or sign via microSD card.").show() + + self.set_result(False) + return + if isinstance(result, ur.Value): self.ur_type = result.ur_type() From d4bf7f3e1a45188a0363b2cd44e6e2c709f7fe80 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 11 Aug 2023 09:40:19 -0500 Subject: [PATCH 020/239] SFT-2458: shortened max sequence text --- .../stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py index 7aeca5db5..7e96bd742 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py @@ -62,8 +62,8 @@ async def scan_transaction(self): return # Run it again with no max frames if the user wants if result == Error.QR_TOO_LARGE: - await ErrorPage("This QR sequence is too large for Passport to handle. \ -\nSpend fewer coins in a single transaction or sign via microSD card.").show() + await ErrorPage("This transaction is too large for QR signing. \ +\nSpend fewer coins or sign via microSD card.").show() self.set_result(False) return From cd40fa97d72878230bf374ceeab0287de7d13bbf Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 8 Aug 2023 15:31:04 -0500 Subject: [PATCH 021/239] SFT-2392: found next address based on xfp and account number --- .../Passport/modules/flows/verify_address_flow.py | 14 ++++++++++++-- ports/stm32/boards/Passport/modules/utils.py | 12 ++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py index 0596fb079..7cc58d628 100644 --- a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py @@ -72,6 +72,7 @@ async def scan_address(self): from wallets.utils import get_addr_type_from_address, get_deriv_path_from_addr_type_and_acct from utils import is_valid_btc_address, get_next_addr from data_codecs.qr_type import QRType + from common import settings result = await ScanQRFlow(qr_types=[QRType.QR], data_description='a Bitcoin address').run() @@ -100,7 +101,15 @@ async def scan_address(self): self.deriv_path = get_deriv_path_from_addr_type_and_acct(self.addr_type, self.acct_num, self.is_multisig) # Setup initial ranges - a = [get_next_addr(self.acct_num, self.addr_type, False), get_next_addr(self.acct_num, self.addr_type, True)] + xfp = settings.get('xfp') + a = [get_next_addr(self.acct_num, + self.addr_type, + xfp, + False), + get_next_addr(self.acct_num, + self.addr_type, + xfp, + True)] self.low_range = [(a[_RECEIVE_ADDR], a[_RECEIVE_ADDR]), (a[_CHANGE_ADDR], a[_CHANGE_ADDR])] self.high_range = [(a[_RECEIVE_ADDR], a[_RECEIVE_ADDR]), (a[_CHANGE_ADDR], a[_CHANGE_ADDR])] @@ -214,9 +223,10 @@ async def found(self): from pages import SuccessPage, LongSuccessPage from utils import save_next_addr, format_btc_address import passport + from common import settings # Remember where to start from next time - save_next_addr(self.acct_num, self.addr_type, self.found_addr_idx, self.found_is_change) + save_next_addr(self.acct_num, self.addr_type, self.found_addr_idx, settings.get('xfp'), self.found_is_change) address = format_btc_address(self.address, self.addr_type) msg = '''{} diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index b37f0d71f..ea646231c 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -949,23 +949,23 @@ def get_width_from_num_words(num_words): # return False -def make_next_addr_key(acct_num, addr_type, is_change): - return '{}/{}{}'.format(acct_num, addr_type, '/1' if is_change else '') +def make_next_addr_key(acct_num, addr_type, xfp, is_change): + return '{}.{}/{}{}'.format(xfp, acct_num, addr_type, '/1' if is_change else '') -def get_next_addr(acct_num, addr_type, is_change): +def get_next_addr(acct_num, addr_type, xfp, is_change): from common import settings next_addrs = settings.get('next_addrs', {}) - key = make_next_addr_key(acct_num, addr_type, is_change) + key = make_next_addr_key(acct_num, addr_type, xfp, is_change) return next_addrs.get(key, 0) # Save the next address to use for the specific account and address type -def save_next_addr(acct_num, addr_type, addr_idx, is_change, force_update=False): +def save_next_addr(acct_num, addr_type, addr_idx, xfp, is_change, force_update=False): from common import settings next_addrs = settings.get('next_addrs', {}) - key = make_next_addr_key(acct_num, addr_type, is_change) + key = make_next_addr_key(acct_num, addr_type, xfp, is_change) # Only save the found index if it's newer if next_addrs.get(key, -1) < addr_idx or force_update: From 56c0703c02061f9d5444014fee5f41cc48909efd Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 9 Aug 2023 11:08:03 -0500 Subject: [PATCH 022/239] SFT-2392: differentiated between testnet and mainnet for address searching --- .../Passport/modules/flows/verify_address_flow.py | 14 +++++++++++--- ports/stm32/boards/Passport/modules/utils.py | 12 ++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py index 7cc58d628..bfacfd08b 100644 --- a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py @@ -87,10 +87,10 @@ async def scan_address(self): self.address = result # Simple check on the data type first - chain_name = chains.current_chain().name + chain = chains.current_chain() self.address, is_valid_btc = is_valid_btc_address(self.address) if not is_valid_btc: - await ErrorPage("Not a valid {} address.".format(chain_name)).show() + await ErrorPage("Not a valid {} address.".format(chain.name)).show() return # Get the address type from the address @@ -105,10 +105,12 @@ async def scan_address(self): a = [get_next_addr(self.acct_num, self.addr_type, xfp, + chain.b44_cointype, False), get_next_addr(self.acct_num, self.addr_type, xfp, + chain.b44_cointype, True)] self.low_range = [(a[_RECEIVE_ADDR], a[_RECEIVE_ADDR]), (a[_CHANGE_ADDR], a[_CHANGE_ADDR])] self.high_range = [(a[_RECEIVE_ADDR], a[_RECEIVE_ADDR]), (a[_CHANGE_ADDR], a[_CHANGE_ADDR])] @@ -224,9 +226,15 @@ async def found(self): from utils import save_next_addr, format_btc_address import passport from common import settings + import chains # Remember where to start from next time - save_next_addr(self.acct_num, self.addr_type, self.found_addr_idx, settings.get('xfp'), self.found_is_change) + save_next_addr(self.acct_num, + self.addr_type, + self.found_addr_idx, + settings.get('xfp'), + chains.current_chain().b44_cointype, + self.found_is_change) address = format_btc_address(self.address, self.addr_type) msg = '''{} diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index ea646231c..36ebd5a52 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -949,23 +949,23 @@ def get_width_from_num_words(num_words): # return False -def make_next_addr_key(acct_num, addr_type, xfp, is_change): - return '{}.{}/{}{}'.format(xfp, acct_num, addr_type, '/1' if is_change else '') +def make_next_addr_key(acct_num, addr_type, xfp, chain_type, is_change): + return '{}.{}.{}/{}{}'.format(chain_type, xfp, acct_num, addr_type, '/1' if is_change else '') -def get_next_addr(acct_num, addr_type, xfp, is_change): +def get_next_addr(acct_num, addr_type, xfp, chain_type, is_change): from common import settings next_addrs = settings.get('next_addrs', {}) - key = make_next_addr_key(acct_num, addr_type, xfp, is_change) + key = make_next_addr_key(acct_num, addr_type, xfp, chain_type, is_change) return next_addrs.get(key, 0) # Save the next address to use for the specific account and address type -def save_next_addr(acct_num, addr_type, addr_idx, xfp, is_change, force_update=False): +def save_next_addr(acct_num, addr_type, addr_idx, xfp, chain_type, is_change, force_update=False): from common import settings next_addrs = settings.get('next_addrs', {}) - key = make_next_addr_key(acct_num, addr_type, xfp, is_change) + key = make_next_addr_key(acct_num, addr_type, xfp, chain_type, is_change) # Only save the found index if it's newer if next_addrs.get(key, -1) < addr_idx or force_update: From 86bdbae32b7cab2d70c11b26967726d1a7c10e74 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 21 Aug 2023 12:02:03 -0400 Subject: [PATCH 023/239] SFT-2392: added migration for next_addrs defaulting to main xfp and mainnet --- ports/stm32/boards/Passport/modules/flows/main_flow.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/main_flow.py b/ports/stm32/boards/Passport/modules/flows/main_flow.py index 09742aae4..a9ede4782 100644 --- a/ports/stm32/boards/Passport/modules/flows/main_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/main_flow.py @@ -12,7 +12,7 @@ def __init__(self): async def start(self): import common - from utils import start_task, is_logged_in, has_seed + from utils import start_task, is_logged_in, has_seed, xfp2str from flows import SelectSetupModeFlow, LoginFlow, InitialSeedSetupFlow await SelectSetupModeFlow().run() @@ -23,6 +23,14 @@ async def start(self): if not has_seed(): await InitialSeedSetupFlow(allow_backtrack=False).run() + # Update old next_addrs keys to include the coin type and xfp + next_addrs = common.settings.get('next_addrs', {}) + xfp = xfp2str(common.settings.get('xfp')) + for key in next_addrs: + if "." not in key: # new key format uses periods to prepend coin type and xfp + next_addrs["0.{}.".format(xfp) + key] = next_addrs[key] + del next_addrs[key] + # Create initial cards by calling ui.update_cards() common.ui.update_cards(is_init=True) From cb90e0316a3341e77bf5f664db3e5d200555f071 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 22 Aug 2023 10:01:21 -0400 Subject: [PATCH 024/239] SFT-2605: reordered key manager new keys page --- ports/stm32/boards/Passport/modules/derived_key.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/derived_key.py b/ports/stm32/boards/Passport/modules/derived_key.py index 3bfc6eb02..7f6e3437d 100644 --- a/ports/stm32/boards/Passport/modules/derived_key.py +++ b/ports/stm32/boards/Passport/modules/derived_key.py @@ -11,18 +11,18 @@ # Each task must return ({'priv', , etc.}, None) # tn stands for Type Number key_types = [ - {'tn': 0, - 'title': '24 Word Seed', - 'icon': 'ICON_SEED', - 'indexed': True, - 'words': True, - 'task': bip85_24_word_seed_task}, {'tn': 1, 'title': '12 Word Seed', 'icon': 'ICON_SEED', 'indexed': True, 'words': True, 'task': bip85_12_word_seed_task}, + {'tn': 0, + 'title': '24 Word Seed', + 'icon': 'ICON_SEED', + 'indexed': True, + 'words': True, + 'task': bip85_24_word_seed_task}, {'tn': 2, 'title': 'Nostr Key', 'icon': 'ICON_NOSTR', From d74940aa287b5d7085fe30737823e72fa2b462e6 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 11 Aug 2023 09:56:11 -0500 Subject: [PATCH 025/239] SFT-2537: changed 'transaction details' to 'transaction info' in founders edition --- .../Passport/modules/flows/sign_psbt_common_flow.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py index 7f2900da7..1b55a2647 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py @@ -17,6 +17,7 @@ from tasks import sign_psbt_task, validate_psbt_task from utils import spinner_task, recolor import gc +import passport class SignPsbtCommonFlow(Flow): @@ -26,6 +27,7 @@ def __init__(self, psbt_len): self.psbt = None self.psbt_len = psbt_len self.chain = chains.current_chain() + self.header = 'Transaction Details' if passport.IS_COLOR else 'Transaction Info' async def validate_psbt(self): from pages import ErrorPage @@ -87,7 +89,7 @@ async def show_transaction_details(self): result = await LongTextPage( text=outputs.getvalue(), centered=True, - card_header={'title': 'Transaction Details'} + card_header={'title': self.header} ).show() if result: if self.psbt.self_send: @@ -113,7 +115,7 @@ async def show_change(self): result = await LongTextPage( text=msg, centered=True, - card_header={'title': 'Transaction Details'} + card_header={'title': self.header} ).show() gc.collect() if not result: @@ -135,7 +137,7 @@ async def show_warnings(self): result = await LongTextPage( text=warnings, centered=True, - card_header={'title': 'Transaction Details'} + card_header={'title': self.header} ).show() if not result: self.back() From 89de9cccacb3d4c5e990b0e3f7f03e8190ac000b Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 22 Aug 2023 09:53:46 -0400 Subject: [PATCH 026/239] SFT-2357: changed header to "Transaction Info" for both firmware versions --- .../boards/Passport/modules/flows/sign_psbt_common_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py index 1b55a2647..cf4086c54 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py @@ -27,7 +27,7 @@ def __init__(self, psbt_len): self.psbt = None self.psbt_len = psbt_len self.chain = chains.current_chain() - self.header = 'Transaction Details' if passport.IS_COLOR else 'Transaction Info' + self.header = 'Transaction Info' async def validate_psbt(self): from pages import ErrorPage From 0d44129833ea295b5faa6ac9d073d7180703d640 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 22 Aug 2023 15:15:30 -0400 Subject: [PATCH 027/239] SFT-2357: removed unused import --- .../stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py index cf4086c54..4e3f7385c 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py @@ -17,7 +17,6 @@ from tasks import sign_psbt_task, validate_psbt_task from utils import spinner_task, recolor import gc -import passport class SignPsbtCommonFlow(Flow): From 2a71bacaeadfb8f44e2686ae20b4b53a3a1efd1c Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 30 Aug 2023 15:53:14 -0400 Subject: [PATCH 028/239] SFT-2604: added 12 word option for passport seed creation --- .../Passport/modules/flows/new_seed_flow.py | 21 +++++++++++++++++-- ports/stm32/boards/Passport/modules/stash.py | 2 +- .../Passport/modules/tasks/new_seed_task.py | 6 +++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index 1ddb78fc4..5c06f2267 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -17,6 +17,7 @@ def __init__(self, refresh_cards_when_done=False, autobackup=True, show_words=Fa self.autobackup = autobackup self.show_words = show_words self.full_backup = full_backup + self.seed_length = None async def confirm_generate(self): # Ensure we don't overwrite an existing seed @@ -27,12 +28,28 @@ async def confirm_generate(self): result = await QuestionPage(text='Generate a new seed phrase now?').show() if result: - self.goto(self.generate_seed) + self.goto(self.pick_length) else: self.set_result(False) + async def pick_length(self): + from public_constants import SEED_LENGTHS + from pages import ChooserPage + + options = [] + for length in SEED_LENGTHS: + options.append({'label': '{} Word Seed'.format(length), 'value': length}) + + self.seed_length = await ChooserPage(card_header={'title': 'Seed Length'}, options=options).show() + if not self.seed_length: + self.back() + else: + self.goto(self.generate_seed) + async def generate_seed(self): - (seed, error) = await spinner_task('Generating Seed', new_seed_task) + (seed, error) = await spinner_task('Generating Seed', + new_seed_task, + args=[self.seed_length]) if error is None: self.seed = seed self.goto(self.save_seed) diff --git a/ports/stm32/boards/Passport/modules/stash.py b/ports/stm32/boards/Passport/modules/stash.py index c52a3c0a0..04a58feaa 100644 --- a/ports/stm32/boards/Passport/modules/stash.py +++ b/ports/stm32/boards/Passport/modules/stash.py @@ -54,7 +54,7 @@ def encode(seed_bits=None, master_secret=None, xprv=None): # typical: seed bits without checksum bits vlen = len(seed_bits) - # TODO: Do we support all of these?s + # TODO: Do we support all of these? assert vlen in [16, 24, 32] nv[0] = 0x80 | ((vlen // 8) - 2) nv[1:1 + vlen] = seed_bits diff --git a/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py b/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py index 9effd5e1e..07a7a6cd3 100644 --- a/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py @@ -8,12 +8,16 @@ from utils import b2a_hex -async def new_seed_task(on_done): +async def new_seed_task(on_done, words): seed = bytearray(32) common.noise.random_bytes(seed, common.noise.ALL) # Hash to mitigate any potential bias in RNG sources seed = trezorcrypto.sha256(seed).digest() + + # Math to convert number of words to number of bytes + seed = seed[0:((words // 3) * 4)] + # print('create_new_wallet_seed(): New seed = {}'.format(b2a_hex(seed))) await on_done(seed, None) From 8f012d3211712e3f93313fbe364ea17fd56314fc Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 31 Aug 2023 12:29:51 -0400 Subject: [PATCH 029/239] SFT-2604: rearranged new seed flow, fixed copy text in seed length menu --- .../Passport/modules/flows/new_seed_flow.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index 5c06f2267..6f6a128aa 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -12,25 +12,21 @@ class NewSeedFlow(Flow): def __init__(self, refresh_cards_when_done=False, autobackup=True, show_words=False, full_backup=False): - super().__init__(initial_state=self.confirm_generate, name='NewSeedFlow') + super().__init__(initial_state=self.check_for_seed, name='NewSeedFlow') self.refresh_cards_when_done = refresh_cards_when_done self.autobackup = autobackup self.show_words = show_words self.full_backup = full_backup self.seed_length = None - async def confirm_generate(self): + async def check_for_seed(self): # Ensure we don't overwrite an existing seed if has_secrets(): await ErrorPage(text='Passport already has a seed!').show() self.set_result(False) return - result = await QuestionPage(text='Generate a new seed phrase now?').show() - if result: - self.goto(self.pick_length) - else: - self.set_result(False) + self.goto(self.pick_length) async def pick_length(self): from public_constants import SEED_LENGTHS @@ -38,13 +34,20 @@ async def pick_length(self): options = [] for length in SEED_LENGTHS: - options.append({'label': '{} Word Seed'.format(length), 'value': length}) + options.append({'label': '{} Words'.format(length), 'value': length}) self.seed_length = await ChooserPage(card_header={'title': 'Seed Length'}, options=options).show() if not self.seed_length: - self.back() + self.set_result(False) else: + self.goto(self.confirm_generate) + + async def confirm_generate(self): + result = await QuestionPage(text='Generate a new seed phrase now?').show() + if result: self.goto(self.generate_seed) + else: + self.back() async def generate_seed(self): (seed, error) = await spinner_task('Generating Seed', From 53ce0c5c1cc2174ca25fbbcd641fc564726d51f1 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 31 Aug 2023 12:35:31 -0400 Subject: [PATCH 030/239] SFT-2604: changed variable name from "words" to "seed_length" --- ports/stm32/boards/Passport/modules/tasks/new_seed_task.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py b/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py index 07a7a6cd3..b7531246b 100644 --- a/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/new_seed_task.py @@ -8,7 +8,7 @@ from utils import b2a_hex -async def new_seed_task(on_done, words): +async def new_seed_task(on_done, seed_length): seed = bytearray(32) common.noise.random_bytes(seed, common.noise.ALL) @@ -16,7 +16,7 @@ async def new_seed_task(on_done, words): seed = trezorcrypto.sha256(seed).digest() # Math to convert number of words to number of bytes - seed = seed[0:((words // 3) * 4)] + seed = seed[0:((seed_length // 3) * 4)] # print('create_new_wallet_seed(): New seed = {}'.format(b2a_hex(seed))) From e2ce9c770bb6f4dfd3fcc02675401ae83076acdf Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 31 Aug 2023 13:00:08 -0400 Subject: [PATCH 031/239] SFT-2273: first pass added a warning to do setup in a secure place --- .../modules/flows/select_setup_mode_flow.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py b/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py index 79d1f00a5..319a11bac 100644 --- a/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py @@ -30,6 +30,19 @@ async def show_welcome(self): statusbar={'title': 'WELCOME', 'icon': 'ICON_HOME'}, left_micron=microns.Shutdown, right_micron=microns.Forward).show() + if result: + self.goto(self.show_warning) + else: + await ShutdownPage().show() + + async def show_warning(self): + from pages import InfoPage, ShutdownPage + + result = await InfoPage( + text='Make sure you are in a secure place, with resources like a pen, ' + + 'paper, and internet, for the best experience.', + left_micron=microns.Shutdown, + right_micron=microns.Forward).show() if result: self.goto(self.select_mode) else: From 1ad27260afbcb8ff5d5163ef1b8052ea28af7d3a Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 7 Sep 2023 11:50:57 -0400 Subject: [PATCH 032/239] SFT-2273: updated copy text --- .../boards/Passport/modules/flows/select_setup_mode_flow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py b/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py index 319a11bac..168496846 100644 --- a/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py @@ -39,8 +39,7 @@ async def show_warning(self): from pages import InfoPage, ShutdownPage result = await InfoPage( - text='Make sure you are in a secure place, with resources like a pen, ' + - 'paper, and internet, for the best experience.', + text='Make sure you are in a quiet and secure place with access to a pen, paper and the internet.', left_micron=microns.Shutdown, right_micron=microns.Forward).show() if result: From f1ede4bddd71a636812b0819ecb080ace9725b90 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 8 Sep 2023 12:19:08 -0400 Subject: [PATCH 033/239] SFT-2273: added commas --- .../boards/Passport/modules/flows/select_setup_mode_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py b/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py index 168496846..ab3e05e1a 100644 --- a/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/select_setup_mode_flow.py @@ -39,7 +39,7 @@ async def show_warning(self): from pages import InfoPage, ShutdownPage result = await InfoPage( - text='Make sure you are in a quiet and secure place with access to a pen, paper and the internet.', + text='Make sure you are in a quiet and secure place, with access to a pen, paper, and the internet.', left_micron=microns.Shutdown, right_micron=microns.Forward).show() if result: From f2aa80cb430ab06f362716d1e8246f4f8c43702a Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 28 Aug 2023 15:38:11 -0500 Subject: [PATCH 034/239] SFT-2237: first pass per-xfp accounts and migration --- .../Passport/modules/flows/main_flow.py | 19 ++++++++++++++++--- .../modules/tasks/save_new_account_task.py | 3 ++- ports/stm32/boards/Passport/modules/ui/ui.py | 5 +++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/main_flow.py b/ports/stm32/boards/Passport/modules/flows/main_flow.py index a9ede4782..9c561557a 100644 --- a/ports/stm32/boards/Passport/modules/flows/main_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/main_flow.py @@ -12,7 +12,7 @@ def __init__(self): async def start(self): import common - from utils import start_task, is_logged_in, has_seed, xfp2str + from utils import start_task, is_logged_in, has_seed, xfp2str, get_accounts from flows import SelectSetupModeFlow, LoginFlow, InitialSeedSetupFlow await SelectSetupModeFlow().run() @@ -25,12 +25,25 @@ async def start(self): # Update old next_addrs keys to include the coin type and xfp next_addrs = common.settings.get('next_addrs', {}) - xfp = xfp2str(common.settings.get('xfp')) + xfp = common.settings.get('xfp') + string_xfp = xfp2str(xfp) for key in next_addrs: if "." not in key: # new key format uses periods to prepend coin type and xfp - next_addrs["0.{}.".format(xfp) + key] = next_addrs[key] + next_addrs["0.{}.".format(string_xfp) + key] = next_addrs[key] del next_addrs[key] + # Update account settings to include a show_all xfp + accounts = get_accounts() + print(accounts) + for i in range(len(accounts)): + account = accounts[i] + if account.get('xfp', None) is None: + account['xfp'] = xfp + del accounts[i] + accounts.append(account) + print(accounts) + # settings.set('accounts', accounts) + # Create initial cards by calling ui.update_cards() common.ui.update_cards(is_init=True) diff --git a/ports/stm32/boards/Passport/modules/tasks/save_new_account_task.py b/ports/stm32/boards/Passport/modules/tasks/save_new_account_task.py index 2c21a45cd..c8b944127 100644 --- a/ports/stm32/boards/Passport/modules/tasks/save_new_account_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/save_new_account_task.py @@ -10,7 +10,8 @@ async def save_new_account_task(on_done, account_num, account_name): from common import settings accounts = get_accounts() - accounts.append({'name': account_name, 'acct_num': account_num}) + xfp = settings.get('xfp') + accounts.append({'name': account_name, 'acct_num': account_num, 'xfp': xfp}) settings.set('accounts', accounts) settings.save() # TODO: This can fail, so may need a try/except handler here diff --git a/ports/stm32/boards/Passport/modules/ui/ui.py b/ports/stm32/boards/Passport/modules/ui/ui.py index b5bbc9008..b30b44c7f 100644 --- a/ports/stm32/boards/Passport/modules/ui/ui.py +++ b/ports/stm32/boards/Passport/modules/ui/ui.py @@ -236,6 +236,8 @@ def update_cards( elif stay_on_same_card: new_card_idx = self.active_card_idx + xfp = common.settings.get('xfp') + # Only add these once there is a seed if has_seed(): import stash @@ -243,6 +245,9 @@ def update_cards( account = accounts[i] # print('account[{}]={}'.format(account, i)) + if account['xfp'] != xfp: + continue + account_card = { 'right_icon': 'ICON_BITCOIN', 'header_color': LIGHT_GREY, From 5be62a492cc8c2b720defa789ea863b44a964749 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 29 Aug 2023 10:06:40 -0500 Subject: [PATCH 035/239] SFT-2237: refined accounts with passphrases --- .../Passport/modules/flows/apply_passphrase_flow.py | 2 +- .../Passport/modules/flows/new_account_flow.py | 10 +++++++--- ports/stm32/boards/Passport/modules/ui/ui.py | 13 ++++++++++--- ports/stm32/boards/Passport/modules/utils.py | 8 ++++---- .../stm32/boards/Passport/modules/wallets/utils.py | 4 ++-- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/apply_passphrase_flow.py b/ports/stm32/boards/Passport/modules/flows/apply_passphrase_flow.py index 32771d4a8..56b0a32b0 100644 --- a/ports/stm32/boards/Passport/modules/flows/apply_passphrase_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/apply_passphrase_flow.py @@ -129,7 +129,7 @@ async def confirm_xfp(self): self.goto(self.enter_passphrase) return - common.ui.update_cards(stay_on_same_card=True) + common.ui.update_cards(stay_on_last_card=True) async def start_main_task(): common.ui.start_card_task(card_idx=common.ui.active_card_idx) diff --git a/ports/stm32/boards/Passport/modules/flows/new_account_flow.py b/ports/stm32/boards/Passport/modules/flows/new_account_flow.py index 4f15a5dd1..8dfab0e48 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_account_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_account_flow.py @@ -11,6 +11,7 @@ from utils import get_account_by_name, get_account_by_number, get_accounts, spinner_task from translations import t, T from wallets.utils import get_next_account_num +from common import settings class NewAccountFlow(Flow): @@ -24,7 +25,8 @@ def __init__(self): self.error = None # Suggest the next available account number to the user - self.account_num = get_next_account_num() + xfp = settings.get('xfp') + self.account_num = get_next_account_num(xfp) self.account_name = None def on_save_account_done(self, error=None): @@ -49,7 +51,8 @@ async def enter_account_num(self): self.account_num = int(result) # Check for existing account with this number - existing_account = get_account_by_number(self.account_num) + xfp = settings.get('xfp') + existing_account = get_account_by_number(self.account_num, xfp) if existing_account is not None: await ErrorPage(text='An account named "{}" already exists with account number {}.'.format( existing_account.get('name'), self.account_num)).show() @@ -72,7 +75,8 @@ async def enter_account_name(self): self.account_name = result # Check for existing account with this name - existing_account = get_account_by_name(self.account_name) + xfp = settings.get('xfp') + existing_account = get_account_by_name(self.account_name, xfp) if existing_account is not None: await ErrorPage(text='Account ##{} already exists with the name "{}".'.format( existing_account.get('acct_num'), self.account_name)).show() diff --git a/ports/stm32/boards/Passport/modules/ui/ui.py b/ports/stm32/boards/Passport/modules/ui/ui.py index b30b44c7f..d39f5775e 100644 --- a/ports/stm32/boards/Passport/modules/ui/ui.py +++ b/ports/stm32/boards/Passport/modules/ui/ui.py @@ -193,8 +193,12 @@ def update_cards_on_top_level(self): # print('Setting update_cards_pending to True') self.update_cards_pending = True - def update_cards( - self, is_delete_account=False, stay_on_same_card=False, is_new_account=False, is_init=False): + def update_cards(self, + is_delete_account=False, + stay_on_same_card=False, + is_new_account=False, + is_init=False, + stay_on_last_card=False): from flows import MenuFlow from utils import get_accounts, has_seed from menus import account_menu, plus_menu @@ -245,7 +249,7 @@ def update_cards( account = accounts[i] # print('account[{}]={}'.format(account, i)) - if account['xfp'] != xfp: + if account['xfp'] != xfp and account['acct_num'] != 0: continue account_card = { @@ -284,6 +288,9 @@ def update_cards( } card_descs.append(more_card) + if stay_on_last_card: + new_card_idx = len(card_descs) - 1 + if new_card_idx is None: new_card_idx = 1 if len(card_descs) > 1 else 0 diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 36ebd5a52..b1e73af17 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -875,19 +875,19 @@ def get_accounts(): return accounts -def get_account_by_name(name): +def get_account_by_name(name, xfp): accounts = get_accounts() for account in accounts: - if account.get('name') == name: + if account.get('name') == name and account.get('xfp') == xfp: return account return None -def get_account_by_number(acct_num): +def get_account_by_number(acct_num, xfp): accounts = get_accounts() for account in accounts: - if account.get('acct_num') == acct_num: + if account.get('acct_num') == acct_num and account.get('xfp') == xfp: return account return None diff --git a/ports/stm32/boards/Passport/modules/wallets/utils.py b/ports/stm32/boards/Passport/modules/wallets/utils.py index 63a3283b3..9c96ab288 100644 --- a/ports/stm32/boards/Passport/modules/wallets/utils.py +++ b/ports/stm32/boards/Passport/modules/wallets/utils.py @@ -12,11 +12,11 @@ # Dynamically find the next account number rather than storing it - we never want to skip an account number # since that would create gaps and potentially make recovering funds harder if we exceeded the gap limit. -def get_next_account_num(): +def get_next_account_num(xfp): accts = get_accounts() acct_nums = [] - for acct in accts: + for acct in (acct for acct in accts if acct['xfp'] == xfp or acct['acct_num'] == 0): acct_nums.append(acct['acct_num']) acct_nums.sort() From 2666d3a05abdea5f13194cb4153f586e5fd35814 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 29 Aug 2023 10:37:09 -0500 Subject: [PATCH 036/239] SFT-2237: first pass at per-xfp extensions, made next_addrs and account migrations persist --- .../boards/Passport/modules/flows/main_flow.py | 16 +++++++++++++--- ports/stm32/boards/Passport/modules/ui/ui.py | 4 ++-- ports/stm32/boards/Passport/modules/utils.py | 6 ++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/main_flow.py b/ports/stm32/boards/Passport/modules/flows/main_flow.py index 9c561557a..a87cbd42a 100644 --- a/ports/stm32/boards/Passport/modules/flows/main_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/main_flow.py @@ -14,6 +14,7 @@ async def start(self): import common from utils import start_task, is_logged_in, has_seed, xfp2str, get_accounts from flows import SelectSetupModeFlow, LoginFlow, InitialSeedSetupFlow + from extensions.extensions import supported_extensions await SelectSetupModeFlow().run() @@ -23,6 +24,8 @@ async def start(self): if not has_seed(): await InitialSeedSetupFlow(allow_backtrack=False).run() + # TODO: consider using a "2.3.0 migrations" flag to skip all this after first run + # Update old next_addrs keys to include the coin type and xfp next_addrs = common.settings.get('next_addrs', {}) xfp = common.settings.get('xfp') @@ -31,18 +34,25 @@ async def start(self): if "." not in key: # new key format uses periods to prepend coin type and xfp next_addrs["0.{}.".format(string_xfp) + key] = next_addrs[key] del next_addrs[key] + common.settings.set('next_addrs', next_addrs) # Update account settings to include a show_all xfp accounts = get_accounts() - print(accounts) for i in range(len(accounts)): account = accounts[i] if account.get('xfp', None) is None: account['xfp'] = xfp del accounts[i] accounts.append(account) - print(accounts) - # settings.set('accounts', accounts) + common.settings.set('accounts', accounts) + + # Update old extensions keys to include the xfp + for extension in supported_extensions: + old_key = 'ext.{}.{}'.format(extension['name'], 'enabled') + if common.settings.get(old_key): + print("converting {} to {}".format(old_key, old_key + ".{}".format(string_xfp))) + common.settings.remove(old_key) + common.settings.set(old_key + ".{}".format(string_xfp), True) # Create initial cards by calling ui.update_cards() common.ui.update_cards(is_init=True) diff --git a/ports/stm32/boards/Passport/modules/ui/ui.py b/ports/stm32/boards/Passport/modules/ui/ui.py index d39f5775e..91a1f2bd8 100644 --- a/ports/stm32/boards/Passport/modules/ui/ui.py +++ b/ports/stm32/boards/Passport/modules/ui/ui.py @@ -200,7 +200,7 @@ def update_cards(self, is_init=False, stay_on_last_card=False): from flows import MenuFlow - from utils import get_accounts, has_seed + from utils import get_accounts, has_seed, is_extension_enabled from menus import account_menu, plus_menu from extensions.extensions import supported_extensions from constants import MAX_ACCOUNTS @@ -272,7 +272,7 @@ def update_cards(self, # Add special accounts for extension in supported_extensions: - if common.settings.get('ext.{}.enabled'.format(extension['name']), False): + if is_extension_enabled(extension['name']): if len(stash.bip39_passphrase) > 0: extension['card']['icon'] = 'ICON_PASSPHRASE' else: diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index b1e73af17..73f28777b 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1232,11 +1232,13 @@ def restore_sd_cb(): def make_extension_path(ext_name, ext_prop): - return 'ext.{}.{}'.format(ext_name, ext_prop) + from common import settings + xfp = xfp2str(settings.get('xfp')) + return 'ext.{}.{}.{}'.format(ext_name, ext_prop, xfp) def toggle_extension_enabled(ext_name): - from common import ui + from common import ui, settings ext_path = make_extension_path(ext_name, 'enabled') is_enabled = common.settings.get(ext_path, False) common.settings.set(ext_path, not is_enabled) From 028cee1a9a8372903d4f84dc5230916951e6f4b5 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 29 Aug 2023 16:32:07 -0400 Subject: [PATCH 037/239] SFT-2237: removed debug print --- ports/stm32/boards/Passport/modules/flows/main_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/main_flow.py b/ports/stm32/boards/Passport/modules/flows/main_flow.py index a87cbd42a..3f3b15745 100644 --- a/ports/stm32/boards/Passport/modules/flows/main_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/main_flow.py @@ -50,7 +50,6 @@ async def start(self): for extension in supported_extensions: old_key = 'ext.{}.{}'.format(extension['name'], 'enabled') if common.settings.get(old_key): - print("converting {} to {}".format(old_key, old_key + ".{}".format(string_xfp))) common.settings.remove(old_key) common.settings.set(old_key + ".{}".format(string_xfp), True) From 60210ccc76a56d0c5bbd0a073bca5151c0d3ca56 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 7 Sep 2023 11:19:43 -0400 Subject: [PATCH 038/239] SFT-2237: split list comprehension into 2 lines --- ports/stm32/boards/Passport/modules/wallets/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/utils.py b/ports/stm32/boards/Passport/modules/wallets/utils.py index 9c96ab288..d5896b127 100644 --- a/ports/stm32/boards/Passport/modules/wallets/utils.py +++ b/ports/stm32/boards/Passport/modules/wallets/utils.py @@ -16,7 +16,8 @@ def get_next_account_num(xfp): accts = get_accounts() acct_nums = [] - for acct in (acct for acct in accts if acct['xfp'] == xfp or acct['acct_num'] == 0): + current_accounts = [acct for acct in accts if acct['xfp'] == xfp or acct['acct_num'] == 0] + for acct in current_accounts: acct_nums.append(acct['acct_num']) acct_nums.sort() From 91b89b7a1d3f0a55887dc84128b2d0eccbbbe55e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 12 Sep 2023 14:35:14 -0400 Subject: [PATCH 039/239] SFT-2751: fixed linting and compiling --- ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py | 2 +- ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py b/ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py index b0a69afe2..4785b0b5b 100644 --- a/ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/scan_qr_flow.py @@ -17,7 +17,7 @@ def __init__(self, explicit_type=None, data_description=None, max_frames=None, - failure_message=None): + failure_message=None, pass_error=False): """ Initialize the scan QR flow. diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py index 7e96bd742..f3774ab70 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_qr_flow.py @@ -35,7 +35,7 @@ async def scan_transaction(self): data_description='a PSBT file', max_frames=self.max_frames, failure_message="Unable to Scan QR code, \ -try signing using the microSD card.\n\n{}").run() +try signing using the microSD card.\n\n{}", pass_error=True).run() if result is None: # User canceled the scan From b3c59a1d984da49898dd24e4f12122f5d204c954 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 13 Sep 2023 11:59:28 -0400 Subject: [PATCH 040/239] SFT-2757: fixed account renaming --- .../Passport/modules/flows/rename_account_flow.py | 11 ++++++++--- .../Passport/modules/tasks/rename_account_task.py | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/rename_account_flow.py b/ports/stm32/boards/Passport/modules/flows/rename_account_flow.py index a3710ab52..80f139792 100644 --- a/ports/stm32/boards/Passport/modules/flows/rename_account_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/rename_account_flow.py @@ -29,6 +29,8 @@ def __init__(self): super().__init__(initial_state=initial_state, name='RenameAccountFLow') async def ask_for_name(self): + from common import settings + name = self.account.get('name') if self.new_account_name is None else self.new_account_name result = await TextInputPage(initial_text=name, max_length=MAX_ACCOUNT_NAME_LEN, @@ -38,7 +40,7 @@ async def ask_for_name(self): self.new_account_name = result # Check for existing account with this name - existing_account = get_account_by_name(self.new_account_name) + existing_account = get_account_by_name(self.new_account_name, settings.get('xfp')) if existing_account is not None: await ErrorPage(text='Account #{} already exists with the name "{}".'.format( existing_account.get('acct_num'), self.new_account_name)).show() @@ -49,9 +51,12 @@ async def ask_for_name(self): self.set_result(False) async def do_rename(self): - from common import ui + from common import ui, settings + (error,) = await spinner_task('Renaming account', rename_account_task, - args=[self.account.get('acct_num'), self.new_account_name]) + args=[self.account.get('acct_num'), + self.new_account_name, + settings.get('xfp')]) if error is None: import common from utils import start_task diff --git a/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py b/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py index f84d43181..a38233bf6 100644 --- a/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py @@ -6,12 +6,12 @@ from utils import get_accounts -async def rename_account_task(on_done, account_num, account_name): +async def rename_account_task(on_done, account_num, account_name, xfp): from common import settings accounts = get_accounts() for account in accounts: - if account.get('acct_num') == account_num: + if account.get('acct_num') == account_num and account.get('xfp') == xfp: account['name'] = account_name break From 3e1ef62d1b4414722fbee54d181ac18981b4b2a4 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 13 Sep 2023 13:10:37 -0400 Subject: [PATCH 041/239] SFT-2757: fixed account deletion with passphrases --- .../boards/Passport/modules/flows/delete_account_flow.py | 5 ++++- .../boards/Passport/modules/tasks/delete_account_task.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/delete_account_flow.py b/ports/stm32/boards/Passport/modules/flows/delete_account_flow.py index cb20d0c1c..1e8f93c57 100644 --- a/ports/stm32/boards/Passport/modules/flows/delete_account_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/delete_account_flow.py @@ -42,8 +42,11 @@ async def confirm_delete(self): self.set_result(False) async def do_delete(self): + from common import settings + (error,) = await spinner_task('Deleting Account', delete_account_task, - args=[self.account.get('acct_num')]) + args=[self.account.get('acct_num'), + settings.get('xfp')]) if error is None: import common from utils import start_task diff --git a/ports/stm32/boards/Passport/modules/tasks/delete_account_task.py b/ports/stm32/boards/Passport/modules/tasks/delete_account_task.py index 8e7869598..a8d4c5a6a 100644 --- a/ports/stm32/boards/Passport/modules/tasks/delete_account_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/delete_account_task.py @@ -6,11 +6,11 @@ from utils import get_accounts -async def delete_account_task(on_done, account_num): +async def delete_account_task(on_done, account_num, xfp): from common import settings accounts = get_accounts() - accounts = list(filter(lambda acct: acct.get('acct_num') != account_num, accounts)) + accounts = list(filter(lambda acct: (acct.get('acct_num') != account_num or acct.get('xfp') != xfp), accounts)) settings.set('accounts', accounts) settings.save() From 1454a79343973a70827bb2495fac9ba728a7ebf7 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 13 Sep 2023 11:48:03 -0400 Subject: [PATCH 042/239] SFT-2755: switched order of seed import sizes --- .../stm32/boards/Passport/modules/flows/restore_seed_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index 538d06319..0094d1572 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -27,8 +27,8 @@ async def choose_restore_method(self): from pages import ChooserPage from data_codecs.qr_type import QRType - options = [{'label': '24 words', 'value': 24}, - {'label': '12 words', 'value': 12}, + options = [{'label': '12 words', 'value': 12}, + {'label': '24 words', 'value': 24}, {'label': 'Compact SeedQR', 'value': QRType.COMPACT_SEED_QR}, {'label': 'SeedQR', 'value': QRType.SEED_QR}] From 1aa6384a58cd83a99c9be510c212974eae129520 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 13 Sep 2023 11:45:06 -0400 Subject: [PATCH 043/239] SFT-2754: explicitly saved migrated settings, added default value to xfp fetch --- ports/stm32/boards/Passport/modules/flows/main_flow.py | 2 ++ ports/stm32/boards/Passport/modules/ui/ui.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/main_flow.py b/ports/stm32/boards/Passport/modules/flows/main_flow.py index 3f3b15745..b00bb5f82 100644 --- a/ports/stm32/boards/Passport/modules/flows/main_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/main_flow.py @@ -53,6 +53,8 @@ async def start(self): common.settings.remove(old_key) common.settings.set(old_key + ".{}".format(string_xfp), True) + common.settings.save() + # Create initial cards by calling ui.update_cards() common.ui.update_cards(is_init=True) diff --git a/ports/stm32/boards/Passport/modules/ui/ui.py b/ports/stm32/boards/Passport/modules/ui/ui.py index 91a1f2bd8..86d8fe39e 100644 --- a/ports/stm32/boards/Passport/modules/ui/ui.py +++ b/ports/stm32/boards/Passport/modules/ui/ui.py @@ -249,7 +249,7 @@ def update_cards(self, account = accounts[i] # print('account[{}]={}'.format(account, i)) - if account['xfp'] != xfp and account['acct_num'] != 0: + if account.get('xfp', xfp) != xfp and account['acct_num'] != 0: continue account_card = { From e40dac4eaefe001ea0b2fb00e7d03a57755b5a20 Mon Sep 17 00:00:00 2001 From: Ken Carpenter Date: Thu, 14 Sep 2023 16:31:32 -0700 Subject: [PATCH 044/239] SFT-2074: Optimize build size --- lib/lv_bindings/lv_conf.h | 6 +++- lib/lv_bindings/lvgl/src/core/lv_core.mk | 2 ++ lib/lv_bindings/lvgl/src/core/lv_indev.c | 10 +++++- lib/lv_bindings/lvgl/src/core/lv_obj_scroll.c | 4 +++ lib/lv_bindings/lvgl/src/core/lv_refr.c | 11 +++--- lib/lv_bindings/lvgl/src/widgets/lv_roller.c | 2 ++ ports/stm32/boards/Passport/framebuffer.h | 2 ++ ports/stm32/mpconfigport.h | 34 +++++++++---------- ports/unix/mpconfigport.h | 6 ++-- 9 files changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/lv_bindings/lv_conf.h b/lib/lv_bindings/lv_conf.h index 7804156fb..05489315f 100644 --- a/lib/lv_bindings/lv_conf.h +++ b/lib/lv_bindings/lv_conf.h @@ -119,7 +119,7 @@ *With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images. *However the opened images might consume additional RAM. *0: to disable caching*/ -#define LV_IMG_CACHE_DEF_SIZE 1 +#define LV_IMG_CACHE_DEF_SIZE 0 /*Maximum buffer size to allocate for rotation. Only used if software rotation is enabled in the display driver.*/ #define LV_DISP_ROT_MAX_BUF (10*1024) @@ -546,6 +546,10 @@ e.g. "stm32f769xx.h" or "stm32f429xx.h"*/ /*Enable the examples to be built with the library*/ #define LV_BUILD_EXAMPLES 0 +/* Custom flags defined */ +#define LV_TOUCHSCREEN 0 + + /*--END OF LV_CONF_H--*/ #endif /*LV_CONF_H*/ diff --git a/lib/lv_bindings/lvgl/src/core/lv_core.mk b/lib/lv_bindings/lvgl/src/core/lv_core.mk index 677a9f6b4..4d41264eb 100644 --- a/lib/lv_bindings/lvgl/src/core/lv_core.mk +++ b/lib/lv_bindings/lvgl/src/core/lv_core.mk @@ -1,7 +1,9 @@ CSRCS += lv_disp.c CSRCS += lv_group.c CSRCS += lv_indev.c +ifeq ($(LV_TOUCHSCREEN),1) CSRCS += lv_indev_scroll.c +endif CSRCS += lv_obj.c CSRCS += lv_obj_class.c CSRCS += lv_obj_draw.c diff --git a/lib/lv_bindings/lvgl/src/core/lv_indev.c b/lib/lv_bindings/lvgl/src/core/lv_indev.c index 473badca4..b9460c0d5 100644 --- a/lib/lv_bindings/lvgl/src/core/lv_indev.c +++ b/lib/lv_bindings/lvgl/src/core/lv_indev.c @@ -9,7 +9,9 @@ #include "lv_indev.h" #include "lv_disp.h" #include "lv_obj.h" +#if LV_TOUCHSCREEN #include "lv_indev_scroll.h" +#endif #include "lv_group.h" #include "lv_refr.h" @@ -21,7 +23,7 @@ * DEFINES *********************/ #if LV_INDEV_DEF_SCROLL_THROW <= 0 - #warning "LV_INDEV_DRAG_THROW must be greater than 0" +#warning "LV_INDEV_DRAG_THROW must be greater than 0" #endif /********************** @@ -811,7 +813,9 @@ static void indev_proc_press(_lv_indev_proc_t * proc) if(new_obj_searched && proc->types.pointer.last_obj) { proc->types.pointer.scroll_throw_vect.x = 0; proc->types.pointer.scroll_throw_vect.y = 0; + #if LV_TOUCHSCREEN _lv_indev_scroll_throw_handler(proc); + #endif if(indev_reset_check(proc)) return; } @@ -877,7 +881,9 @@ static void indev_proc_press(_lv_indev_proc_t * proc) if(indev_act->proc.wait_until_release) return; + #if LV_TOUCHSCREEN _lv_indev_scroll_handler(proc); + #endif if(indev_reset_check(proc)) return; indev_gesture(proc); if(indev_reset_check(proc)) return; @@ -953,7 +959,9 @@ static void indev_proc_release(_lv_indev_proc_t * proc) /*The reset can be set in the Call the ancestor's event handler function. * In case of reset query ignore the remaining parts.*/ if(scroll_obj) { + #if LV_TOUCHSCREEN _lv_indev_scroll_throw_handler(proc); + #endif if(indev_reset_check(proc)) return; } } diff --git a/lib/lv_bindings/lvgl/src/core/lv_obj_scroll.c b/lib/lv_bindings/lvgl/src/core/lv_obj_scroll.c index 394a62060..aea7e92b8 100644 --- a/lib/lv_bindings/lvgl/src/core/lv_obj_scroll.c +++ b/lib/lv_bindings/lvgl/src/core/lv_obj_scroll.c @@ -10,7 +10,9 @@ #include "lv_obj.h" #include "lv_indev.h" #include "lv_disp.h" +#if LV_TOUCHSCREEN #include "lv_indev_scroll.h" +#endif /********************* * DEFINES @@ -419,10 +421,12 @@ bool lv_obj_is_scrolling(const lv_obj_t * obj) void lv_obj_update_snap(lv_obj_t * obj, lv_anim_enable_t anim_en) { + #if LV_TOUCHSCREEN lv_obj_update_layout(obj); lv_point_t p; lv_indev_scroll_get_snap_dist(obj, &p); lv_obj_scroll_by(obj, p.x, p.y, anim_en); + #endif } void lv_obj_get_scrollbar_area(lv_obj_t * obj, lv_area_t * hor_area, lv_area_t * ver_area) diff --git a/lib/lv_bindings/lvgl/src/core/lv_refr.c b/lib/lv_bindings/lvgl/src/core/lv_refr.c index b8accac2c..29755d45e 100644 --- a/lib/lv_bindings/lvgl/src/core/lv_refr.c +++ b/lib/lv_bindings/lvgl/src/core/lv_refr.c @@ -19,7 +19,7 @@ #include "../font/lv_font_fmt_txt.h" #if LV_USE_PERF_MONITOR || LV_USE_MEM_MONITOR - #include "../widgets/lv_label.h" +#include "../widgets/lv_label.h" #endif /********************* @@ -807,7 +807,7 @@ static void lv_refr_obj(lv_obj_t * obj, const lv_area_t * mask_ori_p) } } -static void draw_buf_rotate_180(lv_disp_drv_t * drv, lv_area_t * area, lv_color_t * color_p) +#ifdef LV_ROTATE_DRAW_BUF { lv_coord_t area_w = lv_area_get_width(area); lv_coord_t area_h = lv_area_get_height(area); @@ -983,6 +983,7 @@ static void draw_buf_rotate(lv_area_t * area, lv_color_t * color_p) if(rot_buf != NULL) lv_mem_buf_release(rot_buf); } } +#endif /** * Flush the content of the draw buffer @@ -1010,11 +1011,14 @@ static void draw_buf_flush(void) else draw_buf->flushing_last = 0; if(disp->driver->flush_cb) { +#ifdef LV_ROTATE_DRAW_BUF /*Rotate the buffer to the display's native orientation if necessary*/ if(disp->driver->rotated != LV_DISP_ROT_NONE && disp->driver->sw_rotate) { draw_buf_rotate(&draw_buf->area, draw_buf->buf_act); } - else { + else +#endif + { call_flush_cb(disp->driver, &draw_buf->area, color_p); } } @@ -1063,4 +1067,3 @@ static void mem_monitor_init(mem_monitor_t * _mem_monitor) _mem_monitor->mem_label = NULL; } #endif - diff --git a/lib/lv_bindings/lvgl/src/widgets/lv_roller.c b/lib/lv_bindings/lvgl/src/widgets/lv_roller.c index 34b50f8e4..d4b1ca73a 100644 --- a/lib/lv_bindings/lvgl/src/widgets/lv_roller.c +++ b/lib/lv_bindings/lvgl/src/widgets/lv_roller.c @@ -13,7 +13,9 @@ #include "../draw/lv_draw.h" #include "../core/lv_group.h" #include "../core/lv_indev.h" +#if LV_TOUCHSCREEN #include "../core/lv_indev_scroll.h" +#endif /********************* * DEFINES diff --git a/ports/stm32/boards/Passport/framebuffer.h b/ports/stm32/boards/Passport/framebuffer.h index 099cf19a8..3e7a607d7 100644 --- a/ports/stm32/boards/Passport/framebuffer.h +++ b/ports/stm32/boards/Passport/framebuffer.h @@ -7,7 +7,9 @@ #include +#ifndef FACTORY_TEST #include "lvgl/lvgl.h" +#endif #if defined(SCREEN_MODE_MONO) && defined(SCREEN_MODE_COLOR) #error "SCREEN_MODE_MONO and SCREEN_MODE_COLOR cannot be used at the same time" diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 9c773170e..59a8d6455 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -59,10 +59,10 @@ #define MICROPY_COMP_RETURN_IF_EXPR (1) // optimisations -#define MICROPY_OPT_COMPUTED_GOTO (1) +#define MICROPY_OPT_COMPUTED_GOTO (0) #define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0) -#define MICROPY_OPT_MPZ_BITWISE (1) -#define MICROPY_OPT_MATH_FACTORIAL (1) +#define MICROPY_OPT_MPZ_BITWISE (0) +#define MICROPY_OPT_MATH_FACTORIAL (0) // Python internal features #define MICROPY_READER_VFS (1) @@ -72,7 +72,7 @@ #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (0) #define MICROPY_KBD_EXCEPTION (1) -#define MICROPY_HELPER_REPL (1) +#define MICROPY_HELPER_REPL (0) #define MICROPY_REPL_INFO (0) #define MICROPY_REPL_EMACS_KEYS (0) #define MICROPY_REPL_AUTO_INDENT (0) @@ -84,7 +84,7 @@ #define MICROPY_STREAMS_NON_BLOCK (1) #define MICROPY_MODULE_BUILTIN_INIT (1) #define MICROPY_MODULE_WEAK_LINKS (1) -#define MICROPY_CAN_OVERRIDE_BUILTINS (1) +#define MICROPY_CAN_OVERRIDE_BUILTINS (0) #define MICROPY_USE_INTERNAL_ERRNO (1) #define MICROPY_ENABLE_SCHEDULER (1) #define MICROPY_SCHEDULER_DEPTH (8) @@ -95,8 +95,8 @@ #define MICROPY_PY_DESCRIPTORS (1) #define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) -#define MICROPY_PY_BUILTINS_STR_CENTER (1) -#define MICROPY_PY_BUILTINS_STR_PARTITION (1) +#define MICROPY_PY_BUILTINS_STR_CENTER (0) +#define MICROPY_PY_BUILTINS_STR_PARTITION (0) #define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) #define MICROPY_PY_BUILTINS_MEMORYVIEW (1) #define MICROPY_PY_BUILTINS_FROZENSET (1) @@ -104,22 +104,22 @@ #define MICROPY_PY_BUILTINS_SLICE_INDICES (1) #define MICROPY_PY_BUILTINS_ROUND_INT (1) #define MICROPY_PY_ALL_SPECIAL_METHODS (1) -#define MICROPY_PY_REVERSE_SPECIAL_METHODS (1) +#define MICROPY_PY_REVERSE_SPECIAL_METHODS (0) #define MICROPY_PY_BUILTINS_COMPILE (MICROPY_ENABLE_COMPILER) #define MICROPY_PY_BUILTINS_EXECFILE (MICROPY_ENABLE_COMPILER) #define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (1) #define MICROPY_PY_BUILTINS_INPUT (1) -#define MICROPY_PY_BUILTINS_POW3 (1) +#define MICROPY_PY_BUILTINS_POW3 (0) #define MICROPY_PY_BUILTINS_HELP (0) #ifndef MICROPY_PY_BUILTINS_HELP_TEXT #define MICROPY_PY_BUILTINS_HELP_TEXT stm32_help_text #endif -#define MICROPY_PY_BUILTINS_HELP_MODULES (1) +#define MICROPY_PY_BUILTINS_HELP_MODULES (0) #define MICROPY_PY_MICROPYTHON_MEM_INFO (1) #define MICROPY_PY_ARRAY_SLICE_ASSIGN (1) -#define MICROPY_PY_COLLECTIONS_DEQUE (1) +#define MICROPY_PY_COLLECTIONS_DEQUE (0) #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) -#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) +#define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (0) #define MICROPY_PY_MATH_ISCLOSE (1) #define MICROPY_PY_MATH_FACTORIAL (0) #define MICROPY_PY_CMATH (1) @@ -146,7 +146,7 @@ #define MICROPY_PY_UCTYPES (1) #endif #ifndef MICROPY_PY_UZLIB -#define MICROPY_PY_UZLIB (1) +#define MICROPY_PY_UZLIB (0) #endif #ifndef MICROPY_PY_UJSON #define MICROPY_PY_UJSON (1) @@ -168,7 +168,7 @@ #define MICROPY_PY_UCRYPTOLIB (MICROPY_PY_USSL) #ifndef MICROPY_PY_UBINASCII #define MICROPY_PY_UBINASCII (1) -#define MICROPY_PY_UBINASCII_CRC32 (1) +#define MICROPY_PY_UBINASCII_CRC32 (1) #endif #ifndef MICROPY_PY_UOS #define MICROPY_PY_UOS (1) @@ -182,7 +182,7 @@ #ifndef MICROPY_PY_URANDOM_EXTRA_FUNCS #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) #endif -#define MICROPY_PY_USELECT (1) +#define MICROPY_PY_USELECT (1) #ifndef MICROPY_PY_UTIME #define MICROPY_PY_UTIME (1) #endif @@ -232,7 +232,7 @@ #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ #define MICROPY_FATFS_USE_LABEL (1) #define MICROPY_FATFS_RPATH (2) -#define MICROPY_FATFS_MULTI_PARTITION (1) +#define MICROPY_FATFS_MULTI_PARTITION (0) // TODO these should be generic, not bound to a particular FS implementation #if MICROPY_VFS_FAT @@ -535,7 +535,7 @@ static inline mp_uint_t disable_irq(void) { #endif // We need an implementation of the log2 function which is not a macro -#define MP_NEED_LOG2 (1) +#define MP_NEED_LOG2 (0) // We need to provide a declaration/definition of alloca() #include diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 57f6b113c..eafb967e1 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -96,12 +96,12 @@ #define MICROPY_PY_DESCRIPTORS (1) #define MICROPY_PY_DELATTR_SETATTR (1) #define MICROPY_PY_BUILTINS_STR_UNICODE (1) -#define MICROPY_PY_BUILTINS_STR_CENTER (1) -#define MICROPY_PY_BUILTINS_STR_PARTITION (1) +#define MICROPY_PY_BUILTINS_STR_CENTER (0) +#define MICROPY_PY_BUILTINS_STR_PARTITION (0) #define MICROPY_PY_BUILTINS_STR_SPLITLINES (1) #define MICROPY_PY_BUILTINS_MEMORYVIEW (1) #define MICROPY_PY_BUILTINS_FROZENSET (1) -#define MICROPY_PY_BUILTINS_COMPILE (1) +#define MICROPY_PY_BUILTINS_COMPILE (0) #define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (1) #define MICROPY_PY_BUILTINS_INPUT (1) #define MICROPY_PY_BUILTINS_POW3 (1) From a3540a5be1200fdfbeef1f995e7916737e40da4b Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 14 Sep 2023 20:19:12 -0400 Subject: [PATCH 045/239] SFT-2074: added uzlib back in to fix compiling --- ports/stm32/mpconfigport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 59a8d6455..0606d4f67 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -146,7 +146,7 @@ #define MICROPY_PY_UCTYPES (1) #endif #ifndef MICROPY_PY_UZLIB -#define MICROPY_PY_UZLIB (0) +#define MICROPY_PY_UZLIB (1) #endif #ifndef MICROPY_PY_UJSON #define MICROPY_PY_UJSON (1) From a730769da7b29f4d7dfe27650d45c1fb2f970a95 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 18 Sep 2023 12:48:26 -0400 Subject: [PATCH 046/239] SFT-2755: capitalized "Words" in import menu --- .../stm32/boards/Passport/modules/flows/restore_seed_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index 0094d1572..f18e9c818 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -27,8 +27,8 @@ async def choose_restore_method(self): from pages import ChooserPage from data_codecs.qr_type import QRType - options = [{'label': '12 words', 'value': 12}, - {'label': '24 words', 'value': 24}, + options = [{'label': '12 Words', 'value': 12}, + {'label': '24 Words', 'value': 24}, {'label': 'Compact SeedQR', 'value': QRType.COMPACT_SEED_QR}, {'label': 'SeedQR', 'value': QRType.SEED_QR}] From b386966cdc84c67827bf62bea32531ba11d21846 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 20 Sep 2023 16:48:18 -0400 Subject: [PATCH 047/239] SFT-2770: fixed all known instances of "#" causing problems in user-input text --- .../Passport/modules/flows/import_multisig_wallet_flow.py | 4 ++-- .../modules/flows/view_derived_key_details_flow.py | 3 ++- .../Passport/modules/flows/view_multisig_details_flow.py | 5 +++-- ports/stm32/boards/Passport/modules/menus.py | 7 ++++--- ports/stm32/boards/Passport/modules/multisig_wallet.py | 6 +++--- .../boards/Passport/modules/pages/account_details_page.py | 3 ++- ports/stm32/boards/Passport/modules/ui/ui.py | 4 ++-- ports/stm32/boards/Passport/modules/utils.py | 4 ++++ 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/import_multisig_wallet_flow.py b/ports/stm32/boards/Passport/modules/flows/import_multisig_wallet_flow.py index 08c9ff991..32899264a 100644 --- a/ports/stm32/boards/Passport/modules/flows/import_multisig_wallet_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/import_multisig_wallet_flow.py @@ -29,12 +29,12 @@ async def show_overview(self): async def show_details(self): from errors import Error - from utils import spinner_task + from utils import spinner_task, escape_text from tasks import save_multisig_wallet_task msg = self.ms.format_details() - result = await LongTextPage(card_header={'title': self.ms.name}, text=msg, centered=True).show() + result = await LongTextPage(card_header={'title': escape_text(self.ms.name)}, text=msg, centered=True).show() if not result: self.back() else: diff --git a/ports/stm32/boards/Passport/modules/flows/view_derived_key_details_flow.py b/ports/stm32/boards/Passport/modules/flows/view_derived_key_details_flow.py index 194ff6fd6..670a1f5ba 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_derived_key_details_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_derived_key_details_flow.py @@ -17,6 +17,7 @@ async def show_overview(self): from pages import LongTextPage, ErrorPage from derived_key import get_key_type_from_tn import microns + from utils import escape_text key_type = get_key_type_from_tn(self.key['tn']) @@ -30,7 +31,7 @@ async def show_overview(self): recolor(HIGHLIGHT_TEXT_HEX, 'Key Index'), self.key['index']) - await LongTextPage(card_header={'title': self.key['name']}, + await LongTextPage(card_header={'title': escape_text(self.key['name'])}, text=msg, left_micron=None, right_micron=microns.Checkmark, diff --git a/ports/stm32/boards/Passport/modules/flows/view_multisig_details_flow.py b/ports/stm32/boards/Passport/modules/flows/view_multisig_details_flow.py index 48b712eb7..356c45353 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_multisig_details_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_multisig_details_flow.py @@ -5,6 +5,7 @@ from flows import Flow from pages import LongTextPage +from utils import escape_text class ViewMultisigDetailsFlow(Flow): @@ -19,7 +20,7 @@ def __init__(self, context=None): async def show_overview(self): msg, _ = self.ms.format_overview(importing=False) - result = await LongTextPage(card_header={'title': self.ms.name}, text=msg, centered=True).show() + result = await LongTextPage(card_header={'title': escape_text(self.ms.name)}, text=msg, centered=True).show() if not result: self.set_result(False) return @@ -30,7 +31,7 @@ async def show_details(self): import microns msg = self.ms.format_details() - result = await LongTextPage(card_header={'title': self.ms.name}, + result = await LongTextPage(card_header={'title': escape_text(self.ms.name)}, text=msg, right_micron=microns.Checkmark, centered=True).show() diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index 5b74cf914..bfb0961d0 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -193,7 +193,7 @@ def nostr_menu(): def key_manager_menu(): - from utils import get_derived_keys, are_hidden_keys_showing + from utils import get_derived_keys, are_hidden_keys_showing, escape_text from derived_key import get_key_type_from_tn from common import settings @@ -210,7 +210,7 @@ def key_manager_menu(): if not key_type: continue - title = "{} ({})".format(key['name'], key['index']) + title = "{} ({})".format(escape_text(key['name']), key['index']) result.append({'icon': key_type['icon'], 'label': title, @@ -297,6 +297,7 @@ def multisig_menu(): from multisig_wallet import MultisigWallet from pages import MultisigPolicySettingPage, ErrorPage from flows import ImportMultisigWalletFromMicroSDFlow, ImportMultisigWalletFromQRFlow + from utils import escape_text if not MultisigWallet.exists(): items = [{'icon': 'ICON_TWO_KEYS', 'label': '(None setup yet)', 'page': ErrorPage, @@ -304,7 +305,7 @@ def multisig_menu(): else: items = [] for ms in MultisigWallet.get_all(): - nice_name = '%d/%d: %s' % (ms.M, ms.N, ms.name) + nice_name = '%d/%d: %s' % (ms.M, ms.N, escape_text(ms.name)) items.append({ 'icon': 'ICON_TWO_KEYS', 'label': nice_name, diff --git a/ports/stm32/boards/Passport/modules/multisig_wallet.py b/ports/stm32/boards/Passport/modules/multisig_wallet.py index 0c7251741..46059e4d1 100644 --- a/ports/stm32/boards/Passport/modules/multisig_wallet.py +++ b/ports/stm32/boards/Passport/modules/multisig_wallet.py @@ -614,7 +614,7 @@ def from_file(cls, config, name=None): ln = ln[1:] else: continue - elif comm != -1: + elif comm != -1 and ln[0:4] != "Name": if not ln[comm + 1:comm + 2].isdigit(): ln = ln[0:comm] @@ -944,7 +944,7 @@ def get_deriv_paths(self): return derivs, dsum def format_overview(self, importing=True): - from utils import recolor + from utils import recolor, escape_text from styles.colors import HIGHLIGHT_TEXT_HEX, COPPER_HEX M, N = self.M, self.N @@ -1002,7 +1002,7 @@ def format_overview(self, importing=True): M=M, N=N, name_title=recolor(HIGHLIGHT_TEXT_HEX, 'Wallet Name'), - name=self.name, + name=escape_text(self.name), policy_title=recolor(HIGHLIGHT_TEXT_HEX, 'Policy:'), exp=exp, addr_title=recolor(HIGHLIGHT_TEXT_HEX, 'Addresses'), diff --git a/ports/stm32/boards/Passport/modules/pages/account_details_page.py b/ports/stm32/boards/Passport/modules/pages/account_details_page.py index edce9079f..089031778 100644 --- a/ports/stm32/boards/Passport/modules/pages/account_details_page.py +++ b/ports/stm32/boards/Passport/modules/pages/account_details_page.py @@ -7,6 +7,7 @@ from pages import LongTextPage import microns from styles.colors import HIGHLIGHT_TEXT_HEX +from utils import escape_text class AccountDetailsPage(LongTextPage): @@ -45,7 +46,7 @@ def __init__( {deriv_title} {deriv}'''.format( acct_name_title=recolor(HIGHLIGHT_TEXT_HEX, 'Account Name'), - acct_name=self.account.get('name'), + acct_name=escape_text(self.account.get('name')), acct_num_title=recolor(HIGHLIGHT_TEXT_HEX, 'Account Number'), acct_num=self.account.get('acct_num'), deriv_title=recolor(HIGHLIGHT_TEXT_HEX, 'Derivation Path'), diff --git a/ports/stm32/boards/Passport/modules/ui/ui.py b/ports/stm32/boards/Passport/modules/ui/ui.py index 86d8fe39e..0520cc6b0 100644 --- a/ports/stm32/boards/Passport/modules/ui/ui.py +++ b/ports/stm32/boards/Passport/modules/ui/ui.py @@ -200,7 +200,7 @@ def update_cards(self, is_init=False, stay_on_last_card=False): from flows import MenuFlow - from utils import get_accounts, has_seed, is_extension_enabled + from utils import get_accounts, has_seed, is_extension_enabled, escape_text from menus import account_menu, plus_menu from extensions.extensions import supported_extensions from constants import MAX_ACCOUNTS @@ -257,7 +257,7 @@ def update_cards(self, 'header_color': LIGHT_GREY, 'header_fg_color': LIGHT_TEXT, 'statusbar': {'title': 'ACCOUNT', 'icon': 'ICON_FOLDER', 'fg_color': get_account_fg(account)}, - 'title': account.get('name'), + 'title': escape_text(account.get('name')), 'page_micron': microns.PageDot, 'bg_color': get_account_bg(account), 'flow': MenuFlow, diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 73f28777b..01d354d7a 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1434,4 +1434,8 @@ def is_key_hidden(key): return updated['hidden'] +def escape_text(text): + return text.replace("#", "##") + + # EOF From d2b941d9b519f676dd3070f9266c63a5149d9ac6 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 14:40:18 -0400 Subject: [PATCH 048/239] SFT-2325: added hash files to github workflow, and added to hashes.md to every "just hash" output --- .github/workflows/validate_and_build.yaml | 273 ++++++++++++---------- ports/stm32/Justfile | 1 + 2 files changed, 152 insertions(+), 122 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 00ae93dc1..6c0362a41 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -17,24 +17,24 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - - - name: Lint the codebase - run: just lint + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + + - name: Lint the codebase + run: just lint build-firmware: name: Build Firmware @@ -43,7 +43,11 @@ jobs: strategy: matrix: - build: [{screen: "mono", suffix: "-founders-passport"}, {screen: "color", suffix: "-passport"}] + build: + [ + { screen: 'mono', suffix: '-founders-passport', hash_suffix: '' }, + { screen: 'color', suffix: '-passport', hash_suffix: '-founders' }, + ] services: registry: @@ -52,45 +56,70 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: | - echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - echo "SCREEN_MODE=$(echo "${{ matrix.build.screen }}" | tr a-z A-Z)" >> $GITHUB_ENV - - - name: Build - run: | - echo "$SIGNING_KEY" > ports/stm32/signing_key.pem - version=$(cat version.txt) - echo "version=$(cat version.txt)" >> $GITHUB_ENV - - just sign signing_key.pem "${version}" ${{ matrix.build.screen }} - env: - SIGNING_KEY: ${{ secrets.UserSigningKey }} - - - name: Upload firmware (unsigned) - uses: actions/upload-artifact@v2 - with: - name: v${{env.version}}-unsigned${{ matrix.build.suffix }}.bin - path: ports/stm32/build-Passport/firmware-${{ env.SCREEN_MODE }}.bin - - - name: Upload firmware (signed) - uses: actions/upload-artifact@v2 - with: - name: v${{env.version}}-beta${{ matrix.build.suffix }}.bin - path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: | + echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + echo "SCREEN_MODE=$(echo "${{ matrix.build.screen }}" | tr a-z A-Z)" >> $GITHUB_ENV + + - name: Build + run: | + echo "$SIGNING_KEY" > ports/stm32/signing_key.pem + version=$(cat version.txt) + echo "version=$(cat version.txt)" >> $GITHUB_ENV + + just sign signing_key.pem "${version}" ${{ matrix.build.screen }} + just hash + env: + SIGNING_KEY: ${{ secrets.UserSigningKey }} + + - name: Upload firmware (unsigned) + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}-unsigned${{ matrix.build.suffix }}.bin + path: ports/stm32/build-Passport/firmware-${{ env.SCREEN_MODE }}.bin + + - name: Upload firmware (signed) + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}-beta${{ matrix.build.suffix }}.bin + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin + + - name: Upload MD5 Hash + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}{{ matrix.build.hash_suffix }}-md5 + path: ports/stm32/build-Passport/v${{env.version}}-beta-md5 + + - name: Upload Build Hash + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}{{ matrix.build.hash_suffix }}-build-hash + path: ports/stm32/build-Passport/v${{env.version}}-beta-build-hash + + - name: Upload SHA256 Hash + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}{{ matrix.build.hash_suffix }}-sha256 + path: ports/stm32/build-Passport/v${{env.version}}-beta-sha256 + + - name: Upload Hashes Markdown + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}{{ matrix.build.hash_suffix }}-hashes.md + path: ports/stm32/build-Passport/v${{env.version}}-beta-hashes.md build-bootloader: name: Build Bootloader @@ -100,7 +129,7 @@ jobs: # TODO: PASS1-665. strategy: matrix: - screen: ["color"] + screen: ['color'] services: registry: @@ -109,32 +138,32 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: | - echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - echo "SCREEN_MODE=$(echo ${{ matrix.screen }} | tr a-z A-Z)" >> $GITHUB_ENV - - - name: Build - run: just build-bootloader ${{ matrix.screen }} - - - name: Upload bootloader - uses: actions/upload-artifact@v2 - with: - name: bootloader-${{ env.SCREEN_MODE }}.bin - path: ports/stm32/boards/Passport/bootloader/arm/release/bootloader-${{ env.SCREEN_MODE }}.bin + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: | + echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + echo "SCREEN_MODE=$(echo ${{ matrix.screen }} | tr a-z A-Z)" >> $GITHUB_ENV + + - name: Build + run: just build-bootloader ${{ matrix.screen }} + + - name: Upload bootloader + uses: actions/upload-artifact@v2 + with: + name: bootloader-${{ env.SCREEN_MODE }}.bin + path: ports/stm32/boards/Passport/bootloader/arm/release/bootloader-${{ env.SCREEN_MODE }}.bin build-simulator: name: Build Simulator @@ -143,7 +172,7 @@ jobs: strategy: matrix: - screen: ["mono", "color"] + screen: ['mono', 'color'] services: registry: @@ -152,24 +181,24 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - - - name: Build - run: just build-simulator ${{ matrix.screen }} + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + + - name: Build + run: just build-simulator ${{ matrix.screen }} build-tools: name: Build Tools @@ -183,21 +212,21 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - - - name: Build - run: just tools + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + + - name: Build + run: just tools diff --git a/ports/stm32/Justfile b/ports/stm32/Justfile index cc3b0c4d8..5463d980a 100644 --- a/ports/stm32/Justfile +++ b/ports/stm32/Justfile @@ -353,6 +353,7 @@ hash filepath screen="mono" output_file="": if [ -z "{{output_file}}" ]; then echo -e "$output" + echo -e "$output" > ${directory}/${release_name}-hashes.md else echo -e "$output" > "{{output_file}}" fi From c9693a5439173ae90ec758d9ed4809e68a06a71d Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 18:38:50 -0400 Subject: [PATCH 049/239] SFT-2325: fixed hash-suffix and just hash command --- .github/workflows/validate_and_build.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 6c0362a41..46ad7acdd 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -45,8 +45,8 @@ jobs: matrix: build: [ - { screen: 'mono', suffix: '-founders-passport', hash_suffix: '' }, - { screen: 'color', suffix: '-passport', hash_suffix: '-founders' }, + { screen: 'mono', suffix: '-founders-passport', hash_suffix: '-founders'}, + { screen: 'color', suffix: '-passport', hash_suffix: ''}, ] services: @@ -81,7 +81,7 @@ jobs: echo "version=$(cat version.txt)" >> $GITHUB_ENV just sign signing_key.pem "${version}" ${{ matrix.build.screen }} - just hash + just hash ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} From 8a161b5be7cabd54f0a0d49a9118c77fbe67ae3e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 18:52:28 -0400 Subject: [PATCH 050/239] SFT-2325: added hash recipe to root justfile --- Justfile | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Justfile b/Justfile index 0cee790fc..fa71f3e79 100644 --- a/Justfile +++ b/Justfile @@ -74,6 +74,50 @@ test: # Lint the codebase. lint: (run-in-docker "just ports/stm32/lint") (run-in-docker "just extmod/foundation-rust/lint") +# Hash the files +hash filepath screen="mono" output_file="": + #!/usr/bin/env bash + + if [ "{{screen}}" != "mono" ] && [ "{{screen}}" != "color" ]; then + echo "Screen must be either 'mono' or 'color', the default value is 'mono'." + exit 1 + fi + + set -e + filename=`basename {{filepath}}` + directory=`dirname {{filepath}}` + release_name=${filename::-13} + + # SHA256 + sha=`shasum -b -a 256 {{filepath}} | sed -rn 's/^(.*) .*$/\1/p'` + echo "$sha" > ${directory}/${release_name}-sha256 + + # MD5 + md5=`mdsum {{filepath}} | sed -rn 's/^(.*) .*$/\1/p' | xargs` + echo "$md5" > ${directory}/${release_name}-md5 + + # Build Hash + build_hash=`cosign -t {{screen}} -p -f {{filepath}} | sed -rn 's/^FW Build Hash: (.*)$/\1/p'` + echo "$build_hash" > ${directory}/${release_name}-build-hash + + output="## RELEASE HASHES\n\n" + + if [ -n "{{output_file}}" ]; then + output+="### $filename:\n\n" + fi + + output+="SHA256: \`$sha\`\nMD5: \`$md5\`\n\nYou can check these hashes with the following commands on most operating systems:\n\n" + + output+="SHA256: \`shasum -b -a 256 $filename\`\nMD5: \`md5 $filename\` or \`mdsum $filename\` or \`md5sum $filename\`\n\n" + output+="### DEVELOPERS ONLY\n\nBuild Hash: \`$build_hash\`" + + if [ -z "{{output_file}}" ]; then + echo -e "$output" + echo -e "$output" > ${directory}/${release_name}-hashes.md + else + echo -e "$output" > "{{output_file}}" + fi + [private] mpy-cross: (run-in-docker "make -C mpy-cross PROG=mpy-cross-docker BUILD=build-docker") From ddcd7949a41e3e8891d245b3e2cb1f35569e6bda Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 18:58:49 -0400 Subject: [PATCH 051/239] SFT-2325: called nested justfile for hash recipe --- .github/workflows/validate_and_build.yaml | 2 +- Justfile | 44 ----------------------- 2 files changed, 1 insertion(+), 45 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 46ad7acdd..357a08012 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -81,7 +81,7 @@ jobs: echo "version=$(cat version.txt)" >> $GITHUB_ENV just sign signing_key.pem "${version}" ${{ matrix.build.screen }} - just hash ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} + just ports/stm32/hash ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} diff --git a/Justfile b/Justfile index fa71f3e79..0cee790fc 100644 --- a/Justfile +++ b/Justfile @@ -74,50 +74,6 @@ test: # Lint the codebase. lint: (run-in-docker "just ports/stm32/lint") (run-in-docker "just extmod/foundation-rust/lint") -# Hash the files -hash filepath screen="mono" output_file="": - #!/usr/bin/env bash - - if [ "{{screen}}" != "mono" ] && [ "{{screen}}" != "color" ]; then - echo "Screen must be either 'mono' or 'color', the default value is 'mono'." - exit 1 - fi - - set -e - filename=`basename {{filepath}}` - directory=`dirname {{filepath}}` - release_name=${filename::-13} - - # SHA256 - sha=`shasum -b -a 256 {{filepath}} | sed -rn 's/^(.*) .*$/\1/p'` - echo "$sha" > ${directory}/${release_name}-sha256 - - # MD5 - md5=`mdsum {{filepath}} | sed -rn 's/^(.*) .*$/\1/p' | xargs` - echo "$md5" > ${directory}/${release_name}-md5 - - # Build Hash - build_hash=`cosign -t {{screen}} -p -f {{filepath}} | sed -rn 's/^FW Build Hash: (.*)$/\1/p'` - echo "$build_hash" > ${directory}/${release_name}-build-hash - - output="## RELEASE HASHES\n\n" - - if [ -n "{{output_file}}" ]; then - output+="### $filename:\n\n" - fi - - output+="SHA256: \`$sha\`\nMD5: \`$md5\`\n\nYou can check these hashes with the following commands on most operating systems:\n\n" - - output+="SHA256: \`shasum -b -a 256 $filename\`\nMD5: \`md5 $filename\` or \`mdsum $filename\` or \`md5sum $filename\`\n\n" - output+="### DEVELOPERS ONLY\n\nBuild Hash: \`$build_hash\`" - - if [ -z "{{output_file}}" ]; then - echo -e "$output" - echo -e "$output" > ${directory}/${release_name}-hashes.md - else - echo -e "$output" > "{{output_file}}" - fi - [private] mpy-cross: (run-in-docker "make -C mpy-cross PROG=mpy-cross-docker BUILD=build-docker") From 413ed413586538bc969b5a05c38dd22569aa78ea Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 19:12:19 -0400 Subject: [PATCH 052/239] SFT-2325: attempted to fix target filepath --- .github/workflows/validate_and_build.yaml | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 357a08012..a73047743 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -81,7 +81,7 @@ jobs: echo "version=$(cat version.txt)" >> $GITHUB_ENV just sign signing_key.pem "${version}" ${{ matrix.build.screen }} - just ports/stm32/hash ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} + just ports/stm32/hash ports/stm32/build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} diff --git a/version.txt b/version.txt index ccbccc3dc..276cbf9e2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.2.0 +2.3.0 From 1603d0871f82b6eeefb2edc08560ce0bcfd921f0 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 19:23:29 -0400 Subject: [PATCH 053/239] SFT-2325: used file path relative to nested justfile for hash command --- .github/workflows/validate_and_build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index a73047743..96b0c07b3 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -81,7 +81,7 @@ jobs: echo "version=$(cat version.txt)" >> $GITHUB_ENV just sign signing_key.pem "${version}" ${{ matrix.build.screen }} - just ports/stm32/hash ports/stm32/build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} + just ports/stm32/hash build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} From 5e93cc007b5f1d40f802aeeb06c037d2f6ac9922 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 19:34:31 -0400 Subject: [PATCH 054/239] SFT-2325: fixed hash file paths --- .github/workflows/validate_and_build.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 96b0c07b3..9c0d3640b 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -100,25 +100,25 @@ jobs: - name: Upload MD5 Hash uses: actions/upload-artifact@v2 with: - name: v${{env.version}}{{ matrix.build.hash_suffix }}-md5 + name: v${{env.version}}${{ matrix.build.hash_suffix }}-md5 path: ports/stm32/build-Passport/v${{env.version}}-beta-md5 - name: Upload Build Hash uses: actions/upload-artifact@v2 with: - name: v${{env.version}}{{ matrix.build.hash_suffix }}-build-hash + name: v${{env.version}}${{ matrix.build.hash_suffix }}-build-hash path: ports/stm32/build-Passport/v${{env.version}}-beta-build-hash - name: Upload SHA256 Hash uses: actions/upload-artifact@v2 with: - name: v${{env.version}}{{ matrix.build.hash_suffix }}-sha256 + name: v${{env.version}}${{ matrix.build.hash_suffix }}-sha256 path: ports/stm32/build-Passport/v${{env.version}}-beta-sha256 - name: Upload Hashes Markdown uses: actions/upload-artifact@v2 with: - name: v${{env.version}}{{ matrix.build.hash_suffix }}-hashes.md + name: v${{env.version}}${{ matrix.build.hash_suffix }}-hashes.md path: ports/stm32/build-Passport/v${{env.version}}-beta-hashes.md build-bootloader: From fdc65b8daab9352b6019b64ec1dd2443c3ace829 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 20:00:47 -0400 Subject: [PATCH 055/239] SFT-2325: fixed founders edition hash artifact paths --- .github/workflows/validate_and_build.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 9c0d3640b..1d7414442 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -101,25 +101,25 @@ jobs: uses: actions/upload-artifact@v2 with: name: v${{env.version}}${{ matrix.build.hash_suffix }}-md5 - path: ports/stm32/build-Passport/v${{env.version}}-beta-md5 + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-md5 - name: Upload Build Hash uses: actions/upload-artifact@v2 with: name: v${{env.version}}${{ matrix.build.hash_suffix }}-build-hash - path: ports/stm32/build-Passport/v${{env.version}}-beta-build-hash + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-build-hash - name: Upload SHA256 Hash uses: actions/upload-artifact@v2 with: name: v${{env.version}}${{ matrix.build.hash_suffix }}-sha256 - path: ports/stm32/build-Passport/v${{env.version}}-beta-sha256 + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-sha256 - name: Upload Hashes Markdown uses: actions/upload-artifact@v2 with: name: v${{env.version}}${{ matrix.build.hash_suffix }}-hashes.md - path: ports/stm32/build-Passport/v${{env.version}}-beta-hashes.md + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-hashes.md build-bootloader: name: Build Bootloader From 97410471a7df5f5ea2e31bffec7d844f28b8313f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 20:20:27 -0400 Subject: [PATCH 056/239] SFT-2325: attempting to fix md5 hashes in github actions --- ports/stm32/Justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/Justfile b/ports/stm32/Justfile index 5463d980a..e43c4d1a0 100644 --- a/ports/stm32/Justfile +++ b/ports/stm32/Justfile @@ -333,7 +333,7 @@ hash filepath screen="mono" output_file="": echo "$sha" > ${directory}/${release_name}-sha256 # MD5 - md5=`mdsum {{filepath}} | sed -rn 's/^(.*) .*$/\1/p' | xargs` + md5=`md5sum {{filepath}} | sed -rn 's/^(.*) .*$/\1/p' | xargs` echo "$md5" > ${directory}/${release_name}-md5 # Build Hash From 4ff54d51f74267c62d79b7690013c6e30a05a3f6 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 20:35:01 -0400 Subject: [PATCH 057/239] SFT-2325: used newer just version --- .github/workflows/validate_and_build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 1d7414442..d587a43dd 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -30,7 +30,7 @@ jobs: cache-from: type=gha cache-to: type=gha tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - uses: extractions/setup-just@69d82fb0233557aec017ef13706851d0694e0f1d - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - name: Lint the codebase From f51ea3a22f93e20d8e0bd201352b274e4d19beba Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 20:39:44 -0400 Subject: [PATCH 058/239] SFT-2325: debugging cosign not found during actions --- .github/workflows/validate_and_build.yaml | 1 + ports/stm32/Justfile | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index d587a43dd..9ea0755b3 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -81,6 +81,7 @@ jobs: echo "version=$(cat version.txt)" >> $GITHUB_ENV just sign signing_key.pem "${version}" ${{ matrix.build.screen }} + which cosign just ports/stm32/hash build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} diff --git a/ports/stm32/Justfile b/ports/stm32/Justfile index e43c4d1a0..c32d4b6ff 100644 --- a/ports/stm32/Justfile +++ b/ports/stm32/Justfile @@ -337,6 +337,8 @@ hash filepath screen="mono" output_file="": echo "$md5" > ${directory}/${release_name}-md5 # Build Hash + which cosign + testing=`which cosign` build_hash=`cosign -t {{screen}} -p -f {{filepath}} | sed -rn 's/^FW Build Hash: (.*)$/\1/p'` echo "$build_hash" > ${directory}/${release_name}-build-hash From cfbce08146515971edd306b2936586805c527571 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 20:49:04 -0400 Subject: [PATCH 059/239] SFT-2325: removed failed debug --- .github/workflows/validate_and_build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 9ea0755b3..d587a43dd 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -81,7 +81,6 @@ jobs: echo "version=$(cat version.txt)" >> $GITHUB_ENV just sign signing_key.pem "${version}" ${{ matrix.build.screen }} - which cosign just ports/stm32/hash build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} From a95f899a0102b69364dfa91c7db6ba0c539d6dbd Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 21:09:08 -0400 Subject: [PATCH 060/239] SFT-2325: testing subshelling vs command substitution --- ports/stm32/Justfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ports/stm32/Justfile b/ports/stm32/Justfile index c32d4b6ff..d7517292f 100644 --- a/ports/stm32/Justfile +++ b/ports/stm32/Justfile @@ -337,9 +337,7 @@ hash filepath screen="mono" output_file="": echo "$md5" > ${directory}/${release_name}-md5 # Build Hash - which cosign - testing=`which cosign` - build_hash=`cosign -t {{screen}} -p -f {{filepath}} | sed -rn 's/^FW Build Hash: (.*)$/\1/p'` + build_hash=$(cosign -t {{screen}} -p -f {{filepath}} | sed -rn 's/^FW Build Hash: (.*)$/\1/p') echo "$build_hash" > ${directory}/${release_name}-build-hash output="## RELEASE HASHES\n\n" From 2993d2167955935766795f1fa4ea21be2ba95217 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 21:38:10 -0400 Subject: [PATCH 061/239] SFT-2325: replaced sign with hash in github action --- .github/workflows/validate_and_build.yaml | 4 ++-- Justfile | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index d587a43dd..416ab16d8 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -80,8 +80,8 @@ jobs: version=$(cat version.txt) echo "version=$(cat version.txt)" >> $GITHUB_ENV - just sign signing_key.pem "${version}" ${{ matrix.build.screen }} - just ports/stm32/hash build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} +# just sign signing_key.pem "${version}" ${{ matrix.build.screen }} + just hash signing_key.pem "${version}" ports/stm32/build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} diff --git a/Justfile b/Justfile index 0cee790fc..8945da2bc 100644 --- a/Justfile +++ b/Justfile @@ -47,6 +47,9 @@ build-cosign: (run-in-docker "make -C ports/stm32/boards/Passport/tools/cosign") # Sign the built firmware using a private key and the cosign tool sign keypath version screen="mono": (build-firmware screen) (build-cosign) (run-in-docker ("just cosign_filepath=build-Passport/firmware-" + uppercase(screen) + ".bin cosign_keypath=" + keypath + " ports/stm32/sign " + version + " " + screen)) +# Produce hashes of the firmware +hash keypath version file screen="mono": (sign keypath version screen) (run-in-docker ("just cosign_filepath=build-Passport/firmware-" + uppercase(screen) + ".bin cosign_keypath=" + keypath + " ports/stm32/hash " + file + " " + screen)) + # Clean firmware build clean: (run-in-docker "just ports/stm32/clean") From 467fddb49c250c6728c32fa9e84b4182b6aa99d0 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 21:41:12 -0400 Subject: [PATCH 062/239] SFT-2325: removed comment line --- .github/workflows/validate_and_build.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 416ab16d8..40c84590f 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -80,7 +80,6 @@ jobs: version=$(cat version.txt) echo "version=$(cat version.txt)" >> $GITHUB_ENV -# just sign signing_key.pem "${version}" ${{ matrix.build.screen }} just hash signing_key.pem "${version}" ports/stm32/build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} From 2ecf71636058512fc37049158ecd3c5887522f02 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Sep 2023 21:50:39 -0400 Subject: [PATCH 063/239] SFT-2325: fixed target file path --- .github/workflows/validate_and_build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 40c84590f..67e91c055 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -80,7 +80,7 @@ jobs: version=$(cat version.txt) echo "version=$(cat version.txt)" >> $GITHUB_ENV - just hash signing_key.pem "${version}" ports/stm32/build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} + just hash signing_key.pem "${version}" build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} env: SIGNING_KEY: ${{ secrets.UserSigningKey }} From a3150dcc6f9f39a18e0a3bcf64bc463763e7eb41 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 25 Sep 2023 19:55:25 -0400 Subject: [PATCH 064/239] SFT-2325: corrected IDE spacing changes --- .github/workflows/validate_and_build.yaml | 286 +++++++++++----------- 1 file changed, 143 insertions(+), 143 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 67e91c055..7193acb9f 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -17,24 +17,24 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@69d82fb0233557aec017ef13706851d0694e0f1d - - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - - - name: Lint the codebase - run: just lint + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@69d82fb0233557aec017ef13706851d0694e0f1d + - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + + - name: Lint the codebase + run: just lint build-firmware: name: Build Firmware @@ -56,69 +56,69 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: | - echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - echo "SCREEN_MODE=$(echo "${{ matrix.build.screen }}" | tr a-z A-Z)" >> $GITHUB_ENV - - - name: Build - run: | - echo "$SIGNING_KEY" > ports/stm32/signing_key.pem - version=$(cat version.txt) - echo "version=$(cat version.txt)" >> $GITHUB_ENV - - just hash signing_key.pem "${version}" build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} - env: - SIGNING_KEY: ${{ secrets.UserSigningKey }} - - - name: Upload firmware (unsigned) - uses: actions/upload-artifact@v2 - with: - name: v${{env.version}}-unsigned${{ matrix.build.suffix }}.bin - path: ports/stm32/build-Passport/firmware-${{ env.SCREEN_MODE }}.bin - - - name: Upload firmware (signed) - uses: actions/upload-artifact@v2 - with: - name: v${{env.version}}-beta${{ matrix.build.suffix }}.bin - path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin - - - name: Upload MD5 Hash - uses: actions/upload-artifact@v2 - with: - name: v${{env.version}}${{ matrix.build.hash_suffix }}-md5 - path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-md5 - - - name: Upload Build Hash - uses: actions/upload-artifact@v2 - with: - name: v${{env.version}}${{ matrix.build.hash_suffix }}-build-hash - path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-build-hash - - - name: Upload SHA256 Hash - uses: actions/upload-artifact@v2 - with: - name: v${{env.version}}${{ matrix.build.hash_suffix }}-sha256 - path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-sha256 - - - name: Upload Hashes Markdown - uses: actions/upload-artifact@v2 - with: - name: v${{env.version}}${{ matrix.build.hash_suffix }}-hashes.md - path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-hashes.md + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: | + echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + echo "SCREEN_MODE=$(echo "${{ matrix.build.screen }}" | tr a-z A-Z)" >> $GITHUB_ENV + + - name: Build + run: | + echo "$SIGNING_KEY" > ports/stm32/signing_key.pem + version=$(cat version.txt) + echo "version=$(cat version.txt)" >> $GITHUB_ENV + + just hash signing_key.pem "${version}" build-Passport/v${version}-beta${{ matrix.build.suffix }}.bin ${{ matrix.build.screen}} + env: + SIGNING_KEY: ${{ secrets.UserSigningKey }} + + - name: Upload firmware (unsigned) + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}-unsigned${{ matrix.build.suffix }}.bin + path: ports/stm32/build-Passport/firmware-${{ env.SCREEN_MODE }}.bin + + - name: Upload firmware (signed) + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}-beta${{ matrix.build.suffix }}.bin + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin + + - name: Upload MD5 Hash + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}${{ matrix.build.hash_suffix }}-md5 + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-md5 + + - name: Upload Build Hash + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}${{ matrix.build.hash_suffix }}-build-hash + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-build-hash + + - name: Upload SHA256 Hash + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}${{ matrix.build.hash_suffix }}-sha256 + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-sha256 + + - name: Upload Hashes Markdown + uses: actions/upload-artifact@v2 + with: + name: v${{env.version}}${{ matrix.build.hash_suffix }}-hashes.md + path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-hashes.md build-bootloader: name: Build Bootloader @@ -137,32 +137,32 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: | - echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - echo "SCREEN_MODE=$(echo ${{ matrix.screen }} | tr a-z A-Z)" >> $GITHUB_ENV - - - name: Build - run: just build-bootloader ${{ matrix.screen }} - - - name: Upload bootloader - uses: actions/upload-artifact@v2 - with: - name: bootloader-${{ env.SCREEN_MODE }}.bin - path: ports/stm32/boards/Passport/bootloader/arm/release/bootloader-${{ env.SCREEN_MODE }}.bin + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: | + echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + echo "SCREEN_MODE=$(echo ${{ matrix.screen }} | tr a-z A-Z)" >> $GITHUB_ENV + + - name: Build + run: just build-bootloader ${{ matrix.screen }} + + - name: Upload bootloader + uses: actions/upload-artifact@v2 + with: + name: bootloader-${{ env.SCREEN_MODE }}.bin + path: ports/stm32/boards/Passport/bootloader/arm/release/bootloader-${{ env.SCREEN_MODE }}.bin build-simulator: name: Build Simulator @@ -180,24 +180,24 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - - - name: Build - run: just build-simulator ${{ matrix.screen }} + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + + - name: Build + run: just build-simulator ${{ matrix.screen }} build-tools: name: Build Tools @@ -211,21 +211,21 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 - with: - driver-opts: network=host - - uses: docker/build-push-action@v4 - with: - push: true - context: . - cache-from: type=gha - cache-to: type=gha - tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb - - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - - - name: Build - run: just tools + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - uses: docker/build-push-action@v4 + with: + push: true + context: . + cache-from: type=gha + cache-to: type=gha + tags: localhost:5000/foundation-devices/passport2:latest + - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV + + - name: Build + run: just tools From 5ce079ab7cc578ceece01a0bc18cf9b82cec1aba Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 21 Sep 2023 11:45:36 -0400 Subject: [PATCH 065/239] SFT-2237: refined account limit system --- .../Passport/modules/flows/new_account_flow.py | 6 +++--- .../Passport/modules/tasks/rename_account_task.py | 2 +- ports/stm32/boards/Passport/modules/ui/ui.py | 12 ++++-------- ports/stm32/boards/Passport/modules/utils.py | 13 +++++++++---- .../stm32/boards/Passport/modules/wallets/utils.py | 7 +++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/new_account_flow.py b/ports/stm32/boards/Passport/modules/flows/new_account_flow.py index 8dfab0e48..0295794cd 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_account_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_account_flow.py @@ -8,7 +8,7 @@ import microns from pages import ErrorPage, SuccessPage, TextInputPage, ErrorPage from tasks import save_new_account_task -from utils import get_account_by_name, get_account_by_number, get_accounts, spinner_task +from utils import get_account_by_name, get_account_by_number, get_accounts_by_xfp, spinner_task from translations import t, T from wallets.utils import get_next_account_num from common import settings @@ -17,7 +17,8 @@ class NewAccountFlow(Flow): def __init__(self): initial_state = self.enter_account_num - self.accounts = get_accounts() + xfp = settings.get('xfp') + self.accounts = get_accounts_by_xfp(xfp) if len(self.accounts) >= MAX_ACCOUNTS: initial_state = self.show_account_limit_message @@ -25,7 +26,6 @@ def __init__(self): self.error = None # Suggest the next available account number to the user - xfp = settings.get('xfp') self.account_num = get_next_account_num(xfp) self.account_name = None diff --git a/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py b/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py index a38233bf6..58d461289 100644 --- a/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py @@ -11,7 +11,7 @@ async def rename_account_task(on_done, account_num, account_name, xfp): accounts = get_accounts() for account in accounts: - if account.get('acct_num') == account_num and account.get('xfp') == xfp: + if account.get('acct_num') == account_num and (account.get('xfp') == xfp or account_num == 0): account['name'] = account_name break diff --git a/ports/stm32/boards/Passport/modules/ui/ui.py b/ports/stm32/boards/Passport/modules/ui/ui.py index 0520cc6b0..fd0d8afd4 100644 --- a/ports/stm32/boards/Passport/modules/ui/ui.py +++ b/ports/stm32/boards/Passport/modules/ui/ui.py @@ -200,7 +200,7 @@ def update_cards(self, is_init=False, stay_on_last_card=False): from flows import MenuFlow - from utils import get_accounts, has_seed, is_extension_enabled, escape_text + from utils import get_accounts_by_xfp, has_seed, is_extension_enabled, escape_text from menus import account_menu, plus_menu from extensions.extensions import supported_extensions from constants import MAX_ACCOUNTS @@ -214,8 +214,9 @@ def update_cards(self, card_descs = [settings_card] # Add the account cards - accounts = get_accounts() - accounts = accounts[:MAX_ACCOUNTS] + xfp = common.settings.get('xfp') + accounts = get_accounts_by_xfp(xfp) + # accounts = accounts[:MAX_ACCOUNTS] new_card_idx = None @@ -240,8 +241,6 @@ def update_cards(self, elif stay_on_same_card: new_card_idx = self.active_card_idx - xfp = common.settings.get('xfp') - # Only add these once there is a seed if has_seed(): import stash @@ -249,9 +248,6 @@ def update_cards(self, account = accounts[i] # print('account[{}]={}'.format(account, i)) - if account.get('xfp', xfp) != xfp and account['acct_num'] != 0: - continue - account_card = { 'right_icon': 'ICON_BITCOIN', 'header_color': LIGHT_GREY, diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 01d354d7a..690f375a3 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -875,19 +875,24 @@ def get_accounts(): return accounts -def get_account_by_name(name, xfp): +def get_accounts_by_xfp(xfp): accounts = get_accounts() + return [acct for acct in accounts if acct['xfp'] == xfp or acct['acct_num'] == 0] + + +def get_account_by_name(name, xfp): + accounts = get_accounts_by_xfp(xfp) for account in accounts: - if account.get('name') == name and account.get('xfp') == xfp: + if account.get('name') == name: return account return None def get_account_by_number(acct_num, xfp): - accounts = get_accounts() + accounts = get_accounts_by_xfp(xfp) for account in accounts: - if account.get('acct_num') == acct_num and account.get('xfp') == xfp: + if account.get('acct_num') == acct_num: return account return None diff --git a/ports/stm32/boards/Passport/modules/wallets/utils.py b/ports/stm32/boards/Passport/modules/wallets/utils.py index d5896b127..0ba59e0aa 100644 --- a/ports/stm32/boards/Passport/modules/wallets/utils.py +++ b/ports/stm32/boards/Passport/modules/wallets/utils.py @@ -7,17 +7,16 @@ import chains import common from public_constants import AF_CLASSIC, AF_P2SH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, AF_P2WPKH, AF_P2WSH -from utils import get_accounts, get_derived_keys +from utils import get_accounts_by_xfp, get_derived_keys # Dynamically find the next account number rather than storing it - we never want to skip an account number # since that would create gaps and potentially make recovering funds harder if we exceeded the gap limit. def get_next_account_num(xfp): - accts = get_accounts() + accounts = get_accounts_by_xfp(xfp) acct_nums = [] - current_accounts = [acct for acct in accts if acct['xfp'] == xfp or acct['acct_num'] == 0] - for acct in current_accounts: + for acct in accounts: acct_nums.append(acct['acct_num']) acct_nums.sort() From 1bb54d994a7c80c5fe4cd31c37e4a0e0589dc9c5 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 25 Sep 2023 14:10:01 -0400 Subject: [PATCH 066/239] SFT-2237: allowed multiple names for account 0 --- .../Passport/modules/tasks/rename_account_task.py | 5 ++++- ports/stm32/boards/Passport/modules/ui/ui.py | 10 +++++++++- ports/stm32/boards/Passport/modules/utils.py | 7 +++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py b/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py index 58d461289..7eb13bf53 100644 --- a/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/rename_account_task.py @@ -11,9 +11,12 @@ async def rename_account_task(on_done, account_num, account_name, xfp): accounts = get_accounts() for account in accounts: - if account.get('acct_num') == account_num and (account.get('xfp') == xfp or account_num == 0): + if account.get('acct_num') == account_num and account.get('xfp') == xfp: account['name'] = account_name break + else: + # If account wasn't found, it's likely an unsaved default account being renamed + accounts.append({'acct_num': account_num, 'name': account_name, 'xfp': xfp}) settings.set('accounts', accounts) settings.save() diff --git a/ports/stm32/boards/Passport/modules/ui/ui.py b/ports/stm32/boards/Passport/modules/ui/ui.py index fd0d8afd4..73dcee0c1 100644 --- a/ports/stm32/boards/Passport/modules/ui/ui.py +++ b/ports/stm32/boards/Passport/modules/ui/ui.py @@ -203,7 +203,7 @@ def update_cards(self, from utils import get_accounts_by_xfp, has_seed, is_extension_enabled, escape_text from menus import account_menu, plus_menu from extensions.extensions import supported_extensions - from constants import MAX_ACCOUNTS + from constants import MAX_ACCOUNTS, DEFAULT_ACCOUNT_ENTRY from styles.colors import DARK_GREY, LIGHT_GREY, LIGHT_TEXT, WHITE import microns @@ -218,6 +218,14 @@ def update_cards(self, accounts = get_accounts_by_xfp(xfp) # accounts = accounts[:MAX_ACCOUNTS] + # If current xfp doesn't have a saved account 0, add it + print(accounts) + acct_0 = [acct for acct in accounts if acct.get('acct_num') == 0] + if len(acct_0) == 0: + default = DEFAULT_ACCOUNT_ENTRY + default['xfp'] = xfp + accounts.insert(0, default) + new_card_idx = None # print('len(self.card_descs)={} len(accounts)={}'.format(len(self.card_descs), len(accounts))) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 690f375a3..aa277191c 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -870,14 +870,17 @@ def folder_exists(path): def get_accounts(): from common import settings from constants import DEFAULT_ACCOUNT_ENTRY - accounts = settings.get('accounts', [DEFAULT_ACCOUNT_ENTRY]) + default = DEFAULT_ACCOUNT_ENTRY + default['xfp'] = settings.get('root_xfp') + accounts = settings.get('accounts', [default]) accounts.sort(key=lambda a: a.get('acct_num', 0)) + print(accounts) return accounts def get_accounts_by_xfp(xfp): accounts = get_accounts() - return [acct for acct in accounts if acct['xfp'] == xfp or acct['acct_num'] == 0] + return [acct for acct in accounts if acct.get('xfp', None) == xfp] def get_account_by_name(name, xfp): From 3e306da2145687bba5814a4cbb2bd501d231a294 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 25 Sep 2023 19:58:49 -0400 Subject: [PATCH 067/239] SFT-2237: removed debug and commented code --- ports/stm32/boards/Passport/modules/ui/ui.py | 4 +--- ports/stm32/boards/Passport/modules/utils.py | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/ui/ui.py b/ports/stm32/boards/Passport/modules/ui/ui.py index 73dcee0c1..c097b6ced 100644 --- a/ports/stm32/boards/Passport/modules/ui/ui.py +++ b/ports/stm32/boards/Passport/modules/ui/ui.py @@ -203,7 +203,7 @@ def update_cards(self, from utils import get_accounts_by_xfp, has_seed, is_extension_enabled, escape_text from menus import account_menu, plus_menu from extensions.extensions import supported_extensions - from constants import MAX_ACCOUNTS, DEFAULT_ACCOUNT_ENTRY + from constants import DEFAULT_ACCOUNT_ENTRY from styles.colors import DARK_GREY, LIGHT_GREY, LIGHT_TEXT, WHITE import microns @@ -216,10 +216,8 @@ def update_cards(self, # Add the account cards xfp = common.settings.get('xfp') accounts = get_accounts_by_xfp(xfp) - # accounts = accounts[:MAX_ACCOUNTS] # If current xfp doesn't have a saved account 0, add it - print(accounts) acct_0 = [acct for acct in accounts if acct.get('acct_num') == 0] if len(acct_0) == 0: default = DEFAULT_ACCOUNT_ENTRY diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index aa277191c..a805ef2ef 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -874,7 +874,6 @@ def get_accounts(): default['xfp'] = settings.get('root_xfp') accounts = settings.get('accounts', [default]) accounts.sort(key=lambda a: a.get('acct_num', 0)) - print(accounts) return accounts From 49021653a265852aa41c4f0827d4254383d9b7a9 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 2 Oct 2023 11:34:13 -0400 Subject: [PATCH 068/239] SFT-2237: removed default initialization of accounts list --- ports/stm32/boards/Passport/modules/flows/main_flow.py | 2 +- ports/stm32/boards/Passport/modules/utils.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/main_flow.py b/ports/stm32/boards/Passport/modules/flows/main_flow.py index b00bb5f82..a9901c2d3 100644 --- a/ports/stm32/boards/Passport/modules/flows/main_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/main_flow.py @@ -36,7 +36,7 @@ async def start(self): del next_addrs[key] common.settings.set('next_addrs', next_addrs) - # Update account settings to include a show_all xfp + # Update account settings to include the default xfp accounts = get_accounts() for i in range(len(accounts)): account = accounts[i] diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index a805ef2ef..55e671097 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -869,10 +869,7 @@ def folder_exists(path): def get_accounts(): from common import settings - from constants import DEFAULT_ACCOUNT_ENTRY - default = DEFAULT_ACCOUNT_ENTRY - default['xfp'] = settings.get('root_xfp') - accounts = settings.get('accounts', [default]) + accounts = settings.get('accounts', []) accounts.sort(key=lambda a: a.get('acct_num', 0)) return accounts From 07613782e24f1c2072d51f361d008ba05d427b7d Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 10 Oct 2023 11:55:24 -0400 Subject: [PATCH 069/239] SFT-2835: fixed micron bar indexing after single seed word list page --- .../boards/Passport/modules/pages/seed_words_list_page.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/pages/seed_words_list_page.py b/ports/stm32/boards/Passport/modules/pages/seed_words_list_page.py index d15ba30ac..95ff2643d 100644 --- a/ports/stm32/boards/Passport/modules/pages/seed_words_list_page.py +++ b/ports/stm32/boards/Passport/modules/pages/seed_words_list_page.py @@ -135,11 +135,12 @@ def update(self): if self.is_mounted(): self.container.mount_children() - # Update the page micron index - common.ui.set_micron_bar_active_idx(self.page_idx) - # Update microns if self.num_pages > 1: + + # Update the page micron index + common.ui.set_micron_bar_active_idx(self.page_idx) + if self.page_idx == 0: common.ui.set_left_micron(self.left_micron) common.ui.set_right_micron(microns.Forward) From a08004ca307c783f6dadc9307409d0efc58e70b4 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 10 Oct 2023 19:12:01 -0400 Subject: [PATCH 070/239] SFT-2851: skipped sig type chooser page if there's one option --- .../modules/flows/verify_address_flow.py | 8 ++++++-- .../boards/Passport/modules/multisig_wallet.py | 13 +++++++++++++ .../pages/singlesig_multisig_chooser_page.py | 16 ++++------------ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py index bfacfd08b..7340dddf9 100644 --- a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py @@ -48,13 +48,17 @@ def __init__(self, sig_type=None, multisig_wallet=None): async def choose_sig_type(self): from pages import SinglesigMultisigChooserPage from multisig_wallet import MultisigWallet + from common import settings + + xfp = settings.get('xfp') + multisigs = MultisigWallet.get_by_xfp(xfp) - if MultisigWallet.get_count() == 0: + if len(multisigs) == 0: self.sig_type = 'single-sig' self.goto(self.scan_address, save_curr=False) # Don't save this since we're skipping this state else: result = await SinglesigMultisigChooserPage( - initial_value=self.sig_type).show() + initial_value=self.sig_type, multisigs=multisigs).show() if result is None: if not self.back(): self.set_result(False) diff --git a/ports/stm32/boards/Passport/modules/multisig_wallet.py b/ports/stm32/boards/Passport/modules/multisig_wallet.py index 46059e4d1..0dd325782 100644 --- a/ports/stm32/boards/Passport/modules/multisig_wallet.py +++ b/ports/stm32/boards/Passport/modules/multisig_wallet.py @@ -367,6 +367,19 @@ def get_by_id(cls, id): return None + @classmethod + def get_by_xfp(cls, xfp): + lst = cls.get_all() + lst_by_xfp = [] + + for ms in lst: + for xpub in ms.xpubs: + if xfp == xpub[0]: # XFP entry in the multisig's xpub tuple + lst_by_xfp.append(ms) + break + + return lst_by_xfp + @classmethod def delete_by_id(cls, id): from utils import to_str diff --git a/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py index aa06d33fd..9ed5c7c70 100644 --- a/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py @@ -8,20 +8,12 @@ class SinglesigMultisigChooserPage(ChooserPage): - def __init__(self, card_header={'title': 'Single/Multisig'}, initial_value=None): - from multisig_wallet import MultisigWallet - from common import settings - + def __init__(self, multisigs, card_header={'title': 'Single/Multisig'}, initial_value=None): options = [{'label': 'Single-sig', 'value': ('single-sig', None)}] - xfp = settings.get('xfp') - - for ms in MultisigWallet.get_all(): - for xpub in ms.xpubs: - if xfp == xpub[0]: # XFP entry in the multisig's xpub tuple - label = '%d/%d: %s' % (ms.M, ms.N, ms.name) - options.append({'label': label, 'value': ('multisig', ms)}) - continue + for ms in multisigs: + label = '%d/%d: %s' % (ms.M, ms.N, ms.name) + options.append({'label': label, 'value': ('multisig', ms)}) super().__init__( card_header=card_header, From 886c474cbd0b42b34afe76d31ec4b040288a027a Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 10 Oct 2023 12:58:16 -0400 Subject: [PATCH 071/239] SFT-2846: only see multisig configs relevant to the current passphrase/xfp --- ports/stm32/boards/Passport/modules/menus.py | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index bfb0961d0..5c28a785d 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -298,23 +298,28 @@ def multisig_menu(): from pages import MultisigPolicySettingPage, ErrorPage from flows import ImportMultisigWalletFromMicroSDFlow, ImportMultisigWalletFromQRFlow from utils import escape_text + from common import settings if not MultisigWallet.exists(): items = [{'icon': 'ICON_TWO_KEYS', 'label': '(None setup yet)', 'page': ErrorPage, 'args': {'text': "You haven't imported any multisig wallets yet."}}] else: items = [] + xfp = settings.get('xfp') for ms in MultisigWallet.get_all(): - nice_name = '%d/%d: %s' % (ms.M, ms.N, escape_text(ms.name)) - items.append({ - 'icon': 'ICON_TWO_KEYS', - 'label': nice_name, - 'submenu': multisig_item_menu, - # Adding this below causes the header to stick around after it shoudl be gone - # Probably need MenuFlow() to pop it off after - # 'args': {'card_header': {'title': nice_name}, 'context': ms.storage_idx} - 'args': {'context': ms.storage_idx} - }) + for xpub in ms.xpubs: + if xfp == xpub[0]: # XFP entry in the multisig's xpub tuple + nice_name = '%d/%d: %s' % (ms.M, ms.N, escape_text(ms.name)) + items.append({ + 'icon': 'ICON_TWO_KEYS', + 'label': nice_name, + 'submenu': multisig_item_menu, + # Adding this below causes the header to stick around after it shoudl be gone + # Probably need MenuFlow() to pop it off after + # 'args': {'card_header': {'title': nice_name}, 'context': ms.storage_idx} + 'args': {'context': ms.storage_idx} + }) + break items.append({'icon': 'ICON_SCAN_QR', 'label': 'Import from QR', 'flow': ImportMultisigWalletFromQRFlow, 'statusbar': {'title': 'IMPORT'}}) From 2efdaa5d885d89af7d1259be51cdeebfc5ba5c31 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 11 Oct 2023 16:15:57 -0400 Subject: [PATCH 072/239] SFT-2486: simplified multisig per-xfp display --- ports/stm32/boards/Passport/modules/menus.py | 31 ++++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index 5c28a785d..fd64c03be 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -300,26 +300,25 @@ def multisig_menu(): from utils import escape_text from common import settings - if not MultisigWallet.exists(): + xfp = settings.get('xfp') + multisigs = MultisigWallet.get_by_xfp(xfp) + + if len(multisigs) == 0: items = [{'icon': 'ICON_TWO_KEYS', 'label': '(None setup yet)', 'page': ErrorPage, 'args': {'text': "You haven't imported any multisig wallets yet."}}] else: items = [] - xfp = settings.get('xfp') - for ms in MultisigWallet.get_all(): - for xpub in ms.xpubs: - if xfp == xpub[0]: # XFP entry in the multisig's xpub tuple - nice_name = '%d/%d: %s' % (ms.M, ms.N, escape_text(ms.name)) - items.append({ - 'icon': 'ICON_TWO_KEYS', - 'label': nice_name, - 'submenu': multisig_item_menu, - # Adding this below causes the header to stick around after it shoudl be gone - # Probably need MenuFlow() to pop it off after - # 'args': {'card_header': {'title': nice_name}, 'context': ms.storage_idx} - 'args': {'context': ms.storage_idx} - }) - break + for ms in multisigs: + nice_name = '%d/%d: %s' % (ms.M, ms.N, escape_text(ms.name)) + items.append({ + 'icon': 'ICON_TWO_KEYS', + 'label': nice_name, + 'submenu': multisig_item_menu, + # Adding this below causes the header to stick around after it shoudl be gone + # Probably need MenuFlow() to pop it off after + # 'args': {'card_header': {'title': nice_name}, 'context': ms.storage_idx} + 'args': {'context': ms.storage_idx} + }) items.append({'icon': 'ICON_SCAN_QR', 'label': 'Import from QR', 'flow': ImportMultisigWalletFromQRFlow, 'statusbar': {'title': 'IMPORT'}}) From 787374fde8ad093c5ed7c6aa7e05496d8522a9ad Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 12 Sep 2023 18:16:56 -0400 Subject: [PATCH 073/239] SFT-1708: first pass at taproot address verification, incorrect public key generating --- extmod/foundation/modtcc-codecs.c | 4 ++++ ports/stm32/boards/Passport/modules/chains.py | 4 +++- .../Passport/modules/public_constants.py | 3 +++ .../modules/tasks/search_for_address_task.py | 3 +++ .../boards/Passport/modules/wallets/utils.py | 18 +++++++++++++++--- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/extmod/foundation/modtcc-codecs.c b/extmod/foundation/modtcc-codecs.c index 38af7781d..dc6a54801 100644 --- a/extmod/foundation/modtcc-codecs.c +++ b/extmod/foundation/modtcc-codecs.c @@ -113,6 +113,10 @@ STATIC mp_obj_t modtcc_bech32_encode(size_t n_args, const mp_obj_t *args) { const uint32_t segwit_version = mp_obj_int_get_checked(args[1]); uint32_t bech32_version = BECH32_ENCODING_BECH32; + if (segwit_version > 0) { + bech32_version = BECH32_ENCODING_BECH32M; + } + if (n_args == 4) { bech32_version = mp_obj_int_get_checked(args[3]); } diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 21cf3172c..a892a61d2 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -12,7 +12,7 @@ import trezorcrypto import tcc from public_constants import AF_CLASSIC, AF_P2SH, AF_P2WPKH, AF_P2WSH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH -from public_constants import AFC_PUBKEY, AFC_SEGWIT, AFC_BECH32, AFC_SCRIPT, AFC_WRAPPED +from public_constants import AFC_PUBKEY, AFC_SEGWIT, AFC_BECH32, AFC_SCRIPT, AFC_WRAPPED, AFC_BECH32M from serializations import hash160, ser_compact_size from ucollections import namedtuple from opcodes import OP_CHECKMULTISIG @@ -116,6 +116,8 @@ def address(cls, node, addr_fmt): if addr_fmt & AFC_BECH32: # bech32 encoded segwit p2pkh return tcc.codecs.bech32_encode(cls.bech32_hrp, 0, raw) + elif addr_fmt & AFC_BECH32M: + return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, raw) # see bip-141, "P2WPKH nested in BIP16 P2SH" section assert addr_fmt == AF_P2WPKH_P2SH diff --git a/ports/stm32/boards/Passport/modules/public_constants.py b/ports/stm32/boards/Passport/modules/public_constants.py index 13f983a7d..077462207 100644 --- a/ports/stm32/boards/Passport/modules/public_constants.py +++ b/ports/stm32/boards/Passport/modules/public_constants.py @@ -50,6 +50,7 @@ AFC_BECH32 = const(0x04) # just how we're encoding it? AFC_SCRIPT = const(0x08) # paying into a script AFC_WRAPPED = const(0x10) # for transition/compat types for segwit vs. old +AFC_BECH32M = const(0x20) # bech32m encoding # Numeric codes for specific address types AF_CLASSIC = AFC_PUBKEY # 1addr @@ -58,6 +59,7 @@ AF_P2WSH = AFC_SCRIPT | AFC_SEGWIT | AFC_BECH32 # segwit multisig AF_P2WPKH_P2SH = AFC_WRAPPED | AFC_PUBKEY | AFC_SEGWIT # looks classic P2SH, but p2wpkh inside AF_P2WSH_P2SH = AFC_WRAPPED | AFC_SCRIPT | AFC_SEGWIT # looks classic P2SH, segwit multisig +AF_P2TR = AFC_PUBKEY | AFC_SEGWIT | AFC_BECH32M # taproot allows scripts and pubkeys SUPPORTED_ADDR_FORMATS = frozenset([ AF_CLASSIC, @@ -66,6 +68,7 @@ AF_P2WSH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, + AF_P2TR, ]) # BIP-174 aka PSBT defined values diff --git a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py index b8c813041..b2ace014f 100644 --- a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py @@ -24,6 +24,7 @@ async def search_for_address_task( import stash from errors import Error from uasyncio import sleep_ms + from ubinascii import hexlify as b2a_hex try: with stash.SensitiveValues() as sv: @@ -50,9 +51,11 @@ async def search_for_address_task( addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address # print('Singlesig: addr_path={}'.format(addr_path)) node = sv.derive_path(addr_path) + print(b2a_hex(node.public_key())) curr_address = sv.chain.address(node, addr_type) # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, # addr_type, curr_address)) + print("path: {}, address: {}".format(addr_path, curr_address)) if curr_address == address: await on_done(curr_idx, addr_path, None) return diff --git a/ports/stm32/boards/Passport/modules/wallets/utils.py b/ports/stm32/boards/Passport/modules/wallets/utils.py index 0ba59e0aa..94eb16181 100644 --- a/ports/stm32/boards/Passport/modules/wallets/utils.py +++ b/ports/stm32/boards/Passport/modules/wallets/utils.py @@ -6,7 +6,7 @@ import chains import common -from public_constants import AF_CLASSIC, AF_P2SH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, AF_P2WPKH, AF_P2WSH +from public_constants import AF_CLASSIC, AF_P2SH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, AF_P2WPKH, AF_P2WSH, AF_P2TR from utils import get_accounts_by_xfp, get_derived_keys @@ -72,7 +72,10 @@ def get_addr_type_from_address(address, is_multisig): return AF_P2WSH_P2SH if is_multisig else AF_P2WPKH_P2SH elif (address[0] == 'b' and address[1] == 'c' and address[2] == '1') or \ (address[0] == 't' and address[1] == 'b' and address[2] == '1'): - return AF_P2WSH if is_multisig else AF_P2WPKH + if address[3] == 'p': + return AF_P2TR + else: + return AF_P2WSH if is_multisig else AF_P2WPKH return None @@ -90,6 +93,8 @@ def get_bip_num_from_addr_type(addr_type, is_multisig): return 84 elif addr_type == AF_P2WPKH_P2SH: return 49 + elif addr_type == AF_P2TR: + return 86 else: raise ValueError(addr_type) @@ -109,6 +114,8 @@ def get_addr_type_from_deriv(path): return AF_P2WSH_P2SH elif subpath == '2': return AF_P2WSH + elif type_str == '86': + return AF_P2TR return None @@ -132,7 +139,10 @@ def get_deriv_fmt_from_address(address, is_multisig): return "m/49'/{coin_type}'/{acct}'" elif ((address[0] == 'b' and address[1] == 'c' and address[2] == '1') or (address[0] == 't' and address[1] == 'b' and address[2] == '1')): - return "m/84'/{coin_type}'/{acct}'" + if address[3] == 'p': + return "m/86'/{coin_type}'/{acct}'" + else: + return "m/84'/{coin_type}'/{acct}'" return None @@ -153,6 +163,8 @@ def get_deriv_fmt_from_addr_type(addr_type, is_multisig): return "m/49'/{coin_type}'/{acct}'" elif addr_type == AF_P2WPKH: return "m/84'/{coin_type}'/{acct}'" + elif addr_type == AF_P2TR: + return "m/86'/{coin_type}'/{acct}'" return None From 00266aaa456a0085127381869c6154c3543f3b78 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 26 Sep 2023 14:39:15 -0400 Subject: [PATCH 074/239] SFT-1708: added ecdsa function to get x coordinate of a pubkey, started other functions needed to BIP86 --- .../modtrezorcrypto/modtrezorcrypto-ecdsa.h | 67 +++++++++++++++++ .../extmod/modtrezorcrypto/modtrezorcrypto.c | 3 +- extmod/trezor-firmware/crypto/ecdsa.c | 12 ++++ extmod/trezor-firmware/crypto/ecdsa.h | 1 + ports/stm32/boards/Passport/modules/chains.py | 16 +++++ .../modules/tasks/search_for_address_task.py | 72 +++++++++---------- ports/stm32/boards/Passport/modules/utils.py | 11 +++ 7 files changed, 145 insertions(+), 37 deletions(-) create mode 100644 extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h new file mode 100644 index 000000000..af1752bd2 --- /dev/null +++ b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h @@ -0,0 +1,67 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "py/obj.h" +#include "py/objstr.h" +#include "py/runtime.h" + +#include "secp256k1.h" +#include "ecdsa.h" + +#define X_SIZE 32 + +/// package: trezorcrypto.ecdsa + +/// def get_x(pubkey: bytes) -> bytes: +/// """ +/// Gets the X coordinate of the point corresponding to an ECDSA pubkey +/// """ +STATIC mp_obj_t mod_trezorcrypto_ecdsa_get_x(mp_obj_t pubkey_obj) { + mp_buffer_info_t pubkey; + mp_get_buffer_raise(pubkey_obj, &pubkey, MP_BUFFER_READ); + + vstr_t x_vstr; + vstr_init_len(&x_vstr, X_SIZE); + x_vstr.len = X_SIZE; + + int rv = ecdsa_get_x(&secp256k1, pubkey.buf, (uint8_t *)x_vstr.buf); + if (rv == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("failed to get x")); + } + + return mp_obj_new_str_from_vstr(&mp_type_bytes, &x_vstr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mod_trezorcrypto_ecdsa_get_x_obj, + mod_trezorcrypto_ecdsa_get_x); + +STATIC const mp_rom_map_elem_t mod_trezorcrypto_ecdsa_globals_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ecdsa)}, + {MP_ROM_QSTR(MP_QSTR_get_x), + MP_ROM_PTR(&mod_trezorcrypto_ecdsa_get_x_obj)}, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_ecdsa_globals, + mod_trezorcrypto_ecdsa_globals_table); + +STATIC const mp_obj_module_t mod_trezorcrypto_ecdsa_module = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&mod_trezorcrypto_ecdsa_globals, +}; diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c index 9f16b0924..47f07762b 100644 --- a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c +++ b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c @@ -46,6 +46,7 @@ static void wrapped_ui_wait_callback(uint32_t current, uint32_t total) { #include "modtrezorcrypto-chacha20poly1305.h" #include "modtrezorcrypto-crc.h" #include "modtrezorcrypto-curve25519.h" +#include "modtrezorcrypto-ecdsa.h" #if USE_KECCAK #include "modtrezorcrypto-ed25519.h" #endif @@ -56,7 +57,6 @@ static void wrapped_ui_wait_callback(uint32_t current, uint32_t total) { #include "modtrezorcrypto-random.h" #include "modtrezorcrypto-ripemd160.h" #include "modtrezorcrypto-secp256k1.h" -#include "modtrezorcrypto-schnorr.h" #include "modtrezorcrypto-sha1.h" #include "modtrezorcrypto-sha256.h" #if USE_KECCAK @@ -79,6 +79,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_aes), MP_ROM_PTR(&mod_trezorcrypto_AES_type)}, {MP_ROM_QSTR(MP_QSTR_bip32), MP_ROM_PTR(&mod_trezorcrypto_bip32_module)}, {MP_ROM_QSTR(MP_QSTR_bip39), MP_ROM_PTR(&mod_trezorcrypto_bip39_module)}, + {MP_ROM_QSTR(MP_QSTR_ecdsa), MP_ROM_PTR(&mod_trezorcrypto_ecdsa_module)}, #ifndef FOUNDATION_ADDITIONS {MP_ROM_QSTR(MP_QSTR_blake256), MP_ROM_PTR(&mod_trezorcrypto_Blake256_type)}, diff --git a/extmod/trezor-firmware/crypto/ecdsa.c b/extmod/trezor-firmware/crypto/ecdsa.c index d5d8bd8aa..07ac3f088 100644 --- a/extmod/trezor-firmware/crypto/ecdsa.c +++ b/extmod/trezor-firmware/crypto/ecdsa.c @@ -962,6 +962,18 @@ int ecdsa_read_pubkey(const ecdsa_curve *curve, const uint8_t *pub_key, return 0; } +int ecdsa_get_x(const ecdsa_curve *curve, const uint8_t *pub_key, uint8_t * x) { + curve_point pub = {0}; + + if (!ecdsa_read_pubkey(curve, pub_key, &pub)) { + return 0; + } + + bn_write_be(&pub.x, x); + + return 1; +} + // Verifies that: // - pub is not the point at infinity. // - pub->x and pub->y are in range [0,p-1]. diff --git a/extmod/trezor-firmware/crypto/ecdsa.h b/extmod/trezor-firmware/crypto/ecdsa.h index 7b951d236..991d0afdc 100644 --- a/extmod/trezor-firmware/crypto/ecdsa.h +++ b/extmod/trezor-firmware/crypto/ecdsa.h @@ -113,6 +113,7 @@ int ecdsa_address_decode(const char *addr, uint32_t version, HasherType hasher_base58, uint8_t *out); int ecdsa_read_pubkey(const ecdsa_curve *curve, const uint8_t *pub_key, curve_point *pub); +int ecdsa_get_x(const ecdsa_curve *curve, const uint8_t *pub_key, uint8_t * x); int ecdsa_validate_pubkey(const ecdsa_curve *curve, const curve_point *pub); int ecdsa_verify(const ecdsa_curve *curve, HasherType hasher_sign, const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index a892a61d2..e147be668 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -117,6 +117,22 @@ def address(cls, node, addr_fmt): # bech32 encoded segwit p2pkh return tcc.codecs.bech32_encode(cls.bech32_hrp, 0, raw) elif addr_fmt & AFC_BECH32M: + from utils import precomputed_tagged_hash + from ubinascii import unhexlify as a2b_hex + from ubinascii import hexlify as b2a_hex + from trezorcrypto import ecdsa + + print("here 1") + tap_tweak_sha256 = a2b_hex("e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9") + print("here 2") + + x = ecdsa.get_x(node.public_key()) + print("here 3") + print(b2a_hex(x)) + print("here 4") + hash_tap_tweaked_pubkey = precomputed_tagged_hash(tap_tweak_sha256, x) + # point = lift_x(pubkey) + hash_tap_tweaked_pubkey * secp256k1_generator() + # tweaked_pubkey = point.x() return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, raw) # see bip-141, "P2WPKH nested in BIP16 P2SH" section diff --git a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py index b2ace014f..ba284d798 100644 --- a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py @@ -26,43 +26,43 @@ async def search_for_address_task( from uasyncio import sleep_ms from ubinascii import hexlify as b2a_hex - try: - with stash.SensitiveValues() as sv: - if multisig_wallet: - # NOTE: Can't easily reverse order here, so this is slightly less efficient - for (curr_idx, paths, curr_address, script) in multisig_wallet.yield_addresses( - start_address_idx, - max_to_check, - change_idx=1 if is_change else 0): - # print('Multisig: curr_idx={}: paths={} curr_address = {}'.format(curr_idx, paths, curr_address)) + # try: + with stash.SensitiveValues() as sv: + if multisig_wallet: + # NOTE: Can't easily reverse order here, so this is slightly less efficient + for (curr_idx, paths, curr_address, script) in multisig_wallet.yield_addresses( + start_address_idx, + max_to_check, + change_idx=1 if is_change else 0): + # print('Multisig: curr_idx={}: paths={} curr_address = {}'.format(curr_idx, paths, curr_address)) - if curr_address == address: - # NOTE: Paths are the full paths of the addresses of each signer - await on_done(curr_idx, paths, None) - return - await sleep_ms(1) + if curr_address == address: + # NOTE: Paths are the full paths of the addresses of each signer + await on_done(curr_idx, paths, None) + return + await sleep_ms(1) - else: - r = range(start_address_idx, start_address_idx + max_to_check) - if reverse: - r = reversed(r) + else: + r = range(start_address_idx, start_address_idx + max_to_check) + if reverse: + r = reversed(r) - for curr_idx in r: - addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address - # print('Singlesig: addr_path={}'.format(addr_path)) - node = sv.derive_path(addr_path) - print(b2a_hex(node.public_key())) - curr_address = sv.chain.address(node, addr_type) - # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, - # addr_type, curr_address)) - print("path: {}, address: {}".format(addr_path, curr_address)) - if curr_address == address: - await on_done(curr_idx, addr_path, None) - return - await sleep_ms(1) + for curr_idx in r: + addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address + # print('Singlesig: addr_path={}'.format(addr_path)) + node = sv.derive_path(addr_path) + print(b2a_hex(node.public_key())) + curr_address = sv.chain.address(node, addr_type) + # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, + # addr_type, curr_address)) + print("path: {}, address: {}".format(addr_path, curr_address)) + if curr_address == address: + await on_done(curr_idx, addr_path, None) + return + await sleep_ms(1) - await on_done(-1, None, Error.ADDRESS_NOT_FOUND) - except Exception as e: - # print('EXCEPTION: e={}'.format(e)) - # Any address handling exceptions result in no address found - await on_done(-1, None, Error.ADDRESS_NOT_FOUND) + await on_done(-1, None, Error.ADDRESS_NOT_FOUND) + # except Exception as e: + # # print('EXCEPTION: e={}'.format(e)) + # # Any address handling exceptions result in no address found + # await on_done(-1, None, Error.ADDRESS_NOT_FOUND) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 55e671097..b21363c86 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1442,4 +1442,15 @@ def escape_text(text): return text.replace("#", "##") +def precomputed_tagged_hash(tag_hash, data): + from serializations import sha256 + + return sha256(tag_hash + tag_hash + data) + + +def lift_x(pubkey): + field_size = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F + x = pubkey + + # EOF From b70ace4bd458aa51467d6cdc5256d0107c3633f0 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 27 Sep 2023 16:47:00 -0400 Subject: [PATCH 075/239] SFT-1708: trying to use existing bip340 micropython module --- .../extmod/modtrezorcrypto/modtrezorcrypto.c | 2 +- ports/stm32/boards/Passport/modules/chains.py | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c index 47f07762b..9ac65d2f1 100644 --- a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c +++ b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c @@ -118,8 +118,8 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { MP_ROM_PTR(&mod_trezorcrypto_Ripemd160_type)}, {MP_ROM_QSTR(MP_QSTR_secp256k1), MP_ROM_PTR(&mod_trezorcrypto_secp256k1_module)}, -#ifndef FOUNDATION_ADDITIONS {MP_ROM_QSTR(MP_QSTR_bip340), MP_ROM_PTR(&mod_trezorcrypto_bip340_module)}, +#ifndef FOUNDATION_ADDITIONS {MP_ROM_QSTR(MP_QSTR_sha1), MP_ROM_PTR(&mod_trezorcrypto_Sha1_type)}, #endif // FOUNDATION_ADDITIONS {MP_ROM_QSTR(MP_QSTR_sha256), MP_ROM_PTR(&mod_trezorcrypto_Sha256_type)}, diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index e147be668..4e1732c5a 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -117,20 +117,24 @@ def address(cls, node, addr_fmt): # bech32 encoded segwit p2pkh return tcc.codecs.bech32_encode(cls.bech32_hrp, 0, raw) elif addr_fmt & AFC_BECH32M: - from utils import precomputed_tagged_hash - from ubinascii import unhexlify as a2b_hex + # from utils import precomputed_tagged_hash + # from ubinascii import unhexlify as a2b_hex from ubinascii import hexlify as b2a_hex - from trezorcrypto import ecdsa + # from trezorcrypto import ecdsa + from trezorcrypto import bip340 - print("here 1") - tap_tweak_sha256 = a2b_hex("e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9") - print("here 2") + tweaked = bip340.tweak_public_key(node.public_key()) + print(b2a_hex(tweaked)) - x = ecdsa.get_x(node.public_key()) - print("here 3") - print(b2a_hex(x)) - print("here 4") - hash_tap_tweaked_pubkey = precomputed_tagged_hash(tap_tweak_sha256, x) + # print("here 1") + # tap_tweak_sha256 = a2b_hex("e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9") + # print("here 2") + + # x = ecdsa.get_x(node.public_key()) + # print("here 3") + # print(b2a_hex(x)) + # print("here 4") + # hash_tap_tweaked_pubkey = precomputed_tagged_hash(tap_tweak_sha256, x) # point = lift_x(pubkey) + hash_tap_tweaked_pubkey * secp256k1_generator() # tweaked_pubkey = point.x() return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, raw) From 31feee8fd8ac5f2e59aff0568d2a648d91b49db5 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 2 Oct 2023 15:20:44 -0400 Subject: [PATCH 076/239] SFT-1708: changing directions to use rust secp256k1 to support taproot address generation --- .../core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c | 2 +- ports/stm32/boards/Passport/modules/chains.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c index 9ac65d2f1..47f07762b 100644 --- a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c +++ b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c @@ -118,8 +118,8 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { MP_ROM_PTR(&mod_trezorcrypto_Ripemd160_type)}, {MP_ROM_QSTR(MP_QSTR_secp256k1), MP_ROM_PTR(&mod_trezorcrypto_secp256k1_module)}, - {MP_ROM_QSTR(MP_QSTR_bip340), MP_ROM_PTR(&mod_trezorcrypto_bip340_module)}, #ifndef FOUNDATION_ADDITIONS + {MP_ROM_QSTR(MP_QSTR_bip340), MP_ROM_PTR(&mod_trezorcrypto_bip340_module)}, {MP_ROM_QSTR(MP_QSTR_sha1), MP_ROM_PTR(&mod_trezorcrypto_Sha1_type)}, #endif // FOUNDATION_ADDITIONS {MP_ROM_QSTR(MP_QSTR_sha256), MP_ROM_PTR(&mod_trezorcrypto_Sha256_type)}, diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 4e1732c5a..774abed7d 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -117,14 +117,11 @@ def address(cls, node, addr_fmt): # bech32 encoded segwit p2pkh return tcc.codecs.bech32_encode(cls.bech32_hrp, 0, raw) elif addr_fmt & AFC_BECH32M: - # from utils import precomputed_tagged_hash - # from ubinascii import unhexlify as a2b_hex + from ubinascii import unhexlify as a2b_hex from ubinascii import hexlify as b2a_hex - # from trezorcrypto import ecdsa - from trezorcrypto import bip340 - tweaked = bip340.tweak_public_key(node.public_key()) - print(b2a_hex(tweaked)) + # tweaked = bip340.tweak_public_key(node.public_key()) + # print(b2a_hex(tweaked)) # print("here 1") # tap_tweak_sha256 = a2b_hex("e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9") From 47a5ec9c3d8674799bdaf22a0e547bae32cf1721 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 4 Oct 2023 20:18:03 -0400 Subject: [PATCH 077/239] SFT-1708: used rust secp256k1 to get x-only pubkey --- extmod/foundation-rust/include/foundation.h | 10 +++++++++ extmod/foundation-rust/src/secp256k1.rs | 17 ++++++++++++++- extmod/foundation/modfoundation-secp56k1.h | 21 +++++++++++++++++++ ports/stm32/boards/Passport/modules/chains.py | 4 +++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/extmod/foundation-rust/include/foundation.h b/extmod/foundation-rust/include/foundation.h index cbb790588..fbb56b178 100644 --- a/extmod/foundation-rust/include/foundation.h +++ b/extmod/foundation-rust/include/foundation.h @@ -371,6 +371,14 @@ void foundation_secp256k1_schnorr_sign(const uint8_t (*data)[32], const uint8_t (*secret_key)[32], uint8_t (*signature)[64]); +/** + * Returns an x-only public key given a public key + * - `pubkey` is the original public key + * - `x_only_pubkey` is the resulting x-only public key + */ +void foundation_secp256k1_x_only_public_key(const uint8_t (*pubkey)[33], + uint8_t (*x_only_pubkey)[32]); + /** * Receive a Uniform Resource part. * @@ -501,6 +509,8 @@ void ur_registry_new_passport_response(UR_Value *value, const char *passport_firmware_version, size_t passport_firmware_version_len); +// TODO: add taproot functions + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/extmod/foundation-rust/src/secp256k1.rs b/extmod/foundation-rust/src/secp256k1.rs index d77f14bd9..41001f5cd 100644 --- a/extmod/foundation-rust/src/secp256k1.rs +++ b/extmod/foundation-rust/src/secp256k1.rs @@ -3,7 +3,7 @@ use once_cell::sync::Lazy; use secp256k1::{ - ffi::types::AlignedType, AllPreallocated, KeyPair, Message, Secp256k1, + ffi::types::AlignedType, AllPreallocated, KeyPair, Message, Secp256k1, constants::PUBLIC_KEY_SIZE, constants::SCHNORR_PUBLIC_KEY_SIZE, PublicKey }; /// cbindgen:ignore @@ -37,6 +37,21 @@ pub extern "C" fn secp256k1_sign_schnorr( signature.copy_from_slice(sig.as_ref()); } +/// Returns an x-only public key given a public key +/// +/// - `pubkey` is the original public key +/// - `x_only_pubkey` is the resulting x-only public key +#[export_name = "foundation_secp256k1_x_only_public_key"] +pub extern "C" fn secp256k1_x_only_public_key( + pubkey: &[u8; PUBLIC_KEY_SIZE], + x_only_pubkey: &mut [u8; SCHNORR_PUBLIC_KEY_SIZE], +) { + let pk_struct = PublicKey::from_slice(pubkey).unwrap(); + let (x_only_struct, _) = pk_struct.x_only_public_key(); + let x_only_bytes = x_only_struct.serialize(); + x_only_pubkey.copy_from_slice(&x_only_bytes) +} + #[cfg(target_arch = "arm")] fn rng() -> crate::rand::PassportRng { crate::rand::PassportRng diff --git a/extmod/foundation/modfoundation-secp56k1.h b/extmod/foundation/modfoundation-secp56k1.h index 2ca47cf97..fa365cd45 100644 --- a/extmod/foundation/modfoundation-secp56k1.h +++ b/extmod/foundation/modfoundation-secp56k1.h @@ -33,8 +33,29 @@ STATIC mp_obj_t mod_foundation_secp256k1_sign_schnorr(mp_obj_t data_obj, STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_foundation_secp256k1_sign_schnorr_obj, mod_foundation_secp256k1_sign_schnorr); +/// def x_only_public_key(public_key) -> x_only_public_key: +/// """ +STATIC mp_obj_t mod_foundation_secp256k1_x_only_public_key(mp_obj_t public_key_obj) +{ + mp_buffer_info_t public_key; + uint8_t x_only_public_key[32]; + + mp_get_buffer_raise(public_key_obj, &public_key, MP_BUFFER_READ); + + if (public_key.len != 33) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("public_key should be 33 bytes")); + } + + foundation_secp256k1_x_only_public_key(public_key.buf, &x_only_public_key); + + return mp_obj_new_bytes(x_only_public_key, sizeof(x_only_public_key)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_foundation_secp256k1_x_only_public_key_obj, + mod_foundation_secp256k1_x_only_public_key); + STATIC const mp_rom_map_elem_t mod_foundation_secp256k1_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_schnorr_sign), MP_ROM_PTR(&mod_foundation_secp256k1_sign_schnorr_obj) }, + { MP_ROM_QSTR(MP_QSTR_x_only_public_key), MP_ROM_PTR(&mod_foundation_secp256k1_x_only_public_key_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mod_foundation_secp256k1_globals, mod_foundation_secp256k1_globals_table); diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 774abed7d..c6973d062 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -119,6 +119,7 @@ def address(cls, node, addr_fmt): elif addr_fmt & AFC_BECH32M: from ubinascii import unhexlify as a2b_hex from ubinascii import hexlify as b2a_hex + from foundation import secp256k1 # tweaked = bip340.tweak_public_key(node.public_key()) # print(b2a_hex(tweaked)) @@ -127,7 +128,8 @@ def address(cls, node, addr_fmt): # tap_tweak_sha256 = a2b_hex("e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9") # print("here 2") - # x = ecdsa.get_x(node.public_key()) + x = secp256k1.x_only_public_key(node.public_key()) + print("x only: {}".format(b2a_hex(x))) # print("here 3") # print(b2a_hex(x)) # print("here 4") From 3aa70669bff5452ad4d84aea4d3d4b696dc5eb27 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 4 Oct 2023 20:22:39 -0400 Subject: [PATCH 078/239] SFT-1708: removed previous get_x function --- extmod/foundation-rust/include/foundation.h | 2 - .../modtrezorcrypto/modtrezorcrypto-ecdsa.h | 67 ------------------- .../extmod/modtrezorcrypto/modtrezorcrypto.c | 2 - extmod/trezor-firmware/crypto/ecdsa.c | 12 ---- extmod/trezor-firmware/crypto/ecdsa.h | 1 - 5 files changed, 84 deletions(-) delete mode 100644 extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h diff --git a/extmod/foundation-rust/include/foundation.h b/extmod/foundation-rust/include/foundation.h index fbb56b178..338c8dab2 100644 --- a/extmod/foundation-rust/include/foundation.h +++ b/extmod/foundation-rust/include/foundation.h @@ -509,8 +509,6 @@ void ur_registry_new_passport_response(UR_Value *value, const char *passport_firmware_version, size_t passport_firmware_version_len); -// TODO: add taproot functions - #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h deleted file mode 100644 index af1752bd2..000000000 --- a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * This file is part of the TREZOR project, https://trezor.io/ - * - * Copyright (c) SatoshiLabs - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -#include "py/obj.h" -#include "py/objstr.h" -#include "py/runtime.h" - -#include "secp256k1.h" -#include "ecdsa.h" - -#define X_SIZE 32 - -/// package: trezorcrypto.ecdsa - -/// def get_x(pubkey: bytes) -> bytes: -/// """ -/// Gets the X coordinate of the point corresponding to an ECDSA pubkey -/// """ -STATIC mp_obj_t mod_trezorcrypto_ecdsa_get_x(mp_obj_t pubkey_obj) { - mp_buffer_info_t pubkey; - mp_get_buffer_raise(pubkey_obj, &pubkey, MP_BUFFER_READ); - - vstr_t x_vstr; - vstr_init_len(&x_vstr, X_SIZE); - x_vstr.len = X_SIZE; - - int rv = ecdsa_get_x(&secp256k1, pubkey.buf, (uint8_t *)x_vstr.buf); - if (rv == 0) { - mp_raise_ValueError(MP_ERROR_TEXT("failed to get x")); - } - - return mp_obj_new_str_from_vstr(&mp_type_bytes, &x_vstr); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1( - mod_trezorcrypto_ecdsa_get_x_obj, - mod_trezorcrypto_ecdsa_get_x); - -STATIC const mp_rom_map_elem_t mod_trezorcrypto_ecdsa_globals_table[] = { - {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ecdsa)}, - {MP_ROM_QSTR(MP_QSTR_get_x), - MP_ROM_PTR(&mod_trezorcrypto_ecdsa_get_x_obj)}, -}; -STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_ecdsa_globals, - mod_trezorcrypto_ecdsa_globals_table); - -STATIC const mp_obj_module_t mod_trezorcrypto_ecdsa_module = { - .base = {&mp_type_module}, - .globals = (mp_obj_dict_t *)&mod_trezorcrypto_ecdsa_globals, -}; diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c index 47f07762b..816b951c3 100644 --- a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c +++ b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c @@ -46,7 +46,6 @@ static void wrapped_ui_wait_callback(uint32_t current, uint32_t total) { #include "modtrezorcrypto-chacha20poly1305.h" #include "modtrezorcrypto-crc.h" #include "modtrezorcrypto-curve25519.h" -#include "modtrezorcrypto-ecdsa.h" #if USE_KECCAK #include "modtrezorcrypto-ed25519.h" #endif @@ -79,7 +78,6 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_aes), MP_ROM_PTR(&mod_trezorcrypto_AES_type)}, {MP_ROM_QSTR(MP_QSTR_bip32), MP_ROM_PTR(&mod_trezorcrypto_bip32_module)}, {MP_ROM_QSTR(MP_QSTR_bip39), MP_ROM_PTR(&mod_trezorcrypto_bip39_module)}, - {MP_ROM_QSTR(MP_QSTR_ecdsa), MP_ROM_PTR(&mod_trezorcrypto_ecdsa_module)}, #ifndef FOUNDATION_ADDITIONS {MP_ROM_QSTR(MP_QSTR_blake256), MP_ROM_PTR(&mod_trezorcrypto_Blake256_type)}, diff --git a/extmod/trezor-firmware/crypto/ecdsa.c b/extmod/trezor-firmware/crypto/ecdsa.c index 07ac3f088..d5d8bd8aa 100644 --- a/extmod/trezor-firmware/crypto/ecdsa.c +++ b/extmod/trezor-firmware/crypto/ecdsa.c @@ -962,18 +962,6 @@ int ecdsa_read_pubkey(const ecdsa_curve *curve, const uint8_t *pub_key, return 0; } -int ecdsa_get_x(const ecdsa_curve *curve, const uint8_t *pub_key, uint8_t * x) { - curve_point pub = {0}; - - if (!ecdsa_read_pubkey(curve, pub_key, &pub)) { - return 0; - } - - bn_write_be(&pub.x, x); - - return 1; -} - // Verifies that: // - pub is not the point at infinity. // - pub->x and pub->y are in range [0,p-1]. diff --git a/extmod/trezor-firmware/crypto/ecdsa.h b/extmod/trezor-firmware/crypto/ecdsa.h index 991d0afdc..7b951d236 100644 --- a/extmod/trezor-firmware/crypto/ecdsa.h +++ b/extmod/trezor-firmware/crypto/ecdsa.h @@ -113,7 +113,6 @@ int ecdsa_address_decode(const char *addr, uint32_t version, HasherType hasher_base58, uint8_t *out); int ecdsa_read_pubkey(const ecdsa_curve *curve, const uint8_t *pub_key, curve_point *pub); -int ecdsa_get_x(const ecdsa_curve *curve, const uint8_t *pub_key, uint8_t * x); int ecdsa_validate_pubkey(const ecdsa_curve *curve, const curve_point *pub); int ecdsa_verify(const ecdsa_curve *curve, HasherType hasher_sign, const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, From 6aafeb9f077e87814b5de81dd23d67e7d2b5622f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 4 Oct 2023 23:26:07 -0400 Subject: [PATCH 079/239] SFT-1700: added tagged hash of x only pubkey --- ports/stm32/boards/Passport/modules/chains.py | 23 ++++++++----------- .../Passport/modules/public_constants.py | 5 +++- ports/stm32/boards/Passport/modules/utils.py | 7 +++++- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index c6973d062..8205f9a56 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -4,6 +4,9 @@ # SPDX-FileCopyrightText: 2018 Coinkite, Inc. # SPDX-License-Identifier: GPL-3.0-only # +# SPDX-FileCopyrightText: 2021 Emanuele Bellocchia +# SPDX-License-Identifier: MIT +# # (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard # and is covered by GPLv3 license found in COPYING. # @@ -120,22 +123,16 @@ def address(cls, node, addr_fmt): from ubinascii import unhexlify as a2b_hex from ubinascii import hexlify as b2a_hex from foundation import secp256k1 + from utils import hash_tap_tweak - # tweaked = bip340.tweak_public_key(node.public_key()) - # print(b2a_hex(tweaked)) - - # print("here 1") - # tap_tweak_sha256 = a2b_hex("e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9") - # print("here 2") - - x = secp256k1.x_only_public_key(node.public_key()) + pubkey = node.public_key() + x = secp256k1.x_only_public_key(pubkey) print("x only: {}".format(b2a_hex(x))) - # print("here 3") - # print(b2a_hex(x)) - # print("here 4") - # hash_tap_tweaked_pubkey = precomputed_tagged_hash(tap_tweak_sha256, x) + hash_tap_tweaked_pubkey = hash_tap_tweak(x) + print("hash tap tweaked: {}".format(b2a_hex(hash_tap_tweaked_pubkey))) + # lifted_x = lift_x(pubkey) # point = lift_x(pubkey) + hash_tap_tweaked_pubkey * secp256k1_generator() - # tweaked_pubkey = point.x() + # tweaked_pubkey = secp256k1.x_only_public_key(point) return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, raw) # see bip-141, "P2WPKH nested in BIP16 P2SH" section diff --git a/ports/stm32/boards/Passport/modules/public_constants.py b/ports/stm32/boards/Passport/modules/public_constants.py index 077462207..85796983c 100644 --- a/ports/stm32/boards/Passport/modules/public_constants.py +++ b/ports/stm32/boards/Passport/modules/public_constants.py @@ -4,6 +4,9 @@ # SPDX-FileCopyrightText: 2018 Coinkite, Inc. # SPDX-License-Identifier: GPL-3.0-only # +# SPDX-FileCopyrightText: 2021 Emanuele Bellocchia +# SPDX-License-Identifier: MIT +# # Constants and various "limits" shared between embedded and desktop USB protocol # try: @@ -129,5 +132,5 @@ {}: {} ''' - +TAP_TWEAK_SHA256 = "e80fe1639c9ca050e3af1b39c143c63e429cbceb15d940fbb5c5a1f4af57c5e9" # EOF diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index b21363c86..dbfa5cf66 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -4,6 +4,9 @@ # SPDX-FileCopyrightText: 2018 Coinkite, Inc. # SPDX-License-Identifier: GPL-3.0-only # +# SPDX-FileCopyrightText: 2021 Emanuele Bellocchia +# SPDX-License-Identifier: MIT +# # (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard # and is covered by GPLv3 license found in COPYING. # @@ -1442,9 +1445,11 @@ def escape_text(text): return text.replace("#", "##") -def precomputed_tagged_hash(tag_hash, data): +def hash_tap_tweak(data): from serializations import sha256 + from public_constants import TAP_TWEAK_SHA256 + tag_hash = a2b_hex(TAP_TWEAK_SHA256) return sha256(tag_hash + tag_hash + data) From e5ee64e0d7f83c15e0840c745c6ac05182186470 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 5 Oct 2023 14:30:36 -0400 Subject: [PATCH 080/239] SFT-1708: added add_tweak rust function --- extmod/foundation-rust/include/foundation.h | 12 +++++++- extmod/foundation-rust/src/secp256k1.rs | 22 +++++++++++++-- extmod/foundation/modfoundation-secp56k1.h | 28 +++++++++++++++++++ ports/stm32/boards/Passport/modules/chains.py | 5 ++-- 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/extmod/foundation-rust/include/foundation.h b/extmod/foundation-rust/include/foundation.h index 338c8dab2..6a03fa9bc 100644 --- a/extmod/foundation-rust/include/foundation.h +++ b/extmod/foundation-rust/include/foundation.h @@ -372,13 +372,23 @@ void foundation_secp256k1_schnorr_sign(const uint8_t (*data)[32], uint8_t (*signature)[64]); /** - * Returns an x-only public key given a public key + * Finds the x-only public key given a public key * - `pubkey` is the original public key * - `x_only_pubkey` is the resulting x-only public key */ void foundation_secp256k1_x_only_public_key(const uint8_t (*pubkey)[33], uint8_t (*x_only_pubkey)[32]); +/** + * Adds a tweak to an x-only public key + * - `x_only_pubkey` is the public key + * - `tweak` is the tweak value + * - `tweaked_pubkey` is the result of the tweak + */ +void foundation_secp256k1_add_tweak(const uint8_t (*x_only_pubkey)[32], + const uint8_t (*tweak)[32], + uint8_t (*tweaked_pubkey)[32]); + /** * Receive a Uniform Resource part. * diff --git a/extmod/foundation-rust/src/secp256k1.rs b/extmod/foundation-rust/src/secp256k1.rs index 41001f5cd..02e3e5207 100644 --- a/extmod/foundation-rust/src/secp256k1.rs +++ b/extmod/foundation-rust/src/secp256k1.rs @@ -3,7 +3,7 @@ use once_cell::sync::Lazy; use secp256k1::{ - ffi::types::AlignedType, AllPreallocated, KeyPair, Message, Secp256k1, constants::PUBLIC_KEY_SIZE, constants::SCHNORR_PUBLIC_KEY_SIZE, PublicKey + ffi::types::AlignedType, AllPreallocated, KeyPair, Message, Secp256k1, constants::PUBLIC_KEY_SIZE, constants::SCHNORR_PUBLIC_KEY_SIZE, PublicKey, XOnlyPublicKey, Scalar }; /// cbindgen:ignore @@ -37,7 +37,7 @@ pub extern "C" fn secp256k1_sign_schnorr( signature.copy_from_slice(sig.as_ref()); } -/// Returns an x-only public key given a public key +/// Finds the x-only public key given a public key /// /// - `pubkey` is the original public key /// - `x_only_pubkey` is the resulting x-only public key @@ -52,6 +52,24 @@ pub extern "C" fn secp256k1_x_only_public_key( x_only_pubkey.copy_from_slice(&x_only_bytes) } +/// Adds a tweak to an x-only public key +/// +/// - `x_only_pubkey` is the public key +/// - `tweak` is the tweak value +/// - `tweaked_pubkey` is the result of the tweak +#[export_name = "foundation_secp256k1_add_tweak"] +pub extern "C" fn secp256k1_add_tweak( + x_only_pubkey: &[u8; SCHNORR_PUBLIC_KEY_SIZE], + tweak: &[u8; SCHNORR_PUBLIC_KEY_SIZE], + tweaked_pubkey: &mut [u8; SCHNORR_PUBLIC_KEY_SIZE], +) { + let pk_struct = XOnlyPublicKey::from_slice(x_only_pubkey).unwrap(); + let tweak_struct = Scalar::from_le_bytes(*tweak).unwrap(); + let (tweaked_pubkey_struct, _) = pk_struct.add_tweak(&PRE_ALLOCATED_CTX, &tweak_struct).unwrap(); + let tweaked_pubkey_bytes = tweaked_pubkey_struct.serialize(); + tweaked_pubkey.copy_from_slice(&tweaked_pubkey_bytes) +} + #[cfg(target_arch = "arm")] fn rng() -> crate::rand::PassportRng { crate::rand::PassportRng diff --git a/extmod/foundation/modfoundation-secp56k1.h b/extmod/foundation/modfoundation-secp56k1.h index fa365cd45..986374a74 100644 --- a/extmod/foundation/modfoundation-secp56k1.h +++ b/extmod/foundation/modfoundation-secp56k1.h @@ -53,9 +53,37 @@ STATIC mp_obj_t mod_foundation_secp256k1_x_only_public_key(mp_obj_t public_key_o STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_foundation_secp256k1_x_only_public_key_obj, mod_foundation_secp256k1_x_only_public_key); +/// def add_tweak(x_only_public_key, tweak) -> tweaked_public_key: +/// """ +// """ +STATIC mp_obj_t mod_foundation_secp256k1_add_tweak(mp_obj_t x_only_public_key_obj, mp_obj_t tweak_obj) +{ + mp_buffer_info_t x_only_public_key; + mp_buffer_info_t tweak; + uint8_t tweaked_public_key[32]; + + mp_get_buffer_raise(x_only_public_key_obj, &x_only_public_key, MP_BUFFER_READ); + mp_get_buffer_raise(tweak_obj, &tweak, MP_BUFFER_READ); + + if (x_only_public_key.len != 32) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("x_only_public_key should be 32 bytes")); + } + + if (tweak.len != 32) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("tweak should be 32 bytes")); + } + + foundation_secp256k1_add_tweak(x_only_public_key.buf, tweak.buf, &tweaked_public_key); + + return mp_obj_new_bytes(tweaked_public_key, sizeof(tweaked_public_key)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_foundation_secp256k1_add_tweak_obj, + mod_foundation_secp256k1_add_tweak); + STATIC const mp_rom_map_elem_t mod_foundation_secp256k1_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_schnorr_sign), MP_ROM_PTR(&mod_foundation_secp256k1_sign_schnorr_obj) }, { MP_ROM_QSTR(MP_QSTR_x_only_public_key), MP_ROM_PTR(&mod_foundation_secp256k1_x_only_public_key_obj) }, + { MP_ROM_QSTR(MP_QSTR_add_tweak), MP_ROM_PTR(&mod_foundation_secp256k1_add_tweak_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mod_foundation_secp256k1_globals, mod_foundation_secp256k1_globals_table); diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 8205f9a56..cb48bab11 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -132,8 +132,9 @@ def address(cls, node, addr_fmt): print("hash tap tweaked: {}".format(b2a_hex(hash_tap_tweaked_pubkey))) # lifted_x = lift_x(pubkey) # point = lift_x(pubkey) + hash_tap_tweaked_pubkey * secp256k1_generator() - # tweaked_pubkey = secp256k1.x_only_public_key(point) - return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, raw) + final_pubkey = secp256k1.add_tweak(x, hash_tap_tweaked_pubkey) + print("final_pubkey: {}".format(final_pubkey)) + return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, final_pubkey) # see bip-141, "P2WPKH nested in BIP16 P2SH" section assert addr_fmt == AF_P2WPKH_P2SH From 8b5a9d73e2d1df2b3bb9d7f9a0fe16b1ce8ef705 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 5 Oct 2023 19:51:30 -0400 Subject: [PATCH 081/239] SFT-1708: mocked out lift_x, need to implement secp256k1.from_coordinates --- ports/stm32/boards/Passport/modules/chains.py | 5 ++--- ports/stm32/boards/Passport/modules/utils.py | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index cb48bab11..6734b2949 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -130,9 +130,8 @@ def address(cls, node, addr_fmt): print("x only: {}".format(b2a_hex(x))) hash_tap_tweaked_pubkey = hash_tap_tweak(x) print("hash tap tweaked: {}".format(b2a_hex(hash_tap_tweaked_pubkey))) - # lifted_x = lift_x(pubkey) - # point = lift_x(pubkey) + hash_tap_tweaked_pubkey * secp256k1_generator() - final_pubkey = secp256k1.add_tweak(x, hash_tap_tweaked_pubkey) + lifted_x = lift_x(x) + final_pubkey = secp256k1.add_tweak(lifted_x, hash_tap_tweaked_pubkey) print("final_pubkey: {}".format(final_pubkey)) return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, final_pubkey) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index dbfa5cf66..57c7ccf57 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1453,9 +1453,23 @@ def hash_tap_tweak(data): return sha256(tag_hash + tag_hash + data) -def lift_x(pubkey): +def lift_x(x_only_pubkey): + from foundation import secp256k1 + field_size = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F - x = pubkey + x = int.from_bytes(x_only_pubkey, "little") + x2 = int.from_bytes(x_only_pubkey, "big") + print("x: {}, x2: {}, x_original".format(x, x2, b2a_hex(x_only_pubkey))) + + if x >= field_size: + raise ValueError("Unable to compute LiftX point") + + c = (pow(x, 3, p) + 7) % p + y = pow(c, (p + 1) // 4, p) + + if c != pow(y, 2, p): + raise ValueError("Unable to compute LiftX point") + return secp256k1.from_coordinates(x, y if y % 2 == 0 else p - y) # EOF From f801dafd88f29eea2c7c8af3c26dddfe8c0e182a Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 13 Oct 2023 19:37:59 -0400 Subject: [PATCH 082/239] SFT-1708: got correct internal key and tagged_hash --- ports/stm32/boards/Passport/modules/chains.py | 18 +++++++++------- ports/stm32/boards/Passport/modules/utils.py | 21 ++----------------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 6734b2949..84f7655d7 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -125,15 +125,17 @@ def address(cls, node, addr_fmt): from foundation import secp256k1 from utils import hash_tap_tweak + # TODO: Move all this to rust in a single p2tr function pubkey = node.public_key() - x = secp256k1.x_only_public_key(pubkey) - print("x only: {}".format(b2a_hex(x))) - hash_tap_tweaked_pubkey = hash_tap_tweak(x) - print("hash tap tweaked: {}".format(b2a_hex(hash_tap_tweaked_pubkey))) - lifted_x = lift_x(x) - final_pubkey = secp256k1.add_tweak(lifted_x, hash_tap_tweaked_pubkey) - print("final_pubkey: {}".format(final_pubkey)) - return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, final_pubkey) + internal_key = secp256k1.x_only_public_key(pubkey) + print("internal_key: {}".format(b2a_hex(internal_key))) # Correct up to and including this line + tweak = hash_tap_tweak(internal_key) + print("tweak: {}".format(b2a_hex(tweak))) + output_key = secp256k1.add_tweak(internal_key, tweak) + print("output_key: {}".format(b2a_hex(output_key))) + script_pubkey = a2b_hex('5120') + output_key + print("script_pubkey: {}".format(b2a_hex(script_pubkey))) + return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, script_pubkey) # see bip-141, "P2WPKH nested in BIP16 P2SH" section assert addr_fmt == AF_P2WPKH_P2SH diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 57c7ccf57..40b7e93e2 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1450,26 +1450,9 @@ def hash_tap_tweak(data): from public_constants import TAP_TWEAK_SHA256 tag_hash = a2b_hex(TAP_TWEAK_SHA256) + text = tag_hash + tag_hash + data + print("hash_tap_tweak text: {}".format(b2a_hex(text))) return sha256(tag_hash + tag_hash + data) -def lift_x(x_only_pubkey): - from foundation import secp256k1 - - field_size = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F - x = int.from_bytes(x_only_pubkey, "little") - x2 = int.from_bytes(x_only_pubkey, "big") - print("x: {}, x2: {}, x_original".format(x, x2, b2a_hex(x_only_pubkey))) - - if x >= field_size: - raise ValueError("Unable to compute LiftX point") - - c = (pow(x, 3, p) + 7) % p - y = pow(c, (p + 1) // 4, p) - - if c != pow(y, 2, p): - raise ValueError("Unable to compute LiftX point") - - return secp256k1.from_coordinates(x, y if y % 2 == 0 else p - y) - # EOF From 458f7c4ee57f721e81c2c63347b2646f4e19d228 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 13 Oct 2023 20:33:06 -0400 Subject: [PATCH 083/239] SFT-1708: fixed add_tweak rust function --- extmod/foundation-rust/src/secp256k1.rs | 2 +- ports/stm32/boards/Passport/modules/chains.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extmod/foundation-rust/src/secp256k1.rs b/extmod/foundation-rust/src/secp256k1.rs index 02e3e5207..554b08506 100644 --- a/extmod/foundation-rust/src/secp256k1.rs +++ b/extmod/foundation-rust/src/secp256k1.rs @@ -64,7 +64,7 @@ pub extern "C" fn secp256k1_add_tweak( tweaked_pubkey: &mut [u8; SCHNORR_PUBLIC_KEY_SIZE], ) { let pk_struct = XOnlyPublicKey::from_slice(x_only_pubkey).unwrap(); - let tweak_struct = Scalar::from_le_bytes(*tweak).unwrap(); + let tweak_struct = Scalar::from_be_bytes(*tweak).unwrap(); let (tweaked_pubkey_struct, _) = pk_struct.add_tweak(&PRE_ALLOCATED_CTX, &tweak_struct).unwrap(); let tweaked_pubkey_bytes = tweaked_pubkey_struct.serialize(); tweaked_pubkey.copy_from_slice(&tweaked_pubkey_bytes) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 84f7655d7..0dcf8202f 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -128,13 +128,13 @@ def address(cls, node, addr_fmt): # TODO: Move all this to rust in a single p2tr function pubkey = node.public_key() internal_key = secp256k1.x_only_public_key(pubkey) - print("internal_key: {}".format(b2a_hex(internal_key))) # Correct up to and including this line + print("internal_key: {}".format(b2a_hex(internal_key))) tweak = hash_tap_tweak(internal_key) print("tweak: {}".format(b2a_hex(tweak))) output_key = secp256k1.add_tweak(internal_key, tweak) print("output_key: {}".format(b2a_hex(output_key))) script_pubkey = a2b_hex('5120') + output_key - print("script_pubkey: {}".format(b2a_hex(script_pubkey))) + print("script_pubkey: {}".format(b2a_hex(script_pubkey))) # Correct up to and including this line return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, script_pubkey) # see bip-141, "P2WPKH nested in BIP16 P2SH" section From c3917aefb843a71055c9a7ffb386d0c3cb948804 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 13 Oct 2023 21:05:54 -0400 Subject: [PATCH 084/239] SFT-1708: successfully verified taproot addresses --- ports/stm32/boards/Passport/modules/chains.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 0dcf8202f..ffaf40102 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -135,7 +135,7 @@ def address(cls, node, addr_fmt): print("output_key: {}".format(b2a_hex(output_key))) script_pubkey = a2b_hex('5120') + output_key print("script_pubkey: {}".format(b2a_hex(script_pubkey))) # Correct up to and including this line - return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, script_pubkey) + return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, output_key) # see bip-141, "P2WPKH nested in BIP16 P2SH" section assert addr_fmt == AF_P2WPKH_P2SH From cee72f66a3576273e8033fa1c19344a321b73bb7 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 13 Oct 2023 21:09:30 -0400 Subject: [PATCH 085/239] SFT-1708: removed some debug --- ports/stm32/boards/Passport/modules/chains.py | 12 ++---------- ports/stm32/boards/Passport/modules/utils.py | 2 -- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index ffaf40102..a57c8d1e7 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -19,6 +19,8 @@ from serializations import hash160, ser_compact_size from ucollections import namedtuple from opcodes import OP_CHECKMULTISIG +from foundation import secp256k1 +from utils import hash_tap_tweak # See SLIP 132 # for background on these version bytes. Not to be confused with SLIP-32 which involves Bech32. @@ -120,21 +122,11 @@ def address(cls, node, addr_fmt): # bech32 encoded segwit p2pkh return tcc.codecs.bech32_encode(cls.bech32_hrp, 0, raw) elif addr_fmt & AFC_BECH32M: - from ubinascii import unhexlify as a2b_hex - from ubinascii import hexlify as b2a_hex - from foundation import secp256k1 - from utils import hash_tap_tweak - # TODO: Move all this to rust in a single p2tr function pubkey = node.public_key() internal_key = secp256k1.x_only_public_key(pubkey) - print("internal_key: {}".format(b2a_hex(internal_key))) tweak = hash_tap_tweak(internal_key) - print("tweak: {}".format(b2a_hex(tweak))) output_key = secp256k1.add_tweak(internal_key, tweak) - print("output_key: {}".format(b2a_hex(output_key))) - script_pubkey = a2b_hex('5120') + output_key - print("script_pubkey: {}".format(b2a_hex(script_pubkey))) # Correct up to and including this line return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, output_key) # see bip-141, "P2WPKH nested in BIP16 P2SH" section diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 40b7e93e2..ad35dfe8d 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1450,8 +1450,6 @@ def hash_tap_tweak(data): from public_constants import TAP_TWEAK_SHA256 tag_hash = a2b_hex(TAP_TWEAK_SHA256) - text = tag_hash + tag_hash + data - print("hash_tap_tweak text: {}".format(b2a_hex(text))) return sha256(tag_hash + tag_hash + data) From 28fdb34cdd464ce6bf2b7c58490968cec4cf2e3a Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 13 Oct 2023 21:15:53 -0400 Subject: [PATCH 086/239] SFT-1708: restored try-except --- .../modules/tasks/search_for_address_task.py | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py index ba284d798..b8c813041 100644 --- a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py @@ -24,45 +24,42 @@ async def search_for_address_task( import stash from errors import Error from uasyncio import sleep_ms - from ubinascii import hexlify as b2a_hex - # try: - with stash.SensitiveValues() as sv: - if multisig_wallet: - # NOTE: Can't easily reverse order here, so this is slightly less efficient - for (curr_idx, paths, curr_address, script) in multisig_wallet.yield_addresses( - start_address_idx, - max_to_check, - change_idx=1 if is_change else 0): - # print('Multisig: curr_idx={}: paths={} curr_address = {}'.format(curr_idx, paths, curr_address)) + try: + with stash.SensitiveValues() as sv: + if multisig_wallet: + # NOTE: Can't easily reverse order here, so this is slightly less efficient + for (curr_idx, paths, curr_address, script) in multisig_wallet.yield_addresses( + start_address_idx, + max_to_check, + change_idx=1 if is_change else 0): + # print('Multisig: curr_idx={}: paths={} curr_address = {}'.format(curr_idx, paths, curr_address)) - if curr_address == address: - # NOTE: Paths are the full paths of the addresses of each signer - await on_done(curr_idx, paths, None) - return - await sleep_ms(1) + if curr_address == address: + # NOTE: Paths are the full paths of the addresses of each signer + await on_done(curr_idx, paths, None) + return + await sleep_ms(1) - else: - r = range(start_address_idx, start_address_idx + max_to_check) - if reverse: - r = reversed(r) + else: + r = range(start_address_idx, start_address_idx + max_to_check) + if reverse: + r = reversed(r) - for curr_idx in r: - addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address - # print('Singlesig: addr_path={}'.format(addr_path)) - node = sv.derive_path(addr_path) - print(b2a_hex(node.public_key())) - curr_address = sv.chain.address(node, addr_type) - # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, - # addr_type, curr_address)) - print("path: {}, address: {}".format(addr_path, curr_address)) - if curr_address == address: - await on_done(curr_idx, addr_path, None) - return - await sleep_ms(1) + for curr_idx in r: + addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address + # print('Singlesig: addr_path={}'.format(addr_path)) + node = sv.derive_path(addr_path) + curr_address = sv.chain.address(node, addr_type) + # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, + # addr_type, curr_address)) + if curr_address == address: + await on_done(curr_idx, addr_path, None) + return + await sleep_ms(1) - await on_done(-1, None, Error.ADDRESS_NOT_FOUND) - # except Exception as e: - # # print('EXCEPTION: e={}'.format(e)) - # # Any address handling exceptions result in no address found - # await on_done(-1, None, Error.ADDRESS_NOT_FOUND) + await on_done(-1, None, Error.ADDRESS_NOT_FOUND) + except Exception as e: + # print('EXCEPTION: e={}'.format(e)) + # Any address handling exceptions result in no address found + await on_done(-1, None, Error.ADDRESS_NOT_FOUND) From 3f87923049c8023b88a0782857366619556e93d0 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 13 Oct 2023 21:28:14 -0400 Subject: [PATCH 087/239] SFT-1708: removed unused license --- ports/stm32/boards/Passport/modules/chains.py | 3 --- ports/stm32/boards/Passport/modules/public_constants.py | 3 --- ports/stm32/boards/Passport/modules/utils.py | 3 --- 3 files changed, 9 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index a57c8d1e7..978915ef0 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -4,9 +4,6 @@ # SPDX-FileCopyrightText: 2018 Coinkite, Inc. # SPDX-License-Identifier: GPL-3.0-only # -# SPDX-FileCopyrightText: 2021 Emanuele Bellocchia -# SPDX-License-Identifier: MIT -# # (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard # and is covered by GPLv3 license found in COPYING. # diff --git a/ports/stm32/boards/Passport/modules/public_constants.py b/ports/stm32/boards/Passport/modules/public_constants.py index 85796983c..43ecca28a 100644 --- a/ports/stm32/boards/Passport/modules/public_constants.py +++ b/ports/stm32/boards/Passport/modules/public_constants.py @@ -4,9 +4,6 @@ # SPDX-FileCopyrightText: 2018 Coinkite, Inc. # SPDX-License-Identifier: GPL-3.0-only # -# SPDX-FileCopyrightText: 2021 Emanuele Bellocchia -# SPDX-License-Identifier: MIT -# # Constants and various "limits" shared between embedded and desktop USB protocol # try: diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index ad35dfe8d..be939039c 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -4,9 +4,6 @@ # SPDX-FileCopyrightText: 2018 Coinkite, Inc. # SPDX-License-Identifier: GPL-3.0-only # -# SPDX-FileCopyrightText: 2021 Emanuele Bellocchia -# SPDX-License-Identifier: MIT -# # (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard # and is covered by GPLv3 license found in COPYING. # From 53bba1ce45ab677836684ff82cbd8ab74aa023bc Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 00:00:53 -0400 Subject: [PATCH 088/239] SFT-1708: removed x_only_public_key function --- extmod/foundation-rust/include/foundation.h | 8 ------- extmod/foundation-rust/src/secp256k1.rs | 17 +-------------- extmod/foundation/modfoundation-secp56k1.h | 21 ------------------- ports/stm32/boards/Passport/modules/chains.py | 3 ++- 4 files changed, 3 insertions(+), 46 deletions(-) diff --git a/extmod/foundation-rust/include/foundation.h b/extmod/foundation-rust/include/foundation.h index 6a03fa9bc..c4ca23f18 100644 --- a/extmod/foundation-rust/include/foundation.h +++ b/extmod/foundation-rust/include/foundation.h @@ -371,14 +371,6 @@ void foundation_secp256k1_schnorr_sign(const uint8_t (*data)[32], const uint8_t (*secret_key)[32], uint8_t (*signature)[64]); -/** - * Finds the x-only public key given a public key - * - `pubkey` is the original public key - * - `x_only_pubkey` is the resulting x-only public key - */ -void foundation_secp256k1_x_only_public_key(const uint8_t (*pubkey)[33], - uint8_t (*x_only_pubkey)[32]); - /** * Adds a tweak to an x-only public key * - `x_only_pubkey` is the public key diff --git a/extmod/foundation-rust/src/secp256k1.rs b/extmod/foundation-rust/src/secp256k1.rs index 554b08506..0c94f7760 100644 --- a/extmod/foundation-rust/src/secp256k1.rs +++ b/extmod/foundation-rust/src/secp256k1.rs @@ -3,7 +3,7 @@ use once_cell::sync::Lazy; use secp256k1::{ - ffi::types::AlignedType, AllPreallocated, KeyPair, Message, Secp256k1, constants::PUBLIC_KEY_SIZE, constants::SCHNORR_PUBLIC_KEY_SIZE, PublicKey, XOnlyPublicKey, Scalar + ffi::types::AlignedType, AllPreallocated, KeyPair, Message, Secp256k1, constants::SCHNORR_PUBLIC_KEY_SIZE, XOnlyPublicKey, Scalar }; /// cbindgen:ignore @@ -37,21 +37,6 @@ pub extern "C" fn secp256k1_sign_schnorr( signature.copy_from_slice(sig.as_ref()); } -/// Finds the x-only public key given a public key -/// -/// - `pubkey` is the original public key -/// - `x_only_pubkey` is the resulting x-only public key -#[export_name = "foundation_secp256k1_x_only_public_key"] -pub extern "C" fn secp256k1_x_only_public_key( - pubkey: &[u8; PUBLIC_KEY_SIZE], - x_only_pubkey: &mut [u8; SCHNORR_PUBLIC_KEY_SIZE], -) { - let pk_struct = PublicKey::from_slice(pubkey).unwrap(); - let (x_only_struct, _) = pk_struct.x_only_public_key(); - let x_only_bytes = x_only_struct.serialize(); - x_only_pubkey.copy_from_slice(&x_only_bytes) -} - /// Adds a tweak to an x-only public key /// /// - `x_only_pubkey` is the public key diff --git a/extmod/foundation/modfoundation-secp56k1.h b/extmod/foundation/modfoundation-secp56k1.h index 986374a74..6491e9396 100644 --- a/extmod/foundation/modfoundation-secp56k1.h +++ b/extmod/foundation/modfoundation-secp56k1.h @@ -33,26 +33,6 @@ STATIC mp_obj_t mod_foundation_secp256k1_sign_schnorr(mp_obj_t data_obj, STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_foundation_secp256k1_sign_schnorr_obj, mod_foundation_secp256k1_sign_schnorr); -/// def x_only_public_key(public_key) -> x_only_public_key: -/// """ -STATIC mp_obj_t mod_foundation_secp256k1_x_only_public_key(mp_obj_t public_key_obj) -{ - mp_buffer_info_t public_key; - uint8_t x_only_public_key[32]; - - mp_get_buffer_raise(public_key_obj, &public_key, MP_BUFFER_READ); - - if (public_key.len != 33) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("public_key should be 33 bytes")); - } - - foundation_secp256k1_x_only_public_key(public_key.buf, &x_only_public_key); - - return mp_obj_new_bytes(x_only_public_key, sizeof(x_only_public_key)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_foundation_secp256k1_x_only_public_key_obj, - mod_foundation_secp256k1_x_only_public_key); - /// def add_tweak(x_only_public_key, tweak) -> tweaked_public_key: /// """ // """ @@ -82,7 +62,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_foundation_secp256k1_add_tweak_obj, STATIC const mp_rom_map_elem_t mod_foundation_secp256k1_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_schnorr_sign), MP_ROM_PTR(&mod_foundation_secp256k1_sign_schnorr_obj) }, - { MP_ROM_QSTR(MP_QSTR_x_only_public_key), MP_ROM_PTR(&mod_foundation_secp256k1_x_only_public_key_obj) }, { MP_ROM_QSTR(MP_QSTR_add_tweak), MP_ROM_PTR(&mod_foundation_secp256k1_add_tweak_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mod_foundation_secp256k1_globals, mod_foundation_secp256k1_globals_table); diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 978915ef0..8735c2ef5 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -99,6 +99,7 @@ def p2sh_address(cls, addr_fmt, witdeem_script): @classmethod def address(cls, node, addr_fmt): + from ubinascii import hexlify as b2a_hex # return a human-readable, properly formatted address if addr_fmt == AF_CLASSIC: @@ -121,7 +122,7 @@ def address(cls, node, addr_fmt): elif addr_fmt & AFC_BECH32M: pubkey = node.public_key() - internal_key = secp256k1.x_only_public_key(pubkey) + internal_key = pubkey[1::] tweak = hash_tap_tweak(internal_key) output_key = secp256k1.add_tweak(internal_key, tweak) return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, output_key) From 96281f34801f3b4542bcf2e01fbefe0c3d7033c8 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 00:55:24 -0400 Subject: [PATCH 089/239] SFT-1708: first steps towards homemade add_tweak --- ports/stm32/boards/Passport/modules/chains.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 8735c2ef5..30a4132a2 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -16,8 +16,8 @@ from serializations import hash160, ser_compact_size from ucollections import namedtuple from opcodes import OP_CHECKMULTISIG -from foundation import secp256k1 from utils import hash_tap_tweak +from foundation import secp256k1 # See SLIP 132 # for background on these version bytes. Not to be confused with SLIP-32 which involves Bech32. @@ -123,7 +123,11 @@ def address(cls, node, addr_fmt): pubkey = node.public_key() internal_key = pubkey[1::] + # internal_key = int.from_bytes(pubkey[1::], "big") tweak = hash_tap_tweak(internal_key) + # tweak = int.from_bytes(hash_tap_tweak(internal_key), "big") + # TODO: expose point_add and point_multiply + # output_key = internal_key + trezorcrypto.secp256k1.multiply(tweak, G) output_key = secp256k1.add_tweak(internal_key, tweak) return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, output_key) From 3e4beb6eeb6b80be2950799ec63c70cf59a0ef02 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 04:45:07 -0400 Subject: [PATCH 090/239] SFT-1708: taproot address verification is slow but working on device --- extmod/foundation-rust/include/foundation.h | 10 -- extmod/foundation-rust/src/secp256k1.rs | 20 +-- extmod/foundation/modfoundation-secp56k1.h | 28 --- ports/stm32/boards/Passport/manifest.py | 1 + ports/stm32/boards/Passport/modules/chains.py | 14 +- .../stm32/boards/Passport/modules/taproot.py | 163 ++++++++++++++++++ .../modules/tasks/search_for_address_task.py | 68 ++++---- ports/stm32/boards/Passport/modules/utils.py | 8 - ports/stm32/mpconfigport.h | 2 +- py/mpconfig.h | 4 +- 10 files changed, 207 insertions(+), 111 deletions(-) create mode 100644 ports/stm32/boards/Passport/modules/taproot.py diff --git a/extmod/foundation-rust/include/foundation.h b/extmod/foundation-rust/include/foundation.h index c4ca23f18..cbb790588 100644 --- a/extmod/foundation-rust/include/foundation.h +++ b/extmod/foundation-rust/include/foundation.h @@ -371,16 +371,6 @@ void foundation_secp256k1_schnorr_sign(const uint8_t (*data)[32], const uint8_t (*secret_key)[32], uint8_t (*signature)[64]); -/** - * Adds a tweak to an x-only public key - * - `x_only_pubkey` is the public key - * - `tweak` is the tweak value - * - `tweaked_pubkey` is the result of the tweak - */ -void foundation_secp256k1_add_tweak(const uint8_t (*x_only_pubkey)[32], - const uint8_t (*tweak)[32], - uint8_t (*tweaked_pubkey)[32]); - /** * Receive a Uniform Resource part. * diff --git a/extmod/foundation-rust/src/secp256k1.rs b/extmod/foundation-rust/src/secp256k1.rs index 0c94f7760..d77f14bd9 100644 --- a/extmod/foundation-rust/src/secp256k1.rs +++ b/extmod/foundation-rust/src/secp256k1.rs @@ -3,7 +3,7 @@ use once_cell::sync::Lazy; use secp256k1::{ - ffi::types::AlignedType, AllPreallocated, KeyPair, Message, Secp256k1, constants::SCHNORR_PUBLIC_KEY_SIZE, XOnlyPublicKey, Scalar + ffi::types::AlignedType, AllPreallocated, KeyPair, Message, Secp256k1, }; /// cbindgen:ignore @@ -37,24 +37,6 @@ pub extern "C" fn secp256k1_sign_schnorr( signature.copy_from_slice(sig.as_ref()); } -/// Adds a tweak to an x-only public key -/// -/// - `x_only_pubkey` is the public key -/// - `tweak` is the tweak value -/// - `tweaked_pubkey` is the result of the tweak -#[export_name = "foundation_secp256k1_add_tweak"] -pub extern "C" fn secp256k1_add_tweak( - x_only_pubkey: &[u8; SCHNORR_PUBLIC_KEY_SIZE], - tweak: &[u8; SCHNORR_PUBLIC_KEY_SIZE], - tweaked_pubkey: &mut [u8; SCHNORR_PUBLIC_KEY_SIZE], -) { - let pk_struct = XOnlyPublicKey::from_slice(x_only_pubkey).unwrap(); - let tweak_struct = Scalar::from_be_bytes(*tweak).unwrap(); - let (tweaked_pubkey_struct, _) = pk_struct.add_tweak(&PRE_ALLOCATED_CTX, &tweak_struct).unwrap(); - let tweaked_pubkey_bytes = tweaked_pubkey_struct.serialize(); - tweaked_pubkey.copy_from_slice(&tweaked_pubkey_bytes) -} - #[cfg(target_arch = "arm")] fn rng() -> crate::rand::PassportRng { crate::rand::PassportRng diff --git a/extmod/foundation/modfoundation-secp56k1.h b/extmod/foundation/modfoundation-secp56k1.h index 6491e9396..2ca47cf97 100644 --- a/extmod/foundation/modfoundation-secp56k1.h +++ b/extmod/foundation/modfoundation-secp56k1.h @@ -33,36 +33,8 @@ STATIC mp_obj_t mod_foundation_secp256k1_sign_schnorr(mp_obj_t data_obj, STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_foundation_secp256k1_sign_schnorr_obj, mod_foundation_secp256k1_sign_schnorr); -/// def add_tweak(x_only_public_key, tweak) -> tweaked_public_key: -/// """ -// """ -STATIC mp_obj_t mod_foundation_secp256k1_add_tweak(mp_obj_t x_only_public_key_obj, mp_obj_t tweak_obj) -{ - mp_buffer_info_t x_only_public_key; - mp_buffer_info_t tweak; - uint8_t tweaked_public_key[32]; - - mp_get_buffer_raise(x_only_public_key_obj, &x_only_public_key, MP_BUFFER_READ); - mp_get_buffer_raise(tweak_obj, &tweak, MP_BUFFER_READ); - - if (x_only_public_key.len != 32) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("x_only_public_key should be 32 bytes")); - } - - if (tweak.len != 32) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("tweak should be 32 bytes")); - } - - foundation_secp256k1_add_tweak(x_only_public_key.buf, tweak.buf, &tweaked_public_key); - - return mp_obj_new_bytes(tweaked_public_key, sizeof(tweaked_public_key)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_foundation_secp256k1_add_tweak_obj, - mod_foundation_secp256k1_add_tweak); - STATIC const mp_rom_map_elem_t mod_foundation_secp256k1_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_schnorr_sign), MP_ROM_PTR(&mod_foundation_secp256k1_sign_schnorr_obj) }, - { MP_ROM_QSTR(MP_QSTR_add_tweak), MP_ROM_PTR(&mod_foundation_secp256k1_add_tweak_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mod_foundation_secp256k1_globals, mod_foundation_secp256k1_globals_table); diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index 800efb1be..a9c512220 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -40,6 +40,7 @@ 'sflash.py', 'stash.py', 'stat.py', + 'taproot.py', 't9.py', 'utils.py', 'version.py', diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 30a4132a2..c934cd603 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -16,8 +16,7 @@ from serializations import hash160, ser_compact_size from ucollections import namedtuple from opcodes import OP_CHECKMULTISIG -from utils import hash_tap_tweak -from foundation import secp256k1 +from taproot import output_script # See SLIP 132 # for background on these version bytes. Not to be confused with SLIP-32 which involves Bech32. @@ -119,16 +118,13 @@ def address(cls, node, addr_fmt): if addr_fmt & AFC_BECH32: # bech32 encoded segwit p2pkh return tcc.codecs.bech32_encode(cls.bech32_hrp, 0, raw) - elif addr_fmt & AFC_BECH32M: + elif addr_fmt & AFC_BECH32M: pubkey = node.public_key() internal_key = pubkey[1::] - # internal_key = int.from_bytes(pubkey[1::], "big") - tweak = hash_tap_tweak(internal_key) - # tweak = int.from_bytes(hash_tap_tweak(internal_key), "big") - # TODO: expose point_add and point_multiply - # output_key = internal_key + trezorcrypto.secp256k1.multiply(tweak, G) - output_key = secp256k1.add_tweak(internal_key, tweak) + print("internal_key: {}".format(b2a_hex(internal_key))) + output_key = output_script(internal_key, None)[2::] + print("output_key: {}".format(b2a_hex(output_key))) return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, output_key) # see bip-141, "P2WPKH nested in BIP16 P2SH" section diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py new file mode 100644 index 000000000..7fd4ed47a --- /dev/null +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -0,0 +1,163 @@ +# TODO: add BSD-2-Clause license for BIP340 and BSD-3-Clase for BIP341 + +import ubinascii +import utime + +# Set DEBUG to True to get a detailed debug output including +# intermediate values during key generation, signing, and +# verification. This is implemented via calls to the +# debug_print_vars() function. +# +# If you want to print values on an individual basis, use +# the pretty() function, e.g., print(pretty(foo)). +DEBUG = False + +p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F +SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + +# Points are tuples of X and Y coordinates and the point at infinity is +# represented by the None keyword. +G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, + 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8) + +# Point = Tuple[int, int] + +# This implementation can be sped up by storing the midstate after hashing +# tag_hash instead of rehashing it all the time. +# def tagged_hash(tag, msg) -> bytes: +# tag_hash = uhashlib.sha256(tag.encode()).digest() +# return uhashlib.sha256(tag_hash + tag_hash + msg).digest() + + +# TODO: optimize for TapTweak +def hash_tap_tweak(data): + from serializations import sha256 + from public_constants import TAP_TWEAK_SHA256 + from ubinascii import unhexlify as a2b_hex + + tag_hash = a2b_hex(TAP_TWEAK_SHA256) + return sha256(tag_hash + tag_hash + data) + + +def is_infinite(P): + return P is None + + +def x(P): + assert not is_infinite(P) + return P[0] + + +def y(P): + assert not is_infinite(P) + return P[1] + + +def point_add(P1, P2): + if P1 is None: + return P2 + if P2 is None: + return P1 + if (x(P1) == x(P2)) and (y(P1) != y(P2)): + return None + if P1 == P2: + lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p + else: + lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p + x3 = (lam * lam - x(P1) - x(P2)) % p + return (x3, (lam * (x(P1) - x3) - y(P1)) % p) + + +def point_mul(P, n): + R = None + for i in range(256): + if (n >> i) & 1: + R = point_add(R, P) + P = point_add(P, P) + return R + + +def bytes_from_int(x): + return x.to_bytes(32, "big") + + +def bytes_from_point(P): + return bytes_from_int(x(P)) + + +def xor_bytes(b0, b1): + return bytes(x ^ y for (x, y) in zip(b0, b1)) + + +def lift_x(x): + if x >= p: + return None + y_sq = (pow(x, 3, p) + 7) % p + y = pow(y_sq, (p + 1) // 4, p) + if pow(y, 2, p) != y_sq: + return None + return (x, y if y & 1 == 0 else p - y) + + +def int_from_bytes(b): + return int.from_bytes(b, "big") + + +def has_even_y(P): + assert not is_infinite(P) + return y(P) % 2 == 0 + + +def tweak_internal_key(internal_key, h): + print("1 {}".format(utime.ticks_ms())) + t = int_from_bytes(hash_tap_tweak(internal_key + h)) + print("2 {}".format(utime.ticks_ms())) + if t >= SECP256K1_ORDER: + raise ValueError + print("3 {}".format(utime.ticks_ms())) + P = lift_x(int_from_bytes(internal_key)) + print("4 {}".format(utime.ticks_ms())) + if P is None: + raise ValueError + print("5 {}".format(utime.ticks_ms())) + Q = point_add(P, point_mul(G, t)) + print("6 {}".format(utime.ticks_ms())) + return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q)) + + +# def taproot_tweak_seckey(seckey0, h): +# seckey0 = int_from_bytes(seckey0) +# P = point_mul(G, seckey0) +# seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0 +# t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h)) +# if t >= SECP256K1_ORDER: +# raise ValueError +# return bytes_from_int((seckey + t) % SECP256K1_ORDER) +# +# +# def taproot_tree_helper(script_tree): +# if isinstance(script_tree, tuple): +# leaf_version, script = script_tree +# h = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_script(script)) +# return ([((leaf_version, script), bytes())], h) +# left, left_h = taproot_tree_helper(script_tree[0]) +# right, right_h = taproot_tree_helper(script_tree[1]) +# ret = [(l, c + right_h) for l, c in left] + [(l, c + left_h) for l, c in right] +# if right_h < left_h: +# left_h, right_h = right_h, left_h +# return (ret, tagged_hash("TapBranch", left_h + right_h)) +# +# +def output_script(internal_pubkey, script_tree): + """Given a internal public key and a tree of scripts, compute the output script. + script_tree is either: + - a (leaf_version, script) tuple (leaf_version is 0xc0 for [[bip-0342.mediawiki|BIP342]] scripts) + - a list of two elements, each with the same structure as script_tree itself + - None + """ + if script_tree is None: + h = bytes() + else: + _, h = taproot_tree_helper(script_tree) + _, output_pubkey = tweak_internal_key(internal_pubkey, h) + return bytes([0x51, 0x20]) + output_pubkey diff --git a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py index b8c813041..0a916d133 100644 --- a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py @@ -25,41 +25,41 @@ async def search_for_address_task( from errors import Error from uasyncio import sleep_ms - try: - with stash.SensitiveValues() as sv: - if multisig_wallet: - # NOTE: Can't easily reverse order here, so this is slightly less efficient - for (curr_idx, paths, curr_address, script) in multisig_wallet.yield_addresses( - start_address_idx, - max_to_check, - change_idx=1 if is_change else 0): - # print('Multisig: curr_idx={}: paths={} curr_address = {}'.format(curr_idx, paths, curr_address)) + # try: + with stash.SensitiveValues() as sv: + if multisig_wallet: + # NOTE: Can't easily reverse order here, so this is slightly less efficient + for (curr_idx, paths, curr_address, script) in multisig_wallet.yield_addresses( + start_address_idx, + max_to_check, + change_idx=1 if is_change else 0): + # print('Multisig: curr_idx={}: paths={} curr_address = {}'.format(curr_idx, paths, curr_address)) - if curr_address == address: - # NOTE: Paths are the full paths of the addresses of each signer - await on_done(curr_idx, paths, None) - return - await sleep_ms(1) + if curr_address == address: + # NOTE: Paths are the full paths of the addresses of each signer + await on_done(curr_idx, paths, None) + return + await sleep_ms(1) - else: - r = range(start_address_idx, start_address_idx + max_to_check) - if reverse: - r = reversed(r) + else: + r = range(start_address_idx, start_address_idx + max_to_check) + if reverse: + r = reversed(r) - for curr_idx in r: - addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address - # print('Singlesig: addr_path={}'.format(addr_path)) - node = sv.derive_path(addr_path) - curr_address = sv.chain.address(node, addr_type) - # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, - # addr_type, curr_address)) - if curr_address == address: - await on_done(curr_idx, addr_path, None) - return - await sleep_ms(1) + for curr_idx in r: + addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address + # print('Singlesig: addr_path={}'.format(addr_path)) + node = sv.derive_path(addr_path) + curr_address = sv.chain.address(node, addr_type) + # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, + # addr_type, curr_address)) + if curr_address == address: + await on_done(curr_idx, addr_path, None) + return + await sleep_ms(1) - await on_done(-1, None, Error.ADDRESS_NOT_FOUND) - except Exception as e: - # print('EXCEPTION: e={}'.format(e)) - # Any address handling exceptions result in no address found - await on_done(-1, None, Error.ADDRESS_NOT_FOUND) + await on_done(-1, None, Error.ADDRESS_NOT_FOUND) + # except Exception as e: + # # print('EXCEPTION: e={}'.format(e)) + # # Any address handling exceptions result in no address found + # await on_done(-1, None, Error.ADDRESS_NOT_FOUND) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index be939039c..55e671097 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1442,12 +1442,4 @@ def escape_text(text): return text.replace("#", "##") -def hash_tap_tweak(data): - from serializations import sha256 - from public_constants import TAP_TWEAK_SHA256 - - tag_hash = a2b_hex(TAP_TWEAK_SHA256) - return sha256(tag_hash + tag_hash + data) - - # EOF diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 0606d4f67..a63389bdf 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -109,7 +109,7 @@ #define MICROPY_PY_BUILTINS_EXECFILE (MICROPY_ENABLE_COMPILER) #define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (1) #define MICROPY_PY_BUILTINS_INPUT (1) -#define MICROPY_PY_BUILTINS_POW3 (0) +#define MICROPY_PY_BUILTINS_POW3 (1) #define MICROPY_PY_BUILTINS_HELP (0) #ifndef MICROPY_PY_BUILTINS_HELP_TEXT #define MICROPY_PY_BUILTINS_HELP_TEXT stm32_help_text diff --git a/py/mpconfig.h b/py/mpconfig.h index 2c039bd88..537ff7e57 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -655,7 +655,7 @@ #define MICROPY_LONGINT_IMPL_MPZ (2) #ifndef MICROPY_LONGINT_IMPL -#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_NONE) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #endif #if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_LONGLONG @@ -1075,7 +1075,7 @@ typedef double mp_float_t; // Support for calls to pow() with 3 integer arguments #ifndef MICROPY_PY_BUILTINS_POW3 -#define MICROPY_PY_BUILTINS_POW3 (0) +#define MICROPY_PY_BUILTINS_POW3 (1) #endif // Whether to provide the help function From ceede1c007b41d750973c5df17e0974335443f6c Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 04:50:06 -0400 Subject: [PATCH 091/239] SFT-1708: added note about using trezor point_add and point_multiply --- ports/stm32/boards/Passport/modules/taproot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py index 7fd4ed47a..fddc02a70 100644 --- a/ports/stm32/boards/Passport/modules/taproot.py +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -120,6 +120,7 @@ def tweak_internal_key(internal_key, h): if P is None: raise ValueError print("5 {}".format(utime.ticks_ms())) + # TODO: use trezor point_add and point_multiply Q = point_add(P, point_mul(G, t)) print("6 {}".format(utime.ticks_ms())) return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q)) From 2bf5ec9c8e2a53e30991dfc35ee2f7796834788e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 05:00:49 -0400 Subject: [PATCH 092/239] SFT-1708: fixed license lengths --- ports/stm32/boards/Passport/modules/chains.py | 2 +- ports/stm32/boards/Passport/modules/taproot.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index c934cd603..4cbe3e58d 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -7,7 +7,7 @@ # (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard # and is covered by GPLv3 license found in COPYING. # -# chains.py - Magic values for the coins and altcoins we support +# chains.py - Magic values for the bitcoin testnet and mainnet chains # import trezorcrypto import tcc diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py index fddc02a70..c57a5fd2c 100644 --- a/ports/stm32/boards/Passport/modules/taproot.py +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -1,4 +1,10 @@ -# TODO: add BSD-2-Clause license for BIP340 and BSD-3-Clase for BIP341 +# SPDX-FileCopyrightText: © 2020 Pieter Wuille, Jonas Nick, Tim Ruffing +# SPDX-License-Identifier: BSD-2-Clause +# +# SPDX-FileCopyrightText: © 2020 Pieter Wuille, Jonas Nick, Anthony Towns +# SPDX-License-Identifier: BSD-3-Clause +# +# taproot.py - taproot helper functions import ubinascii import utime From 8e3ad87f8675dedde7ee86b75dfc05fa191019e8 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 16:52:16 -0400 Subject: [PATCH 093/239] SFT-1708: address verification on device is fast enough, still room for optimization --- .../modtrezorcrypto/modtrezorcrypto-ecdsa.h | 82 +++++++++++++++++++ .../extmod/modtrezorcrypto/modtrezorcrypto.c | 2 + .../stm32/boards/Passport/modules/taproot.py | 16 ++-- 3 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h new file mode 100644 index 000000000..fefebddcc --- /dev/null +++ b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h @@ -0,0 +1,82 @@ +/* + * This file is part of the TREZOR project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "py/obj.h" +#include "py/objstr.h" +#include "py/runtime.h" + +#include "ecdsa.h" +#include "secp256k1.h" +#include "bignum.h" + + +/// package: trezorcrypto.ecdsa + +/// def (k: bytes) -> multiplied_point: (bytes, bytes) +/// """ +/// Multiply a scalar by the generator point G +/// """ +STATIC mp_obj_t mod_trezorcrypto_ecdsa_scalar_multiply(mp_obj_t k_obj) { + + // Get k from object + mp_buffer_info_t k_buf; + mp_get_buffer_raise(k_obj, &k_buf, MP_BUFFER_READ); + + // Convert k to a bignum + bignum256 k; + bn_read_be((uint8_t *)k_buf.buf, &k); + + // Multiply + curve_point output; + int rv = scalar_multiply(&secp256k1, &k, &output); + if (rv != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("scalar multiply failed")); + } + + // Retrieve x and y bytes from output point + vstr_t x_vstr, y_vstr; + vstr_init_len(&x_vstr, 32); + vstr_init_len(&y_vstr, 32); + bn_write_be(&output.x, (uint8_t *)x_vstr.buf); + bn_write_be(&output.y, (uint8_t *)y_vstr.buf); + + // Put x and y bytes in a tuple + mp_obj_tuple_t * tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = mp_obj_new_str_from_vstr(&mp_type_bytes, &x_vstr); + tuple->items[1] = mp_obj_new_str_from_vstr(&mp_type_bytes, &y_vstr); + return MP_OBJ_FROM_PTR(tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mod_trezorcrypto_ecdsa_scalar_multiply_obj, + mod_trezorcrypto_ecdsa_scalar_multiply); + +STATIC const mp_rom_map_elem_t mod_trezorcrypto_ecdsa_globals_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ecdsa)}, + {MP_ROM_QSTR(MP_QSTR_scalar_multiply), + MP_ROM_PTR(&mod_trezorcrypto_ecdsa_scalar_multiply_obj)}, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_ecdsa_globals, + mod_trezorcrypto_ecdsa_globals_table); + +STATIC const mp_obj_module_t mod_trezorcrypto_ecdsa_module = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&mod_trezorcrypto_ecdsa_globals, +}; diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c index 816b951c3..47f07762b 100644 --- a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c +++ b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto.c @@ -46,6 +46,7 @@ static void wrapped_ui_wait_callback(uint32_t current, uint32_t total) { #include "modtrezorcrypto-chacha20poly1305.h" #include "modtrezorcrypto-crc.h" #include "modtrezorcrypto-curve25519.h" +#include "modtrezorcrypto-ecdsa.h" #if USE_KECCAK #include "modtrezorcrypto-ed25519.h" #endif @@ -78,6 +79,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorcrypto_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_aes), MP_ROM_PTR(&mod_trezorcrypto_AES_type)}, {MP_ROM_QSTR(MP_QSTR_bip32), MP_ROM_PTR(&mod_trezorcrypto_bip32_module)}, {MP_ROM_QSTR(MP_QSTR_bip39), MP_ROM_PTR(&mod_trezorcrypto_bip39_module)}, + {MP_ROM_QSTR(MP_QSTR_ecdsa), MP_ROM_PTR(&mod_trezorcrypto_ecdsa_module)}, #ifndef FOUNDATION_ADDITIONS {MP_ROM_QSTR(MP_QSTR_blake256), MP_ROM_PTR(&mod_trezorcrypto_Blake256_type)}, diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py index c57a5fd2c..db30512c2 100644 --- a/ports/stm32/boards/Passport/modules/taproot.py +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -8,6 +8,7 @@ import ubinascii import utime +from trezorcrypto import ecdsa # Set DEBUG to True to get a detailed debug output including # intermediate values during key generation, signing, and @@ -74,13 +75,10 @@ def point_add(P1, P2): return (x3, (lam * (x(P1) - x3) - y(P1)) % p) -def point_mul(P, n): - R = None - for i in range(256): - if (n >> i) & 1: - R = point_add(R, P) - P = point_add(P, P) - return R +def scalar_multiply(n): + n = bytes_from_int(n) + (x, y) = ecdsa.scalar_multiply(n) + return (int_from_bytes(x), int_from_bytes(y)) def bytes_from_int(x): @@ -126,8 +124,8 @@ def tweak_internal_key(internal_key, h): if P is None: raise ValueError print("5 {}".format(utime.ticks_ms())) - # TODO: use trezor point_add and point_multiply - Q = point_add(P, point_mul(G, t)) + # TODO: use trezor point_add + Q = point_add(P, scalar_multiply(t)) print("6 {}".format(utime.ticks_ms())) return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q)) From 0f7844955eea264160b1cc39656de1725acb31ef Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 18:31:50 -0400 Subject: [PATCH 094/239] SFT-1708: called trezor point_add, now takes 55ms per p2tr address with debug --- .../modtrezorcrypto/modtrezorcrypto-ecdsa.h | 52 +++++++++++++++++++ .../stm32/boards/Passport/modules/taproot.py | 20 +++---- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h index fefebddcc..6ae8013d1 100644 --- a/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h +++ b/extmod/trezor-firmware/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-ecdsa.h @@ -68,10 +68,62 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1( mod_trezorcrypto_ecdsa_scalar_multiply_obj, mod_trezorcrypto_ecdsa_scalar_multiply); +/// def (x1: bytes, y1: bytes, x2: bytes, y2: bytes) -> added_point: (bytes, bytes) +/// """ +/// Add two ECDSA points +/// """ +STATIC mp_obj_t mod_trezorcrypto_ecdsa_point_add(size_t n_args, const mp_obj_t * args) { + + // Build the points + curve_point p1, p2; + + // Get the coordinates from their objects + mp_buffer_info_t x1_buf; + mp_get_buffer_raise(args[0], &x1_buf, MP_BUFFER_READ); + + mp_buffer_info_t y1_buf; + mp_get_buffer_raise(args[1], &y1_buf, MP_BUFFER_READ); + + mp_buffer_info_t x2_buf; + mp_get_buffer_raise(args[2], &x2_buf, MP_BUFFER_READ); + + mp_buffer_info_t y2_buf; + mp_get_buffer_raise(args[3], &y2_buf, MP_BUFFER_READ); + + // Convert coordinates to bignums + bn_read_be((uint8_t *)x1_buf.buf, &p1.x); + bn_read_be((uint8_t *)y1_buf.buf, &p1.y); + bn_read_be((uint8_t *)x2_buf.buf, &p2.x); + bn_read_be((uint8_t *)y2_buf.buf, &p2.y); + + // Add + point_add(&secp256k1, &p1, &p2); + + // Retrieve x and y bytes from output point + vstr_t x_vstr, y_vstr; + vstr_init_len(&x_vstr, 32); + vstr_init_len(&y_vstr, 32); + bn_write_be(&p2.x, (uint8_t *)x_vstr.buf); + bn_write_be(&p2.y, (uint8_t *)y_vstr.buf); + + // Put x and y bytes in a tuple + mp_obj_tuple_t * tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = mp_obj_new_str_from_vstr(&mp_type_bytes, &x_vstr); + tuple->items[1] = mp_obj_new_str_from_vstr(&mp_type_bytes, &y_vstr); + return MP_OBJ_FROM_PTR(tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( + mod_trezorcrypto_ecdsa_point_add_obj, + 4, + 4, + mod_trezorcrypto_ecdsa_point_add); + STATIC const mp_rom_map_elem_t mod_trezorcrypto_ecdsa_globals_table[] = { {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ecdsa)}, {MP_ROM_QSTR(MP_QSTR_scalar_multiply), MP_ROM_PTR(&mod_trezorcrypto_ecdsa_scalar_multiply_obj)}, + {MP_ROM_QSTR(MP_QSTR_point_add), + MP_ROM_PTR(&mod_trezorcrypto_ecdsa_point_add_obj)}, }; STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_ecdsa_globals, mod_trezorcrypto_ecdsa_globals_table); diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py index db30512c2..a543ceda3 100644 --- a/ports/stm32/boards/Passport/modules/taproot.py +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -61,18 +61,14 @@ def y(P): def point_add(P1, P2): - if P1 is None: - return P2 - if P2 is None: - return P1 - if (x(P1) == x(P2)) and (y(P1) != y(P2)): - return None - if P1 == P2: - lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p - else: - lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p - x3 = (lam * lam - x(P1) - x(P2)) % p - return (x3, (lam * (x(P1) - x3) - y(P1)) % p) + + x1 = bytes_from_int(x(P1)) + y1 = bytes_from_int(y(P1)) + x2 = bytes_from_int(x(P2)) + y2 = bytes_from_int(y(P2)) + + (x3, y3) = ecdsa.point_add(x1, y1, x2, y2) + return (int_from_bytes(x3), int_from_bytes(y3)) def scalar_multiply(n): From 49ccb2bd853690b3292edd5270b06c8e3ead411d Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 18:33:14 -0400 Subject: [PATCH 095/239] SFT-1708: removed debug --- ports/stm32/boards/Passport/modules/chains.py | 2 -- ports/stm32/boards/Passport/modules/taproot.py | 6 ------ 2 files changed, 8 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 4cbe3e58d..d3c4ad306 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -122,9 +122,7 @@ def address(cls, node, addr_fmt): elif addr_fmt & AFC_BECH32M: pubkey = node.public_key() internal_key = pubkey[1::] - print("internal_key: {}".format(b2a_hex(internal_key))) output_key = output_script(internal_key, None)[2::] - print("output_key: {}".format(b2a_hex(output_key))) return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, output_key) # see bip-141, "P2WPKH nested in BIP16 P2SH" section diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py index a543ceda3..a988a70e8 100644 --- a/ports/stm32/boards/Passport/modules/taproot.py +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -109,20 +109,14 @@ def has_even_y(P): def tweak_internal_key(internal_key, h): - print("1 {}".format(utime.ticks_ms())) t = int_from_bytes(hash_tap_tweak(internal_key + h)) - print("2 {}".format(utime.ticks_ms())) if t >= SECP256K1_ORDER: raise ValueError - print("3 {}".format(utime.ticks_ms())) P = lift_x(int_from_bytes(internal_key)) - print("4 {}".format(utime.ticks_ms())) if P is None: raise ValueError - print("5 {}".format(utime.ticks_ms())) # TODO: use trezor point_add Q = point_add(P, scalar_multiply(t)) - print("6 {}".format(utime.ticks_ms())) return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q)) From 89a4204bc129f8db8b8a83ca5caac243cfd8f7ee Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 14 Oct 2023 18:34:12 -0400 Subject: [PATCH 096/239] SFT-1708: added foundation license to taproot lib --- ports/stm32/boards/Passport/modules/taproot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py index a988a70e8..bc22b7f2b 100644 --- a/ports/stm32/boards/Passport/modules/taproot.py +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# # SPDX-FileCopyrightText: © 2020 Pieter Wuille, Jonas Nick, Tim Ruffing # SPDX-License-Identifier: BSD-2-Clause # From 8ef96401ab60e44db6eb72dda9ff2241fe25e9f3 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 15 Oct 2023 17:36:35 -0400 Subject: [PATCH 097/239] SFT-1708: restored exception handling in search_for_address_task --- ports/stm32/boards/Passport/modules/chains.py | 1 - .../modules/tasks/search_for_address_task.py | 68 +++++++++---------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index d3c4ad306..ce618bc24 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -98,7 +98,6 @@ def p2sh_address(cls, addr_fmt, witdeem_script): @classmethod def address(cls, node, addr_fmt): - from ubinascii import hexlify as b2a_hex # return a human-readable, properly formatted address if addr_fmt == AF_CLASSIC: diff --git a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py index 0a916d133..b8c813041 100644 --- a/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/search_for_address_task.py @@ -25,41 +25,41 @@ async def search_for_address_task( from errors import Error from uasyncio import sleep_ms - # try: - with stash.SensitiveValues() as sv: - if multisig_wallet: - # NOTE: Can't easily reverse order here, so this is slightly less efficient - for (curr_idx, paths, curr_address, script) in multisig_wallet.yield_addresses( - start_address_idx, - max_to_check, - change_idx=1 if is_change else 0): - # print('Multisig: curr_idx={}: paths={} curr_address = {}'.format(curr_idx, paths, curr_address)) + try: + with stash.SensitiveValues() as sv: + if multisig_wallet: + # NOTE: Can't easily reverse order here, so this is slightly less efficient + for (curr_idx, paths, curr_address, script) in multisig_wallet.yield_addresses( + start_address_idx, + max_to_check, + change_idx=1 if is_change else 0): + # print('Multisig: curr_idx={}: paths={} curr_address = {}'.format(curr_idx, paths, curr_address)) - if curr_address == address: - # NOTE: Paths are the full paths of the addresses of each signer - await on_done(curr_idx, paths, None) - return - await sleep_ms(1) + if curr_address == address: + # NOTE: Paths are the full paths of the addresses of each signer + await on_done(curr_idx, paths, None) + return + await sleep_ms(1) - else: - r = range(start_address_idx, start_address_idx + max_to_check) - if reverse: - r = reversed(r) + else: + r = range(start_address_idx, start_address_idx + max_to_check) + if reverse: + r = reversed(r) - for curr_idx in r: - addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address - # print('Singlesig: addr_path={}'.format(addr_path)) - node = sv.derive_path(addr_path) - curr_address = sv.chain.address(node, addr_type) - # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, - # addr_type, curr_address)) - if curr_address == address: - await on_done(curr_idx, addr_path, None) - return - await sleep_ms(1) + for curr_idx in r: + addr_path = '{}/{}/{}'.format(path, is_change, curr_idx) # Zero for non-change address + # print('Singlesig: addr_path={}'.format(addr_path)) + node = sv.derive_path(addr_path) + curr_address = sv.chain.address(node, addr_type) + # print(' curr_idx={}: path={} addr_type={} curr_address = {}'.format(curr_idx, addr_path, + # addr_type, curr_address)) + if curr_address == address: + await on_done(curr_idx, addr_path, None) + return + await sleep_ms(1) - await on_done(-1, None, Error.ADDRESS_NOT_FOUND) - # except Exception as e: - # # print('EXCEPTION: e={}'.format(e)) - # # Any address handling exceptions result in no address found - # await on_done(-1, None, Error.ADDRESS_NOT_FOUND) + await on_done(-1, None, Error.ADDRESS_NOT_FOUND) + except Exception as e: + # print('EXCEPTION: e={}'.format(e)) + # Any address handling exceptions result in no address found + await on_done(-1, None, Error.ADDRESS_NOT_FOUND) From b503de7f61e2103d3cf3f4bc3fb4ec157b8a22f9 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 16 Oct 2023 22:04:53 -0400 Subject: [PATCH 098/239] SFT-2797: added battery page to device menu --- ports/stm32/boards/Passport/manifest.py | 1 + ports/stm32/boards/Passport/manifest_dev.py | 1 - .../Passport/modules/developer/__init__.py | 1 - .../modules/developer/battery_page.py | 42 ------------------- ports/stm32/boards/Passport/modules/menus.py | 5 +-- .../boards/Passport/modules/pages/__init__.py | 1 + 6 files changed, 4 insertions(+), 47 deletions(-) delete mode 100644 ports/stm32/boards/Passport/modules/developer/battery_page.py diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index a9c512220..3c8bc6caa 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -168,6 +168,7 @@ ('pages/__init__.py', 'pages/account_details_page.py', 'pages/backup_code_page.py', + 'pages/battery_page.py', 'pages/brandmark_page.py', 'pages/chooser_page.py', 'pages/color_picker_page.py', diff --git a/ports/stm32/boards/Passport/manifest_dev.py b/ports/stm32/boards/Passport/manifest_dev.py index 484515427..66dd08f3d 100644 --- a/ports/stm32/boards/Passport/manifest_dev.py +++ b/ports/stm32/boards/Passport/manifest_dev.py @@ -6,7 +6,6 @@ freeze('$(MPY_DIR)/ports/stm32/boards/Passport/modules', ('developer/__init__.py', - 'developer/battery_page.py', 'developer/delete_derived_keys_flow.py', 'developer/developer_functions_flow.py', 'developer/fcc_test_flow.py', diff --git a/ports/stm32/boards/Passport/modules/developer/__init__.py b/ports/stm32/boards/Passport/modules/developer/__init__.py index 8434e6ffc..96e4e2bcf 100644 --- a/ports/stm32/boards/Passport/modules/developer/__init__.py +++ b/ports/stm32/boards/Passport/modules/developer/__init__.py @@ -3,7 +3,6 @@ # # __init__.py -from .battery_page import * from .delete_derived_keys_flow import * from .developer_functions_flow import * from .fcc_copy_files_task import * diff --git a/ports/stm32/boards/Passport/modules/developer/battery_page.py b/ports/stm32/boards/Passport/modules/developer/battery_page.py deleted file mode 100644 index 3b4d4f8a6..000000000 --- a/ports/stm32/boards/Passport/modules/developer/battery_page.py +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Foundation Devices, Inc. -# SPDX-License-Identifier: GPL-3.0-or-later -# -# info_page.py - -import lvgl as lv -from pages import StatusPage -import microns - - -class BatteryPage(StatusPage): - def __init__(self, text=None, card_header={'title': 'Battery'}, - statusbar=None, left_micron=None, right_micron=microns.Checkmark): - from common import ui - - super().__init__( - show_progress=True, - percent=ui.battery_level, - card_header=card_header, - statusbar=statusbar, - left_micron=left_micron, - right_micron=right_micron) - - def update_battery(self): - from common import ui - self.set_progress(ui.battery_level) - - def on_timer(self, _t): - self.update_battery() - - def attach(self, group): - super().attach(group) - - # Create a timer to update the battery - self.timer = lv.timer_create(self.on_timer, 5000, None) - - def detach(self): - super().detach() - - if self.timer is not None: - self.timer._del() - self.timer = None diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index fd64c03be..9f35e7fe4 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -111,7 +111,7 @@ def plus_menu(): def device_menu(): from flows import AboutFlow, ChangePINFlow - from pages import AutoShutdownSettingPage, BrightnessSettingPage + from pages import AutoShutdownSettingPage, BrightnessSettingPage, BatteryPage from utils import is_logged_in return [ @@ -119,6 +119,7 @@ def device_menu(): {'icon': 'ICON_COUNTDOWN', 'label': 'Auto-Shutdown', 'page': AutoShutdownSettingPage}, {'icon': 'ICON_PIN', 'label': 'Change PIN', 'flow': ChangePINFlow, 'is_visible': is_logged_in}, {'icon': 'ICON_INFO', 'label': 'About', 'flow': AboutFlow}, + {'icon': 'ICON_BATTERY', 'label': 'Battery', 'page': BatteryPage}, ] @@ -370,7 +371,6 @@ def developer_menu(): SetInitialPINFlow, ) from developer import ( - BatteryPage, DeleteDerivedKeysFlow, DeveloperFunctionsFlow, FCCTestFlow, @@ -381,7 +381,6 @@ def developer_menu(): from foundation import ur return [ - {'icon': 'ICON_BATTERY', 'label': 'Battery', 'page': BatteryPage}, {'icon': 'ICON_ERASE', 'label': 'Factory Reset', 'flow': DeveloperFunctionsFlow, 'args': {'fn_name': 'factory_reset'}}, {'icon': 'ICON_RETRY', 'label': 'Spin!!!', 'flow': SpinDelayFlow, 'args': {'delay_ms': 10000}}, diff --git a/ports/stm32/boards/Passport/modules/pages/__init__.py b/ports/stm32/boards/Passport/modules/pages/__init__.py index aa9936ee2..d9b634e3a 100644 --- a/ports/stm32/boards/Passport/modules/pages/__init__.py +++ b/ports/stm32/boards/Passport/modules/pages/__init__.py @@ -14,6 +14,7 @@ from .address_type_chooser_page import * from .auto_shutdown_setting_page import * from .backup_code_page import * +from .battery_page import * from .brandmark_page import * from .brightness_setting_page import * from .chain_setting_page import * From 64361285c42406660fa5cdc1c6fd831375e84382 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 16 Oct 2023 21:29:49 -0400 Subject: [PATCH 099/239] SFT-36: added device names --- ports/stm32/boards/Passport/manifest.py | 1 + .../boards/Passport/modules/flows/__init__.py | 1 + .../Passport/modules/flows/login_flow.py | 2 +- .../modules/flows/rename_device_flow.py | 38 +++++++++++++++++++ ports/stm32/boards/Passport/modules/menus.py | 3 +- 5 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 ports/stm32/boards/Passport/modules/flows/rename_device_flow.py diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index 3c8bc6caa..b67a2cdb7 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -132,6 +132,7 @@ 'flows/remove_dev_pubkey_flow.py', 'flows/rename_account_flow.py', 'flows/rename_derived_key_flow.py', + 'flows/rename_device_flow.py', 'flows/rename_multisig_flow.py', 'flows/reset_pin_flow.py', 'flows/restore_backup_flow.py', diff --git a/ports/stm32/boards/Passport/modules/flows/__init__.py b/ports/stm32/boards/Passport/modules/flows/__init__.py index 6d38292ef..b1d89ed4b 100644 --- a/ports/stm32/boards/Passport/modules/flows/__init__.py +++ b/ports/stm32/boards/Passport/modules/flows/__init__.py @@ -55,6 +55,7 @@ from .remove_dev_pubkey_flow import * from .rename_account_flow import * from .rename_derived_key_flow import * +from .rename_device_flow import * from .rename_multisig_flow import * from .reset_pin_flow import * from .restore_backup_flow import * diff --git a/ports/stm32/boards/Passport/modules/flows/login_flow.py b/ports/stm32/boards/Passport/modules/flows/login_flow.py index 824d2fb11..af32cbdb8 100644 --- a/ports/stm32/boards/Passport/modules/flows/login_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/login_flow.py @@ -23,7 +23,7 @@ def __init__(self): async def enter_pin(self): try: (self.pin, is_done) = await PINEntryPage( - card_header={'title': 'Enter PIN'}, + card_header={'title': common.settings.get('device_name', None) or 'Enter PIN'}, security_words_message='Recognize these Security Words?', left_micron=microns.Shutdown, right_micron=microns.Checkmark, diff --git a/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py b/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py new file mode 100644 index 000000000..0830589c5 --- /dev/null +++ b/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# +# name_device_flow.py - Name or rename the device + +from flows import Flow +from serializations import sha256 +from pages import TextInputPage, ErrorPage +from common import settings +from constants import MAX_ACCOUNT_NAME_LEN +import microns + + +class RenameDeviceFlow(Flow): + def __init__(self): + super().__init__(initial_state=self.rename_device, name='NameDeviceFLow') + + async def rename_device(self): + name = settings.get('device_name', None) or '' + result = await TextInputPage(initial_text=name, + max_length=MAX_ACCOUNT_NAME_LEN, + left_micron=microns.Cancel, + right_micron=microns.Checkmark).show() + if result is None: + self.set_result(False) + return + + if len(result) == 0: + settings.set('device_name', None) + self.set_result(True) + return + + if sha256(result[:4]) == settings.get('pin_prefix_hash'): + await ErrorPage("Don't use your pin as the device name, because it will be shown on the login page.").show() + return + + settings.set('device_name', result) + self.set_result(True) diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index 9f35e7fe4..2435deaf6 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -110,7 +110,7 @@ def plus_menu(): def device_menu(): - from flows import AboutFlow, ChangePINFlow + from flows import AboutFlow, ChangePINFlow, RenameDeviceFlow from pages import AutoShutdownSettingPage, BrightnessSettingPage, BatteryPage from utils import is_logged_in @@ -120,6 +120,7 @@ def device_menu(): {'icon': 'ICON_PIN', 'label': 'Change PIN', 'flow': ChangePINFlow, 'is_visible': is_logged_in}, {'icon': 'ICON_INFO', 'label': 'About', 'flow': AboutFlow}, {'icon': 'ICON_BATTERY', 'label': 'Battery', 'page': BatteryPage}, + {'icon': 'ICON_SIGN', 'label': 'Device Name', 'flow': RenameDeviceFlow}, ] From 1bc008a99b7b99e70782cb0228f6f56b58eb514f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 16:49:54 -0400 Subject: [PATCH 100/239] SFT-36: made sure pin and device name can't be the same upper or lowercase --- ports/stm32/boards/Passport/modules/flows/change_pin_flow.py | 3 +++ .../stm32/boards/Passport/modules/flows/rename_device_flow.py | 4 +++- .../boards/Passport/modules/flows/set_initial_pin_flow.py | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/change_pin_flow.py b/ports/stm32/boards/Passport/modules/flows/change_pin_flow.py index 9f67d1b00..cb3475b04 100644 --- a/ports/stm32/boards/Passport/modules/flows/change_pin_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/change_pin_flow.py @@ -9,6 +9,8 @@ from utils import spinner_task from translations import t, T import microns +from common import settings +from serializations import sha256 class ChangePINFlow(Flow): @@ -58,6 +60,7 @@ async def change_pin(self): (result, error) = await spinner_task('Changing PIN', change_pin_task, args=[self.old_pin, self.new_pin]) if result: + settings.set_volatile('pin_prefix_hash', sha256(self.new_pin[:4])) self.goto(self.show_success) else: self.goto(self.show_error) diff --git a/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py b/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py index 0830589c5..3f2942be1 100644 --- a/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py @@ -30,7 +30,9 @@ async def rename_device(self): self.set_result(True) return - if sha256(result[:4]) == settings.get('pin_prefix_hash'): + prefix = result[:4] + hashes = [sha256(prefix), sha256(prefix.upper()), sha256(prefix.lower())] + if settings.get('pin_prefix_hash') in hashes: await ErrorPage("Don't use your pin as the device name, because it will be shown on the login page.").show() return diff --git a/ports/stm32/boards/Passport/modules/flows/set_initial_pin_flow.py b/ports/stm32/boards/Passport/modules/flows/set_initial_pin_flow.py index ff53e5e6e..c7e619766 100644 --- a/ports/stm32/boards/Passport/modules/flows/set_initial_pin_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/set_initial_pin_flow.py @@ -51,6 +51,8 @@ async def confirm_new_pin(self): from pages import PINEntryPage from tasks import set_initial_pin_task, login_task from utils import spinner_task + from common import settings + from serializations import sha256 (confirmed_pin, is_done) = await PINEntryPage( card_header={'title': 'Confirm PIN'}, @@ -61,6 +63,7 @@ async def confirm_new_pin(self): self.back() else: if self.new_pin == confirmed_pin: + settings.set_volatile('pin_prefix_hash', sha256(self.new_pin[:4])) (result, error) = await spinner_task('Setting initial PIN', set_initial_pin_task, args=[self.new_pin]) From 966d69e4d59efe0e30793598a21aff4a9eaaf640 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 17:00:43 -0400 Subject: [PATCH 101/239] SFT-36: moved device name to statusbar --- ports/stm32/boards/Passport/modules/flows/login_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/login_flow.py b/ports/stm32/boards/Passport/modules/flows/login_flow.py index af32cbdb8..12dcf309f 100644 --- a/ports/stm32/boards/Passport/modules/flows/login_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/login_flow.py @@ -23,7 +23,8 @@ def __init__(self): async def enter_pin(self): try: (self.pin, is_done) = await PINEntryPage( - card_header={'title': common.settings.get('device_name', None) or 'Enter PIN'}, + card_header={'title': 'Enter PIN'}, + statusbar={'title': common.settings.get('device_name', None)}, security_words_message='Recognize these Security Words?', left_micron=microns.Shutdown, right_micron=microns.Checkmark, From f4be9528db857de05065d654abf30a988fe4ece0 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 17:15:53 -0400 Subject: [PATCH 102/239] SFT-36: added device name to envoy connection --- ports/stm32/boards/Passport/modules/wallets/envoy.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/envoy.py b/ports/stm32/boards/Passport/modules/wallets/envoy.py index 920ca29b8..bd27b801e 100644 --- a/ports/stm32/boards/Passport/modules/wallets/envoy.py +++ b/ports/stm32/boards/Passport/modules/wallets/envoy.py @@ -65,6 +65,8 @@ def create_envoy_export(sw_wallet=None, if acct is not None: acct_name = acct.get('name', '') + device_name = common.settings.get('device_name', '') + rv = dict(derivation=acct_path, xfp=xfp, xpub=xpub, @@ -72,7 +74,8 @@ def create_envoy_export(sw_wallet=None, acct_num=acct_num, hw_version=1.2 if passport.IS_COLOR else 1, fw_version=fw_version, - serial=serial_num) + serial=serial_num, + device_name=device_name) # Return the possible account mappings that the exported wallet can choose from # When we get the address back, we can determine the 'fmt' from the address and then look it up to From 18ba4595d161959147abaf47bb5aceeeca9073a3 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 18 Oct 2023 11:58:34 -0400 Subject: [PATCH 103/239] SFT-36: uppercased device name on login page --- ports/stm32/boards/Passport/modules/flows/login_flow.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/login_flow.py b/ports/stm32/boards/Passport/modules/flows/login_flow.py index 12dcf309f..c5bd7c201 100644 --- a/ports/stm32/boards/Passport/modules/flows/login_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/login_flow.py @@ -21,10 +21,15 @@ def __init__(self): super().__init__(initial_state=self.enter_pin, name='LoginFlow') async def enter_pin(self): + + device_name = common.settings.get('device_name', None) + if device_name is not None: + device_name = device_name.upper() + try: (self.pin, is_done) = await PINEntryPage( card_header={'title': 'Enter PIN'}, - statusbar={'title': common.settings.get('device_name', None)}, + statusbar={'title': device_name}, security_words_message='Recognize these Security Words?', left_micron=microns.Shutdown, right_micron=microns.Checkmark, From adb6584068e203d0e185cf372bddfb6a91d04df1 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 19 Oct 2023 12:52:50 -0400 Subject: [PATCH 104/239] SFT-36: about page to the bottom of device list --- ports/stm32/boards/Passport/modules/menus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index 2435deaf6..c1f891cce 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -118,9 +118,9 @@ def device_menu(): {'icon': 'ICON_BRIGHTNESS', 'label': 'Screen Brightness', 'page': BrightnessSettingPage}, {'icon': 'ICON_COUNTDOWN', 'label': 'Auto-Shutdown', 'page': AutoShutdownSettingPage}, {'icon': 'ICON_PIN', 'label': 'Change PIN', 'flow': ChangePINFlow, 'is_visible': is_logged_in}, - {'icon': 'ICON_INFO', 'label': 'About', 'flow': AboutFlow}, {'icon': 'ICON_BATTERY', 'label': 'Battery', 'page': BatteryPage}, {'icon': 'ICON_SIGN', 'label': 'Device Name', 'flow': RenameDeviceFlow}, + {'icon': 'ICON_INFO', 'label': 'About', 'flow': AboutFlow}, ] From 3646bd4e0b3ace57791b345a510364569cfc5d09 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 16 Oct 2023 22:13:28 -0400 Subject: [PATCH 105/239] SFT-2270: removed wasabi --- ports/stm32/boards/Passport/modules/wallets/sw_wallets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/sw_wallets.py b/ports/stm32/boards/Passport/modules/wallets/sw_wallets.py index 4dd4f2bb2..dbba2b9a7 100644 --- a/ports/stm32/boards/Passport/modules/wallets/sw_wallets.py +++ b/ports/stm32/boards/Passport/modules/wallets/sw_wallets.py @@ -20,7 +20,7 @@ from .simple_bitcoin_wallet import SimpleBitcoinWallet from .sparrow import SparrowWallet from .specter import SpecterWallet -from .wasabi import WasabiWallet +# from .wasabi import WasabiWallet # Array of all supported software wallets and their attributes. # Used to build wallet menus and drive their behavior. @@ -41,5 +41,5 @@ SimpleBitcoinWallet, SparrowWallet, SpecterWallet, - WasabiWallet, + # WasabiWallet, ] From 01fc7a94ff0aa1c478e5d01127b61b40f6323094 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 16 Oct 2023 22:16:40 -0400 Subject: [PATCH 106/239] SFT-2270: removed wasabi from manifest --- ports/stm32/boards/Passport/manifest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index b67a2cdb7..c803e03ef 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -358,7 +358,6 @@ 'wallets/sw_wallets.py', 'wallets/utils.py', 'wallets/vault.py', - 'wallets/wasabi.py', 'wallets/keeper.py')) # Extensions From c2528e8447a016d5ca335af1b0d429d24fcac4c7 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 18 Oct 2023 01:23:03 -0400 Subject: [PATCH 107/239] SFT-2270: removed wasabi file --- .../Passport/modules/wallets/sw_wallets.py | 2 - .../boards/Passport/modules/wallets/wasabi.py | 61 ------------------- 2 files changed, 63 deletions(-) delete mode 100644 ports/stm32/boards/Passport/modules/wallets/wasabi.py diff --git a/ports/stm32/boards/Passport/modules/wallets/sw_wallets.py b/ports/stm32/boards/Passport/modules/wallets/sw_wallets.py index dbba2b9a7..0fa923854 100644 --- a/ports/stm32/boards/Passport/modules/wallets/sw_wallets.py +++ b/ports/stm32/boards/Passport/modules/wallets/sw_wallets.py @@ -20,7 +20,6 @@ from .simple_bitcoin_wallet import SimpleBitcoinWallet from .sparrow import SparrowWallet from .specter import SpecterWallet -# from .wasabi import WasabiWallet # Array of all supported software wallets and their attributes. # Used to build wallet menus and drive their behavior. @@ -41,5 +40,4 @@ SimpleBitcoinWallet, SparrowWallet, SpecterWallet, - # WasabiWallet, ] diff --git a/ports/stm32/boards/Passport/modules/wallets/wasabi.py b/ports/stm32/boards/Passport/modules/wallets/wasabi.py deleted file mode 100644 index 37c677a89..000000000 --- a/ports/stm32/boards/Passport/modules/wallets/wasabi.py +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-FileCopyrightText: © 2021 Foundation Devices, Inc. -# SPDX-License-Identifier: GPL-3.0-or-later -# -# SPDX-FileCopyrightText: 2018 Coinkite, Inc. -# SPDX-License-Identifier: GPL-3.0-only -# -# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard -# and is covered by GPLv3 license found in COPYING. -# -# wasabi.py - Wasabi wallet -# - -from public_constants import AF_P2WPKH, AF_CLASSIC -import chains -import stash -import ujson -from utils import xfp2str, to_str -from common import settings, system - - -def create_wasabi_export(sw_wallet=None, - addr_type=None, - acct_num=0, - multisig=False, - legacy=False, - export_mode='qr', - qr_type=None): - # Generate the data for a JSON file which Wasabi can open directly as a new wallet. - - chain = chains.current_chain() - - with stash.SensitiveValues() as sv: - acct_path = "m/84'/{coin_type}'/{acct}'".format(coin_type=chain.b44_cointype, acct=acct_num) - node = sv.derive_path(acct_path) - xfp = xfp2str(settings.get('xfp')) - xpub = chain.serialize_public(node, AF_CLASSIC) - - assert chain.ctype in {'BTC', 'TBTC'}, "Only Bitcoin supported" - - (fw_version, _, _, _, _) = system.get_software_info() - - rv = dict(MasterFingerprint=xfp, - ExtPubKey=xpub, - FirmwareVersion=fw_version) - - accts = [{'fmt': AF_P2WPKH, 'deriv': acct_path, 'acct': acct_num}] - msg = ujson.dumps(rv) - # print('msg={}'.format(to_str(msg))) - return (msg, accts) - - -WasabiWallet = { - 'label': 'Wasabi', - 'sig_types': [ - {'id': 'single-sig', 'label': 'Single-sig', 'addr_type': AF_P2WPKH, 'create_wallet': create_wasabi_export}, - ], - 'export_modes': [ - {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-wasabi.json', - 'filename_pattern_multisig': '{xfp}-wasabi-multisig.json'} - ] -} From 8de0a30ad612b5280aa1b74cb6b0442addcc9a54 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 18 Oct 2023 19:53:40 -0400 Subject: [PATCH 108/239] SFT-2887: improve address display with blocks of black and grey text --- .../modules/flows/sign_psbt_common_flow.py | 4 ++- .../modules/flows/verify_address_flow.py | 6 ++--- ports/stm32/boards/Passport/modules/utils.py | 25 +++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py index 4e3f7385c..c14855c38 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py @@ -15,8 +15,8 @@ from pages.chooser_page import ChooserPage from styles.colors import HIGHLIGHT_TEXT_HEX, BLACK_HEX from tasks import sign_psbt_task, validate_psbt_task -from utils import spinner_task, recolor import gc +from utils import spinner_task, recolor, stylize_address class SignPsbtCommonFlow(Flow): @@ -194,6 +194,8 @@ def render_output(self, o): val = ' '.join(self.chain.render_value(o.nValue)) dest = self.chain.render_address(o.scriptPubKey) + dest = stylize_address(dest) + return '\n{}\n{}\n\n{}\n{}'.format( recolor(HIGHLIGHT_TEXT_HEX, 'Amount'), val, diff --git a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py index 7340dddf9..767500015 100644 --- a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py @@ -227,7 +227,7 @@ async def not_found(self): async def found(self): from pages import SuccessPage, LongSuccessPage - from utils import save_next_addr, format_btc_address + from utils import save_next_addr, format_btc_address, stylize_address import passport from common import settings import chains @@ -239,12 +239,12 @@ async def found(self): settings.get('xfp'), chains.current_chain().b44_cointype, self.found_is_change) - address = format_btc_address(self.address, self.addr_type) + address = stylize_address(self.address) msg = '''{} {} Address {}'''.format( - self.address, + address, 'Change' if self.found_is_change == 1 else 'Receive', self.found_addr_idx) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 55e671097..4069c883d 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1442,4 +1442,29 @@ def escape_text(text): return text.replace("#", "##") +def stylize_address(address): + from styles.colors import TEXT_GREY_HEX, BLACK_HEX + + stylized = '' + colors = [BLACK_HEX, TEXT_GREY_HEX] + color_index = 0 + block = '' + + for i in range(len(address)): + # Every 4 characters, append the recolored block of characters + if i % 4 == 0 and i != 0: + stylized += recolor(colors[color_index], block) + block = '' + color_index ^= 1 + # Every 4 blocks, start a new line + if i % 16 == 0: + stylized += '\n' + else: + stylized += ' ' + block += address[i] + stylized += recolor(colors[color_index], block) + + return stylized + + # EOF From 241ce1377bd4430073bbb48844f3e63d99d57768 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 19 Oct 2023 13:49:52 -0400 Subject: [PATCH 109/239] SFT-2910: fixed account creation indices and crashing --- ports/stm32/boards/Passport/modules/wallets/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/wallets/utils.py b/ports/stm32/boards/Passport/modules/wallets/utils.py index 94eb16181..948766587 100644 --- a/ports/stm32/boards/Passport/modules/wallets/utils.py +++ b/ports/stm32/boards/Passport/modules/wallets/utils.py @@ -16,6 +16,11 @@ def get_next_account_num(xfp): accounts = get_accounts_by_xfp(xfp) acct_nums = [] + + # Mock out account 0, it always exists but may not be saved + if len(accounts) == 0 or accounts[0]['acct_num'] != 0: + acct_nums.append(0) + for acct in accounts: acct_nums.append(acct['acct_num']) From 1ff0895fd6c1a7156fa1581027990233d968113e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 16 Oct 2023 20:02:42 -0400 Subject: [PATCH 110/239] SFT-2884: added firmware filename to firmware info screen --- .../boards/Passport/modules/flows/update_firmware_flow.py | 4 +++- .../Passport/modules/flows/view_current_firmware_flow.py | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py b/ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py index 5d6987f41..b79a0fa77 100644 --- a/ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/update_firmware_flow.py @@ -23,6 +23,7 @@ def __init__(self, reset_after=True, statusbar=None): self.error = None self.reset_after = reset_after self.statusbar = statusbar + self.filename = None async def on_done(self, error=None): self.error = error @@ -39,7 +40,7 @@ async def choose_file(self): self.set_result(False) return - _filename, full_path, is_folder = result + self.filename, full_path, is_folder = result if not is_folder: self.update_file_path = full_path self.goto(self.show_firmware_details) @@ -112,6 +113,7 @@ async def copy_to_flash(self): # we are updating, then reboot (curr_version, _, _, _, _) = system.get_software_info() settings.set('update', '{}->{}'.format(curr_version, self.version)) # old_version->new_version + settings.set('firmware_title', self.filename) settings.save() if self.reset_after: diff --git a/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py b/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py index 16d24f12e..45be31846 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py @@ -13,12 +13,16 @@ def __init__(self): super().__init__(initial_state=self.show_info, name='ViewCurrentFirmwareFlow') async def show_info(self): - from common import system, ui + from common import system, ui, settings (fw_version, fw_timestamp, boot_counter, user_signed, fw_date) = system.get_software_info() msg = '''Version {fw_version} {fw_date}'''.format(fw_version=fw_version, fw_date=fw_date) + title = settings.get("firmware_title", None) + if title is not None: + msg += '\n{}'.format(title) + # if user_signed: # msg += '\nSigned by User' From a58b34cfac8d8ee9b5c3d0a35e08f0c8dd92b63e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 16:00:57 -0400 Subject: [PATCH 111/239] SFT-2884: cut off "-passport.bin" from firmware display name --- .../boards/Passport/modules/flows/view_current_firmware_flow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py b/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py index 45be31846..0cc0f2498 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py @@ -21,6 +21,8 @@ async def show_info(self): title = settings.get("firmware_title", None) if title is not None: + if len(title) > 13: + title = title[:-13] msg += '\n{}'.format(title) # if user_signed: From e9671f3cb99c1e07123c93f2c27a2af75aec87be Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 20 Oct 2023 17:23:49 -0400 Subject: [PATCH 112/239] SFT-2884: added the beta number to the the version number stored in flash --- DEVELOPMENT.md | 3 +++ .../stm32/boards/Passport/bootloader/Justfile | 2 +- .../flows/view_current_firmware_flow.py | 20 +++++++++---------- .../boards/Passport/tools/cosign/cosign.c | 16 ++++++++++----- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index cca9acea3..93ace8131 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -88,6 +88,9 @@ To build and sign the firmware with a Developer Pubkey, use one of the following just sign 2.0.4 color just sign 2.0.4 mono + just sign 2.0.4bB color + +Note that beta numbers can be added, delimited by a lowercase 'b', followed by the beta number as an uppercase hexidecimal character. If you just want to build without signing, use one of the following commands: diff --git a/ports/stm32/boards/Passport/bootloader/Justfile b/ports/stm32/boards/Passport/bootloader/Justfile index 9c6477aeb..4a1a23d85 100644 --- a/ports/stm32/boards/Passport/bootloader/Justfile +++ b/ports/stm32/boards/Passport/bootloader/Justfile @@ -72,7 +72,7 @@ flash screen="mono" rel="release" factory_test="": (build screen rel factory_tes flash-only screen="mono" rel="release": && (reset) just run-ocd-command "flash write_image erase arm/{{rel}}/bootloader-{{uppercase(screen)}}.bin 0x8000000" -# Build and flash the bootloader with no secrets (use to setup a new Secure Element) +# Build and flash the bootloader with saved secrets (use to restore the bootloader of a bricked device) flash-with-secrets screen="mono" rel="release": && (reset) add-secrets -b "arm/{{rel}}/bootloader-{{uppercase(screen)}}.bin" -s secrets just run-ocd-command "flash write_image erase arm/{{rel}}/bootloader-{{uppercase(screen)}}-secrets.bin 0x8000000" diff --git a/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py b/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py index 0cc0f2498..858418beb 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_current_firmware_flow.py @@ -16,17 +16,15 @@ async def show_info(self): from common import system, ui, settings (fw_version, fw_timestamp, boot_counter, user_signed, fw_date) = system.get_software_info() - msg = '''Version {fw_version} -{fw_date}'''.format(fw_version=fw_version, fw_date=fw_date) - - title = settings.get("firmware_title", None) - if title is not None: - if len(title) > 13: - title = title[:-13] - msg += '\n{}'.format(title) - - # if user_signed: - # msg += '\nSigned by User' + # If there's a beta number, split the string, and convert to decimal + fw_strings = fw_version.split('b') + beta_version = '' + if len(fw_strings) > 1: + fw_version = fw_strings[0] + beta_version = " beta {}".format(int(fw_strings[1], 16)) + + msg = '''Version {fw_version}{beta_version} +{fw_date}'''.format(fw_version=fw_version, beta_version=beta_version, fw_date=fw_date) msg += ''' diff --git a/ports/stm32/boards/Passport/tools/cosign/cosign.c b/ports/stm32/boards/Passport/tools/cosign/cosign.c index 03fb0a093..4b18778ed 100644 --- a/ports/stm32/boards/Passport/tools/cosign/cosign.c +++ b/ports/stm32/boards/Passport/tools/cosign/cosign.c @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: © 2020 Foundation Devices, Inc. // SPDX-License-Identifier: GPL-3.0-or-later // +#include #include #include #include @@ -334,21 +335,26 @@ bool is_valid_version(char* version) { int version_major; int version_minor; int version_rev; + char beta_num = NULL; char left_over; - num_matched = sscanf(version, "%u.%u.%u%c", &version_major, &version_minor, &version_rev, &left_over); + num_matched = sscanf(version, "%u.%u.%ub%c%c", &version_major, &version_minor, &version_rev, &beta_num, &left_over); - if (num_matched != 3) { + if (num_matched != 3 && num_matched != 4) { return false; } // Version major is restricted to only 0-9 while others are 0-99 // Max version number string is 7 + null terminator if (version_major > 9 || version_major < 0 || version_minor > 99 || version_minor < 0 || version_rev > 99 || - version_rev < 0) { + version_rev < 0 || (beta_num != NULL && (!isxdigit(beta_num) || beta_num != toupper(beta_num)))) { return false; } else { - sprintf(version, "%d.%d.%d", version_major, version_minor, version_rev); + if (num_matched == 3) { + sprintf(version, "%d.%d.%d", version_major, version_minor, version_rev); + } else { + sprintf(version, "%d.%d.%db%c", version_major, version_minor, version_rev, beta_num); + } return true; } } @@ -505,7 +511,7 @@ static void sign_firmware(char* fw, char* key, char* version) { } if (!is_valid_version(version)) { - printf("ERROR: Incorrect version number. Correct format: <0-9>.<0-99>.<0-99> (e.g., 1.12.34)\n"); + printf("ERROR: Incorrect version number. Correct format: <0-9>.<0-99>.<0-99><0-F> (e.g., 1.12.34 or 2.3.0bA)\n"); exit(1); goto out; } From 58dc8c57cfe5ed990510f1837260fce1fbda8372 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 20 Oct 2023 17:44:18 -0400 Subject: [PATCH 113/239] SFT-2884: added beta number to version.txt for testing CI builds --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 276cbf9e2..e13d7a2bd 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.0 +2.3.0b2 From 83b509f2058a400bb28d37ff20b1b67064dc9d04 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 11 Nov 2023 18:43:54 -0500 Subject: [PATCH 114/239] SFT-1834: updated headers --- ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py | 1 + ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py | 1 + 2 files changed, 2 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py index aba000a82..e05794c49 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py @@ -32,6 +32,7 @@ def __init__(self, refresh_cards_when_done=False, autobackup=True, full_backup=F self.backup_password_prefixes = [] self.full_backup = full_backup self.autobackup = autobackup + self.statusbar = {'title': 'RESTORE BACKUP', 'icon': 'ICON_SEED'} async def check_if_erased(self): from common import pa diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index f18e9c818..519ee3aeb 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -22,6 +22,7 @@ def __init__(self, refresh_cards_when_done=False, autobackup=True, full_backup=F self.seed_words = [] self.full_backup = full_backup self.autobackup = autobackup + self.statusbar = {'title': 'RESOTRE SEED', 'icon': 'ICON_SEED'} async def choose_restore_method(self): from pages import ChooserPage From 80f0ee5c870bf0dcd654996df48cb25a24b56e26 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 11 Nov 2023 18:45:32 -0500 Subject: [PATCH 115/239] SFT-1834: fixed typo --- ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index 519ee3aeb..1a0a2b4e6 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -22,7 +22,7 @@ def __init__(self, refresh_cards_when_done=False, autobackup=True, full_backup=F self.seed_words = [] self.full_backup = full_backup self.autobackup = autobackup - self.statusbar = {'title': 'RESOTRE SEED', 'icon': 'ICON_SEED'} + self.statusbar = {'title': 'RESTORE SEED', 'icon': 'ICON_SEED'} async def choose_restore_method(self): from pages import ChooserPage From ff8c04c49e3fb5558f1c4c227de69f6e6445c1e1 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 16 Oct 2023 22:39:31 -0400 Subject: [PATCH 116/239] SFT-2212: added option to view seed words in envoy setup flow --- .../Passport/modules/flows/initial_seed_setup_flow.py | 5 ++--- .../boards/Passport/modules/flows/manual_setup_flow.py | 2 +- .../stm32/boards/Passport/modules/flows/new_seed_flow.py | 8 ++------ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/initial_seed_setup_flow.py b/ports/stm32/boards/Passport/modules/flows/initial_seed_setup_flow.py index 6a2c7ea24..9948b6e05 100644 --- a/ports/stm32/boards/Passport/modules/flows/initial_seed_setup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/initial_seed_setup_flow.py @@ -8,9 +8,8 @@ class InitialSeedSetupFlow(Flow): - def __init__(self, is_envoy=True, allow_backtrack=True): + def __init__(self, allow_backtrack=True): super().__init__(initial_state=self.show_intro, name='InitialSeedSetupFlow') - self.is_envoy = is_envoy self.statusbar = {'title': 'CREATE SEED', 'icon': 'ICON_SEED'} self.allow_backtrack = allow_backtrack @@ -44,7 +43,7 @@ async def show_seed_setup_menu(self): import microns options = [{'label': 'Create New Seed', - 'value': lambda: NewSeedFlow(show_words=not self.is_envoy, full_backup=True)}, + 'value': lambda: NewSeedFlow(full_backup=True)}, {'label': 'Restore Seed', 'value': lambda: RestoreSeedFlow(full_backup=True)}, {'label': 'Restore Backup', 'value': lambda: RestoreBackupFlow(full_backup=True)}] diff --git a/ports/stm32/boards/Passport/modules/flows/manual_setup_flow.py b/ports/stm32/boards/Passport/modules/flows/manual_setup_flow.py index f6eaba5df..0f6835797 100644 --- a/ports/stm32/boards/Passport/modules/flows/manual_setup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/manual_setup_flow.py @@ -108,7 +108,7 @@ async def setup_seed(self): return await self.ensure_logged_in() - result = await InitialSeedSetupFlow(is_envoy=False).run() + result = await InitialSeedSetupFlow().run() if result is None: self.back() elif not result: diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index 6f6a128aa..324a00656 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -11,11 +11,10 @@ class NewSeedFlow(Flow): - def __init__(self, refresh_cards_when_done=False, autobackup=True, show_words=False, full_backup=False): + def __init__(self, refresh_cards_when_done=False, autobackup=True, full_backup=False): super().__init__(initial_state=self.check_for_seed, name='NewSeedFlow') self.refresh_cards_when_done = refresh_cards_when_done self.autobackup = autobackup - self.show_words = show_words self.full_backup = full_backup self.seed_length = None @@ -63,10 +62,7 @@ async def generate_seed(self): async def save_seed(self): (error,) = await spinner_task('Saving Seed', save_seed_task, args=[self.seed]) if error is None: - if self.show_words: - self.goto(self.show_seed_words) - else: - self.goto(self.show_success) + self.goto(self.show_seed_words) else: self.error = 'Unable to save seed.' self.goto(self.show_error) From 618dbc7037326aeea624d637e60f5df024e005da Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 23 Oct 2023 12:27:24 -0400 Subject: [PATCH 117/239] SFT-2212: changed seed words warning for initial setup --- .../Passport/modules/flows/new_seed_flow.py | 2 +- .../modules/flows/seed_warning_flow.py | 25 +++++++++++++++++-- .../modules/flows/view_seed_words_flow.py | 12 ++++++--- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index 324a00656..b32225d3a 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -69,7 +69,7 @@ async def save_seed(self): async def show_seed_words(self): from flows import ViewSeedWordsFlow - await ViewSeedWordsFlow().run() + await ViewSeedWordsFlow(initial=True).run() self.goto(self.show_success) async def show_success(self): diff --git a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py index 4c54dc9e5..6f5d7ef6d 100644 --- a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py @@ -9,11 +9,32 @@ class SeedWarningFlow(Flow): def __init__(self, mention_passphrase=False, action_text="display your seed words", - continue_text=None): + continue_text=None, + initial=False): self.mention_passphrase = mention_passphrase self.action_text = action_text self.continue_text = continue_text or "control your funds" - super().__init__(initial_state=self.show_intro, name='SeedWarningFlow') + initial_state = self.show_initial if initial else self.show_intro + super().__init__(initial_state=initial_state, name='SeedWarningFlow') + + async def show_initial(self): + from pages import InfoPage + import microns + import lvgl as lv + + text = '''After setup completion, your seed words can be viewed in the advanced menu. + +Would you like to view them now?''' + + result = await InfoPage(text=text, + icon=lv.LARGE_ICON_SEED, + left_micron=microns.Cancel).show() + + if not result: + self.set_result(False) + return + + self.set_result(True) async def show_intro(self): import lvgl as lv diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index 9146391cf..871d35b0f 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -7,7 +7,7 @@ class ViewSeedWordsFlow(Flow): - def __init__(self, external_key=None, qr_option=False, sd_option=False, path=None, filename=None): + def __init__(self, external_key=None, qr_option=False, sd_option=False, path=None, filename=None, initial=False): self.external_key = external_key self.qr_option = qr_option @@ -21,6 +21,7 @@ def __init__(self, external_key=None, qr_option=False, sd_option=False, path=Non self.words = None self.seed_micron = None self.mention_passphrase = True if not external_key else False + self.initial = initial super().__init__(initial_state=self.generate_words, name='ViewSeedWordsFlow') async def generate_words(self): @@ -74,7 +75,8 @@ async def show_qr(self): import microns result = await SeedWarningFlow(action_text="display your seed as a QR code", - mention_passphrase=self.mention_passphrase).run() + mention_passphrase=self.mention_passphrase, + initial=self.initial).run() if not result: self.back() @@ -107,7 +109,8 @@ async def save_to_sd(self): from flows import SaveToMicroSDFlow result = await SeedWarningFlow(action_text="copy your seed to the microSD card", - mention_passphrase=self.mention_passphrase).run() + mention_passphrase=self.mention_passphrase, + initial=self.initial).run() if not result: self.back() @@ -129,7 +132,8 @@ async def show_seed_words(self): from pages import SeedWordsListPage if not self.qr_type: # We already gave the seed warning flow - result = await SeedWarningFlow(mention_passphrase=self.mention_passphrase).run() + result = await SeedWarningFlow(mention_passphrase=self.mention_passphrase, + initial=self.initial).run() if not result: self.set_result(False) From 64505c6213771007d0a77d596ddce2a05821d7e4 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 29 Oct 2023 21:27:02 -0400 Subject: [PATCH 118/239] SFT-2212: refactored setup flow to at least backup or show seed words --- .../modules/flows/auto_backup_flow.py | 3 +- .../Passport/modules/flows/backup_flow.py | 5 ++-- .../Passport/modules/flows/new_seed_flow.py | 30 ++++++++++++++----- .../modules/flows/seed_warning_flow.py | 25 ++++++++++++---- .../modules/flows/view_seed_words_flow.py | 19 +++++++++--- 5 files changed, 63 insertions(+), 19 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/auto_backup_flow.py b/ports/stm32/boards/Passport/modules/flows/auto_backup_flow.py index 3131c889d..9fbc7487e 100644 --- a/ports/stm32/boards/Passport/modules/flows/auto_backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/auto_backup_flow.py @@ -51,4 +51,5 @@ async def do_backup(self): from flows import BackupCommonFlow result = await BackupCommonFlow(self.backup_code, automatic=True).run() - self.set_result(result) + result_bool = result is not None + self.set_result(result_bool) diff --git a/ports/stm32/boards/Passport/modules/flows/backup_flow.py b/ports/stm32/boards/Passport/modules/flows/backup_flow.py index 7d05ccb03..90cd2f547 100644 --- a/ports/stm32/boards/Passport/modules/flows/backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/backup_flow.py @@ -30,7 +30,7 @@ async def show_intro(self): msgs = ['Passport is about to create an updated microSD backup.', 'The Backup Code is the same as what you were previously shown.'] else: - msgs = ['Passport is about to create your first encrypted microSD backup.', + msgs = ['Passport is about to create your first encrypted microSD backup containing your seed words.', 'The next screen will show you the Backup Code that is {} to decrypt the backup.'.format( recolor(HIGHLIGHT_TEXT_HEX, 'REQUIRED')), 'We recommend writing down the Backup Code on the included security card.', @@ -110,4 +110,5 @@ async def do_backup(self): from flows import BackupCommonFlow result = await BackupCommonFlow(self.backup_code).run() - self.set_result(result) + result_bool = result is not None + self.set_result(result_bool) diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index b32225d3a..3b0e75de6 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -17,6 +17,7 @@ def __init__(self, refresh_cards_when_done=False, autobackup=True, full_backup=F self.autobackup = autobackup self.full_backup = full_backup self.seed_length = None + self.skip_seed_prompt = False async def check_for_seed(self): # Ensure we don't overwrite an existing seed @@ -62,25 +63,40 @@ async def generate_seed(self): async def save_seed(self): (error,) = await spinner_task('Saving Seed', save_seed_task, args=[self.seed]) if error is None: - self.goto(self.show_seed_words) + self.goto(self.do_backup) else: self.error = 'Unable to save seed.' self.goto(self.show_error) + async def do_backup(self): + from flows import AutoBackupFlow, BackupFlow + + backup_flow = None + + if self.full_backup: + backup_flow = BackupFlow() + elif self.autobackup: + backup_flow = AutoBackupFlow(offer=True) + + if backup_flow is None: + self.goto(self.show_seed_words) + return + + # Run whichever backup flow is used, and + # determine if the seed can be skipped + self.skip_seed_prompt = await backup_flow.run() + self.goto(self.show_seed_words) + async def show_seed_words(self): from flows import ViewSeedWordsFlow - await ViewSeedWordsFlow(initial=True).run() + await ViewSeedWordsFlow(initial=self.skip_seed_prompt, + allow_skip=self.skip_seed_prompt).run() self.goto(self.show_success) async def show_success(self): import common - from flows import AutoBackupFlow, BackupFlow await SuccessPage(text='New seed created and saved.').show() - if self.full_backup: - await BackupFlow().run() - elif self.autobackup: - await AutoBackupFlow(offer=True).run() if self.refresh_cards_when_done: common.ui.full_cards_refresh() diff --git a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py index 6f5d7ef6d..d259608ca 100644 --- a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py @@ -10,10 +10,12 @@ class SeedWarningFlow(Flow): def __init__(self, mention_passphrase=False, action_text="display your seed words", continue_text=None, - initial=False): + initial=False, + allow_skip=True): self.mention_passphrase = mention_passphrase self.action_text = action_text self.continue_text = continue_text or "control your funds" + self.allow_skip = allow_skip initial_state = self.show_initial if initial else self.show_intro super().__init__(initial_state=initial_state, name='SeedWarningFlow') @@ -22,10 +24,12 @@ async def show_initial(self): import microns import lvgl as lv - text = '''After setup completion, your seed words can be viewed in the advanced menu. + text = '''After setup, your seed words can be viewed in the advanced menu. Would you like to view them now?''' + left_micron = microns.Cancel if self.allow_skip else None + result = await InfoPage(text=text, icon=lv.LARGE_ICON_SEED, left_micron=microns.Cancel).show() @@ -47,9 +51,12 @@ async def show_intro(self): else: text = 'Passport is about to {}'.format(self.action_text) + # Empty microns have no action, so backing out isn't allowed + left_micron = microns.Back if self.allow_skip else None + result = await InfoPage( icon=lv.LARGE_ICON_SEED, text=text, - left_micron=microns.Back, right_micron=microns.Forward).show() + left_micron=left_micron, right_micron=microns.Forward).show() if result: self.goto(self.confirm_show) @@ -58,7 +65,15 @@ async def show_intro(self): async def confirm_show(self): from pages import QuestionPage + import microns + + text = 'Anyone who knows this information can {}.'.format(self.continue_text) + left_micron = microns.Cancel + + if not self.allow_skip: + left_micron = None + else: + text += '\n\nContinue?' - result = await QuestionPage( - 'Anyone who knows this information can {}.\n\nContinue?'.format(self.continue_text)).show() + result = await QuestionPage(text, left_micron=left_micron).show() self.set_result(result) diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index 871d35b0f..13f304d4a 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -7,7 +7,14 @@ class ViewSeedWordsFlow(Flow): - def __init__(self, external_key=None, qr_option=False, sd_option=False, path=None, filename=None, initial=False): + def __init__(self, + external_key=None, + qr_option=False, + sd_option=False, + path=None, + filename=None, + initial=False, + allow_skip=True): self.external_key = external_key self.qr_option = qr_option @@ -22,6 +29,7 @@ def __init__(self, external_key=None, qr_option=False, sd_option=False, path=Non self.seed_micron = None self.mention_passphrase = True if not external_key else False self.initial = initial + self.allow_skip = allow_skip super().__init__(initial_state=self.generate_words, name='ViewSeedWordsFlow') async def generate_words(self): @@ -76,7 +84,8 @@ async def show_qr(self): result = await SeedWarningFlow(action_text="display your seed as a QR code", mention_passphrase=self.mention_passphrase, - initial=self.initial).run() + initial=self.initial, + allow_skip=self.allow_skip).run() if not result: self.back() @@ -110,7 +119,8 @@ async def save_to_sd(self): result = await SeedWarningFlow(action_text="copy your seed to the microSD card", mention_passphrase=self.mention_passphrase, - initial=self.initial).run() + initial=self.initial, + allow_skip=self.allow_skip).run() if not result: self.back() @@ -133,7 +143,8 @@ async def show_seed_words(self): if not self.qr_type: # We already gave the seed warning flow result = await SeedWarningFlow(mention_passphrase=self.mention_passphrase, - initial=self.initial).run() + initial=self.initial, + allow_skip=self.allow_skip).run() if not result: self.set_result(False) From 77c05532490c0c280338d78059e29392747cdf9d Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 13 Nov 2023 17:25:12 -0500 Subject: [PATCH 119/239] SFT-2212: added seed backup prompt page in initial seed warning --- .../Passport/modules/flows/new_seed_flow.py | 2 +- .../modules/flows/seed_warning_flow.py | 31 ++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index 3b0e75de6..5c02fa278 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -89,7 +89,7 @@ async def do_backup(self): async def show_seed_words(self): from flows import ViewSeedWordsFlow - await ViewSeedWordsFlow(initial=self.skip_seed_prompt, + await ViewSeedWordsFlow(initial=True, allow_skip=self.skip_seed_prompt).run() self.goto(self.show_success) diff --git a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py index d259608ca..04ca436a8 100644 --- a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py @@ -16,10 +16,11 @@ def __init__(self, mention_passphrase=False, self.action_text = action_text self.continue_text = continue_text or "control your funds" self.allow_skip = allow_skip - initial_state = self.show_initial if initial else self.show_intro + self.initial = initial + initial_state = self.show_skippable if allow_skip else self.show_intro super().__init__(initial_state=initial_state, name='SeedWarningFlow') - async def show_initial(self): + async def show_skippable(self): from pages import InfoPage import microns import lvgl as lv @@ -58,10 +59,30 @@ async def show_intro(self): icon=lv.LARGE_ICON_SEED, text=text, left_micron=left_micron, right_micron=microns.Forward).show() - if result: - self.goto(self.confirm_show) - else: + if not result: self.set_result(False) + return + + if self.initial: + self.goto(self.prompt_backup) + return + + self.goto(self.confirm_show) + + async def prompt_backup(self): + from pages import InfoPage + import microns + + text = 'Write down these words on the backup card provided. ' \ + 'Store the card securely and never take a photo of it.' + + result = await InfoPage(text, left_micron=microns.Back).show() + + if not result: + self.back() + return + + self.goto(self.confirm_show) async def confirm_show(self): from pages import QuestionPage From 3498327278fcda7173e50330a2ab4c61abceb511 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 15 Nov 2023 23:30:25 -0500 Subject: [PATCH 120/239] SFT-2212: updated seed creation page with Zach's suggestion --- .../stm32/boards/Passport/modules/flows/new_seed_flow.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index 5c02fa278..0e9d2c0a7 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -4,10 +4,11 @@ # new_seed_flow.py - Create a new random seed from flows import Flow -from pages import ErrorPage, QuestionPage, SuccessPage +from pages import ErrorPage, QuestionPage, SuccessPage, YesNoChooserPage from tasks import new_seed_task, save_seed_task from utils import has_secrets, spinner_task from translations import t, T +import lvgl as lv class NewSeedFlow(Flow): @@ -43,7 +44,11 @@ async def pick_length(self): self.goto(self.confirm_generate) async def confirm_generate(self): - result = await QuestionPage(text='Generate a new seed phrase now?').show() + # result = await QuestionPage(text='Generate a new seed phrase now?').show() + result = await YesNoChooserPage(text='Passport will create and back up a new 12-word seed phrase', + yes_text='Continue', + no_text='Back', + icon=lv.LARGE_ICON_INFO).show() if result: self.goto(self.generate_seed) else: From 1e6c1e21c42a0e3e9e5cc4fa90dad1b016d7aeab Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 16 Nov 2023 11:03:47 -0500 Subject: [PATCH 121/239] SFT-2212: changed x to back arrow --- ports/stm32/boards/Passport/modules/flows/new_seed_flow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index 0e9d2c0a7..de94e94ef 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -9,6 +9,7 @@ from utils import has_secrets, spinner_task from translations import t, T import lvgl as lv +import microns class NewSeedFlow(Flow): @@ -48,7 +49,8 @@ async def confirm_generate(self): result = await YesNoChooserPage(text='Passport will create and back up a new 12-word seed phrase', yes_text='Continue', no_text='Back', - icon=lv.LARGE_ICON_INFO).show() + icon=lv.LARGE_ICON_INFO, + left_micron=microns.Back).show() if result: self.goto(self.generate_seed) else: From 88e1177c405dad419a01bd68f85c0b8b5f5ee911 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 16 Nov 2023 17:56:07 -0500 Subject: [PATCH 122/239] SFT-2212: added period and 12 or 24 word distinction to new seed flow intro --- ports/stm32/boards/Passport/modules/flows/new_seed_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index de94e94ef..ccd296f71 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -46,7 +46,8 @@ async def pick_length(self): async def confirm_generate(self): # result = await QuestionPage(text='Generate a new seed phrase now?').show() - result = await YesNoChooserPage(text='Passport will create and back up a new 12-word seed phrase', + text = 'Passport will create and back up a new {}-word seed phrase.'.format(self.seed_length) + result = await YesNoChooserPage(text=text, yes_text='Continue', no_text='Back', icon=lv.LARGE_ICON_INFO, From 392d4aff872d9ce385a6246220765f2bd5c9b9c3 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 21 Oct 2023 00:51:01 -0400 Subject: [PATCH 123/239] SFT-967: basic monotonic address explorer --- ports/stm32/boards/Passport/manifest.py | 1 + .../boards/Passport/modules/flows/__init__.py | 1 + .../modules/flows/address_explorer_flow.py | 136 ++++++++++++++++++ ports/stm32/boards/Passport/modules/menus.py | 3 +- 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index c803e03ef..8dd7213de 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -78,6 +78,7 @@ freeze('$(MPY_DIR)/ports/stm32/boards/Passport/modules', ('flows/__init__.py', 'flows/about_flow.py', + 'flows/address_explorer_flow.py', 'flows/apply_passphrase_flow.py', 'flows/auto_backup_flow.py', 'flows/backup_common_flow.py', diff --git a/ports/stm32/boards/Passport/modules/flows/__init__.py b/ports/stm32/boards/Passport/modules/flows/__init__.py index b1d89ed4b..25b00176b 100644 --- a/ports/stm32/boards/Passport/modules/flows/__init__.py +++ b/ports/stm32/boards/Passport/modules/flows/__init__.py @@ -24,6 +24,7 @@ from .magic_scan_flow import * from .about_flow import * +from .address_explorer_flow import * from .apply_passphrase_flow import * from .auto_backup_flow import * from .backup_flow import * diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py new file mode 100644 index 000000000..a713a4c80 --- /dev/null +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -0,0 +1,136 @@ +# SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2018 Coinkite, Inc. +# SPDX-License-Identifier: GPL-3.0-only +# +# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard +# and is covered by GPLv3 license found in COPYING. +# +# address_explorer_flow.py - View addresses related to the current account + +from flows import Flow + + +class AddressExplorerFlow(Flow): + def __init__(self): + from common import ui + + super().__init__(initial_state=self.choose_sig_type, name='AddressExplorerFlow') + self.account = ui.get_active_account() + self.acct_num = self.account.get('acct_num') + self.sig_type = None + self.multisig_wallet = None + self.is_multisig = False + self.addr_type = None + self.deriv_path = None + self.is_change = None + self.curr_idx = 0 + + async def choose_sig_type(self): + from pages import SinglesigMultisigChooserPage + from multisig_wallet import MultisigWallet + from common import settings + + xfp = settings.get('xfp') + multisigs = MultisigWallet.get_by_xfp(xfp) + + if len(multisigs) == 0: + self.sig_type = 'single-sig' + self.goto(self.choose_addr_type, save_curr=False) # Skipping this state + else: + result = await SinglesigMultisigChooserPage( + initial_value=self.sig_type, multisigs=multisigs).show() + + if result is None: + self.set_result(False) + return + + (self.sig_type, self.multisig_wallet) = result + + self.is_multisig = self.sig_type == 'multisig' + self.goto(self.choose_addr_type) + + async def choose_addr_type(self): + from pages import AddressTypeChooserPage + from wallets.utils import get_deriv_path_from_addr_type_and_acct + + result = await AddressTypeChooserPage().show() + + if result is None: + self.back() + return + + self.addr_type = result + self.deriv_path = get_deriv_path_from_addr_type_and_acct(self.addr_type, self.acct_num, self.is_multisig) + self.goto(self.choose_change) + + async def choose_change(self): + from pages import YesNoChooserPage + + result = await YesNoChooserPage(text="Explore normal or change addresses?", + yes_text="Normal", + no_text="Change").show() + + if result is None: + self.back() + return + + self.is_change = not result + self.goto(self.explore) + + async def explore(self): + import chains + from utils import get_next_addr, format_btc_address + from common import settings + import stash + import passport + from pages import SuccessPage, LongSuccessPage + import microns + + xfp = settings.get('xfp') + chain = chains.current_chain() + index = get_next_addr(self.acct_num, + self.addr_type, + xfp, + chain.b44_cointype, + self.is_change) + + while True: + # TODO: break this into a util function + address = None + try: + with stash.SensitiveValues() as sv: + if self.is_multisig: + (curr_idx, paths, address, script) = list(self.multisig_wallet.yield_addresses( + start_idx=index, + count=1, + change_idx=1 if self.is_change else 0))[0] + else: + addr_path = '{}/{}/{}'.format(self.deriv_path, 1 if self.is_change else 0, index) + print(addr_path) + node = sv.derive_path(addr_path) + address = sv.chain.address(node, self.addr_type) + except Exception as e: + # TODO: make error page + break + # TODO: use nice new address formatting + nice_address = format_btc_address(address, self.addr_type) + + msg = '''{} + +{} Address {}'''.format( + nice_address, + 'Change' if self.is_change else 'Receive', + index) + + # TODO: make a new page that allows navigation + page_class = SuccessPage if passport.IS_COLOR else LongSuccessPage + result = await page_class(msg, left_micron=microns.Cancel).show() + + if not result: + break + else: + index += 1 + + self.set_result(True) diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index c1f891cce..454eb73dc 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -29,7 +29,7 @@ def manage_account_menu(): - from flows import RenameAccountFlow, DeleteAccountFlow, ConnectWalletFlow + from flows import RenameAccountFlow, DeleteAccountFlow, ConnectWalletFlow, AddressExplorerFlow from pages import AccountDetailsPage return [ @@ -37,6 +37,7 @@ def manage_account_menu(): {'icon': 'ICON_INFO', 'label': 'Rename Account', 'flow': RenameAccountFlow}, {'icon': 'ICON_CONNECT', 'label': 'Connect Wallet', 'flow': ConnectWalletFlow, 'statusbar': {'title': 'CONNECT'}}, + {'icon': 'ICON_VERIFY_ADDRESS', 'label': 'Explore Addresses', 'flow': AddressExplorerFlow}, {'icon': 'ICON_CANCEL', 'label': 'Delete Account', 'flow': DeleteAccountFlow}, ] From c84ee9fa8864e191c5d26f4ee16c164a02bc8089 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 21 Oct 2023 01:08:14 -0400 Subject: [PATCH 124/239] SFT-967: moved single address fetch into its own function --- .../modules/flows/address_explorer_flow.py | 44 +++++++++---------- ports/stm32/boards/Passport/modules/utils.py | 26 +++++++++++ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index a713a4c80..e35c4f373 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -38,18 +38,22 @@ async def choose_sig_type(self): if len(multisigs) == 0: self.sig_type = 'single-sig' self.goto(self.choose_addr_type, save_curr=False) # Skipping this state - else: - result = await SinglesigMultisigChooserPage( - initial_value=self.sig_type, multisigs=multisigs).show() + return - if result is None: - self.set_result(False) - return + result = await SinglesigMultisigChooserPage( + initial_value=self.sig_type, multisigs=multisigs).show() - (self.sig_type, self.multisig_wallet) = result + if result is None: + self.set_result(False) + return - self.is_multisig = self.sig_type == 'multisig' + (self.sig_type, self.multisig_wallet) = result + self.is_multisig = self.sig_type == 'multisig' + if not self.is_multisig: self.goto(self.choose_addr_type) + return + + self.goto(self.explore) async def choose_addr_type(self): from pages import AddressTypeChooserPage @@ -81,9 +85,8 @@ async def choose_change(self): async def explore(self): import chains - from utils import get_next_addr, format_btc_address + from utils import get_next_addr, format_btc_address, get_single_address from common import settings - import stash import passport from pages import SuccessPage, LongSuccessPage import microns @@ -97,20 +100,15 @@ async def explore(self): self.is_change) while True: - # TODO: break this into a util function - address = None try: - with stash.SensitiveValues() as sv: - if self.is_multisig: - (curr_idx, paths, address, script) = list(self.multisig_wallet.yield_addresses( - start_idx=index, - count=1, - change_idx=1 if self.is_change else 0))[0] - else: - addr_path = '{}/{}/{}'.format(self.deriv_path, 1 if self.is_change else 0, index) - print(addr_path) - node = sv.derive_path(addr_path) - address = sv.chain.address(node, self.addr_type) + address = get_single_address(xfp, + chain, + index, + self.is_multisig, + self.multisig_wallet, + self.is_change, + self.deriv_path, + self.addr_type) except Exception as e: # TODO: make error page break diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 4069c883d..7a3b2046a 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1467,4 +1467,30 @@ def stylize_address(address): return stylized +def get_single_address(xfp, + chain, + index, + is_multisig, + multisig_wallet, + is_change, + deriv_path, + addr_type): + import stash + + change_bit = 1 if is_change else 0 + with stash.SensitiveValues() as sv: + if is_multisig: + (curr_idx, paths, address, script) = list(multisig_wallet.yield_addresses( + start_idx=index, + count=1, + change_idx=change_bit))[0] + else: + addr_path = '{}/{}/{}'.format(deriv_path, change_bit, index) + print(addr_path) + node = sv.derive_path(addr_path) + address = sv.chain.address(node, addr_type) + + return address + + # EOF From 452189fb81c3338e5c0991d0a46dadc32b00558b Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 21 Oct 2023 02:05:47 -0400 Subject: [PATCH 125/239] SFT-967: first pass at full address explorer, needs copy editing and UI work --- ports/stm32/boards/Passport/manifest.py | 1 + .../modules/flows/address_explorer_flow.py | 95 +++++++++++-------- .../boards/Passport/modules/pages/__init__.py | 1 + .../modules/pages/address_explorer_page.py | 48 ++++++++++ 4 files changed, 106 insertions(+), 39 deletions(-) create mode 100644 ports/stm32/boards/Passport/modules/pages/address_explorer_page.py diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index 8dd7213de..201ce39b8 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -169,6 +169,7 @@ freeze('$(MPY_DIR)/ports/stm32/boards/Passport/modules', ('pages/__init__.py', 'pages/account_details_page.py', + 'pages/address_explorer_page.py', 'pages/backup_code_page.py', 'pages/battery_page.py', 'pages/brandmark_page.py', diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index e35c4f373..2e14c2198 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -13,6 +13,13 @@ class AddressExplorerFlow(Flow): + NAV_VALS = { + 'up': -10, + 'down': 10, + 'left': -1, + 'right': 1, + } + def __init__(self): from common import ui @@ -25,7 +32,7 @@ def __init__(self): self.addr_type = None self.deriv_path = None self.is_change = None - self.curr_idx = 0 + self.index = 0 async def choose_sig_type(self): from pages import SinglesigMultisigChooserPage @@ -53,7 +60,7 @@ async def choose_sig_type(self): self.goto(self.choose_addr_type) return - self.goto(self.explore) + self.goto(self.prepare) async def choose_addr_type(self): from pages import AddressTypeChooserPage @@ -81,54 +88,64 @@ async def choose_change(self): return self.is_change = not result + self.goto(self.prepare) + + async def prepare(self): + from common import settings + import chains + from utils import get_next_addr + + self.xfp = settings.get('xfp') + self.chain = chains.current_chain() + self.index = get_next_addr(self.acct_num, + self.addr_type, + self.xfp, + self.chain.b44_cointype, + self.is_change) + self.goto(self.explore) async def explore(self): - import chains - from utils import get_next_addr, format_btc_address, get_single_address - from common import settings + from utils import stylize_address, get_single_address import passport - from pages import SuccessPage, LongSuccessPage + from pages import ShowQRPage, AddressExplorerPage import microns - xfp = settings.get('xfp') - chain = chains.current_chain() - index = get_next_addr(self.acct_num, - self.addr_type, - xfp, - chain.b44_cointype, - self.is_change) + try: + self.address = get_single_address(self.xfp, + self.chain, + self.index, + self.is_multisig, + self.multisig_wallet, + self.is_change, + self.deriv_path, + self.addr_type) + except Exception as e: + # TODO: make error page + self.set_result(False) + return - while True: - try: - address = get_single_address(xfp, - chain, - index, - self.is_multisig, - self.multisig_wallet, - self.is_change, - self.deriv_path, - self.addr_type) - except Exception as e: - # TODO: make error page - break - # TODO: use nice new address formatting - nice_address = format_btc_address(address, self.addr_type) + nice_address = stylize_address(self.address) - msg = '''{} + msg = '''{} {} Address {}'''.format( - nice_address, - 'Change' if self.is_change else 'Receive', - index) + nice_address, + 'Change' if self.is_change else 'Receive', + self.index) - # TODO: make a new page that allows navigation - page_class = SuccessPage if passport.IS_COLOR else LongSuccessPage - result = await page_class(msg, left_micron=microns.Cancel).show() + while True: + result = await AddressExplorerPage(msg, + left_micron=microns.Cancel, + right_micron=microns.ScanQR).show() if not result: - break + self.set_result(True) + return + elif result is True: + await ShowQRPage(qr_data=self.address, + left_micron=None, + right_micron=microns.Checkmark).show() else: - index += 1 - - self.set_result(True) + self.index = max(0, self.index + self.NAV_VALS[result]) + break diff --git a/ports/stm32/boards/Passport/modules/pages/__init__.py b/ports/stm32/boards/Passport/modules/pages/__init__.py index d9b634e3a..c3db4d37a 100644 --- a/ports/stm32/boards/Passport/modules/pages/__init__.py +++ b/ports/stm32/boards/Passport/modules/pages/__init__.py @@ -11,6 +11,7 @@ from .accept_terms_chooser_page import * from .account_details_page import * +from .address_explorer_page import * from .address_type_chooser_page import * from .auto_shutdown_setting_page import * from .backup_code_page import * diff --git a/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py b/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py new file mode 100644 index 000000000..cd4888f3b --- /dev/null +++ b/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# +# address_explorer_page.py + + +import lvgl as lv +from pages import StatusPage +import microns +import common + + +class AddressExplorerPage(StatusPage): + + def __init__(self, + text=None, + card_header=None, + statusbar=None, + left_micron=microns.Back, + right_micron=microns.Checkmark): + super().__init__( + text=text, + card_header=card_header, + statusbar=statusbar, + right_micron=right_micron, + left_micron=left_micron) + + def attach(self, group): + super().attach(group) + self.lvgl_root.add_event_cb(self.on_key, lv.EVENT.KEY, None) + self.prev_top_level = common.ui.set_is_top_level(False) + + def detach(self): + common.ui.set_is_top_level(self.prev_top_level) + self.lvgl_root.remove_event_cb(self.on_key) + super().detach() + + def on_key(self, event): + key = event.get_key() + + if key == lv.KEY.RIGHT: + self.set_result('right') + elif key == lv.KEY.LEFT: + self.set_result('left') + elif key == lv.KEY.UP: + self.set_result('up') + elif key == lv.KEY.DOWN: + self.set_result('down') From 6d19fa30298eee95b6d42f4935251641188fb5e6 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 21 Oct 2023 02:21:13 -0400 Subject: [PATCH 126/239] SFT-967: added navigation explainer placeholder page --- .../boards/Passport/modules/flows/address_explorer_flow.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 2e14c2198..b06e5dfd4 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -94,6 +94,13 @@ async def prepare(self): from common import settings import chains from utils import get_next_addr + from pages import InfoPage + + result = await InfoPage("TODO: explain navigation").show() + + if not result: + self.back() + return self.xfp = settings.get('xfp') self.chain = chains.current_chain() From ab78abd917fc18473229f2559994f3d414a92954 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 22 Oct 2023 19:15:21 -0400 Subject: [PATCH 127/239] SFT-967: improved address explorer UI --- .../Passport/modules/flows/address_explorer_flow.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index b06e5dfd4..ffb8596a4 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -79,7 +79,7 @@ async def choose_addr_type(self): async def choose_change(self): from pages import YesNoChooserPage - result = await YesNoChooserPage(text="Explore normal or change addresses?", + result = await YesNoChooserPage(text="\nExplore normal or change addresses?", yes_text="Normal", no_text="Change").show() @@ -134,12 +134,11 @@ async def explore(self): nice_address = stylize_address(self.address) - msg = '''{} + msg = '''{} Address {} -{} Address {}'''.format( - nice_address, - 'Change' if self.is_change else 'Receive', - self.index) +{}'''.format('Change' if self.is_change else 'Receive', + self.index, + nice_address) while True: result = await AddressExplorerPage(msg, From 718fd393ca39ffd4a6fdd4f59d6a3e870f894d36 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 29 Oct 2023 22:02:39 -0400 Subject: [PATCH 128/239] SFT-967: address explorer UI edits --- .../boards/Passport/modules/flows/address_explorer_flow.py | 6 +++--- ports/stm32/boards/Passport/modules/menus.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index ffb8596a4..e159abf6f 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -68,7 +68,7 @@ async def choose_addr_type(self): result = await AddressTypeChooserPage().show() - if result is None: + if not result: self.back() return @@ -79,8 +79,8 @@ async def choose_addr_type(self): async def choose_change(self): from pages import YesNoChooserPage - result = await YesNoChooserPage(text="\nExplore normal or change addresses?", - yes_text="Normal", + result = await YesNoChooserPage(text="\nExplore receive or change addresses?", + yes_text="Receive", no_text="Change").show() if result is None: diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index 454eb73dc..9fab93187 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -37,7 +37,8 @@ def manage_account_menu(): {'icon': 'ICON_INFO', 'label': 'Rename Account', 'flow': RenameAccountFlow}, {'icon': 'ICON_CONNECT', 'label': 'Connect Wallet', 'flow': ConnectWalletFlow, 'statusbar': {'title': 'CONNECT'}}, - {'icon': 'ICON_VERIFY_ADDRESS', 'label': 'Explore Addresses', 'flow': AddressExplorerFlow}, + {'icon': 'ICON_VERIFY_ADDRESS', 'label': 'Explore Addresses', 'flow': AddressExplorerFlow, + 'statusbar': {'title': 'EXPLORE'}}, {'icon': 'ICON_CANCEL', 'label': 'Delete Account', 'flow': DeleteAccountFlow}, ] From 910216b3bb5ca82de968998bbdf569f91249d608 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 30 Oct 2023 20:08:02 -0400 Subject: [PATCH 129/239] SFT-967: first pass of address explorer navigation explainer --- .../modules/flows/address_explorer_flow.py | 20 +++++++++++++++++-- ports/stm32/boards/Passport/modules/menus.py | 2 +- ports/stm32/boards/Passport/modules/utils.py | 1 - 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index e159abf6f..39c27a994 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -94,14 +94,30 @@ async def prepare(self): from common import settings import chains from utils import get_next_addr - from pages import InfoPage + from pages import LongTextPage + import microns + + text1 = 'Use the directional pad to navigate through addresses. ' \ + 'Pressing left or right moves down/up the list one by one. ' \ + 'Pressing up or down moves +/- 10 addresses at a time for faster navigation.' - result = await InfoPage("TODO: explain navigation").show() + result = await LongTextPage(text=text1, centered=True).show() if not result: self.back() return + text2 = 'Warning - Take care when using this feature to receive directly to Passport. ' \ + 'Passport cannot know if any displayed address has been used previously. ' \ + 'Connect Passport with a wallet like Envoy to remove the risk of reusing addresses.' + + result2 = await LongTextPage(text=text2, + centered=True, + right_micron=microns.Checkmark).show() + + if not result2: + return + self.xfp = settings.get('xfp') self.chain = chains.current_chain() self.index = get_next_addr(self.acct_num, diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index 9fab93187..a023afd93 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -38,7 +38,7 @@ def manage_account_menu(): {'icon': 'ICON_CONNECT', 'label': 'Connect Wallet', 'flow': ConnectWalletFlow, 'statusbar': {'title': 'CONNECT'}}, {'icon': 'ICON_VERIFY_ADDRESS', 'label': 'Explore Addresses', 'flow': AddressExplorerFlow, - 'statusbar': {'title': 'EXPLORE'}}, + 'statusbar': {'title': 'LIST ADDRESSES'}}, {'icon': 'ICON_CANCEL', 'label': 'Delete Account', 'flow': DeleteAccountFlow}, ] diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 7a3b2046a..63c6aff6e 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1486,7 +1486,6 @@ def get_single_address(xfp, change_idx=change_bit))[0] else: addr_path = '{}/{}/{}'.format(deriv_path, change_bit, index) - print(addr_path) node = sv.derive_path(addr_path) address = sv.chain.address(node, addr_type) From c95958eadd0ff57d6cebfb01b66f6da6a2584d07 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 30 Oct 2023 20:31:22 -0400 Subject: [PATCH 130/239] SFT-967: exploring alternate options for address explorer intro --- .../modules/flows/address_explorer_flow.py | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 39c27a994..847dbbcba 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -94,12 +94,18 @@ async def prepare(self): from common import settings import chains from utils import get_next_addr - from pages import LongTextPage + from pages import LongTextPage, ErrorPage import microns - text1 = 'Use the directional pad to navigate through addresses. ' \ - 'Pressing left or right moves down/up the list one by one. ' \ - 'Pressing up or down moves +/- 10 addresses at a time for faster navigation.' + # text1 = 'Use the directional pad to navigate through addresses. ' \ + # 'Pressing left or right moves down/up the list one by one. ' \ + # 'Pressing up or down moves +/- 10 addresses at a time for faster navigation.' + + text1 = '''\nUse the directional pad to navigate.\n +up: -10 +down: +10 +left: -1 +right: +1''' result = await LongTextPage(text=text1, centered=True).show() @@ -107,13 +113,16 @@ async def prepare(self): self.back() return - text2 = 'Warning - Take care when using this feature to receive directly to Passport. ' \ - 'Passport cannot know if any displayed address has been used previously. ' \ - 'Connect Passport with a wallet like Envoy to remove the risk of reusing addresses.' + # text2 = 'Warning - Take care when using this feature to receive directly to Passport. ' \ + # 'Passport cannot know if any displayed address has been used previously. ' \ + # 'Connect Passport with a wallet like Envoy to remove the risk of reusing addresses.' + text2 = 'Use your watch-only wallet of choice to avoid address reuse' + + # result2 = await LongTextPage(text=text2, + # centered=True, + # right_micron=microns.Checkmark).show() - result2 = await LongTextPage(text=text2, - centered=True, - right_micron=microns.Checkmark).show() + result2 = await ErrorPage(text=text2).show() if not result2: return From 7923e33bdaffcc48bc71f149b58a0846ea488e3b Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 10 Nov 2023 18:26:00 -0500 Subject: [PATCH 131/239] SFT-967: updated address explorer copy --- .../modules/flows/address_explorer_flow.py | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 847dbbcba..466b0f592 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -95,17 +95,14 @@ async def prepare(self): import chains from utils import get_next_addr from pages import LongTextPage, ErrorPage + from flows import SeriesOfPagesFlow import microns - # text1 = 'Use the directional pad to navigate through addresses. ' \ - # 'Pressing left or right moves down/up the list one by one. ' \ - # 'Pressing up or down moves +/- 10 addresses at a time for faster navigation.' - - text1 = '''\nUse the directional pad to navigate.\n -up: -10 -down: +10 -left: -1 -right: +1''' + text1 = '''\nUse the directional pad to navigate addresses.\n +Up: -10 +Down: +10 +Left: -1 +Right: +1''' result = await LongTextPage(text=text1, centered=True).show() @@ -113,16 +110,13 @@ async def prepare(self): self.back() return - # text2 = 'Warning - Take care when using this feature to receive directly to Passport. ' \ - # 'Passport cannot know if any displayed address has been used previously. ' \ - # 'Connect Passport with a wallet like Envoy to remove the risk of reusing addresses.' - text2 = 'Use your watch-only wallet of choice to avoid address reuse' - - # result2 = await LongTextPage(text=text2, - # centered=True, - # right_micron=microns.Checkmark).show() + messages = [{'text': 'Passport cannot know if displayed addresses have been used.', + 'left_micron': microns.Back, + 'right_micron': microns.Forward}, + {'text': 'Connect Passport with a wallet like Envoy to avoid reusing addresses.', + 'left_micron': microns.Back}] - result2 = await ErrorPage(text=text2).show() + result2 = await SeriesOfPagesFlow(ErrorPage, messages).run() if not result2: return From 53c23c7f94543c40ce41a73fd4f18de6ec0b6454 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 15 Nov 2023 12:45:37 -0500 Subject: [PATCH 132/239] SFT-967: updated copy to match conventions --- .../boards/Passport/modules/flows/address_explorer_flow.py | 6 +++--- .../Passport/modules/pages/address_type_chooser_page.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 466b0f592..417e2eb3b 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -98,7 +98,7 @@ async def prepare(self): from flows import SeriesOfPagesFlow import microns - text1 = '''\nUse the directional pad to navigate addresses.\n + text1 = '''\nUse the directional pad to navigate addresses\n Up: -10 Down: +10 Left: -1 @@ -110,10 +110,10 @@ async def prepare(self): self.back() return - messages = [{'text': 'Passport cannot know if displayed addresses have been used.', + messages = [{'text': 'Passport cannot know if displayed addresses have been used', 'left_micron': microns.Back, 'right_micron': microns.Forward}, - {'text': 'Connect Passport with a wallet like Envoy to avoid reusing addresses.', + {'text': 'Connect Passport with a wallet like Envoy to avoid reusing addresses', 'left_micron': microns.Back}] result2 = await SeriesOfPagesFlow(ErrorPage, messages).run() diff --git a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py index 3eb91695b..cfec3287d 100644 --- a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py @@ -10,9 +10,9 @@ class AddressTypeChooserPage(ChooserPage): OPTIONS = [ - {'label': 'Native Segwit', 'value': AF_P2WPKH}, - {'label': 'P2SH-Segwit', 'value': AF_P2WPKH_P2SH}, - {'label': 'Legacy (P2PKH)', 'value': AF_CLASSIC}, + {'label': 'Segwit', 'value': AF_P2WPKH}, + {'label': 'Wrapped Segwit', 'value': AF_P2WPKH_P2SH}, + {'label': 'Legacy', 'value': AF_CLASSIC}, ] def __init__(self, card_header={'title': 'Address Type'}, initial_value=None): From 5a67eeea7a43f2174d87be9a8e8dda6ab038fdab Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 15 Nov 2023 14:36:26 -0500 Subject: [PATCH 133/239] SFT-967: fixed navigation freezing on address explorer --- .../Passport/modules/flows/address_explorer_flow.py | 1 - .../Passport/modules/pages/address_explorer_page.py | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 417e2eb3b..8748e4df9 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -133,7 +133,6 @@ async def prepare(self): async def explore(self): from utils import stylize_address, get_single_address - import passport from pages import ShowQRPage, AddressExplorerPage import microns diff --git a/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py b/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py index cd4888f3b..189878d63 100644 --- a/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py +++ b/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py @@ -27,12 +27,20 @@ def __init__(self, def attach(self, group): super().attach(group) + group.add_obj(self.lvgl_root) self.lvgl_root.add_event_cb(self.on_key, lv.EVENT.KEY, None) self.prev_top_level = common.ui.set_is_top_level(False) + # common.keypad.set_intercept_key_cb(self.on_key) + common.keypad.set_key_repeat(lv.KEY.UP, False) + common.keypad.set_key_repeat(lv.KEY.DOWN, False) def detach(self): common.ui.set_is_top_level(self.prev_top_level) self.lvgl_root.remove_event_cb(self.on_key) + lv.group_remove_obj(self.lvgl_root) + # common.keypad.set_intercept_key_cb(None) + common.keypad.set_key_repeat(lv.KEY.UP, True) + common.keypad.set_key_repeat(lv.KEY.DOWN, True) super().detach() def on_key(self, event): From 4ebddfa4cf3e3db0c617ca26b9fd51e8dbc1d4e6 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 16 Nov 2023 11:23:12 -0500 Subject: [PATCH 134/239] SFT-967: added commas to 2nd warning page --- .../boards/Passport/modules/flows/address_explorer_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 8748e4df9..cb42bed98 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -113,7 +113,7 @@ async def prepare(self): messages = [{'text': 'Passport cannot know if displayed addresses have been used', 'left_micron': microns.Back, 'right_micron': microns.Forward}, - {'text': 'Connect Passport with a wallet like Envoy to avoid reusing addresses', + {'text': 'Connect Passport with a wallet, like Envoy, to avoid reusing addresses', 'left_micron': microns.Back}] result2 = await SeriesOfPagesFlow(ErrorPage, messages).run() From 6b48919e8b27d4d50ad81d25ba66076479acc47f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 10 Nov 2023 21:58:04 -0500 Subject: [PATCH 135/239] SFT-2961: added button to show seedqr from seed words list page --- .../modules/flows/view_seed_words_flow.py | 58 +++++++++++++++++-- ports/stm32/boards/Passport/modules/menus.py | 3 +- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index 13f304d4a..3e843a14c 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -7,6 +7,7 @@ class ViewSeedWordsFlow(Flow): +<<<<<<< HEAD def __init__(self, external_key=None, qr_option=False, @@ -14,7 +15,10 @@ def __init__(self, path=None, filename=None, initial=False, - allow_skip=True): + allow_skip=True, + qr_button=False): + import microns + self.external_key = external_key self.qr_option = qr_option @@ -26,10 +30,12 @@ def __init__(self, self.filename = filename self.qr_type = None self.words = None - self.seed_micron = None + self.seed_micron = None if not qr_button else microns.ScanQR self.mention_passphrase = True if not external_key else False self.initial = initial self.allow_skip = allow_skip + self.use_qr_button = qr_button + self.seen_warning = False super().__init__(initial_state=self.generate_words, name='ViewSeedWordsFlow') async def generate_words(self): @@ -91,6 +97,7 @@ async def show_qr(self): self.back() return + self.seen_warning = True result = await ShowQRPage(qr_type=self.qr_type, qr_data=self.words, right_micron=microns.Checkmark).show() if not result: @@ -126,6 +133,7 @@ async def save_to_sd(self): self.back() return + self.seen_warning = True text = " ".join(self.words) result = await SaveToMicroSDFlow(filename=self.filename, path=self.path, @@ -141,7 +149,7 @@ async def show_seed_words(self): from flows import SeedWarningFlow from pages import SeedWordsListPage - if not self.qr_type: # We already gave the seed warning flow + if not self.seen_warning: # We already gave the seed warning flow result = await SeedWarningFlow(mention_passphrase=self.mention_passphrase, initial=self.initial, allow_skip=self.allow_skip).run() @@ -150,16 +158,54 @@ async def show_seed_words(self): self.set_result(False) return + self.seen_warning = True + result = False while not result: result = await SeedWordsListPage(words=self.words, left_micron=self.seed_micron).show() - if not result and self.qr_type: - self.back() - return + if not result: + + if self.qr_type: + self.back() + return + + if self.use_qr_button: + self.goto(self.qr_intro) + return self.goto(self.show_passphrase) + async def qr_intro(self): + from pages import InfoPage + import microns + + result = await InfoPage('The following QR code contains your seed words. ' + 'Never scan it with an internet connected device like your phone.', + left_micron=microns.Back).show() + + if not result: + self.back() + return + + self.goto(self.qr_button) + + async def qr_button(self): + from pages import ShowQRPage + from data_codecs.qr_type import QRType + import microns + + result = await ShowQRPage(QRType.COMPACT_SEED_QR, + qr_data=self.words, + left_micron=microns.Back, + right_micron=microns.Checkmark).show() + + if not result: + self.back() + return + + self.set_result(True) + async def show_passphrase(self): import stash from pages import InfoPage diff --git a/ports/stm32/boards/Passport/modules/menus.py b/ports/stm32/boards/Passport/modules/menus.py index a023afd93..e4389b438 100644 --- a/ports/stm32/boards/Passport/modules/menus.py +++ b/ports/stm32/boards/Passport/modules/menus.py @@ -353,7 +353,8 @@ def advanced_menu(): return [ {'icon': 'ICON_SETTINGS', 'label': 'Security Words', 'flow': ShowSecurityWordsSettingFlow}, {'icon': 'ICON_SEED', 'label': 'View Seed Words', 'flow': ViewSeedWordsFlow, 'is_visible': has_seed, - 'statusbar': {'title': 'SEED WORDS', 'icon': 'ICON_SEED'}}, + 'statusbar': {'title': 'SEED WORDS', 'icon': 'ICON_SEED'}, + 'args': {'qr_button': True}}, {'icon': 'ICON_ONE_KEY', 'label': 'Developer Pubkey', 'submenu': developer_pubkey_menu, 'statusbar': {'title': 'DEV. PUBKEY'}}, {'icon': 'ICON_MICROSD', 'label': 'microSD', 'submenu': microsd_menu}, From a3de4a48c3d56f10477ad01eaa6cee4787a0b8e5 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 15 Nov 2023 23:11:39 -0500 Subject: [PATCH 136/239] SFT-2961: updated qr intro text with Zach's suggestion --- .../boards/Passport/modules/flows/view_seed_words_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index 3e843a14c..0550a8601 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -180,8 +180,8 @@ async def qr_intro(self): from pages import InfoPage import microns - result = await InfoPage('The following QR code contains your seed words. ' - 'Never scan it with an internet connected device like your phone.', + result = await InfoPage('The following QR code contains your seed words.\n\n' + 'NEVER scan it with an internet connected device like your phone.', left_micron=microns.Back).show() if not result: From 2958ff9a553c183c49e28e4d6a0bad8f8744963e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 16 Nov 2023 10:57:56 -0500 Subject: [PATCH 137/239] SFT-2961: restored hyphen and capitalized Internet --- .../stm32/boards/Passport/modules/flows/view_seed_words_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index 0550a8601..5d75f6241 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -181,7 +181,7 @@ async def qr_intro(self): import microns result = await InfoPage('The following QR code contains your seed words.\n\n' - 'NEVER scan it with an internet connected device like your phone.', + 'NEVER scan it with an Internet-connected device like your phone.', left_micron=microns.Back).show() if not result: From 8061f40491968cdd909c44c6992f8ab4b56d9336 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 16 Nov 2023 17:48:37 -0500 Subject: [PATCH 138/239] SFT-2961: fully matched Zach's suggestion --- .../stm32/boards/Passport/modules/flows/view_seed_words_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index 5d75f6241..e912bd4f3 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -181,7 +181,7 @@ async def qr_intro(self): import microns result = await InfoPage('The following QR code contains your seed words.\n\n' - 'NEVER scan it with an Internet-connected device like your phone.', + 'NEVER scan it from an Internet connected device.', left_micron=microns.Back).show() if not result: From 5aae72f0700de3e383544f6c4c09d6628398ebf7 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 16 Nov 2023 18:23:18 -0500 Subject: [PATCH 139/239] SFT-2961: removed rebase artifact --- .../stm32/boards/Passport/modules/flows/view_seed_words_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index e912bd4f3..b2f98020b 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -7,7 +7,6 @@ class ViewSeedWordsFlow(Flow): -<<<<<<< HEAD def __init__(self, external_key=None, qr_option=False, From f6a86fa60d37e14cf608ed675404ed2d9f8727ba Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 21 Nov 2023 14:34:03 -0600 Subject: [PATCH 140/239] SFT-3015: fixed optional screen presenting --- ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py index 04ca436a8..0af508fcf 100644 --- a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py @@ -17,7 +17,7 @@ def __init__(self, mention_passphrase=False, self.continue_text = continue_text or "control your funds" self.allow_skip = allow_skip self.initial = initial - initial_state = self.show_skippable if allow_skip else self.show_intro + initial_state = self.show_skippable if (initial and allow_skip) else self.show_intro super().__init__(initial_state=initial_state, name='SeedWarningFlow') async def show_skippable(self): From 358d6fb0f606bf3096a53e5cb82d19b1212994b0 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 19 Nov 2023 23:00:23 -0500 Subject: [PATCH 141/239] SFT-3004: fixed address explorer type chooser back button --- .../boards/Passport/modules/flows/address_explorer_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index cb42bed98..a7719f5e4 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -69,7 +69,8 @@ async def choose_addr_type(self): result = await AddressTypeChooserPage().show() if not result: - self.back() + if not self.back(): + self.set_result(False) return self.addr_type = result From 27077d2645697bd0b9819ff81dee8479fcd0a8b9 Mon Sep 17 00:00:00 2001 From: sethforprivacy Date: Mon, 27 Nov 2023 06:57:15 -0500 Subject: [PATCH 142/239] Bump Github Action dependencies to latest stable Bump setup-just to v1 Pin setup-just to latest v1 commit Fix setup-just pin to v1 tag instead of trunk --- .github/workflows/validate_and_build.yaml | 44 +++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 7193acb9f..75ed9c229 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -17,13 +17,13 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 + - uses: docker/setup-buildx-action@v3 with: driver-opts: network=host - - uses: docker/build-push-action@v4 + - uses: docker/build-push-action@v5 with: push: true context: . @@ -56,20 +56,20 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 + - uses: docker/setup-buildx-action@v3 with: driver-opts: network=host - - uses: docker/build-push-action@v4 + - uses: docker/build-push-action@v5 with: push: true context: . cache-from: type=gha cache-to: type=gha tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - uses: extractions/setup-just@69d82fb0233557aec017ef13706851d0694e0f1d - run: | echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV echo "SCREEN_MODE=$(echo "${{ matrix.build.screen }}" | tr a-z A-Z)" >> $GITHUB_ENV @@ -85,13 +85,13 @@ jobs: SIGNING_KEY: ${{ secrets.UserSigningKey }} - name: Upload firmware (unsigned) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: v${{env.version}}-unsigned${{ matrix.build.suffix }}.bin path: ports/stm32/build-Passport/firmware-${{ env.SCREEN_MODE }}.bin - name: Upload firmware (signed) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: v${{env.version}}-beta${{ matrix.build.suffix }}.bin path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin @@ -137,20 +137,20 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 + - uses: docker/setup-buildx-action@v3 with: driver-opts: network=host - - uses: docker/build-push-action@v4 + - uses: docker/build-push-action@v5 with: push: true context: . cache-from: type=gha cache-to: type=gha tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - uses: extractions/setup-just@69d82fb0233557aec017ef13706851d0694e0f1d - run: | echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV echo "SCREEN_MODE=$(echo ${{ matrix.screen }} | tr a-z A-Z)" >> $GITHUB_ENV @@ -159,7 +159,7 @@ jobs: run: just build-bootloader ${{ matrix.screen }} - name: Upload bootloader - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: bootloader-${{ env.SCREEN_MODE }}.bin path: ports/stm32/boards/Passport/bootloader/arm/release/bootloader-${{ env.SCREEN_MODE }}.bin @@ -180,20 +180,20 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 + - uses: docker/setup-buildx-action@v3 with: driver-opts: network=host - - uses: docker/build-push-action@v4 + - uses: docker/build-push-action@v5 with: push: true context: . cache-from: type=gha cache-to: type=gha tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - uses: extractions/setup-just@69d82fb0233557aec017ef13706851d0694e0f1d - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - name: Build @@ -211,20 +211,20 @@ jobs: - 5000:5000 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: docker/setup-buildx-action@v2 + - uses: docker/setup-buildx-action@v3 with: driver-opts: network=host - - uses: docker/build-push-action@v4 + - uses: docker/build-push-action@v5 with: push: true context: . cache-from: type=gha cache-to: type=gha tags: localhost:5000/foundation-devices/passport2:latest - - uses: extractions/setup-just@aa5d15c144db4585980a44ebfdd2cf337c4f14cb + - uses: extractions/setup-just@69d82fb0233557aec017ef13706851d0694e0f1d - run: echo "DOCKER_IMAGE=localhost:5000/foundation-devices/passport2:latest" >> $GITHUB_ENV - name: Build From 13c750121e5f2005f06fbdb32ac6f23b5c72b619 Mon Sep 17 00:00:00 2001 From: sethforprivacy Date: Mon, 27 Nov 2023 07:32:20 -0500 Subject: [PATCH 143/239] Add automatic Dependabot Github Action dependency updates. Add license to dependabot.yaml --- .github/workflows/dependabot.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/dependabot.yaml diff --git a/.github/workflows/dependabot.yaml b/.github/workflows/dependabot.yaml new file mode 100644 index 000000000..051d65ac1 --- /dev/null +++ b/.github/workflows/dependabot.yaml @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: © 2021 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# +# dependabot.yaml - Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" \ No newline at end of file From 14c1860f7970a2f830e063c18e8d9435984af6f0 Mon Sep 17 00:00:00 2001 From: sethforprivacy Date: Mon, 27 Nov 2023 07:29:19 -0500 Subject: [PATCH 144/239] Remove unnecessary lock files --- .../core/embed/rust/Cargo.lock | 317 --- .../core/embed/rust/fuzz/Cargo.lock | 392 ---- extmod/trezor-firmware/poetry.lock | 1726 ----------------- lib/lv_bindings/lvgl/docs/requirements.txt | 34 - 4 files changed, 2469 deletions(-) delete mode 100644 extmod/trezor-firmware/core/embed/rust/Cargo.lock delete mode 100644 extmod/trezor-firmware/core/embed/rust/fuzz/Cargo.lock delete mode 100644 extmod/trezor-firmware/poetry.lock delete mode 100644 lib/lv_bindings/lvgl/docs/requirements.txt diff --git a/extmod/trezor-firmware/core/embed/rust/Cargo.lock b/extmod/trezor-firmware/core/embed/rust/Cargo.lock deleted file mode 100644 index 9085fcb44..000000000 --- a/extmod/trezor-firmware/core/embed/rust/Cargo.lock +++ /dev/null @@ -1,317 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bindgen" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitvec" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "cc" -version = "1.0.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" - -[[package]] -name = "cexpr" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clang-sys" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10612c0ec0e0a1ff0e97980647cb058a6e7aedb913d01d009c406b8b7d0b26ee" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "cstr_core" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ba9efe9e1e736671d5a03f006afc4e7e3f32503e2077e0bcaf519c0c8c1d3" -dependencies = [ - "cty", - "memchr", -] - -[[package]] -name = "cty" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3" - -[[package]] -name = "funty" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" - -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14db22a3fec113074342010bb85a75ba17789244649af8a3178594e0dc97c381" -dependencies = [ - "hash32", - "spin", - "stable_deref_trait", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" - -[[package]] -name = "libloading" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "lock_api" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "nom" -version = "6.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" -dependencies = [ - "bitvec", - "funty", - "memchr", - "version_check", -] - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "proc-macro2" -version = "1.0.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" - -[[package]] -name = "regex" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "spin" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" -dependencies = [ - "lock_api", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "trezor_lib" -version = "0.1.0" -dependencies = [ - "bindgen", - "cc", - "cstr_core", - "cty", - "glob", - "heapless", -] - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "wyz" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" diff --git a/extmod/trezor-firmware/core/embed/rust/fuzz/Cargo.lock b/extmod/trezor-firmware/core/embed/rust/fuzz/Cargo.lock deleted file mode 100644 index e9bb29b3f..000000000 --- a/extmod/trezor-firmware/core/embed/rust/fuzz/Cargo.lock +++ /dev/null @@ -1,392 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "arbitrary" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237430fd6ed3740afe94eefcc278ae21e050285be882804e0d6e8695f0c94691" - -[[package]] -name = "atomic-polyfill" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30302dda7a66f8c55932ebf208f7def840743ff64d495e9ceffcd97c18f11d39" -dependencies = [ - "cortex-m", -] - -[[package]] -name = "bare-metal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "bindgen" -version = "0.58.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", -] - -[[package]] -name = "bitfield" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "cc" -version = "1.0.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" - -[[package]] -name = "cexpr" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clang-sys" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "cortex-m" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643a210c1bdc23d0db511e2a576082f4ff4dcae9d0c37f50b431b8f8439d6d6b" -dependencies = [ - "bare-metal", - "bitfield", - "embedded-hal", - "volatile-register", -] - -[[package]] -name = "cstr_core" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2807c5e92588b6bf1c8c0354af2a4f079d0586c683df322aea719d5dc9b8d5bb" -dependencies = [ - "cty", - "memchr", -] - -[[package]] -name = "cty" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3" - -[[package]] -name = "embedded-hal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db184d3fa27bc7a2344250394c0264144dfe0bc81a4401801dcb964b8dd172ad" -dependencies = [ - "nb 0.1.3", - "void", -] - -[[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7ee8a997d259962217f40279f34201fdf06e669bafa69d7c1f4c7ff1893b5f6" -dependencies = [ - "atomic-polyfill", - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3" -dependencies = [ - "arbitrary", - "cc", - "once_cell", -] - -[[package]] -name = "libloading" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "memchr" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" - -[[package]] -name = "nb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" -dependencies = [ - "nb 1.0.0", -] - -[[package]] -name = "nb" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" - -[[package]] -name = "nom" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "memchr", - "version_check", -] - -[[package]] -name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "proc-macro2" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "shlex" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "trezor_lib" -version = "0.1.0" -dependencies = [ - "bindgen", - "cc", - "cstr_core", - "cty", - "glob", - "heapless", -] - -[[package]] -name = "trezor_lib-fuzz" -version = "0.0.0" -dependencies = [ - "libfuzzer-sys", - "trezor_lib", -] - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "vcell" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" - -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "volatile-register" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286" -dependencies = [ - "vcell", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/extmod/trezor-firmware/poetry.lock b/extmod/trezor-firmware/poetry.lock deleted file mode 100644 index e996f7ba8..000000000 --- a/extmod/trezor-firmware/poetry.lock +++ /dev/null @@ -1,1726 +0,0 @@ -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "astroid" -version = "2.8.2" -description = "An abstract syntax tree for Python with inference support." -category = "main" -optional = false -python-versions = "~=3.6" - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} -typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<1.13" - -[[package]] -name = "atomicwrites" -version = "1.4.0" -description = "Atomic file writes." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "attrs" -version = "20.3.0" -description = "Classes Without Boilerplate" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] - -[[package]] -name = "autoflake" -version = "1.4" -description = "Removes unused imports and unused variables" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pyflakes = ">=1.1.0" - -[[package]] -name = "black" -version = "20.8b1" -description = "The uncompromising code formatter." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -appdirs = "*" -click = ">=7.1.2" -dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} -mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" -regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] - -[[package]] -name = "certifi" -version = "2020.12.5" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "cffi" -version = "1.14.4" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "click" -version = "7.1.2" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "click-default-group" -version = "1.2.2" -description = "Extends click.Group to invoke a command without explicit subcommand name" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -click = "*" - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "construct" -version = "2.10.58" -description = "A powerful declarative symmetric parser/builder for binary data" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -extras = ["enum34", "numpy", "arrow", "ruamel.yaml"] - -[[package]] -name = "coverage" -version = "4.5.4" -description = "Code coverage measurement for Python" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" - -[[package]] -name = "cryptography" -version = "35.0.0" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools_rust (>=0.11.4)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] - -[[package]] -name = "curve25519-donna" -version = "1.3" -description = "Python wrapper for the Curve25519 cryptographic library" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "dataclasses" -version = "0.8" -description = "A backport of the dataclasses module for Python 3.6" -category = "main" -optional = false -python-versions = ">=3.6, <3.7" - -[[package]] -name = "demjson3" -version = "3.0.5" -description = "encoder, decoder, and lint/validator for JSON (JavaScript Object Notation) compliant with RFC 7159" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "distlib" -version = "0.3.1" -description = "Distribution utilities" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "dominate" -version = "2.6.0" -description = "Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "ecdsa" -version = "0.16.1" -description = "ECDSA cryptographic signature library (pure python)" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -gmpy = ["gmpy"] -gmpy2 = ["gmpy2"] - -[[package]] -name = "ed25519" -version = "1.5" -description = "Ed25519 public-key signatures" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "fido2" -version = "0.8.1" -description = "Python based FIDO 2.0 library" -category = "main" -optional = false -python-versions = ">=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" - -[package.dependencies] -cryptography = ">=1.5" -six = "*" -uhid-freebsd = {version = ">=1.2.1", markers = "platform_system == \"FreeBSD\""} - -[package.extras] -pcsc = ["pyscard"] - -[[package]] -name = "filelock" -version = "3.0.12" -description = "A platform independent file lock." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "flake8" -version = "3.8.4" -description = "the modular source code checker: pep8 pyflakes and co" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" - -[[package]] -name = "flake8-requirements" -version = "1.3.3" -description = "Package requirements checker, plugin for flake8" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -flake8 = ">=2.0.0" -toml = "*" - -[[package]] -name = "flaky" -version = "3.7.0" -description = "Plugin for nose or pytest that automatically reruns flaky tests." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "graphviz" -version = "0.16" -description = "Simple Python interface for Graphviz" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" - -[package.extras] -dev = ["tox (>=3)", "flake8", "pep8-naming", "wheel", "twine"] -docs = ["sphinx (>=1.8)", "sphinx-rtd-theme"] -test = ["mock (>=3)", "pytest (>=4)", "pytest-mock (>=2)", "pytest-cov"] - -[[package]] -name = "hypothesis" -version = "6.1.1" -description = "A library for property-based testing" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -attrs = ">=19.2.0" -sortedcontainers = ">=2.1.0,<3.0.0" - -[package.extras] -all = ["black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.3)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "importlib-resources (>=3.3.0)", "importlib-metadata", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"] -cli = ["click (>=7.0)", "black (>=19.10b0)"] -codemods = ["libcst (>=0.3.16)"] -dateutil = ["python-dateutil (>=1.4)"] -django = ["pytz (>=2014.1)", "django (>=2.2)"] -dpcontracts = ["dpcontracts (>=0.4)"] -ghostwriter = ["black (>=19.10b0)"] -lark = ["lark-parser (>=0.6.5)"] -numpy = ["numpy (>=1.9.0)"] -pandas = ["pandas (>=0.25)"] -pytest = ["pytest (>=4.3)"] -pytz = ["pytz (>=2014.1)"] -redis = ["redis (>=3.0.0)"] -zoneinfo = ["importlib-resources (>=3.3.0)", "backports.zoneinfo (>=0.2.1)", "tzdata (>=2020.4)"] - -[[package]] -name = "idna" -version = "2.10" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "importlib-metadata" -version = "3.4.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] - -[[package]] -name = "importlib-resources" -version = "5.1.0" -description = "Read resources from Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] - -[[package]] -name = "incremental" -version = "21.3.0" -description = "A small library that versions your Python projects." -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -scripts = ["click (>=6.0)", "twisted (>=16.4.0)"] - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "inotify" -version = "0.2.10" -description = "An adapter to Linux kernel support for inotify directory-watching." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -nose = "*" - -[[package]] -name = "isort" -version = "4.3.21" -description = "A Python utility / library to sort Python imports." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -pipfile = ["pipreqs", "requirementslib"] -pyproject = ["toml"] -requirements = ["pipreqs", "pip-api"] -xdg_home = ["appdirs (>=1.4.0)"] - -[[package]] -name = "jinja2" -version = "2.11.3" -description = "A very fast and expressive template engine." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -MarkupSafe = ">=0.23" - -[package.extras] -i18n = ["Babel (>=0.8)"] - -[[package]] -name = "lazy-object-proxy" -version = "1.6.0" -description = "A fast and thorough lazy object proxy." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[[package]] -name = "libusb1" -version = "1.9.1" -description = "Pure-python wrapper for libusb-1.0" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "mako" -version = "1.1.4" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["babel"] -lingua = ["lingua"] - -[[package]] -name = "markupsafe" -version = "1.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" - -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "mnemonic" -version = "0.20" -description = "Implementation of Bitcoin BIP-0039" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "munch" -version = "2.5.0" -description = "A dot-accessible dictionary (a la JavaScript objects)" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - -[package.extras] -testing = ["pytest", "coverage", "astroid (>=1.5.3,<1.6.0)", "pylint (>=1.7.2,<1.8.0)", "astroid (>=2.0)", "pylint (>=2.3.1,<2.4.0)"] -yaml = ["PyYAML (>=5.1.0)"] - -[[package]] -name = "mypy" -version = "0.910" -description = "Optional static typing for Python" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.7.4" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "nanopb" -version = "0.4.5.post1" -description = "Nanopb is a small code-size Protocol Buffers implementation in ansi C. It is especially suitable for use in microcontrollers, but fits any memory restricted system." -category = "main" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -protobuf = ">=3.6" - -[package.extras] -grpcio-tools = ["grpcio-tools (>=1.26.0rc1)"] - -[[package]] -name = "nose" -version = "1.3.7" -description = "nose extends unittest to make testing easier" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "packaging" -version = "20.9" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -pyparsing = ">=2.0.2" - -[[package]] -name = "pathspec" -version = "0.8.1" -description = "Utility library for gitignore style pattern matching of file paths." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pillow" -version = "8.1.0" -description = "Python Imaging Library (Fork)" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "platformdirs" -version = "2.4.0" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] - -[[package]] -name = "pluggy" -version = "0.13.1" -description = "plugin and hook calling mechanisms for python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] - -[[package]] -name = "protobuf" -version = "3.14.0" -description = "Protocol Buffers" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = ">=1.9" - -[[package]] -name = "py" -version = "1.10.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyasn1" -version = "0.4.8" -description = "ASN.1 types and codecs" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyblake2" -version = "1.1.2" -description = "BLAKE2 hash function extension module" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pycodestyle" -version = "2.6.0" -description = "Python style guide checker" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pycparser" -version = "2.20" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyflakes" -version = "2.2.0" -description = "passive checker of Python programs" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pylint" -version = "2.11.1" -description = "python code static checker" -category = "main" -optional = false -python-versions = "~=3.6" - -[package.dependencies] -astroid = ">=2.8.0,<2.9" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.7" -platformdirs = ">=2.2.0" -toml = ">=0.7.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[[package]] -name = "pyparsing" -version = "2.4.7" -description = "Python parsing module" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "pyro4" -version = "4.80" -description = "distributed object middleware for Python (RPC)" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -serpent = {version = ">=1.27", markers = "python_version >= \"3.2\""} - -[[package]] -name = "pyserial" -version = "3.5" -description = "Python Serial Port Extension" -category = "main" -optional = false -python-versions = "*" - -[package.extras] -cp2110 = ["hidapi"] - -[[package]] -name = "pytest" -version = "6.2.2" -description = "pytest: simple powerful testing with Python" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<1.0.0a1" -py = ">=1.8.2" -toml = "*" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - -[[package]] -name = "pytest-ordering" -version = "0.6" -description = "pytest plugin to run your tests in a specific order" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pytest = "*" - -[[package]] -name = "pytest-random-order" -version = "1.0.4" -description = "Randomise the order in which pytest tests are run with some control over the randomness" -category = "main" -optional = false -python-versions = ">=3.5.0" - -[package.dependencies] -pytest = ">=3.0.0" - -[[package]] -name = "pytest-timeout" -version = "1.4.2" -description = "py.test plugin to abort hanging tests" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pytest = ">=3.6.0" - -[[package]] -name = "pyyaml" -version = "5.4.1" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[[package]] -name = "regex" -version = "2020.11.13" -description = "Alternative regular expression module, to replace re." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "requests" -version = "2.25.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] - -[[package]] -name = "scan-build" -version = "2.0.19" -description = "static code analyzer wrapper for Clang." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "scons" -version = "4.1.0.post1" -description = "Open Source next-generation build tool." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "serpent" -version = "1.30.2" -description = "Serialization based on ast.literal_eval" -category = "main" -optional = false -python-versions = ">=3.2" - -[[package]] -name = "shamir-mnemonic" -version = "0.2.1" -description = "SLIP-39 Shamir Mnemonics" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -attrs = "*" -click = ">=7,<8" -colorama = "*" - -[[package]] -name = "six" -version = "1.15.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "sortedcontainers" -version = "2.3.0" -description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "termcolor" -version = "1.1.0" -description = "ANSII Color formatting for output in terminal." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "towncrier" -version = "21.3.0" -description = "Building newsfiles for your project." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -click = "*" -click-default-group = "*" -incremental = "*" -jinja2 = "*" -toml = "*" - -[package.extras] -dev = ["packaging"] - -[[package]] -name = "tox" -version = "3.21.4" -description = "tox is a generic virtualenv management and test command line tool" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} -filelock = ">=3.0.0" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" -toml = ">=0.9.4" -virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" - -[package.extras] -docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)", "pathlib2 (>=2.3.3)"] - -[[package]] -name = "trezor" -version = "0.13.0" -description = "Python library for communicating with Trezor Hardware Wallet" -category = "main" -optional = false -python-versions = ">=3.6" -develop = true - -[package.dependencies] -attrs = "*" -click = ">=7,<8" -construct = ">=2.9" -ecdsa = ">=0.9" -libusb1 = ">=1.6.4" -mnemonic = ">=0.17" -requests = ">=2.4.0" -typing_extensions = ">=3.7.4" - -[package.extras] -ethereum = ["rlp (>=1.1.0)", "web3 (>=4.8)"] -extra = ["pillow"] -full = ["pillow", "pyqt5", "hidapi (>=0.7.99.post20)", "rlp (>=1.1.0)", "web3 (>=4.8)"] -hidapi = ["hidapi (>=0.7.99.post20)"] -qt-widgets = ["pyqt5"] - -[package.source] -type = "directory" -url = "python" - -[[package]] -name = "typed-ast" -version = "1.4.2" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "types-click" -version = "7.1.8" -description = "Typing stubs for click" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "uhid-freebsd" -version = "1.2.1" -description = "Get information on FreeBSD uhid devices." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "urllib3" -version = "1.26.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.4.2" -description = "Virtual Python Environment builder" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" - -[package.dependencies] -appdirs = ">=1.4.3,<2" -distlib = ">=0.3.1,<1" -filelock = ">=3.0.0,<4" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -importlib-resources = {version = ">=1.0", markers = "python_version < \"3.7\""} -six = ">=1.9.0,<2" - -[package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] - -[[package]] -name = "wrapt" -version = "1.12.1" -description = "Module for decorators, wrappers and monkey patching." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "yamllint" -version = "1.26.0" -description = "A linter for YAML files." -category = "main" -optional = false -python-versions = ">=3.5.*" - -[package.dependencies] -pathspec = ">=0.5.3" -pyyaml = "*" - -[[package]] -name = "zipp" -version = "3.4.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.6" -content-hash = "da1edd6acca40dd11b35035252e99b47ad309ed65ce13ed1fd19040968a40476" - -[metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -astroid = [ - {file = "astroid-2.8.2-py3-none-any.whl", hash = "sha256:9eaeaf92b3e21b70bec1a262e7eb118d2e96294892a5de595c92a12adc80dfc2"}, - {file = "astroid-2.8.2.tar.gz", hash = "sha256:304e99c129794f2cfda584a12b71fde85205da950e2f330f4be09150525ae949"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, -] -autoflake = [ - {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"}, -] -black = [ - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, -] -certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, -] -cffi = [ - {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, - {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, - {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, - {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, - {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, - {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, - {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, - {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, - {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, - {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"}, - {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, - {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, - {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"}, - {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, - {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, - {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e"}, - {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, - {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, - {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, -] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] -click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, -] -click-default-group = [ - {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -construct = [ - {file = "construct-2.10.58.tar.gz", hash = "sha256:c424fffe3b9d6a2efe50413be88a23e0fca0d3fcbdce0e3f43b94720dd66f1af"}, -] -coverage = [ - {file = "coverage-4.5.4-cp26-cp26m-macosx_10_12_x86_64.whl", hash = "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28"}, - {file = "coverage-4.5.4-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c"}, - {file = "coverage-4.5.4-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce"}, - {file = "coverage-4.5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe"}, - {file = "coverage-4.5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888"}, - {file = "coverage-4.5.4-cp27-cp27m-win32.whl", hash = "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc"}, - {file = "coverage-4.5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24"}, - {file = "coverage-4.5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437"}, - {file = "coverage-4.5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6"}, - {file = "coverage-4.5.4-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5"}, - {file = "coverage-4.5.4-cp34-cp34m-macosx_10_12_x86_64.whl", hash = "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef"}, - {file = "coverage-4.5.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e"}, - {file = "coverage-4.5.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca"}, - {file = "coverage-4.5.4-cp34-cp34m-win32.whl", hash = "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0"}, - {file = "coverage-4.5.4-cp34-cp34m-win_amd64.whl", hash = "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1"}, - {file = "coverage-4.5.4-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7"}, - {file = "coverage-4.5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47"}, - {file = "coverage-4.5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"}, - {file = "coverage-4.5.4-cp35-cp35m-win32.whl", hash = "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e"}, - {file = "coverage-4.5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d"}, - {file = "coverage-4.5.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9"}, - {file = "coverage-4.5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755"}, - {file = "coverage-4.5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9"}, - {file = "coverage-4.5.4-cp36-cp36m-win32.whl", hash = "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f"}, - {file = "coverage-4.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5"}, - {file = "coverage-4.5.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca"}, - {file = "coverage-4.5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650"}, - {file = "coverage-4.5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2"}, - {file = "coverage-4.5.4-cp37-cp37m-win32.whl", hash = "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5"}, - {file = "coverage-4.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351"}, - {file = "coverage-4.5.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"}, - {file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"}, -] -cryptography = [ - {file = "cryptography-35.0.0-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9"}, - {file = "cryptography-35.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6"}, - {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d"}, - {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa"}, - {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e"}, - {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992"}, - {file = "cryptography-35.0.0-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6"}, - {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d"}, - {file = "cryptography-35.0.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6"}, - {file = "cryptography-35.0.0-cp36-abi3-win32.whl", hash = "sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8"}, - {file = "cryptography-35.0.0-cp36-abi3-win_amd64.whl", hash = "sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588"}, - {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953"}, - {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6"}, - {file = "cryptography-35.0.0-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd"}, - {file = "cryptography-35.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76"}, - {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999"}, - {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad"}, - {file = "cryptography-35.0.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2"}, - {file = "cryptography-35.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c"}, - {file = "cryptography-35.0.0.tar.gz", hash = "sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d"}, -] -curve25519-donna = [ - {file = "curve25519-donna-1.3.tar.gz", hash = "sha256:1818a9d5356a05c022cd504f44fe1d2f641a5c020f8a4c51b2294e02bd9c1bf0"}, -] -dataclasses = [ - {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, - {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, -] -demjson3 = [ - {file = "demjson3-3.0.5.tar.gz", hash = "sha256:ab9aabdd85695f3684fc296f39766a2730f6c8de81d23f7048073dfe2f616d80"}, -] -distlib = [ - {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, - {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, -] -dominate = [ - {file = "dominate-2.6.0-py2.py3-none-any.whl", hash = "sha256:84b5f71ed30021193cb0faa45d7776e1083f392cfe67a49f44e98cb2ed76c036"}, - {file = "dominate-2.6.0.tar.gz", hash = "sha256:76ec2cde23700a6fc4fee098168b9dee43b99c2f1dd0ca6a711f683e8eb7e1e4"}, -] -ecdsa = [ - {file = "ecdsa-0.16.1-py2.py3-none-any.whl", hash = "sha256:881fa5e12bb992972d3d1b3d4dfbe149ab76a89f13da02daa5ea1ec7dea6e747"}, - {file = "ecdsa-0.16.1.tar.gz", hash = "sha256:cfc046a2ddd425adbd1a78b3c46f0d1325c657811c0f45ecc3a0a6236c1e50ff"}, -] -ed25519 = [ - {file = "ed25519-1.5.tar.gz", hash = "sha256:02053ee019ceef0df97294be2d4d5a8fc120fc86e81e08bec1245fc0f9403358"}, -] -fido2 = [ - {file = "fido2-0.8.1.tar.gz", hash = "sha256:449068f6876f397c8bb96ebc6a75c81c2692f045126d3f13ece21d409acdf7c3"}, -] -filelock = [ - {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, - {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, -] -flake8 = [ - {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, - {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, -] -flake8-requirements = [ - {file = "flake8_requirements-1.3.3-py2.py3-none-any.whl", hash = "sha256:36b3c35c39e254814eb8c39d1c4c104a4da640613b53628dbcfbfd231d680d69"}, -] -flaky = [ - {file = "flaky-3.7.0-py2.py3-none-any.whl", hash = "sha256:d6eda73cab5ae7364504b7c44670f70abed9e75f77dd116352f662817592ec9c"}, - {file = "flaky-3.7.0.tar.gz", hash = "sha256:3ad100780721a1911f57a165809b7ea265a7863305acb66708220820caf8aa0d"}, -] -graphviz = [ - {file = "graphviz-0.16-py2.py3-none-any.whl", hash = "sha256:3cad5517c961090dfc679df6402a57de62d97703e2880a1a46147bb0dc1639eb"}, - {file = "graphviz-0.16.zip", hash = "sha256:d2d25af1c199cad567ce4806f0449cb74eb30cf451fd7597251e1da099ac6e57"}, -] -hypothesis = [ - {file = "hypothesis-6.1.1-py3-none-any.whl", hash = "sha256:4da042e6df01417308d389afa8769de0a762b827476f431eb7848852f4a02647"}, - {file = "hypothesis-6.1.1.tar.gz", hash = "sha256:47673d3c0cb87055f5b4d322d3d16a7556ae5962e9677aa98d6f5503ffd74f7a"}, -] -idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, -] -importlib-metadata = [ - {file = "importlib_metadata-3.4.0-py3-none-any.whl", hash = "sha256:ace61d5fc652dc280e7b6b4ff732a9c2d40db2c0f92bc6cb74e07b73d53a1771"}, - {file = "importlib_metadata-3.4.0.tar.gz", hash = "sha256:fa5daa4477a7414ae34e95942e4dd07f62adf589143c875c133c1e53c4eff38d"}, -] -importlib-resources = [ - {file = "importlib_resources-5.1.0-py3-none-any.whl", hash = "sha256:885b8eae589179f661c909d699a546cf10d83692553e34dca1bf5eb06f7f6217"}, - {file = "importlib_resources-5.1.0.tar.gz", hash = "sha256:bfdad047bce441405a49cf8eb48ddce5e56c696e185f59147a8b79e75e9e6380"}, -] -incremental = [ - {file = "incremental-21.3.0-py2.py3-none-any.whl", hash = "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"}, - {file = "incremental-21.3.0.tar.gz", hash = "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -inotify = [ - {file = "inotify-0.2.10-py2-none-any.whl", hash = "sha256:397f8785450e41f606fe4eb6f5e8e0a1c70b354b56495225fc6c6fe7e07db0c9"}, - {file = "inotify-0.2.10.tar.gz", hash = "sha256:974a623a338482b62e16d4eb705fb863ed33ec178680fc3e96ccdf0df6c02a07"}, -] -isort = [ - {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, - {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, -] -jinja2 = [ - {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, - {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, -] -lazy-object-proxy = [ - {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, - {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, - {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, - {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, - {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, - {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, -] -libusb1 = [ - {file = "libusb1-1.9.1-py2-none-any.whl", hash = "sha256:4a024fffe195c49f3e7eadd2266087b4be065982f0cb41ef4b7e2c5053e7e65c"}, - {file = "libusb1-1.9.1-py2-none-win32.whl", hash = "sha256:16203d77a1f623b6f8f4e6c9d6bac79c1293b8d3e11de5f2f3c30d91380ae478"}, - {file = "libusb1-1.9.1-py2-none-win_amd64.whl", hash = "sha256:3905e907156f0a3fade75ddf82a777a6a901b245aa14500429275d221a1606c2"}, - {file = "libusb1-1.9.1-py3-none-any.whl", hash = "sha256:46708965226154681f8e0b14c48325c6d02e253c218e5d3aeff846ec274ceda8"}, - {file = "libusb1-1.9.1-py3-none-win32.whl", hash = "sha256:3a53d94add2799eaa1b412e7a5e384486c9109745217b9ac7f94101ad0f41b96"}, - {file = "libusb1-1.9.1-py3-none-win_amd64.whl", hash = "sha256:b12666e8ad4df78e8f1bae36298c7d6f8f45d70ceea058b88631ef8478fd1eb0"}, - {file = "libusb1-1.9.1.tar.gz", hash = "sha256:d03ef15248c8b8ce440f6be4248eaadc074fc2dc5edd36c48e6e78eef3999292"}, -] -mako = [ - {file = "Mako-1.1.4-py2.py3-none-any.whl", hash = "sha256:aea166356da44b9b830c8023cd9b557fa856bd8b4035d6de771ca027dfc5cc6e"}, - {file = "Mako-1.1.4.tar.gz", hash = "sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab"}, -] -markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -mnemonic = [ - {file = "mnemonic-0.20-py3-none-any.whl", hash = "sha256:acd2168872d0379e7a10873bb3e12bf6c91b35de758135c4fbd1015ef18fafc5"}, - {file = "mnemonic-0.20.tar.gz", hash = "sha256:7c6fb5639d779388027a77944680aee4870f0fcd09b1e42a5525ee2ce4c625f6"}, -] -munch = [ - {file = "munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd"}, - {file = "munch-2.5.0.tar.gz", hash = "sha256:2d735f6f24d4dba3417fa448cae40c6e896ec1fdab6cdb5e6510999758a4dbd2"}, -] -mypy = [ - {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, - {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, - {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, - {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, - {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, - {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, - {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, - {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, - {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, - {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, - {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, - {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, - {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, - {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, - {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, - {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, - {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, - {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, - {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, - {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, - {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, - {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, - {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -nanopb = [ - {file = "nanopb-0.4.5.post1-py2.py3-none-any.whl", hash = "sha256:ff8d09a0e5dab73ace1bff538a0ff0dcee2a9abdc0c8aded6068eb215a25102e"}, - {file = "nanopb-0.4.5.post1.tar.gz", hash = "sha256:81b693d10f73772eeb030a59e3342952080dc3b8239738e95c700a90fa403f02"}, -] -nose = [ - {file = "nose-1.3.7-py2-none-any.whl", hash = "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a"}, - {file = "nose-1.3.7-py3-none-any.whl", hash = "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac"}, - {file = "nose-1.3.7.tar.gz", hash = "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"}, -] -packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, -] -pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, -] -pillow = [ - {file = "Pillow-8.1.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a"}, - {file = "Pillow-8.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2"}, - {file = "Pillow-8.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174"}, - {file = "Pillow-8.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded"}, - {file = "Pillow-8.1.0-cp36-cp36m-win32.whl", hash = "sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d"}, - {file = "Pillow-8.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d"}, - {file = "Pillow-8.1.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234"}, - {file = "Pillow-8.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8"}, - {file = "Pillow-8.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17"}, - {file = "Pillow-8.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7"}, - {file = "Pillow-8.1.0-cp37-cp37m-win32.whl", hash = "sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e"}, - {file = "Pillow-8.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b"}, - {file = "Pillow-8.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0"}, - {file = "Pillow-8.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a"}, - {file = "Pillow-8.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d"}, - {file = "Pillow-8.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae"}, - {file = "Pillow-8.1.0-cp38-cp38-win32.whl", hash = "sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59"}, - {file = "Pillow-8.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c"}, - {file = "Pillow-8.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6"}, - {file = "Pillow-8.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378"}, - {file = "Pillow-8.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7"}, - {file = "Pillow-8.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0"}, - {file = "Pillow-8.1.0-cp39-cp39-win32.whl", hash = "sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b"}, - {file = "Pillow-8.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865"}, - {file = "Pillow-8.1.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9"}, - {file = "Pillow-8.1.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913"}, - {file = "Pillow-8.1.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206"}, - {file = "Pillow-8.1.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9"}, - {file = "Pillow-8.1.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032"}, - {file = "Pillow-8.1.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820"}, - {file = "Pillow-8.1.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5"}, - {file = "Pillow-8.1.0.tar.gz", hash = "sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba"}, -] -platformdirs = [ - {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, - {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, -] -pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] -protobuf = [ - {file = "protobuf-3.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:629b03fd3caae7f815b0c66b41273f6b1900a579e2ccb41ef4493a4f5fb84f3a"}, - {file = "protobuf-3.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5b7a637212cc9b2bcf85dd828b1178d19efdf74dbfe1ddf8cd1b8e01fdaaa7f5"}, - {file = "protobuf-3.14.0-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:43b554b9e73a07ba84ed6cf25db0ff88b1e06be610b37656e292e3cbb5437472"}, - {file = "protobuf-3.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5e9806a43232a1fa0c9cf5da8dc06f6910d53e4390be1fa06f06454d888a9142"}, - {file = "protobuf-3.14.0-cp35-cp35m-win32.whl", hash = "sha256:1c51fda1bbc9634246e7be6016d860be01747354ed7015ebe38acf4452f470d2"}, - {file = "protobuf-3.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:4b74301b30513b1a7494d3055d95c714b560fbb630d8fb9956b6f27992c9f980"}, - {file = "protobuf-3.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:86a75477addde4918e9a1904e5c6af8d7b691f2a3f65587d73b16100fbe4c3b2"}, - {file = "protobuf-3.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ecc33531a213eee22ad60e0e2aaea6c8ba0021f0cce35dbf0ab03dee6e2a23a1"}, - {file = "protobuf-3.14.0-cp36-cp36m-win32.whl", hash = "sha256:72230ed56f026dd664c21d73c5db73ebba50d924d7ba6b7c0d81a121e390406e"}, - {file = "protobuf-3.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:0fc96785262042e4863b3f3b5c429d4636f10d90061e1840fce1baaf59b1a836"}, - {file = "protobuf-3.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4e75105c9dfe13719b7293f75bd53033108f4ba03d44e71db0ec2a0e8401eafd"}, - {file = "protobuf-3.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2a7e2fe101a7ace75e9327b9c946d247749e564a267b0515cf41dfe450b69bac"}, - {file = "protobuf-3.14.0-cp37-cp37m-win32.whl", hash = "sha256:b0d5d35faeb07e22a1ddf8dce620860c8fe145426c02d1a0ae2688c6e8ede36d"}, - {file = "protobuf-3.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8971c421dbd7aad930c9bd2694122f332350b6ccb5202a8b7b06f3f1a5c41ed5"}, - {file = "protobuf-3.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9616f0b65a30851e62f1713336c931fcd32c057202b7ff2cfbfca0fc7d5e3043"}, - {file = "protobuf-3.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:22bcd2e284b3b1d969c12e84dc9b9a71701ec82d8ce975fdda19712e1cfd4e00"}, - {file = "protobuf-3.14.0-py2.py3-none-any.whl", hash = "sha256:0e247612fadda953047f53301a7b0407cb0c3cb4ae25a6fde661597a04039b3c"}, - {file = "protobuf-3.14.0.tar.gz", hash = "sha256:1d63eb389347293d8915fb47bee0951c7b5dab522a4a60118b9a18f33e21f8ce"}, -] -py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, -] -pyasn1 = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, - {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, - {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, -] -pyblake2 = [ - {file = "pyblake2-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:3757f7ad709b0e1b2a6b3919fa79fe3261f166fc375cd521f2be480f8319dde9"}, - {file = "pyblake2-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:8043267fbc0b2f3748c6920591cd0b8b5609dcce60c504c32858aa36206386f2"}, - {file = "pyblake2-1.1.2-cp34-cp34m-win32.whl", hash = "sha256:4d47b4a2c1d292b1e460bde1dda4d13aa792ed2ed70fcc263b6bc24632c8e902"}, - {file = "pyblake2-1.1.2-cp34-cp34m-win_amd64.whl", hash = "sha256:982295a87907d50f4723db6bc724660da76b6547826d52160171d54f95b919ac"}, - {file = "pyblake2-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:baa2190bfe549e36163aa44664d4ee3a9080b236fc5d42f50dc6fd36bbdc749e"}, - {file = "pyblake2-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:407e02c7f8f36fcec1b7aa114ddca0c1060c598142ea6f6759d03710b946a7e3"}, - {file = "pyblake2-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:fbc9fcde75713930bc2a91b149e97be2401f7c9c56d735b46a109210f58d7358"}, - {file = "pyblake2-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:c53417ee0bbe77db852d5fd1036749f03696ebc2265de359fe17418d800196c4"}, - {file = "pyblake2-1.1.2.tar.gz", hash = "sha256:5ccc7eb02edb82fafb8adbb90746af71460fbc29aa0f822526fc976dff83e93f"}, -] -pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, -] -pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, -] -pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, -] -pylint = [ - {file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"}, - {file = "pylint-2.11.1.tar.gz", hash = "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"}, -] -pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, -] -pyro4 = [ - {file = "Pyro4-4.80-py2.py3-none-any.whl", hash = "sha256:f195a4a9403f58807e66ca269c771e1d268064945f65cfbbf59a1feb12a1695f"}, - {file = "Pyro4-4.80.tar.gz", hash = "sha256:46847ca703de3f483fbd0b2d22622f36eff03e6ef7ec7704d4ecaa3964cb2220"}, -] -pyserial = [ - {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, - {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, -] -pytest = [ - {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, - {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, -] -pytest-ordering = [ - {file = "pytest-ordering-0.6.tar.gz", hash = "sha256:561ad653626bb171da78e682f6d39ac33bb13b3e272d406cd555adb6b006bda6"}, - {file = "pytest_ordering-0.6-py2-none-any.whl", hash = "sha256:27fba3fc265f5d0f8597e7557885662c1bdc1969497cd58aff6ed21c3b617de2"}, - {file = "pytest_ordering-0.6-py3-none-any.whl", hash = "sha256:3f314a178dbeb6777509548727dc69edf22d6d9a2867bf2d310ab85c403380b6"}, -] -pytest-random-order = [ - {file = "pytest-random-order-1.0.4.tar.gz", hash = "sha256:6b2159342a4c8c10855bc4fc6d65ee890fc614cb2b4ff688979b008a82a0ff52"}, - {file = "pytest_random_order-1.0.4-py3-none-any.whl", hash = "sha256:72279a7f823969e18b10e438950f58330d17e0fcffb57cbd7929770cd687ecb2"}, -] -pytest-timeout = [ - {file = "pytest-timeout-1.4.2.tar.gz", hash = "sha256:20b3113cf6e4e80ce2d403b6fb56e9e1b871b510259206d40ff8d609f48bda76"}, - {file = "pytest_timeout-1.4.2-py2.py3-none-any.whl", hash = "sha256:541d7aa19b9a6b4e475c759fd6073ef43d7cdc9a92d95644c260076eb257a063"}, -] -pyyaml = [ - {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, - {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, - {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, - {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, - {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, - {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, - {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, - {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, - {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, -] -regex = [ - {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, - {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, - {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, - {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, - {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, - {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, - {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, - {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, - {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, - {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, - {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, - {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, - {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, -] -requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, -] -scan-build = [ - {file = "scan-build-2.0.19.tar.gz", hash = "sha256:79edcbcdec433682528258870ed1efbf66d3ebcf857ecca335c830d8adbf17eb"}, -] -scons = [ - {file = "SCons-4.1.0.post1-py3-none-any.whl", hash = "sha256:52272288986f3e401d28590562c573405dff0decfbf701926e751c0954b64da6"}, - {file = "SCons-4.1.0.post1.tar.gz", hash = "sha256:ecb062482b9d80319b56758c0341eb717735437f86a575bac3552804428bd73e"}, -] -serpent = [ - {file = "serpent-1.30.2-py3-none-any.whl", hash = "sha256:b2e8fa890481ca068faf9913c7019db5a8c30aa5e7e5870846996749872cecb3"}, - {file = "serpent-1.30.2.tar.gz", hash = "sha256:72753820246a7d8486e8b385353e3bbf769abfceec2e850fa527a288b084ff7a"}, -] -shamir-mnemonic = [ - {file = "shamir-mnemonic-0.2.1.tar.gz", hash = "sha256:174415bf04b2d50237238fb66d5d2955b6418b0156912b015928eb791f7f8ea6"}, - {file = "shamir_mnemonic-0.2.1-py3-none-any.whl", hash = "sha256:f883e540ec5594970a8d7ae64bbd33eb9936067042f39f2b6ad292de2ba5e5fe"}, -] -six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, -] -sortedcontainers = [ - {file = "sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f"}, - {file = "sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1"}, -] -termcolor = [ - {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -towncrier = [ - {file = "towncrier-21.3.0-py2.py3-none-any.whl", hash = "sha256:e6ccec65418bbcb8de5c908003e130e37fe0e9d6396cb77c1338241071edc082"}, - {file = "towncrier-21.3.0.tar.gz", hash = "sha256:6eed0bc924d72c98c000cb8a64de3bd566e5cb0d11032b73fcccf8a8f956ddfe"}, -] -tox = [ - {file = "tox-3.21.4-py2.py3-none-any.whl", hash = "sha256:65d0e90ceb816638a50d64f4b47b11da767b284c0addda2294cb3cd69bd72425"}, - {file = "tox-3.21.4.tar.gz", hash = "sha256:cf7fef81a3a2434df4d7af2a6d1bf606d2970220addfbe7dea2615bd4bb2c252"}, -] -trezor = [] -typed-ast = [ - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, - {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, - {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, - {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, - {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, - {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, - {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, - {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, - {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, - {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, -] -types-click = [ - {file = "types-click-7.1.8.tar.gz", hash = "sha256:b6604968be6401dc516311ca50708a0a28baa7a0cb840efd7412f0dbbff4e092"}, - {file = "types_click-7.1.8-py3-none-any.whl", hash = "sha256:8cb030a669e2e927461be9827375f83c16b8178c365852c060a34e24871e7e81"}, -] -typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, -] -uhid-freebsd = [ - {file = "uhid-freebsd-1.2.1.tar.gz", hash = "sha256:13436ea492271b27cd4847fe3168fd467c0f31aa295abd4aeab61e92920fdd58"}, -] -urllib3 = [ - {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, - {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, -] -virtualenv = [ - {file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"}, - {file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"}, -] -wrapt = [ - {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, -] -yamllint = [ - {file = "yamllint-1.26.0-py2.py3-none-any.whl", hash = "sha256:8a5f8e442f49309eaf3e9d7232ce76f2fc8026f5c0c0b164b83f33fed1399637"}, - {file = "yamllint-1.26.0.tar.gz", hash = "sha256:b0e4c89985c7f5f8451c2eb8c67d804d10ac13a4abe031cbf49bdf3465d01087"}, -] -zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, -] diff --git a/lib/lv_bindings/lvgl/docs/requirements.txt b/lib/lv_bindings/lvgl/docs/requirements.txt deleted file mode 100644 index 06400084d..000000000 --- a/lib/lv_bindings/lvgl/docs/requirements.txt +++ /dev/null @@ -1,34 +0,0 @@ -alabaster==0.7.12 -Babel==2.9.1 -breathe==4.30.0 -certifi==2020.12.5 -chardet==4.0.0 -commonmark==0.9.1 -docutils==0.16 -idna==2.10 -imagesize==1.2.0 -importlib-metadata==4.0.1 -Jinja2==2.11.3 -Markdown==3.3.4 -MarkupSafe==1.1.1 -packaging==20.9 -Pygments==2.9.0 -pyparsing==2.4.7 -pytz==2021.1 -recommonmark==0.6.0 -requests==2.25.1 -six==1.16.0 -snowballstemmer==2.1.0 -Sphinx==4.0.1 -sphinx-markdown-tables==0.0.15 -sphinx-rtd-theme==0.5.2 -sphinx-sitemap==2.2.0 -sphinxcontrib-applehelp==1.0.2 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==1.0.3 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.4 -typing-extensions==3.10.0.0 -urllib3==1.26.5 -zipp==3.4.1 From e66252c2852b1f2ee43a33844d11e77f65349615 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 1 Dec 2023 16:35:17 -0600 Subject: [PATCH 145/239] SFT-967: added taproot to address explorer --- .../boards/Passport/modules/pages/address_type_chooser_page.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py index cfec3287d..3f8352887 100644 --- a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py @@ -5,7 +5,7 @@ from pages import ChooserPage -from public_constants import AF_P2WPKH, AF_P2WPKH_P2SH, AF_CLASSIC +from public_constants import AF_P2WPKH, AF_P2WPKH_P2SH, AF_CLASSIC, AF_P2TR class AddressTypeChooserPage(ChooserPage): @@ -13,6 +13,7 @@ class AddressTypeChooserPage(ChooserPage): {'label': 'Segwit', 'value': AF_P2WPKH}, {'label': 'Wrapped Segwit', 'value': AF_P2WPKH_P2SH}, {'label': 'Legacy', 'value': AF_CLASSIC}, + {'label': 'Taproot', 'value': AF_P2TR}, ] def __init__(self, card_header={'title': 'Address Type'}, initial_value=None): From 12455370c158aaeb280601ab8dfc9f45c59fc0df Mon Sep 17 00:00:00 2001 From: sethforprivacy Date: Mon, 18 Dec 2023 05:53:37 -0500 Subject: [PATCH 146/239] Bump actions/upload-artifact to v4 --- .github/workflows/validate_and_build.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/validate_and_build.yaml b/.github/workflows/validate_and_build.yaml index 75ed9c229..796f8b94e 100644 --- a/.github/workflows/validate_and_build.yaml +++ b/.github/workflows/validate_and_build.yaml @@ -85,37 +85,37 @@ jobs: SIGNING_KEY: ${{ secrets.UserSigningKey }} - name: Upload firmware (unsigned) - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: v${{env.version}}-unsigned${{ matrix.build.suffix }}.bin path: ports/stm32/build-Passport/firmware-${{ env.SCREEN_MODE }}.bin - name: Upload firmware (signed) - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: v${{env.version}}-beta${{ matrix.build.suffix }}.bin path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.suffix }}.bin - name: Upload MD5 Hash - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: v${{env.version}}${{ matrix.build.hash_suffix }}-md5 path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-md5 - name: Upload Build Hash - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: v${{env.version}}${{ matrix.build.hash_suffix }}-build-hash path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-build-hash - name: Upload SHA256 Hash - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: v${{env.version}}${{ matrix.build.hash_suffix }}-sha256 path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-sha256 - name: Upload Hashes Markdown - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: v${{env.version}}${{ matrix.build.hash_suffix }}-hashes.md path: ports/stm32/build-Passport/v${{env.version}}-beta${{ matrix.build.hash_suffix }}-hashes.md @@ -159,7 +159,7 @@ jobs: run: just build-bootloader ${{ matrix.screen }} - name: Upload bootloader - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: bootloader-${{ env.SCREEN_MODE }}.bin path: ports/stm32/boards/Passport/bootloader/arm/release/bootloader-${{ env.SCREEN_MODE }}.bin From 2d75e5b33285a51dce2bce9cb44bdd753f4d2a08 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 18 Dec 2023 20:29:53 -0500 Subject: [PATCH 147/239] SFT-3093: presented nostr key with nice new address formatting --- .../boards/Passport/modules/flows/export_derived_key_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py index c7569f7a1..53b3165a9 100644 --- a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py @@ -95,6 +95,7 @@ async def show_qr_code(self): async def confirm_qr(self): from pages import InfoPage, LongTextPage import microns + from utils import stylize_address text = 'Confirm the exported {} on the following page'.format(self.key_type['title']) result = await InfoPage(text=text, left_micron=microns.Back).show() @@ -103,7 +104,7 @@ async def confirm_qr(self): self.back() return - result = await LongTextPage(text="\n" + self.data, + result = await LongTextPage(text="\n" + stylize_address(self.data), centered=True, card_header={'title': 'Confirm Key'}, left_micron=microns.Back).show() From f50a23ee0828e2d4a28fa584f5b9216ccf91443f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 19 Dec 2023 02:34:40 -0500 Subject: [PATCH 148/239] SFT-2961: added menu to choose seedqr type for master seed --- .../modules/flows/view_seed_words_flow.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index b2f98020b..88be48315 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -35,6 +35,7 @@ def __init__(self, self.allow_skip = allow_skip self.use_qr_button = qr_button self.seen_warning = False + self.qr_mode = None super().__init__(initial_state=self.generate_words, name='ViewSeedWordsFlow') async def generate_words(self): @@ -176,15 +177,24 @@ async def show_seed_words(self): self.goto(self.show_passphrase) async def qr_intro(self): - from pages import InfoPage + from pages import InfoPage, ChooserPage import microns + from data_codecs.qr_type import QRType + + options = [{'label': 'Compact SeedQR', 'value': QRType.COMPACT_SEED_QR}, + {'label': 'SeedQR', 'value': QRType.SEED_QR}] + + self.qr_mode = await ChooserPage(card_header={'title': 'Format'}, options=options).show() + + if self.qr_mode is None: + self.back() + return result = await InfoPage('The following QR code contains your seed words.\n\n' 'NEVER scan it from an Internet connected device.', left_micron=microns.Back).show() if not result: - self.back() return self.goto(self.qr_button) @@ -194,7 +204,7 @@ async def qr_button(self): from data_codecs.qr_type import QRType import microns - result = await ShowQRPage(QRType.COMPACT_SEED_QR, + result = await ShowQRPage(qr_type=self.qr_mode, qr_data=self.words, left_micron=microns.Back, right_micron=microns.Checkmark).show() From c8ef211a43565fd21e86eb52036d32e93b5581a1 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 19 Dec 2023 02:21:15 -0500 Subject: [PATCH 149/239] SFT-36: device name shows while loading menu --- ports/stm32/boards/Passport/modules/flows/login_flow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/login_flow.py b/ports/stm32/boards/Passport/modules/flows/login_flow.py index c5bd7c201..e73266f5a 100644 --- a/ports/stm32/boards/Passport/modules/flows/login_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/login_flow.py @@ -18,18 +18,18 @@ class LoginFlow(Flow): def __init__(self): self.pin = '' - super().__init__(initial_state=self.enter_pin, name='LoginFlow') - - async def enter_pin(self): device_name = common.settings.get('device_name', None) if device_name is not None: device_name = device_name.upper() + self.statusbar = {'title': (device_name or 'PASSPORT')} + super().__init__(initial_state=self.enter_pin, name='LoginFlow', statusbar=self.statusbar) + + async def enter_pin(self): try: (self.pin, is_done) = await PINEntryPage( card_header={'title': 'Enter PIN'}, - statusbar={'title': device_name}, security_words_message='Recognize these Security Words?', left_micron=microns.Shutdown, right_micron=microns.Checkmark, From 3442eeb2c1c4bd68a4bf5b6ba24a25aef521794a Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 19 Dec 2023 18:56:04 -0500 Subject: [PATCH 150/239] SFT-36: removed redundant line --- ports/stm32/boards/Passport/modules/flows/login_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/login_flow.py b/ports/stm32/boards/Passport/modules/flows/login_flow.py index e73266f5a..54714656d 100644 --- a/ports/stm32/boards/Passport/modules/flows/login_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/login_flow.py @@ -22,8 +22,8 @@ def __init__(self): device_name = common.settings.get('device_name', None) if device_name is not None: device_name = device_name.upper() - self.statusbar = {'title': (device_name or 'PASSPORT')} - super().__init__(initial_state=self.enter_pin, name='LoginFlow', statusbar=self.statusbar) + statusbar = {'title': (device_name or 'PASSPORT')} + super().__init__(initial_state=self.enter_pin, name='LoginFlow', statusbar=statusbar) async def enter_pin(self): From 59c73ad13dcbabb2b09fea9ab4fec8bc6abdb7a7 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 19 Dec 2023 02:56:59 -0500 Subject: [PATCH 151/239] SFT-3090: envoy export now uses correct account name based on passphrase --- ports/stm32/boards/Passport/modules/wallets/envoy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/envoy.py b/ports/stm32/boards/Passport/modules/wallets/envoy.py index bd27b801e..946e515fc 100644 --- a/ports/stm32/boards/Passport/modules/wallets/envoy.py +++ b/ports/stm32/boards/Passport/modules/wallets/envoy.py @@ -8,7 +8,7 @@ import ujson import chains import common -from utils import to_str, get_accounts +from utils import to_str, get_accounts_by_xfp from data_codecs.qr_type import QRType from public_constants import AF_CLASSIC, AF_P2WPKH from foundation import ur @@ -37,6 +37,7 @@ def create_envoy_export(sw_wallet=None, mode = get_bip_num_from_addr_type(addr_type, multisig) chain = chains.current_chain() + xfp = settings.get('xfp') with stash.SensitiveValues() as sv: acct_path = "m/{mode}'/{coin}'/{acct}'".format( @@ -45,12 +46,11 @@ def create_envoy_export(sw_wallet=None, acct=acct_num) child_node = sv.derive_path(acct_path) - xfp = settings.get('xfp') xpub = sv.chain.serialize_public(child_node, AF_CLASSIC) # print('xfp to export: {}'.format(xfp)) # print('xpub to export: {}'.format(xpub)) - accounts = get_accounts() + accounts = get_accounts_by_xfp(xfp) acct_name = '' if accounts is not None and len(accounts) > 0: for acct in accounts: From 2fabf6940d62c15694e42f3bd30de5a2c665ed8c Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 19 Dec 2023 18:34:25 -0500 Subject: [PATCH 152/239] SFT-3090: simplified account migration --- ports/stm32/boards/Passport/modules/flows/main_flow.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/main_flow.py b/ports/stm32/boards/Passport/modules/flows/main_flow.py index a9901c2d3..6aab2152b 100644 --- a/ports/stm32/boards/Passport/modules/flows/main_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/main_flow.py @@ -38,12 +38,11 @@ async def start(self): # Update account settings to include the default xfp accounts = get_accounts() - for i in range(len(accounts)): - account = accounts[i] + print(accounts) + for account in accounts: if account.get('xfp', None) is None: account['xfp'] = xfp - del accounts[i] - accounts.append(account) + print(accounts) common.settings.set('accounts', accounts) # Update old extensions keys to include the xfp From f38a9d22387d081085d5aa88386546f98b29122d Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 19 Dec 2023 18:54:12 -0500 Subject: [PATCH 153/239] SFT-3090: removed prints --- ports/stm32/boards/Passport/modules/flows/main_flow.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/main_flow.py b/ports/stm32/boards/Passport/modules/flows/main_flow.py index 6aab2152b..5c209349e 100644 --- a/ports/stm32/boards/Passport/modules/flows/main_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/main_flow.py @@ -38,11 +38,9 @@ async def start(self): # Update account settings to include the default xfp accounts = get_accounts() - print(accounts) for account in accounts: if account.get('xfp', None) is None: account['xfp'] = xfp - print(accounts) common.settings.set('accounts', accounts) # Update old extensions keys to include the xfp From e8cce82ae0314aae20c0abc6c725dfa2dc3a0b06 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 21 Nov 2023 14:45:10 -0600 Subject: [PATCH 154/239] SFT-2212: trial of new seed warning text --- ports/stm32/boards/Passport/modules/derived_key.py | 1 + .../Passport/modules/flows/export_derived_key_flow.py | 2 ++ .../boards/Passport/modules/flows/seed_warning_flow.py | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/derived_key.py b/ports/stm32/boards/Passport/modules/derived_key.py index 7f6e3437d..ad5873308 100644 --- a/ports/stm32/boards/Passport/modules/derived_key.py +++ b/ports/stm32/boards/Passport/modules/derived_key.py @@ -30,6 +30,7 @@ 'words': False, 'task': nostr_key_task, 'continue_text': 'post on your behalf', + 'info_type_text': 'your keys', 'menu': nostr_menu}, ] diff --git a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py index 53b3165a9..e2a818402 100644 --- a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py @@ -79,6 +79,7 @@ async def show_qr_code(self): result = await SeedWarningFlow(action_text="display your {} as a QR code" .format(self.key_type['title']), + info_type_text=self.key_type.get('info_type_text'), continue_text=self.key_type.get('continue_text', None)).run() if not result: @@ -119,6 +120,7 @@ async def save_to_sd(self): result = await SeedWarningFlow(action_text="copy your {} to the microSD card" .format(self.key_type['title']), + info_type_text=self.key_type.get('info_type_text'), continue_text=self.key_type.get('continue_text', None)).run() if not result: diff --git a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py index 0af508fcf..9a1d8f723 100644 --- a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py @@ -10,11 +10,13 @@ class SeedWarningFlow(Flow): def __init__(self, mention_passphrase=False, action_text="display your seed words", continue_text=None, + info_type_text=None, initial=False, allow_skip=True): self.mention_passphrase = mention_passphrase self.action_text = action_text self.continue_text = continue_text or "control your funds" + self.info_type_text = info_type_text or "these words" self.allow_skip = allow_skip self.initial = initial initial_state = self.show_skippable if (initial and allow_skip) else self.show_intro @@ -88,7 +90,9 @@ async def confirm_show(self): from pages import QuestionPage import microns - text = 'Anyone who knows this information can {}.'.format(self.continue_text) + text = 'Beware of any request to expose {} outside of Passport.' \ + 'Sharing them in any way could allow someone else to {}.' \ + .format(self.info_type_text, self.continue_text) left_micron = microns.Cancel if not self.allow_skip: From 9610932df8b8181cc42681dd50cbc6004524f863 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 27 Nov 2023 11:44:52 -0600 Subject: [PATCH 155/239] SFT-2212: removed 2nd warning from key manager exports to accommodate longer message on other flows --- .../boards/Passport/modules/derived_key.py | 4 ++-- .../modules/flows/export_derived_key_flow.py | 9 ++++----- .../modules/flows/seed_warning_flow.py | 18 +++++++++++++----- .../modules/flows/view_seed_words_flow.py | 13 +++++++++---- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/derived_key.py b/ports/stm32/boards/Passport/modules/derived_key.py index ad5873308..aeda4f15b 100644 --- a/ports/stm32/boards/Passport/modules/derived_key.py +++ b/ports/stm32/boards/Passport/modules/derived_key.py @@ -29,8 +29,8 @@ 'indexed': True, 'words': False, 'task': nostr_key_task, - 'continue_text': 'post on your behalf', - 'info_type_text': 'your keys', + 'continue_text': 'profile', + 'info_type_text': 'your key', 'menu': nostr_menu}, ] diff --git a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py index e2a818402..b3dc94caf 100644 --- a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py @@ -47,7 +47,8 @@ async def generate_key(self): qr_option=True, sd_option=True, path=self.path, - filename=self.filename).run() + filename=self.filename, + key_manager=True).run() self.set_result(result) return @@ -79,8 +80,7 @@ async def show_qr_code(self): result = await SeedWarningFlow(action_text="display your {} as a QR code" .format(self.key_type['title']), - info_type_text=self.key_type.get('info_type_text'), - continue_text=self.key_type.get('continue_text', None)).run() + key_manager=True).run() if not result: self.back() @@ -120,8 +120,7 @@ async def save_to_sd(self): result = await SeedWarningFlow(action_text="copy your {} to the microSD card" .format(self.key_type['title']), - info_type_text=self.key_type.get('info_type_text'), - continue_text=self.key_type.get('continue_text', None)).run() + key_manager=True).run() if not result: self.back() diff --git a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py index 9a1d8f723..ce6d359da 100644 --- a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py @@ -12,13 +12,15 @@ def __init__(self, mention_passphrase=False, continue_text=None, info_type_text=None, initial=False, - allow_skip=True): + allow_skip=True, + key_manager=False): self.mention_passphrase = mention_passphrase self.action_text = action_text - self.continue_text = continue_text or "control your funds" + self.continue_text = continue_text or "funds" self.info_type_text = info_type_text or "these words" self.allow_skip = allow_skip self.initial = initial + self.key_manager = key_manager initial_state = self.show_skippable if (initial and allow_skip) else self.show_intro super().__init__(initial_state=initial_state, name='SeedWarningFlow') @@ -57,9 +59,11 @@ async def show_intro(self): # Empty microns have no action, so backing out isn't allowed left_micron = microns.Back if self.allow_skip else None + right_micron = microns.Checkmark if self.key_manager else microns.Forward + result = await InfoPage( icon=lv.LARGE_ICON_SEED, text=text, - left_micron=left_micron, right_micron=microns.Forward).show() + left_micron=left_micron, right_micron=right_micron).show() if not result: self.set_result(False) @@ -69,6 +73,10 @@ async def show_intro(self): self.goto(self.prompt_backup) return + if self.key_manager: + self.set_result(True) + return + self.goto(self.confirm_show) async def prompt_backup(self): @@ -90,8 +98,8 @@ async def confirm_show(self): from pages import QuestionPage import microns - text = 'Beware of any request to expose {} outside of Passport.' \ - 'Sharing them in any way could allow someone else to {}.' \ + text = 'Anyone requesting you expose {} outside Passport ' \ + 'will gain full control over your {}. Take care.' \ .format(self.info_type_text, self.continue_text) left_micron = microns.Cancel diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index 88be48315..0a11bf3bb 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -15,7 +15,8 @@ def __init__(self, filename=None, initial=False, allow_skip=True, - qr_button=False): + qr_button=False, + key_manager=False): import microns self.external_key = external_key @@ -36,6 +37,7 @@ def __init__(self, self.use_qr_button = qr_button self.seen_warning = False self.qr_mode = None + self.key_manager = key_manager super().__init__(initial_state=self.generate_words, name='ViewSeedWordsFlow') async def generate_words(self): @@ -91,7 +93,8 @@ async def show_qr(self): result = await SeedWarningFlow(action_text="display your seed as a QR code", mention_passphrase=self.mention_passphrase, initial=self.initial, - allow_skip=self.allow_skip).run() + allow_skip=self.allow_skip, + key_manager=self.key_manager).run() if not result: self.back() @@ -127,7 +130,8 @@ async def save_to_sd(self): result = await SeedWarningFlow(action_text="copy your seed to the microSD card", mention_passphrase=self.mention_passphrase, initial=self.initial, - allow_skip=self.allow_skip).run() + allow_skip=self.allow_skip, + key_manager=self.key_manager).run() if not result: self.back() @@ -152,7 +156,8 @@ async def show_seed_words(self): if not self.seen_warning: # We already gave the seed warning flow result = await SeedWarningFlow(mention_passphrase=self.mention_passphrase, initial=self.initial, - allow_skip=self.allow_skip).run() + allow_skip=self.allow_skip, + key_manager=self.key_manager).run() if not result: self.set_result(False) From 147c575754f97e834af6152a75de9c7cad87bc4e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 18 Dec 2023 20:21:13 -0500 Subject: [PATCH 156/239] SFT-2212: added "child" to key manager seed exports --- .../modules/flows/export_derived_key_flow.py | 4 ++-- .../Passport/modules/flows/seed_warning_flow.py | 2 +- .../Passport/modules/flows/view_seed_words_flow.py | 14 +++++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py index b3dc94caf..6976987f0 100644 --- a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py @@ -78,7 +78,7 @@ async def show_qr_code(self): from flows import SeedWarningFlow import microns - result = await SeedWarningFlow(action_text="display your {} as a QR code" + result = await SeedWarningFlow(action_text="display your child {} as a QR code" .format(self.key_type['title']), key_manager=True).run() @@ -118,7 +118,7 @@ async def confirm_qr(self): async def save_to_sd(self): from flows import SaveToMicroSDFlow, SeedWarningFlow - result = await SeedWarningFlow(action_text="copy your {} to the microSD card" + result = await SeedWarningFlow(action_text="copy your child {} to the microSD card" .format(self.key_type['title']), key_manager=True).run() diff --git a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py index ce6d359da..9754c2ea0 100644 --- a/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/seed_warning_flow.py @@ -15,7 +15,7 @@ def __init__(self, mention_passphrase=False, allow_skip=True, key_manager=False): self.mention_passphrase = mention_passphrase - self.action_text = action_text + self.action_text = action_text or "display your seed words" self.continue_text = continue_text or "funds" self.info_type_text = info_type_text or "these words" self.allow_skip = allow_skip diff --git a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py index 0a11bf3bb..d99de814f 100644 --- a/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/view_seed_words_flow.py @@ -32,6 +32,7 @@ def __init__(self, self.words = None self.seed_micron = None if not qr_button else microns.ScanQR self.mention_passphrase = True if not external_key else False + self.mention_child = 'child ' if external_key else '' self.initial = initial self.allow_skip = allow_skip self.use_qr_button = qr_button @@ -90,7 +91,9 @@ async def show_qr(self): from pages import ShowQRPage import microns - result = await SeedWarningFlow(action_text="display your seed as a QR code", + action_text = "display your {}seed as a QR code".format(self.mention_child) + + result = await SeedWarningFlow(action_text=action_text, mention_passphrase=self.mention_passphrase, initial=self.initial, allow_skip=self.allow_skip, @@ -127,7 +130,9 @@ async def save_to_sd(self): from utils import B2A from flows import SaveToMicroSDFlow - result = await SeedWarningFlow(action_text="copy your seed to the microSD card", + action_text = "copy your {}seed to the microSD card".format(self.mention_child) + + result = await SeedWarningFlow(action_text=action_text, mention_passphrase=self.mention_passphrase, initial=self.initial, allow_skip=self.allow_skip, @@ -153,11 +158,14 @@ async def show_seed_words(self): from flows import SeedWarningFlow from pages import SeedWordsListPage + action_text = "display your {}seed words".format(self.mention_child) + if not self.seen_warning: # We already gave the seed warning flow result = await SeedWarningFlow(mention_passphrase=self.mention_passphrase, initial=self.initial, allow_skip=self.allow_skip, - key_manager=self.key_manager).run() + key_manager=self.key_manager, + action_text=action_text).run() if not result: self.set_result(False) From e5face5343c221de7f456b7d893bc480886718a3 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 3 Jan 2024 12:35:31 -0500 Subject: [PATCH 157/239] SFT-3116: don't show security words if pin entry failed during setting change --- .../flows/show_security_words_setting_flow.py | 15 ++++++++++++--- .../Passport/modules/pages/pin_entry_page.py | 6 ++---- ports/stm32/boards/Passport/modules/utils.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py b/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py index 20bbf34f0..8f64b5047 100644 --- a/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py @@ -7,6 +7,8 @@ from flows import Flow from pages import PINEntryPage, ShowSecurityWordsSettingPage import microns +from common import settings +from utils import check_pin_prefix_hash class ShowSecurityWordsSettingFlow(Flow): @@ -27,8 +29,15 @@ async def enter_pin(self): check_pin_prefix=True, left_micron=microns.Back, right_micron=microns.Checkmark).show() + if not is_done: + settings.set('security_words', False) self.back() - else: - # TODO: it would be good to check the pin - self.set_result(True) + return + + if not check_pin_prefix_hash(pin): + settings.set('security_words', False) + self.back() + return + + self.set_result(True) diff --git a/ports/stm32/boards/Passport/modules/pages/pin_entry_page.py b/ports/stm32/boards/Passport/modules/pages/pin_entry_page.py index 88df83ad0..d37a6aac7 100644 --- a/ports/stm32/boards/Passport/modules/pages/pin_entry_page.py +++ b/ports/stm32/boards/Passport/modules/pages/pin_entry_page.py @@ -265,15 +265,13 @@ def on_key(self, key, pressed): self.input.set_mode(self.t9.mode) async def on_security_words(self, security_words, error): - from serializations import sha256 + from utils import check_pin_prefix_hash # NOTE: Be aware that this is called from the context of another task # TODO: error not handled if error is None: self.security_words = security_words self.displaying_security_words = True - new_pin_sha = sha256(self.pin) - true_pin_sha = common.settings.get('pin_prefix_hash') - if self.check_pin_prefix and not all(x == y for x, y in zip(new_pin_sha, true_pin_sha)): + if self.check_pin_prefix and not check_pin_prefix_hash(self.pin): self.security_words_message = ("Your PIN is incorrect.\n" "Try again.") self.update_message(show_security_words=False, title="Warning", message=self.security_words_message) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 63c6aff6e..fbf9a4d3c 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1492,4 +1492,15 @@ def get_single_address(xfp, return address +def check_pin_prefix_hash(pin): + from serializations import sha256 + + if len(pin) < 4: + return False + + new_pin_sha = sha256(pin[0:4]) + true_pin_sha = common.settings.get('pin_prefix_hash') + return all(x == y for x, y in zip(new_pin_sha, true_pin_sha)) + + # EOF From dbd84a261770af376fa6a80a172473923d886614 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 3 Jan 2024 12:43:24 -0500 Subject: [PATCH 158/239] SFT-3116: returned user to settings page upon successful pin entry --- .../modules/flows/show_security_words_setting_flow.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py b/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py index 8f64b5047..f1da5d757 100644 --- a/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py @@ -20,7 +20,7 @@ async def select_setting(self): if selected_value: self.goto(self.enter_pin) else: - self.set_result(None) + self.set_result(True) async def enter_pin(self): (pin, is_done) = await PINEntryPage( @@ -32,12 +32,8 @@ async def enter_pin(self): if not is_done: settings.set('security_words', False) - self.back() - return if not check_pin_prefix_hash(pin): settings.set('security_words', False) - self.back() - return - self.set_result(True) + self.back() From 36368748c0e5f9801fff7ba53bb29f76840d7cbd Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 4 Jan 2024 13:50:49 -0500 Subject: [PATCH 159/239] SFT-3126: fixed navigation of predictive text explanation --- .../Passport/modules/flows/restore_seed_flow.py | 9 ++++++++- .../boards/Passport/modules/pages/status_page.py | 15 ++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index 1a0a2b4e6..aca455429 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -85,11 +85,18 @@ async def scan_qr(self): async def explain_input_method(self): from pages import InfoPage + import microns result = await InfoPage([ "Passport uses predictive text input to help you restore your seed words.", - "Example: If you want to enter \"car\", type 2 2 7 and select \"car\" from the dropdown."] + "Example: If you want to enter \"car\", type 2 2 7 and select \"car\" from the dropdown."], + left_micron=microns.Back, ).show() + + if not result: + self.back() + return + self.goto(self.enter_seed_words) async def enter_seed_words(self): diff --git a/ports/stm32/boards/Passport/modules/pages/status_page.py b/ports/stm32/boards/Passport/modules/pages/status_page.py index 21bf24d54..d58ca369b 100644 --- a/ports/stm32/boards/Passport/modules/pages/status_page.py +++ b/ports/stm32/boards/Passport/modules/pages/status_page.py @@ -38,6 +38,8 @@ def __init__(self, text=None, icon=None, icon_color=None, show_progress=False, p self.centered = centered self.show_spinner = show_spinner self.interactive = interactive + self.true_left_micron = left_micron + self.true_right_micron = right_micron self.is_list_mode = isinstance(self.text, list) @@ -65,12 +67,15 @@ def update(self): # Update microns in a special way for array text if self.is_list_mode: - common.ui.set_left_micron(microns.Back) - common.ui.set_right_micron(microns.Forward) + self.left_micron = microns.Back + self.right_micron = microns.Forward if self.page_idx == 0: - common.ui.set_left_micron(self.left_micron) - elif self.page_idx == len(self.text): - common.ui.set_right_micron(self.right_micron) + self.left_micron = self.true_left_micron + elif self.page_idx == len(self.text) - 1: + self.right_micron = self.true_right_micron + + common.ui.set_left_micron(self.left_micron) + common.ui.set_right_micron(self.right_micron) # center container so we can center the icon or progress in it self.center_container = View() From b4f02867bd10b30852b36db07dc4289a1bf7312c Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 4 Jan 2024 15:13:08 -0500 Subject: [PATCH 160/239] SFT-3122: made sure initial backup warning only shows during device setup --- .../Passport/modules/flows/backup_flow.py | 17 +++++++++++------ .../Passport/modules/flows/new_seed_flow.py | 2 +- .../modules/flows/restore_backup_flow.py | 2 +- .../Passport/modules/flows/restore_seed_flow.py | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/backup_flow.py b/ports/stm32/boards/Passport/modules/flows/backup_flow.py index 90cd2f547..be576fade 100644 --- a/ports/stm32/boards/Passport/modules/flows/backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/backup_flow.py @@ -13,13 +13,14 @@ class BackupFlow(Flow): - def __init__(self): + def __init__(self, initial_backup=False): from common import settings super().__init__(initial_state=self.show_intro, name='BackupFlow') self.backup_quiz_passed = settings.get('backup_quiz', False) self.quiz_result = [None] * TOTAL_BACKUP_CODE_DIGITS self.statusbar = {'title': 'BACKUP', 'icon': 'ICON_BACKUP'} + self.initial_backup = initial_backup async def show_intro(self): from pages import InfoPage @@ -43,15 +44,19 @@ async def show_intro(self): text=msgs, left_micron=microns.Back, right_micron=microns.Forward).show() + if result: self.goto(self.get_backup_code) - else: - result = await QuestionPage(text='Skip initial backup?\n\n{}'.format( + return + + if self.initial_backup and not self.backup_quiz_passed: + result2 = await QuestionPage(text='Skip initial backup?\n\n{}'.format( recolor(HIGHLIGHT_TEXT_HEX, '(Not recommended)')), left_micron=microns.Retry).show() - if result: + if result2: self.set_result(False) - else: - return + return # Repeat show_into if user retries + + self.set_result(False) async def get_backup_code(self): from utils import spinner_task diff --git a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py index ccd296f71..00bc10f7b 100644 --- a/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/new_seed_flow.py @@ -82,7 +82,7 @@ async def do_backup(self): backup_flow = None if self.full_backup: - backup_flow = BackupFlow() + backup_flow = BackupFlow(initial_backup=True) elif self.autobackup: backup_flow = AutoBackupFlow(offer=True) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py index e05794c49..d8482f745 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py @@ -133,7 +133,7 @@ async def do_restore(self): if self.full_backup: if error_2 is not None or self.backup_code != new_backup_code: await InfoPage("You will receive a new Backup Code to use with your new Passport.").show() - await BackupFlow().run() + await BackupFlow(initial_backup=True).run() elif self.autobackup: await AutoBackupFlow(offer=True).run() diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index 1a0a2b4e6..ca0ec03f9 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -153,7 +153,7 @@ async def valid_seed(self): await SuccessPage(text='New seed restored and saved.').show() if self.full_backup: - await BackupFlow().run() + await BackupFlow(initial_backup=True).run() elif self.autobackup: await AutoBackupFlow(offer=True).run() From 3d240e3735aee58c8443f26d4b4674bf47303330 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 4 Jan 2024 15:17:24 -0500 Subject: [PATCH 161/239] SFT-3122: show initial backup warning if and only if it's during initial setup --- ports/stm32/boards/Passport/modules/flows/backup_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/backup_flow.py b/ports/stm32/boards/Passport/modules/flows/backup_flow.py index be576fade..b038b65df 100644 --- a/ports/stm32/boards/Passport/modules/flows/backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/backup_flow.py @@ -49,7 +49,7 @@ async def show_intro(self): self.goto(self.get_backup_code) return - if self.initial_backup and not self.backup_quiz_passed: + if self.initial_backup: result2 = await QuestionPage(text='Skip initial backup?\n\n{}'.format( recolor(HIGHLIGHT_TEXT_HEX, '(Not recommended)')), left_micron=microns.Retry).show() if result2: From 8e6cdea285f88027f7a241b29a6fbd98e8c78b59 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 4 Jan 2024 17:43:30 -0500 Subject: [PATCH 162/239] SFT-3120: removed error when backing out of seedqr restore --- .../stm32/boards/Passport/modules/flows/restore_seed_flow.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index 1a0a2b4e6..f59186184 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -53,7 +53,7 @@ async def choose_restore_method(self): async def scan_qr(self): from flows import ScanQRFlow - from pages import InfoPage, SeedWordsListPage, ErrorPage + from pages import InfoPage, SeedWordsListPage import microns from data_codecs.qr_type import QRType @@ -61,8 +61,6 @@ async def scan_qr(self): data_description=self.validate_text).run() if result is None: - await ErrorPage("Invalid {} detected. Make sure you're using the right format." - .format(self.validate_text)).show() self.back() return From 4d010761a00d0bf1e9dd43c6de4af41557104fb3 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 18 Dec 2023 21:09:14 -0500 Subject: [PATCH 163/239] SFT-2887: doubled spacing in address stylization, stylized change addresses --- .../boards/Passport/modules/flows/sign_psbt_common_flow.py | 2 +- ports/stm32/boards/Passport/modules/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py index c14855c38..21fb8a49f 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py @@ -219,7 +219,7 @@ def render_change_text(self): continue # print('idx: {} output:{}'.format(idx, self.chain.render_address(tx_out.scriptPubKey))) total += tx_out.nValue - addrs.append(self.chain.render_address(tx_out.scriptPubKey)) + addrs.append(stylize_address(self.chain.render_address(tx_out.scriptPubKey))) if len(addrs) == 0: msg.write('\nNo change') diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index fbf9a4d3c..083d8ad3c 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1460,7 +1460,7 @@ def stylize_address(address): if i % 16 == 0: stylized += '\n' else: - stylized += ' ' + stylized += ' ' block += address[i] stylized += recolor(colors[color_index], block) From 8f8b0c2bf698c7bc91ef247d4db69568eb9b2fb9 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 21 Dec 2023 13:21:28 -0500 Subject: [PATCH 164/239] SFT-2887: reduced margins in pages that display addresses to 0 --- .../Passport/modules/flows/address_explorer_flow.py | 4 +++- .../Passport/modules/flows/export_derived_key_flow.py | 4 +++- .../Passport/modules/flows/sign_psbt_common_flow.py | 8 ++++++-- .../boards/Passport/modules/flows/verify_address_flow.py | 3 ++- .../Passport/modules/pages/address_explorer_page.py | 6 ++++-- .../boards/Passport/modules/pages/long_success_page.py | 6 ++++-- .../stm32/boards/Passport/modules/pages/long_text_page.py | 6 ++++-- ports/stm32/boards/Passport/modules/pages/status_page.py | 6 ++++-- ports/stm32/boards/Passport/modules/pages/success_page.py | 6 ++++-- ports/stm32/boards/Passport/modules/public_constants.py | 2 ++ 10 files changed, 36 insertions(+), 15 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index a7719f5e4..f775807e4 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -136,6 +136,7 @@ async def explore(self): from utils import stylize_address, get_single_address from pages import ShowQRPage, AddressExplorerPage import microns + from public_constants import MARGIN_FOR_ADDRESSES try: self.address = get_single_address(self.xfp, @@ -162,7 +163,8 @@ async def explore(self): while True: result = await AddressExplorerPage(msg, left_micron=microns.Cancel, - right_micron=microns.ScanQR).show() + right_micron=microns.ScanQR, + margins=MARGIN_FOR_ADDRESSES).show() if not result: self.set_result(True) diff --git a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py index 6976987f0..a47ede1fc 100644 --- a/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/export_derived_key_flow.py @@ -97,6 +97,7 @@ async def confirm_qr(self): from pages import InfoPage, LongTextPage import microns from utils import stylize_address + from public_constants import MARGIN_FOR_ADDRESSES text = 'Confirm the exported {} on the following page'.format(self.key_type['title']) result = await InfoPage(text=text, left_micron=microns.Back).show() @@ -108,7 +109,8 @@ async def confirm_qr(self): result = await LongTextPage(text="\n" + stylize_address(self.data), centered=True, card_header={'title': 'Confirm Key'}, - left_micron=microns.Back).show() + left_micron=microns.Back, + margins=MARGIN_FOR_ADDRESSES).show() if not result: return diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py index 21fb8a49f..612bb1d69 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py @@ -57,6 +57,7 @@ async def check_multisig_import(self): async def show_transaction_details(self): import uio from pages import LongTextPage, ErrorPage + from public_constants import MARGIN_FOR_ADDRESSES try: outputs = uio.StringIO() @@ -88,7 +89,8 @@ async def show_transaction_details(self): result = await LongTextPage( text=outputs.getvalue(), centered=True, - card_header={'title': self.header} + card_header={'title': self.header}, + margins=MARGIN_FOR_ADDRESSES, ).show() if result: if self.psbt.self_send: @@ -108,13 +110,15 @@ async def show_transaction_details(self): async def show_change(self): from pages import LongTextPage, ErrorPage + from public_constants import MARGIN_FOR_ADDRESSES try: msg = self.render_change_text() result = await LongTextPage( text=msg, centered=True, - card_header={'title': self.header} + card_header={'title': self.header}, + margins=MARGIN_FOR_ADDRESSES ).show() gc.collect() if not result: diff --git a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py index 767500015..bbc281e5f 100644 --- a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py @@ -231,6 +231,7 @@ async def found(self): import passport from common import settings import chains + from public_constants import MARGIN_FOR_ADDRESSES # Remember where to start from next time save_next_addr(self.acct_num, @@ -249,5 +250,5 @@ async def found(self): self.found_addr_idx) page_class = SuccessPage if passport.IS_COLOR else LongSuccessPage - await page_class(msg).show() + await page_class(text=msg, margins=MARGIN_FOR_ADDRESSES).show() self.set_result(True) diff --git a/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py b/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py index 189878d63..4ab9cb516 100644 --- a/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py +++ b/ports/stm32/boards/Passport/modules/pages/address_explorer_page.py @@ -17,13 +17,15 @@ def __init__(self, card_header=None, statusbar=None, left_micron=microns.Back, - right_micron=microns.Checkmark): + right_micron=microns.Checkmark, + margins=None): super().__init__( text=text, card_header=card_header, statusbar=statusbar, right_micron=right_micron, - left_micron=left_micron) + left_micron=left_micron, + margins=margins) def attach(self, group): super().attach(group) diff --git a/ports/stm32/boards/Passport/modules/pages/long_success_page.py b/ports/stm32/boards/Passport/modules/pages/long_success_page.py index 9e30c7623..d2e3016cb 100644 --- a/ports/stm32/boards/Passport/modules/pages/long_success_page.py +++ b/ports/stm32/boards/Passport/modules/pages/long_success_page.py @@ -16,7 +16,8 @@ def __init__( card_header=None, statusbar=None, left_micron=None, - right_micron=microns.Checkmark): + right_micron=microns.Checkmark, + margins=None): super().__init__( text=text, card_header=card_header, @@ -25,4 +26,5 @@ def __init__( icon_color=DEFAULT_LARGE_ICON_COLOR, centered=True, left_micron=left_micron, - right_micron=right_micron) + right_micron=right_micron, + margins=margins) diff --git a/ports/stm32/boards/Passport/modules/pages/long_text_page.py b/ports/stm32/boards/Passport/modules/pages/long_text_page.py index 02f46e3e5..052d349b9 100644 --- a/ports/stm32/boards/Passport/modules/pages/long_text_page.py +++ b/ports/stm32/boards/Passport/modules/pages/long_text_page.py @@ -21,7 +21,8 @@ def __init__(self, card_header=None, statusbar=None, left_micron=microns.Back, - right_micron=microns.Forward): + right_micron=microns.Forward, + margins=None): super().__init__( card_header=card_header, statusbar=statusbar, @@ -32,6 +33,7 @@ def __init__(self, self.icon = icon self.icon_color = icon_color self.centered = centered + self.margins = (margins if margins is not None else 12) # Set non-style props self.set_width(lv.pct(100)) @@ -53,7 +55,7 @@ def __init__(self, self.container.set_width(lv.pct(100)) with Stylize(self.container) as default: default.flex_fill() - default.pad(left=12, right=12) + default.pad(left=self.margins, right=self.margins) with Stylize(self.container, selector=lv.PART.SCROLLBAR) as scrollbar: if not passport.IS_COLOR: diff --git a/ports/stm32/boards/Passport/modules/pages/status_page.py b/ports/stm32/boards/Passport/modules/pages/status_page.py index d58ca369b..d8cf685a0 100644 --- a/ports/stm32/boards/Passport/modules/pages/status_page.py +++ b/ports/stm32/boards/Passport/modules/pages/status_page.py @@ -18,7 +18,8 @@ class StatusPage(Page): def __init__(self, text=None, icon=None, icon_color=None, show_progress=False, percent=0, centered=True, show_spinner=False, interactive=True, card_header=None, - statusbar=None, left_micron=microns.Back, right_micron=microns.Forward): + statusbar=None, left_micron=microns.Back, right_micron=microns.Forward, + margins=None): super().__init__(card_header=card_header, statusbar=statusbar, left_micron=left_micron, @@ -40,6 +41,7 @@ def __init__(self, text=None, icon=None, icon_color=None, show_progress=False, p self.interactive = interactive self.true_left_micron = left_micron self.true_right_micron = right_micron + self.margins = (margins if margins is not None else 8) self.is_list_mode = isinstance(self.text, list) @@ -48,7 +50,7 @@ def __init__(self, text=None, icon=None, icon_color=None, show_progress=False, p self.container = View(flex_flow=lv.FLEX_FLOW.COLUMN) self.container.set_size(lv.pct(100), lv.pct(100)) with Stylize(self.container) as default: - default.pad(left=8, right=8) + default.pad(left=self.margins, right=self.margins) default.pad_row(20) default.flex_align(main=lv.FLEX_ALIGN.CENTER, cross=lv.FLEX_ALIGN.CENTER, track=lv.FLEX_ALIGN.CENTER) self.set_children([self.container]) diff --git a/ports/stm32/boards/Passport/modules/pages/success_page.py b/ports/stm32/boards/Passport/modules/pages/success_page.py index 8d29c7621..0474ea150 100644 --- a/ports/stm32/boards/Passport/modules/pages/success_page.py +++ b/ports/stm32/boards/Passport/modules/pages/success_page.py @@ -16,7 +16,8 @@ def __init__( card_header=None, statusbar=None, left_micron=None, - right_micron=microns.Checkmark): + right_micron=microns.Checkmark, + margins=None): super().__init__( text=text, card_header=card_header, @@ -24,4 +25,5 @@ def __init__( icon=lv.LARGE_ICON_SUCCESS, icon_color=DEFAULT_LARGE_ICON_COLOR, left_micron=left_micron, - right_micron=right_micron) + right_micron=right_micron, + margins=margins) diff --git a/ports/stm32/boards/Passport/modules/public_constants.py b/ports/stm32/boards/Passport/modules/public_constants.py index 43ecca28a..1d9e0e8b2 100644 --- a/ports/stm32/boards/Passport/modules/public_constants.py +++ b/ports/stm32/boards/Passport/modules/public_constants.py @@ -108,6 +108,8 @@ DIR_TRANSACTIONS = 'transactions' DIR_HEALTH_CHECKS = 'health_checks' +MARGIN_FOR_ADDRESSES = 0 + RFC_SIGNATURE_TEMPLATE = '''\ -----BEGIN {blockchain} SIGNED MESSAGE----- {msg} From 2acb449bc82725ed161addec10c22799bcbb7d0b Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 22 Dec 2023 11:50:49 -0500 Subject: [PATCH 165/239] SFT-2887: reduced spacing when too many 'w's and 'm's are present --- ports/stm32/boards/Passport/modules/utils.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 083d8ad3c..300783cb0 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1445,6 +1445,16 @@ def escape_text(text): def stylize_address(address): from styles.colors import TEXT_GREY_HEX, BLACK_HEX + num_wm = 0 + + for i in range(len(address)): + if address[i] == 'm' or address[i] == 'n': + num_wm += 1 + + spacer = ' ' + if num_wm > 5: + spacer = ' ' + stylized = '' colors = [BLACK_HEX, TEXT_GREY_HEX] color_index = 0 @@ -1460,7 +1470,7 @@ def stylize_address(address): if i % 16 == 0: stylized += '\n' else: - stylized += ' ' + stylized += spacer block += address[i] stylized += recolor(colors[color_index], block) From 913a0c82522e728c63bbc55bed478ed8b030d6cb Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 20 Dec 2023 03:12:48 -0500 Subject: [PATCH 166/239] SFT-927: first steps on last word generation --- .../pages/predictive_text_input_page.py | 4 +- .../Passport/modules/predictive_utils.py | 40 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py b/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py index 30c363d91..5a9353918 100644 --- a/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py +++ b/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py @@ -12,7 +12,7 @@ from styles import Stylize from keys import KEY_0, KEY_9 import microns -from predictive_utils import get_words_matching_prefix, word_to_keypad_numbers +from predictive_utils import get_words_matching_prefix, word_to_keypad_numbers, get_last_word import passport @@ -94,6 +94,8 @@ def update_predictions(self): # print('Lookup words for {}'.format(prefix)) set_list(self.prefixes, self.word_idx, prefix) self.predictions = get_words_matching_prefix(prefix, max=5, word_list=self.word_list) + elif self.word_idx == self.total_words - 1: + self.predictions = [get_last_word(self.selected_words)] else: self.predictions = [] diff --git a/ports/stm32/boards/Passport/modules/predictive_utils.py b/ports/stm32/boards/Passport/modules/predictive_utils.py index 80e5a31e2..34a23a10a 100644 --- a/ports/stm32/boards/Passport/modules/predictive_utils.py +++ b/ports/stm32/boards/Passport/modules/predictive_utils.py @@ -58,3 +58,43 @@ def get_words_matching_prefix(prefix, max=5, word_list='bip39'): if len(words) == 1 and words[0] == '': words = [] return words + + +def get_last_word(seed_words, word_list='bip39'): + from utils import get_width_from_num_words + import common + from trezorcrypto import bip39 + import uio + + binary_str = uio.StringIO() + + for word in word_list: + index = bip39.find_word(word) + index_bin = '{:011b}'.format(index) + binary_str.write(index_bin) + + # Determine how much more entropy is needed + total_bits = get_width_from_num_words(len(word_list) + 1) * 8 + entropy_needed = total_bits - len(binary_str.getvalue()) + + extra_entropy = bytearray(1) + common.noise.random_bytes(extra_entropy, common.noise.ALL) + entropy_bin = '{:08b}'.format(extra_entropy[0])[:entropy_needed] + print("len(word_list): {}, total_bits: {}, len(binary_str): {}".format(len(word_list), + total_bits, + len(binary_str.getvalue()))) + print("entropy_needed: {}, extra_entropy: {}, entropy_bin: {}".format(entropy_needed, + extra_entropy[0], + entropy_bin)) + + binary_str.write(entropy_bin) + + binary_str = binary_str.getvalue() + + width = get_width_from_num_words(len(word_list) + 1) + as_bytes = bytearray(width) + for i in range(0, width): + as_bytes[i] = int('0b' + binary_str[i * 8:(i + 1) * 8], 2) + + # TODO: get checksum bits + return 'abandon' From 9f417af40c46d50f124fce73f83a4a261de77c4f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 3 Jan 2024 18:03:21 -0500 Subject: [PATCH 167/239] SFT-927: suggested last word working --- .../modules/flows/restore_seed_flow.py | 15 +----- .../Passport/modules/predictive_utils.py | 51 ++++++++----------- .../Passport/modules/public_constants.py | 1 + ports/stm32/boards/Passport/modules/utils.py | 18 +++++++ 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index a1bf794f5..c301b86e9 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -137,21 +137,10 @@ async def invalid_seed(self): self.goto(self.enter_seed_words) async def valid_seed(self): - from foundation import bip39 from flows import AutoBackupFlow, BackupFlow + from utils import get_seed_from_words - entropy = bytearray(33) # Includes an extra byte for the checksum bits - - len = bip39.mnemonic_to_bits(self.mnemonic, entropy) - - if len == 264: # 24 words x 11 bits each - trim_pos = 32 - elif len == 198: # 18 words x 11 bits each - trim_pos = 24 - elif len == 132: # 12 words x 11 bits each - trim_pos = 16 - entropy = entropy[:trim_pos] # Trim off the excess (including checksum bits) - + entropy = get_seed_from_words(self.mnemonic) (error,) = await spinner_task('Saving seed', save_seed_task, args=[entropy]) if error is None: import common diff --git a/ports/stm32/boards/Passport/modules/predictive_utils.py b/ports/stm32/boards/Passport/modules/predictive_utils.py index 34a23a10a..7890bbfb3 100644 --- a/ports/stm32/boards/Passport/modules/predictive_utils.py +++ b/ports/stm32/boards/Passport/modules/predictive_utils.py @@ -61,40 +61,31 @@ def get_words_matching_prefix(prefix, max=5, word_list='bip39'): def get_last_word(seed_words, word_list='bip39'): - from utils import get_width_from_num_words + import trezorcrypto import common - from trezorcrypto import bip39 - import uio - - binary_str = uio.StringIO() - - for word in word_list: - index = bip39.find_word(word) - index_bin = '{:011b}'.format(index) - binary_str.write(index_bin) + from foundation import bip39 + from utils import get_seed_from_words, get_words_from_seed + from public_constants import SEED_WORD_LIST_LENGTH - # Determine how much more entropy is needed - total_bits = get_width_from_num_words(len(word_list) + 1) * 8 - entropy_needed = total_bits - len(binary_str.getvalue()) + # The last word is partially entropy, partially checksum, + # in different proportions based on the mnemonic length, - extra_entropy = bytearray(1) - common.noise.random_bytes(extra_entropy, common.noise.ALL) - entropy_bin = '{:08b}'.format(extra_entropy[0])[:entropy_needed] - print("len(word_list): {}, total_bits: {}, len(binary_str): {}".format(len(word_list), - total_bits, - len(binary_str.getvalue()))) - print("entropy_needed: {}, extra_entropy: {}, entropy_bin: {}".format(entropy_needed, - extra_entropy[0], - entropy_bin)) + # choose a random last word + index_bytes = bytearray(2) # 16 bit int + common.noise.random_bytes(index_bytes, common.noise.ALL) + index = int.from_bytes(index_bytes, "little") % SEED_WORD_LIST_LENGTH + mock_last_word = trezorcrypto.bip39.get_word(index) + copy_words = ' '.join(seed_words) # Later function requires joined string + copy_words += ' ' + mock_last_word - binary_str.write(entropy_bin) + # compute the bits without the checksum + entropy = get_seed_from_words(copy_words) - binary_str = binary_str.getvalue() + # get the correct words with the checkusm + (final_words, error) = get_words_from_seed(entropy) - width = get_width_from_num_words(len(word_list) + 1) - as_bytes = bytearray(width) - for i in range(0, width): - as_bytes[i] = int('0b' + binary_str[i * 8:(i + 1) * 8], 2) + if not final_words: + return "" - # TODO: get checksum bits - return 'abandon' + # return the correct final word + return final_words[-1] diff --git a/ports/stm32/boards/Passport/modules/public_constants.py b/ports/stm32/boards/Passport/modules/public_constants.py index 1d9e0e8b2..cbffe0ada 100644 --- a/ports/stm32/boards/Passport/modules/public_constants.py +++ b/ports/stm32/boards/Passport/modules/public_constants.py @@ -15,6 +15,7 @@ # Seed phrase lengths accepted SEED_LENGTHS = [12, 24] +SEED_WORD_LIST_LENGTH = const(2048) # Max PSBT txn we support (896k as PSBT) # - the max on the wire for mainnet is 100k diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 300783cb0..e27aecf28 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1338,6 +1338,24 @@ def get_words_from_seed(seed): return (None, '{}'.format(e)) +# Requires words to be a single string, space-separated +def get_seed_from_words(words): + from foundation import bip39 + + entropy = bytearray(33) # Includes an extra byte for the checksum bits + length = bip39.mnemonic_to_bits(words, entropy) + + trim_pos = 0 + if length == 264: # 24 words x 11 bits each + trim_pos = 32 + elif length == 198: # 18 words x 11 bits each + trim_pos = 24 + elif length == 132: # 12 words x 11 bits each + trim_pos = 16 + entropy = entropy[:trim_pos] # Trim off the excess (including checksum bits) + return entropy + + def nostr_pubkey_from_pk(pk): from trezorcrypto import secp256k1 return secp256k1.publickey(pk, True)[1:] From dc5a01d98f382d59812cb884f9be27c27f67d9c0 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 5 Jan 2024 15:30:13 -0500 Subject: [PATCH 168/239] SFT-927: resimplified final word generation --- ports/stm32/boards/Passport/manifest.py | 1 + .../boards/Passport/modules/flows/__init__.py | 1 + .../modules/flows/initial_seed_setup_flow.py | 4 +- .../modules/flows/random_final_word_flow.py | 45 +++++++++++ .../modules/flows/restore_seed_flow.py | 29 ++++++-- .../pages/predictive_text_input_page.py | 74 +++++++++++-------- 6 files changed, 114 insertions(+), 40 deletions(-) create mode 100644 ports/stm32/boards/Passport/modules/flows/random_final_word_flow.py diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index 201ce39b8..b6f285f58 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -129,6 +129,7 @@ 'flows/new_seed_flow.py', 'flows/connect_wallet_flow.py', 'flows/page_flow.py', + 'flows/random_final_word_flow.py', 'flows/read_file_flow.py', 'flows/remove_dev_pubkey_flow.py', 'flows/rename_account_flow.py', diff --git a/ports/stm32/boards/Passport/modules/flows/__init__.py b/ports/stm32/boards/Passport/modules/flows/__init__.py index 25b00176b..03049a70e 100644 --- a/ports/stm32/boards/Passport/modules/flows/__init__.py +++ b/ports/stm32/boards/Passport/modules/flows/__init__.py @@ -52,6 +52,7 @@ from .new_derived_key_flow import * from .new_seed_flow import * from .connect_wallet_flow import * +from .random_final_word_flow import * from .read_file_flow import * from .remove_dev_pubkey_flow import * from .rename_account_flow import * diff --git a/ports/stm32/boards/Passport/modules/flows/initial_seed_setup_flow.py b/ports/stm32/boards/Passport/modules/flows/initial_seed_setup_flow.py index 9948b6e05..5c9838680 100644 --- a/ports/stm32/boards/Passport/modules/flows/initial_seed_setup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/initial_seed_setup_flow.py @@ -30,7 +30,7 @@ async def show_intro(self): result = await InfoPage( icon=lv.LARGE_ICON_SEED, - text='Next, let\'s create or restore a wallet seed.', + text='Next, let\'s create or import a wallet seed.', left_micron=left_micron, right_micron=microns.Forward).show() if result: self.goto(self.show_seed_setup_menu) @@ -44,7 +44,7 @@ async def show_seed_setup_menu(self): options = [{'label': 'Create New Seed', 'value': lambda: NewSeedFlow(full_backup=True)}, - {'label': 'Restore Seed', 'value': lambda: RestoreSeedFlow(full_backup=True)}, + {'label': 'Import Seed', 'value': lambda: RestoreSeedFlow(full_backup=True)}, {'label': 'Restore Backup', 'value': lambda: RestoreBackupFlow(full_backup=True)}] flow = await ChooserPage( diff --git a/ports/stm32/boards/Passport/modules/flows/random_final_word_flow.py b/ports/stm32/boards/Passport/modules/flows/random_final_word_flow.py new file mode 100644 index 000000000..2fa6c5685 --- /dev/null +++ b/ports/stm32/boards/Passport/modules/flows/random_final_word_flow.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# +# random_final_word_flow.py - Flow class to track position in a menu + +from flows import Flow + + +class RandomFinalWordFlow(Flow): + def __init__(self, selected_words): + self.selected_words = selected_words + super().__init__(initial_state=self.explainer, name='RandomFinalWordFlow') + + async def explainer(self): + from pages import InfoPage + import microns + + result = await InfoPage('Passport will generate a random final word to complete your seed', + left_micron=microns.Back, + right_micron=microns.Forward).show() + + if not result: + self.set_result(None) + return + + self.goto(self.generate_word) + + async def generate_word(self): + from predictive_utils import get_last_word + from pages import InfoPage + import microns + from utils import recolor + from styles.colors import HIGHLIGHT_TEXT_HEX + + last_word = get_last_word(self.selected_words) + styled_last_word = recolor(HIGHLIGHT_TEXT_HEX, last_word) + text = 'Your final word is\n{}.\n\nImport and save this seed?'.format(styled_last_word) + result = await InfoPage(text=text, + left_micron=microns.Retry).show() + + if not result: + self.back() + return + + self.set_result(last_word) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index c301b86e9..ae063db0c 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -4,7 +4,7 @@ # restore_seed_flow.py -Restore a seed to Passport by entering the seed words. -from flows import Flow +from flows import Flow, RandomFinalWordFlow import microns from pages import ErrorPage, PredictiveTextInputPage, SuccessPage, QuestionPage from utils import spinner_task @@ -19,10 +19,11 @@ def __init__(self, refresh_cards_when_done=False, autobackup=True, full_backup=F self.seed_format = None self.seed_length = None self.validate_text = None + self.index = None self.seed_words = [] self.full_backup = full_backup self.autobackup = autobackup - self.statusbar = {'title': 'RESTORE SEED', 'icon': 'ICON_SEED'} + self.statusbar = {'title': 'IMPORT SEED', 'icon': 'ICON_SEED'} async def choose_restore_method(self): from pages import ChooserPage @@ -86,7 +87,7 @@ async def explain_input_method(self): import microns result = await InfoPage([ - "Passport uses predictive text input to help you restore your seed words.", + "Passport uses predictive text input to help you import your seed words.", "Example: If you want to enter \"car\", type 2 2 7 and select \"car\" from the dropdown."], left_micron=microns.Back, ).show() @@ -101,16 +102,28 @@ async def enter_seed_words(self): result = await PredictiveTextInputPage( word_list='bip39', total_words=self.seed_length, - initial_words=self.seed_words).show() + initial_words=self.seed_words, + start_index=self.index).show() + if result is None: cancel = await QuestionPage( text='Cancel seed entry? All progress will be lost.').show() if cancel: self.set_result(False) return - else: - self.seed_words, self.prefixes = result - self.goto(self.validate_seed_words) + + self.seed_words, self.prefixes, get_last_word = result + + if get_last_word: + last_word = await RandomFinalWordFlow(self.seed_words).run() + + if not last_word: + self.index = self.seed_length - 1 + return # Go back to input a last word + + self.seed_words.append(last_word) + + self.goto(self.validate_seed_words) async def validate_seed_words(self): from trezorcrypto import bip39 @@ -145,7 +158,7 @@ async def valid_seed(self): if error is None: import common - await SuccessPage(text='New seed restored and saved.').show() + await SuccessPage(text='New seed imported and saved.').show() if self.full_backup: await BackupFlow(initial_backup=True).run() elif self.autobackup: diff --git a/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py b/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py index 5a9353918..e37aa3fda 100644 --- a/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py +++ b/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py @@ -12,9 +12,11 @@ from styles import Stylize from keys import KEY_0, KEY_9 import microns -from predictive_utils import get_words_matching_prefix, word_to_keypad_numbers, get_last_word +from predictive_utils import get_words_matching_prefix, word_to_keypad_numbers import passport +RANDOM_WORD_STRING = "Generate Final Word" + class PredictiveTextInputPage(Page): ''' @@ -30,7 +32,8 @@ def __init__(self, initial_words=[], initial_prefixes=[], left_micron=microns.Back, - right_micron=microns.Forward): + right_micron=microns.Forward, + start_index=None): super().__init__(card_header=card_header, statusbar=statusbar, right_micron=right_micron, @@ -40,7 +43,7 @@ def __init__(self, self.title = title self.word_list = word_list self.total_words = total_words - self.word_idx = 0 + self.word_idx = start_index if (start_index is not None and start_index < self.total_words) else 0 self.prediction_idx = 0 self.selected_words = initial_words self.prefixes = initial_prefixes @@ -56,7 +59,7 @@ def __init__(self, default.text_align(lv.TEXT_ALIGN.CENTER) self.add_child(self.title_view) - self.initial_prefix = self.prefixes[self.word_idx] if len(self.prefixes) > 0 else '' + self.initial_prefix = self.prefixes[self.word_idx] if len(self.prefixes) > self.word_idx else '' self.input = TextInput(text=self.initial_prefix, input_mode=InputMode.NUMERIC) self.input.set_width(lv.pct(100)) @@ -95,7 +98,7 @@ def update_predictions(self): set_list(self.prefixes, self.word_idx, prefix) self.predictions = get_words_matching_prefix(prefix, max=5, word_list=self.word_list) elif self.word_idx == self.total_words - 1: - self.predictions = [get_last_word(self.selected_words)] + self.predictions = [RANDOM_WORD_STRING] else: self.predictions = [] @@ -150,31 +153,42 @@ def get_title(self): return self.title.format(word_idx=self.word_idx + 1, total_words=self.total_words) def right_action(self, is_pressed): - if not is_pressed: - # TODO: Get the index of the selected word (see how menu_page does it) - # Then add to the list of words - if len(self.predictions) > 0: - set_list(self.selected_words, self.word_idx, self.predictions[self.prediction_idx]) - - # Fill out the prefix to the full length of the selected word so the prediction - # list has a single item in it. - set_list(self.prefixes, self.word_idx, str( - word_to_keypad_numbers(self.predictions[self.prediction_idx]))) - # print('selected_words={}'.format(self.selected_words)) - if len(self.selected_words) == self.total_words and self.word_idx == self.total_words - 1: - # print('Returning selected_words and prefixes!') - self.set_result((self.selected_words, self.prefixes)) - return - else: - # Reset (or lookup the prediction prefix and go to the next word index - self.word_idx += 1 - prefix_text = '' - if self.word_idx < len(self.prefixes): - prefix_text = self.prefixes[self.word_idx] - self.input.set_text(prefix_text) - - self.update_title() - self.update_predictions() + if is_pressed: + return + + # TODO: Get the index of the selected word (see how menu_page does it) + # Then add to the list of words + if len(self.predictions) <= 0: + return + + # "Random Final Word" was selected + if len(self.predictions) == 1 and self.predictions[0] == RANDOM_WORD_STRING: + # Last value is "get random last word" + self.set_result((self.selected_words, self.prefixes, True)) + return + + set_list(self.selected_words, self.word_idx, self.predictions[self.prediction_idx]) + + # Fill out the prefix to the full length of the selected word so the prediction + # list has a single item in it. + set_list(self.prefixes, self.word_idx, str( + word_to_keypad_numbers(self.predictions[self.prediction_idx]))) + # print('selected_words={}'.format(self.selected_words)) + if len(self.selected_words) == self.total_words and self.word_idx == self.total_words - 1: + # print('Returning selected_words and prefixes!') + # Last value is "get random last word" + self.set_result((self.selected_words, self.prefixes, False)) + return + else: + # Reset (or lookup the prediction prefix and go to the next word index + self.word_idx += 1 + prefix_text = '' + if self.word_idx < len(self.prefixes): + prefix_text = self.prefixes[self.word_idx] + self.input.set_text(prefix_text) + + self.update_title() + self.update_predictions() def left_action(self, is_pressed): if not is_pressed: From 96bfdd592e2a2293969369a1ac7387ae398e6426 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 5 Jan 2024 15:48:42 -0500 Subject: [PATCH 169/239] SFT-927: fixed backing out from predictive text page --- .../boards/Passport/modules/flows/restore_seed_flow.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index ae063db0c..c4217e171 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -19,7 +19,7 @@ def __init__(self, refresh_cards_when_done=False, autobackup=True, full_backup=F self.seed_format = None self.seed_length = None self.validate_text = None - self.index = None + self.index = 0 self.seed_words = [] self.full_backup = full_backup self.autobackup = autobackup @@ -108,10 +108,14 @@ async def enter_seed_words(self): if result is None: cancel = await QuestionPage( text='Cancel seed entry? All progress will be lost.').show() + if cancel: self.set_result(False) return + self.index = 0 + return + self.seed_words, self.prefixes, get_last_word = result if get_last_word: From a70b2577480727d05845c27486e4f7b312fe70d4 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 5 Jan 2024 16:54:11 -0500 Subject: [PATCH 170/239] SFT-927: fixed rng on physical device --- ports/stm32/boards/Passport/modules/predictive_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/predictive_utils.py b/ports/stm32/boards/Passport/modules/predictive_utils.py index 7890bbfb3..059204f94 100644 --- a/ports/stm32/boards/Passport/modules/predictive_utils.py +++ b/ports/stm32/boards/Passport/modules/predictive_utils.py @@ -71,7 +71,7 @@ def get_last_word(seed_words, word_list='bip39'): # in different proportions based on the mnemonic length, # choose a random last word - index_bytes = bytearray(2) # 16 bit int + index_bytes = bytearray(4) # noise.random_bytes requires a 4 byte buffer common.noise.random_bytes(index_bytes, common.noise.ALL) index = int.from_bytes(index_bytes, "little") % SEED_WORD_LIST_LENGTH mock_last_word = trezorcrypto.bip39.get_word(index) From 71334c4b2bdf17fb2c58a32f5150eebccf3a7bb5 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 5 Jan 2024 17:23:41 -0500 Subject: [PATCH 171/239] SFT-927: removed failed last words --- .../Passport/modules/pages/predictive_text_input_page.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py b/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py index e37aa3fda..91ccbbe5f 100644 --- a/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py +++ b/ports/stm32/boards/Passport/modules/pages/predictive_text_input_page.py @@ -164,6 +164,11 @@ def right_action(self, is_pressed): # "Random Final Word" was selected if len(self.predictions) == 1 and self.predictions[0] == RANDOM_WORD_STRING: # Last value is "get random last word" + + # If we've previously failed importing a seed, de-select the last word + if len(self.selected_words) == self.total_words: + self.selected_words = self.selected_words[:-1] + self.set_result((self.selected_words, self.prefixes, True)) return From e435e0e85989c998c4150c9bdb9300aab246431b Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 6 Jan 2024 11:49:59 -0500 Subject: [PATCH 172/239] SFT-3139: OP_RETURN signing first pass --- ports/stm32/boards/Passport/modules/chains.py | 9 +++++++++ ports/stm32/boards/Passport/modules/utils.py | 3 +++ 2 files changed, 12 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index ce618bc24..8592dde06 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -209,6 +209,15 @@ def render_address(cls, script): if ll == 34 and script[0:2] == b'\x51\x20': return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, script[2:], tcc.codecs.BECH32_ENCODING_BECH32M) + # OP_RETURN, 3 bytes of metadata + print("script: {}".format(script)) + print("op_return length: {}".format(script[2])) + print("script length: {}".format(ll)) + if script[0:2] == b'\x6a\x4c' and script[2] == ll - 3: + message = script[3:].decode() + print("message: {}".format(message)) + return 'OP_RETURN:\n{}'.format(message) + raise ValueError('Unknown payment script', repr(script)) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 300783cb0..b355c56b0 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1445,6 +1445,9 @@ def escape_text(text): def stylize_address(address): from styles.colors import TEXT_GREY_HEX, BLACK_HEX + if address.startswith("OP_RETURN"): + return address + num_wm = 0 for i in range(len(address)): From 007c1d9acc6de26446db20ed5d8fb561f475fd04 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 6 Jan 2024 18:27:58 -0500 Subject: [PATCH 173/239] SFT-3139: improved OP_RETURN message display in transaction review --- .../boards/Passport/modules/flows/sign_psbt_common_flow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py index 612bb1d69..dc74b1835 100644 --- a/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/sign_psbt_common_flow.py @@ -198,6 +198,11 @@ def render_output(self, o): val = ' '.join(self.chain.render_value(o.nValue)) dest = self.chain.render_address(o.scriptPubKey) + if dest.startswith("OP_RETURN"): + return '\n{}\n{}'.format( + recolor(HIGHLIGHT_TEXT_HEX, 'Message'), + dest.split('\n', 1)[1]) # user-defined message starts after "OP_RETURN:\n" + dest = stylize_address(dest) return '\n{}\n{}\n\n{}\n{}'.format( From 72212303485afd8d4a82316e3227303357150785 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 6 Jan 2024 18:54:35 -0500 Subject: [PATCH 174/239] SFT-3139: removed prints --- ports/stm32/boards/Passport/modules/chains.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 8592dde06..595c2f52b 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -210,12 +210,8 @@ def render_address(cls, script): return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, script[2:], tcc.codecs.BECH32_ENCODING_BECH32M) # OP_RETURN, 3 bytes of metadata - print("script: {}".format(script)) - print("op_return length: {}".format(script[2])) - print("script length: {}".format(ll)) if script[0:2] == b'\x6a\x4c' and script[2] == ll - 3: message = script[3:].decode() - print("message: {}".format(message)) return 'OP_RETURN:\n{}'.format(message) raise ValueError('Unknown payment script', repr(script)) From 91600b5b6ebbf1febdbbf929103a2dea6d4bc9ea Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 8 Jan 2024 11:46:49 -0500 Subject: [PATCH 175/239] SFT-927: first pass of insufficient randomness warning --- .../Passport/modules/flows/restore_seed_flow.py | 11 ++++++++++- ports/stm32/boards/Passport/modules/utils.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index c4217e171..9444e9214 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -7,7 +7,7 @@ from flows import Flow, RandomFinalWordFlow import microns from pages import ErrorPage, PredictiveTextInputPage, SuccessPage, QuestionPage -from utils import spinner_task +from utils import spinner_task, insufficient_randomness from tasks import save_seed_task from public_constants import SEED_LENGTHS @@ -119,6 +119,7 @@ async def enter_seed_words(self): self.seed_words, self.prefixes, get_last_word = result if get_last_word: + last_word = await RandomFinalWordFlow(self.seed_words).run() if not last_word: @@ -127,6 +128,14 @@ async def enter_seed_words(self): self.seed_words.append(last_word) + if insufficient_randomness(self.seed_words): + result2 = await ErrorPage("Seeds with low randomness can be vulnerable to draining attacks.\n\nContinue?", + left_micron=microns.Cancel).show() + + if not result2: + self.index = 0 + return # Restart seed input + self.goto(self.validate_seed_words) async def validate_seed_words(self): diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index e27aecf28..3b8f5ee10 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1531,4 +1531,14 @@ def check_pin_prefix_hash(pin): return all(x == y for x, y in zip(new_pin_sha, true_pin_sha)) +def insufficient_randomness(seed_words): + word_frequency = {} + for word in seed_words: + word_frequency[word] = word_frequency.get(word, 0) + 1 + if word_frequency[word] > 2: + return True + + return False + + # EOF From 172ebd40ff25b2cbf445192bcf99a76b4b2ce38f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 8 Jan 2024 12:47:48 -0500 Subject: [PATCH 176/239] SFT-3145: auto shutdown defaults to 10 minutes until a seed is saved --- .../boards/Passport/modules/tasks/auto_shutdown_task.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/tasks/auto_shutdown_task.py b/ports/stm32/boards/Passport/modules/tasks/auto_shutdown_task.py index a26dde2c2..1c14a021e 100644 --- a/ports/stm32/boards/Passport/modules/tasks/auto_shutdown_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/auto_shutdown_task.py @@ -7,7 +7,7 @@ import utime import common import passport -from utils import get_screen_brightness +from utils import get_screen_brightness, has_seed async def auto_shutdown_task(): @@ -20,7 +20,8 @@ async def auto_shutdown_task(): await sleep_ms(1000) # Get timeout in seconds, and current time - timeout = common.settings.get('shutdown_timeout', 5 * 60) + minutes = 5 if has_seed() else 10 + timeout = common.settings.get('shutdown_timeout', minutes * 60) now = utime.ticks_ms() # Dim screen for 30s before shutdown From b0004f41fb7cde8cf3d5eb840f9df05048b0bbe4 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 9 Jan 2024 14:22:08 -0500 Subject: [PATCH 177/239] SFT-927: improved repetitive seed warning copy --- ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py index 9444e9214..64824777e 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_seed_flow.py @@ -129,7 +129,8 @@ async def enter_seed_words(self): self.seed_words.append(last_word) if insufficient_randomness(self.seed_words): - result2 = await ErrorPage("Seeds with low randomness can be vulnerable to draining attacks.\n\nContinue?", + text = "This seed contains 3 or more repeat words and may put funds at risk.\n\nSave seed and continue?" + result2 = await ErrorPage(text=text, left_micron=microns.Cancel).show() if not result2: From 0e773475dfb242dda57ee069d516e4c7c9bc7544 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 12 Jan 2024 13:09:08 -0500 Subject: [PATCH 178/239] SFT-3139: improved handling of binary data --- ports/stm32/boards/Passport/modules/chains.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index 595c2f52b..fd5e325e5 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -210,9 +210,14 @@ def render_address(cls, script): return tcc.codecs.bech32_encode(cls.bech32_hrp, 1, script[2:], tcc.codecs.BECH32_ENCODING_BECH32M) # OP_RETURN, 3 bytes of metadata - if script[0:2] == b'\x6a\x4c' and script[2] == ll - 3: - message = script[3:].decode() - return 'OP_RETURN:\n{}'.format(message) + if ll > 3 and script[0:2] == b'\x6a\x4c' and script[2] == ll - 3: + message = script[3:] + try: + decoded_message = message.decode() + return 'OP_RETURN:\n{}'.format(decoded_message) + except UnicodeDecodeError: + hex_data = message.hex() + return 'OP_RETURN:\n{}'.format(hex_data) raise ValueError('Unknown payment script', repr(script)) From 254b00520e870598f6b8f135c453d4ae5d97d6d0 Mon Sep 17 00:00:00 2001 From: mjg-foundation <114431859+mjg-foundation@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:47:28 -0500 Subject: [PATCH 179/239] Update version.txt to 2.3.0b5 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e13d7a2bd..f03ace7cc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.0b2 +2.3.0b5 From 42d5de823d84447bdac6802954e347c626d5653e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 17 Jan 2024 11:26:45 -0500 Subject: [PATCH 180/239] SFT-3004: address explorer back button only goes back one step --- .../boards/Passport/modules/flows/address_explorer_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index f775807e4..89a4c388c 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -167,7 +167,7 @@ async def explore(self): margins=MARGIN_FOR_ADDRESSES).show() if not result: - self.set_result(True) + self.back() return elif result is True: await ShowQRPage(qr_data=self.address, From 03731672fd0a797b346bb540aa276daba8d2098f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 17 Jan 2024 13:01:45 -0500 Subject: [PATCH 181/239] SFT-3004: changed all cancel icons in the address explorer flow to back icons --- .../Passport/modules/flows/address_explorer_flow.py | 13 +++++++++---- .../modules/pages/address_type_chooser_page.py | 7 +++++-- .../pages/singlesig_multisig_chooser_page.py | 7 +++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 89a4c388c..0e5e8b0e4 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -48,7 +48,9 @@ async def choose_sig_type(self): return result = await SinglesigMultisigChooserPage( - initial_value=self.sig_type, multisigs=multisigs).show() + initial_value=self.sig_type, + multisigs=multisigs, + left_micron=microns.Back).show() if result is None: self.set_result(False) @@ -65,8 +67,9 @@ async def choose_sig_type(self): async def choose_addr_type(self): from pages import AddressTypeChooserPage from wallets.utils import get_deriv_path_from_addr_type_and_acct + import microns - result = await AddressTypeChooserPage().show() + result = await AddressTypeChooserPage(left_micron=microns.Back).show() if not result: if not self.back(): @@ -79,10 +82,12 @@ async def choose_addr_type(self): async def choose_change(self): from pages import YesNoChooserPage + import microns result = await YesNoChooserPage(text="\nExplore receive or change addresses?", yes_text="Receive", - no_text="Change").show() + no_text="Change", + left_micron=microns.Back).show() if result is None: self.back() @@ -162,7 +167,7 @@ async def explore(self): while True: result = await AddressExplorerPage(msg, - left_micron=microns.Cancel, + left_micron=microns.Back, right_micron=microns.ScanQR, margins=MARGIN_FOR_ADDRESSES).show() diff --git a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py index 3f8352887..f2419f29a 100644 --- a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py @@ -16,8 +16,11 @@ class AddressTypeChooserPage(ChooserPage): {'label': 'Taproot', 'value': AF_P2TR}, ] - def __init__(self, card_header={'title': 'Address Type'}, initial_value=None): + def __init__(self, card_header={'title': 'Address Type'}, initial_value=None, + left_micron=None, right_micron=None): super().__init__( card_header=card_header, options=self.OPTIONS, - initial_value=initial_value or self.OPTIONS[0].get('value')) + initial_value=initial_value or self.OPTIONS[0].get('value'), + left_micron=left_micron, + right_micron=right_micron) diff --git a/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py index 9ed5c7c70..aced61c55 100644 --- a/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/singlesig_multisig_chooser_page.py @@ -8,7 +8,8 @@ class SinglesigMultisigChooserPage(ChooserPage): - def __init__(self, multisigs, card_header={'title': 'Single/Multisig'}, initial_value=None): + def __init__(self, multisigs, card_header={'title': 'Single/Multisig'}, initial_value=None, + left_micron=None, right_micron=None): options = [{'label': 'Single-sig', 'value': ('single-sig', None)}] for ms in multisigs: @@ -18,4 +19,6 @@ def __init__(self, multisigs, card_header={'title': 'Single/Multisig'}, initial_ super().__init__( card_header=card_header, options=options, - initial_value=initial_value or options[0].get('value')) + initial_value=initial_value or options[0].get('value'), + left_micron=left_micron, + right_micron=right_micron) From 4d065b2ee7e92df8a1b7f2f69da03605e37bd353 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 25 Jan 2024 14:29:16 -0500 Subject: [PATCH 182/239] SFT-36: added spinner and success page after device rename --- .../boards/Passport/modules/flows/rename_device_flow.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py b/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py index 3f2942be1..811f0c84b 100644 --- a/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py @@ -16,6 +16,10 @@ def __init__(self): super().__init__(initial_state=self.rename_device, name='NameDeviceFLow') async def rename_device(self): + from utils import spinner_task + from tasks import delay_task + from pages import SuccessPage + name = settings.get('device_name', None) or '' result = await TextInputPage(initial_text=name, max_length=MAX_ACCOUNT_NAME_LEN, @@ -37,4 +41,8 @@ async def rename_device(self): return settings.set('device_name', result) + + await spinner_task('Renaming Device', delay_task, args=[1000, False]) + await SuccessPage(text='Device Renamed Successfully').show() + self.set_result(True) From b4bcdebc5bbab84d6c9509e8fec711111fea06a1 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 29 Jan 2024 09:28:52 -0500 Subject: [PATCH 183/239] SFT-3223: fixed crash --- .../stm32/boards/Passport/modules/flows/address_explorer_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 0e5e8b0e4..8c502e14e 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -38,6 +38,7 @@ async def choose_sig_type(self): from pages import SinglesigMultisigChooserPage from multisig_wallet import MultisigWallet from common import settings + import microns xfp = settings.get('xfp') multisigs = MultisigWallet.get_by_xfp(xfp) From 09c350ef538c6cf55c25ab835b53f6c742e263f7 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 15 Oct 2023 19:40:27 -0400 Subject: [PATCH 184/239] SFT-1707: connected to sparrow and other generic_json_wallets --- ports/stm32/boards/Passport/modules/chains.py | 4 ++-- .../boards/Passport/modules/wallets/generic_json_wallet.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/chains.py b/ports/stm32/boards/Passport/modules/chains.py index fd5e325e5..c53236595 100644 --- a/ports/stm32/boards/Passport/modules/chains.py +++ b/ports/stm32/boards/Passport/modules/chains.py @@ -11,7 +11,7 @@ # import trezorcrypto import tcc -from public_constants import AF_CLASSIC, AF_P2SH, AF_P2WPKH, AF_P2WSH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH +from public_constants import AF_CLASSIC, AF_P2SH, AF_P2WPKH, AF_P2WSH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, AF_P2TR from public_constants import AFC_PUBKEY, AFC_SEGWIT, AFC_BECH32, AFC_SCRIPT, AFC_WRAPPED, AFC_BECH32M from serializations import hash160, ser_compact_size from ucollections import namedtuple @@ -57,7 +57,7 @@ def serialize_private(cls, node, addr_fmt=AF_CLASSIC): @classmethod def serialize_public(cls, node, addr_fmt=AF_CLASSIC): # output a xpub - addr_fmt = AF_CLASSIC if addr_fmt == AF_P2SH else addr_fmt + addr_fmt = AF_CLASSIC if (addr_fmt == AF_P2SH or addr_fmt == AF_P2TR) else addr_fmt return node.serialize_public(cls.slip132[addr_fmt].pub) @classmethod diff --git a/ports/stm32/boards/Passport/modules/wallets/generic_json_wallet.py b/ports/stm32/boards/Passport/modules/wallets/generic_json_wallet.py index f6bfc2943..1e91c6ce8 100644 --- a/ports/stm32/boards/Passport/modules/wallets/generic_json_wallet.py +++ b/ports/stm32/boards/Passport/modules/wallets/generic_json_wallet.py @@ -15,7 +15,7 @@ import stash from utils import xfp2str, to_str from common import settings -from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, AF_P2WSH +from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, AF_P2WSH, AF_P2TR from data_codecs.qr_type import QRType from foundation import ur @@ -46,6 +46,7 @@ def create_generic_json_wallet(sw_wallet=None, ('bip84', "m/84'/{coin_type}'/{acct}'", AF_P2WPKH, 'p2wpkh', False), ('bip48_1', "m/48'/{coin_type}'/{acct}'/1'", AF_P2WSH_P2SH, 'p2sh-p2wsh', True), ('bip48_2', "m/48'/{coin_type}'/{acct}'/2'", AF_P2WSH, 'p2wsh', True), + ('bip86', "m/86'/{coin_type}'/{acct}'", AF_P2TR, 'p2tr', False), ]: dd = deriv.format(coin_type=chain.b44_cointype, acct=acct_num) node = sv.derive_path(dd) From e1be1f01264b3a30a3e35a598976b56f20eeec87 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 15 Oct 2023 20:28:11 -0400 Subject: [PATCH 185/239] SFT-1707: first steps on electum-based wallets --- .../stm32/boards/Passport/modules/flows/connect_wallet_flow.py | 2 +- ports/stm32/boards/Passport/modules/wallets/electrum.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/connect_wallet_flow.py b/ports/stm32/boards/Passport/modules/flows/connect_wallet_flow.py index a57aea87a..0676169b3 100644 --- a/ports/stm32/boards/Passport/modules/flows/connect_wallet_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/connect_wallet_flow.py @@ -199,7 +199,7 @@ async def choose_sig_type(self): else: self.goto(self.choose_export_mode, save_curr=save_curr) - async def choose_address_type(self): + async def choose_addr_type(self): result = await AddressTypeChooserPage(initial_value=self.addr_type).show() if result is None: if not self.back(): diff --git a/ports/stm32/boards/Passport/modules/wallets/electrum.py b/ports/stm32/boards/Passport/modules/wallets/electrum.py index a2d9cd870..f73011161 100644 --- a/ports/stm32/boards/Passport/modules/wallets/electrum.py +++ b/ports/stm32/boards/Passport/modules/wallets/electrum.py @@ -18,7 +18,7 @@ from utils import xfp2str, to_str # from .multisig_json import create_multisig_json_wallet # from .multisig_import import read_multisig_config_from_qr, read_multisig_config_from_microsd -from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH +from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR from .utils import get_bip_num_from_addr_type From eb455ec4d83ef55ed15620e9e097f14fe97449d2 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 03:14:03 -0400 Subject: [PATCH 186/239] SFT-1707: added taproot export for envoy and reworked address type chooser page --- .../modules/flows/connect_wallet_flow.py | 5 ++-- .../pages/address_type_chooser_page.py | 29 ++++++++++++------- .../boards/Passport/modules/wallets/envoy.py | 4 ++- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/connect_wallet_flow.py b/ports/stm32/boards/Passport/modules/flows/connect_wallet_flow.py index 0676169b3..75dc3019d 100644 --- a/ports/stm32/boards/Passport/modules/flows/connect_wallet_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/connect_wallet_flow.py @@ -194,13 +194,14 @@ async def choose_sig_type(self): # NOTE: Nothing uses this option at the moment, but leaving it here in case we need it for a # new wallet later. - if self.sw_wallet.get('options', {}).get('select_addr_type', False): + if self.sw_wallet.get('select_addr_type', False): self.goto(self.choose_addr_type, save_curr=save_curr) else: self.goto(self.choose_export_mode, save_curr=save_curr) async def choose_addr_type(self): - result = await AddressTypeChooserPage(initial_value=self.addr_type).show() + result = await AddressTypeChooserPage(initial_value=self.addr_type, + options=self.sw_wallet.get('addr_options', None)).show() if result is None: if not self.back(): self.set_result(False) diff --git a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py index f2419f29a..c304e6b8b 100644 --- a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py @@ -9,18 +9,25 @@ class AddressTypeChooserPage(ChooserPage): - OPTIONS = [ - {'label': 'Segwit', 'value': AF_P2WPKH}, - {'label': 'Wrapped Segwit', 'value': AF_P2WPKH_P2SH}, - {'label': 'Legacy', 'value': AF_CLASSIC}, - {'label': 'Taproot', 'value': AF_P2TR}, - ] - - def __init__(self, card_header={'title': 'Address Type'}, initial_value=None, - left_micron=None, right_micron=None): + LABELS = { + AF_P2WPKH: 'Segwit', + AF_P2WPKH_P2SH: 'Wrapped Segwit', + AF_CLASSIC: 'Legacy', + AF_P2TR: 'Taproot', + } + + def __init__(self, card_header={'title': 'Address Type'}, initial_value=None, options=None, left_micron=None, right_micron=None): + + if options is None: + options = list(self.LABELS.keys()) + + final_options = [] + for addr_type in options: + final_options.append({'label': self.LABELS[addr_type], 'value': addr_type}) + super().__init__( card_header=card_header, - options=self.OPTIONS, - initial_value=initial_value or self.OPTIONS[0].get('value'), + options=final_options, + initial_value=initial_value or final_options[0].get('value'), left_micron=left_micron, right_micron=right_micron) diff --git a/ports/stm32/boards/Passport/modules/wallets/envoy.py b/ports/stm32/boards/Passport/modules/wallets/envoy.py index 946e515fc..1b2d4e482 100644 --- a/ports/stm32/boards/Passport/modules/wallets/envoy.py +++ b/ports/stm32/boards/Passport/modules/wallets/envoy.py @@ -10,7 +10,7 @@ import common from utils import to_str, get_accounts_by_xfp from data_codecs.qr_type import QRType -from public_constants import AF_CLASSIC, AF_P2WPKH +from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2TR from foundation import ur import passport @@ -97,4 +97,6 @@ def create_envoy_export(sw_wallet=None, ], 'skip_address_validation': False, 'skip_multisig_import': True, + 'select_addr_type': True, + 'addr_options': [AF_P2WPKH, AF_P2TR], } From 12c25f43993c8c78d70634e4398b117f55c6f949 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 03:25:23 -0400 Subject: [PATCH 187/239] SFT-1707: added taproot for specter wallet --- ports/stm32/boards/Passport/modules/wallets/specter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/specter.py b/ports/stm32/boards/Passport/modules/wallets/specter.py index f8438b5ef..80cd801b7 100644 --- a/ports/stm32/boards/Passport/modules/wallets/specter.py +++ b/ports/stm32/boards/Passport/modules/wallets/specter.py @@ -8,7 +8,7 @@ from .multisig_json import create_multisig_json_wallet from .multisig_import import read_multisig_config_from_qr, read_multisig_config_from_microsd from data_codecs.qr_type import QRType -from public_constants import AF_P2WPKH +from public_constants import AF_P2WPKH, AF_P2TR SpecterWallet = { 'label': 'Specter', @@ -21,5 +21,7 @@ {'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2}, {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-specter.json', 'filename_pattern_multisig': '{xfp}-specter-multisig.json', 'multisig_import_mode': 'qr'} - ] + ], + 'select_addr_type': True, + 'addr_options': [AF_P2WPKH, AF_P2TR], } From 0bbfbb4fa3b5f555e0f921581f2658d4869651d6 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 03:50:26 -0400 Subject: [PATCH 188/239] SFT-1707: added taproot for bitcoin core --- .../Passport/modules/public_constants.py | 3 +++ .../Passport/modules/wallets/bitcoin_core.py | 21 ++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/public_constants.py b/ports/stm32/boards/Passport/modules/public_constants.py index cbffe0ada..8616746f1 100644 --- a/ports/stm32/boards/Passport/modules/public_constants.py +++ b/ports/stm32/boards/Passport/modules/public_constants.py @@ -72,6 +72,9 @@ AF_P2TR, ]) +DESCRIPTOR_CODES = {AF_P2WPKH: 'wpkh', + AF_P2TR: 'tr'} + # BIP-174 aka PSBT defined values # PSBT_GLOBAL_UNSIGNED_TX = const(0) diff --git a/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py b/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py index fd57d7cd0..2cc872d12 100644 --- a/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py +++ b/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py @@ -15,6 +15,8 @@ import chains from common import settings from utils import xfp2str +from public_constants import AF_P2WPKH, AF_P2TR, DESCRIPTOR_CODES +from .utils import get_bip_num_from_addr_type def create_bitcoin_core_export(sw_wallet=None, @@ -29,7 +31,7 @@ def create_bitcoin_core_export(sw_wallet=None, # make the data example_addrs = [] - payload = ujson.dumps(list(generate_bitcoin_core_wallet(example_addrs, acct_num))) + payload = ujson.dumps(list(generate_bitcoin_core_wallet(addr_type, example_addrs, acct_num))) body = '''\ # Bitcoin Core Wallet Import File @@ -56,16 +58,16 @@ def create_bitcoin_core_export(sw_wallet=None, return (body, accts) -def generate_bitcoin_core_wallet(example_addrs, acct_num): +def generate_bitcoin_core_wallet(addr_type, example_addrs, acct_num): # Generate the data for an RPC command to import keys into Bitcoin Core # - yields dicts for json purposes from descriptor import append_checksum - from public_constants import AF_P2WPKH + mode = get_bip_num_from_addr_type(addr_type, False) chain = chains.current_chain() - derive = "84'/{coin_type}'/{account}'".format(account=acct_num, coin_type=chain.b44_cointype) + derive = "{mode}'/{coin_type}'/{account}'".format(mode=mode, account=acct_num, coin_type=chain.b44_cointype) with stash.SensitiveValues() as sv: prefix = sv.derive_path(derive) @@ -74,7 +76,7 @@ def generate_bitcoin_core_wallet(example_addrs, acct_num): for i in range(3): sp = '0/%d' % i node = sv.derive_path(sp, master=prefix) - a = chain.address(node, AF_P2WPKH) + a = chain.address(node, addr_type) example_addrs.append(('m/%s/%s' % (derive, sp), a)) xfp = settings.get('xfp') @@ -82,8 +84,11 @@ def generate_bitcoin_core_wallet(example_addrs, acct_num): chain = chains.current_chain() + descriptor_code = DESCRIPTOR_CODES[addr_type] + for internal in [False, True]: - desc = "wpkh([{fingerprint}/{derive}]{xpub}/{change}/*)".format( + desc = "{descriptor_code}([{fingerprint}/{derive}]{xpub}/{change}/*)".format( + descriptor_code=descriptor_code, derive=derive.replace("'", "h"), fingerprint=txt_xfp, coin_type=chain.b44_cointype, @@ -109,5 +114,7 @@ def generate_bitcoin_core_wallet(example_addrs, acct_num): 'export_modes': [ {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-bitcoin-core.txt', 'ext': '.txt', 'filename_pattern_multisig': '{xfp}-bitcoin-core-multisig.json'} - ] + ], + 'select_addr_type': True, + 'addr_options': [AF_P2WPKH, AF_P2TR] } From 257d6f2f45d053053dde65ee734bdd56a3da21e0 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 03:55:08 -0400 Subject: [PATCH 189/239] SFT-1707: first pass at btcpay server taproot support --- ports/stm32/boards/Passport/modules/wallets/btcpay.py | 6 ++++-- ports/stm32/boards/Passport/modules/wallets/vault.py | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/btcpay.py b/ports/stm32/boards/Passport/modules/wallets/btcpay.py index 778931b0e..bacde003b 100644 --- a/ports/stm32/boards/Passport/modules/wallets/btcpay.py +++ b/ports/stm32/boards/Passport/modules/wallets/btcpay.py @@ -6,7 +6,7 @@ from .vault import create_vault_export from data_codecs.qr_type import QRType -from public_constants import AF_P2WPKH +from public_constants import AF_P2WPKH, AF_P2TR BtcPayWallet = { 'label': 'BTCPay', @@ -18,5 +18,7 @@ {'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.QR}, {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-btcpay.json', 'filename_pattern_multisig': '{xfp}-btcpay-multisig.json'} - ] + ], + 'select_addr_type': True, + 'addr_options': [AF_P2WPKH, AF_P2TR], } diff --git a/ports/stm32/boards/Passport/modules/wallets/vault.py b/ports/stm32/boards/Passport/modules/wallets/vault.py index e60833f2b..33d2a4d2d 100644 --- a/ports/stm32/boards/Passport/modules/wallets/vault.py +++ b/ports/stm32/boards/Passport/modules/wallets/vault.py @@ -9,6 +9,7 @@ import ujson from utils import xfp2str, to_str from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH +from .utils import get_bip_num_from_addr_type def create_vault_export(sw_wallet=None, @@ -22,8 +23,12 @@ def create_vault_export(sw_wallet=None, chain = chains.current_chain() + mode = get_bip_num_from_addr_type(addr_type, multisig) + (fw_version, _, _, _, _) = system.get_software_info() - acct_path = "84'/{coin_type}'/{acct}'".format(coin_type=chain.b44_cointype, acct=acct_num) + acct_path = "{mode}'/{coin_type}'/{acct}'".format(mode=mode, + coin_type=chain.b44_cointype, + acct=acct_num) master_xfp = xfp2str(settings.get('xfp')) with stash.SensitiveValues() as sv: From 54deec2bf5de77c41e8c78d8cf73231ca2bf8315 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 15 Nov 2023 19:37:06 -0500 Subject: [PATCH 190/239] SFT-1707: added taproot to envoy export, added microsd export option for debugging --- .../boards/Passport/modules/wallets/envoy.py | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/envoy.py b/ports/stm32/boards/Passport/modules/wallets/envoy.py index 1b2d4e482..782403553 100644 --- a/ports/stm32/boards/Passport/modules/wallets/envoy.py +++ b/ports/stm32/boards/Passport/modules/wallets/envoy.py @@ -34,8 +34,6 @@ def create_envoy_export(sw_wallet=None, serial_num = system.get_serial_number() fw_version = 'v{}'.format(system.get_software_info()[0]) - mode = get_bip_num_from_addr_type(addr_type, multisig) - chain = chains.current_chain() xfp = settings.get('xfp') @@ -67,24 +65,45 @@ def create_envoy_export(sw_wallet=None, device_name = common.settings.get('device_name', '') - rv = dict(derivation=acct_path, - xfp=xfp, - xpub=xpub, - acct_name=acct_name, + rv = dict(acct_name=acct_name, acct_num=acct_num, hw_version=1.2 if passport.IS_COLOR else 1, fw_version=fw_version, serial=serial_num, device_name=device_name) + for name, mode in [ + ('bip84', 84), + ('bip86', 86) + ]: + with stash.SensitiveValues() as sv: + acct_path = "m/{mode}'/{coin}'/{acct}'".format( + mode=mode, + coin=chain.b44_cointype, + acct=acct_num) + + child_node = sv.derive_path(acct_path) + xfp = settings.get('xfp') + xpub = sv.chain.serialize_public(child_node, AF_CLASSIC) + # print('xfp to export: {}'.format(xfp)) + # print('xpub to export: {}'.format(xpub)) + + rv[name] = dict(derivation=acct_path, + xfp=xfp, + xpub=xpub) + # Return the possible account mappings that the exported wallet can choose from # When we get the address back, we can determine the 'fmt' from the address and then look it up to # find the derivation path and account number. accts = [{'fmt': addr_type, 'deriv': acct_path, 'acct': acct_num}] msg = ujson.dumps(rv) + + if export_mode == 'qr': + return (ur.new_bytes(msg), accts) # print('msg={}'.format(to_str(msg))) - return (ur.new_bytes(msg), accts) + + return (msg, accts) EnvoyWallet = { @@ -93,10 +112,9 @@ def create_envoy_export(sw_wallet=None, {'id': 'single-sig', 'label': 'Single-sig', 'addr_type': AF_P2WPKH, 'create_wallet': create_envoy_export}, ], 'export_modes': [ - {'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2} + {'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2}, + {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-envoy.json'} ], 'skip_address_validation': False, 'skip_multisig_import': True, - 'select_addr_type': True, - 'addr_options': [AF_P2WPKH, AF_P2TR], } From 8a646974766b395fb5d0cee703e4cfc7e341eb08 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 19 Nov 2023 22:24:48 -0500 Subject: [PATCH 191/239] SFT-1707: updated btcpay export format for taproot --- .../boards/Passport/modules/wallets/vault.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/vault.py b/ports/stm32/boards/Passport/modules/wallets/vault.py index 33d2a4d2d..0215dd67a 100644 --- a/ports/stm32/boards/Passport/modules/wallets/vault.py +++ b/ports/stm32/boards/Passport/modules/wallets/vault.py @@ -8,7 +8,7 @@ import stash import ujson from utils import xfp2str, to_str -from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH +from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR from .utils import get_bip_num_from_addr_type @@ -31,15 +31,25 @@ def create_vault_export(sw_wallet=None, acct=acct_num) master_xfp = xfp2str(settings.get('xfp')) + xpub = None with stash.SensitiveValues() as sv: child_node = sv.derive_path(acct_path) xpub = sv.chain.serialize_public(child_node, addr_type) - msg = ujson.dumps(dict(ExtPubKey=xpub, - MasterFingerprint=master_xfp, - AccountKeyPath=acct_path, - FirmwareVersion=fw_version)) + rv = dict() - accts = [{'fmt': AF_P2WPKH, 'deriv': acct_path, 'acct': acct_num}] + if addr_type == AF_P2TR: + rv['Descriptor'] = "tr([{}/{}]{}/0/*)".format(master_xfp, acct_path, xpub) + else: + rv['ExtPubKey'] = xpub + rv['MasterFingerprint'] = master_xfp + rv['AccountKeyPath'] = acct_path + + rv['FirmwareVersion'] = fw_version + rv['Source'] = 'Passport' + + msg = ujson.dumps(rv) + + accts = [{'fmt': addr_type, 'deriv': acct_path, 'acct': acct_num}] return (msg, accts) From 43d38a6ca9f88574fe0af64c941eab09cb4991d3 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 19 Nov 2023 23:04:50 -0500 Subject: [PATCH 192/239] SFT-1707: fixed ordering of address type options --- .../modules/pages/address_type_chooser_page.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py index c304e6b8b..f6c09f517 100644 --- a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py @@ -6,15 +6,16 @@ from pages import ChooserPage from public_constants import AF_P2WPKH, AF_P2WPKH_P2SH, AF_CLASSIC, AF_P2TR +from ucollections import OrderedDict class AddressTypeChooserPage(ChooserPage): - LABELS = { - AF_P2WPKH: 'Segwit', - AF_P2WPKH_P2SH: 'Wrapped Segwit', - AF_CLASSIC: 'Legacy', - AF_P2TR: 'Taproot', - } + LABELS = OrderedDict([ + (AF_P2WPKH, 'Segwit'), + (AF_P2WPKH_P2SH, 'Wrapped Segwit'), + (AF_CLASSIC, 'Legacy'), + (AF_P2TR, 'Taproot'), + ]) def __init__(self, card_header={'title': 'Address Type'}, initial_value=None, options=None, left_micron=None, right_micron=None): From d870b13deace533d1e76f03b3beb6d6d41b9010d Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 19 Nov 2023 23:36:56 -0500 Subject: [PATCH 193/239] SFT-1707: used account num in bitcoin_core export --- ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py b/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py index 2cc872d12..c785d7063 100644 --- a/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py +++ b/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py @@ -92,7 +92,7 @@ def generate_bitcoin_core_wallet(addr_type, example_addrs, acct_num): derive=derive.replace("'", "h"), fingerprint=txt_xfp, coin_type=chain.b44_cointype, - account=0, + account=acct_num, xpub=xpub, change=(1 if internal else 0)) From a473624208b2df22c384ef02f3dd929f35d3657f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 17 Oct 2023 03:14:03 -0400 Subject: [PATCH 194/239] SFT-1707: added taproot export for envoy and reworked address type chooser page --- ports/stm32/boards/Passport/modules/wallets/envoy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/wallets/envoy.py b/ports/stm32/boards/Passport/modules/wallets/envoy.py index 782403553..acdd34fbb 100644 --- a/ports/stm32/boards/Passport/modules/wallets/envoy.py +++ b/ports/stm32/boards/Passport/modules/wallets/envoy.py @@ -117,4 +117,6 @@ def create_envoy_export(sw_wallet=None, ], 'skip_address_validation': False, 'skip_multisig_import': True, + 'select_addr_type': True, + 'addr_options': [AF_P2WPKH, AF_P2TR], } From e925a6b4892099a78d36251610ad7ebddfdaf8a1 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 27 Nov 2023 10:14:21 -0600 Subject: [PATCH 195/239] SFT-1707: removed address type selection for envoy --- ports/stm32/boards/Passport/modules/wallets/envoy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/envoy.py b/ports/stm32/boards/Passport/modules/wallets/envoy.py index acdd34fbb..782403553 100644 --- a/ports/stm32/boards/Passport/modules/wallets/envoy.py +++ b/ports/stm32/boards/Passport/modules/wallets/envoy.py @@ -117,6 +117,4 @@ def create_envoy_export(sw_wallet=None, ], 'skip_address_validation': False, 'skip_multisig_import': True, - 'select_addr_type': True, - 'addr_options': [AF_P2WPKH, AF_P2TR], } From 15e76389a6149541d53f8bda0e16373aa3a64a8e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 25 Jan 2024 12:53:56 -0500 Subject: [PATCH 196/239] SFT-1707: removed taproot export options for bitcoin core and specter until compatibility is confirmed --- ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py | 4 ++-- ports/stm32/boards/Passport/modules/wallets/specter.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py b/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py index c785d7063..81dbf4e17 100644 --- a/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py +++ b/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py @@ -115,6 +115,6 @@ def generate_bitcoin_core_wallet(addr_type, example_addrs, acct_num): {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-bitcoin-core.txt', 'ext': '.txt', 'filename_pattern_multisig': '{xfp}-bitcoin-core-multisig.json'} ], - 'select_addr_type': True, - 'addr_options': [AF_P2WPKH, AF_P2TR] + # 'select_addr_type': True, + # 'addr_options': [AF_P2WPKH, AF_P2TR] } diff --git a/ports/stm32/boards/Passport/modules/wallets/specter.py b/ports/stm32/boards/Passport/modules/wallets/specter.py index 80cd801b7..0b4e5f2ea 100644 --- a/ports/stm32/boards/Passport/modules/wallets/specter.py +++ b/ports/stm32/boards/Passport/modules/wallets/specter.py @@ -22,6 +22,6 @@ {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-specter.json', 'filename_pattern_multisig': '{xfp}-specter-multisig.json', 'multisig_import_mode': 'qr'} ], - 'select_addr_type': True, - 'addr_options': [AF_P2WPKH, AF_P2TR], + # 'select_addr_type': True, + # 'addr_options': [AF_P2WPKH, AF_P2TR], } From 03a9342e87f10e97daab7467965fa17dbcf452d5 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 25 Jan 2024 13:05:09 -0500 Subject: [PATCH 197/239] SFT-1707: reduced diff, removed microsd export for envoy --- ports/stm32/boards/Passport/modules/wallets/electrum.py | 2 +- ports/stm32/boards/Passport/modules/wallets/envoy.py | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/electrum.py b/ports/stm32/boards/Passport/modules/wallets/electrum.py index f73011161..a2d9cd870 100644 --- a/ports/stm32/boards/Passport/modules/wallets/electrum.py +++ b/ports/stm32/boards/Passport/modules/wallets/electrum.py @@ -18,7 +18,7 @@ from utils import xfp2str, to_str # from .multisig_json import create_multisig_json_wallet # from .multisig_import import read_multisig_config_from_qr, read_multisig_config_from_microsd -from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR +from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH from .utils import get_bip_num_from_addr_type diff --git a/ports/stm32/boards/Passport/modules/wallets/envoy.py b/ports/stm32/boards/Passport/modules/wallets/envoy.py index 782403553..383f5b9e8 100644 --- a/ports/stm32/boards/Passport/modules/wallets/envoy.py +++ b/ports/stm32/boards/Passport/modules/wallets/envoy.py @@ -98,12 +98,8 @@ def create_envoy_export(sw_wallet=None, accts = [{'fmt': addr_type, 'deriv': acct_path, 'acct': acct_num}] msg = ujson.dumps(rv) - - if export_mode == 'qr': - return (ur.new_bytes(msg), accts) # print('msg={}'.format(to_str(msg))) - - return (msg, accts) + return (ur.new_bytes(msg), accts) EnvoyWallet = { @@ -112,8 +108,7 @@ def create_envoy_export(sw_wallet=None, {'id': 'single-sig', 'label': 'Single-sig', 'addr_type': AF_P2WPKH, 'create_wallet': create_envoy_export}, ], 'export_modes': [ - {'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2}, - {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-envoy.json'} + {'id': 'qr', 'label': 'QR Code', 'qr_type': QRType.UR2} ], 'skip_address_validation': False, 'skip_multisig_import': True, From 924bfa425304b2990fca080f5a3e7b4175fd5008 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 25 Jan 2024 13:15:56 -0500 Subject: [PATCH 198/239] SFT-1707: removed old export formatting in envoy --- ports/stm32/boards/Passport/modules/wallets/envoy.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/envoy.py b/ports/stm32/boards/Passport/modules/wallets/envoy.py index 383f5b9e8..fda020d34 100644 --- a/ports/stm32/boards/Passport/modules/wallets/envoy.py +++ b/ports/stm32/boards/Passport/modules/wallets/envoy.py @@ -37,17 +37,6 @@ def create_envoy_export(sw_wallet=None, chain = chains.current_chain() xfp = settings.get('xfp') - with stash.SensitiveValues() as sv: - acct_path = "m/{mode}'/{coin}'/{acct}'".format( - mode=mode, - coin=chain.b44_cointype, - acct=acct_num) - - child_node = sv.derive_path(acct_path) - xpub = sv.chain.serialize_public(child_node, AF_CLASSIC) - # print('xfp to export: {}'.format(xfp)) - # print('xpub to export: {}'.format(xpub)) - accounts = get_accounts_by_xfp(xfp) acct_name = '' if accounts is not None and len(accounts) > 0: From b6cf6bdaed4b044051d57f076443921c068be1dc Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 25 Jan 2024 20:52:12 -0500 Subject: [PATCH 199/239] SFT-1707: fixed lint in address type chooser page --- .../Passport/modules/pages/address_type_chooser_page.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py index f6c09f517..c4b0070bf 100644 --- a/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py +++ b/ports/stm32/boards/Passport/modules/pages/address_type_chooser_page.py @@ -17,7 +17,12 @@ class AddressTypeChooserPage(ChooserPage): (AF_P2TR, 'Taproot'), ]) - def __init__(self, card_header={'title': 'Address Type'}, initial_value=None, options=None, left_micron=None, right_micron=None): + def __init__(self, + card_header={'title': 'Address Type'}, + initial_value=None, + options=None, + left_micron=None, + right_micron=None): if options is None: options = list(self.LABELS.keys()) From ac1b76114d89f463c1815b83907ad765b82980cd Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 29 Jan 2024 14:00:32 -0500 Subject: [PATCH 200/239] SFT-1707: fixed bitcoin core export, used segwit as default address type --- .../stm32/boards/Passport/modules/wallets/bitcoin_core.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py b/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py index 81dbf4e17..b2fcb017b 100644 --- a/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py +++ b/ports/stm32/boards/Passport/modules/wallets/bitcoin_core.py @@ -20,7 +20,7 @@ def create_bitcoin_core_export(sw_wallet=None, - addr_type=None, + addr_type=AF_P2WPKH, acct_num=0, multisig=False, legacy=False, @@ -109,7 +109,10 @@ def generate_bitcoin_core_wallet(addr_type, example_addrs, acct_num): BitcoinCoreWallet = { 'label': 'Bitcoin Core', 'sig_types': [ - {'id': 'single-sig', 'label': 'Single-sig', 'addr_type': None, 'create_wallet': create_bitcoin_core_export}, + {'id': 'single-sig', + 'label': 'Single-sig', + 'addr_type': AF_P2WPKH, + 'create_wallet': create_bitcoin_core_export}, ], 'export_modes': [ {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-bitcoin-core.txt', 'ext': '.txt', From 9a84294727042eba14fc208d3c0cab83ee26ba4a Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 15 Jan 2024 11:39:14 -0500 Subject: [PATCH 201/239] SFT-3060: first pass on new address verified icon --- .../Passport/images/color/ICON_VERIFIED.c | 60 ++++++++++++++++++ .../color/ICON_VERIFIED__CF_INDEXED_2_BIT.png | Bin 0 -> 920 bytes ports/stm32/boards/Passport/images/images.h | 2 + .../Passport/images/mono/ICON_VERIFIED.c | 59 +++++++++++++++++ .../mono/ICON_VERIFIED__CF_INDEXED_1_BIT.png | Bin 0 -> 920 bytes .../modules/flows/verify_address_flow.py | 5 +- .../modules/pages/long_success_page.py | 3 +- .../Passport/modules/pages/success_page.py | 3 +- 8 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 ports/stm32/boards/Passport/images/color/ICON_VERIFIED.c create mode 100644 ports/stm32/boards/Passport/images/color/ICON_VERIFIED__CF_INDEXED_2_BIT.png create mode 100644 ports/stm32/boards/Passport/images/mono/ICON_VERIFIED.c create mode 100644 ports/stm32/boards/Passport/images/mono/ICON_VERIFIED__CF_INDEXED_1_BIT.png diff --git a/ports/stm32/boards/Passport/images/color/ICON_VERIFIED.c b/ports/stm32/boards/Passport/images/color/ICON_VERIFIED.c new file mode 100644 index 000000000..7dc2bd375 --- /dev/null +++ b/ports/stm32/boards/Passport/images/color/ICON_VERIFIED.c @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: © 2024 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + + +#ifndef LV_ATTRIBUTE_MEM_ALIGN +#define LV_ATTRIBUTE_MEM_ALIGN +#endif +#ifndef LV_ATTRIBUTE_IMG_ICON_VERIFIED +#define LV_ATTRIBUTE_IMG_ICON_VERIFIED +#endif +const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_IMG_ICON_VERIFIED uint8_t ICON_VERIFIED_map[] = { + 0x00, 0x00, 0x00, 0x00, /*Color of index 0*/ + 0xff, 0xff, 0xff, 0x2d, /*Color of index 1*/ + 0xfe, 0xfe, 0xfe, 0x98, /*Color of index 2*/ + 0xfe, 0xfe, 0xfe, 0xfd, /*Color of index 3*/ + + 0x00, 0x00, 0x6b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x00, 0x00, + 0x00, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x90, 0x00, + 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x00, + 0x01, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x40, + 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x90, + 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, + 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xbb, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xfd, + 0xbf, 0xff, 0xff, 0xff, 0xff, 0xd2, 0xff, 0xf6, 0xff, 0xdf, 0xff, 0xff, 0xf9, 0xf9, 0xa6, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xfe, + 0xbf, 0xff, 0xff, 0xff, 0xff, 0x4b, 0xff, 0xf6, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0xfd, 0x2f, 0xff, 0xfd, 0xff, 0x7e, 0x57, 0xf6, 0x6d, 0xd5, 0x76, 0xf9, 0x5f, 0xe5, 0x67, 0xff, 0xff, + 0xff, 0xff, 0xfb, 0xff, 0xf4, 0xbf, 0xff, 0xfd, 0xbe, 0x79, 0xb9, 0xf1, 0xb9, 0xf6, 0xf6, 0xe6, 0xe7, 0x9b, 0x97, 0xff, 0xff, + 0xff, 0xff, 0xf5, 0xbf, 0xd2, 0xff, 0xff, 0xfe, 0x7d, 0xb6, 0xf9, 0xb3, 0xf9, 0xf6, 0xf6, 0xdb, 0xe7, 0x6f, 0xe7, 0xff, 0xff, + 0xff, 0xff, 0xfd, 0x6f, 0x4b, 0xff, 0xff, 0xff, 0x69, 0xf5, 0x55, 0xb3, 0xf9, 0xf6, 0xf6, 0xc5, 0x57, 0x6f, 0xe7, 0xff, 0xff, + 0xbf, 0xff, 0xff, 0x49, 0x6f, 0xff, 0xff, 0xff, 0x96, 0xf6, 0xff, 0xf3, 0xfd, 0xf6, 0xf6, 0xdb, 0xff, 0x9f, 0xe7, 0xff, 0xfe, + 0xbf, 0xff, 0xff, 0xd1, 0xbf, 0xff, 0xff, 0xff, 0xd3, 0xfd, 0x69, 0xf3, 0xf9, 0xf6, 0xf6, 0xe1, 0x97, 0xd6, 0x57, 0xff, 0xfe, + 0x7f, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xff, 0x9b, 0xfb, 0xfe, 0xfb, 0xfb, 0xfe, 0x6f, 0xfa, 0xbb, 0xff, 0xfd, + 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, + 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd0, + 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x40, + 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x00, + 0x00, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x90, 0x00, + 0x00, 0x00, 0x6b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x00, 0x00, +}; + +const lv_img_dsc_t ICON_VERIFIED = { + .header.cf = LV_IMG_CF_INDEXED_2BIT, + .header.always_zero = 0, + .header.reserved = 0, + .header.w = 84, + .header.h = 26, + .data_size = 562, + .data = ICON_VERIFIED_map, +}; diff --git a/ports/stm32/boards/Passport/images/color/ICON_VERIFIED__CF_INDEXED_2_BIT.png b/ports/stm32/boards/Passport/images/color/ICON_VERIFIED__CF_INDEXED_2_BIT.png new file mode 100644 index 0000000000000000000000000000000000000000..df15cbd3b92c9214f13ec23c91a81c7eadb36fc3 GIT binary patch literal 920 zcmV;J184k+P)TM5!R8f*c*#6_`{Irvh*Vh6+RnkP4(!0ImRa03BTJ>E2n#f#D=Hnem`E zvvbmYcK4q4z8c5{Xswk=ei%WTQf>)*BO4=cMizs?U~{hW;G+T(IT1@_j~a!M7ej1u zs_=+VLu4YB$Y<$s4l}XD?)}UILJgUzNmfR_Nsky{_&*v_OPBFZC?m6wp3J5IsUsJu zcU}R8c~53@48?L+p^8v0K)Lj|28hzaMd(Dj2Zlf?w}XnqI}@D9WR8q{m;Z;- z$V-s7VjjxoV&}WIx{C{S6h=BCbS&MXB9j~7*E4m#%4sJ{mhGh(3b6z_{Fa(v__7eY zId6kPjz88ZD9oR^?hZRfJoIK4>W(R~`Ava{S?F$%QQsxx==e@|gRQpv*cdV^<3^Ie z{uVlz0yDlt#$LXw=gQaA=Q^#ZJMr{A7+HC?rh#tXk0o`aOs6`I(5>ir%;@+@MYC?d*i_l|9-TYTNl8>%Et=Y^Ljv;79C~J8Z53-HEOvIh=B0BUImu zT?EpI2^ag+5oIZMXMna?fJ&n)ljaMYV)_UC@cD@;7A2gQ7he4&Xdk{u0jC;}6to}G zCgdS#@QRHPb*Ms#QaB`wKxc$S*9X<$#u;T{>C1qr2G0c{sf*nK)tJfuH< z*Mkqq;j96F88>du!t&V%27$pSJ?|0000 +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + + +#ifndef LV_ATTRIBUTE_MEM_ALIGN +#define LV_ATTRIBUTE_MEM_ALIGN +#endif +#ifndef LV_ATTRIBUTE_IMG_ICON_VERIFIED +#define LV_ATTRIBUTE_IMG_ICON_VERIFIED +#endif + +const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_IMG_ICON_VERIFIED uint8_t ICON_VERIFIED_map[] = { + 0x00, 0x00, 0x00, 0x00, /*Color of index 0*/ + 0xfe, 0xfe, 0xfe, 0xe9, /*Color of index 1*/ + + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, + 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, + 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xef, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xdf, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xbf, 0xff, 0xff, 0xfd, 0xff, 0xfb, 0xff, 0xff, 0xf0, + 0xff, 0xfb, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0xff, 0xfd, 0xff, 0xff, 0xdf, 0xfd, 0xff, 0xfd, 0xff, 0xff, 0xf0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, + 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, +}; + +const lv_img_dsc_t ICON_VERIFIED = { + .header.cf = LV_IMG_CF_INDEXED_1BIT, + .header.always_zero = 0, + .header.reserved = 0, + .header.w = 84, + .header.h = 26, + .data_size = 294, + .data = ICON_VERIFIED_map, +}; diff --git a/ports/stm32/boards/Passport/images/mono/ICON_VERIFIED__CF_INDEXED_1_BIT.png b/ports/stm32/boards/Passport/images/mono/ICON_VERIFIED__CF_INDEXED_1_BIT.png new file mode 100644 index 0000000000000000000000000000000000000000..df15cbd3b92c9214f13ec23c91a81c7eadb36fc3 GIT binary patch literal 920 zcmV;J184k+P)TM5!R8f*c*#6_`{Irvh*Vh6+RnkP4(!0ImRa03BTJ>E2n#f#D=Hnem`E zvvbmYcK4q4z8c5{Xswk=ei%WTQf>)*BO4=cMizs?U~{hW;G+T(IT1@_j~a!M7ej1u zs_=+VLu4YB$Y<$s4l}XD?)}UILJgUzNmfR_Nsky{_&*v_OPBFZC?m6wp3J5IsUsJu zcU}R8c~53@48?L+p^8v0K)Lj|28hzaMd(Dj2Zlf?w}XnqI}@D9WR8q{m;Z;- z$V-s7VjjxoV&}WIx{C{S6h=BCbS&MXB9j~7*E4m#%4sJ{mhGh(3b6z_{Fa(v__7eY zId6kPjz88ZD9oR^?hZRfJoIK4>W(R~`Ava{S?F$%QQsxx==e@|gRQpv*cdV^<3^Ie z{uVlz0yDlt#$LXw=gQaA=Q^#ZJMr{A7+HC?rh#tXk0o`aOs6`I(5>ir%;@+@MYC?d*i_l|9-TYTNl8>%Et=Y^Ljv;79C~J8Z53-HEOvIh=B0BUImu zT?EpI2^ag+5oIZMXMna?fJ&n)ljaMYV)_UC@cD@;7A2gQ7he4&Xdk{u0jC;}6to}G zCgdS#@QRHPb*Ms#QaB`wKxc$S*9X<$#u;T{>C1qr2G0c{sf*nK)tJfuH< z*Mkqq;j96F88>du!t&V%27$pSJ?|0000 Date: Tue, 30 Jan 2024 19:11:26 -0500 Subject: [PATCH 202/239] SFT-3116: moved all messaging about security words until after the pin is entered --- .../flows/show_security_words_setting_flow.py | 48 +++++++++++++++---- .../Passport/modules/pages/pin_entry_page.py | 3 +- .../Passport/modules/public_constants.py | 3 ++ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py b/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py index f1da5d757..8986ab365 100644 --- a/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py @@ -5,10 +5,13 @@ import lvgl as lv from flows import Flow -from pages import PINEntryPage, ShowSecurityWordsSettingPage +from pages import PINEntryPage, ChooserPage, ErrorPage, InfoPage import microns from common import settings -from utils import check_pin_prefix_hash +from utils import check_pin_prefix_hash, spinner_task, recolor +from public_constants import NUM_DIGITS_FOR_SECURITY_WORDS +from tasks import get_security_words_task +from styles.colors import HIGHLIGHT_TEXT_HEX class ShowSecurityWordsSettingFlow(Flow): @@ -16,24 +19,53 @@ def __init__(self): super().__init__(initial_state=self.select_setting, name='LoginFlow') async def select_setting(self): - selected_value = await ShowSecurityWordsSettingPage().show() + options = [ + {'label': 'Show at Login', 'value': True}, + {'label': 'Don\'t Show', 'value': False} + ] + initial_value = settings.get('security_words', False) + + selected_value = await ChooserPage(options=options, + initial_value=initial_value).show() + if selected_value is None: + self.set_result(True) + return + if selected_value: self.goto(self.enter_pin) else: - self.set_result(True) + settings.set('security_words', False) + self.set_result(False) async def enter_pin(self): (pin, is_done) = await PINEntryPage( card_header={'title': 'Enter PIN'}, - security_words_message='Remember these Security Words', - check_pin_prefix=True, left_micron=microns.Back, right_micron=microns.Checkmark).show() if not is_done: - settings.set('security_words', False) + self.back() + return if not check_pin_prefix_hash(pin): - settings.set('security_words', False) + await ErrorPage('Wrong PIN!').show() + self.back() + return + + if len(pin) < NUM_DIGITS_FOR_SECURITY_WORDS: + await ErrorPage('Your PIN Is Too Short!').show() + self.back() + return + + prefix = pin[:NUM_DIGITS_FOR_SECURITY_WORDS] + security_words, error = await spinner_task('Getting Security Words', + get_security_words_task, + args=[prefix]) + + message = 'Remember These Security Words:\n\n' + message += recolor(HIGHLIGHT_TEXT_HEX, ' '.join(security_words)) + + await InfoPage(message).show() + settings.set('security_words', True) self.back() diff --git a/ports/stm32/boards/Passport/modules/pages/pin_entry_page.py b/ports/stm32/boards/Passport/modules/pages/pin_entry_page.py index d37a6aac7..f1d6e85c7 100644 --- a/ports/stm32/boards/Passport/modules/pages/pin_entry_page.py +++ b/ports/stm32/boards/Passport/modules/pages/pin_entry_page.py @@ -11,6 +11,7 @@ from utils import InputMode from views import Label, View, PINInput, Icon from constants import MENU_ITEM_CORNER_RADIUS +from public_constants import NUM_DIGITS_FOR_SECURITY_WORDS from styles import Stylize from t9 import T9 import microns @@ -18,8 +19,6 @@ from common import pa NUM_ATTEMPTS_LEFT_BRICK_WARNING = 5 - -NUM_DIGITS_FOR_SECURITY_WORDS = 4 MIN_PIN_LENGTH = 6 MAX_PIN_LENGTH = 12 diff --git a/ports/stm32/boards/Passport/modules/public_constants.py b/ports/stm32/boards/Passport/modules/public_constants.py index cbffe0ada..d98ecfe8a 100644 --- a/ports/stm32/boards/Passport/modules/public_constants.py +++ b/ports/stm32/boards/Passport/modules/public_constants.py @@ -111,6 +111,9 @@ MARGIN_FOR_ADDRESSES = 0 +# Size of a pin prefix: +NUM_DIGITS_FOR_SECURITY_WORDS = const(4) + RFC_SIGNATURE_TEMPLATE = '''\ -----BEGIN {blockchain} SIGNED MESSAGE----- {msg} From 104e5108e4855d74c7b70c6ee64336817965731b Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 30 Jan 2024 20:32:04 -0500 Subject: [PATCH 203/239] SFT-3060: first pass at lvgl verified icon --- .../modules/flows/verify_address_flow.py | 7 +++--- .../Passport/modules/pages/status_page.py | 25 ++++++++++++++++--- .../Passport/modules/pages/success_page.py | 6 +++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py index d05e18791..c171ebca5 100644 --- a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py @@ -250,8 +250,7 @@ async def found(self): 'Change' if self.found_is_change == 1 else 'Receive', self.found_addr_idx) - page_class = SuccessPage if passport.IS_COLOR else LongSuccessPage - await page_class(text=msg, - margins=MARGIN_FOR_ADDRESSES, - icon=lv.ICON_VERIFIED).show() + await SuccessPage(text=msg, + margins=MARGIN_FOR_ADDRESSES, + custom_verified_icon=True).show() self.set_result(True) diff --git a/ports/stm32/boards/Passport/modules/pages/status_page.py b/ports/stm32/boards/Passport/modules/pages/status_page.py index d8cf685a0..6568cee03 100644 --- a/ports/stm32/boards/Passport/modules/pages/status_page.py +++ b/ports/stm32/boards/Passport/modules/pages/status_page.py @@ -7,7 +7,7 @@ from styles.style import Stylize from views import Arc, Label, View, Icon, Spinner from pages import Page -from styles.colors import DEFAULT_SPINNER, TEXT_GREY, SCROLLBAR_BG_COLOR +from styles.colors import DEFAULT_SPINNER, TEXT_GREY, SCROLLBAR_BG_COLOR, WHITE import microns import common import passport @@ -19,7 +19,7 @@ class StatusPage(Page): def __init__(self, text=None, icon=None, icon_color=None, show_progress=False, percent=0, centered=True, show_spinner=False, interactive=True, card_header=None, statusbar=None, left_micron=microns.Back, right_micron=microns.Forward, - margins=None): + margins=None, custom_verified_icon=False): super().__init__(card_header=card_header, statusbar=statusbar, left_micron=left_micron, @@ -31,6 +31,7 @@ def __init__(self, text=None, icon=None, icon_color=None, show_progress=False, p self.text = text self.page_idx = 0 self.icon = icon + self.custom_verified_icon = custom_verified_icon self.icon_color = icon_color self.show_progress = show_progress @@ -84,7 +85,25 @@ def update(self): self.center_container.set_size(lv.pct(100), lv.SIZE.CONTENT) self.center_content = None - if self.icon is not None: + if self.custom_verified_icon: + self.center_content = View(flex_flow=lv.FLEX_FLOW.ROW) + self.center_content.set_size(lv.pct(50), lv.SIZE.CONTENT) + with Stylize(self.center_content) as default: + default.flex_align(main=lv.FLEX_ALIGN.CENTER) + if self.icon_color is not None: + default.bg_color(self.icon_color) + default.radius(20) + self.message_icon = Icon(lv.ICON_CHECKMARK, color=WHITE) + with Stylize(self.message_icon) as default: + default.flex_align(cross=lv.FLEX_ALIGN.CENTER) + default.pad(left=4, right=2, top=8, bottom=4) + self.center_content.add_child(self.message_icon) + self.message = Label(text='Verified', color=WHITE, center=True) + with Stylize(self.message) as default: + default.flex_align(cross=lv.FLEX_ALIGN.CENTER) + default.pad(top=8, right=4, bottom=4) + self.center_content.add_child(self.message) + elif self.icon is not None: self.center_content = Icon(self.icon) self.center_content.set_size(self.icon.header.w, self.icon.header.h) self.center_content.set_no_scroll() diff --git a/ports/stm32/boards/Passport/modules/pages/success_page.py b/ports/stm32/boards/Passport/modules/pages/success_page.py index 5d3d83edb..f40b770fe 100644 --- a/ports/stm32/boards/Passport/modules/pages/success_page.py +++ b/ports/stm32/boards/Passport/modules/pages/success_page.py @@ -18,7 +18,8 @@ def __init__( left_micron=None, right_micron=microns.Checkmark, icon=lv.LARGE_ICON_SUCCESS, - margins=None): + margins=None, + custom_verified_icon=False): super().__init__( text=text, card_header=card_header, @@ -27,4 +28,5 @@ def __init__( icon_color=DEFAULT_LARGE_ICON_COLOR, left_micron=left_micron, right_micron=right_micron, - margins=margins) + margins=margins, + custom_verified_icon=custom_verified_icon) From 5d3a850fcd757b22a78e267bc350c93fee241ff2 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 30 Jan 2024 20:38:54 -0500 Subject: [PATCH 204/239] SFT-3060: fixed mono lvgl verified icon size --- ports/stm32/boards/Passport/modules/pages/status_page.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/pages/status_page.py b/ports/stm32/boards/Passport/modules/pages/status_page.py index 6568cee03..a303a867c 100644 --- a/ports/stm32/boards/Passport/modules/pages/status_page.py +++ b/ports/stm32/boards/Passport/modules/pages/status_page.py @@ -87,7 +87,8 @@ def update(self): self.center_content = None if self.custom_verified_icon: self.center_content = View(flex_flow=lv.FLEX_FLOW.ROW) - self.center_content.set_size(lv.pct(50), lv.SIZE.CONTENT) + size = 50 if passport.IS_COLOR else 55 + self.center_content.set_size(lv.pct(size), lv.SIZE.CONTENT) with Stylize(self.center_content) as default: default.flex_align(main=lv.FLEX_ALIGN.CENTER) if self.icon_color is not None: From 21a5dd0ac8c018c5e8a4afcdb793aa28bd00c5f6 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 31 Jan 2024 10:12:42 -0500 Subject: [PATCH 205/239] SFT-3060: added more padding to verified icon --- ports/stm32/boards/Passport/modules/pages/status_page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/pages/status_page.py b/ports/stm32/boards/Passport/modules/pages/status_page.py index a303a867c..4bda5b611 100644 --- a/ports/stm32/boards/Passport/modules/pages/status_page.py +++ b/ports/stm32/boards/Passport/modules/pages/status_page.py @@ -97,12 +97,12 @@ def update(self): self.message_icon = Icon(lv.ICON_CHECKMARK, color=WHITE) with Stylize(self.message_icon) as default: default.flex_align(cross=lv.FLEX_ALIGN.CENTER) - default.pad(left=4, right=2, top=8, bottom=4) + default.pad(left=6, right=2, top=8, bottom=6) self.center_content.add_child(self.message_icon) self.message = Label(text='Verified', color=WHITE, center=True) with Stylize(self.message) as default: default.flex_align(cross=lv.FLEX_ALIGN.CENTER) - default.pad(top=8, right=4, bottom=4) + default.pad(top=8, right=6, bottom=4) self.center_content.add_child(self.message) elif self.icon is not None: self.center_content = Icon(self.icon) From 97850fb606de9f17a78ec4f4f2e751958cea7a52 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 31 Jan 2024 10:39:08 -0500 Subject: [PATCH 206/239] SFT-3060: adjusted more padding in verified icon --- ports/stm32/boards/Passport/modules/pages/status_page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/pages/status_page.py b/ports/stm32/boards/Passport/modules/pages/status_page.py index 4bda5b611..84e64c84d 100644 --- a/ports/stm32/boards/Passport/modules/pages/status_page.py +++ b/ports/stm32/boards/Passport/modules/pages/status_page.py @@ -97,12 +97,12 @@ def update(self): self.message_icon = Icon(lv.ICON_CHECKMARK, color=WHITE) with Stylize(self.message_icon) as default: default.flex_align(cross=lv.FLEX_ALIGN.CENTER) - default.pad(left=6, right=2, top=8, bottom=6) + default.pad(left=7, right=-2, top=8, bottom=6) self.center_content.add_child(self.message_icon) self.message = Label(text='Verified', color=WHITE, center=True) with Stylize(self.message) as default: default.flex_align(cross=lv.FLEX_ALIGN.CENTER) - default.pad(top=8, right=6, bottom=4) + default.pad(top=9, right=7, bottom=3) self.center_content.add_child(self.message) elif self.icon is not None: self.center_content = Icon(self.icon) From 4ad721cd9c9abeea0a05638afaae4d78080ebc8e Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 31 Jan 2024 10:43:36 -0500 Subject: [PATCH 207/239] SFT-3060: removed new verified icon images --- .../Passport/images/color/ICON_VERIFIED.c | 60 ------------------ .../color/ICON_VERIFIED__CF_INDEXED_2_BIT.png | Bin 920 -> 0 bytes ports/stm32/boards/Passport/images/images.h | 2 - .../Passport/images/mono/ICON_VERIFIED.c | 59 ----------------- .../mono/ICON_VERIFIED__CF_INDEXED_1_BIT.png | Bin 920 -> 0 bytes 5 files changed, 121 deletions(-) delete mode 100644 ports/stm32/boards/Passport/images/color/ICON_VERIFIED.c delete mode 100644 ports/stm32/boards/Passport/images/color/ICON_VERIFIED__CF_INDEXED_2_BIT.png delete mode 100644 ports/stm32/boards/Passport/images/mono/ICON_VERIFIED.c delete mode 100644 ports/stm32/boards/Passport/images/mono/ICON_VERIFIED__CF_INDEXED_1_BIT.png diff --git a/ports/stm32/boards/Passport/images/color/ICON_VERIFIED.c b/ports/stm32/boards/Passport/images/color/ICON_VERIFIED.c deleted file mode 100644 index 7dc2bd375..000000000 --- a/ports/stm32/boards/Passport/images/color/ICON_VERIFIED.c +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: © 2024 Foundation Devices, Inc. -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#ifdef LV_LVGL_H_INCLUDE_SIMPLE -#include "lvgl.h" -#else -#include "lvgl/lvgl.h" -#endif - - -#ifndef LV_ATTRIBUTE_MEM_ALIGN -#define LV_ATTRIBUTE_MEM_ALIGN -#endif -#ifndef LV_ATTRIBUTE_IMG_ICON_VERIFIED -#define LV_ATTRIBUTE_IMG_ICON_VERIFIED -#endif -const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_IMG_ICON_VERIFIED uint8_t ICON_VERIFIED_map[] = { - 0x00, 0x00, 0x00, 0x00, /*Color of index 0*/ - 0xff, 0xff, 0xff, 0x2d, /*Color of index 1*/ - 0xfe, 0xfe, 0xfe, 0x98, /*Color of index 2*/ - 0xfe, 0xfe, 0xfe, 0xfd, /*Color of index 3*/ - - 0x00, 0x00, 0x6b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x00, 0x00, - 0x00, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x90, 0x00, - 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x00, - 0x01, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x40, - 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x90, - 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, - 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xbb, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xfd, - 0xbf, 0xff, 0xff, 0xff, 0xff, 0xd2, 0xff, 0xf6, 0xff, 0xdf, 0xff, 0xff, 0xf9, 0xf9, 0xa6, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xfe, - 0xbf, 0xff, 0xff, 0xff, 0xff, 0x4b, 0xff, 0xf6, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xfe, - 0xff, 0xff, 0xff, 0xff, 0xfd, 0x2f, 0xff, 0xfd, 0xff, 0x7e, 0x57, 0xf6, 0x6d, 0xd5, 0x76, 0xf9, 0x5f, 0xe5, 0x67, 0xff, 0xff, - 0xff, 0xff, 0xfb, 0xff, 0xf4, 0xbf, 0xff, 0xfd, 0xbe, 0x79, 0xb9, 0xf1, 0xb9, 0xf6, 0xf6, 0xe6, 0xe7, 0x9b, 0x97, 0xff, 0xff, - 0xff, 0xff, 0xf5, 0xbf, 0xd2, 0xff, 0xff, 0xfe, 0x7d, 0xb6, 0xf9, 0xb3, 0xf9, 0xf6, 0xf6, 0xdb, 0xe7, 0x6f, 0xe7, 0xff, 0xff, - 0xff, 0xff, 0xfd, 0x6f, 0x4b, 0xff, 0xff, 0xff, 0x69, 0xf5, 0x55, 0xb3, 0xf9, 0xf6, 0xf6, 0xc5, 0x57, 0x6f, 0xe7, 0xff, 0xff, - 0xbf, 0xff, 0xff, 0x49, 0x6f, 0xff, 0xff, 0xff, 0x96, 0xf6, 0xff, 0xf3, 0xfd, 0xf6, 0xf6, 0xdb, 0xff, 0x9f, 0xe7, 0xff, 0xfe, - 0xbf, 0xff, 0xff, 0xd1, 0xbf, 0xff, 0xff, 0xff, 0xd3, 0xfd, 0x69, 0xf3, 0xf9, 0xf6, 0xf6, 0xe1, 0x97, 0xd6, 0x57, 0xff, 0xfe, - 0x7f, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xff, 0x9b, 0xfb, 0xfe, 0xfb, 0xfb, 0xfe, 0x6f, 0xfa, 0xbb, 0xff, 0xfd, - 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, - 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, - 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd0, - 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x40, - 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x00, - 0x00, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x90, 0x00, - 0x00, 0x00, 0x6b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x00, 0x00, -}; - -const lv_img_dsc_t ICON_VERIFIED = { - .header.cf = LV_IMG_CF_INDEXED_2BIT, - .header.always_zero = 0, - .header.reserved = 0, - .header.w = 84, - .header.h = 26, - .data_size = 562, - .data = ICON_VERIFIED_map, -}; diff --git a/ports/stm32/boards/Passport/images/color/ICON_VERIFIED__CF_INDEXED_2_BIT.png b/ports/stm32/boards/Passport/images/color/ICON_VERIFIED__CF_INDEXED_2_BIT.png deleted file mode 100644 index df15cbd3b92c9214f13ec23c91a81c7eadb36fc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 920 zcmV;J184k+P)TM5!R8f*c*#6_`{Irvh*Vh6+RnkP4(!0ImRa03BTJ>E2n#f#D=Hnem`E zvvbmYcK4q4z8c5{Xswk=ei%WTQf>)*BO4=cMizs?U~{hW;G+T(IT1@_j~a!M7ej1u zs_=+VLu4YB$Y<$s4l}XD?)}UILJgUzNmfR_Nsky{_&*v_OPBFZC?m6wp3J5IsUsJu zcU}R8c~53@48?L+p^8v0K)Lj|28hzaMd(Dj2Zlf?w}XnqI}@D9WR8q{m;Z;- z$V-s7VjjxoV&}WIx{C{S6h=BCbS&MXB9j~7*E4m#%4sJ{mhGh(3b6z_{Fa(v__7eY zId6kPjz88ZD9oR^?hZRfJoIK4>W(R~`Ava{S?F$%QQsxx==e@|gRQpv*cdV^<3^Ie z{uVlz0yDlt#$LXw=gQaA=Q^#ZJMr{A7+HC?rh#tXk0o`aOs6`I(5>ir%;@+@MYC?d*i_l|9-TYTNl8>%Et=Y^Ljv;79C~J8Z53-HEOvIh=B0BUImu zT?EpI2^ag+5oIZMXMna?fJ&n)ljaMYV)_UC@cD@;7A2gQ7he4&Xdk{u0jC;}6to}G zCgdS#@QRHPb*Ms#QaB`wKxc$S*9X<$#u;T{>C1qr2G0c{sf*nK)tJfuH< z*Mkqq;j96F88>du!t&V%27$pSJ?|0000 -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#ifdef LV_LVGL_H_INCLUDE_SIMPLE -#include "lvgl.h" -#else -#include "lvgl/lvgl.h" -#endif - - -#ifndef LV_ATTRIBUTE_MEM_ALIGN -#define LV_ATTRIBUTE_MEM_ALIGN -#endif -#ifndef LV_ATTRIBUTE_IMG_ICON_VERIFIED -#define LV_ATTRIBUTE_IMG_ICON_VERIFIED -#endif - -const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_IMG_ICON_VERIFIED uint8_t ICON_VERIFIED_map[] = { - 0x00, 0x00, 0x00, 0x00, /*Color of index 0*/ - 0xfe, 0xfe, 0xfe, 0xe9, /*Color of index 1*/ - - 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, - 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, - 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, - 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, - 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, - 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, - 0xff, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, - 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, - 0xff, 0xff, 0xef, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xf0, - 0xff, 0xff, 0xdf, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xf0, - 0xff, 0xff, 0xbf, 0xff, 0xff, 0xfd, 0xff, 0xfb, 0xff, 0xff, 0xf0, - 0xff, 0xfb, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xf0, - 0xff, 0xfd, 0xff, 0xff, 0xdf, 0xfd, 0xff, 0xfd, 0xff, 0xff, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, - 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, - 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, - 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, - 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, - 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, -}; - -const lv_img_dsc_t ICON_VERIFIED = { - .header.cf = LV_IMG_CF_INDEXED_1BIT, - .header.always_zero = 0, - .header.reserved = 0, - .header.w = 84, - .header.h = 26, - .data_size = 294, - .data = ICON_VERIFIED_map, -}; diff --git a/ports/stm32/boards/Passport/images/mono/ICON_VERIFIED__CF_INDEXED_1_BIT.png b/ports/stm32/boards/Passport/images/mono/ICON_VERIFIED__CF_INDEXED_1_BIT.png deleted file mode 100644 index df15cbd3b92c9214f13ec23c91a81c7eadb36fc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 920 zcmV;J184k+P)TM5!R8f*c*#6_`{Irvh*Vh6+RnkP4(!0ImRa03BTJ>E2n#f#D=Hnem`E zvvbmYcK4q4z8c5{Xswk=ei%WTQf>)*BO4=cMizs?U~{hW;G+T(IT1@_j~a!M7ej1u zs_=+VLu4YB$Y<$s4l}XD?)}UILJgUzNmfR_Nsky{_&*v_OPBFZC?m6wp3J5IsUsJu zcU}R8c~53@48?L+p^8v0K)Lj|28hzaMd(Dj2Zlf?w}XnqI}@D9WR8q{m;Z;- z$V-s7VjjxoV&}WIx{C{S6h=BCbS&MXB9j~7*E4m#%4sJ{mhGh(3b6z_{Fa(v__7eY zId6kPjz88ZD9oR^?hZRfJoIK4>W(R~`Ava{S?F$%QQsxx==e@|gRQpv*cdV^<3^Ie z{uVlz0yDlt#$LXw=gQaA=Q^#ZJMr{A7+HC?rh#tXk0o`aOs6`I(5>ir%;@+@MYC?d*i_l|9-TYTNl8>%Et=Y^Ljv;79C~J8Z53-HEOvIh=B0BUImu zT?EpI2^ag+5oIZMXMna?fJ&n)ljaMYV)_UC@cD@;7A2gQ7he4&Xdk{u0jC;}6to}G zCgdS#@QRHPb*Ms#QaB`wKxc$S*9X<$#u;T{>C1qr2G0c{sf*nK)tJfuH< z*Mkqq;j96F88>du!t&V%27$pSJ?|0000 Date: Wed, 31 Jan 2024 11:07:48 -0500 Subject: [PATCH 208/239] SFT-3060: reorganized custom verified icon code --- ports/stm32/boards/Passport/manifest.py | 1 + .../modules/flows/verify_address_flow.py | 6 ++-- .../Passport/modules/pages/status_page.py | 27 +++----------- .../Passport/modules/pages/success_page.py | 4 +-- .../boards/Passport/modules/views/__init__.py | 1 + .../Passport/modules/views/verified_icon.py | 36 +++++++++++++++++++ 6 files changed, 48 insertions(+), 27 deletions(-) create mode 100644 ports/stm32/boards/Passport/modules/views/verified_icon.py diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index b6f285f58..b30c49e13 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -334,6 +334,7 @@ 'views/table.py', 'views/text_area.py', 'views/text_input.py', + 'views/verified_icon.py', 'views/view.py')) # Wallets diff --git a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py index c171ebca5..930629647 100644 --- a/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/verify_address_flow.py @@ -226,13 +226,13 @@ async def not_found(self): self.set_result(False) async def found(self): - from pages import SuccessPage, LongSuccessPage + from pages import SuccessPage from utils import save_next_addr, format_btc_address, stylize_address - import passport from common import settings import chains from public_constants import MARGIN_FOR_ADDRESSES import lvgl as lv + from views import VerifiedIcon # Remember where to start from next time save_next_addr(self.acct_num, @@ -252,5 +252,5 @@ async def found(self): await SuccessPage(text=msg, margins=MARGIN_FOR_ADDRESSES, - custom_verified_icon=True).show() + custom_view=VerifiedIcon).show() self.set_result(True) diff --git a/ports/stm32/boards/Passport/modules/pages/status_page.py b/ports/stm32/boards/Passport/modules/pages/status_page.py index 84e64c84d..01fcd8391 100644 --- a/ports/stm32/boards/Passport/modules/pages/status_page.py +++ b/ports/stm32/boards/Passport/modules/pages/status_page.py @@ -5,7 +5,7 @@ import lvgl as lv from styles.style import Stylize -from views import Arc, Label, View, Icon, Spinner +from views import Arc, Label, View, Icon, Spinner, VerifiedIcon from pages import Page from styles.colors import DEFAULT_SPINNER, TEXT_GREY, SCROLLBAR_BG_COLOR, WHITE import microns @@ -19,7 +19,7 @@ class StatusPage(Page): def __init__(self, text=None, icon=None, icon_color=None, show_progress=False, percent=0, centered=True, show_spinner=False, interactive=True, card_header=None, statusbar=None, left_micron=microns.Back, right_micron=microns.Forward, - margins=None, custom_verified_icon=False): + margins=None, custom_view=None): super().__init__(card_header=card_header, statusbar=statusbar, left_micron=left_micron, @@ -31,7 +31,7 @@ def __init__(self, text=None, icon=None, icon_color=None, show_progress=False, p self.text = text self.page_idx = 0 self.icon = icon - self.custom_verified_icon = custom_verified_icon + self.custom_view = custom_view self.icon_color = icon_color self.show_progress = show_progress @@ -85,25 +85,8 @@ def update(self): self.center_container.set_size(lv.pct(100), lv.SIZE.CONTENT) self.center_content = None - if self.custom_verified_icon: - self.center_content = View(flex_flow=lv.FLEX_FLOW.ROW) - size = 50 if passport.IS_COLOR else 55 - self.center_content.set_size(lv.pct(size), lv.SIZE.CONTENT) - with Stylize(self.center_content) as default: - default.flex_align(main=lv.FLEX_ALIGN.CENTER) - if self.icon_color is not None: - default.bg_color(self.icon_color) - default.radius(20) - self.message_icon = Icon(lv.ICON_CHECKMARK, color=WHITE) - with Stylize(self.message_icon) as default: - default.flex_align(cross=lv.FLEX_ALIGN.CENTER) - default.pad(left=7, right=-2, top=8, bottom=6) - self.center_content.add_child(self.message_icon) - self.message = Label(text='Verified', color=WHITE, center=True) - with Stylize(self.message) as default: - default.flex_align(cross=lv.FLEX_ALIGN.CENTER) - default.pad(top=9, right=7, bottom=3) - self.center_content.add_child(self.message) + if self.custom_view: + self.center_content = self.custom_view() elif self.icon is not None: self.center_content = Icon(self.icon) self.center_content.set_size(self.icon.header.w, self.icon.header.h) diff --git a/ports/stm32/boards/Passport/modules/pages/success_page.py b/ports/stm32/boards/Passport/modules/pages/success_page.py index f40b770fe..7716c7532 100644 --- a/ports/stm32/boards/Passport/modules/pages/success_page.py +++ b/ports/stm32/boards/Passport/modules/pages/success_page.py @@ -19,7 +19,7 @@ def __init__( right_micron=microns.Checkmark, icon=lv.LARGE_ICON_SUCCESS, margins=None, - custom_verified_icon=False): + custom_view=None): super().__init__( text=text, card_header=card_header, @@ -29,4 +29,4 @@ def __init__( left_micron=left_micron, right_micron=right_micron, margins=margins, - custom_verified_icon=custom_verified_icon) + custom_view=custom_view) diff --git a/ports/stm32/boards/Passport/modules/views/__init__.py b/ports/stm32/boards/Passport/modules/views/__init__.py index 58a6d6acb..3d86f87ee 100644 --- a/ports/stm32/boards/Passport/modules/views/__init__.py +++ b/ports/stm32/boards/Passport/modules/views/__init__.py @@ -37,3 +37,4 @@ from .pin_input import * from .symbol_picker import * from .qrcode import * +from .verified_icon import * diff --git a/ports/stm32/boards/Passport/modules/views/verified_icon.py b/ports/stm32/boards/Passport/modules/views/verified_icon.py new file mode 100644 index 000000000..56bc05bef --- /dev/null +++ b/ports/stm32/boards/Passport/modules/views/verified_icon.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: © 2024 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later +# +# verified_icon.py - custom icon for address verification + + +import lvgl as lv +from views import View, Icon, Label +from styles.style import Stylize +from styles.colors import WHITE, DEFAULT_LARGE_ICON_COLOR +import passport + + +class VerifiedIcon(View): + def __init__(self, icon_color=DEFAULT_LARGE_ICON_COLOR): + + self.icon_color = icon_color + super().__init__(flex_flow=lv.FLEX_FLOW.ROW) + + size = 50 if passport.IS_COLOR else 55 + self.set_size(lv.pct(size), lv.SIZE.CONTENT) + with Stylize(self) as default: + default.flex_align(main=lv.FLEX_ALIGN.CENTER) + if self.icon_color is not None: + default.bg_color(self.icon_color) + default.radius(20) + self.message_icon = Icon(lv.ICON_CHECKMARK, color=WHITE) + with Stylize(self.message_icon) as default: + default.flex_align(cross=lv.FLEX_ALIGN.CENTER) + default.pad(left=7, right=-2, top=8, bottom=6) + self.add_child(self.message_icon) + self.message = Label(text='Verified', color=WHITE, center=True) + with Stylize(self.message) as default: + default.flex_align(cross=lv.FLEX_ALIGN.CENTER) + default.pad(top=9, right=7, bottom=3) + self.add_child(self.message) From ef64560d0b65c57a357edaac6fc2a88364992ef8 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 31 Jan 2024 13:35:40 -0500 Subject: [PATCH 209/239] SFT-3116: removed unused security words setting page --- ports/stm32/boards/Passport/manifest.py | 1 - .../boards/Passport/modules/pages/__init__.py | 1 - .../pages/show_security_words_setting_page.py | 29 ------------------- 3 files changed, 31 deletions(-) delete mode 100644 ports/stm32/boards/Passport/modules/pages/show_security_words_setting_page.py diff --git a/ports/stm32/boards/Passport/manifest.py b/ports/stm32/boards/Passport/manifest.py index b6f285f58..07243f50d 100644 --- a/ports/stm32/boards/Passport/manifest.py +++ b/ports/stm32/boards/Passport/manifest.py @@ -206,7 +206,6 @@ 'pages/brightness_setting_page.py', 'pages/chain_setting_page.py', 'pages/multisig_policy_setting_page.py', - 'pages/show_security_words_setting_page.py', 'pages/units_setting_page.py', # Chooser pages diff --git a/ports/stm32/boards/Passport/modules/pages/__init__.py b/ports/stm32/boards/Passport/modules/pages/__init__.py index c3db4d37a..4d0c5e4a2 100644 --- a/ports/stm32/boards/Passport/modules/pages/__init__.py +++ b/ports/stm32/boards/Passport/modules/pages/__init__.py @@ -39,7 +39,6 @@ from .seed_words_list_page import * from .setup_mode_chooser_page import * from .shield_page import * -from .show_security_words_setting_page import * from .show_qr_page import * from .shutdown_page import * from .singlesig_multisig_chooser_page import * diff --git a/ports/stm32/boards/Passport/modules/pages/show_security_words_setting_page.py b/ports/stm32/boards/Passport/modules/pages/show_security_words_setting_page.py deleted file mode 100644 index 944c82084..000000000 --- a/ports/stm32/boards/Passport/modules/pages/show_security_words_setting_page.py +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-FileCopyrightText: © 2022 Foundation Devices, Inc. -# SPDX-License-Identifier: GPL-3.0-or-later -# -# show_security_words_setting_page.py - Chooser to decide to show security words at login or not. - - -import lvgl as lv -from pages import SettingPage - - -class ShowSecurityWordsSettingPage(SettingPage): - - OPTIONS = [ - {'label': 'Show at Login', 'value': True}, - {'label': 'Don\'t Show', 'value': False} - ] - - def __init__(self, card_header=None, statusbar=None): - super().__init__( - card_header=card_header, - statusbar=statusbar, - options=self.OPTIONS, - setting_name='security_words', - default_value=self.OPTIONS[1].get('value'), - on_change=self.on_change - ) - - def on_change(self, selected_value): - self.set_result(selected_value) From b022222a1ebd47215f5e3f38afb81000af7d2f18 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 19 Oct 2023 20:23:17 -0400 Subject: [PATCH 210/239] SFT-2862: found entry points for taproot signing code --- ports/stm32/boards/Passport/modules/psbt.py | 15 ++++++++++++++- .../boards/Passport/modules/serializations.py | 5 +++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 51ae0fd51..56a8b6012 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -432,6 +432,9 @@ def validate(self, out_idx, txo, my_xfp, active_multisig): # input is hash160 of a single public key assert len(addr_or_pubkey) == 20 expect_pkh = hash160(expect_pubkey) + elif addr_type == 'p2tr': + # TODO: implement handling + assert(True) else: # we don't know how to "solve" this type of input return @@ -459,7 +462,7 @@ class psbtInputProxy(psbtProxy): 'utxo', 'witness_utxo', 'sighash', 'redeem_script', 'witness_script', 'fully_signed', 'is_segwit', 'is_multisig', 'is_p2sh', 'num_our_keys', - 'required_key', 'scriptSig', 'amount', 'scriptCode', 'added_sig') + 'required_key', 'scriptSig', 'amount', 'scriptCode', 'added_sig', 'is_taproot') def __init__(self, fd, idx): super().__init__() @@ -482,6 +485,7 @@ def __init__(self, fd, idx): # self.is_segwit = None # self.is_multisig = None # self.is_p2sh = False + # self.is_taproot = None # self.required_key = None # which of our keys will be used to sign input # self.scriptSig = None @@ -615,6 +619,7 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): self.is_p2sh = False which_key = None + # TODO: add taproot to utxo.get_address() addr_type, addr_or_pubkey, addr_is_segwit = utxo.get_address() if addr_is_segwit and not self.is_segwit: self.is_segwit = True @@ -685,6 +690,11 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): if addr_or_pubkey in self.subpaths: which_key = addr_or_pubkey + elif addr_type == 'p2tr': + self.is_taproot = True + # TODO: implement taproot handling + # Should be similar to p2pkh, but also need to check for scripts + else: # we don't know how to "solve" this type of input pass @@ -735,6 +745,9 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): # assert not self.is_multisig self.scriptCode = b'\x19\x76\xa9\x14' + addr + b'\x88\xac' + if addr_type == 'p2tr': + # TODO: add implementation + assert(True) elif not self.scriptCode: # Segwit P2SH. We need the witness script to be provided. if not self.witness_script: diff --git a/ports/stm32/boards/Passport/modules/serializations.py b/ports/stm32/boards/Passport/modules/serializations.py index 8429feaf6..2c43bd310 100644 --- a/ports/stm32/boards/Passport/modules/serializations.py +++ b/ports/stm32/boards/Passport/modules/serializations.py @@ -343,6 +343,11 @@ def get_address(self): # aka. P2WSH return 'p2sh', self.scriptPubKey[2:2 + 32], True + if len(self.scriptPubKey) == 34 and \ + self.scriptPubKey[0] == 81 and self.scriptPubKey[1] == 32: + # aka P2TR + return 'p2tr', self.scriptPubKey[2:2 + 32], True + if self.is_p2pkh(): return 'p2pkh', self.scriptPubKey[3:3 + 20], False From 9fe930b4a2c261a5c7d44405aa047668ff708116 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 19 Oct 2023 20:31:00 -0400 Subject: [PATCH 211/239] SFT-2862: need to add taproot sighashes --- ports/stm32/boards/Passport/modules/psbt.py | 1 + ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 56a8b6012..4383405b6 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -1482,6 +1482,7 @@ def make_txn_sighash(self, replace_idx, replacement, sighash_type): # double SHA256 return trezorcrypto.sha256(rv.digest()).digest() + # TODO: add taproot sighashes def make_txn_segwit_sighash(self, replace_idx, replacement, amount, scriptCode, sighash_type): # Implement BIP 143 hashing algo for signature of segwit programs. # see diff --git a/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py b/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py index b47b18395..17029fea0 100644 --- a/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py @@ -18,6 +18,7 @@ async def sign_psbt_task(on_done, psbt): import trezorcrypto import stash import gc + from foundation import secp256k1 try: with stash.SensitiveValues() as sv: @@ -101,7 +102,10 @@ async def sign_psbt_task(on_done, psbt): # print(" digest %s" % b2a_hex(digest).decode('ascii')) # Do the ACTUAL signature ... finally!!! - result = trezorcrypto.secp256k1.sign(pk, digest) + if inp.is_taproot: + result = secp256k1.schnorr_sign(digest, pk) + else: + result = trezorcrypto.secp256k1.sign(pk, digest) # private key no longer required stash.blank_object(pk) From ddcc84a60d1986aa466eed8a418c1b6bb9506351 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 10 Jan 2024 20:33:33 -0500 Subject: [PATCH 212/239] SFT-2862: first pass parsing PSBTs with taproot inputs, sees all taproot inputs as already signed --- ports/stm32/boards/Passport/modules/psbt.py | 70 +++++++++++++++---- .../Passport/modules/public_constants.py | 9 +++ .../boards/Passport/modules/serializations.py | 31 ++++++-- 3 files changed, 89 insertions(+), 21 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 4383405b6..4e0a06eda 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -20,8 +20,8 @@ from public_constants import MAX_SIGNERS from multisig_wallet import MultisigWallet, disassemble_multisig_mn from exceptions import FatalPSBTIssue, FraudulentChangeOutput -from serializations import ser_compact_size, deser_compact_size, hash160 -from serializations import CTxIn, CTxInWitness, CTxOut, SIGHASH_ALL +from serializations import ser_compact_size, deser_compact_size, hash160, deser_compact_size_bytes +from serializations import CTxIn, CTxInWitness, CTxOut, SIGHASH_ALL, VALID_SIGHASHES from serializations import ser_push_data, uint256_from_bytes from serializations import ser_string @@ -30,7 +30,7 @@ PSBT_IN_PARTIAL_SIG, PSBT_IN_SIGHASH_TYPE, PSBT_IN_REDEEM_SCRIPT, PSBT_IN_WITNESS_SCRIPT, PSBT_IN_BIP32_DERIVATION, PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT, - PSBT_OUT_BIP32_DERIVATION, MAX_PATH_DEPTH + PSBT_OUT_BIP32_DERIVATION, MAX_PATH_DEPTH, PSBT_IN_TAP_BIP32_DERIVATION, PSBT_OUT_TAP_BIP32_DERIVATION ) # print some things @@ -140,7 +140,7 @@ class psbtProxy: no_keys = () # these fields will return None but are not stored unless a value is set - blank_flds = ('unknown', ) + blank_flds = ('unknown', 'subpaths', 'tap_subpaths') def __init__(self): self.fd = None @@ -225,7 +225,7 @@ def parse_subpaths(self, my_xfp): # - creates dictionary: pubkey => [xfp, *path] # - will be single entry for non-p2sh ins and outs - if not self.subpaths: + if not self.subpaths and not self.tap_subpaths: return 0 if self.num_our_keys is not None: @@ -259,6 +259,35 @@ def parse_subpaths(self, my_xfp): # or an input we're not supposed to be able to sign... and that's okay. pass + for pk in self.tap_subpaths: + assert len(pk) == 32, "taproot hdpath pubkey len" + + vl = self.tap_subpaths[pk][1] + + # parse leaf hashes and path + v = self.get(self.tap_subpaths[pk]) + (num_tap_hashes, compact_length) = deser_compact_size_bytes(v) + tap_hashes = [uint256_from_bytes(v[i * 32:(i + 1) * 32]) for i in range(0, num_tap_hashes)] + v = v[(num_tap_hashes * 32 + compact_length):] + vl = len(v) + + # force them to use a derived key, never the master + assert vl >= 8, 'too short key path' + assert (vl % 4) == 0, 'corrupt key path' + assert (vl // 4) <= MAX_PATH_DEPTH, 'too deep' + + here = list(unpack_from('<%dI' % (vl // 4), v)) + + # update in place + self.tap_subpaths[pk] = (here, tap_hashes) + + if here[0] == my_xfp or here[0] == swab32(my_xfp): + num_ours += 1 + else: + # Address that isn't based on my seed; might be another leg in a p2sh, + # or an input we're not supposed to be able to sign... and that's okay. + pass + self.num_our_keys = num_ours return num_ours @@ -268,7 +297,7 @@ def parse_subpaths(self, my_xfp): class psbtOutputProxy(psbtProxy): no_keys = {PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT} blank_flds = ('unknown', 'subpaths', 'redeem_script', 'witness_script', - 'is_change', 'num_our_keys') + 'is_change', 'num_our_keys', 'tap_subpaths') def __init__(self, fd, idx): super().__init__() @@ -292,6 +321,10 @@ def store(self, kt, key, val): self.redeem_script = val elif kt == PSBT_OUT_WITNESS_SCRIPT: self.witness_script = val + elif kt == PSBT_OUT_TAP_BIP32_DERIVATION: + if not self.tap_subpaths: + self.tap_subpaths = {} + self.tap_subpaths[key[1:]] = val else: self.unknown[key] = val @@ -333,11 +366,12 @@ def validate(self, out_idx, txo, my_xfp, active_multisig): return # - must match expected address for this output, coming from unsigned txn - addr_type, addr_or_pubkey, is_segwit = txo.get_address() + addr_type, addr_or_pubkey, is_segwit, is_taproot = txo.get_address() if len(self.subpaths) == 1: # p2pk, p2pkh, p2wpkh cases expect_pubkey, = self.subpaths.keys() + # TODO: add case for p2tr else: # p2wsh/p2sh cases need full set of pubkeys, and therefore redeem script expect_pubkey = None @@ -472,6 +506,7 @@ def __init__(self, fd, idx): self.part_sig = {} # self.sighash = None self.subpaths = {} # will typically be non-empty for all inputs + self.tap_subpaths = {} # self.redeem_script = None # self.witness_script = None @@ -514,7 +549,7 @@ def validate(self, idx, txin, my_xfp): # sighash, but we're probably going to ignore anyway. self.sighash = SIGHASH_ALL if self.sighash is None else self.sighash - if self.sighash != SIGHASH_ALL: + if self.sighash not in VALID_SIGHASHES: # - someday we will expand to other types, but not yet raise FatalPSBTIssue('Can only do SIGHASH_ALL') @@ -524,7 +559,8 @@ def validate(self, idx, txin, my_xfp): # - seems harmless if they fool us into thinking already signed; we do nothing # - could also look at pubkey needed vs. sig provided # - could consider structure of MofN in p2sh cases - self.fully_signed = (len(self.part_sig) >= len(self.subpaths)) + num_subpaths = len(self.tap_subpaths) if len(self.tap_subpaths) > 0 else len(self.subpaths) + self.fully_signed = len(self.part_sig) >= num_subpaths else: # No signatures at all yet for this input (typical non multisig) self.fully_signed = False @@ -608,7 +644,7 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): self.amount = utxo.nValue - if not self.subpaths or self.fully_signed: + if not (self.subpaths or self.tap_subpaths) or self.fully_signed: # without xfp+path we will not be able to sign this input # - okay if fully signed # - okay if payjoin or other multi-signer (not multisig) txn @@ -617,13 +653,16 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): self.is_multisig = False self.is_p2sh = False + self.is_p2tr = False which_key = None - # TODO: add taproot to utxo.get_address() - addr_type, addr_or_pubkey, addr_is_segwit = utxo.get_address() + addr_type, addr_or_pubkey, addr_is_segwit, addr_is_taproot = utxo.get_address() if addr_is_segwit and not self.is_segwit: self.is_segwit = True + if addr_is_taproot: + self.is_p2tr = True + if addr_type == 'p2sh': # multisig input self.is_p2sh = True @@ -777,6 +816,8 @@ def store(self, kt, key, val): self.witness_script = val elif kt == PSBT_IN_SIGHASH_TYPE: self.sighash = unpack(' Date: Fri, 12 Jan 2024 15:07:24 -0500 Subject: [PATCH 213/239] SFT-2862: debugging taproot inputs appearing as "already signed" --- ports/stm32/boards/Passport/modules/psbt.py | 30 ++++++++++++++----- .../boards/Passport/modules/serializations.py | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 4e0a06eda..6f1ea61d3 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -24,6 +24,7 @@ from serializations import CTxIn, CTxInWitness, CTxOut, SIGHASH_ALL, VALID_SIGHASHES from serializations import ser_push_data, uint256_from_bytes from serializations import ser_string +from ubinascii import hexlify as b2a_hex from public_constants import ( PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_XPUB, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, @@ -643,11 +644,13 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): # - also validates redeem_script when present self.amount = utxo.nValue + print("amount: {}".format(self.amount)) - if not (self.subpaths or self.tap_subpaths) or self.fully_signed: + if (not self.subpaths and not self.tap_subpaths) or self.fully_signed: # without xfp+path we will not be able to sign this input # - okay if fully signed # - okay if payjoin or other multi-signer (not multisig) txn + print("required_key = None") self.required_key = None return @@ -660,7 +663,7 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): if addr_is_segwit and not self.is_segwit: self.is_segwit = True - if addr_is_taproot: + if addr_is_taproot and not self.is_p2tr: self.is_p2tr = True if addr_type == 'p2sh': @@ -730,10 +733,17 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): which_key = addr_or_pubkey elif addr_type == 'p2tr': - self.is_taproot = True - # TODO: implement taproot handling - # Should be similar to p2pkh, but also need to check for scripts + # input is an x-only public key or merkle tree root + # TODO: handle tapscript tree + self.scriptSig = utxo.scriptPubKey + + print("addr_or_pubkey: {}".format(b2a_hex(addr_or_pubkey))) + for n, tap_subpath in enumerate(self.tap_subpaths): + print("tap_subpath[n]: {}".format(b2a_hex(tap_subpath))) + + if addr_or_pubkey in self.tap_subpaths: + which_key = addr_or_pubkey else: # we don't know how to "solve" this type of input pass @@ -784,9 +794,8 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): # assert not self.is_multisig self.scriptCode = b'\x19\x76\xa9\x14' + addr + b'\x88\xac' - if addr_type == 'p2tr': - # TODO: add implementation - assert(True) + elif addr_type == 'p2tr': + pass elif not self.scriptCode: # Segwit P2SH. We need the witness script to be provided. if not self.witness_script: @@ -1353,9 +1362,14 @@ def consider_inputs(self): # We should know pubkey required for each input now. # - but we may not be the signer for those inputs, which is fine. # - TODO: but what if not SIGHASH_ALL + for n, inp in enumerate(self.inputs): + print("n: {}, inp.fully_signed: {}".format(n, inp.fully_signed)) + no_keys = set(n for n, inp in enumerate(self.inputs) if inp.required_key is None and not inp.fully_signed) + print("no_keys: {}".format(no_keys)) + gc.collect() if no_keys: diff --git a/ports/stm32/boards/Passport/modules/serializations.py b/ports/stm32/boards/Passport/modules/serializations.py index be150b669..9fff7302a 100644 --- a/ports/stm32/boards/Passport/modules/serializations.py +++ b/ports/stm32/boards/Passport/modules/serializations.py @@ -363,7 +363,7 @@ def get_address(self): if len(self.scriptPubKey) == 34 and \ self.scriptPubKey[0] == 81 and self.scriptPubKey[1] == 32: - return 'p2tr', self.scriptPubKey[2:2 + 32], False, True + return 'p2tr', self.scriptPubKey[2:2 + 32], True, True if self.is_p2pkh(): return 'p2pkh', self.scriptPubKey[3:3 + 20], False, False From 4a60c88debb1ca7f1f3d9ca8ab9453b8f6c2e01f Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 16 Jan 2024 18:41:16 -0500 Subject: [PATCH 214/239] SFT-2862: identifying owned keys in taproot inputs, parsing all taproot psbt fields --- ports/stm32/boards/Passport/modules/psbt.py | 43 +++++++++++++++------ 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 6f1ea61d3..978fe452b 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -25,13 +25,17 @@ from serializations import ser_push_data, uint256_from_bytes from serializations import ser_string from ubinascii import hexlify as b2a_hex +from taproot import output_script from public_constants import ( PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_XPUB, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, PSBT_IN_PARTIAL_SIG, PSBT_IN_SIGHASH_TYPE, PSBT_IN_REDEEM_SCRIPT, PSBT_IN_WITNESS_SCRIPT, PSBT_IN_BIP32_DERIVATION, PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT, - PSBT_OUT_BIP32_DERIVATION, MAX_PATH_DEPTH, PSBT_IN_TAP_BIP32_DERIVATION, PSBT_OUT_TAP_BIP32_DERIVATION + PSBT_OUT_BIP32_DERIVATION, MAX_PATH_DEPTH, PSBT_IN_TAP_BIP32_DERIVATION, + PSBT_OUT_TAP_BIP32_DERIVATION, PSBT_IN_TAP_KEY_SIG, PSBT_IN_TAP_SCRIPT_SIG, + PSBT_IN_TAP_LEAF_SCRIPT, PSBT_IN_TAP_INTERNAL_KEY, PSBT_IN_TAP_MERKLE_ROOT, + PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE, PSBT_OUT_TAP_BIP32_DERIVATION ) # print some things @@ -272,8 +276,9 @@ def parse_subpaths(self, my_xfp): v = v[(num_tap_hashes * 32 + compact_length):] vl = len(v) - # force them to use a derived key, never the master - assert vl >= 8, 'too short key path' + # Master key can be used if there is no tapscript tree + if tap_hashes: + assert vl >= 8, 'too short key path' assert (vl % 4) == 0, 'corrupt key path' assert (vl // 4) <= MAX_PATH_DEPTH, 'too deep' @@ -491,13 +496,15 @@ class psbtInputProxy(psbtProxy): # only part-sigs have a key to be stored. no_keys = {PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, PSBT_IN_SIGHASH_TYPE, PSBT_IN_REDEEM_SCRIPT, PSBT_IN_WITNESS_SCRIPT, PSBT_IN_FINAL_SCRIPTSIG, - PSBT_IN_FINAL_SCRIPTWITNESS} + PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_IN_TAP_KEY_SIG, PSBT_IN_TAP_INTERNAL_KEY, + PSBT_IN_TAP_MERKLE_ROOT} blank_flds = ('unknown', 'utxo', 'witness_utxo', 'sighash', 'redeem_script', 'witness_script', 'fully_signed', 'is_segwit', 'is_multisig', 'is_p2sh', 'num_our_keys', - 'required_key', 'scriptSig', 'amount', 'scriptCode', 'added_sig', 'is_taproot') + 'required_key', 'scriptSig', 'amount', 'scriptCode', 'added_sig', + 'tap_internal_key', 'tap_key_sig', 'tap_merkle_root') def __init__(self, fd, idx): super().__init__() @@ -508,6 +515,8 @@ def __init__(self, fd, idx): # self.sighash = None self.subpaths = {} # will typically be non-empty for all inputs self.tap_subpaths = {} + self.tap_script_sigs = {} + self.tap_leaf_scripts = {} # self.redeem_script = None # self.witness_script = None @@ -521,7 +530,6 @@ def __init__(self, fd, idx): # self.is_segwit = None # self.is_multisig = None # self.is_p2sh = False - # self.is_taproot = None # self.required_key = None # which of our keys will be used to sign input # self.scriptSig = None @@ -739,11 +747,13 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): print("addr_or_pubkey: {}".format(b2a_hex(addr_or_pubkey))) - for n, tap_subpath in enumerate(self.tap_subpaths): - print("tap_subpath[n]: {}".format(b2a_hex(tap_subpath))) - - if addr_or_pubkey in self.tap_subpaths: - which_key = addr_or_pubkey + if len(self.tap_subpaths) == 1: # No script path + subpath = list(self.tap_subpaths.items())[0] + pubkey, (path, tap_hashes) = subpath + tweaked_pubkey = output_script(pubkey, None)[2:] + print("tweaked_pubkey: {}".format(b2a_hex(tweaked_pubkey))) + if path[0] == my_xfp and tweaked_pubkey == addr_or_pubkey: + which_key = pubkey else: # we don't know how to "solve" this type of input pass @@ -825,8 +835,18 @@ def store(self, kt, key, val): self.witness_script = val elif kt == PSBT_IN_SIGHASH_TYPE: self.sighash = unpack(' Date: Thu, 18 Jan 2024 11:33:54 -0500 Subject: [PATCH 215/239] SFT-2862: removed unneeded is_taproot return value --- ports/stm32/boards/Passport/modules/psbt.py | 2 +- .../stm32/boards/Passport/modules/serializations.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 978fe452b..4a848cc8f 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -372,7 +372,7 @@ def validate(self, out_idx, txo, my_xfp, active_multisig): return # - must match expected address for this output, coming from unsigned txn - addr_type, addr_or_pubkey, is_segwit, is_taproot = txo.get_address() + addr_type, addr_or_pubkey, is_segwit = txo.get_address() if len(self.subpaths) == 1: # p2pk, p2pkh, p2wpkh cases diff --git a/ports/stm32/boards/Passport/modules/serializations.py b/ports/stm32/boards/Passport/modules/serializations.py index 9fff7302a..cdf8ccdd3 100644 --- a/ports/stm32/boards/Passport/modules/serializations.py +++ b/ports/stm32/boards/Passport/modules/serializations.py @@ -354,26 +354,26 @@ def get_address(self): if len(self.scriptPubKey) == 22 and \ self.scriptPubKey[0] == 0 and self.scriptPubKey[1] == 20: # aka. P2WPKH - return 'p2pkh', self.scriptPubKey[2:2 + 20], True, False + return 'p2pkh', self.scriptPubKey[2:2 + 20], True if len(self.scriptPubKey) == 34 and \ self.scriptPubKey[0] == 0 and self.scriptPubKey[1] == 32: # aka. P2WSH - return 'p2sh', self.scriptPubKey[2:2 + 32], True, False + return 'p2sh', self.scriptPubKey[2:2 + 32], True if len(self.scriptPubKey) == 34 and \ self.scriptPubKey[0] == 81 and self.scriptPubKey[1] == 32: - return 'p2tr', self.scriptPubKey[2:2 + 32], True, True + return 'p2tr', self.scriptPubKey[2:2 + 32], True if self.is_p2pkh(): - return 'p2pkh', self.scriptPubKey[3:3 + 20], False, False + return 'p2pkh', self.scriptPubKey[3:3 + 20], False if self.is_p2sh(): - return 'p2sh', self.scriptPubKey[2:2 + 20], False, False + return 'p2sh', self.scriptPubKey[2:2 + 20], False if self.is_p2pk(): # rare, pay to full pubkey - return 'p2pk', self.scriptPubKey[2:2 + 33], False, False + return 'p2pk', self.scriptPubKey[2:2 + 33], False # If this is reached, we do not understand the output well # enough to allow the user to authorize the spend, so fail hard. From e2f023f243c76e4c2678d7e382dd58beb9951868 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 23 Jan 2024 21:34:11 -0500 Subject: [PATCH 216/239] SFT-2862: fixed return value count mismatch --- ports/stm32/boards/Passport/modules/psbt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 4a848cc8f..b334819e0 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -664,16 +664,12 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): self.is_multisig = False self.is_p2sh = False - self.is_p2tr = False which_key = None - addr_type, addr_or_pubkey, addr_is_segwit, addr_is_taproot = utxo.get_address() + addr_type, addr_or_pubkey, addr_is_segwit = utxo.get_address() if addr_is_segwit and not self.is_segwit: self.is_segwit = True - if addr_is_taproot and not self.is_p2tr: - self.is_p2tr = True - if addr_type == 'p2sh': # multisig input self.is_p2sh = True From 619528dcfb850b33ca4247d88637204dd9c2517c Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 23 Jan 2024 22:03:10 -0500 Subject: [PATCH 217/239] SFT-2862: first pass of taproot signing for key-only spends --- ports/stm32/boards/Passport/modules/psbt.py | 80 ++++++++++++++++-- .../boards/Passport/modules/serializations.py | 4 +- .../stm32/boards/Passport/modules/taproot.py | 83 ++++++++++++------- .../Passport/modules/tasks/sign_psbt_task.py | 67 +++++++++------ 4 files changed, 171 insertions(+), 63 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index b334819e0..999377837 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -21,11 +21,11 @@ from multisig_wallet import MultisigWallet, disassemble_multisig_mn from exceptions import FatalPSBTIssue, FraudulentChangeOutput from serializations import ser_compact_size, deser_compact_size, hash160, deser_compact_size_bytes -from serializations import CTxIn, CTxInWitness, CTxOut, SIGHASH_ALL, VALID_SIGHASHES +from serializations import CTxIn, CTxInWitness, CTxOut, SIGHASH_ALL, VALID_SIGHASHES, SIGHASH_DEFAULT from serializations import ser_push_data, uint256_from_bytes from serializations import ser_string from ubinascii import hexlify as b2a_hex -from taproot import output_script +from taproot import output_script, tagged_hash from public_constants import ( PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_XPUB, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, @@ -504,7 +504,7 @@ class psbtInputProxy(psbtProxy): 'redeem_script', 'witness_script', 'fully_signed', 'is_segwit', 'is_multisig', 'is_p2sh', 'num_our_keys', 'required_key', 'scriptSig', 'amount', 'scriptCode', 'added_sig', - 'tap_internal_key', 'tap_key_sig', 'tap_merkle_root') + 'tap_internal_key', 'tap_key_sig', 'tap_merkle_root', 'tap_sig') def __init__(self, fd, idx): super().__init__() @@ -538,6 +538,7 @@ def __init__(self, fd, idx): # after signing, we'll have a signature to add to output PSBT # self.added_sig = None + # self.tap_sig = None self.parse(fd) gc.collect() @@ -919,6 +920,10 @@ def __init__(self): self.hashSequence = None self.hashOutputs = None + # taproot additions to reused segwit hashes + self.hashAmounts = None + self.hashScriptPubkeys = None + # this points to a MS wallet, during operation # - we are only supporting a single multisig wallet during signing self.active_multisig = None @@ -1547,7 +1552,6 @@ def make_txn_sighash(self, replace_idx, replacement, sighash_type): assert sighash_type in VALID_SIGHASHES # "only SIGHASH_ALL supported" # SIGHASH_ALL==1 value - # TODO: use correct sighash for taproot rv.update(b'\x01\x00\x00\x00') fd.seek(old_pos) @@ -1555,7 +1559,6 @@ def make_txn_sighash(self, replace_idx, replacement, sighash_type): # double SHA256 return trezorcrypto.sha256(rv.digest()).digest() - # TODO: add taproot sighashes def make_txn_segwit_sighash(self, replace_idx, replacement, amount, scriptCode, sighash_type): # Implement BIP 143 hashing algo for signature of segwit programs. # see @@ -1564,7 +1567,7 @@ def make_txn_segwit_sighash(self, replace_idx, replacement, amount, scriptCode, fd = self.fd old_pos = fd.tell() - assert sighash_type in VALID_SIGHASHES # add support for others here + assert sighash_type == SIGHASH_ALL # add support for others here if self.hashPrevouts is None: # First time thru, we'll need to hash up this stuff. @@ -1622,6 +1625,71 @@ def make_txn_segwit_sighash(self, replace_idx, replacement, amount, scriptCode, # double SHA256 return trezorcrypto.sha256(rv.digest()).digest() + def make_txn_taproot_sighash(self, input_idx, sighash_type, annex=None, ext_flag=0): + # Implement BIP 341 hashing algo for signature of segwit programs. + # see + + fd = self.fd + old_pos = fd.tell() + + assert sighash_type == SIGHASH_DEFAULT + + if self.hashPrevouts is None: + # First time thru, we'll need to hash up this stuff. + + prevouts = trezorcrypto.sha256() + amounts = trezorcrypto.sha256() + script_pubkeys = trezorcrypto.sha256() + sequences = trezorcrypto.sha256() + + # input side + for in_idx, txi in self.input_iter(): + utxo = self.inputs[in_idx].get_utxo(txi.prevout.n) + prevouts.update(txi.prevout.serialize()) + amounts.update(pack(" + + fd.seek(old_pos) + return tagged_hash('TapSighash', bytes([0]) + data) + def is_complete(self): # Are all the inputs (now) signed? diff --git a/ports/stm32/boards/Passport/modules/serializations.py b/ports/stm32/boards/Passport/modules/serializations.py index cdf8ccdd3..3fc5a611b 100644 --- a/ports/stm32/boards/Passport/modules/serializations.py +++ b/ports/stm32/boards/Passport/modules/serializations.py @@ -53,13 +53,13 @@ def hash160(s): return ripemd160(sha256(s)) -SIGHASH_ALL_TAPROOT = 0 +SIGHASH_DEFAULT = 0 SIGHASH_ALL = 1 SIGHASH_NONE = 2 SIGHASH_SINGLE = 3 SIGHASH_ANYONECANPAY = 0x80 -VALID_SIGHASHES = [SIGHASH_ALL_TAPROOT, SIGHASH_ALL] +VALID_SIGHASHES = [SIGHASH_DEFAULT, SIGHASH_ALL] # Serialization/deserialization tools diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py index bc22b7f2b..2d81d2036 100644 --- a/ports/stm32/boards/Passport/modules/taproot.py +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -32,14 +32,14 @@ # Point = Tuple[int, int] -# This implementation can be sped up by storing the midstate after hashing -# tag_hash instead of rehashing it all the time. -# def tagged_hash(tag, msg) -> bytes: -# tag_hash = uhashlib.sha256(tag.encode()).digest() -# return uhashlib.sha256(tag_hash + tag_hash + msg).digest() + +def tagged_hash(tag, msg): + from serializations import sha256 + + tag_hash = sha256(tag.encode()) + return sha256(tag_hash + tag_hash + msg) -# TODO: optimize for TapTweak def hash_tap_tweak(data): from serializations import sha256 from public_constants import TAP_TWEAK_SHA256 @@ -118,34 +118,35 @@ def tweak_internal_key(internal_key, h): P = lift_x(int_from_bytes(internal_key)) if P is None: raise ValueError - # TODO: use trezor point_add Q = point_add(P, scalar_multiply(t)) return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q)) -# def taproot_tweak_seckey(seckey0, h): -# seckey0 = int_from_bytes(seckey0) -# P = point_mul(G, seckey0) -# seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0 -# t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h)) -# if t >= SECP256K1_ORDER: -# raise ValueError -# return bytes_from_int((seckey + t) % SECP256K1_ORDER) -# -# -# def taproot_tree_helper(script_tree): -# if isinstance(script_tree, tuple): -# leaf_version, script = script_tree -# h = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_script(script)) -# return ([((leaf_version, script), bytes())], h) -# left, left_h = taproot_tree_helper(script_tree[0]) -# right, right_h = taproot_tree_helper(script_tree[1]) -# ret = [(l, c + right_h) for l, c in left] + [(l, c + left_h) for l, c in right] -# if right_h < left_h: -# left_h, right_h = right_h, left_h -# return (ret, tagged_hash("TapBranch", left_h + right_h)) -# -# +def taproot_tweak_seckey(seckey0, h): + seckey0 = int_from_bytes(seckey0) + P = scalar_multiply(seckey0) + seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0 + t = int_from_bytes(hash_tap_tweak(bytes_from_int(x(P)) + h)) + if t >= SECP256K1_ORDER: + raise ValueError + return bytes_from_int((seckey + t) % SECP256K1_ORDER) + + +def taproot_tree_helper(script_tree): + from serializations import ser_string + + if isinstance(script_tree, tuple): + leaf_version, script = script_tree + h = tagged_hash("TapLeaf", bytes([leaf_version]) + ser_string(script)) + return ([((leaf_version, script), bytes())], h) + left, left_h = taproot_tree_helper(script_tree[0]) + right, right_h = taproot_tree_helper(script_tree[1]) + ret = [(leaf, c + right_h) for leaf, c in left] + [(leaf, c + left_h) for leaf, c in right] + if right_h < left_h: + left_h, right_h = right_h, left_h + return (ret, tagged_hash("TapBranch", left_h + right_h)) + + def output_script(internal_pubkey, script_tree): """Given a internal public key and a tree of scripts, compute the output script. script_tree is either: @@ -159,3 +160,25 @@ def output_script(internal_pubkey, script_tree): _, h = taproot_tree_helper(script_tree) _, output_pubkey = tweak_internal_key(internal_pubkey, h) return bytes([0x51, 0x20]) + output_pubkey + + +def taproot_sign_key(script_tree, internal_seckey, hash_type, sighash): + from foundation import secp256k1 + + if script_tree is None: + h = bytes() + else: + _, h = taproot_tree_helper(script_tree) + output_seckey = taproot_tweak_seckey(internal_seckey, h) + sig = secp256k1.schnorr_sign(sighash, output_seckey) + if hash_type != 0: + sig += bytes([hash_type]) + return [sig] + + +def taproot_sign_script(internal_pubkey, script_tree, script_num, inputs): + info, h = taproot_tree_helper(script_tree) + (leaf_version, script), path = info[script_num] + output_pubkey_y_parity, _ = tweak_internal_key(internal_pubkey, h) + pubkey_data = bytes([output_pubkey_y_parity + leaf_version]) + internal_pubkey + return inputs + [script, pubkey_data + path] diff --git a/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py b/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py index 17029fea0..df7efa206 100644 --- a/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py @@ -19,6 +19,7 @@ async def sign_psbt_task(on_done, psbt): import stash import gc from foundation import secp256k1 + from taproot import taproot_sign_key try: with stash.SensitiveValues() as sv: @@ -54,6 +55,9 @@ async def sign_psbt_task(on_done, psbt): if not inp.is_segwit: # Hash by serializing/blanking various subparts of the transaction digest = psbt.make_txn_sighash(in_idx, txi, inp.sighash) + elif len(inp.tap_subpaths) > 0: + # TODO: add annex and ext_flag + digest = psbt.make_txn_taproot_sighash(in_idx, inp.sighash) else: # Hash the inputs and such in totally new ways, based on BIP-143 digest = psbt.make_txn_segwit_sighash(in_idx, txi, @@ -78,19 +82,31 @@ async def sign_psbt_task(on_done, psbt): which_key = inp.required_key assert not inp.added_sig, "already done??" - assert which_key in inp.subpaths, 'unk key' + assert not inp.tap_sig, "already done taproot??" - if inp.subpaths[which_key][0] != psbt.my_xfp and inp.subpaths[which_key][0] != swab32(psbt.my_xfp): - # we don't have the key for this subkey - # (redundant, required_key wouldn't be set) - continue + if len(inp.subpaths) > 0 and \ + (inp.subpaths[which_key][0] == psbt.my_xfp or + inp.subpaths[which_key][0] == swab32(psbt.my_xfp)): - # get node required - skp = keypath_to_str(inp.subpaths[which_key]) - node = sv.derive_path(skp, register=False) + # get node required + skp = keypath_to_str(inp.subpaths[which_key]) + node = sv.derive_path(skp, register=False) + + # expensive test, but works... and important + pu = node.public_key() + + # tap_subpaths have type ([path_elements], [tap_hashes]) + elif len(inp.tap_subpaths) > 0 and \ + (inp.tap_subpaths[which_key][0][0] == psbt.my_xfp or + inp.tap_subpaths[which_key][0][0] == swab32(psbt.my_xfp)): + + # get node required + skp = keypath_to_str(inp.tap_subpaths[which_key][0]) + node = sv.derive_path(skp, register=False) + + # expensive test, but works... and important + pu = node.public_key()[1:] - # expensive test, but works... and important - pu = node.public_key() if pu != which_key: raise AssertionError("Path (%s) led to wrong pubkey for input #%d" % (skp, in_idx)) @@ -102,11 +118,24 @@ async def sign_psbt_task(on_done, psbt): # print(" digest %s" % b2a_hex(digest).decode('ascii')) # Do the ACTUAL signature ... finally!!! - if inp.is_taproot: - result = secp256k1.schnorr_sign(digest, pk) + if len(inp.tap_subpaths) > 0: + # TODO: handle taproot scripts + inp.tap_sig = taproot_sign_key(None, pk, inp.sighash, digest) else: result = trezorcrypto.secp256k1.sign(pk, digest) + # convert signature to DER format + if len(result) != 65: + raise AssertionError('Incorrect signature length.') + + r = result[1:33] + s = result[33:65] + + inp.added_sig = (which_key, ser_sig_der(r, s, inp.sighash)) + + # Memory cleanup + del result, r, s + # private key no longer required stash.blank_object(pk) stash.blank_object(node) @@ -114,20 +143,8 @@ async def sign_psbt_task(on_done, psbt): # print("result %s" % b2a_hex(result).decode('ascii')) - # convert signature to DER format - if len(result) != 65: - raise AssertionError('Incorrect signature length.') - - r = result[1:33] - s = result[33:65] - - inp.added_sig = (which_key, ser_sig_der(r, s, inp.sighash)) - success.add(in_idx) - # Memory cleanup - del result, r, s - # All went well, so just fall through and call on_done() except FraudulentChangeOutput as e: @@ -147,7 +164,7 @@ async def sign_psbt_task(on_done, psbt): error_msg = 'Transaction is too complex.' error_code = Error.OUT_OF_MEMORY_ERROR except BaseException as e: - # print('BaseException: {}'.format(e)) + print('BaseException: {}'.format(e)) error_msg = 'Invalid PSBT.' error_code = Error.PSBT_FATAL_ERROR finally: From 79c67f22abd2b6be09e5f363902239082d82a31d Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 26 Jan 2024 16:53:04 -0500 Subject: [PATCH 218/239] SFT-2862: first attempt at outputting signed taproot PSBTs, parsing failures --- ports/stm32/boards/Passport/modules/psbt.py | 61 ++++++++++++---- .../Passport/modules/tasks/sign_psbt_task.py | 4 +- .../modules/tasks/validate_psbt_task.py | 72 +++++++++---------- 3 files changed, 86 insertions(+), 51 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 999377837..cdb0f808f 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -35,7 +35,7 @@ PSBT_OUT_BIP32_DERIVATION, MAX_PATH_DEPTH, PSBT_IN_TAP_BIP32_DERIVATION, PSBT_OUT_TAP_BIP32_DERIVATION, PSBT_IN_TAP_KEY_SIG, PSBT_IN_TAP_SCRIPT_SIG, PSBT_IN_TAP_LEAF_SCRIPT, PSBT_IN_TAP_INTERNAL_KEY, PSBT_IN_TAP_MERKLE_ROOT, - PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE, PSBT_OUT_TAP_BIP32_DERIVATION + PSBT_OUT_TAP_INTERNAL_KEY, PSBT_OUT_TAP_TREE ) # print some things @@ -199,13 +199,23 @@ def write(self, out_fd, ktype, val, key=b''): out_fd.write(key) if isinstance(val, tuple): - (pos, ll) = val - out_fd.write(ser_compact_size(ll)) - self.fd.seek(pos) - while ll: - t = self.fd.read(min(64, ll)) - out_fd.write(t) - ll -= len(t) + if ktype in (PSBT_IN_TAP_BIP32_DERIVATION, PSBT_OUT_TAP_BIP32_DERIVATION): + path, tap_hashes = val + output = ser_compact_size(len(tap_hashes)) + for h in tap_hashes: + output += h + for element in path: + output += pack(' 0 and \ (inp.subpaths[which_key][0] == psbt.my_xfp or @@ -120,7 +120,7 @@ async def sign_psbt_task(on_done, psbt): # Do the ACTUAL signature ... finally!!! if len(inp.tap_subpaths) > 0: # TODO: handle taproot scripts - inp.tap_sig = taproot_sign_key(None, pk, inp.sighash, digest) + inp.tap_key_sig = taproot_sign_key(None, pk, inp.sighash, digest) else: result = trezorcrypto.secp256k1.sign(pk, digest) diff --git a/ports/stm32/boards/Passport/modules/tasks/validate_psbt_task.py b/ports/stm32/boards/Passport/modules/tasks/validate_psbt_task.py index a18ecb4c1..993cff131 100644 --- a/ports/stm32/boards/Passport/modules/tasks/validate_psbt_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/validate_psbt_task.py @@ -23,42 +23,42 @@ async def validate_psbt_task(on_done, psbt_len): # Do the main validation psbt = None - try: - # Read TXN from SPI Flash (we put it there whether it came from a QR code or an SD card) - with SFFile(TXN_INPUT_OFFSET, length=psbt_len) as fd: - psbt = psbtObject.read_psbt(fd) + # try: + # Read TXN from SPI Flash (we put it there whether it came from a QR code or an SD card) + with SFFile(TXN_INPUT_OFFSET, length=psbt_len) as fd: + psbt = psbtObject.read_psbt(fd) - gc.collect() - await psbt.validate() - gc.collect() - psbt.consider_inputs() - gc.collect() - psbt.consider_keys() - gc.collect() - psbt.consider_outputs() + gc.collect() + await psbt.validate() + gc.collect() + psbt.consider_inputs() + gc.collect() + psbt.consider_keys() + gc.collect() + psbt.consider_outputs() - # All went well! - except FraudulentChangeOutput as e: - # print('validate_psbt_task 1') - error_msg = e.args[0] - error_code = Error.PSBT_FRAUDULENT_CHANGE_ERROR - except FatalPSBTIssue as e: - # print('validate_psbt_task 2') - error_msg = e.args[0] - error_code = Error.PSBT_FATAL_ERROR - except MemoryError as e: - # print('validate_psbt_task 3') - error_msg = 'Transaction is too complex: {}'.format(e) - error_code = Error.OUT_OF_MEMORY_ERROR - except BaseException as e: - # print('validate_psbt_task 4') - error_msg = 'Invalid PSBT: {}'.format(e) - error_code = Error.PSBT_FATAL_ERROR - finally: - # print('validate_psbt_task 5 - finally') - if error_code is not None: - del psbt - psbt = None - gc.collect() + # All went well! + # except FraudulentChangeOutput as e: + # # print('validate_psbt_task 1') + # error_msg = e.args[0] + # error_code = Error.PSBT_FRAUDULENT_CHANGE_ERROR + # except FatalPSBTIssue as e: + # # print('validate_psbt_task 2') + # error_msg = e.args[0] + # error_code = Error.PSBT_FATAL_ERROR + # except MemoryError as e: + # # print('validate_psbt_task 3') + # error_msg = 'Transaction is too complex: {}'.format(e) + # error_code = Error.OUT_OF_MEMORY_ERROR + # except BaseException as e: + # # print('validate_psbt_task 4') + # error_msg = 'Invalid PSBT: {}'.format(e) + # error_code = Error.PSBT_FATAL_ERROR + # finally: + # print('validate_psbt_task 5 - finally') + if error_code is not None: + del psbt + psbt = None + gc.collect() - await on_done(psbt, error_msg, error_code) + await on_done(psbt, error_msg, error_code) From 217f36eb3712d54f0e1921a4732ae3e3f947b987 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 27 Jan 2024 17:29:37 -0500 Subject: [PATCH 219/239] SFT-2862: signed taproot PSBTs show as fully signed --- ports/stm32/boards/Passport/modules/psbt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index cdb0f808f..d7a761542 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -1737,7 +1737,7 @@ def is_complete(self): # but we can't combine/finalize multisig stuff, so will never't be 'final' return False - if inp.added_sig: + if inp.added_sig or inp.tap_key_sig: signed += 1 return signed == self.num_inputs From 96b0979ab61fe42ea176a4a32b1e7ef73fb9bd90 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sat, 27 Jan 2024 17:55:31 -0500 Subject: [PATCH 220/239] SFT-2862: first successful taproot signature --- ports/stm32/boards/Passport/modules/psbt.py | 2 +- ports/stm32/boards/Passport/modules/taproot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index d7a761542..2b0f57978 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -909,7 +909,7 @@ def wr(*a): wr(PSBT_IN_TAP_KEY_SIG, self.tap_key_sig) for k in self.tap_subpaths: - wr(PSBT_IN_TAP_BIP32_DERIVATION, self.tap_subpaths, k) + wr(PSBT_IN_TAP_BIP32_DERIVATION, self.tap_subpaths[k], k) if self.tap_internal_key: wr(PSBT_IN_TAP_INTERNAL_KEY, self.tap_internal_key) diff --git a/ports/stm32/boards/Passport/modules/taproot.py b/ports/stm32/boards/Passport/modules/taproot.py index 2d81d2036..62820edc9 100644 --- a/ports/stm32/boards/Passport/modules/taproot.py +++ b/ports/stm32/boards/Passport/modules/taproot.py @@ -173,7 +173,7 @@ def taproot_sign_key(script_tree, internal_seckey, hash_type, sighash): sig = secp256k1.schnorr_sign(sighash, output_seckey) if hash_type != 0: sig += bytes([hash_type]) - return [sig] + return sig def taproot_sign_script(internal_pubkey, script_tree, script_num, inputs): From e3db934ce9f7ad79aeb1265c37395b163eb14bb8 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 28 Jan 2024 18:54:04 -0500 Subject: [PATCH 221/239] SFT-2862: first pass at finalizing transactions, fixed taproot self-send detection --- ports/stm32/boards/Passport/modules/psbt.py | 108 +++++++++--------- .../tasks/double_check_psbt_change_task.py | 37 +++--- 2 files changed, 76 insertions(+), 69 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 2b0f57978..d112633e0 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -248,61 +248,63 @@ def parse_subpaths(self, my_xfp): return self.num_our_keys num_ours = 0 - for pk in self.subpaths: - assert len(pk) in {33, 65}, "hdpath pubkey len" - if len(pk) == 33: - assert pk[0] in {0x02, 0x03}, "uncompressed pubkey" + if self.subpaths: + for pk in self.subpaths: + assert len(pk) in {33, 65}, "hdpath pubkey len" + if len(pk) == 33: + assert pk[0] in {0x02, 0x03}, "uncompressed pubkey" - vl = self.subpaths[pk][1] + vl = self.subpaths[pk][1] - # force them to use a derived key, never the master - assert vl >= 8, 'too short key path' - assert (vl % 4) == 0, 'corrupt key path' - assert (vl // 4) <= MAX_PATH_DEPTH, 'too deep' + # force them to use a derived key, never the master + assert vl >= 8, 'too short key path' + assert (vl % 4) == 0, 'corrupt key path' + assert (vl // 4) <= MAX_PATH_DEPTH, 'too deep' - # promote to a list of ints - v = self.get(self.subpaths[pk]) - here = list(unpack_from('<%dI' % (vl // 4), v)) + # promote to a list of ints + v = self.get(self.subpaths[pk]) + here = list(unpack_from('<%dI' % (vl // 4), v)) - # update in place - self.subpaths[pk] = here + # update in place + self.subpaths[pk] = here - if here[0] == my_xfp or here[0] == swab32(my_xfp): - num_ours += 1 - else: - # Address that isn't based on my seed; might be another leg in a p2sh, - # or an input we're not supposed to be able to sign... and that's okay. - pass + if here[0] == my_xfp or here[0] == swab32(my_xfp): + num_ours += 1 + else: + # Address that isn't based on my seed; might be another leg in a p2sh, + # or an input we're not supposed to be able to sign... and that's okay. + pass - for pk in self.tap_subpaths: - assert len(pk) == 32, "taproot hdpath pubkey len" + if self.tap_subpaths: + for pk in self.tap_subpaths: + assert len(pk) == 32, "taproot hdpath pubkey len" - vl = self.tap_subpaths[pk][1] + vl = self.tap_subpaths[pk][1] - # parse leaf hashes and path - v = self.get(self.tap_subpaths[pk]) - (num_tap_hashes, compact_length) = deser_compact_size_bytes(v) - tap_hashes = [uint256_from_bytes(v[i * 32:(i + 1) * 32]) for i in range(0, num_tap_hashes)] - v = v[(num_tap_hashes * 32 + compact_length):] - vl = len(v) + # parse leaf hashes and path + v = self.get(self.tap_subpaths[pk]) + (num_tap_hashes, compact_length) = deser_compact_size_bytes(v) + tap_hashes = [uint256_from_bytes(v[i * 32:(i + 1) * 32]) for i in range(0, num_tap_hashes)] + v = v[(num_tap_hashes * 32 + compact_length):] + vl = len(v) - # Master key can be used if there is no tapscript tree - if tap_hashes: - assert vl >= 8, 'too short key path' - assert (vl % 4) == 0, 'corrupt key path' - assert (vl // 4) <= MAX_PATH_DEPTH, 'too deep' + # Master key can be used if there is no tapscript tree + if tap_hashes: + assert vl >= 8, 'too short key path' + assert (vl % 4) == 0, 'corrupt key path' + assert (vl // 4) <= MAX_PATH_DEPTH, 'too deep' - here = list(unpack_from('<%dI' % (vl // 4), v)) + here = list(unpack_from('<%dI' % (vl // 4), v)) - # update in place - self.tap_subpaths[pk] = (here, tap_hashes) + # update in place + self.tap_subpaths[pk] = (here, tap_hashes) - if here[0] == my_xfp or here[0] == swab32(my_xfp): - num_ours += 1 - else: - # Address that isn't based on my seed; might be another leg in a p2sh, - # or an input we're not supposed to be able to sign... and that's okay. - pass + if here[0] == my_xfp or here[0] == swab32(my_xfp): + num_ours += 1 + else: + # Address that isn't based on my seed; might be another leg in a p2sh, + # or an input we're not supposed to be able to sign... and that's okay. + pass self.num_our_keys = num_ours return num_ours @@ -768,13 +770,10 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): # TODO: handle tapscript tree self.scriptSig = utxo.scriptPubKey - print("addr_or_pubkey: {}".format(b2a_hex(addr_or_pubkey))) - if len(self.tap_subpaths) == 1: # No script path subpath = list(self.tap_subpaths.items())[0] pubkey, (path, tap_hashes) = subpath tweaked_pubkey = output_script(pubkey, None)[2:] - print("tweaked_pubkey: {}".format(b2a_hex(tweaked_pubkey))) if path[0] == my_xfp and tweaked_pubkey == addr_or_pubkey: which_key = pubkey else: @@ -1418,15 +1417,9 @@ def consider_inputs(self): # We should know pubkey required for each input now. # - but we may not be the signer for those inputs, which is fine. # - TODO: but what if not SIGHASH_ALL - for n, inp in enumerate(self.inputs): - print("n: {}, inp.fully_signed: {}".format(n, inp.fully_signed)) - print("inp.required_key: {}".format(b2a_hex(inp.required_key))) - no_keys = set(n for n, inp in enumerate(self.inputs) if inp.required_key is None and not inp.fully_signed) - print("no_keys: {}".format(no_keys)) - gc.collect() if no_keys: @@ -1812,14 +1805,17 @@ def finalize(self, fd): for in_idx, wit in self.input_witness_iter(): inp = self.inputs[in_idx] - if inp.is_segwit and inp.added_sig: + if inp.is_segwit and (inp.added_sig or inp.tap_key_sig): # put in new sig: wit is a CTxInWitness assert not wit.scriptWitness.stack, 'replacing non-empty?' assert not inp.is_multisig, 'Multisig PSBT combine not supported' - pubkey, der_sig = inp.added_sig - assert pubkey[0] in {0x02, 0x03} and len(pubkey) == 33, "bad v0 pubkey" - wit.scriptWitness.stack = [der_sig, pubkey] + if inp.added_sig: + pubkey, der_sig = inp.added_sig + assert pubkey[0] in {0x02, 0x03} and len(pubkey) == 33, "bad v0 pubkey" + wit.scriptWitness.stack = [der_sig, pubkey] + else: + wit.scriptWitness.stack = [inp.tap_key_sig] fd.write(wit.serialize()) diff --git a/ports/stm32/boards/Passport/modules/tasks/double_check_psbt_change_task.py b/ports/stm32/boards/Passport/modules/tasks/double_check_psbt_change_task.py index 9a3fa9237..3cde2c14c 100644 --- a/ports/stm32/boards/Passport/modules/tasks/double_check_psbt_change_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/double_check_psbt_change_task.py @@ -28,19 +28,30 @@ async def double_check_psbt_change_task(on_done, psbt): oup = psbt.outputs[out_idx] good = 0 - for pubkey, subpath in oup.subpaths.items(): - if subpath[0] != psbt.my_xfp and subpath[0] != swab32(psbt.my_xfp): - # For multisig, will be N paths, and exactly one will be our key. - # For single-signer, should always be my XFP. - continue - - # Derive actual pubkey from private - skp = keypath_to_str(subpath) - node = sv.derive_path(skp) - - # check the pubkey of this BIP32 node - if pubkey == node.public_key(): - good += 1 + if oup.subpaths: + for pubkey, subpath in oup.subpaths.items(): + if subpath[0] != psbt.my_xfp and subpath[0] != swab32(psbt.my_xfp): + # For multisig, will be N paths, and exactly one will be our key. + # For single-signer, should always be my XFP. + continue + + # Derive actual pubkey from private + skp = keypath_to_str(subpath) + node = sv.derive_path(skp) + + # check the pubkey of this BIP32 node + if pubkey == node.public_key(): + good += 1 + elif oup.tap_subpaths: + for pubkey, (tap_subpath, tap_hashes) in oup.tap_subpaths.items(): + if tap_subpath[0] != psbt.my_xfp and tap_subpath[0] != swab32(psbt.my_xfp): + continue + + skp = keypath_to_str(tap_subpath) + node = sv.derive_path(skp) + + if pubkey == node.public_key()[1:]: + good += 1 if not good: # print('double_check_psbt_change_task() Fraudulent Change Error') From bd400a89a3b80654732fb374dc68dc57c47d9169 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 28 Jan 2024 19:12:28 -0500 Subject: [PATCH 222/239] SFT-2862: reduced diff --- .../Passport/modules/tasks/sign_psbt_task.py | 2 +- .../modules/tasks/validate_psbt_task.py | 72 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py b/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py index 823619681..c73e9eb03 100644 --- a/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/sign_psbt_task.py @@ -164,7 +164,7 @@ async def sign_psbt_task(on_done, psbt): error_msg = 'Transaction is too complex.' error_code = Error.OUT_OF_MEMORY_ERROR except BaseException as e: - print('BaseException: {}'.format(e)) + # print('BaseException: {}'.format(e)) error_msg = 'Invalid PSBT.' error_code = Error.PSBT_FATAL_ERROR finally: diff --git a/ports/stm32/boards/Passport/modules/tasks/validate_psbt_task.py b/ports/stm32/boards/Passport/modules/tasks/validate_psbt_task.py index 993cff131..a18ecb4c1 100644 --- a/ports/stm32/boards/Passport/modules/tasks/validate_psbt_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/validate_psbt_task.py @@ -23,42 +23,42 @@ async def validate_psbt_task(on_done, psbt_len): # Do the main validation psbt = None - # try: - # Read TXN from SPI Flash (we put it there whether it came from a QR code or an SD card) - with SFFile(TXN_INPUT_OFFSET, length=psbt_len) as fd: - psbt = psbtObject.read_psbt(fd) + try: + # Read TXN from SPI Flash (we put it there whether it came from a QR code or an SD card) + with SFFile(TXN_INPUT_OFFSET, length=psbt_len) as fd: + psbt = psbtObject.read_psbt(fd) - gc.collect() - await psbt.validate() - gc.collect() - psbt.consider_inputs() - gc.collect() - psbt.consider_keys() - gc.collect() - psbt.consider_outputs() + gc.collect() + await psbt.validate() + gc.collect() + psbt.consider_inputs() + gc.collect() + psbt.consider_keys() + gc.collect() + psbt.consider_outputs() - # All went well! - # except FraudulentChangeOutput as e: - # # print('validate_psbt_task 1') - # error_msg = e.args[0] - # error_code = Error.PSBT_FRAUDULENT_CHANGE_ERROR - # except FatalPSBTIssue as e: - # # print('validate_psbt_task 2') - # error_msg = e.args[0] - # error_code = Error.PSBT_FATAL_ERROR - # except MemoryError as e: - # # print('validate_psbt_task 3') - # error_msg = 'Transaction is too complex: {}'.format(e) - # error_code = Error.OUT_OF_MEMORY_ERROR - # except BaseException as e: - # # print('validate_psbt_task 4') - # error_msg = 'Invalid PSBT: {}'.format(e) - # error_code = Error.PSBT_FATAL_ERROR - # finally: - # print('validate_psbt_task 5 - finally') - if error_code is not None: - del psbt - psbt = None - gc.collect() + # All went well! + except FraudulentChangeOutput as e: + # print('validate_psbt_task 1') + error_msg = e.args[0] + error_code = Error.PSBT_FRAUDULENT_CHANGE_ERROR + except FatalPSBTIssue as e: + # print('validate_psbt_task 2') + error_msg = e.args[0] + error_code = Error.PSBT_FATAL_ERROR + except MemoryError as e: + # print('validate_psbt_task 3') + error_msg = 'Transaction is too complex: {}'.format(e) + error_code = Error.OUT_OF_MEMORY_ERROR + except BaseException as e: + # print('validate_psbt_task 4') + error_msg = 'Invalid PSBT: {}'.format(e) + error_code = Error.PSBT_FATAL_ERROR + finally: + # print('validate_psbt_task 5 - finally') + if error_code is not None: + del psbt + psbt = None + gc.collect() - await on_done(psbt, error_msg, error_code) + await on_done(psbt, error_msg, error_code) From ce3451cf269d527a56113647456cc3889e20c725 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Sun, 28 Jan 2024 19:15:57 -0500 Subject: [PATCH 223/239] SFT-2862: further reduced diff --- ports/stm32/boards/Passport/modules/psbt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index d112633e0..252b8719d 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -532,7 +532,7 @@ class psbtInputProxy(psbtProxy): 'redeem_script', 'witness_script', 'fully_signed', 'is_segwit', 'is_multisig', 'is_p2sh', 'num_our_keys', 'required_key', 'scriptSig', 'amount', 'scriptCode', 'added_sig', - 'tap_internal_key', 'tap_key_sig', 'tap_merkle_root', 'tap_sig') + 'tap_internal_key', 'tap_key_sig', 'tap_merkle_root') def __init__(self, fd, idx): super().__init__() @@ -566,7 +566,7 @@ def __init__(self, fd, idx): # after signing, we'll have a signature to add to output PSBT # self.added_sig = None - # self.tap_sig = None + # self.tap_key_sig = None self.parse(fd) gc.collect() From ef81f4b87b102a68ba5ab3d0e4432587b2439c61 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Tue, 30 Jan 2024 13:30:02 -0500 Subject: [PATCH 224/239] SFT-2862: added default sighash for taproot PSBTs --- ports/stm32/boards/Passport/modules/psbt.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py index 252b8719d..00e373057 100644 --- a/ports/stm32/boards/Passport/modules/psbt.py +++ b/ports/stm32/boards/Passport/modules/psbt.py @@ -586,7 +586,13 @@ def validate(self, idx, txin, my_xfp): self.parse_subpaths(my_xfp) # sighash, but we're probably going to ignore anyway. - self.sighash = SIGHASH_ALL if self.sighash is None else self.sighash + if self.sighash is None: + # Taproot PSBTs can be built with no explicit sighash + if len(self.tap_subpaths) > 0: + self.sighash = SIGHASH_DEFAULT + else: + self.sighash = SIGHASH_ALL + if self.sighash not in VALID_SIGHASHES: # - someday we will expand to other types, but not yet raise FatalPSBTIssue('Can only do SIGHASH_ALL') @@ -681,13 +687,11 @@ def determine_my_signing_key(self, my_idx, utxo, my_xfp, psbt): # - also validates redeem_script when present self.amount = utxo.nValue - print("amount: {}".format(self.amount)) if (not self.subpaths and not self.tap_subpaths) or self.fully_signed: # without xfp+path we will not be able to sign this input # - okay if fully signed # - okay if payjoin or other multi-signer (not multisig) txn - print("required_key = None") self.required_key = None return From 5a4188947f7ec96785a25edacd222415a4225678 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Wed, 31 Jan 2024 15:36:39 -0500 Subject: [PATCH 225/239] SFT-2862: removed BTCPay taproot option --- ports/stm32/boards/Passport/modules/wallets/btcpay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/wallets/btcpay.py b/ports/stm32/boards/Passport/modules/wallets/btcpay.py index bacde003b..c67f78856 100644 --- a/ports/stm32/boards/Passport/modules/wallets/btcpay.py +++ b/ports/stm32/boards/Passport/modules/wallets/btcpay.py @@ -19,6 +19,6 @@ {'id': 'microsd', 'label': 'microSD', 'filename_pattern': '{xfp}-btcpay.json', 'filename_pattern_multisig': '{xfp}-btcpay-multisig.json'} ], - 'select_addr_type': True, - 'addr_options': [AF_P2WPKH, AF_P2TR], + # 'select_addr_type': True, + # 'addr_options': [AF_P2WPKH, AF_P2TR], } From f703640f631b8c31b7e182417d1d32c6b3329792 Mon Sep 17 00:00:00 2001 From: mjg-foundation <114431859+mjg-foundation@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:49:59 -0500 Subject: [PATCH 226/239] Update version.txt --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f03ace7cc..ad2e19ad6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.0b5 +2.3.0b7 From aee865ffeab6eacfc3cf45901112b9b329454bb1 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 2 Feb 2024 03:23:22 -0500 Subject: [PATCH 227/239] SFT-3223: enabled receive or change option for exploring multisig addresses --- .../boards/Passport/modules/flows/address_explorer_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py index 8c502e14e..4ac7a1ef0 100644 --- a/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py @@ -63,7 +63,7 @@ async def choose_sig_type(self): self.goto(self.choose_addr_type) return - self.goto(self.prepare) + self.goto(self.choose_change) async def choose_addr_type(self): from pages import AddressTypeChooserPage From 19e3434fbed460e47f5d08c988ffed8381bd7664 Mon Sep 17 00:00:00 2001 From: mjg-foundation <114431859+mjg-foundation@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:37:24 -0500 Subject: [PATCH 228/239] Update version.txt to 2.3.0b8 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index ad2e19ad6..4a033fdcc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.0b7 +2.3.0b8 From b6b92028393d0b80a298393482565f37f5402e73 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 8 Feb 2024 11:39:20 -0500 Subject: [PATCH 229/239] SFT-3338: fixed file deletion option --- ports/stm32/boards/Passport/modules/flows/file_picker_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/file_picker_flow.py b/ports/stm32/boards/Passport/modules/flows/file_picker_flow.py index 930ac06d9..b0281414b 100644 --- a/ports/stm32/boards/Passport/modules/flows/file_picker_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/file_picker_flow.py @@ -28,7 +28,7 @@ def __init__( suffix=None, filter_fn=None, select_text="Select", - allow_delete=False): + allow_delete=True): from files import CardSlot from utils import bind, show_card_missing From ceb973cbe15b9eb597a6d974f4a11ee9215cf945 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 8 Feb 2024 12:28:54 -0500 Subject: [PATCH 230/239] SFT-3337: fixed capitalization for security word and device renaming flows --- ports/stm32/boards/Passport/modules/flows/rename_device_flow.py | 2 +- .../Passport/modules/flows/show_security_words_setting_flow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py b/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py index 811f0c84b..a2d430372 100644 --- a/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/rename_device_flow.py @@ -43,6 +43,6 @@ async def rename_device(self): settings.set('device_name', result) await spinner_task('Renaming Device', delay_task, args=[1000, False]) - await SuccessPage(text='Device Renamed Successfully').show() + await SuccessPage(text='Device renamed successfully').show() self.set_result(True) diff --git a/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py b/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py index 8986ab365..8d091dce4 100644 --- a/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/show_security_words_setting_flow.py @@ -62,7 +62,7 @@ async def enter_pin(self): get_security_words_task, args=[prefix]) - message = 'Remember These Security Words:\n\n' + message = 'Remember these security words:\n\n' message += recolor(HIGHLIGHT_TEXT_HEX, ' '.join(security_words)) await InfoPage(message).show() From bc6e0f676b09bd10c9e1b24dfd7ada53b650e9cc Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Thu, 8 Feb 2024 12:49:27 -0500 Subject: [PATCH 231/239] SFT-3336: added error handling to restore flow check for backups folder --- .../modules/flows/restore_backup_flow.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py index d8482f745..c767706eb 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py @@ -17,10 +17,11 @@ SuccessPage, YesNoChooserPage ) -from utils import spinner_task, get_backup_code_as_password +from utils import spinner_task, get_backup_code_as_password, bind, show_card_missing from tasks import restore_backup_task, get_backup_code_task from errors import Error import common +from files import CardMissingError class RestoreBackupFlow(Flow): @@ -33,6 +34,9 @@ def __init__(self, refresh_cards_when_done=False, autobackup=True, full_backup=F self.full_backup = full_backup self.autobackup = autobackup self.statusbar = {'title': 'RESTORE BACKUP', 'icon': 'ICON_SEED'} + self.return_bool = True + + bind(self, show_card_missing) async def check_if_erased(self): from common import pa @@ -56,11 +60,15 @@ async def choose_file(self): from utils import get_file_list, get_backups_folder_path suffix = '.7z' - files = get_file_list(suffix=suffix) - if len(files) == 0: - initial_path = get_backups_folder_path() - else: - initial_path = None + try: + files = get_file_list(suffix=suffix) + if len(files) == 0: + initial_path = get_backups_folder_path() + else: + initial_path = None + except CardMissingError: + self.goto(self.show_card_missing) + return result = await FilePickerFlow(initial_path=initial_path, suffix=suffix, From 27d739b66fff4911d27c0914d01dd75c6b2dfbcc Mon Sep 17 00:00:00 2001 From: mjg-foundation <114431859+mjg-foundation@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:54:01 -0500 Subject: [PATCH 232/239] Update version.txt to 2.3.0b9 --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 4a033fdcc..b8df0242e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.0b8 +2.3.0b9 From 906890116e294667d6b4fbf542fb440460c37d97 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 9 Feb 2024 05:27:04 -0500 Subject: [PATCH 233/239] SFT-3343: switched to 3 character blocks per line in FE address displays --- ports/stm32/boards/Passport/modules/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/utils.py b/ports/stm32/boards/Passport/modules/utils.py index 0b9b42d16..4905f7ecf 100644 --- a/ports/stm32/boards/Passport/modules/utils.py +++ b/ports/stm32/boards/Passport/modules/utils.py @@ -1480,6 +1480,7 @@ def stylize_address(address): colors = [BLACK_HEX, TEXT_GREY_HEX] color_index = 0 block = '' + blocks_per_line = 4 if passport.IS_COLOR else 3 for i in range(len(address)): # Every 4 characters, append the recolored block of characters @@ -1488,7 +1489,7 @@ def stylize_address(address): block = '' color_index ^= 1 # Every 4 blocks, start a new line - if i % 16 == 0: + if i % (4 * blocks_per_line) == 0: stylized += '\n' else: stylized += spacer From ad64258ddeffcc68a0037a3854587d59f4f9e3c9 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 9 Feb 2024 05:33:16 -0500 Subject: [PATCH 234/239] SFT-3336: disabled select or delete page for backup restoration --- .../stm32/boards/Passport/modules/flows/restore_backup_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py index c767706eb..79c09e69a 100644 --- a/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py +++ b/ports/stm32/boards/Passport/modules/flows/restore_backup_flow.py @@ -72,7 +72,8 @@ async def choose_file(self): result = await FilePickerFlow(initial_path=initial_path, suffix=suffix, - show_folders=True).run() + show_folders=True, + allow_delete=False).run() if result is None: # No file chosen, so go back to menu self.set_result(False) From b6576868dbd8ac22c5802551e94fd310f922de4b Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 12 Feb 2024 18:09:29 -0500 Subject: [PATCH 235/239] SFT-3348: set just version to 1.23.0 since 1.24.0 is broken --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f73b07c33..f8b2f885b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ RUN rustup component add clippy && \ # Install binaries using cargo. RUN cargo install cbindgen@^0.24 && \ - cargo install just@^1.13 && \ + cargo install just@1.23.0 && \ mv /cargo/bin/cbindgen /usr/local/bin/cbindgen && \ mv /cargo/bin/just /usr/local/bin/just && \ chmod 755 /usr/local/bin/cbindgen && \ From 0c87eb79d933bc988c701183e7d18561fbf1bc86 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 9 Feb 2024 05:02:26 -0500 Subject: [PATCH 236/239] SFT-3342: first pass at missing flow state fix, untested --- .../boards/Passport/modules/flows/flow.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/flow.py b/ports/stm32/boards/Passport/modules/flows/flow.py index 88861b9ce..29b2e9cfa 100644 --- a/ports/stm32/boards/Passport/modules/flows/flow.py +++ b/ports/stm32/boards/Passport/modules/flows/flow.py @@ -47,6 +47,12 @@ def deserialize_prev_states(self, saved_stack): stack = [] for state_function_name in saved_stack: # print('lookup "{}"'.format(state_function_name)) + + # State name doesn't exist, so the firmware must have changed + # All saved data is unsafe, return None for caller error handling + if not hasattr(self, state_function_name): + return None + stack.append(getattr(self, state_function_name)) # print('deserialized stack={}'.format(stack)) @@ -60,8 +66,27 @@ def save(self): def restore_items(self, data): # print('restore_items = {}'.format(data)) common.page_transition_dir = data.get('page_transition_dir', TRANSITION_DIR_PUSH) - self.prev_states = self.deserialize_prev_states(data.get('prev_states', [])) - self.state = getattr(self, data.get('state')) + + prev_states = self.deserialize_prev_states(data.get('prev_states', [])) + + # This is the error case, while the "empty" case is '[]' + if prev_states is None: + self.erase_settings() + return + + self.prev_states = prev_states + + current_state = data.get('state', None) + + # In case the stack is present but the saved state wasn't + # If the saved state isn't available, the stack will be unsafe + if current_state is None or not hasattr(self, current_state): + self.erase_settings() + self.prev_states = [] + return + + # We have ensured that the state is valid + self.state = getattr(self, current_state) def erase_settings(self): if self.settings_key is not None: From 753ed9e39911b7f139284cd57a39c03daf9b06f3 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Fri, 9 Feb 2024 05:16:23 -0500 Subject: [PATCH 237/239] SFT-3342: erased saved flow states during passport erase --- ports/stm32/boards/Passport/modules/flows/flow.py | 4 ++++ .../boards/Passport/modules/tasks/erase_passport_task.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ports/stm32/boards/Passport/modules/flows/flow.py b/ports/stm32/boards/Passport/modules/flows/flow.py index 29b2e9cfa..3b8ab0f5a 100644 --- a/ports/stm32/boards/Passport/modules/flows/flow.py +++ b/ports/stm32/boards/Passport/modules/flows/flow.py @@ -7,6 +7,8 @@ from utils import handle_fatal_error import common +saved_flow_keys = [] + class Flow(): def __init__(self, @@ -62,6 +64,8 @@ def save(self): if self.settings_key is not None: save_data = self.get_save_data() common.settings.set(self.settings_key, save_data) + if self.settings_key not in saved_flow_keys: + saved_flow_keys.append(self.settings_key) def restore_items(self, data): # print('restore_items = {}'.format(data)) diff --git a/ports/stm32/boards/Passport/modules/tasks/erase_passport_task.py b/ports/stm32/boards/Passport/modules/tasks/erase_passport_task.py index c3e9f5483..77960e5fe 100644 --- a/ports/stm32/boards/Passport/modules/tasks/erase_passport_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/erase_passport_task.py @@ -16,6 +16,7 @@ async def erase_passport_task(on_done, full_reset): from common import pa, settings + from flows import saved_flow_keys if full_reset: settings.clear() @@ -33,6 +34,9 @@ async def erase_passport_task(on_done, full_reset): settings.remove_regex("^ext\\.*") settings.remove('next_addrs') settings.remove('derived_keys') + for key in saved_flow_keys: + print("erasing saved flow: {}".format(key)) + settings.remove(key) await sleep_ms(1) From 1e84417a5fc6e49a9ce5051083d3e5d931845988 Mon Sep 17 00:00:00 2001 From: Matt Gleason Date: Mon, 12 Feb 2024 10:31:43 -0500 Subject: [PATCH 238/239] SFT-3342: saved keys of persistent states rather than using dynamic list --- ports/stm32/boards/Passport/modules/flows/flow.py | 6 +++--- .../boards/Passport/modules/tasks/erase_passport_task.py | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ports/stm32/boards/Passport/modules/flows/flow.py b/ports/stm32/boards/Passport/modules/flows/flow.py index 3b8ab0f5a..73423ad2f 100644 --- a/ports/stm32/boards/Passport/modules/flows/flow.py +++ b/ports/stm32/boards/Passport/modules/flows/flow.py @@ -7,7 +7,9 @@ from utils import handle_fatal_error import common -saved_flow_keys = [] +saved_flow_keys = ['manual_setup', + 'setup_flow', + 'envoy_setup'] class Flow(): @@ -64,8 +66,6 @@ def save(self): if self.settings_key is not None: save_data = self.get_save_data() common.settings.set(self.settings_key, save_data) - if self.settings_key not in saved_flow_keys: - saved_flow_keys.append(self.settings_key) def restore_items(self, data): # print('restore_items = {}'.format(data)) diff --git a/ports/stm32/boards/Passport/modules/tasks/erase_passport_task.py b/ports/stm32/boards/Passport/modules/tasks/erase_passport_task.py index 77960e5fe..14fa80f67 100644 --- a/ports/stm32/boards/Passport/modules/tasks/erase_passport_task.py +++ b/ports/stm32/boards/Passport/modules/tasks/erase_passport_task.py @@ -29,13 +29,10 @@ async def erase_passport_task(on_done, full_reset): settings.remove('accounts') settings.remove('backup_quiz') settings.remove('enable_passphrase') - settings.remove('envoy_setup') - settings.remove('manual_setup') settings.remove_regex("^ext\\.*") settings.remove('next_addrs') settings.remove('derived_keys') for key in saved_flow_keys: - print("erasing saved flow: {}".format(key)) settings.remove(key) await sleep_ms(1) From 6f4c1996693cd2ff53edce9d7b7365ee15719cc6 Mon Sep 17 00:00:00 2001 From: mjg-foundation <114431859+mjg-foundation@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:38:27 -0500 Subject: [PATCH 239/239] Update version.txt to 2.3.0bA (beta 10) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b8df0242e..fb096ad9c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.3.0b9 +2.3.0bA