Bitcoin Core  25.99.0
P2P Digital Currency
crypto_chacha20.cpp
Go to the documentation of this file.
1 // Copyright (c) 2020-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 <crypto/chacha20.h>
7 #include <test/fuzz/fuzz.h>
8 #include <test/fuzz/util.h>
10 
11 #include <cstdint>
12 #include <vector>
13 
14 FUZZ_TARGET(crypto_chacha20)
15 {
16  FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
17 
18  ChaCha20 chacha20;
19  if (fuzzed_data_provider.ConsumeBool()) {
20  const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
21  chacha20 = ChaCha20{key.data()};
22  }
23  LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
24  CallOneOf(
25  fuzzed_data_provider,
26  [&] {
27  std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
28  chacha20.SetKey32(key.data());
29  },
30  [&] {
31  chacha20.SetIV(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
32  },
33  [&] {
34  chacha20.Seek64(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
35  },
36  [&] {
37  std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
38  chacha20.Keystream(output.data(), output.size());
39  },
40  [&] {
41  std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
42  const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size());
43  chacha20.Crypt(input.data(), output.data(), input.size());
44  });
45  }
46 }
47 
48 namespace
49 {
50 
58 template<bool UseCrypt>
59 void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
60 {
61  // Determine key, iv, start position, length.
62  unsigned char key[32] = {0};
63  auto key_bytes = provider.ConsumeBytes<unsigned char>(32);
64  std::copy(key_bytes.begin(), key_bytes.end(), key);
65  uint64_t iv = provider.ConsumeIntegral<uint64_t>();
66  uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000);
67  /* ~x = 2^64 - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */
68  uint64_t seek = provider.ConsumeIntegralInRange<uint64_t>(0, ~(total_bytes >> 6));
69 
70  // Initialize two ChaCha20 ciphers, with the same key/iv/position.
71  ChaCha20 crypt1(key);
72  ChaCha20 crypt2(key);
73  crypt1.SetIV(iv);
74  crypt1.Seek64(seek);
75  crypt2.SetIV(iv);
76  crypt2.Seek64(seek);
77 
78  // Construct vectors with data.
79  std::vector<unsigned char> data1, data2;
80  data1.resize(total_bytes);
81  data2.resize(total_bytes);
82 
83  // If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based
84  // stream.
85  if constexpr (UseCrypt) {
86  uint64_t seed = provider.ConsumeIntegral<uint64_t>();
87  XoRoShiRo128PlusPlus rng(seed);
88  uint64_t bytes = 0;
89  while (bytes < (total_bytes & ~uint64_t{7})) {
90  uint64_t val = rng();
91  WriteLE64(data1.data() + bytes, val);
92  WriteLE64(data2.data() + bytes, val);
93  bytes += 8;
94  }
95  if (bytes < total_bytes) {
96  unsigned char valbytes[8];
97  uint64_t val = rng();
98  WriteLE64(valbytes, val);
99  std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes);
100  std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes);
101  }
102  }
103 
104  // Whether UseCrypt is used or not, the two byte arrays must match.
105  assert(data1 == data2);
106 
107  // Encrypt data1, the whole array at once.
108  if constexpr (UseCrypt) {
109  crypt1.Crypt(data1.data(), data1.data(), total_bytes);
110  } else {
111  crypt1.Keystream(data1.data(), total_bytes);
112  }
113 
114  // Encrypt data2, in at most 256 chunks.
115  uint64_t bytes2 = 0;
116  int iter = 0;
117  while (true) {
118  bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool();
119  ++iter;
120  // Determine how many bytes to encrypt in this chunk: a fuzzer-determined
121  // amount for all but the last chunk (which processes all remaining bytes).
122  uint64_t now = is_last ? total_bytes - bytes2 :
123  provider.ConsumeIntegralInRange<uint64_t>(0, total_bytes - bytes2);
124  // For each chunk, consider using Crypt() even when UseCrypt is false.
125  // This tests that Keystream() has the same behavior as Crypt() applied
126  // to 0x00 input bytes.
127  if (UseCrypt || provider.ConsumeBool()) {
128  crypt2.Crypt(data2.data() + bytes2, data2.data() + bytes2, now);
129  } else {
130  crypt2.Keystream(data2.data() + bytes2, now);
131  }
132  bytes2 += now;
133  if (is_last) break;
134  }
135  // We should have processed everything now.
136  assert(bytes2 == total_bytes);
137  // And the result should match.
138  assert(data1 == data2);
139 }
140 
141 } // namespace
142 
143 FUZZ_TARGET(chacha20_split_crypt)
144 {
145  FuzzedDataProvider provider{buffer.data(), buffer.size()};
146  ChaCha20SplitFuzz<true>(provider);
147 }
148 
149 FUZZ_TARGET(chacha20_split_keystream)
150 {
151  FuzzedDataProvider provider{buffer.data(), buffer.size()};
152  ChaCha20SplitFuzz<false>(provider);
153 }
Unrestricted ChaCha20 cipher.
Definition: chacha20.h:46
void SetKey32(const unsigned char *key32)
set 32-byte key.
Definition: chacha20.h:59
void Keystream(unsigned char *c, size_t bytes)
outputs the keystream of size <bytes> into
Definition: chacha20.cpp:274
void SetIV(uint64_t iv)
set the 64-bit nonce.
Definition: chacha20.h:66
void Seek64(uint64_t pos)
set the 64bit block counter (pos seeks to byte position 64*pos).
Definition: chacha20.h:69
void Crypt(const unsigned char *input, unsigned char *output, size_t bytes)
enciphers the message <input> of length <bytes> and write the enciphered representation into <output>...
Definition: chacha20.cpp:297
std::vector< T > ConsumeBytes(size_t num_bytes)
T ConsumeIntegralInRange(T min, T max)
xoroshiro128++ PRNG.
static void WriteLE64(unsigned char *ptr, uint64_t x)
Definition: common.h:50
FUZZ_TARGET(crypto_chacha20)
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:18
std::vector< uint8_t > ConsumeFixedLengthByteVector(FuzzedDataProvider &fuzzed_data_provider, const size_t length) noexcept
Returns a byte vector of specified size regardless of the number of remaining bytes available from th...
Definition: util.h:212
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition: util.h:35
assert(!tx.IsCoinBase())