mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
Merge #15649: Add ChaCha20Poly1305@Bitcoin AEAD
bb326add9f38f2a8e5ce5ee29d98ce08038200d8 Add ChaCha20Poly1305@Bitcoin AEAD benchmark (Jonas Schnelli) 99aea045d688059caf89c0e485fa427bd28eddd8 Add ChaCha20Poly1305@Bitcoin tests (Jonas Schnelli) af5d1b5f4a7b56628a76af21284c258d845894f0 Add ChaCha20Poly1305@Bitcoin AEAD implementation (Jonas Schnelli) Pull request description: This adds a new AEAD (authenticated encryption with additional data) construct optimised for small messages (like used in Bitcoins p2p network). Includes: #15519, #15512 (please review those first). The construct is specified here. https://gist.github.com/jonasschnelli/c530ea8421b8d0e80c51486325587c52#ChaCha20Poly1305Bitcoin_Cipher_Suite This aims for being used in v2 peer-to-peer messages. ACKs for top commit: laanwj: code review ACK bb326add9f38f2a8e5ce5ee29d98ce08038200d8 Tree-SHA512: 15bcb86c510fce7abb7a73536ff2ae89893b24646bf108c6cf18f064d672dbbbea8b1dd0868849fdac0c6854e498f1345d01dab56d1c92031afd728302234686 Add new line
This commit is contained in:
parent
996b1f0784
commit
0840ce3a92
@ -367,6 +367,8 @@ crypto_libdash_crypto_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) $(PIC_FLAGS)
|
|||||||
crypto_libdash_crypto_a_SOURCES = \
|
crypto_libdash_crypto_a_SOURCES = \
|
||||||
crypto/aes.cpp \
|
crypto/aes.cpp \
|
||||||
crypto/aes.h \
|
crypto/aes.h \
|
||||||
|
crypto/chacha_poly_aead.h \
|
||||||
|
crypto/chacha_poly_aead.cpp \
|
||||||
crypto/chacha20.h \
|
crypto/chacha20.h \
|
||||||
crypto/chacha20.cpp \
|
crypto/chacha20.cpp \
|
||||||
crypto/common.h \
|
crypto/common.h \
|
||||||
@ -588,7 +590,7 @@ dash_tx_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(CRYPTO_LIBS) $(BLS_LIBS)
|
|||||||
# dashconsensus library #
|
# dashconsensus library #
|
||||||
if BUILD_BITCOIN_LIBS
|
if BUILD_BITCOIN_LIBS
|
||||||
include_HEADERS = script/dashconsensus.h
|
include_HEADERS = script/dashconsensus.h
|
||||||
libdashconsensus_la_SOURCES = $(crypto_libdash_crypto_a_SOURCES) $(libdash_consensus_a_SOURCES)
|
libdashconsensus_la_SOURCES = support/cleanse.cpp $(crypto_libdash_crypto_a_SOURCES) $(libdash_consensus_a_SOURCES)
|
||||||
|
|
||||||
if GLIBC_BACK_COMPAT
|
if GLIBC_BACK_COMPAT
|
||||||
libdashconsensus_la_SOURCES += compat/glibc_compat.cpp
|
libdashconsensus_la_SOURCES += compat/glibc_compat.cpp
|
||||||
|
@ -23,6 +23,7 @@ bench_bench_dash_SOURCES = \
|
|||||||
bench/Examples.cpp \
|
bench/Examples.cpp \
|
||||||
bench/rollingbloom.cpp \
|
bench/rollingbloom.cpp \
|
||||||
bench/chacha20.cpp \
|
bench/chacha20.cpp \
|
||||||
|
bench/chacha_poly_aead.cpp \
|
||||||
bench/crypto_hash.cpp \
|
bench/crypto_hash.cpp \
|
||||||
bench/ccoins_caching.cpp \
|
bench/ccoins_caching.cpp \
|
||||||
bench/mempool_eviction.cpp \
|
bench/mempool_eviction.cpp \
|
||||||
|
@ -47,4 +47,5 @@ static void CHACHA20_1MB(benchmark::State& state)
|
|||||||
//BENCHMARK(CHACHA20_1MB, 340);
|
//BENCHMARK(CHACHA20_1MB, 340);
|
||||||
BENCHMARK(CHACHA20_64BYTES);
|
BENCHMARK(CHACHA20_64BYTES);
|
||||||
BENCHMARK(CHACHA20_256BYTES);
|
BENCHMARK(CHACHA20_256BYTES);
|
||||||
BENCHMARK(CHACHA20_1MB);
|
BENCHMARK(CHACHA20_1MB);
|
||||||
|
|
||||||
|
123
src/bench/chacha_poly_aead.cpp
Normal file
123
src/bench/chacha_poly_aead.cpp
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// Copyright (c) 2019 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 <iostream>
|
||||||
|
|
||||||
|
#include <bench/bench.h>
|
||||||
|
#include <crypto/chacha_poly_aead.h>
|
||||||
|
#include <crypto/poly1305.h> // for the POLY1305_TAGLEN constant
|
||||||
|
#include <hash.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/* 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::State& state, 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;
|
||||||
|
while (state.KeepRunning()) {
|
||||||
|
// encrypt or decrypt the buffer with a static key
|
||||||
|
assert(aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true));
|
||||||
|
|
||||||
|
if (include_decryption) {
|
||||||
|
// if we decrypt, include the GetLength
|
||||||
|
assert(aead.GetLength(&len, seqnr_aad, aad_pos, in.data()));
|
||||||
|
assert(aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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::State& state)
|
||||||
|
{
|
||||||
|
CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_TINY, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT(benchmark::State& state)
|
||||||
|
{
|
||||||
|
CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_SMALL, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT(benchmark::State& state)
|
||||||
|
{
|
||||||
|
CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_LARGE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT(benchmark::State& state)
|
||||||
|
{
|
||||||
|
CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_TINY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT(benchmark::State& state)
|
||||||
|
{
|
||||||
|
CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_SMALL, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT(benchmark::State& state)
|
||||||
|
{
|
||||||
|
CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_LARGE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Hash() (dbl-sha256) bench for comparison
|
||||||
|
|
||||||
|
static void HASH(benchmark::State& state, size_t buffersize)
|
||||||
|
{
|
||||||
|
uint8_t hash[CHash256::OUTPUT_SIZE];
|
||||||
|
std::vector<uint8_t> in(buffersize,0);
|
||||||
|
while (state.KeepRunning())
|
||||||
|
CHash256().Write(in.data(), in.size()).Finalize(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HASH_64BYTES(benchmark::State& state)
|
||||||
|
{
|
||||||
|
HASH(state, BUFFER_SIZE_TINY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HASH_256BYTES(benchmark::State& state)
|
||||||
|
{
|
||||||
|
HASH(state, BUFFER_SIZE_SMALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HASH_1MB(benchmark::State& state)
|
||||||
|
{
|
||||||
|
HASH(state, BUFFER_SIZE_LARGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT, 500000);
|
||||||
|
BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT, 250000);
|
||||||
|
BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT, 340);
|
||||||
|
BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT, 500000);
|
||||||
|
BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT, 250000);
|
||||||
|
BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT, 340);
|
||||||
|
BENCHMARK(HASH_64BYTES, 500000);
|
||||||
|
BENCHMARK(HASH_256BYTES, 250000);
|
||||||
|
BENCHMARK(HASH_1MB, 340);
|
126
src/crypto/chacha_poly_aead.cpp
Normal file
126
src/crypto/chacha_poly_aead.cpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright (c) 2019 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/common.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);
|
||||||
|
m_chacha_main.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN);
|
||||||
|
m_chacha_header.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN);
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
m_chacha_main.SetIV(seqnr_payload);
|
||||||
|
|
||||||
|
// 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.Seek(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_auth(expected_tag, src, src_len - POLY1305_TAGLEN, poly_key);
|
||||||
|
|
||||||
|
// 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 covert 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.SetIV(seqnr_aad);
|
||||||
|
m_chacha_header.Seek(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.Seek(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_auth(dest + src_len, dest, src_len, poly_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.SetIV(seqnr_aad); // use LE for the nonce
|
||||||
|
m_chacha_header.Seek(0); // block counter 0
|
||||||
|
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;
|
||||||
|
}
|
146
src/crypto/chacha_poly_aead.h
Normal file
146
src/crypto/chacha_poly_aead.h
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Copyright (c) 2019 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>[http://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>[http://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_main; // payload and poly1305 key-derivation cipher instance
|
||||||
|
ChaCha20 m_chacha_header; // AAD cipher instance (encrypted length)
|
||||||
|
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
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "crypto/aes.h"
|
#include "crypto/aes.h"
|
||||||
#include "crypto/chacha20.h"
|
#include "crypto/chacha20.h"
|
||||||
|
#include "crypto/chacha_poly_aead.h"
|
||||||
#include "crypto/poly1305.h"
|
#include "crypto/poly1305.h"
|
||||||
#include "crypto/ripemd160.h"
|
#include "crypto/ripemd160.h"
|
||||||
#include "crypto/sha1.h"
|
#include "crypto/sha1.h"
|
||||||
@ -619,6 +620,131 @@ BOOST_AUTO_TEST_CASE(poly1305_testvector)
|
|||||||
"13000000000000000000000000000000");
|
"13000000000000000000000000000000");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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_2.data(), 32);
|
||||||
|
|
||||||
|
// 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.SetIV(seqnr_aad);
|
||||||
|
cmp_ctx.Seek(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.SetIV(seqnr_aad);
|
||||||
|
cmp_ctx.Seek(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 (payload) */ "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
/* k2 (AAD) */ "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",
|
||||||
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||||
|
"ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
||||||
|
"c640c1711e3ee904ac35c57ab9791c8a1c408603a90b77a83b54f6c844cb4b06d94e7fc6c800e165acd66147e80ec45a567f6ce66d05ec0cae679dceeb890017",
|
||||||
|
"3940c1e92da4582ff6f92a776aeb14d014d384eeb30f660dacf70a14a23fd31e91212701334e2ce1acf5199dc84f4d61ddbe6571bca5af874b4c9226c26e650995d157644e1848b96ed6c2102d5489a050e71d29a5a66ece11de5fb5c9558d54da28fe45b0bc4db4e5b88030bfc4a352b4b7068eccf656bae7ad6a35615315fc7c49d4200388d5eca67c2e822e069336c69b40db67e0f3c81209c50f3216a4b89fb3ae1b984b7851a2ec6f68ab12b101ab120e1ea7313bb93b5a0f71185c7fea017ddb92769861c29dba4fbc432280d5dff21b36d1c4c790128b22699950bb18bf74c448cdfe547d8ed4f657d8005fdc0cd7a050c2d46050a44c4376355858981fbe8b184288276e7a93eabc899c4a",
|
||||||
|
"f039c6689eaeef0456685200feaab9d54bbd9acde4410a3b6f4321296f4a8ca2604b49727d8892c57e005d799b2a38e85e809f20146e08eec75169691c8d4f54a0d51a1e1c7b381e0474eb02f994be9415ef3ffcbd2343f0601e1f3b172a1d494f838824e4df570f8e3b0c04e27966e36c82abd352d07054ef7bd36b84c63f9369afe7ed79b94f953873006b920c3fa251a771de1b63da927058ade119aa898b8c97e42a606b2f6df1e2d957c22f7593c1e2002f4252f4c9ae4bf773499e5cfcfe14dfc1ede26508953f88553bf4a76a802f6a0068d59295b01503fd9a600067624203e880fdf53933b96e1f4d9eb3f4e363dd8165a278ff667a41ee42b9892b077cefff92b93441f7be74cf10e6cd");
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(countbits_tests)
|
BOOST_AUTO_TEST_CASE(countbits_tests)
|
||||||
{
|
{
|
||||||
FastRandomContext ctx;
|
FastRandomContext ctx;
|
||||||
|
Loading…
Reference in New Issue
Block a user