psbt: preserve proprietary fields when combining PSBTs
What changed, and why it matters
This commit fixes a bug in Bitcoin Core's handling of PSBTs (Partially Signed Bitcoin Transactions). When two PSBTs were combined, the software correctly kept 'unknown' extra data but accidentally discarded 'proprietary' custom data fields added by wallets or tools. The patch makes sure proprietary fields are preserved during merging, preventing potential loss of transaction metadata that some wallets rely on.
Treat as a routine bugfix. Review whether any wallet workflows depend on proprietary PSBT fields and add regression tests covering global/input/output proprietary field preservation during combinepsbt. No urgent security response is indicated by the diff alone.
Security signals we found
Data loss bug in PSBT merge operation
Proprietary PSBT fields silently dropped during combinepsbt
No input validation or memory safety changes
Behavioral fix only; no cryptographic or consensus code touched
Evidence from the diff
The Merge() methods for PartiallySignedTransaction, PSBTInput, and PSBTOutput already unioned the unknown map but omitted m_proprietary. The patch adds m_proprietary.insert(...) in all three merge paths so proprietary key-value records at global, input, and output levels are preserved when PSBTs are combined. This is a data-fidelity fix with no consensus or validation logic changes.
Changed components
src/psbt.cppPartiallySignedTransaction::Merge()PSBTInput::Merge()PSBTOutput::Merge()RPC combinepsbt / PSBT mergingInspect captured patch +3 / −0
diff --git a/src/psbt.cpp b/src/psbt.cpp
index f01d915f..8f2e9ab1 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -79,6 +79,7 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
m_tx_modifiable = final_modifiable;
}
+ m_proprietary.insert(psbt.m_proprietary.begin(), psbt.m_proprietary.end());
unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
return true;
@@ -436,6 +437,7 @@ bool PSBTInput::Merge(const PSBTInput& input)
hash160_preimages.insert(input.hash160_preimages.begin(), input.hash160_preimages.end());
hash256_preimages.insert(input.hash256_preimages.begin(), input.hash256_preimages.end());
hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end());
+ m_proprietary.insert(input.m_proprietary.begin(), input.m_proprietary.end());
unknown.insert(input.unknown.begin(), input.unknown.end());
m_tap_script_sigs.insert(input.m_tap_script_sigs.begin(), input.m_tap_script_sigs.end());
m_tap_scripts.insert(input.m_tap_scripts.begin(), input.m_tap_scripts.end());
@@ -534,6 +536,7 @@ bool PSBTOutput::IsNull() const
bool PSBTOutput::Merge(const PSBTOutput& output)
{
hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end());
+ m_proprietary.insert(output.m_proprietary.begin(), output.m_proprietary.end());
unknown.insert(output.unknown.begin(), output.unknown.end());
m_tap_bip32_paths.insert(output.m_tap_bip32_paths.begin(), output.m_tap_bip32_paths.end());
Why this scored 28/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.