psbt: Only include PSBT_OUT_TAP_TREE when the output has a script path #25858

pull achow101 wants to merge 6 commits into bitcoin:master from achow101:psbt-no-empty-taptree changing 7 files +58 −28
  1. achow101 commented at 8:15 pm on August 16, 2022: member

    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

  2. [BugFix]: Do not allow deserializing PSBT with empty PSBT_OUT_TAP_TREE 0652dc53b2
  3. achow101 added this to the milestone 24.0 on Aug 16, 2022
  4. DrahtBot added the label PSBT on Aug 16, 2022
  5. in src/script/standard.h:319 in bac92f9c05 outdated
    314@@ -315,6 +315,8 @@ class TaprootBuilder
    315     TaprootSpendData GetSpendData() const;
    316     /** Returns a vector of tuples representing the depth, leaf version, and script */
    317     std::vector<std::tuple<uint8_t, uint8_t, CScript>> GetTreeTuples() const;
    318+    /** Returns true if there are any tapscripts */
    319+    bool HasScripts() const { return m_branch.size() > 0; }
    


    aureleoules commented at 9:17 pm on August 16, 2022:
    0    bool HasScripts() const { return !m_branch.empty(); }
    

    achow101 commented at 4:25 pm on September 13, 2022:
    Done
  6. JeremyRubin commented at 0:46 am on August 17, 2022: contributor

    Either approach should work.

    Not as big a fan of this approach because we may, in the future, wish to have access to the TaprootBuilder even HasScripts is false. E.g., were the TaprootBuilder to be extended to contain information about MuSig derivations, in which case the changes would be more straightforward IMO.

    Of course, it’s silly to predict how future changes might be made, but I just personally find it conceptually cleaner to hang onto whatever data the sigdata passed us, and to use it correctly on output.

  7. achow101 commented at 0:52 am on August 17, 2022: member

    Not as big a fan of this approach because we may, in the future, wish to have access to the TaprootBuilder even HasScripts is false. E.g., were the TaprootBuilder to be extended to contain information about MuSig derivations, in which case the changes would be more straightforward IMO.

    I highly doubt that would be the case. And if it were, it’s not particularly difficult to deal with it then. Furthermore, the general structure of the PSBT implementation is that each field has its own explicit member inside its struct, so any future things would get their own fields, which will then be un/packed from TaprootBuilder as needed.

    Perhaps the more correct way to implement m_tap_tree is to make it the vector of tuples rather than a TaprootBuilder.

  8. achow101 force-pushed on Aug 17, 2022
  9. achow101 commented at 1:20 am on August 17, 2022: member

    Perhaps the more correct way to implement m_tap_tree is to make it the vector of tuples rather than a TaprootBuilder.

    Added a commit that does this.

  10. DrahtBot commented at 7:47 am on August 17, 2022: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #26238 (clang-tidy: fixup named argument comments by fanquake)
    • #25877 (refactor: Do not use CScript for tapleaf scripts until the tapleaf version is known by roconnor-blockstream)

    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.

  11. in src/psbt.cpp:228 in 9e74569c81 outdated
    227+            CScript script = std::get<2>(tuple);
    228+            builder.Add((int)depth, script, (int)leaf_ver, /*track=*/true);
    229+        }
    230+        assert(builder.IsComplete());
    231+        builder.Finalize(m_tap_internal_key);
    232+        TaprootSpendData spenddata = builder.GetSpendData();
    


    JeremyRubin commented at 2:58 pm on August 17, 2022:
    maybe this should just be a function in TaprootBuilder?
  12. JeremyRubin commented at 2:59 pm on August 17, 2022: contributor
    approach ACK, this seems OK.
  13. fanquake requested review from instagibbs on Sep 1, 2022
  14. fanquake requested review from darosior on Sep 1, 2022
  15. in src/psbt.h:716 in a94fe72b50 outdated
    712@@ -713,7 +713,7 @@ struct PSBTOutput
    713     CScript witness_script;
    714     std::map<CPubKey, KeyOriginInfo> hd_keypaths;
    715     XOnlyPubKey m_tap_internal_key;
    716-    std::optional<TaprootBuilder> m_tap_tree;
    717+    std::optional<std::vector<std::tuple<uint8_t, uint8_t, CScript>>> m_tap_tree;
    


    darosior commented at 9:07 am on September 13, 2022:
    nit: What’s the reason for keeping it optional now it’s a vector?

    achow101 commented at 4:25 pm on September 13, 2022:
    Dropped the optional
  16. in test/functional/rpc_psbt.py:852 in 9e74569c81 outdated
    846@@ -834,6 +847,9 @@ def test_psbt_input_keys(psbt_input, keys):
    847         assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [psbt1, psbt2])
    848         assert_equal(self.nodes[0].combinepsbt([psbt1, psbt1]), psbt1)
    849 
    850+        if self.options.descriptors:
    851+            self.log.info("Test that PSBT_OUT_TAPROOT_TREE is only present for Taproot outputs that have script paths")
    852+
    


    darosior commented at 9:13 am on September 13, 2022:
    Looks like you actually didn’t commit this test?

    achow101 commented at 4:25 pm on September 13, 2022:
    Removed. I changed how I wanted to do the test and forgot to drop the log line.
  17. darosior commented at 9:15 am on September 13, 2022: member
    Code looks good to me. Looks like part of the functional test is missing.
  18. in src/psbt.cpp:226 in 9e74569c81 outdated
    218@@ -219,7 +219,16 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
    219         sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
    220     }
    221     if (m_tap_tree.has_value() && m_tap_internal_key.IsFullyValid()) {
    222-        TaprootSpendData spenddata = m_tap_tree->GetSpendData();
    223+        TaprootBuilder builder;
    224+        for (const auto& tuple : m_tap_tree.value()) {
    225+            uint8_t depth = std::get<0>(tuple);
    226+            uint8_t leaf_ver = std::get<1>(tuple);
    227+            CScript script = std::get<2>(tuple);
    


    aureleoules commented at 9:49 am on September 13, 2022:
    0        for (const auto& [depth, leaf_ver, script] : m_tap_tree.value()) {
    

    achow101 commented at 4:25 pm on September 13, 2022:
    Done
  19. in src/psbt.h:764 in 9e74569c81 outdated
    757@@ -758,8 +758,7 @@ struct PSBTOutput
    758             SerializeToVector(s, PSBT_OUT_TAP_TREE);
    759             std::vector<unsigned char> value;
    760             CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0);
    761-            const auto& tuples = m_tap_tree->GetTreeTuples();
    762-            for (const auto& tuple : tuples) {
    763+            for (const auto& tuple : m_tap_tree.value()) {
    764                 uint8_t depth = std::get<0>(tuple);
    765                 uint8_t leaf_ver = std::get<1>(tuple);
    766                 CScript script = std::get<2>(tuple);
    


    aureleoules commented at 9:49 am on September 13, 2022:
    0            for (const auto& [depth, leaf_ver, script] : m_tap_tree.value()) {
    

    achow101 commented at 4:25 pm on September 13, 2022:
    Done
  20. in src/rpc/rawtransaction.cpp:1249 in 9e74569c81 outdated
    1242@@ -1243,8 +1243,7 @@ static RPCHelpMan decodepsbt()
    1243         // Taproot tree
    1244         if (output.m_tap_tree.has_value()) {
    1245             UniValue tree(UniValue::VARR);
    1246-            const auto& tuples = output.m_tap_tree->GetTreeTuples();
    1247-            for (const auto& tuple : tuples) {
    1248+            for (const auto& tuple : output.m_tap_tree.value()) {
    1249                 uint8_t depth = std::get<0>(tuple);
    1250                 uint8_t leaf_ver = std::get<1>(tuple);
    1251                 CScript script = std::get<2>(tuple);
    


    aureleoules commented at 9:50 am on September 13, 2022:
    0            for (const auto& [depth, leaf_ver, script] : output.m_tap_tree.value()) {
    

    achow101 commented at 4:25 pm on September 13, 2022:
    Done
  21. achow101 force-pushed on Sep 13, 2022
  22. S3RK commented at 6:42 am on September 14, 2022: contributor
    Concept ACK
  23. darosior commented at 10:16 am on September 14, 2022: member
    ACK e04f5b93fb
  24. glozow requested review from JeremyRubin on Sep 15, 2022
  25. glozow requested review from S3RK on Sep 15, 2022
  26. in src/psbt.cpp:274 in e04f5b93fb outdated
    270@@ -265,7 +271,7 @@ void PSBTOutput::Merge(const PSBTOutput& output)
    271     if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
    272     if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
    273     if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key;
    274-    if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree;
    275+    if (!m_tap_tree.empty() && output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree;
    


    JeremyRubin commented at 2:39 pm on September 15, 2022:

    isn’t this the opposite of what we want?

    why is the logic switched to “clear this m_tap_tree if merging output has none set” from “if unset, set m_tap_tree if output has one set”.


    darosior commented at 2:46 pm on September 15, 2022:
    The logic is still “clear this m_tap_tree if merging output has none set”. It’s just that “has value” is the opposite of “is empty” so it had to be inversed.

    JeremyRubin commented at 2:53 pm on September 15, 2022:

    hmmm, even if not inverted, is the original behavior the intended behavior and why?

    Other lines seem to be opposite, e.g. 273 has set m_tap_key_internal from non-null output.m_tap_key_internal semantics, not unset m_tap_key_internal from null output.m_tap_key_internal

    0if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key;
    

    achow101 commented at 3:43 pm on September 15, 2022:
    Oops, got my booleans backwards, good catch.

    JeremyRubin commented at 4:30 pm on September 15, 2022:
    There should be a test covering this stuff as well?

    achow101 commented at 4:06 pm on September 16, 2022:
    Added a test.
  27. achow101 force-pushed on Sep 15, 2022
  28. achow101 force-pushed on Sep 16, 2022
  29. achow101 force-pushed on Sep 16, 2022
  30. glozow requested review from darosior on Sep 19, 2022
  31. glozow requested review from JeremyRubin on Sep 19, 2022
  32. in src/psbt.cpp:274 in 356bdd2b06 outdated
    270@@ -265,7 +271,7 @@ void PSBTOutput::Merge(const PSBTOutput& output)
    271     if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
    272     if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
    273     if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key;
    274-    if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree;
    275+    if (m_tap_tree.empty() && !output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree;
    


    darosior commented at 1:03 pm on September 27, 2022:

    Good catch Jeremy that the existing behaviour was incorrect, i had missed that by just checking the diff preserved the same behaviour.

    nit: since this is a bugfix unrelated to this commit maybe it could have its own commit?


    achow101 commented at 5:15 pm on October 6, 2022:
    Not really? It’s part of the change of m_tap_tree’s type.

    JeremyRubin commented at 6:53 pm on October 6, 2022:

    i think what @darosior is suggesting is that this PR should have a “parent” PR which first fixes this bug?

    the original line

    0    if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree;
    

    reads

    replace the m_tap_tree if the incoming value is Empty and the current value is Something

    which is clearly incorrect. now the PR is really two independent fixes, both of which might due with test coverage.


    achow101 commented at 7:33 pm on October 6, 2022:
    Ah, I see. I’ve split the fix and it’s test into two separate commits.
  33. in test/functional/test_framework/psbt.py:135 in 5e2b8d194f outdated
    130+        for m in self.i + self.o:
    131+            m.map.clear()
    132+
    133+        old_g = self.g
    134+        self.g = PSBTMap()
    135+        self.g.map[0] = old_g.map[0]
    


    darosior commented at 1:29 pm on September 27, 2022:

    You need to copy self.g in order to really keep PSBT_GLOBAL_UNSIGNED_TX. This code will wipe the whole map since old_g is just a reference to self.g.

     0diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
     1index 1fe3b21542..131ffe85e0 100755
     2--- a/test/functional/rpc_psbt.py
     3+++ b/test/functional/rpc_psbt.py
     4@@ -790,6 +790,8 @@ class PSBTTest(BitcoinTestFramework):
     5             assert_greater_than(len(parsed_psbt.o[vout].map[PSBT_OUT_TAP_TREE]), 0)
     6             assert "taproot_tree" in self.nodes[0].decodepsbt(psbt)["outputs"][vout]
     7             parsed_psbt.make_blank()
     8+            # make_blank shouldn't have removed PSBT_GLOBAL_UNSIGNED_TX, but it has.
     9+            assert parsed_psbt.g == PSBTMap()
    10             comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()])
    11             assert_equal(comb_psbt, psbt)
    12 
    13diff --git a/test/functional/test_framework/psbt.py b/test/functional/test_framework/psbt.py
    14index 6bb899be53..6fb81ba4cf 100644
    15--- a/test/functional/test_framework/psbt.py
    16+++ b/test/functional/test_framework/psbt.py
    17@@ -132,6 +132,7 @@ class PSBT:
    18 
    19         old_g = self.g
    20         self.g = PSBTMap()
    21+        assert old_g == self.g == PSBTMap()
    22         self.g.map[0] = old_g.map[0]
    23 
    24     def to_base64(self):
    

    achow101 commented at 5:15 pm on October 6, 2022:
    Fixed.
  34. in test/functional/rpc_psbt.py:794 in 5e2b8d194f outdated
    791+            parsed_psbt = PSBT.from_base64(psbt)
    792+            assert_greater_than(len(parsed_psbt.o[vout].map[PSBT_OUT_TAP_TREE]), 0)
    793+            assert "taproot_tree" in self.nodes[0].decodepsbt(psbt)["outputs"][vout]
    794+            parsed_psbt.make_blank()
    795+            comb_psbt = self.nodes[0].combinepsbt([psbt, parsed_psbt.to_base64()])
    796+            assert_equal(comb_psbt, psbt)
    


    darosior commented at 4:11 pm on September 27, 2022:

    This does not quite test the bug Jeremy found. You can reintroduce it and the test won’t fail

     0diff --git a/src/psbt.cpp b/src/psbt.cpp
     1index cbf2f88788..1567998039 100644
     2--- a/src/psbt.cpp
     3+++ b/src/psbt.cpp
     4@@ -271,7 +271,7 @@ void PSBTOutput::Merge(const PSBTOutput& output)
     5     if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
     6     if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
     7     if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key;
     8-    if (m_tap_tree.empty() && !output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree;
     9+    if (!m_tap_tree.empty() && output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree;
    10 }
    11 bool PSBTInputSigned(const PSBTInput& input)
    12 {
    

    achow101 commented at 5:15 pm on October 6, 2022:
    Should work now with the self.g fix.
  35. achow101 force-pushed on Oct 6, 2022
  36. psbt: Fix merging of m_tap_tree
    Merging should be checking that the current PSBTOutput doesn't have a
    taptree and the other one's is copied over. The original merging had
    this inverted and would remove m_tap_tree if the other did not have it.
    7df6e1bb77
  37. tests: Test that PSBT_OUT_TAP_TREE is combined correctly 22c051ca70
  38. psbt: Change m_tap_tree to store just the tuples
    Instead of having an entire TaprootBuilder which may or may not be
    complete, and could potentially have future changes that interact oddly
    with taproot tree tuples, have m_tap_tree be just the tuples.
    
    When needed in other a TaprootBuilder for actual use, the tuples will be
    added to a a TaprootBuilder that, in the future, can take in whatever
    other data is needed as well.
    0577d423ad
  39. psbt: Only include m_tap_tree if it has scripts 30ff25cf37
  40. tests: Test that PSBT_OUT_TAP_TREE is included correctly 9e386afb67
  41. achow101 force-pushed on Oct 6, 2022
  42. in src/psbt.cpp:252 in 30ff25cf37 outdated
    248@@ -249,7 +249,7 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
    249     if (!sigdata.tr_spenddata.internal_key.IsNull()) {
    250         m_tap_internal_key = sigdata.tr_spenddata.internal_key;
    251     }
    252-    if (sigdata.tr_builder.has_value()) {
    


    instagibbs commented at 8:19 pm on October 10, 2022:
    let’s add a test for this specific case

    achow101 commented at 9:22 pm on October 10, 2022:
    I think this is not really testable since we would not have a tr_builder for change that doesn’t have scripts. This could only have been reached through a bad psbt, which have now been disallowed through serialization, and this change is more of a belt-and-suspenders.

    instagibbs commented at 5:49 pm on October 11, 2022:
    seems this would only be hit internally, resulting in a psbt with less data. not too troubling
  43. glozow requested review from instagibbs on Oct 12, 2022
  44. glozow requested review from darosior on Oct 12, 2022
  45. darosior approved
  46. darosior commented at 9:00 am on October 13, 2022: member
    ACK 9e386afb67bf8fa71b72f730da1695eeb11828cd
  47. glozow merged this on Oct 13, 2022
  48. glozow closed this on Oct 13, 2022

  49. glozow added the label Needs backport (24.x) on Oct 13, 2022
  50. fanquake commented at 3:48 pm on October 13, 2022: member
    Added to #26133 for backport to 24.x.
  51. fanquake removed the label Needs backport (24.x) on Oct 13, 2022
  52. fanquake referenced this in commit 1390c96c8e on Oct 13, 2022
  53. fanquake referenced this in commit 4abd2ab18e on Oct 13, 2022
  54. fanquake referenced this in commit a9419eff0c on Oct 13, 2022
  55. fanquake referenced this in commit d810fde8ea on Oct 13, 2022
  56. fanquake referenced this in commit 4d42c3a240 on Oct 13, 2022
  57. fanquake referenced this in commit e2e4c2969b on Oct 13, 2022
  58. achow101 referenced this in commit 885366c67a on Oct 13, 2022
  59. JeremyRubin commented at 1:30 pm on October 14, 2022: contributor
    post ack, looks correct to me now
  60. sidhujag referenced this in commit c95704fb03 on Oct 23, 2022
  61. bitcoin locked this on Oct 14, 2023

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-21 15:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me