From e5ca820416e7f267f64e7cc66850ecfdbafd0a11 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Fri, 22 Apr 2022 08:57:55 -0500 Subject: [PATCH 01/39] Options to add entropy for the final word --- src/seedsigner/gui/screens/tools_screens.py | 264 +++++++++++++++++- src/seedsigner/helpers/mnemonic_generation.py | 22 +- src/seedsigner/views/seed_views.py | 18 +- src/seedsigner/views/tools_views.py | 147 +++++++++- 4 files changed, 422 insertions(+), 29 deletions(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index 2e8b2276..c8169541 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -262,7 +262,269 @@ class ToolsDiceEntropyEntryScreen(BaseTopNavScreen): @dataclass -class ToolsCalcFinalWordShowFinalWordScreen(ButtonListScreen): +class ToolsCoinFlipEntryScreen(BaseTopNavScreen): + """ + This is a lame mega copy-paste from the dice rolls Screen. It probably isn't worth + the effort to generalize these screens. But the copy-paste is far from ideal. + """ + total_flips: int = None + + def __post_init__(self): + self.title = f"Coin Flip 1/{self.total_flips}" + super().__post_init__() + + self.coin_flips = "" + + # Set up the keyboard params + keyboard_width = self.canvas_width - 2*GUIConstants.EDGE_PADDING + text_entry_display_y = self.top_nav.height + text_entry_display_height = 30 + + keyboard_start_y = text_entry_display_y + text_entry_display_height + GUIConstants.COMPONENT_PADDING + rows = 1 + button_height = int(1.5*GUIConstants.BUTTON_FONT_SIZE + 2*GUIConstants.EDGE_PADDING) + self.keyboard_digits = Keyboard( + draw=self.renderer.draw, + charset="01", + font_size=button_height - GUIConstants.COMPONENT_PADDING, + rows=rows, + cols=4, + rect=( + GUIConstants.EDGE_PADDING, + keyboard_start_y, + GUIConstants.EDGE_PADDING + keyboard_width, + keyboard_start_y + rows * button_height + (rows - 1) * 2 + ), + auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT], + render_now=False + ) + self.keyboard_digits.set_selected_key(selected_letter="0") + + self.text_entry_display = TextEntryDisplay( + canvas=self.renderer.canvas, + rect=( + GUIConstants.EDGE_PADDING, + text_entry_display_y, + self.canvas_width - GUIConstants.EDGE_PADDING, + text_entry_display_y + text_entry_display_height + ), + cursor_mode=TextEntryDisplay.CURSOR_MODE__BAR, + is_centered=False, + ) + + + def _render(self): + super()._render() + + self.keyboard_digits.render_keys() + self.text_entry_display.render() + + self.renderer.show_image() + + + def _run(self): + cursor_position = len(self.coin_flips) + + # Start the interactive update loop + while True: + input = self.hw_inputs.wait_for( + HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN + [HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY3], + check_release=True, + release_keys=[HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY3] + ) + + # Check possible exit condition + if self.top_nav.is_selected and input == HardwareButtonsConstants.KEY_PRESS: + return RET_CODE__BACK_BUTTON + + # Process normal input + if input in [HardwareButtonsConstants.KEY_UP, HardwareButtonsConstants.KEY_DOWN] and self.top_nav.is_selected: + # We're navigating off the previous button + self.top_nav.is_selected = False + self.top_nav.render_buttons() + + # Override the actual input w/an ENTER signal for the Keyboard + if input == HardwareButtonsConstants.KEY_DOWN: + input = Keyboard.ENTER_TOP + else: + input = Keyboard.ENTER_BOTTOM + elif input in [HardwareButtonsConstants.KEY_LEFT, HardwareButtonsConstants.KEY_RIGHT] and self.top_nav.is_selected: + # ignore + continue + + ret_val = self.keyboard_digits.update_from_input(input) + + # Now process the result from the keyboard + if ret_val in Keyboard.EXIT_DIRECTIONS: + self.top_nav.is_selected = True + self.top_nav.render_buttons() + + elif ret_val in Keyboard.ADDITIONAL_KEYS and input == HardwareButtonsConstants.KEY_PRESS: + if ret_val == Keyboard.KEY_BACKSPACE["code"]: + if len(self.coin_flips) > 0: + self.coin_flips = self.coin_flips[:-1] + cursor_position -= 1 + + elif input == HardwareButtonsConstants.KEY_PRESS and ret_val not in Keyboard.ADDITIONAL_KEYS: + # User has locked in the current letter + self.coin_flips += ret_val + cursor_position += 1 + + if cursor_position == self.total_flips: + return self.coin_flips + + # Render a new TextArea over the TopNav title bar + TextArea( + text=f"Coin Flip {cursor_position + 1}/{self.total_flips}", + font_name=GUIConstants.TOP_NAV_TITLE_FONT_NAME, + font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE, + height=self.top_nav.height, + ).render() + self.top_nav.render_buttons() + + elif input in HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN: + # Live joystick movement; haven't locked this new letter in yet. + # Leave current spot blank for now. Only update the active keyboard keys + # when a selection has been locked in (KEY_PRESS) or removed ("del"). + pass + + # Render the text entry display and cursor block + self.text_entry_display.render(self.coin_flips) + + self.renderer.show_image() + + + +@dataclass +class ToolsCalcFinalWordScreen(ButtonListScreen): + selected_final_word: str = None + selected_final_bits: str = None + checksum_bits: str = None + actual_final_word: str = None + + def __post_init__(self): + self.is_bottom_list = True + super().__post_init__() + + # First what's the total bit display width and where do the checksum bits start? + bit_font_size = GUIConstants.BUTTON_FONT_SIZE + 2 + font = Fonts.get_font(GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, bit_font_size) + (left, top, bit_display_width, bit_font_height) = font.getbbox("0" * 11, anchor="lt") + (left, top, checksum_x, bottom) = font.getbbox("0" * (11 - len(self.checksum_bits)), anchor="lt") + bit_display_x = int((self.canvas_width - bit_display_width)/2) + checksum_x += bit_display_x + + # Display the user's selected final word + if self.selected_final_word: + selection_text = self.selected_final_word + keeper_selected_bits = self.selected_final_bits[:11 - len(self.checksum_bits)] + discard_selected_bits = self.selected_final_bits[-1*len(self.checksum_bits):] + else: + # User entered coin flips or all zeros + selection_text = self.selected_final_bits + keeper_selected_bits = self.selected_final_bits + discard_selected_bits = "_" * (len(self.checksum_bits)) + + self.components.append(TextArea( + text=f"""Your Selection: \"{selection_text}\"""", + screen_y=self.top_nav.height, + )) + + # ...and its associated 11 bits + screen_y=self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING + self.components.append(TextArea( + text=keeper_selected_bits, + font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, + font_size=bit_font_size, + edge_padding=0, + screen_x=bit_display_x, + screen_y=screen_y, + height=bit_font_height, + is_text_centered=False, + )) + self.components.append(TextArea( + text=discard_selected_bits, + font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, + font_color=GUIConstants.LABEL_FONT_COLOR, + font_size=bit_font_size, + edge_padding=0, + screen_x=checksum_x, + screen_y=screen_y, + height=bit_font_height, + is_text_centered=False, + )) + + # Show the checksum + self.components.append(TextArea( + text="Checksum", + edge_padding=0, + screen_y=self.components[-1].screen_y + self.components[-1].height + 2*GUIConstants.COMPONENT_PADDING, + )) + + # ...and its actual bits + checksum_spacer = "_" * (11 - len(self.checksum_bits)) + + screen_y = self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING + self.components.append(TextArea( + text=checksum_spacer, + font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, + font_color=GUIConstants.LABEL_FONT_COLOR, + font_size=bit_font_size, + edge_padding=0, + screen_x=bit_display_x, + screen_y=screen_y, + height=bit_font_height, + is_text_centered=False, + )) + self.components.append(TextArea( + text=self.checksum_bits, + font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, + font_size=bit_font_size, + font_color=GUIConstants.ACCENT_COLOR, + edge_padding=0, + screen_x=checksum_x, + screen_y=screen_y, + is_text_centered=False, + )) + + # And now the actual final word + self.components.append(TextArea( + text=f"""Final Word: \"{self.actual_final_word}\"""", + screen_y=self.components[-1].screen_y + self.components[-1].height + 2*GUIConstants.COMPONENT_PADDING, + )) + + # And the bits that came from the user's selected final word... + # * 7 bits for a 12-word seed + # * 3 bits for a 24-word seed + num_checksum_bits = len(self.checksum_bits) + user_component = self.selected_final_bits[:11 - num_checksum_bits] + screen_y = self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING + self.components.append(TextArea( + text=user_component, + font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, + font_size=bit_font_size, + edge_padding=0, + screen_x=bit_display_x, + screen_y=screen_y, + is_text_centered=False, + )) + + # ...and now overlay the checksum's bits + self.components.append(TextArea( + text=self.checksum_bits, + font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, + font_color=GUIConstants.ACCENT_COLOR, + font_size=bit_font_size, + edge_padding=0, + screen_x=checksum_x, + screen_y=screen_y, + is_text_centered=False, + )) + + + +@dataclass +class ToolsCalcFinalWordDoneScreen(ButtonListScreen): final_word: str = None mnemonic_word_length: int = 12 fingerprint: str = None diff --git a/src/seedsigner/helpers/mnemonic_generation.py b/src/seedsigner/helpers/mnemonic_generation.py index e8fb44aa..6723b512 100644 --- a/src/seedsigner/helpers/mnemonic_generation.py +++ b/src/seedsigner/helpers/mnemonic_generation.py @@ -8,23 +8,17 @@ from typing import List from seedsigner.models.seed import Seed -def calculate_checksum(partial_mnemonic: list, wordlist_language_code: str) -> List[str]: - """ Provide 11- or 23-word mnemonic, returns complete mnemonic w/checksum as a list """ - if len(partial_mnemonic) not in [11, 23]: - raise Exception("Pass in a 11- or 23-word mnemonic") - +def calculate_checksum(mnemonic: list, wordlist_language_code: str) -> List[str]: + """ Provide 12- or 24-word mnemonic, returns complete mnemonic w/checksum as a list """ + if len(mnemonic) not in [12, 24]: + raise Exception("Pass in a 12- or 24-word mnemonic") + # Work on a copy of the input list - mnemonic_copy = partial_mnemonic.copy() - - # 12-word seeds contribute 7 bits of entropy to the final word; 24-word seeds - # contribute 3 bits. But we don't have any partial entropy bits to use to help us - # create the final word. So just default to filling those missing values with zeroes - # ("abandon" is word 0000, so effectively inserts zeroes). - mnemonic_copy.append("abandon") + mnemonic_copy = mnemonic.copy() # Convert the resulting mnemonic to bytes, but we `ignore_checksum` validation - # because we have to assume it's incorrect since we just hard-coded it above; we'll - # fix that next. + # because we assume it's incorrect since we either let the user select their own + # final word OR we injected the 0000 word from the wordlist. mnemonic_bytes = bip39.mnemonic_to_bytes(unicodedata.normalize("NFKD", " ".join(mnemonic_copy)), ignore_checksum=True, wordlist=Seed.get_wordlist(wordlist_language_code)) # This function will convert the bytes back into a mnemonic, but it will also diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 9fb12d43..df5b10fb 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -142,17 +142,15 @@ class SeedMnemonicEntryView(View): self.controller.storage.update_pending_mnemonic(ret, self.cur_word_index) if self.is_calc_final_word and self.cur_word_index == self.controller.storage.pending_mnemonic_length - 2: - # Time to calculate the last word - # TODO: Option to add missing entropy for the last word: - # * 3 bits for a 24-word seed - # * 7 bits for a 12-word seed - from seedsigner.helpers import mnemonic_generation + # Time to calculate the last word. User must either select a final word to + # contribute entropy to the checksum word OR we assume 0 ("abandon"). + from seedsigner.views.tools_views import ToolsCalcFinalWordSelectFinalWordPromptView + return Destination(ToolsCalcFinalWordSelectFinalWordPromptView) + + if self.is_calc_final_word and self.cur_word_index == self.controller.storage.pending_mnemonic_length - 1: + # Time to calculate the last word. User must either select a final word to + # contribute entropy to the checksum word OR we assume 0 ("abandon"). from seedsigner.views.tools_views import ToolsCalcFinalWordShowFinalWordView - full_mnemonic = mnemonic_generation.calculate_checksum( - self.controller.storage.pending_mnemonic[:-1], # Must omit the last word's empty value - wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) - ) - self.controller.storage.update_pending_mnemonic(full_mnemonic[-1], self.cur_word_index+1) return Destination(ToolsCalcFinalWordShowFinalWordView) if self.cur_word_index < self.controller.storage.pending_mnemonic_length - 1: diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 8c6b2216..61edd8ca 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -8,7 +8,7 @@ from PIL.ImageOps import autocontrast from seedsigner.hardware.camera import Camera from seedsigner.gui.components import FontAwesomeIconConstants from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen) -from seedsigner.gui.screens.tools_screens import ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordShowFinalWordScreen +from seedsigner.gui.screens.tools_screens import ToolsCalcFinalWordScreen, ToolsCoinFlipEntryScreen, ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordDoneScreen from seedsigner.helpers import mnemonic_generation from seedsigner.models.seed import Seed from seedsigner.models.settings_definition import SettingsConstants @@ -240,15 +240,154 @@ class ToolsCalcFinalWordNumWordsView(View): elif button_data[selected_menu_num] == TWELVE: self.controller.storage.init_pending_mnemonic(12) - return Destination(SeedMnemonicEntryView, view_args={"is_calc_final_word": True}) + + # DEBUGGING + for i in range(0, 10): + self.controller.storage.update_pending_mnemonic("abandon", i) + + # return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True)) + return Destination(SeedMnemonicEntryView, view_args=dict(cur_word_index=10, is_calc_final_word=True)) elif button_data[selected_menu_num] == TWENTY_FOUR: self.controller.storage.init_pending_mnemonic(24) - return Destination(SeedMnemonicEntryView, view_args={"is_calc_final_word": True}) + + # DEBUGGING + for i in range(0, 22): + self.controller.storage.update_pending_mnemonic("abandon", i) + + # return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True)) + return Destination(SeedMnemonicEntryView, view_args=dict(cur_word_index=22, is_calc_final_word=True)) + + + +class ToolsCalcFinalWordSelectFinalWordPromptView(View): + def run(self): + mnemonic = self.controller.storage.pending_mnemonic + mnemonic_length = len(mnemonic) + COIN_FLIPS = "Finalize w/coin flips" + SELECT_WORD = f"Finalize w/{mnemonic_length}th word" + ZEROS = "Finalize w/zeros" + + button_data = [COIN_FLIPS, SELECT_WORD, ZEROS] + selected_menu_num = ButtonListScreen( + title=f"{mnemonic_length}th Word", + button_data=button_data, + is_bottom_list=True, + is_button_text_centered=True, + ).display() + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + elif button_data[selected_menu_num] == COIN_FLIPS: + return Destination(ToolsCalcFinalWordCoinFlipsView) + + elif button_data[selected_menu_num] == SELECT_WORD: + return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True, cur_word_index=mnemonic_length - 1)) + + elif button_data[selected_menu_num] == ZEROS: + # User skipped the option to select a final word to provide last bits of + # entropy. We'll insert all zeros and piggy-back on the coin flip attr + if mnemonic_length == 12: + total_flips = 7 + else: + total_flips = 3 + wordlist_language_code = self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) + self.controller.storage.update_pending_mnemonic(Seed.get_wordlist(wordlist_language_code)[0], mnemonic_length - 1) + return Destination(ToolsCalcFinalWordShowFinalWordView, view_args=dict(coin_flips="0" * total_flips)) + + + +class ToolsCalcFinalWordCoinFlipsView(View): + def run(self): + mnemonic = self.controller.storage.pending_mnemonic + mnemonic_length = len(mnemonic) + + if mnemonic_length == 12: + total_flips = 7 + else: + total_flips = 3 + + ret_val = ToolsCoinFlipEntryScreen( + total_flips=total_flips, + ).display() + + if ret_val == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + else: + print(ret_val) + binary_string = ret_val + "0" * (11 - total_flips) + wordlist_index = int(binary_string, 2) + wordlist = Seed.get_wordlist(self.controller.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE)) + word = wordlist[wordlist_index] + self.controller.storage.update_pending_mnemonic(word, mnemonic_length - 1) + + return Destination(ToolsCalcFinalWordShowFinalWordView, view_args=dict(coin_flips=ret_val)) class ToolsCalcFinalWordShowFinalWordView(View): + def __init__(self, coin_flips=None): + super().__init__() + self.coin_flips = coin_flips + + + def run(self): + # Construct the actual final word. The user's selected_final_word + # contributes: + # * 3 bits to a 24-word seed (plus 8-bit checksum) + # * 7 bits to a 12-word seed (plus 4-bit checksum) + from seedsigner.helpers import mnemonic_generation + + mnemonic = self.controller.storage.pending_mnemonic + mnemonic_length = len(mnemonic) + wordlist_language_code = self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) + wordlist = Seed.get_wordlist(wordlist_language_code) + + final_mnemonic = mnemonic_generation.calculate_checksum( + mnemonic=self.controller.storage.pending_mnemonic, + wordlist_language_code=wordlist_language_code, + ) + self.controller.storage.update_pending_mnemonic(final_mnemonic[-1], mnemonic_length - 1) + + # Prep the user's selected word (if there was one) and the actual final word for + # the display. + if self.coin_flips: + selected_final_word = None + selected_final_bits = self.coin_flips + else: + # Convert the user's final word selection into its binary index equivalent + selected_final_word = mnemonic[-1] + selected_final_bits = format(wordlist.index(selected_final_word), '011b') + + # And grab the actual final word's checksum bits + actual_final_word = self.controller.storage.pending_mnemonic[-1] + if mnemonic_length == 12: + checksum_bits = format(wordlist.index(actual_final_word), '011b')[-4:] + else: + checksum_bits = format(wordlist.index(actual_final_word), '011b')[-8:] + + NEXT = "Next" + button_data = [NEXT] + selected_menu_num = ToolsCalcFinalWordScreen( + title="Final Word Calc", + button_data=button_data, + selected_final_word=selected_final_word, + selected_final_bits=selected_final_bits, + checksum_bits=checksum_bits, + actual_final_word=actual_final_word, + ).display() + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + elif button_data[selected_menu_num] == NEXT: + return Destination(ToolsCalcFinalWordDoneView) + + + +class ToolsCalcFinalWordDoneView(View): def run(self): mnemonic = self.controller.storage.pending_mnemonic mnemonic_word_length = len(mnemonic) @@ -258,7 +397,7 @@ class ToolsCalcFinalWordShowFinalWordView(View): DISCARD = ("Discard", None, None, "red") button_data = [LOAD, DISCARD] - selected_menu_num = ToolsCalcFinalWordShowFinalWordScreen( + selected_menu_num = ToolsCalcFinalWordDoneScreen( final_word=final_word, mnemonic_word_length=mnemonic_word_length, fingerprint=self.controller.storage.get_pending_mnemonic_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)), From b4f70b536ac32ee7de115c56d9a0cfe8ca181f01 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Fri, 22 Apr 2022 10:12:15 -0500 Subject: [PATCH 02/39] Various improvements --- src/seedsigner/gui/components.py | 4 ++- src/seedsigner/gui/screens/tools_screens.py | 34 +++++++++++++++++++-- src/seedsigner/views/seed_views.py | 23 +++++++------- src/seedsigner/views/tools_views.py | 30 +++++++++--------- 4 files changed, 62 insertions(+), 29 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index ebd451ea..9e6ec211 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -289,7 +289,9 @@ class TextArea(BaseComponent): # fits in its bounding rect (plus accounting for edge padding) using its given # font. # Measure from left baseline ("ls") - # getbbox() seems to ignore "\n" so won't affect height calcs + # TODO: getbbox() seems to ignore "\n" so isn't properly factored into height + # calcs and yields incorrect full_text_width. For now must specify self.height to + # render properly. Centering will be wrong. (left, top, full_text_width, bottom) = self.font.getbbox(self.text, anchor="ls") self.text_font_height = -1 * top self.bbox_height = self.text_font_height + bottom diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index c8169541..790b4c54 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -261,6 +261,24 @@ class ToolsDiceEntropyEntryScreen(BaseTopNavScreen): +@dataclass +class ToolsCalcFinalWordFinalizePromptScreen(ButtonListScreen): + mnemonic_length: int = None + num_entropy_bits: int = None + + def __post_init__(self): + self.title = "Build Final Word" + self.is_bottom_list = True + self.is_button_text_centered = True + super().__post_init__() + + self.components.append(TextArea( + text=f"The {self.mnemonic_length}th word is built from {self.num_entropy_bits} more entropy bits plus auto-calculated checksum.", + screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, + )) + + + @dataclass class ToolsCoinFlipEntryScreen(BaseTopNavScreen): """ @@ -285,7 +303,7 @@ class ToolsCoinFlipEntryScreen(BaseTopNavScreen): button_height = int(1.5*GUIConstants.BUTTON_FONT_SIZE + 2*GUIConstants.EDGE_PADDING) self.keyboard_digits = Keyboard( draw=self.renderer.draw, - charset="01", + charset="10", font_size=button_height - GUIConstants.COMPONENT_PADDING, rows=rows, cols=4, @@ -298,7 +316,7 @@ class ToolsCoinFlipEntryScreen(BaseTopNavScreen): auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT], render_now=False ) - self.keyboard_digits.set_selected_key(selected_letter="0") + self.keyboard_digits.set_selected_key(selected_letter="1") self.text_entry_display = TextEntryDisplay( canvas=self.renderer.canvas, @@ -312,6 +330,15 @@ class ToolsCoinFlipEntryScreen(BaseTopNavScreen): is_centered=False, ) + self.components.append(TextArea( + text="Heads = 1", + screen_y = keyboard_start_y + self.keyboard_digits.height + 4*GUIConstants.COMPONENT_PADDING, + )) + self.components.append(TextArea( + text="Tails = 0", + screen_y = self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING, + )) + def _render(self): super()._render() @@ -371,6 +398,7 @@ class ToolsCoinFlipEntryScreen(BaseTopNavScreen): cursor_position += 1 if cursor_position == self.total_flips: + # Return results as a binary string return self.coin_flips # Render a new TextArea over the TopNav title bar @@ -426,7 +454,7 @@ class ToolsCalcFinalWordScreen(ButtonListScreen): discard_selected_bits = "_" * (len(self.checksum_bits)) self.components.append(TextArea( - text=f"""Your Selection: \"{selection_text}\"""", + text=f"""Your input: \"{selection_text}\"""", screen_y=self.top_nav.height, )) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index df5b10fb..9ac4c3d9 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -127,13 +127,14 @@ class SeedMnemonicEntryView(View): if ret == RET_CODE__BACK_BUTTON: if self.cur_word_index > 0: - return Destination( - SeedMnemonicEntryView, - view_args={ - "cur_word_index": self.cur_word_index - 1, - "is_calc_final_word": self.is_calc_final_word - } - ) + return Destination(BackStackView) + # return Destination( + # SeedMnemonicEntryView, + # view_args={ + # "cur_word_index": self.cur_word_index - 1, + # "is_calc_final_word": self.is_calc_final_word + # } + # ) else: self.controller.storage.discard_pending_mnemonic() return Destination(MainMenuView) @@ -142,10 +143,10 @@ class SeedMnemonicEntryView(View): self.controller.storage.update_pending_mnemonic(ret, self.cur_word_index) if self.is_calc_final_word and self.cur_word_index == self.controller.storage.pending_mnemonic_length - 2: - # Time to calculate the last word. User must either select a final word to - # contribute entropy to the checksum word OR we assume 0 ("abandon"). - from seedsigner.views.tools_views import ToolsCalcFinalWordSelectFinalWordPromptView - return Destination(ToolsCalcFinalWordSelectFinalWordPromptView) + # Time to calculate the last word. User must decide how they want to specify + # the last bits of entropy for the final word. + from seedsigner.views.tools_views import ToolsCalcFinalWordFinalizePromptView + return Destination(ToolsCalcFinalWordFinalizePromptView) if self.is_calc_final_word and self.cur_word_index == self.controller.storage.pending_mnemonic_length - 1: # Time to calculate the last word. User must either select a final word to diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 61edd8ca..dcc54f96 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -8,7 +8,7 @@ from PIL.ImageOps import autocontrast from seedsigner.hardware.camera import Camera from seedsigner.gui.components import FontAwesomeIconConstants from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen) -from seedsigner.gui.screens.tools_screens import ToolsCalcFinalWordScreen, ToolsCoinFlipEntryScreen, ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordDoneScreen +from seedsigner.gui.screens.tools_screens import ToolsCalcFinalWordFinalizePromptScreen, ToolsCalcFinalWordScreen, ToolsCoinFlipEntryScreen, ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordDoneScreen from seedsigner.helpers import mnemonic_generation from seedsigner.models.seed import Seed from seedsigner.models.settings_definition import SettingsConstants @@ -260,20 +260,24 @@ class ToolsCalcFinalWordNumWordsView(View): -class ToolsCalcFinalWordSelectFinalWordPromptView(View): +class ToolsCalcFinalWordFinalizePromptView(View): def run(self): mnemonic = self.controller.storage.pending_mnemonic mnemonic_length = len(mnemonic) - COIN_FLIPS = "Finalize w/coin flips" - SELECT_WORD = f"Finalize w/{mnemonic_length}th word" - ZEROS = "Finalize w/zeros" + if mnemonic_length == 12: + num_entropy_bits = 7 + else: + num_entropy_bits = 3 + + COIN_FLIPS = "Coin flip entropy" + SELECT_WORD = f"Word selection entropy" + ZEROS = "Finalize with zeros" button_data = [COIN_FLIPS, SELECT_WORD, ZEROS] - selected_menu_num = ButtonListScreen( - title=f"{mnemonic_length}th Word", + selected_menu_num = ToolsCalcFinalWordFinalizePromptScreen( + mnemonic_length=mnemonic_length, + num_entropy_bits=num_entropy_bits, button_data=button_data, - is_bottom_list=True, - is_button_text_centered=True, ).display() if selected_menu_num == RET_CODE__BACK_BUTTON: @@ -283,18 +287,16 @@ class ToolsCalcFinalWordSelectFinalWordPromptView(View): return Destination(ToolsCalcFinalWordCoinFlipsView) elif button_data[selected_menu_num] == SELECT_WORD: + # Clear the final word slot, just in case we're returning via BACK button + self.controller.storage.update_pending_mnemonic(None, mnemonic_length - 1) return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True, cur_word_index=mnemonic_length - 1)) elif button_data[selected_menu_num] == ZEROS: # User skipped the option to select a final word to provide last bits of # entropy. We'll insert all zeros and piggy-back on the coin flip attr - if mnemonic_length == 12: - total_flips = 7 - else: - total_flips = 3 wordlist_language_code = self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) self.controller.storage.update_pending_mnemonic(Seed.get_wordlist(wordlist_language_code)[0], mnemonic_length - 1) - return Destination(ToolsCalcFinalWordShowFinalWordView, view_args=dict(coin_flips="0" * total_flips)) + return Destination(ToolsCalcFinalWordShowFinalWordView, view_args=dict(coin_flips="0" * num_entropy_bits)) From f9e92f73d6898b34cae36eb9910ff58a64b5f4d1 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 24 Apr 2022 08:38:27 -0500 Subject: [PATCH 03/39] Removing debugging --- src/seedsigner/views/tools_views.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index dcc54f96..4af46ada 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -241,20 +241,12 @@ class ToolsCalcFinalWordNumWordsView(View): elif button_data[selected_menu_num] == TWELVE: self.controller.storage.init_pending_mnemonic(12) - # DEBUGGING - for i in range(0, 10): - self.controller.storage.update_pending_mnemonic("abandon", i) - # return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True)) return Destination(SeedMnemonicEntryView, view_args=dict(cur_word_index=10, is_calc_final_word=True)) elif button_data[selected_menu_num] == TWENTY_FOUR: self.controller.storage.init_pending_mnemonic(24) - # DEBUGGING - for i in range(0, 22): - self.controller.storage.update_pending_mnemonic("abandon", i) - # return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True)) return Destination(SeedMnemonicEntryView, view_args=dict(cur_word_index=22, is_calc_final_word=True)) From f6e20a4be1b87fd25adb70f003d6c15f0f5a8066 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 24 Apr 2022 09:06:31 -0500 Subject: [PATCH 04/39] Improved code comments --- src/seedsigner/gui/screens/tools_screens.py | 29 ++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index 790b4c54..0370a6f0 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -442,15 +442,21 @@ class ToolsCalcFinalWordScreen(ButtonListScreen): bit_display_x = int((self.canvas_width - bit_display_width)/2) checksum_x += bit_display_x - # Display the user's selected final word + # Display the user's additional entropy input if self.selected_final_word: selection_text = self.selected_final_word keeper_selected_bits = self.selected_final_bits[:11 - len(self.checksum_bits)] + + # The word's least significant bits will be rendered differently to convey + # the fact that they're being discarded. discard_selected_bits = self.selected_final_bits[-1*len(self.checksum_bits):] else: # User entered coin flips or all zeros selection_text = self.selected_final_bits keeper_selected_bits = self.selected_final_bits + + # We'll append spacer chars to preserve the vertical alignment (most + # significant n bits always rendered in same column) discard_selected_bits = "_" * (len(self.checksum_bits)) self.components.append(TextArea( @@ -458,7 +464,7 @@ class ToolsCalcFinalWordScreen(ButtonListScreen): screen_y=self.top_nav.height, )) - # ...and its associated 11 bits + # ...and that entropy's associated 11 bits screen_y=self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING self.components.append(TextArea( text=keeper_selected_bits, @@ -470,6 +476,9 @@ class ToolsCalcFinalWordScreen(ButtonListScreen): height=bit_font_height, is_text_centered=False, )) + + # Render the least significant bits that will be replaced by the checksum in a + # de-emphasized font color. self.components.append(TextArea( text=discard_selected_bits, font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, @@ -482,17 +491,19 @@ class ToolsCalcFinalWordScreen(ButtonListScreen): is_text_centered=False, )) - # Show the checksum + # Show the checksum.. self.components.append(TextArea( text="Checksum", edge_padding=0, screen_y=self.components[-1].screen_y + self.components[-1].height + 2*GUIConstants.COMPONENT_PADDING, )) - # ...and its actual bits + # ...and its actual bits. Prepend spacers to keep vertical alignment checksum_spacer = "_" * (11 - len(self.checksum_bits)) screen_y = self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING + + # This time we de-emphasize the prepended spacers that are irrelevant self.components.append(TextArea( text=checksum_spacer, font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, @@ -504,6 +515,8 @@ class ToolsCalcFinalWordScreen(ButtonListScreen): height=bit_font_height, is_text_centered=False, )) + + # And especially highlight (orange!) the actual checksum bits self.components.append(TextArea( text=self.checksum_bits, font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, @@ -515,15 +528,13 @@ class ToolsCalcFinalWordScreen(ButtonListScreen): is_text_centered=False, )) - # And now the actual final word + # And now the *actual* final word after merging the bit data self.components.append(TextArea( text=f"""Final Word: \"{self.actual_final_word}\"""", screen_y=self.components[-1].screen_y + self.components[-1].height + 2*GUIConstants.COMPONENT_PADDING, )) - # And the bits that came from the user's selected final word... - # * 7 bits for a 12-word seed - # * 3 bits for a 24-word seed + # Once again show the bits that came from the user's entropy... num_checksum_bits = len(self.checksum_bits) user_component = self.selected_final_bits[:11 - num_checksum_bits] screen_y = self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING @@ -537,7 +548,7 @@ class ToolsCalcFinalWordScreen(ButtonListScreen): is_text_centered=False, )) - # ...and now overlay the checksum's bits + # ...and append the checksum's bits, still highlighted in orange self.components.append(TextArea( text=self.checksum_bits, font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, From 2f260aebf478f80e5a53e5a3af0bfc96bf72c2cf Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 24 Apr 2022 18:59:11 -0500 Subject: [PATCH 05/39] interim commit, starting to refactor to `KeyboardScreen` --- src/seedsigner/gui/screens/tools_screens.py | 142 ++------------------ src/seedsigner/views/tools_views.py | 2 +- 2 files changed, 15 insertions(+), 129 deletions(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index 07c470db..fce63e19 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -169,59 +169,22 @@ class ToolsCalcFinalWordFinalizePromptScreen(ButtonListScreen): @dataclass -class ToolsCoinFlipEntryScreen(BaseTopNavScreen): - """ - This is a lame mega copy-paste from the dice rolls Screen. It probably isn't worth - the effort to generalize these screens. But the copy-paste is far from ideal. - """ - total_flips: int = None - +class ToolsCoinFlipEntryScreen(KeyboardScreen): def __post_init__(self): + # Override values set by the parent class self.title = f"Coin Flip 1/{self.total_flips}" + + # Specify the keys in the keyboard + self.rows = 1 + self.cols = 4 + self.keys_charset = "10" + + # Now initialize the parent class super().__post_init__() - - self.coin_flips = "" - - # Set up the keyboard params - keyboard_width = self.canvas_width - 2*GUIConstants.EDGE_PADDING - text_entry_display_y = self.top_nav.height - text_entry_display_height = 30 - - keyboard_start_y = text_entry_display_y + text_entry_display_height + GUIConstants.COMPONENT_PADDING - rows = 1 - button_height = int(1.5*GUIConstants.BUTTON_FONT_SIZE + 2*GUIConstants.EDGE_PADDING) - self.keyboard_digits = Keyboard( - draw=self.renderer.draw, - charset="10", - font_size=button_height - GUIConstants.COMPONENT_PADDING, - rows=rows, - cols=4, - rect=( - GUIConstants.EDGE_PADDING, - keyboard_start_y, - GUIConstants.EDGE_PADDING + keyboard_width, - keyboard_start_y + rows * button_height + (rows - 1) * 2 - ), - auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT], - render_now=False - ) - self.keyboard_digits.set_selected_key(selected_letter="1") - - self.text_entry_display = TextEntryDisplay( - canvas=self.renderer.canvas, - rect=( - GUIConstants.EDGE_PADDING, - text_entry_display_y, - self.canvas_width - GUIConstants.EDGE_PADDING, - text_entry_display_y + text_entry_display_height - ), - cursor_mode=TextEntryDisplay.CURSOR_MODE__BAR, - is_centered=False, - ) - + self.components.append(TextArea( text="Heads = 1", - screen_y = keyboard_start_y + self.keyboard_digits.height + 4*GUIConstants.COMPONENT_PADDING, + screen_y = self.keyboard_digits.rect[3] + 4*GUIConstants.COMPONENT_PADDING, )) self.components.append(TextArea( text="Tails = 0", @@ -229,86 +192,9 @@ class ToolsCoinFlipEntryScreen(BaseTopNavScreen): )) - def _render(self): - super()._render() - - self.keyboard_digits.render_keys() - self.text_entry_display.render() - - self.renderer.show_image() - - - def _run(self): - cursor_position = len(self.coin_flips) - - # Start the interactive update loop - while True: - input = self.hw_inputs.wait_for( - HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN + [HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY3], - check_release=True, - release_keys=[HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY3] - ) - - # Check possible exit condition - if self.top_nav.is_selected and input == HardwareButtonsConstants.KEY_PRESS: - return RET_CODE__BACK_BUTTON - - # Process normal input - if input in [HardwareButtonsConstants.KEY_UP, HardwareButtonsConstants.KEY_DOWN] and self.top_nav.is_selected: - # We're navigating off the previous button - self.top_nav.is_selected = False - self.top_nav.render_buttons() - - # Override the actual input w/an ENTER signal for the Keyboard - if input == HardwareButtonsConstants.KEY_DOWN: - input = Keyboard.ENTER_TOP - else: - input = Keyboard.ENTER_BOTTOM - elif input in [HardwareButtonsConstants.KEY_LEFT, HardwareButtonsConstants.KEY_RIGHT] and self.top_nav.is_selected: - # ignore - continue - - ret_val = self.keyboard_digits.update_from_input(input) - - # Now process the result from the keyboard - if ret_val in Keyboard.EXIT_DIRECTIONS: - self.top_nav.is_selected = True - self.top_nav.render_buttons() - - elif ret_val in Keyboard.ADDITIONAL_KEYS and input == HardwareButtonsConstants.KEY_PRESS: - if ret_val == Keyboard.KEY_BACKSPACE["code"]: - if len(self.coin_flips) > 0: - self.coin_flips = self.coin_flips[:-1] - cursor_position -= 1 - - elif input == HardwareButtonsConstants.KEY_PRESS and ret_val not in Keyboard.ADDITIONAL_KEYS: - # User has locked in the current letter - self.coin_flips += ret_val - cursor_position += 1 - - if cursor_position == self.total_flips: - # Return results as a binary string - return self.coin_flips - - # Render a new TextArea over the TopNav title bar - TextArea( - text=f"Coin Flip {cursor_position + 1}/{self.total_flips}", - font_name=GUIConstants.TOP_NAV_TITLE_FONT_NAME, - font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE, - height=self.top_nav.height, - ).render() - self.top_nav.render_buttons() - - elif input in HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN: - # Live joystick movement; haven't locked this new letter in yet. - # Leave current spot blank for now. Only update the active keyboard keys - # when a selection has been locked in (KEY_PRESS) or removed ("del"). - pass - - # Render the text entry display and cursor block - self.text_entry_display.render(self.coin_flips) - - self.renderer.show_image() + def update_title(self) -> bool: + self.title = f"Coin Flip {self.cursor_position + 1}/{self.return_after_n_chars}" + return True diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index d5eb9b68..272eefa6 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -303,7 +303,7 @@ class ToolsCalcFinalWordCoinFlipsView(View): total_flips = 3 ret_val = ToolsCoinFlipEntryScreen( - total_flips=total_flips, + return_after_n_chars=total_flips, ).display() if ret_val == RET_CODE__BACK_BUTTON: From d62c8cfb0e87e11b904381cfdbcfe28967738475 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 24 Apr 2022 19:11:09 -0500 Subject: [PATCH 06/39] Finalizing refactor to `KeyboardScreen`. --- src/seedsigner/gui/screens/tools_screens.py | 5 +++-- src/seedsigner/views/tools_views.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index fce63e19..229146d5 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -172,11 +172,12 @@ class ToolsCalcFinalWordFinalizePromptScreen(ButtonListScreen): class ToolsCoinFlipEntryScreen(KeyboardScreen): def __post_init__(self): # Override values set by the parent class - self.title = f"Coin Flip 1/{self.total_flips}" + self.title = f"Coin Flip 1/{self.return_after_n_chars}" # Specify the keys in the keyboard self.rows = 1 self.cols = 4 + self.key_height = GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 2 + 2*GUIConstants.EDGE_PADDING self.keys_charset = "10" # Now initialize the parent class @@ -184,7 +185,7 @@ class ToolsCoinFlipEntryScreen(KeyboardScreen): self.components.append(TextArea( text="Heads = 1", - screen_y = self.keyboard_digits.rect[3] + 4*GUIConstants.COMPONENT_PADDING, + screen_y = self.keyboard.rect[3] + 4*GUIConstants.COMPONENT_PADDING, )) self.components.append(TextArea( text="Tails = 0", diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 272eefa6..bfee4738 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -242,13 +242,13 @@ class ToolsCalcFinalWordNumWordsView(View): self.controller.storage.init_pending_mnemonic(12) # return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True)) - return Destination(SeedMnemonicEntryView, view_args=dict(cur_word_index=10, is_calc_final_word=True)) + return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True)) elif button_data[selected_menu_num] == TWENTY_FOUR: self.controller.storage.init_pending_mnemonic(24) # return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True)) - return Destination(SeedMnemonicEntryView, view_args=dict(cur_word_index=22, is_calc_final_word=True)) + return Destination(SeedMnemonicEntryView, view_args=dict(is_calc_final_word=True)) From b83449ecfe43ae44413b7472dc8c9c95dc9f7399 Mon Sep 17 00:00:00 2001 From: _CyberNomad <95991570+cyb3rn0mad@users.noreply.github.com> Date: Sun, 22 May 2022 14:34:48 -0500 Subject: [PATCH 07/39] Request to add links to community desighed enclosures. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index c07579db..b4469cb4 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,14 @@ The Orange Pill enclosure design offers a more finished look that includes butto The upper and lower portions of the enclosure can be printed using a standard FDM 3D printer, no supports necessary. The buttons and joystick nub should ideally be produced with a SLA/resin printer. An overview of the entire assembly process can be found [here](https://youtu.be/aIIc2DiZYcI). To access the design files and printable models, click [here](https://github.com/SeedSigner/seedsigner/tree/main/enclosures/orange_pill). +### Community Designs + +* [Lil Pill](https://cults3d.com/en/3d-model/gadget/lil-pill-seedsigner-case) by @_CyberNomad +* [OrangeSurf Case](https://github.com/orangesurf/orangesurf-seedsigner-case) by @OrangeSurfBTC +* [PS4 Seedsigner](https://www.thingiverse.com/thing:5363525) by @Silexperience +* [OpenPill Faceplate](https://www.printables.com/en/model/179924-seedsigner-open-pill-cover-plates-digital-cross-jo) by @Revetuzo +* [Waveshare CoverPlate](https://cults3d.com/en/3d-model/various/seedsigner-coverplate-for-waveshare-1-3-inch-lcd-hat-with-240x240-pixel-display) by @Adathome1 + --------------- # SeedQR Printable Templates From 2f6629bb71cdc19a915b634985bbb54ebbe35777 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 25 May 2022 11:35:08 -0500 Subject: [PATCH 08/39] Bugfix for manual seed entry after scanning psbt --- src/seedsigner/views/psbt_views.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 1b8da55d..5a3cbb06 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -32,7 +32,8 @@ class PSBTSelectSeedView(View): seeds = self.controller.storage.seeds SCAN_SEED = ("Scan a seed", FontAwesomeIconConstants.QRCODE) - ENTER_WORDS = "Enter 12/24 words" + TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) + TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) button_data = [] for seed in seeds: button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)) @@ -45,7 +46,8 @@ class PSBTSelectSeedView(View): pass button_data.append((button_str, SeedSignerCustomIconConstants.FINGERPRINT, "blue")) button_data.append(SCAN_SEED) - button_data.append(ENTER_WORDS) + button_data.append(TYPE_12WORD) + button_data.append(TYPE_24WORD) selected_menu_num = ButtonListScreen( title="Select Signer", @@ -68,8 +70,12 @@ class PSBTSelectSeedView(View): from seedsigner.views.scan_views import ScanView return Destination(ScanView) - elif button_data[selected_menu_num] == ENTER_WORDS: + elif button_data[selected_menu_num] in [TYPE_12WORD, TYPE_24WORD]: from seedsigner.views.seed_views import SeedMnemonicEntryView + if button_data[selected_menu_num] == TYPE_12WORD: + self.controller.storage.init_pending_mnemonic(12) + else: + self.controller.storage.init_pending_mnemonic(24) return Destination(SeedMnemonicEntryView) From e7d09a1c3b8db43144ae31ad63aa5179a783c333 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 25 May 2022 11:45:22 -0500 Subject: [PATCH 09/39] Fix for single sig addr verification --- src/seedsigner/views/psbt_views.py | 4 ++-- src/seedsigner/views/seed_views.py | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 5a3cbb06..5e2149cc 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -73,9 +73,9 @@ class PSBTSelectSeedView(View): elif button_data[selected_menu_num] in [TYPE_12WORD, TYPE_24WORD]: from seedsigner.views.seed_views import SeedMnemonicEntryView if button_data[selected_menu_num] == TYPE_12WORD: - self.controller.storage.init_pending_mnemonic(12) + self.controller.storage.init_pending_mnemonic(num_words=12) else: - self.controller.storage.init_pending_mnemonic(24) + self.controller.storage.init_pending_mnemonic(num_words=24) return Destination(SeedMnemonicEntryView) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 9fb12d43..9e580456 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -73,13 +73,13 @@ class SeedsMenuView(View): class LoadSeedView(View): def run(self): SEED_QR = (" Scan a SeedQR", FontAwesomeIconConstants.QRCODE) - TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) + TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) CREATE = (" Create a seed", FontAwesomeIconConstants.PLUS) button_data=[ SEED_QR, - TYPE_24WORD, TYPE_12WORD, + TYPE_24WORD, CREATE, ] @@ -96,14 +96,14 @@ class LoadSeedView(View): from .scan_views import ScanView return Destination(ScanView) - elif button_data[selected_menu_num] == TYPE_24WORD: - self.controller.storage.init_pending_mnemonic(num_words=24) - return Destination(SeedMnemonicEntryView) - elif button_data[selected_menu_num] == TYPE_12WORD: self.controller.storage.init_pending_mnemonic(num_words=12) return Destination(SeedMnemonicEntryView) + elif button_data[selected_menu_num] == TYPE_24WORD: + self.controller.storage.init_pending_mnemonic(num_words=24) + return Destination(SeedMnemonicEntryView) + elif button_data[selected_menu_num] == CREATE: from .tools_views import ToolsMenuView return Destination(ToolsMenuView) @@ -1314,7 +1314,8 @@ class SeedSingleSigAddressVerificationSelectSeedView(View): seeds = self.controller.storage.seeds SCAN_SEED = ("Scan a seed", FontAwesomeIconConstants.QRCODE) - ENTER_WORDS = "Enter 12/24 words" + TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) + TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) button_data = [] text = "Load the seed to verify" @@ -1330,7 +1331,8 @@ class SeedSingleSigAddressVerificationSelectSeedView(View): text = "Select seed to verify" button_data.append(SCAN_SEED) - button_data.append(ENTER_WORDS) + button_data.append(TYPE_12WORD) + button_data.append(TYPE_24WORD) selected_menu_num = seed_screens.SeedSingleSigAddressVerificationSelectSeedScreen( title="Verify Address", @@ -1357,7 +1359,12 @@ class SeedSingleSigAddressVerificationSelectSeedView(View): from seedsigner.views.scan_views import ScanView return Destination(ScanView) - elif button_data[selected_menu_num] == ENTER_WORDS: + elif button_data[selected_menu_num] in [TYPE_12WORD, TYPE_24WORD]: + from seedsigner.views.seed_views import SeedMnemonicEntryView + if button_data[selected_menu_num] == TYPE_12WORD: + self.controller.storage.init_pending_mnemonic(num_words=12) + else: + self.controller.storage.init_pending_mnemonic(num_words=24) return Destination(SeedMnemonicEntryView) From 3afeeda11c83c04410c4ae5aabfa37a147564413 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 26 May 2022 08:09:03 -0500 Subject: [PATCH 10/39] Improved handling for the PSBTOverview loading screen spinner Fixes #188 * Only runs the spinner when the psbt actually needs to be reparsed (returning to the PSBT Overview screen via BACK button doesn't need to reparse the psbt so the spinner is no longer displayed). --- src/seedsigner/views/psbt_views.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 5e2149cc..1895a273 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -84,18 +84,22 @@ class PSBTOverviewView(View): def __init__(self): super().__init__() - # The PSBTParser takes a while to read the PSBT. Run the loading screen while we - # wait. - self.loading_screen = LoadingScreenThread(text="Parsing PSBT...") - self.loading_screen.start() + self.loading_screen = None if not self.controller.psbt_parser or self.controller.psbt_parser.seed != self.controller.psbt_seed: - # Must run the PSBTParser or re-parse - self.controller.psbt_parser = PSBTParser( - self.controller.psbt, - seed=self.controller.psbt_seed, - network=self.settings.get_value(SettingsConstants.SETTING__NETWORK) - ) + # The PSBTParser takes a while to read the PSBT. Run the loading screen while + # we wait. + self.loading_screen = LoadingScreenThread(text="Parsing PSBT...") + self.loading_screen.start() + try: + self.controller.psbt_parser = PSBTParser( + self.controller.psbt, + seed=self.controller.psbt_seed, + network=self.settings.get_value(SettingsConstants.SETTING__NETWORK) + ) + except Exception as e: + self.loading_screen.stop() + raise e def run(self): @@ -132,7 +136,8 @@ class PSBTOverviewView(View): ) # Everything is set. Stop the loading screen - self.loading_screen.stop() + if self.loading_screen: + self.loading_screen.stop() # Run the overview screen selected_menu_num = screen.display() @@ -151,6 +156,8 @@ class PSBTOverviewView(View): else: return Destination(PSBTMathView) + + class PSBTUnsupportedScriptTypeWarningView(View): def run(self): selected_menu_num = WarningScreen( From ad9375cf53f53b7fe5c6ac4b15da7bdea388c752 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 26 May 2022 09:43:00 -0500 Subject: [PATCH 11/39] Allow `PSBTOverviewScreen` to truncate "self-transfer" Fixes #215 --- src/seedsigner/gui/screens/psbt_screens.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index 89bef6b1..b25fad79 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -118,7 +118,7 @@ class PSBTOverviewScreen(ButtonListScreen): # max_destination_col_width += curve_width # Now let's maximize the actual destination col by adjusting our addr truncation - def calculate_destination_col_width(truncate_at: int): + def calculate_destination_col_width(truncate_at: int = 0): def truncate_destination_addr(addr): return f"{addr[:truncate_at]}..." @@ -129,7 +129,7 @@ class PSBTOverviewScreen(ButtonListScreen): destination_column.append(truncate_destination_addr(addr)) for i in range(0, self.num_self_transfer_outputs): - destination_column.append(f"self-transfer") + destination_column.append(truncate_destination_addr("self-transfer")) else: # destination_column.append(f"{len(self.destination_addresses)} recipients") destination_column.append(f"recipient 1") @@ -151,7 +151,7 @@ class PSBTOverviewScreen(ButtonListScreen): if len(self.destination_addresses) + self.num_self_transfer_outputs > 3: # We're not going to display any destination addrs so truncation doesn't matter - (destination_text_width, destination_column) = calculate_destination_col_width(truncate_at=0) + (destination_text_width, destination_column) = calculate_destination_col_width() else: # Steadliy widen out the destination column until we run out of space for i in range(6, 13): From fc988418e68ed3768cb498edc784b485a19fce5c Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 26 May 2022 09:52:49 -0500 Subject: [PATCH 12/39] Update psbt_screens.py Ensure that regular displays that don't have space constraints aren't unnecessarily truncated. --- src/seedsigner/gui/screens/psbt_screens.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index b25fad79..17c1eb04 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -120,6 +120,9 @@ class PSBTOverviewScreen(ButtonListScreen): # Now let's maximize the actual destination col by adjusting our addr truncation def calculate_destination_col_width(truncate_at: int = 0): def truncate_destination_addr(addr): + if len(addr) <= truncate_at + len("..."): + # No point in truncating + return addr return f"{addr[:truncate_at]}..." destination_column = [] @@ -154,7 +157,7 @@ class PSBTOverviewScreen(ButtonListScreen): (destination_text_width, destination_column) = calculate_destination_col_width() else: # Steadliy widen out the destination column until we run out of space - for i in range(6, 13): + for i in range(6, 14): (new_width, new_col_text) = calculate_destination_col_width(truncate_at=i) if new_width > max_destination_col_width: break From 6f6062254a2ae9cd52fe224ac3aa515f03901304 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 26 May 2022 09:54:58 -0500 Subject: [PATCH 13/39] Note for upcoming issue with translations --- src/seedsigner/gui/screens/psbt_screens.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index 17c1eb04..053d47e5 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -120,6 +120,7 @@ class PSBTOverviewScreen(ButtonListScreen): # Now let's maximize the actual destination col by adjusting our addr truncation def calculate_destination_col_width(truncate_at: int = 0): def truncate_destination_addr(addr): + # TODO: Properly handle the ellipsis truncation in different languages if len(addr) <= truncate_at + len("..."): # No point in truncating return addr From 16cd896ea8d322dc165a45ef0f269ddcf501cd36 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 26 May 2022 17:57:16 -0500 Subject: [PATCH 14/39] Additional bulletproofing for `LoadingScreenThread` spinner --- src/seedsigner/views/psbt_views.py | 11 ++--- src/seedsigner/views/seed_views.py | 70 ++++++++++++++++-------------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 1895a273..b5150388 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -475,11 +475,12 @@ class PSBTFinalizeView(View): loading_screen = LoadingScreenThread(text="Signing PSBT...") loading_screen.start() - sig_cnt = PSBTParser.sig_count(psbt) - psbt.sign_with(psbt_parser.root) - trimmed_psbt = PSBTParser.trim(psbt) - - loading_screen.stop() + try: + sig_cnt = PSBTParser.sig_count(psbt) + psbt.sign_with(psbt_parser.root) + trimmed_psbt = PSBTParser.trim(psbt) + finally: + loading_screen.stop() if sig_cnt == PSBTParser.sig_count(trimmed_psbt): # Signing failed / didn't do anything diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 5260f017..5370ecdb 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -648,46 +648,50 @@ class SeedExportXpubDetailsView(View): self.loading_screen = LoadingScreenThread(text="Generating xpub...") self.loading_screen.start() - if self.script_type == SettingsConstants.CUSTOM_DERIVATION: - derivation_path = self.custom_derivation - else: - derivation_path = PSBTParser.calc_derivation( - network=self.settings.get_value(SettingsConstants.SETTING__NETWORK), - wallet_type=self.sig_type, - script_type=self.script_type - ) + try: + if self.script_type == SettingsConstants.CUSTOM_DERIVATION: + derivation_path = self.custom_derivation + else: + derivation_path = PSBTParser.calc_derivation( + network=self.settings.get_value(SettingsConstants.SETTING__NETWORK), + wallet_type=self.sig_type, + script_type=self.script_type + ) - if self.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__ENABLED: - embit_network = NETWORKS[SettingsConstants.map_network_to_embit(self.settings.get_value(SettingsConstants.SETTING__NETWORK))] - version = embit.bip32.detect_version( - derivation_path, - default="xpub", - network=embit_network - ) + if self.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__ENABLED: + embit_network = NETWORKS[SettingsConstants.map_network_to_embit(self.settings.get_value(SettingsConstants.SETTING__NETWORK))] + version = embit.bip32.detect_version( + derivation_path, + default="xpub", + network=embit_network + ) - root = embit.bip32.HDKey.from_seed( - self.seed.seed_bytes, - version=embit_network["xprv"] - ) + root = embit.bip32.HDKey.from_seed( + self.seed.seed_bytes, + version=embit_network["xprv"] + ) - fingerprint = hexlify(root.child(0).fingerprint).decode('utf-8') - xprv = root.derive(derivation_path) - xpub = xprv.to_public() - xpub_base58 = xpub.to_string(version=version) + fingerprint = hexlify(root.child(0).fingerprint).decode('utf-8') + xprv = root.derive(derivation_path) + xpub = xprv.to_public() + xpub_base58 = xpub.to_string(version=version) - screen = seed_screens.SeedExportXpubDetailsScreen( - fingerprint=fingerprint, - has_passphrase=self.seed.passphrase is not None, - derivation_path=derivation_path, - xpub=xpub_base58, - ) + screen = seed_screens.SeedExportXpubDetailsScreen( + fingerprint=fingerprint, + has_passphrase=self.seed.passphrase is not None, + derivation_path=derivation_path, + xpub=xpub_base58, + ) + self.loading_screen.stop() + selected_menu_num = screen.display() + + else: + selected_menu_num = 0 + + finally: self.loading_screen.stop() - selected_menu_num = screen.display() - else: - self.loading_screen.stop() - selected_menu_num = 0 if selected_menu_num == 0: return Destination( From 2bc4b22384abf10062ea32fc07e57a413c775940 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 30 May 2022 22:04:57 -0500 Subject: [PATCH 15/39] Basic framework functional for single sig from seed --- src/seedsigner/controller.py | 4 + src/seedsigner/gui/screens/screen.py | 82 +++++-- src/seedsigner/gui/screens/seed_screens.py | 3 +- src/seedsigner/gui/screens/tools_screens.py | 51 +++- src/seedsigner/hardware/buttons.py | 12 +- src/seedsigner/helpers/embit_utils.py | 95 ++++++++ src/seedsigner/models/encode_qr.py | 23 ++ src/seedsigner/models/psbt_parser.py | 36 --- src/seedsigner/models/seed.py | 10 +- src/seedsigner/views/scan_views.py | 8 +- src/seedsigner/views/seed_views.py | 134 +++++----- src/seedsigner/views/tools_views.py | 257 +++++++++++++++++++- 12 files changed, 565 insertions(+), 150 deletions(-) create mode 100644 src/seedsigner/helpers/embit_utils.py diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index a03a160b..ff7ae4a3 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -69,6 +69,8 @@ class Controller(Singleton): image_entropy_preview_frames: List[Image] = None image_entropy_final_image: Image = None + + address_explorer_data: dict = None # TODO: end refactor section # Destination placeholder for when we need to jump out to a side flow but intend to @@ -77,6 +79,7 @@ class Controller(Singleton): FLOW__PSBT = "psbt" FLOW__VERIFY_MULTISIG_ADDR = "multisig_addr" FLOW__VERIFY_SINGLESIG_ADDR = "singlesig_addr" + FLOW__ADDRESS_EXPLORER = "address_explorer" resume_main_flow: str = None back_stack: BackStack = None @@ -224,6 +227,7 @@ class Controller(Singleton): self.resume_main_flow = None self.multisig_wallet_descriptor = None self.unverified_address = None + self.address_explorer_data = None print(f"back_stack: {self.back_stack}") diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index ba64f741..6ea6fc17 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -5,7 +5,7 @@ from PIL import Image, ImageDraw, ImageColor from typing import Any, List, Tuple from seedsigner.gui.renderer import Renderer -from seedsigner.models.threads import BaseThread +from seedsigner.models.threads import BaseThread, ThreadsafeCounter from seedsigner.models.encode_qr import EncodeQR from seedsigner.models.settings import Settings, SettingsConstants @@ -660,41 +660,71 @@ class LargeButtonScreen(BaseTopNavScreen): class QRDisplayScreen(BaseScreen): qr_encoder: EncodeQR = None + class QRDisplayThread(BaseThread): + def __init__(self, qr_encoder: EncodeQR, qr_brightness: ThreadsafeCounter, renderer: Renderer): + super().__init__() + self.qr_encoder = qr_encoder + self.qr_brightness = qr_brightness + self.renderer = renderer + + + def run(self): + # Loop whether the QR is a single frame or animated; each loop might adjust + # brightness setting. + while self.keep_running: + # convert the self.qr_brightness integer (31-255) into hex triplets + hex_color = (hex(self.qr_brightness.cur_count).split('x')[1]) * 3 + image = self.qr_encoder.next_part_image(240,240, border=2, background_color=hex_color) + with self.renderer.lock: + self.renderer.show_image(image) + + # Target n held frames per second before rendering next QR image + time.sleep(5/30.0) + + + def __post_init__(self): + from seedsigner.models.settings import Settings + super().__post_init__() + + # Shared coordination var so the display thread can detect success + settings = Settings.get_instance() + self.qr_brightness = ThreadsafeCounter(initial_value=settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS)) + + self.threads.append(QRDisplayScreen.QRDisplayThread( + qr_encoder=self.qr_encoder, + qr_brightness=self.qr_brightness, + renderer=self.renderer, + )) + + def _run(self): from seedsigner.models.settings import Settings - settings = Settings.get_instance() - cur_brightness = settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS) - # Loop whether the QR is a single frame or animated; each loop might adjust - # brightness setting. while True: - ret = self._run_callback() - if ret is not None: - return ret - - # convert the cur_brightness integer (31-255) into hex triplets - hex_color = (hex(cur_brightness).split('x')[1]) * 3 - image = self.qr_encoder.next_part_image(240,240, border=2, background_color=hex_color) - self.renderer.show_image(image) - - # Target n held frames per second before rendering next QR image - time.sleep(5/30.0) - - if self.hw_inputs.check_for_low(HardwareButtonsConstants.KEY_DOWN): + user_input = self.hw_inputs.wait_for( + [ + HardwareButtonsConstants.KEY_UP, + HardwareButtonsConstants.KEY_DOWN, + HardwareButtonsConstants.KEY_LEFT, + HardwareButtonsConstants.KEY_RIGHT, + ] + HardwareButtonsConstants.KEYS__ANYCLICK, + check_release=True, + release_keys=HardwareButtonsConstants.KEYS__ANYCLICK + ) + if user_input == HardwareButtonsConstants.KEY_DOWN: # Reduce QR code background brightness - cur_brightness = max(31, cur_brightness - 31) + self.qr_brightness.set_value(max(31, self.qr_brightness.cur_count - 31)) - elif self.hw_inputs.check_for_low(HardwareButtonsConstants.KEY_UP): + elif user_input == HardwareButtonsConstants.KEY_UP: # Incrase QR code background brightness - cur_brightness = min(cur_brightness + 31, 255) + self.qr_brightness.set_value(min(self.qr_brightness.cur_count + 31, 255)) - elif self.hw_inputs.check_for_low(HardwareButtonsConstants.KEY_RIGHT): + else: + # Any other input exits the screen + self.threads[-1].stop() break - - settings.set_value(SettingsConstants.SETTING__QR_BRIGHTNESS, cur_brightness) - - # TODO: handle left as BACK + Settings.get_instance().set_value(SettingsConstants.SETTING__QR_BRIGHTNESS, self.qr_brightness.cur_count) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index b86cbf17..71f3f838 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -436,7 +436,6 @@ class SeedFinalizeScreen(ButtonListScreen): @dataclass class SeedOptionsScreen(ButtonListScreen): # Customize defaults - is_bottom_list: bool = True fingerprint: str = None has_passphrase: bool = False @@ -445,6 +444,8 @@ class SeedOptionsScreen(ButtonListScreen): self.top_nav_icon_color = "blue" self.title = self.fingerprint self.is_button_text_centered = False + self.is_bottom_list = True + super().__post_init__() diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index 2e8b2276..fa13fc41 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -1,11 +1,14 @@ from dataclasses import dataclass +from typing import Any from PIL.Image import Image from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay from seedsigner.hardware.camera import Camera -from seedsigner.gui.components import FontAwesomeIconConstants, Fonts, GUIConstants, IconTextLine, SeedSignerCustomIconConstants, TextArea +from seedsigner.gui.components import FontAwesomeIconConstants, Fonts, FormattedAddress, GUIConstants, IconTextLine, SeedSignerCustomIconConstants, TextArea -from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, BaseScreen, BaseTopNavScreen, ButtonListScreen +from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, BaseScreen, BaseTopNavScreen, ButtonListScreen, QRDisplayScreen from seedsigner.hardware.buttons import HardwareButtonsConstants +from seedsigner.helpers.qr import QR +from seedsigner.models.settings_definition import SettingsConstants @@ -289,3 +292,47 @@ class ToolsCalcFinalWordShowFinalWordScreen(ButtonListScreen): is_text_centered=True, screen_y=self.components[-1].screen_y + self.components[-1].height + 3*GUIConstants.COMPONENT_PADDING, )) + + + +@dataclass +class ToolsAddressExplorerAddressTypeScreen(ButtonListScreen): + fingerprint: str = None + wallet_descriptor: Any = None + script_type: str = None + custom_derivation_path: str = None + + def __post_init__(self): + self.title = "Address Explorer" + self.is_bottom_list = True + super().__post_init__() + + if self.fingerprint: + self.components.append(IconTextLine( + icon_name=SeedSignerCustomIconConstants.FINGERPRINT, + icon_color="blue", + label_text="Fingerprint", + value_text=self.fingerprint, + screen_x=GUIConstants.EDGE_PADDING, + screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, + )) + + if self.script_type: + self.components.append(IconTextLine( + icon_name=SeedSignerCustomIconConstants.PATH, + label_text="Derivation", + value_text="Native Segwit" if self.script_type == SettingsConstants.NATIVE_SEGWIT else "Nested Segwit", + screen_x=GUIConstants.EDGE_PADDING, + screen_y=self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING, + )) + else: + self.components.append(IconTextLine( + icon_name=SeedSignerCustomIconConstants.PATH, + label_text="Derivation", + value_text=self.custom_derivation_path, + screen_x=GUIConstants.EDGE_PADDING, + screen_y=self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING, + )) + else: + raise Exception("Implement wallet descriptor support!") + diff --git a/src/seedsigner/hardware/buttons.py b/src/seedsigner/hardware/buttons.py index d1ae20e4..c64d4c1e 100644 --- a/src/seedsigner/hardware/buttons.py +++ b/src/seedsigner/hardware/buttons.py @@ -1,3 +1,4 @@ +from typing import List import RPi.GPIO as GPIO import time @@ -145,10 +146,13 @@ class HardwareButtons(Singleton): HardwareButtonsConstants.release_lock = True return True - def check_for_low(self, key) -> bool: - if self.GPIO.input(key) == self.GPIO.LOW: - self.update_last_input_time() - return True + def check_for_low(self, key: int = None, keys: List[int] = None) -> bool: + if key: + keys = [key] + for key in keys: + if self.GPIO.input(key) == self.GPIO.LOW: + self.update_last_input_time() + return True else: return False diff --git a/src/seedsigner/helpers/embit_utils.py b/src/seedsigner/helpers/embit_utils.py new file mode 100644 index 00000000..9fecbf61 --- /dev/null +++ b/src/seedsigner/helpers/embit_utils.py @@ -0,0 +1,95 @@ +import embit +from embit import bip32 +from embit.bip32 import HDKey +from embit.descriptor import Descriptor +from embit.networks import NETWORKS +from embit.networks import NETWORKS + +from seedsigner.models.settings_definition import SettingsConstants + + +""" + Collection of generic embit-powered util methods. +""" +# TODO: PR these directly into `embit`? Or replace with new/existing methods already in `embit`? + + +def get_standard_derivation_path(network: str = SettingsConstants.MAINNET, wallet_type: str = SettingsConstants.SINGLE_SIG, script_type: str = SettingsConstants.NATIVE_SEGWIT) -> str: + if network == SettingsConstants.MAINNET: + network_path = "0'" + elif network == SettingsConstants.TESTNET: + network_path = "1'" + elif network == SettingsConstants.REGTEST: + network_path = "1'" + else: + raise Exception("Unexpected network") + + if wallet_type == SettingsConstants.SINGLE_SIG: + if script_type == SettingsConstants.NATIVE_SEGWIT: + return f"m/84'/{network_path}/0'" + elif script_type == SettingsConstants.NESTED_SEGWIT: + return f"m/49'/{network_path}/0'" + elif script_type == SettingsConstants.TAPROOT: + return f"m/86'/{network_path}/0'" + else: + raise Exception("Unexpected script type") + + elif wallet_type == SettingsConstants.MULTISIG: + if script_type == SettingsConstants.NATIVE_SEGWIT: + return f"m/48'/{network_path}/0'/2'" + elif script_type == SettingsConstants.NESTED_SEGWIT: + return f"m/48'/{network_path}/0'/1'" + elif script_type == SettingsConstants.TAPROOT: + raise Exception("Taproot multisig/musig not yet supported") + else: + raise Exception("Unexpected script type") + else: + raise Exception("Unexpected wallet type") # checks that all inputs are from the same wallet + + + +def get_xpub(seed_bytes, derivation_path: str, embit_network: str = "main") -> HDKey: + root = bip32.HDKey.from_seed(seed_bytes, version=NETWORKS[embit_network]["xprv"]) + xprv = root.derive(derivation_path) + xpub = xprv.to_public() + return xpub + + + +def get_single_sig_address(xpub: HDKey, script_type: str = SettingsConstants.NATIVE_SEGWIT, index: int = 0, is_change: bool = False, embit_network: str = "main") -> str: + if is_change: + pubkey = xpub.derive([1,index]).key + else: + pubkey = xpub.derive([0,index]).key + + if script_type == SettingsConstants.NATIVE_SEGWIT: + return embit.script.p2wpkh(pubkey).address(network=NETWORKS[embit_network]) + + elif script_type == SettingsConstants.NESTED_SEGWIT: + return embit.script.p2sh(embit.script.p2wpkh(pubkey)).address(network=NETWORKS[embit_network]) + + elif script_type == SettingsConstants.LEGACY_P2PKH: + return embit.script.p2pkh(pubkey).address(network=NETWORKS[embit_network]) + + elif script_type == SettingsConstants.TAPROOT: + # TODO: Not yet implemented! + raise Exception("Taproot verification not yet implemented!") + + + +def get_multisig_address(self, descriptor: Descriptor, script_type: str = SettingsConstants.NATIVE_SEGWIT, index: int = 0, is_change: bool = False, embit_network: str = "main"): + if is_change: + branch_index = 1 + else: + branch_index = 0 + + if script_type in [SettingsConstants.NATIVE_SEGWIT, SettingsConstants.NESTED_SEGWIT]: + return descriptor.derive(index, branch_index=branch_index).script_pubkey().address(network=NETWORKS[embit_network]) + + elif self.script_type == SettingsConstants.LEGACY_P2PKH: + # TODO: Not yet implemented! + raise Exception("Legacy P2PKH verification not yet implemented!") + + elif self.script_type == SettingsConstants.TAPROOT: + # TODO: Not yet implemented! + raise Exception("Taproot verification not yet implemented!") diff --git a/src/seedsigner/models/encode_qr.py b/src/seedsigner/models/encode_qr.py index d342597a..37e41848 100644 --- a/src/seedsigner/models/encode_qr.py +++ b/src/seedsigner/models/encode_qr.py @@ -37,6 +37,7 @@ class EncodeQR: qr_type: str = None qr_density: str = SettingsConstants.DENSITY__MEDIUM wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH + bitcoin_address: str = None def __post_init__(self): self.qr = QR() @@ -95,6 +96,9 @@ class EncodeQR: elif self.qr_type == QRType.SEED__COMPACTSEEDQR: self.encoder = CompactSeedQrEncoder(seed_phrase=self.seed_phrase, wordlist_language_code=self.wordlist_language_code) + + elif self.qr_type == QRType.BITCOIN_ADDRESS: + self.encoder = BitcoinAddressEncoder(address=self.bitcoin_address) else: raise Exception('QR Type not supported') @@ -318,6 +322,25 @@ class CompactSeedQrEncoder(SeedQrEncoder): +class BitcoinAddressEncoder(BaseQrEncoder): + def __init__(self, address: str): + super().__init__() + self.address = address + + + def seq_len(self): + return 1 + + + def next_part(self): + return self.address + + + @property + def is_complete(self): + return True + + class XpubQrEncoder(BaseQrEncoder): def __init__(self, seed_phrase, passphrase, derivation, network, wordlist_language_code): self.seed_phrase = seed_phrase diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index af29a882..03444fd8 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -189,42 +189,6 @@ class PSBTParser(): return cnt - @staticmethod - def calc_derivation(network, wallet_type, script_type): - # TODO: Move this to Seed? - if network == SettingsConstants.MAINNET: - network_path = "0'" - elif network == SettingsConstants.TESTNET: - network_path = "1'" - elif network == SettingsConstants.REGTEST: - # TODO: Is this right? - network_path = "1'" - else: - raise Exception("Unexpected network") - - if wallet_type == SettingsConstants.SINGLE_SIG: - if script_type == SettingsConstants.NATIVE_SEGWIT: - return f"m/84'/{network_path}/0'" - elif script_type == SettingsConstants.NESTED_SEGWIT: - return f"m/49'/{network_path}/0'" - elif script_type == SettingsConstants.TAPROOT: - return f"m/86'/{network_path}/0'" - else: - raise Exception("Unexpected script type") - - elif wallet_type == SettingsConstants.MULTISIG: - if script_type == SettingsConstants.NATIVE_SEGWIT: - return f"m/48'/{network_path}/0'/2'" - elif script_type == SettingsConstants.NESTED_SEGWIT: - return f"m/48'/{network_path}/0'/1'" - elif script_type == SettingsConstants.TAPROOT: - raise Exception("Taproot multisig/musig not yet supported") - else: - raise Exception("Unexpected script type") - else: - raise Exception("Unexpected wallet type") # checks that all inputs are from the same wallet - - @staticmethod def _get_policy(scope, scriptpubkey, xpubs): """Parse scope and get policy""" diff --git a/src/seedsigner/models/seed.py b/src/seedsigner/models/seed.py index 43a2f4d8..276037a7 100644 --- a/src/seedsigner/models/seed.py +++ b/src/seedsigner/models/seed.py @@ -6,6 +6,7 @@ from embit.networks import NETWORKS from typing import List from seedsigner.models.settings import SettingsConstants +from seedsigner.helpers import embit_utils @@ -106,12 +107,11 @@ class Seed: root = bip32.HDKey.from_seed(self.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(network)]["xprv"]) return hexlify(root.child(0).fingerprint).decode('utf-8') + def get_xpub(self, wallet_path: str = '/', network: str = SettingsConstants.MAINNET): - root = bip32.HDKey.from_seed(self.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(network)]["xprv"]) - xprv = root.derive(wallet_path) - xpub = xprv.to_public() - return xpub - + return embit_utils.get_xpub(seed_bytes=self.seed_bytes, derivation_path=wallet_path, embit_network=SettingsConstants.map_network_to_embit(network)) + + ### override operators def __eq__(self, other): if isinstance(other, Seed): diff --git a/src/seedsigner/views/scan_views.py b/src/seedsigner/views/scan_views.py index 1376cf1d..d263d261 100644 --- a/src/seedsigner/views/scan_views.py +++ b/src/seedsigner/views/scan_views.py @@ -15,13 +15,13 @@ class ScanView(View): def run(self): from seedsigner.gui.screens.scan_screens import ScanScreen - # Run the live preview and QR code capture process - # TODO: Does this belong in its own BaseThread? wordlist_language_code = self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) self.decoder = DecodeQR(wordlist_language_code=wordlist_language_code) - screen = ScanScreen(decoder=self.decoder) - screen.display() + # Start the live preview and background QR reading + ScanScreen(decoder=self.decoder).display() + + # Handle the results if self.decoder.is_complete: if self.decoder.is_seed: seed_mnemonic = self.decoder.get_seed_phrase() diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 9fb12d43..29778e4d 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -10,6 +10,7 @@ from typing import List from seedsigner.controller import Controller from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerCustomIconConstants +from seedsigner.helpers import embit_utils from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens) from seedsigner.gui.screens.screen import LargeIconStatusScreen, LoadingScreenThread, QRDisplayScreen @@ -18,7 +19,7 @@ from seedsigner.models.encode_qr import EncodeQR from seedsigner.models.psbt_parser import PSBTParser from seedsigner.models.qr_type import QRType from seedsigner.models.seed import InvalidSeedException, Seed -from seedsigner.models.settings import SettingsConstants +from seedsigner.models.settings import Settings, SettingsConstants from seedsigner.models.settings_definition import SettingsDefinition from seedsigner.models.threads import BaseThread, ThreadsafeCounter from seedsigner.views.psbt_views import PSBTChangeDetailsView @@ -349,11 +350,18 @@ class SeedOptionsView(View): REVIEW_PSBT = "Review PSBT" VERIFY_ADDRESS = "Verify Addr" EXPORT_XPUB = "Export Xpub" + EXPLORER = "Address Explorer" BACKUP = ("Backup Seed", None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT) DISCARD = ("Discard Seed", None, None, "red") button_data = [] + if self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER: + # Jump straight back into the address explorer script type selection flow + # But do ont cancel the `resume_main_flow` as we'll still need that after + # derivation path is specified. + return Destination(SeedExportXpubScriptTypeView, view_args=dict(seed_num=self.seed_num, sig_type=SettingsConstants.SINGLE_SIG), skip_current_view=True) + if self.controller.unverified_address: if self.controller.resume_main_flow == Controller.FLOW__VERIFY_SINGLESIG_ADDR: # Jump straight back into the single sig addr verification flow @@ -363,7 +371,7 @@ class SeedOptionsView(View): addr = self.controller.unverified_address["address"][:7] 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: @@ -382,13 +390,14 @@ class SeedOptionsView(View): if self.settings.get_value(SettingsConstants.SETTING__XPUB_EXPORT) == SettingsConstants.OPTION__ENABLED: button_data.append(EXPORT_XPUB) + button_data.append(EXPLORER) button_data.append(BACKUP) button_data.append(DISCARD) selected_menu_num = seed_screens.SeedOptionsScreen( button_data=button_data, fingerprint=self.seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)), - has_passphrase=self.seed.passphrase is not None + has_passphrase=self.seed.passphrase is not None, ).display() if selected_menu_num == RET_CODE__BACK_BUTTON: @@ -404,16 +413,20 @@ class SeedOptionsView(View): return Destination(ScanView) elif button_data[selected_menu_num] == VERIFY_ADDRESS: - return Destination(SeedAddressVerificationView, view_args={"seed_num": self.seed_num}) + return Destination(SeedAddressVerificationView, view_args=dict(seed_num=self.seed_num)) elif button_data[selected_menu_num] == EXPORT_XPUB: - return Destination(SeedExportXpubSigTypeView, view_args={"seed_num": self.seed_num}) + return Destination(SeedExportXpubSigTypeView, view_args=dict(seed_num=self.seed_num)) + + elif button_data[selected_menu_num] == EXPLORER: + self.controller.resume_main_flow = Controller.FLOW__ADDRESS_EXPLORER + return Destination(SeedExportXpubScriptTypeView, view_args=dict(seed_num=self.seed_num, sig_type=SettingsConstants.SINGLE_SIG)) elif button_data[selected_menu_num] == BACKUP: - return Destination(SeedBackupView, view_args={"seed_num": self.seed_num}) + return Destination(SeedBackupView, view_args=dict(seed_num=self.seed_num)) elif button_data[selected_menu_num] == DISCARD: - return Destination(SeedDiscardView, view_args={"seed_num": self.seed_num}) + return Destination(SeedDiscardView, view_args=dict(seed_num=self.seed_num)) @@ -488,23 +501,37 @@ class SeedExportXpubScriptTypeView(View): def run(self): + from .tools_views import ToolsAddressExplorerAddressTypeView args = {"seed_num": self.seed_num, "sig_type": self.sig_type} if len(self.settings.get_value(SettingsConstants.SETTING__SCRIPT_TYPES)) == 1: # Nothing to select; skip this screen args["script_type"] = self.settings.get_value(SettingsConstants.SETTING__SCRIPT_TYPES)[0] - return Destination(SeedExportXpubCoordinatorView, view_args=args, skip_current_view=True) + + if self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER: + del args["sig_type"] + return Destination(ToolsAddressExplorerAddressTypeView, view_args=args, skip_current_view=True) + else: + return Destination(SeedExportXpubCoordinatorView, view_args=args, skip_current_view=True) button_data = [] for script_type in self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SCRIPT_TYPES): button_data.append(script_type) + + title = "Export Xpub" + if self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER: + title = "Address Explorer" + selected_menu_num = ButtonListScreen( - title="Export Xpub", + title=title, is_button_text_centered=False, button_data=button_data, + is_bottom_list=True, ).display() - if selected_menu_num < len(button_data): + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + else: script_types_settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__SCRIPT_TYPES) selected_display_name = button_data[selected_menu_num] args["script_type"] = script_types_settings_entry.get_selection_option_value_by_display_name(selected_display_name) @@ -512,10 +539,11 @@ class SeedExportXpubScriptTypeView(View): if args["script_type"] == SettingsConstants.CUSTOM_DERIVATION: return Destination(SeedExportXpubCustomDerivationView, view_args=args) - return Destination(SeedExportXpubCoordinatorView, view_args=args) - - elif selected_menu_num == RET_CODE__BACK_BUTTON: - return Destination(BackStackView) + if self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER: + del args["sig_type"] + return Destination(ToolsAddressExplorerAddressTypeView, view_args=args) + else: + return Destination(SeedExportXpubCoordinatorView, view_args=args) @@ -535,9 +563,13 @@ class SeedExportXpubCustomDerivationView(View): if ret == RET_CODE__BACK_BUTTON: return Destination(BackStackView) - # ret should be the custom derivation path; store it in Settings + # ret will be the custom derivation path custom_derivation = ret + if self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER: + from .tools_views import ToolsAddressExplorerAddressTypeView + return Destination(ToolsAddressExplorerAddressTypeView, view_args=dict(seed_num=self.seed_num, script_type=self.script_type, custom_derivation=self.custom_derivation_path)) + return Destination( SeedExportXpubCoordinatorView, view_args={ @@ -651,7 +683,7 @@ class SeedExportXpubDetailsView(View): if self.script_type == SettingsConstants.CUSTOM_DERIVATION: derivation_path = self.custom_derivation else: - derivation_path = PSBTParser.calc_derivation( + derivation_path = embit_utils.get_standard_derivation_path( network=self.settings.get_value(SettingsConstants.SETTING__NETWORK), wallet_type=self.sig_type, script_type=self.script_type @@ -1254,7 +1286,7 @@ class AddressVerificationStartView(View): # TODO: detect single sig vs multisig or have to prompt? destination = Destination(NotYetImplementedView) - derivation_path = PSBTParser.calc_derivation( + derivation_path = embit_utils.get_standard_derivation_path( network=self.controller.unverified_address["network"], wallet_type=sig_type, script_type=self.controller.unverified_address["script_type"] @@ -1298,7 +1330,7 @@ class AddressVerificationSigTypeView(View): destination = Destination(LoadMultisigWalletDescriptorView) self.controller.unverified_address["sig_type"] = sig_type - derivation_path = PSBTParser.calc_derivation( + derivation_path = embit_utils.get_standard_derivation_path( network=self.controller.unverified_address["network"], wallet_type=sig_type, script_type=self.controller.unverified_address["script_type"] @@ -1412,7 +1444,7 @@ class SeedAddressVerificationView(View): seed=self.seed, descriptor=self.controller.multisig_wallet_descriptor, script_type=self.script_type, - network=embit_network, + embit_network=embit_network, derivation_path=self.derivation_path, threadsafe_counter=self.threadsafe_counter, verified_index=self.verified_index, @@ -1483,7 +1515,7 @@ class SeedAddressVerificationView(View): class BruteForceAddressVerificationThread(BaseThread): - def __init__(self, address: str, seed: Seed, descriptor: Descriptor, script_type: str, network: str, derivation_path: str, threadsafe_counter: ThreadsafeCounter, verified_index: ThreadsafeCounter, verified_index_is_change: ThreadsafeCounter): + def __init__(self, address: str, seed: Seed, descriptor: Descriptor, script_type: str, embit_network: str, derivation_path: str, threadsafe_counter: ThreadsafeCounter, verified_index: ThreadsafeCounter, verified_index_is_change: ThreadsafeCounter): """ Either seed or descriptor will be None """ @@ -1492,17 +1524,15 @@ class SeedAddressVerificationView(View): self.seed = seed self.descriptor = descriptor self.script_type = script_type - self.network = network + self.embit_network = embit_network self.derivation_path = derivation_path self.threadsafe_counter = threadsafe_counter self.verified_index = verified_index self.verified_index_is_change = verified_index_is_change if self.seed: - root = embit.bip32.HDKey.from_seed(self.seed.seed_bytes, version=network["xprv"]) - xprv = root.derive(self.derivation_path) - self.xpub = xprv.to_public() - + self.xpub = self.seed.get_xpub(wallet_path=self.derivation_path, network=Settings.get_instance().get_value(SettingsConstants.SETTING__NETWORK)) + def run(self): while self.keep_running: @@ -1512,9 +1542,12 @@ class SeedAddressVerificationView(View): i = self.threadsafe_counter.cur_count if self.descriptor: - (receive_address, change_address) = self.derive_multisig(i) + receive_address = embit_utils.get_multisig_address(self.descriptor, self.script_type, index=i, is_change=False, embit_network=self.embit_network) + change_address = embit_utils.get_multisig_address(self.descriptor, self.script_type, index=i, is_change=True, embit_network=self.embit_network) + else: - (receive_address, change_address) = self.derive_single_sig(i) + receive_address = embit_utils.get_single_sig_address(self.xpub, self.script_type, index=i, is_change=False, embit_network=self.embit_network) + change_address = embit_utils.get_single_sig_address(self.xpub, self.script_type, index=i, is_change=True, embit_network=self.embit_network) if self.address == receive_address: self.verified_index.set_value(i) @@ -1530,47 +1563,8 @@ class SeedAddressVerificationView(View): # Increment our index counter self.threadsafe_counter.increment() - - - def derive_single_sig(self, index): - r_pubkey = self.xpub.derive([0,index]).key - c_pubkey = self.xpub.derive([1,index]).key - - receive_address = "" - change_address = "" - - if self.script_type == SettingsConstants.NATIVE_SEGWIT: - receive_address = embit.script.p2wpkh(r_pubkey).address(network=self.network) - change_address = embit.script.p2wpkh(c_pubkey).address(network=self.network) - elif self.script_type == SettingsConstants.NESTED_SEGWIT: - receive_address = embit.script.p2sh(embit.script.p2wpkh(r_pubkey)).address(network=self.network) - change_address = embit.script.p2sh(embit.script.p2wpkh(c_pubkey)).address(network=self.network) - elif self.script_type == SettingsConstants.LEGACY_P2PKH: - receive_address = embit.script.p2pkh(r_pubkey).address(network=self.network) - change_address = embit.script.p2pkh(c_pubkey).address(network=self.network) - elif self.script_type == SettingsConstants.TAPROOT: - # TODO: Not yet implemented! - raise Exception("Taproot verification not yet implemented!") - - return (receive_address, change_address) - def derive_multisig(self, index): - if self.script_type in [SettingsConstants.NATIVE_SEGWIT, SettingsConstants.NESTED_SEGWIT]: - receive_address = self.descriptor.derive(index, branch_index=0).script_pubkey().address(network=self.network) - change_address = self.descriptor.derive(index, branch_index=1).script_pubkey().address(network=self.network) - - elif self.script_type == SettingsConstants.LEGACY_P2PKH: - # TODO: Not yet implemented! - raise Exception("Taproot verification not yet implemented!") - - elif self.script_type == SettingsConstants.TAPROOT: - # TODO: Not yet implemented! - raise Exception("Taproot verification not yet implemented!") - - return (receive_address, change_address) - - class AddressVerificationSuccessView(View): def __init__(self, seed_num: int): @@ -1634,6 +1628,7 @@ class MultisigWalletDescriptorView(View): RETURN = "Return to PSBT" VERIFY = "Verify Addr" + EXPLORER = "Address Explorer" OK = "OK" button_data = [OK] @@ -1643,6 +1638,8 @@ class MultisigWalletDescriptorView(View): elif self.controller.resume_main_flow == Controller.FLOW__VERIFY_MULTISIG_ADDR and self.controller.unverified_address: VERIFY += f""" {self.controller.unverified_address["address"][:7]}""" button_data = [VERIFY] + elif self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER: + button_data = [EXPLORER] selected_menu_num = seed_screens.MultisigWalletDescriptorScreen( policy=policy, @@ -1664,4 +1661,9 @@ class MultisigWalletDescriptorView(View): # TODO: Route properly when multisig brute-force addr verification is done return Destination(SeedAddressVerificationView) + elif button_data[selected_menu_num] == EXPLORER: + self.controller.resume_main_flow = None + # TODO: Finish implementation + return Destination(NotYetImplementedView) + return Destination(MainMenuView) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 8c6b2216..0d2a9a28 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -4,15 +4,19 @@ import time from PIL import Image from PIL.ImageOps import autocontrast +from seedsigner.controller import Controller +from seedsigner.gui.screens.screen import LoadingScreenThread, QRDisplayScreen from seedsigner.hardware.camera import Camera -from seedsigner.gui.components import FontAwesomeIconConstants +from seedsigner.gui.components import FontAwesomeIconConstants, GUIConstants, SeedSignerCustomIconConstants from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen) -from seedsigner.gui.screens.tools_screens import ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordShowFinalWordScreen -from seedsigner.helpers import mnemonic_generation +from seedsigner.gui.screens.tools_screens import ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordShowFinalWordScreen, ToolsAddressExplorerAddressTypeScreen +from seedsigner.helpers import embit_utils, mnemonic_generation +from seedsigner.models.encode_qr import EncodeQR +from seedsigner.models.qr_type import QRType from seedsigner.models.seed import Seed from seedsigner.models.settings_definition import SettingsConstants -from seedsigner.views.seed_views import SeedDiscardView, SeedFinalizeView, SeedMnemonicEntryView, SeedWordsWarningView +from seedsigner.views.seed_views import SeedDiscardView, SeedFinalizeView, SeedMnemonicEntryView, SeedWordsWarningView, SeedExportXpubScriptTypeView from .view import View, Destination, BackStackView @@ -23,7 +27,8 @@ class ToolsMenuView(View): IMAGE = (" New seed", FontAwesomeIconConstants.CAMERA) DICE = ("New seed", FontAwesomeIconConstants.DICE) KEYBOARD = ("Calc 12th/24th word", FontAwesomeIconConstants.KEYBOARD) - button_data = [IMAGE, DICE, KEYBOARD] + EXPLORER = "Address Explorer" + button_data = [IMAGE, DICE, KEYBOARD, EXPLORER] screen = ButtonListScreen( title="Tools", is_button_text_centered=False, @@ -43,6 +48,10 @@ class ToolsMenuView(View): elif button_data[selected_menu_num] == KEYBOARD: return Destination(ToolsCalcFinalWordNumWordsView) + elif button_data[selected_menu_num] == EXPLORER: + return Destination(ToolsAddressExplorerSelectSourceView) + + """**************************************************************************** @@ -200,7 +209,7 @@ class ToolsDiceEntropyEntryView(View): def run(self): ret = ToolsDiceEntropyEntryScreen( - total_rolls=self.total_rolls, + return_after_n_chars=self.total_rolls, ).display() if ret == RET_CODE__BACK_BUTTON: @@ -276,3 +285,239 @@ class ToolsCalcFinalWordShowFinalWordView(View): elif button_data[selected_menu_num] == DISCARD: return Destination(SeedDiscardView) + + +"""**************************************************************************** + Address Explorer Views +****************************************************************************""" +class ToolsAddressExplorerSelectSourceView(View): + def run(self): + SCAN_SEED = ("Scan a seed", FontAwesomeIconConstants.QRCODE) + SCAN_DESCRIPTOR = ("Scan wallet descriptor", FontAwesomeIconConstants.QRCODE) + TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) + TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) + button_data = [] + + seeds = self.controller.storage.seeds + for seed in seeds: + button_str = seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)) + + if seed.passphrase is not None: + # TODO: Include lock icon on right side of button + pass + button_data.append((button_str, SeedSignerCustomIconConstants.FINGERPRINT, "blue")) + + button_data.append(SCAN_SEED) + button_data.append(SCAN_DESCRIPTOR) + button_data.append(TYPE_12WORD) + button_data.append(TYPE_24WORD) + + selected_menu_num = ButtonListScreen( + title="Address Explorer", + button_data=button_data, + is_button_text_centered=False, + is_bottom_list=True, + ).display() + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + # Most of the options require us to go through a side flow(s) before we can + # continue to the address explorer. Set the Controller-level flow so that it + # knows to re-route us once the side flow is complete. + self.controller.resume_main_flow = Controller.FLOW__ADDRESS_EXPLORER + + if len(seeds) > 0 and selected_menu_num < len(seeds): + # User selected one of the n seeds + return Destination( + SeedExportXpubScriptTypeView, + view_args=dict( + seed_num=selected_menu_num, + sig_type=SettingsConstants.SINGLE_SIG, + ) + ) + + elif button_data[selected_menu_num] in [SCAN_SEED, SCAN_DESCRIPTOR]: + from seedsigner.views.scan_views import ScanView + return Destination(ScanView) + + elif button_data[selected_menu_num] in [TYPE_12WORD, TYPE_24WORD]: + from seedsigner.views.seed_views import SeedMnemonicEntryView + if button_data[selected_menu_num] == TYPE_12WORD: + self.controller.storage.init_pending_mnemonic(num_words=12) + else: + self.controller.storage.init_pending_mnemonic(num_words=24) + return Destination(SeedMnemonicEntryView) + + + +class ToolsAddressExplorerAddressTypeView(View): + def __init__(self, seed_num: int = None, script_type: str = None, custom_derivation: str = None): + """ + If the explorer source is a seed, `seed_num` and `script_type` must be + specified. `custom_derivation` can be specified as needed. + + If the source is a multisig or single sig wallet descriptor, `seed_num`, + `script_type`, and `custom_derivation` should be `None`. + """ + super().__init__() + self.seed_num = seed_num + self.script_type = script_type + self.custom_derivation = custom_derivation + + network = self.settings.get_value(SettingsConstants.SETTING__NETWORK) + + # Store everything in the Controller's `address_explorer_data` so we don't have + # to keep passing vals around from View to View and recalculating. + data = dict( + seed_num=seed_num, + network=self.settings.get_value(SettingsConstants.SETTING__NETWORK), + embit_network=SettingsConstants.map_network_to_embit(network), + script_type=script_type, + ) + if self.seed_num is not None: + self.seed = self.controller.storage.seeds[seed_num] + data["seed_num"] = self.seed + + if self.script_type == SettingsConstants.CUSTOM_DERIVATION: + derivation_path = self.custom_derivation + else: + derivation_path = embit_utils.get_standard_derivation_path( + network=self.settings.get_value(SettingsConstants.SETTING__NETWORK), + wallet_type=SettingsConstants.SINGLE_SIG, + script_type=self.script_type, + ) + + data["derivation_path"] = derivation_path + data["xpub"] = self.seed.get_xpub(derivation_path, network=network) + + else: + # TODO: + data["wallet_descriptor"] = None + raise Exception("Not yet implemented") + + self.controller.address_explorer_data = data + + + def run(self): + # embit_utils.get_single_sig_address(xpub=xpub, script_type=self.script_type, index=0, is_change=True, embit_network=embit_network) + data = self.controller.address_explorer_data + + RECEIVE = "Receive Addresses" + CHANGE = "Change Addresses" + button_data = [RECEIVE, CHANGE] + + selected_menu_num = ToolsAddressExplorerAddressTypeScreen( + button_data=button_data, + fingerprint=self.seed.get_fingerprint() if self.seed else None, + wallet_descriptor=data["wallet_descriptor"] if "wallet_descriptor" in data else None, + script_type=data["script_type"] if "script_type" in data else None, + ).display() + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + elif button_data[selected_menu_num] in [RECEIVE, CHANGE]: + return Destination(ToolsAddressExplorerAddressListView, view_args=dict(is_change=button_data[selected_menu_num] == CHANGE)) + + + +class ToolsAddressExplorerAddressListView(View): + def __init__(self, is_change: bool = False, start_index: int = 0, selected_button_index: int = 0): + super().__init__() + self.is_change = is_change + self.start_index = start_index + self.selected_button_index = selected_button_index + self.loading_screen = LoadingScreenThread(text="Calculating addrs...") + + + def run(self): + self.loading_screen.start() + + try: + addresses = [] + button_data = [] + data = self.controller.address_explorer_data + addrs_per_screen = 10 + + addr_storage_key = "receive_addrs" + if self.is_change: + addr_storage_key = "change_addrs" + + if addr_storage_key in data and len(data[addr_storage_key]) >= self.start_index + addrs_per_screen: + # We already calculated this range addresses; just retrieve them + addresses = data[addr_storage_key][self.start_index:self.start_index + addrs_per_screen] + + else: + if addr_storage_key not in data: + data[addr_storage_key] = [] + + if "xpub" in data: + # Single sig explore from seed + if "script_type" in data: + # Standard derivation path + for i in range(self.start_index, self.start_index + addrs_per_screen): + address = embit_utils.get_single_sig_address(xpub=data["xpub"], script_type=data["script_type"], index=i, is_change=self.is_change, embit_network=data["embit_network"]) + addresses.append(address) + data[addr_storage_key].append(address) + else: + # Custom derivation path + raise Exception("Not yet implemented") + + for i, address in enumerate(addresses): + cur_index = i + self.start_index + if cur_index < 10: + end_digits = -6 + elif cur_index < 100: + end_digits = -5 + else: + end_digits = -4 + button_data.append(f"{cur_index}:{address[:8]}...{address[end_digits:]}") + + button_data.append(("Next {}".format(addrs_per_screen), None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT)) + + screen = ButtonListScreen( + title="{} Addrs".format("Receive" if not self.is_change else "Change"), + button_data=button_data, + button_font_name=GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME, + button_font_size=GUIConstants.BUTTON_FONT_SIZE + 4, + is_button_text_centered=False, + is_bottom_list=True, + selected_button=self.selected_button_index, + ) + finally: + # Everything is set. Stop the loading screen + self.loading_screen.stop() + + selected_menu_num = screen.display() + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + if selected_menu_num == len(addresses): + # User clicked NEXT + return Destination(ToolsAddressExplorerAddressListView, view_args=dict(is_change=self.is_change, start_index=self.start_index + addrs_per_screen)) + + 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) + + + +class ToolsAddressExplorerAddressView(View): + def __init__(self, index: int, address: str, is_change: bool, start_index: int): + super().__init__() + self.index = index + self.address = address + self.is_change = is_change + self.start_index = start_index + + data = self.controller.address_explorer_data + + def run(self): + qr_encoder = EncodeQR(qr_type=QRType.BITCOIN_ADDRESS, bitcoin_address=self.address) + QRDisplayScreen( + qr_encoder=qr_encoder, + ).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) From c8a433acbdd0fe24bbbd7c654cb59ecf1fe6080e Mon Sep 17 00:00:00 2001 From: kdmukai Date: Tue, 31 May 2022 07:58:09 -0500 Subject: [PATCH 16/39] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6ae5b379..8422bd5c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ src/seedsigner.egg-info/ .nova .vscode src/seedsigner/models/settings_definition.json +*.mo \ No newline at end of file From c0691e4c303c145c916c287b82d26dff457d2a88 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 1 Jun 2022 20:13:03 -0500 Subject: [PATCH 17/39] Bugfix and more in-depth explainer comments --- src/seedsigner/gui/screens/seed_screens.py | 7 +++++-- src/seedsigner/views/seed_views.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 71f3f838..8878cf98 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -1498,6 +1498,7 @@ class SeedAddressVerificationScreen(ButtonListScreen): print(f"verified_index: {self.verified_index.cur_count}") if self.verified_index.cur_count is not None: print("Screen callback returning success!") + self.threads[-1].stop() return 1 @@ -1511,10 +1512,12 @@ class SeedAddressVerificationScreen(ButtonListScreen): def run(self): - font = Fonts.get_font(GUIConstants.BODY_FONT_NAME, GUIConstants.BODY_FONT_SIZE) while self.keep_running: if self.verified_index.cur_count is not None: - # Have to trigger a hw_input event to break the Screen out of the wait_for loop + # This thread will detect the success state while its parent Screen + # holds in its `wait_for`. Have to trigger a hw_input event to break + # the Screen._run out of the `wait_for` state. The Screen will then + # call its `_run_callback` and detect the success state and exit. HardwareButtons.get_instance().trigger_override(force_release=True) return diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 29778e4d..6c6a8c28 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1427,7 +1427,7 @@ class SeedAddressVerificationView(View): return Destination(NotYetImplementedView) # TODO: This should be in `Seed` or `PSBT` utility class - embit_network = NETWORKS[SettingsConstants.map_network_to_embit(self.network)] + embit_network = SettingsConstants.map_network_to_embit(self.network) # The ThreadsafeCounter will be shared by the brute-force thread to keep track of # its current addr index number and the Screen to display its progress and From 1c6b56636908deb182993b229f7ff5572e85df0f Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 1 Jun 2022 20:30:13 -0500 Subject: [PATCH 18/39] Cleaner, guaranteed thread exits --- src/seedsigner/gui/screens/screen.py | 2 ++ src/seedsigner/gui/screens/seed_screens.py | 2 ++ src/seedsigner/views/seed_views.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index 6ea6fc17..5340dcdb 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -722,6 +722,8 @@ class QRDisplayScreen(BaseScreen): else: # Any other input exits the screen self.threads[-1].stop() + while self.threads[-1].is_alive(): + time.sleep(0.01) break Settings.get_instance().set_value(SettingsConstants.SETTING__QR_BRIGHTNESS, self.qr_brightness.cur_count) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 8878cf98..7893616e 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -1499,6 +1499,8 @@ class SeedAddressVerificationScreen(ButtonListScreen): if self.verified_index.cur_count is not None: print("Screen callback returning success!") self.threads[-1].stop() + while self.threads[-1].is_alive(): + time.sleep(0.01) return 1 diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 6c6a8c28..98f280ce 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1509,6 +1509,8 @@ class SeedAddressVerificationView(View): # Halt the thread if the user gave up (will already be stopped if it verified the # target addr). self.addr_verification_thread.stop() + while self.addr_verification_thread.is_alive(): + time.sleep(0.01) return Destination(MainMenuView) From 7bbe7cf55a3b964a52cb06949682e2e61aa8e789 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 12 Jun 2022 13:36:40 -0500 Subject: [PATCH 19/39] Use SeedSigner fork of `embit` @v0.4.14 w/`secp256k1` binary for Pi Zero --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1fb2f7b7..5a58303a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -embit==0.4.10 +embit @ git+https://github.com/seedsigner/embit.git@03cba13e0add64b241972c67e2aa970cb2e3a5d2 numpy==1.21.1 picamera==1.13 Pillow==8.2.0 From cbd53fd3d91765af7975f33106f8f036d30ee806 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 12 Jun 2022 15:42:35 -0500 Subject: [PATCH 20/39] Multisig wallet descriptor support --- src/seedsigner/gui/screens/tools_screens.py | 12 +++++-- src/seedsigner/helpers/embit_utils.py | 9 ++--- src/seedsigner/views/seed_views.py | 13 ++++--- src/seedsigner/views/tools_views.py | 38 +++++++++++++++------ 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index fa13fc41..669e3d12 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -298,7 +298,7 @@ class ToolsCalcFinalWordShowFinalWordScreen(ButtonListScreen): @dataclass class ToolsAddressExplorerAddressTypeScreen(ButtonListScreen): fingerprint: str = None - wallet_descriptor: Any = None + wallet_descriptor_display_name: Any = None script_type: str = None custom_derivation_path: str = None @@ -333,6 +333,12 @@ class ToolsAddressExplorerAddressTypeScreen(ButtonListScreen): screen_x=GUIConstants.EDGE_PADDING, screen_y=self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING, )) + else: - raise Exception("Implement wallet descriptor support!") - + self.components.append(IconTextLine( + label_text="Wallet descriptor", + value_text=self.wallet_descriptor_display_name, + is_text_centered=False, + screen_x=GUIConstants.EDGE_PADDING, + screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, + )) diff --git a/src/seedsigner/helpers/embit_utils.py b/src/seedsigner/helpers/embit_utils.py index 9fecbf61..5c6a5a6b 100644 --- a/src/seedsigner/helpers/embit_utils.py +++ b/src/seedsigner/helpers/embit_utils.py @@ -77,19 +77,20 @@ def get_single_sig_address(xpub: HDKey, script_type: str = SettingsConstants.NAT -def get_multisig_address(self, descriptor: Descriptor, script_type: str = SettingsConstants.NATIVE_SEGWIT, index: int = 0, is_change: bool = False, embit_network: str = "main"): +def get_multisig_address(descriptor: Descriptor, index: int = 0, is_change: bool = False, embit_network: str = "main"): if is_change: branch_index = 1 else: branch_index = 0 - if script_type in [SettingsConstants.NATIVE_SEGWIT, SettingsConstants.NESTED_SEGWIT]: + if descriptor.is_segwit: + # Could be native segwit or nested segwit (descriptor.is_wrapped) return descriptor.derive(index, branch_index=branch_index).script_pubkey().address(network=NETWORKS[embit_network]) - elif self.script_type == SettingsConstants.LEGACY_P2PKH: + elif descriptor.is_legacy: # TODO: Not yet implemented! raise Exception("Legacy P2PKH verification not yet implemented!") - elif self.script_type == SettingsConstants.TAPROOT: + elif descriptor.is_taproot: # TODO: Not yet implemented! raise Exception("Taproot verification not yet implemented!") diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 98f280ce..51e23909 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1544,12 +1544,12 @@ class SeedAddressVerificationView(View): i = self.threadsafe_counter.cur_count if self.descriptor: - receive_address = embit_utils.get_multisig_address(self.descriptor, self.script_type, index=i, is_change=False, embit_network=self.embit_network) - change_address = embit_utils.get_multisig_address(self.descriptor, self.script_type, index=i, is_change=True, embit_network=self.embit_network) + receive_address = embit_utils.get_multisig_address(descriptor=self.descriptor, index=i, is_change=False, embit_network=self.embit_network) + change_address = embit_utils.get_multisig_address(descriptor=self.descriptor, index=i, is_change=True, embit_network=self.embit_network) else: - receive_address = embit_utils.get_single_sig_address(self.xpub, self.script_type, index=i, is_change=False, embit_network=self.embit_network) - change_address = embit_utils.get_single_sig_address(self.xpub, self.script_type, index=i, is_change=True, embit_network=self.embit_network) + receive_address = embit_utils.get_single_sig_address(xpub=self.xpub, script_type=self.script_type, index=i, is_change=False, embit_network=self.embit_network) + change_address = embit_utils.get_single_sig_address(xpub=self.xpub, script_type=self.script_type, index=i, is_change=True, embit_network=self.embit_network) if self.address == receive_address: self.verified_index.set_value(i) @@ -1660,12 +1660,11 @@ class MultisigWalletDescriptorView(View): elif button_data[selected_menu_num] == VERIFY: self.controller.resume_main_flow = None - # TODO: Route properly when multisig brute-force addr verification is done return Destination(SeedAddressVerificationView) elif button_data[selected_menu_num] == EXPLORER: + from seedsigner.views.tools_views import ToolsAddressExplorerAddressTypeView self.controller.resume_main_flow = None - # TODO: Finish implementation - return Destination(NotYetImplementedView) + return Destination(ToolsAddressExplorerAddressTypeView) return Destination(MainMenuView) diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index 0d2a9a28..a8c5fad3 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -2,6 +2,7 @@ import hashlib import os import time +from embit.descriptor import Descriptor from PIL import Image from PIL.ImageOps import autocontrast from seedsigner.controller import Controller @@ -392,9 +393,7 @@ class ToolsAddressExplorerAddressTypeView(View): data["xpub"] = self.seed.get_xpub(derivation_path, network=network) else: - # TODO: - data["wallet_descriptor"] = None - raise Exception("Not yet implemented") + data["wallet_descriptor"] = self.controller.multisig_wallet_descriptor self.controller.address_explorer_data = data @@ -407,11 +406,17 @@ class ToolsAddressExplorerAddressTypeView(View): CHANGE = "Change Addresses" button_data = [RECEIVE, CHANGE] + wallet_descriptor_display_name = None + if "wallet_descriptor" in data: + wallet_descriptor_display_name = data["wallet_descriptor"].brief_policy.replace(" (sorted)", "") + + script_type = data["script_type"] if "script_type" in data else None + selected_menu_num = ToolsAddressExplorerAddressTypeScreen( button_data=button_data, - fingerprint=self.seed.get_fingerprint() if self.seed else None, - wallet_descriptor=data["wallet_descriptor"] if "wallet_descriptor" in data else None, - script_type=data["script_type"] if "script_type" in data else None, + fingerprint=self.seed.get_fingerprint() if self.seed_num else None, + wallet_descriptor_display_name=wallet_descriptor_display_name, + script_type=script_type, ).display() if selected_menu_num == RET_CODE__BACK_BUTTON: @@ -428,12 +433,10 @@ class ToolsAddressExplorerAddressListView(View): self.is_change = is_change self.start_index = start_index self.selected_button_index = selected_button_index - self.loading_screen = LoadingScreenThread(text="Calculating addrs...") def run(self): - self.loading_screen.start() - + self.loading_screen = None try: addresses = [] button_data = [] @@ -449,6 +452,9 @@ class ToolsAddressExplorerAddressListView(View): addresses = data[addr_storage_key][self.start_index:self.start_index + addrs_per_screen] else: + self.loading_screen = LoadingScreenThread(text="Calculating addrs...") + self.loading_screen.start() + if addr_storage_key not in data: data[addr_storage_key] = [] @@ -463,6 +469,17 @@ class ToolsAddressExplorerAddressListView(View): else: # Custom derivation path raise Exception("Not yet implemented") + + elif "wallet_descriptor" in data: + descriptor: Descriptor = data["wallet_descriptor"] + if descriptor.is_basic_multisig: + for i in range(self.start_index, self.start_index + addrs_per_screen): + address = embit_utils.get_multisig_address(descriptor=descriptor, index=i, is_change=self.is_change, embit_network=data["embit_network"]) + addresses.append(address) + data[addr_storage_key].append(address) + + else: + raise Exception("Single sig descriptors not yet supported") for i, address in enumerate(addresses): cur_index = i + self.start_index @@ -487,7 +504,8 @@ class ToolsAddressExplorerAddressListView(View): ) finally: # Everything is set. Stop the loading screen - self.loading_screen.stop() + if self.loading_screen: + self.loading_screen.stop() selected_menu_num = screen.display() From a42408a3e49e18e0864302a434cef4cf31b3e938 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 15 Jun 2022 14:51:47 -0500 Subject: [PATCH 21/39] mnemonic_generation test case bug fix; additional test case. --- src/seedsigner/helpers/mnemonic_generation.py | 10 +++++++++- tests/test_mnemonic_generation.py | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/seedsigner/helpers/mnemonic_generation.py b/src/seedsigner/helpers/mnemonic_generation.py index 6723b512..dab63a48 100644 --- a/src/seedsigner/helpers/mnemonic_generation.py +++ b/src/seedsigner/helpers/mnemonic_generation.py @@ -9,7 +9,15 @@ from seedsigner.models.seed import Seed def calculate_checksum(mnemonic: list, wordlist_language_code: str) -> List[str]: - """ Provide 12- or 24-word mnemonic, returns complete mnemonic w/checksum as a list """ + """ + Provide 12- or 24-word mnemonic, returns complete mnemonic w/checksum as a list. + + If 11- or 23-words are provided, append word `0000` to end of list as temp final + word. + """ + if len(mnemonic) in [11, 23]: + mnemonic.append(Seed.get_wordlist(wordlist_language_code)[0]) + if len(mnemonic) not in [12, 24]: raise Exception("Pass in a 12- or 24-word mnemonic") diff --git a/tests/test_mnemonic_generation.py b/tests/test_mnemonic_generation.py index a5b3fd0c..a0c04ed8 100644 --- a/tests/test_mnemonic_generation.py +++ b/tests/test_mnemonic_generation.py @@ -43,6 +43,26 @@ def test_calculate_checksum(): +def test_calculate_checksum_with_default_final_word(): + """ 11-word and 23-word mnemonics use word `0000` as a temp final word to complete + the mnemonic. + """ + partial_mnemonic = "crawl focus rescue cable view pledge rather dinner cousin unfair day" + mnemonic1 = mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) + + partial_mnemonic += " abandon" + mnemonic2 = mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) + assert mnemonic1 == mnemonic2 + + partial_mnemonic = "bubble father debate ankle injury fence mesh evolve section wet coyote violin pyramid flower rent arrow round clutch myth safe base skin mobile" + mnemonic1 = mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) + + partial_mnemonic += " abandon" + mnemonic2 = mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist_language_code=SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) + assert mnemonic1 == mnemonic2 + + + def test_verify_against_coldcard_sample(): """ https://coldcard.com/docs/verifying-dice-roll-math """ dice_rolls = "123456" From 36635a1b7bf82b37018643fbab527fea1c3e4512 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 15 Jun 2022 15:08:24 -0500 Subject: [PATCH 22/39] Update seed_views.py --- src/seedsigner/views/seed_views.py | 48 ++++++++++++++---------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 0744f40d..df968338 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -676,10 +676,6 @@ class SeedExportXpubDetailsView(View): def run(self): - # The calc_derivation takes a few moments. Run the loading screen while we wait. - self.loading_screen = LoadingScreenThread(text="Generating xpub...") - self.loading_screen.start() - if self.script_type == SettingsConstants.CUSTOM_DERIVATION: derivation_path = self.custom_derivation else: @@ -689,7 +685,16 @@ class SeedExportXpubDetailsView(View): script_type=self.script_type ) - if self.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__ENABLED: + if self.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__DISABLED: + # We're just skipping right past this screen + selected_menu_num = 0 + + else: + # The derivation calc takes a few moments. Run the loading screen while we wait. + self.loading_screen = LoadingScreenThread(text="Generating xpub...") + self.loading_screen.start() + + try: embit_network = NETWORKS[SettingsConstants.map_network_to_embit(self.settings.get_value(SettingsConstants.SETTING__NETWORK))] version = embit.bip32.detect_version( derivation_path, @@ -707,33 +712,26 @@ class SeedExportXpubDetailsView(View): xpub = xprv.to_public() xpub_base58 = xpub.to_string(version=version) - screen = seed_screens.SeedExportXpubDetailsScreen( - fingerprint=fingerprint, - has_passphrase=self.seed.passphrase is not None, - derivation_path=derivation_path, - xpub=xpub_base58, - ) - + finally: self.loading_screen.stop() - selected_menu_num = screen.display() - else: - selected_menu_num = 0 - - finally: - self.loading_screen.stop() + selected_menu_num = seed_screens.SeedExportXpubDetailsScreen( + fingerprint=fingerprint, + has_passphrase=self.seed.passphrase is not None, + derivation_path=derivation_path, + xpub=xpub_base58, + ).display() if selected_menu_num == 0: return Destination( SeedExportXpubQRDisplayView, - { - "seed_num": self.seed_num, - "sig_type": self.sig_type, - "script_type": self.script_type, - "coordinator": self.coordinator, - "derivation_path": derivation_path, - } + dict(seed_num=self.seed_num, + sig_type=self.sig_type, + script_type=self.script_type, + coordinator=self.coordinator, + derivation_path=derivation_path, + ) ) elif selected_menu_num == RET_CODE__BACK_BUTTON: From 8de643bf219a052424a328ff700c1b88559910f5 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Thu, 16 Jun 2022 09:03:43 -0500 Subject: [PATCH 23/39] version num bump! --- 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 a03a160b..44b06e69 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -48,7 +48,7 @@ class Controller(Singleton): rather than at the top in order avoid circular imports. """ - VERSION = "0.5.0" + VERSION = "0.5.1" # Declare class member vars with type hints to enable richer IDE support throughout # the code. From b8eca14c0eb10bfa85f21d35f4ced39d710af677 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 25 Jun 2022 11:54:53 -0700 Subject: [PATCH 24/39] Update controller.py --- src/seedsigner/controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 44b06e69..48b2977b 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -197,9 +197,9 @@ class Controller(Singleton): def run(self, some_arg, other_arg): print(other_arg) - class OtherView(): + class OtherView(View): def run(self): - return (MyView, {"some_arg": 1, "other_arg": "hello"}) + return (MyView, dict(some_arg=1, other_arg="hello")) When `OtherView` is instantiated and run, we capture its return values: From 0f758b6552b6e13e740b3728709bb38023dc646a Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 27 Jun 2022 11:58:14 -0700 Subject: [PATCH 25/39] Adds Taproot address support * Bugfix after latest merges from `dev`. * More build-out and bugfixes for custom derivation but still not supported. --- src/seedsigner/gui/screens/tools_screens.py | 6 +++--- src/seedsigner/helpers/embit_utils.py | 3 +-- src/seedsigner/views/seed_views.py | 2 +- src/seedsigner/views/tools_views.py | 12 ++++++------ 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/seedsigner/gui/screens/tools_screens.py b/src/seedsigner/gui/screens/tools_screens.py index bbea0726..65ffcc3d 100644 --- a/src/seedsigner/gui/screens/tools_screens.py +++ b/src/seedsigner/gui/screens/tools_screens.py @@ -8,7 +8,7 @@ from seedsigner.gui.components import FontAwesomeIconConstants, Fonts, Formatted from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, BaseScreen, BaseTopNavScreen, ButtonListScreen, KeyboardScreen from seedsigner.hardware.buttons import HardwareButtonsConstants from seedsigner.helpers.qr import QR -from seedsigner.models.settings_definition import SettingsConstants +from seedsigner.models.settings_definition import SettingsConstants, SettingsDefinition @@ -394,11 +394,11 @@ class ToolsAddressExplorerAddressTypeScreen(ButtonListScreen): screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, )) - if self.script_type: + if self.script_type != SettingsConstants.CUSTOM_DERIVATION: self.components.append(IconTextLine( icon_name=SeedSignerCustomIconConstants.PATH, label_text="Derivation", - value_text="Native Segwit" if self.script_type == SettingsConstants.NATIVE_SEGWIT else "Nested Segwit", + value_text=SettingsDefinition.get_settings_entry(attr_name=SettingsConstants.SETTING__SCRIPT_TYPES).get_selection_option_display_name_by_value(value=self.script_type), screen_x=GUIConstants.EDGE_PADDING, screen_y=self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING, )) diff --git a/src/seedsigner/helpers/embit_utils.py b/src/seedsigner/helpers/embit_utils.py index 5c6a5a6b..bdd95b7f 100644 --- a/src/seedsigner/helpers/embit_utils.py +++ b/src/seedsigner/helpers/embit_utils.py @@ -72,8 +72,7 @@ def get_single_sig_address(xpub: HDKey, script_type: str = SettingsConstants.NAT return embit.script.p2pkh(pubkey).address(network=NETWORKS[embit_network]) elif script_type == SettingsConstants.TAPROOT: - # TODO: Not yet implemented! - raise Exception("Taproot verification not yet implemented!") + return embit.script.p2tr(pubkey).address(network=NETWORKS[embit_network]) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 14828aa1..736b95a7 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -567,7 +567,7 @@ class SeedExportXpubCustomDerivationView(View): if self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER: from .tools_views import ToolsAddressExplorerAddressTypeView - return Destination(ToolsAddressExplorerAddressTypeView, view_args=dict(seed_num=self.seed_num, script_type=self.script_type, custom_derivation=self.custom_derivation_path)) + return Destination(ToolsAddressExplorerAddressTypeView, view_args=dict(seed_num=self.seed_num, script_type=self.script_type, custom_derivation=custom_derivation)) return Destination( SeedExportXpubCoordinatorView, diff --git a/src/seedsigner/views/tools_views.py b/src/seedsigner/views/tools_views.py index a7c8fe78..da8926e1 100644 --- a/src/seedsigner/views/tools_views.py +++ b/src/seedsigner/views/tools_views.py @@ -11,7 +11,7 @@ from seedsigner.gui.screens.screen import LoadingScreenThread, QRDisplayScreen from seedsigner.hardware.camera import Camera from seedsigner.gui.components import FontAwesomeIconConstants, GUIConstants, SeedSignerCustomIconConstants from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen) -from seedsigner.gui.screens.tools_screens import ToolsCalcFinalWordDoneScreen, ToolsCalcFinalWordFinalizePromptScreen, ToolsCalcFinalWordScreen, ToolsCoinFlipEntryScreen, ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsCalcFinalWordShowFinalWordScreen, ToolsAddressExplorerAddressTypeScreen +from seedsigner.gui.screens.tools_screens import ToolsCalcFinalWordDoneScreen, ToolsCalcFinalWordFinalizePromptScreen, ToolsCalcFinalWordScreen, ToolsCoinFlipEntryScreen, ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen, ToolsImageEntropyLivePreviewScreen, ToolsAddressExplorerAddressTypeScreen from seedsigner.helpers import embit_utils, mnemonic_generation from seedsigner.models.encode_qr import EncodeQR from seedsigner.models.qr_type import QRType @@ -532,7 +532,6 @@ class ToolsAddressExplorerAddressTypeView(View): def run(self): - # embit_utils.get_single_sig_address(xpub=xpub, script_type=self.script_type, index=0, is_change=True, embit_network=embit_network) data = self.controller.address_explorer_data RECEIVE = "Receive Addresses" @@ -547,9 +546,10 @@ class ToolsAddressExplorerAddressTypeView(View): selected_menu_num = ToolsAddressExplorerAddressTypeScreen( button_data=button_data, - fingerprint=self.seed.get_fingerprint() if self.seed_num else None, + fingerprint=self.seed.get_fingerprint() if self.seed_num is not None else None, wallet_descriptor_display_name=wallet_descriptor_display_name, script_type=script_type, + custom_derivation_path=self.custom_derivation, ).display() if selected_menu_num == RET_CODE__BACK_BUTTON: @@ -593,15 +593,15 @@ class ToolsAddressExplorerAddressListView(View): if "xpub" in data: # Single sig explore from seed - if "script_type" in data: + if "script_type" in data and data["script_type"] != SettingsConstants.CUSTOM_DERIVATION: # Standard derivation path for i in range(self.start_index, self.start_index + addrs_per_screen): address = embit_utils.get_single_sig_address(xpub=data["xpub"], script_type=data["script_type"], index=i, is_change=self.is_change, embit_network=data["embit_network"]) addresses.append(address) data[addr_storage_key].append(address) else: - # Custom derivation path - raise Exception("Not yet implemented") + # TODO: Custom derivation path + raise Exception("Custom Derivation address explorer not yet implemented") elif "wallet_descriptor" in data: descriptor: Descriptor = data["wallet_descriptor"] From cfa96a08d33f59795cf052b09f6e426ab812a8bc Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 27 Jun 2022 16:39:27 -0700 Subject: [PATCH 26/39] Update requirements.txt `embit` v0.5.0 merged the compiled secp256k1 binary for Pi Zero architecture (armv6l) so we no longer need to point to our fork. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5a58303a..81a74c78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -embit @ git+https://github.com/seedsigner/embit.git@03cba13e0add64b241972c67e2aa970cb2e3a5d2 +embit==0.5.0 numpy==1.21.1 picamera==1.13 Pillow==8.2.0 From ee3bb475bf2790e7f5c2595c95ef3abce217c99b Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 27 Jun 2022 16:47:02 -0700 Subject: [PATCH 27/39] Removed loading spinner where no longer needed --- src/seedsigner/views/psbt_views.py | 12 ++--- src/seedsigner/views/seed_views.py | 77 +++++++++++++----------------- 2 files changed, 35 insertions(+), 54 deletions(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index b5150388..3ad69f02 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -472,15 +472,9 @@ class PSBTFinalizeView(View): if selected_menu_num == 0: # Sign PSBT - loading_screen = LoadingScreenThread(text="Signing PSBT...") - loading_screen.start() - - try: - sig_cnt = PSBTParser.sig_count(psbt) - psbt.sign_with(psbt_parser.root) - trimmed_psbt = PSBTParser.trim(psbt) - finally: - loading_screen.stop() + sig_cnt = PSBTParser.sig_count(psbt) + psbt.sign_with(psbt_parser.root) + trimmed_psbt = PSBTParser.trim(psbt) if sig_cnt == PSBTParser.sig_count(trimmed_psbt): # Signing failed / didn't do anything diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 13a26f81..5acafd9e 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -643,53 +643,42 @@ class SeedExportXpubDetailsView(View): def run(self): - # The calc_derivation takes a few moments. Run the loading screen while we wait. - self.loading_screen = LoadingScreenThread(text="Generating xpub...") - self.loading_screen.start() + if self.script_type == SettingsConstants.CUSTOM_DERIVATION: + derivation_path = self.custom_derivation + else: + derivation_path = PSBTParser.calc_derivation( + network=self.settings.get_value(SettingsConstants.SETTING__NETWORK), + wallet_type=self.sig_type, + script_type=self.script_type + ) - try: - if self.script_type == SettingsConstants.CUSTOM_DERIVATION: - derivation_path = self.custom_derivation - else: - derivation_path = PSBTParser.calc_derivation( - network=self.settings.get_value(SettingsConstants.SETTING__NETWORK), - wallet_type=self.sig_type, - script_type=self.script_type - ) + if self.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__ENABLED: + embit_network = NETWORKS[SettingsConstants.map_network_to_embit(self.settings.get_value(SettingsConstants.SETTING__NETWORK))] + version = embit.bip32.detect_version( + derivation_path, + default="xpub", + network=embit_network + ) - if self.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__ENABLED: - embit_network = NETWORKS[SettingsConstants.map_network_to_embit(self.settings.get_value(SettingsConstants.SETTING__NETWORK))] - version = embit.bip32.detect_version( - derivation_path, - default="xpub", - network=embit_network - ) + root = embit.bip32.HDKey.from_seed( + self.seed.seed_bytes, + version=embit_network["xprv"] + ) - root = embit.bip32.HDKey.from_seed( - self.seed.seed_bytes, - version=embit_network["xprv"] - ) + fingerprint = hexlify(root.child(0).fingerprint).decode('utf-8') + xprv = root.derive(derivation_path) + xpub = xprv.to_public() + xpub_base58 = xpub.to_string(version=version) - fingerprint = hexlify(root.child(0).fingerprint).decode('utf-8') - xprv = root.derive(derivation_path) - xpub = xprv.to_public() - xpub_base58 = xpub.to_string(version=version) + selected_menu_num = seed_screens.SeedExportXpubDetailsScreen( + fingerprint=fingerprint, + has_passphrase=self.seed.passphrase is not None, + derivation_path=derivation_path, + xpub=xpub_base58, + ).display() - screen = seed_screens.SeedExportXpubDetailsScreen( - fingerprint=fingerprint, - has_passphrase=self.seed.passphrase is not None, - derivation_path=derivation_path, - xpub=xpub_base58, - ) - - self.loading_screen.stop() - selected_menu_num = screen.display() - - else: - selected_menu_num = 0 - - finally: - self.loading_screen.stop() + else: + selected_menu_num = 0 if selected_menu_num == 0: @@ -697,8 +686,6 @@ class SeedExportXpubDetailsView(View): SeedExportXpubQRDisplayView, { "seed_num": self.seed_num, - "sig_type": self.sig_type, - "script_type": self.script_type, "coordinator": self.coordinator, "derivation_path": derivation_path, } @@ -710,7 +697,7 @@ class SeedExportXpubDetailsView(View): class SeedExportXpubQRDisplayView(View): - def __init__(self, seed_num: int, sig_type: str, script_type: str, coordinator: str, derivation_path: str): + def __init__(self, seed_num: int, coordinator: str, derivation_path: str): super().__init__() self.seed = self.controller.get_seed(seed_num) From 9402ffe6f242e471e73f49cf2344d192747a26dc Mon Sep 17 00:00:00 2001 From: SeedSigner <65379725+SeedSigner@users.noreply.github.com> Date: Wed, 3 Aug 2022 09:11:01 -0500 Subject: [PATCH 28/39] Update README.md --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4469cb4..a0b7a037 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,19 @@ Notes: --------------- # Software Installation + +## Special Note on Minimizing Trust +As is the nature of pre-packaged software downloads, downloading and using the prepared SeedSigner release images means implicitly placing trust in the individual preparing those images; in our project the release images are prepared and signed by the eponymous creator of the project, SeedSigner "the person". That individual is additionally the only person in possession of the PGP keys that are used to sign the release images. + +However, one of the many advantages of the open source software model is that the need for this kind of trust can be negated by our users' ability to (1) review the project's source code and (2) assemble the operating image necessary to use the software themselves. From our project's inception, instructions to build a SeedSigner operating image (using precisely the same process that is used to create the prepared release images) have been made availabile. We have put a lot of thought and work into making these instructions easy to understand and follow, even for less technical users. These instructions can be found [here](docs/manual_installation.md). + +## Downloading the Software + The quickest and easiest way to install the software is to download the most recent "seedsigner_X_X_X.zip" file in the [software releases](https://github.com/SeedSigner/seedsigner/releases) section of this repository. -After downloading the .zip file, extract the seedsigner .img file, and write it to a MicroSD card (at least 4GB in size or larger). Then install the MicroSD in the assembled hardware and off you go. If your goal is a more trustless installation, you can follow the [manual installation instructions](docs/manual_installation.md). +After downloading the .zip file, extract the seedsigner .img file, and write it to a MicroSD card (at least 4GB in size or larger). Then install the MicroSD in the assembled hardware and off you go. -## Verifying Your Software +## Verifying the Software You can verify the data integrity and authenticity of the latest release with as little as three commands. This process assumes that you know [how to navigate on a terminal](https://terminalcheatsheet.com/guides/navigate-terminal) and have navigated to the folder where you have these four relevant files present: (This will most likely be your Downloads folder.) * seedsigner_pubkey.gpg (from the main folder of this repo) From 5a08f48720ce4e5d995f8d77cf90ca5d4f8578fe Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Sat, 6 Aug 2022 18:21:22 -0400 Subject: [PATCH 29/39] Legacy RPi functionality + documentation --- .gitignore | 1 + README.md | 2 +- docs/img/legacy_hardware_remapped_pins.jpg | Bin 0 -> 145140 bytes .../legacy_hardware_remapped_pins_photo.jpg | Bin 0 -> 148993 bytes docs/legacy_hardware.md | 22 +++++++ src/seedsigner/hardware/buttons.py | 57 ++++++++++++------ 6 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 docs/img/legacy_hardware_remapped_pins.jpg create mode 100644 docs/img/legacy_hardware_remapped_pins_photo.jpg create mode 100644 docs/legacy_hardware.md diff --git a/.gitignore b/.gitignore index 6ae5b379..47a850c2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ src/seedsigner.egg-info/ .nova .vscode src/seedsigner/models/settings_definition.json +.idea diff --git a/README.md b/README.md index b4469cb4..3754dd76 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ If you have specific questions about the project, our [Telegram Group](https://t To build a SeedSigner, you will need: -* Raspberry Pi Zero (preferably version 1.3 with no WiFi/Bluetooth capability, but any Raspberry Pi 2/3/4 or Zero model will work) +* Raspberry Pi Zero (preferably version 1.3 with no WiFi/Bluetooth capability, but any Raspberry Pi 2/3/4 or Zero model will work, Raspberry Pi 1 devices will require a hardware modification to the Waveshare LCD Hat, as per the [instructions here](./docs/legacy_hardware.md)) * Waveshare 1.3" 240x240 pxl LCD (correct pixel count is important, more info at https://www.waveshare.com/wiki/1.3inch_LCD_HAT) * Pi Zero-compatible camera (tested to work with the Aokin / AuviPal 5MP 1080p with OV5647 Sensor) diff --git a/docs/img/legacy_hardware_remapped_pins.jpg b/docs/img/legacy_hardware_remapped_pins.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f6af8b47122fa1e48d63b9fd53c3bef20db64d61 GIT binary patch literal 145140 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%0Dk&SgO<-d$`udeNk0V{0D&% z8UEohjy>;hF9OegGmSfW^S3uiV9Mafkx@&Kvg`ln?{rJjTJm}6Yb&io^_@xO2Q%2F zBlV2-!Xt4GmbRq(Ws&@mtX!=}kr6b-c1ZNx_3C`F`|9sXwb5%lm3w(dc9Tb~5Cy4J9f^0-=JQ>#iGnG-_zHoZuJ3vFaX$ks|lW2?XIbLPV4tn^Y7cwh| zoLyY7o(GU-9kxtsS}L$c!y!npf&D?cwOCoYgYtMvqKCOrNYBEnnX6y z6FC^+BV9xQPk|-6N`bt{-6ctjiNxUcx1o`n+`&&|P6p{BeXJY?6@H%8)zw>kXN_h;6E^YW5Z9hNux~o0ph-!oA0;y9xn5T(eH084~fhg zDYH-&@J=|Z3_#Gu(%bb?@?rA=A-ycOzatmx{p~+hs}p9Xt>}f(?~06E7-{KzEZBDX z3AKO0HOTm#sOQYD4vK1?!I79z0R@_c>uh;*D@%%YXi@7P?590BuLW{ zATMX_pZbj^>DkQf%y-=BosxRdG z0v1QzOP;aM`Cm|oRzW}KO=T@F>}L8QEknwE%DPxsL#*I>&&?bE4HY@Kt>IzQ zUjt>Tuk0D!-$nQZNlXrEhswEv16c*2QNQ6zjj!M)2Lj(wb0lc>+N!G7)`T{sz>;s` z6m5_)^{13R9R%Uv%U(d=puVe7hU;{P;5EGaY#Pu!U0Yuxt#_D)nB%GW2R?J20qFsJ zSbW~oG2Lc2jonl;F9#6nvu6)}B)scYe_>eQHw_2UZl)?KU2^)gb*^{owa&;WXU&I^ z54Dm2CA2G+{s&22z*-m*ASYH%UDN11_AK(bz|M*TR|I3o*GM6F3=D`o@-q!a$3qH8 z#`842zi%5HghvM`E~*F0>mFPM`DI7R3|!l@yOwhJ!`SwbVL~T4g`1frCsIbo-3OUX z{0+`^sAK8hs;OdJ|7ey&Nh_^x?550>1l6A!V$5*Bq8D?cG?et|*9>jYd2G7ePx=?eL$2-Gf34z5}gIc(YC*#MKJ0S2aXX*T% zx5Eqsnsg=8&sj3{hu*HP$08fuI#HYB$qxrxbq(t+&rVdF7t40k_4W%>1IxRwnT8@r zMXP>%EzW2pxT?CL?2AsHsfgf}W(gS6b&|eL74f&q$Xbghz=~>8!G$_Qj-`Iq@2_rv zRWKjta@!nl;ruUEF7Gcoelyhy7^o9%&dQ0)&ackPmK7Q(0t!|<5WcvY9L?3C@ zJ!%~sUHB?E`A1*hxedcVS{9-s)M{_fFk{`zlPj%C2A(&htjHf_G{1nJ!ueVxiLPhQ7&bQStwqtdx#>#oyNb>0$- zWnZB~XU9)>dgte$PScsWd0YZzC$J43r{v~82-0zC`2QDNlbuITy^Pk*&31HE1+~5qfSu%_j#w58{fW7CL5An$cYkyK01;qt^7-)x_s&Jwuv?24q=9(E^fa-NIGk zpR$lbkP(??jIUnS?Ca_Jf=hWVJ)$m_`v+mr1CHnWgRMQmBTfmbqTZCo8cl5PB6M>h zX^WnH=n;cGYM-R;pj$O~1R$t82Uh+VS1Si)oEg5FbapD;U@bq-nkptYrY3U;tPK^b zIMWXWBu<!bKGb)I3&lrIT9eBZAQ}Z6Kh+a>5WQF05$>fnKXeyM7_#da2 zzY3G-z1a7z5OfFtYQA4BLk3{kKSR(!G(BSYE%m@Crxed9=J|JJ)Mg7^j85e2zrm>f zfk5}6*eL?i`t|C7Hq$Sw3wFQTBI9y*{~+k+-}lD7127QBPmR+5197!S^}hhp{%6&( zI=j5My-^xYX5o=!E;azSl2x9c)G}l$PsFBa$_@ z(+X1x1!I8dSbYM9?ttKE#uv2#$xuy<2r9(QF99RBqtTYf6;owa=Jj6`wriVi&YO(9tmR{rd)4xc#-1Bf zeUp1aZ0(HY=7CXgwuK_9Fjug~>%POt9{Fnf%DDz0fk-r0rdFzxD&uWdDTubYCFWEA z&?hd;HE0;NT(TighSdQt{bsIi)p->TyEkdLKvq9}!FJa9J|1p$+lTZIm_sHBARFxl zyzUB>=s8<>;pWd8t|kr0O5cg!!=c|`{lnpDGrX$Z1TIhkKz1Mvf>_hBqf@DW5VUk6 zMh_^0E3sBMC`F{$WYHLZz6rkD!PfdHja;Jn`AV$T6R8QeV#}FLFMgSouR6ih`gUo@ zBzcY81Xm1P)LUol-Al_J=)JEt0|%ZtZ@l#3R!DjwRag=f*;G;GcA61N#Lh){r#Ji3 zsAN~Q6ngh&TQyup`i}HTS?&85o-~`6{c*U%X2_zxM$PUxHB-0E?5=B zabYC@MLNcnTThOdb*H1YY z7=3Pg{fPe_iJWDcENJfN@VNfuJV7n%u}UMoXP8F3#%Pxa%5x z&nPUW8xanYpOeJbEZp)L#A=jdUUeFUZwGO~T?@nTqhaJI=Lbi$xFo#)-Avhuf%(B8 zS{&=V*YMeB2AW8|ap4g1Sw32RWl@F{DB5~kT@X%qF87x&?rXMkZR)({vtMY=xlY{8 z1G!;!dbak$abpDx5q85ugzc&AS00Rq`hAnUaR{(&W6Yc;7rTnGfQl+8+R@`ap?*x#hg8e+| z1|97`%^ChlAo-I@vd=oBg;#X}hU3dL*zvPtHiyyxZot{wY;o_B&;(c_KU9kT2Qzt| z2UtotHid`Jscc}owD?cMjtJN)bQ$+>$9n*!GRQxq{zLu{2dFFkL2&AX2B%VBMV-x- z9kPACx3>w7W7V1plb6$u)9&e}AUl;~7nKqQT)ZFKz~2zY%DPuBR#Nzct2Q+?9LPu6 zAKXf0&*wb_9L<$gxt%EKvLDL|?OEnmQ880rSD&w~^ge>S9zObH=B=fucu5F}k*{BG zJd$Y^teTlQ_qk(t_WbgD|7*65N3?C=w~Y>RwyAcm6DJR+>ld(zv^Szz)c_6A3!M-^ zl=M;HgZAUxA9P}?XW7>dW}9|)i$Dh~(khM8&p=-k*&>LZ>Fz#fjpGJ@xTsQi{!AG; z_ZfK48PcnvKdu;xap~Xk+S2f#lS@tpDZ9V@@Vfb$ zOG?EWceH4hqS;7=$Bu+{h?ay-jqo6UN#?d4+s>5><5O^uz9Aqnuw&NQ zWN{ir8__n7Iv;afs?F{dHY$(*5H$4%!he?Ij$-R4WcR2g-f0m82968c}V6#=6dC8H;wiqrSepu z3@1_)8R^NTV=+85(|qax@$_~E)$bVHZf}+j=q1hc>~Z=ImNH%TDzFuPBO-MOk($#j z;qs(CV@iDr3@H%o_zrOLgrV`GkbiBoe!6_rM6nYifqyR!Im1DE8X%FT3_k0s76O9Q zpmdjP6WfGcL9`o#{V9@Q>CEiK{l_npBG6+J^xPH09l z8(F{wUE-|=iErOvt}d8?TDooXCLI{2mO0_s;5Nq6gBG$h^9TIZ+F>J)ZBMF9|L-D z6LL&h{bvt~2?z)b5{wfyOK$BrMzBO ze=uLTL2XBVX%`kB{0|i^2B}EiwcvtsMcJNIcjkf>q#l*DLxj#}a%rOLkVoK;IB2ap z4vf-Ff|Si`_y>y2`X>FF`CKLP?wo*tIn?=D7EiNNI@T5cGWhC_pnSL@@s_{noDcchQ04>-?K*)Jurl;EDZ6UAE^BSPWcxL|}xi{-Y z8&!SEKC|+Im13`&zAeCkaDnT2K-Frsg|KvUALEffflO#PL?44y`k0b=@9oXk86bgN3R6`>kHpyo&X|C8u!=L z#w%(r%T)Dma!8OSY$y=1o!v+(+PNDORipz?%(N_CKaPWX4lfv)@e>f~b> z1S6GZx)AQ-Dac}s$&zu%RTR%oR06vorAKw@DKc?iXkJU`yz5mk^n_9LF2xB@&|uoe z(CRAWNE+h0gcm9tfROxK*$|S@uo4#&-tyD)44hZGTjsqzB@>Hbe8&*Dj7%H{z)_pw-rKF}V(;G^o|{(Be`~KK@1s&K9Enl<}SSLfYyS z^Dm-_31x*gG^EhoQ54Z^`^Qqs1>SEC_zum@(r$@wXo}poz16lLD6=y}&VU?f6jZOy z$BQ&+_?@ZN?Z?8r-5kp}T69gWInI7lu{a}n<0Wy8u$RpMN70q&yhV!`|-Y?|24QD z7g=z?_h?oHwezZ@$ERhoqGEo+Cbsj=uicwx+L9%naa8RuOCa`BlM7qZ>0gp}UhCY9 zAb(>qmrN@udHtQFB$ce_b%dxEl~T#p7$|WwJsS$4x{6wAiO|2!U-NSOv+??6KEjVE z(iWLB&caVh`OPK-?N246h!P|ZFdr4w&@!%r#Xd+ee9!S92JWlfUYBxhO=u*+ zd>KEKeeLxtS&cVu-8?5|3jFDSIZGRo|Lr(^)o61;m-)fK{7Zm>u?o1|qDml(K>SPs zI7I)(Udf0FVoBvuI=IHD-8R(aZ&O|%6TTh>)S2EnV0pO>G5q_f%30P(F`<_t6(_N1 zgXf=93!l7ZdgEjn7!kV@*!`!C_x*)<5afCr<<^m}lwqM@L64W2QweX7diwWLEHUO~ z{*&)c6L%*&dL0wjb?)%UeSWQjYmKPgE#Hv%kO=+m=DmZ+hM)RlJbhhKX`s&8H*e@Z zuk;2?-Ji6VYV50^)V82gsDhK!)~fmwP!b9W>(rB&%y!v>03bG zejfqb=K}szaH0q9~NJpuGLi2%T{{?~7yR?)`XU4-3oVxTE1R0upxt2MJ|v+D;oNMq?B z>MRqnoAT_NN@^gH*$0(r4Z@6m59}K}SbR(J2I|d_kBXlfys< zRU;92`ir1q-6=nObonVDp+>`iYz&wu2-i+TO+ToJZM>zx{|U-ZO_E4tD6>)}_rnO6 z;_7MA#yB-Sw@K<;sJNB^MtM}Y(EYj$Pr0E7TIS50P~n%ixes`A$U+4_S9mQfR6aDbT~}REVwR;cg#AZPUqrrj;i+~~P{s0704eF=GR4e$( zkK)9aweM5}3Qtnc{e32Ie@IN_h=RG~Z9^P@adi<|YCb78H{Oh!df7G~bIcK9V2=St z9hs`==3cg}!a?2mi5O*_-%ASl0*UOUr0?Ke%1-fV)y+m=>i!oqUF*`Fb}q2%Py6kE znduMya{|qzwV`(@?0{17e=)K0{`|7`?OZ>KU@UXK537=t;X9_P>6$@l-)h=dD+HQa z)v?*hV>{Gq`XvwHp4`oXcD9U4j7oq;mw2RjB%pp(qf?_}OG}m;lM6^#Ia4`P0g_pc zd5(ZSmp+mJ-`55tS1EwxL3-~#+faf8&RhP11W9StY8h67-$_sZ(wiIy*)%c>u@J%N zB+jb+iC#(dU*f0i)Q;`AG=`T~=JyQvRwQt~KJG|LGIc>Sh%{eUqUX~jV5!ypkozhB zYj!)b$19-{#L)lA@uEER{Ns`W_)X&W#SS-h!86oD*A%B;ceSi@^Zy1mi6r3t0dbZ; z=W4RTg}nkwjdw@hWtZ$j@v5#Nux&En&gD|Jz)!9IAo!e$pM#y2yCDH0!v7|rHM6Z~ z(Sp^dAI%Px9QbM`~^C zp5HbR-T^}kZ<~%M%}Z2)6jssJ+lAYAbyK^7?nXUJnJ1m%Y&6J5segUa@%B<>o}+r@ zs$Y64>KA0n$`!zU8+c9{o6lLe3gW)7vYETc9O{=?EzZ0<+^LY<-NQjT_YRMk>Z~SG zPtYrNXk^>Hri0-Ky-umn(9B-t+H2-hEx)!+*Ijx{@`&AM!*(`@!TFZNV*@W*GrYdm zfkcbnsxtA4`>|%oZp04C&M%ycyyV|)as`q|lPfKO1)5?zqMNB_roP@=8p<`oVXF&7qBAWNbp+8<4dTxogxsaUy>kWGWxIU4vs) zOZQoi1xu$k)9NXAD24oTTt1A|mU!q*@Put5Ut&&PjkUc9dUAp7+<^cW-?xgV1^5*s z5cIf{AR!>7RINy#cj(PyyR%O9GB6G7yqX1@T4 zA&8WY>6{njLRW$R>t!X5tNN_7RaT7Rs%-W*_NKXFOZt{t$I&@g&c~z;@e;R|-7eDG zV2AoEK*0gX^=+Kym}QF}{XsBhEFWfsZ(VWSJf)eoi#GHUA}-Ip#(q^(_^sa&B0W22 zR~IPs0ZtNy9Lg9g>829f=HL>J4y1v4cU;^CIW9-yURRjAMQGTqhd>ph$>Qy7Iwg#Raf>n;#Guz4FwYk(O|8n zvbt?bj~_WAk6OU~v<>R{xm+#z(QyfXk;7KPG3$pWswRVl*w-}?Dy)6F&iwS+Fc!v_nE6}7IF-Ec@sP^g`py&anQk`fK8Od(vb<*_cara<##4aMurSF|EcSfZc`uCk>+r1&28S?FW6><~neC>4 z>(Oq$(U;yrHUpG?N%IA&`AbcB=>D=_`X~K&q5DU8CFo@kkPaCu0T)yj7VN8x@Iojp zzdk6x^&VJ}hair|V_F~lP#%U4*zGtShz2#4Y(C-{rS@6(0R6r$KtCB%P~w=r-q}j} zVuUMP=Rh~gwW`~Q#H&LIlT=(KM z5iROp&B2LV_2Rq^tdXuV$5(x?xfmgS-`()-WXRO%*e!ZoIs;zLgK@Im$D&N~=U(9q3a8FgN zMqaq_*SIa#PGrD-+1c^dyECPdxQFE}1+;GCcL4nx=M_f(xk}6q-snj7W{tYa=n|YW z@q8zbzy&Er6Zi*#IUuF_yJ$gySGWuLN6lhc@Xwls6YOvR5Up}+JMkDsDXY6Z z_!>zyWpR@w*MBzvISeRjfhtnC&n6p7d;50~BR zaHZIse{{qZZ<=SJBocT+|1i-nSbT-00q{AI`&8wgMcTi^HL#ioA9YlZYe}D5IF^Kw zR)(Bd#TwQJ5jKtO*uam^bx)=Fs|uDSNQy(Qbl}#kh0Y)pZpFR+`Y48pH68TUG**<# zc~HO#{fx@!=0C7g|6d`dh96-<56n6)?pMq+w@jpTtW>z{azv_{UYGyTOkH)41?J&C zL9l`0U7~$|!Yr^WPiFBh&+!zs-r&eWXKnfBX8cvvc3BEdO)a_RAFpVb^;gd`+m5h63_F<-#s0EUNoS7{hl_h4&jRomnwhijeBR|ji*muR3S846<`{vZPcu*Boh$f(jShUm91jzWCyU8kRHULJ^%RS)68Y}jcsw%9UO z$<|V9N1#d?PT$M6oWtB}y42!-Ako=GQ-TZiF6WAY3%GDU&(?D(`bJ!S*&*fd4sm*8 zYe;BJ%Uwo*rrb0$A!d5c57I8+BhqV0f#7rD$4Yo+iKy2facAZD&?VGy@{^zBH~Uu>?%_bgZy?u6)DWNiuhFI zIIpf!iU#z!j%2l-jP{%i?CV?XZn2 zZylAePZBHs8YaBtSdQ|o;C!B|&^R~wu=s@Y_$!PIlcaJW*81z>I#y!e?3eRO3Z)tm z0VSb_3-OAr!GpRCiz}Or{yJ?naK3^Tgv-m?now_5(V;uH{Stks6+7EF;W8>r)f!>h>P|EVviulEjpnXzer@Hn4~D_iX8wlZ72 z!&OzGhm;0>@dPVKy70?7uhoOM-FnqqFp;W#^75BoebirMsJFb|lH6I2_@&8c^TE8# zCXYgK&>H&$lk^2+c`AGV$S`sUZ#R4Bt0gg&blGj7^$YTWMLJswZJGaScYbB015suG z*BN(dl`kXTXN_s$D0LZY;c}B*GJ864SF%3dc|GOr7O6C)7Cq-$HCA;AdIPi9VHz6K zXrX)fJX`X0XmenJeLHC=2X9DRMzD0Ip#hVVVzQ@ckn(=#D;~qs#L@1;lj(~sueO_T z7Y|or$7$!|Y`gLD{5LbjVL1&ojW4+g_ml`8Z1D~q7mn3xeYCtE zd#Madc3lh`wQnAEp`q8I#&!v}ycAz&)?|#fPgmU_o6$4tI2&^WvStd19K@=Iv`0!R z`;c!6*!+gU5%qHrEtmMZ?=(3%IBUP6IlntMpP?stNk~+xV0`;wfNU{yK?SGJ!fNMS4)I`S^5u8KmSm!}k!ZWK8EiYu z%My0!ea<{oS)!0X#aLFnyYlgNfwMDLV-RFA!lY_1atY6h26Amqh#k(V3G0+2{f6wj zZn^W%Lg`kGo4csplXSsT0p?ZQci7Va8SB^ZD- z-T6oI1TTCnX4Pq5SoSwku@U2*(F;74Z9QdV-EvsSmWVO>u2mp-|MrHZ|Rbs%GAodgQi7i6AZszxGN*U`eE@v^!uDj&am65 zPenTNDPy9<4#Bq}W1!E)85KL#>pO}S7wRv|_DF;$E&8(<1G-#P#Jms6llvGUyl+Xe4ACw1 z7g>(A{KOEi0u0f*`>7_XXnJi%1Oe?rIve@bVdg!ml?8WzUdz~MLoAdcasWctg*SjQ zBsU@K7unAdm9|O#PA?RANBjki-LXI~u|0WlgQQf`<`-20MKnJFEwux6fB;EFDqc~K z?~#-F@{ZO49sa&L0Vva&hmhK${`5O?&HtVs1W?$y#k~XMhNBV3Y=8b;xq6=4XonMM zKsr(O_tll$*bl$R2BJu+zsS}e9&H_{te$`a|Mlnkdzj6Tp@tR`Bj^Yy2l$fpx1Xf~h*tt^hD3Ym>7Njgec{y$ z{UxSU+GZ>C>0UBPDIU~Pqp86)muXzs_sD~RQ~ioKr<4*$nt)_uHhV@2~q!R zP5kraS!<|Cx+1R-!ZpP1@Aq>wh-e1Vbh@BJmS1Eg>LZ9cZt%`Ud>3f@AFodjf8S3S zu`L4(B=r%+z||?cR7CS3aQMvh1iV>xF$7g^Y?BLOITi3rAp+l`hv*R_xDSbpA!A;E zrCBoONWlLZKx6=Vy;UDDU0XaLy~O~=EdvPLC4{v)32{#;@7=$@qx!xiVrf_9|NZ;X zYvV|Vd;k-Sa;fz-eTowdlhBj4i^1EoV~(y(0ZHKBtu{&xZ4p*HycE|+6%NQHvYi8- zpc-&t`TYbfw}GuxzcgUR|PYfCO5Qgo6@es%J;V6#!b-xl+U zOTwSOAz0Gerj4XuO`=T|c;&0|xZDxYRm-%?NIr4&RqrM18*5{xy}FZfgRud?;j(p!6>TbOVD#veu4Rn`Ha_uP&3W4=EbW ztVqI~JNlU_!oqyDu7-0b$gI^TD5+^KdmClI$y=)|MX{0TB@WS-uhgo%H-6kY{qDQ} zYkDL83uuGL)ec~)PB)us5xMscIcb=F+`K{`a0+>ynEr?#88Jjgsi*@d<80c1>CL3) ztp7aLhwSx)5-V=o4>W4yCd8xb=j-wixs{XGbv zo+X<*(?+c7K%EqR#w*7m%08X0LHjxL>H?wlbyTBbMBsLs+B%naplLd`PX@KC(*TS- z|6~B9LFVVNs|rOMm=8Els{GroENwmU%>V{=GCb>#fekoO{JGz*fBO3ubVKI$b|b$K zZb1Lw*l*js3Jcf1fCSyY6Q{&?)_N z4uEgcU*?|r-zVJfsd(}KgB#vczJjlv^IDf&!%)H*AJG4Js+rk1uoOR7V1Ch>cR@}` z{ehnlBZ+zI8|LU@2#FGqTfx^I-A9F#JHCf^F#;a!bxQ%OM^bLALj?#Ac?yCECJeQE zm5y-90;q+2foPt@*8@s+0O0af6$8Mx7U`=a0P@iXA?eX=^Nxc_kgp;C=^FK;$`*j& z4FtAWz}o5mx@+ph+JMzUCZa-wu2q?9U>CDNRddxnsRwSeJ^${i4K0wN;h>LJnek0{ zmiVbvPV?G&4n_I;J0HjDA#PRZ8N^cv;t$@bHU8q9Mlz{Bop=_yeF&ODs-U$GoCb$z zeT#hna#GzSG#5cBZua)Kl**s9%TK7Ma$=_t|CAQ(`b7p4Rc2m+&yfUf0mbU2jKi`n zGg(>~b|t+Tbr6*=XZq29W23(J0)*WX6@mOYAY)n#MKnAFO3wQJRC3l^aO${836W#@ zWw-Ru_xfQDV5UYzf}l3iN!=FC6gkglfY2FHH3%5lJLOa?($^6|N2P&zwyWE z(8o;Cz&U+RyE@{55#Dk#)1>AE`|dpe_N7NPz5DzKo@f6t``&}2&EQJRnwZq4nOH4) z#hFu)$Z$)MjQ*7M;bTvvA~AVvc3`G5A*#TkE;lvbL^9WS%gN^D#qIhVVE0osN^f}^ zU3C0cz4stNfIQB_G3DHDWR%My3N?WF>dzN>GzE@i06bYY$z+WfV=j^p&f~Vsxak_? zeZ^+rl80>4H4c2WDeVV}qa5{T0nb+EFZ-w8s@|1Xn8FNDpu}unya1D5X{yr}k}I_} zNt2DM7rxn0dYiO*MY#RvX=n%Ol)1TzKn*GDNvx{5ea9%`6s#mhg-qRX z2=6j;oKG;B#V}(`yPmjrSef~l1oLS;(aD%VP~GYnAEdTR6Vi3>>P3`i?SP3avp1Ya zJP{*1O0=Q#*Z;HF#dkoU8wibok{8^QVLUWyozz}4NNR>_#@ELbtEk1j=KW~w41A*( z%bM6?O2CrimAjiyR{;kRZaN2Pw)VG*APzREk;J;iHvB{dd)uQrNqg7bZYO-~w?r_^ z#bu_1VXl>B;I61|jjge@M%1KjbPfBbXL17P-f9+QSL$L4P(xQHYeTN9vBh zH*w<^xtXgn9F)`Da-eK(uj|Wd;n$59n8VgLx9H+l9P@I5@o>hV1ro2?VXS+YoNacnbK>zAVW*Uy- zX@6!rG$F*?BTeu8T-g#|f}q7uK;W4O8mPQeoo+3MrT0DbTkAB?mfAEm@jL}GqxG&c zxDG?&*enTrEf>e{c_|q^rT{D2d}eoYt+hmh&X3k~^O+uh9J9eombB+FbZ}et(XNu@wyrask zAq`n`$G(%Ho3K;dZMP(@shHzzYhiO|`zk=RsZ?CLQ1x>jxXJXE| z8suGzt=b>~%Nehi)eSYUo5w=q#?-LcA#urp-i?jb5PbLC0(j!o=BY2e^Fr~s-cWa% z3negLrH|UPG^Wqc>>8WQbfQet-+R8i>L8;f9I>rt={(BMoJ>UU_O3j~1i1UTnGDzf z^yd)NCsAgDw@xn13m={-KQX+`5p%0o3)>rk+WzfkR)ZiVXhJkwp1r=c$9usI5Z0(x zNh@-`Pzlq^bpIb$Jox&eLK|s^{_Bzl#p+hbC3g#Pg$QjutXf_^-gY!9xuB$3&E(Eu zw#FObj*-P_`d(dVpRZi;C_GH1d}z0pI>9LV!jmUApZU=L4E%b)G-`If0By)Cv}Rkc zw6Uls)7V9{cS}?TRp`A<;G(Un@>)t`eVR6}%y3S1g7NZU@)^76@W|RybXkci`hm-a z5J|*8u&KlYC}j;|dh5s`*};{kaHQL@E!(I zERgDo7tu`>j7u9i)K&xJZEAM7gGkpBTe#S8bisS&Xh-GC9+6BWp*QbI^J0VVvWN>v zZER%XwdjxI;}f5JMnseb1M!P)wY>v;uh!3+^Y_GIY<`hZ;O;~Oh6OMG~)% zW)@HX@m~H-mFzzmEB`1pr2bDX%a&fpcMu&sBfrQdfv#5P|0{$lWQJ{j+u%A~!h5d< zCUCz$?K;;iJTJy;UiHCkN^3()Oe++uF^z|NhNj08D@h!4E^6J@`D@AeKMs=*^UU1Y4+_=aEw3Zz$pkrW6;yz{(Y0WZ^cf zlhy9(>RS%BQq=(9$_LfjPy z9-0JCmVJ+k;b0n`DY_{$MTE3oa!SH6frkX;*qe?76S~iQ|6H_qB$UwI+2fCk+vA%<>fXE+e*WWuUe8oY z4?vqhkt1344(L-9fT4(|tnNa57#m5$AJ5o+n4NsEcB8A8aK& zM)y|AUg^3tkFj?eT9A|%gLdqFq8C^B@0W~Py zdprHFaZ=}<8vPGPAEgw&k*tVcWDk>a28E9l{Ce8^<^P8@u1%tlZBYzBpi<1>ok457)?w}^S{{ITyY(}^jz;Y1-iB?ESWBdbgHl7{2sdyH201j z%+*Km+eKgJ7+c}nW8!A4^Y0qhs$$%)6u5K?9j+jD5YN(9_Vspw)g?ID44d!)7x+~` z562uU6;v5CJ7LMb{GJsOgO+u2@!;DTOcTNYDD6D>{qiIIJi%zkm0i+q1wo%4nq<09 zc&Bga_31CnM5&xDiW^G2CJyA^z1lHXDMb*Sh;Ke)t zqLuL*j`;(V>BVRG>hFiKDgvExFQ;jUHe*o~*Ct&@9ypR;kSH@Mm9u_`^6~MyxZ+JS zF~w_vfcQK_Fx6I81&p{xo_Ru*+8yvigJYWP+^xaEVek~cg^`6%{f5#yKNT^Nt=03c zN6KNUxJ2XJRl_e&V;g7$kg^|a1AZ_?TwmeMy%JS8)@j7L3PwwSy(%Oj42 z{JyH1{86oFBsGV`@1a5KLeSrJuc#0}6CTv6w!yS>`{A%aebsx9RRpB=N zClnr(t^R7YsMl$NbvWe9U7|N>iX9r37k4?MXNZdT`s#DtOLiD^ML1*L4R`QKIZ#5v z&dse-lJL|ji<>>zpSA+sFw9&~n4DfAY5lT?SC(}x^86X{ZYY6&q;iV^{=`D1wJ2A3 zant@WOpebuJ5qYI!G$lSyo!#)I&`Ql>T?}Ryc!BQs?j}&HdY4i^ z)(_NuVL5h($bCqY-a1x=oQp7PNn`hu?yq=b6|=cWVo9mjq7uw@T=_ztATl_|g!rO- ztrmBBjkzwq;lZk@>YDL{2z z8TbhJDFOlI{GGxvd*+0sd9ePZS=*<@9*=lV!`qWPJn;f)az`Udb47IQtMc)6_!n@O zV?hoD)dEd93D5%*eb<-X)bI2JKCu{f>f$(A?n;>z_**7ac5$&+;BtoM*XBI2b1M;4 zSE>fAqhA-hl0J8Hd$LZu_e$x_QnZxFm|^|1^6X7@!)yw43$Lb(3tOFktoNf(W11`A zbE>kv$tF~fk&_d^Rx9!2gZs87*nShC&A9pG zgMcgI%JlqhJAOn{QdxL#kNx~sNrf4wpxojgT`9H3cO|U38r(8m79uQps zo%+zqo7;|>^*OMF75f9Z8byh@OZWnTFjE@x^=(rsMn|#S3Em0XjVWEKr!U$Re1;Hg zc)C{P+!}%bPpQMJRdbeoI=6T3LL1({pdEs@7|C7`V;_hdNAFX9_Wtst=12 z>VI)a=+*2?2m2`@s%OfA@f5VC_CLT5;peO{%zf zyXv{N_9g(}TL^OXq~^H{L6cOMNcarM&Rdig_+I~ZVxD-ToSD8XTu7O=ubnJyR92Rd z&$>XBOR$JCt{()zpVt@W-^Yjd960xV8AUpWKYxF(teg64c+X#F>_cOUtJCn0?Lh~?Y7*!I2MbrDBT)-L`~q@P#5B5@kUBIPdI=35I& zObxXo+}L`9*fo|y9PbDKGCN2GCd*WZorfWU>*i0;Lm_|r7_f6cXayY!v!RHGu|QFX zkyJZSClRECqa%f#Ljq6#6lfp-tfk>YfCmgT0v=Etfm=N(fO&`kQrgR(9Dk7=Eb5Vz z44swP-~*~dlI)X4v$lDKW4T4px-kvobHr)VZa7G*r^q}?H#lx~0)79WPH2&m2S|!X znV=?I7A)Ky-B8^&gDnXxBu2;Ko6x%yzP?1Z(%%i3QC&eIbnE7sb zpn$+jZ~ergkc$N1rDbUcfQMX30YPGy_#mfzkH{K3w?Vn|4MZ;{fcNnoQxB8Weq=UI z{27Y4$;ers3*h@P?Ln6>E5cU%&qE(z@csw)6E$JHX6VxU@~K6)o)5_Uu$j^nL&e5u z9`K&4^6p#k$xiP;;cpNKL7=yw#GY}I{jN8ip4Qy;!?hL&CBZ%4AIa z-A~e|OyvH2|K*Jzz0s7-jb`E*9COM4mk&WRdcG>ai27OtyuuG4{GP@4 zPyhK#EPzMmUk6J85B|>FE?;_BQa$6~`*~1aDggTk4P?m(zFV{ix?WWQbjrU>jei;6 zjdUBr0z`7;+PwPzy>qK(nZy9cv2bInq?~IP<>+=2Qh^6eZb$8cr67OI<9b9DFrSae zqoz=Y41RHdOeARLA^d<8+6HvPA7SI~mqcwephzzP1`+9=(*GqFjFiHio42(4oSZ5- z@9OAoo)6IW;@NO`kDzA-{UGXijV4$Hf4id=bXF&?j!>oA%n&v-fkq~gfKzfu2b_Wg z#J?jGe$Qvf<9Pr91AR{dw8(xO2>=+vJDn}!GsFpvz)~UdcL` z#3l(isQi14q!T#jL_zS+aL)gff*{?TUr$aRL29^?hWkTGyt_z%KjE-4BYNSb3D{G{l!82(8f*x&>>5hLexM8*)v#YqIF29e$ zHqEsy84rr=9a<$ms0g_6)3k++`NoA11r8=llUFXu`A9V^-J95NX*6i}?|tg~-^uI9 z-$lw=;crSciKaYyn%Q*W`Oy|?g0v&sm7DE8zIq(qLu2Ub*!@slPY=M`(QA0U`tp^^WIlN zv%fu-OgH4E>|B+q%d2<+F&o*u?wn(f>K}$;og4i!M1+OjeSDbRK9XX!oPTI9dw&?+ zOI_t@aJzH$b$A@_vwbk-)0p2qSg-IKMP}gp0(pL?h6W*#n3?KKh&0gB9I zK6*cr9_(shq&c=B+EkoT;^@89lFki*m`yp3pFW|aJ&@nCP<*%>*l=lf&kh~S4Z*Q(sf|dnk9P|hC(JvRLW?m1>086IDvizadA&ORiUMqHeuY|Z2wp! zgEV(hTN5~VJm~&Kb_%J4*DOIi^}Skyi$vwG)0oMn>sUQF)pUDPJxua(!iLvsw|8pRyEH0X+MaxxTA;{UKgFO3?1X` zq;4}znLCppfYd6Xdn49x700fJZ7cDT&d7$+Ck$*lpO?6EWsOk=7ua@TDY~OXc;@=I z#@$1}&?43z?3Q;aCKTp5fbv1MKgmg`J%_@HwX(6iafeRIo5?XK$+uBeS1?{R{~&YK zU|sV#dzy9#B$*7Xs^_x?Y$L=wZdkA5|Mh?#+IPu+L?h(%0oWO!@$JCai_$JhoxF8a;ti12- zJyK?9K;m%u*~K+d?ZB6iEcAl%ugPS?lhg?1KT;zPMqpc;yS3A37m@YSd3LbZ#*fuN z)OSvC*Maoj95?O@1onJ5W6Rr23#jx_P8@&IM9JIgXsTPb6TWTK;_zwfsmj-Lm$gJ( zTv|w+p{8S#H0c-Nh-0X6pL1^QI5cNMYP4#5XG!~0A;ajL^Q<>V%8PH#gL0NP=l~7a zcUPLs5}hls7SLC;z%bswSsU(-SB$P83t*^n}fJVJ_mW!C^WLgS;ABA8RX)?=!|- zRv+VJHKrA3r$ARU2+U;nJsm7vto4~RdiY+?>&16QpR<&Oh03di;iqE!H1jt6#?8#k z7KnT_7MP-Gx zxB4(NnudiR_t%yy5wEVLP9K^M@r}4oIvyBU)oEIx62_#QXL#Sn-;Erln2OcGPGvtd z8Q3eadsttd@F=EpRUy_z5n6p#Mzvy|KINcSZZU-RZ9T&D7nvy=(MF+3bs=y1n6Rg~ z^o2`gsNpKIDt2~lyl9gAL1RtzK&5u={V0y;&i=#aa(mxgd;X1QcC4WVzD z#avh!8>;hyx^tN-E?Ebth)v%qSzYm2_Hw_n((!fIgYx>7e;i`}35^IXqW+q?)^0{^ z0SBBjKvJ0P{~tRLs=0PT4BaKfNCrSA3Njg#9IZ?bEO)4C=ylKZx)lY!`IxS!QMN0Q zHix*?*MVRFf%+C27>mZoN!?-YLg&AwMJqBCJp+=xT2c@ z9kU9fl8wMr#96NV4+Mr?M6A+X2kQWq1W)7H2yKPhFAwBnd?*)GE4-&lpvcvlK&=5! zO)HLHWY>(t*+Y8u60hD_z90}o)*0*CFp}3Dv08HCnDlB#(${fy>cWVHF_XOS{7tzt zUNCb&xdC~|*92T!N&{3pv(*mACT0?KyRp@f?jw$DrzTNirpVTACjUT#f^FY~dERx~ z$8(RBdV7J>d{W;DQ#SlHu9^rpLoDAgK~K@4B#t*dhS3s%QEL>Fu6xgVKu29QqIC80azdDGnU3@oJ;*t-BFiGk#rB=SGs#L#B+_~;B;X__|3OExtLc_7nZ{23aM*Wv`J{cSsc=fW; zmUbu;vnDp$=kyI^9%>)D;3pB$BRYlB24jpN)4;)qJ?#S)0S*M?-7*@e6}zBME7Cx- zks4pX1`1pfyF9gGTxK$Fo21~%EqcVI{VUM56_)7p{8~j^t?L{AqS*Q$62)XG)M;n$SbM<` z&H`n%!HcK0-d=sSsv*nE8y;|8c{Vj|`r(kTN|yaj3vCHteJPw8M!PZa!QFOqz3;2OON}blO*s+CF5N6(6-_*;$cG55)jdMw+`_7bU>cNeXWa z?D;gGM62*-odZ*3oN>SOyxz_1_BOYw#$^Z{Xx;?KtgK;(f(dn|M~;v)ey6K({Hx8O zecyo?+7PlIQq~YU-eSpL5wv-=4>>$LE6K_u{%G^)^hzx{-%IYS_)xSrpl-w{FqJm@yat za#Abr!uAWEt@_ZLM+LcyKy`4vA&M-sL0(&uql*b>;GifnR6ROxC_v2k0wOx4%xzYV zY5J{^&i2If8%RAq2#bX%YrBnSAvJ8Gb}34cvk54>84S7Oh)lU)L)F$9 z`q&+9-W!x3J=YFx6b_k=iv^kFS7Kw|U+~m7LNLqp<+Ntu4{pNegZMBxOGG&kR zFlX$h>g!e<$2?@q7!3t`ptj?LyP;}V|AyH?qzEv8zzLjLi7h}r5Ln_YnSPP^rsD+; zFOT5;?fxBzs(S*Wn*Ns{>c2>J{+|OXt?-|l>-55$&L%X{eEarM?q<8EDkoNKGVOi68v$(z z%Wy2SFkg8|X6-Y2Aj?Vqog1K&etEmGVjh9|f(nt|X_vI*;CkxB>1kAqY7tDn+#30q5ziT0$Z5-81jC!u^sQyE6QPM`7EXQ7YuPUG$II~oDy;p?Y!(@RnT_4~%Chfk;`K2%=w zx)sThvdKZqer0$o*o}o>%%+gEi}>;0mX=aedlsXO*-~wk0dmq56_d_7mIII`3pI1E zn1;IWG+S{pcc)--#nb0Gi>$%;Z`f+1G9M*vv&h-e@4`_wZi5_wBxJyO3Z9@Axo{)F z9DF;WBTXUAG0VHObKIL}i6EvI0@}S5MK}#)a_3%z%pqH%M_PfjAz3L1KPl7lA!cOM z9|*e=+%Vc%L^B@m0w^L}0p7JxI!OlLd`ATXGeew?aJm+_+`XF^6;9IohWKEKR%#)! zw6`FRSSbM7Bap$bTT(Q^(M^wp-2;4%ouM^ZZ9mHp9A2zTPsp;jz1kdYVxBA(&)|_e zQdyxd?0R+90RM#22wd!pbJXJmurgorI*!;cX%t%?rGpmY@TQ(N+`>b+fvv>yQ^4=t z4G0peK$aMX>Huyb5pUly`3~aijJNY~5NVj=kG78XC<~t26d)h%C#TEjDOZ%hiA@6} zU+~lZ%C$Gv?-StjEIU{E=2J^ApZ0kt?uaOJ^<11M7#${*nSXjQ(0}M(RdeVsK)8aS zD}9qR{xzrccEFg>PgyxWX4Nx+vC%1PAYn4Gq&8md5r9s|+kH@c) zn6<+U79WfNMvpF>0u(xp>`h$=Gjko4S);E-VJpaV)0nYUm%RZKbw5WSFg86goWw7d z+NZPnUtIR+J{Mb}0%NZ_7`1Y%QzPx%iW2tgxK^_|BWPvQVIYn8xDcAEv0a_FheCWv zcgIOg=X_fqNgr2MUH@TeR-_>YTYQ(krO8%Azpg9TlZ&9 z8PdGmAFJ$v;9Op%$P-#%i`Wrs2#IXsq@!I!1Q*O0O&iv_(y~U-7@)!iz+7yAVRI80 zHtfK#F-hue(>nzZUb{E28{ubHA8S}C7(5LBN}S&OPPVqD4;E?G^tcumXPd0#5qcOC zD!&kvo0mOly2^|*AJ=532sNZGX@3Uo1=;FXPqzhA+&&;fU=D@gGV8Vtb0;VS!fuG9 zhWN5yfm2JKlXG8#JG(pV`Gi67OSgaUT&8ckLn0F=TlreV{W&!}g^-;d+L_jSEK{yMfK`#x>ZV{m*8&t>v2pdPR#!Y4A3;swFQqB3975>I(aYutQ zjA{PJRTqB;KIR!kK+Ih3k=&5qRH(mxeD^cWkFe#B+Z44WB3U8!0z3Vr;)iRZ=;|fL z;bj4GfgIOM@R2~03_%gS`w;3k%OB}pU-vu{?a6LAtNt+lF0J7!0@UU2y?7Mr>Ucri zu|w-QwB}!W5SaMD7qhq#3(?iwXW+ZpVACpn$8y28uo4& z=6P7Bu@0&bv9{if4uYVOD;UNmI)@}!cVu!=Nt}#;gj8kagu!>e`-QJ|wo*p4zT+HB zRB(_*n6U2>P8(}_w1VuJQ@n+fn4t(}w&{02e#0C0d){B0eQqoiOcp!%D%R8p!s^d7 zi7}ZnhekIJB}Qa% z(@ej6RmJX%+r>a7tY)~!*S8uv!rmk zK=)DZjFfGd?&|XO{68h}{{<)T6QcwomL7qT5Dq1PB>o)&FK~jum;6@<{NHi`*FXLr zhPK6}F!U?l9Pd=~CuD0<7?~O_b||7x4Hml=-6F4TJFD~Yq&{8}=aXg8?g^{Oc=}zi zrLa8BFeHO)#I7v;^M1m>5vlaam~LAH)4P9id4hD>29;PvsKkGJ|7saKQY+itr{}qh|ydYipP$$MxDne z8~KUd8=kI3Jup@&n-irlsE<3hcj)%bLQzSpEN;3qt0sHUaP)H#+(l*Bnb{AbIv}=a z__N%?v|Z@z+Dl``NFAUe?KNXFXjgC1eJ$9*-!Cn2zOfI{d|`(B zi0K74fkg$NegYL==LKFPmJRz5SPMGfFkwW@2?wgcfVJvN`e6v44+Y^$8a*)BjDXD0 z*B2my4mUOcu1*TFpa2rx07{ILL^xQ3mUdBC0g_b**Vn(_Ps5jDhJ2(s>D1B6?{A|j z%F=g^Ab3V#xXwpC2AWF+T(c`^&;3Vxvq%@kfLr$!d_+vwag(lnK3e1@`D<@kFZ>r@ zE_M5sfT0G8l3gysJ=H5B@5AEkEuTJ3!8P&-(ouvj#+B{Omab}V*~UHFtf;+GdFY=d zVvUEm=8ylZdX+gg39M=Pp~ou6Hxh{$Y5qyPkq*#&mOxuQ`)Zdxyd zYd(rnG^pI@hwcbqOHja~%%Yh7lZ&Gt$$ZmO>jh_Y!R0xhRu;ubi6o~_zCsZTlOOHR zT{h3IzL7~$9TD+?!X1bwGGTEl5!g?WtybNd zTA5Z z_Ti2EqT+SsLF(GAFE2Xi_(^#QkM;D!-s1_GBL1##0M zrmKLi&+pWCg-HPjdlS}r(I92W6Qky#n z%%G&PGeOyj-CfRJEcJY3^dr0*$6REUxm;~jSGmv_pKY(Aqxk9);}=#y3xjrV5mG*d z%$xGPF;EP1fh{yvctw1x`%WEw?n7sSB@25^Vqz6-`Mac^-)1-)PY~pXe#UxtS(Ycz(sV5 zF$OJDdWo^Zj_>6&1%}&-o&ila;0$X<+7=QfiN?5VNO9p@A=Y=b>*w}PS>H*a#)^CK z#SW>{8Dw*OtQaQ%IzIY3`6Ye`9zf~e;tMG)O^j@?C z7r9M2;AU>P^;%x3bxHu29`5`x;e2IRr5Rs5rGi;2WpR)mujd20$8_FB$EA7je8N!T zJo13jr{=(ufLY2|l>~}r$)^!#;08OlB_{FKDrvTj4d*mp;6J=!5r5TTjwRy`asjGT z|Jsj!O4klqJ{Iv6YKpG^t>>fgMpM*4&{1!};6+ZNSW)f!zYha$EV6+WD20m(L1E2K zBA4oyzuo0i!^2#H6gG~^wGl_jV}zG&{3K zx`Py%wBk9hw@+C}%F}Q+r=1;^tcR)VV%q{mQadm3c%ZX%I=wl>PlM=zNpu>EAW|k# zkf<_%N^!?2iHjBcF~_5UxtZ%g^gjzsijx0ij+SxI11{aA@EE^&gr624mX_Q`F%hn2Tk zi`-vl&mU%QwlQ(GcDcClp)XLX<@{6&U%$;d^N2z3)0w1%^-{^w_2_Z7(`;GgU9k8Z?@!=on!96M^(N67dtyT6D?HiL69^@?ag0I)imaw+e{P1u$dU3S}eg&jy zaoWE^?wf-6jhWR)=KT&GIa+nxqTx`VN4>itZ3vm9#i+XAz+L+sv_)mH>P=wPd6ieaQ>#!d8Kc#hcw_gUIW+igyu!_ ziAG)K7NDl%RV9tL2y?snv04s5^{~^(sz?n~c6~W6J%^FqN-^@$ZDKCvni?{l&Q5k+ zzXT@#2lwOu9POi*r@!*i0LmW0I1Vi@rt!)%qY+tDFr3v-yeB}+)Z}W*z$1L9eEo5g zO;L(&4sNDq6FT!X_TSnrF}AbHIUR={)N%`X99T#ltN;f~-XxdVJxJb)+s|d>mA-%O zS{sV?161XWUcb+uP}H;ujP$N_e~g&Ay81T#tC;$s!~n8*V{0R#ab8=J+Av9G!R!ou z^u$wIp*Hs|dN`t$o4rBE!6VVzuqO^(!8m2oSlbvKJoJ`|%wtbZ_RKW(V#OD==gH5O zvo|o>zRfvt>~>ZC=KN>>o6paGM<+;4AO8iNu=TZ!+-XnJjm`k}K0|El7VQcc^HFUW zQ(^v<)NR4}9fI7-F7j!R*4;NhEaGnq`y3D5feK8LURmV#=V+}|`ixsljgYFjAO{!*bLjN*fbTR99cW6jzS_FeAn`;Vb*O5}V$tB&e;RaVlWsQGf)0 z!%FNy879p&cJ(YjKNA-$jSQ|Rg3;aiG~i@yys?pCJb1kgs6(AdiQOx?>hFN*@aM+r zDysN5(-8Bn0nnt^Yep21{<3cy5cb!m@o(RF_yzUiJ`hmh6i<9Vr(3i&28TjUTa-2} zes4p1i*H>6~=}(~(onED^#$`$QnMnPV`b4bjn_ zj`|2D@iiLjo3a8Uz$g_kF5cFt1PGl^02*e8@hm8~r-9e?7pO8ECf&FQ2)3+~$i-6hV>IQIfoJEkmi4VjCV+^UrT zhrhi!bVW5vP8q3JhbhSMI+nkgojO(hxiPjX(K6&Jc1+*ap|`W_fN!~s!BB%*Q4TyrJ;~b5-C^D-?fOeHC+Fu(w>N!Ko~`0u zvXr8p_(4B3nLbE_a{Z8M-&V?wLC9t*bj{7tMJB1m`_`g&OO)K1s+qSV$IY!u!>n6nU7R_`0T}?w<-t)87!FD5LIHl zS3Yp%33g2FYIt-{f1kf`3FW}xd|YkzPot34#G|MPWiiJDk0Y(sG4-JMGahc{#TPX_ zT9lu@4(2Ppe0}UTucGJ1mz-X~7>@-V%%=9vz>gjTMM>h`cI`4ZouVsC7^+f4;8UXZ z@QZ?muyIy%xro}Y;LFr`6cprn=mwX+a^xFZD*Pg?WGzYam76KfC~~$^+(?%4TR>=& z5g1qIz*>DrDtg-G@C*QFTJ(|vDskn2AF+ZFcC_f9#=NVE&Een~)!WU1-*>Yq80Tmx zZlg(Tj{7oTea*)r*}}!I(j4A%NV~@J%3W5{X3LsEQ9={a&#e1OF&&LU)xO*wVYq>~ zQq4zA-x=_T>u2enSx;oId9gg^s@GH=%_E3ot*|9I#NiBK8Me&=u`#jSAdj}} zXJu*%JhT*tOshWXdbBg${?3V`E(g^v^$#zI+9tZJ_#>jn^vzZO8S;%{eErjAeGs%J zD@Wk6d_7pvIHY@DMA;$w>fQ0kJp&*gXD7E`T1?Yac5X~lxwJX$Q_MCZ8Uyv`k>+A0 zjh3UOEHtc)O~%K?C*lRp9J*A#;(VqC4aZ4xW6KdU+yNtML&90*gI7^WTly7H@GI;2 z%#aod8SkyG4`9Kqc{%AJQ&cqlmQdAi8nMX*K{}dh#Qbw%CRw;8K@%wJhv`-mTNH`^x^s78zfW|9Dz^(6_4|XZ-UP&Z>87B4yGy{mW|*$3C5cR5(b`75giNrz z(J}wT=f;IX372DD=FY3UNZ+~Rwz^(Ac;P!n@v*h9W zHm%d^Od)Qb=KC~ROY}I3b&0(>N|6Iuy1$t)8@oWNUx(m&BPoKUl+N|&@N6!)LzjyU z=H>bHKc^8A_^4B0U)%L9;#Gn^=J{>mjk)%>f02QoGEi%<>Cb~ZqsuvGAS~tVlMFL&^pso^&3`91l2?GOD;dOPSbeqT3$M?fe zu~N!@=Y&FLdLsawo)hot4!d(iwr%dlOd_f)#)n-0+F~uNj)Xi++=Dm6Ix~H%n(B`{n*H2xG^)O;H@=$b{*~~w(i)wHySe2J~16$uycw_nhu=n0^Q8nAPD2R%RfFcOU0O(ut4#9n&3E0vBw(G8QJRl- z+LPxUE=-V&7b7vOhiOHZ^FmASc}_`72fJ4J3(#~@!&~q-X#kj!+g|7>fM}Z;$-i&5 znN(BF&{hK7f$!D!Xz)YNDJ!tONyS##Hl;?#_4i|hEcUu-PMf#tPmpKyvokhW4!GtQ zY6ef7obl3*idudy!|oZQ=_hk?bO%t8fkNIV)q9VDTEl#IDSZCTj-w05@fMab&QYT6j8wdmhOO}DnhXJ}4 zLgjs}5dCcQk0PONN=P{_$IOh!%CW6Zeez2afNcK#M==#vIptyVU z(T)+s>HSCv-^PUD)Fy|mwW-GX<+eN{C#~U{GLPco0OD^F~%Ag z7#;v8-w>q1zf3D_fsoScpvTwJ&wrQlKuh93jX_RsaP@+K(Kvnw*_;FfM#qF3PYu6i9KH~~^ z*6{(101gKOQ1j?vxE6ZzdHp@qtTgJ=H-b%@?jnKx#4@{+Di}w(fdOnr(whtaL9@Wu z{d0RL^2^}@kiHg!(Vd6m2&4po3Wfmie=Sdu&IjCR2Vz6uCjv`ij_lWa4G$jw@`nb0 zxgSgW{a@!A*^p<0?x1g9M*Fl_MmIf~2She3-r=Bt>c&7L67s#<2VP%4AV)Ss?K-~H z>=~-z;Q(WD#RLVzVVSVrF`JMH5`6GB5X?jXfr*a7*85w7^1DRlJP#%3c==#1>v_7L4>SkJKu95MVOfj`YP72>4Nyc#E84I%ji8$M2v@SBtS2$z3agEzMy3j`?&x1at zNR_icf!r^HFkQ_mviQ8$D!?L5oFUKL14!a{V#~X$9R{72faKz+j95pm#L5XxNOl=U zyGzGQe0sSHR+KR-Gb^?lKJxRBH)W>t-6|CpuSJL){fn;yy(j!H-uQ>_mrf1c*^gsE z9fh<@9!CW2;|?JbYt8cHoRu%__;?q3k|ro1Hdt@K-w}sEwgEsDNE`k6zH-G!s_=Lb z=lk*>C&{n2JGik6T?S5oB>&x6_y4%T=8i&NPp&;rNK3f0|9%9HEf4TjSRnG9K^_A8 z(Z9;c4QKM>W6(N&n(d&T_z>FRa6*8##pB6=eBPh$N;^TzKz11vT~xsH8w%Li_pI z9CogCe}pqHna5Gotz+oVhi;EQg5qIN6t@4L3R9(g7pAKGkA!zaP=Pr(cD5NBj#R6lT#oN4$B$N4sH%0W0j;#Y_952MD1kjS@g$8F7s=nEX zhd7^a(yP7JR9OI&yHp;_$$xaCyehJbIqeff@au)e3Q};J4Ex6j?b6UkgZq=unV@?1c?-7v@~s+vpw{isR`706cOu<;EnuX+unG$y6$Rb)#qyjYT)H)Al_ zB3%4cwRk}^EkSk z8*&fvSJJ$-+BKYYCwg&x?Yr;q2`#g_!+EMOiYJAx2hv2^hRda|tOX74dW|wJQ73@f zv|r;JeD0p#YXnSR5RU%K_!&rqg(+!aerOb|Qv2qel z)jON|Bk|#N5+7aP8&p{L9r}eg-QJ0L>3iNGZ_VVoGN0z4r5^d)j-&(m8%7daSWe!WLd3FDvoypQ=&4hz@G? z=N^=+&xDv6uI>rqlE#+A8TG03N=#%OFQvGi`+Af;*_buJsTs=Pvlq2(t{+TT_A#FSXt+5gQ-f}P{xtI7>0S!Xpmvyl5YxjZhkX~k~cInh#7 zKeXZUfpy?%nc5(Hd*28IY`89`q_-M6(3q@p+Ri2pT~Cv1-Ltu(n5>EUz^viy#ZY!` zv!6K~wHebU<{-3gtfG2aZ{<~8m|Q~Q*~j$X2u__pJL43Y77#yHXDxAyI^XBm5Lk9X zDYqS5BU+Oj+8ENQgz>MWC|y_1Q>!6*TsN82o=Q^5?TT$zIFhrZ6@J?B{A|{c*da=7y|bB)Rr{ikdiznTimxBoXI21d;O{BS&L}-ZepHdv&C#2d^)qwucs7}lCY&G`X|DIwKcqsp zA@f9%tdT?Ra-qth2OwrqG|m)A{cpr1i$nmOtrU-ZI9 zCWD0O5p^MJIYrI%W*EZ;tfciQsUvLP&sMHt#LjhcTIOWAGwx*67#bF_H?a-O7-&`gBwL=w8O`gU_VDo18O{HPzP*~>PxXN#&GyddVD0w~vE za>mBSam$+azPh%>tE&*FY=m_@jl#r4yB^ES7uba1ouW5@!bYSbi)G(4l>#D1B?KBqk2T=hJ~NY(%cXg<;42+- zsfa31i|qK^q8XB&3+<_=?i2|7!E$X8;*t;pa$ ziw6x&tW+;cq+q4G@ofV9P49gwNb^Zkxks_hiG8D)?XyEp5_z7A$W=!sNS^?q`tH1g z*!5k{Oac4e4Peu?zQvY9?--%$+r_@H?a=+b#!O~~SaS^|Vup8AiVCS9?8n=3?K5Gq(c%lv?#ekwLF45`Bb5w(f(o@G4hxqJChrXcElq3F zu{Ft|ljn|003Hcn+eG8r%Cbua;LoQmT?0q$pPKWeiM~4jY4#~uD}2~7ip*DQ5mL^2 zl!uoWxv3NA_g!4fWnHQMt%I+m@MsZWv6}`Dr@aMUcY5&k_?E8R-*g9Z!GM-@FclDXChD(%T=5KTvnw+8wBENhB$e4UWQjhcV@*KLBg1a3 z-8{XI>;q8p&`&Zfs?=yD)v=-J;--<}I%>N~I=w;ypU8Y*p(&|6caOAi+Ta_3&`esw zalA+GVgV*P5D?A%Bli36)#xk_Lhk^HX`yYPgB8gB4P((4kOi$l$a*&uj+zT#?Y(ut z`0U?KY-37aar6fwJ@Mf?VBGUf$o{Q^Tl`=%EC)>+Q1(7BeDSybaP@$!n2w3is`z## zzNOL8KmgAREntJ3Ze&{MOP=$Hc#H}S)mhW`4YSWR)XTlyP)Kr{rL#LDj`EZvF$*C= zcy8wA&T^pXj%7>Lkg)h-=}?Kic|($tWWYk53$r<)yG556alCP@J9W`e3U$%}xO)>_ zs%_0B(~pHgHS2L%xp5dI$+Y$2%#cFy(yL%*{wuE@N<}LkKpzdT1lp0P#0}(E*;-B9 zY!um~?LeAvUx_SWZ-g&+jM(TIs7RWCF%B!nyTiSEB7EP!2i1y@aUF~E_xlctU7 zkf|20poBL8Lh`LzfTgZ^cN|EoXWy9<=w>nJrjdHZA0N}|@3y-fh%g%Vm`ksHE|yraOysB$rLzwVKYwAjf5H&2U=GLm-316tzvcT6 zjwgO2XlNN3+#wHuV!^m{@^Im=c|$ze`uH}>r>1*v$wccPE<~%|k$`7>xbR+!fZ!8R zr9li2rpjEWEdSiSTiqb(qYai|9T5RD)6-RQo>2PqPj!Ga(4wCUQl24dbicC$2vgfuS_-oqBkQo50GV6#^XLoBJbj-yMAyCrU#lt=D6;9)nr=b@-(x2S z-&boo8IVH9+3FE$b1BSHV|@$7UD1&JeD3aKi;9=+y1COqOgY+Y$;rlE;O_Rf-S%%d zT6`3B4+aKz2Rd0enoJq)o4q1`cH+pjL{7@vXqRnEU!0n0GjpwMhw4jG^y`c;RsuFA z@#E%$2{{3QyPK)F$xZu+zc$MT6z z{*}(@(a2|2oHR?+};@ak9{F1obrdXvL1p zSP{2b&u2uu?69>J5aW?Xbm8Blv?M(f=eU7LGGl*n-TMta`)TZ0qd_VkvSZL+&U1Ud z=VXOaC-k}&eZZR{UfJp^Z{Nz}56M6V;fV|Gx2TtNzwV}w2gl>7Mg|#qvqsoxF8HG$ ziBM$tK`pa_*7?gur)ba8eI~w+yGvIPRcmJ7vn#J6v3^AZhgkB8iD)>(RtTL!Cw{7% zT){I(H6%x$4@ABuPmt{}GE2EU9DQJk%!_oBRy7%Ekeu7(IeMX{n=Cjj9fY9Om07m{^sPN`~AwSj#8?%xyBQc<1BMN zX(0%YE>=7ly*-H7UO$=uh9xhvA?wa}e##HJ%D<1=)u%b98^VWuj7(0Y=+O>RuC6yu zq)@Q1zm|AC0M?+^DBcITFpNq|g=AZa)cNN_&S1wnOIc8pw1l#1XSM40I#@1A2YXCDev>yV zu1~n`LHSth%IH@HVYTPHk=FsKE3sHIX~N#89}k`$ z&!#oWD|0_EBUa3;L-^>YUNkYHuBMQkQ{t=))8eSFzp&FTc%;MX`U&fjLZQMHkCG2L zASa#x8?NF-2J9(zS4$Q|4V<2^m=d;6zL|e6KWgh@%wAdL4||`@uT{%>zQeuBebPR1 zsyy(7(~Y4ghSutcKDC)eXUpzXi!Ck7$IHj#Pg<8*>&Y^nl;deVeLZjXEwuw`6-jSi z3^gp0)`ZK(md4ljGCN!o!3%NoJl*a-tU`dku!d)^c3%*wH+C;8TU~b{xr-8B4)v9X z6s(xY3>9THzV6`(v|h7|_Bp+W?)Y#WUQkk=n{CB5vLTvs>OuGWv7Te^ryV~SJHlQ9A`?-f_&^cJ5f0KG^SA5tvUCe)W8%a1WFUIQ0K(v5^G2XH z9#Scn?2<&9@QPkhRqnEfO?Q&y-B@Chd)0_lM3$o)3vQ|9vp!8azD8@X^IlskuCZYg zZ>gDSbKW*jmaMR3HL?2PZHpJ@h{(n@Hv6Gshg1-Z84v-?h)TzOBLMFdUlWIwePM{g zO@}BKSeLc1Kqhp|oZs}PG+6GGh&~wR-6v&4C=E$DJvn^U&U_zYAKyj0FQvr#oH;cv6lA9&TK!t|GZI$c>pV^e!7P_%kWtPc{-?8klyeZZR<&H+HNr6IIFhKGv_-~j?!iRl|mWF zGt}FrZF7%b#-*59!VM89gBbic5S3eD1>$ZHIM(W?JKOuOKvk}=lk@rz!~qarS#B`T z|7s+g)#2b{%N?p+*_>U_ip2+v{ZQpG0MSc?17*P%8!R)|8E?cPR7=Nr_mu^$eF_Vy zwQFO`gQIAdP1lsnqzSf-KmkmxCN$`Bzu8`S!$$bNwGd*H%QkrlA;l!qTZG$nIWfBm z*QOz+Q7$&H$J=V}ma+L5yH?MQ0wje`l{=~}NuLJX(kYne*CmV(UaLyyG`^mblqET0 ze%k)jVwOTx^HT>E!c$~kqaKwI;{<(hW7NDNYAc?0@`ieS^wi;|#v+|YWzjHZAAYjx z)HA0FL}Zm1gmlIFAnnxjO@-EH)3d-`IoVtOqkfk(xz(Q^nGt7|ewq#nIKLjgXDond z^(RDEB5@ZzW3+b2oI2mU1Z^ooy_Wzqs8JE4i&o7xS%W9iRF^e& zyp!58p1dMP$38$nobe%2xAb+|5!fA8fIVg4oP)E*`7LLu>s?%IASzfzp$*bVKYJ4J_XhyOApJsZIn+=FVB)eg+0!VE*V}QA;0bQXw5%wv z*p*sh@^Goxa%3P45dV~GZ0%lka)2*J_EYFt$FFe|dR=`PpB!g;D&IF~kPTj*ie+))dpNJ$Vb#yh66Z5`l}B%4$J@)xaQFON7Dtobcq3z@v<$Gf z{(EQT|9+$UThF7FGVpM~z;66*Ug?g2k6C3le>Eelh)mJNcxQJpi$dj z2gQG%M8`kY5+Ej;hrXDh*=hQilvYbRSg-d&@_kCit@F$=t#EPRB(XUQI-dXVjbIjt z@?AJ`#L7ptXhhKpn`aJmYnidTGokush`te|NyOmKq@%L%bd#STdmZXPE@wHOs`_N~ ztq0h#2?|6SL)=kJ*qspwiW@)?e)%n&%@;{%jm$s1CJgv;)l(w;jJY-9Vs-1AL!oVA zhsAD3#2*77E#d$(#?uCjriL`fpe?@#Gy6%E0Mxx7t;>G6#8-z2KU63G`2p~6U0j@H zI!5M0esl*^&F=@FV(exU?)cpQLIfT%w5%J=km3IJ>F6L>CRw3Z^@A@tT074Gm#5a` zW9T=>C*r4Uro3QJ65sCBCoP_jN3>2H$-y*IhZemzv$(Bc1Woj731M!zU2a}B$0Wjy z=vW3~;)?=Y5}4Ve&lr;B@}{wyRSoy3GLm&4#44;@h)!1Xn@--B@Dt|1R>?I zf~qYDU<3h+cnny?cPZ$dOW7qBzZlk}%b)fYYw zy16Q=Xe~I!0e#DnQ@EyP?VhP0RA2Aq*h1U$WjT{$3v!2$o#2Yz4CBzS@(6FG@)N7G z^@U}Hr1R&($vT<)SvaN?wv-qpNSQMon$qD1qhXs4>wpty7Mzn0*mq#G&e?`Sd2%Z? zOlzweTpwO@zE9cLe0NLEbBzPd3MsUKTy#}}UQ*mcl!*z#X5JBfl$WVh%ah(qJIG93 zsga2?T=v1=ebbz0&@BH5s{y@@e zKdCBE9G)EgeZCB%j(}+fLDNE-OBVG0`3n)jLBH6V#R>u{{>vdbK!+@`x6<-ZG{}TE#OVd9 zPUd7zOGY-Qgt3#SLernb)!S}SI%2mIZf%6O%sMpp$_dA-HpH!o*o6<+*vwW52FUvo zbyQj^prErw_Fe2R?WjhOZz^k^6Au@&r_bqZ(#sQRD1Z1|)$q>j5n3D-q+M@l%%>ZD zxcdAigP$>rGqGl+f^eP(Mg}Wh9`&?A^cC~%J41+PXTrXx|8A;cJSa!En&!-dU$gdb zRA8_RjMzjYa%i^NoSx&@HF1G}BP{=gx*NQVC{MB27 zkf{yCz^J(~eIGUX-<+}E|D5;sQ+a`6Y@s2WMVSeb(!A~b^o^}wFEnkm*Eo&0bMZ%7 z>y(!d_2fI|&`M^RUX2VtPM?2N+JjN_q?ErY9#ygLSe#mXSS=d#b^m!yqJJuRxkTi?}rD`krIJ9Wu!LN^i`?F27ZO| zBRqVY)s%b17W$fwYoeUYQsHBI-lZ{8nM2!_kBv>*Cb(|=#uWaZ#qd9A!6*E?E*aJ0 zHv&4h`j&i8+hz&7F9~tBa2eyaR(Y~lr$o<4hxLOUy!&#iLfsRO@x&$7(YAjmp5MH7K1@W}XSc z^pDC79~?n-cbIxU&>ci++wn>HASF9n1&M6kcOGSk+`ViXv>J3WqG8v@-T)j8`95v7 zWv@#NOaP$*g*-e&@b#!^1fJgTp#!I=$jc3t!7j2{rV=jIdHG;Fz6Kt@lCK%%@cj=- zyAvmbW$GX8Z|OPTB{=wy@Y*Y3ZF}pPdcnNOn?oDU6KT3ZUm^Iyj{I|1Ka*KPCnxf4 z$J-?Nw_5!kventFC7dvtKXg`ZDYxRtz5_%7TudZ@+6KnV_~t6u$u)CB;8Y}>g?(T@ z2o@q{od<{d)9zFp?xM3)ip53~f{S}H}kaq(YyirX^F>H0i}KTD8D*_l@D2&W__ph{g| z8Y@w39o(S1EAy^Tyu;wrfXqcSamz_tNn!R1$r)VX@vBa_OIK;5d-*HKWy9gtkvv8{ z$r}osT!I6}_w#k=F5Z^4@U*-$EXAMsIeWz&VLm*|Z5HvxaU z_R%6NzxTic`(dLB*<`C3%F^!T66ySUVVWh_e&Ln7T6xCz41{39kdXrkF3tX*_<&G0 zT5$q;^1We_|0qT7=aIxs?P%NmK2>%anhQ=n;!?E!CBZCZCP@Asu1nO z)80GatLV7yNmbkjyUilg@R@11Nou`0wSx2fR|2HFg!iwqvLDnH=9Q zjJv=3ox5-QA945ZuC6{LBWh*5j8YAi63kzN&(II?(T~ybnkQ`yiRYvqvy^{iDHmO{ z)kWGr2fhG2U_VtlGny-gtH0)fT87e%nOVYtQ(qZB>ALPv*{In#+lds_S*30sWg6)U z_`~v!%L_7V!v7kPe}(n=T+wCpp0qsc zxl6)vbnNYM`l!uP<50se_Z>VipH{~RBbm3SBPs->+TdyG%Jli%#9Sv+VU1-Yq2FGRKw9^El{;!@BWT%OT}=4j}p2{;o4}`?3aQe86rOQ*iIZ*3d%C{0D9h6 zad@BW9piS(E5m7}oGBO&eO2#3DR8#C>gTd?x|pNu85~Q62MZk68rukqjzW!Ll-s+q z%7$$Qr|^UHe#7e$-V=t_e0D;cj+sJXwk-K8Wh?jVtm{t>}I@I!W*qlfH+C#eu zL)EmI84+aaA`{0vGu^iSv`Qk2hX>XWzBMw6T&AyB)6$95usd-*ST(Cg$poZPqY@Ic zCx_9Hsxul9f-Sm{UN1*gk|o6DXry{PHc|&-4Ue+Y{z!~ zISvfIcTuqC6QjQB1;r7@XKzE}S#kpB+0PZ-S#T>>CGm`Sw4)P2R{Y*bxG$am@zq?K z7;=vb2Z>*}N^ zKyl*LUy2hSJUv>IEMLwi7&GcGg-H1yY?_CQ_BpDj-f`;CWN%{gveyJaEzYsVa||DN&Bfe>Y_ghRx{&?x^ha!u?AQn86b|W_dsueBq42{njG3*0 z^SFE#gGH_Fi_7Olmo0XkQhbJ9$GtY(!@kR9Ak{pIRoqTIFz`+bXyQXvG+nQVkp+@U zr7f_A!Dt8&iirS{?=v6l#!`v60qD6{^yt*X_ zZ&r@oyrK0yRcs4+XhC6{g5>z>qDAalf~Y(EFxFVFpQ^W_uulAm2?|%=9m}ahZ7R(j7LP>ooy2& z*@9cLW?RY5%|-@+Wt%IOubSJNSBmGY=N;2(*?xs;`2`mN)T<+W4G14-%lrwkdp;3g zpN>0YJI{*MUUBC`mg{PKBS?V6>=#y!@&(fZbpoCTQxdjdWdXRnsTc{n$j9mDao7Va zw0X9^;>6?W-QZTZlcB1jT8s~Dc%NqC7?O-r10?(Za+?1`OCOiW&&fxcqbw5byv_#Y z+saw{W?c)n=SQg+20d?=(5c|`u?bIhhv~3wlmJX(zGTD+yFVaH00TmKt-#rwWa$VW zh6sag4?}jS)*)u{c*ukP2`}P*{wC#bFG}YDQkus=%qG)g&H=NJz{)bAnj9c2spw=p z4$%6$EPp5jS>C_@zrMZ8N;~*t77_qFT5f%DIpvuhBB3vBAAz=FK%B76aQxms-PG_F zZmDC+*Nr5RB|&)lvp`>~!$IHs3ZPVX0H(vB$c{Yj!~uV^wn#D*D@3}DyW&6zLF57w zL_i}U&%gK0ZZ76W->6A`dE-`bf4dU&@>&vED*JQa`=(_!RkdkW(WAK81NHTF{n4md z94Tk_uu4bcAo#X=s!Nu>u`TsP>c~CYWW5P7sZX@BHGyBU%C8cp-#zG0vtnd?f%St6 z_cF3nmj*pjQX0NA5pP`X&~cUG!=-d*E^k2_TpT0@OC8MW2}_)XuR>bi&qvYBF~-bA zX}0+@#>4VdE0iz)L(OEdrv z5onC@ovh;oiL^$9`51gnN1tu(2aoPUE)<+ATCUaOS*U-UE^zNY^(~?^3=dCRM&pA~ z>m6$77L3M_&-sdCTZQzEqcMsamn$xTMYrix$jFFQbKO#fR$0>W*&V=Ww_VEAY(y%I ze6My~&6BEKeGeYeBxYi5U^czrnn%%4~|wi>)V4%NWc1`8xyPRr{dg zf>xr?WVPC_-<%0GAPU=8ZUAPAKM=urSy)O#{)>?Tk4T9CaPz@r8`IG$3?0t>53gvx zN8|jMKZ3{_TPg9A&onvGMto{32T9BVxk>xI&;Oe%KDfW+h zto=b!cF!Ap@DNbp{BHJ=JEA|#J_Zd2`lo^gyupwD6+=q1fY5HH`KDA5o~9R=4zMeK zPKV&V@7>&PMf~U{=(jf+hdcj8n^_oo(B8?7O{thiXrH-vQs9hhigpZYgI^yF`lTI+ z)SD93c<1*Cu93QqF6iWK6o|3=xCyDOA!RLqXv&{!&adUb{*RRTltO5u5vVi(_dc@OaJnp!&~hw*ZUt zhq5j8V@CkF2cX*?e2Dq)!pDEg=JNe6mHc-}+&|g}j0AuMVJjBk-?VjI^@9N^?cXGp z|7e^E1;f)p2EM@1L@t2#>`FP{Ect-h1Ss^gx z-N^jH`)k|(&{Q9=d?Ks~4@n6% zq>qR;HF=*xsazEK^EfB6Yju5$@*o%`E1eS|UH2WJ=2kJjtXs11jq6pji(~H<*3;zp zKZ|Dr1DrT8)XU2nr0Ra1x+-uBnG1GE8r1}$BvjM3@4Km99P$U8`Yb5=8^N2tL>1h7 zJj3*On{azI&+8(A0cA^mSD6=?0_mTU%Ku@!DoQ&yQp3HgH!q{M`kEDrDnns4wC5eJ z6?N*RAHE|Xf*;dmgs6?t5e^;#6@UduHUP0|WepyO+zoqQf2OqyaglQ+{?{J!Fb zih4ty#&F3q=r92V)UCQo|G?cl^>x&9x5J2+kIbTt=_i?C(>%kdJkd=)WTEX_ zoh>oT=Y5RI68bs(m^TUM%F$OeYEQCMEkm7j@{8-O8lWD>*Y05eN466@fLF-{G)Tw!#WN1 zq-SG*IUb>g@gds4ocdjZ9u|_mF97{rW*&vl0v|&tuXZj=Sz_coMf!$lD;U=6k1g%W zl6+s!$EEn;CvoSuyIm&RwqL=6C7(l{md)J7&ppHx_3F zma)N}wHn)u99BTm7xtx`d{cCGIsB-%YJ@q7j_x|6-qTc<-Kwnua*cDHM|oHobU6y% zjHd}wjc?r%Zc$+UvLwlnn)WQ(I1sZ-;J z(MU6=XE-^LrBiuL&hjb;0!xZJx^W;SozM3^P zbr7i+wG^l8xTfOtDD?2{pmN|;k3$~Uo32Wwo{+abE;d&{C@G&I-_=IY6~K|ecYvT? z4|*F=nOhY~pI}FhZbrHoukBgyr8hcAXMgEIXO_jHNBe*A=x#aD`udXW&Q&>>`&jYH z(j;4B8>R6<=Y$=DM@7rS+yUgRW8_$&>fkx6w(hp;uL4L@;uRJb4&r_J=b1LP^N*mm zR<1~sQzgGNdU$y$>qbh zmG`$&$d~(X!u;f1O6$DUO>0L6@0{_4|19pg>Zyy{h0LZen1PYU|5PI7So}YQS^pjy z`l0yY{5@KjxQ(#{@&|Mi{U;#1O0F=>#Wvt*#a`ryXpwu%KS^#+;L5b!4L#H{ybhz7|-->U#0XL!FAwtJ_`ulo-c~| zTWVQ+;*Re7F<3~iu@YV%4;m_g1MK=I)faA^{Yg;e`?wRc0?ZVDz~CVmVK0wO;P}N- ztptqe7x2kV?+#L|khS5wWq~jI_cFJ$+ZZB%kF)XuDue@eaZA9Ku-XAvs0XZPo;iEi zn2a9Kf;zBor~#j7N@x8Ery%%(nS`o;!nTV`j6kTtN{6U4ra7i@XtTlgOJQk~ZB2OG zyjoqtmUg(+J+6m~FO6DN1U$Qh(}BHY@ht2FfF$w29un9r7G^GqkfY5V&H!SehOOWu zJQH469xG4{q`0i`%<*A>bp#;2W+K1yNOVwO+?fVhfSHH{1T3^5EnJ=eeRAd|a0WsF zvhD@nxg`I8OaJ@ENxL6ic&J}_E(|GoJl@a8o99iy_?fv{{A7zs@tva1k+StIUqoYL zLp4^>15vh6oYh(-dby(@K8(@sQ3EsW0!QUVm;Td{PCdY}`C=`1LO#<3b9K!pa?s)X zcY;$d@!g>)1bS#Gbq3HdT6mTfTvejG4_?ljbFBiG-gxk-vkzv!%~i8Q(b1z?I2p{Bh6#77n$bN5JivQ6qMR>` zPnaa%RWgD>nXC8lv!t9B-21GnXPvI<(XB_HBTln5M2c?gWTWF-1lAZ?MxbM69zwUP zQg5BhjushjaEOdg(D^(c>7=|wGCs}cWjOSWV6<5ct_;On0x5A9D`)!wcN6-%y8(!Z zAqIdp|CxsSzq=9bUR<7T8NSJEL5?^QWJykLJ@)Z#hBDNKzHRb#AgBHNXchwV?AV5) zU=s;v1p>DccED|&v8&jANevXc4B(qh){I*C*fg{i`~Fo%&kb^Fm*?>4DVL;`tmxOn zDpM1a3sA&}X4D%v0JA__r;=yS1{b2|bx_Np!cq4ID_%a1+gd_rw@s+&Ycp%B5L_*_ z&!a8G6QT)`;7h!vwjXBJU)e~=uIo+F^4_${HYFl{)b{G!ONsEjp0dXH-SX<= zjMo|vNq-s%DB2u)76Ld@&>xNz9s~mXDAVtLv4z>ZY-?JR*B|$7vI`lf5WeuQx4EVrSH} zSdGBBOGt|zYNDF;+l8fF43f5)BquA>Z=7(-frZy=WHV^RnViLumCTKuvx#=Z!3}Z~q+eVd;_k4}XC;ngz^-3H%8lc*eAfvrd7K2EJ;*PX4_p4h@=@|M}NW@Wgmu{~KMr zbI=xWIN@{9l|`BMv&#p-zO=apo%)SHmIt;CxY8Z%e%&iCQlPYA$GqS%?wOQYsvIgl z6Nka~P}Jm{j~UaIY;ZOyPFWOBmDfgoatnZ@gYd!?{52eH>~eT}MCQXj8;v((X@H7L z;j?gX2ovSR9o@Oemc@2oAd)lS67xMX?gDHMilfqpp{@cwaC}mzQ7B`hwrCv4nNtYx zv%e8gK~@TS0hD9;M|Z(Hy7=%S1ey%eURx5vget@XNgIROKs$u}KSu8Ft$e?xt>n-OWNB167aAz2uX#j-JH6%L8C{phVkqBk7X2O&mL;>0vowbi(ST zuCqz1*y^}P=h>JxOwWM9Z}&e-X!;iSltIF)*CDUcTfWnWvE=XC(3pF*JF2ZpU3f-> zNbFoM#VGaRHv;#Y>0eHF*sliF%swqe%AF-=@CFCckBH)#8i!ej3?Gi|7siw2*?cjo z=-s39h*R2LScvoM{9F=ROr~3>fCa`qFulK16CHo-y9ry0kk}a2pAf=xH)I?+sl6`?=1RCOyqLv>rpZI=WlDf##u3!^gv5lIrk(K3w+aX*uJ&} zfZVy@g&@sPG=Ou->NsGDLG!wBUG^r2V_t!8INV!f!<$7Gac`sV}aQc69S_o1S*iS%FNsa zCME>S6%&To*Ow8#$f16mOYS>f%JJwuzlg6N&xSMWdUqM}hZ+yy?c`{xrK3`c?X=69Im05I!sQx6!a3iNwm zr0zLjP(Us!bQZGQF?Tg?%iFSLn!#wGLB>u>xumMrZ&@%RA;Mzh`eSL-U2&P3O=Dp% zsyw<8=fE;QQtF9ds<(gCA_KzgO}6OCZv^bX2^Iq^Nvlua>^E@Xk3lHaUH<<=BEXBvr0&N($X)8q!4_Li=__HwQL?7h$V?z!K0?s>jHJXBRz&8k^r&Kh%!cf7Ai z4#+jq;cnu=s!7M&+}Xisa*g#N$<4(yY`43YH9wAiixVOA`S;Jm9>BW9(wxi+?Ks*A zElHd5gMA)b-F;i}PF7N?S3ErNygPL_q4Er=0fl)FXRf>Sn~`25TDMsO5rAXRn^~`c zm5O%Ycjn>6nZ?Y6CeByT5R^#ad*@ZO^f0V?gu-2O48|0m+YEbul4l^kMtRQ!(=G5) zFn&FW+dkLivSn07TrIM*OY~deDz02wB5+Ujd?4QMXo}kO!vHD?*qiQ>ENhR|jN4TI!W*)= zQK3G?b6&AIxS3HRmjMc%=Lno1fbVxnY|R_>7i_g9x#n0!FbPa4OaY&~rr7o1M83OS z4>QdS!u8~fE<<$%1@1!&iy8nS;GuyYXV%l-12KOV{nppP(bruCKAzXmdlenfO{_T9vw#lST;NTbi)qLG`mJN#iw z!yBnrS^DibbJ00$E;4tk%=FpO2rXfsX41PFqT5 zQz~A8L-5B2;PsyZc(+pjRODW_0HJ!D z$4aJ7qM6nG?LkddxR5YHiow-pu4CpJ#!M$jwRVt2ar;wM>FFc#XljLV1{ysQ0Xo9| z(pnBwM(?x1!lf$DDP7GEV0Wjli_Ey;PrTVQJYu1ANZl7`+RLVwUkVQR(>`5>KGP6tjc zU46m}ndOU^HS{$_Hp{T?T-?eR@Qc?Irm;y2IekALocBul3oeE+2qkUA;Hf&|JY}*C zj+l!A*M;t+R))BQYkl~-KySfvHC&NHj9yf!24;|OX3H!3HO_`=@^p1*&Ap-tLAQpx zwnJ{^BT!#c2PA;prEW-ME@a6EDrQ=?*CfB%q+uYCT~T$Nmg#?l6Gwjt(jMM49pp0) zU86Hrc1w@YAaHKX$E*F+J!!##6pm(J(|DVF0zqR3B=QGB=pSZZD%7PBPP1N>JnQoD z=bBrm3xb8p(DrVY^;~PpwAR^Gm9DlvqQ0H>;0u4ajCun=+OgcsPqLm&-EZrqFgtT? z{XF!7U|06cRNW-`!`ku-Z^P}QMk=VK`fJk(g$`8DzS?Gjtdyf?tIR3A$y{ynCpTpn zG0FVYZV%LkQ^VRtFFlN~kYv@+_8+)8KS9~2t6A~jYc8RhEonM*i-_~)@8kS$Bc5jo z8G_vX83(R};bN5_Dt|AuU;$Dsq@5=mdf)NFRkzPx!p<%XEBwMQ@~s1t-@6WJdj1Qk zRvv`t?r)@8KM6nn`1AiA^~cvM!xdy*&!4@~r&LHFm~G~9nymMquH8n71ds&G+0nP8 z<0UhCV!8^faB%ir!2Kp4y^A0E)~w7!=-IqlhDjGc@}nWT>TOvCVx>nVtxEa#D0LyM znF@eL@CBr|n^i}+OiuBGo`v{BPF57A80vRs9i8S9>o#M7nQWG7afoW4PF3>Jz+Asm z7#r`#b<=CD4If&?uFh~Eq&-HGF-$lWWOLZbJm7w>q#FyFb4e7(Zi${QlF_Qai;G+G zNTO`j`$Gy|HwM=~v#Y}AIK4&+urKDLu_H^NoyfB##%x`iSvyj?32AvxaRlFHgC*nc z7Bu4efn3BW|13}X9(*xgMUxfGUHHuZ0SAhwSMono#Yi4a>Q2LHcbLFN#F7FW9%9RsQ5ez!LHDgG00A(x{l-T z<*$F1Ec%`Ipjm#Ca_`>nbMfEu5dU`S*nx~|g6jfFuVCW4ZzMC2>~$qkSP*I(1B|IJ5LE z)LO2bv7u98S&68WC@M*`m5D@HnnU{@S|tw>?R?rNRuu)!-B4H)ASI|oTwbHyO4y-l zOuoFMEL35j+PrH3aj7g9*}VwzyL-Z@ASNr8!&VyYb6|07wEi|maU`2}zI&t#C_d@- zZS}_qs5HzR%!}fDg^h9OaFfrIC2 zBw5UFb$8qL%$Z(QUmfwUr?HAXc?%S^wQQNNQ*+WU$})sbl1Y-AyfX%?O6q%$(^CiJ z#bgVY*zFNg2q$jg(gFEq*GCd_LR1eb4KUHvNDuZIaz{9GD1 z+}^F?X&LvzUXCp9(>@+O(OUAeHCfFl7sD482NpiI0OI492Es5ZFF?yNJ!iYfdA&1h zMzfPzUmV#4>G2|f!SO=;Sf^eFJpA1%pvYGjYk4v#)wH%t`pZcQShjMez4m3?F=Quw z{qY`YJ^<$8{R|ZE>Vl2{|0sm%*Vf<2AyA=q?PFf!-E$$k>>PJbQ@VweDQQvg2-`L&KeVNLDLu=seoI zCHHips8wyrVanfUC?Va7@k(FWsxVxlLoZA-d`W*sy)wM3l>j|(ZGP-^9&R{AC~LZS zM()hJ@g#k5am9J&*Sq`Wv5b%g{vRQ_065jq6sTP+gl)$F%c#FRnSuWYZxh)+_t2l7 zO~~7t(ewa-!v4}nf9l zyTEqfJ@M6fHZ`@EE_NOJqtitgB)Yq?U(%4*r*(SzRvB~Pw463$0~;7XN93cH&#ny5 z9IZic>?wyjg2x~h4jtYpz%Jemq4#ipP`lFuAUjU3faOE@r|b(F!e(*-VNFo6J?z^t z@wh@|8-QY4rMo)kY2WI&pc>ZmoC}0+-&kRwi4>DQMRK15x64 z^XPe;UjpQG54i4i0F}n%&$KTz4%pa77$&f_)0Grs0wMveSOL)sB!CSA@VkvW82j&d z4^$Cn$3Xh>{ZE`&tq(n{cK`}}U(EpoFAt9~rfAmNWBy@Lzt;mBd#4k4uGVGdsvY))GFh_{q;&$1BN8iuu`Eo%tQDohe0&7&Xk%l$r=P!{{0crVZ^RHN*dP_v zBWnUfA2sc{vCNBPqmZPNQ%Wd(p-d!HDN}$qqg6GJB3b@^(E&@Ly|I?{X<@=x_R$vd z;iI9UQ-c`~MR)fZTd_`{7{LfXv`BufQ!j{V+%0;8X1`&tl&(&N2*SyKYp)^SEqx1RJIbR2>O; zsid_bR_9u`mgeZFUQDu*QI9`so8-$7$QA3pqe$I-8Nr9KL9H{8&MlRYzAj9_T~{oi zw1dYkbWylwVzgMJDmlL`2hWc>^(LrCd+5*XV_$HsGgZ&Pj*W`agqW_&aJyB{7`@{c z0+G7w=#&dy?Je!9{Kj(&^?|a13+HIZ7JsM9XZz>0fJ7Z z3!h@=RHXG^`1Cq*>l;l^$OBXKrS?MUA_H1*s0^v)iE4LJ#`=w6ks~od;i9Tg{X1^# z+UL$-K~)FvVbwt8_JmC&mORTnL@8g{#FN)%M%Blas~-F;q0fj5$*zeN z0PBn%Kb~~P7f$!w0i$fTGEH-gq2(GTqk?6nty~Y}^_9!-)LoJ$WgvabjV!+FqB6F* zihHivQq?tRr|XH`w~&U-@xQ6|t2!%kEmStSQ-cAvS7Ak9U8(G8KTq z!yNEoVk*vn6=%U&wB)V54j`BH_g(3yd`&nnIC2}vvc6+%X9#!vFFafbYGD+^z)&{o zPE#)5@zl!dZ<3Rab-oiEa2b+>(d!J}vAZs#{J4fZm$h)LzwOMIJz7|Z*3qh!U(D!L zG9)f|YifXc+o;VC>xw>Vt}Yr7GFDKns$aM*k5A*}Jg$Gwu1WZDgIZctunYtC?YdBO zLSItcuYC$C-T*DPPvFk@F}k2)|7W^VHU9tPxcWxw7~y~JutLAnkr5rwOX+aZMetZt zF^Rh?Gp=a>w%iXI4%?6x#d))wS?wwmpN3a0PJ%w~&m8ZE9TXb2i6#^F+B{_BE0r~u z5nLKomAc{>PRuQ^%#%o-ET|GM6d$hx?Z}05JM(mmHR)i#<8`)`4fs$PS&JOW)hqe1uKynHkT#bJE00pxJ8qg>W}|nFEDyZynlm=P)R-zT_!6Rk&_zXNfzs zkP^%qG-o(mhpEsQF!|NftPd#0EBKW{Gi56~<8^M?+V8e&n)zH9?!odvF2H&-Q(?1L zU?|n+)Uk+7rs;3F3S%9yDvtphfzF1VP?Yjx&xK(*|Gdd{1_KBzKuptL7s5>8=#GZX zqgu99rc;M7EZ{Ndxl!TXT_ptI>p((-s4>%O$ZLqsuG5O`GpzA5n*WT5mK2BA2AJ!w zTa-zyOy~kBAW?bW&lgDN+yz?vhaFUSz+Tf?$O(lI(~sNYp=zE!kB*Hz;hp}7f;0B7 z3L6?1H(9=KI`U0DUs5nr5|^6En%HlVstyqlOx}qLDn$?(+&@X$Or)aXU7S#GeEGQ` zWUKRKo_JZFlFT`I~SgLuCvKvV&!A!e^!dv^QcLkgI!%s6dOYtJVG^8FzESEtRp9cQ-I%>BMPTyNotl4}S1wx0vvNzk{#cM(Os^U9ev1cRV<(vj%-Q zgS&1Q%qTrjMxdvwXL(@AML+u}MfAhD107q1Y@u!6VY?i+y*a|5s3ed^t%@NRp;F{E z?If@>N9v()=Dy?hg?gX0GwSSN=|xz#0=&;ij?r;8#oy;@eea{AHIi+Z<40Yf!#Ozj)M$U9F%Efm*s)el6{<<|^`bgKi z+w|)qxvhtj>m#U&*HaJp-wEB&5V%wv{07AB`lo@ceSp=)VquL2WbDrQh za6X_qe^yVzKV1RH2LCcKD2(iY_IA!T#92nC`<%Q*9~7T5hL}Q4p_RxO8?x4#FwIvI z`%8$C+u2+dT$1IF)fInF81O{zz?#8CZZd0hcGuVM=uftb-Zo@ZXv@8H8GmxFG+o|# zDR)5k1!Le6E-X)PUE&4Ri8fG3s#&fl{XV zJdfxv)uszsaD9#?=a%V&U&E8XaQ@S2?8paXSsxl`A7Krg?o z5Z!+HI=HGsP#hqN1=u;2bGlU&ZChi;#X)aPu8PcDF0Er~zOMK*Qq#qTZP}VesyX1x z=8+P_$agvoQ-|vCwlhMj7A9PYgYiwa3T{ zi!0E-spDR;fi~7Nj@XjT8}^pM<(-U!*rpO41rSE(8xkGdvQ}_p=%panLpWX2iF}H4 zX?Bv8IX$@afG$S1#WJr;Ax3Fbm7@x(+FP2$H|M_J#n;&FK3dvuIFVsCy1!yZVxBed`;jic5vF`dUmzcUXir`Bgsm+sKv2m_f~KNgnF- z7AU2pz2dG~v^88IHEy*&5%~zeAHGiO9D`Myh~fLBuMN(*LUPr}k8?JF`P~h%@QYfq zuRb~9xuk!bh&>_vaKe4B5d*Aen0wBU0M2t&tE_)yKm)_FLWvCE*P+nyla<4xT!xiW zVIz5AXQRk{2OY66R0-l74k(^i*%Jr92RqJ_`i+DA_8G{vs|VQ)&6c~&ZeN?&#OHe! zaX(o=M#GK0j=V+328yoGF0F=@s*pt72D3QUy=5iGM8xxW37gOCE65Fche%FkO~;RK z!oqvrL6In!-=HAhro^o(kFdC$JlHiMUfLd%Uv`%F8}?U*AK+=1h-){SpSkiGg?dC< z?_95rT>BxSvS22kk^Wvu(sSfTIVb*V94)_Zsg1}9SYU0qUo7Lr@)D7N;N}1tUfr*@ zYr&c#lprg|eE*N6?9cy-&H99DK%0^QpWbz?3sppR=S_@Ik2i-^=i%Xh!e7^>sm!)b zFWZP%{EkOrk3L0-1{`d&#i@Db!&Sg#ULl|9FUd<1G=StOXC;OXmO|zcgl;L#9j(jjFn83x{oqL&vtn7KNXzq%WR z;}H_#hx7D1n{Aw>!7O5@>?)>g>~@(|bZREh6~{gtY3e3czv7R0baUXV&*E|rb+8tny+v|-=gYbp}DQe<5C;o1$LFz^E-5qQ?L8+0z+EsvNde~L$=imZ@olYWNVx@Ho%=G2=BdxV*g=t!GB17f7lu`>%>FGM7CANAFwWs2tl~#_qI^b~oR?9Px_fmI4hv z9Eug+$x4|=lJLQ^SbH}kJ|$0^2HcFXj|P1JJ-}!lf@+q*#(cHOUpOr6*3LrH0vn!F zHT4|dfNh=}0X0UMg|YSVabKA3!LdpnpVLYO09OPdf;nbA0+BjjWdRQy@SL9ZzTw>1 z$ozm;%*FOm>)osq2YV61l^|l|b;a;4_Khg8v4{i`A40rMyt=$GQeOPv!pjcI#e#5& z>Q7&rUPo*RNUtHA6QT&;IJ>2n)m1024I8>WEiH?51*CQauF;b^zSa%!>WityW%Pyd zy0w(0Z^4f?9%Hm!Ofim`O|Is-29s8Y0Tk#-eHFSb?Pcp2z>#ZkTpg~8+96O0c`|J+ zL%7qN|6)PEg)v;2!|u-FYd(dFb`nAjY6x;Q^ssasv1t`hvA{tVyqz;vI#Anq6DzA6@wc6J z?2xA|W?w)d9KU=_nsif2n;nm8Bvut-!}y`J)qfKrg=eDEZlL7u0dTclrl6xJ(NL*t zHclb7ySzM)PgNJnW5j+QL{a}5^(AL?<)j>Le}_>nGbuV! zQHiFbP~es8l&dQyagpQHk4(T+q8j)UWx*OZi$|+cvHcJ)yRYDnMBf4(GuMGlfwW=InD@+CE>-U6 zUk`_2lzgOMy%7K{@+B}%F@4k=ws#0|1vH5Nvp;^8%#OjxEWgG|`BVTh?B9f$u&lr; z3qWZ+L@(nm0B{Y~k)%LBM!|AM)wUiS4HXrSH4{1t3luV^k~gJb$WpLT3jcVDi0>c> z#ew+_#JcWt_=C`FpksF6rf~pF%j8PyYI=(y4DD*6ZtI9E-@kq|=i=46cc9dFDX^zcuJ@M&>E%UprUATdZ zPYHWzcfoH@>eC6E5#&}fg8gJ2EpdJDqw!BsUgQ((*U3Qls<8;4E@j@*M$ee-z%K*AJWnj5;S7PE9y zd>EGJE*PC9J9pEfp0vLf(&W)yV!G4xZ3h=*N06CsAY2~hX)Ydl!B48@p}@IGFk2|8 z>?@vhlIX-JkFklZ5i|vkV5Fyixp2+3+=KC>y{o``)P182r9H(B4^AfckD+*LN`-bP zh#wnz*w^JKr)W(AzA>IOY3)!Xl`W$uefWyNTQ=vPi-gP2?ohR2oZ3|BzQkW_39qi= zVU6R7Y;`9NwJ^co!hhWWV3hy-RuDN^>Fot_3Vg0zbxk-jSQwyd%IpT@^v`Pom74&d z@NLRGP(ykt8Gy1kL<6=PUw7#r-{XJeU1x+f&oF*H#*(5?8+_!z#i99^uShKoGpq9R zeaCa8NQ8g-j`tTs$B{w8nD$xSJc0Z9aSBiGAq@n37B>ad>PC1Z0SJUY9|vZFzYaBN zKV2Z~YkmYfO#N?p!Vjhz_ql{^(vP+FS^BH-6^ZS)N~1ie(IQ1nA|#HjVA*wmqyK9` zZfm+$(tK~9$^RrQ^H!;@=^o(RJHRNslyWdCU#k7eGDjy&q%(ZX3ihZQgI|ZAPnz;D z_i%=Hv7c?#f-G{>K1Ld9CH1p$M81BnKC;3CGiQ35#{u8OTt>R{9i4$X_v~(9M%rY^ zMaG8_X_nbd0+R|?%F(JU(-ZmS6h)}Z3(q*Ec9IX)rJ;}HXw|c6`fa9HXR6Dm4FZ(* z^iOcG1q$6;z-k=8c9}ru_^)AQleYP#=u%huS49fGra%7J(z4X}fI8-7p1>u#FtHwc zV`?{zXoPVY6YqDt!cW`9lOt%mP!Q@i21fecsY}G7I_j>szbo^T3FX)};$&B|*WHpj zR~YZ?#QM)CQOWPbCZyH*mG)4=TcdZQt_0SU*W+qk@jOm-AH9qoQtC^r1FAXuAP`L% z*KeJ%bv}m4>2mI^um5m`Ogwj@noqvt`FhC+!zoLvZ2BJtD)MbESg-}q`4$A;9SA{& zS$Ah7YWEHKW)cmhtGSkJXz%z31L|ig-XM*g^Hv_oK~fzTw#_fZ?l$N>yQ34)L_%tB zEEq-=1jXk`>aH**zQ(~3lTi7zv&*wJ^7 zd``dfxYK9Jo&L(Meeyx3JW7%maT4K|u3S*XK5i$szP#Y^CaL|>mY|B3{ipvx_=v=; z1v-B{XS4$)(wkB8xN&PnLm(ZLs&H=AO9S+PnoFoeNy}B+z!n@1f2XWneDL%3jj$4R!zq9% zp#jHp3`;qV0wSo(wNI0|pq>io7u6EokxO&ZkCciPclz`Nv~WNjl)js*#voQHYrUQu zda-iAEaJuHZ51i|+E;`T8Bd7mubeAA7d2Ae8_kSB0JoY?WE|1s7Nv?wcbv;Y_7lDd zO*+TiPIqjZl)iSw|7p&&0>feGxrL}9H%5Ny+pdwArFnTze{;QC{W`K_ob-{;=z_?6 zKHv`yRVa*&K@t}2?!+9Ij;^j0ntlKh>3jA!AlW zQF!$5?&Q}``=mD`@a&AZ+UnR7axv-Zs?Xb1Yy=+-y~va${)%wC&{w<+yAtw7mT`1j zX%8-hJUD}(CB$EDZ9>4Rx>MO%*&uYsoN9L!e8$bS<*~sS^s~y%w-bJPwh`3%6q8D< zl(yLU(c$eNek|BxPL*~*p{g&oLp>%HWL*-gbc=^hTwGsE#3lJo1q*R$5qd2uqX55iP!Ko*Zl6q>`z~${PYUk$@& ztZuFL9GDU#FmS4=RWjfVr)8EIJn^!#lOh7ojBcS`0oYC-qrp=p&bdg<02M2{C_;fd9)Y5#f}h zUZViZJX2fPF{=z05Kw#oxR*q$wJbn@G%EspJlawaikX>;km!;pu(FW2nr9Ga6`aZ68zJFV58o zW-dq@N!~VTMZSS8JnDt5H7x)E z&sk_q;&;4fU-EgT$d6>(BT^cqEi^Yy5M2ya9(7K&Kn^LRWYkt_jQ80oeFST#e|zLZ zzSl)xxG%AKo|h?GhFz7?oU^;`meA}X(PX&_O`A;6IprxZ;(?6~(Dy05Z2e3Roen`t-!6-I*$ zC_`S+vqqnVHCNm0_s)#p`7+6y$@ug;UNNlasiS}Ou_r)Z0uaTVc8}ps1ihauA4%fC zdmKCd*hIqDu=VbBU;*N>RqHNX%XoPtqmG;RUGT(_)6G=sO#ayp+%$G9c)-~*a~zZ) z4jLP$d+$TGGPiPP(%h{H_VQ~pENtE+%9ylsu|;UTt8jadi1Qz~XpRx{cGyHfh-3>` z{bfe#_M;J}3qY(A^tBb)v?miV3|b%u+-@V_sH||rubNc54mj?8i4pWzelDy7)OMmh zz82RQtn*vU;xBLV{}hvLb)T_+7eKSy?xl=w0V-Fe1Rc}#tv_3pA3=-flx}x56)#+) zItrx97Vkmn>`OCg6`{lX`GwsUYePv(n!IhS7NXCI2<`enAu<5XEfH%Lm)*<1KCyJG1!A%*KHp?Rp%Mc;zXV-78 z1R8(C-&xpoFH97+)=F?T?Se@XDX)DUFGC{&%+Q&OA#EXriz6H#l&f=!LmoacyYzV> z#|S^sOgwi}iYvzioZT@uW#b(7S((F+^F>R6D(7jj#MaYJ!EgZr_qIod&HcJBvn{-Z zCse#nXR4$MhwVmAuq5??^S6J#AugI2D!wlYxniFPywjMD*O|`tIZq#g_J%>HE1HYm zuVng2u?Jze6Urv620CdZ)G4NaZe&wvN~-u9^I24P#FP@t(jLXRfsm?sgmqx_BAa8h}KmHFZ1-@L;=>X>a9UhaVKh9I(JDxYukIp*;I*$VH zN8fG&ean-$Q`;w9A4>Mn>?|MJ@B|iG49ALl(Dvb&C5V9e@5_!!h&SQ^Wm0{Sd&U@j z>F($ql{hIIZ3mJsgOTF1=R%=D82Re0lsN>|ifeVtR#mE1e`C4p2x93GtNG*(6+kL% zbmD12*{7_&5N)vPc`)U3KKUDB9eJ=I_rI^4`5@X{dAJ0;JpftHY=n?`Z=dLmrBwl8 z_Q=Llt1Mm{_@ARqsQ00ziDq9&1@XyLaYHu{eIAt!S3AQp>E|KM^Z=4Qwx076Fj_q zigd~ibB?-^Da0BQFE%k3W&ne@uMFzoi*Nf6G}qHLDZdI@pC%?fK8dXc|M3>Rh56se zyf(x{9+)1!hb@=Fj(XEKuK}UCpH^T%^KajM*UpWP8E#>UmHh|KZS?lnZeE zZe%=ldd7|KIB0Pb$M}I#)N&>pE4rZ4Jf5+9(VS=12S9K--zh^9tc}No-S$#T<{U6h z04p(Ynt*K)C z^6q{*k3)wxa?R$T-KzDae;uW{D=ev?Y;e;iBokh%bJ%+6Q^0*z%X`|7@8B`U@c$fU zML&jIxmBJ1vi~33zYROHAH$9w@tIWTN|nYX=`UTpH-B-UfV9bfXCB|@GI*a?=Zs(z zK)~%qfxR1~z|c_dgoJU9bi>Zd!wY-34mH4mE0O3=DL6knYy?nr8x3oG13RHX?gEoc zIUpJc0rEwp7><5e^F7EuDS%}LLRGa5O-S!L)OY>I@$U1J9i0z?wiQCp3gO zAn1SC4nn2$oL%t+0JJ|Fumi3|iUGcv*U!OvK{x;;G7iYz{n9{o%c-=!+MKkZas1pj z+J=`tji2xzNnOOg;PNC?CLmdh&u#XqIr#1O800fb67;Q(Q> z2A~jNbejp?$q4jo^7styA~0sJYx4o5Tn`$nV}0{hi`TjwZ{$NuRU9&5!=!SA6>tk3 zRaqp9@=J~QM@Jq!OGI4O!?D##iPg>vk=#bNE}AB#h9kTS-rm7pbysMbA?ca53##__ zcal7}zJHD`f~y3NP&II?D`Pm+&Yp=ob7G6_q-=!qSQgRo4b5RxSy6EQ1I z*A{Q(xxU_E1=sq(XM)yuo zo5ffuBAzhyyM=(v8Ui^ZUH6nd8xVTJ9~>!L1Wn% z_V0KABDib=?7hGEIW zf&vO1vtOKo|IUlvA)Qv!A7OpMWG|lz0WYqRWtE^Dwu*{rzsu9NlbZa908BdYts5#TN&#Ra(B3{PfQ8oCao}+Uijl<9M;Aot=2A)gtA7?Ca!K}aC8b8kn=Wg1OS64PnitriXn7=^j-zLeyXj52w9>taw& zf1jh_aL;ZD_KLGs{4UG^7dP!0L%BfrHxwM8VtLW+Xj436bq{m#fa}Gq&3zhFsAQyX zrxtJf)_O%$n{s=xn#2+0<5cXoHgaN_EZtTW2Ej%VTEIDaW9F-}!!p%If6Z!*MU+$a ztL2D4&BQl`o#~pH;5)T5#0ORJ&>+wEFUHjm-KVAQ;QN+_G?kOcS!iOM$;M1(?E5Z@ zt6bIa_*Q2(WDI|@|HW3_jKv=a_L+`??~u;2n)2!o7e#agKtYZ&1F@8<5-K`WX{eq@qDL-4l&@Gl^@Bz7$(gy^F{#BGFd z2SNN+56B{!C*Vn>)IpPcI~4q|PiT|mGIRf?#yJ+dh+_Rk+mEhK^?}ZWf}wnBDl6=T zTip)-33W$xiQ;%=@RRb28-bnANrP%{rMlIRPm^BPBQ7nE>UtYs!X9#_9qz}J1pb7v zQ|k3<9pH6Qxk=2r`*lxw*6AusWHU)ae4ln?7_-Oa{4J=2zea%o^^?F(boc8lH#0e3 zrQXf@j_d}#HmVOj6^=Kgia2tm*Rpg=E5BjHFPo=u9D?NM|5p#2vaf&lP z!#cCr1MouHG@~L@HL{9rwug-EFp*fUr#`f9Rv#A5-Y_eHHUjrrcM8Mix>PjkcW&g` zLs616U8Of-zi}BxN_4#Jdvl-sNGn|0QP)6yc`d?zch&u3O^;`z^E3J0=c?Se`O^CD>HiQao1t191NHKizY4c776& zV7tOCtRe5z#0?$`3Q;h<%X_hlTGxnEYE|&RwaFTy);mb~t*)N9;}7XIV??3(YXEMo zi%N5d@A;dV$5jI_4$eFBOym+Nav*2$_QJfL){4$sG@WtW$Hb%pqOP$i=0T6kUc1yH z*jJy$D;|+GCwDY7%+-*ux_Q1Q>>=!dT##?{kcX9XP?D|n!!1;-k9aCic6cd1+ zo0AMmUAE3-4eD{ceolZiF6mR4kwyGQJu9Ys<_jYUa)eK_>rRfIX}8pv9!gi{qO?SClxLt-!g4G&5$Z6otEZvh9AT`oW!W_sj^M_&agt?^H4!`4-P7;%r zD7<98x)?HT$(v}FHGQ!9oPHvS#NZovTn_!tMCtBpy1XMQ;i_-6~6fV*%75fNCWH)Y;#Xb2nCJ~(Y zEcs3F{l#q78i6|{Nsr`2MJB3e1_#mLIQv=eD_)TgPrQgc-$;GFgt)UK^3F6}f7;Ek zp*l8SCN(!!tN%$2!T2|bf6Q09^m~_QD>#A~t>##|*kiZB>jREn3`+900w!y&uB&z< z*_{Uv!j;DuizZszkQWp4C2!>+)YDV?NHQfX^l6#x*2-*j;=;noJBomR`|Y@rLhcr% zgs%VKeAVNGhv(iG{CcJ5(v(`~^-l3rb}h~GS3Q&O{C400{_04SEiW2 zK8?grOR?`&$4AyQcPg5hh%pRs-4@!s(Xw7MqGJJNJwAb#fz2ce27~B|_W(e`^7Gm_ zWs}kB-i_<+rfx@1W@p6+X$TWF4+Rf>hNo9VpGOJj6!>WjmqD+RK1xv^+Da=eeq|eJ zWk`fPbL=I_Dyn6e@415cR!H9DQL0-!0aOwOnYFG+yHpY~Vp^aC;)gk?9*?)uNBSnsS{&r3T$?gOGrj*f$_?m}b_=M178A7RG=kY< z#{;96HEQW6Y}a9$f+&w5WTs<)(TZ>E=eEGt~vm8(#xL*d}tw-273=|WTJSR zX@dpB?_>tqisQ)OUskNGCz4%(Ugc@n3bMFCcAUIPwf^&ahRDS+vq!26xO*e$pzw(mH zTy-sYHQ8hDO0Q}|gIjqjc#DBh_9*Ph#N$lvlEpxz@p)|CQiZQD=<4Js0LjBBpR{ic zfFA~*h01c@6mQ@YAf_jC?{OTiWto4}2S}`A7+9Wkkz1|I!Y-d0ih$S&cM$2Q%E3^X z>@uA=*6lsrJ9%*&HT`Y>N@ZOJ(qiKO6i4?`kr>b^lJpNeWVj%Jp3qxB0PX~m(e>qH_`tncPOz7bb z0PzW+L%w8x$FoY0=6eo>l-36o7O23n!k-6YnIApf-C@53HnM#+FGjHmI*jkzpAsB8W8e?m8^nDHeWpH z_Np;l3~=1n!^=nhx!K&P=oz7;(U{oxAQU_f_qGIB9fENK3V(VKYj=fzckh3F8XWBy zY()ix5dvbbLJoiq*)Q9|o!@q5zC=n&r4W`9xQIWucpK>rK9Pp1!IcdLqD(>?4*C)d zz?*VeD~C@96fy2Lrq0FZ2fTUp$off9XRB!I}*D&n{bh$0I|$fVYQ3dMO2iO`=g_a8O5xdO49wf~ymG^XOl~ETmzTwfUWrNt zdpLGkLYe5Kq~Qv*P$rUt5Lp(s2-^adXfA2RtBJb#Djd$MxmB$_=`8Dl!N&ux+k-;e zA@z&{FP@bQ)H6awVjaj#F5NZ@6=i+0s6faP=o7cocEA70omB|wK@F$Ux6tu+vFMbv zxpG`nEUur~?PL3DMXn=`mbL@;b{pV(e)p5WDYXE(q6SD8VgS0TQ!>cjE~_VmnG0q) zLA^i>cz3P=ms!Mi<6IY$qX8+V2%SE$kn6^${V6_>t&Tu}j( zKYv^YM=Jxgfdo`Pf3&gmd5a0%cL>>M!=&S|{68qPt}>Ye9SQ+-Ljh<&$WIke@3^ze zK0v1Ks&~U&vM*Qtp0DYB%S_GiFZKsMoy~j6;jC3kSx;&5SZTt!3F%#e=pB0ZhvA zEEGiw)NS!=Znry?#Ja_L*sCpb#k&4id*2z>RJW~*qNqp_q)1mmX#yg>MnFJBKzb)C zASLwPqaeLW5Kusn4njbBCrGa%QUam(4hc0thrF$;wpGK!$?uk{xjnQxR}++Vt!KpuvCI@`tA)J(*FCP;4gY8YFZ_yhF`eb5f>in0oC)1gtRXut16PFRAj4F>!-vVq#U3}nK* zGT+rhXOKWWNvxk64)l4+TxMFAPO;B~9(!n!X%+npfaAQ?YF7w$GJJ8c6oXdqV0Amc zM^ygXS}4lRVUWMhrJQJc2vEKL@{j=`R2+qVNdTB&(TF+SrvN44zqLl*<@wm4+v@MU zGo5M6$(3Ziu%CY=+w5G?;ZojCShqJeFm6%eB!6a55yH#!(K z^pa!@C1J=ht1&{z#!KXIPI}OKicygC*~H@^JPSk@&26u@S&$JY=h8tMlcFZQw=5Z2 zZlS?epf&hI4((zvmCdMIYBbU49sPm1m`ZVjk@icbtFm)9lYB8b&F%-qlVBgQp-sEF zw$8WEx2n-75{}C=A4A`9Ufi=K>#4MFT>znb`Jo~`lsBG zewQ0}fLr*x+^~$mfN(_&`5)H}BLbxbn`e#vkV@s~hDwM~+exYM`=}eJz^FyK_KPpt zHMlHdA5b{Cvc5klidp#x-i)A|9c6db83##&;*|CQOQZ&X4lUyE_r17CUeSJn#09e0 zMjsa)5}UNi^aN}aeW7RP^OzL0ocM}T!iiBp@PqiH2b zRs7wD4EB$N<-W8eA7E{to!#v&<02q}Y3Acc+}d@KWwouRwx2i-oW$z;kyPDxsn!m- zymYz!I3Ik2OKnWvlPMIxk%)9I7!&sAPbtqb9E`rD@l4qvz2u7e-78)>FC~TYCxs^S zXEvCRHX2E#dS7eQ65PJiler5Ic^r1pW7?lB>ipn!g)nOEyNv{71nt(<+Ibwg4^*;B zs`O1x9?*k>cWrMj8b<&$>O4z*^&hyKzfw@S zW)cn(?iJhyLY4~U4Zb926kX|v-GsUsh7C0~v`NU7M~+1nU#AMPkFhSnhB z7-*044|P~c!%v2Axg&@PaM$y{ywCqEsg5Dk8Ca@^l4Gv}>04jd^#Dd4}lgGIJ}M@Qn+G4hoYJ7Z+smHh6tyB`?s#YB{HprPM#^zzc! z1jSA(Nw${edaG7yhjv)$Th!56s`EcgeeOgI66dwTvQG`1BfhM0O*JQ^!C7Wm-NFvv zi$zmoi|fR4M;ecaYIDbof~H)vQ4Ddm`W%W7w0~ed=TNI%pL^aU^gb$8=6f7^U7o!9&Vj)pENti<||-SmuTwI zJj)k^QzCoonS-({R#jQO$`+4xz-MZ*YDhkzQxkHQR{4)BQS z*SBu*NuI^XwIn7d`krh7`3g^mHbNpM+9giM4cRY3gxu+ash~3Ttrf?t_+O?C6}(&T`rz-J!@GDYCG6j=w+A zclbQS7=I5;U2sjZy-upoMdCSAL_&E}j{_xHL2}1)-j~!u!zX63xPcx9*AW~m-`zA@ zow`)cLT)8Y-ySLnVx$*BCe$ppVaLlD+O%o}Egg4zZLqPe2|Bj+_A_wGxfa?1Sze#2 zXL_dhqbHulR55&X5Mw6_Rm#sSSDng33|$>J@7t7E9Uk6B)*N>IDIU33||vX!$Ee)iqErI8Zr7*BVV zn|f0*-Jz4W^XGFz4xec<`G>BtmzmYuz3nwn9?h{|vGdi>^`u-*5Rj#a*%@Wv;6!+(&e`GJxyVEfdF7UHheZJKU>i; z&kBEjaF2y#jDN>fsOo+<2S_lK;_O>--1up+m0s6ak2$&U#_-w2D*JV}=c)y?OE&0B zdZjgtDmgVC!sM&2z0TVN>Ptot48ZU8Qe~FI03X z=x_!Njm*1zdJ+?XwAIs1&l_!>kZu6dhKvL0oX=9fkonJ?(S~}!;V&o>;$y=B@A;AJ zN?hrTvWlu!u1y5zVG_BuV)8xPqN?F+8q3$tk;$q5l8r#SB_@j5{p zMpPOlU*n`q!Cj$OOh~6uH5awF1}4QR1KEJV{(_ONX_u5d$yQjIs6TOy?01On7moW_ z^`$uKIL6iMR2&yPQK?l>+c3tf?TJ6&?h^j#;p{@Xo_FD4R=QS6&VE!2(R)oO2!EHG zF__X2z=kFjZSCFW&CsbI?ek!7{Ah_Fjcumg?As5SKZe|05RuGGAXdEUT;@$Y?Fza$ z<-;;4*lXI9n03+~^sF{);L%sRg&iHC82?aKsSq~r-T5f)!Hh6%$b;8$bLu#x#Ww$I zaB1y?G48;kM*Vl7pLo9?xaf$#0R3b(2%&>RaNdv{tHfF>1G`YFPDx;4r6`AHqeKZ^ zEUBD)A^mK^iN?tToeN{1=(plJ8k=(I*bxNejoLRld)bj1)>;uWs*&8I?yFO~QS8mb z0+t5$;W$G_yQlSo-IMTIk}I=O0t?*aTbJ%AJ{{RGvTug7wq(pGAxE3X+5|f4rjiU z$&>ep`54GIg3VSS-5&OvlQj;GHEo#`MeD?*8wtl|TlP_r zJ4$MHN^W1zy?U1aLy`Tg(2AQ|g_m@4=#?l2Zx3yf$QLi=8*lkO-!drg=Ik;C&r^SB zdlX5pTY~7V_1agYO}BEsm`dwWbq_th`f8&zBI0%sM~4DyvGb1U$FV6P&3(W7KwgIG zirMsGs-=~{_+e4EZ6u`WpwF1k5h*w=eAVWzq>q_R|H z%ME`5RCsJa4#HH3W;VJ6No{)R)M~VHuI#ry^I*Ws+ySbtodI??xDVBUHq6h$g=f8N zWH}ce@!h-0%1ZQ#aVe5lx^?Dl*@^fBTYX(=LrvA~^IqU&K|?C%LzOw=&$chxTX z@S|CRuf6ItCn~l&+nhppGTU-Y)7k3v<^swg{8&S669J@wo#x_mJyZaP0r-seeuE^P z=+&?Hop?31D=hD?vanw{STm`5J5pOSm$;ONUn9pTZ--2^)q`|+s5>70BLMm<)WPuZ z73fTc!4$(kY0?OLaTYl~3Jn-G=Vihs&?wD_<<-H=>StD{NeOE4w+yuBz>E80lF=;Vy5&AP=`tmN#RwE)=z!iPck8qB%d z0fZUNi}GmqFdwOz{32p2+8U!oZqFDL*hnzdsywjshR?@ZGw!PCEt65E{2FvauoZw7 zt!H^;{jsbxkI<+u6WJ)h`Le&X zZ(YnulbiKqBefYu*BaalVBzZ#fjNw_i}o>b4kxmQxb^`q!zKtX>?7(y0uk~dT#Me)AB@O^U(P?LLj_UDQXX}akRn-S zR|$dKM#*_)K;W6Zw=9@GnQ8WAi|z@vW4oH$D^cqjmaI+R2u5|VmT7}#@7~U@GWm1R zF$LXc@|q*d5wL31bCg|kxR*%f%jOV7ig^n-B2+UizLvChuTrmzct zAC6<8o>=Rbqpl1rC|E)yvG*a|lr5=D<53s+#by+%*-TUHXNn?Rd%-41oY?G!_-CYH ze~;?+h=5RCf5!!l+7d<8Zv@%g99y~~ zsn&^k)yHxQq9n5dFH)+epWl@nMl>0Fn*dXAV^H!z&C}0zi$O#KF!`|ibGAus+e+Qs zk1=hc9~?8*H;a1UC?vj+vVIo@utB?(>E7;1r8Hi3t2pJtz_BGnm((4ix^OKzA|d8E z8%O)y3+x15QGA~o_%Wi`7mhRJ3@b-ZO_D&BsEHMuMLJf}GJh5}#%~0CS@sVuC0f*e9wuD zxqREH`oV&8#FPs;?d~R^)A%RfG-G zEsrV9TyEePt^>h=ES^htE-=1X;r`Z){E)fG*FiQNB6ea&J{g0^-YM!je!$3X>PA}x z3Uzl|T_`Obcv4#-7yo?xe%N|&#&gxXYz|^egsUDV^&E573NW9#66@m?Ci_)qq=tja zX6lmtwsZ-si$|ns%hcw)KdPts3Ol=X_PJ%Q=wI-ATC`&0{49o-%vigXhd%cu-EaIOBLf#M@9! z4Kx_o+gsRoRBW=-l(-bSC3tO)PVm!{Ow~$1?bzC+(ZMH72V5b*mX_hB=Cw<|SfoSw zfC^9T3*>ZXHt3#jg@beU4g#q^|A&e*M+EIbY^N;h!gwa!9;~HyXibgO9Gsj%Y)b%u ztPUTjHA*Zr^Ho;AK%cHn?NS0bTU!7a@-OdCBJCG(di7b-pH9MypkDx6oE3pp|MmSk z1S&GyjM7|Z`K9My-!r#W?)cr3`u|-3GyXq57dKgO9Q#6uB+Qcy&i%{BmrVeBECxtW z4h1z;^vW5Lue6Ra{h!;QjUVA%vf#luaxM2UFb^S>LNu~tQaQv$trGIu9;-n1lE&Rq1 z`TNiIaO5~FP7Y?=k%fJNQOP{($I&BgWYJ}Ra!wRyh4`Ip6@Wg1KPWiNlZ$iX{om$l zrFPZZdNz;~ty~>6yq7?sB6!nGZuQZfr5);d#Kl5@hHok~aVbdBs3HN0ieN$YukJ5v z7gFmf7PmjnAUO4$e@d({j1m5T&#mkbazqRzMJgb=_aqRZHFYSE|K0@~=sc?f&F3Dn zY)?IfH%BymGX|4%U$1?Ul*nEYMy%Svzn+z@Rk&~z+c&2V=R}Ac@?ozYC?heIhXSf$ zJ!bBwbt>a*Cs^O)QPgJd>9n6IR8a6X^M$kta{8#xfFf!3CwaU{pMfY_(5O{6^qZ7% z*(}_(rrlrbF4J;C<9^yHJ;(mW?l3D@8@wWFBv#mlkP@wLy+Oi?hHa?7PXRSo-= z2VPxMt$wAFby0s_$A<7l^$Q#C;iBJ3ScUeeP>w3sxo&0;_{!m_p{?K}u2Sf<*pFzj zE>0UL70DOD6Q*MFYp#N3e*FLd`~LN6-siu6og;^CpM^=pV>9?ai*3JILVrFtW32aJ z&`PDNOzc%=(kKwk`WH(anf7yy-=9waG8}#zPd6N{Vs8Q@R9a#;PpNTuy~rf5m?SQU;b9fFzLsTA%`At+{2EtE=GMl`Q5k zAnMIpNRic5U3r+WzJXJ*A_xdt~XzW+oID zQ!hL3xTP+2t_)cR8x``71K$=PJMZ(Nahg%5sD(+w5bYMj7lLCi zSQWStvSU{^Av?2~o_Oux<5$YnyGudKTtb1;u3x_qIN9&=rCiQaeLV|jt5A|?s05?i zJ!e$xrdm=M2lm2m>o9caJj$HU+ss*Z0uc`tW*4k`-U$zP^gSJR4{%fcD=buJX6}9; zx!hG@5ff&NO#d)&2KKzAvC18B|YL`5q#}K_QcG=ZqkgRc`Q6M;K< ztQTXlORL?W*gt7kx2`N74a4n$1g`T3A*r&}{$T!OgBx8BtBwl_6j@}sn7ErSM?Ra< z$B<>6o=sTyd+0pW6$x{wEE+z8_|p;&hJb$_@hv3MTP#>$JBiPJ&db48@^+P zhK36#Y^26hN2n>}+b;kP(%+w>76FJt)|9cC&9wXxYx=mrlUH(7@$Mx}=dGwNbrldg zc*Vr%A!`CG*BVD>uhGP+WEIvXFSQVQPXo2kUKU}3D(>0<__ATyt4Xl3EA=p1gi}fX zqbK`9I{}`lFeeMb%wJjjYzT2MEM!?c{KPN$ot3_1cOl2On3*ZM;lNuI)Dtx5O1@UpKtk3DnS6={%h zGrd^mXnAYTw7+Zy0E$-9C)8G0QQ=ZjtJmiMdj_iF%CA`z5#)zfoQw@~pT{Fe+IXnvk{jF)T4W1!RE~_oCU6m)EOEkp=}#} zpckFSoiYk1;OTW=?-{s(lzVf{Gx+J(QAa3=Kuw&)YeqjhI2ShY@FSD|CpPUtA5g@7SnF;oI?t#=BD6h)!eq&>srCsup{$2oRaD3}-xiM?P@QmC&Cn?6967Sm>m`)+Ae$K-rpm*8pdy9a#oc!Ycs2n7 zDCpZhA2!4guaElNnv||q&7e-ZrruSEf5uZ=_ETDCS)S7sJuKk2=E|ulfU~r5IIh-B zEjEQMa{LIq{u?iK&U0n7^tz#v46~Ta-8ohwhW5fFZ~EW**?2i9BALtM6uag``m^1p zh2WjI&Z2q==7K4+;oVnHsyuIPW{|`heC~(kywd*SFZ~{wfIDNDpC_s2n7dk2j*It7 zuDBguyJD^t=@=cCWao=X9ZpiwJ_Pkb-)OisYpHNgxF8Mp4(SuYfiB+&=zQ2q2ZSh| zPntH1pJ2@>rJ2-nPZZ=IV`&YC!LvY_L@IU5l79b2VKAyDYy1}Lg{#sC&OYp7y7W41 z>#zvR5;M^AK{nF_?F8vxMow8AB+{n=@G7tS5$iMv(1F-8pwRf)yvIvhDtpOrMmrgair9my=_t{nk28{*N9#lKb>@63ppL~B z#c^Zgs&Bx|1TPQy-2s<;Cj)M7kBy;dhcF`OgZ`pdc_1qhk)iQPiiNP~$La5XK=Kj3 z!CbPZycl`9OF>!TWHQSsnBmf1Ao;OOozWZe-3uSkBp&ErTAMR`8l^2$T9 zH&p3SUn+-=OBOL<9enc9-(q{t>XbwhT_eF|UDf0dowCFm0d7Od zh(DMmXIyvcToo0CZHJ ze8Pi|!|;NFY>7-men-Mm$Ici6pJbBcZBa=rsOeL<{WCm(i^_hlcqgHiw{p}Pw~JhI z^x?yh&wv6qOvdhE&ijv6Z)Ya1nwcma%57duPM)LN(p|(Bqnm71r_^J?C6F-}%h{yP z1gCschvRy(v%0`PQk%eo7cFinzaAeO+H`it#!j~!4OnkOfY5{WZPpFtTyVj2mKhR; ztKMW>o2hfqfC_rpD*qbI!0P$i3A#axt6D&U)dGp)w6$sM(~N(T^`$(~u>Ne!ldi0a z;)y)?TV_rj>W_OI%CPiOvB6lN1p0T{PIX}aZl7sCAr(d(EQ0aZi%XfB@xL)C{z<;b zQF<}>4?52-q~9$M7L%&}TP~@W#*B=m^R2Z{->*KI-qa%gTw<48Ps*vpncb(I-e6_{ zn%yED!N!mB>{Vxr%Mw|^HJ$}NlL{|+^&ra6XgW4FnRwdo+zS7S$#u+2KZZI_9A#-c zb4sLG9L-}y14S|0#+er>^X_es?ANFqQJ2a&SNpX=OW^Yq~vJ;YLyGpyTRY0VH!;Cg%lZaR7wROED!;B_-g`0ajr@X!x(bV%!> z5HQQ$szz(L_9|;JcxH>MYq;%64b9t1x*KMa0;j9a>@tbvPn|FK4u?{CbbqQ8P?8j9 z`YILP)%wYh_O5nwI{`_lslQKlExZ%Wined# zBe1HuR#A`PDR2hJ&EmG_?c)mB%*BXhI8;46Iu}N79hR^p19-Z4fx7|8{BgdNO$1PX@R$5-;i_p>P!&saAtBx{Yh`$OhaM+gNuX1Wb-s zGJ;}icJt6Fwi;ZH85zjg=)+eSs^J9M?~)7@Y6x5ce2(1Fc#$7GywF{KavWjRH-b4= z@M@4FaHfZDTLR2yGJ9dZKOQFH=t?mD)Hu-R_uT?;O{axGrmp^v25hYnH_t`A{e_b0|qzQfLn3kH7Mj&!v}8!~3H0Kn?VU{mTf)D7Ln}xi1mA z2_F8(O*O&o;Ik^zo)1w_RKd&b+X>FS`x&q{@Y|@b$;tb>MG*Eb0VCe)*{pOI<(qWf zqiu!VPe^h!lENZbG}pPfcQQWyFiAK|uEK;B#tBc84Rb+Hi?IX}N}`}Ipkz637gL+J z*h=^<+y{BX`K6SkEpFDToNM;d4aP|)-i)-d*&Wo;j^Z@Bv&jZyktl_Eepa+zFN$u_ z_0Q4JRqpf)2|Sb4JJ^GsY6hyYvG&@j8vXF1t94a3a@+>(%rEoz@fKavl}x$MrmL-| zQzX8%h&1apYk`KLYvCbiYLSdwEv)Y{G~-bu!mFoPD{rQlM2977x{yhHNGJJ!QS_4 zYYm%vh{f+S#;miT;XV{V@??DAf6zhx`2n~E$aZT#`1s#XbqDdegO>2!x?rh@9|u-7 zu^Lu~`79tJax#DnTmgjgW1fL4SV&=PhhE9%FNA}C`M|;vs2n3d4J_!i_rL}P@NEJl zd%v~i8~-HEY{iH?ue*`i5bmDs0Z}ZgQPDx#d_-NpeKNg#w_r_VePP*3iCysRvX*X7 zL=%Q4Yo?jaizQ^UtO@1M6t7$G=A8_F&0!pwj3sZT_Y1(}=-Qv94sV8M7wx~iB)!a9 zd&lQPeSpoGSK@?Y*J~`RTP1KjptY@!Iq1=?oJE9;k3|+1`UzE$k-ID&Uqpl`;AfTn zTBv&>?VQZ7k`)^p@5=om1(J&um>#XWJpHDiXr6MlQloxhCurhrDjl7a0|DKZzxR-M z^vH3y_l;#lW`iuOE&;IJi=?30?pnIn6v;7{6)?~8axDk22DTSFO576lgtX&g7UsHb z6eBhcLps~;i*{Vux#)_00aWu`fb5S-a=F^C^Dkb>sM?n9-j7ZqsMr#b0tHi#1-TTw zF7j4}xIrUB^Zaz?77mLBR~wkefCt5c323izd*4mGM#`LT&YFB7c5#P5`a^HW?&3SN zSMMGdGBi;?lg$%w6fW@a3h+3qB`9N4Hb;4&uHMh%r~nJ5qV} z`2)l|!pAZKK)vw2#AkJN8e8UcY!~0&Ws)E`_eJO6kLL=&D_5SGdZMkQ1v4J`jq&sk z{!@1WAV~B38jJq(gYf6S_mGe7Yg{|{#{|p0t%^dEE+GSi?0Gy2VfW60940y7Tp}cI+p*T?|cAJ zv;`C%V+NiI+>AJ)?ZXoR`mhUxB$>lQq4UK+iK*P`?(yHZcW*wzDd__aMR-+({H{Fnc9;s7Y|1H3W|{+@J^8t{;RpB5b# zM1aYeuY24ovh8yG#;Av}O#oKeyNa5R=AwK7{^y9YeGHSd!e4$^-o={Y?|$4Ju8<-Q zJ8XXZ$I0;jy7{lLjC+6-g$zjgHTa6SU;1^nHa;Gh?c24;#@Rx>(J?)Ptn$b@;o;@m zF|scx{GK~-RcCc~`x3&tYU~0tSKKf6Wm~z?IDc-0pYj6TL2bI5nwz2-q*2WIAZz2 zcl|#L#>;0M3xj)D)&c5P0C$D@yW#wo?tUA4+4rFboTMJ6;!h(Eul~{51~b6>^8RK3 z5EKlB9@0dwk3w%lzn=+!O;Go{#r%6;z8S4kyJrCUWJf+=PwYQQ;#(x&hb}^~#K0Vc zPm&E3mcdU;*vfU#Dp)N#5+=y+-1NMCbMUn3+--czeSiPIUOg-(wDfq zHe;FFH)?`W_Zpp$@Z>3ss7@T(a6^E$Zv!=MRM^?A*{Hnf9nYDn*0{t;o~eDar5+Nk zvz+@;FW{Uk~3R@Q4<&ixkwvB3O*(TJ$;AU#(ge#FDJ>ozz= zi*qQR!Wl&v%H`^l2=f3EJEOua69sOoCzcPs&yZ(1WU&J5AtpFwhMcqC;r6+Mu?WX0 z&rVTVq3RMzbiAmiM$LsQoGGF zfZ{1G2)#SKxVXQi{kkuA-1wsa9e}jPNI>w3op4HOZLGB{w{yVYILstDFW+qlvTTOu zAey*;fD3(v;8`mbLK$aJr|ko@5=| z6LW=LvVP2lRJjyL$sck3Dm!7|s+tUT#-Jukl|Sqt>r9bfN-FvRBvHk)sFM-V2s$Lk zJpj1G{OuPZ?)(1A)zai1F*byBK7qWnMe$6)mOfYAS7FT!c-9&wmlh~jaB4$%n<1>6 zGsGR;+_XIscbin2>d^a6t`5tiMkJ5-Y~PhJoM|aw_s{H@I@=&;r$>4hZ^^u|X@BQG z#Vg{nB)XxZ@e(6ss&nKlC0TeZa78#byAE;B%t$`B_gK}MJ7WTcG6GH&(l6_KOO38E zKgEk@qmFd9%J?%~)Lw(ff4vs@ox^VL$)of=TA?(PF-?R$i;T_g8;i zgfal5f4+hIR^%LCxbmr%9IYl6g_bwjex38|&H2YxL}MdDO7RPZTj!cw z0T|SL10Yi;e?@Kk@&q&tT!f>9qDoD1oR-Bkvxdu*XPK1e(NdViJzE;F+5UCYTDI{_ zih|68#~C2FWB18tAg@CZ!4bzdt2nU7t8OI!zpx~K-crV=lFy23q?<%U{(h8F`knO3 zr&@PlQkCRzV&tGjDoz4TR6aur)f>EtxsjbgR_ugyfA#Km%jA=ZuG?=m7HWg0-PRsf zU~Qpk*fDja57X?G-6WNJp}f1D1=9UzVD65gm-3`1b1JEqyO~r5Usp?wvLzxQ`jeCQ zdq0&89Zf9?s9?Qkxj`WZ(zl^hE87#svK0e{q}SpFE^#;Eaj*PLGE=;n`Cq~IYr6DI3CyIp@U_}guwtztwX4vN& zl~0VbKjasAEKq~AR9X6S4j^_(kLSM#+OtLJy~T6H76k};8G%HA6} zW)!MCwR_c#mQ&<~I8H|gNs1yS5tS4vP<+enPuCw$gZ* z6c4A)D-Gn?2&}t6$CmoKv{;NCHdI&qK{qM7VJHcpGcuXLL$4c&^B|_DcJA0VFk{15 zLMgEw*nBler)_J-7}WG*c;^|Wj1$(YnB@7LF-F}?6P{*NSA|9^X{QD&Os^*dJ`zWg z8Eq2{HxDUNEn>fA+Xb)R!d{1!CHX>UqNT7<^fv-SbZTh%I~_133fx*?eK>Q0x>hjw@njR)b02w2Ttcz>zMoe<$pPHjl$9&F zKQdNH$alk@M;Ls^(_Jke(c|R7xyR(zt&6eC6!H!ynzFEELX2)ah8`}nj<=?MDP?p! pUCXry*VZa~3a2i?*SwCed?Nq!2XNcZI-dXQQ_BBx2cU1G{|7u{TfzVU literal 0 HcmV?d00001 diff --git a/docs/img/legacy_hardware_remapped_pins_photo.jpg b/docs/img/legacy_hardware_remapped_pins_photo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a7abccccce3960256ec767ca6c4803585fc56319 GIT binary patch literal 148993 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 JP}PFx|Jgy&tw;a> literal 0 HcmV?d00001 diff --git a/docs/legacy_hardware.md b/docs/legacy_hardware.md new file mode 100644 index 00000000..09daaaab --- /dev/null +++ b/docs/legacy_hardware.md @@ -0,0 +1,22 @@ +# Legacy Hardware Modifications + +Older Raspberry Pi devices have a smaller GPIO header, 26 pin as opposed to the 40 pin header found on more recent models. The Waveshare LCD hat uses some of the pins between 26 and 40 for the buttons, meaning that these will need to be reassigned (and remapped in software) when used with an older device. + +## Hardware Changes + +A suggested remapping can be found here: +![Remapped Pins Shematic](./img/legacy_hardware_remapped_pins.jpg) + +This remapping can be done by soldering wires on to the Waveshare hat as below: +![Remapped Pins Photo](./img/legacy_hardware_remapped_pins_photo.jpg) + +Alternatively, you could do this by connecting the LCD hat on with individual breadboard jumper wires. + +Once you have re-mapped the pins, it is advised to do an IO Test to ensure that everything works. + +**Warning: Some of the GPIO pins on contain 5 volt output. Raspberry Pi GPIO pins are NOT 5V tolerant, meaning that if you accidentally connect a 5V supply pin to a GPIO input pin, you risk permanent damage.** + +## Software Changes +The Seedsigner software will automatically detect which hardware revision you are using and if the older hardware is detected, will remap the software to match the above modifications to the Waveshare hat. + +If you are using a pre-built Seedsigner image that hasn't yet has this incorporated, you can simply take the file "buttons.py" (/src/seedsigner/hardware/ int his repository) and overwrite same file on your Seedsigner SD card. (The easiest way to do this is to copy it on to the /boot/ partitition of the SD card then copy it over via the command line while connected to your Pi via monitor+keyboard) O \ No newline at end of file diff --git a/src/seedsigner/hardware/buttons.py b/src/seedsigner/hardware/buttons.py index d1ae20e4..44a33d60 100644 --- a/src/seedsigner/hardware/buttons.py +++ b/src/seedsigner/hardware/buttons.py @@ -3,18 +3,30 @@ import time from seedsigner.models.singleton import Singleton - class HardwareButtons(Singleton): - KEY_UP_PIN = 6 - KEY_DOWN_PIN = 19 - KEY_LEFT_PIN = 5 - KEY_RIGHT_PIN = 26 - KEY_PRESS_PIN = 13 + if GPIO.RPI_INFO['P1_REVISION'] == 3: #This indicates that we have revision 3 GPIO + print("Detected 40pin GPIO (Rasbperry Pi 2 and above)") + KEY_UP_PIN = 6 + KEY_DOWN_PIN = 19 + KEY_LEFT_PIN = 5 + KEY_RIGHT_PIN = 26 + KEY_PRESS_PIN = 13 - KEY1_PIN = 21 - KEY2_PIN = 20 - KEY3_PIN = 16 + KEY1_PIN = 21 + KEY2_PIN = 20 + KEY3_PIN = 16 + else: + print("Assuming 26 Pin GPIO (Raspberry P1 1)") + KEY_UP_PIN = 3 + KEY_DOWN_PIN = 17 + KEY_LEFT_PIN = 2 + KEY_RIGHT_PIN = 22 + KEY_PRESS_PIN = 4 + + KEY1_PIN = 23 + KEY2_PIN = 18 + KEY3_PIN = 14 @classmethod def get_instance(cls): @@ -165,14 +177,25 @@ class HardwareButtons(Singleton): # TODO: Implement `release_lock` functionality as a global somewhere. Mixes up design # patterns to have a static constants class plus a settable global value. class HardwareButtonsConstants: - KEY_UP = 6 - KEY_DOWN = 19 - KEY_LEFT = 5 - KEY_RIGHT = 26 - KEY_PRESS = 13 - KEY1 = 21 - KEY2 = 20 - KEY3 = 16 + if GPIO.RPI_INFO['P1_REVISION'] == 3: #This indicates that we have revision 3 GPIO + KEY_UP = 6 + KEY_DOWN = 19 + KEY_LEFT = 5 + KEY_RIGHT = 26 + KEY_PRESS = 13 + KEY1 = 21 + KEY2 = 20 + KEY3 = 16 + else: + KEY_UP = 3 + KEY_DOWN = 17 + KEY_LEFT = 2 + KEY_RIGHT = 22 + KEY_PRESS = 4 + KEY1 = 23 + KEY2 = 18 + KEY3 = 14 + OVERRIDE = 1000 ALL_KEYS = [ From 67be53ad942d447e729d8dd3a3ce0abc72ae3278 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 6 Aug 2022 20:50:54 -0500 Subject: [PATCH 30/39] Update seed_views.py --- src/seedsigner/views/seed_views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 00759882..e37a9d04 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -723,8 +723,6 @@ class SeedExportXpubDetailsView(View): return Destination( SeedExportXpubQRDisplayView, dict(seed_num=self.seed_num, - sig_type=self.sig_type, - script_type=self.script_type, coordinator=self.coordinator, derivation_path=derivation_path, ) From 8d4552526c3cd62347282731335b89cdb0effd2b Mon Sep 17 00:00:00 2001 From: 3rd Iteration Date: Fri, 12 Aug 2022 22:16:21 -0400 Subject: [PATCH 31/39] Switch GPIO numbering mode to board Change the code so that the RPI.GPIO module addressing is based on the physical board pin number rather than the internal SoC channel. Boosts compatibility with holder hardware and makes it easier to see which pins do what. --- src/seedsigner/hardware/ST7789.py | 8 ++-- src/seedsigner/hardware/buttons.py | 71 +++++++++++++++--------------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/seedsigner/hardware/ST7789.py b/src/seedsigner/hardware/ST7789.py index 09cebd20..e1e1fade 100644 --- a/src/seedsigner/hardware/ST7789.py +++ b/src/seedsigner/hardware/ST7789.py @@ -13,11 +13,11 @@ class ST7789(object): self.height = 240 #Initialize DC RST pin - self._dc = 25 - self._rst = 27 - self._bl = 24 + self._dc = 22 + self._rst = 13 + self._bl = 18 - GPIO.setmode(GPIO.BCM) + GPIO.setmode(GPIO.BOARD) GPIO.setwarnings(False) GPIO.setup(self._dc,GPIO.OUT) GPIO.setup(self._rst,GPIO.OUT) diff --git a/src/seedsigner/hardware/buttons.py b/src/seedsigner/hardware/buttons.py index 44a33d60..d187f8c8 100644 --- a/src/seedsigner/hardware/buttons.py +++ b/src/seedsigner/hardware/buttons.py @@ -6,27 +6,27 @@ from seedsigner.models.singleton import Singleton class HardwareButtons(Singleton): if GPIO.RPI_INFO['P1_REVISION'] == 3: #This indicates that we have revision 3 GPIO print("Detected 40pin GPIO (Rasbperry Pi 2 and above)") - KEY_UP_PIN = 6 - KEY_DOWN_PIN = 19 - KEY_LEFT_PIN = 5 - KEY_RIGHT_PIN = 26 - KEY_PRESS_PIN = 13 + KEY_UP_PIN = 31 + KEY_DOWN_PIN = 35 + KEY_LEFT_PIN = 29 + KEY_RIGHT_PIN = 37 + KEY_PRESS_PIN = 33 - KEY1_PIN = 21 - KEY2_PIN = 20 - KEY3_PIN = 16 + KEY1_PIN = 40 + KEY2_PIN = 38 + KEY3_PIN = 36 else: print("Assuming 26 Pin GPIO (Raspberry P1 1)") - KEY_UP_PIN = 3 - KEY_DOWN_PIN = 17 - KEY_LEFT_PIN = 2 - KEY_RIGHT_PIN = 22 - KEY_PRESS_PIN = 4 + KEY_UP_PIN = 5 + KEY_DOWN_PIN = 11 + KEY_LEFT_PIN = 3 + KEY_RIGHT_PIN = 15 + KEY_PRESS_PIN = 7 - KEY1_PIN = 23 - KEY2_PIN = 18 - KEY3_PIN = 14 + KEY1_PIN = 16 + KEY2_PIN = 12 + KEY3_PIN = 8 @classmethod def get_instance(cls): @@ -35,7 +35,7 @@ class HardwareButtons(Singleton): cls._instance = cls.__new__(cls) #init GPIO - GPIO.setmode(GPIO.BCM) + GPIO.setmode(GPIO.BOARD) GPIO.setup(HardwareButtons.KEY_UP_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up GPIO.setup(HardwareButtons.KEY_DOWN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up GPIO.setup(HardwareButtons.KEY_LEFT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Input with pull-up @@ -170,31 +170,30 @@ class HardwareButtons(Singleton): return True return False - - - # class used as short hand for static button/channel lookup values # TODO: Implement `release_lock` functionality as a global somewhere. Mixes up design # patterns to have a static constants class plus a settable global value. class HardwareButtonsConstants: if GPIO.RPI_INFO['P1_REVISION'] == 3: #This indicates that we have revision 3 GPIO - KEY_UP = 6 - KEY_DOWN = 19 - KEY_LEFT = 5 - KEY_RIGHT = 26 - KEY_PRESS = 13 - KEY1 = 21 - KEY2 = 20 - KEY3 = 16 + KEY_UP = 31 + KEY_DOWN = 35 + KEY_LEFT = 29 + KEY_RIGHT = 37 + KEY_PRESS = 33 + + KEY1 = 40 + KEY2 = 38 + KEY3 = 36 else: - KEY_UP = 3 - KEY_DOWN = 17 - KEY_LEFT = 2 - KEY_RIGHT = 22 - KEY_PRESS = 4 - KEY1 = 23 - KEY2 = 18 - KEY3 = 14 + KEY_UP = 5 + KEY_DOWN = 11 + KEY_LEFT = 3 + KEY_RIGHT = 15 + KEY_PRESS = 7 + + KEY1 = 16 + KEY2 = 12 + KEY3 = 8 OVERRIDE = 1000 From 9113eabe72870289e1c42ea4c8a5f3e8dd63486a Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 13 Aug 2022 20:49:50 -0500 Subject: [PATCH 32/39] Additional Compact SeedQR test vectors Closes #234. --- docs/seed_qr/README.md | 69 ++++++++++++++++++++ docs/seed_qr/img/vector7_compact_12word.png | Bin 0 -> 7489 bytes docs/seed_qr/img/vector8_compact_12word.png | Bin 0 -> 7383 bytes docs/seed_qr/img/vector9_compact_12word.png | Bin 0 -> 7142 bytes 4 files changed, 69 insertions(+) create mode 100644 docs/seed_qr/img/vector7_compact_12word.png create mode 100644 docs/seed_qr/img/vector8_compact_12word.png create mode 100644 docs/seed_qr/img/vector9_compact_12word.png diff --git a/docs/seed_qr/README.md b/docs/seed_qr/README.md index 3eba91de..b6a5f825 100644 --- a/docs/seed_qr/README.md +++ b/docs/seed_qr/README.md @@ -480,3 +480,72 @@ b'\n\xcb\xba\x00\x8d\x9b\xa0\x05\xf5\x99k@\xa3G\\\xd9' +--- + +## Test Vector 7: Additional Compact SeedQR problem characters +Explicitly check Compact SeedQRs whose byte stream contains `\n`, `\r`, or `\r\n`: + +`\n`: +```bash +# 12-word seed: +dignity utility vacant shiver thought canoe feel multiply item youth actor coyote + +# Standard SeedQR digit stream: +049619221923158517990268067811630950204300210397 + +# CompactSeedQR bitstream: +00111110000111100000101111000001111000110001111000001110010000110001010100110100100010110111011011011111111011000000101010011000 + +# CompactSeedQR bytestream: +b'>\x1e\x0b\xc1\xe3\x1e\x0eC\x154\x8bv\xdf\xec\n\x98' +``` + + + + + +

CompactSeedQR
+ + +`\r`: +```bash +# 12-word seed: +corn voice scrap arrow original diamond trial property benefit choose junk lock + +# Standard SeedQR digit stream: +038719631547010112530489185713790169032209701051 + +# CompactSeedQR bitstream: +00110000011111101010111100000101100001100101100111001010011110100111101000001101011000110001010100100101000010011110010101000001 + +# CompactSeedQR bytestream: +b'0~\xaf\x05\x86Y\xcazz\rc\x15%\t\xe5A' +``` + + + + + +

CompactSeedQR
+ + +`\r\n`: +```bash +# 12-word seed: +vocal tray giggle tool duck letter category pattern train magnet excite swamp + +# Standard SeedQR digit stream: +196218530783182905421028028912901848107106301753 + +# CompactSeedQR bitstream: +11110101010111001111010110000111111100100101010000111101000000010000100100001101000010101110011100010000101111010011101101101101 + +# CompactSeedQR bytestream: +b'\xf5\\\xf5\x87\xf2T=\x01\t\r\n\xe7\x10\xbd;m' +``` + + + + + +

CompactSeedQR
diff --git a/docs/seed_qr/img/vector7_compact_12word.png b/docs/seed_qr/img/vector7_compact_12word.png new file mode 100644 index 0000000000000000000000000000000000000000..afdd9d3a5c7536cf535782b0e3494bc3838ae9c2 GIT binary patch literal 7489 zcmchcWmuG5yN2oR?rugv>F!Wq=n_e37(lukDG?DJq(qQ%5a~u3QV|gm5F8o=q)W;n z_Tt^~z3+GIpZnN9U=HA!#XR@A*L9!Q^`sa=wMmJXh%hiPNN?+C7=zcn>n{R4@LkDl z{V4_pE5U6IRnw6C?Se=znh$3m3a4ko4}Z9RA(O#dW}?;8OSj2l5Tal7q-iANA>}o$ zGDMXa^1>3CEczef3G$i|QgGk7OO@(j`%Me_yvdyQt}&BoL1--%v+5&Gfj1Kj)3Gvm zUHOrs$J^VNm&rQJN5SIQ^3rp)g&6_S+pj}cSC8A9#ScFp%IJ+rV5(9G8u7Jb6U5CZ z3E1OgGSG;{>5vdHsPf?9KH{K?$I_xmQN`p=?t2wOso`jdB;dOFe|uT3fc9?Jl~O}f zQ=9`s@jX;rM~4CdUi`x08DG0F2FBO9IkJL+0yxUI^2+eCgV(M#b6wcdN>-t&^aseA1(ws^|*P51J?rFZY{ zN&MJ44@w}_F|U~{(=8WIBwP1|GJ3`e6#CZxQo;L`&WokG*=k}8`CHy%;$mVL+8Jz# zo0HY$vlgzd{4B~*`7OMSUO#SCyn6=?w+ngjfi)PZPOgX}?)oKv>zx=iQR1trDp+0n zzF-s8-usg|V)>x07hoy>vjbn9Yrisx~&Pqb_4*`o@GZ%F65;8yl}mN}TH2 zdp;XP|1q&9oHDQeTDMKzKI`o(!=e^Zi=)9qMTn`mnAE$7(LAmAW448ND<}hX2ifq8 zt(`JmD^b%*wg9>@%$>(T(i`I@A@{EbSn#pO{(Rk3k^d)2eUpS4%#?d(0}2cjLwl_mixG0#eV(cZtQt#SQ4T zfOV>lW0Yr~e~EW9bI>+Q(~F-jt$TGiFGY%xk#Ri8t!36*;Kq%{aXjJK+1an-<2c;h z-0ml4Ha4Wf7Vq@h*(JXpQDylJSt&j;t62zV3|Luf%VN<2xB=kBI>)*;L zVRNBqB!4u8zuuHt#wC(X?C0l~9A1!HrmDckpE@O>kNBuR_LEAp6d|NU;)Ovpg%`1( zW|#0j{DkTx7L7(bEDxmjnf;XYUq!}Z<0p5vwYM)0@)ve&cVb12lW`^IW14O7|Armv31s)rbZuWhxt#%Fc5dFopK7sTqN-3))&W$ z#c*=j!m&9;pLn?kTf>(~A&rkpX2?nazu z8XYBUF5*z1riN#Mh2f0dNqF>D0T81?)yY7I6-SEAf77IO90-DCWpA%0U`1VlEn`JW zLjQHtZe}K>1G=y6T`%|pg_V<1CMNPo^x51rJGBH>r$?vu~8rVgOm@;_l|9*r>7k@zgLM#Nx{GIF_84^MqhwFKAR-%jhVkZZtkPHk&}~C6NwILIhhMj>2&*jbaX9Y*%XFG5x+;Zvt|=V z?1eGyx}sR$*>x%Xn5FoV&Mdu9)jY?A0C>lVsl(Ob*MD-Qeb}DC;Z!iUDTFWT$Nq-&Pi|&qLNR z=~{ETSgEd#;=N;;{?{4SdJ&^PmJe4w2-2m{4!>|tNt7aqUpJffp0~jnbTW1mdnATw zpr@xd#Bo+zd((RMV}l9~d`#!{>(}Gs^$#5=n)}Oy%G`;nJUG3W-_+DlASx@%XS;l@ zta7S~gXbboY-A$;1bo^YbfUVJgSA+3aq%}lggb0SA$FblZEZ>u*Be1hoOnjfw_qQu^Z%2r?AU&n8dLE}~k{JMD=$;WabS#B`7taPmvviz-m^EoAWbEV^p8x&8hZaQ5N|iNL8c*Z>Ek|!Ac`>$3S&S2@V;n9FGPPax!mXpolx7g!V$A zd#D|-*qE7@pZ3R2&=Uf+S-dN#>QhJWvU7%jNXXW#h1PFRUDq_=ylb)WgvDml#z_Ke!anvF_Ts|NusI0@A z0k7k1HLowy-F7)UGZWLNV;|4S$*H7aaA+vrUM$&l(8Pg=D@QLkMI-L?NA3M2LX5r< zFp-mCp!WW~4@t|)-1{ zpO-3`e3ys+t?ElNttMvIyy#C_QJ^+|P(BQJflQ9MeA(}zY@tJz1K_vBTH4wvU1l;Y zO=iX_?GBYG_6lsxUavRhvKKIV#ac|+oH&`%{RXE7>yjmvl)QhC6GN8i)3x4^!wN_E zXTuV8e4Qx%4!72XFPu#ZU0%EA285>p%pm_=u3|(Zl0;%jC*?lDx0PIl^N8bP73Wqp zqQ0K`F}pH!hRV&|y{O55sgH0e>zf#fO!VbB>}cP-HR?i+MKO#94-XI0c(~KPd#ehW z2G%;=u;?Okc6Jto4Se{p=o5`*4hRe^eXsmqd;u60>RxZ(jb+*D$J;Wy^Gu4CmbS)u zh;He^!p25_+dx;BxZ~n@TCiFeOlUM}GPzsp9%bX=%+!<)DM!s%`Kwn*g}@D(eR-P} z-)@MYV9xK(x$_zvuJOr9>glf%!wM0nVzuc&k^GM~Cg2~c>+8oL0GiIFO}NZhVWO@LDX$ej|(D*N~;8*=EBdD13^DPI6x zr^|K|NbR9fpJ=)q@w_j?j;Ko(I!K@uape4`lOSezh&k#OaE+}HVh;&s zcrOPyZ%ubczDnp@!i`W^g>HsOIsl{NnxvU~$xhg>Tn(*}b!zqL|VxZ4a^ z+C!dP>6GJhzHFA;VK+WI5>TXxEjv4V5*Uoekts_{{b@CUo90Wszh8#$#A>sJCdfn; z84}EeERz;j8wv!p&+3sEOXXYL;_{kpd!AO!|1v~PhXbE@dwY9zrq%z($yy5k%LTDp zUe`paHjrV#fj{Ir+ZO1;mC13uM&EUs=&YPumV-~u_Vdi2qT9j^C<^Gk5AGCf>j5ED z86R3T2vh#pLfk{06P)*N570E5>}IPyamV=|f5xqcq^71Gmu1O#uK+(N=GqTY7!T`* zBnx4@b7yL9sB3WA0`=^HO7dpi_SgfJtKHHD@FbamiL-+puE4ezqM$)ppTAYKqWwM4 zW~!{yJ$F1$kttm*D5%MqvR;F!_UGfW2lX?A+b#xtS$+Moya&Z! zzLc$H?i3jxQs&cm5s%^X_r@ed$g#{52wM;cqT}6pgN|;?ItB@8X=>?dFx9_*|31C3 zT;+GPVJ73dfDv)Jm?Q{0k5cyfQP+Q94NTn4z`)bf{pG!K<%_|m>_C1Cug=#N0nrKq zcw}Hv2zm12%*oA-fQgA|0k8t==qeEKzRS;r%Iq&A4sH|RVEz6={cCqO34j0Q`YxWDBj|0lO9BSIyNmiz&8eVH)lW*eV<0n2E z9*Ms67n#5Kn8~f7Jm~Z-;%BCI!Z{hxt$TGa7FoXp-Dum8Mcg4UE&|dlgNTZQL4!t{ z>yITk%m0*=;&Yx4b97X*Y^BaJ`A&@rcI=1V#KWL^r#2*DZQ{aG4Zn6;Ll5ALjg{Yo)0 zu)I$}-dSH#ZbXCYGMwvt7OcaUMUmDI(T=KpG!M#Fu?;NLh7TAKlCtrWo3wPK0tGRi z9v=3CPk90I9)&q{$Kh(^T^kMm5k>8D`Y*%7rFfkA^1)<(RpqSl@~Ar62|~>+x=FEj#O(sCWKDE z1b3=8$c7(76pp9v|D|!t=ZoZvo#-9z;9UBwR>Jg%9`>tKZBA864EswUrMX+xXhDm2 z_ihdi4W(SzbVjuPn4Z@4N}4=XJp3#^t~5C@VKg1OUYZ?3N=duEiZr3rAFGJH>Y!kB zGvEfJ!HRuBG756P_ZI%ZCz3Ieq+U*lN|v z#I`m$%SI2gJ9(B6yzt|#Sd((7)y7ak@Ejqtw0DC1ZijfOesHj~0w`49uk!A0RgdHw zL#MPfG+*Um;@}*Ym*d~Pdsm~io^;Xoh3z4(pb3xHO!6D9I(w? z6dL9sFOiy>3O*Yqcriu+qCmbeDX+M>CJ~ZeI7{(YTGEsoox&w9o;B3D(AS64tZ0G? zyvtJEySP9llbM;RS`a+ocvGZQJ@onweD=0KaGm2P%Q^?2U}oVX5VsWFLL)>qjVijS z%iR6;c|FcG;w!HW_GL0KP2YxgTY$d+y#`_-=Q98odeX zQF^+O_&u2dsDOX~2T{PzOsg-5RvabFwYBlhD6Fik3=IucXe#D+lZfd>ETb4e=2Kc) zDp*~K5cc6(olSm9d$M|{`t9idtwx;4yRtEzkW6sm?GzBnc zud(aeD$()r@l|BrEnYzT{a_ z@rgbN2N(PZ^&b@?|ER-5McKsW(2W18swva**UnDaj_Lt&#jo2Ycve$?jVm>8?M>As zalNTe4Rur(`%c@Yh~v-6I_II5pN=A6OJ3^ZojUdB1(2Z@eUay9hn%9KBYx4APoPMB zef^r~t4k2}jEp4F+eJ_c2@8+yq$L^(;IOi?!l#QSj%t~~rX@t8k15AyLloFV5@*@M zesTV1Fe2J{%60Q~N|}3&{~C12+0_!{IFs*es1L{$6ck7@b$GU;EK@YNnl>^7B9z#l zJ$vRWJ8p1)^>fa6gB--jt>NR%mpan;t7d#p2OcqsCz$D~7y~pVAtxu^@247r=3#s0 zYQ4Su$PBV1j*rnQe+yfskP`Zie*TOOZxlWrybm=OD>-wNeW6xA8ffEYPY4pAUu{Y& z<}bK2365N(N#X^oAmo_!TZQ%G_%5}#d8qUvmoP=@Q88d2dtPTK-Ra1wzSoL%eak+p zz+wRvCpNZ~Cc&1IlY3QO&R?27ww{ohO0+cR{73PzfXzZM{pY;pXOFS|G6KSFu+ z#>}kLn-#P#S?AToF7*gb^kLq7!oYym(sz!bV*{|UbZbKMv%2`1jM6-*2UcJ{Sf57{z4nD76T!H476mWsMt z83fpre>v0}D}m$Eri`tZDCtS)=lKyQVq9wr(?2>mwb(bOca+S{Etk)R_BOm8i9kRH zdj$pTZ!bJ2j9cMbHKQ`4ftF{to0rXT-&O`VjRW?B>K0%g zP}9>XozMa~&6ze3Ed#@*u`7`XPC3BLKWMZG3e0t&>l6ey#wZZPs0+|=X(0-fqhH>Q zTKQJ0`xWp$%>lsFH-s!;rK0+J@s&VLqgQ~vQq$5>w)e1by|Cv(*H|m6s(4+7Lbhr@ z*w1%H_C-0V$|mvXp{jXwt)n*TN0TpY^S*p@HUl0jwPm4c)MAq97lw zr`h#}!4jM2!qVL8-N*P4|DC7{R(s~+p-*7i0R*AOLv#7V^?Ymryr>c32B=W~qhMzl~&oP2&#hmJpZ9r>wbU zMkl_SW~A5V;dL-bX4~Fdz+)&-pAsy0xn^kBORiq!fik%-88)= z7`;auM$}!YR_LXAg+LgexQ1>IqpIXMi9(3Fmsh_5kucX}`_%>=rsxs=sAlq3yWL;SQm9NeWraD3oor2?YK!6HT(p-waCX>rBlLrSr?3^uR;ktYOA~;D& zNkd~}zjhnk7y~TX+}zw(?d{XX@*S3K0fG_|5)>z~)_3nNOx51+3%dL>+0}zYs$7F3 zGFQ^GA_X(VLHMud7uMF(PQkr!Q&UqU{DyV16bR_YIj})kOS^Y*GEaUoAAJS;eV{zk z@&FrJs03ri6t(SOpcA##Yj_}6V2at?(4YdW^Qg;QM`#a@S0!*b=yP)ONK+X0Yo&aj$O-^(^zt)@vjFw&+ znmfNQ(YxxNo|OT@S?2$l+vq5GeKEdU+S0;+Kp>LIvEko@04a=L7_ucps*Eiy353nx z*087!+=`OW$Tp|sv&T_AjZd@u7eD>~KKr}E->tIR4yY7W1r literal 0 HcmV?d00001 diff --git a/docs/seed_qr/img/vector8_compact_12word.png b/docs/seed_qr/img/vector8_compact_12word.png new file mode 100644 index 0000000000000000000000000000000000000000..539b0ac2911a0cc2de1f135ea2c8b325af460385 GIT binary patch literal 7383 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 Yp&h1pufC894lJ>BwG1_D)E#2}1BKSBy#N3J literal 0 HcmV?d00001 diff --git a/docs/seed_qr/img/vector9_compact_12word.png b/docs/seed_qr/img/vector9_compact_12word.png new file mode 100644 index 0000000000000000000000000000000000000000..213453f1194d8c693d8e50c9316aa73f8e0c057d GIT binary patch literal 7142 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�XKTeL*cS;31;X@30pfhB$b!24}% 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 Btup`s literal 0 HcmV?d00001 From e546b26af6c58b5f2eb72465bb5c5cf30797b956 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 13 Aug 2022 20:52:32 -0500 Subject: [PATCH 33/39] Update README.md --- docs/seed_qr/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/seed_qr/README.md b/docs/seed_qr/README.md index b6a5f825..f22905a3 100644 --- a/docs/seed_qr/README.md +++ b/docs/seed_qr/README.md @@ -482,7 +482,7 @@ b'\n\xcb\xba\x00\x8d\x9b\xa0\x05\xf5\x99k@\xa3G\\\xd9' --- -## Test Vector 7: Additional Compact SeedQR problem characters +## Test Vectors 7-9: Additional Compact SeedQR problem characters Explicitly check Compact SeedQRs whose byte stream contains `\n`, `\r`, or `\r\n`: `\n`: From d05cc8a983c75aaeb7843deb3e55f7143961edfb Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Tue, 16 Aug 2022 12:50:06 -0400 Subject: [PATCH 34/39] pi zero 2W hangs in psbt overview screen with animation, adding small sleep allows button actions to be better captured --- src/seedsigner/gui/screens/psbt_screens.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index 053d47e5..11709947 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from lzma import is_check_supported from PIL import Image, ImageDraw, ImageFilter from typing import List +import time from seedsigner.gui.renderer import Renderer from seedsigner.models.threads import BaseThread @@ -439,7 +440,7 @@ class PSBTOverviewScreen(ButtonListScreen): self.renderer.show_image() # No need to CPU limit when running in its own thread? - # time.sleep(0.02) + time.sleep(0.02) From 004ccd81e298382c14b4a9f70147912c2bac10f4 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 20 Aug 2022 17:36:30 -0500 Subject: [PATCH 35/39] Removing step that asks for user to select their coordinator during PSBT signing flow --- src/seedsigner/gui/screens/psbt_screens.py | 16 ------------ src/seedsigner/views/psbt_views.py | 30 ++-------------------- 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/src/seedsigner/gui/screens/psbt_screens.py b/src/seedsigner/gui/screens/psbt_screens.py index 053d47e5..60c428ab 100644 --- a/src/seedsigner/gui/screens/psbt_screens.py +++ b/src/seedsigner/gui/screens/psbt_screens.py @@ -696,19 +696,3 @@ class PSBTFinalizeScreen(ButtonListScreen): text="Click to authorize this transaction", screen_y=icon.screen_y + icon.height + GUIConstants.COMPONENT_PADDING )) - - - -@dataclass -class PSBTSelectCoordinatorScreen(ButtonListScreen): - def __post_init__(self): - # Customize defaults - self.title = "Signed PSBT" - self.is_bottom_list = True - super().__post_init__() - - self.components.append(TextArea( - text="Export as a QR code for:", - is_text_centered=True, - screen_y=self.top_nav.height + GUIConstants.COMPONENT_PADDING, - )) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 3ad69f02..03e9d830 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -484,44 +484,18 @@ class PSBTFinalizeView(View): else: self.controller.psbt = trimmed_psbt - - if len(self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)) == 1: - return Destination(PSBTSignedQRDisplayView, view_args={"coordinator": self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)[0]}) - else: - return Destination(PSBTSelectCoordinatorView) + return Destination(PSBTSignedQRDisplayView) if selected_menu_num == RET_CODE__BACK_BUTTON: return Destination(BackStackView) -class PSBTSelectCoordinatorView(View): - def run(self): - button_data = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS) - selected_menu_num = psbt_screens.PSBTSelectCoordinatorScreen( - button_data=button_data - ).display() - - if selected_menu_num == RET_CODE__BACK_BUTTON: - return Destination(BackStackView) - - return Destination(PSBTSignedQRDisplayView, view_args={"coordinator": button_data[selected_menu_num]}) - - - class PSBTSignedQRDisplayView(View): - def __init__(self, coordinator: str): - super().__init__() - self.coordinator = coordinator - def run(self): - qr_psbt_type = QRType.PSBT__UR2 - if self.coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP: - qr_psbt_type = QRType.PSBT__SPECTER - qr_encoder = EncodeQR( psbt=self.controller.psbt, - qr_type=qr_psbt_type, + qr_type=QRType.PSBT__UR2, qr_density=self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY), wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE), ) From a11d034a6c4bc154505596b9351775ce8b1c092a Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 20 Aug 2022 17:54:43 -0500 Subject: [PATCH 36/39] clarifying comment --- src/seedsigner/views/psbt_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 03e9d830..613a9052 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -495,7 +495,7 @@ class PSBTSignedQRDisplayView(View): def run(self): qr_encoder = EncodeQR( psbt=self.controller.psbt, - qr_type=QRType.PSBT__UR2, + qr_type=QRType.PSBT__UR2, # All coordinators (as of 2022-08) use this format qr_density=self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY), wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE), ) From b3765257721d2d063bcef789fbdcaa4261b41032 Mon Sep 17 00:00:00 2001 From: Thomas Kayser Date: Mon, 12 Sep 2022 22:42:01 +0200 Subject: [PATCH 37/39] Fix link text --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0b7a037..cb81cbaf 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ * [Project Summary](#project-summary) * [Shopping List](#shopping-list) * [Software Installation](#software-installation) - * [Verifying Your Software](#verifying-your-software) + * [Verifying the Software](#verifying-the-software) * [Enclosure Designs](#enclosure-designs) * [SeedQR Printable Templates](#seedqr-printable-templates) * [Manual Installation Instructions](#manual-installation-instructions) From 580646eb3ef1396d7608de295b37820bfffd096d Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 24 Sep 2022 19:21:18 -0500 Subject: [PATCH 38/39] Fixes #255 --- src/seedsigner/views/seed_views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index e37a9d04..f03a5d9d 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1282,11 +1282,12 @@ class AddressVerificationStartView(View): destination = Destination(SeedSingleSigAddressVerificationSelectSeedView) elif self.controller.unverified_address["script_type"] == SettingsConstants.TAPROOT: - destination = Destination(NotYetImplementedView) + # TODO: add Taproot support + return Destination(NotYetImplementedView) elif self.controller.unverified_address["script_type"] == SettingsConstants.LEGACY_P2PKH: # TODO: detect single sig vs multisig or have to prompt? - destination = Destination(NotYetImplementedView) + return Destination(NotYetImplementedView) derivation_path = embit_utils.get_standard_derivation_path( network=self.controller.unverified_address["network"], From 02736e09ea29c0e1e3e16f932e929d5603d080bc Mon Sep 17 00:00:00 2001 From: initfortherekt Date: Fri, 28 Oct 2022 09:59:34 -0600 Subject: [PATCH 39/39] fix(SeedReviewScreen): allow overflow for long passphrases [#263] --- src/seedsigner/gui/screens/seed_screens.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 84b05885..ebd63e3e 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -1018,6 +1018,7 @@ class SeedReviewPassphraseScreen(ButtonListScreen): font_size=font_size, is_text_centered=True, screen_y=screen_y, + allow_text_overflow=True )) screen_y += char_height + 2