Repository navigation
Expand file tree
/
Copy pathpmt_tests.cpp
More file actions
168 lines (143 loc) · 6.44 KB
/
Copy pathpmt_tests.cpp
File metadata and controls
168 lines (143 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Copyright (c) 2012-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <consensus/consensus.h>
#include <consensus/merkle.h>
#include <merkleblock.h>
#include <serialize.h>
#include <streams.h>
#include <test/util/common.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <uint256.h>
#include <vector>
#include <boost/test/unit_test.hpp>
class CPartialMerkleTreeTester : public CPartialMerkleTree
{
public:
CPartialMerkleTreeTester(FastRandomContext& rng) : m_rng{rng} {}
// flip one bit in one of the hashes - this should break the authentication
void Damage() {
unsigned int n = m_rng.randrange(vHash.size());
int bit = m_rng.randbits(8);
*(vHash[n].begin() + (bit>>3)) ^= 1<<(bit&7);
}
FastRandomContext& m_rng;
};
BOOST_FIXTURE_TEST_SUITE(pmt_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(pmt_test1)
{
static const unsigned int tx_counts[] = {1, 4, 7, 17, 56, 100, 127, 256, 312, 513, 1000, 4095};
for (int i = 0; i < 12; i++) {
unsigned int nTx = tx_counts[i];
// build a block with some dummy transactions
CBlock block;
for (unsigned int j=0; j<nTx; j++) {
CMutableTransaction tx;
tx.nLockTime = j; // actual transaction data doesn't matter; just make the nLockTime's unique
block.vtx.push_back(MakeTransactionRef(std::move(tx)));
}
// calculate actual merkle root and height
uint256 merkleRoot1 = BlockMerkleRoot(block);
std::vector<Txid> vTxid(nTx);
for (unsigned int j=0; j<nTx; j++)
vTxid[j] = block.vtx[j]->GetHash();
int nHeight = 1, nTx_ = nTx;
while (nTx_ > 1) {
nTx_ = (nTx_+1)/2;
nHeight++;
}
// check with random subsets with inclusion chances 1, 1/2, 1/4, ..., 1/128
for (int att = 1; att < 15; att++) {
// build random subset of txid's
std::vector<bool> vMatch(nTx, false);
std::vector<Txid> vMatchTxid1;
for (unsigned int j=0; j<nTx; j++) {
bool fInclude = m_rng.randbits(att / 2) == 0;
vMatch[j] = fInclude;
if (fInclude)
vMatchTxid1.push_back(vTxid[j]);
}
// build the partial merkle tree
CPartialMerkleTree pmt1(vTxid, vMatch);
// serialize
DataStream ss{};
ss << pmt1;
// verify CPartialMerkleTree's size guarantees
unsigned int n = std::min<unsigned int>(nTx, 1 + vMatchTxid1.size()*nHeight);
BOOST_CHECK(ss.size() <= 10 + (258*n+7)/8);
// deserialize into a tester copy
CPartialMerkleTreeTester pmt2{m_rng};
ss >> pmt2;
// extract merkle root and matched txids from copy
std::vector<Txid> vMatchTxid2;
std::vector<unsigned int> vIndex;
uint256 merkleRoot2 = pmt2.ExtractMatches(vMatchTxid2, vIndex);
// check that it has the same merkle root as the original, and a valid one
BOOST_CHECK(merkleRoot1 == merkleRoot2);
BOOST_CHECK(!merkleRoot2.IsNull());
// check that it contains the matched transactions (in the same order!)
BOOST_CHECK(vMatchTxid1 == vMatchTxid2);
// check that random bit flips break the authentication
for (int j=0; j<4; j++) {
CPartialMerkleTreeTester pmt3(pmt2);
pmt3.Damage();
std::vector<Txid> vMatchTxid3;
uint256 merkleRoot3 = pmt3.ExtractMatches(vMatchTxid3, vIndex);
BOOST_CHECK(merkleRoot3 != merkleRoot1);
}
}
}
}
BOOST_AUTO_TEST_CASE(pmt_malleability)
{
std::vector<Txid> vTxid{
Txid::FromUint256(uint256{1}), Txid::FromUint256(uint256{2}),
Txid::FromUint256(uint256{3}), Txid::FromUint256(uint256{4}),
Txid::FromUint256(uint256{5}), Txid::FromUint256(uint256{6}),
Txid::FromUint256(uint256{7}), Txid::FromUint256(uint256{8}),
Txid::FromUint256(uint256{9}), Txid::FromUint256(uint256{10}),
Txid::FromUint256(uint256{9}), Txid::FromUint256(uint256{10}),
};
std::vector<bool> vMatch = {false, false, false, false, false, false, false, false, false, true, true, false};
CPartialMerkleTree tree(vTxid, vMatch);
std::vector<unsigned int> vIndex;
BOOST_CHECK(tree.ExtractMatches(vTxid, vIndex).IsNull());
}
BOOST_AUTO_TEST_CASE(pmt_malformed)
{
// Deserialize a partial merkle tree with the given fields and extract its matches.
const auto extract{[](uint32_t tx_count, uint32_t hash_count, std::vector<unsigned char> bits) {
std::vector<uint256> hashes;
for (uint32_t i{1}; i <= hash_count; ++i) hashes.emplace_back(i);
DataStream ss;
ss << tx_count << hashes << bits;
CPartialMerkleTree tree;
ss >> tree;
std::vector<Txid> matches;
std::vector<unsigned int> indexes;
return tree.ExtractMatches(matches, indexes);
}};
// The transaction count is limited to MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT.
constexpr uint32_t max_tx_count{MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT};
static_assert(max_tx_count == 16666);
BOOST_CHECK_EQUAL(extract(max_tx_count, 1, {0x00}), uint256{1});
BOOST_CHECK(extract(max_tx_count + 1, 1, {0x00}).IsNull());
// An empty tree, more hashes than transactions, and fewer bits than hashes are
// rejected before traversal. Traversal would reject each of them anyway, so these
// check the result, not the early returns.
BOOST_CHECK(extract(0, 0, {0x00}).IsNull());
BOOST_CHECK(extract(1, 2, {0x00}).IsNull());
BOOST_CHECK(extract(9, 9, {0xff}).IsNull());
// Traversal runs out of hashes: the root is a parent of a match, so both of its
// leaves need a hash, but only one is provided.
BOOST_CHECK(extract(2, 1, {0x01}).IsNull());
// Traversal runs out of bits: eight bits reach the third leaf but not the fourth.
BOOST_CHECK(extract(16, 8, {0xff}).IsNull());
// Not all bits are consumed: a second byte of bits is more than padding.
BOOST_CHECK(extract(1, 1, {0x00, 0x00}).IsNull());
// Not all hashes are consumed: the root is not a parent of a match, so only its
// own hash is used.
BOOST_CHECK(extract(2, 2, {0x00}).IsNull());
}
BOOST_AUTO_TEST_SUITE_END()