diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 7718d1489..376a4a06f 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -58,7 +58,6 @@ except ImportError as e: from electrum.i18n import _, set_language from electrum.plugin import run_hook -from electrum.base_wizard import GoBack from electrum.util import (UserCancelled, profiler, send_exception_to_crash_reporter, WalletFileException, BitcoinException, get_new_wallet_name) from electrum.wallet import Wallet, Abstract_Wallet @@ -67,7 +66,6 @@ from electrum.logging import Logger from electrum.gui import BaseElectrumGui from electrum.simple_config import SimpleConfig -from .installwizard import InstallWizard, WalletAlreadyOpenInMemory from .util import read_QIcon, ColorScheme, custom_message_box, MessageBoxMixin, WWLabel from .main_window import ElectrumWindow from .network_dialog import NetworkDialog @@ -500,8 +498,6 @@ class ElectrumGui(BaseElectrumGui, Logger): self.init_network() except UserCancelled: return - except GoBack: - return except Exception as e: self.logger.exception('') return diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py deleted file mode 100644 index b875f4e3a..000000000 --- a/electrum/gui/qt/installwizard.py +++ /dev/null @@ -1,797 +0,0 @@ -# Copyright (C) 2018 The Electrum developers -# Distributed under the MIT software license, see the accompanying -# file LICENCE or http://www.opensource.org/licenses/mit-license.php - -import os -import json -import sys -import threading -import traceback -from typing import Tuple, List, Callable, NamedTuple, Optional, TYPE_CHECKING -from functools import partial - -from PyQt5.QtCore import QRect, QEventLoop, Qt, pyqtSignal -from PyQt5.QtGui import QPalette, QPen, QPainter, QPixmap -from PyQt5.QtWidgets import (QWidget, QDialog, QLabel, QHBoxLayout, QMessageBox, - QVBoxLayout, QLineEdit, QFileDialog, QPushButton, - QGridLayout, QSlider, QScrollArea, QApplication) - -from electrum.wallet import Wallet, Abstract_Wallet -from electrum.storage import WalletStorage, StorageReadWriteError -from electrum.util import UserCancelled, InvalidPassword, WalletFileException, get_new_wallet_name -from electrum.base_wizard import BaseWizard, HWD_SETUP_DECRYPT_WALLET, GoBack, ReRunDialog -from electrum.network import Network -from electrum.i18n import _ - -from .seed_dialog import SeedLayout, KeysLayout -from .network_dialog import NetworkChoiceLayout -from .util import (MessageBoxMixin, Buttons, icon_path, ChoicesLayout, WWLabel, - InfoButton, char_width_in_lineedit, PasswordLineEdit, font_height) -from .password_dialog import PasswordLayout, PasswordLayoutForHW, PW_NEW -from .bip39_recovery_dialog import Bip39RecoveryDialog -from electrum.plugin import run_hook, Plugins - -if TYPE_CHECKING: - from electrum.simple_config import SimpleConfig - from electrum.wallet_db import WalletDB - from . import ElectrumGui - - -MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\ - + _("Leave this field empty if you want to disable encryption.") -MSG_HW_STORAGE_ENCRYPTION = _("Set wallet file encryption.") + '\n'\ - + _("Your wallet file does not contain secrets, mostly just metadata. ") \ - + _("It also contains your master public key that allows watching your addresses.") + '\n\n'\ - + _("Note: If you enable this setting, you will need your hardware device to open your wallet.") -WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' + - _('A few examples') + ':\n' + - 'p2pkh:KxZcY47uGp9a... \t-> 1DckmggQM...\n' + - 'p2wpkh-p2sh:KxZcY47uGp9a... \t-> 3NhNeZQXF...\n' + - 'p2wpkh:KxZcY47uGp9a... \t-> bc1q3fjfk...') -# note: full key is KxZcY47uGp9aVQAb6VVvuBs8SwHKgkSR2DbZUzjDzXf2N2GPhG9n -MSG_PASSPHRASE_WARN_ISSUE4566 = _("Warning") + ": "\ - + _("You have multiple consecutive whitespaces or leading/trailing " - "whitespaces in your passphrase.") + " " \ - + _("This is discouraged.") + " " \ - + _("Due to a bug, old versions of Electrum will NOT be creating the " - "same wallet as newer versions or other software.") - - -class CosignWidget(QWidget): - - def __init__(self, m, n): - QWidget.__init__(self) - self.size = max(120, 9 * font_height()) - self.R = QRect(0, 0, self.size, self.size) - self.setGeometry(self.R) - self.setMinimumHeight(self.size) - self.setMaximumHeight(self.size) - self.m = m - self.n = n - - def set_n(self, n): - self.n = n - self.update() - - def set_m(self, m): - self.m = m - self.update() - - def paintEvent(self, event): - bgcolor = self.palette().color(QPalette.Background) - pen = QPen(bgcolor, 7, Qt.SolidLine) - qp = QPainter() - qp.begin(self) - qp.setPen(pen) - qp.setRenderHint(QPainter.Antialiasing) - qp.setBrush(Qt.gray) - for i in range(self.n): - alpha = int(16* 360 * i/self.n) - alpha2 = int(16* 360 * 1/self.n) - qp.setBrush(Qt.green if i Tuple[str, Optional[WalletStorage]]: - if os.path.isdir(path): - raise Exception("wallet path cannot point to a directory") - - vbox = QVBoxLayout() - hbox = QHBoxLayout() - hbox.addWidget(QLabel(_('Wallet') + ':')) - name_e = QLineEdit() - hbox.addWidget(name_e) - button = QPushButton(_('Choose...')) - hbox.addWidget(button) - vbox.addLayout(hbox) - - msg_label = WWLabel('') - vbox.addWidget(msg_label) - hbox2 = QHBoxLayout() - pw_e = PasswordLineEdit('', self) - pw_e.setFixedWidth(17 * char_width_in_lineedit()) - pw_label = QLabel(_('Password') + ':') - hbox2.addWidget(pw_label) - hbox2.addWidget(pw_e) - hbox2.addStretch() - vbox.addLayout(hbox2) - - vbox.addSpacing(50) - vbox_create_new = QVBoxLayout() - vbox_create_new.addWidget(QLabel(_('Alternatively') + ':'), alignment=Qt.AlignLeft) - button_create_new = QPushButton(_('Create New Wallet')) - button_create_new.setMinimumWidth(120) - vbox_create_new.addWidget(button_create_new, alignment=Qt.AlignLeft) - widget_create_new = QWidget() - widget_create_new.setLayout(vbox_create_new) - vbox_create_new.setContentsMargins(0, 0, 0, 0) - vbox.addWidget(widget_create_new) - - self.set_layout(vbox, title=_('Electrum wallet')) - - temp_storage = None # type: Optional[WalletStorage] - wallet_folder = os.path.dirname(path) - - def on_choose(): - path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) - if path: - name_e.setText(path) - - def on_filename(filename): - # FIXME? "filename" might contain ".." (etc) and hence sketchy path traversals are possible - nonlocal temp_storage - temp_storage = None - msg = None - if filename: - path = os.path.join(wallet_folder, filename) - wallet_from_memory = get_wallet_from_daemon(path) - try: - if wallet_from_memory: - temp_storage = wallet_from_memory.storage # type: Optional[WalletStorage] - else: - temp_storage = WalletStorage(path) - except (StorageReadWriteError, WalletFileException) as e: - msg = _('Cannot read file') + f'\n{repr(e)}' - except Exception as e: - self.logger.exception('') - msg = _('Cannot read file') + f'\n{repr(e)}' - else: - msg = "" - self.next_button.setEnabled(temp_storage is not None) - user_needs_to_enter_password = False - if temp_storage: - if not temp_storage.file_exists(): - msg =_("This file does not exist.") + '\n' \ - + _("Press 'Next' to create this wallet, or choose another file.") - elif not wallet_from_memory: - if temp_storage.is_encrypted_with_user_pw(): - msg = _("This file is encrypted with a password.") + '\n' \ - + _('Enter your password or choose another file.') - user_needs_to_enter_password = True - elif temp_storage.is_encrypted_with_hw_device(): - msg = _("This file is encrypted using a hardware device.") + '\n' \ - + _("Press 'Next' to choose device to decrypt.") - else: - msg = _("Press 'Next' to open this wallet.") - else: - msg = _("This file is already open in memory.") + "\n" \ - + _("Press 'Next' to create/focus window.") - if msg is None: - msg = _('Cannot read file') - msg_label.setText(msg) - widget_create_new.setVisible(bool(temp_storage and temp_storage.file_exists())) - if user_needs_to_enter_password: - pw_label.show() - pw_e.show() - pw_e.setFocus() - else: - pw_label.hide() - pw_e.hide() - - button.clicked.connect(on_choose) - button_create_new.clicked.connect( - lambda: name_e.setText(get_new_wallet_name(wallet_folder))) # FIXME get_new_wallet_name might raise - name_e.textChanged.connect(on_filename) - name_e.setText(os.path.basename(path)) - - def run_user_interaction_loop(): - while True: - if self.loop.exec_() != 2: # 2 = next - raise UserCancelled() - assert temp_storage - if temp_storage.file_exists() and not temp_storage.is_encrypted(): - break - if not temp_storage.file_exists(): - break - wallet_from_memory = get_wallet_from_daemon(temp_storage.path) - if wallet_from_memory: - raise WalletAlreadyOpenInMemory(wallet_from_memory) - if temp_storage.file_exists() and temp_storage.is_encrypted(): - if temp_storage.is_encrypted_with_user_pw(): - password = pw_e.text() - try: - temp_storage.decrypt(password) - break - except InvalidPassword as e: - self.show_message(title=_('Error'), msg=str(e)) - continue - except BaseException as e: - self.logger.exception('') - self.show_message(title=_('Error'), msg=repr(e)) - raise UserCancelled() - elif temp_storage.is_encrypted_with_hw_device(): - try: - self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=temp_storage) - except InvalidPassword as e: - self.show_message(title=_('Error'), - msg=_('Failed to decrypt using this hardware device.') + '\n' + - _('If you use a passphrase, make sure it is correct.')) - self.reset_stack() - return self.select_storage(path, get_wallet_from_daemon) - except (UserCancelled, GoBack): - raise - except BaseException as e: - self.logger.exception('') - self.show_message(title=_('Error'), msg=repr(e)) - raise UserCancelled() - if temp_storage.is_past_initial_decryption(): - break - else: - raise UserCancelled() - else: - raise Exception('Unexpected encryption version') - - try: - run_user_interaction_loop() - finally: - try: - pw_e.clear() - except RuntimeError: # wrapped C/C++ object has been deleted. - pass # happens when decrypting with hw device - - return temp_storage.path, (temp_storage if temp_storage.file_exists() else None) - - def run_upgrades(self, storage: WalletStorage, db: 'WalletDB') -> None: - path = storage.path - if db.requires_split(): - self.hide() - msg = _("The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n" - "Do you want to split your wallet into multiple files?").format(path) - if not self.question(msg): - return - file_list = db.split_accounts(path) - msg = _('Your accounts have been moved to') + ':\n' + '\n'.join(file_list) + '\n\n'+ _('Do you want to delete the old file') + ':\n' + path - if self.question(msg): - os.remove(path) - self.show_warning(_('The file was removed')) - # raise now, to avoid having the old storage opened - raise UserCancelled() - - action = db.get_action() - if action and db.requires_upgrade(): - raise WalletFileException('Incomplete wallet files cannot be upgraded.') - if action: - self.hide() - msg = _("The file '{}' contains an incompletely created wallet.\n" - "Do you want to complete its creation now?").format(path) - if not self.question(msg): - if self.question(_("Do you want to delete '{}'?").format(path)): - os.remove(path) - self.show_warning(_('The file was removed')) - return - self.show() - self.data = json.loads(storage.read()) - self.run(action) - for k, v in self.data.items(): - db.put(k, v) - db.write() - return - - if db.requires_upgrade(): - self.upgrade_db(storage, db) - - def on_error(self, exc_info): - if not isinstance(exc_info[1], UserCancelled): - self.logger.error("on_error", exc_info=exc_info) - self.show_error(str(exc_info[1])) - - def set_icon(self, filename): - prior_filename, self.icon_filename = self.icon_filename, filename - self.logo.setPixmap(QPixmap(icon_path(filename)) - .scaledToWidth(60, mode=Qt.SmoothTransformation)) - return prior_filename - - def set_layout(self, layout, title=None, next_enabled=True): - self.title.setText("%s"%title if title else "") - self.title.setVisible(bool(title)) - # Get rid of any prior layout by assigning it to a temporary widget - prior_layout = self.main_widget.layout() - if prior_layout: - QWidget().setLayout(prior_layout) - self.main_widget.setLayout(layout) - self.back_button.setEnabled(True) - self.next_button.setEnabled(next_enabled) - if next_enabled: - self.next_button.setFocus() - self.main_widget.setVisible(True) - self.please_wait.setVisible(False) - - def exec_layout(self, layout, title=None, raise_on_cancel=True, - next_enabled=True, focused_widget=None): - self.set_layout(layout, title, next_enabled) - if focused_widget: - focused_widget.setFocus() - result = self.loop.exec_() - if not result and raise_on_cancel: - raise UserCancelled() - if result == 1: - raise GoBack from None - self.title.setVisible(False) - self.back_button.setEnabled(False) - self.next_button.setEnabled(False) - self.main_widget.setVisible(False) - self.please_wait.setVisible(True) - self.refresh_gui() - return result - - def refresh_gui(self): - # For some reason, to refresh the GUI this needs to be called twice - self.app.processEvents() - self.app.processEvents() - - def remove_from_recently_open(self, filename): - self.config.remove_from_recently_open(filename) - - def text_input(self, title, message, is_valid, allow_multi=False): - slayout = KeysLayout(parent=self, header_layout=message, is_valid=is_valid, - allow_multi=allow_multi, config=self.config) - self.exec_layout(slayout, title, next_enabled=False) - return slayout.get_text() - - def seed_input(self, title, message, is_seed, options): - slayout = SeedLayout( - title=message, - is_seed=is_seed, - options=options, - parent=self, - config=self.config, - ) - self.exec_layout(slayout, title, next_enabled=False) - return slayout.get_seed(), slayout.seed_type, slayout.is_ext - - @wizard_dialog - def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False, show_wif_help=False): - header_layout = QHBoxLayout() - label = WWLabel(message) - label.setMinimumWidth(400) - header_layout.addWidget(label) - if show_wif_help: - header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight) - return self.text_input(title, header_layout, is_valid, allow_multi) - - @wizard_dialog - def add_cosigner_dialog(self, run_next, index, is_valid): - title = _("Add Cosigner") + " %d"%index - message = ' '.join([ - _('Please enter the master public key (xpub) of your cosigner.'), - _('Enter their master private key (xprv) if you want to be able to sign for them.') - ]) - return self.text_input(title, message, is_valid) - - @wizard_dialog - def restore_seed_dialog(self, run_next, test): - options = [] - if self.opt_ext: - options.append('ext') - if self.opt_bip39: - options.append('bip39') - if self.opt_slip39: - options.append('slip39') - title = _('Enter Seed') - message = _('Please enter your seed phrase in order to restore your wallet.') - return self.seed_input(title, message, test, options) - - @wizard_dialog - def confirm_seed_dialog(self, run_next, seed, test): - self.app.clipboard().clear() - title = _('Confirm Seed') - message = ' '.join([ - _('Your seed is important!'), - _('If you lose your seed, your money will be permanently lost.'), - _('To make sure that you have properly saved your seed, please retype it here.') - ]) - seed, seed_type, is_ext = self.seed_input(title, message, test, None) - return seed - - @wizard_dialog - def show_seed_dialog(self, run_next, seed_text): - title = _("Your wallet generation seed is:") - slayout = SeedLayout( - seed=seed_text, - title=title, - msg=True, - options=['ext'], - config=self.config, - ) - self.exec_layout(slayout) - return slayout.is_ext - - def pw_layout(self, msg, kind, force_disable_encrypt_cb): - pw_layout = PasswordLayout( - msg=msg, kind=kind, OK_button=self.next_button, - force_disable_encrypt_cb=force_disable_encrypt_cb) - pw_layout.encrypt_cb.setChecked(True) - try: - self.exec_layout(pw_layout.layout(), focused_widget=pw_layout.new_pw) - return pw_layout.new_password(), pw_layout.encrypt_cb.isChecked() - finally: - pw_layout.clear_password_fields() - - @wizard_dialog - def request_password(self, run_next, force_disable_encrypt_cb=False): - """Request the user enter a new password and confirm it. Return - the password or None for no password.""" - return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb) - - @wizard_dialog - def request_storage_encryption(self, run_next): - playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION) - playout.encrypt_cb.setChecked(True) - self.exec_layout(playout.layout()) - return playout.encrypt_cb.isChecked() - - @wizard_dialog - def confirm_dialog(self, title, message, run_next): - self.confirm(message, title) - - def confirm(self, message, title): - label = WWLabel(message) - vbox = QVBoxLayout() - vbox.addWidget(label) - self.exec_layout(vbox, title) - - @wizard_dialog - def action_dialog(self, action, run_next): - self.run(action) - - def terminate(self, **kwargs): - self.accept_signal.emit() - - def waiting_dialog(self, task, msg, on_finished=None): - label = WWLabel(msg) - vbox = QVBoxLayout() - vbox.addSpacing(100) - label.setMinimumWidth(300) - label.setAlignment(Qt.AlignCenter) - vbox.addWidget(label) - self.set_layout(vbox, next_enabled=False) - self.back_button.setEnabled(False) - - t = threading.Thread(target=task) - t.start() - while True: - t.join(1.0/60) - if t.is_alive(): - self.refresh_gui() - else: - break - if on_finished: - on_finished() - - def run_task_without_blocking_gui(self, task, *, msg=None): - assert self.gui_thread == threading.current_thread(), 'must be called from GUI thread' - if msg is None: - msg = _("Please wait...") - - exc = None # type: Optional[Exception] - res = None - def task_wrapper(): - nonlocal exc - nonlocal res - try: - res = task() - except Exception as e: - exc = e - self.waiting_dialog(task_wrapper, msg=msg) - if exc is None: - return res - else: - raise exc - - @wizard_dialog - def choice_dialog(self, title, message, choices, run_next): - c_values = [x[0] for x in choices] - c_titles = [x[1] for x in choices] - clayout = ChoicesLayout(message, c_titles) - vbox = QVBoxLayout() - vbox.addLayout(clayout.layout()) - self.exec_layout(vbox, title) - action = c_values[clayout.selected_index()] - return action - - def query_choice(self, msg, choices): - """called by hardware wallets""" - clayout = ChoicesLayout(msg, choices) - vbox = QVBoxLayout() - vbox.addLayout(clayout.layout()) - self.exec_layout(vbox, '') - return clayout.selected_index() - - @wizard_dialog - def derivation_and_script_type_gui_specific_dialog( - self, - *, - title: str, - message1: str, - choices: List[Tuple[str, str, str]], - hide_choices: bool = False, - message2: str, - test_text: Callable[[str], int], - run_next, - default_choice_idx: int = 0, - get_account_xpub=None, - ) -> Tuple[str, str]: - vbox = QVBoxLayout() - - if get_account_xpub: - button = QPushButton(_("Detect Existing Accounts")) - def on_account_select(account): - script_type = account["script_type"] - if script_type == "p2pkh": - script_type = "standard" - button_index = c_values.index(script_type) - button = clayout.group.buttons()[button_index] - button.setChecked(True) - line.setText(account["derivation_path"]) - button.clicked.connect(lambda: Bip39RecoveryDialog(self, get_account_xpub, on_account_select)) - vbox.addWidget(button, alignment=Qt.AlignLeft) - vbox.addWidget(QLabel(_("Or"))) - - c_values = [x[0] for x in choices] - c_titles = [x[1] for x in choices] - c_default_text = [x[2] for x in choices] - def on_choice_click(clayout): - idx = clayout.selected_index() - line.setText(c_default_text[idx]) - clayout = ChoicesLayout(message1, c_titles, on_choice_click, - checked_index=default_choice_idx) - if not hide_choices: - vbox.addLayout(clayout.layout()) - - vbox.addWidget(WWLabel(message2)) - - line = QLineEdit() - def on_text_change(text): - self.next_button.setEnabled(test_text(text)) - line.textEdited.connect(on_text_change) - on_choice_click(clayout) # set default text for "line" - vbox.addWidget(line) - - self.exec_layout(vbox, title) - choice = c_values[clayout.selected_index()] - return str(line.text()), choice - - @wizard_dialog - def line_dialog(self, run_next, title, message, default, test, warning='', - presets=(), warn_issue4566=False): - vbox = QVBoxLayout() - vbox.addWidget(WWLabel(message)) - line = QLineEdit() - line.setText(default) - def f(text): - self.next_button.setEnabled(test(text)) - if warn_issue4566: - text_whitespace_normalised = ' '.join(text.split()) - warn_issue4566_label.setVisible(text != text_whitespace_normalised) - line.textEdited.connect(f) - vbox.addWidget(line) - vbox.addWidget(WWLabel(warning)) - - warn_issue4566_label = WWLabel(MSG_PASSPHRASE_WARN_ISSUE4566) - warn_issue4566_label.setVisible(False) - vbox.addWidget(warn_issue4566_label) - - for preset in presets: - button = QPushButton(preset[0]) - button.clicked.connect(lambda __, text=preset[1]: line.setText(text)) - button.setMinimumWidth(150) - hbox = QHBoxLayout() - hbox.addWidget(button, alignment=Qt.AlignCenter) - vbox.addLayout(hbox) - - self.exec_layout(vbox, title, next_enabled=test(default)) - return line.text() - - @wizard_dialog - def show_xpub_dialog(self, xpub, run_next): - msg = ' '.join([ - _("Here is your master public key."), - _("Please share it with your cosigners.") - ]) - vbox = QVBoxLayout() - layout = SeedLayout( - xpub, - title=msg, - icon=False, - for_seed_words=False, - config=self.config, - ) - vbox.addLayout(layout.layout()) - self.exec_layout(vbox, _('Master Public Key')) - return None - - def init_network(self, network: 'Network'): - message = _("Electrum communicates with remote servers to get " - "information about your transactions and addresses. The " - "servers all fulfill the same purpose only differing in " - "hardware. In most cases you simply want to let Electrum " - "pick one at random. However if you prefer feel free to " - "select a server manually.") - choices = [_("Auto connect"), _("Select server manually")] - title = _("How do you want to connect to a server? ") - clayout = ChoicesLayout(message, choices) - self.back_button.setText(_('Cancel')) - self.exec_layout(clayout.layout(), title) - r = clayout.selected_index() - if r == 1: - nlayout = NetworkChoiceLayout(network, self.config, wizard=True) - if self.exec_layout(nlayout.layout()): - nlayout.accept() - self.config.NETWORK_AUTO_CONNECT = network.auto_connect - else: - network.auto_connect = True - self.config.NETWORK_AUTO_CONNECT = True - - @wizard_dialog - def multisig_dialog(self, run_next): - cw = CosignWidget(2, 2) - n_edit = QSlider(Qt.Horizontal, self) - m_edit = QSlider(Qt.Horizontal, self) - n_edit.setMinimum(2) - n_edit.setMaximum(15) - m_edit.setMinimum(1) - m_edit.setMaximum(2) - n_edit.setValue(2) - m_edit.setValue(2) - n_label = QLabel() - m_label = QLabel() - grid = QGridLayout() - grid.addWidget(n_label, 0, 0) - grid.addWidget(n_edit, 0, 1) - grid.addWidget(m_label, 1, 0) - grid.addWidget(m_edit, 1, 1) - def on_m(m): - m_label.setText(_('Require {0} signatures').format(m)) - cw.set_m(m) - backup_warning_label.setVisible(cw.m != cw.n) - def on_n(n): - n_label.setText(_('From {0} cosigners').format(n)) - cw.set_n(n) - m_edit.setMaximum(n) - backup_warning_label.setVisible(cw.m != cw.n) - n_edit.valueChanged.connect(on_n) - m_edit.valueChanged.connect(on_m) - vbox = QVBoxLayout() - vbox.addWidget(cw) - vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:"))) - vbox.addLayout(grid) - vbox.addSpacing(2 * char_width_in_lineedit()) - backup_warning_label = WWLabel(_("Warning: to be able to restore a multisig wallet, " - "you should include the master public key for each cosigner " - "in all of your backups.")) - vbox.addWidget(backup_warning_label) - on_n(2) - on_m(2) - self.exec_layout(vbox, _("Multi-Signature Wallet")) - m = int(m_edit.value()) - n = int(n_edit.value()) - return (m, n) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 16e1ebcf0..80f654024 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -90,7 +90,7 @@ from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialo getOpenFileName, getSaveFileName, BlockingWaitingDialog, font_height) from .util import ButtonsLineEdit, ShowQRLineEdit from .util import QtEventListener, qt_event_listener, event_listener -from .installwizard import WIF_HELP_TEXT +from .wizard.wallet import WIF_HELP_TEXT from .history_list import HistoryList, HistoryModel from .update_checker import UpdateCheck, UpdateCheckThread from .channels_list import ChannelsList diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index a7a3a3355..f5b8b681a 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -38,7 +38,6 @@ from electrum.qrreader import MissingQrDetectionLib if TYPE_CHECKING: from .main_window import ElectrumWindow - from .installwizard import InstallWizard from .paytoedit import PayToEdit from electrum.simple_config import SimpleConfig diff --git a/electrum/keystore.py b/electrum/keystore.py index fdab6f644..f4d4a2188 100644 --- a/electrum/keystore.py +++ b/electrum/keystore.py @@ -58,6 +58,8 @@ if TYPE_CHECKING: class CannotDerivePubkey(Exception): pass +class ScriptTypeNotSupported(Exception): pass + def also_test_none_password(check_password_fn): """Decorator for check_password, simply to give a friendlier exception if diff --git a/electrum/plugins/bitbox02/bitbox02.py b/electrum/plugins/bitbox02/bitbox02.py index 599890421..3e3207a1e 100644 --- a/electrum/plugins/bitbox02/bitbox02.py +++ b/electrum/plugins/bitbox02/bitbox02.py @@ -9,13 +9,11 @@ from electrum import bip32, constants from electrum.i18n import _ from electrum.keystore import Hardware_KeyStore from electrum.transaction import PartialTransaction, Sighash -from electrum.wallet import Standard_Wallet, Multisig_Wallet, Deterministic_Wallet +from electrum.wallet import Multisig_Wallet, Deterministic_Wallet from electrum.util import UserFacingException -from electrum.base_wizard import ScriptTypeNotSupported, BaseWizard from electrum.logging import get_logger from electrum.plugin import Device, DeviceInfo, runs_in_hwd_thread from electrum.simple_config import SimpleConfig -from electrum.json_db import StoredDict from electrum.storage import get_derivation_used_for_hw_device_encryption from electrum.bitcoin import OnchainOutputType @@ -668,30 +666,6 @@ class BitBox02Plugin(HW_PluginBase): def create_client(self, device, handler) -> BitBox02Client: return BitBox02Client(handler, device, self.config, plugin=self) - def setup_device( - self, device_info: DeviceInfo, wizard: BaseWizard, purpose: int - ): - device_id = device_info.device.id_ - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - assert isinstance(client, BitBox02Client) - if client.bitbox02_device is None: - wizard.run_task_without_blocking_gui( - task=lambda client=client: client.pairing_dialog()) - client.fail_if_not_initialized() - return client - - def get_xpub( - self, device_id: str, derivation: str, xtype: str, wizard: BaseWizard - ): - if xtype not in self.SUPPORTED_XTYPES: - raise ScriptTypeNotSupported( - _("This type of script is not supported with {}: {}").format(self.device, xtype) - ) - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - assert isinstance(client, BitBox02Client) - assert client.bitbox02_device is not None - return client.get_xpub(derivation, xtype) - @runs_in_hwd_thread def show_address( self, diff --git a/electrum/plugins/coldcard/coldcard.py b/electrum/plugins/coldcard/coldcard.py index f55c975f2..25a7edac1 100644 --- a/electrum/plugins/coldcard/coldcard.py +++ b/electrum/plugins/coldcard/coldcard.py @@ -2,8 +2,8 @@ # Coldcard Electrum plugin main code. # # -import os, time, io -import traceback +import os +import time from typing import TYPE_CHECKING, Optional import struct @@ -15,7 +15,6 @@ from electrum.keystore import Hardware_KeyStore, KeyStoreWithMPK from electrum.transaction import PartialTransaction from electrum.wallet import Standard_Wallet, Multisig_Wallet, Abstract_Wallet from electrum.util import bfh, versiontuple, UserFacingException -from electrum.base_wizard import ScriptTypeNotSupported from electrum.logging import get_logger from ..hw_wallet import HW_PluginBase, HardwareClientBase @@ -521,22 +520,6 @@ class ColdcardPlugin(HW_PluginBase): self.logger.exception('late failure connecting to device?') return None - def setup_device(self, device_info, wizard, purpose): - device_id = device_info.device.id_ - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - return client - - def get_xpub(self, device_id, derivation, xtype, wizard): - # this seems to be part of the pairing process only, not during normal ops? - # base_wizard:on_hw_derivation - if xtype not in self.SUPPORTED_XTYPES: - raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device)) - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - client.ping_check() - - xpub = client.get_xpub(derivation, xtype) - return xpub - @runs_in_hwd_thread def get_client(self, keystore, force_pair=True, *, devices=None, allow_user_interaction=True) -> Optional['CKCCClient']: diff --git a/electrum/plugins/coldcard/qt.py b/electrum/plugins/coldcard/qt.py index 0c9d24803..d46896230 100644 --- a/electrum/plugins/coldcard/qt.py +++ b/electrum/plugins/coldcard/qt.py @@ -1,4 +1,3 @@ -import time, os from functools import partial from typing import TYPE_CHECKING @@ -7,13 +6,11 @@ from PyQt5.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayo from electrum.gui.qt.util import (WindowModalDialog, CloseButton, Buttons, getOpenFileName, getSaveFileName) -from electrum.gui.qt.transaction_dialog import TxDialog from electrum.gui.qt.main_window import ElectrumWindow from electrum.i18n import _ from electrum.plugin import hook from electrum.wallet import Multisig_Wallet -from electrum.transaction import PartialTransaction from .coldcard import ColdcardPlugin, xfp2str from ..hw_wallet.qt import QtHandlerBase, QtPluginBase diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py index 970d4658a..6da9b876c 100644 --- a/electrum/plugins/digitalbitbox/digitalbitbox.py +++ b/electrum/plugins/digitalbitbox/digitalbitbox.py @@ -30,7 +30,6 @@ from electrum.transaction import Transaction, PartialTransaction, PartialTxInput from electrum.i18n import _ from electrum.keystore import Hardware_KeyStore from electrum.util import to_string, UserCancelled, UserFacingException, bfh -from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET from electrum.network import Network from electrum.logging import get_logger from electrum.plugin import runs_in_hwd_thread, run_in_hwd_thread @@ -691,7 +690,6 @@ class DigitalBitboxPlugin(HW_PluginBase): dev.open_path(device.path) return dev - def create_client(self, device, handler): if device.interface_number == 0 or device.usage_page == 0xffff: client = self.get_dbb_device(device) @@ -701,21 +699,9 @@ class DigitalBitboxPlugin(HW_PluginBase): else: return None - - def setup_device(self, device_info, wizard, purpose): - device_id = device_info.device.id_ - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - if purpose == HWD_SETUP_NEW_WALLET: - client.setupRunning = True - wizard.run_task_without_blocking_gui( - task=lambda: client.get_xpub("m/44'/0'", 'standard')) - return client - - def is_mobile_paired(self): return ENCRYPTION_PRIVKEY_KEY in self.digitalbitbox_config - def comserver_post_notification(self, payload, *, handler: 'HardwareHandlerBase'): assert self.is_mobile_paired(), "unexpected mobile pairing error" url = 'https://digitalbitbox.com/smartverification/index.php' @@ -732,18 +718,6 @@ class DigitalBitboxPlugin(HW_PluginBase): _logger.exception("") handler.show_error(repr(e)) # repr because str(Exception()) == '' - - def get_xpub(self, device_id, derivation, xtype, wizard): - if xtype not in self.SUPPORTED_XTYPES: - raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device)) - if is_all_public_derivation(derivation): - raise Exception(f"The {self.device} does not reveal xpubs corresponding to non-hardened paths. (path: {derivation})") - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - client.check_device_dialog() - xpub = client.get_xpub(derivation, xtype) - return xpub - - def get_client(self, keystore, force_pair=True, *, devices=None, allow_user_interaction=True): client = super().get_client(keystore, force_pair, diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py index 4c347da80..76c1bb34b 100644 --- a/electrum/plugins/hw_wallet/plugin.py +++ b/electrum/plugins/hw_wallet/plugin.py @@ -24,15 +24,14 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from typing import TYPE_CHECKING, Dict, List, Union, Tuple, Sequence, Optional, Type, Iterable, Any -from functools import partial +from typing import TYPE_CHECKING, Sequence, Optional, Type, Iterable, Any -from electrum.plugin import (BasePlugin, hook, Device, DeviceMgr, DeviceInfo, +from electrum.plugin import (BasePlugin, hook, Device, DeviceMgr, assert_runs_in_hwd_thread, runs_in_hwd_thread) from electrum.i18n import _ from electrum.bitcoin import is_address, opcodes -from electrum.util import bfh, versiontuple, UserFacingException -from electrum.transaction import TxOutput, Transaction, PartialTransaction, PartialTxInput, PartialTxOutput +from electrum.util import versiontuple, UserFacingException +from electrum.transaction import TxOutput, PartialTransaction from electrum.bip32 import BIP32Node from electrum.storage import get_derivation_used_for_hw_device_encryption from electrum.keystore import Xpub, Hardware_KeyStore @@ -40,7 +39,6 @@ from electrum.keystore import Xpub, Hardware_KeyStore if TYPE_CHECKING: import threading from electrum.wallet import Abstract_Wallet - from electrum.base_wizard import BaseWizard class HW_PluginBase(BasePlugin): @@ -90,25 +88,6 @@ class HW_PluginBase(BasePlugin): if keystore.thread: keystore.thread.stop() - def scan_and_create_client_for_device(self, *, device_id: str, wizard: 'BaseWizard') -> 'HardwareClientBase': - devmgr = self.device_manager() - client = wizard.run_task_without_blocking_gui( - task=partial(devmgr.client_by_id, device_id)) - if client is None: - raise UserFacingException(_('Failed to create a client for this device.') + '\n' + - _('Make sure it is in the correct state.')) - client.handler = self.create_handler(wizard) - return client - - def setup_device(self, device_info: DeviceInfo, wizard: 'BaseWizard', purpose) -> 'HardwareClientBase': - """Called when creating a new wallet or when using the device to decrypt - an existing wallet. Select the device to use. If the device is - uninitialized, go through the initialization process. - - Runs in GUI thread. - """ - raise NotImplementedError() - def get_client(self, keystore: 'Hardware_KeyStore', force_pair: bool = True, *, devices: Sequence['Device'] = None, allow_user_interaction: bool = True) -> Optional['HardwareClientBase']: @@ -192,11 +171,8 @@ class HW_PluginBase(BasePlugin): handler: Optional['HardwareHandlerBase']) -> Optional['HardwareClientBase']: raise NotImplementedError() - def get_xpub(self, device_id: str, derivation: str, xtype, wizard: 'BaseWizard') -> str: - raise NotImplementedError() - def create_handler(self, window) -> 'HardwareHandlerBase': - # note: in Qt GUI, 'window' is either an ElectrumWindow or an InstallWizard + # note: in Qt GUI, 'window' is either an ElectrumWindow or an QENewWalletWizard raise NotImplementedError() def can_recognize_device(self, device: Device) -> bool: @@ -207,7 +183,6 @@ class HW_PluginBase(BasePlugin): class HardwareClientBase: - handler = None # type: Optional['HardwareHandlerBase'] def __init__(self, *, plugin: 'HW_PluginBase'): diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index c181900fe..aedd16d61 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -26,7 +26,7 @@ import threading from functools import partial -from typing import TYPE_CHECKING, Union, Optional, Callable, Any +from typing import TYPE_CHECKING, Union, Optional from PyQt5.QtCore import QObject, pyqtSignal, Qt from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel @@ -35,13 +35,11 @@ from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDialog, Buttons, CancelButton, TaskThread, char_width_in_lineedit, PasswordLineEdit) -from electrum.gui.qt.main_window import StatusBarButton, ElectrumWindow -from electrum.gui.qt.installwizard import InstallWizard +from electrum.gui.qt.main_window import StatusBarButton from electrum.i18n import _ from electrum.logging import Logger from electrum.util import UserCancelled, UserFacingException -from electrum.bip21 import parse_bip21_URI, InvalidBitcoinURI from electrum.plugin import hook, DeviceUnpairableError from .plugin import OutdatedHwFirmwareException, HW_PluginBase, HardwareHandlerBase @@ -49,6 +47,8 @@ from .plugin import OutdatedHwFirmwareException, HW_PluginBase, HardwareHandlerB if TYPE_CHECKING: from electrum.wallet import Abstract_Wallet from electrum.keystore import Hardware_KeyStore + from electrum.gui.qt import ElectrumWindow + from electrum.gui.qt.wizard.wallet import QENewWalletWizard # The trickiest thing about this handler was getting windows properly @@ -66,7 +66,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger): yes_no_signal = pyqtSignal(object) status_signal = pyqtSignal(object) - def __init__(self, win: Union[ElectrumWindow, InstallWizard], device: str): + def __init__(self, win: Union['ElectrumWindow', 'QENewWalletWizard'], device: str): QObject.__init__(self) Logger.__init__(self) assert win.gui_thread == threading.current_thread(), 'must be called from GUI thread' @@ -209,7 +209,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger): class QtPluginBase(object): @hook - def load_wallet(self: Union['QtPluginBase', HW_PluginBase], wallet: 'Abstract_Wallet', window: ElectrumWindow): + def load_wallet(self: Union['QtPluginBase', HW_PluginBase], wallet: 'Abstract_Wallet', window: 'ElectrumWindow'): relevant_keystores = [keystore for keystore in wallet.get_keystores() if isinstance(keystore, self.keystore_class)] if not relevant_keystores: @@ -237,14 +237,14 @@ class QtPluginBase(object): some_keystore = relevant_keystores[0] some_keystore.thread.add(trigger_pairings) - def _on_status_bar_button_click(self, *, window: ElectrumWindow, keystore: 'Hardware_KeyStore'): + def _on_status_bar_button_click(self, *, window: 'ElectrumWindow', keystore: 'Hardware_KeyStore'): try: self.show_settings_dialog(window=window, keystore=keystore) except (UserFacingException, UserCancelled) as e: exc_info = (type(e), e, e.__traceback__) self.on_task_thread_error(window=window, keystore=keystore, exc_info=exc_info) - def on_task_thread_error(self: Union['QtPluginBase', HW_PluginBase], window: ElectrumWindow, + def on_task_thread_error(self: Union['QtPluginBase', HW_PluginBase], window: 'ElectrumWindow', keystore: 'Hardware_KeyStore', exc_info): e = exc_info[1] if isinstance(e, OutdatedHwFirmwareException): @@ -261,7 +261,7 @@ class QtPluginBase(object): else: window.on_error(exc_info) - def choose_device(self: Union['QtPluginBase', HW_PluginBase], window: ElectrumWindow, + def choose_device(self: Union['QtPluginBase', HW_PluginBase], window: 'ElectrumWindow', keystore: 'Hardware_KeyStore') -> Optional[str]: '''This dialog box should be usable even if the user has forgotten their PIN or it is in bootloader mode.''' @@ -275,7 +275,7 @@ class QtPluginBase(object): device_id = info.device.id_ return device_id - def show_settings_dialog(self, window: ElectrumWindow, keystore: 'Hardware_KeyStore') -> None: + def show_settings_dialog(self, window: 'ElectrumWindow', keystore: 'Hardware_KeyStore') -> None: # default implementation (if no dialog): just try to connect to device def connect(): device_id = self.choose_device(window, keystore) @@ -283,7 +283,7 @@ class QtPluginBase(object): def add_show_address_on_hw_device_button_for_receive_addr(self, wallet: 'Abstract_Wallet', keystore: 'Hardware_KeyStore', - main_window: ElectrumWindow): + main_window: 'ElectrumWindow'): plugin = keystore.plugin receive_tab = main_window.receive_tab @@ -293,5 +293,5 @@ class QtPluginBase(object): dev_name = f"{plugin.device} ({keystore.label})" receive_tab.toolbar_menu.addAction(read_QIcon("eye1.png"), _("Show address on {}").format(dev_name), show_address) - def create_handler(self, window: Union[ElectrumWindow, InstallWizard]) -> 'QtHandlerBase': + def create_handler(self, window: Union['ElectrumWindow', 'QENewWalletWizard']) -> 'QtHandlerBase': raise NotImplementedError() diff --git a/electrum/plugins/jade/jade.py b/electrum/plugins/jade/jade.py index 2c14a9688..e2adfa3ba 100644 --- a/electrum/plugins/jade/jade.py +++ b/electrum/plugins/jade/jade.py @@ -10,7 +10,6 @@ from electrum.keystore import Hardware_KeyStore from electrum.transaction import Transaction from electrum.wallet import Multisig_Wallet from electrum.util import UserFacingException -from electrum.base_wizard import ScriptTypeNotSupported from electrum.logging import get_logger from electrum.plugin import runs_in_hwd_thread, Device from electrum.network import Network @@ -439,22 +438,6 @@ class JadePlugin(HW_PluginBase): return client - def setup_device(self, device_info, wizard, purpose): - device_id = device_info.device.id_ - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - - # Call authenticate on hww to ensure unlocked and suitable for network - # May involve user entering PIN on (or even setting up!) hardware device - wizard.run_task_without_blocking_gui(task=lambda: client.authenticate()) - return client - - def get_xpub(self, device_id, derivation, xtype, wizard): - if xtype not in self.SUPPORTED_XTYPES: - raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device)) - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - xpub = client.get_xpub(derivation, xtype) - return xpub - def show_address(self, wallet, address, keystore=None): if keystore is None: keystore = wallet.get_keystore() diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py index ea9e7a488..ecbb0444e 100644 --- a/electrum/plugins/keepkey/keepkey.py +++ b/electrum/plugins/keepkey/keepkey.py @@ -1,17 +1,13 @@ -from binascii import hexlify, unhexlify -import traceback -import sys -from typing import NamedTuple, Any, Optional, Dict, Union, List, Tuple, TYPE_CHECKING, Sequence +from typing import Optional, TYPE_CHECKING, Sequence -from electrum.util import bfh, UserCancelled, UserFacingException +from electrum.util import UserFacingException from electrum.bip32 import BIP32Node from electrum import descriptor from electrum import constants from electrum.i18n import _ -from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, Sighash +from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, Sighash from electrum.keystore import Hardware_KeyStore from electrum.plugin import Device, runs_in_hwd_thread -from electrum.base_wizard import ScriptTypeNotSupported from ..hw_wallet import HW_PluginBase from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data @@ -200,52 +196,8 @@ class KeepKeyPlugin(HW_PluginBase): def get_coin_name(self): return "Testnet" if constants.net.TESTNET else "Bitcoin" - def initialize_device(self, device_id, wizard, handler): - # Initialization method - msg = _("Choose how you want to initialize your {}.\n\n" - "The first two methods are secure as no secret information " - "is entered into your computer.\n\n" - "For the last two methods you input secrets on your keyboard " - "and upload them to your {}, and so you should " - "only do those on a computer you know to be trustworthy " - "and free of malware." - ).format(self.device, self.device) - choices = [ - # Must be short as QT doesn't word-wrap radio button text - (TIM_NEW, _("Let the device generate a completely new seed randomly")), - (TIM_RECOVER, _("Recover from a seed you have previously written down")), - (TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")), - (TIM_PRIVKEY, _("Upload a master private key")) - ] - def f(method): - import threading - settings = self.request_keepkey_init_settings(wizard, method, self.device) - t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler)) - t.daemon = True - t.start() - exit_code = wizard.loop.exec_() - if exit_code != 0: - # this method (initialize_device) was called with the expectation - # of leaving the device in an initialized state when finishing. - # signal that this is not the case: - raise UserCancelled() - wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f) - - def _initialize_device_safe(self, settings, method, device_id, wizard, handler): - exit_code = 0 - try: - self._initialize_device(settings, method, device_id, wizard, handler) - except UserCancelled: - exit_code = 1 - except BaseException as e: - self.logger.exception('') - handler.show_error(repr(e)) - exit_code = 1 - finally: - wizard.loop.exit(exit_code) - @runs_in_hwd_thread - def _initialize_device(self, settings, method, device_id, wizard, handler): + def _initialize_device(self, settings, method, device_id, handler): item, label, pin_protection, passphrase_protection = settings language = 'english' @@ -284,24 +236,6 @@ class KeepKeyPlugin(HW_PluginBase): ) return self.types.HDNodePathType(node=node, address_n=address_n) - def setup_device(self, device_info, wizard, purpose): - device_id = device_info.device.id_ - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - if not device_info.initialized: - self.initialize_device(device_id, wizard, client.handler) - wizard.run_task_without_blocking_gui( - task=lambda: client.get_xpub("m", 'standard')) - client.used() - return client - - def get_xpub(self, device_id, derivation, xtype, wizard): - if xtype not in self.SUPPORTED_XTYPES: - raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device)) - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - xpub = client.get_xpub(derivation, xtype) - client.used() - return xpub - def get_keepkey_input_script_type(self, electrum_txin_type: str): if electrum_txin_type in ('p2wpkh', 'p2wsh'): return self.types.SPENDWITNESS diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py index 8990592f5..acebce3f1 100644 --- a/electrum/plugins/keepkey/qt.py +++ b/electrum/plugins/keepkey/qt.py @@ -221,14 +221,6 @@ class QtPlugin(QtPluginBase): SettingsDialog(window, self, keystore, device_id).exec_() keystore.thread.add(connect, on_success=show_dialog) - def request_keepkey_init_settings(self, wizard, method, device): - keepkey_init_layout = KeepkeyInitLayout(method, device) - keepkey_init_layout.validChanged.connect(wizard.next_button.setEnabled) - next_enabled = method != TIM_PRIVKEY - wizard.exec_layout(keepkey_init_layout, next_enabled=next_enabled) - - return keepkey_init_layout.get_settings() - def clean_text(widget): text = widget.toPlainText().strip() @@ -678,13 +670,13 @@ class WCKeepkeyInit(WizardComponent, Logger): client = self.plugins.device_manager.client_by_id(device_id, scan_now=False) client.handler = self.plugin.create_handler(self.wizard) - def initialize_device_task(settings, method, device_id, wizard, handler): - self.plugin._initialize_device(settings, method, device_id, wizard, handler) + def initialize_device_task(settings, method, device_id, handler): + self.plugin._initialize_device(settings, method, device_id, handler) self.init_done() t = threading.Thread( target=initialize_device_task, - args=(settings, method, device_id, None, client.handler), + args=(settings, method, device_id, client.handler), daemon=True) t.start() diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index c2c8765ed..da2728ee4 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -8,7 +8,6 @@ from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING from electrum import bip32, constants, ecc from electrum import descriptor -from electrum.base_wizard import ScriptTypeNotSupported from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath, normalize_bip32_derivation from electrum.bitcoin import EncodeBase58Check, int_to_hex, is_b58_address, is_segwit_script_type, var_int from electrum.crypto import hash_160 @@ -1441,19 +1440,6 @@ class LedgerPlugin(HW_PluginBase): self.logger.info(f"cannot connect at {device.path} {e}", exc_info=e) return None - def setup_device(self, device_info, wizard, purpose): - device_id = device_info.device.id_ - client: Ledger_Client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - wizard.run_task_without_blocking_gui( - task=lambda: client.get_master_fingerprint()) - return client - - def get_xpub(self, device_id, derivation, xtype, wizard): - if xtype not in self.SUPPORTED_XTYPES: - raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device)) - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - return client.get_xpub(derivation, xtype) - @runs_in_hwd_thread def show_address(self, wallet, address, keystore=None): if keystore is None: diff --git a/electrum/plugins/ledger/qt.py b/electrum/plugins/ledger/qt.py index fd4ba61e6..b1014f77c 100644 --- a/electrum/plugins/ledger/qt.py +++ b/electrum/plugins/ledger/qt.py @@ -2,12 +2,11 @@ from functools import partial from typing import TYPE_CHECKING from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QInputDialog, QLabel, QVBoxLayout, QLineEdit +from PyQt5.QtWidgets import QInputDialog, QLineEdit from electrum.i18n import _ from electrum.plugin import hook from electrum.wallet import Standard_Wallet -from electrum.gui.qt.util import WindowModalDialog from .ledger import LedgerPlugin, Ledger_Client from ..hw_wallet.qt import QtHandlerBase, QtPluginBase diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index d6a1a2f9b..ee72665a5 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -19,7 +19,7 @@ from ..hw_wallet.qt import QtHandlerBase, QtPluginBase from ..hw_wallet.plugin import only_hook_if_libraries_available from .safe_t import SafeTPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY -from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUninitialized, WCHWUnlock, WCHWXPub +from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWXPub from electrum.gui.qt.wizard.wizard import WizardComponent if TYPE_CHECKING: @@ -98,14 +98,6 @@ class QtPlugin(QtPluginBase): SettingsDialog(window, self, keystore, device_id).exec_() keystore.thread.add(connect, on_success=show_dialog) - def request_safe_t_init_settings(self, wizard, method, device): - safe_t_init_layout = SafeTInitLayout(method, device) - safe_t_init_layout.validChanged.connect(wizard.next_button.setEnabled) - next_enabled = method != TIM_PRIVKEY - wizard.exec_layout(safe_t_init_layout, next_enabled=next_enabled) - - return safe_t_init_layout.get_settings() - def clean_text(widget): text = widget.toPlainText().strip() @@ -611,13 +603,13 @@ class WCSafeTInit(WizardComponent, Logger): client = self.plugins.device_manager.client_by_id(device_id, scan_now=False) client.handler = self.plugin.create_handler(self.wizard) - def initialize_device_task(settings, method, device_id, wizard, handler): - self.plugin._initialize_device(settings, method, device_id, wizard, handler) + def initialize_device_task(settings, method, device_id, handler): + self.plugin._initialize_device(settings, method, device_id, handler) self.init_done() t = threading.Thread( target=initialize_device_task, - args=(settings, method, device_id, None, client.handler), + args=(settings, method, device_id, client.handler), daemon=True) t.start() diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py index 64f4eb4e0..f26b89390 100644 --- a/electrum/plugins/safe_t/safe_t.py +++ b/electrum/plugins/safe_t/safe_t.py @@ -1,17 +1,13 @@ -from binascii import hexlify, unhexlify -import traceback -import sys -from typing import NamedTuple, Any, Optional, Dict, Union, List, Tuple, TYPE_CHECKING, Sequence +from typing import Optional, TYPE_CHECKING, Sequence -from electrum.util import bfh, versiontuple, UserCancelled, UserFacingException +from electrum.util import UserFacingException from electrum.bip32 import BIP32Node from electrum import descriptor from electrum import constants from electrum.i18n import _ from electrum.plugin import Device, runs_in_hwd_thread -from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, Sighash +from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, Sighash from electrum.keystore import Hardware_KeyStore -from electrum.base_wizard import ScriptTypeNotSupported from ..hw_wallet import HW_PluginBase from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data @@ -158,52 +154,8 @@ class SafeTPlugin(HW_PluginBase): def get_coin_name(self): return "Testnet" if constants.net.TESTNET else "Bitcoin" - def initialize_device(self, device_id, wizard, handler): - # Initialization method - msg = _("Choose how you want to initialize your {}.\n\n" - "The first two methods are secure as no secret information " - "is entered into your computer.\n\n" - "For the last two methods you input secrets on your keyboard " - "and upload them to your {}, and so you should " - "only do those on a computer you know to be trustworthy " - "and free of malware." - ).format(self.device, self.device) - choices = [ - # Must be short as QT doesn't word-wrap radio button text - (TIM_NEW, _("Let the device generate a completely new seed randomly")), - (TIM_RECOVER, _("Recover from a seed you have previously written down")), - (TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")), - (TIM_PRIVKEY, _("Upload a master private key")) - ] - def f(method): - import threading - settings = self.request_safe_t_init_settings(wizard, method, self.device) - t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler)) - t.daemon = True - t.start() - exit_code = wizard.loop.exec_() - if exit_code != 0: - # this method (initialize_device) was called with the expectation - # of leaving the device in an initialized state when finishing. - # signal that this is not the case: - raise UserCancelled() - wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f) - - def _initialize_device_safe(self, settings, method, device_id, wizard, handler): - exit_code = 0 - try: - self._initialize_device(settings, method, device_id, wizard, handler) - except UserCancelled: - exit_code = 1 - except BaseException as e: - self.logger.exception('') - handler.show_error(repr(e)) - exit_code = 1 - finally: - wizard.loop.exit(exit_code) - @runs_in_hwd_thread - def _initialize_device(self, settings, method, device_id, wizard, handler): + def _initialize_device(self, settings, method, device_id, handler): item, label, pin_protection, passphrase_protection = settings if method == TIM_RECOVER: @@ -254,24 +206,6 @@ class SafeTPlugin(HW_PluginBase): ) return self.types.HDNodePathType(node=node, address_n=address_n) - def setup_device(self, device_info, wizard, purpose): - device_id = device_info.device.id_ - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - if not device_info.initialized: - self.initialize_device(device_id, wizard, client.handler) - wizard.run_task_without_blocking_gui( - task=lambda: client.get_xpub("m", 'standard')) - client.used() - return client - - def get_xpub(self, device_id, derivation, xtype, wizard): - if xtype not in self.SUPPORTED_XTYPES: - raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device)) - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - xpub = client.get_xpub(derivation, xtype) - client.used() - return xpub - def get_safet_input_script_type(self, electrum_txin_type: str): if electrum_txin_type in ('p2wpkh', 'p2wsh'): return self.types.InputScriptType.SPENDWITNESS diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py index 501eb66d7..a46147039 100644 --- a/electrum/plugins/trezor/qt.py +++ b/electrum/plugins/trezor/qt.py @@ -262,13 +262,6 @@ class QtPlugin(QtPluginBase): SettingsDialog(window, self, keystore, device_id).exec_() keystore.thread.add(connect, on_success=show_dialog) - def request_trezor_init_settings(self, wizard, method, device_id): - vbox = InitSettingsLayout(self.device_manager(), method, device_id) - - wizard.exec_layout(vbox) - - return vbox.get_settings() - class InitSettingsLayout(QVBoxLayout): def __init__(self, devmgr, method, device_id) -> QVBoxLayout: @@ -870,13 +863,13 @@ class WCTrezorInit(WizardComponent, Logger): client = self.plugins.device_manager.client_by_id(device_id, scan_now=False) client.handler = self.plugin.create_handler(self.wizard) - def initialize_device_task(settings, method, device_id, wizard, handler): - self.plugin._initialize_device(settings, method, device_id, wizard, handler) + def initialize_device_task(settings, method, device_id, handler): + self.plugin._initialize_device(settings, method, device_id, handler) self.init_done() t = threading.Thread( target=initialize_device_task, - args=(settings, method, device_id, None, client.handler), + args=(settings, method, device_id, client.handler), daemon=True) t.start() diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py index fd86ea4b0..ed153044a 100644 --- a/electrum/plugins/trezor/trezor.py +++ b/electrum/plugins/trezor/trezor.py @@ -8,7 +8,6 @@ from electrum.i18n import _ from electrum.plugin import Device, runs_in_hwd_thread from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, Sighash from electrum.keystore import Hardware_KeyStore -from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET from electrum.logging import get_logger from electrum.plugins.hw_wallet import HW_PluginBase @@ -214,43 +213,8 @@ class TrezorPlugin(HW_PluginBase): def get_coin_name(self): return "Testnet" if constants.net.TESTNET else "Bitcoin" - def initialize_device(self, device_id, wizard, handler): - # Initialization method - msg = _("Choose how you want to initialize your {}.").format(self.device, self.device) - choices = [ - # Must be short as QT doesn't word-wrap radio button text - (TIM_NEW, _("Let the device generate a completely new seed randomly")), - (TIM_RECOVER, _("Recover from a seed you have previously written down")), - ] - def f(method): - import threading - settings = self.request_trezor_init_settings(wizard, method, device_id) - t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler)) - t.daemon = True - t.start() - exit_code = wizard.loop.exec_() - if exit_code != 0: - # this method (initialize_device) was called with the expectation - # of leaving the device in an initialized state when finishing. - # signal that this is not the case: - raise UserCancelled() - wizard.choice_dialog(title=_('Initialize Device'), message=msg, choices=choices, run_next=f) - - def _initialize_device_safe(self, settings, method, device_id, wizard, handler): - exit_code = 0 - try: - self._initialize_device(settings, method, device_id, wizard, handler) - except UserCancelled: - exit_code = 1 - except BaseException as e: - self.logger.exception('') - handler.show_error(repr(e)) - exit_code = 1 - finally: - wizard.loop.exit(exit_code) - @runs_in_hwd_thread - def _initialize_device(self, settings: TrezorInitSettings, method, device_id, wizard, handler): + def _initialize_device(self, settings: TrezorInitSettings, method, device_id, handler): if method == TIM_RECOVER and settings.recovery_type == RecoveryDeviceType.ScrambledWords: handler.show_error(_( "You will be asked to enter 24 words regardless of your " @@ -297,32 +261,6 @@ class TrezorPlugin(HW_PluginBase): ) return HDNodePathType(node=node, address_n=address_n) - def setup_device(self, device_info, wizard, purpose): - device_id = device_info.device.id_ - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - - if not client.is_uptodate(): - msg = (_('Outdated {} firmware for device labelled {}. Please ' - 'download the updated firmware from {}') - .format(self.device, client.label(), self.firmware_URL)) - raise OutdatedHwFirmwareException(msg) - - if not device_info.initialized: - self.initialize_device(device_id, wizard, client.handler) - is_creating_wallet = purpose == HWD_SETUP_NEW_WALLET - wizard.run_task_without_blocking_gui( - task=lambda: client.get_xpub('m', 'standard', creating=is_creating_wallet)) - client.used() - return client - - def get_xpub(self, device_id, derivation, xtype, wizard): - if xtype not in self.SUPPORTED_XTYPES: - raise ScriptTypeNotSupported(_('This type of script is not supported with {}.').format(self.device)) - client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard) - xpub = client.get_xpub(derivation, xtype) - client.used() - return xpub - def get_trezor_input_script_type(self, electrum_txin_type: str): if electrum_txin_type in ('p2wpkh', 'p2wsh'): return InputScriptType.SPENDWITNESS diff --git a/electrum/plugins/trustedcoin/qt.py b/electrum/plugins/trustedcoin/qt.py index 4c2e43123..b59a41fd6 100644 --- a/electrum/plugins/trustedcoin/qt.py +++ b/electrum/plugins/trustedcoin/qt.py @@ -37,7 +37,6 @@ from electrum.i18n import _ from electrum.plugin import hook from electrum.util import is_valid_email from electrum.logging import Logger, get_logger -from electrum.base_wizard import GoBack, UserCancelled from electrum import keystore from electrum.gui.qt.util import (read_QIcon, WindowModalDialog, WaitingDialog, OkButton, @@ -46,7 +45,6 @@ from electrum.gui.qt.util import (read_QIcon, WindowModalDialog, WaitingDialog, from electrum.gui.qt.qrcodewidget import QRCodeWidget from electrum.gui.qt.amountedit import AmountEdit from electrum.gui.qt.main_window import StatusBarButton -from electrum.gui.qt.installwizard import InstallWizard from electrum.gui.qt.wizard.wallet import WCCreateSeed, WCConfirmSeed, WCHaveSeed, WCEnterExt, WCConfirmExt from electrum.gui.qt.wizard.wizard import WizardComponent @@ -225,25 +223,6 @@ class Plugin(TrustedCoinPlugin): vbox.addLayout(Buttons(CloseButton(d))) d.exec_() - def go_online_dialog(self, wizard: InstallWizard): - msg = [ - _("Your wallet file is: {}.").format(os.path.abspath(wizard.path)), - _("You need to be online in order to complete the creation of " - "your wallet. If you generated your seed on an offline " - 'computer, click on "{}" to close this window, move your ' - "wallet file to an online computer, and reopen it with " - "Electrum.").format(_('Cancel')), - _('If you are online, click on "{}" to continue.').format(_('Next')) - ] - msg = '\n\n'.join(msg) - wizard.reset_stack() - try: - wizard.confirm_dialog(title='', message=msg, run_next = lambda x: wizard.run('accept_terms_of_use')) - except (GoBack, UserCancelled): - # user clicked 'Cancel' and decided to move wallet file manually - storage, db = wizard.create_storage(wizard.path) - raise - def accept_terms_of_use(self, window): vbox = QVBoxLayout() vbox.addWidget(QLabel(_("Terms of Service")))