mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-13 11:35:52 +01:00
Merge 3ddbf19b84
into 6afce8d608
This commit is contained in:
commit
9cbd944a0b
3 changed files with 385 additions and 20 deletions
|
@ -3,6 +3,7 @@
|
|||
package musig2
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
|
@ -52,6 +53,27 @@ var (
|
|||
// required combined nonces.
|
||||
ErrCombinedNonceUnavailable = fmt.Errorf("missing combined nonce")
|
||||
|
||||
// ErrFinalSigUnavailable is returned when a caller attempts to adapt a
|
||||
// final signature, but the final signature is not available.
|
||||
ErrFinalSigUnavailable = fmt.Errorf("final signature not available")
|
||||
|
||||
// ErrAdaptorSecretUnavailable is returned when a caller attempts to adapt a
|
||||
// final signature, but the adaptor secret key is not available.
|
||||
ErrAdaptorSecretUnavailable = fmt.Errorf("adaptor secret key not available")
|
||||
|
||||
// ErrAdaptorPointUnavailable is returned when a caller attempts to recover
|
||||
// the adaptor signature from a valid signature, but the adaptor point
|
||||
// is not available.
|
||||
ErrAdaptorPointUnavailable = fmt.Errorf("adaptor point not available")
|
||||
|
||||
// ErrInvalidAdaptorPoint is returned when a caller attempts to provide
|
||||
// an adaptor point that is incompatible with the combined nonce.
|
||||
ErrInvalidAdaptorPoint = fmt.Errorf("invalid adaptor point")
|
||||
|
||||
// ErrAlreadySigned is returned when a caller attempts to provide an
|
||||
// adaptor point, but a signature was already signed.
|
||||
ErrAlreadySigned = fmt.Errorf("already signed")
|
||||
|
||||
// ErrTaprootInternalKeyUnavailable is returned when a user attempts to
|
||||
// obtain the
|
||||
ErrTaprootInternalKeyUnavailable = fmt.Errorf("taproot tweak not used")
|
||||
|
@ -99,7 +121,7 @@ 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
|
||||
// tweaks is the set of optional tweaks to apply to the combined public
|
||||
// key.
|
||||
tweaks []KeyTweakDesc
|
||||
|
||||
|
@ -439,6 +461,9 @@ type Session struct {
|
|||
|
||||
msg [32]byte
|
||||
|
||||
adaptorSecret *btcec.ModNScalar
|
||||
adaptorPoint *btcec.JacobianPoint
|
||||
|
||||
ourSig *PartialSignature
|
||||
sigs []*PartialSignature
|
||||
|
||||
|
@ -548,6 +573,129 @@ func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) {
|
|||
return haveAllNonces, nil
|
||||
}
|
||||
|
||||
// GenerateAdaptorSecret generates an adaptor secret key. This must be called
|
||||
// after the public nonces have been registered for all signers, because
|
||||
// the validity of the adaptor secret key depends on the combined nonce. It
|
||||
// must also be called before any partial signatures are generated or provided,
|
||||
// because the validation of the signatures depends on the adaptor point.
|
||||
func (s *Session) GenerateAdaptor(msg [32]byte) (*btcec.ModNScalar, *btcec.JacobianPoint, error) {
|
||||
if s.combinedNonce == nil {
|
||||
return nil, nil, ErrCombinedNonceUnavailable
|
||||
}
|
||||
|
||||
if len(s.sigs) != 0 {
|
||||
return nil, nil, ErrAlreadySigned
|
||||
}
|
||||
|
||||
nonce, _, err := computeSigningNonce(
|
||||
*s.combinedNonce, s.ctx.combinedKey.FinalKey, msg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// The adaptor point added to the combined nonce must be even.
|
||||
// We keep generating random secrets until we find one that works.
|
||||
for {
|
||||
var secretB [32]byte
|
||||
_, err := rand.Read(secretB[:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var secret btcec.ModNScalar
|
||||
secret.SetBytes(&secretB)
|
||||
|
||||
var adaptorPoint btcec.JacobianPoint
|
||||
btcec.ScalarBaseMultNonConst(&secret, &adaptorPoint)
|
||||
adaptorPoint.ToAffine()
|
||||
|
||||
var adaptedNonce btcec.JacobianPoint
|
||||
btcec.AddNonConst(&adaptorPoint, nonce, &adaptedNonce)
|
||||
adaptedNonce.ToAffine()
|
||||
|
||||
if !adaptedNonce.Y.IsOdd() {
|
||||
s.adaptorSecret = &secret
|
||||
s.adaptorPoint = &adaptorPoint
|
||||
return &secret, &adaptorPoint, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetAdaptorSecret sets the adaptor secret key for the session. This must be
|
||||
// called after the public nonces have been registered for all signers, because
|
||||
// the validity of the adaptor secret key depends on the combined nonce. It
|
||||
// must also be called before any partial signatures are generated or provided,
|
||||
// because the validation of the signatures depends on the adaptor point.
|
||||
func (s *Session) SetAdaptorSecret(msg [32]byte, adaptorSecret *btcec.ModNScalar) error {
|
||||
if s.combinedNonce == nil {
|
||||
return ErrCombinedNonceUnavailable
|
||||
}
|
||||
|
||||
if len(s.sigs) != 0 {
|
||||
return ErrAlreadySigned
|
||||
}
|
||||
|
||||
nonce, _, err := computeSigningNonce(
|
||||
*s.combinedNonce, s.ctx.combinedKey.FinalKey, msg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var adaptorPoint btcec.JacobianPoint
|
||||
btcec.ScalarBaseMultNonConst(adaptorSecret, &adaptorPoint)
|
||||
adaptorPoint.ToAffine()
|
||||
|
||||
var adaptedNonce btcec.JacobianPoint
|
||||
btcec.AddNonConst(&adaptorPoint, nonce, &adaptedNonce)
|
||||
adaptedNonce.ToAffine()
|
||||
|
||||
if adaptedNonce.Y.IsOdd() {
|
||||
return ErrInvalidAdaptorPoint
|
||||
}
|
||||
|
||||
s.adaptorSecret = adaptorSecret
|
||||
s.adaptorPoint = &adaptorPoint
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAdaptorPoint sets the adaptor point for the session. This is called by
|
||||
// other signers after the adaptor point has been generated by the signer that
|
||||
// knows the adaptor secret key. It must be called after all the public nonces
|
||||
// have been registered, and before any partial signatures are generated or
|
||||
// provided.
|
||||
func (s *Session) SetAdaptorPoint(msg [32]byte, adaptorPoint *btcec.JacobianPoint) error {
|
||||
if s.combinedNonce == nil {
|
||||
return ErrCombinedNonceUnavailable
|
||||
}
|
||||
|
||||
if len(s.sigs) != 0 {
|
||||
return ErrAlreadySigned
|
||||
}
|
||||
|
||||
// Verify that the adaptor point added to the combined nonce will result in
|
||||
// a point with even y-coordinate.
|
||||
nonce, _, err := computeSigningNonce(
|
||||
*s.combinedNonce, s.ctx.combinedKey.FinalKey, msg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var adaptedNonce btcec.JacobianPoint
|
||||
btcec.AddNonConst(adaptorPoint, nonce, &adaptedNonce)
|
||||
adaptedNonce.ToAffine()
|
||||
if adaptedNonce.Y.IsOdd() {
|
||||
return ErrInvalidAdaptorPoint
|
||||
}
|
||||
|
||||
s.adaptorPoint = adaptorPoint
|
||||
|
||||
return 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.
|
||||
|
@ -579,6 +727,10 @@ func (s *Session) Sign(msg [32]byte,
|
|||
signOpts = append(signOpts, WithTweaks(s.ctx.opts.tweaks...))
|
||||
}
|
||||
|
||||
if s.adaptorPoint != nil {
|
||||
signOpts = append(signOpts, WithAdaptorSign(s.adaptorPoint))
|
||||
}
|
||||
|
||||
partialSig, err := Sign(
|
||||
s.localNonces.SecNonce, s.ctx.signingKey, *s.combinedNonce,
|
||||
s.ctx.opts.keySet, msg, signOpts...,
|
||||
|
@ -647,14 +799,21 @@ func (s *Session) CombineSig(sig *PartialSignature) (bool, error) {
|
|||
)
|
||||
}
|
||||
|
||||
if s.adaptorPoint != nil {
|
||||
combineOpts = append(combineOpts, WithAdaptorCombine(s.msg, s.adaptorPoint))
|
||||
}
|
||||
|
||||
finalSig := CombineSigs(s.ourSig.R, s.sigs, combineOpts...)
|
||||
|
||||
// We'll also verify the signature at this point to ensure it's
|
||||
// valid.
|
||||
// valid. For adaptor signatures, verification is done when the
|
||||
// signature is adapted using the secret key.
|
||||
//
|
||||
// TODO(roasbef): allow skipping?
|
||||
if !finalSig.Verify(s.msg[:], s.ctx.combinedKey.FinalKey) {
|
||||
return false, ErrFinalSigInvalid
|
||||
if s.adaptorPoint == nil {
|
||||
if !finalSig.Verify(s.msg[:], s.ctx.combinedKey.FinalKey) {
|
||||
return false, ErrFinalSigInvalid
|
||||
}
|
||||
}
|
||||
|
||||
s.finalSig = finalSig
|
||||
|
@ -667,3 +826,51 @@ func (s *Session) CombineSig(sig *PartialSignature) (bool, error) {
|
|||
func (s *Session) FinalSig() *schnorr.Signature {
|
||||
return s.finalSig
|
||||
}
|
||||
|
||||
// AdaptFinalSig adapts the final signature with the provided adaptor secret
|
||||
// key, and returns a valid signature.
|
||||
func (s *Session) AdaptFinalSig(adaptorSecret *btcec.ModNScalar) (*schnorr.Signature, error) {
|
||||
if s.finalSig == nil {
|
||||
return nil, ErrFinalSigUnavailable
|
||||
}
|
||||
if s.adaptorSecret == nil {
|
||||
return nil, ErrAdaptorSecretUnavailable
|
||||
}
|
||||
|
||||
sigB := s.finalSig.Serialize()
|
||||
r := new(btcec.FieldVal)
|
||||
r.SetByteSlice(sigB[0:32])
|
||||
sigS := new(btcec.ModNScalar)
|
||||
sigS.SetByteSlice(sigB[32:64])
|
||||
|
||||
sigS.Add(adaptorSecret)
|
||||
adaptedSig := schnorr.NewSignature(r, sigS)
|
||||
|
||||
if !adaptedSig.Verify(s.msg[:], s.ctx.combinedKey.FinalKey) {
|
||||
return nil, ErrFinalSigInvalid
|
||||
}
|
||||
|
||||
return adaptedSig, nil
|
||||
}
|
||||
|
||||
// RecoverAdaptorSecret recovers the adaptor secret key from a valid signature
|
||||
// created by the signer that knows the adaptor secret key.
|
||||
func (s *Session) RecoverAdaptorSecret(sig *schnorr.Signature) (*btcec.ModNScalar, error) {
|
||||
if s.finalSig == nil {
|
||||
return nil, ErrFinalSigUnavailable
|
||||
}
|
||||
if s.adaptorPoint == nil {
|
||||
return nil, ErrAdaptorPointUnavailable
|
||||
}
|
||||
|
||||
finalSigB := s.finalSig.Serialize()
|
||||
finalSigS := new(btcec.ModNScalar)
|
||||
finalSigS.SetByteSlice(finalSigB[32:64])
|
||||
|
||||
sigSB := sig.Serialize()
|
||||
sigS := new(btcec.ModNScalar)
|
||||
sigS.SetByteSlice(sigSB[32:64])
|
||||
sigS.Add(finalSigS.Negate())
|
||||
|
||||
return sigS, nil
|
||||
}
|
||||
|
|
|
@ -134,6 +134,10 @@ type signOptions struct {
|
|||
// 86 style, where we don't expect an actual tweak and instead just
|
||||
// commit to the public key itself.
|
||||
bip86Tweak bool
|
||||
|
||||
// adaptorPoint is used to tweak the nonce in the commitment in order
|
||||
// to create a partial adaptor signature.
|
||||
adaptorPoint *btcec.JacobianPoint
|
||||
}
|
||||
|
||||
// defaultSignOptions returns the default set of signing operations.
|
||||
|
@ -181,6 +185,14 @@ func WithTaprootSignTweak(scriptRoot []byte) SignOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithAdaptorSign allows a caller to specify an adaptor point that should be
|
||||
// used to create a partial adaptor signature.
|
||||
func WithAdaptorSign(adaptorPoint *btcec.JacobianPoint) SignOption {
|
||||
return func(o *signOptions) {
|
||||
o.adaptorPoint = adaptorPoint
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -320,6 +332,19 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
nonce.ToAffine()
|
||||
|
||||
var adaptedNonce *btcec.JacobianPoint
|
||||
if opts.adaptorPoint != nil {
|
||||
adaptedNonce = new(btcec.JacobianPoint)
|
||||
btcec.AddNonConst(nonce, opts.adaptorPoint, adaptedNonce)
|
||||
adaptedNonce.ToAffine()
|
||||
} else {
|
||||
adaptedNonce = nonce
|
||||
}
|
||||
|
||||
adaptedNonceKey := btcec.NewPublicKey(&adaptedNonce.X, &adaptedNonce.Y)
|
||||
|
||||
// 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
|
||||
|
@ -330,13 +355,9 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
|
|||
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() {
|
||||
if adaptedNonce.Y.IsOdd() {
|
||||
k1.Negate()
|
||||
k2.Negate()
|
||||
}
|
||||
|
@ -368,7 +389,7 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
|
|||
// 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(adaptedNonceKey))
|
||||
challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
|
||||
challengeMsg.Write(msg[:])
|
||||
challengeBytes := chainhash.TaggedHash(
|
||||
|
@ -386,7 +407,7 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey,
|
|||
s := new(btcec.ModNScalar)
|
||||
s.Add(&k1).Add(k2.Mul(nonceBlinder)).Add(e.Mul(a).Mul(&privKeyScalar))
|
||||
|
||||
sig := NewPartialSignature(s, nonceKey)
|
||||
sig := NewPartialSignature(s, adaptedNonceKey)
|
||||
|
||||
// If we're not in fast sign mode, then we'll also validate our partial
|
||||
// signature.
|
||||
|
@ -500,6 +521,21 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte,
|
|||
btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
|
||||
btcec.AddNonConst(&r1J, &r2J, &nonce)
|
||||
|
||||
var adaptedNonce *btcec.JacobianPoint
|
||||
if opts.adaptorPoint != nil {
|
||||
adaptedNonce = new(btcec.JacobianPoint)
|
||||
btcec.AddNonConst(&nonce, opts.adaptorPoint, adaptedNonce)
|
||||
} else {
|
||||
adaptedNonce = &nonce
|
||||
}
|
||||
|
||||
// If the nonce is the infinity point we set it to the Generator.
|
||||
if *adaptedNonce == infinityPoint {
|
||||
btcec.GeneratorJacobian(adaptedNonce)
|
||||
} else {
|
||||
adaptedNonce.ToAffine()
|
||||
}
|
||||
|
||||
// Next, we'll parse out the set of public nonces this signer used to
|
||||
// generate the signature.
|
||||
pubNonce1J, err := btcec.ParseJacobian(
|
||||
|
@ -515,13 +551,6 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte,
|
|||
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
|
||||
|
@ -533,7 +562,7 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte,
|
|||
|
||||
// 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() {
|
||||
if adaptedNonce.Y.IsOdd() {
|
||||
pubNonceJ.Y.Negate(1)
|
||||
pubNonceJ.Y.Normalize()
|
||||
}
|
||||
|
@ -543,7 +572,7 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte,
|
|||
// * e = H(tag=ChallengeHashTag, R || Q || m) mod n
|
||||
var challengeMsg bytes.Buffer
|
||||
challengeMsg.Write(schnorr.SerializePubKey(btcec.NewPublicKey(
|
||||
&nonce.X, &nonce.Y,
|
||||
&adaptedNonce.X, &adaptedNonce.Y,
|
||||
)))
|
||||
challengeMsg.Write(schnorr.SerializePubKey(combinedKey.FinalKey))
|
||||
challengeMsg.Write(msg[:])
|
||||
|
@ -606,6 +635,8 @@ type combineOptions struct {
|
|||
combinedKey *btcec.PublicKey
|
||||
|
||||
tweakAcc *btcec.ModNScalar
|
||||
|
||||
adaptorPub *btcec.JacobianPoint
|
||||
}
|
||||
|
||||
// defaultCombineOptions returns the default set of signing operations.
|
||||
|
@ -632,6 +663,16 @@ func WithTweakedCombine(msg [32]byte, keys []*btcec.PublicKey,
|
|||
}
|
||||
}
|
||||
|
||||
// WithAdaptorCombine is a functional option that allows callers to specify
|
||||
// that the signature was produced using an adaptor point. In order to properly
|
||||
// combine the partial signatures, the caller must specify the adaptor point.
|
||||
func WithAdaptorCombine(msg [32]byte, adaptorPub *btcec.JacobianPoint) CombineOption {
|
||||
return func(o *combineOptions) {
|
||||
o.msg = msg
|
||||
o.adaptorPub = adaptorPub
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
|
@ -4,6 +4,7 @@ package musig2
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -389,3 +390,119 @@ func TestMusig2SignCombine(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMusig2SignCombineAdaptor(t *testing.T) {
|
||||
// Pre-generate 10 deterministic private keys
|
||||
privKeys := make([]*btcec.PrivateKey, 10)
|
||||
pubKeys := make([]*btcec.PublicKey, 10)
|
||||
for i := 0; i < 10; i++ {
|
||||
seed := sha256.Sum256([]byte(fmt.Sprintf("privkey_%d", i)))
|
||||
privKey, _ := btcec.PrivKeyFromBytes(seed[:])
|
||||
privKeys[i] = privKey
|
||||
pubKeys[i] = privKey.PubKey()
|
||||
}
|
||||
|
||||
// Helper function to run test with different number of signers and context options
|
||||
runTest := func(t *testing.T, numSigners int, ctxOpt ContextOption) {
|
||||
contexts := make([]*Context, numSigners)
|
||||
sessions := make([]*Session, numSigners)
|
||||
|
||||
// Create context for each signer
|
||||
for i := 0; i < numSigners; i++ {
|
||||
ctx, err := NewContext(
|
||||
privKeys[i], true,
|
||||
WithNumSigners(numSigners),
|
||||
ctxOpt,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
contexts[i] = ctx
|
||||
|
||||
// Register all other public keys
|
||||
for j := 0; j < numSigners; j++ {
|
||||
if j != i {
|
||||
_, err = contexts[i].RegisterSigner(pubKeys[j])
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create session
|
||||
sess, err := contexts[i].NewSession()
|
||||
require.NoError(t, err)
|
||||
sessions[i] = sess
|
||||
}
|
||||
|
||||
// Exchange public nonces
|
||||
for i := 0; i < numSigners; i++ {
|
||||
for j := 0; j < numSigners; j++ {
|
||||
if j != i {
|
||||
_, err := sessions[i].RegisterPubNonce(sessions[j].PublicNonce())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message to sign
|
||||
msg := [32]byte{1, 2, 3, 4}
|
||||
|
||||
// Signer 0 generates and uses the adaptor secret
|
||||
adaptorSecret, adaptorPoint, err := sessions[0].GenerateAdaptor(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
// No-op, but test SetAdaptorSecret
|
||||
err = sessions[0].SetAdaptorSecret(msg, adaptorSecret)
|
||||
require.NoError(t, err)
|
||||
|
||||
// All other signers set the adaptor point
|
||||
for i := 1; i < numSigners; i++ {
|
||||
err = sessions[i].SetAdaptorPoint(msg, adaptorPoint)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Each signer generates a partial signature
|
||||
partialSigs := make([]*PartialSignature, numSigners)
|
||||
for i := 0; i < numSigners; i++ {
|
||||
sig, err := sessions[i].Sign(msg)
|
||||
require.NoError(t, err)
|
||||
partialSigs[i] = sig
|
||||
}
|
||||
|
||||
// Each signer combines all partial signatures
|
||||
for i := 0; i < numSigners; i++ {
|
||||
for j := 0; j < numSigners; j++ {
|
||||
if j != i {
|
||||
_, err := sessions[i].CombineSig(partialSigs[j])
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Signer 0 adapts the final signature
|
||||
validSig, err := sessions[0].AdaptFinalSig(adaptorSecret)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the final signature is valid under the combined key
|
||||
combinedKey, _ := contexts[0].CombinedKey()
|
||||
require.True(t, validSig.Verify(msg[:], combinedKey),
|
||||
"Final signature verification failed")
|
||||
|
||||
// All other signers recover the adaptor secret
|
||||
for i := 1; i < numSigners; i++ {
|
||||
recoveredSecret, err := sessions[i].RecoverAdaptorSecret(validSig)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, adaptorSecret.Bytes(), recoveredSecret.Bytes(),
|
||||
"Recovered secret mismatch for signer %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Run test for 2-10 signers with different context options
|
||||
for numSigners := 2; numSigners <= 10; numSigners++ {
|
||||
t.Run(fmt.Sprintf("BIP86_%d_signers", numSigners), func(t *testing.T) {
|
||||
runTest(t, numSigners, WithBip86TweakCtx())
|
||||
})
|
||||
|
||||
scriptRoot := sha256.Sum256([]byte("test_script_root"))
|
||||
t.Run(fmt.Sprintf("Taproot_%d_signers", numSigners), func(t *testing.T) {
|
||||
runTest(t, numSigners, WithTaprootTweakCtx(scriptRoot[:]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue