fuzz coverage

Coverage Report

Created: 2025-06-01 19:34

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