electrum

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

commit b75d82491bfe939af14e1436708a7be06751ed16
parent 95780a39a3a3c87099e9a6eab407e86392a5dce2
Author: ThomasV <thomasv@electrum.org>
Date:   Thu, 22 Mar 2018 16:39:01 +0100

kivy: request PIN code on startup

Diffstat:
Mgui/kivy/main.kv | 3++-
Mgui/kivy/main_window.py | 69++++++++++++++++++++++++++++++++-------------------------------------
Mgui/kivy/uix/dialogs/installwizard.py | 28+++++++++-------------------
Mgui/kivy/uix/dialogs/password_dialog.py | 95++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mgui/kivy/uix/dialogs/settings.py | 3+--
5 files changed, 112 insertions(+), 86 deletions(-)

diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv @@ -285,7 +285,8 @@ <KButton@Button>: size_hint: 1, None - height: '48dp' + height: '60dp' + font_size: '30dp' on_release: self.parent.update_amount(self.text) diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py @@ -244,6 +244,7 @@ class ElectrumWindow(App): self.tabs = None self.is_exit = False self.wallet = None + self.pause_time = 0 App.__init__(self)#, **kwargs) @@ -445,7 +446,6 @@ class ElectrumWindow(App): #win.softinput_mode = 'below_target' self.on_size(win, win.size) self.init_ui() - self.load_wallet_by_name(self.electrum_config.get_wallet_path()) # init plugins run_hook('init_kivy', self) # fiat currency @@ -467,6 +467,8 @@ class ElectrumWindow(App): self.network.register_callback(self.on_fee, ['fee']) self.network.register_callback(self.on_quotes, ['on_quotes']) self.network.register_callback(self.on_history, ['on_history']) + # load wallet + self.load_wallet_by_name(self.electrum_config.get_wallet_path()) # URI passed in config uri = self.electrum_config.get('url') if uri: @@ -484,17 +486,18 @@ class ElectrumWindow(App): wallet.start_threads(self.daemon.network) self.daemon.add_wallet(wallet) self.load_wallet(wallet) - self.on_resume() def load_wallet_by_name(self, path): if not path: return + if self.wallet and self.wallet.storage.path == path: + return wallet = self.daemon.load_wallet(path, None) if wallet: - if wallet != self.wallet: - self.stop_wallet() + if wallet.has_password(): + self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop) + else: self.load_wallet(wallet) - self.on_resume() else: Logger.debug('Electrum: Wallet not found. Launching install wizard') storage = WalletStorage(path) @@ -504,6 +507,7 @@ class ElectrumWindow(App): wizard.run(action) def on_stop(self): + Logger.info('on_stop') self.stop_wallet() def stop_wallet(self): @@ -617,6 +621,8 @@ class ElectrumWindow(App): @profiler def load_wallet(self, wallet): + if self.wallet: + self.stop_wallet() self.wallet = wallet self.update_wallet() # Once GUI has been initialized check if we want to announce something @@ -625,6 +631,7 @@ class ElectrumWindow(App): self.receive_screen.clear() self.update_tabs() run_hook('load_wallet', wallet, self) + print('load wallet done', self.wallet) def update_status(self, *dt): self.num_blocks = self.network.get_local_height() @@ -684,12 +691,16 @@ class ElectrumWindow(App): Logger.Error('Notification: needs plyer; `sudo pip install plyer`') def on_pause(self): + self.pause_time = time.time() # pause nfc if self.nfcscanner: self.nfcscanner.nfc_disable() return True def on_resume(self): + now = time.time() + if self.wallet.has_password and now - self.pause_time > 60: + self.password_dialog(self.wallet, _('Enter PIN'), None, self.stop) if self.nfcscanner: self.nfcscanner.nfc_enable() @@ -875,7 +886,8 @@ class ElectrumWindow(App): def protected(self, msg, f, args): if self.wallet.has_password(): - self.password_dialog(msg, f, args) + on_success = lambda pw: f(*(args + (pw,))) + self.password_dialog(self.wallet, msg, on_success, lambda: None) else: f(*(args + (None,))) @@ -887,7 +899,7 @@ class ElectrumWindow(App): def _delete_wallet(self, b): if b: - basename = os.path.basename(self.wallet.storage.path) + basename = self.wallet.basename() self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ()) def __delete_wallet(self, pw): @@ -925,40 +937,23 @@ class ElectrumWindow(App): if passphrase: label.text += '\n\n' + _('Passphrase') + ': ' + passphrase - def change_password(self, cb): - if self.wallet.has_password(): - self.protected(_("Changing PIN code.") + '\n' + _("Enter your current PIN:"), self._change_password, (cb,)) - else: - self._change_password(cb, None) - - def _change_password(self, cb, old_password): - if self.wallet.has_password(): - if old_password is None: - return - try: - self.wallet.check_password(old_password) - except InvalidPassword: - self.show_error("Invalid PIN") - return - self.password_dialog(_('Enter new PIN'), self._change_password2, (cb, old_password,)) - - def _change_password2(self, cb, old_password, new_password): - self.password_dialog(_('Confirm new PIN'), self._change_password3, (cb, old_password, new_password)) - - def _change_password3(self, cb, old_password, new_password, confirmed_password): - if new_password == confirmed_password: - self.wallet.update_password(old_password, new_password) - cb() - else: - self.show_error("PIN numbers do not match") + def password_dialog(self, wallet, msg, on_success, on_failure): + 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.open() - def password_dialog(self, msg, f, args): + def change_password(self, cb): from .uix.dialogs.password_dialog import PasswordDialog - def callback(pw): - Clock.schedule_once(lambda x: f(*(args + (pw,))), 0.1) if self._password_dialog is None: self._password_dialog = PasswordDialog() - self._password_dialog.init(msg, callback) + 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, self.wallet, message, on_success, on_failure, is_change=1) self._password_dialog.open() def export_private_keys(self, pk_label, addr): diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py @@ -802,28 +802,18 @@ class InstallWizard(BaseWizard, Widget): app = App.get_running_app() Clock.schedule_once(lambda dt: app.show_error(msg)) - def password_dialog(self, message, callback): + def request_password(self, run_next, force_disable_encrypt_cb=False): + def on_success(old_pin, pin): + assert old_pin is None + run_next(pin, False) + def on_failure(): + self.show_error(_('PIN mismatch')) + self.run('request_password', run_next) popup = PasswordDialog() - popup.init(message, callback) + app = App.get_running_app() + popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2) popup.open() - def request_password(self, run_next, force_disable_encrypt_cb=False): - def callback(pin): - if pin: - self.run('confirm_password', pin, run_next) - else: - run_next(None, None) - self.password_dialog('Choose a PIN code', callback) - - def confirm_password(self, pin, run_next): - def callback(conf): - if conf == pin: - run_next(pin, False) - else: - self.show_error(_('PIN mismatch')) - self.run('request_password', run_next) - self.password_dialog('Confirm your PIN code', callback) - def action_dialog(self, action, run_next): f = getattr(self, action) f() diff --git a/gui/kivy/uix/dialogs/password_dialog.py b/gui/kivy/uix/dialogs/password_dialog.py @@ -5,35 +5,42 @@ from kivy.lang import Builder from decimal import Decimal from kivy.clock import Clock +from electrum.util import InvalidPassword +from electrum_gui.kivy.i18n import _ + Builder.load_string(''' <PasswordDialog@Popup> id: popup - title: _('PIN Code') + title: 'Electrum' message: '' - size_hint: 0.9, 0.9 BoxLayout: + size_hint: 1, 1 orientation: 'vertical' Widget: - size_hint: 1, 1 + size_hint: 1, 0.05 Label: + font_size: '20dp' text: root.message text_size: self.width, None size: self.texture_size Widget: - size_hint: 1, 1 + size_hint: 1, 0.05 Label: id: a - text: ' * '*len(kb.password) + ' o '*(6-len(kb.password)) + font_size: '50dp' + text: '*'*len(kb.password) + '-'*(6-len(kb.password)) + size: self.texture_size Widget: - size_hint: 1, 1 + size_hint: 1, 0.05 GridLayout: id: kb + size_hint: 1, None + height: self.minimum_height update_amount: popup.update_password password: '' on_password: popup.on_password(self.password) - size_hint: 1, None - height: '200dp' + spacing: '2dp' cols: 3 KButton: text: '1' @@ -59,30 +66,44 @@ Builder.load_string(''' text: '0' KButton: text: '<' - BoxLayout: - size_hint: 1, None - height: '48dp' - Widget: - size_hint: 0.5, None - Button: - size_hint: 0.5, None - height: '48dp' - text: _('Cancel') - on_release: - popup.dismiss() - popup.callback(None) ''') class PasswordDialog(Factory.Popup): - #def __init__(self, message, callback): - # Factory.Popup.__init__(self) - - def init(self, message, callback): + def init(self, app, wallet, message, on_success, on_failure, is_change=0): + self.app = app + self.wallet = wallet self.message = message - self.callback = callback + self.on_success = on_success + self.on_failure = on_failure self.ids.kb.password = '' + 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 '') + + def check_password(self, password): + if self.is_change > 1: + return True + try: + self.wallet.check_password(password) + return True + except InvalidPassword as e: + return False + + def on_dismiss(self): + if not self.success: + if self.on_failure: + self.on_failure() + else: + # keep dialog open + return True + else: + if self.on_success: + args = (self.pw, self.new_password) if self.is_change else (self.pw,) + Clock.schedule_once(lambda dt: self.on_success(*args), 0.1) def update_password(self, c): kb = self.ids.kb @@ -97,5 +118,25 @@ class PasswordDialog(Factory.Popup): def on_password(self, pw): if len(pw) == 6: - self.dismiss() - Clock.schedule_once(lambda dt: self.callback(pw), 0.1) + if self.check_password(pw): + if self.is_change == 0: + self.success = True + self.pw = pw + self.message = _('Please wait...') + self.dismiss() + elif self.is_change == 1: + self.pw = pw + self.message = _('Enter new PIN') + self.ids.kb.password = '' + self.is_change = 2 + elif self.is_change == 2: + self.new_password = pw + self.message = _('Confirm new PIN') + self.ids.kb.password = '' + self.is_change = 3 + elif self.is_change == 3: + self.success = pw == self.new_password + self.dismiss() + else: + self.app.show_error(_('Wrong PIN')) + self.ids.kb.password = '' diff --git a/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py @@ -36,9 +36,8 @@ Builder.load_string(''' action: partial(root.language_dialog, self) CardSeparator SettingsItem: - status: '' if root.disable_pin else ('ON' if root.use_encryption else 'OFF') disabled: root.disable_pin - title: _('PIN code') + ': ' + self.status + title: _('PIN code') description: _("Change your PIN code.") action: partial(root.change_password, self) CardSeparator