Skip to content

Commit 26ce159

Browse files
committed
itest: add test for invalid input proof rejection
Add a new icase, 'testPsbtInvalidInputProofRejection', that tests that pre-anchored send packages with corrupted input proofs are rejected as expected during the SendStateVerifyPreBroadcast state.
1 parent c4ddbf6 commit 26ce159

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

itest/psbt_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

itest/test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ var allTestCases = []*testCase{
241241
name: "psbt relative lock time send with proof failure",
242242
test: testPsbtRelativeLockTimeSendProofFail,
243243
},
244+
{
245+
name: "psbt invalid input proof rejection",
246+
test: testPsbtInvalidInputProofRejection,
247+
},
244248
{
245249
name: "psbt normal interactive split send",
246250
test: testPsbtNormalInteractiveSplitSend,

0 commit comments

Comments
 (0)