Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion btcec/schnorr/musig2/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ var (
// ErrNotEnoughSigners is returned if a caller attempts to obtain an
// early nonce when it wasn't specified
ErrNoEarlyNonce = fmt.Errorf("no early nonce available")

// ErrCombinedNonceAfterPubNonces is returned if RegisterCombinedNonce
// is called after public nonces have already been registered.
ErrCombinedNonceAfterPubNonces = fmt.Errorf("can't register combined " +
"nonce after public nonces")
)

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

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

// CombinedNonce returns the combined public nonce for the signing session.
// This will be available after either:
// - All individual nonces have been registered via RegisterPubNonce, or
// - A combined nonce has been registered via RegisterCombinedNonce
//
// If the combined nonce is not yet available, this method returns an error.
func (s *Session) CombinedNonce() ([PubNonceSize]byte, error) {
Copy link
Collaborator Author

@sputn1ck sputn1ck Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this approach is good(allows for more missuse of the nonces i guess) or if we should just return the combined nonce one time only if we register the last pubnonce? (changing RegisterPubNonce sig to return the aggregated nonce, instead of a havAllNonces bool)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each time we sign, we blank out localNonces, which prevents us from signing again directly after unless a new set of local nonces are generated.

if s.combinedNonce == nil {
return [PubNonceSize]byte{}, ErrCombinedNonceUnavailable
}

return *s.combinedNonce, nil
}

// RegisterCombinedNonce allows a caller to directly register a combined nonce
// that was generated externally. This is useful in coordinator-based
// protocols where the coordinator aggregates all nonces and distributes the
// combined nonce to participants, rather than each participant aggregating
// nonces themselves.
func (s *Session) RegisterCombinedNonce(
combinedNonce [PubNonceSize]byte) error {

// If we already have a combined nonce, then this method was called too
// many times.
if s.combinedNonce != nil {
return ErrAlredyHaveAllNonces
}

// We also don't allow this method to be called if we already registered
// some public nonces.
if len(s.pubNonces) > 1 {
return ErrCombinedNonceAfterPubNonces
}

// We'll now try to parse the combined nonce into it's two points to
// ensure it's valid.
_, err := btcec.ParsePubKey(combinedNonce[:33])
if err != nil {
return fmt.Errorf("invalid combined nonce: %w", err)
}
_, err = btcec.ParsePubKey(combinedNonce[33:])
if err != nil {
return fmt.Errorf("invalid combined nonce: %w", err)
}

// Otherwise, we'll just set the combined nonce directly.
s.combinedNonce = &combinedNonce

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.
Expand Down
Loading
Loading