Skip to content

Commit 824e867

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 824e867

File tree

2 files changed

+336
-1
lines changed

2 files changed

+336
-1
lines changed

btcec/schnorr/musig2/context.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) {
525525
// If we already have all the nonces, then this method was called too
526526
// many times.
527527
haveAllNonces := len(s.pubNonces) == s.ctx.opts.numSigners
528-
if haveAllNonces {
528+
if haveAllNonces || s.combinedNonce != nil {
529529
return false, ErrAlredyHaveAllNonces
530530
}
531531

@@ -548,6 +548,33 @@ 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+
575+
return nil
576+
}
577+
551578
// Sign generates a partial signature for the target message, using the target
552579
// context. If this method is called more than once per context, then an error
553580
// is returned, as that means a nonce was re-used.

btcec/schnorr/musig2/musig2_test.go

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,311 @@ 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+
// nonces are aggregated externally and provided to participants via
445+
// RegisterCombinedNonce, rather than each participant aggregating nonces
446+
// themselves via RegisterPubNonce.
447+
func TestSigningWithAggregatedNonce(t *testing.T) {
448+
t.Run("basic flow", func(t *testing.T) {
449+
const numSigners = 5
450+
451+
// Generate signers.
452+
signerKeys := make([]*btcec.PrivateKey, numSigners)
453+
signSet := make([]*btcec.PublicKey, numSigners)
454+
for i := 0; i < numSigners; i++ {
455+
privKey, err := btcec.NewPrivateKey()
456+
if err != nil {
457+
t.Fatalf("unable to gen priv key: %v", err)
458+
}
459+
signerKeys[i] = privKey
460+
signSet[i] = privKey.PubKey()
461+
}
462+
463+
// Each signer creates a context and session.
464+
sessions := make([]*Session, numSigners)
465+
for i, signerKey := range signerKeys {
466+
signCtx, err := NewContext(
467+
signerKey, false, WithKnownSigners(signSet),
468+
)
469+
if err != nil {
470+
t.Fatalf("unable to generate context: %v", err)
471+
}
472+
473+
session, err := signCtx.NewSession()
474+
if err != nil {
475+
t.Fatalf("unable to generate new session: %v", err)
476+
}
477+
sessions[i] = session
478+
}
479+
480+
// Phase 1: Collect all public nonces.
481+
pubNonces := make([][PubNonceSize]byte, numSigners)
482+
for i, session := range sessions {
483+
pubNonces[i] = session.PublicNonce()
484+
}
485+
486+
// Phase 2: Aggregate nonces externally.
487+
combinedNonce, err := AggregateNonces(pubNonces)
488+
if err != nil {
489+
t.Fatalf("unable to aggregate nonces: %v", err)
490+
}
491+
492+
// Phase 3: Participants register combined nonce and sign.
493+
msg := sha256.Sum256([]byte("aggregated nonce signing"))
494+
495+
partialSigs := make([]*PartialSignature, numSigners)
496+
for i, session := range sessions {
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.
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+
})
522+
523+
t.Run("error: register combined nonce twice", func(t *testing.T) {
524+
privKey, _ := btcec.NewPrivateKey()
525+
privKey2, _ := btcec.NewPrivateKey()
526+
signSet := []*btcec.PublicKey{privKey.PubKey(), privKey2.PubKey()}
527+
528+
signCtx, _ := NewContext(privKey, false, WithKnownSigners(signSet))
529+
session, _ := signCtx.NewSession()
530+
531+
fakeCombinedNonce := [PubNonceSize]byte{}
532+
533+
// First call should succeed.
534+
err := session.RegisterCombinedNonce(fakeCombinedNonce)
535+
if err != nil {
536+
t.Fatalf("first RegisterCombinedNonce failed: %v", err)
537+
}
538+
539+
// Second call should fail.
540+
err = session.RegisterCombinedNonce(fakeCombinedNonce)
541+
if err != ErrAlredyHaveAllNonces {
542+
t.Fatalf("expected ErrAlredyHaveAllNonces, got: %v", err)
543+
}
544+
})
545+
546+
t.Run("error: register combined nonce after register pub nonce",
547+
func(t *testing.T) {
548+
549+
privKey, _ := btcec.NewPrivateKey()
550+
privKey2, _ := btcec.NewPrivateKey()
551+
privKey3, _ := btcec.NewPrivateKey()
552+
signSet := []*btcec.PublicKey{
553+
privKey.PubKey(),
554+
privKey2.PubKey(),
555+
privKey3.PubKey(),
556+
}
557+
558+
signCtx, _ := NewContext(privKey, false, WithKnownSigners(signSet))
559+
session, _ := signCtx.NewSession()
560+
561+
signCtx2, _ := NewContext(privKey2, false, WithKnownSigners(signSet))
562+
session2, _ := signCtx2.NewSession()
563+
564+
// Register one public nonce first.
565+
_, err := session.RegisterPubNonce(session2.PublicNonce())
566+
if err != nil {
567+
t.Fatalf("RegisterPubNonce failed: %v", err)
568+
}
569+
570+
// Now try to register a combined nonce - this should fail.
571+
fakeCombinedNonce := [PubNonceSize]byte{}
572+
err = session.RegisterCombinedNonce(fakeCombinedNonce)
573+
if err == nil {
574+
t.Fatalf("expected error when calling RegisterCombinedNonce " +
575+
"after RegisterPubNonce")
576+
}
577+
})
578+
579+
t.Run("error: register pub nonce after register combined nonce",
580+
func(t *testing.T) {
581+
582+
const numSigners = 3
583+
584+
signerKeys := make([]*btcec.PrivateKey, numSigners)
585+
signSet := make([]*btcec.PublicKey, numSigners)
586+
for i := 0; i < numSigners; i++ {
587+
privKey, _ := btcec.NewPrivateKey()
588+
signerKeys[i] = privKey
589+
signSet[i] = privKey.PubKey()
590+
}
591+
592+
sessions := make([]*Session, numSigners)
593+
for i, signerKey := range signerKeys {
594+
signCtx, _ := NewContext(signerKey, false, WithKnownSigners(signSet))
595+
session, _ := signCtx.NewSession()
596+
sessions[i] = session
597+
}
598+
599+
pubNonces := make([][PubNonceSize]byte, numSigners)
600+
for i, session := range sessions {
601+
pubNonces[i] = session.PublicNonce()
602+
}
603+
604+
combinedNonce, _ := AggregateNonces(pubNonces)
605+
606+
// Register the combined nonce first.
607+
err := sessions[0].RegisterCombinedNonce(combinedNonce)
608+
if err != nil {
609+
t.Fatalf("RegisterCombinedNonce failed: %v", err)
610+
}
611+
612+
// Now try to register individual nonces - this should fail.
613+
_, err = sessions[0].RegisterPubNonce(pubNonces[1])
614+
if err == nil {
615+
t.Fatalf("expected error when calling RegisterPubNonce " +
616+
"after RegisterCombinedNonce")
617+
}
618+
})
619+
620+
t.Run("nonce reuse prevention", func(t *testing.T) {
621+
privKey, _ := btcec.NewPrivateKey()
622+
privKey2, _ := btcec.NewPrivateKey()
623+
signSet := []*btcec.PublicKey{privKey.PubKey(), privKey2.PubKey()}
624+
625+
signCtx, _ := NewContext(privKey, false, WithKnownSigners(signSet))
626+
session, _ := signCtx.NewSession()
627+
628+
fakeCombinedNonce := [PubNonceSize]byte{}
629+
session.RegisterCombinedNonce(fakeCombinedNonce)
630+
631+
msg := sha256.Sum256([]byte("nonce reuse test"))
632+
633+
// First sign should succeed.
634+
_, err := session.Sign(msg)
635+
if err != nil {
636+
t.Fatalf("first sign failed: %v", err)
637+
}
638+
639+
// Second sign should fail due to nonce reuse.
640+
_, err = session.Sign(msg)
641+
if err != ErrSigningContextReuse {
642+
t.Fatalf("expected nonce reuse error, got: %v", err)
643+
}
644+
})
645+
646+
t.Run("incorrect combined nonce produces invalid sig", func(t *testing.T) {
647+
const numSigners = 3
648+
649+
signerKeys := make([]*btcec.PrivateKey, numSigners)
650+
signSet := make([]*btcec.PublicKey, numSigners)
651+
for i := 0; i < numSigners; i++ {
652+
privKey, _ := btcec.NewPrivateKey()
653+
signerKeys[i] = privKey
654+
signSet[i] = privKey.PubKey()
655+
}
656+
657+
sessions := make([]*Session, numSigners)
658+
for i, signerKey := range signerKeys {
659+
signCtx, _ := NewContext(signerKey, false, WithKnownSigners(signSet))
660+
session, _ := signCtx.NewSession()
661+
sessions[i] = session
662+
}
663+
664+
pubNonces := make([][PubNonceSize]byte, numSigners)
665+
for i, session := range sessions {
666+
pubNonces[i] = session.PublicNonce()
667+
}
668+
669+
// Create INCORRECT combined nonce using only a subset.
670+
wrongNonces := pubNonces[:2]
671+
incorrectCombinedNonce, _ := AggregateNonces(wrongNonces)
672+
673+
msg := sha256.Sum256([]byte("incorrect nonce test"))
674+
675+
partialSigs := make([]*PartialSignature, numSigners)
676+
for i, session := range sessions {
677+
session.RegisterCombinedNonce(incorrectCombinedNonce)
678+
sig, _ := session.Sign(msg)
679+
partialSigs[i] = sig
680+
}
681+
682+
finalSig := CombineSigs(partialSigs[0].R, partialSigs)
683+
combinedKey, _, _, _ := AggregateKeys(signSet, false)
684+
685+
// Final signature should be INVALID.
686+
if finalSig.Verify(msg[:], combinedKey.FinalKey) {
687+
t.Fatalf("final signature should be invalid with incorrect nonce")
688+
}
689+
})
690+
691+
t.Run("mixed registration methods", func(t *testing.T) {
692+
const numSigners = 4
693+
694+
signerKeys := make([]*btcec.PrivateKey, numSigners)
695+
signSet := make([]*btcec.PublicKey, numSigners)
696+
for i := 0; i < numSigners; i++ {
697+
privKey, _ := btcec.NewPrivateKey()
698+
signerKeys[i] = privKey
699+
signSet[i] = privKey.PubKey()
700+
}
701+
702+
sessions := make([]*Session, numSigners)
703+
for i, signerKey := range signerKeys {
704+
signCtx, _ := NewContext(signerKey, false, WithKnownSigners(signSet))
705+
session, _ := signCtx.NewSession()
706+
sessions[i] = session
707+
}
708+
709+
pubNonces := make([][PubNonceSize]byte, numSigners)
710+
for i, session := range sessions {
711+
pubNonces[i] = session.PublicNonce()
712+
}
713+
714+
combinedNonce, _ := AggregateNonces(pubNonces)
715+
msg := sha256.Sum256([]byte("mixed registration test"))
716+
717+
// Half use RegisterCombinedNonce.
718+
for i := 0; i < numSigners/2; i++ {
719+
sessions[i].RegisterCombinedNonce(combinedNonce)
720+
}
721+
722+
// Other half use RegisterPubNonce.
723+
for i := numSigners / 2; i < numSigners; i++ {
724+
for j, nonce := range pubNonces {
725+
if i == j {
726+
continue
727+
}
728+
sessions[i].RegisterPubNonce(nonce)
729+
}
730+
}
731+
732+
// All should be able to sign.
733+
partialSigs := make([]*PartialSignature, numSigners)
734+
for i, session := range sessions {
735+
sig, err := session.Sign(msg)
736+
if err != nil {
737+
t.Fatalf("signer %d unable to sign: %v", i, err)
738+
}
739+
partialSigs[i] = sig
740+
}
741+
742+
finalSig := CombineSigs(partialSigs[0].R, partialSigs)
743+
combinedKey, _, _, _ := AggregateKeys(signSet, false)
744+
745+
if !finalSig.Verify(msg[:], combinedKey.FinalKey) {
746+
t.Fatalf("final signature is invalid")
747+
}
748+
})
749+
}

0 commit comments

Comments
 (0)