Monday, January 13, 2025
HomeBitcoinpython - Why Double SHA-256 Does not Work for Pizza Transaction Enter...

python – Why Double SHA-256 Does not Work for Pizza Transaction Enter Validation and Tips on how to Assemble Appropriate Signing Information in bitcoin


Bitcoin transaction signature validation issues

To validate every enter of a Bitcoin transaction utilizing ECDSA on the secp256k1 curve, we’d like three parameters:

  1. Public key (Uncompressed instance: 042e930f39ba62c6534ee98ed20ca98959d34aa9e057cda01cfd422c6bab3667b76426529382c23f42b9b08d7832d4fee1d6b437a8526e59667ce9c4e9dcebcabb)
  2. Signature (Thought-about in DER format: 30450221009908144ca6539e09512b9295c8a27050d478fbb96f8addbc3d075544dc41328702201aa528be2b907d316d2da068dd9eb1e23243d97e444d59290d2fddf25269ee0e, final byte 01 SIGHASH_ALL eliminated)
  3. Double hash of uncooked transaction information that the sender initially signed (Discovered hash: 083867478cb0d1d8bb864175bbc49728cffcc114bc2e762c6df64f2c965a9a66)

Right here’s the documentation on how one can create transaction information for signing https://en.bitcoin.it/wiki/OP_CHECKSIG

In all places in documentation and books, it’s said how one can assemble this information after which compute its double SHA-256 hash

I tried to collect all of the transaction information from this pizza transaction (https://blockstream.information/api/tx/cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79) and adopted the step-by-step offered on this Bitcoin StackExchange reply

  1. However the creator received: 692678553d1b85ccf87d4d4443095f276cdf600f2bb7dd44f6effbd7458fd4c2 after double hashing
  2. And I attempted hash it ones, received this: 083867478cb0d1d8bb864175bbc49728cffcc114bc2e762c6df64f2c965a9a66

Used on-line EDCDSA verification software https://emn178.github.io/online-tools/ecdsa/confirm/

Then look, my enter was right and initially signed message was like this 083867478cb0d1d8bb864175bbc49728cffcc114bc2e762c6df64f2c965a9a66

DOUBLE SHA256:
enter image description here

ONE SHA256:
enter image description here

I wrote a script that takes a transaction ID and validates its inputs, however my script at the moment returns a completely incorrect enter (public key and signature right)

Questions:

Right here is my code:

import requests
import hashlib
from bitcoin.core import CTransaction, CTxIn, CTxOut, COutPoint, x, b2x, lx
from bitcoin.core.script import CScript
from cryptography.hazmat.primitives.uneven import ec
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.exceptions import InvalidSignature
import logging


def fetch_raw_transaction(transaction_id):
    """
    Fetch the uncooked transaction hex from Blockstream API.

    :param transaction_id: Transaction ID (txid)
    :return: Uncooked transaction hex
    """
    url = f"https://blockstream.information/api/tx/{transaction_id}/hex"
    response = requests.get(url)
    if response.status_code == 200:
        logging.debug(f"Uncooked transaction fetched: {response.textual content.strip()}")
        return bytes.fromhex(response.textual content.strip())  # Return as bytes
    else:
        elevate Exception(f"Did not fetch transaction. Standing code: {response.status_code}")

def fetch_utxo_script_pubkey(txid, vout):
    """
    Fetch the ScriptPubKey for a given UTXO utilizing Blockstream API.

    :param txid: Transaction ID of the UTXO
    :param vout: Output index of the UTXO
    :return: ScriptPubKey as bytes
    """
    url = f"https://blockstream.information/api/tx/{txid}"
    response = requests.get(url)
    if response.status_code == 200:
        outputs = response.json().get('vout', [])

        if 0 <= vout < len(outputs):
            script_pubkey = outputs[vout].get('scriptpubkey', None)
            if script_pubkey:
                return bytes.fromhex(script_pubkey)
            else:
                elevate Exception(f"ScriptPubKey not discovered for vout {vout} in transaction {txid}")
        else:
            elevate Exception(f"Invalid vout index: {vout} for transaction {txid}")
    else:
        elevate Exception(f"Did not fetch transaction particulars. Standing code: {response.status_code}")

def compute_sighash(transaction_id, input_index, script_pubkey):
    """
    Compute the sighash for a particular enter in a Bitcoin transaction.

    :param transaction_id: Transaction ID (txid)
    :param input_index: Index of the enter being signed
    :param script_pubkey: ScriptPubKey for the enter
    :return: SIGHASH (message for signature)
    """
    strive:
        # Fetch the uncooked transaction bytes
        raw_tx_bytes = fetch_raw_transaction(transaction_id)
        logging.debug(f"Uncooked transaction bytes: {raw_tx_bytes.hex()}")

        # Deserialize the transaction
        tx = CTransaction.deserialize(raw_tx_bytes)
        logging.debug(f"Deserialized transaction: {tx}")

        # Create a brand new transaction with up to date scriptSig
        modified_tx_ins = []
        for i, tx_in in enumerate(tx.vin):
            if i == input_index:
                # Exchange scriptSig for the required enter
                modified_tx_ins.append(CTxIn(tx_in.prevout, CScript(script_pubkey), tx_in.nSequence))
            else:
                # Hold different inputs unchanged
                modified_tx_ins.append(tx_in)

        modified_tx = CTransaction(modified_tx_ins, tx.vout, tx.nLockTime)
        logging.debug(f"Modified transaction: {modified_tx}")

        # Serialize transaction with SIGHASH_ALL (0x01)
        tx_raw_with_sighash = modified_tx.serialize() + bytes([1])  # 0x01 = SIGHASH_ALL
        logging.debug(f"Serialized transaction with SIGHASH_ALL: {tx_raw_with_sighash.hex()}")

        # Compute double SHA-256
        sighash = hashlib.sha256(hashlib.sha256(tx_raw_with_sighash).digest()).digest()
        logging.debug(f"Computed SIGHASH: {sighash.hex()}")

        return sighash
    besides Exception as e:
        logging.error("Error throughout SIGHASH computation", exc_info=True)
        elevate

def verify_signature(public_key_hex, signature_hex, message):
    """
    Confirm the signature utilizing ECDSA.

    :param public_key_hex: Hex-encoded public key
    :param signature_hex: Hex-encoded signature
    :param message: Message (hash) to confirm
    :return: True if legitimate, raises exception in any other case
    """
    strive:
        public_key_bytes = bytes.fromhex(public_key_hex)
        signature_bytes = bytes.fromhex(signature_hex[:-2])  # Take away the final byte (SIGHASH flag)

        # Load the general public key
        public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), public_key_bytes)

        # Confirm the signature
        public_key.confirm(signature_bytes, message, ec.ECDSA(SHA256()))
        print("Signature is legitimate.")
    besides InvalidSignature:
        print("Signature is invalid.")
    besides Exception as e:
        logging.error("Error throughout signature verification", exc_info=True)
        elevate

if __name__ == "__main__":
    transaction_id = "cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79"

    strive:
        # Fetch the uncooked transaction bytes
        raw_tx_bytes = fetch_raw_transaction(transaction_id)
        tx = CTransaction.deserialize(raw_tx_bytes)

        # Iterate over every enter to validate signatures
        for i, tx_in in enumerate(tx.vin):
            print(f"nValidating enter {i}:")

            # Extract public key and signature from scriptSig
            script_sig = tx_in.scriptSig
            signature_hex = b2x(script_sig[1:1 + script_sig[0]])
            public_key_hex = b2x(script_sig[1 + script_sig[0] + 1:])

            print(f"Public Key: {public_key_hex}")
            print(f"Signature: {signature_hex}")

            # Fetch ScriptPubKey for this enter
            prev_txid = b2x(tx_in.prevout.hash[::-1])  # Convert to little-endian
            script_pubkey = fetch_utxo_script_pubkey(prev_txid, tx_in.prevout.n)
            print(f"ScriptPubKey: {script_pubkey.hex()}")

            # Compute sighash
            sighash = compute_sighash(transaction_id, i, script_pubkey)
            print(f"Computed SIGHASH (message): {sighash.hex()}")

            # Confirm signature
            verify_signature(public_key_hex, signature_hex, sighash)
    besides Exception as e:
        print("Error:", str(e))

Output:

Validating enter 0:
Public Key: 042e930f39ba62c6534ee98ed20ca98959d34aa9e057cda01cfd422c6bab3667b76426529382c23f42b9b08d7832d4fee1d6b437a8526e59667ce9c4e9dcebcabb
Signature: 30450221009908144ca6539e09512b9295c8a27050d478fbb96f8addbc3d075544dc41328702201aa528be2b907d316d2da068dd9eb1e23243d97e444d59290d2fddf25269ee0e01
ScriptPubKey: 76a91446af3fb481837fadbb421727f9959c2d32a3682988ac
Computed SIGHASH (message): 48fd60a2aeb4247255d2c1929c28ab47bdaaa49b12ff17e50b742789a3604602
Signature is invalid.

Anticipated Computed SIGHASH (message) is 083867478cb0d1d8bb864175bbc49728cffcc114bc2e762c6df64f2c965a9a66

Earlier than providing recommendation, please take a look at the code and confirm the correctness of the answer.
I’ve already reviewed many solutions on how one can appropriately assemble the message for transaction signing, however none of them work

If there may be any API that return signed hash of transaction, so I can validate it, please inform me

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments