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,131 @@ 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+ if len (localOutputs ) > 0 {
1548+ for _ , output := range localOutputs {
1549+ pkt , ok := vPackets [output .AssetID .Val ]
1550+ if ! ok {
1551+ return nil , fmt .Errorf ("no vPacket found " +
1552+ "for asset ID %s" , output .AssetID .Val )
1553+ }
1554+
1555+ outAsset := output .Proof .Val .Asset
1556+ vOut , err := assetToInteractiveVOutput (
1557+ outAsset , asset .V0 , localShutdownMsg ,
1558+ localOutputIndex ,
1559+ )
1560+ if err != nil {
1561+ return nil , fmt .Errorf ("error creating " +
1562+ "vOutput: %w" , err )
1563+ }
1564+
1565+ pkt .Outputs = append (pkt .Outputs , vOut )
1566+ }
1567+ }
1568+
1569+ if len (remoteOutputs ) > 0 {
1570+ for _ , output := range remoteOutputs {
1571+ pkt , ok := vPackets [output .AssetID .Val ]
1572+ if ! ok {
1573+ return nil , fmt .Errorf ("no vPacket found " +
1574+ "for asset ID %s" , output .AssetID .Val )
1575+ }
1576+
1577+ outAsset := output .Proof .Val .Asset
1578+ vOut , err := assetToInteractiveVOutput (
1579+ outAsset , asset .V0 , remoteShutdownMsg ,
1580+ remoteOutputIndex ,
1581+ )
1582+ if err != nil {
1583+ return nil , fmt .Errorf ("error creating " +
1584+ "vOutput: %w" , err )
1585+ }
1586+
1587+ pkt .Outputs = append (pkt .Outputs , vOut )
1588+ }
1589+ }
1590+
1591+ return maps .Values (vPackets ), nil
1592+ }
1593+
1594+ // assetToInteractiveVOutput creates a VOutput for an asset that is part of an
1595+ // interactive transaction.
1596+ func assetToInteractiveVOutput (a asset.Asset , version asset.Version ,
1597+ shutdownMsg cmsg.AuxShutdownMsg ,
1598+ anchorOutputIndex uint32 ) (* tappsbt.VOutput , error ) {
1599+
1600+ scriptKey , ok := shutdownMsg .ScriptKeys .Val [a .ID ()]
1601+ if ! ok {
1602+ return nil , fmt .Errorf ("no script key for asset %s" , a .ID ())
1603+ }
1604+
1605+ var proofDeliveryUrl * url.URL
1606+ err := lfn .MapOptionZ (
1607+ shutdownMsg .ProofDeliveryAddr .ValOpt (), func (u []byte ) error {
1608+ var err error
1609+ proofDeliveryUrl , err = url .Parse (string (u ))
1610+ return err
1611+ },
1612+ )
1613+ if err != nil {
1614+ return nil , fmt .Errorf ("unable to decode proof delivery " +
1615+ "address: %w" , err )
1616+ }
1617+
1618+ return & tappsbt.VOutput {
1619+ Amount : a .Amount ,
1620+ AssetVersion : version ,
1621+ Type : func () tappsbt.VOutputType {
1622+ if a .SplitCommitmentRoot == nil {
1623+ return tappsbt .TypeSimple
1624+ }
1625+ return tappsbt .TypeSplitRoot
1626+ }(),
1627+ Interactive : true ,
1628+ AnchorOutputIndex : anchorOutputIndex ,
1629+ AnchorOutputInternalKey : shutdownMsg .AssetInternalKey .Val ,
1630+ ScriptKey : asset .NewScriptKey (& scriptKey ),
1631+ ProofDeliveryAddress : proofDeliveryUrl ,
1632+ }, nil
1633+ }
1634+
15081635// InPlaceCustomCommitSort performs an in-place sort of a transaction, given a
15091636// list of allocations. The sort is applied to the transaction outputs, using
15101637// the allocation's OutputIndex. The transaction inputs are sorted by the
0 commit comments