@@ -927,6 +927,220 @@ func runPsbtInteractiveSplitSendTest(ctxt context.Context, t *harnessTest,
927927 )
928928}
929929
930+ // testPsbtInteractiveAltLeafAnchoring tests that the tapfreighter and PSBT flow
931+ // RPC calls will reject a vPkt with invalid AltLeaves. It also tests that
932+ // AltLeaves from multiple vOutputs are merged into one anchor output correctly.
933+ func testPsbtInteractiveAltLeafAnchoring (t * harnessTest ) {
934+ // First, we'll make a normal asset with a bunch of units. We're also
935+ // minting a passive asset that should remain where it is.
936+ rpcAssets := MintAssetsConfirmBatch (
937+ t .t , t .lndHarness .Miner ().Client , t .tapd ,
938+ []* mintrpc.MintAssetRequest {
939+ issuableAssets [0 ],
940+ // Our "passive" asset.
941+ {
942+ Asset : & mintrpc.MintAsset {
943+ AssetType : taprpc .AssetType_NORMAL ,
944+ Name : "itestbuxx-passive" ,
945+ AssetMeta : & taprpc.AssetMeta {
946+ Data : []byte ("some metadata" ),
947+ },
948+ Amount : 123 ,
949+ },
950+ },
951+ },
952+ )
953+
954+ mintedAsset := rpcAssets [0 ]
955+ genInfo := rpcAssets [0 ].AssetGenesis
956+
957+ ctxb := context .Background ()
958+ ctxt , cancel := context .WithTimeout (ctxb , defaultWaitTimeout )
959+ defer cancel ()
960+
961+ // Now that we have the asset created, we'll make a new node that'll
962+ // serve as the node which'll receive the assets.
963+ secondTapd := setupTapdHarness (
964+ t .t , t , t .lndHarness .Bob , t .universeServer ,
965+ )
966+ defer func () {
967+ require .NoError (t .t , secondTapd .stop (! * noDelete ))
968+ }()
969+
970+ var (
971+ sender = t .tapd
972+ senderLnd = t .lndHarness .Alice
973+ receiver = secondTapd
974+ id = fn.ToArray [[32 ]byte ](genInfo .AssetId )
975+ partialAmt = mintedAsset .Amount / 4
976+ chainParams = & address .RegressionNetTap
977+ )
978+
979+ // Now, let's create a vPkt for receiving the active asset. We'll use
980+ // two outputs with different script keys and different altLeaves.
981+ receiverScriptKey1 , receiverAnchorIntKeyDesc := DeriveKeys (
982+ t .t , receiver ,
983+ )
984+ receiverScriptKey1Bytes := receiverScriptKey1 .PubKey .
985+ SerializeCompressed ()
986+ receiverScriptKey2 , _ := DeriveKeys (t .t , receiver )
987+ receiverScriptKey2Bytes := receiverScriptKey2 .PubKey .
988+ SerializeCompressed ()
989+
990+ vPkt := tappsbt .ForInteractiveSend (
991+ id , partialAmt , receiverScriptKey1 , 0 , 0 , 0 ,
992+ receiverAnchorIntKeyDesc , asset .V0 , chainParams ,
993+ )
994+ tappsbt .AddOutput (
995+ vPkt , partialAmt * 2 , receiverScriptKey2 , 0 ,
996+ receiverAnchorIntKeyDesc , asset .V0 ,
997+ )
998+
999+ // Let's make multiple sets of altLeaves. Set 1 and 2 should be valid
1000+ // if they were combined, but set 3 is a subset of set 1. If we submit
1001+ // two vPkts with sets 1 and 3, they should be rejected.
1002+ altLeaves1 := asset .RandAltLeaves (t .t , true )
1003+ altLeaves2 := asset .RandAltLeaves (t .t , true )
1004+ altLeaves3 := []* asset.Asset {altLeaves1 [0 ].Copy ()}
1005+
1006+ require .NoError (t .t , vPkt .Outputs [0 ].SetAltLeaves (altLeaves1 ))
1007+ require .NoError (t .t , vPkt .Outputs [1 ].SetAltLeaves (altLeaves3 ))
1008+
1009+ // Packet funding with conflicting altLeaves should fail.
1010+ _ , err := maybeFundPacket (t , sender , vPkt )
1011+ require .ErrorContains (t .t , err , asset .ErrDuplicateAltLeafKey .Error ())
1012+
1013+ // Let's unset the conflicting altLeaf, sign the vPkt, re-add the
1014+ // conflicting altLeaf, and try to anchor the vPkt.
1015+ vPkt .Outputs [1 ].AltLeaves = nil
1016+
1017+ fundResp := fundPacket (t , sender , vPkt )
1018+ signActiveResp , err := sender .SignVirtualPsbt (
1019+ ctxt , & wrpc.SignVirtualPsbtRequest {
1020+ FundedPsbt : fundResp .FundedPsbt ,
1021+ },
1022+ )
1023+ require .NoError (t .t , err )
1024+ require .Len (t .t , fundResp .PassiveAssetPsbts , 1 )
1025+
1026+ signPassiveResp , err := sender .SignVirtualPsbt (
1027+ ctxt , & wrpc.SignVirtualPsbtRequest {
1028+ FundedPsbt : fundResp .PassiveAssetPsbts [0 ],
1029+ },
1030+ )
1031+ require .NoError (t .t , err )
1032+
1033+ passivevPkt , err := tappsbt .Decode (signPassiveResp .SignedPsbt )
1034+ require .NoError (t .t , err )
1035+
1036+ signedvPkt , err := tappsbt .Decode (signActiveResp .SignedPsbt )
1037+ require .NoError (t .t , err )
1038+
1039+ signedvPktCopy := signedvPkt .Copy ()
1040+ require .NoError (t .t , signedvPkt .Outputs [1 ].SetAltLeaves (altLeaves3 ))
1041+ signedvPktBytes , err := tappsbt .Encode (signedvPkt )
1042+ require .NoError (t .t , err )
1043+
1044+ // Anchoring this vPkt should fail when creating the Tap commitments for
1045+ // each anchor output.
1046+ _ , err = sender .AnchorVirtualPsbts (
1047+ ctxt , & wrpc.AnchorVirtualPsbtsRequest {
1048+ VirtualPsbts : [][]byte {signedvPktBytes },
1049+ },
1050+ )
1051+ require .ErrorContains (t .t , err , asset .ErrDuplicateAltLeafKey .Error ())
1052+
1053+ // Trying to anchor the vPkt via the PSBT flow should also fail, for the
1054+ // same reason.
1055+ allPackets := []* tappsbt.VPacket {signedvPkt , passivevPkt }
1056+ btcPacket , err := tapsend .PrepareAnchoringTemplate (allPackets )
1057+ require .NoError (t .t , err )
1058+
1059+ var btcPacketBuf bytes.Buffer
1060+ require .NoError (t .t , btcPacket .Serialize (& btcPacketBuf ))
1061+
1062+ commitReq := & wrpc.CommitVirtualPsbtsRequest {
1063+ VirtualPsbts : [][]byte {signedvPktBytes },
1064+ PassiveAssetPsbts : [][]byte {signPassiveResp .SignedPsbt },
1065+ AnchorPsbt : btcPacketBuf .Bytes (),
1066+ Fees : & wrpc.CommitVirtualPsbtsRequest_SatPerVbyte {
1067+ SatPerVbyte : uint64 (feeRateSatPerKVByte / 1000 ),
1068+ },
1069+ AnchorChangeOutput : & wrpc.CommitVirtualPsbtsRequest_Add {
1070+ Add : true ,
1071+ },
1072+ }
1073+ _ , err = sender .CommitVirtualPsbts (ctxt , commitReq )
1074+ require .ErrorContains (t .t , err , asset .ErrDuplicateAltLeafKey .Error ())
1075+
1076+ // Now, let's set non-conflicting altLeaves for the second vOutput, and
1077+ // complete the transfer via the PSBT flow. This should succeed.
1078+ require .NoError (t .t , signedvPktCopy .Outputs [1 ].SetAltLeaves (altLeaves2 ))
1079+ signedvPktBytes , err = tappsbt .Encode (signedvPktCopy )
1080+
1081+ require .NoError (t .t , err )
1082+
1083+ commitReq .VirtualPsbts = [][]byte {signedvPktBytes }
1084+ commitResp , err := sender .CommitVirtualPsbts (ctxt , commitReq )
1085+ require .NoError (t .t , err )
1086+
1087+ commitPacket , err := psbt .NewFromRawBytes (
1088+ bytes .NewReader (commitResp .AnchorPsbt ), false ,
1089+ )
1090+ require .NoError (t .t , err )
1091+ require .Len (t .t , commitResp .VirtualPsbts , 1 )
1092+ require .Len (t .t , commitResp .PassiveAssetPsbts , 1 )
1093+
1094+ activePacket , err := tappsbt .Decode (commitResp .VirtualPsbts [0 ])
1095+ require .NoError (t .t , err )
1096+
1097+ passivePacket , err := tappsbt .Decode (commitResp .PassiveAssetPsbts [0 ])
1098+ require .NoError (t .t , err )
1099+
1100+ commitPacket = signPacket (t .t , senderLnd , commitPacket )
1101+ commitPacket = FinalizePacket (t .t , senderLnd .RPC , commitPacket )
1102+ publishResp := LogAndPublish (
1103+ t .t , sender , commitPacket , []* tappsbt.VPacket {activePacket },
1104+ []* tappsbt.VPacket {passivePacket }, commitResp ,
1105+ )
1106+
1107+ expectedAmounts := []uint64 {
1108+ partialAmt , partialAmt * 2 , partialAmt ,
1109+ }
1110+ ConfirmAndAssertOutboundTransferWithOutputs (
1111+ t .t , t .lndHarness .Miner ().Client , sender , publishResp ,
1112+ genInfo .AssetId , expectedAmounts , 0 , 1 , len (expectedAmounts ),
1113+ )
1114+
1115+ // This is an interactive transfer, so we do need to manually send the
1116+ // proofs from the sender to the receiver.
1117+ _ = sendProof (
1118+ t , sender , receiver , publishResp , receiverScriptKey1Bytes ,
1119+ genInfo ,
1120+ )
1121+ _ = sendProof (
1122+ t , sender , receiver , publishResp , receiverScriptKey2Bytes ,
1123+ genInfo ,
1124+ )
1125+
1126+ // Now, both output proofs should contain altLeaf sets 1 and 2, since
1127+ // our two vOutputs should be committed in the same anchor output.
1128+ receiverAssets , err := receiver .ListAssets (
1129+ ctxt , & taprpc.ListAssetRequest {},
1130+ )
1131+ require .NoError (t .t , err )
1132+
1133+ allAltLeaves := append (altLeaves1 , altLeaves2 ... )
1134+ leafMap := map [string ][]* asset.Asset {
1135+ string (receiverScriptKey1Bytes ): allAltLeaves ,
1136+ string (receiverScriptKey2Bytes ): allAltLeaves ,
1137+ }
1138+
1139+ for _ , asset := range receiverAssets .Assets {
1140+ AssertProofAltLeaves (t .t , receiver , asset , leafMap )
1141+ }
1142+ }
1143+
9301144// testPsbtInteractiveTapscriptSibling tests that we can send assets to an
9311145// anchor output that also commits to a tapscript sibling.
9321146func testPsbtInteractiveTapscriptSibling (t * harnessTest ) {
0 commit comments