diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 2d10b5dd0f..dff6429259 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -191,8 +191,13 @@ public: /** Get the size of the generated public key(s) in bytes (33 or 65). */ virtual size_t GetSize() const = 0; + enum class StringType { + PUBLIC, + COMPAT // string calculation that mustn't change over time to stay compatible with previous software versions + }; + /** Get the descriptor string form. */ - virtual std::string ToString() const = 0; + virtual std::string ToString(StringType type=StringType::PUBLIC) const = 0; /** Get the descriptor string form including private data (if available in arg). */ virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0; @@ -212,9 +217,11 @@ class OriginPubkeyProvider final : public PubkeyProvider std::unique_ptr m_provider; bool m_apostrophe; - std::string OriginString(bool normalized=false) const + std::string OriginString(StringType type, bool normalized=false) const { - return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path, /*apostrophe=*/!normalized && m_apostrophe); + // If StringType==COMPAT, always use the apostrophe to stay compatible with previous versions + bool use_apostrophe = (!normalized && m_apostrophe) || type == StringType::COMPAT; + return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path, use_apostrophe); } public: @@ -228,12 +235,12 @@ public: } bool IsRange() const override { return m_provider->IsRange(); } size_t GetSize() const override { return m_provider->GetSize(); } - std::string ToString() const override { return "[" + OriginString() + "]" + m_provider->ToString(); } + std::string ToString(StringType type) const override { return "[" + OriginString(type) + "]" + m_provider->ToString(type); } bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override { std::string sub; if (!m_provider->ToPrivateString(arg, sub)) return false; - ret = "[" + OriginString() + "]" + std::move(sub); + ret = "[" + OriginString(StringType::PUBLIC) + "]" + std::move(sub); return true; } bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override @@ -245,9 +252,9 @@ public: // and append that to our own origin string. if (sub[0] == '[') { sub = sub.substr(9); - ret = "[" + OriginString(/*normalized=*/true) + std::move(sub); + ret = "[" + OriginString(StringType::PUBLIC, /*normalized=*/true) + std::move(sub); } else { - ret = "[" + OriginString(/*normalized=*/true) + "]" + std::move(sub); + ret = "[" + OriginString(StringType::PUBLIC, /*normalized=*/true) + "]" + std::move(sub); } return true; } @@ -275,7 +282,7 @@ public: } bool IsRange() const override { return false; } size_t GetSize() const override { return m_pubkey.size(); } - std::string ToString() const override { return m_xonly ? HexStr(m_pubkey).substr(2) : HexStr(m_pubkey); } + std::string ToString(StringType type) const override { return m_xonly ? HexStr(m_pubkey).substr(2) : HexStr(m_pubkey); } bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override { CKey key; @@ -293,7 +300,7 @@ public: } bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override { - ret = ToString(); + ret = ToString(StringType::PUBLIC); return true; } bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override @@ -421,9 +428,10 @@ public: return true; } - std::string ToString(bool normalized) const + std::string ToString(StringType type, bool normalized) const { - const bool use_apostrophe = !normalized && m_apostrophe; + // If StringType==COMPAT, always use the apostrophe to stay compatible with previous versions + const bool use_apostrophe = (!normalized && m_apostrophe) || type == StringType::COMPAT; std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path, /*apostrophe=*/use_apostrophe); if (IsRange()) { ret += "/*"; @@ -431,9 +439,9 @@ public: } return ret; } - std::string ToString() const override + std::string ToString(StringType type=StringType::PUBLIC) const override { - return ToString(/*normalized=*/false); + return ToString(type, /*normalized=*/false); } bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { @@ -449,7 +457,7 @@ public: bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override { if (m_derive == DeriveType::HARDENED) { - out = ToString(/*normalized=*/true); + out = ToString(StringType::PUBLIC, /*normalized=*/true); return true; } @@ -556,6 +564,7 @@ public: PUBLIC, PRIVATE, NORMALIZED, + COMPAT, // string calculation that mustn't change over time to stay compatible with previous software versions }; bool IsSolvable() const override @@ -607,6 +616,9 @@ public: case StringType::PUBLIC: tmp = pubkey->ToString(); break; + case StringType::COMPAT: + tmp = pubkey->ToString(PubkeyProvider::StringType::COMPAT); + break; } ret += tmp; } @@ -617,10 +629,10 @@ public: return true; } - std::string ToString() const final + std::string ToString(bool compat_format) const final { std::string ret; - ToStringHelper(nullptr, ret, StringType::PUBLIC); + ToStringHelper(nullptr, ret, compat_format ? StringType::COMPAT : StringType::PUBLIC); return AddChecksum(ret); } @@ -1780,7 +1792,7 @@ std::unique_ptr InferDescriptor(const CScript& script, const Signing uint256 DescriptorID(const Descriptor& desc) { - std::string desc_str = desc.ToString(); + std::string desc_str = desc.ToString(/*compat_format=*/true); uint256 id; CSHA256().Write((unsigned char*)desc_str.data(), desc_str.size()).Finalize(id.begin()); return id; diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 0684febf70..c6860c5cf6 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -106,7 +106,7 @@ struct Descriptor { virtual bool IsSolvable() const = 0; /** Convert the descriptor back to a string, undoing parsing. */ - virtual std::string ToString() const = 0; + virtual std::string ToString(bool compat_format=false) const = 0; /** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */ virtual bool IsSingleType() const = 0; diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp index 58166ae492..1bd2bf012f 100644 --- a/src/wallet/test/walletload_tests.cpp +++ b/src/wallet/test/walletload_tests.cpp @@ -20,7 +20,7 @@ public: explicit DummyDescriptor(const std::string& descriptor) : desc(descriptor) {}; ~DummyDescriptor() = default; - std::string ToString() const override { return desc; } + std::string ToString(bool compat_format) const override { return desc; } std::optional GetOutputType() const override { return OutputType::UNKNOWN; } bool IsRange() const override { return false; }