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 <ranges>
8 : : #include <stack>
9 : :
10 : 2 : void MockedDescriptorConverter::Init() {
11 : : // The data to use as a private key or a seed for an xprv.
12 : 2 : std::array<std::byte, 32> key_data{std::byte{1}};
13 : : // Generate keys of all kinds and store them in the keys array.
14 [ + + ]: 514 : for (size_t i{0}; i < TOTAL_KEYS_GENERATED; i++) {
15 : 512 : 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 [ + + + + : 512 : if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) {
+ + + + ]
20 : 344 : CKey privkey;
21 [ + - ]: 344 : privkey.Set(key_data.begin(), key_data.end(), !IdIsUnCompPubKey(i));
22 [ + + + + ]: 344 : if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) {
23 [ + - ]: 172 : CPubKey pubkey{privkey.GetPubKey()};
24 [ + - ]: 172 : keys_str[i] = HexStr(pubkey);
25 [ + + ]: 172 : } else if (IdIsXOnlyPubKey(i)) {
26 [ + - ]: 86 : const XOnlyPubKey pubkey{privkey.GetPubKey()};
27 [ + - ]: 86 : keys_str[i] = HexStr(pubkey);
28 : : } else {
29 [ + - ]: 86 : keys_str[i] = EncodeSecret(privkey);
30 : : }
31 : 344 : } else {
32 [ + - ]: 168 : CExtKey ext_privkey;
33 [ + - ]: 168 : ext_privkey.SetSeed(key_data);
34 [ + + ]: 168 : if (IdIsXprv(i)) {
35 [ + - ]: 84 : keys_str[i] = EncodeExtKey(ext_privkey);
36 : : } else {
37 [ + - ]: 84 : const CExtPubKey ext_pubkey{ext_privkey.Neuter()};
38 [ + - ]: 84 : keys_str[i] = EncodeExtPubKey(ext_pubkey);
39 : : }
40 : 168 : }
41 : : }
42 : 2 : }
43 : :
44 : 360520 : std::optional<uint8_t> MockedDescriptorConverter::IdxFromHex(std::string_view hex_characters) const {
45 [ - + ]: 360520 : if (hex_characters.size() != 2) return {};
46 : 360520 : auto idx = ParseHex(hex_characters);
47 [ + + ]: 360520 : if (idx.size() != 1) return {};
48 : 360466 : return idx[0];
49 : 360520 : }
50 : :
51 : 10485 : std::optional<std::string> MockedDescriptorConverter::GetDescriptor(std::string_view mocked_desc) const {
52 : : // The smallest fragment would be "pk(%00)"
53 [ + + ]: 10485 : if (mocked_desc.size() < 7) return {};
54 : :
55 : : // The actual descriptor string to be returned.
56 [ + - ]: 10415 : std::string desc;
57 [ + - ]: 10415 : desc.reserve(mocked_desc.size());
58 : :
59 : : // Replace all occurrences of '%' followed by two hex characters with the corresponding key.
60 : 7587304 : for (size_t i = 0; i < mocked_desc.size();) {
61 [ + + ]: 7576951 : if (mocked_desc[i] == '%') {
62 [ + + ]: 360528 : if (i + 3 >= mocked_desc.size()) return {};
63 [ + - + - : 360520 : if (const auto idx = IdxFromHex(mocked_desc.substr(i + 1, 2))) {
+ + ]
64 [ + - ]: 360466 : desc += keys_str[*idx];
65 : : i += 3;
66 : : } else {
67 : 54 : return {};
68 : : }
69 : : } else {
70 [ + - + + ]: 14803727 : desc += mocked_desc[i++];
71 : : }
72 : : }
73 : :
74 : 10353 : return desc;
75 : 10415 : }
76 : :
77 : 12986 : bool HasDeepDerivPath(const FuzzBufferType& buff, const int max_depth)
78 : : {
79 : 12986 : auto depth{0};
80 [ + + ]: 18106297 : for (const auto& ch: buff) {
81 [ + + ]: 18093319 : if (ch == ',') {
82 : : // A comma is always present between two key expressions, so we use that as a delimiter.
83 : : depth = 0;
84 [ + + ]: 17452336 : } else if (ch == '/') {
85 [ + + ]: 141755 : if (++depth > max_depth) return true;
86 : : }
87 : : }
88 : : return false;
89 : : }
90 : :
91 : 5529 : bool HasTooManySubFrag(const FuzzBufferType& buff, const int max_subs, const size_t max_nested_subs)
92 : : {
93 : : // We use a stack because there may be many nested sub-frags.
94 : 5529 : std::stack<int> counts;
95 [ + + ]: 14839064 : for (const auto& ch: buff) {
96 : : // The fuzzer may generate an input with a ton of parentheses. Rule out pathological cases.
97 [ + + ]: 14833540 : if (counts.size() > max_nested_subs) return true;
98 : :
99 [ + + ]: 14833537 : 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 [ + - ]: 15145984 : counts.push(1);
103 [ + + + + ]: 14521088 : } 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 [ + + ]: 558866 : if (++counts.top() > max_subs) return true;
107 [ + + + + ]: 13962222 : } else if (ch == ')' && !counts.empty()) {
108 : : // Fragment closed! Drop its sub count and resume to counting the number of subs for its parent.
109 : 182357 : counts.pop();
110 : : }
111 : : }
112 : : return false;
113 : 5529 : }
114 : :
115 : 5524 : bool HasTooManyWrappers(const FuzzBufferType& buff, const int max_wrappers)
116 : : {
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 : 5524 : 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 [ + + ]: 11902772 : for (const auto ch: buff | std::views::reverse) {
127 : : // A colon, start counting.
128 [ + + ]: 11897253 : if (ch == ':') {
129 : : // The colon itself is not a wrapper so we start at 0.
130 : 280191 : count = 0;
131 [ + + ]: 11617062 : } 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 [ + + + + ]: 2072436 : if (ch == ',' || ch == '(' || ch == '{') {
137 : 279554 : count.reset();
138 [ + + ]: 1792882 : } else if (++*count > max_wrappers) {
139 : : return true;
140 : : }
141 : : }
142 : : }
143 : :
144 : : return false;
145 : : }
|