Skip to content

Commit b2ec032

Browse files
committed
Merge bitcoin#28008: BIP324 ciphersuite
1c7582e tests: add decryption test to bip324_tests (Pieter Wuille) 990f0f8 Add BIP324Cipher, encapsulating key agreement, derivation, and stream/AEAD ciphers (Pieter Wuille) c91cedf crypto: support split plaintext in ChaCha20Poly1305 Encrypt/Decrypt (Pieter Wuille) af2b44c bench: add benchmark for FSChaCha20Poly1305 (Pieter Wuille) aa8cee9 crypto: add FSChaCha20Poly1305, rekeying wrapper around ChaCha20Poly1305 (Pieter Wuille) 0fee267 crypto: add FSChaCha20, a rekeying wrapper around ChaCha20 (Pieter Wuille) 9ff0768 crypto: add the ChaCha20Poly1305 AEAD as specified in RFC8439 (Pieter Wuille) 9fd085a crypto: remove outdated variant of ChaCha20Poly1305 AEAD (Pieter Wuille) Pull request description: Depends on bitcoin#27985 and bitcoin#27993, based on and partially replaces bitcoin#25361, part of bitcoin#27634. Draft while dependencies are not merged. This adds implementations of: * The ChaCha20Poly1305 AEAD from [RFC8439 section 2.8](https://datatracker.ietf.org/doc/html/rfc8439#section-2.8), including test vectors. * The FSChaCha20 stream cipher as specified in [BIP324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki#rekeying-wrappers-fschacha20poly1305-and-fschacha20), a rekeying wrapper around ChaCha20. * The FSChaCha20Poly1305 AEAD as specified in [BIP324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki#rekeying-wrappers-fschacha20poly1305-and-fschacha20), a rekeying wrapper around ChaCha20Poly1305. * A BIP324Cipher class that encapsulates key agreement, key derivation, and stream ciphers and AEADs for [BIP324 packet encoding](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki#overall-packet-encryption-and-decryption-pseudocode). The ChaCha20Poly1305 and FSChaCha20Poly1305 implementations are new, taking advance of the improvements in bitcoin#27993. ACKs for top commit: jamesob: reACK 1c7582e theStack: ACK 1c7582e stratospher: tested ACK 1c7582e. Tree-SHA512: 06728b4b95b21c5b732ed08faf40e94d0583f9d86ff4db3b92dd519dcd9fbfa0f310bc66ef1e59c9e49dd844ba8c5ac06e2001762a804fb5aa97027816045a46
2 parents ef3f9f3 + 1c7582e commit b2ec032

19 files changed

+1312
-605
lines changed

src/Makefile.am

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ BITCOIN_CORE_H = \
124124
banman.h \
125125
base58.h \
126126
bech32.h \
127+
bip324.h \
127128
blockencodings.h \
128129
blockfilter.h \
129130
chain.h \
@@ -376,6 +377,7 @@ libbitcoin_node_a_SOURCES = \
376377
addrdb.cpp \
377378
addrman.cpp \
378379
banman.cpp \
380+
bip324.cpp \
379381
blockencodings.cpp \
380382
blockfilter.cpp \
381383
chain.cpp \
@@ -546,10 +548,10 @@ crypto_libbitcoin_crypto_base_la_LDFLAGS = $(AM_LDFLAGS) -static
546548
crypto_libbitcoin_crypto_base_la_SOURCES = \
547549
crypto/aes.cpp \
548550
crypto/aes.h \
549-
crypto/chacha_poly_aead.h \
550-
crypto/chacha_poly_aead.cpp \
551551
crypto/chacha20.h \
552552
crypto/chacha20.cpp \
553+
crypto/chacha20poly1305.h \
554+
crypto/chacha20poly1305.cpp \
553555
crypto/common.h \
554556
crypto/hkdf_sha256_32.cpp \
555557
crypto/hkdf_sha256_32.h \

src/Makefile.bench.include

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ bench_bench_bitcoin_SOURCES = \
2222
bench/block_assemble.cpp \
2323
bench/ccoins_caching.cpp \
2424
bench/chacha20.cpp \
25-
bench/chacha_poly_aead.cpp \
2625
bench/checkblock.cpp \
2726
bench/checkqueue.cpp \
2827
bench/crypto_hash.cpp \

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ BITCOIN_TESTS =\
7474
test/base64_tests.cpp \
7575
test/bech32_tests.cpp \
7676
test/bip32_tests.cpp \
77+
test/bip324_tests.cpp \
7778
test/blockchain_tests.cpp \
7879
test/blockencodings_tests.cpp \
7980
test/blockfilter_index_tests.cpp \
@@ -246,6 +247,7 @@ test_fuzz_fuzz_SOURCES = \
246247
test/fuzz/banman.cpp \
247248
test/fuzz/base_encode_decode.cpp \
248249
test/fuzz/bech32.cpp \
250+
test/fuzz/bip324.cpp \
249251
test/fuzz/bitdeque.cpp \
250252
test/fuzz/block.cpp \
251253
test/fuzz/block_header.cpp \
@@ -261,7 +263,6 @@ test_fuzz_fuzz_SOURCES = \
261263
test/fuzz/crypto_aes256.cpp \
262264
test/fuzz/crypto_aes256cbc.cpp \
263265
test/fuzz/crypto_chacha20.cpp \
264-
test/fuzz/crypto_chacha20_poly1305_aead.cpp \
265266
test/fuzz/crypto_common.cpp \
266267
test/fuzz/crypto_diff_fuzz_chacha20.cpp \
267268
test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp \

src/bench/chacha20.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <bench/bench.h>
77
#include <crypto/chacha20.h>
8+
#include <crypto/chacha20poly1305.h>
89

910
/* Number of bytes to process per iteration */
1011
static const uint64_t BUFFER_SIZE_TINY = 64;
@@ -23,6 +24,18 @@ static void CHACHA20(benchmark::Bench& bench, size_t buffersize)
2324
});
2425
}
2526

27+
static void FSCHACHA20POLY1305(benchmark::Bench& bench, size_t buffersize)
28+
{
29+
std::vector<std::byte> key(32);
30+
FSChaCha20Poly1305 ctx(key, 224);
31+
std::vector<std::byte> in(buffersize);
32+
std::vector<std::byte> aad;
33+
std::vector<std::byte> out(buffersize + FSChaCha20Poly1305::EXPANSION);
34+
bench.batch(in.size()).unit("byte").run([&] {
35+
ctx.Encrypt(in, aad, out);
36+
});
37+
}
38+
2639
static void CHACHA20_64BYTES(benchmark::Bench& bench)
2740
{
2841
CHACHA20(bench, BUFFER_SIZE_TINY);
@@ -38,6 +51,24 @@ static void CHACHA20_1MB(benchmark::Bench& bench)
3851
CHACHA20(bench, BUFFER_SIZE_LARGE);
3952
}
4053

54+
static void FSCHACHA20POLY1305_64BYTES(benchmark::Bench& bench)
55+
{
56+
FSCHACHA20POLY1305(bench, BUFFER_SIZE_TINY);
57+
}
58+
59+
static void FSCHACHA20POLY1305_256BYTES(benchmark::Bench& bench)
60+
{
61+
FSCHACHA20POLY1305(bench, BUFFER_SIZE_SMALL);
62+
}
63+
64+
static void FSCHACHA20POLY1305_1MB(benchmark::Bench& bench)
65+
{
66+
FSCHACHA20POLY1305(bench, BUFFER_SIZE_LARGE);
67+
}
68+
4169
BENCHMARK(CHACHA20_64BYTES, benchmark::PriorityLevel::HIGH);
4270
BENCHMARK(CHACHA20_256BYTES, benchmark::PriorityLevel::HIGH);
4371
BENCHMARK(CHACHA20_1MB, benchmark::PriorityLevel::HIGH);
72+
BENCHMARK(FSCHACHA20POLY1305_64BYTES, benchmark::PriorityLevel::HIGH);
73+
BENCHMARK(FSCHACHA20POLY1305_256BYTES, benchmark::PriorityLevel::HIGH);
74+
BENCHMARK(FSCHACHA20POLY1305_1MB, benchmark::PriorityLevel::HIGH);

src/bench/chacha_poly_aead.cpp

Lines changed: 0 additions & 126 deletions
This file was deleted.

src/bip324.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) 2023 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 <bip324.h>
6+
7+
#include <chainparams.h>
8+
#include <crypto/chacha20.h>
9+
#include <crypto/chacha20poly1305.h>
10+
#include <crypto/hkdf_sha256_32.h>
11+
#include <random.h>
12+
#include <span.h>
13+
#include <support/cleanse.h>
14+
15+
#include <algorithm>
16+
#include <assert.h>
17+
#include <cstdint>
18+
#include <cstddef>
19+
20+
BIP324Cipher::BIP324Cipher() noexcept
21+
{
22+
m_key.MakeNewKey(true);
23+
uint256 entropy = GetRandHash();
24+
m_our_pubkey = m_key.EllSwiftCreate(MakeByteSpan(entropy));
25+
}
26+
27+
BIP324Cipher::BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcept :
28+
m_key(key)
29+
{
30+
m_our_pubkey = m_key.EllSwiftCreate(ent32);
31+
}
32+
33+
BIP324Cipher::BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept :
34+
m_key(key), m_our_pubkey(pubkey) {}
35+
36+
void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt) noexcept
37+
{
38+
// Determine salt (fixed string + network magic bytes)
39+
const auto& message_header = Params().MessageStart();
40+
std::string salt = std::string{"bitcoin_v2_shared_secret"} + std::string(std::begin(message_header), std::end(message_header));
41+
42+
// Perform ECDH to derive shared secret.
43+
ECDHSecret ecdh_secret = m_key.ComputeBIP324ECDHSecret(their_pubkey, m_our_pubkey, initiator);
44+
45+
// Derive encryption keys from shared secret, and initialize stream ciphers and AEADs.
46+
bool side = (initiator != self_decrypt);
47+
CHKDF_HMAC_SHA256_L32 hkdf(UCharCast(ecdh_secret.data()), ecdh_secret.size(), salt);
48+
std::array<std::byte, 32> hkdf_32_okm;
49+
hkdf.Expand32("initiator_L", UCharCast(hkdf_32_okm.data()));
50+
(side ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
51+
hkdf.Expand32("initiator_P", UCharCast(hkdf_32_okm.data()));
52+
(side ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
53+
hkdf.Expand32("responder_L", UCharCast(hkdf_32_okm.data()));
54+
(side ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
55+
hkdf.Expand32("responder_P", UCharCast(hkdf_32_okm.data()));
56+
(side ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
57+
58+
// Derive garbage terminators from shared secret.
59+
hkdf.Expand32("garbage_terminators", UCharCast(hkdf_32_okm.data()));
60+
std::copy(std::begin(hkdf_32_okm), std::begin(hkdf_32_okm) + GARBAGE_TERMINATOR_LEN,
61+
(initiator ? m_send_garbage_terminator : m_recv_garbage_terminator).begin());
62+
std::copy(std::end(hkdf_32_okm) - GARBAGE_TERMINATOR_LEN, std::end(hkdf_32_okm),
63+
(initiator ? m_recv_garbage_terminator : m_send_garbage_terminator).begin());
64+
65+
// Derive session id from shared secret.
66+
hkdf.Expand32("session_id", UCharCast(m_session_id.data()));
67+
68+
// Wipe all variables that contain information which could be used to re-derive encryption keys.
69+
memory_cleanse(ecdh_secret.data(), ecdh_secret.size());
70+
memory_cleanse(hkdf_32_okm.data(), sizeof(hkdf_32_okm));
71+
memory_cleanse(&hkdf, sizeof(hkdf));
72+
m_key = CKey();
73+
}
74+
75+
void BIP324Cipher::Encrypt(Span<const std::byte> contents, Span<const std::byte> aad, bool ignore, Span<std::byte> output) noexcept
76+
{
77+
assert(output.size() == contents.size() + EXPANSION);
78+
79+
// Encrypt length.
80+
std::byte len[LENGTH_LEN];
81+
len[0] = std::byte{(uint8_t)(contents.size() & 0xFF)};
82+
len[1] = std::byte{(uint8_t)((contents.size() >> 8) & 0xFF)};
83+
len[2] = std::byte{(uint8_t)((contents.size() >> 16) & 0xFF)};
84+
m_send_l_cipher->Crypt(len, output.first(LENGTH_LEN));
85+
86+
// Encrypt plaintext.
87+
std::byte header[HEADER_LEN] = {ignore ? IGNORE_BIT : std::byte{0}};
88+
m_send_p_cipher->Encrypt(header, contents, aad, output.subspan(LENGTH_LEN));
89+
}
90+
91+
uint32_t BIP324Cipher::DecryptLength(Span<const std::byte> input) noexcept
92+
{
93+
assert(input.size() == LENGTH_LEN);
94+
95+
std::byte buf[LENGTH_LEN];
96+
// Decrypt length
97+
m_recv_l_cipher->Crypt(input, buf);
98+
// Convert to number.
99+
return uint32_t(buf[0]) + (uint32_t(buf[1]) << 8) + (uint32_t(buf[2]) << 16);
100+
}
101+
102+
bool BIP324Cipher::Decrypt(Span<const std::byte> input, Span<const std::byte> aad, bool& ignore, Span<std::byte> contents) noexcept
103+
{
104+
assert(input.size() + LENGTH_LEN == contents.size() + EXPANSION);
105+
106+
std::byte header[HEADER_LEN];
107+
if (!m_recv_p_cipher->Decrypt(input, aad, header, contents)) return false;
108+
109+
ignore = (header[0] & IGNORE_BIT) == IGNORE_BIT;
110+
return true;
111+
}

0 commit comments

Comments
 (0)