From 0508625afc79a1b5e47c3f0c308b4870990cef31 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 18 May 2025 14:39:05 +0000 Subject: [PATCH] transaction: add method verify_sig_for_txin This new `Transaction.verify_sig_for_txin` function is an instance method of `Transaction` instead of `PartialTransaction`. It takes a complete txin, a pubkey and a signature, and verifies the signature. - `get_preimage_script` is renamed to `get_scriptcode_for_sighash` and now effectively has two implementations: - the old impl became `PartialTxInput.get_scriptcode_for_sighash` - this assumes we are the ones constructing a spending txin and can have knowledge beyond what will be revealed onchain - the new impl is in the base class, `TxInput.get_scriptcode_for_sighash` - this assumes the txin is already "complete", and mimics a consensus-verifier by extracting the required fields from the already complete witness/scriptSig and the scriptpubkey of the funding utxo - `serialize_preimage` now does not require a PartialTransaction, it also works on the base class Transaction ----- I intend to use this for debugging only atm: I noticed TxBatcher sometimes creates invalid signatures by seeing that bitcoind rejects txs with `mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)`. However the txs in question have multiple txins, with some txins containing multiple signatures, and bitcoind does not tell us which txin/signature is invalid. Knowing which signature is invalid would be a start, but I can now add some temp debug logging to `serialize_preimage` to compare the message being signed with the message being verified. As can be seen from the tests, the signature and the pubkey needs to be manually extracted from the txin to be verified: we still don't have a script interpreter so we don't have logic to "verify a txin". However this new code adds logic to verify a signature for a txin/pubkey combo (which is a small part of an interpreter/verifier). --- .../plugins/digitalbitbox/digitalbitbox.py | 4 +- electrum/plugins/ledger/ledger.py | 2 +- electrum/transaction.py | 185 ++++++++++++++---- tests/test_transaction.py | 114 +++++++++++ 4 files changed, 262 insertions(+), 43 deletions(-) diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py index f3db13971..b96449452 100644 --- a/electrum/plugins/digitalbitbox/digitalbitbox.py +++ b/electrum/plugins/digitalbitbox/digitalbitbox.py @@ -565,10 +565,10 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): if p2pkhTransaction: tx_copy = copy.deepcopy(tx) # monkey-patch method of tx_copy instance to change serialization - def input_script(self, txin: PartialTxInput, *, estimate_size=False): + def input_script(self, txin: PartialTxInput, *, estimate_size=False) -> bytes: desc = txin.script_descriptor if isinstance(desc, descriptor.PKHDescriptor): - return Transaction.get_preimage_script(txin) + return txin.get_scriptcode_for_sighash() 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() diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index 60ffeaa7a..135f2867d 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -592,7 +592,7 @@ class Ledger_Client_Legacy(Ledger_Client): self.give_error("No matching pubkey for sign_transaction") # should never happen full_path = convert_bip32_intpath_to_strpath(full_path)[2:] - redeemScript = Transaction.get_preimage_script(txin).hex() + redeemScript = txin.get_scriptcode_for_sighash().hex() txin_prev_tx = txin.utxo if txin_prev_tx is None and not txin.is_segwit(): raise UserFacingException(_('Missing previous tx for legacy input.')) diff --git a/electrum/transaction.py b/electrum/transaction.py index ffe2648af..a8e1aa77f 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -218,7 +218,7 @@ class BIP143SharedTxDigestFields(NamedTuple): # witness v0 hashOutputs: bytes @classmethod - def from_tx(cls, tx: 'PartialTransaction') -> 'BIP143SharedTxDigestFields': + def from_tx(cls, tx: 'Transaction') -> 'BIP143SharedTxDigestFields': inputs = tx.inputs() outputs = tx.outputs() hashPrevouts = sha256d(b''.join(txin.prevout.serialize_to_network() for txin in inputs)) @@ -241,7 +241,7 @@ class BIP341SharedTxDigestFields(NamedTuple): # witness v1 sha_outputs: bytes @classmethod - def from_tx(cls, tx: 'PartialTransaction') -> 'BIP341SharedTxDigestFields': + def from_tx(cls, tx: 'Transaction') -> 'BIP341SharedTxDigestFields': inputs = tx.inputs() outputs = tx.outputs() sha_prevouts = sha256(b''.join(txin.prevout.serialize_to_network() for txin in inputs)) @@ -270,12 +270,12 @@ class SighashCache: self._witver0 = None # type: Optional[BIP143SharedTxDigestFields] self._witver1 = None # type: Optional[BIP341SharedTxDigestFields] - def get_witver0_data_for_tx(self, tx: 'PartialTransaction') -> BIP143SharedTxDigestFields: + def get_witver0_data_for_tx(self, tx: 'Transaction') -> BIP143SharedTxDigestFields: if self._witver0 is None: self._witver0 = BIP143SharedTxDigestFields.from_tx(tx) return self._witver0 - def get_witver1_data_for_tx(self, tx: 'PartialTransaction') -> BIP341SharedTxDigestFields: + def get_witver1_data_for_tx(self, tx: 'Transaction') -> BIP341SharedTxDigestFields: if self._witver1 is None: self._witver1 = BIP341SharedTxDigestFields.from_tx(tx) return self._witver1 @@ -342,6 +342,8 @@ class TxInput: self.__address = None # type: Optional[str] self.__value_sats = None # type: Optional[int] + self._is_taproot = None # type: Optional[bool] # None means unknown + def get_time_based_relative_locktime(self) -> Optional[int]: # see bip 68 if self.nsequence & (1<<31): @@ -451,6 +453,12 @@ class TxInput: return True return False + def is_taproot(self) -> Optional[bool]: + if self._is_taproot is None: + if self.address: + self._is_taproot = bitcoin.is_taproot_address(self.address) + return self._is_taproot + async def add_info_from_network( self, network: Optional['Network'], @@ -480,6 +488,67 @@ class TxInput: self.utxo = await fetch_from_network(txid=self.prevout.txid.hex()) return self.utxo is not None + def get_scriptcode_for_sighash(self) -> bytes: + """Reconstructs the scriptcode part of the preimage for OP_CHECKSIG, + for an already complete txin, in order to *verify the signature*. + """ + scriptpubkey = self.scriptpubkey + if scriptpubkey is None: + raise Exception("missing scriptpubkey. 'utxo' not set?") + script_type = get_script_type_from_output_script(scriptpubkey) + if script_type == "p2wsh": + wit_elems = self.witness_elements() + if not wit_elems: + raise Exception(f"missing witness for {script_type=}") + witness_script = wit_elems[-1] + if self.address != bitcoin.script_to_p2wsh(witness_script): + raise Exception("witness_script from witness does not match address") + if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(witness_script)]: + raise Exception('OP_CODESEPARATOR black magic is not supported') + return witness_script + elif script_type == "p2wpkh": + pkh = scriptpubkey[-20:] + assert len(pkh) == 20 + p2pkh_script = bitcoin.pubkeyhash_to_p2pkh_script(pkh) + return p2pkh_script + elif script_type == "p2sh": + if not self.script_sig: + raise Exception(f"missing script_sig for {script_type=}") + parsed_ss = list(script_GetOp(self.script_sig)) + redeem_script = parsed_ss[-1][1] + if self.address != bitcoin.hash160_to_p2sh(hash_160(redeem_script)): + raise Exception("redeem_script from script_sig does not match address") + if self.is_segwit(): # p2sh-wrapped-segwit + inner_script_type = get_script_type_from_output_script(redeem_script) + if inner_script_type == "p2wsh": + wit_elems = self.witness_elements() + witness_script = wit_elems[-1] + if redeem_script != bitcoin.p2wsh_nested_script(witness_script): + raise Exception("witness_script from witness does not match redeem_script") + if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(witness_script)]: + raise Exception('OP_CODESEPARATOR black magic is not supported') + return witness_script + elif inner_script_type == "p2wpkh": + pkh = self.script_sig[-20:] + assert len(pkh) == 20 + p2pkh_script = bitcoin.pubkeyhash_to_p2pkh_script(pkh) + return p2pkh_script + else: + raise Exception(f"unexpected {inner_script_type=} wrapped in p2sh. script_sig={self.script_sig.hex()}") + else: + if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(redeem_script)]: + raise Exception('OP_CODESEPARATOR black magic is not supported') + return redeem_script + elif script_type in ("p2pkh", "p2pk"): + # For "raw scriptpubkey" case, it is usually the scriptPubKey that is signed. + # Complications for the general case: + # - signatures should be removed ("FindAndDelete") + # - OP_CODESEPARATOR black magic + return scriptpubkey + else: + raise Exception(f"cannot handle {script_type=} ({scriptpubkey.hex()=})") + raise Exception("should not get here") + class BCDataStream(object): """Workalike python implementation of Bitcoin's CDataStream class.""" @@ -670,6 +739,7 @@ class OPGeneric: OPPushDataPubkey = OPPushDataGeneric(lambda x: x in (33, 65)) OP_ANYSEGWIT_VERSION = OPGeneric(lambda x: x in list(range(opcodes.OP_1, opcodes.OP_16 + 1))) +SCRIPTPUBKEY_TEMPLATE_P2PK = [OPPushDataGeneric(lambda x: x in (33, 65)), opcodes.OP_CHECKSIG] SCRIPTPUBKEY_TEMPLATE_P2PKH = [opcodes.OP_DUP, opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG] @@ -677,6 +747,7 @@ SCRIPTPUBKEY_TEMPLATE_P2SH = [opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x SCRIPTPUBKEY_TEMPLATE_WITNESS_V0 = [opcodes.OP_0, OPPushDataGeneric(lambda x: x in (20, 32))] SCRIPTPUBKEY_TEMPLATE_P2WPKH = [opcodes.OP_0, OPPushDataGeneric(lambda x: x == 20)] SCRIPTPUBKEY_TEMPLATE_P2WSH = [opcodes.OP_0, OPPushDataGeneric(lambda x: x == 32)] +SCRIPTPUBKEY_TEMPLATE_P2TR = [opcodes.OP_1, OPPushDataGeneric(lambda x: x == 32)] SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT = [OP_ANYSEGWIT_VERSION, OPPushDataGeneric(lambda x: x in list(range(2, 40 + 1)))] @@ -741,13 +812,15 @@ def match_script_against_template(script, template, debug=False) -> bool: return True -def get_script_type_from_output_script(_bytes: bytes) -> Optional[str]: - if _bytes is None: +def get_script_type_from_output_script(scriptpubkey: bytes) -> Optional[str]: + if scriptpubkey is None: return None try: - decoded = [x for x in script_GetOp(_bytes)] + decoded = [x for x in script_GetOp(scriptpubkey)] except MalformedBitcoinScript: return None + if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2PK): + return 'p2pk' if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2PKH): return 'p2pkh' if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2SH): @@ -756,6 +829,8 @@ def get_script_type_from_output_script(_bytes: bytes) -> Optional[str]: return 'p2wpkh' if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2WSH): return 'p2wsh' + if match_script_against_template(decoded, SCRIPTPUBKEY_TEMPLATE_P2TR): + return 'p2tr' return None @@ -974,6 +1049,7 @@ class Transaction: self, txin_index: int, *, + sighash: Optional[int] = None, sighash_cache: SighashCache = None, ) -> bytes: nVersion = int.to_bytes(self.version, length=4, byteorder="little", signed=True) @@ -981,9 +1057,12 @@ class Transaction: inputs = self.inputs() outputs = self.outputs() txin = inputs[txin_index] - sighash = txin.sighash - if sighash is None: - sighash = Sighash.DEFAULT if txin.is_taproot() else Sighash.ALL + if isinstance(txin, PartialTxInput): + if sighash is None: + sighash = txin.sighash + if sighash is None: + sighash = Sighash.DEFAULT if txin.is_taproot() else Sighash.ALL + assert sighash is not None if not Sighash.is_valid(sighash, is_taproot=txin.is_taproot()): raise Exception(f"SIGHASH_FLAG ({sighash}) not supported!") if sighash_cache is None: @@ -1044,7 +1123,7 @@ class Transaction: else: hashOutputs = bytes(32) outpoint = txin.prevout.serialize_to_network() - preimage_script = self.get_preimage_script(txin) + preimage_script = txin.get_scriptcode_for_sighash() scriptCode = var_int(len(preimage_script)) + preimage_script amount = int.to_bytes(txin.value_sats(), length=8, byteorder="little", signed=False) nSequence = int.to_bytes(txin.nsequence, length=4, byteorder="little", signed=False) @@ -1054,7 +1133,7 @@ class Transaction: else: # legacy sighash (pre-segwit) if sighash != Sighash.ALL: raise Exception(f"SIGHASH_FLAG ({sighash}) not supported! (for legacy sighash)") - preimage_script = self.get_preimage_script(txin) + preimage_script = txin.get_scriptcode_for_sighash() txins = var_int(len(inputs)) + b"".join( txin.serialize_to_network(script_sig=preimage_script if txin_index==k else b"") for k, txin in enumerate(inputs)) @@ -1064,23 +1143,24 @@ class Transaction: return preimage raise Exception("should not reach this") - @classmethod - def get_preimage_script(cls, txin: 'PartialTxInput') -> bytes: - if txin.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 - 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 - - if desc := txin.script_descriptor: - sc = desc.expand() - if script := sc.scriptcode_for_sighash: - return script - raise Exception(f"don't know scriptcode for descriptor: {desc.to_string()}") - raise UnknownTxinType(f'cannot construct preimage_script') + def verify_sig_for_txin( + self, + *, + txin_index: int, + pubkey_bytes: bytes, + sig: bytes, + sighash_cache: SighashCache = None, + ) -> bool: + txin = self.inputs()[txin_index] + if txin.is_taproot(): + raise Exception("not implemented") # TODO + else: + der_sig, sighash = sig[:-1], sig[-1] + pre_hash = self.serialize_preimage(txin_index, sighash=sighash, sighash_cache=sighash_cache) + pubkey = ecc.ECPubkey(pubkey_bytes) + msg_hash = sha256d(pre_hash) + sig64 = ecc.ecdsa_sig64_from_der_sig(der_sig) + return pubkey.ecdsa_verify(sig64, msg_hash) def is_segwit(self, *, guess_for_address=False): return any(txin.is_segwit(guess_for_address=guess_for_address) @@ -1580,7 +1660,7 @@ class PartialTxInput(TxInput, PSBTSection): self._witness_utxo = None # type: Optional[TxOutput] self.sigs_ecdsa = {} # type: Dict[bytes, bytes] # pubkey -> sig self.tap_key_sig = None # type: Optional[bytes] # sig for taproot key-path-spending - self.sighash = None # type: Optional[int] + self.sighash = None # type: Optional[int] # note: wrong abstraction level. should be per-signature self.bip32_paths = {} # type: Dict[bytes, Tuple[bytes, Sequence[int]]] # pubkey -> (xpub_fingerprint, path) self.redeem_script = None # type: Optional[bytes] self.witness_script = None # type: Optional[bytes] @@ -1594,7 +1674,6 @@ class PartialTxInput(TxInput, PSBTSection): self._trusted_address = None # type: Optional[str] self._is_p2sh_segwit = None # type: Optional[bool] # None means unknown self._is_native_segwit = None # type: Optional[bool] # None means unknown - self._is_taproot = None # type: Optional[bool] # None means unknown self.witness_sizehint = None # type: Optional[int] # byte size of serialized complete witness, for tx size est @property @@ -1967,13 +2046,12 @@ class PartialTxInput(TxInput, PSBTSection): return dummy_desc.is_segwit() return False # can be false-negative - def is_taproot(self) -> bool: - if self._is_taproot is None: - if self.address: - self._is_taproot = bitcoin.is_taproot_address(self.address) + def is_taproot(self) -> Optional[bool]: + if (is_taproot := super().is_taproot()) is not None: + return is_taproot if desc := self.script_descriptor: return desc.is_taproot() - return self._is_taproot + return None def already_has_some_signatures(self) -> bool: """Returns whether progress has been made towards completing this input.""" @@ -1982,6 +2060,33 @@ class PartialTxInput(TxInput, PSBTSection): or self.script_sig is not None or self.witness is not None) + def get_scriptcode_for_sighash(self) -> bytes: + """Constructs the scriptcode part of the preimage for OP_CHECKSIG, + for a partial txin, to create a new signature. + + Note: the base impl works by mimicking a consensus-verifier and extracting + the required fields from the already complete witness/scriptSig + and the scriptpubkey of the funding utxo. + In contrast, here we are the one constructing a spending txin, + and can have knowledge beyond what will be revealed onchain. + """ + if self.witness_script: + if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(self.witness_script)]: + raise Exception('OP_CODESEPARATOR black magic is not supported') + return self.witness_script + if not self.is_segwit() and self.redeem_script: + if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(self.redeem_script)]: + raise Exception('OP_CODESEPARATOR black magic is not supported') + return self.redeem_script + + if desc := self.script_descriptor: + sc = desc.expand() + if script := sc.scriptcode_for_sighash: + return script + raise Exception(f"don't know scriptcode for descriptor: {desc.to_string()}") + + raise UnknownTxinType(f'cannot construct preimage_script') + class PartialTxOutput(TxOutput, PSBTSection): def __init__(self, *args, **kwargs): @@ -2105,10 +2210,10 @@ class PartialTransaction(Transaction): return d @classmethod - def from_tx(cls, tx: Transaction) -> 'PartialTransaction': + def from_tx(cls, tx: Transaction, *, strip_witness: bool = True) -> 'PartialTransaction': assert tx res = cls() - res._inputs = [PartialTxInput.from_txin(txin, strip_witness=True) + res._inputs = [PartialTxInput.from_txin(txin, strip_witness=strip_witness) for txin in tx.inputs()] res._outputs = [PartialTxOutput.from_txout(txout) for txout in tx.outputs()] res.version = tx.version @@ -2395,14 +2500,14 @@ class PartialTransaction(Transaction): def serialize(self) -> str: """Returns PSBT as base64 text, or raw hex of network tx (if complete).""" - self.finalize_psbt() + self.finalize_psbt() # FIXME this side-effects self if self.is_complete(): return Transaction.serialize(self) return self._serialize_as_base64() def serialize_as_bytes(self, *, force_psbt: bool = False) -> bytes: """Returns PSBT as raw bytes, or raw bytes of network tx (if complete).""" - self.finalize_psbt() + self.finalize_psbt() # FIXME this side-effects self if force_psbt or not self.is_complete(): with io.BytesIO() as fd: self._serialize_psbt(fd) diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 6757d824e..9b0402a19 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -943,6 +943,120 @@ class TestTransactionTestnet(ElectrumTestCase): self.assertEqual('020000000001019ad573c69e60c209e0ff36f281ae4f700a8d59f846e7ff5c020352fd1e97808600000000000000000001fa840100000000001600145a209b202bc19b3d345a75cf8ab51cb471913a790247304402207b191c1e3ff1a2d3541770b496c9f871406114746b3aa7347ec4ef0423d3a975022043d3a746fa7a794d97e95d74b6d17d618dfc4cd7644476813e08006f271e51bd012a046c4f855fb1752102aec53aa5f347219a7378b13006eb16ce48125f9cf14f04a5509a565ad5e51507ac6c4f855f', tx.serialize()) + +class TestTransactionVerifySig(ElectrumTestCase): + + def test_verifysig_spending_uncompressed_p2pk(self): + funding_tx_raw = "010000000001021b41471d6af3aa80ebe536dbf4f505a6d46af456131a8e12e1950171959b690e0f00000000fdffffff2ef29833a69863b31e884fc5e6f7b99a23b5601e14f0eb65905faa42fec0776d0000000000fdffffff02f96a070000000000160014e61b989a740056254b5f8061281ac96ca15d35e140420f00000000004341049afa8fb50f52104b381a673c6e4fb7fb54987271d0e948dd9a568bb2af6f9310a7a809ce06e09d1510e5836f20414596232e2c0be63715459fa3cf8e7092af05ac0247304402201fe20012c1c732a6a8f942c4e0feed5ed0bddfb94db736ec3d0c0d38f0f7f46a022021d690e6d2688b90b76002f4c3134981502d666211e85e8a6ca91e78405dfa3801210346fb31136ab48e6c648865264d32004b43643d01f0ba485cffac4bb0b3f739470247304402204a2473ab4b3bfc8e6b1a6b8675dc2c3d115d8c04f5df37f29779dca6d300d9db02205e72ebbccd018c67b86ae4da6b0e6222902a8de85915ed6115330b9328764b370121027a93ffc9444a12d99307318e2e538949072cb35b2aca344b8163795a022414c7d73a1400" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "010000000129349e5641d79915e9d0282fdbaee8c3df0b6731bab9d70bf626e8588bde24ac010000004847304402206bf0d0a93abae0d5873a62ebf277a5dd2f33837821e8b93e74d04e19d71b578002201a6d729bc159941ef5c4c9e5fe13ece9fc544351ba531b00f68ba549c8b38a9a01fdffffff01b82e0f00000000001600148ba0a0bc12b51831f58c7ea8607e76c5982c071fd93a1400" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig = list(script_GetOp(spending_tx.inputs()[0].script_sig))[0][1] + pubkey = list(script_GetOp(funding_tx.outputs()[1].scriptpubkey))[0][1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey, sig=sig)) + + def test_verifysig_spending_compressed_p2pk(self): + funding_tx_raw = "02000000000102b7bfcd442c91134743c6e4100bb9f79456a6015de3c3920166bb0c3b7a8f7c070100000000fdffffff5ab39480d4b35ffa843691d944a8479dfe825d38b03fcb1804197482bfad80fb0100000000fdffffff02d4ec000000000000160014769114e56e0913de3719a3b00a446b78e61751f007b201000000000023210332e147520e4743299d95196afaf9db7c86fe02507d9ca89acd7a4e96a63653d5ac0247304402200387fe79ffe10cec73d9b131058d7128665f729d14597828b483842889c4f5ea02201197b2f1295e4011e2d174d53c240fd13c6351451ab961ccb3678fc21fa5323b0121023c221dfbf7c3f61b9e5f66343c1a302d6beca2a8883504b0f484faec9919636b024730440220687d387af37df458efc104ee0065262cb5ea195e526ed7a480fd16e6cf708c3a022019bd3fd9c3ca3f1a1fbeabe20547876eb4572a7339de37b706fbd55031e60428012102c9c459e58b01a864d7bb80f6d577326465a04219c48541b5f3ea556a06ca61a425ed2400" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "02000000015eb359ccfcd67c3e6b10bb937a796807007708c1f413840d0e627a3f94a1a48401000000484730440220043fc85a43e918ac41e494e309fdf204ca245d260cb5ea09108b196ca65d8a09022056f852f0f521e79ab2124d7e9f779c7290329ce5628ef8e92601980b065d3eb501fdffffff017f9e010000000000160014a709434b13a510e6db68bdd672062c70a2f39d3a26ed2400" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig = list(script_GetOp(spending_tx.inputs()[0].script_sig))[0][1] + pubkey = list(script_GetOp(funding_tx.outputs()[1].scriptpubkey))[0][1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey, sig=sig)) + + def test_verifysig_spending_uncompressed_p2pkh(self): + funding_tx_raw = "02000000000101c6a49fbd701f1526c8e43025a6dda8dd235b3593cfd38af040cba3e37b474fdb0e00000000fdffffff020e640300000000001976a914f1b02b7028fb81aefbb25809a2baf8d94d0c2ba288acb9e3080000000000160014c2eee75efe6621be177f7edd8198f671d1640c2602473044022072b8a6154590704063c377af451b4d69f76cc9064085d4a0c80f08625c57628802207844164839d93ce54ce7db092bbd809d5270142b5dedc823e95400e8bdae88c6012102b6ad13f48fd679a209b7d822376550e5e694a3a2862546ceb72c4012977eac4829ed2400" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "02000000010615142d6c296f276c7da9fb2b09f655d594f73b76740404f1424c66c78ca715000000008a47304402206d2dae571ca2f51e0d4a8ce6a6335fa25ac09f4bbed26439124d93f035bdbb130220249dc2039f1da338a40679f0e79c25a2dc2983688e6c04753348f2aa8435e375014104b875ab889006d4a9be8467c9256cf54e1073f7f9a037604f571cc025bbf47b2987b4c862d5b687bb5328adccc69e67a17b109b6328228695a1c384573acd6199fdffffff0186500300000000001600148ba0a0bc12b51831f58c7ea8607e76c5982c071f2aed2400" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig = list(script_GetOp(spending_tx.inputs()[0].script_sig))[0][1] + pubkey = list(script_GetOp(spending_tx.inputs()[0].script_sig))[1][1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey, sig=sig)) + + def test_verifysig_spending_compressed_p2pkh(self): + funding_tx_raw = "020000000001010615142d6c296f276c7da9fb2b09f655d594f73b76740404f1424c66c78ca7150100000000fdffffff0240e20100000000001976a914f1d49f51f9b58c4805431c303d12d3dcf51ae54188ace9000700000000001600145bdb04f2d096ee48b8b350c85481392ab47c01e70247304402200a72a4599cb27f16011cd67e2951733d6775cbd008506eacb2c20d69db3f531702204c944ec09224a347481c9eea78cac79b77b194b19dfef01b1e3b428010a82570012102fc38612ca7cc42d05a7089f1a6ec3900535604bd779f83c7817aae7bfd907dbd2aed2400" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "02000000016717835a2e1e152a69e7528a0f1346c1d37ee6e76c5e23b5d1c5a5b40241768a000000006a473044022038ad38003943bfd3ed39ba4340d545753fcad632a8fe882d01e4f0140ddb3cfb022019498260e29f5fbbcde9176bfb3553b7acec5fe284a9a3a33547a2d082b60355012103b875ab889006d4a9be8467c9256cf54e1073f7f9a037604f571cc025bbf47b29fdffffff0158de010000000000160014f1d49f51f9b58c4805431c303d12d3dcf51ae5412aed2400" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig = list(script_GetOp(spending_tx.inputs()[0].script_sig))[0][1] + pubkey = list(script_GetOp(spending_tx.inputs()[0].script_sig))[1][1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey, sig=sig)) + + def test_verifysig_spending_p2wpkh_p2sh(self): + funding_tx_raw = "020000000001038fc862be3bc8022866cc83b4f2feeaa914b015a3c6644251960baaccc4a5740b0000000000fdffffff7bfd61e391034e28848fae269183f1c5929e26befd5b2d798cf12c91d4d00dbf0100000000fdffffff014764d324e70e7e3e4fa27077bda2d880b3d1545588b75f79deb2855d9f31cb0000000000fdffffff01f04902000000000017a9147d0530db22c8124ff1558269f543dfeedd37131b87024730440220568ae75314f6414ccf2b0bbed522e1b4b1086ed6eb185ba4bc044ba2723c1f3402206c82253797d0f180db38986b46d8ad952829cf25bc31e3ca6ee54665f5a44b3c0121038a466bdcb979b96d70fde84b9ded4aba0c3cd9c0d2d59121fc3555428fd1a4890247304402203ba1b482b0b6ce5c3d29ef21ee8afad641af8381d3b131103c384757922f0c04022072320e260b60fc862669b2ea3dfb663f7f3a0b6babe8d265ac9ebf268e7225c2012103ff0877f34157a3444afbfdd7432032a93187bc1932e1c155d56dd66ef527906c02473044022058b1c1a2a8c1a256d4870b550ba93777a2cce36b89abe3515f024fd4eec48ce4022023e0002193a26064275433e8ade98642d74d58ee4f8e9717a8acca737856a6c401210364e8f5d9c30986931bca1197138d7250a17a0711a223f113b3ccc11ef09efccb2aed2400" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "020000000001011d1725072a6e60687a59b878ecaf940ea0385880613d9d5502102bd78ef97b9a0000000017160014e7a6a58b657f629516cc37ee2863cbabdadb3fd4fdffffff01fc47020000000000160014e7a6a58b657f629516cc37ee2863cbabdadb3fd402473044022048ea4c558fd374f5d5066440a7f4933393cb377802cb949e3039fedf0378a29402204b4a58c591117cc1e37f07b03cc03cc6198dbf547e2bff813e2e2102bd2057e00121029f46ba81b3c6ad84e52841364dc54ca1097d0c30a68fb529766504c4b1c599352aed2400" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig = spending_tx.inputs()[0].witness_elements()[0] + pubkey = spending_tx.inputs()[0].witness_elements()[1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey, sig=sig)) + + def test_verifysig_spending_p2wpkh(self): + funding_tx_raw = "02000000000101208840a3310ae4b88181374b5812f56f5dd56f12574f3bcd8041b48bfadc92cf0000000000fdffffff02fc7f010000000000160014d339efed7cd5d28d31995caf10b8973a9a13c656a08601000000000043410403886197eb13c59721b94a29f9a68a841caedb7782b35121cd81d50d0cc70db3f8955c7a07b08dd6470141b66eedd324406e29d6b6799033314512334461e3f9ac0247304402203328153753e934d7a13215bf58f093f84281d57f8c7d42f3b7704cd714c7b32c02205a502f3f3e4302561ccc93df413be3c78a439ff35b60cea03d19f8804a9a1239012103f41052be701441d1bc8f7cc6a6053d7e7f5e63be212fe5e3687344ddd52e3af525ed2400" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "02000000000101e328aeb4f9dc1b85a2709ce59b0478a15ed9fb5e7f84fb62422f99b8cd6ad7010000000000fdffffff01087e010000000000160014bf08acd69f1b7012c2b91642b352ce3627db89010247304402204993099c4663d92ef4c9a28b3f45a40a6585754fe22ecfdc0a76c43fda7c9d04022006a75e0fd3ad1862d8e81015a71d2a1489ec7a9264e6e63b8fe6bb90c27e799b0121038ca94e7c715152fd89803c2a40a934c7c4035fb87b3cba981cd1e407369cfe312aed2400" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig = spending_tx.inputs()[0].witness_elements()[0] + pubkey = spending_tx.inputs()[0].witness_elements()[1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey, sig=sig)) + + def test_verifysig_spending_p2sh_multisig_2of3(self): + funding_tx_raw = "010000000001014121f99dc02f0364d2dab3d08905ff4c36fc76c55437fd90b769c35cc18618280100000000fdffffff02d4c22d00000000001600143fd1bc5d32245850c8cb5be5b09c73ccbb9a0f75001bb7000000000017a91480c2353f6a7bc3c71e99e062655b19adb3dd2e4887024830450221008781c78df0c9d4b5ea057333195d5d76bc29494d773f14fa80e27d2f288b2c360220762531614799b6f0fb8d539b18cb5232ab4253dd4385435157b28a44ff63810d0121033de77d21926e09efd04047ae2d39dbd3fb9db446e8b7ed53e0f70f9c9478f735dac11300" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "01000000017120d4e1f2cdfe7df000d632cff74167fb354f0546d5cfc228e5c98756d55cb201000000fc004730440220751ee3599e59debb8b2aeef61bb5f574f26379cd961caf382d711a507bc632390220598d53e62557c4a5ab8cfb2f8948f37cca06a861714b55c781baf2c3d7a580b501473044022023b55c679397bdf3a04d545adc6193eabc11b3a28850d3d46049a51a30c6732402205dbfdade5620e9072ae4aa7577c5f0fd294f59a6b0064cc7105093c0fe7a6d24014c69522102afb4af9a91264e1c6dce3ebe5312801723270ac0ba8134b7b49129328fcb0f2821030b482838721a38d94847699fed8818b5c5f56500ef72f13489e365b65e5749cf2103e5db7969ae2f2576e6a061bf3bb2db16571e77ffb41e0b27170734359235cbce53aefeffffff0250a50500000000001976a9149cd3dfb0d87a861770ae4e268e74b45335cf00ab88ac2862b1000000000017a9142e517854aa54668128c0e9a3fdd4dec13ad571368700000000" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig1 = list(script_GetOp(spending_tx.inputs()[0].script_sig))[1][1] + sig2 = list(script_GetOp(spending_tx.inputs()[0].script_sig))[2][1] + redeem_script = list(script_GetOp(spending_tx.inputs()[0].script_sig))[-1][1] + pubkey1 = list(script_GetOp(redeem_script))[1][1] + pubkey2 = list(script_GetOp(redeem_script))[2][1] + pubkey3 = list(script_GetOp(redeem_script))[3][1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey1, sig=sig1)) + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey2, sig=sig2)) + + self.assertFalse(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey3, sig=sig2)) + + def test_verifysig_spending_p2wsh_p2sh_multisig_2of2(self): + funding_tx_raw = "01000000000101213e1012a461e056752fab5a6414a2fb63f950cd21a50ac5e2b82d339d6cbdd20000000000feffffff023075000000000000220020cc5e4cc05a76d0648cd0742768556317e9f8cc729aed077134287909035dba88888402000000000017a914187842cea9c15989a51ce7ca889a08b824bf8743870400473044022055cb04fa71c4b5955724d7ac5da90436d75212e7847fc121cb588f54bcdffdc4022064eca1ad639b7c748101059dc69f2893abb3b396bcf9c13f670415076f93ddbf01473044022009230e456724f2a4c10d886c836eeec599b21db0bf078aa8fc8c95868b8920ec02200dfda835a66acb5af50f0d95fcc4b76c6e8f4789a7184c182275b087d1efe556016952210223f815ab09f6bfc8519165c5232947ae89d9d43d678fb3486f3b28382a2371fa210273c529c2c9a99592f2066cebc2172a48991af2b471cb726b9df78c6497ce984e2102aa8fc578b445a1e4257be6b978fcece92980def98dce0e1eb89e7364635ae94153ae00000000" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "0100000000010149d077be0ee9d52776211e9b4fec1cc02bd53661a04e120a97db8b78d83c9c6e01000000232200204311edae835c7a5aa712c8ca644180f13a3b2f3b420fa879b181474724d6163cfeffffff0260ea00000000000017a9143025051b6b5ccd4baf30dfe2de8aa84f0dd567ed87a086010000000000220020f7b6b30c3073ae2680a7e90c589bbfec5303331be68bbab843eed5d51ba0123904004730440220091ea67af7c1131f51f62fe9596dff0a60c8b45bfc5be675389e193912e8a71802201bf813bbf83933a35ecc46e2d5b0442bd8758fa82e0f8ed16392c10d51f7f7660147304402203ecf75b0316a449dd31bc549251b687dc904194aa551941bd5e8c67603661bdb02204ed58b3a6b070ec138d2127093bebcc6581495818fa611583e1c81cd9b2cf5ee0147522102119f899075a131d4d519d4cdcf5de5907dc2df3b93d54b53ded852211d2b6cb12102fdb0f6775d4b6619257c43343ba5e7807b0164f1eb3f00f2b594ab9e53ab812652ae00000000" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig1 = spending_tx.inputs()[0].witness_elements()[1] + sig2 = spending_tx.inputs()[0].witness_elements()[2] + witness_script = spending_tx.inputs()[0].witness_elements()[-1] + pubkey1 = list(script_GetOp(witness_script))[1][1] + pubkey2 = list(script_GetOp(witness_script))[2][1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey1, sig=sig1)) + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey2, sig=sig2)) + + self.assertFalse(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey1, sig=sig2)) + + def test_verifysig_spending_p2wsh_multisig_2of3(self): + funding_tx_raw = "01000000000101a41aae475d026c9255200082c7fad26dc47771275b0afba238dccda98a597bd20000000000fdffffff02400d0300000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c9dcd410000000000160014824626055515f3ed1d2cfc9152d2e70685c71e8f02483045022100b9f39fad57d07ce1e18251424034f21f10f20e59931041b5167ae343ce973cf602200fefb727fa0ffd25b353f1bcdae2395898fe407b692c62f5885afbf52fa06f5701210301a28f68511ace43114b674371257bb599fd2c686c4b19544870b1799c954b40e9c11300" + funding_tx = Transaction(funding_tx_raw) + spending_tx_raw = "01000000000101213e1012a461e056752fab5a6414a2fb63f950cd21a50ac5e2b82d339d6cbdd20000000000feffffff023075000000000000220020cc5e4cc05a76d0648cd0742768556317e9f8cc729aed077134287909035dba88888402000000000017a914187842cea9c15989a51ce7ca889a08b824bf8743870400473044022055cb04fa71c4b5955724d7ac5da90436d75212e7847fc121cb588f54bcdffdc4022064eca1ad639b7c748101059dc69f2893abb3b396bcf9c13f670415076f93ddbf01473044022009230e456724f2a4c10d886c836eeec599b21db0bf078aa8fc8c95868b8920ec02200dfda835a66acb5af50f0d95fcc4b76c6e8f4789a7184c182275b087d1efe556016952210223f815ab09f6bfc8519165c5232947ae89d9d43d678fb3486f3b28382a2371fa210273c529c2c9a99592f2066cebc2172a48991af2b471cb726b9df78c6497ce984e2102aa8fc578b445a1e4257be6b978fcece92980def98dce0e1eb89e7364635ae94153ae00000000" + spending_tx = Transaction(spending_tx_raw) + spending_tx.inputs()[0].utxo = funding_tx + sig1 = spending_tx.inputs()[0].witness_elements()[1] + sig2 = spending_tx.inputs()[0].witness_elements()[2] + witness_script = spending_tx.inputs()[0].witness_elements()[-1] + pubkey1 = list(script_GetOp(witness_script))[1][1] + pubkey2 = list(script_GetOp(witness_script))[2][1] + pubkey3 = list(script_GetOp(witness_script))[3][1] + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey1, sig=sig1)) + self.assertTrue(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey3, sig=sig2)) + + self.assertFalse(spending_tx.verify_sig_for_txin(txin_index=0, pubkey_bytes=pubkey2, sig=sig2)) + + class TestSighashBIP143(ElectrumTestCase): #These tests are taken from bip143, https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki #Input of transaction