diff --git a/electrum/gui/qml/components/wizard/WCHaveSeed.qml b/electrum/gui/qml/components/wizard/WCHaveSeed.qml index 9fea691c0..1e300f59f 100644 --- a/electrum/gui/qml/components/wizard/WCHaveSeed.qml +++ b/electrum/gui/qml/components/wizard/WCHaveSeed.qml @@ -18,17 +18,20 @@ WizardComponent { property int participants: 0 property string multisigMasterPubkey: wizard_data['multisig_master_pubkey'] + property string _seedType + property string _validationMessage + function apply() { if (cosigner) { wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed'] = seedtext.text wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_variant'] = seed_variant_cb.currentValue - wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_type'] = bitcoin.seedType + wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_type'] = _seedType wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extend'] = extendcb.checked wizard_data['multisig_cosigner_data'][cosigner.toString()]['seed_extra_words'] = extendcb.checked ? customwordstext.text : '' } else { wizard_data['seed'] = seedtext.text wizard_data['seed_variant'] = seed_variant_cb.currentValue - wizard_data['seed_type'] = bitcoin.seedType + wizard_data['seed_type'] = _seedType wizard_data['seed_extend'] = extendcb.checked wizard_data['seed_extra_words'] = extendcb.checked ? customwordstext.text : '' @@ -38,7 +41,7 @@ WizardComponent { wizard_data['script_type'] = { 'standard': 'p2sh', 'segwit': 'p2wsh' - }[bitcoin.seedType] + }[_seedType] } } } @@ -66,14 +69,18 @@ WizardComponent { function checkValid() { valid = false - validationtext.text = '' + _validationMessage = '' if (extendcb.checked && customwordstext.text == '') return - var validSeed = bitcoin.verifySeed(seedtext.text, seed_variant_cb.currentValue, wizard_data['wallet_type']) - if (!cosigner || !validSeed) { - valid = validSeed + var verifyResult = wiz.verifySeed(seedtext.text, seed_variant_cb.currentValue, wizard_data['wallet_type']) + + _validationMessage = verifyResult.message + _seedType = verifyResult.type + + if (!cosigner || !verifyResult.valid) { + valid = verifyResult.valid return } else { // bip39 validate after derivation path is known @@ -196,21 +203,19 @@ WizardComponent { placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed') indicatorValid: root.valid - + ? root._seedType == 'bip39' && root._validationMessage + ? false + : root.valid + : root.valid + indicatorText: root.valid + ? root._validationMessage + ? root._validationMessage + : root._seedType + : '' onTextChanged: { startValidationTimer() } } - TextArea { - id: validationtext - visible: text - Layout.fillWidth: true - readOnly: true - wrapMode: TextInput.WordWrap - background: Rectangle { - color: 'transparent' - } - } ElCheckBox { id: extendcb @@ -231,14 +236,10 @@ WizardComponent { } } - Bitcoin { - id: bitcoin - onSeedTypeChanged: seedtext.indicatorText = bitcoin.seedType - } - function startValidationTimer() { valid = false - seedtext.indicatorText = '' + root._seedType = '' + root._validationMessage = '' validationTimer.restart() } diff --git a/electrum/gui/qml/qebitcoin.py b/electrum/gui/qml/qebitcoin.py index 3c4b2264b..2f1f0df40 100644 --- a/electrum/gui/qml/qebitcoin.py +++ b/electrum/gui/qml/qebitcoin.py @@ -7,10 +7,9 @@ from electrum import keystore from electrum.i18n import _ from electrum.bip32 import is_bip32_derivation, xpub_type from electrum.logging import get_logger -from electrum.slip39 import decode_mnemonic, Slip39Error from electrum.util import get_asyncio_loop from electrum.transaction import tx_from_any -from electrum.mnemonic import Mnemonic, is_any_2fa_seed_type +from electrum.mnemonic import Mnemonic from electrum.old_mnemonic import wordlist as old_wordlist from electrum.bitcoin import is_address @@ -50,7 +49,7 @@ class QEBitcoin(QObject): @pyqtSlot() @pyqtSlot(str) - @pyqtSlot(str,str) + @pyqtSlot(str, str) def generateSeed(self, seed_type='segwit', language='en'): self._logger.debug('generating seed of type ' + str(seed_type)) @@ -61,50 +60,6 @@ class QEBitcoin(QObject): asyncio.run_coroutine_threadsafe(co_gen_seed(seed_type, language), get_asyncio_loop()) - @pyqtSlot(str,str,str, result=bool) - def verifySeed(self, seed, seed_variant, wallet_type='standard'): - seed_type = '' - seed_valid = False - self.validationMessage = '' - - if seed_variant == 'electrum': - seed_type = mnemonic.seed_type(seed) - if seed_type != '': - seed_valid = True - elif seed_variant == 'bip39': - is_checksum, is_wordlist = keystore.bip39_is_checksum_valid(seed) - status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist' - self.validationMessage = 'BIP39 (%s)' % status - - 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) - seed_type = 'slip39' - self.validationMessage = 'SLIP39: share #%d in %dof%d scheme' % (share.group_index, share.group_threshold, share.group_count) - except Slip39Error as e: - self.validationMessage = 'SLIP39: %s' % str(e) - seed_valid = False # for now - 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']: - seed_valid = False - elif wallet_type == 'multisig' and seed_type not in ['standard', 'segwit', 'bip39']: - seed_valid = False - - self._seed_type = seed_type - self.seedTypeChanged.emit() - - self._logger.debug('seed verified: ' + str(seed_valid)) - - return seed_valid - @pyqtSlot(str, str, result=bool) def verifyMasterKey(self, key, wallet_type='standard'): self.validationMessage = '' diff --git a/electrum/gui/qml/qewizard.py b/electrum/gui/qml/qewizard.py index 3bd12fb21..ac289fd67 100644 --- a/electrum/gui/qml/qewizard.py +++ b/electrum/gui/qml/qewizard.py @@ -107,6 +107,15 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): def isMatchingSeed(self, seed, seed_again): return mnemonic.is_matching_seed(seed=seed, seed_again=seed_again) + @pyqtSlot(str, str, str, result='QVariantMap') + def verifySeed(self, seed, seed_variant, wallet_type='standard'): + seed_valid, seed_type, validation_message = self.validate_seed(seed, seed_variant, wallet_type) + return { + 'valid': seed_valid, + 'type': seed_type, + 'message': validation_message + } + @pyqtSlot('QJSValue', bool, str) def createStorage(self, js_data, single_password_enabled, single_password): self._logger.info('Creating wallet from wizard data') diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py index b282016f7..e3ee09c88 100644 --- a/electrum/gui/qt/seed_dialog.py +++ b/electrum/gui/qt/seed_dialog.py @@ -267,8 +267,9 @@ class SeedLayout(QVBoxLayout): if self.seed_type == 'bip39': from electrum.keystore import bip39_is_checksum_valid is_checksum, is_wordlist = bip39_is_checksum_valid(s) - status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist' - label = 'BIP39' + ' (%s)'%status + label = '' + if bool(s): + label = ('' if is_checksum else _('BIP39 Checksum failed')) if is_wordlist else _('Unknown BIP39 wordlist') elif self.seed_type == 'slip39': self.slip39_mnemonics[self.slip39_mnemonic_index] = s try: diff --git a/electrum/wizard.py b/electrum/wizard.py index 91148fed5..9c7833542 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -478,12 +478,13 @@ class NewWalletWizard(AbstractWizard): seed_valid = True elif seed_variant == 'bip39': is_checksum, is_wordlist = keystore.bip39_is_checksum_valid(seed) - status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist' - validation_message = 'BIP39 (%s)' % status - - if is_checksum: - seed_type = 'bip39' - seed_valid = True + validation_message = ('' if is_checksum else _('BIP39 checksum failed')) if is_wordlist else _('Unknown BIP39 wordlist') + if not bool(seed): + validation_message = '' + seed_type = 'bip39' + # bip39 always valid, even if checksum failed, see #8720 + # however, reject empty string + seed_valid = bool(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):