diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 5a3cb50f8..72448ab54 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -1799,7 +1799,7 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns( channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, - bobCommitPoint, *fundingTxIn, true, + bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit, ) if err != nil { return nil, nil, nil, err diff --git a/channeldb/channel.go b/channeldb/channel.go index 84e96c8ea..edbe42796 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -320,10 +320,14 @@ type ChannelCommitment struct { // LocalBalance is the current available settled balance within the // channel directly spendable by us. + // + // NOTE: This is the balance *after* subtracting any commitment fee. LocalBalance lnwire.MilliSatoshi // RemoteBalance is the current available settled balance within the // channel directly spendable by the remote node. + // + // NOTE: This is the balance *after* subtracting any commitment fee. RemoteBalance lnwire.MilliSatoshi // CommitFee is the amount calculated to be paid in fees for the diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 7ea3da142..e9eb8e396 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -331,7 +331,8 @@ func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription { // based off of only the set of outputs included. func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig, commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64, - revocationProducer shachain.Producer, tweakless bool) (bool, error) { + revocationProducer shachain.Producer, + chanType channeldb.ChannelType) (bool, error) { // First, we'll re-derive our commitment point for this state since // this is what we use to randomize each of the keys for this state. @@ -345,13 +346,14 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig, // and remote keys for this state. We use our point as only we can // revoke our own commitment. commitKeyRing := lnwallet.DeriveCommitmentKeys( - commitPoint, true, tweakless, &localChanCfg, &remoteChanCfg, + commitPoint, true, chanType, &localChanCfg, &remoteChanCfg, ) // With the keys derived, we'll construct the remote script that'll be // present if they have a non-dust balance on the commitment. - remotePkScript, err := input.CommitScriptUnencumbered( - commitKeyRing.NoDelayKey, + remoteDelay := uint32(remoteChanCfg.CsvDelay) + remoteScript, err := lnwallet.CommitScriptToRemote( + chanType, remoteDelay, commitKeyRing.ToRemoteKey, ) if err != nil { return false, err @@ -361,7 +363,7 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig, // the remote party allowing them to claim this output before the CSV // delay if we breach. localScript, err := input.CommitScriptToSelf( - uint32(localChanCfg.CsvDelay), commitKeyRing.DelayKey, + uint32(localChanCfg.CsvDelay), commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey, ) if err != nil { @@ -382,7 +384,7 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig, case bytes.Equal(localPkScript, pkScript): return true, nil - case bytes.Equal(remotePkScript, pkScript): + case bytes.Equal(remoteScript.PkScript, pkScript): return true, nil } } @@ -422,11 +424,6 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) { // revoked state...!!! commitTxBroadcast := commitSpend.SpendingTx - // An additional piece of information we need to properly - // dispatch a close event if is this channel was using the - // tweakless remove key format or not. - tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless() - localCommit, remoteCommit, err := c.cfg.chanState.LatestCommitments() if err != nil { log.Errorf("Unable to fetch channel state for "+ @@ -484,7 +481,7 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) { c.cfg.chanState.LocalChanCfg, c.cfg.chanState.RemoteChanCfg, commitSpend, broadcastStateNum, c.cfg.chanState.RevocationProducer, - tweaklessCommit, + c.cfg.chanState.ChanType, ) if err != nil { log.Errorf("unable to determine self commit for "+ @@ -596,6 +593,7 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) { // close and sweep immediately using a fake commitPoint // as it isn't actually needed for recovery anymore. commitPoint := c.cfg.chanState.RemoteCurrentRevocation + tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless() if !tweaklessCommit { commitPoint = c.waitForCommitmentPoint() if commitPoint == nil { @@ -928,8 +926,8 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail retribution.KeyRing.CommitPoint.Curve = nil retribution.KeyRing.LocalHtlcKey = nil retribution.KeyRing.RemoteHtlcKey = nil - retribution.KeyRing.DelayKey = nil - retribution.KeyRing.NoDelayKey = nil + retribution.KeyRing.ToLocalKey = nil + retribution.KeyRing.ToRemoteKey = nil retribution.KeyRing.RevocationKey = nil return spew.Sdump(retribution) })) diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index dc796ba31..9efe89dca 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -1973,7 +1973,7 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) { t.Fatalf("unable to query fee estimator: %v", err) } htlcFee := lnwire.NewMSatFromSatoshis( - feePerKw.FeeForWeight(input.HtlcWeight), + feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount @@ -2462,7 +2462,7 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) { // TODO(roasbeef): increase sleep time.Sleep(time.Second * 1) - commitWeight := input.CommitWeight + input.HtlcWeight*numHTLCs + commitWeight := int64(input.CommitWeight + input.HTLCWeight*numHTLCs) htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(commitWeight), ) @@ -2646,7 +2646,7 @@ func TestChannelLinkTrimCircuitsPending(t *testing.T) { defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( - feePerKw.FeeForWeight(input.HtlcWeight), + feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount @@ -2925,7 +2925,7 @@ func TestChannelLinkTrimCircuitsNoCommit(t *testing.T) { defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( - feePerKw.FeeForWeight(input.HtlcWeight), + feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount @@ -3181,7 +3181,7 @@ func TestChannelLinkBandwidthChanReserve(t *testing.T) { t.Fatalf("unable to query fee estimator: %v", err) } htlcFee := lnwire.NewMSatFromSatoshis( - feePerKw.FeeForWeight(input.HtlcWeight), + feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 9ed8179a8..da6b07edd 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -262,7 +262,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte, aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns( aliceAmount, bobAmount, &aliceCfg, &bobCfg, aliceCommitPoint, - bobCommitPoint, *fundingTxIn, true, + bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit, ) if err != nil { return nil, nil, nil, err diff --git a/input/signdescriptor.go b/input/signdescriptor.go index 0dfb5d464..2aa2af9c2 100644 --- a/input/signdescriptor.go +++ b/input/signdescriptor.go @@ -17,9 +17,9 @@ var ( ErrTweakOverdose = errors.New("sign descriptor should only have one tweak") ) -// SignDescriptor houses the necessary information required to successfully sign -// a given output. This struct is used by the Signer interface in order to gain -// access to critical data needed to generate a valid signature. +// SignDescriptor houses the necessary information required to successfully +// sign a given segwit output. This struct is used by the Signer interface in +// order to gain access to critical data needed to generate a valid signature. type SignDescriptor struct { // KeyDesc is a descriptor that precisely describes *which* key to use // for signing. This may provide the raw public key directly, or @@ -56,8 +56,9 @@ type SignDescriptor struct { DoubleTweak *btcec.PrivateKey // WitnessScript is the full script required to properly redeem the - // output. This field will only be populated if a p2wsh or a p2sh - // output is being signed. + // output. This field should be set to the full script if a p2wsh + // output is being signed. For p2wkh it should be set to the hashed + // script (PkScript). WitnessScript []byte // Output is the target output which should be signed. The PkScript and diff --git a/input/size.go b/input/size.go index 07055dc84..68fa85e4e 100644 --- a/input/size.go +++ b/input/size.go @@ -5,15 +5,6 @@ import ( "github.com/btcsuite/btcd/wire" ) -const ( - // CommitWeight is the weight of the base commitment transaction which - // includes: one p2wsh input, out p2wkh output, and one p2wsh output. - CommitWeight int64 = 724 - - // HtlcWeight is the weight of an HTLC output. - HtlcWeight int64 = 172 -) - const ( // witnessScaleFactor determines the level of "discount" witness data // receives compared to "base" data. A scale factor of 4, denotes that @@ -168,6 +159,9 @@ const ( // WitnessCommitmentTxWeight 224 weight WitnessCommitmentTxWeight = WitnessHeaderSize + WitnessSize + // CommitWeight 724 weight + CommitWeight = BaseCommitmentTxWeight + WitnessCommitmentTxWeight + // HTLCWeight 172 weight HTLCWeight = witnessScaleFactor * HTLCSize diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 837f05cb5..fbb58d0cf 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -502,7 +502,7 @@ type commitment struct { // evaluating all the add/remove/settle log entries before the listed // indexes. // - // NOTE: This is the balance *before* subtracting any commitment fee. + // NOTE: This is the balance *after* subtracting any commitment fee. ourBalance lnwire.MilliSatoshi theirBalance lnwire.MilliSatoshi @@ -866,11 +866,6 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool, diskCommit *channeldb.ChannelCommitment, localCommitPoint, remoteCommitPoint *btcec.PublicKey) (*commitment, error) { - // If this commit is tweakless, then it'll affect the way we derive our - // keys, which will affect the commitment transaction reconstruction. - // So we'll determine this first, before we do anything else. - tweaklessCommit := lc.channelState.ChanType.IsTweakless() - // First, we'll need to re-derive the commitment key ring for each // party used within this particular state. If this is a pending commit // (we extended but weren't able to complete the commitment dance @@ -879,14 +874,16 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool, var localCommitKeys, remoteCommitKeys *CommitmentKeyRing if localCommitPoint != nil { localCommitKeys = DeriveCommitmentKeys( - localCommitPoint, true, tweaklessCommit, - lc.localChanCfg, lc.remoteChanCfg, + localCommitPoint, true, lc.channelState.ChanType, + &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, ) } if remoteCommitPoint != nil { remoteCommitKeys = DeriveCommitmentKeys( - remoteCommitPoint, false, tweaklessCommit, - lc.localChanCfg, lc.remoteChanCfg, + remoteCommitPoint, false, lc.channelState.ChanType, + &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, ) } @@ -935,121 +932,6 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool, return commit, nil } -// 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 -// with each key may belong to the commitment owner or the "other party" which -// is referred to in the field comments, regardless of which is local and which -// is remote. -type CommitmentKeyRing struct { - // commitPoint is the "per commitment point" used to derive the tweak - // for each base point. - CommitPoint *btcec.PublicKey - - // LocalCommitKeyTweak is the tweak used to derive the local public key - // from the local payment base point or the local private key from the - // base point secret. This may be included in a SignDescriptor to - // generate signatures for the local payment key. - LocalCommitKeyTweak []byte - - // TODO(roasbeef): need delay tweak as well? - - // LocalHtlcKeyTweak is the teak used to derive the local HTLC key from - // the local HTLC base point. This value is needed in order to - // derive the final key used within the HTLC scripts in the commitment - // transaction. - LocalHtlcKeyTweak []byte - - // LocalHtlcKey is the key that will be used in the "to self" clause of - // any HTLC scripts within the commitment transaction for this key ring - // set. - LocalHtlcKey *btcec.PublicKey - - // RemoteHtlcKey is the key that will be used in clauses within the - // HTLC script that send money to the remote party. - RemoteHtlcKey *btcec.PublicKey - - // DelayKey is the commitment transaction owner's key which is included - // in HTLC success and timeout transaction scripts. - DelayKey *btcec.PublicKey - - // NoDelayKey is the other party's payment key in the commitment tx. - // This is the key used to generate the unencumbered output within the - // commitment transaction. - NoDelayKey *btcec.PublicKey - - // RevocationKey is the key that can be used by the other party to - // redeem outputs from a revoked commitment transaction if it were to - // be published. - RevocationKey *btcec.PublicKey -} - -// DeriveCommitmentKey generates a new commitment key set using the base points -// and commitment point. The keys are derived differently depending whether the -// commitment transaction is ours or the remote peer's. -func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, - isOurCommit, tweaklessCommit bool, - localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *CommitmentKeyRing { - - // First, we'll derive all the keys that don't depend on the context of - // whose commitment transaction this is. - keyRing := &CommitmentKeyRing{ - CommitPoint: commitPoint, - - LocalCommitKeyTweak: input.SingleTweakBytes( - commitPoint, localChanCfg.PaymentBasePoint.PubKey, - ), - LocalHtlcKeyTweak: input.SingleTweakBytes( - commitPoint, localChanCfg.HtlcBasePoint.PubKey, - ), - LocalHtlcKey: input.TweakPubKey( - localChanCfg.HtlcBasePoint.PubKey, commitPoint, - ), - RemoteHtlcKey: input.TweakPubKey( - remoteChanCfg.HtlcBasePoint.PubKey, commitPoint, - ), - } - - // We'll now compute the delay, no delay, and revocation key based on - // the current commitment point. All keys are tweaked each state in - // order to ensure the keys from each state are unlinkable. To create - // the revocation key, we take the opposite party's revocation base - // point and combine that with the current commitment point. - var ( - delayBasePoint *btcec.PublicKey - noDelayBasePoint *btcec.PublicKey - revocationBasePoint *btcec.PublicKey - ) - if isOurCommit { - delayBasePoint = localChanCfg.DelayBasePoint.PubKey - noDelayBasePoint = remoteChanCfg.PaymentBasePoint.PubKey - revocationBasePoint = remoteChanCfg.RevocationBasePoint.PubKey - } else { - delayBasePoint = remoteChanCfg.DelayBasePoint.PubKey - noDelayBasePoint = localChanCfg.PaymentBasePoint.PubKey - revocationBasePoint = localChanCfg.RevocationBasePoint.PubKey - } - - // With the base points assigned, we can now derive the actual keys - // using the base point, and the current commitment tweak. - keyRing.DelayKey = input.TweakPubKey(delayBasePoint, commitPoint) - keyRing.RevocationKey = input.DeriveRevocationPubkey( - revocationBasePoint, commitPoint, - ) - - // If this commitment should omit the tweak for the remote point, then - // we'll use that directly, and ignore the commitPoint tweak. - if tweaklessCommit { - keyRing.NoDelayKey = noDelayBasePoint - } else { - keyRing.NoDelayKey = input.TweakPubKey( - noDelayBasePoint, commitPoint, - ) - } - - return keyRing -} - // commitmentChain represents a chain of unrevoked commitments. The tail of the // chain is the latest fully signed, yet unrevoked commitment. Two chains are // tracked, one for the local node, and another for the remote node. New @@ -1339,10 +1221,6 @@ type LightningChannel struct { // Capacity is the total capacity of this channel. Capacity btcutil.Amount - // stateHintObfuscator is a 48-bit state hint that's used to obfuscate - // the current state number on the commitment transactions. - stateHintObfuscator [StateHintSize]byte - // currentHeight is the current height of our local commitment chain. // This is also the same as the number of updates to the channel we've // accepted. @@ -1360,9 +1238,7 @@ type LightningChannel struct { channelState *channeldb.OpenChannel - localChanCfg *channeldb.ChannelConfig - - remoteChanCfg *channeldb.ChannelConfig + commitBuilder *CommitmentBuilder // [local|remote]Log is a (mostly) append-only log storing all the HTLC // updates to this channel. The log is walked backwards as HTLC updates @@ -1416,8 +1292,7 @@ func NewLightningChannel(signer input.Signer, remoteCommitChain: newCommitmentChain(), localCommitChain: newCommitmentChain(), channelState: state, - localChanCfg: &state.LocalChanCfg, - remoteChanCfg: &state.RemoteChanCfg, + commitBuilder: NewCommitmentBuilder(state), localUpdateLog: localUpdateLog, remoteUpdateLog: remoteUpdateLog, ChanPoint: &state.FundingOutpoint, @@ -1441,16 +1316,16 @@ func NewLightningChannel(signer input.Signer, return nil, err } - lc.createStateHintObfuscator() - return lc, nil } // createSignDesc derives the SignDescriptor for commitment transactions from // other fields on the LightningChannel. func (lc *LightningChannel) createSignDesc() error { - localKey := lc.localChanCfg.MultiSigKey.PubKey.SerializeCompressed() - remoteKey := lc.remoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() + localKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + remoteKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. + SerializeCompressed() multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey) if err != nil { @@ -1462,7 +1337,7 @@ func (lc *LightningChannel) createSignDesc() error { return err } lc.signDesc = &input.SignDescriptor{ - KeyDesc: lc.localChanCfg.MultiSigKey, + KeyDesc: lc.channelState.LocalChanCfg.MultiSigKey, WitnessScript: multiSigScript, Output: &wire.TxOut{ PkScript: fundingPkScript, @@ -1475,24 +1350,6 @@ func (lc *LightningChannel) createSignDesc() error { return nil } -// createStateHintObfuscator derives and assigns the state hint obfuscator for -// the channel, which is used to encode the commitment height in the sequence -// number of commitment transaction inputs. -func (lc *LightningChannel) createStateHintObfuscator() { - state := lc.channelState - if state.IsInitiator { - lc.stateHintObfuscator = DeriveStateHintObfuscator( - state.LocalChanCfg.PaymentBasePoint.PubKey, - state.RemoteChanCfg.PaymentBasePoint.PubKey, - ) - } else { - lc.stateHintObfuscator = DeriveStateHintObfuscator( - state.RemoteChanCfg.PaymentBasePoint.PubKey, - state.LocalChanCfg.PaymentBasePoint.PubKey, - ) - } -} - // ResetState resets the state of the channel back to the default state. This // ensures that any active goroutines which need to act based on on-chain // events do so properly. @@ -1719,10 +1576,9 @@ func (lc *LightningChannel) restoreCommitState( // We'll also re-create the set of commitment keys needed to // fully re-derive the state. - tweaklessCommit := lc.channelState.ChanType.IsTweakless() pendingRemoteKeyChain = DeriveCommitmentKeys( - pendingCommitPoint, false, tweaklessCommit, - lc.localChanCfg, lc.remoteChanCfg, + pendingCommitPoint, false, lc.channelState.ChanType, + &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, ) } @@ -2007,45 +1863,50 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // With the commitment point generated, we can now generate the four // keys we'll need to reconstruct the commitment state, - tweaklessCommit := chanState.ChanType.IsTweakless() keyRing := DeriveCommitmentKeys( - commitmentPoint, false, tweaklessCommit, + commitmentPoint, false, chanState.ChanType, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) // Next, reconstruct the scripts as they were present at this state // number so we can have the proper witness script to sign and include // within the final witness. - remoteDelay := uint32(chanState.RemoteChanCfg.CsvDelay) - remotePkScript, err := input.CommitScriptToSelf( - remoteDelay, keyRing.DelayKey, keyRing.RevocationKey, + theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay) + theirPkScript, err := input.CommitScriptToSelf( + theirDelay, keyRing.ToLocalKey, keyRing.RevocationKey, ) if err != nil { return nil, err } - remoteWitnessHash, err := input.WitnessScriptHash(remotePkScript) + theirWitnessHash, err := input.WitnessScriptHash(theirPkScript) if err != nil { return nil, err } - localPkScript, err := input.CommitScriptUnencumbered(keyRing.NoDelayKey) + + // Since it is the remote breach we are reconstructing, the output going + // to us will be a to-remote script with our local params. + ourDelay := uint32(chanState.LocalChanCfg.CsvDelay) + ourScript, err := CommitScriptToRemote( + chanState.ChanType, ourDelay, keyRing.ToRemoteKey, + ) 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 local+remote commitment outputs. - localOutpoint := wire.OutPoint{ + // to find the exact index of the commitment outputs. + ourOutpoint := wire.OutPoint{ Hash: commitHash, } - remoteOutpoint := wire.OutPoint{ + theirOutpoint := wire.OutPoint{ Hash: commitHash, } for i, txOut := range revokedSnapshot.CommitTx.TxOut { switch { - case bytes.Equal(txOut.PkScript, localPkScript): - localOutpoint.Index = uint32(i) - case bytes.Equal(txOut.PkScript, remoteWitnessHash): - remoteOutpoint.Index = uint32(i) + case bytes.Equal(txOut.PkScript, ourScript.PkScript): + ourOutpoint.Index = uint32(i) + case bytes.Equal(txOut.PkScript, theirWitnessHash): + theirOutpoint.Index = uint32(i) } } @@ -2053,45 +1914,39 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // commitment outputs. If either is considered dust using the remote // party's dust limit, the respective sign descriptor will be nil. var ( - localSignDesc *input.SignDescriptor - remoteSignDesc *input.SignDescriptor + ourSignDesc *input.SignDescriptor + theirSignDesc *input.SignDescriptor ) - // Compute the local and remote balances in satoshis. - localAmt := revokedSnapshot.LocalBalance.ToSatoshis() - remoteAmt := revokedSnapshot.RemoteBalance.ToSatoshis() + // Compute the balances in satoshis. + ourAmt := revokedSnapshot.LocalBalance.ToSatoshis() + theirAmt := revokedSnapshot.RemoteBalance.ToSatoshis() - // If the local balance exceeds the remote party's dust limit, - // instantiate the local sign descriptor. - if localAmt >= chanState.RemoteChanCfg.DustLimit { - localSignDesc = &input.SignDescriptor{ + // If our balance exceeds the remote party's dust limit, instantiate + // the sign descriptor for our output. + if ourAmt >= chanState.RemoteChanCfg.DustLimit { + ourSignDesc = &input.SignDescriptor{ SingleTweak: keyRing.LocalCommitKeyTweak, KeyDesc: chanState.LocalChanCfg.PaymentBasePoint, - WitnessScript: localPkScript, + WitnessScript: ourScript.WitnessScript, Output: &wire.TxOut{ - PkScript: localPkScript, - Value: int64(localAmt), + PkScript: ourScript.PkScript, + Value: int64(ourAmt), }, HashType: txscript.SigHashAll, } - - // If this is a tweakless commitment, then we can safely blank - // out the SingleTweak value as it isn't needed. - if tweaklessCommit { - localSignDesc.SingleTweak = nil - } } - // Similarly, if the remote balance exceeds the remote party's dust - // limit, assemble the remote sign descriptor. - if remoteAmt >= chanState.RemoteChanCfg.DustLimit { - remoteSignDesc = &input.SignDescriptor{ + // 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 { + theirSignDesc = &input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg.RevocationBasePoint, DoubleTweak: commitmentSecret, - WitnessScript: remotePkScript, + WitnessScript: theirPkScript, Output: &wire.TxOut{ - PkScript: remoteWitnessHash, - Value: int64(remoteAmt), + PkScript: theirWitnessHash, + Value: int64(theirAmt), }, HashType: txscript.SigHashAll, } @@ -2122,7 +1977,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // remote commitment transaction, and *they* go to the second // level. secondLevelWitnessScript, err := input.SecondLevelHtlcScript( - keyRing.RevocationKey, keyRing.DelayKey, remoteDelay, + keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay, ) if err != nil { return nil, err @@ -2188,13 +2043,13 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, BreachHeight: breachHeight, RevokedStateNum: stateNum, PendingHTLCs: revokedSnapshot.Htlcs, - LocalOutpoint: localOutpoint, - LocalOutputSignDesc: localSignDesc, - RemoteOutpoint: remoteOutpoint, - RemoteOutputSignDesc: remoteSignDesc, + LocalOutpoint: ourOutpoint, + LocalOutputSignDesc: ourSignDesc, + RemoteOutpoint: theirOutpoint, + RemoteOutputSignDesc: theirSignDesc, HtlcRetributions: htlcRetributions, KeyRing: keyRing, - RemoteDelay: remoteDelay, + RemoteDelay: theirDelay, }, nil } @@ -2305,33 +2160,41 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, keyRing *CommitmentKeyRing) (*commitment, error) { commitChain := lc.localCommitChain + dustLimit := lc.channelState.LocalChanCfg.DustLimit if remoteChain { commitChain = lc.remoteCommitChain + dustLimit = lc.channelState.RemoteChanCfg.DustLimit } nextHeight := commitChain.tip().height + 1 // Run through all the HTLCs that will be covered by this transaction // in order to update their commitment addition height, and to adjust - // the balances on the commitment transaction accordingly. + // the balances on the commitment transaction accordingly. Note that + // these balances will be *before* taking a commitment fee from the + // initiator. htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex) ourBalance, theirBalance, _, filteredHTLCView := lc.computeView( htlcView, remoteChain, true, ) feePerKw := filteredHTLCView.feePerKw - // Determine how many current HTLCs are over the dust limit, and should - // be counted for the purpose of fee calculation. - var dustLimit btcutil.Amount - if remoteChain { - dustLimit = lc.remoteChanCfg.DustLimit - } else { - dustLimit = lc.localChanCfg.DustLimit + // Actually generate unsigned commitment transaction for this view. + commitTx, err := lc.commitBuilder.createUnsignedCommitmentTx( + ourBalance, theirBalance, !remoteChain, feePerKw, nextHeight, + filteredHTLCView, keyRing, + ) + if err != nil { + return nil, err } + // With the commitment view created, store the resulting balances and + // transaction with the other parameters for this height. c := &commitment{ - ourBalance: ourBalance, - theirBalance: theirBalance, + ourBalance: commitTx.ourBalance, + theirBalance: commitTx.theirBalance, + txn: commitTx.txn, + fee: commitTx.fee, ourMessageIndex: ourLogIndex, ourHtlcIndex: ourHtlcIndex, theirMessageIndex: theirLogIndex, @@ -2342,11 +2205,6 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, isOurs: !remoteChain, } - // Actually generate unsigned commitment transaction for this view. - if err := lc.createCommitmentTx(c, filteredHTLCView, keyRing); err != nil { - return nil, err - } - // In order to ensure _none_ of the HTLC's associated with this new // commitment are mutated, we'll manually copy over each HTLC to its // respective slice. @@ -2368,162 +2226,8 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, return c, nil } -func (lc *LightningChannel) fundingTxIn() wire.TxIn { - return *wire.NewTxIn(&lc.channelState.FundingOutpoint, nil, nil) -} - -// createCommitmentTx generates the unsigned commitment transaction for a -// commitment view and assigns to txn field. -func (lc *LightningChannel) createCommitmentTx(c *commitment, - filteredHTLCView *htlcView, keyRing *CommitmentKeyRing) error { - - ourBalance := c.ourBalance - theirBalance := c.theirBalance - - numHTLCs := int64(0) - for _, htlc := range filteredHTLCView.ourUpdates { - if htlcIsDust(false, c.isOurs, c.feePerKw, - htlc.Amount.ToSatoshis(), c.dustLimit) { - - continue - } - - numHTLCs++ - } - for _, htlc := range filteredHTLCView.theirUpdates { - if htlcIsDust(true, c.isOurs, c.feePerKw, - htlc.Amount.ToSatoshis(), c.dustLimit) { - - continue - } - - numHTLCs++ - } - - // Next, we'll calculate the fee for the commitment transaction based - // 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) - - // With the weight known, we can now calculate the commitment fee, - // ensuring that we account for any dust outputs trimmed above. - commitFee := c.feePerKw.FeeForWeight(totalCommitWeight) - commitFeeMSat := lnwire.NewMSatFromSatoshis(commitFee) - - // Currently, within the protocol, the initiator always pays the fees. - // So we'll subtract the fee amount from the balance of the current - // initiator. If the initiator is unable to pay the fee fully, then - // their entire output is consumed. - switch { - case lc.channelState.IsInitiator && commitFee > ourBalance.ToSatoshis(): - ourBalance = 0 - - case lc.channelState.IsInitiator: - ourBalance -= commitFeeMSat - - case !lc.channelState.IsInitiator && commitFee > theirBalance.ToSatoshis(): - theirBalance = 0 - - case !lc.channelState.IsInitiator: - theirBalance -= commitFeeMSat - } - - var ( - delay uint32 - delayBalance, p2wkhBalance btcutil.Amount - ) - if c.isOurs { - delay = uint32(lc.localChanCfg.CsvDelay) - delayBalance = ourBalance.ToSatoshis() - p2wkhBalance = theirBalance.ToSatoshis() - } else { - delay = uint32(lc.remoteChanCfg.CsvDelay) - delayBalance = theirBalance.ToSatoshis() - p2wkhBalance = ourBalance.ToSatoshis() - } - - // Generate a new commitment transaction with all the latest - // unsettled/un-timed out HTLCs. - commitTx, err := CreateCommitTx(lc.fundingTxIn(), keyRing, delay, - delayBalance, p2wkhBalance, c.dustLimit) - if err != nil { - return err - } - - // We'll now add all the HTLC outputs to the commitment transaction. - // Each output includes an off-chain 2-of-2 covenant clause, so we'll - // need the objective local/remote keys for this particular commitment - // as well. For any non-dust HTLCs that are manifested on the commitment - // transaction, we'll also record its CLTV which is required to sort the - // commitment transaction below. The slice is initially sized to the - // number of existing outputs, since any outputs already added are - // commitment outputs and should correspond to zero values for the - // purposes of sorting. - cltvs := make([]uint32, len(commitTx.TxOut)) - for _, htlc := range filteredHTLCView.ourUpdates { - if htlcIsDust(false, c.isOurs, c.feePerKw, - htlc.Amount.ToSatoshis(), c.dustLimit) { - continue - } - - err := lc.addHTLC(commitTx, c.isOurs, false, htlc, keyRing) - if err != nil { - return err - } - cltvs = append(cltvs, htlc.Timeout) - } - for _, htlc := range filteredHTLCView.theirUpdates { - if htlcIsDust(true, c.isOurs, c.feePerKw, - htlc.Amount.ToSatoshis(), c.dustLimit) { - continue - } - - err := lc.addHTLC(commitTx, c.isOurs, true, htlc, keyRing) - if err != nil { - return err - } - cltvs = append(cltvs, htlc.Timeout) - } - - // Set the state hint of the commitment transaction to facilitate - // quickly recovering the necessary penalty state in the case of an - // uncooperative broadcast. - err = SetStateNumHint(commitTx, c.height, lc.stateHintObfuscator) - if err != nil { - return err - } - - // Sort the transactions according to the agreed upon canonical - // ordering. This lets us skip sending the entire transaction over, - // instead we'll just send signatures. - InPlaceCommitSort(commitTx, cltvs) - - // Next, we'll ensure that we don't accidentally create a commitment - // transaction which would be invalid by consensus. - uTx := btcutil.NewTx(commitTx) - if err := blockchain.CheckTransactionSanity(uTx); err != nil { - return err - } - - // Finally, we'll assert that were not attempting to draw more out of - // the channel that was originally placed within it. - var totalOut btcutil.Amount - for _, txOut := range commitTx.TxOut { - totalOut += btcutil.Amount(txOut.Value) - } - if totalOut > lc.channelState.Capacity { - return fmt.Errorf("height=%v, for ChannelPoint(%v) attempts "+ - "to consume %v while channel capacity is %v", - c.height, lc.channelState.FundingOutpoint, - totalOut, lc.channelState.Capacity) - } - - c.txn = commitTx - c.fee = commitFee - c.ourBalance = ourBalance - c.theirBalance = theirBalance - return nil +func fundingTxIn(chanState *channeldb.OpenChannel) wire.TxIn { + return *wire.NewTxIn(&chanState.FundingOutpoint, nil, nil) } // evaluateHTLCView processes all update entries in both HTLC update logs, @@ -2852,7 +2556,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, sigJob.Tx, err = createHtlcTimeoutTx( op, outputAmt, htlc.Timeout, uint32(remoteChanCfg.CsvDelay), - keyRing.RevocationKey, keyRing.DelayKey, + keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { return nil, nil, err @@ -2903,7 +2607,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, } sigJob.Tx, err = createHtlcSuccessTx( op, outputAmt, uint32(remoteChanCfg.CsvDelay), - keyRing.RevocationKey, keyRing.DelayKey, + keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { return nil, nil, err @@ -3143,12 +2847,12 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, switch { case ourBalance < ourInitialBalance && ourBalance < lnwire.NewMSatFromSatoshis( - lc.localChanCfg.ChanReserve): + lc.channelState.LocalChanCfg.ChanReserve): return ErrBelowChanReserve case theirBalance < theirInitialBalance && theirBalance < lnwire.NewMSatFromSatoshis( - lc.remoteChanCfg.ChanReserve): + lc.channelState.RemoteChanCfg.ChanReserve): return ErrBelowChanReserve } @@ -3199,7 +2903,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // First check that the remote updates won't violate it's channel // constraints. err := validateUpdates( - filteredView.theirUpdates, lc.remoteChanCfg, + filteredView.theirUpdates, &lc.channelState.RemoteChanCfg, ) if err != nil { return err @@ -3208,7 +2912,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // Secondly check that our updates won't violate our channel // constraints. err = validateUpdates( - filteredView.ourUpdates, lc.localChanCfg, + filteredView.ourUpdates, &lc.channelState.LocalChanCfg, ) if err != nil { return err @@ -3275,8 +2979,8 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch // used within fetchCommitmentView to derive all the keys necessary to // construct the commitment state. keyRing := DeriveCommitmentKeys( - commitPoint, false, lc.channelState.ChanType.IsTweakless(), - lc.localChanCfg, lc.remoteChanCfg, + commitPoint, false, lc.channelState.ChanType, + &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, ) // Create a new commitment view which will calculate the evaluated @@ -3313,7 +3017,8 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch // 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, - lc.localChanCfg, lc.remoteChanCfg, newCommitView, + &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, + newCommitView, ) if err != nil { return sig, htlcSigs, nil, err @@ -3696,10 +3401,10 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, *htlcView) { commitChain := lc.localCommitChain - dustLimit := lc.localChanCfg.DustLimit + dustLimit := lc.channelState.LocalChanCfg.DustLimit if remoteChain { commitChain = lc.remoteCommitChain - dustLimit = lc.remoteChanCfg.DustLimit + dustLimit = lc.channelState.RemoteChanCfg.DustLimit } // Since the fetched htlc view will include all updates added after the @@ -3745,7 +3450,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, continue } - totalHtlcWeight += input.HtlcWeight + totalHtlcWeight += input.HTLCWeight } for _, htlc := range filteredHTLCView.theirUpdates { if htlcIsDust(!remoteChain, !remoteChain, feePerKw, @@ -3753,7 +3458,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, continue } - totalHtlcWeight += input.HtlcWeight + totalHtlcWeight += input.HTLCWeight } totalCommitWeight := input.CommitWeight + totalHtlcWeight @@ -3814,7 +3519,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, successTx, err := createHtlcSuccessTx(op, outputAmt, uint32(localChanCfg.CsvDelay), - keyRing.RevocationKey, keyRing.DelayKey) + keyRing.RevocationKey, keyRing.ToLocalKey) if err != nil { return nil, err } @@ -3867,7 +3572,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, timeoutTx, err := createHtlcTimeoutTx(op, outputAmt, htlc.Timeout, uint32(localChanCfg.CsvDelay), - keyRing.RevocationKey, keyRing.DelayKey, + keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { return nil, err @@ -4032,8 +3737,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, } commitPoint := input.ComputeCommitmentPoint(commitSecret[:]) keyRing := DeriveCommitmentKeys( - commitPoint, true, lc.channelState.ChanType.IsTweakless(), - lc.localChanCfg, lc.remoteChanCfg, + commitPoint, true, lc.channelState.ChanType, + &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, ) // With the current commitment point re-calculated, construct the new @@ -4081,8 +3786,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, // pool to verify each of the HTLc signatures presented. Once // generated, we'll submit these jobs to the worker pool. verifyJobs, err := genHtlcSigValidationJobs( - localCommitmentView, keyRing, htlcSigs, lc.localChanCfg, - lc.remoteChanCfg, + localCommitmentView, keyRing, htlcSigs, + &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, ) if err != nil { return err @@ -4095,8 +3800,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, // we'll ensure that the newly constructed commitment state has a valid // signature. verifyKey := btcec.PublicKey{ - X: lc.remoteChanCfg.MultiSigKey.PubKey.X, - Y: lc.remoteChanCfg.MultiSigKey.PubKey.Y, + X: lc.channelState.RemoteChanCfg.MultiSigKey.PubKey.X, + Y: lc.channelState.RemoteChanCfg.MultiSigKey.PubKey.Y, Curve: btcec.S256(), } cSig, err := commitSig.ToSignature() @@ -4930,101 +4635,6 @@ func (lc *LightningChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress return lc.channelState.RemoteShutdownScript } -// 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, - keyRing *CommitmentKeyRing) ([]byte, []byte, error) { - - var ( - witnessScript []byte - err error - ) - - // 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. - switch { - // The HTLC is paying to us, and being applied to our commitment - // 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[:]) - - // 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[:]) - - // 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[:]) - - // 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[:]) - } - if err != nil { - return nil, nil, err - } - - // Now that we have the redeem scripts, create the P2WSH public key - // script for the output itself. - htlcP2WSH, err := input.WitnessScriptHash(witnessScript) - if err != nil { - return nil, nil, err - } - - return htlcP2WSH, witnessScript, nil -} - -// addHTLC adds a new HTLC to the passed commitment transaction. One of four -// full scripts will be generated for the HTLC output depending on if the HTLC -// is incoming and if it's being applied to our commitment transaction or that -// of the remote node's. Additionally, in order to be able to efficiently -// locate the added HTLC on the commitment transaction from the -// PaymentDescriptor that generated it, the generated script is stored within -// the descriptor itself. -func (lc *LightningChannel) addHTLC(commitTx *wire.MsgTx, ourCommit bool, - isIncoming bool, paymentDesc *PaymentDescriptor, - keyRing *CommitmentKeyRing) error { - - timeout := paymentDesc.Timeout - rHash := paymentDesc.RHash - - p2wsh, witnessScript, err := genHtlcScript(isIncoming, ourCommit, - timeout, rHash, keyRing) - if err != nil { - return err - } - - // Add the new HTLC outputs to the respective commitment transactions. - amountPending := int64(paymentDesc.Amount.ToSatoshis()) - commitTx.AddTxOut(wire.NewTxOut(amountPending, p2wsh)) - - // Store the pkScript of this particular PaymentDescriptor so we can - // quickly locate it within the commitment transaction later. - if ourCommit { - paymentDesc.ourPkScript = p2wsh - paymentDesc.ourWitnessScript = witnessScript - } else { - paymentDesc.theirPkScript = p2wsh - paymentDesc.theirWitnessScript = witnessScript - } - - return nil -} - // getSignedCommitTx function take the latest commitment transaction and // populate it with witness data. func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { @@ -5046,8 +4656,10 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { // With the final signature generated, create the witness stack // required to spend from the multi-sig output. - ourKey := lc.localChanCfg.MultiSigKey.PubKey.SerializeCompressed() - theirKey := lc.remoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() + ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. + SerializeCompressed() commitTx.TxIn[0].Witness = input.SpendMultiSig( lc.signDesc.WitnessScript, ourKey, @@ -5130,10 +4742,9 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // First, we'll generate the commitment point and the revocation point // so we can re-construct the HTLC state and also our payment key. - tweaklessCommit := chanState.ChanType.IsTweakless() keyRing := DeriveCommitmentKeys( - commitPoint, false, tweaklessCommit, &chanState.LocalChanCfg, - &chanState.RemoteChanCfg, + commitPoint, false, chanState.ChanType, + &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) // Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we @@ -5153,7 +4764,10 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // Before we can generate the proper sign descriptor, we'll need to // locate the output index of our non-delayed output on the commitment // transaction. - selfP2WKH, err := input.CommitScriptUnencumbered(keyRing.NoDelayKey) + localDelay := uint32(chanState.LocalChanCfg.CsvDelay) + selfScript, err := CommitScriptToRemote( + chanState.ChanType, localDelay, keyRing.ToRemoteKey, + ) if err != nil { return nil, fmt.Errorf("unable to create self commit "+ "script: %v", err) @@ -5165,7 +4779,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si ) for outputIndex, txOut := range commitTxBroadcast.TxOut { - if bytes.Equal(txOut.PkScript, selfP2WKH) { + if bytes.Equal(txOut.PkScript, selfScript.PkScript) { selfPoint = &wire.OutPoint{ Hash: *commitSpend.SpenderTxHash, Index: uint32(outputIndex), @@ -5186,21 +4800,15 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si SelfOutputSignDesc: input.SignDescriptor{ KeyDesc: localPayBase, SingleTweak: keyRing.LocalCommitKeyTweak, - WitnessScript: selfP2WKH, + WitnessScript: selfScript.WitnessScript, Output: &wire.TxOut{ Value: localBalance, - PkScript: selfP2WKH, + PkScript: selfScript.PkScript, }, HashType: txscript.SigHashAll, }, MaturityDelay: 0, } - - // If this is a tweakless commitment, then we can safely blank - // out the SingleTweak value as it isn't needed. - if tweaklessCommit { - commitResolution.SelfOutputSignDesc.SingleTweak = nil - } } closeSummary := channeldb.ChannelCloseSummary{ @@ -5396,7 +5004,7 @@ func newOutgoingHtlcResolution(signer input.Signer, // transaction. timeoutTx, err := createHtlcTimeoutTx( op, secondLevelOutputAmt, htlc.RefundTimeout, csvDelay, - keyRing.RevocationKey, keyRing.DelayKey, + keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { return nil, err @@ -5436,7 +5044,7 @@ func newOutgoingHtlcResolution(signer input.Signer, // transaction creates so we can generate the signDesc required to // complete the claim process after a delay period. htlcSweepScript, err := input.SecondLevelHtlcScript( - keyRing.RevocationKey, keyRing.DelayKey, csvDelay, + keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, ) if err != nil { return nil, err @@ -5530,7 +5138,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee successTx, err := createHtlcSuccessTx( op, secondLevelOutputAmt, csvDelay, - keyRing.RevocationKey, keyRing.DelayKey, + keyRing.RevocationKey, keyRing.ToLocalKey, ) if err != nil { return nil, err @@ -5573,7 +5181,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan // creates so we can generate the proper signDesc to sweep it after the // CSV delay has passed. htlcSweepScript, err := input.SecondLevelHtlcScript( - keyRing.RevocationKey, keyRing.DelayKey, csvDelay, + keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay, ) if err != nil { return nil, err @@ -5788,11 +5396,13 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si } commitPoint := input.ComputeCommitmentPoint(revocation[:]) keyRing := DeriveCommitmentKeys( - commitPoint, true, chanState.ChanType.IsTweakless(), + commitPoint, true, chanState.ChanType, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, ) - selfScript, err := input.CommitScriptToSelf(csvTimeout, keyRing.DelayKey, - keyRing.RevocationKey) + + selfScript, err := input.CommitScriptToSelf( + csvTimeout, keyRing.ToLocalKey, keyRing.RevocationKey, + ) if err != nil { return nil, err } @@ -5826,9 +5436,6 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // nil. var commitResolution *CommitOutputResolution if len(delayScript) != 0 { - singleTweak := input.SingleTweakBytes( - commitPoint, chanState.LocalChanCfg.DelayBasePoint.PubKey, - ) localBalance := localCommit.LocalBalance commitResolution = &CommitOutputResolution{ SelfOutPoint: wire.OutPoint{ @@ -5837,7 +5444,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si }, SelfOutputSignDesc: input.SignDescriptor{ KeyDesc: chanState.LocalChanCfg.DelayBasePoint, - SingleTweak: singleTweak, + SingleTweak: keyRing.LocalCommitKeyTweak, WitnessScript: selfScript, Output: &wire.TxOut{ PkScript: delayScript, @@ -5910,10 +5517,11 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, theirBalance = theirBalance - proposedFee + commitFee } - closeTx := CreateCooperativeCloseTx(lc.fundingTxIn(), - lc.localChanCfg.DustLimit, lc.remoteChanCfg.DustLimit, - ourBalance, theirBalance, localDeliveryScript, - remoteDeliveryScript, lc.channelState.IsInitiator) + closeTx := CreateCooperativeCloseTx( + fundingTxIn(lc.channelState), lc.channelState.LocalChanCfg.DustLimit, + lc.channelState.RemoteChanCfg.DustLimit, ourBalance, theirBalance, + localDeliveryScript, remoteDeliveryScript, + ) // Ensure that the transaction doesn't explicitly violate any // consensus rules such as being too big, or having any value with a @@ -5980,10 +5588,11 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte, // Create the transaction used to return the current settled balance // on this active channel back to both parties. In this current model, // the initiator pays full fees for the cooperative close transaction. - closeTx := CreateCooperativeCloseTx(lc.fundingTxIn(), - lc.localChanCfg.DustLimit, lc.remoteChanCfg.DustLimit, - ourBalance, theirBalance, localDeliveryScript, - remoteDeliveryScript, lc.channelState.IsInitiator) + closeTx := CreateCooperativeCloseTx( + fundingTxIn(lc.channelState), lc.channelState.LocalChanCfg.DustLimit, + lc.channelState.RemoteChanCfg.DustLimit, ourBalance, theirBalance, + localDeliveryScript, remoteDeliveryScript, + ) // Ensure that the transaction doesn't explicitly validate any // consensus rules such as being too big, or having any value with a @@ -5996,8 +5605,10 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte, // Finally, construct the witness stack minding the order of the // pubkeys+sigs on the stack. - ourKey := lc.localChanCfg.MultiSigKey.PubKey.SerializeCompressed() - theirKey := lc.remoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() + ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. + SerializeCompressed() witness := input.SpendMultiSig(lc.signDesc.WitnessScript, ourKey, localSig, theirKey, remoteSig) closeTx.TxIn[0].Witness = witness @@ -6210,60 +5821,6 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd return revocationMsg, nil } -// CreateCommitTx creates a commitment transaction, spending from specified -// funding output. The commitment transaction contains two outputs: one paying -// to the "owner" of the commitment transaction which can be spent after a -// relative block delay or revocation event, and the other paying the -// counterparty within the channel, which can be spent immediately. -func CreateCommitTx(fundingOutput wire.TxIn, - keyRing *CommitmentKeyRing, csvTimeout uint32, - amountToSelf, amountToThem, dustLimit btcutil.Amount) (*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 - // output after a relative block delay, or the remote node can claim - // the funds with the revocation key if we broadcast a revoked - // commitment transaction. - ourRedeemScript, err := input.CommitScriptToSelf(csvTimeout, keyRing.DelayKey, - keyRing.RevocationKey) - if err != nil { - return nil, err - } - payToUsScriptHash, err := input.WitnessScriptHash(ourRedeemScript) - if err != nil { - return nil, err - } - - // Next, we create the script paying to them. This is just a regular - // P2WPKH output, without any added CSV delay. - theirWitnessKeyHash, err := input.CommitScriptUnencumbered(keyRing.NoDelayKey) - if err != nil { - return nil, err - } - - // Now that both output scripts have been created, we can finally create - // the transaction itself. We use a transaction version of 2 since CSV - // will fail unless the tx version is >= 2. - commitTx := wire.NewMsgTx(2) - commitTx.AddTxIn(&fundingOutput) - - // Avoid creating dust outputs within the commitment transaction. - if amountToSelf >= dustLimit { - commitTx.AddTxOut(&wire.TxOut{ - PkScript: payToUsScriptHash, - Value: int64(amountToSelf), - }) - } - if amountToThem >= dustLimit { - commitTx.AddTxOut(&wire.TxOut{ - PkScript: theirWitnessKeyHash, - Value: int64(amountToThem), - }) - } - - return commitTx, nil -} - // CreateCooperativeCloseTx creates a transaction which if signed by both // parties, then broadcast cooperatively closes an active channel. The creation // of the closure transaction is modified by a boolean indicating if the party @@ -6272,8 +5829,7 @@ func CreateCommitTx(fundingOutput wire.TxIn, // transaction in full. func CreateCooperativeCloseTx(fundingTxIn wire.TxIn, localDust, remoteDust, ourBalance, theirBalance btcutil.Amount, - ourDeliveryScript, theirDeliveryScript []byte, - initiator bool) *wire.MsgTx { + ourDeliveryScript, theirDeliveryScript []byte) *wire.MsgTx { // Construct the transaction to perform a cooperative closure of the // channel. In the event that one side doesn't have any settled funds @@ -6445,7 +6001,7 @@ func (lc *LightningChannel) ActiveHtlcs() []channeldb.HTLC { // LocalChanReserve returns our local ChanReserve requirement for the remote party. func (lc *LightningChannel) LocalChanReserve() btcutil.Amount { - return lc.localChanCfg.ChanReserve + return lc.channelState.LocalChanCfg.ChanReserve } // NextLocalHtlcIndex returns the next unallocated local htlc index. To ensure @@ -6470,5 +6026,5 @@ func (lc *LightningChannel) RemoteCommitHeight() uint64 { // FwdMinHtlc returns the minimum HTLC value required by the remote node, i.e. // the minimum value HTLC we can forward on this channel. func (lc *LightningChannel) FwdMinHtlc() lnwire.MilliSatoshi { - return lc.localChanCfg.MinHTLC + return lc.channelState.LocalChanCfg.MinHTLC } diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 665917cf4..214613117 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -600,7 +600,7 @@ func TestForceClose(t *testing.T) { // Factoring in the fee rate, Alice's amount should properly reflect // that we've added two additional HTLC to the commitment transaction. - totalCommitWeight := input.CommitWeight + (input.HtlcWeight * 2) + totalCommitWeight := int64(input.CommitWeight + (input.HTLCWeight * 2)) feePerKw := chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) @@ -615,7 +615,7 @@ func TestForceClose(t *testing.T) { // Alice's listed CSV delay should also match the delay that was // pre-committed to at channel opening. if aliceCommitResolution.MaturityDelay != - uint32(aliceChannel.localChanCfg.CsvDelay) { + uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay) { t.Fatalf("alice: incorrect local CSV delay in ForceCloseSummary, "+ "expected %v, got %v", @@ -816,10 +816,10 @@ func TestForceCloseDustOutput(t *testing.T) { // We set both node's channel reserves to 0, to make sure // they can create small dust ouputs without going under // their channel reserves. - aliceChannel.localChanCfg.ChanReserve = 0 - bobChannel.localChanCfg.ChanReserve = 0 - aliceChannel.remoteChanCfg.ChanReserve = 0 - bobChannel.remoteChanCfg.ChanReserve = 0 + aliceChannel.channelState.LocalChanCfg.ChanReserve = 0 + bobChannel.channelState.LocalChanCfg.ChanReserve = 0 + aliceChannel.channelState.RemoteChanCfg.ChanReserve = 0 + bobChannel.channelState.RemoteChanCfg.ChanReserve = 0 htlcAmount := lnwire.NewMSatFromSatoshis(500) @@ -1268,8 +1268,8 @@ func TestChannelBalanceDustLimit(t *testing.T) { // To allow Alice's balance to get beneath her dust limit, set the // channel reserve to be 0. - aliceChannel.localChanCfg.ChanReserve = 0 - bobChannel.remoteChanCfg.ChanReserve = 0 + aliceChannel.channelState.LocalChanCfg.ChanReserve = 0 + bobChannel.channelState.RemoteChanCfg.ChanReserve = 0 // This amount should leave an amount larger than Alice's dust limit // once fees have been subtracted, but smaller than Bob's dust limit. @@ -2553,7 +2553,7 @@ func TestAddHTLCNegativeBalance(t *testing.T) { // We set the channel reserve to 0, such that we can add HTLCs all the // way to a negative balance. - aliceChannel.localChanCfg.ChanReserve = 0 + aliceChannel.channelState.LocalChanCfg.ChanReserve = 0 // First, we'll add 3 HTLCs of 1 BTC each to Alice's commitment. const numHTLCs = 3 @@ -5346,14 +5346,14 @@ func TestMaxAcceptedHTLCs(t *testing.T) { // Set the remote's required MaxAcceptedHtlcs. This means that alice // can only offer the remote up to numHTLCs HTLCs. - aliceChannel.localChanCfg.MaxAcceptedHtlcs = numHTLCs - bobChannel.remoteChanCfg.MaxAcceptedHtlcs = numHTLCs + aliceChannel.channelState.LocalChanCfg.MaxAcceptedHtlcs = numHTLCs + bobChannel.channelState.RemoteChanCfg.MaxAcceptedHtlcs = numHTLCs // Similarly, set the remote config's MaxAcceptedHtlcs. This means // that the remote will be aware that Alice will only accept up to // numHTLCsRecevied at a time. - aliceChannel.remoteChanCfg.MaxAcceptedHtlcs = numHTLCsReceived - bobChannel.localChanCfg.MaxAcceptedHtlcs = numHTLCsReceived + aliceChannel.channelState.RemoteChanCfg.MaxAcceptedHtlcs = numHTLCsReceived + bobChannel.channelState.LocalChanCfg.MaxAcceptedHtlcs = numHTLCsReceived // Each HTLC amount is 0.1 BTC. htlcAmt := lnwire.NewMSatFromSatoshis(0.1 * btcutil.SatoshiPerBitcoin) @@ -5409,8 +5409,8 @@ func TestMaxPendingAmount(t *testing.T) { // We set the max pending amount of Alice's config. This mean that she // cannot offer Bob HTLCs with a total value above this limit at a given // time. - aliceChannel.localChanCfg.MaxPendingAmount = maxPending - bobChannel.remoteChanCfg.MaxPendingAmount = maxPending + aliceChannel.channelState.LocalChanCfg.MaxPendingAmount = maxPending + bobChannel.channelState.RemoteChanCfg.MaxPendingAmount = maxPending // First, we'll add 2 HTLCs of 1.5 BTC each to Alice's commitment. // This won't trigger Alice's ErrMaxPendingAmount error. @@ -5498,20 +5498,20 @@ func TestChanReserve(t *testing.T) { // Alice will need to keep her reserve above aliceMinReserve, // so set this limit to here local config. - aliceChannel.localChanCfg.ChanReserve = aliceMinReserve + aliceChannel.channelState.LocalChanCfg.ChanReserve = aliceMinReserve // During channel opening Bob will also get to know Alice's // minimum reserve, and this will be found in his remote // config. - bobChannel.remoteChanCfg.ChanReserve = aliceMinReserve + bobChannel.channelState.RemoteChanCfg.ChanReserve = aliceMinReserve // We set Bob's channel reserve to a value that is larger than // his current balance in the channel. This will ensure that // after a channel is first opened, Bob can still receive HTLCs // even though his balance is less than his channel reserve. bobMinReserve := btcutil.Amount(6 * btcutil.SatoshiPerBitcoin) - bobChannel.localChanCfg.ChanReserve = bobMinReserve - aliceChannel.remoteChanCfg.ChanReserve = bobMinReserve + bobChannel.channelState.LocalChanCfg.ChanReserve = bobMinReserve + aliceChannel.channelState.RemoteChanCfg.ChanReserve = bobMinReserve return aliceChannel, bobChannel, cleanUp } @@ -5713,8 +5713,8 @@ func TestMinHTLC(t *testing.T) { // Setting the min value in Alice's local config means that the // remote will not accept any HTLCs of value less than specified. - aliceChannel.localChanCfg.MinHTLC = minValue - bobChannel.remoteChanCfg.MinHTLC = minValue + aliceChannel.channelState.LocalChanCfg.MinHTLC = minValue + bobChannel.channelState.RemoteChanCfg.MinHTLC = minValue // First, we will add an HTLC of 0.5 BTC. This will not trigger // ErrBelowMinHTLC. diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go new file mode 100644 index 000000000..459dd83bd --- /dev/null +++ b/lnwallet/commitment.go @@ -0,0 +1,578 @@ +package lnwallet + +import ( + "fmt" + + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwire" +) + +// 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 +// with each key may belong to the commitment owner or the "other party" which +// is referred to in the field comments, regardless of which is local and which +// is remote. +type CommitmentKeyRing struct { + // CommitPoint is the "per commitment point" used to derive the tweak + // for each base point. + CommitPoint *btcec.PublicKey + + // LocalCommitKeyTweak is the tweak used to derive the local public key + // from the local payment base point or the local private key from the + // base point secret. This may be included in a SignDescriptor to + // generate signatures for the local payment key. + // + // NOTE: This will always refer to "our" local key, regardless of + // whether this is our commit or not. + LocalCommitKeyTweak []byte + + // TODO(roasbeef): need delay tweak as well? + + // LocalHtlcKeyTweak is the tweak used to derive the local HTLC key + // from the local HTLC base point. This value is needed in order to + // derive the final key used within the HTLC scripts in the commitment + // transaction. + // + // NOTE: This will always refer to "our" local HTLC key, regardless of + // whether this is our commit or not. + LocalHtlcKeyTweak []byte + + // LocalHtlcKey is the key that will be used in any clause paying to + // our node of any HTLC scripts within the commitment transaction for + // this key ring set. + // + // NOTE: This will always refer to "our" local HTLC key, regardless of + // whether this is our commit or not. + LocalHtlcKey *btcec.PublicKey + + // RemoteHtlcKey is the key that will be used in clauses within the + // HTLC script that send money to the remote party. + // + // NOTE: This will always refer to "their" remote HTLC key, regardless + // of whether this is our commit or not. + RemoteHtlcKey *btcec.PublicKey + + // ToLocalKey is the commitment transaction owner's key which is + // included in HTLC success and timeout transaction scripts. This is + // the public key used for the to_local output of the commitment + // transaction. + // + // NOTE: Who's key this is depends on the current perspective. If this + // is our commitment this will be our key. + ToLocalKey *btcec.PublicKey + + // ToRemoteKey is the non-owner's payment key in the commitment tx. + // This is the key used to generate the to_remote output within the + // commitment transaction. + // + // NOTE: Who's key this is depends on the current perspective. If this + // is our commitment this will be their key. + ToRemoteKey *btcec.PublicKey + + // RevocationKey is the key that can be used by the other party to + // redeem outputs from a revoked commitment transaction if it were to + // be published. + // + // NOTE: Who can sign for this key depends on the current perspective. + // If this is our commitment, it means the remote node can sign for + // this key in case of a breach. + RevocationKey *btcec.PublicKey +} + +// DeriveCommitmentKeys generates a new commitment key set using the base points +// and commitment point. The keys are derived differently depending on the type +// of channel, and whether the commitment transaction is ours or the remote +// peer's. +func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, + isOurCommit bool, chanType channeldb.ChannelType, + localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *CommitmentKeyRing { + + tweaklessCommit := chanType.IsTweakless() + + // Depending on if this is our commit or not, we'll choose the correct + // base point. + localBasePoint := localChanCfg.PaymentBasePoint + if isOurCommit { + localBasePoint = localChanCfg.DelayBasePoint + } + + // First, we'll derive all the keys that don't depend on the context of + // whose commitment transaction this is. + keyRing := &CommitmentKeyRing{ + CommitPoint: commitPoint, + + LocalCommitKeyTweak: input.SingleTweakBytes( + commitPoint, localBasePoint.PubKey, + ), + LocalHtlcKeyTweak: input.SingleTweakBytes( + commitPoint, localChanCfg.HtlcBasePoint.PubKey, + ), + LocalHtlcKey: input.TweakPubKey( + localChanCfg.HtlcBasePoint.PubKey, commitPoint, + ), + RemoteHtlcKey: input.TweakPubKey( + remoteChanCfg.HtlcBasePoint.PubKey, commitPoint, + ), + } + + // We'll now compute the to_local, to_remote, and revocation key based + // on the current commitment point. All keys are tweaked each state in + // order to ensure the keys from each state are unlinkable. To create + // the revocation key, we take the opposite party's revocation base + // point and combine that with the current commitment point. + var ( + toLocalBasePoint *btcec.PublicKey + toRemoteBasePoint *btcec.PublicKey + revocationBasePoint *btcec.PublicKey + ) + if isOurCommit { + toLocalBasePoint = localChanCfg.DelayBasePoint.PubKey + toRemoteBasePoint = remoteChanCfg.PaymentBasePoint.PubKey + revocationBasePoint = remoteChanCfg.RevocationBasePoint.PubKey + } else { + toLocalBasePoint = remoteChanCfg.DelayBasePoint.PubKey + toRemoteBasePoint = localChanCfg.PaymentBasePoint.PubKey + revocationBasePoint = localChanCfg.RevocationBasePoint.PubKey + } + + // With the base points assigned, we can now derive the actual keys + // using the base point, and the current commitment tweak. + keyRing.ToLocalKey = input.TweakPubKey(toLocalBasePoint, commitPoint) + keyRing.RevocationKey = input.DeriveRevocationPubkey( + revocationBasePoint, commitPoint, + ) + + // If this commitment should omit the tweak for the remote point, then + // we'll use that directly, and ignore the commitPoint tweak. + if tweaklessCommit { + keyRing.ToRemoteKey = toRemoteBasePoint + + // If this is not our commitment, the above ToRemoteKey will be + // ours, and we blank out the local commitment tweak to + // indicate that the key should not be tweaked when signing. + if !isOurCommit { + keyRing.LocalCommitKeyTweak = nil + } + } else { + keyRing.ToRemoteKey = input.TweakPubKey( + toRemoteBasePoint, commitPoint, + ) + } + + return keyRing +} + +// ScriptInfo holds a redeem script and hash. +type ScriptInfo struct { + // PkScript is the output's PkScript. + PkScript []byte + + // WitnessScript is the full script required to properly redeem the + // output. This field should be set to the full script if a p2wsh + // output is being signed. For p2wkh it should be set equal to the + // PkScript. + WitnessScript []byte +} + +// 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, + key *btcec.PublicKey) (*ScriptInfo, error) { + + p2wkh, err := input.CommitScriptUnencumbered(key) + if err != nil { + return nil, err + } + + // Since this is a regular P2WKH, the WitnessScipt and PkScript should + // both be set to the script hash. + return &ScriptInfo{ + WitnessScript: p2wkh, + PkScript: p2wkh, + }, nil +} + +// CommitmentBuilder is a type that wraps the type of channel we are dealing +// with, and abstracts the various ways of constructing commitment +// transactions. +type CommitmentBuilder struct { + // chanState is the underlying channels's state struct, used to + // determine the type of channel we are dealing with, and relevant + // parameters. + chanState *channeldb.OpenChannel + + // obfuscator is a 48-bit state hint that's used to obfuscate the + // current state number on the commitment transactions. + obfuscator [StateHintSize]byte +} + +// NewCommitmentBuilder creates a new CommitmentBuilder from chanState. +func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder { + return &CommitmentBuilder{ + chanState: chanState, + obfuscator: createStateHintObfuscator(chanState), + } +} + +// createStateHintObfuscator derives and assigns the state hint obfuscator for +// the channel, which is used to encode the commitment height in the sequence +// number of commitment transaction inputs. +func createStateHintObfuscator(state *channeldb.OpenChannel) [StateHintSize]byte { + if state.IsInitiator { + return DeriveStateHintObfuscator( + state.LocalChanCfg.PaymentBasePoint.PubKey, + state.RemoteChanCfg.PaymentBasePoint.PubKey, + ) + } + + return DeriveStateHintObfuscator( + state.RemoteChanCfg.PaymentBasePoint.PubKey, + state.LocalChanCfg.PaymentBasePoint.PubKey, + ) +} + +// unsignedCommitmentTx is the final commitment created from evaluating an HTLC +// view at a given height, along with some meta data. +type unsignedCommitmentTx struct { + // txn is the final, unsigned commitment transaction for this view. + txn *wire.MsgTx + + // 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 + // transaction as one party must pay the commitment fee. + ourBalance lnwire.MilliSatoshi + theirBalance lnwire.MilliSatoshi +} + +// 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. +func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, + theirBalance lnwire.MilliSatoshi, isOurs bool, + feePerKw chainfee.SatPerKWeight, height uint64, + filteredHTLCView *htlcView, + keyRing *CommitmentKeyRing) (*unsignedCommitmentTx, error) { + + dustLimit := cb.chanState.LocalChanCfg.DustLimit + if !isOurs { + dustLimit = cb.chanState.RemoteChanCfg.DustLimit + } + + numHTLCs := int64(0) + for _, htlc := range filteredHTLCView.ourUpdates { + if htlcIsDust(false, isOurs, feePerKw, + htlc.Amount.ToSatoshis(), dustLimit) { + + continue + } + + numHTLCs++ + } + for _, htlc := range filteredHTLCView.theirUpdates { + if htlcIsDust(true, isOurs, feePerKw, + htlc.Amount.ToSatoshis(), dustLimit) { + + continue + } + + numHTLCs++ + } + + // Next, we'll calculate the fee for the commitment transaction based + // 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) + + // With the weight known, we can now calculate the commitment fee, + // ensuring that we account for any dust outputs trimmed above. + commitFee := feePerKw.FeeForWeight(totalCommitWeight) + commitFeeMSat := lnwire.NewMSatFromSatoshis(commitFee) + + // Currently, within the protocol, the initiator always pays the fees. + // So we'll subtract the fee amount from the balance of the current + // initiator. If the initiator is unable to pay the fee fully, then + // their entire output is consumed. + switch { + case cb.chanState.IsInitiator && commitFee > ourBalance.ToSatoshis(): + ourBalance = 0 + + case cb.chanState.IsInitiator: + ourBalance -= commitFeeMSat + + case !cb.chanState.IsInitiator && commitFee > theirBalance.ToSatoshis(): + theirBalance = 0 + + case !cb.chanState.IsInitiator: + theirBalance -= commitFeeMSat + } + + var ( + commitTx *wire.MsgTx + err error + ) + + // Depending on whether the transaction is ours or not, we call + // CreateCommitTx with parameters matching the perspective, to generate + // a new commitment transaction with all the latest unsettled/un-timed + // out HTLCs. + if isOurs { + commitTx, err = CreateCommitTx( + cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, + &cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg, + ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), + ) + } else { + commitTx, err = CreateCommitTx( + cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, + &cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg, + theirBalance.ToSatoshis(), ourBalance.ToSatoshis(), + ) + } + if err != nil { + return nil, err + } + + // We'll now add all the HTLC outputs to the commitment transaction. + // Each output includes an off-chain 2-of-2 covenant clause, so we'll + // need the objective local/remote keys for this particular commitment + // as well. For any non-dust HTLCs that are manifested on the commitment + // transaction, we'll also record its CLTV which is required to sort the + // commitment transaction below. The slice is initially sized to the + // number of existing outputs, since any outputs already added are + // commitment outputs and should correspond to zero values for the + // purposes of sorting. + cltvs := make([]uint32, len(commitTx.TxOut)) + for _, htlc := range filteredHTLCView.ourUpdates { + if htlcIsDust(false, isOurs, feePerKw, + htlc.Amount.ToSatoshis(), dustLimit) { + continue + } + + err := addHTLC(commitTx, isOurs, false, htlc, keyRing) + if err != nil { + return nil, err + } + cltvs = append(cltvs, htlc.Timeout) + } + for _, htlc := range filteredHTLCView.theirUpdates { + if htlcIsDust(true, isOurs, feePerKw, + htlc.Amount.ToSatoshis(), dustLimit) { + continue + } + + err := addHTLC(commitTx, isOurs, true, htlc, keyRing) + if err != nil { + return nil, err + } + cltvs = append(cltvs, htlc.Timeout) + } + + // Set the state hint of the commitment transaction to facilitate + // quickly recovering the necessary penalty state in the case of an + // uncooperative broadcast. + err = SetStateNumHint(commitTx, height, cb.obfuscator) + if err != nil { + return nil, err + } + + // Sort the transactions according to the agreed upon canonical + // ordering. This lets us skip sending the entire transaction over, + // instead we'll just send signatures. + InPlaceCommitSort(commitTx, cltvs) + + // Next, we'll ensure that we don't accidentally create a commitment + // transaction which would be invalid by consensus. + uTx := btcutil.NewTx(commitTx) + if err := blockchain.CheckTransactionSanity(uTx); err != nil { + return nil, err + } + + // Finally, we'll assert that were not attempting to draw more out of + // the channel that was originally placed within it. + var totalOut btcutil.Amount + for _, txOut := range commitTx.TxOut { + totalOut += btcutil.Amount(txOut.Value) + } + if totalOut > cb.chanState.Capacity { + return nil, fmt.Errorf("height=%v, for ChannelPoint(%v) "+ + "attempts to consume %v while channel capacity is %v", + height, cb.chanState.FundingOutpoint, + totalOut, cb.chanState.Capacity) + } + + return &unsignedCommitmentTx{ + txn: commitTx, + fee: commitFee, + ourBalance: ourBalance, + theirBalance: theirBalance, + }, nil +} + +// CreateCommitTx creates a commitment transaction, spending from specified +// funding output. The commitment transaction contains two outputs: one local +// output paying to the "owner" of the commitment transaction which can be +// spent after a relative block delay or revocation event, and a remote output +// paying the counterparty within the channel, which can be spent immediately +// or after a delay depending on the commitment type.. +func CreateCommitTx(chanType channeldb.ChannelType, + fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, + localChanCfg, remoteChanCfg *channeldb.ChannelConfig, + amountToLocal, amountToRemote btcutil.Amount) (*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 + // output after a relative block delay, or the remote node can claim + // the funds with the revocation key if we broadcast a revoked + // commitment transaction. + toLocalRedeemScript, err := input.CommitScriptToSelf( + uint32(localChanCfg.CsvDelay), keyRing.ToLocalKey, + keyRing.RevocationKey, + ) + if err != nil { + return nil, err + } + toLocalScriptHash, err := input.WitnessScriptHash( + toLocalRedeemScript, + ) + if err != nil { + return nil, err + } + + // Next, we create the script paying to the remote. + toRemoteScript, err := CommitScriptToRemote( + chanType, uint32(remoteChanCfg.CsvDelay), keyRing.ToRemoteKey, + ) + if err != nil { + return nil, err + } + + // Now that both output scripts have been created, we can finally create + // the transaction itself. We use a transaction version of 2 since CSV + // will fail unless the tx version is >= 2. + commitTx := wire.NewMsgTx(2) + commitTx.AddTxIn(&fundingOutput) + + // Avoid creating dust outputs within the commitment transaction. + if amountToLocal >= localChanCfg.DustLimit { + commitTx.AddTxOut(&wire.TxOut{ + PkScript: toLocalScriptHash, + Value: int64(amountToLocal), + }) + } + if amountToRemote >= localChanCfg.DustLimit { + commitTx.AddTxOut(&wire.TxOut{ + PkScript: toRemoteScript.PkScript, + Value: int64(amountToRemote), + }) + } + + 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, + keyRing *CommitmentKeyRing) ([]byte, []byte, error) { + + var ( + witnessScript []byte + err error + ) + + // 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. + switch { + // The HTLC is paying to us, and being applied to our commitment + // 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[:]) + + // 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[:]) + + // 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[:]) + + // 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[:]) + } + if err != nil { + return nil, nil, err + } + + // Now that we have the redeem scripts, create the P2WSH public key + // script for the output itself. + htlcP2WSH, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, nil, err + } + + return htlcP2WSH, witnessScript, nil +} + +// addHTLC adds a new HTLC to the passed commitment transaction. One of four +// full scripts will be generated for the HTLC output depending on if the HTLC +// is incoming and if it's being applied to our commitment transaction or that +// of the remote node's. Additionally, in order to be able to efficiently +// locate the added HTLC on the commitment transaction from the +// PaymentDescriptor that generated it, the generated script is stored within +// the descriptor itself. +func addHTLC(commitTx *wire.MsgTx, ourCommit bool, + isIncoming bool, paymentDesc *PaymentDescriptor, + keyRing *CommitmentKeyRing) error { + + timeout := paymentDesc.Timeout + rHash := paymentDesc.RHash + + p2wsh, witnessScript, err := genHtlcScript(isIncoming, ourCommit, + timeout, rHash, keyRing) + if err != nil { + return err + } + + // Add the new HTLC outputs to the respective commitment transactions. + amountPending := int64(paymentDesc.Amount.ToSatoshis()) + commitTx.AddTxOut(wire.NewTxOut(amountPending, p2wsh)) + + // Store the pkScript of this particular PaymentDescriptor so we can + // quickly locate it within the commitment transaction later. + if ourCommit { + paymentDesc.ourPkScript = p2wsh + paymentDesc.ourWitnessScript = witnessScript + } else { + paymentDesc.theirPkScript = p2wsh + paymentDesc.theirWitnessScript = witnessScript + } + + return nil +} diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index d9f8d3cab..5253acdda 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -206,9 +206,14 @@ func CreateTestChannels(tweaklessCommits bool) ( } aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:]) + chanType := channeldb.SingleFunderTweaklessBit + if !tweaklessCommits { + chanType = channeldb.SingleFunderBit + } + aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns( channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, - bobCommitPoint, *fundingTxIn, tweaklessCommits, + bobCommitPoint, *fundingTxIn, chanType, ) if err != nil { return nil, nil, nil, err @@ -275,7 +280,7 @@ func CreateTestChannels(tweaklessCommits bool) ( IdentityPub: aliceKeys[0].PubKey(), FundingOutpoint: *prevOut, ShortChannelID: shortChanID, - ChanType: channeldb.SingleFunderTweaklessBit, + ChanType: chanType, IsInitiator: true, Capacity: channelCapacity, RemoteCurrentRevocation: bobCommitPoint, @@ -293,7 +298,7 @@ func CreateTestChannels(tweaklessCommits bool) ( IdentityPub: bobKeys[0].PubKey(), FundingOutpoint: *prevOut, ShortChannelID: shortChanID, - ChanType: channeldb.SingleFunderTweaklessBit, + ChanType: chanType, IsInitiator: false, Capacity: channelCapacity, RemoteCurrentRevocation: aliceCommitPoint, @@ -305,11 +310,6 @@ func CreateTestChannels(tweaklessCommits bool) ( Packager: channeldb.NewChannelPackager(shortChanID), } - if !tweaklessCommits { - aliceChannelState.ChanType = channeldb.SingleFunderBit - bobChannelState.ChanType = channeldb.SingleFunderBit - } - aliceSigner := &input.MockSigner{Privkeys: aliceKeys} bobSigner := &input.MockSigner{Privkeys: bobKeys} @@ -324,6 +324,8 @@ func CreateTestChannels(tweaklessCommits bool) ( } alicePool.Start() + obfuscator := createStateHintObfuscator(aliceChannelState) + bobPool := NewSigPool(1, bobSigner) channelBob, err := NewLightningChannel( bobSigner, bobChannelState, bobPool, @@ -334,13 +336,13 @@ func CreateTestChannels(tweaklessCommits bool) ( bobPool.Start() err = SetStateNumHint( - aliceCommitTx, 0, channelAlice.stateHintObfuscator, + aliceCommitTx, 0, obfuscator, ) if err != nil { return nil, nil, nil, err } err = SetStateNumHint( - bobCommitTx, 0, channelAlice.stateHintObfuscator, + bobCommitTx, 0, obfuscator, ) if err != nil { return nil, nil, nil, err diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index ba6d07265..4f5cc65a4 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -423,14 +423,12 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { channel := LightningChannel{ channelState: &channelState, Signer: signer, - localChanCfg: &channelState.LocalChanCfg, - remoteChanCfg: &channelState.RemoteChanCfg, + commitBuilder: NewCommitmentBuilder(&channelState), } err = channel.createSignDesc() if err != nil { t.Fatalf("Failed to generate channel sign descriptor: %v", err) } - channel.createStateHintObfuscator() // The commitmentPoint is technically hidden in the spec, but we need it to // generate the correct tweak. @@ -441,8 +439,8 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { LocalHtlcKeyTweak: tweak, LocalHtlcKey: tc.localPaymentPubKey, RemoteHtlcKey: tc.remotePaymentPubKey, - DelayKey: tc.localDelayPubKey, - NoDelayKey: tc.remotePaymentPubKey, + ToLocalKey: tc.localDelayPubKey, + ToRemoteKey: tc.remotePaymentPubKey, RevocationKey: tc.localRevocationPubKey, } @@ -796,23 +794,32 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { } theHTLCView := htlcViewFromHTLCs(htlcs) + feePerKw := chainfee.SatPerKWeight(test.commitment.FeePerKw) + isOurs := true + height := test.commitment.CommitHeight + // Create unsigned commitment transaction. - commitmentView := &commitment{ - height: test.commitment.CommitHeight, - ourBalance: test.commitment.LocalBalance, - theirBalance: test.commitment.RemoteBalance, - feePerKw: chainfee.SatPerKWeight(test.commitment.FeePerKw), - dustLimit: tc.dustLimit, - isOurs: true, - } - err = channel.createCommitmentTx( - commitmentView, theHTLCView, keys, + view, err := channel.commitBuilder.createUnsignedCommitmentTx( + test.commitment.LocalBalance, + test.commitment.RemoteBalance, isOurs, feePerKw, + height, theHTLCView, keys, ) if err != nil { t.Errorf("Case %d: Failed to create new commitment tx: %v", i, err) continue } + commitmentView := &commitment{ + ourBalance: view.ourBalance, + theirBalance: view.theirBalance, + txn: view.txn, + fee: view.fee, + height: height, + feePerKw: feePerKw, + dustLimit: tc.dustLimit, + isOurs: isOurs, + } + // Initialize LocalCommit, which is used in getSignedCommitTx. channelState.LocalCommitment = test.commitment channelState.LocalCommitment.Htlcs = htlcs @@ -845,8 +852,8 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { // commitment tx. htlcResolutions, err := extractHtlcResolutions( chainfee.SatPerKWeight(test.commitment.FeePerKw), true, signer, - htlcs, keys, channel.localChanCfg, channel.remoteChanCfg, - commitTx.TxHash(), + htlcs, keys, &channel.channelState.LocalChanCfg, + &channel.channelState.RemoteChanCfg, commitTx.TxHash(), ) if err != nil { t.Errorf("Case %d: Failed to extract HTLC resolutions: %v", i, err) @@ -1017,7 +1024,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) const channelBalance = btcutil.Amount(1 * 10e8) - const csvTimeout = uint32(5) + 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 @@ -1041,8 +1048,10 @@ func testSpendValidation(t *testing.T, tweakless bool) { // 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 } aliceCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub) @@ -1052,6 +1061,20 @@ func testSpendValidation(t *testing.T, tweakless bool) { Privkeys: []*btcec.PrivateKey{aliceKeyPriv}, } + aliceChanCfg := &channeldb.ChannelConfig{ + ChannelConstraints: channeldb.ChannelConstraints{ + DustLimit: DefaultDustLimit(), + CsvDelay: csvTimeout, + }, + } + + bobChanCfg := &channeldb.ChannelConfig{ + ChannelConstraints: channeldb.ChannelConstraints{ + DustLimit: DefaultDustLimit(), + CsvDelay: csvTimeout, + }, + } + // With all the test data set up, we create the commitment transaction. // We only focus on a single party's transactions, as the scripts are // identical with the roles reversed. @@ -1060,13 +1083,13 @@ func testSpendValidation(t *testing.T, tweakless bool) { // of 5 blocks before sweeping the output, while bob can spend // immediately with either the revocation key, or his regular key. keyRing := &CommitmentKeyRing{ - DelayKey: aliceDelayKey, + ToLocalKey: aliceDelayKey, RevocationKey: revokePubKey, - NoDelayKey: bobPayKey, + ToRemoteKey: bobPayKey, } commitmentTx, err := CreateCommitTx( - *fakeFundingTxIn, keyRing, csvTimeout, channelBalance, - channelBalance, DefaultDustLimit(), + channelType, *fakeFundingTxIn, keyRing, aliceChanCfg, + bobChanCfg, channelBalance, channelBalance, ) if err != nil { t.Fatalf("unable to create commitment transaction: %v", nil) diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index a6b9b8753..5143b9f5c 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -772,21 +772,20 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, ourChanCfg, theirChanCfg *channeldb.ChannelConfig, localCommitPoint, remoteCommitPoint *btcec.PublicKey, - fundingTxIn wire.TxIn, - tweaklessCommit bool) (*wire.MsgTx, *wire.MsgTx, error) { + fundingTxIn wire.TxIn, chanType channeldb.ChannelType) ( + *wire.MsgTx, *wire.MsgTx, error) { localCommitmentKeys := DeriveCommitmentKeys( - localCommitPoint, true, tweaklessCommit, ourChanCfg, - theirChanCfg, + localCommitPoint, true, chanType, ourChanCfg, theirChanCfg, ) remoteCommitmentKeys := DeriveCommitmentKeys( - remoteCommitPoint, false, tweaklessCommit, ourChanCfg, - theirChanCfg, + remoteCommitPoint, false, chanType, ourChanCfg, theirChanCfg, ) - ourCommitTx, err := CreateCommitTx(fundingTxIn, localCommitmentKeys, - uint32(ourChanCfg.CsvDelay), localBalance, remoteBalance, - ourChanCfg.DustLimit) + ourCommitTx, err := CreateCommitTx( + chanType, fundingTxIn, localCommitmentKeys, ourChanCfg, + theirChanCfg, localBalance, remoteBalance, + ) if err != nil { return nil, nil, err } @@ -796,9 +795,10 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, return nil, nil, err } - theirCommitTx, err := CreateCommitTx(fundingTxIn, remoteCommitmentKeys, - uint32(theirChanCfg.CsvDelay), remoteBalance, localBalance, - theirChanCfg.DustLimit) + theirCommitTx, err := CreateCommitTx( + chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg, + ourChanCfg, remoteBalance, localBalance, + ) if err != nil { return nil, nil, err } @@ -928,13 +928,12 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { // With the funding tx complete, create both commitment transactions. localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis() remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis() - tweaklessCommits := pendingReservation.partialState.ChanType.IsTweakless() ourCommitTx, theirCommitTx, err := CreateCommitmentTxns( localBalance, remoteBalance, ourContribution.ChannelConfig, theirContribution.ChannelConfig, ourContribution.FirstCommitmentPoint, theirContribution.FirstCommitmentPoint, fundingTxIn, - tweaklessCommits, + pendingReservation.partialState.ChanType, ) if err != nil { req.err <- err @@ -1288,14 +1287,13 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { // remote node's commitment transactions. localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis() remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis() - tweaklessCommits := pendingReservation.partialState.ChanType.IsTweakless() ourCommitTx, theirCommitTx, err := CreateCommitmentTxns( localBalance, remoteBalance, pendingReservation.ourContribution.ChannelConfig, pendingReservation.theirContribution.ChannelConfig, pendingReservation.ourContribution.FirstCommitmentPoint, pendingReservation.theirContribution.FirstCommitmentPoint, - *fundingTxIn, tweaklessCommits, + *fundingTxIn, pendingReservation.partialState.ChanType, ) if err != nil { req.err <- err diff --git a/test_utils.go b/test_utils.go index afb16cd54..757d33e4a 100644 --- a/test_utils.go +++ b/test_utils.go @@ -195,7 +195,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, publTx chan *wire.MsgTx, aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns( channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, - bobCommitPoint, *fundingTxIn, true, + bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit, ) if err != nil { return nil, nil, nil, nil, err diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index abdabe2f9..c112c1016 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -189,7 +189,7 @@ func (t *backupTask) craftSessionPayload( justiceKit := &blob.JusticeKit{ SweepAddress: t.sweepPkScript, RevocationPubKey: toBlobPubKey(keyRing.RevocationKey), - LocalDelayPubKey: toBlobPubKey(keyRing.DelayKey), + LocalDelayPubKey: toBlobPubKey(keyRing.ToLocalKey), CSVDelay: t.breachInfo.RemoteDelay, } @@ -199,7 +199,7 @@ func (t *backupTask) craftSessionPayload( // output to spend from. if t.toRemoteInput != nil { justiceKit.CommitToRemotePubKey = toBlobPubKey( - keyRing.NoDelayKey, + keyRing.ToRemoteKey, ) } diff --git a/watchtower/wtclient/backup_task_internal_test.go b/watchtower/wtclient/backup_task_internal_test.go index d845631ce..3c8ed17f7 100644 --- a/watchtower/wtclient/backup_task_internal_test.go +++ b/watchtower/wtclient/backup_task_internal_test.go @@ -121,8 +121,8 @@ func genTaskTest( BreachTransaction: breachTxn, KeyRing: &lnwallet.CommitmentKeyRing{ RevocationKey: revPK, - DelayKey: toLocalPK, - NoDelayKey: toRemotePK, + ToLocalKey: toLocalPK, + ToRemoteKey: toRemotePK, }, RemoteDelay: csvDelay, } @@ -565,9 +565,9 @@ func testBackupTask(t *testing.T, test backupTaskTest) { } keyRing := test.breachInfo.KeyRing - expToLocalPK := keyRing.DelayKey.SerializeCompressed() + expToLocalPK := keyRing.ToLocalKey.SerializeCompressed() expRevPK := keyRing.RevocationKey.SerializeCompressed() - expToRemotePK := keyRing.NoDelayKey.SerializeCompressed() + expToRemotePK := keyRing.ToRemoteKey.SerializeCompressed() // Assert that the blob contained the serialized revocation and to-local // pubkeys. diff --git a/watchtower/wtclient/client_test.go b/watchtower/wtclient/client_test.go index bd16ac065..ea681d575 100644 --- a/watchtower/wtclient/client_test.go +++ b/watchtower/wtclient/client_test.go @@ -291,8 +291,8 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) { commitKeyRing := &lnwallet.CommitmentKeyRing{ RevocationKey: c.revPK, - NoDelayKey: c.toLocalPK, - DelayKey: c.toRemotePK, + ToRemoteKey: c.toLocalPK, + ToLocalKey: c.toRemotePK, } retribution := &lnwallet.BreachRetribution{