diff --git a/src/Makefile.am b/src/Makefile.am index e0258a677531..c05f56bfd805 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -138,6 +138,7 @@ BITCOIN_CORE_H = \ base58.h \ batchedlogger.h \ bech32.h \ + bip324.h \ blockencodings.h \ bloom.h \ cachemap.h \ @@ -408,6 +409,7 @@ libbitcoin_server_a_SOURCES = \ addrman.cpp \ banman.cpp \ batchedlogger.cpp \ + bip324.cpp \ blockencodings.cpp \ blockfilter.cpp \ chain.cpp \ @@ -577,10 +579,10 @@ crypto_libbitcoin_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(PIC_FLA crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/aes.cpp \ crypto/aes.h \ - crypto/chacha_poly_aead.h \ - crypto/chacha_poly_aead.cpp \ crypto/chacha20.h \ crypto/chacha20.cpp \ + crypto/chacha20poly1305.h \ + crypto/chacha20poly1305.cpp \ crypto/common.h \ crypto/hkdf_sha256_32.cpp \ crypto/hkdf_sha256_32.h \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 1165988b24e3..8efcee2868ae 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -31,7 +31,6 @@ bench_bench_dash_SOURCES = \ bench/examples.cpp \ bench/rollingbloom.cpp \ bench/chacha20.cpp \ - bench/chacha_poly_aead.cpp \ bench/crypto_hash.cpp \ bench/ccoins_caching.cpp \ bench/gcs_filter.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index a7cd3ceb0b2d..f642dc03cefa 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -83,6 +83,7 @@ BITCOIN_TESTS =\ test/base64_tests.cpp \ test/bech32_tests.cpp \ test/bip32_tests.cpp \ + test/bip324_tests.cpp \ test/block_reward_reallocation_tests.cpp \ test/blockchain_tests.cpp \ test/blockencodings_tests.cpp \ @@ -242,6 +243,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/banman.cpp \ test/fuzz/base_encode_decode.cpp \ test/fuzz/bech32.cpp \ + test/fuzz/bip324.cpp \ test/fuzz/block.cpp \ test/fuzz/block_header.cpp \ test/fuzz/blockfilter.cpp \ @@ -255,7 +257,6 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/crypto_aes256.cpp \ test/fuzz/crypto_aes256cbc.cpp \ test/fuzz/crypto_chacha20.cpp \ - test/fuzz/crypto_chacha20_poly1305_aead.cpp \ test/fuzz/crypto_common.cpp \ test/fuzz/crypto_diff_fuzz_chacha20.cpp \ test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp \ diff --git a/src/bench/chacha20.cpp b/src/bench/chacha20.cpp index f02179bf6a5a..bd20fb971ee6 100644 --- a/src/bench/chacha20.cpp +++ b/src/bench/chacha20.cpp @@ -5,6 +5,7 @@ #include #include +#include /* Number of bytes to process per iteration */ static const uint64_t BUFFER_SIZE_TINY = 64; @@ -13,14 +14,25 @@ static const uint64_t BUFFER_SIZE_LARGE = 1024*1024; static void CHACHA20(benchmark::Bench& bench, size_t buffersize) { - std::vector key(32,0); - ChaCha20 ctx(key.data()); - ctx.SetIV(0); - ctx.Seek64(0); - std::vector in(buffersize,0); - std::vector out(buffersize,0); + std::vector key(32, {}); + ChaCha20 ctx(key); + ctx.Seek({0, 0}, 0); + std::vector in(buffersize, {}); + std::vector out(buffersize, {}); bench.batch(in.size()).unit("byte").run([&] { - ctx.Crypt(in.data(), out.data(), in.size()); + ctx.Crypt(in, out); + }); +} + +static void FSCHACHA20POLY1305(benchmark::Bench& bench, size_t buffersize) +{ + std::vector key(32); + FSChaCha20Poly1305 ctx(key, 224); + std::vector in(buffersize); + std::vector aad; + std::vector out(buffersize + FSChaCha20Poly1305::EXPANSION); + bench.batch(in.size()).unit("byte").run([&] { + ctx.Encrypt(in, aad, out); }); } @@ -39,6 +51,24 @@ static void CHACHA20_1MB(benchmark::Bench& bench) CHACHA20(bench, BUFFER_SIZE_LARGE); } +static void FSCHACHA20POLY1305_64BYTES(benchmark::Bench& bench) +{ + FSCHACHA20POLY1305(bench, BUFFER_SIZE_TINY); +} + +static void FSCHACHA20POLY1305_256BYTES(benchmark::Bench& bench) +{ + FSCHACHA20POLY1305(bench, BUFFER_SIZE_SMALL); +} + +static void FSCHACHA20POLY1305_1MB(benchmark::Bench& bench) +{ + FSCHACHA20POLY1305(bench, BUFFER_SIZE_LARGE); +} + BENCHMARK(CHACHA20_64BYTES); BENCHMARK(CHACHA20_256BYTES); BENCHMARK(CHACHA20_1MB); +BENCHMARK(FSCHACHA20POLY1305_64BYTES); +BENCHMARK(FSCHACHA20POLY1305_256BYTES); +BENCHMARK(FSCHACHA20POLY1305_1MB); diff --git a/src/bench/chacha_poly_aead.cpp b/src/bench/chacha_poly_aead.cpp deleted file mode 100644 index 154e27d0f0f9..000000000000 --- a/src/bench/chacha_poly_aead.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2019-2020 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - - -#include -#include -#include // for the POLY1305_TAGLEN constant -#include - -#include -#include - -/* Number of bytes to process per iteration */ -static constexpr uint64_t BUFFER_SIZE_TINY = 64; -static constexpr uint64_t BUFFER_SIZE_SMALL = 256; -static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024; - -static const unsigned char k1[32] = {0}; -static const unsigned char k2[32] = {0}; - -static ChaCha20Poly1305AEAD aead(k1, 32, k2, 32); - -static void CHACHA20_POLY1305_AEAD(benchmark::Bench& bench, size_t buffersize, bool include_decryption) -{ - std::vector in(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - std::vector out(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - uint64_t seqnr_payload = 0; - uint64_t seqnr_aad = 0; - int aad_pos = 0; - uint32_t len = 0; - bench.batch(buffersize).unit("byte").run([&] { - // encrypt or decrypt the buffer with a static key - const bool crypt_ok_1 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true); - assert(crypt_ok_1); - - if (include_decryption) { - // if we decrypt, include the GetLength - const bool get_length_ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data()); - assert(get_length_ok); - const bool crypt_ok_2 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true); - assert(crypt_ok_2); - } - - // increase main sequence number - seqnr_payload++; - // increase aad position (position in AAD keystream) - aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; - if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { - aad_pos = 0; - seqnr_aad++; - } - if (seqnr_payload + 1 == std::numeric_limits::max()) { - // reuse of nonce+key is okay while benchmarking. - seqnr_payload = 0; - seqnr_aad = 0; - aad_pos = 0; - } - }); -} - -static void CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_TINY, false); -} - -static void CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_SMALL, false); -} - -static void CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_LARGE, false); -} - -static void CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_TINY, true); -} - -static void CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_SMALL, true); -} - -static void CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT(benchmark::Bench& bench) -{ - CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_LARGE, true); -} - -// Add Hash() (dbl-sha256) bench for comparison - -static void HASH(benchmark::Bench& bench, size_t buffersize) -{ - uint8_t hash[CHash256::OUTPUT_SIZE]; - std::vector in(buffersize,0); - bench.batch(in.size()).unit("byte").run([&] { - CHash256().Write(in).Finalize(hash); - }); -} - -static void HASH_64BYTES(benchmark::Bench& bench) -{ - HASH(bench, BUFFER_SIZE_TINY); -} - -static void HASH_256BYTES(benchmark::Bench& bench) -{ - HASH(bench, BUFFER_SIZE_SMALL); -} - -static void HASH_1MB(benchmark::Bench& bench) -{ - HASH(bench, BUFFER_SIZE_LARGE); -} - -BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT); -BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT); -BENCHMARK(HASH_64BYTES); -BENCHMARK(HASH_256BYTES); -BENCHMARK(HASH_1MB); diff --git a/src/bench/poly1305.cpp b/src/bench/poly1305.cpp index 9745d3fcb1bb..9be31a0b7bb2 100644 --- a/src/bench/poly1305.cpp +++ b/src/bench/poly1305.cpp @@ -7,6 +7,8 @@ #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; @@ -14,11 +16,11 @@ static constexpr uint64_t BUFFER_SIZE_LARGE = 1024*1024; static void POLY1305(benchmark::Bench& bench, size_t buffersize) { - std::vector tag(POLY1305_TAGLEN, 0); - std::vector key(POLY1305_KEYLEN, 0); - std::vector in(buffersize, 0); + std::vector tag(Poly1305::TAGLEN, {}); + std::vector key(Poly1305::KEYLEN, {}); + std::vector in(buffersize, {}); bench.batch(in.size()).unit("byte").run([&] { - poly1305_auth(tag.data(), in.data(), in.size(), key.data()); + Poly1305{key}.Update(in).Finalize(tag); }); } diff --git a/src/bip324.cpp b/src/bip324.cpp new file mode 100644 index 000000000000..314e756829fa --- /dev/null +++ b/src/bip324.cpp @@ -0,0 +1,116 @@ +// Copyright (c) 2023 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 + +#include +#include +#include +#include +#include +#include + +BIP324Cipher::BIP324Cipher() noexcept +{ + m_key.MakeNewKey(true); + uint256 entropy = GetRandHash(); + m_our_pubkey = m_key.EllSwiftCreate(MakeByteSpan(entropy)); +} + +BIP324Cipher::BIP324Cipher(const CKey& key, Span ent32) noexcept : + m_key(key) +{ + m_our_pubkey = m_key.EllSwiftCreate(ent32); +} + +BIP324Cipher::BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept : + m_key(key), m_our_pubkey(pubkey) {} + +void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt) noexcept +{ + // Determine salt (fixed string + network magic bytes) + const auto& message_header = Params().MessageStart(); + std::string salt = std::string{"bitcoin_v2_shared_secret"} + std::string(std::begin(message_header), std::end(message_header)); + + // Perform ECDH to derive shared secret. + ECDHSecret ecdh_secret = m_key.ComputeBIP324ECDHSecret(their_pubkey, m_our_pubkey, initiator); + + // Derive encryption keys from shared secret, and initialize stream ciphers and AEADs. + bool side = (initiator != self_decrypt); + CHKDF_HMAC_SHA256_L32 hkdf(UCharCast(ecdh_secret.data()), ecdh_secret.size(), salt); + std::array hkdf_32_okm; + hkdf.Expand32("initiator_L", UCharCast(hkdf_32_okm.data())); + (side ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + hkdf.Expand32("initiator_P", UCharCast(hkdf_32_okm.data())); + (side ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + hkdf.Expand32("responder_L", UCharCast(hkdf_32_okm.data())); + (side ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + hkdf.Expand32("responder_P", UCharCast(hkdf_32_okm.data())); + (side ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + + // Derive garbage terminators from shared secret. + hkdf.Expand32("garbage_terminators", UCharCast(hkdf_32_okm.data())); + std::copy(std::begin(hkdf_32_okm), std::begin(hkdf_32_okm) + GARBAGE_TERMINATOR_LEN, + (initiator ? m_send_garbage_terminator : m_recv_garbage_terminator).begin()); + std::copy(std::end(hkdf_32_okm) - GARBAGE_TERMINATOR_LEN, std::end(hkdf_32_okm), + (initiator ? m_recv_garbage_terminator : m_send_garbage_terminator).begin()); + + // Derive session id from shared secret. + hkdf.Expand32("session_id", UCharCast(m_session_id.data())); + + // Wipe all variables that contain information which could be used to re-derive encryption keys. + memory_cleanse(ecdh_secret.data(), ecdh_secret.size()); + memory_cleanse(hkdf_32_okm.data(), sizeof(hkdf_32_okm)); + memory_cleanse(&hkdf, sizeof(hkdf)); + m_key = CKey(); +} + +void BIP324Cipher::Encrypt(Span contents, Span aad, bool ignore, Span output) noexcept +{ + assert(output.size() == contents.size() + EXPANSION); + + // Encrypt length. + std::byte len[LENGTH_LEN]; + len[0] = std::byte{(uint8_t)(contents.size() & 0xFF)}; + len[1] = std::byte{(uint8_t)((contents.size() >> 8) & 0xFF)}; + len[2] = std::byte{(uint8_t)((contents.size() >> 16) & 0xFF)}; + m_send_l_cipher->Crypt(len, output.first(LENGTH_LEN)); + + // Encrypt plaintext. + std::byte header[HEADER_LEN] = {ignore ? IGNORE_BIT : std::byte{0}}; + m_send_p_cipher->Encrypt(header, contents, aad, output.subspan(LENGTH_LEN)); +} + +uint32_t BIP324Cipher::DecryptLength(Span input) noexcept +{ + assert(input.size() == LENGTH_LEN); + + std::byte buf[LENGTH_LEN]; + // Decrypt length + m_recv_l_cipher->Crypt(input, buf); + // Convert to number. + return uint32_t(buf[0]) + (uint32_t(buf[1]) << 8) + (uint32_t(buf[2]) << 16); +} + +bool BIP324Cipher::Decrypt(Span input, Span aad, bool& ignore, Span contents) noexcept +{ + assert(input.size() + LENGTH_LEN == contents.size() + EXPANSION); + + std::byte header[HEADER_LEN]; + if (!m_recv_p_cipher->Decrypt(input, aad, header, contents)) return false; + + ignore = (header[0] & IGNORE_BIT) == IGNORE_BIT; + return true; +} diff --git a/src/bip324.h b/src/bip324.h new file mode 100644 index 000000000000..0238c479c084 --- /dev/null +++ b/src/bip324.h @@ -0,0 +1,96 @@ +// Copyright (c) 2023 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_BIP324_H +#define BITCOIN_BIP324_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +/** The BIP324 packet cipher, encapsulating its key derivation, stream cipher, and AEAD. */ +class BIP324Cipher +{ +public: + static constexpr unsigned SESSION_ID_LEN{32}; + static constexpr unsigned GARBAGE_TERMINATOR_LEN{16}; + static constexpr unsigned REKEY_INTERVAL{224}; + static constexpr unsigned LENGTH_LEN{3}; + static constexpr unsigned HEADER_LEN{1}; + static constexpr unsigned EXPANSION = LENGTH_LEN + HEADER_LEN + FSChaCha20Poly1305::EXPANSION; + static constexpr std::byte IGNORE_BIT{0x80}; + +private: + std::optional m_send_l_cipher; + std::optional m_recv_l_cipher; + std::optional m_send_p_cipher; + std::optional m_recv_p_cipher; + + CKey m_key; + EllSwiftPubKey m_our_pubkey; + + std::array m_session_id; + std::array m_send_garbage_terminator; + std::array m_recv_garbage_terminator; + +public: + /** Initialize a BIP324 cipher with securely generated random keys. */ + BIP324Cipher() noexcept; + + /** Initialize a BIP324 cipher with specified key and encoding entropy (testing only). */ + BIP324Cipher(const CKey& key, Span ent32) noexcept; + + /** Initialize a BIP324 cipher with specified key (testing only). */ + BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept; + + /** Retrieve our public key. */ + const EllSwiftPubKey& GetOurPubKey() const noexcept { return m_our_pubkey; } + + /** Initialize when the other side's public key is received. Can only be called once. + * + * initiator is set to true if we are the initiator establishing the v2 P2P connection. + * self_decrypt is only for testing, and swaps encryption/decryption keys, so that encryption + * and decryption can be tested without knowing the other side's private key. + */ + void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt = false) noexcept; + + /** Determine whether this cipher is fully initialized. */ + explicit operator bool() const noexcept { return m_send_l_cipher.has_value(); } + + /** Encrypt a packet. Only after Initialize(). + * + * It must hold that output.size() == contents.size() + EXPANSION. + */ + void Encrypt(Span contents, Span aad, bool ignore, Span output) noexcept; + + /** Decrypt the length of a packet. Only after Initialize(). + * + * It must hold that input.size() == LENGTH_LEN. + */ + unsigned DecryptLength(Span input) noexcept; + + /** Decrypt a packet. Only after Initialize(). + * + * It must hold that input.size() + LENGTH_LEN == contents.size() + EXPANSION. + * Contents.size() must equal the length returned by DecryptLength. + */ + bool Decrypt(Span input, Span aad, bool& ignore, Span contents) noexcept; + + /** Get the Session ID. Only after Initialize(). */ + Span GetSessionID() const noexcept { return m_session_id; } + + /** Get the Garbage Terminator to send. Only after Initialize(). */ + Span GetSendGarbageTerminator() const noexcept { return m_send_garbage_terminator; } + + /** Get the expected Garbage Terminator to receive. Only after Initialize(). */ + Span GetReceiveGarbageTerminator() const noexcept { return m_recv_garbage_terminator; } +}; + +#endif // BITCOIN_BIP324_H diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index c917b70da73d..517c1d08f9fb 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -7,6 +7,8 @@ #include #include +#include +#include #include #include @@ -21,46 +23,47 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | ( #define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0) -void ChaCha20Aligned::SetKey32(const unsigned char* k) +void ChaCha20Aligned::SetKey(Span key) noexcept { - input[0] = ReadLE32(k + 0); - input[1] = ReadLE32(k + 4); - input[2] = ReadLE32(k + 8); - input[3] = ReadLE32(k + 12); - input[4] = ReadLE32(k + 16); - input[5] = ReadLE32(k + 20); - input[6] = ReadLE32(k + 24); - input[7] = ReadLE32(k + 28); + assert(key.size() == KEYLEN); + input[0] = ReadLE32(UCharCast(key.data() + 0)); + input[1] = ReadLE32(UCharCast(key.data() + 4)); + input[2] = ReadLE32(UCharCast(key.data() + 8)); + input[3] = ReadLE32(UCharCast(key.data() + 12)); + input[4] = ReadLE32(UCharCast(key.data() + 16)); + input[5] = ReadLE32(UCharCast(key.data() + 20)); + input[6] = ReadLE32(UCharCast(key.data() + 24)); + input[7] = ReadLE32(UCharCast(key.data() + 28)); input[8] = 0; input[9] = 0; input[10] = 0; input[11] = 0; } -ChaCha20Aligned::ChaCha20Aligned() +ChaCha20Aligned::~ChaCha20Aligned() { - memset(input, 0, sizeof(input)); + memory_cleanse(input, sizeof(input)); } -ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32) +ChaCha20Aligned::ChaCha20Aligned(Span key) noexcept { - SetKey32(key32); + SetKey(key); } -void ChaCha20Aligned::SetIV(uint64_t iv) +void ChaCha20Aligned::Seek(Nonce96 nonce, uint32_t block_counter) noexcept { - input[10] = iv; - input[11] = iv >> 32; + input[8] = block_counter; + input[9] = nonce.first; + input[10] = nonce.second; + input[11] = nonce.second >> 32; } -void ChaCha20Aligned::Seek64(uint64_t pos) +inline void ChaCha20Aligned::Keystream(Span output) noexcept { - input[8] = pos; - input[9] = pos >> 32; -} + unsigned char* c = UCharCast(output.data()); + size_t blocks = output.size() / BLOCKLEN; + assert(blocks * BLOCKLEN == output.size()); -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; uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; @@ -152,12 +155,18 @@ inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) return; } blocks -= 1; - c += 64; + c += BLOCKLEN; } } -inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks) +inline void ChaCha20Aligned::Crypt(Span in_bytes, Span out_bytes) noexcept { + assert(in_bytes.size() == out_bytes.size()); + const unsigned char* m = UCharCast(in_bytes.data()); + unsigned char* c = UCharCast(out_bytes.data()); + size_t blocks = out_bytes.size() / BLOCKLEN; + assert(blocks * BLOCKLEN == out_bytes.size()); + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; @@ -266,59 +275,99 @@ inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, s return; } blocks -= 1; - c += 64; - m += 64; + c += BLOCKLEN; + m += BLOCKLEN; } } -void ChaCha20::Keystream(unsigned char* c, size_t bytes) +void ChaCha20::Keystream(Span out) noexcept { - if (!bytes) return; + if (out.empty()) return; if (m_bufleft) { - unsigned reuse = std::min(m_bufleft, bytes); - memcpy(c, m_buffer + 64 - m_bufleft, reuse); + unsigned reuse = std::min(m_bufleft, out.size()); + std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin()); m_bufleft -= reuse; - bytes -= reuse; - c += reuse; + out = out.subspan(reuse); } - if (bytes >= 64) { - size_t blocks = bytes / 64; - m_aligned.Keystream64(c, blocks); - c += blocks * 64; - bytes -= blocks * 64; + if (out.size() >= m_aligned.BLOCKLEN) { + size_t blocks = out.size() / m_aligned.BLOCKLEN; + m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN)); + out = out.subspan(blocks * m_aligned.BLOCKLEN); } - if (bytes) { - m_aligned.Keystream64(m_buffer, 1); - memcpy(c, m_buffer, bytes); - m_bufleft = 64 - bytes; + if (!out.empty()) { + m_aligned.Keystream(m_buffer); + std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin()); + m_bufleft = m_aligned.BLOCKLEN - out.size(); } } -void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) +void ChaCha20::Crypt(Span input, Span output) noexcept { - if (!bytes) return; + assert(input.size() == output.size()); + + if (!input.size()) return; if (m_bufleft) { - unsigned reuse = std::min(m_bufleft, bytes); + unsigned reuse = std::min(m_bufleft, input.size()); for (unsigned i = 0; i < reuse; i++) { - c[i] = m[i] ^ m_buffer[64 - m_bufleft + i]; + output[i] = input[i] ^ m_buffer[m_aligned.BLOCKLEN - m_bufleft + i]; } m_bufleft -= reuse; - bytes -= reuse; - c += reuse; - m += reuse; + output = output.subspan(reuse); + input = input.subspan(reuse); } - if (bytes >= 64) { - size_t blocks = bytes / 64; - m_aligned.Crypt64(m, c, blocks); - c += blocks * 64; - m += blocks * 64; - bytes -= blocks * 64; + if (input.size() >= m_aligned.BLOCKLEN) { + size_t blocks = input.size() / m_aligned.BLOCKLEN; + m_aligned.Crypt(input.first(blocks * m_aligned.BLOCKLEN), output.first(blocks * m_aligned.BLOCKLEN)); + output = output.subspan(blocks * m_aligned.BLOCKLEN); + input = input.subspan(blocks * m_aligned.BLOCKLEN); } - if (bytes) { - m_aligned.Keystream64(m_buffer, 1); - for (unsigned i = 0; i < bytes; i++) { - c[i] = m[i] ^ m_buffer[i]; + if (!input.empty()) { + m_aligned.Keystream(m_buffer); + for (unsigned i = 0; i < input.size(); i++) { + output[i] = input[i] ^ m_buffer[i]; } - m_bufleft = 64 - bytes; + m_bufleft = m_aligned.BLOCKLEN - input.size(); + } +} + +ChaCha20::~ChaCha20() +{ + memory_cleanse(m_buffer.data(), m_buffer.size()); +} + +void ChaCha20::SetKey(Span key) noexcept +{ + m_aligned.SetKey(key); + m_bufleft = 0; + memory_cleanse(m_buffer.data(), m_buffer.size()); +} + +FSChaCha20::FSChaCha20(Span key, uint32_t rekey_interval) noexcept : + m_chacha20(key), m_rekey_interval(rekey_interval) +{ + assert(key.size() == KEYLEN); +} + +void FSChaCha20::Crypt(Span input, Span output) noexcept +{ + assert(input.size() == output.size()); + + // Invoke internal stream cipher for actual encryption/decryption. + m_chacha20.Crypt(input, output); + + // Rekey after m_rekey_interval encryptions/decryptions. + if (++m_chunk_counter == m_rekey_interval) { + // Get new key from the stream cipher. + std::byte new_key[KEYLEN]; + m_chacha20.Keystream(new_key); + // Update its key. + m_chacha20.SetKey(new_key); + // Wipe the key (a copy remains inside m_chacha20, where it'll be wiped on the next rekey + // or on destruction). + memory_cleanse(new_key, sizeof(new_key)); + // Set the nonce for the new section of output. + m_chacha20.Seek({0, ++m_rekey_counter}, 0); + // Reset the chunk counter. + m_chunk_counter = 0; } } diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 87a626431ed3..b5ad5d2b8b5b 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -5,11 +5,21 @@ #ifndef BITCOIN_CRYPTO_CHACHA20_H #define BITCOIN_CRYPTO_CHACHA20_H +#include + +#include +#include #include #include +#include // classes for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein -// https://cr.yp.to/chacha/chacha-20080128.pdf */ +// https://cr.yp.to/chacha/chacha-20080128.pdf. +// +// The 128-bit input is here implemented as a 96-bit nonce and a 32-bit block +// counter, as in RFC8439 Section 2.3. When the 32-bit block counter overflows +// the first 32-bit part of the nonce is automatically incremented, making it +// conceptually compatible with variants that use a 64/64 split instead. /** ChaCha20 cipher that only operates on multiples of 64 bytes. */ class ChaCha20Aligned @@ -18,27 +28,49 @@ class ChaCha20Aligned uint32_t input[12]; public: - ChaCha20Aligned(); + /** Expected key length in constructor and SetKey. */ + static constexpr unsigned KEYLEN{32}; + + /** Block size (inputs/outputs to Keystream / Crypt should be multiples of this). */ + static constexpr unsigned BLOCKLEN{64}; + + /** For safety, disallow initialization without key. */ + ChaCha20Aligned() noexcept = delete; /** Initialize a cipher with specified 32-byte key. */ - ChaCha20Aligned(const unsigned char* key32); + ChaCha20Aligned(Span key) noexcept; - /** set 32-byte key. */ - void SetKey32(const unsigned char* key32); + /** Destructor to clean up private memory. */ + ~ChaCha20Aligned(); - /** set the 64-bit nonce. */ - void SetIV(uint64_t iv); + /** Set 32-byte key, and seek to nonce 0 and block position 0. */ + void SetKey(Span key) noexcept; - /** set the 64bit block counter (pos seeks to byte position 64*pos). */ - void Seek64(uint64_t pos); + /** Type for 96-bit nonces used by the Set function below. + * + * The first field corresponds to the LE32-encoded first 4 bytes of the nonce, also referred + * to as the '32-bit fixed-common part' in Example 2.8.2 of RFC8439. + * + * The second field corresponds to the LE64-encoded last 8 bytes of the nonce. + * + */ + using Nonce96 = std::pair; - /** outputs the keystream of size <64*blocks> into */ - void Keystream64(unsigned char* c, size_t blocks); + /** Set the 96-bit nonce and 32-bit block counter. + * + * Block_counter selects a position to seek to (to byte BLOCKLEN*block_counter). After 256 GiB, + * the block counter overflows, and nonce.first is incremented. + */ + void Seek(Nonce96 nonce, uint32_t block_counter) noexcept; + + /** outputs the keystream into out, whose length must be a multiple of BLOCKLEN. */ + void Keystream(Span out) noexcept; - /** enciphers the message of length <64*blocks> and write the enciphered representation into - * Used for encryption and decryption (XOR) + /** en/deciphers the message and write the result into + * + * The size of input and output must be equal, and be a multiple of BLOCKLEN. */ - void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks); + void Crypt(Span input, Span output) noexcept; }; /** Unrestricted ChaCha20 cipher. */ @@ -46,39 +78,82 @@ class ChaCha20 { private: ChaCha20Aligned m_aligned; - unsigned char m_buffer[64] = {0}; + std::array m_buffer; unsigned m_bufleft{0}; public: - ChaCha20() = default; + /** Expected key length in constructor and SetKey. */ + static constexpr unsigned KEYLEN = ChaCha20Aligned::KEYLEN; + + /** For safety, disallow initialization without key. */ + ChaCha20() noexcept = delete; /** Initialize a cipher with specified 32-byte key. */ - ChaCha20(const unsigned char* key32) : m_aligned(key32) {} + ChaCha20(Span key) noexcept : m_aligned(key) {} - /** set 32-byte key. */ - void SetKey32(const unsigned char* key32) - { - m_aligned.SetKey32(key32); - m_bufleft = 0; - } + /** Destructor to clean up private memory. */ + ~ChaCha20(); - /** set the 64-bit nonce. */ - void SetIV(uint64_t iv) { m_aligned.SetIV(iv); } + /** Set 32-byte key, and seek to nonce 0 and block position 0. */ + void SetKey(Span key) noexcept; - /** set the 64bit block counter (pos seeks to byte position 64*pos). */ - void Seek64(uint64_t pos) + /** 96-bit nonce type. */ + using Nonce96 = ChaCha20Aligned::Nonce96; + + /** Set the 96-bit nonce and 32-bit block counter. See ChaCha20Aligned::Seek. */ + void Seek(Nonce96 nonce, uint32_t block_counter) noexcept { - m_aligned.Seek64(pos); + m_aligned.Seek(nonce, block_counter); m_bufleft = 0; } - /** outputs the keystream of size into */ - void Keystream(unsigned char* c, size_t bytes); - - /** enciphers the message of length and write the enciphered representation into - * Used for encryption and decryption (XOR) + /** en/deciphers the message and write the result into + * + * The size of in_bytes and out_bytes must be equal. */ - void Crypt(const unsigned char* input, unsigned char* output, size_t bytes); + void Crypt(Span in_bytes, Span out_bytes) noexcept; + + /** outputs the keystream to out. */ + void Keystream(Span out) noexcept; +}; + +/** Forward-secure ChaCha20 + * + * This implements a stream cipher that automatically transitions to a new stream with a new key + * and new nonce after a predefined number of encryptions or decryptions. + * + * See BIP324 for details. + */ +class FSChaCha20 +{ +private: + /** Internal stream cipher. */ + ChaCha20 m_chacha20; + + /** The number of encryptions/decryptions before a rekey happens. */ + const uint32_t m_rekey_interval; + + /** The number of encryptions/decryptions since the last rekey. */ + uint32_t m_chunk_counter{0}; + + /** The number of rekey operations that have happened. */ + uint64_t m_rekey_counter{0}; + +public: + /** Length of keys expected by the constructor. */ + static constexpr unsigned KEYLEN = 32; + + // No copy or move to protect the secret. + FSChaCha20(const FSChaCha20&) = delete; + FSChaCha20(FSChaCha20&&) = delete; + FSChaCha20& operator=(const FSChaCha20&) = delete; + FSChaCha20& operator=(FSChaCha20&&) = delete; + + /** Construct an FSChaCha20 cipher that rekeys every rekey_interval Crypt() calls. */ + FSChaCha20(Span key, uint32_t rekey_interval) noexcept; + + /** Encrypt or decrypt a chunk. */ + void Crypt(Span input, Span output) noexcept; }; #endif // BITCOIN_CRYPTO_CHACHA20_H diff --git a/src/crypto/chacha20poly1305.cpp b/src/crypto/chacha20poly1305.cpp new file mode 100644 index 000000000000..59671d304c44 --- /dev/null +++ b/src/crypto/chacha20poly1305.cpp @@ -0,0 +1,140 @@ +// Copyright (c) 2023 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 + +AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span key) noexcept : m_chacha20(key) +{ + assert(key.size() == KEYLEN); +} + +void AEADChaCha20Poly1305::SetKey(Span key) noexcept +{ + assert(key.size() == KEYLEN); + m_chacha20.SetKey(key); +} + +namespace { + +#ifndef HAVE_TIMINGSAFE_BCMP +#define HAVE_TIMINGSAFE_BCMP + +int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) noexcept +{ + const unsigned char *p1 = b1, *p2 = b2; + int ret = 0; + for (; n > 0; n--) + ret |= *p1++ ^ *p2++; + return (ret != 0); +} + +#endif + +/** Compute poly1305 tag. chacha20 must be set to the right nonce, block 0. Will be at block 1 after. */ +void ComputeTag(ChaCha20& chacha20, Span aad, Span cipher, Span tag) noexcept +{ + static const std::byte PADDING[16] = {{}}; + + // Get block of keystream (use a full 64 byte buffer to avoid the need for chacha20's own buffering). + std::byte first_block[ChaCha20Aligned::BLOCKLEN]; + chacha20.Keystream(first_block); + + // Use the first 32 bytes of the first keystream block as poly1305 key. + Poly1305 poly1305{Span{first_block}.first(Poly1305::KEYLEN)}; + + // Compute tag: + // - Process the padded AAD with Poly1305. + const unsigned aad_padding_length = (16 - (aad.size() % 16)) % 16; + poly1305.Update(aad).Update(Span{PADDING}.first(aad_padding_length)); + // - Process the padded ciphertext with Poly1305. + const unsigned cipher_padding_length = (16 - (cipher.size() % 16)) % 16; + poly1305.Update(cipher).Update(Span{PADDING}.first(cipher_padding_length)); + // - Process the AAD and plaintext length with Poly1305. + std::byte length_desc[Poly1305::TAGLEN]; + WriteLE64(UCharCast(length_desc), aad.size()); + WriteLE64(UCharCast(length_desc + 8), cipher.size()); + poly1305.Update(length_desc); + + // Output tag. + poly1305.Finalize(tag); +} + +} // namespace + +void AEADChaCha20Poly1305::Encrypt(Span plain1, Span plain2, Span aad, Nonce96 nonce, Span cipher) noexcept +{ + assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION); + + // Encrypt using ChaCha20 (starting at block 1). + m_chacha20.Seek(nonce, 1); + m_chacha20.Crypt(plain1, cipher.first(plain1.size())); + m_chacha20.Crypt(plain2, cipher.subspan(plain1.size()).first(plain2.size())); + + // Seek to block 0, and compute tag using key drawn from there. + m_chacha20.Seek(nonce, 0); + ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION)); +} + +bool AEADChaCha20Poly1305::Decrypt(Span cipher, Span aad, Nonce96 nonce, Span plain1, Span plain2) noexcept +{ + assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION); + + // Verify tag (using key drawn from block 0). + m_chacha20.Seek(nonce, 0); + std::byte expected_tag[EXPANSION]; + ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag); + if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.last(EXPANSION).data()), EXPANSION)) return false; + + // Decrypt (starting at block 1). + m_chacha20.Crypt(cipher.first(plain1.size()), plain1); + m_chacha20.Crypt(cipher.subspan(plain1.size()).first(plain2.size()), plain2); + return true; +} + +void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span keystream) noexcept +{ + // Skip the first output block, as it's used for generating the poly1305 key. + m_chacha20.Seek(nonce, 1); + m_chacha20.Keystream(keystream); +} + +void FSChaCha20Poly1305::NextPacket() noexcept +{ + if (++m_packet_counter == m_rekey_interval) { + // Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though + // we only need KEYLEN (32) bytes. + std::byte one_block[ChaCha20Aligned::BLOCKLEN]; + m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block); + // Switch keys. + m_aead.SetKey(Span{one_block}.first(KEYLEN)); + // Wipe the generated keystream (a copy remains inside m_aead, which will be cleaned up + // once it cycles again, or is destroyed). + memory_cleanse(one_block, sizeof(one_block)); + // Update counters. + m_packet_counter = 0; + ++m_rekey_counter; + } +} + +void FSChaCha20Poly1305::Encrypt(Span plain1, Span plain2, Span aad, Span cipher) noexcept +{ + m_aead.Encrypt(plain1, plain2, aad, {m_packet_counter, m_rekey_counter}, cipher); + NextPacket(); +} + +bool FSChaCha20Poly1305::Decrypt(Span cipher, Span aad, Span plain1, Span plain2) noexcept +{ + bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain1, plain2); + NextPacket(); + return ret; +} diff --git a/src/crypto/chacha20poly1305.h b/src/crypto/chacha20poly1305.h new file mode 100644 index 000000000000..a847c258ef15 --- /dev/null +++ b/src/crypto/chacha20poly1305.h @@ -0,0 +1,148 @@ +// Copyright (c) 2023 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_CHACHA20POLY1305_H +#define BITCOIN_CRYPTO_CHACHA20POLY1305_H + +#include +#include + +#include +#include +#include + +/** The AEAD_CHACHA20_POLY1305 authenticated encryption algorithm from RFC8439 section 2.8. */ +class AEADChaCha20Poly1305 +{ + /** Internal stream cipher. */ + ChaCha20 m_chacha20; + +public: + /** Expected size of key argument in constructor. */ + static constexpr unsigned KEYLEN = 32; + + /** Expansion when encrypting. */ + static constexpr unsigned EXPANSION = Poly1305::TAGLEN; + + /** Initialize an AEAD instance with a specified 32-byte key. */ + AEADChaCha20Poly1305(Span key) noexcept; + + /** Switch to another 32-byte key. */ + void SetKey(Span key) noexcept; + + /** 96-bit nonce type. */ + using Nonce96 = ChaCha20::Nonce96; + + /** Encrypt a message with a specified 96-bit nonce and aad. + * + * Requires cipher.size() = plain.size() + EXPANSION. + */ + void Encrypt(Span plain, Span aad, Nonce96 nonce, Span cipher) noexcept + { + Encrypt(plain, {}, aad, nonce, cipher); + } + + /** Encrypt a message (given split into plain1 + plain2) with a specified 96-bit nonce and aad. + * + * Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION. + */ + void Encrypt(Span plain1, Span plain2, Span aad, Nonce96 nonce, Span cipher) noexcept; + + /** Decrypt a message with a specified 96-bit nonce and aad. Returns true if valid. + * + * Requires cipher.size() = plain.size() + EXPANSION. + */ + bool Decrypt(Span cipher, Span aad, Nonce96 nonce, Span plain) noexcept + { + return Decrypt(cipher, aad, nonce, plain, {}); + } + + /** Decrypt a message with a specified 96-bit nonce and aad and split the result. Returns true if valid. + * + * Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION. + */ + bool Decrypt(Span cipher, Span aad, Nonce96 nonce, Span plain1, Span plain2) noexcept; + + /** Get a number of keystream bytes from the underlying stream cipher. + * + * This is equivalent to Encrypt() with plain set to that many zero bytes, and dropping the + * last EXPANSION bytes off the result. + */ + void Keystream(Nonce96 nonce, Span keystream) noexcept; +}; + +/** Forward-secure wrapper around AEADChaCha20Poly1305. + * + * This implements an AEAD which automatically increments the nonce on every encryption or + * decryption, and cycles keys after a predetermined number of encryptions or decryptions. + * + * See BIP324 for details. + */ +class FSChaCha20Poly1305 +{ +private: + /** Internal AEAD. */ + AEADChaCha20Poly1305 m_aead; + + /** Every how many iterations this cipher rekeys. */ + const uint32_t m_rekey_interval; + + /** The number of encryptions/decryptions since the last rekey. */ + uint32_t m_packet_counter{0}; + + /** The number of rekeys performed so far. */ + uint64_t m_rekey_counter{0}; + + /** Update counters (and if necessary, key) to transition to the next message. */ + void NextPacket() noexcept; + +public: + /** Length of keys expected by the constructor. */ + static constexpr auto KEYLEN = AEADChaCha20Poly1305::KEYLEN; + + /** Expansion when encrypting. */ + static constexpr auto EXPANSION = AEADChaCha20Poly1305::EXPANSION; + + // No copy or move to protect the secret. + FSChaCha20Poly1305(const FSChaCha20Poly1305&) = delete; + FSChaCha20Poly1305(FSChaCha20Poly1305&&) = delete; + FSChaCha20Poly1305& operator=(const FSChaCha20Poly1305&) = delete; + FSChaCha20Poly1305& operator=(FSChaCha20Poly1305&&) = delete; + + /** Construct an FSChaCha20Poly1305 cipher that rekeys every rekey_interval operations. */ + FSChaCha20Poly1305(Span key, uint32_t rekey_interval) noexcept : + m_aead(key), m_rekey_interval(rekey_interval) {} + + /** Encrypt a message with a specified aad. + * + * Requires cipher.size() = plain.size() + EXPANSION. + */ + void Encrypt(Span plain, Span aad, Span cipher) noexcept + { + Encrypt(plain, {}, aad, cipher); + } + + /** Encrypt a message (given split into plain1 + plain2) with a specified aad. + * + * Requires cipher.size() = plain.size() + EXPANSION. + */ + void Encrypt(Span plain1, Span plain2, Span aad, Span cipher) noexcept; + + /** Decrypt a message with a specified aad. Returns true if valid. + * + * Requires cipher.size() = plain.size() + EXPANSION. + */ + bool Decrypt(Span cipher, Span aad, Span plain) noexcept + { + return Decrypt(cipher, aad, plain, {}); + } + + /** Decrypt a message with a specified aad and split the result. Returns true if valid. + * + * Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION. + */ + bool Decrypt(Span cipher, Span aad, Span plain1, Span plain2) noexcept; +}; + +#endif // BITCOIN_CRYPTO_CHACHA20POLY1305_H diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp deleted file mode 100644 index 722f89cdfb7d..000000000000 --- a/src/crypto/chacha_poly_aead.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) 2019 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 - -#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 6a7998335d48..000000000000 --- a/src/crypto/chacha_poly_aead.h +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2019 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/muhash.cpp b/src/crypto/muhash.cpp index aca1dcc8fd0b..c249ebc2d77d 100644 --- a/src/crypto/muhash.cpp +++ b/src/crypto/muhash.cpp @@ -299,7 +299,8 @@ Num3072 MuHash3072::ToNum3072(Span in) { unsigned char tmp[Num3072::BYTE_SIZE]; uint256 hashed_in = (CHashWriter(SER_DISK, 0) << in).GetSHA256(); - ChaCha20Aligned(hashed_in.data()).Keystream64(tmp, Num3072::BYTE_SIZE / 64); + static_assert(sizeof(tmp) % ChaCha20Aligned::BLOCKLEN == 0); + ChaCha20Aligned{MakeByteSpan(hashed_in)}.Keystream(MakeWritableByteSpan(tmp)); Num3072 out{tmp}; return out; diff --git a/src/crypto/poly1305.cpp b/src/crypto/poly1305.cpp index 8a86c9601ce7..7fcbab4108de 100644 --- a/src/crypto/poly1305.cpp +++ b/src/crypto/poly1305.cpp @@ -2,140 +2,222 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -// Based on the public domain implementation by Andrew Moon -// poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna - #include #include #include -#define mul32x32_64(a,b) ((uint64_t)(a) * (b)) +namespace poly1305_donna { -void poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) { - uint32_t t0,t1,t2,t3; - uint32_t h0,h1,h2,h3,h4; +// Based on the public domain implementation by Andrew Moon +// poly1305-donna-32.h from https://github.com/floodyberry/poly1305-donna + +void poly1305_init(poly1305_context *st, const unsigned char key[32]) noexcept { + /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */ + st->r[0] = (ReadLE32(&key[ 0]) ) & 0x3ffffff; + st->r[1] = (ReadLE32(&key[ 3]) >> 2) & 0x3ffff03; + st->r[2] = (ReadLE32(&key[ 6]) >> 4) & 0x3ffc0ff; + st->r[3] = (ReadLE32(&key[ 9]) >> 6) & 0x3f03fff; + st->r[4] = (ReadLE32(&key[12]) >> 8) & 0x00fffff; + + /* h = 0 */ + st->h[0] = 0; + st->h[1] = 0; + st->h[2] = 0; + st->h[3] = 0; + st->h[4] = 0; + + /* save pad for later */ + st->pad[0] = ReadLE32(&key[16]); + st->pad[1] = ReadLE32(&key[20]); + st->pad[2] = ReadLE32(&key[24]); + st->pad[3] = ReadLE32(&key[28]); + + st->leftover = 0; + st->final = 0; +} + +static void poly1305_blocks(poly1305_context *st, const unsigned char *m, size_t bytes) noexcept { + const uint32_t hibit = (st->final) ? 0 : (1UL << 24); /* 1 << 128 */ uint32_t r0,r1,r2,r3,r4; uint32_t s1,s2,s3,s4; - uint32_t b, nb; - size_t j; - uint64_t t[5]; - uint64_t f0,f1,f2,f3; - uint64_t g0,g1,g2,g3,g4; - uint64_t c; - unsigned char mp[16]; - - /* clamp key */ - t0 = ReadLE32(key+0); - t1 = ReadLE32(key+4); - t2 = ReadLE32(key+8); - t3 = ReadLE32(key+12); - - /* precompute multipliers */ - r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6; - r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12; - r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18; - r3 = t2 & 0x3f03fff; t3 >>= 8; - r4 = t3 & 0x00fffff; + uint32_t h0,h1,h2,h3,h4; + uint64_t d0,d1,d2,d3,d4; + uint32_t c; + + r0 = st->r[0]; + r1 = st->r[1]; + r2 = st->r[2]; + r3 = st->r[3]; + r4 = st->r[4]; s1 = r1 * 5; s2 = r2 * 5; s3 = r3 * 5; s4 = r4 * 5; - /* init state */ - h0 = 0; - h1 = 0; - h2 = 0; - h3 = 0; - h4 = 0; - - /* full blocks */ - if (inlen < 16) goto poly1305_donna_atmost15bytes; -poly1305_donna_16bytes: - m += 16; - inlen -= 16; - - t0 = ReadLE32(m-16); - t1 = ReadLE32(m-12); - t2 = ReadLE32(m-8); - t3 = ReadLE32(m-4); - - h0 += t0 & 0x3ffffff; - h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff; - h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff; - h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff; - h4 += (t3 >> 8) | (1 << 24); - - -poly1305_donna_mul: - t[0] = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1); - t[1] = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2); - t[2] = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3); - t[3] = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4); - t[4] = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0); - - h0 = (uint32_t)t[0] & 0x3ffffff; c = (t[0] >> 26); - t[1] += c; h1 = (uint32_t)t[1] & 0x3ffffff; b = (uint32_t)(t[1] >> 26); - t[2] += b; h2 = (uint32_t)t[2] & 0x3ffffff; b = (uint32_t)(t[2] >> 26); - t[3] += b; h3 = (uint32_t)t[3] & 0x3ffffff; b = (uint32_t)(t[3] >> 26); - t[4] += b; h4 = (uint32_t)t[4] & 0x3ffffff; b = (uint32_t)(t[4] >> 26); - h0 += b * 5; - - if (inlen >= 16) goto poly1305_donna_16bytes; - - /* final bytes */ -poly1305_donna_atmost15bytes: - if (!inlen) goto poly1305_donna_finish; - - for (j = 0; j < inlen; j++) mp[j] = m[j]; - mp[j++] = 1; - for (; j < 16; j++) mp[j] = 0; - inlen = 0; - - t0 = ReadLE32(mp+0); - t1 = ReadLE32(mp+4); - t2 = ReadLE32(mp+8); - t3 = ReadLE32(mp+12); - - h0 += t0 & 0x3ffffff; - h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff; - h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff; - h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff; - h4 += (t3 >> 8); - - goto poly1305_donna_mul; - -poly1305_donna_finish: - b = h0 >> 26; h0 = h0 & 0x3ffffff; - h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff; - h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff; - h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff; - h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff; - h0 += b * 5; b = h0 >> 26; h0 = h0 & 0x3ffffff; - h1 += b; - - g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff; - g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff; - g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff; - g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff; - g4 = h4 + b - (1 << 26); - - b = (g4 >> 31) - 1; - nb = ~b; - h0 = (h0 & nb) | (g0 & b); - h1 = (h1 & nb) | (g1 & b); - h2 = (h2 & nb) | (g2 & b); - h3 = (h3 & nb) | (g3 & b); - h4 = (h4 & nb) | (g4 & b); - - f0 = ((h0 ) | (h1 << 26)) + (uint64_t)ReadLE32(&key[16]); - f1 = ((h1 >> 6) | (h2 << 20)) + (uint64_t)ReadLE32(&key[20]); - f2 = ((h2 >> 12) | (h3 << 14)) + (uint64_t)ReadLE32(&key[24]); - f3 = ((h3 >> 18) | (h4 << 8)) + (uint64_t)ReadLE32(&key[28]); - - WriteLE32(&out[ 0], f0); f1 += (f0 >> 32); - WriteLE32(&out[ 4], f1); f2 += (f1 >> 32); - WriteLE32(&out[ 8], f2); f3 += (f2 >> 32); - WriteLE32(&out[12], f3); + h0 = st->h[0]; + h1 = st->h[1]; + h2 = st->h[2]; + h3 = st->h[3]; + h4 = st->h[4]; + + while (bytes >= POLY1305_BLOCK_SIZE) { + /* h += m[i] */ + h0 += (ReadLE32(m+ 0) ) & 0x3ffffff; + h1 += (ReadLE32(m+ 3) >> 2) & 0x3ffffff; + h2 += (ReadLE32(m+ 6) >> 4) & 0x3ffffff; + h3 += (ReadLE32(m+ 9) >> 6) & 0x3ffffff; + h4 += (ReadLE32(m+12) >> 8) | hibit; + + /* h *= r */ + d0 = ((uint64_t)h0 * r0) + ((uint64_t)h1 * s4) + ((uint64_t)h2 * s3) + ((uint64_t)h3 * s2) + ((uint64_t)h4 * s1); + d1 = ((uint64_t)h0 * r1) + ((uint64_t)h1 * r0) + ((uint64_t)h2 * s4) + ((uint64_t)h3 * s3) + ((uint64_t)h4 * s2); + d2 = ((uint64_t)h0 * r2) + ((uint64_t)h1 * r1) + ((uint64_t)h2 * r0) + ((uint64_t)h3 * s4) + ((uint64_t)h4 * s3); + d3 = ((uint64_t)h0 * r3) + ((uint64_t)h1 * r2) + ((uint64_t)h2 * r1) + ((uint64_t)h3 * r0) + ((uint64_t)h4 * s4); + d4 = ((uint64_t)h0 * r4) + ((uint64_t)h1 * r3) + ((uint64_t)h2 * r2) + ((uint64_t)h3 * r1) + ((uint64_t)h4 * r0); + + /* (partial) h %= p */ + c = (uint32_t)(d0 >> 26); h0 = (uint32_t)d0 & 0x3ffffff; + d1 += c; c = (uint32_t)(d1 >> 26); h1 = (uint32_t)d1 & 0x3ffffff; + d2 += c; c = (uint32_t)(d2 >> 26); h2 = (uint32_t)d2 & 0x3ffffff; + d3 += c; c = (uint32_t)(d3 >> 26); h3 = (uint32_t)d3 & 0x3ffffff; + d4 += c; c = (uint32_t)(d4 >> 26); h4 = (uint32_t)d4 & 0x3ffffff; + h0 += c * 5; c = (h0 >> 26); h0 = h0 & 0x3ffffff; + h1 += c; + + m += POLY1305_BLOCK_SIZE; + bytes -= POLY1305_BLOCK_SIZE; + } + + st->h[0] = h0; + st->h[1] = h1; + st->h[2] = h2; + st->h[3] = h3; + st->h[4] = h4; +} + +void poly1305_finish(poly1305_context *st, unsigned char mac[16]) noexcept { + uint32_t h0,h1,h2,h3,h4,c; + uint32_t g0,g1,g2,g3,g4; + uint64_t f; + uint32_t mask; + + /* process the remaining block */ + if (st->leftover) { + size_t i = st->leftover; + st->buffer[i++] = 1; + for (; i < POLY1305_BLOCK_SIZE; i++) { + st->buffer[i] = 0; + } + st->final = 1; + poly1305_blocks(st, st->buffer, POLY1305_BLOCK_SIZE); + } + + /* fully carry h */ + h0 = st->h[0]; + h1 = st->h[1]; + h2 = st->h[2]; + h3 = st->h[3]; + h4 = st->h[4]; + + c = h1 >> 26; h1 = h1 & 0x3ffffff; + h2 += c; c = h2 >> 26; h2 = h2 & 0x3ffffff; + h3 += c; c = h3 >> 26; h3 = h3 & 0x3ffffff; + h4 += c; c = h4 >> 26; h4 = h4 & 0x3ffffff; + h0 += c * 5; c = h0 >> 26; h0 = h0 & 0x3ffffff; + h1 += c; + + /* compute h + -p */ + g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff; + g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff; + g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff; + g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff; + g4 = h4 + c - (1UL << 26); + + /* select h if h < p, or h + -p if h >= p */ + mask = (g4 >> ((sizeof(uint32_t) * 8) - 1)) - 1; + g0 &= mask; + g1 &= mask; + g2 &= mask; + g3 &= mask; + g4 &= mask; + mask = ~mask; + h0 = (h0 & mask) | g0; + h1 = (h1 & mask) | g1; + h2 = (h2 & mask) | g2; + h3 = (h3 & mask) | g3; + h4 = (h4 & mask) | g4; + + /* h = h % (2^128) */ + h0 = ((h0 ) | (h1 << 26)) & 0xffffffff; + h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff; + h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff; + h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff; + + /* mac = (h + pad) % (2^128) */ + f = (uint64_t)h0 + st->pad[0] ; h0 = (uint32_t)f; + f = (uint64_t)h1 + st->pad[1] + (f >> 32); h1 = (uint32_t)f; + f = (uint64_t)h2 + st->pad[2] + (f >> 32); h2 = (uint32_t)f; + f = (uint64_t)h3 + st->pad[3] + (f >> 32); h3 = (uint32_t)f; + + WriteLE32(mac + 0, h0); + WriteLE32(mac + 4, h1); + WriteLE32(mac + 8, h2); + WriteLE32(mac + 12, h3); + + /* zero out the state */ + st->h[0] = 0; + st->h[1] = 0; + st->h[2] = 0; + st->h[3] = 0; + st->h[4] = 0; + st->r[0] = 0; + st->r[1] = 0; + st->r[2] = 0; + st->r[3] = 0; + st->r[4] = 0; + st->pad[0] = 0; + st->pad[1] = 0; + st->pad[2] = 0; + st->pad[3] = 0; } + +void poly1305_update(poly1305_context *st, const unsigned char *m, size_t bytes) noexcept { + size_t i; + + /* handle leftover */ + if (st->leftover) { + size_t want = (POLY1305_BLOCK_SIZE - st->leftover); + if (want > bytes) { + want = bytes; + } + for (i = 0; i < want; i++) { + st->buffer[st->leftover + i] = m[i]; + } + bytes -= want; + m += want; + st->leftover += want; + if (st->leftover < POLY1305_BLOCK_SIZE) return; + poly1305_blocks(st, st->buffer, POLY1305_BLOCK_SIZE); + st->leftover = 0; + } + + /* process full blocks */ + if (bytes >= POLY1305_BLOCK_SIZE) { + size_t want = (bytes & ~(POLY1305_BLOCK_SIZE - 1)); + poly1305_blocks(st, m, want); + m += want; + bytes -= want; + } + + /* store leftover */ + if (bytes) { + for (i = 0; i < bytes; i++) { + st->buffer[st->leftover + i] = m[i]; + } + st->leftover += bytes; + } +} + +} // namespace poly1305_donna diff --git a/src/crypto/poly1305.h b/src/crypto/poly1305.h index 1598b013b966..1f42f9c8f99e 100644 --- a/src/crypto/poly1305.h +++ b/src/crypto/poly1305.h @@ -5,13 +5,66 @@ #ifndef BITCOIN_CRYPTO_POLY1305_H #define BITCOIN_CRYPTO_POLY1305_H +#include + +#include #include #include -#define POLY1305_KEYLEN 32 -#define POLY1305_TAGLEN 16 +#define POLY1305_BLOCK_SIZE 16 + +namespace poly1305_donna { + +// Based on the public domain implementation by Andrew Moon +// poly1305-donna-32.h from https://github.com/floodyberry/poly1305-donna + +typedef struct { + uint32_t r[5]; + uint32_t h[5]; + uint32_t pad[4]; + size_t leftover; + unsigned char buffer[POLY1305_BLOCK_SIZE]; + unsigned char final; +} poly1305_context; + +void poly1305_init(poly1305_context *st, const unsigned char key[32]) noexcept; +void poly1305_update(poly1305_context *st, const unsigned char *m, size_t bytes) noexcept; +void poly1305_finish(poly1305_context *st, unsigned char mac[16]) noexcept; + +} // namespace poly1305_donna + +/** C++ wrapper with std::byte Span interface around poly1305_donna code. */ +class Poly1305 +{ + poly1305_donna::poly1305_context m_ctx; + +public: + /** Length of the output produced by Finalize(). */ + static constexpr unsigned TAGLEN{16}; + + /** Length of the keys expected by the constructor. */ + static constexpr unsigned KEYLEN{32}; + + /** Construct a Poly1305 object with a given 32-byte key. */ + Poly1305(Span key) noexcept + { + assert(key.size() == KEYLEN); + poly1305_donna::poly1305_init(&m_ctx, UCharCast(key.data())); + } + + /** Process message bytes. */ + Poly1305& Update(Span msg) noexcept + { + poly1305_donna::poly1305_update(&m_ctx, UCharCast(msg.data()), msg.size()); + return *this; + } -void poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, - const unsigned char key[POLY1305_KEYLEN]); + /** Write authentication tag to 16-byte out. */ + void Finalize(Span out) noexcept + { + assert(out.size() == TAGLEN); + poly1305_donna::poly1305_finish(&m_ctx, UCharCast(out.data())); + } +}; #endif // BITCOIN_CRYPTO_POLY1305_H diff --git a/src/pubkey.h b/src/pubkey.h index 2b3af515a304..384d5da1422c 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -213,6 +213,9 @@ struct EllSwiftPubKey std::array m_pubkey; public: + /** Default constructor creates all-zero pubkey (which is valid). */ + EllSwiftPubKey() noexcept = default; + /** Construct a new ellswift public key from a given serialization. */ EllSwiftPubKey(const std::array& ellswift) : m_pubkey(ellswift) {} diff --git a/src/random.cpp b/src/random.cpp index 8b6ea488bfc5..0c6129ea256e 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include // for Mutex #include // for GetTimeMicros() +#include #include #include @@ -618,7 +620,7 @@ bool GetRandBool(double rate) void FastRandomContext::RandomSeed() { uint256 seed = GetRandHash(); - rng.SetKey32(seed.begin()); + rng.SetKey(MakeByteSpan(seed)); requires_seed = false; } @@ -626,18 +628,15 @@ uint256 FastRandomContext::rand256() noexcept { if (requires_seed) RandomSeed(); uint256 ret; - rng.Keystream(ret.data(), ret.size()); + rng.Keystream(MakeWritableByteSpan(ret)); return ret; } template std::vector FastRandomContext::randbytes(size_t len) { - if (requires_seed) RandomSeed(); std::vector ret(len); - if (len > 0) { - rng.Keystream(UCharCast(ret.data()), len); - } + fillrand(MakeWritableByteSpan(ret)); return ret; } template std::vector FastRandomContext::randbytes(size_t); @@ -646,13 +645,10 @@ template std::vector FastRandomContext::randbytes(size_t); void FastRandomContext::fillrand(Span output) { if (requires_seed) RandomSeed(); - rng.Keystream(UCharCast(output.data()), output.size()); + rng.Keystream(output); } -FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bitbuf_size(0) -{ - rng.SetKey32(seed.begin()); -} +FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), rng(MakeByteSpan(seed)), bitbuf_size(0) {} bool Random_SanityCheck() { @@ -700,13 +696,13 @@ bool Random_SanityCheck() return true; } -FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bitbuf_size(0) +static constexpr std::array ZERO_KEY{}; + +FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), rng(ZERO_KEY), bitbuf_size(0) { - if (!fDeterministic) { - return; - } - uint256 seed; - rng.SetKey32(seed.begin()); + // Note that despite always initializing with ZERO_KEY, requires_seed is set to true if not + // fDeterministic. That means the rng will be reinitialized with a secure random key upon first + // use. } FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept diff --git a/src/random.h b/src/random.h index 6b096b477664..c9295df63ea1 100644 --- a/src/random.h +++ b/src/random.h @@ -156,9 +156,9 @@ class FastRandomContext uint64_t rand64() noexcept { if (requires_seed) RandomSeed(); - unsigned char buf[8]; - rng.Keystream(buf, 8); - return ReadLE64(buf); + std::array buf; + rng.Keystream(buf); + return ReadLE64(UCharCast(buf.data())); } /** Generate a random (bits)-bit integer. */ diff --git a/src/test/bip324_tests.cpp b/src/test/bip324_tests.cpp new file mode 100644 index 000000000000..309b89962d1b --- /dev/null +++ b/src/test/bip324_tests.cpp @@ -0,0 +1,305 @@ +// Copyright (c) 2023 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 + +#include + +namespace { + +void TestBIP324PacketVector( + uint32_t in_idx, + const std::string& in_priv_ours_hex, + const std::string& in_ellswift_ours_hex, + const std::string& in_ellswift_theirs_hex, + bool in_initiating, + const std::string& in_contents_hex, + uint32_t in_multiply, + const std::string& in_aad_hex, + bool in_ignore, + const std::string& mid_send_garbage_hex, + const std::string& mid_recv_garbage_hex, + const std::string& out_session_id_hex, + const std::string& out_ciphertext_hex, + const std::string& out_ciphertext_endswith_hex) +{ + // Convert input from hex to char/byte vectors/arrays. + const auto in_priv_ours = ParseHex(in_priv_ours_hex); + const auto in_ellswift_ours_vec = ParseHex(in_ellswift_ours_hex); + assert(in_ellswift_ours_vec.size() == 64); + std::array in_ellswift_ours; + std::copy(in_ellswift_ours_vec.begin(), in_ellswift_ours_vec.end(), in_ellswift_ours.begin()); + const auto in_ellswift_theirs_vec = ParseHex(in_ellswift_theirs_hex); + assert(in_ellswift_theirs_vec.size() == 64); + std::array in_ellswift_theirs; + std::copy(in_ellswift_theirs_vec.begin(), in_ellswift_theirs_vec.end(), in_ellswift_theirs.begin()); + const auto in_contents = ParseHex(in_contents_hex); + const auto in_aad = ParseHex(in_aad_hex); + const auto mid_send_garbage = ParseHex(mid_send_garbage_hex); + const auto mid_recv_garbage = ParseHex(mid_recv_garbage_hex); + const auto out_session_id = ParseHex(out_session_id_hex); + const auto out_ciphertext = ParseHex(out_ciphertext_hex); + const auto out_ciphertext_endswith = ParseHex(out_ciphertext_endswith_hex); + + // Load keys + CKey key; + key.Set(in_priv_ours.begin(), in_priv_ours.end(), true); + EllSwiftPubKey ellswift_ours(in_ellswift_ours); + EllSwiftPubKey ellswift_theirs(in_ellswift_theirs); + + // Instantiate encryption BIP324 cipher. + BIP324Cipher cipher(key, ellswift_ours); + BOOST_CHECK(!cipher); + BOOST_CHECK(cipher.GetOurPubKey() == ellswift_ours); + cipher.Initialize(ellswift_theirs, in_initiating); + BOOST_CHECK(cipher); + + // Compare session variables. + BOOST_CHECK(Span{out_session_id} == cipher.GetSessionID()); + BOOST_CHECK(Span{mid_send_garbage} == cipher.GetSendGarbageTerminator()); + BOOST_CHECK(Span{mid_recv_garbage} == cipher.GetReceiveGarbageTerminator()); + + // Vector of encrypted empty messages, encrypted in order to seek to the right position. + std::vector> dummies(in_idx); + + // Seek to the numbered packet. + for (uint32_t i = 0; i < in_idx; ++i) { + dummies[i].resize(cipher.EXPANSION); + cipher.Encrypt({}, {}, true, dummies[i]); + } + + // Construct contents and encrypt it. + std::vector contents; + for (uint32_t i = 0; i < in_multiply; ++i) { + contents.insert(contents.end(), in_contents.begin(), in_contents.end()); + } + std::vector ciphertext(contents.size() + cipher.EXPANSION); + cipher.Encrypt(contents, in_aad, in_ignore, ciphertext); + + // Verify ciphertext. Note that the test vectors specify either out_ciphertext (for short + // messages) or out_ciphertext_endswith (for long messages), so only check the relevant one. + if (!out_ciphertext.empty()) { + BOOST_CHECK(out_ciphertext == ciphertext); + } else { + BOOST_CHECK(ciphertext.size() >= out_ciphertext_endswith.size()); + BOOST_CHECK(Span{out_ciphertext_endswith} == Span{ciphertext}.last(out_ciphertext_endswith.size())); + } + + for (unsigned error = 0; error <= 12; ++error) { + // error selects a type of error introduced: + // - error=0: no errors, decryption should be successful + // - error=1: wrong side + // - error=2..9: bit error in ciphertext + // - error=10: bit error in aad + // - error=11: extra 0x00 at end of aad + // - error=12: message index wrong + + // Instantiate self-decrypting BIP324 cipher. + BIP324Cipher dec_cipher(key, ellswift_ours); + BOOST_CHECK(!dec_cipher); + BOOST_CHECK(dec_cipher.GetOurPubKey() == ellswift_ours); + dec_cipher.Initialize(ellswift_theirs, (error == 1) ^ in_initiating, /*self_decrypt=*/true); + BOOST_CHECK(dec_cipher); + + // Compare session variables. + BOOST_CHECK((Span{out_session_id} == dec_cipher.GetSessionID()) == (error != 1)); + BOOST_CHECK((Span{mid_send_garbage} == dec_cipher.GetSendGarbageTerminator()) == (error != 1)); + BOOST_CHECK((Span{mid_recv_garbage} == dec_cipher.GetReceiveGarbageTerminator()) == (error != 1)); + + // Seek to the numbered packet. + if (in_idx == 0 && error == 12) continue; + uint32_t dec_idx = in_idx ^ (error == 12 ? (1U << InsecureRandRange(16)) : 0); + for (uint32_t i = 0; i < dec_idx; ++i) { + unsigned use_idx = i < in_idx ? i : 0; + bool dec_ignore{false}; + dec_cipher.DecryptLength(Span{dummies[use_idx]}.first(cipher.LENGTH_LEN)); + dec_cipher.Decrypt(Span{dummies[use_idx]}.subspan(cipher.LENGTH_LEN), {}, dec_ignore, {}); + } + + // Construct copied (and possibly damaged) copy of ciphertext. + // Decrypt length + auto to_decrypt = ciphertext; + if (error >= 2 && error <= 9) { + to_decrypt[InsecureRandRange(to_decrypt.size())] ^= std::byte(1U << (error - 2)); + } + + // Decrypt length and resize ciphertext to accommodate. + uint32_t dec_len = dec_cipher.DecryptLength(MakeByteSpan(to_decrypt).first(cipher.LENGTH_LEN)); + to_decrypt.resize(dec_len + cipher.EXPANSION); + + // Construct copied (and possibly damaged) copy of aad. + auto dec_aad = in_aad; + if (error == 10) { + if (in_aad.size() == 0) continue; + dec_aad[InsecureRandRange(dec_aad.size())] ^= std::byte(1U << InsecureRandRange(8)); + } + if (error == 11) dec_aad.push_back({}); + + // Decrypt contents. + std::vector decrypted(dec_len); + bool dec_ignore{false}; + bool dec_ok = dec_cipher.Decrypt(Span{to_decrypt}.subspan(cipher.LENGTH_LEN), dec_aad, dec_ignore, decrypted); + + // Verify result. + BOOST_CHECK(dec_ok == !error); + if (dec_ok) { + BOOST_CHECK(decrypted == contents); + BOOST_CHECK(dec_ignore == in_ignore); + } + } +} + +} // namespace + +BOOST_FIXTURE_TEST_SUITE(bip324_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(packet_test_vectors) { + // BIP324 key derivation uses network magic in the HKDF process. We use mainnet params here + // as that is what the test vectors are written for. + SelectParams(CBaseChainParams::MAIN); + + // The test vectors are converted using the following Python code in the BIP bip-0324/ directory: + // + // import sys + // import csv + // with open('packet_encoding_test_vectors.csv', newline='', encoding='utf-8') as csvfile: + // reader = csv.DictReader(csvfile) + // quote = lambda x: "\"" + x + "\"" + // for row in reader: + // args = [ + // row['in_idx'], + // quote(row['in_priv_ours']), + // quote(row['in_ellswift_ours']), + // quote(row['in_ellswift_theirs']), + // "true" if int(row['in_initiating']) else "false", + // quote(row['in_contents']), + // row['in_multiply'], + // quote(row['in_aad']), + // "true" if int(row['in_ignore']) else "false", + // quote(row['mid_send_garbage_terminator']), + // quote(row['mid_recv_garbage_terminator']), + // quote(row['out_session_id']), + // quote(row['out_ciphertext']), + // quote(row['out_ciphertext_endswith']) + // ] + // print(" TestBIP324PacketVector(\n " + ",\n ".join(args) + ");") + TestBIP324PacketVector( + 1, + "61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7", + "ec0adff257bbfe500c188c80b4fdd640f6b45a482bbc15fc7cef5931deff0aa186f6eb9bba7b85dc4dcc28b28722de1e3d9108b985e2967045668f66098e475b", + "a4a94dfce69b4a2a0a099313d10f9f7e7d649d60501c9e1d274c300e0d89aafaffffffffffffffffffffffffffffffffffffffffffffffffffffffff8faf88d5", + true, + "8e", + 1, + "", + false, + "359322d6efd790a4ac050d2f2a561bc3", + "c10bd34176bd82588a08414cccf9c133", + "bc611d6d31ea2e839fcfd2270c2dcfdd685121a6eb492e9e5cd7159dfebdc590", + "1f53be00a6e48ab452932abc4faf9113da33d4ca45", + ""); + TestBIP324PacketVector( + 999, + "1f9c581b35231838f0f17cf0c979835baccb7f3abbbb96ffcc318ab71e6e126f", + "a1855e10e94e00baa23041d916e259f7044e491da6171269694763f018c7e63693d29575dcb464ac816baa1be353ba12e3876cba7628bd0bd8e755e721eb0140", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0000000000000000000000000000000000000000000000000000000000000000", + false, + "3eb1d4e98035cfd8eeb29bac969ed3824a", + 1, + "", + false, + "7c2686bae27164ca86251b33a92bd42a", + "21d43697e76db39672a79a18488efc07", + "e497a4516826d2b1543c54d1f2e44751e94dd0b6f997d4089434475c49561f75", + "77a051dd61f0cf84be204ed0ddeee4fa3218c8c3dfacf4c5d026807fc3e41fb949bfd9a267", + ""); + TestBIP324PacketVector( + 0, + "0286c41cd30913db0fdff7a64ebda5c8e3e7cef10f2aebc00a7650443cf4c60d", + "d1ee8a93a01130cbf299249a258f94feb5f469e7d0f2f28f69ee5e9aa8f9b54a60f2c3ff2d023634ec7f4127a96cc11662e402894cf1f694fb9a7eaa5f1d9244", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff22d5e441524d571a52b3def126189d3f416890a99d4da6ede2b0cde1760ce2c3f98457ae", + true, + "054290a6c6ba8d80478172e89d32bf690913ae9835de6dcf206ff1f4d652286fe0ddf74deba41d55de3edc77c42a32af79bbea2c00bae7492264c60866ae5a", + 1, + "84932a55aac22b51e7b128d31d9f0550da28e6a3f394224707d878603386b2f9d0c6bcd8046679bfed7b68c517e7431e75d9dd34605727d2ef1c2babbf680ecc8d68d2c4886e9953a4034abde6da4189cd47c6bb3192242cf714d502ca6103ee84e08bc2ca4fd370d5ad4e7d06c7fbf496c6c7cc7eb19c40c61fb33df2a9ba48497a96c98d7b10c1f91098a6b7b16b4bab9687f27585ade1491ae0dba6a79e1e2d85dd9d9d45c5135ca5fca3f0f99a60ea39edbc9efc7923111c937913f225d67788d5f7e8852b697e26b92ec7bfcaa334a1665511c2b4c0a42d06f7ab98a9719516c8fd17f73804555ee84ab3b7d1762f6096b778d3cb9c799cbd49a9e4a325197b4e6cc4a5c4651f8b41ff88a92ec428354531f970263b467c77ed11312e2617d0d53fe9a8707f51f9f57a77bfb49afe3d89d85ec05ee17b9186f360c94ab8bb2926b65ca99dae1d6ee1af96cad09de70b6767e949023e4b380e66669914a741ed0fa420a48dbc7bfae5ef2019af36d1022283dd90655f25eec7151d471265d22a6d3f91dc700ba749bb67c0fe4bc0888593fbaf59d3c6fff1bf756a125910a63b9682b597c20f560ecb99c11a92c8c8c3f7fbfaa103146083a0ccaecf7a5f5e735a784a8820155914a289d57d8141870ffcaf588882332e0bcd8779efa931aa108dab6c3cce76691e345df4a91a03b71074d66333fd3591bff071ea099360f787bbe43b7b3dff2a59c41c7642eb79870222ad1c6f2e5a191ed5acea51134679587c9cf71c7d8ee290be6bf465c4ee47897a125708704ad610d8d00252d01959209d7cd04d5ecbbb1419a7e84037a55fefa13dee464b48a35c96bcb9a53e7ed461c3a1607ee00c3c302fd47cd73fda7493e947c9834a92d63dcfbd65aa7c38c3e3a2748bb5d9a58e7495d243d6b741078c8f7ee9c8813e473a323375702702b0afae1550c8341eedf5247627343a95240cb02e3e17d5dca16f8d8d3b2228e19c06399f8ec5c5e9dbe4caef6a0ea3ffb1d3c7eac03ae030e791fa12e537c80d56b55b764cadf27a8701052df1282ba8b5e3eb62b5dc7973ac40160e00722fa958d95102fc25c549d8c0e84bed95b7acb61ba65700c4de4feebf78d13b9682c52e937d23026fb4c6193e6644e2d3c99f91f4f39a8b9fc6d013f89c3793ef703987954dc0412b550652c01d922f525704d32d70d6d4079bc3551b563fb29577b3aecdc9505011701dddfd94830431e7a4918927ee44fb3831ce8c4513839e2deea1287f3fa1ab9b61a256c09637dbc7b4f0f8fbb783840f9c24526da883b0df0c473cf231656bd7bc1aaba7f321fec0971c8c2c3444bff2f55e1df7fea66ec3e440a612db9aa87bb505163a59e06b96d46f50d8120b92814ac5ab146bc78dbbf91065af26107815678ce6e33812e6bf3285d4ef3b7b04b076f21e7820dcbfdb4ad5218cf4ff6a65812d8fcb98ecc1e95e2fa58e3efe4ce26cd0bd400d6036ab2ad4f6c713082b5e3f1e04eb9e3b6c8f63f57953894b9e220e0130308e1fd91f72d398c1e7962ca2c31be83f31d6157633581a0a6910496de8d55d3d07090b6aa087159e388b7e7dec60f5d8a60d93ca2ae91296bd484d916bfaaa17c8f45ea4b1a91b37c82821199a2b7596672c37156d8701e7352aa48671d3b1bbbd2bd5f0a2268894a25b0cb2514af39c8743f8cce8ab4b523053739fd8a522222a09acf51ac704489cf17e4b7125455cb8f125b4d31af1eba1f8cf7f81a5a100a141a7ee72e8083e065616649c241f233645c5fc865d17f0285f5c52d9f45312c979bfb3ce5f2a1b951deddf280ffb3f370410cffd1583bfa90077835aa201a0712d1dcd1293ee177738b14e6b5e2a496d05220c3253bb6578d6aff774be91946a614dd7e879fb3dcf7451e0b9adb6a8c44f53c2c464bcc0019e9fad89cac7791a0a3f2974f759a9856351d4d2d7c5612c17cfc50f8479945df57716767b120a590f4bf656f4645029a525694d8a238446c5f5c2c1c995c09c1405b8b1eb9e0352ffdf766cc964f8dcf9f8f043dfab6d102cf4b298021abd78f1d9025fa1f8e1d710b38d9d1652f2d88d1305874ec41609b6617b65c5adb19b6295dc5c5da5fdf69f28144ea12f17c3c6fcce6b9b5157b3dfc969d6725fa5b098a4d9b1d31547ed4c9187452d281d0a5d456008caf1aa251fac8f950ca561982dc2dc908d3691ee3b6ad3ae3d22d002577264ca8e49c523bd51c4846be0d198ad9407bf6f7b82c79893eb2c05fe9981f687a97a4f01fe45ff8c8b7ecc551135cd960a0d6001ad35020be07ffb53cb9e731522ca8ae9364628914b9b8e8cc2f37f03393263603cc2b45295767eb0aac29b0930390eb89587ab2779d2e3decb8042acece725ba42eda650863f418f8d0d50d104e44fbbe5aa7389a4a144a8cecf00f45fb14c39112f9bfb56c0acbd44fa3ff261f5ce4acaa5134c2c1d0cca447040820c81ab1bcdc16aa075b7c68b10d06bbb7ce08b5b805e0238f24402cf24a4b4e00701935a0c68add3de090903f9b85b153cb179a582f57113bfc21c2093803f0cfa4d9d4672c2b05a24f7e4c34a8e9101b70303a7378b9c50b6cddd46814ef7fd73ef6923feceab8fc5aa8b0d185f2e83c7a99dcb1077c0ab5c1f5d5f01ba2f0420443f75c4417db9ebf1665efbb33dca224989920a64b44dc26f682cc77b4632c8454d49135e52503da855bc0f6ff8edc1145451a9772c06891f41064036b66c3119a0fc6e80dffeb65dc456108b7ca0296f4175fff3ed2b0f842cd46bd7e86f4c62dfaf1ddbf836263c00b34803de164983d0811cebfac86e7720c726d3048934c36c23189b02386a722ca9f0fe00233ab50db928d3bccea355cc681144b8b7edcaae4884d5a8f04425c0890ae2c74326e138066d8c05f4c82b29df99b034ea727afde590a1f2177ace3af99cfb1729d6539ce7f7f7314b046aab74497e63dd399e1f7d5f16517c23bd830d1fdee810f3c3b77573dd69c4b97d80d71fb5a632e00acdfa4f8e829faf3580d6a72c40b28a82172f8dcd4627663ebf6069736f21735fd84a226f427cd06bb055f94e7c92f31c48075a2955d82a5b9d2d0198ce0d4e131a112570a8ee40fb80462a81436a58e7db4e34b6e2c422e82f934ecda9949893da5730fc5c23c7c920f363f85ab28cc6a4206713c3152669b47efa8238fa826735f17b4e78750276162024ec85458cd5808e06f40dd9fd43775a456a3ff6cae90550d76d8b2899e0762ad9a371482b3e38083b1274708301d6346c22fea9bb4b73db490ff3ab05b2f7f9e187adef139a7794454b7300b8cc64d3ad76c0e4bc54e08833a4419251550655380d675bc91855aeb82585220bb97f03e976579c08f321b5f8f70988d3061f41465517d53ac571dbf1b24b94443d2e9a8e8a79b392b3d6a4ecdd7f626925c365ef6221305105ce9b5f5b6ecc5bed3d702bd4b7f5008aa8eb8c7aa3ade8ecf6251516fbefeea4e1082aa0e1848eddb31ffe44b04792d296054402826e4bd054e671f223e5557e4c94f89ca01c25c44f1a2ff2c05a70b43408250705e1b858bf0670679fdcd379203e36be3500dd981b1a6422c3cf15224f7fefdef0a5f225c5a09d15767598ecd9e262460bb33a4b5d09a64591efabc57c923d3be406979032ae0bc0997b65336a06dd75b253332ad6a8b63ef043f780a1b3fb6d0b6cad98b1ef4a02535eb39e14a866cfc5fc3a9c5deb2261300d71280ebe66a0776a151469551c3c5fa308757f956655278ec6330ae9e3625468c5f87e02cd9a6489910d4143c1f4ee13aa21a6859d907b788e28572fecee273d44e4a900fa0aa668dd861a60fb6b6b12c2c5ef3c8df1bd7ef5d4b0d1cdb8c15fffbb365b9784bd94abd001c6966216b9b67554ad7cb7f958b70092514f7800fc40244003e0fd1133a9b850fb17f4fcafde07fc87b07fb510670654a5d2d6fc9876ac74728ea41593beef003d6858786a52d3a40af7529596767c17000bfaf8dc52e871359f4ad8bf6e7b2853e5229bdf39657e213580294a5317c5df172865e1e17fe37093b585e04613f5f078f761b2b1752eb32983afda24b523af8851df9a02b37e77f543f18888a782a994a50563334282bf9cdfccc183fdf4fcd75ad86ee0d94f91ee2300a5befbccd14e03a77fc031a8cfe4f01e4c5290f5ac1da0d58ea054bd4837cfd93e5e34fc0eb16e48044ba76131f228d16cde9b0bb978ca7cdcd10653c358bdb26fdb723a530232c32ae0a4cecc06082f46e1c1d596bfe60621ad1e354e01e07b040cc7347c016653f44d926d13ca74e6cbc9d4ab4c99f4491c95c76fff5076b3936eb9d0a286b97c035ca88a3c6309f5febfd4cdaac869e4f58ed409b1e9eb4192fb2f9c2f12176d460fd98286c9d6df84598f260119fd29c63f800c07d8df83d5cc95f8c2fea2812e7890e8a0718bb1e031ecbebc0436dcf3e3b9a58bcc06b4c17f711f80fe1dffc3326a6eb6e00283055c6dabe20d311bfd5019591b7954f8163c9afad9ef8390a38f3582e0a79cdf0353de8eeb6b5f9f27b16ffdef7dd62869b4840ee226ccdce95e02c4545eb981b60571cd83f03dc5eaf8c97a0829a4318a9b3dc06c0e003db700b2260ff1fa8fee66890e637b109abb03ec901b05ca599775f48af50154c0e67d82bf0f558d7d3e0778dc38bea1eb5f74dc8d7f90abdf5511a424be66bf8b6a3cacb477d2e7ef4db68d2eba4d5289122d851f9501ba7e9c4957d8eba3be3fc8e785c4265a1d65c46f2809b70846c693864b169c9dcb78be26ea14b8613f145b01887222979a9e67aee5f800caa6f5c4229bdeefc901232ace6143c9865e4d9c07f51aa200afaf7e48a7d1d8faf366023beab12906ffcb3eaf72c0eb68075e4daf3c080e0c31911befc16f0cc4a09908bb7c1e26abab38bd7b788e1a09c0edf1a35a38d2ff1d3ed47fcdaae2f0934224694f5b56705b9409b6d3d64f3833b686f7576ec64bbdd6ff174e56c2d1edac0011f904681a73face26573fbba4e34652f7ae84acfb2fa5a5b3046f98178cd0831df7477de70e06a4c00e305f31aafc026ef064dd68fd3e4252b1b91d617b26c6d09b6891a00df68f105b5962e7f9d82da101dd595d286da721443b72b2aba2377f6e7772e33b3a5e3753da9c2578c5d1daab80187f55518c72a64ee150a7cb5649823c08c9f62cd7d020b45ec2cba8310db1a7785a46ab24785b4d54ff1660b5ca78e05a9a55edba9c60bf044737bc468101c4e8bd1480d749be5024adefca1d998abe33eaeb6b11fbb39da5d905fdd3f611b2e51517ccee4b8af72c2d948573505590d61a6783ab7278fc43fe55b1fcc0e7216444d3c8039bb8145ef1ce01c50e95a3f3feab0aee883fdb94cc13ee4d21c542aa795e18932228981690f4d4c57ca4db6eb5c092e29d8a05139d509a8aeb48baa1eb97a76e597a32b280b5e9d6c36859064c98ff96ef5126130264fa8d2f49213870d9fb036cff95da51f270311d9976208554e48ffd486470d0ecdb4e619ccbd8226147204baf8e235f54d8b1cba8fa34a9a4d055de515cdf180d2bb6739a175183c472e30b5c914d09eeb1b7dafd6872b38b48c6afc146101200e6e6a44fe5684e220adc11f5c403ddb15df8051e6bdef09117a3a5349938513776286473a3cf1d2788bb875052a2e6459fa7926da33380149c7f98d7700528a60c954e6f5ecb65842fde69d614be69eaa2040a4819ae6e756accf936e14c1e894489744a79c1f2c1eb295d13e2d767c09964b61f9cfe497649f712", + false, + "f9fb7d8aedf397e607ab5694ab468432", + "4b882980f5679f588ccadd9f8cf5c9a2", + "095487080e9fe547df12823c2136239d77a318f637f7bd0412746a18fe934bf2", + "093c47f243939bba99b9324b20765450e5a8b3dc06f4d88305343d7445b926bca0c3f58b63d3542662135afc604fb9631ac06cb2bf882928ccf405a5921cb88815734f77702e6abc74e115456391536bd42af1", + ""); + TestBIP324PacketVector( + 223, + "6c77432d1fda31e9f942f8af44607e10f3ad38a65f8a4bddae823e5eff90dc38", + "d2685070c1e6376e633e825296634fd461fa9e5bdf2109bcebd735e5a91f3e587c5cb782abb797fbf6bb5074fd1542a474f2a45b673763ec2db7fb99b737bbb9", + "56bd0c06f10352c3a1a9f4b4c92f6fa2b26df124b57878353c1fc691c51abea77c8817daeeb9fa546b77c8daf79d89b22b0e1b87574ece42371f00237aa9d83a", + false, + "7e0e78eb6990b059e6cf0ded66ea93ef82e72aa2f18ac24f2fc6ebab561ae557420729da103f64cecfa20527e15f9fb669a49bbbf274ef0389b3e43c8c44e5f60bf2ac38e2b55e7ec4273dba15ba41d21f8f5b3ee1688b3c29951218caf847a97fb50d75a86515d445699497d968164bf740012679b8962de573be941c62b7ef", + 1, + "", + true, + "31044c8fb0118b25b175e8bef5a00745", + "698b8fe0582f447da04c501809348088", + "8ec2df42766d4ac2f05c0c26e5edc79924eea64d0bcb6527886e572207a02c48", + "", + "1f46a8bab91742665bb7e817b9d3f66d368540364a8bde713f6bb664b70f6caba3e2f94aa6bce78c012a8b041cb16e9c178261fc8b55b42ba3f5083fdc21863b7370170a8d378eb1ff047e37a05cf8036d7b833cd23ab6789fcabff5a26b74741767f3e4a91c6a43b89a00a508dcce868927af2ce563d16db6f37e09d2bd4ace"); + TestBIP324PacketVector( + 448, + "a6ec25127ca1aa4cf16b20084ba1e6516baae4d32422288e9b36d8bddd2de35a", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff053d7ecca53e33e185a8b9be4e7699a97c6ff4c795522e5918ab7cd6b6884f67e683f3dc", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffa7730be30000000000000000000000000000000000000000000000000000000000000000", + true, + "00cf68f8f7ac49ffaa02c4864fdf6dfe7bbf2c740b88d98c50ebafe32c92f3427f57601ffcb21a3435979287db8fee6c302926741f9d5e464c647eeb9b7acaeda46e00abd7506fc9a719847e9a7328215801e96198dac141a15c7c2f68e0690dd1176292a0dded04d1f548aad88f1aebdc0a8f87da4bb22df32dd7c160c225b843e83f6525d6d484f502f16d923124fc538794e21da2eb689d18d87406ecced5b9f92137239ed1d37bcfa7836641a83cf5e0a1cf63f51b06f158e499a459ede41c", + 1, + "", + false, + "4b71504ba580ddb23d277144562f5747", + "1802ca38f6a90050209038e756f666df", + "27a98ef3e2e0e21b7ed907c90e4258a1912c5ee8d3ec9ebfe967233d9c83a4cd", + "", + "992d88b6e51042bd7ce24b2783663fcad5c251d1cd22d5a21e17012740f24a97e8c2031643c82a0fb8181f9aea3bc77b621d8769ed516f5e011d7418428524518088f2dd34558d716efbf81222c2469588f0f5fc11865299b764c107a74ab742e9c431381d78eeea57902a9c85a68709a55688bdd7e2fbbdacd5b6d35a936cc3"); + TestBIP324PacketVector( + 673, + "0af952659ed76f80f585966b95ab6e6fd68654672827878684c8b547b1b94f5a", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc81017fd92fd31637c26c906b42092e11cc0d3afae8d9019d2578af22735ce7bc469c72d", + "9652d78baefc028cd37a6a92625b8b8f85fde1e4c944ad3f20e198bef8c02f19fffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e91870", + false, + "5c6272ee55da855bbbf7b1246d9885aa7aa601a715ab86fa46c50da533badf82b97597c968293ae04e", + 97561, + "", + false, + "5ba28412779bc0303731e63e7017a17d", + "2d937cc97a3d6fa883c7d435be1e0fbf", + "cef5f004931f1fef79a9c54fc34308a6367217b2875fa5dbe074e819d46859d4", + "", + "1ba334b13c38704903189f73cc3be82681e1a665863d564a18feb7d0cd32ce7e93d3663f66a2d73b7693619e65f5cd7705e9da9fdceb483d8e073c396ecb452b7465df939745ce52075a78da5c9817d7a972e594a2357a60b36c807a1a9f39e11affde6823137c1f27d134f3b2b76d442c2a64d738b6de453063eb585dd6dcd5"); + TestBIP324PacketVector( + 1024, + "f90e080c64b05824c5a24b2501d5aeaf08af3872ee860aa80bdcd430f7b63494", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff115173765dc202cf029ad3f15479735d57697af12b0131dd21430d5772e4ef11474d58b9", + "12a50f3fafea7c1eeada4cf8d33777704b77361453afc83bda91eef349ae044d20126c6200547ea5a6911776c05dee2a7f1a9ba7dfbabbbd273c3ef29ef46e46", + true, + "5f67d15d22ca9b2804eeab0a66f7f8e3a10fa5de5809a046084348cbc5304e843ef96f59a59c7d7fdfe5946489f3ea297d941bac326225df316a25fc90f0e65b0d31a9c497e960fdbf8c482516bc8a9c1c77b7f6d0e1143810c737f76f9224e6f2c9af5186b4f7259c7e8d165b6e4fe3d38a60bdbdd4d06ecdcaaf62086070dbb68686b802d53dfd7db14b18743832605f5461ad81e2af4b7e8ff0eff0867a25b93cec7becf15c43131895fed09a83bf1ee4a87d44dd0f02a837bf5a1232e201cb882734eb9643dc2dc4d4e8b5690840766212c7ac8f38ad8a9ec47c7a9b3e022ae3eb6a32522128b518bd0d0085dd81c5", + 69615, + "", + true, + "02ac125939ff56d8ab46cb240593798c", + "f487ef08611fcd6bf20e893bdc1d00af", + "eb62406ae40d839381b79a5c29c43d6ab3491e88e73271ae3d5e1c7f41105de0", + "", + "fc24d1c073a931ccfe99d8a0fea9b2ec342d2267e3e9683e934d89574fa0167ec1dde7eab0f5447b363485f1937c178c741cb42f35fdf6acfb15aae2fcd23ddec8046d1e66511bec14305fd439324b98bdbbc63bd8f058532f413b14f582a9b9f61323f1377bddf63a579f8e8a1c541c97d26ef39ba9cce630ef3e9483242290"); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index e262b40d748d..bddab92fbc0c 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include #include @@ -131,31 +131,29 @@ static void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, b } } -static void TestChaCha20(const std::string &hex_message, const std::string &hexkey, uint64_t nonce, uint64_t seek, const std::string& hexout) +static void TestChaCha20(const std::string &hex_message, const std::string &hexkey, ChaCha20::Nonce96 nonce, uint32_t seek, const std::string& hexout) { - std::vector key = ParseHex(hexkey); + auto key = ParseHex(hexkey); assert(key.size() == 32); - std::vector m = ParseHex(hex_message); - ChaCha20 rng(key.data()); - rng.SetIV(nonce); - rng.Seek64(seek); - std::vector outres; + auto m = ParseHex(hex_message); + ChaCha20 rng{key}; + rng.Seek(nonce, seek); + std::vector outres; outres.resize(hexout.size() / 2); assert(hex_message.empty() || m.size() * 2 == hexout.size()); // perform the ChaCha20 round(s), if message is provided it will output the encrypted ciphertext otherwise the keystream if (!hex_message.empty()) { - rng.Crypt(m.data(), outres.data(), outres.size()); + rng.Crypt(m, outres); } else { - rng.Keystream(outres.data(), outres.size()); + rng.Keystream(outres); } BOOST_CHECK_EQUAL(hexout, HexStr(outres)); if (!hex_message.empty()) { // Manually XOR with the keystream and compare the output - rng.SetIV(nonce); - rng.Seek64(seek); - std::vector only_keystream(outres.size()); - rng.Keystream(only_keystream.data(), only_keystream.size()); + rng.Seek(nonce, seek); + std::vector only_keystream(outres.size()); + rng.Keystream(only_keystream); for (size_t i = 0; i != m.size(); i++) { outres[i] = m[i] ^ only_keystream[i]; } @@ -169,14 +167,14 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk lens[1] = InsecureRandRange(hexout.size() / 2U + 1U - lens[0]); lens[2] = hexout.size() / 2U - lens[0] - lens[1]; - rng.Seek64(seek); - outres.assign(hexout.size() / 2U, 0); + rng.Seek(nonce, seek); + outres.assign(hexout.size() / 2U, {}); size_t pos = 0; for (int j = 0; j < 3; ++j) { if (!hex_message.empty()) { - rng.Crypt(m.data() + pos, outres.data() + pos, lens[j]); + rng.Crypt(Span{m}.subspan(pos, lens[j]), Span{outres}.subspan(pos, lens[j])); } else { - rng.Keystream(outres.data() + pos, lens[j]); + rng.Keystream(Span{outres}.subspan(pos, lens[j])); } pos += lens[j]; } @@ -184,15 +182,156 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk } } +static void TestFSChaCha20(const std::string& hex_plaintext, const std::string& hexkey, uint32_t rekey_interval, const std::string& ciphertext_after_rotation) +{ + auto key = ParseHex(hexkey); + BOOST_CHECK_EQUAL(FSChaCha20::KEYLEN, key.size()); + + auto plaintext = ParseHex(hex_plaintext); + + auto fsc20 = FSChaCha20{key, rekey_interval}; + auto c20 = ChaCha20{key}; + + 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(plaintext, fsc20_output); + c20.Crypt(plaintext, c20_output); + BOOST_CHECK(c20_output == fsc20_output); + } + + // At the rotation interval, the outputs will no longer match + fsc20.Crypt(plaintext, fsc20_output); + auto c20_copy = c20; + c20.Crypt(plaintext, c20_output); + BOOST_CHECK(c20_output != fsc20_output); + + std::byte new_key[FSChaCha20::KEYLEN]; + c20_copy.Keystream(new_key); + c20.SetKey(new_key); + c20.Seek({0, 1}, 0); + + // Outputs should match again after simulating key rotation + c20.Crypt(plaintext, c20_output); + BOOST_CHECK(c20_output == fsc20_output); + + BOOST_CHECK_EQUAL(HexStr(fsc20_output), ciphertext_after_rotation); +} + static void TestPoly1305(const std::string &hexmessage, const std::string &hexkey, const std::string& hextag) { - std::vector key = ParseHex(hexkey); - std::vector m = ParseHex(hexmessage); - std::vector tag = ParseHex(hextag); - std::vector tagres; - tagres.resize(POLY1305_TAGLEN); - poly1305_auth(tagres.data(), m.data(), m.size(), key.data()); - BOOST_CHECK(tag == tagres); + auto key = ParseHex(hexkey); + auto m = ParseHex(hexmessage); + std::vector tagres(Poly1305::TAGLEN); + Poly1305{key}.Update(m).Finalize(tagres); + BOOST_CHECK_EQUAL(HexStr(tagres), hextag); + + // Test incremental interface + for (int splits = 0; splits < 10; ++splits) { + for (int iter = 0; iter < 10; ++iter) { + auto data = Span{m}; + Poly1305 poly1305{key}; + for (int chunk = 0; chunk < splits; ++chunk) { + size_t now = InsecureRandRange(data.size() + 1); + poly1305.Update(data.first(now)); + data = data.subspan(now); + } + tagres.assign(Poly1305::TAGLEN, std::byte{}); + poly1305.Update(data).Finalize(tagres); + BOOST_CHECK_EQUAL(HexStr(tagres), hextag); + } + } +} + +static void TestChaCha20Poly1305(const std::string& plain_hex, const std::string& aad_hex, const std::string& key_hex, ChaCha20::Nonce96 nonce, const std::string& cipher_hex) +{ + auto plain = ParseHex(plain_hex); + auto aad = ParseHex(aad_hex); + auto key = ParseHex(key_hex); + auto expected_cipher = ParseHex(cipher_hex); + + for (int i = 0; i < 10; ++i) { + // During i=0, use single-plain Encrypt/Decrypt; others use a split at prefix. + size_t prefix = i ? InsecureRandRange(plain.size() + 1) : plain.size(); + // Encrypt. + std::vector cipher(plain.size() + AEADChaCha20Poly1305::EXPANSION); + AEADChaCha20Poly1305 aead{key}; + if (i == 0) { + aead.Encrypt(plain, aad, nonce, cipher); + } else { + aead.Encrypt(Span{plain}.first(prefix), Span{plain}.subspan(prefix), aad, nonce, cipher); + } + BOOST_CHECK(cipher == expected_cipher); + + // Decrypt. + std::vector decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION); + bool ret{false}; + if (i == 0) { + ret = aead.Decrypt(cipher, aad, nonce, decipher); + } else { + ret = aead.Decrypt(cipher, aad, nonce, Span{decipher}.first(prefix), Span{decipher}.subspan(prefix)); + } + BOOST_CHECK(ret); + BOOST_CHECK(decipher == plain); + } + + // Test Keystream output. + std::vector keystream(plain.size()); + AEADChaCha20Poly1305 aead{key}; + aead.Keystream(nonce, keystream); + for (size_t i = 0; i < plain.size(); ++i) { + BOOST_CHECK_EQUAL(plain[i] ^ keystream[i], expected_cipher[i]); + } +} + +static void TestFSChaCha20Poly1305(const std::string& plain_hex, const std::string& aad_hex, const std::string& key_hex, uint64_t msg_idx, const std::string& cipher_hex) +{ + auto plain = ParseHex(plain_hex); + auto aad = ParseHex(aad_hex); + auto key = ParseHex(key_hex); + auto expected_cipher = ParseHex(cipher_hex); + std::vector cipher(plain.size() + FSChaCha20Poly1305::EXPANSION); + + for (int it = 0; it < 10; ++it) { + // During it==0 we use the single-plain Encrypt/Decrypt; others use a split at prefix. + size_t prefix = it ? InsecureRandRange(plain.size() + 1) : plain.size(); + std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}}; + + // Do msg_idx dummy encryptions to seek to the correct packet. + FSChaCha20Poly1305 enc_aead{key, 224}; + for (uint64_t i = 0; i < msg_idx; ++i) { + enc_aead.Encrypt(Span{dummy_tag}.first(0), Span{dummy_tag}.first(0), dummy_tag); + } + + // Invoke single-plain or plain1/plain2 Encrypt. + if (it == 0) { + enc_aead.Encrypt(plain, aad, cipher); + } else { + enc_aead.Encrypt(Span{plain}.first(prefix), Span{plain}.subspan(prefix), aad, cipher); + } + BOOST_CHECK(cipher == expected_cipher); + + // Do msg_idx dummy decryptions to seek to the correct packet. + FSChaCha20Poly1305 dec_aead{key, 224}; + for (uint64_t i = 0; i < msg_idx; ++i) { + dec_aead.Decrypt(dummy_tag, Span{dummy_tag}.first(0), Span{dummy_tag}.first(0)); + } + + // Invoke single-plain or plain1/plain2 Decrypt. + std::vector decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION); + bool ret{false}; + if (it == 0) { + ret = dec_aead.Decrypt(cipher, aad, decipher); + } else { + ret = dec_aead.Decrypt(cipher, aad, Span{decipher}.first(prefix), Span{decipher}.subspan(prefix)); + } + BOOST_CHECK(ret); + BOOST_CHECK(decipher == plain); + } } static void TestHKDF_SHA256_32(const std::string &ikm_hex, const std::string &salt_hex, const std::string &info_hex, const std::string &okm_check_hex) { @@ -505,38 +644,57 @@ BOOST_AUTO_TEST_CASE(pbkdf2_hmac_sha512_test) { BOOST_AUTO_TEST_CASE(chacha20_testvector) { + /* Example from RFC8439 section 2.3.2. */ + TestChaCha20("", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + {0x09000000, 0x4a000000}, 1, + "10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4e" + "d2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e"); + + /* Example from RFC8439 section 2.4.2. */ + TestChaCha20("4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + "637265656e20776f756c642062652069742e", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + {0, 0x4a000000}, 1, + "6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b" + "f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8" + "07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736" + "5af90bbf74a35be6b40b8eedf2785e42874d"); + // RFC 7539/8439 A.1 Test Vector #1: TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", - 0, 0, + {0, 0}, 0, "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); // RFC 7539/8439 A.1 Test Vector #2: TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", - 0, 1, + {0, 0}, 1, "9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed" "29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f"); // RFC 7539/8439 A.1 Test Vector #3: TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000001", - 0, 1, + {0, 0}, 1, "3aeb5224ecf849929b9d828db1ced4dd832025e8018b8160b82284f3c949aa5a" "8eca00bbb4a73bdad192b5c42f73f2fd4e273644c8b36125a64addeb006c13a0"); // RFC 7539/8439 A.1 Test Vector #4: TestChaCha20("", "00ff000000000000000000000000000000000000000000000000000000000000", - 0, 2, + {0, 0}, 2, "72d54dfbf12ec44b362692df94137f328fea8da73990265ec1bbbea1ae9af0ca" "13b25aa26cb4a648cb9b9d1be65b2c0924a66c54d545ec1b7374f4872e99f096"); // RFC 7539/8439 A.1 Test Vector #5: TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", - 0x200000000000000, 0, + {0, 0x200000000000000}, 0, "c2c64d378cd536374ae204b9ef933fcd1a8b2288b3dfa49672ab765b54ee27c7" "8a970e0e955c14f3a88e741b97c286f75f8fc299e8148362fa198a39531bed6d"); @@ -544,7 +702,7 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector) TestChaCha20("0000000000000000000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", - 0, 0, + {0, 0}, 0, "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); @@ -562,7 +720,7 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector) "74696f6e73206d61646520617420616e792074696d65206f7220706c6163652c" "207768696368206172652061646472657373656420746f", "0000000000000000000000000000000000000000000000000000000000000001", - 0x200000000000000, 1, + {0, 0x200000000000000}, 1, "a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec" "2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d" "4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e527950" @@ -582,45 +740,125 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector) "6162653a0a416c6c206d696d737920776572652074686520626f726f676f7665" "732c0a416e6420746865206d6f6d65207261746873206f757467726162652e", "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", - 0x200000000000000, 42, + {0, 0x200000000000000}, 42, "62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf" "166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553eb" "f39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f77" "04c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1"); + // RFC 7539/8439 A.4 Test Vector #1: + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000000", + {0, 0}, 0, + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7"); + + // RFC 7539/8439 A.4 Test Vector #2: + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000001", + {0, 0x200000000000000}, 0, + "ecfa254f845f647473d3cb140da9e87606cb33066c447b87bc2666dde3fbb739"); + + // RFC 7539/8439 A.4 Test Vector #3: + TestChaCha20("", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + {0, 0x200000000000000}, 0, + "965e3bc6f9ec7ed9560808f4d229f94b137ff275ca9b3fcbdd59deaad23310ae"); + // test encryption TestChaCha20("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756" "c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e" "20776f756c642062652069742e", - "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0x4a000000UL, 1, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", {0, 0x4a000000UL}, 1, "6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0bf91b65c5524733ab8f593dabcd62b3571639d" "624e65152ab8f530c359f0861d807ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab77937365af90bbf74" "a35be6b40b8eedf2785e42874d" ); // test keystream output - TestChaCha20("", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0x4a000000UL, 1, + TestChaCha20("", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", {0, 0x4a000000UL}, 1, "224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cb" "a40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a" "832c89c167eacd901d7e2bf363"); + + // Test vectors from https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 + // The first one is identical to the above one from the RFC8439 A.1 vectors, but repeated here + // for completeness. + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000000", + {0, 0}, 0, + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" + "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000001", + {0, 0}, 0, + "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41" + "bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963"); + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000000", + {0, 0x0100000000000000ULL}, 0, + "de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031" + "e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3"); + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000000", + {0, 1}, 0, + "ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32" + "111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"); + TestChaCha20("", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + {0, 0x0706050403020100ULL}, 0, + "f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c1" + "34a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a" + "38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd" + "9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c7" + "9db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025" + "d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d7" + "0eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2" + "ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9"); + + // Test overflow of 32-bit block counter, should increment the first 32-bit + // part of the nonce to retain compatibility with >256 GiB output. + // The test data was generated with an implementation that uses a 64-bit + // counter and a 64-bit initialization vector (PyCryptodome's ChaCha20 class + // with 8 bytes nonce length). + TestChaCha20("", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + {0, 0xdeadbeef12345678}, 0xffffffff, + "2d292c880513397b91221c3a647cfb0765a4815894715f411e3df5e0dd0ba9df" + "fd565dea5addbdb914208fde7950f23e0385f9a727143f6a6ac51d84b1c0fb3e" + "2e3b00b63d6841a1cc6d1538b1d3a74bef1eb2f54c7b7281e36e484dba89b351" + "c8f572617e61e342879f211b0e4c515df50ea9d0771518fad96cd0baee62deb6"); + + // Forward secure ChaCha20 + TestFSChaCha20("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "0000000000000000000000000000000000000000000000000000000000000000", + 256, + "a93df4ef03011f3db95f60d996e1785df5de38fc39bfcb663a47bb5561928349"); + TestFSChaCha20("01", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + 5, + "ea"); + TestFSChaCha20("e93fdb5c762804b9a706816aca31e35b11d2aa3080108ef46a5b1f1508819c0a", + "8ec4c3ccdaea336bdeb245636970be01266509b33f3d2642504eaf412206207a", + 4096, + "8bfaa4eacff308fdb4a94a5ff25bd9d0c1f84b77f81239f67ff39d6e1ac280c9"); } BOOST_AUTO_TEST_CASE(chacha20_midblock) { - auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000"); - ChaCha20 c20{key.data()}; + auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000"); + ChaCha20 c20{key}; // get one block of keystream - unsigned char block[64]; - c20.Keystream(block, CHACHA20_ROUND_OUTPUT); - unsigned char b1[5], b2[7], b3[52]; - c20 = ChaCha20{key.data()}; - c20.Keystream(b1, 5); - c20.Keystream(b2, 7); - c20.Keystream(b3, 52); - - BOOST_CHECK_EQUAL(0, memcmp(b1, block, 5)); - BOOST_CHECK_EQUAL(0, memcmp(b2, block + 5, 7)); - BOOST_CHECK_EQUAL(0, memcmp(b3, block + 12, 52)); + std::byte block[64]; + c20.Keystream(block); + std::byte b1[5], b2[7], b3[52]; + c20 = ChaCha20{key}; + c20.Keystream(b1); + c20.Keystream(b2); + c20.Keystream(b3); + + BOOST_CHECK(Span{block}.first(5) == Span{b1}); + BOOST_CHECK(Span{block}.subspan(5, 7) == Span{b2}); + BOOST_CHECK(Span{block}.last(52) == Span{b3}); } BOOST_AUTO_TEST_CASE(poly1305_testvector) @@ -691,6 +929,138 @@ BOOST_AUTO_TEST_CASE(poly1305_testvector) TestPoly1305("e33594d7505e43b900000000000000003394d7505e4379cd010000000000000000000000000000000000000000000000", "0100000000000000040000000000000000000000000000000000000000000000", "13000000000000000000000000000000"); + + // Tests from https://github.com/floodyberry/poly1305-donna/blob/master/poly1305-donna.c + TestPoly1305("8e993b9f48681273c29650ba32fc76ce48332ea7164d96a4476fb8c531a1186a" + "c0dfc17c98dce87b4da7f011ec48c97271d2c20f9b928fe2270d6fb863d51738" + "b48eeee314a7cc8ab932164548e526ae90224368517acfeabd6bb3732bc0e9da" + "99832b61ca01b6de56244a9e88d5f9b37973f622a43d14a6599b1f654cb45a74" + "e355a5", + "eea6a7251c1e72916d11c2cb214d3c252539121d8e234e652d651fa4c8cff880", + "f3ffc7703f9400e52a7dfb4b3d3305d9"); + { + // mac of the macs of messages of length 0 to 256, where the key and messages have all + // their values set to the length. + auto total_key = ParseHex("01020304050607fffefdfcfbfaf9ffffffffffffffffffffffffffff00000000"); + Poly1305 total_ctx(total_key); + for (unsigned i = 0; i < 256; ++i) { + std::vector key(32, std::byte{uint8_t(i)}); + std::vector msg(i, std::byte{uint8_t(i)}); + std::array tag; + Poly1305{key}.Update(msg).Finalize(tag); + total_ctx.Update(tag); + } + std::vector total_tag(Poly1305::TAGLEN); + total_ctx.Finalize(total_tag); + BOOST_CHECK_EQUAL(HexStr(total_tag), "64afe2e8d6ad7bbdd287f97c44623d39"); + } + + // Tests with sparse messages and random keys. + TestPoly1305("000000000000000000000094000000000000b07c4300000000002c002600d500" + "00000000000000000000000000bc58000000000000000000c9000000dd000000" + "00000000000000d34c000000000000000000000000f9009100000000000000c2" + "4b0000e900000000000000000000000000000000000e00000027000074000000" + "0000000003000000000000f1000000000000dce2000000000000003900000000" + "0000000000000000000000000000000000000000000000520000000000000000" + "000000000000000000000000009500000000000000000000000000cf00826700" + "000000a900000000000000000000000000000000000000000079000000000000" + "0000de0000004c000000000033000000000000000000000000002800aa000000" + "00003300860000e000000000", + "6e543496db3cf677592989891ab021f58390feb84fb419fbc7bb516a60bfa302", + "7ea80968354d40d9d790b45310caf7f3"); + TestPoly1305("0000005900000000c40000002f00000000000000000000000000000029690000" + "0000e8000037000000000000000000000000000b000000000000000000000000" + "000000000000000000000000001800006e0000000000a4000000000000000000" + "00000000000000004d00000000000000b0000000000000000000005a00000000" + "0000000000b7c300000000000000540000000000000000000000000a00000000" + "00005b0000000000000000000000000000000000002d00e70000000000000000" + "000000000000003400006800d700000000000000000000360000000000000000" + "00eb000000000000000000000000000000000000000000000000000028000000" + "37000000000000000000000000000000000000000000000000000000008f0000" + "000000000000000000000000", + "f0b659a4f3143d8a1e1dacb9a409fe7e7cd501dfb58b16a2623046c5d337922a", + "0e410fa9d7a40ac582e77546be9a72bb"); +} + +BOOST_AUTO_TEST_CASE(chacha20poly1305_testvectors) +{ + // Note that in our implementation, the authentication is suffixed to the ciphertext. + // The RFC test vectors specify them separately. + + // RFC 8439 Example from section 2.8.2 + TestChaCha20Poly1305("4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + "637265656e20776f756c642062652069742e", + "50515253c0c1c2c3c4c5c6c7", + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + {7, 0x4746454443424140}, + "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" + "0691"); + + // RFC 8439 Test vector A.5 + TestChaCha20Poly1305("496e7465726e65742d4472616674732061726520647261667420646f63756d65" + "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" + "6f6e74687320616e64206d617920626520757064617465642c207265706c6163" + "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" + "6e747320617420616e792074696d652e20497420697320696e617070726f7072" + "6961746520746f2075736520496e7465726e65742d4472616674732061732072" + "65666572656e6365206d6174657269616c206f7220746f206369746520746865" + "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" + "726573732e2fe2809d", + "f33388860000000000004e91", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + {0, 0x0807060504030201}, + "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" + "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" + "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" + "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" + "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" + "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" + "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" + "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" + "a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38"); + + // Test vectors exercising aad and plaintext which are multiples of 16 bytes. + TestChaCha20Poly1305("8d2d6a8befd9716fab35819eaac83b33269afb9f1a00fddf66095a6c0cd91951" + "a6b7ad3db580be0674c3f0b55f618e34", + "", + "72ddc73f07101282bbbcf853b9012a9f9695fc5d36b303a97fd0845d0314e0c3", + {0x3432b75f, 0xb3585537eb7f4024}, + "f760b8224fb2a317b1b07875092606131232a5b86ae142df5df1c846a7f6341a" + "f2564483dd77f836be45e6230808ffe402a6f0a3e8be074b3d1f4ea8a7b09451"); + TestChaCha20Poly1305("", + "36970d8a704c065de16250c18033de5a400520ac1b5842b24551e5823a3314f3" + "946285171e04a81ebfbe3566e312e74ab80e94c7dd2ff4e10de0098a58d0f503", + "77adda51d6730b9ad6c995658cbd49f581b2547e7c0c08fcc24ceec797461021", + {0x1f90da88, 0x75dafa3ef84471a4}, + "aaae5bb81e8407c94b2ae86ae0c7efbe"); + + // FSChaCha20Poly1305 tests. + TestFSChaCha20Poly1305("d6a4cb04ef0f7c09c1866ed29dc24d820e75b0491032a51b4c3366f9ca35c19e" + "a3047ec6be9d45f9637b63e1cf9eb4c2523a5aab7b851ebeba87199db0e839cf" + "0d5c25e50168306377aedbe9089fd2463ded88b83211cf51b73b150608cc7a60" + "0d0f11b9a742948482e1b109d8faf15b450aa7322e892fa2208c6691e3fecf4c" + "711191b14d75a72147", + "786cb9b6ebf44288974cf0", + "5c9e1c3951a74fba66708bf9d2c217571684556b6a6a3573bff2847d38612654", + 500, + "9dcebbd3281ea3dd8e9a1ef7d55a97abd6743e56ebc0c190cb2c4e14160b385e" + "0bf508dddf754bd02c7c208447c131ce23e47a4a14dfaf5dd8bc601323950f75" + "4e05d46e9232f83fc5120fbbef6f5347a826ec79a93820718d4ec7a2b7cfaaa4" + "4b21e16d726448b62f803811aff4f6d827ed78e738ce8a507b81a8ae13131192" + "8039213de18a5120dc9b7370baca878f50ff254418de3da50c"); + TestFSChaCha20Poly1305("8349b7a2690b63d01204800c288ff1138a1d473c832c90ea8b3fc102d0bb3adc" + "44261b247c7c3d6760bfbe979d061c305f46d94c0582ac3099f0bf249f8cb234", + "", + "3bd2093fcbcb0d034d8c569583c5425c1a53171ea299f8cc3bbf9ae3530adfce", + 60000, + "30a6757ff8439b975363f166a0fa0e36722ab35936abd704297948f45083f4d4" + "99433137ce931f7fca28a0acd3bc30f57b550acbc21cbd45bbef0739d9caf30c" + "14b94829deb27f0b1923a2af704ae5d6"); } BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests) @@ -713,131 +1083,6 @@ 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) -{ - // 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); - 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 - 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); - BOOST_CHECK(res); - - // make sure we repetitive get the same plaintext - BOOST_CHECK(memcmp(plaintext_buf.data(), plaintext_buf_new.data(), plaintext_buf.size()) == 0); - - // 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++; - } - } -} - -BOOST_AUTO_TEST_CASE(chacha20_poly1305_aead_testvector) -{ - /* 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"); -} - BOOST_AUTO_TEST_CASE(countbits_tests) { FastRandomContext ctx; diff --git a/src/test/fuzz/bip324.cpp b/src/test/fuzz/bip324.cpp new file mode 100644 index 000000000000..276d2c63007b --- /dev/null +++ b/src/test/fuzz/bip324.cpp @@ -0,0 +1,132 @@ +// Copyright (c) 2023 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 initialize_bip324() +{ + ECC_Start(); + SelectParams(CBaseChainParams::MAIN); +} + +FUZZ_TARGET_INIT(bip324_cipher_roundtrip, initialize_bip324) +{ + // Test that BIP324Cipher's encryption and decryption agree. + + // Load keys from fuzzer. + FuzzedDataProvider provider(buffer.data(), buffer.size()); + // Initiator key + auto init_key_data = provider.ConsumeBytes(32); + init_key_data.resize(32); + CKey init_key; + init_key.Set(init_key_data.begin(), init_key_data.end(), true); + if (!init_key.IsValid()) return; + // Initiator entropy + auto init_ent = provider.ConsumeBytes(32); + init_ent.resize(32); + // Responder key + auto resp_key_data = provider.ConsumeBytes(32); + resp_key_data.resize(32); + CKey resp_key; + resp_key.Set(resp_key_data.begin(), resp_key_data.end(), true); + if (!resp_key.IsValid()) return; + // Responder entropy + auto resp_ent = provider.ConsumeBytes(32); + resp_ent.resize(32); + + // Initialize ciphers by exchanging public keys. + BIP324Cipher initiator(init_key, init_ent); + assert(!initiator); + BIP324Cipher responder(resp_key, resp_ent); + assert(!responder); + initiator.Initialize(responder.GetOurPubKey(), true); + assert(initiator); + responder.Initialize(initiator.GetOurPubKey(), false); + assert(responder); + + // Initialize RNG deterministically, to generate contents and AAD. We assume that there are no + // (potentially buggy) edge cases triggered by specific values of contents/AAD, so we can avoid + // reading the actual data for those from the fuzzer input (which would need large amounts of + // data). + XoRoShiRo128PlusPlus rng(provider.ConsumeIntegral()); + + // Compare session IDs and garbage terminators. + assert(initiator.GetSessionID() == responder.GetSessionID()); + assert(initiator.GetSendGarbageTerminator() == responder.GetReceiveGarbageTerminator()); + assert(initiator.GetReceiveGarbageTerminator() == responder.GetSendGarbageTerminator()); + + LIMITED_WHILE(provider.remaining_bytes(), 1000) { + // Mode: + // - Bit 0: whether the ignore bit is set in message + // - Bit 1: whether the responder (0) or initiator (1) sends + // - Bit 2: whether this ciphertext will be corrupted (making it the last sent one) + // - Bit 3-4: controls the maximum aad length (max 4095 bytes) + // - Bit 5-7: controls the maximum content length (max 16383 bytes, for performance reasons) + unsigned mode = provider.ConsumeIntegral(); + bool ignore = mode & 1; + bool from_init = mode & 2; + bool damage = mode & 4; + unsigned aad_length_bits = 4 * ((mode >> 3) & 3); + unsigned aad_length = provider.ConsumeIntegralInRange(0, (1 << aad_length_bits) - 1); + unsigned length_bits = 2 * ((mode >> 5) & 7); + unsigned length = provider.ConsumeIntegralInRange(0, (1 << length_bits) - 1); + // Generate aad and content. + std::vector aad(aad_length); + for (auto& val : aad) val = std::byte{(uint8_t)rng()}; + std::vector contents(length); + for (auto& val : contents) val = std::byte{(uint8_t)rng()}; + + // Pick sides. + auto& sender{from_init ? initiator : responder}; + auto& receiver{from_init ? responder : initiator}; + + // Encrypt + std::vector ciphertext(length + initiator.EXPANSION); + sender.Encrypt(contents, aad, ignore, ciphertext); + + // Optionally damage 1 bit in either the ciphertext (corresponding to a change in transit) + // or the aad (to make sure that decryption will fail if the AAD mismatches). + if (damage) { + unsigned damage_bit = provider.ConsumeIntegralInRange(0, + (ciphertext.size() + aad.size()) * 8U - 1U); + unsigned damage_pos = damage_bit >> 3; + std::byte damage_val{(uint8_t)(1U << (damage_bit & 3))}; + if (damage_pos >= ciphertext.size()) { + aad[damage_pos - ciphertext.size()] ^= damage_val; + } else { + ciphertext[damage_pos] ^= damage_val; + } + } + + // Decrypt length + uint32_t dec_length = receiver.DecryptLength(Span{ciphertext}.first(initiator.LENGTH_LEN)); + if (!damage) { + assert(dec_length == length); + } else { + // For performance reasons, don't try to decode if length got increased too much. + if (dec_length > 16384 + length) break; + // Otherwise, just append zeros if dec_length > length. + ciphertext.resize(dec_length + initiator.EXPANSION); + } + + // Decrypt + std::vector decrypt(dec_length); + bool dec_ignore{false}; + bool ok = receiver.Decrypt(Span{ciphertext}.subspan(initiator.LENGTH_LEN), aad, dec_ignore, decrypt); + // Decryption *must* fail if the packet was damaged, and succeed if it wasn't. + assert(!ok == damage); + if (!ok) break; + assert(ignore == dec_ignore); + assert(decrypt == contents); + } +} diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index 109c06ccb693..9f96664d1ffc 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 @@ -15,32 +17,31 @@ FUZZ_TARGET(crypto_chacha20) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - ChaCha20 chacha20; - if (fuzzed_data_provider.ConsumeBool()) { - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); - chacha20 = ChaCha20{key.data()}; - } + const auto key = ConsumeFixedLengthByteVector(fuzzed_data_provider, ChaCha20::KEYLEN); + ChaCha20 chacha20{key}; + while (fuzzed_data_provider.ConsumeBool()) { CallOneOf( fuzzed_data_provider, [&] { - std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); - chacha20.SetKey32(key.data()); - }, - [&] { - chacha20.SetIV(fuzzed_data_provider.ConsumeIntegral()); + auto key = ConsumeFixedLengthByteVector(fuzzed_data_provider, ChaCha20::KEYLEN); + chacha20.SetKey(key); }, [&] { - chacha20.Seek64(fuzzed_data_provider.ConsumeIntegral()); + chacha20.Seek( + { + fuzzed_data_provider.ConsumeIntegral(), + fuzzed_data_provider.ConsumeIntegral() + }, fuzzed_data_provider.ConsumeIntegral()); }, [&] { std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); - chacha20.Keystream(output.data(), output.size()); + chacha20.Keystream(MakeWritableByteSpan(output)); }, [&] { - std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); - const std::vector input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); - chacha20.Crypt(input.data(), output.data(), input.size()); + std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); + const auto input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); + chacha20.Crypt(input, output); }); } } @@ -59,24 +60,21 @@ template void ChaCha20SplitFuzz(FuzzedDataProvider& provider) { // Determine key, iv, start position, length. - unsigned char key[32] = {0}; - auto key_bytes = provider.ConsumeBytes(32); - std::copy(key_bytes.begin(), key_bytes.end(), key); + auto key_bytes = ConsumeFixedLengthByteVector(provider, ChaCha20::KEYLEN); uint64_t iv = provider.ConsumeIntegral(); + uint32_t iv_prefix = provider.ConsumeIntegral(); uint64_t total_bytes = provider.ConsumeIntegralInRange(0, 1000000); - /* ~x = 2^64 - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */ - uint64_t seek = provider.ConsumeIntegralInRange(0, ~(total_bytes >> 6)); + /* ~x = 2^BITS - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */ + uint32_t seek = provider.ConsumeIntegralInRange(0, ~(uint32_t)(total_bytes >> 6)); // Initialize two ChaCha20 ciphers, with the same key/iv/position. - ChaCha20 crypt1(key); - ChaCha20 crypt2(key); - crypt1.SetIV(iv); - crypt1.Seek64(seek); - crypt2.SetIV(iv); - crypt2.Seek64(seek); + ChaCha20 crypt1(key_bytes); + ChaCha20 crypt2(key_bytes); + crypt1.Seek({iv_prefix, iv}, seek); + crypt2.Seek({iv_prefix, iv}, seek); // Construct vectors with data. - std::vector data1, data2; + std::vector data1, data2; data1.resize(total_bytes); data2.resize(total_bytes); @@ -88,14 +86,14 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider) uint64_t bytes = 0; while (bytes < (total_bytes & ~uint64_t{7})) { uint64_t val = rng(); - WriteLE64(data1.data() + bytes, val); - WriteLE64(data2.data() + bytes, val); + WriteLE64(UCharCast(data1.data() + bytes), val); + WriteLE64(UCharCast(data2.data() + bytes), val); bytes += 8; } if (bytes < total_bytes) { - unsigned char valbytes[8]; + std::byte valbytes[8]; uint64_t val = rng(); - WriteLE64(valbytes, val); + WriteLE64(UCharCast(valbytes), val); std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes); std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes); } @@ -106,9 +104,9 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider) // Encrypt data1, the whole array at once. if constexpr (UseCrypt) { - crypt1.Crypt(data1.data(), data1.data(), total_bytes); + crypt1.Crypt(data1, data1); } else { - crypt1.Keystream(data1.data(), total_bytes); + crypt1.Keystream(data1); } // Encrypt data2, in at most 256 chunks. @@ -125,9 +123,9 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider) // This tests that Keystream() has the same behavior as Crypt() applied // to 0x00 input bytes. if (UseCrypt || provider.ConsumeBool()) { - crypt2.Crypt(data2.data() + bytes2, data2.data() + bytes2, now); + crypt2.Crypt(Span{data2}.subspan(bytes2, now), Span{data2}.subspan(bytes2, now)); } else { - crypt2.Keystream(data2.data() + bytes2, now); + crypt2.Keystream(Span{data2}.subspan(bytes2, now)); } bytes2 += now; if (is_last) break; @@ -151,3 +149,21 @@ 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 = fuzzed_data_provider.ConsumeBytes(FSChaCha20::KEYLEN); + key.resize(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 9b1b9f5a7d97..000000000000 --- a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 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 -#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(); - while (fuzzed_data_provider.ConsumeBool()) { - 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_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp index 46122bf1284d..c815e47e0865 100644 --- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp +++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp @@ -267,23 +267,18 @@ void ECRYPT_keystream_bytes(ECRYPT_ctx* x, u8* stream, u32 bytes) FUZZ_TARGET(crypto_diff_fuzz_chacha20) { - static const unsigned char ZEROKEY[32] = {0}; FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - ChaCha20 chacha20; ECRYPT_ctx ctx; - if (fuzzed_data_provider.ConsumeBool()) { - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); - chacha20 = ChaCha20{key.data()}; - ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0); - } else { - // The default ChaCha20 constructor is equivalent to using the all-0 key. - ECRYPT_keysetup(&ctx, ZEROKEY, 256, 0); - } + const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); + ChaCha20 chacha20{MakeByteSpan(key)}; + ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0); - // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does + // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does static const uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + ChaCha20::Nonce96 nonce{0, 0}; + uint32_t counter{0}; ECRYPT_ivsetup(&ctx, iv); LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 3000) { @@ -291,46 +286,57 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) fuzzed_data_provider, [&] { const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); - chacha20.SetKey32(key.data()); + chacha20.SetKey(MakeByteSpan(key)); + nonce = {0, 0}; + counter = 0; ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0); - // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does + // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0}; ECRYPT_ivsetup(&ctx, iv); }, [&] { + uint32_t iv_prefix = fuzzed_data_provider.ConsumeIntegral(); uint64_t iv = fuzzed_data_provider.ConsumeIntegral(); - chacha20.SetIV(iv); + nonce = {iv_prefix, iv}; + counter = fuzzed_data_provider.ConsumeIntegral(); + chacha20.Seek(nonce, counter); + ctx.input[12] = counter; + ctx.input[13] = iv_prefix; ctx.input[14] = iv; ctx.input[15] = iv >> 32; }, - [&] { - uint64_t counter = fuzzed_data_provider.ConsumeIntegral(); - chacha20.Seek64(counter); - ctx.input[12] = counter; - ctx.input[13] = counter >> 32; - }, [&] { uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); - // DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that. - uint64_t pos = ctx.input[12] + (((uint64_t)ctx.input[13]) << 32) + ((integralInRange + 63) >> 6); std::vector output(integralInRange); - chacha20.Keystream(output.data(), output.size()); + chacha20.Keystream(MakeWritableByteSpan(output)); std::vector djb_output(integralInRange); ECRYPT_keystream_bytes(&ctx, djb_output.data(), djb_output.size()); assert(output == djb_output); - chacha20.Seek64(pos); + // DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that. + uint32_t old_counter = counter; + counter += (integralInRange + 63) >> 6; + if (counter < old_counter) ++nonce.first; + if (integralInRange & 63) { + chacha20.Seek(nonce, counter); + } + assert(counter == ctx.input[12]); }, [&] { uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); - // DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that. - uint64_t pos = ctx.input[12] + (((uint64_t)ctx.input[13]) << 32) + ((integralInRange + 63) >> 6); std::vector output(integralInRange); const std::vector input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); - chacha20.Crypt(input.data(), output.data(), input.size()); + chacha20.Crypt(MakeByteSpan(input), MakeWritableByteSpan(output)); std::vector djb_output(integralInRange); ECRYPT_encrypt_bytes(&ctx, input.data(), djb_output.data(), input.size()); assert(output == djb_output); - chacha20.Seek64(pos); + // DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that. + uint32_t old_counter = counter; + counter += (integralInRange + 63) >> 6; + if (counter < old_counter) ++nonce.first; + if (integralInRange & 63) { + chacha20.Seek(nonce, counter); + } + assert(counter == ctx.input[12]); }); } } diff --git a/src/test/fuzz/crypto_poly1305.cpp b/src/test/fuzz/crypto_poly1305.cpp index ac555ed68cef..6ce6648f56ab 100644 --- a/src/test/fuzz/crypto_poly1305.cpp +++ b/src/test/fuzz/crypto_poly1305.cpp @@ -14,9 +14,39 @@ FUZZ_TARGET(crypto_poly1305) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, POLY1305_KEYLEN); - const std::vector in = ConsumeRandomLengthByteVector(fuzzed_data_provider); + const auto key = ConsumeFixedLengthByteVector(fuzzed_data_provider, Poly1305::KEYLEN); + const auto in = ConsumeRandomLengthByteVector(fuzzed_data_provider); - std::vector tag_out(POLY1305_TAGLEN); - poly1305_auth(tag_out.data(), in.data(), in.size(), key.data()); + std::vector tag_out(Poly1305::TAGLEN); + Poly1305{key}.Update(in).Finalize(tag_out); +} + +FUZZ_TARGET(crypto_poly1305_split) +{ + FuzzedDataProvider provider{buffer.data(), buffer.size()}; + + // Read key and instantiate two Poly1305 objects with it. + auto key = provider.ConsumeBytes(Poly1305::KEYLEN); + key.resize(Poly1305::KEYLEN); + Poly1305 poly_full{key}, poly_split{key}; + + // Vector that holds all bytes processed so far. + std::vector total_input; + + // Process input in pieces. + LIMITED_WHILE(provider.remaining_bytes(), 100) { + auto in = ConsumeRandomLengthByteVector(provider); + poly_split.Update(in); + // Update total_input to match what was processed. + total_input.insert(total_input.end(), in.begin(), in.end()); + } + + // Process entire input at once. + poly_full.Update(total_input); + + // Verify both agree. + std::array tag_split, tag_full; + poly_split.Finalize(tag_split); + poly_full.Finalize(tag_full); + assert(tag_full == tag_split); } diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 0ff2855a2860..8bc55ffffa80 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -58,12 +58,16 @@ auto& PickValue(FuzzedDataProvider& fuzzed_data_provider, Collection& col) return *it; } -[[ nodiscard ]] inline std::vector ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional& max_length = std::nullopt) noexcept +template +[[ nodiscard ]] inline std::vector ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional& max_length = std::nullopt) noexcept { + static_assert(sizeof(B) == 1); const std::string s = max_length ? fuzzed_data_provider.ConsumeRandomLengthString(*max_length) : fuzzed_data_provider.ConsumeRandomLengthString(); - return {s.begin(), s.end()}; + std::vector ret(s.size()); + std::copy(s.begin(), s.end(), reinterpret_cast(ret.data())); + return ret; } [[ nodiscard ]] inline std::vector ConsumeRandomLengthBitVector(FuzzedDataProvider& fuzzed_data_provider, const std::optional& max_length = std::nullopt) noexcept @@ -255,14 +259,13 @@ inline void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider) noexcept * Returns a byte vector of specified size regardless of the number of remaining bytes available * from the fuzzer. Pads with zero value bytes if needed to achieve the specified size. */ -[[ nodiscard ]] inline std::vector ConsumeFixedLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t length) noexcept +template +[[ nodiscard ]] inline std::vector ConsumeFixedLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t length) noexcept { - std::vector result(length); - const std::vector random_bytes = fuzzed_data_provider.ConsumeBytes(length); - if (!random_bytes.empty()) { - std::memcpy(result.data(), random_bytes.data(), random_bytes.size()); - } - return result; + static_assert(sizeof(B) == 1); + auto random_bytes = fuzzed_data_provider.ConsumeBytes(length); + random_bytes.resize(length); + return random_bytes; } inline CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py index 0fb3713c6528..c8d7781e0d99 100755 --- a/test/functional/feature_utxo_set_hash.py +++ b/test/functional/feature_utxo_set_hash.py @@ -12,7 +12,7 @@ COutPoint, FromHex, ) -from test_framework.muhash import MuHash3072 +from test_framework.crypto.muhash import MuHash3072 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal diff --git a/test/functional/test_framework/blockfilter.py b/test/functional/test_framework/blockfilter.py index a30e37ea5bb1..3d6b38a23d0c 100644 --- a/test/functional/test_framework/blockfilter.py +++ b/test/functional/test_framework/blockfilter.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Helper routines relevant for compact block filters (BIP158). """ -from .siphash import siphash +from .crypto.siphash import siphash def bip158_basic_element_hash(script_pub_key, N, block_hash): diff --git a/test/functional/test_framework/crypto/bip324_cipher.py b/test/functional/test_framework/crypto/bip324_cipher.py new file mode 100644 index 000000000000..56190647f222 --- /dev/null +++ b/test/functional/test_framework/crypto/bip324_cipher.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +# 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. + +"""Test-only implementation of ChaCha20 Poly1305 AEAD Construction in RFC 8439 and FSChaCha20Poly1305 for BIP 324 + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. +""" + +import unittest + +from .chacha20 import chacha20_block, REKEY_INTERVAL +from .poly1305 import Poly1305 + + +def pad16(x): + if len(x) % 16 == 0: + return b'' + return b'\x00' * (16 - (len(x) % 16)) + + +def aead_chacha20_poly1305_encrypt(key, nonce, aad, plaintext): + """Encrypt a plaintext using ChaCha20Poly1305.""" + ret = bytearray() + msg_len = len(plaintext) + for i in range((msg_len + 63) // 64): + now = min(64, msg_len - 64 * i) + keystream = chacha20_block(key, nonce, i + 1) + for j in range(now): + ret.append(plaintext[j + 64 * i] ^ keystream[j]) + poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32]) + mac_data = aad + pad16(aad) + mac_data += ret + pad16(ret) + mac_data += len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little') + ret += poly1305.tag(mac_data) + return bytes(ret) + + +def aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext): + """Decrypt a ChaCha20Poly1305 ciphertext.""" + if len(ciphertext) < 16: + return None + msg_len = len(ciphertext) - 16 + poly1305 = Poly1305(chacha20_block(key, nonce, 0)[:32]) + mac_data = aad + pad16(aad) + mac_data += ciphertext[:-16] + pad16(ciphertext[:-16]) + mac_data += len(aad).to_bytes(8, 'little') + msg_len.to_bytes(8, 'little') + if ciphertext[-16:] != poly1305.tag(mac_data): + return None + ret = bytearray() + for i in range((msg_len + 63) // 64): + now = min(64, msg_len - 64 * i) + keystream = chacha20_block(key, nonce, i + 1) + for j in range(now): + ret.append(ciphertext[j + 64 * i] ^ keystream[j]) + return bytes(ret) + + +class FSChaCha20Poly1305: + """Rekeying wrapper AEAD around ChaCha20Poly1305.""" + def __init__(self, initial_key): + self._key = initial_key + self._packet_counter = 0 + + def _crypt(self, aad, text, is_decrypt): + nonce = ((self._packet_counter % REKEY_INTERVAL).to_bytes(4, 'little') + + (self._packet_counter // REKEY_INTERVAL).to_bytes(8, 'little')) + if is_decrypt: + ret = aead_chacha20_poly1305_decrypt(self._key, nonce, aad, text) + else: + ret = aead_chacha20_poly1305_encrypt(self._key, nonce, aad, text) + if (self._packet_counter + 1) % REKEY_INTERVAL == 0: + rekey_nonce = b"\xFF\xFF\xFF\xFF" + nonce[4:] + self._key = aead_chacha20_poly1305_encrypt(self._key, rekey_nonce, b"", b"\x00" * 32)[:32] + self._packet_counter += 1 + return ret + + def decrypt(self, aad, ciphertext): + return self._crypt(aad, ciphertext, True) + + def encrypt(self, aad, plaintext): + return self._crypt(aad, plaintext, False) + + +# Test vectors from RFC8439 consisting of plaintext, aad, 32 byte key, 12 byte nonce and ciphertext +AEAD_TESTS = [ + # RFC 8439 Example from section 2.8.2 + ["4c616469657320616e642047656e746c656d656e206f662074686520636c6173" + "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" + "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" + "637265656e20776f756c642062652069742e", + "50515253c0c1c2c3c4c5c6c7", + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + [7, 0x4746454443424140], + "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" + "3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" + "92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" + "3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" + "0691"], + # RFC 8439 Test vector A.5 + ["496e7465726e65742d4472616674732061726520647261667420646f63756d65" + "6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" + "6f6e74687320616e64206d617920626520757064617465642c207265706c6163" + "65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" + "6e747320617420616e792074696d652e20497420697320696e617070726f7072" + "6961746520746f2075736520496e7465726e65742d4472616674732061732072" + "65666572656e6365206d6174657269616c206f7220746f206369746520746865" + "6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" + "726573732e2fe2809d", + "f33388860000000000004e91", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + [0, 0x0807060504030201], + "64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" + "4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" + "332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" + "9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" + "b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" + "af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" + "0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" + "49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" + "a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38"], + # Test vectors exercising aad and plaintext which are multiples of 16 bytes. + ["8d2d6a8befd9716fab35819eaac83b33269afb9f1a00fddf66095a6c0cd91951" + "a6b7ad3db580be0674c3f0b55f618e34", + "", + "72ddc73f07101282bbbcf853b9012a9f9695fc5d36b303a97fd0845d0314e0c3", + [0x3432b75f, 0xb3585537eb7f4024], + "f760b8224fb2a317b1b07875092606131232a5b86ae142df5df1c846a7f6341a" + "f2564483dd77f836be45e6230808ffe402a6f0a3e8be074b3d1f4ea8a7b09451"], + ["", + "36970d8a704c065de16250c18033de5a400520ac1b5842b24551e5823a3314f3" + "946285171e04a81ebfbe3566e312e74ab80e94c7dd2ff4e10de0098a58d0f503", + "77adda51d6730b9ad6c995658cbd49f581b2547e7c0c08fcc24ceec797461021", + [0x1f90da88, 0x75dafa3ef84471a4], + "aaae5bb81e8407c94b2ae86ae0c7efbe"], +] + +FSAEAD_TESTS = [ + ["d6a4cb04ef0f7c09c1866ed29dc24d820e75b0491032a51b4c3366f9ca35c19e" + "a3047ec6be9d45f9637b63e1cf9eb4c2523a5aab7b851ebeba87199db0e839cf" + "0d5c25e50168306377aedbe9089fd2463ded88b83211cf51b73b150608cc7a60" + "0d0f11b9a742948482e1b109d8faf15b450aa7322e892fa2208c6691e3fecf4c" + "711191b14d75a72147", + "786cb9b6ebf44288974cf0", + "5c9e1c3951a74fba66708bf9d2c217571684556b6a6a3573bff2847d38612654", + 500, + "9dcebbd3281ea3dd8e9a1ef7d55a97abd6743e56ebc0c190cb2c4e14160b385e" + "0bf508dddf754bd02c7c208447c131ce23e47a4a14dfaf5dd8bc601323950f75" + "4e05d46e9232f83fc5120fbbef6f5347a826ec79a93820718d4ec7a2b7cfaaa4" + "4b21e16d726448b62f803811aff4f6d827ed78e738ce8a507b81a8ae13131192" + "8039213de18a5120dc9b7370baca878f50ff254418de3da50c"], + ["8349b7a2690b63d01204800c288ff1138a1d473c832c90ea8b3fc102d0bb3adc" + "44261b247c7c3d6760bfbe979d061c305f46d94c0582ac3099f0bf249f8cb234", + "", + "3bd2093fcbcb0d034d8c569583c5425c1a53171ea299f8cc3bbf9ae3530adfce", + 60000, + "30a6757ff8439b975363f166a0fa0e36722ab35936abd704297948f45083f4d4" + "99433137ce931f7fca28a0acd3bc30f57b550acbc21cbd45bbef0739d9caf30c" + "14b94829deb27f0b1923a2af704ae5d6"], +] + + +class TestFrameworkAEAD(unittest.TestCase): + def test_aead(self): + """ChaCha20Poly1305 AEAD test vectors.""" + for test_vector in AEAD_TESTS: + hex_plain, hex_aad, hex_key, hex_nonce, hex_cipher = test_vector + plain = bytes.fromhex(hex_plain) + aad = bytes.fromhex(hex_aad) + key = bytes.fromhex(hex_key) + nonce = hex_nonce[0].to_bytes(4, 'little') + hex_nonce[1].to_bytes(8, 'little') + + ciphertext = aead_chacha20_poly1305_encrypt(key, nonce, aad, plain) + self.assertEqual(hex_cipher, ciphertext.hex()) + plaintext = aead_chacha20_poly1305_decrypt(key, nonce, aad, ciphertext) + self.assertEqual(plain, plaintext) + + def test_fschacha20poly1305aead(self): + "FSChaCha20Poly1305 AEAD test vectors." + for test_vector in FSAEAD_TESTS: + hex_plain, hex_aad, hex_key, msg_idx, hex_cipher = test_vector + plain = bytes.fromhex(hex_plain) + aad = bytes.fromhex(hex_aad) + key = bytes.fromhex(hex_key) + + enc_aead = FSChaCha20Poly1305(key) + dec_aead = FSChaCha20Poly1305(key) + + for _ in range(msg_idx): + enc_aead.encrypt(b"", b"") + ciphertext = enc_aead.encrypt(aad, plain) + self.assertEqual(hex_cipher, ciphertext.hex()) + + for _ in range(msg_idx): + dec_aead.decrypt(b"", bytes(16)) + plaintext = dec_aead.decrypt(aad, ciphertext) + self.assertEqual(plain, plaintext) diff --git a/test/functional/test_framework/crypto/chacha20.py b/test/functional/test_framework/crypto/chacha20.py new file mode 100644 index 000000000000..19b6698dfb31 --- /dev/null +++ b/test/functional/test_framework/crypto/chacha20.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# 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. + +"""Test-only implementation of ChaCha20 cipher and FSChaCha20 for BIP 324 + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. +""" + +import unittest + +CHACHA20_INDICES = ( + (0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15), + (0, 5, 10, 15), (1, 6, 11, 12), (2, 7, 8, 13), (3, 4, 9, 14) +) + +CHACHA20_CONSTANTS = (0x61707865, 0x3320646e, 0x79622d32, 0x6b206574) +REKEY_INTERVAL = 224 # packets + + +def rotl32(v, bits): + """Rotate the 32-bit value v left by bits bits.""" + bits %= 32 # Make sure the term below does not throw an exception + return ((v << bits) & 0xffffffff) | (v >> (32 - bits)) + + +def chacha20_doubleround(s): + """Apply a ChaCha20 double round to 16-element state array s. + See https://cr.yp.to/chacha/chacha-20080128.pdf and https://tools.ietf.org/html/rfc8439 + """ + for a, b, c, d in CHACHA20_INDICES: + s[a] = (s[a] + s[b]) & 0xffffffff + s[d] = rotl32(s[d] ^ s[a], 16) + s[c] = (s[c] + s[d]) & 0xffffffff + s[b] = rotl32(s[b] ^ s[c], 12) + s[a] = (s[a] + s[b]) & 0xffffffff + s[d] = rotl32(s[d] ^ s[a], 8) + s[c] = (s[c] + s[d]) & 0xffffffff + s[b] = rotl32(s[b] ^ s[c], 7) + + +def chacha20_block(key, nonce, cnt): + """Compute the 64-byte output of the ChaCha20 block function. + Takes as input a 32-byte key, 12-byte nonce, and 32-bit integer counter. + """ + # Initial state. + init = [0] * 16 + init[:4] = CHACHA20_CONSTANTS[:4] + init[4:12] = [int.from_bytes(key[i:i+4], 'little') for i in range(0, 32, 4)] + init[12] = cnt + init[13:16] = [int.from_bytes(nonce[i:i+4], 'little') for i in range(0, 12, 4)] + # Perform 20 rounds. + state = list(init) + for _ in range(10): + chacha20_doubleround(state) + # Add initial values back into state. + for i in range(16): + state[i] = (state[i] + init[i]) & 0xffffffff + # Produce byte output + return b''.join(state[i].to_bytes(4, 'little') for i in range(16)) + +class FSChaCha20: + """Rekeying wrapper stream cipher around ChaCha20.""" + def __init__(self, initial_key, rekey_interval=REKEY_INTERVAL): + self._key = initial_key + self._rekey_interval = rekey_interval + self._block_counter = 0 + self._chunk_counter = 0 + self._keystream = b'' + + def _get_keystream_bytes(self, nbytes): + while len(self._keystream) < nbytes: + nonce = ((0).to_bytes(4, 'little') + (self._chunk_counter // self._rekey_interval).to_bytes(8, 'little')) + self._keystream += chacha20_block(self._key, nonce, self._block_counter) + self._block_counter += 1 + ret = self._keystream[:nbytes] + self._keystream = self._keystream[nbytes:] + return ret + + def crypt(self, chunk): + ks = self._get_keystream_bytes(len(chunk)) + ret = bytes([ks[i] ^ chunk[i] for i in range(len(chunk))]) + if ((self._chunk_counter + 1) % self._rekey_interval) == 0: + self._key = self._get_keystream_bytes(32) + self._block_counter = 0 + self._keystream = b'' + self._chunk_counter += 1 + return ret + + +# Test vectors from RFC7539/8439 consisting of 32 byte key, 12 byte nonce, block counter +# and 64 byte output after applying `chacha20_block` function +CHACHA20_TESTS = [ + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0x09000000, 0x4a000000], 1, + "10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4e" + "d2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e"], + ["0000000000000000000000000000000000000000000000000000000000000000", [0, 0], 0, + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" + "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"], + ["0000000000000000000000000000000000000000000000000000000000000000", [0, 0], 1, + "9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed" + "29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f"], + ["0000000000000000000000000000000000000000000000000000000000000001", [0, 0], 1, + "3aeb5224ecf849929b9d828db1ced4dd832025e8018b8160b82284f3c949aa5a" + "8eca00bbb4a73bdad192b5c42f73f2fd4e273644c8b36125a64addeb006c13a0"], + ["00ff000000000000000000000000000000000000000000000000000000000000", [0, 0], 2, + "72d54dfbf12ec44b362692df94137f328fea8da73990265ec1bbbea1ae9af0ca" + "13b25aa26cb4a648cb9b9d1be65b2c0924a66c54d545ec1b7374f4872e99f096"], + ["0000000000000000000000000000000000000000000000000000000000000000", [0, 0x200000000000000], 0, + "c2c64d378cd536374ae204b9ef933fcd1a8b2288b3dfa49672ab765b54ee27c7" + "8a970e0e955c14f3a88e741b97c286f75f8fc299e8148362fa198a39531bed6d"], + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0, 0x4a000000], 1, + "224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf78" + "8a3b0aa372600a92b57974cded2b9334794cba40c63e34cdea212c4cf07d41b7"], + ["0000000000000000000000000000000000000000000000000000000000000001", [0, 0], 0, + "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41" + "bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963"], + ["0000000000000000000000000000000000000000000000000000000000000000", [0, 1], 0, + "ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32" + "111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"], + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", [0, 0x0706050403020100], 0, + "f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c1" + "34a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a"], +] + +FSCHACHA20_TESTS = [ + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "0000000000000000000000000000000000000000000000000000000000000000", 256, + "a93df4ef03011f3db95f60d996e1785df5de38fc39bfcb663a47bb5561928349"], + ["01", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 5, "ea"], + ["e93fdb5c762804b9a706816aca31e35b11d2aa3080108ef46a5b1f1508819c0a", + "8ec4c3ccdaea336bdeb245636970be01266509b33f3d2642504eaf412206207a", 4096, + "8bfaa4eacff308fdb4a94a5ff25bd9d0c1f84b77f81239f67ff39d6e1ac280c9"], +] + + +class TestFrameworkChacha(unittest.TestCase): + def test_chacha20(self): + """ChaCha20 test vectors.""" + for test_vector in CHACHA20_TESTS: + hex_key, nonce, counter, hex_output = test_vector + key = bytes.fromhex(hex_key) + nonce_bytes = nonce[0].to_bytes(4, 'little') + nonce[1].to_bytes(8, 'little') + keystream = chacha20_block(key, nonce_bytes, counter) + self.assertEqual(hex_output, keystream.hex()) + + def test_fschacha20(self): + """FSChaCha20 test vectors.""" + for test_vector in FSCHACHA20_TESTS: + hex_plaintext, hex_key, rekey_interval, hex_ciphertext_after_rotation = test_vector + plaintext = bytes.fromhex(hex_plaintext) + key = bytes.fromhex(hex_key) + fsc20 = FSChaCha20(key, rekey_interval) + for _ in range(rekey_interval): + fsc20.crypt(plaintext) + + ciphertext = fsc20.crypt(plaintext) + self.assertEqual(hex_ciphertext_after_rotation, ciphertext.hex()) diff --git a/test/functional/test_framework/ellswift.py b/test/functional/test_framework/crypto/ellswift.py similarity index 99% rename from test/functional/test_framework/ellswift.py rename to test/functional/test_framework/crypto/ellswift.py index 97b10118e648..429b7b9f4d3f 100644 --- a/test/functional/test_framework/ellswift.py +++ b/test/functional/test_framework/crypto/ellswift.py @@ -12,7 +12,7 @@ import random import unittest -from test_framework.secp256k1 import FE, G, GE +from test_framework.crypto.secp256k1 import FE, G, GE # Precomputed constant square root of -3 (mod p). MINUS_3_SQRT = FE(-3).sqrt() diff --git a/test/functional/test_framework/ellswift_decode_test_vectors.csv b/test/functional/test_framework/crypto/ellswift_decode_test_vectors.csv similarity index 100% rename from test/functional/test_framework/ellswift_decode_test_vectors.csv rename to test/functional/test_framework/crypto/ellswift_decode_test_vectors.csv diff --git a/test/functional/test_framework/crypto/hkdf.py b/test/functional/test_framework/crypto/hkdf.py new file mode 100644 index 000000000000..7e8958733c92 --- /dev/null +++ b/test/functional/test_framework/crypto/hkdf.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test-only HKDF-SHA256 implementation + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. +""" + +import hashlib +import hmac + + +def hmac_sha256(key, data): + """Compute HMAC-SHA256 from specified byte arrays key and data.""" + return hmac.new(key, data, hashlib.sha256).digest() + + +def hkdf_sha256(length, ikm, salt, info): + """Derive a key using HKDF-SHA256.""" + if len(salt) == 0: + salt = bytes([0] * 32) + prk = hmac_sha256(salt, ikm) + t = b"" + okm = b"" + for i in range((length + 32 - 1) // 32): + t = hmac_sha256(prk, t + info + bytes([i + 1])) + okm += t + return okm[:length] diff --git a/test/functional/test_framework/crypto/muhash.py b/test/functional/test_framework/crypto/muhash.py new file mode 100644 index 000000000000..09241f620370 --- /dev/null +++ b/test/functional/test_framework/crypto/muhash.py @@ -0,0 +1,55 @@ +# Copyright (c) 2020 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Native Python MuHash3072 implementation.""" + +import hashlib +import unittest + +from .chacha20 import chacha20_block + +def data_to_num3072(data): + """Hash a 32-byte array data to a 3072-bit number using 6 Chacha20 operations.""" + bytes384 = b"" + for counter in range(6): + bytes384 += chacha20_block(data, bytes(12), counter) + return int.from_bytes(bytes384, 'little') + +class MuHash3072: + """Class representing the MuHash3072 computation of a set. + + See https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf and https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-May/014337.html + """ + + MODULUS = 2**3072 - 1103717 + + def __init__(self): + """Initialize for an empty set.""" + self.numerator = 1 + self.denominator = 1 + + def insert(self, data): + """Insert a byte array data in the set.""" + data_hash = hashlib.sha256(data).digest() + self.numerator = (self.numerator * data_to_num3072(data_hash)) % self.MODULUS + + def remove(self, data): + """Remove a byte array from the set.""" + data_hash = hashlib.sha256(data).digest() + self.denominator = (self.denominator * data_to_num3072(data_hash)) % self.MODULUS + + def digest(self): + """Extract the final hash. Does not modify this object.""" + val = (self.numerator * pow(self.denominator, -1, self.MODULUS)) % self.MODULUS + bytes384 = val.to_bytes(384, 'little') + return hashlib.sha256(bytes384).digest() + +class TestFrameworkMuhash(unittest.TestCase): + def test_muhash(self): + muhash = MuHash3072() + muhash.insert(b'\x00' * 32) + muhash.insert((b'\x01' + b'\x00' * 31)) + muhash.remove((b'\x02' + b'\x00' * 31)) + finalized = muhash.digest() + # This mirrors the result in the C++ MuHash3072 unit test + self.assertEqual(finalized[::-1].hex(), "10d312b100cbd32ada024a6646e40d3482fcff103668d2625f10002a607d5863") diff --git a/test/functional/test_framework/crypto/poly1305.py b/test/functional/test_framework/crypto/poly1305.py new file mode 100644 index 000000000000..967b90254d9e --- /dev/null +++ b/test/functional/test_framework/crypto/poly1305.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# 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. + +"""Test-only implementation of Poly1305 authenticator + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. +""" + +import unittest + + +class Poly1305: + """Class representing a running poly1305 computation.""" + MODULUS = 2**130 - 5 + + def __init__(self, key): + self.r = int.from_bytes(key[:16], 'little') & 0xffffffc0ffffffc0ffffffc0fffffff + self.s = int.from_bytes(key[16:], 'little') + + def tag(self, data): + """Compute the poly1305 tag.""" + acc, length = 0, len(data) + for i in range((length + 15) // 16): + chunk = data[i * 16:min(length, (i + 1) * 16)] + val = int.from_bytes(chunk, 'little') + 256**len(chunk) + acc = (self.r * (acc + val)) % Poly1305.MODULUS + return ((acc + self.s) & 0xffffffffffffffffffffffffffffffff).to_bytes(16, 'little') + + +# Test vectors from RFC7539/8439 consisting of message to be authenticated, 32 byte key and computed 16 byte tag +POLY1305_TESTS = [ + # RFC 7539, section 2.5.2. + ["43727970746f6772617068696320466f72756d2052657365617263682047726f7570", + "85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b", + "a8061dc1305136c6c22b8baf0c0127a9"], + # RFC 7539, section A.3. + ["00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000"], + ["416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e747269627" + "5746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465" + "726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686" + "520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e2022494554" + "4620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c20737461746" + "56d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c65" + "6374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207" + "768696368206172652061646472657373656420746f", + "0000000000000000000000000000000036e5f6b5c5e06070f0efca96227a863e", + "36e5f6b5c5e06070f0efca96227a863e"], + ["416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e747269627" + "5746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465" + "726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686" + "520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e2022494554" + "4620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c20737461746" + "56d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c65" + "6374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207" + "768696368206172652061646472657373656420746f", + "36e5f6b5c5e06070f0efca96227a863e00000000000000000000000000000000", + "f3477e7cd95417af89a6b8794c310cf0"], + ["2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e6420676" + "96d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e" + "6420746865206d6f6d65207261746873206f757467726162652e", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + "4541669a7eaaee61e708dc7cbcc5eb62"], + ["ffffffffffffffffffffffffffffffff", + "0200000000000000000000000000000000000000000000000000000000000000", + "03000000000000000000000000000000"], + ["02000000000000000000000000000000", + "02000000000000000000000000000000ffffffffffffffffffffffffffffffff", + "03000000000000000000000000000000"], + ["fffffffffffffffffffffffffffffffff0ffffffffffffffffffffffffffffff11000000000000000000000000000000", + "0100000000000000000000000000000000000000000000000000000000000000", + "05000000000000000000000000000000"], + ["fffffffffffffffffffffffffffffffffbfefefefefefefefefefefefefefefe01010101010101010101010101010101", + "0100000000000000000000000000000000000000000000000000000000000000", + "00000000000000000000000000000000"], + ["fdffffffffffffffffffffffffffffff", + "0200000000000000000000000000000000000000000000000000000000000000", + "faffffffffffffffffffffffffffffff"], + ["e33594d7505e43b900000000000000003394d7505e4379cd01000000000000000000000000000000000000000000000001000000000000000000000000000000", + "0100000000000000040000000000000000000000000000000000000000000000", + "14000000000000005500000000000000"], + ["e33594d7505e43b900000000000000003394d7505e4379cd010000000000000000000000000000000000000000000000", + "0100000000000000040000000000000000000000000000000000000000000000", + "13000000000000000000000000000000"], +] + + +class TestFrameworkPoly1305(unittest.TestCase): + def test_poly1305(self): + """Poly1305 test vectors.""" + for test_vector in POLY1305_TESTS: + hex_message, hex_key, hex_tag = test_vector + message = bytes.fromhex(hex_message) + key = bytes.fromhex(hex_key) + tag = bytes.fromhex(hex_tag) + comp_tag = Poly1305(key).tag(message) + self.assertEqual(tag, comp_tag) diff --git a/test/functional/test_framework/ripemd160.py b/test/functional/test_framework/crypto/ripemd160.py similarity index 100% rename from test/functional/test_framework/ripemd160.py rename to test/functional/test_framework/crypto/ripemd160.py diff --git a/test/functional/test_framework/secp256k1.py b/test/functional/test_framework/crypto/secp256k1.py similarity index 100% rename from test/functional/test_framework/secp256k1.py rename to test/functional/test_framework/crypto/secp256k1.py diff --git a/test/functional/test_framework/siphash.py b/test/functional/test_framework/crypto/siphash.py similarity index 100% rename from test/functional/test_framework/siphash.py rename to test/functional/test_framework/crypto/siphash.py diff --git a/test/functional/test_framework/xswiftec_inv_test_vectors.csv b/test/functional/test_framework/crypto/xswiftec_inv_test_vectors.csv similarity index 100% rename from test/functional/test_framework/xswiftec_inv_test_vectors.csv rename to test/functional/test_framework/crypto/xswiftec_inv_test_vectors.csv diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index 6f2bd86cd21a..fc3ef17f7d1b 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -13,7 +13,7 @@ import random import unittest -from test_framework import secp256k1 +from test_framework.crypto import secp256k1 # Order of the secp256k1 curve ORDER = secp256k1.GE.ORDER diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index b53f64becd7f..d055226ca8be 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -26,7 +26,7 @@ import struct import time -from test_framework.siphash import siphash256 +from test_framework.crypto.siphash import siphash256 from test_framework.util import hex_str_to_bytes, assert_equal import dash_hash diff --git a/test/functional/test_framework/muhash.py b/test/functional/test_framework/muhash.py deleted file mode 100644 index 0d96114e3eb7..000000000000 --- a/test/functional/test_framework/muhash.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (c) 2020 Pieter Wuille -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Native Python MuHash3072 implementation.""" - -import hashlib -import unittest - -def rot32(v, bits): - """Rotate the 32-bit value v left by bits bits.""" - bits %= 32 # Make sure the term below does not throw an exception - return ((v << bits) & 0xffffffff) | (v >> (32 - bits)) - -def chacha20_doubleround(s): - """Apply a ChaCha20 double round to 16-element state array s. - - See https://cr.yp.to/chacha/chacha-20080128.pdf and https://tools.ietf.org/html/rfc8439 - """ - QUARTER_ROUNDS = [(0, 4, 8, 12), - (1, 5, 9, 13), - (2, 6, 10, 14), - (3, 7, 11, 15), - (0, 5, 10, 15), - (1, 6, 11, 12), - (2, 7, 8, 13), - (3, 4, 9, 14)] - - for a, b, c, d in QUARTER_ROUNDS: - s[a] = (s[a] + s[b]) & 0xffffffff - s[d] = rot32(s[d] ^ s[a], 16) - s[c] = (s[c] + s[d]) & 0xffffffff - s[b] = rot32(s[b] ^ s[c], 12) - s[a] = (s[a] + s[b]) & 0xffffffff - s[d] = rot32(s[d] ^ s[a], 8) - s[c] = (s[c] + s[d]) & 0xffffffff - s[b] = rot32(s[b] ^ s[c], 7) - -def chacha20_32_to_384(key32): - """Specialized ChaCha20 implementation with 32-byte key, 0 IV, 384-byte output.""" - # See RFC 8439 section 2.3 for chacha20 parameters - CONSTANTS = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] - - key_bytes = [0]*8 - for i in range(8): - key_bytes[i] = int.from_bytes(key32[(4 * i):(4 * (i+1))], 'little') - - INITIALIZATION_VECTOR = [0] * 4 - init = CONSTANTS + key_bytes + INITIALIZATION_VECTOR - out = bytearray() - for counter in range(6): - init[12] = counter - s = init.copy() - for _ in range(10): - chacha20_doubleround(s) - for i in range(16): - out.extend(((s[i] + init[i]) & 0xffffffff).to_bytes(4, 'little')) - return bytes(out) - -def data_to_num3072(data): - """Hash a 32-byte array data to a 3072-bit number using 6 Chacha20 operations.""" - bytes384 = chacha20_32_to_384(data) - return int.from_bytes(bytes384, 'little') - -class MuHash3072: - """Class representing the MuHash3072 computation of a set. - - See https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf and https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2017-May/014337.html - """ - - MODULUS = 2**3072 - 1103717 - - def __init__(self): - """Initialize for an empty set.""" - self.numerator = 1 - self.denominator = 1 - - def insert(self, data): - """Insert a byte array data in the set.""" - data_hash = hashlib.sha256(data).digest() - self.numerator = (self.numerator * data_to_num3072(data_hash)) % self.MODULUS - - def remove(self, data): - """Remove a byte array from the set.""" - data_hash = hashlib.sha256(data).digest() - self.denominator = (self.denominator * data_to_num3072(data_hash)) % self.MODULUS - - def digest(self): - """Extract the final hash. Does not modify this object.""" - val = (self.numerator * pow(self.denominator, -1, self.MODULUS)) % self.MODULUS - bytes384 = val.to_bytes(384, 'little') - return hashlib.sha256(bytes384).digest() - -class TestFrameworkMuhash(unittest.TestCase): - def test_muhash(self): - muhash = MuHash3072() - muhash.insert(b'\x00' * 32) - muhash.insert((b'\x01' + b'\x00' * 31)) - muhash.remove((b'\x02' + b'\x00' * 31)) - finalized = muhash.digest() - # This mirrors the result in the C++ MuHash3072 unit test - self.assertEqual(finalized[::-1].hex(), "10d312b100cbd32ada024a6646e40d3482fcff103668d2625f10002a607d5863") - - def test_chacha20(self): - def chacha_check(key, result): - self.assertEqual(chacha20_32_to_384(key)[:64].hex(), result) - - # Test vectors from https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 - # Since the nonce is hardcoded to 0 in our function we only use those vectors. - chacha_check([0]*32, "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586") - chacha_check([0]*31 + [1], "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963") diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 999c70f02db7..fe4602213cdf 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -17,7 +17,7 @@ sha256, ) -from .ripemd160 import ripemd160 +from .crypto.ripemd160 import ripemd160 MAX_SCRIPT_ELEMENT_SIZE = 520 def hash160(s): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 9d8ac457e422..3a4302afc611 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -71,11 +71,14 @@ # the output of `git grep unittest.TestCase ./test/functional/test_framework` TEST_FRAMEWORK_MODULES = [ "address", + "crypto.bip324_cipher", "blocktools", - "ellswift", + "crypto.chacha20", + "crypto.ellswift", "key", - "muhash", - "ripemd160", + "crypto.muhash", + "crypto.poly1305", + "crypto.ripemd160", "script", ]