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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

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

76.87
/lnwallet/musig_session.go
1
package lnwallet
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "io"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/btcsuite/btcd/btcec/v2/schnorr"
10
        "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
11
        "github.com/btcsuite/btcd/chaincfg/chainhash"
12
        "github.com/btcsuite/btcd/txscript"
13
        "github.com/btcsuite/btcd/wire"
14
        "github.com/lightningnetwork/lnd/fn/v2"
15
        "github.com/lightningnetwork/lnd/input"
16
        "github.com/lightningnetwork/lnd/keychain"
17
        "github.com/lightningnetwork/lnd/lnwire"
18
        "github.com/lightningnetwork/lnd/shachain"
19
)
20

21
// MusigCommitType is an enum that denotes if this is the local or remote
22
// commitment.
23
type MusigCommitType uint8
24

25
const (
26
        // LocalMusigCommit denotes that this a session for the local
27
        // commitment.
28
        LocalMusigCommit MusigCommitType = iota
29

30
        // RemoteMusigCommit denotes that this is a session for the remote
31
        // commitment.
32
        RemoteMusigCommit
33
)
34

35
var (
36
        // ErrSessionNotFinalized is returned when the SignCommit method is
37
        // called for a local commitment, without the session being finalized
38
        // (missing nonce).
39
        ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
40
)
41

42
// tapscriptRootToSignOpt is a function that takes a tapscript root and returns
43
// a MuSig2 sign opt that'll apply the tweak when signing+verifying.
UNCOV
44
func tapscriptRootToSignOpt(root chainhash.Hash) musig2.SignOption {
×
UNCOV
45
        return musig2.WithTaprootSignTweak(root[:])
×
UNCOV
46
}
×
47

48
// TapscriptRootToTweak is a helper function that converts a tapscript root
49
// into a tweak that can be used with the MuSig2 API.
UNCOV
50
func TapscriptRootToTweak(root chainhash.Hash) input.MuSig2Tweaks {
×
UNCOV
51
        return input.MuSig2Tweaks{
×
UNCOV
52
                TaprootTweak: root[:],
×
UNCOV
53
        }
×
UNCOV
54
}
×
55

56
// MusigPartialSig is a wrapper around the base musig2.PartialSignature type
57
// that also includes information about the set of nonces used, and also the
58
// signer. This allows us to implement the input.Signature interface, as that
59
// requires the ability to perform abstract verification based on a public key.
60
type MusigPartialSig struct {
61
        // sig is the actual musig2 partial signature.
62
        sig *musig2.PartialSignature
63

64
        // signerNonce is the nonce used by the signer to generate the partial
65
        // signature.
66
        signerNonce lnwire.Musig2Nonce
67

68
        // combinedNonce is the combined nonce of all signers.
69
        combinedNonce lnwire.Musig2Nonce
70

71
        // signerKeys is the set of public keys of all signers.
72
        signerKeys []*btcec.PublicKey
73

74
        // tapscriptTweak is an optional tweak, that if specified, will be used
75
        // instead of the normal BIP 86 tweak when validating the signature.
76
        tapscriptTweak fn.Option[chainhash.Hash]
77
}
78

79
// NewMusigPartialSig creates a new MuSig2 partial signature.
80
func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce,
81
        combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey,
82
        tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig {
3✔
83

3✔
84
        return &MusigPartialSig{
3✔
85
                sig:            sig,
3✔
86
                signerNonce:    signerNonce,
3✔
87
                combinedNonce:  combinedNonce,
3✔
88
                signerKeys:     signerKeys,
3✔
89
                tapscriptTweak: tapscriptTweak,
3✔
90
        }
3✔
91
}
3✔
92

93
// FromWireSig maps a wire partial sig to this internal type that we'll use to
94
// perform signature validation.
95
func (p *MusigPartialSig) FromWireSig(
96
        sig *lnwire.PartialSigWithNonce) *MusigPartialSig {
3✔
97

3✔
98
        p.sig = &musig2.PartialSignature{
3✔
99
                S: &sig.Sig,
3✔
100
        }
3✔
101
        p.signerNonce = sig.Nonce
3✔
102

3✔
103
        return p
3✔
104
}
3✔
105

106
// ToWireSig maps the partial signature to something that we can use to write
107
// out for the wire protocol.
108
func (p *MusigPartialSig) ToWireSig() *lnwire.PartialSigWithNonce {
3✔
109
        return &lnwire.PartialSigWithNonce{
3✔
110
                PartialSig: lnwire.NewPartialSig(*p.sig.S),
3✔
111
                Nonce:      p.signerNonce,
3✔
112
        }
3✔
113
}
3✔
114

115
// Serialize serializes the musig2 partial signature. The serializing includes
116
// the signer's public nonce _and_ the partial signature. The final signature
117
// is always 98 bytes in length.
118
func (p *MusigPartialSig) Serialize() []byte {
3✔
119
        var b bytes.Buffer
3✔
120

3✔
121
        _ = p.ToWireSig().Encode(&b)
3✔
122

3✔
123
        return b.Bytes()
3✔
124
}
3✔
125

126
// ToSchnorrShell converts the musig partial signature to a regular schnorr.
127
// This schnorr signature uses a zero value for the 'r' field, so we're just
128
// only using the last 32-bytes of the signature. This is useful when we need
129
// to convert an HTLC schnorr signature into something we can send using the
130
// existing messages.
131
func (p *MusigPartialSig) ToSchnorrShell() *schnorr.Signature {
×
132
        var zeroVal btcec.FieldVal
×
133
        return schnorr.NewSignature(&zeroVal, p.sig.S)
×
134
}
×
135

136
// FromSchnorrShell takes a schnorr signature and parses out the last 32 bytes
137
// as a normal musig2 partial signature.
138
func (p *MusigPartialSig) FromSchnorrShell(sig *schnorr.Signature) {
×
139
        var (
×
140
                partialS      btcec.ModNScalar
×
141
                partialSBytes [32]byte
×
142
        )
×
143
        copy(partialSBytes[:], sig.Serialize()[32:])
×
144
        partialS.SetBytes(&partialSBytes)
×
145

×
146
        p.sig = &musig2.PartialSignature{
×
147
                S: &partialS,
×
148
        }
×
149
}
×
150

151
// Verify attempts to verify the partial musig2 signature using the passed
152
// message and signer public key.
153
//
154
// NOTE: This implements the input.Signature interface.
155
func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
3✔
156
        var m [32]byte
3✔
157
        copy(m[:], msg)
3✔
158

3✔
159
        // If we have a tapscript tweak, then we'll use that as a tweak
3✔
160
        // otherwise, we'll fall back to the normal BIP 86 sign tweak.
3✔
161
        signOpts := fn.MapOption(tapscriptRootToSignOpt)(
3✔
162
                p.tapscriptTweak,
3✔
163
        ).UnwrapOr(musig2.WithBip86SignTweak())
3✔
164

3✔
165
        return p.sig.Verify(
3✔
166
                p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
3✔
167
                musig2.WithSortedKeys(), signOpts,
3✔
168
        )
3✔
169
}
3✔
170

171
// MusigNoncePair holds the two nonces needed to sign/verify a new commitment
172
// state. The signer nonce is the nonce used by the signer (remote nonce), and
173
// the verification nonce, the nonce used by the verifier (local nonce).
174
type MusigNoncePair struct {
175
        // SigningNonce is the nonce used by the signer to sign the commitment.
176
        SigningNonce musig2.Nonces
177

178
        // VerificationNonce is the nonce used by the verifier to verify the
179
        // commitment.
180
        VerificationNonce musig2.Nonces
181
}
182

183
// String returns a string representation of the MusigNoncePair.
184
func (n *MusigNoncePair) String() string {
3✔
185
        return fmt.Sprintf("NoncePair(verification_nonce=%x, "+
3✔
186
                "signing_nonce=%x)", n.VerificationNonce.PubNonce[:],
3✔
187
                n.SigningNonce.PubNonce[:])
3✔
188
}
3✔
189

190
// TapscriptRootToTweak is a function that takes a MuSig2 taproot tweak and
191
// returns the root hash of the tapscript tree.
UNCOV
192
func muSig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash {
×
UNCOV
193
        var root chainhash.Hash
×
UNCOV
194
        copy(root[:], tweak.TaprootTweak)
×
UNCOV
195
        return root
×
UNCOV
196
}
×
197

198
// MusigSession abstracts over the details of a logical musig session. A single
199
// session is used for each commitment transactions. The sessions use a JIT
200
// nonce style, wherein part of the session can be created using only the
201
// verifier nonce. Once a new state is signed, then the signer nonce is
202
// generated. Similarly, the verifier then uses the received signer nonce to
203
// complete the session and verify the incoming signature.
204
type MusigSession struct {
205
        // session is the backing musig2 session. We'll use this to interact
206
        // with the musig2 signer.
207
        session *input.MuSig2SessionInfo
208

209
        // combinedNonce is the combined nonce of all signers.
210
        combinedNonce lnwire.Musig2Nonce
211

212
        // nonces is the set of nonces that'll be used to generate/verify the
213
        // next commitment.
214
        nonces MusigNoncePair
215

216
        // inputTxOut is the funding input.
217
        inputTxOut *wire.TxOut
218

219
        // signerKeys is the set of public keys of all signers.
220
        signerKeys []*btcec.PublicKey
221

222
        // remoteKey is the key desc of the remote key.
223
        remoteKey keychain.KeyDescriptor
224

225
        // localKey is the key desc of the local key.
226
        localKey keychain.KeyDescriptor
227

228
        // signer is the signer that'll be used to interact with the musig
229
        // session.
230
        signer input.MuSig2Signer
231

232
        // commitType tracks if this is the session for the local or remote
233
        // commitment.
234
        commitType MusigCommitType
235

236
        // tapscriptTweak is an optional tweak, that if specified, will be used
237
        // instead of the normal BIP 86 tweak when creating the MuSig2
238
        // aggregate key and session.
239
        tapscriptTweak fn.Option[input.MuSig2Tweaks]
240
}
241

242
// NewPartialMusigSession creates a new musig2 session given only the
243
// verification nonce (local nonce), and the other information that has already
244
// been bound to the session.
245
func NewPartialMusigSession(verificationNonce musig2.Nonces,
246
        localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer,
247
        inputTxOut *wire.TxOut, commitType MusigCommitType,
248
        tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
3✔
249

3✔
250
        signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
3✔
251

3✔
252
        nonces := MusigNoncePair{
3✔
253
                VerificationNonce: verificationNonce,
3✔
254
        }
3✔
255

3✔
256
        return &MusigSession{
3✔
257
                nonces:         nonces,
3✔
258
                remoteKey:      remoteKey,
3✔
259
                localKey:       localKey,
3✔
260
                inputTxOut:     inputTxOut,
3✔
261
                signerKeys:     signerKeys,
3✔
262
                signer:         signer,
3✔
263
                commitType:     commitType,
3✔
264
                tapscriptTweak: tapscriptTweak,
3✔
265
        }
3✔
266
}
3✔
267

268
// FinalizeSession finalizes the session given the signer nonce.  This is
269
// called before signing or verifying a new commitment.
270
func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
3✔
271
        var (
3✔
272
                localNonce, remoteNonce musig2.Nonces
3✔
273
                err                     error
3✔
274
        )
3✔
275

3✔
276
        // First, we'll stash the freshly generated signing nonce. Depending on
3✔
277
        // who's commitment we're handling, this'll either be our generated
3✔
278
        // nonce, or the one we just got from the remote party.
3✔
279
        m.nonces.SigningNonce = signingNonce
3✔
280

3✔
281
        switch m.commitType {
3✔
282
        // If we're making a session for the remote commitment, then the nonce
283
        // we use to sign is actually will be the signing nonce for the
284
        // session, and their nonce the verification nonce.
285
        case RemoteMusigCommit:
3✔
286
                localNonce = m.nonces.SigningNonce
3✔
287
                remoteNonce = m.nonces.VerificationNonce
3✔
288

289
        // Otherwise, we're generating/receiving a signature for our local
290
        // commitment (to broadcast), so now our verification nonce is the one
291
        // we've already generated, and we want to bind their new signing
292
        // nonce.
293
        case LocalMusigCommit:
3✔
294
                localNonce = m.nonces.VerificationNonce
3✔
295
                remoteNonce = m.nonces.SigningNonce
3✔
296
        }
297

298
        tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
3✔
299
                TaprootBIP0086Tweak: true,
3✔
300
        })
3✔
301
        m.session, err = m.signer.MuSig2CreateSession(
3✔
302
                input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
3✔
303
                &tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
3✔
304
                &localNonce,
3✔
305
        )
3✔
306
        if err != nil {
3✔
307
                return err
×
308
        }
×
309

310
        // We'll need the raw combined nonces later to be able to verify
311
        // partial signatures, and also combine partial signatures, so we'll
312
        // generate it now ourselves.
313
        aggNonce, err := musig2.AggregateNonces([][musig2.PubNonceSize]byte{
3✔
314
                m.nonces.SigningNonce.PubNonce,
3✔
315
                m.nonces.VerificationNonce.PubNonce,
3✔
316
        })
3✔
317
        if err != nil {
3✔
318
                return nil
×
319
        }
×
320

321
        m.combinedNonce = aggNonce
3✔
322

3✔
323
        return nil
3✔
324
}
325

326
// taprootKeyspendSighash generates the sighash for a taproot key spend. As
327
// this is a musig2 channel output, the keyspend is the only path we can take.
328
func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte,
329
        value int64) ([]byte, error) {
3✔
330

3✔
331
        prevOutputFetcher := txscript.NewCannedPrevOutputFetcher(
3✔
332
                pkScript, value,
3✔
333
        )
3✔
334

3✔
335
        sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
3✔
336

3✔
337
        return txscript.CalcTaprootSignatureHash(
3✔
338
                sigHashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher,
3✔
339
        )
3✔
340
}
3✔
341

342
// SignCommit signs the passed commitment w/ the current signing (relative
343
// remote) nonce. Given nonces should only ever be used once, once the method
344
// returns a new nonce is returned, w/ the existing nonce blanked out.
345
func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
3✔
346
        switch {
3✔
347
        // If we already have a session, then we don't need to finalize as this
348
        // was done up front (symmetric nonce case, like for co-op close).
349
        case m.session == nil && m.commitType == RemoteMusigCommit:
3✔
350
                // Before we can sign a new commitment, we'll need to generate
3✔
351
                // a fresh nonce that'll be sent along side our signature. With
3✔
352
                // the nonce in hand, we can finalize the session.
3✔
353
                txHash := tx.TxHash()
3✔
354
                signingNonce, err := musig2.GenNonces(
3✔
355
                        musig2.WithPublicKey(m.localKey.PubKey),
3✔
356
                        musig2.WithNonceAuxInput(txHash[:]),
3✔
357
                )
3✔
358
                if err != nil {
3✔
359
                        return nil, err
×
360
                }
×
361
                if err := m.FinalizeSession(*signingNonce); err != nil {
3✔
362
                        return nil, err
×
363
                }
×
364

365
        // Otherwise, we're trying to make a new commitment transaction without
366
        // an active session, so we'll error out.
UNCOV
367
        case m.session == nil:
×
UNCOV
368
                return nil, ErrSessionNotFinalized
×
369
        }
370

371
        // Next we can sign, we'll need to generate the sighash for their
372
        // commitment transaction.
373
        sigHash, err := taprootKeyspendSighash(
3✔
374
                tx, m.inputTxOut.PkScript, m.inputTxOut.Value,
3✔
375
        )
3✔
376
        if err != nil {
3✔
377
                return nil, err
×
378
        }
×
379

380
        // Now that we have our session created, we'll use it to generate the
381
        // initial partial signature over our sighash.
382
        var sigHashMsg [32]byte
3✔
383
        copy(sigHashMsg[:], sigHash)
3✔
384

3✔
385
        walletLog.Infof("Generating new musig2 sig for session=%x, nonces=%s",
3✔
386
                m.session.SessionID[:], m.nonces.String())
3✔
387

3✔
388
        sig, err := m.signer.MuSig2Sign(
3✔
389
                m.session.SessionID, sigHashMsg, false,
3✔
390
        )
3✔
391
        if err != nil {
3✔
392
                return nil, err
×
393
        }
×
394

395
        tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
3✔
396

3✔
397
        return NewMusigPartialSig(
3✔
398
                sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
3✔
399
                tapscriptRoot,
3✔
400
        ), nil
3✔
401
}
402

403
// Refresh is called once we receive a new verification nonce from the remote
404
// party after sending a signature. This nonce will be coupled within the
405
// revoke-and-ack message of the remote party.
406
func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
407
) (*MusigSession, error) {
3✔
408

3✔
409
        return NewPartialMusigSession(
3✔
410
                *verificationNonce, m.localKey, m.remoteKey, m.signer,
3✔
411
                m.inputTxOut, m.commitType, m.tapscriptTweak,
3✔
412
        ), nil
3✔
413
}
3✔
414

415
// VerificationNonce returns the current verification nonce for the session.
416
func (m *MusigSession) VerificationNonce() *musig2.Nonces {
×
417
        return &m.nonces.VerificationNonce
×
418
}
×
419

420
// musigSessionOpts is a set of options that can be used to modify calls to the
421
// musig session.
422
type musigSessionOpts struct {
423
        // customRand is an optional custom random source that can be used to
424
        // generate nonces via a counter scheme.
425
        customRand io.Reader
426
}
427

428
// defaultMusigSessionOpts returns the default set of options for the musig
429
// session.
430
func defaultMusigSessionOpts() *musigSessionOpts {
3✔
431
        return &musigSessionOpts{}
3✔
432
}
3✔
433

434
// MusigSessionOpt is a functional option that can be used to modify calls to
435
// the musig session.
436
type MusigSessionOpt func(*musigSessionOpts)
437

438
// WithLocalCounterNonce is used to generate local nonces based on the shachain
439
// producer and the current height. This allows us to not have to write secret
440
// nonce state to disk. Instead, we can use this to derive the nonce we need to
441
// sign and broadcast our own commitment transaction.
442
func WithLocalCounterNonce(targetHeight uint64,
443
        shaGen shachain.Producer) MusigSessionOpt {
3✔
444

3✔
445
        return func(opt *musigSessionOpts) {
6✔
446
                nextPreimage, _ := shaGen.AtIndex(targetHeight)
3✔
447

3✔
448
                opt.customRand = bytes.NewBuffer(nextPreimage[:])
3✔
449
        }
3✔
450
}
451

452
// invalidPartialSigError is used to return additional debug information to a
453
// caller that encounters an invalid partial sig.
454
type invalidPartialSigError struct {
455
        partialSig        []byte
456
        sigHash           []byte
457
        signingNonce      [musig2.PubNonceSize]byte
458
        verificationNonce [musig2.PubNonceSize]byte
459
}
460

461
// Error returns the error string for the partial sig error.
462
func (i invalidPartialSigError) Error() string {
×
463
        return fmt.Sprintf("invalid partial sig: partial_sig=%x, "+
×
464
                "sig_hash=%x, signing_nonce=%x, verification_nonce=%x",
×
465
                i.partialSig, i.sigHash, i.signingNonce[:],
×
466
                i.verificationNonce[:])
×
467
}
×
468

469
// VerifyCommitSig attempts to verify the passed partial signature against the
470
// passed commitment transaction. A keyspend sighash is assumed to generate the
471
// signed message. As we never re-use nonces, a new verification nonce (our
472
// relative local nonce) returned to transmit to the remote party, which allows
473
// them to generate another signature.
474
func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
475
        sig *lnwire.PartialSigWithNonce,
476
        musigOpts ...MusigSessionOpt) (*musig2.Nonces, error) {
3✔
477

3✔
478
        opts := defaultMusigSessionOpts()
3✔
479
        for _, optFunc := range musigOpts {
6✔
480
                optFunc(opts)
3✔
481
        }
3✔
482

483
        if sig == nil {
3✔
484
                return nil, fmt.Errorf("sig not provided")
×
485
        }
×
486

487
        // Before we can verify the signature, we'll need to finalize the
488
        // session by binding the remote party's provided signing nonce.
489
        if err := m.FinalizeSession(musig2.Nonces{
3✔
490
                PubNonce: sig.Nonce,
3✔
491
        }); err != nil {
3✔
492
                return nil, err
×
493
        }
×
494

495
        // When we verify a commitment signature, we always assume that we're
496
        // verifying a signature on our local commitment. Therefore, we'll use:
497
        // their remote nonce, and also public key.
498
        tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
3✔
499
        partialSig := NewMusigPartialSig(
3✔
500
                &musig2.PartialSignature{S: &sig.Sig},
3✔
501
                m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
3✔
502
                tapscriptRoot,
3✔
503
        )
3✔
504

3✔
505
        // With the partial sig loaded with the proper context, we'll now
3✔
506
        // generate the sighash that the remote party should have signed.
3✔
507
        sigHash, err := taprootKeyspendSighash(
3✔
508
                commitTx, m.inputTxOut.PkScript, m.inputTxOut.Value,
3✔
509
        )
3✔
510
        if err != nil {
3✔
511
                return nil, err
×
512
        }
×
513

514
        walletLog.Infof("Verifying new musig2 sig for session=%x, nonce=%s",
3✔
515
                m.session.SessionID[:], m.nonces.String())
3✔
516

3✔
517
        if partialSig == nil {
3✔
518
                return nil, fmt.Errorf("partial sig not set")
×
519
        }
×
520

521
        if !partialSig.Verify(sigHash, m.remoteKey.PubKey) {
3✔
522
                return nil, &invalidPartialSigError{
×
523
                        partialSig:        partialSig.Serialize(),
×
524
                        sigHash:           sigHash,
×
525
                        verificationNonce: m.nonces.VerificationNonce.PubNonce,
×
526
                        signingNonce:      m.nonces.SigningNonce.PubNonce,
×
527
                }
×
528
        }
×
529

530
        nonceOpts := []musig2.NonceGenOption{
3✔
531
                musig2.WithPublicKey(m.localKey.PubKey),
3✔
532
        }
3✔
533
        if opts.customRand != nil {
6✔
534
                nonceOpts = append(
3✔
535
                        nonceOpts, musig2.WithCustomRand(opts.customRand),
3✔
536
                )
3✔
537
        }
3✔
538

539
        // At this point, we know that their signature is valid, so we'll
540
        // generate another verification nonce for them, so they can generate a
541
        // new state transition.
542
        nextVerificationNonce, err := musig2.GenNonces(nonceOpts...)
3✔
543
        if err != nil {
3✔
544
                return nil, fmt.Errorf("unable to gen new nonce: %w", err)
×
545
        }
×
546

547
        return nextVerificationNonce, nil
3✔
548
}
549

550
// CombineSigs combines the passed partial signatures into a valid schnorr
551
// signature.
552
func (m *MusigSession) CombineSigs(sigs ...*musig2.PartialSignature,
553
) (*schnorr.Signature, error) {
3✔
554

3✔
555
        sig, _, err := m.signer.MuSig2CombineSig(
3✔
556
                m.session.SessionID, sigs,
3✔
557
        )
3✔
558
        if err != nil {
3✔
559
                return nil, err
×
560
        }
×
561

562
        return sig, nil
3✔
563
}
564

565
// MusigSessionCfg is used to create a new musig2 pair session. It contains the
566
// keys for both parties, as well as their initial verification nonces.
567
type MusigSessionCfg struct {
568
        // LocalKey is a key desc for the local key.
569
        LocalKey keychain.KeyDescriptor
570

571
        // RemoteKey is a key desc for the remote key.
572
        RemoteKey keychain.KeyDescriptor
573

574
        // LocalNonce is the local party's initial verification nonce.
575
        LocalNonce musig2.Nonces
576

577
        // RemoteNonce is the remote party's initial verification nonce.
578
        RemoteNonce musig2.Nonces
579

580
        // Signer is the signer that will be used to generate the session.
581
        Signer input.MuSig2Signer
582

583
        // InputTxOut is the output that we're signing for. This will be the
584
        // funding input.
585
        InputTxOut *wire.TxOut
586

587
        // TapscriptTweak is an optional tweak that can be used to modify the
588
        // MuSig2 public key used in the session.
589
        TapscriptTweak fn.Option[chainhash.Hash]
590
}
591

592
// MusigPairSession houses the two musig2 sessions needed to do funding and
593
// drive forward the state machine.  The local session is used to verify
594
// incoming commitment states. The remote session is used to propose new
595
// commitment states to the remote party.
596
type MusigPairSession struct {
597
        // LocalSession is the local party's musig2 session.
598
        LocalSession *MusigSession
599

600
        // RemoteSession is the remote party's musig2 session.
601
        RemoteSession *MusigSession
602

603
        // signer is the signer that will be used to drive the session.
604
        signer input.MuSig2Signer
605
}
606

607
// NewMusigPairSession creates a new musig2 pair session.
608
func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
3✔
609
        // Given the config passed in, we'll now create our two sessions: one
3✔
610
        // for the local commit, and one for the remote commit.
3✔
611
        //
3✔
612
        // Both sessions will be created using only the verification nonce for
3✔
613
        // the local+remote party.
3✔
614
        tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak)
3✔
615
        localSession := NewPartialMusigSession(
3✔
616
                cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
3✔
617
                cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
3✔
618
        )
3✔
619
        remoteSession := NewPartialMusigSession(
3✔
620
                cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
3✔
621
                cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
3✔
622
        )
3✔
623

3✔
624
        return &MusigPairSession{
3✔
625
                LocalSession:  localSession,
3✔
626
                RemoteSession: remoteSession,
3✔
627
                signer:        cfg.Signer,
3✔
628
        }
3✔
629
}
3✔
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