mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
Merge bitcoin/bitcoin#26153: Reduce wasted pseudorandom bytes in ChaCha20 + various improvements
511aa4f1c7
Add unit test for ChaCha20's new caching (Pieter Wuille)fb243d25f7
Improve test vectors for ChaCha20 (Pieter Wuille)93aee8bbda
Inline ChaCha20 32-byte specific constants (Pieter Wuille)62ec713961
Only support 32-byte keys in ChaCha20{,Aligned} (Pieter Wuille)f21994a02e
Use ChaCha20Aligned in MuHash3072 code (Pieter Wuille)5d16f75763
Use ChaCha20 caching in FastRandomContext (Pieter Wuille)38eaece67b
Add fuzz test for testing that ChaCha20 works as a stream (Pieter Wuille)5f05b27841
Add xoroshiro128++ PRNG (Martin Leitner-Ankerl)12ff72476a
Make unrestricted ChaCha20 cipher not waste keystream bytes (Pieter Wuille)6babf40213
Rename ChaCha20::Seek -> Seek64 to clarify multiple of 64 (Pieter Wuille)e37bcaa0a6
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 reACK511aa4f1c7
dhruv: tACK crACK511aa4f1c7
Tree-SHA512: 3aa80971322a93e780c75a8d35bd39da3a9ea570fbae4491eaf0c45242f5f670a24a592c50ad870d5fd09b9f88ec06e274e8aa3cefd9561d623c63f7198cf2c7
This commit is contained in:
commit
1e0198b6c1
15 changed files with 578 additions and 221 deletions
|
@ -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 += \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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([&] {
|
||||
|
|
|
@ -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;
|
||||
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
|
||||
|
||||
if (!bytes) return;
|
||||
if (!blocks) 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];
|
||||
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;
|
||||
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
|
||||
|
||||
if (!bytes) return;
|
||||
if (!blocks) 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];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
20
src/random.h
20
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. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
71
src/test/util/xoroshiro128plusplus.h
Normal file
71
src/test/util/xoroshiro128plusplus.h
Normal file
|
@ -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
|
29
src/test/xoroshiro128plusplus_tests.cpp
Normal file
29
src/test/xoroshiro128plusplus_tests.cpp
Normal file
|
@ -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()
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue