Have PSBTInput and PSBTOutput know the PSBT's version
What changed, and why it matters
This commit is a small internal refactor in Bitcoin Core's Partially Signed Bitcoin Transaction (PSBT) handling. It makes each PSBT input and output aware of which PSBT version it belongs to, currently hard-coded to version 0. The change does not fix a known bug or vulnerability by itself; it appears to lay groundwork for future PSBT version support. There is no evidence in the commit or supplied references that this is a security patch.
No immediate action required. Treat as routine refactor. If auditing, verify that follow-up commits use the stored version to enforce version-specific PSBT rules rather than leaving it unused.
Security signals we found
No security-relevant behavioral change visible in diff
Constructor asserts version == 0, which could abort on malformed future-version data but does not currently change parsing
Refactor touches PSBT deserialization and RPC PSBT creation paths
Evidence from the diff
The change adds a private m_psbt_version field to PSBTInput and PSBTOutput, replaces their default constructors with explicit constructors taking a version, and updates call sites to pass 0 or GetVersion(). PartiallySignedTransaction::GetVersion() is unchanged and still returns 0 when m_version is unset. Constructors assert the version is 0. The diff is purely structural: no serialization logic, validation rules, or memory handling were altered.
Changed components
src/psbt.cppsrc/psbt.hsrc/rpc/rawtransaction.cppsrc/test/fuzz/deserialize.cppInspect captured patch +33 / −13
diff --git a/src/psbt.cpp b/src/psbt.cpp
index 3086ae77..416eae11 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -15,8 +15,8 @@ using common::PSBTError;
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
{
- inputs.resize(tx.vin.size());
- outputs.resize(tx.vout.size());
+ inputs.resize(tx.vin.size(), PSBTInput(GetVersion()));
+ outputs.resize(tx.vout.size(), PSBTOutput(GetVersion()));
}
bool PartiallySignedTransaction::IsNull() const
diff --git a/src/psbt.h b/src/psbt.h
index f6bf144a..4272cd3a 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -263,6 +263,9 @@ static inline void ExpectedKeySize(const std::string& key_name, const std::vecto
/** A structure for PSBTs which contain per-input information */
class PSBTInput
{
+private:
+ uint32_t m_psbt_version;
+
public:
CTransactionRef non_witness_utxo;
CTxOut witness_utxo;
@@ -300,7 +303,12 @@ public:
void FillSignatureData(SignatureData& sigdata) const;
void FromSignatureData(const SignatureData& sigdata);
void Merge(const PSBTInput& input);
- PSBTInput() = default;
+ uint32_t GetVersion() const { return m_psbt_version; }
+ explicit PSBTInput(uint32_t psbt_version)
+ : m_psbt_version(psbt_version)
+ {
+ assert(m_psbt_version == 0);
+ }
template <typename Stream>
inline void Serialize(Stream& s) const {
@@ -794,6 +802,9 @@ public:
/** A structure for PSBTs which contains per output information */
class PSBTOutput
{
+private:
+ uint32_t m_psbt_version;
+
public:
CScript redeem_script;
CScript witness_script;
@@ -809,7 +820,12 @@ public:
void FillSignatureData(SignatureData& sigdata) const;
void FromSignatureData(const SignatureData& sigdata);
void Merge(const PSBTOutput& output);
- PSBTOutput() = default;
+ uint32_t GetVersion() const { return m_psbt_version; }
+ explicit PSBTOutput(uint32_t psbt_version)
+ : m_psbt_version(psbt_version)
+ {
+ assert(m_psbt_version == 0);
+ }
template <typename Stream>
inline void Serialize(Stream& s) const {
@@ -1031,6 +1047,9 @@ public:
/** A version of CTransaction with the PSBT format*/
class PartiallySignedTransaction
{
+private:
+ std::optional<uint32_t> m_version;
+
public:
std::optional<CMutableTransaction> tx;
// We use a vector of CExtPubKey in the event that there happens to be the same KeyOriginInfos for different CExtPubKeys
@@ -1039,7 +1058,6 @@ public:
std::vector<PSBTInput> inputs;
std::vector<PSBTOutput> outputs;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
- std::optional<uint32_t> m_version;
std::set<PSBTProprietary> m_proprietary;
bool IsNull() const;
@@ -1241,10 +1259,12 @@ public:
throw std::ios_base::failure("No unsigned transaction was provided");
}
+ const uint32_t psbt_ver = GetVersion();
+
// Read input data
unsigned int i = 0;
while (!s.empty() && i < tx->vin.size()) {
- PSBTInput input;
+ PSBTInput input(psbt_ver);
s >> input;
inputs.push_back(input);
@@ -1267,7 +1287,7 @@ public:
// Read output data
i = 0;
while (!s.empty() && i < tx->vout.size()) {
- PSBTOutput output;
+ PSBTOutput output(psbt_ver);
s >> output;
outputs.push_back(output);
++i;
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 0f3cebb5..67067110 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1652,10 +1652,10 @@ static RPCMethod createpsbt()
PartiallySignedTransaction psbtx;
psbtx.tx = rawTx;
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
- psbtx.inputs.emplace_back();
+ psbtx.inputs.emplace_back(0);
}
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
- psbtx.outputs.emplace_back();
+ psbtx.outputs.emplace_back(0);
}
// Serialize the PSBT
@@ -1720,10 +1720,10 @@ static RPCMethod converttopsbt()
PartiallySignedTransaction psbtx;
psbtx.tx = tx;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
- psbtx.inputs.emplace_back();
+ psbtx.inputs.emplace_back(0);
}
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
- psbtx.outputs.emplace_back();
+ psbtx.outputs.emplace_back(0);
}
// Serialize the PSBT
diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp
index 1329f471..b3f35baa 100644
--- a/src/test/fuzz/deserialize.cpp
+++ b/src/test/fuzz/deserialize.cpp
@@ -192,11 +192,11 @@ FUZZ_TARGET_DESERIALIZE(prefilled_transaction_deserialize, {
DeserializeFromFuzzingInput(buffer, prefilled_transaction);
})
FUZZ_TARGET_DESERIALIZE(psbt_input_deserialize, {
- PSBTInput psbt_input;
+ PSBTInput psbt_input(0);
DeserializeFromFuzzingInput(buffer, psbt_input);
})
FUZZ_TARGET_DESERIALIZE(psbt_output_deserialize, {
- PSBTOutput psbt_output;
+ PSBTOutput psbt_output(0);
DeserializeFromFuzzingInput(buffer, psbt_output);
})
FUZZ_TARGET_DESERIALIZE(block_deserialize, {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.