electrum

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

commit 8aebb8249a0163cb2b1953b6aeaab9ff0c569f61
parent 70c32590a9bdfccc1ff6896d0fc558fb163c203a
Author: SomberNight <somber.night@protonmail.com>
Date:   Sun, 30 Sep 2018 01:29:27 +0200

keepkey: full segwit support

ported from trezor plugin
needs new fw to work (5.8??)

fixes #3462

Diffstat:
Melectrum/plugins/keepkey/keepkey.py | 112+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Melectrum/plugins/keepkey/qt.py | 13+++++++------
Melectrum/plugins/trezor/trezor.py | 4++--
3 files changed, 81 insertions(+), 48 deletions(-)

diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py @@ -3,9 +3,8 @@ import traceback import sys from electrum.util import bfh, bh2u, UserCancelled -from electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey, - TYPE_ADDRESS, TYPE_SCRIPT, - is_segwit_address) +from electrum.bitcoin import (xpub_from_pubkey, deserialize_xpub, + TYPE_ADDRESS, TYPE_SCRIPT) from electrum import constants from electrum.i18n import _ from electrum.plugin import BasePlugin @@ -29,9 +28,6 @@ class KeepKey_KeyStore(Hardware_KeyStore): def get_derivation(self): return self.derivation - def is_segwit(self): - return self.derivation.startswith("m/49'/") - def get_client(self, force_pair=True): return self.plugin.get_client(self, force_pair) @@ -79,7 +75,7 @@ class KeepKeyPlugin(HW_PluginBase): libraries_URL = 'https://github.com/keepkey/python-keepkey' minimum_firmware = (1, 0, 0) keystore_class = KeepKey_KeyStore - SUPPORTED_XTYPES = ('standard', ) + SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh') MAX_LABEL_LEN = 32 @@ -232,6 +228,17 @@ class KeepKeyPlugin(HW_PluginBase): client.load_device_by_xprv(item, pin, passphrase_protection, label, language) + def _make_node_path(self, xpub, address_n): + _, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub) + node = self.types.HDNodeType( + depth=depth, + fingerprint=int.from_bytes(fingerprint, 'big'), + child_num=int.from_bytes(child_num, 'big'), + chain_code=chain_code, + public_key=key, + ) + return self.types.HDNodePathType(node=node, address_n=address_n) + def setup_device(self, device_info, wizard, purpose): devmgr = self.device_manager() device_id = device_info.device.id_ @@ -256,12 +263,34 @@ class KeepKeyPlugin(HW_PluginBase): client.used() return xpub + def get_keepkey_input_script_type(self, electrum_txin_type: str): + if electrum_txin_type in ('p2wpkh', 'p2wsh'): + return self.types.SPENDWITNESS + if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'): + return self.types.SPENDP2SHWITNESS + if electrum_txin_type in ('p2pkh', ): + return self.types.SPENDADDRESS + if electrum_txin_type in ('p2sh', ): + return self.types.SPENDMULTISIG + raise ValueError('unexpected txin type: {}'.format(electrum_txin_type)) + + def get_keepkey_output_script_type(self, electrum_txin_type: str): + if electrum_txin_type in ('p2wpkh', 'p2wsh'): + return self.types.PAYTOWITNESS + if electrum_txin_type in ('p2wpkh-p2sh', 'p2wsh-p2sh'): + return self.types.PAYTOP2SHWITNESS + if electrum_txin_type in ('p2pkh', ): + return self.types.PAYTOADDRESS + if electrum_txin_type in ('p2sh', ): + return self.types.PAYTOMULTISIG + raise ValueError('unexpected txin type: {}'.format(electrum_txin_type)) + def sign_transaction(self, keystore, tx, prev_tx, xpub_path): self.prev_tx = prev_tx self.xpub_path = xpub_path client = self.get_client(keystore) - inputs = self.tx_inputs(tx, True, keystore.is_segwit()) - outputs = self.tx_outputs(keystore.get_derivation(), tx, keystore.is_segwit()) + inputs = self.tx_inputs(tx, True) + outputs = self.tx_outputs(keystore.get_derivation(), tx) signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[0] signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures) @@ -271,22 +300,34 @@ class KeepKeyPlugin(HW_PluginBase): keystore = wallet.get_keystore() if not self.show_address_helper(wallet, address, keystore): return - if type(wallet) is not Standard_Wallet: - keystore.handler.show_error(_('This function is only available for standard wallets when using {}.').format(self.device)) - return - client = self.get_client(wallet.keystore) + client = self.get_client(keystore) if not client.atleast_version(1, 3): - wallet.keystore.handler.show_error(_("Your device firmware is too old")) + keystore.handler.show_error(_("Your device firmware is too old")) return change, index = wallet.get_address_index(address) - derivation = wallet.keystore.derivation + derivation = keystore.derivation address_path = "%s/%d/%d"%(derivation, change, index) address_n = client.expand_path(address_path) - segwit = wallet.keystore.is_segwit() - script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDADDRESS - client.get_address(self.get_coin_name(), address_n, True, script_type=script_type) - - def tx_inputs(self, tx, for_sig=False, segwit=False): + xpubs = wallet.get_master_public_keys() + if len(xpubs) == 1: + script_type = self.get_keepkey_input_script_type(wallet.txin_type) + client.get_address(self.get_coin_name(), address_n, True, script_type=script_type) + else: + def f(xpub): + return self._make_node_path(xpub, [change, index]) + pubkeys = wallet.get_public_keys(address) + # sort xpubs using the order of pubkeys + sorted_pubkeys, sorted_xpubs = zip(*sorted(zip(pubkeys, xpubs))) + pubkeys = list(map(f, sorted_xpubs)) + multisig = self.types.MultisigRedeemScriptType( + pubkeys=pubkeys, + signatures=[b''] * wallet.n, + m=wallet.m, + ) + script_type = self.get_keepkey_input_script_type(wallet.txin_type) + client.get_address(self.get_coin_name(), address_n, True, multisig=multisig, script_type=script_type) + + def tx_inputs(self, tx, for_sig=False): inputs = [] for txin in tx.inputs(): txinputtype = self.types.TxInputType() @@ -301,7 +342,7 @@ class KeepKeyPlugin(HW_PluginBase): xpub, s = parse_xpubkey(x_pubkey) xpub_n = self.client_class.expand_path(self.xpub_path[xpub]) txinputtype.address_n.extend(xpub_n + s) - txinputtype.script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDADDRESS + txinputtype.script_type = self.get_keepkey_input_script_type(txin['type']) else: def f(x_pubkey): if is_xpubkey(x_pubkey): @@ -309,15 +350,14 @@ class KeepKeyPlugin(HW_PluginBase): else: xpub = xpub_from_pubkey(0, bfh(x_pubkey)) s = [] - node = self.ckd_public.deserialize(xpub) - return self.types.HDNodePathType(node=node, address_n=s) - pubkeys = map(f, x_pubkeys) + return self._make_node_path(xpub, s) + pubkeys = list(map(f, x_pubkeys)) multisig = self.types.MultisigRedeemScriptType( pubkeys=pubkeys, signatures=map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures')), m=txin.get('num_sig'), ) - script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDMULTISIG + script_type = self.get_keepkey_input_script_type(txin['type']) txinputtype = self.types.TxInputType( script_type=script_type, multisig=multisig @@ -349,11 +389,11 @@ class KeepKeyPlugin(HW_PluginBase): return inputs - def tx_outputs(self, derivation, tx, segwit=False): + def tx_outputs(self, derivation, tx): def create_output_by_derivation(): + script_type = self.get_keepkey_output_script_type(info.script_type) if len(xpubs) == 1: - script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOADDRESS address_n = self.client_class.expand_path(derivation + "/%d/%d" % index) txoutputtype = self.types.TxOutputType( amount=amount, @@ -361,10 +401,8 @@ class KeepKeyPlugin(HW_PluginBase): address_n=address_n, ) else: - script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOMULTISIG address_n = self.client_class.expand_path("/%d/%d" % index) - nodes = map(self.ckd_public.deserialize, xpubs) - pubkeys = [self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes] + pubkeys = [self._make_node_path(xpub, address_n) for xpub in xpubs] multisig = self.types.MultisigRedeemScriptType( pubkeys=pubkeys, signatures=[b''] * len(pubkeys), @@ -383,16 +421,7 @@ class KeepKeyPlugin(HW_PluginBase): txoutputtype.script_type = self.types.PAYTOOPRETURN txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o) elif _type == TYPE_ADDRESS: - if is_segwit_address(address): - txoutputtype.script_type = self.types.PAYTOWITNESS - else: - addrtype, hash_160 = b58_address_to_hash160(address) - if addrtype == constants.net.ADDRTYPE_P2PKH: - txoutputtype.script_type = self.types.PAYTOADDRESS - elif addrtype == constants.net.ADDRTYPE_P2SH: - txoutputtype.script_type = self.types.PAYTOSCRIPTHASH - else: - raise Exception('addrtype: ' + str(addrtype)) + txoutputtype.script_type = self.types.PAYTOADDRESS txoutputtype.address = address return txoutputtype @@ -424,6 +453,9 @@ class KeepKeyPlugin(HW_PluginBase): def electrum_tx_to_txtype(self, tx): t = self.types.TransactionType() + if tx is None: + # probably for segwit input and we don't need this prev txn + return t d = deserialize(tx.raw) t.version = d['version'] t.lock_time = d['lockTime'] diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py @@ -198,13 +198,14 @@ class QtPlugin(QtPluginBase): @only_hook_if_libraries_available @hook def receive_menu(self, menu, addrs, wallet): - if type(wallet) is not Standard_Wallet: + if len(addrs) != 1: return - keystore = wallet.get_keystore() - if type(keystore) == self.keystore_class and len(addrs) == 1: - def show_address(): - keystore.thread.add(partial(self.show_address, wallet, addrs[0])) - menu.addAction(_("Show on {}").format(self.device), show_address) + for keystore in wallet.get_keystores(): + if type(keystore) == self.keystore_class: + def show_address(): + keystore.thread.add(partial(self.show_address, wallet, addrs[0], keystore)) + device_name = "{} ({})".format(self.device, keystore.label) + menu.addAction(_("Show on {}").format(device_name), show_address) def show_settings_dialog(self, window, keystore): device_id = self.choose_device(window, keystore) diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py @@ -3,8 +3,8 @@ import traceback import sys from electrum.util import bfh, bh2u, versiontuple, UserCancelled -from electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey, deserialize_xpub, - TYPE_ADDRESS, TYPE_SCRIPT, is_address) +from electrum.bitcoin import (xpub_from_pubkey, deserialize_xpub, + TYPE_ADDRESS, TYPE_SCRIPT) from electrum import constants from electrum.i18n import _ from electrum.plugin import BasePlugin, Device