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

     1  package chanvalidate
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"github.com/decred/dcrd/chaincfg/chainhash"
     8  	"github.com/decred/dcrd/dcrec"
     9  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    10  	"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
    11  	"github.com/decred/dcrd/dcrutil/v4"
    12  	"github.com/decred/dcrd/txscript/v4"
    13  	"github.com/decred/dcrd/txscript/v4/sign"
    14  	"github.com/decred/dcrd/wire"
    15  	"github.com/decred/dcrlnd/input"
    16  	"github.com/decred/dcrlnd/lnwire"
    17  )
    18  
    19  func privKeyFromBytes(b []byte) (*secp256k1.PrivateKey, *secp256k1.PublicKey) {
    20  	key := secp256k1.PrivKeyFromBytes(b)
    21  	return key, key.PubKey()
    22  }
    23  
    24  var (
    25  	aliceKey = chainhash.Hash{
    26  		0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
    27  		0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
    28  		0x4f, 0x2f, 0x6f, 0x25, 0x18, 0xa3, 0xef, 0xb9,
    29  		0x64, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
    30  	}
    31  	bobKey = chainhash.Hash{
    32  		0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
    33  		0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
    34  		0x4f, 0x2f, 0x6f, 0x25, 0x98, 0xa3, 0xef, 0xb9,
    35  		0x69, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
    36  	}
    37  
    38  	alicePriv, alicePub = privKeyFromBytes(aliceKey[:])
    39  	bobPriv, bobPub     = privKeyFromBytes(bobKey[:])
    40  )
    41  
    42  // channelTestCtx holds shared context that will be used in all tests cases
    43  // below.
    44  type channelTestCtx struct {
    45  	fundingTx *wire.MsgTx
    46  
    47  	invalidCommitTx, validCommitTx *wire.MsgTx
    48  
    49  	chanPoint wire.OutPoint
    50  	cid       lnwire.ShortChannelID
    51  
    52  	fundingScript []byte
    53  }
    54  
    55  // newChannelTestCtx creates a new channelCtx for use in the validation tests
    56  // below. This creates a fake funding transaction, as well as an invalid and
    57  // valid commitment transaction.
    58  func newChannelTestCtx(chanSize int64) (*channelTestCtx, error) {
    59  	multiSigScript, err := input.GenMultiSigScript(
    60  		alicePub.SerializeCompressed(), bobPub.SerializeCompressed(),
    61  	)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	pkScript, err := input.ScriptHashPkScript(multiSigScript)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	fundingOutput := wire.TxOut{
    71  		Value:    chanSize,
    72  		PkScript: pkScript,
    73  	}
    74  
    75  	fundingTx := &wire.MsgTx{
    76  		TxIn: []*wire.TxIn{
    77  			{},
    78  		},
    79  		TxOut: []*wire.TxOut{
    80  			&fundingOutput,
    81  			{
    82  				Value:    9999,
    83  				PkScript: bytes.Repeat([]byte{'a'}, 32),
    84  			},
    85  			{
    86  				Value:    99999,
    87  				PkScript: bytes.Repeat([]byte{'b'}, 32),
    88  			},
    89  		},
    90  	}
    91  
    92  	fundingTxHash := fundingTx.TxHash()
    93  
    94  	commitTx := &wire.MsgTx{
    95  		TxIn: []*wire.TxIn{
    96  			{
    97  				PreviousOutPoint: wire.OutPoint{
    98  					Hash:  fundingTxHash,
    99  					Index: 0,
   100  				},
   101  			},
   102  		},
   103  		TxOut: []*wire.TxOut{
   104  			&fundingOutput,
   105  		},
   106  	}
   107  
   108  	aliceSigRaw, err := sign.RawTxInSignature(
   109  		commitTx, 0, multiSigScript, txscript.SigHashAll, alicePriv.Serialize(),
   110  		dcrec.STEcdsaSecp256k1,
   111  	)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	aliceSig, err := ecdsa.ParseDERSignature(
   117  		aliceSigRaw[:len(aliceSigRaw)-1],
   118  	)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	bobSigRaw, err := sign.RawTxInSignature(
   124  		commitTx, 0, multiSigScript, txscript.SigHashAll, bobPriv.Serialize(),
   125  		dcrec.STEcdsaSecp256k1,
   126  	)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	bobSig, err := ecdsa.ParseDERSignature(
   132  		bobSigRaw[:len(bobSigRaw)-1],
   133  	)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	witnessStack := input.SpendMultiSig(
   139  		multiSigScript, alicePub.SerializeCompressed(), aliceSig,
   140  		bobPub.SerializeCompressed(), bobSig,
   141  	)
   142  	sigScript, err := input.WitnessStackToSigScript(witnessStack)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	commitTx.TxIn[0].SignatureScript = sigScript
   147  	invalidCommitTx := commitTx.Copy()
   148  	invalidCommitTx.TxIn[0].PreviousOutPoint.Index = 2
   149  
   150  	return &channelTestCtx{
   151  		fundingTx:       fundingTx,
   152  		validCommitTx:   commitTx,
   153  		invalidCommitTx: invalidCommitTx,
   154  		chanPoint: wire.OutPoint{
   155  			Hash:  fundingTxHash,
   156  			Index: 0,
   157  		},
   158  		cid: lnwire.ShortChannelID{
   159  			TxPosition: 0,
   160  		},
   161  		fundingScript: pkScript,
   162  	}, nil
   163  }
   164  
   165  // TestValidate ensures that the Validate method is able to detect all cases of
   166  // invalid channels, and properly accept invalid channels.
   167  func TestValidate(t *testing.T) {
   168  	t.Parallel()
   169  
   170  	chanSize := int64(1000000)
   171  	channelCtx, err := newChannelTestCtx(chanSize)
   172  	if err != nil {
   173  		t.Fatalf("unable to make channel context: %v", err)
   174  	}
   175  
   176  	testCases := []struct {
   177  		// expectedErr is the error we expect, this should be nil if
   178  		// the channel is valid.
   179  		expectedErr error
   180  
   181  		// locator is how the Validate method should find the target
   182  		// outpoint.
   183  		locator ChanLocator
   184  
   185  		// chanPoint is the expected final out point.
   186  		chanPoint wire.OutPoint
   187  
   188  		// chanScript is the funding pkScript.
   189  		chanScript []byte
   190  
   191  		// fundingTx is the funding transaction to use in the test.
   192  		fundingTx *wire.MsgTx
   193  
   194  		// commitTx is the commitment transaction to use in the test,
   195  		// this is optional.
   196  		commitTx *wire.MsgTx
   197  
   198  		// expectedValue is the value of the funding transaction we
   199  		// should expect. This is only required if commitTx is non-nil.
   200  		expectedValue int64
   201  	}{
   202  		// Short chan ID channel locator, unable to find target
   203  		// outpoint.
   204  		{
   205  			expectedErr: ErrInvalidOutPoint,
   206  			locator: &ShortChanIDChanLocator{
   207  				ID: lnwire.NewShortChanIDFromInt(9),
   208  			},
   209  			fundingTx: &wire.MsgTx{},
   210  		},
   211  
   212  		// Chan point based channel locator, unable to find target
   213  		// outpoint.
   214  		{
   215  			expectedErr: ErrInvalidOutPoint,
   216  			locator: &OutPointChanLocator{
   217  				ChanPoint: wire.OutPoint{
   218  					Index: 99,
   219  				},
   220  			},
   221  			fundingTx: &wire.MsgTx{},
   222  		},
   223  
   224  		// Invalid pkScript match on mined funding transaction, chan
   225  		// point based locator.
   226  		{
   227  			expectedErr: ErrWrongPkScript,
   228  			locator: &OutPointChanLocator{
   229  				ChanPoint: channelCtx.chanPoint,
   230  			},
   231  			chanScript: bytes.Repeat([]byte("a"), 32),
   232  			fundingTx:  channelCtx.fundingTx,
   233  		},
   234  
   235  		// Invalid pkScript match on mined funding transaction, short
   236  		// chan ID based locator.
   237  		{
   238  			expectedErr: ErrWrongPkScript,
   239  			locator: &ShortChanIDChanLocator{
   240  				ID: channelCtx.cid,
   241  			},
   242  			chanScript: bytes.Repeat([]byte("a"), 32),
   243  			fundingTx:  channelCtx.fundingTx,
   244  		},
   245  
   246  		// Invalid amount on funding transaction.
   247  		{
   248  			expectedErr: ErrInvalidSize,
   249  			locator: &OutPointChanLocator{
   250  				ChanPoint: channelCtx.chanPoint,
   251  			},
   252  			chanScript:    channelCtx.fundingScript,
   253  			fundingTx:     channelCtx.fundingTx,
   254  			expectedValue: 555,
   255  			commitTx:      channelCtx.validCommitTx,
   256  		},
   257  
   258  		// Validation failure on final commitment transaction
   259  		{
   260  			expectedErr: &ErrScriptValidateError{},
   261  			locator: &OutPointChanLocator{
   262  				ChanPoint: channelCtx.chanPoint,
   263  			},
   264  			chanScript:    channelCtx.fundingScript,
   265  			fundingTx:     channelCtx.fundingTx,
   266  			expectedValue: chanSize,
   267  			commitTx:      channelCtx.invalidCommitTx,
   268  		},
   269  
   270  		// Fully valid 3rd party verification.
   271  		{
   272  			expectedErr: nil,
   273  			locator: &OutPointChanLocator{
   274  				ChanPoint: channelCtx.chanPoint,
   275  			},
   276  			chanScript: channelCtx.fundingScript,
   277  			fundingTx:  channelCtx.fundingTx,
   278  			chanPoint:  channelCtx.chanPoint,
   279  		},
   280  
   281  		// Fully valid self-channel verification.
   282  		{
   283  			expectedErr: nil,
   284  			locator: &OutPointChanLocator{
   285  				ChanPoint: channelCtx.chanPoint,
   286  			},
   287  			chanScript:    channelCtx.fundingScript,
   288  			fundingTx:     channelCtx.fundingTx,
   289  			expectedValue: chanSize,
   290  			commitTx:      channelCtx.validCommitTx,
   291  			chanPoint:     channelCtx.chanPoint,
   292  		},
   293  	}
   294  
   295  	for i, testCase := range testCases {
   296  		ctx := &Context{
   297  			Locator:          testCase.locator,
   298  			MultiSigPkScript: testCase.chanScript,
   299  			FundingTx:        testCase.fundingTx,
   300  		}
   301  
   302  		if testCase.commitTx != nil {
   303  			ctx.CommitCtx = &CommitmentContext{
   304  				Value: dcrutil.Amount(
   305  					testCase.expectedValue,
   306  				),
   307  				FullySignedCommitTx: testCase.commitTx,
   308  			}
   309  		}
   310  
   311  		chanPoint, err := Validate(ctx)
   312  		if err != testCase.expectedErr {
   313  			_, ok := testCase.expectedErr.(*ErrScriptValidateError)
   314  			_, scriptErr := err.(*ErrScriptValidateError)
   315  			if ok && scriptErr {
   316  				continue
   317  			}
   318  
   319  			t.Fatalf("test #%v: validation failed: expected %v, "+
   320  				"got %v", i, testCase.expectedErr, err)
   321  		}
   322  
   323  		if err != nil {
   324  			continue
   325  		}
   326  
   327  		if *chanPoint != testCase.chanPoint {
   328  			t.Fatalf("test #%v: wrong outpoint: want %v, got %v",
   329  				i, testCase.chanPoint, chanPoint)
   330  		}
   331  	}
   332  }