URI: 
       tMerge pull request #6685 from SomberNight/202010_bitcoin_script - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 8e9d6a4c913df9759fc54f0829d8569f223b35aa
   DIR parent 200f547a07f77e2edf04cd544a1bf954fb7c9b1c
  HTML Author: ghost43 <somber.night@protonmail.com>
       Date:   Sat, 24 Oct 2020 23:06:55 +0000
       
       Merge pull request #6685 from SomberNight/202010_bitcoin_script
       
       bitcoin/transaction: construct_script, and clean-ups
       Diffstat:
         M electrum/bitcoin.py                 |      65 +++++++++++++++++++++++--------
         M electrum/coinchooser.py             |       2 +-
         M electrum/gui/qt/paytoedit.py        |       9 ++++-----
         M electrum/lnsweep.py                 |       4 ++--
         M electrum/lnutil.py                  |     122 ++++++++++++++++++++++---------
         M electrum/plugins/keepkey/keepkey.py |       2 +-
         M electrum/plugins/ledger/ledger.py   |       2 +-
         M electrum/plugins/safe_t/safe_t.py   |       2 +-
         M electrum/submarine_swaps.py         |       5 +++--
         M electrum/tests/test_transaction.py  |      80 ++++++++++++++++++++++++++++++-
         M electrum/transaction.py             |      98 +++++++++++++------------------
         M electrum/wallet.py                  |       4 ++--
       
       12 files changed, 270 insertions(+), 125 deletions(-)
       ---
   DIR diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py
       t@@ -24,11 +24,11 @@
        # SOFTWARE.
        
        import hashlib
       -from typing import List, Tuple, TYPE_CHECKING, Optional, Union
       +from typing import List, Tuple, TYPE_CHECKING, Optional, Union, Sequence
        import enum
        from enum import IntEnum, Enum
        
       -from .util import bfh, bh2u, BitcoinException, assert_bytes, to_bytes, inv_dict
       +from .util import bfh, bh2u, BitcoinException, assert_bytes, to_bytes, inv_dict, is_hex_str
        from . import version
        from . import segwit_addr
        from . import constants
       t@@ -299,6 +299,38 @@ def add_number_to_script(i: int) -> bytes:
            return bfh(push_script(script_num_to_hex(i)))
        
        
       +def construct_witness(items: Sequence[Union[str, int, bytes]]) -> str:
       +    """Constructs a witness from the given stack items."""
       +    witness = var_int(len(items))
       +    for item in items:
       +        if type(item) is int:
       +            item = script_num_to_hex(item)
       +        elif isinstance(item, (bytes, bytearray)):
       +            item = bh2u(item)
       +        else:
       +            assert is_hex_str(item)
       +        witness += witness_push(item)
       +    return witness
       +
       +
       +def construct_script(items: Sequence[Union[str, int, bytes, opcodes]]) -> str:
       +    """Constructs bitcoin script from given items."""
       +    script = ''
       +    for item in items:
       +        if isinstance(item, opcodes):
       +            script += item.hex()
       +        elif type(item) is int:
       +            script += add_number_to_script(item).hex()
       +        elif isinstance(item, (bytes, bytearray)):
       +            script += push_script(item.hex())
       +        elif isinstance(item, str):
       +            assert is_hex_str(item)
       +            script += push_script(item)
       +        else:
       +            raise Exception(f'unexpected item for script: {item!r}')
       +    return script
       +
       +
        def relayfee(network: 'Network' = None) -> int:
            """Returns feerate in sat/kbyte."""
            from .simple_config import FEERATE_DEFAULT_RELAY, FEERATE_MAX_RELAY
       t@@ -374,12 +406,12 @@ def script_to_p2wsh(script: str, *, net=None) -> str:
            return hash_to_segwit_addr(sha256(bfh(script)), witver=0, net=net)
        
        def p2wpkh_nested_script(pubkey: str) -> str:
       -    pkh = bh2u(hash_160(bfh(pubkey)))
       -    return '00' + push_script(pkh)
       +    pkh = hash_160(bfh(pubkey))
       +    return construct_script([0, pkh])
        
        def p2wsh_nested_script(witness_script: str) -> str:
       -    wsh = bh2u(sha256(bfh(witness_script)))
       -    return '00' + push_script(wsh)
       +    wsh = sha256(bfh(witness_script))
       +    return construct_script([0, wsh])
        
        def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str:
            if net is None: net = constants.net
       t@@ -424,16 +456,12 @@ def address_to_script(addr: str, *, net=None) -> str:
            if witprog is not None:
                if not (0 <= witver <= 16):
                    raise BitcoinException(f'impossible witness version: {witver}')
       -        script = bh2u(add_number_to_script(witver))
       -        script += push_script(bh2u(bytes(witprog)))
       -        return script
       +        return construct_script([witver, bytes(witprog)])
            addrtype, hash_160_ = b58_address_to_hash160(addr)
            if addrtype == net.ADDRTYPE_P2PKH:
                script = pubkeyhash_to_p2pkh_script(bh2u(hash_160_))
            elif addrtype == net.ADDRTYPE_P2SH:
       -        script = opcodes.OP_HASH160.hex()
       -        script += push_script(bh2u(hash_160_))
       -        script += opcodes.OP_EQUAL.hex()
       +        script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL])
            else:
                raise BitcoinException(f'unknown address type: {addrtype}')
            return script
       t@@ -481,13 +509,16 @@ def script_to_scripthash(script: str) -> str:
            return bh2u(bytes(reversed(h)))
        
        def public_key_to_p2pk_script(pubkey: str) -> str:
       -    return push_script(pubkey) + opcodes.OP_CHECKSIG.hex()
       +    return construct_script([pubkey, opcodes.OP_CHECKSIG])
        
        def pubkeyhash_to_p2pkh_script(pubkey_hash160: str) -> str:
       -    script = bytes([opcodes.OP_DUP, opcodes.OP_HASH160]).hex()
       -    script += push_script(pubkey_hash160)
       -    script += bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]).hex()
       -    return script
       +    return construct_script([
       +        opcodes.OP_DUP,
       +        opcodes.OP_HASH160,
       +        pubkey_hash160,
       +        opcodes.OP_EQUALVERIFY,
       +        opcodes.OP_CHECKSIG
       +    ])
        
        
        __b58chars = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
   DIR diff --git a/electrum/coinchooser.py b/electrum/coinchooser.py
       t@@ -120,7 +120,7 @@ class CoinChooserBase(Logger):
                constant_fee = fee_estimator_vb(2000) == fee_estimator_vb(200)
        
                def make_Bucket(desc: str, coins: List[PartialTxInput]):
       -            witness = any(Transaction.is_segwit_input(coin, guess_for_address=True) for coin in coins)
       +            witness = any(coin.is_segwit(guess_for_address=True) for coin in coins)
                    # note that we're guessing whether the tx uses segwit based
                    # on this single bucket
                    weight = sum(Transaction.estimated_input_weight(coin, witness)
   DIR diff --git a/electrum/gui/qt/paytoedit.py b/electrum/gui/qt/paytoedit.py
       t@@ -31,8 +31,8 @@ from PyQt5.QtGui import QFontMetrics, QFont
        
        from electrum import bitcoin
        from electrum.util import bfh, maybe_extract_bolt11_invoice
       -from electrum.transaction import push_script, PartialTxOutput
       -from electrum.bitcoin import opcodes
       +from electrum.transaction import PartialTxOutput
       +from electrum.bitcoin import opcodes, construct_script
        from electrum.logging import Logger
        from electrum.lnaddr import LnDecodeException
        
       t@@ -111,11 +111,10 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
                for word in x.split():
                    if word[0:3] == 'OP_':
                        opcode_int = opcodes[word]
       -                assert opcode_int < 256  # opcode is single-byte
       -                script += bitcoin.int_to_hex(opcode_int)
       +                script += construct_script([opcode_int])
                    else:
                        bfh(word)  # to test it is hex data
       -                script += push_script(word)
       +                script += construct_script([word])
                return script
        
            def parse_amount(self, x):
   DIR diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
       t@@ -6,7 +6,7 @@ from typing import Optional, Dict, List, Tuple, TYPE_CHECKING, NamedTuple, Calla
        from enum import Enum, auto
        
        from .util import bfh, bh2u
       -from .bitcoin import redeem_script_to_address, dust_threshold
       +from .bitcoin import redeem_script_to_address, dust_threshold, construct_witness
        from . import ecc
        from .lnutil import (make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script,
                             derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey,
       t@@ -15,7 +15,7 @@ from .lnutil import (make_commitment_output_to_remote_address, make_commitment_o
                             get_ordered_channel_configs, privkey_to_pubkey, get_per_commitment_secret_from_seed,
                             RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret, SENT, RECEIVED,
                             map_htlcs_to_ctx_output_idxs, Direction)
       -from .transaction import (Transaction, TxOutput, construct_witness, PartialTransaction, PartialTxInput,
       +from .transaction import (Transaction, TxOutput, PartialTransaction, PartialTxInput,
                                  PartialTxOutput, TxOutpoint)
        from .simple_config import SimpleConfig
        from .logging import get_logger, Logger
   DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py
       t@@ -19,7 +19,8 @@ from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOut
                                  PartialTxOutput, opcodes, TxOutput)
        from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number
        from . import ecc, bitcoin, crypto, transaction
       -from .bitcoin import push_script, redeem_script_to_address, address_to_script
       +from .bitcoin import (push_script, redeem_script_to_address, address_to_script,
       +                      construct_witness, construct_script)
        from . import segwit_addr
        from .i18n import _
        from .lnaddr import lndecode
       t@@ -452,13 +453,17 @@ def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_dela
            assert type(local_feerate) is int
            assert type(revocationpubkey) is bytes
            assert type(local_delayedpubkey) is bytes
       -    script = bytes([opcodes.OP_IF]) \
       -        + bfh(push_script(bh2u(revocationpubkey))) \
       -        + bytes([opcodes.OP_ELSE]) \
       -        + bitcoin.add_number_to_script(to_self_delay) \
       -        + bytes([opcodes.OP_CHECKSEQUENCEVERIFY, opcodes.OP_DROP]) \
       -        + bfh(push_script(bh2u(local_delayedpubkey))) \
       -        + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG])
       +    script = bfh(construct_script([
       +        opcodes.OP_IF,
       +        revocationpubkey,
       +        opcodes.OP_ELSE,
       +        to_self_delay,
       +        opcodes.OP_CHECKSEQUENCEVERIFY,
       +        opcodes.OP_DROP,
       +        local_delayedpubkey,
       +        opcodes.OP_ENDIF,
       +        opcodes.OP_CHECKSIG,
       +    ]))
        
            p2wsh = bitcoin.redeem_script_to_address('p2wsh', bh2u(script))
            weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT
       t@@ -475,7 +480,7 @@ def make_htlc_tx_witness(remotehtlcsig: bytes, localhtlcsig: bytes,
            assert type(localhtlcsig) is bytes
            assert type(payment_preimage) is bytes
            assert type(witness_script) is bytes
       -    return bfh(transaction.construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script]))
       +    return bfh(construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script]))
        
        def make_htlc_tx_inputs(htlc_output_txid: str, htlc_output_index: int,
                                amount_msat: int, witness_script: str) -> List[PartialTxInput]:
       t@@ -503,13 +508,35 @@ def make_offered_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
            assert type(remote_htlcpubkey) is bytes
            assert type(local_htlcpubkey) is bytes
            assert type(payment_hash) is bytes
       -    return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey))))\
       -        + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \
       -        + bfh(push_script(bh2u(remote_htlcpubkey)))\
       -        + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) + bitcoin.add_number_to_script(32) + bytes([opcodes.OP_EQUAL, opcodes.OP_NOTIF, opcodes.OP_DROP])\
       -        + bitcoin.add_number_to_script(2) + bytes([opcodes.OP_SWAP]) + bfh(push_script(bh2u(local_htlcpubkey))) + bitcoin.add_number_to_script(2)\
       -        + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_HASH160])\
       -        + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) + bytes([opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF])
       +    script = bfh(construct_script([
       +        opcodes.OP_DUP,
       +        opcodes.OP_HASH160,
       +        bitcoin.hash_160(revocation_pubkey),
       +        opcodes.OP_EQUAL,
       +        opcodes.OP_IF,
       +        opcodes.OP_CHECKSIG,
       +        opcodes.OP_ELSE,
       +        remote_htlcpubkey,
       +        opcodes.OP_SWAP,
       +        opcodes.OP_SIZE,
       +        32,
       +        opcodes.OP_EQUAL,
       +        opcodes.OP_NOTIF,
       +        opcodes.OP_DROP,
       +        2,
       +        opcodes.OP_SWAP,
       +        local_htlcpubkey,
       +        2,
       +        opcodes.OP_CHECKMULTISIG,
       +        opcodes.OP_ELSE,
       +        opcodes.OP_HASH160,
       +        crypto.ripemd(payment_hash),
       +        opcodes.OP_EQUALVERIFY,
       +        opcodes.OP_CHECKSIG,
       +        opcodes.OP_ENDIF,
       +        opcodes.OP_ENDIF,
       +    ]))
       +    return script
        
        def make_received_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
                               local_htlcpubkey: bytes, payment_hash: bytes, cltv_expiry: int) -> bytes:
       t@@ -517,22 +544,38 @@ def make_received_htlc(revocation_pubkey: bytes, remote_htlcpubkey: bytes,
                assert type(i) is bytes
            assert type(cltv_expiry) is int
        
       -    return bytes([opcodes.OP_DUP, opcodes.OP_HASH160]) \
       -        + bfh(push_script(bh2u(bitcoin.hash_160(revocation_pubkey)))) \
       -        + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_CHECKSIG, opcodes.OP_ELSE]) \
       -        + bfh(push_script(bh2u(remote_htlcpubkey))) \
       -        + bytes([opcodes.OP_SWAP, opcodes.OP_SIZE]) \
       -        + bitcoin.add_number_to_script(32) \
       -        + bytes([opcodes.OP_EQUAL, opcodes.OP_IF, opcodes.OP_HASH160]) \
       -        + bfh(push_script(bh2u(crypto.ripemd(payment_hash)))) \
       -        + bytes([opcodes.OP_EQUALVERIFY]) \
       -        + bitcoin.add_number_to_script(2) \
       -        + bytes([opcodes.OP_SWAP]) \
       -        + bfh(push_script(bh2u(local_htlcpubkey))) \
       -        + bitcoin.add_number_to_script(2) \
       -        + bytes([opcodes.OP_CHECKMULTISIG, opcodes.OP_ELSE, opcodes.OP_DROP]) \
       -        + bitcoin.add_number_to_script(cltv_expiry) \
       -        + bytes([opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF])
       +    script = bfh(construct_script([
       +        opcodes.OP_DUP,
       +        opcodes.OP_HASH160,
       +        bitcoin.hash_160(revocation_pubkey),
       +        opcodes.OP_EQUAL,
       +        opcodes.OP_IF,
       +        opcodes.OP_CHECKSIG,
       +        opcodes.OP_ELSE,
       +        remote_htlcpubkey,
       +        opcodes.OP_SWAP,
       +        opcodes.OP_SIZE,
       +        32,
       +        opcodes.OP_EQUAL,
       +        opcodes.OP_IF,
       +        opcodes.OP_HASH160,
       +        crypto.ripemd(payment_hash),
       +        opcodes.OP_EQUALVERIFY,
       +        2,
       +        opcodes.OP_SWAP,
       +        local_htlcpubkey,
       +        2,
       +        opcodes.OP_CHECKMULTISIG,
       +        opcodes.OP_ELSE,
       +        opcodes.OP_DROP,
       +        cltv_expiry,
       +        opcodes.OP_CHECKLOCKTIMEVERIFY,
       +        opcodes.OP_DROP,
       +        opcodes.OP_CHECKSIG,
       +        opcodes.OP_ENDIF,
       +        opcodes.OP_ENDIF,
       +    ]))
       +    return script
        
        def make_htlc_output_witness_script(is_received_htlc: bool, remote_revocation_pubkey: bytes, remote_htlc_pubkey: bytes,
                                            local_htlc_pubkey: bytes, payment_hash: bytes, cltv_expiry: Optional[int]) -> bytes:
       t@@ -796,9 +839,18 @@ def make_commitment(
        
        def make_commitment_output_to_local_witness_script(
                revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> bytes:
       -    local_script = bytes([opcodes.OP_IF]) + bfh(push_script(bh2u(revocation_pubkey))) + bytes([opcodes.OP_ELSE]) + bitcoin.add_number_to_script(to_self_delay) \
       -                   + bytes([opcodes.OP_CHECKSEQUENCEVERIFY, opcodes.OP_DROP]) + bfh(push_script(bh2u(delayed_pubkey))) + bytes([opcodes.OP_ENDIF, opcodes.OP_CHECKSIG])
       -    return local_script
       +    script = bfh(construct_script([
       +        opcodes.OP_IF,
       +        revocation_pubkey,
       +        opcodes.OP_ELSE,
       +        to_self_delay,
       +        opcodes.OP_CHECKSEQUENCEVERIFY,
       +        opcodes.OP_DROP,
       +        delayed_pubkey,
       +        opcodes.OP_ENDIF,
       +        opcodes.OP_CHECKSIG,
       +    ]))
       +    return script
        
        def make_commitment_output_to_local_address(
                revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str:
   DIR diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py
       t@@ -53,7 +53,7 @@ class KeepKey_KeyStore(Hardware_KeyStore):
                prev_tx = {}
                for txin in tx.inputs():
                    tx_hash = txin.prevout.txid.hex()
       -            if txin.utxo is None and not Transaction.is_segwit_input(txin):
       +            if txin.utxo is None and not txin.is_segwit():
                        raise UserFacingException(_('Missing previous tx for legacy input.'))
                    prev_tx[tx_hash] = txin.utxo
        
   DIR diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py
       t@@ -386,7 +386,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
        
                    redeemScript = Transaction.get_preimage_script(txin)
                    txin_prev_tx = txin.utxo
       -            if txin_prev_tx is None and not Transaction.is_segwit_input(txin):
       +            if txin_prev_tx is None and not txin.is_segwit():
                        raise UserFacingException(_('Missing previous tx for legacy input.'))
                    txin_prev_tx_raw = txin_prev_tx.serialize() if txin_prev_tx else None
                    inputs.append([txin_prev_tx_raw,
   DIR diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py
       t@@ -51,7 +51,7 @@ class SafeTKeyStore(Hardware_KeyStore):
                prev_tx = {}
                for txin in tx.inputs():
                    tx_hash = txin.prevout.txid.hex()
       -            if txin.utxo is None and not Transaction.is_segwit_input(txin):
       +            if txin.utxo is None and not txin.is_segwit():
                        raise UserFacingException(_('Missing previous tx for legacy input.'))
                    prev_tx[tx_hash] = txin.utxo
        
   DIR diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
       t@@ -7,8 +7,9 @@ import attr
        
        from .crypto import sha256, hash_160
        from .ecc import ECPrivkey
       -from .bitcoin import address_to_script, script_to_p2wsh, redeem_script_to_address, opcodes, p2wsh_nested_script, push_script, is_segwit_address
       -from .transaction import TxOutpoint, PartialTxInput, PartialTxOutput, PartialTransaction, construct_witness
       +from .bitcoin import (script_to_p2wsh, opcodes, p2wsh_nested_script, push_script,
       +                      is_segwit_address, construct_witness)
       +from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction
        from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey
        from .util import log_exceptions
        from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY, ln_dummy_address, LN_MAX_HTLC_VALUE_MSAT
   DIR diff --git a/electrum/tests/test_transaction.py b/electrum/tests/test_transaction.py
       t@@ -1,10 +1,15 @@
        from typing import NamedTuple, Union
        
        from electrum import transaction, bitcoin
       -from electrum.transaction import convert_raw_tx_to_hex, tx_from_any, Transaction, PartialTransaction
       +from electrum.transaction import (convert_raw_tx_to_hex, tx_from_any, Transaction,
       +                                  PartialTransaction, TxOutpoint, PartialTxInput,
       +                                  PartialTxOutput)
        from electrum.util import bh2u, bfh
       +from electrum.bitcoin import (deserialize_privkey, opcodes,
       +                              construct_script, construct_witness)
       +from electrum.ecc import ECPrivkey
        
       -from . import ElectrumTestCase
       +from . import ElectrumTestCase, TestCaseForTestnet
        
        signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b424eb4031000000006c493046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d985012102e61d176da16edd1d258a200ad9759ef63adf8e14cd97f53227bae35cdb84d2f6ffffffff0140420f00000000001976a914230ac37834073a42146f11ef8414ae929feaafc388ac00000000'
        v2_blob = "0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700"
       t@@ -840,3 +845,74 @@ class TestTransaction(ElectrumTestCase):
                self._run_naive_tests_on_tx(raw_tx, txid)
        
        # txns from Bitcoin Core ends <---
       +
       +
       +class TestTransactionTestnet(TestCaseForTestnet):
       +
       +    def test_spending_op_cltv_p2sh(self):
       +        # from https://github.com/brianddk/reddit/blob/8ca383c9e00cb5a4c1201d1bab534d5886d3cb8f/python/elec-p2sh-hodl.py
       +        wif = 'cQNjiPwYKMBr2oB3bWzf3rgBsu198xb8Nxxe51k6D3zVTA98L25N'
       +        sats = 9999
       +        sats_less_fees = sats - 200
       +        locktime = 1602565200
       +
       +        # Build the Transaction Input
       +        _, privkey, compressed = deserialize_privkey(wif)
       +        pubkey = ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
       +        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,
       +        ]))
       +        txin.redeem_script = redeem_script
       +
       +        # Build the Transaction Output
       +        txout = PartialTxOutput.from_address_and_value(
       +            'tb1qv9hg20f0g08d460l67ph6p4ukwt7m0ttqzj7mk', sats_less_fees)
       +
       +        # Build and sign the transaction
       +        tx = PartialTransaction.from_io([txin], [txout], locktime=locktime, version=1)
       +        sig = tx.sign_txin(0, privkey)
       +        txin.script_sig = bfh(construct_script([sig, redeem_script]))
       +
       +        # note: in testnet3 chain, signature differs (no low-R grinding),
       +        # so txid there is: a8110bbdd40d65351f615897d98c33cbe33e4ebedb4ba2fc9e8c644423dadc93
       +        self.assertEqual('3266138b0b79007f35ac9a1824e294763708bd4a6440b5c227f4e1251b66e92b',
       +                         tx.txid())
       +
       +    def test_spending_op_cltv_p2wsh(self):
       +        wif = 'cSw3py1CQa2tmzzDm3ghQVrgqqNuFhUyBXjABge5j8KRxzd6kaFj'
       +        sats = 99_878
       +        sats_less_fees = sats - 300
       +        locktime = 1602572140
       +
       +        # Build the Transaction Input
       +        _, privkey, compressed = deserialize_privkey(wif)
       +        pubkey = ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
       +        witness_script = bfh(construct_script([
       +            locktime, opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, pubkey, opcodes.OP_CHECKSIG,
       +        ]))
       +        from_addr = bitcoin.script_to_p2wsh(witness_script.hex())
       +        self.assertEqual("tb1q9dn6qke9924xe3zmptmhrdge0s043pjxpjndypgnu2t9fvsd4crs2qjuer", from_addr)
       +        prevout = TxOutpoint(txid=bfh('8680971efd5203025cffe746f8598d0a704fae81f236ffe009c2609ec673d59a'), out_idx=0)
       +        txin = PartialTxInput(prevout=prevout)
       +        txin._trusted_value_sats = sats
       +        txin.nsequence = 0
       +        txin.script_sig = b''
       +        txin.witness_script = witness_script
       +
       +        # Build the Transaction Output
       +        txout = PartialTxOutput.from_address_and_value(
       +            'tb1qtgsfkgptcxdn6dz6wh8c4dguk3cezwne5j5c47', sats_less_fees)
       +
       +        # Build and sign the transaction
       +        tx = PartialTransaction.from_io([txin], [txout], locktime=locktime, version=2)
       +        sig = tx.sign_txin(0, privkey)
       +        txin.witness = bfh(construct_witness([sig, witness_script]))
       +
       +        self.assertEqual('1cdb274755b144090c7134b6459e8d4cb6b4552fe620102836d751e8389b2694',
       +                         tx.txid())
       +        self.assertEqual('020000000001019ad573c69e60c209e0ff36f281ae4f700a8d59f846e7ff5c020352fd1e97808600000000000000000001fa840100000000001600145a209b202bc19b3d345a75cf8ab51cb471913a790247304402207b191c1e3ff1a2d3541770b496c9f871406114746b3aa7347ec4ef0423d3a975022043d3a746fa7a794d97e95d74b6d17d618dfc4cd7644476813e08006f271e51bd012a046c4f855fb1752102aec53aa5f347219a7378b13006eb16ce48125f9cf14f04a5509a565ad5e51507ac6c4f855f',
       +                         tx.serialize())
   DIR diff --git a/electrum/transaction.py b/electrum/transaction.py
       t@@ -48,7 +48,7 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
                              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,
       -                      base_encode)
       +                      base_encode, construct_witness, construct_script)
        from .crypto import sha256d
        from .logging import get_logger
        
       t@@ -243,6 +243,11 @@ class TxInput:
                n = vds.read_compact_size()
                return list(vds.read_bytes(vds.read_compact_size()) for i in range(n))
        
       +    def is_segwit(self, *, guess_for_address=False) -> bool:
       +        if self.witness not in (b'\x00', b'', None):
       +            return True
       +        return False
       +
        
        class BCDataStream(object):
            """Workalike python implementation of Bitcoin's CDataStream class."""
       t@@ -479,18 +484,6 @@ def parse_input(vds: BCDataStream) -> TxInput:
            return TxInput(prevout=prevout, script_sig=script_sig, nsequence=nsequence)
        
        
       -def construct_witness(items: Sequence[Union[str, int, bytes]]) -> str:
       -    """Constructs a witness from the given stack items."""
       -    witness = var_int(len(items))
       -    for item in items:
       -        if type(item) is int:
       -            item = bitcoin.script_num_to_hex(item)
       -        elif isinstance(item, (bytes, bytearray)):
       -            item = bh2u(item)
       -        witness += bitcoin.witness_push(item)
       -    return witness
       -
       -
        def parse_witness(vds: BCDataStream, txin: TxInput) -> None:
            n = vds.read_compact_size()
            witness_elements = list(vds.read_bytes(vds.read_compact_size()) for i in range(n))
       t@@ -512,10 +505,7 @@ def parse_output(vds: BCDataStream) -> TxOutput:
        def multisig_script(public_keys: Sequence[str], m: int) -> str:
            n = len(public_keys)
            assert 1 <= m <= n <= 15, f'm {m}, n {n}'
       -    op_m = bh2u(add_number_to_script(m))
       -    op_n = bh2u(add_number_to_script(n))
       -    keylist = [push_script(k) for k in public_keys]
       -    return op_m + ''.join(keylist) + op_n + opcodes.OP_CHECKMULTISIG.hex()
       +    return construct_script([m, *public_keys, n, opcodes.OP_CHECKMULTISIG])
        
        
        
       t@@ -650,8 +640,8 @@ class Transaction:
                assert isinstance(txin, PartialTxInput)
        
                _type = txin.script_type
       -        if not cls.is_segwit_input(txin):
       -            return '00'
       +        if not txin.is_segwit():
       +            return construct_witness([])
        
                if _type in ('address', 'unknown') and estimate_size:
                    _type = cls.guess_txintype_from_address(txin.address)
       t@@ -660,29 +650,12 @@ class Transaction:
                    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])
       +            return construct_witness([0, *sig_list, witness_script])
                elif _type in ['p2pk', 'p2pkh', 'p2sh']:
       -            return '00'
       +            return construct_witness([])
                raise UnknownTxinType(f'cannot construct witness for txin_type: {_type}')
        
            @classmethod
       -    def is_segwit_input(cls, txin: 'TxInput', *, guess_for_address=False) -> bool:
       -        if txin.witness not in (b'\x00', b'', None):
       -            return True
       -        if not isinstance(txin, PartialTxInput):
       -            return False
       -        if txin.is_native_segwit() or txin.is_p2sh_segwit():
       -            return True
       -        if txin.is_native_segwit() is False and txin.is_p2sh_segwit() is False:
       -            return False
       -        if txin.witness_script:
       -            return True
       -        _type = txin.script_type
       -        if _type == 'address' and guess_for_address:
       -            _type = cls.guess_txintype_from_address(txin.address)
       -        return is_segwit_script_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.
       t@@ -714,47 +687,46 @@ class Transaction:
                assert isinstance(txin, PartialTxInput)
        
                if txin.is_p2sh_segwit() and txin.redeem_script:
       -            return push_script(txin.redeem_script.hex())
       +            return construct_script([txin.redeem_script])
                if txin.is_native_segwit():
                    return ''
        
                _type = txin.script_type
                pubkeys, sig_list = self.get_siglist(txin, estimate_size=estimate_size)
       -        script = ''.join(push_script(x) for x in sig_list)
                if _type in ('address', 'unknown') and estimate_size:
                    _type = self.guess_txintype_from_address(txin.address)
                if _type == 'p2pk':
       -            return script
       +            return construct_script([sig_list[0]])
                elif _type == 'p2sh':
                    # put op_0 before script
       -            script = '00' + script
                    redeem_script = multisig_script(pubkeys, txin.num_sig)
       -            script += push_script(redeem_script)
       -            return script
       +            return construct_script([0, *sig_list, redeem_script])
                elif _type == 'p2pkh':
       -            script += push_script(pubkeys[0])
       -            return script
       +            return construct_script([sig_list[0], pubkeys[0]])
                elif _type in ['p2wpkh', 'p2wsh']:
                    return ''
                elif _type == 'p2wpkh-p2sh':
                    redeem_script = bitcoin.p2wpkh_nested_script(pubkeys[0])
       -            return push_script(redeem_script)
       +            return construct_script([redeem_script])
                elif _type == 'p2wsh-p2sh':
                    if estimate_size:
                        witness_script = ''
                    else:
                        witness_script = self.get_preimage_script(txin)
                    redeem_script = bitcoin.p2wsh_nested_script(witness_script)
       -            return push_script(redeem_script)
       +            return construct_script([redeem_script])
                raise UnknownTxinType(f'cannot construct scriptSig for txin_type: {_type}')
        
            @classmethod
            def get_preimage_script(cls, txin: 'PartialTxInput') -> str:
                if txin.witness_script:
       -            opcodes_in_witness_script = [x[0] for x in script_GetOp(txin.witness_script)]
       -            if opcodes.OP_CODESEPARATOR in opcodes_in_witness_script:
       +            if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(txin.witness_script)]:
                        raise Exception('OP_CODESEPARATOR black magic is not supported')
                    return txin.witness_script.hex()
       +        if not txin.is_segwit() and txin.redeem_script:
       +            if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(txin.redeem_script)]:
       +                raise Exception('OP_CODESEPARATOR black magic is not supported')
       +            return txin.redeem_script.hex()
        
                pubkeys = [pk.hex() for pk in txin.pubkeys]
                if txin.script_type in ['p2sh', 'p2wsh', 'p2wsh-p2sh']:
       t@@ -790,7 +762,7 @@ class Transaction:
                                                  hashOutputs=hashOutputs)
        
            def is_segwit(self, *, guess_for_address=False):
       -        return any(self.is_segwit_input(txin, guess_for_address=guess_for_address)
       +        return any(txin.is_segwit(guess_for_address=guess_for_address)
                           for txin in self.inputs())
        
            def invalidate_ser_cache(self):
       t@@ -848,7 +820,7 @@ class Transaction:
            def txid(self) -> Optional[str]:
                if self._cached_txid is None:
                    self.deserialize()
       -            all_segwit = all(self.is_segwit_input(x) for x in self.inputs())
       +            all_segwit = all(txin.is_segwit() for txin in self.inputs())
                    if not all_segwit and not self.is_complete():
                        return None
                    try:
       t@@ -892,7 +864,7 @@ class Transaction:
                script = cls.input_script(txin, estimate_size=True)
                input_size = len(cls.serialize_input(txin, script)) // 2
        
       -        if cls.is_segwit_input(txin, guess_for_address=True):
       +        if txin.is_segwit(guess_for_address=True):
                    witness_size = len(cls.serialize_witness(txin, estimate_size=True)) // 2
                else:
                    witness_size = 1 if is_segwit_tx else 0
       t@@ -1219,7 +1191,7 @@ class PartialTxInput(TxInput, PSBTSection):
                # without verifying the input amount. This means, given a maliciously modified PSBT,
                # for non-segwit inputs, we might end up burning coins as miner fees.
                if for_signing and False:
       -            if not Transaction.is_segwit_input(self) and self.witness_utxo:
       +            if not self.is_segwit() and self.witness_utxo:
                        raise PSBTInputConsistencyFailure(f"PSBT input validation: "
                                                          f"If a witness UTXO is provided, no non-witness signature may be created")
                if self.redeem_script and self.address:
       t@@ -1359,7 +1331,7 @@ class PartialTxInput(TxInput, PSBTSection):
                    return True
                if self.is_coinbase_input():
                    return True
       -        if self.script_sig is not None and not Transaction.is_segwit_input(self):
       +        if self.script_sig is not None and not self.is_segwit():
                    return True
                signatures = list(self.part_sigs.values())
                s = len(signatures)
       t@@ -1461,6 +1433,20 @@ class PartialTxInput(TxInput, PSBTSection):
                    self._is_p2sh_segwit = calc_if_p2sh_segwit_now()
                return self._is_p2sh_segwit
        
       +    def is_segwit(self, *, guess_for_address=False) -> bool:
       +        if super().is_segwit():
       +            return True
       +        if self.is_native_segwit() or self.is_p2sh_segwit():
       +            return True
       +        if self.is_native_segwit() is False and self.is_p2sh_segwit() is False:
       +            return False
       +        if self.witness_script:
       +            return True
       +        _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)
       +
            def already_has_some_signatures(self) -> bool:
                """Returns whether progress has been made towards completing this input."""
                return (self.part_sigs
       t@@ -1809,7 +1795,7 @@ class PartialTransaction(Transaction):
                    raise Exception("only SIGHASH_ALL signing is supported!")
                nHashType = int_to_hex(sighash, 4)
                preimage_script = self.get_preimage_script(txin)
       -        if self.is_segwit_input(txin):
       +        if txin.is_segwit():
                    if bip143_shared_txdigest_fields is None:
                        bip143_shared_txdigest_fields = self._calc_bip143_shared_txdigest_fields()
                    hashPrevouts = bip143_shared_txdigest_fields.hashPrevouts
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -2162,7 +2162,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                if all([txin.utxo for txin in tx.inputs()]):
                    return None
                # a single segwit input -> fine
       -        if len(tx.inputs()) == 1 and Transaction.is_segwit_input(tx.inputs()[0]) and tx.inputs()[0].witness_utxo:
       +        if len(tx.inputs()) == 1 and tx.inputs()[0].is_segwit() and tx.inputs()[0].witness_utxo:
                    return None
                # coinjoin or similar
                if any([not self.is_mine(txin.address) for txin in tx.inputs()]):
       t@@ -2170,7 +2170,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                            + _("The input amounts could not be verified as the previous transactions are missing.\n"
                                "The amount of money being spent CANNOT be verified."))
                # some inputs are legacy
       -        if any([not Transaction.is_segwit_input(txin) for txin in tx.inputs()]):
       +        if any([not txin.is_segwit() for txin in tx.inputs()]):
                    return (_("Warning") + ": "
                            + _("The fee could not be verified. Signing non-segwit inputs is risky:\n"
                                "if this transaction was maliciously modified before you sign,\n"