aboutsummaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2022-06-13 18:17:28 -0400
committerPieter Wuille <pieter@wuille.net>2023-01-30 18:12:21 -0500
commit38eaece67b1bc37b2f502348c5d7537480a34346 (patch)
tree61e7d0ea983a7bfac11af7b5d3bb0e67cdb34a7e /src/test
parent5f05b27841af0bed1b6e7de5f46ffe33e5919e4d (diff)
Add fuzz test for testing that ChaCha20 works as a stream
Diffstat (limited to 'src/test')
-rw-r--r--src/test/fuzz/crypto_chacha20.cpp108
1 files changed, 108 insertions, 0 deletions
diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp
index a109099394..f1e239bcc8 100644
--- a/src/test/fuzz/crypto_chacha20.cpp
+++ b/src/test/fuzz/crypto_chacha20.cpp
@@ -6,6 +6,7 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
+#include <test/util/xoroshiro128plusplus.h>
#include <cstdint>
#include <vector>
@@ -43,3 +44,110 @@ FUZZ_TARGET(crypto_chacha20)
});
}
}
+
+namespace
+{
+
+/** Fuzzer that invokes ChaCha20::Crypt() or ChaCha20::Keystream multiple times:
+ once for a large block at once, and then the same data in chunks, comparing
+ the outcome.
+
+ If UseCrypt, seeded Xoroshiro128++ output is used as input to Crypt().
+ If not, Keystream() is used directly, or sequences of 0x00 are encrypted.
+*/
+template<bool UseCrypt>
+void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
+{
+ // Determine key, iv, start position, length.
+ unsigned char key[32] = {0};
+ auto key_bytes = provider.ConsumeBytes<unsigned char>(32);
+ std::copy(key_bytes.begin(), key_bytes.end(), key);
+ uint64_t iv = provider.ConsumeIntegral<uint64_t>();
+ uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000);
+ /* ~x = 2^64 - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */
+ uint64_t seek = provider.ConsumeIntegralInRange<uint64_t>(0, ~(total_bytes >> 6));
+
+ // Initialize two ChaCha20 ciphers, with the same key/iv/position.
+ ChaCha20 crypt1(key, 32);
+ ChaCha20 crypt2(key, 32);
+ crypt1.SetIV(iv);
+ crypt1.Seek64(seek);
+ crypt2.SetIV(iv);
+ crypt2.Seek64(seek);
+
+ // Construct vectors with data.
+ std::vector<unsigned char> data1, data2;
+ data1.resize(total_bytes);
+ data2.resize(total_bytes);
+
+ // If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based
+ // stream.
+ if constexpr (UseCrypt) {
+ uint64_t seed = provider.ConsumeIntegral<uint64_t>();
+ XoRoShiRo128PlusPlus rng(seed);
+ uint64_t bytes = 0;
+ while (bytes < (total_bytes & ~uint64_t{7})) {
+ uint64_t val = rng();
+ WriteLE64(data1.data() + bytes, val);
+ WriteLE64(data2.data() + bytes, val);
+ bytes += 8;
+ }
+ if (bytes < total_bytes) {
+ unsigned char valbytes[8];
+ uint64_t val = rng();
+ WriteLE64(valbytes, val);
+ std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes);
+ std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes);
+ }
+ }
+
+ // Whether UseCrypt is used or not, the two byte arrays must match.
+ assert(data1 == data2);
+
+ // Encrypt data1, the whole array at once.
+ if constexpr (UseCrypt) {
+ crypt1.Crypt(data1.data(), data1.data(), total_bytes);
+ } else {
+ crypt1.Keystream(data1.data(), total_bytes);
+ }
+
+ // Encrypt data2, in at most 256 chunks.
+ uint64_t bytes2 = 0;
+ int iter = 0;
+ while (true) {
+ bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool();
+ ++iter;
+ // Determine how many bytes to encrypt in this chunk: a fuzzer-determined
+ // amount for all but the last chunk (which processes all remaining bytes).
+ uint64_t now = is_last ? total_bytes - bytes2 :
+ provider.ConsumeIntegralInRange<uint64_t>(0, total_bytes - bytes2);
+ // For each chunk, consider using Crypt() even when UseCrypt is false.
+ // This tests that Keystream() has the same behavior as Crypt() applied
+ // to 0x00 input bytes.
+ if (UseCrypt || provider.ConsumeBool()) {
+ crypt2.Crypt(data2.data() + bytes2, data2.data() + bytes2, now);
+ } else {
+ crypt2.Keystream(data2.data() + bytes2, now);
+ }
+ bytes2 += now;
+ if (is_last) break;
+ }
+ // We should have processed everything now.
+ assert(bytes2 == total_bytes);
+ // And the result should match.
+ assert(data1 == data2);
+}
+
+} // namespace
+
+FUZZ_TARGET(chacha20_split_crypt)
+{
+ FuzzedDataProvider provider{buffer.data(), buffer.size()};
+ ChaCha20SplitFuzz<true>(provider);
+}
+
+FUZZ_TARGET(chacha20_split_keystream)
+{
+ FuzzedDataProvider provider{buffer.data(), buffer.size()};
+ ChaCha20SplitFuzz<false>(provider);
+}