diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py index 7a68f2bae..acb77ec04 100644 --- a/electrum/base_wizard.py +++ b/electrum/base_wizard.py @@ -719,7 +719,11 @@ class BaseWizard(Logger): def confirm_seed(self, seed, passphrase): f = lambda x: self.confirm_passphrase(seed, passphrase) - self.confirm_seed_dialog(run_next=f, seed=seed if self.config.get('debug_seed') else '', test=lambda x: x==seed) + self.confirm_seed_dialog( + run_next=f, + seed=seed if self.config.get('debug_seed') else '', + test=lambda x: mnemonic.is_matching_seed(seed=seed, seed_again=x), + ) def confirm_passphrase(self, seed, passphrase): f = lambda x: self.run('create_keystore', seed, x) diff --git a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml index ab04a44ae..17d262e5d 100644 --- a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml +++ b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml @@ -13,7 +13,7 @@ WizardComponent { valid: false function checkValid() { - var seedvalid = confirm.text == wizard_data['seed'] + var seedvalid = wizard.wiz.isMatchingSeed(wizard_data['seed'], confirm.text) var customwordsvalid = customwordstext.text == wizard_data['seed_extra_words'] valid = seedvalid && (wizard_data['seed_extend'] ? customwordsvalid : true) } diff --git a/electrum/gui/qml/qewizard.py b/electrum/gui/qml/qewizard.py index 55486e644..6020a5d96 100644 --- a/electrum/gui/qml/qewizard.py +++ b/electrum/gui/qml/qewizard.py @@ -4,8 +4,10 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from PyQt5.QtQml import QQmlApplicationEngine from electrum.logging import get_logger +from electrum import mnemonic from electrum.wizard import NewWalletWizard, ServerConnectWizard + class QEAbstractWizard(QObject): _logger = get_logger(__name__) @@ -93,6 +95,10 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard): data = js_data.toVariant() return self.has_heterogeneous_masterkeys(data) + @pyqtSlot(str, str, result=bool) + def isMatchingSeed(self, seed, seed_again): + return mnemonic.is_matching_seed(seed=seed, seed_again=seed_again) + @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/mnemonic.py b/electrum/mnemonic.py index 4134575ac..30f5c0fb4 100644 --- a/electrum/mnemonic.py +++ b/electrum/mnemonic.py @@ -90,6 +90,16 @@ def normalize_text(seed: str) -> str: return seed +def is_matching_seed(*, seed: str, seed_again: str) -> bool: + """Compare two seeds for equality, as used in "confirm seed" screen in wizard. + Not just for electrum seeds, but other types (e.g. bip39) as well. + Note: we don't use normalize_text, as that is specific to electrum seeds. + """ + seed = " ".join(seed.split()) + seed_again = " ".join(seed_again.split()) + return seed == seed_again + + _WORDLIST_CACHE = {} # type: Dict[str, Wordlist] diff --git a/electrum/tests/test_mnemonic.py b/electrum/tests/test_mnemonic.py index 6273388e6..870e16bf8 100644 --- a/electrum/tests/test_mnemonic.py +++ b/electrum/tests/test_mnemonic.py @@ -7,7 +7,7 @@ from electrum import mnemonic from electrum import slip39 from electrum import old_mnemonic from electrum.util import bfh -from electrum.mnemonic import is_new_seed, is_old_seed, seed_type +from electrum.mnemonic import is_new_seed, is_old_seed, seed_type, is_matching_seed from electrum.version import SEED_PREFIX_SW, SEED_PREFIX from . import ElectrumTestCase @@ -193,6 +193,26 @@ class Test_seeds(ElectrumTestCase): with self.subTest(msg=f"seed_type_subcase_{idx}", seed_words=seed_words): self.assertEqual(_type, seed_type(seed_words), msg=seed_words) + def test_is_matching_seed(self): + self.assertTrue(is_matching_seed(seed="9dk", seed_again="9dk ")) + self.assertTrue(is_matching_seed(seed="9dk", seed_again=" 9dk")) + self.assertTrue(is_matching_seed(seed="9dk", seed_again=" 9dk ")) + self.assertTrue(is_matching_seed(seed="when blade focus", seed_again="when blade focus ")) + self.assertTrue(is_matching_seed(seed="when blade focus", seed_again=" when blade focus ")) + self.assertTrue(is_matching_seed(seed=" when blade focus ", seed_again=" when blade focus ")) + self.assertTrue(is_matching_seed( + seed=" when blade focus ", + seed_again= + """ when blade + + focus """)) + + self.assertFalse(is_matching_seed(seed="when blade focus", seed_again="wen blade focus")) + self.assertFalse(is_matching_seed(seed="when blade focus", seed_again="when bladefocus")) + self.assertFalse(is_matching_seed(seed="when blade focus", seed_again="when blAde focus")) + self.assertFalse(is_matching_seed(seed="when blade focus", seed_again="when bl4de focus")) + self.assertFalse(is_matching_seed(seed="when blade focus", seed_again="when bla4de focus")) + class Test_slip39(ElectrumTestCase): """ Test SLIP39 test vectors. """