|
| 1 | +// Copyright (c) 2020 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 <hash.h> |
| 6 | +#include <primitives/transaction.h> |
| 7 | +#include <script/interpreter.h> |
| 8 | +#include <test/fuzz/FuzzedDataProvider.h> |
| 9 | +#include <test/fuzz/fuzz.h> |
| 10 | +#include <test/fuzz/util.h> |
| 11 | + |
| 12 | +#include <cstdint> |
| 13 | +#include <optional> |
| 14 | +#include <string> |
| 15 | +#include <vector> |
| 16 | + |
| 17 | +/** |
| 18 | + * SignatureHashSchnorr from Bitcoin Core 23.0. |
| 19 | + * |
| 20 | + * Only change is using an assert directly instead of HandleMissingData. |
| 21 | + */ |
| 22 | +template<typename T> |
| 23 | +bool OldSignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb) |
| 24 | +{ |
| 25 | + uint8_t ext_flag, key_version; |
| 26 | + switch (sigversion) { |
| 27 | + case SigVersion::TAPROOT: |
| 28 | + ext_flag = 0; |
| 29 | + // key_version is not used and left uninitialized. |
| 30 | + break; |
| 31 | + case SigVersion::TAPSCRIPT: |
| 32 | + ext_flag = 1; |
| 33 | + // key_version must be 0 for now, representing the current version of |
| 34 | + // 32-byte public keys in the tapscript signature opcode execution. |
| 35 | + // An upgradable public key version (with a size not 32-byte) may |
| 36 | + // request a different key_version with a new sigversion. |
| 37 | + key_version = 0; |
| 38 | + break; |
| 39 | + default: |
| 40 | + assert(false); |
| 41 | + } |
| 42 | + assert(in_pos < tx_to.vin.size()); |
| 43 | + assert(cache.m_bip341_taproot_ready && cache.m_spent_outputs_ready); |
| 44 | + |
| 45 | + HashWriter ss = HASHER_TAPSIGHASH; |
| 46 | + |
| 47 | + // Epoch |
| 48 | + static constexpr uint8_t EPOCH = 0; |
| 49 | + ss << EPOCH; |
| 50 | + |
| 51 | + // Hash type |
| 52 | + const uint8_t output_type = (hash_type == SIGHASH_DEFAULT) ? SIGHASH_ALL : (hash_type & SIGHASH_OUTPUT_MASK); // Default (no sighash byte) is equivalent to SIGHASH_ALL |
| 53 | + const uint8_t input_type = hash_type & SIGHASH_INPUT_MASK; |
| 54 | + if (!(hash_type <= 0x03 || (hash_type >= 0x81 && hash_type <= 0x83))) return false; |
| 55 | + ss << hash_type; |
| 56 | + |
| 57 | + // Transaction level data |
| 58 | + ss << tx_to.version; |
| 59 | + ss << tx_to.nLockTime; |
| 60 | + if (input_type != SIGHASH_ANYONECANPAY) { |
| 61 | + ss << cache.m_prevouts_single_hash; |
| 62 | + ss << cache.m_spent_amounts_single_hash; |
| 63 | + ss << cache.m_spent_scripts_single_hash; |
| 64 | + ss << cache.m_sequences_single_hash; |
| 65 | + } |
| 66 | + if (output_type == SIGHASH_ALL) { |
| 67 | + ss << cache.m_outputs_single_hash; |
| 68 | + } |
| 69 | + |
| 70 | + // Data about the input/prevout being spent |
| 71 | + assert(execdata.m_annex_init); |
| 72 | + const bool have_annex = execdata.m_annex_present; |
| 73 | + const uint8_t spend_type = (ext_flag << 1) + (have_annex ? 1 : 0); // The low bit indicates whether an annex is present. |
| 74 | + ss << spend_type; |
| 75 | + if (input_type == SIGHASH_ANYONECANPAY) { |
| 76 | + ss << tx_to.vin[in_pos].prevout; |
| 77 | + ss << cache.m_spent_outputs[in_pos]; |
| 78 | + ss << tx_to.vin[in_pos].nSequence; |
| 79 | + } else { |
| 80 | + ss << in_pos; |
| 81 | + } |
| 82 | + if (have_annex) { |
| 83 | + ss << execdata.m_annex_hash; |
| 84 | + } |
| 85 | + |
| 86 | + // Data about the output (if only one). |
| 87 | + if (output_type == SIGHASH_SINGLE) { |
| 88 | + if (in_pos >= tx_to.vout.size()) return false; |
| 89 | + if (!execdata.m_output_hash) { |
| 90 | + HashWriter sha_single_output{}; |
| 91 | + sha_single_output << tx_to.vout[in_pos]; |
| 92 | + execdata.m_output_hash = sha_single_output.GetSHA256(); |
| 93 | + } |
| 94 | + ss << execdata.m_output_hash.value(); |
| 95 | + } |
| 96 | + |
| 97 | + // Additional data for BIP 342 signatures |
| 98 | + if (sigversion == SigVersion::TAPSCRIPT) { |
| 99 | + assert(execdata.m_tapleaf_hash_init); |
| 100 | + ss << execdata.m_tapleaf_hash; |
| 101 | + ss << key_version; |
| 102 | + assert(execdata.m_codeseparator_pos_init); |
| 103 | + ss << execdata.m_codeseparator_pos; |
| 104 | + } |
| 105 | + |
| 106 | + hash_out = ss.GetSHA256(); |
| 107 | + return true; |
| 108 | +} |
| 109 | + |
| 110 | +/** Test APO's SignatureHashSchnorr against Bitcoin 23.0's SignatureHashSchnorr. |
| 111 | + * |
| 112 | + * This makes sure the behaviour for non-APO keys was conserved with the introduction of the APO logic. |
| 113 | + */ |
| 114 | +FUZZ_TARGET(anyprevout_old_new_sighash) |
| 115 | +{ |
| 116 | + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
| 117 | + { |
| 118 | + // Get a transaction and pick the input we'll generate the sighash for from the fuzzer output. |
| 119 | + std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); |
| 120 | + if (!mtx) return; |
| 121 | + const uint32_t in_pos = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); |
| 122 | + if (in_pos >= mtx->vin.size()) return; |
| 123 | + |
| 124 | + // Make sure at least one of the transaction's inputs has a non-empty witness, as cache.Init() |
| 125 | + // below needs it. Then take a script code (we don't need it to be valid), a signature version |
| 126 | + // (which must be anything but APO), and a hash type to use from the fuzzer's output. |
| 127 | + if (mtx->vin[0].scriptWitness.IsNull()) mtx->vin[0].scriptWitness.stack.push_back({0}); |
| 128 | + const CTransaction tx_to{*mtx}; |
| 129 | + const CScript script_code = ConsumeScript(fuzzed_data_provider); |
| 130 | + const SigVersion sig_version{fuzzed_data_provider.PickValueInArray({SigVersion::TAPROOT, SigVersion::TAPSCRIPT})}; |
| 131 | + const uint8_t hash_type{fuzzed_data_provider.ConsumeIntegral<uint8_t>()}; |
| 132 | + |
| 133 | + // Populate the transaction cache with the transaction and dummy spent outputs. The scriptPubkKey |
| 134 | + // must match the Taproot template for Init() to populate the right fields. |
| 135 | + PrecomputedTransactionData cache; |
| 136 | + std::vector<CTxOut> spent_outputs; |
| 137 | + spent_outputs.reserve(tx_to.vin.size()); |
| 138 | + for (unsigned i = 0; i < tx_to.vin.size(); ++i) { |
| 139 | + CScript script_pubkey; |
| 140 | + script_pubkey << OP_1 << fuzzed_data_provider.ConsumeBytes<uint8_t>(WITNESS_V1_TAPROOT_SIZE); |
| 141 | + if (script_pubkey.size() != 2 + WITNESS_V1_TAPROOT_SIZE) return; |
| 142 | + const CAmount value{fuzzed_data_provider.ConsumeIntegral<int64_t>()}; |
| 143 | + spent_outputs.emplace_back(value, std::move(script_pubkey)); |
| 144 | + } |
| 145 | + cache.Init(tx_to, std::move(spent_outputs)); |
| 146 | + assert(cache.m_spent_outputs_ready); |
| 147 | + assert(cache.m_bip341_taproot_ready); |
| 148 | + |
| 149 | + // Now populate the Script execution data. Annex needs always be populated and tapleaf hash |
| 150 | + // is only need for Script-path spends. |
| 151 | + ScriptExecutionData exec_data; |
| 152 | + exec_data.m_annex_present = false; |
| 153 | + if (fuzzed_data_provider.ConsumeBool()) { |
| 154 | + if (const auto annex_hash = ConsumeDeserializable<uint256>(fuzzed_data_provider)) { |
| 155 | + exec_data.m_annex_hash = *annex_hash; |
| 156 | + exec_data.m_annex_present = true; |
| 157 | + } |
| 158 | + } |
| 159 | + exec_data.m_annex_init = true; |
| 160 | + if (sig_version == SigVersion::TAPSCRIPT) { |
| 161 | + if (const auto tapleaf_hash = ConsumeDeserializable<uint256>(fuzzed_data_provider)) { |
| 162 | + exec_data.m_tapleaf_hash = *tapleaf_hash; |
| 163 | + exec_data.m_tapleaf_hash_init = true; |
| 164 | + } else { |
| 165 | + return; |
| 166 | + } |
| 167 | + exec_data.m_codeseparator_pos = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); |
| 168 | + exec_data.m_codeseparator_pos_init = true; |
| 169 | + } |
| 170 | + |
| 171 | + // Assert the existing behaviour was conserved with the introduction of the APO logic for |
| 172 | + // the fully pseudo-random hashtype. |
| 173 | + uint256 old_sighash, new_sighash; |
| 174 | + const auto new_res = SignatureHashSchnorr(new_sighash, exec_data, tx_to, in_pos, hash_type, sig_version, |
| 175 | + KeyVersion::TAPROOT, cache, MissingDataBehavior::ASSERT_FAIL); |
| 176 | + const auto old_res = OldSignatureHashSchnorr(old_sighash, exec_data, tx_to, in_pos, hash_type, sig_version, |
| 177 | + cache, MissingDataBehavior::ASSERT_FAIL); |
| 178 | + assert(new_res == old_res); |
| 179 | + assert(new_sighash == old_sighash); |
| 180 | + const auto new_res_h = SignatureHashSchnorr(new_sighash, exec_data, tx_to, in_pos, hash_type, sig_version, |
| 181 | + KeyVersion::TAPROOT, cache, MissingDataBehavior::ASSERT_FAIL); |
| 182 | + const auto old_res_h = OldSignatureHashSchnorr(old_sighash, exec_data, tx_to, in_pos, hash_type, sig_version, |
| 183 | + cache, MissingDataBehavior::ASSERT_FAIL); |
| 184 | + assert(new_res_h == old_res_h); |
| 185 | + assert(new_sighash == old_sighash); |
| 186 | + } |
| 187 | +} |
0 commit comments