/root/bitcoin/src/test/fuzz/util/descriptor.cpp
Line | Count | Source (jump to first uncovered line) |
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 <ranges> |
8 | | #include <stack> |
9 | | |
10 | 0 | void MockedDescriptorConverter::Init() { |
11 | | // The data to use as a private key or a seed for an xprv. |
12 | 0 | std::array<std::byte, 32> key_data{std::byte{1}}; |
13 | | // Generate keys of all kinds and store them in the keys array. |
14 | 0 | for (size_t i{0}; i < TOTAL_KEYS_GENERATED; i++) { |
15 | 0 | key_data[31] = std::byte(i); |
16 | | |
17 | | // If this is a "raw" key, generate a normal privkey. Otherwise generate |
18 | | // an extended one. |
19 | 0 | if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) { |
20 | 0 | CKey privkey; |
21 | 0 | privkey.Set(key_data.begin(), key_data.end(), !IdIsUnCompPubKey(i)); |
22 | 0 | if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) { |
23 | 0 | CPubKey pubkey{privkey.GetPubKey()}; |
24 | 0 | keys_str[i] = HexStr(pubkey); |
25 | 0 | } else if (IdIsXOnlyPubKey(i)) { |
26 | 0 | const XOnlyPubKey pubkey{privkey.GetPubKey()}; |
27 | 0 | keys_str[i] = HexStr(pubkey); |
28 | 0 | } else { |
29 | 0 | keys_str[i] = EncodeSecret(privkey); |
30 | 0 | } |
31 | 0 | } else { |
32 | 0 | CExtKey ext_privkey; |
33 | 0 | ext_privkey.SetSeed(key_data); |
34 | 0 | if (IdIsXprv(i)) { |
35 | 0 | keys_str[i] = EncodeExtKey(ext_privkey); |
36 | 0 | } else { |
37 | 0 | const CExtPubKey ext_pubkey{ext_privkey.Neuter()}; |
38 | 0 | keys_str[i] = EncodeExtPubKey(ext_pubkey); |
39 | 0 | } |
40 | 0 | } |
41 | 0 | } |
42 | 0 | } |
43 | | |
44 | 0 | std::optional<uint8_t> MockedDescriptorConverter::IdxFromHex(std::string_view hex_characters) const { |
45 | 0 | if (hex_characters.size() != 2) return {}; |
46 | 0 | auto idx = ParseHex(hex_characters); |
47 | 0 | if (idx.size() != 1) return {}; |
48 | 0 | return idx[0]; |
49 | 0 | } |
50 | | |
51 | 0 | std::optional<std::string> MockedDescriptorConverter::GetDescriptor(std::string_view mocked_desc) const { |
52 | | // The smallest fragment would be "pk(%00)" |
53 | 0 | if (mocked_desc.size() < 7) return {}; |
54 | | |
55 | | // The actual descriptor string to be returned. |
56 | 0 | std::string desc; |
57 | 0 | desc.reserve(mocked_desc.size()); |
58 | | |
59 | | // Replace all occurrences of '%' followed by two hex characters with the corresponding key. |
60 | 0 | for (size_t i = 0; i < mocked_desc.size();) { |
61 | 0 | if (mocked_desc[i] == '%') { |
62 | 0 | if (i + 3 >= mocked_desc.size()) return {}; |
63 | 0 | if (const auto idx = IdxFromHex(mocked_desc.substr(i + 1, 2))) { |
64 | 0 | desc += keys_str[*idx]; |
65 | 0 | i += 3; |
66 | 0 | } else { |
67 | 0 | return {}; |
68 | 0 | } |
69 | 0 | } else { |
70 | 0 | desc += mocked_desc[i++]; |
71 | 0 | } |
72 | 0 | } |
73 | | |
74 | 0 | return desc; |
75 | 0 | } |
76 | | |
77 | | bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth) |
78 | 0 | { |
79 | 0 | auto depth{0}; |
80 | 0 | for (const auto& ch: buff) { |
81 | 0 | if (ch == ',') { |
82 | | // A comma is always present between two key expressions, so we use that as a delimiter. |
83 | 0 | depth = 0; |
84 | 0 | } else if (ch == '/') { |
85 | 0 | if (++depth > max_depth) return true; |
86 | 0 | } |
87 | 0 | } |
88 | 0 | return false; |
89 | 0 | } |
90 | | |
91 | | bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs, const size_t max_nested_subs) |
92 | 0 | { |
93 | | // We use a stack because there may be many nested sub-frags. |
94 | 0 | std::stack<int> counts; |
95 | 0 | for (const auto& ch: buff) { |
96 | | // The fuzzer may generate an input with a ton of parentheses. Rule out pathological cases. |
97 | 0 | if (counts.size() > max_nested_subs) return true; |
98 | | |
99 | 0 | if (ch == '(') { |
100 | | // A new fragment was opened, create a new sub-count for it and start as one since any fragment with |
101 | | // parentheses has at least one sub. |
102 | 0 | counts.push(1); |
103 | 0 | } else if (ch == ',' && !counts.empty()) { |
104 | | // When encountering a comma, account for an additional sub in the last opened fragment. If it exceeds the |
105 | | // limit, bail. |
106 | 0 | if (++counts.top() > max_subs) return true; |
107 | 0 | } else if (ch == ')' && !counts.empty()) { |
108 | | // Fragment closed! Drop its sub count and resume to counting the number of subs for its parent. |
109 | 0 | counts.pop(); |
110 | 0 | } |
111 | 0 | } |
112 | 0 | return false; |
113 | 0 | } |
114 | | |
115 | | bool HasTooManyWrappers(const FuzzBufferType& buff, const int max_wrappers) |
116 | 0 | { |
117 | | // The number of nested wrappers. Nested wrappers are always characters which follow each other so we don't have to |
118 | | // use a stack as we do above when counting the number of sub-fragments. |
119 | 0 | std::optional<int> count; |
120 | | |
121 | | // We want to detect nested wrappers. A wrapper is a character prepended to a fragment, separated by a colon. There |
122 | | // may be more than one wrapper, in which case the colon is not repeated. For instance `jjjjj:pk()`. To count |
123 | | // wrappers we iterate in reverse and use the colon to detect the end of a wrapper expression and count how many |
124 | | // characters there are since the beginning of the expression. We stop counting when we encounter a character |
125 | | // indicating the beginning of a new expression. |
126 | 0 | for (const auto ch: buff | std::views::reverse) { |
127 | | // A colon, start counting. |
128 | 0 | if (ch == ':') { |
129 | | // The colon itself is not a wrapper so we start at 0. |
130 | 0 | count = 0; |
131 | 0 | } else if (count) { |
132 | | // If we are counting wrappers, stop when we crossed the beginning of the wrapper expression. Otherwise keep |
133 | | // counting and bail if we reached the limit. |
134 | | // A wrapper may only ever occur as the first sub of a descriptor/miniscript expression ('('), as the |
135 | | // first Taproot leaf in a pair ('{') or as the nth sub in each case (','). |
136 | 0 | if (ch == ',' || ch == '(' || ch == '{') { |
137 | 0 | count.reset(); |
138 | 0 | } else if (++*count > max_wrappers) { |
139 | 0 | return true; |
140 | 0 | } |
141 | 0 | } |
142 | 0 | } |
143 | | |
144 | 0 | return false; |
145 | 0 | } |