Browse Source

Finish wizard unification

283
ThomasV 9 years ago
parent
commit
e7d25faf02
  1. 1
      gui/kivy/__init__.py
  2. 41
      gui/kivy/main_window.py
  3. 97
      gui/kivy/uix/dialogs/installwizard.py
  4. 15
      gui/qt/__init__.py
  5. 276
      gui/qt/installwizard.py
  6. 236
      lib/base_wizard.py
  7. 36
      lib/daemon.py
  8. 328
      lib/wizard.py
  9. 18
      plugins/hw_wallet/plugin.py
  10. 11
      plugins/trustedcoin/qt.py
  11. 39
      plugins/trustedcoin/trustedcoin.py

1
gui/kivy/__init__.py

@ -45,6 +45,7 @@ class ElectrumGui:
def __init__(self, config, daemon, plugins):
Logger.debug('ElectrumGUI: initialising')
self.daemon = daemon
self.network = daemon.network
self.config = config
self.plugins = plugins

41
gui/kivy/main_window.py

@ -199,8 +199,8 @@ class ElectrumWindow(App):
self.plugins = kwargs.get('plugins', [])
self.gui_object = kwargs.get('gui_object', None)
self.daemon = self.gui_object.daemon
#self.config = self.gui_object.config
self.contacts = Contacts(self.electrum_config)
self.invoices = InvoiceStore(self.electrum_config)
@ -408,36 +408,32 @@ class ElectrumWindow(App):
else:
return ''
def load_wallet_by_name(self, wallet_path):
if not wallet_path:
return
config = self.electrum_config
try:
storage = WalletStorage(wallet_path)
except IOError:
self.show_error("Cannot read wallet file")
def on_wizard_complete(self, instance, wallet):
if wallet:
self.daemon.add_wallet(wallet)
self.load_wallet(wallet)
self.on_resume()
def load_wallet_by_name(self, path):
if not path:
return
if storage.file_exists:
wallet = Wallet(storage)
action = wallet.get_action()
wallet = self.daemon.load_wallet(path)
if wallet:
self.load_wallet(wallet)
self.on_resume()
else:
action = 'new'
if action is not None:
# start installation wizard
Logger.debug('Electrum: Wallet not found. Launching install wizard')
wizard = Factory.InstallWizard(config, self.network, storage)
wizard.bind(on_wizard_complete=lambda instance, wallet: self.load_wallet(wallet))
wizard = Factory.InstallWizard(self.electrum_config, self.network, path)
wizard.bind(on_wizard_complete=self.on_wizard_complete)
action = wizard.get_action()
wizard.run(action)
else:
self.load_wallet(wallet)
self.on_resume()
def on_stop(self):
self.stop_wallet()
def stop_wallet(self):
if self.wallet:
self.wallet.stop_threads()
self.daemon.stop_wallet(self.wallet.storage.path)
self.wallet = None
def on_key_down(self, instance, key, keycode, codepoint, modifiers):
@ -539,9 +535,10 @@ class ElectrumWindow(App):
@profiler
def load_wallet(self, wallet):
print "load wallet", wallet.storage.path
self.stop_wallet()
self.wallet = wallet
self.wallet.start_threads(self.network)
self.current_account = self.wallet.storage.get('current_account', None)
self.update_wallet()
# Once GUI has been initialized check if we want to announce something

97
gui/kivy/uix/dialogs/installwizard.py

@ -24,7 +24,9 @@ from password_dialog import PasswordDialog
from electrum.base_wizard import BaseWizard
is_test = True
test_seed = "time taxi field recycle tiny license olive virus report rare steel portion achieve"
test_xpub = "xpub661MyMwAqRbcEbvVtRRSjqxVnaWVUMewVzMiURAKyYratih4TtBpMypzzefmv8zUNebmNVzB3PojdC5sV2P9bDgMoo9B3SARw1MXUUfU1GL"
Builder.load_string('''
#:import Window kivy.core.window.Window
@ -152,7 +154,7 @@ Builder.load_string('''
<WizardChoiceDialog>
msg : ''
message : ''
Widget:
size_hint: 1, 1
Label:
@ -160,7 +162,7 @@ Builder.load_string('''
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: root.msg
text: root.message
Widget
size_hint: 1, 1
GridLayout:
@ -408,11 +410,12 @@ class WizardDialog(EventsDialog):
'''
crcontent = ObjectProperty(None)
def __init__(self, **kwargs):
def __init__(self, wizard, **kwargs):
super(WizardDialog, self).__init__(**kwargs)
#self.action = kwargs.get('action')
self.wizard = wizard
self.ids.back.disabled = not wizard.can_go_back()
self.app = App.get_running_app()
self.run_next = kwargs['run_next']
self.run_prev = kwargs['run_prev']
_trigger_size_dialog = Clock.create_trigger(self._size_dialog)
Window.bind(size=_trigger_size_dialog,
rotation=_trigger_size_dialog)
@ -443,7 +446,7 @@ class WizardDialog(EventsDialog):
app.stop()
def get_params(self, button):
return ()
return (None,)
def on_release(self, button):
self._on_release = True
@ -452,7 +455,7 @@ class WizardDialog(EventsDialog):
self.parent.dispatch('on_wizard_complete', None)
return
if button is self.ids.back:
self.run_prev()
self.wizard.go_back()
return
params = self.get_params(button)
self.run_next(*params)
@ -467,13 +470,13 @@ class WizardMultisigDialog(WizardDialog):
class WizardChoiceDialog(WizardDialog):
def __init__(self, **kwargs):
super(WizardChoiceDialog, self).__init__(**kwargs)
self.msg = kwargs.get('msg', '')
def __init__(self, wizard, **kwargs):
super(WizardChoiceDialog, self).__init__(wizard, **kwargs)
self.message = kwargs.get('message', '')
choices = kwargs.get('choices', [])
layout = self.ids.choices
layout.bind(minimum_height=layout.setter('height'))
for text, action in choices:
for action, text in choices:
l = WizardButton(text=text)
l.action = action
l.height = '48dp'
@ -508,17 +511,18 @@ class WordButton(Button):
class WizardButton(Button):
pass
class RestoreSeedDialog(WizardDialog):
message = StringProperty('')
def __init__(self, **kwargs):
super(RestoreSeedDialog, self).__init__(**kwargs)
self._test = kwargs['test']
def __init__(self, wizard, **kwargs):
super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
self._test = kwargs['is_valid']
from electrum.mnemonic import Mnemonic
from electrum.old_mnemonic import words as old_wordlist
self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
self.ids.text_input_seed.text = ''
self.ids.text_input_seed.text = test_seed if is_test else ''
def get_suggestions(self, prefix):
for w in self.words:
@ -616,9 +620,8 @@ class RestoreSeedDialog(WizardDialog):
class ShowXpubDialog(WizardDialog):
def __init__(self, **kwargs):
WizardDialog.__init__(self, **kwargs)
self.app = App.get_running_app()
def __init__(self, wizard, **kwargs):
WizardDialog.__init__(self, wizard, **kwargs)
self.xpub = kwargs['xpub']
self.ids.next.disabled = False
@ -636,15 +639,14 @@ class ShowXpubDialog(WizardDialog):
class AddXpubDialog(WizardDialog):
def __init__(self, **kwargs):
WizardDialog.__init__(self, **kwargs)
self.app = App.get_running_app()
self._test = kwargs['test']
def __init__(self, wizard, **kwargs):
WizardDialog.__init__(self, wizard, **kwargs)
self.is_valid = kwargs['is_valid']
self.title = kwargs['title']
self.message = kwargs['message']
def check_text(self, dt):
self.ids.next.disabled = not bool(self._test(self.get_text()))
self.ids.next.disabled = not bool(self.is_valid(self.get_text()))
def get_text(self):
ti = self.ids.text_input
@ -659,7 +661,7 @@ class AddXpubDialog(WizardDialog):
self.app.scan_qr(on_complete)
def do_paste(self):
self.ids.text_input.text = unicode(self.app._clipboard.paste())
self.ids.text_input.text = test_xpub if is_test else unicode(self.app._clipboard.paste())
def do_clear(self):
self.ids.text_input.text = ''
@ -681,7 +683,7 @@ class InstallWizard(BaseWizard, Widget):
"""overriden by main_window"""
pass
def waiting_dialog(self, task, msg, on_complete=None):
def waiting_dialog(self, task, msg):
'''Perform a blocking task in the background by running the passed
method in a thread.
'''
@ -693,8 +695,6 @@ class InstallWizard(BaseWizard, Widget):
Clock.schedule_once(lambda dt: app.show_error(str(err)))
# on completion hide message
Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
if on_complete:
on_complete()
app.show_info_bubble(
text=msg, icon='atlas://gui/kivy/theming/light/important',
@ -702,17 +702,42 @@ class InstallWizard(BaseWizard, Widget):
t = threading.Thread(target = target)
t.start()
def choice_dialog(self, **kwargs): WizardChoiceDialog(**kwargs).open()
def multisig_dialog(self, **kwargs): WizardMultisigDialog(**kwargs).open()
def show_seed_dialog(self, **kwargs): ShowSeedDialog(**kwargs).open()
def restore_seed_dialog(self, **kwargs): RestoreSeedDialog(**kwargs).open()
def add_xpub_dialog(self, **kwargs): AddXpubDialog(**kwargs).open()
def show_xpub_dialog(self, **kwargs): ShowXpubDialog(**kwargs).open()
def terminate(self, **kwargs):
self.wallet.start_threads(self.network)
self.dispatch('on_wizard_complete', self.wallet)
def choice_dialog(self, **kwargs): WizardChoiceDialog(self, **kwargs).open()
def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
def enter_seed_dialog(self, **kwargs): RestoreSeedDialog(self, **kwargs).open()
def add_xpub_dialog(self, **kwargs): AddXpubDialog(self, **kwargs).open()
def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()
def show_error(self, msg):
app.show_error(msg, duration=0.5)
def password_dialog(self, message, callback):
popup = PasswordDialog()
popup.init(message, callback)
popup.open()
def show_error(self, msg):
app.show_error(msg, duration=0.5)
def request_password(self, run_next):
def callback(pin):
if pin:
self.run('confirm_password', (pin, run_next))
else:
run_next(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)
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()

15
gui/qt/__init__.py

@ -149,9 +149,6 @@ class ElectrumGui:
run_hook('on_new_window', w)
return w
def get_wizard(self):
return InstallWizard(self.config, self.app, self.plugins)
def start_new_window(self, path, uri):
'''Raises the window for the wallet if it is open. Otherwise
opens the wallet and creates a new window for it.'''
@ -160,14 +157,18 @@ class ElectrumGui:
w.bring_to_top()
break
else:
wallet = self.daemon.load_wallet(path, self.get_wizard)
wallet = self.daemon.load_wallet(path)
if not wallet:
return
wizard = InstallWizard(self.config, self.app, self.plugins, self.daemon.network, path)
wallet = wizard.run_and_get_wallet()
if not wallet:
return
if wallet.get_action():
return
self.daemon.add_wallet(wallet)
w = self.create_window_for_wallet(wallet)
if uri:
w.pay_to_URI(uri)
return w
def close_window(self, window):

276
gui/qt/installwizard.py

@ -5,6 +5,10 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import electrum
from electrum.wallet import Wallet
from electrum.mnemonic import prepare_seed
from electrum.util import UserCancelled
from electrum.base_wizard import BaseWizard
from electrum.i18n import _
from seed_dialog import SeedDisplayLayout, SeedWarningLayout, SeedInputLayout
@ -12,14 +16,23 @@ from network_dialog import NetworkChoiceLayout
from util import *
from password_dialog import PasswordLayout, PW_NEW, PW_PASSPHRASE
from electrum.wallet import Wallet
from electrum.mnemonic import prepare_seed
from electrum.util import UserCancelled
from electrum.wizard import (WizardBase,
MSG_ENTER_PASSWORD, MSG_RESTORE_PASSPHRASE,
MSG_COSIGNER, MSG_ENTER_SEED_OR_MPK,
MSG_SHOW_MPK, MSG_VERIFY_SEED,
MSG_GENERATING_WAIT)
class GoBack(Exception):
pass
MSG_GENERATING_WAIT = _("Electrum is generating your addresses, please wait...")
MSG_ENTER_ANYTHING = _("Please enter a seed phrase, a master key, a list of "
"Bitcoin addresses, or a list of private keys")
MSG_ENTER_SEED_OR_MPK = _("Please enter a seed phrase or a master key (xpub or xprv):")
MSG_VERIFY_SEED = _("Your seed is important!\nTo make sure that you have properly saved your seed, please retype it here.")
MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:")
MSG_SHOW_MPK = _("Here is your master public key:")
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys. "
"Enter nothing if you want to disable encryption.")
MSG_RESTORE_PASSPHRASE = \
_("Please enter the passphrase you used when creating your %s wallet. "
"Note this is NOT a password. Enter nothing if you did not use "
"one or are unsure.")
def clean_text(seed_e):
text = unicode(seed_e.toPlainText()).strip()
@ -63,14 +76,42 @@ class CosignWidget(QWidget):
qp.end()
def wizard_dialog(func):
def func_wrapper(*args, **kwargs):
run_next = kwargs['run_next']
wizard = args[0]
wizard.back_button.setText(_('Back') if wizard.can_go_back() else _('Cancel'))
try:
out = func(*args, **kwargs)
except GoBack:
print "go back"
wizard.go_back()
return
except UserCancelled:
print "usercancelled"
return
#if out is None:
# out = ()
if type(out) is not tuple:
out = (out,)
apply(run_next, out)
return func_wrapper
# WindowModalDialog must come first as it overrides show_error
class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
def __init__(self, config, app, plugins, network, storage):
def __init__(self, config, app, plugins):
BaseWizard.__init__(self, config, network, storage)
QDialog.__init__(self, None)
self.setWindowTitle('Electrum - ' + _('Install Wizard'))
self.app = app
self.config = config
# Set for base base class
self.plugins = plugins
self.language_for_seed = config.get('language')
@ -79,7 +120,7 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.connect(self, QtCore.SIGNAL('accept'), self.accept)
self.title = WWLabel()
self.main_widget = QWidget()
self.cancel_button = QPushButton(_("Cancel"), self)
self.back_button = QPushButton(_("Back"), self)
self.next_button = QPushButton(_("Next"), self)
self.next_button.setDefault(True)
self.logo = QLabel()
@ -87,9 +128,9 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.please_wait.setAlignment(Qt.AlignCenter)
self.icon_filename = None
self.loop = QEventLoop()
self.rejected.connect(lambda: self.loop.exit(False))
self.cancel_button.clicked.connect(lambda: self.loop.exit(False))
self.next_button.clicked.connect(lambda: self.loop.exit(True))
self.rejected.connect(lambda: self.loop.exit(0))
self.back_button.clicked.connect(lambda: self.loop.exit(1))
self.next_button.clicked.connect(lambda: self.loop.exit(2))
outer_vbox = QVBoxLayout(self)
inner_vbox = QVBoxLayout()
inner_vbox = QVBoxLayout()
@ -107,12 +148,35 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
hbox.addLayout(inner_vbox)
hbox.setStretchFactor(inner_vbox, 1)
outer_vbox.addLayout(hbox)
outer_vbox.addLayout(Buttons(self.cancel_button, self.next_button))
outer_vbox.addLayout(Buttons(self.back_button, self.next_button))
self.set_icon(':icons/electrum.png')
self.show()
self.raise_()
self.refresh_gui() # Need for QT on MacOSX. Lame.
def run_and_get_wallet(self):
# Show network dialog if config does not exist
if self.network:
if self.config.get('auto_connect') is None:
self.choose_server(self.network)
action = self.get_action()
if action != 'new':
self.hide()
path = self.storage.path
msg = _("The file '%s' contains an incompletely created wallet.\n"
"Do you want to complete its creation now?") % path
if not self.question(msg):
if self.question(_("Do you want to delete '%s'?") % path):
import os
os.remove(path)
self.show_warning(_('The file was removed'))
return
return
self.show()
self.run(action)
return self.wallet
def finished(self):
'''Ensure the dialog is closed.'''
self.accept()
@ -137,15 +201,17 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
if prior_layout:
QWidget().setLayout(prior_layout)
self.main_widget.setLayout(layout)
self.cancel_button.setEnabled(True)
self.back_button.setEnabled(True)
self.next_button.setEnabled(next_enabled)
self.main_widget.setVisible(True)
self.please_wait.setVisible(False)
result = self.loop.exec_()
if not result and raise_on_cancel:
raise UserCancelled
if result == 1:
raise GoBack
self.title.setVisible(False)
self.cancel_button.setEnabled(False)
self.back_button.setEnabled(False)
self.next_button.setEnabled(False)
self.main_widget.setVisible(False)
self.please_wait.setVisible(True)
@ -157,58 +223,42 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.app.processEvents()
self.app.processEvents()
def run(self, *args):
'''Wrap the base wizard implementation with try/except blocks
to give a sensible error message to the user.'''
wallet = None
try:
wallet = WizardBase.run(self, *args)
except UserCancelled:
self.print_error("wallet creation cancelled by user")
self.accept() # For when called from menu
except BaseException as e:
self.on_error(sys.exc_info())
raise
return wallet
def remove_from_recently_open(self, filename):
self.config.remove_from_recently_open(filename)
def request_seed(self, title, is_valid=None):
is_valid = is_valid or Wallet.is_any
slayout = SeedInputLayout()
def text_input(self, title, message, is_valid):
slayout = SeedInputLayout(title=message)
def sanitized_seed():
return clean_text(slayout.seed_edit())
def set_enabled():
self.next_button.setEnabled(is_valid(sanitized_seed()))
slayout.seed_edit().textChanged.connect(set_enabled)
self.set_main_layout(slayout.layout(), title, next_enabled=False)
return sanitized_seed()
seed = sanitized_seed()
return seed
def show_seed(self, seed):
slayout = SeedWarningLayout(seed)
self.set_main_layout(slayout.layout())
def verify_seed(self, seed, is_valid=None):
while True:
r = self.request_seed(MSG_VERIFY_SEED, is_valid)
if prepare_seed(r) == prepare_seed(seed):
return
self.show_error(_('Incorrect seed'))
@wizard_dialog
def add_xpub_dialog(self, title, message, is_valid, run_next):
return self.text_input(title, message, is_valid)
def show_and_verify_seed(self, seed, is_valid=None):
"""Show the user their seed. Ask them to re-enter it. Return
True on success."""
self.show_seed(seed)
@wizard_dialog
def enter_seed_dialog(self, run_next, title, message, is_valid):
self.app.clipboard().clear()
self.verify_seed(seed, is_valid)
return self.text_input(title, message, is_valid)
@wizard_dialog
def show_seed_dialog(self, run_next, message, seed_text):
slayout = SeedWarningLayout(seed_text)
self.set_main_layout(slayout.layout())
return seed_text
def pw_layout(self, msg, kind):
playout = PasswordLayout(None, msg, kind, self.next_button)
self.set_main_layout(playout.layout())
return playout.new_password()
def request_passphrase(self, device_text):
@wizard_dialog
def request_passphrase(self, device_text, run_next):
"""When restoring a wallet, request the passphrase that was used for
the wallet on the given device and confirm it. Should return
a unicode string."""
@ -218,10 +268,11 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
raise UserCancelled
return phrase
def request_password(self, msg=None):
@wizard_dialog
def request_password(self, run_next):
"""Request the user enter a new password and confirm it. Return
the password or None for no password."""
return self.pw_layout(msg or MSG_ENTER_PASSWORD, PW_NEW)
return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW)
def show_restore(self, wallet, network):
# FIXME: these messages are shown after the install wizard is
@ -244,85 +295,43 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
"contain more addresses than displayed.")
self.show_message(msg)
def create_addresses(self, wallet):
def task():
wallet.synchronize()
self.emit(QtCore.SIGNAL('accept'))
t = threading.Thread(target = task)
t.start()
self.please_wait.setText(MSG_GENERATING_WAIT)
self.refresh_gui()
def query_create_or_restore(self, wallet_kinds):
"""Ask the user what they want to do, and which wallet kind.
wallet_kinds is an array of translated wallet descriptions.
Return a a tuple (action, kind_index). Action is 'create' or
'restore', and kind the index of the wallet kind chosen."""
def confirm(self, msg):
vbox = QVBoxLayout()
vbox.addWidget(WWLabel(msg))
self.set_main_layout(vbox)
actions = [_("Create a new wallet"),
_("Restore a wallet from seed words or from keys")]
title = _("Electrum could not find an existing wallet.")
actions_clayout = ChoicesLayout(_("What do you want to do?"), actions)
wallet_clayout = ChoicesLayout(_("Wallet kind:"), wallet_kinds)
@wizard_dialog
def action_dialog(self, action, run_next):
self.run(action)
vbox = QVBoxLayout()
vbox.addLayout(actions_clayout.layout())
vbox.addLayout(wallet_clayout.layout())
self.set_main_layout(vbox, title)
def terminate(self):
self.wallet.start_threads(self.network)
self.emit(QtCore.SIGNAL('accept'))
action = ['create', 'restore'][actions_clayout.selected_index()]
return action, wallet_clayout.selected_index()
def waiting_dialog(self, task, msg):
self.please_wait.setText(MSG_GENERATING_WAIT)
self.refresh_gui()
t = threading.Thread(target = task)
t.start()
def query_hw_wallet_choice(self, msg, choices):
@wizard_dialog
def choice_dialog(self, title, message, choices, run_next):
c_values = map(lambda x: x[0], choices)
c_titles = map(lambda x: x[1], choices)
clayout = ChoicesLayout(message, c_titles)
vbox = QVBoxLayout()
if choices:
wallet_clayout = ChoicesLayout(msg, choices)
vbox.addLayout(wallet_clayout.layout())
else:
vbox.addWidget(QLabel(msg, wordWrap=True))
self.set_main_layout(vbox, next_enabled=len(choices) != 0)
return wallet_clayout.selected_index() if choices else 0
vbox.addLayout(clayout.layout())
self.set_main_layout(vbox, title)
action = c_values[clayout.selected_index()]
return action
def request_many(self, n, xpub_hot=None):
@wizard_dialog
def show_xpub_dialog(self, xpub, run_next):
vbox = QVBoxLayout()
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.NoFrame)
vbox.addWidget(scroll)
w = QWidget()
innerVbox = QVBoxLayout(w)
scroll.setWidget(w)
entries = []
if xpub_hot:
layout = SeedDisplayLayout(xpub_hot, title=MSG_SHOW_MPK, sid='hot')
else:
layout = SeedInputLayout(title=MSG_ENTER_SEED_OR_MPK, sid='hot')
entries.append(layout.seed_edit())
innerVbox.addLayout(layout.layout())
for i in range(n):
msg = MSG_COSIGNER % (i + 1) if xpub_hot else MSG_ENTER_SEED_OR_MPK
layout = SeedInputLayout(title=msg, sid='cold')
innerVbox.addLayout(layout.layout())
entries.append(layout.seed_edit())
def get_texts():
return [clean_text(entry) for entry in entries]
def set_enabled():
texts = get_texts()
is_valid = Wallet.is_xpub if xpub_hot else Wallet.is_any
all_valid = all(is_valid(text) for text in texts)
if xpub_hot:
texts.append(xpub_hot)
has_dups = len(set(texts)) < len(texts)
self.next_button.setEnabled(all_valid and not has_dups)
for e in entries:
e.textChanged.connect(set_enabled)
self.set_main_layout(vbox, next_enabled=False)
return get_texts()
layout = SeedDisplayLayout(xpub, title=MSG_SHOW_MPK, sid='hot')
vbox.addLayout(layout.layout())
self.set_main_layout(vbox, MSG_SHOW_MPK)
return None
def choose_server(self, network):
title = _("Electrum communicates with remote servers to get "
@ -335,7 +344,6 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
choices_title = _("How do you want to connect to a server? ")
clayout = ChoicesLayout(choices_title, choices)
self.set_main_layout(clayout.layout(), title)
auto_connect = True
if clayout.selected_index() == 1:
nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
@ -345,12 +353,8 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.config.set_key('auto_connect', auto_connect, True)
network.auto_connect = auto_connect
def query_choice(self, msg, choices):
clayout = ChoicesLayout(msg, choices)
self.set_main_layout(clayout.layout(), next_enabled=bool(choices))
return clayout.selected_index()
def query_multisig(self, action):
@wizard_dialog
def multisig_dialog(self, run_next):
cw = CosignWidget(2, 2)
m_edit = QSlider(Qt.Horizontal, self)
n_edit = QSlider(Qt.Horizontal, self)
@ -360,7 +364,6 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
m_edit.setMaximum(2)
n_edit.setValue(2)
m_edit.setValue(2)
n_label = QLabel()
m_label = QLabel()
grid = QGridLayout()
@ -379,14 +382,11 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
m_edit.valueChanged.connect(on_m)
on_n(2)
on_m(2)
vbox = QVBoxLayout()
vbox.addWidget(cw)
vbox.addWidget(WWLabel(_("Choose the number of signatures needed "
"to unlock funds in your wallet:")))
vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:")))
vbox.addLayout(grid)
self.set_main_layout(vbox, _("Multi-Signature Wallet"))
m = int(m_edit.value())
n = int(n_edit.value())
wallet_type = '%dof%d'%(m,n)
return wallet_type
return (m, n)

236
lib/base_wizard.py

@ -24,144 +24,232 @@
# SOFTWARE.
import os
from electrum.wallet import Wallet, Multisig_Wallet
from electrum.wallet import Wallet, Multisig_Wallet, WalletStorage
from i18n import _
class BaseWizard(object):
def __init__(self, config, network, storage):
def __init__(self, config, network, path):
super(BaseWizard, self).__init__()
self.config = config
self.network = network
self.storage = storage
self.storage = WalletStorage(path)
self.wallet = None
self.stack = []
def run(self, action, *args):
'''Entry point of our Installation wizard'''
self.stack.append((action, args))
if not action:
return
if hasattr(self, action):
if hasattr(self.wallet, 'plugin'):
if hasattr(self.wallet.plugin, action):
f = getattr(self.wallet.plugin, action)
apply(f, (self.wallet, self) + args)
elif hasattr(self, action):
f = getattr(self, action)
apply(f, *args)
else:
raise BaseException("unknown action", action)
def get_action(self):
if self.storage.file_exists:
self.wallet = Wallet(self.storage)
action = self.wallet.get_action()
else:
action = 'new'
return action
def get_wallet(self):
if self.wallet and self.wallet.get_action() is None:
return self.wallet
def can_go_back(self):
return len(self.stack)>1
def go_back(self):
if not self.can_go_back():
return
self.stack.pop()
action, args = self.stack.pop()
self.run(action, *args)
def run_wallet(self):
self.stack = []
action = self.wallet.get_action()
if action:
self.action_dialog(action=action, run_next=lambda x: self.run_wallet())
def new(self):
name = os.path.basename(self.storage.path)
msg = "\n".join([
_("Welcome to the Electrum installation wizard."),
title = _("Welcome to the Electrum installation wizard.")
message = '\n'.join([
_("The wallet '%s' does not exist.") % name,
_("What kind of wallet do you want to create?")
])
choices = [
(_('Standard wallet'), 'create_standard'),
(_('Multi-signature wallet'), 'create_multisig'),
wallet_kinds = [
('standard', _("Standard wallet")),
('twofactor', _("Wallet with two-factor authentication")),
('multisig', _("Multi-signature wallet")),
('hardware', _("Hardware wallet")),
]
self.choice_dialog(msg=msg, choices=choices, run_prev=self.cancel, run_next=self.run)
registered_kinds = Wallet.categories()
choices = [pair for pair in wallet_kinds if pair[0] in registered_kinds]
self.choice_dialog(title = title, message=message, choices=choices, run_next=self.on_wallet_type)
def on_wallet_type(self, choice):
self.wallet_type = choice
if choice == 'standard':
action = 'choose_seed'
elif choice == 'multisig':
action = 'choose_multisig'
elif choice == 'hardware':
action = 'choose_hw'
elif choice == 'twofactor':
action = 'choose_seed'
self.run(action)
def choose_multisig(self):
def on_multisig(m, n):
self.multisig_type = "%dof%d"%(m, n)
self.run('choose_seed')
self.multisig_dialog(run_next=on_multisig)
def choose_seed(self):
msg = ' '.join([
_("Do you want to create a new seed, or to restore a wallet using an existing seed?")
])
choices = [
(_('Create a new seed'), 'create_seed'),
(_('I already have a seed'), 'restore_seed'),
(_('Watching-only wallet'), 'restore_xpub')
]
self.choice_dialog(msg=msg, choices=choices, run_prev=self.new, run_next=self.run)
def create_multisig(self):
def f(m, n):
self.wallet_type = "%dof%d"%(m, n)
self.run('choose_seed')
name = os.path.basename(self.storage.path)
self.multisig_dialog(run_prev=self.new, run_next=f)
title = _('Private Keys')
message = _("Do you want to create a new seed, or to restore a wallet using an existing seed?")
if self.wallet_type == 'standard':
choices = [
('create_seed', _('Create a new seed')),
('restore_seed', _('I already have a seed')),
('restore_xpub', _('Watching-only wallet')),
]
elif self.wallet_type == 'twofactor':
choices = [
('create_2fa', _('Create a new seed')),
('restore_2fa', _('I already have a seed')),
]
elif self.wallet_type == 'multisig':
choices = [
('create_seed', _('Create a new seed')),
('restore_seed', _('I already have a seed')),
('restore_xpub', _('Watching-only wallet')),
('choose_hw', _('Cosign with hardware wallet')),
]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
def create_2fa(self):
print 'create 2fa'
self.storage.put('wallet_type', '2fa')
self.wallet = Wallet(self.storage)
self.run_wallet()
def restore_seed(self):
msg = _('Please type your seed phrase using the virtual keyboard.')
self.restore_seed_dialog(run_prev=self.new, run_next=self.enter_pin, test=Wallet.is_seed, message=msg)
title = _('Enter Seed')
self.enter_seed_dialog(run_next=self.add_password, title=title, message=msg, is_valid=Wallet.is_seed)
def restore_xpub(self):
title = "MASTER PUBLIC KEY"
message = _('To create a watching-only wallet, paste your master public key, or scan it using the camera button.')
self.add_xpub_dialog(run_prev=self.new, run_next=lambda xpub: self.create_wallet(xpub, None), title=title, message=message, test=Wallet.is_mpk)
def create_standard(self):
self.wallet_type = 'standard'
self.run('choose_seed')
message = _('To create a watching-only wallet, paste your master public key, or scan it using the camera button.')
self.add_xpub_dialog(run_next=lambda xpub: self.create_wallet(xpub, None), title=title, message=message, is_valid=Wallet.is_mpk)
def restore_2fa(self):
self.storage.put('wallet_type', '2fa')
self.wallet = Wallet(self.storage)
self.wallet.plugin.on_restore_wallet(self.wallet, self)
def choose_hw(self):
hw_wallet_types, choices = self.plugins.hardware_wallets('create')
choices = zip(hw_wallet_types, choices)
title = _('Hardware wallet')
if choices:
msg = _('Select the type of hardware wallet: ')
else:
msg = ' '.join([
_('No hardware wallet support found on your system.'),
_('Please install the relevant libraries (eg python-trezor for Trezor).'),
])
self.choice_dialog(title=title, message=msg, choices=choices, run_next=self.on_hardware)
def on_hardware(self, hw_type):
self.hw_type = hw_type
if self.wallet_type == 'multisig':
self.create_hardware_multisig()
else:
title = _('Hardware wallet') + ' [%s]' % hw_type
message = _('Do you have a device, or do you want to restore a wallet using an existing seed?')
choices = [
('create_hardware_wallet', _('I have a device')),
('restore_hardware_wallet', _('Use hardware wallet seed')),
]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
def create_hardware_multisig(self):
self.storage.put('wallet_type', self.multisig_type)
self.wallet = Multisig_Wallet(self.storage)
# todo: get the xpub from the plugin
self.run('create_wallet', xpub, None)
def create_hardware_wallet(self):
self.storage.put('wallet_type', self.hw_type)
self.wallet = Wallet(self.storage)
self.wallet.plugin.on_create_wallet(self.wallet, self)
self.terminate()
def restore_hardware_wallet(self):
self.storage.put('wallet_type', self.wallet_type)
self.wallet = Wallet(self.storage)
self.wallet.plugin.on_restore_wallet(self.wallet, self)
self.terminate()
def create_wallet(self, text, password):
if self.wallet_type == 'standard':
self.wallet = Wallet.from_text(text, password, self.storage)
self.run('create_addresses')
else:
self.storage.put('wallet_type', self.wallet_type)
elif self.wallet_type == 'multisig':
self.storage.put('wallet_type', self.multisig_type)
self.wallet = Multisig_Wallet(self.storage)
self.wallet.add_seed(text, password)
self.wallet.create_master_keys(password)
action = self.wallet.get_action()
self.run(action)
self.run_wallet()
def add_cosigners(self):
xpub = self.wallet.master_public_keys.get('x1/')
self.show_xpub_dialog(run_prev=self.create_multisig, run_next=self.add_cosigner, xpub=xpub, test=Wallet.is_xpub)
self.show_xpub_dialog(run_next=lambda x: self.add_cosigner(), xpub=xpub)
def add_cosigner(self):
def on_xpub(xpub):
self.wallet.add_cosigner(xpub)
i = self.wallet.get_missing_cosigner()
action = 'add_cosigner' if i else 'create_main_account'
action = 'add_cosigner' if i else 'create_addresses'
self.run(action)
title = "ADD COSIGNER"
i = self.wallet.get_missing_cosigner()
title = _("Add Cosigner") + " %d"%(i-1)
message = _('Please paste your cosigners master public key, or scan it using the camera button.')
self.add_xpub_dialog(run_prev=self.add_cosigners, run_next=on_xpub, title=title, message=message, test=Wallet.is_xpub)
def create_main_account(self):
self.wallet.create_main_account()
self.run('create_addresses')
self.add_xpub_dialog(run_next=on_xpub, title=title, message=message, is_valid=Wallet.is_any)
def create_addresses(self):
def task():
self.wallet.create_main_account()
self.wallet.synchronize()
self.terminate()
msg= _("Electrum is generating your addresses, please wait.")
self.waiting_dialog(task, msg, on_complete=self.terminate)
self.waiting_dialog(task, msg)
def create_seed(self):
from electrum.wallet import BIP32_Wallet
seed = BIP32_Wallet.make_seed()
msg = _("If you forget your PIN or lose your device, your seed phrase will be the "
"only way to recover your funds.")
self.show_seed_dialog(run_prev=self.new, run_next=self.confirm_seed, message=msg, seed_text=seed)
self.show_seed_dialog(run_next=self.confirm_seed, message=msg, seed_text=seed)
def confirm_seed(self, seed):
assert Wallet.is_seed(seed)
title = _('Confirm Seed')
msg = _('Please retype your seed phrase, to confirm that you properly saved it')
self.restore_seed_dialog(run_prev=self.create_seed, run_next=self.enter_pin, test=lambda x: x==seed, message=msg)
def enter_pin(self, seed):
def callback(pin):
action = 'confirm_pin' if pin else 'create_wallet'
self.run(action, (seed, pin))
self.password_dialog('Choose a PIN code', callback)
def confirm_pin(self, seed, pin):
def callback(conf):
if conf == pin:
self.run('create_wallet', (seed, pin))
else:
self.show_error(_('PIN mismatch'))
self.run('enter_pin', (seed,))
self.password_dialog('Confirm your PIN code', callback)
def terminate(self):
self.wallet.start_threads(self.network)
self.dispatch('on_wizard_complete', self.wallet)
def cancel(self):
self.dispatch('on_wizard_complete', None)
return True
self.enter_seed_dialog(run_next=self.add_password, title=title, message=msg, is_valid=lambda x: x==seed)
def add_password(self, seed):
f = lambda x: self.create_wallet(seed, x)
self.request_password(run_next=f)

36
lib/daemon.py

@ -171,27 +171,29 @@ class Daemon(DaemonThread):
response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
return response
def load_wallet(self, path, get_wizard=None):
def load_wallet(self, path):
if path in self.wallets:
wallet = self.wallets[path]
else:
storage = WalletStorage(path)
if storage.file_exists:
wallet = Wallet(storage)
action = wallet.get_action()
else:
action = 'new'
if action:
if get_wizard is None:
return None
wizard = get_wizard()
wallet = wizard.run(self.network, storage)
else:
wallet.start_threads(self.network)
if wallet:
self.wallets[path] = wallet
return wallet
storage = WalletStorage(path)
if not storage.file_exists:
return
wallet = Wallet(storage)
action = wallet.get_action()
if action:
return
wallet.start_threads(self.network)
self.wallets[path] = wallet
return wallet
def add_wallet(self, wallet):
path = wallet.storage.path
self.wallets[path] = wallet
def stop_wallet(self, path):
wallet = self.wallets.pop(path)
wallet.stop_threads()
def run_cmdline(self, config_options):
config = SimpleConfig(config_options)
cmdname = config.get('cmd')

328
lib/wizard.py

@ -1,328 +0,0 @@
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2015 thomasv@gitorious, kyuupichan@gmail
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from electrum import WalletStorage
from electrum.plugins import run_hook
from util import PrintError
from wallet import Wallet
from i18n import _
MSG_GENERATING_WAIT = _("Electrum is generating your addresses, please wait...")
MSG_ENTER_ANYTHING = _("Please enter a seed phrase, a master key, a list of "
"Bitcoin addresses, or a list of private keys")
MSG_ENTER_SEED_OR_MPK = _("Please enter a seed phrase or a master key (xpub or xprv):")
MSG_VERIFY_SEED = _("Your seed is important!\nTo make sure that you have properly saved your seed, please retype it here.")
MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:")
MSG_SHOW_MPK = _("Here is your master public key:")
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys. "
"Enter nothing if you want to disable encryption.")
MSG_RESTORE_PASSPHRASE = \
_("Please enter the passphrase you used when creating your %s wallet. "
"Note this is NOT a password. Enter nothing if you did not use "
"one or are unsure.")
class WizardBase(PrintError):
'''Base class for gui-specific install wizards.'''
user_actions = ('create', 'restore')
wallet_kinds = [
('standard', _("Standard wallet")),
('twofactor', _("Wallet with two-factor authentication")),
('multisig', _("Multi-signature wallet")),
('hardware', _("Hardware wallet")),
]
# Derived classes must set:
# self.language_for_seed
# self.plugins
def show_error(self, msg):
raise NotImplementedError
def show_warning(self, msg):
raise NotImplementedError
def remove_from_recently_open(self, filename):
"""Remove filename from the recently used list."""
raise NotImplementedError
def query_create_or_restore(self, wallet_kinds):
"""Ask the user what they want to do, and which wallet kind.
wallet_kinds is an array of translated wallet descriptions.
Return a a tuple (action, kind_index). Action is 'create' or
'restore', and kind the index of the wallet kind chosen."""
raise NotImplementedError
def query_multisig(self, action):
"""Asks the user what kind of multisig wallet they want. Returns a
string like "2of3". Action is 'create' or 'restore'."""
raise NotImplementedError
def query_choice(self, msg, choices):
"""Asks the user which of several choices they would like.
Return the index of the choice."""
raise NotImplementedError
def query_hw_wallet_choice(self, msg, action, choices):
"""Asks the user which hardware wallet kind they are using. Action is
'create' or 'restore' from the initial screen. As this is
confusing for hardware wallets ask a new question with the
three possibilities Initialize ('create'), Use ('create') or
Restore a software-only wallet ('restore'). Return a pair
(action, choice)."""
raise NotImplementedError
def show_and_verify_seed(self, seed):
"""Show the user their seed. Ask them to re-enter it. Return
True on success."""
raise NotImplementedError
def request_passphrase(self, device_text):
"""When restoring a wallet, request the passphrase that was used for
the wallet on the given device and confirm it. Should return
a unicode string."""
raise NotImplementedError
def request_password(self, msg=None):
"""Request the user enter a new password and confirm it. Return
the password or None for no password."""
raise NotImplementedError
def request_seed(self, msg, is_valid=None):
"""Request the user enter a seed. Returns the seed the user entered.
is_valid is a function that returns True if a seed is valid, for
dynamic feedback. If not provided, Wallet.is_any is used."""
raise NotImplementedError
def request_many(self, n, xpub_hot=None):
"""If xpub_hot is provided, a new wallet is being created. Request N
master public keys for cosigners; xpub_hot is the master xpub
key for the wallet.
If xpub_hot is None, request N cosigning master xpub keys,
xprv keys, or seeds in order to perform wallet restore."""
raise NotImplementedError
def choose_server(self, network):
"""Choose a server if one is not set in the config anyway."""
raise NotImplementedError
def show_restore(self, wallet, network):
"""Show restore result"""
pass
def finished(self):
"""Called when the wizard is done."""
pass
def run(self, network, storage):
'''The main entry point of the wizard. Open a wallet from the given
filename. If the file doesn't exist launch the GUI-specific
install wizard proper, created by calling create_wizard().'''
need_sync = False
is_restore = False
if storage.file_exists:
wallet = Wallet(storage)
if wallet.imported_keys:
self.update_wallet_format(wallet)
action = wallet.get_action()
if action != 'new':
self.hide()
path = storage.path
msg = _("The file '%s' contains an incompletely created wallet.\n"
"Do you want to complete its creation now?") % path
if not self.question(msg):
if self.question(_("Do you want to delete '%s'?") % path):
import os
os.remove(path)
self.show_warning(_('The file was removed'))
return
return
self.show()
else:
cr, wallet = self.create_or_restore(storage)
if not wallet:
return
need_sync = True
is_restore = (cr == 'restore')
while True:
action = wallet.get_action()
if not action:
break
need_sync = True
self.run_wallet_action(wallet, action)
# Save the wallet after each action
wallet.storage.write()
if network:
# Show network dialog if config does not exist
if self.config.get('auto_connect') is None:
self.choose_server(network)
else:
self.show_warning(_('You are offline'))
if need_sync:
self.create_addresses(wallet)
# start wallet threads
if network:
wallet.start_threads(network)
if is_restore:
self.show_restore(wallet, network)
self.finished()
return wallet
def run_wallet_action(self, wallet, action):
self.print_error("action %s on %s" % (action, wallet.basename()))
# Run the action on the wallet plugin, if any, then the
# wallet and finally ourselves
calls = []
if hasattr(wallet, 'plugin'):
calls.append((wallet.plugin, (wallet, self)))
calls.extend([(wallet, ()), (self, (wallet, ))])
calls = [(getattr(actor, action), args) for (actor, args) in calls
if hasattr(actor, action)]
if not calls:
raise RuntimeError("No handler found for %s action" % action)
for method, args in calls:
method(*args)
def create_or_restore(self, storage):
'''After querying the user what they wish to do, create or restore
a wallet and return it.'''
self.remove_from_recently_open(storage.path)
# Filter out any unregistered wallet kinds
registered_kinds = Wallet.categories()
kinds, descriptions = zip(*[pair for pair in WizardBase.wallet_kinds
if pair[0] in registered_kinds])
action, kind_index = self.query_create_or_restore(descriptions)
assert action in WizardBase.user_actions
kind = kinds[kind_index]
if kind == 'multisig':
wallet_type = self.query_multisig(action)
elif kind == 'hardware':
hw_wallet_types, choices = self.plugins.hardware_wallets(action)
if choices:
msg = _('Select the type of hardware wallet: ')
else:
msg = ' '.join([
_('No hardware wallet support found on your system.'),
_('Please install the relevant libraries (eg python-trezor for Trezor).'),
])
choice = self.query_hw_wallet_choice(msg, choices)
wallet_type = hw_wallet_types[choice]
elif kind == 'twofactor':
wallet_type = '2fa'
else:
wallet_type = 'standard'
if action == 'create':
wallet = self.create_wallet(storage, wallet_type, kind)
else:
wallet = self.restore_wallet(storage, wallet_type, kind)
return action, wallet
def construct_wallet(self, storage, wallet_type):
storage.put('wallet_type', wallet_type)
return Wallet(storage)
def create_wallet(self, storage, wallet_type, kind):
wallet = self.construct_wallet(storage, wallet_type)
if kind == 'hardware':
wallet.plugin.on_create_wallet(wallet, self)
return wallet
def restore_wallet(self, storage, wallet_type, kind):
if wallet_type == 'standard':
return self.restore_standard_wallet(storage)
if kind == 'multisig':
return self.restore_multisig_wallet(storage, wallet_type)
# Plugin (two-factor or hardware)
wallet = self.construct_wallet(storage, wallet_type)
return wallet.plugin.on_restore_wallet(wallet, self)
def restore_standard_wallet(self, storage):
text = self.request_seed(MSG_ENTER_ANYTHING)
need_password = Wallet.should_encrypt(text)
password = self.request_password() if need_password else None
return Wallet.from_text(text, password, storage)
def restore_multisig_wallet(self, storage, wallet_type):
# FIXME: better handling of duplicate keys
m, n = Wallet.multisig_type(wallet_type)
key_list = self.request_many(n - 1)
need_password = any(Wallet.should_encrypt(text) for text in key_list)
password = self.request_password() if need_password else None
return Wallet.from_multisig(key_list, password, storage, wallet_type)
def create_seed(self, wallet):
'''The create_seed action creates a seed and generates
master keys.'''
seed = wallet.make_seed(self.language_for_seed)
self.show_and_verify_seed(seed)
password = self.request_password()
wallet.add_seed(seed, password)
wallet.create_master_keys(password)
def create_main_account(self, wallet):
# FIXME: BIP44 restore requires password
wallet.create_main_account()
def create_addresses(self, wallet):
wallet.synchronize()
def add_cosigners(self, wallet):
# FIXME: better handling of duplicate keys
m, n = Wallet.multisig_type(wallet.wallet_type)
xpub1 = wallet.master_public_keys.get("x1/")
xpubs = self.request_many(n - 1, xpub1)
for i, xpub in enumerate(xpubs):
wallet.add_master_public_key("x%d/" % (i + 2), xpub)
def update_wallet_format(self, wallet):
# Backwards compatibility: convert old-format imported keys
msg = _("Please enter your password in order to update "
"imported keys")
if wallet.use_encryption:
password = self.request_password(msg)
else:
password = None
try:
wallet.convert_imported_keys(password)
except Exception as e:
self.show_error(str(e))
# Call synchronize to regenerate addresses in case we're offline
if wallet.get_master_public_keys() and not wallet.addresses():
wallet.synchronize()

18
plugins/hw_wallet/plugin.py

@ -53,21 +53,27 @@ class HW_PluginBase(BasePlugin):
def on_restore_wallet(self, wallet, wizard):
assert isinstance(wallet, self.wallet_class)
msg = _("Enter the seed for your %s wallet:" % self.device)
seed = wizard.request_seed(msg, is_valid = self.is_valid_seed)
f = lambda x: wizard.run('on_restore_seed', x)
wizard.enter_seed_dialog(run_next=f, title=_('Restore hardware wallet'), message=msg, is_valid=self.is_valid_seed)
def on_restore_seed(self, wallet, wizard, seed):
f = lambda x: wizard.run('on_restore_passphrase', seed, x)
wizard.request_passphrase(self.device, run_next=f)
def on_restore_passphrase(self, wallet, wizard, seed, passphrase):
f = lambda x: wizard.run('on_restore_password', seed, passphrase, x)
wizard.request_password(run_next=f)
def on_restore_password(self, wallet, wizard, seed, passphrase, password):
# Restored wallets are not hardware wallets
wallet_class = self.wallet_class.restore_wallet_class
wallet.storage.put('wallet_type', wallet_class.wallet_type)
wallet = wallet_class(wallet.storage)
passphrase = wizard.request_passphrase(self.device)
password = wizard.request_password()
wallet.add_seed(seed, password)
wallet.add_xprv_from_seed(seed, 'x/', password, passphrase)
wallet.create_hd_account(password)
return wallet
wizard.create_addresses()
@staticmethod
def is_valid_seed(seed):

11
plugins/trustedcoin/qt.py

@ -110,14 +110,9 @@ class Plugin(TrustedCoinPlugin):
return WaitingDialog(window, 'Getting billing information...', task,
on_finished)
def confirm(self, window, msg):
vbox = QVBoxLayout()
vbox.addWidget(WWLabel(msg))
window.set_main_layout(vbox)
def show_disclaimer(self, wallet, window):
window.set_icon(':icons/trustedcoin.png')
self.confirm(window, '\n\n'.join(DISCLAIMER))
def show_disclaimer(self, wallet, wizard):
wizard.set_icon(':icons/trustedcoin.png')
wizard.confirm('\n\n'.join(DISCLAIMER))
self.set_enabled(wallet, True)
@hook

39
plugins/trustedcoin/trustedcoin.py

@ -346,21 +346,32 @@ class TrustedCoinPlugin(BasePlugin):
wallet.price_per_tx = dict(billing_info['price_per_tx'])
return True
def create_extended_seed(self, wallet, window):
def create_extended_seed(self, wallet, wizard):
self.wallet = wallet
self.wizard = wizard
seed = wallet.make_seed()
window.show_and_verify_seed(seed, is_valid=self.is_valid_seed)
f = lambda x: wizard.run('confirm_seed', x)
self.wizard.show_seed_dialog(run_next=f, message="z", seed_text=seed)
password = window.request_password()
def confirm_seed(self, wallet, wizard, seed):
title = _('Confirm Seed')
msg = _('Please retype your seed phrase, to confirm that you properly saved it')
f = lambda x: wizard.run('add_password', x)
self.wizard.enter_seed_dialog(run_next=f, title=title, message=msg, is_valid=lambda x: x==seed)
def add_password(self, wallet, wizard, seed):
f = lambda x: self.create_wallet(seed, x)
self.wizard.request_password(run_next=f)
def create_wallet(self, seed, password):
wallet = self.wallet
wallet.storage.put('seed_version', wallet.seed_version)
wallet.storage.put('use_encryption', password is not None)
words = seed.split()
n = len(words)/2
wallet.add_xprv_from_seed(' '.join(words[0:n]), 'x1/', password)
wallet.add_xpub_from_seed(' '.join(words[n:]), 'x2/')
wallet.storage.write()
msg = [
_("Your wallet file is: %s.")%os.path.abspath(wallet.storage.path),
_("You need to be online in order to complete the creation of "
@ -371,7 +382,8 @@ class TrustedCoinPlugin(BasePlugin):
_('If you are online, click on "%s" to continue.') % _('Next')
]
msg = '\n\n'.join(msg)
self.confirm(window, msg)
self.wizard.confirm(msg)
return wallet
@hook
def do_clear(self, window):
@ -379,19 +391,22 @@ class TrustedCoinPlugin(BasePlugin):
def on_restore_wallet(self, wallet, wizard):
assert isinstance(wallet, self.wallet_class)
title = _("Restore two-factor Wallet")
f = lambda x: wizard.run('on_restore_seed', x)
wizard.enter_seed_dialog(run_next=f, title=title, message=RESTORE_MSG, is_valid=self.is_valid_seed)
seed = wizard.request_seed(RESTORE_MSG, is_valid=self.is_valid_seed)
password = wizard.request_password()
def on_restore_seed(self, wallet, wizard, seed):
f = lambda x: wizard.run('on_restore_pw', seed, x)
wizard.request_password(run_next=f)
def on_restore_pw(self, wallet, wizard, seed, password):
wallet.add_seed(seed, password)
words = seed.split()
n = len(words)/2
wallet.add_xprv_from_seed(' '.join(words[0:n]), 'x1/', password)
wallet.add_xprv_from_seed(' '.join(words[n:]), 'x2/', password)
restore_third_key(wallet)
wallet.create_main_account()
return wallet
wizard.create_addresses()
def create_remote_key(self, wallet, window):
email = self.accept_terms_of_use(window)

Loading…
Cancel
Save