mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-01 00:34:01 +02:00
Merge bitcoin/bitcoin#21283: Implement BIP 370 PSBTv2
9fa4076b20test: Test merging implicit PSBTv0 with explicit PSBTv0 (w0xlt)1660c18232doc: Release notes for psbtv2 (Ava Chow)470e52a5f8fuzz: Enforce additional version invariants in PSBT fuzzer (Antoine Poinsot)5bd0579c09test: Tests for PSBT AddInput and AddOutput (Ava Chow)b8b6e7f0c2tests: Add PSBT unit test for ComputeTimeLock (Ava Chow)0bc1c2e508tests: Add test vectors from BIP 370 (Ava Chow)e0e4dbdeb5psbt: Change default psbt version to 2 (Ava Chow)bcc1dca77bAdd psbt_version to PSBT RPCs and default to v2 (Ava Chow)ab38c30195Implement PSBTv2 field merging (Ava Chow)93e339e29fImplement PSBTv2 AddInput and AddOutput (Ava Chow)b39c86ae60Allow specifying PSBT version in constructor (Ava Chow)dcc9a3c8dfImplement PSBTv2 in decodepsbt (Ava Chow)5770dbd39fAdd PSBT::ComputeLockTime() (Ava Chow)863cf47b33Update test_framework/psbt.py for PSBTv2 (Ava Chow)925161eaf0Implement PSBTv2 fields de/ser (Ava Chow)d9cf658ee0Restrict joinpsbts to PSBTv0 only (Ava Chow)3da0e16012Replace PSBT.tx with PSBT::GetUnsignedTx and PSBT::GetUniqueID (Ava Chow)c568624ff2psbt: Return std::optional from PrecomputePSBTData (Ava Chow)092de4f1f6Replace PSBT::GetInputUTXO with PSBTInput::GetUTXO (Ava Chow)82c9fe3179psbt: Use PSBTInput and PSBTOutput fields instead of accessing global tx (Ava Chow)95897507e9psbt: AddInput and AddOutput should take only PSBTInput and PSBTOutput (Ava Chow)1b7d323a72Add PSBTInput::GetOutPoint (Ava Chow)543d3e1cdcpsbt: add PSBTv2 global tx fields (Ava Chow)c01c7f068cpsbt: Remove default constructor (Ava Chow)9671aa08c2psbt: add tx input and output fields in PSBTInput and PSBTOutput (Ava Chow)990b084f11Have PSBTInput and PSBTOutput know the PSBT's version (Ava Chow)7eacc21ff6psbt: make PSBT structs into classes (Ava Chow)f926c326bbgui: Store PSBT in std::optional in PSBTOperationsDialog (Ava Chow)1e2d146b47psbt: Refactor duplicate key lookup and size checks (Ava Chow)88384180d3test: PSBTs should roundtrip through RPCs that do nothing (Ava Chow)001877500dtest: construct psbt with unknown field programmatically (David Gumberg)0cb884e6dfpsbt: Fill hash preimages and taproot builder from SignatureData (Ava Chow) Pull request description: BIP 370 PSBTv2 introduces several new fields and different invariants for PSBT. This PR implements those new fields and restructures the PSBT implementation to match PSBTv2 but still remain compatible with PSBTv0. ACKs for top commit: nervana21: re-ACK9fa4076b20theStack: re-ACK9fa4076b20w0xlt: ACK9fa4076b20Tree-SHA512: ab0a5ada4fa5fca27ba9ec9c291a44b30e69d6db11971957572d86c58c71c4caa4557dc25f403e1170ba4fac751306d074cc582defefc6e2fdd37be51c3d9dd0
This commit is contained in:
@@ -81,6 +81,7 @@ add_executable(test_bitcoin
|
||||
pow_tests.cpp
|
||||
prevector_tests.cpp
|
||||
private_broadcast_tests.cpp
|
||||
psbt_tests.cpp
|
||||
raii_event_tests.cpp
|
||||
random_tests.cpp
|
||||
rbf_tests.cpp
|
||||
|
||||
@@ -90,8 +90,5 @@ FUZZ_TARGET(psbt_base64_decode)
|
||||
{
|
||||
const std::string random_string{buffer.begin(), buffer.end()};
|
||||
|
||||
PartiallySignedTransaction psbt;
|
||||
std::string error;
|
||||
const bool ok{DecodeBase64PSBT(psbt, random_string, error)};
|
||||
assert(ok == error.empty());
|
||||
util::Result<PartiallySignedTransaction> psbt = DecodeBase64PSBT(random_string);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,19 @@ void DeserializeFromFuzzingInput(FuzzBufferType buffer, T&& obj)
|
||||
assert(buffer.empty() || !Serialize(obj).empty());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T DeserializeConstructFromFuzzingInput(FuzzBufferType buffer)
|
||||
{
|
||||
try {
|
||||
SpanReader reader{buffer};
|
||||
T obj(deserialize, reader);
|
||||
assert(buffer.empty() || !Serialize(obj).empty());
|
||||
return obj;
|
||||
} catch (const std::ios_base::failure&) {
|
||||
throw invalid_fuzzing_input_exception();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename P>
|
||||
void AssertEqualAfterSerializeDeserialize(const T& obj, const P& params)
|
||||
{
|
||||
@@ -184,19 +197,18 @@ FUZZ_TARGET_DESERIALIZE(key_origin_info_deserialize, {
|
||||
AssertEqualAfterSerializeDeserialize(key_origin_info);
|
||||
})
|
||||
FUZZ_TARGET_DESERIALIZE(partially_signed_transaction_deserialize, {
|
||||
PartiallySignedTransaction partially_signed_transaction;
|
||||
DeserializeFromFuzzingInput(buffer, partially_signed_transaction);
|
||||
PartiallySignedTransaction partially_signed_transaction = DeserializeConstructFromFuzzingInput<PartiallySignedTransaction>(buffer);
|
||||
})
|
||||
FUZZ_TARGET_DESERIALIZE(prefilled_transaction_deserialize, {
|
||||
PrefilledTransaction prefilled_transaction;
|
||||
DeserializeFromFuzzingInput(buffer, prefilled_transaction);
|
||||
})
|
||||
FUZZ_TARGET_DESERIALIZE(psbt_input_deserialize, {
|
||||
PSBTInput psbt_input;
|
||||
PSBTInput psbt_input(0, Txid{}, 0);
|
||||
DeserializeFromFuzzingInput(buffer, psbt_input);
|
||||
})
|
||||
FUZZ_TARGET_DESERIALIZE(psbt_output_deserialize, {
|
||||
PSBTOutput psbt_output;
|
||||
PSBTOutput psbt_output(0, 0, CScript());
|
||||
DeserializeFromFuzzingInput(buffer, psbt_output);
|
||||
})
|
||||
FUZZ_TARGET_DESERIALIZE(block_deserialize, {
|
||||
|
||||
@@ -25,19 +25,23 @@ FUZZ_TARGET(psbt)
|
||||
{
|
||||
SeedRandomStateForTest(SeedRand::ZEROS);
|
||||
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||
PartiallySignedTransaction psbt_mut;
|
||||
std::string error;
|
||||
auto str = fuzzed_data_provider.ConsumeRandomLengthString();
|
||||
if (!DecodeRawPSBT(psbt_mut, MakeByteSpan(str), error)) {
|
||||
util::Result<PartiallySignedTransaction> psbt_res = DecodeRawPSBT(MakeByteSpan(str));
|
||||
if (!psbt_res) {
|
||||
return;
|
||||
}
|
||||
PartiallySignedTransaction psbt_mut = *psbt_res;
|
||||
const PartiallySignedTransaction psbt = psbt_mut;
|
||||
|
||||
// We are on purpose not forward compatible, and version 1 is disabled.
|
||||
const auto psbt_version{psbt.GetVersion()};
|
||||
Assert(psbt_version == 0 || psbt_version == 2);
|
||||
|
||||
// A PSBT must roundtrip.
|
||||
PartiallySignedTransaction psbt_roundtrip;
|
||||
std::vector<uint8_t> psbt_ser;
|
||||
VectorWriter{psbt_ser, 0, psbt};
|
||||
SpanReader{psbt_ser} >> psbt_roundtrip;
|
||||
SpanReader reader{psbt_ser};
|
||||
PartiallySignedTransaction psbt_roundtrip(deserialize, reader);
|
||||
|
||||
// And be stable across roundtrips.
|
||||
std::vector<uint8_t> roundtrip_ser;
|
||||
@@ -51,29 +55,71 @@ FUZZ_TARGET(psbt)
|
||||
}
|
||||
|
||||
(void)psbt.IsNull();
|
||||
|
||||
std::optional<CMutableTransaction> tx = psbt.tx;
|
||||
if (tx) {
|
||||
const CMutableTransaction& mtx = *tx;
|
||||
const PartiallySignedTransaction psbt_from_tx{mtx};
|
||||
}
|
||||
(void)psbt.GetUnsignedTx();
|
||||
|
||||
for (const PSBTInput& input : psbt.inputs) {
|
||||
(void)PSBTInputSigned(input);
|
||||
(void)input.IsNull();
|
||||
PSBTInput input_mod = input;
|
||||
CTxOut tx_out;
|
||||
if (input.GetUTXO(tx_out)) {
|
||||
(void)tx_out.IsNull();
|
||||
(void)tx_out.ToString();
|
||||
}
|
||||
// A PSBT input must roundtrip to signature data.
|
||||
PSBTInput input_fill{psbt_version, input_mod.prev_txid, input_mod.prev_out, input_mod.sequence};
|
||||
SignatureData sig_data;
|
||||
input_mod.FillSignatureData(sig_data);
|
||||
input_fill.FromSignatureData(sig_data);
|
||||
|
||||
// Only final_script_sig and final_script_witness are filled when sigdata is complete
|
||||
if (sig_data.complete) {
|
||||
Assert(input_mod.final_script_sig == input_fill.final_script_sig);
|
||||
Assert(input_mod.final_script_witness == input_fill.final_script_witness);
|
||||
} else {
|
||||
// UTXOs don't go into SignatureData
|
||||
input_mod.non_witness_utxo.reset();
|
||||
input_mod.witness_utxo.SetNull();
|
||||
// Sighash type doesn't go into SignatureData
|
||||
input_mod.sighash_type.reset();
|
||||
// Timelocks don't go into SignatureData
|
||||
input_mod.time_locktime.reset();
|
||||
input_mod.height_locktime.reset();
|
||||
// Proprietary fields are not included in SignatureData
|
||||
input_mod.m_proprietary.clear();
|
||||
// Unknown fields are not included in SignatureData
|
||||
input_mod.unknown.clear();
|
||||
|
||||
Assert(input_mod == input_fill);
|
||||
}
|
||||
}
|
||||
(void)CountPSBTUnsignedInputs(psbt);
|
||||
|
||||
for (const PSBTOutput& output : psbt.outputs) {
|
||||
(void)output.IsNull();
|
||||
}
|
||||
PSBTOutput output_mod = output;
|
||||
// A PSBT output must roundtrip to signature data.
|
||||
PSBTOutput output_fill{psbt_version, output_mod.amount, output_mod.script};
|
||||
SignatureData sig_data;
|
||||
output_mod.FillSignatureData(sig_data);
|
||||
output_fill.FromSignatureData(sig_data);
|
||||
|
||||
for (size_t i = 0; i < psbt.tx->vin.size(); ++i) {
|
||||
CTxOut tx_out;
|
||||
if (psbt.GetInputUTXO(tx_out, i)) {
|
||||
(void)tx_out.IsNull();
|
||||
(void)tx_out.ToString();
|
||||
// FillSignatureData will not fill tap tree or internal key if the tree is empty or
|
||||
// the key is not fully valid. These need to be cleared before checking for equivalence
|
||||
if (output_mod.m_tap_tree.empty() || !output_mod.m_tap_internal_key.IsFullyValid()) {
|
||||
output_mod.m_tap_tree.clear();
|
||||
std::fill(output_mod.m_tap_internal_key.begin(), output_mod.m_tap_internal_key.end(), 0);
|
||||
}
|
||||
// Sort m_tap_tree to ensure the vectors match
|
||||
std::sort(output_mod.m_tap_tree.begin(), output_mod.m_tap_tree.end());
|
||||
std::sort(output_fill.m_tap_tree.begin(), output_fill.m_tap_tree.end());
|
||||
// Proprietary fields are not included in SignatureData
|
||||
output_mod.m_proprietary.clear();
|
||||
// Unknown fields are not included in SignatureData
|
||||
output_mod.unknown.clear();
|
||||
|
||||
Assert(output_mod.m_tap_internal_key == output_fill.m_tap_internal_key);
|
||||
Assert(output_mod == output_fill);
|
||||
}
|
||||
|
||||
psbt_mut = psbt;
|
||||
@@ -85,21 +131,26 @@ FUZZ_TARGET(psbt)
|
||||
const PartiallySignedTransaction psbt_from_tx{result};
|
||||
}
|
||||
|
||||
PartiallySignedTransaction psbt_merge;
|
||||
PartiallySignedTransaction psbt_merge = psbt;
|
||||
str = fuzzed_data_provider.ConsumeRandomLengthString();
|
||||
if (!DecodeRawPSBT(psbt_merge, MakeByteSpan(str), error)) {
|
||||
psbt_merge = psbt;
|
||||
util::Result<PartiallySignedTransaction> psbt_merge_res = DecodeRawPSBT(MakeByteSpan(str));
|
||||
if (psbt_merge_res) {
|
||||
psbt_merge = *psbt_merge_res;
|
||||
}
|
||||
psbt_mut = psbt;
|
||||
(void)psbt_mut.Merge(psbt_merge);
|
||||
psbt_mut = psbt;
|
||||
(void)CombinePSBTs(psbt_mut, {psbt_mut, psbt_merge});
|
||||
psbt_mut = psbt;
|
||||
for (unsigned int i = 0; i < psbt_merge.tx->vin.size(); ++i) {
|
||||
(void)psbt_mut.AddInput(psbt_merge.tx->vin[i], psbt_merge.inputs[i]);
|
||||
std::optional<PartiallySignedTransaction> comb_res = CombinePSBTs({psbt_mut, psbt_merge});
|
||||
if (comb_res) {
|
||||
psbt_mut = *comb_res;
|
||||
}
|
||||
for (unsigned int i = 0; i < psbt_merge.tx->vout.size(); ++i) {
|
||||
Assert(psbt_mut.AddOutput(psbt_merge.tx->vout[i], psbt_merge.outputs[i]));
|
||||
for (const auto& psbt_in : psbt_merge.inputs) {
|
||||
(void)psbt_mut.AddInput(psbt_in);
|
||||
}
|
||||
for (const auto& psbt_out : psbt_merge.outputs) {
|
||||
(void)psbt_mut.AddOutput(psbt_out);
|
||||
}
|
||||
psbt_mut.unknown.insert(psbt_merge.unknown.begin(), psbt_merge.unknown.end());
|
||||
|
||||
RemoveUnnecessaryTransactions(psbt_mut);
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider, b
|
||||
},
|
||||
[&] {
|
||||
// base64 encoded psbt
|
||||
std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider);
|
||||
std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializableConstructor<PartiallySignedTransaction>(fuzzed_data_provider);
|
||||
if (!opt_psbt) {
|
||||
good_data = false;
|
||||
return;
|
||||
|
||||
@@ -126,6 +126,19 @@ template <typename T>
|
||||
return obj;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] inline std::optional<T> ConsumeDeserializableConstructor(FuzzedDataProvider& fuzzed_data_provider, const std::optional<size_t>& max_length = std::nullopt) noexcept
|
||||
{
|
||||
const std::vector<uint8_t> buffer = ConsumeRandomLengthByteVector(fuzzed_data_provider, max_length);
|
||||
SpanReader ds{buffer};
|
||||
try {
|
||||
T obj(deserialize, ds);
|
||||
return obj;
|
||||
} catch (const std::ios_base::failure&) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename WeakEnumType, size_t size>
|
||||
[[nodiscard]] WeakEnumType ConsumeWeakEnum(FuzzedDataProvider& fuzzed_data_provider, const WeakEnumType (&all_types)[size]) noexcept
|
||||
{
|
||||
|
||||
170
src/test/psbt_tests.cpp
Normal file
170
src/test/psbt_tests.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) 2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <psbt.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <test/util/setup_common.h>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(psbt_tests, BasicTestingSetup)
|
||||
|
||||
void CheckTimeLock(const std::string& base64_psbt, std::optional<uint32_t> timelock)
|
||||
{
|
||||
util::Result<PartiallySignedTransaction> psbt = DecodeBase64PSBT(base64_psbt);
|
||||
BOOST_CHECK(psbt);
|
||||
|
||||
std::optional<uint32_t> computed_timelock = psbt->ComputeTimeLock();
|
||||
std::optional<CMutableTransaction> tx = psbt->GetUnsignedTx();
|
||||
if (timelock) {
|
||||
BOOST_CHECK(computed_timelock);
|
||||
BOOST_CHECK_EQUAL(*computed_timelock, *timelock);
|
||||
BOOST_CHECK(tx);
|
||||
BOOST_CHECK_EQUAL(tx->nLockTime, *timelock);
|
||||
} else {
|
||||
BOOST_CHECK(!computed_timelock);
|
||||
BOOST_CHECK(!tx);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(psbt2_timelock_test)
|
||||
{
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", 0);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 0);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 10000);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAESBCgjAAAAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA==", 10000);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 10000);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiARIEKCMAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 10000);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 1657048460);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 1657048460);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAREEjI3EYgABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 1657048460);
|
||||
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()
|
||||
Reference in New Issue
Block a user