Skip to content

Commit 3bae4c6

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 d9610dd commit 3bae4c6

File tree

2 files changed

+121
-19
lines changed

2 files changed

+121
-19
lines changed

tapfreighter/fund.go

+117-16
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,117 @@ 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+
// The keys are the same, so this is already correct.
279+
return nil
280+
}
281+
282+
// There is no anchor yet, so we add it to the map.
283+
addAnchorKey(vOut)
284+
285+
return nil
286+
}
287+
288+
// Do a first pass through all packets to collect all existing anchor
289+
// keys. At the same time we make sure we don't already have diverging
290+
// information.
291+
for _, vPkt := range packets {
292+
for _, vOut := range vPkt.Outputs {
293+
if err := extractAnchorKey(vOut); err != nil {
294+
return err
295+
}
296+
}
297+
}
298+
299+
// We now do a second pass through all packets and set the internal keys
300+
// for all outputs that don't have one yet. If we don't have any key for
301+
// an output index, we create a new one.
302+
// nolint: lll
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+
vOut.AnchorOutputInternalKey = anchor.InternalKey
317+
vOut.AnchorOutputBip32Derivation = anchor.Bip32Derivation
318+
vOut.AnchorOutputTaprootBip32Derivation = anchor.TrBip32Derivation
319+
320+
continue
321+
}
322+
323+
newInternalKey, err := keyRing.DeriveNextKey(
324+
ctx, asset.TaprootAssetsKeyFamily,
325+
)
326+
if err != nil {
327+
return err
328+
}
329+
vOut.SetAnchorInternalKey(
330+
newInternalKey, vPkt.ChainParams.HDCoinType,
331+
)
332+
333+
// Store this anchor information in case we have other
334+
// outputs in other packets that need it.
335+
addAnchorKey(vOut)
336+
}
337+
}
338+
339+
return nil
340+
}
341+
241342
// setVPacketInputs sets the inputs of the given vPkt to the given send eligible
242343
// commitments. It also returns the assets that were used as inputs.
243344
func setVPacketInputs(ctx context.Context, exporter proof.Exporter,

tapfreighter/fund_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,10 @@ func TestFundPacket(t *testing.T) {
291291
ScriptKey: asset.NUMSScriptKey,
292292
Interactive: false,
293293
}, {
294-
Amount: mintAmount,
295-
ScriptKey: scriptKey,
296-
Interactive: false,
294+
Amount: mintAmount,
295+
ScriptKey: scriptKey,
296+
Interactive: false,
297+
AnchorOutputIndex: 1,
297298
}},
298299
},
299300
selectedCommitments: []*AnchoredCommitment{{

0 commit comments

Comments
 (0)