wizard: add derivation passphrase and bip39 support
This commit is contained in:
parent
808703bacb
commit
b907a668ec
24
electrum
24
electrum
@ -88,7 +88,7 @@ from electrum.util import print_msg, print_stderr, json_encode, json_decode
|
|||||||
from electrum.util import set_verbosity, InvalidPassword, check_www_dir
|
from electrum.util import set_verbosity, InvalidPassword, check_www_dir
|
||||||
from electrum.commands import get_parser, known_commands, Commands, config_variables
|
from electrum.commands import get_parser, known_commands, Commands, config_variables
|
||||||
from electrum import daemon
|
from electrum import daemon
|
||||||
from electrum.keystore import from_text, is_private
|
from electrum import keystore
|
||||||
from electrum.mnemonic import Mnemonic
|
from electrum.mnemonic import Mnemonic
|
||||||
|
|
||||||
# get password routine
|
# get password routine
|
||||||
@ -117,13 +117,18 @@ def run_non_RPC(config):
|
|||||||
|
|
||||||
if cmdname == 'restore':
|
if cmdname == 'restore':
|
||||||
text = config.get('text')
|
text = config.get('text')
|
||||||
password = password_dialog() if is_private(text) else None
|
passphrase = config.get('passphrase', '')
|
||||||
try:
|
password = password_dialog() if keystore.is_private(text) else None
|
||||||
k = from_text(text, password)
|
if keystore.is_seed(text):
|
||||||
except BaseException as e:
|
k = keystore.from_seed(text, passphrase, password)
|
||||||
|
elif keystore.is_any_key(text):
|
||||||
|
k = keystore.from_keys(text, password)
|
||||||
|
else:
|
||||||
sys.exit(str(e))
|
sys.exit(str(e))
|
||||||
k.save(storage, 'x/')
|
storage.put('keystore', k.dump())
|
||||||
storage.put('wallet_type', 'standard')
|
storage.put('wallet_type', 'standard')
|
||||||
|
storage.put('use_encryption', bool(password))
|
||||||
|
storage.write()
|
||||||
wallet = Wallet(storage)
|
wallet = Wallet(storage)
|
||||||
if not config.get('offline'):
|
if not config.get('offline'):
|
||||||
network = Network(config)
|
network = Network(config)
|
||||||
@ -139,10 +144,13 @@ def run_non_RPC(config):
|
|||||||
|
|
||||||
elif cmdname == 'create':
|
elif cmdname == 'create':
|
||||||
password = password_dialog()
|
password = password_dialog()
|
||||||
|
passphrase = config.get('passphrase', '')
|
||||||
seed = Mnemonic('en').make_seed()
|
seed = Mnemonic('en').make_seed()
|
||||||
k = from_text(seed, password)
|
k = keystore.from_seed(seed, passphrase, password)
|
||||||
k.save(storage, 'x/')
|
storage.put('keystore', k.dump())
|
||||||
storage.put('wallet_type', 'standard')
|
storage.put('wallet_type', 'standard')
|
||||||
|
storage.put('use_encryption', bool(password))
|
||||||
|
storage.write()
|
||||||
wallet = Wallet(storage)
|
wallet = Wallet(storage)
|
||||||
wallet.synchronize()
|
wallet.synchronize()
|
||||||
print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
|
print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
|
||||||
|
|||||||
@ -28,10 +28,10 @@ MSG_ENTER_SEED_OR_MPK = _("Please enter a seed phrase or a master key (xpub or x
|
|||||||
MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:")
|
MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:")
|
||||||
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\
|
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\
|
||||||
+ _("Leave this field empty if you want to disable encryption.")
|
+ _("Leave this field empty if you want to disable encryption.")
|
||||||
MSG_RESTORE_PASSPHRASE = \
|
MSG_PASSPHRASE = \
|
||||||
_("Please enter the passphrase you used when creating your %s wallet. "
|
_("Please enter your seed derivation passphrase. "
|
||||||
"Note this is NOT a password. Enter nothing if you did not use "
|
"Note: this is NOT your encryption password. "
|
||||||
"one or are unsure.")
|
"Leave this field empty if you did not use one or are unsure.")
|
||||||
|
|
||||||
def clean_text(seed_e):
|
def clean_text(seed_e):
|
||||||
text = unicode(seed_e.toPlainText()).strip()
|
text = unicode(seed_e.toPlainText()).strip()
|
||||||
@ -247,17 +247,39 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
|||||||
def remove_from_recently_open(self, filename):
|
def remove_from_recently_open(self, filename):
|
||||||
self.config.remove_from_recently_open(filename)
|
self.config.remove_from_recently_open(filename)
|
||||||
|
|
||||||
def text_input(self, title, message, is_valid):
|
def text_input_layout(self, title, message, is_valid):
|
||||||
slayout = SeedInputLayout(title=message)
|
slayout = SeedInputLayout(title=message)
|
||||||
def sanitized_seed():
|
slayout.is_valid = is_valid
|
||||||
return clean_text(slayout.seed_edit())
|
slayout.sanitized_text = lambda: clean_text(slayout.seed_edit())
|
||||||
def set_enabled():
|
slayout.set_enabled = lambda: self.next_button.setEnabled(slayout.is_valid(slayout.sanitized_text()))
|
||||||
self.next_button.setEnabled(is_valid(sanitized_seed()))
|
slayout.seed_edit().textChanged.connect(slayout.set_enabled)
|
||||||
slayout.seed_edit().textChanged.connect(set_enabled)
|
return slayout
|
||||||
|
|
||||||
|
def text_input(self, title, message, is_valid):
|
||||||
|
slayout = self.text_input_layout(title, message, is_valid)
|
||||||
self.set_main_layout(slayout.layout(), title, next_enabled=False)
|
self.set_main_layout(slayout.layout(), title, next_enabled=False)
|
||||||
seed = sanitized_seed()
|
seed = slayout.sanitized_text()
|
||||||
return seed
|
return seed
|
||||||
|
|
||||||
|
def seed_input(self, title, message, is_valid, bip39=False):
|
||||||
|
slayout = self.text_input_layout(title, message, is_valid)
|
||||||
|
vbox = QVBoxLayout()
|
||||||
|
vbox.addLayout(slayout.layout())
|
||||||
|
cb_passphrase = QCheckBox(_('Add a passphrase to this seed'))
|
||||||
|
vbox.addWidget(cb_passphrase)
|
||||||
|
cb_bip39 = QCheckBox(_('BIP39/BIP44 seed'))
|
||||||
|
cb_bip39.setVisible(bip39)
|
||||||
|
vbox.addWidget(cb_bip39)
|
||||||
|
def f(b):
|
||||||
|
slayout.is_valid = (lambda x: bool(x)) if b else is_valid
|
||||||
|
slayout.set_enabled()
|
||||||
|
cb_bip39.toggled.connect(f)
|
||||||
|
self.set_main_layout(vbox, title, next_enabled=False)
|
||||||
|
seed = slayout.sanitized_text()
|
||||||
|
add_passphrase = cb_passphrase.isChecked()
|
||||||
|
is_bip39 = cb_bip39.isChecked()
|
||||||
|
return seed, add_passphrase, is_bip39
|
||||||
|
|
||||||
@wizard_dialog
|
@wizard_dialog
|
||||||
def restore_keys_dialog(self, title, message, is_valid, run_next):
|
def restore_keys_dialog(self, title, message, is_valid, run_next):
|
||||||
return self.text_input(title, message, is_valid)
|
return self.text_input(title, message, is_valid)
|
||||||
@ -275,8 +297,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
|||||||
def restore_seed_dialog(self, run_next, is_valid):
|
def restore_seed_dialog(self, run_next, is_valid):
|
||||||
title = _('Enter Seed')
|
title = _('Enter Seed')
|
||||||
message = _('Please enter your seed phrase in order to restore your wallet.')
|
message = _('Please enter your seed phrase in order to restore your wallet.')
|
||||||
text = self.text_input(title, message, is_valid)
|
return self.seed_input(title, message, is_valid, bip39=True)
|
||||||
return text, False, True
|
|
||||||
|
|
||||||
@wizard_dialog
|
@wizard_dialog
|
||||||
def confirm_seed_dialog(self, run_next, is_valid):
|
def confirm_seed_dialog(self, run_next, is_valid):
|
||||||
@ -286,7 +307,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
|||||||
_('Your seed is important!'),
|
_('Your seed is important!'),
|
||||||
_('To make sure that you have properly saved your seed, please retype it here.')
|
_('To make sure that you have properly saved your seed, please retype it here.')
|
||||||
])
|
])
|
||||||
return self.text_input(title, message, is_valid)
|
return self.seed_input(title, message, is_valid)
|
||||||
|
|
||||||
@wizard_dialog
|
@wizard_dialog
|
||||||
def show_seed_dialog(self, run_next, seed_text):
|
def show_seed_dialog(self, run_next, seed_text):
|
||||||
@ -300,12 +321,11 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
|||||||
return playout.new_password()
|
return playout.new_password()
|
||||||
|
|
||||||
@wizard_dialog
|
@wizard_dialog
|
||||||
def request_passphrase(self, device_text, run_next):
|
def request_passphrase(self, run_next):
|
||||||
"""When restoring a wallet, request the passphrase that was used for
|
"""When restoring a wallet, request the passphrase that was used for
|
||||||
the wallet on the given device and confirm it. Should return
|
the wallet on the given device and confirm it. Should return
|
||||||
a unicode string."""
|
a unicode string."""
|
||||||
phrase = self.pw_layout(MSG_RESTORE_PASSPHRASE % device_text,
|
phrase = self.pw_layout(MSG_PASSPHRASE, PW_PASSPHRASE)
|
||||||
PW_PASSPHRASE)
|
|
||||||
if phrase is None:
|
if phrase is None:
|
||||||
raise UserCancelled
|
raise UserCancelled
|
||||||
return phrase
|
return phrase
|
||||||
|
|||||||
@ -159,10 +159,13 @@ class BaseWizard(object):
|
|||||||
self.restore_keys_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v)
|
self.restore_keys_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v)
|
||||||
|
|
||||||
def on_restore_from_key(self, text):
|
def on_restore_from_key(self, text):
|
||||||
|
def f(password):
|
||||||
|
k = keystore.from_keys(text, password)
|
||||||
|
self.on_keystore(k, password)
|
||||||
if keystore.is_private(text):
|
if keystore.is_private(text):
|
||||||
self.add_password(text)
|
self.run('request_password', run_next=f)
|
||||||
else:
|
else:
|
||||||
self.create_keystore(text, None)
|
f(None)
|
||||||
|
|
||||||
def choose_hw_device(self):
|
def choose_hw_device(self):
|
||||||
title = _('Hardware Keystore')
|
title = _('Hardware Keystore')
|
||||||
@ -221,18 +224,18 @@ class BaseWizard(object):
|
|||||||
self.on_keystore(k, None)
|
self.on_keystore(k, None)
|
||||||
|
|
||||||
def restore_from_seed(self):
|
def restore_from_seed(self):
|
||||||
self.restore_seed_dialog(run_next=self.on_restore_from_seed, is_valid=keystore.is_seed)
|
self.restore_seed_dialog(run_next=self.on_seed, is_valid=keystore.is_seed)
|
||||||
|
|
||||||
def on_restore_from_seed(self, seed, is_bip39, is_passphrase):
|
def on_seed(self, seed, add_passphrase, is_bip39):
|
||||||
self.is_bip39 = is_bip39
|
self.is_bip39 = is_bip39
|
||||||
f = lambda x: self.run('on_passphrase', seed, x)
|
f = lambda x: self.run('on_passphrase', seed, x)
|
||||||
if is_passphrase:
|
if add_passphrase:
|
||||||
self.request_passphrase(self.storage.get('hw_type'), run_next=f)
|
self.request_passphrase(run_next=f)
|
||||||
else:
|
else:
|
||||||
self.run('on_passphrase', seed, '')
|
f('')
|
||||||
|
|
||||||
def on_passphrase(self, seed, passphrase):
|
def on_passphrase(self, seed, passphrase):
|
||||||
f = lambda x: self.run('on_password', seed, passphrase, password)
|
f = lambda x: self.run('on_password', seed, passphrase, x)
|
||||||
self.request_password(run_next=f)
|
self.request_password(run_next=f)
|
||||||
|
|
||||||
def on_password(self, seed, passphrase, password):
|
def on_password(self, seed, passphrase, password):
|
||||||
@ -240,15 +243,14 @@ class BaseWizard(object):
|
|||||||
f = lambda account_id: self.run('on_bip44', seed, passphrase, password, account_id)
|
f = lambda account_id: self.run('on_bip44', seed, passphrase, password, account_id)
|
||||||
self.account_id_dialog(run_next=f)
|
self.account_id_dialog(run_next=f)
|
||||||
else:
|
else:
|
||||||
self.create_keystore(seed, passphrase, password)
|
k = keystore.from_seed(seed, passphrase, password)
|
||||||
|
self.on_keystore(k, password)
|
||||||
|
|
||||||
def on_bip44(self, seed, passphrase, password, account_id):
|
def on_bip44(self, seed, passphrase, password, account_id):
|
||||||
import keystore
|
import keystore
|
||||||
k = keystore.BIP32_KeyStore()
|
k = keystore.BIP32_KeyStore({})
|
||||||
k.add_seed(seed, password)
|
|
||||||
bip32_seed = keystore.bip39_to_seed(seed, passphrase)
|
bip32_seed = keystore.bip39_to_seed(seed, passphrase)
|
||||||
derivation = "m/44'/0'/%d'"%account_id
|
derivation = "m/44'/0'/%d'"%account_id
|
||||||
self.storage.put('account_id', account_id)
|
|
||||||
k.add_xprv_from_seed(bip32_seed, derivation, password)
|
k.add_xprv_from_seed(bip32_seed, derivation, password)
|
||||||
self.on_keystore(k, password)
|
self.on_keystore(k, password)
|
||||||
|
|
||||||
@ -283,7 +285,7 @@ class BaseWizard(object):
|
|||||||
self.add_cosigner_dialog(run_next=lambda x: self.on_cosigner(x, password, i), index=i, is_valid=keystore.is_xpub)
|
self.add_cosigner_dialog(run_next=lambda x: self.on_cosigner(x, password, i), index=i, is_valid=keystore.is_xpub)
|
||||||
|
|
||||||
def on_cosigner(self, text, password, i):
|
def on_cosigner(self, text, password, i):
|
||||||
k = keystore.from_text(text, password)
|
k = keystore.from_keys(text, password)
|
||||||
self.on_keystore(k)
|
self.on_keystore(k)
|
||||||
|
|
||||||
def create_seed(self):
|
def create_seed(self):
|
||||||
@ -292,15 +294,7 @@ class BaseWizard(object):
|
|||||||
self.show_seed_dialog(run_next=self.confirm_seed, seed_text=seed)
|
self.show_seed_dialog(run_next=self.confirm_seed, seed_text=seed)
|
||||||
|
|
||||||
def confirm_seed(self, seed):
|
def confirm_seed(self, seed):
|
||||||
self.confirm_seed_dialog(run_next=self.add_password, is_valid=lambda x: x==seed)
|
self.confirm_seed_dialog(run_next=self.on_seed, is_valid=lambda x: x==seed)
|
||||||
|
|
||||||
def add_password(self, text):
|
|
||||||
f = lambda pw: self.run('create_keystore', text, pw)
|
|
||||||
self.request_password(run_next=f)
|
|
||||||
|
|
||||||
def create_keystore(self, text, password):
|
|
||||||
k = keystore.from_text(text, password)
|
|
||||||
self.on_keystore(k, password)
|
|
||||||
|
|
||||||
def create_addresses(self):
|
def create_addresses(self):
|
||||||
def task():
|
def task():
|
||||||
|
|||||||
@ -623,14 +623,14 @@ is_bip32_key = lambda x: is_xprv(x) or is_xpub(x)
|
|||||||
def bip44_derivation(account_id):
|
def bip44_derivation(account_id):
|
||||||
return "m/44'/0'/%d'"% int(account_id)
|
return "m/44'/0'/%d'"% int(account_id)
|
||||||
|
|
||||||
def from_seed(seed, password):
|
def from_seed(seed, passphrase, password):
|
||||||
if is_old_seed(seed):
|
if is_old_seed(seed):
|
||||||
keystore = Old_KeyStore({})
|
keystore = Old_KeyStore({})
|
||||||
keystore.add_seed(seed, password)
|
keystore.add_seed(seed, password)
|
||||||
elif is_new_seed(seed):
|
elif is_new_seed(seed):
|
||||||
keystore = BIP32_KeyStore({})
|
keystore = BIP32_KeyStore({})
|
||||||
keystore.add_seed(seed, password)
|
keystore.add_seed(seed, password)
|
||||||
bip32_seed = Mnemonic.mnemonic_to_seed(seed, '')
|
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
|
||||||
keystore.add_xprv_from_seed(bip32_seed, "m/", password)
|
keystore.add_xprv_from_seed(bip32_seed, "m/", password)
|
||||||
return keystore
|
return keystore
|
||||||
|
|
||||||
@ -663,12 +663,12 @@ def xprv_from_seed(seed, password):
|
|||||||
xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed, ''))
|
xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed, ''))
|
||||||
return from_xprv(xprv, password)
|
return from_xprv(xprv, password)
|
||||||
|
|
||||||
def xpub_from_seed(seed):
|
def xpub_from_seed(seed, passphrase):
|
||||||
# store only master xpub
|
# store only master xpub
|
||||||
xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed,''))
|
xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed,''))
|
||||||
return from_xpub(xpub)
|
return from_xpub(xpub)
|
||||||
|
|
||||||
def from_text(text, password):
|
def from_keys(text, password):
|
||||||
if is_xprv(text):
|
if is_xprv(text):
|
||||||
k = from_xprv(text, password)
|
k = from_xprv(text, password)
|
||||||
elif is_old_mpk(text):
|
elif is_old_mpk(text):
|
||||||
@ -677,8 +677,6 @@ def from_text(text, password):
|
|||||||
k = from_xpub(text)
|
k = from_xpub(text)
|
||||||
elif is_private_key_list(text):
|
elif is_private_key_list(text):
|
||||||
k = from_private_key_list(text, password)
|
k = from_private_key_list(text, password)
|
||||||
elif is_seed(text):
|
|
||||||
k = from_seed(text, password)
|
|
||||||
else:
|
else:
|
||||||
raise BaseException('Invalid seedphrase or key')
|
raise BaseException('Invalid key')
|
||||||
return k
|
return k
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user