Skip to content

Commit e341586

Browse files
gijswijsguggero
authored andcommitted
itest: stxo exclusion proof test
This commit adds an itest that test whether the stxo exclusion proof is added to the proof suffix and whether the proof is actually a valid exclusion proof for the minimal asset we expect to see in the stxo set.
1 parent 5714598 commit e341586

File tree

2 files changed

+174
-0
lines changed

2 files changed

+174
-0
lines changed

itest/psbt_test.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/btcsuite/btcd/btcutil/hdkeychain"
1414
"github.com/btcsuite/btcd/btcutil/psbt"
1515
"github.com/btcsuite/btcd/txscript"
16+
"github.com/btcsuite/btcd/wire"
1617
"github.com/btcsuite/btcwallet/waddrmgr"
1718
"github.com/davecgh/go-spew/spew"
1819
"github.com/lightninglabs/taproot-assets/address"
@@ -2511,6 +2512,175 @@ func testPsbtTrustlessSwap(t *harnessTest) {
25112512
require.Equal(t.t, bobScriptKeyBytes, bobAssets.Assets[0].ScriptKey)
25122513
}
25132514

2515+
// testPsbtSTXOExclusionProofs tests that we can properly send normal assets
2516+
// back and forth, using partial amounts, between nodes with the use of PSBTs,
2517+
// and that we see the expected STXO exclusion proofs.
2518+
func testPsbtSTXOExclusionProofs(t *harnessTest) {
2519+
// First, we'll make a normal asset with a bunch of units that we are
2520+
// going to send backand forth. We're also minting a passive asset that
2521+
// should remain where it is.
2522+
rpcAssets := MintAssetsConfirmBatch(
2523+
t.t, t.lndHarness.Miner().Client, t.tapd,
2524+
[]*mintrpc.MintAssetRequest{
2525+
simpleAssets[0],
2526+
// Our "passive" asset.
2527+
{
2528+
Asset: &mintrpc.MintAsset{
2529+
AssetType: taprpc.AssetType_NORMAL,
2530+
Name: "itestbuxx-passive",
2531+
AssetMeta: &taprpc.AssetMeta{
2532+
Data: []byte("some metadata"),
2533+
},
2534+
Amount: 123,
2535+
},
2536+
},
2537+
},
2538+
)
2539+
2540+
ctxb := context.Background()
2541+
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
2542+
defer cancel()
2543+
2544+
mintedAsset := rpcAssets[0]
2545+
genInfo := rpcAssets[0].AssetGenesis
2546+
var assetId asset.ID
2547+
copy(assetId[:], genInfo.AssetId)
2548+
2549+
// Now that we have the asset created, we'll make a new node that'll
2550+
// serve as the node which'll receive the assets.
2551+
bobLnd := t.lndHarness.NewNodeWithCoins("Bob", nil)
2552+
bob := setupTapdHarness(t.t, t, bobLnd, t.universeServer)
2553+
defer func() {
2554+
require.NoError(t.t, bob.stop(!*noDelete))
2555+
}()
2556+
2557+
alice := t.tapd
2558+
2559+
// We need to derive two keys, one for the new script key and
2560+
// one for the internal key.
2561+
bobScriptKey, bobAnchorIntKeyDesc := DeriveKeys(t.t, bob)
2562+
2563+
var id [32]byte
2564+
copy(id[:], genInfo.AssetId)
2565+
sendAmt := uint64(2400)
2566+
2567+
vPkt := tappsbt.ForInteractiveSend(
2568+
id, sendAmt, bobScriptKey, 0, 0, 0,
2569+
bobAnchorIntKeyDesc, asset.V0, chainParams,
2570+
)
2571+
2572+
// Next, we'll attempt to complete a transfer with PSBTs from
2573+
// alice to bob, using the partial amount.
2574+
fundResp := fundPacket(t, alice, vPkt)
2575+
signResp, err := alice.SignVirtualPsbt(
2576+
ctxt, &wrpc.SignVirtualPsbtRequest{
2577+
FundedPsbt: fundResp.FundedPsbt,
2578+
},
2579+
)
2580+
require.NoError(t.t, err)
2581+
2582+
// Now we'll attempt to complete the transfer.
2583+
sendResp, err := alice.AnchorVirtualPsbts(
2584+
ctxt, &wrpc.AnchorVirtualPsbtsRequest{
2585+
VirtualPsbts: [][]byte{signResp.SignedPsbt},
2586+
},
2587+
)
2588+
require.NoError(t.t, err)
2589+
2590+
numOutputs := 2
2591+
changeAmt := mintedAsset.Amount - sendAmt
2592+
ConfirmAndAssertOutboundTransferWithOutputs(
2593+
t.t, t.lndHarness.Miner().Client, alice, sendResp,
2594+
genInfo.AssetId, []uint64{changeAmt, sendAmt}, 0, 1, numOutputs,
2595+
)
2596+
2597+
// We want the proof of the change asset since that is the root asset.
2598+
aliceScriptKeyBytes := sendResp.Transfer.Outputs[0].ScriptKey
2599+
proofResp := exportProof(
2600+
t, alice, sendResp, aliceScriptKeyBytes, genInfo,
2601+
)
2602+
proofFile, err := proof.DecodeFile(proofResp.RawProofFile)
2603+
require.NoError(t.t, err)
2604+
require.Equal(t.t, proofFile.NumProofs(), 2)
2605+
latestProof, err := proofFile.LastProof()
2606+
require.NoError(t.t, err)
2607+
2608+
// This proof should contain the STXO exclusion proofs
2609+
stxoProofs := latestProof.ExclusionProofs[0].CommitmentProof.STXOProofs
2610+
require.NotNil(t.t, stxoProofs)
2611+
2612+
// We expect a single exclusion proof for the change output, which is
2613+
// the input asset that we spent which should not be committed to in the
2614+
// other anchor output.
2615+
outpoint, err := wire.NewOutPointFromString(
2616+
mintedAsset.ChainAnchor.AnchorOutpoint,
2617+
)
2618+
require.NoError(t.t, err)
2619+
2620+
prevId := asset.PrevID{
2621+
OutPoint: *outpoint,
2622+
ID: id,
2623+
ScriptKey: asset.SerializedKey(mintedAsset.ScriptKey),
2624+
}
2625+
2626+
prevIdKey := asset.DeriveBurnKey(prevId)
2627+
expectedScriptKey := asset.NewScriptKey(prevIdKey)
2628+
2629+
pubKey := expectedScriptKey.PubKey
2630+
identifier := asset.ToSerialized(pubKey)
2631+
2632+
require.Len(t.t, stxoProofs, 1)
2633+
2634+
// If we derive the identifier from the script key we expect of the
2635+
// minimal asset, it should yield a proof when used as a key for the
2636+
// stxoProofs.
2637+
require.NotNil(t.t, stxoProofs[identifier])
2638+
2639+
// Create the minimal asset for which we expect to see the STXO
2640+
// exclusion.
2641+
minAsset, err := asset.NewAltLeaf(expectedScriptKey, asset.ScriptV0)
2642+
require.NoError(t.t, err)
2643+
2644+
// We need to copy the base exclusion proof for each STXO because we'll
2645+
// modify it with the specific asset and taproot proofs.
2646+
stxoProof := stxoProofs[identifier]
2647+
stxoExclProof := proof.MakeSTXOProof(
2648+
latestProof.ExclusionProofs[0], &stxoProof,
2649+
)
2650+
2651+
// Derive the possible taproot keys assuming the exclusion proof is
2652+
// correct.
2653+
derivedKeys, err := stxoExclProof.DeriveByAssetExclusion(
2654+
minAsset.AssetCommitmentKey(),
2655+
minAsset.TapCommitmentKey(),
2656+
)
2657+
require.NoError(t.t, err)
2658+
2659+
// Extract the actual taproot key from the anchor tx.
2660+
expectedTaprootKey, err := proof.ExtractTaprootKey(
2661+
&latestProof.AnchorTx, stxoExclProof.OutputIndex,
2662+
)
2663+
require.NoError(t.t, err)
2664+
expectedKey := schnorr.SerializePubKey(expectedTaprootKey)
2665+
2666+
// Convert the derived (possible) keys into their schnorr serialized
2667+
// counterparts.
2668+
serializedKeys := make([][]byte, 0, len(derivedKeys))
2669+
for derivedKey := range derivedKeys {
2670+
serializedKeys = append(
2671+
serializedKeys, derivedKey.SchnorrSerialized(),
2672+
)
2673+
}
2674+
2675+
// The derived keys should contain the expected key.
2676+
require.Contains(t.t, serializedKeys, expectedKey)
2677+
2678+
// This is an interactive transfer, so we do need to manually
2679+
// send the proof from the sender to the receiver.
2680+
bobScriptKeyBytes := bobScriptKey.PubKey.SerializeCompressed()
2681+
sendProof(t, alice, bob, sendResp, bobScriptKeyBytes, genInfo)
2682+
}
2683+
25142684
// testPsbtExternalCommit tests the ability to fully customize the BTC level of
25152685
// an asset transfer using a PSBT. This exercises the CommitVirtualPsbts and
25162686
// PublishAndLogTransfer RPCs. The test case moves some assets into an output

itest/test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ package itest
55
import "github.com/lightninglabs/taproot-assets/proof"
66

77
var testCases = []*testCase{
8+
{
9+
name: "psbt stxo exclusion proofs",
10+
test: testPsbtSTXOExclusionProofs,
11+
},
812
{
913
name: "mint assets",
1014
test: testMintAssets,

0 commit comments

Comments
 (0)