mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-03 01:33:20 +02:00
crypto: add FSChaCha20Poly1305, rekeying wrapper around ChaCha20Poly1305
This adds the FSChaCha20Poly1305 AEAD as specified in BIP324, a wrapper around the ChaCha20Poly1305 AEAD (as specified in RFC8439 section 2.8) which automatically rekeys every N messages, and automatically increments the nonce every message.
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include <crypto/chacha20.h>
|
||||
#include <crypto/poly1305.h>
|
||||
#include <span.h>
|
||||
#include <support/cleanse.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <cstdint>
|
||||
@@ -99,3 +100,41 @@ bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std:
|
||||
m_chacha20.Crypt(UCharCast(cipher.data()), UCharCast(plain.data()), plain.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> plain, Span<const std::byte> aad, Span<std::byte> cipher) noexcept
|
||||
{
|
||||
m_aead.Encrypt(plain, 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> plain) noexcept
|
||||
{
|
||||
bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain);
|
||||
NextPacket();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,68 @@ public:
|
||||
* 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;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** 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;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_CRYPTO_CHACHA20POLY1305_H
|
||||
|
||||
Reference in New Issue
Block a user