Bitcoin transaction timelocks

Timelocks have been a long-standing fascination of mine, my first blog post even describing the usage of the checklocktimeverify opcode. Though I knew at the time what the rough role of checklocktimeverify was, I did not know how the different fields in a bitcoin transaction actually play together to enforce timelocks. As it turns out, there are a bunch of different things that usually get conflated with each other into this concept of a “timelock”. This is my attempt of disentangling them.

Lets start with the 4 byte nLockTime field in a bitcoin transaction. The values of nLockTime are interpreted as block height if the value is below 500000000, or as a UNIX timestamp if the value is >= 500000000. A transaction is only relayed, or minable if the nLockTime is either less or equal than the current block height, or if the nLockTime is either less or equal to the median timestamp of the past 11 blocks respectively (with the exception that I’m about to describe next).

Lets move on to the nsequence field. Every transaction input in a bitcoin transaction has a 4 byte nsequence field. Its value has meaning in the context of timelocks. The nLockTime and nsequence field have influence on when a transaction is considered ‘final’, ‘final’ meaning relayable and minable. If all transaction inputs have a nsequence value of 0xffffffff, then a transaction is final regardless of the locktime value. This means that nLockTime is only interpreted if one of the transaction inputs has a nsequence value that is below 0xffffffff.

So far nLockTime was only usable as a relay and mining restriction on a new transaction regardless of the transaction’s inputs. Given the rules above this new (still unrelayed) transaction could be replaced by yet another new transaction with a lower locktime that can be relayed and mined instantly. It would be nice to permanently lock up coins in an output that can only be spent after the timelock has expired.

This is where checklocktimeverify comes in. It is a bitcoin script opcode, that allows transaction outputs to be encumbered by a timelock. As described in a prior blog post, the checklocktimeverify opcode should be preceded by a timelock value (either a block or timestamp) in the script. The opcode fails the script if the transaction containing the checklocktimeverify output as an input has a nLockTime below the value preceding the checklocktimeverify opcode. This means that some outputs (in a mined and confirmed transaction) can only be spent once the nLockTime is above their required value. Additionally, checklocktimeverify fails, if the nsequence field in the same input is not below 0xffffffff (other inptus may be equal to 0xffffffff). It also fails if the nLockTime provided is not the same type (timestamp or block height) as the value preceding the opcode.

Both nLockTime and checklocktimeverify require knowledge of the current absolute median time, or block height of bitcoin to be used correctly. It would be useful to be able to say that a transaction input is only valid after a certain amount of blocks or time have passed, instead of saying what the absolute median timestamp or block height has to be for the transaction to be mined. This concept is called relative locktime.

Relative locktime uses the nsequence field to determine the relative amount of blocks or time that has to pass between when an input of a transaction is mined and when its corresponding consumed output was mined. The sequence number holds this relative time lock in the following way:

  1. If the most significant bit is set, the sequence numbers are not treated as relative timelocks.
  2. The 10th bit (counting from the significant bit upwards) is the type flag. If it is set, the relative time lock is interpreted in units of 512 seconds. If it is not set, the relative time lock is interpreted as number of blocks.
  3. Bits 17 through 32 hold the actual value of the relative timelock, allowing about a year of relative locktime. If the check of the relative timelock fails, the transaction is not considered final and will not be added to the mempool, or mined.

To achieve a similar effect as checklocktimeverify does with the absolute locktime on the relative locktime, checksequenceverify was introduced. This is a bitcoin script opcode that checks that the value preceding it in the script is lower than the relative locktime encoded in the sequence number of the transaction input containing it. It also checks that the value preceding the opcode has the same type as encoded by the type flag in the input’s nsequence field.

Additionally the nsequence field also plays a role in determining if a transaction is replaceable or not. Replaceable describes a transaction that has been relayed, but not mined yet, that can be replaced by another transaction. A transaction is considered replaceable if any of the transaction’s inputs has a nsequence field value below 0xffffffff-1.

Another great article on the subject can be found here. It also cites sources properly, unlike this one.

Written on December 13, 2019