/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 | } |