diff --git a/src/key.cpp b/src/key.cpp index 3a3f0b2bc2..efaea5b1b3 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -331,6 +332,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 4e092fffea..8382b0a670 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 { @@ -156,6 +162,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 ae5dccfb5a..4866feed67 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -335,6 +336,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 b3edafea7f..90b2204905 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -291,6 +291,28 @@ 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(); } +}; + struct CExtPubKey { unsigned char version[4]; unsigned char nDepth; 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); }