electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit d9021788fae1244cde1374358a91b3cfd55ea548
parent 71de14240d58e4eb721903f755593c45c1e84cbd
Author: ThomasV <thomasv@electrum.org>
Date:   Mon, 15 Aug 2016 11:48:33 +0200

request account_id in wizard, for hardware wallets. cleanup bip44 code

Diffstat:
Mgui/qt/installwizard.py | 12+++++++++---
Mlib/base_wizard.py | 34++++++++++++++++++++++++++++++----
Mlib/keystore.py | 98++++++++++++++++---------------------------------------------------------------
Mplugins/trezor/clientbase.py | 4++--
Mplugins/trezor/plugin.py | 5++---
5 files changed, 62 insertions(+), 91 deletions(-)

diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py @@ -372,14 +372,20 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): return action @wizard_dialog - def input_dialog(self, title, message, run_next): + def account_id_dialog(self, run_next): + message = '\n'.join([ + _('Enter your account number here.'), + _('If you are not sure what this is, leave this field to zero.') + ]) + default = '0' + title = _('Account Number') line = QLineEdit() + line.setText(default) vbox = QVBoxLayout() vbox.addWidget(QLabel(message)) vbox.addWidget(line) self.set_main_layout(vbox, title) - action = line.text() - return action + return int(line.text()) @wizard_dialog def show_xpub_dialog(self, xpub, run_next): diff --git a/lib/base_wizard.py b/lib/base_wizard.py @@ -173,16 +173,42 @@ class BaseWizard(object): self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run) def on_hardware_device(self): + f = lambda x: self.run('on_hardware_account_id', x) + self.account_id_dialog(run_next=f) + + def on_hardware_account_id(self, account_id): from keystore import load_keystore + self.storage.put('account_id', int(account_id)) keystore = load_keystore(self.storage, None) keystore.plugin.on_create_wallet(keystore, self) def on_hardware_seed(self): - from keystore import load_keystore self.storage.put('key_type', 'hw_seed') - keystore = load_keystore(self.storage, None) - self.plugin = keystore #fixme .plugin - keystore.on_restore_wallet(self) + is_valid = lambda x: True #fixme: bip39 + f = lambda seed: self.run('on_bip39_seed', seed) + self.restore_seed_dialog(run_next=f, is_valid=is_valid) + + def on_bip_39_seed(self, seed): + f = lambda passphrase: self.run('on_bip39_passphrase', seed, passphrase) + self.request_passphrase(self.storage.get('hw_type'), run_next=f) + + def on_bip39_passphrase(self, seed, passphrase): + f = lambda account_id: self.run('on_bip44_account_id', seed, passphrase, account_id) + self.account_id_dialog(run_next=f) + + def on_bip44_account_id(self, seed, passphrase, account_id): + f = lambda pw: self.run('on_bip44', seed, passphrase, account_id, pw) + self.request_password(run_next=f) + + def on_bip44(self, seed, passphrase, account_id, password): + import keystore + k = keystore.BIP32_KeyStore() + k.add_seed(seed, password) + bip32_seed = keystore.bip39_to_seed(seed, passphrase) + derivation = "m/44'/0'/%d'"%account_id + self.storage.put('account_id', account_id) + k.add_xprv_from_seed(bip32_seed, derivation, password) + k.save(self.storage, 'x/') self.wallet = Standard_Wallet(self.storage) self.run('create_addresses') diff --git a/lib/keystore.py b/lib/keystore.py @@ -216,7 +216,6 @@ class Xpub: class BIP32_KeyStore(Deterministic_KeyStore, Xpub): - root_derivation = "m/" def __init__(self): Xpub.__init__(self) @@ -298,43 +297,19 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub): if keypairs: tx.sign(keypairs) - def derive_xkeys(self, root, derivation, password): - x = self.master_private_keys[root] - root_xprv = pw_decode(x, password) - xprv, xpub = bip32_private_derivation(root_xprv, root, derivation) - return xpub, xprv - def get_mnemonic(self, password): return self.get_seed(password) - def mnemonic_to_seed(self, seed, password): - return Mnemonic.mnemonic_to_seed(seed, password) - - @classmethod - def make_seed(self, lang=None): - return Mnemonic(lang).make_seed() - - #@classmethod - #def address_derivation(self, account_id, change, address_index): - # account_derivation = self.account_derivation(account_id) - # return "%s/%d/%d" % (account_derivation, change, address_index) - - #def address_id(self, address): - # acc_id, (change, address_index) = self.get_address_index(address) - # return self.address_derivation(acc_id, change, address_index) - - def add_seed_and_xprv(self, seed, password, passphrase=''): - xprv, xpub = bip32_root(self.mnemonic_to_seed(seed, passphrase)) - xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation) - self.add_seed(seed, password) - self.add_master_private_key(xprv, password) - self.add_master_public_key(xpub) - def add_xprv(self, xprv, password): xpub = bitcoin.xpub_from_xprv(xprv) self.add_master_private_key(xprv, password) self.add_master_public_key(xpub) + def add_xprv_from_seed(self, bip32_seed, derivation, password): + xprv, xpub = bip32_root(bip32_seed) + xprv, xpub = bip32_private_derivation(xprv, "m/", derivation) + self.add_xprv(xprv, password) + def can_sign(self, xpub): return xpub == self.xpub and self.xprv is not None @@ -533,54 +508,19 @@ class Hardware_KeyStore(KeyStore, Xpub): def can_change_password(self): return False - def derive_xkeys(self, root, derivation, password): - if self.master_public_keys.get(self.root_name): - return BIP44_wallet.derive_xkeys(self, root, derivation, password) - # When creating a wallet we need to ask the device for the - # master public key - xpub = self.get_public_key(derivation) - return xpub, None - -class BIP44_KeyStore(BIP32_KeyStore): - root_derivation = "m/44'/0'/0'" - @classmethod - def normalize_passphrase(self, passphrase): - return normalize('NFKD', unicode(passphrase or '')) - - def is_valid_seed(self, seed): - return True - - def mnemonic_to_seed(self, mnemonic, passphrase): - # See BIP39 - import pbkdf2, hashlib, hmac - PBKDF2_ROUNDS = 2048 - mnemonic = normalize('NFKD', ' '.join(mnemonic.split())) - passphrase = self.normalize_passphrase(passphrase) - return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, - iterations = PBKDF2_ROUNDS, macmodule = hmac, - digestmodule = hashlib.sha512).read(64) +def bip39_normalize_passphrase(passphrase): + return normalize('NFKD', unicode(passphrase or '')) - def on_restore_wallet(self, wizard): - #assert isinstance(keystore, self.keystore_class) - #msg = _("Enter the seed for your %s wallet:" % self.device) - #title=_('Restore hardware wallet'), - f = lambda seed: wizard.run('on_restore_seed', seed) - wizard.restore_seed_dialog(run_next=f, is_valid=self.is_valid_seed) - - def on_restore_seed(self, wizard, seed): - f = lambda passphrase: wizard.run('on_restore_passphrase', seed, passphrase) - self.device = '' - wizard.request_passphrase(self.device, run_next=f) - - def on_restore_passphrase(self, wizard, seed, passphrase): - f = lambda pw: wizard.run('on_restore_password', seed, passphrase, pw) - wizard.request_password(run_next=f) - - def on_restore_password(self, wizard, seed, passphrase, password): - self.add_seed_and_xprv(seed, password, passphrase) - self.save(wizard.storage, 'x/') +def bip39_to_seed(mnemonic, passphrase): + import pbkdf2, hashlib, hmac + PBKDF2_ROUNDS = 2048 + mnemonic = normalize('NFKD', ' '.join(mnemonic.split())) + passphrase = bip39_normalize_passphrase(passphrase) + return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, + iterations = PBKDF2_ROUNDS, macmodule = hmac, + digestmodule = hashlib.sha512).read(64) @@ -596,7 +536,7 @@ def load_keystore(storage, name): k = Imported_KeyStore() elif name and name not in [ 'x/', 'x1/' ]: k = BIP32_KeyStore() - elif t == 'seed': + elif t in ['seed', 'hw_seed']: k = BIP32_KeyStore() elif t == 'hardware': hw_type = storage.get('hardware_type') @@ -606,8 +546,6 @@ def load_keystore(storage, name): break else: raise BaseException('unknown hardware type') - elif t == 'hw_seed': - k = BIP44_KeyStore() else: raise BaseException('unknown wallet type', t) k.load(storage, name) @@ -665,7 +603,9 @@ def from_seed(seed, password): keystore.add_seed(seed, password) elif is_new_seed(seed): keystore = BIP32_KeyStore() - keystore.add_seed_and_xprv(seed, password) + keystore.add_seed(seed, password) + bip32_seed = Mnemonic.mnemonic_to_seed(seed, '') + keystore.add_xprv_from_seed(bip32_seed, "m/", password) return keystore def from_private_key_list(text, password): diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py @@ -3,7 +3,7 @@ from struct import pack from electrum.i18n import _ from electrum.util import PrintError, UserCancelled -from electrum.keystore import BIP44_KeyStore +from electrum.keystore import bip39_normalize_passphrase from electrum.bitcoin import EncodeBase58Check @@ -65,7 +65,7 @@ class GuiMixin(object): passphrase = self.handler.get_passphrase(msg, self.creating_wallet) if passphrase is None: return self.proto.Cancel() - passphrase = BIP44_KeyStore.normalize_passphrase(passphrase) + passphrase = bip39_normalize_passphrase(passphrase) return self.proto.PassphraseAck(passphrase=passphrase) def callback_WordRequest(self, msg): diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py @@ -22,14 +22,13 @@ from ..hw_wallet import HW_PluginBase TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4) class TrezorCompatibleKeyStore(Hardware_KeyStore): - root = "m/44'/0'" - account_id = 0 def load(self, storage, name): self.xpub = storage.get('master_public_keys', {}).get(name) + self.account_id = storage.get('account_id') def get_derivation(self): - return self.root + "/%d'"%self.account_id + return "m/44'/0'/%d'"%self.account_id def get_client(self, force_pair=True): return self.plugin.get_client(self, force_pair)