Skip to content

Commit 990f0f8

Browse files
sipadhruv
andcommitted
Add BIP324Cipher, encapsulating key agreement, derivation, and stream/AEAD ciphers
Co-authored-by: dhruv <856960+dhruv@users.noreply.github.com>
1 parent c91cedf commit 990f0f8

File tree

7 files changed

+586
-0
lines changed

7 files changed

+586
-0
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ BITCOIN_CORE_H = \
124124
banman.h \
125125
base58.h \
126126
bech32.h \
127+
bip324.h \
127128
blockencodings.h \
128129
blockfilter.h \
129130
chain.h \
@@ -376,6 +377,7 @@ libbitcoin_node_a_SOURCES = \
376377
addrdb.cpp \
377378
addrman.cpp \
378379
banman.cpp \
380+
bip324.cpp \
379381
blockencodings.cpp \
380382
blockfilter.cpp \
381383
chain.cpp \

src/Makefile.test.include

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ BITCOIN_TESTS =\
7474
test/base64_tests.cpp \
7575
test/bech32_tests.cpp \
7676
test/bip32_tests.cpp \
77+
test/bip324_tests.cpp \
7778
test/blockchain_tests.cpp \
7879
test/blockencodings_tests.cpp \
7980
test/blockfilter_index_tests.cpp \
@@ -246,6 +247,7 @@ test_fuzz_fuzz_SOURCES = \
246247
test/fuzz/banman.cpp \
247248
test/fuzz/base_encode_decode.cpp \
248249
test/fuzz/bech32.cpp \
250+
test/fuzz/bip324.cpp \
249251
test/fuzz/bitdeque.cpp \
250252
test/fuzz/block.cpp \
251253
test/fuzz/block_header.cpp \

src/bip324.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) 2023 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <bip324.h>
6+
7+
#include <chainparams.h>
8+
#include <crypto/chacha20.h>
9+
#include <crypto/chacha20poly1305.h>
10+
#include <crypto/hkdf_sha256_32.h>
11+
#include <random.h>
12+
#include <span.h>
13+
#include <support/cleanse.h>
14+
15+
#include <algorithm>
16+
#include <assert.h>
17+
#include <cstdint>
18+
#include <cstddef>
19+
20+
BIP324Cipher::BIP324Cipher() noexcept
21+
{
22+
m_key.MakeNewKey(true);
23+
uint256 entropy = GetRandHash();
24+
m_our_pubkey = m_key.EllSwiftCreate(MakeByteSpan(entropy));
25+
}
26+
27+
BIP324Cipher::BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcept :
28+
m_key(key)
29+
{
30+
m_our_pubkey = m_key.EllSwiftCreate(ent32);
31+
}
32+
33+
BIP324Cipher::BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept :
34+
m_key(key), m_our_pubkey(pubkey) {}
35+
36+
void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator) noexcept
37+
{
38+
// Determine salt (fixed string + network magic bytes)
39+
const auto& message_header = Params().MessageStart();
40+
std::string salt = std::string{"bitcoin_v2_shared_secret"} + std::string(std::begin(message_header), std::end(message_header));
41+
42+
// Perform ECDH to derive shared secret.
43+
ECDHSecret ecdh_secret = m_key.ComputeBIP324ECDHSecret(their_pubkey, m_our_pubkey, initiator);
44+
45+
// Derive encryption keys from shared secret, and initialize stream ciphers and AEADs.
46+
CHKDF_HMAC_SHA256_L32 hkdf(UCharCast(ecdh_secret.data()), ecdh_secret.size(), salt);
47+
std::array<std::byte, 32> hkdf_32_okm;
48+
hkdf.Expand32("initiator_L", UCharCast(hkdf_32_okm.data()));
49+
(initiator ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
50+
hkdf.Expand32("initiator_P", UCharCast(hkdf_32_okm.data()));
51+
(initiator ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
52+
hkdf.Expand32("responder_L", UCharCast(hkdf_32_okm.data()));
53+
(initiator ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
54+
hkdf.Expand32("responder_P", UCharCast(hkdf_32_okm.data()));
55+
(initiator ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
56+
57+
// Derive garbage terminators from shared secret.
58+
hkdf.Expand32("garbage_terminators", UCharCast(hkdf_32_okm.data()));
59+
std::copy(std::begin(hkdf_32_okm), std::begin(hkdf_32_okm) + GARBAGE_TERMINATOR_LEN,
60+
(initiator ? m_send_garbage_terminator : m_recv_garbage_terminator).begin());
61+
std::copy(std::end(hkdf_32_okm) - GARBAGE_TERMINATOR_LEN, std::end(hkdf_32_okm),
62+
(initiator ? m_recv_garbage_terminator : m_send_garbage_terminator).begin());
63+
64+
// Derive session id from shared secret.
65+
hkdf.Expand32("session_id", UCharCast(m_session_id.data()));
66+
67+
// Wipe all variables that contain information which could be used to re-derive encryption keys.
68+
memory_cleanse(ecdh_secret.data(), ecdh_secret.size());
69+
memory_cleanse(hkdf_32_okm.data(), sizeof(hkdf_32_okm));
70+
memory_cleanse(&hkdf, sizeof(hkdf));
71+
m_key = CKey();
72+
}
73+
74+
void BIP324Cipher::Encrypt(Span<const std::byte> contents, Span<const std::byte> aad, bool ignore, Span<std::byte> output) noexcept
75+
{
76+
assert(output.size() == contents.size() + EXPANSION);
77+
78+
// Encrypt length.
79+
std::byte len[LENGTH_LEN];
80+
len[0] = std::byte{(uint8_t)(contents.size() & 0xFF)};
81+
len[1] = std::byte{(uint8_t)((contents.size() >> 8) & 0xFF)};
82+
len[2] = std::byte{(uint8_t)((contents.size() >> 16) & 0xFF)};
83+
m_send_l_cipher->Crypt(len, output.first(LENGTH_LEN));
84+
85+
// Encrypt plaintext.
86+
std::byte header[HEADER_LEN] = {ignore ? IGNORE_BIT : std::byte{0}};
87+
m_send_p_cipher->Encrypt(header, contents, aad, output.subspan(LENGTH_LEN));
88+
}
89+
90+
uint32_t BIP324Cipher::DecryptLength(Span<const std::byte> input) noexcept
91+
{
92+
assert(input.size() == LENGTH_LEN);
93+
94+
std::byte buf[LENGTH_LEN];
95+
// Decrypt length
96+
m_recv_l_cipher->Crypt(input, buf);
97+
// Convert to number.
98+
return uint32_t(buf[0]) + (uint32_t(buf[1]) << 8) + (uint32_t(buf[2]) << 16);
99+
}
100+
101+
bool BIP324Cipher::Decrypt(Span<const std::byte> input, Span<const std::byte> aad, bool& ignore, Span<std::byte> contents) noexcept
102+
{
103+
assert(input.size() + LENGTH_LEN == contents.size() + EXPANSION);
104+
105+
std::byte header[HEADER_LEN];
106+
if (!m_recv_p_cipher->Decrypt(input, aad, header, contents)) return false;
107+
108+
ignore = (header[0] & IGNORE_BIT) == IGNORE_BIT;
109+
return true;
110+
}

src/bip324.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) 2023 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_BIP324_H
6+
#define BITCOIN_BIP324_H
7+
8+
#include <cstddef>
9+
#include <optional>
10+
11+
#include <crypto/chacha20.h>
12+
#include <crypto/chacha20poly1305.h>
13+
#include <key.h>
14+
#include <pubkey.h>
15+
#include <span.h>
16+
17+
/** The BIP324 packet cipher, encapsulating its key derivation, stream cipher, and AEAD. */
18+
class BIP324Cipher
19+
{
20+
public:
21+
static constexpr unsigned SESSION_ID_LEN{32};
22+
static constexpr unsigned GARBAGE_TERMINATOR_LEN{16};
23+
static constexpr unsigned REKEY_INTERVAL{224};
24+
static constexpr unsigned LENGTH_LEN{3};
25+
static constexpr unsigned HEADER_LEN{1};
26+
static constexpr unsigned EXPANSION = LENGTH_LEN + HEADER_LEN + FSChaCha20Poly1305::EXPANSION;
27+
static constexpr std::byte IGNORE_BIT{0x80};
28+
29+
private:
30+
std::optional<FSChaCha20> m_send_l_cipher;
31+
std::optional<FSChaCha20> m_recv_l_cipher;
32+
std::optional<FSChaCha20Poly1305> m_send_p_cipher;
33+
std::optional<FSChaCha20Poly1305> m_recv_p_cipher;
34+
35+
CKey m_key;
36+
EllSwiftPubKey m_our_pubkey;
37+
38+
std::array<std::byte, SESSION_ID_LEN> m_session_id;
39+
std::array<std::byte, GARBAGE_TERMINATOR_LEN> m_send_garbage_terminator;
40+
std::array<std::byte, GARBAGE_TERMINATOR_LEN> m_recv_garbage_terminator;
41+
42+
public:
43+
/** Initialize a BIP324 cipher with securely generated random keys. */
44+
BIP324Cipher() noexcept;
45+
46+
/** Initialize a BIP324 cipher with specified key and encoding entropy (testing only). */
47+
BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcept;
48+
49+
/** Initialize a BIP324 cipher with specified key (testing only). */
50+
BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept;
51+
52+
/** Retrieve our public key. */
53+
const EllSwiftPubKey& GetOurPubKey() const noexcept { return m_our_pubkey; }
54+
55+
/** Initialize when the other side's public key is received. Can only be called once. */
56+
void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator) noexcept;
57+
58+
/** Determine whether this cipher is fully initialized. */
59+
explicit operator bool() const noexcept { return m_send_l_cipher.has_value(); }
60+
61+
/** Encrypt a packet. Only after Initialize().
62+
*
63+
* It must hold that output.size() == contents.size() + EXPANSION.
64+
*/
65+
void Encrypt(Span<const std::byte> contents, Span<const std::byte> aad, bool ignore, Span<std::byte> output) noexcept;
66+
67+
/** Decrypt the length of a packet. Only after Initialize().
68+
*
69+
* It must hold that input.size() == LENGTH_LEN.
70+
*/
71+
unsigned DecryptLength(Span<const std::byte> input) noexcept;
72+
73+
/** Decrypt a packet. Only after Initialize().
74+
*
75+
* It must hold that input.size() + LENGTH_LEN == contents.size() + EXPANSION.
76+
* Contents.size() must equal the length returned by DecryptLength.
77+
*/
78+
bool Decrypt(Span<const std::byte> input, Span<const std::byte> aad, bool& ignore, Span<std::byte> contents) noexcept;
79+
80+
/** Get the Session ID. Only after Initialize(). */
81+
Span<const std::byte> GetSessionID() const noexcept { return m_session_id; }
82+
83+
/** Get the Garbage Terminator to send. Only after Initialize(). */
84+
Span<const std::byte> GetSendGarbageTerminator() const noexcept { return m_send_garbage_terminator; }
85+
86+
/** Get the expected Garbage Terminator to receive. Only after Initialize(). */
87+
Span<const std::byte> GetReceiveGarbageTerminator() const noexcept { return m_recv_garbage_terminator; }
88+
};
89+
90+
#endif // BITCOIN_BIP324_H

src/pubkey.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,9 @@ struct EllSwiftPubKey
299299
std::array<std::byte, SIZE> m_pubkey;
300300

301301
public:
302+
/** Default constructor creates all-zero pubkey (which is valid). */
303+
EllSwiftPubKey() noexcept = default;
304+
302305
/** Construct a new ellswift public key from a given serialization. */
303306
EllSwiftPubKey(const std::array<std::byte, SIZE>& ellswift) :
304307
m_pubkey(ellswift) {}

0 commit comments

Comments
 (0)