0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-08 10:31:50 -05:00

Merge bitcoin/bitcoin#28100: crypto: more Span<std::byte> modernization & follow-ups

57cc136282 crypto: make ChaCha20::SetKey wipe buffer (Pieter Wuille)
da0ec62e34 tests: miscellaneous hex / std::byte improvements (Pieter Wuille)
bdcbc8594c fuzz: support std::byte in Consume{Fixed,Variable}LengthByteVector (Pieter Wuille)
7d1cd93234 crypto: require key on ChaCha20 initialization (Pieter Wuille)
44c11769a8 random: simplify FastRandomContext::randbytes using fillrand (Pieter Wuille)
3da636e08b crypto: refactor ChaCha20 classes to use Span<std::byte> interface (Pieter Wuille)

Pull request description:

  This modernizes the ChaCha20 and ChaCha20Aligned interfaces to be `Span<std::byte>` based, and other improvements.

  * Modifies all functions and constructors of `ChaCha20` and `ChaCha20Aligned` to be `Span<std::byte>` based (aligning them with `FSChaCha20`, `AEADChaCha20Poly1305`, and `FSChaCha20Poly1305`)
  * Remove default constructors, to make sure all call sites provide a key (suggested in https://github.com/bitcoin/bitcoin/pull/26153#discussion_r1129313162)
  * Wipe key material on rekey for security (suggested in https://github.com/bitcoin/bitcoin/pull/26153#discussion_r1267164605)
  * Use `HexStr` on byte vectors in tests (suggested in https://github.com/bitcoin/bitcoin/pull/27993#discussion_r1262023316)
  * Support `std::byte` vectors in `ConsumeRandomLengthByteVector` and `ConsumeFixedLengthByteVector`, and use it (suggested in https://github.com/bitcoin/bitcoin/pull/27993#discussion_r1265337111)
  * And a few more.

  While related, I don't see this as a necessary for BIP324.

ACKs for top commit:
  stratospher:
    ACK 57cc136.
  theStack:
    re-ACK 57cc136282

Tree-SHA512: 361da4ff003c8465a32eeac0983a8a6f047dbbf5b400168b409c8e3234e79d577fc854e0764389446585da3e12b964c94dd67fc0c9c1d1d092cec296121e05d4
This commit is contained in:
fanquake 2023-08-18 10:59:47 +01:00
commit 5eb669024f
No known key found for this signature in database
GPG key ID: 2EEB9F5CC09526C1
12 changed files with 241 additions and 233 deletions

View file

@ -14,13 +14,13 @@ static const uint64_t BUFFER_SIZE_LARGE = 1024*1024;
static void CHACHA20(benchmark::Bench& bench, size_t buffersize) static void CHACHA20(benchmark::Bench& bench, size_t buffersize)
{ {
std::vector<uint8_t> key(32,0); std::vector<std::byte> key(32, {});
ChaCha20 ctx(key.data()); ChaCha20 ctx(key);
ctx.Seek64({0, 0}, 0); ctx.Seek({0, 0}, 0);
std::vector<uint8_t> in(buffersize,0); std::vector<std::byte> in(buffersize, {});
std::vector<uint8_t> out(buffersize,0); std::vector<std::byte> out(buffersize, {});
bench.batch(in.size()).unit("byte").run([&] { bench.batch(in.size()).unit("byte").run([&] {
ctx.Crypt(in.data(), out.data(), in.size()); ctx.Crypt(in, out);
}); });
} }

View file

@ -8,6 +8,7 @@
#include <crypto/common.h> #include <crypto/common.h>
#include <crypto/chacha20.h> #include <crypto/chacha20.h>
#include <support/cleanse.h> #include <support/cleanse.h>
#include <span.h>
#include <algorithm> #include <algorithm>
#include <string.h> #include <string.h>
@ -22,38 +23,34 @@ 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) #define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0)
void ChaCha20Aligned::SetKey32(const unsigned char* k) void ChaCha20Aligned::SetKey(Span<const std::byte> key) noexcept
{ {
input[0] = ReadLE32(k + 0); assert(key.size() == KEYLEN);
input[1] = ReadLE32(k + 4); input[0] = ReadLE32(UCharCast(key.data() + 0));
input[2] = ReadLE32(k + 8); input[1] = ReadLE32(UCharCast(key.data() + 4));
input[3] = ReadLE32(k + 12); input[2] = ReadLE32(UCharCast(key.data() + 8));
input[4] = ReadLE32(k + 16); input[3] = ReadLE32(UCharCast(key.data() + 12));
input[5] = ReadLE32(k + 20); input[4] = ReadLE32(UCharCast(key.data() + 16));
input[6] = ReadLE32(k + 24); input[5] = ReadLE32(UCharCast(key.data() + 20));
input[7] = ReadLE32(k + 28); input[6] = ReadLE32(UCharCast(key.data() + 24));
input[7] = ReadLE32(UCharCast(key.data() + 28));
input[8] = 0; input[8] = 0;
input[9] = 0; input[9] = 0;
input[10] = 0; input[10] = 0;
input[11] = 0; input[11] = 0;
} }
ChaCha20Aligned::ChaCha20Aligned()
{
memset(input, 0, sizeof(input));
}
ChaCha20Aligned::~ChaCha20Aligned() ChaCha20Aligned::~ChaCha20Aligned()
{ {
memory_cleanse(input, sizeof(input)); memory_cleanse(input, sizeof(input));
} }
ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32) ChaCha20Aligned::ChaCha20Aligned(Span<const std::byte> key) noexcept
{ {
SetKey32(key32); SetKey(key);
} }
void ChaCha20Aligned::Seek64(Nonce96 nonce, uint32_t block_counter) void ChaCha20Aligned::Seek(Nonce96 nonce, uint32_t block_counter) noexcept
{ {
input[8] = block_counter; input[8] = block_counter;
input[9] = nonce.first; input[9] = nonce.first;
@ -61,8 +58,12 @@ void ChaCha20Aligned::Seek64(Nonce96 nonce, uint32_t block_counter)
input[11] = nonce.second >> 32; input[11] = nonce.second >> 32;
} }
inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) inline void ChaCha20Aligned::Keystream(Span<std::byte> output) noexcept
{ {
unsigned char* c = UCharCast(output.data());
size_t blocks = output.size() / BLOCKLEN;
assert(blocks * BLOCKLEN == output.size());
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
@ -154,12 +155,18 @@ inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks)
return; return;
} }
blocks -= 1; blocks -= 1;
c += 64; c += BLOCKLEN;
} }
} }
inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks) inline void ChaCha20Aligned::Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept
{ {
assert(in_bytes.size() == out_bytes.size());
const unsigned char* m = UCharCast(in_bytes.data());
unsigned char* c = UCharCast(out_bytes.data());
size_t blocks = out_bytes.size() / BLOCKLEN;
assert(blocks * BLOCKLEN == out_bytes.size());
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
@ -268,70 +275,75 @@ inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, s
return; return;
} }
blocks -= 1; blocks -= 1;
c += 64; c += BLOCKLEN;
m += 64; m += BLOCKLEN;
} }
} }
void ChaCha20::Keystream(unsigned char* c, size_t bytes) void ChaCha20::Keystream(Span<std::byte> out) noexcept
{ {
if (!bytes) return; if (out.empty()) return;
if (m_bufleft) { if (m_bufleft) {
unsigned reuse = std::min<size_t>(m_bufleft, bytes); unsigned reuse = std::min<size_t>(m_bufleft, out.size());
memcpy(c, m_buffer + 64 - m_bufleft, reuse); std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin());
m_bufleft -= reuse; m_bufleft -= reuse;
bytes -= reuse; out = out.subspan(reuse);
c += reuse;
} }
if (bytes >= 64) { if (out.size() >= m_aligned.BLOCKLEN) {
size_t blocks = bytes / 64; size_t blocks = out.size() / m_aligned.BLOCKLEN;
m_aligned.Keystream64(c, blocks); m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN));
c += blocks * 64; out = out.subspan(blocks * m_aligned.BLOCKLEN);
bytes -= blocks * 64;
} }
if (bytes) { if (!out.empty()) {
m_aligned.Keystream64(m_buffer, 1); m_aligned.Keystream(m_buffer);
memcpy(c, m_buffer, bytes); std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin());
m_bufleft = 64 - bytes; m_bufleft = m_aligned.BLOCKLEN - out.size();
} }
} }
void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) void ChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
{ {
if (!bytes) return; assert(input.size() == output.size());
if (!input.size()) return;
if (m_bufleft) { if (m_bufleft) {
unsigned reuse = std::min<size_t>(m_bufleft, bytes); unsigned reuse = std::min<size_t>(m_bufleft, input.size());
for (unsigned i = 0; i < reuse; i++) { for (unsigned i = 0; i < reuse; i++) {
c[i] = m[i] ^ m_buffer[64 - m_bufleft + i]; output[i] = input[i] ^ m_buffer[m_aligned.BLOCKLEN - m_bufleft + i];
} }
m_bufleft -= reuse; m_bufleft -= reuse;
bytes -= reuse; output = output.subspan(reuse);
c += reuse; input = input.subspan(reuse);
m += reuse;
} }
if (bytes >= 64) { if (input.size() >= m_aligned.BLOCKLEN) {
size_t blocks = bytes / 64; size_t blocks = input.size() / m_aligned.BLOCKLEN;
m_aligned.Crypt64(m, c, blocks); m_aligned.Crypt(input.first(blocks * m_aligned.BLOCKLEN), output.first(blocks * m_aligned.BLOCKLEN));
c += blocks * 64; output = output.subspan(blocks * m_aligned.BLOCKLEN);
m += blocks * 64; input = input.subspan(blocks * m_aligned.BLOCKLEN);
bytes -= blocks * 64;
} }
if (bytes) { if (!input.empty()) {
m_aligned.Keystream64(m_buffer, 1); m_aligned.Keystream(m_buffer);
for (unsigned i = 0; i < bytes; i++) { for (unsigned i = 0; i < input.size(); i++) {
c[i] = m[i] ^ m_buffer[i]; output[i] = input[i] ^ m_buffer[i];
} }
m_bufleft = 64 - bytes; m_bufleft = m_aligned.BLOCKLEN - input.size();
} }
} }
ChaCha20::~ChaCha20() ChaCha20::~ChaCha20()
{ {
memory_cleanse(m_buffer, sizeof(m_buffer)); memory_cleanse(m_buffer.data(), m_buffer.size());
}
void ChaCha20::SetKey(Span<const std::byte> key) noexcept
{
m_aligned.SetKey(key);
m_bufleft = 0;
memory_cleanse(m_buffer.data(), m_buffer.size());
} }
FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept : FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
m_chacha20(UCharCast(key.data())), m_rekey_interval(rekey_interval) m_chacha20(key), m_rekey_interval(rekey_interval)
{ {
assert(key.size() == KEYLEN); assert(key.size() == KEYLEN);
} }
@ -341,20 +353,20 @@ void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noex
assert(input.size() == output.size()); assert(input.size() == output.size());
// Invoke internal stream cipher for actual encryption/decryption. // Invoke internal stream cipher for actual encryption/decryption.
m_chacha20.Crypt(UCharCast(input.data()), UCharCast(output.data()), input.size()); m_chacha20.Crypt(input, output);
// Rekey after m_rekey_interval encryptions/decryptions. // Rekey after m_rekey_interval encryptions/decryptions.
if (++m_chunk_counter == m_rekey_interval) { if (++m_chunk_counter == m_rekey_interval) {
// Get new key from the stream cipher. // Get new key from the stream cipher.
std::byte new_key[KEYLEN]; std::byte new_key[KEYLEN];
m_chacha20.Keystream(UCharCast(new_key), sizeof(new_key)); m_chacha20.Keystream(new_key);
// Update its key. // Update its key.
m_chacha20.SetKey32(UCharCast(new_key)); m_chacha20.SetKey(new_key);
// Wipe the key (a copy remains inside m_chacha20, where it'll be wiped on the next rekey // Wipe the key (a copy remains inside m_chacha20, where it'll be wiped on the next rekey
// or on destruction). // or on destruction).
memory_cleanse(new_key, sizeof(new_key)); memory_cleanse(new_key, sizeof(new_key));
// Set the nonce for the new section of output. // Set the nonce for the new section of output.
m_chacha20.Seek64({0, ++m_rekey_counter}, 0); m_chacha20.Seek({0, ++m_rekey_counter}, 0);
// Reset the chunk counter. // Reset the chunk counter.
m_chunk_counter = 0; m_chunk_counter = 0;
} }

View file

@ -28,16 +28,23 @@ private:
uint32_t input[12]; uint32_t input[12];
public: public:
ChaCha20Aligned(); /** Expected key length in constructor and SetKey. */
static constexpr unsigned KEYLEN{32};
/** Block size (inputs/outputs to Keystream / Crypt should be multiples of this). */
static constexpr unsigned BLOCKLEN{64};
/** For safety, disallow initialization without key. */
ChaCha20Aligned() noexcept = delete;
/** Initialize a cipher with specified 32-byte key. */ /** Initialize a cipher with specified 32-byte key. */
ChaCha20Aligned(const unsigned char* key32); ChaCha20Aligned(Span<const std::byte> key) noexcept;
/** Destructor to clean up private memory. */ /** Destructor to clean up private memory. */
~ChaCha20Aligned(); ~ChaCha20Aligned();
/** set 32-byte key. */ /** Set 32-byte key, and seek to nonce 0 and block position 0. */
void SetKey32(const unsigned char* key32); void SetKey(Span<const std::byte> key) noexcept;
/** Type for 96-bit nonces used by the Set function below. /** Type for 96-bit nonces used by the Set function below.
* *
@ -51,18 +58,19 @@ public:
/** Set the 96-bit nonce and 32-bit block counter. /** Set the 96-bit nonce and 32-bit block counter.
* *
* Block_counter selects a position to seek to (to byte 64*block_counter). After 256 GiB, the * Block_counter selects a position to seek to (to byte BLOCKLEN*block_counter). After 256 GiB,
* block counter overflows, and nonce.first is incremented. * the block counter overflows, and nonce.first is incremented.
*/ */
void Seek64(Nonce96 nonce, uint32_t block_counter); void Seek(Nonce96 nonce, uint32_t block_counter) noexcept;
/** outputs the keystream of size <64*blocks> into <c> */ /** outputs the keystream into out, whose length must be a multiple of BLOCKLEN. */
void Keystream64(unsigned char* c, size_t blocks); void Keystream(Span<std::byte> out) noexcept;
/** enciphers the message <input> of length <64*blocks> and write the enciphered representation into <output> /** en/deciphers the message <input> and write the result into <output>
* Used for encryption and decryption (XOR) *
* The size of input and output must be equal, and be a multiple of BLOCKLEN.
*/ */
void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks); void Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept;
}; };
/** Unrestricted ChaCha20 cipher. */ /** Unrestricted ChaCha20 cipher. */
@ -70,42 +78,43 @@ class ChaCha20
{ {
private: private:
ChaCha20Aligned m_aligned; ChaCha20Aligned m_aligned;
unsigned char m_buffer[64] = {0}; std::array<std::byte, ChaCha20Aligned::BLOCKLEN> m_buffer;
unsigned m_bufleft{0}; unsigned m_bufleft{0};
public: public:
ChaCha20() = default; /** Expected key length in constructor and SetKey. */
static constexpr unsigned KEYLEN = ChaCha20Aligned::KEYLEN;
/** For safety, disallow initialization without key. */
ChaCha20() noexcept = delete;
/** Initialize a cipher with specified 32-byte key. */ /** Initialize a cipher with specified 32-byte key. */
ChaCha20(const unsigned char* key32) : m_aligned(key32) {} ChaCha20(Span<const std::byte> key) noexcept : m_aligned(key) {}
/** Destructor to clean up private memory. */ /** Destructor to clean up private memory. */
~ChaCha20(); ~ChaCha20();
/** set 32-byte key. */ /** Set 32-byte key, and seek to nonce 0 and block position 0. */
void SetKey32(const unsigned char* key32) void SetKey(Span<const std::byte> key) noexcept;
{
m_aligned.SetKey32(key32);
m_bufleft = 0;
}
/** 96-bit nonce type. */ /** 96-bit nonce type. */
using Nonce96 = ChaCha20Aligned::Nonce96; using Nonce96 = ChaCha20Aligned::Nonce96;
/** Set the 96-bit nonce and 32-bit block counter. */ /** Set the 96-bit nonce and 32-bit block counter. See ChaCha20Aligned::Seek. */
void Seek64(Nonce96 nonce, uint32_t block_counter) void Seek(Nonce96 nonce, uint32_t block_counter) noexcept
{ {
m_aligned.Seek64(nonce, block_counter); m_aligned.Seek(nonce, block_counter);
m_bufleft = 0; m_bufleft = 0;
} }
/** outputs the keystream of size <bytes> into <c> */ /** en/deciphers the message <in_bytes> and write the result into <out_bytes>
void Keystream(unsigned char* c, size_t bytes); *
* The size of in_bytes and out_bytes must be equal.
/** enciphers the message <input> of length <bytes> and write the enciphered representation into <output>
* Used for encryption and decryption (XOR)
*/ */
void Crypt(const unsigned char* input, unsigned char* output, size_t bytes); void Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept;
/** outputs the keystream to out. */
void Keystream(Span<std::byte> out) noexcept;
}; };
/** Forward-secure ChaCha20 /** Forward-secure ChaCha20

View file

@ -13,7 +13,7 @@
#include <assert.h> #include <assert.h>
#include <cstddef> #include <cstddef>
AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept : m_chacha20(UCharCast(key.data())) AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept : m_chacha20(key)
{ {
assert(key.size() == KEYLEN); assert(key.size() == KEYLEN);
} }
@ -21,7 +21,7 @@ AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept :
void AEADChaCha20Poly1305::SetKey(Span<const std::byte> key) noexcept void AEADChaCha20Poly1305::SetKey(Span<const std::byte> key) noexcept
{ {
assert(key.size() == KEYLEN); assert(key.size() == KEYLEN);
m_chacha20.SetKey32(UCharCast(key.data())); m_chacha20.SetKey(key);
} }
namespace { namespace {
@ -46,8 +46,8 @@ void ComputeTag(ChaCha20& chacha20, Span<const std::byte> aad, Span<const std::b
static const std::byte PADDING[16] = {{}}; static const std::byte PADDING[16] = {{}};
// Get block of keystream (use a full 64 byte buffer to avoid the need for chacha20's own buffering). // Get block of keystream (use a full 64 byte buffer to avoid the need for chacha20's own buffering).
std::byte first_block[64]; std::byte first_block[ChaCha20Aligned::BLOCKLEN];
chacha20.Keystream(UCharCast(first_block), sizeof(first_block)); chacha20.Keystream(first_block);
// Use the first 32 bytes of the first keystream block as poly1305 key. // Use the first 32 bytes of the first keystream block as poly1305 key.
Poly1305 poly1305{Span{first_block}.first(Poly1305::KEYLEN)}; Poly1305 poly1305{Span{first_block}.first(Poly1305::KEYLEN)};
@ -76,12 +76,12 @@ void AEADChaCha20Poly1305::Encrypt(Span<const std::byte> plain1, Span<const std:
assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION); assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
// Encrypt using ChaCha20 (starting at block 1). // Encrypt using ChaCha20 (starting at block 1).
m_chacha20.Seek64(nonce, 1); m_chacha20.Seek(nonce, 1);
m_chacha20.Crypt(UCharCast(plain1.data()), UCharCast(cipher.data()), plain1.size()); m_chacha20.Crypt(plain1, cipher.first(plain1.size()));
m_chacha20.Crypt(UCharCast(plain2.data()), UCharCast(cipher.data() + plain1.size()), plain2.size()); m_chacha20.Crypt(plain2, cipher.subspan(plain1.size()).first(plain2.size()));
// Seek to block 0, and compute tag using key drawn from there. // Seek to block 0, and compute tag using key drawn from there.
m_chacha20.Seek64(nonce, 0); m_chacha20.Seek(nonce, 0);
ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION)); ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION));
} }
@ -90,22 +90,22 @@ bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std:
assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION); assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
// Verify tag (using key drawn from block 0). // Verify tag (using key drawn from block 0).
m_chacha20.Seek64(nonce, 0); m_chacha20.Seek(nonce, 0);
std::byte expected_tag[EXPANSION]; std::byte expected_tag[EXPANSION];
ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag); ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag);
if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.last(EXPANSION).data()), EXPANSION)) return false; if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.last(EXPANSION).data()), EXPANSION)) return false;
// Decrypt (starting at block 1). // Decrypt (starting at block 1).
m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain1.data()), plain1.size()); m_chacha20.Crypt(cipher.first(plain1.size()), plain1);
m_chacha20.Crypt(UCharCast(cipher.data() + plain1.size()), UCharCast(plain2.data()), plain2.size()); m_chacha20.Crypt(cipher.subspan(plain1.size()).first(plain2.size()), plain2);
return true; return true;
} }
void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept
{ {
// Skip the first output block, as it's used for generating the poly1305 key. // Skip the first output block, as it's used for generating the poly1305 key.
m_chacha20.Seek64(nonce, 1); m_chacha20.Seek(nonce, 1);
m_chacha20.Keystream(UCharCast(keystream.data()), keystream.size()); m_chacha20.Keystream(keystream);
} }
void FSChaCha20Poly1305::NextPacket() noexcept void FSChaCha20Poly1305::NextPacket() noexcept
@ -113,7 +113,7 @@ void FSChaCha20Poly1305::NextPacket() noexcept
if (++m_packet_counter == m_rekey_interval) { if (++m_packet_counter == m_rekey_interval) {
// Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though // Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though
// we only need KEYLEN (32) bytes. // we only need KEYLEN (32) bytes.
std::byte one_block[64]; std::byte one_block[ChaCha20Aligned::BLOCKLEN];
m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block); m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block);
// Switch keys. // Switch keys.
m_aead.SetKey(Span{one_block}.first(KEYLEN)); m_aead.SetKey(Span{one_block}.first(KEYLEN));

View file

@ -299,7 +299,8 @@ Num3072 MuHash3072::ToNum3072(Span<const unsigned char> in) {
unsigned char tmp[Num3072::BYTE_SIZE]; unsigned char tmp[Num3072::BYTE_SIZE];
uint256 hashed_in{(HashWriter{} << in).GetSHA256()}; uint256 hashed_in{(HashWriter{} << in).GetSHA256()};
ChaCha20Aligned(hashed_in.data()).Keystream64(tmp, Num3072::BYTE_SIZE / 64); static_assert(sizeof(tmp) % ChaCha20Aligned::BLOCKLEN == 0);
ChaCha20Aligned{MakeByteSpan(hashed_in)}.Keystream(MakeWritableByteSpan(tmp));
Num3072 out{tmp}; Num3072 out{tmp};
return out; return out;

View file

@ -6,6 +6,7 @@
#include <random.h> #include <random.h>
#include <compat/cpuid.h> #include <compat/cpuid.h>
#include <crypto/chacha20.h>
#include <crypto/sha256.h> #include <crypto/sha256.h>
#include <crypto/sha512.h> #include <crypto/sha512.h>
#include <logging.h> #include <logging.h>
@ -16,6 +17,7 @@
#include <sync.h> #include <sync.h>
#include <util/time.h> #include <util/time.h>
#include <array>
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <thread> #include <thread>
@ -577,7 +579,7 @@ uint256 GetRandHash() noexcept
void FastRandomContext::RandomSeed() void FastRandomContext::RandomSeed()
{ {
uint256 seed = GetRandHash(); uint256 seed = GetRandHash();
rng.SetKey32(seed.begin()); rng.SetKey(MakeByteSpan(seed));
requires_seed = false; requires_seed = false;
} }
@ -585,18 +587,15 @@ uint256 FastRandomContext::rand256() noexcept
{ {
if (requires_seed) RandomSeed(); if (requires_seed) RandomSeed();
uint256 ret; uint256 ret;
rng.Keystream(ret.data(), ret.size()); rng.Keystream(MakeWritableByteSpan(ret));
return ret; return ret;
} }
template <typename B> template <typename B>
std::vector<B> FastRandomContext::randbytes(size_t len) std::vector<B> FastRandomContext::randbytes(size_t len)
{ {
if (requires_seed) RandomSeed();
std::vector<B> ret(len); std::vector<B> ret(len);
if (len > 0) { fillrand(MakeWritableByteSpan(ret));
rng.Keystream(UCharCast(ret.data()), len);
}
return ret; return ret;
} }
template std::vector<unsigned char> FastRandomContext::randbytes(size_t); template std::vector<unsigned char> FastRandomContext::randbytes(size_t);
@ -605,13 +604,10 @@ template std::vector<std::byte> FastRandomContext::randbytes(size_t);
void FastRandomContext::fillrand(Span<std::byte> output) void FastRandomContext::fillrand(Span<std::byte> output)
{ {
if (requires_seed) RandomSeed(); if (requires_seed) RandomSeed();
rng.Keystream(UCharCast(output.data()), output.size()); rng.Keystream(output);
} }
FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bitbuf_size(0) FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), rng(MakeByteSpan(seed)), bitbuf_size(0) {}
{
rng.SetKey32(seed.begin());
}
bool Random_SanityCheck() bool Random_SanityCheck()
{ {
@ -659,13 +655,13 @@ bool Random_SanityCheck()
return true; return true;
} }
FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bitbuf_size(0) static constexpr std::array<std::byte, ChaCha20::KEYLEN> ZERO_KEY{};
FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), rng(ZERO_KEY), bitbuf_size(0)
{ {
if (!fDeterministic) { // Note that despite always initializing with ZERO_KEY, requires_seed is set to true if not
return; // fDeterministic. That means the rng will be reinitialized with a secure random key upon first
} // use.
uint256 seed;
rng.SetKey32(seed.begin());
} }
FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept

View file

@ -175,9 +175,9 @@ public:
uint64_t rand64() noexcept uint64_t rand64() noexcept
{ {
if (requires_seed) RandomSeed(); if (requires_seed) RandomSeed();
unsigned char buf[8]; std::array<std::byte, 8> buf;
rng.Keystream(buf, 8); rng.Keystream(buf);
return ReadLE64(buf); return ReadLE64(UCharCast(buf.data()));
} }
/** Generate a random (bits)-bit integer. */ /** Generate a random (bits)-bit integer. */

View file

@ -133,27 +133,27 @@ static void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, b
static void TestChaCha20(const std::string &hex_message, const std::string &hexkey, ChaCha20::Nonce96 nonce, uint32_t seek, const std::string& hexout) static void TestChaCha20(const std::string &hex_message, const std::string &hexkey, ChaCha20::Nonce96 nonce, uint32_t seek, const std::string& hexout)
{ {
std::vector<unsigned char> key = ParseHex(hexkey); auto key = ParseHex<std::byte>(hexkey);
assert(key.size() == 32); assert(key.size() == 32);
std::vector<unsigned char> m = ParseHex(hex_message); auto m = ParseHex<std::byte>(hex_message);
ChaCha20 rng(key.data()); ChaCha20 rng{key};
rng.Seek64(nonce, seek); rng.Seek(nonce, seek);
std::vector<unsigned char> outres; std::vector<std::byte> outres;
outres.resize(hexout.size() / 2); outres.resize(hexout.size() / 2);
assert(hex_message.empty() || m.size() * 2 == hexout.size()); 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 // perform the ChaCha20 round(s), if message is provided it will output the encrypted ciphertext otherwise the keystream
if (!hex_message.empty()) { if (!hex_message.empty()) {
rng.Crypt(m.data(), outres.data(), outres.size()); rng.Crypt(m, outres);
} else { } else {
rng.Keystream(outres.data(), outres.size()); rng.Keystream(outres);
} }
BOOST_CHECK_EQUAL(hexout, HexStr(outres)); BOOST_CHECK_EQUAL(hexout, HexStr(outres));
if (!hex_message.empty()) { if (!hex_message.empty()) {
// Manually XOR with the keystream and compare the output // Manually XOR with the keystream and compare the output
rng.Seek64(nonce, seek); rng.Seek(nonce, seek);
std::vector<unsigned char> only_keystream(outres.size()); std::vector<std::byte> only_keystream(outres.size());
rng.Keystream(only_keystream.data(), only_keystream.size()); rng.Keystream(only_keystream);
for (size_t i = 0; i != m.size(); i++) { for (size_t i = 0; i != m.size(); i++) {
outres[i] = m[i] ^ only_keystream[i]; outres[i] = m[i] ^ only_keystream[i];
} }
@ -167,14 +167,14 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk
lens[1] = InsecureRandRange(hexout.size() / 2U + 1U - lens[0]); lens[1] = InsecureRandRange(hexout.size() / 2U + 1U - lens[0]);
lens[2] = hexout.size() / 2U - lens[0] - lens[1]; lens[2] = hexout.size() / 2U - lens[0] - lens[1];
rng.Seek64(nonce, seek); rng.Seek(nonce, seek);
outres.assign(hexout.size() / 2U, 0); outres.assign(hexout.size() / 2U, {});
size_t pos = 0; size_t pos = 0;
for (int j = 0; j < 3; ++j) { for (int j = 0; j < 3; ++j) {
if (!hex_message.empty()) { if (!hex_message.empty()) {
rng.Crypt(m.data() + pos, outres.data() + pos, lens[j]); rng.Crypt(Span{m}.subspan(pos, lens[j]), Span{outres}.subspan(pos, lens[j]));
} else { } else {
rng.Keystream(outres.data() + pos, lens[j]); rng.Keystream(Span{outres}.subspan(pos, lens[j]));
} }
pos += lens[j]; pos += lens[j];
} }
@ -190,7 +190,7 @@ static void TestFSChaCha20(const std::string& hex_plaintext, const std::string&
auto plaintext = ParseHex<std::byte>(hex_plaintext); auto plaintext = ParseHex<std::byte>(hex_plaintext);
auto fsc20 = FSChaCha20{key, rekey_interval}; auto fsc20 = FSChaCha20{key, rekey_interval};
auto c20 = ChaCha20{UCharCast(key.data())}; auto c20 = ChaCha20{key};
std::vector<std::byte> fsc20_output; std::vector<std::byte> fsc20_output;
fsc20_output.resize(plaintext.size()); fsc20_output.resize(plaintext.size());
@ -200,23 +200,23 @@ static void TestFSChaCha20(const std::string& hex_plaintext, const std::string&
for (size_t i = 0; i < rekey_interval; i++) { for (size_t i = 0; i < rekey_interval; i++) {
fsc20.Crypt(plaintext, fsc20_output); fsc20.Crypt(plaintext, fsc20_output);
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size()); c20.Crypt(plaintext, c20_output);
BOOST_CHECK(c20_output == fsc20_output); BOOST_CHECK(c20_output == fsc20_output);
} }
// At the rotation interval, the outputs will no longer match // At the rotation interval, the outputs will no longer match
fsc20.Crypt(plaintext, fsc20_output); fsc20.Crypt(plaintext, fsc20_output);
auto c20_copy = c20; auto c20_copy = c20;
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size()); c20.Crypt(plaintext, c20_output);
BOOST_CHECK(c20_output != fsc20_output); BOOST_CHECK(c20_output != fsc20_output);
std::byte new_key[FSChaCha20::KEYLEN]; std::byte new_key[FSChaCha20::KEYLEN];
c20_copy.Keystream(UCharCast(new_key), sizeof(new_key)); c20_copy.Keystream(new_key);
c20.SetKey32(UCharCast(new_key)); c20.SetKey(new_key);
c20.Seek64({0, 1}, 0); c20.Seek({0, 1}, 0);
// Outputs should match again after simulating key rotation // Outputs should match again after simulating key rotation
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size()); c20.Crypt(plaintext, c20_output);
BOOST_CHECK(c20_output == fsc20_output); BOOST_CHECK(c20_output == fsc20_output);
BOOST_CHECK_EQUAL(HexStr(fsc20_output), ciphertext_after_rotation); BOOST_CHECK_EQUAL(HexStr(fsc20_output), ciphertext_after_rotation);
@ -226,10 +226,9 @@ static void TestPoly1305(const std::string &hexmessage, const std::string &hexke
{ {
auto key = ParseHex<std::byte>(hexkey); auto key = ParseHex<std::byte>(hexkey);
auto m = ParseHex<std::byte>(hexmessage); auto m = ParseHex<std::byte>(hexmessage);
auto tag = ParseHex<std::byte>(hextag);
std::vector<std::byte> tagres(Poly1305::TAGLEN); std::vector<std::byte> tagres(Poly1305::TAGLEN);
Poly1305{key}.Update(m).Finalize(tagres); Poly1305{key}.Update(m).Finalize(tagres);
BOOST_CHECK(tag == tagres); BOOST_CHECK_EQUAL(HexStr(tagres), hextag);
// Test incremental interface // Test incremental interface
for (int splits = 0; splits < 10; ++splits) { for (int splits = 0; splits < 10; ++splits) {
@ -243,7 +242,7 @@ static void TestPoly1305(const std::string &hexmessage, const std::string &hexke
} }
tagres.assign(Poly1305::TAGLEN, std::byte{}); tagres.assign(Poly1305::TAGLEN, std::byte{});
poly1305.Update(data).Finalize(tagres); poly1305.Update(data).Finalize(tagres);
BOOST_CHECK(tag == tagres); BOOST_CHECK_EQUAL(HexStr(tagres), hextag);
} }
} }
} }
@ -823,20 +822,20 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector)
BOOST_AUTO_TEST_CASE(chacha20_midblock) BOOST_AUTO_TEST_CASE(chacha20_midblock)
{ {
auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000"); auto key = ParseHex<std::byte>("0000000000000000000000000000000000000000000000000000000000000000");
ChaCha20 c20{key.data()}; ChaCha20 c20{key};
// get one block of keystream // get one block of keystream
unsigned char block[64]; std::byte block[64];
c20.Keystream(block, sizeof(block)); c20.Keystream(block);
unsigned char b1[5], b2[7], b3[52]; std::byte b1[5], b2[7], b3[52];
c20 = ChaCha20{key.data()}; c20 = ChaCha20{key};
c20.Keystream(b1, 5); c20.Keystream(b1);
c20.Keystream(b2, 7); c20.Keystream(b2);
c20.Keystream(b3, 52); c20.Keystream(b3);
BOOST_CHECK_EQUAL(0, memcmp(b1, block, 5)); BOOST_CHECK(Span{block}.first(5) == Span{b1});
BOOST_CHECK_EQUAL(0, memcmp(b2, block + 5, 7)); BOOST_CHECK(Span{block}.subspan(5, 7) == Span{b2});
BOOST_CHECK_EQUAL(0, memcmp(b3, block + 12, 52)); BOOST_CHECK(Span{block}.last(52) == Span{b3});
} }
BOOST_AUTO_TEST_CASE(poly1305_testvector) BOOST_AUTO_TEST_CASE(poly1305_testvector)
@ -922,15 +921,15 @@ BOOST_AUTO_TEST_CASE(poly1305_testvector)
auto total_key = ParseHex<std::byte>("01020304050607fffefdfcfbfaf9ffffffffffffffffffffffffffff00000000"); auto total_key = ParseHex<std::byte>("01020304050607fffefdfcfbfaf9ffffffffffffffffffffffffffff00000000");
Poly1305 total_ctx(total_key); Poly1305 total_ctx(total_key);
for (unsigned i = 0; i < 256; ++i) { for (unsigned i = 0; i < 256; ++i) {
std::vector<std::byte> key(32, std::byte{(uint8_t)i}); std::vector<std::byte> key(32, std::byte{uint8_t(i)});
std::vector<std::byte> msg(i, std::byte{(uint8_t)i}); std::vector<std::byte> msg(i, std::byte{uint8_t(i)});
std::array<std::byte, Poly1305::TAGLEN> tag; std::array<std::byte, Poly1305::TAGLEN> tag;
Poly1305{key}.Update(msg).Finalize(tag); Poly1305{key}.Update(msg).Finalize(tag);
total_ctx.Update(tag); total_ctx.Update(tag);
} }
std::vector<std::byte> total_tag(Poly1305::TAGLEN); std::vector<std::byte> total_tag(Poly1305::TAGLEN);
total_ctx.Finalize(total_tag); total_ctx.Finalize(total_tag);
BOOST_CHECK(total_tag == ParseHex<std::byte>("64afe2e8d6ad7bbdd287f97c44623d39")); BOOST_CHECK_EQUAL(HexStr(total_tag), "64afe2e8d6ad7bbdd287f97c44623d39");
} }
// Tests with sparse messages and random keys. // Tests with sparse messages and random keys.

View file

@ -17,20 +17,18 @@ FUZZ_TARGET(crypto_chacha20)
{ {
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
ChaCha20 chacha20; const auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, ChaCha20::KEYLEN);
if (fuzzed_data_provider.ConsumeBool()) { ChaCha20 chacha20{key};
const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
chacha20 = ChaCha20{key.data()};
}
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
CallOneOf( CallOneOf(
fuzzed_data_provider, fuzzed_data_provider,
[&] { [&] {
std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, ChaCha20::KEYLEN);
chacha20.SetKey32(key.data()); chacha20.SetKey(key);
}, },
[&] { [&] {
chacha20.Seek64( chacha20.Seek(
{ {
fuzzed_data_provider.ConsumeIntegral<uint32_t>(), fuzzed_data_provider.ConsumeIntegral<uint32_t>(),
fuzzed_data_provider.ConsumeIntegral<uint64_t>() fuzzed_data_provider.ConsumeIntegral<uint64_t>()
@ -38,12 +36,12 @@ FUZZ_TARGET(crypto_chacha20)
}, },
[&] { [&] {
std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
chacha20.Keystream(output.data(), output.size()); chacha20.Keystream(MakeWritableByteSpan(output));
}, },
[&] { [&] {
std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); std::vector<std::byte> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096));
const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); const auto input = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, output.size());
chacha20.Crypt(input.data(), output.data(), input.size()); chacha20.Crypt(input, output);
}); });
} }
} }
@ -62,9 +60,7 @@ template<bool UseCrypt>
void ChaCha20SplitFuzz(FuzzedDataProvider& provider) void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
{ {
// Determine key, iv, start position, length. // Determine key, iv, start position, length.
unsigned char key[32] = {0}; auto key_bytes = ConsumeFixedLengthByteVector<std::byte>(provider, ChaCha20::KEYLEN);
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 iv = provider.ConsumeIntegral<uint64_t>();
uint32_t iv_prefix = provider.ConsumeIntegral<uint32_t>(); uint32_t iv_prefix = provider.ConsumeIntegral<uint32_t>();
uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000); uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000);
@ -72,13 +68,13 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
uint32_t seek = provider.ConsumeIntegralInRange<uint32_t>(0, ~(uint32_t)(total_bytes >> 6)); uint32_t seek = provider.ConsumeIntegralInRange<uint32_t>(0, ~(uint32_t)(total_bytes >> 6));
// Initialize two ChaCha20 ciphers, with the same key/iv/position. // Initialize two ChaCha20 ciphers, with the same key/iv/position.
ChaCha20 crypt1(key); ChaCha20 crypt1(key_bytes);
ChaCha20 crypt2(key); ChaCha20 crypt2(key_bytes);
crypt1.Seek64({iv_prefix, iv}, seek); crypt1.Seek({iv_prefix, iv}, seek);
crypt2.Seek64({iv_prefix, iv}, seek); crypt2.Seek({iv_prefix, iv}, seek);
// Construct vectors with data. // Construct vectors with data.
std::vector<unsigned char> data1, data2; std::vector<std::byte> data1, data2;
data1.resize(total_bytes); data1.resize(total_bytes);
data2.resize(total_bytes); data2.resize(total_bytes);
@ -90,14 +86,14 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
uint64_t bytes = 0; uint64_t bytes = 0;
while (bytes < (total_bytes & ~uint64_t{7})) { while (bytes < (total_bytes & ~uint64_t{7})) {
uint64_t val = rng(); uint64_t val = rng();
WriteLE64(data1.data() + bytes, val); WriteLE64(UCharCast(data1.data() + bytes), val);
WriteLE64(data2.data() + bytes, val); WriteLE64(UCharCast(data2.data() + bytes), val);
bytes += 8; bytes += 8;
} }
if (bytes < total_bytes) { if (bytes < total_bytes) {
unsigned char valbytes[8]; std::byte valbytes[8];
uint64_t val = rng(); uint64_t val = rng();
WriteLE64(valbytes, val); WriteLE64(UCharCast(valbytes), val);
std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes); std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes);
std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes); std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes);
} }
@ -108,9 +104,9 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
// Encrypt data1, the whole array at once. // Encrypt data1, the whole array at once.
if constexpr (UseCrypt) { if constexpr (UseCrypt) {
crypt1.Crypt(data1.data(), data1.data(), total_bytes); crypt1.Crypt(data1, data1);
} else { } else {
crypt1.Keystream(data1.data(), total_bytes); crypt1.Keystream(data1);
} }
// Encrypt data2, in at most 256 chunks. // Encrypt data2, in at most 256 chunks.
@ -127,9 +123,9 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider)
// This tests that Keystream() has the same behavior as Crypt() applied // This tests that Keystream() has the same behavior as Crypt() applied
// to 0x00 input bytes. // to 0x00 input bytes.
if (UseCrypt || provider.ConsumeBool()) { if (UseCrypt || provider.ConsumeBool()) {
crypt2.Crypt(data2.data() + bytes2, data2.data() + bytes2, now); crypt2.Crypt(Span{data2}.subspan(bytes2, now), Span{data2}.subspan(bytes2, now));
} else { } else {
crypt2.Keystream(data2.data() + bytes2, now); crypt2.Keystream(Span{data2}.subspan(bytes2, now));
} }
bytes2 += now; bytes2 += now;
if (is_last) break; if (is_last) break;

View file

@ -267,22 +267,15 @@ void ECRYPT_keystream_bytes(ECRYPT_ctx* x, u8* stream, u32 bytes)
FUZZ_TARGET(crypto_diff_fuzz_chacha20) FUZZ_TARGET(crypto_diff_fuzz_chacha20)
{ {
static const unsigned char ZEROKEY[32] = {0};
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
ChaCha20 chacha20;
ECRYPT_ctx ctx; ECRYPT_ctx ctx;
if (fuzzed_data_provider.ConsumeBool()) { const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); ChaCha20 chacha20{MakeByteSpan(key)};
chacha20 = ChaCha20{key.data()}; ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0);
ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0);
} 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 // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does
static const uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0}; static const uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0};
ChaCha20::Nonce96 nonce{0, 0}; ChaCha20::Nonce96 nonce{0, 0};
uint32_t counter{0}; uint32_t counter{0};
@ -293,11 +286,11 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
fuzzed_data_provider, fuzzed_data_provider,
[&] { [&] {
const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32);
chacha20.SetKey32(key.data()); chacha20.SetKey(MakeByteSpan(key));
nonce = {0, 0}; nonce = {0, 0};
counter = 0; counter = 0;
ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0); ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0);
// ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does // 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}; uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0};
ECRYPT_ivsetup(&ctx, iv); ECRYPT_ivsetup(&ctx, iv);
}, },
@ -306,7 +299,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
uint64_t iv = fuzzed_data_provider.ConsumeIntegral<uint64_t>(); uint64_t iv = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
nonce = {iv_prefix, iv}; nonce = {iv_prefix, iv};
counter = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); counter = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
chacha20.Seek64(nonce, counter); chacha20.Seek(nonce, counter);
ctx.input[12] = counter; ctx.input[12] = counter;
ctx.input[13] = iv_prefix; ctx.input[13] = iv_prefix;
ctx.input[14] = iv; ctx.input[14] = iv;
@ -315,7 +308,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
[&] { [&] {
uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096); uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
std::vector<uint8_t> output(integralInRange); std::vector<uint8_t> output(integralInRange);
chacha20.Keystream(output.data(), output.size()); chacha20.Keystream(MakeWritableByteSpan(output));
std::vector<uint8_t> djb_output(integralInRange); std::vector<uint8_t> djb_output(integralInRange);
ECRYPT_keystream_bytes(&ctx, djb_output.data(), djb_output.size()); ECRYPT_keystream_bytes(&ctx, djb_output.data(), djb_output.size());
assert(output == djb_output); assert(output == djb_output);
@ -324,7 +317,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
counter += (integralInRange + 63) >> 6; counter += (integralInRange + 63) >> 6;
if (counter < old_counter) ++nonce.first; if (counter < old_counter) ++nonce.first;
if (integralInRange & 63) { if (integralInRange & 63) {
chacha20.Seek64(nonce, counter); chacha20.Seek(nonce, counter);
} }
assert(counter == ctx.input[12]); assert(counter == ctx.input[12]);
}, },
@ -332,7 +325,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096); uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096);
std::vector<uint8_t> output(integralInRange); std::vector<uint8_t> output(integralInRange);
const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size());
chacha20.Crypt(input.data(), output.data(), input.size()); chacha20.Crypt(MakeByteSpan(input), MakeWritableByteSpan(output));
std::vector<uint8_t> djb_output(integralInRange); std::vector<uint8_t> djb_output(integralInRange);
ECRYPT_encrypt_bytes(&ctx, input.data(), djb_output.data(), input.size()); ECRYPT_encrypt_bytes(&ctx, input.data(), djb_output.data(), input.size());
assert(output == djb_output); assert(output == djb_output);
@ -341,7 +334,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20)
counter += (integralInRange + 63) >> 6; counter += (integralInRange + 63) >> 6;
if (counter < old_counter) ++nonce.first; if (counter < old_counter) ++nonce.first;
if (integralInRange & 63) { if (integralInRange & 63) {
chacha20.Seek64(nonce, counter); chacha20.Seek(nonce, counter);
} }
assert(counter == ctx.input[12]); assert(counter == ctx.input[12]);
}); });

View file

@ -14,14 +14,13 @@ FUZZ_TARGET(crypto_poly1305)
{ {
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
const std::vector<uint8_t> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, Poly1305::KEYLEN); const auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, Poly1305::KEYLEN);
const std::vector<uint8_t> in = ConsumeRandomLengthByteVector(fuzzed_data_provider); const auto in = ConsumeRandomLengthByteVector<std::byte>(fuzzed_data_provider);
std::vector<std::byte> tag_out(Poly1305::TAGLEN); std::vector<std::byte> tag_out(Poly1305::TAGLEN);
Poly1305{MakeByteSpan(key)}.Update(MakeByteSpan(in)).Finalize(tag_out); Poly1305{key}.Update(in).Finalize(tag_out);
} }
FUZZ_TARGET(crypto_poly1305_split) FUZZ_TARGET(crypto_poly1305_split)
{ {
FuzzedDataProvider provider{buffer.data(), buffer.size()}; FuzzedDataProvider provider{buffer.data(), buffer.size()};
@ -36,10 +35,10 @@ FUZZ_TARGET(crypto_poly1305_split)
// Process input in pieces. // Process input in pieces.
LIMITED_WHILE(provider.remaining_bytes(), 100) { LIMITED_WHILE(provider.remaining_bytes(), 100) {
auto in = provider.ConsumeRandomLengthString(); auto in = ConsumeRandomLengthByteVector<std::byte>(provider);
poly_split.Update(MakeByteSpan(in)); poly_split.Update(in);
// Update total_input to match what was processed. // Update total_input to match what was processed.
total_input.insert(total_input.end(), MakeByteSpan(in).begin(), MakeByteSpan(in).end()); total_input.insert(total_input.end(), in.begin(), in.end());
} }
// Process entire input at once. // Process entire input at once.

View file

@ -53,12 +53,16 @@ auto& PickValue(FuzzedDataProvider& fuzzed_data_provider, Collection& col)
return *it; return *it;
} }
[[nodiscard]] inline std::vector<uint8_t> ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional<size_t>& max_length = std::nullopt) noexcept template<typename B = uint8_t>
[[nodiscard]] inline std::vector<B> ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional<size_t>& max_length = std::nullopt) noexcept
{ {
static_assert(sizeof(B) == 1);
const std::string s = max_length ? const std::string s = max_length ?
fuzzed_data_provider.ConsumeRandomLengthString(*max_length) : fuzzed_data_provider.ConsumeRandomLengthString(*max_length) :
fuzzed_data_provider.ConsumeRandomLengthString(); fuzzed_data_provider.ConsumeRandomLengthString();
return {s.begin(), s.end()}; std::vector<B> ret(s.size());
std::copy(s.begin(), s.end(), reinterpret_cast<char*>(ret.data()));
return ret;
} }
[[nodiscard]] inline std::vector<bool> ConsumeRandomLengthBitVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional<size_t>& max_length = std::nullopt) noexcept [[nodiscard]] inline std::vector<bool> ConsumeRandomLengthBitVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional<size_t>& max_length = std::nullopt) noexcept
@ -209,14 +213,13 @@ inline void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider) noexcept
* Returns a byte vector of specified size regardless of the number of remaining bytes available * Returns a byte vector of specified size regardless of the number of remaining bytes available
* from the fuzzer. Pads with zero value bytes if needed to achieve the specified size. * from the fuzzer. Pads with zero value bytes if needed to achieve the specified size.
*/ */
[[nodiscard]] inline std::vector<uint8_t> ConsumeFixedLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t length) noexcept template<typename B = uint8_t>
[[nodiscard]] inline std::vector<B> ConsumeFixedLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t length) noexcept
{ {
std::vector<uint8_t> result(length); static_assert(sizeof(B) == 1);
const std::vector<uint8_t> random_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(length); auto random_bytes = fuzzed_data_provider.ConsumeBytes<B>(length);
if (!random_bytes.empty()) { random_bytes.resize(length);
std::memcpy(result.data(), random_bytes.data(), random_bytes.size()); return random_bytes;
}
return result;
} }
class FuzzedFileProvider class FuzzedFileProvider