TeachMeBitcoin

Escrow Script:

From TeachMeBitcoin, the free encyclopedia Reading time: 4 min

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