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