lnd.xprv/lnwallet/transactions_test.go
Joost Jager 793b21b79e
lnwallet/test: make use of test htlcs explicit
Previously whether or not to add test htlcs was implictly controlled by
a nil value of the HtlcDescs test parameter. With the conversion to
json, that nil value got lost.

The reason that the test still passed is because with the fee rate of
the no-htlc test case, the htlcs were trimmed. Also because in the test
json, balances are specified after applying htlcs, the test didn't fail
with a mismatching balance.
2020-08-19 08:44:00 +02:00

1033 lines
30 KiB
Go

package lnwallet
import (
"bytes"
"crypto/rand"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"sort"
"testing"
"time"
"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/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/stretchr/testify/require"
)
/**
* This file implements that different types of transactions used in the
* lightning protocol are created correctly. To do so, the tests use the test
* vectors defined in Appendix B & C of BOLT 03.
*/
// testContext contains the test parameters defined in Appendix B & C of the
// BOLT 03 spec.
type testContext struct {
localFundingPrivkey *btcec.PrivateKey
localPaymentBasepointSecret *btcec.PrivateKey
localDelayedPaymentBasepointSecret *btcec.PrivateKey
remoteFundingPrivkey *btcec.PrivateKey
remoteRevocationBasepointSecret *btcec.PrivateKey
remotePaymentBasepointSecret *btcec.PrivateKey
localPerCommitSecret lntypes.Hash
fundingTx *btcutil.Tx
localCsvDelay uint16
fundingAmount btcutil.Amount
dustLimit btcutil.Amount
commitHeight uint64
t *testing.T
}
// newTestContext populates a new testContext struct with the constant
// parameters defined in the BOLT 03 spec.
func newTestContext(t *testing.T) (tc *testContext) {
tc = new(testContext)
priv := func(v string) *btcec.PrivateKey {
k, err := privkeyFromHex(v)
require.NoError(t, err)
return k
}
tc.remoteFundingPrivkey = priv("1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e13")
tc.remoteRevocationBasepointSecret = priv("2222222222222222222222222222222222222222222222222222222222222222")
tc.remotePaymentBasepointSecret = priv("4444444444444444444444444444444444444444444444444444444444444444")
tc.localPaymentBasepointSecret = priv("1111111111111111111111111111111111111111111111111111111111111111")
tc.localDelayedPaymentBasepointSecret = priv("3333333333333333333333333333333333333333333333333333333333333333")
tc.localFundingPrivkey = priv("30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749")
var err error
tc.localPerCommitSecret, err = lntypes.MakeHashFromStr("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")
require.NoError(t, err)
const fundingTxHex = "0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000"
tc.fundingTx, err = txFromHex(fundingTxHex)
require.NoError(t, err)
tc.localCsvDelay = 144
tc.fundingAmount = 10000000
tc.dustLimit = 546
tc.commitHeight = 42
tc.t = t
return tc
}
var testHtlcs = []struct {
incoming bool
amount lnwire.MilliSatoshi
expiry uint32
preimage string
}{
{
incoming: true,
amount: 1000000,
expiry: 500,
preimage: "0000000000000000000000000000000000000000000000000000000000000000",
},
{
incoming: true,
amount: 2000000,
expiry: 501,
preimage: "0101010101010101010101010101010101010101010101010101010101010101",
},
{
incoming: false,
amount: 2000000,
expiry: 502,
preimage: "0202020202020202020202020202020202020202020202020202020202020202",
},
{
incoming: false,
amount: 3000000,
expiry: 503,
preimage: "0303030303030303030303030303030303030303030303030303030303030303",
},
{
incoming: true,
amount: 4000000,
expiry: 504,
preimage: "0404040404040404040404040404040404040404040404040404040404040404",
},
}
// htlcDesc is a description used to construct each HTLC in each test case.
type htlcDesc struct {
RemoteSigHex string
ResolutionTxHex string
}
type testCase struct {
Name string
LocalBalance lnwire.MilliSatoshi
RemoteBalance lnwire.MilliSatoshi
FeePerKw btcutil.Amount
// UseTestHtlcs defined whether the fixed set of test htlc should be
// added to the channel before checking the commitment assertions.
UseTestHtlcs bool
HtlcDescs []htlcDesc
ExpectedCommitmentTxHex string
RemoteSigHex string
}
// TestCommitmentAndHTLCTransactions checks the test vectors specified in
// BOLT 03, Appendix C. This deterministically generates commitment and second
// level HTLC transactions and checks that they match the expected values.
func TestCommitmentAndHTLCTransactions(t *testing.T) {
t.Parallel()
vectorSets := []struct {
name string
jsonFile string
chanType channeldb.ChannelType
}{
{
name: "legacy",
chanType: channeldb.SingleFunderBit,
jsonFile: "test_vectors_legacy.json",
},
{
name: "anchors",
chanType: channeldb.SingleFunderTweaklessBit | channeldb.AnchorOutputsBit,
jsonFile: "test_vectors_anchors.json",
},
}
for _, set := range vectorSets {
set := set
var testCases []testCase
jsonText, err := ioutil.ReadFile(set.jsonFile)
require.NoError(t, err)
err = json.Unmarshal(jsonText, &testCases)
require.NoError(t, err)
t.Run(set.name, func(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.Name, func(t *testing.T) {
testVectors(t, set.chanType, test)
})
}
})
}
}
// addTestHtlcs adds the test vector htlcs to the update logs of the local and
// remote node.
func addTestHtlcs(t *testing.T, remote,
local *LightningChannel) map[[20]byte]lntypes.Preimage {
hash160map := make(map[[20]byte]lntypes.Preimage)
for _, htlc := range testHtlcs {
preimage, err := lntypes.MakePreimageFromStr(htlc.preimage)
require.NoError(t, err)
hash := preimage.Hash()
// Store ripemd160 hash of the payment hash to later identify
// resolutions.
var hash160 [20]byte
copy(hash160[:], input.Ripemd160H(hash[:]))
hash160map[hash160] = preimage
// Add htlc to the channel.
chanID := lnwire.NewChanIDFromOutPoint(remote.ChanPoint)
msg := &lnwire.UpdateAddHTLC{
Amount: htlc.amount,
ChanID: chanID,
Expiry: htlc.expiry,
PaymentHash: hash,
}
if htlc.incoming {
htlcID, err := remote.AddHTLC(msg, nil)
require.NoError(t, err, "unable to add htlc")
msg.ID = htlcID
_, err = local.ReceiveHTLC(msg)
require.NoError(t, err, "unable to recv htlc")
} else {
htlcID, err := local.AddHTLC(msg, nil)
require.NoError(t, err, "unable to add htlc")
msg.ID = htlcID
_, err = remote.ReceiveHTLC(msg)
require.NoError(t, err, "unable to recv htlc")
}
}
return hash160map
}
// testVectors executes a commit dance to end up with the commitment transaction
// that is described in the test vectors and then asserts that all values are
// correct.
func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) {
tc := newTestContext(t)
// Balances in the test vectors are before subtraction of in-flight
// htlcs. Convert to spendable balances.
remoteBalance := test.RemoteBalance
localBalance := test.LocalBalance
if test.UseTestHtlcs {
for _, htlc := range testHtlcs {
if htlc.incoming {
remoteBalance += htlc.amount
} else {
localBalance += htlc.amount
}
}
}
// Set up a test channel on which the test commitment transaction is
// going to be produced.
remoteChannel, localChannel, cleanUp := createTestChannelsForVectors(
tc,
chanType, test.FeePerKw,
remoteBalance.ToSatoshis(),
localBalance.ToSatoshis(),
)
defer cleanUp()
// Add htlcs (if any) to the update logs of both sides and save a hash
// map that allows us to identify the htlcs in the scripts later on and
// retrieve the corresponding preimage.
var hash160map map[[20]byte]lntypes.Preimage
if test.UseTestHtlcs {
hash160map = addTestHtlcs(t, remoteChannel, localChannel)
}
// Execute commit dance to arrive at the point where the local node has
// received the test commitment and the remote signature.
localSig, localHtlcSigs, _, err := localChannel.SignNextCommitment()
require.NoError(t, err, "local unable to sign commitment")
err = remoteChannel.ReceiveNewCommitment(localSig, localHtlcSigs)
require.NoError(t, err)
revMsg, _, err := remoteChannel.RevokeCurrentCommitment()
require.NoError(t, err)
_, _, _, _, err = localChannel.ReceiveRevocation(revMsg)
require.NoError(t, err)
remoteSig, remoteHtlcSigs, _, err := remoteChannel.SignNextCommitment()
require.NoError(t, err)
require.Equal(t, test.RemoteSigHex, hex.EncodeToString(remoteSig.ToSignatureBytes()))
for i, sig := range remoteHtlcSigs {
require.Equal(t, test.HtlcDescs[i].RemoteSigHex, hex.EncodeToString(sig.ToSignatureBytes()))
}
err = localChannel.ReceiveNewCommitment(remoteSig, remoteHtlcSigs)
require.NoError(t, err)
_, _, err = localChannel.RevokeCurrentCommitment()
require.NoError(t, err)
// Now the local node force closes the channel so that we can inspect
// its state.
forceCloseSum, err := localChannel.ForceClose()
require.NoError(t, err)
// Assert that the commitment transaction itself is as expected.
var txBytes bytes.Buffer
require.NoError(t, forceCloseSum.CloseTx.Serialize(&txBytes))
require.Equal(t, test.ExpectedCommitmentTxHex, hex.EncodeToString(txBytes.Bytes()))
// Obtain the second level transactions that the local node's channel
// state machine has produced. Store them in a map indexed by commit tx
// output index. Also complete the second level transaction with the
// preimage. This is normally done later in the contract resolver.
secondLevelTxes := map[uint32]*wire.MsgTx{}
storeTx := func(index uint32, tx *wire.MsgTx) {
// Prevent overwrites.
_, exists := secondLevelTxes[index]
require.False(t, exists)
secondLevelTxes[index] = tx
}
for _, r := range forceCloseSum.HtlcResolutions.IncomingHTLCs {
successTx := r.SignedSuccessTx
witnessScript := successTx.TxIn[0].Witness[4]
var hash160 [20]byte
copy(hash160[:], witnessScript[69:69+20])
preimage := hash160map[hash160]
successTx.TxIn[0].Witness[3] = preimage[:]
storeTx(r.HtlcPoint().Index, successTx)
}
for _, r := range forceCloseSum.HtlcResolutions.OutgoingHTLCs {
storeTx(r.HtlcPoint().Index, r.SignedTimeoutTx)
}
// Create a list of second level transactions ordered by commit tx
// output index.
var keys []uint32
for k := range secondLevelTxes {
keys = append(keys, k)
}
sort.Slice(keys, func(a, b int) bool {
return keys[a] < keys[b]
})
// Assert that this list matches the test vectors.
for i, idx := range keys {
tx := secondLevelTxes[idx]
var b bytes.Buffer
err := tx.Serialize(&b)
require.NoError(t, err)
require.Equal(
t,
test.HtlcDescs[i].ResolutionTxHex,
hex.EncodeToString(b.Bytes()),
)
}
}
// htlcViewFromHTLCs constructs an htlcView of PaymentDescriptors from a slice
// of channeldb.HTLC structs.
func htlcViewFromHTLCs(htlcs []channeldb.HTLC) *htlcView {
var theHTLCView htlcView
for _, htlc := range htlcs {
paymentDesc := &PaymentDescriptor{
RHash: htlc.RHash,
Timeout: htlc.RefundTimeout,
Amount: htlc.Amt,
}
if htlc.Incoming {
theHTLCView.theirUpdates =
append(theHTLCView.theirUpdates, paymentDesc)
} else {
theHTLCView.ourUpdates =
append(theHTLCView.ourUpdates, paymentDesc)
}
}
return &theHTLCView
}
func TestCommitTxStateHint(t *testing.T) {
t.Parallel()
stateHintTests := []struct {
name string
from uint64
to uint64
inputs int
shouldFail bool
}{
{
name: "states 0 to 1000",
from: 0,
to: 1000,
inputs: 1,
shouldFail: false,
},
{
name: "states 'maxStateHint-1000' to 'maxStateHint'",
from: maxStateHint - 1000,
to: maxStateHint,
inputs: 1,
shouldFail: false,
},
{
name: "state 'maxStateHint+1'",
from: maxStateHint + 1,
to: maxStateHint + 10,
inputs: 1,
shouldFail: true,
},
{
name: "commit transaction with two inputs",
inputs: 2,
shouldFail: true,
},
}
var obfuscator [StateHintSize]byte
copy(obfuscator[:], testHdSeed[:StateHintSize])
timeYesterday := uint32(time.Now().Unix() - 24*60*60)
for _, test := range stateHintTests {
commitTx := wire.NewMsgTx(2)
// Add supplied number of inputs to the commitment transaction.
for i := 0; i < test.inputs; i++ {
commitTx.AddTxIn(&wire.TxIn{})
}
for i := test.from; i <= test.to; i++ {
stateNum := uint64(i)
err := SetStateNumHint(commitTx, stateNum, obfuscator)
if err != nil && !test.shouldFail {
t.Fatalf("unable to set state num %v: %v", i, err)
} else if err == nil && test.shouldFail {
t.Fatalf("Failed(%v): test should fail but did not", test.name)
}
locktime := commitTx.LockTime
sequence := commitTx.TxIn[0].Sequence
// Locktime should not be less than 500,000,000 and not larger
// than the time 24 hours ago. One day should provide a good
// enough buffer for the tests.
if locktime < 5e8 || locktime > timeYesterday {
if !test.shouldFail {
t.Fatalf("The value of locktime (%v) may cause the commitment "+
"transaction to be unspendable", locktime)
}
}
if sequence&wire.SequenceLockTimeDisabled == 0 {
if !test.shouldFail {
t.Fatalf("Sequence locktime is NOT disabled when it should be")
}
}
extractedStateNum := GetStateNumHint(commitTx, obfuscator)
if extractedStateNum != stateNum && !test.shouldFail {
t.Fatalf("state number mismatched, expected %v, got %v",
stateNum, extractedStateNum)
} else if extractedStateNum == stateNum && test.shouldFail {
t.Fatalf("Failed(%v): test should fail but did not", test.name)
}
}
t.Logf("Passed: %v", test.name)
}
}
// testSpendValidation ensures that we're able to spend all outputs in the
// commitment transaction that we create.
func testSpendValidation(t *testing.T, tweakless bool) {
// We generate a fake output, and the corresponding txin. This output
// doesn't need to exist, as we'll only be validating spending from the
// transaction that references this.
txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
if err != nil {
t.Fatalf("unable to create txid: %v", err)
}
fundingOut := &wire.OutPoint{
Hash: *txid,
Index: 50,
}
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
const channelBalance = btcutil.Amount(1 * 10e8)
const csvTimeout = 5
// We also set up set some resources for the commitment transaction.
// Each side currently has 1 BTC within the channel, with a total
// channel capacity of 2BTC.
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
btcec.S256(), testWalletPrivKey,
)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(
btcec.S256(), bobsPrivKey,
)
revocationPreimage := testHdSeed.CloneBytes()
commitSecret, commitPoint := btcec.PrivKeyFromBytes(
btcec.S256(), revocationPreimage,
)
revokePubKey := input.DeriveRevocationPubkey(bobKeyPub, commitPoint)
aliceDelayKey := input.TweakPubKey(aliceKeyPub, commitPoint)
// Bob will have the channel "force closed" on him, so for the sake of
// our commitments, if it's tweakless, his key will just be his regular
// pubkey.
bobPayKey := input.TweakPubKey(bobKeyPub, commitPoint)
channelType := channeldb.SingleFunderBit
if tweakless {
bobPayKey = bobKeyPub
channelType = channeldb.SingleFunderTweaklessBit
}
remoteCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub)
localCommitTweak := input.SingleTweakBytes(commitPoint, bobKeyPub)
aliceSelfOutputSigner := &input.MockSigner{
Privkeys: []*btcec.PrivateKey{aliceKeyPriv},
}
aliceChanCfg := &channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: DefaultDustLimit(),
CsvDelay: csvTimeout,
},
}
bobChanCfg := &channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: DefaultDustLimit(),
CsvDelay: csvTimeout,
},
}
// With all the test data set up, we create the commitment transaction.
// We only focus on a single party's transactions, as the scripts are
// identical with the roles reversed.
//
// This is Alice's commitment transaction, so she must wait a CSV delay
// of 5 blocks before sweeping the output, while bob can spend
// immediately with either the revocation key, or his regular key.
keyRing := &CommitmentKeyRing{
ToLocalKey: aliceDelayKey,
RevocationKey: revokePubKey,
ToRemoteKey: bobPayKey,
}
commitmentTx, err := CreateCommitTx(
channelType, *fakeFundingTxIn, keyRing, aliceChanCfg,
bobChanCfg, channelBalance, channelBalance, 0,
)
if err != nil {
t.Fatalf("unable to create commitment transaction: %v", nil)
}
delayOutput := commitmentTx.TxOut[0]
regularOutput := commitmentTx.TxOut[1]
// We're testing an uncooperative close, output sweep, so construct a
// transaction which sweeps the funds to a random address.
targetOutput, err := input.CommitScriptUnencumbered(aliceKeyPub)
if err != nil {
t.Fatalf("unable to create target output: %v", err)
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{
Hash: commitmentTx.TxHash(),
Index: 0,
}, nil, nil))
sweepTx.AddTxOut(&wire.TxOut{
PkScript: targetOutput,
Value: 0.5 * 10e8,
})
// First, we'll test spending with Alice's key after the timeout.
delayScript, err := input.CommitScriptToSelf(
csvTimeout, aliceDelayKey, revokePubKey,
)
if err != nil {
t.Fatalf("unable to generate alice delay script: %v", err)
}
sweepTx.TxIn[0].Sequence = input.LockTimeToSequence(false, csvTimeout)
signDesc := &input.SignDescriptor{
WitnessScript: delayScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
SingleTweak: remoteCommitTweak,
SigHashes: txscript.NewTxSigHashes(sweepTx),
Output: &wire.TxOut{
Value: int64(channelBalance),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
aliceWitnessSpend, err := input.CommitSpendTimeout(
aliceSelfOutputSigner, signDesc, sweepTx,
)
if err != nil {
t.Fatalf("unable to generate delay commit spend witness: %v", err)
}
sweepTx.TxIn[0].Witness = aliceWitnessSpend
vm, err := txscript.NewEngine(delayOutput.PkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(channelBalance))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("spend from delay output is invalid: %v", err)
}
localSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}}
// Next, we'll test bob spending with the derived revocation key to
// simulate the scenario when Alice broadcasts this commitment
// transaction after it's been revoked.
signDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
DoubleTweak: commitSecret,
WitnessScript: delayScript,
SigHashes: txscript.NewTxSigHashes(sweepTx),
Output: &wire.TxOut{
Value: int64(channelBalance),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
bobWitnessSpend, err := input.CommitSpendRevoke(localSigner, signDesc,
sweepTx)
if err != nil {
t.Fatalf("unable to generate revocation witness: %v", err)
}
sweepTx.TxIn[0].Witness = bobWitnessSpend
vm, err = txscript.NewEngine(delayOutput.PkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(channelBalance))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("revocation spend is invalid: %v", err)
}
// In order to test the final scenario, we modify the TxIn of the sweep
// transaction to instead point to the regular output (non delay)
// within the commitment transaction.
sweepTx.TxIn[0] = &wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: commitmentTx.TxHash(),
Index: 1,
},
}
// Finally, we test bob sweeping his output as normal in the case that
// Alice broadcasts this commitment transaction.
bobScriptP2WKH, err := input.CommitScriptUnencumbered(bobPayKey)
if err != nil {
t.Fatalf("unable to create bob p2wkh script: %v", err)
}
signDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
WitnessScript: bobScriptP2WKH,
SigHashes: txscript.NewTxSigHashes(sweepTx),
Output: &wire.TxOut{
Value: int64(channelBalance),
PkScript: bobScriptP2WKH,
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
if !tweakless {
signDesc.SingleTweak = localCommitTweak
}
bobRegularSpend, err := input.CommitSpendNoDelay(
localSigner, signDesc, sweepTx, tweakless,
)
if err != nil {
t.Fatalf("unable to create bob regular spend: %v", err)
}
sweepTx.TxIn[0].Witness = bobRegularSpend
vm, err = txscript.NewEngine(
regularOutput.PkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(channelBalance),
)
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("bob p2wkh spend is invalid: %v", err)
}
}
// TestCommitmentSpendValidation test the spendability of both outputs within
// the commitment transaction.
//
// The following spending cases are covered by this test:
// * Alice's spend from the delayed output on her commitment transaction.
// * Bob's spend from Alice's delayed output when she broadcasts a revoked
// commitment transaction.
// * Bob's spend from his unencumbered output within Alice's commitment
// transaction.
func TestCommitmentSpendValidation(t *testing.T) {
t.Parallel()
// In the modern network, all channels use the new tweakless format,
// but we also need to support older nodes that want to open channels
// with the legacy format, so we'll test spending in both scenarios.
for _, tweakless := range []bool{true, false} {
tweakless := tweakless
t.Run(fmt.Sprintf("tweak=%v", tweakless), func(t *testing.T) {
testSpendValidation(t, tweakless)
})
}
}
type mockProducer struct {
secret chainhash.Hash
}
func (p *mockProducer) AtIndex(uint64) (*chainhash.Hash, error) {
return &p.secret, nil
}
func (p *mockProducer) Encode(w io.Writer) error {
_, err := w.Write(p.secret[:])
return err
}
// createTestChannelsForVectors creates two LightningChannel instances for the
// test channel that is used to verify the test vectors.
func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelType,
feeRate btcutil.Amount, remoteBalance, localBalance btcutil.Amount) (
*LightningChannel, *LightningChannel, func()) {
t := tc.t
prevOut := &wire.OutPoint{
Hash: *tc.fundingTx.Hash(),
Index: 0,
}
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
// Generate random some keys that don't actually matter but need to be
// set.
var (
remoteDummy1, remoteDummy2 *btcec.PrivateKey
localDummy2, localDummy1 *btcec.PrivateKey
)
generateKeys := []**btcec.PrivateKey{
&remoteDummy1, &remoteDummy2, &localDummy1, &localDummy2,
}
for _, keyRef := range generateKeys {
privkey, err := btcec.NewPrivateKey(btcec.S256())
require.NoError(t, err)
*keyRef = privkey
}
// Define channel configurations.
remoteCfg := channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: tc.dustLimit,
MaxPendingAmount: lnwire.NewMSatFromSatoshis(
tc.fundingAmount,
),
ChanReserve: 0,
MinHTLC: 0,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
CsvDelay: tc.localCsvDelay,
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: tc.remoteFundingPrivkey.PubKey(),
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: tc.remotePaymentBasepointSecret.PubKey(),
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: tc.remotePaymentBasepointSecret.PubKey(),
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: remoteDummy1.PubKey(),
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: tc.remoteRevocationBasepointSecret.PubKey(),
},
}
localCfg := channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: tc.dustLimit,
MaxPendingAmount: lnwire.NewMSatFromSatoshis(
tc.fundingAmount,
),
ChanReserve: 0,
MinHTLC: 0,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
CsvDelay: tc.localCsvDelay,
},
MultiSigKey: keychain.KeyDescriptor{
PubKey: tc.localFundingPrivkey.PubKey(),
},
PaymentBasePoint: keychain.KeyDescriptor{
PubKey: tc.localPaymentBasepointSecret.PubKey(),
},
HtlcBasePoint: keychain.KeyDescriptor{
PubKey: tc.localPaymentBasepointSecret.PubKey(),
},
DelayBasePoint: keychain.KeyDescriptor{
PubKey: tc.localDelayedPaymentBasepointSecret.PubKey(),
},
RevocationBasePoint: keychain.KeyDescriptor{
PubKey: localDummy1.PubKey(),
},
}
// Create mock producers to force usage of the test vector commitment
// point.
remotePreimageProducer := &mockProducer{
secret: chainhash.Hash(tc.localPerCommitSecret),
}
remoteCommitPoint := input.ComputeCommitmentPoint(
tc.localPerCommitSecret[:],
)
localPreimageProducer := &mockProducer{
secret: chainhash.Hash(tc.localPerCommitSecret),
}
localCommitPoint := input.ComputeCommitmentPoint(
tc.localPerCommitSecret[:],
)
// Create temporary databases.
remotePath, err := ioutil.TempDir("", "remotedb")
require.NoError(t, err)
dbRemote, err := channeldb.Open(remotePath)
require.NoError(t, err)
localPath, err := ioutil.TempDir("", "localdb")
require.NoError(t, err)
dbLocal, err := channeldb.Open(localPath)
require.NoError(t, err)
// Create the initial commitment transactions for the channel.
feePerKw := chainfee.SatPerKWeight(feeRate)
commitWeight := int64(input.CommitWeight)
if chanType.HasAnchors() {
commitWeight = input.AnchorCommitWeight
}
commitFee := feePerKw.FeeForWeight(commitWeight)
var anchorAmt btcutil.Amount
if chanType.HasAnchors() {
anchorAmt = 2 * anchorSize
}
remoteCommitTx, localCommitTx, err := CreateCommitmentTxns(
remoteBalance, localBalance-commitFee,
&remoteCfg, &localCfg, remoteCommitPoint,
localCommitPoint, *fundingTxIn, chanType,
)
require.NoError(t, err)
// Set up the full channel state.
// Subtract one because extra sig exchange will take place during setup
// to get to the right test point.
var commitHeight = tc.commitHeight - 1
remoteCommit := channeldb.ChannelCommitment{
CommitHeight: commitHeight,
LocalBalance: lnwire.NewMSatFromSatoshis(remoteBalance),
RemoteBalance: lnwire.NewMSatFromSatoshis(localBalance - commitFee - anchorAmt),
CommitFee: commitFee,
FeePerKw: btcutil.Amount(feePerKw),
CommitTx: remoteCommitTx,
CommitSig: testSigBytes,
}
localCommit := channeldb.ChannelCommitment{
CommitHeight: commitHeight,
LocalBalance: lnwire.NewMSatFromSatoshis(localBalance - commitFee - anchorAmt),
RemoteBalance: lnwire.NewMSatFromSatoshis(remoteBalance),
CommitFee: commitFee,
FeePerKw: btcutil.Amount(feePerKw),
CommitTx: localCommitTx,
CommitSig: testSigBytes,
}
var chanIDBytes [8]byte
_, err = io.ReadFull(rand.Reader, chanIDBytes[:])
require.NoError(t, err)
shortChanID := lnwire.NewShortChanIDFromInt(
binary.BigEndian.Uint64(chanIDBytes[:]),
)
remoteChannelState := &channeldb.OpenChannel{
LocalChanCfg: remoteCfg,
RemoteChanCfg: localCfg,
IdentityPub: remoteDummy2.PubKey(),
FundingOutpoint: *prevOut,
ShortChannelID: shortChanID,
ChanType: chanType,
IsInitiator: false,
Capacity: tc.fundingAmount,
RemoteCurrentRevocation: localCommitPoint,
RevocationProducer: remotePreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: remoteCommit,
RemoteCommitment: remoteCommit,
Db: dbRemote,
Packager: channeldb.NewChannelPackager(shortChanID),
FundingTxn: tc.fundingTx.MsgTx(),
}
localChannelState := &channeldb.OpenChannel{
LocalChanCfg: localCfg,
RemoteChanCfg: remoteCfg,
IdentityPub: localDummy2.PubKey(),
FundingOutpoint: *prevOut,
ShortChannelID: shortChanID,
ChanType: chanType,
IsInitiator: true,
Capacity: tc.fundingAmount,
RemoteCurrentRevocation: remoteCommitPoint,
RevocationProducer: localPreimageProducer,
RevocationStore: shachain.NewRevocationStore(),
LocalCommitment: localCommit,
RemoteCommitment: localCommit,
Db: dbLocal,
Packager: channeldb.NewChannelPackager(shortChanID),
FundingTxn: tc.fundingTx.MsgTx(),
}
// Create mock signers that can sign for the keys that are used.
localSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{
tc.localPaymentBasepointSecret, tc.localDelayedPaymentBasepointSecret,
tc.localFundingPrivkey, localDummy1, localDummy2,
}}
remoteSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{
tc.remoteFundingPrivkey, tc.remoteRevocationBasepointSecret,
tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2,
}}
remotePool := NewSigPool(1, remoteSigner)
channelRemote, err := NewLightningChannel(
remoteSigner, remoteChannelState, remotePool,
)
require.NoError(t, err)
require.NoError(t, remotePool.Start())
localPool := NewSigPool(1, localSigner)
channelLocal, err := NewLightningChannel(
localSigner, localChannelState, localPool,
)
require.NoError(t, err)
require.NoError(t, localPool.Start())
// Create state hunt obfuscator for the commitment transaction.
obfuscator := createStateHintObfuscator(remoteChannelState)
err = SetStateNumHint(
remoteCommitTx, commitHeight, obfuscator,
)
require.NoError(t, err)
err = SetStateNumHint(
localCommitTx, commitHeight, obfuscator,
)
require.NoError(t, err)
// Initialize the database.
addr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18556,
}
require.NoError(t, channelRemote.channelState.SyncPending(addr, 101))
addr = &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 18555,
}
require.NoError(t, channelLocal.channelState.SyncPending(addr, 101))
// Now that the channel are open, simulate the start of a session by
// having local and remote extend their revocation windows to each other.
err = initRevocationWindows(channelRemote, channelLocal)
require.NoError(t, err)
// Return a clean up function that stops goroutines and removes the test
// databases.
cleanUpFunc := func() {
dbLocal.Close()
dbRemote.Close()
os.RemoveAll(localPath)
os.RemoveAll(remotePath)
require.NoError(t, remotePool.Stop())
require.NoError(t, localPool.Stop())
}
return channelRemote, channelLocal, cleanUpFunc
}