From a56ed72bd7d136f8eb85c01aabc9fc8cab544f49 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Mon, 6 Jan 2020 11:42:04 +0100 Subject: [PATCH] lnwallet: use channel type to derive remote script Based on the current channel type, we derive the script used for the to_remote output. Currently only the unencumbered p2wkh type is used, but that will change with upcoming channel types. --- contractcourt/chain_watcher.go | 7 +-- lnwallet/channel.go | 91 +++++++++++++++++++--------------- lnwallet/commitment.go | 55 +++++++++++++++----- lnwallet/transactions_test.go | 6 ++- lnwallet/wallet.go | 8 +-- 5 files changed, 105 insertions(+), 62 deletions(-) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index c37677b7d..e9eb8e396 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -351,8 +351,9 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig, // 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.ToRemoteKey, + remoteDelay := uint32(remoteChanCfg.CsvDelay) + remoteScript, err := lnwallet.CommitScriptToRemote( + chanType, remoteDelay, commitKeyRing.ToRemoteKey, ) if err != nil { return false, err @@ -383,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 } } diff --git a/lnwallet/channel.go b/lnwallet/channel.go index a8bde14ef..210328e49 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1871,36 +1871,42 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // 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.ToLocalKey, 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.ToRemoteKey) + + // 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) } } @@ -1908,39 +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, } } - // 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, } @@ -1971,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.ToLocalKey, remoteDelay, + keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay, ) if err != nil { return nil, err @@ -2037,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 } @@ -4758,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.ToRemoteKey) + 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) @@ -4770,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), @@ -4791,10 +4800,10 @@ 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, }, diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 59f5f197b..b00e60435 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -162,6 +162,37 @@ func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, 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. @@ -292,15 +323,15 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, // out HTLCs. if isOurs { commitTx, err = CreateCommitTx( - fundingTxIn(cb.chanState), keyRing, &cb.chanState.LocalChanCfg, - &cb.chanState.RemoteChanCfg, ourBalance.ToSatoshis(), - theirBalance.ToSatoshis(), + cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, + &cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg, + ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), ) } else { commitTx, err = CreateCommitTx( - fundingTxIn(cb.chanState), keyRing, &cb.chanState.RemoteChanCfg, - &cb.chanState.LocalChanCfg, theirBalance.ToSatoshis(), - ourBalance.ToSatoshis(), + cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing, + &cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg, + theirBalance.ToSatoshis(), ourBalance.ToSatoshis(), ) } if err != nil { @@ -389,7 +420,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance, // 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(fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, +func CreateCommitTx(chanType channeldb.ChannelType, + fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) { @@ -412,10 +444,9 @@ func CreateCommitTx(fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, return nil, err } - // Next, we create the script paying to the remote. This is just a - // regular P2WPKH output, without any added CSV delay. - toRemoteWitnessKeyHash, err := input.CommitScriptUnencumbered( - keyRing.ToRemoteKey, + // Next, we create the script paying to the remote. + toRemoteScript, err := CommitScriptToRemote( + chanType, uint32(remoteChanCfg.CsvDelay), keyRing.ToRemoteKey, ) if err != nil { return nil, err @@ -436,7 +467,7 @@ func CreateCommitTx(fundingOutput wire.TxIn, keyRing *CommitmentKeyRing, } if amountToRemote >= localChanCfg.DustLimit { commitTx.AddTxOut(&wire.TxOut{ - PkScript: toRemoteWitnessKeyHash, + PkScript: toRemoteScript.PkScript, Value: int64(amountToRemote), }) } diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index 20f250352..4f5cc65a4 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -1048,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) @@ -1086,8 +1088,8 @@ func testSpendValidation(t *testing.T, tweakless bool) { ToRemoteKey: bobPayKey, } commitmentTx, err := CreateCommitTx( - *fakeFundingTxIn, keyRing, aliceChanCfg, bobChanCfg, - channelBalance, channelBalance, + 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 a466a6d49..5143b9f5c 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -783,8 +783,8 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, ) ourCommitTx, err := CreateCommitTx( - fundingTxIn, localCommitmentKeys, ourChanCfg, theirChanCfg, - localBalance, remoteBalance, + chanType, fundingTxIn, localCommitmentKeys, ourChanCfg, + theirChanCfg, localBalance, remoteBalance, ) if err != nil { return nil, nil, err @@ -796,8 +796,8 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, } theirCommitTx, err := CreateCommitTx( - fundingTxIn, remoteCommitmentKeys, theirChanCfg, ourChanCfg, - remoteBalance, localBalance, + chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg, + ourChanCfg, remoteBalance, localBalance, ) if err != nil { return nil, nil, err