@@ -13,6 +13,7 @@ import (
13
13
"github.com/btcsuite/btcd/btcutil/hdkeychain"
14
14
"github.com/btcsuite/btcd/btcutil/psbt"
15
15
"github.com/btcsuite/btcd/txscript"
16
+ "github.com/btcsuite/btcd/wire"
16
17
"github.com/btcsuite/btcwallet/waddrmgr"
17
18
"github.com/davecgh/go-spew/spew"
18
19
"github.com/lightninglabs/taproot-assets/address"
@@ -2511,6 +2512,175 @@ func testPsbtTrustlessSwap(t *harnessTest) {
2511
2512
require .Equal (t .t , bobScriptKeyBytes , bobAssets .Assets [0 ].ScriptKey )
2512
2513
}
2513
2514
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
+
2514
2684
// testPsbtExternalCommit tests the ability to fully customize the BTC level of
2515
2685
// an asset transfer using a PSBT. This exercises the CommitVirtualPsbts and
2516
2686
// PublishAndLogTransfer RPCs. The test case moves some assets into an output
0 commit comments