@@ -4061,3 +4061,134 @@ func updateTaprootOutputKeysMarkerV0(btcPacket *psbt.Packet,
40614061
40624062 return nil
40634063}
4064+
4065+ // testPsbtInvalidInputProofRejection tests that the ChainPorter correctly
4066+ // rejects pre-anchored send packages with invalid input proofs at the
4067+ // SendStateVerifyPreBroadcast state, preventing broadcast of transactions with
4068+ // corrupted proofs. This validates the core invariant that invalid input
4069+ // proofs cannot be broadcast.
4070+ func testPsbtInvalidInputProofRejection (t * harnessTest ) {
4071+ ctxb := context .Background ()
4072+ ctxt , cancel := context .WithTimeout (ctxb , defaultWaitTimeout )
4073+ defer cancel ()
4074+
4075+ alice := t .tapd
4076+
4077+ // Mint a simple asset to Alice.
4078+ rpcAssets := MintAssetsConfirmBatch (
4079+ t .t , t .lndHarness .Miner ().Client , alice ,
4080+ []* mintrpc.MintAssetRequest {simpleAssets [0 ]},
4081+ )
4082+
4083+ mintedAsset := rpcAssets [0 ]
4084+ genInfo := mintedAsset .AssetGenesis
4085+ assetID := genInfo .AssetId
4086+ fullAmt := mintedAsset .Amount
4087+
4088+ t .t .Logf ("Minted asset %x with amount %d" , assetID , fullAmt )
4089+
4090+ // Create Bob node to receive the initial transfer.
4091+ lndBob := t .lndHarness .NewNodeWithCoins ("Bob" , nil )
4092+ bob := setupTapdHarness (t .t , t , lndBob , t .universeServer )
4093+ defer func () {
4094+ require .NoError (t .t , bob .stop (! * noDelete ))
4095+ }()
4096+
4097+ // Transfer from Alice to Bob, creating a valid input proof for Bob.
4098+ bobAddr , err := bob .NewAddr (ctxt , & taprpc.NewAddrRequest {
4099+ AssetId : assetID ,
4100+ Amt : fullAmt ,
4101+ })
4102+ require .NoError (t .t , err )
4103+
4104+ sendResp , _ := sendAssetsToAddr (t , alice , bobAddr )
4105+ ConfirmAndAssertOutboundTransfer (
4106+ t .t , t .lndHarness .Miner ().Client , alice , sendResp ,
4107+ assetID , []uint64 {0 , fullAmt }, 0 , 1 ,
4108+ )
4109+ AssertNonInteractiveRecvComplete (t .t , bob , 1 )
4110+
4111+ // Bob now has the asset with a valid proof chain.
4112+ assertNumAssetOutputs (t .t , alice , assetID , 0 )
4113+ assertNumAssetOutputs (t .t , bob , assetID , 1 )
4114+
4115+ // Create valid spend transaction Bob -> Alice.
4116+ aliceScriptKey , aliceInternalKey := DeriveKeys (t .t , alice )
4117+
4118+ var (
4119+ id [32 ]byte
4120+ chainParams = & address .RegressionNetTap
4121+ )
4122+ copy (id [:], assetID )
4123+
4124+ // Fund the virtual packet, creating valid input proofs.
4125+ fundResp := fundPacket (t , bob , tappsbt .ForInteractiveSend (
4126+ id , fullAmt , aliceScriptKey , 0 , 0 , 0 , aliceInternalKey ,
4127+ asset .V0 , chainParams ,
4128+ ))
4129+
4130+ // Sign the virtual packet.
4131+ signResp , err := bob .SignVirtualPsbt (
4132+ ctxt , & wrpc.SignVirtualPsbtRequest {
4133+ FundedPsbt : fundResp .FundedPsbt ,
4134+ },
4135+ )
4136+ require .NoError (t .t , err )
4137+
4138+ // Decode to get vPacket structure.
4139+ vPacket , err := tappsbt .Decode (signResp .SignedPsbt )
4140+ require .NoError (t .t , err )
4141+
4142+ // Prepare anchoring template and commit.
4143+ vPackets := []* tappsbt.VPacket {vPacket }
4144+ btcPacket , err := tapsend .PrepareAnchoringTemplate (vPackets )
4145+ require .NoError (t .t , err )
4146+
4147+ btcPacket , vPackets , _ , commitResp := CommitVirtualPsbts (
4148+ t .t , bob , btcPacket , vPackets , nil , - 1 ,
4149+ )
4150+
4151+ // Now we want to corrupt the input proof. First, make a copy to avoid
4152+ // modifying the original.
4153+ corruptedVPacket := vPackets [0 ].Copy ()
4154+
4155+ // Get the input proof.
4156+ require .NotEmpty (t .t , corruptedVPacket .Inputs ,
4157+ "vPacket should have inputs" )
4158+
4159+ inputProof := corruptedVPacket .Inputs [0 ].Proof
4160+ require .NotNil (t .t , inputProof , "input proof should exist" )
4161+
4162+ // Modify the block header's PrevBlock hash. This will cause
4163+ // VerifyProofIntegrity to fail during header verification, but won't
4164+ // affect validity of the BTC-level anchor transaction.
4165+ inputProof .BlockHeader .PrevBlock = chainhash.Hash {}
4166+
4167+ // Finalize and publish the anchor transaction.
4168+ btcPacket = signPacket (t .t , lndBob , btcPacket )
4169+ btcPacket = FinalizePacket (t .t , lndBob .RPC , btcPacket )
4170+ anchorTx , err := psbt .Extract (btcPacket )
4171+ require .NoError (t .t , err )
4172+
4173+ anchorTxBytes , err := fn .Serialize (anchorTx )
4174+ require .NoError (t .t , err )
4175+
4176+ _ , err = lndBob .RPC .WalletKit .PublishTransaction (
4177+ ctxt , & walletrpc.Transaction {
4178+ TxHex : anchorTxBytes ,
4179+ },
4180+ )
4181+ require .NoError (t .t , err )
4182+ t .lndHarness .Miner ().AssertNumTxsInMempool (1 )
4183+
4184+ // Attempt to publish the corrupted proof. This creates a pre-anchored
4185+ // parcel that starts at SendStateVerifyPreBroadcast. The state machine
4186+ // should detect the invalid proof and reject it with an error.
4187+ PublishAndLogTransfer (
4188+ t .t , bob , btcPacket ,
4189+ []* tappsbt.VPacket {corruptedVPacket },
4190+ nil ,
4191+ commitResp ,
4192+ withExpectedErr ("unable to verify" ),
4193+ )
4194+ }
0 commit comments