TeachMeBitcoin

Fields in the sighash preimage (SIGHASH_ALL example)

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

9. How OP_CHECKSIG Validates a Signature

Detailed Validation Steps

When the script interpreter encounters OP_CHECKSIG, it performs a multi-step verification process:

Step 1: Pop pubkey from stack

Step 2: Pop signature from stack

Step 3: Separate the sighash type byte from the signature

Step 4: Serialize the transaction for signing (based on sighash type)

Step 5: Hash the serialized transaction data

Step 6: Verify the signature using ECDSA or Schnorr

Step 7: Push 0x01 (true) or 0x00 (false)

Step-by-Step: ECDSA Verification

def checksig_verify(signature_bytes, pubkey_bytes, tx, input_index):
    # Step 1: Extract sighash type
    sighash_type = signature_bytes[-1]  # Last byte
    der_sig = signature_bytes[:-1]      # Everything except last byte

    # Step 2: Parse DER signature to (r, s) integers
    r, s = parse_der_signature(der_sig)

    # Step 3: Serialize transaction for signing
    tx_commitment = serialize_for_signing(tx, input_index, sighash_type)

    # Step 4: Hash the commitment
    z = int.from_bytes(
        hashlib.sha256(hashlib.sha256(tx_commitment).digest()).digest(),
        'big'
    )

    # Step 5: Parse public key
    point = parse_public_key(pubkey_bytes)  # Returns (x, y) on secp256k1

    # Step 6: ECDSA verification
    w = pow(s, n - 2, n)  # modular inverse of s
    u1 = (z * w) % n
    u2 = (r * w) % n
    point_result = u1 * G + u2 * point
    return point_result.x % n == r

The Sighash Serialization Format (Legacy)

For legacy transactions, the transaction commitment is constructed as follows:

# Fields in the sighash preimage (SIGHASH_ALL example)

1. nVersion (4 bytes, little-endian)

2. vin_count (varint)

3. For each input:
   a. prev_txid (32 bytes, reversed)
   b. prev_vout (4 bytes, little-endian)
   c. scriptCode (current input's scriptPubKey with OP_CODESEPARATOR stripping)
      (other inputs get empty scriptCode)
   d. sequence (4 bytes, little-endian)

4. vout_count (varint)

5. For each output:
   a. value (8 bytes, little-endian)
   b. scriptPubKey (with length prefix)

6. nLockTime (4 bytes, little-endian)

7. sighash_type (4 bytes, little-endian — note: 4 bytes even though it's 1-byte in signature!)

SegWit BIP143 Sighash

SegWit (BIP143) introduced a new sighash algorithm that commits to input amounts (preventing the "unknown input value" problem for hardware wallets):

# BIP143 sighash preimage

1. nVersion (4 bytes)

2. hashPrevouts (32 bytes — HASH256 of all outpoints, or zeros for ANYONECANPAY)

3. hashSequence (32 bytes — HASH256 of all sequences, or zeros)

4. outpoint (36 bytes — txid + vout of current input)

5. scriptCode (of current input)

6. value (8 bytes — amount of current input's UTXO)

7. nSequence (4 bytes — of current input)

8. hashOutputs (32 bytes — HASH256 of all outputs, or variations)

9. nLockTime (4 bytes)

10. sighash type (4 bytes)

Taproot BIP341 Sighash

Taproot (BIP341) further extends the sighash to include more context and protect against "fee sniping" and related attacks:

# Taproot sighash preimage (simplified)

1. epoch = 0x00

2. hash_type (1 byte)

3. nVersion (4 bytes)

4. nLockTime (4 bytes)

5. sha_prevouts (32 bytes)

6. sha_amounts (32 bytes)   ← New: commits to all input amounts

7. sha_scriptpubkeys (32 bytes) ← New: commits to all input scripts

8. sha_sequences (32 bytes)

9. sha_outputs (32 bytes)

10. spend_type (1 byte)

11. input_index (4 bytes)

What Makes a Signature Invalid?

Reasons OP_CHECKSIG returns 0 (false):

1. Signature uses wrong private key (doesn't match pubkey)

2. Transaction data has changed (different outputs, amounts, etc.)

3. Wrong sighash type applied during verification

4. Signature is malformed (invalid DER encoding)

5. s value is too high (violates BIP 66 low-S requirement)

6. Empty signature with non-null sighash byte

7. Public key is not a valid curve point

8. Transaction context doesn't match what was signed
☕ Help support TeachMeBitcoin

TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!