|
| 1 | +// Copyright (c) 2009-present 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 <addresstype.h> |
| 6 | +#include <consensus/amount.h> |
| 7 | +#include <policy/policy.h> |
| 8 | +#include <primitives/transaction.h> |
| 9 | +#include <script/interpreter.h> |
| 10 | +#include <script/script.h> |
| 11 | +#include <script/signingprovider.h> |
| 12 | +#include <test/fuzz/FuzzedDataProvider.h> |
| 13 | +#include <test/fuzz/fuzz.h> |
| 14 | +#include <test/fuzz/util.h> |
| 15 | +#include <test/util/script.h> |
| 16 | + |
| 17 | +//! Maximum number of inputs and outputs in transactions consumed from fuzzer. |
| 18 | +static constexpr int MAX_TX_IN_OUT{10'000}; |
| 19 | +//! Verification flags to set for script validation. |
| 20 | +static constexpr unsigned VERIFY_FLAGS{MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_TEMPLATEHASH}; |
| 21 | + |
| 22 | +static bool VerifyTemplateCheck(const CMutableTransaction& tx, unsigned int in_index, |
| 23 | + std::vector<CTxOut> spent_outputs, const CScript& spent_spk) |
| 24 | +{ |
| 25 | + constexpr auto mdb{MissingDataBehavior::ASSERT_FAIL}; |
| 26 | + constexpr CAmount dummy_am{0}; // We never check signatures. |
| 27 | + PrecomputedTransactionData precomp; |
| 28 | + precomp.Init(tx, std::move(spent_outputs)); |
| 29 | + const auto checker{GenericTransactionSignatureChecker(&tx, in_index, dummy_am, precomp, mdb)}; |
| 30 | + return VerifyScript(tx.vin[in_index].scriptSig, spent_spk, &tx.vin[in_index].scriptWitness, VERIFY_FLAGS, checker); |
| 31 | +} |
| 32 | + |
| 33 | +/** Target specialized on the new logic introduced for OP_TEMPLATEHASH. */ |
| 34 | +FUZZ_TARGET(gettemplatehash) |
| 35 | +{ |
| 36 | + FuzzedDataProvider provider(buffer.data(), buffer.size()); |
| 37 | + |
| 38 | + // First get the transaction for which to generate the template hash. It's unnecessary to |
| 39 | + // get the fuzzer to try to find a valid deserialization, as only the version+locktime is |
| 40 | + // used by GetTemplateHash(). |
| 41 | + CMutableTransaction tx; |
| 42 | + tx.version = provider.ConsumeIntegral<uint32_t>(); |
| 43 | + tx.nLockTime = provider.ConsumeIntegral<uint32_t>(); |
| 44 | + |
| 45 | + // Manually set the precomputed values used in the template hash. |
| 46 | + PrecomputedTransactionData precomp{tx}; |
| 47 | + precomp.m_sequences_single_hash = ConsumeUInt256(provider); |
| 48 | + precomp.m_outputs_single_hash = ConsumeUInt256(provider); |
| 49 | + precomp.m_bip341_taproot_ready = true; |
| 50 | + |
| 51 | + // Sometimes commit to the annex too. |
| 52 | + ScriptExecutionData execdata; |
| 53 | + execdata.m_annex_present = provider.ConsumeBool(); |
| 54 | + if (execdata.m_annex_present) { |
| 55 | + execdata.m_annex_hash = ConsumeUInt256(provider); |
| 56 | + } |
| 57 | + execdata.m_annex_init = true; |
| 58 | + |
| 59 | + // Finally, exercise the GetTemplateHash() function. |
| 60 | + const auto in_index{provider.ConsumeIntegral<unsigned>()}; |
| 61 | + const CAmount dummy_am{0}; |
| 62 | + const auto dummy_mdb{MissingDataBehavior::ASSERT_FAIL}; |
| 63 | + const auto checker{GenericTransactionSignatureChecker(&tx, in_index, dummy_am, precomp, dummy_mdb)}; |
| 64 | + (void)checker.GetTemplateHash(execdata); |
| 65 | +} |
| 66 | + |
| 67 | +/** Broader target which exercises the commit-to-spending transaction use case of OP_TEMPLATEHASH for |
| 68 | + * various fuzzer-provided transactions and asserts invariants. */ |
| 69 | +FUZZ_TARGET(spendtemplatehash) |
| 70 | +{ |
| 71 | + FuzzedDataProvider provider(buffer.data(), buffer.size()); |
| 72 | + |
| 73 | + // Compute the template hash for a fuzzer-provided transaction and input index. |
| 74 | + std::vector<uint8_t> annex; |
| 75 | + auto tx{ConsumeTransaction(provider, {}, MAX_TX_IN_OUT, MAX_TX_IN_OUT)}; |
| 76 | + if (tx.vin.empty()) return; |
| 77 | + const auto in_index{provider.ConsumeIntegralInRange<unsigned int>(0, tx.vin.size() - 1)}; |
| 78 | + const auto template_hash{GetTemplateHash(tx, in_index, &annex)}; |
| 79 | + |
| 80 | + // Construct the output script committing to this template hash. |
| 81 | + const auto leaf_script{CScript() << template_hash << OP_TEMPLATEHASH << OP_EQUAL}; |
| 82 | + TaprootBuilder builder; |
| 83 | + builder.Add(0, leaf_script, TAPROOT_LEAF_TAPSCRIPT); |
| 84 | + builder.Finalize(XOnlyPubKey::NUMS_H); |
| 85 | + const CScript spent_spk{GetScriptForDestination(builder.GetOutput())}; |
| 86 | + |
| 87 | + // Set the witness for this transaction input. |
| 88 | + const auto spend_data{builder.GetSpendData()}; |
| 89 | + const auto control_blocks{spend_data.scripts.begin()->second}; |
| 90 | + const auto& cb{*control_blocks.begin()}; |
| 91 | + tx.vin[in_index].scriptWitness.stack.clear(); |
| 92 | + tx.vin[in_index].scriptWitness.stack.emplace_back(leaf_script.begin(), leaf_script.end()); |
| 93 | + tx.vin[in_index].scriptWitness.stack.emplace_back(cb.begin(), cb.end()); |
| 94 | + if (!annex.empty()) { |
| 95 | + tx.vin[in_index].scriptWitness.stack.push_back(std::move(annex)); // TODO: a good way to test the target is to comment out this line |
| 96 | + } |
| 97 | + |
| 98 | + // Get the vector of spent outputs from the fuzzer. No spent output are taken into account in |
| 99 | + // computing the template hash. Therefore the specific values of the other spent outputs can |
| 100 | + // be set freely. However the spent output referred to by the input being verified must be |
| 101 | + // correctly set to a Taproot scriptpubkey for bip341 subfields to be precomputed. |
| 102 | + std::vector<CTxOut> spent_outputs(tx.vin.size()); |
| 103 | + for (unsigned i{0}; i < spent_outputs.size(); ++i) { |
| 104 | + spent_outputs[i].scriptPubKey = i == in_index ? spent_spk : ConsumeScript(provider); |
| 105 | + spent_outputs[i].nValue = ConsumeMoney(provider); |
| 106 | + } |
| 107 | + |
| 108 | + // Run script validation for this transaction input. It must pass. |
| 109 | + tx.vin[in_index].scriptSig.clear(); // witness requires empty scriptSig |
| 110 | + Assert(VerifyTemplateCheck(tx, in_index, std::vector<CTxOut>(spent_outputs), spent_spk)); |
| 111 | + |
| 112 | + // Malleate a field of the spending transaction and assert whether it invalidates the spend. |
| 113 | + CallOneOf( |
| 114 | + provider, |
| 115 | + // Changing version will invalidate template hash. |
| 116 | + [&] { |
| 117 | + const auto prev_version{tx.version}; |
| 118 | + tx.version = provider.ConsumeIntegral<uint32_t>(); |
| 119 | + const bool version_changed{tx.version != prev_version}; |
| 120 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk) == !version_changed); |
| 121 | + }, |
| 122 | + // Changing locktime will invalidate template hash. |
| 123 | + [&] { |
| 124 | + const auto prev_locktime{tx.nLockTime}; |
| 125 | + tx.nLockTime = provider.ConsumeIntegral<uint32_t>(); |
| 126 | + const bool locktime_changed{tx.nLockTime != prev_locktime}; |
| 127 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk) == !locktime_changed); |
| 128 | + }, |
| 129 | + // Changing the sequence of any input will invalidate template hash. |
| 130 | + [&] { |
| 131 | + const auto i{provider.ConsumeIntegralInRange<size_t>(0, tx.vin.size() - 1)}; |
| 132 | + const auto prev_sequence{tx.vin[i].nSequence}; |
| 133 | + tx.vin[i].nSequence = provider.ConsumeIntegral<uint32_t>(); |
| 134 | + const bool seq_changed{tx.vin[i].nSequence != prev_sequence}; |
| 135 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk) == !seq_changed); |
| 136 | + }, |
| 137 | + // Changing the prevout of any input will not invalidate template hash. |
| 138 | + [&] { |
| 139 | + const auto i{provider.ConsumeIntegralInRange<size_t>(0, tx.vin.size() - 1)}; |
| 140 | + tx.vin[i].prevout.hash = Txid::FromUint256(ConsumeUInt256(provider)); |
| 141 | + tx.vin[i].prevout.n = provider.ConsumeIntegral<uint32_t>(); |
| 142 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk)); |
| 143 | + }, |
| 144 | + // Changing the scriptSig of an input will only make Script validation fail if it malleates |
| 145 | + // that of the input being validated (because Segwit mandates empty scriptSig). |
| 146 | + [&] { |
| 147 | + const auto i{provider.ConsumeIntegralInRange<size_t>(0, tx.vin.size() - 1)}; |
| 148 | + tx.vin[i].scriptSig = ConsumeScript(provider); |
| 149 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk) == tx.vin[in_index].scriptSig.empty()); |
| 150 | + }, |
| 151 | + // Changing the annex of the spending input will invalidate template hash. Changing the |
| 152 | + // witness of any other input will not invalidate template hash. |
| 153 | + [&] { |
| 154 | + const auto i{provider.ConsumeIntegralInRange<size_t>(0, tx.vin.size() - 1)}; |
| 155 | + if (i == in_index) { |
| 156 | + // Don't necessarily create a well-formatted annex. |
| 157 | + auto new_annex{ConsumeRandomLengthByteVector(provider)}; |
| 158 | + const bool expect_failure{annex.empty() || annex != new_annex}; |
| 159 | + if (annex.empty()) { |
| 160 | + tx.vin[i].scriptWitness.stack.emplace_back(); |
| 161 | + } |
| 162 | + tx.vin[i].scriptWitness.stack.back() = std::move(new_annex); |
| 163 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk) == !expect_failure); |
| 164 | + } else { |
| 165 | + tx.vin[i].scriptWitness = ConsumeScriptWitness(provider); |
| 166 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk)); |
| 167 | + } |
| 168 | + }, |
| 169 | + // Changing the value of any output will invalidate template hash. |
| 170 | + [&] { |
| 171 | + if (tx.vout.empty()) return; |
| 172 | + const auto i{provider.ConsumeIntegralInRange<size_t>(0, tx.vout.size() - 1)}; |
| 173 | + const auto prev_value{tx.vout[i].nValue}; |
| 174 | + tx.vout[i].nValue = provider.ConsumeIntegral<CAmount>(); |
| 175 | + const bool value_changed{tx.vout[i].nValue != prev_value}; |
| 176 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk) == !value_changed); |
| 177 | + }, |
| 178 | + // Changing the scriptPubKey of any output will invalidate template hash. |
| 179 | + [&] { |
| 180 | + if (tx.vout.empty()) return; |
| 181 | + const auto i{provider.ConsumeIntegralInRange<size_t>(0, tx.vout.size() - 1)}; |
| 182 | + const auto prev_spk{tx.vout[i].scriptPubKey}; |
| 183 | + tx.vout[i].scriptPubKey = ConsumeScript(provider); |
| 184 | + const bool spk_changed{tx.vout[i].scriptPubKey != prev_spk}; |
| 185 | + Assert(VerifyTemplateCheck(tx, in_index, std::move(spent_outputs), spent_spk) == !spk_changed); |
| 186 | + } |
| 187 | + // TODO: check that adding/removing inputs/outputs invalidates template hash. |
| 188 | + ); |
| 189 | +} |
0 commit comments