Collin Rukundo

Understanding Bitcoin's transaction timelocks

Timelocks are the heart of many Bitcoin smart contracts and the Lightning Network, where they are applied to punish a cheating channel partner who attempts to publish an old state.

They are also perfect for delaying transactions where you can create a transaction now that pays someone after a specified amount of time.

In this post, I will describe timelocks with respect to transactions on the Bitcoin network. I will get into what they are, how they are classified and how they work.

What are timelocks?

Timelocks are a primitive (building block) in Bitcoin smart contracts that make it possible to specify explicit conditions under which a transaction can be considered valid on the network. Simply put, they are a type of smart contract that restricts the spending of specific bitcoin until a specified future time (UNIX timestamp) or block height.

Wait, what is time?

Before diving further into timelocks, it is important to understand how time works in this digital realm. How do you ‘agree’ on time in a decentralised trust-less system?

The average time between blocks is 10 minutes but there's no mechanism to guarantee this because there are so many variables at play like hash power and network conditions. However, the bitcoin mining difficulty algorithm which adjusts every 2016 blocks attempts to remedy this and ensure blocks come in roughly within that time.

Timestamps were messy too since nodes couldn't be trusted to provide accurate timestamps. To fix the issue for timestamp-based locks, 'median time past' (MTP) was introduced in #BIP113 which, rather than using the timestamp of the block including the transaction, made it so that timelocks use the median of the past 11 blocks' timestamps. The consensus rules guaranteed that MTP will advance monotonically which was favourable.

Classifying timelocks

Timelocks can be classified by orientation as absolute or relative. They can also be classified as transaction-level or script-level depending on where they are used. The table below categorises the existing timelocks that I’ll get into in detail below.

Classification ↓ → Absolute Relative
**Transaction Level** nLockTime nSequence
**Script Level** CheckLockTimeVerify (CLTV) CheckSequenceVerify (CSV)

Absolute vs Relative

It is important to classify timelocks as absolute or relative because this determines a target for when the lock is released. ‘At 22:00’ and ‘in 6 hours’ are absolute and relative ways, respectfully, to define future time-based targets.

Absolute locks define a target as a specific timestamp or block height and transactions outputs are “unspendable” until that time has passed.

Relative locks, on the other-hand, make a transaction's outputs unspendable until a specified amount of time/blocks has passed since the transaction’s previous outputs were mined in a block.

Relative timelocks need to go on-chain before they “activate” while Absolute timelocks are active even off-chain.

Transaction-level vs Script-level

Transaction-level timelocks like nLockTime and nSequence are found in the transaction’s structure and they affect whether or not a transaction is valid, and this validity changes over time.

Script-level timelocks, on the other hand - like CLTV and CSV - are set in the transaction’s locking script and they determine if a transaction can even be made in the first place. CLTV and CSV combined with conditional logic are a magic wand that can build really complex contracts and versatile transactions.

A script-level timelocked transaction is valid before the timelock is reached, the UTXO just can’t be spent. Transaction-level timelocks make transactions invalid before the timelock is reached.

Transaction-level timelocks only prevent the transaction from being broadcast/mined, which means the outputs can be double-spent in the meantime. Here’s an example pulled from Mastering Bitcoin:

Alice signs a transaction spending one of her outputs to Bob’s address, and sets the transaction nLocktime to 3 months in the future. Alice sends that transaction to Bob to hold. With this transaction Alice and Bob know that:

  • Bob cannot transmit the transaction to redeem the funds until 3 months have elapsed.
  • Bob may transmit the transaction after 3 months.

However:

  • Alice can create another transaction, double-spending the same inputs without a locktime. Thus, Alice can spend the same UTXO before the 3 months have elapsed.
  • Bob has no guarantee that Alice won’t do that.

This double-spend problem is a limitation of nLockTime. The only guarantee is that Bob will not redeem these funds before 3 months elapse and even then, there’s no guarantee that Bob will get the funds.

Such a guarantee can only be gotten through a script-level timelock that restricts spending the UTXO, rather than on the transaction.

Script-level timelocks don’t invalidate a transaction so they can be mined right away and prevent the double-spending problem.

Let's dive into each timelock:

nLockTime

Every Bitcoin transaction has a field in its structure called ‘locktime’ and it specifies the earliest time a transaction can be added to a valid block.

Below is the structure of a Bitcoin transaction where locktime is set to 0:

{
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
      "vout": 0,
      "scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.01500000,
      "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
    },
    {
      "value": 0.08450000,
      "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
    }
  ]
}

By default, transactions created by Bitcoin Core (and perhaps other implementations) have locktime set to the current block to prevent fee snipping. If locktime <= 500000000, it is treated as block height. Any number beyond that is interpreted as a UNIX timestamp. Therefore, a transaction will only be considered valid and relayed if:

current block height >= nLockTime or if nLockTime <= MTP

With nLockTime, transactions can be encumbered for up to 9500 years using block numbers or until early 2106 with UNIX timestamps.

OP_CHECKLOCKTIMEVERIFY(OP_CLTV)

OP_CLTV is a script-level absolute timelock opcode detailed in #BIP65 and soft forked in late 2015. It allows transaction outputs to be encumbered, rather than whole transactions. OP_CLTV adds an encumbrance in the locking script by making the UTXO(coins) unspendable until the time specified in nLocktime has elapsed. We compare the OP_CLTV time of the first transaction with the nLockTime of the second transaction that spends the inputs from the first transaction. If it’s too early, the script fails - or if the time condition passes, the script proceeds.

Example of OP_CLTV in action:

Alice and Bob each create their own key pair. Bob then creates this transaction script:

IF
    <now + 6 months> CHECKLOCKTIMEVERIFY DROP
    <Bob’s pubkey> CHECKSIGVERIFY
ELSE
    <Alice’s pubkey> CHECKSIGVERIFY
ENDIF

After creating the script, Bob hashes it, encodes it as a P2SH address and sends the address to Alice. If Alice sends any Bitcoin to this address, she can redeem it with the following scriptSig:

0 <Alice’s signature> 0

After the 6 months have passed, Bob can also redeem the transaction output Alice created with the following:

0 <Bob’s signature> 1

nSequence

The sequence in every transaction (see transaction structure above) was originally made available to supersede unconfirmed transactions in the mempool. #BIP68 repurposed the field it into a timelock. nSequence is a field that can be used for a transaction-level relative timelock - specifying the earliest time an input can be added to a block based on the age of the output being spent by the input after being mined in a block.

By default, transactions made by Bitcoin Core have sequence of each input set to 0xFFFFFFFE.

If nSequence > 0xFFFFFFFE, then there’s no meaning to the sequence number and the transaction can be included in any block. But, if nSequence <= 0xFFFFFFFE, it implies an encumbrance and the nSequence has to evaluated. More on this here.

**Some context for how nSequence works: **

Consider two transactions A and B. B spends from A’s output. If A (which is B's input) has a sequence number of 10 blocks, then it means B can only be added to the block after 10 blocks have been generated after A was included in a block.

OP_CHECKSEQUENCEVERIFY(OP_CSV)

OP_CSV is a script-level relative timelock documented in #BIP112 and soft-forked in with nSequence and MTP in May 2016. OP_CSV is a bit similar to OP_CLTV in function, only relative.

The OP_CSV makes UTXO unspendable until a certain amount of time/blocks has elapsed relative to the time the UTXO in question was mined. So for example, if OP_CSV is set to 65 blocks, then the UTXO can only be spent after time estimated by 65 blocks has passed since the UTXO was mined.

“With CheckSequenceVerify you can say: I want this output to be spendable when it has X confirmation (relative to time of mining)

With CheckLocktimeVerify you can say: I want this output to be spendable at height X (absolute time)”

OP_CSV is used in the L2 Lightning Network because it allows for stringing together multiple chains of transactions. Since this script allows us to set an expiration date relative to the first broadcast transaction, a chain of transactions can be created while maintaining timelock guarantees.

OP_CSV can lock transactions for up to 65535 blocks (about 455 days) or 65535*512 seconds (about 388 days).

Here’s an example: An escrow transaction that times out automatically 60 days after being funded can be established like so:

Alice, Bob and an escrow service create a 2-of-3 multisig with the following redeeming script:

IF
        2 <Alice's pubkey> <Bob's pubkey> <Escrow's pubkey> 3 CHECKMULTISIG
    ELSE
        “60d” CHECKSEQUENCEVERIFY DROP
        <Alice's pubkey> CHECKSIG
    ENDIF

That's it. That's all I've learned about timelocks. I hope this post helps someone who is discovering them for the first time.

Here are other great resources on timelocks:

Thoughts? Leave a comment