Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#23561 CI (x-only ECDH) #8

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1957,7 +1957,7 @@ LIBS_TEMP="$LIBS"
unset LIBS
LIBS="$LIBS_TEMP"

ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-schnorrsig"
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-schnorrsig --enable-module-ecdh -enable-experimental"
AC_CONFIG_SUBDIRS([src/secp256k1])

AC_OUTPUT
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.bench.include
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ bench_bench_bitcoin_SOURCES = \
bench/data.cpp \
bench/data.h \
bench/duplicate_inputs.cpp \
bench/ecdh.cpp \
bench/examples.cpp \
bench/gcs_filter.cpp \
bench/hashpadding.cpp \
Expand Down
44 changes: 44 additions & 0 deletions src/bench/ecdh.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2022 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 <bench/bench.h>

#include <key.h>
#include <pubkey.h>
#include <random.h>

#include <vector>

CKey GetRandomKey()
{
CKey key;
key.MakeNewKey(true);
return key;
}

static void ECDH(benchmark::Bench& bench)
{
ECC_Start();
auto privkey = GetRandomKey();
auto other_privkey = GetRandomKey();
auto other_pubkey = other_privkey.GetPubKey();

std::vector<uint8_t> ellsq_bytes;
ellsq_bytes.resize(64);
GetRandBytes(ellsq_bytes.data(), 32);
GetRandBytes(ellsq_bytes.data() + 32, 32);

std::vector<uint8_t> other_ellsq_bytes;
other_ellsq_bytes.resize(64);
GetRandBytes(other_ellsq_bytes.data(), 32);
GetRandBytes(other_ellsq_bytes.data() + 32, 32);

ECDHSecret ecdh_secret;
bench.batch(1).unit("ecdh").run([&] {
privkey.ComputeBIP324ECDHSecret(other_pubkey, ellsq_bytes, other_ellsq_bytes, ecdh_secret);
});
ECC_Stop();
}

BENCHMARK(ECDH);
36 changes: 36 additions & 0 deletions src/key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
#include <crypto/hmac_sha512.h>
#include <hash.h>
#include <random.h>
#include <support/cleanse.h>

#include <secp256k1.h>
#include <secp256k1_ecdh.h>
#include <secp256k1_extrakeys.h>
#include <secp256k1_recovery.h>
#include <secp256k1_schnorrsig.h>
Expand Down Expand Up @@ -332,6 +334,40 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const
return ret;
}

// Returns just the x-coordinate in big endian
static int bip324_ecdh_hash(unsigned char* output, const unsigned char* x32, const unsigned char* y32, void* data)
{
memcpy(output, x32, 32);
return 1;
}

bool CKey::ComputeBIP324ECDHSecret(const CPubKey& pubkey, const Span<uint8_t> initiator_hdata, const Span<uint8_t> responder_hdata,
ECDHSecret& secret) const
{
secp256k1_pubkey pubkey_internal;
if (!secp256k1_ec_pubkey_parse(secp256k1_context_sign, &pubkey_internal, pubkey.data(), pubkey.size())) {
return false;
}

std::vector<uint8_t, secure_allocator<uint8_t>> xonly_ecdh;
xonly_ecdh.resize(ECDH_SECRET_SIZE);
assert(secp256k1_ecdh(secp256k1_context_sign, xonly_ecdh.data(), &pubkey_internal,
keydata.data(), bip324_ecdh_hash, NULL));

CSHA256 hasher;
hasher.Write(BIP324_ECDH_TAG.data(), BIP324_ECDH_TAG.size());
hasher.Write(BIP324_ECDH_TAG.data(), BIP324_ECDH_TAG.size());
hasher.Write(initiator_hdata.data(), initiator_hdata.size());
hasher.Write(responder_hdata.data(), responder_hdata.size());

hasher.Write(xonly_ecdh.data(), xonly_ecdh.size());

secret.resize(ECDH_SECRET_SIZE);
hasher.Finalize(secret.data());
memory_cleanse(xonly_ecdh.data(), ECDH_SECRET_SIZE);
return true;
}

bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const {
out.nDepth = nDepth + 1;
CKeyID id = key.GetPubKey().GetID();
Expand Down
15 changes: 15 additions & 0 deletions src/key.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
#ifndef BITCOIN_KEY_H
#define BITCOIN_KEY_H

#include <crypto/sha256.h>
#include <pubkey.h>
#include <serialize.h>
#include <span.h>
#include <support/allocators/secure.h>
#include <uint256.h>
#include <util/strencodings.h>

#include <stdexcept>
#include <vector>
Expand All @@ -22,6 +25,14 @@
*/
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;

constexpr static size_t ECDH_SECRET_SIZE = CSHA256::OUTPUT_SIZE;

// SHA256("secp256k1_ellsq_xonly_ecdh") proven with a unit test (xonly_ecdh_tag)
const static auto BIP324_ECDH_TAG = ParseHex("4dbebba5394890bf7370e9ca2ca6112941b974c56950e5c4fef7f00a2bbf3e55");

// Used to represent a ECDH secret (ECDH_SECRET_SIZE bytes)
using ECDHSecret = std::vector<uint8_t, secure_allocator<uint8_t>>;

/** An encapsulated private key. */
class CKey
{
Expand Down Expand Up @@ -156,6 +167,10 @@ class CKey

//! Load private key and check that public key matches.
bool Load(const CPrivKey& privkey, const CPubKey& vchPubKey, bool fSkipCheck);

// Returns false if an invalid public key is provided
bool ComputeBIP324ECDHSecret(const CPubKey& pubkey, const Span<uint8_t> initiator_hdata, const Span<uint8_t> responder_hdata,
ECDHSecret& secret) const;
};

struct CExtKey {
Expand Down
32 changes: 32 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,36 @@ 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*>(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 <logging.h>
#include <net_permissions.h>
#include <netaddress.h>
Expand All @@ -22,6 +23,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 @@ -406,6 +408,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
38 changes: 38 additions & 0 deletions src/test/fuzz/key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <script/signingprovider.h>
#include <script/standard.h>
#include <streams.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <util/strencodings.h>

Expand Down Expand Up @@ -306,3 +307,40 @@ FUZZ_TARGET_INIT(key, initialize_key)
}
}
}

FUZZ_TARGET_INIT(ecdh, initialize_key)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
auto rnd32 = fuzzed_data_provider.ConsumeBytes<uint8_t>(32);
rnd32.resize(32);
CKey k1;
k1.Set(rnd32.begin(), rnd32.end(), true);

if (!k1.IsValid()) {
return;
}

rnd32 = fuzzed_data_provider.ConsumeBytes<uint8_t>(32);
rnd32.resize(32);
CKey k2;
k2.Set(rnd32.begin(), rnd32.end(), true);

if (!k2.IsValid()) {
return;
}

auto k1_ellsq = fuzzed_data_provider.ConsumeBytes<uint8_t>(64);
k1_ellsq.resize(64);

auto k2_ellsq = fuzzed_data_provider.ConsumeBytes<uint8_t>(64);
k2_ellsq.resize(64);

CPubKey k1_pubkey = k1.GetPubKey();
CPubKey k2_pubkey = k2.GetPubKey();
ECDHSecret ecdh_secret_1, ecdh_secret_2;
assert(k1.ComputeBIP324ECDHSecret(k2_pubkey, k1_ellsq, k2_ellsq, ecdh_secret_1));
assert(k2.ComputeBIP324ECDHSecret(k1_pubkey, k1_ellsq, k2_ellsq, ecdh_secret_2));
assert(ecdh_secret_1.size() == ECDH_SECRET_SIZE);
assert(ecdh_secret_2.size() == ECDH_SECRET_SIZE);
assert(memcmp(ecdh_secret_1.data(), ecdh_secret_2.data(), ECDH_SECRET_SIZE) == 0);
}
33 changes: 33 additions & 0 deletions src/test/fuzz/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <chainparams.h>
#include <chainparamsbase.h>
#include <key.h>
#include <net.h>
#include <net_permissions.h>
#include <netaddress.h>
Expand Down Expand Up @@ -77,3 +78,35 @@ FUZZ_TARGET_INIT(net, initialize_net)
(void)node.HasPermission(net_permission_flags);
(void)node.ConnectedThroughNetwork();
}

void initialize_chainparams()
{
SelectParams(CBaseChainParams::REGTEST);
}

FUZZ_TARGET_INIT(bip324, initialize_chainparams)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};

ECDHSecret ecdh_secret;
ecdh_secret.resize(ECDH_SECRET_SIZE);
auto ecdh_secret_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(ECDH_SECRET_SIZE);
ecdh_secret_bytes.resize(ECDH_SECRET_SIZE);

memcpy(ecdh_secret.data(), ecdh_secret_bytes.data(), ECDH_SECRET_SIZE);

auto initiator_hdata_len = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096);
auto initiator_hdata = fuzzed_data_provider.ConsumeBytes<uint8_t>(initiator_hdata_len);

auto responder_hdata_len = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096);
auto responder_hdata = fuzzed_data_provider.ConsumeBytes<uint8_t>(responder_hdata_len);

BIP324Keys keys;
assert(DeriveBIP324Keys(std::move(ecdh_secret), initiator_hdata, responder_hdata, keys));
assert(keys.initiator_F.size() == BIP324_KEY_LEN);
assert(keys.initiator_V.size() == BIP324_KEY_LEN);
assert(keys.responder_F.size() == BIP324_KEY_LEN);
assert(keys.responder_V.size() == BIP324_KEY_LEN);
assert(keys.session_id.size() == BIP324_KEY_LEN);
assert("0000000000000000000000000000000000000000000000000000000000000000" == HexStr(ecdh_secret));
}
43 changes: 43 additions & 0 deletions src/test/key_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <key.h>

#include <key_io.h>
#include <span.h>
#include <streams.h>
#include <test/util/setup_common.h>
#include <uint256.h>
Expand Down Expand Up @@ -344,4 +345,46 @@ BOOST_AUTO_TEST_CASE(bip340_test_vectors)
}
}

BOOST_AUTO_TEST_CASE(bip324_ecdh)
{
CKey initiator_key = DecodeSecret(strSecret1);
CKey responder_key = DecodeSecret(strSecret2C);

ECDHSecret initiator_secret, responder_secret;
auto initiator_hdata_vec = ParseHex("2deb41da6887640dda029ae41c9c9958881d0bb8e28f6bb9039ee9b7bb11091d62f4cbe65cc418df7aefd738f4d3e96c66365b4d38eefd0a883be64112f4495");
auto responder_hdata_vec = ParseHex("4c469c70ba242ae0fc98d4eff6258cf19ecab96611c9c736356a4cf11d66edfa4d2970e56744a6d071861a4cbe2730eb7733a38b166e3df73450ef37112dd32f");

Span<uint8_t> initiator_hdata{initiator_hdata_vec.data(), initiator_hdata_vec.size()};
Span<uint8_t> responder_hdata{responder_hdata_vec.data(), responder_hdata_vec.size()};

BOOST_CHECK(initiator_key.ComputeBIP324ECDHSecret(responder_key.GetPubKey(), initiator_hdata, responder_hdata, initiator_secret));
BOOST_CHECK(responder_key.ComputeBIP324ECDHSecret(initiator_key.GetPubKey(), initiator_hdata, responder_hdata, responder_secret));
BOOST_CHECK_EQUAL(initiator_secret.size(), ECDH_SECRET_SIZE);
BOOST_CHECK_EQUAL(responder_secret.size(), ECDH_SECRET_SIZE);
BOOST_CHECK_EQUAL(0, memcmp(initiator_secret.data(), responder_secret.data(), ECDH_SECRET_SIZE));
BOOST_CHECK_EQUAL("41562adf8c3d56d1682fe7c3ca2d1a07de177bfec072478ac6267753495597bf", HexStr(initiator_secret));
BOOST_CHECK_EQUAL("41562adf8c3d56d1682fe7c3ca2d1a07de177bfec072478ac6267753495597bf", HexStr(responder_secret));

// ECDH computation with invalid pubkey
std::vector<unsigned char> pubkeydata;
auto responder_pubkey = responder_key.GetPubKey();
pubkeydata.insert(pubkeydata.end(), responder_pubkey.begin(), responder_pubkey.end());
pubkeydata[0] = 0xFF;
CPubKey invalid_responder_pubkey(pubkeydata);
BOOST_CHECK(!initiator_key.ComputeBIP324ECDHSecret(invalid_responder_pubkey, initiator_hdata, responder_hdata, initiator_secret));
}


BOOST_AUTO_TEST_CASE(bip324_xonly_ecdh_tag)
{
CSHA256 hasher;
static std::string tag = "secp256k1_ellsq_xonly_ecdh";
uint8_t tag_hash[CSHA256::OUTPUT_SIZE];
hasher.Write(reinterpret_cast<unsigned char*>(tag.data()), tag.size());
hasher.Finalize(tag_hash);

BOOST_CHECK_EQUAL(32, BIP324_ECDH_TAG.size());
BOOST_CHECK_EQUAL(memcmp(tag_hash, BIP324_ECDH_TAG.data(), CSHA256::OUTPUT_SIZE), 0);
}

BOOST_AUTO_TEST_SUITE_END()
Loading