Chapter 3: P2SH Script Engineering#


Bitcoin’s first real programmability emerged with Pay-to-Script-Hash (P2SH), where complex spending conditions are elegantly hidden behind a simple 20-byte hash. This chapter bridges basic P2PKH transactions and Taproot’s complex script trees, showing how Bitcoin’s script system enables real-world applications like corporate treasury management and time-locked inheritance planning.

# Chapter environment: bitcoinutils (load once, reuse in subsequent code cells)
from bitcoinutils.setup import setup
from bitcoinutils.keys import PrivateKey, P2pkhAddress, P2shAddress
from bitcoinutils.script import Script
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, Sequence
from bitcoinutils.utils import to_satoshis
from bitcoinutils.constants import TYPE_RELATIVE_TIMELOCK
setup('testnet')

3.1 P2SH Architecture: Script Behind the Hash#

P2SH allows any script to be represented by a compact 20-byte hash, moving script complexity from the UTXO set to spending time.

Two-Stage Verification Model#

P2SH operates through two distinct phases:

Stage 1: Hash verification

OP_HASH160 <script_hash> OP_EQUAL

Stage 2: Script execution

<revealed_script> → Execute as Bitcoin Script

P2SH Address Generation Process#

P2SH follows the Hash160 → Base58Check pattern from Chapter 1, but hashes the script rather than the public key:

Script Serialization → hex_encoded_script
Hash160(script)     → 20_bytes_script_hash  
Version + Base58Check → 3...address (mainnet)

All P2SH addresses start with ‘3’ on mainnet and ‘2’ on testnet, immediately distinguishing them from P2PKH.

ScriptSig Construction Pattern#

P2SH’s unlocking script (ScriptSig) follows a specific pattern:

<script_data> <serialized_redeem_script>

Where <script_data> contains values needed to satisfy the redeem script, and <serialized_redeem_script> is the original script whose hash matches the locking script.

3.2 Multisignature Treasury: 2-of-3 Enterprise Security#

Enterprise Bitcoin custody typically requires multi-party authorization to prevent single points of failure. A 2-of-3 multisig scheme ensures no single person can unilaterally access funds while maintaining operational flexibility.

Business Scenario: Startup Treasury Management#

Consider a blockchain startup with three key stakeholders:

  • Alice: CEO with operational authority

  • Bob: CTO with technical oversight

  • Carol: CFO with financial controls

Their treasury policy requires two signatures for any fund movement—preventing single-person risk without requiring unanimous consensus.

# Example 1: Create multisig P2SH
# Reference: code/chapter03/01_create_multisig_p2sh.py

alice_pk = '02898711e6bf63f5cbe1b38c05e89d6c391c59e9f8f695da44bf3d20ca674c8519'
bob_pk = '0284b5951609b76619a1ce7f48977b4312ebe226987166ef044bfb374ceef63af5'
carol_pk = '0317aa89b43f46a0c0cdbd9a302f2508337ba6a06d123854481b52de9c20996011'
redeem_script = Script(['OP_2', alice_pk, bob_pk, carol_pk, 'OP_3', 'OP_CHECKMULTISIG'])
p2sh_addr = P2shAddress.from_script(redeem_script)
print(f"Redeem Script: {redeem_script.to_hex()[:32]}...{redeem_script.to_hex()[-8:]}")
print(f"P2SH Address: {p2sh_addr.to_string()}")

Key Functions#

  • Script([...]): List of opcodes and data → Script object

  • P2shAddress.from_script(script): Hash160(script) → Base58Check

  • Serialization: 522102898711...601153ae (OP_2 + 3×pubkey + OP_3 + OP_CHECKMULTISIG)

# Example 2: Spend multisig P2SH
# Reference: code/chapter03/02_spend_multisig_p2sh.py

alice_sk = PrivateKey('cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT')
bob_sk = PrivateKey('cSNdLFDf3wjx1rswNL2jKykbVkC6o56o5nYZi4FUkWKjFn2Q5DSG')
redeem_script = Script(['OP_2', alice_pk, bob_pk, carol_pk, 'OP_3', 'OP_CHECKMULTISIG'])
txin = TxInput('4b869865bc4a156d7e0ba14590b5c8971e57b8198af64d88872558ca88a8ba5f', 0)
txout = TxOutput(to_satoshis(0.00000888), P2pkhAddress('myYHJtG3cyoRseuTwvViGHgP2efAvZkYa4').to_script_pub_key())
tx = Transaction([txin], [txout])
alice_sig = alice_sk.sign_input(tx, 0, redeem_script)
bob_sig = bob_sk.sign_input(tx, 0, redeem_script)
txin.script_sig = Script(['OP_0', alice_sig, bob_sig, redeem_script.to_hex()])
signed_tx = tx.serialize()
print(f"Transaction size: {tx.get_size()} bytes")

Multisig Stack Execution (Brief)#

TXID: e68bef534c7536300c3ae5ccd0f79e031cab29d262380a37269151e8ba0fd4e0

Stage 1 (hash verification): ScriptSig → OP_0 + sig1 + sig2 + redeem_script → OP_HASH160 + expected hash → OP_EQUAL

Stage 2 (redeem script): OP_2 + 3 pubkeys + OP_3 + OP_CHECKMULTISIG → verify 2-of-3 signatures → TRUE

P2SH two stages: hash verification → stack reset → redeem script execution → 1 (true)

3.3 Time-Locked Inheritance: CSV-Enhanced P2SH#

CheckSequenceVerify (CSV) supports relative timelocks where spending is delayed relative to UTXO creation. Let’s examine a real implementation using testnet data.

Real Implementation: 3-Block Timelock#

Transaction ID:34f5bf0cf328d77059b5674e71442ded8cdcfc723d0136733e0dbf180861906f

This transaction demonstrates a P2SH script combining CSV timelock with P2PKH signature verification—a common pattern for inheritance and escrow.

# Example 3: Create CSV timelock script
# Reference: code/chapter03/03_create_csv_script.py

sk_csv = PrivateKey('cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT')
pk_csv = sk_csv.get_public_key()
seq = Sequence(TYPE_RELATIVE_TIMELOCK, 3)
redeem_csv = Script([seq.for_script(), 'OP_CHECKSEQUENCEVERIFY', 'OP_DROP',
    'OP_DUP', 'OP_HASH160', pk_csv.get_address().to_hash160(), 'OP_EQUALVERIFY', 'OP_CHECKSIG'])
p2sh_csv = P2shAddress.from_script(redeem_csv)
print(f"P2SH Address: {p2sh_csv.to_string()}")
print(f"Time Lock: 3 blocks")

Key Functions#

  • Sequence(TYPE_RELATIVE_TIMELOCK, blocks): Relative timelock

  • seq.for_script(): Push to redeem script

  • seq.for_input_sequence(): Set input’s sequence field

# Example 4: Spend CSV timelock script
# Reference: code/chapter03/04_spend_csv_script.py

txin_csv = TxInput('34f5bf0cf328d77059b5674e71442ded8cdcfc723d0136733e0dbf180861906f', 0, sequence=seq.for_input_sequence())
txout_csv = TxOutput(to_satoshis(0.00001), P2pkhAddress('myYHJtG3cyoRseuTwvViGHgP2efAvZkYa4').to_script_pub_key())
tx_csv = Transaction([txin_csv], [txout_csv])
sig_csv = sk_csv.sign_input(tx_csv, 0, redeem_csv)
txin_csv.script_sig = Script([sig_csv, pk_csv.to_hex(), redeem_csv.to_hex()])
print(f"Transaction size: {tx_csv.get_size()} bytes")

CSV Stack Execution (Brief)#

Redeem script: OP_3 OP_CHECKSEQUENCEVERIFY OP_DROP + P2PKH

Flow: PUSH 3 → CSV verification → OP_DROP → OP_DUP → OP_HASH160 → OP_EQUALVERIFY → OP_CHECKSIG → 1 (true)

Applications: digital inheritance, business continuity, Lightning channels.

3.4 P2SH vs Taproot#

P2SH extends scripts to multi-party and time conditions, but spending requires exposing the full redeem script—unlike Taproot, which can reveal only the execution branch.

Chapter Summary#

This chapter bridges basic P2PKH transactions and Taproot’s advanced features by exploring P2SH’s two fundamental patterns: multisig authorization and timelock conditions.

Key concepts mastered:

Two-stage verification: P2SH’s hash-then-execute model provides conceptual foundation for Taproot’s commitment scheme where complex scripts stay private until spent.

Multi-party authorization: The 2-of-3 multisig pattern shows how Bitcoin Script handles conditional logic and multiple verification requirements—skills essential for understanding Taproot script tree execution.

Time constraints: CSV-based timelocks introduce relative time concepts that underpin Lightning Network and other Layer 2 protocols built on Taproot.

Stack-based programming: Detailed stack execution tracing for multisig and timelock scenarios provides analytical skills needed for debugging and optimizing Taproot script paths.

bitcoinutils proficiency: Hands-on experience with Script building, P2shAddress generation, and signature creation prepares developers for Taproot’s more complex primitives.

Real transaction analysis: Using actual testnet transactions and mempool data builds the experiential skills needed for production Taproot development.

The progression from P2PKH’s simple signature verification to P2SH’s complex conditional logic laid the groundwork for Taproot’s revolutionary approach: making complex smart contracts indistinguishable from simple payments while providing unprecedented script flexibility and privacy.

In the next chapter, we’ll examine how SegWit’s witness structure revolutionized transaction malleability and fee calculation—concepts that directly support Taproot’s efficiency improvements and form the foundation for understanding P2TR’s witness-based spending paths.