From 0647a2cf9f66ce1c3ada0f1d05c29e73d6a95a59 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 26 Feb 2023 13:28:31 +0000 Subject: [PATCH] transaction.py: rm PartialTxInput.{num_sig, script_type} --- electrum/commands.py | 5 -- electrum/descriptor.py | 90 +++++++++++++++++++ electrum/lnsweep.py | 3 - electrum/lnutil.py | 4 - electrum/plugins/bitbox02/bitbox02.py | 5 +- .../plugins/digitalbitbox/digitalbitbox.py | 9 +- electrum/plugins/jade/jade.py | 5 +- electrum/plugins/keepkey/keepkey.py | 14 +-- electrum/plugins/ledger/ledger.py | 35 ++++---- electrum/plugins/safe_t/safe_t.py | 14 +-- electrum/plugins/trezor/trezor.py | 14 +-- electrum/submarine_swaps.py | 4 - electrum/tests/test_transaction.py | 11 --- electrum/tests/test_wallet_vertical.py | 8 -- electrum/transaction.py | 63 +++++-------- electrum/wallet.py | 45 ++-------- 16 files changed, 169 insertions(+), 160 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 6d8fc7bf7..33073bb39 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -397,9 +397,6 @@ class Commands: keypairs[pubkey] = privkey, compressed desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey, script_type=txin_type) txin.script_descriptor = desc - txin.script_type = txin_type - txin.pubkeys = [bfh(pubkey)] - txin.num_sig = 1 inputs.append(txin) outputs = [PartialTxOutput.from_address_and_value(txout['address'], int(txout.get('value', txout['value_sats']))) @@ -428,8 +425,6 @@ class Commands: if address in txins_dict.keys(): for txin in txins_dict[address]: txin.script_descriptor = desc - txin.pubkeys = [pubkey] - txin.script_type = txin_type tx.sign({pubkey.hex(): (priv2, compressed)}) return tx.serialize() diff --git a/electrum/descriptor.py b/electrum/descriptor.py index b87f52be7..be33bd3fc 100644 --- a/electrum/descriptor.py +++ b/electrum/descriptor.py @@ -43,6 +43,7 @@ from typing import ( Tuple, Sequence, Mapping, + Set, ) @@ -376,6 +377,22 @@ class Descriptor(object): script_sig=script_sig, ) + def get_satisfaction_progress( + self, + *, + sigdata: Mapping[bytes, bytes] = None, # pubkey -> sig + ) -> Tuple[int, int]: + """Returns (num_sigs_we_have, num_sigs_required) towards satisfying this script. + Besides signatures, later this can also consider hash-preimages. + """ + assert not self.is_range() + nhave, nreq = 0, 0 + for desc in self.subdescriptors: + a, b = desc.get_satisfaction_progress() + nhave += a + nreq += b + return nhave, nreq + def is_range(self) -> bool: for pubkey in self.pubkeys: if pubkey.is_range(): @@ -388,6 +405,47 @@ class Descriptor(object): def is_segwit(self) -> bool: return any([desc.is_segwit() for desc in self.subdescriptors]) + def get_all_pubkeys(self) -> Set[bytes]: + """Returns set of pubkeys that appear at any level in this descriptor.""" + assert not self.is_range() + all_pubkeys = set([p.get_pubkey_bytes() for p in self.pubkeys]) + for desc in self.subdescriptors: + all_pubkeys |= desc.get_all_pubkeys() + return all_pubkeys + + def get_simple_singlesig(self) -> Optional['Descriptor']: + """Returns innermost pk/pkh/wpkh descriptor, or None if we are not a simple singlesig. + + note: besides pk,pkh,sh(wpkh),wpkh, overly complicated stuff such as sh(pk),wsh(sh(pkh),etc is also accepted + """ + if len(self.subdescriptors) == 1: + return self.subdescriptors[0].get_simple_singlesig() + return None + + def get_simple_multisig(self) -> Optional['MultisigDescriptor']: + """Returns innermost multi descriptor, or None if we are not a simple multisig.""" + if len(self.subdescriptors) == 1: + return self.subdescriptors[0].get_simple_multisig() + return None + + def to_legacy_electrum_script_type(self) -> str: + if isinstance(self, PKDescriptor): + return "p2pk" + elif isinstance(self, PKHDescriptor): + return "p2pkh" + elif isinstance(self, WPKHDescriptor): + return "p2wpkh" + elif isinstance(self, SHDescriptor) and isinstance(self.subdescriptors[0], WPKHDescriptor): + return "p2wpkh-p2sh" + elif isinstance(self, SHDescriptor) and isinstance(self.subdescriptors[0], MultisigDescriptor): + return "p2sh" + elif isinstance(self, WSHDescriptor) and isinstance(self.subdescriptors[0], MultisigDescriptor): + return "p2wsh" + elif (isinstance(self, SHDescriptor) and isinstance(self.subdescriptors[0], WSHDescriptor) + and isinstance(self.subdescriptors[0].subdescriptors[0], MultisigDescriptor)): + return "p2wsh-p2sh" + return "unknown" + class PKDescriptor(Descriptor): """ @@ -421,6 +479,14 @@ class PKDescriptor(Descriptor): witness_items=(sig,), ) + def get_satisfaction_progress(self, *, sigdata=None) -> Tuple[int, int]: + if sigdata is None: sigdata = {} + signatures = list(sigdata.values()) + return len(signatures), 1 + + def get_simple_singlesig(self) -> Optional['Descriptor']: + return self + class PKHDescriptor(Descriptor): """ @@ -455,6 +521,14 @@ class PKHDescriptor(Descriptor): witness_items=(sig, pubkey), ) + def get_satisfaction_progress(self, *, sigdata=None) -> Tuple[int, int]: + if sigdata is None: sigdata = {} + signatures = list(sigdata.values()) + return len(signatures), 1 + + def get_simple_singlesig(self) -> Optional['Descriptor']: + return self + class WPKHDescriptor(Descriptor): """ @@ -492,9 +566,17 @@ class WPKHDescriptor(Descriptor): witness_items=(sig, pubkey), ) + def get_satisfaction_progress(self, *, sigdata=None) -> Tuple[int, int]: + if sigdata is None: sigdata = {} + signatures = list(sigdata.values()) + return len(signatures), 1 + def is_segwit(self) -> bool: return True + def get_simple_singlesig(self) -> Optional['Descriptor']: + return self + class MultisigDescriptor(Descriptor): """ @@ -552,6 +634,14 @@ class MultisigDescriptor(Descriptor): witness_items=(0, *signatures), ) + def get_satisfaction_progress(self, *, sigdata=None) -> Tuple[int, int]: + if sigdata is None: sigdata = {} + signatures = list(sigdata.values()) + return len(signatures), self.thresh + + def get_simple_multisig(self) -> Optional['MultisigDescriptor']: + return self + class SHDescriptor(Descriptor): """ diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py index d9a989242..10151c33c 100644 --- a/electrum/lnsweep.py +++ b/electrum/lnsweep.py @@ -516,9 +516,6 @@ def create_sweeptx_their_ctx_to_remote( txin._trusted_value_sats = val desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=our_payment_pubkey, script_type='p2wpkh') txin.script_descriptor = desc - txin.script_type = 'p2wpkh' - txin.pubkeys = [bfh(our_payment_pubkey)] - txin.num_sig = 1 sweep_inputs = [txin] tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh fee = config.estimate_fee(tx_size_bytes, allow_fallback_to_static_rates=True) diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 8fab2a86f..b325fb7ab 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -823,10 +823,6 @@ def make_funding_input(local_funding_pubkey: bytes, remote_funding_pubkey: bytes ppubkeys = [descriptor.PubkeyProvider.parse(pk) for pk in pubkeys] multi = descriptor.MultisigDescriptor(pubkeys=ppubkeys, thresh=2, is_sorted=True) c_input.script_descriptor = descriptor.WSHDescriptor(subdescriptor=multi) - - c_input.script_type = 'p2wsh' - c_input.pubkeys = [bfh(pk) for pk in pubkeys] - c_input.num_sig = 2 c_input._trusted_value_sats = funding_sat return c_input diff --git a/electrum/plugins/bitbox02/bitbox02.py b/electrum/plugins/bitbox02/bitbox02.py index cb7e614a1..4b328dc6b 100644 --- a/electrum/plugins/bitbox02/bitbox02.py +++ b/electrum/plugins/bitbox02/bitbox02.py @@ -444,9 +444,10 @@ class BitBox02Client(HardwareClientBase): } ) + assert (desc := txin.script_descriptor) if tx_script_type is None: - tx_script_type = txin.script_type - elif tx_script_type != txin.script_type: + tx_script_type = desc.to_legacy_electrum_script_type() + elif tx_script_type != desc.to_legacy_electrum_script_type(): raise Exception("Cannot mix different input script types") if tx_script_type == "p2wpkh": diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py index 2d64b2f66..1f774c62d 100644 --- a/electrum/plugins/digitalbitbox/digitalbitbox.py +++ b/electrum/plugins/digitalbitbox/digitalbitbox.py @@ -19,6 +19,7 @@ import copy from electrum.crypto import sha256d, EncodeAES_bytes, DecodeAES_bytes, hmac_oneshot from electrum.bitcoin import public_key_to_p2pkh from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath, is_all_public_derivation +from electrum import descriptor from electrum import ecc from electrum.ecc import msg_magic from electrum.wallet import Standard_Wallet @@ -527,7 +528,8 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): if txin.is_coinbase_input(): self.give_error("Coinbase not supported") # should never happen - if txin.script_type != 'p2pkh': + assert (desc := txin.script_descriptor) + if desc.to_legacy_electrum_script_type() != 'p2pkh': p2pkhTransaction = False my_pubkey, inputPath = self.find_my_pubkey_in_txinout(txin) @@ -557,9 +559,10 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): tx_copy = copy.deepcopy(tx) # monkey-patch method of tx_copy instance to change serialization def input_script(self, txin: PartialTxInput, *, estimate_size=False): - if txin.script_type == 'p2pkh': + desc = txin.script_descriptor + if isinstance(desc, descriptor.PKHDescriptor): return Transaction.get_preimage_script(txin) - raise Exception("unsupported type %s" % txin.script_type) + raise Exception(f"unsupported txin type. only p2pkh is supported. got: {desc.to_string()[:10]}") tx_copy.input_script = input_script.__get__(tx_copy, PartialTransaction) tx_dbb_serialized = tx_copy.serialize_to_network() else: diff --git a/electrum/plugins/jade/jade.py b/electrum/plugins/jade/jade.py index 0869cee41..135713d6d 100644 --- a/electrum/plugins/jade/jade.py +++ b/electrum/plugins/jade/jade.py @@ -264,7 +264,7 @@ class Jade_KeyStore(Hardware_KeyStore): jade_inputs = [] for txin in tx.inputs(): pubkey, path = self.find_my_pubkey_in_txinout(txin) - witness_input = txin.script_type in ['p2wpkh-p2sh', 'p2wsh-p2sh', 'p2wpkh', 'p2wsh'] + witness_input = txin.is_segwit() redeem_script = Transaction.get_preimage_script(txin) redeem_script = bytes.fromhex(redeem_script) if redeem_script is not None else None input_tx = txin.utxo @@ -280,6 +280,7 @@ class Jade_KeyStore(Hardware_KeyStore): change = [None] * len(tx.outputs()) for index, txout in enumerate(tx.outputs()): if txout.is_mine and txout.is_change: + assert (desc := txout.script_descriptor) if is_multisig: # Multisig - wallet details must be registered on Jade hw multisig_name = _register_multisig_wallet(wallet, self, txout.address) @@ -294,7 +295,7 @@ class Jade_KeyStore(Hardware_KeyStore): else: # Pass entire path pubkey, path = self.find_my_pubkey_in_txinout(txout) - change[index] = {'path':path, 'variant': txout.script_type} + change[index] = {'path':path, 'variant': desc.to_legacy_electrum_script_type()} # The txn itself txn_bytes = bytes.fromhex(tx.serialize_to_network()) diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py index 7bbb0d7ae..0e3c36019 100644 --- a/electrum/plugins/keepkey/keepkey.py +++ b/electrum/plugins/keepkey/keepkey.py @@ -376,12 +376,13 @@ class KeepKeyPlugin(HW_PluginBase): assert isinstance(tx, PartialTransaction) assert isinstance(txin, PartialTxInput) assert keystore - if len(txin.pubkeys) > 1: + 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(txin.num_sig, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) else: multisig = None - script_type = self.get_keepkey_input_script_type(txin.script_type) + script_type = self.get_keepkey_input_script_type(desc.to_legacy_electrum_script_type()) txinputtype = self.types.TxInputType( script_type=script_type, multisig=multisig) @@ -418,10 +419,11 @@ class KeepKeyPlugin(HW_PluginBase): def tx_outputs(self, tx: PartialTransaction, *, keystore: 'KeepKey_KeyStore'): def create_output_by_derivation(): - script_type = self.get_keepkey_output_script_type(txout.script_type) - if len(txout.pubkeys) > 1: + 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(txout.num_sig, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) else: multisig = None my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout) diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index be1ca437f..5b1d213db 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -8,6 +8,7 @@ from typing import Dict, List, Optional, Sequence, Tuple from electrum import bip32, constants, ecc +from electrum import descriptor from electrum.base_wizard import ScriptTypeNotSupported from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath from electrum.bitcoin import EncodeBase58Check, int_to_hex, is_b58_address, is_segwit_script_type, var_int @@ -16,7 +17,7 @@ from electrum.i18n import _ from electrum.keystore import Hardware_KeyStore from electrum.logging import get_logger from electrum.plugin import Device, runs_in_hwd_thread -from electrum.transaction import PartialTransaction, Transaction +from electrum.transaction import PartialTransaction, Transaction, PartialTxInput from electrum.util import bfh, UserFacingException, versiontuple from electrum.wallet import Standard_Wallet @@ -544,20 +545,25 @@ class Ledger_Client_Legacy(Ledger_Client): pin = "" # prompt for the PIN before displaying the dialog if necessary + def is_txin_legacy_multisig(txin: PartialTxInput) -> bool: + desc = txin.script_descriptor + return (isinstance(desc, descriptor.SHDescriptor) + and isinstance(desc.subdescriptors[0], descriptor.MultisigDescriptor)) + # Fetch inputs of the transaction to sign for txin in tx.inputs(): if txin.is_coinbase_input(): self.give_error("Coinbase not supported") # should never happen - if txin.script_type in ['p2sh']: + if is_txin_legacy_multisig(txin): p2shTransaction = True - if txin.script_type in ['p2wpkh-p2sh', 'p2wsh-p2sh']: + if txin.is_p2sh_segwit(): if not self.supports_segwit(): self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT) segwitTransaction = True - if txin.script_type in ['p2wpkh', 'p2wsh']: + if txin.is_native_segwit(): if not self.supports_native_segwit(): self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT) segwitTransaction = True @@ -584,7 +590,7 @@ class Ledger_Client_Legacy(Ledger_Client): # Sanity check if p2shTransaction: for txin in tx.inputs(): - if txin.script_type != 'p2sh': + if not is_txin_legacy_multisig(txin): self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen txOutput = var_int(len(tx.outputs())) @@ -1083,7 +1089,6 @@ class Ledger_Client_New(Ledger_Client): raise UserFacingException("Coinbase not supported") # should never happen utxo = None - scriptcode = b"" if psbt_in.witness_utxo: utxo = psbt_in.witness_utxo if psbt_in.non_witness_utxo: @@ -1094,19 +1099,9 @@ class Ledger_Client_New(Ledger_Client): if utxo is None: continue - scriptcode = utxo.scriptPubKey - if electrum_txin.script_type in ['p2sh', 'p2wpkh-p2sh']: - if len(psbt_in.redeem_script) == 0: - continue - scriptcode = psbt_in.redeem_script - elif electrum_txin.script_type in ['p2wsh', 'p2wsh-p2sh']: - if len(psbt_in.witness_script) == 0: - continue - scriptcode = psbt_in.witness_script - - p2sh = False - if electrum_txin.script_type in ['p2sh', 'p2wpkh-p2sh', 'p2wsh-p2sh']: - p2sh = True + if (desc := electrum_txin.script_descriptor) is None: + raise Exception("script_descriptor missing for txin ") + scriptcode = desc.expand().scriptcode_for_sighash is_wit, wit_ver, __ = is_witness(psbt_in.redeem_script or utxo.scriptPubKey) @@ -1115,7 +1110,7 @@ class Ledger_Client_New(Ledger_Client): # if it's a segwit spend (any version), make sure the witness_utxo is also present psbt_in.witness_utxo = utxo - if p2sh: + if electrum_txin.is_p2sh_segwit(): if wit_ver == 0: script_addrtype = AddressType.SH_WIT else: diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py index 376bbfa60..863fa796d 100644 --- a/electrum/plugins/safe_t/safe_t.py +++ b/electrum/plugins/safe_t/safe_t.py @@ -346,12 +346,13 @@ class SafeTPlugin(HW_PluginBase): assert isinstance(tx, PartialTransaction) assert isinstance(txin, PartialTxInput) assert keystore - if len(txin.pubkeys) > 1: + 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(txin.num_sig, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) else: multisig = None - script_type = self.get_safet_input_script_type(txin.script_type) + script_type = self.get_safet_input_script_type(desc.to_legacy_electrum_script_type()) txinputtype = self.types.TxInputType( script_type=script_type, multisig=multisig) @@ -388,10 +389,11 @@ class SafeTPlugin(HW_PluginBase): def tx_outputs(self, tx: PartialTransaction, *, keystore: 'SafeTKeyStore'): def create_output_by_derivation(): - script_type = self.get_safet_output_script_type(txout.script_type) - if len(txout.pubkeys) > 1: + 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(txout.num_sig, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) 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 db52bd53f..503eed6bb 100644 --- a/electrum/plugins/trezor/trezor.py +++ b/electrum/plugins/trezor/trezor.py @@ -417,10 +417,11 @@ class TrezorPlugin(HW_PluginBase): assert isinstance(tx, PartialTransaction) assert isinstance(txin, PartialTxInput) assert keystore - if len(txin.pubkeys) > 1: + 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(txin.num_sig, xpubs_and_deriv_suffixes) - txinputtype.script_type = self.get_trezor_input_script_type(txin.script_type) + 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()) my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txin) if full_path: txinputtype.address_n = full_path @@ -445,10 +446,11 @@ class TrezorPlugin(HW_PluginBase): def tx_outputs(self, tx: PartialTransaction, *, keystore: 'TrezorKeyStore'): def create_output_by_derivation(): - script_type = self.get_trezor_output_script_type(txout.script_type) - if len(txout.pubkeys) > 1: + 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(txout.num_sig, xpubs_and_deriv_suffixes) + multisig = self._make_multisig(multi.thresh, xpubs_and_deriv_suffixes) else: multisig = None my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txout) diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index 9532bc8a6..7b82b739a 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -124,7 +124,6 @@ def create_claim_tx( """Create tx to either claim successful reverse-swap, or to get refunded for timed-out forward-swap. """ - txin.script_type = 'p2wsh' txin.script_sig = b'' txin.witness_script = witness_script txout = PartialTxOutput.from_address_and_value(address, amount_sat) @@ -622,8 +621,6 @@ class SwapManager(Logger): return preimage = swap.preimage if swap.is_reverse else 0 witness_script = swap.redeem_script - txin.script_type = 'p2wsh' - txin.num_sig = 1 # hack so that txin not considered "is_complete" txin.script_sig = b'' txin.witness_script = witness_script sig_dummy = b'\x00' * 71 # DER-encoded ECDSA sig, with low S and low R @@ -637,7 +634,6 @@ class SwapManager(Logger): txin = tx.inputs()[0] assert len(tx.inputs()) == 1, f"expected 1 input for swap claim tx. found {len(tx.inputs())}" assert txin.prevout.txid.hex() == swap.funding_txid - txin.script_type = 'p2wsh' txin.script_sig = b'' txin.witness_script = witness_script sig = bytes.fromhex(tx.sign_txin(0, swap.privkey)) diff --git a/electrum/tests/test_transaction.py b/electrum/tests/test_transaction.py index 1e7264aad..50fbafeda 100644 --- a/electrum/tests/test_transaction.py +++ b/electrum/tests/test_transaction.py @@ -94,9 +94,6 @@ class TestTransaction(ElectrumTestCase): script_type = 'p2pkh' desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=script_type) tx.inputs()[0].script_descriptor = desc - tx.inputs()[0].script_type = script_type - tx.inputs()[0].pubkeys = [pubkey] - tx.inputs()[0].num_sig = 1 tx.update_signatures(signed_blob_signatures) self.assertEqual(tx.serialize(), signed_blob) @@ -878,7 +875,6 @@ class TestTransactionTestnet(ElectrumTestCase): prevout = TxOutpoint(txid=bfh('6d500966f9e494b38a04545f0cea35fc7b3944e341a64b804fed71cdee11d434'), out_idx=1) txin = PartialTxInput(prevout=prevout) txin.nsequence = 2 ** 32 - 3 - txin.script_type = 'p2sh' redeem_script = bfh(construct_script([ locktime, opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, pubkey, opcodes.OP_CHECKSIG, ])) @@ -940,7 +936,6 @@ class TestSighashTypes(ElectrumTestCase): prevout = TxOutpoint(txid=bfh('6eb98797a21c6c10aa74edf29d618be109f48a8e94c694f3701e08ca69186436'), out_idx=1) txin = PartialTxInput(prevout=prevout) txin.nsequence=0xffffffff - txin.script_type='p2sh-p2wsh' txin.witness_script = bfh('56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae') txin.redeem_script = bfh('0020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54') txin._trusted_value_sats = 987654321 @@ -950,7 +945,6 @@ class TestSighashTypes(ElectrumTestCase): def test_check_sighash_types_sighash_all(self): self.txin.sighash=Sighash.ALL - self.txin.pubkeys = [bfh('0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3')] privkey = bfh('730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6') tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) @@ -959,7 +953,6 @@ class TestSighashTypes(ElectrumTestCase): def test_check_sighash_types_sighash_none(self): self.txin.sighash=Sighash.NONE - self.txin.pubkeys = [bfh('03b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b')] privkey = bfh('11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3') tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) @@ -968,7 +961,6 @@ class TestSighashTypes(ElectrumTestCase): def test_check_sighash_types_sighash_single(self): self.txin.sighash=Sighash.SINGLE - self.txin.pubkeys = [bfh('034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a')] privkey = bfh('77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661') tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) @@ -978,7 +970,6 @@ class TestSighashTypes(ElectrumTestCase): @disable_ecdsa_r_value_grinding def test_check_sighash_types_sighash_all_anyonecanpay(self): self.txin.sighash=Sighash.ALL|Sighash.ANYONECANPAY - self.txin.pubkeys = [bfh('033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f4')] privkey = bfh('14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49') tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) @@ -988,7 +979,6 @@ class TestSighashTypes(ElectrumTestCase): @disable_ecdsa_r_value_grinding def test_check_sighash_types_sighash_none_anyonecanpay(self): self.txin.sighash=Sighash.NONE|Sighash.ANYONECANPAY - self.txin.pubkeys = [bfh('03a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac16')] privkey = bfh('fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323') tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) @@ -997,7 +987,6 @@ class TestSighashTypes(ElectrumTestCase): def test_check_sighash_types_sighash_single_anyonecanpay(self): self.txin.sighash=Sighash.SINGLE|Sighash.ANYONECANPAY - self.txin.pubkeys = [bfh('02d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b')] privkey = bfh('428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890') tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py index 83d90d2b3..f61c8c3d1 100644 --- a/electrum/tests/test_wallet_vertical.py +++ b/electrum/tests/test_wallet_vertical.py @@ -729,7 +729,6 @@ class TestWalletSending(ElectrumTestCase): self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_segwit()) self.assertEqual(1, len(tx.inputs())) - self.assertEqual(wallet1.txin_type, tx.inputs()[0].script_type) tx_copy = tx_from_any(tx.serialize()) self.assertTrue(wallet1.is_mine(wallet1.adb.get_txin_address(tx_copy.inputs()[0]))) @@ -749,7 +748,6 @@ class TestWalletSending(ElectrumTestCase): self.assertTrue(tx.is_complete()) self.assertFalse(tx.is_segwit()) self.assertEqual(1, len(tx.inputs())) - self.assertEqual(wallet2.txin_type, tx.inputs()[0].script_type) tx_copy = tx_from_any(tx.serialize()) self.assertTrue(wallet2.is_mine(wallet2.adb.get_txin_address(tx_copy.inputs()[0]))) @@ -809,7 +807,6 @@ class TestWalletSending(ElectrumTestCase): self.assertTrue(tx.is_complete()) self.assertFalse(tx.is_segwit()) self.assertEqual(1, len(tx.inputs())) - self.assertEqual(wallet1a.txin_type, tx.inputs()[0].script_type) tx_copy = tx_from_any(tx.serialize()) self.assertTrue(wallet1a.is_mine(wallet1a.adb.get_txin_address(tx_copy.inputs()[0]))) @@ -829,7 +826,6 @@ class TestWalletSending(ElectrumTestCase): self.assertTrue(tx.is_complete()) self.assertFalse(tx.is_segwit()) self.assertEqual(1, len(tx.inputs())) - self.assertEqual(wallet2.txin_type, tx.inputs()[0].script_type) tx_copy = tx_from_any(tx.serialize()) self.assertTrue(wallet2.is_mine(wallet2.adb.get_txin_address(tx_copy.inputs()[0]))) @@ -908,7 +904,6 @@ class TestWalletSending(ElectrumTestCase): self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_segwit()) self.assertEqual(1, len(tx.inputs())) - self.assertEqual(wallet1a.txin_type, tx.inputs()[0].script_type) tx_copy = tx_from_any(tx.serialize()) self.assertTrue(wallet1a.is_mine(wallet1a.adb.get_txin_address(tx_copy.inputs()[0]))) @@ -937,7 +932,6 @@ class TestWalletSending(ElectrumTestCase): self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_segwit()) self.assertEqual(1, len(tx.inputs())) - self.assertEqual(wallet2a.txin_type, tx.inputs()[0].script_type) tx_copy = tx_from_any(tx.serialize()) self.assertTrue(wallet2a.is_mine(wallet2a.adb.get_txin_address(tx_copy.inputs()[0]))) @@ -987,7 +981,6 @@ class TestWalletSending(ElectrumTestCase): self.assertTrue(tx.is_complete()) self.assertFalse(tx.is_segwit()) self.assertEqual(1, len(tx.inputs())) - self.assertEqual(wallet1a.txin_type, tx.inputs()[0].script_type) tx_copy = tx_from_any(tx.serialize()) self.assertTrue(wallet1a.is_mine(wallet1a.adb.get_txin_address(tx_copy.inputs()[0]))) @@ -1007,7 +1000,6 @@ class TestWalletSending(ElectrumTestCase): self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_segwit()) self.assertEqual(1, len(tx.inputs())) - self.assertEqual(wallet2.txin_type, tx.inputs()[0].script_type) tx_copy = tx_from_any(tx.serialize()) self.assertTrue(wallet2.is_mine(wallet2.adb.get_txin_address(tx_copy.inputs()[0]))) diff --git a/electrum/transaction.py b/electrum/transaction.py index 436a46e43..46805873a 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -741,7 +741,6 @@ class Transaction: return '' assert isinstance(txin, PartialTxInput) - _type = txin.script_type if not txin.is_segwit(): return construct_witness([]) @@ -801,7 +800,7 @@ class Transaction: if script := sc.scriptcode_for_sighash: return script.hex() raise Exception(f"don't know scriptcode for descriptor: {desc.to_string()}") - raise UnknownTxinType(f'cannot construct preimage_script for txin_type: {txin.script_type}') + raise UnknownTxinType(f'cannot construct preimage_script') def _calc_bip143_shared_txdigest_fields(self) -> BIP143SharedTxDigestFields: inputs = self.inputs() @@ -1188,9 +1187,6 @@ class PartialTxInput(TxInput, PSBTSection): self._unknown = {} # type: Dict[bytes, bytes] self.script_descriptor = None # type: Optional[Descriptor] - self.script_type = 'unknown' - self.num_sig = 0 # type: int # num req sigs for multisig - self.pubkeys = [] # type: List[bytes] # note: order matters self._trusted_value_sats = None # type: Optional[int] self._trusted_address = None # type: Optional[str] self._is_p2sh_segwit = None # type: Optional[bool] # None means unknown @@ -1223,6 +1219,12 @@ class PartialTxInput(TxInput, PSBTSection): self._witness_utxo = value self.validate_data() + @property + def pubkeys(self) -> Set[bytes]: + if desc := self.script_descriptor: + return desc.get_all_pubkeys() + return set() + def to_json(self): d = super().to_json() d.update({ @@ -1402,22 +1404,6 @@ class PartialTxInput(TxInput, PSBTSection): return self.witness_utxo.scriptpubkey return None - def set_script_type(self) -> None: - if self.scriptpubkey is None: - return - type = get_script_type_from_output_script(self.scriptpubkey) - inner_type = None - if type is not None: - if type == 'p2sh': - inner_type = get_script_type_from_output_script(self.redeem_script) - elif type == 'p2wsh': - inner_type = get_script_type_from_output_script(self.witness_script) - if inner_type is not None: - type = inner_type + '-' + type - if type in ('p2pkh', 'p2wpkh-p2sh', 'p2wpkh'): - self.script_type = type - return - def is_complete(self) -> bool: if self.script_sig is not None and self.witness is not None: return True @@ -1434,6 +1420,11 @@ class PartialTxInput(TxInput, PSBTSection): return True return False + def get_satisfaction_progress(self) -> Tuple[int, int]: + if desc := self.script_descriptor: + return desc.get_satisfaction_progress(sigdata=self.part_sigs) + return 0, 0 + def finalize(self) -> None: def clear_fields_when_finalized(): # BIP-174: "All other data except the UTXO and unknown fields in the @@ -1547,12 +1538,15 @@ class PartialTxOutput(TxOutput, PSBTSection): self._unknown = {} # type: Dict[bytes, bytes] self.script_descriptor = None # type: Optional[Descriptor] - self.script_type = 'unknown' - self.num_sig = 0 # num req sigs for multisig - self.pubkeys = [] # type: List[bytes] # note: order matters self.is_mine = False # type: bool # whether the wallet considers the output to be ismine self.is_change = False # type: bool # whether the wallet considers the output to be change + @property + def pubkeys(self) -> Set[bytes]: + if desc := self.script_descriptor: + return desc.get_all_pubkeys() + return set() + def to_json(self): d = super().to_json() d.update({ @@ -1947,15 +1941,12 @@ class PartialTransaction(Transaction): return all([txin.is_complete() for txin in self.inputs()]) def signature_count(self) -> Tuple[int, int]: - s = 0 # "num Sigs we have" - r = 0 # "Required" + nhave, nreq = 0, 0 for txin in self.inputs(): - if txin.is_coinbase_input(): - continue - signatures = list(txin.part_sigs.values()) - s += len(signatures) - r += txin.num_sig - return s, r + a, b = txin.get_satisfaction_progress() + nhave += a + nreq += b + return nhave, nreq def serialize(self) -> str: """Returns PSBT as base64 text, or raw hex of network tx (if complete).""" @@ -2104,14 +2095,6 @@ class PartialTransaction(Transaction): assert not self.is_complete() self.invalidate_ser_cache() - def update_txin_script_type(self): - """Determine the script_type of each input by analyzing the scripts. - It updates all tx-Inputs, NOT only the wallet owned, if the - scriptpubkey is present. - """ - for txin in self.inputs(): - if txin.script_type in ('unknown', 'address'): - txin.set_script_type() def pack_bip32_root_fingerprint_and_int_path(xfp: bytes, path: Sequence[int]) -> bytes: if len(xfp) != 4: diff --git a/electrum/wallet.py b/electrum/wallet.py index 21bdda164..fc62cd3d3 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -126,13 +126,6 @@ async def _append_utxos_to_inputs( txin.utxo = prev_tx txin.block_height = int(item['height']) txin.script_descriptor = script_descriptor - # TODO rm as much of below (.num_sig / .pubkeys) as possible - # TODO need unit tests for other scripts (only have p2pk atm) - txin.script_type = txin_type - txin.pubkeys = [bfh(pubkey)] - txin.num_sig = 1 - if txin_type == 'p2wpkh-p2sh': - txin.redeem_script = bfh(bitcoin.p2wpkh_nested_script(pubkey)) inputs.append(txin) u = await network.listunspent_for_scripthash(scripthash) @@ -2151,10 +2144,6 @@ class Abstract_Wallet(ABC, Logger, EventListener): tx_new.add_info_from_wallet(self) return tx_new - @abstractmethod - def _add_input_sig_info(self, txin: PartialTxInput, address: str, *, only_der_suffix: bool) -> None: - pass - def _add_txinout_derivation_info(self, txinout: Union[PartialTxInput, PartialTxOutput], address: str, *, only_der_suffix: bool) -> None: pass # implemented by subclasses @@ -2208,22 +2197,19 @@ class Abstract_Wallet(ABC, Logger, EventListener): self.lnworker.swap_manager.add_txin_info(txin) return txin.script_descriptor = self._get_script_descriptor_for_address(address) - # set script_type first, as later checks might rely on it: # TODO rm most of below in favour of osd - txin.script_type = self.get_txin_type(address) - txin.num_sig = self.m if isinstance(self, Multisig_Wallet) else 1 - if txin.redeem_script is None: + if txin.redeem_script is None: # FIXME should be set in transaction.py instead, based on the script desc try: redeem_script_hex = self.get_redeem_script(address) txin.redeem_script = bfh(redeem_script_hex) if redeem_script_hex else None except UnknownTxinType: pass - if txin.witness_script is None: + if txin.witness_script is None: # FIXME should be set in transaction.py instead, based on the script desc try: witness_script_hex = self.get_witness_script(address) txin.witness_script = bfh(witness_script_hex) if witness_script_hex else None except UnknownTxinType: pass - self._add_input_sig_info(txin, address, only_der_suffix=only_der_suffix) + self._add_txinout_derivation_info(txin, address, only_der_suffix=only_der_suffix) txin.block_height = self.adb.get_tx_height(txin.prevout.txid.hex()).height def _get_script_descriptor_for_address(self, address: str) -> Optional[Descriptor]: @@ -2306,19 +2292,16 @@ class Abstract_Wallet(ABC, Logger, EventListener): if not is_mine: return txout.script_descriptor = self._get_script_descriptor_for_address(address) - txout.script_type = self.get_txin_type(address) txout.is_mine = True txout.is_change = self.is_change(address) - if isinstance(self, Multisig_Wallet): - txout.num_sig = self.m self._add_txinout_derivation_info(txout, address, only_der_suffix=only_der_suffix) - if txout.redeem_script is None: + if txout.redeem_script is None: # FIXME should be set in transaction.py instead, based on the script desc try: redeem_script_hex = self.get_redeem_script(address) txout.redeem_script = bfh(redeem_script_hex) if redeem_script_hex else None except UnknownTxinType: pass - if txout.witness_script is None: + if txout.witness_script is None: # FIXME should be set in transaction.py instead, based on the script desc try: witness_script_hex = self.get_witness_script(address) txout.witness_script = bfh(witness_script_hex) if witness_script_hex else None @@ -3189,20 +3172,6 @@ class Imported_Wallet(Simple_Wallet): if addr != bitcoin.pubkey_to_address(txin_type, pubkey): raise InternalAddressCorruption() - def _add_input_sig_info(self, txin, address, *, only_der_suffix): - if not self.is_mine(address): - return - if txin.script_type in ('unknown', 'address'): - return - elif txin.script_type in ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh'): - pubkey = self.get_public_key(address) - if not pubkey: - return - txin.pubkeys = [bfh(pubkey)] - else: - raise Exception(f'Unexpected script type: {txin.script_type}. ' - f'Imported wallets are not implemented to handle this.') - def pubkeys_to_address(self, pubkeys): pubkey = pubkeys[0] # FIXME This is slow. @@ -3330,14 +3299,10 @@ class Deterministic_Wallet(Abstract_Wallet): return {k.derive_pubkey(*der_suffix): (k, der_suffix) for k in self.get_keystores()} - def _add_input_sig_info(self, txin, address, *, only_der_suffix): - self._add_txinout_derivation_info(txin, address, only_der_suffix=only_der_suffix) - def _add_txinout_derivation_info(self, txinout, address, *, only_der_suffix): if not self.is_mine(address): return pubkey_deriv_info = self.get_public_keys_with_deriv_info(address) - txinout.pubkeys = sorted([pk for pk in list(pubkey_deriv_info)]) for pubkey in pubkey_deriv_info: ks, der_suffix = pubkey_deriv_info[pubkey] fp_bytes, der_full = ks.get_fp_and_derivation_to_be_used_in_partial_tx(der_suffix,