mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
118 lines
4.8 KiB
Markdown
118 lines
4.8 KiB
Markdown
|
# MuSig2
|
||
|
|
||
|
With `lnd v0.15.0-beta` a new, experimental MuSig2 API was added to the
|
||
|
`signrpc` subserver RPC. With MuSig2 it is possible to combine the public keys
|
||
|
of multiple signing participants into a single, combined key. The signers can
|
||
|
then later come together and cooperatively produce a single signature that is
|
||
|
valid for that combined public key. MuSig2 therefore is an interactive `n-of-n`
|
||
|
signature scheme that produces a final/complete Schnorr signature out of `n`
|
||
|
partial signatures.
|
||
|
|
||
|
**NOTE**: At the time the MuSig2 code in `btcd`/`lnd` was written, there was no
|
||
|
"official" MuSig2 support merged to either `bitcoind` or `secp256k1`. Therefore,
|
||
|
some smaller details in the signing protocol could change in the future that
|
||
|
might not be backward compatible. So this API must be seen as highly
|
||
|
experimental and backward compatibility can't be guaranteed at this point.
|
||
|
|
||
|
## References
|
||
|
* [MuSig2 paper](https://eprint.iacr.org/2020/1261.pdf)
|
||
|
* [Draft MuSig
|
||
|
BIP](https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki)
|
||
|
* [MuSig2 implementation discussion in
|
||
|
`bitcoind`](https://github.com/bitcoin/bitcoin/issues/23326)
|
||
|
|
||
|
## A note on security
|
||
|
|
||
|
The MuSig2 signing protocol is secure from leaking private key information of
|
||
|
the signers **as long as the same secret nonce is never used multiple times**.
|
||
|
If the same nonce is used for signing multiple times then the private key can
|
||
|
leak. To avoid this security risk, the `signrpc.MuSig2Sign` RPC can only be
|
||
|
called a single time for the same session ID. This has the implication that if a
|
||
|
signing session fails or is aborted (for example because a participant isn't
|
||
|
responsive or the message changes after some participants have already started
|
||
|
signing), a completely new signing session needs to be initialized, which
|
||
|
internally creates fresh nonces.
|
||
|
|
||
|
## Examples
|
||
|
|
||
|
An API is sometimes easiest explained by showing concrete usage examples. Here
|
||
|
we take a look at the MuSig2 integration tests in `lnd`, since they both serve
|
||
|
to test the RPCs and to showcase the different use cases.
|
||
|
|
||
|
### 3-of-3 Taproot key spend path (BIP-0086)
|
||
|
|
||
|
See `testTaprootMuSig2KeySpendBip86` in
|
||
|
[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see
|
||
|
the full code.
|
||
|
|
||
|
This example uses combines the public keys of 3 participants into a shared
|
||
|
MuSig2 public key that is then tweaked with the
|
||
|
[BIP-0086](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki#address-derivation)
|
||
|
TapTweak to be turned into a Taproot public key that can be used directly as the
|
||
|
`pkScript` of a p2tr output on chain.
|
||
|
|
||
|
The most notable parameter for this to work is the `TaprootTweak` parameter in
|
||
|
the `MuSig2CreateSession` RPC call:
|
||
|
|
||
|
```go
|
||
|
taprootTweak := &signrpc.TaprootTweakDesc{
|
||
|
KeySpendOnly: true,
|
||
|
}
|
||
|
|
||
|
sessResp1, err := node.SignerClient.MuSig2CreateSession(
|
||
|
ctx, &signrpc.MuSig2SessionRequest{
|
||
|
KeyLoc: keyDesc1.KeyLoc,
|
||
|
AllSignerPubkeys: allPubKeys,
|
||
|
TaprootTweak: taprootTweak,
|
||
|
},
|
||
|
)
|
||
|
```
|
||
|
|
||
|
### 3-of-3 Taproot key spend path with root hash commitment
|
||
|
|
||
|
See `testTaprootMuSig2KeySpendRootHash` in
|
||
|
[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see
|
||
|
the full code.
|
||
|
|
||
|
This is very similar to the above example but with the main difference that the
|
||
|
p2tr output on chain not only commits to a key spend path but also a script
|
||
|
path. The MuSig2 combined key becomes the Taproot internal key and the TapTweak
|
||
|
commits to both the internal key and the Taproot script merkle root hash.
|
||
|
|
||
|
The most notable parameter for this to work is the `TaprootTweak` parameter in
|
||
|
the `MuSig2CreateSession` RPC call:
|
||
|
|
||
|
```go
|
||
|
taprootTweak := &signrpc.TaprootTweakDesc{
|
||
|
ScriptRoot: rootHash[:],
|
||
|
}
|
||
|
|
||
|
sessResp1, err := node.SignerClient.MuSig2CreateSession(
|
||
|
ctx, &signrpc.MuSig2SessionRequest{
|
||
|
KeyLoc: keyDesc1.KeyLoc,
|
||
|
AllSignerPubkeys: allPubKeys,
|
||
|
TaprootTweak: taprootTweak,
|
||
|
},
|
||
|
)
|
||
|
```
|
||
|
|
||
|
### 3-of-3 `OP_CHECKSIG` in Taproot script spend path
|
||
|
|
||
|
See `testTaprootMuSig2CombinedLeafKeySpend` in
|
||
|
[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see
|
||
|
the full code.
|
||
|
|
||
|
This example is definitely the most involved one. To be able to use a MuSig2
|
||
|
combined key and then spend it through a Taproot script spend with an
|
||
|
`OP_CHECKSIG` script, the following steps need to be performed:
|
||
|
|
||
|
1. Derive signing keys on signers, combine them through `MuSig2CombineKeys`.
|
||
|
2. Create a Taproot script tree with a script leaf `<combinedKey> OP_CHECKSIG`.
|
||
|
3. Create the Taproot key by committing to the script tree root hash.
|
||
|
4. When spending the output, the message being signed needs to be the sighash of
|
||
|
a Taproot script spend that also commits to the leaf hash.
|
||
|
5. The final witness stack needs to contain the combined signature, the leaf
|
||
|
script and the control block (which contains the internal public key and the
|
||
|
inclusion proof if there were any other script leaves).
|
||
|
|