X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=gui%2Fqt%2Finstallwizard.py;h=06f7190c26e73bfb6d1cdf68ce78ffd3f210a168;hb=295a71173c8979035aedcfcf9fb0be2224420d5a;hp=e38df56764d3baa32580510ccce51146f5bc1f47;hpb=8558488337a6043bce8a246f3e7d1d471d13068d;p=electrum-nvc.git diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index e38df56..06f7190 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -4,7 +4,8 @@ import PyQt4.QtCore as QtCore from electrum.i18n import _ from electrum import Wallet, Wallet_2of2, Wallet_2of3 -import electrum.bitcoin as bitcoin +from electrum import bitcoin +from electrum import util import seed_dialog from network_dialog import NetworkDialog @@ -19,7 +20,7 @@ from electrum.plugins import run_hook MSG_ENTER_ANYTHING = _("Please enter a wallet seed, a master public key, a list of Bitcoin addresses, or a list of private keys") MSG_SHOW_MPK = _("This is your master public key") MSG_ENTER_MPK = _("Please enter your master public key") -MSG_ENTER_COLD_MPK = _("Please enter the master public key of your cosigning wallet") +MSG_ENTER_COLD_MPK = _("Please enter the master public key of your cosigner wallet") MSG_ENTER_SEED_OR_MPK = _("Please enter a wallet seed, or master public key") MSG_VERIFY_SEED = _("Your seed is important!") + "\n" + _("To make sure that you have properly saved your seed, please retype it here.") @@ -81,50 +82,63 @@ class InstallWizard(QDialog): grid2 = QGridLayout() grid2.setSpacing(5) - label2 = QLabel(_("Wallet type:")) - grid2.addWidget(label2, 3, 0) + class ClickableLabel(QLabel): + def mouseReleaseEvent(self, ev): + self.emit(SIGNAL('clicked()')) + + label2 = ClickableLabel(_("Wallet type:") + " [+]") + hbox = QHBoxLayout() + hbox.addWidget(label2) + grid2.addLayout(hbox, 0, 0) gb2 = QGroupBox() grid.addWidget(gb2, 3, 0) - group2 = QButtonGroup() - bb1 = QRadioButton(gb2) - bb1.setText(_("Standard wallet")) - bb1.setChecked(True) - - bb2 = QRadioButton(gb2) - bb2.setText(_("Wallet with two-factor authentication (plugin)")) - - bb3 = QRadioButton(gb2) - bb3.setText(_("Multisig wallet (paired manually)")) - - grid2.addWidget(bb1, 4, 0) - grid2.addWidget(bb2, 5, 0) - grid2.addWidget(bb3, 6, 0) - - group2.addButton(bb1) - group2.addButton(bb2) - group2.addButton(bb3) + self.wallet_types = [ + ('standard', _("Standard wallet"), Wallet), + ('2of2', _("Multisig wallet (2 of 2)"), Wallet_2of2), + ('2of3', _("Multisig wallet (2 of 3)"), Wallet_2of3) + ] + run_hook('add_wallet_types', self.wallet_types) + + for i, (t,l,c) in enumerate(self.wallet_types): + button = QRadioButton(gb2) + button.setText(l) + grid2.addWidget(button, i+1, 0) + group2.addButton(button) + group2.setId(button, i) + if i==0: + button.setChecked(True) + #else: + # button.setHidden(True) + + + def toggle(): + buttons = group2.buttons() + x = buttons[1].isHidden() + label2.setText(_("Wallet type:") + (' [+]' if x else ' [-]')) + for b in buttons[1:]: + b.setHidden(not x) + + self.connect(label2, SIGNAL('clicked()'), toggle) + grid2.addWidget(label2) vbox.addLayout(grid2) vbox.addStretch(1) - vbox.addLayout(ok_cancel_buttons(self, _('Next'))) - + hbox, button = ok_cancel_buttons2(self, _('Next')) + vbox.addLayout(hbox) self.set_layout(vbox) + self.show() + self.raise_() + button.setDefault(True) + if not self.exec_(): return None, None action = 'create' if b1.isChecked() else 'restore' - - if bb1.isChecked(): - t = 'standard' - elif bb2.isChecked(): - t = 'multisig_plugin' - elif bb3.isChecked(): - t = 'multisig_manual' - - return action, t + wallet_type = self.wallet_types[group2.checkedId()][0] + return action, wallet_type def verify_seed(self, seed, sid): @@ -144,15 +158,17 @@ class InstallWizard(QDialog): text = ' '.join(text.split()) return text - def is_any(self, seed_e): text = self.get_seed_text(seed_e) - return Wallet.is_seed(text) or Wallet.is_mpk(text) or Wallet.is_address(text) or Wallet.is_private_key(text) + return Wallet.is_seed(text) or Wallet.is_old_mpk(text) or Wallet.is_xpub(text) or Wallet.is_xprv(text) or Wallet.is_address(text) or Wallet.is_private_key(text) def is_mpk(self, seed_e): text = self.get_seed_text(seed_e) - return Wallet.is_mpk(text) + return Wallet.is_xpub(text) or Wallet.is_old_mpk(text) + def is_xpub(self, seed_e): + text = self.get_seed_text(seed_e) + return Wallet.is_xpub(text) def enter_seed_dialog(self, msg, sid): vbox, seed_e = seed_dialog.enter_seed_box(msg, sid) @@ -167,43 +183,53 @@ class InstallWizard(QDialog): return self.get_seed_text(seed_e) - def cold_mpk_dialog(self, xpub_hot): + def multi_mpk_dialog(self, xpub_hot, n): vbox = QVBoxLayout() - vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_SHOW_MPK, 'hot') - seed_e1.setText(xpub_hot) - seed_e1.setReadOnly(True) - vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, 'cold') - vbox.addLayout(vbox1) - vbox.addLayout(vbox2) + vbox0, seed_e0 = seed_dialog.enter_seed_box(MSG_SHOW_MPK, 'hot') + vbox.addLayout(vbox0) + seed_e0.setText(xpub_hot) + seed_e0.setReadOnly(True) + entries = [] + for i in range(n): + vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_COLD_MPK, 'cold') + vbox.addLayout(vbox2) + entries.append(seed_e2) vbox.addStretch(1) hbox, button = ok_cancel_buttons2(self, _('Next')) vbox.addLayout(hbox) button.setEnabled(False) - f = lambda: button.setEnabled(self.is_mpk(seed_e2)) - seed_e2.textChanged.connect(f) + f = lambda: button.setEnabled( map(lambda e: self.is_xpub(e), entries) == [True]*len(entries)) + for e in entries: + e.textChanged.connect(f) self.set_layout(vbox) if not self.exec_(): - return - return self.get_seed_text(seed_e2) + return + return map(lambda e: self.get_seed_text(e), entries) - def double_seed_dialog(self): + def multi_seed_dialog(self, n): vbox = QVBoxLayout() vbox1, seed_e1 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'hot') - vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'cold') vbox.addLayout(vbox1) - vbox.addLayout(vbox2) + entries = [seed_e1] + for i in range(n): + vbox2, seed_e2 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, 'cold') + vbox.addLayout(vbox2) + entries.append(seed_e2) vbox.addStretch(1) hbox, button = ok_cancel_buttons2(self, _('Next')) vbox.addLayout(hbox) button.setEnabled(False) - f = lambda: button.setEnabled(self.is_any(seed_e1) and self.is_any(seed_e2)) - seed_e1.textChanged.connect(f) - seed_e2.textChanged.connect(f) + + f = lambda: button.setEnabled( map(lambda e: self.is_any(e), entries) == [True]*len(entries)) + for e in entries: + e.textChanged.connect(f) + self.set_layout(vbox) if not self.exec_(): return - return self.get_seed_text(seed_e1), self.get_seed_text(seed_e2) + return map(lambda e: self.get_seed_text(e), entries) + @@ -317,78 +343,126 @@ class InstallWizard(QDialog): return run_password_dialog(self, None, self)[2] + def create_cold_seed(self, wallet): + from electrum.bitcoin import mnemonic_to_seed, bip32_root + msg = _('You are about to generate the cold storage seed of your wallet.') + '\n' \ + + _('For safety, you should do this on an offline computer.') + icon = QPixmap( ':icons/cold_seed.png').scaledToWidth(56) + if not self.question(msg, icon): + return + + cold_seed = wallet.make_seed() + if not self.show_seed(cold_seed, 'cold'): + return + if not self.verify_seed(cold_seed, 'cold'): + return + + hex_seed = mnemonic_to_seed(cold_seed,'').encode('hex') + xpriv, xpub = bip32_root(hex_seed) + wallet.add_master_public_key('cold/', xpub) + + msg = _('Your master public key was saved in your wallet file.') + '\n'\ + + _('Your cold seed must be stored on paper; it is not in the wallet file.')+ '\n\n' \ + + _('This program is about to close itself.') + '\n'\ + + _('You will need to reopen your wallet on an online computer, in order to complete the creation of your wallet') + self.show_message(msg) + def run(self, action): if action == 'new': - action, t = self.restore_or_create() + action, wallet_type = self.restore_or_create() + self.storage.put('wallet_type', wallet_type, False) - if action is None: + if action is None: return - if action == 'create': - if t == 'multisig_plugin': - action = 'create_2of3_1' - if t == 'multisig_manual': - action = 'create_2of2_1' + if action == 'restore': + wallet = self.restore(wallet_type) + if not wallet: + return + action = None - if action in ['create']: + else: wallet = Wallet(self.storage) - elif action in ['create_2of2_1','create_2of2_2']: - wallet = Wallet_2of2(self.storage) + action = wallet.get_action() + # fixme: password is only needed for multiple accounts + password = None + while action is not None: - if action == 'create': - seed = wallet.make_seed() - if not self.show_seed(seed, None): - return - if not self.verify_seed(seed, None): - return - password = self.password_dialog() - wallet.add_seed(seed, password) - wallet.create_accounts(password) - self.waiting_dialog(wallet.synchronize) + util.print_error("installwizard:", wallet, action) + + if action == 'create_seed': + seed = wallet.make_seed() + if not self.show_seed(seed, None): + return + if not self.verify_seed(seed, None): + return + password = self.password_dialog() + wallet.add_seed(seed, password) + elif action == 'add_cosigner': + xpub_hot = wallet.master_public_keys.get("m/") + r = self.multi_mpk_dialog(xpub_hot, 1) + if not r: + return + xpub_cold = r[0] + wallet.add_master_public_key("cold/", xpub_cold) - if action == 'create_2of3_1': - run_hook('create_cold_seed', self.storage, self) - return + elif action == 'add_two_cosigners': + xpub_hot = wallet.master_public_keys.get("m/") + r = self.multi_mpk_dialog(xpub_hot, 2) + if not r: + return + xpub1, xpub2 = r + wallet.add_master_public_key("cold/", xpub1) + wallet.add_master_public_key("remote/", xpub2) + elif action == 'create_accounts': + wallet.create_accounts(password) + self.waiting_dialog(wallet.synchronize) - if action in ['create_2of2_1', 'create_2of3_2']: - seed = wallet.make_seed() - if not self.show_seed(seed, 'hot'): - return - if not self.verify_seed(seed, 'hot'): + elif action == 'create_cold_seed': + self.create_cold_seed(wallet) return - password = self.password_dialog() - wallet.add_seed(seed, password) - if action == 'create_2of2_1': - # display mpk - action = 'create_2of2_2' + else: - action = 'create_2of3_3' + r = run_hook('install_wizard_action', self, wallet, action) + if not r: + raise BaseException('unknown wizard action', action) - if action == 'create_2of2_2': - xpub_hot = wallet.master_public_keys.get("m/") - xpub = self.cold_mpk_dialog(xpub_hot) - if not Wallet.is_mpk(xpub): - return - wallet.add_master_public_key("cold/", xpub) - wallet.create_account() - self.waiting_dialog(wallet.synchronize) + # next action + action = wallet.get_action() - if action == 'create_2of3_3': - run_hook('create_remote_key', wallet, self) - if not wallet.master_public_keys.get("remote/"): - return - wallet.create_account() - self.waiting_dialog(wallet.synchronize) + if self.network: + if self.network.interfaces: + self.network_dialog() + else: + QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK')) + self.network.stop() + self.network = None + # start wallet threads + wallet.start_threads(self.network) if action == 'restore': + self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText)) + if self.network: + if wallet.is_found(): + QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK')) + else: + QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK')) + else: + QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK')) + + return wallet + + + + def restore(self, t): if t == 'standard': text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None) @@ -399,8 +473,13 @@ class InstallWizard(QDialog): wallet = Wallet.from_seed(text, self.storage) wallet.add_seed(text, password) wallet.create_accounts(password) - elif Wallet.is_mpk(text): - wallet = Wallet.from_mpk(text, self.storage) + elif Wallet.is_xprv(text): + password = self.password_dialog() + wallet = Wallet.from_xprv(text, password, self.storage) + elif Wallet.is_old_mpk(text): + wallet = Wallet.from_old_mpk(text, self.storage) + elif Wallet.is_xpub(text): + wallet = Wallet.from_xpub(text, self.storage) elif Wallet.is_address(text): wallet = Wallet.from_address(text, self.storage) elif Wallet.is_private_key(text): @@ -408,16 +487,16 @@ class InstallWizard(QDialog): else: raise - elif t in ['multisig_plugin', 'multisig_manual']: - r = self.double_seed_dialog() + elif t in ['2of2']: + r = self.multi_seed_dialog(1) if not r: return text1, text2 = r - password = self.password_dialog() - if t == 'multisig_manual': - wallet = Wallet_2of2(self.storage) + wallet = Wallet_2of2(self.storage) + if Wallet.is_seed(text1) or Wallet.is_seed(text2): + password = self.password_dialog() else: - wallet = Wallet_2of3(self.storage) + password = None if Wallet.is_seed(text1): wallet.add_seed(text1, password) @@ -425,8 +504,8 @@ class InstallWizard(QDialog): wallet.add_cold_seed(text2, password) else: wallet.add_master_public_key("cold/", text2) - - elif Wallet.is_mpk(text1): + else: + assert Wallet.is_xpub(text1) if Wallet.is_seed(text2): wallet.add_seed(text2, password) wallet.add_master_public_key("cold/", text1) @@ -434,38 +513,41 @@ class InstallWizard(QDialog): wallet.add_master_public_key("m/", text1) wallet.add_master_public_key("cold/", text2) - if t == '2of3': - run_hook('restore_third_key', wallet, self) + wallet.create_accounts(password) - wallet.create_account() - - else: - raise + elif t in ['2of3']: + r = self.multi_seed_dialog(2) + if not r: + return + text1, text2, text3 = r + wallet = Wallet_2of3(self.storage) + if Wallet.is_seed(text1) or Wallet.is_seed(text2) or Wallet.is_seed(text3): + password = self.password_dialog() + else: + password = None - - #if not self.config.get('server'): - if self.network: - if self.network.interfaces: - self.network_dialog() - else: - QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK')) - self.network.stop() - self.network = None - - # start wallet threads - wallet.start_threads(self.network) + if Wallet.is_seed(text1): + wallet.add_seed(text1, password) + if Wallet.is_seed(text2): + wallet.add_cold_seed(text2, password) + else: + wallet.add_master_public_key("cold/", text2) - if action == 'restore': + elif Wallet.is_xpub(text1): + if Wallet.is_seed(text2): + wallet.add_seed(text2, password) + wallet.add_master_public_key("cold/", text1) + else: + wallet.add_master_public_key("m/", text1) + wallet.add_master_public_key("cold/", text2) - self.waiting_dialog(lambda: wallet.restore(self.waiting_label.setText)) + wallet.create_accounts(password) - if self.network: - if wallet.is_found(): - QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK')) - else: - QMessageBox.information(None, _('Information'), _("No transactions found for this seed"), _('OK')) else: - QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK')) + wallet = run_hook('installwizard_restore', self, self.storage) - return wallet + # create first keys offline + self.waiting_dialog(wallet.synchronize) + + return wallet