Skip to content

Commit d416a6f

Browse files
committed
tapfreighter: create anchor output keys correctly
Once we have multiple asset IDs (and with that multiple distinct virtual packets), we need to make sure that we create our anchor outputs correctly. We need to make sure that the anchor outputs for the same index are actually the same (e.g. same internal key). If we just blindly loop over them and assign new keys, then two virtual outputs that reference the same anchor output index would have different keys and we'd fail a check then attempting to commit those packets to a BTC anchor transaction.
1 parent b47fe32 commit d416a6f

File tree

1 file changed

+135
-16
lines changed

1 file changed

+135
-16
lines changed

tapfreighter/fund.go

+135-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88

9+
"github.com/btcsuite/btcd/btcec/v2"
910
"github.com/btcsuite/btcd/btcutil/psbt"
1011
"github.com/btcsuite/btcd/txscript"
1112
"github.com/btcsuite/btcd/wire"
@@ -85,29 +86,19 @@ func createFundedPacketWithInputs(ctx context.Context, exporter proof.Exporter,
8586
// a new internal key for the anchor outputs. We assume any output that
8687
// hasn't got an internal key set is going to a local anchor, and we
8788
// provide the internal key for that.
88-
for idx := range vPkt.Outputs {
89-
vOut := vPkt.Outputs[idx]
90-
if vOut.AnchorOutputInternalKey != nil {
91-
continue
92-
}
93-
94-
newInternalKey, err := keyRing.DeriveNextKey(
95-
ctx, asset.TaprootAssetsKeyFamily,
96-
)
97-
if err != nil {
98-
return nil, err
99-
}
100-
vOut.SetAnchorInternalKey(
101-
newInternalKey, vPkt.ChainParams.HDCoinType,
102-
)
89+
packets := []*tappsbt.VPacket{vPkt}
90+
err = generateOutputAnchorInternalKeys(ctx, packets, keyRing)
91+
if err != nil {
92+
return nil, fmt.Errorf("unable to generate output anchor "+
93+
"internal keys: %w", err)
10394
}
10495

10596
if err := tapsend.PrepareOutputAssets(ctx, vPkt); err != nil {
10697
return nil, fmt.Errorf("unable to prepare outputs: %w", err)
10798
}
10899

109100
return &FundedVPacket{
110-
VPackets: []*tappsbt.VPacket{vPkt},
101+
VPackets: packets,
111102
InputCommitments: inputCommitments,
112103
}, nil
113104
}
@@ -238,6 +229,134 @@ func createChangeOutput(ctx context.Context, vPkt *tappsbt.VPacket,
238229
return nil
239230
}
240231

232+
// vOutAnchor is a helper struct that holds the anchor output information that
233+
// might be set on a virtual output.
234+
type vOutAnchor struct {
235+
internalKey *btcec.PublicKey
236+
derivation []*psbt.Bip32Derivation
237+
trDerivation []*psbt.TaprootBip32Derivation
238+
siblingPreimage *commitment.TapscriptPreimage
239+
}
240+
241+
// newVOutAnchor creates a new vOutAnchor from the given virtual output.
242+
func newVOutAnchor(vOut *tappsbt.VOutput) vOutAnchor {
243+
return vOutAnchor{
244+
internalKey: vOut.AnchorOutputInternalKey,
245+
derivation: vOut.AnchorOutputBip32Derivation,
246+
trDerivation: vOut.AnchorOutputTaprootBip32Derivation,
247+
siblingPreimage: vOut.AnchorOutputTapscriptSibling,
248+
}
249+
}
250+
251+
// applyFields applies the anchor output information from the given vOutAnchor
252+
// to the given virtual output.
253+
func (a vOutAnchor) applyFields(vOut *tappsbt.VOutput) {
254+
vOut.AnchorOutputInternalKey = a.internalKey
255+
vOut.AnchorOutputBip32Derivation = a.derivation
256+
vOut.AnchorOutputTaprootBip32Derivation = a.trDerivation
257+
vOut.AnchorOutputTapscriptSibling = a.siblingPreimage
258+
}
259+
260+
// generateOutputAnchorInternalKeys generates internal keys for the anchor
261+
// outputs of the given virtual packets. If an output already has an internal
262+
// key set, it will be used. If not, a new key will be derived and set.
263+
// At the same time we make sure that we don't use different keys for the same
264+
// anchor output index in case there are multiple packets.
265+
func generateOutputAnchorInternalKeys(ctx context.Context,
266+
packets []*tappsbt.VPacket, keyRing KeyRing) error {
267+
268+
// We need to make sure we don't use different keys for the same anchor
269+
// output index in case there are multiple packets. So we'll keep track
270+
// of any set keys here. This will be a merged set of existing and new
271+
// keys.
272+
anchorKeys := make(map[uint32]vOutAnchor)
273+
274+
// extractAnchorKey is a helper function that extracts the anchor key
275+
// from a virtual output and makes sure it is consistent with the
276+
// existing anchor keys from previous outputs of the same or different
277+
// packets.
278+
extractAnchorKey := func(vOut *tappsbt.VOutput) error {
279+
if vOut.AnchorOutputInternalKey == nil {
280+
return nil
281+
}
282+
283+
anchorIndex := vOut.AnchorOutputIndex
284+
anchorKey := vOut.AnchorOutputInternalKey
285+
286+
// Handle the case where we already have an anchor defined for
287+
// this index.
288+
if _, ok := anchorKeys[anchorIndex]; ok {
289+
existingPubKey := anchorKeys[anchorIndex].internalKey
290+
if !existingPubKey.IsEqual(anchorKey) {
291+
return fmt.Errorf("anchor output index %d "+
292+
"already has a different internal key "+
293+
"set: %x", anchorIndex,
294+
existingPubKey.SerializeCompressed())
295+
}
296+
297+
// The keys are the same, so this is already correct.
298+
return nil
299+
}
300+
301+
// There is no anchor yet, so we add it to the map.
302+
anchorKeys[anchorIndex] = newVOutAnchor(vOut)
303+
304+
return nil
305+
}
306+
307+
// Do a first pass through all packets to collect all existing anchor
308+
// keys. At the same time we make sure we don't already have diverging
309+
// information.
310+
for _, vPkt := range packets {
311+
for _, vOut := range vPkt.Outputs {
312+
if err := extractAnchorKey(vOut); err != nil {
313+
return err
314+
}
315+
}
316+
}
317+
318+
// We now do a second pass through all packets and set the internal keys
319+
// for all outputs that don't have one yet. If we don't have any key for
320+
// an output index, we create a new one.
321+
// nolint: lll
322+
for _, vPkt := range packets {
323+
for idx := range vPkt.Outputs {
324+
vOut := vPkt.Outputs[idx]
325+
anchorIndex := vOut.AnchorOutputIndex
326+
327+
// Skip any outputs that already have an internal key.
328+
if vOut.AnchorOutputInternalKey != nil {
329+
continue
330+
}
331+
332+
// Check if we can use an existing key for this output
333+
// index.
334+
existingAnchor, ok := anchorKeys[anchorIndex]
335+
if ok {
336+
existingAnchor.applyFields(vOut)
337+
338+
continue
339+
}
340+
341+
newInternalKey, err := keyRing.DeriveNextKey(
342+
ctx, asset.TaprootAssetsKeyFamily,
343+
)
344+
if err != nil {
345+
return err
346+
}
347+
vOut.SetAnchorInternalKey(
348+
newInternalKey, vPkt.ChainParams.HDCoinType,
349+
)
350+
351+
// Store this anchor information in case we have other
352+
// outputs in other packets that need it.
353+
anchorKeys[anchorIndex] = newVOutAnchor(vOut)
354+
}
355+
}
356+
357+
return nil
358+
}
359+
241360
// setVPacketInputs sets the inputs of the given vPkt to the given send eligible
242361
// commitments. It also returns the assets that were used as inputs.
243362
func setVPacketInputs(ctx context.Context, exporter proof.Exporter,

0 commit comments

Comments
 (0)