Browse Source

trezor: TrezorPlugin._make_multisig to use MultisigDescriptor

This fixes a regression where the plugin was assuming ordering for
txin.pubkeys (which is now a set).
(previously txin.pubkeys was a list ordered according to the final
sort order of keys inside the bitcoin script)
master
SomberNight 3 years ago
parent
commit
e457bb50e9
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 19
      electrum/plugins/hw_wallet/plugin.py
  2. 39
      electrum/plugins/keepkey/keepkey.py
  3. 39
      electrum/plugins/safe_t/safe_t.py
  4. 39
      electrum/plugins/trezor/trezor.py

19
electrum/plugins/hw_wallet/plugin.py

@ -354,25 +354,6 @@ def validate_op_return_output(output: TxOutput, *, max_size: int = None) -> None
raise UserFacingException(_("Amount for OP_RETURN output must be zero.")) raise UserFacingException(_("Amount for OP_RETURN output must be zero."))
def get_xpubs_and_der_suffixes_from_txinout(tx: PartialTransaction,
txinout: Union[PartialTxInput, PartialTxOutput]) \
-> List[Tuple[str, List[int]]]:
xfp_to_xpub_map = {xfp: bip32node for bip32node, (xfp, path)
in tx.xpubs.items()} # type: Dict[bytes, BIP32Node]
xfps = [txinout.bip32_paths[pubkey][0] for pubkey in txinout.pubkeys]
try:
xpubs = [xfp_to_xpub_map[xfp] for xfp in xfps]
except KeyError as e:
raise Exception(f"Partial transaction is missing global xpub for "
f"fingerprint ({str(e)}) in input/output") from e
xpubs_and_deriv_suffixes = []
for bip32node, pubkey in zip(xpubs, txinout.pubkeys):
xfp, path = txinout.bip32_paths[pubkey]
der_suffix = list(path)[bip32node.depth:]
xpubs_and_deriv_suffixes.append((bip32node.to_xpub(), der_suffix))
return xpubs_and_deriv_suffixes
def only_hook_if_libraries_available(func): def only_hook_if_libraries_available(func):
# note: this decorator must wrap @hook, not the other way around, # note: this decorator must wrap @hook, not the other way around,
# as 'hook' uses the name of the function it wraps # as 'hook' uses the name of the function it wraps

39
electrum/plugins/keepkey/keepkey.py

@ -1,10 +1,11 @@
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
import traceback import traceback
import sys import sys
from typing import NamedTuple, Any, Optional, Dict, Union, List, Tuple, TYPE_CHECKING from typing import NamedTuple, Any, Optional, Dict, Union, List, Tuple, TYPE_CHECKING, Sequence
from electrum.util import bfh, UserCancelled, UserFacingException from electrum.util import bfh, UserCancelled, UserFacingException
from electrum.bip32 import BIP32Node from electrum.bip32 import BIP32Node
from electrum import descriptor
from electrum import constants from electrum import constants
from electrum.i18n import _ from electrum.i18n import _
from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, Sighash from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, Sighash
@ -13,8 +14,7 @@ from electrum.plugin import Device, runs_in_hwd_thread
from electrum.base_wizard import ScriptTypeNotSupported from electrum.base_wizard import ScriptTypeNotSupported
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data, from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data
get_xpubs_and_der_suffixes_from_txinout)
if TYPE_CHECKING: if TYPE_CHECKING:
import usb1 import usb1
@ -271,7 +271,7 @@ 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): def _make_node_path(self, xpub: str, address_n: Sequence[int]):
bip32node = BIP32Node.from_xkey(xpub) bip32node = BIP32Node.from_xkey(xpub)
node = self.types.HDNodeType( node = self.types.HDNodeType(
depth=bip32node.depth, depth=bip32node.depth,
@ -351,14 +351,9 @@ class KeepKeyPlugin(HW_PluginBase):
script_type = self.get_keepkey_input_script_type(wallet.txin_type) script_type = self.get_keepkey_input_script_type(wallet.txin_type)
# prepare multisig, if available: # prepare multisig, if available:
xpubs = wallet.get_master_public_keys() desc = wallet.get_script_descriptor_for_address(address)
if len(xpubs) > 1: if multi := desc.get_simple_multisig():
pubkeys = wallet.get_public_keys(address) multisig = self._make_multisig(multi)
# sort xpubs using the order of pubkeys
sorted_pairs = sorted(zip(pubkeys, xpubs))
multisig = self._make_multisig(
wallet.m,
[(xpub, deriv_suffix) for pubkey, xpub in sorted_pairs])
else: else:
multisig = None multisig = None
@ -378,8 +373,7 @@ class KeepKeyPlugin(HW_PluginBase):
assert keystore assert keystore
assert (desc := txin.script_descriptor) assert (desc := txin.script_descriptor)
if multi := desc.get_simple_multisig(): if multi := desc.get_simple_multisig():
xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txin) multisig = self._make_multisig(multi)
multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes)
else: else:
multisig = None multisig = None
script_type = self.get_keepkey_input_script_type(desc.to_legacy_electrum_script_type()) script_type = self.get_keepkey_input_script_type(desc.to_legacy_electrum_script_type())
@ -407,14 +401,18 @@ class KeepKeyPlugin(HW_PluginBase):
return inputs return inputs
def _make_multisig(self, m, xpubs): def _make_multisig(self, desc: descriptor.MultisigDescriptor):
if len(xpubs) == 1: pubkeys = []
return None for pubkey_provider in desc.pubkeys:
pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs] assert not pubkey_provider.is_range()
assert pubkey_provider.extkey is not None
xpub = pubkey_provider.pubkey
der_suffix = pubkey_provider.get_der_suffix_int_list()
pubkeys.append(self._make_node_path(xpub, der_suffix))
return self.types.MultisigRedeemScriptType( return self.types.MultisigRedeemScriptType(
pubkeys=pubkeys, pubkeys=pubkeys,
signatures=[b''] * len(pubkeys), signatures=[b''] * len(pubkeys),
m=m) m=desc.thresh)
def tx_outputs(self, tx: PartialTransaction, *, keystore: 'KeepKey_KeyStore'): def tx_outputs(self, tx: PartialTransaction, *, keystore: 'KeepKey_KeyStore'):
@ -422,8 +420,7 @@ class KeepKeyPlugin(HW_PluginBase):
assert (desc := txout.script_descriptor) assert (desc := txout.script_descriptor)
script_type = self.get_keepkey_output_script_type(desc.to_legacy_electrum_script_type()) script_type = self.get_keepkey_output_script_type(desc.to_legacy_electrum_script_type())
if multi := desc.get_simple_multisig(): if multi := desc.get_simple_multisig():
xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txout) multisig = self._make_multisig(multi)
multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes)
else: else:
multisig = None multisig = None
my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout) my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout)

39
electrum/plugins/safe_t/safe_t.py

@ -1,10 +1,11 @@
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
import traceback import traceback
import sys import sys
from typing import NamedTuple, Any, Optional, Dict, Union, List, Tuple, TYPE_CHECKING from typing import NamedTuple, Any, Optional, Dict, Union, List, Tuple, TYPE_CHECKING, Sequence
from electrum.util import bfh, versiontuple, UserCancelled, UserFacingException from electrum.util import bfh, versiontuple, UserCancelled, UserFacingException
from electrum.bip32 import BIP32Node from electrum.bip32 import BIP32Node
from electrum import descriptor
from electrum import constants from electrum import constants
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import Device, runs_in_hwd_thread from electrum.plugin import Device, runs_in_hwd_thread
@ -13,8 +14,7 @@ from electrum.keystore import Hardware_KeyStore
from electrum.base_wizard import ScriptTypeNotSupported from electrum.base_wizard import ScriptTypeNotSupported
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data, from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data
get_xpubs_and_der_suffixes_from_txinout)
if TYPE_CHECKING: if TYPE_CHECKING:
from .client import SafeTClient from .client import SafeTClient
@ -241,7 +241,7 @@ class SafeTPlugin(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): def _make_node_path(self, xpub: str, address_n: Sequence[int]):
bip32node = BIP32Node.from_xkey(xpub) bip32node = BIP32Node.from_xkey(xpub)
node = self.types.HDNodeType( node = self.types.HDNodeType(
depth=bip32node.depth, depth=bip32node.depth,
@ -321,14 +321,9 @@ class SafeTPlugin(HW_PluginBase):
script_type = self.get_safet_input_script_type(wallet.txin_type) script_type = self.get_safet_input_script_type(wallet.txin_type)
# prepare multisig, if available: # prepare multisig, if available:
xpubs = wallet.get_master_public_keys() desc = wallet.get_script_descriptor_for_address(address)
if len(xpubs) > 1: if multi := desc.get_simple_multisig():
pubkeys = wallet.get_public_keys(address) multisig = self._make_multisig(multi)
# sort xpubs using the order of pubkeys
sorted_pairs = sorted(zip(pubkeys, xpubs))
multisig = self._make_multisig(
wallet.m,
[(xpub, deriv_suffix) for pubkey, xpub in sorted_pairs])
else: else:
multisig = None multisig = None
@ -348,8 +343,7 @@ class SafeTPlugin(HW_PluginBase):
assert keystore assert keystore
assert (desc := txin.script_descriptor) assert (desc := txin.script_descriptor)
if multi := desc.get_simple_multisig(): if multi := desc.get_simple_multisig():
xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txin) multisig = self._make_multisig(multi)
multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes)
else: else:
multisig = None multisig = None
script_type = self.get_safet_input_script_type(desc.to_legacy_electrum_script_type()) script_type = self.get_safet_input_script_type(desc.to_legacy_electrum_script_type())
@ -377,14 +371,18 @@ class SafeTPlugin(HW_PluginBase):
return inputs return inputs
def _make_multisig(self, m, xpubs): def _make_multisig(self, desc: descriptor.MultisigDescriptor):
if len(xpubs) == 1: pubkeys = []
return None for pubkey_provider in desc.pubkeys:
pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs] assert not pubkey_provider.is_range()
assert pubkey_provider.extkey is not None
xpub = pubkey_provider.pubkey
der_suffix = pubkey_provider.get_der_suffix_int_list()
pubkeys.append(self._make_node_path(xpub, der_suffix))
return self.types.MultisigRedeemScriptType( return self.types.MultisigRedeemScriptType(
pubkeys=pubkeys, pubkeys=pubkeys,
signatures=[b''] * len(pubkeys), signatures=[b''] * len(pubkeys),
m=m) m=desc.thresh)
def tx_outputs(self, tx: PartialTransaction, *, keystore: 'SafeTKeyStore'): def tx_outputs(self, tx: PartialTransaction, *, keystore: 'SafeTKeyStore'):
@ -392,8 +390,7 @@ class SafeTPlugin(HW_PluginBase):
assert (desc := txout.script_descriptor) assert (desc := txout.script_descriptor)
script_type = self.get_safet_output_script_type(desc.to_legacy_electrum_script_type()) script_type = self.get_safet_output_script_type(desc.to_legacy_electrum_script_type())
if multi := desc.get_simple_multisig(): if multi := desc.get_simple_multisig():
xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txout) multisig = self._make_multisig(multi)
multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes)
else: else:
multisig = None multisig = None
my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout) my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout)

39
electrum/plugins/trezor/trezor.py

@ -1,9 +1,10 @@
import traceback import traceback
import sys import sys
from typing import NamedTuple, Any, Optional, Dict, Union, List, Tuple, TYPE_CHECKING from typing import NamedTuple, Any, Optional, Dict, Union, List, Tuple, TYPE_CHECKING, Sequence
from electrum.util import bfh, versiontuple, UserCancelled, UserFacingException from electrum.util import bfh, versiontuple, UserCancelled, UserFacingException
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path
from electrum import descriptor
from electrum import constants from electrum import constants
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import Device, runs_in_hwd_thread from electrum.plugin import Device, runs_in_hwd_thread
@ -14,8 +15,7 @@ from electrum.logging import get_logger
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data, from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data,
LibraryFoundButUnusable, OutdatedHwFirmwareException, LibraryFoundButUnusable, OutdatedHwFirmwareException)
get_xpubs_and_der_suffixes_from_txinout)
_logger = get_logger(__name__) _logger = get_logger(__name__)
@ -284,7 +284,7 @@ class TrezorPlugin(HW_PluginBase):
else: else:
raise RuntimeError("Unsupported recovery method") raise RuntimeError("Unsupported recovery method")
def _make_node_path(self, xpub, address_n): def _make_node_path(self, xpub: str, address_n: Sequence[int]):
bip32node = BIP32Node.from_xkey(xpub) bip32node = BIP32Node.from_xkey(xpub)
node = HDNodeType( node = HDNodeType(
depth=bip32node.depth, depth=bip32node.depth,
@ -386,14 +386,9 @@ class TrezorPlugin(HW_PluginBase):
script_type = self.get_trezor_input_script_type(wallet.txin_type) script_type = self.get_trezor_input_script_type(wallet.txin_type)
# prepare multisig, if available: # prepare multisig, if available:
xpubs = wallet.get_master_public_keys() desc = wallet.get_script_descriptor_for_address(address)
if len(xpubs) > 1: if multi := desc.get_simple_multisig():
pubkeys = wallet.get_public_keys(address) multisig = self._make_multisig(multi)
# sort xpubs using the order of pubkeys
sorted_pairs = sorted(zip(pubkeys, xpubs))
multisig = self._make_multisig(
wallet.m,
[(xpub, deriv_suffix) for pubkey, xpub in sorted_pairs])
else: else:
multisig = None multisig = None
@ -419,8 +414,7 @@ class TrezorPlugin(HW_PluginBase):
assert keystore assert keystore
assert (desc := txin.script_descriptor) assert (desc := txin.script_descriptor)
if multi := desc.get_simple_multisig(): if multi := desc.get_simple_multisig():
xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txin) txinputtype.multisig = self._make_multisig(multi)
txinputtype.multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes)
txinputtype.script_type = self.get_trezor_input_script_type(desc.to_legacy_electrum_script_type()) txinputtype.script_type = self.get_trezor_input_script_type(desc.to_legacy_electrum_script_type())
my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txin) my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txin)
if full_path: if full_path:
@ -434,14 +428,18 @@ class TrezorPlugin(HW_PluginBase):
return inputs return inputs
def _make_multisig(self, m, xpubs): def _make_multisig(self, desc: descriptor.MultisigDescriptor):
if len(xpubs) == 1: pubkeys = []
return None for pubkey_provider in desc.pubkeys:
pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs] assert not pubkey_provider.is_range()
assert pubkey_provider.extkey is not None
xpub = pubkey_provider.pubkey
der_suffix = pubkey_provider.get_der_suffix_int_list()
pubkeys.append(self._make_node_path(xpub, der_suffix))
return MultisigRedeemScriptType( return MultisigRedeemScriptType(
pubkeys=pubkeys, pubkeys=pubkeys,
signatures=[b''] * len(pubkeys), signatures=[b''] * len(pubkeys),
m=m) m=desc.thresh)
def tx_outputs(self, tx: PartialTransaction, *, keystore: 'TrezorKeyStore'): def tx_outputs(self, tx: PartialTransaction, *, keystore: 'TrezorKeyStore'):
@ -449,8 +447,7 @@ class TrezorPlugin(HW_PluginBase):
assert (desc := txout.script_descriptor) assert (desc := txout.script_descriptor)
script_type = self.get_trezor_output_script_type(desc.to_legacy_electrum_script_type()) script_type = self.get_trezor_output_script_type(desc.to_legacy_electrum_script_type())
if multi := desc.get_simple_multisig(): if multi := desc.get_simple_multisig():
xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txout) multisig = self._make_multisig(multi)
multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes)
else: else:
multisig = None multisig = None
my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout) my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout)

Loading…
Cancel
Save