mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-01 08:44:02 +02:00
Merge bitcoin/bitcoin#28008: BIP324 ciphersuite
1c7582ead6tests: add decryption test to bip324_tests (Pieter Wuille)990f0f8da9Add BIP324Cipher, encapsulating key agreement, derivation, and stream/AEAD ciphers (Pieter Wuille)c91cedf281crypto: support split plaintext in ChaCha20Poly1305 Encrypt/Decrypt (Pieter Wuille)af2b44c76ebench: add benchmark for FSChaCha20Poly1305 (Pieter Wuille)aa8cee9334crypto: add FSChaCha20Poly1305, rekeying wrapper around ChaCha20Poly1305 (Pieter Wuille)0fee267792crypto: add FSChaCha20, a rekeying wrapper around ChaCha20 (Pieter Wuille)9ff0768bdccrypto: add the ChaCha20Poly1305 AEAD as specified in RFC8439 (Pieter Wuille)9fd085a1a4crypto: remove outdated variant of ChaCha20Poly1305 AEAD (Pieter Wuille) Pull request description: Depends on #27985 and #27993, based on and partially replaces #25361, part of #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 #27993. ACKs for top commit: jamesob: reACK1c7582etheStack: ACK1c7582ead6stratospher: tested ACK1c7582e. Tree-SHA512: 06728b4b95b21c5b732ed08faf40e94d0583f9d86ff4db3b92dd519dcd9fbfa0f310bc66ef1e59c9e49dd844ba8c5ac06e2001762a804fb5aa97027816045a46
This commit is contained in:
137
src/test/fuzz/bip324.cpp
Normal file
137
src/test/fuzz/bip324.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
// 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 <bip324.h>
|
||||
#include <chainparams.h>
|
||||
#include <span.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
#include <test/util/xoroshiro128plusplus.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
ECC_Start();
|
||||
SelectParams(ChainType::MAIN);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize)
|
||||
{
|
||||
// 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<unsigned char>(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<std::byte>(32);
|
||||
init_ent.resize(32);
|
||||
// Responder key
|
||||
auto resp_key_data = provider.ConsumeBytes<unsigned char>(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<std::byte>(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<uint64_t>());
|
||||
|
||||
// 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 511 bytes)
|
||||
// - Bit 5-7: controls the maximum content length (max 16383 bytes, for performance reasons)
|
||||
unsigned mode = provider.ConsumeIntegral<uint8_t>();
|
||||
bool ignore = mode & 1;
|
||||
bool from_init = mode & 2;
|
||||
bool damage = mode & 4;
|
||||
unsigned aad_length_bits = 3 * ((mode >> 3) & 3);
|
||||
unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1);
|
||||
unsigned length_bits = 2 * ((mode >> 5) & 7);
|
||||
unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1);
|
||||
// Generate aad and content.
|
||||
std::vector<std::byte> aad(aad_length);
|
||||
for (auto& val : aad) val = std::byte{(uint8_t)rng()};
|
||||
std::vector<std::byte> 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<std::byte> 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<unsigned>(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<std::byte> 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);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <test/fuzz/util.h>
|
||||
#include <test/util/xoroshiro128plusplus.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
@@ -151,3 +153,21 @@ FUZZ_TARGET(chacha20_split_keystream)
|
||||
FuzzedDataProvider provider{buffer.data(), buffer.size()};
|
||||
ChaCha20SplitFuzz<false>(provider);
|
||||
}
|
||||
|
||||
FUZZ_TARGET(crypto_fschacha20)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||
|
||||
auto key = fuzzed_data_provider.ConsumeBytes<std::byte>(FSChaCha20::KEYLEN);
|
||||
key.resize(FSChaCha20::KEYLEN);
|
||||
|
||||
auto fsc20 = FSChaCha20{key, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 1024)};
|
||||
|
||||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
|
||||
{
|
||||
auto input = fuzzed_data_provider.ConsumeBytes<std::byte>(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096));
|
||||
std::vector<std::byte> output;
|
||||
output.resize(input.size());
|
||||
fsc20.Crypt(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) 2020-2021 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <crypto/chacha_poly_aead.h>
|
||||
#include <crypto/poly1305.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
#include <util/overflow.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
FUZZ_TARGET(crypto_chacha20_poly1305_aead)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||
|
||||
const std::vector<uint8_t> k1 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN);
|
||||
const std::vector<uint8_t> 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<size_t>(0, 4096);
|
||||
std::vector<uint8_t> in(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + Poly1305::TAGLEN, 0);
|
||||
std::vector<uint8_t> out(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + Poly1305::TAGLEN, 0);
|
||||
bool is_encrypt = fuzzed_data_provider.ConsumeBool();
|
||||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
|
||||
CallOneOf(
|
||||
fuzzed_data_provider,
|
||||
[&] {
|
||||
buffer_size = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(64, 4096);
|
||||
in = std::vector<uint8_t>(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + Poly1305::TAGLEN, 0);
|
||||
out = std::vector<uint8_t>(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<uint64_t>(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<uint64_t>(1))) {
|
||||
return;
|
||||
}
|
||||
seqnr_aad += 1;
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
seqnr_payload = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
|
||||
},
|
||||
[&] {
|
||||
seqnr_aad = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
|
||||
},
|
||||
[&] {
|
||||
is_encrypt = fuzzed_data_provider.ConsumeBool();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user