Browse Source

transaction.py: rm PartialTxInput.{num_sig, script_type}

master
SomberNight 3 years ago
parent
commit
0647a2cf9f
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 5
      electrum/commands.py
  2. 90
      electrum/descriptor.py
  3. 3
      electrum/lnsweep.py
  4. 4
      electrum/lnutil.py
  5. 5
      electrum/plugins/bitbox02/bitbox02.py
  6. 9
      electrum/plugins/digitalbitbox/digitalbitbox.py
  7. 5
      electrum/plugins/jade/jade.py
  8. 14
      electrum/plugins/keepkey/keepkey.py
  9. 35
      electrum/plugins/ledger/ledger.py
  10. 14
      electrum/plugins/safe_t/safe_t.py
  11. 14
      electrum/plugins/trezor/trezor.py
  12. 4
      electrum/submarine_swaps.py
  13. 11
      electrum/tests/test_transaction.py
  14. 8
      electrum/tests/test_wallet_vertical.py
  15. 63
      electrum/transaction.py
  16. 45
      electrum/wallet.py

5
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()

90
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):
"""

3
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)

4
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

5
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":

9
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:

5
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())

14
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)

35
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:

14
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)

14
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)

4
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))

11
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)

8
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])))

63
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:

45
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,

Loading…
Cancel
Save