From 54b3c73ec50d4c3902049a66bca56438bb4078ba Mon Sep 17 00:00:00 2001 From: josibake Date: Sun, 23 Jul 2023 16:14:52 +0200 Subject: [PATCH] crypto: Add a KeyPair wrapper class The wallet returns an untweaked internal key for taproot outputs. If the output commits to a tree of scripts, this key needs to be tweaked with the merkle root. Even if the output does not commit to a tree of scripts, BIP341/342 recommend commiting to a hash of the public key. Previously, this logic for applying the taptweak was implemented in the ::SignSchnorr method. Move this tweaking and signing logic to a new class, KeyPair, and add a method to CKey for computing a KeyPair, CKey::ComputeKeyPair. This class is a wrapper for the `secp256k1_keypair` type. The motivation is to be able to use the the tweaked internal key outside of signing, e.g. in silent payments when retreiving the private key before ECDH. Having the KeyPair class is also a general improvement in that we almost always convert to `secp256k1_keypair` objects when using taproot private keys with libsecp256k1. Co-authored-by: Cory Fields --- src/key.cpp | 71 +++++++++++++++++++++++++++++++++++++---------------- src/key.h | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 21 deletions(-) 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();