electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 02baae10d75f5ea95eb3096b17556a2d371f0113
parent 72491bdf1808e21b7ed24f3bbdad8e8df9325307
Author: SomberNight <somber.night@protonmail.com>
Date:   Tue, 17 Dec 2019 18:39:52 +0100

kivy: implement opening storage-encrypted wallet files

Diffstat:
Melectrum/gui/kivy/main_window.py | 52+++++++++++++++++++++++++++++++++++-----------------
Melectrum/gui/kivy/uix/dialogs/installwizard.py | 3++-
Melectrum/gui/kivy/uix/dialogs/password_dialog.py | 14++++++++++++--
Melectrum/storage.py | 11+++++++++--
Melectrum/wallet.py | 2+-
5 files changed, 59 insertions(+), 23 deletions(-)

diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py @@ -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, @@ -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 @@ -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 @@ -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: @@ -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() @@ -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,))) @@ -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): @@ -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): diff --git a/electrum/gui/kivy/uix/dialogs/installwizard.py b/electrum/gui/kivy/uix/dialogs/installwizard.py @@ -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): diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py @@ -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 @@ -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> @@ -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 = '' diff --git a/electrum/storage.py b/electrum/storage.py @@ -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() @@ -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() @@ -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!") diff --git a/electrum/wallet.py b/electrum/wallet.py @@ -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()