@@ -927,6 +927,220 @@ func runPsbtInteractiveSplitSendTest(ctxt context.Context, t *harnessTest,
927
927
)
928
928
}
929
929
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
+
930
1144
// testPsbtInteractiveTapscriptSibling tests that we can send assets to an
931
1145
// anchor output that also commits to a tapscript sibling.
932
1146
func testPsbtInteractiveTapscriptSibling (t * harnessTest ) {
0 commit comments