Skip to content

Commit b2594f2

Browse files
committed
musig2: add Session.RegisterCombinedNonce
This commit adds a new function to musig2.Session, which allows the caller to add an external aggregated nonce to the session.
1 parent b7d0706 commit b2594f2

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

btcec/schnorr/musig2/context.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,32 @@ func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) {
548548
return haveAllNonces, nil
549549
}
550550

551+
// RegisterCombinedNonce allows a caller to directly register a combined nonce
552+
// that was generated externally. This is useful in coordinator-based
553+
// protocols where the coordinator aggregates all nonces and distributes the
554+
// combined nonce to participants, rather than each participant aggregating
555+
// nonces themselves.
556+
func (s *Session) RegisterCombinedNonce(
557+
combinedNonce [PubNonceSize]byte) error {
558+
559+
// If we already have a combined nonce, then this method was called too
560+
// many times.
561+
if s.combinedNonce != nil {
562+
return ErrAlredyHaveAllNonces
563+
}
564+
565+
// We also don't allow this method to be called if we already registered
566+
// some public nonces.
567+
if len(s.pubNonces) != 1 {
568+
return fmt.Errorf("cannot register combined nonce after " +
569+
"registering public nonces")
570+
}
571+
572+
// Otherwise, we'll just set the combined nonce directly.
573+
s.combinedNonce = &combinedNonce
574+
return nil
575+
}
576+
551577
// Sign generates a partial signature for the target message, using the target
552578
// context. If this method is called more than once per context, then an error
553579
// is returned, as that means a nonce was re-used.

btcec/schnorr/musig2/musig2_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,83 @@ func (mr *memsetRandReader) Read(buf []byte) (n int, err error) {
439439
}
440440
return len(buf), nil
441441
}
442+
443+
// TestSigningWithAggregatedNonce tests the aggregated nonce signing flow where
444+
// a coordinator aggregates nonces and distributes the combined nonce to
445+
// participants, rather than each participant aggregating nonces themselves.
446+
func TestSigningWithAggregatedNonce(t *testing.T) {
447+
const numSigners = 5
448+
449+
// Generate signers.
450+
signerKeys := make([]*btcec.PrivateKey, numSigners)
451+
signSet := make([]*btcec.PublicKey, numSigners)
452+
for i := 0; i < numSigners; i++ {
453+
privKey, err := btcec.NewPrivateKey()
454+
if err != nil {
455+
t.Fatalf("unable to gen priv key: %v", err)
456+
}
457+
signerKeys[i] = privKey
458+
signSet[i] = privKey.PubKey()
459+
}
460+
461+
// Each signer creates a context and session.
462+
sessions := make([]*Session, numSigners)
463+
for i, signerKey := range signerKeys {
464+
signCtx, err := NewContext(
465+
signerKey, false, WithKnownSigners(signSet),
466+
)
467+
if err != nil {
468+
t.Fatalf("unable to generate context: %v", err)
469+
}
470+
471+
session, err := signCtx.NewSession()
472+
if err != nil {
473+
t.Fatalf("unable to generate new session: %v", err)
474+
}
475+
sessions[i] = session
476+
}
477+
478+
// Phase 1: Coordinator collects all public nonces.
479+
pubNonces := make([][PubNonceSize]byte, numSigners)
480+
for i, session := range sessions {
481+
pubNonces[i] = session.PublicNonce()
482+
}
483+
484+
// Phase 2: Coordinator aggregates nonces.
485+
combinedNonce, err := AggregateNonces(pubNonces)
486+
if err != nil {
487+
t.Fatalf("unable to aggregate nonces: %v", err)
488+
}
489+
490+
// Phase 3: Coordinator distributes combined nonce to all participants.
491+
// Participants sign using the external combined nonce.
492+
msg := sha256.Sum256([]byte("coordinator-based signing"))
493+
494+
partialSigs := make([]*PartialSignature, numSigners)
495+
for i, session := range sessions {
496+
// Participants first register the combined nonce.
497+
err = session.RegisterCombinedNonce(combinedNonce)
498+
if err != nil {
499+
t.Fatalf("signer %d unable to register combined nonce: %v",
500+
i, err)
501+
}
502+
sig, err := session.Sign(msg)
503+
if err != nil {
504+
t.Fatalf("signer %d unable to sign: %v", i, err)
505+
}
506+
partialSigs[i] = sig
507+
}
508+
509+
// Phase 4: Combine all partial signatures (can be done by any party).
510+
finalSig := CombineSigs(partialSigs[0].R, partialSigs)
511+
512+
// Verify the final signature.
513+
combinedKey, _, _, err := AggregateKeys(signSet, false)
514+
if err != nil {
515+
t.Fatalf("unable to aggregate keys: %v", err)
516+
}
517+
518+
if !finalSig.Verify(msg[:], combinedKey.FinalKey) {
519+
t.Fatalf("final signature is invalid")
520+
}
521+
}

0 commit comments

Comments
 (0)