44	"bytes" 
55	"context" 
66	"fmt" 
7+ 	"net/url" 
78	"sort" 
89
910	"github.com/btcsuite/btcd/btcec/v2/schnorr" 
@@ -28,6 +29,7 @@ import (
2829	"github.com/lightningnetwork/lnd/lnwallet" 
2930	"github.com/lightningnetwork/lnd/lnwallet/chainfee" 
3031	"github.com/lightningnetwork/lnd/lnwire" 
32+ 	"golang.org/x/exp/maps" 
3133)
3234
3335// DecodedDescriptor is a wrapper around a PaymentDescriptor that also includes 
@@ -1505,6 +1507,132 @@ func deriveFundingScriptKey(ctx context.Context, addrBook address.Storage,
15051507	return  fundingScriptKey , nil 
15061508}
15071509
1510+ // CommitmentToPackets converts a commitment to a list of vPackets. The 
1511+ // commitment must not contain any HTLCs, as this only works for coop-closed 
1512+ // channels. 
1513+ func  CommitmentToPackets (c  * cmsg.Commitment , inputs  []* proof.Proof ,
1514+ 	chainParams  * address.ChainParams , localShutdownMsg ,
1515+ 	remoteShutdownMsg  cmsg.AuxShutdownMsg , localOutputIndex ,
1516+ 	remoteOutputIndex  uint32 ,
1517+ 	vPktVersion  tappsbt.VPacketVersion ) ([]* tappsbt.VPacket , error ) {
1518+ 
1519+ 	if  len (c .IncomingHtlcAssets .Val .HtlcOutputs ) >  0  || 
1520+ 		len (c .OutgoingHtlcAssets .Val .HtlcOutputs ) >  0  {
1521+ 
1522+ 		return  nil , fmt .Errorf ("commitment contains HTLCs, cannot "  + 
1523+ 			"create vPackets" )
1524+ 	}
1525+ 
1526+ 	// We group the inputs by asset ID, so we can create a vPacket for each 
1527+ 	// asset ID. The number of vPackets is dictated by the number of 
1528+ 	// different asset IDs in the commitment transaction. 
1529+ 	groupedInputs  :=  tapsend .GroupProofsByAssetID (inputs )
1530+ 	vPackets  :=  make (map [asset.ID ]* tappsbt.VPacket , len (groupedInputs ))
1531+ 	for  assetID , proofsByID  :=  range  groupedInputs  {
1532+ 		pkt , err  :=  tappsbt .FromProofs (
1533+ 			proofsByID , chainParams , vPktVersion ,
1534+ 		)
1535+ 		if  err  !=  nil  {
1536+ 			return  nil , fmt .Errorf ("error creating vPacket: %w" ,
1537+ 				err )
1538+ 		}
1539+ 
1540+ 		vPackets [assetID ] =  pkt 
1541+ 	}
1542+ 
1543+ 	localOutputs  :=  c .LocalOutputs ()
1544+ 	remoteOutputs  :=  c .RemoteOutputs ()
1545+ 
1546+ 	// We now distribute the outputs to the vPackets. 
1547+ 	for  _ , output  :=  range  localOutputs  {
1548+ 		pkt , ok  :=  vPackets [output .AssetID .Val ]
1549+ 		if  ! ok  {
1550+ 			return  nil , fmt .Errorf ("no vPacket found " + 
1551+ 				"for asset ID %s" , output .AssetID .Val )
1552+ 		}
1553+ 
1554+ 		outAsset  :=  output .Proof .Val .Asset 
1555+ 		vOut , err  :=  assetToInteractiveVOutput (
1556+ 			outAsset , asset .V0 , localShutdownMsg ,
1557+ 			localOutputIndex ,
1558+ 		)
1559+ 		if  err  !=  nil  {
1560+ 			return  nil , fmt .Errorf ("error creating " + 
1561+ 				"vOutput: %w" , err )
1562+ 		}
1563+ 
1564+ 		pkt .Outputs  =  append (pkt .Outputs , vOut )
1565+ 	}
1566+ 
1567+ 	for  _ , output  :=  range  remoteOutputs  {
1568+ 		pkt , ok  :=  vPackets [output .AssetID .Val ]
1569+ 		if  ! ok  {
1570+ 			return  nil , fmt .Errorf ("no vPacket found " + 
1571+ 				"for asset ID %s" , output .AssetID .Val )
1572+ 		}
1573+ 
1574+ 		outAsset  :=  output .Proof .Val .Asset 
1575+ 		vOut , err  :=  assetToInteractiveVOutput (
1576+ 			outAsset , asset .V0 , remoteShutdownMsg ,
1577+ 			remoteOutputIndex ,
1578+ 		)
1579+ 		if  err  !=  nil  {
1580+ 			return  nil , fmt .Errorf ("error creating " + 
1581+ 				"vOutput: %w" , err )
1582+ 		}
1583+ 
1584+ 		pkt .Outputs  =  append (pkt .Outputs , vOut )
1585+ 	}
1586+ 
1587+ 	return  maps .Values (vPackets ), nil 
1588+ }
1589+ 
1590+ // assetToInteractiveVOutput creates a VOutput for an asset that is part of an 
1591+ // interactive transaction. 
1592+ func  assetToInteractiveVOutput (a  asset.Asset , version  asset.Version ,
1593+ 	shutdownMsg  cmsg.AuxShutdownMsg ,
1594+ 	anchorOutputIndex  uint32 ) (* tappsbt.VOutput , error ) {
1595+ 
1596+ 	scriptKey , ok  :=  shutdownMsg .ScriptKeys .Val [a .ID ()]
1597+ 	if  ! ok  {
1598+ 		return  nil , fmt .Errorf ("no script key for asset %s" , a .ID ())
1599+ 	}
1600+ 
1601+ 	proofDeliveryUrl , err  :=  lfn .MapOptionZ (
1602+ 		shutdownMsg .ProofDeliveryAddr .ValOpt (),
1603+ 		func (u  []byte ) lfn.Result [* url.URL ] {
1604+ 			proofDeliveryUrl , err  :=  url .Parse (string (u ))
1605+ 			if  err  !=  nil  {
1606+ 				return  lfn.Err [* url.URL ](fmt .Errorf ("unable " + 
1607+ 					"to parse proof delivery address: %w" ,
1608+ 					err ))
1609+ 			}
1610+ 
1611+ 			return  lfn .Ok (proofDeliveryUrl )
1612+ 		},
1613+ 	).Unpack ()
1614+ 	if  err  !=  nil  {
1615+ 		return  nil , fmt .Errorf ("unable to decode proof delivery " + 
1616+ 			"address: %w" , err )
1617+ 	}
1618+ 
1619+ 	outType  :=  tappsbt .TypeSplitRoot 
1620+ 	if  a .SplitCommitmentRoot  ==  nil  {
1621+ 		outType  =  tappsbt .TypeSimple 
1622+ 	}
1623+ 
1624+ 	return  & tappsbt.VOutput {
1625+ 		Amount :                  a .Amount ,
1626+ 		AssetVersion :            version ,
1627+ 		Type :                    outType ,
1628+ 		Interactive :             true ,
1629+ 		AnchorOutputIndex :       anchorOutputIndex ,
1630+ 		AnchorOutputInternalKey : shutdownMsg .AssetInternalKey .Val ,
1631+ 		ScriptKey :               asset .NewScriptKey (& scriptKey ),
1632+ 		ProofDeliveryAddress :    proofDeliveryUrl ,
1633+ 	}, nil 
1634+ }
1635+ 
15081636// InPlaceCustomCommitSort performs an in-place sort of a transaction, given a 
15091637// list of allocations. The sort is applied to the transaction outputs, using 
15101638// the allocation's OutputIndex. The transaction inputs are sorted by the 
0 commit comments