Skip to content

Commit 3d7ff86

Browse files
authored
Merge pull request #10436 from ellemouton/musigRegisterAggNonce
2 parents 456d7dc + a7b61f3 commit 3d7ff86

File tree

21 files changed

+1541
-178
lines changed

21 files changed

+1541
-178
lines changed

docs/release-notes/release-notes-0.21.0.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@
5050

5151
## RPC Additions
5252

53+
* [Added support for coordinator-based MuSig2 signing
54+
patterns](https://github.com/lightningnetwork/lnd/pull/10436) with two new
55+
RPCs: `MuSig2RegisterCombinedNonce` allows registering a pre-aggregated
56+
combined nonce for a session (useful when a coordinator aggregates all nonces
57+
externally), and `MuSig2GetCombinedNonce` retrieves the combined nonce after
58+
it becomes available. These methods provide an alternative to the standard
59+
`MuSig2RegisterNonces` workflow and are only supported in MuSig2 v1.0.0rc2.
60+
5361
## lncli Additions
5462

5563
# Improvements

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ require (
55
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344
66
github.com/andybalholm/brotli v1.0.4
77
github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6
8-
github.com/btcsuite/btcd/btcec/v2 v2.3.4
8+
github.com/btcsuite/btcd/btcec/v2 v2.3.6
99
github.com/btcsuite/btcd/btcutil v1.1.5
1010
github.com/btcsuite/btcd/btcutil/psbt v1.1.8
1111
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 h1:8n9k3I7e8DkpdQ
4646
github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6/go.mod h1:OmM4kFtB0klaG/ZqT86rQiyw/1iyXlJgc3UHClPhhbs=
4747
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
4848
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
49-
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
50-
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
49+
github.com/btcsuite/btcd/btcec/v2 v2.3.6 h1:IzlsEr9olcSRKB/n7c4351F3xHKxS2lma+1UFGCYd4E=
50+
github.com/btcsuite/btcd/btcec/v2 v2.3.6/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
5151
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
5252
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
5353
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=

input/mocks.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,29 @@ func (m *MockInputSigner) MuSig2RegisterNonces(versio MuSig2SessionID,
258258
return args.Bool(0), args.Error(1)
259259
}
260260

261+
// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a
262+
// session identified by its ID.
263+
func (m *MockInputSigner) MuSig2RegisterCombinedNonce(sessionID MuSig2SessionID,
264+
combinedNonce [musig2.PubNonceSize]byte) error {
265+
266+
args := m.Called(sessionID, combinedNonce)
267+
268+
return args.Error(0)
269+
}
270+
271+
// MuSig2GetCombinedNonce retrieves the combined nonce for a session identified
272+
// by its ID.
273+
func (m *MockInputSigner) MuSig2GetCombinedNonce(sessionID MuSig2SessionID) (
274+
[musig2.PubNonceSize]byte, error) {
275+
276+
args := m.Called(sessionID)
277+
if args.Get(0) == nil {
278+
return [musig2.PubNonceSize]byte{}, args.Error(1)
279+
}
280+
281+
return args.Get(0).([musig2.PubNonceSize]byte), args.Error(1)
282+
}
283+
261284
// MuSig2Sign creates a partial signature using the local signing key that was
262285
// specified when the session was created.
263286
func (m *MockInputSigner) MuSig2Sign(sessionID MuSig2SessionID,

input/musig2.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,25 @@ type MuSig2Signer interface {
6464
MuSig2RegisterNonces(MuSig2SessionID,
6565
[][musig2.PubNonceSize]byte) (bool, error)
6666

67+
// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce
68+
// for a session identified by its ID. This is an alternative to
69+
// MuSig2RegisterNonces and is used when a coordinator has already
70+
// aggregated all individual nonces and wants to distribute the combined
71+
// nonce to participants.
72+
//
73+
// NOTE: This method is mutually exclusive with MuSig2RegisterNonces for
74+
// the same session. Once this method is called, MuSig2RegisterNonces
75+
// will return an error if called later for the same session.
76+
MuSig2RegisterCombinedNonce(MuSig2SessionID,
77+
[musig2.PubNonceSize]byte) error
78+
79+
// MuSig2GetCombinedNonce retrieves the combined nonce for a session
80+
// identified by its ID. This will be available after either all
81+
// individual nonces have been registered via MuSig2RegisterNonces, or a
82+
// combined nonce has been registered via MuSig2RegisterCombinedNonce.
83+
MuSig2GetCombinedNonce(MuSig2SessionID) ([musig2.PubNonceSize]byte,
84+
error)
85+
6786
// MuSig2Sign creates a partial signature using the local signing key
6887
// that was specified when the session was created. This can only be
6988
// called when all public nonces of all participants are known and have
@@ -130,6 +149,26 @@ type MuSig2Session interface {
130149
// of signers. This method returns true once all the public nonces have
131150
// been accounted for.
132151
RegisterPubNonce(nonce [musig2.PubNonceSize]byte) (bool, error)
152+
153+
// CombinedNonce returns the combined/aggregated public nonce for the
154+
// session. This will be available after either all individual nonces
155+
// have been registered via RegisterPubNonce, or a combined nonce has
156+
// been registered via RegisterCombinedNonce.
157+
//
158+
// If the combined nonce is not yet available, this method returns an
159+
// error.
160+
CombinedNonce() ([musig2.PubNonceSize]byte, error)
161+
162+
// RegisterCombinedNonce allows a caller to directly register a
163+
// pre-aggregated nonce that was generated externally. This is useful
164+
// in coordinator-based protocols where the coordinator aggregates all
165+
// nonces and distributes the combined nonce to participants.
166+
//
167+
// NOTE: This method is mutually exclusive with RegisterPubNonce. Once
168+
// this method is called, RegisterPubNonce will return an error if
169+
// called later. Similarly, if RegisterPubNonce has already been called,
170+
// this method will return an error.
171+
RegisterCombinedNonce(combinedNonce [musig2.PubNonceSize]byte) error
133172
}
134173

135174
// MuSig2SessionInfo is a struct for keeping track of a signing session

input/musig2_session_manager.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,70 @@ func (m *MusigSessionManager) MuSig2RegisterNonces(sessionID MuSig2SessionID,
302302

303303
return session.HaveAllNonces, nil
304304
}
305+
306+
// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a
307+
// session identified by its ID. This is an alternative to MuSig2RegisterNonces
308+
// and is used when a coordinator has already aggregated all individual nonces
309+
// and wants to distribute the combined nonce to participants.
310+
//
311+
// NOTE: This method is mutually exclusive with MuSig2RegisterNonces for the
312+
// same session. Once this method is called, MuSig2RegisterNonces will return
313+
// an error if called later for the same session.
314+
func (m *MusigSessionManager) MuSig2RegisterCombinedNonce(
315+
sessionID MuSig2SessionID,
316+
combinedNonce [musig2.PubNonceSize]byte) error {
317+
318+
// Hold the lock during the whole operation.
319+
m.sessionMtx.Lock(sessionID)
320+
defer m.sessionMtx.Unlock(sessionID)
321+
322+
// Load the session.
323+
session, ok := m.musig2Sessions.Load(sessionID)
324+
if !ok {
325+
return fmt.Errorf("session with ID %x not found", sessionID[:])
326+
}
327+
328+
// Check if we already have all nonces.
329+
if session.HaveAllNonces {
330+
return fmt.Errorf("already have all nonces")
331+
}
332+
333+
// Delegate to the version-specific implementation.
334+
err := session.session.RegisterCombinedNonce(combinedNonce)
335+
if err != nil {
336+
return fmt.Errorf("error registering combined nonce: %w", err)
337+
}
338+
339+
// Mark that we have all nonces now.
340+
session.HaveAllNonces = true
341+
342+
return nil
343+
}
344+
345+
// MuSig2GetCombinedNonce retrieves the combined nonce for a session identified
346+
// by its ID. This will be available after either all individual nonces have
347+
// been registered via MuSig2RegisterNonces, or a combined nonce has been
348+
// registered via MuSig2RegisterCombinedNonce.
349+
func (m *MusigSessionManager) MuSig2GetCombinedNonce(
350+
sessionID MuSig2SessionID) ([musig2.PubNonceSize]byte, error) {
351+
352+
// Hold the lock during the operation.
353+
m.sessionMtx.Lock(sessionID)
354+
defer m.sessionMtx.Unlock(sessionID)
355+
356+
// Load the session.
357+
session, ok := m.musig2Sessions.Load(sessionID)
358+
if !ok {
359+
return [musig2.PubNonceSize]byte{}, fmt.Errorf("session with "+
360+
"ID %x not found", sessionID[:])
361+
}
362+
363+
// Get the combined nonce from the session.
364+
combinedNonce, err := session.session.CombinedNonce()
365+
if err != nil {
366+
return [musig2.PubNonceSize]byte{}, fmt.Errorf("error getting "+
367+
"combined nonce: %w", err)
368+
}
369+
370+
return combinedNonce, nil
371+
}

internal/musig2v040/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,17 @@ This corresponds to the [MuSig2 BIP specification version of
88

99
We only keep this code here to allow implementing a backward compatible,
1010
versioned MuSig2 RPC.
11+
12+
## Unsupported Methods
13+
14+
The following methods from the newer MuSig2 specifications are not supported in
15+
this legacy v0.4.0 implementation and will return `ErrUnsupportedMethod` if
16+
called:
17+
18+
- `CombinedNonce()`: Returns error instead of the combined nonce.
19+
- `RegisterCombinedNonce()`: Returns error instead of registering a
20+
pre-aggregated combined nonce.
21+
22+
These methods are only available when using MuSig2 v1.0.0rc2 or later. To use
23+
these features, create sessions with `MuSig2Version100RC2` instead of
24+
`MuSig2Version040`.

internal/musig2v040/context.go

Lines changed: 17 additions & 0 deletions
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+
// ErrUnsupportedMethod is returned when calling methods that are not
64+
// supported in the legacy v0.4.0 implementation.
65+
ErrUnsupportedMethod = fmt.Errorf("method not supported in MuSig2 " +
66+
"v0.4.0")
6267
)
6368

6469
// Context is a managed signing context for musig2. It takes care of things
@@ -668,3 +673,15 @@ func (s *Session) CombineSig(sig *PartialSignature) (bool, error) {
668673
func (s *Session) FinalSig() *schnorr.Signature {
669674
return s.finalSig
670675
}
676+
677+
// CombinedNonce is not supported in the legacy v0.4.0 implementation and will
678+
// always return an error.
679+
func (s *Session) CombinedNonce() ([PubNonceSize]byte, error) {
680+
return [PubNonceSize]byte{}, ErrUnsupportedMethod
681+
}
682+
683+
// RegisterCombinedNonce is not supported in the legacy v0.4.0 implementation
684+
// and will always return an error.
685+
func (s *Session) RegisterCombinedNonce(_ [PubNonceSize]byte) error {
686+
return ErrUnsupportedMethod
687+
}

0 commit comments

Comments
 (0)