Bitcoin Timelocks: How Absolute and Relative Locks Actually Enforce Time at Script Level

You write the script, test the logic, and fund the output. Then six months later, when the spending transaction hits the mempool, it bounces. The coins sit there, locked, and the error message tells you almost nothing useful. Somewhere between the script and the transaction fields, a timelock was misapplied. It happens more than developers admit, and it almost always traces back to a single confused assumption: that `OP_CHECKLOCKTIMEVERIFY` and `OP_CHECKSEQUENCEVERIFY` are two spellings of the same idea.

They are not. One locks against a fixed position in Bitcoin's history. The other locks against the age of the specific output being spent. Conflating them produces scripts that either enforce time too loosely or fail consensus entirely while looking, on the surface, perfectly correct.

The Clock That Starts at Genesis vs. the Clock That Starts at Deposit

`OP_CHECKLOCKTIMEVERIFY`, defined in BIP 65, compares the script's encoded value against the transaction's `nLockTime` field and against current chain state. If your script encodes block height 850000, the spending transaction must declare an `nLockTime` of at least 850000, and the network must actually have reached that height. Both conditions must hold simultaneously. The script has no interest in when the coins arrived; it cares only about an absolute position in Bitcoin's sequence of blocks.

Convert to Unix time rather than block height (values above 500,000,000 are interpreted as timestamps), and the same logic applies: the spending transaction's `nLockTime` must meet or exceed that Unix value.

Consider two people, Priya and Marcus, who each receive a CLTV-locked output under the same script encoding block 850000. Priya's output was created at block 600000. Marcus's at block 849500. Neither can spend until 850000. The wait is wildly different in calendar time. The enforcement is identical in chain terms.

That asymmetry is not a flaw. It is exactly what absolute locking is for.

`OP_CHECKSEQUENCEVERIFY`, from BIP 112, is a different animal entirely. It reads the spending input's `nSequence` field and compares it against the script's encoded value. `nSequence` expresses relative age: how many blocks, or 512-second intervals, must have elapsed since the output being spent was confirmed. The stopwatch starts at confirmation, not at genesis.

If a CSV script encodes 144 blocks, the spending transaction must set that input's `nSequence` to at least 144, and the output being spent must be at least 144 blocks deep. A coin confirmed yesterday cannot satisfy this condition regardless of current block height.

Run the same Priya-and-Marcus scenario under a 144-block CSV lock. Priya's output confirms at block 600000; she can spend from block 600144 onward. Marcus's confirms at 849500; he unlocks at 849644. The relative duration is identical. The absolute calendar moment differs by months. Neither script needed to know anything about the other.

Here is the detail that trips people up most reliably: both opcodes are script-level checks, but they depend on transaction-level fields. CLTV validates against `nLockTime`. CSV validates against `nSequence` on the specific input in question. A script using CLTV must also ensure the spending transaction sets `nLockTime` correctly. A script using CSV must ensure `nSequence` is set on the correct input with the right encoding. Getting the script right while botching the transaction fields produces something that passes a casual code review and still fails consensus. The script is a lock; the transaction fields are the key. You need both.

Where Each One Actually Belongs in Practice

CSV is the structural backbone of Lightning Network payment channels. When Alice and Bob open a channel, their commitment transactions use CSV delays on the revocation path, giving the honest party a window to publish a penalty transaction if the counterparty broadcasts an outdated state. Think of it less like a calendar appointment and more like a mandatory cooling-off period: what matters is reaction time after a transaction lands on-chain, not any fixed date.

CLTV fits the opposite situation, one where you genuinely need a fixed horizon. An inheritance arrangement that unlocks at a specific block height, a vesting schedule anchored to Bitcoin's own history, a dead-man's switch with a known expiry. The absolute nature is the feature, not a constraint to work around.

Mixing the two is both legal and sometimes exactly right. A redeem script can require that the chain has passed block 900000 and that the output is at least 48 blocks old, with CLTV and CSV each enforcing its own condition independently. The conditions do not interact; they stack.

One honest caveat, worth stating plainly: the mapping between block height and calendar time is approximate. Bitcoin targets ten-minute blocks, but variance is real, and anyone who has watched difficulty adjustments knows the distribution has tails. A CLTV lock calibrated to "roughly six months from now" in block-height terms might resolve a week early or late. That is not a reason to avoid CLTV; it is a reason to build in margin, particularly for time-sensitive contracts where early resolution carries legal or financial consequence.

Is a one-week variance acceptable for your use case? That question should be answered before the script is written, not after the output is funded.

CSV carries the same variance, applied to relative depth rather than absolute height. The uncertainty is proportionally smaller for short locks, more meaningful for long ones.

The cleaner mental model, and one worth keeping: CLTV is a date on a deed. CSV is a waiting period on a cheque. Both enforce time, but they answer completely different questions about which time they are measuring. A developer who internalizes that distinction will not spend an afternoon debugging a bounced transaction that looked, right up until confirmation, like it should have worked.