Add support for inferring tr() descriptors #22166

pull sipa wants to merge 3 commits into bitcoin:master from sipa:202106_infer_tap changing 7 files +275 −24
  1. sipa commented at 7:47 am on June 6, 2021: member

    Includes:

    • First commit from #21365, adding TaprootSpendData in SigningProvider
    • A refactor to expose ComputeTapleafHash and ComputeTaprootMerkleRoot from script/interpreter
    • A tiny change to make getaddressinfo report tr() descriptors as solvable (so that inferred descriptors are shown), despite not having signing code for them.
    • Logic to infer the script tree back from TaprootSpendData, and then use that to infer descriptors.
  2. sipa force-pushed on Jun 6, 2021
  3. DrahtBot added the label Consensus on Jun 6, 2021
  4. DrahtBot added the label Descriptors on Jun 6, 2021
  5. DrahtBot added the label RPC/REST/ZMQ on Jun 6, 2021
  6. DrahtBot added the label Wallet on Jun 6, 2021
  7. DrahtBot commented at 2:36 pm on June 6, 2021: 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:

    • #13062 (Make script interpreter independent from storage type CScript by sipa)

    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.

  8. sipa force-pushed on Jun 6, 2021
  9. in src/script/standard.cpp:616 in 77466ae5c5 outdated
    611+            return std::nullopt;
    612+        } else if (node.sub[0]->done && !node.sub[1]->done && !node.sub[1]->leaf && !node.sub[1]->sub[0] && !node.sub[1]->hash.IsNull() &&
    613+                   (CHashWriter{HASHER_TAPBRANCH} << node.sub[1]->hash << node.sub[1]->hash).GetSHA256() == node.hash) {
    614+            // Special case to deal with duplicate subtrees: after exploring the left subtree (which holds actual data),
    615+            // iterate through it again, but mark the right (unexplored, but implicitly identical) subtree as finished,
    616+            // so that we don't end up in an infinite loop.
    


    achow101 commented at 8:12 pm on June 7, 2021:

    In 77466ae5c58b4a46e545d3d24521fa635f7b8fbe “Taproot descriptor inference”

    It would be useful to explain in more detail why these conditions matter.

    IIUC, this happens because in the section above, if we have duplicate subtrees, we end up making the same side of the tree when we have duplicates which means that the normal tree handling part of this loop runs out of nodes to look at on the other side of the branch.

    However it is still not clear to me why !node.sub[1]->sub[0] is necessary.


    sipa commented at 0:38 am on June 8, 2021:

    Restructured things a bit by adding explicit “explored” and “inner” flags to the nodes. This avoids the need for testing the specifics of the unexplored child.

    Also expanded the comment to explain this better.

  10. achow101 commented at 9:05 pm on June 7, 2021: member
    ACK 77466ae5c58b4a46e545d3d24521fa635f7b8fbe
  11. sipa force-pushed on Jun 8, 2021
  12. achow101 commented at 4:22 pm on June 8, 2021: member
    ACK f9d6f9a52c61833bbc5c611ff6a3e59066e1c92f
  13. sipa force-pushed on Jun 9, 2021
  14. meshcollider added this to the milestone 22.0 on Jun 9, 2021
  15. sipa force-pushed on Jun 12, 2021
  16. laanwj commented at 3:21 pm on June 14, 2021: member

    Reviewed that the consensus commit (e3739fb3b042c3805b5b6357f6f79df787d11144) is a pure refactor.

    Code review ACK 13c6ab699eb6906c6554b4319a377c33fa0d157f

  17. achow101 commented at 7:33 pm on June 14, 2021: member
    re-ACK 13c6ab699eb6906c6554b4319a377c33fa0d157f
  18. in src/script/descriptor.cpp:1234 in 13c6ab699e outdated
    1231     return key_provider;
    1232 }
    1233 
    1234+std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider)
    1235+{
    1236+    unsigned char full_key[33] = {0x02};
    


    laanwj commented at 11:35 am on June 15, 2021:
    I would have a slight preference to use constants here instead of hardcoding 0x02 (and 0x03 below) and 33.

    sipa commented at 9:24 pm on June 15, 2021:
    I agree with this, but prefer keeping it for a follow-up. There are several places where this is already done, and I’d like to introduce constants for it, as well as using them everywhere in the same PR - doing so here would be a distraction.
  19. in src/script/descriptor.cpp:1222 in 13c6ab699e outdated
    1218@@ -1217,44 +1219,68 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
    1219     return nullptr;
    1220 }
    1221 
    1222-std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
    1223+std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider, bool xonly = false)
    


    laanwj commented at 11:37 am on June 15, 2021:
    It looks like the optional xonly argument to InferPubkey is never passed?

    sipa commented at 9:24 pm on June 15, 2021:
    Fixed.
  20. in src/script/interpreter.cpp:1877 in 13c6ab699e outdated
    1870+}
    1871+
    1872+static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash)
    1873+{
    1874+    //! The internal pubkey (x-only, so no Y coordinate parity).
    1875+    const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
    


    laanwj commented at 12:13 pm on June 15, 2021:
    would be nice to add an assertion here with regard to control.size() before indexing into it (maybe same for program)

    sipa commented at 9:57 pm on June 15, 2021:
    Done.

    Sjors commented at 3:38 pm on June 18, 2021:

    I think you lost the asserts in your rebase:

    0assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE);
    1assert(program.size() >= uint256::size());
    
  21. sipa force-pushed on Jun 15, 2021
  22. sipa force-pushed on Jun 15, 2021
  23. sipa commented at 10:01 pm on June 15, 2021: member

    Addressed some comments, and also made this observable change:

    0             if (permit_uncompressed || key.IsCompressed()) {
    1                 CPubKey pubkey = key.GetPubKey();
    2                 out.keys.emplace(pubkey.GetID(), key);
    3-                return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey);
    4+                return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, ctx == ParseScriptContext::P2TR);
    5             } else {
    

    Which will make descriptors with a provided private key inside tr() be interpreted as the equivalent xonly pubkey rather than the compressed or uncompressed one.

  24. achow101 commented at 4:53 pm on June 16, 2021: member
    re-ACK 834935511c2c799c8cb2312d617dbed6336000c4
  25. Sjors commented at 7:35 pm on June 16, 2021: member
    It’s too hot here to review code, but I can confirm that this correctly infers my tr(xpub...) and tr(xpub1, pk(xpub2)) descriptors (and surviving a roundtrip through deriveaddresses). Might be a good sanity check to try with a rebased #21329 too.
  26. laanwj commented at 10:33 am on June 17, 2021: member

    It’s too hot here to review code

    Same, I propose we postpone the feature freeze a bit. Not able to concentrate at the moment.

  27. in src/script/standard.h:219 in 70ff719c36 outdated
    214+{
    215+    /** The BIP341 internal key. */
    216+    XOnlyPubKey internal_key;
    217+    /** The Merkle root of the script tree (0 if no scripts). */
    218+    uint256 merkle_root;
    219+    /** Map from (script, leaf_version) to (sets of) control blocks. */
    


    Sjors commented at 11:18 am on June 17, 2021:
    70ff719c364999346495a2368760f6fbb5a6ca36: why would a script have more than one control block? I assume this only happens if an identical script appears in multiple branches? Can you expand the comment?

    sipa commented at 1:19 am on June 18, 2021:
    Doing so in #22275.
  28. in src/script/standard.cpp:499 in 70ff719c36 outdated
    495     return *this;
    496 }
    497 
    498 WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_key}; }
    499+
    500+TaprootSpendData TaprootBuilder::GetSpendData() const
    


    Sjors commented at 12:39 pm on June 17, 2021:
    70ff719c364999346495a2368760f6fbb5a6ca36 : maybe assert IsComplete()?

    sipa commented at 1:18 am on June 18, 2021:
    Done in #22275.
  29. in src/script/descriptor.cpp:1329 in 834935511c outdated
    1324+                    std::unique_ptr<DescriptorImpl> subdesc;
    1325+                    if (leaf_ver == TAPROOT_LEAF_TAPSCRIPT) {
    1326+                        subdesc = InferScript(script, ParseScriptContext::P2TR, provider);
    1327+                    }
    1328+                    if (!subdesc) {
    1329+                        ok = false;
    


    Sjors commented at 1:11 pm on June 17, 2021:
    834935511c2c799c8cb2312d617dbed6336000c4: rather than failing, could we return raw() (at least for leaf_ver == TAPROOT_LEAF_TAPSCRIPT)? That can wait for a followup though, since we can’t have tr() descriptors with raw leaves in the wallet anyway atm, and raw() is still limited to the top level.

    sipa commented at 1:20 am on June 18, 2021:
    I think that’s exactly what this code does.
  30. Sjors approved
  31. Sjors commented at 1:37 pm on June 17, 2021: member

    tACK 8349355

    However I glanced over InferTaprootTree. I think it would help to include tests for the various problems that function is checking for.

  32. DrahtBot added the label Needs rebase on Jun 17, 2021
  33. sipa force-pushed on Jun 18, 2021
  34. sipa commented at 1:23 am on June 18, 2021: member
    Rebased, will work on adding some tests for failure cases of InferTaprootTree.
  35. DrahtBot removed the label Needs rebase on Jun 18, 2021
  36. Sjors commented at 3:43 pm on June 18, 2021: member

    I think something went wrong in your rebase.

    For example in the original https://github.com/bitcoin/bitcoin/commit/834935511c2c799c8cb2312d617dbed6336000c4 you changed the argument bool xonly = false to bool xonly in the ConstPubkeyProvider provider.

  37. consensus refactor: extract ComputeTapleafHash, ComputeTaprootMerkleRoot 29e5dd1a5b
  38. Report address as solvable based on inferred descriptor c7388e5ada
  39. Taproot descriptor inference d637a9b397
  40. sipa force-pushed on Jun 18, 2021
  41. sipa commented at 6:33 pm on June 18, 2021: member
    @Sjors Oops, thanks for catching that. I redid the rebase; does it look better now?
  42. achow101 commented at 6:44 pm on June 18, 2021: member

    re-ACK d637a9b397816e34652d0c4d383308e39770737a

    Rebase looks good.

  43. Sjors commented at 3:48 pm on June 21, 2021: member

    re-utACK d637a9b

    Much better

  44. in src/script/standard.cpp:533 in d637a9b397
    528+    if (!tweak || tweak->first != output) return std::nullopt;
    529+    // If the Merkle root is 0, the tree is empty, and we're done.
    530+    std::vector<std::tuple<int, CScript, int>> ret;
    531+    if (spenddata.merkle_root.IsNull()) return ret;
    532+
    533+    /** Data structure to represent the nodes of the tree we're going to be build. */
    


    meshcollider commented at 10:02 am on June 23, 2021:
    nit: to build

    jonatack commented at 11:37 am on June 23, 2021:
    touched up the various nits in #22323, feel free to add any others there
  45. in src/script/standard.cpp:535 in d637a9b397
    530+    std::vector<std::tuple<int, CScript, int>> ret;
    531+    if (spenddata.merkle_root.IsNull()) return ret;
    532+
    533+    /** Data structure to represent the nodes of the tree we're going to be build. */
    534+    struct TreeNode {
    535+        /** Hash of this none, if known; 0 otherwise. */
    


    meshcollider commented at 10:03 am on June 23, 2021:
    nit: node
  46. in src/script/standard.cpp:550 in d637a9b397
    545+        bool inner;
    546+        /** Whether or not we have produced output for this subtree. */
    547+        bool done = false;
    548+    };
    549+
    550+    // Build tree from the provides branches.
    


    meshcollider commented at 10:04 am on June 23, 2021:
    nit: provided
  47. meshcollider commented at 10:18 am on June 23, 2021: contributor
    Code review ACK d637a9b397816e34652d0c4d383308e39770737a
  48. meshcollider merged this on Jun 23, 2021
  49. meshcollider closed this on Jun 23, 2021

  50. MarcoFalke referenced this in commit c0e30933e0 on Jun 23, 2021
  51. sidhujag referenced this in commit f4fdc9018c on Jun 24, 2021
  52. sidhujag referenced this in commit 216ca115fa on Jun 24, 2021
  53. fanquake referenced this in commit e826b22da2 on Aug 23, 2021
  54. sidhujag referenced this in commit a77514a7e9 on Aug 23, 2021
  55. gwillen referenced this in commit a503005be7 on Jun 1, 2022
  56. in src/script/interpreter.cpp:1850 in d637a9b397
    1846@@ -1847,16 +1847,14 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
    1847     return true;
    1848 }
    1849 
    1850-static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash)
    1851+uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script)
    


    roconnor-blockstream commented at 3:59 pm on August 12, 2022:
    I mildly object the the use of the type CScript here. While it is true that BIP-341 names this stack item “script”, this value is only interpreted as a CScript in the case of BIP-342 where the leaf_version is equal to TAPROOT_LEAF_TAPSCRIPT (modulo TAPROOT_LEAF_MASK). Otherwise it is simply a bytestring.
  57. bitcoin locked this on Aug 12, 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