mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-20 02:27:21 +01:00
c199ad30ac
In this commit, we create a new `chanvalidate` package which it to house all logic required for 1st and 3rd party channel verification. 1st party verification occurs when we find a channel in the chain that is allegedly ours, while 3rd party verification will occur when a peer sends us a channel proof of a new channel. In the scope of the recent CVE, we actually fully verified 3rd party channels, but failed to also include those checks in our 1st party verification code. In order to unify this logic, and prevent future issues, in this PR we move to concentrate all validation logic into a single function. Both 1st and 3rd party validation will then use this function. Additionally, having all the logic in a single place makes it easier to audit, and also write tests against.
308 lines
7.3 KiB
Go
308 lines
7.3 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)
|
|
aliceSig, err := txscript.RawTxInWitnessSignature(
|
|
commitTx, sigHashes, 0, chanSize,
|
|
multiSigScript, txscript.SigHashAll, alicePriv,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bobSig, err := txscript.RawTxInWitnessSignature(
|
|
commitTx, sigHashes, 0, chanSize,
|
|
multiSigScript, txscript.SigHashAll, bobPriv,
|
|
)
|
|
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)
|
|
}
|
|
}
|
|
}
|