test: Descriptor roundtrip and raw()/ addr() coverage #36013

pull pablomartin4btc wants to merge 2 commits into bitcoin:master from pablomartin4btc:descriptor/roundtrip-raw-addr-tests changing 1 files +112 −8
  1. pablomartin4btc commented at 7:29 PM on August 18, 2026: member

    Part 1 of 2, reviving #24361.

    <details> <summary>Ports the two test-only commits from the original PR above, re-applied against the current <code>descriptor_tests.cpp</code> — the original no longer applies cleanly, since <code>DoCheck()</code>'s signature has changed substantially since the original PR was created back in 2022.</summary> <br>

    norm_prv was dropped, and op_desc_id, spender_nlocktime, spender_nsequence, preimages, expected_prv/expected_pub, and desc_index were all added - so the original diff no longer applies...

    diff -u <(git show 15220ec903:src/test/descriptor_tests.cpp | sed -n '/^void DoCheck/,/^{/p') \
            <(git show HEAD:src/test/descriptor_tests.cpp | sed -n '/^void DoCheck/,/^{/p')
    
    @@ -1,3 +1,7 @@
    -void DoCheck(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY,
    -    bool replace_apostrophe_with_h_in_prv=false, bool replace_apostrophe_with_h_in_pub=false)
    +void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int flags,
    +             const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, std::optional<uint256> op_desc_id = std::nullopt,
    +             const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY, bool replace_apostrophe_with_h_in_prv=false,
    +             bool replace_apostrophe_with_h_in_pub=false, uint32_t spender_nlocktime=0, uint32_t spender_nsequence=CTxIn::SEQUENCE_FINAL,
    +             std::map<std::vector<uint8_t>, std::vector<uint8_t>> preimages={},
    +             std::optional<std::string> expected_prv = std::nullopt, std::optional<std::string> expected_pub = std::nullopt, int desc_index = 0)
     {
     
    

    </details>

    • Roundtrip testing that inferring a descriptor from a generated scriptPubKey reproduces the original descriptor's structure (keys stripped from both sides for comparison). Added extension for MuSig key expressions (#31244) and BIP389 multipath (#22838), both added since 2022, and skipping for multi-branch taproot trees, where sibling order isn't meaningful. (For more details check 1st commit body)
    • Check() coverage for valid raw() and addr() descriptors, previously exercised only for invalid inputs and only in the script-to-descriptor direction. Added a new NO_KEYS flag, since these descriptors have zero keys and several existing checks assumed at least one. (For more details check 2nd commit body)

    No behavior change, test-only.


    Part 2 (not yet opened): the ToString()/ToPrivateString() merge from #24361, independent of this PR — needs more design work before it's ready.

  2. DrahtBot added the label Tests on Aug 18, 2026
  3. DrahtBot commented at 7:30 PM on August 18, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK fametrano
    Concept ACK w0xlt, jeanpablojp

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    No conflicts as of last run.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. w0xlt commented at 9:17 PM on August 18, 2026: contributor

    Concept ACK

  5. in src/test/descriptor_tests.cpp:356 in 5b4c861a60
     350 | @@ -285,13 +351,23 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
     351 |      BOOST_CHECK_EQUAL(parse_pub->IsRange(), (flags & RANGE) != 0);
     352 |      BOOST_CHECK_EQUAL(parse_priv->IsRange(), (flags & RANGE) != 0);
     353 |  
     354 | -    // Check that the highest key expression index matches the number of keys in the descriptor
     355 | +    // Check that the highest key expression index matches the number of keys in the descriptor.
     356 | +    // Descriptors with no keys at all (flags & NO_KEYS) have neither.
     357 |      BOOST_TEST_INFO("Pub desc: " + pub);
    


    rustaceanrob commented at 10:10 AM on August 24, 2026:

    Now that we have a few checks in the true part of the if branch below, perhaps we can change this to be BOOST_TEST_INFO_SCOPE so the descriptor is also printed if BOOST_CHECK_EQUAL(parse_pub->GetKeyCount(), 0U); fails.


    pablomartin4btc commented at 2:01 AM on August 27, 2026:

    Done in 2d230d8366d2149c71e69a2e2ec65561ca09cd0e, thanks!

  6. in src/test/descriptor_tests.cpp:364 in 5b4c861a60
     361 | +        BOOST_CHECK_EQUAL(key_exprs, 0U);
     362 | +        BOOST_CHECK_EQUAL(parse_pub->GetKeyCount(), 0U);
     363 | +    } else {
     364 | +        BOOST_CHECK_EQUAL(key_exprs + 1, parse_pub->GetKeyCount());
     365 | +    }
     366 |      BOOST_TEST_INFO("Priv desc: " + prv);
    


    rustaceanrob commented at 10:11 AM on August 24, 2026:

    Same feedback as the comment above, perhaps could be BOOST_TEST_INFO_SCOPE


    pablomartin4btc commented at 2:01 AM on August 27, 2026:

    Done in 2d230d8366d2149c71e69a2e2ec65561ca09cd0e, thanks!

  7. rustaceanrob commented at 10:12 AM on August 24, 2026: member

    Left a comment regarding BOOST_TEST_INFO, which only prints the value the next failing check, not any check in scope

  8. pablomartin4btc force-pushed on Aug 26, 2026
  9. DrahtBot added the label CI failed on Aug 26, 2026
  10. DrahtBot removed the label CI failed on Aug 27, 2026
  11. pablomartin4btc commented at 2:06 AM on August 27, 2026: member

    -<ins>Updates</ins>:

  12. jeanpablojp commented at 4:56 PM on September 3, 2026: contributor

    Concept ACK

    Built at the head, ran the unit suite, and checked the six new addresses by decoding them outside of Core.

  13. in src/test/descriptor_tests.cpp:186 in 2d230d8366
     181 | +const std::string STR_PUBKEYHASH = "([a-f0-9]{40})";
     182 | +/** ERE string matching an xpub or xprv. */
     183 | +const std::string STR_XPUBPRV = "((xpub|xprv)" + STR_BASE58CHAR + "{74,108})";
     184 | +/** ERE string matching a single derivation path step: a plain index, or a BIP389 multipath
     185 | + *  specifier like <1;2;3>. */
     186 | +const std::string STR_KEYSTEP = "([0-9]+['h]?|[<][0-9]+['h]?([;][0-9]+['h]?)*[>])";
    


    jeanpablojp commented at 4:56 PM on September 3, 2026:

    The multipath alternative here seems avoidable. It only exists because the roundtrip comparison uses pub, the literal test string. In these checks, pub1 is expanded, and DoCheck() already compares it against pub, or the expanded expected_pub literal for multipath. With the old STR_KEYSTEP and the comparison on pub1, the suite passes. Both forms run the same 666 roundtrip checks and produce the same 60 failures under the tested InferScript() mutation. mark_count() is worth keeping either way. Is the literal intended to catch something pub1 would not?


    pablomartin4btc commented at 12:58 AM on October 8, 2026:

    Thanks! The multipath branch was only needed because the comparison at line ~531 used pub directly instead of pub1. I'll switch it to pub1, reverting this to the simpler form.


    pablomartin4btc commented at 4:19 AM on October 8, 2026:

    Done.

  14. in src/test/descriptor_tests.cpp:195 in 2d230d8366
     190 | +const std::string STR_KEYEXPR = "(" + STR_MAYBE_ORIGIN + "(" + STR_PUBKEY + "|" + STR_PUBKEYHASH + "|" + STR_BIP32KEY + "))";
     191 | +
     192 | +/** Regular expression matching a descriptor key expression. */
     193 | +const std::regex KEY_RE("([(,])" + STR_KEYEXPR + "([),])", std::regex::extended);
     194 | +/** Regular expression matching the word "sortedmulti". */
     195 | +const std::regex SORTEDMULTI_RE("(^|[(,])sortedmulti[(]", std::regex::extended);
    


    jeanpablojp commented at 4:56 PM on September 3, 2026:

    nit: this matches sortedmulti( but not sortedmulti_a(, which InferMultiA() gives back as multi_a. No vector uses it today, so nothing breaks, but I flipped the existing 1-of-1 multi_a vector to sortedmulti_a, same script by construction, and only the roundtrip comparison failed, six times, multi_a versus sortedmulti_a. Allowing an optional _a here and keeping it in the replacement fixes it, ran that and the suite went back to green.


    pablomartin4btc commented at 4:20 AM on October 8, 2026:

    Done, thanks!

  15. in src/test/descriptor_tests.cpp:309 in 2d230d8366 outdated
     304 | @@ -240,7 +305,8 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
     305 |      }
     306 |  
     307 |      // Check that both can be serialized with private key back to the private version, but not without private key.
     308 | -    if (!(flags & MISSING_PRIVKEYS)) {
     309 | +    // (Descriptors with no keys at all have nothing to check here.)
     310 | +    if (!(flags & (MISSING_PRIVKEYS | NO_KEYS))) {
    


    jeanpablojp commented at 4:56 PM on September 3, 2026:

    Could we assert the ToPrivateString() contract for NO_KEYS here instead of only skipping the block? The header promises a false return when no private keys are available at all, which is exactly the raw() and addr() case. I changed both implementations to return true and the whole unit suite stayed green. An else if (flags & NO_KEYS) with the two negative checks closes it. I ran that, and the same mutation now fails 14 times, twice per new vector.


    pablomartin4btc commented at 4:19 AM on October 8, 2026:

    Done, thanks!

  16. fametrano commented at 8:03 PM on October 3, 2026: contributor

    ACK 2d230d8366d2149c71e69a2e2ec65561ca09cd0e

    Built test_bitcoin at the head and ran descriptor_tests, no errors. Recomputed the six new addr() vectors with btclib: the scriptPubKeys, the addresses back from them, the witness versions and programs, and the legacy/bech32/bech32m output types all match. One nit: the v1 vector cannot be a plain taproot address, since a P2TR script infers back as a solvable rawtr() and fails both the new structure compare and the existing solvability checks (tried addr(bc1padda8…) for 5120eb5b…); a short comment saying why it is a 40-byte program instead would spare the next reader the experiment.

  17. in src/test/descriptor_tests.cpp:1140 in 2d230d8366 outdated
    1131 | @@ -1047,6 +1132,14 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
    1132 |      CheckUnparsable("", "raw(asdf)", "Raw script is not hex"); // Invalid script
    1133 |      CheckUnparsable("", "raw(Ü)#00000000", "Invalid characters in payload"); // Invalid chars
    1134 |  
    1135 | +    Check("raw(1337)", "raw(1337)", "raw(1337)", NO_KEYS | UNSOLVABLE, {{"1337"}}, std::nullopt, /*op_desc_id=*/std::nullopt, NO_PATHS);
    1136 | +    Check("addr(13MKnpg1J36ogDYVT8GSFHYZQzphkDTESu)", "addr(13MKnpg1J36ogDYVT8GSFHYZQzphkDTESu)", "addr(13MKnpg1J36ogDYVT8GSFHYZQzphkDTESu)", NO_KEYS | UNSOLVABLE, {{"76a91419c84184d9473c2d52913c7468dc6743db09ea9688ac"}}, OutputType::LEGACY, /*op_desc_id=*/std::nullopt, NO_PATHS);
    1137 | +    Check("addr(3P14159f73E4gFr7JterCCQh9QjiTjiZrG)", "addr(3P14159f73E4gFr7JterCCQh9QjiTjiZrG)", "addr(3P14159f73E4gFr7JterCCQh9QjiTjiZrG)", NO_KEYS | UNSOLVABLE, {{"a914e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a87"}}, OutputType::LEGACY, /*op_desc_id=*/std::nullopt, NO_PATHS);
    1138 | +    Check("addr(bc1qg9stkxrszkdqsuj92lm4c7akvk36zvhqw7p6ck)", "addr(bc1qg9stkxrszkdqsuj92lm4c7akvk36zvhqw7p6ck)", "addr(bc1qg9stkxrszkdqsuj92lm4c7akvk36zvhqw7p6ck)", NO_KEYS | UNSOLVABLE, {{"00144160bb1870159a08724557f75c7bb665a3a132e0"}}, OutputType::BECH32, /*op_desc_id=*/std::nullopt, NO_PATHS);
    1139 | +    Check("addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", "addr(bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3)", NO_KEYS | UNSOLVABLE, {{"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"}}, OutputType::BECH32, /*op_desc_id=*/std::nullopt, NO_PATHS);
    1140 | +    Check("addr(bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y)", "addr(bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y)", "addr(bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y)", NO_KEYS | UNSOLVABLE, {{"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}}, OutputType::BECH32M, /*op_desc_id=*/std::nullopt, NO_PATHS);
    


    fametrano commented at 8:03 PM on October 3, 2026:
        // 40-byte v1 program: a 32-byte one would infer as a solvable rawtr() and fail the checks
        Check("addr(bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y)", "addr(bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y)", "addr(bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y)", NO_KEYS | UNSOLVABLE, {{"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}}, OutputType::BECH32M, /*op_desc_id=*/std::nullopt, NO_PATHS);
    

    pablomartin4btc commented at 12:59 AM on October 8, 2026:

    Thanks! I'll include a short comment explaining the 40-byte choice.


    pablomartin4btc commented at 4:19 AM on October 8, 2026:

    Done.

  18. DrahtBot requested review from jeanpablojp on Oct 3, 2026
  19. pablomartin4btc force-pushed on Oct 8, 2026
  20. test: Roundtripping of descriptors ignoring keys
    Add DropKeys(), which strips a descriptor string down to structure
    only (key material replaced with "<KEY>", sortedmulti()/sortedmulti_a()
    normalized to multi()/multi_a(), checksum dropped), and use it to check
    that inferring a descriptor back from a script it generated reproduces
    the original descriptor's structure.
    
    A few descriptor features need special handling for this comparison
    to hold:
    
    - MuSig key expressions (musig(...)) aren't recognized by the plain
      key-matching regex. Added a separate pass collapsing musig()
      expressions (and any trailing derivation path) to <KEY> before the
      plain-key regex runs, rather than teaching that regex to understand
      musig()'s internals recursively.
    - Sibling branches within a taproot script tree have no meaningful
      order (it's a Merkle tree), so InferDescriptor() reconstructing a
      multi-leaf tree in a different but equally valid branch order isn't
      something a plain string comparison can account for. Skipped the
      check for any taproot descriptor with more than one script leaf,
      detected via the presence of '{' in the descriptor string (the
      grammar only emits '{'/'}' for grouping 2+ branches).
    - sortedmulti_a() wasn't recognized by the normalization regex (found
      by jeanpablojp). Made the "_a" suffix optional and kept it in the
      replacement.
    
    jeanpablojp also pointed out that comparing against the descriptor's
    own re-serialization, rather than the original test string, avoids
    needing this regex to understand BIP389 multipath syntax at all.
    Switched the comparison accordingly and dropped the multipath
    handling.
    
    Co-authored-by: Pieter Wuille <pieter@wuille.net>
    b970993985
  21. test: raw() and addr() descriptors in unit tests
    Add Check() coverage for valid raw() and addr() descriptors: a raw
    script, legacy and P2SH addresses, two bech32 v0 addresses, and two
    future-witness-version addresses (v1 and v2, both expected
    non-standard/unsolvable) - verifying checksum handling, solvability,
    script derivation, and structural roundtripping. Address/script pairs
    verified against a live node's own validateaddress before use. The v1
    vector uses a 40-byte witness program instead of a real 32-byte
    taproot output key, since the latter infers back as a solvable
    rawtr() (noted by fametrano).
    
    These descriptors have no key expressions at all, which DoCheck()
    didn't previously account for:
    
    - keys_priv.keys is expected to be empty rather than non-empty, gated
      on a new NO_KEYS flag.
    - GetMaxKeyExpr()/GetKeyCount() are expected to both report zero
      rather than the usual "highest index + 1 == count" relationship.
      Added the NO_KEYS-aware branch for it.
    - HavePrivateKeys() returns false for a keyless descriptor, not
      vacuously true - it didn't exist when the original patch was
      written, so this wasn't previously exercised. Gated
      have_all_private_keys on NO_KEYS in addition to the existing
      MISSING_PRIVKEYS.
    - raw()/addr() hardcode ToPrivateString() to always fail, so the
      existing "can serialize back to the private version" check needs
      skipping for these descriptors too. Extended its MISSING_PRIVKEYS
      gate to also cover NO_KEYS. Added an explicit check that it returns
      false, per jeanpablojp.
    
    Co-authored-by: Pieter Wuille <pieter@wuille.net>
    cf9821aaa5
  22. DrahtBot added the label CI failed on Oct 8, 2026
  23. DrahtBot commented at 3:10 AM on October 8, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/37714644244/job/113108232448</sub> <sub>LLM reason (✨ experimental): CI failed because clang-tidy flagged /*op_desc_id=*/ comments in descriptor_tests.cpp as mismatched with the parameter name op_compat_desc_hash (warnings treated as errors).</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  24. pablomartin4btc force-pushed on Oct 8, 2026
  25. pablomartin4btc commented at 4:27 AM on October 8, 2026: member

    -<ins>Updates</ins>:

    • Addressed @jeanpablojp's and @fametrano's feedback, thanks both!
    • Rebased — #35445 renamed DescriptorID/ op_desc_id to CompatDescriptorHash/ op_compat_desc_hash, leaving a few comments out of sync and failing tidy CI. Fixed.
  26. DrahtBot removed the label CI failed on Oct 8, 2026
  27. fametrano commented at 7:00 AM on October 8, 2026: contributor

    reACK cf9821aaa5ed971c9e3eb16bbf5daed0495a9252

    descriptor_tests passes at the new head, and the raw()/addr() vectors are unchanged since 2d230d8, so my earlier check of them still holds. Thanks for the comment on the 40-byte v1 program.

  28. in src/test/descriptor_tests.cpp:206 in b970993985
     201 | + *  KEY_RE's actual capture count rather than hardcoded, so it can't silently drift out of sync
     202 | + *  if STR_KEYEXPR's structure changes again in the future. */
     203 | +const std::string KEY_RE_REPLACEMENT = "$1<KEY>$" + util::ToString(KEY_RE.mark_count());
     204 | +
     205 | +/** Replace all key expressions in desc with "<KEY>", "sortedmulti" with "multi", and drop checksum. */
     206 | +std::string DropKeys(std::string desc)
    


    rustaceanrob commented at 11:10 PM on October 17, 2026:

    Not sure if I am missing something, but it seems like this function can be simplified using regex_replace and std::regex:

    diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
    index 70c7d5e8b5..622157b2f6 100644
    --- a/src/test/descriptor_tests.cpp
    +++ b/src/test/descriptor_tests.cpp
    @@ -14,7 +14,6 @@
    
     #include <optional>
     #include <regex>
    -#include <sstream>
     #include <string>
     #include <vector>
    
    @@ -188,46 +187,22 @@ const std::string STR_BIP32KEY = "(" + STR_XPUBPRV + "(/" + STR_KEYSTEP + ")*(/[
     /** ERE string matching a descriptor key expression. */
     const std::string STR_KEYEXPR = "(" + STR_MAYBE_ORIGIN + "(" + STR_PUBKEY + "|" + STR_PUBKEYHASH + "|" + STR_BIP32KEY + "))";
    
    -/** Regular expression matching a descriptor key expression. */
    -const std::regex KEY_RE("([(,])" + STR_KEYEXPR + "([),])", std::regex::extended);
    -/** Regular expression matching "sortedmulti" or "sortedmulti_a", capturing the optional "_a". */
    -const std::regex SORTEDMULTI_RE("(^|[(,])sortedmulti(_a)?[(]", std::regex::extended);
    +/** Regular expression matching a descriptor key expression, with the trailing separator as lookahead. */
    +const std::regex KEY_RE("([(,])" + STR_KEYEXPR + "(?=[),])");
     /** Regular expression collapsing a whole musig() key expression, including any trailing
      *  derivation path, to a single unit. musig()'s participants are plain keys with no
      *  parentheses of their own, and musig() cannot be nested or carry origin info (see
      *  ParsePubkey() in script/descriptor.cpp), so a match up to the first ')' is sufficient;
      *  KEY_RE above doesn't need to understand musig()'s internal structure as a result. */
    -const std::regex MUSIG_RE("musig[(][^()]*[)][^(),]*", std::regex::extended);
    -/** Replacement string for KEY_RE: keeps the separators captured in groups 1 and mark_count(),
    - *  replacing the key expression itself with "<KEY>". The trailing group number is computed from
    - *  KEY_RE's actual capture count rather than hardcoded, so it can't silently drift out of sync
    - *  if STR_KEYEXPR's structure changes again in the future. */
    -const std::string KEY_RE_REPLACEMENT = "$1<KEY>$" + util::ToString(KEY_RE.mark_count());
    +const std::regex MUSIG_RE("musig[(][^()]*[)][^(),]*");
    
     /** Replace all key expressions in desc with "<KEY>", "sortedmulti" with "multi", and drop checksum. */
     std::string DropKeys(std::string desc)
     {
    -    if (desc.size() >= 9 && *(desc.end() - 9) == '#') {
    -        // Drop checksum
    -        desc = desc.substr(0, desc.size() - 9);
    -    }
    -    {
    -        // Collapse musig() expressions first; KEY_RE below only understands plain keys.
    -        std::ostringstream ostr;
    -        std::regex_replace(std::ostreambuf_iterator<char>(ostr), desc.begin(), desc.end(), MUSIG_RE, "<KEY>");
    -        desc = ostr.str();
    -    }
    -    while (true) {
    -        std::ostringstream ostr;
    -        std::regex_replace(std::ostreambuf_iterator<char>(ostr), desc.begin(), desc.end(), KEY_RE, KEY_RE_REPLACEMENT);
    -        auto newdesc = ostr.str();
    -        ostr.str("");
    -        std::regex_replace(std::ostreambuf_iterator<char>(ostr), newdesc.begin(), newdesc.end(), SORTEDMULTI_RE, "$1multi$2(");
    -        auto newdesc2 = ostr.str();
    -        std::swap(desc, newdesc2);
    -        if (newdesc2 == desc) break;
    -    }
    -    return desc;
    +    desc = desc.substr(0, desc.find('#'));
    +    desc = std::regex_replace(desc, MUSIG_RE, "<KEY>");
    +    desc = std::regex_replace(desc, KEY_RE, "$1<KEY>");
    +    return std::regex_replace(desc, std::regex("sortedmulti"), "multi");
     }
    
  29. in src/test/descriptor_tests.cpp:279 in cf9821aaa5
     275 | @@ -274,9 +276,11 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
     276 |      BOOST_CHECK(parse_pub->GetOutputType() == type);
     277 |  
     278 |      // Check private keys are extracted from the private version but not the public one.
     279 | -    BOOST_CHECK(keys_priv.keys.size());
     280 | +    BOOST_CHECK_EQUAL(keys_priv.keys.empty(), !!(flags & NO_KEYS));
    


    rustaceanrob commented at 11:29 PM on October 17, 2026:

    nit: Could use (flags & NO_KEYS) != 0 for legibility. Had to look up the !! case


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: 2026-10-21 02:51 UTC

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