From 07ba0e632933d638bf45729151e860aec03eb68c Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 18 Apr 2025 12:37:19 +0200 Subject: [PATCH] Qt: do not require password in memory - do not require full encryption - do not store password on startup - add lock/unlock functions to qt GUI --- electrum/daemon.py | 2 -- electrum/gui/qt/__init__.py | 1 - electrum/gui/qt/main_window.py | 64 +++++++++++++++++++++++----------- electrum/wallet.py | 11 +++--- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/electrum/daemon.py b/electrum/daemon.py index caf2dc4bf..434dd7092 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -470,8 +470,6 @@ class Daemon(Logger): if wallet := self._wallets.get(wallet_key): return wallet wallet = self._load_wallet(path, password, upgrade=upgrade, config=self.config) - if wallet.requires_unlock() and password is not None: - wallet.unlock(password) wallet.start_network(self.network) self.add_wallet(wallet) self.update_recently_opened_wallets(path) diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 4d102d0ab..891a5ba31 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -308,7 +308,6 @@ class ElectrumGui(BaseElectrumGui, Logger): self.build_tray_menu() w.warn_if_testnet() w.warn_if_watching_only() - w.require_full_encryption() return w def count_wizards_in_progress(func): diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 1fc988190..3f87c03c7 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -141,7 +141,7 @@ def protected(func): password = None msg = kwargs.get('message') while self.wallet.has_keystore_encryption(): - password = self.password_dialog(parent=parent, msg=msg) + password = self.wallet.get_unlocked_password() or self.password_dialog(parent=parent, msg=msg) if password is None: # User cancelled password input return @@ -330,6 +330,45 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): self._coroutines_scheduled[fut] = name self.need_update.set() + def toggle_lock(self): + if self.wallet.get_unlocked_password(): + self.lock_wallet() + else: + msg = ' '.join([ + _('Your wallet is locked.'), + _('If you unlock it, its password will not be required to sign transactions.'), + _('Enter your password to unlock your wallet:') + ]) + self.unlock_wallet(message=msg) + + def update_lock_menu(self): + self.lock_menu.setEnabled(self.wallet.has_password()) + text = _('Lock') if self.wallet.get_unlocked_password() else _('Unlock') + self.lock_menu.setText(text) + + @protected + def unlock_wallet(self, password, message=None): + self.wallet.unlock(password) + self.update_lock_icon() + self.update_lock_menu() + icon = read_QIcon("unlock.png") + msg = ' '.join([ + _('Your wallet is unlocked.'), + _('Its password will not be required to sign transactions.'), + ]) + self.show_message(msg, icon=icon.pixmap(30)) + + def lock_wallet(self): + self.wallet.lock_wallet() + self.update_lock_icon() + self.update_lock_menu() + icon = read_QIcon("lock.png") + msg = ' '.join([ + _('Your wallet is locked.'), + _('Its password will be required to sign transactions.'), + ]) + self.show_message(msg, icon=icon.pixmap(30)) + def on_fx_history(self): self.history_model.refresh('fx_history') self.address_list.refresh_all() @@ -580,24 +619,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): ]) self.show_warning(msg, title=_('Watch-only wallet')) - def require_full_encryption(self): - if self.wallet.has_keystore_encryption() and not self.wallet.has_storage_encryption(): - msg = ' '.join([ - _("Your wallet is password-protected, but the wallet file is not encrypted."), - _("This is no longer supported."), - _("Please enter your password in order to encrypt your wallet file."), - ]) - while True: - password = self.password_dialog(msg) - if not password: - self.close() - raise UserCancelled() - try: - self.wallet.update_password(password, password, encrypt_storage=True) - break - except InvalidPassword as e: - self.show_error(str(e)) - def warn_if_testnet(self): if not constants.net.TESTNET: return @@ -724,6 +745,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): self.wallet_menu.addSeparator() self.password_menu = self.wallet_menu.addAction(_("&Password"), self.change_password_dialog) + self.lock_menu = self.wallet_menu.addAction(_("&Unlock"), self.toggle_lock) + self.update_lock_menu() self.seed_menu = self.wallet_menu.addAction(_("&Seed"), self.show_seed_dialog) self.private_keys_menu = self.wallet_menu.addMenu(_("&Private keys")) self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog) @@ -1864,7 +1887,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): "Payments are more likely to succeed with a more complete graph.")) def update_lock_icon(self): - icon = read_QIcon("lock.png") if self.wallet.has_password() else read_QIcon("unlock.png") + icon = read_QIcon("lock.png") if self.wallet.has_password() and (self.wallet.get_unlocked_password() is None) else read_QIcon("unlock.png") self.password_button.setIcon(icon) def update_buttons_on_seed(self): @@ -1897,6 +1920,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): return self._update_wallet_password( old_password=old_password, new_password=new_password, encrypt_storage=encrypt_file) + self.update_lock_menu() def _update_wallet_password(self, *, old_password, new_password, encrypt_storage: bool): try: diff --git a/electrum/wallet.py b/electrum/wallet.py index 12e2df1b5..d77430ede 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -502,9 +502,6 @@ class Abstract_Wallet(ABC, Logger, EventListener): def has_channels(self): return self.lnworker is not None and len(self.lnworker._channels) > 0 - def requires_unlock(self): - return self.config.ENABLE_ANCHOR_CHANNELS and self.has_channels() - def can_have_lightning(self) -> bool: """ whether this wallet can create new channels """ # we want static_remotekey to be a wallet address @@ -3098,9 +3095,8 @@ class Abstract_Wallet(ABC, Logger, EventListener): # save changes. force full rewrite to rm remnants of old password if self.storage and self.storage.file_exists(): self.db.write_and_force_consolidation() - # if wallet was previously unlocked, update password in memory - if self.requires_unlock(): - self.unlock(new_pw) + # if wallet was previously unlocked, reset password_in_memory + self.lock_wallet() @abstractmethod def _update_password_for_keystore(self, old_pw: Optional[str], new_pw: Optional[str]) -> None: @@ -3418,6 +3414,9 @@ class Abstract_Wallet(ABC, Logger, EventListener): self.check_password(password) self._password_in_memory = password + def lock_wallet(self): + self._password_in_memory = None + def get_unlocked_password(self): return self._password_in_memory