TeachMeBitcoin

Pseudocode for CLTV validation logic

From TeachMeBitcoin, the free encyclopedia Reading time: 4 min

2. How CLTV Compares nLockTime to Stack Value

Understanding the Two-Layer Lock

CLTV operates at the intersection of two distinct Bitcoin mechanisms: the transaction-level nLockTime field and the script-level stack value. To understand how CLTV works mechanically, you need to understand both layers.

Layer 1: Transaction nLockTime

Every Bitcoin transaction has a 4-byte field called nLockTime. This value specifies the earliest time or block height at which the transaction can be included in a block. If nLockTime is:

The network enforces this: miners will not include a transaction in a block if the block's height (or median time past) is less than the transaction's nLockTime.

Layer 2: The CLTV Stack Value

When a script contains <locktime_value> OP_CHECKLOCKTIMEVERIFY, the <locktime_value> is a number encoded as a script integer. This value is what the script author demands as the minimum nLockTime the spending transaction must declare.

The Comparison Algorithm

When the script interpreter encounters OP_CHECKLOCKTIMEVERIFY, it performs the following checks in order:


1. Fail if stack is empty.

2. Let stack_value = top of stack (do not pop).

3. Fail if stack_value < 0.

4. Fail if stack_value > 0xFFFFFFFF (out of range for nLockTime).

5. Let tx_locktime = the spending transaction's nLockTime field.

6. Fail if tx_locktime < stack_value.

7. Fail if (stack_value < 500,000,000) AND (tx_locktime >= 500,000,000).
   (This prevents mixing block-height and timestamp comparisons)

8. Fail if nSequence of the current input == 0xFFFFFFFF.
   (This prevents bypassing nLockTime by using the final sequence number)

9. Otherwise: do nothing (act as NOP). Leave stack unchanged.

The nSequence Trap

One subtle but critical point is step 8 above. In Bitcoin, if any input in a transaction has nSequence == 0xFFFFFFFF (the maximum value, sometimes written as SEQUENCE_FINAL), then nLockTime is completely ignored by the protocol. A node will include such a transaction in a block regardless of what nLockTime says.

This creates a potential bypass attack: a malicious spender might set nSequence = 0xFFFFFFFF to disable the nLockTime check. CLTV explicitly guards against this by failing if the current input's nSequence is 0xFFFFFFFF. This forces the spending transaction to have a valid, non-final sequence number, which in turn means nLockTime will be respected by miners.

# Pseudocode for CLTV validation logic
def check_cltv(stack, tx, input_index):
    if len(stack) == 0:
        raise ScriptError("Empty stack")

    stack_value = stack[-1]  # Peek, do not pop

    if stack_value < 0:
        raise ScriptError("Negative locktime")

    tx_locktime = tx.nLockTime

    # Type mismatch check
    LOCKTIME_THRESHOLD = 500_000_000
    if (stack_value < LOCKTIME_THRESHOLD) != (tx_locktime < LOCKTIME_THRESHOLD):
        raise ScriptError("Locktime type mismatch (block vs time)")

    if tx_locktime < stack_value:
        raise ScriptError("Transaction locktime too early")

    if tx.inputs[input_index].nSequence == 0xFFFFFFFF:
        raise ScriptError("nSequence is FINAL; nLockTime disabled")

    # All checks pass — act as NOP
    return stack

Concrete Example

Suppose Alice creates a UTXO with this locking script (using block height 800,000 as the lock):

800000 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <AlicePubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

Bob (or Alice herself) wants to spend this output. To do so, the spending transaction must:

  1. Have nLockTime >= 800000.

  2. Have the spending input's nSequence != 0xFFFFFFFF (e.g., set to 0xFFFFFFFE).

  3. Be broadcast only after block 800,000 has been mined (otherwise miners will reject it based on nLockTime).

If any of these conditions aren't met, the transaction will either fail script validation or be rejected by miners.

Why Not Just Use nLockTime Alone?

The elegance of CLTV is that it makes the locktime part of the coin's spending condition, not just the transaction. Without CLTV, a coin could be re-spent with any transaction the key holder wishes to construct. With CLTV, the scriptPubKey itself enforces the time gate. Even if someone had Alice's private key, they couldn't construct a valid spending transaction before block 800,000 — the script itself would reject it.

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