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