Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add BIP324Cipher, encapsulating key agreement, derivation, and stream…
…/AEAD ciphers Co-authored-by: dhruv <856960+dhruv@users.noreply.github.com>
- Loading branch information
Showing
7 changed files
with
586 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// 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) 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. | ||
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())); | ||
(initiator ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); | ||
hkdf.Expand32("initiator_P", UCharCast(hkdf_32_okm.data())); | ||
(initiator ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); | ||
hkdf.Expand32("responder_L", UCharCast(hkdf_32_okm.data())); | ||
(initiator ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); | ||
hkdf.Expand32("responder_P", UCharCast(hkdf_32_okm.data())); | ||
(initiator ? 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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// 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. */ | ||
void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator) 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.