Don’t Mix Your Timelocks
How and why we keep block height and timestamp timelocks separate in Miniscript
By Sanket Kanjalkar & Andrew Poelstra
Many applications of Bitcoin, including the Liquid Network, Lightning, and cross-chain atomic swaps, rely on timelocks, a smart contract primitive that restricts the spending of coins until a specified time in the future. These times may be specified relative to the start of the blockchain, or relative to a transaction’s inclusion in the blockchain. These times can also be specified in one of two ways: as a minimum block height, meaning that transactions using this primitive are valid only in blocks with greater height; or as a number of seconds, in which case validity is determined by looking at blocks’ timestamps rather than their height.
Recently, at Blockstream Research, we discovered a surprising interaction between height-based and timestamp-based timelocks. In a nutshell, while a given script may specify both height-based and timestamp-based timelocks, these cannot be used together in a single spend. While this restriction may sound sensible or even intuitive, its effect is that policies encoded in Script may not be satisfiable even when they appear to be.
Given the limited use of timelocks in modern Bitcoin applications today, this does not affect Bitcoin users directly, but wallet developers making use of timelocks must be aware of it. Our recommendation is that they avoid timestamp-based timelocks altogether.
What Are Timelocks?
In Bitcoin Script, we have two types of timelock: absolute and relative. Absolute timelocks (enforced by the CLTV opcode) are used to make outputs unspendable until a specified time. Relative timelocks (enforced by the CSV opcode), on the other hand, make outputs unspendable until they have been confirmed in the blockchain for a given amount of time.
In addition to the absolute/relative distinction, a CLTV/CSV argument above 500 million is interpreted as a timestamp, while an argument below 500 million is interpreted as a block height.
The CLTV opcode operates by comparing its argument to the nLockTime field of the transaction. In contrast, CSV compares its argument to the nSequence field, which appears in every transaction input. The sharing of nLocktime across all transaction inputs has interesting consequences that we will discuss later.
Timelocks in Miniscript
Last year, we released Miniscript, a language for representing a list of spending conditions in Bitcoin in a structured way. Miniscript allows efficient script composition, generic signing, and fee estimation. Miniscript’s goals are to make advanced Script functionality accommodate both machine and human analysis. Having a low-level property of timelocks invalidating transactions is antithetical to both goals — though fortunately, the interaction does not directly affect the core functionalities of Miniscript libraries.
Semantic analysis is the ability to determine the high-level behavior of a piece of code. Prior to Miniscript, there was no general way to do this type of analysis on Bitcoin Script.
To enable this analysis, we use a simplified representation of Miniscript called a semantic policy. Such a representation lets us abstract away the many ways that Script lets us express identical policies, leaving only the user-meaningful aspects of the script. For example, we can assert that certain coins can only be spent if they have a certain user’s signature. Alternately, we can use high-level templates to check whether the script (which may be jointly constructed by several users) meets all users’ expectations. For example, consider the following semantic policy:
Looking at the template, it would seem that Alice, Bob and Carol with their private keys should be able to spend the coins after 1 hour or 6 blocks, whichever is later. However, since the nSequence field in an input can be specified as a time or height but not both, the above policy is actually unspendable on the Bitcoin network.
This is even worse for absolute timelocks, as the nLocktime field is shared across all inputs. It is impossible to spend two inputs together where one of them relies on a time-based timelock and the other relies on a height-based timelock.
To avoid this surprising behavior, and to ensure that semantic policies accurately reflect the intended semantics of a script, we need to adjust our software to prevent mixed timelocks from causing user confusion.
Changes to Miniscript
One strategy might be to simply require that any script must use either height-based or timestamp-based timelocks, and forbid the mixing of the two in any script. However, such a broad requirement may eliminate some valid scripts, since problems only arise when timelocks of different types must be satisfied at the same time. Further, even if some branches are unsatisfiable due to timelock mixing, a script may still make sense and match the user’s intended behavior.
On the other hand, if a script contains branches that require mixed timelocks, semantic analysis becomes much harder. As we saw in the above example, changes in one part of a script may now affect unrelated branches, making the composition of scripts more difficult.
In light of the above considerations, we have changed the type system of Miniscript to detect whether any possible spend path might contain such a combination of height-based and timestamp-based timelocks. Such scripts are still valid Miniscript, and our signing algorithm will sign for them, but it will be visible to the user that there are conflicting timelocks. Our software cannot compute a semantic policy corresponding to such scripts (because it is unclear how to usefully represent the semantics of mixed timelocks), meaning that many forms of analysis are not possible.
Along with Miniscript, we released a compiler that determines the most efficient Miniscript implementing a semantic policy. The compiler takes as input a “policy language” which includes information about which branches are likely to be taken in practice. If a policy contains branches that include mixed timelocks, we refuse to compile them, assuming that the mixed-timelock behavior is not intended.
Timelocks may be specified either in terms of block height or clock time, but the combination of the two may cause otherwise-legal scripts to be unspendable. This may cause problems for a single script, whose semantics do not match user expectations, or across multiple scripts, when a wallet contains multiple coins which cannot be spent simultaneously. Wallet developers, when working with timelocks, must be aware of this interaction.
Miniscript was designed with the goal of making script semantics accessible for both developers and automated analysis. We have changed its design to ensure that this confusing aspect of Script is not also exposed as a confusing aspect of Miniscript.
Most Bitcoin users today use only height-based timelocks, and we encourage future application and wallet developers to do the same. By avoiding time-based timelocks, users will never need to think about this at all.