LCOV - code coverage report
Current view: top level - src/test/fuzz - script_assets_test_minimizer.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 27 115 23.5 %
Date: 2023-10-05 12:38:51 Functions: 5 14 35.7 %
Branches: 43 326 13.2 %

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

Generated by: LCOV version 1.14