diff --git a/src/key.cpp b/src/key.cpp index e8458f2e3b292..592a8d122b8b9 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -277,27 +277,7 @@ bool CKey::SignCompact(const uint256 &hash, std::vector& vchSig) bool CKey::SignSchnorr(const uint256& hash, Span sig, const uint256* merkle_root, const uint256& aux) const { - assert(sig.size() == 64); - secp256k1_keypair keypair; - if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, UCharCast(begin()))) return false; - if (merkle_root) { - secp256k1_xonly_pubkey pubkey; - if (!secp256k1_keypair_xonly_pub(secp256k1_context_sign, &pubkey, nullptr, &keypair)) return false; - unsigned char pubkey_bytes[32]; - if (!secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, pubkey_bytes, &pubkey)) return false; - uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root); - if (!secp256k1_keypair_xonly_tweak_add(secp256k1_context_static, &keypair, tweak.data())) return false; - } - bool ret = secp256k1_schnorrsig_sign32(secp256k1_context_sign, sig.data(), hash.data(), &keypair, aux.data()); - if (ret) { - // Additional verification step to prevent using a potentially corrupted signature - secp256k1_xonly_pubkey pubkey_verify; - ret = secp256k1_keypair_xonly_pub(secp256k1_context_static, &pubkey_verify, nullptr, &keypair); - ret &= secp256k1_schnorrsig_verify(secp256k1_context_static, sig.data(), hash.begin(), 32, &pubkey_verify); - } - if (!ret) memory_cleanse(sig.data(), sig.size()); - memory_cleanse(&keypair, sizeof(keypair)); - return ret; + return ComputeKeyPair(merkle_root).SignSchnorr(hash, sig, aux); } bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) { @@ -369,6 +349,11 @@ ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, c return output; } +KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const +{ + return KeyPair(*this, merkle_root); +} + CKey GenerateRandomKey(bool compressed) noexcept { CKey key; @@ -426,6 +411,50 @@ void CExtKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) { if ((nDepth == 0 && (nChild != 0 || ReadLE32(vchFingerprint) != 0)) || code[41] != 0) key = CKey(); } +KeyPair::KeyPair(const CKey& key, const uint256* merkle_root) +{ + static_assert(std::tuple_size() == sizeof(secp256k1_keypair)); + auto keydata = make_secure_unique(); + if (!secp256k1_keypair_create(secp256k1_context_sign, reinterpret_cast(keydata->data()), UCharCast(key.data()))) return; + if (merkle_root) { + secp256k1_xonly_pubkey pubkey; + if (!secp256k1_keypair_xonly_pub(secp256k1_context_sign, &pubkey, nullptr, reinterpret_cast(keydata->data()))) return; + unsigned char pubkey_bytes[32]; + if (!secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, pubkey_bytes, &pubkey)) return; + uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root); + if (!secp256k1_keypair_xonly_tweak_add(secp256k1_context_static, reinterpret_cast(keydata->data()), tweak.data())) return; + } + m_keydata = std::move(keydata); +} + +bool KeyPair::GetKey(CKey& key) const +{ + if (!m_keydata) return false; + unsigned char tweaked_secret_bytes[32]; + if (!secp256k1_keypair_sec(secp256k1_context_sign, tweaked_secret_bytes, reinterpret_cast(m_keydata->data()))) { + return false; + } + key.Set(std::begin(tweaked_secret_bytes), std::end(tweaked_secret_bytes), true); + memory_cleanse(tweaked_secret_bytes, sizeof(tweaked_secret_bytes)); + return true; +} + +bool KeyPair::SignSchnorr(const uint256& hash, Span sig, const uint256& aux) const +{ + if (!m_keydata) return false; + assert(sig.size() == 64); + bool ret; + ret = secp256k1_schnorrsig_sign32(secp256k1_context_sign, sig.data(), hash.data(), reinterpret_cast(m_keydata->data()), aux.data()); + if (ret) { + // Additional verification step to prevent using a potentially corrupted signature + secp256k1_xonly_pubkey pubkey_verify; + ret = secp256k1_keypair_xonly_pub(secp256k1_context_static, &pubkey_verify, nullptr, reinterpret_cast(m_keydata->data())); + ret &= secp256k1_schnorrsig_verify(secp256k1_context_static, sig.data(), hash.begin(), 32, &pubkey_verify); + } + if (!ret) memory_cleanse(sig.data(), sig.size()); + return ret; +} + bool ECC_InitSanityCheck() { CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); diff --git a/src/key.h b/src/key.h index 36d093b7dc9b4..b608fa0fde8db 100644 --- a/src/key.h +++ b/src/key.h @@ -28,6 +28,8 @@ constexpr static size_t ECDH_SECRET_SIZE = CSHA256::OUTPUT_SIZE; // Used to represent ECDH shared secret (ECDH_SECRET_SIZE bytes) using ECDHSecret = std::array; +class KeyPair; + /** An encapsulated private key. */ class CKey { @@ -203,6 +205,20 @@ class CKey ECDHSecret ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, const EllSwiftPubKey& our_ellswift, bool initiating) const; + /** Compute a KeyPair + * + * Wraps a `secp256k1_keypair` type. `merkle_root` is used to optionally perform tweaking of + * the internal key, as specified in BIP341: + * + * - If merkle_root == nullptr: no tweaking is done, use the internal key directly (this is + * used for signatures in BIP342 script). + * - If merkle_root->IsNull(): tweak the internal key with H_TapTweak(pubkey) (this is used for + * key path spending when no scripts are present). + * - Otherwise: tweak the internal key H_TapTweak(pubkey || *merkle_root) + * (this is used for key path spending, with specific + * Merkle root of the script tree). + */ + KeyPair ComputeKeyPair(const uint256* merkle_root) const; }; CKey GenerateRandomKey(bool compressed = true) noexcept; @@ -236,6 +252,41 @@ struct CExtKey { void SetSeed(Span seed); }; +/** KeyPair + * + * Wraps a `secp256k1_keypair` type. `merkle_root` is used to optionally perform tweaking of + * the internal key, as specified in BIP341: + * + * - If merkle_root == nullptr: no tweaking is done, use the internal key directly (this is + * used for signatures in BIP342 script). + * - If merkle_root->IsNull(): tweak the internal key with H_TapTweak(pubkey) (this is used for + * key path spending when no scripts are present). + * - Otherwise: tweak the internal key H_TapTweak(pubkey || *merkle_root) + * (this is used for key path spending, with specific + * Merkle root of the script tree). + */ +class KeyPair +{ +public: + KeyPair(KeyPair&&) noexcept = default; + KeyPair& operator=(KeyPair&&) noexcept = default; + + friend KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const; + [[nodiscard]] bool GetKey(CKey& key) const; + [[nodiscard]] bool SignSchnorr(const uint256& hash, Span sig, const uint256& aux) const; + + //! Simple read-only vector-like interface. + unsigned int size() const { return m_keydata ? m_keydata->size() : 0; } + const std::byte* data() const { return m_keydata ? reinterpret_cast(m_keydata->data()) : nullptr; } + const std::byte* begin() const { return data(); } + const std::byte* end() const { return data() + size(); } +private: + KeyPair(const CKey& key, const uint256* merkle_root); + + using KeyType = std::array; + secure_unique_ptr m_keydata; +}; + /** Check that required EC support is available at runtime. */ bool ECC_InitSanityCheck();