Files
purple-electrumwallet/electrum/plugins/trustedcoin/qt.py
T
f321x d954ddf0bd trustedcoin: qt: set higher minimumHeight for QR component
The default minimumHeight for wizard components is a bit small for the
2fa confirmation component as it only shows the QR code but not the
input field. This seems to confuse users as its not intuitive to scroll
down if there is no large text shown (as for example in the ToS component).
This change increases the minimumHeight and restores it to the previous
height once the user leaves the component again.
2025-10-22 14:12:15 +02:00

641 lines
26 KiB
Python

#!/usr/bin/env python
#
# Electrum - Lightweight Bitcoin Client
# Copyright (C) 2015 Thomas Voegtlin
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from functools import partial
import os
from typing import TYPE_CHECKING
from PyQt6.QtGui import QPixmap, QMovie, QColor
from PyQt6.QtCore import QObject, pyqtSignal, QSize, Qt
from PyQt6.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout,
QRadioButton, QCheckBox, QPushButton, QWidget)
from electrum.i18n import _
from electrum.plugin import hook
from electrum.util import InvalidPassword, ChoiceItem
from electrum.logging import Logger, get_logger
from electrum import keystore
from electrum.gui.qt.util import (WindowModalDialog, WaitingDialog, OkButton, CancelButton, Buttons, icon_path,
internal_plugin_icon_path, WWLabel, CloseButton, ColorScheme,
ChoiceWidget, PasswordLineEdit, char_width_in_lineedit)
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.wizard.wallet import (WCCreateSeed, WCConfirmSeed, WCHaveSeed, WCEnterExt, WCConfirmExt,
WalletWizardComponent)
from electrum.gui.qt.util import read_QIcon_from_bytes
from .common_qt import TrustedcoinPluginQObject
from .trustedcoin import TrustedCoinPlugin, DISCLAIMER
if TYPE_CHECKING:
from electrum.gui.qt.main_window import ElectrumWindow
from electrum.wallet import Abstract_Wallet
from electrum.gui.qt.wizard.wallet import QENewWalletWizard
class TOS(QTextEdit):
tos_signal = pyqtSignal()
error_signal = pyqtSignal(object)
class HandlerTwoFactor(QObject, Logger):
def __init__(self, plugin, window):
QObject.__init__(self)
self.plugin = plugin
self.window = window
Logger.__init__(self)
def prompt_user_for_otp(self, wallet, tx, on_success, on_failure):
if not isinstance(wallet, self.plugin.wallet_class):
return
if wallet.can_sign_without_server():
return
if not wallet.keystores['x3'].can_sign(tx, ignore_watching_only=True):
self.logger.info("twofactor: xpub3 not needed")
return
window = self.window.top_level_window()
auth_code = self.plugin.auth_dialog(window)
WaitingDialog(parent=window,
message=_('Waiting for TrustedCoin server to sign transaction...'),
task=lambda: wallet.on_otp(tx, auth_code),
on_success=lambda *args: on_success(tx),
on_error=on_failure)
class Plugin(TrustedCoinPlugin):
def __init__(self, parent, config, name):
super().__init__(parent, config, name)
@hook
def load_wallet(self, wallet: 'Abstract_Wallet', window: 'ElectrumWindow'):
if not isinstance(wallet, self.wallet_class):
return
wallet.handler_2fa = HandlerTwoFactor(self, window)
if wallet.can_sign_without_server():
msg = ' '.join([
_('This wallet was restored from seed, and it contains two master private keys.'),
_('Therefore, two-factor authentication is disabled.')
])
action = lambda: window.show_message(msg)
icon = read_QIcon_from_bytes(self.read_file("trustedcoin-status-disabled.png"))
else:
action = partial(self.settings_dialog, window)
icon = read_QIcon_from_bytes(self.read_file("trustedcoin-status.png"))
sb = window.statusBar()
button = StatusBarButton(icon, _("TrustedCoin"), action, sb.height())
sb.addPermanentWidget(button)
self.start_request_thread(window.wallet)
def auth_dialog(self, window):
d = WindowModalDialog(window, _("Authorization"))
vbox = QVBoxLayout(d)
pw = AmountEdit(None, is_int=True)
msg = _('Please enter your Google Authenticator code')
vbox.addWidget(QLabel(msg))
grid = QGridLayout()
grid.setSpacing(8)
grid.addWidget(QLabel(_('Code')), 1, 0)
grid.addWidget(pw, 1, 1)
vbox.addLayout(grid)
msg = _('If you have lost your second factor, you need to restore your wallet from seed in order to request a new code.')
label = QLabel(msg)
label.setWordWrap(1)
vbox.addWidget(label)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
if not d.exec():
return
return pw.get_amount()
def prompt_user_for_otp(self, wallet, tx, on_success, on_failure):
wallet.handler_2fa.prompt_user_for_otp(wallet, tx, on_success, on_failure)
def waiting_dialog_for_billing_info(self, window, *, on_finished=None):
def task():
return self.request_billing_info(window.wallet, suppress_connection_error=False)
def on_error(exc_info):
e = exc_info[1]
window.show_error("{header}\n{exc}\n\n{tor}"
.format(header=_('Error getting TrustedCoin account info.'),
exc=repr(e),
tor=_('If you keep experiencing network problems, try using a Tor proxy.')))
return WaitingDialog(parent=window,
message=_('Requesting account info from TrustedCoin server...'),
task=task,
on_success=on_finished,
on_error=on_error)
@hook
def abort_send(self, window):
wallet = window.wallet
if not isinstance(wallet, self.wallet_class):
return
if wallet.can_sign_without_server():
return
if wallet.billing_info is None:
self.waiting_dialog_for_billing_info(window)
return True
return False
def settings_dialog(self, window):
self.waiting_dialog_for_billing_info(window,
on_finished=partial(self.show_settings_dialog, window))
def icon_path(self, name):
return internal_plugin_icon_path(self.name, name)
def show_settings_dialog(self, window, success):
if not success:
window.show_message(_('Server not reachable.'))
return
wallet = window.wallet
d = WindowModalDialog(window, _("TrustedCoin Information"))
d.setMinimumSize(500, 200)
vbox = QVBoxLayout(d)
hbox = QHBoxLayout()
logo = QLabel()
logo.setPixmap(QPixmap(self.icon_path("trustedcoin-status.png")))
msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '<br/>'\
+ _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
label = QLabel(msg)
label.setOpenExternalLinks(1)
hbox.addStretch(10)
hbox.addWidget(logo)
hbox.addStretch(10)
hbox.addWidget(label)
hbox.addStretch(10)
vbox.addLayout(hbox)
vbox.addStretch(10)
msg = _('TrustedCoin charges a small fee to co-sign transactions. The fee depends on how many prepaid transactions you buy. An extra output is added to your transaction every time you run out of prepaid transactions.') + '<br/>'
label = QLabel(msg)
label.setWordWrap(1)
vbox.addWidget(label)
vbox.addStretch(10)
grid = QGridLayout()
vbox.addLayout(grid)
price_per_tx = wallet.price_per_tx
n_prepay = wallet.num_prepay()
i = 0
for k, v in sorted(price_per_tx.items()):
if k == 1:
continue
grid.addWidget(QLabel("Pay every %d transactions:"%k), i, 0)
grid.addWidget(QLabel(window.format_amount(v/k) + ' ' + window.base_unit() + "/tx"), i, 1)
b = QRadioButton()
b.setChecked(k == n_prepay)
def on_click(b, k):
self.config.PLUGIN_TRUSTEDCOIN_NUM_PREPAY = k
b.clicked.connect(partial(on_click, k=k))
grid.addWidget(b, i, 2)
i += 1
n = wallet.billing_info.get('tx_remaining', 0)
grid.addWidget(QLabel(_("Your wallet has {} prepaid transactions.").format(n)), i, 0)
vbox.addLayout(Buttons(CloseButton(d)))
d.exec()
@hook
def init_wallet_wizard(self, wizard: 'QENewWalletWizard'):
wizard.trustedcoin_qhelper = TrustedcoinPluginQObject(self, wizard, None)
self.extend_wizard(wizard)
if wizard.start_viewstate and wizard.start_viewstate.view.startswith('trustedcoin_'):
wizard.start_viewstate.params.update({'icon': self.icon_path('trustedcoin-wizard.png')})
def extend_wizard(self, wizard: 'QENewWalletWizard'):
super().extend_wizard(wizard)
views = {
'trustedcoin_start': {
'gui': WCDisclaimer,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_choose_seed': {
'gui': WCChooseSeed,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_create_seed': {
'gui': WCCreateSeed,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_create_ext': {
'gui': WCEnterExt,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_confirm_seed': {
'gui': WCConfirmSeed,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_confirm_ext': {
'gui': WCConfirmExt,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_have_seed': {
'gui': WCHaveSeed,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_have_ext': {
'gui': WCEnterExt,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_keep_disable': {
'gui': WCKeepDisable,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_tos': {
'gui': WCTerms,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_keystore_unlock': {
'gui': WCKeystorePassword,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
},
'trustedcoin_show_confirm_otp': {
'gui': WCShowConfirmOTP,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
}
}
wizard.navmap_merge(views)
# insert page offering choice to go online or continue on another system
ext_online = {
'trustedcoin_continue_online': {
'gui': WCContinueOnline,
'params': {'icon': self.icon_path('trustedcoin-wizard.png')},
'next': lambda d: 'trustedcoin_tos' if d['trustedcoin_go_online'] else 'wallet_password',
'accept': self.on_continue_online,
'last': lambda d: not d['trustedcoin_go_online'] and wizard.is_single_password()
},
'trustedcoin_confirm_seed': {
'next': lambda d: 'trustedcoin_confirm_ext' if wizard.wants_ext(d) else 'trustedcoin_continue_online'
},
'trustedcoin_confirm_ext': {
'next': 'trustedcoin_continue_online',
},
'trustedcoin_keep_disable': {
'next': lambda d: 'trustedcoin_continue_online' if d['trustedcoin_keepordisable'] != 'disable'
else 'wallet_password',
}
}
wizard.navmap_merge(ext_online)
def on_continue_online(self, wizard_data):
if not wizard_data['trustedcoin_go_online']:
self.logger.debug('Staying offline, create keystores here')
xprv1, xpub1, xprv2, xpub2, xpub3, short_id = self.create_keys(wizard_data)
k1 = keystore.from_xprv(xprv1)
k2 = keystore.from_xpub(xpub2)
wizard_data['x1'] = k1.dump()
wizard_data['x2'] = k2.dump()
class WCDisclaimer(WalletWizardComponent):
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Disclaimer'))
self.layout().addWidget(WWLabel('\n\n'.join(DISCLAIMER)))
self.layout().addStretch(1)
self._valid = True
def apply(self):
pass
class WCChooseSeed(WalletWizardComponent):
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Create or restore'))
message = _('Do you want to create a new seed, or restore a wallet using an existing seed?')
choices = [
ChoiceItem(key='createseed', label=_('Create a new seed')),
ChoiceItem(key='haveseed', label=_('I already have a seed')),
]
self.choice_w = ChoiceWidget(message=message, choices=choices)
self.layout().addWidget(self.choice_w)
self.layout().addStretch(1)
self._valid = True
def apply(self):
self.wizard_data['keystore_type'] = self.choice_w.selected_key
class WCTerms(WalletWizardComponent):
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Terms and conditions'))
self._has_tos = False
self.tos_e = TOS()
self.tos_e.setReadOnly(True)
self.layout().addWidget(self.tos_e)
def on_ready(self):
self.fetch_terms_and_conditions()
def fetch_terms_and_conditions(self):
self.wizard.trustedcoin_qhelper.busyChanged.connect(self.on_busy_changed)
self.wizard.trustedcoin_qhelper.termsAndConditionsRetrieved.connect(self.on_terms_retrieved)
self.wizard.trustedcoin_qhelper.termsAndConditionsError.connect(self.on_terms_error)
self.wizard.trustedcoin_qhelper.fetchTermsAndConditions()
def on_busy_changed(self):
self.busy = self.wizard.trustedcoin_qhelper.busy
def on_terms_retrieved(self, tos: str) -> None:
self._has_tos = True
self.tos_e.setText(tos)
self.validate()
def on_terms_error(self, error: str) -> None:
self.error = error
def validate(self):
self.valid = self._has_tos
def apply(self):
pass
class WCShowConfirmOTP(WalletWizardComponent):
_logger = get_logger(__name__)
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Authenticator secret'))
self._otp_verified = False
self._is_online_continuation = False
self.new_otp = QWidget()
new_otp_layout = QVBoxLayout()
scanlabel = WWLabel(_('Enter or scan into authenticator app. Then authenticate below'))
new_otp_layout.addWidget(scanlabel)
self.qr = QRCodeWidget('')
new_otp_layout.addWidget(self.qr)
self.secretlabel = WWLabel()
new_otp_layout.addWidget(self.secretlabel)
self.new_otp.setLayout(new_otp_layout)
self.exist_otp = QWidget()
exist_otp_layout = QVBoxLayout()
knownlabel = WWLabel(_('This wallet is already registered with TrustedCoin.'))
exist_otp_layout.addWidget(knownlabel)
self.knownsecretlabel = WWLabel(_('If you still have your OTP secret, then authenticate below to finalize wallet creation'))
exist_otp_layout.addWidget(self.knownsecretlabel)
self.exist_otp.setLayout(exist_otp_layout)
self.authlabelnew = WWLabel(_('Then, enter your Google Authenticator code:'))
self.authlabelexist = WWLabel(_('Google Authenticator code:'))
self.spinner = QMovie(icon_path('spinner.gif'))
self.spinner.setScaledSize(QSize(24, 24))
self.spinner.setBackgroundColor(QColor('black'))
self.spinner_l = QLabel()
self.spinner_l.setMargin(5)
self.spinner_l.setVisible(False)
self.spinner_l.setMovie(self.spinner)
self.otp_status_l = QLabel()
self.otp_status_l.setAlignment(Qt.AlignmentFlag.AlignHCenter)
self.otp_status_l.setVisible(False)
self.resetlabel = WWLabel(_('If you have lost your OTP secret, click the button below to request a new secret from the server.'))
self.button = QPushButton('Request OTP secret')
self.button.clicked.connect(self.on_request_otp)
hbox = QHBoxLayout()
hbox.addWidget(self.authlabelnew)
hbox.addWidget(self.authlabelexist)
hbox.addStretch(1)
hbox.addWidget(self.spinner_l)
self.otp_e = AmountEdit(None, is_int=True)
self.otp_e.setFocus()
self.otp_e.setMaximumWidth(150)
self.otp_e.textEdited.connect(self.on_otp_edited)
hbox.addWidget(self.otp_e)
self.layout().addWidget(self.new_otp)
self.layout().addWidget(self.exist_otp)
self.layout().addLayout(hbox)
self.layout().addWidget(self.otp_status_l)
self.layout().addWidget(self.resetlabel)
self.layout().addWidget(self.button)
self.layout().addStretch(1)
def on_ready(self):
self.wizard.trustedcoin_qhelper.busyChanged.connect(self.on_busy_changed)
self.wizard.trustedcoin_qhelper.remoteKeyError.connect(self.on_remote_key_error)
self.wizard.trustedcoin_qhelper.otpSuccess.connect(self.on_otp_success)
self.wizard.trustedcoin_qhelper.otpError.connect(self.on_otp_error)
self.wizard.trustedcoin_qhelper.remoteKeyError.connect(self.on_remote_key_error)
# set higher minHeight so the qr code and the input field are shown without scrolling
prev_height = self.wizard.height()
prev_min_height = self.wizard.minimumHeight()
def restore_prev_height():
self.wizard.setMinimumHeight(prev_min_height)
self.wizard.resize(self.wizard.width(), prev_height)
self.wizard.next_button.clicked.disconnect(restore_prev_height)
self.wizard.back_button.clicked.disconnect(restore_prev_height)
self.wizard.setMinimumHeight(530)
self.wizard.next_button.clicked.connect(restore_prev_height)
self.wizard.back_button.clicked.connect(restore_prev_height)
self._is_online_continuation = 'seed' not in self.wizard_data
if self._is_online_continuation:
self.knownsecretlabel.setText(_('Authenticate below to finalize wallet creation'))
self.wizard.trustedcoin_qhelper.createKeystore()
def update(self):
is_new = bool(self.wizard.trustedcoin_qhelper.remoteKeyState != 'wallet_known')
self.new_otp.setVisible(is_new)
self.exist_otp.setVisible(not is_new)
self.authlabelnew.setVisible(is_new)
self.authlabelexist.setVisible(not is_new)
self.authlabelexist.setEnabled(not self._otp_verified)
self.otp_e.setEnabled(not self._otp_verified)
self.resetlabel.setVisible(not is_new and not self._otp_verified and not self._is_online_continuation)
self.button.setVisible(not is_new and not self._otp_verified and not self._is_online_continuation)
if self.wizard.trustedcoin_qhelper.otpSecret:
self.secretlabel.setText(self.wizard.trustedcoin_qhelper.otpSecret)
uri = 'otpauth://totp/Electrum 2FA %s?secret=%s&digits=6' % (
os.path.basename(self.wizard_data['wallet_name']), self.wizard.trustedcoin_qhelper.otpSecret)
self.qr.setData(uri)
def on_busy_changed(self):
if not self.wizard.trustedcoin_qhelper._verifyingOtp:
self.busy = self.wizard.trustedcoin_qhelper.busy
if not self.busy:
self.update()
def on_remote_key_error(self, text):
self._logger.error(text)
self.error = text
def on_request_otp(self):
self.otp_status_l.setVisible(False)
self.wizard.trustedcoin_qhelper.resetOtpSecret()
self.update()
def on_otp_success(self):
self._otp_verified = True
self.otp_status_l.setText('Valid!')
self.otp_status_l.setVisible(True)
self.otp_status_l.setStyleSheet(ColorScheme.GREEN.as_stylesheet(False))
self.setEnabled(True)
self.spinner_l.setVisible(False)
self.spinner.stop()
self.valid = True
def on_otp_error(self, message):
self.otp_status_l.setText(message)
self.otp_status_l.setVisible(True)
self.otp_status_l.setStyleSheet(ColorScheme.RED.as_stylesheet(False))
self.setEnabled(True)
self.spinner_l.setVisible(False)
self.spinner.stop()
def on_otp_edited(self):
self.otp_status_l.setVisible(False)
text = self.otp_e.text()
if len(text) > 0:
try:
otp_int = int(text)
except ValueError:
return
if len(text) == 6:
# verify otp
self.wizard.trustedcoin_qhelper.checkOtp(self.wizard.trustedcoin_qhelper.shortId, otp_int)
self.setEnabled(False)
self.spinner_l.setVisible(True)
self.spinner.start()
self.otp_e.setText('')
def apply(self):
pass
class WCKeepDisable(WalletWizardComponent):
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Restore 2FA wallet'))
message = ' '.join([
'You are going to restore a wallet protected with two-factor authentication.',
'Do you want to keep using two-factor authentication with this wallet,',
'or do you want to disable it, and have two master private keys in your wallet?'
])
choices = [
ChoiceItem(key='keep', label=_('Keep')),
ChoiceItem(key='disable', label=_('Disable')),
]
self.choice_w = ChoiceWidget(message=message, choices=choices)
self.layout().addWidget(self.choice_w)
self.layout().addStretch(1)
self._valid = True
def apply(self):
self.wizard_data['trustedcoin_keepordisable'] = self.choice_w.selected_key
class WCContinueOnline(WalletWizardComponent):
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Continue Online'))
self.cb_online = QCheckBox(_('Go online to complete wallet creation'))
def on_ready(self):
path = os.path.join(os.path.dirname(self.wizard._daemon.config.get_wallet_path()), self.wizard_data['wallet_name'])
msg = [
_("Your wallet file is: {}.").format(path),
_("You need to be online in order to complete the creation of "
"your wallet. If you want to continue online, keep the checkbox "
"checked and press Next."),
_("If you want this system to stay offline "
"and continue the completion of the wallet on an online system, "
"uncheck the checkbox and press Finish.")
]
self.layout().addWidget(WWLabel('\n\n'.join(msg)))
self.layout().addStretch(1)
self.cb_online.setChecked(True)
self.cb_online.stateChanged.connect(self.on_updated)
# self.cb_online.setToolTip(_("Check this box to request a new secret. You will need to retype your seed."))
self.layout().addWidget(self.cb_online)
self.layout().setAlignment(self.cb_online, Qt.AlignmentFlag.AlignHCenter)
self.layout().addStretch(1)
self._valid = True
def apply(self):
self.wizard_data['trustedcoin_go_online'] = self.cb_online.isChecked()
class WCKeystorePassword(WalletWizardComponent):
def __init__(self, parent, wizard):
WalletWizardComponent.__init__(self, parent, wizard, title=_('Unlock Keystore'))
self.layout().addStretch(1)
hbox2 = QHBoxLayout()
hbox2.addStretch(1)
self.pw_e = PasswordLineEdit('', self)
self.pw_e.setFixedWidth(17 * char_width_in_lineedit())
self.pw_e.textEdited.connect(self.on_text)
pw_label = QLabel(_('Password') + ':')
hbox2.addWidget(pw_label)
hbox2.addWidget(self.pw_e)
hbox2.addStretch(1)
self.layout().addLayout(hbox2)
self.layout().addStretch(1)
self.ks = None
def on_ready(self):
self.ks = self.wizard_data['xprv1']
def on_text(self):
try:
self.ks.check_password(self.pw_e.text())
except InvalidPassword:
self.valid = False
return
self.valid = True
def apply(self):
if self.valid:
self.wizard_data['xprv1'] = self.ks.get_master_private_key(self.pw_e.text())
self.wizard_data['password'] = self.pw_e.text()