mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-01 10:11:11 +02:00
watchtower: introduce CommitmentType
In this commit a new enum, CommitmentType, is introduced and initially there are 3 CommitmentTypes: Legacy, LegacyTweakless and Anchor. Then, various methods are added to `CommitmentType`. This allows us to remove a bunch of "if-else" chains from the `wtclient` and `lookout` code. This will also make things easier to extend when a new commitment type (like Taproot) is added.
This commit is contained in:
174
watchtower/blob/commitments.go
Normal file
174
watchtower/blob/commitments.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package blob
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// CommitmentType characterises the various properties of the breach commitment
|
||||
// transaction.
|
||||
type CommitmentType uint8
|
||||
|
||||
const (
|
||||
// LegacyCommitment represents a legacy commitment transaction where
|
||||
// anchor outputs are not yet used and so the to_remote output is just
|
||||
// a regular but tweaked P2WKH.
|
||||
LegacyCommitment CommitmentType = iota
|
||||
|
||||
// LegacyTweaklessCommitment is similar to the LegacyCommitment with the
|
||||
// added detail of the to_remote output not being tweaked.
|
||||
LegacyTweaklessCommitment
|
||||
|
||||
// AnchorCommitment represents the commitment transaction of an
|
||||
// anchor channel. The key differences are that the to_remote is
|
||||
// encumbered by a 1 block CSV and so is thus a P2WSH output.
|
||||
AnchorCommitment
|
||||
)
|
||||
|
||||
// ToLocalInput constructs the input that will be used to spend the to_local
|
||||
// output.
|
||||
func (c CommitmentType) ToLocalInput(info *lnwallet.BreachRetribution) (
|
||||
input.Input, error) {
|
||||
|
||||
witnessType, err := c.ToLocalWitnessType()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return input.NewBaseInput(
|
||||
&info.RemoteOutpoint, witnessType, info.RemoteOutputSignDesc, 0,
|
||||
), nil
|
||||
}
|
||||
|
||||
// ToRemoteInput constructs the input that will be used to spend the to_remote
|
||||
// output.
|
||||
func (c CommitmentType) ToRemoteInput(info *lnwallet.BreachRetribution) (
|
||||
input.Input, error) {
|
||||
|
||||
witnessType, err := c.ToRemoteWitnessType()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch c {
|
||||
case LegacyCommitment, LegacyTweaklessCommitment:
|
||||
return input.NewBaseInput(
|
||||
&info.LocalOutpoint, witnessType,
|
||||
info.LocalOutputSignDesc, 0,
|
||||
), nil
|
||||
|
||||
case AnchorCommitment:
|
||||
// Anchor channels have a CSV-encumbered to-remote output. We'll
|
||||
// construct a CSV input and assign the proper CSV delay of 1.
|
||||
return input.NewCsvInput(
|
||||
&info.LocalOutpoint, witnessType,
|
||||
info.LocalOutputSignDesc, 0, 1,
|
||||
), nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown commitment type: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// ToLocalWitnessType is the input type of the to_local output.
|
||||
func (c CommitmentType) ToLocalWitnessType() (input.WitnessType, error) {
|
||||
switch c {
|
||||
case LegacyTweaklessCommitment, LegacyCommitment, AnchorCommitment:
|
||||
return input.CommitmentRevoke, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown commitment type: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// ToRemoteWitnessType is the input type of the to_remote output.
|
||||
func (c CommitmentType) ToRemoteWitnessType() (input.WitnessType, error) {
|
||||
switch c {
|
||||
case LegacyTweaklessCommitment:
|
||||
return input.CommitSpendNoDelayTweakless, nil
|
||||
|
||||
case LegacyCommitment:
|
||||
return input.CommitmentNoDelay, nil
|
||||
|
||||
case AnchorCommitment:
|
||||
return input.CommitmentToRemoteConfirmed, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown commitment type: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// ToRemoteWitnessSize is the size of the witness that will be required to spend
|
||||
// the to_remote output.
|
||||
func (c CommitmentType) ToRemoteWitnessSize() (int, error) {
|
||||
switch c {
|
||||
// Legacy channels (both tweaked and non-tweaked) spend from P2WKH
|
||||
// output.
|
||||
case LegacyTweaklessCommitment, LegacyCommitment:
|
||||
return input.P2WKHWitnessSize, nil
|
||||
|
||||
// Anchor channels spend a to-remote confirmed P2WSH output.
|
||||
case AnchorCommitment:
|
||||
return input.ToRemoteConfirmedWitnessSize, nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown commitment type: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// ToLocalWitnessSize is the size of the witness that will be required to spend
|
||||
// the to_local output.
|
||||
func (c CommitmentType) ToLocalWitnessSize() (int, error) {
|
||||
switch c {
|
||||
// An older ToLocalPenaltyWitnessSize constant used to underestimate the
|
||||
// size by one byte. The difference in weight can cause different output
|
||||
// values on the sweep transaction, so we mimic the original bug and
|
||||
// create signatures using the original weight estimate.
|
||||
case LegacyTweaklessCommitment, LegacyCommitment:
|
||||
return input.ToLocalPenaltyWitnessSize - 1, nil
|
||||
|
||||
case AnchorCommitment:
|
||||
return input.ToLocalPenaltyWitnessSize, nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown commitment type: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseRawSig parses a wire.TxWitness and creates an lnwire.Sig.
|
||||
func (c CommitmentType) ParseRawSig(witness wire.TxWitness) (lnwire.Sig,
|
||||
error) {
|
||||
|
||||
switch c {
|
||||
case LegacyCommitment, LegacyTweaklessCommitment, AnchorCommitment:
|
||||
// Check that the witness has at least one item.
|
||||
if len(witness) < 1 {
|
||||
return lnwire.Sig{}, fmt.Errorf("the witness should " +
|
||||
"have at least one element")
|
||||
}
|
||||
|
||||
// Check that the first witness element is non-nil. This is to
|
||||
// ensure that the witness length check below does not panic.
|
||||
if witness[0] == nil {
|
||||
return lnwire.Sig{}, fmt.Errorf("the first witness " +
|
||||
"element should not be nil")
|
||||
}
|
||||
|
||||
// Parse the DER-encoded signature from the first position of
|
||||
// the resulting witness. We trim an extra byte to remove the
|
||||
// sighash flag.
|
||||
rawSignature := witness[0][:len(witness[0])-1]
|
||||
|
||||
// Re-encode the DER signature into a fixed-size 64 byte
|
||||
// signature.
|
||||
return lnwire.NewSigFromECDSARawSignature(rawSignature)
|
||||
|
||||
default:
|
||||
return lnwire.Sig{}, fmt.Errorf("unknown commitment type: %v",
|
||||
c)
|
||||
}
|
||||
}
|
@@ -3,6 +3,8 @@ package blob
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
)
|
||||
|
||||
// Flag represents a specify option that can be present in a Type.
|
||||
@@ -81,6 +83,27 @@ func (t Type) Identifier() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// CommitmentType returns the appropriate CommitmentType for the given blob Type
|
||||
// and channel type.
|
||||
func (t Type) CommitmentType(chanType *channeldb.ChannelType) (CommitmentType,
|
||||
error) {
|
||||
|
||||
switch {
|
||||
case t.Has(FlagAnchorChannel):
|
||||
return AnchorCommitment, nil
|
||||
|
||||
case t.Has(FlagCommitOutputs):
|
||||
if chanType != nil && chanType.IsTweakless() {
|
||||
return LegacyTweaklessCommitment, nil
|
||||
}
|
||||
|
||||
return LegacyCommitment, nil
|
||||
|
||||
default:
|
||||
return 0, ErrUnknownBlobType
|
||||
}
|
||||
}
|
||||
|
||||
// Has returns true if the Type has the passed flag enabled.
|
||||
func (t Type) Has(flag Flag) bool {
|
||||
return Flag(t)&flag == flag
|
||||
|
Reference in New Issue
Block a user