1
0

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).
This commit is contained in:
SomberNight
2025-05-18 14:39:05 +00:00
parent 120d8ac62e
commit 0508625afc
4 changed files with 262 additions and 43 deletions

View File

@@ -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()

View File

@@ -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.'))

View File

@@ -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)

View File

@@ -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