Skip to content

Commit 1f42525

Browse files
committed
tapfreighter: burn by group key
Allow the creation of multiple vPackets when burning by group key.
1 parent 83a4116 commit 1f42525

File tree

2 files changed

+53
-59
lines changed

2 files changed

+53
-59
lines changed

tapfreighter/fund.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,6 @@ func createFundedPacketWithInputs(ctx context.Context, exporter proof.Exporter,
138138
"internal keys: %w", err)
139139
}
140140

141-
for _, vPkt := range allPackets {
142-
if err := tapsend.PrepareOutputAssets(ctx, vPkt); err != nil {
143-
log.Errorf("Error preparing output assets: %v, "+
144-
"packets: %v", err, limitSpewer.Sdump(vPkt))
145-
return nil, fmt.Errorf("unable to prepare outputs: %w",
146-
err)
147-
}
148-
}
149-
150141
// Extract just the TAP commitments by input from the selected anchored
151142
// commitments.
152143
inputCommitments := make(

tapfreighter/wallet.go

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"fmt"
77
"net/url"
8-
"slices"
98
"time"
109

1110
"github.com/btcsuite/btcd/blockchain"
@@ -684,6 +683,16 @@ func (f *AssetWallet) FundPacket(ctx context.Context,
684683
return nil, err
685684
}
686685

686+
for _, vPkt := range pkt.VPackets {
687+
if err := tapsend.PrepareOutputAssets(ctx, vPkt); err != nil {
688+
log.Errorf("Error preparing output assets: %v, "+
689+
"packets: %v", err, limitSpewer.Sdump(vPkt))
690+
691+
return nil, fmt.Errorf("unable to prepare outputs: %w",
692+
err)
693+
}
694+
}
695+
687696
success = true
688697
return pkt, nil
689698
}
@@ -693,12 +702,6 @@ func (f *AssetWallet) FundPacket(ctx context.Context,
693702
func (f *AssetWallet) FundBurn(ctx context.Context,
694703
fundDesc *tapsend.FundingDescriptor) (*FundedVPacket, error) {
695704

696-
// Extract the asset ID and group key from the funding descriptor.
697-
assetId, err := fundDesc.AssetSpecifier.UnwrapIdOrErr()
698-
if err != nil {
699-
return nil, err
700-
}
701-
702705
// We need to find a commitment that has enough assets to satisfy this
703706
// send request. We'll map the address to a set of constraints, so we
704707
// can use that to do Taproot asset coin selection.
@@ -733,11 +736,10 @@ func (f *AssetWallet) FundBurn(ctx context.Context,
733736
}
734737
}()
735738

736-
activeAssets := fn.Filter(
737-
selectedCommitments, func(c *AnchoredCommitment) bool {
738-
return c.Asset.ID() == assetId
739-
},
740-
)
739+
// Determine the highest asset version across all selected inputs. We'll
740+
// use this as a default for the output version; we'll refine per packet
741+
// later once inputs are assigned.
742+
activeAssets := selectedCommitments
741743

742744
maxVersion := asset.V0
743745
for _, activeAsset := range activeAssets {
@@ -746,52 +748,23 @@ func (f *AssetWallet) FundBurn(ctx context.Context,
746748
}
747749
}
748750

749-
// Now that we know what inputs we're going to spend, we know that by
750-
// definition, we use the first input's info as the burn's PrevID. But
751-
// to know which input will actually be assigned as the first input in
752-
// the allocated virtual packet, we first apply the same sorting that
753-
// the allocation code will also apply.
754-
slices.SortFunc(activeAssets, func(a, b *AnchoredCommitment) int {
755-
return tapsend.AssetSortForInputs(*a.Asset, *b.Asset)
756-
})
757-
firstInput := activeAssets[0]
758-
firstPrevID := asset.PrevID{
759-
OutPoint: firstInput.AnchorPoint,
760-
ID: firstInput.Asset.ID(),
761-
ScriptKey: asset.ToSerialized(
762-
firstInput.Asset.ScriptKey.PubKey,
763-
),
764-
}
765-
burnKey := asset.NewScriptKey(asset.DeriveBurnKey(firstPrevID))
766-
newInternalKey, err := f.cfg.KeyRing.DeriveNextKey(
767-
ctx, asset.TaprootAssetsKeyFamily,
768-
)
769-
if err != nil {
770-
return nil, err
771-
}
772-
773751
// We want both the burn output and the change to be in the same anchor
774752
// output, that's why we create the packet manually.
775753
vPkt := &tappsbt.VPacket{
776-
Inputs: []*tappsbt.VInput{{
777-
PrevID: asset.PrevID{
778-
ID: assetId,
779-
},
780-
}},
754+
// No inputs are set here; they'll be populated during funding.
781755
Outputs: []*tappsbt.VOutput{{
782756
Amount: fundDesc.Amount,
783757
Type: tappsbt.TypeSimple,
784758
Interactive: true,
785759
AnchorOutputIndex: 0,
786760
AssetVersion: maxVersion,
787-
ScriptKey: burnKey,
761+
// We'll set the burn script key per packet after
762+
// inputs are selected so we use a placeholder here.
763+
ScriptKey: asset.NUMSScriptKey,
788764
}},
789765
ChainParams: f.cfg.ChainParams,
790766
Version: tappsbt.V1,
791767
}
792-
vPkt.Outputs[0].SetAnchorInternalKey(
793-
newInternalKey, f.cfg.ChainParams.HDCoinType,
794-
)
795768

796769
// The virtual transaction is now ready to be further enriched with the
797770
// split commitment and other data.
@@ -803,11 +776,41 @@ func (f *AssetWallet) FundBurn(ctx context.Context,
803776
return nil, err
804777
}
805778

806-
// We don't support burning by group key yet, so we only expect a single
807-
// vPacket (which implies a single asset ID is involved).
808-
if len(fundedPkt.VPackets) != 1 {
809-
return nil, fmt.Errorf("expected a single vPacket, got %d",
810-
len(fundedPkt.VPackets))
779+
// Now that inputs are assigned, set the proper burn script key and
780+
// output versions per virtual packet, and build the outputs.
781+
for _, pkt := range fundedPkt.VPackets {
782+
if len(pkt.Inputs) == 0 {
783+
return nil, fmt.Errorf("no inputs in funded burn +" +
784+
"packet")
785+
}
786+
787+
// Determine the per-packet max asset version from its inputs.
788+
pktMaxVersion := asset.V0
789+
for _, in := range pkt.Inputs {
790+
if in.Asset().Version > pktMaxVersion {
791+
pktMaxVersion = in.Asset().Version
792+
}
793+
}
794+
795+
// The burn output is the interactive, non-split output.
796+
for _, out := range pkt.Outputs {
797+
if out.Type.IsSplitRoot() || !out.Interactive {
798+
continue
799+
}
800+
801+
// Compute burn key from the first input's PrevID.
802+
firstPrevID := pkt.Inputs[0].PrevID
803+
burnKey := asset.NewScriptKey(
804+
asset.DeriveBurnKey(firstPrevID),
805+
)
806+
out.ScriptKey = burnKey
807+
out.AssetVersion = pktMaxVersion
808+
}
809+
810+
if err := tapsend.PrepareOutputAssets(ctx, pkt); err != nil {
811+
return nil, fmt.Errorf("unable to prepare burn + "+
812+
"outputs: %w", err)
813+
}
811814
}
812815

813816
// Don't release the coins we've selected, as so far we've been

0 commit comments

Comments
 (0)