URI: 
       tkivy: implement opening storage-encrypted wallet files - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 02baae10d75f5ea95eb3096b17556a2d371f0113
   DIR parent 72491bdf1808e21b7ed24f3bbdad8e8df9325307
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Tue, 17 Dec 2019 18:39:52 +0100
       
       kivy: implement opening storage-encrypted wallet files
       
       Diffstat:
         M electrum/gui/kivy/main_window.py    |      52 +++++++++++++++++++++----------
         M electrum/gui/kivy/uix/dialogs/inst… |       3 ++-
         M electrum/gui/kivy/uix/dialogs/pass… |      14 ++++++++++++--
         M electrum/storage.py                 |      11 +++++++++--
         M electrum/wallet.py                  |       2 +-
       
       5 files changed, 59 insertions(+), 23 deletions(-)
       ---
   DIR diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
       t@@ -7,10 +7,10 @@ import traceback
        from decimal import Decimal
        import threading
        import asyncio
       -from typing import TYPE_CHECKING, Optional
       +from typing import TYPE_CHECKING, Optional, Union, Callable
        
        from electrum.storage import WalletStorage, StorageReadWriteError
       -from electrum.wallet import Wallet, InternalAddressCorruption
       +from electrum.wallet import Wallet, InternalAddressCorruption, Abstract_Wallet
        from electrum.plugin import run_hook
        from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter,
                                   format_satoshis, format_satoshis_plain, format_fee_satoshis,
       t@@ -81,7 +81,6 @@ from .uix.dialogs.lightning_channels import LightningChannelsDialog
        if TYPE_CHECKING:
            from . import ElectrumGui
            from electrum.simple_config import SimpleConfig
       -    from electrum.wallet import Abstract_Wallet
            from electrum.plugin import Plugins
        
        
       t@@ -600,6 +599,16 @@ class ElectrumWindow(App):
                    self.load_wallet_by_name(self.electrum_config.get_wallet_path(use_gui_last_wallet=True),
                                             ask_if_wizard=True)
        
       +    def _on_decrypted_storage(self, storage: WalletStorage):
       +        assert storage.is_past_initial_decryption()
       +        if storage.requires_upgrade():
       +            wizard = Factory.InstallWizard(self.electrum_config, self.plugins)
       +            wizard.path = storage.path
       +            wizard.bind(on_wizard_complete=self.on_wizard_complete)
       +            wizard.upgrade_storage(storage)
       +        else:
       +            self.on_wizard_complete(wizard=None, storage=storage)
       +
            def load_wallet_by_name(self, path, ask_if_wizard=False):
                if not path:
                    return
       t@@ -608,23 +617,29 @@ class ElectrumWindow(App):
                wallet = self.daemon.load_wallet(path, None)
                if wallet:
                    if platform == 'android' and wallet.has_password():
       -                self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop)
       +                self.password_dialog(wallet=wallet, msg=_('Enter PIN code'),
       +                                     on_success=lambda x: self.load_wallet(wallet), on_failure=self.stop)
                    else:
                        self.load_wallet(wallet)
                else:
                    def launch_wizard():
       -                wizard = Factory.InstallWizard(self.electrum_config, self.plugins)
       -                wizard.path = path
       -                wizard.bind(on_wizard_complete=self.on_wizard_complete)
                        storage = WalletStorage(path, manual_upgrades=True)
                        if not storage.file_exists():
       +                    wizard = Factory.InstallWizard(self.electrum_config, self.plugins)
       +                    wizard.path = path
       +                    wizard.bind(on_wizard_complete=self.on_wizard_complete)
                            wizard.run('new')
       -                elif storage.is_encrypted():
       -                    raise Exception("Kivy GUI does not support encrypted wallet files.")
       -                elif storage.requires_upgrade():
       -                    wizard.upgrade_storage(storage)
                        else:
       -                    raise Exception("unexpected storage file situation")
       +                    if storage.is_encrypted():
       +                        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):
       +                            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)
       +                        return
       +                    self._on_decrypted_storage(storage)
                    if not ask_if_wizard:
                        launch_wizard()
                    else:
       t@@ -917,7 +932,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(self.wallet, _('Enter PIN'), None, self.stop)
       +            self.password_dialog(wallet=self.wallet, msg=_('Enter PIN'), on_success=None, on_failure=self.stop)
                if self.nfcscanner:
                    self.nfcscanner.nfc_enable()
        
       t@@ -1082,7 +1097,7 @@ class ElectrumWindow(App):
            def protected(self, msg, f, args):
                if self.wallet.has_password():
                    on_success = lambda pw: f(*(args + (pw,)))
       -            self.password_dialog(self.wallet, msg, on_success, lambda: None)
       +            self.password_dialog(wallet=self.wallet, msg=msg, on_success=on_success, on_failure=lambda: None)
                else:
                    f(*(args + (None,)))
        
       t@@ -1160,11 +1175,13 @@ class ElectrumWindow(App):
                if passphrase:
                    label.data += '\n\n' + _('Passphrase') + ': ' + passphrase
        
       -    def password_dialog(self, wallet, msg, on_success, on_failure):
       +    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
                if self._password_dialog is None:
                    self._password_dialog = PasswordDialog()
       -        self._password_dialog.init(self, wallet, msg, on_success, on_failure)
       +        self._password_dialog.init(self, wallet=wallet, msg=msg,
       +                                   on_success=on_success, on_failure=on_failure)
                self._password_dialog.open()
        
            def change_password(self, cb):
       t@@ -1176,7 +1193,8 @@ class ElectrumWindow(App):
                    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, self.wallet, message, on_success, on_failure, is_change=1)
       +        self._password_dialog.init(self, wallet=self.wallet, msg=message,
       +                                   on_success=on_success, on_failure=on_failure, is_change=1)
                self._password_dialog.open()
        
            def export_private_keys(self, pk_label, addr):
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/installwizard.py b/electrum/gui/kivy/uix/dialogs/installwizard.py
       t@@ -1157,7 +1157,8 @@ class InstallWizard(BaseWizard, Widget):
                    self.run('request_password', run_next)
                popup = PasswordDialog()
                app = App.get_running_app()
       -        popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2)
       +        popup.init(app, wallet=None, msg=_('Choose PIN code'),
       +                   on_success=on_success, on_failure=on_failure, is_change=2)
                popup.open()
        
            def action_dialog(self, action, run_next):
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py
       t@@ -1,3 +1,5 @@
       +from typing import Callable, TYPE_CHECKING, Optional, Union
       +
        from kivy.app import App
        from kivy.factory import Factory
        from kivy.properties import ObjectProperty
       t@@ -8,6 +10,11 @@ from kivy.clock import Clock
        from electrum.util import InvalidPassword
        from electrum.gui.kivy.i18n import _
        
       +if TYPE_CHECKING:
       +    from ...main_window import ElectrumWindow
       +    from electrum.wallet import Abstract_Wallet
       +    from electrum.storage import WalletStorage
       +
        Builder.load_string('''
        
        <PasswordDialog@Popup>
       t@@ -71,10 +78,13 @@ Builder.load_string('''
        
        class PasswordDialog(Factory.Popup):
        
       -    def init(self, app, wallet, message, on_success, on_failure, is_change=0):
       +    def init(self, app: 'ElectrumWindow', *,
       +             wallet: Union['Abstract_Wallet', 'WalletStorage'] = None,
       +             msg: str, on_success: Callable = None, on_failure: Callable = None,
       +             is_change: int = 0):
                self.app = app
                self.wallet = wallet
       -        self.message = message
       +        self.message = msg
                self.on_success = on_success
                self.on_failure = on_failure
                self.ids.kb.password = ''
   DIR diff --git a/electrum/storage.py b/electrum/storage.py
       t@@ -203,7 +203,9 @@ class WalletStorage(Logger):
                else:
                    raise WalletFileException('no encryption magic for version: %s' % v)
        
       -    def decrypt(self, password):
       +    def decrypt(self, password) -> None:
       +        if self.is_past_initial_decryption():
       +            return
                ec_key = self.get_eckey_from_password(password)
                if self.raw:
                    enc_magic = self._get_encryption_magic()
       t@@ -226,10 +228,12 @@ class WalletStorage(Logger):
                    s = s.decode('utf8')
                return s
        
       -    def check_password(self, password):
       +    def check_password(self, password) -> None:
                """Raises an InvalidPassword exception on invalid password"""
                if not self.is_encrypted():
                    return
       +        if not self.is_past_initial_decryption():
       +            self.decrypt(password)  # this sets self.pubkey
                if self.pubkey and self.pubkey != self.get_eckey_from_password(password).get_public_key_hex():
                    raise InvalidPassword()
        
       t@@ -250,6 +254,9 @@ class WalletStorage(Logger):
                # make sure next storage.write() saves changes
                self.db.set_modified(True)
        
       +    def basename(self) -> str:
       +        return os.path.basename(self.path)
       +
            def requires_upgrade(self):
                if not self.is_past_initial_decryption():
                    raise Exception("storage not yet decrypted!")
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -333,7 +333,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                return []
        
            def basename(self) -> str:
       -        return os.path.basename(self.storage.path)
       +        return self.storage.basename()
        
            def test_addresses_sanity(self) -> None:
                addrs = self.get_receiving_addresses()