Skip to content

Commit 8c3d631

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 5687bc8 commit 8c3d631

File tree

1 file changed

+136
-16
lines changed

1 file changed

+136
-16
lines changed

tapfreighter/fund.go

Lines changed: 136 additions & 16 deletions
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,135 @@ 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+
261+
// generateOutputAnchorInternalKeys generates internal keys for the anchor
262+
// outputs of the given virtual packets. If an output already has an internal
263+
// key set, it will be used. If not, a new key will be derived and set.
264+
// At the same time we make sure that we don't use different keys for the same
265+
// anchor output index in case there are multiple packets.
266+
func generateOutputAnchorInternalKeys(ctx context.Context,
267+
packets []*tappsbt.VPacket, keyRing KeyRing) error {
268+
269+
// We need to make sure we don't use different keys for the same anchor
270+
// output index in case there are multiple packets. So we'll keep track
271+
// of any set keys here. This will be a merged set of existing and new
272+
// keys.
273+
anchorKeys := make(map[uint32]vOutAnchor)
274+
275+
// extractAnchorKey is a helper function that extracts the anchor key
276+
// from a virtual output and makes sure it is consistent with the
277+
// existing anchor keys from previous outputs of the same or different
278+
// packets.
279+
extractAnchorKey := func(vOut *tappsbt.VOutput) error {
280+
if vOut.AnchorOutputInternalKey == nil {
281+
return nil
282+
}
283+
284+
anchorIndex := vOut.AnchorOutputIndex
285+
anchorKey := vOut.AnchorOutputInternalKey
286+
287+
// Handle the case where we already have an anchor defined for
288+
// this index.
289+
if _, ok := anchorKeys[anchorIndex]; ok {
290+
existingPubKey := anchorKeys[anchorIndex].internalKey
291+
if !existingPubKey.IsEqual(anchorKey) {
292+
return fmt.Errorf("anchor output index %d "+
293+
"already has a different internal key "+
294+
"set: %x", anchorIndex,
295+
existingPubKey.SerializeCompressed())
296+
}
297+
298+
// The keys are the same, so this is already correct.
299+
return nil
300+
}
301+
302+
// There is no anchor yet, so we add it to the map.
303+
anchorKeys[anchorIndex] = newVOutAnchor(vOut)
304+
305+
return nil
306+
}
307+
308+
// Do a first pass through all packets to collect all existing anchor
309+
// keys. At the same time we make sure we don't already have diverging
310+
// information.
311+
for _, vPkt := range packets {
312+
for _, vOut := range vPkt.Outputs {
313+
if err := extractAnchorKey(vOut); err != nil {
314+
return err
315+
}
316+
}
317+
}
318+
319+
// We now do a second pass through all packets and set the internal keys
320+
// for all outputs that don't have one yet. If we don't have any key for
321+
// an output index, we create a new one.
322+
// nolint: lll
323+
for _, vPkt := range packets {
324+
for idx := range vPkt.Outputs {
325+
vOut := vPkt.Outputs[idx]
326+
anchorIndex := vOut.AnchorOutputIndex
327+
328+
// Skip any outputs that already have an internal key.
329+
if vOut.AnchorOutputInternalKey != nil {
330+
continue
331+
}
332+
333+
// Check if we can use an existing key for this output
334+
// index.
335+
existingAnchor, ok := anchorKeys[anchorIndex]
336+
if ok {
337+
existingAnchor.applyFields(vOut)
338+
339+
continue
340+
}
341+
342+
newInternalKey, err := keyRing.DeriveNextKey(
343+
ctx, asset.TaprootAssetsKeyFamily,
344+
)
345+
if err != nil {
346+
return err
347+
}
348+
vOut.SetAnchorInternalKey(
349+
newInternalKey, vPkt.ChainParams.HDCoinType,
350+
)
351+
352+
// Store this anchor information in case we have other
353+
// outputs in other packets that need it.
354+
anchorKeys[anchorIndex] = newVOutAnchor(vOut)
355+
}
356+
}
357+
358+
return nil
359+
}
360+
241361
// setVPacketInputs sets the inputs of the given vPkt to the given send eligible
242362
// commitments. It also returns the assets that were used as inputs.
243363
func setVPacketInputs(ctx context.Context, exporter proof.Exporter,

0 commit comments

Comments
 (0)