1

I'm looking at Bolt 3 in the Lightning rfc. My question is about how a HTLC is redeemed. Example HTLC script for reference:

# 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

My understanding is that it can be redeemed in two ways. The first way can be done if the revocation secret is known.

<revocation_sig> <revocationpubkey>

revocationpubkey will produce the correct hash, OP_EQUAL will return "1", the script goes into OP_IF, and the revocation_sig will be valid for OP_CHECKSIG.

The second way involves

<remotehtlcsig> <payment_preimage>

Can someone explain how this satisfies the script? For example, what OP_SWAP does or when it would go through OP_NOTIF/OP_ELSE?

2

There are three ways in which that offered HTLC output can be redeemed: Revocation using the revocation public key (the example you mentioned), redeemed using a valid payment preimage or back to the local node after the timeout.

Redeemed using a valid pre-image by remote node:

  • The stack starts at <remotehtlcsig> <payment_preimage>
  • Since the HASH160 of <payment_preimage> is not equal to the HASH160 of revocationpubkey, the outer ELSE statement is triggered
  • <remote_htlcpubkey> is pushed to the stack. Stack is now <remotehtlcsig> <payment_preimage> <remote_htlcpubkey>.
  • OP_SWAP is pushed to the stack, which swaps the top two items on the stack.
  • Stack is now <remotehtlcsig> <remote_htlcpubkey> <payment_preimage>
  • OP_SIZE 32 OP_EQUAL verifies that the payment_preimage is 32 bytes.
  • Since the payment_preimage is 32 bytes it will output 1. As a result OP_NOTIF will not be triggered, and hence OP_ELSE statement will be triggered.
    • OP_NOTIF is triggered is the value on the stack is 0
    • OP_ELSE is triggered if the preceding OP_IF, OP_NOTIF and OP_ELSE are not executed.
  • The stack is still <remotehtlcsig> <remote_htlcpubkey> <payment_preimage>
  • The OP_HASH160 will hash the paymentpreimage. Since payment hash is SHA256 of the payment preimage, we need to hash it with RIPEMD160 so that it is equal to HASH160 of the paymentpreimage.
  • If the hash matches, then the stack will be <remotehtlcsig> <remote_htlcpubkey> OP_CHECKSIG
  • If the signature matches the remote_htlcpubkey then 1 will be returned to the stack and this offered HTLC output is successfully spent by the remote node.

Redeemed after time-out by the local node:

  • To trigger the HTLC timeout condition, the local node will use the witness: 0 <remotehtlcsig> <localhtlcsig> 0

  • The stack starts at 0 <remotehtlcsig> <localhtlcsig> 0

  • Since the HASH160 of 0 is not equal to the HASH160 of revocationpubkey, the outer ELSE statement is triggered

  • <remote_htlcpubkey> is pushed to the stack. Stack is now: 0 <remotehtlcsig> <localhtlcsig> 0 <remote_htlcpubkey>

  • OP_SWAP swaps the top two items on the stack. Stack is now: 0 <remotehtlcsig> <localhtlcsig> <remote_htlcpubkey> 0

  • Since size of 0 is not 32 bytes it will return 0 on top of stack. This triggers the OP_NOTIF statement. Stack is still: 0 <remotehtlcsig> <localhtlcsig> <remote_htlcpubkey> 0

  • OP_DROP drops the top item on the stack which is 0. Stack is now : 0 <remotehtlcsig> <localhtlcsig> <remote_htlcpubkey>

  • 2 is pushed to the stack. Stack is 0 <remotehtlcsig> <localhtlcsig> <remote_htlcpubkey> 2

  • OP_SWAP swaps the top two items on the stac. Stack is: 0 <remotehtlcsig> <localhtlcsig> 2 <remote_htlcpubkey>

  • <local_htlcpubkey> 2 OP_CHECKMULTISIG is pushed to the stack. The stack is thus 0 <remotehtlcsig> <localhtlcsig> 2 <remote_htlcpubkey> <local_htlcpubkey> 2 OP_CHECKMULTISIG.

  • The above is a multisig output which can be spent only with valid signatures from the local and remote keys. These keys are exchanged when adding the HTLC. The output of the above transaction is sent to the local node using a to_self_delay using the time-out transactions.

| improve this answer | |
  • Thanks, this clears up a lot. A couple of questions though. 1) Why is the OP_SIZE 32 check needed? Isn't it enough to check that OP_HASH160 gives the right value? 2) Why is OP_NOTIF used instead of OP_IF (and of course switching what goes into OP_ELSE)? – Darius Dec 3 '19 at 19:15
  • 1) According to the BOLTs payment preimage HAS to be 32 bytes of data. Assume you generate an invoice with payment hash representing the SHA256 of a 40 byte data. Now when fulfilling the HTLC, you cannot send the 40 bytes of data and as a result would want to terminate the transaction on-chain. If the size counter was not there, the hash would be equal and you would get the bitcoins. But all the other nodes in the path will also have to terminate their channels on chain to get the funds as they won't be able to look get the preimage from the confirmed tx. The size keeps this behavior in check. – Ugam Kamat Dec 3 '19 at 19:27
  • @Darius 2) The "Redeemed after time-out by local node case" that I mentioned in my answer is what triggers the OP_ELSE statement. – Ugam Kamat Dec 4 '19 at 12:43
1

Check the Script wiki would be helpful. I once did this by hand, here's the process.

First, put the unlocking script on top of the locking script,

<remotehtlcsig> <payment_preimage>

# 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

If it helps, put them vertically,

<remotehtlcsig>
<payment_preimage>
OP_DUP
OP_HASH160
<RIPEMD160(SHA256(revocationpubkey))>
OP_EQUAL
OP_IF
OP_CHECKSIG
OP_ELSE
...

Second, run them by hand. Keep in mind that, the instructions are read from top to bottom(or left to right), the stack grows as follows,

  1. <remotehtlcsig>
  2. <remotehtlcsig> <payment_preimage>
  3. <remotehtlcsig> <payment_preimage> OP_DUP

OP_DUP, from wiki, duplicates the top stack item, so,

  1. <remotehtlcsig> <payment_preimage> <payment_preimage>
  2. <remotehtlcsig> <payment_preimage> <payment_preimage> OP_HASH160

OP_HASH160, from wiki, The input is hashed twice: first with SHA-256 and then with RIPEMD-160, so we hash,

  1. <remotehtlcsig> <payment_preimage> <payment_preimage_hash160>
  2. <remotehtlcsig> <payment_preimage> <payment_preimage_hash160> <RIPEMD160(SHA256(revocationpubkey))>
  3. <remotehtlcsig> <payment_preimage> <payment_preimage_hash160> <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL

OP_EQUAL, from wiki, Returns 1 if the inputs are exactly equal, 0 otherwise. We check that <payment_preimage_hash160> != <RIPEMD160(SHA256(revocationpubkey))> and return 0,

  1. <remotehtlcsig> <payment_preimage> 0
  2. <remotehtlcsig> <payment_preimage> OP_IF OP_CHECKSIG OP_ELSE

OP_IF, OP_ELSE, and OP_NOTIF are flow controls, from wiki, we will jump into the OP_ELSE clause, which is,

...
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

Now we keep working on the stack,

  1. <remotehtlcsig> <payment_preimage> <remote_htlcpubkey>
  2. <remotehtlcsig> <payment_preimage> <remote_htlcpubkey> OP_SWAP OP_SWAP, from wiki, the top two items on the stack are swapped, so we swap <payment_preimage> and <remote_htlcpubkey>,
  3. <remotehtlcsig> <remote_htlcpubkey> <payment_preimage>
  4. <remotehtlcsig> <remote_htlcpubkey> <payment_preimage> OP_SIZE

OP_SIZE, from wiki, pushes the string length of the top element of the stack (without popping it).

  1. <remotehtlcsig> <remote_htlcpubkey> <payment_preimage> <size of payment_preimage>

Notice that the size of a valid <payment_preimage> is 32,

  1. <remotehtlcsig> <remote_htlcpubkey> <payment_preimage> <size of payment_preimage> 32
  2. <remotehtlcsig> <remote_htlcpubkey> <payment_preimage> <size of payment_preimage> 32 OP_EQUAL
  3. <remotehtlcsig> <remote_htlcpubkey> <payment_preimage> 1

We have a 1 here, so we jump into the OP_ELSE section in the whole OP_NOTIF ... OP_ENDIF, to remind you, the script left is,

...
    OP_ELSE
        # To remote node with preimage.
        OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF
  1. <remotehtlcsig> <remote_htlcpubkey> <payment_preimage> 1 OP_NOTIF ... OP_ELSE

Once jumped into the OP_ELSE, the rest is straightforward, step by step, we have,

  1. <remotehtlcsig> <remote_htlcpubkey> <payment_preimage> OP_HASH160
  2. <remotehtlcsig> <remote_htlcpubkey> <hash160 of payment_preimage>
  3. <remotehtlcsig> <remote_htlcpubkey> <hash160 of payment_preimage> <RIPEMD160(payment_hash)>
  4. <remotehtlcsig> <remote_htlcpubkey> <hash160 of payment_preimage> <RIPEMD160(payment_hash)> OP_EQUALVERIFY
  5. <remotehtlcsig> <remote_htlcpubkey>
  6. <remotehtlcsig> <remote_htlcpubkey> OP_CHECKSIG

The final OP_CHECKSIG will check <remotehtlcsig> and <remote_htlcpubkey>, thus the HTLC will be spent via the unlocking script, <remotehtlcsig> <payment_preimage>.

The orders may not be strictly precise, but the general direction should be correct. Another tool is Bitcoin Script Debugger. And yes, as pointed out by Ugam Kamat, there are three ways to redeem a HTCL.

Happy Lightning!

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.