• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.02
/input/musig2_session_manager.go
1
package input
2

3
import (
4
        "crypto/sha256"
5
        "fmt"
6

7
        "github.com/btcsuite/btcd/btcec/v2"
8
        "github.com/btcsuite/btcd/btcec/v2/schnorr"
9
        "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
10
        "github.com/lightningnetwork/lnd/keychain"
11
        "github.com/lightningnetwork/lnd/lnutils"
12
        "github.com/lightningnetwork/lnd/multimutex"
13
)
14

15
// MuSig2State is a struct that holds on to the internal signing session state
16
// of a MuSig2 session.
17
type MuSig2State struct {
18
        // MuSig2SessionInfo is the associated meta information of the signing
19
        // session.
20
        MuSig2SessionInfo
21

22
        // context is the signing context responsible for keeping track of the
23
        // public keys involved in the signing process.
24
        context MuSig2Context
25

26
        // session is the signing session responsible for keeping track of the
27
        // nonces and partial signatures involved in the signing process.
28
        session MuSig2Session
29
}
30

31
// PrivKeyFetcher is used to fetch a private key that matches a given key desc.
32
type PrivKeyFetcher func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error)
33

34
// MusigSessionMusigSessionManager houses the state needed to manage concurrent
35
// musig sessions. Each session is identified by a unique session ID which is
36
// used by callers to interact with a given session.
37
type MusigSessionManager struct {
38
        keyFetcher PrivKeyFetcher
39

40
        sessionMtx *multimutex.Mutex[MuSig2SessionID]
41

42
        musig2Sessions *lnutils.SyncMap[MuSig2SessionID, *MuSig2State]
43
}
44

45
// NewMusigSessionManager creates a new musig manager given an abstract key
46
// fetcher.
47
func NewMusigSessionManager(keyFetcher PrivKeyFetcher) *MusigSessionManager {
750✔
48
        return &MusigSessionManager{
750✔
49
                keyFetcher: keyFetcher,
750✔
50
                musig2Sessions: &lnutils.SyncMap[
750✔
51
                        MuSig2SessionID, *MuSig2State,
750✔
52
                ]{},
750✔
53
                sessionMtx: multimutex.NewMutex[MuSig2SessionID](),
750✔
54
        }
750✔
55
}
750✔
56

57
// MuSig2CreateSession creates a new MuSig2 signing session using the local key
58
// identified by the key locator. The complete list of all public keys of all
59
// signing parties must be provided, including the public key of the local
60
// signing key. If nonces of other parties are already known, they can be
61
// submitted as well to reduce the number of method calls necessary later on.
62
//
63
// The set of sessionOpts are _optional_ and allow a caller to modify the
64
// generated sessions. As an example the local nonce might already be generated
65
// ahead of time.
66
func (m *MusigSessionManager) MuSig2CreateSession(bipVersion MuSig2Version,
67
        keyLoc keychain.KeyLocator, allSignerPubKeys []*btcec.PublicKey,
68
        tweaks *MuSig2Tweaks, otherSignerNonces [][musig2.PubNonceSize]byte,
69
        localNonces *musig2.Nonces) (*MuSig2SessionInfo, error) {
219✔
70

219✔
71
        // We need to derive the private key for signing. In the remote signing
219✔
72
        // setup, this whole RPC call will be forwarded to the signing
219✔
73
        // instance, which requires it to be stateful.
219✔
74
        privKey, err := m.keyFetcher(&keychain.KeyDescriptor{
219✔
75
                KeyLocator: keyLoc,
219✔
76
        })
219✔
77
        if err != nil {
219✔
78
                return nil, fmt.Errorf("error deriving private key: %w", err)
×
79
        }
×
80

81
        // Create a signing context and session with the given private key and
82
        // list of all known signer public keys.
83
        musigContext, musigSession, err := MuSig2CreateContext(
219✔
84
                bipVersion, privKey, allSignerPubKeys, tweaks, localNonces,
219✔
85
        )
219✔
86
        if err != nil {
219✔
87
                return nil, fmt.Errorf("error creating signing context: %w",
×
88
                        err)
×
89
        }
×
90

91
        // Add all nonces we might've learned so far.
92
        haveAllNonces := false
219✔
93
        for _, otherSignerNonce := range otherSignerNonces {
438✔
94
                haveAllNonces, err = musigSession.RegisterPubNonce(
219✔
95
                        otherSignerNonce,
219✔
96
                )
219✔
97
                if err != nil {
219✔
98
                        return nil, fmt.Errorf("error registering other "+
×
99
                                "signer public nonce: %v", err)
×
100
                }
×
101
        }
102

103
        // Register the new session.
104
        combinedKey, err := musigContext.CombinedKey()
219✔
105
        if err != nil {
219✔
106
                return nil, fmt.Errorf("error getting combined key: %w", err)
×
107
        }
×
108
        session := &MuSig2State{
219✔
109
                MuSig2SessionInfo: MuSig2SessionInfo{
219✔
110
                        SessionID: NewMuSig2SessionID(
219✔
111
                                combinedKey, musigSession.PublicNonce(),
219✔
112
                        ),
219✔
113
                        Version:       bipVersion,
219✔
114
                        PublicNonce:   musigSession.PublicNonce(),
219✔
115
                        CombinedKey:   combinedKey,
219✔
116
                        TaprootTweak:  tweaks.HasTaprootTweak(),
219✔
117
                        HaveAllNonces: haveAllNonces,
219✔
118
                },
219✔
119
                context: musigContext,
219✔
120
                session: musigSession,
219✔
121
        }
219✔
122

219✔
123
        // The internal key is only calculated if we are using a taproot tweak
219✔
124
        // and need to know it for a potential script spend.
219✔
125
        if tweaks.HasTaprootTweak() {
438✔
126
                internalKey, err := musigContext.TaprootInternalKey()
219✔
127
                if err != nil {
219✔
128
                        return nil, fmt.Errorf("error getting internal key: %w",
×
129
                                err)
×
130
                }
×
131
                session.TaprootInternalKey = internalKey
219✔
132
        }
133

134
        // Since we generate new nonces for every session, there is no way that
135
        // a session with the same ID already exists. So even if we call the API
136
        // twice with the same signers, we still get a new ID.
137
        //
138
        // We'll use just all zeroes as the session ID for the mutex, as this
139
        // is a "global" action.
140
        m.musig2Sessions.Store(session.SessionID, session)
219✔
141

219✔
142
        return &session.MuSig2SessionInfo, nil
219✔
143
}
144

145
// MuSig2Sign creates a partial signature using the local signing key
146
// that was specified when the session was created. This can only be
147
// called when all public nonces of all participants are known and have
148
// been registered with the session. If this node isn't responsible for
149
// combining all the partial signatures, then the cleanup parameter
150
// should be set, indicating that the session can be removed from memory
151
// once the signature was produced.
152
func (m *MusigSessionManager) MuSig2Sign(sessionID MuSig2SessionID,
153
        msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
119✔
154

119✔
155
        // We hold the lock during the whole operation, we don't want any
119✔
156
        // interference with calls that might come through in parallel for the
119✔
157
        // same session.
119✔
158
        m.sessionMtx.Lock(sessionID)
119✔
159
        defer m.sessionMtx.Unlock(sessionID)
119✔
160

119✔
161
        session, ok := m.musig2Sessions.Load(sessionID)
119✔
162
        if !ok {
119✔
UNCOV
163
                return nil, fmt.Errorf("session with ID %x not found",
×
UNCOV
164
                        sessionID[:])
×
UNCOV
165
        }
×
166

167
        // We can only sign once we have all other signer's nonces.
168
        if !session.HaveAllNonces {
119✔
169
                return nil, fmt.Errorf("only have %d of %d required nonces",
×
170
                        session.session.NumRegisteredNonces(),
×
171
                        len(session.context.SigningKeys()))
×
172
        }
×
173

174
        // Create our own partial signature with the local signing key.
175
        partialSig, err := MuSig2Sign(session.session, msg, true)
119✔
176
        if err != nil {
119✔
177
                return nil, fmt.Errorf("error signing with local key: %w", err)
×
178
        }
×
179

180
        // Clean up our local state if requested.
181
        if cleanUp {
119✔
UNCOV
182
                m.musig2Sessions.Delete(sessionID)
×
UNCOV
183
        }
×
184

185
        return partialSig, nil
119✔
186
}
187

188
// MuSig2CombineSig combines the given partial signature(s) with the
189
// local one, if it already exists. Once a partial signature of all
190
// participants is registered, the final signature will be combined and
191
// returned.
192
func (m *MusigSessionManager) MuSig2CombineSig(sessionID MuSig2SessionID,
193
        partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
194
        error) {
8✔
195

8✔
196
        // We hold the lock during the whole operation, we don't want any
8✔
197
        // interference with calls that might come through in parallel for the
8✔
198
        // same session.
8✔
199
        m.sessionMtx.Lock(sessionID)
8✔
200
        defer m.sessionMtx.Unlock(sessionID)
8✔
201

8✔
202
        session, ok := m.musig2Sessions.Load(sessionID)
8✔
203
        if !ok {
8✔
204
                return nil, false, fmt.Errorf("session with ID %x not found",
×
205
                        sessionID[:])
×
206
        }
×
207

208
        // Make sure we don't exceed the number of expected partial signatures
209
        // as that would indicate something is wrong with the signing setup.
210
        if session.HaveAllSigs {
8✔
211
                return nil, true, fmt.Errorf("already have all partial" +
×
212
                        "signatures")
×
213
        }
×
214

215
        // Add all sigs we got so far.
216
        var (
8✔
217
                finalSig *schnorr.Signature
8✔
218
                err      error
8✔
219
        )
8✔
220
        for _, otherPartialSig := range partialSigs {
16✔
221
                session.HaveAllSigs, err = MuSig2CombineSig(
8✔
222
                        session.session, otherPartialSig,
8✔
223
                )
8✔
224
                if err != nil {
8✔
225
                        return nil, false, fmt.Errorf("error combining "+
×
226
                                "partial signature: %w", err)
×
227
                }
×
228
        }
229

230
        // If we have all partial signatures, we should be able to get the
231
        // complete signature now. We also remove this session from memory since
232
        // there is nothing more left to do.
233
        if session.HaveAllSigs {
16✔
234
                finalSig = session.session.FinalSig()
8✔
235
                m.musig2Sessions.Delete(sessionID)
8✔
236
        }
8✔
237

238
        return finalSig, session.HaveAllSigs, nil
8✔
239
}
240

241
// MuSig2Cleanup removes a session from memory to free up resources.
UNCOV
242
func (m *MusigSessionManager) MuSig2Cleanup(sessionID MuSig2SessionID) error {
×
UNCOV
243
        // We hold the lock during the whole operation, we don't want any
×
UNCOV
244
        // interference with calls that might come through in parallel for the
×
UNCOV
245
        // same session.
×
UNCOV
246
        m.sessionMtx.Lock(sessionID)
×
UNCOV
247
        defer m.sessionMtx.Unlock(sessionID)
×
UNCOV
248

×
UNCOV
249
        _, ok := m.musig2Sessions.Load(sessionID)
×
UNCOV
250
        if !ok {
×
251
                return fmt.Errorf("session with ID %x not found", sessionID[:])
×
252
        }
×
253

UNCOV
254
        m.musig2Sessions.Delete(sessionID)
×
UNCOV
255

×
UNCOV
256
        return nil
×
257
}
258

259
// MuSig2RegisterNonces registers one or more public nonces of other signing
260
// participants for a session identified by its ID. This method returns true
261
// once we have all nonces for all other signing participants.
262
func (m *MusigSessionManager) MuSig2RegisterNonces(sessionID MuSig2SessionID,
UNCOV
263
        otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) {
×
UNCOV
264

×
UNCOV
265
        // We hold the lock during the whole operation, we don't want any
×
UNCOV
266
        // interference with calls that might come through in parallel for the
×
UNCOV
267
        // same session.
×
UNCOV
268
        m.sessionMtx.Lock(sessionID)
×
UNCOV
269
        defer m.sessionMtx.Unlock(sessionID)
×
UNCOV
270

×
UNCOV
271
        session, ok := m.musig2Sessions.Load(sessionID)
×
UNCOV
272
        if !ok {
×
273
                return false, fmt.Errorf("session with ID %x not found",
×
274
                        sessionID[:])
×
275
        }
×
276

277
        // Make sure we don't exceed the number of expected nonces as that would
278
        // indicate something is wrong with the signing setup.
UNCOV
279
        if session.HaveAllNonces {
×
280
                return true, fmt.Errorf("already have all nonces")
×
281
        }
×
282

UNCOV
283
        numSigners := len(session.context.SigningKeys())
×
UNCOV
284
        remainingNonces := numSigners - session.session.NumRegisteredNonces()
×
UNCOV
285
        if len(otherSignerNonces) > remainingNonces {
×
286
                return false, fmt.Errorf("only %d other nonces remaining but "+
×
287
                        "trying to register %d more", remainingNonces,
×
288
                        len(otherSignerNonces))
×
289
        }
×
290

291
        // Add all nonces we've learned so far.
UNCOV
292
        var err error
×
UNCOV
293
        for _, otherSignerNonce := range otherSignerNonces {
×
UNCOV
294
                session.HaveAllNonces, err = session.session.RegisterPubNonce(
×
UNCOV
295
                        otherSignerNonce,
×
UNCOV
296
                )
×
UNCOV
297
                if err != nil {
×
298
                        return false, fmt.Errorf("error registering other "+
×
299
                                "signer public nonce: %v", err)
×
300
                }
×
301
        }
302

UNCOV
303
        return session.HaveAllNonces, nil
×
304
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc