From 2bc4b22384abf10062ea32fc07e57a413c775940 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Mon, 30 May 2022 22:04:57 -0500 Subject: [PATCH 01/16] 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 02/16] 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 03/16] 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 04/16] 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 cbd53fd3d91765af7975f33106f8f036d30ee806 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sun, 12 Jun 2022 15:42:35 -0500 Subject: [PATCH 05/16] 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 36635a1b7bf82b37018643fbab527fea1c3e4512 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 15 Jun 2022 15:08:24 -0500 Subject: [PATCH 06/16] 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 b8eca14c0eb10bfa85f21d35f4ced39d710af677 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 25 Jun 2022 11:54:53 -0700 Subject: [PATCH 07/16] 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 08/16] 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 67be53ad942d447e729d8dd3a3ce0abc72ae3278 Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 6 Aug 2022 20:50:54 -0500 Subject: [PATCH 09/16] 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 9113eabe72870289e1c42ea4c8a5f3e8dd63486a Mon Sep 17 00:00:00 2001 From: kdmukai Date: Sat, 13 Aug 2022 20:49:50 -0500 Subject: [PATCH 10/16] 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 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 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 11/16] 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 12/16] 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 13/16] 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 14/16] 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 15/16] 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 16/16] 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"],