@@ -204,7 +204,9 @@ 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 no private data is available, the string will be the same as ToString(StringType::PUBLIC).
209+ */
208210 virtual bool ToPrivateString (const SigningProvider& arg, std::string& out) const = 0;
209211
210212 /* * Get the descriptor string form with the xpub at the last hardened derivation,
@@ -260,9 +262,9 @@ class OriginPubkeyProvider final : public PubkeyProvider
260262 bool ToPrivateString (const SigningProvider& arg, std::string& ret) const override
261263 {
262264 std::string sub;
263- if (! m_provider->ToPrivateString (arg, sub)) return false ;
265+ bool has_priv_key{ m_provider->ToPrivateString (arg, sub)} ;
264266 ret = " [" + OriginString (StringType::PUBLIC) + " ]" + std::move (sub);
265- return true ;
267+ return has_priv_key ;
266268 }
267269 bool ToNormalizedString (const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
268270 {
@@ -329,7 +331,10 @@ class ConstPubkeyProvider final : public PubkeyProvider
329331 bool ToPrivateString (const SigningProvider& arg, std::string& ret) const override
330332 {
331333 std::optional<CKey> key = GetPrivKey (arg);
332- if (!key) return false ;
334+ if (!key) {
335+ ret = ToString (StringType::PUBLIC);
336+ return false ;
337+ }
333338 ret = EncodeSecret (*key);
334339 return true ;
335340 }
@@ -492,7 +497,10 @@ class BIP32PubkeyProvider final : public PubkeyProvider
492497 bool ToPrivateString (const SigningProvider& arg, std::string& out) const override
493498 {
494499 CExtKey key;
495- if (!GetExtKey (arg, key)) return false ;
500+ if (!GetExtKey (arg, key)) {
501+ out = ToString (StringType::PUBLIC);
502+ return false ;
503+ }
496504 out = EncodeExtKey (key) + FormatHDKeypath (m_path, /* apostrophe=*/ m_apostrophe);
497505 if (IsRange ()) {
498506 out += " /*" ;
@@ -841,6 +849,31 @@ class DescriptorImpl : public Descriptor
841849 return true ;
842850 }
843851
852+ bool IsWatchOnly (const SigningProvider& arg) const override
853+ {
854+ size_t privkey_count{0 };
855+ std::set<CPubKey> pubkeys;
856+ std::set<CExtPubKey> ext_pubkeys;
857+ this ->GetPubKeys (pubkeys, ext_pubkeys);
858+ auto output_type{this ->GetOutputType ()};
859+ for (auto pubkey : pubkeys) {
860+ CKey key;
861+ if (arg.GetKey (pubkey.GetID (), key)) {
862+ privkey_count += 1 ;
863+ } else if (output_type.has_value () && *output_type == OutputType::BECH32M && arg.GetKeyByXOnly (XOnlyPubKey (pubkey), key)) {
864+ privkey_count += 1 ;
865+ }
866+ }
867+ for (auto extpubkey : ext_pubkeys) {
868+ CKey dummy;
869+ if (arg.GetKey (extpubkey.pubkey .GetID (), dummy)) {
870+ privkey_count += 1 ;
871+ }
872+ }
873+ size_t pubkey_count{pubkeys.size () + ext_pubkeys.size ()};
874+ return (privkey_count == 0 || pubkey_count > privkey_count);
875+ }
876+
844877 // NOLINTNEXTLINE(misc-no-recursion)
845878 bool IsRange () const final
846879 {
@@ -854,24 +887,27 @@ class DescriptorImpl : public Descriptor
854887 }
855888
856889 // NOLINTNEXTLINE(misc-no-recursion)
857- virtual bool ToStringSubScriptHelper (const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr ) const
890+ virtual bool ToStringSubScriptHelper (const SigningProvider* arg, std::string& ret, const StringType type, bool * has_priv_key, const DescriptorCache* cache = nullptr ) const
858891 {
859892 size_t pos = 0 ;
860893 for (const auto & scriptarg : m_subdescriptor_args) {
861894 if (pos++) ret += " ," ;
862895 std::string tmp;
863- if (!scriptarg->ToStringHelper (arg, tmp, type, cache)) return false ;
896+ bool subscript_has_priv_key{false };
897+ if (!scriptarg->ToStringHelper (arg, tmp, type, &subscript_has_priv_key, cache)) return false ;
898+ *has_priv_key = *has_priv_key || subscript_has_priv_key;
864899 ret += tmp;
865900 }
866901 return true ;
867902 }
868903
869904 // NOLINTNEXTLINE(misc-no-recursion)
870- virtual bool ToStringHelper (const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr ) const
905+ virtual bool ToStringHelper (const SigningProvider* arg, std::string& out, const StringType type, bool * has_priv_key = nullptr , const DescriptorCache* cache = nullptr ) const
871906 {
872907 std::string extra = ToStringExtra ();
873908 size_t pos = extra.size () > 0 ? 1 : 0 ;
874909 std::string ret = m_name + " (" + extra;
910+
875911 for (const auto & pubkey : m_pubkey_args) {
876912 if (pos++) ret += " ," ;
877913 std::string tmp;
@@ -880,7 +916,8 @@ class DescriptorImpl : public Descriptor
880916 if (!pubkey->ToNormalizedString (*arg, tmp, cache)) return false ;
881917 break ;
882918 case StringType::PRIVATE:
883- if (!pubkey->ToPrivateString (*arg, tmp)) return false ;
919+ assert (has_priv_key != nullptr );
920+ *has_priv_key = pubkey->ToPrivateString (*arg, tmp) || *has_priv_key;
884921 break ;
885922 case StringType::PUBLIC:
886923 tmp = pubkey->ToString ();
@@ -892,7 +929,9 @@ class DescriptorImpl : public Descriptor
892929 ret += tmp;
893930 }
894931 std::string subscript;
895- if (!ToStringSubScriptHelper (arg, subscript, type, cache)) return false ;
932+ bool subscript_has_priv_key{false };
933+ if (!ToStringSubScriptHelper (arg, subscript, type, &subscript_has_priv_key, cache)) return false ;
934+ if (has_priv_key != nullptr ) *has_priv_key = *has_priv_key || subscript_has_priv_key;
896935 if (pos && subscript.size ()) ret += ' ,' ;
897936 out = std::move (ret) + std::move (subscript) + " )" ;
898937 return true ;
@@ -907,14 +946,17 @@ class DescriptorImpl : public Descriptor
907946
908947 bool ToPrivateString (const SigningProvider& arg, std::string& out) const override
909948 {
910- bool ret = ToStringHelper (&arg, out, StringType::PRIVATE);
949+ bool has_priv_key{false };
950+ // ToStringHelper should never fail for StringType::PRIVATE,
951+ // because it falls back to StringType::PUBLIC when no private key is available.
952+ assert (ToStringHelper (&arg, out, StringType::PRIVATE, &has_priv_key));
911953 out = AddChecksum (out);
912- return ret ;
954+ return has_priv_key ;
913955 }
914956
915957 bool ToNormalizedString (const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override final
916958 {
917- bool ret = ToStringHelper (&arg, out, StringType::NORMALIZED, cache);
959+ bool ret = ToStringHelper (&arg, out, StringType::NORMALIZED, nullptr , cache);
918960 out = AddChecksum (out);
919961 return ret;
920962 }
@@ -1390,18 +1432,21 @@ class TRDescriptor final : public DescriptorImpl
13901432 out.tr_trees [output] = builder;
13911433 return Vector (GetScriptForDestination (output));
13921434 }
1393- bool ToStringSubScriptHelper (const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr ) const override
1435+ bool ToStringSubScriptHelper (const SigningProvider* arg, std::string& ret, const StringType type, bool * has_priv_key, const DescriptorCache* cache = nullptr ) const override
13941436 {
13951437 if (m_depths.empty ()) return true ;
13961438 std::vector<bool > path;
1439+
13971440 for (size_t pos = 0 ; pos < m_depths.size (); ++pos) {
13981441 if (pos) ret += ' ,' ;
13991442 while ((int )path.size () <= m_depths[pos]) {
14001443 if (path.size ()) ret += ' {' ;
14011444 path.push_back (false );
14021445 }
14031446 std::string tmp;
1404- if (!m_subdescriptor_args[pos]->ToStringHelper (arg, tmp, type, cache)) return false ;
1447+ bool subscript_has_priv_key{false };
1448+ if (!m_subdescriptor_args[pos]->ToStringHelper (arg, tmp, type, &subscript_has_priv_key, cache)) return false ;
1449+ if (has_priv_key != nullptr ) *has_priv_key = *has_priv_key || subscript_has_priv_key;
14051450 ret += tmp;
14061451 while (!path.empty () && path.back ()) {
14071452 if (path.size () > 1 ) ret += ' }' ;
@@ -1498,11 +1543,12 @@ class StringMaker {
14981543 StringMaker (const SigningProvider* arg LIFETIMEBOUND, const std::vector<std::unique_ptr<PubkeyProvider>>& pubkeys LIFETIMEBOUND, bool priv)
14991544 : m_arg(arg), m_pubkeys(pubkeys), m_private(priv) {}
15001545
1501- std::optional<std:: string> ToString (uint32_t key) const
1546+ std::string ToString (uint32_t key, bool * has_priv_key = nullptr ) const
15021547 {
15031548 std::string ret;
15041549 if (m_private) {
1505- if (!m_pubkeys[key]->ToPrivateString (*m_arg, ret)) return {};
1550+ assert (has_priv_key != nullptr );
1551+ *has_priv_key = m_pubkeys[key]->ToPrivateString (*m_arg, ret);
15061552 } else {
15071553 ret = m_pubkeys[key]->ToString ();
15081554 }
@@ -1535,13 +1581,10 @@ class MiniscriptDescriptor final : public DescriptorImpl
15351581 : DescriptorImpl(std::move(providers), " ?" ), m_node(std::move(node)) {}
15361582
15371583 bool ToStringHelper (const SigningProvider* arg, std::string& out, const StringType type,
1538- const DescriptorCache* cache = nullptr ) const override
1584+ bool * has_priv_key, const DescriptorCache* cache = nullptr ) const override
15391585 {
1540- if (const auto res = m_node->ToString (StringMaker (arg, m_pubkey_args, type == StringType::PRIVATE))) {
1541- out = *res;
1542- return true ;
1543- }
1544- return false ;
1586+ out = m_node->ToString (StringMaker (arg, m_pubkey_args, type == StringType::PRIVATE), has_priv_key);
1587+ return true ;
15451588 }
15461589
15471590 bool IsSolvable () const override { return true ; }
@@ -2096,7 +2139,7 @@ struct KeyParser {
20962139 return key;
20972140 }
20982141
2099- std::optional<std:: string> ToString (const Key& key) const
2142+ std::string ToString (const Key& key, bool * has_priv_key = nullptr ) const
21002143 {
21012144 return m_keys.at (key).at (0 )->ToString ();
21022145 }
@@ -2514,7 +2557,7 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
25142557 // Try to find the first insane sub for better error reporting.
25152558 auto insane_node = node.get ();
25162559 if (const auto sub = node->FindInsaneSub ()) insane_node = sub;
2517- if ( const auto str = insane_node->ToString (parser)) error = *str ;
2560+ error = insane_node->ToString (parser);
25182561 if (!insane_node->IsValid ()) {
25192562 error += " is invalid" ;
25202563 } else if (!node->IsSane ()) {
0 commit comments