• 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

67.72
/input/musig2.go
1
package input
2

3
import (
4
        "bytes"
5
        "crypto/sha256"
6
        "fmt"
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/lightningnetwork/lnd/internal/musig2v040"
12
        "github.com/lightningnetwork/lnd/keychain"
13
)
14

15
// MuSig2Version is a type that defines the different versions of the MuSig2
16
// as defined in the BIP draft:
17
// (https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki)
18
type MuSig2Version uint8
19

20
const (
21
        // MuSig2Version040 is version 0.4.0 of the MuSig2 BIP draft. This will
22
        // use the lnd internal/musig2v040 package.
23
        MuSig2Version040 MuSig2Version = 0
24

25
        // MuSig2Version100RC2 is version 1.0.0rc2 of the MuSig2 BIP draft. This
26
        // uses the github.com/btcsuite/btcd/btcec/v2/schnorr/musig2 package
27
        // at git tag `btcec/v2.3.1`.
28
        MuSig2Version100RC2 MuSig2Version = 1
29
)
30

31
const (
32
        // MuSig2PartialSigSize is the size of a MuSig2 partial signature.
33
        // Because a partial signature is just the s value, this corresponds to
34
        // the length of a scalar.
35
        MuSig2PartialSigSize = 32
36
)
37

38
// MuSig2SessionID is a type for a session ID that is just a hash of the MuSig2
39
// combined key and the local public nonces.
40
type MuSig2SessionID [sha256.Size]byte
41

42
// MuSig2Signer is an interface that declares all methods that a MuSig2
43
// compatible signer needs to implement.
44
type MuSig2Signer interface {
45
        // MuSig2CreateSession creates a new MuSig2 signing session using the
46
        // local key identified by the key locator. The complete list of all
47
        // public keys of all signing parties must be provided, including the
48
        // public key of the local signing key. If nonces of other parties are
49
        // already known, they can be submitted as well to reduce the number of
50
        // method calls necessary later on.
51
        //
52
        // The localNonces field is optional. If it is set, then the specified
53
        // nonces will be used instead of generating from scratch.  This is
54
        // useful in instances where the nonces are generated ahead of time
55
        // before the set of signers is known.
56
        MuSig2CreateSession(MuSig2Version, keychain.KeyLocator,
57
                []*btcec.PublicKey, *MuSig2Tweaks, [][musig2.PubNonceSize]byte,
58
                *musig2.Nonces) (*MuSig2SessionInfo, error)
59

60
        // MuSig2RegisterNonces registers one or more public nonces of other
61
        // signing participants for a session identified by its ID. This method
62
        // returns true once we have all nonces for all other signing
63
        // participants.
64
        MuSig2RegisterNonces(MuSig2SessionID,
65
                [][musig2.PubNonceSize]byte) (bool, error)
66

67
        // MuSig2Sign creates a partial signature using the local signing key
68
        // that was specified when the session was created. This can only be
69
        // called when all public nonces of all participants are known and have
70
        // been registered with the session. If this node isn't responsible for
71
        // combining all the partial signatures, then the cleanup parameter
72
        // should be set, indicating that the session can be removed from memory
73
        // once the signature was produced.
74
        MuSig2Sign(MuSig2SessionID, [sha256.Size]byte,
75
                bool) (*musig2.PartialSignature, error)
76

77
        // MuSig2CombineSig combines the given partial signature(s) with the
78
        // local one, if it already exists. Once a partial signature of all
79
        // participants is registered, the final signature will be combined and
80
        // returned.
81
        MuSig2CombineSig(MuSig2SessionID,
82
                []*musig2.PartialSignature) (*schnorr.Signature, bool, error)
83

84
        // MuSig2Cleanup removes a session from memory to free up resources.
85
        MuSig2Cleanup(MuSig2SessionID) error
86
}
87

88
// MuSig2Context is an interface that is an abstraction over the MuSig2 signing
89
// context. This interface does not contain all of the methods the underlying
90
// implementations have because those use package specific types which cannot
91
// easily be made compatible. Those calls (such as NewSession) are implemented
92
// in this package instead and do the necessary type switch (see
93
// MuSig2CreateContext).
94
type MuSig2Context interface {
95
        // SigningKeys returns the set of keys used for signing.
96
        SigningKeys() []*btcec.PublicKey
97

98
        // CombinedKey returns the combined public key that will be used to
99
        // generate multi-signatures  against.
100
        CombinedKey() (*btcec.PublicKey, error)
101

102
        // TaprootInternalKey returns the internal taproot key, which is the
103
        // aggregated key _before_ the tweak is applied. If a taproot tweak was
104
        // specified, then CombinedKey() will return the fully tweaked output
105
        // key, with this method returning the internal key. If a taproot tweak
106
        // wasn't specified, then this method will return an error.
107
        TaprootInternalKey() (*btcec.PublicKey, error)
108
}
109

110
// MuSig2Session is an interface that is an abstraction over the MuSig2 signing
111
// session. This interface does not contain all of the methods the underlying
112
// implementations have because those use package specific types which cannot
113
// easily be made compatible. Those calls (such as CombineSig or Sign) are
114
// implemented in this package instead and do the necessary type switch (see
115
// MuSig2CombineSig or MuSig2Sign).
116
type MuSig2Session interface {
117
        // FinalSig returns the final combined multi-signature, if present.
118
        FinalSig() *schnorr.Signature
119

120
        // PublicNonce returns the public nonce for a signer. This should be
121
        // sent to other parties before signing begins, so they can compute the
122
        // aggregated public nonce.
123
        PublicNonce() [musig2.PubNonceSize]byte
124

125
        // NumRegisteredNonces returns the total number of nonces that have been
126
        // registered so far.
127
        NumRegisteredNonces() int
128

129
        // RegisterPubNonce should be called for each public nonce from the set
130
        // of signers. This method returns true once all the public nonces have
131
        // been accounted for.
132
        RegisterPubNonce(nonce [musig2.PubNonceSize]byte) (bool, error)
133
}
134

135
// MuSig2SessionInfo is a struct for keeping track of a signing session
136
// information in memory.
137
type MuSig2SessionInfo struct {
138
        // SessionID is the wallet's internal unique ID of this session. The ID
139
        // is the hash over the combined public key and the local public nonces.
140
        SessionID [32]byte
141

142
        // Version is the version of the MuSig2 BIP this signing session is
143
        // using.
144
        Version MuSig2Version
145

146
        // PublicNonce contains the public nonce of the local signer session.
147
        PublicNonce [musig2.PubNonceSize]byte
148

149
        // CombinedKey is the combined public key with all tweaks applied to it.
150
        CombinedKey *btcec.PublicKey
151

152
        // TaprootTweak indicates whether a taproot tweak (BIP-0086 or script
153
        // path) was used. The TaprootInternalKey will only be set if this is
154
        // set to true.
155
        TaprootTweak bool
156

157
        // TaprootInternalKey is the raw combined public key without any tweaks
158
        // applied to it. This is only set if TaprootTweak is true.
159
        TaprootInternalKey *btcec.PublicKey
160

161
        // HaveAllNonces indicates whether this session already has all nonces
162
        // of all other signing participants registered.
163
        HaveAllNonces bool
164

165
        // HaveAllSigs indicates whether this session already has all partial
166
        // signatures of all other signing participants registered.
167
        HaveAllSigs bool
168
}
169

170
// MuSig2Tweaks is a struct that contains all tweaks that can be applied to a
171
// MuSig2 combined public key.
172
type MuSig2Tweaks struct {
173
        // GenericTweaks is a list of normal tweaks to apply to the combined
174
        // public key (and to the private key when signing).
175
        GenericTweaks []musig2.KeyTweakDesc
176

177
        // TaprootBIP0086Tweak indicates that the final key should use the
178
        // taproot tweak as defined in BIP 341, with the BIP 86 modification:
179
        //     outputKey = internalKey + h_tapTweak(internalKey)*G.
180
        // In this case, the aggregated key before the tweak will be used as the
181
        // internal key. If this is set to true then TaprootTweak will be
182
        // ignored.
183
        TaprootBIP0086Tweak bool
184

185
        // TaprootTweak specifies that the final key should use the taproot
186
        // tweak as defined in BIP 341:
187
        //     outputKey = internalKey + h_tapTweak(internalKey || scriptRoot).
188
        // In this case, the aggregated key before the tweak will be used as the
189
        // internal key. Will be ignored if TaprootBIP0086Tweak is set to true.
190
        TaprootTweak []byte
191
}
192

193
// HasTaprootTweak returns true if either a taproot BIP0086 tweak or a taproot
194
// script root tweak is set.
195
func (t *MuSig2Tweaks) HasTaprootTweak() bool {
3✔
196
        return t.TaprootBIP0086Tweak || len(t.TaprootTweak) > 0
3✔
197
}
3✔
198

199
// ToContextOptions converts the tweak descriptor to context options.
200
func (t *MuSig2Tweaks) ToContextOptions() []musig2.ContextOption {
3✔
201
        var tweakOpts []musig2.ContextOption
3✔
202
        if len(t.GenericTweaks) > 0 {
3✔
203
                tweakOpts = append(tweakOpts, musig2.WithTweakedContext(
×
204
                        t.GenericTweaks...,
×
205
                ))
×
206
        }
×
207

208
        // The BIP0086 tweak and the taproot script tweak are mutually
209
        // exclusive.
210
        if t.TaprootBIP0086Tweak {
6✔
211
                tweakOpts = append(tweakOpts, musig2.WithBip86TweakCtx())
3✔
212
        } else if len(t.TaprootTweak) > 0 {
9✔
213
                tweakOpts = append(tweakOpts, musig2.WithTaprootTweakCtx(
3✔
214
                        t.TaprootTweak,
3✔
215
                ))
3✔
216
        }
3✔
217

218
        return tweakOpts
3✔
219
}
220

221
// ToV040ContextOptions converts the tweak descriptor to v0.4.0 context options.
222
func (t *MuSig2Tweaks) ToV040ContextOptions() []musig2v040.ContextOption {
3✔
223
        var tweakOpts []musig2v040.ContextOption
3✔
224
        if len(t.GenericTweaks) > 0 {
3✔
225
                genericTweaksCopy := make(
×
226
                        []musig2v040.KeyTweakDesc, len(t.GenericTweaks),
×
227
                )
×
228
                for idx := range t.GenericTweaks {
×
229
                        genericTweaksCopy[idx] = musig2v040.KeyTweakDesc{
×
230
                                Tweak:   t.GenericTweaks[idx].Tweak,
×
231
                                IsXOnly: t.GenericTweaks[idx].IsXOnly,
×
232
                        }
×
233
                }
×
234
                tweakOpts = append(tweakOpts, musig2v040.WithTweakedContext(
×
235
                        genericTweaksCopy...,
×
236
                ))
×
237
        }
238

239
        // The BIP0086 tweak and the taproot script tweak are mutually
240
        // exclusive.
241
        if t.TaprootBIP0086Tweak {
6✔
242
                tweakOpts = append(tweakOpts, musig2v040.WithBip86TweakCtx())
3✔
243
        } else if len(t.TaprootTweak) > 0 {
9✔
244
                tweakOpts = append(tweakOpts, musig2v040.WithTaprootTweakCtx(
3✔
245
                        t.TaprootTweak,
3✔
246
                ))
3✔
247
        }
3✔
248

249
        return tweakOpts
3✔
250
}
251

252
// MuSig2ParsePubKeys parses a list of raw public keys as the signing keys of a
253
// MuSig2 signing session.
254
func MuSig2ParsePubKeys(bipVersion MuSig2Version,
255
        rawPubKeys [][]byte) ([]*btcec.PublicKey, error) {
3✔
256

3✔
257
        allSignerPubKeys := make([]*btcec.PublicKey, len(rawPubKeys))
3✔
258
        if len(rawPubKeys) < 2 {
3✔
259
                return nil, fmt.Errorf("need at least two signing public keys")
×
260
        }
×
261

262
        for idx, pubKeyBytes := range rawPubKeys {
6✔
263
                switch bipVersion {
3✔
264
                case MuSig2Version040:
3✔
265
                        pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
3✔
266
                        if err != nil {
3✔
267
                                return nil, fmt.Errorf("error parsing signer "+
×
268
                                        "public key %d for v0.4.0 (x-only "+
×
269
                                        "format): %v", idx, err)
×
270
                        }
×
271
                        allSignerPubKeys[idx] = pubKey
3✔
272

273
                case MuSig2Version100RC2:
3✔
274
                        pubKey, err := btcec.ParsePubKey(pubKeyBytes)
3✔
275
                        if err != nil {
3✔
276
                                return nil, fmt.Errorf("error parsing signer "+
×
277
                                        "public key %d for v1.0.0rc2 ("+
×
278
                                        "compressed format): %v", idx, err)
×
279
                        }
×
280
                        allSignerPubKeys[idx] = pubKey
3✔
281

282
                default:
×
283
                        return nil, fmt.Errorf("unknown MuSig2 version: <%d>",
×
284
                                bipVersion)
×
285
                }
286
        }
287

288
        return allSignerPubKeys, nil
3✔
289
}
290

291
// MuSig2CombineKeys combines the given set of public keys into a single
292
// combined MuSig2 combined public key, applying the given tweaks.
293
func MuSig2CombineKeys(bipVersion MuSig2Version,
294
        allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
295
        tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
3✔
296

3✔
297
        switch bipVersion {
3✔
298
        case MuSig2Version040:
3✔
299
                return combineKeysV040(allSignerPubKeys, sortKeys, tweaks)
3✔
300

301
        case MuSig2Version100RC2:
3✔
302
                return combineKeysV100RC2(allSignerPubKeys, sortKeys, tweaks)
3✔
303

UNCOV
304
        default:
×
UNCOV
305
                return nil, fmt.Errorf("unknown MuSig2 version: <%d>",
×
UNCOV
306
                        bipVersion)
×
307
        }
308
}
309

310
// combineKeysV100rc1 implements the MuSigCombineKeys logic for the MuSig2 BIP
311
// draft version 1.0.0rc2.
312
func combineKeysV100RC2(allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
313
        tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
3✔
314

3✔
315
        // Convert the tweak options into the appropriate MuSig2 API functional
3✔
316
        // options.
3✔
317
        var keyAggOpts []musig2.KeyAggOption
3✔
318
        switch {
3✔
319
        case tweaks.TaprootBIP0086Tweak:
3✔
320
                keyAggOpts = append(keyAggOpts, musig2.WithBIP86KeyTweak())
3✔
321
        case len(tweaks.TaprootTweak) > 0:
3✔
322
                keyAggOpts = append(keyAggOpts, musig2.WithTaprootKeyTweak(
3✔
323
                        tweaks.TaprootTweak,
3✔
324
                ))
3✔
325
        case len(tweaks.GenericTweaks) > 0:
×
326
                keyAggOpts = append(keyAggOpts, musig2.WithKeyTweaks(
×
327
                        tweaks.GenericTweaks...,
×
328
                ))
×
329
        }
330

331
        // Then we'll use this information to compute the aggregated public key.
332
        combinedKey, _, _, err := musig2.AggregateKeys(
3✔
333
                allSignerPubKeys, sortKeys, keyAggOpts...,
3✔
334
        )
3✔
335
        return combinedKey, err
3✔
336
}
337

338
// combineKeysV040 implements the MuSigCombineKeys logic for the MuSig2 BIP
339
// draft version 0.4.0.
340
func combineKeysV040(allSignerPubKeys []*btcec.PublicKey, sortKeys bool,
341
        tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
3✔
342

3✔
343
        // Convert the tweak options into the appropriate MuSig2 API functional
3✔
344
        // options.
3✔
345
        var keyAggOpts []musig2v040.KeyAggOption
3✔
346
        switch {
3✔
347
        case tweaks.TaprootBIP0086Tweak:
3✔
348
                keyAggOpts = append(keyAggOpts, musig2v040.WithBIP86KeyTweak())
3✔
349
        case len(tweaks.TaprootTweak) > 0:
3✔
350
                keyAggOpts = append(keyAggOpts, musig2v040.WithTaprootKeyTweak(
3✔
351
                        tweaks.TaprootTweak,
3✔
352
                ))
3✔
353
        case len(tweaks.GenericTweaks) > 0:
×
354
                genericTweaksCopy := make(
×
355
                        []musig2v040.KeyTweakDesc, len(tweaks.GenericTweaks),
×
356
                )
×
357
                for idx := range tweaks.GenericTweaks {
×
358
                        genericTweaksCopy[idx] = musig2v040.KeyTweakDesc{
×
359
                                Tweak:   tweaks.GenericTweaks[idx].Tweak,
×
360
                                IsXOnly: tweaks.GenericTweaks[idx].IsXOnly,
×
361
                        }
×
362
                }
×
363
                keyAggOpts = append(keyAggOpts, musig2v040.WithKeyTweaks(
×
364
                        genericTweaksCopy...,
×
365
                ))
×
366
        }
367

368
        // Then we'll use this information to compute the aggregated public key.
369
        combinedKey, _, _, err := musig2v040.AggregateKeys(
3✔
370
                allSignerPubKeys, sortKeys, keyAggOpts...,
3✔
371
        )
3✔
372

3✔
373
        // Copy the result back into the default version's native type.
3✔
374
        return &musig2.AggregateKey{
3✔
375
                FinalKey:      combinedKey.FinalKey,
3✔
376
                PreTweakedKey: combinedKey.PreTweakedKey,
3✔
377
        }, err
3✔
378
}
379

380
// MuSig2CreateContext creates a new MuSig2 signing context.
381
func MuSig2CreateContext(bipVersion MuSig2Version, privKey *btcec.PrivateKey,
382
        allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
383
        localNonces *musig2.Nonces,
384
) (MuSig2Context, MuSig2Session, error) {
3✔
385

3✔
386
        switch bipVersion {
3✔
387
        case MuSig2Version040:
3✔
388
                return createContextV040(
3✔
389
                        privKey, allSignerPubKeys, tweaks, localNonces,
3✔
390
                )
3✔
391

392
        case MuSig2Version100RC2:
3✔
393
                return createContextV100RC2(
3✔
394
                        privKey, allSignerPubKeys, tweaks, localNonces,
3✔
395
                )
3✔
396

397
        default:
×
398
                return nil, nil, fmt.Errorf("unknown MuSig2 version: <%d>",
×
399
                        bipVersion)
×
400
        }
401
}
402

403
// createContextV100RC2 implements the MuSig2CreateContext logic for the MuSig2
404
// BIP draft version 1.0.0rc2.
405
func createContextV100RC2(privKey *btcec.PrivateKey,
406
        allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
407
        localNonces *musig2.Nonces,
408
) (*musig2.Context, *musig2.Session, error) {
3✔
409

3✔
410
        // The context keeps track of all signing keys and our local key.
3✔
411
        allOpts := append(
3✔
412
                []musig2.ContextOption{
3✔
413
                        musig2.WithKnownSigners(allSignerPubKeys),
3✔
414
                },
3✔
415
                tweaks.ToContextOptions()...,
3✔
416
        )
3✔
417
        muSigContext, err := musig2.NewContext(privKey, true, allOpts...)
3✔
418
        if err != nil {
3✔
419
                return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
×
420
                        "context: %v", err)
×
421
        }
×
422

423
        var sessionOpts []musig2.SessionOption
3✔
424
        if localNonces != nil {
6✔
425
                sessionOpts = append(
3✔
426
                        sessionOpts, musig2.WithPreGeneratedNonce(localNonces),
3✔
427
                )
3✔
428
        }
3✔
429

430
        muSigSession, err := muSigContext.NewSession(sessionOpts...)
3✔
431
        if err != nil {
3✔
432
                return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
×
433
                        "session: %v", err)
×
434
        }
×
435

436
        return muSigContext, muSigSession, nil
3✔
437
}
438

439
// createContextV040 implements the MuSig2CreateContext logic for the MuSig2 BIP
440
// draft version 0.4.0.
441
func createContextV040(privKey *btcec.PrivateKey,
442
        allSignerPubKeys []*btcec.PublicKey, tweaks *MuSig2Tweaks,
443
        _ *musig2.Nonces,
444
) (*musig2v040.Context, *musig2v040.Session, error) {
3✔
445

3✔
446
        // The context keeps track of all signing keys and our local key.
3✔
447
        allOpts := append(
3✔
448
                []musig2v040.ContextOption{
3✔
449
                        musig2v040.WithKnownSigners(allSignerPubKeys),
3✔
450
                },
3✔
451
                tweaks.ToV040ContextOptions()...,
3✔
452
        )
3✔
453
        muSigContext, err := musig2v040.NewContext(privKey, true, allOpts...)
3✔
454
        if err != nil {
3✔
455
                return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
×
456
                        "context: %v", err)
×
457
        }
×
458

459
        muSigSession, err := muSigContext.NewSession()
3✔
460
        if err != nil {
3✔
461
                return nil, nil, fmt.Errorf("error creating MuSig2 signing "+
×
462
                        "session: %v", err)
×
463
        }
×
464

465
        return muSigContext, muSigSession, nil
3✔
466
}
467

468
// MuSig2Sign calls the Sign() method on the given versioned signing session and
469
// returns the result in the most recent version of the MuSig2 API.
470
func MuSig2Sign(session MuSig2Session, msg [32]byte,
471
        withSortedKeys bool) (*musig2.PartialSignature, error) {
3✔
472

3✔
473
        switch s := session.(type) {
3✔
474
        case *musig2.Session:
3✔
475
                var opts []musig2.SignOption
3✔
476
                if withSortedKeys {
6✔
477
                        opts = append(opts, musig2.WithSortedKeys())
3✔
478
                }
3✔
479
                partialSig, err := s.Sign(msg, opts...)
3✔
480
                if err != nil {
3✔
481
                        return nil, fmt.Errorf("error signing with local key: "+
×
482
                                "%v", err)
×
483
                }
×
484

485
                return partialSig, nil
3✔
486

487
        case *musig2v040.Session:
3✔
488
                var opts []musig2v040.SignOption
3✔
489
                if withSortedKeys {
6✔
490
                        opts = append(opts, musig2v040.WithSortedKeys())
3✔
491
                }
3✔
492
                partialSig, err := s.Sign(msg, opts...)
3✔
493
                if err != nil {
3✔
494
                        return nil, fmt.Errorf("error signing with local key: "+
×
495
                                "%v", err)
×
496
                }
×
497

498
                return &musig2.PartialSignature{
3✔
499
                        S: partialSig.S,
3✔
500
                        R: partialSig.R,
3✔
501
                }, nil
3✔
502

503
        default:
×
504
                return nil, fmt.Errorf("invalid session type <%T>", s)
×
505
        }
506
}
507

508
// MuSig2CombineSig calls the CombineSig() method on the given versioned signing
509
// session and returns the result in the most recent version of the MuSig2 API.
510
func MuSig2CombineSig(session MuSig2Session,
511
        otherPartialSig *musig2.PartialSignature) (bool, error) {
3✔
512

3✔
513
        switch s := session.(type) {
3✔
514
        case *musig2.Session:
3✔
515
                haveAllSigs, err := s.CombineSig(otherPartialSig)
3✔
516
                if err != nil {
3✔
517
                        return false, fmt.Errorf("error combining partial "+
×
518
                                "signature: %v", err)
×
519
                }
×
520

521
                return haveAllSigs, nil
3✔
522

523
        case *musig2v040.Session:
3✔
524
                haveAllSigs, err := s.CombineSig(&musig2v040.PartialSignature{
3✔
525
                        S: otherPartialSig.S,
3✔
526
                        R: otherPartialSig.R,
3✔
527
                })
3✔
528
                if err != nil {
3✔
529
                        return false, fmt.Errorf("error combining partial "+
×
530
                                "signature: %v", err)
×
531
                }
×
532

533
                return haveAllSigs, nil
3✔
534

535
        default:
×
536
                return false, fmt.Errorf("invalid session type <%T>", s)
×
537
        }
538
}
539

540
// NewMuSig2SessionID returns the unique ID of a MuSig2 session by using the
541
// combined key and the local public nonces and hashing that data.
542
func NewMuSig2SessionID(combinedKey *btcec.PublicKey,
543
        publicNonces [musig2.PubNonceSize]byte) MuSig2SessionID {
3✔
544

3✔
545
        // We hash the data to save some bytes in memory.
3✔
546
        hash := sha256.New()
3✔
547
        _, _ = hash.Write(combinedKey.SerializeCompressed())
3✔
548
        _, _ = hash.Write(publicNonces[:])
3✔
549

3✔
550
        id := MuSig2SessionID{}
3✔
551
        copy(id[:], hash.Sum(nil))
3✔
552
        return id
3✔
553
}
3✔
554

555
// SerializePartialSignature encodes the partial signature to a fixed size byte
556
// array.
557
func SerializePartialSignature(
558
        sig *musig2.PartialSignature) ([MuSig2PartialSigSize]byte, error) {
3✔
559

3✔
560
        var (
3✔
561
                buf    bytes.Buffer
3✔
562
                result [MuSig2PartialSigSize]byte
3✔
563
        )
3✔
564
        if err := sig.Encode(&buf); err != nil {
3✔
565
                return result, fmt.Errorf("error encoding partial signature: "+
×
566
                        "%v", err)
×
567
        }
×
568

569
        if buf.Len() != MuSig2PartialSigSize {
3✔
570
                return result, fmt.Errorf("invalid partial signature length, "+
×
571
                        "got %d wanted %d", buf.Len(), MuSig2PartialSigSize)
×
572
        }
×
573

574
        copy(result[:], buf.Bytes())
3✔
575

3✔
576
        return result, nil
3✔
577
}
578

579
// DeserializePartialSignature decodes a partial signature from a byte slice.
580
func DeserializePartialSignature(scalarBytes []byte) (*musig2.PartialSignature,
581
        error) {
3✔
582

3✔
583
        if len(scalarBytes) != MuSig2PartialSigSize {
3✔
584
                return nil, fmt.Errorf("invalid partial signature length, got "+
×
585
                        "%d wanted %d", len(scalarBytes), MuSig2PartialSigSize)
×
586
        }
×
587

588
        sig := &musig2.PartialSignature{}
3✔
589
        if err := sig.Decode(bytes.NewReader(scalarBytes)); err != nil {
3✔
590
                return nil, fmt.Errorf("error decoding partial signature: %w",
×
591
                        err)
×
592
        }
×
593

594
        return sig, nil
3✔
595
}
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