P2SH Script - Full Anatomy and Execution
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()
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: