Escrow Script:
10. Timelock Script Patterns for Escrow
Escrow Without a Trusted Third Party
Traditional escrow requires a trusted intermediary — a lawyer, bank, or escrow service — who holds funds until contract conditions are met. Bitcoin timelocks enable trustless escrow: funds are held by the Bitcoin protocol itself, with conditions enforced by consensus rules rather than human judgment.
Pattern 1: Two-Party Escrow with Timeout
The simplest escrow: Alice pays Bob for a service. Bob must deliver before a deadline, or Alice reclaims.
# Escrow Script:
# Path 1 (OP_IF = 1): Bob + Alice cooperate → Bob receives payment (service delivered)
# Path 2 (OP_ELSE): Alice reclaims after timeout (service not delivered)
redeemScript:
OP_IF
# Cooperative release: both parties sign
2 <AlicePubKey> <BobPubKey> 2 OP_CHECKMULTISIG
OP_ELSE
# Alice's refund after 2 weeks (2016 blocks)
2016 OP_CHECKSEQUENCEVERIFY OP_DROP
OP_DUP OP_HASH160 <AlicePubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
OP_ENDIF
# When Bob delivers and Alice confirms:
scriptSig: 0 <AliceSig> <BobSig> 1 <redeemScript>
# When deadline passes without delivery:
scriptSig: <AliceSig> <AlicePubKey> 0 <redeemScript>
# + nSequence must be >= 2016 (set in spending tx input)
Pattern 2: Three-Party Escrow with Arbiter
For larger transactions, an arbiter (trusted third party) can resolve disputes, but is only involved when parties disagree:
# Path 1: Alice + Bob agree (2-of-2) → payment released, no arbiter involved
# Path 2: Alice + Arbiter (Alice wins dispute) → Alice refunded
# Path 3: Bob + Arbiter (Bob wins dispute) → Bob paid
# Path 4: CLTV expiry → Alice can reclaim without arbiter (arbiter disappeared)
redeemScript:
OP_IF
# Happy path: direct agreement
2 <AlicePubKey> <BobPubKey> 2 OP_CHECKMULTISIG
OP_ELSE
OP_IF
# Dispute: arbiter sides with Alice
2 <AlicePubKey> <ArbiterPubKey> 2 OP_CHECKMULTISIG
OP_ELSE
OP_IF
# Dispute: arbiter sides with Bob
2 <BobPubKey> <ArbiterPubKey> 2 OP_CHECKMULTISIG
OP_ELSE
# Absolute fallback: Alice reclaims if arbiter vanishes
<AbsoluteExpiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
OP_DUP OP_HASH160 <AlicePubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
OP_ENDIF
OP_ENDIF
OP_ENDIF
Pattern 3: Hash-Locked Escrow (Conditional on Secret)
Combine a hash preimage with a timelock for atomic escrow, where payment is tied to revealing a secret:
# Bob is paid if he reveals the secret (preimage of hash H)
# Alice is refunded after timeout if secret is not revealed
witnessScript:
OP_IF
# Bob reveals preimage and signs
OP_SIZE 32 OP_EQUALVERIFY
OP_HASH256 <H> OP_EQUALVERIFY
<BobPubKey> OP_CHECKSIG
OP_ELSE
# Alice timeout recovery
<ExpiryBlock> OP_CHECKLOCKTIMEVERIFY OP_DROP
<AlicePubKey> OP_CHECKSIG
OP_ENDIF
# Bob spends (knowing preimage):
witness:
<BobSignature>
<preimage> (32 bytes)
01 (OP_IF branch = true)
<witnessScript>
# Alice refunds (after expiry):
witness:
<AliceSignature>
00 (OP_ELSE branch = false)
<witnessScript>
Pattern 4: Escrow with Staged Release
For long-term contracts (e.g., software development milestones), funds are released in stages:
# Milestone-based escrow using multiple UTXOs
# Milestone 1 (3 BTC): released by client signature after milestone 1 block
# Milestone 2 (3 BTC): released by client signature after milestone 2 block
# Milestone 3 (4 BTC): released by client + auditor after milestone 3 block
def milestone_script(developer_key, client_key, milestone_block):
return f"""
OP_IF
# Client approves milestone payment
{milestone_block} OP_CHECKLOCKTIMEVERIFY OP_DROP
2 {developer_key} {client_key} 2 OP_CHECKMULTISIG
OP_ELSE
# Developer reclaims if client disappears (long timeout)
{milestone_block + 26280} OP_CHECKLOCKTIMEVERIFY OP_DROP
{developer_key} OP_CHECKSIG
OP_ENDIF
"""
Taproot-Enhanced Escrow
With Taproot (BIP 341), escrow scripts can be more private and efficient. The cooperative path (KeyPath spend) looks like a regular single-signature transaction, and the timelock/dispute paths are hidden in the Merkle tree until needed:
# Taproot escrow:
# Internal key: MuSig2(Alice, Bob) → cooperative KeyPath spend (private)
# Script leaf 1: Alice reclaims after CLTV (visible only if used)
# Script leaf 2: Arbiter resolution (visible only if used)
internal_key = musig2_combine(alice_key, bob_key)
leaf1 = Script("<CLTV> OP_CHECKLOCKTIMEVERIFY OP_DROP <AliceKey> OP_CHECKSIG")
leaf2 = Script("2 <AliceKey> <ArbiterKey> 2 OP_CHECKMULTISIG")
taproot_output = taproot_construct(internal_key, [leaf1, leaf2])
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: