Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
oxf71 committed Jan 8, 2024
1 parent 740a133 commit 21553b9
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 0 deletions.
224 changes: 224 additions & 0 deletions musig2/musig2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package musig2

import (
"bytes"
"fmt"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
)

const (
// MuSig2PartialSigSize is the size of a MuSig2 partial signature.
// Because a partial signature is just the s value, this corresponds to
// the length of a scalar.
MuSig2PartialSigSize = 32
)

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)
}

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)
}

type MuSig2Tweaks struct {
// GenericTweaks is a list of normal tweaks to apply to the combined
// public key (and to the private key when signing).
GenericTweaks []musig2.KeyTweakDesc

// TaprootBIP0086Tweak indicates that 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 aggregated key before the tweak will be used as the
// internal key. If this is set to true then TaprootTweak will be
// ignored.
TaprootBIP0086Tweak bool

// TaprootTweak specifies that 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. Will be ignored if TaprootBIP0086Tweak is set to true.
TaprootTweak []byte
}

// ToContextOptions converts the tweak descriptor to context options.
func (t *MuSig2Tweaks) ToContextOptions() []musig2.ContextOption {
var tweakOpts []musig2.ContextOption
if len(t.GenericTweaks) > 0 {
tweakOpts = append(tweakOpts, musig2.WithTweakedContext(
t.GenericTweaks...,
))
}

// The BIP0086 tweak and the taproot script tweak are mutually
// exclusive.
if t.TaprootBIP0086Tweak {
tweakOpts = append(tweakOpts, musig2.WithBip86TweakCtx())
} else if len(t.TaprootTweak) > 0 {
tweakOpts = append(tweakOpts, musig2.WithTaprootTweakCtx(
t.TaprootTweak,
))
}

return tweakOpts
}
func MuSig2CombineKeys(allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {

// Convert the tweak options into the appropriate MuSig2 API functional
// options.
var keyAggOpts []musig2.KeyAggOption
switch {
case tweaks.TaprootBIP0086Tweak:
keyAggOpts = append(keyAggOpts, musig2.WithBIP86KeyTweak())
case len(tweaks.TaprootTweak) > 0:
keyAggOpts = append(keyAggOpts, musig2.WithTaprootKeyTweak(
tweaks.TaprootTweak,
))
case len(tweaks.GenericTweaks) > 0:
keyAggOpts = append(keyAggOpts, musig2.WithKeyTweaks(
tweaks.GenericTweaks...,
))
}

// Then we'll use this information to compute the aggregated public key.
combinedKey, _, _, err := musig2.AggregateKeys(
allSignerPubKeys, sortKeys, keyAggOpts...,
)
return combinedKey, err
}

func MuSig2CreateContext(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
}

// 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 *musig2.Session, msg [32]byte,
withSortedKeys bool) (*musig2.PartialSignature, error) {

var opts []musig2.SignOption
if withSortedKeys {
opts = append(opts, musig2.WithSortedKeys())
}
partialSig, err := session.Sign(msg, opts...)
if err != nil {
return nil, fmt.Errorf("error signing with local key: "+
"%v", err)
}

return partialSig, nil
}

// 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 *musig2.Session,
otherPartialSig *musig2.PartialSignature) (bool, error) {

haveAllSigs, err := session.CombineSig(otherPartialSig)
if err != nil {
return false, fmt.Errorf("error combining partial "+
"signature: %v", err)
}

return haveAllSigs, nil
}

func MuSig2FinalSig(session *musig2.Session) *schnorr.Signature {
return session.FinalSig()
}

// SerializePartialSignature encodes the partial signature to a fixed size byte
// array.
func SerializePartialSignature(
sig *musig2.PartialSignature) ([MuSig2PartialSigSize]byte, error) {

var (
buf bytes.Buffer
result [MuSig2PartialSigSize]byte
)
if err := sig.Encode(&buf); err != nil {
return result, fmt.Errorf("error encoding partial signature: "+
"%v", err)
}

if buf.Len() != MuSig2PartialSigSize {
return result, fmt.Errorf("invalid partial signature length, "+
"got %d wanted %d", buf.Len(), MuSig2PartialSigSize)
}

copy(result[:], buf.Bytes())

return result, nil
}

// DeserializePartialSignature decodes a partial signature from a byte slice.
func DeserializePartialSignature(scalarBytes []byte) (*musig2.PartialSignature,
error) {

if len(scalarBytes) != MuSig2PartialSigSize {
return nil, fmt.Errorf("invalid partial signature length, got "+
"%d wanted %d", len(scalarBytes), MuSig2PartialSigSize)
}

sig := &musig2.PartialSignature{}
if err := sig.Decode(bytes.NewReader(scalarBytes)); err != nil {
return nil, fmt.Errorf("error decoding partial signature: %v",
err)
}

return sig, nil
}
39 changes: 39 additions & 0 deletions musig2/musig2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package musig2_test

import (
"encoding/hex"
"fmt"
"testing"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
musig2demo "github.com/oxf71/musig2-demo/musig2"
)

func TestMuSig2CombineKeys(t *testing.T) {
_, publicKey1 := btcec.PrivKeyFromBytes(decodeHex("440bb3ec56d213e90d006d344d74f6478db4f7fa4cdd388095d8f4edef0c5156"))
_, publicKey2 := btcec.PrivKeyFromBytes(decodeHex("e0087817fd1d1154a781c11b394a0dcec82f076bbf026df9d61667ead16fa778"))
_ = [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,
}

allSignerPubKeys := []*btcec.PublicKey{publicKey1, publicKey2}

muSig2Tweaks := musig2demo.MuSig2Tweaks{
TaprootBIP0086Tweak: false,
// TaprootTweak: testTweak[:],
GenericTweaks: []musig2.KeyTweakDesc{},
}

combinedKey, err := musig2demo.MuSig2CombineKeys(allSignerPubKeys, false, &muSig2Tweaks)
if err != nil {
t.Fatal(err)
}

fmt.Println(hex.EncodeToString(combinedKey.FinalKey.SerializeCompressed()))

t.Fail()
}

0 comments on commit 21553b9

Please sign in to comment.