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.
This commit is contained in:
Johan T. Halseth 2020-01-06 11:42:04 +01:00
parent 9b5809a884
commit a56ed72bd7
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
5 changed files with 105 additions and 62 deletions

@ -351,8 +351,9 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
// With the keys derived, we'll construct the remote script that'll be // With the keys derived, we'll construct the remote script that'll be
// present if they have a non-dust balance on the commitment. // present if they have a non-dust balance on the commitment.
remotePkScript, err := input.CommitScriptUnencumbered( remoteDelay := uint32(remoteChanCfg.CsvDelay)
commitKeyRing.ToRemoteKey, remoteScript, err := lnwallet.CommitScriptToRemote(
chanType, remoteDelay, commitKeyRing.ToRemoteKey,
) )
if err != nil { if err != nil {
return false, err return false, err
@ -383,7 +384,7 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
case bytes.Equal(localPkScript, pkScript): case bytes.Equal(localPkScript, pkScript):
return true, nil return true, nil
case bytes.Equal(remotePkScript, pkScript): case bytes.Equal(remoteScript.PkScript, pkScript):
return true, nil return true, nil
} }
} }

@ -1871,36 +1871,42 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// Next, reconstruct the scripts as they were present at this state // Next, reconstruct the scripts as they were present at this state
// number so we can have the proper witness script to sign and include // number so we can have the proper witness script to sign and include
// within the final witness. // within the final witness.
remoteDelay := uint32(chanState.RemoteChanCfg.CsvDelay) theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
remotePkScript, err := input.CommitScriptToSelf( theirPkScript, err := input.CommitScriptToSelf(
remoteDelay, keyRing.ToLocalKey, keyRing.RevocationKey, theirDelay, keyRing.ToLocalKey, keyRing.RevocationKey,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
remoteWitnessHash, err := input.WitnessScriptHash(remotePkScript) theirWitnessHash, err := input.WitnessScriptHash(theirPkScript)
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
// In order to fully populate the breach retribution struct, we'll need // In order to fully populate the breach retribution struct, we'll need
// to find the exact index of the local+remote commitment outputs. // to find the exact index of the commitment outputs.
localOutpoint := wire.OutPoint{ ourOutpoint := wire.OutPoint{
Hash: commitHash, Hash: commitHash,
} }
remoteOutpoint := wire.OutPoint{ theirOutpoint := wire.OutPoint{
Hash: commitHash, Hash: commitHash,
} }
for i, txOut := range revokedSnapshot.CommitTx.TxOut { for i, txOut := range revokedSnapshot.CommitTx.TxOut {
switch { switch {
case bytes.Equal(txOut.PkScript, localPkScript): case bytes.Equal(txOut.PkScript, ourScript.PkScript):
localOutpoint.Index = uint32(i) ourOutpoint.Index = uint32(i)
case bytes.Equal(txOut.PkScript, remoteWitnessHash): case bytes.Equal(txOut.PkScript, theirWitnessHash):
remoteOutpoint.Index = uint32(i) 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 // commitment outputs. If either is considered dust using the remote
// party's dust limit, the respective sign descriptor will be nil. // party's dust limit, the respective sign descriptor will be nil.
var ( var (
localSignDesc *input.SignDescriptor ourSignDesc *input.SignDescriptor
remoteSignDesc *input.SignDescriptor theirSignDesc *input.SignDescriptor
) )
// Compute the local and remote balances in satoshis. // Compute the balances in satoshis.
localAmt := revokedSnapshot.LocalBalance.ToSatoshis() ourAmt := revokedSnapshot.LocalBalance.ToSatoshis()
remoteAmt := revokedSnapshot.RemoteBalance.ToSatoshis() theirAmt := revokedSnapshot.RemoteBalance.ToSatoshis()
// If the local balance exceeds the remote party's dust limit, // If our balance exceeds the remote party's dust limit, instantiate
// instantiate the local sign descriptor. // the sign descriptor for our output.
if localAmt >= chanState.RemoteChanCfg.DustLimit { if ourAmt >= chanState.RemoteChanCfg.DustLimit {
localSignDesc = &input.SignDescriptor{ ourSignDesc = &input.SignDescriptor{
SingleTweak: keyRing.LocalCommitKeyTweak, SingleTweak: keyRing.LocalCommitKeyTweak,
KeyDesc: chanState.LocalChanCfg.PaymentBasePoint, KeyDesc: chanState.LocalChanCfg.PaymentBasePoint,
WitnessScript: localPkScript, WitnessScript: ourScript.WitnessScript,
Output: &wire.TxOut{ Output: &wire.TxOut{
PkScript: localPkScript, PkScript: ourScript.PkScript,
Value: int64(localAmt), Value: int64(ourAmt),
}, },
HashType: txscript.SigHashAll, HashType: txscript.SigHashAll,
} }
} }
// Similarly, if the remote balance exceeds the remote party's dust // Similarly, if their balance exceeds the remote party's dust limit,
// limit, assemble the remote sign descriptor. // assemble the sign descriptor for their output, which we can sweep.
if remoteAmt >= chanState.RemoteChanCfg.DustLimit { if theirAmt >= chanState.RemoteChanCfg.DustLimit {
remoteSignDesc = &input.SignDescriptor{ theirSignDesc = &input.SignDescriptor{
KeyDesc: chanState.LocalChanCfg.RevocationBasePoint, KeyDesc: chanState.LocalChanCfg.RevocationBasePoint,
DoubleTweak: commitmentSecret, DoubleTweak: commitmentSecret,
WitnessScript: remotePkScript, WitnessScript: theirPkScript,
Output: &wire.TxOut{ Output: &wire.TxOut{
PkScript: remoteWitnessHash, PkScript: theirWitnessHash,
Value: int64(remoteAmt), Value: int64(theirAmt),
}, },
HashType: txscript.SigHashAll, HashType: txscript.SigHashAll,
} }
@ -1971,7 +1977,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// remote commitment transaction, and *they* go to the second // remote commitment transaction, and *they* go to the second
// level. // level.
secondLevelWitnessScript, err := input.SecondLevelHtlcScript( secondLevelWitnessScript, err := input.SecondLevelHtlcScript(
keyRing.RevocationKey, keyRing.ToLocalKey, remoteDelay, keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -2037,13 +2043,13 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
BreachHeight: breachHeight, BreachHeight: breachHeight,
RevokedStateNum: stateNum, RevokedStateNum: stateNum,
PendingHTLCs: revokedSnapshot.Htlcs, PendingHTLCs: revokedSnapshot.Htlcs,
LocalOutpoint: localOutpoint, LocalOutpoint: ourOutpoint,
LocalOutputSignDesc: localSignDesc, LocalOutputSignDesc: ourSignDesc,
RemoteOutpoint: remoteOutpoint, RemoteOutpoint: theirOutpoint,
RemoteOutputSignDesc: remoteSignDesc, RemoteOutputSignDesc: theirSignDesc,
HtlcRetributions: htlcRetributions, HtlcRetributions: htlcRetributions,
KeyRing: keyRing, KeyRing: keyRing,
RemoteDelay: remoteDelay, RemoteDelay: theirDelay,
}, nil }, 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 // Before we can generate the proper sign descriptor, we'll need to
// locate the output index of our non-delayed output on the commitment // locate the output index of our non-delayed output on the commitment
// transaction. // transaction.
selfP2WKH, err := input.CommitScriptUnencumbered(keyRing.ToRemoteKey) localDelay := uint32(chanState.LocalChanCfg.CsvDelay)
selfScript, err := CommitScriptToRemote(
chanState.ChanType, localDelay, keyRing.ToRemoteKey,
)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create self commit "+ return nil, fmt.Errorf("unable to create self commit "+
"script: %v", err) "script: %v", err)
@ -4770,7 +4779,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
) )
for outputIndex, txOut := range commitTxBroadcast.TxOut { for outputIndex, txOut := range commitTxBroadcast.TxOut {
if bytes.Equal(txOut.PkScript, selfP2WKH) { if bytes.Equal(txOut.PkScript, selfScript.PkScript) {
selfPoint = &wire.OutPoint{ selfPoint = &wire.OutPoint{
Hash: *commitSpend.SpenderTxHash, Hash: *commitSpend.SpenderTxHash,
Index: uint32(outputIndex), Index: uint32(outputIndex),
@ -4791,10 +4800,10 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
SelfOutputSignDesc: input.SignDescriptor{ SelfOutputSignDesc: input.SignDescriptor{
KeyDesc: localPayBase, KeyDesc: localPayBase,
SingleTweak: keyRing.LocalCommitKeyTweak, SingleTweak: keyRing.LocalCommitKeyTweak,
WitnessScript: selfP2WKH, WitnessScript: selfScript.WitnessScript,
Output: &wire.TxOut{ Output: &wire.TxOut{
Value: localBalance, Value: localBalance,
PkScript: selfP2WKH, PkScript: selfScript.PkScript,
}, },
HashType: txscript.SigHashAll, HashType: txscript.SigHashAll,
}, },

@ -162,6 +162,37 @@ func DeriveCommitmentKeys(commitPoint *btcec.PublicKey,
return keyRing 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 // CommitmentBuilder is a type that wraps the type of channel we are dealing
// with, and abstracts the various ways of constructing commitment // with, and abstracts the various ways of constructing commitment
// transactions. // transactions.
@ -292,15 +323,15 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
// out HTLCs. // out HTLCs.
if isOurs { if isOurs {
commitTx, err = CreateCommitTx( commitTx, err = CreateCommitTx(
fundingTxIn(cb.chanState), keyRing, &cb.chanState.LocalChanCfg, cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
&cb.chanState.RemoteChanCfg, ourBalance.ToSatoshis(), &cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
) )
} else { } else {
commitTx, err = CreateCommitTx( commitTx, err = CreateCommitTx(
fundingTxIn(cb.chanState), keyRing, &cb.chanState.RemoteChanCfg, cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
&cb.chanState.LocalChanCfg, theirBalance.ToSatoshis(), &cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
) )
} }
if err != nil { 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 // spent after a relative block delay or revocation event, and a remote output
// paying the counterparty within the channel, which can be spent immediately // paying the counterparty within the channel, which can be spent immediately
// or after a delay depending on the commitment type.. // 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, localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) { amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) {
@ -412,10 +444,9 @@ func CreateCommitTx(fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
return nil, err return nil, err
} }
// Next, we create the script paying to the remote. This is just a // Next, we create the script paying to the remote.
// regular P2WPKH output, without any added CSV delay. toRemoteScript, err := CommitScriptToRemote(
toRemoteWitnessKeyHash, err := input.CommitScriptUnencumbered( chanType, uint32(remoteChanCfg.CsvDelay), keyRing.ToRemoteKey,
keyRing.ToRemoteKey,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -436,7 +467,7 @@ func CreateCommitTx(fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
} }
if amountToRemote >= localChanCfg.DustLimit { if amountToRemote >= localChanCfg.DustLimit {
commitTx.AddTxOut(&wire.TxOut{ commitTx.AddTxOut(&wire.TxOut{
PkScript: toRemoteWitnessKeyHash, PkScript: toRemoteScript.PkScript,
Value: int64(amountToRemote), Value: int64(amountToRemote),
}) })
} }

@ -1048,8 +1048,10 @@ func testSpendValidation(t *testing.T, tweakless bool) {
// our commitments, if it's tweakless, his key will just be his regular // our commitments, if it's tweakless, his key will just be his regular
// pubkey. // pubkey.
bobPayKey := input.TweakPubKey(bobKeyPub, commitPoint) bobPayKey := input.TweakPubKey(bobKeyPub, commitPoint)
channelType := channeldb.SingleFunderBit
if tweakless { if tweakless {
bobPayKey = bobKeyPub bobPayKey = bobKeyPub
channelType = channeldb.SingleFunderTweaklessBit
} }
aliceCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub) aliceCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub)
@ -1086,8 +1088,8 @@ func testSpendValidation(t *testing.T, tweakless bool) {
ToRemoteKey: bobPayKey, ToRemoteKey: bobPayKey,
} }
commitmentTx, err := CreateCommitTx( commitmentTx, err := CreateCommitTx(
*fakeFundingTxIn, keyRing, aliceChanCfg, bobChanCfg, channelType, *fakeFundingTxIn, keyRing, aliceChanCfg,
channelBalance, channelBalance, bobChanCfg, channelBalance, channelBalance,
) )
if err != nil { if err != nil {
t.Fatalf("unable to create commitment transaction: %v", nil) t.Fatalf("unable to create commitment transaction: %v", nil)

@ -783,8 +783,8 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
) )
ourCommitTx, err := CreateCommitTx( ourCommitTx, err := CreateCommitTx(
fundingTxIn, localCommitmentKeys, ourChanCfg, theirChanCfg, chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
localBalance, remoteBalance, theirChanCfg, localBalance, remoteBalance,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -796,8 +796,8 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
} }
theirCommitTx, err := CreateCommitTx( theirCommitTx, err := CreateCommitTx(
fundingTxIn, remoteCommitmentKeys, theirChanCfg, ourChanCfg, chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
remoteBalance, localBalance, ourChanCfg, remoteBalance, localBalance,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err