Files
guides/en/ln-05.md
T
Bitcoin Txoko a312b97f03 Create ln-05.md
2024-11-23 14:17:45 +01:00

10 KiB

  • Title: LN chapter 5: HTLC deep dive
  • Summary: In this chapter we will build on what we learned in the last chapter about HTLCs and see in more detail what they actually look like and how they fit into commitment transactions.

In the previous chapter we had a simplified overview of HTLCs, hopefully that gave you an understanding of the general mechanism and incentives at play. In this chapter we will delve into how the HTLCs look like in more detail and how they fit into commitment transactions

From nostr:naddr1qqxnzdenxgerzve5x5enyv3sqyg8wumn8ghj7mn0wd68ytnhd9hx2q3qtx0k0a7lw62vvqax6p3ku90tccgdka7ul4radews2wrdsg0m865sxpqqqp65wc4f8xj we learned about commitment transactions. Here's a quick recap:

Commitment transactions are asymmetric: Alice and Bob (participants of a channel) each hold their own commitment transactions. The commitment transaction that each participant holds looks slightly different to that of their peer's in that any output going to the local node must be encumbered by a relative time lock of to_self_delay. This is to give the other party a chance to spend along the revocation path of the output if they need to.

With this in mind, let's continue with the example from the previous chapter where Alice is sending 2 BTC to a recipient using her channel with Bob as the first hop in the route. Remember that Alice has been given a hash H to which she needs to pay. In this example, Alice is the HTLC offerer and Bob is the HTLC receiver.

Alice - HTLC offerer

Let's focus on how Alice will construct her commitment transaction to now include the HTLC. The commitment transaction will have three outputs:

  • One 5 BTC output to Bob, spendable immediately
  • One 3 BTC output with two possible spending paths:
    1. One spendable by Alice after a to_self_delay
    2. One immediately spendable by Bob if he has the required revocation key (see nostr:naddr1qqxnzdenxgerzve5x5enyv3sqyg8wumn8ghj7mn0wd68ytnhd9hx2q3qtx0k0a7lw62vvqax6p3ku90tccgdka7ul4radews2wrdsg0m865sxpqqqp65wc4f8xj and nostr:naddr1qqxnzdenxgerzvek8qenyvekqyxhwumn8ghj7mn0wvhxcmmvqgs9n8m87l0hd9xxqwndqcmwzh4uvyxmwlw0637kuhg98pkcy8ana2grqsqqqa282rd072 if you need a reminder)
  • One 2 BTC output with... hmm let's think about what needs to happen here together.

c7_1

This output is where the HTLC magic must happen. We need the following spending paths on this output:

  • It needs to be spendable by Bob if he has the pre-image of H; this is the hash-locked path
  • OR spendable by Alice after cltv_expiry
  • OR spendable by Bob immediately if he has the revocation key

BUT remember that Alice's outputs to herself must always have a relative timelock of to_self_delay even after cltv_expiry. So let's update the spending paths with this information:

  • It needs to be spendable by Bob if he has the pre-image of H; this is the hash-locked path
  • OR spendable by Alice after cltv_expiry AND after relative delay to_self_delay
  • OR spendable by Bob immediately if he has the revocation key

There is still a problem: encumbering the output to Alice by both timelocks could in the worst case extend the HTLC's timeout by to_self_delay. i.e. Bob could have an extra to_self_delay blocks in order to sweep the hash-locked output even though the HTLC is technically expired. So instead of locking this output with both timelock conditions, it is instead locked only by the cltv_expiry. Then instead of sending funds to Alice directly, funds are sent to a separate HTLC-timeout transaction (signed by both Alice and bob) and this separate timeout transaction enforces the to_self_delay. This allows Alice to lock in the fact that the HTLC has expired and prevents Bob from claiming the hash-locked output all while ensuring that Alice can only get her funds after to_self_delay and thus still allow Bob to spend from the revocation path (of the HTLC-timeout transaction) if needed.

The final form of the commitment transaction's HTLC output spending paths looks as follows:

  • One spending path to Bob if he has the pre-image of H (hash-locked path)
  • One spending path to Bob if he has the revocation key
  • One spending path to a second-stage HTLC-timeout transaction

The HTLC-timeout transaction is constructed like this:

  • The transaction itself is timelocked with nLocktime set to cltv_expiry. This means the spending path in the original commitment transaction that sends to this HTLC-timeout transaction is effectively time delayed by cltv_expiry
  • The transaction has one output with two possible spending paths:
  1. One to Alice after to_self_delay
  2. One to Bob if he can provide the revocation key

c7_2

The script for Alice's (the HTLC offere's) commitment transaction's HTLC output looks like this:

# To remote node with revocation key
OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_NOTIF
        # To local node via HTLC-timeout transaction (timelocked).
        OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
    OP_ELSE
        # To remote node with preimage.
        OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF

You can see that the first path is the revocation path, the second is the path to the HTLC-timeout transaction (the time-locked path) and the third is the hash-locked path.

The HTLC-timeout transaction output script looks like this:

OP_IF
    # Penalty transaction
    <revocationpubkey>
OP_ELSE
    `to_self_delay`
    OP_CHECKSEQUENCEVERIFY
    OP_DROP
    <local_delayedpubkey>
OP_ENDIF
OP_CHECKSIG

Bob - HTLC receiver

Let's now focus on how Bob (the HTLC receiver) will construct his commitment transaction to include the HTLC. The commitment transaction will have three outputs:

  • One 3 BTC output to Alice spendable immediately
  • One 5 BTC output with two possible spending paths:
    1. One spendable by Bob after a to_self_delay
    2. One immediately spendable by Alice if she has the required revocation key
  • One 2 BTC output that's a bit more complicated; let's dive in.

c7_3

Let's think about what spending paths this output should have:

  • One spending path that Bob can claim if he has the pre-image of H (hash-locked path) but since it is an output to himself it needs to have a to_self_delay
  • One spending path that Alice can spend immediately if she has the necessary revocation key
  • One spending path that Alice can spend after ctlv_expiry (time-locked path)

Again, having two different time locks in the same script poses a problem: if Bob knows the pre-image but has to wait to_self_delay blocks in order to spend from the hash-locked path then there is a chance that this to_self_delay is longer than the cltv_expiry that Alice must wait in order to claim the time-locked path. This would mean that Alice could potentially spend along the time-locked path even though Bob does have the pre-image. Therefore, Bob needs a way to lock in the fact that the hash-locked path will be used while still delaying his redemption of the funds by to_self_delay. So a separate HTLC-success transaction is used for this, allowing Bob to spend from the hash-locked path to this HTLC-success transaction which will then separately enforce the to_self_delay condition.

The final form of the commitment transaction's HTLC output spending paths looks like this:

  • One spending path to Alice if she has the revocation key (revocation path)
  • One spending path to Alice after cltv_expiry (time-locked path)
  • One spending path to the HTLC-success transaction if Bob can reveal the pre-image of H (hash-locked path)

The HTLC-success transaction looks like this:

  • The transaction is not time-locked (unlike in Alice's case)
  • The transactoin has one output with two possible spending paths:
    1. One to Bob after to_self_delay
    2. One to Alice if she can provide the revocation key

c7_4

The script for Bob's (the HTLC receiver's) commitment transaction's HTLC output looks like this:

# To remote node with revocation key
OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_IF
        # To local node via HTLC-success transaction.
        OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
        2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
    OP_ELSE
        # To remote node after timeout.
        OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF

You can see in the script above that the first path is the revocation path, the second is the path to the HTLC-success transaction (and is also the hash-locked path) and the third is the time-locked spending path.

The HTLC-success transaction output script looks like this:

OP_IF
    # Penalty transaction
    <revocationpubkey>
OP_ELSE
    `to_self_delay`
    OP_CHECKSEQUENCEVERIFY
    OP_DROP
    <local_delayedpubkey>
OP_ENDIF
OP_CHECKSIG

The full picture

c7_5

Review

  • The HTLC output in a commitment transaction differs depending on whether the transaction constructor is the HTLC offerer or the HTLC receiver
  • The HTLC output needs to be encumbered by two time locks, but having both time locks in the script can cause problems
  • The solution is to sweep the output to a second-stage HTLC-timeout or HTLC-success transaction and impose the to_self_delay on this transaction

References