electrum

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

commit 33e57fe5a79c1b83aa0f21087eab9d69f36b09e7
parent 42719cd0a9df28b62fe82bb711e9145666ae000d
Author: Neil Booth <kyuupichan@gmail.com>
Date:   Sat, 26 Dec 2015 19:42:57 +0900

Share non-GUI plugin parts of KeepKey and Trezor

Diffstat:
Mplugins/keepkey/keepkey.py | 211++++---------------------------------------------------------------------------
Aplugins/trezor/plugin_generic.py | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplugins/trezor/trezor.py | 202+++++--------------------------------------------------------------------------
3 files changed, 227 insertions(+), 392 deletions(-)

diff --git a/plugins/keepkey/keepkey.py b/plugins/keepkey/keepkey.py @@ -1,19 +1,9 @@ -from binascii import unhexlify - -import electrum -from electrum import bitcoin - -from electrum.account import BIP32_Account -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 from electrum.wallet import BIP32_Hardware_Wallet -from electrum.util import print_error + from plugins.trezor.gui_mixin import GuiMixin +from plugins.trezor.plugin_generic import TrezorCompatiblePlugin try: - from keepkeylib.client import types from keepkeylib.client import proto, BaseClient, ProtocolMixin from keepkeylib.transport import ConnectionError from keepkeylib.transport_hid import HidTransport @@ -21,12 +11,6 @@ try: except ImportError: KEEPKEY = False -import keepkeylib.ckd_public as ckd_public - -def give_error(message): - print_error(message) - raise Exception(message) - class KeepKeyWallet(BIP32_Hardware_Wallet): wallet_type = 'keepkey' @@ -34,50 +18,18 @@ class KeepKeyWallet(BIP32_Hardware_Wallet): device = 'KeepKey' -class KeepKeyPlugin(BasePlugin): +class KeepKeyPlugin(TrezorCompatiblePlugin): + wallet_type = 'keepkey' + import keepkeylib.ckd_public as ckd_public + from keepkeylib.client import types - def __init__(self, parent, config, name): - BasePlugin.__init__(self, parent, config, name) - self._is_available = self._init() - self.wallet = None - self.handler = None - self.client = None - self.transport = None + @staticmethod + def libraries_available(): + return KEEPKEY def constructor(self, s): return KeepKeyWallet(s) - def _init(self): - return KEEPKEY - - def is_available(self): - if not self._is_available: - return False - if not self.wallet: - return False - if self.wallet.storage.get('wallet_type') != 'keepkey': - return False - return True - - def set_enabled(self, enabled): - self.wallet.storage.put('use_' + self.name, enabled) - - def is_enabled(self): - if not self.is_available(): - return False - if self.wallet.has_seed(): - return False - return True - - def compare_version(self, major, minor=0, patch=0): - features = self.get_client().features - v = [features.major_version, features.minor_version, features.patch_version] - self.print_error('firmware version', v) - return cmp(v, [major, minor, patch]) - - def atleast_version(self, major, minor=0, patch=0): - return self.compare_version(major, minor, patch) >= 0 - def get_client(self): if not KEEPKEY: give_error('please install github.com/keepkey/python-keepkey') @@ -96,151 +48,6 @@ class KeepKeyPlugin(BasePlugin): give_error('Outdated KeepKey firmware. Please update the firmware from https://www.keepkey.com') return self.client - @hook - def close_wallet(self): - print_error("keepkey: clear session") - if self.client: - self.client.clear_session() - self.client.transport.close() - self.client = None - self.wallet = None - - - def show_address(self, address): - self.wallet.check_proper_device() - try: - address_path = self.wallet.address_id(address) - address_n = self.get_client().expand_path(address_path) - except Exception, e: - give_error(e) - try: - self.get_client().get_address('Bitcoin', address_n, True) - except Exception, e: - give_error(e) - finally: - self.handler.stop() - - - def sign_transaction(self, tx, prev_tx, xpub_path): - self.prev_tx = prev_tx - self.xpub_path = xpub_path - client = self.get_client() - inputs = self.tx_inputs(tx, True) - outputs = self.tx_outputs(tx) - try: - signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1] - except Exception, e: - self.handler.stop() - give_error(e) - - self.handler.stop() - - raw = signed_tx.encode('hex') - tx.update_signatures(raw) - - - def tx_inputs(self, tx, for_sig=False): - inputs = [] - for txin in tx.inputs: - txinputtype = types.TxInputType() - if txin.get('is_coinbase'): - prev_hash = "\0"*32 - prev_index = 0xffffffff # signed int -1 - else: - if for_sig: - x_pubkeys = txin['x_pubkeys'] - if len(x_pubkeys) == 1: - x_pubkey = x_pubkeys[0] - xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) - xpub_n = self.get_client().expand_path(self.xpub_path[xpub]) - txinputtype.address_n.extend(xpub_n + s) - else: - def f(x_pubkey): - if is_extended_pubkey(x_pubkey): - xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) - else: - xpub = xpub_from_pubkey(x_pubkey.decode('hex')) - s = [] - node = ckd_public.deserialize(xpub) - return types.HDNodePathType(node=node, address_n=s) - pubkeys = map(f, x_pubkeys) - multisig = types.MultisigRedeemScriptType( - pubkeys=pubkeys, - signatures=map(lambda x: x.decode('hex') if x else '', txin.get('signatures')), - m=txin.get('num_sig'), - ) - txinputtype = types.TxInputType( - script_type=types.SPENDMULTISIG, - multisig= multisig - ) - # find which key is mine - for x_pubkey in x_pubkeys: - if is_extended_pubkey(x_pubkey): - xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) - if xpub in self.xpub_path: - xpub_n = self.get_client().expand_path(self.xpub_path[xpub]) - txinputtype.address_n.extend(xpub_n + s) - break - - prev_hash = unhexlify(txin['prevout_hash']) - prev_index = txin['prevout_n'] - - txinputtype.prev_hash = prev_hash - txinputtype.prev_index = prev_index - - if 'scriptSig' in txin: - script_sig = txin['scriptSig'].decode('hex') - txinputtype.script_sig = script_sig - - if 'sequence' in txin: - sequence = txin['sequence'] - txinputtype.sequence = sequence - - inputs.append(txinputtype) - - return inputs - - def tx_outputs(self, tx): - outputs = [] - - for type, address, amount in tx.outputs: - assert type == 'address' - txoutputtype = types.TxOutputType() - if self.wallet.is_change(address): - address_path = self.wallet.address_id(address) - address_n = self.get_client().expand_path(address_path) - txoutputtype.address_n.extend(address_n) - else: - txoutputtype.address = address - txoutputtype.amount = amount - addrtype, hash_160 = bc_address_to_hash_160(address) - if addrtype == 0: - txoutputtype.script_type = types.PAYTOADDRESS - elif addrtype == 5: - txoutputtype.script_type = types.PAYTOSCRIPTHASH - else: - raise BaseException('addrtype') - outputs.append(txoutputtype) - - return outputs - - def electrum_tx_to_txtype(self, tx): - t = types.TransactionType() - d = deserialize(tx.raw) - t.version = d['version'] - t.lock_time = d['lockTime'] - inputs = self.tx_inputs(tx) - t.inputs.extend(inputs) - for vout in d['outputs']: - o = t.bin_outputs.add() - o.amount = vout['value'] - o.script_pubkey = vout['scriptPubKey'].decode('hex') - return t - - def get_tx(self, tx_hash): - tx = self.prev_tx[tx_hash] - tx.deserialize() - return self.electrum_tx_to_txtype(tx) if KEEPKEY: class QtGuiKeepKeyClient(ProtocolMixin, GuiMixin, BaseClient): diff --git a/plugins/trezor/plugin_generic.py b/plugins/trezor/plugin_generic.py @@ -0,0 +1,206 @@ +from binascii import unhexlify + +from electrum.account import BIP32_Account +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 + +class TrezorCompatiblePlugin(BasePlugin): + # Derived classes provide: + + # libraries_available() + # constructor() + # ckd_public + # types + # wallet_type + + def __init__(self, parent, config, name): + BasePlugin.__init__(self, parent, config, name) + self.wallet = None + self.handler = None + self.client = None + self.transport = None + + def constructor(self, s): + raise NotImplementedError + + @staticmethod + def libraries_available(): + raise NotImplementedError + + def give_error(self, message): + self.print_error(message) + raise Exception(message) + + def is_available(self): + if not self.libraries_available(): + return False + if not self.wallet: + return False + if self.wallet.storage.get('wallet_type') != self.wallet_type: + return False + return True + + def set_enabled(self, enabled): + self.wallet.storage.put('use_' + self.name, enabled) + + def is_enabled(self): + if not self.is_available(): + return False + if self.wallet.has_seed(): + return False + return True + + def compare_version(self, major, minor=0, patch=0): + features = self.get_client().features + v = [features.major_version, features.minor_version, features.patch_version] + self.print_error('firmware version', v) + return cmp(v, [major, minor, patch]) + + def atleast_version(self, major, minor=0, patch=0): + return self.compare_version(major, minor, patch) >= 0 + + @hook + def close_wallet(self): + self.print_error("clear session") + if self.client: + self.client.clear_session() + self.client.transport.close() + self.client = None + self.wallet = None + + def sign_transaction(self, tx, prev_tx, xpub_path): + self.prev_tx = prev_tx + self.xpub_path = xpub_path + client = self.get_client() + inputs = self.tx_inputs(tx, True) + outputs = self.tx_outputs(tx) + try: + signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1] + except Exception as e: + self.give_error(e) + finally: + self.handler.stop() + raw = signed_tx.encode('hex') + tx.update_signatures(raw) + + def show_address(self, address): + client = self.get_client() + self.wallet.check_proper_device() + try: + address_path = self.wallet.address_id(address) + address_n = client.expand_path(address_path) + except Exception as e: + self.give_error(e) + try: + client.get_address('Bitcoin', address_n, True) + except Exception as e: + self.give_error(e) + finally: + self.handler.stop() + + def tx_inputs(self, tx, for_sig=False): + client = self.get_client() + inputs = [] + for txin in tx.inputs: + txinputtype = self.types.TxInputType() + if txin.get('is_coinbase'): + prev_hash = "\0"*32 + prev_index = 0xffffffff # signed int -1 + else: + if for_sig: + x_pubkeys = txin['x_pubkeys'] + if len(x_pubkeys) == 1: + x_pubkey = x_pubkeys[0] + xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) + xpub_n = client.expand_path(self.xpub_path[xpub]) + txinputtype.address_n.extend(xpub_n + s) + else: + def f(x_pubkey): + if is_extended_pubkey(x_pubkey): + xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) + else: + xpub = xpub_from_pubkey(x_pubkey.decode('hex')) + s = [] + node = ckd_public.deserialize(xpub) + return self.types.HDNodePathType(node=node, address_n=s) + pubkeys = map(f, x_pubkeys) + multisig = self.types.MultisigRedeemScriptType( + pubkeys=pubkeys, + signatures=map(lambda x: x.decode('hex') if x else '', txin.get('signatures')), + m=txin.get('num_sig'), + ) + txinputtype = self.types.TxInputType( + script_type=self.types.SPENDMULTISIG, + multisig= multisig + ) + # find which key is mine + for x_pubkey in x_pubkeys: + if is_extended_pubkey(x_pubkey): + xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) + if xpub in self.xpub_path: + xpub_n = client.expand_path(self.xpub_path[xpub]) + txinputtype.address_n.extend(xpub_n + s) + break + + prev_hash = unhexlify(txin['prevout_hash']) + prev_index = txin['prevout_n'] + + txinputtype.prev_hash = prev_hash + txinputtype.prev_index = prev_index + + if 'scriptSig' in txin: + script_sig = txin['scriptSig'].decode('hex') + txinputtype.script_sig = script_sig + + if 'sequence' in txin: + sequence = txin['sequence'] + txinputtype.sequence = sequence + + inputs.append(txinputtype) + + return inputs + + def tx_outputs(self, tx): + client = self.get_client() + outputs = [] + + for type, address, amount in tx.outputs: + assert type == 'address' + txoutputtype = self.types.TxOutputType() + if self.wallet.is_change(address): + address_path = self.wallet.address_id(address) + address_n = client.expand_path(address_path) + txoutputtype.address_n.extend(address_n) + else: + txoutputtype.address = address + txoutputtype.amount = amount + addrtype, hash_160 = bc_address_to_hash_160(address) + if addrtype == 0: + txoutputtype.script_type = self.types.PAYTOADDRESS + elif addrtype == 5: + txoutputtype.script_type = self.types.PAYTOSCRIPTHASH + else: + raise BaseException('addrtype') + outputs.append(txoutputtype) + + return outputs + + def electrum_tx_to_txtype(self, tx): + t = self.types.TransactionType() + d = deserialize(tx.raw) + t.version = d['version'] + t.lock_time = d['lockTime'] + inputs = self.tx_inputs(tx) + t.inputs.extend(inputs) + for vout in d['outputs']: + o = t.bin_outputs.add() + o.amount = vout['value'] + o.script_pubkey = vout['scriptPubKey'].decode('hex') + return t + + def get_tx(self, tx_hash): + tx = self.prev_tx[tx_hash] + tx.deserialize() + return self.electrum_tx_to_txtype(tx) diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py @@ -1,19 +1,9 @@ -from binascii import unhexlify - -import electrum -from electrum import bitcoin - -from electrum.account import BIP32_Account -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 from electrum.wallet import BIP32_Hardware_Wallet -from electrum.util import print_error + from plugins.trezor.gui_mixin import GuiMixin +from plugins.trezor.plugin_generic import TrezorCompatiblePlugin try: - from trezorlib.client import types from trezorlib.client import proto, BaseClient, ProtocolMixin from trezorlib.transport import ConnectionError from trezorlib.transport_hid import HidTransport @@ -21,71 +11,32 @@ try: except ImportError: TREZOR = False -import trezorlib.ckd_public as ckd_public - -def give_error(message): - print_error(message) - raise Exception(message) - class TrezorWallet(BIP32_Hardware_Wallet): wallet_type = 'trezor' root_derivation = "m/44'/0'" device = 'Trezor' +class TrezorPlugin(TrezorCompatiblePlugin): + wallet_type = 'trezor' + import trezorlib.ckd_public as ckd_public + from trezorlib.client import types -class TrezorPlugin(BasePlugin): - - def __init__(self, parent, config, name): - BasePlugin.__init__(self, parent, config, name) - self._is_available = self._init() - self.wallet = None - self.handler = None - self.client = None - self.transport = None + @staticmethod + def libraries_available(): + return TREZOR def constructor(self, s): return TrezorWallet(s) - def _init(self): - return TREZOR - - def is_available(self): - if not self._is_available: - return False - if not self.wallet: - return False - if self.wallet.storage.get('wallet_type') != 'trezor': - return False - return True - - def set_enabled(self, enabled): - self.wallet.storage.put('use_' + self.name, enabled) - - def is_enabled(self): - if not self.is_available(): - return False - if self.wallet.has_seed(): - return False - return True - - def compare_version(self, major, minor=0, patch=0): - features = self.get_client().features - v = [features.major_version, features.minor_version, features.patch_version] - self.print_error('firmware version', v) - return cmp(v, [major, minor, patch]) - - def atleast_version(self, major, minor=0, patch=0): - return self.compare_version(major, minor, patch) >= 0 - def get_client(self): if not TREZOR: - give_error('please install github.com/trezor/python-trezor') + self.give_error('please install github.com/trezor/python-trezor') if not self.client or self.client.bad: d = HidTransport.enumerate() if not d: - give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.') + self.give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.') self.transport = HidTransport(d[0]) self.client = QtGuiTrezorClient(self.transport) self.client.handler = self.handler @@ -93,138 +44,9 @@ class TrezorPlugin(BasePlugin): self.client.bad = False if not self.atleast_version(1, 2, 1): self.client = None - give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com') + self.give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com') return self.client - @hook - def close_wallet(self): - print_error("trezor: clear session") - if self.client: - self.client.clear_session() - self.client.transport.close() - self.client = None - self.wallet = None - - def sign_transaction(self, tx, prev_tx, xpub_path): - self.prev_tx = prev_tx - self.xpub_path = xpub_path - client = self.get_client() - inputs = self.tx_inputs(tx, True) - outputs = self.tx_outputs(tx) - #try: - signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1] - #except Exception, e: - # give_error(e) - #finally: - self.handler.stop() - - raw = signed_tx.encode('hex') - tx.update_signatures(raw) - - - def tx_inputs(self, tx, for_sig=False): - inputs = [] - for txin in tx.inputs: - txinputtype = types.TxInputType() - if txin.get('is_coinbase'): - prev_hash = "\0"*32 - prev_index = 0xffffffff # signed int -1 - else: - if for_sig: - x_pubkeys = txin['x_pubkeys'] - if len(x_pubkeys) == 1: - x_pubkey = x_pubkeys[0] - xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) - xpub_n = self.get_client().expand_path(self.xpub_path[xpub]) - txinputtype.address_n.extend(xpub_n + s) - else: - def f(x_pubkey): - if is_extended_pubkey(x_pubkey): - xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) - else: - xpub = xpub_from_pubkey(x_pubkey.decode('hex')) - s = [] - node = ckd_public.deserialize(xpub) - return types.HDNodePathType(node=node, address_n=s) - pubkeys = map(f, x_pubkeys) - multisig = types.MultisigRedeemScriptType( - pubkeys=pubkeys, - signatures=map(lambda x: x.decode('hex') if x else '', txin.get('signatures')), - m=txin.get('num_sig'), - ) - txinputtype = types.TxInputType( - script_type=types.SPENDMULTISIG, - multisig= multisig - ) - # find which key is mine - for x_pubkey in x_pubkeys: - if is_extended_pubkey(x_pubkey): - xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) - if xpub in self.xpub_path: - xpub_n = self.get_client().expand_path(self.xpub_path[xpub]) - txinputtype.address_n.extend(xpub_n + s) - break - - prev_hash = unhexlify(txin['prevout_hash']) - prev_index = txin['prevout_n'] - - txinputtype.prev_hash = prev_hash - txinputtype.prev_index = prev_index - - if 'scriptSig' in txin: - script_sig = txin['scriptSig'].decode('hex') - txinputtype.script_sig = script_sig - - if 'sequence' in txin: - sequence = txin['sequence'] - txinputtype.sequence = sequence - - inputs.append(txinputtype) - - return inputs - - def tx_outputs(self, tx): - outputs = [] - - for type, address, amount in tx.outputs: - assert type == 'address' - txoutputtype = types.TxOutputType() - if self.wallet.is_change(address): - address_path = self.wallet.address_id(address) - address_n = self.get_client().expand_path(address_path) - txoutputtype.address_n.extend(address_n) - else: - txoutputtype.address = address - txoutputtype.amount = amount - addrtype, hash_160 = bc_address_to_hash_160(address) - if addrtype == 0: - txoutputtype.script_type = types.PAYTOADDRESS - elif addrtype == 5: - txoutputtype.script_type = types.PAYTOSCRIPTHASH - else: - raise BaseException('addrtype') - outputs.append(txoutputtype) - - return outputs - - def electrum_tx_to_txtype(self, tx): - t = types.TransactionType() - d = deserialize(tx.raw) - t.version = d['version'] - t.lock_time = d['lockTime'] - inputs = self.tx_inputs(tx) - t.inputs.extend(inputs) - for vout in d['outputs']: - o = t.bin_outputs.add() - o.amount = vout['value'] - o.script_pubkey = vout['scriptPubKey'].decode('hex') - return t - - def get_tx(self, tx_hash): - tx = self.prev_tx[tx_hash] - tx.deserialize() - return self.electrum_tx_to_txtype(tx) - if TREZOR: class QtGuiTrezorClient(ProtocolMixin, GuiMixin, BaseClient): protocol = proto