Skip to content

Commit 840267d

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 c0347b5 commit 840267d

File tree

2 files changed

+122
-19
lines changed

2 files changed

+122
-19
lines changed

tapfreighter/fund.go

Lines changed: 118 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,29 +85,19 @@ func createFundedPacketWithInputs(ctx context.Context, exporter proof.Exporter,
8585
// a new internal key for the anchor outputs. We assume any output that
8686
// hasn't got an internal key set is going to a local anchor, and we
8787
// 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-
)
88+
packets := []*tappsbt.VPacket{vPkt}
89+
err = generateOutputAnchorInternalKeys(ctx, packets, keyRing)
90+
if err != nil {
91+
return nil, fmt.Errorf("unable to generate output anchor "+
92+
"internal keys: %w", err)
10393
}
10494

10595
if err := tapsend.PrepareOutputAssets(ctx, vPkt); err != nil {
10696
return nil, fmt.Errorf("unable to prepare outputs: %w", err)
10797
}
10898

10999
return &FundedVPacket{
110-
VPackets: []*tappsbt.VPacket{vPkt},
100+
VPackets: packets,
111101
InputCommitments: inputCommitments,
112102
}, nil
113103
}
@@ -238,6 +228,118 @@ func createChangeOutput(ctx context.Context, vPkt *tappsbt.VPacket,
238228
return nil
239229
}
240230

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

tapfreighter/fund_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,10 @@ func TestFundPacket(t *testing.T) {
292292
ScriptKey: asset.NUMSScriptKey,
293293
Interactive: false,
294294
}, {
295-
Amount: mintAmount,
296-
ScriptKey: scriptKey,
297-
Interactive: false,
295+
Amount: mintAmount,
296+
ScriptKey: scriptKey,
297+
Interactive: false,
298+
AnchorOutputIndex: 1,
298299
}},
299300
},
300301
selectedCommitments: []*AnchoredCommitment{{

0 commit comments

Comments
 (0)