Skip to content

Commit 442ef28

Browse files
authored
Merge pull request #2443 from sputn1ck/musig2_sign_with_agg_nonce
musig2: add WithExternalCombinedNonce option to Sign
2 parents b7d0706 + 21eb99e commit 442ef28

File tree

2 files changed

+460
-1
lines changed

2 files changed

+460
-1
lines changed

btcec/schnorr/musig2/context.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ var (
5959
// ErrNotEnoughSigners is returned if a caller attempts to obtain an
6060
// early nonce when it wasn't specified
6161
ErrNoEarlyNonce = fmt.Errorf("no early nonce available")
62+
63+
// ErrCombinedNonceAfterPubNonces is returned if RegisterCombinedNonce
64+
// is called after public nonces have already been registered.
65+
ErrCombinedNonceAfterPubNonces = fmt.Errorf("can't register combined " +
66+
"nonce after public nonces")
6267
)
6368

6469
// Context is a managed signing context for musig2. It takes care of things
@@ -525,7 +530,7 @@ func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) {
525530
// If we already have all the nonces, then this method was called too
526531
// many times.
527532
haveAllNonces := len(s.pubNonces) == s.ctx.opts.numSigners
528-
if haveAllNonces {
533+
if haveAllNonces || s.combinedNonce != nil {
529534
return false, ErrAlredyHaveAllNonces
530535
}
531536

@@ -548,6 +553,57 @@ func (s *Session) RegisterPubNonce(nonce [PubNonceSize]byte) (bool, error) {
548553
return haveAllNonces, nil
549554
}
550555

556+
// CombinedNonce returns the combined public nonce for the signing session.
557+
// This will be available after either:
558+
// - All individual nonces have been registered via RegisterPubNonce, or
559+
// - A combined nonce has been registered via RegisterCombinedNonce
560+
//
561+
// If the combined nonce is not yet available, this method returns an error.
562+
func (s *Session) CombinedNonce() ([PubNonceSize]byte, error) {
563+
if s.combinedNonce == nil {
564+
return [PubNonceSize]byte{}, ErrCombinedNonceUnavailable
565+
}
566+
567+
return *s.combinedNonce, nil
568+
}
569+
570+
// RegisterCombinedNonce allows a caller to directly register a combined nonce
571+
// that was generated externally. This is useful in coordinator-based
572+
// protocols where the coordinator aggregates all nonces and distributes the
573+
// combined nonce to participants, rather than each participant aggregating
574+
// nonces themselves.
575+
func (s *Session) RegisterCombinedNonce(
576+
combinedNonce [PubNonceSize]byte) error {
577+
578+
// If we already have a combined nonce, then this method was called too
579+
// many times.
580+
if s.combinedNonce != nil {
581+
return ErrAlredyHaveAllNonces
582+
}
583+
584+
// We also don't allow this method to be called if we already registered
585+
// some public nonces.
586+
if len(s.pubNonces) > 1 {
587+
return ErrCombinedNonceAfterPubNonces
588+
}
589+
590+
// We'll now try to parse the combined nonce into it's two points to
591+
// ensure it's valid.
592+
_, err := btcec.ParsePubKey(combinedNonce[:33])
593+
if err != nil {
594+
return fmt.Errorf("invalid combined nonce: %w", err)
595+
}
596+
_, err = btcec.ParsePubKey(combinedNonce[33:])
597+
if err != nil {
598+
return fmt.Errorf("invalid combined nonce: %w", err)
599+
}
600+
601+
// Otherwise, we'll just set the combined nonce directly.
602+
s.combinedNonce = &combinedNonce
603+
604+
return nil
605+
}
606+
551607
// Sign generates a partial signature for the target message, using the target
552608
// context. If this method is called more than once per context, then an error
553609
// is returned, as that means a nonce was re-used.

0 commit comments

Comments
 (0)