From 9230f0dea0e07e28f2b4c59c76742cdd226087ff Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Mon, 12 Dec 2022 16:57:03 -0500 Subject: [PATCH 01/30] Skip Seed selection when previously selected after PSBT Scan and remove PSBT Review --- src/seedsigner/controller.py | 9 +++++++-- src/seedsigner/views/psbt_views.py | 16 ++++++---------- src/seedsigner/views/seed_views.py | 21 ++------------------- src/seedsigner/views/view.py | 6 ++++++ 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 7adb6457..ae2874b7 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -133,8 +133,7 @@ class Controller(Singleton): controller.microsd.start_detection() # Store one working psbt in memory - controller.psbt = None - controller.psbt_parser = None + controller.clear_psbt_data() # Configure the Renderer Renderer.configure_instance() @@ -185,6 +184,12 @@ class Controller(Singleton): self.back_stack = BackStack() + def clear_psbt_data(self): + self.psbt = None + self.psbt_parser = None + self.psbt_seed = None + + def start(self) -> None: from .views import MainMenuView, BackStackView from .views.screensaver import OpeningSplashScreen diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 613a9052..f6c6cb85 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -49,6 +49,11 @@ class PSBTSelectSeedView(View): button_data.append(TYPE_12WORD) button_data.append(TYPE_24WORD) + if self.controller.psbt_seed: + if PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=self.controller.psbt_seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)): + # skip the seed prompt if a seed was previous selected and has matching input fingerprint + return Destination(PSBTOverviewView) + selected_menu_num = ButtonListScreen( title="Select Signer", is_button_text_centered=False, @@ -143,6 +148,7 @@ class PSBTOverviewView(View): selected_menu_num = screen.display() if selected_menu_num == RET_CODE__BACK_BUTTON: + self.controller.psbt_seed = None return Destination(BackStackView) # expecting p2sh (legacy multisig) and p2pkh to have no policy set @@ -446,11 +452,6 @@ class PSBTAddressVerificationFailedView(View): show_back_button=False, ).display() - # Clear out the bad PSBT - self.controller.psbt = None - self.controller.psbt_parser = None - self.controller.psbt_seed = None - return Destination(MainMenuView, clear_history=True) @@ -501,11 +502,6 @@ class PSBTSignedQRDisplayView(View): ) QRDisplayScreen(qr_encoder=qr_encoder).display() - # We're done with this PSBT. Remove all related data - self.controller.psbt = None - self.controller.psbt_parser = None - self.controller.psbt_seed = None - return Destination(MainMenuView, clear_history=True) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index c935ee7b..34c8e4f3 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -346,7 +346,6 @@ class SeedOptionsView(View): from seedsigner.views.psbt_views import PSBTOverviewView SCAN_PSBT = ("Scan PSBT", FontAwesomeIconConstants.QRCODE) - REVIEW_PSBT = "Review PSBT" VERIFY_ADDRESS = "Verify Addr" EXPORT_XPUB = "Export Xpub" EXPLORER = "Address Explorer" @@ -372,20 +371,7 @@ class SeedOptionsView(View): VERIFY_ADDRESS += f" {addr}" button_data.append(VERIFY_ADDRESS) - if self.controller.psbt: - if PSBTParser.has_matching_input_fingerprint(self.controller.psbt, self.seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)): - if self.controller.resume_main_flow and self.controller.resume_main_flow == Controller.FLOW__PSBT: - # Re-route us directly back to the start of the PSBT flow - self.controller.resume_main_flow = None - self.controller.psbt_seed = self.seed - return Destination(PSBTOverviewView, skip_current_view=True) - else: - # This seed does not seem to be a signer for this PSBT - # TODO: How sure are we? Should disable this entirely if we're 100% sure? - REVIEW_PSBT += " (?)" - button_data.append(REVIEW_PSBT) - else: - button_data.append(SCAN_PSBT) + button_data.append(SCAN_PSBT) if self.settings.get_value(SettingsConstants.SETTING__XPUB_EXPORT) == SettingsConstants.OPTION__ENABLED: button_data.append(EXPORT_XPUB) @@ -408,12 +394,9 @@ class SeedOptionsView(View): # Force BACK to always return to the Main Menu return Destination(MainMenuView) - if button_data[selected_menu_num] == REVIEW_PSBT: - self.controller.psbt_seed = self.controller.get_seed(self.seed_num) - return Destination(PSBTOverviewView) - if button_data[selected_menu_num] == SCAN_PSBT: from seedsigner.views.scan_views import ScanView + self.controller.psbt_seed = self.controller.get_seed(self.seed_num) return Destination(ScanView) elif button_data[selected_menu_num] == VERIFY_ADDRESS: diff --git a/src/seedsigner/views/view.py b/src/seedsigner/views/view.py index 50b1c40a..9e3f2590 100644 --- a/src/seedsigner/views/view.py +++ b/src/seedsigner/views/view.py @@ -121,6 +121,12 @@ class Destination: # ######################################################################################### class MainMenuView(View): + def __init__(self): + super().__init__() + # always clear psbt state when on Main Menu + self.controller.clear_psbt_data() + + def run(self): from .seed_views import SeedsMenuView from .settings_views import SettingsMenuView From 8c7cf0f11250dc8593bf9a85b990b627ae0657de Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Wed, 14 Dec 2022 15:33:00 -0500 Subject: [PATCH 02/30] moving all temp var wiping to controller and adding some additional comments --- src/seedsigner/controller.py | 18 ++++++------------ src/seedsigner/views/psbt_views.py | 4 ++++ src/seedsigner/views/view.py | 6 ------ 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index ae2874b7..fa045c7d 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -133,7 +133,8 @@ class Controller(Singleton): controller.microsd.start_detection() # Store one working psbt in memory - controller.clear_psbt_data() + controller.psbt = None + controller.psbt_parser = None # Configure the Renderer Renderer.configure_instance() @@ -184,12 +185,6 @@ class Controller(Singleton): self.back_stack = BackStack() - def clear_psbt_data(self): - self.psbt = None - self.psbt_parser = None - self.psbt_seed = None - - def start(self) -> None: from .views import MainMenuView, BackStackView from .views.screensaver import OpeningSplashScreen @@ -230,15 +225,14 @@ class Controller(Singleton): next_destination = Destination(MainMenuView) if next_destination.View_cls == MainMenuView: - # Home always wipes the back_stack - self.clear_back_stack() - - # Clear other temp vars + # Home always wipes the back_stack/state of temp vars self.resume_main_flow = None self.multisig_wallet_descriptor = None self.unverified_address = None self.address_explorer_data = None - + self.psbt = None + self.psbt_parser = None + self.psbt_seed = None print(f"back_stack: {self.back_stack}") diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index f6c6cb85..a725c612 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -452,6 +452,8 @@ class PSBTAddressVerificationFailedView(View): show_back_button=False, ).display() + # We're done with this PSBT. Route back to MainMenuView which always + # clears all ephemeral data (except in-memory seeds). return Destination(MainMenuView, clear_history=True) @@ -502,6 +504,8 @@ class PSBTSignedQRDisplayView(View): ) QRDisplayScreen(qr_encoder=qr_encoder).display() + # We're done with this PSBT. Route back to MainMenuView which always + # clears all ephemeral data (except in-memory seeds). return Destination(MainMenuView, clear_history=True) diff --git a/src/seedsigner/views/view.py b/src/seedsigner/views/view.py index 9e3f2590..50b1c40a 100644 --- a/src/seedsigner/views/view.py +++ b/src/seedsigner/views/view.py @@ -121,12 +121,6 @@ class Destination: # ######################################################################################### class MainMenuView(View): - def __init__(self): - super().__init__() - # always clear psbt state when on Main Menu - self.controller.clear_psbt_data() - - def run(self): from .seed_views import SeedsMenuView from .settings_views import SettingsMenuView From 293e3de34d0e6e896c7746200ef0ee1e491fe851 Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Wed, 14 Dec 2022 15:34:44 -0500 Subject: [PATCH 03/30] add clear_back_stack back --- src/seedsigner/controller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index fa045c7d..7d6e13c6 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -225,6 +225,9 @@ class Controller(Singleton): next_destination = Destination(MainMenuView) if next_destination.View_cls == MainMenuView: + # Home always wipes the back_stack + self.clear_back_stack() + # Home always wipes the back_stack/state of temp vars self.resume_main_flow = None self.multisig_wallet_descriptor = None From 118052fb4600011180d740b3d4e9ede131ac5899 Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Wed, 14 Dec 2022 15:35:11 -0500 Subject: [PATCH 04/30] typo --- src/seedsigner/controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 7d6e13c6..19b40935 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -226,7 +226,7 @@ class Controller(Singleton): if next_destination.View_cls == MainMenuView: # Home always wipes the back_stack - self.clear_back_stack() + self.clear_back_stack() # Home always wipes the back_stack/state of temp vars self.resume_main_flow = None From f9e39cb4d502330ec4c7faba21d71bbab82bd4ff Mon Sep 17 00:00:00 2001 From: Jean Do Date: Thu, 5 Jan 2023 12:32:26 -0400 Subject: [PATCH 05/30] whitespace around passphrase not stripped. --- src/seedsigner/gui/screens/seed_screens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 9d29ff83..112c9cde 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -838,7 +838,7 @@ class SeedAddPassphraseScreen(BaseTopNavScreen): self.renderer.show_image() if len(self.passphrase) > 0: - return self.passphrase.strip() + return self.passphrase elif input == HardwareButtonsConstants.KEY_PRESS and self.top_nav.is_selected: # Back button clicked @@ -1495,4 +1495,4 @@ class SeedBIP85SelectChildIndexScreen(KeyboardScreen): self.keys_charset = "0123456789" self.show_save_button = True - super().__post_init__() \ No newline at end of file + super().__post_init__() From 21a93f2d220467325b4d7b470e683ad1903d0e14 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Thu, 5 Jan 2023 13:13:10 -0400 Subject: [PATCH 06/30] An empty passphrase IS valid: goto finalize. --- src/seedsigner/gui/screens/seed_screens.py | 4 +--- src/seedsigner/views/seed_views.py | 7 +++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 112c9cde..038c1d72 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -836,9 +836,7 @@ class SeedAddPassphraseScreen(BaseTopNavScreen): self.hw_button3.is_selected = True self.hw_button3.render() self.renderer.show_image() - - if len(self.passphrase) > 0: - return self.passphrase + return self.passphrase elif input == HardwareButtonsConstants.KEY_PRESS and self.top_nav.is_selected: # Back button clicked diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index c935ee7b..4dc9c454 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -245,9 +245,12 @@ class SeedAddPassphraseView(View): if ret == RET_CODE__BACK_BUTTON: return Destination(BackStackView) - # The new passphrase will be the return value + # The new passphrase will be the return value; it might be empty. self.seed.set_passphrase(ret) - return Destination(SeedReviewPassphraseView) + if len(self.seed.passphrase) > 0: + return Destination(SeedReviewPassphraseView) + else: + return Destination(SeedFinalizeView) From f99b1368b8895e4e731694221e223afdf0e73719 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Fri, 6 Jan 2023 05:27:48 -0400 Subject: [PATCH 07/30] display spacey passphrase in dbl-quotes --- src/seedsigner/gui/screens/seed_screens.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 038c1d72..a4d6267e 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -1016,6 +1016,8 @@ class SeedReviewPassphraseScreen(ButtonListScreen): screen_y = self.buttons[0].screen_y - GUIConstants.COMPONENT_PADDING - int(GUIConstants.BODY_FONT_SIZE*2.5) )) + if self.passphrase != self.passphrase.strip(): + self.passphrase = '"{}"'.format(self.passphrase) available_height = self.components[-1].screen_y - self.top_nav.height + GUIConstants.COMPONENT_PADDING max_font_size = GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 8 min_font_size = GUIConstants.TOP_NAV_TITLE_FONT_SIZE - 4 From 6de2458766cdbf74edb9a58ed83ee4907df5b9b3 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Sun, 8 Jan 2023 07:41:08 -0400 Subject: [PATCH 08/30] if leading/trailing/consecutive spaces in passphrase, replace w/left-7/8s-block --- src/seedsigner/gui/screens/seed_screens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index a4d6267e..f41e3e8a 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -1016,8 +1016,8 @@ class SeedReviewPassphraseScreen(ButtonListScreen): screen_y = self.buttons[0].screen_y - GUIConstants.COMPONENT_PADDING - int(GUIConstants.BODY_FONT_SIZE*2.5) )) - if self.passphrase != self.passphrase.strip(): - self.passphrase = '"{}"'.format(self.passphrase) + if self.passphrase != self.passphrase.strip() or " " in self.passphrase: + self.passphrase = self.passphrase.replace(" ", "\u2589") available_height = self.components[-1].screen_y - self.top_nav.height + GUIConstants.COMPONENT_PADDING max_font_size = GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 8 min_font_size = GUIConstants.TOP_NAV_TITLE_FONT_SIZE - 4 From 18892e11ed8f399a9c02c445ae2f78b33d27bd72 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 10 Jan 2023 13:27:58 -0400 Subject: [PATCH 09/30] typo 'visibiilty' becomes 'visibility' in .get_settings_entries() --- src/seedsigner/models/settings_definition.py | 4 ++-- src/seedsigner/views/settings_views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py index 64936fdc..ed4d5ab4 100644 --- a/src/seedsigner/models/settings_definition.py +++ b/src/seedsigner/models/settings_definition.py @@ -483,10 +483,10 @@ class SettingsDefinition: @classmethod - def get_settings_entries(cls, visibiilty: str = SettingsConstants.VISIBILITY__GENERAL) -> List[SettingsEntry]: + def get_settings_entries(cls, visibility: str = SettingsConstants.VISIBILITY__GENERAL) -> List[SettingsEntry]: entries = [] for entry in cls.settings_entries: - if entry.visibility == visibiilty: + if entry.visibility == visibility: entries.append(entry) return entries diff --git a/src/seedsigner/views/settings_views.py b/src/seedsigner/views/settings_views.py index 9aeb0dcb..655b61d1 100644 --- a/src/seedsigner/views/settings_views.py +++ b/src/seedsigner/views/settings_views.py @@ -20,7 +20,7 @@ class SettingsMenuView(View): DONATE = "Donate" settings_entries = SettingsDefinition.get_settings_entries( - visibiilty=self.visibility + visibility=self.visibility ) button_data=[e.display_name for e in settings_entries] From 5f08c5970b113687bc366bee9bc8f1b65cc9cca7 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Wed, 11 Jan 2023 07:22:34 -0400 Subject: [PATCH 10/30] bip85 child seeds via embit.bip85.derive_mnemonic() --- requirements.txt | 2 +- src/seedsigner/models/seed.py | 21 ++------------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/requirements.txt b/requirements.txt index 029b0983..34cf5663 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -embit==0.6.1 +embit==0.7.0 numpy==1.21.1 picamera==1.13 Pillow==8.2.0 diff --git a/src/seedsigner/models/seed.py b/src/seedsigner/models/seed.py index f050e1b8..4cbc022b 100644 --- a/src/seedsigner/models/seed.py +++ b/src/seedsigner/models/seed.py @@ -1,7 +1,7 @@ import unicodedata from binascii import hexlify -from embit import bip39, bip32 +from embit import bip39, bip32, bip85 from embit.networks import NETWORKS from typing import List @@ -121,25 +121,8 @@ class Seed: root = bip32.HDKey.from_seed(self.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(network)]["xprv"]) # TODO: Support other bip-39 wordlist languages! - # As per the BIP85 spec, 39 is the application number used for bip39 mnemonic, - # we use the selected word count and the index to form the full path. - path = "m/83696968'/39'/0'/{bip85_num_words}'/{bip85_index}'".format( - bip85_num_words=bip85_num_words, - bip85_index=bip85_index) - # Derive the child xprv (HDKey) using the path format defined above - xprv = root.derive(path) - - # The xprv.secret plus the BIP85 key is hashed together using hmac sha512. - entropy = hmac.new(key=b'bip-entropy-from-k', msg=xprv.secret, digestmod=hashlib.sha512).digest() - - # Calculate number of bytes to retain for the entropy - # 24 words the width is 32bytes (256bits) - # 12 words the width is 16bytes (128bits) - width = round(bip85_num_words / 12 * 16) - - # Return the derived BIP85 child mnemonic using the truncated derived entropy - return bip39.mnemonic_from_bytes(entropy[:width]) + return bip85.derive_mnemonic(root, bip85_num_words, bip85_index) ### override operators From ab6f7b7d2e919db9f3547107467077be14aac2db Mon Sep 17 00:00:00 2001 From: Jean Do Date: Wed, 11 Jan 2023 08:34:03 -0400 Subject: [PATCH 11/30] importing hmac,hashlib no longer necessary --- src/seedsigner/models/seed.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/seedsigner/models/seed.py b/src/seedsigner/models/seed.py index 4cbc022b..36f4a7b5 100644 --- a/src/seedsigner/models/seed.py +++ b/src/seedsigner/models/seed.py @@ -8,9 +8,6 @@ from typing import List from seedsigner.models.settings import SettingsConstants from seedsigner.helpers import embit_utils -import hashlib -import hmac - class InvalidSeedException(Exception): pass From d2278c793f78e80cfc3e534dffc1d6ad5abe5375 Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Fri, 13 Jan 2023 22:39:18 -0500 Subject: [PATCH 12/30] Update README.md to resolve broken SeedQR Links This should resolve Issue #300. This was broken because the templates were updated in PR #293 Added both dot, grid and trading card style templates available in the repo. --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 16e1160e..dd82f10e 100644 --- a/README.md +++ b/README.md @@ -170,13 +170,19 @@ You can use SeedSigner to export your seed to a hand-transcribed SeedQR format t Standard SeedQR templates: -* [12-word SeedQR template (25x25)](docs/seed_qr/printable_templates/12words_seedqr_template.pdf) -* [24-word SeedQR template (29x29)](docs/seed_qr/printable_templates/24words_seedqr_template.pdf) -* [Baseball card template: 24-word SeedQR (29x29)](docs/seed_qr/printable_templates/Seed_QR_Card.pdf) +* [12-word SeedQR template dots (25x25)](docs/seed_qr/printable_templates/dots_25x25.pdf) +* [24-word SeedQR template dots (29x29)](docs/seed_qr/printable_templates/dots_29x29.pdf) +* [12-word SeedQR template grid (25x25)](docs/seed_qr/printable_templates/grid_25x25.pdf) +* [24-word SeedQR template grid (29x29)](docs/seed_qr/printable_templates/grid_29x29.pdf) +* [Baseball card template: 24-word SeedQR (29x29)](docs/seed_qr/printable_templates/trading_card_29x29_w24words.pdf) CompactSeedQR templates: -* [12-word CompactSeedQR template (21x21)](docs/seed_qr/printable_templates/compact_seedqr/12words_compactseedqr_template.pdf) -* [24-word CompactSeedQR template (25x25)](docs/seed_qr/printable_templates/compact_seedqr/24words_compactseedqr_template.pdf) +* [12-word CompactSeedQR template dots (21x21)](docs/seed_qr/printable_templates/compact_seedqr/dots_21x21.pdf) +* [24-word CompactSeedQR template dots (25x25)](docs/seed_qr/printable_templates/compact_seedqr/dots_25x25.pdf) +* [12-word CompactSeedQR template grid (21x21)](docs/seed_qr/printable_templates/compact_seedqr/grid_21x21.pdf) +* [24-word CompactSeedQR template grid (25x25)](docs/seed_qr/printable_templates/compact_seedqr/grid_25x25.pdf) +* [Baseball card template: 12-word Compact SeedQR (29x29)](docs/seed_qr/printable_templates/trading_card_21x21_w12words.pdf) +* [Baseball card template: 24-word Compact SeedQR (29x29)](docs/seed_qr/printable_templates/trading_card_25x25_w24words.pdf) _note: CompactSeedQR is an advanced feature that can be enabled in Settings_ From 51f0bba77e65297efc8b242e127ed61a4dfc5073 Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Fri, 13 Jan 2023 23:03:22 -0500 Subject: [PATCH 13/30] Bump version to 0.5.2 --- src/seedsigner/controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 7adb6457..1076ff5d 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -50,7 +50,7 @@ class Controller(Singleton): rather than at the top in order avoid circular imports. """ - VERSION = "0.5.1" + VERSION = "0.5.2" # Declare class member vars with type hints to enable richer IDE support throughout # the code. From bf7644fc1176e35cadbad680225a133dacc25687 Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Fri, 13 Jan 2023 23:09:09 -0500 Subject: [PATCH 14/30] Make Compact SeedQR default as enabled Compact SeedQR is currently disabled by default. For the 0.5.2 release, this setting will be changed to be enabled by default. --- src/seedsigner/models/settings_definition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py index 64936fdc..1c7483b1 100644 --- a/src/seedsigner/models/settings_definition.py +++ b/src/seedsigner/models/settings_definition.py @@ -438,7 +438,7 @@ class SettingsDefinition: attr_name=SettingsConstants.SETTING__COMPACT_SEEDQR, display_name="CompactSeedQR", visibility=SettingsConstants.VISIBILITY__ADVANCED, - default_value=SettingsConstants.OPTION__DISABLED), + default_value=SettingsConstants.OPTION__ENABLED), SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES, attr_name=SettingsConstants.SETTING__BIP85_CHILD_SEEDS, From 6d00a2028c8520d85077e2a8b3826bfcb26217de Mon Sep 17 00:00:00 2001 From: Jean Do Date: Sat, 14 Jan 2023 13:18:32 -0400 Subject: [PATCH 15/30] coding style clean-up --- src/seedsigner/models/seed.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/seedsigner/models/seed.py b/src/seedsigner/models/seed.py index 36f4a7b5..94dfbff6 100644 --- a/src/seedsigner/models/seed.py +++ b/src/seedsigner/models/seed.py @@ -111,14 +111,11 @@ class Seed: return embit_utils.get_xpub(seed_bytes=self.seed_bytes, derivation_path=wallet_path, embit_network=SettingsConstants.map_network_to_embit(network)) - # Derives a BIP85 mnemonic (seed word) from the master seed words using embit functions def get_bip85_child_mnemonic(self, bip85_index: int, bip85_num_words: int, network: str = SettingsConstants.MAINNET): - - # Calculate the master bip32 root key from the parents bip39 seed_bytes (the mnemonic entropy) + """Derives the seed's nth BIP-85 child mnemonic""" root = bip32.HDKey.from_seed(self.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(network)]["xprv"]) # TODO: Support other bip-39 wordlist languages! - return bip85.derive_mnemonic(root, bip85_num_words, bip85_index) From 927cc0ea98c31650d57d18667ab1744808dc28e7 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Sat, 14 Jan 2023 15:12:53 -0400 Subject: [PATCH 16/30] "bip-39" in todo comments becomes "BIP-39" --- src/seedsigner/models/seed.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/models/seed.py b/src/seedsigner/models/seed.py index 94dfbff6..7a848ecd 100644 --- a/src/seedsigner/models/seed.py +++ b/src/seedsigner/models/seed.py @@ -34,7 +34,7 @@ class Seed: @staticmethod def get_wordlist(wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> List[str]: - # TODO: Support other bip-39 wordlist languages! + # TODO: Support other BIP-39 wordlist languages! if wordlist_language_code == SettingsConstants.WORDLIST_LANGUAGE__ENGLISH: return bip39.WORDLIST else: @@ -98,7 +98,7 @@ class Seed: def set_wordlist_language_code(self, language_code: str): - # TODO: Support other bip-39 wordlist languages! + # TODO: Support other BIP-39 wordlist languages! raise Exception("Not yet implemented!") @@ -115,7 +115,7 @@ class Seed: """Derives the seed's nth BIP-85 child mnemonic""" root = bip32.HDKey.from_seed(self.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(network)]["xprv"]) - # TODO: Support other bip-39 wordlist languages! + # TODO: Support other BIP-39 wordlist languages! return bip85.derive_mnemonic(root, bip85_num_words, bip85_index) From 7e990029260d0466314f07ecd732261a17215a1a Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Mon, 16 Jan 2023 21:35:15 -0500 Subject: [PATCH 17/30] Update setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index cc8d8fb3..9aa1e28b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh: setuptools.setup( name="seedsigner", - version="0.5.0", + version="0.5.2", author="SeedSigner", author_email="author@example.com", description="Build an offline, airgapped Bitcoin signing device for less than $50!", @@ -23,4 +23,4 @@ setuptools.setup( package_dir={"": "src"}, packages=setuptools.find_packages(where="src"), python_requires=">=3.6", -) \ No newline at end of file +) From d91c46b6c5b736c95690d2bab66942cd6d3478c5 Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Mon, 16 Jan 2023 21:45:12 -0500 Subject: [PATCH 18/30] Update README.md --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dd82f10e..64a7071b 100644 --- a/README.md +++ b/README.md @@ -177,14 +177,12 @@ Standard SeedQR templates: * [Baseball card template: 24-word SeedQR (29x29)](docs/seed_qr/printable_templates/trading_card_29x29_w24words.pdf) CompactSeedQR templates: -* [12-word CompactSeedQR template dots (21x21)](docs/seed_qr/printable_templates/compact_seedqr/dots_21x21.pdf) -* [24-word CompactSeedQR template dots (25x25)](docs/seed_qr/printable_templates/compact_seedqr/dots_25x25.pdf) -* [12-word CompactSeedQR template grid (21x21)](docs/seed_qr/printable_templates/compact_seedqr/grid_21x21.pdf) -* [24-word CompactSeedQR template grid (25x25)](docs/seed_qr/printable_templates/compact_seedqr/grid_25x25.pdf) -* [Baseball card template: 12-word Compact SeedQR (29x29)](docs/seed_qr/printable_templates/trading_card_21x21_w12words.pdf) -* [Baseball card template: 24-word Compact SeedQR (29x29)](docs/seed_qr/printable_templates/trading_card_25x25_w24words.pdf) - -_note: CompactSeedQR is an advanced feature that can be enabled in Settings_ +* [12-word CompactSeedQR template dots (21x21)](docs/seed_qr/printable_templates/dots_21x21.pdf) +* [24-word CompactSeedQR template dots (25x25)](docs/seed_qr/printable_templates/dots_25x25.pdf) +* [12-word CompactSeedQR template grid (21x21)](docs/seed_qr/printable_templates/grid_21x21.pdf) +* [24-word CompactSeedQR template grid (25x25)](docs/seed_qr/printable_templates/grid_25x25.pdf) +* [Baseball card template: 12-word Compact SeedQR (21x21)](docs/seed_qr/printable_templates/trading_card_21x21_w12words.pdf) +* [Baseball card template: 24-word Compact SeedQR (25x25)](docs/seed_qr/printable_templates/trading_card_25x25_w24words.pdf) --------------- From b29d9cae7b203f38b3f26a699c5b894886b3497b Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 17 Jan 2023 20:53:19 -0400 Subject: [PATCH 19/30] foreach test: fixture sets-up, runs-test, tears-down --- tests/test_controller.py | 67 ++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/tests/test_controller.py b/tests/test_controller.py index b6887369..13f4c62a 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -1,29 +1,34 @@ import configparser import pytest from mock import MagicMock +from seedsigner.hardware.microsd import MicroSD from seedsigner.controller import Controller -from seedsigner.models.settings import Settings from seedsigner.models.settings_definition import SettingsConstants -def test_singleton_init_fails(): +@pytest.fixture() +def reset_controller(): + print("\nEntering reset_controller fixture; setting up...") + Controller.configure_instance(disable_hardware=True) + print("...done setting up; yielding to run a test...") + yield + print("\n...back from the test; tearing down...") + MicroSD._instance = None + Controller._instance = None + print("...done tearing down; leaving reset_controller fixture.") + + +def test_singleton_init_fails(reset_controller): """ The Controller should not allow any code to instantiate it via Controller() """ with pytest.raises(Exception): c = Controller() -def test_singleton_get_instance_preserves_state(): +def test_singleton_get_instance_preserves_state(reset_controller): """ Changes to the Controller singleton should be preserved across calls to get_instance() """ - # Must reset Singleton instances; pytest cannot properly isolate Singletons for us - # automatically. - # TODO: Cleaner solution here would be nice. - Settings._instance = None - Controller._instance = None - # Initialize the instance and verify that it read the config settings - Controller.configure_instance(disable_hardware=True) controller = Controller.get_instance() assert controller.unverified_address is None @@ -35,19 +40,33 @@ def test_singleton_get_instance_preserves_state(): assert controller.unverified_address == "123abc" -def test_missing_settings_get_defaults(): - """ Should gracefully handle any missing fields from `settings.ini` """ - # TODO: This is not complete; currently only handles missing compact_seedqr_enabled. +def test_missing_settings_get_defaults(reset_controller): + """ Should gracefully handle all missing fields from `settings.json` """ - # Must reset Singleton instances; pytest cannot properly isolate Singletons for us - # automatically. - # TODO: Cleaner solution here would be nice. - Settings._instance = None - Controller._instance = None - - # Controller should parse the settings fine, even though a field is missing - Controller.configure_instance(disable_hardware=True) - - # Controller should still have a default value controller = Controller.get_instance() - assert controller.settings.get_value(SettingsConstants.SETTING__COMPACT_SEEDQR) == SettingsConstants.OPTION__DISABLED + + # Settings defaults + assert controller.settings.get_value(SettingsConstants.SETTING__LANGUAGE) == SettingsConstants.LANGUAGE__ENGLISH + assert controller.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) == SettingsConstants.WORDLIST_LANGUAGE__ENGLISH + assert controller.settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == SettingsConstants.OPTION__DISABLED + assert controller.settings.get_value(SettingsConstants.SETTING__COORDINATORS) == [i for i,j in SettingsConstants.ALL_COORDINATORS] + assert controller.settings.get_value(SettingsConstants.SETTING__BTC_DENOMINATION) == SettingsConstants.BTC_DENOMINATION__THRESHOLD + + # Advanced Settings defaults + assert controller.settings.get_value(SettingsConstants.SETTING__NETWORK) == SettingsConstants.MAINNET + assert controller.settings.get_value(SettingsConstants.SETTING__QR_DENSITY) == SettingsConstants.DENSITY__MEDIUM + assert controller.settings.get_value(SettingsConstants.SETTING__XPUB_EXPORT) == SettingsConstants.OPTION__ENABLED + assert controller.settings.get_value(SettingsConstants.SETTING__SIG_TYPES) == [i for i,j in SettingsConstants.ALL_SIG_TYPES] + assert controller.settings.get_value(SettingsConstants.SETTING__SCRIPT_TYPES) == [SettingsConstants.NATIVE_SEGWIT, SettingsConstants.NESTED_SEGWIT] + assert controller.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__ENABLED + assert controller.settings.get_value(SettingsConstants.SETTING__PASSPHRASE) == SettingsConstants.OPTION__ENABLED + assert controller.settings.get_value(SettingsConstants.SETTING__CAMERA_ROTATION) == SettingsConstants.CAMERA_ROTATION__0 + assert controller.settings.get_value(SettingsConstants.SETTING__COMPACT_SEEDQR) == SettingsConstants.OPTION__ENABLED + assert controller.settings.get_value(SettingsConstants.SETTING__BIP85_CHILD_SEEDS) == SettingsConstants.OPTION__DISABLED + assert controller.settings.get_value(SettingsConstants.SETTING__PRIVACY_WARNINGS) == SettingsConstants.OPTION__ENABLED + assert controller.settings.get_value(SettingsConstants.SETTING__DIRE_WARNINGS) == SettingsConstants.OPTION__ENABLED + assert controller.settings.get_value(SettingsConstants.SETTING__PARTNER_LOGOS) == SettingsConstants.OPTION__ENABLED + + # Hidden Settings defaults + assert controller.settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS) == 189 + From a8e5e3a0fb9f5a3251dde2fe7afa6d139a036c23 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Thu, 19 Jan 2023 15:38:41 -0400 Subject: [PATCH 20/30] verbose print statements removed from reset_controller fixture. --- tests/test_controller.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_controller.py b/tests/test_controller.py index 13f4c62a..99c6f7b3 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -9,14 +9,17 @@ from seedsigner.models.settings_definition import SettingsConstants @pytest.fixture() def reset_controller(): - print("\nEntering reset_controller fixture; setting up...") + """fixture to setup, then yield to run test, then tear down""" + + # setup Controller.configure_instance(disable_hardware=True) - print("...done setting up; yielding to run a test...") + + # yield to run a single test yield - print("\n...back from the test; tearing down...") + + # tear down MicroSD._instance = None Controller._instance = None - print("...done tearing down; leaving reset_controller fixture.") def test_singleton_init_fails(reset_controller): From ef08cd830672600a541c709b6ba44a43af75c579 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Mon, 23 Jan 2023 17:35:36 -0400 Subject: [PATCH 21/30] improved ui: "BIP-39 Passphrase" * while finalizing: button "Add Passphrase" becomes "BIP-39 Passphrase" w/o lock icon * while adding/editing: title "Add Passphrase" becomes "BIP-39 Passphrase" --- src/seedsigner/gui/screens/seed_screens.py | 2 +- src/seedsigner/views/seed_views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index f41e3e8a..ab5d8ed4 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -616,7 +616,7 @@ class SeedExportXpubDetailsScreen(WarningEdgesMixin, ButtonListScreen): @dataclass class SeedAddPassphraseScreen(BaseTopNavScreen): - title: str = "Add Passphrase" + title: str = "BIP-39 Passphrase" passphrase: str = "" KEYBOARD__LOWERCASE_BUTTON_TEXT = "abc" diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 39a5a1ad..8c24d851 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -211,7 +211,7 @@ class SeedFinalizeView(View): def run(self): FINALIZE = "Done" - PASSPHRASE = ("Add Passphrase", FontAwesomeIconConstants.LOCK) + PASSPHRASE = "BIP-39 Passphrase" button_data = [] button_data.append(FINALIZE) From 360d4be9d1a7abb21d6ad925454b16105cb3eb2f Mon Sep 17 00:00:00 2001 From: Jean Do Date: Wed, 25 Jan 2023 09:39:57 -0400 Subject: [PATCH 22/30] typo fix / text changes --- docs/recovery.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/recovery.md b/docs/recovery.md index 5725dc75..035bc339 100644 --- a/docs/recovery.md +++ b/docs/recovery.md @@ -23,7 +23,7 @@ Derivation paths for standard script types for mainnet: - Script Type: P2WSH in P2SH - Public Key Encoding: 0x0295b43f - Ypub -Custom derivation paths are also optional when generating an xpub from SeedSigner. The Public Key Encodings is detected based on the derivation path configured. Embit bitcoin library does the detection and is documented [here](https://github.com/diybitcoinhardware/embit/blob/master/docs/api/bip32.md#detect_version). For a video explination of these standards see a presentation by Stepan of Embit/Specter on this topic: https://youtube.com/watch?v=JCaC5DG2HTM +Custom derivation paths are also optional when generating an xpub from SeedSigner. The Public Key Encodings are detected based on the derivation path configured. The Embit bitcoin library does this detection and is documented [here](https://github.com/diybitcoinhardware/embit/blob/master/docs/api/bip32.md#detect_version). For a video explanation of these standards see a presentation by Stepan of Embit/Specter on this topic: https://youtube.com/watch?v=JCaC5DG2HTM Changing the network settings from main to test in SeedSigner will change the public key encoding and derivation path following [slip-0132](https://github.com/satoshilabs/slips/blob/master/slip-0132.md) standards. @@ -32,4 +32,4 @@ Related Standards: - [bip-0044](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) - [bip-0048](https://github.com/bitcoin/bips/blob/master/bip-0048.mediawiki) - [bip-0049](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki) -- [bip-0084](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) \ No newline at end of file +- [bip-0084](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) From ccf8ce1e84743787590e87925268c436231551a5 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Wed, 25 Jan 2023 12:08:22 -0400 Subject: [PATCH 23/30] conditional power-off: 'Powering Off' or 'Just Unplug It' --- src/seedsigner/gui/screens/screen.py | 17 ++++++++++++++++- src/seedsigner/views/view.py | 20 +++++++++----------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index 3da014a0..c0e01f3b 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -906,6 +906,21 @@ class PowerOffScreen(BaseTopNavScreen): +@dataclass +class PowerOffNotRequiredScreen(BaseTopNavScreen): + def __post_init__(self): + self.title = "Just Unplug It" + self.show_back_button = True + super().__post_init__() + + self.components.append(TextArea( + text="In SeedSigner-OS, it is safe to disconnect power at any time.", + screen_y=self.top_nav.height, + height=self.canvas_height - self.top_nav.height, + )) + + + @dataclass class KeyboardScreen(BaseTopNavScreen): """ @@ -1149,4 +1164,4 @@ class MicroSDToastScreen(BaseScreen): label_text="MicroSD inserted" ) - self.toast.render() \ No newline at end of file + self.toast.render() diff --git a/src/seedsigner/views/view.py b/src/seedsigner/views/view.py index 50b1c40a..02543c4d 100644 --- a/src/seedsigner/views/view.py +++ b/src/seedsigner/views/view.py @@ -3,7 +3,7 @@ from typing import List from seedsigner.gui.components import FontAwesomeIconConstants, GUIConstants from seedsigner.gui.screens import RET_CODE__POWER_BUTTON -from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, DireWarningScreen, LargeButtonScreen, PowerOffScreen, ResetScreen, WarningScreen +from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, DireWarningScreen, LargeButtonScreen, PowerOffScreen, PowerOffNotRequiredScreen, ResetScreen, WarningScreen from seedsigner.models.threads import BaseThread from seedsigner.models import Settings @@ -199,9 +199,13 @@ class RestartView(View): class PowerOffView(View): def run(self): - thread = PowerOffView.PowerOffThread() - thread.start() - PowerOffScreen().display() + if Settings.HOSTNAME == Settings.SEEDSIGNER_OS: + PowerOffNotRequiredScreen().display() + return Destination(BackStackView) + else: + thread = PowerOffView.PowerOffThread() + thread.start() + PowerOffScreen().display() class PowerOffThread(BaseThread): @@ -210,13 +214,7 @@ class PowerOffView(View): from subprocess import call while self.keep_running: time.sleep(5) - if Settings.HOSTNAME == Settings.SEEDSIGNER_OS: - # disable microsd detection before shutdown to prevent display of toast notification during shutdown - from seedsigner.controller import Controller - Controller.get_instance().microsd.stop() - call("poweroff", shell=True) - else: - call("sudo shutdown --poweroff now", shell=True) + call("sudo shutdown --poweroff now", shell=True) From 231357a2db2c64ba66321eff69386ae285a4af6c Mon Sep 17 00:00:00 2001 From: Jean Do Date: Thu, 26 Jan 2023 17:39:52 -0400 Subject: [PATCH 24/30] Embit becomes `embit`; removed Specter --- docs/recovery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recovery.md b/docs/recovery.md index 035bc339..7e8569ff 100644 --- a/docs/recovery.md +++ b/docs/recovery.md @@ -23,7 +23,7 @@ Derivation paths for standard script types for mainnet: - Script Type: P2WSH in P2SH - Public Key Encoding: 0x0295b43f - Ypub -Custom derivation paths are also optional when generating an xpub from SeedSigner. The Public Key Encodings are detected based on the derivation path configured. The Embit bitcoin library does this detection and is documented [here](https://github.com/diybitcoinhardware/embit/blob/master/docs/api/bip32.md#detect_version). For a video explanation of these standards see a presentation by Stepan of Embit/Specter on this topic: https://youtube.com/watch?v=JCaC5DG2HTM +Custom derivation paths are also optional when generating an xpub from SeedSigner. The Public Key Encodings are detected based on the derivation path configured. The `embit` bitcoin library does this detection and is documented [here](https://github.com/diybitcoinhardware/embit/blob/master/docs/api/bip32.md#detect_version). For a video explanation of these standards see a presentation by Stepan of `embit` on this topic: https://youtube.com/watch?v=JCaC5DG2HTM Changing the network settings from main to test in SeedSigner will change the public key encoding and derivation path following [slip-0132](https://github.com/satoshilabs/slips/blob/master/slip-0132.md) standards. From 98679505a3611f4443616b5c26b55d7ca0fc60ec Mon Sep 17 00:00:00 2001 From: Jean Do Date: Thu, 26 Jan 2023 17:43:32 -0400 Subject: [PATCH 25/30] No mention of OS since it's the new standard. --- src/seedsigner/gui/screens/screen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index c0e01f3b..e32bf984 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -914,7 +914,7 @@ class PowerOffNotRequiredScreen(BaseTopNavScreen): super().__post_init__() self.components.append(TextArea( - text="In SeedSigner-OS, it is safe to disconnect power at any time.", + text="It is safe to disconnect power at any time.", screen_y=self.top_nav.height, height=self.canvas_height - self.top_nav.height, )) From 0a377a8db8d0a33f0b354c2ba173b88728172de8 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Fri, 27 Jan 2023 10:30:46 -0400 Subject: [PATCH 26/30] Denomination: Btc-only/Sats-only become BTC/sats --- src/seedsigner/models/settings_definition.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py index 273c78b9..e69478f3 100644 --- a/src/seedsigner/models/settings_definition.py +++ b/src/seedsigner/models/settings_definition.py @@ -53,10 +53,10 @@ class SettingsConstants: BTC_DENOMINATION__THRESHOLD = "thr" BTC_DENOMINATION__BTCSATSHYBRID = "hyb" ALL_BTC_DENOMINATIONS = [ - (BTC_DENOMINATION__BTC, "Btc-only"), - (BTC_DENOMINATION__SATS, "Sats-only"), + (BTC_DENOMINATION__BTC, "BTC"), + (BTC_DENOMINATION__SATS, "sats"), (BTC_DENOMINATION__THRESHOLD, "Threshold at 0.01"), - (BTC_DENOMINATION__BTCSATSHYBRID, "Btc | Sats hybrid"), + (BTC_DENOMINATION__BTCSATSHYBRID, "BTC | sats hybrid"), ] CAMERA_ROTATION__0 = 0 From 7481b45b9f03e2b872db8a487cdb1e3b96082036 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 28 Jan 2023 07:52:13 -0600 Subject: [PATCH 27/30] Preserve list state in Advanced settings --- src/seedsigner/gui/screens/screen.py | 10 +++++----- src/seedsigner/views/settings_views.py | 25 +++++++++++++++++-------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index e32bf984..50a94e70 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -265,6 +265,9 @@ class ButtonListScreen(BaseTopNavScreen): Button_cls = Button checked_buttons: List[int] = None + # Enables returning w/buttons rendered at the same place + scroll_y_initial_offset: int = None + def __post_init__(self): super().__post_init__() @@ -315,6 +318,7 @@ class ButtonListScreen(BaseTopNavScreen): right_icon_name=right_icon_name, screen_x=GUIConstants.EDGE_PADDING, screen_y=button_list_y + i * (button_height + GUIConstants.LIST_ITEM_PADDING), + scroll_y=self.scroll_y_initial_offset if self.scroll_y_initial_offset is not None else 0, width=self.canvas_width - (2 * GUIConstants.EDGE_PADDING), height=button_height, is_text_centered=self.is_button_text_centered, @@ -330,7 +334,7 @@ class ButtonListScreen(BaseTopNavScreen): if self.has_scroll_arrows: self.arrow_half_width = 10 - self.cur_scroll_y = 0 + self.cur_scroll_y = self.scroll_y_initial_offset if self.scroll_y_initial_offset is not None else 0 self.up_arrow_img = Image.new("RGBA", size=(2 * self.arrow_half_width, 8), color="black") self.up_arrow_img_y = self.top_nav.height - 12 arrow_draw = ImageDraw.Draw(self.up_arrow_img) @@ -346,10 +350,6 @@ class ButtonListScreen(BaseTopNavScreen): cur_selected_button = self.buttons[self.selected_button] cur_selected_button.is_selected = True - if self.has_scroll_arrows: - frame_scroll = self.buttons[0].screen_y - cur_selected_button.screen_y - for button in self.buttons: - button.scroll_y -= frame_scroll def _render(self): diff --git a/src/seedsigner/views/settings_views.py b/src/seedsigner/views/settings_views.py index 655b61d1..7d5ef6c0 100644 --- a/src/seedsigner/views/settings_views.py +++ b/src/seedsigner/views/settings_views.py @@ -9,11 +9,14 @@ from seedsigner.models.settings import SettingsConstants, SettingsDefinition class SettingsMenuView(View): - def __init__(self, visibility: str = SettingsConstants.VISIBILITY__GENERAL, selected_attr: str = None): + def __init__(self, visibility: str = SettingsConstants.VISIBILITY__GENERAL, selected_attr: str = None, initial_scroll: int = 0): super().__init__() self.visibility = visibility self.selected_attr = selected_attr + # Used to preserve the rendering position in the list + self.initial_scroll = initial_scroll + def run(self): IO_TEST = "I/O test" @@ -53,12 +56,17 @@ class SettingsMenuView(View): title = "Dev Options" next = None - selected_menu_num = ButtonListScreen( + screen = ButtonListScreen( title=title, is_button_text_centered=False, button_data=button_data, selected_button=selected_button, - ).display() + scroll_y_initial_offset=self.initial_scroll, + ) + selected_menu_num = screen.display() + + # Preserve our scroll position in this Screen so we can return + initial_scroll = screen.buttons[0].scroll_y if selected_menu_num == RET_CODE__BACK_BUTTON: if self.visibility == SettingsConstants.VISIBILITY__GENERAL: @@ -78,8 +86,7 @@ class SettingsMenuView(View): return Destination(DonateView) else: - # TODO: Free-entry types (are there any?) will need their own SettingsEntryUpdateFreeEntryView(?). - return Destination(SettingsEntryUpdateSelectionView, view_args={"attr_name": settings_entries[selected_menu_num].attr_name}) + return Destination(SettingsEntryUpdateSelectionView, view_args=dict(attr_name=settings_entries[selected_menu_num].attr_name, parent_initial_scroll=initial_scroll)) @@ -88,10 +95,11 @@ class SettingsEntryUpdateSelectionView(View): Handles changes to all selection-type settings (Multiselect, SELECT_1, Enabled/Disabled, etc). """ - def __init__(self, attr_name: str): + def __init__(self, attr_name: str, parent_initial_scroll: int = 0): super().__init__() self.settings_entry = SettingsDefinition.get_settings_entry(attr_name) self.selected_button = None + self.parent_initial_scroll = parent_initial_scroll def run(self): @@ -129,7 +137,8 @@ class SettingsEntryUpdateSelectionView(View): SettingsMenuView, view_args={ "visibility": self.settings_entry.visibility, - "selected_attr": self.settings_entry.attr_name + "selected_attr": self.settings_entry.attr_name, + "initial_scroll": self.parent_initial_scroll, } ) @@ -188,4 +197,4 @@ class DonateView(View): def run(self): settings_screens.DonateScreen().display() - return Destination(SettingsMenuView) + return Destination(SettingsMenuView) \ No newline at end of file From 6c3b4016601697b83076ec3b8cea4cbcabe963f9 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 28 Jan 2023 08:01:41 -0600 Subject: [PATCH 28/30] Update settings_views.py --- src/seedsigner/views/settings_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/views/settings_views.py b/src/seedsigner/views/settings_views.py index 7d5ef6c0..8a13b43d 100644 --- a/src/seedsigner/views/settings_views.py +++ b/src/seedsigner/views/settings_views.py @@ -197,4 +197,4 @@ class DonateView(View): def run(self): settings_screens.DonateScreen().display() - return Destination(SettingsMenuView) \ No newline at end of file + return Destination(SettingsMenuView) From 357cd14e82c8261e0dbe97efded7b6126797bf33 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 28 Jan 2023 08:25:09 -0600 Subject: [PATCH 29/30] Updates AddressExplorer to keep scroll position --- src/seedsigner/views/tools_views.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 14a775d5..2c5fa017 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -559,11 +559,12 @@ class ToolsAddressExplorerAddressTypeView(View): class ToolsAddressExplorerAddressListView(View): - def __init__(self, is_change: bool = False, start_index: int = 0, selected_button_index: int = 0): + def __init__(self, is_change: bool = False, start_index: int = 0, selected_button_index: int = 0, initial_scroll: int = 0): super().__init__() self.is_change = is_change self.start_index = start_index self.selected_button_index = selected_button_index + self.initial_scroll = initial_scroll def run(self): @@ -632,6 +633,7 @@ class ToolsAddressExplorerAddressListView(View): is_button_text_centered=False, is_bottom_list=True, selected_button=self.selected_button_index, + scroll_y_initial_offset=self.initial_scroll, ) finally: # Everything is set. Stop the loading screen @@ -647,20 +649,23 @@ class ToolsAddressExplorerAddressListView(View): # User clicked NEXT return Destination(ToolsAddressExplorerAddressListView, view_args=dict(is_change=self.is_change, start_index=self.start_index + addrs_per_screen)) + # Preserve the list's current scroll so we can return to the same spot + initial_scroll = screen.buttons[0].scroll_y + index = selected_menu_num + self.start_index - return Destination(ToolsAddressExplorerAddressView, view_args=dict(index=index, address=addresses[selected_menu_num], is_change=self.is_change, start_index=self.start_index), skip_current_view=True) + return Destination(ToolsAddressExplorerAddressView, view_args=dict(index=index, address=addresses[selected_menu_num], is_change=self.is_change, start_index=self.start_index, parent_initial_scroll=initial_scroll), skip_current_view=True) class ToolsAddressExplorerAddressView(View): - def __init__(self, index: int, address: str, is_change: bool, start_index: int): + def __init__(self, index: int, address: str, is_change: bool, start_index: int, parent_initial_scroll: int = 0): super().__init__() self.index = index self.address = address self.is_change = is_change self.start_index = start_index + self.parent_initial_scroll = parent_initial_scroll - data = self.controller.address_explorer_data def run(self): qr_encoder = EncodeQR(qr_type=QRType.BITCOIN_ADDRESS, bitcoin_address=self.address) @@ -669,4 +674,4 @@ class ToolsAddressExplorerAddressView(View): ).display() # Exiting/Cancelling the QR display screen always returns to the list - return Destination(ToolsAddressExplorerAddressListView, view_args=dict(is_change=self.is_change, start_index=self.start_index, selected_button_index=self.index - self.start_index), skip_current_view=True) + return Destination(ToolsAddressExplorerAddressListView, view_args=dict(is_change=self.is_change, start_index=self.start_index, selected_button_index=self.index - self.start_index, initial_scroll=self.parent_initial_scroll), skip_current_view=True) From 8776e928a2e58c0db328e4ccf6c987c872d55afb Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Tue, 7 Feb 2023 14:04:46 -0500 Subject: [PATCH 30/30] bump version to 0.6.0 --- setup.py | 2 +- src/seedsigner/controller.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 9aa1e28b..82dcfb5d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh: setuptools.setup( name="seedsigner", - version="0.5.2", + version="0.6.0", author="SeedSigner", author_email="author@example.com", description="Build an offline, airgapped Bitcoin signing device for less than $50!", diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 4aa0b41c..4976d6f7 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -50,7 +50,7 @@ class Controller(Singleton): rather than at the top in order avoid circular imports. """ - VERSION = "0.5.2" + VERSION = "0.6.0" # Declare class member vars with type hints to enable richer IDE support throughout # the code.