Re-Imagining Cold Storage with Timelocks

Bob McElrath
10 min readAug 10, 2016

--

Cold storage is a technique to keep keys offline and inaccessible to attackers. It is used by most major Bitcoin exchanges, occasionally moving funds between a “hot” wallet (used to satisfy user withdrawals) and an offline “cold” wallet. Exchanges then infrequently move Bitcoin between the two as demand changes.

Cold storage requires a mental model of assets tied to an intuitive understanding of countable, physical world things, like gold bars, seashells, or stock certificates. In Bitcoin this picture almost works for Pay-to-Pubkey-Hash (P2PKH) addresses, except for the fact that keys can be in more than one place at once, so the notion of a bitcoin being in any particular place is somewhat flawed. These techniques have not been consistently implemented which has led to a number of well-publicized losses.

Bitcoin has changed in significant ways since Nakamoto’s original design, most recently due the latter three of these BIP integrations.

  1. BIP65 OP_CHECKLOCKTIMEVERIFY
  2. BIP68 Relative nLocktime
  3. BIP112 OP_CHECKSEQUENCEVERIFY
  4. BIP113 Median time-past as endpoint for lock-time calculations

#1 was activated on Bitcoin’s mainnet in December 2015, while #2,3,4 were activated on July 4, 2016. These latter three are known collectively as the “CSV soft fork”, and using them requires that you specify that your transactions are version 2. Everyone operating an exchange or other service requiring holding or moving large amounts of Bitcoin could now be using these primitives to improve security and prevent theft. Let us examine these new possibilities for cold storage. Most of the content here was stated directly or implied by the above-linked Bitcoin Improvement Proposals. Now that the soft-fork has activated, it’s time to adopt more advanced strategies.

Prior to the CSV soft fork, timelocks were present in Bitcoin but very nearly useless. The use of these new timelocks allows a business to:

  1. Move funds on a schedule of the business’ choosing
  2. Notice if funds move off schedule, and take action.

The goal of an attacker is to get hold of a private key to unencumbered funds, which allows him to write a raw transaction sending ill-gotten funds to a destination of his choosing. By careful use of timelocks and signed transactions instead of keys, a business could implement risk mitigation strategies with multiple layers.

A Cornucopia of Time-Locks

Physical vault time locks were invented long before Bitcoin. In the old west, a bank manger might lock his vault at the close of the business day such that a timelock prevented it from being opened until the morning of the following business day. Like other great crypto-finance ideas, it did not see widespread use until long after it came out of its 1831 patent.

The CSV soft-fork contains not one, but at least three different forms of time-lock (depending on how you count). Timelocks can be either relative or absolute. A relative timelock says for example “this transaction cannot be mined until 6 blocks after its input”. An absolute timelock says for example “this transaction cannot be mined until after September 1, 2016 at 2:00am.

The time used in timelocks can be measured either using a number of blocks in the future, or using time in 512 second increments. Block generation is a random process, and one can’t be entirely sure how many blocks will be mined within any time interval, though on average there’s one every 10 minutes. Therefore the CSV soft fork allows one to specify time in units of 512 seconds instead, where the time is computed as the median of timestamps from the last 11 blocks. The reason for this choice is that it’s a power of two that is reasonably close to Bitcoin’s 10-minute (600 second) target block interval. So the timelock data fields (sequence_no and lock_time) can be expressed either way, and encode roughly the same maximum length of time.

The sequence_no field is so named for historical reasons, but the CSV soft fork repurposes it as a per-input timelock. The lock_time is a field in each transaction and is a per-transaction timelock. In both cases, a transaction that “turns on” either of these fields cannot be mined until its specified timelock requirement is satisfied.

Finally the CSV soft fork additionally adds two new opcodes, OP_CHECKSEQUENCEVERIFY which allows an input script to check the sequence_no field, and OP_CHECKLOCKTIMEVERIFY which allows a script to check the lock_time field of the transaction. By making a payment using a Pay-to-Script Hash script that uses these opcodes, one can enforce that an output in a mined transaction is un-spendable for a defined length of time.

Now, let’s imagine how these new tools might be used in an exchange-like operation. We will make the following assumptions about this hypothetical Bitcoin service:

  1. The service will receive deposits throughout the day, and will move funds from their hot wallet to cold storage multiple times throughout the trading day.
  2. The service needs to move funds from cold storage during the day to satisfy demand for withdrawls, and is willing to have those funds immobile for a defined period of time.
  3. The service will use only Pay-to-Script-Hash type transactions, enabling usage of these new timelocks as well as multi-signatures.

The last requirement introduces more complexity than it appears, since scripts can have complex evaluation conditions. It will be difficult for us to mentally decide “where” a Bitcoin balance resides, when it is controlled by scripts. Therefore we encourage you, dear reader, to dump the mental notion that some bitcoins are “Hot” and others are “Cold”, or that Bitcoins reside in any location at all. A particular Bitcoin output could be spent by both keys residing offline and those residing online, under different conditions.

The Structure of a Timelocked Transaction

The first way to use timelocks is simply by specifying the lock_time for a transaction and/or sequence_no fields of a transaction input — this forces me to hold the transaction for a defined period of time, after signing it. This is useful if I’m going to give the signed transaction to a counterparty, who cannot broadcast it until a time in the future that I have chosen. However we have no counterparties when trying to secure our own funds, so will use these fields in a different manner.

Schematic structure of a Bitcoin transaction, with timelocks sequence_no and lock_time in purple. Input and output scripts which enforce the timelocks are in pink.

The second way to use timelocks is to specify a script which checks the timelock. Since in a P2SH transaction, the script is implicit in an output of a transaction, I can thus require that an output be “locked” for a defined length of time that is not visible on-chain. We can then use IF/ELSE statements to chain together multiple timelocks, or “emergency” methods of spending the output. Let’s consider a simple time-locked transaction, with an emergency backout. This form of timelock is analogous to the above-pictured physical timelock, with the vault owner also having a master key.

A P2SH script for transfer from the Cold wallet to the Hot wallet, with emergency backout

Here we are “locking” some funds from the hot wallet, with a 24 hour timelock using OP_CHECKSEQUENCEVERIFY. The timelock ensures that the hot wallet cannot spend it for 24 hours (we are omitting the numeric encoding of the relative time “24 hours” for clarity). The above script is hashed using Hash160 to generate a P2SH address like 3Lockedfor24hqWsoQHL7MevXvZTFrxEhi. The hot wallet makes a payment to this address. Once payment is mined, those funds are time-locked due to the above P2SH script.

The first code path of the above OP_IF forces any transaction that spends these funds to have a sequence_no field 24h in the future from the time the transaction is mined sending funds to the above address. The second code path following OP_ELSE is intended to be used only in emergencies. During the course of normal operation, one should not need to rely on these emergency keys. They should be kept in a safe location.

Relative to payment channel transactions, the above transaction is “backwards” in that the “normal” and “abnormal” code paths are inverted. The “normal” script code path is the one with the timelock, and the other code path is the “abnormal” case. In payment channels the timelock is used in the event of an “adversarial close”. Payment channels with cooperative partners can sign transactions back and forth, so the non-timelocked code path is used in normal operation of a payment channel.

Pay to Timelocked Signed Transaction

Bare keys for P2PKH or multisig outputs are dangerous by themselves. Anyone who gets hold of them can write a transaction sending the funds anywhere they please. Therefore if I want to ensure continuous control of funds, I want to lay out a policy ahead of time, and force it to be executed. A bare key is not capable of doing this. A script by itself also cannot enforce a destination in a future spend. Scripts only validate the ability to spend the input but have no other influence on the output.

Therefore let me introduce a new trick: let’s both spend to a timelocked script, and also write a transaction spending that transaction, sign it, and delete the private key. The final destination of this intermediate transaction can be a hot wallet address, or an address involving keys that are separately stored and later imported to the hot wallet. By doing so we lay out a policy of not only the script/address at which funds reside, but also where they must move next. If I’m sure I have deleted the key, these funds are now impossible to steal until the second transaction is broadcast, and its timelock (if any) expires. Let’s call this “Pay to Timelocked Signed Transaction” or P2TST for short. I can even create further chains of spends to enforce a more complex policy, as well as have the intermediate Timelocked Signed Transaction (TST) involve input or output scripts enforcing a complex policy.

spend to a timelocked script, and also write a transaction spending that transaction, sign it, and delete the private key

The intermediate TST is a signed transaction that I must now store. In order to ensure that an attacker cannot intercept the corresponding private key, I could use a hardware signing device: have the hardware device generate a new key, export the corresponding pubkey, create and sign my transaction, and then reset the device to cause it to generate a new key. The private key is now lost. Thus even if an attacker gains control of the hot wallet server that generates these transactions, he cannot gain control of these private keys if the hardware module refuses to export them. An exchange operator would presumably create such transactions on a regular basis, perhaps any time deposits exceed 100 BTC, throughout the day.

When the operator wants to recover these locked funds, he retrieves and broadcasts these signed transactions. If these TST’s have a timelock on their input, then they cannot be broadcast for a defined time after they are created. If they have a timelock on their output, then their outputs cannot be spent for a defined time after the TST is broadcast. Thus the broadcast of a TST can be a “canary in the coal mine” alerting the exchange to possible nefarious behavior. Since the only way for an attacker to obtain funds is to obtain and broadcast the TSTs, an operator can monitor the blockchain for nefarious activity by looking for its own TSTs that appear off schedule.

When a TST appears on the chain that the operator doesn’t expect, he can take action by bringing out his emergency keys, and redirecting the funds before the timelock expires.

The final destination of this chain and the TST involves keys that are useless until the TST is broadcast. Even if an attacker gains those keys, it’s difficult to tell which outputs he could spend even if the TST is broadcast, since the destination addresses are script hashes, which involve other keys and conditions which are not present in the TST itself.

TSTs are a new object with interesting properties. We can treat them as fungible. An operator may choose to make many TSTs of fixed denomination, and physically store them in a secure location, treating them like gold bars.

Perhaps more interestingly, TSTs are not particularly sensitive. They are not really funds in themselves, nor do they convey control of funds (the private keys that control them, ultimately, are always with the operator). Therefore they can be held in custody by relatively untrusted parties. They can be backed up in multiple places. But, they effectively ensconce funds away from the hands of an attacker.

All the transaction scripts above should have an “emergency” clause as a possible code path using IF/ELSE that avoids the timelock, or is valid with a shorter timelock, so that attempts to steal funds can be thwarted. These “emergency” keys should never be used in the course of normal operation, making them extremely difficult to steal.

The Future of Cold Storage?

One drawback to the timelock policies discussed above is that once a transaction using these policies is spent, the corresponding script is revealed to the world, which exposes the policy of the operator. An attacker might not have your keys, but knowing that there’s a 3-of-3 emergency clause that evades the timelock is still valuable information.

There’s another advance coming to Bitcoin “soon” called Merkle Abstract Syntax Trees (MAST), which allow you to take a complex Boolean expression and convert it to a Merkle-like commitment tree. The advantage of this is that when you spend a transaction using MAST, only the executed code path is revealed, not any of the others. This allows the operator to hide other possible timelocks, emergency backouts, and their structure, from all observers on the network.

We also should note that the timelock scripts described above are all “witness data”, and can be segregated from the main transaction using segregated witness (aka “segwit”). What this means is that complex scripts receive a 75% discount against the 1MB block size limit when using segwit. Segregated witness has already activated on Bitcoin’s testnet and will activate on mainnet soon. Anyone developing a new cold storage policy now should consider timelocks and segregated witness from the start.

Timelocks and pre-signed transactions having deleted private keys (TST’s) are powerful new tools for risk management and loss prevention that are available now. Let’s all embrace these new tools and make better policies than “hot” and “cold” storage, which feels more like an old west model physical safe for gold bars, and perhaps incomplete when applied to Bitcoin.

Thanks to Ivan Brightly and Joey Fiscella for helpful conversations in constructing this post.

SolidX Partners provides consulting and strategy solutions to organizations seeking to learn about blockchain and implementation solutions. The firm also provides blockchain-based software solutions relating to the indelible recording of records, transfer of assets, and identity. For more information, please visit www.sldx.com or email the team at info@sldx.com.

--

--

Bob McElrath

Reverse-engineering the universe and remaking money. Ph.D. Theoretical Physics; Bitcoin hacker; SolidX CTO