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:
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