From 21553b91ec2c4ad0711a33a4bf8b2fbac9cd3cdc Mon Sep 17 00:00:00 2001 From: oxf71 <144757737+oxf71@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:48:23 +0800 Subject: [PATCH] update --- musig2/musig2.go | 224 ++++++++++++++++++++++++++++++++++++++++++ musig2/musig2_test.go | 39 ++++++++ 2 files changed, 263 insertions(+) create mode 100644 musig2/musig2.go create mode 100644 musig2/musig2_test.go diff --git a/musig2/musig2.go b/musig2/musig2.go new file mode 100644 index 0000000..cf3c72a --- /dev/null +++ b/musig2/musig2.go @@ -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 +} diff --git a/musig2/musig2_test.go b/musig2/musig2_test.go new file mode 100644 index 0000000..6e24d59 --- /dev/null +++ b/musig2/musig2_test.go @@ -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() +}