Pseudocode for CLTV validation logic
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:
-
0— the transaction can be included in any block immediately. -
Between
1and499,999,999— it is interpreted as a block height. -
500,000,000or above — it is interpreted as a Unix timestamp (seconds since epoch).
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:
-
Have
nLockTime >= 800000. -
Have the spending input's
nSequence != 0xFFFFFFFF(e.g., set to0xFFFFFFFE). -
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.
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: