88 "github.com/btcsuite/btcd/btcec/v2/schnorr"
99 "github.com/btcsuite/btcd/wire"
1010 tap "github.com/lightninglabs/taproot-assets"
11+ "github.com/lightninglabs/taproot-assets/address"
1112 "github.com/lightninglabs/taproot-assets/asset"
1213 "github.com/lightninglabs/taproot-assets/commitment"
1314 "github.com/lightninglabs/taproot-assets/fn"
@@ -22,6 +23,7 @@ import (
2223 unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
2324 "github.com/lightninglabs/taproot-assets/universe"
2425 "github.com/lightningnetwork/lnd/lntest/wait"
26+ "github.com/lightningnetwork/lnd/tlv"
2527 "github.com/stretchr/testify/require"
2628)
2729
@@ -661,6 +663,179 @@ func runMultiSendTest(ctxt context.Context, t *harnessTest, alice,
661663 }
662664}
663665
666+ // testUnknownTlvType tests that we can create an address with an unknown TLV
667+ // type and that assets can be sent to it. We then modify a proof similarly and
668+ // make sure it can be imported by a node correctly.
669+ func testUnknownTlvType (t * harnessTest ) {
670+ // First, mint an asset, so we have one to create addresses for.
671+ rpcAssets := MintAssetsConfirmBatch (
672+ t .t , t .lndHarness .Miner .Client , t .tapd ,
673+ []* mintrpc.MintAssetRequest {
674+ simpleAssets [0 ], issuableAssets [0 ],
675+ },
676+ )
677+ mintedAsset := rpcAssets [0 ]
678+ genInfo := mintedAsset .AssetGenesis
679+
680+ ctxb := context .Background ()
681+ ctxt , cancel := context .WithTimeout (ctxb , defaultWaitTimeout )
682+ defer cancel ()
683+
684+ // We'll make a second node now that'll be the receiver of all the
685+ // assets made above.
686+ alice := t .tapd
687+ bob := setupTapdHarness (
688+ t .t , t , t .lndHarness .Bob , t .universeServer ,
689+ )
690+ defer func () {
691+ require .NoError (t .t , bob .stop (! * noDelete ))
692+ }()
693+
694+ // We now create an address for Bob and add some unknown TLV type to it.
695+ bobAddr , err := bob .NewAddr (ctxt , & taprpc.NewAddrRequest {
696+ AssetId : genInfo .AssetId ,
697+ Amt : 123 ,
698+ })
699+ require .NoError (t .t , err )
700+
701+ decoded , err := address .DecodeAddress (
702+ bobAddr .Encoded , & address .RegressionNetTap ,
703+ )
704+ require .NoError (t .t , err )
705+
706+ decoded .UnknownOddTypes = tlv.TypeMap {
707+ 345 : []byte ("plz send assets" ),
708+ }
709+ bobAddr .Encoded , err = decoded .EncodeAddress ()
710+ require .NoError (t .t , err )
711+
712+ sendResp , sendEvents := sendAssetsToAddr (t , alice , bobAddr )
713+ sendRespJSON , err := formatProtoJSON (sendResp )
714+ require .NoError (t .t , err )
715+ t .Logf ("Got response from sending assets: %v" , sendRespJSON )
716+
717+ AssertAddrEvent (t .t , bob , bobAddr , 1 , statusDetected )
718+
719+ // Mine a block to make sure the events are marked as confirmed.
720+ _ = MineBlocks (t .t , t .lndHarness .Miner .Client , 1 , 1 )
721+
722+ // Eventually the event should be marked as confirmed.
723+ AssertAddrEventByStatus (t .t , bob , statusConfirmed , 1 )
724+
725+ // Make sure we have imported and finalized all proofs.
726+ AssertNonInteractiveRecvComplete (t .t , bob , 1 )
727+ AssertSendEventsComplete (t .t , bobAddr .ScriptKey , sendEvents )
728+
729+ // We export the proof for the address so we can modify it.
730+ transferProof := exportProof (
731+ t , bob , sendResp , bobAddr .ScriptKey , genInfo ,
732+ )
733+
734+ f , err := proof .DecodeFile (transferProof .RawProofFile )
735+ require .NoError (t .t , err )
736+
737+ lastProof , err := f .LastProof ()
738+ require .NoError (t .t , err )
739+
740+ proofCustomTypes := tlv.TypeMap {
741+ 123 : []byte ("got something to prove" ),
742+ }
743+ lastProof .UnknownOddTypes = proofCustomTypes
744+ lastProof .InclusionProof .UnknownOddTypes = tlv.TypeMap {
745+ 345 : []byte ("it's included" ),
746+ }
747+ cp := lastProof .InclusionProof .CommitmentProof
748+ cp .UnknownOddTypes = tlv.TypeMap {
749+ 567 : []byte ("it's committed" ),
750+ }
751+ cp .TaprootAssetProof .UnknownOddTypes = tlv.TypeMap {
752+ 789 : []byte ("there's assets in here..." ),
753+ }
754+ cp .AssetProof .UnknownOddTypes = tlv.TypeMap {
755+ 987 : []byte ("...and here" ),
756+ }
757+ lastProof .ExclusionProofs [0 ].UnknownOddTypes = tlv.TypeMap {
758+ 321 : []byte ("it's excluded" ),
759+ }
760+
761+ // Let's re-encode the proof and import it to the new node.
762+ err = f .ReplaceLastProof (* lastProof )
763+ require .NoError (t .t , err )
764+ modifiedBlob , err := proof .EncodeFile (f )
765+ require .NoError (t .t , err )
766+
767+ // We make a new node to import the modified proof.
768+ charlie := setupTapdHarness (
769+ t .t , t , t .lndHarness .Bob , t .universeServer ,
770+ )
771+ defer func () {
772+ require .NoError (t .t , charlie .stop (! * noDelete ))
773+ }()
774+
775+ importProof (t , charlie , modifiedBlob , genInfo .GenesisPoint )
776+
777+ // When we export it again, it should have the same TLV types.
778+ transferProof2 := exportProof (
779+ t , charlie , sendResp , bobAddr .ScriptKey , genInfo ,
780+ )
781+ f2 , err := proof .DecodeFile (transferProof2 .RawProofFile )
782+ require .NoError (t .t , err )
783+
784+ lastProof2 , err := f2 .LastProof ()
785+ require .NoError (t .t , err )
786+
787+ // If the contents are identical to what we uploaded, we just need to
788+ // check a single value in the proof to be sure all the custom types are
789+ // there.
790+ require .Equal (t .t , modifiedBlob , transferProof2 .RawProofFile )
791+ require .True (t .t , bytes .Contains (modifiedBlob , []byte ("it's included" )))
792+ require .True (
793+ t .t , bytes .Contains (modifiedBlob , []byte ("it's committed" )),
794+ )
795+ require .Equal (t .t , proofCustomTypes , lastProof2 .UnknownOddTypes )
796+
797+ // The proof should also still be valid. Importing the proof validates
798+ // it, but we also want to do it explicitly.
799+ verifyResp , err := charlie .VerifyProof (ctxb , & taprpc.ProofFile {
800+ RawProofFile : modifiedBlob ,
801+ GenesisPoint : genInfo .GenesisPoint ,
802+ })
803+ require .NoError (t .t , err )
804+ require .True (t .t , verifyResp .Valid )
805+
806+ // The final test involves adding some extra data to the meta reveal of
807+ // a proof. That will invalidate the proof, as the commitments are for
808+ // a different meta. But the meta hash should be calculated differently,
809+ // showing that the extra data is considered for the meta hash
810+ // calculation.
811+ firstProof , err := f2 .ProofAt (0 )
812+ require .NoError (t .t , err )
813+
814+ require .NotNil (t .t , firstProof .MetaReveal )
815+ hashBeforeUpdate := firstProof .MetaReveal .MetaHash ()
816+
817+ // Let's modify the meta hash with some extra data.
818+ firstProof .MetaReveal .UnknownOddTypes = tlv.TypeMap {
819+ 123 : []byte ("extra data" ),
820+ }
821+ hashAfterUpdate := firstProof .MetaReveal .MetaHash ()
822+
823+ require .NotEqual (t .t , hashBeforeUpdate , hashAfterUpdate )
824+
825+ // We should not be able to verify the proof anymore.
826+ err = f2 .ReplaceProofAt (0 , * firstProof )
827+ require .NoError (t .t , err )
828+ modifiedBlob2 , err := proof .EncodeFile (f2 )
829+ require .NoError (t .t , err )
830+
831+ verifyResp , err = charlie .VerifyProof (ctxb , & taprpc.ProofFile {
832+ RawProofFile : modifiedBlob2 ,
833+ GenesisPoint : genInfo .GenesisPoint ,
834+ })
835+ require .NoError (t .t , err )
836+ require .False (t .t , verifyResp .Valid )
837+ }
838+
664839// sendProof manually exports a proof from the given source node and imports it
665840// using the development only ImportProof RPC on the destination node.
666841func sendProof (t * harnessTest , src , dst * tapdHarness ,
0 commit comments