Skip to content

Latest commit

 

History

History
526 lines (459 loc) · 22 KB

taproot-updates.md

File metadata and controls

526 lines (459 loc) · 22 KB

Lightning Is Getting Taprooty Scriptless-Scripty

This article contains a summary of the many changes that Taproot will bring to lightning. But first of all, a disclaimer:

taproot lightning txs image

While Taproot brings many privacy improvements to the table, there are a lot of other, unrelated updates to lightning that are higher priority and orthogonal to it, and implementers are already struggling to find time to review and implement everything. Please be patient, Taproot is coming, but it will take time!

Schnorr, Musig2 and adaptor signatures have been covered in this article: they are the foundations upon which we'll build everything, so I suggest reading it if you want to understand the low-level details of the following proposals.

Now that this is out of the way, let's dive into Taproot stuff.

Table of Contents

Musig2 Channel Funding

Thanks to Schnorr and Musig2, we can make lightning channels indistinguishable from any other key path spend in the cooperative case.

The funding transaction output will be a taproot output without a script path, where the internal_pubkey is the Musig2 aggregated public key of the two channel participants.

We need to add Musig2 nonces to existing messages (in the extension tlv stream) but don't need to define any new message. For example, when updating a commitment, revoke_and_ack will contain the next set of nonces and commit_sig will contain the current nonces and the (partial) signature.

Changing the funding output is a good opportunity to also introduce xpubs. Instead of directly exchanging a funding_pubkey when opening the channel, participants exchange a funding_xpub and then derive the actual funding_pubkey with non-hardened derivation. Future updates of the funding output (e.g. splicing funds in or out) can simply increment a derivation counter, which removes the need to explicitly share the next funding pubkeys and simplifies backup. This idea was proposed in ajtowns' mailing list post.

This change is quite simple and can be added relatively quickly (once Musig2 has been finalized).

Taproot Lightning Transactions

The existing transaction structure (detailed in this article) can be updated to use Taproot scripts.

This doesn't change the protocol: nodes still exchange the same messages when sending payments (update_add_htlc, commit_sig, revoke_and_ack, update_fulfill_htlc, update_fail_htlc).

We simply take the existing scripts and split them into several branches of a taproot tree, leveraging the key path spend whenever it makes sense.

The commitment transaction will then become:

{
  "version": 2,
  "locktime": 543210000,
  "vin": [
    {
      "txid": "...",
      "vout": ...,
      "scriptSig": "<signature for musig2(pubkey1, pubkey2)>",
      "sequence": 2500123456
    }
  ],
  "vout": [
    {
      "value": 0.5,
      "output_type": "to_local",
      "scriptPubKey": {
          "internal_pubkey": "
            # funds go to the remote node with the revocation key
            musig2(<revocationpubkey>,<remote_pubkey>)
          ",
          "tapleaf": "
            # or back to us after a relative delay (<to_self_delay>)
            <local_delayedpubkey>
            OP_CHECKSIGVERIFY
            <to_self_delay>
            OP_CHECKSEQUENCEVERIFY
          "
      }
    },
    {
      "value": 0.3,
      "output_type": "to_remote",
      "scriptPubKey": {
          "internal_pubkey": "<unused_nums_point>",
          "tapleaf": "
            # funds go back to the other channel participant after 1 block
            <remote_pubkey>
            OP_CHECKSIGVERIFY
            1 OP_CHECKSEQUENCEVERIFY
          "
      }
    },
    {
      "value": 0.00000330,
      "output_type": "local_anchor",
      "scriptPubKey": {
          "internal_pubkey": "<local_delayedpubkey>",
          "tapleaf": "
            # after a relative timelock of 16 blocks, anyone can claim this tiny amount
            # once the to_local output has been spent, revealing the local_delayedpubkey
            OP_16 OP_CHECKSEQUENCEVERIFY
          "
      }
    },
    {
      "value": 0.00000330,
      "output_type": "remote_anchor",
      "scriptPubKey": {
          "internal_pubkey": "<remote_pubkey>",
          "tapleaf": "
            # after a relative timelock of 16 blocks, anyone can claim this tiny amount
            # once the to_remote output has been spent, revealing the remote_pubkey
            OP_16 OP_CHECKSEQUENCEVERIFY
          "
      }
    },
    {
      "value": 0.05,
      "output_type": "offered_htlc",
      "scriptPubKey": {
          "internal_pubkey": "
            # funds go to the remote node with the revocation key
            musig2(<revocationpubkey>,<remote_pubkey>)
          ",
          "tapleaf_1": "
            # funds go back to us via a second-stage HTLC-timeout transaction (which contains an absolute delay)
            # NB: we also need the remote signature, which prevents us from unilaterally changing the HTLC-timeout transaction
            <local_htlcpubkey> OP_CHECKSIGVERIFY <remote_htlcpubkey> OP_CHECKSIGVERIFY
            1 OP_CHECKSEQUENCEVERIFY
          ",
          "tapleaf_2": "
            # funds go to the remote node if it has the payment preimage.
            OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
            <remote_htlcpubkey>
            OP_CHECKSIGVERIFY
            1 OP_CHECKSEQUENCEVERIFY
          "
      }
    },
    {
      "value": 0.08,
      "output_type": "received_htlc",
      "scriptPubKey": {
          "internal_pubkey": "
            # funds go to the remote node with the revocation key
            musig2(<revocationpubkey>,<remote_pubkey>)
          ",
          "tapleaf_1": "
            # funds go to us via a second-stage HTLC-success transaction once we have the payment preimage
            # NB: we also need the remote signature, which prevents us from unilaterally changing the HTLC-success transaction
            OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
            <local_htlcpubkey> OP_CHECKSIGVERIFY <remote_htlcpubkey> OP_CHECKSIGVERIFY
            1 OP_CHECKSEQUENCEVERIFY
          ",
          "tapleaf_2": "
            # funds go to the remote node after an absolute delay (timeout)
            <remote_htlcpubkey>
            OP_CHECKSIGVERIFY
            1 OP_CHECKSEQUENCEVERIFY
            <cltv_expiry>
            OP_CHECKLOCKTIMEVERIFY
            OP_DROP
          "
      }
    },
  ]
}

You should notice that the public keys used in the anchor outputs have changed. We were previously using the local_funding_pubkey and remote_funding_pubkey because since they were revealed in the witness of the funding input, it let anyone watching the blockchain claim these outputs after 16 blocks to avoid bloating the utxo set. But with Musig2 the individual funding public keys are never revealed, so we need to use other public keys that may be revealed when outputs of the commitment transaction are spent.

A taproot HTLC-success transaction looks like:

{
  "version": 2,
  "locktime": 0,
  "vin": [
    {
      "txid": "...",
      "vout": 42,
      "scriptSig": "<remotehtlcsig> <localhtlcsig> <payment_preimage>",
      "sequence": 1
    }
  ],
  "vout": [
    {
      "value": 0.04,
      "scriptPubKey": {
        "internal_pubkey": "
          # funds go to the remote node with the revocation key
          musig2(<revocationpubkey>,<remote_pubkey>)
        ",
        "tapleaf": "
          # or back to us after a relative delay (<to_self_delay>)
          <local_delayedpubkey>
          OP_CHECKSIGVERIFY
          <to_self_delay>
          OP_CHECKSEQUENCEVERIFY
        "
      }
    }
  ]
}

A taproot HTLC-timeout transaction looks like:

{
  "version": 2,
  "locktime": <cltv_expiry>,
  "vin": [
    {
      "txid": "...",
      "vout": 42,
      "scriptSig": "<remotehtlcsig> <localhtlcsig>",
      "sequence": 1
    }
  ],
  "vout": [
    {
      "value": 0.04,
      "scriptPubKey": {
        "internal_pubkey": "
          # funds go to the remote node with the revocation key
          musig2(<revocationpubkey>,<remote_pubkey>)
        ",
        "tapleaf": "
          # or back to us after a relative delay (<to_self_delay>)
          <local_delayedpubkey>
          OP_CHECKSIGVERIFY
          <to_self_delay>
          OP_CHECKSEQUENCEVERIFY
        "
      }
    }
  ]
}

Do note that the changes described in this section only make sense to do if we're able to integrate PTLCs without radically changing the transaction format. We will explore that in the next section.

Point Time-Locked Contracts

Once lightning transactions use taproot, we'd like to add support for PTLCs (Point Time Locked Contracts) in addition to HTLCs (Hash Time Locked Contracts). PTLCs can only be used when the whole route supports them, which means we'll have to keep supporting HTLCs until the majority of the network has been updated.

The main benefit of PTLCs is payment decorrelation: instead of using the same secret for each hop in the route (payment_hash for HTLCs) we can use different secrets for each hop, which provides much better privacy.

We can use scriptless scripts multi-hop locks to allow routing PTLCs across multiple hops.

Conceptually, what we would like to do when offering a PTLC is to add an output with the following structure (or something similar) to our commitment transaction:

{
  "internal_pubkey": "
    # funds go to the remote node with the revocation key
    musig2(<revocationpubkey>,<remote_pubkey>)
  ",
  "tapleaf_1": "
    # funds go back to us via a second-stage PTLC-timeout transaction (which contains an absolute delay)
    # NB: we need the remote signature, which prevents us from unilaterally changing the PTLC-timeout transaction
    <remote_ptlcpubkey> OP_CHECKSIGVERIFY <local_ptlcpubkey> OP_CHECKSIGVERIFY
    1 OP_CHECKSEQUENCEVERIFY
  ",
  "tapleaf_2": "
    # funds go to the remote node via a second-stage Claim-PTLC-success transaction by completing an adaptor sig, revealing the payment secret
    # NB: we don't use musig2 here because it would force local and remote signatures to use the same sighash flags
    <local_ptlcpubkey> OP_CHECKSIGVERIFY <remote_ptlcpubkey> OP_CHECKSIGVERIFY
    1 OP_CHECKSEQUENCEVERIFY
  "
}

But this introduces a fundamental change compared to HTLCs: the receiver cannot directly claim the PTLC from our commitment transaction once they have the secret. Instead, we need to introduce a second-stage transaction that must be pre-signed.

First of all, let's explain why this fundamental change is necessary.

With HTLCs, the secret (payment preimage) could be revealed by the receiver in the spending script of the HTLC output. Once the HTLC was spent, the sender could simply look at the signature script to learn the secret and propagate it upstream.

With PTLCs, the secret is a private key. The way it is revealed is a two steps process:

  1. the sender provides an adaptor signature based on the payment point
  2. the receiver completes the adaptor signature (using the payment secret) to spend the output

With both the adaptor signature and the complete signature, the sender can extract the secret. We can see a few problems emerge from that two steps process:

  • more signatures need to be exchanged, because each peer needs to send signatures for 2nd-stage transactions that let the remote peer claim PTLCs from the local commitment
  • claiming successful PTLCs from the remote peer's commitment now requires using RBF and sighash flags similar to anchor outputs HTLC transactions (sighash_single | sighash_anyonecanpay trick)
  • before signing a commitment update, peers must obtain from their counterparty adaptor signatures for their pending received PTLCs in the future remote commitment

Let's now detail a strawman, high-level proposal that enables PTLCs.

First of all, have a look at the existing transaction structure. We simply add two new types of outputs to the commit tx (PTLC offered / PTLC received):

+------------+
| funding tx |
+------------+
      |
      |        +------------------------+
      +------->|      commit tx B       |
               +------------------------+
                 |  |  |  |  |  |  |  |
                 |  |  |  |  |  |  |  | A's main output
                 |  |  |  |  |  |  |  +-----------------> to A after a 1-block relative delay
                 |  |  |  |  |  |  |
                 |  |  |  |  |  |  |                 +---> to B after relative delay
                 |  |  |  |  |  |  | B's main output |
                 |  |  |  |  |  |  +-----------------+
                 |  |  |  |  |  |                    |
                 |  |  |  |  |  |                    +---> to A with revocation key
                 |  |  |  |  |  |
                 |  |  |  |  |  | A's anchor output
                 |  |  |  |  |  +--------------------> to A immediately (or anyone after 16-block relative delay)
                 |  |  |  |  |
                 |  |  |  |  | B's anchor output
                 |  |  |  |  +-----------------------> to B immediately (or anyone after 16-block relative delay)
                 |  |  |  |
                 |  |  |  |     (B's RBF inputs) ---+
                 |  |  |  |                         |                                    +---> to B after relative delay
                 |  |  |  |                         +---->+-----------------+            |
                 |  |  |  |                   +---------->| HTLC-timeout tx |------------+
                 |  |  |  | HTLC offered by B |           +-----------------+            |
                 |  |  |  +-------------------+      (after timeout + 1-block delay)     +---> to A with revocation key
                 |  |  |                      |
                 |  |  |                      +---> to A with payment preimage after a 1-block relative delay
                 |  |  |                      |
                 |  |  |                      +---> to A with revocation key
                 |  |  |
                 |  |  |        (B's RBF inputs) ---+
                 |  |  |                            |                                        +---> to B after relative delay
                 |  |  |                            +---->+-----------------+                |
                 |  |  |                    +------------>| HTLC-success tx |----------------+
                 |  |  | HTLC received by B |             +-----------------+                |
                 |  |  +--------------------+     (with payment preimage + 1-block delay)    +---> to A with revocation key
                 |  |                       |
                 |  |                       +---> to A after timeout (absolute delay + 1-block relative delay)
                 |  |                       |
                 |  |                       +---> to A with revocation key
                 |  |
                 |  |       (B's RBF inputs) ---+
                 |  |                           |                                     +---> to B after relative delay
                 |  |                           +---->+-----------------+             |
                 |  |                   +------------>| PTLC-timeout tx |-------------+
                 |  | PTLC offered by B |             +-----------------+             |
                 |  +-------------------+        (after timeout + 1-block delay)      +---> to A with revocation key
                 |                      |
                 |                      | (A's RBF inputs) ---+
                 |                      |                     |
                 |                      |                     +---->+-----------------------+
                 |                      +-------------------------->| Claim-PTLC-success tx |--------------> to A
                 |                      |                           +-----------------------+
                 |                      |                     (with payment secret + 1-block delay)
                 |                      |
                 |                      +---> to A with revocation key
                 |
                 |      (B's RBF inputs) ---+
                 |                          |                                        +---> to B after relative delay
                 |                          +---->+-----------------+                |
                 |                    +---------->| PTLC-success tx |----------------+
                 | PTLC received by B |           +-----------------+                |
                 +--------------------+    (with payment secret + 1-block delay)     +---> to A with revocation key
                                      |
                                      +---> to A after timeout (absolute delay + 1-block relative delay)
                                      |
                                      +---> to A with revocation key

You should notice that the two PTLC outputs are very similar to the HTLC ones. The only difference is the introduction of the claim-ptlc-success transaction. This claim-ptlc-success transaction directly pays to the remote peer (no delay, no revocation).

The current protocol for updating commitments is:

 Alice                      Bob
   |    commitment_signed    |
   |------------------------>|
   |     revoke_and_ack      |
   |<------------------------|
   |    commitment_signed    |
   |<------------------------|
   |     revoke_and_ack      |
   |------------------------>|

Alice -> Bob: commitment_signed
    channel id
    signature for Bob to spend funding tx
    sigs for Bob to spend HTLCs from his next commitment

Bob -> Alice: revoke_and_ack
    channel id
    reveal previous commitment secret
    next commitment point

Bob -> Alice: commitment_signed
    channel id
    signature for Alice to spend funding tx
    sigs for Alice to spend HTLCs from her next commitment

Alice -> Bob: revoke_and_ack
    channel id
    reveal previous commitment secret
    next commitment point

The main difficulty introduced by the claim-ptlc-success transaction is that Alice needs to obtain adaptor signatures from Bob before she can send her commitment_signed. Let's detail why.

Let's assume that there is currently a pending PTLC paying Alice in both commitments. What happens if Alice sends commitment_signed to Bob?

Bob now has a new version of his commitment transaction, that he can broadcast. But Alice is unable to spend her PTLC output from this transaction, because she doesn't have Bob's signature for the new corresponding claim-ptlc-success (even if she obtains the payment secret).

This can be fixed by changing the protocol:

 Alice                      Bob
   |   commitment_proposed   |
   |------------------------>|
   |   commitment_proposed   |
   |<------------------------|
   |    commitment_signed    |
   |<------------------------|
   |     revoke_and_ack      |
   |------------------------>|
   |    commitment_signed    |
   |------------------------>|
   |     revoke_and_ack      |
   |<------------------------|

Alice -> Bob: commitment_proposed
    channel id
    adaptor sigs for PTLCs to Bob in Alice's next commitment (claim-ptlc-success)
    musig nonces for Alice's signature on Alice's next commitment

Bob -> Alice: commitment_proposed
    channel id
    adaptor sigs for PTLCs to Alice in Bob's next commitment (claim-ptlc-success)
    musig nonces for Bob's signature on Bob's next commitment

Bob -> Alice: commitment_signed
    channel id
    musig nonces for Bob's signature on Alice's next commitment
    Bob's signature on Alice's next commitment
    sigs for Alice to spend HTLCs and PTLCs from her next commitment

Alice -> Bob: revoke_and_ack
    channel id
    reveal previous commitment secret
    next commitment point

Alice -> Bob: commitment_signed
    channel id
    musig nonces for Alice's signature on Bob's next commitment
    Alice's signature on Bob's next commitment
    sigs for Bob to spend HTLCs and PTLCs from his next commitment

Bob -> Alice: revoke_and_ack
    channel id
    reveal previous commitment secret
    next commitment point

The commitment_signed and revoke_and_ack are mostly unchanged, the only difference is that the ptlc signatures in commitment_signed are now adaptor signatures for PTLC-success transactions.

This change adds half a round-trip compared to the previous protocol, and changes which peer signs a new commitment first (with the previous protocol, Alice was signing first, but now it's Bob who signs first to save half another round-trip).

Further changes

We could wait for Eltoo before doing any kind of change that fundamentally updates the transaction structure and update protocol. However Eltoo requires a bitcoin soft-fork, so there is no guarantee that it will ever be possible.

Alternatively, ajtowns proposed a new transaction format design on the mailing list in his lightning over taproot with PTLCs post.

This proposal deserves its own article, which will be written once we have a first set of taproot updates deployed on the network.

Resources