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

lightningnetwork / lnd / 12879602034

21 Jan 2025 03:26AM UTC coverage: 49.413% (-9.3%) from 58.72%
12879602034

Pull #9430

github

Roasbeef
lnwallet: add new NoopAdd payDesc entry type

In this commit, we add a new NoopAdd payDesc entry type. This type is
meant to be used primarily by taproot overlay channels. When we go to
settle this HTLC, rather than credit the settler for the funds, we just
give the funds back to the sender. This results in an add that when
settled, doesn't affect the balance in the channel.

This new HTLC type is intended to be used alongside a push amt, to
ensure the remote party has a non-dust balance from the start. With that
in place, then this new add type can be used for special overlay HTLCs.
Pull Request #9430: lnwallet: add new NoopAdd payDesc entry type

23 of 36 new or added lines in 2 files covered. (63.89%)

26915 existing lines in 431 files now uncovered.

100600 of 203591 relevant lines covered (49.41%)

1.54 hits per line

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

55.83
/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) {
3✔
19
        // First check that this invoice is valid before starting the encoding.
3✔
20
        if err := validateInvoice(invoice); err != nil {
3✔
UNCOV
21
                return "", err
×
UNCOV
22
        }
×
23

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

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

3✔
30
        // The timestamp must be exactly 35 bits, which means 7 groups. If it
3✔
31
        // can fit into fewer groups we add leading zero groups, if it is too
3✔
32
        // big we fail early, as there is not possible to encode it.
3✔
33
        if len(timestampBase32) > timestampBase32Len {
3✔
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))
3✔
41
        _, err := bufferBase32.Write(zeroes)
3✔
42
        if err != nil {
3✔
43
                return "", fmt.Errorf("unable to write to buffer: %w", err)
×
44
        }
×
45
        _, err = bufferBase32.Write(timestampBase32)
3✔
46
        if err != nil {
3✔
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 {
3✔
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
3✔
63
        if invoice.Net.Name == chaincfg.SigNetParams.Name {
3✔
UNCOV
64
                hrp = "lntbs"
×
UNCOV
65
        }
×
66
        if invoice.MilliSat != nil {
6✔
67
                // Encode the amount using the fewest possible characters.
3✔
68
                am, err := encodeAmount(*invoice.MilliSat)
3✔
69
                if err != nil {
3✔
70
                        return "", err
×
71
                }
×
72
                hrp += am
3✔
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)
3✔
78
        if err != nil {
3✔
79
                return "", err
×
80
        }
×
81

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

3✔
84
        // We use compact signature format, and also encoded the recovery ID
3✔
85
        // such that a reader of the invoice can recover our pubkey from the
3✔
86
        // signature.
3✔
87
        sign, err := signer.SignCompact(toSign)
3✔
88
        if err != nil {
3✔
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
3✔
95
        sig, err := lnwire.NewSigFromWireECDSA(sign[1:])
3✔
96
        if err != nil {
3✔
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 {
3✔
UNCOV
103
                signature, err := sig.ToSignature()
×
UNCOV
104
                if err != nil {
×
105
                        return "", fmt.Errorf("unable to deserialize "+
×
106
                                "signature: %v", err)
×
107
                }
×
108

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

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

3✔
127
        // Now we can create the bech32 encoded string from the base32 buffer.
3✔
128
        b32, err := bech32.Encode(hrp, bufferBase32.Bytes())
3✔
129
        if err != nil {
3✔
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 {
3✔
136
                return "", ErrInvoiceTooLarge
×
137
        }
×
138

139
        return b32, nil
3✔
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 {
3✔
145
        if invoice.PaymentHash != nil {
6✔
146
                err := writeBytes32(bufferBase32, fieldTypeP, *invoice.PaymentHash)
3✔
147
                if err != nil {
3✔
148
                        return err
×
149
                }
×
150
        }
151

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

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

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

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

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

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

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

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

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

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

258
                err = writeTaggedField(bufferBase32, fieldTypeR, routeHintBase32)
3✔
259
                if err != nil {
3✔
260
                        return err
×
261
                }
×
262
        }
263

264
        for _, path := range invoice.BlindedPaymentPaths {
6✔
265
                var buf bytes.Buffer
3✔
266

3✔
267
                err := path.Encode(&buf)
3✔
268
                if err != nil {
3✔
269
                        return err
×
270
                }
×
271

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

279
                err = writeTaggedField(
3✔
280
                        bufferBase32, fieldTypeB, blindedPathBase32,
3✔
281
                )
3✔
282
                if err != nil {
3✔
283
                        return err
×
284
                }
×
285
        }
286

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

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

UNCOV
300
                err = writeTaggedField(bufferBase32, fieldTypeN, pubKeyBase32)
×
UNCOV
301
                if err != nil {
×
302
                        return err
×
303
                }
×
304
        }
305

306
        err := fn.MapOptionZ(invoice.PaymentAddr, func(addr [32]byte) error {
6✔
307
                return writeBytes32(bufferBase32, fieldTypeS, addr)
3✔
308
        })
3✔
309
        if err != nil {
3✔
310
                return err
×
311
        }
×
312

313
        if invoice.Features.SerializeSize32() > 0 {
6✔
314
                var b bytes.Buffer
3✔
315
                err := invoice.Features.RawFeatureVector.EncodeBase32(&b)
3✔
316
                if err != nil {
3✔
317
                        return err
×
318
                }
×
319

320
                err = writeTaggedField(bufferBase32, fieldType9, b.Bytes())
3✔
321
                if err != nil {
3✔
322
                        return err
×
323
                }
×
324
        }
325

326
        return nil
3✔
327
}
328

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

338
        return writeTaggedField(bufferBase32, fieldType, base32)
3✔
339
}
340

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

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

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

370
        return nil
3✔
371
}
372

373
// uint64ToBase32 converts a uint64 to a base32 encoded integer encoded using
374
// as few 5-bit groups as possible.
375
func uint64ToBase32(num uint64) []byte {
3✔
376
        // Return at least one group.
3✔
377
        if num == 0 {
6✔
378
                return []byte{0}
3✔
379
        }
3✔
380

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

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