The Indepth Anatomy of an HTLC
Abstract definition: A Hash Time Locked Contract ( HTLC ) is a type of script that a sender can lock funds in with two payment routes. Either the redeemer provides something that unlocks the first payment route, or the first payment route “times out” and the second payment route is exposed to the sender, who then has the option of taking back her funds.
We’ll dive deeper into this. But first, what could this be used for?
HTLCs hold utility for a plethora of reasons — but mainly because they allow for trustless escrow within a standard* bitcoin transaction.
Some examples of HTLC usage:
- Revocation transactions in a Lightning Network channel
- Cross chain atomic swaps to send the value of some Bitcoin to another crypto network
- Submarine swaps to send the value of some Bitcoin from Layer1 to a Layer2 address
An HTLC script is useful for any situation where a sum of Bitcoin is expected in return for an action which is cryptographically verifiable.
*Standard transactions are allowed to propagate between nodes without being in a block. For example, coinbase transactions are nonstandard, but still valid.
How do HTLCs work?
When Alice wants enforce some kind of trade with Bob, she locks her funds behind an HTLC, meaning she sends funds to a P2SH address of an HTLC*. There isn’t one standard for a hash time lock contract, and there are many varieties for different use cases — but the basic premise here is the same.
Once Bob fulfills the off chain promise Alice gives him permission to retrieve his funds by giving Bob the preimage for the HTLC. Bob uses this preimage to redeem the funds locked in the contract as soon as he can, and Bob and Alice have had a successful transaction.
If for whatever reason Alice wants to back out of the off chain deal or Bob is taking too long, this type of bitcoin contract gives Alice the ability to take her funds back from the HTLC. Once a certain number of blocks has passed ( and if Bob hasn’t spent the transaction ), Alice can enforce the contract’s time out option and effectively remove the funds from Bob’s grasp. This is obviously irreversible. An HTLC is not something you want to store funds in long term.
Of course, Alice could rip Bob off by just never handing over the preimage. This is a valid concern, however if neither side is willing to take the elevated risk position, you could remedy this by tying the action Bob performs to the preimage. For example, in submarine swaps between bitcoin and the Lightning Network, Bob knows the preimage he will be given is the same as the preimage he wants by comparing the hash in the HTLC and the hash in the invoice. He can guarantee the action he performs gives him the key he wants.
At an application level, an HTLC is a kind of locking script. If you’re unfamiliar with locking and unlocking scripts, a simple analogy would be two halves of a chain link. The left half hooks into the previous link ( UTXO ) and unlocks the funds in the previous link, and the right half is the new lock waiting to be unlocked by the next chain link.
So when we put money into an HTLC, we’re creating any normal transaction and assigning an output to a pay-to-script-hash ( P2SH ) address of our HTLC.
* An HTLC is a type of bitcoin script. In order to lock funds behind any sort of bitcoin script, you hash the script into the form of a bitcoin address ( this process results in a pay to script hash address, also known as P2SH ).
Let’s Talk Timeouts
A cancellation or cessation that automatically occurs when a predefined interval of time has passed without a certain event occurring.
— Oxford Dictionary
An HTLC isn’t capable of enforcing the repercussions of time out by itself in the way tcp/ip timeouts result in the connection automatically being closed, and the time out implementations can vary. The basic implementation specified by BIP 199 is for a conditional to be set in the unlocking script signifying that the UTXO can be spent by new people after a certain block height.
HTLC timeouts don’t exactly work like your connection timeouts. An HTLC timing out doesn’t prevent the original recipient from redeeming the unspent output. Why not? The answer lies in the fact that Bitcoin script validity is monotonic, meaning once a script payment path is valid it stays valid. We can’t remove the ability for a party to spend their output — we can only enable it, and control when it is enabled and by whom.
Most of the time* it will be Alice who determines when the HTLC will timeout. She does this in two ways: by specifying the OP_CHECKLOCKTIMEVERIFY ( OP_CLTV ) blockheight, which will be the lower bound for the timeout; and by choosing when, which is our upper bound.
Therefor it is imperative that if Alice’s HTLC is allowed to timeout, Alice spends her new payment path immediately. In other words, the sender is responsible for enforcement of timeout in an HTLC.
In Bitcoin, time is a consensual hallucination
— James Prestwich
Timeouts, as the name implies, are reliant on time. However as we all know, time is a bit wild in Bitcoin. For a deeper dive into time and locking, I highly recommend James Prestwich’s take.
Contrary to popular belief, you can use seconds or timestamps along with bitcoin blocks as a metric of time, however there are caveats with each which you should be aware. I don’t recommend one over the other, however I personally use block height metrics to measure time.
The check when evaluating a transaction attempting to timeout an HTLC is performed by OP_CLTV ( or OP_CHECKSEQUENCEVERIFY which is used for relative blockheight instead of absolute ). OP_CLTV compares the value at the top of the stack, which is typically supplied as part of the HTLC, against the nLockTime value of it’s own transaction. The nLockTime value represents the lowest block a transaction can be included in.
* In the case of layer 2 submarine swaps, Alice does not generate the HTLC, but rather Bob does. Bob takes some basic info from Alice such as a refund address and a layer 2 invoice and is able to generate all the other info necessary.
The BIP199 HTLC script
Let’s analyze this. Even if you’re not familiar with bitcoin script, you can see a familiar structure, the if/else op_codes. This provides the functionality you’d expect, and is how the different payment routes are established.
The first payment route ( implemented following
OP_IF ) is for Bob to redeem with some arbitrary key supplied by Alice after Alice receives some sort of off chain goods ( be it lightning network/altcoin funds, or something trivial such as a new snowboard ). Once Bob has said key, he can go about redeeming his payment route.
We can go more in depth though. Let’s take a look the relevant pubkey script for Bob’s payment route. After
OP_IF we have the following:
[HASHOP]: Some hashing algorthm, we’ll use
<digest>: the hashed key ( preimage ) Alice gave bob
OP_EQUALVERIFY: Checks if the last 2 items in the stack are equal
Let’s imagine the last 2 items in our stack are the preimage that Alice gave Bob. Remember, the stack when evaluating bitcoin script is initialized by the scriptSig below, in this case supplied by Bob.
OP_IFis evaluated, pops the stack. The first preimage is a buffer for this operation.
OP_SHA256is evaluated, pops the stack, hashes the preimage, and stores the result in the stack. The digest is stored in the stack.
OP_EQUALVERIFY takes the last 2 items in the stack ( the result of the hashed preimage supplied by Bob and the hashed primage supplied by Alice ) and compares them. If they’re the same, this payment route is valid.
The second payment route is more interesting. This is the route Alice will use to time out the transaction, however first the timeout block height must be met as we discussed earlier.
The Bolt 3 HTLCs
Now we understand both what HTLCs do and how they work. But we haven’t taken a look at any real world use cases ( except for Bob and Alice selling snowboards with painful friction ).
The Lightning Network ( L2 ) makes ample use of this technology, and has some interesting derivations worth looking at. I’ll assume the reader is somewhat familiar with channels in L2. If not, this is a good starting place.
Moving on, L2 channels are funded by a 2-of-2 multisig transaction between the participants. Payments made between the participants take the form of a commitment transaction, the final of which is published to the blockchain.
Now, the commitment tx has two outputs, but 4 different output types for various situations, 3 of which have HTLC locking scripts. That’s what we’re interested in today.
We won’t talk too much more about commitment transactions but if you’re interested in learning more about them, I don’t blame you. Here’s an awesome resource to sink your teeth into.
The to_local HTLC
Let’s start simple. The to_local output in the commitment transaction is for the local node to pay itself in a cooperative channel close, with a check for the remote to ensure no cheating.
But wait, notice anything? It looks almost identical to a BIP199 HTLC. The only difference is the hashing methodology. Instead of asking the bitcoin network to hash the public key supplied to the script, some work is saved by expecting the pubkey to be in the proper form to begin with, so the bitcoin network only has to do a simple comparison.
So what’s going on here? This script is used to commit an L2 channel to the blockchain. If all goes well, the node who published this transaction ( i.e. the local node ) waits for the number of blocks specified by
to_self_delay to pass until they can remove their funds from the HTLC with a exercise settlement transaction.
If the remote node can prove malicious behavior by having the revocation private key, it can spend the commitment transaction in a delivery transaction ( also known as a breach remedy transaction ).
The offered and received HTLC
Keeping up? It’s about to get a whole lot more fun. The offered/received HTLC is added to the commitment transaction when a lightning payment is in it’s pending state ( yes, lightning payments are not transacted by one commitment tx but instead three ). During normal operations, these outputs initially contain the head receive and offered amounts in the channel, and are replaced with simpler outputs in the next commitment tx, either with the to_local HTLC or a simple p2wpkh for the remote.
But, when things get dicey is when these scripts jump into action.
So first thing we must understand about this class of HTLC: they’re designed to be used only in a worst case scenario event. This includes channel payment failure, or suspected malicious activity.
Although, the defense against malicious activity is ample.
Why is simple. Both HTLCs have a catch all check for revocation wrapping the main contract. During the channel negotiation, the private key for the local’s revocation public key is given to the remote. This way the remote has the right to call foul when fit.
Moving past that initial if/else statement we can take a look at the core HTLC. You’ll notice the cores are a little different. But remember, these are two outputs on the same transaction. One to to other party, and one to yourself. We must consider them together, even though they are separate.
You’ll look closely and realize that the offered script is not encumbered by an OP_CLTV like the received script, meaning it’s easier to send than it is to receive as elaborated in the Bolt:
So for example, if the local node publishes its commitment transaction, it will have to wait to claim its own funds, whereas the remote node will have immediate access to its own funds
This system is pretty cool because it allows L2 clients to utilize HTLCs to perform a sort of Mexican standoff with channel participants, ensuring that either everyone plays fairly or only one gunslinger is left standing.
Resources of note:
- Super dank discussion — > https://github.com/lightningnetwork/lightning-rfc/issues/553