Some work on multisig.

This commit is contained in:
Neil Booth 2016-01-13 19:20:58 +09:00
parent 576500aa29
commit 0219687d41
4 changed files with 89 additions and 96 deletions

View File

@ -7,7 +7,7 @@ import PyQt4.QtCore as QtCore
import electrum import electrum
from electrum.i18n import _ from electrum.i18n import _
import seed_dialog from seed_dialog import SeedDisplayLayout, SeedWarningLayout, SeedInputLayout
from network_dialog import NetworkDialog from network_dialog import NetworkDialog
from util import * from util import *
from password_dialog import PasswordLayout, PW_NEW, PW_PASSPHRASE from password_dialog import PasswordLayout, PW_NEW, PW_PASSPHRASE
@ -32,6 +32,8 @@ class CosignWidget(QWidget):
QWidget.__init__(self) QWidget.__init__(self)
self.R = QRect(0, 0, self.size, self.size) self.R = QRect(0, 0, self.size, self.size)
self.setGeometry(self.R) self.setGeometry(self.R)
self.setMinimumHeight(self.size)
self.setMaximumHeight(self.size)
self.m = m self.m = m
self.n = n self.n = n
@ -74,8 +76,7 @@ class InstallWizard(WindowModalDialog, WizardBase):
self.setMinimumSize(518, 360) self.setMinimumSize(518, 360)
self.setMaximumSize(518, 360) self.setMaximumSize(518, 360)
self.connect(self, QtCore.SIGNAL('accept'), self.accept) self.connect(self, QtCore.SIGNAL('accept'), self.accept)
self.title = QLabel() self.title = WWLabel()
self.title.setWordWrap(True)
self.main_widget = QWidget() self.main_widget = QWidget()
self.cancel_button = QPushButton(_("Cancel"), self) self.cancel_button = QPushButton(_("Cancel"), self)
self.next_button = QPushButton(_("Next"), self) self.next_button = QPushButton(_("Next"), self)
@ -100,13 +101,12 @@ class InstallWizard(WindowModalDialog, WizardBase):
icon_vbox.addStretch(1) icon_vbox.addStretch(1)
hbox = QHBoxLayout() hbox = QHBoxLayout()
hbox.addLayout(icon_vbox) hbox.addLayout(icon_vbox)
hbox.addSpacing(5)
hbox.addLayout(inner_vbox) hbox.addLayout(inner_vbox)
hbox.setStretchFactor(inner_vbox, 1) hbox.setStretchFactor(inner_vbox, 1)
outer_vbox.addLayout(hbox) outer_vbox.addLayout(hbox)
outer_vbox.addLayout(Buttons(self.cancel_button, self.next_button)) outer_vbox.addLayout(Buttons(self.cancel_button, self.next_button))
self.set_icon(':icons/electrum.png') self.set_icon(':icons/electrum.png')
self.show()
self.raise_()
def set_icon(self, filename): def set_icon(self, filename):
prior_filename, self.icon_filename = self.icon_filename, filename prior_filename, self.icon_filename = self.icon_filename, filename
@ -120,6 +120,9 @@ class InstallWizard(WindowModalDialog, WizardBase):
prior_layout = self.main_widget.layout() prior_layout = self.main_widget.layout()
if prior_layout: if prior_layout:
QWidget().setLayout(prior_layout) QWidget().setLayout(prior_layout)
else:
self.show()
self.raise_()
self.main_widget.setLayout(layout) self.main_widget.setLayout(layout)
self.cancel_button.setEnabled(True) self.cancel_button.setEnabled(True)
self.next_button.setEnabled(True) self.next_button.setEnabled(True)
@ -155,7 +158,7 @@ class InstallWizard(WindowModalDialog, WizardBase):
def request_seed(self, title, is_valid=None): def request_seed(self, title, is_valid=None):
is_valid = is_valid or Wallet.is_any is_valid = is_valid or Wallet.is_any
slayout = seed_dialog.SeedLayout(None) slayout = SeedInputLayout()
self.next_button.setEnabled(False) self.next_button.setEnabled(False)
def sanitized_seed(): def sanitized_seed():
return clean_text(slayout.seed_edit()) return clean_text(slayout.seed_edit())
@ -167,7 +170,7 @@ class InstallWizard(WindowModalDialog, WizardBase):
def show_seed(self, seed): def show_seed(self, seed):
title = _("Your wallet generation seed is:") title = _("Your wallet generation seed is:")
slayout = seed_dialog.SeedLayout(seed) slayout = SeedWarningLayout(seed)
self.set_main_layout(slayout.layout(), title) self.set_main_layout(slayout.layout(), title)
def verify_seed(self, seed, is_valid=None): def verify_seed(self, seed, is_valid=None):
@ -185,11 +188,8 @@ class InstallWizard(WindowModalDialog, WizardBase):
self.verify_seed(seed, is_valid) self.verify_seed(seed, is_valid)
def pw_layout(self, msg, kind): def pw_layout(self, msg, kind):
hbox = QHBoxLayout()
playout = PasswordLayout(None, msg, kind, self.next_button) playout = PasswordLayout(None, msg, kind, self.next_button)
hbox.addLayout(playout.layout()) self.set_main_layout(playout.layout())
#hbox.addStretch(1)
self.set_main_layout(hbox)
return playout.new_password() return playout.new_password()
def request_passphrase(self, device_text, restore=True): def request_passphrase(self, device_text, restore=True):
@ -234,13 +234,6 @@ class InstallWizard(WindowModalDialog, WizardBase):
self.please_wait.setText(MSG_GENERATING_WAIT) self.please_wait.setText(MSG_GENERATING_WAIT)
self.refresh_gui() self.refresh_gui()
def set_layout(self, layout):
w = QWidget()
w.setLayout(layout)
self.stack.addWidget(w)
self.stack.setCurrentWidget(w)
self.show()
def query_create_or_restore(self, wallet_kinds): def query_create_or_restore(self, wallet_kinds):
"""Ask the user what they want to do, and which wallet kind. """Ask the user what they want to do, and which wallet kind.
wallet_kinds is an array of translated wallet descriptions. wallet_kinds is an array of translated wallet descriptions.
@ -264,37 +257,30 @@ class InstallWizard(WindowModalDialog, WizardBase):
def request_many(self, n, xpub_hot=None): def request_many(self, n, xpub_hot=None):
vbox = QVBoxLayout() vbox = QVBoxLayout()
scroll = QScrollArea() scroll = QScrollArea()
scroll.setEnabled(True)
scroll.setWidgetResizable(True) scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.NoFrame)
vbox.addWidget(scroll) vbox.addWidget(scroll)
w = QWidget() w = QWidget()
innerVbox = QVBoxLayout(w)
scroll.setWidget(w) scroll.setWidget(w)
innerVbox = QVBoxLayout()
w.setLayout(innerVbox)
entries = [] entries = []
if xpub_hot: if xpub_hot:
vbox0 = seed_dialog.show_seed_box(MSG_SHOW_MPK, xpub_hot, 'hot') layout = SeedDisplayLayout(xpub_hot, title=MSG_SHOW_MPK, sid='hot')
else: else:
vbox0, seed_e1 = seed_dialog.enter_seed_box(MSG_ENTER_SEED_OR_MPK, self, 'hot') layout = SeedInputLayout(title=MSG_ENTER_SEED_OR_MPK, sid='hot')
entries.append(seed_e1) entries.append(slayout.seed_edit())
innerVbox.addLayout(vbox0) innerVbox.addLayout(layout.layout())
for i in range(n): for i in range(n):
if xpub_hot: msg = MSG_COSIGNER % (i + 1) if xpub_hot else MSG_ENTER_SEED_OR_MPK
msg = MSG_COSIGNER % (i + 1) layout = SeedInputLayout(title=msg, sid='cold')
else: innerVbox.addLayout(layout.layout())
msg = MSG_ENTER_SEED_OR_MPK entries.append(layout.seed_edit())
vbox2, seed_e2 = seed_dialog.enter_seed_box(msg, self, 'cold')
innerVbox.addLayout(vbox2)
entries.append(seed_e2)
vbox.addStretch(1) self.next_button.setEnabled(False)
button = OkButton(self, _('Next'))
vbox.addLayout(Buttons(CancelButton(self), button))
button.setEnabled(False)
def get_texts(): def get_texts():
return [clean_text(entry) for entry in entries] return [clean_text(entry) for entry in entries]
def set_enabled(): def set_enabled():
@ -304,12 +290,10 @@ class InstallWizard(WindowModalDialog, WizardBase):
if xpub_hot: if xpub_hot:
texts.append(xpub_hot) texts.append(xpub_hot)
has_dups = len(set(texts)) < len(texts) has_dups = len(set(texts)) < len(texts)
button.setEnabled(all_valid and not has_dups) self.next_button.setEnabled(all_valid and not has_dups)
for e in entries: for e in entries:
e.textChanged.connect(set_enabled) e.textChanged.connect(set_enabled)
self.set_layout(vbox) self.set_main_layout(vbox)
if not self.exec_():
raise UserCancelled
return get_texts() return get_texts()
def network_dialog(self, network): def network_dialog(self, network):
@ -355,14 +339,7 @@ class InstallWizard(WindowModalDialog, WizardBase):
return clayout.selected_index() return clayout.selected_index()
def query_multisig(self, action): def query_multisig(self, action):
vbox = QVBoxLayout()
self.set_layout(vbox)
vbox.addWidget(QLabel(_("Multi Signature Wallet")))
cw = CosignWidget(2, 2) cw = CosignWidget(2, 2)
vbox.addWidget(cw, 1)
vbox.addWidget(QLabel(_("Please choose the number of signatures needed to unlock funds in your wallet") + ':'))
m_edit = QSpinBox() m_edit = QSpinBox()
n_edit = QSpinBox() n_edit = QSpinBox()
m_edit.setValue(2) m_edit.setValue(2)
@ -384,11 +361,12 @@ class InstallWizard(WindowModalDialog, WizardBase):
hbox.addWidget(QLabel(_('signatures'))) hbox.addWidget(QLabel(_('signatures')))
hbox.addStretch(1) hbox.addStretch(1)
vbox = QVBoxLayout()
vbox.addWidget(cw)
vbox.addWidget(WWLabel(_("Choose the number of signatures needed "
"to unlock funds in your wallet:")))
vbox.addLayout(hbox) vbox.addLayout(hbox)
vbox.addStretch(1) self.set_main_layout(vbox, _("Multi-Signature Wallet"))
vbox.addLayout(Buttons(CancelButton(self), OkButton(self, _('Next'))))
if not self.exec_():
raise UserCancelled
m = int(m_edit.value()) m = int(m_edit.value())
n = int(n_edit.value()) n = int(n_edit.value())
wallet_type = '%dof%d'%(m,n) wallet_type = '%dof%d'%(m,n)

View File

@ -18,23 +18,11 @@
from PyQt4.QtGui import * from PyQt4.QtGui import *
from PyQt4.QtCore import * from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
from electrum.i18n import _ from electrum.i18n import _
from util import * from util import *
from qrtextedit import ShowQRTextEdit, ScanQRTextEdit from qrtextedit import ShowQRTextEdit, ScanQRTextEdit
class SeedDialog(WindowModalDialog):
def __init__(self, parent, seed, imported_keys):
WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
self.setMinimumWidth(400)
vbox = show_seed_box_msg(seed)
if imported_keys:
vbox.addWidget(QLabel("<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"))
vbox.addLayout(Buttons(CloseButton(self)))
self.setLayout(vbox)
def icon_filename(sid): def icon_filename(sid):
if sid == 'cold': if sid == 'cold':
return ":icons/cold_seed.png" return ":icons/cold_seed.png"
@ -43,22 +31,61 @@ def icon_filename(sid):
else: else:
return ":icons/seed.png" return ":icons/seed.png"
class SeedLayout(object): class SeedDialog(WindowModalDialog):
def __init__(self, seed, sid=None): def __init__(self, parent, seed, imported_keys):
WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
self.setMinimumWidth(400)
vbox = QVBoxLayout(self)
vbox.addLayout(SeedDisplayLayout(seed))
if imported_keys:
warning = ("<b>" + _("WARNING") + ":</b> " +
_("Your wallet contains imported keys. These keys "
"cannot be recovered from your seed.") + "</b><p>")
vbox.addWidget(WWLabel(warning))
vbox.addLayout(Buttons(CloseButton(self)))
class SeedLayoutBase(object):
def _seed_layout(self, seed=None, title=None, sid=None):
logo = QLabel()
logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56))
logo.setMaximumWidth(60)
if seed: if seed:
self.vbox = self.seed_and_warning_layout(seed, sid) self.seed_e = ShowQRTextEdit()
self.seed_e.setText(seed)
else: else:
self.vbox = self.seed_layout(seed, sid) self.seed_e = ScanQRTextEdit()
self.seed_e.setTabChangesFocus(True)
self.seed_e.setMaximumHeight(75)
hbox = QHBoxLayout()
hbox.addWidget(logo)
hbox.addWidget(self.seed_e)
if not title:
return hbox
vbox = QVBoxLayout()
vbox.addWidget(WWLabel(title))
vbox.addLayout(hbox)
return vbox
def layout(self): def layout(self):
return self.vbox return self.layout_
def seed_edit(self): def seed_edit(self):
return self.seed_e return self.seed_e
def seed_and_warning_layout(self, seed, sid=None):
vbox = QVBoxLayout() class SeedInputLayout(SeedLayoutBase):
vbox.addLayout(self.seed_layout(seed, sid)) def __init__(self, title=None, sid=None):
self.layout_ = self._seed_layout(title=title, sid=sid)
class SeedDisplayLayout(SeedLayoutBase):
def __init__(self, seed, title=None, sid=None):
self.layout_ = self._seed_layout(seed=seed, title=title, sid=sid)
class SeedWarningLayout(SeedLayoutBase):
def __init__(self, seed, title=None):
msg = ''.join([ msg = ''.join([
"<p>", "<p>",
_("Please save these %d words on paper (order is important). "), _("Please save these %d words on paper (order is important). "),
@ -72,23 +99,7 @@ class SeedLayout(object):
"<li>" + _("Do not send your seed to a printer.") + "</li>", "<li>" + _("Do not send your seed to a printer.") + "</li>",
"</ul>" "</ul>"
]) % len(seed.split()) ]) % len(seed.split())
label2 = QLabel(msg) vbox = QVBoxLayout()
label2.setWordWrap(True) vbox.addLayout(self._seed_layout(seed=seed, title=title))
vbox.addWidget(label2) vbox.addWidget(WWLabel(msg))
return vbox self.layout_ = vbox
def seed_layout(self, seed, sid=None):
logo = QLabel()
logo.setPixmap(QPixmap(icon_filename(sid)).scaledToWidth(56))
logo.setMaximumWidth(60)
if not seed:
seed_e = ScanQRTextEdit()
seed_e.setTabChangesFocus(True)
else:
seed_e = ShowQRTextEdit(text=seed)
seed_e.setMaximumHeight(100)
self.seed_e = seed_e
hbox = QHBoxLayout()
hbox.addWidget(logo)
hbox.addWidget(seed_e)
return hbox

View File

@ -86,6 +86,12 @@ class ThreadedButton(QPushButton):
t.start() t.start()
class WWLabel(QLabel):
def __init__ (self, text="", parent=None):
QLabel.__init__(self, text, parent)
self.setWordWrap(True)
class HelpLabel(QLabel): class HelpLabel(QLabel):
def __init__(self, text, help_text): def __init__(self, text, help_text):
@ -272,9 +278,7 @@ class ChoicesLayout(object):
def __init__(self, msg, choices, on_clicked=None): def __init__(self, msg, choices, on_clicked=None):
vbox = QVBoxLayout() vbox = QVBoxLayout()
if len(msg) > 50: if len(msg) > 50:
label = QLabel(msg) vbox.addWidget(WWLabel(msg))
label.setWordWrap(True)
vbox.addWidget(label)
msg = "" msg = ""
gb2 = QGroupBox(msg) gb2 = QGroupBox(msg)
vbox.addWidget(gb2) vbox.addWidget(gb2)

View File

@ -108,7 +108,7 @@ class Plugin(TrustedCoinPlugin):
label.setWordWrap(True) label.setWordWrap(True)
vbox = QVBoxLayout() vbox = QVBoxLayout()
vbox.addWidget(label) vbox.addWidget(label)
window.set_main_layout(vbox, _("Two-Factor Authentication")) window.set_main_layout(vbox)
self.set_enabled(wallet, True) self.set_enabled(wallet, True)
window.set_icon(prior_icon) window.set_icon(prior_icon)