aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfanquake <fanquake@gmail.com>2023-02-15 14:51:38 +0000
committerfanquake <fanquake@gmail.com>2023-02-15 14:58:47 +0000
commit1e0198b6c1dbf0ae621e715928ecf7af44e99a5e (patch)
tree8f2979093ba244c9121c09cf00e4b3652dd15383
parent2b0cd7679f826af13e809df889093b4371c3e972 (diff)
parent511aa4f1c7508f15cab8d7e58007900ad6fd3d5d (diff)
Merge bitcoin/bitcoin#26153: Reduce wasted pseudorandom bytes in ChaCha20 + various improvements
511aa4f1c7508f15cab8d7e58007900ad6fd3d5d Add unit test for ChaCha20's new caching (Pieter Wuille) fb243d25f754da8f01793b41e2d225b917f3e5d7 Improve test vectors for ChaCha20 (Pieter Wuille) 93aee8bbdad808b7009279b67470d496cc26b936 Inline ChaCha20 32-byte specific constants (Pieter Wuille) 62ec713961ade7b58e90c905395558a41e8a59f0 Only support 32-byte keys in ChaCha20{,Aligned} (Pieter Wuille) f21994a02e1cc46d41995581b54222abc655be93 Use ChaCha20Aligned in MuHash3072 code (Pieter Wuille) 5d16f757639e2cc6e81db6e07bc1d5dd74abca6c Use ChaCha20 caching in FastRandomContext (Pieter Wuille) 38eaece67b1bc37b2f502348c5d7537480a34346 Add fuzz test for testing that ChaCha20 works as a stream (Pieter Wuille) 5f05b27841af0bed1b6e7de5f46ffe33e5919e4d Add xoroshiro128++ PRNG (Martin Leitner-Ankerl) 12ff72476ac0dbf8add736ad3fb5fad2eeab156c Make unrestricted ChaCha20 cipher not waste keystream bytes (Pieter Wuille) 6babf402130a8f3ef3058594750aeaa50b8f5044 Rename ChaCha20::Seek -> Seek64 to clarify multiple of 64 (Pieter Wuille) e37bcaa0a6dbb334ab6e817efcb609ccee6edc39 Split ChaCha20 into aligned/unaligned variants (Pieter Wuille) Pull request description: This is an alternative to #25354 (by my benchmarking, somewhat faster), subsumes #25712, and adds additional test vectors. It separates the multiple-of-64-bytes-only "core" logic (which becomes simpler) from a layer around which performs caching/slicing to support arbitrary byte amounts. Both have their uses (in particular, the MuHash3072 code can benefit from multiple-of-64-bytes assumptions), plus the separation results in more readable code. Also, since FastRandomContext effectively had its own (more naive) caching on top of ChaCha20, that can be dropped in favor of ChaCha20's new built-in caching. I thought about rebasing #25712 on top of this, but the changes before are fairly extensive, so redid it instead. ACKs for top commit: ajtowns: ut reACK 511aa4f1c7508f15cab8d7e58007900ad6fd3d5d dhruv: tACK crACK 511aa4f1c7 Tree-SHA512: 3aa80971322a93e780c75a8d35bd39da3a9ea570fbae4491eaf0c45242f5f670a24a592c50ad870d5fd09b9f88ec06e274e8aa3cefd9561d623c63f7198cf2c7
-rw-r--r--src/Makefile.test.include3
-rw-r--r--src/Makefile.test_util.include3
-rw-r--r--src/bench/chacha20.cpp4
-rw-r--r--src/crypto/chacha20.cpp260
-rw-r--r--src/crypto/chacha20.h66
-rw-r--r--src/crypto/chacha_poly_aead.cpp13
-rw-r--r--src/crypto/muhash.cpp2
-rw-r--r--src/random.cpp20
-rw-r--r--src/random.h20
-rw-r--r--src/test/crypto_tests.cpp163
-rw-r--r--src/test/fuzz/crypto_chacha20.cpp118
-rw-r--r--src/test/fuzz/crypto_diff_fuzz_chacha20.cpp33
-rw-r--r--src/test/util/xoroshiro128plusplus.h71
-rw-r--r--src/test/xoroshiro128plusplus_tests.cpp29
-rw-r--r--test/sanitizer_suppressions/ubsan2
15 files changed, 582 insertions, 225 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index d6992640ff..fa77e28736 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -162,7 +162,8 @@ BITCOIN_TESTS =\
test/validation_flush_tests.cpp \
test/validation_tests.cpp \
test/validationinterface_tests.cpp \
- test/versionbits_tests.cpp
+ test/versionbits_tests.cpp \
+ test/xoroshiro128plusplus_tests.cpp
if ENABLE_WALLET
BITCOIN_TESTS += \
diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include
index 8496b3698a..ae77b79b8b 100644
--- a/src/Makefile.test_util.include
+++ b/src/Makefile.test_util.include
@@ -19,7 +19,8 @@ TEST_UTIL_H = \
test/util/str.h \
test/util/transaction_utils.h \
test/util/txmempool.h \
- test/util/validation.h
+ test/util/validation.h \
+ test/util/xoroshiro128plusplus.h
if ENABLE_WALLET
TEST_UTIL_H += wallet/test/util.h
diff --git a/src/bench/chacha20.cpp b/src/bench/chacha20.cpp
index 656fb833e7..115cd064bd 100644
--- a/src/bench/chacha20.cpp
+++ b/src/bench/chacha20.cpp
@@ -14,9 +14,9 @@ static const uint64_t BUFFER_SIZE_LARGE = 1024*1024;
static void CHACHA20(benchmark::Bench& bench, size_t buffersize)
{
std::vector<uint8_t> key(32,0);
- ChaCha20 ctx(key.data(), key.size());
+ ChaCha20 ctx(key.data());
ctx.SetIV(0);
- ctx.Seek(0);
+ ctx.Seek64(0);
std::vector<uint8_t> in(buffersize,0);
std::vector<uint8_t> out(buffersize,0);
bench.batch(in.size()).unit("byte").run([&] {
diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp
index 25d7baa8cc..6934cef163 100644
--- a/src/crypto/chacha20.cpp
+++ b/src/crypto/chacha20.cpp
@@ -8,6 +8,7 @@
#include <crypto/common.h>
#include <crypto/chacha20.h>
+#include <algorithm>
#include <string.h>
constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | (v >> (32 - c)); }
@@ -20,95 +21,69 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | (
#define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0)
-static const unsigned char sigma[] = "expand 32-byte k";
-static const unsigned char tau[] = "expand 16-byte k";
-
-void ChaCha20::SetKey(const unsigned char* k, size_t keylen)
+void ChaCha20Aligned::SetKey32(const unsigned char* k)
{
- const unsigned char *constants;
-
- input[4] = ReadLE32(k + 0);
- input[5] = ReadLE32(k + 4);
- input[6] = ReadLE32(k + 8);
- input[7] = ReadLE32(k + 12);
- if (keylen == 32) { /* recommended */
- k += 16;
- constants = sigma;
- } else { /* keylen == 16 */
- constants = tau;
- }
- input[8] = ReadLE32(k + 0);
- input[9] = ReadLE32(k + 4);
- input[10] = ReadLE32(k + 8);
- input[11] = ReadLE32(k + 12);
- input[0] = ReadLE32(constants + 0);
- input[1] = ReadLE32(constants + 4);
- input[2] = ReadLE32(constants + 8);
- input[3] = ReadLE32(constants + 12);
- input[12] = 0;
- input[13] = 0;
- input[14] = 0;
- input[15] = 0;
+ input[0] = ReadLE32(k + 0);
+ input[1] = ReadLE32(k + 4);
+ input[2] = ReadLE32(k + 8);
+ input[3] = ReadLE32(k + 12);
+ input[4] = ReadLE32(k + 16);
+ input[5] = ReadLE32(k + 20);
+ input[6] = ReadLE32(k + 24);
+ input[7] = ReadLE32(k + 28);
+ input[8] = 0;
+ input[9] = 0;
+ input[10] = 0;
+ input[11] = 0;
}
-ChaCha20::ChaCha20()
+ChaCha20Aligned::ChaCha20Aligned()
{
memset(input, 0, sizeof(input));
}
-ChaCha20::ChaCha20(const unsigned char* k, size_t keylen)
+ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32)
{
- SetKey(k, keylen);
+ SetKey32(key32);
}
-void ChaCha20::SetIV(uint64_t iv)
+void ChaCha20Aligned::SetIV(uint64_t iv)
{
- input[14] = iv;
- input[15] = iv >> 32;
+ input[10] = iv;
+ input[11] = iv >> 32;
}
-void ChaCha20::Seek(uint64_t pos)
+void ChaCha20Aligned::Seek64(uint64_t pos)
{
- input[12] = pos;
- input[13] = pos >> 32;
+ input[8] = pos;
+ input[9] = pos >> 32;
}
-void ChaCha20::Keystream(unsigned char* c, size_t bytes)
+inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks)
{
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
- uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
- unsigned char *ctarget = nullptr;
- unsigned char tmp[64];
- unsigned int i;
-
- if (!bytes) return;
-
- j0 = input[0];
- j1 = input[1];
- j2 = input[2];
- j3 = input[3];
- j4 = input[4];
- j5 = input[5];
- j6 = input[6];
- j7 = input[7];
- j8 = input[8];
- j9 = input[9];
- j10 = input[10];
- j11 = input[11];
- j12 = input[12];
- j13 = input[13];
- j14 = input[14];
- j15 = input[15];
+ uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+
+ if (!blocks) return;
+
+ j4 = input[0];
+ j5 = input[1];
+ j6 = input[2];
+ j7 = input[3];
+ j8 = input[4];
+ j9 = input[5];
+ j10 = input[6];
+ j11 = input[7];
+ j12 = input[8];
+ j13 = input[9];
+ j14 = input[10];
+ j15 = input[11];
for (;;) {
- if (bytes < 64) {
- ctarget = c;
- c = tmp;
- }
- x0 = j0;
- x1 = j1;
- x2 = j2;
- x3 = j3;
+ x0 = 0x61707865;
+ x1 = 0x3320646e;
+ x2 = 0x79622d32;
+ x3 = 0x6b206574;
x4 = j4;
x5 = j5;
x6 = j6;
@@ -134,10 +109,10 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes)
QUARTERROUND( x3, x4, x9,x14);
);
- x0 += j0;
- x1 += j1;
- x2 += j2;
- x3 += j3;
+ x0 += 0x61707865;
+ x1 += 0x3320646e;
+ x2 += 0x79622d32;
+ x3 += 0x6b206574;
x4 += j4;
x5 += j5;
x6 += j6;
@@ -171,59 +146,41 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes)
WriteLE32(c + 56, x14);
WriteLE32(c + 60, x15);
- if (bytes <= 64) {
- if (bytes < 64) {
- for (i = 0;i < bytes;++i) ctarget[i] = c[i];
- }
- input[12] = j12;
- input[13] = j13;
+ if (blocks == 1) {
+ input[8] = j12;
+ input[9] = j13;
return;
}
- bytes -= 64;
+ blocks -= 1;
c += 64;
}
}
-void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
+inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks)
{
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
- uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
- unsigned char *ctarget = nullptr;
- unsigned char tmp[64];
- unsigned int i;
-
- if (!bytes) return;
-
- j0 = input[0];
- j1 = input[1];
- j2 = input[2];
- j3 = input[3];
- j4 = input[4];
- j5 = input[5];
- j6 = input[6];
- j7 = input[7];
- j8 = input[8];
- j9 = input[9];
- j10 = input[10];
- j11 = input[11];
- j12 = input[12];
- j13 = input[13];
- j14 = input[14];
- j15 = input[15];
+ uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+
+ if (!blocks) return;
+
+ j4 = input[0];
+ j5 = input[1];
+ j6 = input[2];
+ j7 = input[3];
+ j8 = input[4];
+ j9 = input[5];
+ j10 = input[6];
+ j11 = input[7];
+ j12 = input[8];
+ j13 = input[9];
+ j14 = input[10];
+ j15 = input[11];
for (;;) {
- if (bytes < 64) {
- // if m has fewer than 64 bytes available, copy m to tmp and
- // read from tmp instead
- for (i = 0;i < bytes;++i) tmp[i] = m[i];
- m = tmp;
- ctarget = c;
- c = tmp;
- }
- x0 = j0;
- x1 = j1;
- x2 = j2;
- x3 = j3;
+ x0 = 0x61707865;
+ x1 = 0x3320646e;
+ x2 = 0x79622d32;
+ x3 = 0x6b206574;
x4 = j4;
x5 = j5;
x6 = j6;
@@ -249,10 +206,10 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
QUARTERROUND( x3, x4, x9,x14);
);
- x0 += j0;
- x1 += j1;
- x2 += j2;
- x3 += j3;
+ x0 += 0x61707865;
+ x1 += 0x3320646e;
+ x2 += 0x79622d32;
+ x3 += 0x6b206574;
x4 += j4;
x5 += j5;
x6 += j6;
@@ -303,16 +260,65 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
WriteLE32(c + 56, x14);
WriteLE32(c + 60, x15);
- if (bytes <= 64) {
- if (bytes < 64) {
- for (i = 0;i < bytes;++i) ctarget[i] = c[i];
- }
- input[12] = j12;
- input[13] = j13;
+ if (blocks == 1) {
+ input[8] = j12;
+ input[9] = j13;
return;
}
- bytes -= 64;
+ blocks -= 1;
c += 64;
m += 64;
}
}
+
+void ChaCha20::Keystream(unsigned char* c, size_t bytes)
+{
+ if (!bytes) return;
+ if (m_bufleft) {
+ unsigned reuse = std::min<size_t>(m_bufleft, bytes);
+ memcpy(c, m_buffer + 64 - m_bufleft, reuse);
+ m_bufleft -= reuse;
+ bytes -= reuse;
+ c += reuse;
+ }
+ if (bytes >= 64) {
+ size_t blocks = bytes / 64;
+ m_aligned.Keystream64(c, blocks);
+ c += blocks * 64;
+ bytes -= blocks * 64;
+ }
+ if (bytes) {
+ m_aligned.Keystream64(m_buffer, 1);
+ memcpy(c, m_buffer, bytes);
+ m_bufleft = 64 - bytes;
+ }
+}
+
+void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
+{
+ if (!bytes) return;
+ if (m_bufleft) {
+ unsigned reuse = std::min<size_t>(m_bufleft, bytes);
+ for (unsigned i = 0; i < reuse; i++) {
+ c[i] = m[i] ^ m_buffer[64 - m_bufleft + i];
+ }
+ m_bufleft -= reuse;
+ bytes -= reuse;
+ c += reuse;
+ m += reuse;
+ }
+ if (bytes >= 64) {
+ size_t blocks = bytes / 64;
+ m_aligned.Crypt64(m, c, blocks);
+ c += blocks * 64;
+ m += blocks * 64;
+ bytes -= blocks * 64;
+ }
+ if (bytes) {
+ m_aligned.Keystream64(m_buffer, 1);
+ for (unsigned i = 0; i < bytes; i++) {
+ c[i] = m[i] ^ m_buffer[i];
+ }
+ m_bufleft = 64 - bytes;
+ }
+}
diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h
index 624c083191..b286ef59fe 100644
--- a/src/crypto/chacha20.h
+++ b/src/crypto/chacha20.h
@@ -8,19 +8,69 @@
#include <cstdlib>
#include <stdint.h>
-/** A class for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein
- https://cr.yp.to/chacha/chacha-20080128.pdf */
+// classes for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein
+// https://cr.yp.to/chacha/chacha-20080128.pdf */
+
+/** ChaCha20 cipher that only operates on multiples of 64 bytes. */
+class ChaCha20Aligned
+{
+private:
+ uint32_t input[12];
+
+public:
+ ChaCha20Aligned();
+
+ /** Initialize a cipher with specified 32-byte key. */
+ ChaCha20Aligned(const unsigned char* key32);
+
+ /** set 32-byte key. */
+ void SetKey32(const unsigned char* key32);
+
+ /** set the 64-bit nonce. */
+ void SetIV(uint64_t iv);
+
+ /** set the 64bit block counter (pos seeks to byte position 64*pos). */
+ void Seek64(uint64_t pos);
+
+ /** outputs the keystream of size <64*blocks> into <c> */
+ void Keystream64(unsigned char* c, size_t blocks);
+
+ /** enciphers the message <input> of length <64*blocks> and write the enciphered representation into <output>
+ * Used for encryption and decryption (XOR)
+ */
+ void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks);
+};
+
+/** Unrestricted ChaCha20 cipher. */
class ChaCha20
{
private:
- uint32_t input[16];
+ ChaCha20Aligned m_aligned;
+ unsigned char m_buffer[64] = {0};
+ unsigned m_bufleft{0};
public:
- ChaCha20();
- ChaCha20(const unsigned char* key, size_t keylen);
- void SetKey(const unsigned char* key, size_t keylen); //!< set key with flexible keylength; 256bit recommended */
- void SetIV(uint64_t iv); // set the 64bit nonce
- void Seek(uint64_t pos); // set the 64bit block counter
+ ChaCha20() = default;
+
+ /** Initialize a cipher with specified 32-byte key. */
+ ChaCha20(const unsigned char* key32) : m_aligned(key32) {}
+
+ /** set 32-byte key. */
+ void SetKey32(const unsigned char* key32)
+ {
+ m_aligned.SetKey32(key32);
+ m_bufleft = 0;
+ }
+
+ /** set the 64-bit nonce. */
+ void SetIV(uint64_t iv) { m_aligned.SetIV(iv); }
+
+ /** set the 64bit block counter (pos seeks to byte position 64*pos). */
+ void Seek64(uint64_t pos)
+ {
+ m_aligned.Seek64(pos);
+ m_bufleft = 0;
+ }
/** outputs the keystream of size <bytes> into <c> */
void Keystream(unsigned char* c, size_t bytes);
diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp
index 6511f46adc..119ad6902f 100644
--- a/src/crypto/chacha_poly_aead.cpp
+++ b/src/crypto/chacha_poly_aead.cpp
@@ -36,8 +36,9 @@ ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_
assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
- m_chacha_header.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN);
- m_chacha_main.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN);
+ static_assert(CHACHA20_POLY1305_AEAD_KEY_LEN == 32);
+ m_chacha_header.SetKey32(K_1);
+ m_chacha_main.SetKey32(K_2);
// set the cached sequence number to uint64 max which hints for an unset cache.
// we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB
@@ -62,7 +63,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int
// block counter 0 for the poly1305 key
// use lower 32bytes for the poly1305 key
// (throws away 32 unused bytes (upper 32) from this ChaCha20 round)
- m_chacha_main.Seek(0);
+ m_chacha_main.Seek64(0);
m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key));
// if decrypting, verify the tag prior to decryption
@@ -85,7 +86,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int
if (m_cached_aad_seqnr != seqnr_aad) {
m_cached_aad_seqnr = seqnr_aad;
m_chacha_header.SetIV(seqnr_aad);
- m_chacha_header.Seek(0);
+ m_chacha_header.Seek64(0);
m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT);
}
// crypt the AAD (3 bytes message length) with given position in AAD cipher instance keystream
@@ -94,7 +95,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int
dest[2] = src[2] ^ m_aad_keystream_buffer[aad_pos + 2];
// Set the playload ChaCha instance block counter to 1 and crypt the payload
- m_chacha_main.Seek(1);
+ m_chacha_main.Seek64(1);
m_chacha_main.Crypt(src + CHACHA20_POLY1305_AEAD_AAD_LEN, dest + CHACHA20_POLY1305_AEAD_AAD_LEN, src_len - CHACHA20_POLY1305_AEAD_AAD_LEN);
// If encrypting, calculate and append tag
@@ -117,7 +118,7 @@ bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, in
// we need to calculate the 64 keystream bytes since we reached a new aad sequence number
m_cached_aad_seqnr = seqnr_aad;
m_chacha_header.SetIV(seqnr_aad); // use LE for the nonce
- m_chacha_header.Seek(0); // block counter 0
+ m_chacha_header.Seek64(0); // block counter 0
m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache
}
diff --git a/src/crypto/muhash.cpp b/src/crypto/muhash.cpp
index 26f0248663..471ee6af97 100644
--- a/src/crypto/muhash.cpp
+++ b/src/crypto/muhash.cpp
@@ -299,7 +299,7 @@ Num3072 MuHash3072::ToNum3072(Span<const unsigned char> in) {
unsigned char tmp[Num3072::BYTE_SIZE];
uint256 hashed_in{(HashWriter{} << in).GetSHA256()};
- ChaCha20(hashed_in.data(), hashed_in.size()).Keystream(tmp, Num3072::BYTE_SIZE);
+ ChaCha20Aligned(hashed_in.data()).Keystream64(tmp, Num3072::BYTE_SIZE / 64);
Num3072 out{tmp};
return out;
diff --git a/src/random.cpp b/src/random.cpp
index 23ea9ba6b7..5f50c001cd 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -599,18 +599,15 @@ uint256 GetRandHash() noexcept
void FastRandomContext::RandomSeed()
{
uint256 seed = GetRandHash();
- rng.SetKey(seed.begin(), 32);
+ rng.SetKey32(seed.begin());
requires_seed = false;
}
uint256 FastRandomContext::rand256() noexcept
{
- if (bytebuf_size < 32) {
- FillByteBuffer();
- }
+ if (requires_seed) RandomSeed();
uint256 ret;
- memcpy(ret.begin(), bytebuf + 64 - bytebuf_size, 32);
- bytebuf_size -= 32;
+ rng.Keystream(ret.data(), ret.size());
return ret;
}
@@ -624,9 +621,9 @@ std::vector<unsigned char> FastRandomContext::randbytes(size_t len)
return ret;
}
-FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bytebuf_size(0), bitbuf_size(0)
+FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bitbuf_size(0)
{
- rng.SetKey(seed.begin(), 32);
+ rng.SetKey32(seed.begin());
}
bool Random_SanityCheck()
@@ -675,25 +672,22 @@ bool Random_SanityCheck()
return true;
}
-FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0)
+FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bitbuf_size(0)
{
if (!fDeterministic) {
return;
}
uint256 seed;
- rng.SetKey(seed.begin(), 32);
+ rng.SetKey32(seed.begin());
}
FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept
{
requires_seed = from.requires_seed;
rng = from.rng;
- std::copy(std::begin(from.bytebuf), std::end(from.bytebuf), std::begin(bytebuf));
- bytebuf_size = from.bytebuf_size;
bitbuf = from.bitbuf;
bitbuf_size = from.bitbuf_size;
from.requires_seed = true;
- from.bytebuf_size = 0;
from.bitbuf_size = 0;
return *this;
}
diff --git a/src/random.h b/src/random.h
index bb8b5539a3..49c0dff5bf 100644
--- a/src/random.h
+++ b/src/random.h
@@ -146,23 +146,11 @@ private:
bool requires_seed;
ChaCha20 rng;
- unsigned char bytebuf[64];
- int bytebuf_size;
-
uint64_t bitbuf;
int bitbuf_size;
void RandomSeed();
- void FillByteBuffer()
- {
- if (requires_seed) {
- RandomSeed();
- }
- rng.Keystream(bytebuf, sizeof(bytebuf));
- bytebuf_size = sizeof(bytebuf);
- }
-
void FillBitBuffer()
{
bitbuf = rand64();
@@ -186,10 +174,10 @@ public:
/** Generate a random 64-bit integer. */
uint64_t rand64() noexcept
{
- if (bytebuf_size < 8) FillByteBuffer();
- uint64_t ret = ReadLE64(bytebuf + 64 - bytebuf_size);
- bytebuf_size -= 8;
- return ret;
+ if (requires_seed) RandomSeed();
+ unsigned char buf[8];
+ rng.Keystream(buf, 8);
+ return ReadLE64(buf);
}
/** Generate a random (bits)-bit integer. */
diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp
index d3eef7beb7..ed851b5266 100644
--- a/src/test/crypto_tests.cpp
+++ b/src/test/crypto_tests.cpp
@@ -133,14 +133,14 @@ static void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, b
static void TestChaCha20(const std::string &hex_message, const std::string &hexkey, uint64_t nonce, uint64_t seek, const std::string& hexout)
{
std::vector<unsigned char> key = ParseHex(hexkey);
+ assert(key.size() == 32);
std::vector<unsigned char> m = ParseHex(hex_message);
- ChaCha20 rng(key.data(), key.size());
+ ChaCha20 rng(key.data());
rng.SetIV(nonce);
- rng.Seek(seek);
- std::vector<unsigned char> out = ParseHex(hexout);
+ rng.Seek64(seek);
std::vector<unsigned char> outres;
- outres.resize(out.size());
- assert(hex_message.empty() || m.size() == out.size());
+ outres.resize(hexout.size() / 2);
+ assert(hex_message.empty() || m.size() * 2 == hexout.size());
// perform the ChaCha20 round(s), if message is provided it will output the encrypted ciphertext otherwise the keystream
if (!hex_message.empty()) {
@@ -148,17 +148,38 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk
} else {
rng.Keystream(outres.data(), outres.size());
}
- BOOST_CHECK(out == outres);
+ BOOST_CHECK_EQUAL(hexout, HexStr(outres));
if (!hex_message.empty()) {
// Manually XOR with the keystream and compare the output
rng.SetIV(nonce);
- rng.Seek(seek);
+ rng.Seek64(seek);
std::vector<unsigned char> only_keystream(outres.size());
rng.Keystream(only_keystream.data(), only_keystream.size());
for (size_t i = 0; i != m.size(); i++) {
outres[i] = m[i] ^ only_keystream[i];
}
- BOOST_CHECK(out == outres);
+ BOOST_CHECK_EQUAL(hexout, HexStr(outres));
+ }
+
+ // Repeat 10x, but fragmented into 3 chunks, to exercise the ChaCha20 class's caching.
+ for (int i = 0; i < 10; ++i) {
+ size_t lens[3];
+ lens[0] = InsecureRandRange(hexout.size() / 2U + 1U);
+ lens[1] = InsecureRandRange(hexout.size() / 2U + 1U - lens[0]);
+ lens[2] = hexout.size() / 2U - lens[0] - lens[1];
+
+ rng.Seek64(seek);
+ outres.assign(hexout.size() / 2U, 0);
+ size_t pos = 0;
+ for (int j = 0; j < 3; ++j) {
+ if (!hex_message.empty()) {
+ rng.Crypt(m.data() + pos, outres.data() + pos, lens[j]);
+ } else {
+ rng.Keystream(outres.data() + pos, lens[j]);
+ }
+ pos += lens[j];
+ }
+ BOOST_CHECK_EQUAL(hexout, HexStr(outres));
}
}
@@ -460,7 +481,88 @@ BOOST_AUTO_TEST_CASE(aes_cbc_testvectors) {
BOOST_AUTO_TEST_CASE(chacha20_testvector)
{
- // Test vector from RFC 7539
+ // RFC 7539/8439 A.1 Test Vector #1:
+ TestChaCha20("",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ 0, 0,
+ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7"
+ "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586");
+
+ // RFC 7539/8439 A.1 Test Vector #2:
+ TestChaCha20("",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ 0, 1,
+ "9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed"
+ "29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f");
+
+ // RFC 7539/8439 A.1 Test Vector #3:
+ TestChaCha20("",
+ "0000000000000000000000000000000000000000000000000000000000000001",
+ 0, 1,
+ "3aeb5224ecf849929b9d828db1ced4dd832025e8018b8160b82284f3c949aa5a"
+ "8eca00bbb4a73bdad192b5c42f73f2fd4e273644c8b36125a64addeb006c13a0");
+
+ // RFC 7539/8439 A.1 Test Vector #4:
+ TestChaCha20("",
+ "00ff000000000000000000000000000000000000000000000000000000000000",
+ 0, 2,
+ "72d54dfbf12ec44b362692df94137f328fea8da73990265ec1bbbea1ae9af0ca"
+ "13b25aa26cb4a648cb9b9d1be65b2c0924a66c54d545ec1b7374f4872e99f096");
+
+ // RFC 7539/8439 A.1 Test Vector #5:
+ TestChaCha20("",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ 0x200000000000000, 0,
+ "c2c64d378cd536374ae204b9ef933fcd1a8b2288b3dfa49672ab765b54ee27c7"
+ "8a970e0e955c14f3a88e741b97c286f75f8fc299e8148362fa198a39531bed6d");
+
+ // RFC 7539/8439 A.2 Test Vector #1:
+ TestChaCha20("0000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ 0, 0,
+ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7"
+ "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586");
+
+ // RFC 7539/8439 A.2 Test Vector #2:
+ TestChaCha20("416e79207375626d697373696f6e20746f20746865204945544620696e74656e"
+ "6465642062792074686520436f6e7472696275746f7220666f72207075626c69"
+ "636174696f6e20617320616c6c206f722070617274206f6620616e2049455446"
+ "20496e7465726e65742d4472616674206f722052464320616e6420616e792073"
+ "746174656d656e74206d6164652077697468696e2074686520636f6e74657874"
+ "206f6620616e204945544620616374697669747920697320636f6e7369646572"
+ "656420616e20224945544620436f6e747269627574696f6e222e205375636820"
+ "73746174656d656e747320696e636c756465206f72616c2073746174656d656e"
+ "747320696e20494554462073657373696f6e732c2061732077656c6c20617320"
+ "7772697474656e20616e6420656c656374726f6e696320636f6d6d756e696361"
+ "74696f6e73206d61646520617420616e792074696d65206f7220706c6163652c"
+ "207768696368206172652061646472657373656420746f",
+ "0000000000000000000000000000000000000000000000000000000000000001",
+ 0x200000000000000, 1,
+ "a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec"
+ "2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d"
+ "4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e527950"
+ "42bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85a"
+ "d00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259d"
+ "c4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b"
+ "0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6c"
+ "cc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0b"
+ "c39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f"
+ "5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e6"
+ "98ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab"
+ "7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221");
+
+ // RFC 7539/8439 A.2 Test Vector #3:
+ TestChaCha20("2754776173206272696c6c69672c20616e642074686520736c6974687920746f"
+ "7665730a446964206779726520616e642067696d626c6520696e207468652077"
+ "6162653a0a416c6c206d696d737920776572652074686520626f726f676f7665"
+ "732c0a416e6420746865206d6f6d65207261746873206f757467726162652e",
+ "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
+ 0x200000000000000, 42,
+ "62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf"
+ "166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553eb"
+ "f39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f77"
+ "04c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1");
// test encryption
TestChaCha20("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756"
@@ -477,27 +579,24 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector)
"224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cb"
"a40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a"
"832c89c167eacd901d7e2bf363");
+}
- // Test vectors from https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
- TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", 0, 0,
- "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b"
- "8f41518a11cc387b669b2ee6586");
- TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000001", 0, 0,
- "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d79"
- "2b1c43fea817e9ad275ae546963");
- TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", 0x0100000000000000ULL, 0,
- "de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b52770"
- "62eb7a0433e445f41e3");
- TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", 1, 0,
- "ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc4"
- "97a0b466e7d6bbdb0041b2f586b");
- TestChaCha20("", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0x0706050403020100ULL, 0,
- "f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3b"
- "e59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc1"
- "18be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5"
- "a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5"
- "360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78"
- "fab78c9");
+BOOST_AUTO_TEST_CASE(chacha20_midblock)
+{
+ auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000");
+ ChaCha20 c20{key.data()};
+ // get one block of keystream
+ unsigned char block[64];
+ c20.Keystream(block, CHACHA20_ROUND_OUTPUT);
+ unsigned char b1[5], b2[7], b3[52];
+ c20 = ChaCha20{key.data()};
+ c20.Keystream(b1, 5);
+ c20.Keystream(b2, 7);
+ c20.Keystream(b3, 52);
+
+ BOOST_CHECK_EQUAL(0, memcmp(b1, block, 5));
+ BOOST_CHECK_EQUAL(0, memcmp(b2, block + 5, 7));
+ BOOST_CHECK_EQUAL(0, memcmp(b3, block + 12, 52));
}
BOOST_AUTO_TEST_CASE(poly1305_testvector)
@@ -617,7 +716,7 @@ static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aa
ChaCha20Poly1305AEAD aead(aead_K_1.data(), aead_K_1.size(), aead_K_2.data(), aead_K_2.size());
// create a chacha20 instance to compare against
- ChaCha20 cmp_ctx(aead_K_1.data(), 32);
+ ChaCha20 cmp_ctx(aead_K_1.data());
// encipher
bool res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true);
@@ -631,7 +730,7 @@ static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aa
// manually construct the AAD keystream
cmp_ctx.SetIV(seqnr_aad);
- cmp_ctx.Seek(0);
+ cmp_ctx.Seek64(0);
cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64);
BOOST_CHECK(memcmp(expected_aad_keystream.data(), cmp_ctx_buffer.data(), expected_aad_keystream.size()) == 0);
// crypt the 3 length bytes and compare the length
@@ -659,7 +758,7 @@ static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aa
}
// set nonce and block counter, output the keystream
cmp_ctx.SetIV(seqnr_aad);
- cmp_ctx.Seek(0);
+ cmp_ctx.Seek64(0);
cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64);
// crypt the 3 length bytes and compare the length
diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp
index 3f552a8cda..3fa445096a 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>
@@ -16,21 +17,21 @@ FUZZ_TARGET(crypto_chacha20)
ChaCha20 chacha20;
if (fuzzed_data_provider.ConsumeBool()) {
- const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(16, 32));
- chacha20 = ChaCha20{key.data(), key.size()};
+ const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
+ chacha20 = ChaCha20{key.data()};
}
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
CallOneOf(
fuzzed_data_provider,
[&] {
- const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(16, 32));
- chacha20.SetKey(key.data(), key.size());
+ std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
+ chacha20.SetKey32(key.data());
},
[&] {
chacha20.SetIV(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
},
[&] {
- chacha20.Seek(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
+ chacha20.Seek64(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
},
[&] {
std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
@@ -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);
+ ChaCha20 crypt2(key);
+ 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);
+}
diff --git a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp
index 1b89d55773..78fee48de6 100644
--- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp
+++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp
@@ -267,32 +267,33 @@ void ECRYPT_keystream_bytes(ECRYPT_ctx* x, u8* stream, u32 bytes)
FUZZ_TARGET(crypto_diff_fuzz_chacha20)
{
+ static const unsigned char ZEROKEY[32] = {0};
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
ChaCha20 chacha20;
ECRYPT_ctx ctx;
- // D. J. Bernstein doesn't initialise ctx to 0 while Bitcoin Core initialises chacha20 to 0 in the constructor
- for (int i = 0; i < 16; i++) {
- ctx.input[i] = 0;
- }
if (fuzzed_data_provider.ConsumeBool()) {
- const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(16, 32));
- chacha20 = ChaCha20{key.data(), key.size()};
+ const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
+ chacha20 = ChaCha20{key.data()};
ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0);
- // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does
- uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0};
- ECRYPT_ivsetup(&ctx, iv);
+ } else {
+ // The default ChaCha20 constructor is equivalent to using the all-0 key.
+ ECRYPT_keysetup(&ctx, ZEROKEY, 256, 0);
}
+ // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does
+ static const uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+ ECRYPT_ivsetup(&ctx, iv);
+
LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 3000) {
CallOneOf(
fuzzed_data_provider,
[&] {
- const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(16, 32));
- chacha20.SetKey(key.data(), key.size());
+ const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
+ chacha20.SetKey32(key.data());
ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0);
- // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does
+ // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does
uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0};
ECRYPT_ivsetup(&ctx, iv);
},
@@ -304,26 +305,32 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
},
[&] {
uint64_t counter = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
- chacha20.Seek(counter);
+ chacha20.Seek64(counter);
ctx.input[12] = counter;
ctx.input[13] = counter >> 32;
},
[&] {
uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
+ // DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that.
+ uint64_t pos = ctx.input[12] + (((uint64_t)ctx.input[13]) << 32) + ((integralInRange + 63) >> 6);
std::vector<uint8_t> output(integralInRange);
chacha20.Keystream(output.data(), output.size());
std::vector<uint8_t> djb_output(integralInRange);
ECRYPT_keystream_bytes(&ctx, djb_output.data(), djb_output.size());
assert(output == djb_output);
+ chacha20.Seek64(pos);
},
[&] {
uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
+ // DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that.
+ uint64_t pos = ctx.input[12] + (((uint64_t)ctx.input[13]) << 32) + ((integralInRange + 63) >> 6);
std::vector<uint8_t> output(integralInRange);
const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size());
chacha20.Crypt(input.data(), output.data(), input.size());
std::vector<uint8_t> djb_output(integralInRange);
ECRYPT_encrypt_bytes(&ctx, input.data(), djb_output.data(), input.size());
assert(output == djb_output);
+ chacha20.Seek64(pos);
});
}
}
diff --git a/src/test/util/xoroshiro128plusplus.h b/src/test/util/xoroshiro128plusplus.h
new file mode 100644
index 0000000000..ac9f59b3f5
--- /dev/null
+++ b/src/test/util/xoroshiro128plusplus.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H
+#define BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H
+
+#include <cstdint>
+#include <limits>
+
+/** xoroshiro128++ PRNG. Extremely fast, not appropriate for cryptographic purposes.
+ *
+ * Memory footprint is 128bit, period is 2^128 - 1.
+ * This class is not thread-safe.
+ *
+ * Reference implementation available at https://prng.di.unimi.it/xoroshiro128plusplus.c
+ * See https://prng.di.unimi.it/
+ */
+class XoRoShiRo128PlusPlus
+{
+ uint64_t m_s0;
+ uint64_t m_s1;
+
+ [[nodiscard]] constexpr static uint64_t rotl(uint64_t x, int n)
+ {
+ return (x << n) | (x >> (64 - n));
+ }
+
+ [[nodiscard]] constexpr static uint64_t SplitMix64(uint64_t& seedval) noexcept
+ {
+ uint64_t z = (seedval += UINT64_C(0x9e3779b97f4a7c15));
+ z = (z ^ (z >> 30U)) * UINT64_C(0xbf58476d1ce4e5b9);
+ z = (z ^ (z >> 27U)) * UINT64_C(0x94d049bb133111eb);
+ return z ^ (z >> 31U);
+ }
+
+public:
+ using result_type = uint64_t;
+
+ constexpr explicit XoRoShiRo128PlusPlus(uint64_t seedval) noexcept
+ : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval))
+ {
+ }
+
+ // no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams
+ // with exactly the same results. If you need a copy, call copy().
+ XoRoShiRo128PlusPlus(const XoRoShiRo128PlusPlus&) = delete;
+ XoRoShiRo128PlusPlus& operator=(const XoRoShiRo128PlusPlus&) = delete;
+
+ // allow moves
+ XoRoShiRo128PlusPlus(XoRoShiRo128PlusPlus&&) = default;
+ XoRoShiRo128PlusPlus& operator=(XoRoShiRo128PlusPlus&&) = default;
+
+ ~XoRoShiRo128PlusPlus() = default;
+
+ constexpr result_type operator()() noexcept
+ {
+ uint64_t s0 = m_s0, s1 = m_s1;
+ const uint64_t result = rotl(s0 + s1, 17) + s0;
+ s1 ^= s0;
+ m_s0 = rotl(s0, 49) ^ s1 ^ (s1 << 21);
+ m_s1 = rotl(s1, 28);
+ return result;
+ }
+
+ static constexpr result_type min() noexcept { return std::numeric_limits<result_type>::min(); }
+ static constexpr result_type max() noexcept { return std::numeric_limits<result_type>::max(); }
+ static constexpr double entropy() noexcept { return 0.0; }
+};
+
+#endif // BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H
diff --git a/src/test/xoroshiro128plusplus_tests.cpp b/src/test/xoroshiro128plusplus_tests.cpp
new file mode 100644
index 0000000000..ea1b3e355f
--- /dev/null
+++ b/src/test/xoroshiro128plusplus_tests.cpp
@@ -0,0 +1,29 @@
+// Copyright (c) 2022 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 <test/util/setup_common.h>
+#include <test/util/xoroshiro128plusplus.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(xoroshiro128plusplus_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(reference_values)
+{
+ // numbers generated from reference implementation
+ XoRoShiRo128PlusPlus rng(0);
+ BOOST_TEST(0x6f68e1e7e2646ee1 == rng());
+ BOOST_TEST(0xbf971b7f454094ad == rng());
+ BOOST_TEST(0x48f2de556f30de38 == rng());
+ BOOST_TEST(0x6ea7c59f89bbfc75 == rng());
+
+ // seed with a random number
+ rng = XoRoShiRo128PlusPlus(0x1a26f3fa8546b47a);
+ BOOST_TEST(0xc8dc5e08d844ac7d == rng());
+ BOOST_TEST(0x5b5f1f6d499dad1b == rng());
+ BOOST_TEST(0xbeb0031f93313d6f == rng());
+ BOOST_TEST(0xbfbcf4f43a264497 == rng());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan
index 67ef512895..2fa4e383e2 100644
--- a/test/sanitizer_suppressions/ubsan
+++ b/test/sanitizer_suppressions/ubsan
@@ -53,6 +53,7 @@ unsigned-integer-overflow:policy/fees.cpp
unsigned-integer-overflow:prevector.h
unsigned-integer-overflow:script/interpreter.cpp
unsigned-integer-overflow:txmempool.cpp
+unsigned-integer-overflow:xoroshiro128plusplus.h
implicit-integer-sign-change:compat/stdin.cpp
implicit-integer-sign-change:compressor.h
implicit-integer-sign-change:crypto/
@@ -69,3 +70,4 @@ shift-base:crypto/
shift-base:hash.cpp
shift-base:streams.h
shift-base:util/bip32.cpp
+shift-base:xoroshiro128plusplus.h