Skip to content

Commit

Permalink
HKDF key derivation from ECDH secret for BIP324
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruv committed Mar 8, 2022
1 parent ba1cb38 commit 0573e14
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 0 deletions.
34 changes: 34 additions & 0 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <clientversion.h>
#include <compat.h>
#include <consensus/consensus.h>
#include <crypto/hkdf_sha256_32.h>
#include <crypto/sha256.h>
#include <fs.h>
#include <i2p.h>
Expand All @@ -25,6 +26,7 @@
#include <protocol.h>
#include <random.h>
#include <scheduler.h>
#include <support/cleanse.h>
#include <util/sock.h>
#include <util/strencodings.h>
#include <util/syscall_sandbox.h>
Expand Down Expand Up @@ -436,6 +438,38 @@ static CAddress GetBindAddress(SOCKET sock)
return addr_bind;
}

bool DeriveBIP324Keys(ECDHSecret&& ecdh_secret, const Span<uint8_t> initiator_hdata, const Span<uint8_t> responder_hdata, BIP324Keys& derived_keys)
{
if (ecdh_secret.size() != ECDH_SECRET_SIZE) {
return false;
}

std::string salt{"bitcoin_v2_shared_secret"};
salt += std::string{reinterpret_cast<const char*>(initiator_hdata.data()), initiator_hdata.size()};
salt += std::string{reinterpret_cast<const char*>(responder_hdata.data()), responder_hdata.size()};
salt += std::string{reinterpret_cast<const char*>(Params().MessageStart()), CMessageHeader::MESSAGE_START_SIZE};

CHKDF_HMAC_SHA256_L32 hkdf(ecdh_secret.data(), ecdh_secret.size(), salt);

derived_keys.initiator_F.resize(BIP324_KEY_LEN);
hkdf.Expand32("initiator_F", derived_keys.initiator_F.data());

derived_keys.initiator_V.resize(BIP324_KEY_LEN);
hkdf.Expand32("initiator_V", derived_keys.initiator_V.data());

derived_keys.responder_F.resize(BIP324_KEY_LEN);
hkdf.Expand32("responder_F", derived_keys.responder_F.data());

derived_keys.responder_V.resize(BIP324_KEY_LEN);
hkdf.Expand32("responder_V", derived_keys.responder_V.data());

derived_keys.session_id.resize(BIP324_KEY_LEN);
hkdf.Expand32("session_id", derived_keys.session_id.data());

memory_cleanse(ecdh_secret.data(), ecdh_secret.size());
return true;
}

CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type)
{
assert(conn_type != ConnectionType::INBOUND);
Expand Down
17 changes: 17 additions & 0 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <crypto/siphash.h>
#include <hash.h>
#include <i2p.h>
#include <key.h>
#include <net_permissions.h>
#include <netaddress.h>
#include <netbase.h>
Expand All @@ -21,6 +22,7 @@
#include <random.h>
#include <span.h>
#include <streams.h>
#include <support/allocators/secure.h>
#include <sync.h>
#include <threadinterrupt.h>
#include <uint256.h>
Expand Down Expand Up @@ -399,6 +401,21 @@ class V1TransportSerializer : public TransportSerializer {
void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) override;
};

constexpr size_t BIP324_KEY_LEN = 32;

using BIP324Key = std::vector<uint8_t, secure_allocator<uint8_t>>;

struct BIP324Keys {
BIP324Key initiator_F;
BIP324Key initiator_V;
BIP324Key responder_F;
BIP324Key responder_V;
BIP324Key session_id;
};

// Returns false if the ecdh_secret is malformed.
bool DeriveBIP324Keys(ECDHSecret&& ecdh_secret, const Span<uint8_t> initiator_hdata, const Span<uint8_t> responder_hdata, BIP324Keys& derived_keys);

/** Information about a peer */
class CNode
{
Expand Down
66 changes: 66 additions & 0 deletions src/test/net_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <clientversion.h>
#include <compat.h>
#include <cstdint>
#include <key.h>
#include <key_io.h>
#include <net.h>
#include <net_processing.h>
#include <netaddress.h>
Expand Down Expand Up @@ -909,4 +911,68 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
TestOnlyResetTimeData();
}

BOOST_AUTO_TEST_CASE(bip324_derivation_test)
{
static auto initiator_privkey_bytes = ParseHex("9cdfc7df74056ddebee98e3310026ecb11578cad9c5d09457194cc2162a1973b");
static auto responder_privkey_bytes = ParseHex("2030aaaf44a1437c07c938aa33c58751a6aee0c0e48e285f8031b137f498921d");
static const std::string initiator_hdata = "2deb41da6887640dda029ae41c9c9958881d0bb8e28f6bb9039ee9b7bb11091d62f4cbe65cc418df7aefd738f4d3e926c66365b4d38eefd0a883be64112f4495";
static const std::string responder_hdata = "4c469c70ba242ae0fc98d4eff6258cf19ecab96611c9c736356a4cf11d66edfa4d2970e56744a6d071861a4cbe2730eb7733a38b166e3df73450ef37112dd32f";

CKey initiator_key;
initiator_key.Set(initiator_privkey_bytes.begin(), initiator_privkey_bytes.end(), false);
CKey responder_key;
responder_key.Set(responder_privkey_bytes.begin(), responder_privkey_bytes.end(), false);

auto initiator_pubkey = initiator_key.GetPubKey();
auto responder_pubkey = responder_key.GetPubKey();

ECDHSecret initiator_secret, responder_secret;
BOOST_CHECK(initiator_key.ComputeECDHSecret(responder_pubkey, initiator_secret));
BOOST_CHECK(responder_key.ComputeECDHSecret(initiator_pubkey, responder_secret));

BOOST_CHECK_EQUAL(ECDH_SECRET_SIZE, initiator_secret.size());
BOOST_CHECK_EQUAL(ECDH_SECRET_SIZE, responder_secret.size());
BOOST_CHECK_EQUAL(0, memcmp(initiator_secret.data(), responder_secret.data(), ECDH_SECRET_SIZE));

auto initiator_hdata_vec = ParseHex(initiator_hdata);
Span<uint8_t> initiator_hdata_span{initiator_hdata_vec.data(), initiator_hdata_vec.size()};

auto responder_hdata_vec = ParseHex(responder_hdata);
Span<uint8_t> responder_hdata_span{responder_hdata_vec.data(), responder_hdata_vec.size()};

BIP324Keys initiator_keys, responder_keys;

BOOST_CHECK(DeriveBIP324Keys(std::move(initiator_secret), initiator_hdata_span, responder_hdata_span, initiator_keys));
BOOST_CHECK(DeriveBIP324Keys(std::move(responder_secret), initiator_hdata_span, responder_hdata_span, responder_keys));

// Make sure that the ephemeral ECDH secret is cleansed from memory once the keys are derived.
BOOST_CHECK_EQUAL("0000000000000000000000000000000000000000000000000000000000000000", HexStr(initiator_secret));
BOOST_CHECK_EQUAL("0000000000000000000000000000000000000000000000000000000000000000", HexStr(responder_secret));

BOOST_CHECK_EQUAL(BIP324_KEY_LEN, initiator_keys.initiator_F.size());
BOOST_CHECK_EQUAL(initiator_keys.initiator_F.size(), responder_keys.initiator_F.size());
BOOST_CHECK_EQUAL(0, memcmp(initiator_keys.initiator_F.data(), responder_keys.initiator_F.data(), initiator_keys.initiator_F.size()));
BOOST_CHECK_EQUAL("86deac55ec0654215a35a171a6a988ab46f89702925f1f5ae37b6aa9a6d871d0", HexStr(Span{initiator_keys.initiator_F}));

BOOST_CHECK_EQUAL(BIP324_KEY_LEN, initiator_keys.initiator_V.size());
BOOST_CHECK_EQUAL(initiator_keys.initiator_V.size(), responder_keys.initiator_V.size());
BOOST_CHECK_EQUAL(0, memcmp(initiator_keys.initiator_V.data(), responder_keys.initiator_V.data(), initiator_keys.initiator_V.size()));
BOOST_CHECK_EQUAL("a6a683fa9d39d8585076ca47c2db0b9a6191ac9c687e2ae6bafc9d98b5d873be", HexStr(Span{initiator_keys.initiator_V}));

BOOST_CHECK_EQUAL(BIP324_KEY_LEN, initiator_keys.responder_F.size());
BOOST_CHECK_EQUAL(initiator_keys.responder_F.size(), responder_keys.responder_F.size());
BOOST_CHECK_EQUAL(0, memcmp(initiator_keys.responder_F.data(), responder_keys.responder_F.data(), initiator_keys.responder_F.size()));
BOOST_CHECK_EQUAL("191902192be123c45e33e218d6eb42e4e66043e4e3ae38cbeb149d15cdb0b29b", HexStr(Span{initiator_keys.responder_F}));

BOOST_CHECK_EQUAL(BIP324_KEY_LEN, initiator_keys.responder_V.size());
BOOST_CHECK_EQUAL(initiator_keys.responder_V.size(), responder_keys.responder_V.size());
BOOST_CHECK_EQUAL(0, memcmp(initiator_keys.responder_V.data(), responder_keys.responder_V.data(), initiator_keys.responder_V.size()));
BOOST_CHECK_EQUAL("5968ffa11fd13874dec97871809f1133a83d0ab6a4e81ef96753b7fe79ea61e2", HexStr(Span{initiator_keys.responder_V}));

BOOST_CHECK_EQUAL(BIP324_KEY_LEN, initiator_keys.session_id.size());
BOOST_CHECK_EQUAL(initiator_keys.session_id.size(), responder_keys.session_id.size());
BOOST_CHECK_EQUAL(0, memcmp(initiator_keys.session_id.data(), responder_keys.session_id.data(), initiator_keys.session_id.size()));
BOOST_CHECK_EQUAL("66ebac8c6de2a5793a6b56195b650c850eb8aa79623c0b9894cc0ef33c919f85", HexStr(Span{initiator_keys.session_id}));
}

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 0573e14

Please sign in to comment.