bitcoin-s/docs/crypto/musig.md
Nadav Kohen ae0962d7ed
Musig2 Implementation (#4418)
* An initial (not yet working) implementation with test

* Added custom (non-bip-340) verification for now

* Made KeySet a case class

* Got MuSig2 working with BIP340 verification passing

* Responds to Ben's review

* Fixed hash tags and added parital signature verification

* Added point multiplication that allows infinity and did some refactoring

* Refactored type defs into case classes

* Added tests for more signers and fixed single-party bug

* Added key aggregation test vectors from BIP

* Added nonce generation test vectors from BIP

* Added nonce aggregation test vectors from BIP

* Made nonce aggregation test vectors pass by having MultiNoncePub wrap SecpPoints

* Added remaining static test vectors from BIP

* Implements tweaking support and adds tests, including all of the remaining BIP tests

* Added factory objects for nonce types

* Refactored things into multiple files with renaming and restructuring

* Some minor renaming

* Introduced ParityMultiplier ADT to remove unneccesary computations

* Added scaladocs

* Added messages to invariants

* Fixed a typo

* Nonce generation now takes a SchnorrPublicKey instead of raw bytes

* Made point multiplication more robust

* Responded to Ben nits

* Added musig.md
2022-07-06 12:59:13 -05:00

4.5 KiB

id title
musig MuSig

Bitcoin-S now has support for MuSig.

This module contains classes representing public KeySets, MuSig nonces, and MuSig aggregate key tweaks, as well as utility functions for all MuSig computations.

The functions for aggregating key data are:

  • aggPubKey
    • This is a member of KeySet and returns the aggregate public key for this set of signers, including the tweaks provided. In most uses, a subsequent call to schnorrPublicKey is required for Bitcoin applications.
  • MuSigNoncePub.aggregate
    • Given a Vector[MuSigNoncePub] of the signer's nonces, returns the aggregate MuSigNoncePub. This aggregation can be done before the message, or even the KeySet is known.

The functions for signing and verification are:

  • MuSigUtil.sign
    • This function generates a MuSig partial signature using a private key and MuSigNoncePriv. This consists of a pair (R, s) where R is the aggregate nonce key (same for all signers) and s is the actual partial signature that needs to be shared.
  • MuSigUtil.partialSigVerify
    • This function validates a single partial signature against that signer's public key and MuSigNoncePub.
  • MuSigUtil.signAgg
    • This function aggregates all of the s values into a single valid SchnorrDigitalSignature (using the aggregate nonce key R).

Note that no new function is required for aggregate verification as SchnorrPublicKey's verify function is to be used.

Lastly, it should be mentioned that MuSigNoncePrivs must be constructed using either MuSigNoncePriv.gen or MuSigNoncePriv.genInternal (the latter should only be used with 32 bytes of secure random entropy). These generation functions take as input any context information that is available at nonce generation time, namely your signing key, aggregate public key, message, and any extra bytes you may have available. Including these optional inputs improves the security of nonce generation (which must be absolutely secure).

The following code shows a two-party MuSig execution:

import org.bitcoins.crypto.ECPrivateKey
import org.bitcoins.crypto.musig._

val alicePrivKey = ECPrivateKey.freshPrivateKey
val alicePubKey = alicePrivKey.schnorrPublicKey
val bobPrivKey = ECPrivateKey.freshPrivateKey
val bobPubKey = bobPrivKey.schnorrPublicKey
val msg = scodec.bits.ByteVector.fill(32)(7)
val tweaks = Vector.empty[MuSigTweak]
// Alice and Bob generate and exchange nonce data (new nonces for every sig)
val aliceNoncePriv = MuSigNoncePriv.gen()
val aliceNonce = aliceNoncePriv.toPublicNonces // Alice sends this to Bob
val bobNoncePriv = MuSigNoncePriv.gen()
val bobNonce = bobNoncePriv.toPublicNonces // Bob sends this to Alice

// The aggregate musig nonce can be computed from Alice's and Bob's
val aggMuSigNonce = MuSigNoncePub.aggregate(Vector(aliceNonce, bobNonce))

// Any party can (non-interactively) compute the aggregate public key
val pubKeys = Vector(alicePubKey, bobPubKey)
val keySet = KeySet(pubKeys, tweaks) // This is where you put MuSigTweaks
val aggPubKey = keySet.aggPubKey.schnorrPublicKey

// Alice generates a partial signature for the message
val (aliceR, aliceSig) = 
    MuSigUtil.sign(aliceNoncePriv, aggMuSigNonce, alicePrivKey, msg, keySet)
// Bob generates a partial signature for the message
val (bobR, bobSig) =
    MuSigUtil.sign(bobNoncePriv, aggMuSigNonce, bobPrivKey, msg, keySet)
require(aliceR == bobR)
val R = aliceR

// Alice and Bob exchange and verify each other's sigs (s values)
require(
        MuSigUtil.partialSigVerify(aliceSig,
                                   aliceNonce,
                                   aggMuSigNonce,
                                   alicePubKey,
                                   keySet,
                                   msg))
require(
        MuSigUtil.partialSigVerify(bobSig,
                                   bobNonce,
                                   aggMuSigNonce,
                                   bobPubKey,
                                   keySet,
                                   msg))

// In the case that the aggregator is not Alice or Bob, R can be computed as follows
val R2 = SigningSession(aggMuSigNonce, keySet, msg).aggNonce
require(R2 == R)

// Finally, any party can aggregate the partial signatures
val sig = MuSigUtil.signAgg(Vector(aliceSig, bobSig), R)

// This signature can now be validated as a normal BIP340 Schnorr signature
require(aggPubKey.verify(msg, sig))