1 from PyQt4.QtGui import *
2 from PyQt4.QtCore import *
3 import PyQt4.QtCore as QtCore
5 from electrum.i18n import _
6 from electrum import Wallet, Wallet_2of2, Wallet_2of3
7 import electrum.bitcoin as bitcoin
10 from network_dialog import NetworkDialog
12 from amountedit import AmountEdit
16 from electrum.plugins import run_hook
19 MSG_ENTER_ANYTHING = _("Please enter a wallet seed, a master public key, a list of Bitcoin addresses, or a list of private keys")
20 MSG_SHOW_MPK = _("This is your master public key")
21 MSG_ENTER_MPK = _("Please enter your master public key")
22 MSG_ENTER_COLD_MPK = _("Please enter the master public key of your cosigning wallet")
23 MSG_ENTER_SEED_OR_MPK = _("Please enter a wallet seed, or master public key")
24 MSG_VERIFY_SEED = _("Your seed is important!") + "\n" + _("To make sure that you have properly saved your seed, please retype it here.")
27 class InstallWizard(QDialog):
29 def __init__(self, config, network, storage):
30 QDialog.__init__(self)
32 self.network = network
33 self.storage = storage
34 self.setMinimumSize(575, 400)
35 self.setWindowTitle('Electrum')
36 self.connect(self, QtCore.SIGNAL('accept'), self.accept)
38 self.stack = QStackedLayout()
39 self.setLayout(self.stack)
42 def set_layout(self, layout):
45 self.stack.setCurrentIndex(self.stack.addWidget(w))
48 def restore_or_create(self):
52 main_label = QLabel(_("Electrum could not find an existing wallet."))
53 vbox.addWidget(main_label)
58 label = QLabel(_("What do you want to do?"))
59 label.setWordWrap(True)
60 grid.addWidget(label, 0, 0)
63 grid.addWidget(gb1, 0, 0)
65 group1 = QButtonGroup()
67 b1 = QRadioButton(gb1)
68 b1.setText(_("Create new wallet"))
71 b2 = QRadioButton(gb1)
72 b2.setText(_("Restore an existing wallet"))
77 grid.addWidget(b1, 1, 0)
78 grid.addWidget(b2, 2, 0)
84 label2 = QLabel(_("Wallet type:"))
85 grid2.addWidget(label2, 3, 0)
88 grid.addWidget(gb2, 3, 0)
90 group2 = QButtonGroup()
92 bb1 = QRadioButton(gb2)
93 bb1.setText(_("Standard wallet"))
96 bb2 = QRadioButton(gb2)
97 bb2.setText(_("Wallet with two-factor authentication (plugin)"))
99 bb3 = QRadioButton(gb2)
100 bb3.setText(_("Multisig wallet (2 of 2)"))
102 bb4 = QRadioButton(gb2)
103 bb4.setText(_("Multisig wallet (2 of 3)"))
105 grid2.addWidget(bb1, 4, 0)
106 grid2.addWidget(bb2, 5, 0)
107 grid2.addWidget(bb3, 6, 0)
108 grid2.addWidget(bb4, 7, 0)
110 group2.addButton(bb1)
111 group2.addButton(bb2)
112 group2.addButton(bb3)
113 group2.addButton(bb4)
115 vbox.addLayout(grid2)
117 vbox.addLayout(ok_cancel_buttons(self, _('Next')))
119 self.set_layout(vbox)
123 action = 'create' if b1.isChecked() else 'restore'
127 elif bb2.isChecked():
129 elif bb3.isChecked():
131 elif bb4.isChecked():
137 def verify_seed(self, seed, sid):
138 r = self.enter_seed_dialog(MSG_VERIFY_SEED, sid)
143 QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
149 def get_seed_text(self, seed_e):
150 text = unicode(seed_e.toPlainText()).strip()
151 text = ' '.join(text.split())
155 def is_any(self, seed_e):
156 text = self.get_seed_text(seed_e)
157 return Wallet.is_seed(text) or Wallet.is_mpk(text) or Wallet.is_address(text) or Wallet.is_private_key(text)
159 def is_mpk(self, seed_e):
160 text = self.get_seed_text(seed_e)
161 return Wallet.is_mpk(text)
164 def enter_seed_dialog(self, msg, sid):
165 vbox, seed_e = seed_dialog.enter_seed_box(msg, sid)
167 hbox, button = ok_cancel_buttons2(self, _('Next'))
169 button.setEnabled(False)
170 seed_e.textChanged.connect(lambda: button.setEnabled(self.is_any(seed_e)))
171 self.set_layout(vbox)
174 return self.get_seed_text(seed_e)
177 def cold_mpk_dialog(self, xpub_hot):
179 vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_SHOW_MPK, 'hot')
180 seed_e1.setText(xpub_hot)
181 seed_e1.setReadOnly(True)
182 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, 'cold')
183 vbox.addLayout(vbox1)
184 vbox.addLayout(vbox2)
186 hbox, button = ok_cancel_buttons2(self, _('Next'))
188 button.setEnabled(False)
189 f = lambda: button.setEnabled(self.is_mpk(seed_e2))
190 seed_e2.textChanged.connect(f)
191 self.set_layout(vbox)
194 return self.get_seed_text(seed_e2)
197 def cold_mpk2_dialog(self, xpub_hot):
199 vbox0, seed_e0 = seed_dialog.enter_seed_box(MSG_SHOW_MPK, 'hot')
200 seed_e0.setText(xpub_hot)
201 seed_e0.setReadOnly(True)
202 vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, 'cold')
203 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, 'cold')
204 vbox.addLayout(vbox0)
205 vbox.addLayout(vbox1)
206 vbox.addLayout(vbox2)
208 hbox, button = ok_cancel_buttons2(self, _('Next'))
210 button.setEnabled(False)
211 f = lambda: button.setEnabled(self.is_mpk(seed_e1) and self.is_mpk(seed_e2))
212 seed_e1.textChanged.connect(f)
213 seed_e2.textChanged.connect(f)
214 self.set_layout(vbox)
217 return self.get_seed_text(seed_e1), self.get_seed_text(seed_e2)
220 def double_seed_dialog(self):
222 vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'hot')
223 vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'cold')
224 vbox.addLayout(vbox1)
225 vbox.addLayout(vbox2)
227 hbox, button = ok_cancel_buttons2(self, _('Next'))
229 button.setEnabled(False)
230 f = lambda: button.setEnabled(self.is_any(seed_e1) and self.is_any(seed_e2))
231 seed_e1.textChanged.connect(f)
232 seed_e2.textChanged.connect(f)
233 self.set_layout(vbox)
236 return self.get_seed_text(seed_e1), self.get_seed_text(seed_e2)
241 def waiting_dialog(self, task, msg= _("Electrum is generating your addresses, please wait.")):
244 self.emit(QtCore.SIGNAL('accept'))
247 self.waiting_label = QLabel(msg)
248 vbox.addWidget(self.waiting_label)
249 self.set_layout(vbox)
250 t = threading.Thread(target = target)
257 def network_dialog(self):
262 label = QLabel(_("Electrum communicates with remote servers to get information about your transactions and addresses. The servers all fulfil the same purpose only differing in hardware. In most cases you simply want to let Electrum pick one at random if you have a preference though feel free to select a server manually.") + "\n\n" \
263 + _("How do you want to connect to a server:")+" ")
264 label.setWordWrap(True)
265 grid.addWidget(label, 0, 0)
269 b1 = QRadioButton(gb)
270 b1.setText(_("Auto connect"))
273 b2 = QRadioButton(gb)
274 b2.setText(_("Select server manually"))
276 #b3 = QRadioButton(gb)
277 #b3.setText(_("Stay offline"))
279 grid.addWidget(b1,1,0)
280 grid.addWidget(b2,2,0)
281 #grid.addWidget(b3,3,0)
287 vbox.addLayout(ok_cancel_buttons(self, _('Next')))
289 self.set_layout(vbox)
294 return NetworkDialog(self.network, self.config, None).do_exec()
297 self.config.set_key('auto_cycle', True, True)
301 self.config.set_key("server", None, True)
302 self.config.set_key('auto_cycle', False, True)
306 def show_message(self, msg, icon=None):
308 self.set_layout(vbox)
313 vbox.addWidget(QLabel(msg))
315 vbox.addLayout(close_button(self, _('Next')))
320 def question(self, msg, icon=None):
322 self.set_layout(vbox)
327 vbox.addWidget(QLabel(msg))
329 vbox.addLayout(ok_cancel_buttons(self, _('OK')))
335 def show_seed(self, seed, sid):
336 vbox = seed_dialog.show_seed_box(seed, sid)
337 vbox.addLayout(ok_cancel_buttons(self, _("Next")))
338 self.set_layout(vbox)
342 def password_dialog(self):
343 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
344 +_("Leave these fields empty if you want to disable encryption.")
345 from password_dialog import make_password_dialog, run_password_dialog
346 self.set_layout( make_password_dialog(self, None, msg) )
347 return run_password_dialog(self, None, self)[2]
350 def create_cold_seed(self, wallet):
351 from electrum.bitcoin import mnemonic_to_seed, bip32_root
352 msg = _('You are about to generate the cold storage seed of your wallet.') + '\n' \
353 + _('For safety, you should do this on an offline computer.')
354 icon = QPixmap( ':icons/cold_seed.png').scaledToWidth(56)
355 if not self.question(msg, icon):
358 cold_seed = wallet.make_seed()
359 if not self.show_seed(cold_seed, 'cold'):
361 if not self.verify_seed(cold_seed, 'cold'):
364 hex_seed = mnemonic_to_seed(cold_seed,'').encode('hex')
365 xpriv, xpub = bip32_root(hex_seed)
366 wallet.add_master_public_key('cold/', xpub)
368 msg = _('Your master public key was saved in your wallet file.') + '\n'\
369 + _('Your cold seed must be stored on paper; it is not in the wallet file.')+ '\n\n' \
370 + _('This program is about to close itself.') + '\n'\
371 + _('You will need to reopen your wallet on an online computer, in order to complete the creation of your wallet')
372 self.show_message(msg)
376 def run(self, action):
379 action, t = self.restore_or_create()
384 if action == 'create':
386 wallet = Wallet(self.storage)
389 wallet = Wallet_2of3(self.storage)
390 run_hook('create_cold_seed', wallet, self)
391 self.create_cold_seed(wallet)
395 wallet = Wallet_2of2(self.storage)
396 action = 'create_2of2_1'
399 wallet = Wallet_2of3(self.storage)
400 action = 'create_2of3_1'
403 if action == 'create_2fa_2':
404 wallet = Wallet_2of3(self.storage)
406 if action in ['create', 'create_2of2_1', 'create_2fa_2', 'create_2of3_1']:
407 seed = wallet.make_seed()
408 sid = None if action == 'create' else 'hot'
409 if not self.show_seed(seed, sid):
411 if not self.verify_seed(seed, sid):
413 password = self.password_dialog()
414 wallet.add_seed(seed, password)
415 if action == 'create':
416 wallet.create_accounts(password)
417 self.waiting_dialog(wallet.synchronize)
418 elif action == 'create_2of2_1':
419 action = 'create_2of2_2'
420 elif action == 'create_2of3_1':
421 action = 'create_2of3_2'
422 elif action == 'create_2fa_2':
423 action = 'create_2fa_3'
425 if action == 'create_2of2_2':
426 xpub_hot = wallet.master_public_keys.get("m/")
427 xpub = self.cold_mpk_dialog(xpub_hot)
428 wallet.add_master_public_key("cold/", xpub)
429 wallet.create_account()
430 self.waiting_dialog(wallet.synchronize)
433 if action == 'create_2of3_2':
434 xpub_hot = wallet.master_public_keys.get("m/")
435 xpub1, xpub2 = self.cold_mpk2_dialog(xpub_hot)
436 wallet.add_master_public_key("cold/", xpub1)
437 wallet.add_master_public_key("remote/", xpub2)
438 wallet.create_account()
439 self.waiting_dialog(wallet.synchronize)
442 if action == 'create_2fa_3':
443 run_hook('create_remote_key', wallet, self)
444 if not wallet.master_public_keys.get("remote/"):
446 wallet.create_account()
447 self.waiting_dialog(wallet.synchronize)
450 if action == 'restore':
453 text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None)
456 if Wallet.is_seed(text):
457 password = self.password_dialog()
458 wallet = Wallet.from_seed(text, self.storage)
459 wallet.add_seed(text, password)
460 wallet.create_accounts(password)
461 elif Wallet.is_mpk(text):
462 wallet = Wallet.from_mpk(text, self.storage)
463 elif Wallet.is_address(text):
464 wallet = Wallet.from_address(text, self.storage)
465 elif Wallet.is_private_key(text):
466 wallet = Wallet.from_private_key(text, self.storage)
470 elif t in ['2fa', '2of2','2of3']:
471 r = self.double_seed_dialog()
475 password = self.password_dialog()
477 wallet = Wallet_2of2(self.storage)
479 wallet = Wallet_2of3(self.storage)
481 wallet = Wallet_2of3(self.storage)
483 if Wallet.is_seed(text1):
484 wallet.add_seed(text1, password)
485 if Wallet.is_seed(text2):
486 wallet.add_cold_seed(text2, password)
488 wallet.add_master_public_key("cold/", text2)
490 elif Wallet.is_mpk(text1):
491 if Wallet.is_seed(text2):
492 wallet.add_seed(text2, password)
493 wallet.add_master_public_key("cold/", text1)
495 wallet.add_master_public_key("m/", text1)
496 wallet.add_master_public_key("cold/", text2)
499 run_hook('restore_third_key', wallet, self)
501 wallet.create_account()
508 #if not self.config.get('server'):
510 if self.network.interfaces:
511 self.network_dialog()
513 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
517 # start wallet threads
518 wallet.start_threads(self.network)
520 if action == 'restore':
522 self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
525 if wallet.is_found():
526 QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
528 QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
530 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))