diff --git a/electrum/gui/qt/wizard/wallet.py b/electrum/gui/qt/wizard/wallet.py index 79fbafd72..93deb0c20 100644 --- a/electrum/gui/qt/wizard/wallet.py +++ b/electrum/gui/qt/wizard/wallet.py @@ -31,9 +31,6 @@ WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): _logger = get_logger(__name__) - # createError = pyqtSignal([str], arguments=["error"]) - # createSuccess = pyqtSignal() - def __init__(self, config: 'SimpleConfig', app: QApplication, daemon: Daemon, path, parent=None): NewWalletWizard.__init__(self, daemon) QEAbstractWizard.__init__(self, config, app, parent) @@ -46,10 +43,11 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): 'wallet_type': { 'gui': WCWalletType }, 'keystore_type': { 'gui': WCKeystoreType }, 'create_seed': { 'gui': WCCreateSeed }, - 'create_ext': { 'gui': WCCreateExt }, + 'create_ext': { 'gui': WCEnterExt }, 'confirm_seed': { 'gui': WCConfirmSeed }, 'confirm_ext': { 'gui': WCConfirmExt }, 'have_seed': { 'gui': WCHaveSeed }, + 'have_ext': { 'gui': WCEnterExt }, 'bip39_refine': { 'gui': WCBIP39Refine }, 'have_master_key': { 'gui': WCHaveMasterKey }, 'multisig': { 'gui': WCMultisig }, @@ -76,36 +74,28 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): 'confirm_ext': { 'next': self.on_have_or_confirm_seed, 'accept': self.maybe_master_pubkey, - } + }, + 'have_seed': { + 'next': lambda d: 'have_ext' if d['seed_extend'] else self.on_have_or_confirm_seed(d), + }, + 'have_ext': { + 'next': self.on_have_or_confirm_seed, + 'accept': self.maybe_master_pubkey, + }, }) - # pathChanged = pyqtSignal() - # @pyqtProperty(str, notify=pathChanged) - # def path(self): - # return self._path - # - # @path.setter - # def path(self, path): - # self._path = path - # self.pathChanged.emit() - # + @property + def path(self): + return self._path + + @path.setter + def path(self, path): + self._path = path + def is_single_password(self): # TODO: also take into account if possible with existing set of wallets. see qedaemon.py return self._daemon.config.WALLET_USE_SINGLE_PASSWORD - # @pyqtSlot('QJSValue', result=bool) - # def hasDuplicateMasterKeys(self, js_data): - # self._logger.info('Checking for duplicate masterkeys') - # data = js_data.toVariant() - # return self.has_duplicate_masterkeys(data) - # - # @pyqtSlot('QJSValue', result=bool) - # def hasHeterogeneousMasterKeys(self, js_data): - # self._logger.info('Checking for heterogeneous masterkeys') - # data = js_data.toVariant() - # return self.has_heterogeneous_masterkeys(data) - # - def create_storage(self, single_password: str = None): self._logger.info('Creating wallet from wizard data') data = self._current.wizard_data @@ -121,14 +111,12 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): # minimally populate self after create self._password = data['password'] - # self.path = path + self.path = path - # self.createSuccess.emit() return True except Exception as e: self._logger.error(f"createStorage errored: {e!r}") return False - # self.createError.emit(str(e)) class WCWalletName(WizardComponent): @@ -349,7 +337,7 @@ class WCConfirmSeed(WizardComponent): pass -class WCCreateExt(WizardComponent): +class WCEnterExt(WizardComponent): def __init__(self, parent, wizard): WizardComponent.__init__(self, parent, wizard, title=_('Seed Extension')) @@ -490,7 +478,7 @@ class WCBIP39Refine(WizardComponent): if self.wizard_data['wallet_type'] == 'multisig': choices = [ - # TODO: 'standard' is a backend wallet concept, wizard wants 'p2sh' + # TODO: nicer to refactor 'standard' to 'p2sh', but backend wallet still uses 'standard' ('standard', 'legacy multisig (p2sh)', normalize_bip32_derivation("m/45'/0")), ('p2wsh-p2sh', 'p2sh-segwit multisig (p2wsh-p2sh)', purpose48_derivation(0, xtype='p2wsh-p2sh')), ('p2wsh', 'native segwit multisig (p2wsh)', purpose48_derivation(0, xtype='p2wsh')), @@ -508,24 +496,27 @@ class WCBIP39Refine(WizardComponent): else: default_choice_idx = 2 choices = [ - # TODO: 'standard' is a backend wallet concept, wizard wants 'p2pkh' + # TODO: nicer to refactor 'standard' to 'p2pkh', but backend wallet still uses 'standard' ('standard', 'legacy (p2pkh)', bip44_derivation(0, bip43_purpose=44)), ('p2wpkh-p2sh', 'p2sh-segwit (p2wpkh-p2sh)', bip44_derivation(0, bip43_purpose=49)), ('p2wpkh', 'native segwit (p2wpkh)', bip44_derivation(0, bip43_purpose=84)), ] - passphrase = self.wizard_data['seed_extra_words'] if self.wizard_data['seed_extend'] else '' - root_seed = bip39_to_seed(self.wizard_data['seed'], passphrase) - - def get_account_xpub(account_path): - root_node = BIP32Node.from_rootseed(root_seed, xtype="standard") - account_node = root_node.subkey_at_private_derivation(account_path) - account_xpub = account_node.to_xpub() - return account_xpub - if self.wizard_data['wallet_type'] == 'standard': button = QPushButton(_("Detect Existing Accounts")) + passphrase = self.wizard_data['seed_extra_words'] if self.wizard_data['seed_extend'] else '' + if self.wizard_data['seed_variant'] == 'bip39': + root_seed = bip39_to_seed(self.wizard_data['seed'], passphrase) + elif self.wizard_data['seed_variant'] == 'slip39': + root_seed = self.wizard_data['seed'].decrypt(passphrase) + + def get_account_xpub(account_path): + root_node = BIP32Node.from_rootseed(root_seed, xtype="standard") + account_node = root_node.subkey_at_private_derivation(account_path) + account_xpub = account_node.to_xpub() + return account_xpub + def on_account_select(account): script_type = account["script_type"] if script_type == "p2pkh": @@ -561,7 +552,14 @@ class WCBIP39Refine(WizardComponent): def validate(self): self.apply() - derivation_valid = is_bip32_derivation(self.wizard_data['derivation_path']) + + wizard_data = self.wizard_data + if self.wizard_data['wallet_type'] == 'multisig' and 'multisig_current_cosigner' in self.wizard_data: + cosigner = self.wizard_data['multisig_current_cosigner'] + if cosigner != 0: + wizard_data = self.wizard_data['multisig_cosigner_data'][str(cosigner)] + + derivation_valid = is_bip32_derivation(wizard_data['derivation_path']) if self.wizard_data['wallet_type'] == 'multisig': if self.wizard.has_duplicate_masterkeys(self.wizard_data): diff --git a/electrum/wizard.py b/electrum/wizard.py index cffb74195..6df066e36 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -4,7 +4,7 @@ import os from typing import List, NamedTuple, Any, Dict, Optional from electrum.logging import get_logger -from electrum.slip39 import Slip39Error, decode_mnemonic +from electrum.slip39 import EncryptedSeed from electrum.storage import WalletStorage, StorageEncryptionVersion from electrum.wallet_db import WalletDB from electrum.bip32 import normalize_bip32_derivation, xpub_type @@ -252,6 +252,9 @@ class NewWalletWizard(AbstractWizard): def is_bip39_seed(self, wizard_data): return wizard_data.get('seed_variant') == 'bip39' + def is_slip39_seed(self, wizard_data): + return wizard_data.get('seed_variant') == 'slip39' + def is_multisig(self, wizard_data): return wizard_data['wallet_type'] == 'multisig' @@ -275,6 +278,8 @@ class NewWalletWizard(AbstractWizard): def on_have_or_confirm_seed(self, wizard_data): if self.is_bip39_seed(wizard_data): return 'bip39_refine' + elif self.is_slip39_seed(wizard_data): + return 'bip39_refine' elif self.is_multisig(wizard_data): return 'multisig_cosigner_keystore' else: @@ -282,7 +287,7 @@ class NewWalletWizard(AbstractWizard): def maybe_master_pubkey(self, wizard_data): self._logger.debug('maybe_master_pubkey') - if self.is_bip39_seed(wizard_data) and 'derivation_path' not in wizard_data: + if (self.is_bip39_seed(wizard_data) or self.is_slip39_seed(wizard_data)) and 'derivation_path' not in wizard_data: self._logger.debug('deferred, missing derivation_path') return @@ -361,6 +366,14 @@ class NewWalletWizard(AbstractWizard): else: script = data['script_type'] if data['script_type'] != 'p2pkh' else 'standard' return keystore.from_bip43_rootseed(root_seed, derivation, xtype=script) + elif data['seed_variant'] == 'slip39': + root_seed = data['seed'].decrypt(data['seed_extra_words']) + derivation = normalize_bip32_derivation(data['derivation_path']) + if wallet_type == 'multisig': + script = data['script_type'] if data['script_type'] != 'p2sh' else 'standard' + else: + script = data['script_type'] if data['script_type'] != 'p2pkh' else 'standard' + return keystore.from_bip43_rootseed(root_seed, derivation, xtype=script) else: raise Exception('Unsupported seed variant %s' % data['seed_variant']) elif 'master_key' in data: @@ -385,21 +398,20 @@ class NewWalletWizard(AbstractWizard): if is_checksum: seed_type = 'bip39' seed_valid = True - elif seed_variant == 'slip39': # TODO: incomplete impl, this code only validates a single share. - try: - share = decode_mnemonic(seed) + elif seed_variant == 'slip39': + # seed shares should be already validated by wizard page, we have a combined encrypted seed + if seed and isinstance(seed, EncryptedSeed): + seed_valid = True seed_type = 'slip39' - validation_message = 'SLIP39: share #%d in %dof%d scheme' % (share.group_index, share.group_threshold, share.group_count) - except Slip39Error as e: - validation_message = 'SLIP39: %s' % str(e) - seed_valid = False # for now + else: + seed_valid = False else: raise Exception(f'unknown seed variant {seed_variant}') # check if seed matches wallet type if wallet_type == '2fa' and not is_any_2fa_seed_type(seed_type): seed_valid = False - elif wallet_type == 'standard' and seed_type not in ['old', 'standard', 'segwit', 'bip39']: + elif wallet_type == 'standard' and seed_type not in ['old', 'standard', 'segwit', 'bip39', 'slip39']: seed_valid = False elif wallet_type == 'multisig' and seed_type not in ['standard', 'segwit', 'bip39']: seed_valid = False @@ -435,9 +447,12 @@ class NewWalletWizard(AbstractWizard): if data['seed_type'] in ['old', 'standard', 'segwit']: self._logger.debug('creating keystore from electrum seed') k = keystore.from_seed(data['seed'], data['seed_extra_words'], data['wallet_type'] == 'multisig') - elif data['seed_type'] == 'bip39': - self._logger.debug('creating keystore from bip39 seed') - root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words']) + elif data['seed_type'] in ['bip39', 'slip39']: + self._logger.debug('creating keystore from %s seed' % data['seed_type']) + if data['seed_type'] == 'bip39': + root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words']) + else: + root_seed = data['seed'].decrypt(data['seed_extra_words']) derivation = normalize_bip32_derivation(data['derivation_path']) if data['wallet_type'] == 'multisig': script = data['script_type'] if data['script_type'] != 'p2sh' else 'standard'