diff --git a/electrum/gui/qml/components/OpenWalletDialog.qml b/electrum/gui/qml/components/OpenWalletDialog.qml index b6209dec5..51ec37e61 100644 --- a/electrum/gui/qml/components/OpenWalletDialog.qml +++ b/electrum/gui/qml/components/OpenWalletDialog.qml @@ -12,6 +12,7 @@ ElDialog { property string name property string path + property bool isStartup property bool _invalidPassword: false property bool _unlockClicked: false @@ -40,7 +41,7 @@ ElDialog { InfoTextArea { id: notice - text: Daemon.singlePasswordEnabled || !Daemon.currentWallet + text: Daemon.singlePasswordEnabled || isStartup ? qsTr('Please enter password') : qsTr('Wallet %1 requires password to unlock').arg(name) iconStyle: InfoTextArea.IconStyle.Warn @@ -94,9 +95,39 @@ ElDialog { Daemon.loadWallet(openwalletdialog.path, password.text) } + function maybeUnlockAnyOtherWallet() { + // try to open any other wallet with the password the user entered, hack to improve ux for + // users with non-unified wallet password. + // we should only fall back to opening a random wallet if: + // - the user did not select a specific wallet, otherwise this is confusing + // - there can be more than one password, otherwise this scan would be pointless + if (Daemon.availableWallets.rowCount() <= 1 || password.text === '') { + return false + } + if (Config.walletDidUseSinglePassword) { + // the last time the wallet was unlocked all wallets used the same password. + // trying to decrypt all of them now is most probably useless. + return false + } + if (!openwalletdialog.isStartup) { + return false // this dialog got opened because the user clicked on a specific wallet + } + let wallet_paths = Daemon.getWalletsUnlockableWithPassword(password.text) + if (wallet_paths && wallet_paths.length > 0) { + console.log('could not unlock recent wallet, falling back to: ' + wallet_paths[0]) + Daemon.loadWallet(wallet_paths[0], password.text) + return true + } + return false + } + Connections { target: Daemon function onWalletRequiresPassword() { + if (maybeUnlockAnyOtherWallet()) { + password.text = '' // reset pw so we cannot end up in a loop + return + } console.log('invalid password') _invalidPassword = true password.tf.forceActiveFocus() diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index bf0b0c969..c8d364525 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -634,12 +634,18 @@ ApplicationWindow } property var _opendialog: undefined + property var _opendialog_startup: true function showOpenWalletDialog(name, path) { if (_opendialog == undefined) { - _opendialog = openWalletDialog.createObject(app, { name: name, path: path }) + _opendialog = openWalletDialog.createObject(app, { + name: name, + path: path, + isStartup: _opendialog_startup, + }) _opendialog.closed.connect(function() { _opendialog = undefined + _opendialog_startup = false }) _opendialog.open() } diff --git a/electrum/gui/qml/qeconfig.py b/electrum/gui/qml/qeconfig.py index 9bf2a2997..d0d4d79e1 100644 --- a/electrum/gui/qml/qeconfig.py +++ b/electrum/gui/qml/qeconfig.py @@ -340,6 +340,16 @@ class QEConfig(AuthMixin, QObject): """ return self.config.WALLET_SHOULD_USE_SINGLE_PASSWORD + walletDidUseSinglePasswordChanged = pyqtSignal() + @pyqtProperty(bool, notify=walletDidUseSinglePasswordChanged) + def walletDidUseSinglePassword(self): + """ + Allows to guess if this is a unified password instance without having + unlocked any wallet yet. Might be out of sync e.g. if wallet files get copied manually. + """ + # TODO: consider removing once encrypted wallet file headers are available + return self.config.WALLET_DID_USE_SINGLE_PASSWORD + @pyqtSlot('qint64', result=str) @pyqtSlot(QEAmount, result=str) def formatSatsForEditing(self, satoshis): diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index acbe1a780..2b1bfa35b 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -237,6 +237,7 @@ class QEDaemon(AuthMixin, QObject): self._logger.info(f'use single password: {self._use_single_password}') else: self._logger.info('use single password disabled by config') + self.daemon.config.WALLET_DID_USE_SINGLE_PASSWORD = self._use_single_password run_hook('load_wallet', wallet) diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 92b11defc..a74818626 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -676,6 +676,8 @@ class SimpleConfig(Logger): WALLET_UNCONF_UTXO_FREEZE_THRESHOLD_SAT = ConfigVar('unconf_utxo_freeze_threshold', default=5_000, type_=int) WALLET_PAYREQ_EXPIRY_SECONDS = ConfigVar('request_expiry', default=invoices.PR_DEFAULT_EXPIRATION_WHEN_CREATING, type_=int) WALLET_SHOULD_USE_SINGLE_PASSWORD = ConfigVar('should_use_single_password', default=False, type_=bool) + # TODO: consider removing WALLET_DID_USE_SINGLE_PASSWORD once encrypted wallet file headers are available + WALLET_DID_USE_SINGLE_PASSWORD = ConfigVar('did_use_single_password', default=False, type_=bool) # note: 'use_change' and 'multiple_change' are per-wallet settings WALLET_SEND_CHANGE_TO_LIGHTNING = ConfigVar( 'send_change_to_lightning', default=False, type_=bool,