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

lightningnetwork / lnd / 16027444140

02 Jul 2025 02:08PM UTC coverage: 57.777% (-0.03%) from 57.803%
16027444140

Pull #10018

github

web-flow
Merge 6574a2b47 into 1d2e5472b
Pull Request #10018: Refactor link's long methods

429 of 808 new or added lines in 1 file covered. (53.09%)

60 existing lines in 9 files now uncovered.

98478 of 170444 relevant lines covered (57.78%)

1.79 hits per line

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

76.43
/watchtower/blob/justice_kit.go
1
package blob
2

3
import (
4
        "bytes"
5
        "io"
6

7
        "github.com/btcsuite/btcd/btcec/v2"
8
        "github.com/btcsuite/btcd/btcec/v2/schnorr"
9
        "github.com/btcsuite/btcd/chaincfg/chainhash"
10
        "github.com/btcsuite/btcd/txscript"
11
        "github.com/btcsuite/btcd/wire"
12
        secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
13
        "github.com/lightningnetwork/lnd/fn/v2"
14
        "github.com/lightningnetwork/lnd/input"
15
        "github.com/lightningnetwork/lnd/lnwallet"
16
        "github.com/lightningnetwork/lnd/lnwire"
17
)
18

19
// JusticeKit is an interface that describes lé Blob of Justice. An
20
// implementation of the JusticeKit contains information required to construct
21
// a justice transaction, that sweeps a remote party's revoked commitment
22
// transaction. It supports encryption and decryption using chacha20poly1305,
23
// allowing the client to encrypt the contents of the blob, and for a
24
// watchtower to later decrypt if action must be taken.
25
type JusticeKit interface {
26
        // ToLocalOutputSpendInfo returns the info required to send the to-local
27
        // output. It returns the output pub key script and the witness required
28
        // to spend the output.
29
        ToLocalOutputSpendInfo() (*txscript.PkScript, wire.TxWitness, error)
30

31
        // ToRemoteOutputSpendInfo returns the info required to send the
32
        // to-remote output. It returns the output pub key script, the witness
33
        // required to spend the output and the sequence to apply.
34
        ToRemoteOutputSpendInfo() (*txscript.PkScript, wire.TxWitness, uint32,
35
                error)
36

37
        // HasCommitToRemoteOutput returns true if the kit does include the
38
        // information required to sweep the to-remote output.
39
        HasCommitToRemoteOutput() bool
40

41
        // AddToLocalSig adds the to-local signature to the kit.
42
        AddToLocalSig(sig lnwire.Sig)
43

44
        // AddToRemoteSig adds the to-remote signature to the kit.
45
        AddToRemoteSig(sig lnwire.Sig)
46

47
        // SweepAddress returns the sweep address to be used on the justice tx
48
        // output.
49
        SweepAddress() []byte
50

51
        // PlainTextSize is the size of the encoded-but-unencrypted blob in
52
        // bytes.
53
        PlainTextSize() int
54

55
        encode(w io.Writer) error
56
        decode(r io.Reader) error
57
}
58

59
// legacyJusticeKit is an implementation of the JusticeKit interface which can
60
// be used for backing up commitments of legacy (pre-anchor) channels.
61
type legacyJusticeKit struct {
62
        justiceKitPacketV0
63
}
64

65
// A compile-time check to ensure that legacyJusticeKit implements the
66
// JusticeKit interface.
67
var _ JusticeKit = (*legacyJusticeKit)(nil)
68

69
// newLegacyJusticeKit constructs a new legacyJusticeKit.
70
func newLegacyJusticeKit(sweepScript []byte,
71
        breachInfo *lnwallet.BreachRetribution,
72
        withToRemote bool) *legacyJusticeKit {
3✔
73

3✔
74
        keyRing := breachInfo.KeyRing
3✔
75

3✔
76
        packet := justiceKitPacketV0{
3✔
77
                sweepAddress:     sweepScript,
3✔
78
                revocationPubKey: toBlobPubKey(keyRing.RevocationKey),
3✔
79
                localDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
3✔
80
                csvDelay:         breachInfo.RemoteDelay,
3✔
81
        }
3✔
82

3✔
83
        if withToRemote {
6✔
84
                packet.commitToRemotePubKey = toBlobPubKey(
3✔
85
                        keyRing.ToRemoteKey,
3✔
86
                )
3✔
87
        }
3✔
88

89
        return &legacyJusticeKit{packet}
3✔
90
}
91

92
// ToLocalOutputSpendInfo returns the info required to send the to-local output.
93
// It returns the output pub key script and the witness required to spend the
94
// output.
95
//
96
// NOTE: This is part of the JusticeKit interface.
97
func (l *legacyJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript,
98
        wire.TxWitness, error) {
3✔
99

3✔
100
        revocationPubKey, err := btcec.ParsePubKey(l.revocationPubKey[:])
3✔
101
        if err != nil {
3✔
102
                return nil, nil, err
×
103
        }
×
104

105
        localDelayedPubKey, err := btcec.ParsePubKey(l.localDelayPubKey[:])
3✔
106
        if err != nil {
3✔
107
                return nil, nil, err
×
108
        }
×
109

110
        script, err := input.CommitScriptToSelf(
3✔
111
                l.csvDelay, localDelayedPubKey, revocationPubKey,
3✔
112
        )
3✔
113
        if err != nil {
3✔
114
                return nil, nil, err
×
115
        }
×
116

117
        scriptPubKey, err := input.WitnessScriptHash(script)
3✔
118
        if err != nil {
3✔
119
                return nil, nil, err
×
120
        }
×
121

122
        toLocalSig, err := l.commitToLocalSig.ToSignature()
3✔
123
        if err != nil {
3✔
124
                return nil, nil, err
×
125
        }
×
126

127
        witness := make(wire.TxWitness, 3)
3✔
128
        witness[0] = append(toLocalSig.Serialize(), byte(txscript.SigHashAll))
3✔
129
        witness[1] = []byte{1}
3✔
130
        witness[2] = script
3✔
131

3✔
132
        pkScript, err := txscript.ParsePkScript(scriptPubKey)
3✔
133
        if err != nil {
3✔
134
                return nil, nil, err
×
135
        }
×
136

137
        return &pkScript, witness, nil
3✔
138
}
139

140
// ToRemoteOutputSpendInfo returns the info required to spend the to-remote
141
// output. It returns the output pub key script, the witness required to spend
142
// the output and the sequence to apply.
143
//
144
// NOTE: This is part of the JusticeKit interface.
145
func (l *legacyJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
146
        wire.TxWitness, uint32, error) {
3✔
147

3✔
148
        if !btcec.IsCompressedPubKey(l.commitToRemotePubKey[:]) {
3✔
149
                return nil, nil, 0, ErrNoCommitToRemoteOutput
×
150
        }
×
151

152
        toRemoteScript := l.commitToRemotePubKey[:]
3✔
153

3✔
154
        // Since the to-remote witness script should just be a regular p2wkh
3✔
155
        // output, we'll parse it to retrieve the public key.
3✔
156
        toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript)
3✔
157
        if err != nil {
3✔
158
                return nil, nil, 0, err
×
159
        }
×
160

161
        // Compute the witness script hash from the to-remote pubkey, which will
162
        // be used to locate the output on the breach commitment transaction.
163
        toRemoteScriptHash, err := input.CommitScriptUnencumbered(
3✔
164
                toRemotePubKey,
3✔
165
        )
3✔
166
        if err != nil {
3✔
167
                return nil, nil, 0, err
×
168
        }
×
169

170
        toRemoteSig, err := l.commitToRemoteSig.ToSignature()
3✔
171
        if err != nil {
3✔
172
                return nil, nil, 0, err
×
173
        }
×
174

175
        witness := make(wire.TxWitness, 2)
3✔
176
        witness[0] = append(toRemoteSig.Serialize(), byte(txscript.SigHashAll))
3✔
177
        witness[1] = toRemoteScript
3✔
178

3✔
179
        pkScript, err := txscript.ParsePkScript(toRemoteScriptHash)
3✔
180
        if err != nil {
3✔
181
                return nil, nil, 0, err
×
182
        }
×
183

184
        return &pkScript, witness, 0, nil
3✔
185
}
186

187
// HasCommitToRemoteOutput returns true if the blob contains a to-remote p2wkh
188
// pubkey.
189
//
190
// NOTE: This is part of the JusticeKit interface.
191
func (l *legacyJusticeKit) HasCommitToRemoteOutput() bool {
3✔
192
        return btcec.IsCompressedPubKey(l.commitToRemotePubKey[:])
3✔
193
}
3✔
194

195
// SweepAddress returns the sweep address to be used on the justice tx
196
// output.
197
//
198
// NOTE: This is part of the JusticeKit interface.
199
func (l *legacyJusticeKit) SweepAddress() []byte {
3✔
200
        return l.sweepAddress
3✔
201
}
3✔
202

203
// AddToLocalSig adds the to-local signature to the kit.
204
//
205
// NOTE: This is part of the JusticeKit interface.
206
func (l *legacyJusticeKit) AddToLocalSig(sig lnwire.Sig) {
3✔
207
        l.commitToLocalSig = sig
3✔
208
}
3✔
209

210
// AddToRemoteSig adds the to-remote signature to the kit.
211
//
212
// NOTE: This is part of the JusticeKit interface.
213
func (l *legacyJusticeKit) AddToRemoteSig(sig lnwire.Sig) {
3✔
214
        l.commitToRemoteSig = sig
3✔
215
}
3✔
216

217
// PlainTextSize is the size of the encoded-but-unencrypted blob in
218
// bytes.
219
//
220
// NOTE: This is part of the JusticeKit interface.
221
func (l *legacyJusticeKit) PlainTextSize() int {
3✔
222
        return V0PlaintextSize
3✔
223
}
3✔
224

225
// anchorJusticeKit is an implementation of the JusticeKit interface which can
226
// be used for backing up commitments of anchor channels. It inherits most of
227
// the methods from the legacyJusticeKit and overrides the
228
// ToRemoteOutputSpendInfo method since the to-remote output of an anchor
229
// output is a P2WSH instead of the P2WPKH used by the legacy channels.
230
type anchorJusticeKit struct {
231
        legacyJusticeKit
232
}
233

234
// A compile-time check to ensure that legacyJusticeKit implements the
235
// JusticeKit interface.
236
var _ JusticeKit = (*anchorJusticeKit)(nil)
237

238
// newAnchorJusticeKit constructs a new anchorJusticeKit.
239
func newAnchorJusticeKit(sweepScript []byte,
240
        breachInfo *lnwallet.BreachRetribution,
241
        withToRemote bool) *anchorJusticeKit {
3✔
242

3✔
243
        legacyKit := newLegacyJusticeKit(sweepScript, breachInfo, withToRemote)
3✔
244

3✔
245
        return &anchorJusticeKit{
3✔
246
                legacyJusticeKit: *legacyKit,
3✔
247
        }
3✔
248
}
3✔
249

250
// ToRemoteOutputSpendInfo returns the info required to send the to-remote
251
// output. It returns the output pub key script, the witness required to spend
252
// the output and the sequence to apply.
253
//
254
// NOTE: This is part of the JusticeKit interface.
255
func (a *anchorJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
256
        wire.TxWitness, uint32, error) {
3✔
257

3✔
258
        if !btcec.IsCompressedPubKey(a.commitToRemotePubKey[:]) {
3✔
259
                return nil, nil, 0, ErrNoCommitToRemoteOutput
×
260
        }
×
261

262
        pk, err := btcec.ParsePubKey(a.commitToRemotePubKey[:])
3✔
263
        if err != nil {
3✔
264
                return nil, nil, 0, err
×
265
        }
×
266

267
        toRemoteScript, err := input.CommitScriptToRemoteConfirmed(pk)
3✔
268
        if err != nil {
3✔
269
                return nil, nil, 0, err
×
270
        }
×
271

272
        toRemoteScriptHash, err := input.WitnessScriptHash(toRemoteScript)
3✔
273
        if err != nil {
3✔
274
                return nil, nil, 0, err
×
275
        }
×
276

277
        toRemoteSig, err := a.commitToRemoteSig.ToSignature()
3✔
278
        if err != nil {
3✔
279
                return nil, nil, 0, err
×
280
        }
×
281

282
        witness := make([][]byte, 2)
3✔
283
        witness[0] = append(toRemoteSig.Serialize(), byte(txscript.SigHashAll))
3✔
284
        witness[1] = toRemoteScript
3✔
285

3✔
286
        pkScript, err := txscript.ParsePkScript(toRemoteScriptHash)
3✔
287
        if err != nil {
3✔
288
                return nil, nil, 0, err
×
289
        }
×
290

291
        return &pkScript, witness, 1, nil
3✔
292
}
293

294
// taprootJusticeKit is an implementation of the JusticeKit interface which can
295
// be used for backing up commitments of taproot channels.
296
type taprootJusticeKit struct {
297
        justiceKitPacketV1
298
}
299

300
// A compile-time check to ensure that taprootJusticeKit implements the
301
// JusticeKit interface.
302
var _ JusticeKit = (*taprootJusticeKit)(nil)
303

304
// newTaprootJusticeKit constructs a new taprootJusticeKit.
305
func newTaprootJusticeKit(sweepScript []byte,
306
        breachInfo *lnwallet.BreachRetribution,
307
        withToRemote bool) (*taprootJusticeKit, error) {
3✔
308

3✔
309
        keyRing := breachInfo.KeyRing
3✔
310

3✔
311
        // TODO(roasbeef): aux leaf tower updates needed
3✔
312

3✔
313
        tree, err := input.NewLocalCommitScriptTree(
3✔
314
                breachInfo.RemoteDelay, keyRing.ToLocalKey,
3✔
315
                keyRing.RevocationKey, fn.None[txscript.TapLeaf](),
3✔
316
        )
3✔
317
        if err != nil {
3✔
318
                return nil, err
×
319
        }
×
320

321
        packet := justiceKitPacketV1{
3✔
322
                sweepAddress: sweepScript,
3✔
323
                revocationPubKey: toBlobSchnorrPubKey(
3✔
324
                        keyRing.RevocationKey,
3✔
325
                ),
3✔
326
                localDelayPubKey: toBlobSchnorrPubKey(keyRing.ToLocalKey),
3✔
327
                delayScriptHash:  tree.SettleLeaf.TapHash(),
3✔
328
        }
3✔
329

3✔
330
        if withToRemote {
6✔
331
                packet.commitToRemotePubKey = toBlobPubKey(keyRing.ToRemoteKey)
3✔
332
        }
3✔
333

334
        return &taprootJusticeKit{packet}, nil
3✔
335
}
336

337
// ToLocalOutputSpendInfo returns the info required to send the to-local
338
// output. It returns the output pubkey script and the witness required
339
// to spend the output.
340
//
341
// NOTE: This is part of the JusticeKit interface.
342
func (t *taprootJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript,
343
        wire.TxWitness, error) {
3✔
344

3✔
345
        revocationPubKey, err := schnorr.ParsePubKey(t.revocationPubKey[:])
3✔
346
        if err != nil {
3✔
347
                return nil, nil, err
×
348
        }
×
349

350
        localDelayedPubKey, err := schnorr.ParsePubKey(t.localDelayPubKey[:])
3✔
351
        if err != nil {
3✔
352
                return nil, nil, err
×
353
        }
×
354

355
        revokeScript, err := input.TaprootLocalCommitRevokeScript(
3✔
356
                localDelayedPubKey, revocationPubKey,
3✔
357
        )
3✔
358
        if err != nil {
3✔
359
                return nil, nil, err
×
360
        }
×
361

362
        revokeLeaf := txscript.NewBaseTapLeaf(revokeScript)
3✔
363
        revokeLeafHash := revokeLeaf.TapHash()
3✔
364
        rootHash := tapBranchHash(revokeLeafHash[:], t.delayScriptHash[:])
3✔
365

3✔
366
        outputKey := txscript.ComputeTaprootOutputKey(
3✔
367
                &input.TaprootNUMSKey, rootHash[:],
3✔
368
        )
3✔
369

3✔
370
        scriptPk, err := input.PayToTaprootScript(outputKey)
3✔
371
        if err != nil {
3✔
372
                return nil, nil, err
×
373
        }
×
374

375
        ctrlBlock := txscript.ControlBlock{
3✔
376
                InternalKey:     &input.TaprootNUMSKey,
3✔
377
                OutputKeyYIsOdd: isOddPub(outputKey),
3✔
378
                LeafVersion:     revokeLeaf.LeafVersion,
3✔
379
                InclusionProof:  t.delayScriptHash[:],
3✔
380
        }
3✔
381

3✔
382
        ctrlBytes, err := ctrlBlock.ToBytes()
3✔
383
        if err != nil {
3✔
384
                return nil, nil, err
×
385
        }
×
386

387
        toLocalSig, err := t.commitToLocalSig.ToSignature()
3✔
388
        if err != nil {
3✔
389
                return nil, nil, err
×
390
        }
×
391

392
        witness := make([][]byte, 3)
3✔
393
        witness[0] = toLocalSig.Serialize()
3✔
394
        witness[1] = revokeScript
3✔
395
        witness[2] = ctrlBytes
3✔
396

3✔
397
        pkScript, err := txscript.ParsePkScript(scriptPk)
3✔
398
        if err != nil {
3✔
399
                return nil, nil, err
×
400
        }
×
401

402
        return &pkScript, witness, nil
3✔
403
}
404

405
// ToRemoteOutputSpendInfo returns the info required to send the to-remote
406
// output. It returns the output pubkey script, the witness required to spend
407
// the output and the sequence to apply.
408
//
409
// NOTE: This is part of the JusticeKit interface.
410
func (t *taprootJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
411
        wire.TxWitness, uint32, error) {
3✔
412

3✔
413
        if len(t.commitToRemotePubKey[:]) == 0 {
3✔
414
                return nil, nil, 0, ErrNoCommitToRemoteOutput
×
415
        }
×
416

417
        toRemotePk, err := btcec.ParsePubKey(t.commitToRemotePubKey[:])
3✔
418
        if err != nil {
3✔
419
                return nil, nil, 0, err
×
420
        }
×
421

422
        scriptTree, err := input.NewRemoteCommitScriptTree(
3✔
423
                toRemotePk, fn.None[txscript.TapLeaf](),
3✔
424
        )
3✔
425
        if err != nil {
3✔
426
                return nil, nil, 0, err
×
427
        }
×
428

429
        script, err := input.PayToTaprootScript(scriptTree.TaprootKey)
3✔
430
        if err != nil {
3✔
431
                return nil, nil, 0, err
×
432
        }
×
433

434
        settleControlBlock := input.MakeTaprootCtrlBlock(
3✔
435
                scriptTree.SettleLeaf.Script, &input.TaprootNUMSKey,
3✔
436
                scriptTree.TapscriptTree,
3✔
437
        )
3✔
438

3✔
439
        ctrl, err := settleControlBlock.ToBytes()
3✔
440
        if err != nil {
3✔
441
                return nil, nil, 0, err
×
442
        }
×
443

444
        toRemoteSig, err := t.commitToRemoteSig.ToSignature()
3✔
445
        if err != nil {
3✔
446
                return nil, nil, 0, err
×
447
        }
×
448

449
        witness := make([][]byte, 3)
3✔
450
        witness[0] = toRemoteSig.Serialize()
3✔
451
        witness[1] = scriptTree.SettleLeaf.Script
3✔
452
        witness[2] = ctrl
3✔
453

3✔
454
        pkScript, err := txscript.ParsePkScript(script)
3✔
455
        if err != nil {
3✔
456
                return nil, nil, 0, err
×
457
        }
×
458

459
        return &pkScript, witness, 1, nil
3✔
460
}
461

462
// HasCommitToRemoteOutput returns true if the blob contains a to-remote pubkey.
463
//
464
// NOTE: This is part of the JusticeKit interface.
465
func (t *taprootJusticeKit) HasCommitToRemoteOutput() bool {
3✔
466
        return btcec.IsCompressedPubKey(t.commitToRemotePubKey[:])
3✔
467
}
3✔
468

469
// AddToLocalSig adds the to-local signature to the kit.
470
//
471
// NOTE: This is part of the JusticeKit interface.
472
func (t *taprootJusticeKit) AddToLocalSig(sig lnwire.Sig) {
3✔
473
        t.commitToLocalSig = sig
3✔
474
}
3✔
475

476
// AddToRemoteSig adds the to-remote signature to the kit.
477
//
478
// NOTE: This is part of the JusticeKit interface.
479
func (t *taprootJusticeKit) AddToRemoteSig(sig lnwire.Sig) {
3✔
480
        t.commitToRemoteSig = sig
3✔
481
}
3✔
482

483
// SweepAddress returns the sweep address to be used on the justice tx output.
484
//
485
// NOTE: This is part of the JusticeKit interface.
486
func (t *taprootJusticeKit) SweepAddress() []byte {
3✔
487
        return t.sweepAddress
3✔
488
}
3✔
489

490
// PlainTextSize is the size of the encoded-but-unencrypted blob in bytes.
491
//
492
// NOTE: This is part of the JusticeKit interface.
493
func (t *taprootJusticeKit) PlainTextSize() int {
3✔
494
        return V1PlaintextSize
3✔
495
}
3✔
496

497
func tapBranchHash(l, r []byte) chainhash.Hash {
3✔
498
        if bytes.Compare(l, r) > 0 {
3✔
UNCOV
499
                l, r = r, l
×
UNCOV
500
        }
×
501

502
        return *chainhash.TaggedHash(chainhash.TagTapBranch, l, r)
3✔
503
}
504

505
func isOddPub(key *btcec.PublicKey) bool {
3✔
506
        return key.SerializeCompressed()[0] == secp.PubKeyFormatCompressedOdd
3✔
507
}
3✔
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