wip. trezor works for standard wallet, also for cosigners
This commit is contained in:
@@ -136,6 +136,7 @@ class PasswordLayout(object):
|
||||
and not force_disable_encrypt_cb)
|
||||
self.new_pw.textChanged.connect(enable_OK)
|
||||
self.conf_pw.textChanged.connect(enable_OK)
|
||||
enable_OK()
|
||||
|
||||
self.vbox = vbox
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer, QRect
|
||||
from PyQt5.QtCore import Qt, QTimer, QRect, pyqtSignal
|
||||
from PyQt5.QtGui import QPen, QPainter, QPalette
|
||||
from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget,
|
||||
QFileDialog, QSlider, QGridLayout)
|
||||
@@ -10,16 +13,16 @@ from electrum.bip32 import is_bip32_derivation, BIP32Node, normalize_bip32_deriv
|
||||
from electrum.daemon import Daemon
|
||||
from electrum.i18n import _
|
||||
from electrum.keystore import bip44_derivation, bip39_to_seed, purpose48_derivation
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.plugin import run_hook, HardwarePluginLibraryUnavailable
|
||||
from electrum.storage import StorageReadWriteError
|
||||
from electrum.util import WalletFileException, get_new_wallet_name
|
||||
from electrum.wallet import wallet_types
|
||||
from .wizard import QEAbstractWizard, WizardComponent
|
||||
from electrum.logging import get_logger
|
||||
from electrum.logging import get_logger, Logger
|
||||
from electrum import WalletStorage, mnemonic, keystore
|
||||
from electrum.wizard import NewWalletWizard
|
||||
from ..bip39_recovery_dialog import Bip39RecoveryDialog
|
||||
from ..password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD
|
||||
from ..password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD, PasswordLayoutForHW
|
||||
from ..seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout
|
||||
from ..util import ChoicesLayout, PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height
|
||||
|
||||
@@ -35,12 +38,17 @@ WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\
|
||||
'p2wpkh-p2sh:KxZcY47uGp9a... \t-> 3NhNeZQXF...\n' +
|
||||
'p2wpkh:KxZcY47uGp9a... \t-> bc1q3fjfk...')
|
||||
|
||||
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.")
|
||||
|
||||
|
||||
class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
def __init__(self, config: 'SimpleConfig', app: 'QElectrumApplication', plugins: 'Plugins', daemon: Daemon, path, parent=None):
|
||||
NewWalletWizard.__init__(self, daemon)
|
||||
NewWalletWizard.__init__(self, daemon, plugins)
|
||||
QEAbstractWizard.__init__(self, config, app, plugins, daemon)
|
||||
self._daemon = daemon # TODO: dedupe
|
||||
self._path = path
|
||||
@@ -53,15 +61,18 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
||||
'create_seed': { 'gui': WCCreateSeed },
|
||||
'confirm_seed': { 'gui': WCConfirmSeed },
|
||||
'have_seed': { 'gui': WCHaveSeed },
|
||||
'choose_hardware_device': { 'gui': WCChooseHWDevice },
|
||||
'script_and_derivation': { 'gui': WCScriptAndDerivation},
|
||||
'have_master_key': { 'gui': WCHaveMasterKey },
|
||||
'multisig': { 'gui': WCMultisig },
|
||||
'multisig_cosigner_keystore': { 'gui': WCCosignerKeystore },
|
||||
'multisig_cosigner_key': { 'gui': WCHaveMasterKey },
|
||||
'multisig_cosigner_seed': { 'gui': WCHaveSeed },
|
||||
'multisig_cosigner_hardware': { 'gui': WCChooseHWDevice },
|
||||
'multisig_cosigner_script_and_derivation': { 'gui': WCScriptAndDerivation},
|
||||
'imported': { 'gui': WCImport },
|
||||
'wallet_password': { 'gui': WCWalletPassword }
|
||||
'wallet_password': { 'gui': WCWalletPassword },
|
||||
'wallet_password_hardware': { 'gui': WCWalletPasswordHardware }
|
||||
})
|
||||
|
||||
# modify default flow, insert seed extension entry/confirm as separate views
|
||||
@@ -101,7 +112,7 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
||||
'next': self.on_have_cosigner_seed,
|
||||
'last': lambda d: self.is_single_password() and self.last_cosigner(d) and not self.needs_derivation_path(d),
|
||||
'gui': WCEnterExt
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
run_hook('init_wallet_wizard', self)
|
||||
@@ -565,7 +576,7 @@ class WCScriptAndDerivation(WizardComponent):
|
||||
('p2wpkh', 'native segwit (p2wpkh)', bip44_derivation(0, bip43_purpose=84)),
|
||||
]
|
||||
|
||||
if self.wizard_data['wallet_type'] == 'standard':
|
||||
if self.wizard_data['wallet_type'] == 'standard' and not self.wizard_data['keystore_type'] == 'hardware':
|
||||
button = QPushButton(_("Detect Existing Accounts"))
|
||||
|
||||
passphrase = self.wizard_data['seed_extra_words'] if self.wizard_data['seed_extend'] else ''
|
||||
@@ -619,7 +630,12 @@ class WCScriptAndDerivation(WizardComponent):
|
||||
cosigner_data = self._current_cosigner(self.wizard_data)
|
||||
derivation_valid = is_bip32_derivation(cosigner_data['derivation_path'])
|
||||
|
||||
if derivation_valid and self.wizard_data['wallet_type'] == 'multisig':
|
||||
# TODO: refactor to wizard.current_cosigner_is_hardware
|
||||
cosigner_is_hardware = cosigner_data == self.wizard_data and self.wizard_data['keystore_type'] == 'hardware'
|
||||
if 'cosigner_keystore_type' in self.wizard_data and self.wizard_data['cosigner_keystore_type'] == 'hardware':
|
||||
cosigner_is_hardware = True
|
||||
|
||||
if self.wizard.is_multisig(self.wizard_data) and derivation_valid and not cosigner_is_hardware:
|
||||
if self.wizard.has_duplicate_masterkeys(self.wizard_data):
|
||||
self._logger.debug('Duplicate master keys!')
|
||||
# TODO: user feedback
|
||||
@@ -645,7 +661,7 @@ class WCCosignerKeystore(WizardComponent):
|
||||
choices = [
|
||||
('key', _('Enter cosigner key')),
|
||||
('seed', _('Enter cosigner seed')),
|
||||
('hw_device', _('Cosign with hardware device'))
|
||||
('hardware', _('Cosign with hardware device'))
|
||||
]
|
||||
|
||||
self.c_values = [x[0] for x in choices]
|
||||
@@ -929,3 +945,184 @@ class CosignWidget(QWidget):
|
||||
qp.setBrush(Qt.green if i < self.m else Qt.gray)
|
||||
qp.drawPie(self.R, alpha, alpha2)
|
||||
qp.end()
|
||||
|
||||
|
||||
class WCChooseHWDevice(WizardComponent, Logger):
|
||||
scanFailed = pyqtSignal([str, str], arguments=['code', 'message'])
|
||||
scanComplete = pyqtSignal()
|
||||
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('Choose Hardware Device'))
|
||||
Logger.__init__(self)
|
||||
self.scanFailed.connect(self.on_scan_failed)
|
||||
self.scanComplete.connect(self.on_scan_complete)
|
||||
self.plugins = wizard.plugins
|
||||
|
||||
self.error_l = WWLabel()
|
||||
self.error_l.setVisible(False)
|
||||
|
||||
self.device_list = QWidget()
|
||||
self.device_list_layout = QVBoxLayout()
|
||||
self.device_list.setLayout(self.device_list_layout)
|
||||
self.clayout = None
|
||||
|
||||
self.rescan_button = QPushButton(_('Rescan devices'))
|
||||
self.rescan_button.clicked.connect(self.on_rescan)
|
||||
|
||||
self.layout().addWidget(self.error_l)
|
||||
self.layout().addWidget(self.device_list)
|
||||
self.layout().addStretch(1)
|
||||
self.layout().addWidget(self.rescan_button)
|
||||
|
||||
self.c_values = []
|
||||
|
||||
def on_ready(self):
|
||||
self.scan_devices()
|
||||
|
||||
def on_rescan(self):
|
||||
self.scan_devices()
|
||||
|
||||
def on_scan_failed(self, code, message):
|
||||
self.error_l.setText(message)
|
||||
self.error_l.setVisible(True)
|
||||
self.device_list.setVisible(False)
|
||||
|
||||
self.valid = False
|
||||
|
||||
def on_scan_complete(self):
|
||||
self.error_l.setVisible(False)
|
||||
self.device_list.setVisible(True)
|
||||
|
||||
choices = []
|
||||
for name, info in self.devices:
|
||||
state = _("initialized") if info.initialized else _("wiped")
|
||||
label = info.label or _("An unnamed {}").format(name)
|
||||
try:
|
||||
transport_str = info.device.transport_ui_string[:20]
|
||||
except Exception:
|
||||
transport_str = 'unknown transport'
|
||||
descr = f"{label} [{info.model_name or name}, {state}, {transport_str}]"
|
||||
choices.append(((name, info), descr))
|
||||
msg = _('Select a device') + ':'
|
||||
self.c_values = [x[0] for x in choices]
|
||||
c_titles = [x[1] for x in choices]
|
||||
# remove old component before adding anew
|
||||
a = self.device_list.layout().itemAt(0)
|
||||
self.device_list.layout().removeItem(a)
|
||||
|
||||
self.clayout = ChoicesLayout(msg, c_titles)
|
||||
self.device_list_layout = self.clayout.layout()
|
||||
self.device_list.layout().addLayout(self.device_list_layout)
|
||||
|
||||
self.valid = True
|
||||
|
||||
def failed_getting_device_infos(self, debug_msg, name, e):
|
||||
# nonlocal debug_msg
|
||||
err_str_oneline = ' // '.join(str(e).splitlines())
|
||||
self.logger.warning(f'error getting device infos for {name}: {err_str_oneline}')
|
||||
indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
|
||||
debug_msg += f' {name}: (error getting device infos)\n{indented_error_msg}\n'
|
||||
|
||||
def scan_devices(self):
|
||||
self.valid = False
|
||||
self.busy_msg = _('Scanning devices...')
|
||||
self.busy = True
|
||||
|
||||
def scan_task():
|
||||
# check available plugins
|
||||
supported_plugins = self.plugins.get_hardware_support()
|
||||
devices = [] # type: List[Tuple[str, DeviceInfo]]
|
||||
devmgr = self.plugins.device_manager
|
||||
debug_msg = ''
|
||||
|
||||
# scan devices
|
||||
try:
|
||||
# scanned_devices = self.run_task_without_blocking_gui(task=devmgr.scan_devices,
|
||||
# msg=_("Scanning devices..."))
|
||||
scanned_devices = devmgr.scan_devices()
|
||||
except BaseException as e:
|
||||
self.logger.info('error scanning devices: {}'.format(repr(e)))
|
||||
debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e)
|
||||
else:
|
||||
for splugin in supported_plugins:
|
||||
name, plugin = splugin.name, splugin.plugin
|
||||
# plugin init errored?
|
||||
if not plugin:
|
||||
e = splugin.exception
|
||||
indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
|
||||
debug_msg += f' {name}: (error during plugin init)\n'
|
||||
debug_msg += ' {}\n'.format(_('You might have an incompatible library.'))
|
||||
debug_msg += f'{indented_error_msg}\n'
|
||||
continue
|
||||
# see if plugin recognizes 'scanned_devices'
|
||||
try:
|
||||
# FIXME: side-effect: this sets client.handler
|
||||
device_infos = devmgr.list_pairable_device_infos(
|
||||
handler=None, plugin=plugin, devices=scanned_devices, include_failing_clients=True)
|
||||
except HardwarePluginLibraryUnavailable as e:
|
||||
self.failed_getting_device_infos(debug_msg, name, e)
|
||||
continue
|
||||
except BaseException as e:
|
||||
self.logger.exception('')
|
||||
self.failed_getting_device_infos(debug_msg, name, e)
|
||||
continue
|
||||
device_infos_failing = list(filter(lambda di: di.exception is not None, device_infos))
|
||||
for di in device_infos_failing:
|
||||
self.failed_getting_device_infos(debug_msg, name, di.exception)
|
||||
device_infos_working = list(filter(lambda di: di.exception is None, device_infos))
|
||||
devices += list(map(lambda x: (name, x), device_infos_working))
|
||||
if not debug_msg:
|
||||
debug_msg = ' {}'.format(_('No exceptions encountered.'))
|
||||
if not devices:
|
||||
msg = (_('No hardware device detected.') + '\n' +
|
||||
_('To trigger a rescan, press \'Rescan devices\'.') + '\n\n')
|
||||
if sys.platform == 'win32':
|
||||
msg += _('If your device is not detected on Windows, go to "Settings", "Devices", "Connected devices", '
|
||||
'and do "Remove device". Then, plug your device again.') + '\n'
|
||||
msg += _('While this is less than ideal, it might help if you run Electrum as Administrator.') + '\n'
|
||||
else:
|
||||
msg += _('On Linux, you might have to add a new permission to your udev rules.') + '\n'
|
||||
msg += '\n\n'
|
||||
msg += _('Debug message') + '\n' + debug_msg
|
||||
|
||||
self.scanFailed.emit('no_devices', msg)
|
||||
self.busy = False
|
||||
return
|
||||
|
||||
# select device
|
||||
self.devices = devices
|
||||
self.scanComplete.emit()
|
||||
self.busy = False
|
||||
|
||||
t = threading.Thread(target=scan_task, daemon=True)
|
||||
t.start()
|
||||
|
||||
def apply(self):
|
||||
if self.clayout:
|
||||
# TODO: data is not (de)serializable yet, wizard_data cannot be persisted
|
||||
self.wizard_data['hardware_device'] = self.c_values[self.clayout.selected_index()]
|
||||
self.logger.debug(repr(self.wizard_data['hardware_device']))
|
||||
|
||||
|
||||
class WCWalletPasswordHardware(WizardComponent):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('Password HW'))
|
||||
self.plugins = wizard.plugins
|
||||
|
||||
self.playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION)
|
||||
self.playout.encrypt_cb.setChecked(True)
|
||||
self.layout().addLayout(self.playout.layout())
|
||||
self.layout().addStretch(1)
|
||||
|
||||
self._valid = True
|
||||
|
||||
def apply(self):
|
||||
self.wizard_data['encrypt'] = self.playout.encrypt_cb.isChecked()
|
||||
if self.playout.encrypt_cb.isChecked():
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
device_id = _info.device.id_
|
||||
client = self.plugins.device_manager.client_by_id(device_id, scan_now=False)
|
||||
# client.handler = self.plugin.create_handler(self.wizard)
|
||||
self.wizard_data['password'] = client.get_password_for_storage_encryption()
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import threading
|
||||
from abc import abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -8,7 +9,7 @@ from PyQt5.QtWidgets import (QDialog, QPushButton, QWidget, QLabel, QVBoxLayout,
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.logging import get_logger
|
||||
from electrum.gui.qt.util import Buttons, icon_path
|
||||
from electrum.gui.qt.util import Buttons, icon_path, MessageBoxMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum.simple_config import SimpleConfig
|
||||
@@ -17,7 +18,7 @@ if TYPE_CHECKING:
|
||||
from electrum.gui.qt import QElectrumApplication
|
||||
|
||||
|
||||
class QEAbstractWizard(QDialog):
|
||||
class QEAbstractWizard(QDialog, MessageBoxMixin):
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
# def __init__(self, config: 'SimpleConfig', app: QApplication, plugins: 'Plugins', *, gui_object: 'ElectrumGui'):
|
||||
@@ -25,8 +26,11 @@ class QEAbstractWizard(QDialog):
|
||||
QDialog.__init__(self, None)
|
||||
self.app = app
|
||||
self.config = config
|
||||
self.plugins = plugins
|
||||
# self.gui_thread = gui_object.gui_thread
|
||||
# self.plugins = plugins
|
||||
|
||||
# compat
|
||||
self.gui_thread = threading.current_thread()
|
||||
|
||||
self.setMinimumSize(600, 400)
|
||||
|
||||
self.title = QLabel()
|
||||
@@ -43,9 +47,9 @@ class QEAbstractWizard(QDialog):
|
||||
|
||||
please_wait_layout = QVBoxLayout()
|
||||
please_wait_layout.addStretch(1)
|
||||
please_wait_l = QLabel(_("Please wait..."))
|
||||
please_wait_l.setAlignment(Qt.AlignCenter)
|
||||
please_wait_layout.addWidget(please_wait_l)
|
||||
self.please_wait_l = QLabel(_("Please wait..."))
|
||||
self.please_wait_l.setAlignment(Qt.AlignCenter)
|
||||
please_wait_layout.addWidget(self.please_wait_l)
|
||||
please_wait_layout.addStretch(1)
|
||||
self.please_wait = QWidget()
|
||||
self.please_wait.setLayout(please_wait_layout)
|
||||
@@ -156,6 +160,7 @@ class QEAbstractWizard(QDialog):
|
||||
self.next_button.setEnabled(page.valid)
|
||||
self.main_widget.setVisible(not page.busy and not bool(page.error))
|
||||
self.please_wait.setVisible(page.busy)
|
||||
self.please_wait_l.setText(page.busy_msg if page.busy_msg else _("Please wait..."))
|
||||
self.error_msg.setText(str(page.error))
|
||||
self.error.setVisible(not page.busy and bool(page.error))
|
||||
icon = page.params.get('icon', icon_path('electrum.png'))
|
||||
@@ -213,6 +218,7 @@ class WizardComponent(QWidget):
|
||||
self.setLayout(layout if layout else QVBoxLayout(self))
|
||||
self.wizard_data = {}
|
||||
self.title = title if title is not None else 'No title'
|
||||
self.busy_msg = ''
|
||||
self.wizard = wizard
|
||||
self._error = ''
|
||||
self._valid = False
|
||||
@@ -262,6 +268,7 @@ class WizardComponent(QWidget):
|
||||
self.updated.emit(self)
|
||||
|
||||
# returns (sub)dict of current cosigner (or root if first)
|
||||
# TODO: maybe just always expose self.cosigner_data in wizardcomponent so we can avoid this call
|
||||
def _current_cosigner(self, wizard_data):
|
||||
wdata = wizard_data
|
||||
if wizard_data['wallet_type'] == 'multisig' and 'multisig_current_cosigner' in wizard_data:
|
||||
|
||||
+224
-54
@@ -8,7 +8,7 @@ from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
|
||||
QMessageBox, QFileDialog, QSlider, QTabWidget)
|
||||
|
||||
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
|
||||
OkButton, CloseButton, PasswordLineEdit, getOpenFileName)
|
||||
OkButton, CloseButton, PasswordLineEdit, getOpenFileName, ChoicesLayout)
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import hook
|
||||
|
||||
@@ -16,7 +16,9 @@ from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
|
||||
from ..hw_wallet.plugin import only_hook_if_libraries_available
|
||||
from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, TrezorInitSettings,
|
||||
PASSPHRASE_ON_DEVICE, Capability, BackupType, RecoveryDeviceType)
|
||||
|
||||
from ...gui.qt.wizard.wallet import WCScriptAndDerivation
|
||||
from ...gui.qt.wizard.wizard import WizardComponent
|
||||
from ...logging import Logger
|
||||
|
||||
PASSPHRASE_HELP_SHORT =_(
|
||||
"Passphrases allow you to access new wallets, each "
|
||||
@@ -255,10 +257,25 @@ class QtPlugin(QtPluginBase):
|
||||
keystore.thread.add(connect, on_success=show_dialog)
|
||||
|
||||
def request_trezor_init_settings(self, wizard, method, device_id):
|
||||
vbox = QVBoxLayout()
|
||||
next_enabled = True
|
||||
vbox = InitSettingsLayout(self.device_manager(), method, device_id)
|
||||
|
||||
wizard.exec_layout(vbox)
|
||||
|
||||
return TrezorInitSettings(
|
||||
word_count=vbox.bg_numwords.checkedId(),
|
||||
label=vbox.name.text(),
|
||||
pin_enabled=vbox.cb_pin.isChecked(),
|
||||
passphrase_enabled=vbox.cb_phrase.isChecked(),
|
||||
recovery_type=vbox.bg_rectype.checkedId() if vbox.bg_rectype else None,
|
||||
backup_type=vbox.bg_backuptype.checkedId(),
|
||||
no_backup=vbox.cb_no_backup.isChecked() if vbox.cb_no_backup else False,
|
||||
)
|
||||
|
||||
|
||||
class InitSettingsLayout(QVBoxLayout):
|
||||
def __init__(self, devmgr, method, device_id) -> QVBoxLayout:
|
||||
super().__init__()
|
||||
|
||||
devmgr = self.device_manager()
|
||||
client = devmgr.client_by_id(device_id)
|
||||
if not client:
|
||||
raise Exception(_("The device was disconnected."))
|
||||
@@ -269,40 +286,40 @@ class QtPlugin(QtPluginBase):
|
||||
|
||||
# label
|
||||
label = QLabel(_("Enter a label to name your device:"))
|
||||
name = QLineEdit()
|
||||
self.name = QLineEdit()
|
||||
hl = QHBoxLayout()
|
||||
hl.addWidget(label)
|
||||
hl.addWidget(name)
|
||||
hl.addWidget(self.name)
|
||||
hl.addStretch(1)
|
||||
vbox.addLayout(hl)
|
||||
self.addLayout(hl)
|
||||
|
||||
# Backup type
|
||||
gb_backuptype = QGroupBox()
|
||||
hbox_backuptype = QHBoxLayout()
|
||||
gb_backuptype.setLayout(hbox_backuptype)
|
||||
vbox.addWidget(gb_backuptype)
|
||||
self.addWidget(gb_backuptype)
|
||||
gb_backuptype.setTitle(_('Select backup type:'))
|
||||
bg_backuptype = QButtonGroup()
|
||||
self.bg_backuptype = QButtonGroup()
|
||||
|
||||
rb_single = QRadioButton(gb_backuptype)
|
||||
rb_single.setText(_('Single seed (BIP39)'))
|
||||
bg_backuptype.addButton(rb_single)
|
||||
bg_backuptype.setId(rb_single, BackupType.Bip39)
|
||||
self.bg_backuptype.addButton(rb_single)
|
||||
self.bg_backuptype.setId(rb_single, BackupType.Bip39)
|
||||
hbox_backuptype.addWidget(rb_single)
|
||||
rb_single.setChecked(True)
|
||||
|
||||
rb_shamir = QRadioButton(gb_backuptype)
|
||||
rb_shamir.setText(_('Shamir'))
|
||||
bg_backuptype.addButton(rb_shamir)
|
||||
bg_backuptype.setId(rb_shamir, BackupType.Slip39_Basic)
|
||||
self.bg_backuptype.addButton(rb_shamir)
|
||||
self.bg_backuptype.setId(rb_shamir, BackupType.Slip39_Basic)
|
||||
hbox_backuptype.addWidget(rb_shamir)
|
||||
rb_shamir.setEnabled(Capability.Shamir in capabilities)
|
||||
rb_shamir.setVisible(False) # visible with "expert settings"
|
||||
|
||||
rb_shamir_groups = QRadioButton(gb_backuptype)
|
||||
rb_shamir_groups.setText(_('Super Shamir'))
|
||||
bg_backuptype.addButton(rb_shamir_groups)
|
||||
bg_backuptype.setId(rb_shamir_groups, BackupType.Slip39_Advanced)
|
||||
self.bg_backuptype.addButton(rb_shamir_groups)
|
||||
self.bg_backuptype.setId(rb_shamir_groups, BackupType.Slip39_Advanced)
|
||||
hbox_backuptype.addWidget(rb_shamir_groups)
|
||||
rb_shamir_groups.setEnabled(Capability.ShamirGroups in capabilities)
|
||||
rb_shamir_groups.setVisible(False) # visible with "expert settings"
|
||||
@@ -313,15 +330,15 @@ class QtPlugin(QtPluginBase):
|
||||
gb_numwords = QGroupBox()
|
||||
hbox1 = QHBoxLayout()
|
||||
gb_numwords.setLayout(hbox1)
|
||||
vbox.addWidget(gb_numwords)
|
||||
self.addWidget(gb_numwords)
|
||||
gb_numwords.setTitle(_("Select seed/share length:"))
|
||||
bg_numwords = QButtonGroup()
|
||||
self.bg_numwords = QButtonGroup()
|
||||
for count in (12, 18, 20, 24, 33):
|
||||
rb = QRadioButton(gb_numwords)
|
||||
word_count_buttons[count] = rb
|
||||
rb.setText(_("{:d} words").format(count))
|
||||
bg_numwords.addButton(rb)
|
||||
bg_numwords.setId(rb, count)
|
||||
self.bg_numwords.addButton(rb)
|
||||
self.bg_numwords.setId(rb, count)
|
||||
hbox1.addWidget(rb)
|
||||
rb.setChecked(True)
|
||||
|
||||
@@ -348,7 +365,7 @@ class QtPlugin(QtPluginBase):
|
||||
for c, btn in word_count_buttons.items():
|
||||
btn.setVisible(c in valid_word_counts)
|
||||
|
||||
bg_backuptype.buttonClicked.connect(configure_word_counts)
|
||||
self.bg_backuptype.buttonClicked.connect(configure_word_counts)
|
||||
configure_word_counts()
|
||||
|
||||
# set up conditional visibility:
|
||||
@@ -359,10 +376,10 @@ class QtPlugin(QtPluginBase):
|
||||
gb_numwords.setVisible(False)
|
||||
|
||||
# PIN
|
||||
cb_pin = QCheckBox(_('Enable PIN protection'))
|
||||
cb_pin.setChecked(True)
|
||||
vbox.addWidget(WWLabel(RECOMMEND_PIN))
|
||||
vbox.addWidget(cb_pin)
|
||||
self.cb_pin = QCheckBox(_('Enable PIN protection'))
|
||||
self.cb_pin.setChecked(True)
|
||||
self.addWidget(WWLabel(RECOMMEND_PIN))
|
||||
self.addWidget(self.cb_pin)
|
||||
|
||||
# "expert settings" button
|
||||
expert_vbox = QVBoxLayout()
|
||||
@@ -376,66 +393,55 @@ class QtPlugin(QtPluginBase):
|
||||
rb_shamir.setVisible(True)
|
||||
rb_shamir_groups.setVisible(True)
|
||||
expert_button.clicked.connect(show_expert_settings)
|
||||
vbox.addWidget(expert_button)
|
||||
self.addWidget(expert_button)
|
||||
|
||||
# passphrase
|
||||
passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
|
||||
passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
|
||||
passphrase_warning.setStyleSheet("color: red")
|
||||
cb_phrase = QCheckBox(_('Enable passphrases'))
|
||||
cb_phrase.setChecked(False)
|
||||
self.cb_phrase = QCheckBox(_('Enable passphrases'))
|
||||
self.cb_phrase.setChecked(False)
|
||||
expert_vbox.addWidget(passphrase_msg)
|
||||
expert_vbox.addWidget(passphrase_warning)
|
||||
expert_vbox.addWidget(cb_phrase)
|
||||
expert_vbox.addWidget(self.cb_phrase)
|
||||
|
||||
# ask for recovery type (random word order OR matrix)
|
||||
bg_rectype = None
|
||||
self.bg_rectype = None
|
||||
if method == TIM_RECOVER and model == '1':
|
||||
gb_rectype = QGroupBox()
|
||||
hbox_rectype = QHBoxLayout()
|
||||
gb_rectype.setLayout(hbox_rectype)
|
||||
expert_vbox.addWidget(gb_rectype)
|
||||
gb_rectype.setTitle(_("Select recovery type:"))
|
||||
bg_rectype = QButtonGroup()
|
||||
self.bg_rectype = QButtonGroup()
|
||||
|
||||
rb1 = QRadioButton(gb_rectype)
|
||||
rb1.setText(_('Scrambled words'))
|
||||
bg_rectype.addButton(rb1)
|
||||
bg_rectype.setId(rb1, RecoveryDeviceType.ScrambledWords)
|
||||
self.bg_rectype.addButton(rb1)
|
||||
self.bg_rectype.setId(rb1, RecoveryDeviceType.ScrambledWords)
|
||||
hbox_rectype.addWidget(rb1)
|
||||
rb1.setChecked(True)
|
||||
|
||||
rb2 = QRadioButton(gb_rectype)
|
||||
rb2.setText(_('Matrix'))
|
||||
bg_rectype.addButton(rb2)
|
||||
bg_rectype.setId(rb2, RecoveryDeviceType.Matrix)
|
||||
self.bg_rectype.addButton(rb2)
|
||||
self.bg_rectype.setId(rb2, RecoveryDeviceType.Matrix)
|
||||
hbox_rectype.addWidget(rb2)
|
||||
|
||||
# no backup
|
||||
cb_no_backup = None
|
||||
self.cb_no_backup = None
|
||||
if method == TIM_NEW:
|
||||
cb_no_backup = QCheckBox(f'''{_('Enable seedless mode')}''')
|
||||
cb_no_backup.setChecked(False)
|
||||
self.cb_no_backup = QCheckBox(f'''{_('Enable seedless mode')}''')
|
||||
self.cb_no_backup.setChecked(False)
|
||||
if (model == '1' and fw_version >= (1, 7, 1)
|
||||
or model == 'T' and fw_version >= (2, 0, 9)):
|
||||
cb_no_backup.setToolTip(SEEDLESS_MODE_WARNING)
|
||||
self.cb_no_backup.setToolTip(SEEDLESS_MODE_WARNING)
|
||||
else:
|
||||
cb_no_backup.setEnabled(False)
|
||||
cb_no_backup.setToolTip(_('Firmware version too old.'))
|
||||
expert_vbox.addWidget(cb_no_backup)
|
||||
self.cb_no_backup.setEnabled(False)
|
||||
self.cb_no_backup.setToolTip(_('Firmware version too old.'))
|
||||
expert_vbox.addWidget(self.cb_no_backup)
|
||||
|
||||
vbox.addWidget(expert_widget)
|
||||
wizard.exec_layout(vbox, next_enabled=next_enabled)
|
||||
|
||||
return TrezorInitSettings(
|
||||
word_count=bg_numwords.checkedId(),
|
||||
label=name.text(),
|
||||
pin_enabled=cb_pin.isChecked(),
|
||||
passphrase_enabled=cb_phrase.isChecked(),
|
||||
recovery_type=bg_rectype.checkedId() if bg_rectype else None,
|
||||
backup_type=bg_backuptype.checkedId(),
|
||||
no_backup=cb_no_backup.isChecked() if cb_no_backup else False,
|
||||
)
|
||||
self.addWidget(expert_widget)
|
||||
|
||||
|
||||
class Plugin(TrezorPlugin, QtPlugin):
|
||||
@@ -450,6 +456,22 @@ class Plugin(TrezorPlugin, QtPlugin):
|
||||
from trezorlib.qt.pinmatrix import PinMatrixWidget
|
||||
return PinMatrixWidget
|
||||
|
||||
@hook
|
||||
def init_wallet_wizard(self, wizard: 'QEWalletWizard'):
|
||||
self.extend_wizard(wizard)
|
||||
|
||||
# insert trezor pages in new wallet wizard
|
||||
def extend_wizard(self, wizard: 'NewWalletWizard'):
|
||||
super().extend_wizard(wizard)
|
||||
views = {
|
||||
'trezor_start': { 'gui': WCScriptAndDerivation },
|
||||
'trezor_xpub': { 'gui': WCTrezorXPub },
|
||||
'trezor_not_initialized': { 'gui': WCTrezorInitMethod },
|
||||
'trezor_choose_new_recover': { 'gui': WCTrezorInitParams },
|
||||
'trezor_do_init': { 'gui': WCTrezorInit },
|
||||
}
|
||||
wizard.navmap_merge(views)
|
||||
|
||||
|
||||
class SettingsDialog(WindowModalDialog):
|
||||
'''This dialog doesn't require a device be paired with a wallet.
|
||||
@@ -767,3 +789,151 @@ class SettingsDialog(WindowModalDialog):
|
||||
|
||||
# Update information
|
||||
invoke_client(None)
|
||||
|
||||
|
||||
class WCTrezorXPub(WizardComponent, Logger):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('Hardware wallet information'))
|
||||
Logger.__init__(self)
|
||||
self.plugins = wizard.plugins
|
||||
self.plugin = self.plugins.get_plugin('trezor')
|
||||
self._busy = True
|
||||
self.xpub = None
|
||||
|
||||
self.ok_l = WWLabel('Retrieved Hardware Information')
|
||||
self.ok_l.setAlignment(Qt.AlignCenter)
|
||||
self.layout().addWidget(self.ok_l)
|
||||
|
||||
def on_ready(self):
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
device_id = _info.device.id_
|
||||
client = self.plugins.device_manager.client_by_id(device_id, scan_now=False)
|
||||
client.handler = self.plugin.create_handler(self.wizard)
|
||||
|
||||
cosigner = self._current_cosigner(self.wizard_data)
|
||||
xtype = cosigner['script_type']
|
||||
derivation = cosigner['derivation_path']
|
||||
|
||||
def get_xpub_task(client, derivation, xtype):
|
||||
try:
|
||||
self.xpub = client.get_xpub(derivation, xtype)
|
||||
self.root_fingerprint = client.request_root_fingerprint_from_device()
|
||||
self.label = client.label()
|
||||
self.soft_device_id = client.get_soft_device_id()
|
||||
except Exception as e:
|
||||
# TODO: handle user interaction exceptions (e.g. invalid pin) more gracefully
|
||||
self.error = repr(e)
|
||||
self.logger.error(repr(e))
|
||||
self.xpub_done()
|
||||
|
||||
t = threading.Thread(target=get_xpub_task, args=(client, derivation, xtype), daemon=True)
|
||||
t.start()
|
||||
|
||||
def xpub_done(self):
|
||||
self.logger.debug(f'Done retrieve xpub: {self.xpub}')
|
||||
self.busy = False
|
||||
self.validate()
|
||||
|
||||
def validate(self):
|
||||
if self.xpub and not self.error:
|
||||
self.valid = True
|
||||
else:
|
||||
self.valid = False
|
||||
|
||||
def apply(self):
|
||||
if self.valid:
|
||||
cosigner_data = self._current_cosigner(self.wizard_data)
|
||||
cosigner_data['hw_type'] = 'trezor'
|
||||
cosigner_data['master_key'] = self.xpub
|
||||
cosigner_data['root_fingerprint'] = self.root_fingerprint
|
||||
cosigner_data['label'] = self.label
|
||||
cosigner_data['soft_device_id'] = self.soft_device_id
|
||||
|
||||
|
||||
class WCTrezorInitMethod(WizardComponent, Logger):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('HW Setup'))
|
||||
Logger.__init__(self)
|
||||
|
||||
def on_ready(self):
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
message = _('Choose how you want to initialize your {}.').format(_info.model_name)
|
||||
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")),
|
||||
]
|
||||
self.c_values = [x[0] for x in choices]
|
||||
c_titles = [x[1] for x in choices]
|
||||
self.clayout = ChoicesLayout(message, c_titles)
|
||||
self.layout().addLayout(self.clayout.layout())
|
||||
self.layout().addStretch(1)
|
||||
|
||||
self._valid = True
|
||||
|
||||
def apply(self):
|
||||
self.wizard_data['trezor_init'] = self.c_values[self.clayout.selected_index()]
|
||||
|
||||
|
||||
class WCTrezorInitParams(WizardComponent):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('Set-up trezor'))
|
||||
self.plugins = wizard.plugins
|
||||
self._busy = True
|
||||
|
||||
def on_ready(self):
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
self.settings_layout = InitSettingsLayout(self.plugins.device_manager, self.wizard_data['trezor_init'], _info.device.id_)
|
||||
self.layout().addLayout(self.settings_layout)
|
||||
self.layout().addStretch(1)
|
||||
|
||||
self.valid = True
|
||||
self.busy = False
|
||||
|
||||
def apply(self):
|
||||
vbox = self.settings_layout
|
||||
trezor_settings = TrezorInitSettings(
|
||||
word_count=vbox.bg_numwords.checkedId(),
|
||||
label=vbox.name.text(),
|
||||
pin_enabled=vbox.cb_pin.isChecked(),
|
||||
passphrase_enabled=vbox.cb_phrase.isChecked(),
|
||||
recovery_type=vbox.bg_rectype.checkedId() if vbox.bg_rectype else None,
|
||||
backup_type=vbox.bg_backuptype.checkedId(),
|
||||
no_backup=vbox.cb_no_backup.isChecked() if vbox.cb_no_backup else False,
|
||||
)
|
||||
self.wizard_data['trezor_settings'] = trezor_settings
|
||||
|
||||
|
||||
class WCTrezorInit(WizardComponent, Logger):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('Set-up trezor'))
|
||||
Logger.__init__(self)
|
||||
self.plugins = wizard.plugins
|
||||
self.plugin = self.plugins.get_plugin('trezor')
|
||||
self._busy = True
|
||||
|
||||
def on_ready(self):
|
||||
settings = self.wizard_data['trezor_settings']
|
||||
method = self.wizard_data['trezor_init']
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
device_id = _info.device.id_
|
||||
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)
|
||||
self.init_done()
|
||||
|
||||
t = threading.Thread(
|
||||
target=initialize_device_task,
|
||||
args=(settings, method, device_id, None, client.handler),
|
||||
daemon=True)
|
||||
t.start()
|
||||
|
||||
def init_done(self):
|
||||
self.logger.info('Done initialize device')
|
||||
self.busy = False
|
||||
self.layout().addWidget(WWLabel('Done'))
|
||||
|
||||
def apply(self):
|
||||
pass
|
||||
|
||||
@@ -524,3 +524,30 @@ class TrezorPlugin(HW_PluginBase):
|
||||
for o in tx.outputs()
|
||||
]
|
||||
return t
|
||||
|
||||
# new wizard
|
||||
|
||||
def wizard_entry_for_device(self, device_info: 'DeviceInfo') -> str:
|
||||
return 'trezor_not_initialized' if not device_info.initialized else 'trezor_start'
|
||||
|
||||
# insert trezor pages in new wallet wizard
|
||||
def extend_wizard(self, wizard: 'NewWalletWizard'):
|
||||
views = {
|
||||
'trezor_start': {
|
||||
'next': 'trezor_xpub',
|
||||
},
|
||||
'trezor_xpub': {
|
||||
'next': lambda d: wizard.wallet_password_view(d) if wizard.last_cosigner(d) else 'multisig_cosigner_keystore',
|
||||
'last': lambda d: wizard.is_single_password() and wizard.last_cosigner(d)
|
||||
},
|
||||
'trezor_not_initialized': {
|
||||
'next': 'trezor_choose_new_recover',
|
||||
},
|
||||
'trezor_choose_new_recover': {
|
||||
'next': 'trezor_do_init',
|
||||
},
|
||||
'trezor_do_init': {
|
||||
'next': 'trezor_start',
|
||||
},
|
||||
}
|
||||
wizard.navmap_merge(views)
|
||||
|
||||
+66
-12
@@ -3,7 +3,9 @@ import os
|
||||
|
||||
from typing import List, NamedTuple, Any, Dict, Optional
|
||||
|
||||
from electrum.keystore import hardware_keystore
|
||||
from electrum.logging import get_logger
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.slip39 import EncryptedSeed
|
||||
from electrum.storage import WalletStorage, StorageEncryptionVersion
|
||||
from electrum.wallet_db import WalletDB
|
||||
@@ -74,8 +76,6 @@ class AbstractWizard:
|
||||
if 'next' not in nav:
|
||||
# finished
|
||||
is_finished = True
|
||||
# self.finished(wizard_data)
|
||||
# return WizardViewState(None, wizard_data, {})
|
||||
new_view = WizardViewState(None, wizard_data, {})
|
||||
else:
|
||||
view_next = nav['next']
|
||||
@@ -185,7 +185,7 @@ class NewWalletWizard(AbstractWizard):
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
def __init__(self, daemon):
|
||||
def __init__(self, daemon, plugins):
|
||||
AbstractWizard.__init__(self)
|
||||
self.navmap = {
|
||||
'wallet_name': {
|
||||
@@ -211,13 +211,16 @@ class NewWalletWizard(AbstractWizard):
|
||||
'last': lambda d: self.is_single_password() and not
|
||||
(self.needs_derivation_path(d) or self.is_multisig(d))
|
||||
},
|
||||
'choose_hardware_device': {
|
||||
'next': self.on_hardware_device,
|
||||
},
|
||||
'script_and_derivation': {
|
||||
'next': lambda d: 'wallet_password' if not self.is_multisig(d) else 'multisig_cosigner_keystore',
|
||||
'next': lambda d: self.wallet_password_view(d) if not self.is_multisig(d) else 'multisig_cosigner_keystore',
|
||||
'accept': self.maybe_master_pubkey,
|
||||
'last': lambda d: self.is_single_password() and not self.is_multisig(d)
|
||||
},
|
||||
'have_master_key': {
|
||||
'next': lambda d: 'wallet_password' if not self.is_multisig(d) else 'multisig_cosigner_keystore',
|
||||
'next': lambda d: self.wallet_password_view(d) if not self.is_multisig(d) else 'multisig_cosigner_keystore',
|
||||
'accept': self.maybe_master_pubkey,
|
||||
'last': lambda d: self.is_single_password() and not self.is_multisig(d)
|
||||
},
|
||||
@@ -228,15 +231,18 @@ class NewWalletWizard(AbstractWizard):
|
||||
'next': self.on_cosigner_keystore_type
|
||||
},
|
||||
'multisig_cosigner_key': {
|
||||
'next': lambda d: 'wallet_password' if self.last_cosigner(d) else 'multisig_cosigner_keystore',
|
||||
'next': lambda d: self.wallet_password_view(d) if self.last_cosigner(d) else 'multisig_cosigner_keystore',
|
||||
'last': lambda d: self.is_single_password() and self.last_cosigner(d)
|
||||
},
|
||||
'multisig_cosigner_seed': {
|
||||
'next': self.on_have_cosigner_seed,
|
||||
'last': lambda d: self.is_single_password() and self.last_cosigner(d) and not self.needs_derivation_path(d)
|
||||
},
|
||||
'multisig_cosigner_hardware': {
|
||||
'next': self.on_hardware_device,
|
||||
},
|
||||
'multisig_cosigner_script_and_derivation': {
|
||||
'next': lambda d: 'wallet_password' if self.last_cosigner(d) else 'multisig_cosigner_keystore',
|
||||
'next': lambda d: self.wallet_password_view(d) if self.last_cosigner(d) else 'multisig_cosigner_keystore',
|
||||
'last': lambda d: self.is_single_password() and self.last_cosigner(d)
|
||||
},
|
||||
'imported': {
|
||||
@@ -245,9 +251,13 @@ class NewWalletWizard(AbstractWizard):
|
||||
},
|
||||
'wallet_password': {
|
||||
'last': True
|
||||
},
|
||||
'wallet_password_hardware': {
|
||||
'last': True
|
||||
}
|
||||
}
|
||||
self._daemon = daemon
|
||||
self.plugins = plugins
|
||||
|
||||
def start(self, initial_data=None):
|
||||
if initial_data is None:
|
||||
@@ -292,9 +302,22 @@ class NewWalletWizard(AbstractWizard):
|
||||
return {
|
||||
'createseed': 'create_seed',
|
||||
'haveseed': 'have_seed',
|
||||
'masterkey': 'have_master_key'
|
||||
'masterkey': 'have_master_key',
|
||||
'hardware': 'choose_hardware_device'
|
||||
}.get(t)
|
||||
|
||||
def is_hardware(self, wizard_data):
|
||||
return wizard_data['keystore_type'] == 'hardware'
|
||||
|
||||
def wallet_password_view(self, wizard_data):
|
||||
return 'wallet_password_hardware' if self.is_hardware(wizard_data) else 'wallet_password'
|
||||
|
||||
def on_hardware_device(self, wizard_data):
|
||||
_type, _info = wizard_data['hardware_device']
|
||||
run_hook('init_wallet_wizard', self)
|
||||
plugin = self.plugins.get_plugin(_type)
|
||||
return plugin.wizard_entry_for_device(_info)
|
||||
|
||||
def on_have_or_confirm_seed(self, wizard_data):
|
||||
if self.needs_derivation_path(wizard_data):
|
||||
return 'script_and_derivation'
|
||||
@@ -315,7 +338,8 @@ class NewWalletWizard(AbstractWizard):
|
||||
t = wizard_data['cosigner_keystore_type']
|
||||
return {
|
||||
'key': 'multisig_cosigner_key',
|
||||
'seed': 'multisig_cosigner_seed'
|
||||
'seed': 'multisig_cosigner_seed',
|
||||
'hardware': 'multisig_cosigner_hardware'
|
||||
}.get(t)
|
||||
|
||||
def on_have_cosigner_seed(self, wizard_data):
|
||||
@@ -330,6 +354,9 @@ class NewWalletWizard(AbstractWizard):
|
||||
def last_cosigner(self, wizard_data):
|
||||
# check if we have the final number of cosigners. Doesn't check if cosigner data itself is complete
|
||||
# (should be validated by wizardcomponents)
|
||||
if not self.is_multisig(wizard_data):
|
||||
return True
|
||||
|
||||
if len(wizard_data['multisig_cosigner_data']) < (wizard_data['multisig_participants'] - 1):
|
||||
return False
|
||||
|
||||
@@ -337,8 +364,7 @@ class NewWalletWizard(AbstractWizard):
|
||||
|
||||
def has_duplicate_masterkeys(self, wizard_data) -> bool:
|
||||
"""Multisig wallets need distinct master keys. If True, need to prevent wallet-creation."""
|
||||
xpubs = []
|
||||
xpubs.append(self.keystore_from_data(wizard_data['wallet_type'], wizard_data).get_master_public_key())
|
||||
xpubs = [self.keystore_from_data(wizard_data['wallet_type'], wizard_data).get_master_public_key()]
|
||||
for cosigner in wizard_data['multisig_cosigner_data']:
|
||||
data = wizard_data['multisig_cosigner_data'][cosigner]
|
||||
xpubs.append(self.keystore_from_data(wizard_data['wallet_type'], data).get_master_public_key())
|
||||
@@ -492,13 +518,30 @@ class NewWalletWizard(AbstractWizard):
|
||||
pass
|
||||
else:
|
||||
raise Exception(f"unexpected keystore type: {type(keystore)}")
|
||||
elif data['keystore_type'] == 'hardware': # TODO: prelim impl
|
||||
k = self.hw_keystore(data)
|
||||
if isinstance(k, keystore.Xpub): # has xpub
|
||||
t1 = xpub_type(k.xpub)
|
||||
if data['wallet_type'] == 'multisig':
|
||||
if t1 not in ['standard', 'p2wsh', 'p2wsh-p2sh']:
|
||||
raise Exception('wrong key type %s' % t1)
|
||||
else:
|
||||
if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']:
|
||||
raise Exception('wrong key type %s' % t1)
|
||||
# elif isinstance(k, keystore.Old_KeyStore):
|
||||
# pass
|
||||
else:
|
||||
raise Exception(f"unexpected keystore type: {type(keystore)}")
|
||||
else:
|
||||
raise Exception('unsupported/unknown keystore_type %s' % data['keystore_type'])
|
||||
|
||||
if data['encrypt']:
|
||||
if k and k.may_have_password():
|
||||
k.update_password(None, data['password'])
|
||||
storage.set_password(data['password'], enc_version=StorageEncryptionVersion.USER_PASSWORD)
|
||||
enc_version = StorageEncryptionVersion.USER_PASSWORD
|
||||
if data['keystore_type'] == 'hardware':
|
||||
enc_version = StorageEncryptionVersion.XPUB_PASSWORD
|
||||
storage.set_password(data['password'], enc_version=enc_version)
|
||||
|
||||
db = WalletDB('', storage=storage, manual_upgrades=False)
|
||||
db.set_keystore_encryption(bool(data['password']) and data['encrypt'])
|
||||
@@ -546,6 +589,17 @@ class NewWalletWizard(AbstractWizard):
|
||||
db.load_plugins()
|
||||
db.write()
|
||||
|
||||
def hw_keystore(self, data):
|
||||
return hardware_keystore({
|
||||
'type': 'hardware',
|
||||
'hw_type': data['hw_type'],
|
||||
'derivation': data['derivation_path'],
|
||||
'root_fingerprint': data['root_fingerprint'],
|
||||
'xpub': data['master_key'],
|
||||
'label': data['label'],
|
||||
'soft_device_id': data['soft_device_id']
|
||||
})
|
||||
|
||||
|
||||
class ServerConnectWizard(AbstractWizard):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user