Coverage Report

Created: 2026-04-20 22:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/util/descriptor.cpp
Line
Count
Source
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
void MockedDescriptorConverter::Init()
18
0
{
19
    // The data to use as a private key or a seed for an xprv.
20
0
    std::array<std::byte, 32> key_data{std::byte{1}};
21
    // Generate keys of all kinds and store them in the keys array.
22
0
    for (size_t i{0}; i < TOTAL_KEYS_GENERATED; i++) {
23
0
        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
0
        if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) {
28
0
            CKey privkey;
29
0
            privkey.Set(key_data.begin(), key_data.end(), !IdIsUnCompPubKey(i));
30
0
            if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) {
31
0
                CPubKey pubkey{privkey.GetPubKey()};
32
0
                keys_str[i] = HexStr(pubkey);
33
0
            } else if (IdIsXOnlyPubKey(i)) {
34
0
                const XOnlyPubKey pubkey{privkey.GetPubKey()};
35
0
                keys_str[i] = HexStr(pubkey);
36
0
            } else {
37
0
                keys_str[i] = EncodeSecret(privkey);
38
0
            }
39
0
        } else {
40
0
            CExtKey ext_privkey;
41
0
            ext_privkey.SetSeed(key_data);
42
0
            if (IdIsXprv(i)) {
43
0
                keys_str[i] = EncodeExtKey(ext_privkey);
44
0
            } else {
45
0
                const CExtPubKey ext_pubkey{ext_privkey.Neuter()};
46
0
                keys_str[i] = EncodeExtPubKey(ext_pubkey);
47
0
            }
48
0
        }
49
0
    }
50
0
}
51
52
238k
std::optional<uint8_t> MockedDescriptorConverter::IdxFromHex(std::string_view hex_characters) const {
53
238k
    if (hex_characters.size() != 2) return {};
54
238k
    auto idx = ParseHex(hex_characters);
55
238k
    if (idx.size() != 1) return {};
56
238k
    return idx[0];
57
238k
}
58
59
15.6k
std::optional<std::string> MockedDescriptorConverter::GetDescriptor(std::string_view mocked_desc) const {
60
    // The smallest fragment would be "pk(%00)"
61
15.6k
    if (mocked_desc.size() < 7) return {};
62
63
    // The actual descriptor string to be returned.
64
15.5k
    std::string desc;
65
15.5k
    desc.reserve(mocked_desc.size());
66
67
    // Replace all occurrences of '%' followed by two hex characters with the corresponding key.
68
8.79M
    for (size_t i = 0; i < mocked_desc.size();) {
69
8.78M
        if (mocked_desc[i] == '%') {
70
238k
            if (i + 3 >= mocked_desc.size()) return {};
71
238k
            if (const auto idx = IdxFromHex(mocked_desc.substr(i + 1, 2))) {
72
238k
                desc += keys_str[*idx];
73
238k
                i += 3;
74
238k
            } else {
75
49
                return {};
76
49
            }
77
8.54M
        } else {
78
8.54M
            desc += mocked_desc[i++];
79
8.54M
        }
80
8.78M
    }
81
82
15.4k
    return desc;
83
15.5k
}
84
85
bool HasDeepDerivPath(std::span<const uint8_t> buff, const int max_depth)
86
18.7k
{
87
18.7k
    auto depth{0};
88
39.8M
    for (const auto& ch: buff) {
89
39.8M
        if (ch == ',') {
90
            // A comma is always present between two key expressions, so we use that as a delimiter.
91
1.04M
            depth = 0;
92
38.8M
        } else if (ch == '/') {
93
189k
            if (++depth > max_depth) return true;
94
189k
        }
95
39.8M
    }
96
18.7k
    return false;
97
18.7k
}
98
99
bool HasTooManySubFrag(std::span<const uint8_t> buff, const int max_subs, const size_t max_nested_subs)
100
19.7k
{
101
    // We use a stack because there may be many nested sub-frags.
102
19.7k
    std::stack<int> counts;
103
43.2M
    for (const auto& ch: buff) {
104
        // The fuzzer may generate an input with a ton of parentheses. Rule out pathological cases.
105
43.2M
        if (counts.size() > max_nested_subs) return true;
106
107
43.2M
        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
796k
            counts.push(1);
111
42.4M
        } 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
1.22M
            if (++counts.top() > max_subs) return true;
115
41.2M
        } else if (ch == ')' && !counts.empty()) {
116
            // Fragment closed! Drop its sub count and resume to counting the number of subs for its parent.
117
550k
            counts.pop();
118
550k
        }
119
43.2M
    }
120
19.7k
    return false;
121
19.7k
}
122
123
bool HasTooManyWrappers(std::span<const uint8_t> buff, const int max_wrappers)
124
19.7k
{
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
19.7k
    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
42.2M
    for (const auto ch: buff | std::views::reverse) {
135
        // A colon, start counting.
136
42.2M
        if (ch == ':') {
137
            // The colon itself is not a wrapper so we start at 0.
138
711k
            count = 0;
139
41.4M
        } 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
6.35M
            if (ch == ',' || ch == '(' || ch == '{') {
145
710k
                count.reset();
146
5.64M
            } else if (++*count > max_wrappers) {
147
12
                return true;
148
12
            }
149
6.35M
        }
150
42.2M
    }
151
152
19.7k
    return false;
153
19.7k
}
154
155
bool HasTooLargeLeafSize(std::span<const uint8_t> buff, const uint32_t max_leaf_size)
156
18.7k
{
157
18.7k
    uint32_t leaf_len{0};
158
39.1M
    for (auto c : buff) {
159
39.1M
        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
1.95M
            leaf_len = 0;
163
37.1M
        } 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
37.1M
            if (++leaf_len > max_leaf_size) {
168
8
                return true;
169
8
            }
170
37.1M
        }
171
39.1M
    }
172
18.7k
    return false;
173
18.7k
}