TeachMeBitcoin

P2PKH Script - Full Anatomy and Execution

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

2. P2PKH Script — Full Anatomy and Execution

Overview

Pay-to-Public-Key-Hash (P2PKH) became the dominant script type through most of Bitcoin's history and remains widely used. Instead of embedding the raw public key in the locking script, P2PKH embeds a hash of the public key. This adds a layer of indirection that improves privacy (the public key is only revealed at spend time) and shortens the locking script.

A P2PKH address is a Base58Check-encoded version of the public key hash, which is what most people think of as a "Bitcoin address" starting with 1.

Script Structure

Locking script (scriptPubKey):

OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

Unlocking script (scriptSig):

<signature> <pubkey>

Byte-Level Anatomy

Locking script (25 bytes):
76                    <- OP_DUP
A9                    <- OP_HASH160
14                    <- push 20 bytes
<20-byte pubKeyHash>
88                    <- OP_EQUALVERIFY
AC                    <- OP_CHECKSIG

Unlocking script (~106–107 bytes):
47 or 48              <- push 71–72 byte signature
<DER signature + SIGHASH byte>
21 or 41              <- push 33 or 65 byte pubkey
<compressed or uncompressed pubkey>

The public key hash is computed as:

pubKeyHash = RIPEMD160(SHA256(pubkey))

This double-hash is often written as HASH160(pubkey) in shorthand.

Execution Walkthrough

Combined script for evaluation:

<sig> <pubkey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

Step 1: Push signature

Stack: []

Step 2: Push public key

Stack: [, ]

Step 3: OP_DUP — duplicates the top stack element

Stack: [, , ]

Step 4: OP_HASH160 — hashes top element with SHA256 then RIPEMD160

Stack: [, , HASH160()]

Step 5: Push expected pubKeyHash from scriptPubKey

Stack: [, , HASH160(), ]

Step 6: OP_EQUALVERIFY — pops two top elements, checks equality; fails immediately if unequal

If HASH160(<pubkey>) == <pubKeyHash>:

Stack: [, ]

If not equal:
Script fails immediately ← spender cannot use a wrong public key

Step 7: OP_CHECKSIG — verifies signature against pubkey over serialized tx hash

Stack: [1] (on success)

Address Derivation

import hashlib
import base58

def pubkey_to_p2pkh_address(pubkey_hex: str, mainnet=True) -> str:
    pubkey_bytes = bytes.fromhex(pubkey_hex)
    # Step 1: SHA256
    sha256_hash = hashlib.sha256(pubkey_bytes).digest()
    # Step 2: RIPEMD160
    rmd160 = hashlib.new('ripemd160')
    rmd160.update(sha256_hash)
    pubkey_hash = rmd160.digest()
    # Step 3: Add version byte
    version = b'\x00' if mainnet else b'\x6f'
    payload = version + pubkey_hash
    # Step 4: Checksum (first 4 bytes of double SHA256)
    checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
    # Step 5: Base58Check encode
    return base58.b58encode(payload + checksum).decode()

Why P2PKH Dominates Legacy Addresses

P2PKH solves three key problems with P2PK:

  1. Smaller addresses: 20-byte hash fits in a compact, human-readable Base58Check string

  2. Pre-image security: Public key not exposed until spend time, giving a smaller quantum attack window

  3. Error detection: Base58Check encoding includes a 4-byte checksum

The scriptPubKey for P2PKH is 25 bytes — fixed and predictable — making UTXO management efficient for nodes.

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