From d8121fe36970ac57fcee602cc73a7e98c4ace132 Mon Sep 17 00:00:00 2001 From: dhruv <856960+dhruv@users.noreply.github.com> Date: Wed, 6 Jan 2021 09:48:08 +0100 Subject: [PATCH] BIP324 Cipher Suite --- src/Makefile.am | 4 +- src/Makefile.bench.include | 2 +- src/Makefile.test.include | 2 +- src/bench/bip324_suite.cpp | 120 +++++++++++ src/bench/chacha_poly_aead.cpp | 126 ------------ src/crypto/bip324_suite.cpp | 118 +++++++++++ src/crypto/bip324_suite.h | 75 +++++++ src/crypto/chacha_poly_aead.cpp | 131 ------------ src/crypto/chacha_poly_aead.h | 146 ------------- src/crypto/rfc8439.h | 1 + src/test/crypto_tests.cpp | 191 ++++++++---------- src/test/fuzz/crypto_bip324_suite.cpp | 66 ++++++ .../fuzz/crypto_chacha20_poly1305_aead.cpp | 72 ------- 13 files changed, 463 insertions(+), 591 deletions(-) create mode 100644 src/bench/bip324_suite.cpp delete mode 100644 src/bench/chacha_poly_aead.cpp create mode 100644 src/crypto/bip324_suite.cpp create mode 100644 src/crypto/bip324_suite.h delete mode 100644 src/crypto/chacha_poly_aead.cpp delete mode 100644 src/crypto/chacha_poly_aead.h create mode 100644 src/test/fuzz/crypto_bip324_suite.cpp delete mode 100644 src/test/fuzz/crypto_chacha20_poly1305_aead.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 19bd6e90d5..10eb005416 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -517,8 +517,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 \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 0ad826bea2..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 \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index ccd6a9f851..8bcc5b7ef9 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -254,8 +254,8 @@ 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 \ diff --git a/src/bench/bip324_suite.cpp b/src/bench/bip324_suite.cpp new file mode 100644 index 0000000000..293be27b6e --- /dev/null +++ b/src/bench/bip324_suite.cpp @@ -0,0 +1,120 @@ +// 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 // for the RFC8439_EXPANSION constant +#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::array zero_rekey_salt; + memcpy(zero_arr.data(), zero_vec.data(), BIP324_KEY_LEN); + memcpy(zero_rekey_salt.data(), zero_vec.data(), BIP324_REKEY_SALT_LEN); + + BIP324CipherSuite enc{zero_arr, zero_arr, zero_rekey_salt}; + BIP324CipherSuite dec{zero_arr, zero_arr, zero_rekey_salt}; + + 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; + 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 15b3a4f310..0000000000 --- a/src/bench/chacha_poly_aead.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// 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 // 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/crypto/bip324_suite.cpp b/src/crypto/bip324_suite.cpp new file mode 100644 index 0000000000..4569bb35e4 --- /dev/null +++ b/src/crypto/bip324_suite.cpp @@ -0,0 +1,118 @@ +// 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 + +#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 + +BIP324CipherSuite::~BIP324CipherSuite() +{ + memory_cleanse(key_P.data(), key_P.size()); +} + +void BIP324CipherSuite::CommitToKeys(const Span data, bool commit_to_L, bool commit_to_P) +{ + if (commit_to_L) { + fsc20.CommitToKey(data); + } + + if (commit_to_P) { + assert(CSHA256::OUTPUT_SIZE == BIP324_KEY_LEN); + auto hasher = rekey_hasher; + hasher << MakeUCharSpan(data) << MakeUCharSpan(key_P); + auto new_key = hasher.GetSHA256(); + memcpy(key_P.data(), new_key.data(), BIP324_KEY_LEN); + } + + set_nonce(); +} + +bool BIP324CipherSuite::Crypt(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()); + + memcpy(header_and_contents.data(), &flags, BIP324_HEADER_LEN); + if (!input.empty()) { + 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({}, 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({}, key_P, nonce, input, decrypted_header_and_contents); + if (!authenticated) { + return false; + } + + memcpy(&flags, decrypted_header_and_contents.data(), BIP324_HEADER_LEN); + if (!output.empty()) { + 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 key_P. key_L is automatically re-keyed since we're using a forward-secure version + // of ChaCha20, FSChacha20 + CommitToKeys({(std::byte*)nullptr, 0}, false, true); + } + 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..b9acc8afdd --- /dev/null +++ b/src/crypto/bip324_suite.h @@ -0,0 +1,75 @@ +// 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 +#include + +static constexpr size_t BIP324_KEY_LEN = 32; // bytes +static constexpr size_t BIP324_REKEY_SALT_LEN = 23; // 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 = 256; // 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; + HashWriter rekey_hasher; + 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); + } + +public: + BIP324CipherSuite(const BIP324Key& K_L, const BIP324Key& K_P, + const std::array& rekey_salt) + : fsc20{K_L, rekey_salt, REKEY_INTERVAL}, + key_P{K_P} + { + set_nonce(); + rekey_hasher << MakeUCharSpan(rekey_salt); + }; + + // Resets the suite keys to commit to data + void CommitToKeys(const Span data, bool commit_to_L, bool commit_to_P); + + 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 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/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp deleted file mode 100644 index 4a8516b0a8..0000000000 --- a/src/crypto/chacha_poly_aead.cpp +++ /dev/null @@ -1,131 +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. - -#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.h b/src/crypto/rfc8439.h index efc3e2e162..33b543ba4a 100644 --- a/src/crypto/rfc8439.h +++ b/src/crypto/rfc8439.h @@ -13,6 +13,7 @@ #include constexpr static size_t RFC8439_KEYLEN = 32; +constexpr static size_t RFC8439_EXPANSION = POLY1305_TAGLEN; void RFC8439Encrypt(const Span aad, const Span key, const std::array& nonce, const Span plaintext, Span output); diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 881ee3487f..ffbecd0022 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -3,8 +3,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include -#include #include #include #include @@ -28,6 +28,8 @@ #include +static constexpr size_t CHACHA20_ROUND_OUTPUT = 64; // bytes; + BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup) template @@ -831,129 +833,94 @@ 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_contents, const std::string& hex_key_L, const std::string& hex_key_P, const std::string& hex_rekey_salt, 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; + memcpy(key_L.data(), key_L_vec.data(), BIP324_KEY_LEN); + + auto key_P_vec = ParseHex(hex_key_P); + BIP324Key key_P; + memcpy(key_P.data(), key_P_vec.data(), BIP324_KEY_LEN); + + auto rekey_salt_vec = ParseHex(hex_rekey_salt); + std::array rekey_salt; + memcpy(rekey_salt.data(), rekey_salt_vec.data(), BIP324_REKEY_SALT_LEN); + + 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, rekey_salt); + BIP324CipherSuite suite_dec(key_L, key_P, rekey_salt); + + 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(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); + 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({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, 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(/* plaintext */ "", + /* k_l */ "0000000000000000000000000000000000000000000000000000000000000000", + /* k_p */ "0000000000000000000000000000000000000000000000000000000000000000", + /* rekey_salt */ "0000000000000000000000000000000000000000000000", + /* ciphertext_and_mac_0 */ "76b8e09fbedcfd1809ff3c10adf8277fcc0581b8", + /* ciphertext_and_mac_999 */ "66712b97e33e72c0e908f5a7ce99279cb3cb6769"); + + TestBIP324CipherSuite("0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000", + "56b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed29e7e38bb44c94b6a43c525ffca66c79e9", + "46712b9741ee5bde86518fee0ce0778aa97cf58c1ee3c587ab3dce47de77b25f202b4807e074989c86c4bb8493e76cda937e0aad"); + + TestBIP324CipherSuite("0100000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000", + "56b8e09f06e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed2929449b86c1e4e213676824f2c48e5336", + "46712b9740ee5bde86518fee0ce0778aa97cf58c1ee3c587ab3dce47de77b25f202b48079f2cc4249bd112ea04cccf99a211dfdb"); + + TestBIP324CipherSuite("fc0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9", + "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "6f5ef19ed6f1a5e2db2b119494f21d8c2de638a4c6ec3b", + "3940c1184442315c7340b89171039acb48f95287e66e56f7afa7cf00f95044d26fb69d46ac5c16a2d57a1cadc39160644717559e73480734410a3f543c5f231a7d7ed77af2a64681f6a7417283cef85504ac5de9fdea100e6c67ef7a1bfcd888a92a5f1ef2c9074b44b572aa748f29ff61a850ce40470004dff5cc1d926c5abe25ace47a12c5373094a26bab027e008154fb630aa062490b5421e96691a3f79557f7a79e3cfd9100796671ea241703ddf326f113adf1694bbd6e0ca032e16f936e7bfbf174e7ef4af5b53a6a9102e6fa41a8e589290f39a7bc7a6003088c612a43a36c2e9f2e740797ad3a2a1a80e0f67157fb9abc40487077368e94751a266a0b2dac24f0adabd5c6d7ba54316eee951da560", + "bcc270293211abbeac7a26af3f3d200f8ce44de3d3e86cdbf449dcf7fedb7b5a489629deaae0c87471b1331b2fce7aba3dfabf6f1867e1a534cececba0cdc9e6150e92cb145567401f08778eeb646b2a70165061423b30ca21e754d3e0a0db4de59dd74093b0fc0fc78a598d522571525ab172592620f770b3303c65ee4a35504e4991e8f1d8904c9679824140642c70a184b4449d1ffdf11b8bee4e831a5b3d986006f5119a0912bacb939886abcb279be2437ecbf1f56528ef397f6459f0fd895031c7a8a2a815a3e68199dc1a9b0c7fef3df72c470f9e8e5524049e7e712da407a6b8ab9a3c0a4ae40cc187952b1062e646b8aebc2808a381530791e46b7220a1af222022952872decc6ad5fad2a7b242a7"); } BOOST_AUTO_TEST_CASE(countbits_tests) diff --git a/src/test/fuzz/crypto_bip324_suite.cpp b/src/test/fuzz/crypto_bip324_suite.cpp new file mode 100644 index 0000000000..90dbb47491 --- /dev/null +++ b/src/test/fuzz/crypto_bip324_suite.cpp @@ -0,0 +1,66 @@ +// 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()); + 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); + + std::array rekey_salt; + get_key(fdp, rekey_salt); + + BIP324CipherSuite suite(key_L, key_P, rekey_salt); + + 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)}; + 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(in, out, flags, is_encrypt); + }, + [&] { + std::array encrypted_pkt_len; + 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_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(); - }); - } -}