@@ -204,7 +204,11 @@ struct PubkeyProvider
204204 /* * Get the descriptor string form. */
205205 virtual std::string ToString (StringType type=StringType::PUBLIC) const = 0;
206206
207- /* * Get the descriptor string form including private data (if available in arg). */
207+ /* * Get the descriptor string form including private data (if available in arg).
208+ * If the private data is not available, the output string in the "out" parameter
209+ * will not contain any private key information,
210+ * and this function will return "false".
211+ */
208212 virtual bool ToPrivateString (const SigningProvider& arg, std::string& out) const = 0;
209213
210214 /* * Get the descriptor string form with the xpub at the last hardened derivation,
@@ -260,9 +264,9 @@ class OriginPubkeyProvider final : public PubkeyProvider
260264 bool ToPrivateString (const SigningProvider& arg, std::string& ret) const override
261265 {
262266 std::string sub;
263- if (! m_provider->ToPrivateString (arg, sub)) return false ;
267+ bool has_priv_key{ m_provider->ToPrivateString (arg, sub)} ;
264268 ret = " [" + OriginString (StringType::PUBLIC) + " ]" + std::move (sub);
265- return true ;
269+ return has_priv_key ;
266270 }
267271 bool ToNormalizedString (const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
268272 {
@@ -329,7 +333,10 @@ class ConstPubkeyProvider final : public PubkeyProvider
329333 bool ToPrivateString (const SigningProvider& arg, std::string& ret) const override
330334 {
331335 std::optional<CKey> key = GetPrivKey (arg);
332- if (!key) return false ;
336+ if (!key) {
337+ ret = ToString (StringType::PUBLIC);
338+ return false ;
339+ }
333340 ret = EncodeSecret (*key);
334341 return true ;
335342 }
@@ -492,7 +499,10 @@ class BIP32PubkeyProvider final : public PubkeyProvider
492499 bool ToPrivateString (const SigningProvider& arg, std::string& out) const override
493500 {
494501 CExtKey key;
495- if (!GetExtKey (arg, key)) return false ;
502+ if (!GetExtKey (arg, key)) {
503+ out = ToString (StringType::PUBLIC);
504+ return false ;
505+ }
496506 out = EncodeExtKey (key) + FormatHDKeypath (m_path, /* apostrophe=*/ m_apostrophe);
497507 if (IsRange ()) {
498508 out += " /*" ;
@@ -710,17 +720,14 @@ class MuSigPubkeyProvider final : public PubkeyProvider
710720 std::string tmp;
711721 if (pubkey->ToPrivateString (arg, tmp)) {
712722 any_privkeys = true ;
713- out += tmp;
714- } else {
715- out += pubkey->ToString ();
716723 }
724+ out += tmp;
717725 }
718726 out += " )" ;
719727 out += FormatHDKeypath (m_path);
720728 if (IsRangedDerivation ()) {
721729 out += " /*" ;
722730 }
723- if (!any_privkeys) out.clear ();
724731 return any_privkeys;
725732 }
726733 bool ToNormalizedString (const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr ) const override
@@ -837,6 +844,25 @@ class DescriptorImpl : public Descriptor
837844 return true ;
838845 }
839846
847+ // NOLINTNEXTLINE(misc-no-recursion)
848+ bool HavePrivateKeys (const SigningProvider& arg) const override
849+ {
850+ if (m_pubkey_args.empty () && m_subdescriptor_args.empty ()) return false ;
851+
852+ for (const auto & sub: m_subdescriptor_args) {
853+ if (!sub->HavePrivateKeys (arg)) return false ;
854+ }
855+
856+ FlatSigningProvider tmp_provider;
857+ for (const auto & pubkey : m_pubkey_args) {
858+ tmp_provider.keys .clear ();
859+ pubkey->GetPrivKey (0 , arg, tmp_provider);
860+ if (tmp_provider.keys .empty ()) return false ;
861+ }
862+
863+ return true ;
864+ }
865+
840866 // NOLINTNEXTLINE(misc-no-recursion)
841867 bool IsRange () const final
842868 {
@@ -853,13 +879,19 @@ class DescriptorImpl : public Descriptor
853879 virtual bool ToStringSubScriptHelper (const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr ) const
854880 {
855881 size_t pos = 0 ;
882+ bool is_private{type == StringType::PRIVATE};
883+ // For private string output, track if at least one key has a private key available.
884+ // Initialize to true for non-private types.
885+ bool any_success{!is_private};
856886 for (const auto & scriptarg : m_subdescriptor_args) {
857887 if (pos++) ret += " ," ;
858888 std::string tmp;
859- if (!scriptarg->ToStringHelper (arg, tmp, type, cache)) return false ;
889+ bool subscript_res{scriptarg->ToStringHelper (arg, tmp, type, cache)};
890+ if (!is_private && !subscript_res) return false ;
891+ any_success = any_success || subscript_res;
860892 ret += tmp;
861893 }
862- return true ;
894+ return any_success ;
863895 }
864896
865897 // NOLINTNEXTLINE(misc-no-recursion)
@@ -868,6 +900,11 @@ class DescriptorImpl : public Descriptor
868900 std::string extra = ToStringExtra ();
869901 size_t pos = extra.size () > 0 ? 1 : 0 ;
870902 std::string ret = m_name + " (" + extra;
903+ bool is_private{type == StringType::PRIVATE};
904+ // For private string output, track if at least one key has a private key available.
905+ // Initialize to true for non-private types.
906+ bool any_success{!is_private};
907+
871908 for (const auto & pubkey : m_pubkey_args) {
872909 if (pos++) ret += " ," ;
873910 std::string tmp;
@@ -876,7 +913,7 @@ class DescriptorImpl : public Descriptor
876913 if (!pubkey->ToNormalizedString (*arg, tmp, cache)) return false ;
877914 break ;
878915 case StringType::PRIVATE:
879- if (! pubkey->ToPrivateString (*arg, tmp)) return false ;
916+ any_success = pubkey->ToPrivateString (*arg, tmp) || any_success ;
880917 break ;
881918 case StringType::PUBLIC:
882919 tmp = pubkey->ToString ();
@@ -888,10 +925,12 @@ class DescriptorImpl : public Descriptor
888925 ret += tmp;
889926 }
890927 std::string subscript;
891- if (!ToStringSubScriptHelper (arg, subscript, type, cache)) return false ;
928+ bool subscript_res{ToStringSubScriptHelper (arg, subscript, type, cache)};
929+ if (!is_private && !subscript_res) return false ;
930+ any_success = any_success || subscript_res;
892931 if (pos && subscript.size ()) ret += ' ,' ;
893932 out = std::move (ret) + std::move (subscript) + " )" ;
894- return true ;
933+ return any_success ;
895934 }
896935
897936 std::string ToString (bool compat_format) const final
@@ -903,9 +942,9 @@ class DescriptorImpl : public Descriptor
903942
904943 bool ToPrivateString (const SigningProvider& arg, std::string& out) const override
905944 {
906- bool ret = ToStringHelper (&arg, out, StringType::PRIVATE);
945+ bool has_priv_key{ ToStringHelper (&arg, out, StringType::PRIVATE)} ;
907946 out = AddChecksum (out);
908- return ret ;
947+ return has_priv_key ;
909948 }
910949
911950 bool ToNormalizedString (const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override final
@@ -1398,24 +1437,38 @@ class TRDescriptor final : public DescriptorImpl
13981437 }
13991438 bool ToStringSubScriptHelper (const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr ) const override
14001439 {
1401- if (m_depths.empty ()) return true ;
1440+ if (m_depths.empty ()) {
1441+ // If there are no sub-descriptors and a PRIVATE string
1442+ // is requested, return `false` to indicate that the presence
1443+ // of a private key depends solely on the internal key (which is checked
1444+ // in the caller), not on any sub-descriptor. This ensures correct behavior for
1445+ // descriptors like tr(internal_key) when checking for private keys.
1446+ return type != StringType::PRIVATE;
1447+ }
14021448 std::vector<bool > path;
1449+ bool is_private{type == StringType::PRIVATE};
1450+ // For private string output, track if at least one key has a private key available.
1451+ // Initialize to true for non-private types.
1452+ bool any_success{!is_private};
1453+
14031454 for (size_t pos = 0 ; pos < m_depths.size (); ++pos) {
14041455 if (pos) ret += ' ,' ;
14051456 while ((int )path.size () <= m_depths[pos]) {
14061457 if (path.size ()) ret += ' {' ;
14071458 path.push_back (false );
14081459 }
14091460 std::string tmp;
1410- if (!m_subdescriptor_args[pos]->ToStringHelper (arg, tmp, type, cache)) return false ;
1461+ bool subscript_res{m_subdescriptor_args[pos]->ToStringHelper (arg, tmp, type, cache)};
1462+ if (!is_private && !subscript_res) return false ;
1463+ any_success = any_success || subscript_res;
14111464 ret += tmp;
14121465 while (!path.empty () && path.back ()) {
14131466 if (path.size () > 1 ) ret += ' }' ;
14141467 path.pop_back ();
14151468 }
14161469 if (!path.empty ()) path.back () = true ;
14171470 }
1418- return true ;
1471+ return any_success ;
14191472 }
14201473public:
14211474 TRDescriptor (std::unique_ptr<PubkeyProvider> internal_key, std::vector<std::unique_ptr<DescriptorImpl>> descs, std::vector<int > depths) :
@@ -1508,15 +1561,16 @@ class StringMaker {
15081561 const DescriptorCache* cache LIFETIMEBOUND)
15091562 : m_arg(arg), m_pubkeys(pubkeys), m_type(type), m_cache(cache) {}
15101563
1511- std::optional<std::string> ToString (uint32_t key) const
1564+ std::optional<std::string> ToString (uint32_t key, bool & has_priv_key ) const
15121565 {
15131566 std::string ret;
1567+ has_priv_key = false ;
15141568 switch (m_type) {
15151569 case DescriptorImpl::StringType::PUBLIC:
15161570 ret = m_pubkeys[key]->ToString ();
15171571 break ;
15181572 case DescriptorImpl::StringType::PRIVATE:
1519- if (! m_pubkeys[key]->ToPrivateString (*m_arg, ret)) return {} ;
1573+ has_priv_key = m_pubkeys[key]->ToPrivateString (*m_arg, ret);
15201574 break ;
15211575 case DescriptorImpl::StringType::NORMALIZED:
15221576 if (!m_pubkeys[key]->ToNormalizedString (*m_arg, ret, m_cache)) return {};
@@ -1573,11 +1627,10 @@ class MiniscriptDescriptor final : public DescriptorImpl
15731627 bool ToStringHelper (const SigningProvider* arg, std::string& out, const StringType type,
15741628 const DescriptorCache* cache = nullptr ) const override
15751629 {
1576- if (const auto res = m_node->ToString (StringMaker (arg, m_pubkey_args, type, cache))) {
1577- out = *res;
1578- return true ;
1579- }
1580- return false ;
1630+ bool has_priv_key{false };
1631+ auto res = m_node->ToString (StringMaker (arg, m_pubkey_args, type, cache), has_priv_key);
1632+ if (res) out = *res;
1633+ return type == StringType::PRIVATE ? has_priv_key : res.has_value ();
15811634 }
15821635
15831636 bool IsSolvable () const override { return true ; }
@@ -2132,7 +2185,7 @@ struct KeyParser {
21322185 return key;
21332186 }
21342187
2135- std::optional<std::string> ToString (const Key& key) const
2188+ std::optional<std::string> ToString (const Key& key, bool & ) const
21362189 {
21372190 return m_keys.at (key).at (0 )->ToString ();
21382191 }
@@ -2550,7 +2603,7 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
25502603 // Try to find the first insane sub for better error reporting.
25512604 auto insane_node = node.get ();
25522605 if (const auto sub = node->FindInsaneSub ()) insane_node = sub;
2553- if ( const auto str = insane_node->ToString (parser)) error = *str ;
2606+ error = * insane_node->ToString (parser);
25542607 if (!insane_node->IsValid ()) {
25552608 error += " is invalid" ;
25562609 } else if (!node->IsSane ()) {
0 commit comments