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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

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

82.71
/chanbackup/single.go
1
package chanbackup
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7
        "io"
8
        "net"
9

10
        "github.com/btcsuite/btcd/btcec/v2"
11
        "github.com/btcsuite/btcd/btcutil"
12
        "github.com/btcsuite/btcd/chaincfg/chainhash"
13
        "github.com/btcsuite/btcd/wire"
14
        "github.com/lightningnetwork/lnd/channeldb"
15
        "github.com/lightningnetwork/lnd/fn/v2"
16
        "github.com/lightningnetwork/lnd/keychain"
17
        "github.com/lightningnetwork/lnd/lnencrypt"
18
        "github.com/lightningnetwork/lnd/lnwire"
19
)
20

21
// SingleBackupVersion denotes the version of the single static channel backup.
22
// Based on this version, we know how to pack/unpack serialized versions of the
23
// backup.
24
type SingleBackupVersion byte
25

26
const (
27
        // DefaultSingleVersion is the default version of the single channel
28
        // backup. The serialized version of this static channel backup is
29
        // simply: version || SCB. Where SCB is the known format of the
30
        // version.
31
        DefaultSingleVersion = 0
32

33
        // TweaklessCommitVersion is the second SCB version. This version
34
        // implicitly denotes that this channel uses the new tweakless commit
35
        // format.
36
        TweaklessCommitVersion = 1
37

38
        // AnchorsCommitVersion is the third SCB version. This version
39
        // implicitly denotes that this channel uses the new anchor commitment
40
        // format.
41
        AnchorsCommitVersion = 2
42

43
        // AnchorsZeroFeeHtlcTxCommitVersion is a version that denotes this
44
        // channel is using the zero-fee second-level anchor commitment format.
45
        AnchorsZeroFeeHtlcTxCommitVersion = 3
46

47
        // ScriptEnforcedLeaseVersion is a version that denotes this channel is
48
        // using the zero-fee second-level anchor commitment format along with
49
        // an additional CLTV requirement of the channel lease maturity on any
50
        // commitment and HTLC outputs that pay directly to the channel
51
        // initiator.
52
        ScriptEnforcedLeaseVersion = 4
53

54
        // SimpleTaprootVersion is a version that denotes this channel is using
55
        // the musig2 based taproot commitment format.
56
        SimpleTaprootVersion = 5
57

58
        // TapscriptRootVersion is a version that denotes this is a MuSig2
59
        // channel with a top level tapscript commitment.
60
        TapscriptRootVersion = 6
61

62
        // closeTxVersionMask is the byte mask used that is ORed to version byte
63
        // on wire indicating that the backup has CloseTxInputs.
64
        closeTxVersionMask = 1 << 7
65
)
66

67
// Encode returns encoding of the version to put into channel backup.
68
// Argument "closeTx" specifies if the backup includes force close transaction.
69
func (v SingleBackupVersion) Encode(closeTx bool) byte {
111✔
70
        encoded := byte(v)
111✔
71

111✔
72
        // If the backup includes closing transaction, set this bit in the
111✔
73
        // encoded version.
111✔
74
        if closeTx {
174✔
75
                encoded |= closeTxVersionMask
63✔
76
        }
63✔
77

78
        return encoded
111✔
79
}
80

81
// DecodeVersion decodes the encoding of the version from a channel backup.
82
// It returns the version and if the backup includes the force close tx.
83
func DecodeVersion(encoded byte) (SingleBackupVersion, bool) {
166✔
84
        // Find if it has a closing transaction by inspecting the bit.
166✔
85
        closeTx := (encoded & closeTxVersionMask) != 0
166✔
86

166✔
87
        // The version byte also encodes the closeTxVersion feature, so we
166✔
88
        // extract it here and return it separately to the backup version.
166✔
89
        version := SingleBackupVersion(encoded &^ closeTxVersionMask)
166✔
90

166✔
91
        return version, closeTx
166✔
92
}
166✔
93

94
// IsTaproot returns if this is a backup of a taproot channel. This will also be
95
// true for simple taproot overlay channels when a version is added.
96
func (v SingleBackupVersion) IsTaproot() bool {
153✔
97
        return v == SimpleTaprootVersion || v == TapscriptRootVersion
153✔
98
}
153✔
99

100
// HasTapscriptRoot returns true if the channel is using a top level tapscript
101
// root commitment.
102
func (v SingleBackupVersion) HasTapscriptRoot() bool {
145✔
103
        return v == TapscriptRootVersion
145✔
104
}
145✔
105

106
// Single is a static description of an existing channel that can be used for
107
// the purposes of backing up. The fields in this struct allow a node to
108
// recover the settled funds within a channel in the case of partial or
109
// complete data loss. We provide the network address that we last used to
110
// connect to the peer as well, in case the node stops advertising the IP on
111
// the network for whatever reason.
112
//
113
// TODO(roasbeef): suffix version into struct?
114
type Single struct {
115
        // Version is the version that should be observed when attempting to
116
        // pack the single backup.
117
        Version SingleBackupVersion
118

119
        // IsInitiator is true if we were the initiator of the channel, and
120
        // false otherwise. We'll need to know this information in order to
121
        // properly re-derive the state hint information.
122
        IsInitiator bool
123

124
        // ChainHash is a hash which represents the blockchain that this
125
        // channel will be opened within. This value is typically the genesis
126
        // hash. In the case that the original chain went through a contentious
127
        // hard-fork, then this value will be tweaked using the unique fork
128
        // point on each branch.
129
        ChainHash chainhash.Hash
130

131
        // FundingOutpoint is the outpoint of the final funding transaction.
132
        // This value uniquely and globally identities the channel within the
133
        // target blockchain as specified by the chain hash parameter.
134
        FundingOutpoint wire.OutPoint
135

136
        // ShortChannelID encodes the exact location in the chain in which the
137
        // channel was initially confirmed. This includes: the block height,
138
        // transaction index, and the output within the target transaction.
139
        // Channels that were not confirmed at the time of backup creation will
140
        // have the funding TX broadcast height set as their block height in
141
        // the ShortChannelID.
142
        ShortChannelID lnwire.ShortChannelID
143

144
        // RemoteNodePub is the identity public key of the remote node this
145
        // channel has been established with.
146
        RemoteNodePub *btcec.PublicKey
147

148
        // Addresses is a list of IP address in which either we were able to
149
        // reach the node over in the past, OR we received an incoming
150
        // authenticated connection for the stored identity public key.
151
        Addresses []net.Addr
152

153
        // Capacity is the size of the original channel.
154
        Capacity btcutil.Amount
155

156
        // LocalChanCfg is our local channel configuration. It contains all the
157
        // information we need to re-derive the keys we used within the
158
        // channel. Most importantly, it allows to derive the base public
159
        // that's used to deriving the key used within the non-delayed
160
        // pay-to-self output on the commitment transaction for a node. With
161
        // this information, we can re-derive the private key needed to sweep
162
        // the funds on-chain.
163
        //
164
        // NOTE: Of the items in the ChannelConstraints, we only write the CSV
165
        // delay.
166
        LocalChanCfg channeldb.ChannelConfig
167

168
        // RemoteChanCfg is the remote channel confirmation. We store this as
169
        // well since we'll need some of their keys to re-derive things like
170
        // the state hint obfuscator which will allow us to recognize the state
171
        // their broadcast on chain.
172
        //
173
        // NOTE: Of the items in the ChannelConstraints, we only write the CSV
174
        // delay.
175
        RemoteChanCfg channeldb.ChannelConfig
176

177
        // ShaChainRootDesc describes how to derive the private key that was
178
        // used as the shachain root for this channel.
179
        ShaChainRootDesc keychain.KeyDescriptor
180

181
        // LeaseExpiry represents the absolute expiration as a height of the
182
        // chain of a channel lease that is applied to every output that pays
183
        // directly to the channel initiator in addition to the usual CSV
184
        // requirement.
185
        //
186
        // NOTE: This field will only be present for the following versions:
187
        //
188
        // - ScriptEnforcedLeaseVersion
189
        LeaseExpiry uint32
190

191
        // CloseTxInputs contains data needed to produce a force close tx
192
        // using for example the "chantools scbforceclose" command.
193
        //
194
        // The field is optional.
195
        CloseTxInputs fn.Option[CloseTxInputs]
196
}
197

198
// CloseTxInputs contains data needed to produce a force close transaction
199
// using for example the "chantools scbforceclose" command.
200
type CloseTxInputs struct {
201
        // CommitTx is the latest version of the commitment state, broadcast
202
        // able by us, but not signed. It can be signed by for example the
203
        // "chantools scbforceclose" command.
204
        CommitTx *wire.MsgTx
205

206
        // CommitSig is one half of the signature required to fully complete
207
        // the script for the commitment transaction above. This is the
208
        // signature signed by the remote party for our version of the
209
        // commitment transactions.
210
        CommitSig []byte
211

212
        // CommitHeight is the update number that this ChannelDelta represents
213
        // the total number of commitment updates to this point. This can be
214
        // viewed as sort of a "commitment height" as this number is
215
        // monotonically increasing.
216
        //
217
        // This field is filled only for taproot channels.
218
        CommitHeight uint64
219

220
        // TapscriptRoot is the root of the tapscript tree that will be used to
221
        // create the funding output. This is an optional field that should
222
        // only be set for overlay taproot channels (HasTapscriptRoot).
223
        TapscriptRoot fn.Option[chainhash.Hash]
224
}
225

226
// NewSingle creates a new static channel backup based on an existing open
227
// channel. We also pass in the set of addresses that we used in the past to
228
// connect to the channel peer. If possible, we include the data needed to
229
// produce a force close transaction from the most recent state using externally
230
// provided private key.
231
func NewSingle(channel *channeldb.OpenChannel,
232
        nodeAddrs []net.Addr) Single {
66✔
233

66✔
234
        var shaChainRootDesc keychain.KeyDescriptor
66✔
235

66✔
236
        // If the channel has a populated RevocationKeyLocator, then we can
66✔
237
        // just store that instead of the public key.
66✔
238
        if channel.RevocationKeyLocator.Family == keychain.KeyFamilyRevocationRoot {
66✔
UNCOV
239
                shaChainRootDesc = keychain.KeyDescriptor{
×
UNCOV
240
                        KeyLocator: channel.RevocationKeyLocator,
×
UNCOV
241
                }
×
242
        } else {
66✔
243
                // If the RevocationKeyLocator is not populated, then we'll need
66✔
244
                // to obtain a public point for the shachain root and store that.
66✔
245
                // This is the legacy scheme.
66✔
246
                var b bytes.Buffer
66✔
247
                _ = channel.RevocationProducer.Encode(&b) // Can't return an error.
66✔
248

66✔
249
                // Once we have the root, we'll make a public key from it, such that
66✔
250
                // the backups plaintext don't carry any private information. When
66✔
251
                // we go to recover, we'll present this in order to derive the
66✔
252
                // private key.
66✔
253
                _, shaChainPoint := btcec.PrivKeyFromBytes(b.Bytes())
66✔
254

66✔
255
                shaChainRootDesc = keychain.KeyDescriptor{
66✔
256
                        PubKey: shaChainPoint,
66✔
257
                        KeyLocator: keychain.KeyLocator{
66✔
258
                                Family: keychain.KeyFamilyRevocationRoot,
66✔
259
                        },
66✔
260
                }
66✔
261
        }
66✔
262

263
        // If a channel is unconfirmed, the block height of the ShortChannelID
264
        // is zero. This will lead to problems when trying to restore that
265
        // channel as the spend notifier would get a height hint of zero.
266
        // To work around that problem, we add the channel broadcast height
267
        // to the channel ID so we can use that as height hint on restore.
268
        chanID := channel.ShortChanID()
66✔
269
        if chanID.BlockHeight == 0 {
67✔
270
                chanID.BlockHeight = channel.BroadcastHeight()
1✔
271
        }
1✔
272

273
        // If this is a zero-conf channel, we'll need to have separate logic
274
        // depending on whether it's confirmed or not. This is because the
275
        // ShortChanID is an alias.
276
        if channel.IsZeroConf() {
94✔
277
                // If the channel is confirmed, we'll use the confirmed SCID.
28✔
278
                if channel.ZeroConfConfirmed() {
28✔
UNCOV
279
                        chanID = channel.ZeroConfRealScid()
×
280
                } else {
28✔
281
                        // Else if the zero-conf channel is unconfirmed, we'll
28✔
282
                        // need to use the broadcast height and zero out the
28✔
283
                        // TxIndex and TxPosition fields. This is so
28✔
284
                        // openChannelShell works properly.
28✔
285
                        chanID.BlockHeight = channel.BroadcastHeight()
28✔
286
                        chanID.TxIndex = 0
28✔
287
                        chanID.TxPosition = 0
28✔
288
                }
28✔
289
        }
290

291
        single := Single{
66✔
292
                IsInitiator:      channel.IsInitiator,
66✔
293
                ChainHash:        channel.ChainHash,
66✔
294
                FundingOutpoint:  channel.FundingOutpoint,
66✔
295
                ShortChannelID:   chanID,
66✔
296
                RemoteNodePub:    channel.IdentityPub,
66✔
297
                Addresses:        nodeAddrs,
66✔
298
                Capacity:         channel.Capacity,
66✔
299
                LocalChanCfg:     channel.LocalChanCfg,
66✔
300
                RemoteChanCfg:    channel.RemoteChanCfg,
66✔
301
                ShaChainRootDesc: shaChainRootDesc,
66✔
302
        }
66✔
303

66✔
304
        switch {
66✔
305
        case channel.ChanType.IsTaproot():
36✔
306
                if channel.ChanType.HasTapscriptRoot() {
57✔
307
                        single.Version = TapscriptRootVersion
21✔
308
                } else {
36✔
309
                        single.Version = SimpleTaprootVersion
15✔
310
                }
15✔
311

312
        case channel.ChanType.HasLeaseExpiration():
14✔
313
                single.Version = ScriptEnforcedLeaseVersion
14✔
314
                single.LeaseExpiry = channel.ThawHeight
14✔
315

316
        case channel.ChanType.ZeroHtlcTxFee():
9✔
317
                single.Version = AnchorsZeroFeeHtlcTxCommitVersion
9✔
318

319
        case channel.ChanType.HasAnchors():
3✔
320
                single.Version = AnchorsCommitVersion
3✔
321

UNCOV
322
        case channel.ChanType.IsTweakless():
×
UNCOV
323
                single.Version = TweaklessCommitVersion
×
324

325
        default:
4✔
326
                single.Version = DefaultSingleVersion
4✔
327
        }
328

329
        // Include unsigned force-close transaction for the most recent channel
330
        // state as well as the data needed to produce the signature, given the
331
        // private key is provided separately.
332
        single.CloseTxInputs = buildCloseTxInputs(channel)
66✔
333

66✔
334
        return single
66✔
335
}
336

337
// errEmptyTapscriptRoot is returned by Serialize if field TapscriptRoot is
338
// empty, when it should be filled according to the channel version.
339
var errEmptyTapscriptRoot = errors.New("field TapscriptRoot is not filled")
340

341
// Serialize attempts to write out the serialized version of the target
342
// StaticChannelBackup into the passed io.Writer.
343
func (s *Single) Serialize(w io.Writer) error {
110✔
344
        // Check to ensure that we'll only attempt to serialize a version that
110✔
345
        // we're aware of.
110✔
346
        switch s.Version {
110✔
347
        case DefaultSingleVersion:
12✔
348
        case TweaklessCommitVersion:
4✔
349
        case AnchorsCommitVersion:
7✔
350
        case AnchorsZeroFeeHtlcTxCommitVersion:
8✔
351
        case ScriptEnforcedLeaseVersion:
19✔
352
        case SimpleTaprootVersion:
18✔
353
        case TapscriptRootVersion:
39✔
354
        default:
3✔
355
                return fmt.Errorf("unable to serialize w/ unknown "+
3✔
356
                        "version: %v", s.Version)
3✔
357
        }
358

359
        // If the sha chain root has specified a public key (which is
360
        // optional), then we'll encode it now.
361
        var shaChainPub [33]byte
107✔
362
        if s.ShaChainRootDesc.PubKey != nil {
214✔
363
                copy(
107✔
364
                        shaChainPub[:],
107✔
365
                        s.ShaChainRootDesc.PubKey.SerializeCompressed(),
107✔
366
                )
107✔
367
        }
107✔
368

369
        // First we gather the SCB as is into a temporary buffer so we can
370
        // determine the total length. Before we write out the serialized SCB,
371
        // we write the length which allows us to skip any Singles that we
372
        // don't know of when decoding a multi.
373
        var singleBytes bytes.Buffer
107✔
374
        if err := lnwire.WriteElements(
107✔
375
                &singleBytes,
107✔
376
                s.IsInitiator,
107✔
377
                s.ChainHash[:],
107✔
378
                s.FundingOutpoint,
107✔
379
                s.ShortChannelID,
107✔
380
                s.RemoteNodePub,
107✔
381
                s.Addresses,
107✔
382
                s.Capacity,
107✔
383

107✔
384
                s.LocalChanCfg.CsvDelay,
107✔
385

107✔
386
                // We only need to write out the KeyLocator portion of the
107✔
387
                // local channel config.
107✔
388
                uint32(s.LocalChanCfg.MultiSigKey.Family),
107✔
389
                s.LocalChanCfg.MultiSigKey.Index,
107✔
390
                uint32(s.LocalChanCfg.RevocationBasePoint.Family),
107✔
391
                s.LocalChanCfg.RevocationBasePoint.Index,
107✔
392
                uint32(s.LocalChanCfg.PaymentBasePoint.Family),
107✔
393
                s.LocalChanCfg.PaymentBasePoint.Index,
107✔
394
                uint32(s.LocalChanCfg.DelayBasePoint.Family),
107✔
395
                s.LocalChanCfg.DelayBasePoint.Index,
107✔
396
                uint32(s.LocalChanCfg.HtlcBasePoint.Family),
107✔
397
                s.LocalChanCfg.HtlcBasePoint.Index,
107✔
398

107✔
399
                s.RemoteChanCfg.CsvDelay,
107✔
400

107✔
401
                // We only need to write out the raw pubkey for the remote
107✔
402
                // channel config.
107✔
403
                s.RemoteChanCfg.MultiSigKey.PubKey,
107✔
404
                s.RemoteChanCfg.RevocationBasePoint.PubKey,
107✔
405
                s.RemoteChanCfg.PaymentBasePoint.PubKey,
107✔
406
                s.RemoteChanCfg.DelayBasePoint.PubKey,
107✔
407
                s.RemoteChanCfg.HtlcBasePoint.PubKey,
107✔
408

107✔
409
                shaChainPub[:],
107✔
410
                uint32(s.ShaChainRootDesc.KeyLocator.Family),
107✔
411
                s.ShaChainRootDesc.KeyLocator.Index,
107✔
412
        ); err != nil {
107✔
413
                return err
×
414
        }
×
415
        if s.Version == ScriptEnforcedLeaseVersion {
126✔
416
                err := lnwire.WriteElements(&singleBytes, s.LeaseExpiry)
19✔
417
                if err != nil {
19✔
418
                        return err
×
419
                }
×
420
        }
421

422
        // Encode version enum and hasCloseTx flag to version byte.
423
        version := s.Version.Encode(s.CloseTxInputs.IsSome())
107✔
424

107✔
425
        // Serialize CloseTxInputs if it is provided. Fill err if it fails.
107✔
426
        err := fn.MapOptionZ(s.CloseTxInputs, func(inputs CloseTxInputs) error {
168✔
427
                err := inputs.CommitTx.Serialize(&singleBytes)
61✔
428
                if err != nil {
61✔
429
                        return err
×
430
                }
×
431

432
                err = lnwire.WriteElements(
61✔
433
                        &singleBytes,
61✔
434
                        uint16(len(inputs.CommitSig)), inputs.CommitSig,
61✔
435
                )
61✔
436
                if err != nil {
61✔
437
                        return err
×
438
                }
×
439

440
                if !s.Version.IsTaproot() {
69✔
441
                        return nil
8✔
442
                }
8✔
443

444
                // Write fields needed for taproot channels.
445
                err = lnwire.WriteElements(
53✔
446
                        &singleBytes, inputs.CommitHeight,
53✔
447
                )
53✔
448
                if err != nil {
53✔
449
                        return err
×
450
                }
×
451

452
                if s.Version.HasTapscriptRoot() {
90✔
453
                        opt := inputs.TapscriptRoot
37✔
454
                        var tapscriptRoot chainhash.Hash
37✔
455
                        tapscriptRoot, err = opt.UnwrapOrErr(
37✔
456
                                errEmptyTapscriptRoot,
37✔
457
                        )
37✔
458
                        if err != nil {
38✔
459
                                return err
1✔
460
                        }
1✔
461

462
                        err = lnwire.WriteElements(
36✔
463
                                &singleBytes, tapscriptRoot[:],
36✔
464
                        )
36✔
465
                        if err != nil {
36✔
466
                                return err
×
467
                        }
×
468
                }
469

470
                return nil
52✔
471
        })
472
        if err != nil {
108✔
473
                return fmt.Errorf("failed to encode CloseTxInputs: %w", err)
1✔
474
        }
1✔
475

476
        // TODO(yy): remove the type assertion when we finished refactoring db
477
        // into using write buffer.
478
        buf, ok := w.(*bytes.Buffer)
106✔
479
        if !ok {
106✔
480
                return fmt.Errorf("expect io.Writer to be *bytes.Buffer")
×
481
        }
×
482

483
        return lnwire.WriteElements(
106✔
484
                buf,
106✔
485
                version,
106✔
486
                uint16(len(singleBytes.Bytes())),
106✔
487
                singleBytes.Bytes(),
106✔
488
        )
106✔
489
}
490

491
// PackToWriter is similar to the Serialize method, but takes the operation a
492
// step further by encryption the raw bytes of the static channel back up. For
493
// encryption we use the chacah20poly1305 AEAD cipher with a 24 byte nonce and
494
// 32-byte key size. We use a 24-byte nonce, as we can't ensure that we have a
495
// global counter to use as a sequence number for nonces, and want to ensure
496
// that we're able to decrypt these blobs without any additional context. We
497
// derive the key that we use for encryption via a SHA2 operation of the with
498
// the golden keychain.KeyFamilyBaseEncryption base encryption key.  We then
499
// take the serialized resulting shared secret point, and hash it using sha256
500
// to obtain the key that we'll use for encryption. When using the AEAD, we
501
// pass the nonce as associated data such that we'll be able to package the two
502
// together for storage. Before writing out the encrypted payload, we prepend
503
// the nonce to the final blob.
504
func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
48✔
505
        // First, we'll serialize the SCB (StaticChannelBackup) into a
48✔
506
        // temporary buffer so we can store it in a temporary place before we
48✔
507
        // go to encrypt the entire thing.
48✔
508
        var rawBytes bytes.Buffer
48✔
509
        if err := s.Serialize(&rawBytes); err != nil {
52✔
510
                return err
4✔
511
        }
4✔
512

513
        // Finally, we'll encrypt the raw serialized SCB (using the nonce as
514
        // associated data), and write out the ciphertext prepend with the
515
        // nonce that we used to the passed io.Reader.
516
        e, err := lnencrypt.KeyRingEncrypter(keyRing)
44✔
517
        if err != nil {
45✔
518
                return fmt.Errorf("unable to generate encrypt key %w", err)
1✔
519
        }
1✔
520
        return e.EncryptPayloadToWriter(rawBytes.Bytes(), w)
43✔
521
}
522

523
// readLocalKeyDesc reads a KeyDescriptor encoded within an unpacked Single.
524
// For local KeyDescs, we only write out the KeyLocator information as we can
525
// re-derive the pubkey from it.
526
func readLocalKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
750✔
527
        var keyDesc keychain.KeyDescriptor
750✔
528

750✔
529
        var keyFam uint32
750✔
530
        if err := lnwire.ReadElements(r, &keyFam); err != nil {
750✔
531
                return keyDesc, err
×
532
        }
×
533
        keyDesc.Family = keychain.KeyFamily(keyFam)
750✔
534

750✔
535
        if err := lnwire.ReadElements(r, &keyDesc.Index); err != nil {
750✔
536
                return keyDesc, err
×
537
        }
×
538

539
        return keyDesc, nil
750✔
540
}
541

542
// readRemoteKeyDesc reads a remote KeyDescriptor encoded within an unpacked
543
// Single. For remote KeyDescs, we write out only the PubKey since we don't
544
// actually have the KeyLocator data.
545
func readRemoteKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
750✔
546
        var (
750✔
547
                keyDesc keychain.KeyDescriptor
750✔
548
                pub     [33]byte
750✔
549
        )
750✔
550

750✔
551
        _, err := io.ReadFull(r, pub[:])
750✔
552
        if err != nil {
750✔
553
                return keychain.KeyDescriptor{}, err
×
554
        }
×
555

556
        keyDesc.PubKey, err = btcec.ParsePubKey(pub[:])
750✔
557
        if err != nil {
750✔
558
                return keychain.KeyDescriptor{}, err
×
559
        }
×
560

561
        return keyDesc, nil
750✔
562
}
563

564
// Deserialize attempts to read the raw plaintext serialized SCB from the
565
// passed io.Reader. If the method is successful, then the target
566
// StaticChannelBackup will be fully populated.
567
func (s *Single) Deserialize(r io.Reader) error {
162✔
568
        // First, we'll need to read the version of this single-back up so we
162✔
569
        // can know how to unpack each of the SCB.
162✔
570
        var version byte
162✔
571
        err := lnwire.ReadElements(r, &version)
162✔
572
        if err != nil {
162✔
573
                return err
×
574
        }
×
575

576
        // Decode version byte to version enum and hasCloseTx flag.
577
        var hasCloseTx bool
162✔
578
        s.Version, hasCloseTx = DecodeVersion(version)
162✔
579

162✔
580
        switch s.Version {
162✔
581
        case DefaultSingleVersion:
15✔
582
        case TweaklessCommitVersion:
2✔
583
        case AnchorsCommitVersion:
7✔
584
        case AnchorsZeroFeeHtlcTxCommitVersion:
12✔
585
        case ScriptEnforcedLeaseVersion:
24✔
586
        case SimpleTaprootVersion:
28✔
587
        case TapscriptRootVersion:
62✔
588
        default:
12✔
589
                return fmt.Errorf("unable to de-serialize w/ unknown "+
12✔
590
                        "version: %v", s.Version)
12✔
591
        }
592

593
        var length uint16
150✔
594
        if err := lnwire.ReadElements(r, &length); err != nil {
150✔
595
                return err
×
596
        }
×
597

598
        err = lnwire.ReadElements(
150✔
599
                r, &s.IsInitiator, s.ChainHash[:], &s.FundingOutpoint,
150✔
600
                &s.ShortChannelID, &s.RemoteNodePub, &s.Addresses, &s.Capacity,
150✔
601
        )
150✔
602
        if err != nil {
150✔
603
                return err
×
604
        }
×
605

606
        err = lnwire.ReadElements(r, &s.LocalChanCfg.CsvDelay)
150✔
607
        if err != nil {
150✔
608
                return err
×
609
        }
×
610
        s.LocalChanCfg.MultiSigKey, err = readLocalKeyDesc(r)
150✔
611
        if err != nil {
150✔
612
                return err
×
613
        }
×
614
        s.LocalChanCfg.RevocationBasePoint, err = readLocalKeyDesc(r)
150✔
615
        if err != nil {
150✔
616
                return err
×
617
        }
×
618
        s.LocalChanCfg.PaymentBasePoint, err = readLocalKeyDesc(r)
150✔
619
        if err != nil {
150✔
620
                return err
×
621
        }
×
622
        s.LocalChanCfg.DelayBasePoint, err = readLocalKeyDesc(r)
150✔
623
        if err != nil {
150✔
624
                return err
×
625
        }
×
626
        s.LocalChanCfg.HtlcBasePoint, err = readLocalKeyDesc(r)
150✔
627
        if err != nil {
150✔
628
                return err
×
629
        }
×
630

631
        err = lnwire.ReadElements(r, &s.RemoteChanCfg.CsvDelay)
150✔
632
        if err != nil {
150✔
633
                return err
×
634
        }
×
635
        s.RemoteChanCfg.MultiSigKey, err = readRemoteKeyDesc(r)
150✔
636
        if err != nil {
150✔
637
                return err
×
638
        }
×
639
        s.RemoteChanCfg.RevocationBasePoint, err = readRemoteKeyDesc(r)
150✔
640
        if err != nil {
150✔
641
                return err
×
642
        }
×
643
        s.RemoteChanCfg.PaymentBasePoint, err = readRemoteKeyDesc(r)
150✔
644
        if err != nil {
150✔
645
                return err
×
646
        }
×
647
        s.RemoteChanCfg.DelayBasePoint, err = readRemoteKeyDesc(r)
150✔
648
        if err != nil {
150✔
649
                return err
×
650
        }
×
651
        s.RemoteChanCfg.HtlcBasePoint, err = readRemoteKeyDesc(r)
150✔
652
        if err != nil {
150✔
653
                return err
×
654
        }
×
655

656
        // Finally, we'll parse out the ShaChainRootDesc.
657
        var (
150✔
658
                shaChainPub [33]byte
150✔
659
                zeroPub     [33]byte
150✔
660
        )
150✔
661
        if err := lnwire.ReadElements(r, shaChainPub[:]); err != nil {
150✔
662
                return err
×
663
        }
×
664

665
        // Since this field is optional, we'll check to see if the pubkey has
666
        // been specified or not.
667
        if !bytes.Equal(shaChainPub[:], zeroPub[:]) {
300✔
668
                s.ShaChainRootDesc.PubKey, err = btcec.ParsePubKey(
150✔
669
                        shaChainPub[:],
150✔
670
                )
150✔
671
                if err != nil {
150✔
672
                        return err
×
673
                }
×
674
        }
675

676
        var shaKeyFam uint32
150✔
677
        if err := lnwire.ReadElements(r, &shaKeyFam); err != nil {
150✔
678
                return err
×
679
        }
×
680
        s.ShaChainRootDesc.KeyLocator.Family = keychain.KeyFamily(shaKeyFam)
150✔
681
        err = lnwire.ReadElements(r, &s.ShaChainRootDesc.KeyLocator.Index)
150✔
682
        if err != nil {
150✔
683
                return err
×
684
        }
×
685

686
        if s.Version == ScriptEnforcedLeaseVersion {
174✔
687
                if err := lnwire.ReadElement(r, &s.LeaseExpiry); err != nil {
24✔
688
                        return err
×
689
                }
×
690
        }
691

692
        if !hasCloseTx {
208✔
693
                return nil
58✔
694
        }
58✔
695

696
        // Deserialize CloseTxInputs if it is present in serialized data.
697
        commitTx := &wire.MsgTx{}
92✔
698
        if err := commitTx.Deserialize(r); err != nil {
92✔
699
                return err
×
700
        }
×
701

702
        var commitSigLen uint16
92✔
703
        if err := lnwire.ReadElement(r, &commitSigLen); err != nil {
92✔
704
                return err
×
705
        }
×
706
        commitSig := make([]byte, commitSigLen)
92✔
707
        if err := lnwire.ReadElement(r, commitSig); err != nil {
92✔
708
                return err
×
709
        }
×
710

711
        var commitHeight uint64
92✔
712
        if s.Version.IsTaproot() {
180✔
713
                err := lnwire.ReadElement(r, &commitHeight)
88✔
714
                if err != nil {
88✔
715
                        return err
×
716
                }
×
717
        }
718

719
        tapscriptRootOpt := fn.None[chainhash.Hash]()
92✔
720
        if s.Version.HasTapscriptRoot() {
153✔
721
                var tapscriptRoot chainhash.Hash
61✔
722
                err := lnwire.ReadElement(r, tapscriptRoot[:])
61✔
723
                if err != nil {
61✔
724
                        return err
×
725
                }
×
726
                tapscriptRootOpt = fn.Some(tapscriptRoot)
61✔
727
        }
728

729
        s.CloseTxInputs = fn.Some(CloseTxInputs{
92✔
730
                CommitTx:      commitTx,
92✔
731
                CommitSig:     commitSig,
92✔
732
                CommitHeight:  commitHeight,
92✔
733
                TapscriptRoot: tapscriptRootOpt,
92✔
734
        })
92✔
735

92✔
736
        return nil
92✔
737
}
738

739
// UnpackFromReader is similar to Deserialize method, but it expects the passed
740
// io.Reader to contain an encrypt SCB. Refer to the SerializeAndEncrypt method
741
// for details w.r.t the encryption scheme used. If we're unable to decrypt the
742
// payload for whatever reason (wrong key, wrong nonce, etc), then this method
743
// will return an error.
744
func (s *Single) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
65✔
745
        e, err := lnencrypt.KeyRingEncrypter(keyRing)
65✔
746
        if err != nil {
66✔
747
                return fmt.Errorf("unable to generate key decrypter %w", err)
1✔
748
        }
1✔
749
        plaintext, err := e.DecryptPayloadFromReader(r)
64✔
750
        if err != nil {
65✔
751
                return err
1✔
752
        }
1✔
753

754
        // Finally, we'll pack the bytes into a reader to we can deserialize
755
        // the plaintext bytes of the SCB.
756
        backupReader := bytes.NewReader(plaintext)
63✔
757
        return s.Deserialize(backupReader)
63✔
758
}
759

760
// PackStaticChanBackups accepts a set of existing open channels, and a
761
// keychain.KeyRing, and returns a map of outpoints to the serialized+encrypted
762
// static channel backups. The passed keyRing should be backed by the users
763
// root HD seed in order to ensure full determinism.
764
func PackStaticChanBackups(backups []Single,
765
        keyRing keychain.KeyRing) (map[wire.OutPoint][]byte, error) {
2✔
766

2✔
767
        packedBackups := make(map[wire.OutPoint][]byte)
2✔
768
        for _, chanBackup := range backups {
13✔
769
                chanPoint := chanBackup.FundingOutpoint
11✔
770

11✔
771
                var b bytes.Buffer
11✔
772
                err := chanBackup.PackToWriter(&b, keyRing)
11✔
773
                if err != nil {
12✔
774
                        return nil, fmt.Errorf("unable to pack chan backup "+
1✔
775
                                "for %v: %v", chanPoint, err)
1✔
776
                }
1✔
777

778
                packedBackups[chanPoint] = b.Bytes()
10✔
779
        }
780

781
        return packedBackups, nil
1✔
782
}
783

784
// PackedSingles represents a series of fully packed SCBs. This may be the
785
// combination of a series of individual SCBs in order to batch their
786
// unpacking.
787
type PackedSingles [][]byte
788

789
// Unpack attempts to decrypt the passed set of encrypted SCBs and deserialize
790
// each one into a new SCB struct. The passed keyRing should be backed by the
791
// same HD seed as was used to encrypt the set of backups in the first place.
792
// If we're unable to decrypt any of the back ups, then we'll return an error.
793
func (p PackedSingles) Unpack(keyRing keychain.KeyRing) ([]Single, error) {
6✔
794

6✔
795
        backups := make([]Single, len(p))
6✔
796
        for i, encryptedBackup := range p {
48✔
797
                var backup Single
42✔
798

42✔
799
                backupReader := bytes.NewReader(encryptedBackup)
42✔
800
                err := backup.UnpackFromReader(backupReader, keyRing)
42✔
801
                if err != nil {
44✔
802
                        return nil, err
2✔
803
                }
2✔
804

805
                backups[i] = backup
40✔
806
        }
807

808
        return backups, nil
4✔
809
}
810

811
// TODO(roasbeef): make codec package?
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