123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071 |
- package lnwallet
- import (
- "bytes"
- "crypto/rand"
- "encoding/binary"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io"
- "net"
- "os"
- "sort"
- "strings"
- "testing"
- "time"
- "github.com/btcsuite/btcd/btcec/v2"
- "github.com/btcsuite/btcd/btcutil"
- "github.com/btcsuite/btcd/chaincfg/chainhash"
- "github.com/btcsuite/btcd/txscript"
- "github.com/btcsuite/btcd/wire"
- "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) *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
- }
- type htlc struct {
- incoming bool
- amount lnwire.MilliSatoshi
- expiry uint32
- preimage string
- }
- var testHtlcsSet1 = []htlc{
- // htlc 0.
- {
- incoming: true,
- amount: 1000000,
- expiry: 500,
- preimage: "0000000000000000000000000000000000000000000000000" +
- "000000000000000",
- },
- // htlc 1.
- {
- incoming: true,
- amount: 2000000,
- expiry: 501,
- preimage: "0101010101010101010101010101010101010101010101010" +
- "101010101010101",
- },
- // htlc 2.
- {
- incoming: false,
- amount: 2000000,
- expiry: 502,
- preimage: "0202020202020202020202020202020202020202020202020" +
- "202020202020202",
- },
- // htlc 3.
- {
- incoming: false,
- amount: 3000000,
- expiry: 503,
- preimage: "03030303030303030303030303030303030303030303030303" +
- "03030303030303",
- },
- // htlc 4.
- {
- incoming: true,
- amount: 4000000,
- expiry: 504,
- preimage: "0404040404040404040404040404040404040404040404040" +
- "404040404040404",
- },
- }
- var testHtlcsSet2 = []htlc{
- // htlc 1.
- {
- incoming: true,
- amount: 2000000,
- expiry: 501,
- preimage: "01010101010101010101010101010101010101010101010101" +
- "01010101010101",
- },
- // htlc 5.
- {
- incoming: false,
- amount: 5000000,
- expiry: 506,
- preimage: "05050505050505050505050505050505050505050505050505" +
- "05050505050505",
- },
- // htlc 6.
- {
- incoming: false,
- amount: 5000001,
- expiry: 505,
- preimage: "05050505050505050505050505050505050505050505050505" +
- "05050505050505",
- },
- }
- // 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
- DustLimitSatoshis 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",
- },
- {
- name: "zero fee htlc tx",
- chanType: channeldb.SingleFunderTweaklessBit |
- channeldb.AnchorOutputsBit |
- channeldb.ZeroHtlcTxFeeBit,
- jsonFile: "test_vectors_zero_fee_htlc_tx.json",
- },
- }
- for _, set := range vectorSets {
- set := set
- var testCases []testCase
- jsonText, err := os.ReadFile(set.jsonFile)
- require.NoError(t, err)
- err = json.Unmarshal(jsonText, &testCases)
- require.NoError(t, err)
- for _, test := range testCases {
- test := test
- name := fmt.Sprintf("%s-%s", set.name, test.Name)
- t.Run(name, func(t *testing.T) {
- t.Parallel()
- 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,
- htlcSet []htlc) map[[20]byte]lntypes.Preimage {
- hash160map := make(map[[20]byte]lntypes.Preimage)
- for _, htlc := range htlcSet {
- 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.ChannelPoint())
- msg := &lnwire.UpdateAddHTLC{
- Amount: htlc.amount,
- ChanID: chanID,
- Expiry: htlc.expiry,
- PaymentHash: hash,
- }
- if htlc.incoming {
- htlcID, err := remote.addHTLC(msg, nil, NoBuffer)
- 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, NoBuffer)
- 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)
- // Determine which htlc set to use.
- testHtlcs := testHtlcsSet1
- if strings.Contains(test.Name, "same amount and preimage") {
- testHtlcs = testHtlcsSet2
- }
- // If the test specifies a dust limit, then use it instead of the
- // default.
- if test.DustLimitSatoshis != 0 {
- tc.dustLimit = test.DustLimitSatoshis
- }
- // 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
- }
- }
- }
- // Assert that the remote and local balances add up to the channel
- // capacity.
- require.EqualValues(t, lnwire.NewMSatFromSatoshis(tc.fundingAmount),
- remoteBalance+localBalance)
- // Set up a test channel on which the test commitment transaction is
- // going to be produced.
- remoteChannel, localChannel := createTestChannelsForVectors(
- tc,
- chanType, test.FeePerKw,
- remoteBalance.ToSatoshis(),
- localBalance.ToSatoshis(),
- )
- // 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, testHtlcs,
- )
- }
- // Execute commit dance to arrive at the point where the local node has
- // received the test commitment and the remote signature.
- localNewCommit, err := localChannel.SignNextCommitment()
- require.NoError(t, err, "local unable to sign commitment")
- err = remoteChannel.ReceiveNewCommitment(localNewCommit.CommitSigs)
- require.NoError(t, err)
- revMsg, _, _, err := remoteChannel.RevokeCurrentCommitment()
- require.NoError(t, err)
- _, _, _, _, err = localChannel.ReceiveRevocation(revMsg)
- require.NoError(t, err)
- remoteNewCommit, err := remoteChannel.SignNextCommitment()
- require.NoError(t, err)
- require.Equal(
- t, test.RemoteSigHex,
- hex.EncodeToString(
- remoteNewCommit.CommitSig.ToSignatureBytes(),
- ),
- )
- for i, sig := range remoteNewCommit.HtlcSigs {
- require.Equal(
- t, test.HtlcDescs[i].RemoteSigHex,
- hex.EncodeToString(sig.ToSignatureBytes()),
- )
- }
- err = localChannel.ReceiveNewCommitment(remoteNewCommit.CommitSigs)
- 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()),
- )
- }
- }
- 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())
- require.NoError(t, err, "unable to create txid")
- 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(
- testWalletPrivKey,
- )
- bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(
- bobsPrivKey,
- )
- revocationPreimage := testHdSeed.CloneBytes()
- commitSecret, commitPoint := btcec.PrivKeyFromBytes(
- 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.NewMockSigner(
- []*btcec.PrivateKey{aliceKeyPriv}, nil,
- )
- // Calculate the dust limit we'll use for the test.
- dustLimit := DustLimitForSize(input.UnknownWitnessSize)
- aliceChanCfg := &channeldb.ChannelConfig{
- ChannelConstraints: channeldb.ChannelConstraints{
- DustLimit: dustLimit,
- CsvDelay: csvTimeout,
- },
- }
- bobChanCfg := &channeldb.ChannelConfig{
- ChannelConstraints: channeldb.ChannelConstraints{
- DustLimit: dustLimit,
- 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, true, 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)
- require.NoError(t, err, "unable to create target output")
- 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,
- )
- require.NoError(t, err, "unable to generate alice delay script")
- sweepTx.TxIn[0].Sequence = input.LockTimeToSequence(false, csvTimeout)
- signDesc := &input.SignDescriptor{
- WitnessScript: delayScript,
- KeyDesc: keychain.KeyDescriptor{
- PubKey: aliceKeyPub,
- },
- SingleTweak: remoteCommitTweak,
- SigHashes: input.NewTxSigHashesV0Only(sweepTx),
- Output: &wire.TxOut{
- Value: int64(channelBalance),
- },
- HashType: txscript.SigHashAll,
- InputIndex: 0,
- }
- aliceWitnessSpend, err := input.CommitSpendTimeout(
- aliceSelfOutputSigner, signDesc, sweepTx,
- )
- require.NoError(t, err, "unable to generate delay commit spend witness")
- sweepTx.TxIn[0].Witness = aliceWitnessSpend
- vm, err := txscript.NewEngine(
- delayOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags,
- nil, nil, int64(channelBalance),
- txscript.NewCannedPrevOutputFetcher(nil, 0),
- )
- require.NoError(t, err, "unable to create engine")
- if err := vm.Execute(); err != nil {
- t.Fatalf("spend from delay output is invalid: %v", err)
- }
- localSigner := input.NewMockSigner([]*btcec.PrivateKey{bobKeyPriv}, nil)
- // 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: input.NewTxSigHashesV0Only(sweepTx),
- Output: &wire.TxOut{
- Value: int64(channelBalance),
- },
- HashType: txscript.SigHashAll,
- InputIndex: 0,
- }
- bobWitnessSpend, err := input.CommitSpendRevoke(localSigner, signDesc,
- sweepTx)
- require.NoError(t, err, "unable to generate revocation witness")
- sweepTx.TxIn[0].Witness = bobWitnessSpend
- vm, err = txscript.NewEngine(
- delayOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags,
- nil, nil, int64(channelBalance),
- txscript.NewCannedPrevOutputFetcher(nil, 0),
- )
- require.NoError(t, err, "unable to create engine")
- 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)
- require.NoError(t, err, "unable to create bob p2wkh script")
- signDesc = &input.SignDescriptor{
- KeyDesc: keychain.KeyDescriptor{
- PubKey: bobKeyPub,
- },
- WitnessScript: bobScriptP2WKH,
- SigHashes: input.NewTxSigHashesV0Only(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,
- )
- require.NoError(t, err, "unable to create bob regular spend")
- sweepTx.TxIn[0].Witness = bobRegularSpend
- vm, err = txscript.NewEngine(
- regularOutput.PkScript,
- sweepTx, 0, txscript.StandardVerifyFlags, nil,
- nil, int64(channelBalance),
- txscript.NewCannedPrevOutputFetcher(bobScriptP2WKH, 0),
- )
- require.NoError(t, err, "unable to create engine")
- 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) {
- 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()
- 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.
- dbRemote, err := channeldb.Open(t.TempDir())
- require.NoError(t, err)
- dbLocal, err := channeldb.Open(t.TempDir())
- require.NoError(t, err)
- // Create the initial commitment transactions for the channel.
- feePerKw := chainfee.SatPerKWeight(feeRate)
- commitWeight := lntypes.WeightUnit(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, true, 0,
- )
- 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.ChannelStateDB(),
- 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.ChannelStateDB(),
- Packager: channeldb.NewChannelPackager(shortChanID),
- FundingTxn: tc.fundingTx.MsgTx(),
- }
- // Create mock signers that can sign for the keys that are used.
- localSigner := input.NewMockSigner([]*btcec.PrivateKey{
- tc.localPaymentBasepointSecret, tc.localDelayedPaymentBasepointSecret,
- tc.localFundingPrivkey, localDummy1, localDummy2,
- }, nil)
- remoteSigner := input.NewMockSigner([]*btcec.PrivateKey{
- tc.remoteFundingPrivkey, tc.remoteRevocationBasepointSecret,
- tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2,
- }, nil)
- 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.
- t.Cleanup(func() {
- dbLocal.Close()
- dbRemote.Close()
- require.NoError(t, remotePool.Stop())
- require.NoError(t, localPool.Stop())
- })
- return channelRemote, channelLocal
- }
|