|
| 1 | +// Copyright (c) 2013-2021 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 <core_io.h> |
| 6 | +#include <consensus/tx_check.h> |
| 7 | +#include <consensus/validation.h> |
| 8 | +#include <hash.h> |
| 9 | +#include <script/interpreter.h> |
| 10 | +#include <serialize.h> |
| 11 | +#include <streams.h> |
| 12 | +#include <test/data/ctvhash.json.h> |
| 13 | +#include <test/util/json.h> |
| 14 | +#include <test/util/setup_common.h> |
| 15 | +#include <util/strencodings.h> |
| 16 | +#include <common/system.h> |
| 17 | +#include <random.h> |
| 18 | + |
| 19 | +#include <boost/test/unit_test.hpp> |
| 20 | + |
| 21 | +#include <univalue.h> |
| 22 | + |
| 23 | +BOOST_FIXTURE_TEST_SUITE(ctvhash_tests, BasicTestingSetup) |
| 24 | + |
| 25 | +// Goal: check that CTV Hash Functions generate correct hash |
| 26 | +BOOST_AUTO_TEST_CASE(ctvhash_from_data) |
| 27 | +{ |
| 28 | + UniValue tests = read_json(std::string(json_tests::ctvhash)); |
| 29 | + |
| 30 | + for (unsigned int idx = 0; idx < tests.size(); idx++) { |
| 31 | + const UniValue& test = tests[idx]; |
| 32 | + std::string strTest = test.write(); |
| 33 | + // comment |
| 34 | + if (test.isStr()) |
| 35 | + continue; |
| 36 | + else if (test.isObject()) { |
| 37 | + std::vector<uint256> hash; |
| 38 | + std::vector<uint32_t> spend_index; |
| 39 | + |
| 40 | + try { |
| 41 | + auto& hash_arr = test["result"].get_array(); |
| 42 | + for (size_t i = 0; i < hash_arr.size(); ++i) { |
| 43 | + auto test_hash = uint256::FromHex(hash_arr[i].get_str()); |
| 44 | + |
| 45 | + if (!test_hash) { |
| 46 | + BOOST_ERROR("Bad test: invalid hex string: " << strTest); |
| 47 | + continue; |
| 48 | + } |
| 49 | + |
| 50 | + hash.push_back(*test_hash); |
| 51 | + // reverse because python's sha256().digest().hex() is backwards |
| 52 | + std::reverse(hash.back().begin(), hash.back().end()); |
| 53 | + } |
| 54 | + } catch (...) { |
| 55 | + BOOST_ERROR("Bad test: Results could not be deserialized: " << strTest); |
| 56 | + break; |
| 57 | + } |
| 58 | + try { |
| 59 | + auto& spend_index_arr = test["spend_index"].get_array(); |
| 60 | + for (size_t i = 0; i < spend_index_arr.size(); ++i) { |
| 61 | + spend_index.emplace_back(spend_index_arr[i].getInt<uint32_t>()); |
| 62 | + } |
| 63 | + } catch (...) { |
| 64 | + BOOST_ERROR("Bad test: spend_index could not be deserialized: " << strTest); |
| 65 | + break; |
| 66 | + } |
| 67 | + if (spend_index.size() != hash.size()) { |
| 68 | + BOOST_ERROR("Bad test: Spend Indexes not same length as Result Hashes: " << strTest); |
| 69 | + break; |
| 70 | + } |
| 71 | + CMutableTransaction tx; |
| 72 | + try { |
| 73 | + // deserialize test data |
| 74 | + BOOST_CHECK(DecodeHexTx(tx, test["hex_tx"].get_str())); |
| 75 | + } catch (...) { |
| 76 | + BOOST_ERROR("Bad test, couldn't deserialize hex_tx: " << strTest); |
| 77 | + continue; |
| 78 | + } |
| 79 | + const PrecomputedTransactionData data{tx}; |
| 80 | + for (size_t i = 0; i < hash.size(); ++i) { |
| 81 | + uint256 sh = GetDefaultCheckTemplateVerifyHash(tx, data.m_outputs_single_hash, data.m_sequences_single_hash, spend_index[i]); |
| 82 | + if (sh != hash[i]) { |
| 83 | + BOOST_ERROR("Expected: " << sh << " Got: " << hash[i] << " For:\n" |
| 84 | + << strTest); |
| 85 | + } |
| 86 | + } |
| 87 | + // Change all of the outpoints and there should be no difference. |
| 88 | + FastRandomContext fr; |
| 89 | + |
| 90 | + for (auto i = 0; i < 200; ++i) { |
| 91 | + CMutableTransaction txc = tx; |
| 92 | + bool hash_will_change = false; |
| 93 | + // do n mutations, 50% of being 1, 50% chance of being 2-11 |
| 94 | + const uint64_t n_mutations = fr.randbool()? (fr.randrange(10)+2) : 1; |
| 95 | + for (uint64_t j = 0; j < n_mutations; ++j) { |
| 96 | + // on the first 50 passes, modify in ways that will not change hash |
| 97 | + const int mutate_field = i < 50 ? fr.randrange(2) : fr.randrange(10); |
| 98 | + switch (mutate_field) { |
| 99 | + case 0: { |
| 100 | + if (tx.vin.size() > 0) { |
| 101 | + // no need to rejection sample on 256 bits |
| 102 | + const auto which = fr.randrange(tx.vin.size()); |
| 103 | + tx.vin[which].prevout = {Txid::FromUint256(fr.rand256()), fr.rand32()}; |
| 104 | + } |
| 105 | + break; |
| 106 | + } |
| 107 | + case 1: { |
| 108 | + if (tx.vin.size() > 0) { |
| 109 | + const auto which = fr.randrange(tx.vin.size()); |
| 110 | + tx.vin[which].scriptWitness.stack.push_back(fr.randbytes(500)); |
| 111 | + } |
| 112 | + break; |
| 113 | + } |
| 114 | + case 2: { |
| 115 | + // Mutate a scriptSig |
| 116 | + txc.vin[0].scriptSig.push_back('x'); |
| 117 | + hash_will_change = true; |
| 118 | + break; |
| 119 | + } |
| 120 | + case 3: { |
| 121 | + // Mutate a sequence |
| 122 | + do { |
| 123 | + txc.vin.back().nSequence = fr.rand32(); |
| 124 | + } while (txc.vin.back().nSequence == tx.vin.back().nSequence); |
| 125 | + hash_will_change = true; |
| 126 | + break; |
| 127 | + } |
| 128 | + case 4: { |
| 129 | + // Mutate version |
| 130 | + do { |
| 131 | + txc.version = fr.rand<int32_t>(); |
| 132 | + } while (txc.version == tx.version); |
| 133 | + hash_will_change = true; |
| 134 | + break; |
| 135 | + } |
| 136 | + case 5: { |
| 137 | + if (tx.vin.size() > 0) { |
| 138 | + // Mutate output amount |
| 139 | + const auto which = fr.randrange(tx.vout.size()); |
| 140 | + txc.vout[which].nValue += 1; |
| 141 | + hash_will_change = true; |
| 142 | + } |
| 143 | + break; |
| 144 | + } |
| 145 | + case 6: { |
| 146 | + if (tx.vin.size() > 0) { |
| 147 | + // Mutate output script |
| 148 | + const auto which = fr.randrange(tx.vout.size()); |
| 149 | + txc.vout[which].scriptPubKey.push_back('x'); |
| 150 | + hash_will_change = true; |
| 151 | + } |
| 152 | + break; |
| 153 | + } |
| 154 | + case 7: { |
| 155 | + // Mutate nLockTime |
| 156 | + do { |
| 157 | + txc.nLockTime = fr.rand32(); |
| 158 | + } while (txc.nLockTime == tx.nLockTime); |
| 159 | + hash_will_change = true; |
| 160 | + break; |
| 161 | + } |
| 162 | + case 8: { |
| 163 | + // don't add and remove for a mutation otherwise it may end up valid |
| 164 | + break; |
| 165 | + } |
| 166 | + case 9: { |
| 167 | + // don't add and remove for a mutation otherwise it may end up valid |
| 168 | + break; |
| 169 | + } |
| 170 | + default: |
| 171 | + assert(0); |
| 172 | + } |
| 173 | + } |
| 174 | + const PrecomputedTransactionData data_txc{txc}; |
| 175 | + // iterate twice, one time with the correct spend indexes, one time with incorrect. |
| 176 | + for (auto use_random_index = 0; use_random_index < 2; ++use_random_index) { |
| 177 | + hash_will_change |= use_random_index != 0; |
| 178 | + for (size_t i = 0; i < hash.size(); ++i) { |
| 179 | + uint32_t index{spend_index[i]}; |
| 180 | + while (use_random_index && index == spend_index[i]) { |
| 181 | + index = fr.rand32(); |
| 182 | + } |
| 183 | + uint256 sh = GetDefaultCheckTemplateVerifyHash(txc, data_txc.m_outputs_single_hash, data_txc.m_sequences_single_hash, index); |
| 184 | + const bool hash_equals = sh == hash[i]; |
| 185 | + if (hash_will_change && hash_equals) { |
| 186 | + BOOST_ERROR("Expected: NOT " << hash[i] << " Got: " << sh << " For:\n" |
| 187 | + << strTest); |
| 188 | + } else if (!hash_will_change && !hash_equals) { |
| 189 | + BOOST_ERROR("Expected: " << hash[i] << " Got: " << sh << " For:\n" |
| 190 | + << strTest); |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + |
| 197 | + } else { |
| 198 | + BOOST_ERROR("Bad test: " << strTest); |
| 199 | + continue; |
| 200 | + } |
| 201 | + } |
| 202 | +} |
| 203 | +BOOST_AUTO_TEST_SUITE_END() |
0 commit comments