Browse Source

qt: implement initial bip39 refine and account detect, restore from seed pages

master
Sander van Grieken 2 years ago
parent
commit
e43b005047
  1. 124
      electrum/gui/qt/wizard/wallet.py
  2. 7
      electrum/gui/qt/wizard/wizard.py

124
electrum/gui/qt/wizard/wallet.py

@ -5,8 +5,10 @@ from PyQt5.QtGui import QPen, QPainter, QPalette
from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget, from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget,
QFileDialog, QSlider, QGridLayout) QFileDialog, QSlider, QGridLayout)
from electrum.bip32 import is_bip32_derivation, BIP32Node
from electrum.daemon import Daemon from electrum.daemon import Daemon
from electrum.i18n import _ from electrum.i18n import _
from electrum.keystore import bip44_derivation, bip39_to_seed
from electrum.storage import StorageReadWriteError from electrum.storage import StorageReadWriteError
from electrum.util import WalletFileException, get_new_wallet_name from electrum.util import WalletFileException, get_new_wallet_name
from electrum.wallet import wallet_types from electrum.wallet import wallet_types
@ -14,6 +16,7 @@ from .wizard import QEAbstractWizard, WizardComponent
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum import WalletStorage, mnemonic, keystore from electrum import WalletStorage, mnemonic, keystore
from electrum.wizard import NewWalletWizard from electrum.wizard import NewWalletWizard
from ..bip39_recovery_dialog import Bip39RecoveryDialog
from ..password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD from ..password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD
from ..seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout from ..seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout
from ..util import ChoicesLayout, PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height from ..util import ChoicesLayout, PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height
@ -46,8 +49,8 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
'create_ext': { 'gui': WCCreateExt }, 'create_ext': { 'gui': WCCreateExt },
'confirm_seed': { 'gui': WCConfirmSeed }, 'confirm_seed': { 'gui': WCConfirmSeed },
'confirm_ext': { 'gui': WCConfirmExt }, 'confirm_ext': { 'gui': WCConfirmExt },
'have_seed': { 'gui': 'WCHaveSeed' }, 'have_seed': { 'gui': WCHaveSeed },
'bip39_refine': { 'gui': 'WCBIP39Refine' }, 'bip39_refine': { 'gui': WCBIP39Refine },
'have_master_key': { 'gui': 'WCHaveMasterKey' }, 'have_master_key': { 'gui': 'WCHaveMasterKey' },
'multisig': { 'gui': WCMultisig }, 'multisig': { 'gui': WCMultisig },
'multisig_cosigner_keystore': { 'gui': 'WCCosignerKeystore' }, 'multisig_cosigner_keystore': { 'gui': 'WCCosignerKeystore' },
@ -293,7 +296,9 @@ class WCCreateSeed(WizardComponent):
self.seed_type = 'standard' if self.wizard.config.WIZARD_DONT_CREATE_SEGWIT else 'segwit' self.seed_type = 'standard' if self.wizard.config.WIZARD_DONT_CREATE_SEGWIT else 'segwit'
self.slayout = None self.slayout = None
self.seed = None self.seed = None
QTimer.singleShot(100, self.create_seed)
def on_ready(self):
QTimer.singleShot(1, self.create_seed)
def apply(self): def apply(self):
if self.slayout: if self.slayout:
@ -395,6 +400,119 @@ class WCConfirmExt(WizardComponent):
pass pass
class WCHaveSeed(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Enter Seed'))
self.layout().addWidget(WWLabel(_('Please enter your seed phrase in order to restore your wallet.')))
# TODO: SeedLayout assumes too much in parent, refactor SeedLayout
# for now, fake parent.next_button.setEnabled
class Hack:
def setEnabled(self2, b):
self.valid = b
self.next_button = Hack()
def on_ready(self):
options = ['ext'] if self.wizard_data['wallet_type'] == '2fa' else ['ext', 'bip39', 'slip39']
self.slayout = SeedLayout(
is_seed=self.is_seed,
options=options,
parent=self,
config=self.wizard.config,
)
self.layout().addLayout(self.slayout)
def is_seed(self, x):
if self.wizard_data['wallet_type'] == 'standard':
return mnemonic.is_seed(x)
else:
return mnemonic.seed_type(x) in ['standard', 'segwit']
def apply(self):
self.wizard_data['seed'] = self.slayout.get_seed()
self.wizard_data['seed_variant'] = self.slayout.seed_type
if self.slayout.seed_type == 'electrum':
self.wizard_data['seed_type'] = mnemonic.seed_type(self.slayout.get_seed())
else:
self.wizard_data['seed_type'] = self.slayout.seed_type
self.wizard_data['seed_extend'] = self.slayout.is_ext
self.wizard_data['seed_extra_words'] = '' # empty default
class WCBIP39Refine(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Script type and Derivation path'))
def on_ready(self):
if self.wizard_data['wallet_type'] == 'multisig':
raise Exception('NYI')
message1 = _('Choose the type of addresses in your wallet.')
message2 = ' '.join([
_('You can override the suggested derivation path.'),
_('If you are not sure what this is, leave this field unchanged.')
])
hide_choices = False
default_choice_idx = 2
choices = [
('standard', 'legacy (p2pkh)', bip44_derivation(0, bip43_purpose=44)),
('p2wpkh-p2sh', 'p2sh-segwit (p2wpkh-p2sh)', bip44_derivation(0, bip43_purpose=49)),
('p2wpkh', 'native segwit (p2wpkh)', bip44_derivation(0, bip43_purpose=84)),
]
passphrase = self.wizard_data['seed_extra_words'] if self.wizard_data['seed_extend'] else ''
root_seed = bip39_to_seed(self.wizard_data['seed'], passphrase)
def get_account_xpub(account_path):
root_node = BIP32Node.from_rootseed(root_seed, xtype="standard")
account_node = root_node.subkey_at_private_derivation(account_path)
account_xpub = account_node.to_xpub()
return account_xpub
if get_account_xpub:
button = QPushButton(_("Detect Existing Accounts"))
def on_account_select(account):
script_type = account["script_type"]
if script_type == "p2pkh":
script_type = "standard"
button_index = self.c_values.index(script_type)
button = self.clayout.group.buttons()[button_index]
button.setChecked(True)
self.derivation_path_edit.setText(account["derivation_path"])
button.clicked.connect(lambda: Bip39RecoveryDialog(self, get_account_xpub, on_account_select))
self.layout().addWidget(button, alignment=Qt.AlignLeft)
self.layout().addWidget(QLabel(_("Or")))
self.c_values = [x[0] for x in choices]
c_titles = [x[1] for x in choices]
c_default_text = [x[2] for x in choices]
def on_choice_click(clayout):
idx = clayout.selected_index()
self.derivation_path_edit.setText(c_default_text[idx])
self.clayout = ChoicesLayout(message1, c_titles, on_choice_click,
checked_index=default_choice_idx)
if not hide_choices:
self.layout().addLayout(self.clayout.layout())
self.layout().addWidget(WWLabel(message2))
self.derivation_path_edit = QLineEdit()
self.derivation_path_edit.textChanged.connect(self.validate)
on_choice_click(self.clayout) # set default value for derivation path
self.layout().addWidget(self.derivation_path_edit)
def validate(self):
self.valid = is_bip32_derivation(self.derivation_path_edit.text())
def apply(self):
self.wizard_data['script_type'] = self.c_values[self.clayout.selected_index()]
self.wizard_data['derivation_path'] = str(self.derivation_path_edit.text())
class WCMultisig(WizardComponent): class WCMultisig(WizardComponent):
def __init__(self, parent, wizard): def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Multi-Signature Wallet')) WizardComponent.__init__(self, parent, wizard, title=_('Multi-Signature Wallet'))

7
electrum/gui/qt/wizard/wizard.py

@ -96,12 +96,12 @@ class QEAbstractWizard(QDialog):
self._logger.error(f'not a class: {comp!r}') self._logger.error(f'not a class: {comp!r}')
raise e raise e
page.wizard_data = wdata page.wizard_data = wdata
page.config = self.config
page.updated.connect(self.on_page_updated) page.updated.connect(self.on_page_updated)
self._logger.debug(f'{page!r}') self._logger.debug(f'{page!r}')
# add to stack and update wizard # add to stack and update wizard
self.main_widget.setCurrentIndex(self.main_widget.addWidget(page)) self.main_widget.setCurrentIndex(self.main_widget.addWidget(page))
page.on_ready()
page.apply() page.apply()
self.update() self.update()
@ -208,6 +208,11 @@ class WizardComponent(QWidget):
@abstractmethod @abstractmethod
def apply(self): def apply(self):
# called to apply UI component values to wizard_data
pass
def on_ready(self):
# called when wizard_data is available
pass pass
@pyqtSlot() @pyqtSlot()

Loading…
Cancel
Save