mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-14 23:37:51 +02:00
Merge bitcoin/bitcoin#35665: psbt: avoid duplicate global xpub keys when merging
6d387af562psbt: remove write-only global xpub tracking set (Thomas)3b7051c7e3test: check combinepsbt with conflicting global xpub origins (Thomas)7c632c0e2apsbt: avoid duplicate global xpub keys when merging (Thomas) Pull request description: Global xpubs are stored in a map of key origin to set of xpubs, while the serialization writes one record per xpub, keyed by the xpub. `Merge` unions the map origin-by-origin, so when the combined PSBTs provide different key origins for the same xpub, the result serializes the same `PSBT_GLOBAL_XPUB` key twice. BIP 174 declares PSBTs with duplicate keys invalid and the deserializer rejects them, so `combinepsbt` returns a PSBT that no RPC can parse again. This affects all releases since the merge loop was added in #17034 (v23.0). <details><summary>Reproduction on master</summary> The PSBTs share the unsigned transaction and xpub, and differ only in the master fingerprint of the global xpub record (`00000000` vs `11111111`): ``` $ A=cHNidP8BADwCAAAAAaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqAAAAAAD/////AQAAAAAAAAAAAAAAAABPAQQ1h88AAAAAAAAAAACHPf+BwC9SViP9H+UWfqw6VaBJ3j0xS7Qu4if/7TfVCAM5o2ATMBWX2u9B++WToCzFE9C1VSfsLfEFDi6P9JyFwgQAAAAAAAAA $ B=cHNidP8BADwCAAAAAaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqAAAAAAD/////AQAAAAAAAAAAAAAAAABPAQQ1h88AAAAAAAAAAACHPf+BwC9SViP9H+UWfqw6VaBJ3j0xS7Qu4if/7TfVCAM5o2ATMBWX2u9B++WToCzFE9C1VSfsLfEFDi6P9JyFwgQRERERAAAA $ bitcoin-cli -regtest decodepsbt "$(bitcoin-cli -regtest combinepsbt "[\"$A\",\"$B\"]")" error code: -22 error message: TX decode failed Duplicate Key, global key "01043587cf00...9c85c2" already provided: iostream error ``` </details> Deduplicate by xpub when merging, keeping the origin that is already present: BIP 174 lets the Combiner "pick arbitrarily when conflicts occur", and conflicting unknown and proprietary records are already resolved the same way. The logic is shared between `combinepsbt` and `joinpsbts` through a new `MergeGlobalXPubs` helper. The second commit adds a test that fails on master with the error above, and the last commit removes the `global_xpubs` tracking set in `Unserialize`, write-only since the generic duplicate key check introduced in #21283 (1e2d146b47) replaced the explicit one. Note: the xpub loop in `joinpsbts` currently has no observable effect, since the collected xpubs never reach the returned PSBT. My #35516 fixes that, so this PR should land first: on its own, #35516 would make the same duplicate key issue reachable through `joinpsbts`, while with the shared helper in place it never becomes reachable. I will rebase #35516 on top afterwards. ACKs for top commit: Bicaru20: tACK6d387af562. achow101: ACK6d387af562winterrdog: tACK6d387af562Tree-SHA512: e2a9e02617eeec22a9240d7cf9386ee880a5f3639b143df7de4d8ea3e7b808f8c123f0b0410ff4a22e9a564bd86b2335a5c4aa3b2281af47d111484a6f1fd108
This commit is contained in:
20
src/psbt.cpp
20
src/psbt.cpp
@@ -13,6 +13,8 @@
|
||||
#include <util/result.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using common::PSBTError;
|
||||
|
||||
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx, uint32_t version) : m_version(version)
|
||||
@@ -53,13 +55,7 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto& xpub_pair : psbt.m_xpubs) {
|
||||
if (!m_xpubs.contains(xpub_pair.first)) {
|
||||
m_xpubs[xpub_pair.first] = xpub_pair.second;
|
||||
} else {
|
||||
m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
|
||||
}
|
||||
}
|
||||
MergeGlobalXPubs(psbt);
|
||||
if (fallback_locktime == std::nullopt && psbt.fallback_locktime != std::nullopt) fallback_locktime = psbt.fallback_locktime;
|
||||
|
||||
// Set m_tx_modifiable only if either PSBT had it set
|
||||
@@ -80,6 +76,16 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
|
||||
return true;
|
||||
}
|
||||
|
||||
void PartiallySignedTransaction::MergeGlobalXPubs(const PartiallySignedTransaction& psbt)
|
||||
{
|
||||
for (const auto& [origin, xpubs] : psbt.m_xpubs) {
|
||||
for (const CExtPubKey& xpub : xpubs) {
|
||||
const bool known{std::ranges::any_of(m_xpubs, [&](const auto& entry) { return entry.second.contains(xpub); })};
|
||||
if (!known) m_xpubs[origin].insert(xpub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint32_t> PartiallySignedTransaction::ComputeTimeLock() const
|
||||
{
|
||||
if (GetVersion() >= 2) {
|
||||
|
||||
Reference in New Issue
Block a user