diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index fbd23e236..e4611a32b 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -31,6 +31,7 @@ from kivy.clock import Clock from kivy.factory import Factory from kivy.metrics import inch from kivy.lang import Builder +from .uix.dialogs.password_dialog import PasswordDialog ## lazy imports for factory so that widgets can be used in kv #Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard') @@ -326,7 +327,7 @@ class ElectrumWindow(App): self.wallet = None # type: Optional[Abstract_Wallet] self.pause_time = 0 self.asyncio_loop = asyncio.get_event_loop() - self.pin_code = None + self.password = None App.__init__(self)#, **kwargs) @@ -623,10 +624,12 @@ class ElectrumWindow(App): if wallet.has_password(): def on_success(x): # save pin_code so that we can create backups - self.pin_code = x + self.password = x self.load_wallet(wallet) - self.password_dialog(wallet=wallet, msg=_('Enter PIN code'), - on_success=on_success, on_failure=self.stop) + self.password_dialog( + check_password=wallet.check_password, + on_success=on_success, + on_failure=self.stop) else: self.load_wallet(wallet) else: @@ -642,11 +645,13 @@ class ElectrumWindow(App): if not storage.is_encrypted_with_user_pw(): raise Exception("Kivy GUI does not support this type of encrypted wallet files.") def on_password(pw): - self.pin_code = pw + self.password = pw storage.decrypt(pw) self._on_decrypted_storage(storage) - self.password_dialog(wallet=storage, msg=_('Enter PIN code'), - on_success=on_password, on_failure=self.stop) + self.password_dialog( + check_password=storage.check_password, + on_success=on_password, + on_failure=self.stop) return self._on_decrypted_storage(storage) if not ask_if_wizard: @@ -940,7 +945,7 @@ class ElectrumWindow(App): def on_resume(self): now = time.time() if self.wallet and self.wallet.has_password() and now - self.pause_time > 60: - self.password_dialog(wallet=self.wallet, msg=_('Enter PIN'), on_success=None, on_failure=self.stop) + self.password_dialog(check_password=self.check_pin_code, on_success=None, on_failure=self.stop, is_password=False) if self.nfcscanner: self.nfcscanner.nfc_enable() @@ -1102,12 +1107,12 @@ class ElectrumWindow(App): def on_fee(self, event, *arg): self.fee_status = self.electrum_config.get_fee_status() - def protected(self, msg, f, args): - if self.wallet.has_password(): - on_success = lambda pw: f(*(args + (pw,))) - self.password_dialog(wallet=self.wallet, msg=msg, on_success=on_success, on_failure=lambda: None) + def protected(self, f, args): + if self.electrum_config.get('pin_code'): + on_success = lambda pw: f(*(args + (self.password,))) + self.password_dialog(check_password=self.check_pin_code, on_success=on_success, on_failure=lambda: None, is_password=False) else: - f(*(args + (None,))) + f(*(args + (self.password,))) def toggle_lightning(self): if self.wallet.has_lightning(): @@ -1167,59 +1172,64 @@ class ElectrumWindow(App): self.load_wallet_by_name(new_path) def show_seed(self, label): - self.protected(_("Enter your PIN code in order to decrypt your seed"), self._show_seed, (label,)) + self.protected(self._show_seed, (label,)) def _show_seed(self, label, password): if self.wallet.has_password() and password is None: return keystore = self.wallet.keystore - try: - seed = keystore.get_seed(password) - passphrase = keystore.get_passphrase(password) - except: - self.show_error("Invalid PIN") - return + seed = keystore.get_seed(password) + passphrase = keystore.get_passphrase(password) label.data = seed if passphrase: label.data += '\n\n' + _('Passphrase') + ': ' + passphrase - def password_dialog(self, *, wallet: Union[Abstract_Wallet, WalletStorage], - msg: str, on_success: Callable = None, on_failure: Callable = None): - from .uix.dialogs.password_dialog import PasswordDialog + def has_pin_code(self): + return bool(self.electrum_config.get('pin_code')) + + def check_pin_code(self, pin): + if pin != self.electrum_config.get('pin_code'): + raise InvalidPassword + + def password_dialog(self, *, check_password: Callable = None, + on_success: Callable = None, on_failure: Callable = None, + is_password=True): if self._password_dialog is None: self._password_dialog = PasswordDialog() - self._password_dialog.init(self, wallet=wallet, msg=msg, - on_success=on_success, on_failure=on_failure) + self._password_dialog.init( + self, check_password = check_password, + on_success=on_success, on_failure=on_failure, + is_password=is_password) self._password_dialog.open() def change_password(self, cb): - from .uix.dialogs.password_dialog import PasswordDialog if self._password_dialog is None: self._password_dialog = PasswordDialog() - message = _("Changing PIN code.") + '\n' + _("Enter your current PIN:") def on_success(old_password, new_password): self.wallet.update_password(old_password, new_password) - self.show_info(_("Your PIN code was updated")) - on_failure = lambda: self.show_error(_("PIN codes do not match")) - self._password_dialog.init(self, wallet=self.wallet, msg=message, - on_success=on_success, on_failure=on_failure, is_change=True) + self.password = new_password + self.show_info(_("Your password was updated")) + on_failure = lambda: self.show_error(_("Password not updated")) + self._password_dialog.init( + self, check_password = self.wallet.check_password, + on_success=on_success, on_failure=on_failure, + is_change=True, is_password=True, + has_password=self.wallet.has_password()) self._password_dialog.open() - def change_backup_password(self): - from .uix.dialogs.password_dialog import PasswordDialog - from electrum.util import get_backup_dir - from electrum.storage import WalletStorage + def change_pin_code(self, cb): if self._password_dialog is None: self._password_dialog = PasswordDialog() - message = _("Create backup.") + '\n' + _("Enter your current PIN:") def on_success(old_password, new_password): - backup_pubkey = WalletStorage.get_eckey_from_password(new_password).get_public_key_hex() - # TODO: use a unique PIN for all wallets - self.electrum_config.set_key('backup_pubkey', backup_pubkey) - self.show_info(_("Backup password set")) - on_failure = lambda: self.show_error(_("Passwords do not match")) - self._password_dialog.init(self, wallet=self.wallet, msg=message, - on_success=on_success, on_failure=on_failure, is_change=True, is_backup=True) + self.electrum_config.set_key('pin_code', new_password) + cb() + self.show_info(_("PIN updated") if new_password else _('PIN disabled')) + on_failure = lambda: self.show_error(_("PIN not updated")) + self._password_dialog.init( + self, check_password=self.check_pin_code, + on_success=on_success, on_failure=on_failure, + is_change=True, is_password=False, + has_password = self.has_pin_code()) self._password_dialog.open() def save_backup(self): @@ -1238,7 +1248,7 @@ class ElectrumWindow(App): request_permissions([Permission.WRITE_EXTERNAL_STORAGE], cb) def _save_backup(self): - new_path = self.wallet.save_backup(pin_code=self.pin_code) + new_path = self.wallet.save_backup() if new_path: self.show_info(_("Backup saved:") + f"\n{new_path}") else: diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py index 86b3567aa..6f49a096e 100644 --- a/electrum/gui/kivy/uix/dialogs/password_dialog.py +++ b/electrum/gui/kivy/uix/dialogs/password_dialog.py @@ -112,44 +112,48 @@ Builder.load_string(''' class PasswordDialog(Factory.Popup): def init(self, app: 'ElectrumWindow', *, - wallet: Union['Abstract_Wallet', 'WalletStorage'] = None, - msg: str, on_success: Callable = None, on_failure: Callable = None, - is_change: bool = False, is_backup: bool = False): + check_password = None, + on_success: Callable = None, on_failure: Callable = None, + is_change: bool = False, + is_password: bool = False, + has_password: bool = False): self.app = app - self.is_backup = is_backup - self.wallet = wallet - self.message = msg + self.pw_check = check_password + self.message = '' self.on_success = on_success self.on_failure = on_failure self.success = False self.is_change = is_change self.pw = None self.new_password = None - self.title = 'Electrum' + (' - ' + self.wallet.basename() if self.wallet else '') - self.level = 1 if is_backup else 0 - self.is_generic = self.is_backup + self.title = 'Electrum' + self.level = 1 if is_change and not has_password else 0 + self.is_generic = is_password self.update_screen() def update_screen(self): self.ids.kb.password = '' self.ids.textinput_generic_password.text = '' if self.level == 0: - self.message = _('Enter your PIN') + self.message = _('Enter your password') if self.is_generic else _('Enter your PIN') elif self.level == 1: - self.message = _('Enter a strong password for your backup') if self.is_backup else _('Enter new PIN') + self.message = _('Enter new password') if self.is_generic else _('Enter new PIN') elif self.level == 2: - self.message = _('Confirm backup password') if self.is_backup else _('Confirm new PIN') + self.message = _('Confirm new password') if self.is_generic else _('Confirm new PIN') def check_password(self, password): if self.level > 0: return True try: - self.wallet.check_password(password) + self.pw_check(password) return True except InvalidPassword as e: return False def on_dismiss(self): + if self.level == 1 and not self.is_generic and self.on_success: + self.on_success(self.pw, None) + return False if not self.success: if self.on_failure: self.on_failure() @@ -195,7 +199,6 @@ class PasswordDialog(Factory.Popup): self.update_screen() elif self.level == 2: self.success = pw == self.new_password - self.is_generic = False self.dismiss() else: self.app.show_error(_('Wrong PIN')) diff --git a/electrum/gui/kivy/uix/dialogs/settings.py b/electrum/gui/kivy/uix/dialogs/settings.py index 4f9fe3c9d..2f58e7e64 100644 --- a/electrum/gui/kivy/uix/dialogs/settings.py +++ b/electrum/gui/kivy/uix/dialogs/settings.py @@ -18,7 +18,8 @@ Builder.load_string(''' id: settings title: _('Electrum Settings') - disable_pin: False + disable_password: False + has_pin_code: False use_encryption: False BoxLayout: orientation: 'vertical' @@ -36,10 +37,10 @@ Builder.load_string(''' action: partial(root.language_dialog, self) CardSeparator SettingsItem: - disabled: root.disable_pin - title: _('PIN code') - description: _("Change your PIN code.") - action: partial(root.change_password, self) + status: 'ON' if root.has_pin_code else 'OFF' + title: _('PIN code') + ': ' + self.status + description: _("Change your PIN code.") if root.has_pin_code else _("Add PIN code") + action: partial(root.change_pin_code, self) CardSeparator SettingsItem: bu: app.base_unit @@ -84,9 +85,10 @@ Builder.load_string(''' action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message) CardSeparator SettingsItem: - title: _('Backups') - description: _("Set password for encrypted backups.") - action: root.change_backup_password + disabled: root.disable_password + title: _('Password') + description: _("Change wallet password.") + action: root.change_password # disabled: there is currently only one coin selection policy #CardSeparator @@ -117,17 +119,18 @@ class SettingsDialog(Factory.Popup): def update(self): self.wallet = self.app.wallet - self.disable_pin = self.wallet.is_watching_only() if self.wallet else True + self.disable_password = self.wallet.is_watching_only() if self.wallet else True self.use_encryption = self.wallet.has_password() if self.wallet else False + self.has_pin_code = self.app.has_pin_code() def get_language_name(self): return languages.get(self.config.get('language', 'en_UK'), '') - def change_password(self, item, dt): + def change_password(self, dt): self.app.change_password(self.update) - def change_backup_password(self, dt): - self.app.change_backup_password() + def change_pin_code(self, label, dt): + self.app.change_pin_code(self.update) def language_dialog(self, item, dt): if self._language_dialog is None: diff --git a/electrum/wallet.py b/electrum/wallet.py index 811739495..c6d42958e 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -263,24 +263,15 @@ class Abstract_Wallet(AddressSynchronizer, ABC): if self.storage: self.db.write(self.storage) - def save_backup(self, pin_code=None): + def save_backup(self): new_db = WalletDB(self.db.dump(), manual_upgrades=False) new_db.put('is_backup', True) new_path = os.path.join(get_backup_dir(self.config), self.basename() + '.backup') if new_path is None: return new_storage = WalletStorage(new_path) - if pin_code: - backup_pubkey = self.config.get('backup_pubkey') - if backup_pubkey is None: - return - w2 = Wallet(new_db, None, config=self.config) - w2.update_password(pin_code, None) - new_storage._encryption_version = StorageEncryptionVersion.USER_PASSWORD - new_storage.pubkey = backup_pubkey - else: - new_storage._encryption_version = self.storage._encryption_version - new_storage.pubkey = self.storage.pubkey + new_storage._encryption_version = self.storage._encryption_version + new_storage.pubkey = self.storage.pubkey new_db.set_modified(True) new_db.write(new_storage) return new_path