diff --git a/src/Makefile.am b/src/Makefile.am index 53c809c901..6fdf1fba42 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -530,8 +530,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 \ @@ -545,6 +545,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 a39b0abd9d..dff4c52ec3 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -256,12 +256,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 \ @@ -293,6 +294,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/netbase_dns_lookup.cpp \ test/fuzz/node_eviction.cpp \ test/fuzz/p2p_transport_serialization.cpp \ + test/fuzz/p2p_v2_transport_serialization.cpp \ test/fuzz/parse_hd_keypath.cpp \ test/fuzz/parse_numbers.cpp \ test/fuzz/parse_script.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..4f2680793a --- /dev/null +++ b/src/crypto/rfc8439.cpp @@ -0,0 +1,100 @@ +// 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 + +#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..e3c17714dc --- /dev/null +++ b/src/crypto/rfc8439.h @@ -0,0 +1,32 @@ +// 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 +#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); + +void ComputeRFC8439Tag(const std::array& polykey, + Span aad, Span ciphertext, + Span tag_out); + +std::array GetPoly1305Key(ChaCha20& c20); + +#endif // BITCOIN_CRYPTO_RFC8439_H diff --git a/src/net.cpp b/src/net.cpp index 7023cb0f49..11b5e103f0 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,8 @@ static_assert (MAX_BLOCK_RELAY_ONLY_ANCHORS <= static_cast(MAX_BLOCK_REL /** Anchor IP address database file name */ const char* const ANCHORS_DATABASE_FILENAME = "anchors.dat"; +static constexpr uint64_t V2_MAX_CONTENTS_LENGTH = 0x01000000 - 1; // 2^24 - 1 + // How often to dump addresses to peers.dat static constexpr std::chrono::minutes DUMP_PEERS_INTERVAL{15}; @@ -110,6 +113,8 @@ const std::string NET_MESSAGE_TYPE_OTHER = "*other*"; static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8] static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[0:8] static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256("addrcache")[0:8] + +static constexpr uint8_t V2_LONG_MSG_TYPE_LEN = 12; // V2 (BIP324) message type long ids // // Global state variables // @@ -688,7 +693,14 @@ bool CNode::ReceiveMsgBytes(Span msg_bytes, bool& complete) if (m_deserializer->Complete()) { // decompose a transport agnostic CNetMessage from the deserializer bool reject_message{false}; - CNetMessage msg = m_deserializer->GetMessage(time, reject_message); + bool disconnect{false}; + CNetMessage msg = m_deserializer->GetMessage(time, reject_message, disconnect, {}); + + if (disconnect) { + // v2 p2p incorrect MAC tag. Disconnect from peer. + return false; + } + if (reject_message) { // Message deserialization failed. Drop the message but don't disconnect the peer. // store the size of the corrupt message @@ -780,10 +792,15 @@ const uint256& V1TransportDeserializer::GetMessageHash() const return data_hash; } -CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, bool& reject_message) +CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, + bool& reject_message, + bool& disconnect, + Span aad) { // Initialize out parameter reject_message = false; + disconnect = false; + // decompose a single CNetMessage from the TransportDeserializer CNetMessage msg(std::move(vRecv)); @@ -805,6 +822,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds HexStr(Span{hash}.first(CMessageHeader::CHECKSUM_SIZE)), HexStr(hdr.pchChecksum), m_node_id); + // TODO: Should we disconnect the v1 peer in this case? reject_message = true; } else if (!hdr.IsCommandValid()) { LogPrint(BCLog::NET, "Header error: Invalid message type (%s, %u bytes), peer=%d\n", @@ -817,7 +835,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds return msg; } -void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector& header) const +bool V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector& header) const { // create dbl-sha256 checksum uint256 hash = Hash(msg.data); @@ -829,6 +847,182 @@ void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vec // serialize header header.reserve(CMessageHeader::HEADER_SIZE); CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, header, 0, hdr}; + return true; +} + +int V2TransportDeserializer::readHeader(Span pkt_bytes) +{ + // copy data to temporary parsing buffer + const size_t remaining = BIP324_LENGTH_FIELD_LEN - m_hdr_pos; + const size_t copy_bytes = std::min(remaining, pkt_bytes.size()); + + memcpy(&vRecv[m_hdr_pos], pkt_bytes.data(), copy_bytes); + m_hdr_pos += copy_bytes; + + // if we don't have the encrypted length yet, exit + if (m_hdr_pos < BIP324_LENGTH_FIELD_LEN) { + return copy_bytes; + } + + // we have the 3 bytes encrypted packet length at this point + std::array encrypted_pkt_len; + memcpy(encrypted_pkt_len.data(), vRecv.data(), BIP324_LENGTH_FIELD_LEN); + + // the encrypted packet data = bip324 header + contents (message type + message payload) + m_contents_size = m_cipher_suite->DecryptLength(encrypted_pkt_len); + + // m_contents_size is the size of the p2p message + if (m_contents_size > V2_MAX_CONTENTS_LENGTH) { + return -1; + } + + // switch state to reading message data + m_in_data = true; + + return copy_bytes; +} + +int V2TransportDeserializer::readData(Span pkt_bytes) +{ + // Read the BIP324 encrypted packet data. + const size_t remaining = BIP324_HEADER_LEN + m_contents_size + RFC8439_EXPANSION - m_data_pos; + const size_t copy_bytes = std::min(remaining, pkt_bytes.size()); + + // extend buffer, respect previous copied encrypted length + if (vRecv.size() < BIP324_LENGTH_FIELD_LEN + m_data_pos + copy_bytes) { + // Allocate up to 256 KiB ahead, but never more than the total message size. + vRecv.resize(BIP324_LENGTH_FIELD_LEN + std::min(BIP324_HEADER_LEN + m_contents_size, m_data_pos + copy_bytes + 256 * 1024) + RFC8439_EXPANSION, std::byte{0x00}); + } + + memcpy(&vRecv[BIP324_LENGTH_FIELD_LEN + m_data_pos], pkt_bytes.data(), copy_bytes); + m_data_pos += copy_bytes; + + return copy_bytes; +} + +CNetMessage V2TransportDeserializer::GetMessage(const std::chrono::microseconds time, + bool& reject_message, + bool& disconnect, + Span aad) +{ + const size_t min_contents_size = 1; // BIP324 1-byte message type id is the minimum contents + + // Initialize out parameters + reject_message = (vRecv.size() < V2_MIN_PACKET_LENGTH + min_contents_size); + disconnect = false; + + // In v2, vRecv contains: + // 3 bytes of encrypted packet length + // 1-byte encrypted bip324 header + // variable length encrypted contents(message type and message payload) and + // mac tag + assert(Complete()); + + std::string msg_type; + + BIP324HeaderFlags flags; + size_t msg_type_size = 1; // at least one byte needed for message type + if (m_cipher_suite->Crypt(aad, + Span{reinterpret_cast(vRecv.data() + BIP324_LENGTH_FIELD_LEN), BIP324_HEADER_LEN + m_contents_size + RFC8439_EXPANSION}, + Span{reinterpret_cast(vRecv.data()), m_contents_size}, flags, false)) { + // MAC check was successful + vRecv.resize(m_contents_size); + reject_message = reject_message || (BIP324HeaderFlags(BIP324_IGNORE & flags) != BIP324_NONE); + + if (!reject_message) { + uint8_t msg_type_id = NO_BIP324_SHORT_ID; + try { + vRecv >> msg_type_id; + } catch (const std::ios_base::failure&) { + LogPrint(BCLog::NET, "Failed to read message type, peer=%d\n", m_node_id); + reject_message = true; + } + + if (msg_type_id == NO_BIP324_SHORT_ID) { + if (vRecv.size() < V2_LONG_MSG_TYPE_LEN) { + LogPrint(BCLog::NET, "Invalid v2 message type long id, peer=%d\n", m_node_id); + reject_message = true; + } + msg_type.resize(V2_LONG_MSG_TYPE_LEN); + vRecv.read(MakeWritableByteSpan(msg_type)); + auto trim_pos = std::find(msg_type.begin(), msg_type.end(), 0); + if (trim_pos != msg_type.end()) { + msg_type.resize(trim_pos - msg_type.begin()); + } + msg_type_size += V2_LONG_MSG_TYPE_LEN; + } else { + auto mtype = GetMessageTypeFromShortID(msg_type_id); + if (mtype.has_value()) { + msg_type = mtype.value(); + } else { + // unknown-short-id results in a valid but unknown message (will be skipped) + msg_type = "unknown-" + ToString(msg_type_id); + reject_message = true; + } + } + } + } else { + // Invalid mac tag + LogPrint(BCLog::NET, "Invalid v2 mac tag, peer=%d\n", m_node_id); + disconnect = true; + reject_message = true; + } + + // we'll always return a CNetMessage (even if decryption fails) + // decompose a single CNetMessage from the TransportDeserializer + CNetMessage msg(std::move(vRecv)); + msg.m_type = msg_type; + msg.m_time = time; + + if (!reject_message) { + msg.m_message_size = m_contents_size - msg_type_size; + msg.m_raw_message_size = V2_MIN_PACKET_LENGTH + m_contents_size; // raw wire size + } + + Reset(); + return msg; +} + +bool V2TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector& header) const +{ + size_t serialized_msg_type_size = 1; // short-IDs are 1 byte + uint8_t short_msg_type = GetShortIDFromMessageType(msg.m_type); + if (short_msg_type == NO_BIP324_SHORT_ID) { + // message type without an assigned short-ID + assert(msg.m_type.size() <= V2_LONG_MSG_TYPE_LEN); + // encode as NO_BIP324_SHORT_ID || 12-byte long id + serialized_msg_type_size += V2_LONG_MSG_TYPE_LEN; + } + + std::vector msg_type_bytes(serialized_msg_type_size, 0); + + // append the short-ID or the varstr of the msg type + msg_type_bytes[0] = short_msg_type; + if (short_msg_type == NO_BIP324_SHORT_ID) { + // append ASCII command string + memcpy(msg_type_bytes.data() + 1, msg.m_type.data(), msg.m_type.size()); + } + + // insert message type directly into the CSerializedNetMsg data buffer (insert at begin) + // TODO: if we refactor the BIP324CipherSuite::Crypt() function to allow separate buffers for + // the message type and payload we could avoid a insert and thus a potential reallocation + msg.data.insert(msg.data.begin(), msg_type_bytes.begin(), msg_type_bytes.end()); + + auto contents_size = msg.data.size(); + auto encrypted_pkt_size = V2_MIN_PACKET_LENGTH + contents_size; + // resize the message buffer to make space for the MAC tag + msg.data.resize(encrypted_pkt_size, 0); + + BIP324HeaderFlags flags{BIP324_NONE}; + // encrypt the payload, this should always succeed (controlled buffers, don't check the MAC during encrypting) + auto success = m_cipher_suite->Crypt(msg.aad, + Span{reinterpret_cast(msg.data.data()), contents_size}, + Span{reinterpret_cast(msg.data.data()), encrypted_pkt_size}, + flags, true); + if (!success) { + LogPrint(BCLog::NET, "error in v2 p2p encryption for message type: %s\n", msg.m_type); + } + return success; } size_t CConnman::SocketSendData(CNode& node) const @@ -2787,8 +2981,9 @@ CNode::CNode(NodeId idIn, { if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND); - for (const std::string &msg : getAllNetMessageTypes()) - mapRecvBytesPerMsgType[msg] = 0; + for (const auto& type : getAllNetMessageTypes()) { + mapRecvBytesPerMsgType[type.first] = 0; + } mapRecvBytesPerMsgType[NET_MESSAGE_TYPE_OTHER] = 0; if (fLogIPs) { @@ -2854,7 +3049,10 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) // make sure we use the appropriate network transport format std::vector serializedHeader; - pnode->m_serializer->prepareForTransport(msg, serializedHeader); + if (!pnode->m_serializer->prepareForTransport(msg, serializedHeader)) { + return; + } + size_t nTotalSize = nMessageSize + serializedHeader.size(); size_t nBytesSent = 0; diff --git a/src/net.h b/src/net.h index 9b939aea5c..60ba47ecd1 100644 --- a/src/net.h +++ b/src/net.h @@ -9,15 +9,18 @@ #include #include #include -#include #include +#include +#include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -31,6 +34,7 @@ #include #include +#include #include #include #include @@ -91,6 +95,8 @@ static constexpr bool DEFAULT_FIXEDSEEDS{true}; static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000; static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000; +static constexpr size_t V2_MIN_PACKET_LENGTH = BIP324_LENGTH_FIELD_LEN + BIP324_HEADER_LEN + RFC8439_EXPANSION; + typedef int64_t NodeId; struct AddedNodeInfo @@ -121,6 +127,7 @@ struct CSerializedNetMsg { } std::vector data; + std::vector aad; // associated authenticated data for encrypted BIP324 (v2) transport std::string m_type; }; @@ -264,7 +271,10 @@ class TransportDeserializer { /** read and deserialize data, advances msg_bytes data pointer */ virtual int Read(Span& msg_bytes) = 0; // decomposes a message from the context - virtual CNetMessage GetMessage(std::chrono::microseconds time, bool& reject_message) = 0; + virtual CNetMessage GetMessage(std::chrono::microseconds time, + bool& reject_message, + bool& disconnect, + Span aad) = 0; virtual ~TransportDeserializer() {} }; @@ -328,7 +338,71 @@ class V1TransportDeserializer final : public TransportDeserializer } return ret; } - CNetMessage GetMessage(std::chrono::microseconds time, bool& reject_message) override; + CNetMessage GetMessage(std::chrono::microseconds time, + bool& reject_message, + bool& disconnect, + Span aad) override; +}; + +/** V2TransportDeserializer is a transport deserializer after BIP324 */ +class V2TransportDeserializer final : public TransportDeserializer +{ +private: + std::unique_ptr m_cipher_suite; + const NodeId m_node_id; // Only for logging + bool m_in_data = false; // parsing header (false) or data (true) + size_t m_contents_size = 0; // expected message size + CDataStream vRecv; // received message data (encrypted length, encrypted contents and MAC tag) + size_t m_hdr_pos = 0; // read pos in header + size_t m_data_pos = 0; // read pos in data + +public: + V2TransportDeserializer(const NodeId node_id, + const BIP324Key& key_l, + const BIP324Key& key_p) + : m_cipher_suite(new BIP324CipherSuite(key_l, key_p)), + m_node_id(node_id), + vRecv(SER_NETWORK, INIT_PROTO_VERSION) + { + Reset(); + } + + void Reset() + { + vRecv.clear(); + vRecv.resize(BIP324_LENGTH_FIELD_LEN); + m_in_data = false; + m_hdr_pos = 0; + m_contents_size = 0; + m_data_pos = 0; + } + bool Complete() const override + { + if (!m_in_data) { + return false; + } + return (BIP324_HEADER_LEN + m_contents_size + RFC8439_EXPANSION == m_data_pos); + } + void SetVersion(int nVersionIn) override + { + vRecv.SetVersion(nVersionIn); + } + int readHeader(Span pkt_bytes); + int readData(Span pkt_bytes); + int Read(Span& pkt_bytes) override + { + int ret = m_in_data ? readData(pkt_bytes) : readHeader(pkt_bytes); + if (ret < 0) { + Reset(); + } else { + pkt_bytes = pkt_bytes.subspan(ret); + } + return ret; + } + CNetMessage GetMessage(const std::chrono::microseconds time, + bool& reject_message, + bool& disconnect, + Span aad) override; }; /** The TransportSerializer prepares messages for the network transport @@ -336,13 +410,27 @@ class V1TransportDeserializer final : public TransportDeserializer class TransportSerializer { public: // prepare message for transport (header construction, error-correction computation, payload encryption, etc.) - virtual void prepareForTransport(CSerializedNetMsg& msg, std::vector& header) const = 0; + virtual bool prepareForTransport(CSerializedNetMsg& msg, std::vector& header) const = 0; virtual ~TransportSerializer() {} }; -class V1TransportSerializer : public TransportSerializer { +class V1TransportSerializer : public TransportSerializer +{ +public: + bool prepareForTransport(CSerializedNetMsg& msg, std::vector& header) const override; +}; + +class V2TransportSerializer : public TransportSerializer +{ +private: + std::unique_ptr m_cipher_suite; + public: - void prepareForTransport(CSerializedNetMsg& msg, std::vector& header) const override; + V2TransportSerializer(const BIP324Key& key_L, + const BIP324Key& key_P) + : m_cipher_suite(new BIP324CipherSuite(key_L, key_P)) {} + // prepare for next message + bool prepareForTransport(CSerializedNetMsg& msg, std::vector& header) const override; }; struct CNodeOptions diff --git a/src/protocol.cpp b/src/protocol.cpp index aa59bae6ff..c910cef65f 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -47,47 +47,47 @@ const char *WTXIDRELAY="wtxidrelay"; const char *SENDTXRCNCL="sendtxrcncl"; } // namespace NetMsgType -/** All known message types. Keep this in the same order as the list of - * messages above and in protocol.h. +/** All known message types including the short-ID (as initially defined in BIP324). + * Keep this in the same order as the list of messages above and in protocol.h. */ -const static std::string allNetMessageTypes[] = { - NetMsgType::VERSION, - NetMsgType::VERACK, - NetMsgType::ADDR, - NetMsgType::ADDRV2, - NetMsgType::SENDADDRV2, - NetMsgType::INV, - NetMsgType::GETDATA, - NetMsgType::MERKLEBLOCK, - NetMsgType::GETBLOCKS, - NetMsgType::GETHEADERS, - NetMsgType::TX, - NetMsgType::HEADERS, - NetMsgType::BLOCK, - NetMsgType::GETADDR, - NetMsgType::MEMPOOL, - NetMsgType::PING, - NetMsgType::PONG, - NetMsgType::NOTFOUND, - NetMsgType::FILTERLOAD, - NetMsgType::FILTERADD, - NetMsgType::FILTERCLEAR, - NetMsgType::SENDHEADERS, - NetMsgType::FEEFILTER, - NetMsgType::SENDCMPCT, - NetMsgType::CMPCTBLOCK, - NetMsgType::GETBLOCKTXN, - NetMsgType::BLOCKTXN, - NetMsgType::GETCFILTERS, - NetMsgType::CFILTER, - NetMsgType::GETCFHEADERS, - NetMsgType::CFHEADERS, - NetMsgType::GETCFCHECKPT, - NetMsgType::CFCHECKPT, - NetMsgType::WTXIDRELAY, - NetMsgType::SENDTXRCNCL, -}; -const static std::vector allNetMessageTypesVec(std::begin(allNetMessageTypes), std::end(allNetMessageTypes)); +const static std::map allNetMessageTypes = { + {NetMsgType::VERSION, NO_BIP324_SHORT_ID}, + {NetMsgType::VERACK, NO_BIP324_SHORT_ID}, + {NetMsgType::ADDR, 1}, + {NetMsgType::ADDRV2, 28}, + {NetMsgType::SENDADDRV2, NO_BIP324_SHORT_ID}, + {NetMsgType::INV, 14}, + {NetMsgType::GETDATA, 11}, + {NetMsgType::MERKLEBLOCK, 16}, + {NetMsgType::GETBLOCKS, 9}, + {NetMsgType::GETHEADERS, 12}, + {NetMsgType::TX, 21}, + {NetMsgType::HEADERS, 13}, + {NetMsgType::BLOCK, 2}, + {NetMsgType::GETADDR, NO_BIP324_SHORT_ID}, + {NetMsgType::MEMPOOL, 15}, + {NetMsgType::PING, 18}, + {NetMsgType::PONG, 19}, + {NetMsgType::NOTFOUND, 17}, + {NetMsgType::FILTERLOAD, 8}, + {NetMsgType::FILTERADD, 6}, + {NetMsgType::FILTERCLEAR, 7}, + {NetMsgType::SENDHEADERS, NO_BIP324_SHORT_ID}, + {NetMsgType::FEEFILTER, 5}, + {NetMsgType::SENDCMPCT, 20}, + {NetMsgType::CMPCTBLOCK, 4}, + {NetMsgType::GETBLOCKTXN, 10}, + {NetMsgType::BLOCKTXN, 3}, + {NetMsgType::GETCFILTERS, 22}, + {NetMsgType::CFILTER, 23}, + {NetMsgType::GETCFHEADERS, 24}, + {NetMsgType::CFHEADERS, 25}, + {NetMsgType::GETCFCHECKPT, 26}, + {NetMsgType::CFCHECKPT, 27}, + {NetMsgType::WTXIDRELAY, NO_BIP324_SHORT_ID}, + {NetMsgType::SENDTXRCNCL, NO_BIP324_SHORT_ID}}; + +static std::map shortIDMsgTypes; CMessageHeader::CMessageHeader(const MessageStartChars& pchMessageStartIn, const char* pszCommand, unsigned int nMessageSizeIn) { @@ -178,9 +178,9 @@ std::string CInv::ToString() const } } -const std::vector &getAllNetMessageTypes() +const std::map& getAllNetMessageTypes() { - return allNetMessageTypesVec; + return allNetMessageTypes; } /** @@ -222,3 +222,29 @@ GenTxid ToGenTxid(const CInv& inv) assert(inv.IsGenTxMsg()); return inv.IsMsgWtx() ? GenTxid::Wtxid(inv.hash) : GenTxid::Txid(inv.hash); } + +uint8_t GetShortIDFromMessageType(const std::string& message_type) +{ + auto it = allNetMessageTypes.find(message_type); + if (it != allNetMessageTypes.end()) { + return it->second; + } + return NO_BIP324_SHORT_ID; +} + +std::optional GetMessageTypeFromShortID(const uint8_t shortID) +{ + if (shortIDMsgTypes.empty()) { + for (const std::pair entry : allNetMessageTypes) { + if (entry.second != NO_BIP324_SHORT_ID) { + shortIDMsgTypes[entry.second] = entry.first; + } + } + } + + auto it = shortIDMsgTypes.find(shortID); + if (it != shortIDMsgTypes.end()) { + return it->second; + } + return {}; +} diff --git a/src/protocol.h b/src/protocol.h index cbcd400fef..5102d770cd 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -17,6 +17,9 @@ #include #include +// For use with message types that are not allocated BIP324 short message type IDs +static const uint8_t NO_BIP324_SHORT_ID = 0; + /** Message header. * (4) message start. * (12) command. @@ -266,8 +269,17 @@ extern const char* WTXIDRELAY; extern const char* SENDTXRCNCL; }; // namespace NetMsgType -/* Get a vector of all valid message types (see above) */ -const std::vector& getAllNetMessageTypes(); +/* Get a map of all valid message types (see above) */ +const std::map& getAllNetMessageTypes(); + +/** Short message type IDs are a low bandwidth representations of a message type + * The mapping is a peer to peer agreement (initially defined in BIP324) + * + * returns the short ID for a message type if known, else NO_BIP324_SHORT_ID */ +uint8_t GetShortIDFromMessageType(const std::string& message_type); + +/** returns the message type (string) from a short ID (as initially defined in BIP324) */ +std::optional GetMessageTypeFromShortID(const uint8_t shortID); /** nServices flags */ enum ServiceFlags : uint64_t { 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); + } + } +} diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp index 96254aa222..8b23c0110a 100644 --- a/src/test/fuzz/p2p_transport_serialization.cpp +++ b/src/test/fuzz/p2p_transport_serialization.cpp @@ -69,7 +69,8 @@ FUZZ_TARGET_INIT(p2p_transport_serialization, initialize_p2p_transport_serializa if (deserializer.Complete()) { const std::chrono::microseconds m_time{std::numeric_limits::max()}; bool reject_message{false}; - CNetMessage msg = deserializer.GetMessage(m_time, reject_message); + bool disconnect{false}; + CNetMessage msg = deserializer.GetMessage(m_time, reject_message, disconnect, {}); assert(msg.m_type.size() <= CMessageHeader::COMMAND_SIZE); assert(msg.m_raw_message_size <= mutable_msg_bytes.size()); assert(msg.m_raw_message_size == CMessageHeader::HEADER_SIZE + msg.m_message_size); diff --git a/src/test/fuzz/p2p_v2_transport_serialization.cpp b/src/test/fuzz/p2p_v2_transport_serialization.cpp new file mode 100644 index 0000000000..5620d4fe94 --- /dev/null +++ b/src/test/fuzz/p2p_v2_transport_serialization.cpp @@ -0,0 +1,112 @@ +// 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 + +#include +#include +#include + +FUZZ_TARGET(p2p_v2_transport_serialization) +{ + FuzzedDataProvider fdp{buffer.data(), buffer.size()}; + + // Picking constant keys seems to give us higher fuzz test coverage + // The BIP324 Cipher suite is separately fuzzed, so we don't have to + // pick fuzzed keys here. + BIP324Key key_L, key_P; + memset(key_L.data(), 1, BIP324_KEY_LEN); + memset(key_P.data(), 2, BIP324_KEY_LEN); + + // Construct deserializer, with a dummy NodeId + V2TransportDeserializer deserializer{(NodeId)0, key_L, key_P}; + V2TransportSerializer serializer{key_L, key_P}; + FSChaCha20 fsc20{key_L, REKEY_INTERVAL}; + ChaCha20 c20{reinterpret_cast(key_P.data())}; + + std::array nonce; + memset(nonce.data(), 0, 12); + c20.SetRFC8439Nonce(nonce); + + bool length_assist = fdp.ConsumeBool(); + + // There is no sense in providing a mac assist if the length is incorrect. + bool mac_assist = length_assist && fdp.ConsumeBool(); + auto aad = fdp.ConsumeBytes(fdp.ConsumeIntegralInRange(0, 1024)); + auto encrypted_packet = fdp.ConsumeRemainingBytes(); + bool is_decoy_packet{false}; + + if (encrypted_packet.size() >= V2_MIN_PACKET_LENGTH) { + if (length_assist) { + uint32_t contents_len = encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - BIP324_HEADER_LEN - RFC8439_EXPANSION; + contents_len = htole32(contents_len); + fsc20.Crypt({reinterpret_cast(&contents_len), BIP324_LENGTH_FIELD_LEN}, + {reinterpret_cast(encrypted_packet.data()), BIP324_LENGTH_FIELD_LEN}); + } + + if (mac_assist) { + std::array tag; + ComputeRFC8439Tag(GetPoly1305Key(c20), aad, + {reinterpret_cast(encrypted_packet.data()) + BIP324_LENGTH_FIELD_LEN, + encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - RFC8439_EXPANSION}, + tag); + memcpy(encrypted_packet.data() + encrypted_packet.size() - RFC8439_EXPANSION, tag.data(), RFC8439_EXPANSION); + + std::vector dec_header_and_contents( + encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - RFC8439_EXPANSION); + RFC8439Decrypt(aad, key_P, nonce, + {reinterpret_cast(encrypted_packet.data() + BIP324_LENGTH_FIELD_LEN), + encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN}, + dec_header_and_contents); + if (BIP324HeaderFlags((uint8_t)dec_header_and_contents.at(0) & BIP324_IGNORE) != BIP324_NONE) { + is_decoy_packet = true; + } + } + } + + Span pkt_bytes{encrypted_packet}; + while (pkt_bytes.size() > 0) { + const int handled = deserializer.Read(pkt_bytes); + if (handled < 0) { + break; + } + if (deserializer.Complete()) { + const std::chrono::microseconds m_time{std::numeric_limits::max()}; + bool reject_message{true}; + bool disconnect{true}; + CNetMessage result{deserializer.GetMessage(m_time, reject_message, disconnect, aad)}; + + if (mac_assist) { + assert(!disconnect); + } + + if (is_decoy_packet) { + assert(reject_message); + } + + if (!reject_message) { + assert(result.m_type.size() <= CMessageHeader::COMMAND_SIZE); + assert(result.m_raw_message_size <= buffer.size()); + + auto message_type_size = result.m_raw_message_size - V2_MIN_PACKET_LENGTH - result.m_message_size; + assert(message_type_size <= 13); + assert(message_type_size >= 1); + assert(result.m_time == m_time); + + std::vector header; + auto msg = CNetMsgMaker{result.m_recv.GetVersion()}.Make(result.m_type, MakeUCharSpan(result.m_recv)); + msg.aad = aad; + // if decryption succeeds, encryption must succeed + assert(serializer.prepareForTransport(msg, header)); + } + } + } +} diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 4fbd9b3a6e..9507b77b6d 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -905,4 +906,89 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) TestOnlyResetTimeData(); } +void message_serialize_deserialize_test(bool v2, const std::vector& test_msgs) +{ + // use keys with all zeros + BIP324Key key_L, key_P; + memset(key_L.data(), 1, BIP324_KEY_LEN); + memset(key_P.data(), 2, BIP324_KEY_LEN); + + // construct the serializers + std::unique_ptr serializer; + std::unique_ptr deserializer; + + if (v2) { + serializer = std::make_unique(V2TransportSerializer(key_L, key_P)); + deserializer = std::make_unique(V2TransportDeserializer((NodeId)0, key_L, key_P)); + } else { + serializer = std::make_unique(V1TransportSerializer()); + deserializer = std::make_unique(V1TransportDeserializer(Params(), (NodeId)0, SER_NETWORK, INIT_PROTO_VERSION)); + } + // run 100 times through all messages with the same cipher suite instances + for (unsigned int i = 0; i < 100; i++) { + for (const CSerializedNetMsg& msg_orig : test_msgs) { + // bypass the copy protection + CSerializedNetMsg msg; + msg.data = msg_orig.data; + msg.m_type = msg_orig.m_type; + + std::vector serialized_header; + serializer->prepareForTransport(msg, serialized_header); + + // read two times + // first: read header + size_t read_bytes{0}; + Span span_header(serialized_header.data(), serialized_header.size()); + if (serialized_header.size() > 0) read_bytes += deserializer->Read(span_header); + // second: read the encrypted payload (if required) + Span span_msg(msg.data.data(), msg.data.size()); + if (msg.data.size() > 0) read_bytes += deserializer->Read(span_msg); + if (msg.data.size() > read_bytes) { + Span span_msg(msg.data.data() + read_bytes, msg.data.size() - read_bytes); + read_bytes += deserializer->Read(span_msg); + } + // message must be complete + BOOST_CHECK(deserializer->Complete()); + BOOST_CHECK_EQUAL(read_bytes, msg.data.size() + serialized_header.size()); + + bool reject_message{true}; + bool disconnect{true}; + CNetMessage result{deserializer->GetMessage(GetTime(), reject_message, disconnect, {})}; + BOOST_CHECK(!reject_message); + BOOST_CHECK(!disconnect); + BOOST_CHECK_EQUAL(result.m_type, msg_orig.m_type); + BOOST_CHECK_EQUAL(result.m_message_size, msg_orig.data.size()); + if (!msg_orig.data.empty()) { + BOOST_CHECK_EQUAL(0, memcmp(result.m_recv.data(), msg_orig.data.data(), msg_orig.data.size())); + } + } + } +} + +BOOST_AUTO_TEST_CASE(net_v2) +{ + // create some messages where we perform serialization and deserialization + std::vector test_msgs; + test_msgs.push_back(CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERACK)); + test_msgs.push_back(CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (int)NODE_NETWORK, 123, CAddress(CService(), NODE_NONE), CAddress(CService(), NODE_NONE), 123, "foobar", 500000, true)); + test_msgs.push_back(CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::PING, 123456)); + CDataStream stream(ParseHex("020000000001013107ca31e1950a9b44b75ce3e8f30127e4d823ed8add1263a1cc8adcc8e49164000000001716001487835ecf51ea0351ef266d216a7e7a3e74b84b4efeffffff02082268590000000017a9144a94391b99e672b03f56d3f60800ef28bc304c4f8700ca9a3b0000000017a9146d5df9e79f752e3c53fc468db89cafda4f7d00cb87024730440220677de5b11a5617d541ba06a1fa5921ab6b4509f8028b23f18ab8c01c5eb1fcfb02202fe382e6e87653f60ff157aeb3a18fc888736720f27ced546b0b77431edabdb0012102608c772598e9645933a86bcd662a3b939e02fb3e77966c9713db5648d5ba8a0006010000"), SER_NETWORK, PROTOCOL_VERSION); + test_msgs.push_back(CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::TX, CTransaction(deserialize, stream))); + std::vector vInv; + for (unsigned int i = 0; i < 1000; i++) { + vInv.push_back(CInv(MSG_BLOCK, Params().GenesisBlock().GetHash())); + } + test_msgs.push_back(CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::INV, vInv)); + + // add a dummy message + std::string dummy; + for (unsigned int i = 0; i < 100; i++) { + dummy += "020000000001013107ca31e1950a9b44b75ce3e8f30127e4d823ed8add1263a1cc8adcc8e49164000000001716001487835ecf51ea0351ef266d216a7e7a3e74b84b4efeffffff02082268590000000017a9144a94391b99e672b03f56d3f60800ef28bc304c4f8700ca9a3b0000000017a9146d5df9e79f752e3c53fc468db89cafda4f7d00cb87024730440220677de5b11a5617d541ba06a1fa5921ab6b4509f8028b23f18ab8c01c5eb1fcfb02202fe382e6e87653f60ff157aeb3a18fc888736720f27ced546b0b77431edabdb0012102608c772598e9645933a86bcd662a3b939e02fb3e77966c9713db5648d5ba8a0006010000"; + } + test_msgs.push_back(CNetMsgMaker(INIT_PROTO_VERSION).Make("foobar", dummy)); + + message_serialize_deserialize_test(true, test_msgs); + message_serialize_deserialize_test(false, test_msgs); +} + BOOST_AUTO_TEST_SUITE_END()