diff --git a/electrum/coinchooser.py b/electrum/coinchooser.py index 45555039a..6366f8a87 100644 --- a/electrum/coinchooser.py +++ b/electrum/coinchooser.py @@ -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) diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py index e7f5b1821..a96190a6b 100644 --- a/electrum/plugins/keepkey/keepkey.py +++ b/electrum/plugins/keepkey/keepkey.py @@ -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 diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index a8bf50a56..d85a3c935 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -384,7 +384,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, diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py index 1bb04823f..c8278abad 100644 --- a/electrum/plugins/safe_t/safe_t.py +++ b/electrum/plugins/safe_t/safe_t.py @@ -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 diff --git a/electrum/transaction.py b/electrum/transaction.py index aca0a61ad..2d49d6a33 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -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.""" @@ -635,7 +640,7 @@ class Transaction: assert isinstance(txin, PartialTxInput) _type = txin.script_type - if not cls.is_segwit_input(txin): + if not txin.is_segwit(): return construct_witness([]) if _type in ('address', 'unknown') and estimate_size: @@ -650,23 +655,6 @@ class Transaction: 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 @@ -771,7 +759,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): @@ -829,7 +817,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: @@ -873,7 +861,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 @@ -1200,7 +1188,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: @@ -1340,7 +1328,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) @@ -1442,6 +1430,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 @@ -1790,7 +1792,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 diff --git a/electrum/wallet.py b/electrum/wallet.py index 07c131e19..2efe9cb4e 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -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()]): @@ -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"