TeachMeBitcoin

P2SH Script - Full Anatomy and Execution

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

3. P2SH Script — Full Anatomy and Execution

Overview

Pay-to-Script-Hash (P2SH), defined in BIP 16 (activated April 2012), represents a paradigm shift: rather than locking funds to a key or key hash, you lock them to the hash of an arbitrary redemption script. This moves the burden of storing complex scripts from the sender (who just needs a 20-byte hash address) to the receiver (who must provide the full redeem script at spend time).

P2SH addresses begin with 3 on mainnet and enabled practical multisig and custom scripting without bloating locking scripts.

Script Structure

Locking script (scriptPubKey) — 23 bytes, always:

OP_HASH160 <redeemScriptHash> OP_EQUAL

Unlocking script (scriptSig):

<data pushes satisfying redeemScript> <serialized redeemScript>

Redeem script: Any valid Bitcoin script, e.g., a 2-of-3 multisig:

OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG

Byte-Level Anatomy

Locking script (23 bytes):
A9                    <- OP_HASH160
14                    <- push 20 bytes
<20-byte redeemScriptHash>
87                    <- OP_EQUAL

Redeem script hash:
redeemScriptHash = HASH160(redeemScript)

Dual-Phase Execution

P2SH validation is a two-phase process enforced by a special rule introduced in BIP 16:

Phase 1: Standard script execution

Combined: <...> <redeemScript> OP_HASH160 <hash> OP_EQUAL

Steps:

1. Push all scriptSig data items including redeemScript

2. OP_HASH160 hashes the top item (redeemScript)

3. Push stored hash from scriptPubKey

4. OP_EQUAL checks they match
Stack result: [1]

Phase 2: Redeem script deserialization and re-execution

If phase 1 succeeds AND the scriptPubKey matches P2SH pattern:
  - Deserialize the last pushed item as a script
  - Execute it with the remaining stack items as inputs

This means the redeemScript itself is executed as a second script. Its own logic (e.g., multisig verification) must also complete successfully.

Example: 2-of-3 Multisig via P2SH

Redeem script:

OP_2
<33-byte pubkey1>
<33-byte pubkey2>
<33-byte pubkey3>
OP_3
OP_CHECKMULTISIG

scriptSig for spending:

OP_0                   <- BIP 147 dummy element for CHECKMULTISIG bug
<sig1>
<sig2>
<serialized redeemScript>

Full execution stack trace:

After scriptSig execution:

Stack: [OP_0, , , ]

Phase 1 — hash check:
HASH160(<redeemScript>) == stored hash → TRUE

Phase 2 — redeemScript execution:
Script: OP_2 <pk1> <pk2> <pk3> OP_3 OP_CHECKMULTISIG
With input stack: [OP_0, <sig1>, <sig2>]

OP_CHECKMULTISIG verifies 2 of 3 signatures → [1]

Address Derivation

def redeemscript_to_p2sh_address(redeem_script_hex: str) -> str:
    script_bytes = bytes.fromhex(redeem_script_hex)
    script_hash = hash160(script_bytes)          # RIPEMD160(SHA256(script))
    version = b'\x05'                            # mainnet P2SH version byte
    payload = version + script_hash
    checksum = sha256d(payload)[:4]
    return base58.b58encode(payload + checksum).decode()
☕ 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!