|
6 | 6 | "errors"
|
7 | 7 | "fmt"
|
8 | 8 |
|
| 9 | + "github.com/btcsuite/btcd/btcec/v2" |
9 | 10 | "github.com/btcsuite/btcd/btcutil/psbt"
|
10 | 11 | "github.com/btcsuite/btcd/txscript"
|
11 | 12 | "github.com/btcsuite/btcd/wire"
|
@@ -85,29 +86,19 @@ func createFundedPacketWithInputs(ctx context.Context, exporter proof.Exporter,
|
85 | 86 | // a new internal key for the anchor outputs. We assume any output that
|
86 | 87 | // hasn't got an internal key set is going to a local anchor, and we
|
87 | 88 | // 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) |
103 | 94 | }
|
104 | 95 |
|
105 | 96 | if err := tapsend.PrepareOutputAssets(ctx, vPkt); err != nil {
|
106 | 97 | return nil, fmt.Errorf("unable to prepare outputs: %w", err)
|
107 | 98 | }
|
108 | 99 |
|
109 | 100 | return &FundedVPacket{
|
110 |
| - VPackets: []*tappsbt.VPacket{vPkt}, |
| 101 | + VPackets: packets, |
111 | 102 | InputCommitments: inputCommitments,
|
112 | 103 | }, nil
|
113 | 104 | }
|
@@ -238,6 +229,134 @@ func createChangeOutput(ctx context.Context, vPkt *tappsbt.VPacket,
|
238 | 229 | return nil
|
239 | 230 | }
|
240 | 231 |
|
| 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 | + |
241 | 360 | // setVPacketInputs sets the inputs of the given vPkt to the given send eligible
|
242 | 361 | // commitments. It also returns the assets that were used as inputs.
|
243 | 362 | func setVPacketInputs(ctx context.Context, exporter proof.Exporter,
|
|
0 commit comments