btcd/btcec/schnorr/musig2/musig2_test.go
2022-10-20 17:58:04 -07:00

446 lines
11 KiB
Go

// Copyright 2013-2022 The btcsuite developers
package musig2
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"sync"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
)
const (
testVectorBaseDir = "data"
)
func mustParseHex(str string) []byte {
b, err := hex.DecodeString(str)
if err != nil {
panic(fmt.Errorf("unable to parse hex: %v", err))
}
return b
}
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 := privKey.PubKey()
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!")
}
}
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
}