1
0

qml: OpenWalletDialog: load any wallet if password matches

If the user has wallets with different passwords (non-unified pw) and
enters a password on startup that fails to unlock the recently used
wallet this change will automatically open any other wallet if there
is another wallet that can be unlocked with this password.
This commit is contained in:
f321x
2025-12-17 11:02:26 +01:00
parent ba379b7da4
commit aee0f8fb54
5 changed files with 52 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ ElDialog {
property string name property string name
property string path property string path
property bool isStartup
property bool _invalidPassword: false property bool _invalidPassword: false
property bool _unlockClicked: false property bool _unlockClicked: false
@@ -40,7 +41,7 @@ ElDialog {
InfoTextArea { InfoTextArea {
id: notice id: notice
text: Daemon.singlePasswordEnabled || !Daemon.currentWallet text: Daemon.singlePasswordEnabled || isStartup
? qsTr('Please enter password') ? qsTr('Please enter password')
: qsTr('Wallet <b>%1</b> requires password to unlock').arg(name) : qsTr('Wallet <b>%1</b> requires password to unlock').arg(name)
iconStyle: InfoTextArea.IconStyle.Warn iconStyle: InfoTextArea.IconStyle.Warn
@@ -94,9 +95,39 @@ ElDialog {
Daemon.loadWallet(openwalletdialog.path, password.text) 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 { Connections {
target: Daemon target: Daemon
function onWalletRequiresPassword() { function onWalletRequiresPassword() {
if (maybeUnlockAnyOtherWallet()) {
password.text = '' // reset pw so we cannot end up in a loop
return
}
console.log('invalid password') console.log('invalid password')
_invalidPassword = true _invalidPassword = true
password.tf.forceActiveFocus() password.tf.forceActiveFocus()

View File

@@ -634,12 +634,18 @@ ApplicationWindow
} }
property var _opendialog: undefined property var _opendialog: undefined
property var _opendialog_startup: true
function showOpenWalletDialog(name, path) { function showOpenWalletDialog(name, path) {
if (_opendialog == undefined) { 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.closed.connect(function() {
_opendialog = undefined _opendialog = undefined
_opendialog_startup = false
}) })
_opendialog.open() _opendialog.open()
} }

View File

@@ -340,6 +340,16 @@ class QEConfig(AuthMixin, QObject):
""" """
return self.config.WALLET_SHOULD_USE_SINGLE_PASSWORD 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('qint64', result=str)
@pyqtSlot(QEAmount, result=str) @pyqtSlot(QEAmount, result=str)
def formatSatsForEditing(self, satoshis): def formatSatsForEditing(self, satoshis):

View File

@@ -237,6 +237,7 @@ class QEDaemon(AuthMixin, QObject):
self._logger.info(f'use single password: {self._use_single_password}') self._logger.info(f'use single password: {self._use_single_password}')
else: else:
self._logger.info('use single password disabled by config') 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) run_hook('load_wallet', wallet)

View File

@@ -676,6 +676,8 @@ class SimpleConfig(Logger):
WALLET_UNCONF_UTXO_FREEZE_THRESHOLD_SAT = ConfigVar('unconf_utxo_freeze_threshold', default=5_000, type_=int) 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_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) 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 # note: 'use_change' and 'multiple_change' are per-wallet settings
WALLET_SEND_CHANGE_TO_LIGHTNING = ConfigVar( WALLET_SEND_CHANGE_TO_LIGHTNING = ConfigVar(
'send_change_to_lightning', default=False, type_=bool, 'send_change_to_lightning', default=False, type_=bool,