In my last blog post, I promised a follow-up post on the workings of Taproot channels. However, when I started working on it, I realised that it might be a good idea to first dedicate a post to recap the preliminaries that will be required in order to understand the follow-up Taproot channel articles. So here you go!
Overview
This post will cover some basics around Taproot outputs and how to spend them via either the key or script paths. It will also cover the MuSig2 flow between two signing parties. Note that I won’t go into extreme detail for either of these topics. Instead, this post is aimed at refreshing your memory on these topics or giving you enough of an understanding of how Taproot outputs and MuSig2 work so that the follow-up articles are more easily digestible. There are better articles out there for you if you want to get into the nitty-gritty of these topics and of course you can always go check out the BIPs if you are brave: Schnorr signatures, Taproot, Tapscript and MuSig2.
Ok, enough chit-chat. Onto the good stuff!
A quick note on BIP340 public keys
Public keys will mostly be encoded as 32-byte arrays instead of the usual
33-byte compressed public key representation that you might be used to. If the
secp256k1 curve is plotted over a non-finite field (as shown below) then you can
see that for every x-coordinate, there are two possible y-coordinates. Since the
curve is actually over a finite field with an odd order, one y-coordinate for a
given x-coordinate will always be even and the other one will be odd. The
assumption for 32-byte encoded public keys is that the y-coordinate is always
the even one. This means that if you want to create a valid BIP340 signature but
your private key, d
, produces a public key, P
, with an odd y-coordinate,
then all you need to do is negate your private key. This will produce public
key P’
which has the same x-coordinate as your original public key but with an
even y-coordinate. For more information regarding BIP340 public keys and
signatures (also known as Schnorr signatures), check out the BIP
itself.
Taproot Outputs
A Taproot output shows up in the scriptPubKey
section of a transaction (just
like all other outputs) and has the following form:
The OP_1
indicates that this is a SegWit Version 1 output (a.k.a. Taproot
output) and what follows are 32 bytes that represent the output key (see
BIP340 public keys above). I will often
use Q
to refer to this key. To give the full picture, here is a Taproot
output in a transaction:
Ok cool. But what exactly is in this output? Is it just like a P2PK output? No. That would be lame. The truth is that this simple-looking output could be a huge variety of different things. It could be a simple single public key (yes, like a P2PK). It could be an n-of-n MuSig2 public key. It could also have a bunch of script branch options, or it could even be a combination of all the above! Let’s break these options down a bit.
Single Key or n-of-n MuSig2 outputs
If you wanted to just create an output that sends to a single public key, P
,
then this is easy to do. In this case, your output key, Q
, just becomes your
key, P
, which is often called the internal key.
To spend this output, all you need is to provide a BIP340 signature in the
witness which you would calculate using the private key, d
, used to derive
P
. See the note explained in the BIP340 Public
Keys section about possibly needing to
negate your private key first.
Now, what if you instead wanted to use an n-of-n aggregate MuSig2 public key? Turns out that this will look exactly the same on-chain as for the single key case explained above! All that changes is the steps that you and your fellow signers need to take to set up the aggregate public key and then to calculate the final signature. But once all that is complete, what ends up on-chain looks no different.
Script Paths
Here is where the magic really happens. You can also have the option of spending your Taproot output via a script and each output can have multiple scripts from which it can be spent. Another cool thing is that if you choose to include script paths in your Taproot output, you can still add a regular key path like before. Let’s say, for example, that you want to be able to spend your output at any time, but you also want to add three script paths so that it can also be spent in other scenarios: perhaps after 30 days you want your partner to be able to spend the output. That would be one script path. If you also have two other script paths (perhaps one is a 2-of-3 multi-sig and the other requires a pre-image reveal), then your Taproot output would be constructed as follows:
Let’s walk through the above diagram a bit:
First, the three scripts (Script A
, Script B
and Script C
) are all put
into a Merkle tree as shown bottom right. The root of this Merkle tree
is then hashed along with our internal key, P
, to get the 32-byte tweak, t
.
This tweak is converted to its elliptic curve point form by multiplying it with
the generator point, G
, to get T
which is then added to our internal key,
P
, to get the final output key, Q
. I have skipped over some things here such
as the details of the script encodings and also how the scripts are hashed in
the Merkle tree so check out the relevant BIPs if you are interested.
Alrighty - our fancy Taproot output has been set up! But now… how do we spend
it? There are two ways of spending this transaction: the first is via the
internal key, P
. We call this a key path spend. The
other way is via one of the scripts in the tree. This type of spend is called a
script path spend.
Key Path Spends
Spending via the key path is very simple and is similar to spending the output
if it was just a normal un-tweaked key as described earlier
on. The only difference is that you will
need to tweak your private key, d
, with the tweak, t
. In other words, your
new private key becomes d + t
which is what you will use to calculate your
signature. That’s it! If you spend the output via the key path, there is no
need to reveal any of the scripts and so anyone looking at the spend on-chain
will have no clue that the output even had the potential to be spent via a
script path.
Script Path Spends
To spend via one of the script paths requires a slightly more complicated
witness. Let’s say we want to spend via Script B
. We will need to do a few
things to convince a verifier that we have the right to spend the output:
- We must provide a valid spending script for
Script B
. - We need to prove that
Script B
is actually embedded in the output.
Step 1 is pretty simple: just provide the valid witness script for Script B
along with Script B
itself. Step 2 is slightly more involved. To prove that
Script B
is embedded in Q
, we need to give the verifier all the building
blocks required in order to actually construct Q
. These building blocks are
put in what is called the “control block”. The first thing in the control block
is the internal key, P
. It also contains the Merkle proof that allows the
verifier to compute the Merkle tree root. The witness already includes
Script B
itself (it was required for step 1), so the verifier can compute
hB
(see the diagram above showing the Merkle tree construction) themselves,
and so we just need to provide hA
and hC
. The validator will use these
hashes to calculate the script_root
and then hash this along with the
internal key, P
, in order to arrive at the tweak, t
. The validator can then
compute the corresponding tweak point, T
, add that to the internal key, P
,
to get the output key, Q
. The final thing that the control block must include
is a bit indicating if the final Q
point has an odd or even y-coordinate so
that the validator can check if the Q
they computed does have the correct
y-coordinate.
It is important to note that no knowledge of d
(the private key for the
internal key, P
) was required for spending via Script B
(assuming of course
that Script B
itself does not involve P
). Another cool thing is that we did
not need to reveal the contents of the other scripts in the tree, only their
hashes.
BIP86 Tweaks
There is a clever trick that can be used if you would like to create an output
with no script path that allows you to also prove to a third party that there
is no script path. All you do is construct Q
as if you were constructing an
output with script paths but the script root is left empty.
Spending this output can now only be done via the key spend path using private
key d + t
. Then, P
can be provided to any third parties who want proof that
there is no script path. They would use P
to compute t
and T
and then
would verify that P + T
is equal to the output key, Q
. More info about this
can be found in BIP86.
MuSig2
With the Taproot soft fork, bitcoin nodes now have an understanding of BIP340
signatures (or Schnorr signatures). The beauty of these signatures is their
linearity: the owner of public key P_1
can create a partial signature,
sig_1
for the message msg
and the owner of public key P_2
can create
sig_2
for the same message. The two parties will then be able to combine their
signatures such that sig
where sig = sig_1 + sig_2
is a valid signature for
the aggregate of their public keys: P = P_1 + P_2
.
This is really cool because it means that instead of needing to create a long
n-of-n multisig script and then needing to pay for the blockchain space to store
each of the n
signatures, only one signature will be needed and no long script
will be required at all. Instead, only a single public key (which is actually an
aggregate public key) needs to appear on-chain.
The tricky part here is everything that needs to happen off-chain during the setup of this aggregate public key as well as for the creation of the final signature. MuSig2 is the protocol that defines how this should be done. The various steps have been carefully thought through in order to keep the process trust-less and to protect parties from attacks such as key cancellation.
BIP327 defines the MuSig2 protocol along with a bunch of algorithms that should be used for the various steps of the process. Since the aim of this article is to provide all the building blocks required for understanding Lightning Taproot channels, I will only talk about MuSig2 at an API level using the defined algorithms and will focus more on how it will be used in Lightning. If you would like to dig into it more you can check out the BIP itself. I have also implemented all the MuSig2 methods from scratch here if you are the type of person who prefers looking at code.
MuSig2 vs n-of-n Multisig
An important thing to keep in mind is that with n-of-n multisig outputs, parties
can generate their signatures completely independently of the other parties. As
long as they have the message to be signed along with their private key, they
can create a signature. This signature can then safely be distributed to the
other parties and eventually the transaction witness will have all n
signatures. In other words: no interaction is required between parties at
signing time. With n-of-n MuSig2 this is not the case since there is one
public key on-chain and thus one signature needs to be produced. The n
parties
have to interact with each other in order to produce this final signature.
Explanation by example
Let’s walk through the case where two parties, Alice and Bob, want to set up a 2-of-2 MuSig2 output and then create a signature to spend from it.
This first diagram shows the initial state: Alice and Bob both have private keys and the corresponding public keys and currently the two parties have no shared knowledge.
When the two parties decide to construct an output together, they will first
need to exchange public keys. Both parties will then use the MuSig2 KeySort
algorithm to sort the keys and then the KeyAgg
algorithm to aggregate the
keys. This will produce the aggregate key, P_agg
, which would be the key that
would appear in the transaction output.
Once Alice and Bob have a shared message that they want to sign (which would most likely be the spending transaction), they can move onto the signing phase.
Step one of the signing phase involves each party generating nonces. They will
each generate a secret nonce, called the secnonce
, and from the secnonce
the
associated public nonce, called the pubnonce
, can be determined. Note that
each secnonce
is actually made up of two private keys and each pubnonce
is
made up of the two public keys associated with those private keys. The details
of why there are two nonces is outside the scope of this post. Alice and Bob
will then need to exchange the public nonces and then both parties will use the
MuSig2 NonceAgg
function to determine the aggregate nonce: aggnonce
. Note
that this step had nothing to do with the message to be signed meaning that this
step can actually take place before the message to be signed is known.
When the public nonces have been exchanged and both parties know the message to
be signed then each party can use the MuSig2 Sign
function to produce partial
signatures.
The final step is for the parties to exchange their partial signatures. Each
party can then use the MuSig2 PartialSigAgg
function to calculate the final
signature. This signature will be a valid signature for P_agg
over the
message, msg
.
It is important to become familiar with the above MuSig2 flow because it will be used very often in the next few articles. In Taproot channels the funding output of a channel will be a MuSig2 aggregate public key. This means that every commitment transaction created that spends from the funding output will need to go through this signing flow. Since channel states in Lightning are asymmetric, this also means that this flow will need to happen twice per state update: once to sign the local commitment transaction and once to sign the remote one. But more on that in the next blog post :)
Thanks for reading! I hope that was useful. If you think there is anything that could use clarification or that is incorrect then please don’t hesitate to reach out to let me know.