github.com/decred/dcrlnd@v0.7.6/lnwallet/chanvalidate/validate.go (about)

     1  package chanvalidate
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	"github.com/decred/dcrd/dcrutil/v4"
     8  	"github.com/decred/dcrd/txscript/v4"
     9  	"github.com/decred/dcrd/wire"
    10  	"github.com/decred/dcrlnd/input"
    11  	"github.com/decred/dcrlnd/lnwire"
    12  )
    13  
    14  var (
    15  	// ErrInvalidOutPoint is returned when the ChanLocator is unable to
    16  	// find the target outpoint.
    17  	ErrInvalidOutPoint = fmt.Errorf("output meant to create channel cannot " +
    18  		"be found")
    19  
    20  	// ErrWrongPkScript is returned when the alleged funding transaction is
    21  	// found to have an incorrect pkSript.
    22  	ErrWrongPkScript = fmt.Errorf("wrong pk script")
    23  
    24  	// ErrInvalidSize is returned when the alleged funding transaction
    25  	// output has the wrong size (channel capacity).
    26  	ErrInvalidSize = fmt.Errorf("channel has wrong size")
    27  )
    28  
    29  // ErrScriptValidateError is returned when Script VM validation fails for an
    30  // alleged channel output.
    31  type ErrScriptValidateError struct {
    32  	err error
    33  }
    34  
    35  // Error returns a human readable string describing the error.
    36  func (e *ErrScriptValidateError) Error() string {
    37  	return fmt.Sprintf("script validation failed: %v", e.err)
    38  }
    39  
    40  // Unwrap returns the underlying wrapped VM execution failure error.
    41  func (e *ErrScriptValidateError) Unwrap() error {
    42  	return e.err
    43  }
    44  
    45  // ChanLocator abstracts away obtaining the output that created the channel, as
    46  // well as validating its existence given the funding transaction.  We need
    47  // this as there are several ways (outpoint, short chan ID) to identify the
    48  // output of a channel given the funding transaction.
    49  type ChanLocator interface {
    50  	// Locate attempts to locate the funding output within the funding
    51  	// transaction. It also returns the final out point of the channel
    52  	// which uniquely identifies the output which creates the channel. If
    53  	// the target output cannot be found, or cannot exist on the funding
    54  	// transaction, then an error is to be returned.
    55  	Locate(*wire.MsgTx) (*wire.TxOut, *wire.OutPoint, error)
    56  }
    57  
    58  // OutPointChanLocator is an implementation of the ChanLocator that can be used
    59  // when one already knows the expected chan point.
    60  type OutPointChanLocator struct {
    61  	// ChanPoint is the expected chan point.
    62  	ChanPoint wire.OutPoint
    63  }
    64  
    65  // Locate attempts to locate the funding output within the passed funding
    66  // transaction.
    67  //
    68  // NOTE: Part of the ChanLocator interface.
    69  func (o *OutPointChanLocator) Locate(fundingTx *wire.MsgTx) (
    70  	*wire.TxOut, *wire.OutPoint, error) {
    71  
    72  	// If the expected index is greater than the amount of output in the
    73  	// transaction, then we'll reject this channel as it's invalid.
    74  	if int(o.ChanPoint.Index) >= len(fundingTx.TxOut) {
    75  		return nil, nil, ErrInvalidOutPoint
    76  	}
    77  
    78  	// As an extra sanity check, we'll also ensure the txid hash matches.
    79  	fundingHash := fundingTx.TxHash()
    80  	if !bytes.Equal(fundingHash[:], o.ChanPoint.Hash[:]) {
    81  		return nil, nil, ErrInvalidOutPoint
    82  	}
    83  
    84  	return fundingTx.TxOut[o.ChanPoint.Index], &o.ChanPoint, nil
    85  }
    86  
    87  // ShortChanIDChanLocator is an implementation of the ChanLocator that can be
    88  // used when one only knows the short channel ID of a channel. This should be
    89  // used in contexts when one is verifying a 3rd party channel.
    90  type ShortChanIDChanLocator struct {
    91  	// ID is the short channel ID of the target channel.
    92  	ID lnwire.ShortChannelID
    93  }
    94  
    95  // Locate attempts to locate the funding output within the passed funding
    96  // transaction.
    97  //
    98  // NOTE: Part of the ChanLocator interface.
    99  func (s *ShortChanIDChanLocator) Locate(fundingTx *wire.MsgTx) (
   100  	*wire.TxOut, *wire.OutPoint, error) {
   101  
   102  	// If the expected index is greater than the amount of output in the
   103  	// transaction, then we'll reject this channel as it's invalid.
   104  	outputIndex := s.ID.TxPosition
   105  	if int(outputIndex) >= len(fundingTx.TxOut) {
   106  		return nil, nil, ErrInvalidOutPoint
   107  	}
   108  
   109  	chanPoint := wire.OutPoint{
   110  		Hash:  fundingTx.TxHash(),
   111  		Index: uint32(outputIndex),
   112  	}
   113  
   114  	return fundingTx.TxOut[outputIndex], &chanPoint, nil
   115  }
   116  
   117  // CommitmentContext is optional validation context that can be passed into the
   118  // main Validate for self-owned channel. The information in this context allows
   119  // us to fully verify out initial commitment spend based on the on-chain state
   120  // of the funding output.
   121  type CommitmentContext struct {
   122  	// Value is the known size of the channel.
   123  	Value dcrutil.Amount
   124  
   125  	// FullySignedCommitTx is the fully signed commitment transaction. This
   126  	// should include a valid witness.
   127  	FullySignedCommitTx *wire.MsgTx
   128  }
   129  
   130  // Context is the main validation contxet. For a given channel, all fields but
   131  // the optional CommitCtx should be populated based on existing
   132  // known-to-be-valid parameters.
   133  type Context struct {
   134  	// Locator is a concrete implementation of the ChanLocator interface.
   135  	Locator ChanLocator
   136  
   137  	// MultiSigPkScript is the fully serialized witness script of the
   138  	// multi-sig output. This is the final witness program that should be
   139  	// found in the funding output.
   140  	MultiSigPkScript []byte
   141  
   142  	// FundingTx is channel funding transaction as found confirmed in the
   143  	// chain.
   144  	FundingTx *wire.MsgTx
   145  
   146  	// CommitCtx is an optional additional set of validation context
   147  	// required to validate a self-owned channel. If present, then a full
   148  	// Script VM validation will be performed.
   149  	CommitCtx *CommitmentContext
   150  }
   151  
   152  // Validate given the specified context, this function validates that the
   153  // alleged channel is well formed, and spendable (if the optional CommitCtx is
   154  // specified).  If this method returns an error, then the alleged channel is
   155  // invalid and should be abandoned immediately.
   156  func Validate(ctx *Context) (*wire.OutPoint, error) {
   157  	// First, we'll attempt to locate the target outpoint in the funding
   158  	// transaction. If this returns an error, then we know that the
   159  	// outpoint doesn't actually exist, so we'll exit early.
   160  	fundingOutput, chanPoint, err := ctx.Locator.Locate(
   161  		ctx.FundingTx,
   162  	)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	// The scripts should match up exactly, otherwise the channel is
   168  	// invalid.
   169  	fundingScript := fundingOutput.PkScript
   170  	if !bytes.Equal(ctx.MultiSigPkScript, fundingScript) {
   171  		return nil, ErrWrongPkScript
   172  	}
   173  
   174  	// If there's no commitment context, then we're done here as this is a
   175  	// 3rd party channel.
   176  	if ctx.CommitCtx == nil {
   177  		return chanPoint, nil
   178  	}
   179  
   180  	// Now that we know this is our channel, we'll verify the amount of the
   181  	// created output against our expected size of the channel.
   182  	fundingValue := fundingOutput.Value
   183  	if dcrutil.Amount(fundingValue) != ctx.CommitCtx.Value {
   184  		return nil, ErrInvalidSize
   185  	}
   186  
   187  	// If we reach this point, then all other checks have succeeded, so
   188  	// we'll now attempt a full Script VM execution to ensure that we're
   189  	// able to close the channel using this initial state.
   190  	vm, err := txscript.NewEngine(
   191  		ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx,
   192  		0, input.ScriptVerifyFlags, fundingOutput.Version, nil,
   193  	)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	// Finally, we'll attempt to verify our full spend, if this fails then
   199  	// the channel is definitely invalid.
   200  	err = vm.Execute()
   201  	if err != nil {
   202  		return nil, &ErrScriptValidateError{err: err}
   203  	}
   204  
   205  	return chanPoint, nil
   206  }