wizard: add support for slip39
This commit is contained in:
@@ -31,9 +31,6 @@ WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\
|
||||
class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
# createError = pyqtSignal([str], arguments=["error"])
|
||||
# createSuccess = pyqtSignal()
|
||||
|
||||
def __init__(self, config: 'SimpleConfig', app: QApplication, daemon: Daemon, path, parent=None):
|
||||
NewWalletWizard.__init__(self, daemon)
|
||||
QEAbstractWizard.__init__(self, config, app, parent)
|
||||
@@ -46,10 +43,11 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
||||
'wallet_type': { 'gui': WCWalletType },
|
||||
'keystore_type': { 'gui': WCKeystoreType },
|
||||
'create_seed': { 'gui': WCCreateSeed },
|
||||
'create_ext': { 'gui': WCCreateExt },
|
||||
'create_ext': { 'gui': WCEnterExt },
|
||||
'confirm_seed': { 'gui': WCConfirmSeed },
|
||||
'confirm_ext': { 'gui': WCConfirmExt },
|
||||
'have_seed': { 'gui': WCHaveSeed },
|
||||
'have_ext': { 'gui': WCEnterExt },
|
||||
'bip39_refine': { 'gui': WCBIP39Refine },
|
||||
'have_master_key': { 'gui': WCHaveMasterKey },
|
||||
'multisig': { 'gui': WCMultisig },
|
||||
@@ -76,36 +74,28 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
||||
'confirm_ext': {
|
||||
'next': self.on_have_or_confirm_seed,
|
||||
'accept': self.maybe_master_pubkey,
|
||||
}
|
||||
},
|
||||
'have_seed': {
|
||||
'next': lambda d: 'have_ext' if d['seed_extend'] else self.on_have_or_confirm_seed(d),
|
||||
},
|
||||
'have_ext': {
|
||||
'next': self.on_have_or_confirm_seed,
|
||||
'accept': self.maybe_master_pubkey,
|
||||
},
|
||||
})
|
||||
|
||||
# pathChanged = pyqtSignal()
|
||||
# @pyqtProperty(str, notify=pathChanged)
|
||||
# def path(self):
|
||||
# return self._path
|
||||
#
|
||||
# @path.setter
|
||||
# def path(self, path):
|
||||
# self._path = path
|
||||
# self.pathChanged.emit()
|
||||
#
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
@path.setter
|
||||
def path(self, path):
|
||||
self._path = path
|
||||
|
||||
def is_single_password(self):
|
||||
# TODO: also take into account if possible with existing set of wallets. see qedaemon.py
|
||||
return self._daemon.config.WALLET_USE_SINGLE_PASSWORD
|
||||
|
||||
# @pyqtSlot('QJSValue', result=bool)
|
||||
# def hasDuplicateMasterKeys(self, js_data):
|
||||
# self._logger.info('Checking for duplicate masterkeys')
|
||||
# data = js_data.toVariant()
|
||||
# return self.has_duplicate_masterkeys(data)
|
||||
#
|
||||
# @pyqtSlot('QJSValue', result=bool)
|
||||
# def hasHeterogeneousMasterKeys(self, js_data):
|
||||
# self._logger.info('Checking for heterogeneous masterkeys')
|
||||
# data = js_data.toVariant()
|
||||
# return self.has_heterogeneous_masterkeys(data)
|
||||
#
|
||||
|
||||
def create_storage(self, single_password: str = None):
|
||||
self._logger.info('Creating wallet from wizard data')
|
||||
data = self._current.wizard_data
|
||||
@@ -121,14 +111,12 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
|
||||
|
||||
# minimally populate self after create
|
||||
self._password = data['password']
|
||||
# self.path = path
|
||||
self.path = path
|
||||
|
||||
# self.createSuccess.emit()
|
||||
return True
|
||||
except Exception as e:
|
||||
self._logger.error(f"createStorage errored: {e!r}")
|
||||
return False
|
||||
# self.createError.emit(str(e))
|
||||
|
||||
|
||||
class WCWalletName(WizardComponent):
|
||||
@@ -349,7 +337,7 @@ class WCConfirmSeed(WizardComponent):
|
||||
pass
|
||||
|
||||
|
||||
class WCCreateExt(WizardComponent):
|
||||
class WCEnterExt(WizardComponent):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('Seed Extension'))
|
||||
|
||||
@@ -490,7 +478,7 @@ class WCBIP39Refine(WizardComponent):
|
||||
|
||||
if self.wizard_data['wallet_type'] == 'multisig':
|
||||
choices = [
|
||||
# TODO: 'standard' is a backend wallet concept, wizard wants 'p2sh'
|
||||
# TODO: nicer to refactor 'standard' to 'p2sh', but backend wallet still uses 'standard'
|
||||
('standard', 'legacy multisig (p2sh)', normalize_bip32_derivation("m/45'/0")),
|
||||
('p2wsh-p2sh', 'p2sh-segwit multisig (p2wsh-p2sh)', purpose48_derivation(0, xtype='p2wsh-p2sh')),
|
||||
('p2wsh', 'native segwit multisig (p2wsh)', purpose48_derivation(0, xtype='p2wsh')),
|
||||
@@ -508,24 +496,27 @@ class WCBIP39Refine(WizardComponent):
|
||||
else:
|
||||
default_choice_idx = 2
|
||||
choices = [
|
||||
# TODO: 'standard' is a backend wallet concept, wizard wants 'p2pkh'
|
||||
# TODO: nicer to refactor 'standard' to 'p2pkh', but backend wallet still uses 'standard'
|
||||
('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 self.wizard_data['wallet_type'] == 'standard':
|
||||
button = QPushButton(_("Detect Existing Accounts"))
|
||||
|
||||
passphrase = self.wizard_data['seed_extra_words'] if self.wizard_data['seed_extend'] else ''
|
||||
if self.wizard_data['seed_variant'] == 'bip39':
|
||||
root_seed = bip39_to_seed(self.wizard_data['seed'], passphrase)
|
||||
elif self.wizard_data['seed_variant'] == 'slip39':
|
||||
root_seed = self.wizard_data['seed'].decrypt(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
|
||||
|
||||
def on_account_select(account):
|
||||
script_type = account["script_type"]
|
||||
if script_type == "p2pkh":
|
||||
@@ -561,7 +552,14 @@ class WCBIP39Refine(WizardComponent):
|
||||
|
||||
def validate(self):
|
||||
self.apply()
|
||||
derivation_valid = is_bip32_derivation(self.wizard_data['derivation_path'])
|
||||
|
||||
wizard_data = self.wizard_data
|
||||
if self.wizard_data['wallet_type'] == 'multisig' and 'multisig_current_cosigner' in self.wizard_data:
|
||||
cosigner = self.wizard_data['multisig_current_cosigner']
|
||||
if cosigner != 0:
|
||||
wizard_data = self.wizard_data['multisig_cosigner_data'][str(cosigner)]
|
||||
|
||||
derivation_valid = is_bip32_derivation(wizard_data['derivation_path'])
|
||||
|
||||
if self.wizard_data['wallet_type'] == 'multisig':
|
||||
if self.wizard.has_duplicate_masterkeys(self.wizard_data):
|
||||
|
||||
@@ -4,7 +4,7 @@ import os
|
||||
from typing import List, NamedTuple, Any, Dict, Optional
|
||||
|
||||
from electrum.logging import get_logger
|
||||
from electrum.slip39 import Slip39Error, decode_mnemonic
|
||||
from electrum.slip39 import EncryptedSeed
|
||||
from electrum.storage import WalletStorage, StorageEncryptionVersion
|
||||
from electrum.wallet_db import WalletDB
|
||||
from electrum.bip32 import normalize_bip32_derivation, xpub_type
|
||||
@@ -252,6 +252,9 @@ class NewWalletWizard(AbstractWizard):
|
||||
def is_bip39_seed(self, wizard_data):
|
||||
return wizard_data.get('seed_variant') == 'bip39'
|
||||
|
||||
def is_slip39_seed(self, wizard_data):
|
||||
return wizard_data.get('seed_variant') == 'slip39'
|
||||
|
||||
def is_multisig(self, wizard_data):
|
||||
return wizard_data['wallet_type'] == 'multisig'
|
||||
|
||||
@@ -275,6 +278,8 @@ class NewWalletWizard(AbstractWizard):
|
||||
def on_have_or_confirm_seed(self, wizard_data):
|
||||
if self.is_bip39_seed(wizard_data):
|
||||
return 'bip39_refine'
|
||||
elif self.is_slip39_seed(wizard_data):
|
||||
return 'bip39_refine'
|
||||
elif self.is_multisig(wizard_data):
|
||||
return 'multisig_cosigner_keystore'
|
||||
else:
|
||||
@@ -282,7 +287,7 @@ class NewWalletWizard(AbstractWizard):
|
||||
|
||||
def maybe_master_pubkey(self, wizard_data):
|
||||
self._logger.debug('maybe_master_pubkey')
|
||||
if self.is_bip39_seed(wizard_data) and 'derivation_path' not in wizard_data:
|
||||
if (self.is_bip39_seed(wizard_data) or self.is_slip39_seed(wizard_data)) and 'derivation_path' not in wizard_data:
|
||||
self._logger.debug('deferred, missing derivation_path')
|
||||
return
|
||||
|
||||
@@ -361,6 +366,14 @@ class NewWalletWizard(AbstractWizard):
|
||||
else:
|
||||
script = data['script_type'] if data['script_type'] != 'p2pkh' else 'standard'
|
||||
return keystore.from_bip43_rootseed(root_seed, derivation, xtype=script)
|
||||
elif data['seed_variant'] == 'slip39':
|
||||
root_seed = data['seed'].decrypt(data['seed_extra_words'])
|
||||
derivation = normalize_bip32_derivation(data['derivation_path'])
|
||||
if wallet_type == 'multisig':
|
||||
script = data['script_type'] if data['script_type'] != 'p2sh' else 'standard'
|
||||
else:
|
||||
script = data['script_type'] if data['script_type'] != 'p2pkh' else 'standard'
|
||||
return keystore.from_bip43_rootseed(root_seed, derivation, xtype=script)
|
||||
else:
|
||||
raise Exception('Unsupported seed variant %s' % data['seed_variant'])
|
||||
elif 'master_key' in data:
|
||||
@@ -385,21 +398,20 @@ class NewWalletWizard(AbstractWizard):
|
||||
if is_checksum:
|
||||
seed_type = 'bip39'
|
||||
seed_valid = True
|
||||
elif seed_variant == 'slip39': # TODO: incomplete impl, this code only validates a single share.
|
||||
try:
|
||||
share = decode_mnemonic(seed)
|
||||
elif seed_variant == 'slip39':
|
||||
# seed shares should be already validated by wizard page, we have a combined encrypted seed
|
||||
if seed and isinstance(seed, EncryptedSeed):
|
||||
seed_valid = True
|
||||
seed_type = 'slip39'
|
||||
validation_message = 'SLIP39: share #%d in %dof%d scheme' % (share.group_index, share.group_threshold, share.group_count)
|
||||
except Slip39Error as e:
|
||||
validation_message = 'SLIP39: %s' % str(e)
|
||||
seed_valid = False # for now
|
||||
else:
|
||||
seed_valid = False
|
||||
else:
|
||||
raise Exception(f'unknown seed variant {seed_variant}')
|
||||
|
||||
# check if seed matches wallet type
|
||||
if wallet_type == '2fa' and not is_any_2fa_seed_type(seed_type):
|
||||
seed_valid = False
|
||||
elif wallet_type == 'standard' and seed_type not in ['old', 'standard', 'segwit', 'bip39']:
|
||||
elif wallet_type == 'standard' and seed_type not in ['old', 'standard', 'segwit', 'bip39', 'slip39']:
|
||||
seed_valid = False
|
||||
elif wallet_type == 'multisig' and seed_type not in ['standard', 'segwit', 'bip39']:
|
||||
seed_valid = False
|
||||
@@ -435,9 +447,12 @@ class NewWalletWizard(AbstractWizard):
|
||||
if data['seed_type'] in ['old', 'standard', 'segwit']:
|
||||
self._logger.debug('creating keystore from electrum seed')
|
||||
k = keystore.from_seed(data['seed'], data['seed_extra_words'], data['wallet_type'] == 'multisig')
|
||||
elif data['seed_type'] == 'bip39':
|
||||
self._logger.debug('creating keystore from bip39 seed')
|
||||
root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words'])
|
||||
elif data['seed_type'] in ['bip39', 'slip39']:
|
||||
self._logger.debug('creating keystore from %s seed' % data['seed_type'])
|
||||
if data['seed_type'] == 'bip39':
|
||||
root_seed = keystore.bip39_to_seed(data['seed'], data['seed_extra_words'])
|
||||
else:
|
||||
root_seed = data['seed'].decrypt(data['seed_extra_words'])
|
||||
derivation = normalize_bip32_derivation(data['derivation_path'])
|
||||
if data['wallet_type'] == 'multisig':
|
||||
script = data['script_type'] if data['script_type'] != 'p2sh' else 'standard'
|
||||
|
||||
Reference in New Issue
Block a user