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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

86.85
/routing/blinding.go
1
package routing
2

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

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/decred/dcrd/dcrec/secp256k1/v4"
10
        sphinx "github.com/lightningnetwork/lightning-onion"
11
        "github.com/lightningnetwork/lnd/graph/db/models"
12
        "github.com/lightningnetwork/lnd/input"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
        "github.com/lightningnetwork/lnd/routing/route"
15
)
16

17
// BlindedPathNUMSHex is the hex encoded version of the blinded path target
18
// NUMs key (in compressed format) which has no known private key.
19
// This was generated using the following script:
20
// https://github.com/lightninglabs/lightning-node-connect/tree/master/
21
// mailbox/numsgen, with the seed phrase "Lightning Blinded Path".
22
const BlindedPathNUMSHex = "02667a98ef82ecb522f803b17a74f14508a48b25258f9831" +
23
        "dd6e95f5e299dfd54e"
24

25
var (
26
        // ErrNoBlindedPath is returned when the blinded path in a blinded
27
        // payment is missing.
28
        ErrNoBlindedPath = errors.New("blinded path required")
29

30
        // ErrInsufficientBlindedHops is returned when a blinded path does
31
        // not have enough blinded hops.
32
        ErrInsufficientBlindedHops = errors.New("blinded path requires " +
33
                "at least one hop")
34

35
        // ErrHTLCRestrictions is returned when a blinded path has invalid
36
        // HTLC maximum and minimum values.
37
        ErrHTLCRestrictions = errors.New("invalid htlc minimum and maximum")
38

39
        // BlindedPathNUMSKey is a NUMS key (nothing up my sleeves number) that
40
        // has no known private key.
41
        BlindedPathNUMSKey = input.MustParsePubKey(BlindedPathNUMSHex)
42

43
        // CompressedBlindedPathNUMSKey is the compressed version of the
44
        // BlindedPathNUMSKey.
45
        CompressedBlindedPathNUMSKey = BlindedPathNUMSKey.SerializeCompressed()
46
)
47

48
// BlindedPaymentPathSet groups the data we need to handle sending to a set of
49
// blinded paths provided by the recipient of a payment.
50
//
51
// NOTE: for now this only holds a single BlindedPayment. By the end of the PR
52
// series, it will handle multiple paths.
53
type BlindedPaymentPathSet struct {
54
        // paths is the set of blinded payment paths for a single payment.
55
        // NOTE: For now this will always only have a single entry. By the end
56
        // of this PR, it can hold multiple.
57
        paths []*BlindedPayment
58

59
        // targetPubKey is the ephemeral node pub key that we will inject into
60
        // each path as the last hop. This is only for the sake of path finding.
61
        // Once the path has been found, the original destination pub key is
62
        // used again. In the edge case where there is only a single hop in the
63
        // path (the introduction node is the destination node), then this will
64
        // just be the introduction node's real public key.
65
        targetPubKey *btcec.PublicKey
66

67
        // features is the set of relay features available for the payment.
68
        // This is extracted from the set of blinded payment paths. At the
69
        // moment we require that all paths for the same payment have the
70
        // same feature set.
71
        features *lnwire.FeatureVector
72

73
        // finalCLTV is the final hop's expiry delta of _any_ path in the set.
74
        // For any multi-hop path, the final CLTV delta should be seen as zero
75
        // since the final hop's final CLTV delta is accounted for in the
76
        // accumulated path policy values. The only edge case is for when the
77
        // final hop in the path is also the introduction node in which case
78
        // that path's FinalCLTV must be the non-zero min CLTV of the final hop
79
        // so that it is accounted for in path finding. For this reason, if
80
        // we have any single path in the set with only one hop, then we throw
81
        // away all the other paths. This should be fine to do since if there is
82
        // a path where the intro node is also the destination node, then there
83
        // isn't any need to try any other longer blinded path. In other words,
84
        // if this value is non-zero, then there is only one path in this
85
        // blinded path set and that path only has a single hop: the
86
        // introduction node.
87
        finalCLTV uint16
88
}
89

90
// NewBlindedPaymentPathSet constructs a new BlindedPaymentPathSet from a set of
91
// BlindedPayments. For blinded paths which have more than one single hop a
92
// dummy hop via a NUMS key is appeneded to allow for MPP path finding via
93
// multiple blinded paths.
94
func NewBlindedPaymentPathSet(paths []*BlindedPayment) (*BlindedPaymentPathSet,
95
        error) {
9✔
96

9✔
97
        if len(paths) == 0 {
9✔
98
                return nil, ErrNoBlindedPath
×
99
        }
×
100

101
        // For now, we assert that all the paths have the same set of features.
102
        features := paths[0].Features
9✔
103
        noFeatures := features == nil || features.IsEmpty()
9✔
104
        for i := 1; i < len(paths); i++ {
10✔
105
                noFeats := paths[i].Features == nil ||
1✔
106
                        paths[i].Features.IsEmpty()
1✔
107

1✔
108
                if noFeatures && !noFeats {
1✔
109
                        return nil, fmt.Errorf("all blinded paths must have " +
×
110
                                "the same set of features")
×
111
                }
×
112

113
                if noFeatures {
2✔
114
                        continue
1✔
115
                }
116

117
                if !features.RawFeatureVector.Equals(
×
118
                        paths[i].Features.RawFeatureVector,
×
119
                ) {
×
120

×
121
                        return nil, fmt.Errorf("all blinded paths must have " +
×
122
                                "the same set of features")
×
123
                }
×
124
        }
125

126
        // Deep copy the paths to avoid mutating the original paths.
127
        pathSet := make([]*BlindedPayment, len(paths))
9✔
128
        for i, path := range paths {
19✔
129
                pathSet[i] = path.deepCopy()
10✔
130
        }
10✔
131

132
        // For blinded paths we use the NUMS key as a target if the blinded
133
        // path has more hops than just the introduction node.
134
        targetPub := &BlindedPathNUMSKey
9✔
135

9✔
136
        var finalCLTVDelta uint16
9✔
137

9✔
138
        // In case the paths do NOT include a single hop route we append a
9✔
139
        // dummy hop via a NUMS key to allow for MPP path finding via multiple
9✔
140
        // blinded paths. A unified target is needed to use all blinded paths
9✔
141
        // during the payment lifecycle. A dummy hop is solely added for the
9✔
142
        // path finding process and is removed after the path is found. This
9✔
143
        // ensures that we still populate the mission control with the correct
9✔
144
        // data and also respect these mc entries when looking for a path.
9✔
145
        for _, path := range pathSet {
19✔
146
                pathLength := len(path.BlindedPath.BlindedHops)
10✔
147

10✔
148
                // If any provided blinded path only has a single hop (ie, the
10✔
149
                // destination node is also the introduction node), then we
10✔
150
                // discard all other paths since we know the real pub key of the
10✔
151
                // destination node. We also then set the final CLTV delta to
10✔
152
                // the path's delta since there are no other edge hints that
10✔
153
                // will account for it.
10✔
154
                if pathLength == 1 {
14✔
155
                        pathSet = []*BlindedPayment{path}
4✔
156
                        finalCLTVDelta = path.CltvExpiryDelta
4✔
157
                        targetPub = path.BlindedPath.IntroductionPoint
4✔
158

4✔
159
                        break
4✔
160
                }
161

162
                lastHop := path.BlindedPath.BlindedHops[pathLength-1]
6✔
163
                path.BlindedPath.BlindedHops = append(
6✔
164
                        path.BlindedPath.BlindedHops,
6✔
165
                        &sphinx.BlindedHopInfo{
6✔
166
                                BlindedNodePub: &BlindedPathNUMSKey,
6✔
167
                                // We add the last hop's cipher text so that
6✔
168
                                // the payload size of the final hop is equal
6✔
169
                                // to the real last hop.
6✔
170
                                CipherText: lastHop.CipherText,
6✔
171
                        },
6✔
172
                )
6✔
173
        }
174

175
        return &BlindedPaymentPathSet{
9✔
176
                paths:        pathSet,
9✔
177
                targetPubKey: targetPub,
9✔
178
                features:     features,
9✔
179
                finalCLTV:    finalCLTVDelta,
9✔
180
        }, nil
9✔
181
}
182

183
// TargetPubKey returns the public key to be used as the destination node's
184
// public key during pathfinding.
185
func (s *BlindedPaymentPathSet) TargetPubKey() *btcec.PublicKey {
8✔
186
        return s.targetPubKey
8✔
187
}
8✔
188

189
// Features returns the set of relay features available for the payment.
190
func (s *BlindedPaymentPathSet) Features() *lnwire.FeatureVector {
×
191
        return s.features
×
192
}
×
193

194
// IntroNodeOnlyPath can be called if it is expected that the path set only
195
// contains a single payment path which itself only has one hop. It errors if
196
// this is not the case.
197
func (s *BlindedPaymentPathSet) IntroNodeOnlyPath() (*BlindedPayment, error) {
×
198
        if len(s.paths) != 1 {
×
199
                return nil, fmt.Errorf("expected only a single path in the "+
×
200
                        "blinded payment set, got %d", len(s.paths))
×
201
        }
×
202

203
        if len(s.paths[0].BlindedPath.BlindedHops) > 1 {
×
204
                return nil, fmt.Errorf("an intro node only path cannot have " +
×
205
                        "more than one hop")
×
206
        }
×
207

208
        return s.paths[0], nil
×
209
}
210

211
// IsIntroNode returns true if the given vertex is an introduction node for one
212
// of the paths in the blinded payment path set.
213
func (s *BlindedPaymentPathSet) IsIntroNode(source route.Vertex) bool {
6✔
214
        for _, path := range s.paths {
12✔
215
                introVertex := route.NewVertex(
6✔
216
                        path.BlindedPath.IntroductionPoint,
6✔
217
                )
6✔
218
                if source == introVertex {
7✔
219
                        return true
1✔
220
                }
1✔
221
        }
222

223
        return false
5✔
224
}
225

226
// FinalCLTVDelta is the minimum CLTV delta to use for the final hop on the
227
// route. In most cases this will return zero since the value is accounted for
228
// in the path's accumulated CLTVExpiryDelta. Only in the edge case of the path
229
// set only including a single path which only includes an introduction node
230
// will this return a non-zero value.
231
func (s *BlindedPaymentPathSet) FinalCLTVDelta() uint16 {
4✔
232
        return s.finalCLTV
4✔
233
}
4✔
234

235
// LargestLastHopPayloadPath returns the BlindedPayment in the set that has the
236
// largest last-hop payload. This is to be used for onion size estimation in
237
// path finding.
238
func (s *BlindedPaymentPathSet) LargestLastHopPayloadPath() (*BlindedPayment,
239
        error) {
4✔
240

4✔
241
        var (
4✔
242
                largestPath *BlindedPayment
4✔
243
                currentMax  int
4✔
244
        )
4✔
245

4✔
246
        if len(s.paths) == 0 {
4✔
247
                return nil, fmt.Errorf("no blinded paths in the set")
×
248
        }
×
249

250
        // We set the largest path to make sure we always return a path even
251
        // if the cipher text is empty.
252
        largestPath = s.paths[0]
4✔
253

4✔
254
        for _, path := range s.paths {
10✔
255
                numHops := len(path.BlindedPath.BlindedHops)
6✔
256
                lastHop := path.BlindedPath.BlindedHops[numHops-1]
6✔
257

6✔
258
                if len(lastHop.CipherText) > currentMax {
10✔
259
                        largestPath = path
4✔
260
                        currentMax = len(lastHop.CipherText)
4✔
261
                }
4✔
262
        }
263

264
        return largestPath, nil
4✔
265
}
266

267
// ToRouteHints converts the blinded path payment set into a RouteHints map so
268
// that the blinded payment paths can be treated like route hints throughout the
269
// code base.
270
func (s *BlindedPaymentPathSet) ToRouteHints() (RouteHints, error) {
3✔
271
        hints := make(RouteHints)
3✔
272

3✔
273
        for _, path := range s.paths {
6✔
274
                pathHints, err := path.toRouteHints()
3✔
275
                if err != nil {
3✔
276
                        return nil, err
×
277
                }
×
278

279
                for from, edges := range pathHints {
5✔
280
                        hints[from] = append(hints[from], edges...)
2✔
281
                }
2✔
282
        }
283

284
        if len(hints) == 0 {
5✔
285
                return nil, nil
2✔
286
        }
2✔
287

288
        return hints, nil
1✔
289
}
290

291
// IsBlindedRouteNUMSTargetKey returns true if the given public key is the
292
// NUMS key used as a target for blinded path final hops.
293
func IsBlindedRouteNUMSTargetKey(pk []byte) bool {
3✔
294
        return bytes.Equal(pk, CompressedBlindedPathNUMSKey)
3✔
295
}
3✔
296

297
// BlindedPayment provides the path and payment parameters required to send a
298
// payment along a blinded path.
299
type BlindedPayment struct {
300
        // BlindedPath contains the unblinded introduction point and blinded
301
        // hops for the blinded section of the payment.
302
        BlindedPath *sphinx.BlindedPath
303

304
        // BaseFee is the total base fee to be paid for payments made over the
305
        // blinded path.
306
        BaseFee uint32
307

308
        // ProportionalFeeRate is the aggregated proportional fee rate for
309
        // payments made over the blinded path.
310
        ProportionalFeeRate uint32
311

312
        // CltvExpiryDelta is the total expiry delta for the blinded path. This
313
        // field includes the CLTV for the blinded hops *and* the final cltv
314
        // delta for the receiver.
315
        CltvExpiryDelta uint16
316

317
        // HtlcMinimum is the highest HLTC minimum supported along the blinded
318
        // path (while some hops may have lower values, we're effectively
319
        // bounded by the highest minimum).
320
        HtlcMinimum uint64
321

322
        // HtlcMaximum is the lowest HTLC maximum supported along the blinded
323
        // path (while some hops may have higher values, we're effectively
324
        // bounded by the lowest maximum).
325
        HtlcMaximum uint64
326

327
        // Features is the set of relay features available for the payment.
328
        Features *lnwire.FeatureVector
329
}
330

331
// Validate performs validation on a blinded payment.
332
func (b *BlindedPayment) Validate() error {
5✔
333
        if b.BlindedPath == nil {
6✔
334
                return ErrNoBlindedPath
1✔
335
        }
1✔
336

337
        // The sphinx library inserts the introduction node as the first hop,
338
        // so we expect at least one hop.
339
        if len(b.BlindedPath.BlindedHops) < 1 {
5✔
340
                return fmt.Errorf("%w got: %v", ErrInsufficientBlindedHops,
1✔
341
                        len(b.BlindedPath.BlindedHops))
1✔
342
        }
1✔
343

344
        if b.HtlcMaximum < b.HtlcMinimum {
4✔
345
                return fmt.Errorf("%w: %v < %v", ErrHTLCRestrictions,
1✔
346
                        b.HtlcMaximum, b.HtlcMinimum)
1✔
347
        }
1✔
348

349
        for _, hop := range b.BlindedPath.BlindedHops {
6✔
350
                // The first hop of the blinded path does not necessarily have
4✔
351
                // blinded node pub key because it is the introduction point.
4✔
352
                if hop.BlindedNodePub == nil {
6✔
353
                        continue
2✔
354
                }
355

356
                if IsBlindedRouteNUMSTargetKey(
2✔
357
                        hop.BlindedNodePub.SerializeCompressed(),
2✔
358
                ) {
2✔
359

×
360
                        return fmt.Errorf("blinded path cannot include NUMS "+
×
361
                                "key: %s", BlindedPathNUMSHex)
×
362
                }
×
363
        }
364

365
        return nil
2✔
366
}
367

368
// toRouteHints produces a set of chained route hints that represent a blinded
369
// path. In the case of a single hop blinded route (which is paying directly
370
// to the introduction point), no hints will be returned. In this case callers
371
// *must* account for the blinded route's CLTV delta elsewhere (as this is
372
// effectively the final_cltv_delta for the receiving introduction node). In
373
// the case of multiple blinded hops, CLTV delta is fully accounted for in the
374
// hints (both for intermediate hops and the final_cltv_delta for the receiving
375
// node).
376
func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
8✔
377
        // If we just have a single hop in our blinded route, it just contains
8✔
378
        // an introduction node (this is a valid path according to the spec).
8✔
379
        // Since we have the un-blinded node ID for the introduction node, we
8✔
380
        // don't need to add any route hints.
8✔
381
        if len(b.BlindedPath.BlindedHops) == 1 {
11✔
382
                return nil, nil
3✔
383
        }
3✔
384

385
        hintCount := len(b.BlindedPath.BlindedHops) - 1
5✔
386
        hints := make(
5✔
387
                RouteHints, hintCount,
5✔
388
        )
5✔
389

5✔
390
        // Start at the unblinded introduction node, because our pathfinding
5✔
391
        // will be able to locate this point in the graph.
5✔
392
        fromNode := route.NewVertex(b.BlindedPath.IntroductionPoint)
5✔
393

5✔
394
        features := lnwire.EmptyFeatureVector()
5✔
395
        if b.Features != nil {
7✔
396
                features = b.Features.Clone()
2✔
397
        }
2✔
398

399
        // Use the total aggregate relay parameters for the entire blinded
400
        // route as the policy for the hint from our introduction node. This
401
        // will ensure that pathfinding provides sufficient fees/delay for the
402
        // blinded portion to the introduction node.
403
        firstBlindedHop := b.BlindedPath.BlindedHops[1].BlindedNodePub
5✔
404
        edgePolicy := &models.CachedEdgePolicy{
5✔
405
                TimeLockDelta: b.CltvExpiryDelta,
5✔
406
                MinHTLC:       lnwire.MilliSatoshi(b.HtlcMinimum),
5✔
407
                MaxHTLC:       lnwire.MilliSatoshi(b.HtlcMaximum),
5✔
408
                FeeBaseMSat:   lnwire.MilliSatoshi(b.BaseFee),
5✔
409
                FeeProportionalMillionths: lnwire.MilliSatoshi(
5✔
410
                        b.ProportionalFeeRate,
5✔
411
                ),
5✔
412
                ToNodePubKey: func() route.Vertex {
13✔
413
                        return route.NewVertex(
8✔
414
                                // The first node in this slice is
8✔
415
                                // the introduction node, so we start
8✔
416
                                // at index 1 to get the first blinded
8✔
417
                                // relaying node.
8✔
418
                                firstBlindedHop,
8✔
419
                        )
8✔
420
                },
8✔
421
                ToNodeFeatures: features,
422
        }
423

424
        lastEdge, err := NewBlindedEdge(edgePolicy, b, 0)
5✔
425
        if err != nil {
5✔
426
                return nil, err
×
427
        }
×
428

429
        hints[fromNode] = []AdditionalEdge{lastEdge}
5✔
430

5✔
431
        // Start at an offset of 1 because the first node in our blinded hops
5✔
432
        // is the introduction node and terminate at the second-last node
5✔
433
        // because we're dealing with hops as pairs.
5✔
434
        for i := 1; i < hintCount; i++ {
16✔
435
                // Set our origin node to the current
11✔
436
                fromNode = route.NewVertex(
11✔
437
                        b.BlindedPath.BlindedHops[i].BlindedNodePub,
11✔
438
                )
11✔
439

11✔
440
                // Create a hint which has no fee or cltv delta. We
11✔
441
                // specifically want zero values here because our relay
11✔
442
                // parameters are expressed in encrypted blobs rather than the
11✔
443
                // route itself for blinded routes.
11✔
444
                nextHopIdx := i + 1
11✔
445
                nextNode := route.NewVertex(
11✔
446
                        b.BlindedPath.BlindedHops[nextHopIdx].BlindedNodePub,
11✔
447
                )
11✔
448

11✔
449
                edgePolicy := &models.CachedEdgePolicy{
11✔
450
                        ToNodePubKey: func() route.Vertex {
24✔
451
                                return nextNode
13✔
452
                        },
13✔
453
                        ToNodeFeatures: features,
454
                }
455

456
                lastEdge, err = NewBlindedEdge(edgePolicy, b, i)
11✔
457
                if err != nil {
11✔
458
                        return nil, err
×
459
                }
×
460

461
                hints[fromNode] = []AdditionalEdge{lastEdge}
11✔
462
        }
463

464
        return hints, nil
5✔
465
}
466

467
// deepCopy returns a deep copy of the BlindedPayment.
468
func (b *BlindedPayment) deepCopy() *BlindedPayment {
12✔
469
        if b == nil {
13✔
470
                return nil
1✔
471
        }
1✔
472

473
        cpyPayment := &BlindedPayment{
11✔
474
                BaseFee:             b.BaseFee,
11✔
475
                ProportionalFeeRate: b.ProportionalFeeRate,
11✔
476
                CltvExpiryDelta:     b.CltvExpiryDelta,
11✔
477
                HtlcMinimum:         b.HtlcMinimum,
11✔
478
                HtlcMaximum:         b.HtlcMaximum,
11✔
479
        }
11✔
480

11✔
481
        // Deep copy the BlindedPath if it exists
11✔
482
        if b.BlindedPath != nil {
22✔
483
                cpyPayment.BlindedPath = &sphinx.BlindedPath{
11✔
484
                        BlindedHops: make([]*sphinx.BlindedHopInfo,
11✔
485
                                len(b.BlindedPath.BlindedHops)),
11✔
486
                }
11✔
487

11✔
488
                if b.BlindedPath.IntroductionPoint != nil {
19✔
489
                        cpyPayment.BlindedPath.IntroductionPoint =
8✔
490
                                copyPublicKey(b.BlindedPath.IntroductionPoint)
8✔
491
                }
8✔
492

493
                if b.BlindedPath.BlindingPoint != nil {
16✔
494
                        cpyPayment.BlindedPath.BlindingPoint =
5✔
495
                                copyPublicKey(b.BlindedPath.BlindingPoint)
5✔
496
                }
5✔
497

498
                // Copy each blinded hop info.
499
                for i, hop := range b.BlindedPath.BlindedHops {
29✔
500
                        if hop == nil {
18✔
501
                                continue
×
502
                        }
503

504
                        cpyHop := &sphinx.BlindedHopInfo{
18✔
505
                                CipherText: hop.CipherText,
18✔
506
                        }
18✔
507

18✔
508
                        if hop.BlindedNodePub != nil {
24✔
509
                                cpyHop.BlindedNodePub =
6✔
510
                                        copyPublicKey(hop.BlindedNodePub)
6✔
511
                        }
6✔
512

513
                        cpyHop.CipherText = make([]byte, len(hop.CipherText))
18✔
514
                        copy(cpyHop.CipherText, hop.CipherText)
18✔
515

18✔
516
                        cpyPayment.BlindedPath.BlindedHops[i] = cpyHop
18✔
517
                }
518
        }
519

520
        // Deep copy the Features if they exist
521
        if b.Features != nil {
13✔
522
                cpyPayment.Features = b.Features.Clone()
2✔
523
        }
2✔
524

525
        return cpyPayment
11✔
526
}
527

528
// copyPublicKey makes a deep copy of a public key.
529
//
530
// TODO(ziggie): Remove this function if this is available in the btcec library.
531
func copyPublicKey(pk *btcec.PublicKey) *btcec.PublicKey {
19✔
532
        var result secp256k1.JacobianPoint
19✔
533
        pk.AsJacobian(&result)
19✔
534
        result.ToAffine()
19✔
535

19✔
536
        return btcec.NewPublicKey(&result.X, &result.Y)
19✔
537
}
19✔
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