tProper treatment of restored hardware wallets - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 9d9fcbde647316d4fe178fe2c5df58c61be26c02 DIR parent b50ace4225ce3516172ab61dbe5bc8582ccffa70 HTML Author: Neil Booth <kyuupichan@gmail.com> Date: Mon, 28 Dec 2015 20:24:15 +0900 Proper treatment of restored hardware wallets They have a different wallet type; they require no plugin nor plugin libraries to use. Remove pointless public key code that was implemented in the base classes already. Partial fix for #1592. Unfortunately the plugin and library are still required to actually restore the wallet, but for no reason that isn't fixable. Diffstat: M lib/wallet.py | 51 +++++++++++++++++++++++++++++-- M plugins/trezor/plugin.py | 80 +++---------------------------- M plugins/trezor/qt_generic.py | 9 +++++---- 3 files changed, 59 insertions(+), 81 deletions(-) --- DIR diff --git a/lib/wallet.py b/lib/wallet.py t@@ -26,6 +26,7 @@ import json import copy import re from functools import partial +from unicodedata import normalize from i18n import _ from util import NotEnoughFunds, PrintError, profiler t@@ -1642,6 +1643,7 @@ class BIP32_Simple_Wallet(BIP32_Wallet): class BIP32_HD_Wallet(BIP32_Wallet): + # wallet that can create accounts def __init__(self, storage): BIP32_Wallet.__init__(self, storage) t@@ -1706,6 +1708,49 @@ class BIP32_HD_Wallet(BIP32_Wallet): def accounts_all_used(self): return all(self.account_is_used(acc_id) for acc_id in self.accounts) +class BIP44_Wallet(BIP32_HD_Wallet): + root_derivation = "m/44'/0'" + wallet_type = 'bip44' + + def can_sign_xpubkey(self, x_pubkey): + xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey) + return xpub in self.master_public_keys.values() + + def can_create_accounts(self): + return not self.is_watching_only() + + def prefix(self): + return "/".join(self.root_derivation.split("/")[1:]) + + def account_derivation(self, account_id): + return self.prefix() + "/" + account_id + "'" + + def address_id(self, address): + acc_id, (change, address_index) = self.get_address_index(address) + account_derivation = self.account_derivation(acc_id) + return "%s/%d/%d" % (account_derivation, change, address_index) + + def mnemonic_to_seed(self, mnemonic, passphrase): + # See BIP39 + import pbkdf2, hashlib, hmac + PBKDF2_ROUNDS = 2048 + mnemonic = normalize('NFKD', ' '.join(mnemonic.split())) + passphrase = normalize('NFKD', passphrase) + return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, + iterations = PBKDF2_ROUNDS, macmodule = hmac, + digestmodule = hashlib.sha512).read(64) + + def derive_xkeys(self, root, derivation, password): + x = self.master_private_keys.get(root) + if x: + root_xprv = pw_decode(x, password) + xprv, xpub = bip32_private_derivation(root_xprv, root, derivation) + return xpub, xprv + else: + root_xpub = self.master_public_keys.get(root) + xpub = bip32_public_derivation(root_xub, root, derivation) + return xpub, None + class NewWallet(BIP32_Wallet, Mnemonic): # Standard wallet t@@ -1836,7 +1881,8 @@ wallet_types = [ ('standard', 'standard', ("Standard wallet"), NewWallet), ('standard', 'imported', ("Imported wallet"), Imported_Wallet), ('multisig', '2of2', ("Multisig wallet (2 of 2)"), Multisig_Wallet), - ('multisig', '2of3', ("Multisig wallet (2 of 3)"), Multisig_Wallet) + ('multisig', '2of3', ("Multisig wallet (2 of 3)"), Multisig_Wallet), + ('bip44', 'bip44', ("Restored hardware wallet"), BIP44_Wallet), ] # former WalletFactory t@@ -1846,7 +1892,6 @@ class Wallet(object): type when passed a WalletStorage instance.""" def __new__(self, storage): - seed_version = storage.get('seed_version') if not seed_version: seed_version = OLD_SEED_VERSION if len(storage.get('master_public_key','')) == 128 else NEW_SEED_VERSION t@@ -1880,7 +1925,7 @@ class Wallet(object): if re.match('(\d+)of(\d+)', wallet_type): WalletClass = Multisig_Wallet else: - raise BaseException('unknown wallet type', wallet_type) + raise RuntimeError("Unknown wallet type: " + wallet_type) else: if seed_version == OLD_SEED_VERSION: WalletClass = OldWallet DIR diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py t@@ -1,29 +1,23 @@ import re from binascii import unhexlify -from struct import pack -from unicodedata import normalize from electrum.account import BIP32_Account -from electrum.bitcoin import (bc_address_to_hash_160, xpub_from_pubkey, - bip32_private_derivation, EncodeBase58Check) +from electrum.bitcoin import bc_address_to_hash_160, xpub_from_pubkey from electrum.i18n import _ from electrum.plugins import BasePlugin, hook from electrum.transaction import (deserialize, is_extended_pubkey, Transaction, x_to_xpub) -from electrum.wallet import BIP32_HD_Wallet +from electrum.wallet import BIP44_Wallet -class TrezorCompatibleWallet(BIP32_HD_Wallet): - # A BIP32 hierarchical deterministic wallet - # +class TrezorCompatibleWallet(BIP44_Wallet): + # Extend BIP44 Wallet as required by hardware implementation. # Derived classes must set: # - device # - wallet_type - - root_derivation = "m/44'/0'" + restore_wallet_class = BIP44_Wallet def __init__(self, storage): - BIP32_HD_Wallet.__init__(self, storage) - self.mpk = None + BIP44_Wallet.__init__(self, storage) self.checked_device = False self.proper_device = False t@@ -31,80 +25,18 @@ class TrezorCompatibleWallet(BIP32_HD_Wallet): self.print_error(message) raise Exception(message) - def get_action(self): - if not self.accounts: - return 'create_accounts' - - def can_import(self): - return False - - def can_sign_xpubkey(self, x_pubkey): - xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey) - return xpub in self.master_public_keys.values() - def can_export(self): return False def is_watching_only(self): return self.checked_device and not self.proper_device - def can_create_accounts(self): - return True - def can_change_password(self): return False def get_client(self): return self.plugin.get_client() - def prefix(self): - return "/".join(self.root_derivation.split("/")[1:]) - - def account_derivation(self, account_id): - return self.prefix() + "/" + account_id + "'" - - def address_id(self, address): - acc_id, (change, address_index) = self.get_address_index(address) - account_derivation = self.account_derivation(acc_id) - return "%s/%d/%d" % (account_derivation, change, address_index) - - def mnemonic_to_seed(self, mnemonic, passphrase): - # trezor uses bip39 - import pbkdf2, hashlib, hmac - PBKDF2_ROUNDS = 2048 - mnemonic = normalize('NFKD', ' '.join(mnemonic.split())) - passphrase = normalize('NFKD', passphrase) - return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase, - iterations = PBKDF2_ROUNDS, macmodule = hmac, - digestmodule = hashlib.sha512).read(64) - - def derive_xkeys(self, root, derivation, password): - x = self.master_private_keys.get(root) - if x: - root_xprv = pw_decode(x, password) - xprv, xpub = bip32_private_derivation(root_xprv, root, derivation) - return xpub, xprv - else: - derivation = derivation.replace(self.root_name, self.prefix()+"/") - xpub = self.get_public_key(derivation) - return xpub, None - - def get_public_key(self, bip32_path): - address_n = self.get_client().expand_path(bip32_path) - node = self.get_client().get_public_node(address_n).node - xpub = ("0488B21E".decode('hex') + chr(node.depth) - + self.i4b(node.fingerprint) + self.i4b(node.child_num) - + node.chain_code + node.public_key) - return EncodeBase58Check(xpub) - - def get_master_public_key(self): - if not self.mpk: - self.mpk = self.get_public_key(self.prefix()) - return self.mpk - - def i4b(self, x): - return pack('>I', x) - def decrypt_message(self, pubkey, message, password): raise RuntimeError(_('Decrypt method is not implemented')) DIR diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py t@@ -135,8 +135,11 @@ class QtPlugin(TrezorPlugin): None, func=lambda x: True) if not seed: return - wallet = self.wallet_class(storage) - self.wallet = wallet + # Restored wallets are not hardware wallets + wallet_class = self.wallet_class.restore_wallet_class + storage.put('wallet_type', wallet_class.wallet_type) + self.wallet = wallet = wallet_class(storage) + handler = self.create_handler(wizard) msg = "\n".join([_("Please enter your %s passphrase.") % self.device, _("Press OK if you do not use one.")]) t@@ -147,8 +150,6 @@ class QtPlugin(TrezorPlugin): wallet.add_seed(seed, password) wallet.add_cosigner_seed(seed, 'x/', password, passphrase) wallet.create_main_account(password) - # disable plugin as this is a free-standing wallet - self.set_enabled(False) return wallet @hook