2019-10-01 05:53:58 +03:00
|
|
|
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)
|
2020-04-06 03:07:01 +03:00
|
|
|
aliceSigRaw, err := txscript.RawTxInWitnessSignature(
|
2019-10-01 05:53:58 +03:00
|
|
|
commitTx, sigHashes, 0, chanSize,
|
|
|
|
multiSigScript, txscript.SigHashAll, alicePriv,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-04-06 03:07:01 +03:00
|
|
|
aliceSig, err := btcec.ParseDERSignature(
|
|
|
|
aliceSigRaw, btcec.S256(),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
bobSigRaw, err := txscript.RawTxInWitnessSignature(
|
2019-10-01 05:53:58 +03:00
|
|
|
commitTx, sigHashes, 0, chanSize,
|
|
|
|
multiSigScript, txscript.SigHashAll, bobPriv,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-04-06 03:07:01 +03:00
|
|
|
bobSig, err := btcec.ParseDERSignature(
|
|
|
|
bobSigRaw, btcec.S256(),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-01 05:53:58 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|