Skip to content

Commit 08af2e0

Browse files
JeremyRubinjamesobaverage-gary
authored andcommitted
test: add CTV hash computation unit test & mutation tester
Co-authored-by: James O'Beirne <[email protected]> Co-authored-by: Gary Krause <[email protected]> (cherry picked from commit 21742d6)
1 parent 8cb8b1c commit 08af2e0

File tree

3 files changed

+2409
-0
lines changed

3 files changed

+2409
-0
lines changed

src/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ add_executable(test_bitcoin
3636
compilerbug_tests.cpp
3737
compress_tests.cpp
3838
crypto_tests.cpp
39+
ctvhash_tests.cpp
3940
cuckoocache_tests.cpp
4041
dbwrapper_tests.cpp
4142
denialofservice_tests.cpp
@@ -131,6 +132,7 @@ target_json_data_sources(test_bitcoin
131132
data/base58_encode_decode.json
132133
data/bip341_wallet_vectors.json
133134
data/blockfilters.json
135+
data/ctvhash.json
134136
data/key_io_invalid.json
135137
data/key_io_valid.json
136138
data/script_tests.json

src/test/ctvhash_tests.cpp

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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

Comments
 (0)