Browse Source

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.
master
Neil Booth 10 years ago
parent
commit
9d9fcbde64
  1. 51
      lib/wallet.py
  2. 80
      plugins/trezor/plugin.py
  3. 9
      plugins/trezor/qt_generic.py

51
lib/wallet.py

@ -26,6 +26,7 @@ import json
import copy import copy
import re import re
from functools import partial from functools import partial
from unicodedata import normalize
from i18n import _ from i18n import _
from util import NotEnoughFunds, PrintError, profiler from util import NotEnoughFunds, PrintError, profiler
@ -1642,6 +1643,7 @@ class BIP32_Simple_Wallet(BIP32_Wallet):
class BIP32_HD_Wallet(BIP32_Wallet): class BIP32_HD_Wallet(BIP32_Wallet):
# wallet that can create accounts # wallet that can create accounts
def __init__(self, storage): def __init__(self, storage):
BIP32_Wallet.__init__(self, storage) BIP32_Wallet.__init__(self, storage)
@ -1706,6 +1708,49 @@ class BIP32_HD_Wallet(BIP32_Wallet):
def accounts_all_used(self): def accounts_all_used(self):
return all(self.account_is_used(acc_id) for acc_id in self.accounts) 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): class NewWallet(BIP32_Wallet, Mnemonic):
# Standard wallet # Standard wallet
@ -1836,7 +1881,8 @@ wallet_types = [
('standard', 'standard', ("Standard wallet"), NewWallet), ('standard', 'standard', ("Standard wallet"), NewWallet),
('standard', 'imported', ("Imported wallet"), Imported_Wallet), ('standard', 'imported', ("Imported wallet"), Imported_Wallet),
('multisig', '2of2', ("Multisig wallet (2 of 2)"), Multisig_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 # former WalletFactory
@ -1846,7 +1892,6 @@ class Wallet(object):
type when passed a WalletStorage instance.""" type when passed a WalletStorage instance."""
def __new__(self, storage): def __new__(self, storage):
seed_version = storage.get('seed_version') seed_version = storage.get('seed_version')
if not seed_version: if not seed_version:
seed_version = OLD_SEED_VERSION if len(storage.get('master_public_key','')) == 128 else NEW_SEED_VERSION seed_version = OLD_SEED_VERSION if len(storage.get('master_public_key','')) == 128 else NEW_SEED_VERSION
@ -1880,7 +1925,7 @@ class Wallet(object):
if re.match('(\d+)of(\d+)', wallet_type): if re.match('(\d+)of(\d+)', wallet_type):
WalletClass = Multisig_Wallet WalletClass = Multisig_Wallet
else: else:
raise BaseException('unknown wallet type', wallet_type) raise RuntimeError("Unknown wallet type: " + wallet_type)
else: else:
if seed_version == OLD_SEED_VERSION: if seed_version == OLD_SEED_VERSION:
WalletClass = OldWallet WalletClass = OldWallet

80
plugins/trezor/plugin.py

@ -1,29 +1,23 @@
import re import re
from binascii import unhexlify from binascii import unhexlify
from struct import pack
from unicodedata import normalize
from electrum.account import BIP32_Account from electrum.account import BIP32_Account
from electrum.bitcoin import (bc_address_to_hash_160, xpub_from_pubkey, from electrum.bitcoin import bc_address_to_hash_160, xpub_from_pubkey
bip32_private_derivation, EncodeBase58Check)
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugins import BasePlugin, hook from electrum.plugins import BasePlugin, hook
from electrum.transaction import (deserialize, is_extended_pubkey, from electrum.transaction import (deserialize, is_extended_pubkey,
Transaction, x_to_xpub) Transaction, x_to_xpub)
from electrum.wallet import BIP32_HD_Wallet from electrum.wallet import BIP44_Wallet
class TrezorCompatibleWallet(BIP32_HD_Wallet): class TrezorCompatibleWallet(BIP44_Wallet):
# A BIP32 hierarchical deterministic wallet # Extend BIP44 Wallet as required by hardware implementation.
#
# Derived classes must set: # Derived classes must set:
# - device # - device
# - wallet_type # - wallet_type
restore_wallet_class = BIP44_Wallet
root_derivation = "m/44'/0'"
def __init__(self, storage): def __init__(self, storage):
BIP32_HD_Wallet.__init__(self, storage) BIP44_Wallet.__init__(self, storage)
self.mpk = None
self.checked_device = False self.checked_device = False
self.proper_device = False self.proper_device = False
@ -31,80 +25,18 @@ class TrezorCompatibleWallet(BIP32_HD_Wallet):
self.print_error(message) self.print_error(message)
raise Exception(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): def can_export(self):
return False return False
def is_watching_only(self): def is_watching_only(self):
return self.checked_device and not self.proper_device return self.checked_device and not self.proper_device
def can_create_accounts(self):
return True
def can_change_password(self): def can_change_password(self):
return False return False
def get_client(self): def get_client(self):
return self.plugin.get_client() 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): def decrypt_message(self, pubkey, message, password):
raise RuntimeError(_('Decrypt method is not implemented')) raise RuntimeError(_('Decrypt method is not implemented'))

9
plugins/trezor/qt_generic.py

@ -135,8 +135,11 @@ class QtPlugin(TrezorPlugin):
None, func=lambda x: True) None, func=lambda x: True)
if not seed: if not seed:
return return
wallet = self.wallet_class(storage) # Restored wallets are not hardware wallets
self.wallet = wallet 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) handler = self.create_handler(wizard)
msg = "\n".join([_("Please enter your %s passphrase.") % self.device, msg = "\n".join([_("Please enter your %s passphrase.") % self.device,
_("Press OK if you do not use one.")]) _("Press OK if you do not use one.")])
@ -147,8 +150,6 @@ class QtPlugin(TrezorPlugin):
wallet.add_seed(seed, password) wallet.add_seed(seed, password)
wallet.add_cosigner_seed(seed, 'x/', password, passphrase) wallet.add_cosigner_seed(seed, 'x/', password, passphrase)
wallet.create_main_account(password) wallet.create_main_account(password)
# disable plugin as this is a free-standing wallet
self.set_enabled(False)
return wallet return wallet
@hook @hook

Loading…
Cancel
Save