mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-22 15:04:44 +01:00
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: reACK1c7582e
theStack: ACK1c7582ead6
stratospher: tested ACK1c7582e
. Tree-SHA512: 06728b4b95b21c5b732ed08faf40e94d0583f9d86ff4db3b92dd519dcd9fbfa0f310bc66ef1e59c9e49dd844ba8c5ac06e2001762a804fb5aa97027816045a46
This commit is contained in:
commit
b2ec0326fd
19 changed files with 1312 additions and 605 deletions
|
@ -124,6 +124,7 @@ BITCOIN_CORE_H = \
|
|||
banman.h \
|
||||
base58.h \
|
||||
bech32.h \
|
||||
bip324.h \
|
||||
blockencodings.h \
|
||||
blockfilter.h \
|
||||
chain.h \
|
||||
|
@ -376,6 +377,7 @@ libbitcoin_node_a_SOURCES = \
|
|||
addrdb.cpp \
|
||||
addrman.cpp \
|
||||
banman.cpp \
|
||||
bip324.cpp \
|
||||
blockencodings.cpp \
|
||||
blockfilter.cpp \
|
||||
chain.cpp \
|
||||
|
@ -546,10 +548,10 @@ crypto_libbitcoin_crypto_base_la_LDFLAGS = $(AM_LDFLAGS) -static
|
|||
crypto_libbitcoin_crypto_base_la_SOURCES = \
|
||||
crypto/aes.cpp \
|
||||
crypto/aes.h \
|
||||
crypto/chacha_poly_aead.h \
|
||||
crypto/chacha_poly_aead.cpp \
|
||||
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 \
|
||||
|
|
|
@ -22,7 +22,6 @@ bench_bench_bitcoin_SOURCES = \
|
|||
bench/block_assemble.cpp \
|
||||
bench/ccoins_caching.cpp \
|
||||
bench/chacha20.cpp \
|
||||
bench/chacha_poly_aead.cpp \
|
||||
bench/checkblock.cpp \
|
||||
bench/checkqueue.cpp \
|
||||
bench/crypto_hash.cpp \
|
||||
|
|
|
@ -74,6 +74,7 @@ BITCOIN_TESTS =\
|
|||
test/base64_tests.cpp \
|
||||
test/bech32_tests.cpp \
|
||||
test/bip32_tests.cpp \
|
||||
test/bip324_tests.cpp \
|
||||
test/blockchain_tests.cpp \
|
||||
test/blockencodings_tests.cpp \
|
||||
test/blockfilter_index_tests.cpp \
|
||||
|
@ -246,6 +247,7 @@ test_fuzz_fuzz_SOURCES = \
|
|||
test/fuzz/banman.cpp \
|
||||
test/fuzz/base_encode_decode.cpp \
|
||||
test/fuzz/bech32.cpp \
|
||||
test/fuzz/bip324.cpp \
|
||||
test/fuzz/bitdeque.cpp \
|
||||
test/fuzz/block.cpp \
|
||||
test/fuzz/block_header.cpp \
|
||||
|
@ -261,7 +263,6 @@ test_fuzz_fuzz_SOURCES = \
|
|||
test/fuzz/crypto_aes256.cpp \
|
||||
test/fuzz/crypto_aes256cbc.cpp \
|
||||
test/fuzz/crypto_chacha20.cpp \
|
||||
test/fuzz/crypto_chacha20_poly1305_aead.cpp \
|
||||
test/fuzz/crypto_common.cpp \
|
||||
test/fuzz/crypto_diff_fuzz_chacha20.cpp \
|
||||
test/fuzz/crypto_hkdf_hmac_sha256_l32.cpp \
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <bench/bench.h>
|
||||
#include <crypto/chacha20.h>
|
||||
#include <crypto/chacha20poly1305.h>
|
||||
|
||||
/* Number of bytes to process per iteration */
|
||||
static const uint64_t BUFFER_SIZE_TINY = 64;
|
||||
|
@ -23,6 +24,18 @@ static void CHACHA20(benchmark::Bench& bench, size_t buffersize)
|
|||
});
|
||||
}
|
||||
|
||||
static void FSCHACHA20POLY1305(benchmark::Bench& bench, size_t buffersize)
|
||||
{
|
||||
std::vector<std::byte> key(32);
|
||||
FSChaCha20Poly1305 ctx(key, 224);
|
||||
std::vector<std::byte> in(buffersize);
|
||||
std::vector<std::byte> aad;
|
||||
std::vector<std::byte> out(buffersize + FSChaCha20Poly1305::EXPANSION);
|
||||
bench.batch(in.size()).unit("byte").run([&] {
|
||||
ctx.Encrypt(in, aad, out);
|
||||
});
|
||||
}
|
||||
|
||||
static void CHACHA20_64BYTES(benchmark::Bench& bench)
|
||||
{
|
||||
CHACHA20(bench, BUFFER_SIZE_TINY);
|
||||
|
@ -38,6 +51,24 @@ static void CHACHA20_1MB(benchmark::Bench& bench)
|
|||
CHACHA20(bench, BUFFER_SIZE_LARGE);
|
||||
}
|
||||
|
||||
static void FSCHACHA20POLY1305_64BYTES(benchmark::Bench& bench)
|
||||
{
|
||||
FSCHACHA20POLY1305(bench, BUFFER_SIZE_TINY);
|
||||
}
|
||||
|
||||
static void FSCHACHA20POLY1305_256BYTES(benchmark::Bench& bench)
|
||||
{
|
||||
FSCHACHA20POLY1305(bench, BUFFER_SIZE_SMALL);
|
||||
}
|
||||
|
||||
static void FSCHACHA20POLY1305_1MB(benchmark::Bench& bench)
|
||||
{
|
||||
FSCHACHA20POLY1305(bench, BUFFER_SIZE_LARGE);
|
||||
}
|
||||
|
||||
BENCHMARK(CHACHA20_64BYTES, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(CHACHA20_256BYTES, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(CHACHA20_1MB, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(FSCHACHA20POLY1305_64BYTES, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(FSCHACHA20POLY1305_256BYTES, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(FSCHACHA20POLY1305_1MB, benchmark::PriorityLevel::HIGH);
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
// Copyright (c) 2019-2022 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 <bench/bench.h>
|
||||
#include <crypto/chacha_poly_aead.h>
|
||||
#include <crypto/poly1305.h> // for the POLY1305_TAGLEN constant
|
||||
#include <hash.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits>
|
||||
|
||||
/* Number of bytes to process per iteration */
|
||||
static constexpr uint64_t BUFFER_SIZE_TINY = 64;
|
||||
static constexpr uint64_t BUFFER_SIZE_SMALL = 256;
|
||||
static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024;
|
||||
|
||||
static const unsigned char k1[32] = {0};
|
||||
static const unsigned char k2[32] = {0};
|
||||
|
||||
static ChaCha20Poly1305AEAD aead(k1, 32, k2, 32);
|
||||
|
||||
static void CHACHA20_POLY1305_AEAD(benchmark::Bench& bench, size_t buffersize, bool include_decryption)
|
||||
{
|
||||
std::vector<unsigned char> in(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + Poly1305::TAGLEN, 0);
|
||||
std::vector<unsigned char> out(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + Poly1305::TAGLEN, 0);
|
||||
uint64_t seqnr_payload = 0;
|
||||
uint64_t seqnr_aad = 0;
|
||||
int aad_pos = 0;
|
||||
uint32_t len = 0;
|
||||
bench.batch(buffersize).unit("byte").run([&] {
|
||||
// encrypt or decrypt the buffer with a static key
|
||||
const bool crypt_ok_1 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true);
|
||||
assert(crypt_ok_1);
|
||||
|
||||
if (include_decryption) {
|
||||
// if we decrypt, include the GetLength
|
||||
const bool get_length_ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data());
|
||||
assert(get_length_ok);
|
||||
const bool crypt_ok_2 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true);
|
||||
assert(crypt_ok_2);
|
||||
}
|
||||
|
||||
// increase main sequence number
|
||||
seqnr_payload++;
|
||||
// increase aad position (position in AAD keystream)
|
||||
aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN;
|
||||
if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) {
|
||||
aad_pos = 0;
|
||||
seqnr_aad++;
|
||||
}
|
||||
if (seqnr_payload + 1 == std::numeric_limits<uint64_t>::max()) {
|
||||
// reuse of nonce+key is okay while benchmarking.
|
||||
seqnr_payload = 0;
|
||||
seqnr_aad = 0;
|
||||
aad_pos = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT(benchmark::Bench& bench)
|
||||
{
|
||||
CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_TINY, false);
|
||||
}
|
||||
|
||||
static void CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT(benchmark::Bench& bench)
|
||||
{
|
||||
CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_SMALL, false);
|
||||
}
|
||||
|
||||
static void CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT(benchmark::Bench& bench)
|
||||
{
|
||||
CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_LARGE, false);
|
||||
}
|
||||
|
||||
static void CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench)
|
||||
{
|
||||
CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_TINY, true);
|
||||
}
|
||||
|
||||
static void CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT(benchmark::Bench& bench)
|
||||
{
|
||||
CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_SMALL, true);
|
||||
}
|
||||
|
||||
static void CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT(benchmark::Bench& bench)
|
||||
{
|
||||
CHACHA20_POLY1305_AEAD(bench, BUFFER_SIZE_LARGE, true);
|
||||
}
|
||||
|
||||
// Add Hash() (dbl-sha256) bench for comparison
|
||||
|
||||
static void HASH(benchmark::Bench& bench, size_t buffersize)
|
||||
{
|
||||
uint8_t hash[CHash256::OUTPUT_SIZE];
|
||||
std::vector<uint8_t> in(buffersize,0);
|
||||
bench.batch(in.size()).unit("byte").run([&] {
|
||||
CHash256().Write(in).Finalize(hash);
|
||||
});
|
||||
}
|
||||
|
||||
static void HASH_64BYTES(benchmark::Bench& bench)
|
||||
{
|
||||
HASH(bench, BUFFER_SIZE_TINY);
|
||||
}
|
||||
|
||||
static void HASH_256BYTES(benchmark::Bench& bench)
|
||||
{
|
||||
HASH(bench, BUFFER_SIZE_SMALL);
|
||||
}
|
||||
|
||||
static void HASH_1MB(benchmark::Bench& bench)
|
||||
{
|
||||
HASH(bench, BUFFER_SIZE_LARGE);
|
||||
}
|
||||
|
||||
BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(HASH_64BYTES, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(HASH_256BYTES, benchmark::PriorityLevel::HIGH);
|
||||
BENCHMARK(HASH_1MB, benchmark::PriorityLevel::HIGH);
|
111
src/bip324.cpp
Normal file
111
src/bip324.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
// 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 <crypto/chacha20.h>
|
||||
#include <crypto/chacha20poly1305.h>
|
||||
#include <crypto/hkdf_sha256_32.h>
|
||||
#include <random.h>
|
||||
#include <span.h>
|
||||
#include <support/cleanse.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
BIP324Cipher::BIP324Cipher() noexcept
|
||||
{
|
||||
m_key.MakeNewKey(true);
|
||||
uint256 entropy = GetRandHash();
|
||||
m_our_pubkey = m_key.EllSwiftCreate(MakeByteSpan(entropy));
|
||||
}
|
||||
|
||||
BIP324Cipher::BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcept :
|
||||
m_key(key)
|
||||
{
|
||||
m_our_pubkey = m_key.EllSwiftCreate(ent32);
|
||||
}
|
||||
|
||||
BIP324Cipher::BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept :
|
||||
m_key(key), m_our_pubkey(pubkey) {}
|
||||
|
||||
void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt) noexcept
|
||||
{
|
||||
// Determine salt (fixed string + network magic bytes)
|
||||
const auto& message_header = Params().MessageStart();
|
||||
std::string salt = std::string{"bitcoin_v2_shared_secret"} + std::string(std::begin(message_header), std::end(message_header));
|
||||
|
||||
// Perform ECDH to derive shared secret.
|
||||
ECDHSecret ecdh_secret = m_key.ComputeBIP324ECDHSecret(their_pubkey, m_our_pubkey, initiator);
|
||||
|
||||
// Derive encryption keys from shared secret, and initialize stream ciphers and AEADs.
|
||||
bool side = (initiator != self_decrypt);
|
||||
CHKDF_HMAC_SHA256_L32 hkdf(UCharCast(ecdh_secret.data()), ecdh_secret.size(), salt);
|
||||
std::array<std::byte, 32> hkdf_32_okm;
|
||||
hkdf.Expand32("initiator_L", UCharCast(hkdf_32_okm.data()));
|
||||
(side ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
|
||||
hkdf.Expand32("initiator_P", UCharCast(hkdf_32_okm.data()));
|
||||
(side ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
|
||||
hkdf.Expand32("responder_L", UCharCast(hkdf_32_okm.data()));
|
||||
(side ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
|
||||
hkdf.Expand32("responder_P", UCharCast(hkdf_32_okm.data()));
|
||||
(side ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
|
||||
|
||||
// Derive garbage terminators from shared secret.
|
||||
hkdf.Expand32("garbage_terminators", UCharCast(hkdf_32_okm.data()));
|
||||
std::copy(std::begin(hkdf_32_okm), std::begin(hkdf_32_okm) + GARBAGE_TERMINATOR_LEN,
|
||||
(initiator ? m_send_garbage_terminator : m_recv_garbage_terminator).begin());
|
||||
std::copy(std::end(hkdf_32_okm) - GARBAGE_TERMINATOR_LEN, std::end(hkdf_32_okm),
|
||||
(initiator ? m_recv_garbage_terminator : m_send_garbage_terminator).begin());
|
||||
|
||||
// Derive session id from shared secret.
|
||||
hkdf.Expand32("session_id", UCharCast(m_session_id.data()));
|
||||
|
||||
// Wipe all variables that contain information which could be used to re-derive encryption keys.
|
||||
memory_cleanse(ecdh_secret.data(), ecdh_secret.size());
|
||||
memory_cleanse(hkdf_32_okm.data(), sizeof(hkdf_32_okm));
|
||||
memory_cleanse(&hkdf, sizeof(hkdf));
|
||||
m_key = CKey();
|
||||
}
|
||||
|
||||
void BIP324Cipher::Encrypt(Span<const std::byte> contents, Span<const std::byte> aad, bool ignore, Span<std::byte> output) noexcept
|
||||
{
|
||||
assert(output.size() == contents.size() + EXPANSION);
|
||||
|
||||
// Encrypt length.
|
||||
std::byte len[LENGTH_LEN];
|
||||
len[0] = std::byte{(uint8_t)(contents.size() & 0xFF)};
|
||||
len[1] = std::byte{(uint8_t)((contents.size() >> 8) & 0xFF)};
|
||||
len[2] = std::byte{(uint8_t)((contents.size() >> 16) & 0xFF)};
|
||||
m_send_l_cipher->Crypt(len, output.first(LENGTH_LEN));
|
||||
|
||||
// Encrypt plaintext.
|
||||
std::byte header[HEADER_LEN] = {ignore ? IGNORE_BIT : std::byte{0}};
|
||||
m_send_p_cipher->Encrypt(header, contents, aad, output.subspan(LENGTH_LEN));
|
||||
}
|
||||
|
||||
uint32_t BIP324Cipher::DecryptLength(Span<const std::byte> input) noexcept
|
||||
{
|
||||
assert(input.size() == LENGTH_LEN);
|
||||
|
||||
std::byte buf[LENGTH_LEN];
|
||||
// Decrypt length
|
||||
m_recv_l_cipher->Crypt(input, buf);
|
||||
// Convert to number.
|
||||
return uint32_t(buf[0]) + (uint32_t(buf[1]) << 8) + (uint32_t(buf[2]) << 16);
|
||||
}
|
||||
|
||||
bool BIP324Cipher::Decrypt(Span<const std::byte> input, Span<const std::byte> aad, bool& ignore, Span<std::byte> contents) noexcept
|
||||
{
|
||||
assert(input.size() + LENGTH_LEN == contents.size() + EXPANSION);
|
||||
|
||||
std::byte header[HEADER_LEN];
|
||||
if (!m_recv_p_cipher->Decrypt(input, aad, header, contents)) return false;
|
||||
|
||||
ignore = (header[0] & IGNORE_BIT) == IGNORE_BIT;
|
||||
return true;
|
||||
}
|
94
src/bip324.h
Normal file
94
src/bip324.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
// 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_BIP324_H
|
||||
#define BITCOIN_BIP324_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
|
||||
#include <crypto/chacha20.h>
|
||||
#include <crypto/chacha20poly1305.h>
|
||||
#include <key.h>
|
||||
#include <pubkey.h>
|
||||
#include <span.h>
|
||||
|
||||
/** The BIP324 packet cipher, encapsulating its key derivation, stream cipher, and AEAD. */
|
||||
class BIP324Cipher
|
||||
{
|
||||
public:
|
||||
static constexpr unsigned SESSION_ID_LEN{32};
|
||||
static constexpr unsigned GARBAGE_TERMINATOR_LEN{16};
|
||||
static constexpr unsigned REKEY_INTERVAL{224};
|
||||
static constexpr unsigned LENGTH_LEN{3};
|
||||
static constexpr unsigned HEADER_LEN{1};
|
||||
static constexpr unsigned EXPANSION = LENGTH_LEN + HEADER_LEN + FSChaCha20Poly1305::EXPANSION;
|
||||
static constexpr std::byte IGNORE_BIT{0x80};
|
||||
|
||||
private:
|
||||
std::optional<FSChaCha20> m_send_l_cipher;
|
||||
std::optional<FSChaCha20> m_recv_l_cipher;
|
||||
std::optional<FSChaCha20Poly1305> m_send_p_cipher;
|
||||
std::optional<FSChaCha20Poly1305> m_recv_p_cipher;
|
||||
|
||||
CKey m_key;
|
||||
EllSwiftPubKey m_our_pubkey;
|
||||
|
||||
std::array<std::byte, SESSION_ID_LEN> m_session_id;
|
||||
std::array<std::byte, GARBAGE_TERMINATOR_LEN> m_send_garbage_terminator;
|
||||
std::array<std::byte, GARBAGE_TERMINATOR_LEN> m_recv_garbage_terminator;
|
||||
|
||||
public:
|
||||
/** Initialize a BIP324 cipher with securely generated random keys. */
|
||||
BIP324Cipher() noexcept;
|
||||
|
||||
/** Initialize a BIP324 cipher with specified key and encoding entropy (testing only). */
|
||||
BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcept;
|
||||
|
||||
/** Initialize a BIP324 cipher with specified key (testing only). */
|
||||
BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept;
|
||||
|
||||
/** Retrieve our public key. */
|
||||
const EllSwiftPubKey& GetOurPubKey() const noexcept { return m_our_pubkey; }
|
||||
|
||||
/** Initialize when the other side's public key is received. Can only be called once.
|
||||
*
|
||||
* self_decrypt is only for testing, and swaps encryption/decryption keys, so that encryption
|
||||
* and decryption can be tested without knowing the other side's private key.
|
||||
*/
|
||||
void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt = false) noexcept;
|
||||
|
||||
/** Determine whether this cipher is fully initialized. */
|
||||
explicit operator bool() const noexcept { return m_send_l_cipher.has_value(); }
|
||||
|
||||
/** Encrypt a packet. Only after Initialize().
|
||||
*
|
||||
* It must hold that output.size() == contents.size() + EXPANSION.
|
||||
*/
|
||||
void Encrypt(Span<const std::byte> contents, Span<const std::byte> aad, bool ignore, Span<std::byte> output) noexcept;
|
||||
|
||||
/** Decrypt the length of a packet. Only after Initialize().
|
||||
*
|
||||
* It must hold that input.size() == LENGTH_LEN.
|
||||
*/
|
||||
unsigned DecryptLength(Span<const std::byte> input) noexcept;
|
||||
|
||||
/** Decrypt a packet. Only after Initialize().
|
||||
*
|
||||
* It must hold that input.size() + LENGTH_LEN == contents.size() + EXPANSION.
|
||||
* Contents.size() must equal the length returned by DecryptLength.
|
||||
*/
|
||||
bool Decrypt(Span<const std::byte> input, Span<const std::byte> aad, bool& ignore, Span<std::byte> contents) noexcept;
|
||||
|
||||
/** Get the Session ID. Only after Initialize(). */
|
||||
Span<const std::byte> GetSessionID() const noexcept { return m_session_id; }
|
||||
|
||||
/** Get the Garbage Terminator to send. Only after Initialize(). */
|
||||
Span<const std::byte> GetSendGarbageTerminator() const noexcept { return m_send_garbage_terminator; }
|
||||
|
||||
/** Get the expected Garbage Terminator to receive. Only after Initialize(). */
|
||||
Span<const std::byte> GetReceiveGarbageTerminator() const noexcept { return m_recv_garbage_terminator; }
|
||||
};
|
||||
|
||||
#endif // BITCOIN_BIP324_H
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <crypto/common.h>
|
||||
#include <crypto/chacha20.h>
|
||||
#include <support/cleanse.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
@ -42,6 +43,11 @@ ChaCha20Aligned::ChaCha20Aligned()
|
|||
memset(input, 0, sizeof(input));
|
||||
}
|
||||
|
||||
ChaCha20Aligned::~ChaCha20Aligned()
|
||||
{
|
||||
memory_cleanse(input, sizeof(input));
|
||||
}
|
||||
|
||||
ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32)
|
||||
{
|
||||
SetKey32(key32);
|
||||
|
@ -318,3 +324,38 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
|
|||
m_bufleft = 64 - bytes;
|
||||
}
|
||||
}
|
||||
|
||||
ChaCha20::~ChaCha20()
|
||||
{
|
||||
memory_cleanse(m_buffer, sizeof(m_buffer));
|
||||
}
|
||||
|
||||
FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
|
||||
m_chacha20(UCharCast(key.data())), m_rekey_interval(rekey_interval)
|
||||
{
|
||||
assert(key.size() == KEYLEN);
|
||||
}
|
||||
|
||||
void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
|
||||
{
|
||||
assert(input.size() == output.size());
|
||||
|
||||
// Invoke internal stream cipher for actual encryption/decryption.
|
||||
m_chacha20.Crypt(UCharCast(input.data()), UCharCast(output.data()), input.size());
|
||||
|
||||
// Rekey after m_rekey_interval encryptions/decryptions.
|
||||
if (++m_chunk_counter == m_rekey_interval) {
|
||||
// Get new key from the stream cipher.
|
||||
std::byte new_key[KEYLEN];
|
||||
m_chacha20.Keystream(UCharCast(new_key), sizeof(new_key));
|
||||
// Update its key.
|
||||
m_chacha20.SetKey32(UCharCast(new_key));
|
||||
// Wipe the key (a copy remains inside m_chacha20, where it'll be wiped on the next rekey
|
||||
// or on destruction).
|
||||
memory_cleanse(new_key, sizeof(new_key));
|
||||
// Set the nonce for the new section of output.
|
||||
m_chacha20.Seek64({0, ++m_rekey_counter}, 0);
|
||||
// Reset the chunk counter.
|
||||
m_chunk_counter = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
#ifndef BITCOIN_CRYPTO_CHACHA20_H
|
||||
#define BITCOIN_CRYPTO_CHACHA20_H
|
||||
|
||||
#include <span.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
#include <utility>
|
||||
|
@ -29,6 +33,9 @@ public:
|
|||
/** Initialize a cipher with specified 32-byte key. */
|
||||
ChaCha20Aligned(const unsigned char* key32);
|
||||
|
||||
/** Destructor to clean up private memory. */
|
||||
~ChaCha20Aligned();
|
||||
|
||||
/** set 32-byte key. */
|
||||
void SetKey32(const unsigned char* key32);
|
||||
|
||||
|
@ -72,6 +79,9 @@ public:
|
|||
/** Initialize a cipher with specified 32-byte key. */
|
||||
ChaCha20(const unsigned char* key32) : m_aligned(key32) {}
|
||||
|
||||
/** Destructor to clean up private memory. */
|
||||
~ChaCha20();
|
||||
|
||||
/** set 32-byte key. */
|
||||
void SetKey32(const unsigned char* key32)
|
||||
{
|
||||
|
@ -98,4 +108,43 @@ public:
|
|||
void Crypt(const unsigned char* input, unsigned char* output, size_t bytes);
|
||||
};
|
||||
|
||||
/** Forward-secure ChaCha20
|
||||
*
|
||||
* This implements a stream cipher that automatically transitions to a new stream with a new key
|
||||
* and new nonce after a predefined number of encryptions or decryptions.
|
||||
*
|
||||
* See BIP324 for details.
|
||||
*/
|
||||
class FSChaCha20
|
||||
{
|
||||
private:
|
||||
/** Internal stream cipher. */
|
||||
ChaCha20 m_chacha20;
|
||||
|
||||
/** The number of encryptions/decryptions before a rekey happens. */
|
||||
const uint32_t m_rekey_interval;
|
||||
|
||||
/** The number of encryptions/decryptions since the last rekey. */
|
||||
uint32_t m_chunk_counter{0};
|
||||
|
||||
/** The number of rekey operations that have happened. */
|
||||
uint64_t m_rekey_counter{0};
|
||||
|
||||
public:
|
||||
/** Length of keys expected by the constructor. */
|
||||
static constexpr unsigned KEYLEN = 32;
|
||||
|
||||
// No copy or move to protect the secret.
|
||||
FSChaCha20(const FSChaCha20&) = delete;
|
||||
FSChaCha20(FSChaCha20&&) = delete;
|
||||
FSChaCha20& operator=(const FSChaCha20&) = delete;
|
||||
FSChaCha20& operator=(FSChaCha20&&) = delete;
|
||||
|
||||
/** Construct an FSChaCha20 cipher that rekeys every rekey_interval Crypt() calls. */
|
||||
FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept;
|
||||
|
||||
/** Encrypt or decrypt a chunk. */
|
||||
void Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_CRYPTO_CHACHA20_H
|
||||
|
|
142
src/crypto/chacha20poly1305.cpp
Normal file
142
src/crypto/chacha20poly1305.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
// 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 <support/cleanse.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> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> cipher) noexcept
|
||||
{
|
||||
assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
|
||||
|
||||
// Encrypt using ChaCha20 (starting at block 1).
|
||||
m_chacha20.Seek64(nonce, 1);
|
||||
m_chacha20.Crypt(UCharCast(plain1.data()), UCharCast(cipher.data()), plain1.size());
|
||||
m_chacha20.Crypt(UCharCast(plain2.data()), UCharCast(cipher.data() + plain1.size()), plain2.size());
|
||||
|
||||
// Seek to block 0, and compute tag using key drawn from there.
|
||||
m_chacha20.Seek64(nonce, 0);
|
||||
ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION));
|
||||
}
|
||||
|
||||
bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain1, Span<std::byte> plain2) noexcept
|
||||
{
|
||||
assert(cipher.size() == plain1.size() + plain2.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(cipher.size() - EXPANSION), expected_tag);
|
||||
if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.data() + cipher.size() - EXPANSION), EXPANSION)) return false;
|
||||
|
||||
// Decrypt (starting at block 1).
|
||||
m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain1.data()), plain1.size());
|
||||
m_chacha20.Crypt(UCharCast(cipher.data() + plain1.size()), UCharCast(plain2.data()), plain2.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept
|
||||
{
|
||||
// Skip the first output block, as it's used for generating the poly1305 key.
|
||||
m_chacha20.Seek64(nonce, 1);
|
||||
m_chacha20.Keystream(UCharCast(keystream.data()), keystream.size());
|
||||
}
|
||||
|
||||
void FSChaCha20Poly1305::NextPacket() noexcept
|
||||
{
|
||||
if (++m_packet_counter == m_rekey_interval) {
|
||||
// Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though
|
||||
// we only need KEYLEN (32) bytes.
|
||||
std::byte one_block[64];
|
||||
m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block);
|
||||
// Switch keys.
|
||||
m_aead.SetKey(Span{one_block}.first(KEYLEN));
|
||||
// Wipe the generated keystream (a copy remains inside m_aead, which will be cleaned up
|
||||
// once it cycles again, or is destroyed).
|
||||
memory_cleanse(one_block, sizeof(one_block));
|
||||
// Update counters.
|
||||
m_packet_counter = 0;
|
||||
++m_rekey_counter;
|
||||
}
|
||||
}
|
||||
|
||||
void FSChaCha20Poly1305::Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Span<std::byte> cipher) noexcept
|
||||
{
|
||||
m_aead.Encrypt(plain1, plain2, aad, {m_packet_counter, m_rekey_counter}, cipher);
|
||||
NextPacket();
|
||||
}
|
||||
|
||||
bool FSChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain1, Span<std::byte> plain2) noexcept
|
||||
{
|
||||
bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain1, plain2);
|
||||
NextPacket();
|
||||
return ret;
|
||||
}
|
149
src/crypto/chacha20poly1305.h
Normal file
149
src/crypto/chacha20poly1305.h
Normal file
|
@ -0,0 +1,149 @@
|
|||
// 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
|
||||
{
|
||||
Encrypt(plain, {}, aad, nonce, cipher);
|
||||
}
|
||||
|
||||
/** Encrypt a message (given split into plain1 + plain2) with a specified 96-bit nonce and aad.
|
||||
*
|
||||
* Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION.
|
||||
*/
|
||||
void Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, 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
|
||||
{
|
||||
return Decrypt(cipher, aad, nonce, plain, {});
|
||||
}
|
||||
|
||||
/** Decrypt a message with a specified 96-bit nonce and aad and split the result. Returns true if valid.
|
||||
*
|
||||
* Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION.
|
||||
*/
|
||||
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain1, Span<std::byte> plain2) noexcept;
|
||||
|
||||
/** Get a number of keystream bytes from the underlying stream cipher.
|
||||
*
|
||||
* This is equivalent to Encrypt() with plain set to that many zero bytes, and dropping the
|
||||
* last EXPANSION bytes off the result.
|
||||
*/
|
||||
void Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept;
|
||||
};
|
||||
|
||||
/** Forward-secure wrapper around AEADChaCha20Poly1305.
|
||||
*
|
||||
* This implements an AEAD which automatically increments the nonce on every encryption or
|
||||
* decryption, and cycles keys after a predetermined number of encryptions or decryptions.
|
||||
*
|
||||
* See BIP324 for details.
|
||||
*/
|
||||
class FSChaCha20Poly1305
|
||||
{
|
||||
private:
|
||||
/** Internal AEAD. */
|
||||
AEADChaCha20Poly1305 m_aead;
|
||||
|
||||
/** Every how many iterations this cipher rekeys. */
|
||||
const uint32_t m_rekey_interval;
|
||||
|
||||
/** The number of encryptions/decryptions since the last rekey. */
|
||||
uint32_t m_packet_counter{0};
|
||||
|
||||
/** The number of rekeys performed so far. */
|
||||
uint64_t m_rekey_counter{0};
|
||||
|
||||
/** Update counters (and if necessary, key) to transition to the next message. */
|
||||
void NextPacket() noexcept;
|
||||
|
||||
public:
|
||||
/** Length of keys expected by the constructor. */
|
||||
static constexpr auto KEYLEN = AEADChaCha20Poly1305::KEYLEN;
|
||||
|
||||
/** Expansion when encrypting. */
|
||||
static constexpr auto EXPANSION = AEADChaCha20Poly1305::EXPANSION;
|
||||
|
||||
// No copy or move to protect the secret.
|
||||
FSChaCha20Poly1305(const FSChaCha20Poly1305&) = delete;
|
||||
FSChaCha20Poly1305(FSChaCha20Poly1305&&) = delete;
|
||||
FSChaCha20Poly1305& operator=(const FSChaCha20Poly1305&) = delete;
|
||||
FSChaCha20Poly1305& operator=(FSChaCha20Poly1305&&) = delete;
|
||||
|
||||
/** Construct an FSChaCha20Poly1305 cipher that rekeys every rekey_interval operations. */
|
||||
FSChaCha20Poly1305(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
|
||||
m_aead(key), m_rekey_interval(rekey_interval) {}
|
||||
|
||||
/** Encrypt a message with a specified aad.
|
||||
*
|
||||
* Requires cipher.size() = plain.size() + EXPANSION.
|
||||
*/
|
||||
void Encrypt(Span<const std::byte> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept
|
||||
{
|
||||
Encrypt(plain, {}, aad, cipher);
|
||||
}
|
||||
|
||||
/** Encrypt a message (given split into plain1 + plain2) with a specified aad.
|
||||
*
|
||||
* Requires cipher.size() = plain.size() + EXPANSION.
|
||||
*/
|
||||
void Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Span<std::byte> cipher) noexcept;
|
||||
|
||||
/** Decrypt a message with a specified aad. Returns true if valid.
|
||||
*
|
||||
* Requires cipher.size() = plain.size() + EXPANSION.
|
||||
*/
|
||||
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain) noexcept
|
||||
{
|
||||
return Decrypt(cipher, aad, plain, {});
|
||||
}
|
||||
|
||||
/** Decrypt a message with a specified aad and split the result. Returns true if valid.
|
||||
*
|
||||
* Requires cipher.size() = plain1.size() + plain2.size() + EXPANSION.
|
||||
*/
|
||||
bool Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain1, Span<std::byte> plain2) noexcept;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_CRYPTO_CHACHA20POLY1305_H
|
|
@ -1,132 +0,0 @@
|
|||
// Copyright (c) 2019-2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#if defined(HAVE_CONFIG_H)
|
||||
#include <config/bitcoin-config.h>
|
||||
#endif
|
||||
|
||||
#include <crypto/chacha_poly_aead.h>
|
||||
|
||||
#include <crypto/poly1305.h>
|
||||
#include <support/cleanse.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
#ifndef HAVE_TIMINGSAFE_BCMP
|
||||
|
||||
int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n)
|
||||
{
|
||||
const unsigned char *p1 = b1, *p2 = b2;
|
||||
int ret = 0;
|
||||
|
||||
for (; n > 0; n--)
|
||||
ret |= *p1++ ^ *p2++;
|
||||
return (ret != 0);
|
||||
}
|
||||
|
||||
#endif // TIMINGSAFE_BCMP
|
||||
|
||||
ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len)
|
||||
{
|
||||
assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
|
||||
assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
|
||||
|
||||
static_assert(CHACHA20_POLY1305_AEAD_KEY_LEN == 32);
|
||||
m_chacha_header.SetKey32(K_1);
|
||||
m_chacha_main.SetKey32(K_2);
|
||||
|
||||
// set the cached sequence number to uint64 max which hints for an unset cache.
|
||||
// we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB
|
||||
m_cached_aad_seqnr = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len /* length of the output buffer for sanity checks */, const unsigned char* src, size_t src_len, bool is_encrypt)
|
||||
{
|
||||
// check buffer boundaries
|
||||
if (
|
||||
// if we encrypt, make sure the source contains at least the expected AAD and the destination has at least space for the source + MAC
|
||||
(is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN || dest_len < src_len + Poly1305::TAGLEN)) ||
|
||||
// if we decrypt, make sure the source contains at least the expected AAD+MAC and the destination has at least space for the source - MAC
|
||||
(!is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN + Poly1305::TAGLEN || dest_len < src_len - Poly1305::TAGLEN))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char expected_tag[Poly1305::TAGLEN], poly_key[Poly1305::KEYLEN];
|
||||
memset(poly_key, 0, sizeof(poly_key));
|
||||
|
||||
// block counter 0 for the poly1305 key
|
||||
// use lower 32bytes for the poly1305 key
|
||||
// (throws away 32 unused bytes (upper 32) from this ChaCha20 round)
|
||||
m_chacha_main.Seek64({0, seqnr_payload}, 0);
|
||||
m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key));
|
||||
|
||||
// if decrypting, verify the tag prior to decryption
|
||||
if (!is_encrypt) {
|
||||
const unsigned char* tag = src + src_len - Poly1305::TAGLEN;
|
||||
Poly1305{MakeByteSpan(poly_key)}
|
||||
.Update(AsBytes(Span{src, src_len - Poly1305::TAGLEN}))
|
||||
.Finalize(MakeWritableByteSpan(expected_tag));
|
||||
|
||||
// constant time compare the calculated MAC with the provided MAC
|
||||
if (timingsafe_bcmp(expected_tag, tag, Poly1305::TAGLEN) != 0) {
|
||||
memory_cleanse(expected_tag, sizeof(expected_tag));
|
||||
memory_cleanse(poly_key, sizeof(poly_key));
|
||||
return false;
|
||||
}
|
||||
memory_cleanse(expected_tag, sizeof(expected_tag));
|
||||
// MAC has been successfully verified, make sure we don't convert it in decryption
|
||||
src_len -= Poly1305::TAGLEN;
|
||||
}
|
||||
|
||||
// calculate and cache the next 64byte keystream block if requested sequence number is not yet the cache
|
||||
if (m_cached_aad_seqnr != seqnr_aad) {
|
||||
m_cached_aad_seqnr = seqnr_aad;
|
||||
m_chacha_header.Seek64({0, seqnr_aad}, 0);
|
||||
m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT);
|
||||
}
|
||||
// crypt the AAD (3 bytes message length) with given position in AAD cipher instance keystream
|
||||
dest[0] = src[0] ^ m_aad_keystream_buffer[aad_pos];
|
||||
dest[1] = src[1] ^ m_aad_keystream_buffer[aad_pos + 1];
|
||||
dest[2] = src[2] ^ m_aad_keystream_buffer[aad_pos + 2];
|
||||
|
||||
// Set the playload ChaCha instance block counter to 1 and crypt the payload
|
||||
m_chacha_main.Seek64({0, seqnr_payload}, 1);
|
||||
m_chacha_main.Crypt(src + CHACHA20_POLY1305_AEAD_AAD_LEN, dest + CHACHA20_POLY1305_AEAD_AAD_LEN, src_len - CHACHA20_POLY1305_AEAD_AAD_LEN);
|
||||
|
||||
// If encrypting, calculate and append tag
|
||||
if (is_encrypt) {
|
||||
// the poly1305 tag expands over the AAD (3 bytes length) & encrypted payload
|
||||
Poly1305{MakeByteSpan(poly_key)}
|
||||
.Update(AsBytes(Span{dest, src_len}))
|
||||
.Finalize(AsWritableBytes(Span{dest + src_len, Poly1305::TAGLEN}));
|
||||
}
|
||||
|
||||
// cleanse no longer required MAC and polykey
|
||||
memory_cleanse(poly_key, sizeof(poly_key));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext)
|
||||
{
|
||||
// enforce valid aad position to avoid accessing outside of the 64byte keystream cache
|
||||
// (there is space for 21 times 3 bytes)
|
||||
assert(aad_pos >= 0 && aad_pos < CHACHA20_ROUND_OUTPUT - CHACHA20_POLY1305_AEAD_AAD_LEN);
|
||||
if (m_cached_aad_seqnr != seqnr_aad) {
|
||||
// we need to calculate the 64 keystream bytes since we reached a new aad sequence number
|
||||
m_cached_aad_seqnr = seqnr_aad;
|
||||
m_chacha_header.Seek64({0, seqnr_aad}, 0); // use LE for the nonce
|
||||
m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache
|
||||
}
|
||||
|
||||
// decrypt the ciphertext length by XORing the right position of the 64byte keystream cache with the ciphertext
|
||||
*len24_out = (ciphertext[0] ^ m_aad_keystream_buffer[aad_pos + 0]) |
|
||||
(ciphertext[1] ^ m_aad_keystream_buffer[aad_pos + 1]) << 8 |
|
||||
(ciphertext[2] ^ m_aad_keystream_buffer[aad_pos + 2]) << 16;
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
// Copyright (c) 2019-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.
|
||||
|
||||
#ifndef BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H
|
||||
#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H
|
||||
|
||||
#include <crypto/chacha20.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
static constexpr int CHACHA20_POLY1305_AEAD_KEY_LEN = 32;
|
||||
static constexpr int CHACHA20_POLY1305_AEAD_AAD_LEN = 3; /* 3 bytes length */
|
||||
static constexpr int CHACHA20_ROUND_OUTPUT = 64; /* 64 bytes per round */
|
||||
static constexpr int AAD_PACKAGES_PER_ROUND = 21; /* 64 / 3 round down*/
|
||||
|
||||
/* A AEAD class for ChaCha20-Poly1305@bitcoin.
|
||||
*
|
||||
* ChaCha20 is a stream cipher designed by Daniel Bernstein and described in
|
||||
* <ref>[https://cr.yp.to/chacha/chacha-20080128.pdf ChaCha20]</ref>. It operates
|
||||
* by permuting 128 fixed bits, 128 or 256 bits of key, a 64 bit nonce and a 64
|
||||
* bit counter into 64 bytes of output. This output is used as a keystream, with
|
||||
* any unused bytes simply discarded.
|
||||
*
|
||||
* Poly1305 <ref>[https://cr.yp.to/mac/poly1305-20050329.pdf Poly1305]</ref>, also
|
||||
* by Daniel Bernstein, is a one-time Carter-Wegman MAC that computes a 128 bit
|
||||
* integrity tag given a message and a single-use 256 bit secret key.
|
||||
*
|
||||
* The chacha20-poly1305@bitcoin combines these two primitives into an
|
||||
* authenticated encryption mode. The construction used is based on that proposed
|
||||
* for TLS by Adam Langley in
|
||||
* <ref>[http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-03 "ChaCha20
|
||||
* and Poly1305 based Cipher Suites for TLS", Adam Langley]</ref>, but differs in
|
||||
* the layout of data passed to the MAC and in the addition of encryption of the
|
||||
* packet lengths.
|
||||
*
|
||||
* ==== Detailed Construction ====
|
||||
*
|
||||
* The chacha20-poly1305@bitcoin cipher requires two 256 bits of key material as
|
||||
* output from the key exchange. Each key (K_1 and K_2) are used by two separate
|
||||
* instances of chacha20.
|
||||
*
|
||||
* The instance keyed by K_1 is a stream cipher that is used only to encrypt the 3
|
||||
* byte packet length field and has its own sequence number. The second instance,
|
||||
* keyed by K_2, is used in conjunction with poly1305 to build an AEAD
|
||||
* (Authenticated Encryption with Associated Data) that is used to encrypt and
|
||||
* authenticate the entire packet.
|
||||
*
|
||||
* Two separate cipher instances are used here so as to keep the packet lengths
|
||||
* confidential but not create an oracle for the packet payload cipher by
|
||||
* decrypting and using the packet length prior to checking the MAC. By using an
|
||||
* independently-keyed cipher instance to encrypt the length, an active attacker
|
||||
* seeking to exploit the packet input handling as a decryption oracle can learn
|
||||
* nothing about the payload contents or its MAC (assuming key derivation,
|
||||
* ChaCha20 and Poly1305 are secure).
|
||||
*
|
||||
* The AEAD is constructed as follows: for each packet, generate a Poly1305 key by
|
||||
* taking the first 256 bits of ChaCha20 stream output generated using K_2, an IV
|
||||
* consisting of the packet sequence number encoded as an LE uint64 and a ChaCha20
|
||||
* block counter of zero. The K_2 ChaCha20 block counter is then set to the
|
||||
* little-endian encoding of 1 (i.e. {1, 0, 0, 0, 0, 0, 0, 0}) and this instance
|
||||
* is used for encryption of the packet payload.
|
||||
*
|
||||
* ==== Packet Handling ====
|
||||
*
|
||||
* When receiving a packet, the length must be decrypted first. When 3 bytes of
|
||||
* ciphertext length have been received, they may be decrypted.
|
||||
*
|
||||
* A ChaCha20 round always calculates 64bytes which is sufficient to crypt 21
|
||||
* times a 3 bytes length field (21*3 = 63). The length field sequence number can
|
||||
* thus be used 21 times (keystream caching).
|
||||
*
|
||||
* The length field must be enc-/decrypted with the ChaCha20 keystream keyed with
|
||||
* K_1 defined by block counter 0, the length field sequence number in little
|
||||
* endian and a keystream position from 0 to 60.
|
||||
*
|
||||
* Once the entire packet has been received, the MAC MUST be checked before
|
||||
* decryption. A per-packet Poly1305 key is generated as described above and the
|
||||
* MAC tag calculated using Poly1305 with this key over the ciphertext of the
|
||||
* packet length and the payload together. The calculated MAC is then compared in
|
||||
* constant time with the one appended to the packet and the packet decrypted
|
||||
* using ChaCha20 as described above (with K_2, the packet sequence number as
|
||||
* nonce and a starting block counter of 1).
|
||||
*
|
||||
* Detection of an invalid MAC MUST lead to immediate connection termination.
|
||||
*
|
||||
* To send a packet, first encode the 3 byte length and encrypt it using K_1 as
|
||||
* described above. Encrypt the packet payload (using K_2) and append it to the
|
||||
* encrypted length. Finally, calculate a MAC tag and append it.
|
||||
*
|
||||
* The initiating peer MUST use <code>K_1_A, K_2_A</code> to encrypt messages on
|
||||
* the send channel, <code>K_1_B, K_2_B</code> MUST be used to decrypt messages on
|
||||
* the receive channel.
|
||||
*
|
||||
* The responding peer MUST use <code>K_1_A, K_2_A</code> to decrypt messages on
|
||||
* the receive channel, <code>K_1_B, K_2_B</code> MUST be used to encrypt messages
|
||||
* on the send channel.
|
||||
*
|
||||
* Optimized implementations of ChaCha20-Poly1305@bitcoin are relatively fast in
|
||||
* general, therefore it is very likely that encrypted messages require not more
|
||||
* CPU cycles per bytes then the current unencrypted p2p message format
|
||||
* (ChaCha20/Poly1305 versus double SHA256).
|
||||
*
|
||||
* The initial packet sequence numbers are 0.
|
||||
*
|
||||
* K_2 ChaCha20 cipher instance (payload) must never reuse a {key, nonce} for
|
||||
* encryption nor may it be used to encrypt more than 2^70 bytes under the same
|
||||
* {key, nonce}.
|
||||
*
|
||||
* K_1 ChaCha20 cipher instance (length field/AAD) must never reuse a {key, nonce,
|
||||
* position-in-keystream} for encryption nor may it be used to encrypt more than
|
||||
* 2^70 bytes under the same {key, nonce}.
|
||||
*
|
||||
* We use message sequence numbers for both communication directions.
|
||||
*/
|
||||
|
||||
class ChaCha20Poly1305AEAD
|
||||
{
|
||||
private:
|
||||
ChaCha20 m_chacha_header; // AAD cipher instance (encrypted length) and poly1305 key-derivation cipher instance
|
||||
ChaCha20 m_chacha_main; // payload
|
||||
unsigned char m_aad_keystream_buffer[CHACHA20_ROUND_OUTPUT]; // aad keystream cache
|
||||
uint64_t m_cached_aad_seqnr; // aad keystream cache hint
|
||||
|
||||
public:
|
||||
ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len);
|
||||
|
||||
explicit ChaCha20Poly1305AEAD(const ChaCha20Poly1305AEAD&) = delete;
|
||||
|
||||
/** Encrypts/decrypts a packet
|
||||
seqnr_payload, the message sequence number
|
||||
seqnr_aad, the messages AAD sequence number which allows reuse of the AAD keystream
|
||||
aad_pos, position to use in the AAD keystream to encrypt the AAD
|
||||
dest, output buffer, must be of a size equal or larger then CHACHA20_POLY1305_AEAD_AAD_LEN + payload (+ POLY1305_TAG_LEN in encryption) bytes
|
||||
destlen, length of the destination buffer
|
||||
src, the AAD+payload to encrypt or the AAD+payload+MAC to decrypt
|
||||
src_len, the length of the source buffer
|
||||
is_encrypt, set to true if we encrypt (creates and appends the MAC instead of verifying it)
|
||||
*/
|
||||
bool Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len, const unsigned char* src, size_t src_len, bool is_encrypt);
|
||||
|
||||
/** decrypts the 3 bytes AAD data and decodes it into a uint32_t field */
|
||||
bool GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext);
|
||||
};
|
||||
|
||||
#endif // BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H
|
|
@ -299,6 +299,9 @@ private:
|
|||
std::array<std::byte, SIZE> m_pubkey;
|
||||
|
||||
public:
|
||||
/** Default constructor creates all-zero pubkey (which is valid). */
|
||||
EllSwiftPubKey() noexcept = default;
|
||||
|
||||
/** Construct a new ellswift public key from a given serialization. */
|
||||
EllSwiftPubKey(const std::array<std::byte, SIZE>& ellswift) :
|
||||
m_pubkey(ellswift) {}
|
||||
|
|
304
src/test/bip324_tests.cpp
Normal file
304
src/test/bip324_tests.cpp
Normal file
File diff suppressed because one or more lines are too long
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include <crypto/aes.h>
|
||||
#include <crypto/chacha20.h>
|
||||
#include <crypto/chacha_poly_aead.h>
|
||||
#include <crypto/chacha20poly1305.h>
|
||||
#include <crypto/hkdf_sha256_32.h>
|
||||
#include <crypto/hmac_sha256.h>
|
||||
#include <crypto/hmac_sha512.h>
|
||||
|
@ -182,6 +182,46 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk
|
|||
}
|
||||
}
|
||||
|
||||
static void TestFSChaCha20(const std::string& hex_plaintext, const std::string& hexkey, uint32_t rekey_interval, const std::string& ciphertext_after_rotation)
|
||||
{
|
||||
auto key = ParseHex<std::byte>(hexkey);
|
||||
BOOST_CHECK_EQUAL(FSChaCha20::KEYLEN, key.size());
|
||||
|
||||
auto plaintext = ParseHex<std::byte>(hex_plaintext);
|
||||
|
||||
auto fsc20 = FSChaCha20{key, rekey_interval};
|
||||
auto c20 = ChaCha20{UCharCast(key.data())};
|
||||
|
||||
std::vector<std::byte> fsc20_output;
|
||||
fsc20_output.resize(plaintext.size());
|
||||
|
||||
std::vector<std::byte> c20_output;
|
||||
c20_output.resize(plaintext.size());
|
||||
|
||||
for (size_t i = 0; i < rekey_interval; i++) {
|
||||
fsc20.Crypt(plaintext, fsc20_output);
|
||||
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
|
||||
BOOST_CHECK(c20_output == fsc20_output);
|
||||
}
|
||||
|
||||
// At the rotation interval, the outputs will no longer match
|
||||
fsc20.Crypt(plaintext, fsc20_output);
|
||||
auto c20_copy = c20;
|
||||
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
|
||||
BOOST_CHECK(c20_output != fsc20_output);
|
||||
|
||||
std::byte new_key[FSChaCha20::KEYLEN];
|
||||
c20_copy.Keystream(UCharCast(new_key), sizeof(new_key));
|
||||
c20.SetKey32(UCharCast(new_key));
|
||||
c20.Seek64({0, 1}, 0);
|
||||
|
||||
// Outputs should match again after simulating key rotation
|
||||
c20.Crypt(UCharCast(plaintext.data()), UCharCast(c20_output.data()), plaintext.size());
|
||||
BOOST_CHECK(c20_output == fsc20_output);
|
||||
|
||||
BOOST_CHECK_EQUAL(HexStr(fsc20_output), ciphertext_after_rotation);
|
||||
}
|
||||
|
||||
static void TestPoly1305(const std::string &hexmessage, const std::string &hexkey, const std::string& hextag)
|
||||
{
|
||||
auto key = ParseHex<std::byte>(hexkey);
|
||||
|
@ -208,6 +248,94 @@ 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);
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// During i=0, use single-plain Encrypt/Decrypt; others use a split at prefix.
|
||||
size_t prefix = i ? InsecureRandRange(plain.size() + 1) : plain.size();
|
||||
// Encrypt.
|
||||
std::vector<std::byte> cipher(plain.size() + AEADChaCha20Poly1305::EXPANSION);
|
||||
AEADChaCha20Poly1305 aead{key};
|
||||
if (i == 0) {
|
||||
aead.Encrypt(plain, aad, nonce, cipher);
|
||||
} else {
|
||||
aead.Encrypt(Span{plain}.first(prefix), Span{plain}.subspan(prefix), aad, nonce, cipher);
|
||||
}
|
||||
BOOST_CHECK(cipher == expected_cipher);
|
||||
|
||||
// Decrypt.
|
||||
std::vector<std::byte> decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION);
|
||||
bool ret{false};
|
||||
if (i == 0) {
|
||||
ret = aead.Decrypt(cipher, aad, nonce, decipher);
|
||||
} else {
|
||||
ret = aead.Decrypt(cipher, aad, nonce, Span{decipher}.first(prefix), Span{decipher}.subspan(prefix));
|
||||
}
|
||||
BOOST_CHECK(ret);
|
||||
BOOST_CHECK(decipher == plain);
|
||||
}
|
||||
|
||||
// Test Keystream output.
|
||||
std::vector<std::byte> keystream(plain.size());
|
||||
AEADChaCha20Poly1305 aead{key};
|
||||
aead.Keystream(nonce, keystream);
|
||||
for (size_t i = 0; i < plain.size(); ++i) {
|
||||
BOOST_CHECK_EQUAL(plain[i] ^ keystream[i], expected_cipher[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void TestFSChaCha20Poly1305(const std::string& plain_hex, const std::string& aad_hex, const std::string& key_hex, uint64_t msg_idx, 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() + FSChaCha20Poly1305::EXPANSION);
|
||||
|
||||
for (int it = 0; it < 10; ++it) {
|
||||
// During it==0 we use the single-plain Encrypt/Decrypt; others use a split at prefix.
|
||||
size_t prefix = it ? InsecureRandRange(plain.size() + 1) : plain.size();
|
||||
|
||||
// Do msg_idx dummy encryptions to seek to the correct packet.
|
||||
FSChaCha20Poly1305 enc_aead{key, 224};
|
||||
for (uint64_t i = 0; i < msg_idx; ++i) {
|
||||
std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}};
|
||||
enc_aead.Encrypt(Span{dummy_tag}.first(0), Span{dummy_tag}.first(0), dummy_tag);
|
||||
}
|
||||
|
||||
// Invoke single-plain or plain1/plain2 Encrypt.
|
||||
if (it == 0) {
|
||||
enc_aead.Encrypt(plain, aad, cipher);
|
||||
} else {
|
||||
enc_aead.Encrypt(Span{plain}.first(prefix), Span{plain}.subspan(prefix), aad, cipher);
|
||||
}
|
||||
BOOST_CHECK(cipher == expected_cipher);
|
||||
|
||||
// Do msg_idx dummy decryptions to seek to the correct packet.
|
||||
FSChaCha20Poly1305 dec_aead{key, 224};
|
||||
for (uint64_t i = 0; i < msg_idx; ++i) {
|
||||
std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}};
|
||||
dec_aead.Decrypt(dummy_tag, Span{dummy_tag}.first(0), Span{dummy_tag}.first(0));
|
||||
}
|
||||
|
||||
// Invoke single-plain or plain1/plain2 Decrypt.
|
||||
std::vector<std::byte> decipher(cipher.size() - AEADChaCha20Poly1305::EXPANSION);
|
||||
bool ret{false};
|
||||
if (it == 0) {
|
||||
ret = dec_aead.Decrypt(cipher, aad, decipher);
|
||||
} else {
|
||||
ret = dec_aead.Decrypt(cipher, aad, Span{decipher}.first(prefix), Span{decipher}.subspan(prefix));
|
||||
}
|
||||
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);
|
||||
|
@ -678,6 +806,20 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector)
|
|||
"fd565dea5addbdb914208fde7950f23e0385f9a727143f6a6ac51d84b1c0fb3e"
|
||||
"2e3b00b63d6841a1cc6d1538b1d3a74bef1eb2f54c7b7281e36e484dba89b351"
|
||||
"c8f572617e61e342879f211b0e4c515df50ea9d0771518fad96cd0baee62deb6");
|
||||
|
||||
// Forward secure ChaCha20
|
||||
TestFSChaCha20("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
256,
|
||||
"a93df4ef03011f3db95f60d996e1785df5de38fc39bfcb663a47bb5561928349");
|
||||
TestFSChaCha20("01",
|
||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||
5,
|
||||
"ea");
|
||||
TestFSChaCha20("e93fdb5c762804b9a706816aca31e35b11d2aa3080108ef46a5b1f1508819c0a",
|
||||
"8ec4c3ccdaea336bdeb245636970be01266509b33f3d2642504eaf412206207a",
|
||||
4096,
|
||||
"8bfaa4eacff308fdb4a94a5ff25bd9d0c1f84b77f81239f67ff39d6e1ac280c9");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(chacha20_midblock)
|
||||
|
@ -686,7 +828,7 @@ BOOST_AUTO_TEST_CASE(chacha20_midblock)
|
|||
ChaCha20 c20{key.data()};
|
||||
// get one block of keystream
|
||||
unsigned char block[64];
|
||||
c20.Keystream(block, CHACHA20_ROUND_OUTPUT);
|
||||
c20.Keystream(block, sizeof(block));
|
||||
unsigned char b1[5], b2[7], b3[52];
|
||||
c20 = ChaCha20{key.data()};
|
||||
c20.Keystream(b1, 5);
|
||||
|
@ -819,6 +961,87 @@ 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");
|
||||
|
||||
// FSChaCha20Poly1305 tests.
|
||||
TestFSChaCha20Poly1305("d6a4cb04ef0f7c09c1866ed29dc24d820e75b0491032a51b4c3366f9ca35c19e"
|
||||
"a3047ec6be9d45f9637b63e1cf9eb4c2523a5aab7b851ebeba87199db0e839cf"
|
||||
"0d5c25e50168306377aedbe9089fd2463ded88b83211cf51b73b150608cc7a60"
|
||||
"0d0f11b9a742948482e1b109d8faf15b450aa7322e892fa2208c6691e3fecf4c"
|
||||
"711191b14d75a72147",
|
||||
"786cb9b6ebf44288974cf0",
|
||||
"5c9e1c3951a74fba66708bf9d2c217571684556b6a6a3573bff2847d38612654",
|
||||
500,
|
||||
"9dcebbd3281ea3dd8e9a1ef7d55a97abd6743e56ebc0c190cb2c4e14160b385e"
|
||||
"0bf508dddf754bd02c7c208447c131ce23e47a4a14dfaf5dd8bc601323950f75"
|
||||
"4e05d46e9232f83fc5120fbbef6f5347a826ec79a93820718d4ec7a2b7cfaaa4"
|
||||
"4b21e16d726448b62f803811aff4f6d827ed78e738ce8a507b81a8ae13131192"
|
||||
"8039213de18a5120dc9b7370baca878f50ff254418de3da50c");
|
||||
TestFSChaCha20Poly1305("8349b7a2690b63d01204800c288ff1138a1d473c832c90ea8b3fc102d0bb3adc"
|
||||
"44261b247c7c3d6760bfbe979d061c305f46d94c0582ac3099f0bf249f8cb234",
|
||||
"",
|
||||
"3bd2093fcbcb0d034d8c569583c5425c1a53171ea299f8cc3bbf9ae3530adfce",
|
||||
60000,
|
||||
"30a6757ff8439b975363f166a0fa0e36722ab35936abd704297948f45083f4d4"
|
||||
"99433137ce931f7fca28a0acd3bc30f57b550acbc21cbd45bbef0739d9caf30c"
|
||||
"14b94829deb27f0b1923a2af704ae5d6");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests)
|
||||
{
|
||||
// Use rfc5869 test vectors but truncated to 32 bytes (our implementation only support length 32)
|
||||
|
@ -839,129 +1062,6 @@ BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests)
|
|||
"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d");
|
||||
}
|
||||
|
||||
static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aad_length, const std::string& hex_m, const std::string& hex_k1, const std::string& hex_k2, const std::string& hex_aad_keystream, const std::string& hex_encrypted_message, const std::string& hex_encrypted_message_seq_999)
|
||||
{
|
||||
// we need two sequence numbers, one for the payload cipher instance...
|
||||
uint32_t seqnr_payload = 0;
|
||||
// ... and one for the AAD (length) cipher instance
|
||||
uint32_t seqnr_aad = 0;
|
||||
// we need to keep track of the position in the AAD cipher instance
|
||||
// keystream since we use the same 64byte output 21 times
|
||||
// (21 times 3 bytes length < 64)
|
||||
int aad_pos = 0;
|
||||
|
||||
std::vector<unsigned char> aead_K_1 = ParseHex(hex_k1);
|
||||
std::vector<unsigned char> aead_K_2 = ParseHex(hex_k2);
|
||||
std::vector<unsigned char> plaintext_buf = ParseHex(hex_m);
|
||||
std::vector<unsigned char> expected_aad_keystream = ParseHex(hex_aad_keystream);
|
||||
std::vector<unsigned char> expected_ciphertext_and_mac = ParseHex(hex_encrypted_message);
|
||||
std::vector<unsigned char> expected_ciphertext_and_mac_sequence999 = ParseHex(hex_encrypted_message_seq_999);
|
||||
|
||||
std::vector<unsigned char> ciphertext_buf(plaintext_buf.size() + Poly1305::TAGLEN, 0);
|
||||
std::vector<unsigned char> plaintext_buf_new(plaintext_buf.size(), 0);
|
||||
std::vector<unsigned char> cmp_ctx_buffer(64);
|
||||
uint32_t out_len = 0;
|
||||
|
||||
// create the AEAD instance
|
||||
ChaCha20Poly1305AEAD aead(aead_K_1.data(), aead_K_1.size(), aead_K_2.data(), aead_K_2.size());
|
||||
|
||||
// create a chacha20 instance to compare against
|
||||
ChaCha20 cmp_ctx(aead_K_1.data());
|
||||
|
||||
// encipher
|
||||
bool res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true);
|
||||
// make sure the operation succeeded if expected to succeed
|
||||
BOOST_CHECK_EQUAL(res, must_succeed);
|
||||
if (!res) return;
|
||||
|
||||
// verify ciphertext & mac against the test vector
|
||||
BOOST_CHECK_EQUAL(expected_ciphertext_and_mac.size(), ciphertext_buf.size());
|
||||
BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac.data(), ciphertext_buf.size()) == 0);
|
||||
|
||||
// manually construct the AAD keystream
|
||||
cmp_ctx.Seek64({0, seqnr_aad}, 0);
|
||||
cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64);
|
||||
BOOST_CHECK(memcmp(expected_aad_keystream.data(), cmp_ctx_buffer.data(), expected_aad_keystream.size()) == 0);
|
||||
// crypt the 3 length bytes and compare the length
|
||||
uint32_t len_cmp = 0;
|
||||
len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) |
|
||||
(ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 |
|
||||
(ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16;
|
||||
BOOST_CHECK_EQUAL(len_cmp, expected_aad_length);
|
||||
|
||||
// encrypt / decrypt 1000 packets
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true);
|
||||
BOOST_CHECK(res);
|
||||
BOOST_CHECK(aead.GetLength(&out_len, seqnr_aad, aad_pos, ciphertext_buf.data()));
|
||||
BOOST_CHECK_EQUAL(out_len, expected_aad_length);
|
||||
res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, plaintext_buf_new.data(), plaintext_buf_new.size(), ciphertext_buf.data(), ciphertext_buf.size(), false);
|
||||
BOOST_CHECK(res);
|
||||
|
||||
// make sure we repetitive get the same plaintext
|
||||
BOOST_CHECK(memcmp(plaintext_buf.data(), plaintext_buf_new.data(), plaintext_buf.size()) == 0);
|
||||
|
||||
// compare sequence number 999 against the test vector
|
||||
if (seqnr_payload == 999) {
|
||||
BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac_sequence999.data(), expected_ciphertext_and_mac_sequence999.size()) == 0);
|
||||
}
|
||||
// set nonce and block counter, output the keystream
|
||||
cmp_ctx.Seek64({0, seqnr_aad}, 0);
|
||||
cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64);
|
||||
|
||||
// crypt the 3 length bytes and compare the length
|
||||
len_cmp = 0;
|
||||
len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) |
|
||||
(ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 |
|
||||
(ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16;
|
||||
BOOST_CHECK_EQUAL(len_cmp, expected_aad_length);
|
||||
|
||||
// increment the sequence number(s)
|
||||
// always increment the payload sequence number
|
||||
// increment the AAD keystream position by its size (3)
|
||||
// increment the AAD sequence number if we would hit the 64 byte limit
|
||||
seqnr_payload++;
|
||||
aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN;
|
||||
if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) {
|
||||
aad_pos = 0;
|
||||
seqnr_aad++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(chacha20_poly1305_aead_testvector)
|
||||
{
|
||||
/* test chacha20poly1305@bitcoin AEAD */
|
||||
|
||||
// must fail with no message
|
||||
TestChaCha20Poly1305AEAD(false, 0,
|
||||
"",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000", "", "", "");
|
||||
|
||||
TestChaCha20Poly1305AEAD(true, 0,
|
||||
/* m */ "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
/* k1 (AAD) */ "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
/* k2 (payload) */ "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
/* AAD keystream */ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586",
|
||||
/* encrypted message & MAC */ "76b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32d2fc11829c1b6c1df1f551cd6131ff08",
|
||||
/* encrypted message & MAC at sequence 999 */ "b0a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3aaa7aa16ec62c5e24f040c08bb20c3598");
|
||||
TestChaCha20Poly1305AEAD(true, 1,
|
||||
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586",
|
||||
"77b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32baf0c85b6dff8602b06cf52a6aefc62e",
|
||||
"b1a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3a8bd94d54b5ecabbc41ffbb0c90924080");
|
||||
TestChaCha20Poly1305AEAD(true, 255,
|
||||
"ff0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9",
|
||||
"ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||
"c640c1711e3ee904ac35c57ab9791c8a1c408603a90b77a83b54f6c844cb4b06d94e7fc6c800e165acd66147e80ec45a567f6ce66d05ec0cae679dceeb890017",
|
||||
"3940c1e92da4582ff6f92a776aeb14d014d384eeb30f660dacf70a14a23fd31e91212701334e2ce1acf5199dc84f4d61ddbe6571bca5af874b4c9226c26e650995d157644e1848b96ed6c2102d5489a050e71d29a5a66ece11de5fb5c9558d54da28fe45b0bc4db4e5b88030bfc4a352b4b7068eccf656bae7ad6a35615315fc7c49d4200388d5eca67c2e822e069336c69b40db67e0f3c81209c50f3216a4b89fb3ae1b984b7851a2ec6f68ab12b101ab120e1ea7313bb93b5a0f71185c7fea017ddb92769861c29dba4fbc432280d5dff21b36d1c4c790128b22699950bb18bf74c448cdfe547d8ed4f657d8005fdc0cd7a050c2d46050a44c4376355858981fbe8b184288276e7a93eabc899c4a",
|
||||
"f039c6689eaeef0456685200feaab9d54bbd9acde4410a3b6f4321296f4a8ca2604b49727d8892c57e005d799b2a38e85e809f20146e08eec75169691c8d4f54a0d51a1e1c7b381e0474eb02f994be9415ef3ffcbd2343f0601e1f3b172a1d494f838824e4df570f8e3b0c04e27966e36c82abd352d07054ef7bd36b84c63f9369afe7ed79b94f953873006b920c3fa251a771de1b63da927058ade119aa898b8c97e42a606b2f6df1e2d957c22f7593c1e2002f4252f4c9ae4bf773499e5cfcfe14dfc1ede26508953f88553bf4a76a802f6a0068d59295b01503fd9a600067624203e880fdf53933b96e1f4d9eb3f4e363dd8165a278ff667a41ee42b9892b077cefff92b93441f7be74cf10e6cd");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(countbits_tests)
|
||||
{
|
||||
FastRandomContext ctx;
|
||||
|
|
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();
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue