diff --git a/build_msvc/libsecp256k1/libsecp256k1.vcxproj b/build_msvc/libsecp256k1/libsecp256k1.vcxproj index 5fe1599c83..1d7766983f 100644 --- a/build_msvc/libsecp256k1/libsecp256k1.vcxproj +++ b/build_msvc/libsecp256k1/libsecp256k1.vcxproj @@ -14,7 +14,7 @@ - ENABLE_MODULE_GENERATOR;ENABLE_MODULE_RANGEPROOF;ENABLE_MODULE_SURJECTIONPROOF;ECMULT_WINDOW_SIZE=15;ECMULT_GEN_PREC_BITS=8;ENABLE_MODULE_WHITELIST;ENABLE_MODULE_ECDH;ENABLE_MODULE_ECDSA_ADAPTOR;ENABLE_MODULE_ECDSA_S2C;ENABLE_MODULE_RECOVERY;ENABLE_MODULE_EXTRAKEYS;ENABLE_MODULE_SCHNORRSIG;%(PreprocessorDefinitions) + ENABLE_MODULE_GENERATOR;ENABLE_MODULE_RANGEPROOF;ENABLE_MODULE_SURJECTIONPROOF;ECMULT_WINDOW_SIZE=15;ECMULT_GEN_PREC_BITS=8;ENABLE_MODULE_WHITELIST;ENABLE_MODULE_ECDH;ENABLE_MODULE_ECDSA_ADAPTOR;ENABLE_MODULE_ECDSA_S2C;ENABLE_MODULE_RECOVERY;ENABLE_MODULE_EXTRAKEYS;ENABLE_MODULE_SCHNORRSIG;ENABLE_MODULE_ELLSWIFT;%(PreprocessorDefinitions) USE_ASM_X86_64;%(UndefinePreprocessorDefinitions) ..\..\src\secp256k1;%(AdditionalIncludeDirectories) 4005;4146;4244;4267;4334 diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 5fa94d78be..8fba80cb87 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -18,6 +18,7 @@ bench_bench_bitcoin_SOURCES = \ bench/bench.cpp \ bench/bench.h \ bench/bench_bitcoin.cpp \ + bench/bip324_ecdh.cpp \ bench/block_assemble.cpp \ bench/ccoins_caching.cpp \ bench/chacha20.cpp \ @@ -29,6 +30,7 @@ bench_bench_bitcoin_SOURCES = \ bench/data.h \ bench/descriptors.cpp \ bench/duplicate_inputs.cpp \ + bench/ellswift.cpp \ bench/examples.cpp \ bench/gcs_filter.cpp \ bench/hashpadding.cpp \ diff --git a/src/bench/bip324_ecdh.cpp b/src/bench/bip324_ecdh.cpp new file mode 100644 index 0000000000..659da0f08e --- /dev/null +++ b/src/bench/bip324_ecdh.cpp @@ -0,0 +1,51 @@ +// 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 + +#include +#include +#include +#include + +#include +#include + +static void BIP324_ECDH(benchmark::Bench& bench) +{ + ECC_Start(); + FastRandomContext rng; + + std::array key_data; + std::array our_ellswift_data; + std::array their_ellswift_data; + + rng.fillrand(key_data); + rng.fillrand(our_ellswift_data); + rng.fillrand(their_ellswift_data); + + bench.batch(1).unit("ecdh").run([&] { + CKey key; + key.Set(UCharCast(key_data.data()), UCharCast(key_data.data()) + 32, true); + EllSwiftPubKey our_ellswift(our_ellswift_data); + EllSwiftPubKey their_ellswift(their_ellswift_data); + + auto ret = key.ComputeBIP324ECDHSecret(their_ellswift, our_ellswift, true); + + // To make sure that the computation is not the same on every iteration (ellswift decoding + // is variable-time), distribute bytes from the shared secret over the 3 inputs. The most + // important one is their_ellswift, because that one is actually decoded, so it's given most + // bytes. The data is copied into the middle, so that both halves are affected: + // - Copy 8 bytes from the resulting shared secret into middle of the private key. + std::copy(ret.begin(), ret.begin() + 8, key_data.begin() + 12); + // - Copy 8 bytes from the resulting shared secret into the middle of our ellswift key. + std::copy(ret.begin() + 8, ret.begin() + 16, our_ellswift_data.begin() + 28); + // - Copy 16 bytes from the resulting shared secret into the middle of their ellswift key. + std::copy(ret.begin() + 16, ret.end(), their_ellswift_data.begin() + 24); + }); + + ECC_Stop(); +} + +BENCHMARK(BIP324_ECDH, benchmark::PriorityLevel::HIGH); diff --git a/src/bench/ellswift.cpp b/src/bench/ellswift.cpp new file mode 100644 index 0000000000..75729e170c --- /dev/null +++ b/src/bench/ellswift.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2022-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 + +#include +#include + +static void EllSwiftCreate(benchmark::Bench& bench) +{ + ECC_Start(); + + CKey key; + key.MakeNewKey(true); + + uint256 entropy = GetRandHash(); + + bench.batch(1).unit("pubkey").run([&] { + auto ret = key.EllSwiftCreate(AsBytes(Span{entropy})); + /* Use the first 32 bytes of the ellswift encoded public key as next private key. */ + key.Set(UCharCast(ret.data()), UCharCast(ret.data()) + 32, true); + assert(key.IsValid()); + /* Use the last 32 bytes of the ellswift encoded public key as next entropy. */ + std::copy(ret.begin() + 32, ret.begin() + 64, AsBytePtr(entropy.data())); + }); + + ECC_Stop(); +} + +BENCHMARK(EllSwiftCreate, benchmark::PriorityLevel::HIGH); diff --git a/src/key.cpp b/src/key.cpp index c9d18c6384..ae2e7221dd 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -345,6 +346,42 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const return ret; } +EllSwiftPubKey CKey::EllSwiftCreate(Span ent32) const +{ + assert(fValid); + assert(ent32.size() == 32); + std::array encoded_pubkey; + + auto success = secp256k1_ellswift_create(secp256k1_context_sign, + UCharCast(encoded_pubkey.data()), + keydata.data(), + UCharCast(ent32.data())); + + // Should always succeed for valid keys (asserted above). + assert(success); + return {encoded_pubkey}; +} + +ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, const EllSwiftPubKey& our_ellswift, bool initiating) const +{ + assert(fValid); + + ECDHSecret output; + // BIP324 uses the initiator as party A, and the responder as party B. Remap the inputs + // accordingly: + bool success = secp256k1_ellswift_xdh(secp256k1_context_sign, + UCharCast(output.data()), + UCharCast(initiating ? our_ellswift.data() : their_ellswift.data()), + UCharCast(initiating ? their_ellswift.data() : our_ellswift.data()), + keydata.data(), + initiating ? 0 : 1, + secp256k1_ellswift_xdh_hash_function_bip324, + nullptr); + // Should always succeed for valid keys (assert above). + assert(success); + return output; +} + bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const { if (nDepth == std::numeric_limits::max()) return false; out.nDepth = nDepth + 1; diff --git a/src/key.h b/src/key.h index 157bc7b50d..235836c915 100644 --- a/src/key.h +++ b/src/key.h @@ -22,6 +22,12 @@ */ typedef std::vector > CPrivKey; +/** Size of ECDH shared secrets. */ +constexpr static size_t ECDH_SECRET_SIZE = CSHA256::OUTPUT_SIZE; + +// Used to represent ECDH shared secret (ECDH_SECRET_SIZE bytes) +using ECDHSecret = std::array; + /** An encapsulated private key. */ class CKey { @@ -161,6 +167,27 @@ public: //! Load private key and check that public key matches. bool Load(const CPrivKey& privkey, const CPubKey& vchPubKey, bool fSkipCheck); + + /** Create an ellswift-encoded public key for this key, with specified entropy. + * + * entropy must be a 32-byte span with additional entropy to use in the encoding. Every + * public key has ~2^256 different encodings, and this function will deterministically pick + * one of them, based on entropy. Note that even without truly random entropy, the + * resulting encoding will be indistinguishable from uniform to any adversary who does not + * know the private key (because the private key itself is always used as entropy as well). + */ + EllSwiftPubKey EllSwiftCreate(Span entropy) const; + + /** Compute a BIP324-style ECDH shared secret. + * + * - their_ellswift: EllSwiftPubKey that was received from the other side. + * - our_ellswift: EllSwiftPubKey that was sent to the other side (must have been generated + * from *this using EllSwiftCreate()). + * - initiating: whether we are the initiating party (true) or responding party (false). + */ + ECDHSecret ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, + const EllSwiftPubKey& our_ellswift, + bool initiating) const; }; struct CExtKey { diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 6e8c2e865b..506beb39be 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -359,6 +360,20 @@ bool CPubKey::Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChi return true; } +CPubKey EllSwiftPubKey::Decode() const +{ + secp256k1_pubkey pubkey; + secp256k1_ellswift_decode(secp256k1_context_static, &pubkey, UCharCast(m_pubkey.data())); + + size_t sz = CPubKey::COMPRESSED_SIZE; + std::array vch_bytes; + + secp256k1_ec_pubkey_serialize(secp256k1_context_static, vch_bytes.data(), &sz, &pubkey, SECP256K1_EC_COMPRESSED); + assert(sz == vch_bytes.size()); + + return CPubKey{vch_bytes.begin(), vch_bytes.end()}; +} + void CExtPubKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const { code[0] = nDepth; memcpy(code+1, vchFingerprint, 4); diff --git a/src/pubkey.h b/src/pubkey.h index fbe2601c20..14dc914fb3 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -298,6 +298,38 @@ public: SERIALIZE_METHODS(XOnlyPubKey, obj) { READWRITE(obj.m_keydata); } }; +/** An ElligatorSwift-encoded public key. */ +struct EllSwiftPubKey +{ +private: + static constexpr size_t SIZE = 64; + std::array m_pubkey; + +public: + /** Construct a new ellswift public key from a given serialization. */ + EllSwiftPubKey(const std::array& ellswift) : + m_pubkey(ellswift) {} + + /** Decode to normal compressed CPubKey (for debugging purposes). */ + CPubKey Decode() const; + + // Read-only access for serialization. + const std::byte* data() const { return m_pubkey.data(); } + static constexpr size_t size() { return SIZE; } + auto begin() const { return m_pubkey.cbegin(); } + auto end() const { return m_pubkey.cend(); } + + bool friend operator==(const EllSwiftPubKey& a, const EllSwiftPubKey& b) + { + return a.m_pubkey == b.m_pubkey; + } + + bool friend operator!=(const EllSwiftPubKey& a, const EllSwiftPubKey& b) + { + return a.m_pubkey != b.m_pubkey; + } +}; + struct CExtPubKey { unsigned char version[4]; unsigned char nDepth; diff --git a/src/random.cpp b/src/random.cpp index 54500e6cc6..39ceae4206 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -599,6 +599,12 @@ std::vector FastRandomContext::randbytes(size_t len) return ret; } +void FastRandomContext::fillrand(Span output) +{ + if (requires_seed) RandomSeed(); + rng.Keystream(UCharCast(output.data()), output.size()); +} + FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bitbuf_size(0) { rng.SetKey32(seed.begin()); diff --git a/src/random.h b/src/random.h index 49c0dff5bf..50f56ed911 100644 --- a/src/random.h +++ b/src/random.h @@ -213,6 +213,9 @@ public: /** Generate random bytes. */ std::vector randbytes(size_t len); + /** Fill a byte Span with random bytes. */ + void fillrand(Span output); + /** Generate a random 32-bit integer. */ uint32_t rand32() noexcept { return randbits(32); } diff --git a/src/simplicity/secp256k1/field_5x52_asm_impl.h b/src/simplicity/secp256k1/field_5x52_asm_impl.h index a2118044ab..04a9af2105 100644 --- a/src/simplicity/secp256k1/field_5x52_asm_impl.h +++ b/src/simplicity/secp256k1/field_5x52_asm_impl.h @@ -14,6 +14,8 @@ #ifndef SECP256K1_FIELD_INNER5X52_IMPL_H #define SECP256K1_FIELD_INNER5X52_IMPL_H +#include "util.h" + SECP256K1_INLINE static void secp256k1_fe_mul_inner(uint64_t *r, const uint64_t *a, const uint64_t * SECP256K1_RESTRICT b) { /** * Registers: rdx:rax = multiplication accumulator @@ -278,7 +280,7 @@ __asm__ __volatile__( "addq %%rsi,%%r8\n" /* r[4] = c */ "movq %%r8,32(%%rdi)\n" -: "+S"(a), "=m"(tmp1), "=m"(tmp2), "=m"(tmp3) +: "+S"(a), "=&m"(tmp1), "=&m"(tmp2), "=&m"(tmp3) : "b"(b), "D"(r) : "%rax", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15", "cc", "memory" ); @@ -493,7 +495,7 @@ __asm__ __volatile__( "addq %%rsi,%%r8\n" /* r[4] = c */ "movq %%r8,32(%%rdi)\n" -: "+S"(a), "=m"(tmp1), "=m"(tmp2), "=m"(tmp3) +: "+S"(a), "=&m"(tmp1), "=&m"(tmp2), "=&m"(tmp3) : "D"(r) : "%rax", "%rbx", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15", "cc", "memory" ); diff --git a/src/span.h b/src/span.h index 4692eca7fb..c98784aee4 100644 --- a/src/span.h +++ b/src/span.h @@ -274,6 +274,7 @@ Span MakeWritableByteSpan(V&& v) noexcept // Helper functions to safely cast to unsigned char pointers. inline unsigned char* UCharCast(char* c) { return (unsigned char*)c; } inline unsigned char* UCharCast(unsigned char* c) { return c; } +inline unsigned char* UCharCast(std::byte* c) { return (unsigned char*)c; } inline const unsigned char* UCharCast(const char* c) { return (unsigned char*)c; } inline const unsigned char* UCharCast(const unsigned char* c) { return c; } inline const unsigned char* UCharCast(const std::byte* c) { return reinterpret_cast(c); } diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp index 3eab2e20c0..25ea547435 100644 --- a/src/test/fuzz/key.cpp +++ b/src/test/fuzz/key.cpp @@ -15,13 +15,17 @@ #include