LCOV - code coverage report
Current view: top level - src/test/fuzz/util - descriptor.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 100.0 % 73 73
Test Date: 2026-02-01 04:14:50 Functions: 100.0 % 7 7
Branches: 81.9 % 116 95

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2023-present The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <test/fuzz/util/descriptor.h>
       6                 :             : 
       7                 :             : #include <key.h>
       8                 :             : #include <key_io.h>
       9                 :             : #include <pubkey.h>
      10                 :             : #include <span.h>
      11                 :             : #include <util/strencodings.h>
      12                 :             : 
      13                 :             : #include <ranges>
      14                 :             : #include <stack>
      15                 :             : #include <vector>
      16                 :             : 
      17                 :           2 : void MockedDescriptorConverter::Init()
      18                 :             : {
      19                 :             :     // The data to use as a private key or a seed for an xprv.
      20                 :           2 :     std::array<std::byte, 32> key_data{std::byte{1}};
      21                 :             :     // Generate keys of all kinds and store them in the keys array.
      22         [ +  + ]:         514 :     for (size_t i{0}; i < TOTAL_KEYS_GENERATED; i++) {
      23                 :         512 :         key_data[31] = std::byte(i);
      24                 :             : 
      25                 :             :         // If this is a "raw" key, generate a normal privkey. Otherwise generate
      26                 :             :         // an extended one.
      27   [ +  +  +  +  :         512 :         if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) {
             +  +  +  + ]
      28                 :         344 :             CKey privkey;
      29         [ +  - ]:         344 :             privkey.Set(key_data.begin(), key_data.end(), !IdIsUnCompPubKey(i));
      30   [ +  +  +  + ]:         344 :             if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) {
      31         [ +  - ]:         172 :                 CPubKey pubkey{privkey.GetPubKey()};
      32         [ +  - ]:         172 :                 keys_str[i] = HexStr(pubkey);
      33         [ +  + ]:         172 :             } else if (IdIsXOnlyPubKey(i)) {
      34         [ +  - ]:          86 :                 const XOnlyPubKey pubkey{privkey.GetPubKey()};
      35         [ +  - ]:          86 :                 keys_str[i] = HexStr(pubkey);
      36                 :             :             } else {
      37         [ +  - ]:          86 :                 keys_str[i] = EncodeSecret(privkey);
      38                 :             :             }
      39                 :         344 :         } else {
      40         [ +  - ]:         168 :             CExtKey ext_privkey;
      41         [ +  - ]:         168 :             ext_privkey.SetSeed(key_data);
      42         [ +  + ]:         168 :             if (IdIsXprv(i)) {
      43         [ +  - ]:          84 :                 keys_str[i] = EncodeExtKey(ext_privkey);
      44                 :             :             } else {
      45         [ +  - ]:          84 :                 const CExtPubKey ext_pubkey{ext_privkey.Neuter()};
      46         [ +  - ]:          84 :                 keys_str[i] = EncodeExtPubKey(ext_pubkey);
      47                 :             :             }
      48                 :         168 :         }
      49                 :             :     }
      50                 :           2 : }
      51                 :             : 
      52                 :      980303 : std::optional<uint8_t> MockedDescriptorConverter::IdxFromHex(std::string_view hex_characters) const {
      53         [ -  + ]:      980303 :     if (hex_characters.size() != 2) return {};
      54                 :      980303 :     auto idx = ParseHex(hex_characters);
      55   [ -  +  +  + ]:      980303 :     if (idx.size() != 1) return {};
      56                 :      980234 :     return idx[0];
      57                 :      980303 : }
      58                 :             : 
      59                 :       20153 : std::optional<std::string> MockedDescriptorConverter::GetDescriptor(std::string_view mocked_desc) const {
      60                 :             :     // The smallest fragment would be "pk(%00)"
      61         [ +  + ]:       20153 :     if (mocked_desc.size() < 7) return {};
      62                 :             : 
      63                 :             :     // The actual descriptor string to be returned.
      64         [ +  - ]:       20058 :     std::string desc;
      65         [ +  - ]:       20058 :     desc.reserve(mocked_desc.size());
      66                 :             : 
      67                 :             :     // Replace all occurrences of '%' followed by two hex characters with the corresponding key.
      68                 :    17090845 :     for (size_t i = 0; i < mocked_desc.size();) {
      69         [ +  + ]:    17070864 :         if (mocked_desc[i] == '%') {
      70         [ +  + ]:      980311 :             if (i + 3 >= mocked_desc.size()) return {};
      71   [ +  -  +  -  :      980303 :             if (const auto idx = IdxFromHex(mocked_desc.substr(i + 1, 2))) {
                   +  + ]
      72         [ -  + ]:      980234 :                 desc += keys_str[*idx];
      73                 :             :                 i += 3;
      74                 :             :             } else {
      75                 :          69 :                 return {};
      76                 :             :             }
      77                 :             :         } else {
      78   [ +  -  +  + ]:    33181398 :             desc += mocked_desc[i++];
      79                 :             :         }
      80                 :             :     }
      81                 :             : 
      82                 :       19981 :     return desc;
      83                 :       20058 : }
      84                 :             : 
      85                 :       24519 : bool HasDeepDerivPath(std::span<const uint8_t> buff, const int max_depth)
      86                 :             : {
      87                 :       24519 :     auto depth{0};
      88         [ +  + ]:   248679847 :     for (const auto& ch: buff) {
      89         [ +  + ]:   248655332 :         if (ch == ',') {
      90                 :             :             // A comma is always present between two key expressions, so we use that as a delimiter.
      91                 :             :             depth = 0;
      92         [ +  + ]:   247112213 :         } else if (ch == '/') {
      93         [ +  + ]:      548656 :             if (++depth > max_depth) return true;
      94                 :             :         }
      95                 :             :     }
      96                 :             :     return false;
      97                 :             : }
      98                 :             : 
      99                 :       27000 : bool HasTooManySubFrag(std::span<const uint8_t> buff, const int max_subs, const size_t max_nested_subs)
     100                 :             : {
     101                 :             :     // We use a stack because there may be many nested sub-frags.
     102                 :       27000 :     std::stack<int> counts;
     103         [ +  + ]:   269346785 :     for (const auto& ch: buff) {
     104                 :             :         // The fuzzer may generate an input with a ton of parentheses. Rule out pathological cases.
     105   [ -  +  +  + ]:   269319828 :         if (counts.size() > max_nested_subs) return true;
     106                 :             : 
     107         [ +  + ]:   269319821 :         if (ch == '(') {
     108                 :             :             // A new fragment was opened, create a new sub-count for it and start as one since any fragment with
     109                 :             :             // parentheses has at least one sub.
     110         [ +  - ]:   270301635 :             counts.push(1);
     111   [ +  +  +  + ]:   268337971 :         } else if (ch == ',' && !counts.empty()) {
     112                 :             :             // When encountering a comma, account for an additional sub in the last opened fragment. If it exceeds the
     113                 :             :             // limit, bail.
     114         [ +  + ]:     1988542 :             if (++counts.top() > max_subs) return true;
     115   [ +  +  +  + ]:   266349429 :         } else if (ch == ')' && !counts.empty()) {
     116                 :             :             // Fragment closed! Drop its sub count and resume to counting the number of subs for its parent.
     117                 :      677744 :             counts.pop();
     118                 :             :         }
     119                 :             :     }
     120                 :             :     return false;
     121                 :       27000 : }
     122                 :             : 
     123                 :       26957 : bool HasTooManyWrappers(std::span<const uint8_t> buff, const int max_wrappers)
     124                 :             : {
     125                 :             :     // The number of nested wrappers. Nested wrappers are always characters which follow each other so we don't have to
     126                 :             :     // use a stack as we do above when counting the number of sub-fragments.
     127                 :       26957 :     std::optional<int> count;
     128                 :             : 
     129                 :             :     // We want to detect nested wrappers. A wrapper is a character prepended to a fragment, separated by a colon. There
     130                 :             :     // may be more than one wrapper, in which case the colon is not repeated. For instance `jjjjj:pk()`.  To count
     131                 :             :     // wrappers we iterate in reverse and use the colon to detect the end of a wrapper expression and count how many
     132                 :             :     // characters there are since the beginning of the expression. We stop counting when we encounter a character
     133                 :             :     // indicating the beginning of a new expression.
     134         [ +  + ]:   252761446 :     for (const auto ch: buff | std::views::reverse) {
     135                 :             :         // A colon, start counting.
     136         [ +  + ]:   252734982 :         if (ch == ':') {
     137                 :             :             // The colon itself is not a wrapper so we start at 0.
     138                 :      761341 :             count = 0;
     139         [ +  + ]:   251973641 :         } else if (count) {
     140                 :             :             // If we are counting wrappers, stop when we crossed the beginning of the wrapper expression. Otherwise keep
     141                 :             :             // counting and bail if we reached the limit.
     142                 :             :             // A wrapper may only ever occur as the first sub of a descriptor/miniscript expression ('('), as the
     143                 :             :             // first Taproot leaf in a pair ('{') or as the nth sub in each case (',').
     144   [ +  +  +  + ]:     6595950 :             if (ch == ',' || ch == '(' || ch == '{') {
     145                 :      759324 :                 count.reset();
     146         [ +  + ]:     5836626 :             } else if (++*count > max_wrappers) {
     147                 :             :                 return true;
     148                 :             :             }
     149                 :             :         }
     150                 :             :     }
     151                 :             : 
     152                 :             :     return false;
     153                 :             : }
     154                 :             : 
     155                 :       24151 : bool HasTooLargeLeafSize(std::span<const uint8_t> buff, const uint32_t max_leaf_size)
     156                 :             : {
     157                 :       24151 :     uint32_t leaf_len{0};
     158         [ +  + ]:    37837683 :     for (auto c : buff) {
     159   [ +  +  +  + ]:    37815369 :         if (c == '(' || c == ')' || c == ',' || c == '{' || c == '}') {
     160                 :             :             // Possibly start a fresh leaf, or a fresh function name (with
     161                 :             :             // wrappers), or terminate a prior leaf.
     162                 :             :             leaf_len = 0;
     163                 :             :         } else {
     164                 :             :             // Just treat everything else as a leaf. This will also reject long
     165                 :             :             // function names, but this should be fine if the max_leaf_size is
     166                 :             :             // set large enough.
     167         [ +  + ]:    35863345 :             if (++leaf_len > max_leaf_size) {
     168                 :             :                 return true;
     169                 :             :             }
     170                 :             :         }
     171                 :             :     }
     172                 :             :     return false;
     173                 :             : }
        

Generated by: LCOV version 2.0-1