Add BIP324Cipher, encapsulating key agreement, derivation, and stream/AEAD ciphers
Co-authored-by: dhruv <856960+dhruv@users.noreply.github.com>
This commit is contained in:
1 parent
c91cedf281
commit
990f0f8da9
7 files changed
+586
No files matched your search
@@ -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 \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+110
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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) {}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// 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 <key.h>
|
||||
#include <pubkey.h>
|
||||
#include <test/util/random.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
void TestBIP324PacketVector(
|
||||
uint32_t in_idx,
|
||||
const std::string& in_priv_ours_hex,
|
||||
const std::string& in_ellswift_ours_hex,
|
||||
const std::string& in_ellswift_theirs_hex,
|
||||
bool in_initiating,
|
||||
const std::string& in_contents_hex,
|
||||
uint32_t in_multiply,
|
||||
const std::string& in_aad_hex,
|
||||
bool in_ignore,
|
||||
const std::string& mid_send_garbage_hex,
|
||||
const std::string& mid_recv_garbage_hex,
|
||||
const std::string& out_session_id_hex,
|
||||
const std::string& out_ciphertext_hex,
|
||||
const std::string& out_ciphertext_endswith_hex)
|
||||
{
|
||||
// Convert input from hex to char/byte vectors/arrays.
|
||||
const auto in_priv_ours = ParseHex(in_priv_ours_hex);
|
||||
const auto in_ellswift_ours_vec = ParseHex<std::byte>(in_ellswift_ours_hex);
|
||||
assert(in_ellswift_ours_vec.size() == 64);
|
||||
std::array<std::byte, 64> in_ellswift_ours;
|
||||
std::copy(in_ellswift_ours_vec.begin(), in_ellswift_ours_vec.end(), in_ellswift_ours.begin());
|
||||
const auto in_ellswift_theirs_vec = ParseHex<std::byte>(in_ellswift_theirs_hex);
|
||||
assert(in_ellswift_theirs_vec.size() == 64);
|
||||
std::array<std::byte, 64> in_ellswift_theirs;
|
||||
std::copy(in_ellswift_theirs_vec.begin(), in_ellswift_theirs_vec.end(), in_ellswift_theirs.begin());
|
||||
const auto in_contents = ParseHex<std::byte>(in_contents_hex);
|
||||
const auto in_aad = ParseHex<std::byte>(in_aad_hex);
|
||||
const auto mid_send_garbage = ParseHex<std::byte>(mid_send_garbage_hex);
|
||||
const auto mid_recv_garbage = ParseHex<std::byte>(mid_recv_garbage_hex);
|
||||
const auto out_session_id = ParseHex<std::byte>(out_session_id_hex);
|
||||
const auto out_ciphertext = ParseHex<std::byte>(out_ciphertext_hex);
|
||||
const auto out_ciphertext_endswith = ParseHex<std::byte>(out_ciphertext_endswith_hex);
|
||||
|
||||
// Load keys
|
||||
CKey key;
|
||||
key.Set(in_priv_ours.begin(), in_priv_ours.end(), true);
|
||||
EllSwiftPubKey ellswift_ours(in_ellswift_ours);
|
||||
EllSwiftPubKey ellswift_theirs(in_ellswift_theirs);
|
||||
|
||||
// Instantiate encryption BIP324 cipher.
|
||||
BIP324Cipher cipher(key, ellswift_ours);
|
||||
BOOST_CHECK(!cipher);
|
||||
BOOST_CHECK(cipher.GetOurPubKey() == ellswift_ours);
|
||||
cipher.Initialize(ellswift_theirs, in_initiating);
|
||||
BOOST_CHECK(cipher);
|
||||
|
||||
// Compare session variables.
|
||||
BOOST_CHECK(Span{out_session_id} == cipher.GetSessionID());
|
||||
BOOST_CHECK(Span{mid_send_garbage} == cipher.GetSendGarbageTerminator());
|
||||
BOOST_CHECK(Span{mid_recv_garbage} == cipher.GetReceiveGarbageTerminator());
|
||||
|
||||
// Seek to the numbered packet.
|
||||
for (uint32_t i = 0; i < in_idx; ++i) {
|
||||
std::vector<std::byte> dummy(cipher.EXPANSION);
|
||||
cipher.Encrypt({}, {}, false, dummy);
|
||||
}
|
||||
|
||||
// Construct contents and encrypt it.
|
||||
std::vector<std::byte> contents;
|
||||
for (uint32_t i = 0; i < in_multiply; ++i) {
|
||||
contents.insert(contents.end(), in_contents.begin(), in_contents.end());
|
||||
}
|
||||
std::vector<std::byte> ciphertext(contents.size() + cipher.EXPANSION);
|
||||
cipher.Encrypt(contents, in_aad, in_ignore, ciphertext);
|
||||
|
||||
// Verify ciphertext. Note that the test vectors specify either out_ciphertext (for short
|
||||
// messages) or out_ciphertext_endswith (for long messages), so only check the relevant one.
|
||||
if (!out_ciphertext.empty()) {
|
||||
BOOST_CHECK(out_ciphertext == ciphertext);
|
||||
} else {
|
||||
BOOST_CHECK(ciphertext.size() >= out_ciphertext_endswith.size());
|
||||
BOOST_CHECK(Span{out_ciphertext_endswith} == Span{ciphertext}.last(out_ciphertext_endswith.size()));
|
||||
}
|
||||
|
||||
// Note that we don't test decryption here, as the test vectors don't provide the other party's
|
||||
// private key, so we cannot act like them. See the bip324_cipher_roundtrip fuzz test for a test
|
||||
// that does cover decryption.
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(bip324_tests, BasicTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(packet_test_vectors) {
|
||||
// BIP324 key derivation uses network magic in the HKDF process. We use mainnet params here
|
||||
// as that is what the test vectors are written for.
|
||||
SelectParams(ChainType::MAIN);
|
||||
|
||||
// The test vectors are converted using the following Python code in the BIP bip-0324/ directory:
|
||||
//
|
||||
// import sys
|
||||
// import csv
|
||||
// with open('packet_encoding_test_vectors.csv', newline='', encoding='utf-8') as csvfile:
|
||||
// reader = csv.DictReader(csvfile)
|
||||
// quote = lambda x: "\"" + x + "\""
|
||||
// for row in reader:
|
||||
// args = [
|
||||
// row['in_idx'],
|
||||
// quote(row['in_priv_ours']),
|
||||
// quote(row['in_ellswift_ours']),
|
||||
// quote(row['in_ellswift_theirs']),
|
||||
// "true" if int(row['in_initiating']) else "false",
|
||||
// quote(row['in_contents']),
|
||||
// row['in_multiply'],
|
||||
// quote(row['in_aad']),
|
||||
// "true" if int(row['in_ignore']) else "false",
|
||||
// quote(row['mid_send_garbage_terminator']),
|
||||
// quote(row['mid_recv_garbage_terminator']),
|
||||
// quote(row['out_session_id']),
|
||||
// quote(row['out_ciphertext']),
|
||||
// quote(row['out_ciphertext_endswith'])
|
||||
// ]
|
||||
// print(" TestBIP324PacketVector(\n " + ",\n ".join(args) + ");")
|
||||
TestBIP324PacketVector(
|
||||
1,
|
||||
"61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7",
|
||||
"ec0adff257bbfe500c188c80b4fdd640f6b45a482bbc15fc7cef5931deff0aa186f6eb9bba7b85dc4dcc28b28722de1e3d9108b985e2967045668f66098e475b",
|
||||
"a4a94dfce69b4a2a0a099313d10f9f7e7d649d60501c9e1d274c300e0d89aafaffffffffffffffffffffffffffffffffffffffffffffffffffffffff8faf88d5",
|
||||
true,
|
||||
"8e",
|
||||
1,
|
||||
"",
|
||||
false,
|
||||
"faef555dfcdb936425d84aba524758f3",
|
||||
"02cb8ff24307a6e27de3b4e7ea3fa65b",
|
||||
"ce72dffb015da62b0d0f5474cab8bc72605225b0cee3f62312ec680ec5f41ba5",
|
||||
"7530d2a18720162ac09c25329a60d75adf36eda3c3",
|
||||
"");
|
||||
TestBIP324PacketVector(
|
||||
999,
|
||||
"1f9c581b35231838f0f17cf0c979835baccb7f3abbbb96ffcc318ab71e6e126f",
|
||||
"a1855e10e94e00baa23041d916e259f7044e491da6171269694763f018c7e63693d29575dcb464ac816baa1be353ba12e3876cba7628bd0bd8e755e721eb0140",
|
||||
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0000000000000000000000000000000000000000000000000000000000000000",
|
||||
false,
|
||||
"3eb1d4e98035cfd8eeb29bac969ed3824a",
|
||||
1,
|
||||
"",
|
||||
false,
|
||||
"efb64fd80acd3825ac9bc2a67216535a",
|
||||
"b3cb553453bceb002897e751ff7588bf",
|
||||
"9267c54560607de73f18c563b76a2442718879c52dd39852885d4a3c9912c9ea",
|
||||
"1da1bcf589f9b61872f45b7fa5371dd3f8bdf5d515b0c5f9fe9f0044afb8dc0aa1cd39a8c4",
|
||||
"");
|
||||
TestBIP324PacketVector(
|
||||
0,
|
||||
"0286c41cd30913db0fdff7a64ebda5c8e3e7cef10f2aebc00a7650443cf4c60d",
|
||||
"d1ee8a93a01130cbf299249a258f94feb5f469e7d0f2f28f69ee5e9aa8f9b54a60f2c3ff2d023634ec7f4127a96cc11662e402894cf1f694fb9a7eaa5f1d9244",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff22d5e441524d571a52b3def126189d3f416890a99d4da6ede2b0cde1760ce2c3f98457ae",
|
||||
true,
|
||||
"054290a6c6ba8d80478172e89d32bf690913ae9835de6dcf206ff1f4d652286fe0ddf74deba41d55de3edc77c42a32af79bbea2c00bae7492264c60866ae5a",
|
||||
1,
|
||||
"84932a55aac22b51e7b128d31d9f0550da28e6a3f394224707d878603386b2f9d0c6bcd8046679bfed7b68c517e7431e75d9dd34605727d2ef1c2babbf680ecc8d68d2c4886e9953a4034abde6da4189cd47c6bb3192242cf714d502ca6103ee84e08bc2ca4fd370d5ad4e7d06c7fbf496c6c7cc7eb19c40c61fb33df2a9ba48497a96c98d7b10c1f91098a6b7b16b4bab9687f27585ade1491ae0dba6a79e1e2d85dd9d9d45c5135ca5fca3f0f99a60ea39edbc9efc7923111c937913f225d67788d5f7e8852b697e26b92ec7bfcaa334a1665511c2b4c0a42d06f7ab98a9719516c8fd17f73804555ee84ab3b7d1762f6096b778d3cb9c799cbd49a9e4a325197b4e6cc4a5c4651f8b41ff88a92ec428354531f970263b467c77ed11312e2617d0d53fe9a8707f51f9f57a77bfb49afe3d89d85ec05ee17b9186f360c94ab8bb2926b65ca99dae1d6ee1af96cad09de70b6767e949023e4b380e66669914a741ed0fa420a48dbc7bfae5ef2019af36d1022283dd90655f25eec7151d471265d22a6d3f91dc700ba749bb67c0fe4bc0888593fbaf59d3c6fff1bf756a125910a63b9682b597c20f560ecb99c11a92c8c8c3f7fbfaa103146083a0ccaecf7a5f5e735a784a8820155914a289d57d8141870ffcaf588882332e0bcd8779efa931aa108dab6c3cce76691e345df4a91a03b71074d66333fd3591bff071ea099360f787bbe43b7b3dff2a59c41c7642eb79870222ad1c6f2e5a191ed5acea51134679587c9cf71c7d8ee290be6bf465c4ee47897a125708704ad610d8d00252d01959209d7cd04d5ecbbb1419a7e84037a55fefa13dee464b48a35c96bcb9a53e7ed461c3a1607ee00c3c302fd47cd73fda7493e947c9834a92d63dcfbd65aa7c38c3e3a2748bb5d9a58e7495d243d6b741078c8f7ee9c8813e473a323375702702b0afae1550c8341eedf5247627343a95240cb02e3e17d5dca16f8d8d3b2228e19c06399f8ec5c5e9dbe4caef6a0ea3ffb1d3c7eac03ae030e791fa12e537c80d56b55b764cadf27a8701052df1282ba8b5e3eb62b5dc7973ac40160e00722fa958d95102fc25c549d8c0e84bed95b7acb61ba65700c4de4feebf78d13b9682c52e937d23026fb4c6193e6644e2d3c99f91f4f39a8b9fc6d013f89c3793ef703987954dc0412b550652c01d922f525704d32d70d6d4079bc3551b563fb29577b3aecdc9505011701dddfd94830431e7a4918927ee44fb3831ce8c4513839e2deea1287f3fa1ab9b61a256c09637dbc7b4f0f8fbb783840f9c24526da883b0df0c473cf231656bd7bc1aaba7f321fec0971c8c2c3444bff2f55e1df7fea66ec3e440a612db9aa87bb505163a59e06b96d46f50d8120b92814ac5ab146bc78dbbf91065af26107815678ce6e33812e6bf3285d4ef3b7b04b076f21e7820dcbfdb4ad5218cf4ff6a65812d8fcb98ecc1e95e2fa58e3efe4ce26cd0bd400d6036ab2ad4f6c713082b5e3f1e04eb9e3b6c8f63f57953894b9e220e0130308e1fd91f72d398c1e7962ca2c31be83f31d6157633581a0a6910496de8d55d3d07090b6aa087159e388b7e7dec60f5d8a60d93ca2ae91296bd484d916bfaaa17c8f45ea4b1a91b37c82821199a2b7596672c37156d8701e7352aa48671d3b1bbbd2bd5f0a2268894a25b0cb2514af39c8743f8cce8ab4b523053739fd8a522222a09acf51ac704489cf17e4b7125455cb8f125b4d31af1eba1f8cf7f81a5a100a141a7ee72e8083e065616649c241f233645c5fc865d17f0285f5c52d9f45312c979bfb3ce5f2a1b951deddf280ffb3f370410cffd1583bfa90077835aa201a0712d1dcd1293ee177738b14e6b5e2a496d05220c3253bb6578d6aff774be91946a614dd7e879fb3dcf7451e0b9adb6a8c44f53c2c464bcc0019e9fad89cac7791a0a3f2974f759a9856351d4d2d7c5612c17cfc50f8479945df57716767b120a590f4bf656f4645029a525694d8a238446c5f5c2c1c995c09c1405b8b1eb9e0352ffdf766cc964f8dcf9f8f043dfab6d102cf4b298021abd78f1d9025fa1f8e1d710b38d9d1652f2d88d1305874ec41609b6617b65c5adb19b6295dc5c5da5fdf69f28144ea12f17c3c6fcce6b9b5157b3dfc969d6725fa5b098a4d9b1d31547ed4c9187452d281d0a5d456008caf1aa251fac8f950ca561982dc2dc908d3691ee3b6ad3ae3d22d002577264ca8e49c523bd51c4846be0d198ad9407bf6f7b82c79893eb2c05fe9981f687a97a4f01fe45ff8c8b7ecc551135cd960a0d6001ad35020be07ffb53cb9e731522ca8ae9364628914b9b8e8cc2f37f03393263603cc2b45295767eb0aac29b0930390eb89587ab2779d2e3decb8042acece725ba42eda650863f418f8d0d50d104e44fbbe5aa7389a4a144a8cecf00f45fb14c39112f9bfb56c0acbd44fa3ff261f5ce4acaa5134c2c1d0cca447040820c81ab1bcdc16aa075b7c68b10d06bbb7ce08b5b805e0238f24402cf24a4b4e00701935a0c68add3de090903f9b85b153cb179a582f57113bfc21c2093803f0cfa4d9d4672c2b05a24f7e4c34a8e9101b70303a7378b9c50b6cddd46814ef7fd73ef6923feceab8fc5aa8b0d185f2e83c7a99dcb1077c0ab5c1f5d5f01ba2f0420443f75c4417db9ebf1665efbb33dca224989920a64b44dc26f682cc77b4632c8454d49135e52503da855bc0f6ff8edc1145451a9772c06891f41064036b66c3119a0fc6e80dffeb65dc456108b7ca0296f4175fff3ed2b0f842cd46bd7e86f4c62dfaf1ddbf836263c00b34803de164983d0811cebfac86e7720c726d3048934c36c23189b02386a722ca9f0fe00233ab50db928d3bccea355cc681144b8b7edcaae4884d5a8f04425c0890ae2c74326e138066d8c05f4c82b29df99b034ea727afde590a1f2177ace3af99cfb1729d6539ce7f7f7314b046aab74497e63dd399e1f7d5f16517c23bd830d1fdee810f3c3b77573dd69c4b97d80d71fb5a632e00acdfa4f8e829faf3580d6a72c40b28a82172f8dcd4627663ebf6069736f21735fd84a226f427cd06bb055f94e7c92f31c48075a2955d82a5b9d2d0198ce0d4e131a112570a8ee40fb80462a81436a58e7db4e34b6e2c422e82f934ecda9949893da5730fc5c23c7c920f363f85ab28cc6a4206713c3152669b47efa8238fa826735f17b4e78750276162024ec85458cd5808e06f40dd9fd43775a456a3ff6cae90550d76d8b2899e0762ad9a371482b3e38083b1274708301d6346c22fea9bb4b73db490ff3ab05b2f7f9e187adef139a7794454b7300b8cc64d3ad76c0e4bc54e08833a4419251550655380d675bc91855aeb82585220bb97f03e976579c08f321b5f8f70988d3061f41465517d53ac571dbf1b24b94443d2e9a8e8a79b392b3d6a4ecdd7f626925c365ef6221305105ce9b5f5b6ecc5bed3d702bd4b7f5008aa8eb8c7aa3ade8ecf6251516fbefeea4e1082aa0e1848eddb31ffe44b04792d296054402826e4bLine truncated
|
||||
false,
|
||||
"d4e3f18ac2e2095edb5c3b94236118ad",
|
||||
"4faa6c4233d9fd53d170ede4172142a8",
|
||||
"23f154ac43cfc59c4243e9fc68aeec8f19ad3942d74108e833b36f0dd3dcd357",
|
||||
"8da7de6ea7bf2a81a396a42880ba1f5756734c4821309ac9aeffa2a26ce86873b9dc4935a772de6ec5162c6d075b14536800fb174841153511bfb597e992e2fe8a450c4bce102cc550bb37fd564c4d60bf884e",
|
||||
"");
|
||||
TestBIP324PacketVector(
|
||||
223,
|
||||
"6c77432d1fda31e9f942f8af44607e10f3ad38a65f8a4bddae823e5eff90dc38",
|
||||
"d2685070c1e6376e633e825296634fd461fa9e5bdf2109bcebd735e5a91f3e587c5cb782abb797fbf6bb5074fd1542a474f2a45b673763ec2db7fb99b737bbb9",
|
||||
"56bd0c06f10352c3a1a9f4b4c92f6fa2b26df124b57878353c1fc691c51abea77c8817daeeb9fa546b77c8daf79d89b22b0e1b87574ece42371f00237aa9d83a",
|
||||
false,
|
||||
"7e0e78eb6990b059e6cf0ded66ea93ef82e72aa2f18ac24f2fc6ebab561ae557420729da103f64cecfa20527e15f9fb669a49bbbf274ef0389b3e43c8c44e5f60bf2ac38e2b55e7ec4273dba15ba41d21f8f5b3ee1688b3c29951218caf847a97fb50d75a86515d445699497d968164bf740012679b8962de573be941c62b7ef",
|
||||
1,
|
||||
"",
|
||||
true,
|
||||
"cf2e25f23501399f30738d7eee652b90",
|
||||
"225a477a28a54ea7671d2b217a9c29db",
|
||||
"7ec02fea8c1484e3d0875f978c5f36d63545e2e4acf56311394422f4b66af612",
|
||||
"",
|
||||
"729847a3e9eba7a5bff454b5de3b393431ee360736b6c030d7a5bd01d1203d2e98f528543fd2bf886ccaa1ada5e215a730a36b3f4abfc4e252c89eb01d9512f94916dae8a76bf16e4da28986ffe159090fe5267ee3394300b7ccf4dfad389a26321b3a3423e4594a82ccfbad16d6561ecb8772b0cb040280ff999a29e3d9d4fd");
|
||||
TestBIP324PacketVector(
|
||||
448,
|
||||
"a6ec25127ca1aa4cf16b20084ba1e6516baae4d32422288e9b36d8bddd2de35a",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff053d7ecca53e33e185a8b9be4e7699a97c6ff4c795522e5918ab7cd6b6884f67e683f3dc",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffa7730be30000000000000000000000000000000000000000000000000000000000000000",
|
||||
true,
|
||||
"00cf68f8f7ac49ffaa02c4864fdf6dfe7bbf2c740b88d98c50ebafe32c92f3427f57601ffcb21a3435979287db8fee6c302926741f9d5e464c647eeb9b7acaeda46e00abd7506fc9a719847e9a7328215801e96198dac141a15c7c2f68e0690dd1176292a0dded04d1f548aad88f1aebdc0a8f87da4bb22df32dd7c160c225b843e83f6525d6d484f502f16d923124fc538794e21da2eb689d18d87406ecced5b9f92137239ed1d37bcfa7836641a83cf5e0a1cf63f51b06f158e499a459ede41c",
|
||||
1,
|
||||
"",
|
||||
false,
|
||||
"fead69be77825a23daec377c362aa560",
|
||||
"511d4980526c5e64aa7187462faeafdd",
|
||||
"acb8f084ea763ddd1b92ac4ed23bf44de20b84ab677d4e4e6666a6090d40353d",
|
||||
"",
|
||||
"77b4656934a82de1a593d8481f020194ddafd8cac441f9d72aeb8721e6a14f49698ca6d9b2b6d59d07a01aa552fd4d5b68d0d1617574c77dea10bfadbaa31b83885b7ceac2fd45e3e4a331c51a74e7b1698d81b64c87c73c5b9258b4d83297f9debc2e9aa07f8572ff434dc792b83ecf07b3197de8dc9cf7be56acb59c66cff5");
|
||||
TestBIP324PacketVector(
|
||||
673,
|
||||
"0af952659ed76f80f585966b95ab6e6fd68654672827878684c8b547b1b94f5a",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc81017fd92fd31637c26c906b42092e11cc0d3afae8d9019d2578af22735ce7bc469c72d",
|
||||
"9652d78baefc028cd37a6a92625b8b8f85fde1e4c944ad3f20e198bef8c02f19fffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e91870",
|
||||
false,
|
||||
"5c6272ee55da855bbbf7b1246d9885aa7aa601a715ab86fa46c50da533badf82b97597c968293ae04e",
|
||||
97561,
|
||||
"",
|
||||
false,
|
||||
"5e2375ac629b8df1e4ff3617c6255a70",
|
||||
"70bcbffcb62e4d29d2605d30bceef137",
|
||||
"7332e92a3f9d2792c4d444fac5ed888c39a073043a65eefb626318fd649328f8",
|
||||
"",
|
||||
"657a4a19711ce593c3844cb391b224f60124aba7e04266233bc50cafb971e26c7716b76e98376448f7d214dd11e629ef9a974d60e3770a695810a61c4ba66d78b936ee7892b98f0b48ddae9fcd8b599dca1c9b43e9b95e0226cf8d4459b8a7c2c4e6db80f1d58c7b20dd7208fa5c1057fb78734223ee801dbd851db601fee61e");
|
||||
TestBIP324PacketVector(
|
||||
1024,
|
||||
"f90e080c64b05824c5a24b2501d5aeaf08af3872ee860aa80bdcd430f7b63494",
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff115173765dc202cf029ad3f15479735d57697af12b0131dd21430d5772e4ef11474d58b9",
|
||||
"12a50f3fafea7c1eeada4cf8d33777704b77361453afc83bda91eef349ae044d20126c6200547ea5a6911776c05dee2a7f1a9ba7dfbabbbd273c3ef29ef46e46",
|
||||
true,
|
||||
"5f67d15d22ca9b2804eeab0a66f7f8e3a10fa5de5809a046084348cbc5304e843ef96f59a59c7d7fdfe5946489f3ea297d941bac326225df316a25fc90f0e65b0d31a9c497e960fdbf8c482516bc8a9c1c77b7f6d0e1143810c737f76f9224e6f2c9af5186b4f7259c7e8d165b6e4fe3d38a60bdbdd4d06ecdcaaf62086070dbb68686b802d53dfd7db14b18743832605f5461ad81e2af4b7e8ff0eff0867a25b93cec7becf15c43131895fed09a83bf1ee4a87d44dd0f02a837bf5a1232e201cb882734eb9643dc2dc4d4e8b5690840766212c7ac8f38ad8a9ec47c7a9b3e022ae3eb6a32522128b518bd0d0085dd81c5",
|
||||
69615,
|
||||
"",
|
||||
true,
|
||||
"b709dea25e0be287c50e3603482c2e98",
|
||||
"1f677e9d7392ebe3633fd82c9efb0f16",
|
||||
"889f339285564fd868401fac8380bb9887925122ec8f31c8ae51ce067def103b",
|
||||
"",
|
||||
"7c4b9e1e6c1ce69da7b01513cdc4588fd93b04dafefaf87f31561763d906c672bac3dfceb751ebd126728ac017d4d580e931b8e5c7d5dfe0123be4dc9b2d2238b655c8a7fadaf8082c31e310909b5b731efc12f0a56e849eae6bfeedcc86dd27ef9b91d159256aa8e8d2b71a311f73350863d70f18d0d7302cf551e4303c7733");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user