Repository: lightningnetwork/lnd
Pull request #7171 (2022-11-17 22:41:05)

signrpc: Upgrade to MuSig2 BIP draft v1.0.0rc2, add version flag to RPC
Fixes https://github.com/lightningnetwork/lnd/issues/6974.

This PR upgrades the experimental MuSig2 RPC in the `signrpc` package to the BIP draft version `v1.0.0rc2`.
To remain backward compatible with applications that have on-chain funds on
keys that were created with the previous version of the MuSig2 BIP draft
`v0.4.0` (such as Pool accounts) a version flag was added to the
`MuSig2CombineKeys` and `MuSig2CreateSession` RPC calls. That version flag is
mandatory, which means software using MuSig2 (such as Pool or Loop) must
update in order to use the new versioned RPC and upgrade any on-chain outputs
to the new version.

## Upgrade/compatibility matrix

 - `lnd v0.15.x`
    - client software compatible with `lnd v0.15.x` (e.g. Pool `v0.6.1`): Full compatibility :heavy_check_mark: 
    - client software compatible with `lnd v0.16.x` (e.g. future Pool version): Use `lnd` version to determine what RPC to use :heavy_check_mark: 
 - `lnd v0.16.x`
    - client software compatible with `lnd v0.15.x` (e.g. Pool `v0.6.1`): `lnd` will throw error with hint to upgrade client version :arrow_up_small: 
    - client software compatible with `lnd v0.16.x` (e.g. future Pool version): Use `lnd` version to determine what RPC to use :heavy_check_mark: 

guggero: > Maybe docs/musig2.md could have a hint at that.

Good idea, added the section to the docs.
lightninglabs-deploy: @roasbeef: review reminder
lightninglabs-deploy: @roasbeef: review reminder
lightninglabs-deploy: @guggero, remember to re-request review from reviewers when ready

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 5629e199e..2c5092588 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -2,11 +2,13 @@ run:
   # timeout for analysis
   deadline: 10m
 
-  # Skip autogenerated files for mobile and gRPC.
+  # Skip autogenerated files for mobile and gRPC as well as copied code for
+  # internal use.
   skip-files:
     - "mobile\\/.*generated\\.go"
     - "\\.pb\\.go$"
     - "\\.pb\\.gw\\.go$"
+    - "internal\\/musig2v040"
 
   skip-dirs:
     - channeldb/migration_01_to_11
diff --git a/docs/musig2.md b/docs/musig2.md
index 6a445bbef..e46398832 100644
--- a/docs/musig2.md
+++ b/docs/musig2.md
@@ -13,6 +13,8 @@ partial signatures.
 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.
+See the [versions and compatibility matrix](#versions-and-compatibility-matrix)
+below.
 
 ## References
  * [MuSig2 paper](https://eprint.iacr.org/2020/1261.pdf)
@@ -115,3 +117,44 @@ combined key and then spend it through a Taproot script spend with an
    script and the control block (which contains the internal public key and the
    inclusion proof if there were any other script leaves).
 
+
+# Versions and compatibility matrix
+
+The [MuSig2 BIP
+draft](https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki)
+underwent (and is likely still undergoing) multiple changes and is being
+versioned for that reason. Starting with `lnd v0.16.0-beta` the MuSig2 RPCs will
+offer backward compatibility in order to support applications that might already
+have created MuSig2 based outputs on chain with an earlier version of the
+protocol.
+
+## MuSig2 versions in lnd
+
+* `lnd v0.15.x-beta`: Uses MuSig2 `v0.4.0` exclusively.
+* `lnd v0.16.x-beta`: Supports MuSig2 `v0.4.0` and `v1.0.0rc2` by introducing a
+  new **mandatory version** enum `MuSig2Version` that must be specified for the
+  `MuSig2CombineKeys` and `MuSig2CreateSession` RPCs. A session created with a
+  specific version will use that version during its lifetime (e.g. calls to
+  RPCs that specify the `session_id` field automatically know what version of
+  the MuSig2 API to use under the hood).
+
+## Upgrading client applications
+
+Client software using the MuSig2 API (such as Loop or Pool) will stop working
+with `lnd v0.16.0-beta` if they aren't also updated because the added version
+enum mentioned above is mandatory. In order to prepare for the `lnd 0.16.0-beta`
+release such client software should use one of the following two strategies to
+make sure forward compatibility is ensured:
+ - Compile against `lnd` on `master` branch to pull in updated RPC definitions.
+   Use the `GetVersion` RPC in the `verrpc` package to determine what `lnd`
+   version the application is connected to. Expect MuSig2 `v0.4.0` to be used
+   for `lnd v0.15.x-beta` and expect to explicitly set the `version` field on
+   the `MuSig2CombineKeys` and `MuSig2CreateSession` RPCs for
+   `lnd-v0.16.x-beta`. This is the recommended approach.
+ - Compile against `lnd` on `master` branch to pull in updated RPC definitions.
+   Always set the `version` field on the `MuSig2CombineKeys` and
+   `MuSig2CreateSession` RPCs and check the `version` field in their respective
+   response messages. If the `version` in the response reflects the version
+   sent in the request, you're using `lnd v0.16.x-beta` or later. If the
+   `version` is returned as `MUSIG2_VERSION_UNDEFINED` you're using
+   `lnd v0.15.x-beta` and only `v0.4.0` is supported.
diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md
index 648f3795a..cceca8fa6 100644
--- a/docs/release-notes/release-notes-0.16.0.md
+++ b/docs/release-notes/release-notes-0.16.0.md
@@ -105,6 +105,16 @@ current gossip sync query status.
   `SignOutputRaw` call will now properly work for taproot signatures with a
   non-default sighash.
 
+* [The experimental MuSig2 RPC in the `signrpc` package was upgraded to the BIP
+  draft version `v1.0.0rc2`](https://github.com/lightningnetwork/lnd/pull/7171).
+  To remain backward compatible with applications that have on-chain funds on
+  keys that were created with the previous version of the MuSig2 BIP draft
+  `v0.4.0` (such as Pool accounts) a version flag was added to the
+  `MuSig2CombineKeys` and `MuSig2CreateSession` RPC calls. That version flag is
+  mandatory, which means software using MuSig2 (such as Pool or Loop) must
+  update in order to use the new versioned RPC and upgrade any on-chain outputs
+  to the new version.
+
 ## Wallet
 
 * [Allows Taproot public keys and tap scripts to be imported as watch-only
diff --git a/go.mod b/go.mod
index 066f25bf6..778ce9059 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ require (
 	github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
 	github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344
 	github.com/btcsuite/btcd v0.23.5-0.20230125025938-be056b0a0b2f
-	github.com/btcsuite/btcd/btcec/v2 v2.2.2
+	github.com/btcsuite/btcd/btcec/v2 v2.3.2
 	github.com/btcsuite/btcd/btcutil v1.1.3
 	github.com/btcsuite/btcd/btcutil/psbt v1.1.5
 	github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2
@@ -16,6 +16,7 @@ require (
 	github.com/btcsuite/btcwallet/wtxmgr v1.5.0
 	github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
 	github.com/davecgh/go-spew v1.1.1
+	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
 	github.com/go-errors/errors v1.0.1
 	github.com/golang/protobuf v1.5.2
 	github.com/gorilla/websocket v1.4.2
@@ -76,7 +77,6 @@ require (
 	github.com/coreos/go-systemd/v22 v22.3.2 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
 	github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
-	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
 	github.com/decred/dcrd/lru v1.0.0 // indirect
 	github.com/dsnet/compress v0.0.1 // indirect
 	github.com/dustin/go-humanize v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index c4ff6bd96..4d1654116 100644
--- a/go.sum
+++ b/go.sum
@@ -76,8 +76,9 @@ github.com/btcsuite/btcd v0.23.5-0.20230125025938-be056b0a0b2f/go.mod h1:0QJIIN1
 github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
 github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
 github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
-github.com/btcsuite/btcd/btcec/v2 v2.2.2 h1:5uxe5YjoCq+JeOpg0gZSNHuFgeogrocBYxvg6w9sAgc=
 github.com/btcsuite/btcd/btcec/v2 v2.2.2/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8=
+github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
+github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
 github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
 github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
 github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34=
@@ -405,8 +406,6 @@ github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display h1:RZJ8H4ueU/aQ
 github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display/go.mod h1:2oKOBU042GKFHrdbgGiKax4xVrFiZu51lhacUZQ9MnE=
 github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 h1:Wm0g70gkcAu2pGpNZwfWPSVOY21j8IyYsNewwK4OkT4=
 github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1/go.mod h1:7dDx73ApjEZA0kcknI799m2O5kkpfg4/gr7N092ojNo=
-github.com/lightningnetwork/lnd/cert v1.1.1 h1:Nsav0RlIDRbOnzz2Yu69SQlK939IKya3Q2S0mDviIN8=
-github.com/lightningnetwork/lnd/cert v1.1.1/go.mod h1:1P46svkkd73oSoeI4zjkVKgZNwGq8bkGuPR8z+5vQUs=
 github.com/lightningnetwork/lnd/cert v1.2.0 h1:IWfjHNMI5JgQZU5fdvDptF3DkVI38f4jO/s3tYgWFbE=
 github.com/lightningnetwork/lnd/cert v1.2.0/go.mod h1:04JhIEodoR6usBN5+XBRtLEEmEHsclLi0tEyxZQNP+w=
 github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
diff --git a/input/musig2.go b/input/musig2.go
index 2fc689d77..1ae73d8ce 100644
--- a/input/musig2.go
+++ b/input/musig2.go
@@ -8,9 +8,26 @@ import (
 	"github.com/btcsuite/btcd/btcec/v2"
 	"github.com/btcsuite/btcd/btcec/v2/schnorr"
 	"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
+	"github.com/lightningnetwork/lnd/internal/musig2v040"
 	"github.com/lightningnetwork/lnd/keychain"
 )
 
+// MuSig2Version is a type that defines the different versions of the MuSig2
+// as defined in the BIP draft:
+// (https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki)
+type MuSig2Version uint8
+
+const (
+	// MuSig2Version040 is version 0.4.0 of the MuSig2 BIP draft. This will
+	// use the lnd internal/musig2v040 package.
+	MuSig2Version040 MuSig2Version = 0
+
+	// MuSig2Version100RC2 is version 1.0.0rc2 of the MuSig2 BIP draft. This
+	// uses the github.com/btcsuite/btcd/btcec/v2/schnorr/musig2 package
+	// at git tag `btcec/v2.3.1`.
+	MuSig2Version100RC2 MuSig2Version = 1
+)
+
 const (
 	// MuSig2PartialSigSize is the size of a MuSig2 partial signature.
 	// Because a partial signature is just the s value, this corresponds to
@@ -31,9 +48,9 @@ type MuSig2Signer interface {
 	// public key of the local signing key. If nonces of other parties are
 	// already known, they can be submitted as well to reduce the number of
 	// method calls necessary later on.
-	MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey,
-		*MuSig2Tweaks, [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo,
-		error)
+	MuSig2CreateSession(MuSig2Version, keychain.KeyLocator,
+		[]*btcec.PublicKey, *MuSig2Tweaks,
+		[][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, error)
 
 	// MuSig2RegisterNonces registers one or more public nonces of other
 	// signing participants for a session identified by its ID. This method
@@ -63,6 +80,53 @@ type MuSig2Signer interface {
 	MuSig2Cleanup(MuSig2SessionID) error
 }
 
+// MuSig2Context is an interface that is an abstraction over the MuSig2 signing
+// context. This interface does not contain all of the methods the underlying
+// implementations have because those use package specific types which cannot
+// easily be made compatible. Those calls (such as NewSession) are implemented
+// in this package instead and do the necessary type switch (see
+// MuSig2CreateContext).
+type MuSig2Context interface {
+	// SigningKeys returns the set of keys used for signing.
+	SigningKeys() []*btcec.PublicKey
+
+	// CombinedKey returns the combined public key that will be used to
+	// generate multi-signatures  against.
+	CombinedKey() (*btcec.PublicKey, error)
+
+	// TaprootInternalKey returns the internal taproot key, which is the
+	// aggregated key _before_ the tweak is applied. If a taproot tweak was
+	// specified, then CombinedKey() will return the fully tweaked output
+	// key, with this method returning the internal key. If a taproot tweak
+	// wasn't specified, then this method will return an error.
+	TaprootInternalKey() (*btcec.PublicKey, error)
+}
+
+// MuSig2Session is an interface that is an abstraction over the MuSig2 signing
+// session. This interface does not contain all of the methods the underlying
+// implementations have because those use package specific types which cannot
+// easily be made compatible. Those calls (such as CombineSig or Sign) are
+// implemented in this package instead and do the necessary type switch (see
+// MuSig2CombineSig or MuSig2Sign).
+type MuSig2Session interface {
+	// FinalSig returns the final combined multi-signature, if present.
+	FinalSig() *schnorr.Signature
+
+	// PublicNonce returns the public nonce for a signer. This should be
+	// sent to other parties before signing begins, so they can compute the
+	// aggregated public nonce.
+	PublicNonce() [musig2.PubNonceSize]byte
+
+	// NumRegisteredNonces returns the total number of nonces that have been
+	// registered so far.
+	NumRegisteredNonces() int
+
+	// RegisterPubNonce should be called for each public nonce from the set
+	// of signers. This method returns true once all the public nonces have
+	// been accounted for.
+	RegisterPubNonce(nonce [musig2.PubNonceSize]byte) (bool, error)
+}
+
 // MuSig2SessionInfo is a struct for keeping track of a signing session
 // information in memory.
 type MuSig2SessionInfo struct {
@@ -70,6 +134,10 @@ type MuSig2SessionInfo struct {
 	// is the hash over the combined public key and the local public nonces.
 	SessionID [32]byte
 
+	// Version is the version of the MuSig2 BIP this signing session is
+	// using.
+	Version MuSig2Version
+
 	// PublicNonce contains the public nonce of the local signer session.
 	PublicNonce [musig2.PubNonceSize]byte
 
@@ -145,9 +213,98 @@ func (t *MuSig2Tweaks) ToContextOptions() []musig2.ContextOption {
 	return tweakOpts
 }
 
+// ToV040ContextOptions converts the tweak descriptor to v0.4.0 context options.
+func (t *MuSig2Tweaks) ToV040ContextOptions() []musig2v040.ContextOption {
+	var tweakOpts []musig2v040.ContextOption
+	if len(t.GenericTweaks) > 0 {
+		genericTweaksCopy := make(
+			[]musig2v040.KeyTweakDesc, len(t.GenericTweaks),
+		)
+		for idx := range t.GenericTweaks {
+			genericTweaksCopy[idx] = musig2v040.KeyTweakDesc{
+				Tweak:   t.GenericTweaks[idx].Tweak,
+				IsXOnly: t.GenericTweaks[idx].IsXOnly,
+			}
+		}
+		tweakOpts = append(tweakOpts, musig2v040.WithTweakedContext(
+			genericTweaksCopy...,
+		))
+	}
+
+	// The BIP0086 tweak and the taproot script tweak are mutually
+	// exclusive.
+	if t.TaprootBIP0086Tweak {
+		tweakOpts = append(tweakOpts, musig2v040.WithBip86TweakCtx())
+	} else if len(t.TaprootTweak) > 0 {
+		tweakOpts = append(tweakOpts, musig2v040.WithTaprootTweakCtx(
+			t.TaprootTweak,
+		))
+	}
+
+	return tweakOpts
+}
+
+// MuSig2ParsePubKeys parses a list of raw public keys as the signing keys of a
+// MuSig2 signing session.
+func MuSig2ParsePubKeys(bipVersion MuSig2Version,
+	rawPubKeys [][]byte) ([]*btcec.PublicKey, error) {
+
+	allSignerPubKeys := make([]*btcec.PublicKey, len(rawPubKeys))
+	if len(rawPubKeys) < 2 {
+		return nil, fmt.Errorf("need at least two signing public keys")
+	}
+
+	for idx, pubKeyBytes := range rawPubKeys {
+		switch bipVersion {
+		case MuSig2Version040:
+			pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
+			if err != nil {
+				return nil, fmt.Errorf("error parsing signer "+
+					"public key %d for v0.4.0 (x-only "+
+					"format): %v", idx, err)
+			}
+			allSignerPubKeys[idx] = pubKey
+
+		case MuSig2Version100RC2:
+			pubKey, err := btcec.ParsePubKey(pubKeyBytes)
+			if err != nil {
+				return nil, fmt.Errorf("error parsing signer "+
+					"public key %d for v1.0.0rc2 ("+
+					"compressed format): %v", idx, err)
+			}
+			allSignerPubKeys[idx] = pubKey
+
+		default:
+			return nil, fmt.Errorf("unknown MuSig2 version: <%d>",
+				bipVersion)
+		}
+	}
+
+	return allSignerPubKeys, nil
+}
+
 // MuSig2CombineKeys combines the given set of public keys into a single
 // combined MuSig2 combined public key, applying the given tweaks.
-func MuSig2CombineKeys(allSignerPubKeys []*btcec.PublicKey,
+func MuSig2CombineKeys(bipVersion MuSig2Version,
+	allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
+	tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
+
+	switch bipVersion {
+	case MuSig2Version040:
+		return combineKeysV040(allSignerPubKeys, sortKeys, tweaks)
+
+	case MuSig2Version100RC2:
+		return combineKeysV100RC2(allSignerPubKeys, sortKeys, tweaks)
+
+	default:
+		return nil, fmt.Errorf("unknown MuSig2 version: <%d>",
+			bipVersion)
+	}
+}
+
+// combineKeysV100rc1 implements the MuSigCombineKeys logic for the MuSig2 BIP
+// draft version 1.0.0rc2.
+func combineKeysV100RC2(allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
 	tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
 
 	// Convert the tweak options into the appropriate MuSig2 API functional
@@ -168,11 +325,200 @@ func MuSig2CombineKeys(allSignerPubKeys []*btcec.PublicKey,
 
 	// Then we'll use this information to compute the aggregated public key.
 	combinedKey, _, _, err := musig2.AggregateKeys(
-		allSignerPubKeys, true, keyAggOpts...,
+		allSignerPubKeys, sortKeys, keyAggOpts...,
 	)
 	return combinedKey, err
 }
 
+// combineKeysV040 implements the MuSigCombineKeys logic for the MuSig2 BIP
+// draft version 0.4.0.
+func combineKeysV040(allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
+	tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
+
+	// Convert the tweak options into the appropriate MuSig2 API functional
+	// options.
+	var keyAggOpts []musig2v040.KeyAggOption
+	switch {
+	case tweaks.TaprootBIP0086Tweak:
+		keyAggOpts = append(keyAggOpts, musig2v040.WithBIP86KeyTweak())
+	case len(tweaks.TaprootTweak) > 0:
+		keyAggOpts = append(keyAggOpts, musig2v040.WithTaprootKeyTweak(
+			tweaks.TaprootTweak,
+		))
+	case len(tweaks.GenericTweaks) > 0:
+		genericTweaksCopy := make(
+			[]musig2v040.KeyTweakDesc, len(tweaks.GenericTweaks),
+		)
+		for idx := range tweaks.GenericTweaks {
+			genericTweaksCopy[idx] = musig2v040.KeyTweakDesc{
+				Tweak:   tweaks.GenericTweaks[idx].Tweak,
+				IsXOnly: tweaks.GenericTweaks[idx].IsXOnly,
+			}
+		}
+		keyAggOpts = append(keyAggOpts, musig2v040.WithKeyTweaks(
+			genericTweaksCopy...,
+		))
+	}
+
+	// Then we'll use this information to compute the aggregated public key.
+	combinedKey, _, _, err := musig2v040.AggregateKeys(
+		allSignerPubKeys, sortKeys, keyAggOpts...,
+	)
+
+	// Copy the result back into the default version's native type.
+	return &musig2.AggregateKey{
+		FinalKey:      combinedKey.FinalKey,
+		PreTweakedKey: combinedKey.PreTweakedKey,
+	}, err
+}
+
+// MuSig2CreateContext creates a new MuSig2 signing context.
+func MuSig2CreateContext(bipVersion MuSig2Version, privKey *btcec.PrivateKey,
+	allSignerPubKeys []*btcec.PublicKey,
+	tweaks *MuSig2Tweaks) (MuSig2Context, MuSig2Session, error) {
+
+	switch bipVersion {
+	case MuSig2Version040:
+		return createContextV040(privKey, allSignerPubKeys, tweaks)
+
+	case MuSig2Version100RC2:
+		return createContextV100RC2(privKey, allSignerPubKeys, tweaks)
+
+	default:
+		return nil, nil, fmt.Errorf("unknown MuSig2 version: <%d>",
+			bipVersion)
+	}
+}
+
+// createContextV100RC2 implements the MuSig2CreateContext logic for the MuSig2
+// BIP draft version 1.0.0rc2.
+func createContextV100RC2(privKey *btcec.PrivateKey,
+	allSignerPubKeys []*btcec.PublicKey,
+	tweaks *MuSig2Tweaks) (*musig2.Context, *musig2.Session, error) {
+
+	// The context keeps track of all signing keys and our local key.
+	allOpts := append(
+		[]musig2.ContextOption{
+			musig2.WithKnownSigners(allSignerPubKeys),
+		},
+		tweaks.ToContextOptions()...,
+	)
+	muSigContext, err := musig2.NewContext(privKey, true, allOpts...)
+	if err != nil {
+		return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
+			"context: %v", err)
+	}
+
+	muSigSession, err := muSigContext.NewSession()
+	if err != nil {
+		return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
+			"session: %v", err)
+	}
+
+	return muSigContext, muSigSession, nil
+}
+
+// createContextV040 implements the MuSig2CreateContext logic for the MuSig2 BIP
+// draft version 0.4.0.
+func createContextV040(privKey *btcec.PrivateKey,
+	allSignerPubKeys []*btcec.PublicKey,
+	tweaks *MuSig2Tweaks) (*musig2v040.Context, *musig2v040.Session,
+	error) {
+
+	// The context keeps track of all signing keys and our local key.
+	allOpts := append(
+		[]musig2v040.ContextOption{
+			musig2v040.WithKnownSigners(allSignerPubKeys),
+		},
+		tweaks.ToV040ContextOptions()...,
+	)
+	muSigContext, err := musig2v040.NewContext(privKey, true, allOpts...)
+	if err != nil {
+		return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
+			"context: %v", err)
+	}
+
+	muSigSession, err := muSigContext.NewSession()
+	if err != nil {
+		return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
+			"session: %v", err)
+	}
+
+	return muSigContext, muSigSession, nil
+}
+
+// MuSig2Sign calls the Sign() method on the given versioned signing session and
+// returns the result in the most recent version of the MuSig2 API.
+func MuSig2Sign(session MuSig2Session, msg [32]byte,
+	withSortedKeys bool) (*musig2.PartialSignature, error) {
+
+	switch s := session.(type) {
+	case *musig2.Session:
+		var opts []musig2.SignOption
+		if withSortedKeys {
+			opts = append(opts, musig2.WithSortedKeys())
+		}
+		partialSig, err := s.Sign(msg, opts...)
+		if err != nil {
+			return nil, fmt.Errorf("error signing with local key: "+
+				"%v", err)
+		}
+
+		return partialSig, nil
+
+	case *musig2v040.Session:
+		var opts []musig2v040.SignOption
+		if withSortedKeys {
+			opts = append(opts, musig2v040.WithSortedKeys())
+		}
+		partialSig, err := s.Sign(msg, opts...)
+		if err != nil {
+			return nil, fmt.Errorf("error signing with local key: "+
+				"%v", err)
+		}
+
+		return &musig2.PartialSignature{
+			S: partialSig.S,
+			R: partialSig.R,
+		}, nil
+
+	default:
+		return nil, fmt.Errorf("invalid session type <%T>", s)
+	}
+}
+
+// MuSig2CombineSig calls the CombineSig() method on the given versioned signing
+// session and returns the result in the most recent version of the MuSig2 API.
+func MuSig2CombineSig(session MuSig2Session,
+	otherPartialSig *musig2.PartialSignature) (bool, error) {
+
+	switch s := session.(type) {
+	case *musig2.Session:
+		haveAllSigs, err := s.CombineSig(otherPartialSig)
+		if err != nil {
+			return false, fmt.Errorf("error combining partial "+
+				"signature: %v", err)
+		}
+
+		return haveAllSigs, nil
+
+	case *musig2v040.Session:
+		haveAllSigs, err := s.CombineSig(&musig2v040.PartialSignature{
+			S: otherPartialSig.S,
+			R: otherPartialSig.R,
+		})
+		if err != nil {
+			return false, fmt.Errorf("error combining partial "+
+				"signature: %v", err)
+		}
+
+		return haveAllSigs, nil
+
+	default:
+		return false, fmt.Errorf("invalid session type <%T>", s)
+	}
+}
+
 // NewMuSig2SessionID returns the unique ID of a MuSig2 session by using the
 // combined key and the local public nonces and hashing that data.
 func NewMuSig2SessionID(combinedKey *btcec.PublicKey,
diff --git a/internal/musig2v040/README.md b/internal/musig2v040/README.md
new file mode 100644
index 000000000..bd248d267
--- /dev/null
+++ b/internal/musig2v040/README.md
@@ -0,0 +1,10 @@
+# MuSig2 v0.4.0
+
+This package contains an exact copy of the MuSig2 code as found in
+`github.com/btcsuite/btcec/v2/schnorr/musig2` at the tag `btcec/v2.2.2`.
+
+This corresponds to the [MuSig2 BIP specification version of
+`v0.4.0`](https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki).
+
+We only keep this code here to allow implementing a backward compatible,
+versioned MuSig2 RPC.
diff --git a/internal/musig2v040/context.go b/internal/musig2v040/context.go
new file mode 100644
index 000000000..b1701164c
--- /dev/null
+++ b/internal/musig2v040/context.go
@@ -0,0 +1,670 @@
+// Copyright (c) 2013-2022 The btcsuite developers
+
+package musig2v040
+
+import (
+	"fmt"
+
+	"github.com/btcsuite/btcd/btcec/v2"
+	"github.com/btcsuite/btcd/btcec/v2/schnorr"
+)
+
+var (
+	// ErrSignersNotSpecified is returned when a caller attempts to create
+	// a context without specifying either the total number of signers, or
+	// the complete set of singers.
+	ErrSignersNotSpecified = fmt.Errorf("total number of signers or all " +
+		"signers must be known")
+
+	// ErrSignerNotInKeySet is returned when a the private key for a signer
+	// isn't included in the set of signing public keys.
+	ErrSignerNotInKeySet = fmt.Errorf("signing key is not found in key" +
+		" set")
+
+	// ErrAlredyHaveAllNonces is called when RegisterPubNonce is called too
+	// many times for a given signing session.
+	ErrAlredyHaveAllNonces = fmt.Errorf("already have all nonces")
+
+	// ErrNotEnoughSigners is returned when a caller attempts to create a
+	// session from a context, but before all the required signers are
+	// known.
+	ErrNotEnoughSigners = fmt.Errorf("not enough signers")
+
+	// ErrAlredyHaveAllNonces is returned when a caller attempts to
+	// register a signer, once we already have the total set of known
+	// signers.
+	ErrAlreadyHaveAllSigners = fmt.Errorf("all signers registered")
+
+	// ErrAlredyHaveAllSigs is called when CombineSig is called too many
+	// times for a given signing session.
+	ErrAlredyHaveAllSigs = fmt.Errorf("already have all sigs")
+
+	// ErrSigningContextReuse is returned if a user attempts to sign using
+	// the same signing context more than once.
+	ErrSigningContextReuse = fmt.Errorf("nonce already used")
+
+	// ErrFinalSigInvalid is returned when the combined signature turns out
+	// to be invalid.
+	ErrFinalSigInvalid = fmt.Errorf("final signature is invalid")
+
+	// ErrCombinedNonceUnavailable is returned when a caller attempts to
+	// sign a partial signature, without first having collected all the
+	// required combined nonces.
+	ErrCombinedNonceUnavailable = fmt.Errorf("missing combined nonce")
+
+	// ErrTaprootInternalKeyUnavailable is returned when a user attempts to
+	// obtain the
+	ErrTaprootInternalKeyUnavailable = fmt.Errorf("taproot tweak not used")
+
+	// ErrNotEnoughSigners is returned if a caller attempts to obtain an
+	// early nonce when it wasn't specified
+	ErrNoEarlyNonce = fmt.Errorf("no early nonce available")
+)
+
+// Context is a managed signing context for musig2. It takes care of things
+// like securely generating secret nonces, aggregating keys and nonces, etc.
+type Context struct {
+	// signingKey is the key we'll use for signing.
+	signingKey *btcec.PrivateKey
+
+	// pubKey is our even-y coordinate public  key.
+	pubKey *btcec.PublicKey
+
+	// combinedKey is the aggregated public key.
+	combinedKey *AggregateKey
+
+	// uniqueKeyIndex is the index of the second unique key in the keySet.
+	// This is used to speed up signing and verification computations.
+	uniqueKeyIndex int
+
+	// keysHash is the hash of all the keys as defined in musig2.
+	keysHash []byte
+
+	// opts is the set of options for the context.
+	opts *contextOptions
+
+	// shouldSort keeps track of if the public keys should be sorted before
+	// any operations.
+	shouldSort bool
+
+	// sessionNonce will be populated if the earlyNonce option is true.
+	// After the first session is created, this nonce will be blanked out.
+	sessionNonce *Nonces
+}
+
+// ContextOption is a functional option argument that allows callers to modify
+// the musig2 signing is done within a context.
+type ContextOption func(*contextOptions)
+
+// contextOptions houses the set of functional options that can be used to
+// musig2 signing protocol.
+type contextOptions struct {
+	// tweaks is the set of optinoal tweaks to apply to the combined public
+	// key.
+	tweaks []KeyTweakDesc
+
+	// taprootTweak specifies the taproot tweak. If specified, then we'll
+	// use this as the script root for the BIP 341 taproot (x-only) tweak.
+	// Normally we'd just apply the raw 32 byte tweak, but for taproot, we
+	// first need to compute the aggregated key before tweaking, and then
+	// use it as the internal key. This is required as the taproot tweak
+	// also commits to the public key, which in this case is the aggregated
+	// key before the tweak.
+	taprootTweak []byte
+
+	// bip86Tweak if true, then the weak will just be
+	// h_tapTweak(internalKey) as there is no true script root.
+	bip86Tweak bool
+
+	// keySet is the complete set of signers for this context.
+	keySet []*btcec.PublicKey
+
+	// numSigners is the total number of signers that will eventually be a
+	// part of the context.
+	numSigners int
+
+	// earlyNonce determines if a nonce should be generated during context
+	// creation, to be automatically passed to the created session.
+	earlyNonce bool
+}
+
+// defaultContextOptions returns the default context options.
+func defaultContextOptions() *contextOptions {
+	return &contextOptions{}
+}
+
+// WithTweakedContext specifies that within the context, the aggregated public
+// key should be tweaked with the specified tweaks.
+func WithTweakedContext(tweaks ...KeyTweakDesc) ContextOption {
+	return func(o *contextOptions) {
+		o.tweaks = tweaks
+	}
+}
+
+// WithTaprootTweakCtx specifies that within this context, the final key should
+// use the taproot tweak as defined in BIP 341: outputKey = internalKey +
+// h_tapTweak(internalKey || scriptRoot). In this case, the aggreaged key
+// before the tweak will be used as the internal key.
+func WithTaprootTweakCtx(scriptRoot []byte) ContextOption {
+	return func(o *contextOptions) {
+		o.taprootTweak = scriptRoot
+	}
+}
+
+// WithBip86TweakCtx specifies that within this context, the final key should
+// use the taproot tweak as defined in BIP 341, with the BIP 86 modification:
+// outputKey = internalKey + h_tapTweak(internalKey)*G. In this case, the
+// aggreaged key before the tweak will be used as the internal key.
+func WithBip86TweakCtx() ContextOption {
+	return func(o *contextOptions) {
+		o.bip86Tweak = true
+	}
+}
+
+// WithKnownSigners is an optional parameter that should be used if a session
+// can be created as soon as all the singers are known.
+func WithKnownSigners(signers []*btcec.PublicKey) ContextOption {
+	return func(o *contextOptions) {
+		o.keySet = signers
+		o.numSigners = len(signers)
+	}
+}
+
+// WithNumSigners is a functional option used to specify that a context should
+// be created without knowing all the signers. Instead the total number of
+// signers is specified to ensure that a session can only be created once all
+// the signers are known.
+//
+// NOTE: Either WithKnownSigners or WithNumSigners MUST be specified.
+func WithNumSigners(n int) ContextOption {
+	return func(o *contextOptions) {
+		o.numSigners = n
+	}
+}
+
+// WithEarlyNonceGen allow a caller to specify that a nonce should be generated
+// early, before the session is created. This should be used in protocols that
+// require some partial nonce exchange before all the signers are known.
+//
+// NOTE: This option must only be specified with the WithNumSigners option.
+func WithEarlyNonceGen() ContextOption {
+	return func(o *contextOptions) {
+		o.earlyNonce = true
+	}
+}
+
+// NewContext creates a new signing context with the passed singing key and set
+// of public keys for each of the other signers.
+//
+// NOTE: This struct should be used over the raw Sign API whenever possible.
+func NewContext(signingKey *btcec.PrivateKey, shouldSort bool,
+	ctxOpts ...ContextOption) (*Context, error) {
+
+	// First, parse the set of optional context options.
+	opts := defaultContextOptions()
+	for _, option := range ctxOpts {
+		option(opts)
+	}
+
+	pubKey, err := schnorr.ParsePubKey(
+		schnorr.SerializePubKey(signingKey.PubKey()),
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	ctx := &Context{
+		signingKey: signingKey,
+		pubKey:     pubKey,
+		opts:       opts,
+		shouldSort: shouldSort,
+	}
+
+	switch {
+
+	// We know all the signers, so we can compute the aggregated key, along
+	// with all the other intermediate state we need to do signing and
+	// verification.
+	case opts.keySet != nil:
+		if err := ctx.combineSignerKeys(); err != nil {
+			return nil, err
+		}
+
+	// The total signers are known, so we add ourselves, and skip key
+	// aggregation.
+	case opts.numSigners != 0:
+		// Otherwise, we'll add ourselves as the only known signer, and
+		// await further calls to RegisterSigner before a session can
+		// be created.
+		opts.keySet = make([]*btcec.PublicKey, 0, opts.numSigners)
+		opts.keySet = append(opts.keySet, pubKey)
+
+		// If early nonce generation is specified, then we'll generate
+		// the nonce now to pass in to the session once all the callers
+		// are known.
+		if opts.earlyNonce {
+			ctx.sessionNonce, err = GenNonces()
+			if err != nil {
+				return nil, err
+			}
+		}
+
+	default:
+		return nil, ErrSignersNotSpecified
+	}
+
+	return ctx, nil
+}
+
+// combineSignerKeys is used to compute the aggregated signer key once all the
+// signers are known.
+func (c *Context) combineSignerKeys() error {
+	// As a sanity check, make sure the signing key is actually
+	// amongst the sit of signers.
+	var keyFound bool
+	for _, key := range c.opts.keySet {
+		if key.IsEqual(c.pubKey) {
+			keyFound = true
+			break
+		}
+	}
+	if !keyFound {
+		return ErrSignerNotInKeySet
+	}
+
+	// Now that we know that we're actually a signer, we'll
+	// generate the key hash finger print and second unique key
+	// index so we can speed up signing later.
+	c.keysHash = keyHashFingerprint(c.opts.keySet, c.shouldSort)
+	c.uniqueKeyIndex = secondUniqueKeyIndex(
+		c.opts.keySet, c.shouldSort,
+	)
+
+	keyAggOpts := []KeyAggOption{
+		WithKeysHash(c.keysHash),
+		WithUniqueKeyIndex(c.uniqueKeyIndex),
+	}
+	switch {
+	case c.opts.bip86Tweak:
+		keyAggOpts = append(
+			keyAggOpts, WithBIP86KeyTweak(),
+		)
+	case c.opts.taprootTweak != nil:
+		keyAggOpts = append(
+			keyAggOpts, WithTaprootKeyTweak(c.opts.taprootTweak),
+		)
+	case len(c.opts.tweaks) != 0:
+		keyAggOpts = append(keyAggOpts, WithKeyTweaks(c.opts.tweaks...))
+	}
+
+	// Next, we'll use this information to compute the aggregated
+	// public key that'll be used for signing in practice.
+	var err error
+	c.combinedKey, _, _, err = AggregateKeys(
+		c.opts.keySet, c.shouldSort, keyAggOpts...,
+	)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// EarlySessionNonce returns the early session nonce, if available.
+func (c *Context) EarlySessionNonce() (*Nonces, error) {
+	if c.sessionNonce == nil {
+		return nil, ErrNoEarlyNonce
+	}
+
+	return c.sessionNonce, nil
+}
+
+// RegisterSigner allows a caller to register a signer after the context has
+// been created. This will be used in scenarios where the total number of
+// signers is known, but nonce exchange needs to happen before all the signers
+// are known.
+//
+// A bool is returned which indicates if all the signers have been registered.
+//
+// NOTE: If the set of keys are not to be sorted during signing, then the
+// ordering each key is registered with MUST match the desired ordering.
+func (c *Context) RegisterSigner(pub *btcec.PublicKey) (bool, error) {
+	haveAllSigners := len(c.opts.keySet) == c.opts.numSigners
+	if haveAllSigners {
+		return false, ErrAlreadyHaveAllSigners
+	}
+
+	c.opts.keySet = append(c.opts.keySet, pub)
+
+	// If we have the expected number of signers at this point, then we can
+	// generate the aggregated key and other necessary information.
+	haveAllSigners = len(c.opts.keySet) == c.opts.numSigners
+	if haveAllSigners {
+		if err := c.combineSignerKeys(); err != nil {
+			return false, err
+		}
+	}
+
+	return haveAllSigners, nil
+}
+
+// NumRegisteredSigners returns the total number of registered signers.
+func (c *Context) NumRegisteredSigners() int {
+	return len(c.opts.keySet)
+}
+
+// CombinedKey returns the combined public key that will be used to generate
+// multi-signatures  against.
+func (c *Context) CombinedKey() (*btcec.PublicKey, error) {
+	// If the caller hasn't registered all the signers at this point, then
+	// the combined key won't be available.
+	if c.combinedKey == nil {
+		return nil, ErrNotEnoughSigners
+	}
+
+	return c.combinedKey.FinalKey, nil
+}
+
+// PubKey returns the public key of the signer of this session.
+func (c *Context) PubKey() btcec.PublicKey {
+	return *c.pubKey
+}
+
+// SigningKeys returns the set of keys used for signing.
+func (c *Context) SigningKeys() []*btcec.PublicKey {
+	keys := make([]*btcec.PublicKey, len(c.opts.keySet))
+	copy(keys, c.opts.keySet)
+
+	return keys
+}
+
+// TaprootInternalKey returns the internal taproot key, which is the aggregated
+// key _before_ the tweak is applied. If a taproot tweak was specified, then
+// CombinedKey() will return the fully tweaked output key, with this method
+// returning the internal key. If a taproot tweak wasn't specified, then this
+// method will return an error.
+func (c *Context) TaprootInternalKey() (*btcec.PublicKey, error) {
+	// If the caller hasn't registered all the signers at this point, then
+	// the combined key won't be available.
+	if c.combinedKey == nil {
+		return nil, ErrNotEnoughSigners
+	}
+
+	if c.opts.taprootTweak == nil && !c.opts.bip86Tweak {
+		return nil, ErrTaprootInternalKeyUnavailable
+	}
+
+	return c.combinedKey.PreTweakedKey, nil
+}
+
+// SessionOption is a functional option argument that allows callers to modify
+// the musig2 signing is done within a session.
+type SessionOption func(*sessionOptions)
+
+// sessionOptions houses the set of functional options that can be used to
+// modify the musig2 signing protocol.
+type sessionOptions struct {
+	externalNonce *Nonces
+}
+
+// defaultSessionOptions returns the default session options.
+func defaultSessionOptions() *sessionOptions {
+	return &sessionOptions{}
+}
+
+// WithPreGeneratedNonce allows a caller to start a session using a nonce
+// they've generated themselves. This may be useful in protocols where all the
+// signer keys may not be known before nonce exchange needs to occur.
+func WithPreGeneratedNonce(nonce *Nonces) SessionOption {
+	return func(o *sessionOptions) {
+		o.externalNonce = nonce
+	}
+}
+
+// Session represents a musig2 signing session. A new instance should be
+// created each time a multi-signature is needed. The session struct handles
+// nonces management, incremental partial sig vitrifaction, as well as final
+// signature combination. Errors are returned when unsafe behavior such as
+// nonce re-use is attempted.
+//
+// NOTE: This struct should be used over the raw Sign API whenever possible.
+type Session struct {
+	opts *sessionOptions
+
+	ctx *Context
+
+	localNonces *Nonces
+
+	pubNonces [][PubNonceSize]byte
+
+	combinedNonce *[PubNonceSize]byte
+
+	msg [32]byte
+
+	ourSig *PartialSignature
+	sigs   []*PartialSignature
+
+	finalSig *schnorr.Signature
+}
+
+// NewSession creates a new musig2 signing session.
+func (c *Context) NewSession(options ...SessionOption) (*Session, error) {
+	opts := defaultSessionOptions()
+	for _, opt := range options {
+		opt(opts)
+	}
+
+	// At this point we verify that we know of all the signers, as
+	// otherwise we can't proceed with the session. This check is intended
+	// to catch misuse of the API wherein a caller forgets to register the
+	// remaining signers if they're doing nonce generation ahead of time.
+	if len(c.opts.keySet) != c.opts.numSigners {
+		return nil, ErrNotEnoughSigners
+	}
+
+	// If an early nonce was specified, then we'll automatically add the
+	// corresponding session option for the caller.
+	var localNonces *Nonces
+	if c.sessionNonce != nil {
+		// Apply the early nonce to the session, and also blank out the
+		// session nonce on the context to ensure it isn't ever re-used
+		// for another session.
+		localNonces = c.sessionNonce
+		c.sessionNonce = nil
+	} else if opts.externalNonce != nil {
+		// Otherwise if there's a custom nonce passed in via the
+		// session options, then use that instead.
+		localNonces = opts.externalNonce
+	}
+
+	// Now that we know we have enough signers, we'll either use the caller
+	// specified nonce, or generate a fresh set.
+	var err error
+	if localNonces == nil {
+		// At this point we need to generate a fresh nonce. We'll pass
+		// in some auxiliary information to strengthen the nonce
+		// generated.
+		localNonces, err = GenNonces(
+			WithNonceSecretKeyAux(c.signingKey),
+			WithNonceCombinedKeyAux(c.combinedKey.FinalKey),
+		)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	s := &Session{
+		opts:        opts,
+		ctx:         c,
+		localNonces: localNonces,
+		pubNonces:   make([][PubNonceSize]byte, 0, c.opts.numSigners),
+		sigs:        make([]*PartialSignature, 0, c.opts.numSigners),
+	}
+
+	s.pubNonces = append(s.pubNonces, localNonces.PubNonce)
+
+	return s, nil
+}
+
+// PublicNonce returns the public nonce for a signer. This should be sent to
+// other parties before signing begins, so they can compute the aggregated
+// public nonce.
+func (s *Session) PublicNonce() [PubNonceSize]byte {
+	return s.localNonces.PubNonce
+}
+
+// NumRegisteredNonces returns the total number of nonces that have been
+// regsitered so far.
+func (s *Session) NumRegisteredNonces() int {
+	return len(s.pubNonces)
+}
+
+// RegisterPubNonce should be called for each public nonce from the set of
+// signers. This method returns true once all the public nonces have been
+// accounted for.
+func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) {
+	// If we already have all the nonces, then this method was called too
+	// many times.
+	haveAllNonces := len(s.pubNonces) == s.ctx.opts.numSigners
+	if haveAllNonces {
+		return false, ErrAlredyHaveAllNonces
+	}
+
+	// Add this nonce and check again if we already have tall the nonces we
+	// need.
+	s.pubNonces = append(s.pubNonces, nonce)
+	haveAllNonces = len(s.pubNonces) == s.ctx.opts.numSigners
+
+	// If we have all the nonces, then we can go ahead and combine them
+	// now.
+	if haveAllNonces {
+		combinedNonce, err := AggregateNonces(s.pubNonces)
+		if err != nil {
+			return false, err
+		}
+
+		s.combinedNonce = &combinedNonce
+	}
+
+	return haveAllNonces, nil
+}
+
+// Sign generates a partial signature for the target message, using the target
+// context. If this method is called more than once per context, then an error
+// is returned, as that means a nonce was re-used.
+func (s *Session) Sign(msg [32]byte,
+	signOpts ...SignOption) (*PartialSignature, error) {
+
+	switch {
+	// If no local nonce is present, then this means we already signed, so
+	// we'll return an error to prevent nonce re-use.
+	case s.localNonces == nil:
+		return nil, ErrSigningContextReuse
+
+	// We also need to make sure we have the combined nonce, otherwise this
+	// funciton was called too early.
+	case s.combinedNonce == nil:
+		return nil, ErrCombinedNonceUnavailable
+	}
+
+	switch {
+	case s.ctx.opts.bip86Tweak:
+		signOpts = append(
+			signOpts, WithBip86SignTweak(),
+		)
+	case s.ctx.opts.taprootTweak != nil:
+		signOpts = append(
+			signOpts, WithTaprootSignTweak(s.ctx.opts.taprootTweak),
+		)
+	case len(s.ctx.opts.tweaks) != 0:
+		signOpts = append(signOpts, WithTweaks(s.ctx.opts.tweaks...))
+	}
+
+	partialSig, err := Sign(
+		s.localNonces.SecNonce, s.ctx.signingKey, *s.combinedNonce,
+		s.ctx.opts.keySet, msg, signOpts...,
+	)
+
+	// Now that we've generated our signature, we'll make sure to blank out
+	// our signing nonce.
+	s.localNonces = nil
+
+	if err != nil {
+		return nil, err
+	}
+
+	s.msg = msg
+
+	s.ourSig = partialSig
+	s.sigs = append(s.sigs, partialSig)
+
+	return partialSig, nil
+}
+
+// CombineSig buffers a partial signature received from a signing party. The
+// method returns true once all the signatures are available, and can be
+// combined into the final signature.
+func (s *Session) CombineSig(sig *PartialSignature) (bool, error) {
+	// First check if we already have all the signatures we need. We
+	// already accumulated our own signature when we generated the sig.
+	haveAllSigs := len(s.sigs) == len(s.ctx.opts.keySet)
+	if haveAllSigs {
+		return false, ErrAlredyHaveAllSigs
+	}
+
+	// TODO(roasbeef): incremental check for invalid sig, or just detect at
+	// the very end?
+
+	// Accumulate this sig, and check again if we have all the sigs we
+	// need.
+	s.sigs = append(s.sigs, sig)
+	haveAllSigs = len(s.sigs) == len(s.ctx.opts.keySet)
+
+	// If we have all the signatures, then we can combine them all into the
+	// final signature.
+	if haveAllSigs {
+		var combineOpts []CombineOption
+		switch {
+		case s.ctx.opts.bip86Tweak:
+			combineOpts = append(
+				combineOpts, WithBip86TweakedCombine(
+					s.msg, s.ctx.opts.keySet,
+					s.ctx.shouldSort,
+				),
+			)
+		case s.ctx.opts.taprootTweak != nil:
+			combineOpts = append(
+				combineOpts, WithTaprootTweakedCombine(
+					s.msg, s.ctx.opts.keySet,
+					s.ctx.opts.taprootTweak, s.ctx.shouldSort,
+				),
+			)
+		case len(s.ctx.opts.tweaks) != 0:
+			combineOpts = append(
+				combineOpts, WithTweakedCombine(
+					s.msg, s.ctx.opts.keySet,
+					s.ctx.opts.tweaks, s.ctx.shouldSort,
+				),
+			)
+		}
+
+		finalSig := CombineSigs(s.ourSig.R, s.sigs, combineOpts...)
+
+		// We'll also verify the signature at this point to ensure it's
+		// valid.
+		//
+		// TODO(roasbef): allow skipping?
+		if !finalSig.Verify(s.msg[:], s.ctx.combinedKey.FinalKey) {
+			return false, ErrFinalSigInvalid
+		}
+
+		s.finalSig = finalSig
+	}
+
+	return haveAllSigs, nil
+}
+
+// FinalSig returns the final combined multi-signature, if present.
+func (s *Session) FinalSig() *schnorr.Signature {
+	return s.finalSig
+}
diff --git a/internal/musig2v040/keys.go b/internal/musig2v040/keys.go
new file mode 100644
index 000000000..9016f0b56
--- /dev/null
+++ b/internal/musig2v040/keys.go
@@ -0,0 +1,459 @@
+// Copyright 2013-2022 The btcsuite developers
+
+package musig2v040
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+
+	"github.com/btcsuite/btcd/btcec/v2"
+	"github.com/btcsuite/btcd/btcec/v2/schnorr"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
+	secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
+)
+
+var (
+	// KeyAggTagList is the tagged hash tag used to compute the hash of the
+	// list of sorted public keys.
+	KeyAggTagList = []byte("KeyAgg list")
+
+	// KeyAggTagCoeff is the tagged hash tag used to compute the key
+	// aggregation coefficient for each key.
+	KeyAggTagCoeff = []byte("KeyAgg coefficient")
+
+	// ErrTweakedKeyIsInfinity is returned if while tweaking a key, we end
+	// up with the point at infinity.
+	ErrTweakedKeyIsInfinity = fmt.Errorf("tweaked key is infinity point")
+
+	// ErrTweakedKeyOverflows is returned if a tweaking key is larger than
+	// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141.
+	ErrTweakedKeyOverflows = fmt.Errorf("tweaked key is to large")
+)
+
+// sortableKeys defines a type of slice of public keys that implements the sort
+// interface for BIP 340 keys.
+type sortableKeys []*btcec.PublicKey
+
+// Less reports whether the element with index i must sort before the element
+// with index j.
+func (s sortableKeys) Less(i, j int) bool {
+	// TODO(roasbeef): more efficient way to compare...
+	keyIBytes := schnorr.SerializePubKey(s[i])
+	keyJBytes := schnorr.SerializePubKey(s[j])
+
+	return bytes.Compare(keyIBytes, keyJBytes) == -1
+}
+
+// Swap swaps the elements with indexes i and j.
+func (s sortableKeys) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+// Len is the number of elements in the collection.
+func (s sortableKeys) Len() int {
+	return len(s)
+}
+
+// sortKeys takes a set of schnorr public keys and returns a new slice that is
+// a copy of the keys sorted in lexicographical order bytes on the x-only
+// pubkey serialization.
+func sortKeys(keys []*btcec.PublicKey) []*btcec.PublicKey {
+	keySet := sortableKeys(keys)
+	if sort.IsSorted(keySet) {
+		return keys
+	}
+
+	sort.Sort(keySet)
+	return keySet
+}
+
+// keyHashFingerprint computes the tagged hash of the series of (sorted) public
+// keys passed as input. This is used to compute the aggregation coefficient
+// for each key. The final computation is:
+//   - H(tag=KeyAgg list, pk1 || pk2..)
+func keyHashFingerprint(keys []*btcec.PublicKey, sort bool) []byte {
+	if sort {
+		keys = sortKeys(keys)
+	}
+
+	// We'll create a single buffer and slice into that so the bytes buffer
+	// doesn't continually need to grow the underlying buffer.
+	keyAggBuf := make([]byte, 32*len(keys))
+	keyBytes := bytes.NewBuffer(keyAggBuf[0:0])
+	for _, key := range keys {
+		keyBytes.Write(schnorr.SerializePubKey(key))
+	}
+
+	h := chainhash.TaggedHash(KeyAggTagList, keyBytes.Bytes())
+	return h[:]
+}
+
+// keyBytesEqual returns true if two keys are the same from the PoV of BIP
+// 340's 32-byte x-only public keys.
+func keyBytesEqual(a, b *btcec.PublicKey) bool {
+	return bytes.Equal(
+		schnorr.SerializePubKey(a),
+		schnorr.SerializePubKey(b),
+	)
+}
+
+// aggregationCoefficient computes the key aggregation coefficient for the
+// specified target key. The coefficient is computed as:
+//   - H(tag=KeyAgg coefficient, keyHashFingerprint(pks) || pk)
+func aggregationCoefficient(keySet []*btcec.PublicKey,
+	targetKey *btcec.PublicKey, keysHash []byte,
+	secondKeyIdx int) *btcec.ModNScalar {
+
+	var mu btcec.ModNScalar
+
+	// If this is the second key, then this coefficient is just one.
+	if secondKeyIdx != -1 && keyBytesEqual(keySet[secondKeyIdx], targetKey) {
+		return mu.SetInt(1)
+	}
+
+	// Otherwise, we'll compute the full finger print hash for this given
+	// key and then use that to compute the coefficient tagged hash:
+	//  * H(tag=KeyAgg coefficient, keyHashFingerprint(pks, pk) || pk)
+	var coefficientBytes [64]byte
+	copy(coefficientBytes[:], keysHash[:])
+	copy(coefficientBytes[32:], schnorr.SerializePubKey(targetKey))
+
+	muHash := chainhash.TaggedHash(KeyAggTagCoeff, coefficientBytes[:])
+
+	mu.SetByteSlice(muHash[:])
+
+	return &mu
+}
+
+// secondUniqueKeyIndex returns the index of the second unique key. If all keys
+// are the same, then a value of -1 is returned.
+func secondUniqueKeyIndex(keySet []*btcec.PublicKey, sort bool) int {
+	if sort {
+		keySet = sortKeys(keySet)
+	}
+
+	// Find the first key that isn't the same as the very first key (second
+	// unique key).
+	for i := range keySet {
+		if !keyBytesEqual(keySet[i], keySet[0]) {
+			return i
+		}
+	}
+
+	// A value of negative one is used to indicate that all the keys in the
+	// sign set are actually equal, which in practice actually makes musig2
+	// useless, but we need a value to distinguish this case.
+	return -1
+}
+
+// KeyTweakDesc describes a tweak to be applied to the aggregated public key
+// generation and signing process. The IsXOnly specifies if the target key
+// should be converted to an x-only public key before tweaking.
+type KeyTweakDesc struct {
+	// Tweak is the 32-byte value that will modify the public key.
+	Tweak [32]byte
+
+	// IsXOnly if true, then the public key will be mapped to an x-only key
+	// before the tweaking operation is applied.
+	IsXOnly bool
+}
+
+// KeyAggOption is a functional option argument that allows callers to specify
+// more or less information that has been pre-computed to the main routine.
+type KeyAggOption func(*keyAggOption)
+
+// keyAggOption houses the set of functional options that modify key
+// aggregation.
+type keyAggOption struct {
+	// keyHash is the output of keyHashFingerprint for a given set of keys.
+	keyHash []byte
+
+	// uniqueKeyIndex is the pre-computed index of the second unique key.
+	uniqueKeyIndex *int
+
+	// tweaks specifies a series of tweaks to be applied to the aggregated
+	// public key.
+	tweaks []KeyTweakDesc
+
+	// taprootTweak controls if the tweaks above should be applied in a BIP
+	// 340 style.
+	taprootTweak bool
+
+	// bip86Tweak specifies that the taproot tweak should be done in a BIP
+	// 86 style, where we don't expect an actual tweak and instead just
+	// commit to the public key itself.
+	bip86Tweak bool
+}
+
+// WithKeysHash allows key aggregation to be optimize, by allowing the caller
+// to specify the hash of all the keys.
+func WithKeysHash(keyHash []byte) KeyAggOption {
+	return func(o *keyAggOption) {
+		o.keyHash = keyHash
+	}
+}
+
+// WithUniqueKeyIndex allows the caller to specify the index of the second
+// unique key.
+func WithUniqueKeyIndex(idx int) KeyAggOption {
+	return func(o *keyAggOption) {
+		i := idx
+		o.uniqueKeyIndex = &i
+	}
+}
+
+// WithKeyTweaks allows a caller to specify a series of 32-byte tweaks that
+// should be applied to the final aggregated public key.
+func WithKeyTweaks(tweaks ...KeyTweakDesc) KeyAggOption {
+	return func(o *keyAggOption) {
+		o.tweaks = tweaks
+	}
+}
+
+// WithTaprootKeyTweak specifies that within this context, the final key should
+// use the taproot tweak as defined in BIP 341: outputKey = internalKey +
+// h_tapTweak(internalKey || scriptRoot). In this case, the aggregated key
+// before the tweak will be used as the internal key.
+//
+// This option should be used instead of WithKeyTweaks when the aggregated key
+// is intended to be used as a taproot output key that commits to a script
+// root.
+func WithTaprootKeyTweak(scriptRoot []byte) KeyAggOption {
+	return func(o *keyAggOption) {
+		var tweak [32]byte
+		copy(tweak[:], scriptRoot[:])
+
+		o.tweaks = []KeyTweakDesc{
+			{
+				Tweak:   tweak,
+				IsXOnly: true,
+			},
+		}
+		o.taprootTweak = true
+	}
+}
+
+// WithBIP86KeyTweak specifies that then during key aggregation, the BIP 86
+// tweak which just commits to the hash of the serialized public key should be
+// used. This option should be used when signing with a key that was derived
+// using BIP 86.
+func WithBIP86KeyTweak() KeyAggOption {
+	return func(o *keyAggOption) {
+		o.tweaks = []KeyTweakDesc{
+			{
+				IsXOnly: true,
+			},
+		}
+		o.taprootTweak = true
+		o.bip86Tweak = true
+	}
+}
+
+// defaultKeyAggOptions returns the set of default arguments for key
+// aggregation.
+func defaultKeyAggOptions() *keyAggOption {
+	return &keyAggOption{}
+}
+
+// hasEvenY returns true if the affine representation of the passed jacobian
+// point has an even y coordinate.
+//
+// TODO(roasbeef): double check, can just check the y coord even not jacobian?
+func hasEvenY(pJ btcec.JacobianPoint) bool {
+	pJ.ToAffine()
+	p := btcec.NewPublicKey(&pJ.X, &pJ.Y)
+	keyBytes := p.SerializeCompressed()
+	return keyBytes[0] == secp.PubKeyFormatCompressedEven
+}
+
+// tweakKey applies a tweaks to the passed public key using the specified
+// tweak. The parityAcc and tweakAcc are returned (in that order) which
+// includes the accumulate ration of the parity factor and the tweak multiplied
+// by the parity factor. The xOnly bool specifies if this is to be an x-only
+// tweak or not.
+func tweakKey(keyJ btcec.JacobianPoint, parityAcc btcec.ModNScalar, tweak [32]byte,
+	tweakAcc btcec.ModNScalar,
+	xOnly bool) (btcec.JacobianPoint, btcec.ModNScalar, btcec.ModNScalar, error) {
+
+	// First we'll compute the new parity factor for this key. If the key has
+	// an odd y coordinate (not even), then we'll need to negate it (multiply
+	// by -1 mod n, in this case).
+	var parityFactor btcec.ModNScalar
+	if xOnly && !hasEvenY(keyJ) {
+		parityFactor.SetInt(1).Negate()
+	} else {
+		parityFactor.SetInt(1)
+	}
+
+	// Next, map the tweak into a mod n integer so we can use it for
+	// manipulations below.
+	tweakInt := new(btcec.ModNScalar)
+	overflows := tweakInt.SetBytes(&tweak)
+	if overflows == 1 {
+		return keyJ, parityAcc, tweakAcc, ErrTweakedKeyOverflows
+	}
+
+	// Next, we'll compute: Q_i = g*Q + t*G, where g is our parityFactor and t
+	// is the tweakInt above. We'll space things out a bit to make it easier to
+	// follow.
+	//
+	// First compute t*G:
+	var tweakedGenerator btcec.JacobianPoint
+	btcec.ScalarBaseMultNonConst(tweakInt, &tweakedGenerator)
+
+	// Next compute g*Q:
+	btcec.ScalarMultNonConst(&parityFactor, &keyJ, &keyJ)
+
+	// Finally add both of them together to get our final
+	// tweaked point.
+	btcec.AddNonConst(&tweakedGenerator, &keyJ, &keyJ)
+
+	// As a sanity check, make sure that we didn't just end up with the
+	// point at infinity.
+	if keyJ == infinityPoint {
+		return keyJ, parityAcc, tweakAcc, ErrTweakedKeyIsInfinity
+	}
+
+	// As a final wrap up step, we'll accumulate the parity
+	// factor and also this tweak into the final set of accumulators.
+	parityAcc.Mul(&parityFactor)
+	tweakAcc.Mul(&parityFactor).Add(tweakInt)
+
+	return keyJ, parityAcc, tweakAcc, nil
+}
+
+// AggregateKey is a final aggregated key along with a possible version of the
+// key without any tweaks applied.
+type AggregateKey struct {
+	// FinalKey is the final aggregated key which may include one or more
+	// tweaks applied to it.
+	FinalKey *btcec.PublicKey
+
+	// PreTweakedKey is the aggregated *before* any tweaks have been
+	// applied.  This should be used as the internal key in taproot
+	// contexts.
+	PreTweakedKey *btcec.PublicKey
+}
+
+// AggregateKeys takes a list of possibly unsorted keys and returns a single
+// aggregated key as specified by the musig2 key aggregation algorithm. A nil
+// value can be passed for keyHash, which causes this function to re-derive it.
+// In addition to the combined public key, the parity accumulator and the tweak
+// accumulator are returned as well.
+func AggregateKeys(keys []*btcec.PublicKey, sort bool,
+	keyOpts ...KeyAggOption) (
+	*AggregateKey, *btcec.ModNScalar, *btcec.ModNScalar, error) {
+
+	// First, parse the set of optional signing options.
+	opts := defaultKeyAggOptions()
+	for _, option := range keyOpts {
+		option(opts)
+	}
+
+	// Sort the set of public key so we know we're working with them in
+	// sorted order for all the routines below.
+	if sort {
+		keys = sortKeys(keys)
+	}
+
+	// The caller may provide the hash of all the keys as an optimization
+	// during signing, as it already needs to be computed.
+	if opts.keyHash == nil {
+		opts.keyHash = keyHashFingerprint(keys, sort)
+	}
+
+	// A caller may also specify the unique key index themselves so we
+	// don't need to re-compute it.
+	if opts.uniqueKeyIndex == nil {
+		idx := secondUniqueKeyIndex(keys, sort)
+		opts.uniqueKeyIndex = &idx
+	}
+
+	// For each key, we'll compute the intermediate blinded key: a_i*P_i,
+	// where a_i is the aggregation coefficient for that key, and P_i is
+	// the key itself, then accumulate that (addition) into the main final
+	// key: P = P_1 + P_2 ... P_N.
+	var finalKeyJ btcec.JacobianPoint
+	for _, key := range keys {
+		// Port the key over to Jacobian coordinates as we need it in
+		// this format for the routines below.
+		var keyJ btcec.JacobianPoint
+		key.AsJacobian(&keyJ)
+
+		// Compute the aggregation coefficient for the key, then
+		// multiply it by the key itself: P_i' = a_i*P_i.
+		var tweakedKeyJ btcec.JacobianPoint
+		a := aggregationCoefficient(
+			keys, key, opts.keyHash, *opts.uniqueKeyIndex,
+		)
+		btcec.ScalarMultNonConst(a, &keyJ, &tweakedKeyJ)
+
+		// Finally accumulate this into the final key in an incremental
+		// fashion.
+		btcec.AddNonConst(&finalKeyJ, &tweakedKeyJ, &finalKeyJ)
+	}
+
+	// We'll copy over the key at this point, since this represents the
+	// aggregated key before any tweaks have been applied. This'll be used
+	// as the internal key for script path proofs.
+	finalKeyJ.ToAffine()
+	combinedKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y)
+
+	// At this point, if this is a taproot tweak, then we'll modify the
+	// base tweak value to use the BIP 341 tweak value.
+	if opts.taprootTweak {
+		// Emulate the same behavior as txscript.ComputeTaprootOutputKey
+		// which only operates on the x-only public key.
+		key, _ := schnorr.ParsePubKey(schnorr.SerializePubKey(
+			combinedKey,
+		))
+
+		// We only use the actual tweak bytes if we're not committing
+		// to a BIP-0086 key only spend output. Otherwise, we just
+		// commit to the internal key and an empty byte slice as the
+		// root hash.
+		tweakBytes := []byte{}
+		if !opts.bip86Tweak {
+			tweakBytes = opts.tweaks[0].Tweak[:]
+		}
+
+		// Compute the taproot key tagged hash of:
+		// h_tapTweak(internalKey || scriptRoot). We only do this for
+		// the first one, as you can only specify a single tweak when
+		// using the taproot mode with this API.
+		tapTweakHash := chainhash.TaggedHash(
+			chainhash.TagTapTweak, schnorr.SerializePubKey(key),
+			tweakBytes,
+		)
+		opts.tweaks[0].Tweak = *tapTweakHash
+	}
+
+	var (
+		err       error
+		tweakAcc  btcec.ModNScalar
+		parityAcc btcec.ModNScalar
+	)
+	parityAcc.SetInt(1)
+
+	// In this case we have a set of tweaks, so we'll incrementally apply
+	// each one, until we have our final tweaked key, and the related
+	// accumulators.
+	for i := 1; i <= len(opts.tweaks); i++ {
+		finalKeyJ, parityAcc, tweakAcc, err = tweakKey(
+			finalKeyJ, parityAcc, opts.tweaks[i-1].Tweak, tweakAcc,
+			opts.tweaks[i-1].IsXOnly,
+		)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	}
+
+	finalKeyJ.ToAffine()
+	finalKey := btcec.NewPublicKey(&finalKeyJ.X, &finalKeyJ.Y)
+
+	return &AggregateKey{
+		PreTweakedKey: combinedKey,
+		FinalKey:      finalKey,
+	}, &parityAcc, &tweakAcc, nil
+}
diff --git a/internal/musig2v040/nonces.go b/internal/musig2v040/nonces.go
new file mode 100644
index 000000000..86ca68318
--- /dev/null
+++ b/internal/musig2v040/nonces.go
@@ -0,0 +1,389 @@
+// Copyright 2013-2022 The btcsuite developers
+
+package musig2v040
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/binary"
+	"io"
+
+	"github.com/btcsuite/btcd/btcec/v2"
+	"github.com/btcsuite/btcd/btcec/v2/schnorr"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
+)
+
+const (
+	// PubNonceSize is the size of the public nonces. Each public nonce is
+	// serialized the full compressed encoding, which uses 32 bytes for each
+	// nonce.
+	PubNonceSize = 66
+
+	// SecNonceSize is the size of the secret nonces for musig2. The secret
+	// nonces are the corresponding private keys to the public nonce points.
+	SecNonceSize = 64
+)
+
+var (
+	// NonceAuxTag is the tag used to optionally mix in the secret key with
+	// the set of aux randomness.
+	NonceAuxTag = []byte("MuSig/aux")
+
+	// NonceGenTag is used to generate the value (from a set of required an
+	// optional field) that will be used as the part of the secret nonce.
+	NonceGenTag = []byte("MuSig/nonce")
+
+	byteOrder = binary.BigEndian
+)
+
+// zeroSecNonce is a secret nonce that's all zeroes. This is used to check that
+// we're not attempting to re-use a nonce, and also protect callers from it.
+var zeroSecNonce [SecNonceSize]byte
+
+// Nonces holds the public and secret nonces required for musig2.
+//
+// TODO(roasbeef): methods on this to help w/ parsing, etc?
+type Nonces struct {
+	// PubNonce holds the two 33-byte compressed encoded points that serve
+	// as the public set of nonces.
+	PubNonce [PubNonceSize]byte
+
+	// SecNonce holds the two 32-byte scalar values that are the private
+	// keys to the two public nonces.
+	SecNonce [SecNonceSize]byte
+}
+
+// secNonceToPubNonce takes our two secrete nonces, and produces their two
+// corresponding EC points, serialized in compressed format.
+func secNonceToPubNonce(secNonce [SecNonceSize]byte) [PubNonceSize]byte {
+	var k1Mod, k2Mod btcec.ModNScalar
+	k1Mod.SetByteSlice(secNonce[:btcec.PrivKeyBytesLen])
+	k2Mod.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:])
+
+	var r1, r2 btcec.JacobianPoint
+	btcec.ScalarBaseMultNonConst(&k1Mod, &r1)
+	btcec.ScalarBaseMultNonConst(&k2Mod, &r2)
+
+	// Next, we'll convert the key in jacobian format to a normal public
+	// key expressed in affine coordinates.
+	r1.ToAffine()
+	r2.ToAffine()
+	r1Pub := btcec.NewPublicKey(&r1.X, &r1.Y)
+	r2Pub := btcec.NewPublicKey(&r2.X, &r2.Y)
+
+	var pubNonce [PubNonceSize]byte
+
+	// The public nonces are serialized as: R1 || R2, where both keys are
+	// serialized in compressed format.
+	copy(pubNonce[:], r1Pub.SerializeCompressed())
+	copy(
+		pubNonce[btcec.PubKeyBytesLenCompressed:],
+		r2Pub.SerializeCompressed(),
+	)
+
+	return pubNonce
+}
+
+// NonceGenOption is a function option that allows callers to modify how nonce
+// generation happens.
+type NonceGenOption func(*nonceGenOpts)
+
+// nonceGenOpts is the set of options that control how nonce generation
+// happens.
+type nonceGenOpts struct {
+	// randReader is what we'll use to generate a set of random bytes. If
+	// unspecified, then the normal crypto/rand rand.Read method will be
+	// used in place.
+	randReader io.Reader
+
+	// secretKey is an optional argument that's used to further augment the
+	// generated nonce by xor'ing it with this secret key.
+	secretKey []byte
+
+	// combinedKey is an optional argument that if specified, will be
+	// combined along with the nonce generation.
+	combinedKey []byte
+
+	// msg is an optional argument that will be mixed into the nonce
+	// derivation algorithm.
+	msg []byte
+
+	// auxInput is an optional argument that will be mixed into the nonce
+	// derivation algorithm.
+	auxInput []byte
+}
+
+// cryptoRandAdapter is an adapter struct that allows us to pass in the package
+// level Read function from crypto/rand into a context that accepts an
+// io.Reader.
+type cryptoRandAdapter struct {
+}
+
+// Read implements the io.Reader interface for the crypto/rand package.  By
+// default, we always use the crypto/rand reader, but the caller is able to
+// specify their own generation, which can be useful for deterministic tests.
+func (c *cryptoRandAdapter) Read(p []byte) (n int, err error) {
+	return rand.Read(p)
+}
+
+// defaultNonceGenOpts returns the default set of nonce generation options.
+func defaultNonceGenOpts() *nonceGenOpts {
+	return &nonceGenOpts{
+		randReader: &cryptoRandAdapter{},
+	}
+}
+
+// WithCustomRand allows a caller to use a custom random number generator in
+// place for crypto/rand. This should only really be used to generate
+// determinstic tests.
+func WithCustomRand(r io.Reader) NonceGenOption {
+	return func(o *nonceGenOpts) {
+		o.randReader = r
+	}
+}
+
+// WithNonceSecretKeyAux allows a caller to optionally specify a secret key
+// that should be used to augment the randomness used to generate the nonces.
+func WithNonceSecretKeyAux(secKey *btcec.PrivateKey) NonceGenOption {
+	return func(o *nonceGenOpts) {
+		o.secretKey = secKey.Serialize()
+	}
+}
+
+// WithNonceCombinedKeyAux allows a caller to optionally specify the combined
+// key used in this signing session to further augment the randomness used to
+// generate nonces.
+func WithNonceCombinedKeyAux(combinedKey *btcec.PublicKey) NonceGenOption {
+	return func(o *nonceGenOpts) {
+		o.combinedKey = schnorr.SerializePubKey(combinedKey)
+	}
+}
+
+// WithNonceMessageAux allows a caller to optionally specify a message to be
+// mixed into the randomness generated to create the nonce.
+func WithNonceMessageAux(msg [32]byte) NonceGenOption {
+	return func(o *nonceGenOpts) {
+		o.msg = msg[:]
+	}
+}
+
+// WithNonceAuxInput is a set of auxiliary randomness, similar to BIP 340 that
+// can be used to further augment the nonce generation process.
+func WithNonceAuxInput(aux []byte) NonceGenOption {
+	return func(o *nonceGenOpts) {
+		o.auxInput = aux
+	}
+}
+
+// withCustomOptions allows a caller to pass a complete set of custom
+// nonceGenOpts, without needing to create custom and checked structs such as
+// *btcec.PrivateKey. This is mainly used to match the testcases provided by
+// the MuSig2 BIP.
+func withCustomOptions(customOpts nonceGenOpts) NonceGenOption {
+	return func(o *nonceGenOpts) {
+		o.randReader = customOpts.randReader
+		o.secretKey = customOpts.secretKey
+		o.combinedKey = customOpts.combinedKey
+		o.msg = customOpts.msg
+		o.auxInput = customOpts.auxInput
+	}
+}
+
+// lengthWriter is a function closure that allows a caller to control how the
+// length prefix of a byte slice is written.
+type lengthWriter func(w io.Writer, b []byte) error
+
+// uint8Writer is an implementation of lengthWriter that writes the length of
+// the byte slice using 1 byte.
+func uint8Writer(w io.Writer, b []byte) error {
+	return binary.Write(w, byteOrder, uint8(len(b)))
+}
+
+// uint32Writer is an implementation of lengthWriter that writes the length of
+// the byte slice using 4 bytes.
+func uint32Writer(w io.Writer, b []byte) error {
+	return binary.Write(w, byteOrder, uint32(len(b)))
+}
+
+// writeBytesPrefix is used to write out: len(b) || b, to the passed io.Writer.
+// The lengthWriter function closure is used to allow the caller to specify the
+// precise byte packing of the length.
+func writeBytesPrefix(w io.Writer, b []byte, lenWriter lengthWriter) error {
+	// Write out the length of the byte first, followed by the set of bytes
+	// itself.
+	if err := lenWriter(w, b); err != nil {
+		return err
+	}
+
+	if _, err := w.Write(b); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// genNonceAuxBytes writes out the full byte string used to derive a secret
+// nonce based on some initial randomness as well as the series of optional
+// fields. The byte string used for derivation is:
+//   - tagged_hash("MuSig/nonce", rand || len(aggpk) || aggpk || len(m)
+//     || m || len(in) || in || i).
+//
+// where i is the ith secret nonce being generated.
+func genNonceAuxBytes(rand []byte, i int,
+	opts *nonceGenOpts) (*chainhash.Hash, error) {
+
+	var w bytes.Buffer
+
+	// First, write out the randomness generated in the prior step.
+	if _, err := w.Write(rand); err != nil {
+		return nil, err
+	}
+
+	// Next, we'll write out: len(aggpk) || aggpk.
+	err := writeBytesPrefix(&w, opts.combinedKey, uint8Writer)
+	if err != nil {
+		return nil, err
+	}
+
+	// Next, we'll write out the length prefixed message.
+	err = writeBytesPrefix(&w, opts.msg, uint8Writer)
+	if err != nil {
+		return nil, err
+	}
+
+	// Finally we'll write out the auxiliary input.
+	err = writeBytesPrefix(&w, opts.auxInput, uint32Writer)
+	if err != nil {
+		return nil, err
+	}
+
+	// Next we'll write out the interaction/index number which will
+	// uniquely generate two nonces given the rest of the possibly static
+	// parameters.
+	if err := binary.Write(&w, byteOrder, uint8(i)); err != nil {
+		return nil, err
+	}
+
+	// With the message buffer complete, we'll now derive the tagged hash
+	// using our set of params.
+	return chainhash.TaggedHash(NonceGenTag, w.Bytes()), nil
+}
+
+// GenNonces generates the secret nonces, as well as the public nonces which
+// correspond to an EC point generated using the secret nonce as a private key.
+func GenNonces(options ...NonceGenOption) (*Nonces, error) {
+	opts := defaultNonceGenOpts()
+	for _, opt := range options {
+		opt(opts)
+	}
+
+	// First, we'll start out by generating 32 random bytes drawn from our
+	// CSPRNG.
+	var randBytes [32]byte
+	if _, err := opts.randReader.Read(randBytes[:]); err != nil {
+		return nil, err
+	}
+
+	// If the options contain a secret key, we XOR it with with the tagged
+	// random bytes.
+	if len(opts.secretKey) == 32 {
+		taggedHash := chainhash.TaggedHash(NonceAuxTag, randBytes[:])
+
+		for i := 0; i < chainhash.HashSize; i++ {
+			randBytes[i] = opts.secretKey[i] ^ taggedHash[i]
+		}
+	}
+
+	// Using our randomness and the set of optional params, generate our
+	// two secret nonces: k1 and k2.
+	k1, err := genNonceAuxBytes(randBytes[:], 0, opts)
+	if err != nil {
+		return nil, err
+	}
+	k2, err := genNonceAuxBytes(randBytes[:], 1, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	var k1Mod, k2Mod btcec.ModNScalar
+	k1Mod.SetBytes((*[32]byte)(k1))
+	k2Mod.SetBytes((*[32]byte)(k2))
+
+	// The secret nonces are serialized as the concatenation of the two 32
+	// byte secret nonce values.
+	var nonces Nonces
+	k1Mod.PutBytesUnchecked(nonces.SecNonce[:])
+	k2Mod.PutBytesUnchecked(nonces.SecNonce[btcec.PrivKeyBytesLen:])
+
+	// Next, we'll generate R_1 = k_1*G and R_2 = k_2*G. Along the way we
+	// need to map our nonce values into mod n scalars so we can work with
+	// the btcec API.
+	nonces.PubNonce = secNonceToPubNonce(nonces.SecNonce)
+
+	return &nonces, nil
+}
+
+// AggregateNonces aggregates the set of a pair of public nonces for each party
+// into a single aggregated nonces to be used for multi-signing.
+func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) {
+	// combineNonces is a helper function that aggregates (adds) up a
+	// series of nonces encoded in compressed format. It uses a slicing
+	// function to extra 33 bytes at a time from the packed 2x public
+	// nonces.
+	type nonceSlicer func([PubNonceSize]byte) []byte
+	combineNonces := func(slicer nonceSlicer) (btcec.JacobianPoint, error) {
+		// Convert the set of nonces into jacobian coordinates we can
+		// use to accumulate them all into each other.
+		pubNonceJs := make([]*btcec.JacobianPoint, len(pubNonces))
+		for i, pubNonceBytes := range pubNonces {
+			// Using the slicer, extract just the bytes we need to
+			// decode.
+			var nonceJ btcec.JacobianPoint
+
+			nonceJ, err := btcec.ParseJacobian(slicer(pubNonceBytes))
+			if err != nil {
+				return btcec.JacobianPoint{}, err
+			}
+
+			pubNonceJs[i] = &nonceJ
+		}
+
+		// Now that we have the set of complete nonces, we'll aggregate
+		// them: R = R_i + R_i+1 + ... + R_i+n.
+		var aggregateNonce btcec.JacobianPoint
+		for _, pubNonceJ := range pubNonceJs {
+			btcec.AddNonConst(
+				&aggregateNonce, pubNonceJ, &aggregateNonce,
+			)
+		}
+
+		aggregateNonce.ToAffine()
+		return aggregateNonce, nil
+	}
+
+	// The final nonce public nonce is actually two nonces, one that
+	// aggregate the first nonce of all the parties, and the other that
+	// aggregates the second nonce of all the parties.
+	var finalNonce [PubNonceSize]byte
+	combinedNonce1, err := combineNonces(func(n [PubNonceSize]byte) []byte {
+		return n[:btcec.PubKeyBytesLenCompressed]
+	})
+	if err != nil {
+		return finalNonce, err
+	}
+
+	combinedNonce2, err := combineNonces(func(n [PubNonceSize]byte) []byte {
+		return n[btcec.PubKeyBytesLenCompressed:]
+	})
+	if err != nil {
+		return finalNonce, err
+	}
+
+	copy(finalNonce[:], btcec.JacobianToByteSlice(combinedNonce1))
+	copy(
+		finalNonce[btcec.PubKeyBytesLenCompressed:],
+		btcec.JacobianToByteSlice(combinedNonce2),
+	)
+
+	return finalNonce, nil
+}
diff --git a/internal/musig2v040/sign.go b/internal/musig2v040/sign.go
new file mode 100644
index 000000000..b1236c3a9
--- /dev/null
+++ b/internal/musig2v040/sign.go
@@ -0,0 +1,703 @@
+// Copyright 2013-2022 The btcsuite developers
+
+package musig2v040
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+
+	"github.com/btcsuite/btcd/btcec/v2"
+	"github.com/btcsuite/btcd/btcec/v2/schnorr"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
+	secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
+)
+
+var (
+	// NonceBlindTag is that tag used to construct the value b, which
+	// blinds the second public nonce of each party.
+	NonceBlindTag = []byte("MuSig/noncecoef")
+
+	// ChallengeHashTag is the tag used to construct the challenge hash
+	ChallengeHashTag = []byte("BIP0340/challenge")
+
+	// ErrNoncePointAtInfinity is returned if during signing, the fully
+	// combined public nonce is the point at infinity.
+	ErrNoncePointAtInfinity = fmt.Errorf("signing nonce is the infinity " +
+		"point")
+
+	// ErrPrivKeyZero is returned when the private key for signing is
+	// actually zero.
+	ErrPrivKeyZero = fmt.Errorf("priv key is zero")
+
+	// ErrPartialSigInvalid is returned when a partial is found to be
+	// invalid.
+	ErrPartialSigInvalid = fmt.Errorf("partial signature is invalid")
+
+	// ErrSecretNonceZero is returned when a secret nonce is passed in a
+	// zero.
+	ErrSecretNonceZero = fmt.Errorf("secret nonce is blank")
+)
+
+// infinityPoint is the jacobian representation of the point at infinity.
+var infinityPoint btcec.JacobianPoint
+
+// PartialSignature reprints a partial (s-only) musig2 multi-signature. This
+// isn't a valid schnorr signature by itself, as it needs to be aggregated
+// along with the other partial signatures to be completed.
+type PartialSignature struct {
+	S *btcec.ModNScalar
+
+	R *btcec.PublicKey
+}
+
+// NewPartialSignature returns a new instances of the partial sig struct.
+func NewPartialSignature(s *btcec.ModNScalar,
+	r *btcec.PublicKey) PartialSignature {
+
+	return PartialSignature{
+		S: s,
+		R: r,
+	}
+}
+
+// Encode writes a serialized version of the partial signature to the passed
+// io.Writer
+func (p *PartialSignature) Encode(w io.Writer) error {
+	var sBytes [32]byte
+	p.S.PutBytes(&sBytes)
+
+	if _, err := w.Write(sBytes[:]); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Decode attempts to parse a serialized PartialSignature stored in the passed
+// io reader.
+func (p *PartialSignature) Decode(r io.Reader) error {
+	p.S = new(btcec.ModNScalar)
+
+	var sBytes [32]byte
+	if _, err := io.ReadFull(r, sBytes[:]); err != nil {
+		return nil
+	}
+
+	overflows := p.S.SetBytes(&sBytes)
+	if overflows == 1 {
+		return ErrPartialSigInvalid
+	}
+
+	return nil
+}
+
+// SignOption is a functional option argument that allows callers to modify the
+// way we generate musig2 schnorr signatures.
+type SignOption func(*signOptions)
+
+// signOptions houses the set of functional options that can be used to modify
+// the method used to generate the musig2 partial signature.
+type signOptions struct {
+	// fastSign determines if we'll skip the check at the end of the
+	// routine where we attempt to verify the produced signature.
+	fastSign bool
+
+	// sortKeys determines if the set of keys should be sorted before doing
+	// key aggregation.
+	sortKeys bool
+
+	// tweaks specifies a series of tweaks to be applied to the aggregated
+	// public key, which also partially carries over into the signing
+	// process.
+	tweaks []KeyTweakDesc
+
+	// taprootTweak specifies a taproot specific tweak.  of the tweaks
+	// specified above. Normally we'd just apply the raw 32 byte tweak, but
+	// for taproot, we first need to compute the aggregated key before
+	// tweaking, and then use it as the internal key. This is required as
+	// the taproot tweak also commits to the public key, which in this case
+	// is the aggregated key before the tweak.
+	taprootTweak []byte
+
+	// bip86Tweak specifies that the taproot tweak should be done in a BIP
+	// 86 style, where we don't expect an actual tweak and instead just
+	// commit to the public key itself.
+	bip86Tweak bool
+}
+
+// defaultSignOptions returns the default set of signing operations.
+func defaultSignOptions() *signOptions {
+	return &signOptions{}
+}
+
+// WithFastSign forces signing to skip the extra verification step at the end.
+// Performance sensitive applications may opt to use this option to speed up
+// the signing operation.
+func WithFastSign() SignOption {
+	return func(o *signOptions) {
+		o.fastSign = true
+	}
+}
+
+// WithSortedKeys determines if the set of signing public keys are to be sorted
+// or not before doing key aggregation.
+func WithSortedKeys() SignOption {
+	return func(o *signOptions) {
+		o.sortKeys = true
+	}
+}
+
+// WithTweaks determines if the aggregated public key used should apply a
+// series of tweaks before key aggregation.
+func WithTweaks(tweaks ...KeyTweakDesc) SignOption {
+	return func(o *signOptions) {
+		o.tweaks = tweaks
+	}
+}
+
+// WithTaprootSignTweak allows a caller to specify a tweak that should be used
+// in a bip 340 manner when signing. This differs from WithTweaks as the tweak
+// will be assumed to always be x-only and the intermediate aggregate key
+// before tweaking will be used to generate part of the tweak (as the taproot
+// tweak also commits to the internal key).
+//
+// This option should be used in the taproot context to create a valid
+// signature for the keypath spend for taproot, when the output key is actually
+// committing to a script path, or some other data.
+func WithTaprootSignTweak(scriptRoot []byte) SignOption {
+	return func(o *signOptions) {
+		o.taprootTweak = scriptRoot
+	}
+}
+
+// WithBip86SignTweak allows a caller to specify a tweak that should be used in
+// a bip 340 manner when signing, factoring in BIP 86 as well. This differs
+// from WithTaprootSignTweak as no true script root will be committed to,
+// instead we just commit to the internal key.
+//
+// This option should be used in the taproot context to create a valid
+// signature for the keypath spend for taproot, when the output key was
+// generated using BIP 86.
+func WithBip86SignTweak() SignOption {
+	return func(o *signOptions) {
+		o.bip86Tweak = true
+	}
+}
+
+// Sign generates a musig2 partial signature given the passed key set, secret
+// nonce, public nonce, and private keys. This method returns an error if the
+// generated nonces are either too large, or end up mapping to the point at
+// infinity.
+func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
+	combinedNonce [PubNonceSize]byte, pubKeys []*btcec.PublicKey,
+	msg [32]byte, signOpts ...SignOption) (*PartialSignature, error) {
+
+	// First, parse the set of optional signing options.
+	opts := defaultSignOptions()
+	for _, option := range signOpts {
+		option(opts)
+	}
+
+	// Compute the hash of all the keys here as we'll need it do aggregate
+	// the keys and also at the final step of signing.
+	keysHash := keyHashFingerprint(pubKeys, opts.sortKeys)
+	uniqueKeyIndex := secondUniqueKeyIndex(pubKeys, opts.sortKeys)
+
+	keyAggOpts := []KeyAggOption{
+		WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
+	}
+	switch {
+	case opts.bip86Tweak:
+		keyAggOpts = append(
+			keyAggOpts, WithBIP86KeyTweak(),
+		)
+	case opts.taprootTweak != nil:
+		keyAggOpts = append(
+			keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
+		)
+	case len(opts.tweaks) != 0:
+		keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
+	}
+
+	// Next we'll construct the aggregated public key based on the set of
+	// signers.
+	combinedKey, parityAcc, _, err := AggregateKeys(
+		pubKeys, opts.sortKeys, keyAggOpts...,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	// Next we'll compute the value b, that blinds our second public
+	// nonce:
+	//  * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
+	var (
+		nonceMsgBuf  bytes.Buffer
+		nonceBlinder btcec.ModNScalar
+	)
+	nonceMsgBuf.Write(combinedNonce[:])
+	nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
+	nonceMsgBuf.Write(msg[:])
+	nonceBlindHash := chainhash.TaggedHash(
+		NonceBlindTag, nonceMsgBuf.Bytes(),
+	)
+	nonceBlinder.SetByteSlice(nonceBlindHash[:])
+
+	// Next, we'll parse the public nonces into R1 and R2.
+	r1J, err := btcec.ParseJacobian(
+		combinedNonce[:btcec.PubKeyBytesLenCompressed],
+	)
+	if err != nil {
+		return nil, err
+	}
+	r2J, err := btcec.ParseJacobian(
+		combinedNonce[btcec.PubKeyBytesLenCompressed:],
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	// With our nonce blinding value, we'll now combine both the public
+	// nonces, using the blinding factor to tweak the second nonce:
+	//  * R = R_1 + b*R_2
+	var nonce btcec.JacobianPoint
+	btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
+	btcec.AddNonConst(&r1J, &r2J, &nonce)
+
+	// If the combined nonce it eh point at infinity, then we'll bail out.
+	if nonce == infinityPoint {
+		G := btcec.Generator()
+		G.AsJacobian(&nonce)
+	}
+
+	// Next we'll parse out our two secret nonces, which we'll be using in
+	// the core signing process below.
+	var k1, k2 btcec.ModNScalar
+	k1.SetByteSlice(secNonce[:btcec.PrivKeyBytesLen])
+	k2.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:])
+
+	if k1.IsZero() || k2.IsZero() {
+		return nil, ErrSecretNonceZero
+	}
+
+	nonce.ToAffine()
+
+	nonceKey := btcec.NewPublicKey(&nonce.X, &nonce.Y)
+
+	// If the nonce R has an odd y coordinate, then we'll negate both our
+	// secret nonces.
+	if nonce.Y.IsOdd() {
+		k1.Negate()
+		k2.Negate()
+	}
+
+	privKeyScalar := privKey.Key
+	if privKeyScalar.IsZero() {
+		return nil, ErrPrivKeyZero
+	}
+
+	pubKey := privKey.PubKey()
+	pubKeyYIsOdd := func() bool {
+		pubKeyBytes := pubKey.SerializeCompressed()
+		return pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd
+	}()
+	combinedKeyYIsOdd := func() bool {
+		combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed()
+		return combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd
+	}()
+
+	// Next we'll compute our two parity factors for Q the combined public
+	// key, and P, the public key we're signing with. If the keys are odd,
+	// then we'll negate them.
+	parityCombinedKey := new(btcec.ModNScalar).SetInt(1)
+	paritySignKey := new(btcec.ModNScalar).SetInt(1)
+	if combinedKeyYIsOdd {
+		parityCombinedKey.Negate()
+	}
+	if pubKeyYIsOdd {
+		paritySignKey.Negate()
+	}
+
+	// Before we sign below, we'll multiply by our various parity factors
+	// to ensure that the signing key is properly negated (if necessary):
+	//  * d = gv⋅gaccv⋅gp⋅d'
+	privKeyScalar.Mul(parityCombinedKey).Mul(paritySignKey).Mul(parityAcc)
+
+	// Next we'll create the challenge hash that commits to the combined
+	// nonce, combined public key and also the message:
+	// * e = H(tag=ChallengeHashTag, R || Q || m) mod n
+	var challengeMsg bytes.Buffer
+	challengeMsg.Write(schnorr.SerializePubKey(nonceKey))
+	challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
+	challengeMsg.Write(msg[:])
+	challengeBytes := chainhash.TaggedHash(
+		ChallengeHashTag, challengeMsg.Bytes(),
+	)
+	var e btcec.ModNScalar
+	e.SetByteSlice(challengeBytes[:])
+
+	// Next, we'll compute a, our aggregation coefficient for the key that
+	// we're signing with.
+	a := aggregationCoefficient(pubKeys, pubKey, keysHash, uniqueKeyIndex)
+
+	// With mu constructed, we can finally generate our partial signature
+	// as: s = (k1_1 + b*k_2 + e*a*d) mod n.
+	s := new(btcec.ModNScalar)
+	s.Add(&k1).Add(k2.Mul(&nonceBlinder)).Add(e.Mul(a).Mul(&privKeyScalar))
+
+	sig := NewPartialSignature(s, nonceKey)
+
+	// If we're not in fast sign mode, then we'll also validate our partial
+	// signature.
+	if !opts.fastSign {
+		pubNonce := secNonceToPubNonce(secNonce)
+		sigValid := sig.Verify(
+			pubNonce, combinedNonce, pubKeys, pubKey, msg,
+			signOpts...,
+		)
+		if !sigValid {
+			return nil, fmt.Errorf("sig is invalid!")
+		}
+	}
+
+	return &sig, nil
+}
+
+// Verify implements partial signature verification given the public nonce for
+// the signer, aggregate nonce, signer set and finally the message being
+// signed.
+func (p *PartialSignature) Verify(pubNonce [PubNonceSize]byte,
+	combinedNonce [PubNonceSize]byte, keySet []*btcec.PublicKey,
+	signingKey *btcec.PublicKey, msg [32]byte, signOpts ...SignOption) bool {
+
+	pubKey := schnorr.SerializePubKey(signingKey)
+
+	return verifyPartialSig(
+		p, pubNonce, combinedNonce, keySet, pubKey, msg, signOpts...,
+	) == nil
+}
+
+// verifyPartialSig attempts to verify a partial schnorr signature given the
+// necessary parameters. This is the internal version of Verify that returns
+// detailed errors.  signed.
+func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte,
+	combinedNonce [PubNonceSize]byte, keySet []*btcec.PublicKey,
+	pubKey []byte, msg [32]byte, signOpts ...SignOption) error {
+
+	opts := defaultSignOptions()
+	for _, option := range signOpts {
+		option(opts)
+	}
+
+	// First we'll map the internal partial signature back into something
+	// we can manipulate.
+	s := partialSig.S
+
+	// Next we'll parse out the two public nonces into something we can
+	// use.
+	//
+
+	// Compute the hash of all the keys here as we'll need it do aggregate
+	// the keys and also at the final step of verification.
+	keysHash := keyHashFingerprint(keySet, opts.sortKeys)
+	uniqueKeyIndex := secondUniqueKeyIndex(keySet, opts.sortKeys)
+
+	keyAggOpts := []KeyAggOption{
+		WithKeysHash(keysHash), WithUniqueKeyIndex(uniqueKeyIndex),
+	}
+	switch {
+	case opts.bip86Tweak:
+		keyAggOpts = append(
+			keyAggOpts, WithBIP86KeyTweak(),
+		)
+	case opts.taprootTweak != nil:
+		keyAggOpts = append(
+			keyAggOpts, WithTaprootKeyTweak(opts.taprootTweak),
+		)
+	case len(opts.tweaks) != 0:
+		keyAggOpts = append(keyAggOpts, WithKeyTweaks(opts.tweaks...))
+	}
+
+	// Next we'll construct the aggregated public key based on the set of
+	// signers.
+	combinedKey, parityAcc, _, err := AggregateKeys(
+		keySet, opts.sortKeys, keyAggOpts...,
+	)
+	if err != nil {
+		return err
+	}
+
+	// Next we'll compute the value b, that blinds our second public
+	// nonce:
+	//  * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
+	var (
+		nonceMsgBuf  bytes.Buffer
+		nonceBlinder btcec.ModNScalar
+	)
+	nonceMsgBuf.Write(combinedNonce[:])
+	nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
+	nonceMsgBuf.Write(msg[:])
+	nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes())
+	nonceBlinder.SetByteSlice(nonceBlindHash[:])
+
+	r1J, err := btcec.ParseJacobian(
+		combinedNonce[:btcec.PubKeyBytesLenCompressed],
+	)
+	if err != nil {
+		return err
+	}
+	r2J, err := btcec.ParseJacobian(
+		combinedNonce[btcec.PubKeyBytesLenCompressed:],
+	)
+	if err != nil {
+		return err
+	}
+
+	// With our nonce blinding value, we'll now combine both the public
+	// nonces, using the blinding factor to tweak the second nonce:
+	//  * R = R_1 + b*R_2
+
+	var nonce btcec.JacobianPoint
+	btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
+	btcec.AddNonConst(&r1J, &r2J, &nonce)
+
+	// Next, we'll parse out the set of public nonces this signer used to
+	// generate the signature.
+	pubNonce1J, err := btcec.ParseJacobian(
+		pubNonce[:btcec.PubKeyBytesLenCompressed],
+	)
+	if err != nil {
+		return err
+	}
+	pubNonce2J, err := btcec.ParseJacobian(
+		pubNonce[btcec.PubKeyBytesLenCompressed:],
+	)
+	if err != nil {
+		return err
+	}
+
+	// If the nonce is the infinity point we set it to the Generator.
+	if nonce == infinityPoint {
+		btcec.GeneratorJacobian(&nonce)
+	} else {
+		nonce.ToAffine()
+	}
+
+	// We'll perform a similar aggregation and blinding operator as we did
+	// above for the combined nonces: R' = R_1' + b*R_2'.
+	var pubNonceJ btcec.JacobianPoint
+
+	btcec.ScalarMultNonConst(&nonceBlinder, &pubNonce2J, &pubNonce2J)
+	btcec.AddNonConst(&pubNonce1J, &pubNonce2J, &pubNonceJ)
+
+	pubNonceJ.ToAffine()
+
+	// If the combined nonce used in the challenge hash has an odd y
+	// coordinate, then we'll negate our final public nonce.
+	if nonce.Y.IsOdd() {
+		pubNonceJ.Y.Negate(1)
+		pubNonceJ.Y.Normalize()
+	}
+
+	// Next we'll create the challenge hash that commits to the combined
+	// nonce, combined public key and also the message:
+	//  * e = H(tag=ChallengeHashTag, R || Q || m) mod n
+	var challengeMsg bytes.Buffer
+	challengeMsg.Write(schnorr.SerializePubKey(btcec.NewPublicKey(
+		&nonce.X, &nonce.Y,
+	)))
+	challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
+	challengeMsg.Write(msg[:])
+	challengeBytes := chainhash.TaggedHash(
+		ChallengeHashTag, challengeMsg.Bytes(),
+	)
+	var e btcec.ModNScalar
+	e.SetByteSlice(challengeBytes[:])
+
+	signingKey, err := schnorr.ParsePubKey(pubKey)
+	if err != nil {
+		return err
+	}
+
+	// Next, we'll compute a, our aggregation coefficient for the key that
+	// we're signing with.
+	a := aggregationCoefficient(keySet, signingKey, keysHash, uniqueKeyIndex)
+
+	// If the combined key has an odd y coordinate, then we'll negate
+	// parity factor for the signing key.
+	paritySignKey := new(btcec.ModNScalar).SetInt(1)
+	combinedKeyBytes := combinedKey.FinalKey.SerializeCompressed()
+	if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd {
+		paritySignKey.Negate()
+	}
+
+	// Next, we'll construct the final parity factor by multiplying the
+	// sign key parity factor with the accumulated parity factor for all
+	// the keys.
+	finalParityFactor := paritySignKey.Mul(parityAcc)
+
+	// Now we'll multiply the parity factor by our signing key, which'll
+	// take care of the amount of negation needed.
+	var signKeyJ btcec.JacobianPoint
+	signingKey.AsJacobian(&signKeyJ)
+	btcec.ScalarMultNonConst(finalParityFactor, &signKeyJ, &signKeyJ)
+
+	// In the final set, we'll check that: s*G == R' + e*a*P.
+	var sG, rP btcec.JacobianPoint
+	btcec.ScalarBaseMultNonConst(s, &sG)
+	btcec.ScalarMultNonConst(e.Mul(a), &signKeyJ, &rP)
+	btcec.AddNonConst(&rP, &pubNonceJ, &rP)
+
+	sG.ToAffine()
+	rP.ToAffine()
+
+	if sG != rP {
+		return ErrPartialSigInvalid
+	}
+
+	return nil
+}
+
+// CombineOption is a functional option argument that allows callers to modify the
+// way we combine musig2 schnorr signatures.
+type CombineOption func(*combineOptions)
+
+// combineOptions houses the set of functional options that can be used to
+// modify the method used to combine the musig2 partial signatures.
+type combineOptions struct {
+	msg [32]byte
+
+	combinedKey *btcec.PublicKey
+
+	tweakAcc *btcec.ModNScalar
+}
+
+// defaultCombineOptions returns the default set of signing operations.
+func defaultCombineOptions() *combineOptions {
+	return &combineOptions{}
+}
+
+// WithTweakedCombine is a functional option that allows callers to specify
+// that the signature was produced using a tweaked aggregated public key. In
+// order to properly aggregate the partial signatures, the caller must specify
+// enough information to reconstruct the challenge, and also the final
+// accumulated tweak value.
+func WithTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
+	tweaks []KeyTweakDesc, sort bool) CombineOption {
+
+	return func(o *combineOptions) {
+		combinedKey, _, tweakAcc, _ := AggregateKeys(
+			keys, sort, WithKeyTweaks(tweaks...),
+		)
+
+		o.msg = msg
+		o.combinedKey = combinedKey.FinalKey
+		o.tweakAcc = tweakAcc
+	}
+}
+
+// WithTaprootTweakedCombine is similar to the WithTweakedCombine option, but
+// assumes a BIP 341 context where the final tweaked key is to be used as the
+// output key, where the internal key is the aggregated key pre-tweak.
+//
+// This option should be used over WithTweakedCombine when attempting to
+// aggregate signatures for a top-level taproot keyspend, where the output key
+// commits to a script root.
+func WithTaprootTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
+	scriptRoot []byte, sort bool) CombineOption {
+
+	return func(o *combineOptions) {
+		combinedKey, _, tweakAcc, _ := AggregateKeys(
+			keys, sort, WithTaprootKeyTweak(scriptRoot),
+		)
+
+		o.msg = msg
+		o.combinedKey = combinedKey.FinalKey
+		o.tweakAcc = tweakAcc
+	}
+}
+
+// WithBip86TweakedCombine is similar to the WithTaprootTweakedCombine option,
+// but assumes a BIP 341 + BIP 86 context where the final tweaked key is to be
+// used as the output key, where the internal key is the aggregated key
+// pre-tweak.
+//
+// This option should be used over WithTaprootTweakedCombine when attempting to
+// aggregate signatures for a top-level taproot keyspend, where the output key
+// was generated using BIP 86.
+func WithBip86TweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
+	sort bool) CombineOption {
+
+	return func(o *combineOptions) {
+		combinedKey, _, tweakAcc, _ := AggregateKeys(
+			keys, sort, WithBIP86KeyTweak(),
+		)
+
+		o.msg = msg
+		o.combinedKey = combinedKey.FinalKey
+		o.tweakAcc = tweakAcc
+	}
+}
+
+// CombineSigs combines the set of public keys given the final aggregated
+// nonce, and the series of partial signatures for each nonce.
+func CombineSigs(combinedNonce *btcec.PublicKey,
+	partialSigs []*PartialSignature,
+	combineOpts ...CombineOption) *schnorr.Signature {
+
+	// First, parse the set of optional combine options.
+	opts := defaultCombineOptions()
+	for _, option := range combineOpts {
+		option(opts)
+	}
+
+	// If signer keys and tweaks are specified, then we need to carry out
+	// some intermediate steps before we can combine the signature.
+	var tweakProduct *btcec.ModNScalar
+	if opts.combinedKey != nil && opts.tweakAcc != nil {
+		// Next, we'll construct the parity factor of the combined key,
+		// negating it if the combined key has an even y coordinate.
+		parityFactor := new(btcec.ModNScalar).SetInt(1)
+		combinedKeyBytes := opts.combinedKey.SerializeCompressed()
+		if combinedKeyBytes[0] == secp.PubKeyFormatCompressedOdd {
+			parityFactor.Negate()
+		}
+
+		// Next we'll reconstruct e the challenge has based on the
+		// nonce and combined public key.
+		//  * e = H(tag=ChallengeHashTag, R || Q || m) mod n
+		var challengeMsg bytes.Buffer
+		challengeMsg.Write(schnorr.SerializePubKey(combinedNonce))
+		challengeMsg.Write(schnorr.SerializePubKey(opts.combinedKey))
+		challengeMsg.Write(opts.msg[:])
+		challengeBytes := chainhash.TaggedHash(
+			ChallengeHashTag, challengeMsg.Bytes(),
+		)
+		var e btcec.ModNScalar
+		e.SetByteSlice(challengeBytes[:])
+
+		tweakProduct = new(btcec.ModNScalar).Set(&e)
+		tweakProduct.Mul(opts.tweakAcc).Mul(parityFactor)
+	}
+
+	// Finally, the tweak factor also needs to be re-computed as well.
+	var combinedSig btcec.ModNScalar
+	for _, partialSig := range partialSigs {
+		combinedSig.Add(partialSig.S)
+	}
+
+	// If the tweak product was set above, then we'll need to add the value
+	// at the very end in order to produce a valid signature under the
+	// final tweaked key.
+	if tweakProduct != nil {
+		combinedSig.Add(tweakProduct)
+	}
+
+	// TODO(roasbeef): less verbose way to get the x coord...
+	var nonceJ btcec.JacobianPoint
+	combinedNonce.AsJacobian(&nonceJ)
+	nonceJ.ToAffine()
+
+	return schnorr.NewSignature(&nonceJ.X, &combinedSig)
+}
diff --git a/lnrpc/signrpc/signer.pb.go b/lnrpc/signrpc/signer.pb.go
index f7952d632..8fbc5862f 100644
--- a/lnrpc/signrpc/signer.pb.go
+++ b/lnrpc/signrpc/signer.pb.go
@@ -80,6 +80,62 @@ func (SignMethod) EnumDescriptor() ([]byte, []int) {
 	return file_signrpc_signer_proto_rawDescGZIP(), []int{0}
 }
 
+type MuSig2Version int32
+
+const (
+	// The default value on the RPC is zero for enums so we need to represent an
+	// invalid/undefined version by default to make sure clients upgrade their
+	// software to set the version explicitly.
+	MuSig2Version_MUSIG2_VERSION_UNDEFINED MuSig2Version = 0
+	// The version of MuSig2 that lnd 0.15.x shipped with, which corresponds to the
+	// version v0.4.0 of the MuSig2 BIP draft.
+	MuSig2Version_MUSIG2_VERSION_V040 MuSig2Version = 1
+	// The current version of MuSig2 which corresponds to the version v1.0.0rc2 of
+	// the MuSig2 BIP draft.
+	MuSig2Version_MUSIG2_VERSION_V100RC2 MuSig2Version = 2
+)
+
+// Enum value maps for MuSig2Version.
+var (
+	MuSig2Version_name = map[int32]string{
+		0: "MUSIG2_VERSION_UNDEFINED",
+		1: "MUSIG2_VERSION_V040",
+		2: "MUSIG2_VERSION_V100RC2",
+	}
+	MuSig2Version_value = map[string]int32{
+		"MUSIG2_VERSION_UNDEFINED": 0,
+		"MUSIG2_VERSION_V040":      1,
+		"MUSIG2_VERSION_V100RC2":   2,
+	}
+)
+
+func (x MuSig2Version) Enum() *MuSig2Version {
+	p := new(MuSig2Version)
+	*p = x
+	return p
+}
+
+func (x MuSig2Version) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (MuSig2Version) Descriptor() protoreflect.EnumDescriptor {
+	return file_signrpc_signer_proto_enumTypes[1].Descriptor()
+}
+
+func (MuSig2Version) Type() protoreflect.EnumType {
+	return &file_signrpc_signer_proto_enumTypes[1]
+}
+
+func (x MuSig2Version) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use MuSig2Version.Descriptor instead.
+func (MuSig2Version) EnumDescriptor() ([]byte, []int) {
+	return file_signrpc_signer_proto_rawDescGZIP(), []int{1}
+}
+
 type KeyLocator struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1150,10 +1206,10 @@ type MuSig2CombineKeysRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// A list of all public keys (serialized in 32-byte x-only format!)
-	// participating in the signing session. The list will always be sorted
-	// lexicographically internally. This must include the local key which is
-	// described by the above key_loc.
+	// A list of all public keys (serialized in 32-byte x-only format for v0.4.0
+	// and 33-byte compressed format for v1.0.0rc2!) participating in the signing
+	// session. The list will always be sorted lexicographically internally. This
+	// must include the local key which is described by the above key_loc.
 	AllSignerPubkeys [][]byte `protobuf:"bytes,1,rep,name=all_signer_pubkeys,json=allSignerPubkeys,proto3" json:"all_signer_pubkeys,omitempty"`
 	// A series of optional generic tweaks to be applied to the the aggregated
 	// public key.
@@ -1162,6 +1218,11 @@ type MuSig2CombineKeysRequest struct {
 	// combined key will be used as the main taproot key of a taproot output
 	// on-chain.
 	TaprootTweak *TaprootTweakDesc `protobuf:"bytes,3,opt,name=taproot_tweak,json=taprootTweak,proto3" json:"taproot_tweak,omitempty"`
+	// The mandatory version of the MuSig2 BIP draft to use. This is necessary to
+	// differentiate between the changes that were made to the BIP while this
+	// experimental RPC was already released. Some of those changes affect how the
+	// combined key and nonces are created.
+	Version MuSig2Version `protobuf:"varint,4,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"`
 }
 
 func (x *MuSig2CombineKeysRequest) Reset() {
@@ -1217,6 +1278,13 @@ func (x *MuSig2CombineKeysRequest) GetTaprootTweak() *TaprootTweakDesc {
 	return nil
 }
 
+func (x *MuSig2CombineKeysRequest) GetVersion() MuSig2Version {
+	if x != nil {
+		return x.Version
+	}
+	return MuSig2Version_MUSIG2_VERSION_UNDEFINED
+}
+
 type MuSig2CombineKeysResponse struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1231,6 +1299,8 @@ type MuSig2CombineKeysResponse struct {
 	// internal key that needs to be put into the witness if the script spend path
 	// is used.
 	TaprootInternalKey []byte `protobuf:"bytes,2,opt,name=taproot_internal_key,json=taprootInternalKey,proto3" json:"taproot_internal_key,omitempty"`
+	// The version of the MuSig2 BIP that was used to combine the keys.
+	Version MuSig2Version `protobuf:"varint,4,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"`
 }
 
 func (x *MuSig2CombineKeysResponse) Reset() {
@@ -1279,6 +1349,13 @@ func (x *MuSig2CombineKeysResponse) GetTaprootInternalKey() []byte {
 	return nil
 }
 
+func (x *MuSig2CombineKeysResponse) GetVersion() MuSig2Version {
+	if x != nil {
+		return x.Version
+	}
+	return MuSig2Version_MUSIG2_VERSION_UNDEFINED
+}
+
 type MuSig2SessionRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1286,10 +1363,10 @@ type MuSig2SessionRequest struct {
 
 	// The key locator that identifies which key to use for signing.
 	KeyLoc *KeyLocator `protobuf:"bytes,1,opt,name=key_loc,json=keyLoc,proto3" json:"key_loc,omitempty"`
-	// A list of all public keys (serialized in 32-byte x-only format!)
-	// participating in the signing session. The list will always be sorted
-	// lexicographically internally. This must include the local key which is
-	// described by the above key_loc.
+	// A list of all public keys (serialized in 32-byte x-only format for v0.4.0
+	// and 33-byte compressed format for v1.0.0rc2!) participating in the signing
+	// session. The list will always be sorted lexicographically internally. This
+	// must include the local key which is described by the above key_loc.
 	AllSignerPubkeys [][]byte `protobuf:"bytes,2,rep,name=all_signer_pubkeys,json=allSignerPubkeys,proto3" json:"all_signer_pubkeys,omitempty"`
 	// An optional list of all public nonces of other signing participants that
 	// might already be known.
@@ -1301,6 +1378,11 @@ type MuSig2SessionRequest struct {
 	// combined key will be used as the main taproot key of a taproot output
 	// on-chain.
 	TaprootTweak *TaprootTweakDesc `protobuf:"bytes,5,opt,name=taproot_tweak,json=taprootTweak,proto3" json:"taproot_tweak,omitempty"`
+	// The mandatory version of the MuSig2 BIP draft to use. This is necessary to
+	// differentiate between the changes that were made to the BIP while this
+	// experimental RPC was already released. Some of those changes affect how the
+	// combined key and nonces are created.
+	Version MuSig2Version `protobuf:"varint,6,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"`
 }
 
 func (x *MuSig2SessionRequest) Reset() {
@@ -1370,6 +1452,13 @@ func (x *MuSig2SessionRequest) GetTaprootTweak() *TaprootTweakDesc {
 	return nil
 }
 
+func (x *MuSig2SessionRequest) GetVersion() MuSig2Version {
+	if x != nil {
+		return x.Version
+	}
+	return MuSig2Version_MUSIG2_VERSION_UNDEFINED
+}
+
 type MuSig2SessionResponse struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1395,6 +1484,8 @@ type MuSig2SessionResponse struct {
 	// Indicates whether all nonces required to start the signing process are known
 	// now.
 	HaveAllNonces bool `protobuf:"varint,5,opt,name=have_all_nonces,json=haveAllNonces,proto3" json:"have_all_nonces,omitempty"`
+	// The version of the MuSig2 BIP that was used to create the session.
+	Version MuSig2Version `protobuf:"varint,6,opt,name=version,proto3,enum=signrpc.MuSig2Version" json:"version,omitempty"`
 }
 
 func (x *MuSig2SessionResponse) Reset() {
@@ -1464,6 +1555,13 @@ func (x *MuSig2SessionResponse) GetHaveAllNonces() bool {
 	return false
 }
 
+func (x *MuSig2SessionResponse) GetVersion() MuSig2Version {
+	if x != nil {
+		return x.Version
+	}
+	return MuSig2Version_MUSIG2_VERSION_UNDEFINED
+}
+
 type MuSig2RegisterNoncesRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2005,7 +2103,7 @@ var file_signrpc_signer_proto_rawDesc = []byte{
 	0x52, 0x0a, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x24, 0x0a, 0x0e,
 	0x6b, 0x65, 0x79, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02,
 	0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x4f, 0x6e,
-	0x6c, 0x79, 0x22, 0xb4, 0x01, 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d,
+	0x6c, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d,
 	0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
 	0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75,
 	0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x6c, 0x6c,
@@ -2016,158 +2114,177 @@ var file_signrpc_signer_proto_rawDesc = []byte{
 	0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
 	0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x72, 0x6f,
 	0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x0c, 0x74, 0x61, 0x70,
-	0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x22, 0x70, 0x0a, 0x19, 0x4d, 0x75, 0x53,
-	0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e,
-	0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f,
-	0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x61, 0x70,
-	0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65,
-	0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74,
-	0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x22, 0x9b, 0x02, 0x0a, 0x14,
-	0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e,
-	0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x06, 0x6b, 0x65, 0x79, 0x4c,
-	0x6f, 0x63, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72,
-	0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x10,
-	0x61, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73,
-	0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72,
-	0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03,
-	0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x65,
-	0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x2a, 0x0a,
-	0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e,
-	0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73,
-	0x63, 0x52, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x74, 0x61, 0x70,
-	0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x70, 0x72, 0x6f,
-	0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x0c, 0x74, 0x61, 0x70,
-	0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x22, 0xe3, 0x01, 0x0a, 0x15, 0x4d, 0x75,
-	0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
-	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
-	0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x6b,
-	0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e,
-	0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74,
-	0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x49, 0x6e, 0x74, 0x65,
-	0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x13, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
-	0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x04,
-	0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69,
-	0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x61, 0x76, 0x65, 0x5f,
-	0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
-	0x52, 0x0d, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22,
-	0x79, 0x0a, 0x1b, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
-	0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d,
-	0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3b, 0x0a,
-	0x1a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75,
-	0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
-	0x0c, 0x52, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75,
-	0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x46, 0x0a, 0x1c, 0x4d, 0x75,
-	0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63,
-	0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x61,
-	0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c, 0x4e, 0x6f, 0x6e, 0x63,
-	0x65, 0x73, 0x22, 0x73, 0x0a, 0x11, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e,
+	0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x12, 0x30, 0x0a, 0x07, 0x76, 0x65, 0x72,
+	0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67,
+	0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x56, 0x65, 0x72, 0x73, 0x69,
+	0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa2, 0x01, 0x0a, 0x19,
+	0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79,
+	0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d,
+	0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
+	0x0b, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14,
+	0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+	0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x61, 0x70, 0x72,
+	0x6f, 0x6f, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x30,
+	0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32,
+	0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+	0x22, 0xcd, 0x02, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69,
+	0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x07, 0x6b, 0x65, 0x79,
+	0x5f, 0x6c, 0x6f, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x69, 0x67,
+	0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52,
+	0x06, 0x6b, 0x65, 0x79, 0x4c, 0x6f, 0x63, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x5f, 0x73,
+	0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0c, 0x52, 0x10, 0x61, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75,
+	0x62, 0x6b, 0x65, 0x79, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x73,
+	0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f, 0x6e,
+	0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72,
+	0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63,
+	0x65, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x77, 0x65,
+	0x61, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x52, 0x06, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x73, 0x12, 0x3e,
+	0x0a, 0x0d, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x77, 0x65, 0x61, 0x6b, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e,
+	0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x44, 0x65, 0x73, 0x63,
+	0x52, 0x0c, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x54, 0x77, 0x65, 0x61, 0x6b, 0x12, 0x30,
+	0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32,
+	0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+	0x22, 0x95, 0x02, 0x0a, 0x15, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69,
+	0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65,
+	0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09,
+	0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d,
+	0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52,
+	0x0b, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14,
+	0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+	0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x74, 0x61, 0x70, 0x72,
+	0x6f, 0x6f, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x12, 0x2e,
+	0x0a, 0x13, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e,
+	0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x6c, 0x6f, 0x63,
+	0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26,
+	0x0a, 0x0f, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65,
+	0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c,
+	0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70,
+	0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52,
+	0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x1b, 0x4d, 0x75, 0x53, 0x69,
+	0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73,
 	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69,
 	0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73,
-	0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
-	0x65, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d,
-	0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a,
-	0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
-	0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x22, 0x4c, 0x0a, 0x12, 0x4d, 0x75, 0x53, 0x69, 0x67,
-	0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a,
-	0x17, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73,
-	0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15,
-	0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e,
-	0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x72, 0x0a, 0x17, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43,
-	0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f,
+	0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6e, 0x6f,
+	0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x17, 0x6f, 0x74, 0x68, 0x65,
+	0x72, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4e, 0x6f, 0x6e,
+	0x63, 0x65, 0x73, 0x22, 0x46, 0x0a, 0x1c, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67,
+	0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f,
+	0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61,
+	0x76, 0x65, 0x41, 0x6c, 0x6c, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x11, 0x4d,
+	0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
 	0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01,
 	0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12,
-	0x38, 0x0a, 0x18, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c,
-	0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-	0x0c, 0x52, 0x16, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53,
-	0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x18, 0x4d, 0x75, 0x53,
-	0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c,
-	0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61,
-	0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x73,
-	0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e,
-	0x66, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x35,
-	0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
-	0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73,
-	0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43,
-	0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x9c,
-	0x01, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a,
-	0x16, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x57, 0x49, 0x54,
-	0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30, 0x10, 0x00, 0x12, 0x29, 0x0a, 0x25, 0x53, 0x49, 0x47,
-	0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54,
-	0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x5f, 0x42, 0x49, 0x50, 0x30, 0x30,
-	0x38, 0x36, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54,
-	0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f,
-	0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, 0x53, 0x49, 0x47, 0x4e, 0x5f,
-	0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x53,
-	0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x03, 0x32, 0xdb, 0x06,
-	0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e,
-	0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x61, 0x77, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e,
-	0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x69,
-	0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40,
-	0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63,
-	0x72, 0x69, 0x70, 0x74, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53,
-	0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
-	0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70,
-	0x12, 0x40, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
-	0x17, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65,
-	0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72,
-	0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65,
-	0x73, 0x70, 0x12, 0x46, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73,
-	0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65,
-	0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1a,
-	0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d,
-	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x48, 0x0a, 0x0f, 0x44, 0x65,
-	0x72, 0x69, 0x76, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e,
-	0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
-	0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72,
-	0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x11, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f,
-	0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e,
+	0x25, 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73,
+	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75,
+	0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70,
+	0x22, 0x4c, 0x0a, 0x12, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,
+	0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61,
+	0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x72,
+	0x0a, 0x17, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53,
+	0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73,
+	0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73,
+	0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x6f, 0x74, 0x68, 0x65,
+	0x72, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74,
+	0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x16, 0x6f, 0x74, 0x68, 0x65,
+	0x72, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
+	0x65, 0x73, 0x22, 0x73, 0x0a, 0x18, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62,
+	0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e,
+	0x0a, 0x13, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x76,
+	0x65, 0x41, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x27,
+	0x0a, 0x0f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x69,
+	0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67,
+	0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x17,
+	0x0a, 0x15, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x9c, 0x01, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e,
+	0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d,
+	0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x57, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x56, 0x30,
+	0x10, 0x00, 0x12, 0x29, 0x0a, 0x25, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f,
+	0x44, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50,
+	0x45, 0x4e, 0x44, 0x5f, 0x42, 0x49, 0x50, 0x30, 0x30, 0x38, 0x36, 0x10, 0x01, 0x12, 0x21, 0x0a,
+	0x1d, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x41, 0x50,
+	0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x02,
+	0x12, 0x24, 0x0a, 0x20, 0x53, 0x49, 0x47, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f,
+	0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x53,
+	0x50, 0x45, 0x4e, 0x44, 0x10, 0x03, 0x2a, 0x62, 0x0a, 0x0d, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32,
+	0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x55, 0x53, 0x49, 0x47,
+	0x32, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49,
+	0x4e, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x5f,
+	0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x30, 0x34, 0x30, 0x10, 0x01, 0x12, 0x1a,
+	0x0a, 0x16, 0x4d, 0x55, 0x53, 0x49, 0x47, 0x32, 0x5f, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e,
+	0x5f, 0x56, 0x31, 0x30, 0x30, 0x52, 0x43, 0x32, 0x10, 0x02, 0x32, 0xdb, 0x06, 0x0a, 0x06, 0x53,
+	0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x52, 0x61, 0x77, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
+	0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72,
+	0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x12, 0x43,
+	0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70,
+	0x74, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e,
+	0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e,
+	0x70, 0x75, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a,
+	0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x73,
+	0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e,
+	0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12,
+	0x46, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x12, 0x19, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66,
+	0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x73, 0x69,
+	0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73,
+	0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x48, 0x0a, 0x0f, 0x44, 0x65, 0x72, 0x69, 0x76,
+	0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x2e, 0x73, 0x69, 0x67,
+	0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e,
+	0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x5a, 0x0a, 0x11, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69,
+	0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
+	0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65,
+	0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x73, 0x69, 0x67, 0x6e,
 	0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e,
-	0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x73,
-	0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d,
-	0x62, 0x69, 0x6e, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x12, 0x54, 0x0a, 0x13, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
-	0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70,
-	0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
-	0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32,
-	0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x24,
-	0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52,
-	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d,
-	0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e,
-	0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x4d,
-	0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e,
-	0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e,
-	0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x12, 0x57, 0x0a, 0x10, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62,
-	0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
-	0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69,
-	0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72,
-	0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65,
-	0x53, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x4d,
-	0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x1d, 0x2e, 0x73,
-	0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65,
-	0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69,
-	0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61,
-	0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x67,
-	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e,
-	0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c,
-	0x6e, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x33,
+	0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a,
+	0x13, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73,
+	0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d,
+	0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75,
+	0x53, 0x69, 0x67, 0x32, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x14, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67,
+	0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x73, 0x69,
+	0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x52, 0x65, 0x67, 0x69,
+	0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x25, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69,
+	0x67, 0x32, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x4d, 0x75, 0x53, 0x69,
+	0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x1a, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63,
+	0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53,
+	0x69, 0x67, 0x32, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+	0x57, 0x0a, 0x10, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65,
+	0x53, 0x69, 0x67, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75,
+	0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e,
+	0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x4d, 0x75, 0x53, 0x69,
+	0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x1d, 0x2e, 0x73, 0x69, 0x67, 0x6e,
+	0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75,
+	0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72,
+	0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68,
+	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67,
+	0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70,
+	0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x33,
 }
 
 var (
@@ -2182,82 +2299,87 @@ func file_signrpc_signer_proto_rawDescGZIP() []byte {
 	return file_signrpc_signer_proto_rawDescData
 }
 
-var file_signrpc_signer_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_signrpc_signer_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
 var file_signrpc_signer_proto_msgTypes = make([]protoimpl.MessageInfo, 28)
 var file_signrpc_signer_proto_goTypes = []interface{}{
 	(SignMethod)(0),                      // 0: signrpc.SignMethod
-	(*KeyLocator)(nil),                   // 1: signrpc.KeyLocator
-	(*KeyDescriptor)(nil),                // 2: signrpc.KeyDescriptor
-	(*TxOut)(nil),                        // 3: signrpc.TxOut
-	(*SignDescriptor)(nil),               // 4: signrpc.SignDescriptor
-	(*SignReq)(nil),                      // 5: signrpc.SignReq
-	(*SignResp)(nil),                     // 6: signrpc.SignResp
-	(*InputScript)(nil),                  // 7: signrpc.InputScript
-	(*InputScriptResp)(nil),              // 8: signrpc.InputScriptResp
-	(*SignMessageReq)(nil),               // 9: signrpc.SignMessageReq
-	(*SignMessageResp)(nil),              // 10: signrpc.SignMessageResp
-	(*VerifyMessageReq)(nil),             // 11: signrpc.VerifyMessageReq
-	(*VerifyMessageResp)(nil),            // 12: signrpc.VerifyMessageResp
-	(*SharedKeyRequest)(nil),             // 13: signrpc.SharedKeyRequest
-	(*SharedKeyResponse)(nil),            // 14: signrpc.SharedKeyResponse
-	(*TweakDesc)(nil),                    // 15: signrpc.TweakDesc
-	(*TaprootTweakDesc)(nil),             // 16: signrpc.TaprootTweakDesc
-	(*MuSig2CombineKeysRequest)(nil),     // 17: signrpc.MuSig2CombineKeysRequest
-	(*MuSig2CombineKeysResponse)(nil),    // 18: signrpc.MuSig2CombineKeysResponse
-	(*MuSig2SessionRequest)(nil),         // 19: signrpc.MuSig2SessionRequest
-	(*MuSig2SessionResponse)(nil),        // 20: signrpc.MuSig2SessionResponse
-	(*MuSig2RegisterNoncesRequest)(nil),  // 21: signrpc.MuSig2RegisterNoncesRequest
-	(*MuSig2RegisterNoncesResponse)(nil), // 22: signrpc.MuSig2RegisterNoncesResponse
-	(*MuSig2SignRequest)(nil),            // 23: signrpc.MuSig2SignRequest
-	(*MuSig2SignResponse)(nil),           // 24: signrpc.MuSig2SignResponse
-	(*MuSig2CombineSigRequest)(nil),      // 25: signrpc.MuSig2CombineSigRequest
-	(*MuSig2CombineSigResponse)(nil),     // 26: signrpc.MuSig2CombineSigResponse
-	(*MuSig2CleanupRequest)(nil),         // 27: signrpc.MuSig2CleanupRequest
-	(*MuSig2CleanupResponse)(nil),        // 28: signrpc.MuSig2CleanupResponse
+	(MuSig2Version)(0),                   // 1: signrpc.MuSig2Version
+	(*KeyLocator)(nil),                   // 2: signrpc.KeyLocator
+	(*KeyDescriptor)(nil),                // 3: signrpc.KeyDescriptor
+	(*TxOut)(nil),                        // 4: signrpc.TxOut
+	(*SignDescriptor)(nil),               // 5: signrpc.SignDescriptor
+	(*SignReq)(nil),                      // 6: signrpc.SignReq
+	(*SignResp)(nil),                     // 7: signrpc.SignResp
+	(*InputScript)(nil),                  // 8: signrpc.InputScript
+	(*InputScriptResp)(nil),              // 9: signrpc.InputScriptResp
+	(*SignMessageReq)(nil),               // 10: signrpc.SignMessageReq
+	(*SignMessageResp)(nil),              // 11: signrpc.SignMessageResp
+	(*VerifyMessageReq)(nil),             // 12: signrpc.VerifyMessageReq
+	(*VerifyMessageResp)(nil),            // 13: signrpc.VerifyMessageResp
+	(*SharedKeyRequest)(nil),             // 14: signrpc.SharedKeyRequest
+	(*SharedKeyResponse)(nil),            // 15: signrpc.SharedKeyResponse
+	(*TweakDesc)(nil),                    // 16: signrpc.TweakDesc
+	(*TaprootTweakDesc)(nil),             // 17: signrpc.TaprootTweakDesc
+	(*MuSig2CombineKeysRequest)(nil),     // 18: signrpc.MuSig2CombineKeysRequest
+	(*MuSig2CombineKeysResponse)(nil),    // 19: signrpc.MuSig2CombineKeysResponse
+	(*MuSig2SessionRequest)(nil),         // 20: signrpc.MuSig2SessionRequest
+	(*MuSig2SessionResponse)(nil),        // 21: signrpc.MuSig2SessionResponse
+	(*MuSig2RegisterNoncesRequest)(nil),  // 22: signrpc.MuSig2RegisterNoncesRequest
+	(*MuSig2RegisterNoncesResponse)(nil), // 23: signrpc.MuSig2RegisterNoncesResponse
+	(*MuSig2SignRequest)(nil),            // 24: signrpc.MuSig2SignRequest
+	(*MuSig2SignResponse)(nil),           // 25: signrpc.MuSig2SignResponse
+	(*MuSig2CombineSigRequest)(nil),      // 26: signrpc.MuSig2CombineSigRequest
+	(*MuSig2CombineSigResponse)(nil),     // 27: signrpc.MuSig2CombineSigResponse
+	(*MuSig2CleanupRequest)(nil),         // 28: signrpc.MuSig2CleanupRequest
+	(*MuSig2CleanupResponse)(nil),        // 29: signrpc.MuSig2CleanupResponse
 }
 var file_signrpc_signer_proto_depIdxs = []int32{
-	1,  // 0: signrpc.KeyDescriptor.key_loc:type_name -> signrpc.KeyLocator
-	2,  // 1: signrpc.SignDescriptor.key_desc:type_name -> signrpc.KeyDescriptor
-	3,  // 2: signrpc.SignDescriptor.output:type_name -> signrpc.TxOut
+	2,  // 0: signrpc.KeyDescriptor.key_loc:type_name -> signrpc.KeyLocator
+	3,  // 1: signrpc.SignDescriptor.key_desc:type_name -> signrpc.KeyDescriptor
+	4,  // 2: signrpc.SignDescriptor.output:type_name -> signrpc.TxOut
 	0,  // 3: signrpc.SignDescriptor.sign_method:type_name -> signrpc.SignMethod
-	4,  // 4: signrpc.SignReq.sign_descs:type_name -> signrpc.SignDescriptor
-	3,  // 5: signrpc.SignReq.prev_outputs:type_name -> signrpc.TxOut
-	7,  // 6: signrpc.InputScriptResp.input_scripts:type_name -> signrpc.InputScript
-	1,  // 7: signrpc.SignMessageReq.key_loc:type_name -> signrpc.KeyLocator
-	1,  // 8: signrpc.SharedKeyRequest.key_loc:type_name -> signrpc.KeyLocator
-	2,  // 9: signrpc.SharedKeyRequest.key_desc:type_name -> signrpc.KeyDescriptor
-	15, // 10: signrpc.MuSig2CombineKeysRequest.tweaks:type_name -> signrpc.TweakDesc
-	16, // 11: signrpc.MuSig2CombineKeysRequest.taproot_tweak:type_name -> signrpc.TaprootTweakDesc
-	1,  // 12: signrpc.MuSig2SessionRequest.key_loc:type_name -> signrpc.KeyLocator
-	15, // 13: signrpc.MuSig2SessionRequest.tweaks:type_name -> signrpc.TweakDesc
-	16, // 14: signrpc.MuSig2SessionRequest.taproot_tweak:type_name -> signrpc.TaprootTweakDesc
-	5,  // 15: signrpc.Signer.SignOutputRaw:input_type -> signrpc.SignReq
-	5,  // 16: signrpc.Signer.ComputeInputScript:input_type -> signrpc.SignReq
-	9,  // 17: signrpc.Signer.SignMessage:input_type -> signrpc.SignMessageReq
-	11, // 18: signrpc.Signer.VerifyMessage:input_type -> signrpc.VerifyMessageReq
-	13, // 19: signrpc.Signer.DeriveSharedKey:input_type -> signrpc.SharedKeyRequest
-	17, // 20: signrpc.Signer.MuSig2CombineKeys:input_type -> signrpc.MuSig2CombineKeysRequest
-	19, // 21: signrpc.Signer.MuSig2CreateSession:input_type -> signrpc.MuSig2SessionRequest
-	21, // 22: signrpc.Signer.MuSig2RegisterNonces:input_type -> signrpc.MuSig2RegisterNoncesRequest
-	23, // 23: signrpc.Signer.MuSig2Sign:input_type -> signrpc.MuSig2SignRequest
-	25, // 24: signrpc.Signer.MuSig2CombineSig:input_type -> signrpc.MuSig2CombineSigRequest
-	27, // 25: signrpc.Signer.MuSig2Cleanup:input_type -> signrpc.MuSig2CleanupRequest
-	6,  // 26: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp
-	8,  // 27: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp
-	10, // 28: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp
-	12, // 29: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp
-	14, // 30: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse
-	18, // 31: signrpc.Signer.MuSig2CombineKeys:output_type -> signrpc.MuSig2CombineKeysResponse
-	20, // 32: signrpc.Signer.MuSig2CreateSession:output_type -> signrpc.MuSig2SessionResponse
-	22, // 33: signrpc.Signer.MuSig2RegisterNonces:output_type -> signrpc.MuSig2RegisterNoncesResponse
-	24, // 34: signrpc.Signer.MuSig2Sign:output_type -> signrpc.MuSig2SignResponse
-	26, // 35: signrpc.Signer.MuSig2CombineSig:output_type -> signrpc.MuSig2CombineSigResponse
-	28, // 36: signrpc.Signer.MuSig2Cleanup:output_type -> signrpc.MuSig2CleanupResponse
-	26, // [26:37] is the sub-list for method output_type
-	15, // [15:26] is the sub-list for method input_type
-	15, // [15:15] is the sub-list for extension type_name
-	15, // [15:15] is the sub-list for extension extendee
-	0,  // [0:15] is the sub-list for field type_name
+	5,  // 4: signrpc.SignReq.sign_descs:type_name -> signrpc.SignDescriptor
+	4,  // 5: signrpc.SignReq.prev_outputs:type_name -> signrpc.TxOut
+	8,  // 6: signrpc.InputScriptResp.input_scripts:type_name -> signrpc.InputScript
+	2,  // 7: signrpc.SignMessageReq.key_loc:type_name -> signrpc.KeyLocator
+	2,  // 8: signrpc.SharedKeyRequest.key_loc:type_name -> signrpc.KeyLocator
+	3,  // 9: signrpc.SharedKeyRequest.key_desc:type_name -> signrpc.KeyDescriptor
+	16, // 10: signrpc.MuSig2CombineKeysRequest.tweaks:type_name -> signrpc.TweakDesc
+	17, // 11: signrpc.MuSig2CombineKeysRequest.taproot_tweak:type_name -> signrpc.TaprootTweakDesc
+	1,  // 12: signrpc.MuSig2CombineKeysRequest.version:type_name -> signrpc.MuSig2Version
+	1,  // 13: signrpc.MuSig2CombineKeysResponse.version:type_name -> signrpc.MuSig2Version
+	2,  // 14: signrpc.MuSig2SessionRequest.key_loc:type_name -> signrpc.KeyLocator
+	16, // 15: signrpc.MuSig2SessionRequest.tweaks:type_name -> signrpc.TweakDesc
+	17, // 16: signrpc.MuSig2SessionRequest.taproot_tweak:type_name -> signrpc.TaprootTweakDesc
+	1,  // 17: signrpc.MuSig2SessionRequest.version:type_name -> signrpc.MuSig2Version
+	1,  // 18: signrpc.MuSig2SessionResponse.version:type_name -> signrpc.MuSig2Version
+	6,  // 19: signrpc.Signer.SignOutputRaw:input_type -> signrpc.SignReq
+	6,  // 20: signrpc.Signer.ComputeInputScript:input_type -> signrpc.SignReq
+	10, // 21: signrpc.Signer.SignMessage:input_type -> signrpc.SignMessageReq
+	12, // 22: signrpc.Signer.VerifyMessage:input_type -> signrpc.VerifyMessageReq
+	14, // 23: signrpc.Signer.DeriveSharedKey:input_type -> signrpc.SharedKeyRequest
+	18, // 24: signrpc.Signer.MuSig2CombineKeys:input_type -> signrpc.MuSig2CombineKeysRequest
+	20, // 25: signrpc.Signer.MuSig2CreateSession:input_type -> signrpc.MuSig2SessionRequest
+	22, // 26: signrpc.Signer.MuSig2RegisterNonces:input_type -> signrpc.MuSig2RegisterNoncesRequest
+	24, // 27: signrpc.Signer.MuSig2Sign:input_type -> signrpc.MuSig2SignRequest
+	26, // 28: signrpc.Signer.MuSig2CombineSig:input_type -> signrpc.MuSig2CombineSigRequest
+	28, // 29: signrpc.Signer.MuSig2Cleanup:input_type -> signrpc.MuSig2CleanupRequest
+	7,  // 30: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp
+	9,  // 31: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp
+	11, // 32: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp
+	13, // 33: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp
+	15, // 34: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse
+	19, // 35: signrpc.Signer.MuSig2CombineKeys:output_type -> signrpc.MuSig2CombineKeysResponse
+	21, // 36: signrpc.Signer.MuSig2CreateSession:output_type -> signrpc.MuSig2SessionResponse
+	23, // 37: signrpc.Signer.MuSig2RegisterNonces:output_type -> signrpc.MuSig2RegisterNoncesResponse
+	25, // 38: signrpc.Signer.MuSig2Sign:output_type -> signrpc.MuSig2SignResponse
+	27, // 39: signrpc.Signer.MuSig2CombineSig:output_type -> signrpc.MuSig2CombineSigResponse
+	29, // 40: signrpc.Signer.MuSig2Cleanup:output_type -> signrpc.MuSig2CleanupResponse
+	30, // [30:41] is the sub-list for method output_type
+	19, // [19:30] is the sub-list for method input_type
+	19, // [19:19] is the sub-list for extension type_name
+	19, // [19:19] is the sub-list for extension extendee
+	0,  // [0:19] is the sub-list for field type_name
 }
 
 func init() { file_signrpc_signer_proto_init() }
@@ -2608,7 +2730,7 @@ func file_signrpc_signer_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_signrpc_signer_proto_rawDesc,
-			NumEnums:      1,
+			NumEnums:      2,
 			NumMessages:   28,
 			NumExtensions: 0,
 			NumServices:   1,
diff --git a/lnrpc/signrpc/signer.proto b/lnrpc/signrpc/signer.proto
index 5ea85dd70..6540252f6 100644
--- a/lnrpc/signrpc/signer.proto
+++ b/lnrpc/signrpc/signer.proto
@@ -444,12 +444,33 @@ message TaprootTweakDesc {
     bool key_spend_only = 2;
 }
 
+enum MuSig2Version {
+    /*
+    The default value on the RPC is zero for enums so we need to represent an
+    invalid/undefined version by default to make sure clients upgrade their
+    software to set the version explicitly.
+    */
+    MUSIG2_VERSION_UNDEFINED = 0;
+
+    /*
+    The version of MuSig2 that lnd 0.15.x shipped with, which corresponds to the
+    version v0.4.0 of the MuSig2 BIP draft.
+    */
+    MUSIG2_VERSION_V040 = 1;
+
+    /*
+    The current version of MuSig2 which corresponds to the version v1.0.0rc2 of
+    the MuSig2 BIP draft.
+    */
+    MUSIG2_VERSION_V100RC2 = 2;
+}
+
 message MuSig2CombineKeysRequest {
     /*
-    A list of all public keys (serialized in 32-byte x-only format!)
-    participating in the signing session. The list will always be sorted
-    lexicographically internally. This must include the local key which is
-    described by the above key_loc.
+    A list of all public keys (serialized in 32-byte x-only format for v0.4.0
+    and 33-byte compressed format for v1.0.0rc2!) participating in the signing
+    session. The list will always be sorted lexicographically internally. This
+    must include the local key which is described by the above key_loc.
     */
     repeated bytes all_signer_pubkeys = 1;
 
@@ -465,6 +486,14 @@ message MuSig2CombineKeysRequest {
     on-chain.
     */
     TaprootTweakDesc taproot_tweak = 3;
+
+    /*
+    The mandatory version of the MuSig2 BIP draft to use. This is necessary to
+    differentiate between the changes that were made to the BIP while this
+    experimental RPC was already released. Some of those changes affect how the
+    combined key and nonces are created.
+    */
+    MuSig2Version version = 4;
 }
 
 message MuSig2CombineKeysResponse {
@@ -482,6 +511,11 @@ message MuSig2CombineKeysResponse {
     is used.
     */
     bytes taproot_internal_key = 2;
+
+    /*
+    The version of the MuSig2 BIP that was used to combine the keys.
+    */
+    MuSig2Version version = 4;
 }
 
 message MuSig2SessionRequest {
@@ -491,10 +525,10 @@ message MuSig2SessionRequest {
     KeyLocator key_loc = 1;
 
     /*
-    A list of all public keys (serialized in 32-byte x-only format!)
-    participating in the signing session. The list will always be sorted
-    lexicographically internally. This must include the local key which is
-    described by the above key_loc.
+    A list of all public keys (serialized in 32-byte x-only format for v0.4.0
+    and 33-byte compressed format for v1.0.0rc2!) participating in the signing
+    session. The list will always be sorted lexicographically internally. This
+    must include the local key which is described by the above key_loc.
     */
     repeated bytes all_signer_pubkeys = 2;
 
@@ -516,6 +550,14 @@ message MuSig2SessionRequest {
     on-chain.
     */
     TaprootTweakDesc taproot_tweak = 5;
+
+    /*
+    The mandatory version of the MuSig2 BIP draft to use. This is necessary to
+    differentiate between the changes that were made to the BIP while this
+    experimental RPC was already released. Some of those changes affect how the
+    combined key and nonces are created.
+    */
+    MuSig2Version version = 6;
 }
 
 message MuSig2SessionResponse {
@@ -553,6 +595,11 @@ message MuSig2SessionResponse {
     now.
     */
     bool have_all_nonces = 5;
+
+    /*
+    The version of the MuSig2 BIP that was used to create the session.
+    */
+    MuSig2Version version = 6;
 }
 
 message MuSig2RegisterNoncesRequest {
diff --git a/lnrpc/signrpc/signer.swagger.json b/lnrpc/signrpc/signer.swagger.json
index 5b309b7c3..d83b1df75 100644
--- a/lnrpc/signrpc/signer.swagger.json
+++ b/lnrpc/signrpc/signer.swagger.json
@@ -502,7 +502,7 @@
             "type": "string",
             "format": "byte"
           },
-          "description": "A list of all public keys (serialized in 32-byte x-only format!)\nparticipating in the signing session. The list will always be sorted\nlexicographically internally. This must include the local key which is\ndescribed by the above key_loc."
+          "description": "A list of all public keys (serialized in 32-byte x-only format for v0.4.0\nand 33-byte compressed format for v1.0.0rc2!) participating in the signing\nsession. The list will always be sorted lexicographically internally. This\nmust include the local key which is described by the above key_loc."
         },
         "tweaks": {
           "type": "array",
@@ -514,6 +514,10 @@
         "taproot_tweak": {
           "$ref": "#/definitions/signrpcTaprootTweakDesc",
           "description": "An optional taproot specific tweak that must be specified if the MuSig2\ncombined key will be used as the main taproot key of a taproot output\non-chain."
+        },
+        "version": {
+          "$ref": "#/definitions/signrpcMuSig2Version",
+          "description": "The mandatory version of the MuSig2 BIP draft to use. This is necessary to\ndifferentiate between the changes that were made to the BIP while this\nexperimental RPC was already released. Some of those changes affect how the\ncombined key and nonces are created."
         }
       }
     },
@@ -529,6 +533,10 @@
           "type": "string",
           "format": "byte",
           "description": "The raw combined public key (in the 32-byte x-only format) before any tweaks\nare applied to it. If a taproot tweak is specified, this corresponds to the\ninternal key that needs to be put into the witness if the script spend path\nis used."
+        },
+        "version": {
+          "$ref": "#/definitions/signrpcMuSig2Version",
+          "description": "The version of the MuSig2 BIP that was used to combine the keys."
         }
       }
     },
@@ -604,7 +612,7 @@
             "type": "string",
             "format": "byte"
           },
-          "description": "A list of all public keys (serialized in 32-byte x-only format!)\nparticipating in the signing session. The list will always be sorted\nlexicographically internally. This must include the local key which is\ndescribed by the above key_loc."
+          "description": "A list of all public keys (serialized in 32-byte x-only format for v0.4.0\nand 33-byte compressed format for v1.0.0rc2!) participating in the signing\nsession. The list will always be sorted lexicographically internally. This\nmust include the local key which is described by the above key_loc."
         },
         "other_signer_public_nonces": {
           "type": "array",
@@ -624,6 +632,10 @@
         "taproot_tweak": {
           "$ref": "#/definitions/signrpcTaprootTweakDesc",
           "description": "An optional taproot specific tweak that must be specified if the MuSig2\ncombined key will be used as the main taproot key of a taproot output\non-chain."
+        },
+        "version": {
+          "$ref": "#/definitions/signrpcMuSig2Version",
+          "description": "The mandatory version of the MuSig2 BIP draft to use. This is necessary to\ndifferentiate between the changes that were made to the BIP while this\nexperimental RPC was already released. Some of those changes affect how the\ncombined key and nonces are created."
         }
       }
     },
@@ -653,6 +665,10 @@
         "have_all_nonces": {
           "type": "boolean",
           "description": "Indicates whether all nonces required to start the signing process are known\nnow."
+        },
+        "version": {
+          "$ref": "#/definitions/signrpcMuSig2Version",
+          "description": "The version of the MuSig2 BIP that was used to create the session."
         }
       }
     },
@@ -685,6 +701,16 @@
         }
       }
     },
+    "signrpcMuSig2Version": {
+      "type": "string",
+      "enum": [
+        "MUSIG2_VERSION_UNDEFINED",
+        "MUSIG2_VERSION_V040",
+        "MUSIG2_VERSION_V100RC2"
+      ],
+      "default": "MUSIG2_VERSION_UNDEFINED",
+      "description": " - MUSIG2_VERSION_UNDEFINED: The default value on the RPC is zero for enums so we need to represent an\ninvalid/undefined version by default to make sure clients upgrade their\nsoftware to set the version explicitly.\n - MUSIG2_VERSION_V040: The version of MuSig2 that lnd 0.15.x shipped with, which corresponds to the\nversion v0.4.0 of the MuSig2 BIP draft.\n - MUSIG2_VERSION_V100RC2: The current version of MuSig2 which corresponds to the version v1.0.0rc2 of\nthe MuSig2 BIP draft."
+    },
     "signrpcSharedKeyRequest": {
       "type": "object",
       "properties": {
diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go
index 7c098f257..01fc7696d 100644
--- a/lnrpc/signrpc/signer_server.go
+++ b/lnrpc/signrpc/signer_server.go
@@ -824,20 +824,24 @@ func (s *Server) DeriveSharedKey(_ context.Context, in *SharedKeyRequest) (
 func (s *Server) MuSig2CombineKeys(_ context.Context,
 	in *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error) {
 
-	// Parse the public keys of all signing participants. This must also
-	// include our own, local key.
-	allSignerPubKeys := make([]*btcec.PublicKey, len(in.AllSignerPubkeys))
-	if len(in.AllSignerPubkeys) < 2 {
-		return nil, fmt.Errorf("need at least two signing public keys")
+	// Check the now mandatory version first. We made the version mandatory,
+	// so we don't get unexpected/undefined behavior for old clients that
+	// don't specify the version. Since this API is still declared to be
+	// experimental this should be the approach that leads to the least
+	// amount of unexpected behavior.
+	version, err := UnmarshalMuSig2Version(in.Version)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing version: %w", err)
 	}
 
-	for idx, pubKeyBytes := range in.AllSignerPubkeys {
-		pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
-		if err != nil {
-			return nil, fmt.Errorf("error parsing signer public "+
-				"key %d: %v", idx, err)
-		}
-		allSignerPubKeys[idx] = pubKey
+	// Parse the public keys of all signing participants. This must also
+	// include our own, local key.
+	allSignerPubKeys, err := input.MuSig2ParsePubKeys(
+		version, in.AllSignerPubkeys,
+	)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing all signer public "+
+			"keys: %w", err)
 	}
 
 	// Are there any tweaks to apply to the combined public key?
@@ -848,7 +852,9 @@ func (s *Server) MuSig2CombineKeys(_ context.Context,
 	}
 
 	// Combine the keys now without creating a session in memory.
-	combinedKey, err := input.MuSig2CombineKeys(allSignerPubKeys, tweaks)
+	combinedKey, err := input.MuSig2CombineKeys(
+		version, allSignerPubKeys, true, tweaks,
+	)
 	if err != nil {
 		return nil, fmt.Errorf("error combining keys: %v", err)
 	}
@@ -865,6 +871,7 @@ func (s *Server) MuSig2CombineKeys(_ context.Context,
 			combinedKey.FinalKey,
 		),
 		TaprootInternalKey: internalKeyBytes,
+		Version:            in.Version,
 	}, nil
 }
 
@@ -876,6 +883,16 @@ func (s *Server) MuSig2CombineKeys(_ context.Context,
 func (s *Server) MuSig2CreateSession(_ context.Context,
 	in *MuSig2SessionRequest) (*MuSig2SessionResponse, error) {
 
+	// Check the now mandatory version first. We made the version mandatory,
+	// so we don't get unexpected/undefined behavior for old clients that
+	// don't specify the version. Since this API is still declared to be
+	// experimental this should be the approach that leads to the least
+	// amount of unexpected behavior.
+	version, err := UnmarshalMuSig2Version(in.Version)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing version: %w", err)
+	}
+
 	// A key locator is always mandatory.
 	if in.KeyLoc == nil {
 		return nil, fmt.Errorf("missing key_loc")
@@ -887,18 +904,12 @@ func (s *Server) MuSig2CreateSession(_ context.Context,
 
 	// Parse the public keys of all signing participants. This must also
 	// include our own, local key.
-	allSignerPubKeys := make([]*btcec.PublicKey, len(in.AllSignerPubkeys))
-	if len(in.AllSignerPubkeys) < 2 {
-		return nil, fmt.Errorf("need at least two signing public keys")
-	}
-
-	for idx, pubKeyBytes := range in.AllSignerPubkeys {
-		pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
-		if err != nil {
-			return nil, fmt.Errorf("error parsing signer public "+
-				"key %d: %v", idx, err)
-		}
-		allSignerPubKeys[idx] = pubKey
+	allSignerPubKeys, err := input.MuSig2ParsePubKeys(
+		version, in.AllSignerPubkeys,
+	)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing all signer public "+
+			"keys: %w", err)
 	}
 
 	// We participate a nonce ourselves, so we can't have more nonces than
@@ -927,7 +938,7 @@ func (s *Server) MuSig2CreateSession(_ context.Context,
 
 	// Register the session with the internal wallet/signer now.
 	session, err := s.cfg.Signer.MuSig2CreateSession(
-		keyLoc, allSignerPubKeys, tweaks, otherSignerNonces,
+		version, keyLoc, allSignerPubKeys, tweaks, otherSignerNonces,
 	)
 	if err != nil {
 		return nil, fmt.Errorf("error registering session: %v", err)
@@ -948,6 +959,7 @@ func (s *Server) MuSig2CreateSession(_ context.Context,
 		TaprootInternalKey: internalKeyBytes,
 		LocalPublicNonces:  session.PublicNonce[:],
 		HaveAllNonces:      session.HaveAllNonces,
+		Version:            in.Version,
 	}, nil
 }
 
diff --git a/lnrpc/signrpc/signer_utils.go b/lnrpc/signrpc/signer_utils.go
new file mode 100644
index 000000000..0ceed7c8c
--- /dev/null
+++ b/lnrpc/signrpc/signer_utils.go
@@ -0,0 +1,42 @@
+package signrpc
+
+import (
+	"fmt"
+
+	"github.com/lightningnetwork/lnd/input"
+)
+
+// UnmarshalMuSig2Version parses the RPC MuSig2 version into the native
+// counterpart.
+func UnmarshalMuSig2Version(rpcVersion MuSig2Version) (input.MuSig2Version,
+	error) {
+
+	switch rpcVersion {
+	case MuSig2Version_MUSIG2_VERSION_V040:
+		return input.MuSig2Version040, nil
+
+	case MuSig2Version_MUSIG2_VERSION_V100RC2:
+		return input.MuSig2Version100RC2, nil
+
+	default:
+		return 0, fmt.Errorf("unknown MuSig2 version <%v>, make sure "+
+			"your client software is up to date, the version "+
+			"field is mandatory for this release of lnd",
+			rpcVersion.String())
+	}
+}
+
+// MarshalMuSig2Version turns the native MuSig2 version into its RPC
+// counterpart.
+func MarshalMuSig2Version(version input.MuSig2Version) (MuSig2Version, error) {
+	switch version {
+	case input.MuSig2Version040:
+		return MuSig2Version_MUSIG2_VERSION_V040, nil
+
+	case input.MuSig2Version100RC2:
+		return MuSig2Version_MUSIG2_VERSION_V100RC2, nil
+
+	default:
+		return 0, fmt.Errorf("unknown MuSig2 version <%d>", version)
+	}
+}
diff --git a/lntemp/rpc/signer.go b/lntemp/rpc/signer.go
index fea7bf9dd..79a680c2b 100644
--- a/lntemp/rpc/signer.go
+++ b/lntemp/rpc/signer.go
@@ -72,6 +72,20 @@ func (h *HarnessRPC) MuSig2CreateSession(
 	return resp
 }
 
+// MuSig2CreateSessionErr makes an RPC call to the node's SignerClient and
+// asserts an error is returned.
+func (h *HarnessRPC) MuSig2CreateSessionErr(
+	req *signrpc.MuSig2SessionRequest) error {
+
+	ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
+	defer cancel()
+
+	_, err := h.Signer.MuSig2CreateSession(ctxt, req)
+	require.Error(h, err, "expected error from calling MuSig2CreateSession")
+
+	return err
+}
+
 // MuSig2CombineKeys makes a RPC call to the node's SignerClient and asserts.
 //
 //nolint:lll
@@ -87,6 +101,20 @@ func (h *HarnessRPC) MuSig2CombineKeys(
 	return resp
 }
 
+// MuSig2CombineKeysErr makes an RPC call to the node's SignerClient and
+// asserts an error is returned.
+func (h *HarnessRPC) MuSig2CombineKeysErr(
+	req *signrpc.MuSig2CombineKeysRequest) error {
+
+	ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
+	defer cancel()
+
+	_, err := h.Signer.MuSig2CombineKeys(ctxt, req)
+	require.Error(h, err, "expected error from calling MuSig2CombineKeys")
+
+	return err
+}
+
 // MuSig2RegisterNonces makes a RPC call to the node's SignerClient and asserts.
 //
 //nolint:lll
diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go
index 9522cefc4..da624c726 100644
--- a/lnwallet/btcwallet/signer.go
+++ b/lnwallet/btcwallet/signer.go
@@ -26,7 +26,9 @@ import (
 // of ErrNotMine should be returned instead.
 //
 // This is a part of the WalletController interface.
-func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, error) {
+func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo,
+	error) {
+
 	prevTx, txOut, bip32, confirmations, err := b.wallet.FetchInputInfo(
 		prevOut,
 	)
@@ -476,11 +478,11 @@ type muSig2State struct {
 
 	// context is the signing context responsible for keeping track of the
 	// public keys involved in the signing process.
-	context *musig2.Context
+	context input.MuSig2Context
 
 	// session is the signing session responsible for keeping track of the
 	// nonces and partial signatures involved in the signing process.
-	session *musig2.Session
+	session input.MuSig2Session
 }
 
 // MuSig2CreateSession creates a new MuSig2 signing session using the local
@@ -488,8 +490,9 @@ type muSig2State struct {
 // all signing parties must be provided, including the public key of the local
 // signing key. If nonces of other parties are already known, they can be
 // submitted as well to reduce the number of method calls necessary later on.
-func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator,
-	allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks,
+func (b *BtcWallet) MuSig2CreateSession(bipVersion input.MuSig2Version,
+	keyLoc keychain.KeyLocator, allSignerPubKeys []*btcec.PublicKey,
+	tweaks *input.MuSig2Tweaks,
 	otherSignerNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo,
 	error) {
 
@@ -500,66 +503,57 @@ func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator,
 		KeyLocator: keyLoc,
 	})
 	if err != nil {
-		return nil, fmt.Errorf("error deriving private key: %v", err)
+		return nil, fmt.Errorf("error deriving private key: %w", err)
 	}
 
-	// The context keeps track of all signing keys and our local key.
-	allOpts := append(
-		[]musig2.ContextOption{
-			musig2.WithKnownSigners(allSignerPubKeys),
-		},
-		tweaks.ToContextOptions()...,
+	// Create a signing context and session with the given private key and
+	// list of all known signer public keys.
+	muSigContext, muSigSession, err := input.MuSig2CreateContext(
+		bipVersion, privKey, allSignerPubKeys, tweaks,
 	)
-	musigContext, err := musig2.NewContext(privKey, true, allOpts...)
-	if err != nil {
-		return nil, fmt.Errorf("error creating MuSig2 signing "+
-			"context: %v", err)
-	}
-
-	// The session keeps track of the own and other nonces.
-	musigSession, err := musigContext.NewSession()
 	if err != nil {
-		return nil, fmt.Errorf("error creating MuSig2 signing "+
-			"session: %v", err)
+		return nil, fmt.Errorf("error creating signing context: %w",
+			err)
 	}
 
 	// Add all nonces we might've learned so far.
 	haveAllNonces := false
 	for _, otherSignerNonce := range otherSignerNonces {
-		haveAllNonces, err = musigSession.RegisterPubNonce(
+		haveAllNonces, err = muSigSession.RegisterPubNonce(
 			otherSignerNonce,
 		)
 		if err != nil {
 			return nil, fmt.Errorf("error registering other "+
-				"signer public nonce: %v", err)
+				"signer public nonce: %w", err)
 		}
 	}
 
 	// Register the new session.
-	combinedKey, err := musigContext.CombinedKey()
+	combinedKey, err := muSigContext.CombinedKey()
 	if err != nil {
-		return nil, fmt.Errorf("error getting combined key: %v", err)
+		return nil, fmt.Errorf("error getting combined key: %w", err)
 	}
 	session := &muSig2State{
 		MuSig2SessionInfo: input.MuSig2SessionInfo{
 			SessionID: input.NewMuSig2SessionID(
-				combinedKey, musigSession.PublicNonce(),
+				combinedKey, muSigSession.PublicNonce(),
 			),
-			PublicNonce:   musigSession.PublicNonce(),
+			Version:       bipVersion,
+			PublicNonce:   muSigSession.PublicNonce(),
 			CombinedKey:   combinedKey,
 			TaprootTweak:  tweaks.HasTaprootTweak(),
 			HaveAllNonces: haveAllNonces,
 		},
-		context: musigContext,
-		session: musigSession,
+		context: muSigContext,
+		session: muSigSession,
 	}
 
 	// The internal key is only calculated if we are using a taproot tweak
 	// and need to know it for a potential script spend.
 	if tweaks.HasTaprootTweak() {
-		internalKey, err := musigContext.TaprootInternalKey()
+		internalKey, err := muSigContext.TaprootInternalKey()
 		if err != nil {
-			return nil, fmt.Errorf("error getting internal key: %v",
+			return nil, fmt.Errorf("error getting internal key: %w",
 				err)
 		}
 		session.TaprootInternalKey = internalKey
@@ -615,7 +609,7 @@ func (b *BtcWallet) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
 		)
 		if err != nil {
 			return false, fmt.Errorf("error registering other "+
-				"signer public nonce: %v", err)
+				"signer public nonce: %w", err)
 		}
 	}
 
@@ -652,9 +646,9 @@ func (b *BtcWallet) MuSig2Sign(sessionID input.MuSig2SessionID,
 	}
 
 	// Create our own partial signature with the local signing key.
-	partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys())
+	partialSig, err := input.MuSig2Sign(session.session, msg, true)
 	if err != nil {
-		return nil, fmt.Errorf("error signing with local key: %v", err)
+		return nil, fmt.Errorf("error signing with local key: %w", err)
 	}
 
 	// Clean up our local state if requested.
@@ -698,12 +692,12 @@ func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID,
 		err      error
 	)
 	for _, otherPartialSig := range partialSigs {
-		session.HaveAllSigs, err = session.session.CombineSig(
-			otherPartialSig,
+		session.HaveAllSigs, err = input.MuSig2CombineSig(
+			session.session, otherPartialSig,
 		)
 		if err != nil {
 			return nil, false, fmt.Errorf("error combining "+
-				"partial signature: %v", err)
+				"partial signature: %w", err)
 		}
 	}
 
diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go
index 9172c2525..0d5b2ead9 100644
--- a/lnwallet/rpcwallet/rpcwallet.go
+++ b/lnwallet/rpcwallet/rpcwallet.go
@@ -640,11 +640,17 @@ func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
 // all signing parties must be provided, including the public key of the local
 // signing key. If nonces of other parties are already known, they can be
 // submitted as well to reduce the number of method calls necessary later on.
-func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator,
-	pubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks,
+func (r *RPCKeyRing) MuSig2CreateSession(bipVersion input.MuSig2Version,
+	keyLoc keychain.KeyLocator, pubKeys []*btcec.PublicKey,
+	tweaks *input.MuSig2Tweaks,
 	otherNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo,
 	error) {
 
+	apiVersion, err := signrpc.MarshalMuSig2Version(bipVersion)
+	if err != nil {
+		return nil, err
+	}
+
 	// We need to serialize all data for the RPC call. We can do that by
 	// putting everything directly into the request struct.
 	req := &signrpc.MuSig2SessionRequest{
@@ -657,9 +663,18 @@ func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator,
 			[]*signrpc.TweakDesc, len(tweaks.GenericTweaks),
 		),
 		OtherSignerPublicNonces: make([][]byte, len(otherNonces)),
+		Version:                 apiVersion,
 	}
 	for idx, pubKey := range pubKeys {
-		req.AllSignerPubkeys[idx] = schnorr.SerializePubKey(pubKey)
+		switch bipVersion {
+		case input.MuSig2Version040:
+			req.AllSignerPubkeys[idx] = schnorr.SerializePubKey(
+				pubKey,
+			)
+
+		case input.MuSig2Version100RC2:
+			req.AllSignerPubkeys[idx] = pubKey.SerializeCompressed()
+		}
 	}
 	for idx, genericTweak := range tweaks.GenericTweaks {
 		req.Tweaks[idx] = &signrpc.TweakDesc{
@@ -690,6 +705,7 @@ func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator,
 
 	// De-Serialize all the info back into our native struct.
 	info := &input.MuSig2SessionInfo{
+		Version:       bipVersion,
 		TaprootTweak:  tweaks.HasTaprootTweak(),
 		HaveAllNonces: resp.HaveAllNonces,
 	}
diff --git a/watchtower/wtmock/signer.go b/watchtower/wtmock/signer.go
index b41d8ceb5..6454871ce 100644
--- a/watchtower/wtmock/signer.go
+++ b/watchtower/wtmock/signer.go
@@ -69,8 +69,8 @@ func (s *MockSigner) ComputeInputScript(tx *wire.MsgTx,
 // all signing parties must be provided, including the public key of the local
 // signing key. If nonces of other parties are already known, they can be
 // submitted as well to reduce the number of method calls necessary later on.
-func (s *MockSigner) MuSig2CreateSession(keychain.KeyLocator,
-	[]*btcec.PublicKey, *input.MuSig2Tweaks,
+func (s *MockSigner) MuSig2CreateSession(input.MuSig2Version,
+	keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
 	[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
 
 	return nil, nil


Test patch:
diff --git a/input/musig2_test.go b/input/musig2_test.go
new file mode 100644
index 000000000..1fccd1686
--- /dev/null
+++ b/input/musig2_test.go
@@ -0,0 +1,208 @@
+package input
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/btcsuite/btcd/btcec/v2"
+	"github.com/btcsuite/btcd/btcec/v2/schnorr"
+	"github.com/stretchr/testify/require"
+)
+
+var (
+	hexDecode = func(keyStr string) []byte {
+		keyBytes, _ := hex.DecodeString(keyStr)
+		return keyBytes
+	}
+	dummyPubKey1, _ = btcec.ParsePubKey(hexDecode(
+		"02ec95e4e8ad994861b95fc5986eedaac24739e5ea3d0634db4c8ccd44cd" +
+			"a126ea",
+	))
+	dummyPubKey2, _ = btcec.ParsePubKey(hexDecode(
+		"0356167ba3e54ac542e86e906d4186aba9ca0b9df45001c62b753d33fe06" +
+			"f5b4e8",
+	))
+	dummyPubKey3, _ = btcec.ParsePubKey(hexDecode(
+		"02a9b0e1777e35d4620061a9fb0e614bf0254a50dea4f872babf6d44bf4d" +
+			"8ee7c6",
+	))
+
+	testVector040Key1, _ = schnorr.ParsePubKey(hexDecode(
+		"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE0" +
+			"36F9",
+	))
+	testVector040Key2, _ = schnorr.ParsePubKey(hexDecode(
+		"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502B" +
+			"A659",
+	))
+	testVector040Key3, _ = schnorr.ParsePubKey(hexDecode(
+		"3590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038" +
+			"CA66",
+	))
+
+	testVector100Key1, _ = btcec.ParsePubKey(hexDecode(
+		"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BC" +
+			"E036F9",
+	))
+	testVector100Key2, _ = btcec.ParsePubKey(hexDecode(
+		"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50" +
+			"2BA659",
+	))
+	testVector100Key3, _ = btcec.ParsePubKey(hexDecode(
+		"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D0" +
+			"38CA66",
+	))
+
+	bip86Tweak = &MuSig2Tweaks{TaprootBIP0086Tweak: true}
+)
+
+func TestMuSig2CombineKeys(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		name                string
+		version             MuSig2Version
+		keys                []*btcec.PublicKey
+		sortKeys            bool
+		tweak               *MuSig2Tweaks
+		expectedErr         string
+		expectedFinalKey    string
+		expectedPreTweakKey string
+	}{{
+		name:        "invalid version",
+		version:     7,
+		expectedErr: "unknown MuSig2 version: <7>",
+	}, {
+		name:     "v0.4.0 two dummy keys BIP86",
+		version:  MuSig2Version040,
+		keys:     []*btcec.PublicKey{dummyPubKey1, dummyPubKey2},
+		sortKeys: true, tweak: bip86Tweak,
+		expectedFinalKey: "03b54fb320a8fc3589e86a1559c6aaa774fbab4e4d" +
+			"9fbf31e2fd836b661ac6a132",
+		expectedPreTweakKey: "0279c76a15dcf6786058a571e4022b78633e1bf" +
+			"8a7a4ca440bcbbeeaea772228a2",
+	}, {
+		name:    "v0.4.0 three dummy keys BIP86",
+		version: MuSig2Version040,
+		keys: []*btcec.PublicKey{
+			dummyPubKey1, dummyPubKey2, dummyPubKey3,
+		},
+		sortKeys: true,
+		tweak:    bip86Tweak,
+		expectedFinalKey: "03fa8195d584b195476f20e2fe978fd7312f4b08f2" +
+			"777f080bcdfc9350603cd6e7",
+		expectedPreTweakKey: "03e615b8aad4ed10544537bc48b1d6600e15773" +
+			"476a675c6cbba6808f21b1988e5",
+	}, {
+		name:    "v0.4.0 three test vector keys BIP86",
+		version: MuSig2Version040,
+		keys: []*btcec.PublicKey{
+			testVector040Key1, testVector040Key2, testVector040Key3,
+		},
+		tweak:    bip86Tweak,
+		sortKeys: true,
+		expectedFinalKey: "025b257b4e785d61157ef5303051f45184bd5cb47b" +
+			"c4b4069ed4dd4536459cb83b",
+		expectedPreTweakKey: "02d70cd69a2647f7390973df48cbfa2ccc407b8" +
+			"b2d60b08c5f1641185c7998a290",
+	}, {
+		name: "v0.4.0 three test vector keys BIP86 reverse order",
+		keys: []*btcec.PublicKey{
+			testVector040Key3, testVector040Key2, testVector040Key1,
+		},
+		sortKeys: true,
+		tweak:    bip86Tweak,
+		expectedFinalKey: "025b257b4e785d61157ef5303051f45184bd5cb47b" +
+			"c4b4069ed4dd4536459cb83b",
+		expectedPreTweakKey: "02d70cd69a2647f7390973df48cbfa2ccc407b8" +
+			"b2d60b08c5f1641185c7998a290",
+	}, {
+		name: "v0.4.0 three test vector keys BIP86 no sort",
+		keys: []*btcec.PublicKey{
+			testVector040Key1, testVector040Key2, testVector040Key3,
+		},
+		sortKeys: false,
+		tweak:    bip86Tweak,
+		expectedFinalKey: "0223e0c640e96000e8e92699ec3802e5c39edf47db" +
+			"dfdb788b3735b76a55538179",
+		expectedPreTweakKey: "03e5830140512195d74c8307e39637cbe5fb730" +
+			"ebeab80ec514cf88a877ceeee0b",
+	}, {
+		name:    "v1.0.0rc2 three dummy keys BIP86",
+		version: MuSig2Version100RC2,
+		keys: []*btcec.PublicKey{
+			dummyPubKey1, dummyPubKey2, dummyPubKey3,
+		},
+		sortKeys: true,
+		tweak:    bip86Tweak,
+		expectedFinalKey: "029d11a433e446276c88ee099cecfda1e4234dc486" +
+			"49e2b07f9b159176731eb68a",
+		expectedPreTweakKey: "03f0bacfef76086c519b6db216a252f1435336f" +
+			"d23fc86cfb51f67056439c256d4",
+	}, {
+		name:    "v1.0.0rc2 three test vector keys BIP86",
+		version: MuSig2Version100RC2,
+		keys: []*btcec.PublicKey{
+			testVector100Key1, testVector100Key2, testVector100Key3,
+		},
+		sortKeys: false,
+		tweak:    bip86Tweak,
+		expectedFinalKey: "03f79d14149ecd4bb74921865906a8e4f1333439a9" +
+			"1b96610d72caa7495dcf2376",
+		expectedPreTweakKey: "0290539eede565f5d054f32cc0c220126889ed1" +
+			"e5d193baf15aef344fe59d4610c",
+	}, {
+		name:    "v1.0.0rc2 three test vector keys BIP86 sorted",
+		version: MuSig2Version100RC2,
+		keys: []*btcec.PublicKey{
+			testVector100Key1, testVector100Key2, testVector100Key3,
+		},
+		sortKeys: true,
+		tweak:    bip86Tweak,
+		expectedFinalKey: "0379e6c3e628c9bfbce91de6b7fb28e2aec7713d37" +
+			"7cf260ab599dcbc40e542312",
+		expectedPreTweakKey: "03789d937bade6673538f3e28d8368dda4d0512" +
+			"f94da44cf477a505716d26a1575",
+	}, {
+		name:    "v1.0.0rc2 three test vector keys BIP86 reverse order",
+		version: MuSig2Version100RC2,
+		keys: []*btcec.PublicKey{
+			testVector100Key3, testVector100Key2, testVector100Key1,
+		},
+		sortKeys: false,
+		tweak:    bip86Tweak,
+		expectedFinalKey: "03d61d333ab8c53c330290c144f406ce0c0dc3564b" +
+			"8e3dee6d1daa6288609bfc75",
+		expectedPreTweakKey: "036204de8b083426dc6eaf9502d27024d53fc82" +
+			"6bf7d2012148a0575435df54b2b",
+	}}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(tt *testing.T) {
+			tt.Parallel()
+
+			res, err := MuSig2CombineKeys(
+				tc.version, tc.keys, tc.sortKeys, tc.tweak,
+			)
+
+			if tc.expectedErr != "" {
+				require.ErrorContains(tt, err, tc.expectedErr)
+				return
+			}
+
+			require.NoError(tt, err)
+
+			finalKey := res.FinalKey.SerializeCompressed()
+			preTweakKey := res.PreTweakedKey.SerializeCompressed()
+			require.Equal(
+				tt, tc.expectedFinalKey,
+				hex.EncodeToString(finalKey),
+			)
+			require.Equal(
+				tt, tc.expectedPreTweakKey,
+				hex.EncodeToString(preTweakKey),
+			)
+		})
+	}
+}
diff --git a/input/test_utils.go b/input/test_utils.go
index 99b8e050f..c7e26f9e7 100644
--- a/input/test_utils.go
+++ b/input/test_utils.go
@@ -139,7 +139,7 @@ func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor
 // all signing parties must be provided, including the public key of the local
 // signing key. If nonces of other parties are already known, they can be
 // submitted as well to reduce the number of method calls necessary later on.
-func (m *MockSigner) MuSig2CreateSession(keychain.KeyLocator,
+func (m *MockSigner) MuSig2CreateSession(MuSig2Version, keychain.KeyLocator,
 	[]*btcec.PublicKey, *MuSig2Tweaks,
 	[][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, error) {
 
diff --git a/internal/musig2v040/bench_test.go b/internal/musig2v040/bench_test.go
new file mode 100644
index 000000000..21e014e2f
--- /dev/null
+++ b/internal/musig2v040/bench_test.go
@@ -0,0 +1,313 @@
+// Copyright 2013-2022 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package musig2v040
+
+import (
+	"encoding/hex"
+	"fmt"
+	"testing"
+
+	"github.com/btcsuite/btcd/btcec/v2"
+	"github.com/btcsuite/btcd/btcec/v2/schnorr"
+)
+
+var (
+	testPrivBytes = hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d")
+
+	testMsg = hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
+)
+
+func hexToBytes(s string) []byte {
+	b, err := hex.DecodeString(s)
+	if err != nil {
+		panic("invalid hex in source file: " + s)
+	}
+	return b
+}
+
+func hexToModNScalar(s string) *btcec.ModNScalar {
+	b, err := hex.DecodeString(s)
+	if err != nil {
+		panic("invalid hex in source file: " + s)
+	}
+	var scalar btcec.ModNScalar
+	if overflow := scalar.SetByteSlice(b); overflow {
+		panic("hex in source file overflows mod N scalar: " + s)
+	}
+	return &scalar
+}
+
+func genSigner(t *testing.B) signer {
+	privKey, err := btcec.NewPrivateKey()
+	if err != nil {
+		t.Fatalf("unable to gen priv key: %v", err)
+	}
+
+	pubKey, err := schnorr.ParsePubKey(
+		schnorr.SerializePubKey(privKey.PubKey()),
+	)
+	if err != nil {
+		t.Fatalf("unable to gen key: %v", err)
+	}
+
+	nonces, err := GenNonces()
+	if err != nil {
+		t.Fatalf("unable to gen nonces: %v", err)
+	}
+
+	return signer{
+		privKey: privKey,
+		pubKey:  pubKey,
+		nonces:  nonces,
+	}
+}
+
+var (
+	testSig *PartialSignature
+	testErr error
+)
+
+// BenchmarkPartialSign benchmarks how long it takes to generate a partial
+// signature factoring in if the keys are sorted and also if we're in fast sign
+// mode.
+func BenchmarkPartialSign(b *testing.B) {
+	for _, numSigners := range []int{10, 100} {
+		for _, fastSign := range []bool{true, false} {
+			for _, sortKeys := range []bool{true, false} {
+				name := fmt.Sprintf("num_signers=%v/fast_sign=%v/sort=%v",
+					numSigners, fastSign, sortKeys)
+
+				signers := make(signerSet, numSigners)
+				for i := 0; i < numSigners; i++ {
+					signers[i] = genSigner(b)
+				}
+
+				combinedNonce, err := AggregateNonces(signers.pubNonces())
+				if err != nil {
+					b.Fatalf("unable to generate combined nonce: %v", err)
+				}
+
+				var sig *PartialSignature
+
+				var msg [32]byte
+				copy(msg[:], testMsg[:])
+
+				keys := signers.keys()
+
+				b.Run(name, func(b *testing.B) {
+					var signOpts []SignOption
+					if fastSign {
+						signOpts = append(signOpts, WithFastSign())
+					}
+					if sortKeys {
+						signOpts = append(signOpts, WithSortedKeys())
+					}
+
+					b.ResetTimer()
+					b.ReportAllocs()
+
+					for i := 0; i < b.N; i++ {
+						sig, err = Sign(
+							signers[0].nonces.SecNonce, signers[0].privKey,
+							combinedNonce, keys, msg, signOpts...,
+						)
+						if err != nil {
+							b.Fatalf("unable to generate sig: %v", err)
+						}
+					}
+
+					testSig = sig
+					testErr = err
+				})
+			}
+		}
+	}
+}
+
+// TODO(roasbeef): add impact of sorting ^
+
+var sigOk bool
+
+// BenchmarkPartialVerify benchmarks how long it takes to verify a partial
+// signature.
+func BenchmarkPartialVerify(b *testing.B) {
+	for _, numSigners := range []int{10, 100} {
+		for _, sortKeys := range []bool{true, false} {
+			name := fmt.Sprintf("sort_keys=%v/num_signers=%v",
+				sortKeys, numSigners)
+
+			signers := make(signerSet, numSigners)
+			for i := 0; i < numSigners; i++ {
+				signers[i] = genSigner(b)
+			}
+
+			combinedNonce, err := AggregateNonces(
+				signers.pubNonces(),
+			)
+			if err != nil {
+				b.Fatalf("unable to generate combined "+
+					"nonce: %v", err)
+			}
+
+			var sig *PartialSignature
+
+			var msg [32]byte
+			copy(msg[:], testMsg[:])
+
+			b.ReportAllocs()
+			b.ResetTimer()
+
+			sig, err = Sign(
+				signers[0].nonces.SecNonce, signers[0].privKey,
+				combinedNonce, signers.keys(), msg,
+			)
+			if err != nil {
+				b.Fatalf("unable to generate sig: %v", err)
+			}
+
+			keys := signers.keys()
+			pubKey := signers[0].pubKey
+
+			b.Run(name, func(b *testing.B) {
+				var signOpts []SignOption
+				if sortKeys {
+					signOpts = append(
+						signOpts, WithSortedKeys(),
+					)
+				}
+
+				b.ResetTimer()
+				b.ReportAllocs()
+
+				var ok bool
+				for i := 0; i < b.N; i++ {
+					ok = sig.Verify(
+						signers[0].nonces.PubNonce, combinedNonce,
+						keys, pubKey, msg,
+					)
+					if !ok {
+						b.Fatalf("generated invalid sig!")
+					}
+				}
+				sigOk = ok
+			})
+
+		}
+	}
+}
+
+var finalSchnorrSig *schnorr.Signature
+
+// BenchmarkCombineSigs benchmarks how long it takes to combine a set amount of
+// signatures.
+func BenchmarkCombineSigs(b *testing.B) {
+
+	for _, numSigners := range []int{10, 100} {
+		signers := make(signerSet, numSigners)
+		for i := 0; i < numSigners; i++ {
+			signers[i] = genSigner(b)
+		}
+
+		combinedNonce, err := AggregateNonces(signers.pubNonces())
+		if err != nil {
+			b.Fatalf("unable to generate combined nonce: %v", err)
+		}
+
+		var msg [32]byte
+		copy(msg[:], testMsg[:])
+
+		var finalNonce *btcec.PublicKey
+		for i := range signers {
+			signer := signers[i]
+			partialSig, err := Sign(
+				signer.nonces.SecNonce, signer.privKey,
+				combinedNonce, signers.keys(), msg,
+			)
+			if err != nil {
+				b.Fatalf("unable to generate partial sig: %v",
+					err)
+			}
+
+			signers[i].partialSig = partialSig
+
+			if finalNonce == nil {
+				finalNonce = partialSig.R
+			}
+		}
+
+		sigs := signers.partialSigs()
+
+		name := fmt.Sprintf("num_signers=%v", numSigners)
+		b.Run(name, func(b *testing.B) {
+			b.ResetTimer()
+			b.ReportAllocs()
+
+			finalSig := CombineSigs(finalNonce, sigs)
+
+			finalSchnorrSig = finalSig
+		})
+	}
+}
+
+var testNonce [PubNonceSize]byte
+
+// BenchmarkAggregateNonces benchmarks how long it takes to combine nonces.
+func BenchmarkAggregateNonces(b *testing.B) {
+	for _, numSigners := range []int{10, 100} {
+		signers := make(signerSet, numSigners)
+		for i := 0; i < numSigners; i++ {
+			signers[i] = genSigner(b)
+		}
+
+		nonces := signers.pubNonces()
+
+		name := fmt.Sprintf("num_signers=%v", numSigners)
+		b.Run(name, func(b *testing.B) {
+			b.ResetTimer()
+			b.ReportAllocs()
+
+			pubNonce, err := AggregateNonces(nonces)
+			if err != nil {
+				b.Fatalf("unable to generate nonces: %v", err)
+			}
+
+			testNonce = pubNonce
+		})
+	}
+}
+
+var testKey *btcec.PublicKey
+
+// BenchmarkAggregateKeys benchmarks how long it takes to aggregate public
+// keys.
+func BenchmarkAggregateKeys(b *testing.B) {
+	for _, numSigners := range []int{10, 100} {
+		for _, sortKeys := range []bool{true, false} {
+			signers := make(signerSet, numSigners)
+			for i := 0; i < numSigners; i++ {
+				signers[i] = genSigner(b)
+			}
+
+			signerKeys := signers.keys()
+
+			name := fmt.Sprintf("num_signers=%v/sort_keys=%v",
+				numSigners, sortKeys)
+
+			uniqueKeyIndex := secondUniqueKeyIndex(signerKeys, false)
+
+			b.Run(name, func(b *testing.B) {
+				b.ResetTimer()
+				b.ReportAllocs()
+
+				aggKey, _, _, _ := AggregateKeys(
+					signerKeys, sortKeys,
+					WithUniqueKeyIndex(uniqueKeyIndex),
+				)
+
+				testKey = aggKey.FinalKey
+			})
+		}
+	}
+}
diff --git a/internal/musig2v040/musig2_test.go b/internal/musig2v040/musig2_test.go
new file mode 100644
index 000000000..fbd7f13ee
--- /dev/null
+++ b/internal/musig2v040/musig2_test.go
@@ -0,0 +1,1897 @@
+// Copyright 2013-2022 The btcsuite developers
+
+package musig2v040
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"sync"
+	"testing"
+
+	"github.com/btcsuite/btcd/btcec/v2"
+	"github.com/btcsuite/btcd/btcec/v2/schnorr"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
+	"github.com/decred/dcrd/dcrec/secp256k1/v4"
+)
+
+var (
+	key1Bytes, _ = hex.DecodeString("F9308A019258C31049344F85F89D5229B53" +
+		"1C845836F99B08601F113BCE036F9")
+	key2Bytes, _ = hex.DecodeString("DFF1D77F2A671C5F36183726DB2341BE58F" +
+		"EAE1DA2DECED843240F7B502BA659")
+	key3Bytes, _ = hex.DecodeString("3590A94E768F8E1815C2F24B4D80A8E3149" +
+		"316C3518CE7B7AD338368D038CA66")
+
+	invalidPk1, _ = hex.DecodeString("00000000000000000000000000000000" +
+		"00000000000000000000000000000005")
+	invalidPk2, _ = hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +
+		"FFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30")
+	invalidTweak, _ = hex.DecodeString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" +
+		"BAAEDCE6AF48A03BBFD25E8CD0364141")
+
+	testKeys = [][]byte{key1Bytes, key2Bytes, key3Bytes, invalidPk1,
+		invalidPk2}
+
+	keyCombo1, _ = hex.DecodeString("E5830140512195D74C8307E39637CBE5FB73" +
+		"0EBEAB80EC514CF88A877CEEEE0B")
+	keyCombo2, _ = hex.DecodeString("D70CD69A2647F7390973DF48CBFA2CCC407B" +
+		"8B2D60B08C5F1641185C7998A290")
+	keyCombo3, _ = hex.DecodeString("81A8B093912C9E481408D09776CEFB48AEB8" +
+		"B65481B6BAAFB3C5810106717BEB")
+	keyCombo4, _ = hex.DecodeString("2EB18851887E7BDC5E830E89B19DDBC28078" +
+		"F1FA88AAD0AD01CA06FE4F80210B")
+)
+
+// getInfinityTweak returns a tweak that, when tweaking the Generator, triggers
+// the ErrTweakedKeyIsInfinity error.
+func getInfinityTweak() KeyTweakDesc {
+	generator := btcec.Generator()
+
+	keySet := []*btcec.PublicKey{generator}
+
+	keysHash := keyHashFingerprint(keySet, true)
+	uniqueKeyIndex := secondUniqueKeyIndex(keySet, true)
+
+	n := &btcec.ModNScalar{}
+
+	n.SetByteSlice(invalidTweak)
+
+	coeff := aggregationCoefficient(
+		keySet, generator, keysHash, uniqueKeyIndex,
+	).Negate().Add(n)
+
+	return KeyTweakDesc{
+		Tweak:   coeff.Bytes(),
+		IsXOnly: false,
+	}
+}
+
+const (
+	keyAggTestVectorName = "key_agg_vectors.json"
+
+	nonceAggTestVectorName = "nonce_agg_vectors.json"
+
+	signTestVectorName = "sign_vectors.json"
+)
+
+var dumpJson = flag.Bool("dumpjson", false, "if true, a JSON version of the "+
+	"test vectors will be written to the cwd")
+
+type jsonKeyAggTestCase struct {
+	Keys          []string    `json:"keys"`
+	Tweaks        []jsonTweak `json:"tweaks"`
+	ExpectedKey   string      `json:"expected_key"`
+	ExpectedError string      `json:"expected_error"`
+}
+
+// TestMuSig2KeyAggTestVectors tests that this implementation of musig2 key
+// aggregation lines up with the secp256k1-zkp test vectors.
+func TestMuSig2KeyAggTestVectors(t *testing.T) {
+	t.Parallel()
+
+	var jsonCases []jsonKeyAggTestCase
+
+	testCases := []struct {
+		keyOrder      []int
+		explicitKeys  []*btcec.PublicKey
+		tweaks        []KeyTweakDesc
+		expectedKey   []byte
+		expectedError error
+	}{
+		// Keys in backwards lexicographical order.
+		{
+			keyOrder:    []int{0, 1, 2},
+			expectedKey: keyCombo1,
+		},
+
+		// Keys in sorted order.
+		{
+			keyOrder:    []int{2, 1, 0},
+			expectedKey: keyCombo2,
+		},
+
+		// Only the first key.
+		{
+			keyOrder:    []int{0, 0, 0},
+			expectedKey: keyCombo3,
+		},
+
+		// Duplicate the first key and second keys.
+		{
+			keyOrder:    []int{0, 0, 1, 1},
+			expectedKey: keyCombo4,
+		},
+
+		// Invalid public key.
+		{
+			keyOrder:      []int{0, 3},
+			expectedError: secp256k1.ErrPubKeyNotOnCurve,
+		},
+
+		// Public key exceeds field size.
+		{
+			keyOrder:      []int{0, 4},
+			expectedError: secp256k1.ErrPubKeyXTooBig,
+		},
+
+		//  Tweak is out of range.
+		{
+			keyOrder: []int{0, 1},
+			tweaks: []KeyTweakDesc{
+				{
+					Tweak:   to32ByteSlice(invalidTweak),
+					IsXOnly: true,
+				},
+			},
+			expectedError: ErrTweakedKeyOverflows,
+		},
+
+		// Intermediate tweaking result is point at infinity.
+		{
+			explicitKeys: []*secp256k1.PublicKey{btcec.Generator()},
+			tweaks: []KeyTweakDesc{
+				getInfinityTweak(),
+			},
+			expectedError: ErrTweakedKeyIsInfinity,
+		},
+	}
+	for i, testCase := range testCases {
+		testName := fmt.Sprintf("%v", testCase.keyOrder)
+		t.Run(testName, func(t *testing.T) {
+			var (
+				keys      []*btcec.PublicKey
+				strKeys   []string
+				strTweaks []jsonTweak
+				jsonError string
+			)
+			for _, keyIndex := range testCase.keyOrder {
+				keyBytes := testKeys[keyIndex]
+				pub, err := schnorr.ParsePubKey(keyBytes)
+
+				switch {
+				case testCase.expectedError != nil &&
+					errors.Is(err, testCase.expectedError):
+					return
+				case err != nil:
+					t.Fatalf("unable to parse pubkeys: %v", err)
+				}
+
+				keys = append(keys, pub)
+				strKeys = append(strKeys, hex.EncodeToString(keyBytes))
+			}
+
+			for _, explicitKey := range testCase.explicitKeys {
+				keys = append(keys, explicitKey)
+				strKeys = append(
+					strKeys,
+					hex.EncodeToString(
+						explicitKey.SerializeCompressed(),
+					))
+			}
+
+			for _, tweak := range testCase.tweaks {
+				strTweaks = append(
+					strTweaks,
+					jsonTweak{
+						Tweak: hex.EncodeToString(
+							tweak.Tweak[:],
+						),
+						XOnly: tweak.IsXOnly,
+					})
+			}
+
+			if testCase.expectedError != nil {
+				jsonError = testCase.expectedError.Error()
+			}
+
+			jsonCases = append(
+				jsonCases,
+				jsonKeyAggTestCase{
+					Keys:   strKeys,
+					Tweaks: strTweaks,
+					ExpectedKey: hex.EncodeToString(
+						testCase.expectedKey),
+					ExpectedError: jsonError,
+				})
+
+			uniqueKeyIndex := secondUniqueKeyIndex(keys, false)
+			opts := []KeyAggOption{WithUniqueKeyIndex(uniqueKeyIndex)}
+			if len(testCase.tweaks) > 0 {
+				opts = append(opts, WithKeyTweaks(testCase.tweaks...))
+			}
+
+			combinedKey, _, _, err := AggregateKeys(
+				keys, false, opts...,
+			)
+
+			switch {
+			case testCase.expectedError != nil &&
+				errors.Is(err, testCase.expectedError):
+				return
+
+			case err != nil:
+				t.Fatalf("case #%v, got error %v", i, err)
+			}
+
+			combinedKeyBytes := schnorr.SerializePubKey(combinedKey.FinalKey)
+			if !bytes.Equal(combinedKeyBytes, testCase.expectedKey) {
+				t.Fatalf("case: #%v, invalid aggregation: "+
+					"expected %x, got %x", i, testCase.expectedKey,
+					combinedKeyBytes)
+			}
+		})
+	}
+
+	if *dumpJson {
+		jsonBytes, err := json.Marshal(jsonCases)
+		if err != nil {
+			t.Fatalf("unable to encode json: %v", err)
+		}
+
+		var formattedJson bytes.Buffer
+		json.Indent(&formattedJson, jsonBytes, "", "\t")
+		err = ioutil.WriteFile(
+			keyAggTestVectorName, formattedJson.Bytes(), 0644,
+		)
+		if err != nil {
+			t.Fatalf("unable to write file: %v", err)
+		}
+	}
+}
+
+func mustParseHex(str string) []byte {
+	b, err := hex.DecodeString(str)
+	if err != nil {
+		panic(fmt.Errorf("unable to parse hex: %v", err))
+	}
+
+	return b
+}
+
+func parseKey(xHex string) *btcec.PublicKey {
+	xB, err := hex.DecodeString(xHex)
+	if err != nil {
+		panic(err)
+	}
+
+	var x, y btcec.FieldVal
+	x.SetByteSlice(xB)
+	if !btcec.DecompressY(&x, false, &y) {
+		panic("x not on curve")
+	}
+	y.Normalize()
+
+	return btcec.NewPublicKey(&x, &y)
+}
+
+var (
+	signSetPrivKey, _ = btcec.PrivKeyFromBytes(
+		mustParseHex("7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DF" +
+			"E8D76D7F2D1007671"),
+	)
+	signSetPubKey = schnorr.SerializePubKey(signSetPrivKey.PubKey())
+
+	signTestMsg = mustParseHex("F95466D086770E689964664219266FE5ED215C92A" +
+		"E20BAB5C9D79ADDDDF3C0CF")
+
+	signSetKey2 = mustParseHex("F9308A019258C31049344F85F89D5229B531C8458" +
+		"36F99B08601F113BCE036F9")
+
+	signSetKey3 = mustParseHex("DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA" +
+		"2DECED843240F7B502BA659")
+
+	invalidSetKey1 = mustParseHex("00000000000000000000000000000000" +
+		"00000000000000000000000000000007")
+
+	signExpected1 = mustParseHex("68537CC5234E505BD14061F8DA9E90C220A1818" +
+		"55FD8BDB7F127BB12403B4D3B")
+	signExpected2 = mustParseHex("2DF67BFFF18E3DE797E13C6475C963048138DAE" +
+		"C5CB20A357CECA7C8424295EA")
+	signExpected3 = mustParseHex("0D5B651E6DE34A29A12DE7A8B4183B4AE6A7F7F" +
+		"BE15CDCAFA4A3D1BCAABC7517")
+
+	signExpected4 = mustParseHex("8D5E0407FB4756EEBCD86264C32D792EE36EEB6" +
+		"9E952BBB30B8E41BEBC4D22FA")
+
+	signSetKeys = [][]byte{signSetPubKey, signSetKey2, signSetKey3, invalidPk1}
+
+	aggregatedNonce = toPubNonceSlice(mustParseHex("028465FCF0BBDBCF443AA" +
+		"BCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926" +
+		"D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9"))
+	verifyPnonce1 = mustParsePubNonce("0337C87821AFD50A8644D820A8F3E02E49" +
+		"9C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352A" +
+		"A9405D1428C15F4B75F04DAE642A95C2548480")
+	verifyPnonce2 = mustParsePubNonce("0279BE667EF9DCBBAC55A06295CE870B07" +
+		"029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE" +
+		"870B07029BFCDB2DCE28D959F2815B16F81798")
+	verifyPnonce3 = mustParsePubNonce("032DE2662628C90B03F5E720284EB52FF7" +
+		"D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1C" +
+		"A6BEB2090C93D930321071AD40B2F44E599046")
+	verifyPnonce4 = mustParsePubNonce("0237C87821AFD50A8644D820A8F3E02E49" +
+		"9C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352A" +
+		"A9405D1428C15F4B75F04DAE642A95C2548480")
+
+	tweak1 = KeyTweakDesc{
+		Tweak: [32]byte{
+			0xE8, 0xF7, 0x91, 0xFF, 0x92, 0x25, 0xA2, 0xAF,
+			0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, 0x72, 0x3D,
+			0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79,
+			0x80, 0x2B, 0x26, 0x3C, 0xDF, 0xCD, 0x83, 0xBB,
+		},
+	}
+	tweak2 = KeyTweakDesc{
+		Tweak: [32]byte{
+			0xae, 0x2e, 0xa7, 0x97, 0xcc, 0xf, 0xe7, 0x2a,
+			0xc5, 0xb9, 0x7b, 0x97, 0xf3, 0xc6, 0x95, 0x7d,
+			0x7e, 0x41, 0x99, 0xa1, 0x67, 0xa5, 0x8e, 0xb0,
+			0x8b, 0xca, 0xff, 0xda, 0x70, 0xac, 0x4, 0x55,
+		},
+	}
+	tweak3 = KeyTweakDesc{
+		Tweak: [32]byte{
+			0xf5, 0x2e, 0xcb, 0xc5, 0x65, 0xb3, 0xd8, 0xbe,
+			0xa2, 0xdf, 0xd5, 0xb7, 0x5a, 0x4f, 0x45, 0x7e,
+			0x54, 0x36, 0x98, 0x9, 0x32, 0x2e, 0x41, 0x20,
+			0x83, 0x16, 0x26, 0xf2, 0x90, 0xfa, 0x87, 0xe0,
+		},
+	}
+	tweak4 = KeyTweakDesc{
+		Tweak: [32]byte{
+			0x19, 0x69, 0xad, 0x73, 0xcc, 0x17, 0x7f, 0xa0,
+			0xb4, 0xfc, 0xed, 0x6d, 0xf1, 0xf7, 0xbf, 0x99,
+			0x7, 0xe6, 0x65, 0xfd, 0xe9, 0xba, 0x19, 0x6a,
+			0x74, 0xfe, 0xd0, 0xa3, 0xcf, 0x5a, 0xef, 0x9d,
+		},
+	}
+)
+
+func formatTweakParity(tweaks []KeyTweakDesc) string {
+	var s string
+	for _, tweak := range tweaks {
+		s += fmt.Sprintf("%v/", tweak.IsXOnly)
+	}
+
+	// Snip off that last '/'.
+	s = s[:len(s)-1]
+
+	return s
+}
+
+func genTweakParity(tweak KeyTweakDesc, isXOnly bool) KeyTweakDesc {
+	tweak.IsXOnly = isXOnly
+	return tweak
+}
+
+type jsonTweak struct {
+	Tweak string `json:"tweak"`
+	XOnly bool   `json:"x_only"`
+}
+
+type jsonTweakSignCase struct {
+	Keys     []string    `json:"keys"`
+	Tweaks   []jsonTweak `json:"tweaks,omitempty"`
+	AggNonce string      `json:"agg_nonce"`
+
+	ExpectedSig   string `json:"expected_sig"`
+	ExpectedError string `json:"expected_error`
+}
+
+type jsonSignTestCase struct {
+	SecNonce   string `json:"secret_nonce"`
+	SigningKey string `json:"signing_key"`
+	Msg        string `json:"msg"`
+
+	TestCases []jsonTweakSignCase `json:"test_cases"`
+}
+
+// TestMuSig2SigningTestVectors tests that the musig2 implementation produces
+// the same set of signatures.
+func TestMuSig2SigningTestVectors(t *testing.T) {
+	t.Parallel()
+
+	var jsonCases jsonSignTestCase
+
+	jsonCases.SigningKey = hex.EncodeToString(signSetPrivKey.Serialize())
+	jsonCases.Msg = hex.EncodeToString(signTestMsg)
+
+	var secNonce [SecNonceSize]byte
+	copy(secNonce[:], mustParseHex("508B81A611F100A6B2B6B29656590898AF488B"+
+		"CF2E1F55CF22E5CFB84421FE61"))
+	copy(secNonce[32:], mustParseHex("FA27FD49B1D50085B481285E1CA205D55C82"+
+		"CC1B31FF5CD54A489829355901F7"))
+
+	jsonCases.SecNonce = hex.EncodeToString(secNonce[:])
+
+	testCases := []struct {
+		keyOrder           []int
+		aggNonce           [66]byte
+		expectedPartialSig []byte
+		tweaks             []KeyTweakDesc
+		expectedError      error
+	}{
+		// Vector 1
+		{
+			keyOrder:           []int{0, 1, 2},
+			aggNonce:           aggregatedNonce,
+			expectedPartialSig: signExpected1,
+		},
+
+		// Vector 2
+		{
+			keyOrder:           []int{1, 0, 2},
+			aggNonce:           aggregatedNonce,
+			expectedPartialSig: signExpected2,
+		},
+
+		// Vector 3
+		{
+			keyOrder:           []int{1, 2, 0},
+			aggNonce:           aggregatedNonce,
+			expectedPartialSig: signExpected3,
+		},
+		// Vector 4 Both halves of aggregate nonce correspond to point at infinity
+		{
+			keyOrder:           []int{0, 1},
+			aggNonce:           mustNonceAgg([][66]byte{verifyPnonce1, verifyPnonce4}),
+			expectedPartialSig: signExpected4,
+		},
+
+		// Vector 5: Signer 2 provided an invalid public key
+		{
+			keyOrder:      []int{1, 0, 3},
+			aggNonce:      aggregatedNonce,
+			expectedError: secp256k1.ErrPubKeyNotOnCurve,
+		},
+
+		// Vector 6: Aggregate nonce is invalid due wrong tag, 0x04,
+		// in the first half.
+		{
+
+			keyOrder: []int{1, 2, 0},
+			aggNonce: toPubNonceSlice(
+				mustParseHex("048465FCF0BBDBCF443AABCCE533D42" +
+					"B4B5A10966AC09A49655E8C42DAAB8FCD610" +
+					"37496A3CC86926D452CAFCFD55D25972CA16" +
+					"75D549310DE296BFF42F72EEEA8C9")),
+			expectedError: secp256k1.ErrPubKeyInvalidFormat,
+		},
+
+		// Vector 7: Aggregate nonce is invalid because the second half
+		// does not correspond to an X coordinate.
+		{
+
+			keyOrder: []int{1, 2, 0},
+			aggNonce: toPubNonceSlice(
+				mustParseHex("028465FCF0BBDBCF443AABCCE533D42" +
+					"B4B5A10966AC09A49655E8C42DAAB8FCD610" +
+					"200000000000000000000000000000000000" +
+					"00000000000000000000000000009")),
+			expectedError: secp256k1.ErrPubKeyNotOnCurve,
+		},
+
+		// Vector 8: Aggregate nonce is invalid because the second half
+		// exceeds field size.
+		{
+
+			keyOrder: []int{1, 2, 0},
+			aggNonce: toPubNonceSlice(
+				mustParseHex("028465FCF0BBDBCF443AABCCE533D42" +
+					"B4B5A10966AC09A49655E8C42DAAB8FCD610" +
+					"2FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +
+					"FFFFFFFFFFFFFFFFFFFFEFFFFFC30")),
+			expectedError: secp256k1.ErrPubKeyXTooBig,
+		},
+
+		// A single x-only tweak.
+		{
+			keyOrder: []int{1, 2, 0},
+			aggNonce: aggregatedNonce,
+			expectedPartialSig: mustParseHex("5e24c7496b565debc3b" +
+				"9639e6f1304a21597f9603d3ab05b4913641775e1375b"),
+			tweaks: []KeyTweakDesc{genTweakParity(tweak1, true)},
+		},
+
+		// A single ordinary tweak.
+		{
+			keyOrder: []int{1, 2, 0},
+			aggNonce: aggregatedNonce,
+			expectedPartialSig: mustParseHex("78408ddcab4813d1394c" +
+				"97d493ef1084195c1d4b52e63ecd7bc5991644e44ddd"),
+			tweaks: []KeyTweakDesc{genTweakParity(tweak1, false)},
+		},
+
+		// An ordinary tweak then an x-only tweak.
+		{
+			keyOrder: []int{1, 2, 0},
+			aggNonce: aggregatedNonce,
+			expectedPartialSig: mustParseHex("C3A829A81480E36EC3A" +
+				"B052964509A94EBF34210403D16B226A6F16EC85B7357"),
+			tweaks: []KeyTweakDesc{
+				genTweakParity(tweak1, false),
+				genTweakParity(tweak2, true),
+			},
+		},
+
+		// Four tweaks, in the order: x-only, ordinary, x-only, ordinary.
+		{
+			keyOrder: []int{1, 2, 0},
+			aggNonce: aggregatedNonce,
+			expectedPartialSig: mustParseHex("8C4473C6A382BD3C4AD" +
+				"7BE59818DA5ED7CF8CEC4BC21996CFDA08BB4316B8BC7"),
+			tweaks: []KeyTweakDesc{
+				genTweakParity(tweak1, true),
+				genTweakParity(tweak2, false),
+				genTweakParity(tweak3, true),
+				genTweakParity(tweak4, false),
+			},
+		},
+	}
+
+	var msg [32]byte
+	copy(msg[:], signTestMsg)
+
+	for _, testCase := range testCases {
+		testName := fmt.Sprintf("%v/tweak=%v", testCase.keyOrder, len(testCase.tweaks) != 0)
+		if len(testCase.tweaks) != 0 {
+			testName += fmt.Sprintf("/x_only=%v", formatTweakParity(testCase.tweaks))
+		}
+		t.Run(testName, func(t *testing.T) {
+			keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder))
+			for _, keyIndex := range testCase.keyOrder {
+				keyBytes := signSetKeys[keyIndex]
+				pub, err := schnorr.ParsePubKey(keyBytes)
+
+				switch {
+				case testCase.expectedError != nil &&
+					errors.Is(err, testCase.expectedError):
+
+					return
+				case err != nil:
+					t.Fatalf("unable to parse pubkeys: %v", err)
+				}
+
+				keySet = append(keySet, pub)
+			}
+
+			var opts []SignOption
+			if len(testCase.tweaks) != 0 {
+				opts = append(
+					opts, WithTweaks(testCase.tweaks...),
+				)
+			}
+
+			partialSig, err := Sign(
+				secNonce, signSetPrivKey, testCase.aggNonce,
+				keySet, msg, opts...,
+			)
+
+			switch {
+			case testCase.expectedError != nil &&
+				errors.Is(err, testCase.expectedError):
+
+				return
+			case err != nil:
+				t.Fatalf("unable to generate partial sig: %v", err)
+			}
+
+			var partialSigBytes [32]byte
+			partialSig.S.PutBytesUnchecked(partialSigBytes[:])
+
+			if !bytes.Equal(partialSigBytes[:], testCase.expectedPartialSig) {
+				t.Fatalf("sigs don't match: expected %x, got %x",
+					testCase.expectedPartialSig, partialSigBytes,
+				)
+			}
+
+		})
+
+		if *dumpJson {
+			var (
+				strKeys   []string
+				jsonError string
+			)
+
+			for _, keyIndex := range testCase.keyOrder {
+				keyBytes := signSetKeys[keyIndex]
+				strKeys = append(strKeys, hex.EncodeToString(keyBytes))
+			}
+
+			if testCase.expectedError != nil {
+				jsonError = testCase.expectedError.Error()
+			}
+
+			tweakSignCase := jsonTweakSignCase{
+				Keys:          strKeys,
+				ExpectedSig:   hex.EncodeToString(testCase.expectedPartialSig),
+				AggNonce:      hex.EncodeToString(testCase.aggNonce[:]),
+				ExpectedError: jsonError,
+			}
+
+			var jsonTweaks []jsonTweak
+			for _, tweak := range testCase.tweaks {
+				jsonTweaks = append(
+					jsonTweaks,
+					jsonTweak{
+						Tweak: hex.EncodeToString(tweak.Tweak[:]),
+						XOnly: tweak.IsXOnly,
+					})
+			}
+			tweakSignCase.Tweaks = jsonTweaks
+
+			jsonCases.TestCases = append(jsonCases.TestCases, tweakSignCase)
+		}
+	}
+
+	if *dumpJson {
+		jsonBytes, err := json.Marshal(jsonCases)
+		if err != nil {
+			t.Fatalf("unable to encode json: %v", err)
+		}
+
+		var formattedJson bytes.Buffer
+		json.Indent(&formattedJson, jsonBytes, "", "\t")
+		err = ioutil.WriteFile(
+			signTestVectorName, formattedJson.Bytes(), 0644,
+		)
+		if err != nil {
+			t.Fatalf("unable to write file: %v", err)
+		}
+	}
+}
+
+func TestMusig2PartialSigVerifyTestVectors(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		partialSig    []byte
+		nonces        [][66]byte
+		pubnonceIndex int
+		keyOrder      []int
+		tweaks        []KeyTweakDesc
+		expectedError error
+	}{
+		// A single x-only tweak.
+		{
+			keyOrder: []int{1, 2, 0},
+			nonces: [][66]byte{
+				verifyPnonce2,
+				verifyPnonce3,
+				verifyPnonce1,
+			},
+			pubnonceIndex: 2,
+			partialSig: mustParseHex("5e24c7496b565debc3b9639e" +
+				"6f1304a21597f9603d3ab05b4913641775e1375b"),
+			tweaks: []KeyTweakDesc{genTweakParity(tweak1, true)},
+		},
+		// A single ordinary tweak.
+		{
+			keyOrder: []int{1, 2, 0},
+			nonces: [][66]byte{
+				verifyPnonce2,
+				verifyPnonce3,
+				verifyPnonce1,
+			},
+			pubnonceIndex: 2,
+			partialSig: mustParseHex("78408ddcab4813d1394c97d4" +
+				"93ef1084195c1d4b52e63ecd7bc5991644e44ddd"),
+			tweaks: []KeyTweakDesc{genTweakParity(tweak1, false)},
+		},
+		// An ordinary tweak then an x-only tweak.
+		{
+			keyOrder: []int{1, 2, 0},
+			nonces: [][66]byte{
+				verifyPnonce2,
+				verifyPnonce3,
+				verifyPnonce1,
+			},
+			pubnonceIndex: 2,
+			partialSig: mustParseHex("C3A829A81480E36EC3AB0529" +
+				"64509A94EBF34210403D16B226A6F16EC85B7357"),
+			tweaks: []KeyTweakDesc{
+				genTweakParity(tweak1, false),
+				genTweakParity(tweak2, true),
+			},
+		},
+
+		// Four tweaks, in the order: x-only, ordinary, x-only, ordinary.
+		{
+			keyOrder: []int{1, 2, 0},
+			nonces: [][66]byte{
+				verifyPnonce2,
+				verifyPnonce3,
+				verifyPnonce1,
+			},
+			pubnonceIndex: 2,
+			partialSig: mustParseHex("8C4473C6A382BD3C4AD7BE5" +
+				"9818DA5ED7CF8CEC4BC21996CFDA08BB4316B8BC7"),
+			tweaks: []KeyTweakDesc{
+				genTweakParity(tweak1, true),
+				genTweakParity(tweak2, false),
+				genTweakParity(tweak3, true),
+				genTweakParity(tweak4, false),
+			},
+		},
+		// Vector 9.
+		{
+
+			partialSig:    signExpected1,
+			pubnonceIndex: 0,
+			keyOrder:      []int{0, 1, 2},
+			nonces: [][66]byte{
+				verifyPnonce1,
+				verifyPnonce2,
+				verifyPnonce3,
+			},
+		},
+		// Vector 10.
+		{
+
+			partialSig:    signExpected2,
+			pubnonceIndex: 1,
+			keyOrder:      []int{1, 0, 2},
+			nonces: [][66]byte{
+				verifyPnonce2,
+				verifyPnonce1,
+				verifyPnonce3,
+			},
+		},
+		// Vector 11.
+		{
+
+			partialSig:    signExpected3,
+			pubnonceIndex: 2,
+			keyOrder:      []int{1, 2, 0},
+			nonces: [][66]byte{
+				verifyPnonce2,
+				verifyPnonce3,
+				verifyPnonce1,
+			},
+		},
+		// Vector 12: Both halves of aggregate nonce correspond to
+		// point at infinity.
+		{
+
+			partialSig:    signExpected4,
+			pubnonceIndex: 0,
+			keyOrder:      []int{0, 1},
+			nonces: [][66]byte{
+				verifyPnonce1,
+				verifyPnonce4,
+			},
+		},
+		// Vector 13: Wrong signature (which is equal to the negation
+		// of valid signature expected[0]).
+		{
+
+			partialSig: mustParseHex("97AC833ADCB1AFA42EBF9E0" +
+				"725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406"),
+			pubnonceIndex: 0,
+			keyOrder:      []int{0, 1, 2},
+			nonces: [][66]byte{
+				verifyPnonce1,
+				verifyPnonce2,
+				verifyPnonce3,
+			},
+			expectedError: ErrPartialSigInvalid,
+		},
+		// Vector 12: Wrong signer.
+		{
+
+			partialSig:    signExpected1,
+			pubnonceIndex: 1,
+			keyOrder:      []int{0, 1, 2},
+			nonces: [][66]byte{
+				verifyPnonce1,
+				verifyPnonce2,
+				verifyPnonce3,
+			},
+			expectedError: ErrPartialSigInvalid,
+		},
+		// Vector 13: Signature exceeds group size.
+		{
+
+			partialSig: mustParseHex("FFFFFFFFFFFFFFFFFFFFFFFF" +
+				"FFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),
+			pubnonceIndex: 0,
+			keyOrder:      []int{0, 1, 2},
+			nonces: [][66]byte{
+				verifyPnonce1,
+				verifyPnonce2,
+				verifyPnonce3,
+			},
+			expectedError: ErrPartialSigInvalid,
+		},
+		// Vector 14: Invalid pubnonce.
+		{
+
+			partialSig:    signExpected1,
+			pubnonceIndex: 0,
+			keyOrder:      []int{0, 1, 2},
+			nonces: [][66]byte{
+				canParsePubNonce("020000000000000000000000000" +
+					"000000000000000000000000000000000000009"),
+				verifyPnonce2,
+				verifyPnonce3,
+			},
+			expectedError: secp256k1.ErrPubKeyNotOnCurve,
+		},
+		// Vector 15: Invalid public key.
+		{
+
+			partialSig:    signExpected1,
+			pubnonceIndex: 0,
+			keyOrder:      []int{3, 1, 2},
+			nonces: [][66]byte{
+				verifyPnonce1,
+				verifyPnonce2,
+				verifyPnonce3,
+			},
+			expectedError: secp256k1.ErrPubKeyNotOnCurve,
+		},
+	}
+
+	for _, testCase := range testCases {
+
+		// todo find name
+		testName := fmt.Sprintf("%v/tweak=%v", testCase.pubnonceIndex, testCase.keyOrder)
+
+		t.Run(testName, func(t *testing.T) {
+
+			combinedNonce, err := AggregateNonces(testCase.nonces)
+
+			switch {
+			case testCase.expectedError != nil &&
+				errors.Is(err, testCase.expectedError):
+
+				return
+			case err != nil:
+				t.Fatalf("unable to aggregate nonces %v", err)
+			}
+
+			keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder))
+			for _, keyIndex := range testCase.keyOrder {
+				keyBytes := signSetKeys[keyIndex]
+				pub, err := schnorr.ParsePubKey(keyBytes)
+
+				switch {
+				case testCase.expectedError != nil &&
+					errors.Is(err, testCase.expectedError):
+
+					return
+				case err != nil:
+					t.Fatalf("unable to parse pubkeys: %v", err)
+				}
+
+				keySet = append(keySet, pub)
+			}
+
+			ps := &PartialSignature{}
+			err = ps.Decode(bytes.NewBuffer(testCase.partialSig))
+
+			switch {
+			case testCase.expectedError != nil &&
+				errors.Is(err, testCase.expectedError):
+
+				return
+			case err != nil:
+				t.Fatal(err)
+			}
+
+			var opts []SignOption
+			if len(testCase.tweaks) != 0 {
+				opts = append(
+					opts, WithTweaks(testCase.tweaks...),
+				)
+			}
+
+			err = verifyPartialSig(
+				ps,
+				testCase.nonces[testCase.pubnonceIndex],
+				combinedNonce,
+				keySet,
+				signSetKeys[testCase.keyOrder[testCase.pubnonceIndex]],
+				to32ByteSlice(signTestMsg),
+				opts...,
+			)
+
+			switch {
+			case testCase.expectedError != nil &&
+				errors.Is(err, testCase.expectedError):
+
+				return
+			case err != nil:
+				t.Fatalf("unable to aggregate nonces %v", err)
+			}
+		})
+	}
+}
+
+type signer struct {
+	privKey *btcec.PrivateKey
+	pubKey  *btcec.PublicKey
+
+	nonces *Nonces
+
+	partialSig *PartialSignature
+}
+
+type signerSet []signer
+
+func (s signerSet) keys() []*btcec.PublicKey {
+	keys := make([]*btcec.PublicKey, len(s))
+	for i := 0; i < len(s); i++ {
+		keys[i] = s[i].pubKey
+	}
+
+	return keys
+}
+
+func (s signerSet) partialSigs() []*PartialSignature {
+	sigs := make([]*PartialSignature, len(s))
+	for i := 0; i < len(s); i++ {
+		sigs[i] = s[i].partialSig
+	}
+
+	return sigs
+}
+
+func (s signerSet) pubNonces() [][PubNonceSize]byte {
+	nonces := make([][PubNonceSize]byte, len(s))
+	for i := 0; i < len(s); i++ {
+		nonces[i] = s[i].nonces.PubNonce
+	}
+
+	return nonces
+}
+
+func (s signerSet) combinedKey() *btcec.PublicKey {
+	uniqueKeyIndex := secondUniqueKeyIndex(s.keys(), false)
+	key, _, _, _ := AggregateKeys(
+		s.keys(), false, WithUniqueKeyIndex(uniqueKeyIndex),
+	)
+	return key.FinalKey
+}
+
+// testMultiPartySign executes a multi-party signing context w/ 100 signers.
+func testMultiPartySign(t *testing.T, taprootTweak []byte,
+	tweaks ...KeyTweakDesc) {
+
+	const numSigners = 100
+
+	// First generate the set of signers along with their public keys.
+	signerKeys := make([]*btcec.PrivateKey, numSigners)
+	signSet := make([]*btcec.PublicKey, numSigners)
+	for i := 0; i < numSigners; i++ {
+		privKey, err := btcec.NewPrivateKey()
+		if err != nil {
+			t.Fatalf("unable to gen priv key: %v", err)
+		}
+
+		pubKey, err := schnorr.ParsePubKey(
+			schnorr.SerializePubKey(privKey.PubKey()),
+		)
+		if err != nil {
+			t.Fatalf("unable to gen key: %v", err)
+		}
+
+		signerKeys[i] = privKey
+		signSet[i] = pubKey
+	}
+
+	var combinedKey *btcec.PublicKey
+
+	var ctxOpts []ContextOption
+	switch {
+	case len(taprootTweak) == 0:
+		ctxOpts = append(ctxOpts, WithBip86TweakCtx())
+	case taprootTweak != nil:
+		ctxOpts = append(ctxOpts, WithTaprootTweakCtx(taprootTweak))
+	case len(tweaks) != 0:
+		ctxOpts = append(ctxOpts, WithTweakedContext(tweaks...))
+	}
+
+	ctxOpts = append(ctxOpts, WithKnownSigners(signSet))
+
+	// Now that we have all the signers, we'll make a new context, then
+	// generate a new session for each of them(which handles nonce
+	// generation).
+	signers := make([]*Session, numSigners)
+	for i, signerKey := range signerKeys {
+		signCtx, err := NewContext(
+			signerKey, false, ctxOpts...,
+		)
+		if err != nil {
+			t.Fatalf("unable to generate context: %v", err)
+		}
+
+		if combinedKey == nil {
+			combinedKey, err = signCtx.CombinedKey()
+			if err != nil {
+				t.Fatalf("combined key not available: %v", err)
+			}
+		}
+
+		session, err := signCtx.NewSession()
+		if err != nil {
+			t.Fatalf("unable to generate new session: %v", err)
+		}
+		signers[i] = session
+	}
+
+	// Next, in the pre-signing phase, we'll send all the nonces to each
+	// signer.
+	var wg sync.WaitGroup
+	for i, signCtx := range signers {
+		signCtx := signCtx
+
+		wg.Add(1)
+		go func(idx int, signer *Session) {
+			defer wg.Done()
+
+			for j, otherCtx := range signers {
+				if idx == j {
+					continue
+				}
+
+				nonce := otherCtx.PublicNonce()
+				haveAll, err := signer.RegisterPubNonce(nonce)
+				if err != nil {
+					t.Fatalf("unable to add public nonce")
+				}
+
+				if j == len(signers)-1 && !haveAll {
+					t.Fatalf("all public nonces should have been detected")
+				}
+			}
+		}(i, signCtx)
+	}
+
+	wg.Wait()
+
+	msg := sha256.Sum256([]byte("let's get taprooty"))
+
+	// In the final step, we'll use the first signer as our combiner, and
+	// generate a signature for each signer, and then accumulate that with
+	// the combiner.
+	combiner := signers[0]
+	for i := range signers {
+		signer := signers[i]
+		partialSig, err := signer.Sign(msg)
+		if err != nil {
+			t.Fatalf("unable to generate partial sig: %v", err)
+		}
+
+		// We don't need to combine the signature for the very first
+		// signer, as it already has that partial signature.
+		if i != 0 {
+			haveAll, err := combiner.CombineSig(partialSig)
+			if err != nil {
+				t.Fatalf("unable to combine sigs: %v", err)
+			}
+
+			if i == len(signers)-1 && !haveAll {
+				t.Fatalf("final sig wasn't reconstructed")
+			}
+		}
+	}
+
+	// Finally we'll combined all the nonces, and ensure that it validates
+	// as a single schnorr signature.
+	finalSig := combiner.FinalSig()
+	if !finalSig.Verify(msg[:], combinedKey) {
+		t.Fatalf("final sig is invalid!")
+	}
+
+	// Verify that if we try to sign again with any of the existing
+	// signers, then we'll get an error as the nonces have already been
+	// used.
+	for _, signer := range signers {
+		_, err := signer.Sign(msg)
+		if err != ErrSigningContextReuse {
+			t.Fatalf("expected to get signing context reuse")
+		}
+	}
+}
+
+// TestMuSigMultiParty tests that for a given set of 100 signers, we're able to
+// properly generate valid sub signatures, which ultimately can be combined
+// into a single valid signature.
+func TestMuSigMultiParty(t *testing.T) {
+	t.Parallel()
+
+	testTweak := [32]byte{
+		0xE8, 0xF7, 0x91, 0xFF, 0x92, 0x25, 0xA2, 0xAF,
+		0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, 0x72, 0x3D,
+		0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79,
+		0x80, 0x2B, 0x26, 0x3C, 0xDF, 0xCD, 0x83, 0xBB,
+	}
+
+	t.Run("no_tweak", func(t *testing.T) {
+		t.Parallel()
+
+		testMultiPartySign(t, nil)
+	})
+
+	t.Run("tweaked", func(t *testing.T) {
+		t.Parallel()
+
+		testMultiPartySign(t, nil, KeyTweakDesc{
+			Tweak: testTweak,
+		})
+	})
+
+	t.Run("tweaked_x_only", func(t *testing.T) {
+		t.Parallel()
+
+		testMultiPartySign(t, nil, KeyTweakDesc{
+			Tweak:   testTweak,
+			IsXOnly: true,
+		})
+	})
+
+	t.Run("taproot_tweaked_x_only", func(t *testing.T) {
+		t.Parallel()
+
+		testMultiPartySign(t, testTweak[:])
+	})
+
+	t.Run("taproot_bip_86", func(t *testing.T) {
+		t.Parallel()
+
+		testMultiPartySign(t, []byte{})
+	})
+}
+
+// TestMuSigEarlyNonce tests that for protocols where nonces need to be
+// exchagned before all signers are known, the context API works as expected.
+func TestMuSigEarlyNonce(t *testing.T) {
+	t.Parallel()
+
+	privKey1, err := btcec.NewPrivateKey()
+	if err != nil {
+		t.Fatalf("unable to gen priv key: %v", err)
+	}
+	privKey2, err := btcec.NewPrivateKey()
+	if err != nil {
+		t.Fatalf("unable to gen priv key: %v", err)
+	}
+
+	// If we try to make a context, with just the private key and sorting
+	// value, we should get an error.
+	_, err = NewContext(privKey1, true)
+	if !errors.Is(err, ErrSignersNotSpecified) {
+		t.Fatalf("unexpected ctx error: %v", err)
+	}
+
+	numSigners := 2
+
+	ctx1, err := NewContext(
+		privKey1, true, WithNumSigners(numSigners), WithEarlyNonceGen(),
+	)
+	if err != nil {
+		t.Fatalf("unable to make ctx: %v", err)
+	}
+	pubKey1 := ctx1.PubKey()
+
+	ctx2, err := NewContext(
+		privKey2, true, WithNumSigners(numSigners), WithEarlyNonceGen(),
+	)
+	if err != nil {
+		t.Fatalf("unable to make ctx: %v", err)
+	}
+	pubKey2 := ctx2.PubKey()
+
+	// At this point, the combined key shouldn't be available for both
+	// signers, since we only know of the sole signers.
+	if _, err := ctx1.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) {
+		t.Fatalf("unepxected error: %v", err)
+	}
+	if _, err := ctx2.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) {
+		t.Fatalf("unepxected error: %v", err)
+	}
+
+	// The early nonces _should_ be available at this point.
+	nonce1, err := ctx1.EarlySessionNonce()
+	if err != nil {
+		t.Fatalf("session nonce not available: %v", err)
+	}
+	nonce2, err := ctx2.EarlySessionNonce()
+	if err != nil {
+		t.Fatalf("session nonce not available: %v", err)
+	}
+
+	// The number of registered signers should still be 1 for both parties.
+	if ctx1.NumRegisteredSigners() != 1 {
+		t.Fatalf("expected 1 signer, instead have: %v",
+			ctx1.NumRegisteredSigners())
+	}
+	if ctx2.NumRegisteredSigners() != 1 {
+		t.Fatalf("expected 1 signer, instead have: %v",
+			ctx2.NumRegisteredSigners())
+	}
+
+	// If we try to make a session, we should get an error since we dn't
+	// have all the signers yet.
+	if _, err := ctx1.NewSession(); !errors.Is(err, ErrNotEnoughSigners) {
+		t.Fatalf("unexpected session key error: %v", err)
+	}
+
+	// The combined key should also be unavailable as well.
+	if _, err := ctx1.CombinedKey(); !errors.Is(err, ErrNotEnoughSigners) {
+		t.Fatalf("unexpected combined key error: %v", err)
+	}
+
+	// We'll now register the other signer for both parties.
+	done, err := ctx1.RegisterSigner(&pubKey2)
+	if err != nil {
+		t.Fatalf("unable to register signer: %v", err)
+	}
+	if !done {
+		t.Fatalf("signer 1 doesn't have all keys")
+	}
+	done, err = ctx2.RegisterSigner(&pubKey1)
+	if err != nil {
+		t.Fatalf("unable to register signer: %v", err)
+	}
+	if !done {
+		t.Fatalf("signer 2 doesn't have all keys")
+	}
+
+	// If we try to register the signer again, we should get an error.
+	_, err = ctx2.RegisterSigner(&pubKey1)
+	if !errors.Is(err, ErrAlreadyHaveAllSigners) {
+		t.Fatalf("should not be able to register too many signers")
+	}
+
+	// We should be able to create the session at this point.
+	session1, err := ctx1.NewSession()
+	if err != nil {
+		t.Fatalf("unable to create new session: %v", err)
+	}
+	session2, err := ctx2.NewSession()
+	if err != nil {
+		t.Fatalf("unable to create new session: %v", err)
+	}
+
+	msg := sha256.Sum256([]byte("let's get taprooty, LN style"))
+
+	// If we try to sign before we have the combined nonce, we shoudl get
+	// an error.
+	_, err = session1.Sign(msg)
+	if !errors.Is(err, ErrCombinedNonceUnavailable) {
+		t.Fatalf("unable to gen sig: %v", err)
+	}
+
+	// Now we can exchange nonces to continue with the rest of the signing
+	// process as normal.
+	done, err = session1.RegisterPubNonce(nonce2.PubNonce)
+	if err != nil {
+		t.Fatalf("unable to register nonce: %v", err)
+	}
+	if !done {
+		t.Fatalf("signer 1 doesn't have all nonces")
+	}
+	done, err = session2.RegisterPubNonce(nonce1.PubNonce)
+	if err != nil {
+		t.Fatalf("unable to register nonce: %v", err)
+	}
+	if !done {
+		t.Fatalf("signer 2 doesn't have all nonces")
+	}
+
+	// Registering the nonce again should error out.
+	_, err = session2.RegisterPubNonce(nonce1.PubNonce)
+	if !errors.Is(err, ErrAlredyHaveAllNonces) {
+		t.Fatalf("shouldn't be able to register nonces twice")
+	}
+
+	// Sign the message and combine the two partial sigs into one.
+	_, err = session1.Sign(msg)
+	if err != nil {
+		t.Fatalf("unable to gen sig: %v", err)
+	}
+	sig2, err := session2.Sign(msg)
+	if err != nil {
+		t.Fatalf("unable to gen sig: %v", err)
+	}
+	done, err = session1.CombineSig(sig2)
+	if err != nil {
+		t.Fatalf("unable to combine sig: %v", err)
+	}
+	if !done {
+		t.Fatalf("all sigs should be known now: %v", err)
+	}
+
+	// If we try to combine another sig, then we should get an error.
+	_, err = session1.CombineSig(sig2)
+	if !errors.Is(err, ErrAlredyHaveAllSigs) {
+		t.Fatalf("shouldn't be able to combine again")
+	}
+
+	// Finally, verify that the final signature is valid.
+	combinedKey, err := ctx1.CombinedKey()
+	if err != nil {
+		t.Fatalf("unexpected combined key error: %v", err)
+	}
+	finalSig := session1.FinalSig()
+	if !finalSig.Verify(msg[:], combinedKey) {
+		t.Fatalf("final sig is invalid!")
+	}
+}
+
+// TestMusig2NonceGenTestVectors tests the nonce generation function with
+// the testvectors defined in the Musig2 BIP.
+func TestMusig2NonceGenTestVectors(t *testing.T) {
+	t.Parallel()
+
+	msg := bytes.Repeat([]byte{0x01}, 32)
+	sk := bytes.Repeat([]byte{0x02}, 32)
+	aggpk := bytes.Repeat([]byte{0x07}, 32)
+	extra_in := bytes.Repeat([]byte{0x08}, 32)
+
+	testCases := []struct {
+		opts          nonceGenOpts
+		expectedNonce string
+	}{
+		{
+			opts: nonceGenOpts{
+				randReader:  &memsetRandReader{i: 0},
+				secretKey:   sk[:],
+				combinedKey: aggpk[:],
+				auxInput:    extra_in[:],
+				msg:         msg[:],
+			},
+			expectedNonce: "E8F2E103D86800F19A4E97338D371CB885DB2" +
+				"F19D08C0BD205BBA9B906C971D0D786A17718AAFAD6D" +
+				"E025DDDD99DC823E2DFC1AE1DDFE920888AD53FFF423FC4",
+		},
+		{
+			opts: nonceGenOpts{
+				randReader:  &memsetRandReader{i: 0},
+				secretKey:   sk[:],
+				combinedKey: aggpk[:],
+				auxInput:    extra_in[:],
+				msg:         nil,
+			},
+			expectedNonce: "8A633F5EECBDB690A6BE4921426F41BE78D50" +
+				"9DC1CE894C1215844C0E4C6DE7ABC9A5BE0A3BF3FE31" +
+				"2CCB7E4817D2CB17A7CEA8382B73A99A583E323387B3C32",
+		},
+		{
+			opts: nonceGenOpts{
+				randReader:  &memsetRandReader{i: 0},
+				secretKey:   nil,
+				combinedKey: nil,
+				auxInput:    nil,
+				msg:         nil,
+			},
+			expectedNonce: "7B3B5A002356471AF0E961DE2549C121BD0D4" +
+				"8ABCEEDC6E034BDDF86AD3E0A187ECEE674CEF7364B0" +
+				"BC4BEEFB8B66CAD89F98DE2F8C5A5EAD5D1D1E4BD7D04CD",
+		},
+	}
+
+	for _, testCase := range testCases {
+		nonce, err := GenNonces(withCustomOptions(testCase.opts))
+		if err != nil {
+			t.Fatalf("err gen nonce aux bytes %v", err)
+		}
+
+		expectedBytes, _ := hex.DecodeString(testCase.expectedNonce)
+		if !bytes.Equal(nonce.SecNonce[:], expectedBytes) {
+
+			t.Fatalf("nonces don't match: expected %x, got %x",
+				expectedBytes, nonce.SecNonce[:])
+		}
+	}
+
+}
+
+var (
+	pNonce1, _ = hex.DecodeString("020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E666" +
+		"03BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641")
+	pNonce2, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" +
+		"0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833")
+
+	expectedNonce, _ = hex.DecodeString("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B" +
+		"024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8")
+
+	invalidNonce1, _ = hex.DecodeString("04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833")
+	invalidNonce2, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "0248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831")
+	invalidNonce3, _ = hex.DecodeString("03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A6" + "02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30")
+)
+
+type jsonNonceAggTestCase struct {
+	Nonces        []string `json:"nonces"`
+	ExpectedNonce string   `json:"expected_key"`
+	ExpectedError string   `json:"expected_error"`
+}
+
+func TestMusig2AggregateNoncesTestVectors(t *testing.T) {
+	t.Parallel()
+
+	var jsonCases []jsonNonceAggTestCase
+
+	testCases := []struct {
+		nonces        [][]byte
+		expectedNonce []byte
+		expectedError error
+	}{
+		// Vector 1: Valid.
+		{
+			nonces:        [][]byte{pNonce1, pNonce2},
+			expectedNonce: expectedNonce,
+		},
+
+		// Vector 2: Public nonce from signer 1 is invalid due wrong
+		// tag, 0x04, inthe first half.
+		{
+			nonces:        [][]byte{pNonce1, invalidNonce1},
+			expectedError: secp256k1.ErrPubKeyInvalidFormat,
+		},
+
+		// Vector 3: Public nonce from signer 0 is invalid because the
+		// second half does not correspond to an X coordinate.
+		{
+			nonces:        [][]byte{invalidNonce2, pNonce2},
+			expectedError: secp256k1.ErrPubKeyNotOnCurve,
+		},
+
+		// Vector 4: Public nonce from signer 0 is invalid because
+		// second half exceeds field size.
+		{
+			nonces:        [][]byte{invalidNonce3, pNonce2},
+			expectedError: secp256k1.ErrPubKeyXTooBig,
+		},
+
+		// Vector 5: Sum of second points encoded in the nonces would
+		// be point at infinity, therefore set sum to base point G.
+		{
+			nonces: [][]byte{
+				append(
+					append([]byte{}, pNonce1[0:33]...),
+					getGBytes()...,
+				),
+				append(
+					append([]byte{}, pNonce2[0:33]...),
+					getNegGBytes()...,
+				),
+			},
+			expectedNonce: append(
+				append([]byte{}, expectedNonce[0:33]...),
+				getInfinityBytes()...,
+			),
+		},
+	}
+	for i, testCase := range testCases {
+		testName := fmt.Sprintf("Vector %v", i+1)
+		t.Run(testName, func(t *testing.T) {
+			var (
+				nonces    [][66]byte
+				strNonces []string
+				jsonError string
+			)
+			for _, nonce := range testCase.nonces {
+				nonces = append(nonces, toPubNonceSlice(nonce))
+				strNonces = append(strNonces, hex.EncodeToString(nonce))
+			}
+
+			if testCase.expectedError != nil {
+				jsonError = testCase.expectedError.Error()
+			}
+
+			jsonCases = append(jsonCases, jsonNonceAggTestCase{
+				Nonces:        strNonces,
+				ExpectedNonce: hex.EncodeToString(expectedNonce),
+				ExpectedError: jsonError,
+			})
+
+			aggregatedNonce, err := AggregateNonces(nonces)
+
+			switch {
+			case testCase.expectedError != nil &&
+				errors.Is(err, testCase.expectedError):
+
+				return
+			case err != nil:
+				t.Fatalf("aggregating nonce error: %v", err)
+			}
+
+			if !bytes.Equal(testCase.expectedNonce, aggregatedNonce[:]) {
+				t.Fatalf("case: #%v, invalid nonce aggregation: "+
+					"expected %x, got %x", i, testCase.expectedNonce,
+					aggregatedNonce)
+			}
+
+		})
+	}
+
+	if *dumpJson {
+		jsonBytes, err := json.Marshal(jsonCases)
+		if err != nil {
+			t.Fatalf("unable to encode json: %v", err)
+		}
+
+		var formattedJson bytes.Buffer
+		json.Indent(&formattedJson, jsonBytes, "", "\t")
+		err = ioutil.WriteFile(
+			nonceAggTestVectorName, formattedJson.Bytes(), 0644,
+		)
+		if err != nil {
+			t.Fatalf("unable to write file: %v", err)
+		}
+	}
+}
+
+type memsetRandReader struct {
+	i int
+}
+
+func (mr *memsetRandReader) Read(buf []byte) (n int, err error) {
+	for i := range buf {
+		buf[i] = byte(mr.i)
+	}
+	return len(buf), nil
+}
+
+var (
+	combineSigKey0 = mustParseHex("487D1B83B41B4CBBD07A111F1BBC7BDC8864CF" +
+		"EF5DBF96E46E51C68399B0BEF6")
+	combineSigKey1 = mustParseHex("4795C22501BF534BC478FF619407A7EC9E8D88" +
+		"83646D69BD43A0728944EA802F")
+	combineSigKey2 = mustParseHex("0F5BE837F3AB7E7FEFF1FAA44D673C2017206A" +
+		"E836D2C7893CDE4ACB7D55EDEB")
+	combineSigKey3 = mustParseHex("0FD453223E444FCA91FB5310990AE8A0C5DAA1" +
+		"4D2A4C8944E1C0BC80C30DF682")
+
+	combineSigKeys = [][]byte{combineSigKey0, combineSigKey1,
+		combineSigKey2, combineSigKey3}
+
+	combineSigAggNonce0 = mustParsePubNonce("024FA51009A56F0D6DF737131CE1" +
+		"FBBD833797AF3B4FE6BF0D68F4D49F68B0947E0248FB3BB9191F0CFF1380" +
+		"6A3A2F1429C23012654FCE4E41F7EC9169EAA6056B21")
+	combineSigAggNonce1 = mustParsePubNonce("023B11E63E2460E5E0F1561BB700" +
+		"FEA95B991DD9CA2CBBE92A3960641FA7469F6702CA4CD38375FE8BEB857C" +
+		"770807225BFC7D712F42BA896B83FC71138E56409B21")
+	combineSigAggNonce2 = mustParsePubNonce("03F98BEAA32B8A38FE3797C4E813" +
+		"DC9CE05ADBE32200035FB37EB0A030B735E9B6030E6118EC98EA2BA7A358" +
+		"C2E38E7E13E63681EEB683E067061BF7D52DCF08E615")
+	combineSigAggNonce3 = mustParsePubNonce("026491FBCFD47148043A0F7310E6" +
+		"2EF898C10F2D0376EE6B232EAAD36F3C2E29E303020CB17D168908E2904D" +
+		"E2EB571CD232CA805A6981D0F86CDBBD2F12BD91F6D0")
+
+	psig0 = mustParseHex("E5C1CBD6E7E89FE9EE30D5F3B6D06B9C218846E4A1DEF4E" +
+		"E851410D51ABBD850")
+	psig1 = mustParseHex("9BC470F7F1C9BC848BDF179B0023282FFEF40908E0EF884" +
+		"59784A4355FC86D0C")
+	psig2 = mustParseHex("D5D8A09929BA264B2F5DF15ACA1CF2DEFA47C048DF0C323" +
+		"2E965FFE2F2831B1D")
+	psig3 = mustParseHex("A915197503C1051EA77DC91F01C3A0E60BFD64473BD536C" +
+		"B613F9645BD61C843")
+	psig4 = mustParseHex("99A144D7076A128022134E036B8BDF33811F7EAED9A1E48" +
+		"549B46D8A63D64DC9")
+	psig5 = mustParseHex("716A72A0C1E531EBB4555C8E29FD35C796F4F231C3B0391" +
+		"93D7E8D7AEFBDF5F7")
+	psig6 = mustParseHex("06B6DD04BC0F1EF740916730AD7DAC794255B1612217197" +
+		"65BDE9686A26633DC")
+	psig7 = mustParseHex("BF6D85D4930062726EBC6EBB184AFD68DBB3FED159C5019" +
+		"89690A62600D6FBAB")
+
+	combineSigExpected0 = mustParseHex("4006D4D069F3B51E968762FF8074153E2" +
+		"78E5BCD221AABE0743CA001B77E79F581863CCED9B25C6E7A0FED8EB6F39" +
+		"3CD65CD7306D385DCF85CC6567DAA4E041B")
+	combineSigExpected1 = mustParseHex("98BCD40DFD94B47A3DA37D7B78EB6CCE8" +
+		"ABEACA23C3ADE6F4678902410EB35C67EEDBA0E2D7B2B69D6DBBA79CBE09" +
+		"3C64B9647A96B98C8C28AD3379BDFAEA21F")
+	combineSigExpected2 = mustParseHex("3741FEDCCDD7508B58DCB9A780FF5D974" +
+		"52EC8C0448D8C97004EA7175C14F2007A54D1DE356EBA6719278436EF111" +
+		"DFA8F1B832368371B9B7A25001709039679")
+	combineSigExpected3 = mustParseHex("F4B3DA3CF0D0F7CF5C1840593BF1A1A41" +
+		"5DA341619AE848F2210696DC8C7512540962C84EF7F0CEC491065F2D5772" +
+		"13CF10E8A63D153297361B3B172BE27B61F")
+
+	combineSigTweak0 = mustParseHex32("B511DA492182A91B0FFB9A98020D55F260" +
+		"AE86D7ECBD0399C7383D59A5F2AF7C")
+	combineSigTweak1 = mustParseHex32("A815FE049EE3C5AAB66310477FBC8BCCCA" +
+		"C2F3395F59F921C364ACD78A2F48DC")
+	combineSigTweak2 = mustParseHex32("75448A87274B056468B977BE06EB1E9F65" +
+		"7577B7320B0A3376EA51FD420D18A8")
+	tweak0False = KeyTweakDesc{
+		Tweak:   combineSigTweak0,
+		IsXOnly: false,
+	}
+	tweak0True = KeyTweakDesc{
+		Tweak:   combineSigTweak0,
+		IsXOnly: true,
+	}
+	tweak1False = KeyTweakDesc{
+		Tweak:   combineSigTweak1,
+		IsXOnly: false,
+	}
+	tweak2True = KeyTweakDesc{
+		Tweak:   combineSigTweak2,
+		IsXOnly: true,
+	}
+	combineSigsMsg = mustParseHex32("599C67EA410D005B9DA90817CF03ED3B1C86" +
+		"8E4DA4EDF00A5880B0082C237869")
+)
+
+func TestMusig2CombineSigsTestVectors(t *testing.T) {
+
+	testCases := []struct {
+		partialSigs   [][]byte
+		aggNonce      [66]byte
+		keyOrder      []int
+		expected      []byte
+		tweaks        []KeyTweakDesc
+		expectedError error
+	}{
+		// Vector 1
+		{
+			partialSigs: [][]byte{psig0, psig1},
+			aggNonce:    combineSigAggNonce0,
+			keyOrder:    []int{0, 1},
+			expected:    combineSigExpected0,
+		},
+		// Vector 2
+		{
+			partialSigs: [][]byte{psig2, psig3},
+			aggNonce:    combineSigAggNonce1,
+			keyOrder:    []int{0, 2},
+			expected:    combineSigExpected1,
+		},
+		// Vector 3
+		{
+			partialSigs: [][]byte{psig4, psig5},
+			aggNonce:    combineSigAggNonce2,
+			keyOrder:    []int{0, 2},
+			expected:    combineSigExpected2,
+			tweaks:      []KeyTweakDesc{tweak0False},
+		},
+		// Vector 4
+		{
+			partialSigs: [][]byte{psig6, psig7},
+			aggNonce:    combineSigAggNonce3,
+			keyOrder:    []int{0, 3},
+			expected:    combineSigExpected3,
+			tweaks: []KeyTweakDesc{
+				tweak0True,
+				tweak1False,
+				tweak2True,
+			},
+		},
+		// Vector 5: Partial signature is invalid because it exceeds group size
+		{
+			partialSigs: [][]byte{
+				psig7,
+				mustParseHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +
+					"EBAAEDCE6AF48A03BBFD25E8CD0364141"),
+			},
+			aggNonce:      combineSigAggNonce3,
+			expectedError: ErrPartialSigInvalid,
+		},
+	}
+
+	for _, testCase := range testCases {
+		var pSigs []*PartialSignature
+		for _, partialSig := range testCase.partialSigs {
+			pSig := &PartialSignature{}
+			err := pSig.Decode(bytes.NewReader(partialSig))
+
+			switch {
+			case testCase.expectedError != nil &&
+				errors.Is(err, testCase.expectedError):
+
+				return
+			case err != nil:
+				t.Fatal(err)
+			}
+
+			pSigs = append(pSigs, pSig)
+		}
+
+		keySet := make([]*btcec.PublicKey, 0, len(testCase.keyOrder))
+		for _, keyIndex := range testCase.keyOrder {
+			keyBytes := combineSigKeys[keyIndex]
+			pub, err := schnorr.ParsePubKey(keyBytes)
+			if err != nil {
+				t.Fatalf("unable to parse pubkeys: %v", err)
+			}
+			keySet = append(keySet, pub)
+		}
+
+		uniqueKeyIndex := secondUniqueKeyIndex(keySet, false)
+		aggOpts := []KeyAggOption{
+			WithUniqueKeyIndex(uniqueKeyIndex),
+		}
+		if len(testCase.tweaks) > 0 {
+			aggOpts = append(aggOpts, WithKeyTweaks(testCase.tweaks...))
+		}
+
+		combinedKey, _, _, err := AggregateKeys(
+			keySet, false, aggOpts...,
+		)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		aggPubkey, err := aggNonceToPubkey(
+			testCase.aggNonce, combinedKey, combineSigsMsg,
+		)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		var opts []CombineOption
+		if len(testCase.tweaks) > 0 {
+			opts = append(opts, WithTweakedCombine(
+				combineSigsMsg, keySet, testCase.tweaks, false,
+			))
+		}
+
+		sig := CombineSigs(aggPubkey, pSigs, opts...)
+		expectedSig, err := schnorr.ParseSignature(testCase.expected)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if !bytes.Equal(sig.Serialize(), expectedSig.Serialize()) {
+			t.Fatalf("sigs not expected %x \n got %x", expectedSig.Serialize(), sig.Serialize())
+		}
+
+		if !sig.Verify(combineSigsMsg[:], combinedKey.FinalKey) {
+			t.Fatal("sig not valid for m")
+		}
+	}
+}
+
+// aggNonceToPubkey gets a nonce as a public key for the TestMusig2CombineSigsTestVectors
+// test.
+// TODO(sputn1ck): build into intermediate routine.
+func aggNonceToPubkey(combinedNonce [66]byte, combinedKey *AggregateKey,
+	msg [32]byte) (*btcec.PublicKey, error) {
+
+	// b = int_from_bytes(tagged_hash('MuSig/noncecoef', aggnonce + bytes_from_point(Q) + msg)) % n
+	var (
+		nonceMsgBuf  bytes.Buffer
+		nonceBlinder btcec.ModNScalar
+	)
+	nonceMsgBuf.Write(combinedNonce[:])
+	nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
+	nonceMsgBuf.Write(msg[:])
+	nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes())
+	nonceBlinder.SetByteSlice(nonceBlindHash[:])
+
+	r1, err := btcec.ParsePubKey(
+		combinedNonce[:btcec.PubKeyBytesLenCompressed],
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	r2, err := btcec.ParsePubKey(
+		combinedNonce[btcec.PubKeyBytesLenCompressed:],
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	var nonce, r1J, r2J btcec.JacobianPoint
+	r1.AsJacobian(&r1J)
+	r2.AsJacobian(&r2J)
+
+	// With our nonce blinding value, we'll now combine both the public
+	// nonces, using the blinding factor to tweak the second nonce:
+	//  * R = R_1 + b*R_2
+	btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
+	btcec.AddNonConst(&r1J, &r2J, &nonce)
+
+	nonce.ToAffine()
+
+	return btcec.NewPublicKey(
+		&nonce.X, &nonce.Y,
+	), nil
+
+}
+
+func mustNonceAgg(nonces [][66]byte) [66]byte {
+	aggNonce, err := AggregateNonces(nonces)
+	if err != nil {
+		panic("can't aggregate nonces")
+	}
+	return aggNonce
+}
+
+func memsetLoop(a []byte, v uint8) {
+	for i := range a {
+		a[i] = byte(v)
+	}
+}
+
+func to32ByteSlice(input []byte) [32]byte {
+	if len(input) != 32 {
+		panic("input byte slice has invalid length")
+	}
+	var output [32]byte
+	copy(output[:], input)
+	return output
+}
+
+func toPubNonceSlice(input []byte) [PubNonceSize]byte {
+	var output [PubNonceSize]byte
+	copy(output[:], input)
+
+	return output
+}
+
+func getGBytes() []byte {
+	return btcec.Generator().SerializeCompressed()
+}
+
+func getNegGBytes() []byte {
+	pk := getGBytes()
+	pk[0] = 0x3
+
+	return pk
+}
+
+func getInfinityBytes() []byte {
+	return make([]byte, 33)
+}
+
+func mustParseHex32(str string) [32]byte {
+	b, err := hex.DecodeString(str)
+	if err != nil {
+		panic(fmt.Errorf("unable to parse hex: %v", err))
+	}
+	if len(b) != 32 {
+		panic(fmt.Errorf("not a 32 byte slice: %v", err))
+	}
+
+	return to32ByteSlice(b)
+}
+
+func mustParsePubNonce(str string) [PubNonceSize]byte {
+	b, err := hex.DecodeString(str)
+	if err != nil {
+		panic(fmt.Errorf("unable to parse hex: %v", err))
+	}
+	if len(b) != PubNonceSize {
+		panic(fmt.Errorf("not a public nonce: %v", err))
+	}
+	return toPubNonceSlice(b)
+}
+
+func canParsePubNonce(str string) [PubNonceSize]byte {
+	b, err := hex.DecodeString(str)
+	if err != nil {
+		panic(fmt.Errorf("unable to parse hex: %v", err))
+	}
+	return toPubNonceSlice(b)
+}
diff --git a/lntest/itest/lnd_remote_signer_test.go b/lntest/itest/lnd_remote_signer_test.go
index 032301340..eccf396b9 100644
--- a/lntest/itest/lnd_remote_signer_test.go
+++ b/lntest/itest/lnd_remote_signer_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/btcsuite/btcwallet/waddrmgr"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/lnrpc"
+	"github.com/lightningnetwork/lnd/lnrpc/signrpc"
 	"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
 	"github.com/lightningnetwork/lnd/lntemp"
 	"github.com/lightningnetwork/lnd/lntemp/node"
@@ -132,10 +133,21 @@ func testRemoteSigner(ht *lntemp.HarnessTest) {
 			testTaprootSignOutputRawScriptSpend(tt, wo)
 			testTaprootSignOutputRawKeySpendBip86(tt, wo)
 			testTaprootSignOutputRawKeySpendRootHash(tt, wo)
-			testTaprootMuSig2KeySpendRootHash(tt, wo)
-			testTaprootMuSig2ScriptSpend(tt, wo)
-			testTaprootMuSig2KeySpendBip86(tt, wo)
-			testTaprootMuSig2CombinedLeafKeySpend(tt, wo)
+
+			muSig2Versions := []signrpc.MuSig2Version{
+				signrpc.MuSig2Version_MUSIG2_VERSION_V040,
+				signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2,
+			}
+			for _, version := range muSig2Versions {
+				testTaprootMuSig2KeySpendRootHash(
+					tt, wo, version,
+				)
+				testTaprootMuSig2ScriptSpend(tt, wo, version)
+				testTaprootMuSig2KeySpendBip86(tt, wo, version)
+				testTaprootMuSig2CombinedLeafKeySpend(
+					tt, wo, version,
+				)
+			}
 		},
 	}}
 
diff --git a/lntest/itest/lnd_taproot_test.go b/lntest/itest/lnd_taproot_test.go
index 1cbb82355..a04048195 100644
--- a/lntest/itest/lnd_taproot_test.go
+++ b/lntest/itest/lnd_taproot_test.go
@@ -35,11 +35,14 @@ const (
 )
 
 var (
-	dummyInternalKeyBytes, _ = hex.DecodeString(
+	hexDecode = func(keyStr string) []byte {
+		keyBytes, _ := hex.DecodeString(keyStr)
+		return keyBytes
+	}
+	dummyInternalKey, _ = btcec.ParsePubKey(hexDecode(
 		"03464805f5468e294d88cf15a3f06aef6c89d63ef1bd7b42db2e0c74c1ac" +
 			"eb90fe",
-	)
-	dummyInternalKey, _ = btcec.ParsePubKey(dummyInternalKeyBytes)
+	))
 )
 
 // testTaproot ensures that the daemon can send to and spend from taproot (p2tr)
@@ -57,10 +60,17 @@ func testTaproot(ht *lntemp.HarnessTest) {
 	)
 	testTaprootSignOutputRawKeySpendRootHash(ht, ht.Alice)
 
-	testTaprootMuSig2KeySpendBip86(ht, ht.Alice)
-	testTaprootMuSig2KeySpendRootHash(ht, ht.Alice)
-	testTaprootMuSig2ScriptSpend(ht, ht.Alice)
-	testTaprootMuSig2CombinedLeafKeySpend(ht, ht.Alice)
+	muSig2Versions := []signrpc.MuSig2Version{
+		signrpc.MuSig2Version_MUSIG2_VERSION_V040,
+		signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2,
+	}
+	for _, version := range muSig2Versions {
+		testTaprootMuSig2KeySpendBip86(ht, ht.Alice, version)
+		testTaprootMuSig2KeySpendRootHash(ht, ht.Alice, version)
+		testTaprootMuSig2ScriptSpend(ht, ht.Alice, version)
+		testTaprootMuSig2CombinedLeafKeySpend(ht, ht.Alice, version)
+		testMuSig2CombineKey(ht, ht.Alice, version)
+	}
 
 	testTaprootImportTapscriptFullTree(ht, ht.Alice)
 	testTaprootImportTapscriptPartialReveal(ht, ht.Alice)
@@ -574,7 +584,7 @@ func testTaprootSignOutputRawKeySpendRootHash(ht *lntemp.HarnessTest,
 // testTaprootMuSig2KeySpendBip86 tests that a combined MuSig2 key can also be
 // used as a BIP-0086 key spend only key.
 func testTaprootMuSig2KeySpendBip86(ht *lntemp.HarnessTest,
-	alice *node.HarnessNode) {
+	alice *node.HarnessNode, version signrpc.MuSig2Version) {
 
 	// We're not going to commit to a script. So our taproot tweak will be
 	// empty and just specify the necessary flag.
@@ -582,10 +592,12 @@ func testTaprootMuSig2KeySpendBip86(ht *lntemp.HarnessTest,
 		KeySpendOnly: true,
 	}
 
-	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(ht, alice)
+	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(
+		ht, alice, version,
+	)
 	_, taprootKey, sessResp1, sessResp2, sessResp3 := createMuSigSessions(
 		ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3,
-		allPubKeys,
+		allPubKeys, version,
 	)
 
 	// Send some coins to the generated tapscript address.
@@ -700,7 +712,7 @@ func testTaprootMuSig2KeySpendBip86(ht *lntemp.HarnessTest,
 // testTaprootMuSig2KeySpendRootHash tests that a tapscript address can also be
 // spent using a MuSig2 combined key.
 func testTaprootMuSig2KeySpendRootHash(ht *lntemp.HarnessTest,
-	alice *node.HarnessNode) {
+	alice *node.HarnessNode, version signrpc.MuSig2Version) {
 
 	// We're going to commit to a script as well. This is a hash lock with a
 	// simple preimage of "foobar". We need to know this upfront so, we can
@@ -713,11 +725,11 @@ func testTaprootMuSig2KeySpendRootHash(ht *lntemp.HarnessTest,
 	}
 
 	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(
-		ht, alice,
+		ht, alice, version,
 	)
 	_, taprootKey, sessResp1, sessResp2, sessResp3 := createMuSigSessions(
 		ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3,
-		allPubKeys,
+		allPubKeys, version,
 	)
 
 	// Send some coins to the generated tapscript address.
@@ -832,7 +844,7 @@ func testTaprootMuSig2KeySpendRootHash(ht *lntemp.HarnessTest,
 // testTaprootMuSig2ScriptSpend tests that a tapscript address with an internal
 // key that is a MuSig2 combined key can also be spent using the script path.
 func testTaprootMuSig2ScriptSpend(ht *lntemp.HarnessTest,
-	alice *node.HarnessNode) {
+	alice *node.HarnessNode, version signrpc.MuSig2Version) {
 
 	// We're going to commit to a script and spend the output using the
 	// script. This is a hash lock with a simple preimage of "foobar". We
@@ -845,11 +857,11 @@ func testTaprootMuSig2ScriptSpend(ht *lntemp.HarnessTest,
 	}
 
 	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(
-		ht, alice,
+		ht, alice, version,
 	)
 	internalKey, taprootKey, _, _, _ := createMuSigSessions(
 		ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3,
-		allPubKeys,
+		allPubKeys, version,
 	)
 
 	// Because we know the internal key and the script we want to spend, we
@@ -915,15 +927,16 @@ func testTaprootMuSig2ScriptSpend(ht *lntemp.HarnessTest,
 // testTaprootMuSig2CombinedLeafKeySpend tests that a MuSig2 combined key can be
 // used for an OP_CHECKSIG inside a tap script leaf spend.
 func testTaprootMuSig2CombinedLeafKeySpend(ht *lntemp.HarnessTest,
-	alice *node.HarnessNode) {
+	alice *node.HarnessNode, version signrpc.MuSig2Version) {
 
 	// We're using the combined MuSig2 key in a script leaf. So we need to
 	// derive the combined key first, before we can build the script.
 	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(
-		ht, alice,
+		ht, alice, version,
 	)
 	req := &signrpc.MuSig2CombineKeysRequest{
 		AllSignerPubkeys: allPubKeys,
+		Version:          version,
 	}
 	combineResp := alice.RPC.MuSig2CombineKeys(req)
 	combinedPubKey, err := schnorr.ParsePubKey(combineResp.CombinedKey)
@@ -978,6 +991,7 @@ func testTaprootMuSig2CombinedLeafKeySpend(ht *lntemp.HarnessTest,
 	// Do the actual signing now.
 	_, _, sessResp1, sessResp2, sessResp3 := createMuSigSessions(
 		ht, alice, nil, keyDesc1, keyDesc2, keyDesc3, allPubKeys,
+		version,
 	)
 	require.NoError(ht, err)
 
@@ -1640,8 +1654,8 @@ func confirmAddress(ht *lntemp.HarnessTest, hn *node.HarnessNode,
 
 // deriveSigningKeys derives three signing keys and returns their descriptors,
 // as well as the public keys in the Schnorr serialized format.
-func deriveSigningKeys(ht *lntemp.HarnessTest,
-	node *node.HarnessNode) (*signrpc.KeyDescriptor,
+func deriveSigningKeys(ht *lntemp.HarnessTest, node *node.HarnessNode,
+	version signrpc.MuSig2Version) (*signrpc.KeyDescriptor,
 	*signrpc.KeyDescriptor, *signrpc.KeyDescriptor, [][]byte) {
 
 	// For muSig2 we need multiple keys. We derive three of them from the
@@ -1662,10 +1676,21 @@ func deriveSigningKeys(ht *lntemp.HarnessTest,
 	// Now that we have all three keys we can create three sessions, one
 	// for each of the signers. This would of course normally not happen on
 	// the same node.
-	allPubKeys := [][]byte{
-		schnorr.SerializePubKey(pubKey1),
-		schnorr.SerializePubKey(pubKey2),
-		schnorr.SerializePubKey(pubKey3),
+	var allPubKeys [][]byte
+	switch version {
+	case signrpc.MuSig2Version_MUSIG2_VERSION_V040:
+		allPubKeys = [][]byte{
+			schnorr.SerializePubKey(pubKey1),
+			schnorr.SerializePubKey(pubKey2),
+			schnorr.SerializePubKey(pubKey3),
+		}
+
+	case signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2:
+		allPubKeys = [][]byte{
+			pubKey1.SerializeCompressed(),
+			pubKey2.SerializeCompressed(),
+			pubKey3.SerializeCompressed(),
+		}
 	}
 
 	return keyDesc1, keyDesc2, keyDesc3, allPubKeys
@@ -1678,16 +1703,26 @@ func deriveSigningKeys(ht *lntemp.HarnessTest,
 func createMuSigSessions(ht *lntemp.HarnessTest, node *node.HarnessNode,
 	taprootTweak *signrpc.TaprootTweakDesc,
 	keyDesc1, keyDesc2, keyDesc3 *signrpc.KeyDescriptor,
-	allPubKeys [][]byte) (*btcec.PublicKey, *btcec.PublicKey,
-	*signrpc.MuSig2SessionResponse, *signrpc.MuSig2SessionResponse,
-	*signrpc.MuSig2SessionResponse) {
+	allPubKeys [][]byte, version signrpc.MuSig2Version) (*btcec.PublicKey,
+	*btcec.PublicKey, *signrpc.MuSig2SessionResponse,
+	*signrpc.MuSig2SessionResponse, *signrpc.MuSig2SessionResponse) {
 
-	req := &signrpc.MuSig2SessionRequest{
+	// Make sure that when not specifying a version we get an error, since
+	// it is mandatory.
+	err := node.RPC.MuSig2CreateSessionErr(&signrpc.MuSig2SessionRequest{})
+	require.ErrorContains(ht, err, "unknown MuSig2 version")
+
+	// Create the actual session with the version specified.
+	sessResp1 := node.RPC.MuSig2CreateSession(&signrpc.MuSig2SessionRequest{
 		KeyLoc:           keyDesc1.KeyLoc,
 		AllSignerPubkeys: allPubKeys,
 		TaprootTweak:     taprootTweak,
-	}
-	sessResp1 := node.RPC.MuSig2CreateSession(req)
+		Version:          version,
+	})
+	require.Equal(ht, version, sessResp1.Version)
+
+	// Make sure the version is returned correctly.
+	require.Equal(ht, version, sessResp1.Version)
 
 	// Now that we have the three keys in a combined form, we want to make
 	// sure the tweaking for the taproot key worked correctly. We first need
@@ -1724,11 +1759,17 @@ func createMuSigSessions(ht *lntemp.HarnessTest, node *node.HarnessNode,
 		)
 	}
 
+	// Same with the combine keys RPC, no version specified should give us
+	// an error.
+	err = node.RPC.MuSig2CombineKeysErr(&signrpc.MuSig2CombineKeysRequest{})
+	require.ErrorContains(ht, err, "unknown MuSig2 version")
+
 	// We should also get the same keys when just calling the
 	// MuSig2CombineKeys RPC.
 	combineReq := &signrpc.MuSig2CombineKeysRequest{
 		AllSignerPubkeys: allPubKeys,
 		TaprootTweak:     taprootTweak,
+		Version:          version,
 	}
 	combineResp := node.RPC.MuSig2CombineKeys(combineReq)
 	require.Equal(
@@ -1739,19 +1780,22 @@ func createMuSigSessions(ht *lntemp.HarnessTest, node *node.HarnessNode,
 		ht, schnorr.SerializePubKey(internalKey),
 		combineResp.TaprootInternalKey,
 	)
+	require.Equal(ht, version, combineResp.Version)
 
 	// Everything is good so far, let's continue with creating the signing
 	// session for the other two participants.
-	req = &signrpc.MuSig2SessionRequest{
+	req := &signrpc.MuSig2SessionRequest{
 		KeyLoc:           keyDesc2.KeyLoc,
 		AllSignerPubkeys: allPubKeys,
 		OtherSignerPublicNonces: [][]byte{
 			sessResp1.LocalPublicNonces,
 		},
 		TaprootTweak: taprootTweak,
+		Version:      version,
 	}
 	sessResp2 := node.RPC.MuSig2CreateSession(req)
 	require.Equal(ht, sessResp1.CombinedKey, sessResp2.CombinedKey)
+	require.Equal(ht, version, sessResp2.Version)
 
 	req = &signrpc.MuSig2SessionRequest{
 		KeyLoc:           keyDesc3.KeyLoc,
@@ -1761,10 +1805,11 @@ func createMuSigSessions(ht *lntemp.HarnessTest, node *node.HarnessNode,
 			sessResp2.LocalPublicNonces,
 		},
 		TaprootTweak: taprootTweak,
+		Version:      version,
 	}
 	sessResp3 := node.RPC.MuSig2CreateSession(req)
-	require.NoError(ht, err)
 	require.Equal(ht, sessResp2.CombinedKey, sessResp3.CombinedKey)
+	require.Equal(ht, version, sessResp3.Version)
 	require.Equal(ht, true, sessResp3.HaveAllNonces)
 
 	// We need to distribute the rest of the nonces.
@@ -1856,3 +1901,88 @@ func testTaprootCoopClose(ht *lntemp.HarnessTest) {
 	require.False(ht, assertTaprootDeliveryUsed(closingTxid),
 		"taproot addr shouldn't be used!")
 }
+
+// testMuSig2CombineKey makes sure that combining a key with MuSig2 returns the
+// correct result according to the MuSig2 version specified.
+func testMuSig2CombineKey(ht *lntemp.HarnessTest, alice *node.HarnessNode,
+	version signrpc.MuSig2Version) {
+
+	testVector040Key1 := hexDecode(
+		"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE0" +
+			"36F9",
+	)
+	testVector040Key2 := hexDecode(
+		"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502B" +
+			"A659",
+	)
+	testVector040Key3 := hexDecode(
+		"3590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038" +
+			"CA66",
+	)
+
+	testVector100Key1 := hexDecode(
+		"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BC" +
+			"E036F9",
+	)
+	testVector100Key2 := hexDecode(
+		"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50" +
+			"2BA659",
+	)
+	testVector100Key3 := hexDecode(
+		"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D0" +
+			"38CA66",
+	)
+
+	var allPubKeys [][]byte
+	switch version {
+	case signrpc.MuSig2Version_MUSIG2_VERSION_V040:
+		allPubKeys = [][]byte{
+			testVector040Key1, testVector040Key2, testVector040Key3,
+		}
+
+	case signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2:
+		allPubKeys = [][]byte{
+			testVector100Key1, testVector100Key2, testVector100Key3,
+		}
+	}
+
+	resp := alice.RPC.MuSig2CombineKeys(&signrpc.MuSig2CombineKeysRequest{
+		AllSignerPubkeys: allPubKeys,
+		TaprootTweak: &signrpc.TaprootTweakDesc{
+			KeySpendOnly: true,
+		},
+		Version: version,
+	})
+
+	expectedFinalKey040 := hexDecode(
+		"5b257b4e785d61157ef5303051f45184bd5cb47bc4b4069ed4dd453645" +
+			"9cb83b",
+	)
+	expectedPreTweakKey040 := hexDecode(
+		"d70cd69a2647f7390973df48cbfa2ccc407b8b2d60b08c5f1641185c79" +
+			"98a290",
+	)
+
+	expectedFinalKey100 := hexDecode(
+		"79e6c3e628c9bfbce91de6b7fb28e2aec7713d377cf260ab599dcbc40e54" +
+			"2312",
+	)
+	expectedPreTweakKey100 := hexDecode(
+		"789d937bade6673538f3e28d8368dda4d0512f94da44cf477a505716d26a" +
+			"1575",
+	)
+
+	switch version {
+	case signrpc.MuSig2Version_MUSIG2_VERSION_V040:
+		require.Equal(ht, expectedFinalKey040, resp.CombinedKey)
+		require.Equal(
+			ht, expectedPreTweakKey040, resp.TaprootInternalKey,
+		)
+
+	case signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2:
+		require.Equal(ht, expectedFinalKey100, resp.CombinedKey)
+		require.Equal(
+			ht, expectedPreTweakKey100, resp.TaprootInternalKey,
+		)
+	}
+}
diff --git a/lntest/mock/signer.go b/lntest/mock/signer.go
index 4dd465cd8..ddcef3598 100644
--- a/lntest/mock/signer.go
+++ b/lntest/mock/signer.go
@@ -55,8 +55,8 @@ func (d *DummySigner) ComputeInputScript(tx *wire.MsgTx,
 // all signing parties must be provided, including the public key of the local
 // signing key. If nonces of other parties are already known, they can be
 // submitted as well to reduce the number of method calls necessary later on.
-func (d *DummySigner) MuSig2CreateSession(keychain.KeyLocator,
-	[]*btcec.PublicKey, *input.MuSig2Tweaks,
+func (d *DummySigner) MuSig2CreateSession(input.MuSig2Version,
+	keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
 	[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
 
 	return nil, nil
@@ -194,8 +194,8 @@ func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator,
 // all signing parties must be provided, including the public key of the local
 // signing key. If nonces of other parties are already known, they can be
 // submitted as well to reduce the number of method calls necessary later on.
-func (s *SingleSigner) MuSig2CreateSession(keychain.KeyLocator,
-	[]*btcec.PublicKey, *input.MuSig2Tweaks,
+func (s *SingleSigner) MuSig2CreateSession(input.MuSig2Version,
+	keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks,
 	[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
 
 	return nil, nil