aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am2
-rw-r--r--src/crypto/chacha20poly1305.cpp101
-rw-r--r--src/crypto/chacha20poly1305.h51
-rw-r--r--src/test/crypto_tests.cpp77
4 files changed, 231 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index d8ea4bbd28..b6fe256335 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -548,6 +548,8 @@ crypto_libbitcoin_crypto_base_la_SOURCES = \
crypto/aes.h \
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/crypto/chacha20poly1305.cpp b/src/crypto/chacha20poly1305.cpp
new file mode 100644
index 0000000000..5f27857213
--- /dev/null
+++ b/src/crypto/chacha20poly1305.cpp
@@ -0,0 +1,101 @@
+// 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 <crypto/chacha20poly1305.h>
+
+#include <crypto/common.h>
+#include <crypto/chacha20.h>
+#include <crypto/poly1305.h>
+#include <span.h>
+
+#include <assert.h>
+#include <cstdint>
+#include <cstddef>
+#include <iterator>
+
+AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept : m_chacha20(UCharCast(key.data()))
+{
+ assert(key.size() == KEYLEN);
+}
+
+void AEADChaCha20Poly1305::SetKey(Span<const std::byte> key) noexcept
+{
+ assert(key.size() == KEYLEN);
+ m_chacha20.SetKey32(UCharCast(key.data()));
+}
+
+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<const std::byte> aad, Span<const std::byte> cipher, Span<std::byte> 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[64];
+ chacha20.Keystream(UCharCast(first_block), sizeof(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<const std::byte> plain, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> cipher) noexcept
+{
+ assert(cipher.size() == plain.size() + EXPANSION);
+
+ // Encrypt using ChaCha20 (starting at block 1).
+ m_chacha20.Seek64(nonce, 1);
+ m_chacha20.Crypt(UCharCast(plain.data()), UCharCast(cipher.data()), plain.size());
+
+ // Seek to block 0, and compute tag using key drawn from there.
+ m_chacha20.Seek64(nonce, 0);
+ ComputeTag(m_chacha20, aad, cipher.first(plain.size()), cipher.last(EXPANSION));
+}
+
+bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain) noexcept
+{
+ assert(cipher.size() == plain.size() + EXPANSION);
+
+ // Verify tag (using key drawn from block 0).
+ m_chacha20.Seek64(nonce, 0);
+ std::byte expected_tag[EXPANSION];
+ ComputeTag(m_chacha20, aad, cipher.first(plain.size()), expected_tag);
+ if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.data() + plain.size()), EXPANSION)) return false;
+
+ // Decrypt (starting at block 1).
+ m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain.data()), plain.size());
+ return true;
+}
diff --git a/src/crypto/chacha20poly1305.h b/src/crypto/chacha20poly1305.h
new file mode 100644
index 0000000000..0007ee63a9
--- /dev/null
+++ b/src/crypto/chacha20poly1305.h
@@ -0,0 +1,51 @@
+// 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 <cstddef>
+#include <cstdlib>
+#include <stdint.h>
+
+#include <crypto/chacha20.h>
+#include <crypto/poly1305.h>
+#include <span.h>
+
+/** 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<const std::byte> key) noexcept;
+
+ /** Switch to another 32-byte key. */
+ void SetKey(Span<const std::byte> 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<const std::byte> plain, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> 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<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain) noexcept;
+};
+
+#endif // BITCOIN_CRYPTO_CHACHA20POLY1305_H
diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp
index 809b897998..f62072f5bb 100644
--- a/src/test/crypto_tests.cpp
+++ b/src/test/crypto_tests.cpp
@@ -4,6 +4,7 @@
#include <crypto/aes.h>
#include <crypto/chacha20.h>
+#include <crypto/chacha20poly1305.h>
#include <crypto/hkdf_sha256_32.h>
#include <crypto/hmac_sha256.h>
#include <crypto/hmac_sha512.h>
@@ -207,6 +208,24 @@ static void TestPoly1305(const std::string &hexmessage, const std::string &hexke
}
}
+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<std::byte>(plain_hex);
+ auto aad = ParseHex<std::byte>(aad_hex);
+ auto key = ParseHex<std::byte>(key_hex);
+ auto expected_cipher = ParseHex<std::byte>(cipher_hex);
+
+ std::vector<std::byte> cipher(plain.size() + AEADChaCha20Poly1305::EXPANSION);
+ AEADChaCha20Poly1305 aead{key};
+ aead.Encrypt(plain, aad, nonce, cipher);
+ BOOST_CHECK(cipher == expected_cipher);
+
+ std::vector<std::byte> decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION);
+ bool ret = aead.Decrypt(cipher, aad, nonce, decipher);
+ 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) {
std::vector<unsigned char> initial_key_material = ParseHex(ikm_hex);
std::vector<unsigned char> salt = ParseHex(salt_hex);
@@ -818,6 +837,64 @@ BOOST_AUTO_TEST_CASE(poly1305_testvector)
"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");
+}
+
BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests)
{
// Use rfc5869 test vectors but truncated to 32 bytes (our implementation only support length 32)