• 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

55.88
/zpay32/blinded_path.go
1
package zpay32
2

3
import (
4
        "encoding/binary"
5
        "fmt"
6
        "io"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        sphinx "github.com/lightningnetwork/lightning-onion"
10
        "github.com/lightningnetwork/lnd/lnwire"
11
        "github.com/lightningnetwork/lnd/tlv"
12
)
13

14
const (
15
        // maxNumHopsPerPath is the maximum number of blinded path hops that can
16
        // be included in a single encoded blinded path. This is calculated
17
        // based on the `data_length` limit of 638 bytes for any tagged field in
18
        // a BOLT 11 invoice along with the estimated number of bytes required
19
        // for encoding the most minimal blinded path hop. See the [bLIP
20
        // proposal](https://github.com/lightning/blips/pull/39) for a detailed
21
        // calculation.
22
        maxNumHopsPerPath = 7
23

24
        // maxCipherTextLength defines the largest cipher text size allowed.
25
        // This is derived by using the `data_length` upper bound of 639 bytes
26
        // and then assuming the case of a path with only a single hop (meaning
27
        // the cipher text may be as large as possible).
28
        maxCipherTextLength = 535
29
)
30

31
var (
32
        // byteOrder defines the endian-ness we use for encoding to and from
33
        // buffers.
34
        byteOrder = binary.BigEndian
35
)
36

37
// BlindedPaymentPath holds all the information a payer needs to know about a
38
// blinded path to a receiver of a payment.
39
type BlindedPaymentPath struct {
40
        // FeeBaseMsat is the total base fee for the path in milli-satoshis.
41
        FeeBaseMsat uint32
42

43
        // FeeRate is the total fee rate for the path in parts per million.
44
        FeeRate uint32
45

46
        // CltvExpiryDelta is the total CLTV delta to apply to the path.
47
        CltvExpiryDelta uint16
48

49
        // HTLCMinMsat is the minimum number of milli-satoshis that any hop in
50
        // the path will route.
51
        HTLCMinMsat uint64
52

53
        // HTLCMaxMsat is the maximum number of milli-satoshis that a hop in the
54
        // path will route.
55
        HTLCMaxMsat uint64
56

57
        // Features is the feature bit vector for the path.
58
        Features *lnwire.FeatureVector
59

60
        // FirstEphemeralBlindingPoint is the blinding point to send to the
61
        // introduction node. It will be used by the introduction node to derive
62
        // a shared secret with the receiver which can then be used to decode
63
        // the encrypted payload from the receiver.
64
        FirstEphemeralBlindingPoint *btcec.PublicKey
65

66
        // Hops is the blinded path. The first hop is the introduction node and
67
        // so the BlindedNodeID of this hop will be the real node ID.
68
        Hops []*sphinx.BlindedHopInfo
69
}
70

71
// DecodeBlindedPayment attempts to parse a BlindedPaymentPath from the passed
72
// reader.
73
func DecodeBlindedPayment(r io.Reader) (*BlindedPaymentPath, error) {
3✔
74
        var payment BlindedPaymentPath
3✔
75

3✔
76
        if err := binary.Read(r, byteOrder, &payment.FeeBaseMsat); err != nil {
3✔
UNCOV
77
                return nil, err
×
UNCOV
78
        }
×
79

80
        if err := binary.Read(r, byteOrder, &payment.FeeRate); err != nil {
3✔
UNCOV
81
                return nil, err
×
UNCOV
82
        }
×
83

84
        err := binary.Read(r, byteOrder, &payment.CltvExpiryDelta)
3✔
85
        if err != nil {
3✔
UNCOV
86
                return nil, err
×
UNCOV
87
        }
×
88

89
        err = binary.Read(r, byteOrder, &payment.HTLCMinMsat)
3✔
90
        if err != nil {
3✔
UNCOV
91
                return nil, err
×
UNCOV
92
        }
×
93

94
        err = binary.Read(r, byteOrder, &payment.HTLCMaxMsat)
3✔
95
        if err != nil {
3✔
UNCOV
96
                return nil, err
×
UNCOV
97
        }
×
98

99
        // Parse the feature bit vector.
100
        f := lnwire.EmptyFeatureVector()
3✔
101
        err = f.Decode(r)
3✔
102
        if err != nil {
3✔
UNCOV
103
                return nil, err
×
UNCOV
104
        }
×
105
        payment.Features = f
3✔
106

3✔
107
        // Parse the first ephemeral blinding point.
3✔
108
        var blindingPointBytes [btcec.PubKeyBytesLenCompressed]byte
3✔
109
        _, err = r.Read(blindingPointBytes[:])
3✔
110
        if err != nil {
3✔
UNCOV
111
                return nil, err
×
UNCOV
112
        }
×
113

114
        blinding, err := btcec.ParsePubKey(blindingPointBytes[:])
3✔
115
        if err != nil {
3✔
UNCOV
116
                return nil, err
×
UNCOV
117
        }
×
118
        payment.FirstEphemeralBlindingPoint = blinding
3✔
119

3✔
120
        // Read the one byte hop number.
3✔
121
        var numHops [1]byte
3✔
122
        _, err = r.Read(numHops[:])
3✔
123
        if err != nil {
3✔
UNCOV
124
                return nil, err
×
UNCOV
125
        }
×
126

127
        payment.Hops = make([]*sphinx.BlindedHopInfo, int(numHops[0]))
3✔
128

3✔
129
        // Parse each hop.
3✔
130
        for i := 0; i < len(payment.Hops); i++ {
6✔
131
                hop, err := DecodeBlindedHop(r)
3✔
132
                if err != nil {
3✔
133
                        return nil, err
×
134
                }
×
135

136
                payment.Hops[i] = hop
3✔
137
        }
138

139
        return &payment, nil
3✔
140
}
141

142
// Encode serialises the BlindedPaymentPath and writes the bytes to the passed
143
// writer.
144
// 1) The first 26 bytes contain the relay info:
145
//   - Base Fee in msat: uint32 (4 bytes).
146
//   - Proportional Fee in PPM: uint32 (4 bytes).
147
//   - CLTV expiry delta: uint16 (2 bytes).
148
//   - HTLC min msat: uint64 (8 bytes).
149
//   - HTLC max msat: uint64 (8 bytes).
150
//
151
// 2) Feature bit vector length (2 bytes).
152
// 3) Feature bit vector (can be zero length).
153
// 4) First blinding point: 33 bytes.
154
// 5) Number of hops: 1 byte.
155
// 6) Encoded BlindedHops.
156
func (p *BlindedPaymentPath) Encode(w io.Writer) error {
3✔
157
        if err := binary.Write(w, byteOrder, p.FeeBaseMsat); err != nil {
3✔
158
                return err
×
159
        }
×
160

161
        if err := binary.Write(w, byteOrder, p.FeeRate); err != nil {
3✔
162
                return err
×
163
        }
×
164

165
        if err := binary.Write(w, byteOrder, p.CltvExpiryDelta); err != nil {
3✔
166
                return err
×
167
        }
×
168

169
        if err := binary.Write(w, byteOrder, p.HTLCMinMsat); err != nil {
3✔
170
                return err
×
171
        }
×
172

173
        if err := binary.Write(w, byteOrder, p.HTLCMaxMsat); err != nil {
3✔
174
                return err
×
175
        }
×
176

177
        if err := p.Features.Encode(w); err != nil {
3✔
178
                return err
×
179
        }
×
180

181
        _, err := w.Write(p.FirstEphemeralBlindingPoint.SerializeCompressed())
3✔
182
        if err != nil {
3✔
183
                return err
×
184
        }
×
185

186
        numHops := len(p.Hops)
3✔
187
        if numHops > maxNumHopsPerPath {
3✔
188
                return fmt.Errorf("the number of hops, %d, exceeds the "+
×
189
                        "maximum of %d", numHops, maxNumHopsPerPath)
×
190
        }
×
191

192
        if _, err := w.Write([]byte{byte(numHops)}); err != nil {
3✔
193
                return err
×
194
        }
×
195

196
        for _, hop := range p.Hops {
6✔
197
                if err := EncodeBlindedHop(w, hop); err != nil {
3✔
198
                        return err
×
199
                }
×
200
        }
201

202
        return nil
3✔
203
}
204

205
// DecodeBlindedHop reads a sphinx.BlindedHopInfo from the passed reader.
206
func DecodeBlindedHop(r io.Reader) (*sphinx.BlindedHopInfo, error) {
3✔
207
        var nodeIDBytes [btcec.PubKeyBytesLenCompressed]byte
3✔
208
        _, err := r.Read(nodeIDBytes[:])
3✔
209
        if err != nil {
3✔
210
                return nil, err
×
211
        }
×
212

213
        nodeID, err := btcec.ParsePubKey(nodeIDBytes[:])
3✔
214
        if err != nil {
3✔
215
                return nil, err
×
216
        }
×
217

218
        dataLen, err := tlv.ReadVarInt(r, &[8]byte{})
3✔
219
        if err != nil {
3✔
220
                return nil, err
×
221
        }
×
222

223
        if dataLen > maxCipherTextLength {
3✔
224
                return nil, fmt.Errorf("a blinded hop cipher text blob may "+
×
225
                        "not exceed the maximum of %d bytes",
×
226
                        maxCipherTextLength)
×
227
        }
×
228

229
        encryptedData := make([]byte, dataLen)
3✔
230
        _, err = r.Read(encryptedData)
3✔
231
        if err != nil {
3✔
232
                return nil, err
×
233
        }
×
234

235
        return &sphinx.BlindedHopInfo{
3✔
236
                BlindedNodePub: nodeID,
3✔
237
                CipherText:     encryptedData,
3✔
238
        }, nil
3✔
239
}
240

241
// EncodeBlindedHop writes the passed BlindedHopInfo to the given writer.
242
//
243
// 1) Blinded node pub key: 33 bytes
244
// 2) Cipher text length: BigSize
245
// 3) Cipher text.
246
func EncodeBlindedHop(w io.Writer, hop *sphinx.BlindedHopInfo) error {
3✔
247
        _, err := w.Write(hop.BlindedNodePub.SerializeCompressed())
3✔
248
        if err != nil {
3✔
249
                return err
×
250
        }
×
251

252
        if len(hop.CipherText) > maxCipherTextLength {
3✔
253
                return fmt.Errorf("encrypted recipient data can not exceed a "+
×
254
                        "length of %d bytes", maxCipherTextLength)
×
255
        }
×
256

257
        err = tlv.WriteVarInt(w, uint64(len(hop.CipherText)), &[8]byte{})
3✔
258
        if err != nil {
3✔
259
                return err
×
260
        }
×
261

262
        _, err = w.Write(hop.CipherText)
3✔
263

3✔
264
        return err
3✔
265
}
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