lnd/watchtower/blob/justice_kit.go
Elle Mouton 154e9fafec
watchtower: convert JusticeKit to interface
In this commit, we convert the `JusticeKit` struct to an interface.
Then, we add two implementations of that interface:
1) The `legacyJusticeKit` which implements all the methods of
   `JusticeKit`
2) The `anchorJusticKit` which wraps the `legacyJusticeKit` and just
   re-implements the `ToRemoteOutputSpendInfo` method since.
2024-01-04 14:37:42 +02:00

289 lines
8.3 KiB
Go

package blob
import (
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
// JusticeKit is an interface that describes lé Blob of Justice. An
// implementation of the JusticeKit contains information required to construct
// a justice transaction, that sweeps a remote party's revoked commitment
// transaction. It supports encryption and decryption using chacha20poly1305,
// allowing the client to encrypt the contents of the blob, and for a
// watchtower to later decrypt if action must be taken.
type JusticeKit interface {
// ToLocalOutputSpendInfo returns the info required to send the to-local
// output. It returns the output pub key script and the witness required
// to spend the output.
ToLocalOutputSpendInfo() (*txscript.PkScript, wire.TxWitness, error)
// ToRemoteOutputSpendInfo returns the info required to send the
// to-remote output. It returns the output pub key script, the witness
// required to spend the output and the sequence to apply.
ToRemoteOutputSpendInfo() (*txscript.PkScript, wire.TxWitness, uint32,
error)
// HasCommitToRemoteOutput returns true if the kit does include the
// information required to sweep the to-remote output.
HasCommitToRemoteOutput() bool
// AddToLocalSig adds the to-local signature to the kit.
AddToLocalSig(sig lnwire.Sig)
// AddToRemoteSig adds the to-remote signature to the kit.
AddToRemoteSig(sig lnwire.Sig)
// SweepAddress returns the sweep address to be used on the justice tx
// output.
SweepAddress() []byte
// PlainTextSize is the size of the encoded-but-unencrypted blob in
// bytes.
PlainTextSize() int
encode(w io.Writer) error
decode(r io.Reader) error
}
// legacyJusticeKit is an implementation of the JusticeKit interface which can
// be used for backing up commitments of legacy (pre-anchor) channels.
type legacyJusticeKit struct {
justiceKitPacketV0
}
// A compile-time check to ensure that legacyJusticeKit implements the
// JusticeKit interface.
var _ JusticeKit = (*legacyJusticeKit)(nil)
// newLegacyJusticeKit constructs a new legacyJusticeKit.
func newLegacyJusticeKit(sweepScript []byte,
breachInfo *lnwallet.BreachRetribution,
withToRemote bool) *legacyJusticeKit {
keyRing := breachInfo.KeyRing
packet := justiceKitPacketV0{
sweepAddress: sweepScript,
revocationPubKey: toBlobPubKey(keyRing.RevocationKey),
localDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
csvDelay: breachInfo.RemoteDelay,
commitToRemotePubKey: pubKey{},
}
if withToRemote {
packet.commitToRemotePubKey = toBlobPubKey(
keyRing.ToRemoteKey,
)
}
return &legacyJusticeKit{packet}
}
// ToLocalOutputSpendInfo returns the info required to send the to-local output.
// It returns the output pub key script and the witness required to spend the
// output.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, error) {
revocationPubKey, err := btcec.ParsePubKey(l.revocationPubKey[:])
if err != nil {
return nil, nil, err
}
localDelayedPubKey, err := btcec.ParsePubKey(l.localDelayPubKey[:])
if err != nil {
return nil, nil, err
}
script, err := input.CommitScriptToSelf(
l.csvDelay, localDelayedPubKey, revocationPubKey,
)
if err != nil {
return nil, nil, err
}
scriptPubKey, err := input.WitnessScriptHash(script)
if err != nil {
return nil, nil, err
}
toLocalSig, err := l.commitToLocalSig.ToSignature()
if err != nil {
return nil, nil, err
}
witness := make(wire.TxWitness, 3)
witness[0] = append(toLocalSig.Serialize(), byte(txscript.SigHashAll))
witness[1] = []byte{1}
witness[2] = script
pkScript, err := txscript.ParsePkScript(scriptPubKey)
if err != nil {
return nil, nil, err
}
return &pkScript, witness, nil
}
// ToRemoteOutputSpendInfo returns the info required to spend the to-remote
// output. It returns the output pub key script, the witness required to spend
// the output and the sequence to apply.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, uint32, error) {
if !btcec.IsCompressedPubKey(l.commitToRemotePubKey[:]) {
return nil, nil, 0, ErrNoCommitToRemoteOutput
}
toRemoteScript := l.commitToRemotePubKey[:]
// Since the to-remote witness script should just be a regular p2wkh
// output, we'll parse it to retrieve the public key.
toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript)
if err != nil {
return nil, nil, 0, err
}
// Compute the witness script hash from the to-remote pubkey, which will
// be used to locate the output on the breach commitment transaction.
toRemoteScriptHash, err := input.CommitScriptUnencumbered(
toRemotePubKey,
)
if err != nil {
return nil, nil, 0, err
}
toRemoteSig, err := l.commitToRemoteSig.ToSignature()
if err != nil {
return nil, nil, 0, err
}
witness := make(wire.TxWitness, 2)
witness[0] = append(toRemoteSig.Serialize(), byte(txscript.SigHashAll))
witness[1] = toRemoteScript
pkScript, err := txscript.ParsePkScript(toRemoteScriptHash)
if err != nil {
return nil, nil, 0, err
}
return &pkScript, witness, 0, nil
}
// HasCommitToRemoteOutput returns true if the blob contains a to-remote p2wkh
// pubkey.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) HasCommitToRemoteOutput() bool {
return btcec.IsCompressedPubKey(l.commitToRemotePubKey[:])
}
// SweepAddress returns the sweep address to be used on the justice tx
// output.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) SweepAddress() []byte {
return l.sweepAddress
}
// AddToLocalSig adds the to-local signature to the kit.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) AddToLocalSig(sig lnwire.Sig) {
l.commitToLocalSig = sig
}
// AddToRemoteSig adds the to-remote signature to the kit.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) AddToRemoteSig(sig lnwire.Sig) {
l.commitToRemoteSig = sig
}
// PlainTextSize is the size of the encoded-but-unencrypted blob in
// bytes.
//
// NOTE: This is part of the JusticeKit interface.
func (l *legacyJusticeKit) PlainTextSize() int {
return V0PlaintextSize
}
// anchorJusticeKit is an implementation of the JusticeKit interface which can
// be used for backing up commitments of anchor channels. It inherits most of
// the methods from the legacyJusticeKit and overrides the
// ToRemoteOutputSpendInfo method since the to-remote output of an anchor
// output is a P2WSH instead of the P2WPKH used by the legacy channels.
type anchorJusticeKit struct {
legacyJusticeKit
}
// A compile-time check to ensure that legacyJusticeKit implements the
// JusticeKit interface.
var _ JusticeKit = (*anchorJusticeKit)(nil)
// newAnchorJusticeKit constructs a new anchorJusticeKit.
func newAnchorJusticeKit(sweepScript []byte,
breachInfo *lnwallet.BreachRetribution,
withToRemote bool) *anchorJusticeKit {
legacyKit := newLegacyJusticeKit(sweepScript, breachInfo, withToRemote)
return &anchorJusticeKit{
legacyJusticeKit: *legacyKit,
}
}
// ToRemoteOutputSpendInfo returns the info required to send the to-remote
// output. It returns the output pub key script, the witness required to spend
// the output and the sequence to apply.
//
// NOTE: This is part of the JusticeKit interface.
func (a *anchorJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, uint32, error) {
if !btcec.IsCompressedPubKey(a.commitToRemotePubKey[:]) {
return nil, nil, 0, ErrNoCommitToRemoteOutput
}
pk, err := btcec.ParsePubKey(a.commitToRemotePubKey[:])
if err != nil {
return nil, nil, 0, err
}
toRemoteScript, err := input.CommitScriptToRemoteConfirmed(pk)
if err != nil {
return nil, nil, 0, err
}
toRemoteScriptHash, err := input.WitnessScriptHash(toRemoteScript)
if err != nil {
return nil, nil, 0, err
}
toRemoteSig, err := a.commitToRemoteSig.ToSignature()
if err != nil {
return nil, nil, 0, err
}
witness := make([][]byte, 2)
witness[0] = append(toRemoteSig.Serialize(), byte(txscript.SigHashAll))
witness[1] = toRemoteScript
pkScript, err := txscript.ParsePkScript(toRemoteScriptHash)
if err != nil {
return nil, nil, 0, err
}
return &pkScript, witness, 1, nil
}