Skip to content

Commit 4eef445

Browse files
committed
multi: stxo inclusion proof verification
This commit adds v2 inclusion proof verification.
1 parent b4ded3e commit 4eef445

File tree

5 files changed

+117
-28
lines changed

5 files changed

+117
-28
lines changed

asset/asset.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2070,7 +2070,7 @@ func CollectSTXO(outAsset *Asset) ([]AltLeaf[Asset], error) {
20702070
// Genesis assets have no input asset, so they should have an empty
20712071
// STXO tree. Split leaves will also have a zero PrevID; we will use
20722072
// an empty STXO tree for them as well.
2073-
if outAsset.IsGenesisAsset() || outAsset.HasSplitCommitmentWitness() {
2073+
if !outAsset.IsTransferRoot() {
20742074
return nil, nil
20752075
}
20762076

proof/append.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,14 @@ func CreateTransitionProof(prevOut wire.OutPoint,
171171
"prevID", wit)
172172
}
173173

174-
spentAsset := &asset.Asset{
175-
ScriptKey: asset.NewScriptKey(
176-
asset.DeriveBurnKey(*wit.PrevID),
177-
),
174+
prevIdKey := asset.DeriveBurnKey(*wit.PrevID)
175+
scriptKey := asset.NewScriptKey(prevIdKey)
176+
spentAsset, err := asset.NewAltLeaf(
177+
scriptKey, asset.ScriptV0, nil,
178+
)
179+
if err != nil {
180+
return nil, fmt.Errorf("error creating "+
181+
"altLeaf: %w", err)
178182
}
179183

180184
// Generate an STXO inclusion proof for each prev

proof/append_test.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func TestAppendTransition(t *testing.T) {
7171
withBip86Change: true,
7272
},
7373
{
74-
name: "normal with change",
74+
name: "normal with change (with split)",
7575
assetType: asset.Normal,
7676
amt: 100,
7777
withSplit: true,
@@ -135,6 +135,17 @@ func runAppendTransitionTest(t *testing.T, assetType asset.Type, amt uint64,
135135

136136
// Add some alt leaves to the commitment anchoring the asset transfer.
137137
altLeaves := asset.ToAltLeaves(asset.RandAltLeaves(t, true))
138+
139+
// Commit to the stxo of the previous asset. Otherwise the inclusion
140+
// proofs will fail.
141+
prevIdKey := asset.DeriveBurnKey(*newAsset.PrevWitnesses[0].PrevID)
142+
scriptKey := asset.NewScriptKey(prevIdKey)
143+
stxoAsset, err := asset.NewAltLeaf(
144+
scriptKey, asset.ScriptV0, nil,
145+
)
146+
require.NoError(t, err)
147+
stxoLeaf := asset.ToAltLeaves([]*asset.Asset{stxoAsset})
148+
altLeaves = append(altLeaves, stxoLeaf...)
138149
err = tapCommitment.MergeAltLeaves(altLeaves)
139150
require.NoError(t, err)
140151

@@ -293,8 +304,20 @@ func runAppendTransitionTest(t *testing.T, assetType asset.Type, amt uint64,
293304
nil, split1Commitment,
294305
)
295306
require.NoError(t, err)
307+
308+
// Commit to the stxo of the previous asset. Otherwise the inclusion
309+
// proofs will fail. With splits this is only needed for the root asset.
310+
prevIdKey1 := asset.DeriveBurnKey(*split1Asset.PrevWitnesses[0].PrevID)
311+
scriptKey1 := asset.NewScriptKey(prevIdKey1)
312+
stxoAsset1, err := asset.NewAltLeaf(
313+
scriptKey1, asset.ScriptV0, nil,
314+
)
315+
require.NoError(t, err)
316+
stxoLeaf1 := asset.ToAltLeaves([]*asset.Asset{stxoAsset1})
317+
split1AltLeaves = append(split1AltLeaves, stxoLeaf1...)
296318
err = tap1Commitment.MergeAltLeaves(split1AltLeaves)
297319
require.NoError(t, err)
320+
298321
tap2Commitment, err := commitment.NewTapCommitment(
299322
nil, split2Commitment,
300323
)

proof/verifier.go

+62-11
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,51 @@ func verifyTaprootProof(anchor *wire.MsgTx, proof *TaprootProof,
165165

166166
// verifyInclusionProof verifies the InclusionProof is valid.
167167
func (p *Proof) verifyInclusionProof() (*commitment.TapCommitment, error) {
168+
169+
// Determine if we're dealing with v1 or v2 proofs.
170+
hasV2Proofs := p.InclusionProof.CommitmentProof != nil &&
171+
len(p.InclusionProof.CommitmentProof.STXOProofs) > 0
172+
173+
if hasV2Proofs {
174+
commitVersions := make(
175+
map[uint32][]commitment.TapCommitmentVersion,
176+
)
177+
p2trOutputs := make(map[uint32]fn.Set[asset.SerializedKey])
178+
outIdx := p.InclusionProof.OutputIndex
179+
p2trOutputs[outIdx] = make(fn.Set[asset.SerializedKey])
180+
181+
// Collect the STXOs from the new asset.
182+
stxoAssets, err := asset.CollectSTXO(&p.Asset)
183+
if err != nil {
184+
return nil, fmt.Errorf("error collecting STXO "+
185+
"assets: %w", err)
186+
}
187+
188+
// Map STXOs by serialized key.
189+
assetMap := make(map[asset.SerializedKey]*asset.Asset)
190+
for idx := range stxoAssets {
191+
stxoAsset := stxoAssets[idx].(*asset.Asset)
192+
key := asset.ToSerialized(stxoAsset.ScriptKey.PubKey)
193+
assetMap[key] = stxoAsset
194+
p2trOutputs[outIdx].Add(key)
195+
}
196+
197+
baseProof := p.InclusionProof
198+
199+
commitment, err := p.verifySTXOProofSet(baseProof, assetMap,
200+
p2trOutputs, commitVersions, true)
201+
if err != nil {
202+
return nil, err
203+
}
204+
205+
err = p.verifyRemainingOutputs(p2trOutputs)
206+
if err != nil {
207+
return nil, err
208+
}
209+
210+
return commitment, nil
211+
}
212+
168213
return verifyTaprootProof(
169214
&p.AnchorTx, &p.InclusionProof, &p.Asset, true,
170215
)
@@ -283,8 +328,8 @@ func (p *Proof) verifyV2ExclusionProofs(
283328
continue
284329
}
285330

286-
err := p.verifySTXOProofSet(baseProof, assetMap,
287-
p2trOutputs, commitVersions)
331+
_, err := p.verifySTXOProofSet(baseProof, assetMap,
332+
p2trOutputs, commitVersions, false)
288333
if err != nil {
289334
return nil, err
290335
}
@@ -334,21 +379,26 @@ func (p *Proof) handleBasicExclusionProof(baseProof *TaprootProof,
334379
func (p *Proof) verifySTXOProofSet(baseProof TaprootProof,
335380
assetMap map[asset.SerializedKey]*asset.Asset,
336381
p2trOutputs map[uint32]fn.Set[asset.SerializedKey],
337-
commitVersions map[uint32][]commitment.TapCommitmentVersion) error {
382+
commitVersions map[uint32][]commitment.TapCommitmentVersion,
383+
inclusion bool,
384+
) (*commitment.TapCommitment, error) {
338385

386+
var commitment *commitment.TapCommitment
339387
for key := range baseProof.CommitmentProof.STXOProofs {
340388
stxoProof := baseProof.CommitmentProof.STXOProofs[key]
341389
stxoAsset := assetMap[key]
342-
stxoExclProof := p.makeSTXOExclusionProof(baseProof, &stxoProof)
390+
stxoCombinedProof := p.makeSTXOProof(baseProof, &stxoProof)
343391

344-
commitment, err := verifyTaprootProof(
345-
&p.AnchorTx, &stxoExclProof, stxoAsset, false,
392+
var err error
393+
commitment, err = verifyTaprootProof(
394+
&p.AnchorTx, &stxoCombinedProof, stxoAsset, inclusion,
346395
)
347396
if err != nil {
348-
return fmt.Errorf("error verifying STXO proof: %w", err)
397+
return nil, fmt.Errorf("error verifying STXO "+
398+
"proof: %w", err)
349399
}
350400

351-
outIdx := stxoExclProof.OutputIndex
401+
outIdx := stxoCombinedProof.OutputIndex
352402
delete(p2trOutputs[outIdx], key)
353403
if len(p2trOutputs[outIdx]) == 0 {
354404
delete(p2trOutputs, outIdx)
@@ -359,11 +409,12 @@ func (p *Proof) verifySTXOProofSet(baseProof TaprootProof,
359409
)
360410
}
361411

362-
return nil
412+
return commitment, nil
363413
}
364414

365-
// makeSTXOExclusionProof creates a new exclusion proof for an STXO.
366-
func (p *Proof) makeSTXOExclusionProof(baseProof TaprootProof,
415+
// makeSTXOProof creates a new proof for an STXO reusing the base proof while
416+
// replacing commitmentProof bu the stxoProof.
417+
func (p *Proof) makeSTXOProof(baseProof TaprootProof,
367418
stxoProof *commitment.Proof) TaprootProof {
368419

369420
return TaprootProof{

tapsend/proof_test.go

+22-11
Original file line numberDiff line numberDiff line change
@@ -222,27 +222,38 @@ func addOutputCommitment(t *testing.T, anchorTx *AnchorTransaction,
222222
}
223223
}
224224

225-
for idx, assets := range assetsByOutput {
226-
for idx := range assets {
227-
if !assets[idx].HasSplitCommitmentWitness() {
225+
stxoAssetsByOutput := make(map[uint32][]asset.AltLeaf[asset.Asset])
226+
for idx1, assets := range assetsByOutput {
227+
for idx2 := range assets {
228+
if assets[idx2].IsTransferRoot() {
229+
stxoAssets, err := asset.CollectSTXO(
230+
assets[idx2],
231+
)
232+
stxoAssetsByOutput[idx1] = append(
233+
stxoAssetsByOutput[idx1], stxoAssets...,
234+
)
235+
require.NoError(t, err)
236+
}
237+
if !assets[idx2].HasSplitCommitmentWitness() {
228238
continue
229239
}
230-
assets[idx] = assets[idx].Copy()
231-
assets[idx].PrevWitnesses[0].SplitCommitment = nil
240+
assets[idx2] = assets[idx2].Copy()
241+
assets[idx2].PrevWitnesses[0].SplitCommitment = nil
232242
}
233243

234244
c, err := commitment.FromAssets(nil, assets...)
235245
require.NoError(t, err)
246+
err = c.MergeAltLeaves(stxoAssetsByOutput[idx1])
247+
require.NoError(t, err)
236248

237-
internalKey := keyByOutput[idx]
249+
internalKey := keyByOutput[idx1]
238250
script, err := tapscript.PayToAddrScript(*internalKey, nil, *c)
239251
require.NoError(t, err)
240252

241-
packet.UnsignedTx.TxOut[idx].PkScript = script
242-
packet.Outputs[idx].TaprootInternalKey = schnorr.SerializePubKey(
243-
internalKey,
244-
)
245-
outputCommitments[idx] = c
253+
packet.UnsignedTx.TxOut[idx1].PkScript = script
254+
packet.Outputs[idx1].TaprootInternalKey = schnorr.
255+
SerializePubKey(internalKey)
256+
outputCommitments[idx1] = c
246257
}
247258
}
248259

0 commit comments

Comments
 (0)