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