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

lightningnetwork / lnd / 25455608158

06 May 2026 07:10PM UTC coverage: 62.23% (+0.002%) from 62.228%
25455608158

Pull #10686

github

web-flow
Merge c5cd7ffca into 6fd5b7bb2
Pull Request #10686: chainreg: use getnetworkinfo to count outbound peers (bitcoind backend)

7 of 37 new or added lines in 1 file covered. (18.92%)

31329 existing lines in 480 files now uncovered.

143794 of 231070 relevant lines covered (62.23%)

19066.44 hits per line

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

71.23
/zpay32/encode.go
1
package zpay32
2

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

8
        "github.com/btcsuite/btcd/btcutil"
9
        "github.com/btcsuite/btcd/btcutil/bech32"
10
        "github.com/btcsuite/btcd/chaincfg"
11
        "github.com/btcsuite/btcd/chaincfg/chainhash"
12
        "github.com/lightningnetwork/lnd/fn/v2"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
)
15

16
// Encode takes the given MessageSigner and returns a string encoding this
17
// invoice signed by the node key of the signer.
18
func (invoice *Invoice) Encode(signer MessageSigner) (string, error) {
187✔
19
        // First check that this invoice is valid before starting the encoding.
187✔
20
        if err := validateInvoice(invoice); err != nil {
190✔
UNCOV
21
                return "", err
3✔
UNCOV
22
        }
3✔
23

24
        // The buffer will encoded the invoice data using 5-bit groups (base32).
25
        var bufferBase32 bytes.Buffer
184✔
26

184✔
27
        // The timestamp will be encoded using 35 bits, in base32.
184✔
28
        timestampBase32 := uint64ToBase32(uint64(invoice.Timestamp.Unix()))
184✔
29

184✔
30
        // The timestamp must be exactly 35 bits, which means 7 groups. If it
184✔
31
        // can fit into fewer groups we add leading zero groups, if it is too
184✔
32
        // big we fail early, as there is not possible to encode it.
184✔
33
        if len(timestampBase32) > timestampBase32Len {
184✔
34
                return "", fmt.Errorf("timestamp too big: %d",
×
35
                        invoice.Timestamp.Unix())
×
36
        }
×
37

38
        // Add zero bytes to the first timestampBase32Len-len(timestampBase32)
39
        // groups, then add the non-zero groups.
40
        zeroes := make([]byte, timestampBase32Len-len(timestampBase32))
184✔
41
        _, err := bufferBase32.Write(zeroes)
184✔
42
        if err != nil {
184✔
43
                return "", fmt.Errorf("unable to write to buffer: %w", err)
×
44
        }
×
45
        _, err = bufferBase32.Write(timestampBase32)
184✔
46
        if err != nil {
184✔
47
                return "", fmt.Errorf("unable to write to buffer: %w", err)
×
48
        }
×
49

50
        // We now write the tagged fields to the buffer, which will fill the
51
        // rest of the data part before the signature.
52
        if err := writeTaggedFields(&bufferBase32, invoice); err != nil {
184✔
53
                return "", err
×
54
        }
×
55

56
        // The human-readable part (hrp) is "ln" + net hrp + optional amount,
57
        // except for signet where we add an additional "s" to differentiate it
58
        // from the older testnet3 (Core devs decided to use the same hrp for
59
        // signet as for testnet3 which is not optimal for LN). See
60
        // https://github.com/lightningnetwork/lightning-rfc/pull/844 for more
61
        // information.
62
        hrp := "ln" + invoice.Net.Bech32HRPSegwit
184✔
63
        if invoice.Net.Name == chaincfg.SigNetParams.Name {
194✔
UNCOV
64
                hrp = "lntbs"
10✔
UNCOV
65
        }
10✔
66
        if invoice.MilliSat != nil {
317✔
67
                // Encode the amount using the fewest possible characters.
133✔
68
                am, err := encodeAmount(*invoice.MilliSat)
133✔
69
                if err != nil {
133✔
70
                        return "", err
×
71
                }
×
72
                hrp += am
133✔
73
        }
74

75
        // The signature is over the single SHA-256 hash of the hrp + the
76
        // tagged fields encoded in base256.
77
        taggedFieldsBytes, err := bech32.ConvertBits(bufferBase32.Bytes(), 5, 8, true)
184✔
78
        if err != nil {
184✔
79
                return "", err
×
80
        }
×
81

82
        toSign := append([]byte(hrp), taggedFieldsBytes...)
184✔
83

184✔
84
        // We use compact signature format, and also encoded the recovery ID
184✔
85
        // such that a reader of the invoice can recover our pubkey from the
184✔
86
        // signature.
184✔
87
        sign, err := signer.SignCompact(toSign)
184✔
88
        if err != nil {
184✔
89
                return "", err
×
90
        }
×
91

92
        // From the header byte we can extract the recovery ID, and the last 64
93
        // bytes encode the signature.
94
        recoveryID := sign[0] - 27 - 4
184✔
95
        sig, err := lnwire.NewSigFromWireECDSA(sign[1:])
184✔
96
        if err != nil {
184✔
97
                return "", err
×
98
        }
×
99

100
        // If the pubkey field was explicitly set, it must be set to the pubkey
101
        // used to create the signature.
102
        if invoice.Destination != nil {
252✔
UNCOV
103
                signature, err := sig.ToSignature()
68✔
UNCOV
104
                if err != nil {
68✔
105
                        return "", fmt.Errorf("unable to deserialize "+
×
106
                                "signature: %v", err)
×
107
                }
×
108

UNCOV
109
                hash := chainhash.HashB(toSign)
68✔
UNCOV
110
                valid := signature.Verify(hash, invoice.Destination)
68✔
UNCOV
111
                if !valid {
125✔
UNCOV
112
                        return "", fmt.Errorf("signature does not match " +
57✔
UNCOV
113
                                "provided pubkey")
57✔
UNCOV
114
                }
57✔
115
        }
116

117
        // Convert the signature to base32 before writing it to the buffer.
118
        signBase32, err := bech32.ConvertBits(
127✔
119
                append(sig.RawBytes(), recoveryID),
127✔
120
                8, 5, true,
127✔
121
        )
127✔
122
        if err != nil {
127✔
123
                return "", err
×
124
        }
×
125
        bufferBase32.Write(signBase32)
127✔
126

127✔
127
        // Now we can create the bech32 encoded string from the base32 buffer.
127✔
128
        b32, err := bech32.Encode(hrp, bufferBase32.Bytes())
127✔
129
        if err != nil {
127✔
130
                return "", err
×
131
        }
×
132

133
        // Before returning, check that the bech32 encoded string is not greater
134
        // than our largest supported invoice size.
135
        if len(b32) > maxInvoiceLength {
127✔
136
                return "", ErrInvoiceTooLarge
×
137
        }
×
138

139
        return b32, nil
127✔
140
}
141

142
// writeTaggedFields writes the non-nil tagged fields of the Invoice to the
143
// base32 buffer.
144
func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
184✔
145
        if invoice.PaymentHash != nil {
368✔
146
                err := writeBytes32(bufferBase32, fieldTypeP, *invoice.PaymentHash)
184✔
147
                if err != nil {
184✔
148
                        return err
×
149
                }
×
150
        }
151

152
        if invoice.Description != nil {
343✔
153
                base32, err := bech32.ConvertBits([]byte(*invoice.Description),
159✔
154
                        8, 5, true)
159✔
155
                if err != nil {
159✔
156
                        return err
×
157
                }
×
158
                err = writeTaggedField(bufferBase32, fieldTypeD, base32)
159✔
159
                if err != nil {
159✔
160
                        return err
×
161
                }
×
162
        }
163

164
        if invoice.DescriptionHash != nil {
209✔
UNCOV
165
                err := writeBytes32(
25✔
UNCOV
166
                        bufferBase32, fieldTypeH, *invoice.DescriptionHash,
25✔
UNCOV
167
                )
25✔
UNCOV
168
                if err != nil {
25✔
169
                        return err
×
170
                }
×
171
        }
172

173
        if invoice.Metadata != nil {
192✔
UNCOV
174
                base32, err := bech32.ConvertBits(invoice.Metadata, 8, 5, true)
8✔
UNCOV
175
                if err != nil {
8✔
176
                        return err
×
177
                }
×
UNCOV
178
                err = writeTaggedField(bufferBase32, fieldTypeM, base32)
8✔
UNCOV
179
                if err != nil {
8✔
180
                        return err
×
181
                }
×
182
        }
183

184
        if invoice.minFinalCLTVExpiry != nil {
199✔
185
                finalDelta := uint64ToBase32(*invoice.minFinalCLTVExpiry)
15✔
186
                err := writeTaggedField(bufferBase32, fieldTypeC, finalDelta)
15✔
187
                if err != nil {
15✔
188
                        return err
×
189
                }
×
190
        }
191

192
        if invoice.expiry != nil {
290✔
193
                seconds := invoice.expiry.Seconds()
106✔
194
                expiry := uint64ToBase32(uint64(seconds))
106✔
195
                err := writeTaggedField(bufferBase32, fieldTypeX, expiry)
106✔
196
                if err != nil {
106✔
197
                        return err
×
198
                }
×
199
        }
200

201
        if invoice.FallbackAddr != nil {
196✔
UNCOV
202
                var version byte
12✔
UNCOV
203
                switch addr := invoice.FallbackAddr.(type) {
12✔
UNCOV
204
                case *btcutil.AddressPubKeyHash:
6✔
UNCOV
205
                        version = fallbackVersionPubkeyHash
6✔
UNCOV
206
                case *btcutil.AddressScriptHash:
2✔
UNCOV
207
                        version = fallbackVersionScriptHash
2✔
UNCOV
208
                case *btcutil.AddressWitnessPubKeyHash:
2✔
UNCOV
209
                        version = addr.WitnessVersion()
2✔
UNCOV
210
                case *btcutil.AddressWitnessScriptHash:
1✔
UNCOV
211
                        version = addr.WitnessVersion()
1✔
UNCOV
212
                case *btcutil.AddressTaproot:
1✔
UNCOV
213
                        version = addr.WitnessVersion()
1✔
214
                default:
×
215
                        return fmt.Errorf("unknown fallback address type")
×
216
                }
UNCOV
217
                base32Addr, err := bech32.ConvertBits(
12✔
UNCOV
218
                        invoice.FallbackAddr.ScriptAddress(), 8, 5, true)
12✔
UNCOV
219
                if err != nil {
12✔
220
                        return err
×
221
                }
×
222

UNCOV
223
                err = writeTaggedField(bufferBase32, fieldTypeF,
12✔
UNCOV
224
                        append([]byte{version}, base32Addr...))
12✔
UNCOV
225
                if err != nil {
12✔
226
                        return err
×
227
                }
×
228
        }
229

230
        for _, routeHint := range invoice.RouteHints {
195✔
231
                // Each hop hint is encoded using 51 bytes, so we'll make to
11✔
232
                // sure to allocate enough space for the whole route hint.
11✔
233
                routeHintBase256 := make([]byte, 0, hopHintLen*len(routeHint))
11✔
234

11✔
235
                for _, hopHint := range routeHint {
26✔
236
                        hopHintBase256 := make([]byte, hopHintLen)
15✔
237
                        copy(hopHintBase256[:33], hopHint.NodeID.SerializeCompressed())
15✔
238
                        binary.BigEndian.PutUint64(
15✔
239
                                hopHintBase256[33:41], hopHint.ChannelID,
15✔
240
                        )
15✔
241
                        binary.BigEndian.PutUint32(
15✔
242
                                hopHintBase256[41:45], hopHint.FeeBaseMSat,
15✔
243
                        )
15✔
244
                        binary.BigEndian.PutUint32(
15✔
245
                                hopHintBase256[45:49], hopHint.FeeProportionalMillionths,
15✔
246
                        )
15✔
247
                        binary.BigEndian.PutUint16(
15✔
248
                                hopHintBase256[49:51], hopHint.CLTVExpiryDelta,
15✔
249
                        )
15✔
250
                        routeHintBase256 = append(routeHintBase256, hopHintBase256...)
15✔
251
                }
15✔
252

253
                routeHintBase32, err := bech32.ConvertBits(
11✔
254
                        routeHintBase256, 8, 5, true,
11✔
255
                )
11✔
256
                if err != nil {
11✔
257
                        return err
×
258
                }
×
259

260
                err = writeTaggedField(bufferBase32, fieldTypeR, routeHintBase32)
11✔
261
                if err != nil {
11✔
262
                        return err
×
263
                }
×
264
        }
265

266
        for _, path := range invoice.BlindedPaymentPaths {
192✔
267
                var buf bytes.Buffer
8✔
268

8✔
269
                err := path.Encode(&buf)
8✔
270
                if err != nil {
8✔
271
                        return err
×
272
                }
×
273

274
                blindedPathBase32, err := bech32.ConvertBits(
8✔
275
                        buf.Bytes(), 8, 5, true,
8✔
276
                )
8✔
277
                if err != nil {
8✔
278
                        return err
×
279
                }
×
280

281
                err = writeTaggedField(
8✔
282
                        bufferBase32, fieldTypeB, blindedPathBase32,
8✔
283
                )
8✔
284
                if err != nil {
8✔
285
                        return err
×
286
                }
×
287
        }
288

289
        if invoice.Destination != nil {
252✔
UNCOV
290
                // Convert 33 byte pubkey to 53 5-bit groups.
68✔
UNCOV
291
                pubKeyBase32, err := bech32.ConvertBits(
68✔
UNCOV
292
                        invoice.Destination.SerializeCompressed(), 8, 5, true)
68✔
UNCOV
293
                if err != nil {
68✔
294
                        return err
×
295
                }
×
296

UNCOV
297
                if len(pubKeyBase32) != pubKeyBase32Len {
68✔
298
                        return fmt.Errorf("invalid pubkey length: %d",
×
299
                                len(invoice.Destination.SerializeCompressed()))
×
300
                }
×
301

UNCOV
302
                err = writeTaggedField(bufferBase32, fieldTypeN, pubKeyBase32)
68✔
UNCOV
303
                if err != nil {
68✔
304
                        return err
×
305
                }
×
306
        }
307

308
        err := fn.MapOptionZ(invoice.PaymentAddr, func(addr [32]byte) error {
288✔
309
                return writeBytes32(bufferBase32, fieldTypeS, addr)
104✔
310
        })
104✔
311
        if err != nil {
184✔
312
                return err
×
313
        }
×
314

315
        if invoice.Features.SerializeSize32() > 0 {
219✔
316
                var b bytes.Buffer
35✔
317
                err := invoice.Features.RawFeatureVector.EncodeBase32(&b)
35✔
318
                if err != nil {
35✔
319
                        return err
×
320
                }
×
321

322
                err = writeTaggedField(bufferBase32, fieldType9, b.Bytes())
35✔
323
                if err != nil {
35✔
324
                        return err
×
325
                }
×
326
        }
327

328
        return nil
184✔
329
}
330

331
// writeBytes32 encodes a 32-byte array as base32 and writes it to bufferBase32
332
// under the passed fieldType.
333
func writeBytes32(bufferBase32 *bytes.Buffer, fieldType byte, b [32]byte) error {
309✔
334
        // Convert 32 byte hash to 52 5-bit groups.
309✔
335
        base32, err := bech32.ConvertBits(b[:], 8, 5, true)
309✔
336
        if err != nil {
309✔
337
                return err
×
338
        }
×
339

340
        return writeTaggedField(bufferBase32, fieldType, base32)
309✔
341
}
342

343
// writeTaggedField takes the type of a tagged data field, and the data of
344
// the tagged field (encoded in base32), and writes the type, length and data
345
// to the buffer.
346
func writeTaggedField(bufferBase32 *bytes.Buffer, dataType byte, data []byte) error {
707✔
347
        // Length must be exactly 10 bits, so add leading zero groups if
707✔
348
        // needed.
707✔
349
        lenBase32 := uint64ToBase32(uint64(len(data)))
707✔
350
        for len(lenBase32) < 2 {
991✔
351
                lenBase32 = append([]byte{0}, lenBase32...)
284✔
352
        }
284✔
353

354
        if len(lenBase32) != 2 {
707✔
355
                return fmt.Errorf("data length too big to fit within 10 bits: %d",
×
356
                        len(data))
×
357
        }
×
358

359
        err := bufferBase32.WriteByte(dataType)
707✔
360
        if err != nil {
707✔
361
                return fmt.Errorf("unable to write to buffer: %w", err)
×
362
        }
×
363
        _, err = bufferBase32.Write(lenBase32)
707✔
364
        if err != nil {
707✔
365
                return fmt.Errorf("unable to write to buffer: %w", err)
×
366
        }
×
367
        _, err = bufferBase32.Write(data)
707✔
368
        if err != nil {
707✔
369
                return fmt.Errorf("unable to write to buffer: %w", err)
×
370
        }
×
371

372
        return nil
707✔
373
}
374

375
// uint64ToBase32 converts a uint64 to a base32 encoded integer encoded using
376
// as few 5-bit groups as possible.
377
func uint64ToBase32(num uint64) []byte {
1,001✔
378
        // Return at least one group.
1,001✔
379
        if num == 0 {
1,044✔
380
                return []byte{0}
43✔
381
        }
43✔
382

383
        // To fit an uint64, we need at most is ceil(64 / 5) = 13 groups.
384
        arr := make([]byte, 13)
962✔
385
        i := 13
962✔
386
        for num > 0 {
3,658✔
387
                i--
2,696✔
388
                arr[i] = byte(num & uint64(31)) // 0b11111 in binary
2,696✔
389
                num >>= 5
2,696✔
390
        }
2,696✔
391

392
        // We only return non-zero leading groups.
393
        return arr[i:]
962✔
394
}
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

© 2026 Coveralls, Inc