Skip to content

Commit 4c76b64

Browse files
gijswijsguggero
authored andcommitted
tapsend+proof: Add STXO exclusion proof to params
This commit adds the STXO exclusion proof to the proof parameters. It stores them in `params.ExclusionProofs[].CommitmentProof.STXOProofs[]` where they are later encoded when creating the actual proofs.
1 parent f141fce commit 4c76b64

File tree

2 files changed

+145
-2
lines changed

2 files changed

+145
-2
lines changed

proof/mint.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,23 @@ func (p *BaseProofParams) HaveInclusionProof(anchorOutputIndex uint32) bool {
158158
return p.OutputIndex == int(anchorOutputIndex)
159159
}
160160

161+
// HaveSTXOExclusionProof returns true if we have an STXO exclusion proof for
162+
// the given anchor output index.
163+
func (p *BaseProofParams) HaveSTXOExclusionProof(
164+
anchorOutputIndex uint32) bool {
165+
166+
for _, proof := range p.ExclusionProofs {
167+
if proof.OutputIndex == anchorOutputIndex &&
168+
proof.CommitmentProof != nil &&
169+
len(proof.CommitmentProof.STXOProofs) > 0 {
170+
171+
return true
172+
}
173+
}
174+
175+
return false
176+
}
177+
161178
// MintParams holds the set of chain level information needed to make a proof
162179
// file for the set of assets minted in a batch.
163180
type MintParams struct {
@@ -289,6 +306,7 @@ func baseProof(params *BaseProofParams, prevOut wire.OutPoint) (*Proof, error) {
289306
InternalKey: params.InternalKey,
290307
}
291308
proof.ExclusionProofs = params.ExclusionProofs
309+
292310
return proof, nil
293311
}
294312

tapsend/proof.go

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
332457
func addOtherOutputExclusionProofs(outputs []*tappsbt.VOutput,
333458
asset *asset.Asset, params *proof.TransitionParams,
334459
outputCommitments map[uint32]*commitment.TapCommitment) error {

0 commit comments

Comments
 (0)