Browse Source

qt+plugins: cleanup. remove all old wizard code

master
Sander van Grieken 2 years ago
parent
commit
0aebc1a31e
  1. 4
      electrum/gui/qt/__init__.py
  2. 797
      electrum/gui/qt/installwizard.py
  3. 2
      electrum/gui/qt/main_window.py
  4. 1
      electrum/gui/qt/util.py
  5. 2
      electrum/keystore.py
  6. 28
      electrum/plugins/bitbox02/bitbox02.py
  7. 21
      electrum/plugins/coldcard/coldcard.py
  8. 3
      electrum/plugins/coldcard/qt.py
  9. 26
      electrum/plugins/digitalbitbox/digitalbitbox.py
  10. 35
      electrum/plugins/hw_wallet/plugin.py
  11. 24
      electrum/plugins/hw_wallet/qt.py
  12. 17
      electrum/plugins/jade/jade.py
  13. 74
      electrum/plugins/keepkey/keepkey.py
  14. 14
      electrum/plugins/keepkey/qt.py
  15. 14
      electrum/plugins/ledger/ledger.py
  16. 3
      electrum/plugins/ledger/qt.py
  17. 16
      electrum/plugins/safe_t/qt.py
  18. 74
      electrum/plugins/safe_t/safe_t.py
  19. 13
      electrum/plugins/trezor/qt.py
  20. 64
      electrum/plugins/trezor/trezor.py
  21. 21
      electrum/plugins/trustedcoin/qt.py

4
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

797
electrum/gui/qt/installwizard.py

@ -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<self.m else Qt.gray)
qp.drawPie(self.R, alpha, alpha2)
qp.end()
def wizard_dialog(func):
def func_wrapper(*args, **kwargs):
run_next = kwargs['run_next']
wizard = args[0] # type: InstallWizard
while True:
#wizard.logger.debug(f"dialog stack. len: {len(wizard._stack)}. stack: {wizard._stack}")
wizard.back_button.setText(_('Back') if wizard.can_go_back() else _('Cancel'))
# current dialog
try:
out = func(*args, **kwargs)
if type(out) is not tuple:
out = (out,)
except GoBack:
if not wizard.can_go_back():
wizard.close()
raise UserCancelled
else:
# to go back from the current dialog, we just let the caller unroll the stack:
raise
# next dialog
try:
while True:
try:
run_next(*out)
except ReRunDialog:
# restore state, and then let the loop re-run next
wizard.go_back(rerun_previous=False)
else:
break
except GoBack as e:
# to go back from the next dialog, we ask the wizard to restore state
wizard.go_back(rerun_previous=False)
# and we re-run the current dialog
if wizard.can_go_back():
# also rerun any calculations that might have populated the inputs to the current dialog,
# by going back to just after the *previous* dialog finished
raise ReRunDialog() from e
else:
continue
else:
break
return func_wrapper
class WalletAlreadyOpenInMemory(Exception):
def __init__(self, wallet: Abstract_Wallet):
super().__init__()
self.wallet = wallet
# WindowModalDialog must come first as it overrides show_error
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
accept_signal = pyqtSignal()
def __init__(self, config: 'SimpleConfig', app: QApplication, plugins: 'Plugins', *, gui_object: 'ElectrumGui'):
QDialog.__init__(self, None)
BaseWizard.__init__(self, config, plugins)
self.setWindowTitle('Electrum - ' + _('Install Wizard'))
self.app = app
self.config = config
self.gui_thread = gui_object.gui_thread
self.setMinimumSize(600, 400)
self.accept_signal.connect(self.accept)
self.title = QLabel()
self.main_widget = QWidget()
self.back_button = QPushButton(_("Back"), self)
self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel'))
self.next_button = QPushButton(_("Next"), self)
self.next_button.setDefault(True)
self.logo = QLabel()
self.please_wait = QLabel(_("Please wait..."))
self.please_wait.setAlignment(Qt.AlignCenter)
self.icon_filename = None
self.loop = QEventLoop()
self.rejected.connect(lambda: self.loop.exit(0))
self.back_button.clicked.connect(lambda: self.loop.exit(1))
self.next_button.clicked.connect(lambda: self.loop.exit(2))
outer_vbox = QVBoxLayout(self)
inner_vbox = QVBoxLayout()
inner_vbox.addWidget(self.title)
inner_vbox.addWidget(self.main_widget)
inner_vbox.addStretch(1)
inner_vbox.addWidget(self.please_wait)
inner_vbox.addStretch(1)
scroll_widget = QWidget()
scroll_widget.setLayout(inner_vbox)
scroll = QScrollArea()
scroll.setFocusPolicy(Qt.NoFocus)
scroll.setWidget(scroll_widget)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(True)
icon_vbox = QVBoxLayout()
icon_vbox.addWidget(self.logo)
icon_vbox.addStretch(1)
hbox = QHBoxLayout()
hbox.addLayout(icon_vbox)
hbox.addSpacing(5)
hbox.addWidget(scroll)
hbox.setStretchFactor(scroll, 1)
outer_vbox.addLayout(hbox)
outer_vbox.addLayout(Buttons(self.back_button, self.next_button))
self.set_icon('electrum.png')
self.show()
self.raise_()
self.refresh_gui() # Need for QT on MacOSX. Lame.
def select_storage(self, path, get_wallet_from_daemon) -> 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("<b>%s</b>"%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)

2
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

1
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

2
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

28
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,

21
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']:

3
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

26
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,

35
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'):

24
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()

17
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()

74
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

14
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()

14
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:

3
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

16
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()

74
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

13
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()

64
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

21
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")))

Loading…
Cancel
Save