signrpc: implement combined nonce RPC server handlers
What changed, and why it matters
This commit adds two new server-side RPC handlers for an advanced multi-signature signing feature called MuSig2. The handlers let a caller register a pre-aggregated combined nonce and retrieve the combined nonce for a signing session. The code delegates to existing signer logic, checks the session ID format, validates the nonce length, and assigns macaroon permissions. There is no direct evidence in the commit of a security vulnerability; it appears to be a normal feature-completion change.
Review the underlying Signer.MuSig2RegisterCombinedNonce and Signer.MuSig2GetCombinedNonce implementations for correct state handling and nonce uniqueness, as the RPC layer itself only performs basic input validation. Ensure macaroon permissions align with the principle of least privilege and that combined nonce registration cannot be used to bypass existing per-participant nonce registration controls.
Security signals we found
New RPC surface added to signer subsystem
Macaroon permissions assigned: generate for register, read for get
Input validation performed for session ID and combined nonce length
Delegation to underlying Signer interface methods
Evidence from the diff
The patch implements MuSig2RegisterCombinedNonce and MuSig2GetCombinedNonce in lnrpc/signrpc/signer_server.go. Both handlers parse the session ID via parseMuSig2SessionID, validate the combined nonce length against musig2.PubNonceSize, copy the nonce into a fixed-size array, and delegate to s.cfg.Signer methods. Macaroon permissions are added: generate for register and read for get. The change is additive (+64 lines) and does not modify existing security-critical logic.
Changed components
lnrpc/signrpc/signer_server.goMuSig2 signing RPC interfacemacaroon permission map for /signrpc.SignerInspect captured patch +64 / −0
diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go
index a98e24d..b0b161f 100644
--- a/lnrpc/signrpc/signer_server.go
+++ b/lnrpc/signrpc/signer_server.go
@@ -86,6 +86,14 @@ var (
Entity: "signer",
Action: "generate",
}},
+ "/signrpc.Signer/MuSig2RegisterCombinedNonce": {{
+ Entity: "signer",
+ Action: "generate",
+ }},
+ "/signrpc.Signer/MuSig2GetCombinedNonce": {{
+ Entity: "signer",
+ Action: "read",
+ }},
"/signrpc.Signer/MuSig2Sign": {{
Entity: "signer",
Action: "generate",
@@ -1080,6 +1088,62 @@ func (s *Server) MuSig2RegisterNonces(_ context.Context,
return &MuSig2RegisterNoncesResponse{HaveAllNonces: haveAllNonces}, nil
}
+// MuSig2RegisterCombinedNonce registers a pre-aggregated combined nonce for a
+// session identified by its ID. This is an alternative to MuSig2RegisterNonces
+// and is used when a coordinator has already aggregated all individual nonces.
+func (s *Server) MuSig2RegisterCombinedNonce(_ context.Context,
+ in *MuSig2RegisterCombinedNonceRequest) (
+ *MuSig2RegisterCombinedNonceResponse, error) {
+
+ // Check session ID length.
+ sessionID, err := parseMuSig2SessionID(in.SessionId)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing session ID: %w", err)
+ }
+
+ // Validate the combined nonce length.
+ if len(in.CombinedPublicNonce) != musig2.PubNonceSize {
+ return nil, fmt.Errorf("invalid combined nonce length, got "+
+ "%d wanted %d", len(in.CombinedPublicNonce),
+ musig2.PubNonceSize)
+ }
+
+ // Convert to the expected fixed-size array.
+ var combinedNonce [musig2.PubNonceSize]byte
+ copy(combinedNonce[:], in.CombinedPublicNonce)
+
+ // Register the combined nonce.
+ err = s.cfg.Signer.MuSig2RegisterCombinedNonce(sessionID, combinedNonce)
+ if err != nil {
+ return nil, fmt.Errorf("error registering combined nonce: %w",
+ err)
+ }
+
+ return &MuSig2RegisterCombinedNonceResponse{}, nil
+}
+
+// MuSig2GetCombinedNonce retrieves the combined nonce for a signing session.
+func (s *Server) MuSig2GetCombinedNonce(_ context.Context,
+ in *MuSig2GetCombinedNonceRequest) (
+ *MuSig2GetCombinedNonceResponse, error) {
+
+ // Check session ID length.
+ sessionID, err := parseMuSig2SessionID(in.SessionId)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing session ID: %w", err)
+ }
+
+ // Get the combined nonce from the signer.
+ combinedNonce, err := s.cfg.Signer.MuSig2GetCombinedNonce(sessionID)
+ if err != nil {
+ return nil, fmt.Errorf("error getting combined nonce: %w", err)
+ }
+
+ return &MuSig2GetCombinedNonceResponse{
+ CombinedPublicNonce: combinedNonce[:],
+ }, nil
+}
+
// MuSig2Sign creates a partial signature using the local signing key that was
// specified when the session was created. This can only be called when all
// public nonces of all participants are known and have been registered with
Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.