test: characterize PSBT output metadata

Document the scripts and key origins collected for common output descriptors before changing zero-input handling.
Use a valid input for ECDSA-backed outputs because their zero-input paths currently abort.
This commit is contained in:
Lőrinc
2026-07-24 13:46:15 -07:00
parent 625f951ba2
commit e24e8fa2a6
2 changed files with 95 additions and 1 deletions

View File

@@ -2,10 +2,21 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
#include <addresstype.h>
#include <key.h>
#include <psbt.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <script/signingprovider.h>
#include <script/solver.h>
#include <test/util/setup_common.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <boost/test/unit_test.hpp>
#include <test/util/setup_common.h>
#include <string>
#include <vector>
BOOST_FIXTURE_TEST_SUITE(psbt_tests, BasicTestingSetup)
@@ -216,4 +227,79 @@ BOOST_AUTO_TEST_CASE(merge_proprietary_fields)
BOOST_CHECK(output_it->value == right_prop.value);
}
struct PSBTOutputTest {
CPubKey pubkey;
FlatSigningProvider provider;
CScript script_pubkey;
explicit PSBTOutputTest(std::string descriptor)
{
CKey key{GenerateRandomKey()};
pubkey = key.GetPubKey();
provider.keys.emplace(pubkey.GetID(), key);
util::ReplaceAll(descriptor, "<KEY>", HexStr(pubkey));
std::string error;
auto descriptors{Parse(descriptor, provider, error, /*require_checksum=*/false)};
BOOST_REQUIRE_MESSAGE(!descriptors.empty(), error);
std::vector<CScript> output_scripts;
BOOST_REQUIRE(descriptors[0]->Expand(/*pos=*/0, provider, output_scripts, provider));
BOOST_REQUIRE_EQUAL(output_scripts.size(), 1);
script_pubkey = output_scripts[0];
}
PSBTOutput UpdateOutput(bool has_input) const
{
CMutableTransaction tx;
if (has_input) tx.vin.emplace_back();
tx.vout.emplace_back(0, script_pubkey);
PartiallySignedTransaction psbt{tx};
UpdatePSBTOutput(provider, psbt, 0);
return psbt.outputs[0];
}
};
BOOST_AUTO_TEST_CASE(update_psbt_output_keypaths)
{
for (bool has_input : {true}) { // TODO: Zero-input updates abort during ECDSA sighash creation.
for (const auto& descriptor : {"pkh(<KEY>)", "wpkh(<KEY>)"}) {
PSBTOutputTest test{descriptor};
auto out{test.UpdateOutput(has_input)};
BOOST_CHECK(out.hd_keypaths.contains(test.pubkey));
BOOST_CHECK(out.redeem_script.empty());
BOOST_CHECK(out.witness_script.empty());
}
}
}
BOOST_AUTO_TEST_CASE(update_psbt_output_redeem_script)
{
for (bool has_input : {true}) { // TODO: Zero-input updates abort during ECDSA sighash creation.
PSBTOutputTest test{"sh(wpkh(<KEY>))"};
auto out{test.UpdateOutput(has_input)};
BOOST_CHECK(out.redeem_script == GetScriptForDestination(WitnessV0KeyHash{test.pubkey}));
BOOST_CHECK(out.hd_keypaths.contains(test.pubkey));
}
}
BOOST_AUTO_TEST_CASE(update_psbt_output_witness_script)
{
for (bool has_input : {true}) { // TODO: Zero-input updates abort during ECDSA sighash creation.
PSBTOutputTest test{"wsh(pk(<KEY>))"};
auto out{test.UpdateOutput(has_input)};
BOOST_CHECK(out.witness_script == GetScriptForRawPubKey(test.pubkey));
BOOST_CHECK(out.hd_keypaths.contains(test.pubkey));
}
}
BOOST_AUTO_TEST_CASE(update_psbt_output_taproot)
{
for (bool has_input : {false, true}) {
PSBTOutputTest test{"rawtr(<KEY>)"};
auto out{test.UpdateOutput(has_input)};
BOOST_CHECK(out.m_tap_bip32_paths.contains(XOnlyPubKey{test.pubkey}));
BOOST_CHECK(out.hd_keypaths.empty());
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@@ -1424,6 +1424,14 @@ class PSBTTest(BitcoinTestFramework):
utxo = self.create_outpoints(self.nodes[0], outputs=[{address: 1}])[0]
self.sync_all()
self.log.info("Test descriptorprocesspsbt updates PSBT outputs")
for has_input in [True]: # TODO: Zero-input updates abort during ECDSA sighash creation.
output_psbt = self.nodes[2].createpsbt([utxo] if has_input else [], {address: 1})
processed = self.nodes[2].descriptorprocesspsbt(psbt=output_psbt, descriptors=[descriptor], finalize=False)
decoded = self.nodes[2].decodepsbt(processed["psbt"])
assert_equal(len(decoded["inputs"]), int(has_input))
assert_equal(len(decoded["outputs"][0]["bip32_derivs"]), 1)
psbt = self.nodes[2].createpsbt([utxo], {self.nodes[0].getnewaddress(): 0.99999})
decoded = self.nodes[2].decodepsbt(psbt)
test_psbt_input_keys(decoded['inputs'][0], [])