diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index a42943318d6..e2f7b8e873a 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -835,6 +835,25 @@ public: return true; } + // NOLINTNEXTLINE(misc-no-recursion) + bool HavePrivateKeys(const SigningProvider& arg) const override + { + if (m_pubkey_args.empty() && m_subdescriptor_args.empty()) return false; + + for (const auto& sub: m_subdescriptor_args) { + if (!sub->HavePrivateKeys(arg)) return false; + } + + FlatSigningProvider tmp_provider; + for (const auto& pubkey : m_pubkey_args) { + tmp_provider.keys.clear(); + pubkey->GetPrivKey(0, arg, tmp_provider); + if (tmp_provider.keys.empty()) return false; + } + + return true; + } + // NOLINTNEXTLINE(misc-no-recursion) bool IsRange() const final { diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 9a018300ebe..c7863933ef9 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -111,6 +111,13 @@ struct Descriptor { /** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */ virtual bool IsSingleType() const = 0; + /** Whether the given provider has all private keys required by this descriptor. + * @return `false` if the descriptor doesn't have any keys or subdescriptors, + * or if the provider does not have all private keys required by + * the descriptor. + */ + virtual bool HavePrivateKeys(const SigningProvider& provider) const = 0; + /** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */ virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0; diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index 098007c4cf9..17c756b6033 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -49,7 +49,7 @@ constexpr int SIGNABLE = 1 << 3; // We can sign with this descriptor (this is no constexpr int DERIVE_HARDENED = 1 << 4; // The final derivation is hardened, i.e. ends with *' or *h constexpr int MIXED_PUBKEYS = 1 << 5; constexpr int XONLY_KEYS = 1 << 6; // X-only pubkeys are in use (and thus inferring/caching may swap parity of pubkeys/keyids) -constexpr int MISSING_PRIVKEYS = 1 << 7; // Not all private keys are available, so ToPrivateString will fail. +constexpr int MISSING_PRIVKEYS = 1 << 7; // Not all private keys are available. ToPrivateString() will fail and HavePrivateKeys() will return `false`. constexpr int SIGNABLE_FAILS = 1 << 8; // We can sign with this descriptor, but actually trying to sign will fail constexpr int MUSIG = 1 << 9; // This is a MuSig so key counts will have an extra key constexpr int MUSIG_DERIVATION = 1 << 10; // MuSig with BIP 328 derivation from the aggregate key @@ -243,6 +243,9 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int } else { BOOST_CHECK_MESSAGE(EqualDescriptor(prv, prv1), "Private ser: " + prv1 + " Private desc: " + prv); } + BOOST_CHECK(!parse_priv->HavePrivateKeys(keys_pub)); + BOOST_CHECK(parse_pub->HavePrivateKeys(keys_priv)); + BOOST_CHECK(!parse_priv->ToPrivateString(keys_pub, prv1)); BOOST_CHECK(parse_pub->ToPrivateString(keys_priv, prv1)); if (expected_prv) { diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp index 0c69849d0b6..adbd9aa1bae 100644 --- a/src/wallet/test/walletload_tests.cpp +++ b/src/wallet/test/walletload_tests.cpp @@ -26,6 +26,7 @@ public: bool IsRange() const override { return false; } bool IsSolvable() const override { return false; } bool IsSingleType() const override { return true; } + bool HavePrivateKeys(const SigningProvider&) const override { return false; } bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; } bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; } bool Expand(int pos, const SigningProvider& provider, std::vector& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; };