diff --git a/src/Makefile.am b/src/Makefile.am index 7b9ffe427d..3604a0b7d5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -524,8 +524,8 @@ crypto_libbitcoin_crypto_base_la_LDFLAGS = $(AM_LDFLAGS) -static crypto_libbitcoin_crypto_base_la_SOURCES = \ crypto/aes.cpp \ crypto/aes.h \ - crypto/chacha_poly_aead.h \ - crypto/chacha_poly_aead.cpp \ + crypto/bip324_suite.h \ + crypto/bip324_suite.cpp \ crypto/chacha20.h \ crypto/chacha20.cpp \ crypto/common.h \ @@ -539,6 +539,8 @@ crypto_libbitcoin_crypto_base_la_SOURCES = \ crypto/poly1305.cpp \ crypto/muhash.h \ crypto/muhash.cpp \ + crypto/rfc8439.h \ + crypto/rfc8439.cpp \ crypto/ripemd160.cpp \ crypto/ripemd160.h \ crypto/sha1.cpp \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index f1e4e706a1..c2ae440536 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -18,10 +18,10 @@ bench_bench_bitcoin_SOURCES = \ bench/bench.cpp \ bench/bench.h \ bench/bench_bitcoin.cpp \ + bench/bip324_suite.cpp \ bench/block_assemble.cpp \ bench/ccoins_caching.cpp \ bench/chacha20.cpp \ - bench/chacha_poly_aead.cpp \ bench/checkblock.cpp \ bench/checkqueue.cpp \ bench/crypto_hash.cpp \ @@ -43,6 +43,7 @@ bench_bench_bitcoin_SOURCES = \ bench/peer_eviction.cpp \ bench/poly1305.cpp \ bench/prevector.cpp \ + bench/rfc8439.cpp \ bench/rollingbloom.cpp \ bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index fa77e28736..506664bd88 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -254,12 +254,13 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/crypto.cpp \ test/fuzz/crypto_aes256.cpp \ test/fuzz/crypto_aes256cbc.cpp \ + test/fuzz/crypto_bip324_suite.cpp \ test/fuzz/crypto_chacha20.cpp \ - test/fuzz/crypto_chacha20_poly1305_aead.cpp \ test/fuzz/crypto_common.cpp \ test/fuzz/crypto_diff_fuzz_chacha20.cpp \ test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp \ test/fuzz/crypto_poly1305.cpp \ + test/fuzz/crypto_rfc8439.cpp \ test/fuzz/cuckoocache.cpp \ test/fuzz/decode_tx.cpp \ test/fuzz/descriptor_parse.cpp \ diff --git a/src/bench/bip324_suite.cpp b/src/bench/bip324_suite.cpp new file mode 100644 index 0000000000..6cadaa8871 --- /dev/null +++ b/src/bench/bip324_suite.cpp @@ -0,0 +1,116 @@ +// Copyright (c) 2019-2020 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 +#include +#include +#include +#include + +#include +#include +#include + +/* Number of bytes to process per iteration */ +static constexpr uint64_t BUFFER_SIZE_TINY = 64; +static constexpr uint64_t BUFFER_SIZE_SMALL = 256; +static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024; + +static const std::vector zero_vec(BIP324_KEY_LEN, std::byte{0x00}); + +static void BIP324_CIPHER_SUITE(benchmark::Bench& bench, size_t contents_len, bool include_decryption) +{ + BIP324Key zero_arr; + std::memcpy(zero_arr.data(), zero_vec.data(), BIP324_KEY_LEN); + BIP324CipherSuite enc{zero_arr, zero_arr}; + BIP324CipherSuite dec{zero_arr, zero_arr}; + + auto packet_len = BIP324_LENGTH_FIELD_LEN + BIP324_HEADER_LEN + contents_len + RFC8439_EXPANSION; + + std::vector in(contents_len, std::byte{0x00}); + std::vector out(packet_len, std::byte{0x00}); + + BIP324HeaderFlags flags{BIP324_NONE}; + + bench.batch(contents_len).unit("byte").run([&] { + // encrypt or decrypt the buffer with a static key + const bool crypt_ok_1 = enc.Crypt({}, in, out, flags, true); + assert(crypt_ok_1); + + if (include_decryption) { + // if we decrypt, we need to decrypt the length first + std::array encrypted_pkt_len; + std::memcpy(encrypted_pkt_len.data(), out.data(), BIP324_LENGTH_FIELD_LEN); + (void)dec.DecryptLength(encrypted_pkt_len); + const bool crypt_ok_2 = dec.Crypt({}, {out.data() + BIP324_LENGTH_FIELD_LEN, out.size() - BIP324_LENGTH_FIELD_LEN}, in, flags, false); + assert(crypt_ok_2); + } + }); +} + +static void BIP324_CIPHER_SUITE_64BYTES_ONLY_ENCRYPT(benchmark::Bench& bench) +{ + BIP324_CIPHER_SUITE(bench, BUFFER_SIZE_TINY, false); +} + +static void BIP324_CIPHER_SUITE_256BYTES_ONLY_ENCRYPT(benchmark::Bench& bench) +{ + BIP324_CIPHER_SUITE(bench, BUFFER_SIZE_SMALL, false); +} + +static void BIP324_CIPHER_SUITE_1MB_ONLY_ENCRYPT(benchmark::Bench& bench) +{ + BIP324_CIPHER_SUITE(bench, BUFFER_SIZE_LARGE, false); +} + +static void BIP324_CIPHER_SUITE_64BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench) +{ + BIP324_CIPHER_SUITE(bench, BUFFER_SIZE_TINY, true); +} + +static void BIP324_CIPHER_SUITE_256BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench) +{ + BIP324_CIPHER_SUITE(bench, BUFFER_SIZE_SMALL, true); +} + +static void BIP324_CIPHER_SUITE_1MB_ENCRYPT_DECRYPT(benchmark::Bench& bench) +{ + BIP324_CIPHER_SUITE(bench, BUFFER_SIZE_LARGE, true); +} + +// Add Hash() (dbl-sha256) bench for comparison + +static void HASH(benchmark::Bench& bench, size_t buffersize) +{ + uint8_t hash[CHash256::OUTPUT_SIZE]; + std::vector in(buffersize, 0); + bench.batch(in.size()).unit("byte").run([&] { + CHash256().Write(in).Finalize(hash); + }); +} + +static void HASH_64BYTES(benchmark::Bench& bench) +{ + HASH(bench, BUFFER_SIZE_TINY); +} + +static void HASH_256BYTES(benchmark::Bench& bench) +{ + HASH(bench, BUFFER_SIZE_SMALL); +} + +static void HASH_1MB(benchmark::Bench& bench) +{ + HASH(bench, BUFFER_SIZE_LARGE); +} + +BENCHMARK(BIP324_CIPHER_SUITE_64BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(BIP324_CIPHER_SUITE_256BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(BIP324_CIPHER_SUITE_1MB_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(BIP324_CIPHER_SUITE_64BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(BIP324_CIPHER_SUITE_256BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(BIP324_CIPHER_SUITE_1MB_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(HASH_64BYTES, benchmark::PriorityLevel::HIGH); +BENCHMARK(HASH_256BYTES, benchmark::PriorityLevel::HIGH); +BENCHMARK(HASH_1MB, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/chacha_poly_aead.cpp b/src/bench/chacha_poly_aead.cpp deleted file mode 100644 index db88841c32..0000000000 --- a/src/bench/chacha_poly_aead.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2019-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 -#include -#include // for the POLY1305_TAGLEN constant -#include - -#include -#include - -/* Number of bytes to process per iteration */ -static constexpr uint64_t BUFFER_SIZE_TINY = 64; -static constexpr uint64_t BUFFER_SIZE_SMALL = 256; -static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024; - -static const unsigned char k1[32] = {0}; -static const unsigned char k2[32] = {0}; - -static ChaCha20Poly1305AEAD aead(k1, 32, k2, 32); - -static void CHACHA20_POLY1305_AEAD(benchmark::Bench& bench, size_t buffersize, bool include_decryption) -{ - std::vector in(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - std::vector out(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - uint64_t seqnr_payload = 0; - uint64_t seqnr_aad = 0; - int aad_pos = 0; - uint32_t len = 0; - bench.batch(buffersize).unit("byte").run([&] { - // encrypt or decrypt the buffer with a static key - const bool crypt_ok_1 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true); - assert(crypt_ok_1); - - if (include_decryption) { - // if we decrypt, include the GetLength - const bool get_length_ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data()); - assert(get_length_ok); - const bool crypt_ok_2 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true); - assert(crypt_ok_2); - } - - // increase main sequence number - seqnr_payload++; - // increase aad position (position in AAD keystream) - aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; - if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { - aad_pos = 0; - seqnr_aad++; - } - if (seqnr_payload + 1 == std::numeric_limits::max()) { - // reuse of nonce+key is okay while benchmarking. - seqnr_payload = 0; - seqnr_aad = 0; - aad_pos = 0; - } - }); -} - -static void CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_TINY, false); -} - -static void CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_SMALL, false); -} - -static void CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_LARGE, false); -} - -static void CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_TINY, true); -} - -static void CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_SMALL, true); -} - -static void CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_LARGE, true); -} - -// Add Hash() (dbl-sha256) bench for comparison - -static void HASH(benchmark::Bench& bench, size_t buffersize) -{ - uint8_t hash[CHash256::OUTPUT_SIZE]; - std::vector in(buffersize,0); - bench.batch(in.size()).unit("byte").run([&] { - CHash256().Write(in).Finalize(hash); - }); -} - -static void HASH_64BYTES(benchmark::Bench& bench) -{ - HASH(bench, BUFFER_SIZE_TINY); -} - -static void HASH_256BYTES(benchmark::Bench& bench) -{ - HASH(bench, BUFFER_SIZE_SMALL); -} - -static void HASH_1MB(benchmark::Bench& bench) -{ - HASH(bench, BUFFER_SIZE_LARGE); -} - -BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); -BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); -BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); -BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); -BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); -BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); -BENCHMARK(HASH_64BYTES, benchmark::PriorityLevel::HIGH); -BENCHMARK(HASH_256BYTES, benchmark::PriorityLevel::HIGH); -BENCHMARK(HASH_1MB, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/rfc8439.cpp b/src/bench/rfc8439.cpp new file mode 100644 index 0000000000..883dffb61e --- /dev/null +++ b/src/bench/rfc8439.cpp @@ -0,0 +1,78 @@ +// 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 +#include + +#include +#include +#include +#include + +static constexpr uint64_t AAD_SIZE{32}; +static constexpr uint64_t PLAINTEXT_SIZE_TINY{64}; +static constexpr uint64_t PLAINTEXT_SIZE_SMALL{256}; +static constexpr uint64_t PLAINTEXT_SIZE_LARGE{1024 * 1024}; + +static const std::vector AAD(AAD_SIZE, std::byte{0x00}); +static const std::array NONCE = { + std::byte{0x00}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, + std::byte{0x04}, std::byte{0x05}, std::byte{0x06}, std::byte{0x07}, + std::byte{0x08}, std::byte{0x09}, std::byte{0x0a}, std::byte{0x0b}}; + +static void RFC8439_AEAD(benchmark::Bench& bench, size_t plaintext_size, bool include_decryption) +{ + RFC8439Key zero_key; + std::memset(zero_key.data(), 0, zero_key.size()); + std::vector plaintext_in(plaintext_size, std::byte{0x00}); + std::vector output(plaintext_size + POLY1305_TAGLEN, std::byte{0x00}); + + bench.batch(plaintext_size).unit("byte").run([&] { + RFC8439Encrypt(AAD, zero_key, NONCE, plaintext_in, output); + + if (include_decryption) { + std::vector decrypted_plaintext(plaintext_size); + auto authenticated = RFC8439Decrypt(AAD, zero_key, NONCE, output, decrypted_plaintext); + assert(authenticated); + assert(std::memcmp(decrypted_plaintext.data(), plaintext_in.data(), plaintext_in.size()) == 0); + } + }); +} + +static void RFC8439_AEAD_64BYTES_ONLY_ENCRYPT(benchmark::Bench& bench) +{ + RFC8439_AEAD(bench, PLAINTEXT_SIZE_TINY, false); +} + +static void RFC8439_AEAD_256BYTES_ONLY_ENCRYPT(benchmark::Bench& bench) +{ + RFC8439_AEAD(bench, PLAINTEXT_SIZE_SMALL, false); +} + +static void RFC8439_AEAD_1MB_ONLY_ENCRYPT(benchmark::Bench& bench) +{ + RFC8439_AEAD(bench, PLAINTEXT_SIZE_LARGE, false); +} + +static void RFC8439_AEAD_64BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench) +{ + RFC8439_AEAD(bench, PLAINTEXT_SIZE_TINY, true); +} + +static void RFC8439_AEAD_256BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench) +{ + RFC8439_AEAD(bench, PLAINTEXT_SIZE_SMALL, true); +} + +static void RFC8439_AEAD_1MB_ENCRYPT_DECRYPT(benchmark::Bench& bench) +{ + RFC8439_AEAD(bench, PLAINTEXT_SIZE_LARGE, true); +} + +BENCHMARK(RFC8439_AEAD_64BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(RFC8439_AEAD_256BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(RFC8439_AEAD_1MB_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(RFC8439_AEAD_64BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(RFC8439_AEAD_256BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); +BENCHMARK(RFC8439_AEAD_1MB_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH); diff --git a/src/crypto/bip324_suite.cpp b/src/crypto/bip324_suite.cpp new file mode 100644 index 0000000000..5365f05916 --- /dev/null +++ b/src/crypto/bip324_suite.cpp @@ -0,0 +1,98 @@ +// Copyright (c) 2019-2021 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 + +#include +#include +#include +#include + +#include +#include +#include + +BIP324CipherSuite::~BIP324CipherSuite() +{ + memory_cleanse(key_P.data(), key_P.size()); +} + +void BIP324CipherSuite::Rekey() +{ + ChaCha20 rekey_c20(UCharCast(key_P.data())); + std::array rekey_nonce; + std::memset(rekey_nonce.data(), 0xFF, 4); + std::memcpy(rekey_nonce.data() + 4, nonce.data() + 4, NONCE_LENGTH - 4); + rekey_c20.SetRFC8439Nonce(rekey_nonce); + rekey_c20.SeekRFC8439(1); + rekey_c20.Keystream(reinterpret_cast(key_P.data()), BIP324_KEY_LEN); +} + +bool BIP324CipherSuite::Crypt(const Span aad, + const Span input, + Span output, + BIP324HeaderFlags& flags, bool encrypt) +{ + // check buffer boundaries + if ( + // if we encrypt, make sure the destination has the space for the encrypted length field, header, contents and MAC + (encrypt && (output.size() < BIP324_LENGTH_FIELD_LEN + BIP324_HEADER_LEN + input.size() + RFC8439_EXPANSION)) || + // if we decrypt, make sure the source contains at least the encrypted header + mac and the destination has the space for the input - MAC - header + (!encrypt && (input.size() < BIP324_HEADER_LEN + RFC8439_EXPANSION || output.size() < input.size() - BIP324_HEADER_LEN - RFC8439_EXPANSION))) { + return false; + } + + if (encrypt) { + // input is just the contents + // output will be encrypted contents length + encrypted (header and contents) + mac tag + uint32_t contents_len = input.size(); + WriteLE32(reinterpret_cast(&contents_len), contents_len); + + std::vector header_and_contents(BIP324_HEADER_LEN + input.size()); + + std::memcpy(header_and_contents.data(), &flags, BIP324_HEADER_LEN); + if (!input.empty()) { + std::memcpy(header_and_contents.data() + BIP324_HEADER_LEN, input.data(), input.size()); + } + + auto write_pos = output.data(); + fsc20.Crypt({reinterpret_cast(&contents_len), BIP324_LENGTH_FIELD_LEN}, + {write_pos, BIP324_LENGTH_FIELD_LEN}); + write_pos += BIP324_LENGTH_FIELD_LEN; + RFC8439Encrypt(aad, key_P, nonce, header_and_contents, {write_pos, BIP324_HEADER_LEN + input.size() + RFC8439_EXPANSION}); + } else { + // we must use BIP324CipherSuite::DecryptLength before calling BIP324CipherSuite::Crypt + // input is encrypted (header + contents) and the MAC tag i.e. the RFC8439 ciphertext blob + // decrypted header will be put in flags and output will be plaintext contents. + std::vector decrypted_header_and_contents(input.size() - RFC8439_EXPANSION); + auto authenticated = RFC8439Decrypt(aad, key_P, nonce, input, decrypted_header_and_contents); + if (!authenticated) { + return false; + } + + std::memcpy(&flags, decrypted_header_and_contents.data(), BIP324_HEADER_LEN); + if (!output.empty()) { + std::memcpy(output.data(), + decrypted_header_and_contents.data() + BIP324_HEADER_LEN, + input.size() - BIP324_HEADER_LEN - RFC8439_EXPANSION); + } + } + + packet_counter++; + if (packet_counter % REKEY_INTERVAL == 0) { + Rekey(); + } + set_nonce(); + return true; +} + +uint32_t BIP324CipherSuite::DecryptLength(const std::array& encrypted_length) +{ + std::array length_buffer; + fsc20.Crypt(encrypted_length, MakeWritableByteSpan(length_buffer)); + + return (uint32_t{length_buffer[0]}) | + (uint32_t{length_buffer[1]} << 8) | + (uint32_t{length_buffer[2]} << 16); +} diff --git a/src/crypto/bip324_suite.h b/src/crypto/bip324_suite.h new file mode 100644 index 0000000000..82e9179a72 --- /dev/null +++ b/src/crypto/bip324_suite.h @@ -0,0 +1,72 @@ +// Copyright (c) 2019-2021 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_CRYPTO_BIP324_SUITE_H +#define BITCOIN_CRYPTO_BIP324_SUITE_H + +#include +#include +#include + +#include +#include + +static constexpr size_t BIP324_KEY_LEN = 32; // bytes +static constexpr size_t BIP324_HEADER_LEN = 1; // bytes +static constexpr size_t BIP324_LENGTH_FIELD_LEN = 3; // bytes +static constexpr size_t REKEY_INTERVAL = 224; // packets +static constexpr size_t NONCE_LENGTH = 12; // bytes + +enum BIP324HeaderFlags : uint8_t { + BIP324_NONE = 0, + BIP324_IGNORE = (1 << 7), +}; + +using BIP324Key = std::array; + +class BIP324CipherSuite +{ +private: + FSChaCha20 fsc20; + uint32_t packet_counter{0}; + BIP324Key key_P; + std::array nonce; + + void set_nonce() + { + WriteLE32(reinterpret_cast(nonce.data()), packet_counter % REKEY_INTERVAL); + WriteLE64(reinterpret_cast(nonce.data()) + 4, packet_counter / REKEY_INTERVAL); + } + + void Rekey(); + +public: + BIP324CipherSuite(const BIP324Key& K_L, const BIP324Key& K_P) + : fsc20{K_L, REKEY_INTERVAL}, + key_P{K_P} + { + set_nonce(); + }; + + explicit BIP324CipherSuite(const BIP324CipherSuite&) = delete; + ~BIP324CipherSuite(); + + /** Encrypts/decrypts a packet + input, contents to encrypt or the encrypted header + encrypted contents + MAC to decrypt (encrypted length is decrypted using DecryptLength() prior to calling Crypt() + encrypt, set to true if we encrypt, false to decrypt. + + Returns true upon success. Upon failure, the output should not be used. + */ + [[nodiscard]] bool Crypt(const Span aad, + const Span input, + Span output, + BIP324HeaderFlags& flags, bool encrypt); + + /** Decrypts the 3 byte encrypted length field (the packet header and contents length) and decodes it into a uint32_t field + The FSChaCha20 keystream will advance. As a result, DecryptLength() cannot be called multiple times to get the same result. The caller must cache the result for re-use. + */ + [[nodiscard]] uint32_t DecryptLength(const std::array& encrypted_length); +}; + +#endif // BITCOIN_CRYPTO_BIP324_SUITE_H diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index 6934cef163..13d725b959 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -5,9 +5,10 @@ // Based on the public domain implementation 'merged' by D. J. Bernstein // See https://cr.yp.to/chacha.html. -#include #include +#include + #include #include @@ -59,6 +60,19 @@ void ChaCha20Aligned::Seek64(uint64_t pos) input[9] = pos >> 32; } +void ChaCha20Aligned::SeekRFC8439(uint32_t pos) +{ + input[8] = pos; +} + +void ChaCha20Aligned::SetRFC8439Nonce(const std::array& nonce) +{ + auto nonce_ptr = reinterpret_cast(nonce.data()); + input[9] = ReadLE32(nonce_ptr); + input[10] = ReadLE32(nonce_ptr + 4); + input[11] = ReadLE32(nonce_ptr + 8); +} + 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; @@ -322,3 +336,17 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) m_bufleft = 64 - bytes; } } + +void FSChaCha20::Crypt(Span input, Span output) +{ + assert(input.size() == output.size()); + c20.Crypt(reinterpret_cast(input.data()), + reinterpret_cast(output.data()), input.size()); + chunk_counter++; + + if (chunk_counter % rekey_interval == 0) { + c20.Keystream(reinterpret_cast(key.data()), FSCHACHA20_KEYLEN); + c20.SetKey32(reinterpret_cast(key.data())); + set_nonce(); + } +} diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index b286ef59fe..15597e5073 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -5,9 +5,17 @@ #ifndef BITCOIN_CRYPTO_CHACHA20_H #define BITCOIN_CRYPTO_CHACHA20_H +#include +#include +#include + +#include +#include #include #include +static constexpr size_t FSCHACHA20_KEYLEN = 32; // bytes + // classes for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein // https://cr.yp.to/chacha/chacha-20080128.pdf */ @@ -32,6 +40,9 @@ class ChaCha20Aligned /** set the 64bit block counter (pos seeks to byte position 64*pos). */ void Seek64(uint64_t pos); + void SetRFC8439Nonce(const std::array& nonce); + void SeekRFC8439(uint32_t pos); + /** outputs the keystream of size <64*blocks> into */ void Keystream64(unsigned char* c, size_t blocks); @@ -72,6 +83,17 @@ class ChaCha20 m_bufleft = 0; } + void SetRFC8439Nonce(const std::array& nonce) { + m_aligned.SetRFC8439Nonce(nonce); + m_bufleft = 0; + } + + void SeekRFC8439(uint32_t pos) + { + m_aligned.SeekRFC8439(pos); + m_bufleft = 0; + } + /** outputs the keystream of size into */ void Keystream(unsigned char* c, size_t bytes); @@ -81,4 +103,39 @@ class ChaCha20 void Crypt(const unsigned char* input, unsigned char* output, size_t bytes); }; +class FSChaCha20 +{ +private: + ChaCha20 c20; + size_t rekey_interval; + uint32_t chunk_counter{0}; + std::array key; + + void set_nonce() + { + std::array nonce; + WriteLE32(reinterpret_cast(nonce.data()), uint32_t{0}); + WriteLE64(reinterpret_cast(nonce.data()) + 4, chunk_counter / rekey_interval); + c20.SetRFC8439Nonce(nonce); + } + +public: + FSChaCha20() = delete; + FSChaCha20(const std::array& key, + size_t rekey_interval) + : c20{reinterpret_cast(key.data())}, + rekey_interval{rekey_interval}, + key{key} + { + assert(rekey_interval > 0); + set_nonce(); + } + + ~FSChaCha20() + { + memory_cleanse(key.data(), FSCHACHA20_KEYLEN); + } + + void Crypt(Span input, Span output); +}; #endif // BITCOIN_CRYPTO_CHACHA20_H diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp deleted file mode 100644 index 119ad6902f..0000000000 --- a/src/crypto/chacha_poly_aead.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2019-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. - -#if defined(HAVE_CONFIG_H) -#include -#endif - -#include - -#include -#include - -#include -#include - -#include -#include - -#ifndef HAVE_TIMINGSAFE_BCMP - -int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) -{ - const unsigned char *p1 = b1, *p2 = b2; - int ret = 0; - - for (; n > 0; n--) - ret |= *p1++ ^ *p2++; - return (ret != 0); -} - -#endif // TIMINGSAFE_BCMP - -ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len) -{ - assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN); - assert(K_2_len == 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 - m_cached_aad_seqnr = std::numeric_limits::max(); -} - -bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len /* length of the output buffer for sanity checks */, const unsigned char* src, size_t src_len, bool is_encrypt) -{ - // check buffer boundaries - if ( - // if we encrypt, make sure the source contains at least the expected AAD and the destination has at least space for the source + MAC - (is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN || dest_len < src_len + POLY1305_TAGLEN)) || - // if we decrypt, make sure the source contains at least the expected AAD+MAC and the destination has at least space for the source - MAC - (!is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN || dest_len < src_len - POLY1305_TAGLEN))) { - return false; - } - - unsigned char expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN]; - memset(poly_key, 0, sizeof(poly_key)); - m_chacha_main.SetIV(seqnr_payload); - - // 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.Seek64(0); - m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key)); - - // if decrypting, verify the tag prior to decryption - if (!is_encrypt) { - const unsigned char* tag = src + src_len - POLY1305_TAGLEN; - poly1305_auth(expected_tag, src, src_len - POLY1305_TAGLEN, poly_key); - - // constant time compare the calculated MAC with the provided MAC - if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN) != 0) { - memory_cleanse(expected_tag, sizeof(expected_tag)); - memory_cleanse(poly_key, sizeof(poly_key)); - return false; - } - memory_cleanse(expected_tag, sizeof(expected_tag)); - // MAC has been successfully verified, make sure we don't convert it in decryption - src_len -= POLY1305_TAGLEN; - } - - // calculate and cache the next 64byte keystream block if requested sequence number is not yet the cache - if (m_cached_aad_seqnr != seqnr_aad) { - m_cached_aad_seqnr = seqnr_aad; - m_chacha_header.SetIV(seqnr_aad); - 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 - dest[0] = src[0] ^ m_aad_keystream_buffer[aad_pos]; - dest[1] = src[1] ^ m_aad_keystream_buffer[aad_pos + 1]; - 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.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 - if (is_encrypt) { - // the poly1305 tag expands over the AAD (3 bytes length) & encrypted payload - poly1305_auth(dest + src_len, dest, src_len, poly_key); - } - - // cleanse no longer required MAC and polykey - memory_cleanse(poly_key, sizeof(poly_key)); - return true; -} - -bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext) -{ - // enforce valid aad position to avoid accessing outside of the 64byte keystream cache - // (there is space for 21 times 3 bytes) - assert(aad_pos >= 0 && aad_pos < CHACHA20_ROUND_OUTPUT - CHACHA20_POLY1305_AEAD_AAD_LEN); - if (m_cached_aad_seqnr != seqnr_aad) { - // 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.Seek64(0); // block counter 0 - m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache - } - - // decrypt the ciphertext length by XORing the right position of the 64byte keystream cache with the ciphertext - *len24_out = (ciphertext[0] ^ m_aad_keystream_buffer[aad_pos + 0]) | - (ciphertext[1] ^ m_aad_keystream_buffer[aad_pos + 1]) << 8 | - (ciphertext[2] ^ m_aad_keystream_buffer[aad_pos + 2]) << 16; - - return true; -} diff --git a/src/crypto/chacha_poly_aead.h b/src/crypto/chacha_poly_aead.h deleted file mode 100644 index 5d57b5a5e2..0000000000 --- a/src/crypto/chacha_poly_aead.h +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2019-2021 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_CRYPTO_CHACHA_POLY_AEAD_H -#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H - -#include - -#include - -static constexpr int CHACHA20_POLY1305_AEAD_KEY_LEN = 32; -static constexpr int CHACHA20_POLY1305_AEAD_AAD_LEN = 3; /* 3 bytes length */ -static constexpr int CHACHA20_ROUND_OUTPUT = 64; /* 64 bytes per round */ -static constexpr int AAD_PACKAGES_PER_ROUND = 21; /* 64 / 3 round down*/ - -/* A AEAD class for ChaCha20-Poly1305@bitcoin. - * - * ChaCha20 is a stream cipher designed by Daniel Bernstein and described in - * [https://cr.yp.to/chacha/chacha-20080128.pdf ChaCha20]. It operates - * by permuting 128 fixed bits, 128 or 256 bits of key, a 64 bit nonce and a 64 - * bit counter into 64 bytes of output. This output is used as a keystream, with - * any unused bytes simply discarded. - * - * Poly1305 [https://cr.yp.to/mac/poly1305-20050329.pdf Poly1305], also - * by Daniel Bernstein, is a one-time Carter-Wegman MAC that computes a 128 bit - * integrity tag given a message and a single-use 256 bit secret key. - * - * The chacha20-poly1305@bitcoin combines these two primitives into an - * authenticated encryption mode. The construction used is based on that proposed - * for TLS by Adam Langley in - * [http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-03 "ChaCha20 - * and Poly1305 based Cipher Suites for TLS", Adam Langley], but differs in - * the layout of data passed to the MAC and in the addition of encryption of the - * packet lengths. - * - * ==== Detailed Construction ==== - * - * The chacha20-poly1305@bitcoin cipher requires two 256 bits of key material as - * output from the key exchange. Each key (K_1 and K_2) are used by two separate - * instances of chacha20. - * - * The instance keyed by K_1 is a stream cipher that is used only to encrypt the 3 - * byte packet length field and has its own sequence number. The second instance, - * keyed by K_2, is used in conjunction with poly1305 to build an AEAD - * (Authenticated Encryption with Associated Data) that is used to encrypt and - * authenticate the entire packet. - * - * Two separate cipher instances are used here so as to keep the packet lengths - * confidential but not create an oracle for the packet payload cipher by - * decrypting and using the packet length prior to checking the MAC. By using an - * independently-keyed cipher instance to encrypt the length, an active attacker - * seeking to exploit the packet input handling as a decryption oracle can learn - * nothing about the payload contents or its MAC (assuming key derivation, - * ChaCha20 and Poly1305 are secure). - * - * The AEAD is constructed as follows: for each packet, generate a Poly1305 key by - * taking the first 256 bits of ChaCha20 stream output generated using K_2, an IV - * consisting of the packet sequence number encoded as an LE uint64 and a ChaCha20 - * block counter of zero. The K_2 ChaCha20 block counter is then set to the - * little-endian encoding of 1 (i.e. {1, 0, 0, 0, 0, 0, 0, 0}) and this instance - * is used for encryption of the packet payload. - * - * ==== Packet Handling ==== - * - * When receiving a packet, the length must be decrypted first. When 3 bytes of - * ciphertext length have been received, they may be decrypted. - * - * A ChaCha20 round always calculates 64bytes which is sufficient to crypt 21 - * times a 3 bytes length field (21*3 = 63). The length field sequence number can - * thus be used 21 times (keystream caching). - * - * The length field must be enc-/decrypted with the ChaCha20 keystream keyed with - * K_1 defined by block counter 0, the length field sequence number in little - * endian and a keystream position from 0 to 60. - * - * Once the entire packet has been received, the MAC MUST be checked before - * decryption. A per-packet Poly1305 key is generated as described above and the - * MAC tag calculated using Poly1305 with this key over the ciphertext of the - * packet length and the payload together. The calculated MAC is then compared in - * constant time with the one appended to the packet and the packet decrypted - * using ChaCha20 as described above (with K_2, the packet sequence number as - * nonce and a starting block counter of 1). - * - * Detection of an invalid MAC MUST lead to immediate connection termination. - * - * To send a packet, first encode the 3 byte length and encrypt it using K_1 as - * described above. Encrypt the packet payload (using K_2) and append it to the - * encrypted length. Finally, calculate a MAC tag and append it. - * - * The initiating peer MUST use K_1_A, K_2_A to encrypt messages on - * the send channel, K_1_B, K_2_B MUST be used to decrypt messages on - * the receive channel. - * - * The responding peer MUST use K_1_A, K_2_A to decrypt messages on - * the receive channel, K_1_B, K_2_B MUST be used to encrypt messages - * on the send channel. - * - * Optimized implementations of ChaCha20-Poly1305@bitcoin are relatively fast in - * general, therefore it is very likely that encrypted messages require not more - * CPU cycles per bytes then the current unencrypted p2p message format - * (ChaCha20/Poly1305 versus double SHA256). - * - * The initial packet sequence numbers are 0. - * - * K_2 ChaCha20 cipher instance (payload) must never reuse a {key, nonce} for - * encryption nor may it be used to encrypt more than 2^70 bytes under the same - * {key, nonce}. - * - * K_1 ChaCha20 cipher instance (length field/AAD) must never reuse a {key, nonce, - * position-in-keystream} for encryption nor may it be used to encrypt more than - * 2^70 bytes under the same {key, nonce}. - * - * We use message sequence numbers for both communication directions. - */ - -class ChaCha20Poly1305AEAD -{ -private: - ChaCha20 m_chacha_header; // AAD cipher instance (encrypted length) and poly1305 key-derivation cipher instance - ChaCha20 m_chacha_main; // payload - unsigned char m_aad_keystream_buffer[CHACHA20_ROUND_OUTPUT]; // aad keystream cache - uint64_t m_cached_aad_seqnr; // aad keystream cache hint - -public: - ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len); - - explicit ChaCha20Poly1305AEAD(const ChaCha20Poly1305AEAD&) = delete; - - /** Encrypts/decrypts a packet - seqnr_payload, the message sequence number - seqnr_aad, the messages AAD sequence number which allows reuse of the AAD keystream - aad_pos, position to use in the AAD keystream to encrypt the AAD - dest, output buffer, must be of a size equal or larger then CHACHA20_POLY1305_AEAD_AAD_LEN + payload (+ POLY1305_TAG_LEN in encryption) bytes - destlen, length of the destination buffer - src, the AAD+payload to encrypt or the AAD+payload+MAC to decrypt - src_len, the length of the source buffer - is_encrypt, set to true if we encrypt (creates and appends the MAC instead of verifying it) - */ - bool Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len, const unsigned char* src, size_t src_len, bool is_encrypt); - - /** decrypts the 3 bytes AAD data and decodes it into a uint32_t field */ - bool GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext); -}; - -#endif // BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H diff --git a/src/crypto/rfc8439.cpp b/src/crypto/rfc8439.cpp new file mode 100644 index 0000000000..cd887318a5 --- /dev/null +++ b/src/crypto/rfc8439.cpp @@ -0,0 +1,101 @@ +// Copyright (c) 2021 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 + +#include +#include + +#include + +#ifndef HAVE_TIMINGSAFE_BCMP +#define HAVE_TIMINGSAFE_BCMP + +int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) +{ + const unsigned char *p1 = b1, *p2 = b2; + int ret = 0; + + for (; n > 0; n--) + ret |= *p1++ ^ *p2++; + return (ret != 0); +} + +#endif // TIMINGSAFE_BCMP + +inline size_t padded16_size(size_t len) +{ + return (len % 16 == 0) ? len : (len / 16 + 1) * 16; +} + +void ComputeRFC8439Tag(const std::array& polykey, + const Span aad, const Span ciphertext, + Span tag_out) +{ + assert(tag_out.size() == POLY1305_TAGLEN); + std::vector bytes_to_authenticate; + auto padded_aad_size = padded16_size(aad.size()); + auto padded_ciphertext_size = padded16_size(ciphertext.size()); + bytes_to_authenticate.resize(padded_aad_size + padded_ciphertext_size + 16, std::byte{0x00}); + std::copy(aad.begin(), aad.end(), bytes_to_authenticate.begin()); + std::copy(ciphertext.begin(), ciphertext.end(), bytes_to_authenticate.begin() + padded_aad_size); + WriteLE64(reinterpret_cast(bytes_to_authenticate.data()) + padded_aad_size + padded_ciphertext_size, aad.size()); + WriteLE64(reinterpret_cast(bytes_to_authenticate.data()) + padded_aad_size + padded_ciphertext_size + 8, ciphertext.size()); + + poly1305_auth(reinterpret_cast(tag_out.data()), + reinterpret_cast(bytes_to_authenticate.data()), + bytes_to_authenticate.size(), + reinterpret_cast(polykey.data())); +} + +std::array GetPoly1305Key(ChaCha20& c20) +{ + c20.SeekRFC8439(0); + std::array polykey; + c20.Keystream(reinterpret_cast(polykey.data()), POLY1305_KEYLEN); + return polykey; +} + +static void RFC8439Crypt(ChaCha20& c20, const Span in_bytes, Span out_bytes) +{ + assert(in_bytes.size() <= out_bytes.size()); + c20.SeekRFC8439(1); + c20.Crypt(reinterpret_cast(in_bytes.data()), reinterpret_cast(out_bytes.data()), in_bytes.size()); +} + +void RFC8439Encrypt(const Span aad, const RFC8439Key& key, const std::array& nonce, const Span plaintext, Span output) +{ + assert(output.size() >= plaintext.size() + POLY1305_TAGLEN); + + ChaCha20 c20{reinterpret_cast(key.data())}; + c20.SetRFC8439Nonce(nonce); + + std::array polykey{GetPoly1305Key(c20)}; + + RFC8439Crypt(c20, plaintext, output); + ComputeRFC8439Tag(polykey, aad, + {output.data(), plaintext.size()}, + {output.data() + plaintext.size(), POLY1305_TAGLEN}); +} + +bool RFC8439Decrypt(const Span aad, const RFC8439Key& key, const std::array& nonce, const Span input, Span plaintext) +{ + assert(plaintext.size() >= input.size() - POLY1305_TAGLEN); + + ChaCha20 c20{reinterpret_cast(key.data())}; + c20.SetRFC8439Nonce(nonce); + + std::array polykey{GetPoly1305Key(c20)}; + std::array tag; + + ComputeRFC8439Tag(polykey, aad, {input.data(), input.size() - POLY1305_TAGLEN}, tag); + + if (timingsafe_bcmp(reinterpret_cast(input.data() + input.size() - POLY1305_TAGLEN), + reinterpret_cast(tag.data()), POLY1305_TAGLEN) != 0) { + return false; + } + + RFC8439Crypt(c20, {input.data(), input.size() - POLY1305_TAGLEN}, plaintext); + return true; +} diff --git a/src/crypto/rfc8439.h b/src/crypto/rfc8439.h new file mode 100644 index 0000000000..71113aefaf --- /dev/null +++ b/src/crypto/rfc8439.h @@ -0,0 +1,24 @@ +// Copyright (c) 2021 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_CRYPTO_RFC8439_H +#define BITCOIN_CRYPTO_RFC8439_H + +#include +#include + +#include +#include +#include + +constexpr static size_t RFC8439_KEYLEN{32}; +constexpr static size_t RFC8439_EXPANSION{POLY1305_TAGLEN}; + +using RFC8439Key = std::array; + +void RFC8439Encrypt(const Span aad, const RFC8439Key& key, const std::array& nonce, const Span plaintext, Span output); + +/** returns false if authentication fails */ +bool RFC8439Decrypt(const Span aad, const RFC8439Key& key, const std::array& nonce, const Span input, Span plaintext); +#endif // BITCOIN_CRYPTO_RFC8439_H diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index e4e8596a5d..6cf83f60fd 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -3,28 +3,34 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include -#include #include #include #include +#include #include +#include #include #include #include #include #include -#include #include +#include #include #include #include #include +#include +#include #include #include +static constexpr size_t CHACHA20_ROUND_OUTPUT = 64; // bytes; + BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup) template @@ -184,6 +190,80 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk } } +static void TestFSChaCha20(const std::string& hex_plaintext, const std::string& hexkey, size_t rekey_interval, const std::string& ciphertext_after_rotation) +{ + auto key_vec = ParseHex(hexkey); + BOOST_CHECK_EQUAL(FSCHACHA20_KEYLEN, key_vec.size()); + std::array key; + std::memcpy(key.data(), key_vec.data(), FSCHACHA20_KEYLEN); + + auto plaintext = ParseHex(hex_plaintext); + + auto fsc20 = FSChaCha20{key, rekey_interval}; + auto c20 = ChaCha20{reinterpret_cast(key.data())}; + + std::vector fsc20_output; + fsc20_output.resize(plaintext.size()); + + std::vector c20_output; + c20_output.resize(plaintext.size()); + + for (size_t i = 0; i < rekey_interval; i++) { + fsc20.Crypt(MakeByteSpan(plaintext), fsc20_output); + c20.Crypt(plaintext.data(), c20_output.data(), plaintext.size()); + BOOST_CHECK_EQUAL(0, std::memcmp(c20_output.data(), fsc20_output.data(), plaintext.size())); + } + + // At the rotation interval, the outputs will no longer match + fsc20.Crypt(MakeByteSpan(plaintext), fsc20_output); + auto c20_copy = c20; + c20.Crypt(plaintext.data(), c20_output.data(), plaintext.size()); + BOOST_CHECK(std::memcmp(c20_output.data(), fsc20_output.data(), plaintext.size()) != 0); + + unsigned char new_key[FSCHACHA20_KEYLEN]; + c20_copy.Keystream(new_key, FSCHACHA20_KEYLEN); + c20.SetKey32(new_key); + + std::array new_nonce; + WriteLE32(reinterpret_cast(new_nonce.data()), 0); + WriteLE64(reinterpret_cast(new_nonce.data()) + 4, 1); + c20.SetRFC8439Nonce(new_nonce); + + // Outputs should match again after simulating key rotation + c20.Crypt(plaintext.data(), c20_output.data(), plaintext.size()); + BOOST_CHECK_EQUAL(0, std::memcmp(c20_output.data(), fsc20_output.data(), plaintext.size())); + + BOOST_CHECK_EQUAL(HexStr(fsc20_output), ciphertext_after_rotation); +} + +static void TestChaCha20RFC8439(const std::string& hex_key, const std::array& nonce, uint32_t seek, const std::string& hex_expected_keystream, const std::string& hex_input, const std::string& hex_expected_output) +{ + auto key = ParseHex(hex_key); + + if (!hex_expected_keystream.empty()) { + ChaCha20 c20(key.data()); + c20.SetRFC8439Nonce(nonce); + c20.SeekRFC8439(seek); + std::vector keystream; + keystream.resize(CHACHA20_ROUND_OUTPUT); + c20.Keystream(keystream.data(), CHACHA20_ROUND_OUTPUT); + BOOST_CHECK_EQUAL(HexStr(keystream).substr(0, hex_expected_keystream.size()), hex_expected_keystream); + } + + if (!hex_input.empty()) { + assert(hex_input.size() == hex_expected_output.size()); + ChaCha20 c20(key.data()); + c20.SetRFC8439Nonce(nonce); + c20.SeekRFC8439(seek); + + auto input = ParseHex(hex_input); + std::vector output; + output.resize(input.size()); + c20.Crypt(input.data(), output.data(), input.size()); + BOOST_CHECK_EQUAL(HexStr(output).substr(0, hex_expected_output.size()), hex_expected_output); + } +} + static void TestPoly1305(const std::string &hexmessage, const std::string &hexkey, const std::string& hextag) { std::vector key = ParseHex(hexkey); @@ -580,6 +660,87 @@ 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"); + + // Test vectors from https://datatracker.ietf.org/doc/html/rfc8439#section-2.4.2 + const auto rfc8439_nonce0 = std::array{std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}}; + const auto rfc8439_nonce1 = std::array{std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x4a}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}}; + const auto rfc8439_nonce2 = std::array{std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x02}}; + TestChaCha20RFC8439("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + rfc8439_nonce1, 1, "224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b7", "", ""); + TestChaCha20RFC8439("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + rfc8439_nonce1, 2, "69a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a832c89c167eacd901d7e2bf363740373201aa188fbbce83991c4ed", "", ""); + + // Test vectors from https://datatracker.ietf.org/doc/html/rfc8439#appendix-A.1 + TestChaCha20RFC8439("0000000000000000000000000000000000000000000000000000000000000000", + rfc8439_nonce0, 0, "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", "", ""); + TestChaCha20RFC8439("0000000000000000000000000000000000000000000000000000000000000000", + rfc8439_nonce0, 1, "9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f", "", ""); + TestChaCha20RFC8439("0000000000000000000000000000000000000000000000000000000000000001", + rfc8439_nonce0, 1, "3aeb5224ecf849929b9d828db1ced4dd832025e8018b8160b82284f3c949aa5a8eca00bbb4a73bdad192b5c42f73f2fd4e273644c8b36125a64addeb006c13a0", "", ""); + TestChaCha20RFC8439("00ff000000000000000000000000000000000000000000000000000000000000", + rfc8439_nonce0, 2, "72d54dfbf12ec44b362692df94137f328fea8da73990265ec1bbbea1ae9af0ca13b25aa26cb4a648cb9b9d1be65b2c0924a66c54d545ec1b7374f4872e99f096", "", ""); + TestChaCha20RFC8439("0000000000000000000000000000000000000000000000000000000000000000", + rfc8439_nonce2, 0, "c2c64d378cd536374ae204b9ef933fcd1a8b2288b3dfa49672ab765b54ee27c78a970e0e955c14f3a88e741b97c286f75f8fc299e8148362fa198a39531bed6d", "", ""); + + // Test vectors from https://datatracker.ietf.org/doc/html/rfc8439#appendix-A.2 + TestChaCha20RFC8439("0000000000000000000000000000000000000000000000000000000000000000", + rfc8439_nonce0, 0, "", "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); + TestChaCha20RFC8439("0000000000000000000000000000000000000000000000000000000000000001", + rfc8439_nonce2, 1, "", "416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e7472696275746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e20224945544620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c2073746174656d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c656374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207768696368206172652061646472657373656420746f", "a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85ad00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6ccc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221"); + TestChaCha20RFC8439("1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + rfc8439_nonce2, 42, "", "2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e", "62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1"); + + // Test vectors from https://datatracker.ietf.org/doc/html/rfc8439#appendix-A.4 + TestChaCha20RFC8439("0000000000000000000000000000000000000000000000000000000000000000", + rfc8439_nonce0, 0, "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7", "", ""); + TestChaCha20RFC8439("0000000000000000000000000000000000000000000000000000000000000001", + rfc8439_nonce2, 0, "ecfa254f845f647473d3cb140da9e87606cb33066c447b87bc2666dde3fbb739", "", ""); + TestChaCha20RFC8439("1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + rfc8439_nonce2, 0, "965e3bc6f9ec7ed9560808f4d229f94b137ff275ca9b3fcbdd59deaad23310ae", "", ""); + + // Forward secure ChaCha20 + TestFSChaCha20("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "0000000000000000000000000000000000000000000000000000000000000000", + 256, + "a93df4ef03011f3db95f60d996e1785df5de38fc39bfcb663a47bb5561928349"); + TestFSChaCha20("01", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + 5, + "ea"); + TestFSChaCha20("e93fdb5c762804b9a706816aca31e35b11d2aa3080108ef46a5b1f1508819c0a", + "8ec4c3ccdaea336bdeb245636970be01266509b33f3d2642504eaf412206207a", + 4096, + "8bfaa4eacff308fdb4a94a5ff25bd9d0c1f84b77f81239f67ff39d6e1ac280c9"); } BOOST_AUTO_TEST_CASE(chacha20_midblock) @@ -690,129 +851,100 @@ BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests) "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d"); } -static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aad_length, const std::string& hex_m, const std::string& hex_k1, const std::string& hex_k2, const std::string& hex_aad_keystream, const std::string& hex_encrypted_message, const std::string& hex_encrypted_message_seq_999) +static void TestBIP324CipherSuite(const std::string& hex_aad, const std::string& hex_contents, const std::string& hex_key_L, const std::string& hex_key_P, const std::string& hex_expected_output_seq_0, const std::string& hex_expected_output_seq_999) { - // we need two sequence numbers, one for the payload cipher instance... - uint32_t seqnr_payload = 0; - // ... and one for the AAD (length) cipher instance - uint32_t seqnr_aad = 0; - // we need to keep track of the position in the AAD cipher instance - // keystream since we use the same 64byte output 21 times - // (21 times 3 bytes length < 64) - int aad_pos = 0; - - std::vector aead_K_1 = ParseHex(hex_k1); - std::vector aead_K_2 = ParseHex(hex_k2); - std::vector plaintext_buf = ParseHex(hex_m); - std::vector expected_aad_keystream = ParseHex(hex_aad_keystream); - std::vector expected_ciphertext_and_mac = ParseHex(hex_encrypted_message); - std::vector expected_ciphertext_and_mac_sequence999 = ParseHex(hex_encrypted_message_seq_999); - - std::vector ciphertext_buf(plaintext_buf.size() + POLY1305_TAGLEN, 0); - std::vector plaintext_buf_new(plaintext_buf.size(), 0); - std::vector cmp_ctx_buffer(64); + auto key_L_vec = ParseHex(hex_key_L); + BIP324Key key_L; + std::memcpy(key_L.data(), key_L_vec.data(), BIP324_KEY_LEN); + + auto key_P_vec = ParseHex(hex_key_P); + BIP324Key key_P; + std::memcpy(key_P.data(), key_P_vec.data(), BIP324_KEY_LEN); + + auto aad = ParseHex(hex_aad); + + const auto original_contents_bytes = ParseHex(hex_contents); + auto contents_buf = original_contents_bytes; + + std::vector encrypted_pkt(BIP324_LENGTH_FIELD_LEN + BIP324_HEADER_LEN + contents_buf.size() + RFC8439_EXPANSION, 0); + std::vector contents_buf_dec(contents_buf.size(), 0); uint32_t out_len = 0; - // create the AEAD instance - 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()); - - // encipher - bool res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); - // make sure the operation succeeded if expected to succeed - BOOST_CHECK_EQUAL(res, must_succeed); - if (!res) return; - - // verify ciphertext & mac against the test vector - BOOST_CHECK_EQUAL(expected_ciphertext_and_mac.size(), ciphertext_buf.size()); - BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac.data(), ciphertext_buf.size()) == 0); - - // manually construct the AAD keystream - cmp_ctx.SetIV(seqnr_aad); - 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 - uint32_t len_cmp = 0; - len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) | - (ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 | - (ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16; - BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); - - // encrypt / decrypt 1000 packets + BIP324CipherSuite suite_enc(key_L, key_P); + BIP324CipherSuite suite_dec(key_L, key_P); + + BIP324HeaderFlags flags{BIP324_NONE}; + std::array encrypted_pkt_len; + + // encrypt / decrypt the packet 1000 times for (size_t i = 0; i < 1000; ++i) { - res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); - BOOST_CHECK(res); - BOOST_CHECK(aead.GetLength(&out_len, seqnr_aad, aad_pos, ciphertext_buf.data())); - BOOST_CHECK_EQUAL(out_len, expected_aad_length); - res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, plaintext_buf_new.data(), plaintext_buf_new.size(), ciphertext_buf.data(), ciphertext_buf.size(), false); + // encrypt + auto res = suite_enc.Crypt(MakeByteSpan(aad), MakeByteSpan(contents_buf), MakeWritableByteSpan(encrypted_pkt), flags, true); BOOST_CHECK(res); + // verify ciphertext & mac against the test vector + if (i == 0) { + BOOST_CHECK_EQUAL(HexStr(encrypted_pkt), hex_expected_output_seq_0); + } else if (i == 999) { + BOOST_CHECK_EQUAL(HexStr(encrypted_pkt), hex_expected_output_seq_999); + } - // make sure we repetitive get the same plaintext - BOOST_CHECK(memcmp(plaintext_buf.data(), plaintext_buf_new.data(), plaintext_buf.size()) == 0); + std::memcpy(encrypted_pkt_len.data(), encrypted_pkt.data(), BIP324_LENGTH_FIELD_LEN); + out_len = suite_dec.DecryptLength(encrypted_pkt_len); + BOOST_CHECK_EQUAL(out_len, contents_buf.size()); - // compare sequence number 999 against the test vector - if (seqnr_payload == 999) { - BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac_sequence999.data(), expected_ciphertext_and_mac_sequence999.size()) == 0); - } - // set nonce and block counter, output the keystream - cmp_ctx.SetIV(seqnr_aad); - cmp_ctx.Seek64(0); - cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); - - // crypt the 3 length bytes and compare the length - len_cmp = 0; - len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) | - (ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 | - (ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16; - BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); - - // increment the sequence number(s) - // always increment the payload sequence number - // increment the AAD keystream position by its size (3) - // increment the AAD sequence number if we would hit the 64 byte limit - seqnr_payload++; - aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; - if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { - aad_pos = 0; - seqnr_aad++; + res = suite_dec.Crypt(MakeByteSpan(aad), {reinterpret_cast(encrypted_pkt.data()) + BIP324_LENGTH_FIELD_LEN, encrypted_pkt.size() - BIP324_LENGTH_FIELD_LEN}, MakeWritableByteSpan(contents_buf_dec), flags, false); + BOOST_CHECK(res); + BOOST_CHECK_EQUAL(flags, BIP324_NONE); + + // make sure we always get the same plaintext + BOOST_CHECK_EQUAL(contents_buf_dec.size(), original_contents_bytes.size()); + if (!original_contents_bytes.empty()) { + BOOST_CHECK_EQUAL(0, std::memcmp(contents_buf_dec.data(), original_contents_bytes.data(), original_contents_bytes.size())); } } } -BOOST_AUTO_TEST_CASE(chacha20_poly1305_aead_testvector) +BOOST_AUTO_TEST_CASE(bip324_cipher_suite_testvectors) { - /* test chacha20poly1305@bitcoin AEAD */ - - // must fail with no message - TestChaCha20Poly1305AEAD(false, 0, - "", - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", "", "", ""); - - TestChaCha20Poly1305AEAD(true, 0, - /* m */ "0000000000000000000000000000000000000000000000000000000000000000", - /* k1 (AAD) */ "0000000000000000000000000000000000000000000000000000000000000000", - /* k2 (payload) */ "0000000000000000000000000000000000000000000000000000000000000000", - /* AAD keystream */ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", - /* encrypted message & MAC */ "76b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32d2fc11829c1b6c1df1f551cd6131ff08", - /* encrypted message & MAC at sequence 999 */ "b0a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3aaa7aa16ec62c5e24f040c08bb20c3598"); - TestChaCha20Poly1305AEAD(true, 1, - "0100000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", - "77b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32baf0c85b6dff8602b06cf52a6aefc62e", - "b1a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3a8bd94d54b5ecabbc41ffbb0c90924080"); - TestChaCha20Poly1305AEAD(true, 255, - "ff0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9", - "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", - "c640c1711e3ee904ac35c57ab9791c8a1c408603a90b77a83b54f6c844cb4b06d94e7fc6c800e165acd66147e80ec45a567f6ce66d05ec0cae679dceeb890017", - "3940c1e92da4582ff6f92a776aeb14d014d384eeb30f660dacf70a14a23fd31e91212701334e2ce1acf5199dc84f4d61ddbe6571bca5af874b4c9226c26e650995d157644e1848b96ed6c2102d5489a050e71d29a5a66ece11de5fb5c9558d54da28fe45b0bc4db4e5b88030bfc4a352b4b7068eccf656bae7ad6a35615315fc7c49d4200388d5eca67c2e822e069336c69b40db67e0f3c81209c50f3216a4b89fb3ae1b984b7851a2ec6f68ab12b101ab120e1ea7313bb93b5a0f71185c7fea017ddb92769861c29dba4fbc432280d5dff21b36d1c4c790128b22699950bb18bf74c448cdfe547d8ed4f657d8005fdc0cd7a050c2d46050a44c4376355858981fbe8b184288276e7a93eabc899c4a", - "f039c6689eaeef0456685200feaab9d54bbd9acde4410a3b6f4321296f4a8ca2604b49727d8892c57e005d799b2a38e85e809f20146e08eec75169691c8d4f54a0d51a1e1c7b381e0474eb02f994be9415ef3ffcbd2343f0601e1f3b172a1d494f838824e4df570f8e3b0c04e27966e36c82abd352d07054ef7bd36b84c63f9369afe7ed79b94f953873006b920c3fa251a771de1b63da927058ade119aa898b8c97e42a606b2f6df1e2d957c22f7593c1e2002f4252f4c9ae4bf773499e5cfcfe14dfc1ede26508953f88553bf4a76a802f6a0068d59295b01503fd9a600067624203e880fdf53933b96e1f4d9eb3f4e363dd8165a278ff667a41ee42b9892b077cefff92b93441f7be74cf10e6cd"); + /* test bip324 cipher suite */ + + // encrypting an empty message should result in 20 bytes: + // 3 bytes of encrypted length, 1 byte header and 16 bytes MAC + TestBIP324CipherSuite(/* aad */ "", + /* plaintext */ "", + /* k_l */ "0000000000000000000000000000000000000000000000000000000000000000", + /* k_p */ "0000000000000000000000000000000000000000000000000000000000000000", + /* ciphertext_and_mac_0 */ "76b8e09fbedcfd1809ff3c10adf8277fcc0581b8", + /* ciphertext_and_mac_999 */ "5dd1ef229ae773099415b4ae56d003d21b4e4a08"); + + TestBIP324CipherSuite("", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "56b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed29e7e38bb44c94b6a43c525ffca66c79e9", + "7dd1ef2205b549ef8e0dc60b16342f037e415cfcd3d6111532f8f9e7553e422129d9df0d58e083ad538381c1e30a51a5d296cce2"); + + TestBIP324CipherSuite("", + "0100000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "56b8e09f06e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed2929449b86c1e4e213676824f2c48e5336", + "7dd1ef2204b549ef8e0dc60b16342f037e415cfcd3d6111532f8f9e7553e422129d9df0d04fc5b2ab5cb70895a90edddbe642c00"); + + TestBIP324CipherSuite("", + "fc0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9", + "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "3940c1184442315c7340b89171039acb48f95287e66e56f7afa7cf00f95044d26fb69d46ac5c16a2d57a1cadc39160644717559e73480734410a3f543c5f231a7d7ed77af2a64681f6a7417283cef85504ac5de9fdea100e6c67ef7a1bfcd888a92a5f1ef2c9074b44b572aa748f29ff61a850ce40470004dff5cc1d926c5abe25ace47a12c5373094a26bab027e008154fb630aa062490b5421e96691a3f79557f7a79e3cfd9100796671ea241703ddf326f113adf1694bbd6e0ca032e16f936e7bfbf174e7ef4af5b53a6a9102e6fa41a8e589290f39a7bc7a6003088c612a43a36c2e9f2e740797ad3a2a1a80e0f67157fb9abc40487077368e94751a266a0b2dac24f0adabd5c6d7ba54316eee951da560", + "2db339c8500d35db91994138b4ab5e698086b4ec7fb66e75d083b18f84a9da7d696be75c349cb1555a58f65f123d4b68e2be2277fd7b38ba26ad93040a22ac8f7782b00d75c7650dcff0442f7ef91980aaabecb2c8cefec5d5eb9d495b5e1768fe316ec2a0d69d46b7289cd2e2049f27d30a6183605651d48ac40e0d06af9ec7012d477e473f2af7842335c36acf4f5bdef45605ca243b9007b5363f095850a78945508cf3fa191b8fe7fc5359d6e00741e6504f1d50904152622f4c0bdeaa0745f00d28b995543621c96d9d9d30fa1fbf403b19a716411b1700e8401a3e1e01bb1546653fbda19d83ba5e561695baea229880ff33058f85754fe9fdc09db4491f47ae64ec030ec6163d2838d9474d40ef0579"); + + // Repeat test with non-empty aad - only mac tags (last 16 bytes) in the expected outputs change + TestBIP324CipherSuite("c6d7bc3a5079ae98fec7094bdfb42aac61d3ba64af179d672c7c33fd4a139647", + "fc0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9", + "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "3940c1184442315c7340b89171039acb48f95287e66e56f7afa7cf00f95044d26fb69d46ac5c16a2d57a1cadc39160644717559e73480734410a3f543c5f231a7d7ed77af2a64681f6a7417283cef85504ac5de9fdea100e6c67ef7a1bfcd888a92a5f1ef2c9074b44b572aa748f29ff61a850ce40470004dff5cc1d926c5abe25ace47a12c5373094a26bab027e008154fb630aa062490b5421e96691a3f79557f7a79e3cfd9100796671ea241703ddf326f113adf1694bbd6e0ca032e16f936e7bfbf174e7ef4af5b53a6a9102e6fa41a8e589290f39a7bc7a6003088c612a43a36c2e9f2e740797ad3a2a1a80e0f67157fb9abc40487077368e94751a266a0b2dac4d382097b958da569f3b6fae3faaaaf2", + "2db339c8500d35db91994138b4ab5e698086b4ec7fb66e75d083b18f84a9da7d696be75c349cb1555a58f65f123d4b68e2be2277fd7b38ba26ad93040a22ac8f7782b00d75c7650dcff0442f7ef91980aaabecb2c8cefec5d5eb9d495b5e1768fe316ec2a0d69d46b7289cd2e2049f27d30a6183605651d48ac40e0d06af9ec7012d477e473f2af7842335c36acf4f5bdef45605ca243b9007b5363f095850a78945508cf3fa191b8fe7fc5359d6e00741e6504f1d50904152622f4c0bdeaa0745f00d28b995543621c96d9d9d30fa1fbf403b19a716411b1700e8401a3e1e01bb1546653fbda19d83ba5e561695baea229880ff33058f85754fe9fdc09db4491f47ae0623cf23941a59b7c7dc6cad9bd97cc1"); } BOOST_AUTO_TEST_CASE(countbits_tests) @@ -1047,4 +1179,44 @@ BOOST_AUTO_TEST_CASE(muhash_tests) BOOST_CHECK_EQUAL(HexStr(out4), "3a31e6903aff0de9f62f9a9f7f8b861de76ce2cda09822b90014319ae5dc2271"); } +static void TestRFC8439AEAD(const std::string& hex_aad, const std::string& hex_key, const std::string& hex_nonce, const std::string& hex_plaintext, const std::string& hex_expected_ciphertext, const std::string& hex_expected_auth_tag) +{ + auto aad = ParseHex(hex_aad); + auto key_vec = ParseHex(hex_key); + RFC8439Key key; + std::memcpy(key.data(), key_vec.data(), RFC8439_KEYLEN); + auto nonce = ParseHex(hex_nonce); + std::array nonce_arr; + std::memcpy(nonce_arr.data(), nonce.data(), 12); + auto plaintext = ParseHex(hex_plaintext); + std::vector output(plaintext.size() + POLY1305_TAGLEN, std::byte{0x00}); + RFC8439Encrypt(MakeByteSpan(aad), key, nonce_arr, MakeByteSpan(plaintext), output); + + BOOST_CHECK_EQUAL(HexStr({output.data(), output.size() - POLY1305_TAGLEN}), hex_expected_ciphertext); + BOOST_CHECK_EQUAL(HexStr({output.data() + output.size() - POLY1305_TAGLEN, POLY1305_TAGLEN}), hex_expected_auth_tag); + + std::vector decrypted_plaintext(plaintext.size(), std::byte{0x00}); + auto authenticated = RFC8439Decrypt(MakeByteSpan(aad), key, nonce_arr, output, decrypted_plaintext); + BOOST_CHECK(authenticated); + BOOST_CHECK_EQUAL(0, std::memcmp(decrypted_plaintext.data(), plaintext.data(), plaintext.size())); +} + +BOOST_AUTO_TEST_CASE(rfc8439_tests) +{ + // Test vector from https://datatracker.ietf.org/doc/html/rfc8439#section-2.8.2 + TestRFC8439AEAD("50515253c0c1c2c3c4c5c6c7", + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + "070000004041424344454647", + "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e", + "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116", + "1ae10b594f09e26a7e902ecbd0600691"); + + // Test vector from https://datatracker.ietf.org/doc/html/rfc8439#appendix-A.5 + TestRFC8439AEAD("f33388860000000000004e91", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + "000000000102030405060708", + "496e7465726e65742d4472616674732061726520647261667420646f63756d656e74732076616c696420666f722061206d6178696d756d206f6620736978206d6f6e74687320616e64206d617920626520757064617465642c207265706c616365642c206f72206f62736f6c65746564206279206f7468657220646f63756d656e747320617420616e792074696d652e20497420697320696e617070726f70726961746520746f2075736520496e7465726e65742d447261667473206173207265666572656e6365206d6174657269616c206f7220746f2063697465207468656d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67726573732e2fe2809d", + "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb24c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c8559797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523eaf4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a1049e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29a6ad5cb4022b02709b", + "eead9d67890cbb22392336fea1851f38"); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/fuzz/crypto_bip324_suite.cpp b/src/test/fuzz/crypto_bip324_suite.cpp new file mode 100644 index 0000000000..a0b107e69e --- /dev/null +++ b/src/test/fuzz/crypto_bip324_suite.cpp @@ -0,0 +1,65 @@ +// Copyright (c) 2020-2021 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +void get_key(FuzzedDataProvider& fdp, Span key) +{ + auto key_vec = fdp.ConsumeBytes(key.size()); + key_vec.resize(key.size()); + std::memcpy(key.data(), key_vec.data(), key.size()); +} + + +FUZZ_TARGET(crypto_bip324_suite) +{ + FuzzedDataProvider fdp{buffer.data(), buffer.size()}; + + BIP324Key key_L, key_P; + get_key(fdp, key_L); + get_key(fdp, key_P); + + BIP324CipherSuite suite(key_L, key_P); + + size_t contents_size = fdp.ConsumeIntegralInRange(0, 4096); + std::vector in(BIP324_LENGTH_FIELD_LEN + BIP324_HEADER_LEN + contents_size + RFC8439_EXPANSION, std::byte{0x00}); + std::vector out(BIP324_LENGTH_FIELD_LEN + BIP324_HEADER_LEN + contents_size + RFC8439_EXPANSION, std::byte{0x00}); + bool is_encrypt = fdp.ConsumeBool(); + BIP324HeaderFlags flags{fdp.ConsumeIntegralInRange(0, 255)}; + size_t aad_size = fdp.ConsumeIntegralInRange(0, 255); + auto aad = fdp.ConsumeBytes(aad_size); + LIMITED_WHILE(fdp.ConsumeBool(), 10000) + { + CallOneOf( + fdp, + [&] { + contents_size = fdp.ConsumeIntegralInRange(64, 4096); + in = std::vector(BIP324_LENGTH_FIELD_LEN + BIP324_HEADER_LEN + contents_size + RFC8439_EXPANSION, std::byte{0x00}); + out = std::vector(BIP324_LENGTH_FIELD_LEN + BIP324_HEADER_LEN + contents_size + RFC8439_EXPANSION, std::byte{0x00}); + }, + [&] { + flags = BIP324HeaderFlags{fdp.ConsumeIntegralInRange(0, 255)}; + }, + [&] { + (void)suite.Crypt(aad, in, out, flags, is_encrypt); + }, + [&] { + std::array encrypted_pkt_len; + std::memcpy(encrypted_pkt_len.data(), in.data(), BIP324_LENGTH_FIELD_LEN); + (void)suite.DecryptLength(encrypted_pkt_len); + }, + [&] { + is_encrypt = fdp.ConsumeBool(); + }); + } +} diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index 3fa445096a..afee51c35e 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include @@ -151,3 +153,26 @@ FUZZ_TARGET(chacha20_split_keystream) FuzzedDataProvider provider{buffer.data(), buffer.size()}; ChaCha20SplitFuzz(provider); } + +FUZZ_TARGET(crypto_fschacha20) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + + auto key_vec = ConsumeFixedLengthByteVector(fuzzed_data_provider, FSCHACHA20_KEYLEN); + key_vec.resize(FSCHACHA20_KEYLEN); + auto salt_vec = ConsumeFixedLengthByteVector(fuzzed_data_provider, FSCHACHA20_KEYLEN); + salt_vec.resize(FSCHACHA20_KEYLEN); + + std::array key; + std::memcpy(key.data(), key_vec.data(), FSCHACHA20_KEYLEN); + + auto fsc20 = FSChaCha20{key, fuzzed_data_provider.ConsumeIntegralInRange(1, 1024)}; + + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) + { + auto input = fuzzed_data_provider.ConsumeBytes(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); + std::vector output; + output.resize(input.size()); + fsc20.Crypt(input, output); + } +} diff --git a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp deleted file mode 100644 index 596614a71b..0000000000 --- a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2020-2021 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 -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -FUZZ_TARGET(crypto_chacha20_poly1305_aead) -{ - FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - - const std::vector k1 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN); - const std::vector k2 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN); - - ChaCha20Poly1305AEAD aead(k1.data(), k1.size(), k2.data(), k2.size()); - uint64_t seqnr_payload = 0; - uint64_t seqnr_aad = 0; - int aad_pos = 0; - size_t buffer_size = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); - std::vector in(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - std::vector out(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - bool is_encrypt = fuzzed_data_provider.ConsumeBool(); - LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { - CallOneOf( - fuzzed_data_provider, - [&] { - buffer_size = fuzzed_data_provider.ConsumeIntegralInRange(64, 4096); - in = std::vector(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - out = std::vector(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - }, - [&] { - (void)aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffer_size, is_encrypt); - }, - [&] { - uint32_t len = 0; - const bool ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data()); - assert(ok); - }, - [&] { - if (AdditionOverflow(seqnr_payload, static_cast(1))) { - return; - } - seqnr_payload += 1; - aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; - if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { - aad_pos = 0; - if (AdditionOverflow(seqnr_aad, static_cast(1))) { - return; - } - seqnr_aad += 1; - } - }, - [&] { - seqnr_payload = fuzzed_data_provider.ConsumeIntegral(); - }, - [&] { - seqnr_aad = fuzzed_data_provider.ConsumeIntegral(); - }, - [&] { - is_encrypt = fuzzed_data_provider.ConsumeBool(); - }); - } -} diff --git a/src/test/fuzz/crypto_rfc8439.cpp b/src/test/fuzz/crypto_rfc8439.cpp new file mode 100644 index 0000000000..dcb88244ba --- /dev/null +++ b/src/test/fuzz/crypto_rfc8439.cpp @@ -0,0 +1,53 @@ +// 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 +#include +#include +#include + +#include +#include + +FUZZ_TARGET(crypto_rfc8439) +{ + FuzzedDataProvider fdp{buffer.data(), buffer.size()}; + auto aad_len = fdp.ConsumeIntegralInRange(0, 1023); + auto aad = fdp.ConsumeBytes(aad_len); + aad.resize(aad_len); + + auto key_vec = fdp.ConsumeBytes(RFC8439_KEYLEN); + key_vec.resize(RFC8439_KEYLEN); + RFC8439Key key; + std::memcpy(key.data(), key_vec.data(), RFC8439_KEYLEN); + + auto nonce_vec = fdp.ConsumeBytes(12); + nonce_vec.resize(12); + std::array nonce; + std::memcpy(nonce.data(), nonce_vec.data(), 12); + + auto plaintext_len = fdp.ConsumeIntegralInRange(0, 4095); + auto plaintext = fdp.ConsumeBytes(plaintext_len); + plaintext.resize(plaintext_len); + + std::vector output(plaintext.size() + POLY1305_TAGLEN, std::byte{0x00}); + RFC8439Encrypt(aad, key, nonce, plaintext, output); + + auto bit_flip_attack = !plaintext.empty() && fdp.ConsumeBool(); + if (bit_flip_attack) { + auto byte_to_flip = fdp.ConsumeIntegralInRange(0, static_cast(output.size() - POLY1305_TAGLEN - 1)); + output[byte_to_flip] = output[byte_to_flip] ^ std::byte{0xFF}; + } + + std::vector decrypted_plaintext(plaintext.size(), std::byte{0x00}); + auto authenticated = RFC8439Decrypt(aad, key, nonce, output, decrypted_plaintext); + if (bit_flip_attack) { + assert(!authenticated); + } else { + assert(authenticated); + if (!plaintext.empty()) { + assert(std::memcmp(decrypted_plaintext.data(), plaintext.data(), plaintext.size()) == 0); + } + } +}