keepkey: full segwit support
ported from trezor plugin needs new fw to work (5.8??) fixes #3462
This commit is contained in:
parent
70c32590a9
commit
8aebb8249a
@ -3,9 +3,8 @@ import traceback
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from electrum.util import bfh, bh2u, UserCancelled
|
from electrum.util import bfh, bh2u, UserCancelled
|
||||||
from electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey,
|
from electrum.bitcoin import (xpub_from_pubkey, deserialize_xpub,
|
||||||
TYPE_ADDRESS, TYPE_SCRIPT,
|
TYPE_ADDRESS, TYPE_SCRIPT)
|
||||||
is_segwit_address)
|
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import BasePlugin
|
from electrum.plugin import BasePlugin
|
||||||
@ -29,9 +28,6 @@ class KeepKey_KeyStore(Hardware_KeyStore):
|
|||||||
def get_derivation(self):
|
def get_derivation(self):
|
||||||
return self.derivation
|
return self.derivation
|
||||||
|
|
||||||
def is_segwit(self):
|
|
||||||
return self.derivation.startswith("m/49'/")
|
|
||||||
|
|
||||||
def get_client(self, force_pair=True):
|
def get_client(self, force_pair=True):
|
||||||
return self.plugin.get_client(self, force_pair)
|
return self.plugin.get_client(self, force_pair)
|
||||||
|
|
||||||
@ -79,7 +75,7 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
libraries_URL = 'https://github.com/keepkey/python-keepkey'
|
libraries_URL = 'https://github.com/keepkey/python-keepkey'
|
||||||
minimum_firmware = (1, 0, 0)
|
minimum_firmware = (1, 0, 0)
|
||||||
keystore_class = KeepKey_KeyStore
|
keystore_class = KeepKey_KeyStore
|
||||||
SUPPORTED_XTYPES = ('standard', )
|
SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
|
||||||
|
|
||||||
MAX_LABEL_LEN = 32
|
MAX_LABEL_LEN = 32
|
||||||
|
|
||||||
@ -232,6 +228,17 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
client.load_device_by_xprv(item, pin, passphrase_protection,
|
client.load_device_by_xprv(item, pin, passphrase_protection,
|
||||||
label, language)
|
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):
|
def setup_device(self, device_info, wizard, purpose):
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
device_id = device_info.device.id_
|
device_id = device_info.device.id_
|
||||||
@ -256,12 +263,34 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
client.used()
|
client.used()
|
||||||
return xpub
|
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):
|
def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
|
||||||
self.prev_tx = prev_tx
|
self.prev_tx = prev_tx
|
||||||
self.xpub_path = xpub_path
|
self.xpub_path = xpub_path
|
||||||
client = self.get_client(keystore)
|
client = self.get_client(keystore)
|
||||||
inputs = self.tx_inputs(tx, True, keystore.is_segwit())
|
inputs = self.tx_inputs(tx, True)
|
||||||
outputs = self.tx_outputs(keystore.get_derivation(), tx, keystore.is_segwit())
|
outputs = self.tx_outputs(keystore.get_derivation(), tx)
|
||||||
signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[0]
|
signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime)[0]
|
||||||
signatures = [(bh2u(x) + '01') for x in signatures]
|
signatures = [(bh2u(x) + '01') for x in signatures]
|
||||||
tx.update_signatures(signatures)
|
tx.update_signatures(signatures)
|
||||||
@ -271,22 +300,34 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
keystore = wallet.get_keystore()
|
keystore = wallet.get_keystore()
|
||||||
if not self.show_address_helper(wallet, address, keystore):
|
if not self.show_address_helper(wallet, address, keystore):
|
||||||
return
|
return
|
||||||
if type(wallet) is not Standard_Wallet:
|
client = self.get_client(keystore)
|
||||||
keystore.handler.show_error(_('This function is only available for standard wallets when using {}.').format(self.device))
|
|
||||||
return
|
|
||||||
client = self.get_client(wallet.keystore)
|
|
||||||
if not client.atleast_version(1, 3):
|
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
|
return
|
||||||
change, index = wallet.get_address_index(address)
|
change, index = wallet.get_address_index(address)
|
||||||
derivation = wallet.keystore.derivation
|
derivation = keystore.derivation
|
||||||
address_path = "%s/%d/%d"%(derivation, change, index)
|
address_path = "%s/%d/%d"%(derivation, change, index)
|
||||||
address_n = client.expand_path(address_path)
|
address_n = client.expand_path(address_path)
|
||||||
segwit = wallet.keystore.is_segwit()
|
xpubs = wallet.get_master_public_keys()
|
||||||
script_type = self.types.SPENDP2SHWITNESS if segwit else self.types.SPENDADDRESS
|
if len(xpubs) == 1:
|
||||||
client.get_address(self.get_coin_name(), address_n, True, script_type=script_type)
|
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, segwit=False):
|
def tx_inputs(self, tx, for_sig=False):
|
||||||
inputs = []
|
inputs = []
|
||||||
for txin in tx.inputs():
|
for txin in tx.inputs():
|
||||||
txinputtype = self.types.TxInputType()
|
txinputtype = self.types.TxInputType()
|
||||||
@ -301,7 +342,7 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
xpub, s = parse_xpubkey(x_pubkey)
|
xpub, s = parse_xpubkey(x_pubkey)
|
||||||
xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
|
xpub_n = self.client_class.expand_path(self.xpub_path[xpub])
|
||||||
txinputtype.address_n.extend(xpub_n + s)
|
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:
|
else:
|
||||||
def f(x_pubkey):
|
def f(x_pubkey):
|
||||||
if is_xpubkey(x_pubkey):
|
if is_xpubkey(x_pubkey):
|
||||||
@ -309,15 +350,14 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
else:
|
else:
|
||||||
xpub = xpub_from_pubkey(0, bfh(x_pubkey))
|
xpub = xpub_from_pubkey(0, bfh(x_pubkey))
|
||||||
s = []
|
s = []
|
||||||
node = self.ckd_public.deserialize(xpub)
|
return self._make_node_path(xpub, s)
|
||||||
return self.types.HDNodePathType(node=node, address_n=s)
|
pubkeys = list(map(f, x_pubkeys))
|
||||||
pubkeys = map(f, x_pubkeys)
|
|
||||||
multisig = self.types.MultisigRedeemScriptType(
|
multisig = self.types.MultisigRedeemScriptType(
|
||||||
pubkeys=pubkeys,
|
pubkeys=pubkeys,
|
||||||
signatures=map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures')),
|
signatures=map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures')),
|
||||||
m=txin.get('num_sig'),
|
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(
|
txinputtype = self.types.TxInputType(
|
||||||
script_type=script_type,
|
script_type=script_type,
|
||||||
multisig=multisig
|
multisig=multisig
|
||||||
@ -349,11 +389,11 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
|
|
||||||
return inputs
|
return inputs
|
||||||
|
|
||||||
def tx_outputs(self, derivation, tx, segwit=False):
|
def tx_outputs(self, derivation, tx):
|
||||||
|
|
||||||
def create_output_by_derivation():
|
def create_output_by_derivation():
|
||||||
|
script_type = self.get_keepkey_output_script_type(info.script_type)
|
||||||
if len(xpubs) == 1:
|
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)
|
address_n = self.client_class.expand_path(derivation + "/%d/%d" % index)
|
||||||
txoutputtype = self.types.TxOutputType(
|
txoutputtype = self.types.TxOutputType(
|
||||||
amount=amount,
|
amount=amount,
|
||||||
@ -361,10 +401,8 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
address_n=address_n,
|
address_n=address_n,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
script_type = self.types.PAYTOP2SHWITNESS if segwit else self.types.PAYTOMULTISIG
|
|
||||||
address_n = self.client_class.expand_path("/%d/%d" % index)
|
address_n = self.client_class.expand_path("/%d/%d" % index)
|
||||||
nodes = map(self.ckd_public.deserialize, xpubs)
|
pubkeys = [self._make_node_path(xpub, address_n) for xpub in xpubs]
|
||||||
pubkeys = [self.types.HDNodePathType(node=node, address_n=address_n) for node in nodes]
|
|
||||||
multisig = self.types.MultisigRedeemScriptType(
|
multisig = self.types.MultisigRedeemScriptType(
|
||||||
pubkeys=pubkeys,
|
pubkeys=pubkeys,
|
||||||
signatures=[b''] * len(pubkeys),
|
signatures=[b''] * len(pubkeys),
|
||||||
@ -383,16 +421,7 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
txoutputtype.script_type = self.types.PAYTOOPRETURN
|
txoutputtype.script_type = self.types.PAYTOOPRETURN
|
||||||
txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
|
txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
|
||||||
elif _type == TYPE_ADDRESS:
|
elif _type == TYPE_ADDRESS:
|
||||||
if is_segwit_address(address):
|
txoutputtype.script_type = self.types.PAYTOADDRESS
|
||||||
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.address = address
|
txoutputtype.address = address
|
||||||
return txoutputtype
|
return txoutputtype
|
||||||
|
|
||||||
@ -424,6 +453,9 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||||||
|
|
||||||
def electrum_tx_to_txtype(self, tx):
|
def electrum_tx_to_txtype(self, tx):
|
||||||
t = self.types.TransactionType()
|
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)
|
d = deserialize(tx.raw)
|
||||||
t.version = d['version']
|
t.version = d['version']
|
||||||
t.lock_time = d['lockTime']
|
t.lock_time = d['lockTime']
|
||||||
|
|||||||
@ -198,13 +198,14 @@ class QtPlugin(QtPluginBase):
|
|||||||
@only_hook_if_libraries_available
|
@only_hook_if_libraries_available
|
||||||
@hook
|
@hook
|
||||||
def receive_menu(self, menu, addrs, wallet):
|
def receive_menu(self, menu, addrs, wallet):
|
||||||
if type(wallet) is not Standard_Wallet:
|
if len(addrs) != 1:
|
||||||
return
|
return
|
||||||
keystore = wallet.get_keystore()
|
for keystore in wallet.get_keystores():
|
||||||
if type(keystore) == self.keystore_class and len(addrs) == 1:
|
if type(keystore) == self.keystore_class:
|
||||||
def show_address():
|
def show_address():
|
||||||
keystore.thread.add(partial(self.show_address, wallet, addrs[0]))
|
keystore.thread.add(partial(self.show_address, wallet, addrs[0], keystore))
|
||||||
menu.addAction(_("Show on {}").format(self.device), show_address)
|
device_name = "{} ({})".format(self.device, keystore.label)
|
||||||
|
menu.addAction(_("Show on {}").format(device_name), show_address)
|
||||||
|
|
||||||
def show_settings_dialog(self, window, keystore):
|
def show_settings_dialog(self, window, keystore):
|
||||||
device_id = self.choose_device(window, keystore)
|
device_id = self.choose_device(window, keystore)
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import traceback
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from electrum.util import bfh, bh2u, versiontuple, UserCancelled
|
from electrum.util import bfh, bh2u, versiontuple, UserCancelled
|
||||||
from electrum.bitcoin import (b58_address_to_hash160, xpub_from_pubkey, deserialize_xpub,
|
from electrum.bitcoin import (xpub_from_pubkey, deserialize_xpub,
|
||||||
TYPE_ADDRESS, TYPE_SCRIPT, is_address)
|
TYPE_ADDRESS, TYPE_SCRIPT)
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import BasePlugin, Device
|
from electrum.plugin import BasePlugin, Device
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user