@@ -7039,6 +7039,30 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
70397039 }, nil
70407040}
70417041
7042+ // chanCloseOpt is a functional option that can be used to modify the co-op
7043+ // close process.
7044+ type chanCloseOpt struct {
7045+ musigSession * MusigSession
7046+ }
7047+
7048+ // ChanCloseOpt is a closure type that cen be used to modify the set of default
7049+ // options.
7050+ type ChanCloseOpt func (* chanCloseOpt )
7051+
7052+ // defaultCloseOpts is the default set of close options.
7053+ func defaultCloseOpts () * chanCloseOpt {
7054+ return & chanCloseOpt {}
7055+ }
7056+
7057+ // WithCoopCloseMusigSession can be used to apply an existing musig2 session to
7058+ // the cooperative close process. If specified, then a musig2 co-op close
7059+ // (single sig keyspend) will be used.
7060+ func WithCoopCloseMusigSession (session * MusigSession ) ChanCloseOpt {
7061+ return func (opts * chanCloseOpt ) {
7062+ opts .musigSession = session
7063+ }
7064+ }
7065+
70427066// CreateCloseProposal is used by both parties in a cooperative channel close
70437067// workflow to generate proposed close transactions and signatures. This method
70447068// should only be executed once all pending HTLCs (if any) on the channel have
@@ -7050,8 +7074,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
70507074// TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs,
70517075// settle any in flight.
70527076func (lc * LightningChannel ) CreateCloseProposal (proposedFee btcutil.Amount ,
7053- localDeliveryScript []byte ,
7054- remoteDeliveryScript [] byte ) (input.Signature , * chainhash.Hash ,
7077+ localDeliveryScript []byte , remoteDeliveryScript [] byte ,
7078+ closeOpts ... ChanCloseOpt ) (input.Signature , * chainhash.Hash ,
70557079 btcutil.Amount , error ) {
70567080
70577081 lc .Lock ()
@@ -7063,6 +7087,11 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount,
70637087 return nil , nil , 0 , ErrChanClosing
70647088 }
70657089
7090+ opts := defaultCloseOpts ()
7091+ for _ , optFunc := range closeOpts {
7092+ optFunc (opts )
7093+ }
7094+
70667095 // Get the final balances after subtracting the proposed fee, taking
70677096 // care not to persist the adjusted balance, as the feeRate may change
70687097 // during the channel closing process.
@@ -7088,14 +7117,25 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount,
70887117 return nil , nil , 0 , err
70897118 }
70907119
7091- // Finally, sign the completed cooperative closure transaction. As the
7092- // initiator we'll simply send our signature over to the remote party,
7093- // using the generated txid to be notified once the closure transaction
7094- // has been confirmed.
7095- lc .signDesc .SigHashes = input .NewTxSigHashesV0Only (closeTx )
7096- sig , err := lc .Signer .SignOutputRaw (closeTx , lc .signDesc )
7097- if err != nil {
7098- return nil , nil , 0 , err
7120+ // If we have a co-op close musig session, then this is a taproot
7121+ // channel, so we'll generate a _partial_ signature.
7122+ var sig input.Signature
7123+ if opts .musigSession != nil {
7124+ sig , err = opts .musigSession .SignCommit (closeTx )
7125+ if err != nil {
7126+ return nil , nil , 0 , err
7127+ }
7128+ } else {
7129+ // For regular channels we'll, sign the completed cooperative
7130+ // closure transaction. As the initiator we'll simply send our
7131+ // signature over to the remote party, using the generated txid
7132+ // to be notified once the closure transaction has been
7133+ // confirmed.
7134+ lc .signDesc .SigHashes = input .NewTxSigHashesV0Only (closeTx )
7135+ sig , err = lc .Signer .SignOutputRaw (closeTx , lc .signDesc )
7136+ if err != nil {
7137+ return nil , nil , 0 , err
7138+ }
70997139 }
71007140
71017141 // As everything checks out, indicate in the channel status that a
@@ -7116,7 +7156,8 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount,
71167156func (lc * LightningChannel ) CompleteCooperativeClose (
71177157 localSig , remoteSig input.Signature ,
71187158 localDeliveryScript , remoteDeliveryScript []byte ,
7119- proposedFee btcutil.Amount ) (* wire.MsgTx , btcutil.Amount , error ) {
7159+ proposedFee btcutil.Amount ,
7160+ closeOpts ... ChanCloseOpt ) (* wire.MsgTx , btcutil.Amount , error ) {
71207161
71217162 lc .Lock ()
71227163 defer lc .Unlock ()
@@ -7127,6 +7168,11 @@ func (lc *LightningChannel) CompleteCooperativeClose(
71277168 return nil , 0 , ErrChanClosing
71287169 }
71297170
7171+ opts := defaultCloseOpts ()
7172+ for _ , optFunc := range closeOpts {
7173+ optFunc (opts )
7174+ }
7175+
71307176 // Get the final balances after subtracting the proposed fee.
71317177 ourBalance , theirBalance , err := CoopCloseBalance (
71327178 lc .channelState .ChanType , lc .channelState .IsInitiator ,
@@ -7149,31 +7195,62 @@ func (lc *LightningChannel) CompleteCooperativeClose(
71497195 // consensus rules such as being too big, or having any value with a
71507196 // negative output.
71517197 tx := btcutil .NewTx (closeTx )
7198+ prevOut := lc .signDesc .Output
71527199 if err := blockchain .CheckTransactionSanity (tx ); err != nil {
71537200 return nil , 0 , err
71547201 }
7155- hashCache := input .NewTxSigHashesV0Only (closeTx )
7156-
7157- // Finally, construct the witness stack minding the order of the
7158- // pubkeys+sigs on the stack.
7159- ourKey := lc .channelState .LocalChanCfg .MultiSigKey .PubKey .
7160- SerializeCompressed ()
7161- theirKey := lc .channelState .RemoteChanCfg .MultiSigKey .PubKey .
7162- SerializeCompressed ()
7163- witness := input .SpendMultiSig (
7164- lc .signDesc .WitnessScript , ourKey , localSig , theirKey ,
7165- remoteSig ,
7202+
7203+ prevOutputFetcher := txscript .NewCannedPrevOutputFetcher (
7204+ prevOut .PkScript , prevOut .Value ,
71667205 )
7167- closeTx .TxIn [0 ].Witness = witness
7206+ hashCache := txscript .NewTxSigHashes (closeTx , prevOutputFetcher )
7207+
7208+ // Next, we'll complete the co-op close transaction. Depending on the
7209+ // set of options, we'll either do a regular p2wsh spend, or construct
7210+ // the final schnorr signature from a set of partial sigs.
7211+ if opts .musigSession != nil {
7212+ // For taproot channels, we'll use the attached session to
7213+ // combine the two partial signatures into a proper schnorr
7214+ // signature.
7215+ remotePartialSig , ok := remoteSig .(* MusigPartialSig )
7216+ if ! ok {
7217+ return nil , 0 , fmt .Errorf ("expected MusigPartialSig, " +
7218+ "got %T" , remoteSig )
7219+ }
7220+
7221+ finalSchnorrSig , err := opts .musigSession .CombineSigs (
7222+ remotePartialSig .sig ,
7223+ )
7224+ if err != nil {
7225+ return nil , 0 , fmt .Errorf ("unable to combine " +
7226+ "final co-op close sig: %w" , err )
7227+ }
7228+
7229+ // The witness for a keyspend is just the signature itself.
7230+ closeTx .TxIn [0 ].Witness = wire.TxWitness {
7231+ finalSchnorrSig .Serialize (),
7232+ }
7233+ } else {
7234+
7235+ // For regular channels, we'll need to , construct the witness
7236+ // stack minding the order of the pubkeys+sigs on the stack.
7237+ ourKey := lc .channelState .LocalChanCfg .MultiSigKey .PubKey .
7238+ SerializeCompressed ()
7239+ theirKey := lc .channelState .RemoteChanCfg .MultiSigKey .PubKey .
7240+ SerializeCompressed ()
7241+ witness := input .SpendMultiSig (
7242+ lc .signDesc .WitnessScript , ourKey , localSig , theirKey ,
7243+ remoteSig ,
7244+ )
7245+ closeTx .TxIn [0 ].Witness = witness
7246+
7247+ }
71687248
71697249 // Validate the finalized transaction to ensure the output script is
71707250 // properly met, and that the remote peer supplied a valid signature.
7171- prevOut := lc .signDesc .Output
71727251 vm , err := txscript .NewEngine (
71737252 prevOut .PkScript , closeTx , 0 , txscript .StandardVerifyFlags , nil ,
7174- hashCache , prevOut .Value , txscript .NewCannedPrevOutputFetcher (
7175- prevOut .PkScript , prevOut .Value ,
7176- ),
7253+ hashCache , prevOut .Value , prevOutputFetcher ,
71777254 )
71787255 if err != nil {
71797256 return nil , 0 , err
@@ -7833,11 +7910,23 @@ func (lc *LightningChannel) IdealCommitFeeRate(netFeeRate, minRelayFeeRate,
78337910 return absoluteMaxFee
78347911}
78357912
7836- // RemoteNextRevocation returns the channelState's RemoteNextRevocation.
7913+ // RemoteNextRevocation returns the channelState's RemoteNextRevocation. For
7914+ // musig2 channels, until a nonce pair is processed by the remote party, a nil
7915+ // public key is returned.
7916+ //
7917+ // TODO(roasbeef): revisit, maybe just make a more general method instead?
78377918func (lc * LightningChannel ) RemoteNextRevocation () * btcec.PublicKey {
78387919 lc .RLock ()
78397920 defer lc .RUnlock ()
78407921
7922+ if ! lc .channelState .ChanType .IsTaproot () {
7923+ return lc .channelState .RemoteNextRevocation
7924+ }
7925+
7926+ if lc .musigSessions == nil {
7927+ return nil
7928+ }
7929+
78417930 return lc .channelState .RemoteNextRevocation
78427931}
78437932
0 commit comments