The data that gets hashed and signed in P2PKH:
1. Fully Annotated P2PKH Script Execution
Pay-to-Public-Key-Hash (P2PKH) is the most common Bitcoin script type, used since the very beginning. It locks coins to the hash of a public key and requires a valid signature from the corresponding private key to spend.
The Complete P2PKH Transaction
Sending transaction (creates the locked output):
{
"txid": "abc123...",
"vout": [
{
"value": 0.001,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 89abcdefabbaabbaabbaabbaabbaabbaabbaabba OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac",
"type": "pubkeyhash",
"address": "1DkP9rT3RSUP8XkMBBg7Qj2jSDKCQDC4dN"
}
}
]
}
Decoding the scriptPubKey Hex
Hex: 76 a9 14 89abcdefabbaabbaabbaabbaabbaabbaabbaabba 88 ac
76 → OP_DUP (opcode 0x76, decimal 118)
a9 → OP_HASH160 (opcode 0xa9, decimal 169)
14 → Push 20 bytes (0x14 = decimal 20)
89abcdef...abba → 20-byte public key hash (RIPEMD160(SHA256(pubkey)))
88 → OP_EQUALVERIFY (opcode 0x88, decimal 136)
ac → OP_CHECKSIG (opcode 0xac, decimal 172)
Spending Transaction Input
Spending transaction (unlocks the output):
{
"txid": "def456...",
"vin": [
{
"txid": "abc123...",
"vout": 0,
"scriptSig": {
"asm": "3045022100a7d8...01 02b4e4...f2",
"hex": "483045022100a7d8...0147 2102b4e4...f2"
}
}
]
}
ScriptSig hex decoded:
48 → Push 72 bytes (0x48 = decimal 72)
3045022100a7d8...01 → 71-byte DER-encoded ECDSA signature + 01 (SIGHASH_ALL byte)
47 → cleaner numbers...
Typical P2PKH scriptSig (107 bytes):
47 → Push 71 bytes
<71-byte DER signature><01> → signature with SIGHASH_ALL appended
21 → Push 33 bytes
<33-byte compressed public key> → 02 or 03 prefix + 32-byte x-coordinate
Step-by-Step Execution Trace
Starting state:
Main stack: []
Alt stack: []
PHASE 1: Execute scriptSig
### Step 1: Push <signature>
**Operation:** Push 71-byte DER signature + sighash byte
**Stack:** `[<sig>]`
### Step 2: Push <pubKey>
**Operation:** Push 33-byte compressed public key
**Stack:** `[<sig>, <pubKey>]`
PHASE 2: Execute scriptPubKey
Step 3: OP_DUP (0x76)
Operation: Duplicate the top stack item
Before: [<sig>, <pubKey>]
After: [<sig>, <pubKey>, <pubKey>]
Step 4: OP_HASH160 (0xa9)
Operation: Pop top item, compute RIPEMD160(SHA256(item)), push result
Before: [<sig>, <pubKey>, <pubKey>]
Computation: RIPEMD160(SHA256(<pubKey>)) = <20-byte-hash>
After: [<sig>, <pubKey>, <20-byte-hash>]
Step 5: Push (the one embedded in scriptPubKey)
Operation: Push the 20-byte hash from the locking script
Before: [<sig>, <pubKey>, <20-byte-hash>]
After: [<sig>, <pubKey>, <20-byte-hash>, <expected-hash>]
Step 6: OP_EQUALVERIFY (0x88)
Operation: Pop top two items, compare them
Before: [<sig>, <pubKey>, <20-byte-hash>, <expected-hash>]
Case A (hashes match): Stack becomes [<sig>, <pubKey>], execution continues
Case B (hashes don't match): SCRIPT FAILS immediately
After (success): [<sig>, <pubKey>]
This step answers: "Does this pubKey correspond to the address we locked to?"
Step 7: OP_CHECKSIG (0xac)
Operation: Pop
Compute sighash of the spending transaction
Verify that <sig> is a valid ECDSA signature over the sighash
using <pubKey>
Before: [<sig>, <pubKey>]
Case A (valid signature): Push 1 (TRUE)
Case B (invalid signature): Push 0 (FALSE)
After (success): [1]
=== FINAL CHECK ===
Stack top: 1 (non-zero)
Result: SCRIPT VALID — transaction accepted
What the Signature Actually Signs
# The data that gets hashed and signed in P2PKH:
def p2pkh_sighash(tx, input_index, scriptPubKey, sighash_type=SIGHASH_ALL):
"""
Compute the sighash for a P2PKH input.
This is what the private key actually signs.
"""
# Create a modified transaction copy
tx_copy = copy(tx)
# Clear all input scripts
for inp in tx_copy.vin:
inp.scriptSig = b''
# Put the scriptPubKey in the current input
tx_copy.vin[input_index].scriptSig = scriptPubKey
# Serialize and double-SHA256
raw = tx_copy.serialize() + sighash_type.to_bytes(4, 'little')
return sha256(sha256(raw))
# The private key signs: HASH256(modified_tx_serialization)
# The signature proves: I know the private key for this public key
# AND I authorized this specific transaction
Address Derivation
def pubkey_to_p2pkh_address(pubkey_bytes, network='mainnet'):
"""
Convert a compressed public key to a P2PKH Bitcoin address.
This reverses the locking process — shows how the hash is created.
"""
# Step 1: SHA256 of public key
sha256_hash = sha256(pubkey_bytes)
# Step 2: RIPEMD160 of SHA256 result
ripemd160_hash = ripemd160(sha256_hash)
# This 20-byte value goes into the scriptPubKey
# Step 3: Add version byte (0x00 for mainnet P2PKH)
versioned = bytes([0x00]) + ripemd160_hash
# Step 4: Compute checksum (first 4 bytes of double-SHA256)
checksum = sha256(sha256(versioned))[:4]
# Step 5: Base58Check encode
return base58check_encode(versioned + checksum)
# Result: an address starting with "1"
Technical Insight
This topic covers essential mechanics for Chapter 12. Understanding these details is key to mastering advanced Bitcoin script constructions like Taproot and specialized covenants.
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: