diff --git a/src/bench/chacha20.cpp b/src/bench/chacha20.cpp index 13c19a250e..bd20fb971e 100644 --- a/src/bench/chacha20.cpp +++ b/src/bench/chacha20.cpp @@ -14,13 +14,13 @@ static const uint64_t BUFFER_SIZE_LARGE = 1024*1024; static void CHACHA20(benchmark::Bench& bench, size_t buffersize) { - std::vector key(32,0); - ChaCha20 ctx(key.data()); - ctx.Seek64({0, 0}, 0); - std::vector in(buffersize,0); - std::vector out(buffersize,0); + std::vector key(32, {}); + ChaCha20 ctx(key); + ctx.Seek({0, 0}, 0); + std::vector in(buffersize, {}); + std::vector out(buffersize, {}); bench.batch(in.size()).unit("byte").run([&] { - ctx.Crypt(in.data(), out.data(), in.size()); + ctx.Crypt(in, out); }); } diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index 5feaa22186..517c1d08f9 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -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) -void ChaCha20Aligned::SetKey32(const unsigned char* k) +void ChaCha20Aligned::SetKey(Span key) noexcept { - 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); + assert(key.size() == KEYLEN); + input[0] = ReadLE32(UCharCast(key.data() + 0)); + input[1] = ReadLE32(UCharCast(key.data() + 4)); + input[2] = ReadLE32(UCharCast(key.data() + 8)); + input[3] = ReadLE32(UCharCast(key.data() + 12)); + input[4] = ReadLE32(UCharCast(key.data() + 16)); + input[5] = ReadLE32(UCharCast(key.data() + 20)); + input[6] = ReadLE32(UCharCast(key.data() + 24)); + input[7] = ReadLE32(UCharCast(key.data() + 28)); input[8] = 0; input[9] = 0; input[10] = 0; input[11] = 0; } -ChaCha20Aligned::ChaCha20Aligned() -{ - memset(input, 0, sizeof(input)); -} - ChaCha20Aligned::~ChaCha20Aligned() { memory_cleanse(input, sizeof(input)); } -ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32) +ChaCha20Aligned::ChaCha20Aligned(Span 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[9] = nonce.first; @@ -61,8 +58,12 @@ void ChaCha20Aligned::Seek64(Nonce96 nonce, uint32_t block_counter) input[11] = nonce.second >> 32; } -inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) +inline void ChaCha20Aligned::Keystream(Span 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 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; } 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 in_bytes, Span 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 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; } blocks -= 1; - c += 64; - m += 64; + c += BLOCKLEN; + m += BLOCKLEN; } } -void ChaCha20::Keystream(unsigned char* c, size_t bytes) +void ChaCha20::Keystream(Span out) noexcept { - if (!bytes) return; + if (out.empty()) return; if (m_bufleft) { - unsigned reuse = std::min(m_bufleft, bytes); - memcpy(c, m_buffer + 64 - m_bufleft, reuse); + unsigned reuse = std::min(m_bufleft, out.size()); + std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin()); m_bufleft -= reuse; - bytes -= reuse; - c += reuse; + out = out.subspan(reuse); } - if (bytes >= 64) { - size_t blocks = bytes / 64; - m_aligned.Keystream64(c, blocks); - c += blocks * 64; - bytes -= blocks * 64; + if (out.size() >= m_aligned.BLOCKLEN) { + size_t blocks = out.size() / m_aligned.BLOCKLEN; + m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN)); + out = out.subspan(blocks * m_aligned.BLOCKLEN); } - if (bytes) { - m_aligned.Keystream64(m_buffer, 1); - memcpy(c, m_buffer, bytes); - m_bufleft = 64 - bytes; + if (!out.empty()) { + m_aligned.Keystream(m_buffer); + std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin()); + m_bufleft = m_aligned.BLOCKLEN - out.size(); } } -void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) +void ChaCha20::Crypt(Span input, Span output) noexcept { - if (!bytes) return; + assert(input.size() == output.size()); + + if (!input.size()) return; if (m_bufleft) { - unsigned reuse = std::min(m_bufleft, bytes); + unsigned reuse = std::min(m_bufleft, input.size()); 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; - bytes -= reuse; - c += reuse; - m += reuse; + output = output.subspan(reuse); + input = input.subspan(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 (input.size() >= m_aligned.BLOCKLEN) { + size_t blocks = input.size() / m_aligned.BLOCKLEN; + m_aligned.Crypt(input.first(blocks * m_aligned.BLOCKLEN), output.first(blocks * m_aligned.BLOCKLEN)); + output = output.subspan(blocks * m_aligned.BLOCKLEN); + input = input.subspan(blocks * m_aligned.BLOCKLEN); } - if (bytes) { - m_aligned.Keystream64(m_buffer, 1); - for (unsigned i = 0; i < bytes; i++) { - c[i] = m[i] ^ m_buffer[i]; + if (!input.empty()) { + m_aligned.Keystream(m_buffer); + for (unsigned i = 0; i < input.size(); i++) { + output[i] = input[i] ^ m_buffer[i]; } - m_bufleft = 64 - bytes; + m_bufleft = m_aligned.BLOCKLEN - input.size(); } } ChaCha20::~ChaCha20() { - memory_cleanse(m_buffer, sizeof(m_buffer)); + memory_cleanse(m_buffer.data(), m_buffer.size()); +} + +void ChaCha20::SetKey(Span key) noexcept +{ + m_aligned.SetKey(key); + m_bufleft = 0; + memory_cleanse(m_buffer.data(), m_buffer.size()); } FSChaCha20::FSChaCha20(Span 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); } @@ -341,20 +353,20 @@ void FSChaCha20::Crypt(Span input, Span output) noex assert(input.size() == output.size()); // 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. if (++m_chunk_counter == m_rekey_interval) { // Get new key from the stream cipher. std::byte new_key[KEYLEN]; - m_chacha20.Keystream(UCharCast(new_key), sizeof(new_key)); + m_chacha20.Keystream(new_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 // or on destruction). memory_cleanse(new_key, sizeof(new_key)); // 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. m_chunk_counter = 0; } diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 03a49096b8..b5ad5d2b8b 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -28,16 +28,23 @@ private: uint32_t input[12]; 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. */ - ChaCha20Aligned(const unsigned char* key32); + ChaCha20Aligned(Span key) noexcept; /** Destructor to clean up private memory. */ ~ChaCha20Aligned(); - /** set 32-byte key. */ - void SetKey32(const unsigned char* key32); + /** Set 32-byte key, and seek to nonce 0 and block position 0. */ + void SetKey(Span key) noexcept; /** 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. * - * Block_counter selects a position to seek to (to byte 64*block_counter). After 256 GiB, the - * block counter overflows, and nonce.first is incremented. + * Block_counter selects a position to seek to (to byte BLOCKLEN*block_counter). After 256 GiB, + * 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 */ - void Keystream64(unsigned char* c, size_t blocks); + /** outputs the keystream into out, whose length must be a multiple of BLOCKLEN. */ + void Keystream(Span out) noexcept; - /** enciphers the message of length <64*blocks> and write the enciphered representation into - * Used for encryption and decryption (XOR) + /** en/deciphers the message and write the result into + * + * 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 input, Span output) noexcept; }; /** Unrestricted ChaCha20 cipher. */ @@ -70,42 +78,43 @@ class ChaCha20 { private: ChaCha20Aligned m_aligned; - unsigned char m_buffer[64] = {0}; + std::array m_buffer; unsigned m_bufleft{0}; 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. */ - ChaCha20(const unsigned char* key32) : m_aligned(key32) {} + ChaCha20(Span key) noexcept : m_aligned(key) {} /** Destructor to clean up private memory. */ ~ChaCha20(); - /** set 32-byte key. */ - void SetKey32(const unsigned char* key32) - { - m_aligned.SetKey32(key32); - m_bufleft = 0; - } + /** Set 32-byte key, and seek to nonce 0 and block position 0. */ + void SetKey(Span key) noexcept; /** 96-bit nonce type. */ using Nonce96 = ChaCha20Aligned::Nonce96; - /** Set the 96-bit nonce and 32-bit block counter. */ - void Seek64(Nonce96 nonce, uint32_t block_counter) + /** Set the 96-bit nonce and 32-bit block counter. See ChaCha20Aligned::Seek. */ + void Seek(Nonce96 nonce, uint32_t block_counter) noexcept { - m_aligned.Seek64(nonce, block_counter); + m_aligned.Seek(nonce, block_counter); m_bufleft = 0; } - /** outputs the keystream of size into */ - void Keystream(unsigned char* c, size_t bytes); - - /** enciphers the message of length and write the enciphered representation into - * Used for encryption and decryption (XOR) + /** en/deciphers the message and write the result into + * + * The size of in_bytes and out_bytes must be equal. */ - void Crypt(const unsigned char* input, unsigned char* output, size_t bytes); + void Crypt(Span in_bytes, Span out_bytes) noexcept; + + /** outputs the keystream to out. */ + void Keystream(Span out) noexcept; }; /** Forward-secure ChaCha20 diff --git a/src/crypto/chacha20poly1305.cpp b/src/crypto/chacha20poly1305.cpp index 26161641bb..59671d304c 100644 --- a/src/crypto/chacha20poly1305.cpp +++ b/src/crypto/chacha20poly1305.cpp @@ -13,7 +13,7 @@ #include #include -AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span key) noexcept : m_chacha20(UCharCast(key.data())) +AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span key) noexcept : m_chacha20(key) { assert(key.size() == KEYLEN); } @@ -21,7 +21,7 @@ AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span key) noexcept : void AEADChaCha20Poly1305::SetKey(Span key) noexcept { assert(key.size() == KEYLEN); - m_chacha20.SetKey32(UCharCast(key.data())); + m_chacha20.SetKey(key); } namespace { @@ -46,8 +46,8 @@ void ComputeTag(ChaCha20& chacha20, Span aad, Span plain1, Span cipher, Span keystream) noexcept { // Skip the first output block, as it's used for generating the poly1305 key. - m_chacha20.Seek64(nonce, 1); - m_chacha20.Keystream(UCharCast(keystream.data()), keystream.size()); + m_chacha20.Seek(nonce, 1); + m_chacha20.Keystream(keystream); } void FSChaCha20Poly1305::NextPacket() noexcept @@ -113,7 +113,7 @@ void FSChaCha20Poly1305::NextPacket() noexcept if (++m_packet_counter == m_rekey_interval) { // Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though // 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); // Switch keys. m_aead.SetKey(Span{one_block}.first(KEYLEN)); diff --git a/src/crypto/muhash.cpp b/src/crypto/muhash.cpp index aca1dcc8fd..c249ebc2d7 100644 --- a/src/crypto/muhash.cpp +++ b/src/crypto/muhash.cpp @@ -299,7 +299,8 @@ Num3072 MuHash3072::ToNum3072(Span in) { unsigned char tmp[Num3072::BYTE_SIZE]; uint256 hashed_in = (CHashWriter(SER_DISK, 0) << 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}; return out; diff --git a/src/random.cpp b/src/random.cpp index 8b6ea488bf..0c6129ea25 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include // for Mutex #include // for GetTimeMicros() +#include #include #include @@ -618,7 +620,7 @@ bool GetRandBool(double rate) void FastRandomContext::RandomSeed() { uint256 seed = GetRandHash(); - rng.SetKey32(seed.begin()); + rng.SetKey(MakeByteSpan(seed)); requires_seed = false; } @@ -626,18 +628,15 @@ uint256 FastRandomContext::rand256() noexcept { if (requires_seed) RandomSeed(); uint256 ret; - rng.Keystream(ret.data(), ret.size()); + rng.Keystream(MakeWritableByteSpan(ret)); return ret; } template std::vector FastRandomContext::randbytes(size_t len) { - if (requires_seed) RandomSeed(); std::vector ret(len); - if (len > 0) { - rng.Keystream(UCharCast(ret.data()), len); - } + fillrand(MakeWritableByteSpan(ret)); return ret; } template std::vector FastRandomContext::randbytes(size_t); @@ -646,13 +645,10 @@ template std::vector FastRandomContext::randbytes(size_t); void FastRandomContext::fillrand(Span output) { 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) -{ - rng.SetKey32(seed.begin()); -} +FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), rng(MakeByteSpan(seed)), bitbuf_size(0) {} bool Random_SanityCheck() { @@ -700,13 +696,13 @@ bool Random_SanityCheck() return true; } -FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bitbuf_size(0) +static constexpr std::array ZERO_KEY{}; + +FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), rng(ZERO_KEY), bitbuf_size(0) { - if (!fDeterministic) { - return; - } - uint256 seed; - rng.SetKey32(seed.begin()); + // Note that despite always initializing with ZERO_KEY, requires_seed is set to true if not + // fDeterministic. That means the rng will be reinitialized with a secure random key upon first + // use. } FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept diff --git a/src/random.h b/src/random.h index 6b096b4776..c9295df63e 100644 --- a/src/random.h +++ b/src/random.h @@ -156,9 +156,9 @@ public: uint64_t rand64() noexcept { if (requires_seed) RandomSeed(); - unsigned char buf[8]; - rng.Keystream(buf, 8); - return ReadLE64(buf); + std::array buf; + rng.Keystream(buf); + return ReadLE64(UCharCast(buf.data())); } /** Generate a random (bits)-bit integer. */ diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index eb4fddacea..bddab92fbc 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -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) { - std::vector key = ParseHex(hexkey); + auto key = ParseHex(hexkey); assert(key.size() == 32); - std::vector m = ParseHex(hex_message); - ChaCha20 rng(key.data()); - rng.Seek64(nonce, seek); - std::vector outres; + auto m = ParseHex(hex_message); + ChaCha20 rng{key}; + rng.Seek(nonce, seek); + std::vector outres; 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()) { - rng.Crypt(m.data(), outres.data(), outres.size()); + rng.Crypt(m, outres); } else { - rng.Keystream(outres.data(), outres.size()); + rng.Keystream(outres); } BOOST_CHECK_EQUAL(hexout, HexStr(outres)); if (!hex_message.empty()) { // Manually XOR with the keystream and compare the output - rng.Seek64(nonce, seek); - std::vector only_keystream(outres.size()); - rng.Keystream(only_keystream.data(), only_keystream.size()); + rng.Seek(nonce, seek); + std::vector only_keystream(outres.size()); + rng.Keystream(only_keystream); for (size_t i = 0; i != m.size(); 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[2] = hexout.size() / 2U - lens[0] - lens[1]; - rng.Seek64(nonce, seek); - outres.assign(hexout.size() / 2U, 0); + rng.Seek(nonce, seek); + outres.assign(hexout.size() / 2U, {}); 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]); + rng.Crypt(Span{m}.subspan(pos, lens[j]), Span{outres}.subspan(pos, lens[j])); } else { - rng.Keystream(outres.data() + pos, lens[j]); + rng.Keystream(Span{outres}.subspan(pos, lens[j])); } pos += lens[j]; } @@ -190,7 +190,7 @@ static void TestFSChaCha20(const std::string& hex_plaintext, const std::string& auto plaintext = ParseHex(hex_plaintext); auto fsc20 = FSChaCha20{key, rekey_interval}; - auto c20 = ChaCha20{UCharCast(key.data())}; + auto c20 = ChaCha20{key}; std::vector fsc20_output; 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++) { 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); } // At the rotation interval, the outputs will no longer match fsc20.Crypt(plaintext, fsc20_output); 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); std::byte new_key[FSChaCha20::KEYLEN]; - c20_copy.Keystream(UCharCast(new_key), sizeof(new_key)); - c20.SetKey32(UCharCast(new_key)); - c20.Seek64({0, 1}, 0); + c20_copy.Keystream(new_key); + c20.SetKey(new_key); + c20.Seek({0, 1}, 0); // 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_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(hexkey); auto m = ParseHex(hexmessage); - auto tag = ParseHex(hextag); std::vector tagres(Poly1305::TAGLEN); Poly1305{key}.Update(m).Finalize(tagres); - BOOST_CHECK(tag == tagres); + BOOST_CHECK_EQUAL(HexStr(tagres), hextag); // Test incremental interface 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{}); poly1305.Update(data).Finalize(tagres); - BOOST_CHECK(tag == tagres); + BOOST_CHECK_EQUAL(HexStr(tagres), hextag); } } } @@ -846,20 +845,20 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector) BOOST_AUTO_TEST_CASE(chacha20_midblock) { - auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000"); - ChaCha20 c20{key.data()}; + auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000"); + ChaCha20 c20{key}; // get one block of keystream - unsigned char block[64]; - c20.Keystream(block, sizeof(block)); - unsigned char b1[5], b2[7], b3[52]; - c20 = ChaCha20{key.data()}; - c20.Keystream(b1, 5); - c20.Keystream(b2, 7); - c20.Keystream(b3, 52); + std::byte block[64]; + c20.Keystream(block); + std::byte b1[5], b2[7], b3[52]; + c20 = ChaCha20{key}; + c20.Keystream(b1); + c20.Keystream(b2); + c20.Keystream(b3); - 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_CHECK(Span{block}.first(5) == Span{b1}); + BOOST_CHECK(Span{block}.subspan(5, 7) == Span{b2}); + BOOST_CHECK(Span{block}.last(52) == Span{b3}); } BOOST_AUTO_TEST_CASE(poly1305_testvector) @@ -945,15 +944,15 @@ BOOST_AUTO_TEST_CASE(poly1305_testvector) auto total_key = ParseHex("01020304050607fffefdfcfbfaf9ffffffffffffffffffffffffffff00000000"); Poly1305 total_ctx(total_key); for (unsigned i = 0; i < 256; ++i) { - std::vector key(32, std::byte{(uint8_t)i}); - std::vector msg(i, std::byte{(uint8_t)i}); + std::vector key(32, std::byte{uint8_t(i)}); + std::vector msg(i, std::byte{uint8_t(i)}); std::array tag; Poly1305{key}.Update(msg).Finalize(tag); total_ctx.Update(tag); } std::vector total_tag(Poly1305::TAGLEN); total_ctx.Finalize(total_tag); - BOOST_CHECK(total_tag == ParseHex("64afe2e8d6ad7bbdd287f97c44623d39")); + BOOST_CHECK_EQUAL(HexStr(total_tag), "64afe2e8d6ad7bbdd287f97c44623d39"); } // Tests with sparse messages and random keys. diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index b640d75697..9f96664d1f 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -17,20 +17,18 @@ FUZZ_TARGET(crypto_chacha20) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - ChaCha20 chacha20; - if (fuzzed_data_provider.ConsumeBool()) { - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); - chacha20 = ChaCha20{key.data()}; - } + const auto key = ConsumeFixedLengthByteVector(fuzzed_data_provider, ChaCha20::KEYLEN); + ChaCha20 chacha20{key}; + while (fuzzed_data_provider.ConsumeBool()) { CallOneOf( fuzzed_data_provider, [&] { - std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); - chacha20.SetKey32(key.data()); + auto key = ConsumeFixedLengthByteVector(fuzzed_data_provider, ChaCha20::KEYLEN); + chacha20.SetKey(key); }, [&] { - chacha20.Seek64( + chacha20.Seek( { fuzzed_data_provider.ConsumeIntegral(), fuzzed_data_provider.ConsumeIntegral() @@ -38,12 +36,12 @@ FUZZ_TARGET(crypto_chacha20) }, [&] { std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); - chacha20.Keystream(output.data(), output.size()); + chacha20.Keystream(MakeWritableByteSpan(output)); }, [&] { - std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); - const std::vector input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); - chacha20.Crypt(input.data(), output.data(), input.size()); + std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); + const auto input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); + chacha20.Crypt(input, output); }); } } @@ -62,9 +60,7 @@ template void ChaCha20SplitFuzz(FuzzedDataProvider& provider) { // Determine key, iv, start position, length. - unsigned char key[32] = {0}; - auto key_bytes = provider.ConsumeBytes(32); - std::copy(key_bytes.begin(), key_bytes.end(), key); + auto key_bytes = ConsumeFixedLengthByteVector(provider, ChaCha20::KEYLEN); uint64_t iv = provider.ConsumeIntegral(); uint32_t iv_prefix = provider.ConsumeIntegral(); uint64_t total_bytes = provider.ConsumeIntegralInRange(0, 1000000); @@ -72,13 +68,13 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider) uint32_t seek = provider.ConsumeIntegralInRange(0, ~(uint32_t)(total_bytes >> 6)); // Initialize two ChaCha20 ciphers, with the same key/iv/position. - ChaCha20 crypt1(key); - ChaCha20 crypt2(key); - crypt1.Seek64({iv_prefix, iv}, seek); - crypt2.Seek64({iv_prefix, iv}, seek); + ChaCha20 crypt1(key_bytes); + ChaCha20 crypt2(key_bytes); + crypt1.Seek({iv_prefix, iv}, seek); + crypt2.Seek({iv_prefix, iv}, seek); // Construct vectors with data. - std::vector data1, data2; + std::vector data1, data2; data1.resize(total_bytes); data2.resize(total_bytes); @@ -90,14 +86,14 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider) 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); + WriteLE64(UCharCast(data1.data() + bytes), val); + WriteLE64(UCharCast(data2.data() + bytes), val); bytes += 8; } if (bytes < total_bytes) { - unsigned char valbytes[8]; + std::byte valbytes[8]; 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), data2.data() + bytes); } @@ -108,9 +104,9 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider) // Encrypt data1, the whole array at once. if constexpr (UseCrypt) { - crypt1.Crypt(data1.data(), data1.data(), total_bytes); + crypt1.Crypt(data1, data1); } else { - crypt1.Keystream(data1.data(), total_bytes); + crypt1.Keystream(data1); } // 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 // to 0x00 input bytes. 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 { - crypt2.Keystream(data2.data() + bytes2, now); + crypt2.Keystream(Span{data2}.subspan(bytes2, now)); } bytes2 += now; if (is_last) break; diff --git a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp index 779d8c898d..c815e47e08 100644 --- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp +++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp @@ -267,22 +267,15 @@ 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; - if (fuzzed_data_provider.ConsumeBool()) { - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); - chacha20 = ChaCha20{key.data()}; - 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); - } + const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); + ChaCha20 chacha20{MakeByteSpan(key)}; + 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 static const uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0}; ChaCha20::Nonce96 nonce{0, 0}; uint32_t counter{0}; @@ -293,11 +286,11 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) fuzzed_data_provider, [&] { const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); - chacha20.SetKey32(key.data()); + chacha20.SetKey(MakeByteSpan(key)); nonce = {0, 0}; counter = 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}; ECRYPT_ivsetup(&ctx, iv); }, @@ -306,7 +299,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) uint64_t iv = fuzzed_data_provider.ConsumeIntegral(); nonce = {iv_prefix, iv}; counter = fuzzed_data_provider.ConsumeIntegral(); - chacha20.Seek64(nonce, counter); + chacha20.Seek(nonce, counter); ctx.input[12] = counter; ctx.input[13] = iv_prefix; ctx.input[14] = iv; @@ -315,7 +308,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) [&] { uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); std::vector output(integralInRange); - chacha20.Keystream(output.data(), output.size()); + chacha20.Keystream(MakeWritableByteSpan(output)); std::vector djb_output(integralInRange); ECRYPT_keystream_bytes(&ctx, djb_output.data(), djb_output.size()); assert(output == djb_output); @@ -324,7 +317,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) counter += (integralInRange + 63) >> 6; if (counter < old_counter) ++nonce.first; if (integralInRange & 63) { - chacha20.Seek64(nonce, counter); + chacha20.Seek(nonce, counter); } assert(counter == ctx.input[12]); }, @@ -332,7 +325,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); std::vector output(integralInRange); const std::vector input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); - chacha20.Crypt(input.data(), output.data(), input.size()); + chacha20.Crypt(MakeByteSpan(input), MakeWritableByteSpan(output)); std::vector djb_output(integralInRange); ECRYPT_encrypt_bytes(&ctx, input.data(), djb_output.data(), input.size()); assert(output == djb_output); @@ -341,7 +334,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) counter += (integralInRange + 63) >> 6; if (counter < old_counter) ++nonce.first; if (integralInRange & 63) { - chacha20.Seek64(nonce, counter); + chacha20.Seek(nonce, counter); } assert(counter == ctx.input[12]); }); diff --git a/src/test/fuzz/crypto_poly1305.cpp b/src/test/fuzz/crypto_poly1305.cpp index f49729a34b..6ce6648f56 100644 --- a/src/test/fuzz/crypto_poly1305.cpp +++ b/src/test/fuzz/crypto_poly1305.cpp @@ -14,14 +14,13 @@ FUZZ_TARGET(crypto_poly1305) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, Poly1305::KEYLEN); - const std::vector in = ConsumeRandomLengthByteVector(fuzzed_data_provider); + const auto key = ConsumeFixedLengthByteVector(fuzzed_data_provider, Poly1305::KEYLEN); + const auto in = ConsumeRandomLengthByteVector(fuzzed_data_provider); std::vector 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) { FuzzedDataProvider provider{buffer.data(), buffer.size()}; @@ -36,10 +35,10 @@ FUZZ_TARGET(crypto_poly1305_split) // Process input in pieces. LIMITED_WHILE(provider.remaining_bytes(), 100) { - auto in = provider.ConsumeRandomLengthString(); - poly_split.Update(MakeByteSpan(in)); + auto in = ConsumeRandomLengthByteVector(provider); + poly_split.Update(in); // 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. diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 0ff2855a28..8bc55ffffa 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -58,12 +58,16 @@ auto& PickValue(FuzzedDataProvider& fuzzed_data_provider, Collection& col) return *it; } -[[ nodiscard ]] inline std::vector ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional& max_length = std::nullopt) noexcept +template +[[ nodiscard ]] inline std::vector ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional& max_length = std::nullopt) noexcept { + static_assert(sizeof(B) == 1); const std::string s = max_length ? fuzzed_data_provider.ConsumeRandomLengthString(*max_length) : fuzzed_data_provider.ConsumeRandomLengthString(); - return {s.begin(), s.end()}; + std::vector ret(s.size()); + std::copy(s.begin(), s.end(), reinterpret_cast(ret.data())); + return ret; } [[ nodiscard ]] inline std::vector ConsumeRandomLengthBitVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional& max_length = std::nullopt) noexcept @@ -255,14 +259,13 @@ inline void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider) noexcept * 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. */ -[[ nodiscard ]] inline std::vector ConsumeFixedLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t length) noexcept +template +[[ nodiscard ]] inline std::vector ConsumeFixedLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t length) noexcept { - std::vector result(length); - const std::vector random_bytes = fuzzed_data_provider.ConsumeBytes(length); - if (!random_bytes.empty()) { - std::memcpy(result.data(), random_bytes.data(), random_bytes.size()); - } - return result; + static_assert(sizeof(B) == 1); + auto random_bytes = fuzzed_data_provider.ConsumeBytes(length); + random_bytes.resize(length); + return random_bytes; } inline CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept