/Users/eugenesiegel/btc/bitcoin/src/script/solver.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2009-2010 Satoshi Nakamoto |
2 | | // Copyright (c) 2009-present The Bitcoin Core developers |
3 | | // Distributed under the MIT software license, see the accompanying |
4 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
5 | | |
6 | | #include <pubkey.h> |
7 | | #include <script/interpreter.h> |
8 | | #include <script/script.h> |
9 | | #include <script/solver.h> |
10 | | #include <span.h> |
11 | | |
12 | | #include <algorithm> |
13 | | #include <cassert> |
14 | | #include <string> |
15 | | |
16 | | typedef std::vector<unsigned char> valtype; |
17 | | |
18 | | std::string GetTxnOutputType(TxoutType t) |
19 | 132 | { |
20 | 132 | switch (t) { |
21 | 12 | case TxoutType::NONSTANDARD: return "nonstandard"; |
22 | 12 | case TxoutType::PUBKEY: return "pubkey"; |
23 | 12 | case TxoutType::PUBKEYHASH: return "pubkeyhash"; |
24 | 12 | case TxoutType::SCRIPTHASH: return "scripthash"; |
25 | 12 | case TxoutType::MULTISIG: return "multisig"; |
26 | 12 | case TxoutType::NULL_DATA: return "nulldata"; |
27 | 12 | case TxoutType::ANCHOR: return "anchor"; |
28 | 12 | case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash"; |
29 | 12 | case TxoutType::WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash"; |
30 | 12 | case TxoutType::WITNESS_V1_TAPROOT: return "witness_v1_taproot"; |
31 | 12 | case TxoutType::WITNESS_UNKNOWN: return "witness_unknown"; |
32 | 132 | } // no default case, so the compiler can warn about missing cases |
33 | 0 | assert(false); |
34 | 0 | } |
35 | | |
36 | | static bool MatchPayToPubkey(const CScript& script, valtype& pubkey) |
37 | 0 | { |
38 | 0 | if (script.size() == CPubKey::SIZE + 2 && script[0] == CPubKey::SIZE && script.back() == OP_CHECKSIG) { |
39 | 0 | pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::SIZE + 1); |
40 | 0 | return CPubKey::ValidSize(pubkey); |
41 | 0 | } |
42 | 0 | if (script.size() == CPubKey::COMPRESSED_SIZE + 2 && script[0] == CPubKey::COMPRESSED_SIZE && script.back() == OP_CHECKSIG) { |
43 | 0 | pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::COMPRESSED_SIZE + 1); |
44 | 0 | return CPubKey::ValidSize(pubkey); |
45 | 0 | } |
46 | 0 | return false; |
47 | 0 | } |
48 | | |
49 | | static bool MatchPayToPubkeyHash(const CScript& script, valtype& pubkeyhash) |
50 | 0 | { |
51 | 0 | if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160 && script[2] == 20 && script[23] == OP_EQUALVERIFY && script[24] == OP_CHECKSIG) { |
52 | 0 | pubkeyhash = valtype(script.begin () + 3, script.begin() + 23); |
53 | 0 | return true; |
54 | 0 | } |
55 | 0 | return false; |
56 | 0 | } |
57 | | |
58 | | /** Test for "small positive integer" script opcodes - OP_1 through OP_16. */ |
59 | | static constexpr bool IsSmallInteger(opcodetype opcode) |
60 | 0 | { |
61 | 0 | return opcode >= OP_1 && opcode <= OP_16; |
62 | 0 | } |
63 | | |
64 | | /** Retrieve a minimally-encoded number in range [min,max] from an (opcode, data) pair, |
65 | | * whether it's OP_n or through a push. */ |
66 | | static std::optional<int> GetScriptNumber(opcodetype opcode, valtype data, int min, int max) |
67 | 0 | { |
68 | 0 | int count; |
69 | 0 | if (IsSmallInteger(opcode)) { |
70 | 0 | count = CScript::DecodeOP_N(opcode); |
71 | 0 | } else if (IsPushdataOp(opcode)) { |
72 | 0 | if (!CheckMinimalPush(data, opcode)) return {}; |
73 | 0 | try { |
74 | 0 | count = CScriptNum(data, /* fRequireMinimal = */ true).getint(); |
75 | 0 | } catch (const scriptnum_error&) { |
76 | 0 | return {}; |
77 | 0 | } |
78 | 0 | } else { |
79 | 0 | return {}; |
80 | 0 | } |
81 | 0 | if (count < min || count > max) return {}; |
82 | 0 | return count; |
83 | 0 | } |
84 | | |
85 | | static bool MatchMultisig(const CScript& script, int& required_sigs, std::vector<valtype>& pubkeys) |
86 | 0 | { |
87 | 0 | opcodetype opcode; |
88 | 0 | valtype data; |
89 | |
|
90 | 0 | CScript::const_iterator it = script.begin(); |
91 | 0 | if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false; |
92 | | |
93 | 0 | if (!script.GetOp(it, opcode, data)) return false; |
94 | 0 | auto req_sigs = GetScriptNumber(opcode, data, 1, MAX_PUBKEYS_PER_MULTISIG); |
95 | 0 | if (!req_sigs) return false; |
96 | 0 | required_sigs = *req_sigs; |
97 | 0 | while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) { |
98 | 0 | pubkeys.emplace_back(std::move(data)); |
99 | 0 | } |
100 | 0 | auto num_keys = GetScriptNumber(opcode, data, required_sigs, MAX_PUBKEYS_PER_MULTISIG); |
101 | 0 | if (!num_keys) return false; |
102 | 0 | if (pubkeys.size() != static_cast<unsigned long>(*num_keys)) return false; |
103 | | |
104 | 0 | return (it + 1 == script.end()); |
105 | 0 | } |
106 | | |
107 | | std::optional<std::pair<int, std::vector<std::span<const unsigned char>>>> MatchMultiA(const CScript& script) |
108 | 0 | { |
109 | 0 | std::vector<std::span<const unsigned char>> keyspans; |
110 | | |
111 | | // Redundant, but very fast and selective test. |
112 | 0 | if (script.size() == 0 || script[0] != 32 || script.back() != OP_NUMEQUAL) return {}; |
113 | | |
114 | | // Parse keys |
115 | 0 | auto it = script.begin(); |
116 | 0 | while (script.end() - it >= 34) { |
117 | 0 | if (*it != 32) return {}; |
118 | 0 | ++it; |
119 | 0 | keyspans.emplace_back(&*it, 32); |
120 | 0 | it += 32; |
121 | 0 | if (*it != (keyspans.size() == 1 ? OP_CHECKSIG : OP_CHECKSIGADD)) return {}; |
122 | 0 | ++it; |
123 | 0 | } |
124 | 0 | if (keyspans.size() == 0 || keyspans.size() > MAX_PUBKEYS_PER_MULTI_A) return {}; |
125 | | |
126 | | // Parse threshold. |
127 | 0 | opcodetype opcode; |
128 | 0 | std::vector<unsigned char> data; |
129 | 0 | if (!script.GetOp(it, opcode, data)) return {}; |
130 | 0 | if (it == script.end()) return {}; |
131 | 0 | if (*it != OP_NUMEQUAL) return {}; |
132 | 0 | ++it; |
133 | 0 | if (it != script.end()) return {}; |
134 | 0 | auto threshold = GetScriptNumber(opcode, data, 1, (int)keyspans.size()); |
135 | 0 | if (!threshold) return {}; |
136 | | |
137 | | // Construct result. |
138 | 0 | return std::pair{*threshold, std::move(keyspans)}; |
139 | 0 | } |
140 | | |
141 | | TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned char>>& vSolutionsRet) |
142 | 0 | { |
143 | 0 | vSolutionsRet.clear(); |
144 | | |
145 | | // Shortcut for pay-to-script-hash, which are more constrained than the other types: |
146 | | // it is always OP_HASH160 20 [20 byte hash] OP_EQUAL |
147 | 0 | if (scriptPubKey.IsPayToScriptHash()) |
148 | 0 | { |
149 | 0 | std::vector<unsigned char> hashBytes(scriptPubKey.begin()+2, scriptPubKey.begin()+22); |
150 | 0 | vSolutionsRet.push_back(hashBytes); |
151 | 0 | return TxoutType::SCRIPTHASH; |
152 | 0 | } |
153 | | |
154 | 0 | int witnessversion; |
155 | 0 | std::vector<unsigned char> witnessprogram; |
156 | 0 | if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) { |
157 | 0 | if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_KEYHASH_SIZE) { |
158 | 0 | vSolutionsRet.push_back(std::move(witnessprogram)); |
159 | 0 | return TxoutType::WITNESS_V0_KEYHASH; |
160 | 0 | } |
161 | 0 | if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) { |
162 | 0 | vSolutionsRet.push_back(std::move(witnessprogram)); |
163 | 0 | return TxoutType::WITNESS_V0_SCRIPTHASH; |
164 | 0 | } |
165 | 0 | if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE) { |
166 | 0 | vSolutionsRet.push_back(std::move(witnessprogram)); |
167 | 0 | return TxoutType::WITNESS_V1_TAPROOT; |
168 | 0 | } |
169 | 0 | if (scriptPubKey.IsPayToAnchor()) { |
170 | 0 | return TxoutType::ANCHOR; |
171 | 0 | } |
172 | 0 | if (witnessversion != 0) { |
173 | 0 | vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion}); |
174 | 0 | vSolutionsRet.push_back(std::move(witnessprogram)); |
175 | 0 | return TxoutType::WITNESS_UNKNOWN; |
176 | 0 | } |
177 | 0 | return TxoutType::NONSTANDARD; |
178 | 0 | } |
179 | | |
180 | | // Provably prunable, data-carrying output |
181 | | // |
182 | | // So long as script passes the IsUnspendable() test and all but the first |
183 | | // byte passes the IsPushOnly() test we don't care what exactly is in the |
184 | | // script. |
185 | 0 | if (scriptPubKey.size() >= 1 && scriptPubKey[0] == OP_RETURN && scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) { |
186 | 0 | return TxoutType::NULL_DATA; |
187 | 0 | } |
188 | | |
189 | 0 | std::vector<unsigned char> data; |
190 | 0 | if (MatchPayToPubkey(scriptPubKey, data)) { |
191 | 0 | vSolutionsRet.push_back(std::move(data)); |
192 | 0 | return TxoutType::PUBKEY; |
193 | 0 | } |
194 | | |
195 | 0 | if (MatchPayToPubkeyHash(scriptPubKey, data)) { |
196 | 0 | vSolutionsRet.push_back(std::move(data)); |
197 | 0 | return TxoutType::PUBKEYHASH; |
198 | 0 | } |
199 | | |
200 | 0 | int required; |
201 | 0 | std::vector<std::vector<unsigned char>> keys; |
202 | 0 | if (MatchMultisig(scriptPubKey, required, keys)) { |
203 | 0 | vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..20 |
204 | 0 | vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end()); |
205 | 0 | vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())}); // safe as size is in range 1..20 |
206 | 0 | return TxoutType::MULTISIG; |
207 | 0 | } |
208 | | |
209 | 0 | vSolutionsRet.clear(); |
210 | 0 | return TxoutType::NONSTANDARD; |
211 | 0 | } |
212 | | |
213 | | CScript GetScriptForRawPubKey(const CPubKey& pubKey) |
214 | 0 | { |
215 | 0 | return CScript() << std::vector<unsigned char>(pubKey.begin(), pubKey.end()) << OP_CHECKSIG; |
216 | 0 | } |
217 | | |
218 | | CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys) |
219 | 0 | { |
220 | 0 | CScript script; |
221 | |
|
222 | 0 | script << nRequired; |
223 | 0 | for (const CPubKey& key : keys) |
224 | 0 | script << ToByteVector(key); |
225 | 0 | script << keys.size() << OP_CHECKMULTISIG; |
226 | |
|
227 | 0 | return script; |
228 | 0 | } |