Merge bitcoin/bitcoin#28008: BIP324 ciphersuite

1c7582ead6 tests: add decryption test to bip324_tests (Pieter Wuille)
990f0f8da9 Add BIP324Cipher, encapsulating key agreement, derivation, and stream/AEAD ciphers (Pieter Wuille)
c91cedf281 crypto: support split plaintext in ChaCha20Poly1305 Encrypt/Decrypt (Pieter Wuille)
af2b44c76e bench: add benchmark for FSChaCha20Poly1305 (Pieter Wuille)
aa8cee9334 crypto: add FSChaCha20Poly1305, rekeying wrapper around ChaCha20Poly1305 (Pieter Wuille)
0fee267792 crypto: add FSChaCha20, a rekeying wrapper around ChaCha20 (Pieter Wuille)
9ff0768bdc crypto: add the ChaCha20Poly1305 AEAD as specified in RFC8439 (Pieter Wuille)
9fd085a1a4 crypto: 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:
    reACK 1c7582e
  theStack:
    ACK 1c7582ead6
  stratospher:
    tested ACK 1c7582e.

Tree-SHA512: 06728b4b95b21c5b732ed08faf40e94d0583f9d86ff4db3b92dd519dcd9fbfa0f310bc66ef1e59c9e49dd844ba8c5ac06e2001762a804fb5aa97027816045a46
This commit is contained in:
fanquake
2023-08-10 11:58:41 +02:00
19 changed files with 1312 additions and 605 deletions

137
src/test/fuzz/bip324.cpp Normal file
View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
});
}
}