@@ -9,9 +9,11 @@ import (
99 "github.com/lightninglabs/taproot-assets/asset"
1010 "github.com/lightninglabs/taproot-assets/commitment"
1111 "github.com/lightninglabs/taproot-assets/fn"
12+ "github.com/lightninglabs/taproot-assets/mssmt"
1213 "github.com/lightninglabs/taproot-assets/proof"
1314 "github.com/lightninglabs/taproot-assets/tappsbt"
1415 "github.com/lightningnetwork/lnd/lnwallet/chainfee"
16+ "golang.org/x/exp/maps"
1517)
1618
1719// FundedPsbt represents a fully funded PSBT transaction.
@@ -265,6 +267,19 @@ func proofParams(finalTx *wire.MsgTx, vPkt *tappsbt.VPacket,
265267 allVirtualOutputs , rootOut .Asset , rootParams ,
266268 outputCommitments ,
267269 )
270+
271+ // Add STXO exclusion proofs for all the other outputs, for all
272+ // STXOs spent by _all_ VOutputs that anchor in this output.
273+ // First Collect all STXOs for this anchor output. Then add
274+ // exclusion proofs for all the other anchor outputs. Add these
275+ // in proofParams in tapsend/proof.go Those proofParams end up
276+ // in `CreateTransitionProof` in tapsend/append.go, where we
277+ // create the basic proof template. There we drop the STXO
278+ // exclusion proofs in proof.UnknownOddTypes.
279+ err := addSTXOExclusionProofs (
280+ allVirtualOutputs , rootOut .Asset , rootParams ,
281+ outputCommitments ,
282+ )
268283 if err != nil {
269284 return nil , err
270285 }
@@ -326,9 +341,119 @@ func proofParams(finalTx *wire.MsgTx, vPkt *tappsbt.VPacket,
326341 return splitParams , nil
327342}
328343
344+ // addSTXOExclusionProofs adds exclusion proofs for all the STXOs of the asset,
345+ // for all the outputs that are asset outputs but haven't been processed yet,
346+ // otherwise they'll be skipped. This should only be called after
347+ // `addOtherOutputExclusionProofs` because it depends on
348+ // `params.ExclusionProofs` already being set.
349+ func addSTXOExclusionProofs (outputs []* tappsbt.VOutput ,
350+ newAsset * asset.Asset , params * proof.TransitionParams ,
351+ outputCommitments map [uint32 ]* commitment.TapCommitment ) error {
352+
353+ stxoAssets , err := asset .CollectSTXO (newAsset )
354+ if err != nil {
355+ return fmt .Errorf ("error collecting STXO assets: %w" , err )
356+ }
357+
358+ for idx := range outputs {
359+ vOut := outputs [idx ]
360+
361+ outIndex := vOut .AnchorOutputIndex
362+
363+ // We can use `HaveInclusionProof` here because it is just a
364+ // check on whether we are processing our own anchor output.
365+ haveIProof := params .HaveInclusionProof (outIndex )
366+ haveEProof := params .HaveSTXOExclusionProof (outIndex )
367+ if haveIProof || haveEProof {
368+ continue
369+ }
370+
371+ tapTree := outputCommitments [outIndex ]
372+
373+ // Find the exclusion proofs for this output.
374+ var eProof * proof.TaprootProof
375+ for idx := range params .ExclusionProofs {
376+ e := params .ExclusionProofs [idx ]
377+ if e .OutputIndex == outIndex {
378+ eProof = & params .ExclusionProofs [idx ]
379+ break
380+ }
381+ }
382+ if eProof == nil {
383+ return fmt .Errorf ("no exclusion proof for " +
384+ "output %d" , outIndex )
385+ }
386+
387+ // There aren't any assets in this output, we can skip
388+ // creating exclusion proofs for it.
389+ if eProof .CommitmentProof == nil {
390+ continue
391+ }
392+
393+ commitmentProof := eProof .CommitmentProof
394+
395+ commitmentProof .STXOProofs = make (
396+ map [asset.SerializedKey ]commitment.Proof ,
397+ len (stxoAssets ),
398+ )
399+
400+ for idx := range stxoAssets {
401+ stxoAsset := stxoAssets [idx ].(* asset.Asset )
402+ pubKey := stxoAsset .ScriptKey .PubKey
403+ identifier := asset .ToSerialized (pubKey )
404+
405+ _ , exclusionProof , err := tapTree .Proof (
406+ stxoAsset .TapCommitmentKey (),
407+ stxoAsset .AssetCommitmentKey (),
408+ )
409+ if err != nil {
410+ return err
411+ }
412+
413+ // Confirm that we are creating the stxo proofs for the
414+ // asset that is being created. We do this by confirming
415+ // that the exclusion proof for the newly created asset
416+ // is already present.
417+ err = assertAssetExcluded (eProof , newAsset , tapTree )
418+ if err != nil {
419+ return fmt .Errorf ("v0 exclusion proof for " +
420+ "newly created asset not found during " +
421+ "creation of stxo proofs: %w" , err )
422+ }
423+
424+ commitmentProof .STXOProofs [identifier ] = * exclusionProof
425+ }
426+ }
427+
428+ return nil
429+ }
430+
431+ // assertAssetExcluded checks that the asset exclusion proof for the newly
432+ // created asset is present in the v0 exclusion proof for the given output.
433+ func assertAssetExcluded (exclusionProof * proof.TaprootProof ,
434+ newAsset * asset.Asset , target * commitment.TapCommitment ) error {
435+
436+ derivedKeys , err := exclusionProof .DeriveByAssetExclusion (
437+ newAsset .AssetCommitmentKey (), newAsset .TapCommitmentKey (),
438+ )
439+ if err != nil {
440+ return fmt .Errorf ("error deriving by asset exclusion: %w" , err )
441+ }
442+
443+ commitments := maps .Values (derivedKeys )
444+ if fn .NotAny (commitments , func (c * commitment.TapCommitment ) bool {
445+ return mssmt .IsEqualNode (c .TreeRoot , target .TreeRoot )
446+ }) {
447+
448+ return fmt .Errorf ("no derived commitment matches" )
449+ }
450+
451+ return nil
452+ }
453+
329454// addOtherOutputExclusionProofs adds exclusion proofs for all the outputs that
330- // are asset outputs but haven't been processed yet (the skip function needs to
331- // return false for not yet processed outputs, otherwise they'll be skipped) .
455+ // are asset outputs but haven't been processed yet, otherwise they'll be
456+ // skipped.
332457func addOtherOutputExclusionProofs (outputs []* tappsbt.VOutput ,
333458 asset * asset.Asset , params * proof.TransitionParams ,
334459 outputCommitments map [uint32 ]* commitment.TapCommitment ) error {
0 commit comments