Skip to content

Commit e9dd241

Browse files
committed
tapchannel: add CommitmentToPackets function
This commit adds a new helper function that creates a set of virtual packets from an aux commitment state and a set of input proofs (as well as some negotiated values during the coop channel close flow). The purpose of this is to use the asset allocation that has already happened to distribute the assets of different asset ID pieces within the given channel commitment again for the virtual packets of a coop close transaction. The reason we want to use the already allocated outputs vs. re-allocating them again is to make sure that the distribution matches exactly that of the commitment. See explanation in next commit for more details.
1 parent 14e7bd4 commit e9dd241

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed

tapchannel/commitment.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
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

Comments
 (0)