commit 1e4fa83098104b34eb104a695ccddb884e519cde
parent 94065414564c24dc2a1724f736b4a0651d1eec9e
Author: ThomasV <thomasv@electrum.org>
Date: Wed, 2 Dec 2020 10:03:00 +0100
Kivy: use the same password for all wallets
When the app is started, the password is checked against all
wallets in the directory.
If the test passes:
- subsequent wallet creations will use the same password
- subsequent password updates will be performed on all wallets
- wallets that are not storage encrypted will encrypted
on the next password update (even if they are watching-only)
This behaviour is restricted on Android, with a 'single_password' config variable.
Wallet creation without password is disabled if single_password is set
Diffstat:
7 files changed, 105 insertions(+), 16 deletions(-)
diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
@@ -12,6 +12,8 @@ from typing import TYPE_CHECKING, Optional, Union, Callable, Sequence
from electrum.storage import WalletStorage, StorageReadWriteError
from electrum.wallet_db import WalletDB
from electrum.wallet import Wallet, InternalAddressCorruption, Abstract_Wallet
+from electrum.wallet import check_password_for_directory, update_password_for_directory
+
from electrum.plugin import run_hook
from electrum import util
from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter,
@@ -367,6 +369,7 @@ class ElectrumWindow(App, Logger):
self.pause_time = 0
self.asyncio_loop = asyncio.get_event_loop()
self.password = None
+ self._use_single_password = False
App.__init__(self)#, **kwargs)
Logger.__init__(self)
@@ -634,6 +637,9 @@ class ElectrumWindow(App, Logger):
def on_wizard_success(self, storage, db, password):
self.password = password
+ if self.electrum_config.get('single_password'):
+ self._use_single_password = check_password_for_directory(self.electrum_config, password)
+ self.logger.info(f'use single password: {self._use_single_password}')
wallet = Wallet(db, storage, config=self.electrum_config)
wallet.start_network(self.daemon.network)
self.daemon.add_wallet(wallet)
@@ -649,6 +655,12 @@ class ElectrumWindow(App, Logger):
return
if self.wallet and self.wallet.storage.path == path:
return
+ if self.password and self._use_single_password:
+ storage = WalletStorage(path)
+ # call check_password to decrypt
+ storage.check_password(self.password)
+ self.on_open_wallet(self.password, storage)
+ return
d = OpenWalletDialog(self, path, self.on_open_wallet)
d.open()
@@ -724,10 +736,13 @@ class ElectrumWindow(App, Logger):
if self._channels_dialog:
Clock.schedule_once(lambda dt: self._channels_dialog.update())
+ def is_wallet_creation_disabled(self):
+ return bool(self.electrum_config.get('single_password')) and self.password is None
+
def wallets_dialog(self):
from .uix.dialogs.wallets import WalletDialog
dirname = os.path.dirname(self.electrum_config.get_wallet_path())
- d = WalletDialog(dirname, self.load_wallet_by_name)
+ d = WalletDialog(dirname, self.load_wallet_by_name, self.is_wallet_creation_disabled())
d.open()
def popup_dialog(self, name):
@@ -1219,9 +1234,18 @@ class ElectrumWindow(App, Logger):
def change_password(self, cb):
def on_success(old_password, new_password):
- self.wallet.update_password(old_password, new_password)
+ # called if old_password works on self.wallet
self.password = new_password
- self.show_info(_("Your password was updated"))
+ if self._use_single_password:
+ path = self.wallet.storage.path
+ self.stop_wallet()
+ update_password_for_directory(self.electrum_config, old_password, new_password)
+ self.load_wallet_by_name(path)
+ msg = _("Password updated successfully")
+ else:
+ self.wallet.update_password(old_password, new_password)
+ msg = _("Password updated for {}").format(os.path.basename(self.wallet.storage.path))
+ self.show_info(msg)
on_failure = lambda: self.show_error(_("Password not updated"))
d = ChangePasswordDialog(self, self.wallet, on_success, on_failure)
d.open()
diff --git a/electrum/gui/kivy/uix/dialogs/installwizard.py b/electrum/gui/kivy/uix/dialogs/installwizard.py
@@ -1149,9 +1149,8 @@ class InstallWizard(BaseWizard, Widget):
Clock.schedule_once(lambda dt: self.app.show_error(msg))
def request_password(self, run_next, force_disable_encrypt_cb=False):
- if force_disable_encrypt_cb:
- # do not request PIN for watching-only wallets
- run_next(None, False)
+ if self.app.password is not None:
+ run_next(self.app.password, True)
return
def on_success(old_pw, pw):
assert old_pw is None
diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py
@@ -29,6 +29,7 @@ Builder.load_string('''
message: ''
basename:''
is_change: False
+ hide_wallet_label: False
require_password: True
BoxLayout:
size_hint: 1, 1
@@ -45,13 +46,15 @@ Builder.load_string('''
font_size: '20dp'
text: _('Wallet') + ': ' + root.basename
text_size: self.width, None
+ disabled: root.hide_wallet_label
+ opacity: 0 if root.hide_wallet_label else 1
IconButton:
size_hint: 0.15, None
height: '40dp'
icon: f'atlas://{KIVY_GUI_PATH}/theming/light/btn_create_account'
on_release: root.select_file()
- disabled: root.is_change
- opacity: 0 if root.is_change else 1
+ disabled: root.hide_wallet_label or root.is_change
+ opacity: 0 if root.hide_wallet_label or root.is_change else 1
Widget:
size_hint: 1, 0.05
Label:
@@ -267,6 +270,7 @@ class PasswordDialog(AbstractPasswordDialog):
def __init__(self, app, **kwargs):
AbstractPasswordDialog.__init__(self, app, **kwargs)
+ self.hide_wallet_label = app._use_single_password
def clear_password(self):
self.ids.textinput_generic_password.text = ''
@@ -320,6 +324,7 @@ class ChangePasswordDialog(PasswordDialog):
class OpenWalletDialog(PasswordDialog):
+ """This dialog will let the user choose another wallet file if they don't remember their the password"""
def __init__(self, app, path, callback):
self.app = app
@@ -331,7 +336,7 @@ class OpenWalletDialog(PasswordDialog):
def select_file(self):
dirname = os.path.dirname(self.app.electrum_config.get_wallet_path())
- d = WalletDialog(dirname, self.init_storage_from_path)
+ d = WalletDialog(dirname, self.init_storage_from_path, self.app.is_wallet_creation_disabled())
d.open()
def init_storage_from_path(self, path):
@@ -343,9 +348,14 @@ class OpenWalletDialog(PasswordDialog):
elif self.storage.is_encrypted():
if not self.storage.is_encrypted_with_user_pw():
raise Exception("Kivy GUI does not support this type of encrypted wallet files.")
- self.require_password = True
self.pw_check = self.storage.check_password
- self.message = self.enter_pw_message
+ if self.app.password and self.check_password(self.app.password):
+ self.pw = self.app.password # must be set so that it is returned in callback
+ self.require_password = False
+ self.message = _('Press Next to open')
+ else:
+ self.require_password = True
+ self.message = self.enter_pw_message
else:
# it is a bit wasteful load the wallet here and load it again in main_window,
# but that is fine, because we are progressively enforcing storage encryption.
diff --git a/electrum/gui/kivy/uix/dialogs/settings.py b/electrum/gui/kivy/uix/dialogs/settings.py
@@ -87,7 +87,7 @@ Builder.load_string('''
CardSeparator
SettingsItem:
title: _('Password')
- description: _("Change wallet password.")
+ description: _('Change your password') if app._use_single_password else _("Change your password for this wallet.")
action: root.change_password
CardSeparator
SettingsItem:
diff --git a/electrum/gui/kivy/uix/dialogs/wallets.py b/electrum/gui/kivy/uix/dialogs/wallets.py
@@ -16,6 +16,7 @@ Builder.load_string('''
title: _('Wallets')
id: popup
path: ''
+ disable_new: True
BoxLayout:
orientation: 'vertical'
padding: '10dp'
@@ -33,7 +34,8 @@ Builder.load_string('''
cols: 3
size_hint_y: 0.1
Button:
- id: open_button
+ id: new_button
+ disabled: root.disable_new
size_hint: 0.1, None
height: '48dp'
text: _('New')
@@ -53,12 +55,14 @@ Builder.load_string('''
class WalletDialog(Factory.Popup):
- def __init__(self, path, callback):
+ def __init__(self, path, callback, disable_new):
Factory.Popup.__init__(self)
self.path = path
self.callback = callback
+ self.disable_new = disable_new
def new_wallet(self, dirname):
+ assert self.disable_new is False
def cb(filename):
if not filename:
return
diff --git a/electrum/wallet.py b/electrum/wallet.py
@@ -2951,12 +2951,63 @@ def restore_wallet_from_text(text, *, path, config: SimpleConfig,
if gap_limit is not None:
db.put('gap_limit', gap_limit)
wallet = Wallet(db, storage, config=config)
-
assert not storage.file_exists(), "file was created too soon! plaintext keys might have been written to disk"
wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
wallet.synchronize()
msg = ("This wallet was restored offline. It may contain more addresses than displayed. "
"Start a daemon and use load_wallet to sync its history.")
-
wallet.save_db()
return {'wallet': wallet, 'msg': msg}
+
+
+def check_password_for_directory(config, old_password, new_password=None):
+ """Checks password against all wallets and returns True if they can all be updated.
+ If new_password is not None, update all wallet passwords to new_password.
+ """
+ dirname = os.path.dirname(config.get_wallet_path())
+ failed = []
+ for filename in os.listdir(dirname):
+ path = os.path.join(dirname, filename)
+ basename = os.path.basename(path)
+ storage = WalletStorage(path)
+ if not storage.is_encrypted():
+ # it is a bit wasteful load the wallet here, but that is fine
+ # because we are progressively enforcing storage encryption.
+ db = WalletDB(storage.read(), manual_upgrades=False)
+ wallet = Wallet(db, storage, config=config)
+ if wallet.has_keystore_encryption():
+ try:
+ wallet.check_password(old_password)
+ except:
+ failed.append(basename)
+ continue
+ if new_password:
+ wallet.update_password(old_password, new_password)
+ else:
+ if new_password:
+ wallet.update_password(None, new_password)
+ continue
+ if not storage.is_encrypted_with_user_pw():
+ failed.append(basename)
+ continue
+ try:
+ storage.check_password(old_password)
+ except:
+ failed.append(basename)
+ continue
+ db = WalletDB(storage.read(), manual_upgrades=False)
+ wallet = Wallet(db, storage, config=config)
+ try:
+ wallet.check_password(old_password)
+ except:
+ failed.append(basename)
+ continue
+ if new_password:
+ wallet.update_password(old_password, new_password)
+ return failed == []
+
+
+def update_password_for_directory(config, old_password, new_password) -> bool:
+ assert new_password is not None
+ assert check_password_for_directory(config, old_password, None)
+ return check_password_for_directory(config, old_password, new_password)
diff --git a/run_electrum b/run_electrum
@@ -317,6 +317,7 @@ def main():
'verbosity': '*' if build_config.DEBUG else '',
'cmd': 'gui',
'gui': 'kivy',
+ 'single_password':True,
}
else:
config_options = args.__dict__