TeachMeBitcoin

Vault design:

From TeachMeBitcoin, the free encyclopedia Reading time: 4 min

11. Timelock Script Patterns for Vaults

What Is a Bitcoin Vault?

A Bitcoin vault is a storage pattern that makes it impossible to immediately sweep all funds — even if the private key is compromised. The idea is to introduce a mandatory delay between the "request to spend" and the actual spending, giving the legitimate owner time to detect and cancel unauthorized transactions. Timelocks are the core primitive that makes this possible.

The term "vault" in Bitcoin typically refers to constructions using:

  1. CLTV/CSV delays on the spending path.

  2. Revocation mechanisms that allow the owner to "claw back" funds if an unauthorized spend is detected during the delay period.

Pattern 1: Basic Vault with CSV Delay

# Vault design:
# To spend vault funds, you must first "unvault" them to a hot wallet.
# The unvaulted UTXO has a CSV delay before it can be spent.
# During the delay, the vault owner can sweep back to cold storage.

# Step 1: Vault UTXO (cold storage)
vault_script:
  OP_DUP OP_HASH160 <VaultKeyHash> OP_EQUALVERIFY OP_CHECKSIG
  # OR use a more complex multi-sig vault

# Step 2: Unvaulting transaction creates a "hot pending" UTXO:
hot_pending_script (witnessScript):
  OP_IF
    # Path A: Spend to destination after CSV delay
    144 OP_CHECKSEQUENCEVERIFY OP_DROP
    OP_DUP OP_HASH160 <HotWalletKeyHash> OP_EQUALVERIFY OP_CHECKSIG
  OP_ELSE
    # Path B: Claw back to cold storage immediately (revocation)
    OP_DUP OP_HASH160 <ColdStorageKeyHash> OP_EQUALVERIFY OP_CHECKSIG
  OP_ENDIF

# If no theft:
#   After 144 blocks, hot wallet sweeps funds normally.
# If theft detected:
#   During the 144-block window, owner sweeps back to cold storage.

Pattern 2: Pre-Signed Vault (OP_VAULT Precursor)

Before OP_VAULT (a proposed new opcode), vaults were implemented with pre-signed transactions:

# Pre-signed vault construction:
# 1. Generate ephemeral key pair (vault key)
# 2. Pre-sign "unvault" and "recovery" transactions
# 3. DELETE the ephemeral private key (making the vault one-way)
# 4. The only way to spend is via the pre-signed transactions

# Pseudocode for vault setup:
import secrets
import hashlib

def create_vault(amount_sats, destination_address, recovery_address, delay_blocks=144):
    # Step 1: Ephemeral vault key
    vault_privkey = secrets.token_bytes(32)
    vault_pubkey = privkey_to_pubkey(vault_privkey)

    # Step 2: Create vault UTXO (send to vault pubkey)
    vault_script = p2wpkh_script(vault_pubkey)
    # Fund vault_script with amount_sats

    # Step 3: Pre-sign unvault transaction
    # This TX moves funds from vault → hot_pending_script
    hot_pending_script = make_csv_script(
        delay_blocks=delay_blocks,
        spend_key=destination_address,
        recovery_key=recovery_address
    )
    unvault_tx = create_transaction(
        inputs=[vault_utxo],
        outputs=[hot_pending_script]
    )
    unvault_sig = sign(unvault_tx, vault_privkey)

    # Step 4: Securely delete vault_privkey
    vault_privkey = None  # In practice, use secure erasure

    # Step 5: Pre-sign recovery transaction (from hot_pending_script)
    recovery_tx = create_transaction(
        inputs=[hot_pending_utxo],
        outputs=[p2wpkh_script(recovery_address)]
    )

    return unvault_sig, recovery_tx

Pattern 3: Covenant-Style Vault

Covenants (restricted spending paths) combined with timelocks create more powerful vaults. While full covenants aren't natively in Bitcoin Script today, we can approximate them:

# Vault with two-level protection:
# Level 1: 24-hour delay (CSV = 144 blocks) before funds move to hot wallet
# Level 2: 7-day absolute locktime (CLTV) from vault creation for large moves

# Small withdrawals (< 0.1 BTC):
  144 OP_CSV OP_DROP <HotWalletKey> OP_CHECKSIG

# Large withdrawals (> 0.1 BTC):
  <VaultCreationBlock + 1008> OP_CLTV OP_DROP
  1008 OP_CSV OP_DROP
  2 <HotKey> <ColdKey> 2 OP_CHECKMULTISIG

# Recovery (immediate, any time):
  2 <RecoveryKey1> <RecoveryKey2> <RecoveryKey3> 3 OP_CHECKMULTISIG

Pattern 4: Watchtower-Protected Vault

In production vault setups, watchtowers monitor the mempool for unauthorized unvault transactions and broadcast the revocation transaction:

# Watchtower monitoring logic (simplified)
def watchtower_monitor(mempool, vault_utxos, recovery_txs):
    """
    Monitor mempool for unauthorized vault spends.
    If detected, broadcast pre-signed recovery transaction.
    """
    for tx in mempool.get_new_transactions():
        for inp in tx.inputs:
            if inp.utxo_id in vault_utxos:
                # Unauthorized unvault detected!
                utxo = vault_utxos[inp.utxo_id]

                if not is_authorized(tx, utxo):
                    recovery_tx = recovery_txs[inp.utxo_id]

                    # Broadcast with high fee to beat the attacker
                    recovery_tx.set_fee(high_priority_fee())
                    broadcast(recovery_tx)

                    alert_owner(utxo, tx)

The OP_VAULT Proposal (BIP 345)

BIP 345 proposes new opcodes (OP_VAULT and OP_VAULT_RECOVER) to natively support vault semantics without pre-signed transactions, making vaults more flexible and not reliant on ephemeral key deletion. While not yet activated, it would complement CSV/CLTV by adding covenant-style spend restrictions that enforce where funds must go, not just when.

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