qt+plugins: cleanup. remove all old wizard code
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")))
|
||||
|
||||
Reference in New Issue
Block a user