From 90c45ddd8ffd0580e8ded3f1d0c9bb8e52636dca Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sun, 31 Dec 2023 16:00:01 +0100 Subject: [PATCH] lnwallet: factor out func GetSignedCommitTx This pure function creates signed commit transaction, using various inputs passed as struct TaprootSignedCommitTxInputs and a signer. This is needed to be able to store the inputs without a signature in SCB and sign the transaction in chantools scbforceclose. See https://github.com/lightningnetwork/lnd/pull/8183/files#r1423959791 --- lnwallet/channel.go | 126 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 22 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index f3e076950..aea4719b8 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -6433,29 +6433,84 @@ func (lc *LightningChannel) AbsoluteThawHeight() (uint32, error) { return lc.channelState.AbsoluteThawHeight() } -// getSignedCommitTx function take the latest commitment transaction and -// populate it with witness data. -func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { - // Fetch the current commitment transaction, along with their signature - // for the transaction. - localCommit := lc.channelState.LocalCommitment - commitTx := localCommit.CommitTx.Copy() +// SignedCommitTxInputs contains data needed to create a signed commit +// transaction using a signer. See GetSignedCommitTx. +type SignedCommitTxInputs struct { + // CommitTx is the latest version of the commitment state, broadcast + // able by us. + CommitTx *wire.MsgTx - ourKey := lc.channelState.LocalChanCfg.MultiSigKey - theirKey := lc.channelState.RemoteChanCfg.MultiSigKey + // CommitSig is one half of the signature required to fully complete + // the script for the commitment transaction above. This is the + // signature signed by the remote party for our version of the + // commitment transactions. + CommitSig []byte + + // OurKey is our key to be used within the 2-of-2 output script + // for the owner of this channel. + OurKey keychain.KeyDescriptor + + // TheirKey is their key to be used within the 2-of-2 output script + // for the owner of this channel. + TheirKey keychain.KeyDescriptor + + // SignDesc is the primary sign descriptor that is capable of signing + // the commitment transaction that spends the multi-sig output. + SignDesc *input.SignDescriptor + + // Taproot holds fields needed in case of a taproot channel. + // Iff the channel is of taproot type, this field is filled. + Taproot fn.Option[TaprootSignedCommitTxInputs] +} + +// TaprootSignedCommitTxInputs contains additional data needed to create a +// signed commit transaction using a signer, used in case of a taproot channel. +// See GetSignedCommitTx. +type TaprootSignedCommitTxInputs struct { + // CommitHeight is the update number that this channel state represents. + // It is the total number of commitment updates up to this point. This + // can be viewed as sort of a "commitment height" as this number is + // monotonically increasing. This number is used to make a signature + // for a taproot channel, since it is used by shachain nonce producer + // (TaprootNonceProducer). + CommitHeight uint64 + + // TaprootNonceProducer is used to generate a shachain tree for the + // purpose of generating verification nonces for taproot channels. + TaprootNonceProducer shachain.Producer + + // TapscriptRoot is the root of the tapscript tree that will be used to + // create the funding output. This is an optional field that should + // only be set for taproot channels. + TapscriptRoot fn.Option[chainhash.Hash] +} + +// GetSignedCommitTx creates the witness stack of a channel commitment +// transaction. It can handle all commitment types (taproot, legacy). It is +// exported to give outside tooling the possibility to recreate the witness. +// A key use case is generating the witness data for a commitment transaction +// from a Static Channel Backup (SCB). +func GetSignedCommitTx(inputs SignedCommitTxInputs, + signer input.Signer) (*wire.MsgTx, error) { + + commitTx := inputs.CommitTx.Copy() var witness wire.TxWitness switch { // If this is a taproot channel, then we'll need to re-derive the nonce // we need to generate a new signature - case lc.channelState.ChanType.IsTaproot(): + case inputs.Taproot.IsSome(): + // Extract Taproot from fn.Option. It is safe to call + // UnsafeFromSome because we just checked that it is some. + taproot := inputs.Taproot.UnsafeFromSome() + // First, we'll need to re-derive the local nonce we sent to // the remote party to create this musig session. We pass in // the same height here as we're generating the nonce needed // for the _current_ state. localNonce, err := channeldb.NewMusigVerificationNonce( - ourKey.PubKey, lc.currentHeight, - lc.taprootNonceProducer, + inputs.OurKey.PubKey, taproot.CommitHeight, + taproot.TaprootNonceProducer, ) if err != nil { return nil, fmt.Errorf("unable to re-derive "+ @@ -6463,19 +6518,20 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { } tapscriptTweak := fn.MapOption(TapscriptRootToTweak)( - lc.channelState.TapscriptRoot, + taproot.TapscriptRoot, ) // Now that we have the local nonce, we'll re-create the musig // session we had for this height. musigSession := NewPartialMusigSession( - *localNonce, ourKey, theirKey, lc.Signer, - &lc.fundingOutput, LocalMusigCommit, tapscriptTweak, + *localNonce, inputs.OurKey, inputs.TheirKey, signer, + inputs.SignDesc.Output, LocalMusigCommit, + tapscriptTweak, ) var remoteSig lnwire.PartialSigWithNonce err = remoteSig.Decode( - bytes.NewReader(localCommit.CommitSig), + bytes.NewReader(inputs.CommitSig), ) if err != nil { return nil, fmt.Errorf("unable to decode remote "+ @@ -6521,15 +6577,15 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { // Otherwise, the final witness we generate will be a normal p2wsh // multi-sig spend. default: - theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig) + theirSig, err := ecdsa.ParseDERSignature(inputs.CommitSig) if err != nil { return nil, err } // With this, we then generate the full witness so the caller // can broadcast a fully signed transaction. - lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(commitTx) - ourSig, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) + inputs.SignDesc.SigHashes = input.NewTxSigHashesV0Only(commitTx) + ourSig, err := signer.SignOutputRaw(commitTx, inputs.SignDesc) if err != nil { return nil, err } @@ -6537,9 +6593,9 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { // With the final signature generated, create the witness stack // required to spend from the multi-sig output. witness = input.SpendMultiSig( - lc.signDesc.WitnessScript, - ourKey.PubKey.SerializeCompressed(), ourSig, - theirKey.PubKey.SerializeCompressed(), theirSig, + inputs.SignDesc.WitnessScript, + inputs.OurKey.PubKey.SerializeCompressed(), ourSig, + inputs.TheirKey.PubKey.SerializeCompressed(), theirSig, ) } @@ -6548,6 +6604,32 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { return commitTx, nil } +// getSignedCommitTx method takes the latest commitment transaction and +// populates it with witness data. +func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { + // Fetch the current commitment transaction, along with their signature + // for the transaction. + localCommit := lc.channelState.LocalCommitment + + inputs := SignedCommitTxInputs{ + CommitTx: localCommit.CommitTx, + CommitSig: localCommit.CommitSig, + OurKey: lc.channelState.LocalChanCfg.MultiSigKey, + TheirKey: lc.channelState.RemoteChanCfg.MultiSigKey, + SignDesc: lc.signDesc, + } + + if lc.channelState.ChanType.IsTaproot() { + inputs.Taproot = fn.Some(TaprootSignedCommitTxInputs{ + CommitHeight: lc.currentHeight, + TaprootNonceProducer: lc.taprootNonceProducer, + TapscriptRoot: lc.channelState.TapscriptRoot, + }) + } + + return GetSignedCommitTx(inputs, lc.Signer) +} + // CommitOutputResolution carries the necessary information required to allow // us to sweep our commitment output in the case that either party goes to // chain.