diff --git a/channeldb/channel.go b/channeldb/channel.go index edbe427962..a524c1d926 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -171,6 +171,12 @@ const ( // disk. This bit may be on if the funding transaction was crafted by a // wallet external to the primary daemon. NoFundingTxBit ChannelType = 1 << 2 + + // AnchorOutputsBit indicates that the channel makes use of anchor + // outputs to bump the commitment transaction's effective feerate. This + // channel type also uses a delayed to_remote output script. If bit is + // set, we'll find the size of the anchor outputs in the database. + AnchorOutputsBit ChannelType = 1 << 3 ) // IsSingleFunder returns true if the channel type if one of the known single @@ -196,6 +202,12 @@ func (c ChannelType) HasFundingTx() bool { return c&NoFundingTxBit == 0 } +// HasAnchors returns true if this channel type has anchor ouputs on its +// commitment. +func (c ChannelType) HasAnchors() bool { + return c&AnchorOutputsBit == AnchorOutputsBit +} + // ChannelConstraints represents a set of constraints meant to allow a node to // limit their exposure, enact flow control and ensure that all HTLCs are // economically relevant. This struct will be mirrored for both sides of the @@ -1756,11 +1768,17 @@ func (c *OpenChannel) AppendRemoteCommitChain(diff *CommitDiff) error { // this new pending commitment. Once they revoked their prior state, we'll swap // these pointers, causing the tip and the tail to point to the same entry. func (c *OpenChannel) RemoteCommitChainTip() (*CommitDiff, error) { + return c.RemoteCommitChainTipFrom(false) +} + +// RemoteCommitChainTip returns the "tip" of the current remote commitment +// chain. The confirmed bool indicates which bucket to retrieve the data from. +func (c *OpenChannel) RemoteCommitChainTipFrom(confirmed bool) (*CommitDiff, + error) { + var cd *CommitDiff err := c.Db.View(func(tx *bbolt.Tx) error { - chanBucket, err := fetchChanBucket( - tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, - ) + chanBucket, err := c.fetchChanBucket(tx, confirmed) switch err { case nil: case ErrNoChanDBExists, ErrNoActiveChannels, ErrChannelNotFound: @@ -2427,15 +2445,63 @@ func (c *OpenChannel) Snapshot() *ChannelSnapshot { return snapshot } +// fetchPendingCloseChanBucket is a helper function that returns the bucket +// where a channel's pending close data resides in given the outpoint. +func fetchPendingCloseChanBucket(tx *bbolt.Tx, outPoint *wire.OutPoint) ( + *bbolt.Bucket, error) { + + // First fetch the top level bucket which stores all data related to + // current, active channels. + historicalBucket := tx.Bucket(historicalChannelBucket) + if historicalBucket == nil { + return nil, ErrNoChanDBExists + } + + // With the bucket for the node and chain fetched, we can now go down + // another level, for this channel itself. + var chanPointBuf bytes.Buffer + if err := writeOutpoint(&chanPointBuf, outPoint); err != nil { + return nil, err + } + chanBucket := historicalBucket.Bucket(chanPointBuf.Bytes()) + if chanBucket == nil { + return nil, ErrChannelNotFound + } + + return chanBucket, nil +} + +// fetchChanBucket returns either the open or the confirmed channel bucket. +func (c *OpenChannel) fetchChanBucket(tx *bbolt.Tx, confirmed bool) ( + *bbolt.Bucket, error) { + + if confirmed { + return fetchPendingCloseChanBucket(tx, &c.FundingOutpoint) + } + + return fetchChanBucket( + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, + ) +} + // LatestCommitments returns the two latest commitments for both the local and // remote party. These commitments are read from disk to ensure that only the // latest fully committed state is returned. The first commitment returned is // the local commitment, and the second returned is the remote commitment. -func (c *OpenChannel) LatestCommitments() (*ChannelCommitment, *ChannelCommitment, error) { +func (c *OpenChannel) LatestCommitments() (*ChannelCommitment, + *ChannelCommitment, error) { + + return c.LatestCommitmentsFrom(false) +} + +// LatestCommitmentsFrom returns the two latest commitments for both the local +// and remote party. The confirmed boolean indicates the bucket to read the data +// from. +func (c *OpenChannel) LatestCommitmentsFrom(confirmed bool) (*ChannelCommitment, + *ChannelCommitment, error) { + err := c.Db.View(func(tx *bbolt.Tx) error { - chanBucket, err := fetchChanBucket( - tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, - ) + chanBucket, err := c.fetchChanBucket(tx, confirmed) if err != nil { return err } diff --git a/channeldb/db.go b/channeldb/db.go index c3f6323486..a304d61bca 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -429,23 +429,12 @@ func (d *DB) fetchNodeChannels(chainBucket *bbolt.Bucket) ([]*OpenChannel, error return nil } - // Once we've found a valid channel bucket, we'll extract it - // from the node's chain bucket. - chanBucket := chainBucket.Bucket(chanPoint) - - var outPoint wire.OutPoint - err := readOutpoint(bytes.NewReader(chanPoint), &outPoint) + channel, err := d.fetchChannelFromBucket(chainBucket, chanPoint) if err != nil { return err } - oChannel, err := fetchOpenChannel(chanBucket, &outPoint) - if err != nil { - return fmt.Errorf("unable to read channel data for "+ - "chan_point=%v: %v", outPoint, err) - } - oChannel.Db = d - channels = append(channels, oChannel) + channels = append(channels, channel) return nil }) @@ -456,6 +445,30 @@ func (d *DB) fetchNodeChannels(chainBucket *bbolt.Bucket) ([]*OpenChannel, error return channels, nil } +// fetchChannelFromBucket fetches the channel identifier by chanPoint from the +// given bucket. +func (d *DB) fetchChannelFromBucket(bucket *bbolt.Bucket, chanPoint []byte) ( + *OpenChannel, error) { + + // Once we've found a valid channel bucket, we'll extract it + // from the node's chain bucket. + chanBucket := bucket.Bucket(chanPoint) + + var outPoint wire.OutPoint + err := readOutpoint(bytes.NewReader(chanPoint), &outPoint) + if err != nil { + return nil, err + } + oChannel, err := fetchOpenChannel(chanBucket, &outPoint) + if err != nil { + return nil, fmt.Errorf("unable to read channel data for "+ + "chan_point=%v: %v", outPoint, err) + } + oChannel.Db = d + + return oChannel, nil +} + // FetchChannel attempts to locate a channel specified by the passed channel // point. If the channel cannot be found, then an error will be returned. func (d *DB) FetchChannel(chanPoint wire.OutPoint) (*OpenChannel, error) { diff --git a/fundingmanager.go b/fundingmanager.go index 09d5807b8f..3ffb51a46b 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -1241,6 +1241,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { Flags: msg.ChannelFlags, MinConfs: 1, Tweakless: tweaklessCommitment, + AnchorOutputs: false, } reservation, err := f.cfg.Wallet.InitChannelReservation(req) @@ -2907,6 +2908,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { MinConfs: msg.minConfs, Tweakless: tweaklessCommitment, ChanFunder: msg.chanFunder, + AnchorOutputs: false, } reservation, err := f.cfg.Wallet.InitChannelReservation(req) diff --git a/input/script_utils.go b/input/script_utils.go index 8b47b05681..2085885656 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -150,6 +150,9 @@ func Ripemd160H(d []byte) []byte { // * The receiver of the HTLC sweeping all the funds in the case that a // revoked commitment transaction bearing this HTLC was broadcast. // +// If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation +// cases, to allow sweeping only after confirmation. +// // Possible Input Scripts: // SENDR: <0> <0> (spend using HTLC timeout transaction) // RECVR: @@ -168,9 +171,11 @@ func Ripemd160H(d []byte) []byte { // OP_HASH160 OP_EQUALVERIFY // OP_CHECKSIG // OP_ENDIF +// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. // OP_ENDIF func SenderHTLCScript(senderHtlcKey, receiverHtlcKey, - revocationKey *btcec.PublicKey, paymentHash []byte) ([]byte, error) { + revocationKey *btcec.PublicKey, paymentHash []byte, + confirmedSpend bool) ([]byte, error) { builder := txscript.NewScriptBuilder() @@ -243,6 +248,14 @@ func SenderHTLCScript(senderHtlcKey, receiverHtlcKey, // Close out the OP_IF statement above. builder.AddOp(txscript.OP_ENDIF) + // Add 1 block CSV delay if a confirmation is rquired for the + // non-revocation clauses. + if confirmedSpend { + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + } + // Close out the OP_IF statement at the top of the script. builder.AddOp(txscript.OP_ENDIF) @@ -362,6 +375,9 @@ func SenderHtlcSpendTimeout(receiverSig []byte, signer Signer, // * The sender of the HTLC sweeps the HTLC on-chain after the timeout period // of the HTLC has passed. // +// If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation +// cases, to allow sweeping only after confirmation. +// // Possible Input Scripts: // RECVR: <0> (spend using HTLC success transaction) // REVOK: @@ -381,10 +397,11 @@ func SenderHtlcSpendTimeout(receiverSig []byte, signer Signer, // OP_DROP OP_CHECKLOCKTIMEVERIFY OP_DROP // OP_CHECKSIG // OP_ENDIF +// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. // OP_ENDIF func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, - paymentHash []byte) ([]byte, error) { + paymentHash []byte, confirmedSpend bool) ([]byte, error) { builder := txscript.NewScriptBuilder() @@ -467,6 +484,14 @@ func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, // Close out the inner if statement. builder.AddOp(txscript.OP_ENDIF) + // Add 1 block CSV delay for non-revocation clauses if confirmation is + // required. + if confirmedSpend { + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + } + // Close out the outer if statement. builder.AddOp(txscript.OP_ENDIF) @@ -828,18 +853,6 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) return builder.Script() } -// CommitScriptUnencumbered constructs the public key script on the commitment -// transaction paying to the "other" party. The constructed output is a normal -// p2wkh output spendable immediately, requiring no contestation period. -func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { - // This script goes to the "other" party, and is spendable immediately. - builder := txscript.NewScriptBuilder() - builder.AddOp(txscript.OP_0) - builder.AddData(btcutil.Hash160(key.SerializeCompressed())) - - return builder.Script() -} - // CommitSpendTimeout constructs a valid witness allowing the owner of a // particular commitment transaction to spend the output returning settled // funds back to themselves after a relative block timeout. In order to @@ -948,6 +961,139 @@ func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor, return witness, nil } +// CommitScriptUnencumbered constructs the public key script on the commitment +// transaction paying to the "other" party. The constructed output is a normal +// p2wkh output spendable immediately, requiring no contestation period. +func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { + // This script goes to the "other" party, and is spendable immediately. + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_0) + builder.AddData(btcutil.Hash160(key.SerializeCompressed())) + + return builder.Script() +} + +// CommitScriptToRemoteDelayed constructs the script for the delayed output on +// the commitment transaction paying to the remote party of said commitment +// transaction. The money can only be spend after the timeout has passed. +// +// Possible Input Scripts: +// SWEEP: +// +// Output Script: +// OP_CHECKSIGVERIFY +// OP_CHECKSEQUENCEVERIFY +func CommitScriptToRemoteDelayed(csvTimeout uint32, + key *btcec.PublicKey) ([]byte, error) { + + builder := txscript.NewScriptBuilder() + + // Only the given key can spend the output. + builder.AddData(key.SerializeCompressed()) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + + // Check that the CSV delay has passed. + builder.AddInt64(int64(csvTimeout)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + return builder.Script() +} + +// CommitSpendToRemoteDelayed constructs a valid witness allowing a node to +// spend their settled delayed output on the counterparty's commitment +// transaction. This is used for the anchor channel type. The spending key will +// always be non-tweaked for this output type. +func CommitSpendToRemoteDelayed(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx) (wire.TxWitness, error) { + + if signDesc.KeyDesc.PubKey == nil { + return nil, fmt.Errorf("cannot generate witness with nil " + + "KeyDesc pubkey") + } + + // Similar to non delayed output, only a signature is needed. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // Finally, we'll manually craft the witness. The witness here is the + // signature and the redeem script. + witnessStack := make([][]byte, 2) + witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[1] = signDesc.WitnessScript + + return witnessStack, nil +} + +// CommitScriptAnchor constructs the script for the anchor output spendable by +// the given key immediately, or by anyone after 16 confirmations. +// +// Possible Input Scripts: +// By owner: +// By anyone (after 16 conf): +// +// Output Script: +// OP_CHECKSIG OP_IFDUP +// OP_NOTIF +// OP_16 OP_CSV +// OP_ENDIF +func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) { + builder := txscript.NewScriptBuilder() + + // Spend immediately with key. + builder.AddData(key.SerializeCompressed()) + builder.AddOp(txscript.OP_CHECKSIG) + + // Duplicate the value if true, since it will be consumed by the NOTIF. + builder.AddOp(txscript.OP_IFDUP) + + // Otherwise spendable by anyone after 16 confirmations. + builder.AddOp(txscript.OP_NOTIF) + builder.AddOp(txscript.OP_16) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_ENDIF) + + return builder.Script() +} + +// CommitSpendAnchor constructs a valid witness allowing a node to spend their +// anchor output on the commitment transaction using their funding key. This is +// used for the anchor channel type. +func CommitSpendAnchor(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx) (wire.TxWitness, error) { + + if signDesc.KeyDesc.PubKey == nil { + return nil, fmt.Errorf("cannot generate witness with nil " + + "KeyDesc pubkey") + } + + // Create a signature. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // The witness here is just a signature and the redeem script. + witnessStack := make([][]byte, 2) + witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[1] = signDesc.WitnessScript + + return witnessStack, nil +} + +// CommitSpendAnchorAnyone constructs a witness allowing anyone to spend the +// anchor output after it has gotten 16 confirmations. Since no signing is +// required, only knowledge of the redeem script is necessary to spend it. +func CommitSpendAnchorAnyone(script []byte) (wire.TxWitness, error) { + // The witness here is just the redeem script. + witnessStack := make([][]byte, 2) + witnessStack[0] = nil + witnessStack[1] = script + + return witnessStack, nil +} + // SingleTweakBytes computes set of bytes we call the single tweak. The purpose // of the single tweak is to randomize all regular delay and payment base // points. To do this, we generate a hash that binds the commitment point to diff --git a/input/script_utils_test.go b/input/script_utils_test.go index 1c6c5bc9e5..3fde34e8af 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -15,6 +15,73 @@ import ( "github.com/lightningnetwork/lnd/keychain" ) +// assertEngineExecution executes the VM returned by the newEngine closure, +// asserting the result matches the validity expectation. In the case where it +// doesn't match the expectation, it executes the script step-by-step and +// prints debug information to stdout. +func assertEngineExecution(t *testing.T, testNum int, valid bool, + newEngine func() (*txscript.Engine, error)) { + t.Helper() + + // Get a new VM to execute. + vm, err := newEngine() + if err != nil { + t.Fatalf("unable to create engine: %v", err) + } + + // Execute the VM, only go on to the step-by-step execution if + // it doesn't validate as expected. + vmErr := vm.Execute() + if valid == (vmErr == nil) { + return + } + + // Now that the execution didn't match what we expected, fetch a new VM + // to step through. + vm, err = newEngine() + if err != nil { + t.Fatalf("unable to create engine: %v", err) + } + + // This buffer will trace execution of the Script, dumping out + // to stdout. + var debugBuf bytes.Buffer + + done := false + for !done { + dis, err := vm.DisasmPC() + if err != nil { + t.Fatalf("stepping (%v)\n", err) + } + debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) + + done, err = vm.Step() + if err != nil && valid { + fmt.Println(debugBuf.String()) + t.Fatalf("spend test case #%v failed, spend "+ + "should be valid: %v", testNum, err) + } else if err == nil && !valid && done { + fmt.Println(debugBuf.String()) + t.Fatalf("spend test case #%v succeed, spend "+ + "should be invalid: %v", testNum, err) + } + + debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) + debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) + } + + // If we get to this point the unexpected case was not reached + // during step execution, which happens for some checks, like + // the clean-stack rule. + validity := "invalid" + if valid { + validity = "valid" + } + + fmt.Println(debugBuf.String()) + t.Fatalf("%v spend test case #%v execution ended with: %v", validity, testNum, vmErr) +} + // TestRevocationKeyDerivation tests that given a public key, and a revocation // hash, the homomorphic revocation public and private key derivation work // properly. @@ -145,43 +212,6 @@ func TestHTLCSenderSpendValidation(t *testing.T) { // we'll be using Bob's base point for the revocation key. revocationKey := DeriveRevocationPubkey(bobKeyPub, commitPoint) - // Generate the raw HTLC redemption scripts, and its p2wsh counterpart. - htlcWitnessScript, err := SenderHTLCScript(aliceLocalKey, bobLocalKey, - revocationKey, paymentHash[:]) - if err != nil { - t.Fatalf("unable to create htlc sender script: %v", err) - } - htlcPkScript, err := WitnessScriptHash(htlcWitnessScript) - if err != nil { - t.Fatalf("unable to create p2wsh htlc script: %v", err) - } - - // This will be Alice's commitment transaction. In this scenario Alice - // is sending an HTLC to a node she has a path to (could be Bob, could - // be multiple hops down, it doesn't really matter). - htlcOutput := &wire.TxOut{ - Value: int64(paymentAmt), - PkScript: htlcPkScript, - } - senderCommitTx := wire.NewMsgTx(2) - senderCommitTx.AddTxIn(fakeFundingTxIn) - senderCommitTx.AddTxOut(htlcOutput) - - prevOut := &wire.OutPoint{ - Hash: senderCommitTx.TxHash(), - Index: 0, - } - - sweepTx := wire.NewMsgTx(2) - sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil)) - sweepTx.AddTxOut( - &wire.TxOut{ - PkScript: []byte("doesn't matter"), - Value: 1 * 10e8, - }, - ) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) - bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub) aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub) @@ -191,23 +221,86 @@ func TestHTLCSenderSpendValidation(t *testing.T) { bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} - // We'll also generate a signature on the sweep transaction above - // that will act as Bob's signature to Alice for the second level HTLC - // transaction. - bobSignDesc := SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - PubKey: bobKeyPub, - }, - SingleTweak: bobCommitTweak, - WitnessScript: htlcWitnessScript, - Output: htlcOutput, - HashType: txscript.SigHashAll, - SigHashes: sweepTxSigHashes, - InputIndex: 0, - } - bobRecvrSig, err := bobSigner.SignOutputRaw(sweepTx, &bobSignDesc) - if err != nil { - t.Fatalf("unable to generate alice signature: %v", err) + var ( + htlcWitnessScript, htlcPkScript []byte + htlcOutput *wire.TxOut + sweepTxSigHashes *txscript.TxSigHashes + senderCommitTx, sweepTx *wire.MsgTx + bobRecvrSig []byte + ) + + // genCommitTx generates a commitment tx where the htlc output requires + // confirmation to be sent according to 'confirmed'. + genCommitTx := func(confirmed bool) { + // Generate the raw HTLC redemption scripts, and its p2wsh + // counterpart. + htlcWitnessScript, err = SenderHTLCScript( + aliceLocalKey, bobLocalKey, revocationKey, + paymentHash[:], confirmed, + ) + if err != nil { + t.Fatalf("unable to create htlc sender script: %v", err) + } + htlcPkScript, err = WitnessScriptHash(htlcWitnessScript) + if err != nil { + t.Fatalf("unable to create p2wsh htlc script: %v", err) + } + + // This will be Alice's commitment transaction. In this + // scenario Alice is sending an HTLC to a node she has a path + // to (could be Bob, could be multiple hops down, it doesn't + // really matter). + htlcOutput = &wire.TxOut{ + Value: int64(paymentAmt), + PkScript: htlcPkScript, + } + senderCommitTx = wire.NewMsgTx(2) + senderCommitTx.AddTxIn(fakeFundingTxIn) + senderCommitTx.AddTxOut(htlcOutput) + } + + // genSweepTx generates a swepp of the senderCommitTx, and sets the + // sequence if locktime is true. + genSweepTx := func(locktime bool) { + prevOut := &wire.OutPoint{ + Hash: senderCommitTx.TxHash(), + Index: 0, + } + + sweepTx = wire.NewMsgTx(2) + + sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil)) + if locktime { + sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1) + } + + sweepTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + + sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx) + + // We'll also generate a signature on the sweep transaction above + // that will act as Bob's signature to Alice for the second level HTLC + // transaction. + bobSignDesc := SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + SingleTweak: bobCommitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + bobRecvrSig, err = bobSigner.SignOutputRaw(sweepTx, &bobSignDesc) + if err != nil { + t.Fatalf("unable to generate alice signature: %v", err) + } } testCases := []struct { @@ -218,6 +311,9 @@ func TestHTLCSenderSpendValidation(t *testing.T) { // revoke w/ sig // TODO(roasbeef): test invalid revoke makeWitnessTestCase(t, func() (wire.TxWitness, error) { + genCommitTx(false) + genSweepTx(false) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: bobKeyPub, @@ -238,6 +334,9 @@ func TestHTLCSenderSpendValidation(t *testing.T) { { // HTLC with invalid preimage size makeWitnessTestCase(t, func() (wire.TxWitness, error) { + genCommitTx(false) + genSweepTx(false) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: bobKeyPub, @@ -261,6 +360,37 @@ func TestHTLCSenderSpendValidation(t *testing.T) { // HTLC with valid preimage size + sig // TODO(roasbeef): invalid preimage makeWitnessTestCase(t, func() (wire.TxWitness, error) { + genCommitTx(false) + genSweepTx(false) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + SingleTweak: bobCommitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return SenderHtlcSpendRedeem(bobSigner, signDesc, + sweepTx, paymentPreimage) + }), + true, + }, + { + // HTLC with valid preimage size + sig, and with + // enforced locktime in HTLC script. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + // Make a commit tx that needs confirmation for + // HTLC output to be spent. + genCommitTx(true) + + // Generate a sweep with the locktime set. + genSweepTx(true) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: bobKeyPub, @@ -278,11 +408,73 @@ func TestHTLCSenderSpendValidation(t *testing.T) { }), true, }, + { + // HTLC with valid preimage size + sig, but trying to + // spend CSV output without sequence set. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + // Generate commitment tx with 1 CSV locked + // HTLC. + genCommitTx(true) + + // Generate sweep tx that doesn't have locktime + // enabled. + genSweepTx(false) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: bobKeyPub, + }, + SingleTweak: bobCommitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return SenderHtlcSpendRedeem(bobSigner, signDesc, + sweepTx, paymentPreimage) + }), + false, + }, + + { + // valid spend to the transition the state of the HTLC + // output with the second level HTLC timeout + // transaction. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + genCommitTx(false) + genSweepTx(false) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + SingleTweak: aliceCommitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return SenderHtlcSpendTimeout(bobRecvrSig, aliceSigner, + signDesc, sweepTx) + }), + true, + }, { // valid spend to the transition the state of the HTLC // output with the second level HTLC timeout // transaction. makeWitnessTestCase(t, func() (wire.TxWitness, error) { + // Make a commit tx that needs confirmation for + // HTLC output to be spent. + genCommitTx(true) + + // Generate a sweep with the locktime set. + genSweepTx(true) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: aliceKeyPub, @@ -300,6 +492,36 @@ func TestHTLCSenderSpendValidation(t *testing.T) { }), true, }, + { + // valid spend to the transition the state of the HTLC + // output with the second level HTLC timeout + // transaction. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + // Generate commitment tx with 1 CSV locked + // HTLC. + genCommitTx(true) + + // Generate sweep tx that doesn't have locktime + // enabled. + genSweepTx(false) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + SingleTweak: aliceCommitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return SenderHtlcSpendTimeout(bobRecvrSig, aliceSigner, + signDesc, sweepTx) + }), + false, + }, } // TODO(roasbeef): set of cases to ensure able to sign w/ keypath and @@ -308,39 +530,13 @@ func TestHTLCSenderSpendValidation(t *testing.T) { for i, testCase := range testCases { sweepTx.TxIn[0].Witness = testCase.witness() - vm, err := txscript.NewEngine(htlcPkScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(paymentAmt)) - if err != nil { - t.Fatalf("unable to create engine: %v", err) + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine(htlcPkScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, int64(paymentAmt)) } - // This buffer will trace execution of the Script, only dumping - // out to stdout in the case that a test fails. - var debugBuf bytes.Buffer - - done := false - for !done { - dis, err := vm.DisasmPC() - if err != nil { - t.Fatalf("stepping (%v)\n", err) - } - debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) - - done, err = vm.Step() - if err != nil && testCase.valid { - fmt.Println(debugBuf.String()) - t.Fatalf("spend test case #%v failed, spend "+ - "should be valid: %v", i, err) - } else if err == nil && !testCase.valid && done { - fmt.Println(debugBuf.String()) - t.Fatalf("spend test case #%v succeed, spend "+ - "should be invalid: %v", i, err) - } - - debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) - debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) - } + assertEngineExecution(t, i, testCase.valid, newEngine) } } @@ -400,46 +596,6 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { // be using Alice's base point for the revocation key. revocationKey := DeriveRevocationPubkey(aliceKeyPub, commitPoint) - // Generate the raw HTLC redemption scripts, and its p2wsh counterpart. - htlcWitnessScript, err := ReceiverHTLCScript(cltvTimeout, aliceLocalKey, - bobLocalKey, revocationKey, paymentHash[:]) - if err != nil { - t.Fatalf("unable to create htlc sender script: %v", err) - } - htlcPkScript, err := WitnessScriptHash(htlcWitnessScript) - if err != nil { - t.Fatalf("unable to create p2wsh htlc script: %v", err) - } - - // This will be Bob's commitment transaction. In this scenario Alice is - // sending an HTLC to a node she has a path to (could be Bob, could be - // multiple hops down, it doesn't really matter). - htlcOutput := &wire.TxOut{ - Value: int64(paymentAmt), - PkScript: htlcWitnessScript, - } - - receiverCommitTx := wire.NewMsgTx(2) - receiverCommitTx.AddTxIn(fakeFundingTxIn) - receiverCommitTx.AddTxOut(htlcOutput) - - prevOut := &wire.OutPoint{ - Hash: receiverCommitTx.TxHash(), - Index: 0, - } - - sweepTx := wire.NewMsgTx(2) - sweepTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *prevOut, - }) - sweepTx.AddTxOut( - &wire.TxOut{ - PkScript: []byte("doesn't matter"), - Value: 1 * 10e8, - }, - ) - sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) - bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub) aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub) @@ -449,23 +605,82 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} - // We'll also generate a signature on the sweep transaction above - // that will act as Alice's signature to Bob for the second level HTLC - // transaction. - aliceSignDesc := SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - PubKey: aliceKeyPub, - }, - SingleTweak: aliceCommitTweak, - WitnessScript: htlcWitnessScript, - Output: htlcOutput, - HashType: txscript.SigHashAll, - SigHashes: sweepTxSigHashes, - InputIndex: 0, - } - aliceSenderSig, err := aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc) - if err != nil { - t.Fatalf("unable to generate alice signature: %v", err) + var ( + htlcWitnessScript, htlcPkScript []byte + htlcOutput *wire.TxOut + receiverCommitTx, sweepTx *wire.MsgTx + sweepTxSigHashes *txscript.TxSigHashes + aliceSenderSig []byte + ) + + genCommitTx := func(confirmed bool) { + // Generate the raw HTLC redemption scripts, and its p2wsh + // counterpart. + htlcWitnessScript, err = ReceiverHTLCScript( + cltvTimeout, aliceLocalKey, bobLocalKey, revocationKey, + paymentHash[:], confirmed, + ) + if err != nil { + t.Fatalf("unable to create htlc sender script: %v", err) + } + htlcPkScript, err = WitnessScriptHash(htlcWitnessScript) + if err != nil { + t.Fatalf("unable to create p2wsh htlc script: %v", err) + } + + // This will be Bob's commitment transaction. In this scenario Alice is + // sending an HTLC to a node she has a path to (could be Bob, could be + // multiple hops down, it doesn't really matter). + htlcOutput = &wire.TxOut{ + Value: int64(paymentAmt), + PkScript: htlcWitnessScript, + } + + receiverCommitTx = wire.NewMsgTx(2) + receiverCommitTx.AddTxIn(fakeFundingTxIn) + receiverCommitTx.AddTxOut(htlcOutput) + } + + genSweepTx := func(locktime bool) { + prevOut := &wire.OutPoint{ + Hash: receiverCommitTx.TxHash(), + Index: 0, + } + + sweepTx = wire.NewMsgTx(2) + sweepTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: *prevOut, + }) + if locktime { + sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1) + } + + sweepTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx) + + // We'll also generate a signature on the sweep transaction above + // that will act as Alice's signature to Bob for the second level HTLC + // transaction. + aliceSignDesc := SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + SingleTweak: aliceCommitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + aliceSenderSig, err = aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc) + if err != nil { + t.Fatalf("unable to generate alice signature: %v", err) + } } // TODO(roasbeef): modify valid to check precise script errors? @@ -476,6 +691,9 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { { // HTLC redemption w/ invalid preimage size makeWitnessTestCase(t, func() (wire.TxWitness, error) { + genCommitTx(false) + genSweepTx(false) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: bobKeyPub, @@ -495,9 +713,41 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { }), false, }, + + { + // revoke w/ sig + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + genCommitTx(false) + genSweepTx(false) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + DoubleTweak: commitSecret, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return ReceiverHtlcSpendRevokeWithKey(aliceSigner, + signDesc, revocationKey, sweepTx) + }), + true, + }, { - // HTLC redemption w/ valid preimage size + // HTLC redemption w/ valid preimage size, and with + // enforced locktime in HTLC scripts. makeWitnessTestCase(t, func() (wire.TxWitness, error) { + // Make a commit tx that needs confirmation for + // HTLC output to be spent. + genCommitTx(true) + + // Generate a sweep with the locktime set. + genSweepTx(true) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: bobKeyPub, @@ -517,13 +767,22 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { true, }, { - // revoke w/ sig + // HTLC redemption w/ valid preimage size, but trying + // to spend CSV output without sequence set. makeWitnessTestCase(t, func() (wire.TxWitness, error) { + // Generate commitment tx with 1 CSV locked + // HTLC. + genCommitTx(true) + + // Generate sweep tx that doesn't have locktime + // enabled. + genSweepTx(false) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ - PubKey: aliceKeyPub, + PubKey: bobKeyPub, }, - DoubleTweak: commitSecret, + SingleTweak: bobCommitTweak, WitnessScript: htlcWitnessScript, Output: htlcOutput, HashType: txscript.SigHashAll, @@ -531,14 +790,19 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { InputIndex: 0, } - return ReceiverHtlcSpendRevokeWithKey(aliceSigner, - signDesc, revocationKey, sweepTx) + return ReceiverHtlcSpendRedeem(aliceSenderSig, + paymentPreimage, bobSigner, + signDesc, sweepTx) }), - true, + false, }, + { // refund w/ invalid lock time makeWitnessTestCase(t, func() (wire.TxWitness, error) { + genCommitTx(false) + genSweepTx(false) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: aliceKeyPub, @@ -559,6 +823,9 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { { // refund w/ valid lock time makeWitnessTestCase(t, func() (wire.TxWitness, error) { + genCommitTx(false) + genSweepTx(false) + signDesc := &SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: aliceKeyPub, @@ -576,42 +843,75 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { }), true, }, + { + // refund w/ valid lock time, and enforced locktime in + // HTLC scripts. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + // Make a commit tx that needs confirmation for + // HTLC output to be spent. + genCommitTx(true) + + // Generate a sweep with the locktime set. + genSweepTx(true) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + SingleTweak: aliceCommitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return ReceiverHtlcSpendTimeout(aliceSigner, signDesc, + sweepTx, int32(cltvTimeout)) + }), + true, + }, + { + // refund w/ valid lock time, but no sequence set in + // sweep tx trying to spend CSV locked HTLC output. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + // Generate commitment tx with 1 CSV locked + // HTLC. + genCommitTx(true) + + // Generate sweep tx that doesn't have locktime + // enabled. + genSweepTx(false) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + SingleTweak: aliceCommitTweak, + WitnessScript: htlcWitnessScript, + Output: htlcOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return ReceiverHtlcSpendTimeout(aliceSigner, signDesc, + sweepTx, int32(cltvTimeout)) + }), + false, + }, } for i, testCase := range testCases { sweepTx.TxIn[0].Witness = testCase.witness() - vm, err := txscript.NewEngine(htlcPkScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(paymentAmt)) - if err != nil { - t.Fatalf("unable to create engine: %v", err) + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine(htlcPkScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, int64(paymentAmt)) } - // This buffer will trace execution of the Script, only dumping - // out to stdout in the case that a test fails. - var debugBuf bytes.Buffer - - done := false - for !done { - dis, err := vm.DisasmPC() - if err != nil { - t.Fatalf("stepping (%v)\n", err) - } - debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) - - done, err = vm.Step() - if err != nil && testCase.valid { - fmt.Println(debugBuf.String()) - t.Fatalf("spend test case #%v failed, spend should be valid: %v", i, err) - } else if err == nil && !testCase.valid && done { - fmt.Println(debugBuf.String()) - t.Fatalf("spend test case #%v succeed, spend should be invalid: %v", i, err) - } - - debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) - debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) - } + assertEngineExecution(t, i, testCase.valid, newEngine) } } @@ -811,39 +1111,228 @@ func TestSecondLevelHtlcSpends(t *testing.T) { for i, testCase := range testCases { sweepTx.TxIn[0].Witness = testCase.witness() - vm, err := txscript.NewEngine(htlcPkScript, - sweepTx, 0, txscript.StandardVerifyFlags, nil, - nil, int64(htlcAmt)) - if err != nil { - t.Fatalf("unable to create engine: %v", err) + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine(htlcPkScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, int64(htlcAmt)) + } + + assertEngineExecution(t, i, testCase.valid, newEngine) + } +} + +// TestCommitSpendToRemoteDelayed checks that the delayed version of the +// to_remote version can only be spwnt by the owner, and after the specified +// delay. +func TestCommitSpendToRemoteDelayed(t *testing.T) { + t.Parallel() + + const csvDelay = 1024 + const outputVal = btcutil.Amount(2 * 10e8) + + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), + testWalletPrivKey) + + txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) + if err != nil { + t.Fatalf("unable to create txid: %v", err) + } + commitOut := &wire.OutPoint{ + Hash: *txid, + Index: 0, + } + commitScript, err := CommitScriptToRemoteDelayed(csvDelay, aliceKeyPub) + if err != nil { + t.Fatalf("unable to create htlc script: %v", err) + } + commitPkScript, err := WitnessScriptHash(commitScript) + if err != nil { + t.Fatalf("unable to create htlc output: %v", err) + } + + commitOutput := &wire.TxOut{ + PkScript: commitPkScript, + Value: int64(outputVal), + } + + sweepTx := wire.NewMsgTx(2) + sweepTx.AddTxIn(wire.NewTxIn(commitOut, nil, nil)) + sweepTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + + aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} + + testCases := []struct { + witness func() wire.TxWitness + valid bool + }{ + { + // Alice can spend after the given CSV delay has passed. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, csvDelay) + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendToRemoteDelayed(aliceSigner, signDesc, + sweepTx) + }), + true, + }, + { + // Alice cannot spend output without sequence set. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + WitnessScript: commitScript, + Output: commitOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendToRemoteDelayed(aliceSigner, signDesc, + sweepTx) + }), + false, + }, + } + + for i, testCase := range testCases { + sweepTx.TxIn[0].Witness = testCase.witness() + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine(commitPkScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, int64(outputVal)) } - // This buffer will trace execution of the Script, only dumping - // out to stdout in the case that a test fails. - var debugBuf bytes.Buffer - - done := false - for !done { - dis, err := vm.DisasmPC() - if err != nil { - t.Fatalf("stepping (%v)\n", err) - } - debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) - - done, err = vm.Step() - if err != nil && testCase.valid { - fmt.Println(debugBuf.String()) - t.Fatalf("spend test case #%v failed, spend "+ - "should be valid: %v", i, err) - } else if err == nil && !testCase.valid && done { - fmt.Println(debugBuf.String()) - t.Fatalf("spend test case #%v succeed, spend "+ - "should be invalid: %v", i, err) - } - - debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) - debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) + assertEngineExecution(t, i, testCase.valid, newEngine) + } +} + +// TestSpendAnchor checks that we can spend the anchors using the various spend +// paths. +func TestSpendAnchor(t *testing.T) { + t.Parallel() + + const anchorSize = 294 + + // First we'll set up some initial key state for Alice. + aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), + testWalletPrivKey) + + // Create a fake anchor outpoint that we'll use to generate the + // sweeping transaction. + txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) + if err != nil { + t.Fatalf("unable to create txid: %v", err) + } + anchorOutPoint := &wire.OutPoint{ + Hash: *txid, + Index: 0, + } + + sweepTx := wire.NewMsgTx(2) + sweepTx.AddTxIn(wire.NewTxIn(anchorOutPoint, nil, nil)) + sweepTx.AddTxOut( + &wire.TxOut{ + PkScript: []byte("doesn't matter"), + Value: 1 * 10e8, + }, + ) + + // Generate the anchor script that can be spent by Alice immediately, + // or by anyone after 16 blocks. + anchorScript, err := CommitScriptAnchor(aliceKeyPub) + if err != nil { + t.Fatalf("unable to create htlc script: %v", err) + } + anchorPkScript, err := WitnessScriptHash(anchorScript) + if err != nil { + t.Fatalf("unable to create htlc output: %v", err) + } + + anchorOutput := &wire.TxOut{ + PkScript: anchorPkScript, + Value: int64(anchorSize), + } + + // Create mock signer for Alice. + aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} + + testCases := []struct { + witness func() wire.TxWitness + valid bool + }{ + { + // Alice can spend immediately. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum + sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: aliceKeyPub, + }, + WitnessScript: anchorScript, + Output: anchorOutput, + HashType: txscript.SigHashAll, + SigHashes: sweepTxSigHashes, + InputIndex: 0, + } + + return CommitSpendAnchor(aliceSigner, signDesc, + sweepTx) + }), + true, + }, + { + // Anyone can spend after 16 blocks. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 16) + return CommitSpendAnchorAnyone(anchorScript) + }), + true, + }, + { + // Anyone cannot spend before 16 blocks. + makeWitnessTestCase(t, func() (wire.TxWitness, error) { + sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 15) + return CommitSpendAnchorAnyone(anchorScript) + }), + false, + }, + } + + for i, testCase := range testCases { + sweepTx.TxIn[0].Witness = testCase.witness() + + newEngine := func() (*txscript.Engine, error) { + return txscript.NewEngine(anchorPkScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, int64(anchorSize)) } + + assertEngineExecution(t, i, testCase.valid, newEngine) } } diff --git a/input/size.go b/input/size.go index 68fa85e4e6..542be08b73 100644 --- a/input/size.go +++ b/input/size.go @@ -122,6 +122,12 @@ const ( // - PkScript (P2WPKH) CommitmentKeyHashOutput = 8 + 1 + P2WPKHSize + // CommitmentAnchorOutput 43 bytes + // - Value: 8 bytes + // - VarInt: 1 byte (PkScript length) + // - PkScript (P2WSH) + CommitmentAnchorOutput = 8 + 1 + P2WSHSize + // HTLCSize 43 bytes // - Value: 8 bytes // - VarInt: 1 byte (PkScript length) @@ -159,9 +165,32 @@ const ( // WitnessCommitmentTxWeight 224 weight WitnessCommitmentTxWeight = WitnessHeaderSize + WitnessSize + // BaseAnchorCommitmentTxSize 223 + 43 * num-htlc-outputs bytes + // - Version: 4 bytes + // - WitnessHeader <---- part of the witness data + // - CountTxIn: 1 byte + // - TxIn: 41 bytes + // FundingInput + // - CountTxOut: 1 byte + // - TxOut: 4*43 + 43 * num-htlc-outputs bytes + // OutputPayingToThem, + // OutputPayingToUs, + // AnchorPayingToThem, + // AnchorPayingToUs, + // ....HTLCOutputs... + // - LockTime: 4 bytes + BaseAnchorCommitmentTxSize = 4 + 1 + FundingInputSize + 1 + + 2*CommitmentDelayOutput + 2*CommitmentAnchorOutput + 4 + + // BaseAnchorCommitmentTxWeight 892 weight + BaseAnchorCommitmentTxWeight = witnessScaleFactor * BaseAnchorCommitmentTxSize + // CommitWeight 724 weight CommitWeight = BaseCommitmentTxWeight + WitnessCommitmentTxWeight + // AnchorCommitWeight 1116 weight + AnchorCommitWeight = BaseAnchorCommitmentTxWeight + WitnessCommitmentTxWeight + // HTLCWeight 172 weight HTLCWeight = witnessScaleFactor * HTLCSize diff --git a/lnwallet/channel.go b/lnwallet/channel.go index fbb58d0cf6..d6a97f43d2 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -502,7 +502,8 @@ type commitment struct { // evaluating all the add/remove/settle log entries before the listed // indexes. // - // NOTE: This is the balance *after* subtracting any commitment fee. + // NOTE: This is the balance *after* subtracting any commitment fee, + // AND anchor output values. ourBalance lnwire.MilliSatoshi theirBalance lnwire.MilliSatoshi @@ -772,6 +773,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, ourWitnessScript, theirWitnessScript []byte pd PaymentDescriptor err error + chanType = lc.channelState.ChanType ) // If the either outputs is dust from the local or remote node's @@ -783,8 +785,9 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, htlc.Amt.ToSatoshis(), lc.channelState.LocalChanCfg.DustLimit) if !isDustLocal && localCommitKeys != nil { ourP2WSH, ourWitnessScript, err = genHtlcScript( - htlc.Incoming, true, htlc.RefundTimeout, htlc.RHash, - localCommitKeys) + chanType, htlc.Incoming, true, htlc.RefundTimeout, + htlc.RHash, localCommitKeys, + ) if err != nil { return pd, err } @@ -793,8 +796,9 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, htlc.Amt.ToSatoshis(), lc.channelState.RemoteChanCfg.DustLimit) if !isDustRemote && remoteCommitKeys != nil { theirP2WSH, theirWitnessScript, err = genHtlcScript( - htlc.Incoming, false, htlc.RefundTimeout, htlc.RHash, - remoteCommitKeys) + chanType, htlc.Incoming, false, htlc.RefundTimeout, + htlc.RHash, remoteCommitKeys, + ) if err != nil { return pd, err } @@ -1402,7 +1406,8 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate, wireMsg.Amount.ToSatoshis(), remoteDustLimit) if !isDustRemote { theirP2WSH, theirWitnessScript, err := genHtlcScript( - false, false, wireMsg.Expiry, wireMsg.PaymentHash, + lc.channelState.ChanType, false, false, + wireMsg.Expiry, wireMsg.PaymentHash, remoteCommitKeys, ) if err != nil { @@ -1808,6 +1813,19 @@ type BreachRetribution struct { // party) within the breach transaction. LocalOutpoint wire.OutPoint + // LocalAnchorSignDesc is a SignDescriptor capable of generating the + // signature for our anchor output on the commitment transaction. + // + // NOTE: A nil value indicates that there is no anchor spendable by us + // on this commitmenent. + LocalAnchorSignDesc *input.SignDescriptor + + // LocalAnchor is our anchor output on the commitment. + // + // NOTE: A nil value indicates that there is no anchor spendable by us + // on this commitmenent. + LocalAnchor *wire.OutPoint + // RemoteOutputSignDesc is a SignDescriptor which is capable of // generating the signature required to claim the funds as described // within the revocation clause of the remote party's commitment @@ -1893,6 +1911,20 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, return nil, err } + // Generate our anchor output script. Note that if this channel type + // doesn't have anchors, there will be no output on the commitment + // matching this script, and ourAnchor and sign descriptor will be nil. + var ( + ourAnchor *wire.OutPoint + ourAnchorSignDesc *input.SignDescriptor + ) + ourAnchorScript, _, err := CommitScriptAnchors( + &chanState.LocalChanCfg, &chanState.RemoteChanCfg, + ) + if err != nil { + return nil, err + } + // In order to fully populate the breach retribution struct, we'll need // to find the exact index of the commitment outputs. ourOutpoint := wire.OutPoint{ @@ -1905,6 +1937,11 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, switch { case bytes.Equal(txOut.PkScript, ourScript.PkScript): ourOutpoint.Index = uint32(i) + case bytes.Equal(txOut.PkScript, ourAnchorScript.PkScript): + ourAnchor = &wire.OutPoint{ + Hash: commitHash, + Index: uint32(i), + } case bytes.Equal(txOut.PkScript, theirWitnessHash): theirOutpoint.Index = uint32(i) } @@ -1937,6 +1974,21 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, } } + // If an anchor output was found, initiate the sign descriptor needed + // to spend it. + if ourAnchor != nil { + ourAnchorSignDesc = &input.SignDescriptor{ + SingleTweak: nil, + KeyDesc: chanState.LocalChanCfg.MultiSigKey, + WitnessScript: ourAnchorScript.WitnessScript, + Output: &wire.TxOut{ + PkScript: ourAnchorScript.PkScript, + Value: int64(anchorSize), + }, + HashType: txscript.SigHashAll, + } + } + // Similarly, if their balance exceeds the remote party's dust limit, // assemble the sign descriptor for their output, which we can sweep. if theirAmt >= chanState.RemoteChanCfg.DustLimit { @@ -1957,11 +2009,6 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // remote commitment transaction. htlcRetributions := make([]HtlcRetribution, 0, len(revokedSnapshot.Htlcs)) for _, htlc := range revokedSnapshot.Htlcs { - var ( - htlcWitnessScript []byte - err error - ) - // If the HTLC is dust, then we'll skip it as it doesn't have // an output on the commitment transaction. if htlcIsDust( @@ -1985,31 +2032,13 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // If this is an incoming HTLC, then this means that they were // the sender of the HTLC (relative to us). So we'll - // re-generate the sender HTLC script. - if htlc.Incoming { - htlcWitnessScript, err = input.SenderHTLCScript( - keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, htlc.RHash[:], - ) - if err != nil { - return nil, err - } - - } else { - // Otherwise, is this was an outgoing HTLC that we - // sent, then from the PoV of the remote commitment - // state, they're the receiver of this HTLC. - htlcWitnessScript, err = input.ReceiverHTLCScript( - htlc.RefundTimeout, keyRing.LocalHtlcKey, - keyRing.RemoteHtlcKey, keyRing.RevocationKey, - htlc.RHash[:], - ) - if err != nil { - return nil, err - } - } - - htlcPkScript, err := input.WitnessScriptHash(htlcWitnessScript) + // re-generate the sender HTLC script. Otherwise, is this was + // an outgoing HTLC that we sent, then from the PoV of the + // remote commitment state, they're the receiver of this HTLC. + htlcPkScript, htlcWitnessScript, err := genHtlcScript( + chanState.ChanType, htlc.Incoming, false, + htlc.RefundTimeout, htlc.RHash, keyRing, + ) if err != nil { return nil, err } @@ -2045,6 +2074,8 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, PendingHTLCs: revokedSnapshot.Htlcs, LocalOutpoint: ourOutpoint, LocalOutputSignDesc: ourSignDesc, + LocalAnchor: ourAnchor, + LocalAnchorSignDesc: ourAnchorSignDesc, RemoteOutpoint: theirOutpoint, RemoteOutputSignDesc: theirSignDesc, HtlcRetributions: htlcRetributions, @@ -2507,12 +2538,14 @@ func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64, // signature can be submitted to the sigPool to generate all the signatures // asynchronously and in parallel. func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, + chanType channeldb.ChannelType, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, remoteCommitView *commitment) ([]SignJob, chan struct{}, error) { txHash := remoteCommitView.txn.TxHash() dustLimit := remoteChanCfg.DustLimit feePerKw := remoteCommitView.feePerKw + sigHashType := HtlcSigHashType(chanType) // With the keys generated, we'll make a slice with enough capacity to // hold potentially all the HTLCs. The actual slice may be a bit @@ -2572,7 +2605,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, Output: &wire.TxOut{ Value: int64(htlc.Amount.ToSatoshis()), }, - HashType: txscript.SigHashAll, + HashType: sigHashType, SigHashes: txscript.NewTxSigHashes(sigJob.Tx), InputIndex: 0, } @@ -2623,7 +2656,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, Output: &wire.TxOut{ Value: int64(htlc.Amount.ToSatoshis()), }, - HashType: txscript.SigHashAll, + HashType: sigHashType, SigHashes: txscript.NewTxSigHashes(sigJob.Tx), InputIndex: 0, } @@ -3016,7 +3049,8 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch // need to generate signatures of each of them for the remote party's // commitment state. We do so in two phases: first we generate and // submit the set of signature jobs to the worker pool. - sigBatch, cancelChan, err := genRemoteHtlcSigJobs(keyRing, + sigBatch, cancelChan, err := genRemoteHtlcSigJobs( + keyRing, lc.channelState.ChanType, &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, newCommitView, ) @@ -3461,7 +3495,8 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, totalHtlcWeight += input.HTLCWeight } - totalCommitWeight := input.CommitWeight + totalHtlcWeight + totalCommitWeight := CommitWeight(lc.channelState.ChanType) + + totalHtlcWeight return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView } @@ -3471,10 +3506,12 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, // directly into the pool of workers. func genHtlcSigValidationJobs(localCommitmentView *commitment, keyRing *CommitmentKeyRing, htlcSigs []lnwire.Sig, + chanType channeldb.ChannelType, localChanCfg, remoteChanCfg *channeldb.ChannelConfig) ([]VerifyJob, error) { txHash := localCommitmentView.txn.TxHash() feePerKw := localCommitmentView.feePerKw + sigHashType := HtlcSigHashType(chanType) // With the required state generated, we'll create a slice with large // enough capacity to hold verification jobs for all HTLC's in this @@ -3527,7 +3564,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, hashCache := txscript.NewTxSigHashes(successTx) sigHash, err := txscript.CalcWitnessSigHash( htlc.ourWitnessScript, hashCache, - txscript.SigHashAll, successTx, 0, + sigHashType, successTx, 0, int64(htlc.Amount.ToSatoshis()), ) if err != nil { @@ -3581,7 +3618,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, hashCache := txscript.NewTxSigHashes(timeoutTx) sigHash, err := txscript.CalcWitnessSigHash( htlc.ourWitnessScript, hashCache, - txscript.SigHashAll, timeoutTx, 0, + sigHashType, timeoutTx, 0, int64(htlc.Amount.ToSatoshis()), ) if err != nil { @@ -3787,7 +3824,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, // generated, we'll submit these jobs to the worker pool. verifyJobs, err := genHtlcSigValidationJobs( localCommitmentView, keyRing, htlcSigs, - &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, + lc.channelState.ChanType, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, ) if err != nil { return err @@ -4753,6 +4791,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si chainfee.SatPerKWeight(remoteCommit.FeePerKw), false, signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, *commitSpend.SpenderTxHash, + chanState.ChanType, ) if err != nil { return nil, fmt.Errorf("unable to create htlc "+ @@ -4948,31 +4987,27 @@ func newOutgoingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, feePerKw chainfee.SatPerKWeight, csvDelay uint32, - localCommit bool) (*OutgoingHtlcResolution, error) { + localCommit bool, chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) { op := wire.OutPoint{ Hash: commitHash, Index: uint32(htlc.OutputIndex), } + // First, we'll re-generate the script used to send the HTLC to + // the remote party within their commitment transaction. + htlcScriptHash, htlcScript, err := genHtlcScript( + chanType, false, localCommit, htlc.RefundTimeout, htlc.RHash, + keyRing, + ) + if err != nil { + return nil, err + } + // If we're spending this HTLC output from the remote node's // commitment, then we won't need to go to the second level as our // outputs don't have a CSV delay. if !localCommit { - // First, we'll re-generate the script used to send the HTLC to - // the remote party within their commitment transaction. - htlcReceiverScript, err := input.ReceiverHTLCScript(htlc.RefundTimeout, - keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, - keyRing.RevocationKey, htlc.RHash[:], - ) - if err != nil { - return nil, err - } - htlcScriptHash, err := input.WitnessScriptHash(htlcReceiverScript) - if err != nil { - return nil, err - } - // With the script generated, we can completely populated the // SignDescriptor needed to sweep the output. return &OutgoingHtlcResolution{ @@ -4981,7 +5016,7 @@ func newOutgoingHtlcResolution(signer input.Signer, SweepSignDesc: input.SignDescriptor{ KeyDesc: localChanCfg.HtlcBasePoint, SingleTweak: keyRing.LocalHtlcKeyTweak, - WitnessScript: htlcReceiverScript, + WitnessScript: htlcScript, Output: &wire.TxOut{ PkScript: htlcScriptHash, Value: int64(htlc.Amt.ToSatoshis()), @@ -5013,19 +5048,15 @@ func newOutgoingHtlcResolution(signer input.Signer, // With the transaction created, we can generate a sign descriptor // that's capable of generating the signature required to spend the // HTLC output using the timeout transaction. - htlcCreationScript, err := input.SenderHTLCScript(keyRing.LocalHtlcKey, - keyRing.RemoteHtlcKey, keyRing.RevocationKey, htlc.RHash[:]) - if err != nil { - return nil, err - } + sigHashType := HtlcSigHashType(chanType) timeoutSignDesc := input.SignDescriptor{ KeyDesc: localChanCfg.HtlcBasePoint, SingleTweak: keyRing.LocalHtlcKeyTweak, - WitnessScript: htlcCreationScript, + WitnessScript: htlcScript, Output: &wire.TxOut{ Value: int64(htlc.Amt.ToSatoshis()), }, - HashType: txscript.SigHashAll, + HashType: sigHashType, SigHashes: txscript.NewTxSigHashes(timeoutTx), InputIndex: 0, } @@ -5049,7 +5080,7 @@ func newOutgoingHtlcResolution(signer input.Signer, if err != nil { return nil, err } - htlcScriptHash, err := input.WitnessScriptHash(htlcSweepScript) + htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript) if err != nil { return nil, err } @@ -5070,7 +5101,7 @@ func newOutgoingHtlcResolution(signer input.Signer, SingleTweak: localDelayTweak, WitnessScript: htlcSweepScript, Output: &wire.TxOut{ - PkScript: htlcScriptHash, + PkScript: htlcSweepScriptHash, Value: int64(secondLevelOutputAmt), }, HashType: txscript.SigHashAll, @@ -5085,33 +5116,30 @@ func newOutgoingHtlcResolution(signer input.Signer, // they can just sweep the output immediately with knowledge of the pre-image. // // TODO(roasbeef) consolidate code with above func -func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig, - commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, - feePerKw chainfee.SatPerKWeight, csvDelay uint32, - localCommit bool) (*IncomingHtlcResolution, error) { +func newIncomingHtlcResolution(signer input.Signer, + localChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash, + htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, + feePerKw chainfee.SatPerKWeight, csvDelay uint32, localCommit bool, + chanType channeldb.ChannelType) (*IncomingHtlcResolution, error) { op := wire.OutPoint{ Hash: commitHash, Index: uint32(htlc.OutputIndex), } + // First, we'll re-generate the script the remote party used to + // send the HTLC to us in their commitment transaction. + htlcScriptHash, htlcScript, err := genHtlcScript( + chanType, true, localCommit, htlc.RefundTimeout, htlc.RHash, + keyRing, + ) + if err != nil { + return nil, err + } + // If we're spending this output from the remote node's commitment, // then we can skip the second layer and spend the output directly. if !localCommit { - // First, we'll re-generate the script the remote party used to - // send the HTLC to us in their commitment transaction. - htlcSenderScript, err := input.SenderHTLCScript( - keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, htlc.RHash[:], - ) - if err != nil { - return nil, err - } - htlcScriptHash, err := input.WitnessScriptHash(htlcSenderScript) - if err != nil { - return nil, err - } - // With the script generated, we can completely populated the // SignDescriptor needed to sweep the output. return &IncomingHtlcResolution{ @@ -5120,7 +5148,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan SweepSignDesc: input.SignDescriptor{ KeyDesc: localChanCfg.HtlcBasePoint, SingleTweak: keyRing.LocalHtlcKeyTweak, - WitnessScript: htlcSenderScript, + WitnessScript: htlcScript, Output: &wire.TxOut{ PkScript: htlcScriptHash, Value: int64(htlc.Amt.ToSatoshis()), @@ -5146,21 +5174,15 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan // Once we've created the second-level transaction, we'll generate the // SignDesc needed spend the HTLC output using the success transaction. - htlcCreationScript, err := input.ReceiverHTLCScript(htlc.RefundTimeout, - keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, htlc.RHash[:], - ) - if err != nil { - return nil, err - } + sigHashType := HtlcSigHashType(chanType) successSignDesc := input.SignDescriptor{ KeyDesc: localChanCfg.HtlcBasePoint, SingleTweak: keyRing.LocalHtlcKeyTweak, - WitnessScript: htlcCreationScript, + WitnessScript: htlcScript, Output: &wire.TxOut{ Value: int64(htlc.Amt.ToSatoshis()), }, - HashType: txscript.SigHashAll, + HashType: sigHashType, SigHashes: txscript.NewTxSigHashes(successTx), InputIndex: 0, } @@ -5186,7 +5208,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan if err != nil { return nil, err } - htlcScriptHash, err := input.WitnessScriptHash(htlcSweepScript) + htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript) if err != nil { return nil, err } @@ -5206,7 +5228,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan SingleTweak: localDelayTweak, WitnessScript: htlcSweepScript, Output: &wire.TxOut{ - PkScript: htlcScriptHash, + PkScript: htlcSweepScriptHash, Value: int64(secondLevelOutputAmt), }, HashType: txscript.SigHashAll, @@ -5244,7 +5266,8 @@ func (r *OutgoingHtlcResolution) HtlcPoint() wire.OutPoint { func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, signer input.Signer, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - commitHash chainhash.Hash) (*HtlcResolutions, error) { + commitHash chainhash.Hash, chanType channeldb.ChannelType) ( + *HtlcResolutions, error) { // TODO(roasbeef): don't need to swap csv delay? dustLimit := remoteChanCfg.DustLimit @@ -5257,6 +5280,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, incomingResolutions := make([]IncomingHtlcResolution, 0, len(htlcs)) outgoingResolutions := make([]OutgoingHtlcResolution, 0, len(htlcs)) for _, htlc := range htlcs { + htlc := htlc + // We'll skip any HTLC's which were dust on the commitment // transaction, as these don't have a corresponding output // within the commitment transaction. @@ -5271,8 +5296,9 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, // Otherwise, we'll create an incoming HTLC resolution // as we can satisfy the contract. ihr, err := newIncomingHtlcResolution( - signer, localChanCfg, commitHash, &htlc, keyRing, - feePerKw, uint32(csvDelay), ourCommit, + signer, localChanCfg, commitHash, &htlc, + keyRing, feePerKw, uint32(csvDelay), ourCommit, + chanType, ) if err != nil { return nil, err @@ -5284,7 +5310,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool, ohr, err := newOutgoingHtlcResolution( signer, localChanCfg, commitHash, &htlc, keyRing, - feePerKw, uint32(csvDelay), ourCommit, + feePerKw, uint32(csvDelay), ourCommit, chanType, ) if err != nil { return nil, err @@ -5463,7 +5489,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si htlcResolutions, err := extractHtlcResolutions( chainfee.SatPerKWeight(localCommit.FeePerKw), true, signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg, - &chanState.RemoteChanCfg, txHash, + &chanState.RemoteChanCfg, txHash, chanState.ChanType, ) if err != nil { return nil, err @@ -5633,6 +5659,105 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte, return closeTx, ourBalance, nil } +// AnchorResolutions holds the informations necessary to spend the anchors in +// play. +type AnchorResolutions struct { + // AnchorSignDescriptor is the sign descriptor for all variants of our + // anchor. + AnchorSignDescriptor *input.SignDescriptor + + // LocalCommitAnchor is the anchor outpoint on our local commitment. + // This is nil if the current channel type doesn't have anchor outputs. + LocalCommitAnchor *wire.OutPoint + + // RemoteCommitAnchor is the anchor outpoint on the acked remote + // commitment. This is nil if the current channel type doesn't have + // anchor outputs. + RemoteCommitAnchor *wire.OutPoint + + // RemotePendingCommitAnchor is the anchor outpoint on the pending ack + // remote commitment. This is nil if the current channel type doesn't + // have anchor outputs or there are no pending commit. + RemotePendingCommitAnchor *wire.OutPoint +} + +// NewAnchorResolutions returns the anchor resolutions for the channel state. +// The confirmed boolean indicates where to get the channel data from. +func NewAnchorResolutions(chanState *channeldb.OpenChannel, confirmed bool) ( + *AnchorResolutions, error) { + + // Derive our local anchor script, which will be found on each of the + // commitments. + localAnchor, _, err := CommitScriptAnchors( + &chanState.LocalChanCfg, &chanState.RemoteChanCfg, + ) + if err != nil { + return nil, err + } + + // Similarly the sign descriptor will be the same for all commitments. + anchorSignDesc := &input.SignDescriptor{ + SingleTweak: nil, + KeyDesc: chanState.LocalChanCfg.MultiSigKey, + WitnessScript: localAnchor.WitnessScript, + Output: &wire.TxOut{ + PkScript: localAnchor.PkScript, + Value: int64(anchorSize), + }, + HashType: txscript.SigHashAll, + } + + // locateAnchor is a helper method that finds the local anchor outpoint + // on the given commitment. If not found nil is returned. + locateAnchor := func(commit *channeldb.ChannelCommitment) *wire.OutPoint { + tx := commit.CommitTx + + for i, txOut := range tx.TxOut { + if !bytes.Equal(txOut.PkScript, localAnchor.PkScript) { + continue + } + + return &wire.OutPoint{ + Hash: tx.TxHash(), + Index: uint32(i), + } + } + + // Not found. + return nil + } + + // Fetch the two latest commitments. + localCommit, remoteCommit, err := chanState.LatestCommitmentsFrom( + confirmed, + ) + if err != nil { + return nil, err + } + resolutions := &AnchorResolutions{ + AnchorSignDescriptor: anchorSignDesc, + LocalCommitAnchor: locateAnchor(localCommit), + RemoteCommitAnchor: locateAnchor(remoteCommit), + RemotePendingCommitAnchor: nil, + } + + // Get their pending commit, if any. + remotePendingCommit, err := chanState.RemoteCommitChainTipFrom( + confirmed, + ) + if err != nil && err != channeldb.ErrNoPendingCommit { + return nil, err + } + + if remotePendingCommit != nil { + resolutions.RemotePendingCommitAnchor = locateAnchor( + &remotePendingCommit.Commitment, + ) + } + + return resolutions, nil +} + // AvailableBalance returns the current available balance within the channel. // By available balance, we mean that if at this very instance s new commitment // were to be created which evals all the log entries, what would our available @@ -5861,7 +5986,7 @@ func CreateCooperativeCloseTx(fundingTxIn wire.TxIn, // CalcFee returns the commitment fee to use for the given // fee rate (fee-per-kw). func (lc *LightningChannel) CalcFee(feeRate chainfee.SatPerKWeight) btcutil.Amount { - return feeRate.FeeForWeight(input.CommitWeight) + return feeRate.FeeForWeight(CommitWeight(lc.channelState.ChanType)) } // MaxFeeRate returns the maximum fee rate given an allocation of the channel diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 459dd83bda..afd294e878 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -5,6 +5,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" @@ -13,6 +14,9 @@ import ( "github.com/lightningnetwork/lnd/lnwire" ) +// anchorSize is the constant anchor output size. +const anchorSize = btcutil.Amount(330) + // CommitmentKeyRing holds all derived keys needed to construct commitment and // HTLC transactions. The keys are derived differently depending whether the // commitment transaction is ours or the remote peer's. Private keys associated @@ -184,9 +188,31 @@ type ScriptInfo struct { // CommitScriptToRemote creates the script that will pay to the non-owner of // the commitment transaction, adding a delay to the script based on the // channel type. -func CommitScriptToRemote(_ channeldb.ChannelType, csvTimeout uint32, +func CommitScriptToRemote(chanType channeldb.ChannelType, csvTimeout uint32, key *btcec.PublicKey) (*ScriptInfo, error) { + // If this channel type has anchors, we derive the delayed to_remote + // script. + if chanType.HasAnchors() { + script, err := input.CommitScriptToRemoteDelayed( + csvTimeout, key, + ) + if err != nil { + return nil, err + } + + p2wsh, err := input.WitnessScriptHash(script) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: p2wsh, + WitnessScript: script, + }, nil + } + + // Otherwise the to_remote will be a simple p2wkh. p2wkh, err := input.CommitScriptUnencumbered(key) if err != nil { return nil, err @@ -200,6 +226,68 @@ func CommitScriptToRemote(_ channeldb.ChannelType, csvTimeout uint32, }, nil } +// HtlcSigHashType returns the sighash type to use for HTLC success and timeout +// transactions given the channel type. +func HtlcSigHashType(chanType channeldb.ChannelType) txscript.SigHashType { + if chanType.HasAnchors() { + return txscript.SigHashSingle | txscript.SigHashAnyOneCanPay + } + + return txscript.SigHashAll +} + +// CommitWeight returns the base commitment weight before adding HTLCs. +func CommitWeight(chanType channeldb.ChannelType) int64 { + // If this commitment has anchors, it will be slightly heavier. + if chanType.HasAnchors() { + return input.AnchorCommitWeight + } + + return input.CommitWeight +} + +// CommitScriptAnchors return the scripts to use for the local and remote +// anchor. +func CommitScriptAnchors(localChanCfg, + remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo, + *ScriptInfo, error) { + + // Helper to create anchor ScriptInfo from key. + anchorScript := func(key *btcec.PublicKey) (*ScriptInfo, error) { + script, err := input.CommitScriptAnchor( + key, + ) + if err != nil { + return nil, err + } + + scriptHash, err := input.WitnessScriptHash(script) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: scriptHash, + WitnessScript: script, + }, nil + } + + // Get the script used for the anchor output spendable by the local + // node. + localAnchor, err := anchorScript(localChanCfg.MultiSigKey.PubKey) + if err != nil { + return nil, nil, err + } + + // And the anchor spemdable by the remote node. + remoteAnchor, err := anchorScript(remoteChanCfg.MultiSigKey.PubKey) + if err != nil { + return nil, nil, err + } + + return localAnchor, remoteAnchor, nil +} + // CommitmentBuilder is a type that wraps the type of channel we are dealing // with, and abstracts the various ways of constructing commitment // transactions. @@ -216,6 +304,11 @@ type CommitmentBuilder struct { // NewCommitmentBuilder creates a new CommitmentBuilder from chanState. func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder { + // The anchor channel type MUST be tweakless. + if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() { + panic("invalid channel type combination") + } + return &CommitmentBuilder{ chanState: chanState, obfuscator: createStateHintObfuscator(chanState), @@ -248,8 +341,9 @@ type unsignedCommitmentTx struct { // fee is the total fee of the commitment transaction. fee btcutil.Amount - // ourBalance|theirBalance is the balances of this commitment. This can - // be different than the balances before creating the commitment + // ourBalance|theirBalance are the balances of this commitment *after* + // subtracting commitment fees and anchor outputs. This can be + // different than the balances before creating the commitment // transaction as one party must pay the commitment fee. ourBalance lnwire.MilliSatoshi theirBalance lnwire.MilliSatoshi @@ -258,7 +352,7 @@ type unsignedCommitmentTx struct { // createUnsignedCommitmentTx generates the unsigned commitment transaction for // a commitment view and returns it as part of the unsignedCommitmentTx. The // passed in balances should be balances *before* subtracting any commitment -// fees. +// fees, but after anchor outputs. func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, theirBalance lnwire.MilliSatoshi, isOurs bool, feePerKw chainfee.SatPerKWeight, height uint64, @@ -294,7 +388,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, // on its total weight. Once we have the total weight, we'll multiply // by the current fee-per-kw, then divide by 1000 to get the proper // fee. - totalCommitWeight := input.CommitWeight + (input.HTLCWeight * numHTLCs) + totalCommitWeight := CommitWeight(cb.chanState.ChanType) + + input.HTLCWeight*numHTLCs // With the weight known, we can now calculate the commitment fee, // ensuring that we account for any dust outputs trimmed above. @@ -333,12 +428,14 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, &cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg, ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), + numHTLCs, ) } else { commitTx, err = CreateCommitTx( cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, &cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg, theirBalance.ToSatoshis(), ourBalance.ToSatoshis(), + numHTLCs, ) } if err != nil { @@ -361,7 +458,10 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, continue } - err := addHTLC(commitTx, isOurs, false, htlc, keyRing) + err := addHTLC( + commitTx, isOurs, false, htlc, keyRing, + cb.chanState.ChanType, + ) if err != nil { return nil, err } @@ -373,7 +473,10 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, continue } - err := addHTLC(commitTx, isOurs, true, htlc, keyRing) + err := addHTLC( + commitTx, isOurs, true, htlc, keyRing, + cb.chanState.ChanType, + ) if err != nil { return nil, err } @@ -430,7 +533,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, func CreateCommitTx(chanType channeldb.ChannelType, fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) { + amountToLocal, amountToRemote btcutil.Amount, + numHTLCs int64) (*wire.MsgTx, error) { // First, we create the script for the delayed "pay-to-self" output. // This output has 2 main redemption clauses: either we can redeem the @@ -466,26 +570,58 @@ func CreateCommitTx(chanType channeldb.ChannelType, commitTx.AddTxIn(&fundingOutput) // Avoid creating dust outputs within the commitment transaction. - if amountToLocal >= localChanCfg.DustLimit { + localOutput := amountToLocal >= localChanCfg.DustLimit + if localOutput { commitTx.AddTxOut(&wire.TxOut{ PkScript: toLocalScriptHash, Value: int64(amountToLocal), }) } - if amountToRemote >= localChanCfg.DustLimit { + + remoteOutput := amountToRemote >= localChanCfg.DustLimit + if remoteOutput { commitTx.AddTxOut(&wire.TxOut{ PkScript: toRemoteScript.PkScript, Value: int64(amountToRemote), }) } + // If this channel type has anchors, we'll also add those. + if chanType.HasAnchors() { + localAnchor, remoteAnchor, err := CommitScriptAnchors( + localChanCfg, remoteChanCfg, + ) + if err != nil { + return nil, err + } + + // Add local anchor output only if we have a commitment output + // or there are HTLCs. + if localOutput || numHTLCs > 0 { + commitTx.AddTxOut(&wire.TxOut{ + PkScript: localAnchor.PkScript, + Value: int64(anchorSize), + }) + } + + // Add anchor output to remote only if they have a commitment + // output or there are HTLCs. + if remoteOutput || numHTLCs > 0 { + commitTx.AddTxOut(&wire.TxOut{ + PkScript: remoteAnchor.PkScript, + Value: int64(anchorSize), + }) + } + } + return commitTx, nil } // genHtlcScript generates the proper P2WSH public key scripts for the HTLC // output modified by two-bits denoting if this is an incoming HTLC, and if the // HTLC is being applied to their commitment transaction or ours. -func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, +func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, + timeout uint32, rHash [32]byte, keyRing *CommitmentKeyRing) ([]byte, []byte, error) { var ( @@ -493,6 +629,12 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, err error ) + // Choose scripts based on channel type. + confirmedHtlcSpends := false + if chanType.HasAnchors() { + confirmedHtlcSpends = true + } + // Generate the proper redeem scripts for the HTLC output modified by // two-bits denoting if this is an incoming HTLC, and if the HTLC is // being applied to their commitment transaction or ours. @@ -501,30 +643,37 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, // transaction. So we need to use the receiver's version of HTLC the // script. case isIncoming && ourCommit: - witnessScript, err = input.ReceiverHTLCScript(timeout, - keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, rHash[:]) + witnessScript, err = input.ReceiverHTLCScript( + timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, + keyRing.RevocationKey, rHash[:], confirmedHtlcSpends, + ) // We're being paid via an HTLC by the remote party, and the HTLC is // being added to their commitment transaction, so we use the sender's // version of the HTLC script. case isIncoming && !ourCommit: - witnessScript, err = input.SenderHTLCScript(keyRing.RemoteHtlcKey, - keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:]) + witnessScript, err = input.SenderHTLCScript( + keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, + keyRing.RevocationKey, rHash[:], confirmedHtlcSpends, + ) // We're sending an HTLC which is being added to our commitment // transaction. Therefore, we need to use the sender's version of the // HTLC script. case !isIncoming && ourCommit: - witnessScript, err = input.SenderHTLCScript(keyRing.LocalHtlcKey, - keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:]) + witnessScript, err = input.SenderHTLCScript( + keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, + keyRing.RevocationKey, rHash[:], confirmedHtlcSpends, + ) // Finally, we're paying the remote party via an HTLC, which is being // added to their commitment transaction. Therefore, we use the // receiver's version of the HTLC script. case !isIncoming && !ourCommit: - witnessScript, err = input.ReceiverHTLCScript(timeout, keyRing.LocalHtlcKey, - keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:]) + witnessScript, err = input.ReceiverHTLCScript( + timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, + keyRing.RevocationKey, rHash[:], confirmedHtlcSpends, + ) } if err != nil { return nil, nil, err @@ -549,13 +698,14 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, // the descriptor itself. func addHTLC(commitTx *wire.MsgTx, ourCommit bool, isIncoming bool, paymentDesc *PaymentDescriptor, - keyRing *CommitmentKeyRing) error { + keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error { timeout := paymentDesc.Timeout rHash := paymentDesc.RHash - p2wsh, witnessScript, err := genHtlcScript(isIncoming, ourCommit, - timeout, rHash, keyRing) + p2wsh, witnessScript, err := genHtlcScript( + chanType, isIncoming, ourCommit, timeout, rHash, keyRing, + ) if err != nil { return err } diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index db907d5a4e..c51a3b24ff 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -700,7 +700,7 @@ func testCancelNonExistentReservation(miner *rpctest.Harness, // Create our own reservation, give it some ID. res, err := lnwallet.NewChannelReservation( 10000, 10000, feePerKw, alice, 22, 10, &testHdSeed, - lnwire.FFAnnounceChannel, true, nil, [32]byte{}, + lnwire.FFAnnounceChannel, true, false, nil, [32]byte{}, ) if err != nil { t.Fatalf("unable to create res: %v", err) diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index b7b63da557..8a99d51706 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -136,7 +136,7 @@ type ChannelReservation struct { func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, commitFeePerKw chainfee.SatPerKWeight, wallet *LightningWallet, id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash, - flags lnwire.FundingFlag, tweaklessCommit bool, + flags lnwire.FundingFlag, tweaklessCommit, anchorOutputs bool, fundingAssembler chanfunding.Assembler, pendingChanID [32]byte) (*ChannelReservation, error) { @@ -151,7 +151,13 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, // TODO(halseth): make method take remote funding amount directly // instead of inferring it from capacity and local amt. capacityMSat := lnwire.NewMSatFromSatoshis(capacity) + + // The total fee paid by the initiator will be the commitment fee in + // addition to the two anchor outputs. feeMSat := lnwire.NewMSatFromSatoshis(commitFee) + if anchorOutputs { + feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize) + } // If we're the responder to a single-funder reservation, then we have // no initial balance in the channel unless the remote party is pushing @@ -213,6 +219,16 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, ) } + // Similarly we ensure their balance is reasonable if we are not the + // initiator. + if !initiator && theirBalance.ToSatoshis() <= 2*DefaultDustLimit() { + return nil, ErrFunderBalanceDust( + int64(commitFee), + int64(ourBalance.ToSatoshis()), + int64(2*DefaultDustLimit()), + ) + } + // Next we'll set the channel type based on what we can ascertain about // the balances/push amount within the channel. var chanType channeldb.ChannelType @@ -241,6 +257,11 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, chanType |= channeldb.DualFunderBit } + // We are adding anchor outputs to our commitment. + if anchorOutputs { + chanType |= channeldb.AnchorOutputsBit + } + return &ChannelReservation{ ourContribution: &ChannelContribution{ FundingAmount: ourBalance.ToSatoshis(), diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index 4f5cc65a4b..7a3f08a299 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -851,9 +851,10 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { // Generate second-level HTLC transactions for HTLCs in // commitment tx. htlcResolutions, err := extractHtlcResolutions( - chainfee.SatPerKWeight(test.commitment.FeePerKw), true, signer, - htlcs, keys, &channel.channelState.LocalChanCfg, + chainfee.SatPerKWeight(test.commitment.FeePerKw), true, + signer, htlcs, keys, &channel.channelState.LocalChanCfg, &channel.channelState.RemoteChanCfg, commitTx.TxHash(), + channel.channelState.ChanType, ) if err != nil { t.Errorf("Case %d: Failed to extract HTLC resolutions: %v", i, err) @@ -1089,7 +1090,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { } commitmentTx, err := CreateCommitTx( channelType, *fakeFundingTxIn, keyRing, aliceChanCfg, - bobChanCfg, channelBalance, channelBalance, + bobChanCfg, channelBalance, channelBalance, 0, ) if err != nil { t.Fatalf("unable to create commitment transaction: %v", nil) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 5143b9f5cc..268e0f0f7f 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -102,6 +102,10 @@ type InitFundingReserveMsg struct { // commitment format or not. Tweakless bool + // AnchorSize is set if we agreed to add anchor outputs to the + // commitments. + AnchorOutputs bool + // ChanFunder is an optional channel funder that allows the caller to // control exactly how the channel funding is carried out. If not // specified, then the default chanfunding.WalletAssembler will be @@ -568,7 +572,8 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg reservation, err := NewChannelReservation( capacity, localFundingAmt, req.CommitFeePerKw, l, id, req.PushMSat, l.Cfg.NetParams.GenesisHash, req.Flags, - req.Tweakless, req.ChanFunder, req.PendingChanID, + req.Tweakless, req.AnchorOutputs, req.ChanFunder, + req.PendingChanID, ) if err != nil { if fundingIntent != nil { @@ -784,7 +789,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, ourCommitTx, err := CreateCommitTx( chanType, fundingTxIn, localCommitmentKeys, ourChanCfg, - theirChanCfg, localBalance, remoteBalance, + theirChanCfg, localBalance, remoteBalance, 0, ) if err != nil { return nil, nil, err @@ -797,7 +802,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, theirCommitTx, err := CreateCommitTx( chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg, - ourChanCfg, remoteBalance, localBalance, + ourChanCfg, remoteBalance, localBalance, 0, ) if err != nil { return nil, nil, err