diff --git a/rfq/manager_test.go b/rfq/manager_test.go index 0df3a7d2e0..f6b95b0663 100644 --- a/rfq/manager_test.go +++ b/rfq/manager_test.go @@ -111,6 +111,7 @@ func createChannelWithCustomData(t *testing.T, id asset.ID, localBalance, ), }, nil, nil, lnwallet.CommitAuxLeaves{}, + false, ), OpenChan: *tpchmsg.NewOpenChannel( []*tpchmsg.AssetOutput{ diff --git a/server.go b/server.go index 1864bfc731..6d8a9fdb6a 100644 --- a/server.go +++ b/server.go @@ -833,7 +833,9 @@ func (s *Server) FetchLeavesFromView( // The aux leaf creator is fully stateless, and we don't need to wait // for the server to be started before being able to use it. - return tapchannel.FetchLeavesFromView(s.chainParams, in) + return tapchannel.FetchLeavesFromView( + s.chainParams, in, s.cfg.AuxChanNegotiator, + ) } // FetchLeavesFromCommit attempts to fetch the auxiliary leaves that @@ -889,7 +891,9 @@ func (s *Server) ApplyHtlcView( // The aux leaf creator is fully stateless, and we don't need to wait // for the server to be started before being able to use it. - return tapchannel.ApplyHtlcView(s.chainParams, in) + return tapchannel.ApplyHtlcView( + s.chainParams, in, s.cfg.AuxChanNegotiator, + ) } // InlineParseCustomData replaces any custom data binary blob in the given RPC diff --git a/tapcfg/server.go b/tapcfg/server.go index cf88e8b806..19da8d3774 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -588,6 +588,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, AssetSyncer: addrBook, FeatureBits: lndFeatureBitsVerifier, IgnoreChecker: ignoreCheckerOpt, + AuxChanNegotiator: auxChanNegotiator, ErrChan: mainErrChan, }, ) @@ -619,6 +620,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, GroupVerifier: groupVerifier, ChainBridge: chainBridge, IgnoreChecker: ignoreCheckerOpt, + AuxChanNegotiator: auxChanNegotiator, }, ) auxSweeper := tapchannel.NewAuxSweeper( diff --git a/tapchannel/auf_leaf_signer_test.go b/tapchannel/auf_leaf_signer_test.go index 08cfb99263..05786a890c 100644 --- a/tapchannel/auf_leaf_signer_test.go +++ b/tapchannel/auf_leaf_signer_test.go @@ -111,7 +111,7 @@ func setupAuxLeafSigner(t *testing.T, numJobs int32) (*AuxLeafSigner, } com := cmsg.NewCommitment( - nil, nil, outgoingHtlcs, nil, lnwallet.CommitAuxLeaves{}, + nil, nil, outgoingHtlcs, nil, lnwallet.CommitAuxLeaves{}, false, ) cancelChan := make(chan struct{}) diff --git a/tapchannel/aux_closer.go b/tapchannel/aux_closer.go index 451c21d698..cadaca4cb9 100644 --- a/tapchannel/aux_closer.go +++ b/tapchannel/aux_closer.go @@ -15,6 +15,7 @@ import ( "github.com/lightninglabs/taproot-assets/fn" "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/tapchannelmsg" + "github.com/lightninglabs/taproot-assets/tapfeatures" "github.com/lightninglabs/taproot-assets/tapfreighter" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/tappsbt" @@ -66,6 +67,11 @@ type AuxChanCloserCfg struct { // IgnoreChecker is an optional function that can be used to check if // a proof should be ignored. IgnoreChecker lfn.Option[proof.IgnoreChecker] + + // AuxChanNegotiator is responsible for producing the extra tlv blob + // that is encapsulated in the init and reestablish peer messages. This + // helps us communicate custom feature bits with our peer. + AuxChanNegotiator *tapfeatures.AuxChannelNegotiator } // assetCloseInfo houses the information we need to finalize the close of an @@ -454,11 +460,21 @@ func (a *AuxChanCloser) AuxCloseOutputs( "packets: %w", err) } + features := a.cfg.AuxChanNegotiator.GetChannelFeatures( + lnwire.NewChanIDFromOutPoint(desc.ChanPoint), + ) + supportSTXO := features.HasFeature(tapfeatures.STXOOptional) + + var opts []tapsend.OutputCommitmentOption + if !supportSTXO { + opts = append(opts, tapsend.WithNoSTXOProofs()) + } + // With the outputs prepared, we can now create the set of output // commitments, then with the output index locations known, we can set // the output indexes in the allocations. outCommitments, err := tapsend.CreateOutputCommitments( - vPackets, tapsend.WithNoSTXOProofs(), + vPackets, opts..., ) if err != nil { return none, fmt.Errorf("unable to create output "+ @@ -733,10 +749,22 @@ func (a *AuxChanCloser) FinalizeClose(desc chancloser.AuxCloseDesc, closeInfo.allocations, ) + features := a.cfg.AuxChanNegotiator.GetChannelFeatures( + lnwire.NewChanIDFromOutPoint(desc.ChanPoint), + ) + supportSTXO := features.HasFeature( + tapfeatures.STXOOptional, + ) + + var opts []proof.GenOption + if !supportSTXO { + opts = append(opts, proof.WithNoSTXOProofs()) + } + proofSuffix, err := tapsend.CreateProofSuffixCustom( closeTx, vPkt, closeInfo.outputCommitments, outIdx, closeInfo.vPackets, exclusionCreator, - proof.WithNoSTXOProofs(), + opts..., ) if err != nil { return fmt.Errorf("unable to create proof "+ diff --git a/tapchannel/aux_funding_controller.go b/tapchannel/aux_funding_controller.go index c456e917ec..ed49ff3ce2 100644 --- a/tapchannel/aux_funding_controller.go +++ b/tapchannel/aux_funding_controller.go @@ -28,6 +28,7 @@ import ( "github.com/lightninglabs/taproot-assets/rfq" cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg" "github.com/lightninglabs/taproot-assets/tapdb" + "github.com/lightninglabs/taproot-assets/tapfeatures" "github.com/lightninglabs/taproot-assets/tapfreighter" "github.com/lightninglabs/taproot-assets/tapgarden" "github.com/lightninglabs/taproot-assets/tappsbt" @@ -41,6 +42,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/msgmux" + "github.com/lightningnetwork/lnd/routing/route" ) const ( @@ -269,6 +271,11 @@ type FundingControllerCfg struct { // a proof should be ignored. IgnoreChecker lfn.Option[proof.IgnoreChecker] + // AuxChanNegotiator is responsible for producing the extra tlv blob + // that is encapsulated in the init and reestablish peer messages. This + // helps us communicate custom feature bits with our peer. + AuxChanNegotiator *tapfeatures.AuxChannelNegotiator + // ErrChan is used to report errors back to the main server. ErrChan chan<- error } @@ -419,6 +426,8 @@ type pendingAssetFunding struct { initiator bool + stxo bool + amt uint64 pushAmt btcutil.Amount @@ -462,7 +471,9 @@ func (p *pendingAssetFunding) assetOutputs() []*cmsg.AssetOutput { } // addToFundingCommitment adds a new asset to the funding commitment. -func (p *pendingAssetFunding) addToFundingCommitment(a *asset.Asset) error { +func (p *pendingAssetFunding) addToFundingCommitment(a *asset.Asset, + stxo bool) error { + newCommitment, err := commitment.FromAssets( fn.Ptr(commitment.TapCommitmentV2), a, ) @@ -470,6 +481,22 @@ func (p *pendingAssetFunding) addToFundingCommitment(a *asset.Asset) error { return fmt.Errorf("unable to create commitment: %w", err) } + // If our peer supports STXO we go ahead and append the + // appropriate alt leaves to the VOutput. + if stxo { + altLeaves, err := asset.CollectSTXO(a) + if err != nil { + return err + } + + err = newCommitment.MergeAltLeaves(altLeaves) + if err != nil { + return err + } + + p.stxo = stxo + } + newCommitment, err = commitment.TrimSplitWitnesses( &newCommitment.Version, newCommitment, ) @@ -524,7 +551,8 @@ func (p *pendingAssetFunding) addInputProofChunk( func newCommitBlobAndLeaves(pendingFunding *pendingAssetFunding, lndOpenChan lnwallet.AuxChanState, assetOpenChan *cmsg.OpenChannel, keyRing lntypes.Dual[lnwallet.CommitmentKeyRing], - whoseCommit lntypes.ChannelParty) ([]byte, lnwallet.CommitAuxLeaves, + whoseCommit lntypes.ChannelParty, + stxo bool) ([]byte, lnwallet.CommitAuxLeaves, error) { chanAssets := assetOpenChan.FundedAssets.Val.Outputs @@ -559,6 +587,7 @@ func newCommitBlobAndLeaves(pendingFunding *pendingAssetFunding, // needs the sum of the remote+local assets, so we'll populate that. fakePrevState := cmsg.NewCommitment( localAssets, remoteAssets, nil, nil, lnwallet.CommitAuxLeaves{}, + stxo, ) // Just like above, we don't have a real HTLC view here, so we'll pass @@ -571,6 +600,7 @@ func newCommitBlobAndLeaves(pendingFunding *pendingAssetFunding, fakePrevState, lndOpenChan, assetOpenChan, whoseCommit, localSatBalance, remoteSatBalance, fakeView, pendingFunding.chainParams, keyRing.GetForParty(whoseCommit), + stxo, ) if err != nil { return nil, lnwallet.CommitAuxLeaves{}, err @@ -613,12 +643,14 @@ func (p *pendingAssetFunding) toAuxFundingDesc(req *bindFundingReq, // This will be the information for the very first state (state 0). localCommitBlob, localAuxLeaves, err := newCommitBlobAndLeaves( p, req.openChan, openChanDesc, req.keyRing, lntypes.Local, + p.stxo, ) if err != nil { return nil, err } remoteCommitBlob, remoteAuxLeaves, err := newCommitBlobAndLeaves( p, req.openChan, openChanDesc, req.keyRing, lntypes.Remote, + p.stxo, ) if err != nil { return nil, err @@ -1086,14 +1118,19 @@ func (f *FundingController) signAllVPackets(ctx context.Context, // complete, but unsigned PSBT packet that can be used to create out asset // channel. func (f *FundingController) anchorVPackets(fundedPkt *tapsend.FundedPsbt, - allPackets []*tappsbt.VPacket) ([]*proof.Proof, error) { + allPackets []*tappsbt.VPacket, stxo bool) ([]*proof.Proof, error) { log.Infof("Anchoring funding vPackets to funding PSBT") + var opts []tapsend.OutputCommitmentOption + if !stxo { + opts = append(opts, tapsend.WithNoSTXOProofs()) + } + // Given the set of vPackets we've created, we'll now merge them all to // create a map from output index to final tap commitment. outputCommitments, err := tapsend.CreateOutputCommitments( - allPackets, tapsend.WithNoSTXOProofs(), + allPackets, opts..., ) if err != nil { return nil, fmt.Errorf("unable to create new output "+ @@ -1122,11 +1159,16 @@ func (f *FundingController) anchorVPackets(fundedPkt *tapsend.FundedPsbt, for idx := range allPackets { vPkt := allPackets[idx] + var opts []proof.GenOption + if !stxo { + opts = append(opts, proof.WithNoSTXOProofs()) + } + for vOutIdx := range vPkt.Outputs { proofSuffix, err := tapsend.CreateProofSuffix( fundedPkt.Pkt.UnsignedTx, fundedPkt.Pkt.Outputs, vPkt, outputCommitments, vOutIdx, allPackets, - proof.WithNoSTXOProofs(), + opts..., ) if err != nil { return nil, fmt.Errorf("unable to create "+ @@ -1219,7 +1261,8 @@ func (f *FundingController) sendAssetFundingCreated(ctx context.Context, // ultimately broadcasting the funding transaction. func (f *FundingController) completeChannelFunding(ctx context.Context, fundingState *pendingAssetFunding, - fundedVpkt *tapfreighter.FundedVPacket) (*wire.OutPoint, error) { + fundedVpkt *tapfreighter.FundedVPacket, + stxoEnabled bool) (*wire.OutPoint, error) { log.Debugf("Finalizing funding vPackets and PSBT...") @@ -1330,7 +1373,7 @@ func (f *FundingController) completeChannelFunding(ctx context.Context, // PSBT. This'll update all the pkScripts for our funding output and // change. fundingOutputProofs, err := f.anchorVPackets( - finalFundedPsbt, signedPkts, + finalFundedPsbt, signedPkts, stxoEnabled, ) if err != nil { return nil, fmt.Errorf("unable to anchor vPackets: %w", err) @@ -1545,11 +1588,17 @@ func (f *FundingController) processFundingMsg(ctx context.Context, "proof: %w", err) } + features := f.cfg.AuxChanNegotiator.GetPeerFeatures( + route.Vertex(msg.PeerPub.SerializeCompressed()), + ) + + supportSTXO := features.HasFeature(tapfeatures.STXOOptional) + // If we reached this point, then the asset output and all // inputs are valid, so we'll store the funding asset // commitment. err = assetFunding.addToFundingCommitment( - &assetProof.AssetOutput.Val, + &assetProof.AssetOutput.Val, supportSTXO, ) if err != nil { return tempPID, fmt.Errorf("unable to create "+ @@ -1738,6 +1787,15 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex, maxNumAssetIDs) } + // Now let's see if we should be using STXOs for this channel funding. + features := f.cfg.AuxChanNegotiator.GetPeerFeatures( + route.Vertex(fundReq.PeerPub.SerializeCompressed()), + ) + + supportSTXO := features.HasFeature(tapfeatures.STXOOptional) + + fundingState.stxo = supportSTXO + // Now that we know the final funding asset root along with the splits, // we can derive the tapscript root that'll be used alongside the // internal key (which we'll only learn from lnd later as we finalize @@ -1750,7 +1808,7 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex, } err = fundingState.addToFundingCommitment( - fundingOut.Asset.Copy(), + fundingOut.Asset.Copy(), supportSTXO, ) if err != nil { return fmt.Errorf("unable to add asset to funding "+ @@ -1814,7 +1872,7 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex, } chanPoint, err := f.completeChannelFunding( - fundReq.ctx, fundingState, fundingVpkt, + fundReq.ctx, fundingState, fundingVpkt, supportSTXO, ) if err != nil { // If anything went wrong during the funding process, diff --git a/tapchannel/aux_leaf_creator.go b/tapchannel/aux_leaf_creator.go index 75796115d1..bbf0737ea1 100644 --- a/tapchannel/aux_leaf_creator.go +++ b/tapchannel/aux_leaf_creator.go @@ -10,11 +10,13 @@ import ( "github.com/lightninglabs/taproot-assets/address" "github.com/lightninglabs/taproot-assets/fn" cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg" + "github.com/lightninglabs/taproot-assets/tapfeatures" "github.com/lightningnetwork/lnd/channeldb" lfn "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" lnwl "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" ) @@ -24,10 +26,19 @@ const ( DefaultTimeout = 30 * time.Second ) +// FeatureBitFetcher is responsible for fetching feature bits by referencing a +// channel ID. +type FeatureBitFetcher interface { + // GetChannelFeatures returns the negotiated features that are active + // over the channel identifier by the provided channelID. + GetChannelFeatures(cid lnwire.ChannelID) lnwire.FeatureVector +} + // FetchLeavesFromView attempts to fetch the auxiliary leaves that correspond to // the passed aux blob, and pending fully evaluated HTLC view. func FetchLeavesFromView(chainParams *address.ChainParams, - in lnwl.CommitDiffAuxInput) lfn.Result[lnwl.CommitDiffAuxResult] { + in lnwl.CommitDiffAuxInput, + bitFetcher FeatureBitFetcher) lfn.Result[lnwl.CommitDiffAuxResult] { type returnType = lnwl.CommitDiffAuxResult @@ -50,10 +61,18 @@ func FetchLeavesFromView(chainParams *address.ChainParams, "commit state: %w", err)) } + features := bitFetcher.GetChannelFeatures( + lnwire.NewChanIDFromOutPoint( + in.ChannelState.FundingOutpoint, + ), + ) + + supportsSTXO := features.HasFeature(tapfeatures.STXOOptional) + allocations, newCommitment, err := GenerateCommitmentAllocations( prevState, in.ChannelState, chanAssetState, in.WhoseCommit, in.OurBalance, in.TheirBalance, in.UnfilteredView, chainParams, - in.KeyRing, + in.KeyRing, supportsSTXO, ) if err != nil { return lfn.Err[returnType](fmt.Errorf("unable to generate "+ @@ -98,6 +117,8 @@ func FetchLeavesFromCommit(chainParams *address.ChainParams, "commitment: %w", err)) } + supportSTXO := commitment.STXO.Val + incomingHtlcs := commitment.IncomingHtlcAssets.Val.HtlcOutputs incomingHtlcLeaves := commitment.AuxLeaves.Val.IncomingHtlcLeaves. Val.HtlcAuxLeaves @@ -129,7 +150,7 @@ func FetchLeavesFromCommit(chainParams *address.ChainParams, leaf, err := CreateSecondLevelHtlcTx( chanState, com.CommitTx, htlc.Amt.ToSatoshis(), keys, chainParams, htlcOutputs, cltvTimeout, - htlc.HtlcIndex, + htlc.HtlcIndex, supportSTXO, ) if err != nil { return lfn.Err[returnType](fmt.Errorf("unable "+ @@ -170,7 +191,7 @@ func FetchLeavesFromCommit(chainParams *address.ChainParams, leaf, err := CreateSecondLevelHtlcTx( chanState, com.CommitTx, htlc.Amt.ToSatoshis(), keys, chainParams, htlcOutputs, cltvTimeout, - htlc.HtlcIndex, + htlc.HtlcIndex, supportSTXO, ) if err != nil { return lfn.Err[returnType](fmt.Errorf("unable "+ @@ -225,7 +246,8 @@ func FetchLeavesFromRevocation( // channel's blob. Given the old blob, and an HTLC view, then a new // blob should be returned that reflects the pending updates. func ApplyHtlcView(chainParams *address.ChainParams, - in lnwl.CommitDiffAuxInput) lfn.Result[lfn.Option[tlv.Blob]] { + in lnwl.CommitDiffAuxInput, + bitFetcher FeatureBitFetcher) lfn.Result[lfn.Option[tlv.Blob]] { type returnType = lfn.Option[tlv.Blob] @@ -248,10 +270,20 @@ func ApplyHtlcView(chainParams *address.ChainParams, "commit state: %w", err)) } + features := bitFetcher.GetChannelFeatures( + lnwire.NewChanIDFromOutPoint( + in.ChannelState.FundingOutpoint, + ), + ) + + supportSTXO := features.HasFeature( + tapfeatures.STXOOptional, + ) + _, newCommitment, err := GenerateCommitmentAllocations( prevState, in.ChannelState, chanAssetState, in.WhoseCommit, in.OurBalance, in.TheirBalance, in.UnfilteredView, chainParams, - in.KeyRing, + in.KeyRing, supportSTXO, ) if err != nil { return lfn.Err[returnType](fmt.Errorf("unable to generate "+ diff --git a/tapchannel/aux_sweeper.go b/tapchannel/aux_sweeper.go index 1d741ec01f..059ea88f1d 100644 --- a/tapchannel/aux_sweeper.go +++ b/tapchannel/aux_sweeper.go @@ -1585,6 +1585,8 @@ func (a *AuxSweeper) importCommitTx(req lnwallet.ResolutionReq, } } + supportSTXO := commitState.STXO.Val + // We can now add the witness for the OP_TRUE spend of the commitment // output to the vPackets. vPackets := maps.Values(vPktsByAssetID) @@ -1593,8 +1595,13 @@ func (a *AuxSweeper) importCommitTx(req lnwallet.ResolutionReq, "packets: %w", err) } + var opts []tapsend.OutputCommitmentOption + if !supportSTXO { + opts = append(opts, tapsend.WithNoSTXOProofs()) + } + outCommitments, err := tapsend.CreateOutputCommitments( - vPackets, tapsend.WithNoSTXOProofs(), + vPackets, opts..., ) if err != nil { return fmt.Errorf("unable to create output "+ @@ -1612,10 +1619,14 @@ func (a *AuxSweeper) importCommitTx(req lnwallet.ResolutionReq, for idx := range vPackets { vPkt := vPackets[idx] for outIdx := range vPkt.Outputs { + var opts []proof.GenOption + if !supportSTXO { + opts = append(opts, proof.WithNoSTXOProofs()) + } + proofSuffix, err := tapsend.CreateProofSuffixCustom( req.CommitTx, vPkt, outCommitments, outIdx, - vPackets, exclusionCreator, - proof.WithNoSTXOProofs(), + vPackets, exclusionCreator, opts..., ) if err != nil { return fmt.Errorf("unable to create "+ @@ -2225,9 +2236,7 @@ func (a *AuxSweeper) sweepContracts(inputs []input.Input, // Now that we have our set of resolutions, we'll make a new commitment // out of all the vPackets contained. - outCommitments, err := tapsend.CreateOutputCommitments( - directPkts, tapsend.WithNoSTXOProofs(), - ) + outCommitments, err := tapsend.CreateOutputCommitments(directPkts) if err != nil { return lfn.Errf[returnType]("unable to create "+ "output commitments: %w", err) @@ -2408,9 +2417,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest, } // Now that we have our vPkts, we'll re-create the output commitments. - outCommitments, err := tapsend.CreateOutputCommitments( - vPkts.allPkts(), tapsend.WithNoSTXOProofs(), - ) + outCommitments, err := tapsend.CreateOutputCommitments(vPkts.allPkts()) if err != nil { return fmt.Errorf("unable to create output "+ "commitments: %w", err) @@ -2454,7 +2461,7 @@ func (a *AuxSweeper) registerAndBroadcastSweep(req *sweep.BumpRequest, proofSuffix, err := tapsend.CreateProofSuffixCustom( sweepTx, vPkt, outCommitments, outIdx, allVpkts, - exclusionCreator, proof.WithNoSTXOProofs(), + exclusionCreator, ) if err != nil { return fmt.Errorf("unable to create proof "+ diff --git a/tapchannel/commitment.go b/tapchannel/commitment.go index d802bed092..ce458f3b2a 100644 --- a/tapchannel/commitment.go +++ b/tapchannel/commitment.go @@ -497,7 +497,7 @@ func GenerateCommitmentAllocations(prevState *cmsg.Commitment, whoseCommit lntypes.ChannelParty, ourBalance, theirBalance lnwire.MilliSatoshi, originalView lnwallet.AuxHtlcView, chainParams *address.ChainParams, - keys lnwallet.CommitmentKeyRing) ([]*tapsend.Allocation, + keys lnwallet.CommitmentKeyRing, stxo bool) ([]*tapsend.Allocation, *cmsg.Commitment, error) { log.Tracef("Generating allocations, whoseCommit=%v, ourBalance=%d, "+ @@ -589,8 +589,13 @@ func GenerateCommitmentAllocations(prevState *cmsg.Commitment, "packets: %w", err) } + var opts []tapsend.OutputCommitmentOption + if !stxo { + opts = append(opts, tapsend.WithNoSTXOProofs()) + } + outCommitments, err := tapsend.CreateOutputCommitments( - vPackets, tapsend.WithNoSTXOProofs(), + vPackets, opts..., ) if err != nil { return nil, nil, fmt.Errorf("unable to create output "+ @@ -622,11 +627,16 @@ func GenerateCommitmentAllocations(prevState *cmsg.Commitment, for idx := range vPackets { vPkt := vPackets[idx] for outIdx := range vPkt.Outputs { + var opts []proof.GenOption + if !stxo { + opts = append(opts, proof.WithNoSTXOProofs()) + } + proofSuffix, err := tapsend.CreateProofSuffixCustom( fakeCommitTx, vPkt, outCommitments, outIdx, vPackets, tapsend.NonAssetExclusionProofs( allocations, - ), proof.WithNoSTXOProofs(), + ), opts..., ) if err != nil { return nil, nil, fmt.Errorf("unable to create "+ @@ -641,7 +651,7 @@ func GenerateCommitmentAllocations(prevState *cmsg.Commitment, // Next, we can convert the allocations to auxiliary leaves and from // those construct our Commitment struct that will in the end also hold // our proof suffixes. - newCommitment, err := ToCommitment(allocations, vPackets) + newCommitment, err := ToCommitment(allocations, vPackets, stxo) if err != nil { return nil, nil, fmt.Errorf("unable to convert to commitment: "+ "%w", err) @@ -1145,7 +1155,7 @@ func LeavesFromTapscriptScriptTree( // ToCommitment converts the allocations to a Commitment struct. func ToCommitment(allocations []*tapsend.Allocation, - vPackets []*tappsbt.VPacket) (*cmsg.Commitment, error) { + vPackets []*tappsbt.VPacket, stxo bool) (*cmsg.Commitment, error) { var ( localAssets []*cmsg.AssetOutput @@ -1268,7 +1278,7 @@ func ToCommitment(allocations []*tapsend.Allocation, return cmsg.NewCommitment( localAssets, remoteAssets, outgoingHtlcs, incomingHtlcs, - auxLeaves, + auxLeaves, stxo, ), nil } @@ -1432,7 +1442,7 @@ func CreateSecondLevelHtlcTx(chanState lnwallet.AuxChanState, commitTx *wire.MsgTx, htlcAmt btcutil.Amount, keys lnwallet.CommitmentKeyRing, chainParams *address.ChainParams, htlcOutputs []*cmsg.AssetOutput, htlcTimeout fn.Option[uint32], - htlcIndex uint64) (input.AuxTapLeaf, error) { + htlcIndex uint64, stxo bool) (input.AuxTapLeaf, error) { none := input.NoneTapLeaf() @@ -1445,8 +1455,13 @@ func CreateSecondLevelHtlcTx(chanState lnwallet.AuxChanState, "packets: %w", err) } + var opts []tapsend.OutputCommitmentOption + if !stxo { + opts = append(opts, tapsend.WithNoSTXOProofs()) + } + outCommitments, err := tapsend.CreateOutputCommitments( - vPackets, tapsend.WithNoSTXOProofs(), + vPackets, opts..., ) if err != nil { return none, fmt.Errorf("unable to create output commitments: "+ diff --git a/tapchannelmsg/custom_channel_data_test.go b/tapchannelmsg/custom_channel_data_test.go index 8cbc4d39b5..7ae5a186d2 100644 --- a/tapchannelmsg/custom_channel_data_test.go +++ b/tapchannelmsg/custom_channel_data_test.go @@ -50,6 +50,7 @@ func TestReadChannelCustomData(t *testing.T) { }, map[input.HtlcIndex][]*AssetOutput{ 2: {output4}, }, lnwallet.CommitAuxLeaves{}, + false, ) fundingBlob := fundingState.Bytes() @@ -157,19 +158,19 @@ func TestReadBalanceCustomData(t *testing.T) { openChannel1 := NewCommitment( []*AssetOutput{output1}, []*AssetOutput{output2}, nil, nil, - lnwallet.CommitAuxLeaves{}, + lnwallet.CommitAuxLeaves{}, false, ) openChannel2 := NewCommitment( []*AssetOutput{output2}, []*AssetOutput{output3}, nil, nil, - lnwallet.CommitAuxLeaves{}, + lnwallet.CommitAuxLeaves{}, false, ) pendingChannel1 := NewCommitment( []*AssetOutput{output3}, nil, nil, nil, - lnwallet.CommitAuxLeaves{}, + lnwallet.CommitAuxLeaves{}, false, ) pendingChannel2 := NewCommitment( nil, []*AssetOutput{output1}, nil, nil, - lnwallet.CommitAuxLeaves{}, + lnwallet.CommitAuxLeaves{}, false, ) var customChannelData bytes.Buffer diff --git a/tapchannelmsg/records.go b/tapchannelmsg/records.go index 0bb06e9757..b93d027dec 100644 --- a/tapchannelmsg/records.go +++ b/tapchannelmsg/records.go @@ -453,13 +453,17 @@ type Commitment struct { // AuxLeaves are the auxiliary leaves that correspond to the commitment. AuxLeaves tlv.RecordT[tlv.TlvType4, AuxLeaves] + + // STXO is a flag indicating whether this commitment supports stxo + // proofs. + STXO tlv.RecordT[tlv.TlvType5, bool] } // NewCommitment creates a new Commitment record with the given local and remote // assets, and incoming and outgoing HTLCs. func NewCommitment(localAssets, remoteAssets []*AssetOutput, outgoingHtlcs, incomingHtlcs map[input.HtlcIndex][]*AssetOutput, - auxLeaves lnwallet.CommitAuxLeaves) *Commitment { + auxLeaves lnwallet.CommitAuxLeaves, stxo bool) *Commitment { return &Commitment{ LocalAssets: tlv.NewRecordT[tlv.TlvType0]( @@ -485,6 +489,7 @@ func NewCommitment(localAssets, remoteAssets []*AssetOutput, outgoingHtlcs, auxLeaves.IncomingHtlcLeaves, ), ), + STXO: tlv.NewPrimitiveRecord[tlv.TlvType5](stxo), } } @@ -496,6 +501,7 @@ func (c *Commitment) records() []tlv.Record { c.OutgoingHtlcAssets.Record(), c.IncomingHtlcAssets.Record(), c.AuxLeaves.Record(), + c.STXO.Record(), } } diff --git a/tapchannelmsg/records_test.go b/tapchannelmsg/records_test.go index 1f6222b608..8a1d2cf7e0 100644 --- a/tapchannelmsg/records_test.go +++ b/tapchannelmsg/records_test.go @@ -215,6 +215,7 @@ func TestCommitment(t *testing.T) { name: "commitment with empty HTLC maps", commitment: NewCommitment( nil, nil, nil, nil, lnwallet.CommitAuxLeaves{}, + false, ), }, { @@ -228,7 +229,21 @@ func TestCommitment(t *testing.T) { NewAssetOutput( [32]byte{1}, 1000, *randProof, ), - }, nil, nil, lnwallet.CommitAuxLeaves{}, + }, nil, nil, lnwallet.CommitAuxLeaves{}, false, + ), + }, + { + name: "commitment with balances and stxo", + commitment: NewCommitment( + []*AssetOutput{ + NewAssetOutput( + [32]byte{1}, 1000, *randProof, + ), + }, []*AssetOutput{ + NewAssetOutput( + [32]byte{1}, 1000, *randProof, + ), + }, nil, nil, lnwallet.CommitAuxLeaves{}, true, ), }, { @@ -319,6 +334,7 @@ func TestCommitment(t *testing.T) { }, }, }, + false, ), }, }