Coverage Report

Created: 2025-06-06 15:08

/root/bitcoin/src/test/fuzz/script_assets_test_minimizer.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2020-2022 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/fuzz.h>
6
7
#include <primitives/transaction.h>
8
#include <pubkey.h>
9
#include <script/interpreter.h>
10
#include <serialize.h>
11
#include <streams.h>
12
#include <univalue.h>
13
#include <util/strencodings.h>
14
#include <util/string.h>
15
16
#include <cstdint>
17
#include <string>
18
#include <vector>
19
20
using util::SplitString;
21
22
// This fuzz "test" can be used to minimize test cases for script_assets_test in
23
// src/test/script_tests.cpp. While it written as a fuzz test, and can be used as such,
24
// fuzzing the inputs is unlikely to construct useful test cases.
25
//
26
// Instead, it is primarily intended to be run on a test set that was generated
27
// externally, for example using test/functional/feature_taproot.py's --dumptests mode.
28
// The minimized set can then be concatenated together, surrounded by '[' and ']',
29
// and used as the script_assets_test.json input to the script_assets_test unit test:
30
//
31
// (normal build)
32
// $ mkdir dump
33
// $ for N in $(seq 1 10); do TEST_DUMP_DIR=dump test/functional/feature_taproot.py --dumptests; done
34
// $ ...
35
//
36
// (libFuzzer build)
37
// $ mkdir dump-min
38
// $ FUZZ=script_assets_test_minimizer ./bin/fuzz -merge=1 -use_value_profile=1 dump-min/ dump/
39
// $ (echo -en '[\n'; cat dump-min/* | head -c -2; echo -en '\n]') >script_assets_test.json
40
41
namespace {
42
43
std::vector<unsigned char> CheckedParseHex(const std::string& str)
44
0
{
45
0
    if (str.size() && !IsHex(str)) throw std::runtime_error("Non-hex input '" + str + "'");
46
0
    return ParseHex(str);
47
0
}
48
49
CScript ScriptFromHex(const std::string& str)
50
0
{
51
0
    std::vector<unsigned char> data = CheckedParseHex(str);
52
0
    return CScript(data.begin(), data.end());
53
0
}
54
55
CMutableTransaction TxFromHex(const std::string& str)
56
0
{
57
0
    CMutableTransaction tx;
58
0
    try {
59
0
        SpanReader{CheckedParseHex(str)} >> TX_NO_WITNESS(tx);
60
0
    } catch (const std::ios_base::failure&) {
61
0
        throw std::runtime_error("Tx deserialization failure");
62
0
    }
63
0
    return tx;
64
0
}
65
66
std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
67
0
{
68
0
    if (!univalue.isArray()) throw std::runtime_error("Prevouts must be array");
69
0
    std::vector<CTxOut> prevouts;
70
0
    for (size_t i = 0; i < univalue.size(); ++i) {
71
0
        CTxOut txout;
72
0
        try {
73
0
            SpanReader{CheckedParseHex(univalue[i].get_str())} >> txout;
74
0
        } catch (const std::ios_base::failure&) {
75
0
            throw std::runtime_error("Prevout invalid format");
76
0
        }
77
0
        prevouts.push_back(std::move(txout));
78
0
    }
79
0
    return prevouts;
80
0
}
81
82
CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
83
0
{
84
0
    if (!univalue.isArray()) throw std::runtime_error("Script witness is not array");
85
0
    CScriptWitness scriptwitness;
86
0
    for (size_t i = 0; i < univalue.size(); ++i) {
87
0
        auto bytes = CheckedParseHex(univalue[i].get_str());
88
0
        scriptwitness.stack.push_back(std::move(bytes));
89
0
    }
90
0
    return scriptwitness;
91
0
}
92
93
const std::map<std::string, unsigned int> FLAG_NAMES = {
94
    {std::string("P2SH"), (unsigned int)SCRIPT_VERIFY_P2SH},
95
    {std::string("DERSIG"), (unsigned int)SCRIPT_VERIFY_DERSIG},
96
    {std::string("NULLDUMMY"), (unsigned int)SCRIPT_VERIFY_NULLDUMMY},
97
    {std::string("CHECKLOCKTIMEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY},
98
    {std::string("CHECKSEQUENCEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKSEQUENCEVERIFY},
99
    {std::string("WITNESS"), (unsigned int)SCRIPT_VERIFY_WITNESS},
100
    {std::string("TAPROOT"), (unsigned int)SCRIPT_VERIFY_TAPROOT},
101
};
102
103
std::vector<unsigned int> AllFlags()
104
0
{
105
0
    std::vector<unsigned int> ret;
106
107
0
    for (unsigned int i = 0; i < 128; ++i) {
108
0
        unsigned int flag = 0;
109
0
        if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
110
0
        if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
111
0
        if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
112
0
        if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
113
0
        if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
114
0
        if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
115
0
        if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
116
117
        // SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
118
0
        if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
119
        // SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
120
0
        if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
121
122
0
        ret.push_back(flag);
123
0
    }
124
125
0
    return ret;
126
0
}
127
128
const std::vector<unsigned int> ALL_FLAGS = AllFlags();
129
130
unsigned int ParseScriptFlags(const std::string& str)
131
0
{
132
0
    if (str.empty()) return 0;
133
134
0
    unsigned int flags = 0;
135
0
    std::vector<std::string> words = SplitString(str, ',');
136
137
0
    for (const std::string& word : words) {
138
0
        auto it = FLAG_NAMES.find(word);
139
0
        if (it == FLAG_NAMES.end()) throw std::runtime_error("Unknown verification flag " + word);
140
0
        flags |= it->second;
141
0
    }
142
143
0
    return flags;
144
0
}
145
146
void Test(const std::string& str)
147
0
{
148
0
    UniValue test;
149
0
    if (!test.read(str) || !test.isObject()) throw std::runtime_error("Non-object test input");
150
151
0
    CMutableTransaction tx = TxFromHex(test["tx"].get_str());
152
0
    const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
153
0
    if (prevouts.size() != tx.vin.size()) throw std::runtime_error("Incorrect number of prevouts");
154
0
    size_t idx = test["index"].getInt<int64_t>();
155
0
    if (idx >= tx.vin.size()) throw std::runtime_error("Invalid index");
156
0
    unsigned int test_flags = ParseScriptFlags(test["flags"].get_str());
157
0
    bool final = test.exists("final") && test["final"].get_bool();
158
159
0
    if (test.exists("success")) {
160
0
        tx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
161
0
        tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
162
0
        PrecomputedTransactionData txdata;
163
0
        txdata.Init(tx, std::vector<CTxOut>(prevouts));
164
0
        MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
165
0
        for (const auto flags : ALL_FLAGS) {
166
            // "final": true tests are valid for all flags. Others are only valid with flags that are
167
            // a subset of test_flags.
168
0
            if (final || ((flags & test_flags) == flags)) {
169
0
                (void)VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
170
0
            }
171
0
        }
172
0
    }
173
174
0
    if (test.exists("failure")) {
175
0
        tx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
176
0
        tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
177
0
        PrecomputedTransactionData txdata;
178
0
        txdata.Init(tx, std::vector<CTxOut>(prevouts));
179
0
        MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
180
0
        for (const auto flags : ALL_FLAGS) {
181
            // If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
182
0
            if ((flags & test_flags) == test_flags) {
183
0
                (void)VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
184
0
            }
185
0
        }
186
0
    }
187
0
}
188
189
0
void test_init() {}
190
191
FUZZ_TARGET(script_assets_test_minimizer, .init = test_init, .hidden = true)
192
0
{
193
0
    if (buffer.size() < 2 || buffer.back() != '\n' || buffer[buffer.size() - 2] != ',') return;
194
0
    const std::string str((const char*)buffer.data(), buffer.size() - 2);
195
0
    try {
196
0
        Test(str);
197
0
    } catch (const std::runtime_error&) {
198
0
    }
199
0
}
200
201
} // namespace