Hold on to your hats folks, things are about to get real.
Overview
In this post, I will dive into the structure of Taproot channel funding and commitment transactions. If you missed my previous post about Taproot and MuSig2, it might be a good idea to read that one first for a recap of the building blocks that will be used throughout this post. If you perhaps also need a re-cap of the general structure of commitment transactions then check out this post where I cover why each output in a commitment transaction looks the way it does.
Note that Taproot Channels are still in the design phase and so until the proposal by the one and only Roasbeef is merged, this blog post will be a living document that I will update if any changes are made to the proposal. This is currently up-to-date as of commit e95e7a.
If you have read some of my previous blog posts, you might have noticed that I love to make use of diagrams. Well this post is diagrams on steroids. To help with understanding, here is a legend showing what each colour generally represents:
Ok, ready? Let’s dive in!
A quick note on NUMS points
A NUMS point, or a nothing-up-my-sleeves point, is a public key that no one knows the private key to and that is derived from a string binding it to the context that it is being used in. The NUMS point used in taproot channels is derived from the string “Lightning Simple Taproot” using this NUMS generation tool. Since no one knows the private key for the NUMS point, it can be used as the internal key for a Taproot output in order to effectively cancel out the possibility of a key-path spend since no one would be able to create a valid signature for it.
Funding Transaction Output
The funding transaction output is the output that defines the opening of the channel. In pre-taproot channels, this output pays into a 2-of-2 multisig meaning that any transaction (commitment or closing transaction) spending from the output must be signed by both channel peers. Since the output is a P2SH, once it is spent, the underlying 2-of-2 multisig is revealed in the witness and so it becomes pretty clear to anyone observing the chain that the transaction was for a Lightning channel.
In the case of Taproot channels, this output is now a 2-of-2 MuSig2. Both parties will still need to sign each transaction that spends from the output, however, the signatures will now be aggregated via the MuSig2 protocol into a single signature. This means that in the ideal case where the channel is closed in a cooperative manner, the channel will look no different from any other P2TR key-path spend. This is a huge privacy improvement for unannounced channels since there is no way to tell that the transaction was for a Lightning channel, and it never gets advertised to the network through the gossip protocol. As for announced Taproot channels, the gossip protocol will actually need to be completely re-designed to support the new channel type and there is currently an ongoing debate around if this new gossip version should advertise the channel’s funding transaction at all or not. More on Taproot channel gossip in a future post.
Here is a diagram showing how a Taproot channel funding output is constructed:
The two parties in a channel, the local and remote peer, use the MuSig2
protocol to aggregate their individual funding keys, P_a
and P_b
, into the
aggregate key, P_agg
, which is also the internal key. This internal key is
then tweaked with a BIP86 tweak. You might recall from the previous post that a
BIP86 tweak gives the channel owners the ability to prove to other nodes on the
network that this funding output does not have a hidden script path.
Spending from the funding transaction
The funding output can only ever be spent via the key path. Since the internal
key is an aggregate key produced via MuSig2, to spend it requires both parties
to produce partial signatures for the message being signed. These signatures are
then aggregated using the MuSig2 PartialSigAgg
function and finally tweaked
with the tweak, T
, to produce the signature that would validly spend the
funding output.
Notice that unlike in pre-taproot channels where spending from the funding output would require two signatures created completely independently of each other, in Taproot channels, the signatures are created in an interactive manner between the peers. I will cover the details of how exactly this affects the interaction between the peers in a future blog post.
Commitment Transaction Outputs
There are six different outputs that a commitment transaction can have. These
are: the to_local
and to_remote
outputs, the local and remote anchor
outputs, the offered htlc output and the accepted htlc output. Let’s dive in.
The to_local
output
The to_local
output is responsible for paying the local peer their channel
balance. The output must be revocable by the remote party at all times and only
after to_self_delay
blocks should the local party be able to spend from the
output. As you can see in the diagram below, both these paths are added as Taproot
leaves in the Taproot tree and a public NUMS point is used as the internal key
which effectively cancels out the key-spend path. You might be asking yourself
why the revocation pub key is not used as the internal key and this is a good
question since that is in fact how the output was in the original design. But
you can see from the diagram below that the revocation script does not only
contain the revocation public key but also strangely contains the
P_local_delay
key. Notice also that the key is not followed by OP_CHECKSIG
but rather just by an OP_DROP
which means that a signature is not required for
the key. All that is required is that the key is revealed in the script. This
reveal of P_local_delay
in the revocation path is the only reason why the
revocation public key could not be used as the internal key for the output. The
reason for this design will be made more clear in the section describing the
local anchor output. There is a good reason, I promise
;)
Script path spends
Since the internal key of the output is a public NUMS point, it is only possible to spend this output via a script path.
Revocation path
If this commitment transaction ends up on-chain and is for a state that has already been revoked, then the remote party will be able to sweep the funds via the revocation path. They can do so with the following witness:
It contains a signature for the revocation public key, the revocation script
(which includes a reveal of the P_local_delay
key) and finally it contains a
control block which contains the internal key (the NUMS
key) along with an
inclusion proof for the revocation script.
To-local delay path
If this commitment transaction ends up on-chain as part of an honest force-close
scenario then the remote party will not be able to spend via the revocation
path. In this case, the transaction will be spendable by the local party via the
local delay script path after to_self_delay
blocks have been confirmed. The
following diagram shows the witness that will be required to spend this path:
It contains a witness for the local-delayed script which is a valid signature
from the local party for their key, P_local_delayed
. The script itself must
also be revealed and finally, the control block must be specified. In this case,
the control block only contains the parity bit of to_local
output’s output
key, the internal key (which is the NUMS point) and the inclusion proof for the
to-local delay script.
The to_remote
output
This output pays the remote party their channel balance. As with all anchor channels, any non-anchor outputs must have a CSV of at least one to not break the CPFP carve out rule. Therefore, the remote party is only allowed access to their funds after one confirmation. This type of requirement can only be added in a script and so this output also makes use of the Taproot script tree.
Similarly to the to_local
output, we use the NUMS point for the internal key
here so that a key path spend is not possible.
Script path spend
Once the commitment transaction has one confirmation, the remote peer can spend it via the script path using the following witness:
Remote Anchor Output
This is the output that the remote party will be able to use to CPFP the commitment transaction if required. The remote party’s public key is thus used as the internal key. To ensure that this output (a very small output of only 330 satoshis) is definitely cleaned up at some point from the UTXO set, another output path is added which allows anyone to spend the output after it has been confirmed for 16 blocks. This extra path is added as a script in the script tree.
Key path spend
Spending via the key path just requires a signature from the remote party which they will tweak with the TapTweak.
Script path spend
Once the output has been confirmed for 16 blocks, it becomes fair game. Anyone is allowed to spend from this output as long as they can produce the following spending script:
Notice anything here? If you were a third party trying to sweep some expired
anchor outputs for some free sats, would you be able to produce the above
spending script? The answer is: yes, but only if you know what P_remote
is!
Before reading on, I suggest taking some time to think about how you could
possibly come to know what P_remote
is. It is not present in the funding
transaction, nor is it present in the commitment transaction. Scroll up through
the diagrams to see if you can see where it is revealed.
Ok ready? It is only revealed if the to_remote
output is spent as it appears
in the witness required to spend that output. This means that the remote anchor
is spendable by anyone after 16 blocks only when the to_remote
output has been
spent.
Local Anchor Output
This is the output that you, the local party, will be able to use to CPFP the
commitment transaction. And just like the remote anchor, it is spendable by
anyone after 16 blocks. So the internal key is P_local_delayed
and the “anyone
can spend after 16 blocks” script is put in the script tree.
Key path spend
Spending via the key path just requires a signature from the local party which they will tweak with the TapTweak.
Script path spend
Just like the remote anchor, the parties wanting to spend via the “anyone can
spend” path require the P_local_delayed
public key to first be revealed. This
is revealed when the to_local
output is spent by the local party via the
script path and importantly this is also revealed even if the revocation path
is taken in the to_local
output! This is the whole reason why the internal key
for the to_local
output could not just be the revocation key and why we have
to instead force a script path spend that also reveals the P_local_delayed
key. From the witness below, you can see why knowledge of the P_local_delay
key is required for someone to spend this anchor output via the script path. If
it was not revealed then there is a chance that the output would remain floating
in the UTXO set forever since third parties would not know how to spend it.
Offered HTLC Output
An offered HTLC pays out to the remote party if they reveal the pre-image to a given hash before a certain CLTV timeout. After the timeout, the local party will be able to claim the output via the htlc-timeout transaction (details on that below). If the commitment transaction is a revoked state, then the remote party should be able to sweep the output at any time.
Key path spend
The internal key is set to the revocation key which is spendable only by the remote party and only if the commitment transaction is for a revoked state. If this is the case, then the remote party can sweep the output with the following signature:
Script path spends
The other two spend paths, the success and timeout paths, are placed in the script tree and spending either of them requires providing a valid witness for the script, the script itself and a control block which this time does include an inclusion proof since more than one script is present in the tree.
Success Path
To spend via the success path, the following witness is required.
Timeout Path
The following witness is required to spend via the timeout path. The transaction that will spend the timeout path is the htlc-timeout transaction - more details on that transaction later on. If you need a recap on the reason why second-stage htlc transactions are necessary, check out this post.
Accepted HTLC Output
The accepted HTLC output pays out to an htlc-success transaction if we (the
local party) are able to provide the pre-image for the given payment hash.
Otherwise, after a certain cltv_expiry
, the remote party will be able to sweep
the funds back via the timeout path. If the commitment transaction is a revoked
state, then the remote party should be able to sweep the output at any time.
Key path spend
The internal key is set to the revocation key which is spendable only by the remote party and only if the commitment transaction is for a revoked state. If this is the case, then the remote party can sweep the output with the following signature:
Script path spends
The other two spend paths, the success and timeout paths, are placed in the script tree and spending either of them requires providing a valid witness for the script, the script itself and a control block which this time does include an inclusion proof since more than one script is present in the tree.
Success Path
To spend via the htlc-success transaction, the following witness must be provided:
Timeout Path
To spend via the timout path, the remote party must provide the following witness:
HTLC Timeout and Success Transactions
The htlc-timeout and htlc-success transactions look mostly identical so here is one diagram to describe both:
The first difference between the htlc-success and htlc-timeout transactions is
the input they are spending and hence, the witness required for spending
that input. The htlc-timeout transaction spends the timeout path of the offered
HTLC output and the htlc-success transaction spends the success path of the
accepted HTLC output. The other difference is the Locktime
: the htlc-timeout
transaction has a Locktime
of cltv_expiry
and the htlc-success transaction
has a Locktime
of zero.
The outputs of the htlc-success and htlc-timeout transactions are identical:
they are immediately spendable by the remote party via the revocation path if
the associated commitment transaction is for a revoked state. Otherwise, they
are spendable by the local party after to_self_delay
blocks have been
confirmed.
The key path and script path spend scripts are exactly the same as for
the to_local
output.
Wrap Up
If you have made it all the way through that, congrats! You should now have a pretty solid understanding of the structure of Taproot channel commitment transactions. A future blog post will cover how the various channel peer messages will need to be updated to support MuSig2 signing. I expect it to be a short and sweet one.
As always, if you have any questions, comments or corrections, please feel free to leave a comment down below :)