Skip to content

Commit 9993d53

Browse files
JeremyRubinajtowns
authored andcommitted
[TESTS] Add CTV Hash Computation Unit Test & Mutation Tester
1 parent bc3c3ae commit 9993d53

File tree

4 files changed

+2400
-0
lines changed

4 files changed

+2400
-0
lines changed

src/script/interpreter.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,6 +1490,13 @@ uint256 GetDefaultCheckTemplateVerifyHash(const TxType& tx, const uint256& outpu
14901490
GetDefaultCheckTemplateVerifyHashWithScript(tx, outputs_hash, sequences_hash, GetScriptSigsSHA256(tx), input_index);
14911491
}
14921492

1493+
template
1494+
uint256 GetDefaultCheckTemplateVerifyHash(const CTransaction& tx, const uint256& outputs_hash, const uint256& sequences_hash,
1495+
const uint32_t input_index);
1496+
template
1497+
uint256 GetDefaultCheckTemplateVerifyHash(const CMutableTransaction& tx, const uint256& outputs_hash, const uint256& sequences_hash,
1498+
const uint32_t input_index);
1499+
14931500
template <class T>
14941501
void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs, bool force)
14951502
{

src/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ add_executable(test_bitcoin
3434
compilerbug_tests.cpp
3535
compress_tests.cpp
3636
crypto_tests.cpp
37+
ctvhash_tests.cpp
3738
cuckoocache_tests.cpp
3839
dbwrapper_tests.cpp
3940
denialofservice_tests.cpp
@@ -128,6 +129,7 @@ target_json_data_sources(test_bitcoin
128129
data/base58_encode_decode.json
129130
data/bip341_wallet_vectors.json
130131
data/blockfilters.json
132+
data/ctvhash.json
131133
data/key_io_invalid.json
132134
data/key_io_valid.json
133135
data/script_tests.json

src/test/ctvhash_tests.cpp

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

0 commit comments

Comments
 (0)