abstract and improve seed and key methods
[electrum-nvc.git] / gui / qt / installwizard.py
1 from PyQt4.QtGui import *
2 from PyQt4.QtCore import *
3 import PyQt4.QtCore as QtCore
4
5 from electrum.i18n import _
6 from electrum import Wallet, Wallet_2of3
7
8 import seed_dialog
9 from network_dialog import NetworkDialog
10 from util import *
11 from amountedit import AmountEdit
12
13 import sys
14 import threading
15 from electrum.plugins import run_hook
16
17 class InstallWizard(QDialog):
18
19     def __init__(self, config, network, storage):
20         QDialog.__init__(self)
21         self.config = config
22         self.network = network
23         self.storage = storage
24         self.setMinimumSize(575, 400)
25         self.setWindowTitle('Electrum')
26         self.connect(self, QtCore.SIGNAL('accept'), self.accept)
27
28         self.stack = QStackedLayout()
29         self.setLayout(self.stack)
30
31
32     def set_layout(self, layout):
33         w = QWidget()
34         w.setLayout(layout)
35         self.stack.setCurrentIndex(self.stack.addWidget(w))
36
37
38     def restore_or_create(self):
39
40         grid = QGridLayout()
41         grid.setSpacing(5)
42
43         msg = _("Electrum could not find an existing wallet.") + "\n\n" \
44             + _("What do you want to do?") + "\n"
45         label = QLabel(msg)
46         label.setWordWrap(True)
47         grid.addWidget(label, 0, 0)
48
49         gb = QGroupBox()
50
51         b1 = QRadioButton(gb)
52         b1.setText(_("Create new wallet"))
53         b1.setChecked(True)
54
55         b2 = QRadioButton(gb)
56         b2.setText(_("Restore an existing wallet"))
57
58         grid.addWidget(b1,1,0)
59         grid.addWidget(b2,2,0)
60
61         vbox = QVBoxLayout()
62         self.set_layout(vbox)
63
64         vbox.addLayout(grid)
65         vbox.addStretch(1)
66         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
67
68         if not self.exec_():
69             return
70         
71         return 'create' if b1.isChecked() else 'restore'
72
73
74
75     def verify_seed(self, seed, sid):
76         r = self.enter_seed_dialog(False, sid)
77         if not r:
78             return
79
80         if r != seed:
81             QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
82             return False
83         else:
84             return True
85
86
87     def get_seed_text(self, seed_e):
88         return unicode(seed_e.toPlainText())
89
90
91     def is_seed(self, seed_e):
92         text = self.get_seed_text(seed_e)
93         return Wallet.is_seed(text) or Wallet.is_mpk(text)
94
95
96     def enter_seed_dialog(self, is_restore, sid):
97         vbox, seed_e = seed_dialog.enter_seed_box(is_restore, sid)
98         vbox.addStretch(1)
99         hbox, button = ok_cancel_buttons2(self, _('Next'))
100         vbox.addLayout(hbox)
101         button.setEnabled(False)
102         seed_e.textChanged.connect(lambda: button.setEnabled(self.is_seed(seed_e)))
103         self.set_layout(vbox)
104         if not self.exec_():
105             return
106         return self.get_seed_text(seed_e)
107
108
109     def double_seed_dialog(self):
110         vbox = QVBoxLayout()
111         vbox1, seed_e1 = seed_dialog.enter_seed_box(True, 'hot')
112         vbox2, seed_e2 = seed_dialog.enter_seed_box(True, 'cold')
113         vbox.addLayout(vbox1)
114         vbox.addLayout(vbox2)
115         vbox.addStretch(1)
116         hbox, button = ok_cancel_buttons2(self, _('Next'))
117         vbox.addLayout(hbox)
118         button.setEnabled(False)
119         f = lambda: button.setEnabled(self.is_seed(seed_e1) and self.is_seed(seed_e2))
120         seed_e1.textChanged.connect(f)
121         seed_e2.textChanged.connect(f)
122         self.set_layout(vbox)
123         if not self.exec_():
124             return 
125         return self.get_seed_text(seed_e1), self.get_seed_text(seed_e2)
126
127
128
129
130     def waiting_dialog(self, task, msg= _("Electrum is generating your addresses, please wait.")):
131         def target():
132             task()
133             self.emit(QtCore.SIGNAL('accept'))
134
135         vbox = QVBoxLayout()
136         self.waiting_label = QLabel(msg)
137         vbox.addWidget(self.waiting_label)
138         self.set_layout(vbox)
139         t = threading.Thread(target = target)
140         t.start()
141         self.exec_()
142
143
144
145
146     def network_dialog(self):
147         
148         grid = QGridLayout()
149         grid.setSpacing(5)
150
151         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" \
152                       + _("How do you want to connect to a server:")+" ")
153         label.setWordWrap(True)
154         grid.addWidget(label, 0, 0)
155
156         gb = QGroupBox()
157
158         b1 = QRadioButton(gb)
159         b1.setText(_("Auto connect"))
160         b1.setChecked(True)
161
162         b2 = QRadioButton(gb)
163         b2.setText(_("Select server manually"))
164
165         #b3 = QRadioButton(gb)
166         #b3.setText(_("Stay offline"))
167
168         grid.addWidget(b1,1,0)
169         grid.addWidget(b2,2,0)
170         #grid.addWidget(b3,3,0)
171
172         vbox = QVBoxLayout()
173         vbox.addLayout(grid)
174
175         vbox.addStretch(1)
176         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
177
178         self.set_layout(vbox)
179         if not self.exec_():
180             return
181         
182         if b2.isChecked():
183             return NetworkDialog(self.network, self.config, None).do_exec()
184
185         elif b1.isChecked():
186             self.config.set_key('auto_cycle', True, True)
187             return
188
189         else:
190             self.config.set_key("server", None, True)
191             self.config.set_key('auto_cycle', False, True)
192             return
193         
194
195     def show_message(self, msg):
196         vbox = QVBoxLayout()
197         vbox.addWidget(QLabel(msg))
198         vbox.addStretch(1)
199         vbox.addLayout(close_button(self, _('Next')))
200         self.set_layout(vbox)
201         if not self.exec_(): 
202             return None
203
204     def question(self, msg):
205         vbox = QVBoxLayout()
206         vbox.addWidget(QLabel(msg))
207         vbox.addStretch(1)
208         vbox.addLayout(ok_cancel_buttons(self, _('OK')))
209         self.set_layout(vbox)
210         if not self.exec_(): 
211             return None
212         return True
213
214
215     def show_seed(self, seed, sid):
216         vbox = seed_dialog.show_seed_box(seed, sid)
217         vbox.addLayout(ok_cancel_buttons(self, _("Next")))
218         self.set_layout(vbox)
219         return self.exec_()
220
221
222     def password_dialog(self):
223         msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
224               +_("Leave these fields empty if you want to disable encryption.")
225         from password_dialog import make_password_dialog, run_password_dialog
226         self.set_layout( make_password_dialog(self, None, msg) )
227         return run_password_dialog(self, None, self)[2]
228
229
230     def choose_wallet_type(self):
231         grid = QGridLayout()
232         grid.setSpacing(5)
233
234         msg = _("Choose your wallet.")
235         label = QLabel(msg)
236         label.setWordWrap(True)
237         grid.addWidget(label, 0, 0)
238
239         gb = QGroupBox()
240
241         b1 = QRadioButton(gb)
242         b1.setText(_("Standard wallet (protected by password)"))
243         b1.setChecked(True)
244
245         b2 = QRadioButton(gb)
246         b2.setText(_("Multi-signature wallet (two-factor authentication)"))
247
248         grid.addWidget(b1,1,0)
249         grid.addWidget(b2,2,0)
250
251         vbox = QVBoxLayout()
252
253         vbox.addLayout(grid)
254         vbox.addStretch(1)
255         vbox.addLayout(ok_cancel_buttons(self, _('Next')))
256
257         self.set_layout(vbox)
258         if not self.exec_():
259             return
260         
261         if b1.isChecked():
262             return 'standard'
263         elif b2.isChecked():
264             return '2of3'
265
266
267     def run(self, action = None):
268
269         if action is None:
270             action = self.restore_or_create()
271
272         if action is None: 
273             return
274
275         if action == 'create':            
276             t = self.choose_wallet_type()
277             if not t:
278                 return 
279
280             if t == '2of3':
281                 run_hook('create_cold_seed', self.storage, self)
282                 return
283
284
285         if action in ['create', 'create2of3']:
286
287             wallet = Wallet(self.storage)
288             seed = wallet.make_seed()
289             sid = 'hot' if action == 'create2of3' else None
290             if not self.show_seed(seed, sid):
291                 return
292             if not self.verify_seed(seed, sid):
293                 return
294             password = self.password_dialog()
295             wallet.save_seed(seed, password)
296
297             if action == 'create2of3':
298                 run_hook('create_third_key', wallet, self)
299                 if not wallet.master_public_keys.get("remote/"):
300                     return
301
302             wallet.create_accounts(password)
303             # generate first addresses offline
304             self.waiting_dialog(wallet.synchronize)
305
306         elif action == 'restore':
307             t = self.choose_wallet_type()
308             if not t: 
309                 return
310
311             if t == 'standard':
312                 text = self.enter_seed_dialog(True, None)
313                 if Wallet.is_seed(text):
314                     password = self.password_dialog()
315                     wallet = Wallet.from_seed(text, self.storage)
316                     wallet.save_seed(text, password)
317                     wallet.create_accounts(password)
318                 elif Wallet.is_mpk(text):
319                     wallet = Wallet.from_mpk(text, self.storage)
320                 else:
321                     raise
322
323             elif t in ['2of2', '2of3']:
324                 r = self.double_seed_dialog()
325                 if not r: 
326                     return
327                 text1, text2 = r
328                 password = self.password_dialog()
329                 wallet = Wallet_2of3(self.storage)
330
331                 if Wallet.is_seed(text1):
332                     wallet.add_root("m/", text1, password)
333                 elif Wallet.is_mpk(text1):
334                     wallet.add_master_public_key("m/", text1)
335                 
336                 if Wallet.is_seed(text2):
337                     wallet.add_root("cold/", text2, password)
338                 elif Wallet.is_mpk(text2):
339                     wallet.add_master_public_key("cold/", text2)
340
341                 run_hook('restore_third_key', wallet, self)
342
343                 wallet.create_accounts(None)
344
345             else:
346                 raise
347
348
349
350
351         else: raise
352                 
353         #if not self.config.get('server'):
354         if self.network:
355             if self.network.interfaces:
356                 self.network_dialog()
357             else:
358                 QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
359                 self.network.stop()
360                 self.network = None
361
362         # start wallet threads
363         wallet.start_threads(self.network)
364
365         if action == 'restore':
366
367             self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText))
368
369             if self.network:
370                 if wallet.is_found():
371                     QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
372                 else:
373                     QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK'))
374             else:
375                 QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))
376
377         return wallet