d954ddf0bd
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.
641 lines
26 KiB
Python
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()
|