Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions electrum/gui/qml/components/wizard/WCHaveSeed.qml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ WizardComponent {

property string _seedType
property string _validationMessage
property string _warning
property bool _canPassphrase
property bool _seedValid

Expand Down Expand Up @@ -72,11 +73,18 @@ WizardComponent {
var verifyResult = wiz.verifySeed(seedtext.text, seed_variant_cb.currentValue, wizard_data['wallet_type'])

_validationMessage = verifyResult.message
_warning = verifyResult.warning
_seedType = verifyResult.type
_canPassphrase = verifyResult.can_passphrase

if (!cosigner || !verifyResult.valid) {
_seedValid = verifyResult.valid
if (_warning) {
infotext.text = _warning
Comment thread
accumulator marked this conversation as resolved.
infotext.visible = true
} else {
setSeedTypeHelpText()
}
} else {
// bip39 validate after derivation path is known
if (seed_variant_cb.currentValue == 'electrum') {
Expand Down Expand Up @@ -190,6 +198,7 @@ WizardComponent {
Layout.fillWidth: true
Layout.columnSpan: 2
Layout.topMargin: constants.paddingLarge
iconStyle: root._warning === infotext.text ? InfoTextArea.IconStyle.Warn : InfoTextArea.IconStyle.Info
compact: true
backgroundColor: constants.darkerDialogBackground
}
Expand Down
3 changes: 2 additions & 1 deletion electrum/gui/qml/qewizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,12 @@ def isMatchingSeed(self, seed, seed_again):

@pyqtSlot(str, str, str, result='QVariantMap')
def verifySeed(self, seed, seed_variant, wallet_type='standard'):
seed_valid, seed_type, validation_message, can_passphrase = self.validate_seed(seed, seed_variant, wallet_type)
seed_valid, seed_type, validation_message, warning, can_passphrase = self.validate_seed(seed, seed_variant, wallet_type)
return {
'valid': seed_valid,
'type': seed_type,
'message': validation_message,
'warning': warning,
'can_passphrase': can_passphrase
}

Expand Down
2 changes: 1 addition & 1 deletion electrum/gui/qt/wizard/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ def validate(self):
seed = self.seed_widget.get_seed()
seed_variant = self.seed_widget.seed_type
wallet_type = self.wizard_data['wallet_type']
seed_valid, seed_type, validation_message, self.can_passphrase = self.wizard.validate_seed(seed, seed_variant, wallet_type)
seed_valid, seed_type, validation_message, warning, self.can_passphrase = self.wizard.validate_seed(seed, seed_variant, wallet_type)

is_cosigner = self.wizard_data['wallet_type'] == 'multisig' and 'multisig_current_cosigner' in self.wizard_data

Expand Down
20 changes: 18 additions & 2 deletions electrum/wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,23 @@ def on_hardware_device(self, wizard_data: dict, new_wallet=True) -> str:
run_hook('init_wallet_wizard', self) # TODO: currently only used for hww, hook name might be confusing
return plugin.wizard_entry_for_device(_info, new_wallet=new_wallet)

def validate_seed(self, seed: str, seed_variant: str, wallet_type: str) -> Tuple[bool, str, str, bool]:
def _warning_wrong_electrum_seed_type(self, seed_type: str) -> str:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be used to deduplicate the identical warning in seed_dialog.py:

wiztype_fullname = _('Wallet with two-factor authentication') if is_any_2fa_seed_type(t) else _("Standard wallet")
msg = ' '.join([
'<b>' + _('Warning') + ':</b> ',
_("Looks like you have entered a valid seed of type '{}' but this dialog does not support such seeds.").format(t),
_("If unsure, try restoring as '{}'.").format(wiztype_fullname),
])
self.seed_warning.setText(msg)

This works (click to expand)
diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py
index 0b0da25ae3..e5ffe9c6b9 100644
--- a/electrum/gui/qt/seed_dialog.py
+++ b/electrum/gui/qt/seed_dialog.py
@@ -36,6 +36,7 @@ from electrum.mnemonic import Mnemonic, calc_seed_type, is_any_2fa_seed_type
 from electrum import old_mnemonic
 from electrum import slip39
 from electrum.util import ChoiceItem
+from electrum.wizard import KeystoreWizard
 
 from .util import (
     Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path, EnterButton,
@@ -322,13 +323,7 @@ class SeedWidget(QWidget):
             t = calc_seed_type(s)
             label = _('Seed Type') + ': ' + t if t else ''
             if t and not valid:  # electrum seed, but does not conform to dialog rules
-                wiztype_fullname = _('Wallet with two-factor authentication') if is_any_2fa_seed_type(t) else _("Standard wallet")
-                msg = ' '.join([
-                    '<b>' + _('Warning') + ':</b>  ',
-                    _("Looks like you have entered a valid seed of type '{}' but this dialog does not support such seeds.").format(t),
-                    _("If unsure, try restoring as '{}'.").format(wiztype_fullname),
-                ])
-                self.seed_warning.setText(msg)
+                self.seed_warning.setText(KeystoreWizard.warning_wrong_electrum_seed_type(t))
             else:
                 self.seed_warning.setText("")
 
diff --git a/electrum/wizard.py b/electrum/wizard.py
index d8773f82a1..2b15a06e47 100644
--- a/electrum/wizard.py
+++ b/electrum/wizard.py
@@ -312,7 +312,8 @@ class KeystoreWizard(AbstractWizard):
         run_hook('init_wallet_wizard', self)  # TODO: currently only used for hww, hook name might be confusing
         return plugin.wizard_entry_for_device(_info, new_wallet=new_wallet)
 
-    def _warning_wrong_electrum_seed_type(self, seed_type: str) -> str:
+    @staticmethod
+    def warning_wrong_electrum_seed_type(seed_type: str) -> str:
         if not seed_type:
             return ''
         wiztype_fullname = _('Wallet with two-factor authentication') if is_any_2fa_seed_type(seed_type) \
@@ -359,11 +360,11 @@ class KeystoreWizard(AbstractWizard):
         # check if seed matches wallet type
         if wallet_type == '2fa' and not is_any_2fa_seed_type(seed_type):
             seed_valid = False
-            warning = self._warning_wrong_electrum_seed_type(seed_type)
+            warning = self.warning_wrong_electrum_seed_type(seed_type)
         elif wallet_type == 'standard' and seed_type not in ['old', 'standard', 'segwit', 'bip39', 'slip39']:
             seed_valid = False
             if seed_variant == 'electrum':
-                warning = self._warning_wrong_electrum_seed_type(seed_type)
+                warning = self.warning_wrong_electrum_seed_type(seed_type)
         elif wallet_type == 'multisig' and seed_type not in ['standard', 'segwit', 'bip39', 'slip39']:
             seed_valid = False
 

@accumulator accumulator Aug 27, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I saw that, but I held off on refactoring this like you suggested above because it felt like a circular dependency using the class and its static member in a control that is typically imported in a wizard.

@SomberNight SomberNight Aug 27, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somewhat related, I have long thought we have too many top-level modules (.py files directly in electrum/). One idea for a new folder is mnemonic/, which would include:

  • mnemonic.py
  • old_mnemonic.py
  • slip39.py
  • the "bip39" parts extracted from keystore.py, in a new bip39.py

(EDIT: there is also bip39_recovery.py, bip39_wallet_formats.json, perhaps even the whole wordlist/ folder)

If we had that folder, you could somewhat more cleanly also add another module there for shared UI code, and put this function there.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also add a FIXME pointing out where the duplication is, and we can fix it later, if you want. We should definitely try to avoid unmarked duplication though.
I can do the above mentioned refactor but don't want to hold up this PR on that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I saw that, but I held off on refactoring this like you suggested above because it felt like a circular dependency using the class and its static member in a control that is typically imported in a wizard.

why would that be a circular dependency? it looks fine to me

if not seed_type:
return ''
wiztype_fullname = _('Wallet with two-factor authentication') if is_any_2fa_seed_type(seed_type) \
else _("Standard wallet")
msg = ' '.join([
_("Looks like you have entered a valid seed of type '{}' but this dialog does not support such seeds.").format(
seed_type),
_("If unsure, try restoring as '{}'.").format(wiztype_fullname),
])
return msg

def validate_seed(self, seed: str, seed_variant: str, wallet_type: str) -> Tuple[bool, str, str, str, bool]:
seed_type = ''
seed_valid = False
validation_message = ''
warning = ''
can_passphrase = True

if seed_variant == 'electrum':
Expand Down Expand Up @@ -345,14 +358,17 @@ def validate_seed(self, seed: str, seed_variant: str, wallet_type: str) -> Tuple
# check if seed matches wallet type
if wallet_type == '2fa' and not is_any_2fa_seed_type(seed_type):
seed_valid = False
warning = self._warning_wrong_electrum_seed_type(seed_type)
elif wallet_type == 'standard' and seed_type not in ['old', 'standard', 'segwit', 'bip39', 'slip39']:
seed_valid = False
if seed_variant == 'electrum':
warning = self._warning_wrong_electrum_seed_type(seed_type)
elif wallet_type == 'multisig' and seed_type not in ['standard', 'segwit', 'bip39', 'slip39']:
seed_valid = False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On desktop we also show a warning if the user enters the wrong seed type into the multisig wizard:

Suggested change
warning = self._warning_wrong_electrum_seed_type(seed_type)

self._logger.debug(f'seed verified: {seed_valid}, type={seed_type!r}, validation_message={validation_message}')

return seed_valid, seed_type, validation_message, can_passphrase
return seed_valid, seed_type, validation_message, warning, can_passphrase

def keystore_from_data(self, wallet_type: str, data: dict):
if data['keystore_type'] in ['createseed', 'haveseed'] and 'seed' in data:
Expand Down