|
|
|
|
@ -69,66 +69,10 @@ def seed_warning_msg(seed):
|
|
|
|
|
]).format(len(seed.split())) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SeedLayout(QVBoxLayout): |
|
|
|
|
class SeedWidget(QWidget): |
|
|
|
|
|
|
|
|
|
updated = pyqtSignal() |
|
|
|
|
|
|
|
|
|
def seed_options(self): |
|
|
|
|
dialog = QDialog() |
|
|
|
|
dialog.setWindowTitle(_("Seed Options")) |
|
|
|
|
vbox = QVBoxLayout(dialog) |
|
|
|
|
|
|
|
|
|
seed_types = [ |
|
|
|
|
(value, title) for value, title in ( |
|
|
|
|
('electrum', _('Electrum')), |
|
|
|
|
('bip39', _('BIP39 seed')), |
|
|
|
|
('slip39', _('SLIP39 seed')), |
|
|
|
|
) |
|
|
|
|
if value in self.options or value == 'electrum' |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
if 'ext' in self.options: |
|
|
|
|
cb_ext = QCheckBox(_('Extend this seed with custom words')) |
|
|
|
|
cb_ext.setChecked(self.is_ext) |
|
|
|
|
vbox.addWidget(cb_ext) |
|
|
|
|
|
|
|
|
|
if len(seed_types) >= 2: |
|
|
|
|
def on_selected(idx): |
|
|
|
|
self.seed_type = seed_type_choice.selected_key |
|
|
|
|
self.is_seed = (lambda x: bool(x)) if self.seed_type != 'electrum' else self.saved_is_seed |
|
|
|
|
self.slip39_current_mnemonic_invalid = None |
|
|
|
|
self.seed_status.setText('') |
|
|
|
|
self.on_edit() |
|
|
|
|
if self.seed_type == 'bip39': |
|
|
|
|
msg = ' '.join([ |
|
|
|
|
'<b>' + _('Warning') + ':</b> ', |
|
|
|
|
_('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'), |
|
|
|
|
_('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'), |
|
|
|
|
_('BIP39 seeds do not include a version number, which compromises compatibility with future software.'), |
|
|
|
|
_('We do not guarantee that BIP39 imports will always be supported in Electrum.'), |
|
|
|
|
]) |
|
|
|
|
elif self.seed_type == 'slip39': |
|
|
|
|
msg = ' '.join([ |
|
|
|
|
'<b>' + _('Warning') + ':</b> ', |
|
|
|
|
_('SLIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'), |
|
|
|
|
_('However, we do not generate SLIP39 seeds.'), |
|
|
|
|
]) |
|
|
|
|
else: |
|
|
|
|
msg = '' |
|
|
|
|
self.update_share_buttons() |
|
|
|
|
self.initialize_completer() |
|
|
|
|
self.seed_warning.setText(msg) |
|
|
|
|
|
|
|
|
|
seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=seed_types, selected=self.seed_type) |
|
|
|
|
seed_type_choice.itemSelected.connect(on_selected) |
|
|
|
|
vbox.addWidget(seed_type_choice) |
|
|
|
|
|
|
|
|
|
vbox.addLayout(Buttons(OkButton(dialog))) |
|
|
|
|
if not dialog.exec(): |
|
|
|
|
return None |
|
|
|
|
self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False |
|
|
|
|
self.seed_type = seed_type_choice.selected_key if len(seed_types) >= 2 else 'electrum' |
|
|
|
|
self.updated.emit() |
|
|
|
|
validChanged = pyqtSignal([bool], arguments=['valid']) |
|
|
|
|
|
|
|
|
|
def __init__( |
|
|
|
|
self, |
|
|
|
|
@ -137,20 +81,38 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
icon=True, |
|
|
|
|
msg=None, |
|
|
|
|
options=None, |
|
|
|
|
is_seed=None, |
|
|
|
|
is_seed=None, # only used for electrum seeds |
|
|
|
|
passphrase=None, |
|
|
|
|
parent=None, |
|
|
|
|
for_seed_words=True, |
|
|
|
|
*, |
|
|
|
|
config: 'SimpleConfig', |
|
|
|
|
): |
|
|
|
|
QVBoxLayout.__init__(self) |
|
|
|
|
self.parent = parent |
|
|
|
|
QWidget.__init__(self, parent) |
|
|
|
|
vbox = QVBoxLayout() |
|
|
|
|
self.setLayout(vbox) |
|
|
|
|
|
|
|
|
|
self.options = options |
|
|
|
|
self.config = config |
|
|
|
|
self.seed_type = 'electrum' |
|
|
|
|
|
|
|
|
|
if options: |
|
|
|
|
self.seed_types = [ |
|
|
|
|
(value, title) for value, title in ( |
|
|
|
|
('electrum', _('Electrum')), |
|
|
|
|
('bip39', _('BIP39 seed')), |
|
|
|
|
('slip39', _('SLIP39 seed')), |
|
|
|
|
) |
|
|
|
|
if value in self.options |
|
|
|
|
] |
|
|
|
|
assert len(self.seed_types) |
|
|
|
|
self.seed_type = self.seed_types[0][0] |
|
|
|
|
else: |
|
|
|
|
self.seed_type = 'electrum' |
|
|
|
|
|
|
|
|
|
self.is_seed = is_seed |
|
|
|
|
|
|
|
|
|
if title: |
|
|
|
|
self.addWidget(WWLabel(title)) |
|
|
|
|
vbox.addWidget(WWLabel(title)) |
|
|
|
|
if seed: # "read only", we already have the text |
|
|
|
|
if for_seed_words: |
|
|
|
|
self.seed_e = ButtonsTextEdit() |
|
|
|
|
@ -162,8 +124,6 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
assert for_seed_words |
|
|
|
|
self.seed_e = CompletionTextEdit() |
|
|
|
|
self.seed_e.setTabChangesFocus(False) # so that tab auto-completes |
|
|
|
|
self.is_seed = is_seed |
|
|
|
|
self.saved_is_seed = self.is_seed |
|
|
|
|
self.seed_e.textChanged.connect(self.on_edit) |
|
|
|
|
self.initialize_completer() |
|
|
|
|
|
|
|
|
|
@ -176,7 +136,7 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
logo.setMaximumWidth(60) |
|
|
|
|
hbox.addWidget(logo) |
|
|
|
|
hbox.addWidget(self.seed_e) |
|
|
|
|
self.addLayout(hbox) |
|
|
|
|
vbox.addLayout(hbox) |
|
|
|
|
hbox = QHBoxLayout() |
|
|
|
|
hbox.addStretch(1) |
|
|
|
|
self.seed_type_label = QLabel('') |
|
|
|
|
@ -187,7 +147,7 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
if options: |
|
|
|
|
opt_button = EnterButton(_('Options'), self.seed_options) |
|
|
|
|
hbox.addWidget(opt_button) |
|
|
|
|
self.addLayout(hbox) |
|
|
|
|
vbox.addLayout(hbox) |
|
|
|
|
if passphrase: |
|
|
|
|
hbox = QHBoxLayout() |
|
|
|
|
passphrase_e = QLineEdit() |
|
|
|
|
@ -195,7 +155,7 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
passphrase_e.setReadOnly(True) |
|
|
|
|
hbox.addWidget(QLabel(_("Your seed extension is") + ':')) |
|
|
|
|
hbox.addWidget(passphrase_e) |
|
|
|
|
self.addLayout(hbox) |
|
|
|
|
vbox.addLayout(hbox) |
|
|
|
|
|
|
|
|
|
# slip39 shares |
|
|
|
|
self.slip39_mnemonic_index = 0 |
|
|
|
|
@ -211,15 +171,75 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
self.next_share_btn.clicked.connect(self.on_next_share) |
|
|
|
|
hbox.addWidget(self.next_share_btn) |
|
|
|
|
self.update_share_buttons() |
|
|
|
|
self.addLayout(hbox) |
|
|
|
|
vbox.addLayout(hbox) |
|
|
|
|
|
|
|
|
|
self.addStretch(1) |
|
|
|
|
vbox.addStretch(1) |
|
|
|
|
self.seed_status = WWLabel('') |
|
|
|
|
self.addWidget(self.seed_status) |
|
|
|
|
vbox.addWidget(self.seed_status) |
|
|
|
|
self.seed_warning = WWLabel('') |
|
|
|
|
if msg: |
|
|
|
|
self.seed_warning.setText(seed_warning_msg(seed)) |
|
|
|
|
self.addWidget(self.seed_warning) |
|
|
|
|
else: |
|
|
|
|
self.update_seed_warning() |
|
|
|
|
|
|
|
|
|
vbox.addWidget(self.seed_warning) |
|
|
|
|
|
|
|
|
|
def seed_options(self): |
|
|
|
|
dialog = QDialog() |
|
|
|
|
dialog.setWindowTitle(_("Seed Options")) |
|
|
|
|
vbox = QVBoxLayout(dialog) |
|
|
|
|
|
|
|
|
|
if 'ext' in self.options: |
|
|
|
|
cb_ext = QCheckBox(_('Extend this seed with custom words')) |
|
|
|
|
cb_ext.setChecked(self.is_ext) |
|
|
|
|
vbox.addWidget(cb_ext) |
|
|
|
|
|
|
|
|
|
def on_selected(idx): |
|
|
|
|
self.seed_type = seed_type_choice.selected_key |
|
|
|
|
self.slip39_current_mnemonic_invalid = None |
|
|
|
|
self.seed_status.setText('') |
|
|
|
|
self.update_seed_warning() |
|
|
|
|
self.on_edit() |
|
|
|
|
self.update_share_buttons() |
|
|
|
|
self.initialize_completer() |
|
|
|
|
|
|
|
|
|
if len(self.seed_types) > 1: |
|
|
|
|
seed_type_choice = ChoiceWidget(message=_('Seed type'), choices=self.seed_types, selected=self.seed_type) |
|
|
|
|
seed_type_choice.itemSelected.connect(on_selected) |
|
|
|
|
vbox.addWidget(seed_type_choice) |
|
|
|
|
|
|
|
|
|
vbox.addLayout(Buttons(OkButton(dialog))) |
|
|
|
|
|
|
|
|
|
if not dialog.exec(): |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
if 'ext' in self.options: |
|
|
|
|
self.is_ext = cb_ext.isChecked() |
|
|
|
|
if len(self.seed_types) > 1: |
|
|
|
|
self.seed_type = seed_type_choice.selected_key |
|
|
|
|
|
|
|
|
|
self.update_seed_warning() |
|
|
|
|
self.updated.emit() |
|
|
|
|
|
|
|
|
|
def update_seed_warning(self): |
|
|
|
|
if self.seed_type == 'bip39': |
|
|
|
|
msg = ' '.join([ |
|
|
|
|
'<b>' + _('Warning') + ':</b> ', |
|
|
|
|
_('BIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'), |
|
|
|
|
_('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'), |
|
|
|
|
_('BIP39 seeds do not include a version number, which compromises compatibility with future software.'), |
|
|
|
|
_('We do not guarantee that BIP39 imports will always be supported in Electrum.'), |
|
|
|
|
]) |
|
|
|
|
elif self.seed_type == 'slip39': |
|
|
|
|
msg = ' '.join([ |
|
|
|
|
'<b>' + _('Warning') + ':</b> ', |
|
|
|
|
_('SLIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'), |
|
|
|
|
_('However, we do not generate SLIP39 seeds.'), |
|
|
|
|
]) |
|
|
|
|
else: |
|
|
|
|
msg = '' |
|
|
|
|
|
|
|
|
|
self.seed_warning.setText(msg) |
|
|
|
|
|
|
|
|
|
def initialize_completer(self): |
|
|
|
|
if self.seed_type != 'slip39': |
|
|
|
|
@ -261,12 +281,12 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
|
|
|
|
|
def on_edit(self): |
|
|
|
|
s = ' '.join(self.get_seed_words()) |
|
|
|
|
b = self.is_seed(s) |
|
|
|
|
if self.seed_type == 'bip39': |
|
|
|
|
from electrum.keystore import bip39_is_checksum_valid |
|
|
|
|
is_checksum, is_wordlist = bip39_is_checksum_valid(s) |
|
|
|
|
label = '' |
|
|
|
|
if bool(s): |
|
|
|
|
valid = bool(s) |
|
|
|
|
if valid: |
|
|
|
|
label = ('' if is_checksum else _('BIP39 checksum failed')) if is_wordlist else _('Unknown BIP39 wordlist') |
|
|
|
|
elif self.seed_type == 'slip39': |
|
|
|
|
self.slip39_mnemonics[self.slip39_mnemonic_index] = s |
|
|
|
|
@ -287,15 +307,13 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
self.seed_status.setText(seed_status) |
|
|
|
|
self.slip39_current_mnemonic_invalid = current_mnemonic_invalid |
|
|
|
|
|
|
|
|
|
b = self.slip39_seed is not None |
|
|
|
|
valid = self.slip39_seed is not None |
|
|
|
|
self.update_share_buttons() |
|
|
|
|
else: |
|
|
|
|
valid = self.is_seed(s) |
|
|
|
|
t = calc_seed_type(s) |
|
|
|
|
label = _('Seed Type') + ': ' + t if t else '' |
|
|
|
|
if t and not b: # electrum seed, but does not conform to dialog rules |
|
|
|
|
# FIXME we should just accept any electrum seed and "redirect" the wizard automatically. |
|
|
|
|
# i.e. if user selected wallet_type=="standard" but entered a 2fa seed, accept and redirect |
|
|
|
|
# if user selected wallet_type=="2fa" but entered a std electrum seed, accept and redirect |
|
|
|
|
if t and not valid: # electrum seed, but does not conform to dialog rules |
|
|
|
|
wiztype_fullname = _('Wallet with two-factor authentication') if is_any_2fa_seed_type(t) else _("Standard wallet") |
|
|
|
|
msg = ' '.join([ |
|
|
|
|
'<b>' + _('Warning') + ':</b> ', |
|
|
|
|
@ -307,7 +325,7 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
self.seed_warning.setText("") |
|
|
|
|
|
|
|
|
|
self.seed_type_label.setText(label) |
|
|
|
|
self.parent.next_button.setEnabled(b) |
|
|
|
|
self.validChanged.emit(valid) |
|
|
|
|
|
|
|
|
|
# disable suggestions if user already typed an unknown word |
|
|
|
|
for word in self.get_seed_words()[:-1]: |
|
|
|
|
@ -354,7 +372,10 @@ class SeedLayout(QVBoxLayout):
|
|
|
|
|
self.slip39_current_mnemonic_invalid = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class KeysLayout(QVBoxLayout): |
|
|
|
|
class KeysWidget(QWidget): |
|
|
|
|
|
|
|
|
|
validChanged = pyqtSignal([bool], arguments=['valid']) |
|
|
|
|
|
|
|
|
|
def __init__( |
|
|
|
|
self, |
|
|
|
|
parent=None, |
|
|
|
|
@ -364,29 +385,28 @@ class KeysLayout(QVBoxLayout):
|
|
|
|
|
*, |
|
|
|
|
config: 'SimpleConfig', |
|
|
|
|
): |
|
|
|
|
QVBoxLayout.__init__(self) |
|
|
|
|
self.parent = parent |
|
|
|
|
QWidget.__init__(self, parent) |
|
|
|
|
vbox = QVBoxLayout() |
|
|
|
|
self.setLayout(vbox) |
|
|
|
|
|
|
|
|
|
self.is_valid = is_valid |
|
|
|
|
self.text_e = ScanQRTextEdit(allow_multi=allow_multi, config=config) |
|
|
|
|
self.text_e.textChanged.connect(self.on_edit) |
|
|
|
|
if isinstance(header_layout, str): |
|
|
|
|
self.addWidget(WWLabel(header_layout)) |
|
|
|
|
vbox.addWidget(WWLabel(header_layout)) |
|
|
|
|
else: |
|
|
|
|
self.addLayout(header_layout) |
|
|
|
|
self.addWidget(self.text_e) |
|
|
|
|
vbox.addLayout(header_layout) |
|
|
|
|
vbox.addWidget(self.text_e) |
|
|
|
|
|
|
|
|
|
def get_text(self): |
|
|
|
|
return self.text_e.text() |
|
|
|
|
|
|
|
|
|
def on_edit(self): |
|
|
|
|
valid = False |
|
|
|
|
try: |
|
|
|
|
valid = self.is_valid(self.get_text()) |
|
|
|
|
except Exception as e: |
|
|
|
|
self.parent.next_button.setToolTip(f'{_("Error")}: {str(e)}') |
|
|
|
|
else: |
|
|
|
|
self.parent.next_button.setToolTip('') |
|
|
|
|
self.parent.next_button.setEnabled(valid) |
|
|
|
|
valid = False |
|
|
|
|
self.validChanged.emit(valid) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SeedDialog(WindowModalDialog): |
|
|
|
|
@ -395,13 +415,7 @@ class SeedDialog(WindowModalDialog):
|
|
|
|
|
WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed'))) |
|
|
|
|
self.setMinimumWidth(400) |
|
|
|
|
vbox = QVBoxLayout(self) |
|
|
|
|
title = _("Your wallet generation seed is:") |
|
|
|
|
slayout = SeedLayout( |
|
|
|
|
title=title, |
|
|
|
|
seed=seed, |
|
|
|
|
msg=True, |
|
|
|
|
passphrase=passphrase, |
|
|
|
|
config=config, |
|
|
|
|
) |
|
|
|
|
vbox.addLayout(slayout) |
|
|
|
|
title = _("Your wallet generation seed is:") |
|
|
|
|
seed_widget = SeedWidget(title=title, seed=seed, msg=True, passphrase=passphrase, config=config) |
|
|
|
|
vbox.addWidget(seed_widget) |
|
|
|
|
vbox.addLayout(Buttons(CloseButton(self))) |
|
|
|
|
|