lnd.xprv/lnwallet/chanvalidate/validate_test.go
Conner Fromknecht f2b6e2af04
input: pass input.Signature to multisig spend
Modifies SpendMultiSig to accept input.Signature, so that we can
ultimately assert the size of multisig witnesses.
2020-04-10 14:27:35 -07:00

322 lines
7.5 KiB
Go

package chanvalidate
import (
"bytes"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
aliceKey = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x18, 0xa3, 0xef, 0xb9,
0x64, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
bobKey = chainhash.Hash{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x98, 0xa3, 0xef, 0xb9,
0x69, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
alicePriv, alicePub = btcec.PrivKeyFromBytes(btcec.S256(), aliceKey[:])
bobPriv, bobPub = btcec.PrivKeyFromBytes(btcec.S256(), bobKey[:])
)
// channelTestCtx holds shared context that will be used in all tests cases
// below.
type channelTestCtx struct {
fundingTx *wire.MsgTx
invalidCommitTx, validCommitTx *wire.MsgTx
chanPoint wire.OutPoint
cid lnwire.ShortChannelID
fundingScript []byte
}
// newChannelTestCtx creates a new channelCtx for use in the validation tests
// below. This creates a fake funding transaction, as well as an invalid and
// valid commitment transaction.
func newChannelTestCtx(chanSize int64) (*channelTestCtx, error) {
multiSigScript, err := input.GenMultiSigScript(
alicePub.SerializeCompressed(), bobPub.SerializeCompressed(),
)
if err != nil {
return nil, err
}
pkScript, err := input.WitnessScriptHash(multiSigScript)
if err != nil {
return nil, err
}
fundingOutput := wire.TxOut{
Value: chanSize,
PkScript: pkScript,
}
fundingTx := &wire.MsgTx{
TxIn: []*wire.TxIn{
{},
},
TxOut: []*wire.TxOut{
&fundingOutput,
{
Value: 9999,
PkScript: bytes.Repeat([]byte{'a'}, 32),
},
{
Value: 99999,
PkScript: bytes.Repeat([]byte{'b'}, 32),
},
},
}
fundingTxHash := fundingTx.TxHash()
commitTx := &wire.MsgTx{
TxIn: []*wire.TxIn{
{
PreviousOutPoint: wire.OutPoint{
Hash: fundingTxHash,
Index: 0,
},
},
},
TxOut: []*wire.TxOut{
&fundingOutput,
},
}
sigHashes := txscript.NewTxSigHashes(commitTx)
aliceSigRaw, err := txscript.RawTxInWitnessSignature(
commitTx, sigHashes, 0, chanSize,
multiSigScript, txscript.SigHashAll, alicePriv,
)
if err != nil {
return nil, err
}
aliceSig, err := btcec.ParseDERSignature(
aliceSigRaw, btcec.S256(),
)
if err != nil {
return nil, err
}
bobSigRaw, err := txscript.RawTxInWitnessSignature(
commitTx, sigHashes, 0, chanSize,
multiSigScript, txscript.SigHashAll, bobPriv,
)
if err != nil {
return nil, err
}
bobSig, err := btcec.ParseDERSignature(
bobSigRaw, btcec.S256(),
)
if err != nil {
return nil, err
}
commitTx.TxIn[0].Witness = input.SpendMultiSig(
multiSigScript, alicePub.SerializeCompressed(), aliceSig,
bobPub.SerializeCompressed(), bobSig,
)
invalidCommitTx := commitTx.Copy()
invalidCommitTx.TxIn[0].PreviousOutPoint.Index = 2
return &channelTestCtx{
fundingTx: fundingTx,
validCommitTx: commitTx,
invalidCommitTx: invalidCommitTx,
chanPoint: wire.OutPoint{
Hash: fundingTxHash,
Index: 0,
},
cid: lnwire.ShortChannelID{
TxPosition: 0,
},
fundingScript: pkScript,
}, nil
}
// TestValidate ensures that the Validate method is able to detect all cases of
// invalid channels, and properly accept invalid channels.
func TestValidate(t *testing.T) {
t.Parallel()
chanSize := int64(1000000)
channelCtx, err := newChannelTestCtx(chanSize)
if err != nil {
t.Fatalf("unable to make channel context: %v", err)
}
testCases := []struct {
// expectedErr is the error we expect, this should be nil if
// the channel is valid.
expectedErr error
// locator is how the Validate method should find the target
// outpoint.
locator ChanLocator
// chanPoint is the expected final out point.
chanPoint wire.OutPoint
// chanScript is the funding pkScript.
chanScript []byte
// fundingTx is the funding transaction to use in the test.
fundingTx *wire.MsgTx
// commitTx is the commitment transaction to use in the test,
// this is optional.
commitTx *wire.MsgTx
// expectedValue is the value of the funding transaction we
// should expect. This is only required if commitTx is non-nil.
expectedValue int64
}{
// Short chan ID channel locator, unable to find target
// outpoint.
{
expectedErr: ErrInvalidOutPoint,
locator: &ShortChanIDChanLocator{
ID: lnwire.NewShortChanIDFromInt(9),
},
fundingTx: &wire.MsgTx{},
},
// Chan point based channel locator, unable to find target
// outpoint.
{
expectedErr: ErrInvalidOutPoint,
locator: &OutPointChanLocator{
ChanPoint: wire.OutPoint{
Index: 99,
},
},
fundingTx: &wire.MsgTx{},
},
// Invalid pkScript match on mined funding transaction, chan
// point based locator.
{
expectedErr: ErrWrongPkScript,
locator: &OutPointChanLocator{
ChanPoint: channelCtx.chanPoint,
},
chanScript: bytes.Repeat([]byte("a"), 32),
fundingTx: channelCtx.fundingTx,
},
// Invalid pkScript match on mined funding transaction, short
// chan ID based locator.
{
expectedErr: ErrWrongPkScript,
locator: &ShortChanIDChanLocator{
ID: channelCtx.cid,
},
chanScript: bytes.Repeat([]byte("a"), 32),
fundingTx: channelCtx.fundingTx,
},
// Invalid amount on funding transaction.
{
expectedErr: ErrInvalidSize,
locator: &OutPointChanLocator{
ChanPoint: channelCtx.chanPoint,
},
chanScript: channelCtx.fundingScript,
fundingTx: channelCtx.fundingTx,
expectedValue: 555,
commitTx: channelCtx.validCommitTx,
},
// Validation failure on final commitment transaction
{
expectedErr: &ErrScriptValidateError{},
locator: &OutPointChanLocator{
ChanPoint: channelCtx.chanPoint,
},
chanScript: channelCtx.fundingScript,
fundingTx: channelCtx.fundingTx,
expectedValue: chanSize,
commitTx: channelCtx.invalidCommitTx,
},
// Fully valid 3rd party verification.
{
expectedErr: nil,
locator: &OutPointChanLocator{
ChanPoint: channelCtx.chanPoint,
},
chanScript: channelCtx.fundingScript,
fundingTx: channelCtx.fundingTx,
chanPoint: channelCtx.chanPoint,
},
// Fully valid self-channel verification.
{
expectedErr: nil,
locator: &OutPointChanLocator{
ChanPoint: channelCtx.chanPoint,
},
chanScript: channelCtx.fundingScript,
fundingTx: channelCtx.fundingTx,
expectedValue: chanSize,
commitTx: channelCtx.validCommitTx,
chanPoint: channelCtx.chanPoint,
},
}
for i, testCase := range testCases {
ctx := &Context{
Locator: testCase.locator,
MultiSigPkScript: testCase.chanScript,
FundingTx: testCase.fundingTx,
}
if testCase.commitTx != nil {
ctx.CommitCtx = &CommitmentContext{
Value: btcutil.Amount(
testCase.expectedValue,
),
FullySignedCommitTx: testCase.commitTx,
}
}
chanPoint, err := Validate(ctx)
if err != testCase.expectedErr {
_, ok := testCase.expectedErr.(*ErrScriptValidateError)
_, scriptErr := err.(*ErrScriptValidateError)
if ok && scriptErr {
continue
}
t.Fatalf("test #%v: validation failed: expected %v, "+
"got %v", i, testCase.expectedErr, err)
}
if err != nil {
continue
}
if *chanPoint != testCase.chanPoint {
t.Fatalf("test #%v: wrong outpoint: want %v, got %v",
i, testCase.chanPoint, chanPoint)
}
}
}