electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 5fd83722d8f5b3aa12c529559b628da7b684d88a
parent 0657bb1b36f894a789762904035ad58a8110b6e9
Author: ghost43 <somber.night@protonmail.com>
Date:   Sat, 15 Dec 2018 01:11:14 +0100

Merge pull request #4916 from tiagotrs/master

revealer: new interface
closes #4540 
closes #4676
Diffstat:
Melectrum/gui/qt/seed_dialog.py | 4----
Melectrum/plugins/revealer/__init__.py | 8++------
Melectrum/plugins/revealer/qt.py | 186++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
3 files changed, 105 insertions(+), 93 deletions(-)

diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py @@ -26,8 +26,6 @@ from electrum.i18n import _ from electrum.mnemonic import Mnemonic import electrum.old_mnemonic -from electrum.plugin import run_hook - from .util import * from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit @@ -207,6 +205,4 @@ class SeedDialog(WindowModalDialog): title = _("Your wallet generation seed is:") slayout = SeedLayout(title=title, seed=seed, msg=True, passphrase=passphrase) vbox.addLayout(slayout) - has_extension = True if passphrase else False - run_hook('set_seed', seed, has_extension, slayout.seed_e) vbox.addLayout(Buttons(CloseButton(self))) diff --git a/electrum/plugins/revealer/__init__.py b/electrum/plugins/revealer/__init__.py @@ -1,13 +1,9 @@ from electrum.i18n import _ -fullname = _('Revealer') +fullname = _('Revealer Backup Utility') description = ''.join(["<br/>", "<b>"+_("Do you have something to hide ?")+"</b>", '<br/>', '<br/>', - _("Revealer is a seed phrase back-up solution. It allows you to create a cold, analog, multi-factor backup of your wallet seeds, or of any arbitrary secret."), '<br/>', '<br/>', - _("Using a Revealer is better than writing your seed phrases on paper: a revealer is invulnerable to physical access and allows creation of trustless redundancy."), '<br/>', '<br/>', - _("This plug-in allows you to generate a pdf file of your secret phrase encrypted visually for your physical Revealer. You can print it trustlessly - it can only be decrypted optically with your Revealer."), '<br/>', '<br/>', - _("The plug-in also allows you to generate a digital Revealer file and print it yourself on a transparent overhead foil."), '<br/>', '<br/>', - _("Once activated you can access the plug-in through the icon at the seed dialog."), '<br/>', '<br/>', + _("This plug-in allows you to create a visually encrypted backup of your wallet seeds, or of custom alphanumeric secrets."), '<br/>', '<br/>', _("For more information, visit"), " <a href=\"https://revealer.cc\">https://revealer.cc</a>", '<br/>', '<br/>', ]) diff --git a/electrum/plugins/revealer/qt.py b/electrum/plugins/revealer/qt.py @@ -1,19 +1,12 @@ ''' Revealer -So you have something to hide? - -plug-in for the electrum wallet. - -Features: - - Deep Cold multi-factor backup solution - - Safety - One time pad security - - Redundancy - Trustless printing & distribution - - Encrypt your seedphrase or any secret you want for your revealer - - Based on crypto by legendary cryptographers Naor and Shamir +Do you have something to hide? +Secret backup plug-in for the electrum wallet. Tiago Romagnani Silveira, 2017 + ''' import os @@ -28,14 +21,17 @@ from PyQt5.QtPrintSupport import QPrinter from electrum.plugin import BasePlugin, hook from electrum.i18n import _ -from electrum.util import to_bytes, make_dir +from electrum.util import to_bytes, make_dir, InvalidPassword, UserCancelled from electrum.gui.qt.util import * from electrum.gui.qt.qrtextedit import ScanQRTextEdit +from electrum.gui.qt.main_window import StatusBarButton from .hmac_drbg import DRBG class Plugin(BasePlugin): + MAX_PLAINTEXT_LEN = 189 # chars + def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) self.base_dir = config.electrum_path()+'/revealer/' @@ -57,11 +53,13 @@ class Plugin(BasePlugin): self.rawnoise = False make_dir(self.base_dir) + self.extension = False + @hook - def set_seed(self, seed, has_extension, parent): - self.cseed = seed.upper() - self.has_extension = has_extension - parent.addButton(':icons/revealer.png', partial(self.setup_dialog, parent), "Revealer"+_(" secret backup utility")) + def create_status_bar(self, parent): + b = StatusBarButton(QIcon(':icons/revealer.png'), "Revealer "+_("secret backup utility"), + partial(self.setup_dialog, parent)) + parent.addPermanentWidget(b) def requires_settings(self): return True @@ -69,39 +67,67 @@ class Plugin(BasePlugin): def settings_widget(self, window): return EnterButton(_('Printer Calibration'), partial(self.calibration_dialog, window)) + def password_dialog(self, msg=None, parent=None): + from electrum.gui.qt.password_dialog import PasswordDialog + parent = parent or self + d = PasswordDialog(parent, msg) + return d.run() + + def get_seed(self): + password = None + if self.wallet.has_keystore_encryption(): + password = self.password_dialog(parent=self.d.parent()) + if not password: + raise UserCancelled() + + keystore = self.wallet.get_keystore() + if not keystore or not keystore.has_seed(): + return + self.extension = bool(keystore.get_passphrase(password)) + return keystore.get_seed(password) + def setup_dialog(self, window): - self.update_wallet_name(window.parent().parent().wallet) + self.wallet = window.parent().wallet + self.update_wallet_name(self.wallet) self.user_input = False self.noise_seed = False - self.d = WindowModalDialog(window, "Revealer") - self.d.setMinimumWidth(420) - vbox = QVBoxLayout(self.d) - vbox.addSpacing(21) + + self.d = WindowModalDialog(window, "Setup Dialog") + self.d.setMinimumWidth(500) + self.d.setMinimumHeight(210) + self.d.setMaximumHeight(320) + self.d.setContentsMargins(11,11,1,1) + + self.hbox = QHBoxLayout(self.d) + vbox = QVBoxLayout() logo = QLabel() - vbox.addWidget(logo) + self.hbox.addWidget(logo) logo.setPixmap(QPixmap(':icons/revealer.png')) - logo.setAlignment(Qt.AlignCenter) - vbox.addSpacing(42) - + logo.setAlignment(Qt.AlignLeft) + self.hbox.addSpacing(16) + vbox.addWidget(WWLabel("<b>"+_("Revealer Secret Backup Plugin")+"</b><br>" + +_("To encrypt your backup, first we need to load some noise.")+"<br/>")) + vbox.addSpacing(7) + bcreate = QPushButton(_("Create a new Revealer")) + bcreate.setMaximumWidth(181) + bcreate.setDefault(True) + vbox.addWidget(bcreate, Qt.AlignCenter) self.load_noise = ScanQRTextEdit() self.load_noise.setTabChangesFocus(True) self.load_noise.textChanged.connect(self.on_edit) self.load_noise.setMaximumHeight(33) - - vbox.addWidget(WWLabel("<b>"+_("Enter your physical revealer code:")+"<b>")) + self.hbox.addLayout(vbox) + vbox.addWidget(WWLabel(_("or type a existing revealer code below and click 'next':"))) vbox.addWidget(self.load_noise) - vbox.addSpacing(11) - + vbox.addSpacing(3) self.next_button = QPushButton(_("Next"), self.d) - self.next_button.setDefault(True) self.next_button.setEnabled(False) vbox.addLayout(Buttons(self.next_button)) self.next_button.clicked.connect(self.d.close) self.next_button.clicked.connect(partial(self.cypherseed_dialog, window)) - vbox.addSpacing(21) - - vbox.addWidget(WWLabel(_("or, alternatively: "))) - bcreate = QPushButton(_("Create a digital Revealer")) + vbox.addWidget( + QLabel("<b>" + _("Warning") + "</b>: " + _("Each revealer should be used only once.") + +"<br>"+_("more information at <a href=\"https://revealer.cc/faq\">https://revealer.cc/faq</a>"))) def mk_digital(): try: @@ -112,15 +138,6 @@ class Plugin(BasePlugin): self.cypherseed_dialog(window) bcreate.clicked.connect(mk_digital) - - vbox.addWidget(bcreate) - vbox.addSpacing(11) - vbox.addWidget(QLabel(''.join([ "<b>"+_("WARNING")+ "</b>:" + _("Printing a revealer and encrypted seed"), '<br/>', - _("on the same printer is not trustless towards the printer."), '<br/>', - ]))) - vbox.addSpacing(11) - vbox.addLayout(Buttons(CloseButton(self.d))) - return bool(self.d.exec_()) def get_noise(self): @@ -169,12 +186,14 @@ class Plugin(BasePlugin): def bcrypt(self, dialog): self.rawnoise = False - dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at:").format(self.was, self.version, self.code_id), - "<br/>","<b>", self.base_dir+ self.filename+self.version+"_"+self.code_id,"</b>"])) + dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, self.version, self.code_id), + "<b>", self.base_dir+ self.filename+self.version+"_"+self.code_id,"</b>", "<br/>", + "<br/>", "<b>", _("Always check you backups.") ])) dialog.close() def ext_warning(self, dialog): - dialog.show_message(''.join(["<b>",_("Warning: "), "</b>", _("your seed extension will not be included in the encrypted backup.")])) + dialog.show_message(''.join(["<b>",_("Warning"), ": </b>", + _("your seed extension will <b>not</b> be included in the encrypted backup.")])) dialog.close() def bdone(self, dialog): @@ -185,11 +204,11 @@ class Plugin(BasePlugin): def customtxt_limits(self): txt = self.text.text() self.max_chars.setVisible(False) - self.char_count.setText("("+str(len(txt))+"/216)") + self.char_count.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})") if len(txt)>0: self.ctext.setEnabled(True) - if len(txt) > 216: - self.text.setPlainText(self.text.toPlainText()[:216]) + if len(txt) > self.MAX_PLAINTEXT_LEN: + self.text.setPlainText(txt[:self.MAX_PLAINTEXT_LEN]) self.max_chars.setVisible(True) def t(self): @@ -198,57 +217,51 @@ class Plugin(BasePlugin): def cypherseed_dialog(self, window): - d = WindowModalDialog(window, "Revealer") - d.setMinimumWidth(420) - + d = WindowModalDialog(window, "Encryption Dialog") + d.setMinimumWidth(500) + d.setMinimumHeight(210) + d.setMaximumHeight(450) + d.setContentsMargins(11, 11, 1, 1) self.c_dialog = d - self.vbox = QVBoxLayout(d) - self.vbox.addSpacing(21) - + hbox = QHBoxLayout(d) + self.vbox = QVBoxLayout() logo = QLabel() - self.vbox.addWidget(logo) + hbox.addWidget(logo) logo.setPixmap(QPixmap(':icons/revealer.png')) - logo.setAlignment(Qt.AlignCenter) - self.vbox.addSpacing(42) - + logo.setAlignment(Qt.AlignLeft) + hbox.addSpacing(16) + self.vbox.addWidget(WWLabel("<b>" + _("Revealer Secret Backup Plugin") + "</b><br>" + + _("Ready to encrypt for revealer {}").format(self.version+'_'+self.code_id ))) + self.vbox.addSpacing(11) + hbox.addLayout(self.vbox) grid = QGridLayout() self.vbox.addLayout(grid) - cprint = QPushButton(_("Generate encrypted seed backup")) + cprint = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name)) + cprint.setMaximumWidth(250) cprint.clicked.connect(partial(self.seed_img, True)) self.vbox.addWidget(cprint) - self.vbox.addSpacing(14) - - self.vbox.addWidget(WWLabel(_("OR type any secret below:"))) + self.vbox.addSpacing(1) + self.vbox.addWidget(WWLabel("<b>"+_("OR")+"</b> "+_("type a custom alphanumerical secret below:"))) self.text = ScanQRTextEdit() self.text.setTabChangesFocus(True) self.text.setMaximumHeight(70) self.text.textChanged.connect(self.customtxt_limits) self.vbox.addWidget(self.text) - self.char_count = WWLabel("") self.char_count.setAlignment(Qt.AlignRight) self.vbox.addWidget(self.char_count) - - self.max_chars = WWLabel("<font color='red'>" + _("This version supports a maximum of 216 characters.")+"</font>") + self.max_chars = WWLabel("<font color='red'>" + + _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN) + +"</font>") self.vbox.addWidget(self.max_chars) self.max_chars.setVisible(False) - - self.ctext = QPushButton(_("Generate custom secret encrypted backup")) + self.ctext = QPushButton(_("Encrypt custom secret")) self.ctext.clicked.connect(self.t) - self.vbox.addWidget(self.ctext) self.ctext.setEnabled(False) - self.vbox.addSpacing(11) - self.vbox.addWidget( - QLabel(''.join(["<b>" + _("WARNING") + "</b>: " + _("Revealer is a one-time-pad and should be used only once."), '<br/>', - _("Multiple secrets encrypted for the same Revealer can be attacked."), '<br/>', - ]))) - self.vbox.addSpacing(11) - - self.vbox.addSpacing(21) self.vbox.addLayout(Buttons(CloseButton(d))) return bool(d.exec_()) @@ -259,11 +272,18 @@ class Plugin(BasePlugin): def seed_img(self, is_seed = True): - if not self.cseed and self.txt == False: - return - if is_seed: - txt = self.cseed + try: + cseed = self.get_seed() + except UserCancelled: + return + except InvalidPassword as e: + self.d.show_error(str(e)) + return + if not cseed: + self.d.show_message(_("This wallet has no seed")) + return + txt = cseed.upper() else: txt = self.txt.upper() @@ -282,7 +302,7 @@ class Plugin(BasePlugin): else: fontsize = 12 linespace = 10 - max_letters = 23 + max_letters = 21 max_lines = 9 max_words = int(max_letters/4) @@ -383,9 +403,9 @@ class Plugin(BasePlugin): else: self.filename = self.wallet_name+'_'+ _('seed')+'_' self.was = self.wallet_name +' ' + _('seed') + if self.extension: + self.ext_warning(self.c_dialog) - if self.has_extension: - self.ext_warning(self.c_dialog) if not calibration: self.toPdf(QImage(cypherseed))