Skip to content

Commit cf67426

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 fbd5d00 commit cf67426

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed

tapchannel/commitment.go

Lines changed: 128 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,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

Comments
 (0)