Pseudocode for BIP 68 validation
5. How CSV Uses the nSequence Field
The nSequence Field: Historical Context
The nSequence field is a 32-bit unsigned integer attached to each transaction input. Satoshi Nakamoto originally designed it for a now-abandoned feature: transaction replacement via sequence number increments. In that original vision, a transaction with a lower sequence number could be replaced by a later version with a higher sequence number, and only when nSequence reached 0xFFFFFFFF (SEQUENCE_FINAL) would the transaction be considered "final" and eligible for inclusion in a block.
This mechanism was disabled early in Bitcoin's history due to denial-of-service concerns. For many years, nSequence was used only as a simple binary: either 0xFFFFFFFF (final) or something lower (signaling replaceability via RBF). BIP 68 changed all of that.
BIP 68's Reinterpretation of nSequence
BIP 68 established that for version 2 transactions, the lower bits of nSequence carry enforced consensus meaning:
nSequence Bit Layout (BIP 68):
Bit 31 (0x80000000): Disable Flag
- If SET: BIP 68 does NOT apply. Treat nSequence as old-style.
- If CLEAR: BIP 68 rules apply.
Bit 22 (0x00400000): Type Flag
- If CLEAR: Value in bits 0-15 is a BLOCK COUNT.
- If SET: Value in bits 0-15 is a count of 512-SECOND intervals.
Bits 16-22 (excluding bit 22): Reserved (must be 0 for consensus)
Bits 0-15 (0x0000FFFF): Relative Locktime Value
- Maximum block count: 65535 blocks (~455 days)
- Maximum time: 65535 × 512 seconds = 33,553,920 seconds (~388 days)
How the Network Enforces nSequence
When a miner (or full node) validates a transaction with nSequence-based relative locks:
-
Check that
tx.version >= 2. -
For each input, if
nSequencedoes not have bit 31 set: a. Look up the UTXO being spent and find its confirmation block height (or median time past if time-based). b. Compute how many blocks (or 512-second intervals) have elapsed since that confirmation. c. Compare that elapsed value against the relative lock innSequence. d. If insufficient time has passed, reject the transaction.
# Pseudocode for BIP 68 validation
def validate_relative_locktime(tx, input_index, utxo_block_height, current_block_height,
utxo_median_time, current_median_time):
if tx.version < 2:
return True # BIP 68 doesn't apply
nsequence = tx.inputs[input_index].nSequence
# Check disable flag
DISABLE_FLAG = 1 << 31
if nsequence & DISABLE_FLAG:
return True # Relative locktime disabled for this input
TYPE_FLAG = 1 << 22
VALUE_MASK = 0x0000FFFF
locktime_value = nsequence & VALUE_MASK
if nsequence & TYPE_FLAG:
# Time-based: units of 512 seconds
elapsed_seconds = current_median_time - utxo_median_time
elapsed_units = elapsed_seconds // 512
if elapsed_units < locktime_value:
raise ValidationError("Relative time locktime not satisfied")
else:
# Block-based
elapsed_blocks = current_block_height - utxo_block_height
if elapsed_blocks < locktime_value:
raise ValidationError("Relative block locktime not satisfied")
return True
What CSV Actually Checks
When the script interpreter encounters OP_CHECKSEQUENCEVERIFY:
1. Peek at top of stack (do not pop) → call it `script_sequence`
2. If script_sequence bit 31 is SET → pass (CSV disabled in script; act as NOP)
3. If tx.version < 2 → FAIL
4. Let `input_sequence = nSequence of the current input`
5. If input_sequence bit 31 is SET → FAIL (relative locktime disabled on input)
6. If (script_sequence & TYPE_FLAG) != (input_sequence & TYPE_FLAG) → FAIL (type mismatch)
7. If (input_sequence & VALUE_MASK) < (script_sequence & VALUE_MASK) → FAIL
8. Otherwise: act as NOP (do not pop stack)
Practical nSequence Values for CSV
Commonly Used nSequence Values:
For BLOCK-based relative locks:
1 block: nSequence = 0x00000001
6 blocks: nSequence = 0x00000006 (~1 hour)
144 blocks: nSequence = 0x00000090 (~1 day)
1008 blocks:nSequence = 0x000003F0 (~1 week)
For TIME-based relative locks (512-second units):
~1 hour: nSequence = 0x00400007 (7 × 512s = 3584s ≈ 60 min)
~1 day: nSequence = 0x004000A8 (168 × 512s = 86016s ≈ 24hr)
~1 week: nSequence = 0x00400492 (1170 × 512s ≈ 7 days)
Special values:
RBF signaling: nSequence = 0xFFFFFFFD or 0xFFFFFFFE
SEQUENCE_FINAL: nSequence = 0xFFFFFFFF (disables nLockTime AND BIP68)
CSV in Script vs. nSequence in Transaction
An important subtlety: CSV in the script encodes the minimum required relative lock, while nSequence in the spending transaction encodes the actual claimed relative lock. The spender must set their nSequence to a value that satisfies the script's CSV requirement, AND enough blocks must have actually passed since the UTXO was confirmed. You can't cheat by setting nSequence to a large value if the UTXO is freshly confirmed — miners check the actual elapsed blocks.
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: