From 5bd0579c09c0998cc1c35472a43ce5fb5fe621e6 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 23 Mar 2026 13:47:16 -0700 Subject: [PATCH] test: Tests for PSBT AddInput and AddOutput --- src/test/psbt_tests.cpp | 126 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/src/test/psbt_tests.cpp b/src/test/psbt_tests.cpp index 4018735c625..74690d32f1b 100644 --- a/src/test/psbt_tests.cpp +++ b/src/test/psbt_tests.cpp @@ -41,4 +41,130 @@ BOOST_AUTO_TEST_CASE(psbt2_timelock_test) CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA==", std::nullopt); } +BOOST_AUTO_TEST_CASE(psbt2_addinput) +{ + FastRandomContext rng(/*fDeterministic=*/true); + + CMutableTransaction mtx; + PartiallySignedTransaction psbt(mtx, /*version=*/2); + psbt.m_tx_modifiable.emplace(); + psbt.m_tx_modifiable->set(0, true); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 0); + + // Same PSBT version is required + uint256 txid; + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin_v0(/*psbt_version=*/0, Txid::FromUint256(txid), /*prev_out=*/0); + BOOST_CHECK(!psbt.AddInput(psbtin_v0)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 0); + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin(/*psbt_version=*/2, Txid::FromUint256(txid), /*prev_out=*/0); + BOOST_CHECK(psbt.AddInput(psbtin)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 1); + + // Duplicates are not allowed + BOOST_CHECK(!psbt.AddInput(psbtin)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 1); + + // Input with a unique txid is allowed + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin2(/*psbt_version=*/2, Txid::FromUint256(txid), /*prev_out=*/0); + BOOST_CHECK(psbt.AddInput(psbtin2)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 2); + + // Disabling inputs modifiable flag prevents adding new inputs + psbt.m_tx_modifiable->set(0, false); + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin3(/*psbt_version=*/2, Txid::FromUint256(txid), /*prev_out=*/0); + BOOST_CHECK(!psbt.AddInput(psbtin3)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 2); + psbt.m_tx_modifiable->set(0, true); + + // Make sure that timelock compatibility checks are working + // No previous required timelocks, new input with both height and time timelocks is allowed + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin4(/*psbt_version=*/2, Txid::FromUint256(txid), /*prev_out=*/0); + psbtin4.time_locktime = LOCKTIME_THRESHOLD; + psbtin4.height_locktime = 100; + BOOST_CHECK(psbt.AddInput(psbtin4)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 3); + + // Input with only a time timelock is allowed + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin5(/*psbt_version=*/2, Txid::FromUint256(txid), /*prev_out=*/0); + psbtin5.time_locktime = LOCKTIME_THRESHOLD + 1; + BOOST_CHECK(psbt.AddInput(psbtin5)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 4); + + // Input with only a height timelock is not allowed because of previous + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin6(/*psbt_version=*/2, Txid::FromUint256(txid), /*prev_out=*/0); + psbtin6.height_locktime = 100; + BOOST_CHECK(!psbt.AddInput(psbtin6)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 4); + + // Adding an input that already has a signature is allowed + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin7(/*psbt_version=*/2, Txid::FromUint256(txid), /*prev_out=*/0); + psbtin7.final_script_sig << OP_1; + BOOST_CHECK(psbt.AddInput(psbtin7)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 5); + + // Same thing, but with other things that have signatures + psbtin7.final_script_sig.clear(); + psbtin7.final_script_witness.stack.emplace_back(); + BOOST_CHECK(!psbt.AddInput(psbtin7)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 5); + psbtin7.final_script_witness.SetNull(); + psbtin7.partial_sigs.emplace(); + BOOST_CHECK(!psbt.AddInput(psbtin7)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 5); + psbtin7.partial_sigs.clear(); + psbtin7.m_tap_key_sig.push_back(0); + BOOST_CHECK(!psbt.AddInput(psbtin7)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 5); + psbtin7.m_tap_key_sig.clear(); + psbtin7.m_tap_script_sigs.emplace(); + BOOST_CHECK(!psbt.AddInput(psbtin7)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 5); + psbtin7.m_tap_script_sigs.clear(); + psbtin7.m_musig2_partial_sigs.emplace(); + BOOST_CHECK(!psbt.AddInput(psbtin7)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 5); + + // Adding an input that changes the timelock is no longer allowed + rng.fillrand(MakeWritableByteSpan(txid)); + PSBTInput psbtin8(/*psbt_version=*/2, Txid::FromUint256(txid), /*prev_out=*/0); + psbtin8.time_locktime = LOCKTIME_THRESHOLD + 2; + BOOST_CHECK(!psbt.AddInput(psbtin8)); + BOOST_CHECK_EQUAL(psbt.inputs.size(), 5); +} + +BOOST_AUTO_TEST_CASE(psbt2_addoutput) +{ + CMutableTransaction mtx; + PartiallySignedTransaction psbt(mtx, /*version=*/2); + psbt.m_tx_modifiable.emplace(); + psbt.m_tx_modifiable->set(1, true); + BOOST_CHECK_EQUAL(psbt.outputs.size(), 0); + + // Same PSBT version is required + PSBTOutput psbtout_v0(/*psbt_version=*/0, /*amount=*/1, CScript()); + BOOST_CHECK(!psbt.AddOutput(psbtout_v0)); + BOOST_CHECK_EQUAL(psbt.outputs.size(), 0); + PSBTOutput psbtout(/*psbt_version=*/2, /*amount=*/1, CScript()); + BOOST_CHECK(psbt.AddOutput(psbtout)); + BOOST_CHECK_EQUAL(psbt.outputs.size(), 1); + + // Disabling outputs modifiable flag prevents adding new outputs + psbt.m_tx_modifiable->set(1, false); + PSBTOutput psbtout2(/*psbt_version=*/2, /*amount=*/1, CScript()); + BOOST_CHECK(!psbt.AddOutput(psbtout2)); + BOOST_CHECK_EQUAL(psbt.outputs.size(), 1); + psbt.m_tx_modifiable->set(1, true); + PSBTOutput psbtout3(/*psbt_version=*/2, /*amount=*/1, CScript()); + BOOST_CHECK(psbt.AddOutput(psbtout3)); + BOOST_CHECK_EQUAL(psbt.outputs.size(), 2); +} + BOOST_AUTO_TEST_SUITE_END()