TeachMeBitcoin

Pseudocode for BIP 68 validation

From TeachMeBitcoin, the free encyclopedia Reading time: 4 min

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:

  1. Check that tx.version >= 2.

  2. For each input, if nSequence does 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 in nSequence. 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.

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