diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py index 5886ca57c..4c347da80 100644 --- a/electrum/plugins/hw_wallet/plugin.py +++ b/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.")) -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): # note: this decorator must wrap @hook, not the other way around, # as 'hook' uses the name of the function it wraps diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py index 0e3c36019..573819ec6 100644 --- a/electrum/plugins/keepkey/keepkey.py +++ b/electrum/plugins/keepkey/keepkey.py @@ -1,10 +1,11 @@ from binascii import hexlify, unhexlify import traceback 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.bip32 import BIP32Node +from electrum import descriptor from electrum import constants from electrum.i18n import _ 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 ..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, - get_xpubs_and_der_suffixes_from_txinout) +from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data if TYPE_CHECKING: import usb1 @@ -271,7 +271,7 @@ class KeepKeyPlugin(HW_PluginBase): client.load_device_by_xprv(item, pin, passphrase_protection, 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) node = self.types.HDNodeType( depth=bip32node.depth, @@ -351,14 +351,9 @@ class KeepKeyPlugin(HW_PluginBase): script_type = self.get_keepkey_input_script_type(wallet.txin_type) # prepare multisig, if available: - xpubs = wallet.get_master_public_keys() - if len(xpubs) > 1: - pubkeys = wallet.get_public_keys(address) - # 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]) + desc = wallet.get_script_descriptor_for_address(address) + if multi := desc.get_simple_multisig(): + multisig = self._make_multisig(multi) else: multisig = None @@ -378,8 +373,7 @@ class KeepKeyPlugin(HW_PluginBase): assert keystore assert (desc := txin.script_descriptor) if multi := desc.get_simple_multisig(): - xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txin) - multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi) else: multisig = None script_type = self.get_keepkey_input_script_type(desc.to_legacy_electrum_script_type()) @@ -407,14 +401,18 @@ class KeepKeyPlugin(HW_PluginBase): return inputs - def _make_multisig(self, m, xpubs): - if len(xpubs) == 1: - return None - pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs] + def _make_multisig(self, desc: descriptor.MultisigDescriptor): + pubkeys = [] + for pubkey_provider in desc.pubkeys: + 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( pubkeys=pubkeys, signatures=[b''] * len(pubkeys), - m=m) + m=desc.thresh) def tx_outputs(self, tx: PartialTransaction, *, keystore: 'KeepKey_KeyStore'): @@ -422,8 +420,7 @@ class KeepKeyPlugin(HW_PluginBase): assert (desc := txout.script_descriptor) script_type = self.get_keepkey_output_script_type(desc.to_legacy_electrum_script_type()) if multi := desc.get_simple_multisig(): - xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txout) - multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi) else: multisig = None my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout) diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py index 863fa796d..750171fc0 100644 --- a/electrum/plugins/safe_t/safe_t.py +++ b/electrum/plugins/safe_t/safe_t.py @@ -1,10 +1,11 @@ from binascii import hexlify, unhexlify import traceback 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.bip32 import BIP32Node +from electrum import descriptor from electrum import constants from electrum.i18n import _ 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 ..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, - get_xpubs_and_der_suffixes_from_txinout) +from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data if TYPE_CHECKING: from .client import SafeTClient @@ -241,7 +241,7 @@ class SafeTPlugin(HW_PluginBase): client.load_device_by_xprv(item, pin, passphrase_protection, 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) node = self.types.HDNodeType( depth=bip32node.depth, @@ -321,14 +321,9 @@ class SafeTPlugin(HW_PluginBase): script_type = self.get_safet_input_script_type(wallet.txin_type) # prepare multisig, if available: - xpubs = wallet.get_master_public_keys() - if len(xpubs) > 1: - pubkeys = wallet.get_public_keys(address) - # 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]) + desc = wallet.get_script_descriptor_for_address(address) + if multi := desc.get_simple_multisig(): + multisig = self._make_multisig(multi) else: multisig = None @@ -348,8 +343,7 @@ class SafeTPlugin(HW_PluginBase): assert keystore assert (desc := txin.script_descriptor) if multi := desc.get_simple_multisig(): - xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txin) - multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi) else: multisig = None script_type = self.get_safet_input_script_type(desc.to_legacy_electrum_script_type()) @@ -377,14 +371,18 @@ class SafeTPlugin(HW_PluginBase): return inputs - def _make_multisig(self, m, xpubs): - if len(xpubs) == 1: - return None - pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs] + def _make_multisig(self, desc: descriptor.MultisigDescriptor): + pubkeys = [] + for pubkey_provider in desc.pubkeys: + 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( pubkeys=pubkeys, signatures=[b''] * len(pubkeys), - m=m) + m=desc.thresh) def tx_outputs(self, tx: PartialTransaction, *, keystore: 'SafeTKeyStore'): @@ -392,8 +390,7 @@ class SafeTPlugin(HW_PluginBase): assert (desc := txout.script_descriptor) script_type = self.get_safet_output_script_type(desc.to_legacy_electrum_script_type()) if multi := desc.get_simple_multisig(): - xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txout) - multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi) else: multisig = None my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout) diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py index 503eed6bb..390a812fa 100644 --- a/electrum/plugins/trezor/trezor.py +++ b/electrum/plugins/trezor/trezor.py @@ -1,9 +1,10 @@ import traceback 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.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path +from electrum import descriptor from electrum import constants from electrum.i18n import _ 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.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data, - LibraryFoundButUnusable, OutdatedHwFirmwareException, - get_xpubs_and_der_suffixes_from_txinout) + LibraryFoundButUnusable, OutdatedHwFirmwareException) _logger = get_logger(__name__) @@ -284,7 +284,7 @@ class TrezorPlugin(HW_PluginBase): else: 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) node = HDNodeType( depth=bip32node.depth, @@ -386,14 +386,9 @@ class TrezorPlugin(HW_PluginBase): script_type = self.get_trezor_input_script_type(wallet.txin_type) # prepare multisig, if available: - xpubs = wallet.get_master_public_keys() - if len(xpubs) > 1: - pubkeys = wallet.get_public_keys(address) - # 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]) + desc = wallet.get_script_descriptor_for_address(address) + if multi := desc.get_simple_multisig(): + multisig = self._make_multisig(multi) else: multisig = None @@ -419,8 +414,7 @@ class TrezorPlugin(HW_PluginBase): assert keystore assert (desc := txin.script_descriptor) 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.thresh, xpubs_and_deriv_suffixes) + txinputtype.multisig = self._make_multisig(multi) 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) if full_path: @@ -434,14 +428,18 @@ class TrezorPlugin(HW_PluginBase): return inputs - def _make_multisig(self, m, xpubs): - if len(xpubs) == 1: - return None - pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs] + def _make_multisig(self, desc: descriptor.MultisigDescriptor): + pubkeys = [] + for pubkey_provider in desc.pubkeys: + 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( pubkeys=pubkeys, signatures=[b''] * len(pubkeys), - m=m) + m=desc.thresh) def tx_outputs(self, tx: PartialTransaction, *, keystore: 'TrezorKeyStore'): @@ -449,8 +447,7 @@ class TrezorPlugin(HW_PluginBase): assert (desc := txout.script_descriptor) script_type = self.get_trezor_output_script_type(desc.to_legacy_electrum_script_type()) if multi := desc.get_simple_multisig(): - xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout(tx, txout) - multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi) else: multisig = None my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout)