mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-31 09:21:39 +00:00
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 changed files with 80 additions and 47 deletions
|
@ -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)
|
||||
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, segwit=False):
|
||||
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']
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue