diff --git a/electrum/descriptor.py b/electrum/descriptor.py index 612ad6947..b87f52be7 100644 --- a/electrum/descriptor.py +++ b/electrum/descriptor.py @@ -28,7 +28,10 @@ from .bip32 import convert_bip32_path_to_list_of_uint32, BIP32Node, KeyOriginInfo from . import bitcoin from .bitcoin import construct_script, opcodes, construct_witness +from . import constants from .crypto import hash_160, sha256 +from . import ecc +from . import segwit_addr from .util import bfh from binascii import unhexlify @@ -45,6 +48,14 @@ from typing import ( MAX_TAPROOT_NODES = 128 +# we guess that signatures will be 72 bytes long +# note: DER-encoded ECDSA signatures are 71 or 72 bytes in practice +# See https://bitcoin.stackexchange.com/questions/77191/what-is-the-maximum-size-of-a-der-encoded-ecdsa-signature +# We assume low S (as that is a bitcoin standardness rule). +# We do not assume low R (even though the sigs we create conform), as external sigs, +# e.g. from a hw signer cannot be expected to have a low R. +DUMMY_DER_SIG = 72 * b"\x00" + class ExpandedScripts: @@ -403,7 +414,7 @@ class PKDescriptor(Descriptor): pubkey = self.pubkeys[0].get_pubkey_bytes() sig = sigdata.get(pubkey) if sig is None and allow_dummy: - sig = 72 * b"\x00" + sig = DUMMY_DER_SIG if sig is None: raise MissingSolutionPiece(f"no sig for {pubkey.hex()}") return ScriptSolutionInner( @@ -437,7 +448,7 @@ class PKHDescriptor(Descriptor): pubkey = self.pubkeys[0].get_pubkey_bytes() sig = sigdata.get(pubkey) if sig is None and allow_dummy: - sig = 72 * b"\x00" + sig = DUMMY_DER_SIG if sig is None: raise MissingSolutionPiece(f"no sig for {pubkey.hex()}") return ScriptSolutionInner( @@ -474,7 +485,7 @@ class WPKHDescriptor(Descriptor): pubkey = self.pubkeys[0].get_pubkey_bytes() sig = sigdata.get(pubkey) if sig is None and allow_dummy: - sig = 72 * b"\x00" + sig = DUMMY_DER_SIG if sig is None: raise MissingSolutionPiece(f"no sig for {pubkey.hex()}") return ScriptSolutionInner( @@ -532,7 +543,7 @@ class MultisigDescriptor(Descriptor): if len(signatures) >= self.thresh: break if allow_dummy: - dummy_sig = 72 * b"\x00" + dummy_sig = DUMMY_DER_SIG signatures += (self.thresh - len(signatures)) * [dummy_sig] if len(signatures) < self.thresh: raise MissingSolutionPiece(f"not enough sigs") @@ -900,3 +911,32 @@ def get_singlesig_descriptor_from_legacy_leaf(*, pubkey: str, script_type: str) return SHDescriptor(subdescriptor=wpkh) else: raise NotImplementedError(f"unexpected {script_type=}") + + +def create_dummy_descriptor_from_address(addr: Optional[str]) -> 'Descriptor': + # It's not possible to tell the script type in general just from an address. + # - "1" addresses are of course p2pkh + # - "3" addresses are p2sh but we don't know the redeem script... + # - "bc1" addresses (if they are 42-long) are p2wpkh + # - "bc1" addresses that are 62-long are p2wsh but we don't know the script... + # If we don't know the script, we _guess_ it is pubkeyhash. + # As this method is used e.g. for tx size estimation, + # the estimation will not be precise. + def guess_script_type(addr: Optional[str]) -> str: + if addr is None: + return 'p2wpkh' # the default guess + witver, witprog = segwit_addr.decode_segwit_address(constants.net.SEGWIT_HRP, addr) + if witprog is not None: + return 'p2wpkh' + addrtype, hash_160_ = bitcoin.b58_address_to_hash160(addr) + if addrtype == constants.net.ADDRTYPE_P2PKH: + return 'p2pkh' + elif addrtype == constants.net.ADDRTYPE_P2SH: + return 'p2wpkh-p2sh' + raise Exception(f'unrecognized address: {repr(addr)}') + + script_type = guess_script_type(addr) + # guess pubkey-len to be 33-bytes: + pubkey = ecc.GENERATOR.get_public_key_bytes(compressed=True).hex() + desc = get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey, script_type=script_type) + return desc diff --git a/electrum/transaction.py b/electrum/transaction.py index d9047336d..436a46e43 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -47,12 +47,12 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160, hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr, var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN, int_to_hex, push_script, b58_address_to_hash160, - opcodes, add_number_to_script, base_decode, is_segwit_script_type, + opcodes, add_number_to_script, base_decode, base_encode, construct_witness, construct_script) from .crypto import sha256d from .logging import get_logger from .util import ShortID -from .descriptor import Descriptor, MissingSolutionPiece +from .descriptor import Descriptor, MissingSolutionPiece, create_dummy_descriptor_from_address if TYPE_CHECKING: from .wallet import Abstract_Wallet @@ -733,33 +733,6 @@ class Transaction: if vds.can_read_more(): raise SerializationError('extra junk at the end') - @classmethod - def get_siglist(self, txin: 'PartialTxInput', *, estimate_size=False): - if txin.is_coinbase_input(): - return [], [] - - if estimate_size: - try: - pubkey_size = len(txin.pubkeys[0]) - except IndexError: - pubkey_size = 33 # guess it is compressed - num_pubkeys = max(1, len(txin.pubkeys)) - pk_list = ["00" * pubkey_size] * num_pubkeys - num_sig = max(1, txin.num_sig) - # we guess that signatures will be 72 bytes long - # note: DER-encoded ECDSA signatures are 71 or 72 bytes in practice - # See https://bitcoin.stackexchange.com/questions/77191/what-is-the-maximum-size-of-a-der-encoded-ecdsa-signature - # We assume low S (as that is a bitcoin standardness rule). - # We do not assume low R (even though the sigs we create conform), as external sigs, - # e.g. from a hw signer cannot be expected to have a low R. - sig_list = ["00" * 72] * num_sig - else: - pk_list = [pubkey.hex() for pubkey in txin.pubkeys] - sig_list = [txin.part_sigs.get(pubkey, b'').hex() for pubkey in txin.pubkeys] - if txin.is_complete(): - sig_list = [sig for sig in sig_list if sig] - return pk_list, sig_list - @classmethod def serialize_witness(cls, txin: TxInput, *, estimate_size=False) -> str: if txin.witness is not None: @@ -775,47 +748,15 @@ class Transaction: if estimate_size and txin.witness_sizehint is not None: return '00' * txin.witness_sizehint - if desc := txin.script_descriptor: + dummy_desc = None + if estimate_size: + dummy_desc = create_dummy_descriptor_from_address(txin.address) + if desc := (txin.script_descriptor or dummy_desc): sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs) if sol.witness is not None: return sol.witness.hex() return construct_witness([]) - - assert estimate_size # TODO xxxxx - if _type in ('address', 'unknown') and estimate_size: - _type = cls.guess_txintype_from_address(txin.address) - pubkeys, sig_list = cls.get_siglist(txin, estimate_size=estimate_size) - if _type in ['p2wpkh', 'p2wpkh-p2sh']: - return construct_witness([sig_list[0], pubkeys[0]]) - elif _type in ['p2wsh', 'p2wsh-p2sh']: - witness_script = multisig_script(pubkeys, txin.num_sig) - return construct_witness([0, *sig_list, witness_script]) - elif _type in ['p2pk', 'p2pkh', 'p2sh']: - return construct_witness([]) - raise UnknownTxinType(f'cannot construct witness for txin_type: {_type}') - - @classmethod - def guess_txintype_from_address(cls, addr: Optional[str]) -> str: - # It's not possible to tell the script type in general - # just from an address. - # - "1" addresses are of course p2pkh - # - "3" addresses are p2sh but we don't know the redeem script.. - # - "bc1" addresses (if they are 42-long) are p2wpkh - # - "bc1" addresses that are 62-long are p2wsh but we don't know the script.. - # If we don't know the script, we _guess_ it is pubkeyhash. - # As this method is used e.g. for tx size estimation, - # the estimation will not be precise. - if addr is None: - return 'p2wpkh' - witver, witprog = segwit_addr.decode_segwit_address(constants.net.SEGWIT_HRP, addr) - if witprog is not None: - return 'p2wpkh' - addrtype, hash_160_ = b58_address_to_hash160(addr) - if addrtype == constants.net.ADDRTYPE_P2PKH: - return 'p2pkh' - elif addrtype == constants.net.ADDRTYPE_P2SH: - return 'p2wpkh-p2sh' - raise Exception(f'unrecognized address: {repr(addr)}') + raise UnknownTxinType("cannot construct witness") @classmethod def input_script(self, txin: TxInput, *, estimate_size=False) -> str: @@ -830,7 +771,10 @@ class Transaction: if txin.is_native_segwit(): return '' - if desc := txin.script_descriptor: + dummy_desc = None + if estimate_size: + dummy_desc = create_dummy_descriptor_from_address(txin.address) + if desc := (txin.script_descriptor or dummy_desc): if desc.is_segwit(): if redeem_script := desc.expand().redeem_script: return construct_script([redeem_script]) @@ -839,24 +783,7 @@ class Transaction: if sol.script_sig is not None: return sol.script_sig.hex() return "" - - assert estimate_size # TODO xxxxx - _type = txin.script_type - pubkeys, sig_list = self.get_siglist(txin, estimate_size=estimate_size) - if _type in ('address', 'unknown') and estimate_size: - _type = self.guess_txintype_from_address(txin.address) - if _type == 'p2pk': - return construct_script([sig_list[0]]) - elif _type == 'p2sh': - # put op_0 before script - redeem_script = multisig_script(pubkeys, txin.num_sig) - return construct_script([0, *sig_list, redeem_script]) - elif _type == 'p2pkh': - return construct_script([sig_list[0], pubkeys[0]]) - elif _type == 'p2wpkh-p2sh': - redeem_script = bitcoin.p2wpkh_nested_script(pubkeys[0]) - return construct_script([redeem_script]) - raise UnknownTxinType(f'cannot construct scriptSig for txin_type: {_type}') + raise UnknownTxinType("cannot construct scriptSig") @classmethod def get_preimage_script(cls, txin: 'PartialTxInput') -> str: @@ -1599,10 +1526,10 @@ class PartialTxInput(TxInput, PSBTSection): return True if desc := self.script_descriptor: return desc.is_segwit() - _type = self.script_type - if _type == 'address' and guess_for_address: - _type = Transaction.guess_txintype_from_address(self.address) - return is_segwit_script_type(_type) + if guess_for_address: + dummy_desc = create_dummy_descriptor_from_address(self.address) + return dummy_desc.is_segwit() + return False # can be false-negative def already_has_some_signatures(self) -> bool: """Returns whether progress has been made towards completing this input."""