From b41a83cedac03ba0d7a4c8fa0e824b5288298957 Mon Sep 17 00:00:00 2001 From: tiagotrs Date: Mon, 10 Dec 2018 22:05:39 +0100 Subject: [PATCH 1/3] new hook/interface ref #4540 --- electrum/gui/qt/main_window.py | 1 + electrum/gui/qt/seed_dialog.py | 4 - electrum/plugins/revealer/qt.py | 156 ++++++++++++++++---------------- 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 0db7fa05c..31d67a80c 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -2014,6 +2014,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.status_button = StatusBarButton(QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self)) sb.addPermanentWidget(self.status_button) run_hook('create_status_bar', sb) + run_hook('revealer_hook', sb) self.setStatusBar(sb) def update_lock_icon(self): diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py index 86fc15ec9..bca282b07 100644 --- 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/qt.py b/electrum/plugins/revealer/qt.py index d64dc585e..06f817ddc 100644 --- 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 @@ -31,6 +24,7 @@ from electrum.i18n import _ from electrum.util import to_bytes, make_dir 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 @@ -58,10 +52,8 @@ class Plugin(BasePlugin): make_dir(self.base_dir) @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 revealer_hook(self, parent): + parent.addPermanentWidget(StatusBarButton(QIcon(':icons/revealer.png'), "Revealer"+_(" secret backup utility"), partial(self.setup_dialog, parent))) def requires_settings(self): return True @@ -69,39 +61,70 @@ 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: + return + + keystore = self.wallet.get_keystore() + try: + self.cseed = keystore.get_seed(password) + if keystore.get_passphrase(password): + self.extension = True + except Exception: + traceback.print_exc(file=sys.stdout) + return + 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) - logo = QLabel() - vbox.addWidget(logo) - logo.setPixmap(QPixmap(':icons/revealer.png')) - logo.setAlignment(Qt.AlignCenter) - vbox.addSpacing(42) + 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() + self.hbox.addWidget(logo) + logo.setPixmap(QPixmap(':icons/revealer.png')) + logo.setAlignment(Qt.AlignLeft) + self.hbox.addSpacing(16) + vbox.addWidget(WWLabel(""+_("Revealer Secret Backup Plugin")+"
" + +_("To encrypt your backup, first we need to load some noise.")+"
")) + 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(""+_("Enter your physical revealer code:")+"")) + 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("" + _("Warning") + ": " + _("Each revealer should be used only once.") + +"
"+_("more info at https://revealer.cc/faq"))) def mk_digital(): try: @@ -112,15 +135,6 @@ class Plugin(BasePlugin): self.cypherseed_dialog(window) bcreate.clicked.connect(mk_digital) - - vbox.addWidget(bcreate) - vbox.addSpacing(11) - vbox.addWidget(QLabel(''.join([ ""+_("WARNING")+ ":" + _("Printing a revealer and encrypted seed"), '
', - _("on the same printer is not trustless towards the printer."), '
', - ]))) - vbox.addSpacing(11) - vbox.addLayout(Buttons(CloseButton(self.d))) - return bool(self.d.exec_()) def get_noise(self): @@ -174,7 +188,7 @@ class Plugin(BasePlugin): dialog.close() def ext_warning(self, dialog): - dialog.show_message(''.join(["",_("Warning: "), "", _("your seed extension will not be included in the encrypted backup.")])) + dialog.show_message(''.join(["",_("Warning: "), "", _("your seed extension will ")+""+_("not")+""+_(" be included in the encrypted backup.")])) dialog.close() def bdone(self, dialog): @@ -198,57 +212,49 @@ 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("" + _("Revealer Secret Backup Plugin") + "
" + + _("Ready to encrypt for revealer ")+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 ")+self.wallet_name+_("'s seed")) + 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(""+_("OR ")+""+_("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("" + _("This version supports a maximum of 216 characters.")+"") 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(["" + _("WARNING") + ": " + _("Revealer is a one-time-pad and should be used only once."), '
', - _("Multiple secrets encrypted for the same Revealer can be attacked."), '
', - ]))) - self.vbox.addSpacing(11) - - self.vbox.addSpacing(21) self.vbox.addLayout(Buttons(CloseButton(d))) return bool(d.exec_()) @@ -259,11 +265,9 @@ 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 + self.get_seed() + txt = self.cseed.upper() else: txt = self.txt.upper() @@ -328,7 +332,7 @@ class Plugin(BasePlugin): entropy = binascii.unhexlify(str(format(self.noise_seed, '032x'))) code_id = binascii.unhexlify(self.version + self.code_id) - + print (self.hex_noise) drbg = DRBG(entropy + code_id) noise_array=bin(int.from_bytes(drbg.generate(1929), 'big'))[2:] @@ -384,7 +388,7 @@ class Plugin(BasePlugin): self.filename = self.wallet_name+'_'+ _('seed')+'_' self.was = self.wallet_name +' ' + _('seed') - if self.has_extension: + if self.extension: self.ext_warning(self.c_dialog) if not calibration: From ff2cdf9f16440c3583e9857e16178b29dd65b66d Mon Sep 17 00:00:00 2001 From: tiagotrs Date: Mon, 10 Dec 2018 23:00:50 +0100 Subject: [PATCH 2/3] small fixes, simplification/improvement of texts --- electrum/plugins/revealer/__init__.py | 8 ++------ electrum/plugins/revealer/qt.py | 27 +++++++++++++++------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/electrum/plugins/revealer/__init__.py b/electrum/plugins/revealer/__init__.py index dcc8c31fd..beb58d30e 100644 --- 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(["
", ""+_("Do you have something to hide ?")+"", '
', '
', - _("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."), '
', '
', - _("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."), '
', '
', - _("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."), '
', '
', - _("The plug-in also allows you to generate a digital Revealer file and print it yourself on a transparent overhead foil."), '
', '
', - _("Once activated you can access the plug-in through the icon at the seed dialog."), '
', '
', + _("This plug-in allows you to create a visually encrypted backup of your wallet seeds, or of custom alphanumeric secrets."), '
', '
', _("For more information, visit"), " https://revealer.cc", '
', '
', ]) diff --git a/electrum/plugins/revealer/qt.py b/electrum/plugins/revealer/qt.py index 06f817ddc..b846a1993 100644 --- a/electrum/plugins/revealer/qt.py +++ b/electrum/plugins/revealer/qt.py @@ -79,6 +79,8 @@ class Plugin(BasePlugin): self.cseed = keystore.get_seed(password) if keystore.get_passphrase(password): self.extension = True + else: + self.extension = False except Exception: traceback.print_exc(file=sys.stdout) return @@ -103,7 +105,7 @@ class Plugin(BasePlugin): logo.setAlignment(Qt.AlignLeft) self.hbox.addSpacing(16) vbox.addWidget(WWLabel(""+_("Revealer Secret Backup Plugin")+"
" - +_("To encrypt your backup, first we need to load some noise.")+"
")) + +_("To encrypt your backup, first we need to load some noise.")+"
")) vbox.addSpacing(7) bcreate = QPushButton(_("Create a new Revealer")) bcreate.setMaximumWidth(181) @@ -124,7 +126,7 @@ class Plugin(BasePlugin): self.next_button.clicked.connect(partial(self.cypherseed_dialog, window)) vbox.addWidget( QLabel("" + _("Warning") + ": " + _("Each revealer should be used only once.") - +"
"+_("more info at https://revealer.cc/faq"))) + +"
"+_("more information at https://revealer.cc/faq"))) def mk_digital(): try: @@ -183,8 +185,9 @@ 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), - "
","", self.base_dir+ self.filename+self.version+"_"+self.code_id,""])) + dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, self.version, self.code_id), + "", self.base_dir+ self.filename+self.version+"_"+self.code_id,"", "
", + "
", "", _("Always check you backups.") ])) dialog.close() def ext_warning(self, dialog): @@ -199,11 +202,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("("+str(len(txt))+"/189)") if len(txt)>0: self.ctext.setEnabled(True) - if len(txt) > 216: - self.text.setPlainText(self.text.toPlainText()[:216]) + if len(txt) > 189: + self.text.setPlainText(self.text.toPlainText()[:189]) self.max_chars.setVisible(True) def t(self): @@ -227,7 +230,7 @@ class Plugin(BasePlugin): logo.setAlignment(Qt.AlignLeft) hbox.addSpacing(16) self.vbox.addWidget(WWLabel("" + _("Revealer Secret Backup Plugin") + "
" - + _("Ready to encrypt for revealer ")+self.version+'_'+self.code_id )) + + _("Ready to encrypt for revealer {}").format(self.version+'_'+self.code_id ))) self.vbox.addSpacing(11) hbox.addLayout(self.vbox) grid = QGridLayout() @@ -247,7 +250,7 @@ class Plugin(BasePlugin): self.char_count = WWLabel("") self.char_count.setAlignment(Qt.AlignRight) self.vbox.addWidget(self.char_count) - self.max_chars = WWLabel("" + _("This version supports a maximum of 216 characters.")+"") + self.max_chars = WWLabel("" + _("This version supports a maximum of 189 characters.")+"") self.vbox.addWidget(self.max_chars) self.max_chars.setVisible(False) self.ctext = QPushButton(_("Encrypt custom secret")) @@ -286,7 +289,7 @@ class Plugin(BasePlugin): else: fontsize = 12 linespace = 10 - max_letters = 23 + max_letters = 21 max_lines = 9 max_words = int(max_letters/4) @@ -387,9 +390,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.extension: - self.ext_warning(self.c_dialog) if not calibration: self.toPdf(QImage(cypherseed)) From e1ba962fe184fbbef2c06ebadfff063e932120fb Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 14 Dec 2018 23:59:26 +0100 Subject: [PATCH 3/3] revealer: clean-up prev and fixes --- electrum/gui/qt/main_window.py | 1 - electrum/plugins/revealer/qt.py | 57 ++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 31d67a80c..0db7fa05c 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -2014,7 +2014,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.status_button = StatusBarButton(QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self)) sb.addPermanentWidget(self.status_button) run_hook('create_status_bar', sb) - run_hook('revealer_hook', sb) self.setStatusBar(sb) def update_lock_icon(self): diff --git a/electrum/plugins/revealer/qt.py b/electrum/plugins/revealer/qt.py index b846a1993..5ffca429f 100644 --- a/electrum/plugins/revealer/qt.py +++ b/electrum/plugins/revealer/qt.py @@ -21,7 +21,7 @@ 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 @@ -30,6 +30,8 @@ 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/' @@ -51,9 +53,13 @@ class Plugin(BasePlugin): self.rawnoise = False make_dir(self.base_dir) + self.extension = False + @hook - def revealer_hook(self, parent): - parent.addPermanentWidget(StatusBarButton(QIcon(':icons/revealer.png'), "Revealer"+_(" secret backup utility"), partial(self.setup_dialog, parent))) + 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 @@ -72,18 +78,13 @@ class Plugin(BasePlugin): if self.wallet.has_keystore_encryption(): password = self.password_dialog(parent=self.d.parent()) if not password: - return + raise UserCancelled() keystore = self.wallet.get_keystore() - try: - self.cseed = keystore.get_seed(password) - if keystore.get_passphrase(password): - self.extension = True - else: - self.extension = False - except Exception: - traceback.print_exc(file=sys.stdout) + 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.wallet = window.parent().wallet @@ -191,7 +192,8 @@ class Plugin(BasePlugin): dialog.close() def ext_warning(self, dialog): - dialog.show_message(''.join(["",_("Warning: "), "", _("your seed extension will ")+""+_("not")+""+_(" be included in the encrypted backup.")])) + dialog.show_message(''.join(["",_("Warning"), ": ", + _("your seed extension will not be included in the encrypted backup.")])) dialog.close() def bdone(self, dialog): @@ -202,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))+"/189)") + self.char_count.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})") if len(txt)>0: self.ctext.setEnabled(True) - if len(txt) > 189: - self.text.setPlainText(self.text.toPlainText()[:189]) + if len(txt) > self.MAX_PLAINTEXT_LEN: + self.text.setPlainText(txt[:self.MAX_PLAINTEXT_LEN]) self.max_chars.setVisible(True) def t(self): @@ -236,12 +238,12 @@ class Plugin(BasePlugin): grid = QGridLayout() self.vbox.addLayout(grid) - cprint = QPushButton(_("Encrypt ")+self.wallet_name+_("'s seed")) + 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(1) - self.vbox.addWidget(WWLabel(""+_("OR ")+""+_("type a custom alphanumerical secret below:"))) + self.vbox.addWidget(WWLabel(""+_("OR")+" "+_("type a custom alphanumerical secret below:"))) self.text = ScanQRTextEdit() self.text.setTabChangesFocus(True) self.text.setMaximumHeight(70) @@ -250,7 +252,9 @@ class Plugin(BasePlugin): self.char_count = WWLabel("") self.char_count.setAlignment(Qt.AlignRight) self.vbox.addWidget(self.char_count) - self.max_chars = WWLabel("" + _("This version supports a maximum of 189 characters.")+"") + self.max_chars = WWLabel("" + + _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN) + +"") self.vbox.addWidget(self.max_chars) self.max_chars.setVisible(False) self.ctext = QPushButton(_("Encrypt custom secret")) @@ -269,8 +273,17 @@ class Plugin(BasePlugin): def seed_img(self, is_seed = True): if is_seed: - self.get_seed() - txt = self.cseed.upper() + 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() @@ -335,7 +348,7 @@ class Plugin(BasePlugin): entropy = binascii.unhexlify(str(format(self.noise_seed, '032x'))) code_id = binascii.unhexlify(self.version + self.code_id) - print (self.hex_noise) + drbg = DRBG(entropy + code_id) noise_array=bin(int.from_bytes(drbg.generate(1929), 'big'))[2:]