TeachMeBitcoin

The data that gets hashed and signed in P2PKH:

From TeachMeBitcoin, the free encyclopedia Reading time: 4 min

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 and from stack

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.

☕ 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!