Skip to content

Commit 23944f5

Browse files
committed
RFC8439 implementation and tests
1 parent 42271df commit 23944f5

8 files changed

+304
-0
lines changed

src/Makefile.am

+2
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ crypto_libbitcoin_crypto_base_la_SOURCES = \
509509
crypto/poly1305.cpp \
510510
crypto/muhash.h \
511511
crypto/muhash.cpp \
512+
crypto/rfc8439.h \
513+
crypto/rfc8439.cpp \
512514
crypto/ripemd160.cpp \
513515
crypto/ripemd160.h \
514516
crypto/sha1.cpp \

src/Makefile.bench.include

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ bench_bench_bitcoin_SOURCES = \
4141
bench/peer_eviction.cpp \
4242
bench/poly1305.cpp \
4343
bench/prevector.cpp \
44+
bench/rfc8439.cpp \
4445
bench/rollingbloom.cpp \
4546
bench/rpc_blockchain.cpp \
4647
bench/rpc_mempool.cpp \

src/Makefile.test.include

+1
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ test_fuzz_fuzz_SOURCES = \
248248
test/fuzz/crypto_common.cpp \
249249
test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp \
250250
test/fuzz/crypto_poly1305.cpp \
251+
test/fuzz/crypto_rfc8439.cpp \
251252
test/fuzz/cuckoocache.cpp \
252253
test/fuzz/decode_tx.cpp \
253254
test/fuzz/descriptor_parse.cpp \

src/bench/rfc8439.cpp

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <assert.h>
6+
#include <bench/bench.h>
7+
#include <crypto/rfc8439.h>
8+
9+
#include <array>
10+
#include <cstddef>
11+
#include <vector>
12+
13+
static constexpr uint64_t AAD_SIZE = 32;
14+
static constexpr uint64_t PLAINTEXT_SIZE_TINY = 64;
15+
static constexpr uint64_t PLAINTEXT_SIZE_SMALL = 256;
16+
static constexpr uint64_t PLAINTEXT_SIZE_LARGE = 1024 * 1024;
17+
18+
static std::vector<std::byte> zero_key(32, std::byte{0x00});
19+
static std::vector<std::byte> aad(AAD_SIZE, std::byte{0x00});
20+
std::array<std::byte, 12> nonce = {std::byte{0x00}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03},
21+
std::byte{0x04}, std::byte{0x05}, std::byte{0x06}, std::byte{0x07},
22+
std::byte{0x08}, std::byte{0x09}, std::byte{0x0a}, std::byte{0x0b}};
23+
24+
static void RFC8439_AEAD(benchmark::Bench& bench, size_t plaintext_size, bool include_decryption)
25+
{
26+
std::vector<std::byte> plaintext_in(plaintext_size, std::byte{0x00});
27+
28+
bench.batch(plaintext_size).unit("byte").run([&] {
29+
auto encrypted = RFC8439Encrypt(aad, zero_key, nonce, plaintext_in);
30+
31+
if (include_decryption) {
32+
auto decrypted = RFC8439Decrypt(aad, zero_key, nonce, encrypted);
33+
assert(decrypted.success);
34+
assert(memcmp(decrypted.plaintext.data(), plaintext_in.data(), plaintext_in.size()) == 0);
35+
}
36+
});
37+
}
38+
39+
static void RFC8439_AEAD_64BYTES_ONLY_ENCRYPT(benchmark::Bench& bench)
40+
{
41+
RFC8439_AEAD(bench, PLAINTEXT_SIZE_TINY, false);
42+
}
43+
44+
static void RFC8439_AEAD_256BYTES_ONLY_ENCRYPT(benchmark::Bench& bench)
45+
{
46+
RFC8439_AEAD(bench, PLAINTEXT_SIZE_SMALL, false);
47+
}
48+
49+
static void RFC8439_AEAD_1MB_ONLY_ENCRYPT(benchmark::Bench& bench)
50+
{
51+
RFC8439_AEAD(bench, PLAINTEXT_SIZE_LARGE, false);
52+
}
53+
54+
static void RFC8439_AEAD_64BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench)
55+
{
56+
RFC8439_AEAD(bench, PLAINTEXT_SIZE_TINY, true);
57+
}
58+
59+
static void RFC8439_AEAD_256BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench)
60+
{
61+
RFC8439_AEAD(bench, PLAINTEXT_SIZE_SMALL, true);
62+
}
63+
64+
static void RFC8439_AEAD_1MB_ENCRYPT_DECRYPT(benchmark::Bench& bench)
65+
{
66+
RFC8439_AEAD(bench, PLAINTEXT_SIZE_LARGE, true);
67+
}
68+
69+
BENCHMARK(RFC8439_AEAD_64BYTES_ONLY_ENCRYPT);
70+
BENCHMARK(RFC8439_AEAD_256BYTES_ONLY_ENCRYPT);
71+
BENCHMARK(RFC8439_AEAD_1MB_ONLY_ENCRYPT);
72+
BENCHMARK(RFC8439_AEAD_64BYTES_ENCRYPT_DECRYPT);
73+
BENCHMARK(RFC8439_AEAD_256BYTES_ENCRYPT_DECRYPT);
74+
BENCHMARK(RFC8439_AEAD_1MB_ENCRYPT_DECRYPT);

src/crypto/rfc8439.cpp

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <crypto/chacha20.h>
6+
#include <crypto/common.h>
7+
#include <crypto/rfc8439.h>
8+
9+
#include <cstring>
10+
11+
#ifndef RFC8439_TIMINGSAFE_BCMP
12+
#define RFC8439_TIMINGSAFE_BCMP
13+
14+
int rfc8439_timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n)
15+
{
16+
const unsigned char *p1 = b1, *p2 = b2;
17+
int ret = 0;
18+
19+
for (; n > 0; n--)
20+
ret |= *p1++ ^ *p2++;
21+
return (ret != 0);
22+
}
23+
24+
#endif // RFC8439_TIMINGSAFE_BCMP
25+
26+
inline size_t padded16_size(size_t len)
27+
{
28+
return (len % 16 == 0) ? len : (len / 16 + 1) * 16;
29+
}
30+
31+
std::array<std::byte, POLY1305_TAGLEN> ComputeRFC8439Tag(const std::array<std::byte, POLY1305_KEYLEN>& polykey,
32+
Span<const std::byte> aad, Span<const std::byte> ciphertext)
33+
{
34+
std::vector<std::byte> bytes_to_authenticate;
35+
auto padded_aad_size = padded16_size(aad.size());
36+
auto padded_ciphertext_size = padded16_size(ciphertext.size());
37+
bytes_to_authenticate.resize(padded_aad_size + padded_ciphertext_size + 16, std::byte{0x00});
38+
std::copy(aad.begin(), aad.end(), bytes_to_authenticate.begin());
39+
std::copy(ciphertext.begin(), ciphertext.end(), bytes_to_authenticate.begin() + padded_aad_size);
40+
WriteLE64(reinterpret_cast<unsigned char*>(bytes_to_authenticate.data()) + padded_aad_size + padded_ciphertext_size, aad.size());
41+
WriteLE64(reinterpret_cast<unsigned char*>(bytes_to_authenticate.data()) + padded_aad_size + padded_ciphertext_size + 8, ciphertext.size());
42+
43+
std::array<std::byte, POLY1305_TAGLEN> ret_tag;
44+
poly1305_auth(reinterpret_cast<unsigned char*>(ret_tag.data()),
45+
reinterpret_cast<const unsigned char*>(bytes_to_authenticate.data()),
46+
bytes_to_authenticate.size(),
47+
reinterpret_cast<const unsigned char*>(polykey.data()));
48+
49+
return ret_tag;
50+
}
51+
52+
std::array<std::byte, POLY1305_KEYLEN> GetPoly1305Key(ChaCha20& c20)
53+
{
54+
c20.SeekRFC8439(0);
55+
std::array<std::byte, POLY1305_KEYLEN> polykey;
56+
c20.Keystream(reinterpret_cast<unsigned char*>(polykey.data()), POLY1305_KEYLEN);
57+
return polykey;
58+
}
59+
60+
void RFC8439Crypt(ChaCha20& c20, Span<const std::byte> in_bytes, Span<std::byte> out_bytes)
61+
{
62+
assert(in_bytes.size() == out_bytes.size());
63+
c20.SeekRFC8439(1);
64+
c20.Crypt(reinterpret_cast<const unsigned char*>(in_bytes.data()), reinterpret_cast<unsigned char*>(out_bytes.data()), in_bytes.size());
65+
}
66+
67+
RFC8439Encrypted RFC8439Encrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, Span<const std::byte> plaintext)
68+
{
69+
assert(key.size() == RFC8439_KEYLEN);
70+
RFC8439Encrypted ret;
71+
72+
ChaCha20 c20{reinterpret_cast<const unsigned char*>(key.data()), key.size()};
73+
c20.SetRFC8439Nonce(nonce);
74+
75+
std::array<std::byte, POLY1305_KEYLEN> polykey{GetPoly1305Key(c20)};
76+
77+
ret.ciphertext.resize(plaintext.size());
78+
RFC8439Crypt(c20, plaintext, ret.ciphertext);
79+
ret.tag = ComputeRFC8439Tag(polykey, aad, ret.ciphertext);
80+
return ret;
81+
}
82+
83+
RFC8439Decrypted RFC8439Decrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, const RFC8439Encrypted& encrypted)
84+
{
85+
assert(key.size() == RFC8439_KEYLEN);
86+
assert(encrypted.tag.size() == POLY1305_TAGLEN);
87+
88+
RFC8439Decrypted ret;
89+
90+
ChaCha20 c20{reinterpret_cast<const unsigned char*>(key.data()), key.size()};
91+
c20.SetRFC8439Nonce(nonce);
92+
93+
std::array<std::byte, POLY1305_KEYLEN> polykey{GetPoly1305Key(c20)};
94+
auto tag = ComputeRFC8439Tag(polykey, aad, encrypted.ciphertext);
95+
96+
if (rfc8439_timingsafe_bcmp(reinterpret_cast<const unsigned char*>(encrypted.tag.data()),
97+
reinterpret_cast<const unsigned char*>(tag.data()), encrypted.tag.size()) != 0) {
98+
ret.success = false;
99+
return ret;
100+
}
101+
102+
ret.success = true;
103+
ret.plaintext.resize(encrypted.ciphertext.size());
104+
RFC8439Crypt(c20, encrypted.ciphertext, ret.plaintext);
105+
return ret;
106+
}

src/crypto/rfc8439.h

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_CRYPTO_RFC8439_H
6+
#define BITCOIN_CRYPTO_RFC8439_H
7+
8+
#include <crypto/poly1305.h>
9+
#include <span.h>
10+
11+
#include <array>
12+
#include <cstddef>
13+
#include <vector>
14+
15+
constexpr static size_t RFC8439_KEYLEN = 32;
16+
17+
struct RFC8439Encrypted {
18+
std::vector<std::byte> ciphertext;
19+
std::array<std::byte, POLY1305_TAGLEN> tag;
20+
};
21+
22+
struct RFC8439Decrypted {
23+
bool success;
24+
std::vector<std::byte> plaintext;
25+
std::array<std::byte, POLY1305_TAGLEN> tag;
26+
};
27+
28+
RFC8439Encrypted RFC8439Encrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, Span<const std::byte> plaintext);
29+
30+
RFC8439Decrypted RFC8439Decrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, const RFC8439Encrypted& encrypted);
31+
#endif // BITCOIN_CRYPTO_RFC8439_H

src/test/crypto_tests.cpp

+39
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
#include <crypto/hkdf_sha256_32.h>
99
#include <crypto/hmac_sha256.h>
1010
#include <crypto/hmac_sha512.h>
11+
#include <crypto/muhash.h>
1112
#include <crypto/poly1305.h>
13+
#include <crypto/rfc8439.h>
1214
#include <crypto/ripemd160.h>
1315
#include <crypto/sha1.h>
1416
#include <crypto/sha256.h>
1517
#include <crypto/sha3.h>
1618
#include <crypto/sha512.h>
1719
#include <crypto/muhash.h>
1820
#include <random.h>
21+
#include <span.h>
1922
#include <streams.h>
2023
#include <test/util/setup_common.h>
2124
#include <util/strencodings.h>
@@ -1041,4 +1044,40 @@ BOOST_AUTO_TEST_CASE(muhash_tests)
10411044
BOOST_CHECK_EQUAL(HexStr(out4), "3a31e6903aff0de9f62f9a9f7f8b861de76ce2cda09822b90014319ae5dc2271");
10421045
}
10431046

1047+
static void TestRFC8439AEAD(const std::string& hex_aad, const std::string& hex_key, const std::string& hex_nonce, const std::string& hex_plaintext, const std::string& hex_expected_ciphertext, const std::string& hex_expected_auth_tag)
1048+
{
1049+
auto aad = ParseHex(hex_aad);
1050+
auto key = ParseHex(hex_key);
1051+
auto nonce = ParseHex(hex_nonce);
1052+
std::array<std::byte, 12> nonce_arr;
1053+
memcpy(nonce_arr.data(), nonce.data(), 12);
1054+
auto plaintext = ParseHex(hex_plaintext);
1055+
auto encrypted = RFC8439Encrypt(MakeByteSpan(aad), MakeByteSpan(key), nonce_arr, MakeByteSpan(plaintext));
1056+
1057+
BOOST_CHECK_EQUAL(HexStr(MakeByteSpan(encrypted.ciphertext)), hex_expected_ciphertext);
1058+
BOOST_CHECK_EQUAL(HexStr(MakeByteSpan(encrypted.tag)), hex_expected_auth_tag);
1059+
1060+
auto decrypted = RFC8439Decrypt(MakeByteSpan(aad), MakeByteSpan(key), nonce_arr, encrypted);
1061+
BOOST_CHECK(decrypted.success);
1062+
BOOST_CHECK_EQUAL(0, memcmp(decrypted.plaintext.data(), plaintext.data(), plaintext.size()));
1063+
}
1064+
1065+
BOOST_AUTO_TEST_CASE(rfc8439_tests)
1066+
{
1067+
// Test vector from https://datatracker.ietf.org/doc/html/rfc8439#section-2.8.2
1068+
TestRFC8439AEAD("50515253c0c1c2c3c4c5c6c7",
1069+
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
1070+
"070000004041424344454647",
1071+
"4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e",
1072+
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116",
1073+
"1ae10b594f09e26a7e902ecbd0600691");
1074+
1075+
// Test vector from https://datatracker.ietf.org/doc/html/rfc8439#appendix-A.5
1076+
TestRFC8439AEAD("f33388860000000000004e91",
1077+
"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
1078+
"000000000102030405060708",
1079+
"496e7465726e65742d4472616674732061726520647261667420646f63756d656e74732076616c696420666f722061206d6178696d756d206f6620736978206d6f6e74687320616e64206d617920626520757064617465642c207265706c616365642c206f72206f62736f6c65746564206279206f7468657220646f63756d656e747320617420616e792074696d652e20497420697320696e617070726f70726961746520746f2075736520496e7465726e65742d447261667473206173207265666572656e6365206d6174657269616c206f7220746f2063697465207468656d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67726573732e2fe2809d",
1080+
"64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb24c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c8559797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523eaf4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a1049e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29a6ad5cb4022b02709b",
1081+
"eead9d67890cbb22392336fea1851f38");
1082+
}
10441083
BOOST_AUTO_TEST_SUITE_END()

src/test/fuzz/crypto_rfc8439.cpp

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <crypto/rfc8439.h>
6+
#include <test/fuzz/FuzzedDataProvider.h>
7+
#include <test/fuzz/fuzz.h>
8+
#include <test/fuzz/util.h>
9+
10+
#include <cstddef>
11+
12+
FUZZ_TARGET(crypto_rfc8439)
13+
{
14+
FuzzedDataProvider fdp{buffer.data(), buffer.size()};
15+
auto aad_len = fdp.ConsumeIntegralInRange(0, 1023);
16+
auto aad = fdp.ConsumeBytes<std::byte>(aad_len);
17+
aad.resize(aad_len);
18+
19+
auto key = fdp.ConsumeBytes<std::byte>(RFC8439_KEYLEN);
20+
key.resize(RFC8439_KEYLEN);
21+
22+
auto nonce_vec = fdp.ConsumeBytes<std::byte>(12);
23+
nonce_vec.resize(12);
24+
std::array<std::byte, 12> nonce;
25+
memcpy(nonce.data(), nonce_vec.data(), 12);
26+
27+
auto plaintext_len = fdp.ConsumeIntegralInRange(0, 4095);
28+
auto plaintext = fdp.ConsumeBytes<std::byte>(plaintext_len);
29+
plaintext.resize(plaintext_len);
30+
31+
auto encrypted = RFC8439Encrypt(aad, key, nonce, plaintext);
32+
assert(encrypted.ciphertext.size() == plaintext.size());
33+
34+
auto bit_flip_attack = !plaintext.empty() && fdp.ConsumeBool();
35+
if (bit_flip_attack) {
36+
auto byte_to_flip = fdp.ConsumeIntegralInRange(0, static_cast<int>(encrypted.ciphertext.size() - 1));
37+
encrypted.ciphertext[byte_to_flip] = encrypted.ciphertext[byte_to_flip] ^ std::byte{0xFF};
38+
}
39+
40+
auto decrypted = RFC8439Decrypt(aad, key, nonce, encrypted);
41+
if (bit_flip_attack) {
42+
assert(!decrypted.success);
43+
} else {
44+
assert(decrypted.success);
45+
assert(plaintext.size() == decrypted.plaintext.size());
46+
if (!plaintext.empty()) {
47+
assert(memcmp(decrypted.plaintext.data(), plaintext.data(), plaintext.size()) == 0);
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)