You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

527 lines
22 KiB

# 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](img/taproot-lightning-txs.jpg)
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](./schnorr.md): 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](#musig2-channel-funding)
* [Taproot Lightning Transactions](#taproot-lightning-transactions)
* [Point Time-Locked Contracts](#point-time-locked-contracts)
* [Further Changes](#further-changes)
* [Resources](#resources)
## 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](https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-October/003278.html).
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](./lightning-txs.md)) 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:
```text
{
"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:
```text
{
"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:
```text
{
"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](https://github.com/ElementsProject/scriptless-scripts/blob/master/md/multi-hop-locks.md)
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:
```text
{
"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](./lightning-txs.md#anchor-outputs).
We simply add two new types of outputs to the commit tx (PTLC offered / PTLC received):
```ascii
+------------+
| 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:
```text
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:
```text
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](https://blockstream.com/eltoo.pdf) 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](https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-October/003278.html).
This proposal deserves its own article, which will be written once we have a first set of taproot
updates deployed on the network.
## Resources
* [BIP 341: Taproot](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)
* [Scriptless Scripts Multi-Hop Locks](https://github.com/ElementsProject/scriptless-scripts/blob/master/md/multi-hop-locks.md)
* [Eltoo](https://blockstream.com/eltoo.pdf)
* [ajtowns lightning-dev post](https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-October/003278.html)
* [t-bast lightning-dev post](https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-December/003377.html)