|
|
|
|
@ -1,8 +1,6 @@
|
|
|
|
|
import os |
|
|
|
|
import sys |
|
|
|
|
import threading |
|
|
|
|
import time |
|
|
|
|
import json |
|
|
|
|
|
|
|
|
|
from typing import TYPE_CHECKING |
|
|
|
|
|
|
|
|
|
@ -17,7 +15,7 @@ from electrum.i18n import _
|
|
|
|
|
from electrum.keystore import bip44_derivation, bip39_to_seed, purpose48_derivation, ScriptTypeNotSupported |
|
|
|
|
from electrum.plugin import run_hook, HardwarePluginLibraryUnavailable |
|
|
|
|
from electrum.storage import StorageReadWriteError |
|
|
|
|
from electrum.util import WalletFileException, get_new_wallet_name, UserCancelled, UserFacingException, InvalidPassword |
|
|
|
|
from electrum.util import WalletFileException, get_new_wallet_name, UserFacingException, InvalidPassword |
|
|
|
|
from electrum.wallet import wallet_types |
|
|
|
|
from .wizard import QEAbstractWizard, WizardComponent |
|
|
|
|
from electrum.logging import get_logger, Logger |
|
|
|
|
@ -60,27 +58,28 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard, MessageBoxMixin):
|
|
|
|
|
self.setWindowTitle(_('Create/Restore wallet')) |
|
|
|
|
|
|
|
|
|
self._path = path |
|
|
|
|
self._password = None |
|
|
|
|
|
|
|
|
|
# attach gui classes to views |
|
|
|
|
self.navmap_merge({ |
|
|
|
|
'wallet_name': { 'gui': WCWalletName }, |
|
|
|
|
'wallet_type': { 'gui': WCWalletType }, |
|
|
|
|
'keystore_type': { 'gui': WCKeystoreType }, |
|
|
|
|
'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_hardware': { 'gui': WCWalletPasswordHardware } |
|
|
|
|
'wallet_name': {'gui': WCWalletName}, |
|
|
|
|
'wallet_type': {'gui': WCWalletType}, |
|
|
|
|
'keystore_type': {'gui': WCKeystoreType}, |
|
|
|
|
'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_hardware': {'gui': WCWalletPasswordHardware} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
# add open existing wallet from wizard, incl hw unlock |
|
|
|
|
@ -137,7 +136,6 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard, MessageBoxMixin):
|
|
|
|
|
|
|
|
|
|
run_hook('init_wallet_wizard', self) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def path(self): |
|
|
|
|
return self._path |
|
|
|
|
@ -211,10 +209,10 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard, MessageBoxMixin):
|
|
|
|
|
|
|
|
|
|
exc = None |
|
|
|
|
|
|
|
|
|
def task_wrap(task): |
|
|
|
|
def task_wrap(_task): |
|
|
|
|
nonlocal exc |
|
|
|
|
try: |
|
|
|
|
task() |
|
|
|
|
_task() |
|
|
|
|
except Exception as e: |
|
|
|
|
exc = e |
|
|
|
|
|
|
|
|
|
@ -303,9 +301,9 @@ class WCWalletName(WizardComponent, Logger):
|
|
|
|
|
wallet_folder = os.path.dirname(path) |
|
|
|
|
|
|
|
|
|
def on_choose(): |
|
|
|
|
path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) |
|
|
|
|
if path: |
|
|
|
|
self.name_e.setText(path) |
|
|
|
|
_path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) |
|
|
|
|
if _path: |
|
|
|
|
self.name_e.setText(_path) |
|
|
|
|
|
|
|
|
|
def on_filename(filename): |
|
|
|
|
# FIXME? "filename" might contain ".." (etc) and hence sketchy path traversals are possible |
|
|
|
|
@ -316,14 +314,14 @@ class WCWalletName(WizardComponent, Logger):
|
|
|
|
|
self.wallet_is_open = False |
|
|
|
|
self.wallet_needs_hw_unlock = False |
|
|
|
|
if filename: |
|
|
|
|
path = os.path.join(wallet_folder, filename) |
|
|
|
|
wallet_from_memory = self.wizard._daemon.get_wallet(path) |
|
|
|
|
_path = os.path.join(wallet_folder, filename) |
|
|
|
|
wallet_from_memory = self.wizard._daemon.get_wallet(_path) |
|
|
|
|
try: |
|
|
|
|
if wallet_from_memory: |
|
|
|
|
temp_storage = wallet_from_memory.storage # type: Optional[WalletStorage] |
|
|
|
|
self.wallet_is_open = True |
|
|
|
|
else: |
|
|
|
|
temp_storage = WalletStorage(path) |
|
|
|
|
temp_storage = WalletStorage(_path) |
|
|
|
|
self.wallet_exists = temp_storage.file_exists() |
|
|
|
|
except (StorageReadWriteError, WalletFileException) as e: |
|
|
|
|
msg = _('Cannot read file') + f'\n{repr(e)}' |
|
|
|
|
@ -578,6 +576,8 @@ class WCHaveSeed(WizardComponent, Logger):
|
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Enter Seed')) |
|
|
|
|
Logger.__init__(self) |
|
|
|
|
|
|
|
|
|
self.slayout = None |
|
|
|
|
|
|
|
|
|
self.layout().addWidget(WWLabel(_('Please enter your seed phrase in order to restore your wallet.'))) |
|
|
|
|
|
|
|
|
|
# TODO: SeedLayout assumes too much in parent, refactor SeedLayout |
|
|
|
|
@ -650,6 +650,9 @@ class WCScriptAndDerivation(WizardComponent, Logger):
|
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Script type and Derivation path')) |
|
|
|
|
Logger.__init__(self) |
|
|
|
|
|
|
|
|
|
self.choice_w = None |
|
|
|
|
self.derivation_path_edit = None |
|
|
|
|
|
|
|
|
|
def on_ready(self): |
|
|
|
|
message1 = _('Choose the type of addresses in your wallet.') |
|
|
|
|
message2 = ' '.join([ |
|
|
|
|
@ -797,6 +800,8 @@ class WCHaveMasterKey(WizardComponent):
|
|
|
|
|
def __init__(self, parent, wizard): |
|
|
|
|
WizardComponent.__init__(self, parent, wizard, title=_('Create keystore from a master key')) |
|
|
|
|
|
|
|
|
|
self.slayout = None |
|
|
|
|
|
|
|
|
|
self.message_create = ' '.join([ |
|
|
|
|
_("To create a watching-only wallet, please enter your master public key (xpub/ypub/zpub)."), |
|
|
|
|
_("To create a spending wallet, please enter a master private key (xprv/yprv/zprv).") |
|
|
|
|
@ -816,26 +821,24 @@ class WCHaveMasterKey(WizardComponent):
|
|
|
|
|
class Hack: |
|
|
|
|
def setEnabled(self2, b): |
|
|
|
|
self.valid = b |
|
|
|
|
|
|
|
|
|
def setToolTip(self2, b): |
|
|
|
|
pass |
|
|
|
|
self.next_button = Hack() |
|
|
|
|
|
|
|
|
|
def on_ready(self): |
|
|
|
|
# if self.wallet_type == 'standard': |
|
|
|
|
# v = keystore.is_master_key |
|
|
|
|
# self.add_xpub_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v) |
|
|
|
|
# else: |
|
|
|
|
# i = len(self.keystores) + 1 |
|
|
|
|
# self.add_cosigner_dialog(index=i, run_next=self.on_restore_from_key, is_valid=keystore.is_bip32_key) |
|
|
|
|
if self.wizard_data['wallet_type'] == 'standard': |
|
|
|
|
self.label.setText(self.message_create) |
|
|
|
|
is_valid = lambda x: bool(keystore.from_master_key(x)) |
|
|
|
|
|
|
|
|
|
def is_valid(x) -> bool: |
|
|
|
|
return bool(keystore.from_master_key(x)) |
|
|
|
|
elif self.wizard_data['wallet_type'] == 'multisig': |
|
|
|
|
if 'multisig_current_cosigner' in self.wizard_data: |
|
|
|
|
self.title = _("Add Cosigner {}").format(self.wizard_data['multisig_current_cosigner']) |
|
|
|
|
self.label.setText(self.message_cosign) |
|
|
|
|
else: |
|
|
|
|
self.label.setText(self.message_create) |
|
|
|
|
|
|
|
|
|
def is_valid(x) -> bool: |
|
|
|
|
if not keystore.is_bip32_key(x): |
|
|
|
|
return False |
|
|
|
|
@ -933,12 +936,15 @@ class WCImport(WizardComponent):
|
|
|
|
|
class Hack: |
|
|
|
|
def setEnabled(self2, b): |
|
|
|
|
self.valid = b |
|
|
|
|
|
|
|
|
|
def setToolTip(self2, b): |
|
|
|
|
pass |
|
|
|
|
self.next_button = Hack() |
|
|
|
|
|
|
|
|
|
v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x, raise_on_error=True) |
|
|
|
|
self.slayout = KeysLayout(parent=self, header_layout=header_layout, is_valid=v, |
|
|
|
|
def is_valid(x) -> bool: |
|
|
|
|
return keystore.is_address_list(x) or keystore.is_private_key_list(x, raise_on_error=True) |
|
|
|
|
|
|
|
|
|
self.slayout = KeysLayout(parent=self, header_layout=header_layout, is_valid=is_valid, |
|
|
|
|
allow_multi=True, config=self.wizard.config) |
|
|
|
|
self.layout().addLayout(self.slayout) |
|
|
|
|
|
|
|
|
|
@ -1077,8 +1083,6 @@ class WCChooseHWDevice(WizardComponent, Logger):
|
|
|
|
|
self.layout().addLayout(hbox) |
|
|
|
|
self.layout().addStretch(1) |
|
|
|
|
|
|
|
|
|
self.c_values = [] |
|
|
|
|
|
|
|
|
|
def on_ready(self): |
|
|
|
|
self.scan_devices() |
|
|
|
|
|
|
|
|
|
@ -1137,8 +1141,8 @@ class WCChooseHWDevice(WizardComponent, Logger):
|
|
|
|
|
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' |
|
|
|
|
_indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True)) |
|
|
|
|
debug_msg += f' {name}: (error getting device infos)\n{_indented_error_msg}\n' |
|
|
|
|
|
|
|
|
|
# scan devices |
|
|
|
|
try: |
|
|
|
|
@ -1324,12 +1328,12 @@ class WCHWXPub(WizardComponent, Logger):
|
|
|
|
|
xtype = cosigner_data['script_type'] |
|
|
|
|
derivation = cosigner_data['derivation_path'] |
|
|
|
|
|
|
|
|
|
def get_xpub_task(client, derivation, xtype): |
|
|
|
|
def get_xpub_task(_client, _derivation, _xtype): |
|
|
|
|
try: |
|
|
|
|
self.xpub = self.get_xpub_from_client(client, derivation, xtype) |
|
|
|
|
self.root_fingerprint = client.request_root_fingerprint_from_device() |
|
|
|
|
self.label = client.label() |
|
|
|
|
self.soft_device_id = client.get_soft_device_id() |
|
|
|
|
self.xpub = self.get_xpub_from_client(_client, _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 UserFacingException as e: |
|
|
|
|
self.error = str(e) |
|
|
|
|
self.logger.error(repr(e)) |
|
|
|
|
|