New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BugFix] Make PSBT serializations 174 compliant by excluding empty taptree from serialization #25856
[BugFix] Make PSBT serializations 174 compliant by excluding empty taptree from serialization #25856
Conversation
…ptree from serialization
In what situation are we creating empty output taprtrees? It seems like the only way this can happen is if a PSBT created by something else is being processed. In that case, we should reject the empty taptree during parsing rather than accepting the ostensibly bad PSBT and silently dropping the bad field. The general principle for PSBT is if it came in or if we filled the field internally, then it should be serialized. So changing the serialization in this way is incorrect IMO. Rather we should not accept empty taptrees and make it impossible to add one to the psbt in our own processing. |
Here is a script you can run to generate the bad behavior. It is not using any external tools. I am indifferent on if the empty TapTree is not serialized, or if it is an Option null. ADDR=$(./bitcoin-cli -signet getnewaddress "" bech32m)
TXID=$(./bitcoin-cli -signet send "{\"$ADDR\":20}" | jq '.txid' | xargs echo)
VOUT=$(./bitcoin-cli -signet gettransaction $TXID true true | jq "(.decoded.vout | map(select(.scriptPubKey.address == \"$ADDR\")))[].n")
INPUT="[{\"txid\":\"$TXID\",\"vout\":$VOUT}]"
ADDR2=$(./bitcoin-cli -signet getnewaddress "" bech32m)
PSBT=$(./bitcoin-cli -signet walletcreatefundedpsbt "$INPUT" "[{\"$ADDR2\": 0.1}]" | jq ".psbt" | xargs echo)
echo "PSBT IS: " $PSBT on master:
with this patch
decoded master: {
"tx": {
"txid": "aae4ec9b7a539b7ac60ad39dda5c3a537aaf5c2b42f744343f1c110af156d0fa",
"hash": "aae4ec9b7a539b7ac60ad39dda5c3a537aaf5c2b42f744343f1c110af156d0fa",
"version": 2,
"size": 137,
"vsize": 137,
"weight": 548,
"locktime": 0,
"vin": [
{
"txid": "a8858f4799f616ad21d36ecd9ab7312bffe9f412b6bdf506a9c8d01b4f9b5faa",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967293
}
],
"vout": [
{
"value": 0.10000000,
"n": 0,
"scriptPubKey": {
"asm": "1 0be6e506520fd628a3456c6a8f0d6ef47d36b2aafbcbc7e7a3afcb767bc6a8f9",
"desc": "rawtr(0be6e506520fd628a3456c6a8f0d6ef47d36b2aafbcbc7e7a3afcb767bc6a8f9)#sfw45af0",
"hex": "51200be6e506520fd628a3456c6a8f0d6ef47d36b2aafbcbc7e7a3afcb767bc6a8f9",
"address": "tb1pp0nw2pjjpltz3g69d34g7rtw737ndv42l09u0ear4l9hv77x4rusu9dad7",
"type": "witness_v1_taproot"
}
},
{
"value": 19.89998460,
"n": 1,
"scriptPubKey": {
"asm": "1 ce70ca349ec4c6c00e5e0af80235ffaf33c9a0e513b82e2afac40d4023c2a535",
"desc": "rawtr(ce70ca349ec4c6c00e5e0af80235ffaf33c9a0e513b82e2afac40d4023c2a535)#sydncjf5",
"hex": "5120ce70ca349ec4c6c00e5e0af80235ffaf33c9a0e513b82e2afac40d4023c2a535",
"address": "tb1peecv5dy7cnrvqrj7ptuqyd0l4ueung89zwuzu2h6csx5qg7z556sjz72rf",
"type": "witness_v1_taproot"
}
}
]
},
"global_xpubs": [
],
"psbt_version": 0,
"proprietary": [
],
"unknown": {
},
"inputs": [
{
"witness_utxo": {
"amount": 20.00000000,
"scriptPubKey": {
"asm": "1 37c2a4efc2200a9cd092a0061b027da3c6772ae862d987fb88102bf3279fbd94",
"desc": "rawtr(37c2a4efc2200a9cd092a0061b027da3c6772ae862d987fb88102bf3279fbd94)#ryczaytf",
"hex": "512037c2a4efc2200a9cd092a0061b027da3c6772ae862d987fb88102bf3279fbd94",
"address": "tb1pxlp2fm7zyq9fe5yj5qrpkqna50r8w2hgvtvc07ugzq4lxfulhk2qlkpf56",
"type": "witness_v1_taproot"
}
},
"taproot_bip32_derivs": [
{
"pubkey": "463a2c719a1f61da0a8321e7181dc40745af30f92a2473ffe20e2a97cfbb8c19",
"master_fingerprint": "1f3608ce",
"path": "m/86'/1'/0'/0/97",
"leaf_hashes": [
]
}
],
"taproot_internal_key": "463a2c719a1f61da0a8321e7181dc40745af30f92a2473ffe20e2a97cfbb8c19"
}
],
"outputs": [
{
"taproot_internal_key": "d58081df72e99a419e9b0de7733edf7278e12ff6c11bf3e31fcbe982bd8bddc5",
"taproot_tree": [
],
"taproot_bip32_derivs": [
{
"pubkey": "d58081df72e99a419e9b0de7733edf7278e12ff6c11bf3e31fcbe982bd8bddc5",
"master_fingerprint": "1f3608ce",
"path": "m/86'/1'/0'/0/98",
"leaf_hashes": [
]
}
]
},
{
"taproot_internal_key": "f6330dbacf1c77e180a2c064f709fea4d3fdc27ec0fa3cb656ad1ccbeb2227cc",
"taproot_tree": [
],
"taproot_bip32_derivs": [
{
"pubkey": "f6330dbacf1c77e180a2c064f709fea4d3fdc27ec0fa3cb656ad1ccbeb2227cc",
"master_fingerprint": "1f3608ce",
"path": "m/86'/1'/0'/1/337",
"leaf_hashes": [
]
}
]
}
],
"fee": 0.00001540
} decode patched {
"tx": {
"txid": "4e7d1ab0ae861c82a973c23bf13cebf24ce3af009c591a4169ea56b95e72b92a",
"hash": "4e7d1ab0ae861c82a973c23bf13cebf24ce3af009c591a4169ea56b95e72b92a",
"version": 2,
"size": 137,
"vsize": 137,
"weight": 548,
"locktime": 0,
"vin": [
{
"txid": "717cda6c08fd6d4687a1945cda5e4a567a481b59f043fbc19abe474c4fb1703a",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967293
}
],
"vout": [
{
"value": 0.10000000,
"n": 0,
"scriptPubKey": {
"asm": "1 5d59cf57c02f67c69a42bc79c83f63ddfff6b02e47c99445652880989a4d4ccc",
"desc": "rawtr(5d59cf57c02f67c69a42bc79c83f63ddfff6b02e47c99445652880989a4d4ccc)#qa5395xa",
"hex": "51205d59cf57c02f67c69a42bc79c83f63ddfff6b02e47c99445652880989a4d4ccc",
"address": "tb1pt4vu747q9anudxjzh3uus0mrmhlldvpwglyeg3t99zqf3xjdfnxqpsn99y",
"type": "witness_v1_taproot"
}
},
{
"value": 19.89998460,
"n": 1,
"scriptPubKey": {
"asm": "1 d112c56038f702f9cccae8e5b1d1150b35b985d8feb921ed81d6a3caf2b78a4d",
"desc": "rawtr(d112c56038f702f9cccae8e5b1d1150b35b985d8feb921ed81d6a3caf2b78a4d)#z88xvjpv",
"hex": "5120d112c56038f702f9cccae8e5b1d1150b35b985d8feb921ed81d6a3caf2b78a4d",
"address": "tb1p6yfv2cpc7up0nnx2arjmr5g4pv6mnpwcl6ujrmvp663u4u4h3fxsrqclw3",
"type": "witness_v1_taproot"
}
}
]
},
"global_xpubs": [
],
"psbt_version": 0,
"proprietary": [
],
"unknown": {
},
"inputs": [
{
"witness_utxo": {
"amount": 20.00000000,
"scriptPubKey": {
"asm": "1 69d2f1839e388101dd219ec12c71c87015c8bd46d61299a730cc35a6e55a8fe1",
"desc": "rawtr(69d2f1839e388101dd219ec12c71c87015c8bd46d61299a730cc35a6e55a8fe1)#lcncmzh8",
"hex": "512069d2f1839e388101dd219ec12c71c87015c8bd46d61299a730cc35a6e55a8fe1",
"address": "tb1pd8f0rqu78zqsrhfpnmqjcuwgwq2u302x6cffnfeses66de263lss3w2r37",
"type": "witness_v1_taproot"
}
},
"taproot_bip32_derivs": [
{
"pubkey": "92238dd7c9685c9d0f64bcaad942e17026183f32d5e3b18228cb07ab640a68c0",
"master_fingerprint": "1f3608ce",
"path": "m/86'/1'/0'/0/93",
"leaf_hashes": [
]
}
],
"taproot_internal_key": "92238dd7c9685c9d0f64bcaad942e17026183f32d5e3b18228cb07ab640a68c0"
}
],
"outputs": [
{
"taproot_internal_key": "c94c896fa554a428c29a2ed9d421af3e7e8adc1165cf13e9b09deccc69f87903",
"taproot_bip32_derivs": [
{
"pubkey": "c94c896fa554a428c29a2ed9d421af3e7e8adc1165cf13e9b09deccc69f87903",
"master_fingerprint": "1f3608ce",
"path": "m/86'/1'/0'/0/94",
"leaf_hashes": [
]
}
]
},
{
"taproot_internal_key": "b31fe0ffb3f0c0f0c0747cf642f975207b98291a69018668480ab2c8453d0d7a",
"taproot_bip32_derivs": [
{
"pubkey": "b31fe0ffb3f0c0f0c0747cf642f975207b98291a69018668480ab2c8453d0d7a",
"master_fingerprint": "1f3608ce",
"path": "m/86'/1'/0'/1/329",
"leaf_hashes": [
]
}
]
}
],
"fee": 0.00001540
} |
(I also find the empty leaf_hashes field a bit sus, but couldn't find anything in the spec around if they are allowed to be empty) |
On further research, I do think this is the correct behavior, but the issue is that the field name m_tap_tree should be refactored (in follow up work?) to be m_taproot_builder, because it is the builder (which also contains the pubkey if key-only). The "true" field of tap_tree is only available when derived from taproot_builder by calling the function to check for presence of branches. Thus there is no "stopping this issue at the source", it's just a logic bug in serializing of PSBT from core. |
I think the correct behavior is to change Also I think the This could also use a test. |
Added a patch to prevent invalid deserialization. I think that with that patch, the approach used here is sufficient because not deserializing an empty Tap Tree is something that can, now, only be the result of an internal logic error. W.r.t. "preventing it at the source", I tried this patch independent of any others, but it didn't seem to work. Maybe you can figure out where the bad state is coming from? diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 9bcbe1cee..f6a67be5a 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -885,20 +885,28 @@ class TRDescriptor final : public DescriptorImpl
protected:
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, FlatSigningProvider& out) const override
{
- TaprootBuilder builder;
+ std::optional<TaprootBuilder> builder;
assert(m_depths.size() == scripts.size());
for (size_t pos = 0; pos < m_depths.size(); ++pos) {
- builder.Add(m_depths[pos], scripts[pos], TAPROOT_LEAF_TAPSCRIPT);
+ if (!builder.has_value()) {
+ builder = TaprootBuilder{};
+ }
+ builder->Add(m_depths[pos], scripts[pos], TAPROOT_LEAF_TAPSCRIPT);
}
- if (!builder.IsComplete()) return {};
+ if (builder && !builder->IsComplete()) return {};
assert(keys.size() == 1);
XOnlyPubKey xpk(keys[0]);
if (!xpk.IsFullyValid()) return {};
- builder.Finalize(xpk);
- WitnessV1Taproot output = builder.GetOutput();
- out.tr_trees[output] = builder;
- out.pubkeys.emplace(keys[0].GetID(), keys[0]);
- return Vector(GetScriptForDestination(output));
+ if (builder) {
+ builder->Finalize(xpk);
+ WitnessV1Taproot output = builder->GetOutput();
+ out.tr_trees[output] = *builder;
+ out.pubkeys.emplace(keys[0].GetID(), keys[0]);
+ return Vector(GetScriptForDestination(output));
+ } else {
+ out.pubkeys.emplace(keys[0].GetID(), keys[0]);
+ return Vector(GetScriptForDestination(WitnessV1Taproot(xpk)));
+ }
}
bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const override
{ |
Alternative in #25858 |
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
Going to close this in favour of #25858 for now, as it seems that PR has a number of concept/approach ACKs (including from @JeremyRubin). Jeremy please let me know if this is incorrect. |
…s a script path 9e386af tests: Test that PSBT_OUT_TAP_TREE is included correctly (Andrew Chow) 30ff25c psbt: Only include m_tap_tree if it has scripts (Andrew Chow) 0577d42 psbt: Change m_tap_tree to store just the tuples (Andrew Chow) 22c051c tests: Test that PSBT_OUT_TAP_TREE is combined correctly (Andrew Chow) 7df6e1b psbt: Fix merging of m_tap_tree (Andrew Chow) 0652dc5 [BugFix]: Do not allow deserializing PSBT with empty PSBT_OUT_TAP_TREE (Jeremy Rubin) Pull request description: PSBT_OUT_TAP_TREE should not be included for outputs that do not have such a tree. This should be disallowed during parsing, as well as prior to serialization when the field is populated during updating. Also added some test cases. Alternative to #25856 ACKs for top commit: instagibbs: ACK 9e386af darosior: ACK 9e386af Tree-SHA512: ce5c02a69752d176dbd967c1e8d30129b1905c8f186aeeef034576c1de82059271a1ee846bd040f5be4e66bb77ba711dcf14ac1e597c5707d7e7e2293f6cfefb
…tput has a script path 9e386af tests: Test that PSBT_OUT_TAP_TREE is included correctly (Andrew Chow) 30ff25c psbt: Only include m_tap_tree if it has scripts (Andrew Chow) 0577d42 psbt: Change m_tap_tree to store just the tuples (Andrew Chow) 22c051c tests: Test that PSBT_OUT_TAP_TREE is combined correctly (Andrew Chow) 7df6e1b psbt: Fix merging of m_tap_tree (Andrew Chow) 0652dc5 [BugFix]: Do not allow deserializing PSBT with empty PSBT_OUT_TAP_TREE (Jeremy Rubin) Pull request description: PSBT_OUT_TAP_TREE should not be included for outputs that do not have such a tree. This should be disallowed during parsing, as well as prior to serialization when the field is populated during updating. Also added some test cases. Alternative to bitcoin#25856 ACKs for top commit: instagibbs: ACK bitcoin@9e386af darosior: ACK 9e386af Tree-SHA512: ce5c02a69752d176dbd967c1e8d30129b1905c8f186aeeef034576c1de82059271a1ee846bd040f5be4e66bb77ba711dcf14ac1e597c5707d7e7e2293f6cfefb
According to BIP-174, the tap_tree field must be:
allowing serializing an empty tap tree tuple breaks the "one or more"ness of the field, so serializations should ignore empty but initialized tap_trees.
see also rust-bitcoin/rust-bitcoin#1194, where this caused downstream issues.