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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 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 {
752✔
48
        return &MusigSessionManager{
752✔
49
                keyFetcher: keyFetcher,
752✔
50
                musig2Sessions: &lnutils.SyncMap[
752✔
51
                        MuSig2SessionID, *MuSig2State,
752✔
52
                ]{},
752✔
53
                sessionMtx: multimutex.NewMutex[MuSig2SessionID](),
752✔
54
        }
752✔
55
}
752✔
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