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