descriptor: Add ToCanonicalString

This commit is contained in:
Ava Chow
2026-08-20 16:36:08 -07:00
parent 1d87af26ce
commit 35d6a60dbf
4 changed files with 30 additions and 2 deletions

View File

@@ -203,6 +203,7 @@ public:
enum class StringType {
PUBLIC,
CANONICAL, // string calculation that always use h
COMPAT // string calculation that mustn't change over time to stay compatible with previous software versions
};
@@ -259,7 +260,7 @@ class OriginPubkeyProvider final : public PubkeyProvider
std::string OriginString(StringType type, bool normalized=false) const
{
// If StringType==COMPAT, always use the apostrophe to stay compatible with previous versions
bool use_apostrophe = (!normalized && m_apostrophe) || type == StringType::COMPAT;
bool use_apostrophe = (type != StringType::CANONICAL && !normalized && m_apostrophe) || type == StringType::COMPAT;
return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path, use_apostrophe);
}
@@ -509,7 +510,7 @@ public:
std::string ToString(StringType type, bool normalized) const
{
// If StringType==COMPAT, always use the apostrophe to stay compatible with previous versions
const bool use_apostrophe = (!normalized && m_apostrophe) || type == StringType::COMPAT;
const bool use_apostrophe = (type != StringType::CANONICAL && !normalized && m_apostrophe) || type == StringType::COMPAT;
std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path, /*apostrophe=*/use_apostrophe);
if (IsRange()) {
ret += "/*";
@@ -874,6 +875,7 @@ public:
PUBLIC,
PRIVATE,
NORMALIZED,
CANONICAL,
COMPAT, // string calculation that mustn't change over time to stay compatible with previous software versions
};
@@ -960,6 +962,9 @@ public:
case StringType::COMPAT:
tmp = pubkey->ToString(PubkeyProvider::StringType::COMPAT);
break;
case StringType::CANONICAL:
tmp = pubkey->ToString(PubkeyProvider::StringType::CANONICAL);
break;
}
ret += tmp;
}
@@ -979,6 +984,13 @@ public:
return AddChecksum(ret);
}
std::string ToCanonicalString() const final
{
std::string ret;
ToStringHelper(nullptr, ret, StringType::CANONICAL);
return AddChecksum(ret);
}
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
{
bool has_priv_key{ToStringHelper(&arg, out, StringType::PRIVATE)};
@@ -1667,6 +1679,9 @@ public:
// must be preserved for wallets with Miniscript descriptors to be loaded
ret = m_pubkeys[key]->ToString(PubkeyProvider::StringType::PUBLIC);
break;
case DescriptorImpl::StringType::CANONICAL:
ret = m_pubkeys[key]->ToString(PubkeyProvider::StringType::CANONICAL);
break;
}
return ret;
}

View File

@@ -118,6 +118,11 @@ struct Descriptor {
/** Convert the descriptor back to a string, undoing parsing. */
virtual std::string ToString(bool compat_format=false) const = 0;
/** Convert the descriptor to the canonical string.
* The canonical string is the same as the public string but always uses h as the hardened indicator
*/
virtual std::string ToCanonicalString() const = 0;
/** Whether this descriptor will return at most one scriptPubKey or multiple (aka is or is not combo) */
virtual bool IsSingleType() const = 0;

View File

@@ -226,14 +226,21 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
// Otherwise check that they serialize back to the public version.
std::string pub1 = parse_priv->ToString();
std::string pub2 = parse_pub->ToString();
std::string canonical = UseHInsteadOfApostrophe(pub);
if (expected_pub) {
BOOST_CHECK_MESSAGE(EqualDescriptor(*expected_pub, pub1), "Private ser: " + pub1 + " Public desc: " + *expected_pub);
BOOST_CHECK_MESSAGE(EqualDescriptor(*expected_pub, pub2), "Public ser: " + pub2 + " Public desc: " + *expected_pub);
canonical = UseHInsteadOfApostrophe(*expected_pub);
} else {
BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub1), "Private ser: " + pub1 + " Public desc: " + pub);
BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub2), "Public ser: " + pub2 + " Public desc: " + pub);
}
std::string priv_canonical = parse_priv->ToCanonicalString();
std::string pub_canonical = parse_pub->ToCanonicalString();
BOOST_CHECK_MESSAGE(EqualDescriptor(priv_canonical, canonical), "Private ser: " + priv_canonical + " Expected desc: " + canonical);
BOOST_CHECK_MESSAGE(EqualDescriptor(pub_canonical, canonical), "Public ser: " + pub_canonical + " Expected desc: " + canonical);
// Check that the COMPAT identifier did not change
if (op_desc_id) {
BOOST_CHECK_MESSAGE(DescriptorID(*parse_priv) == *op_desc_id, "DescriptorID() " + DescriptorID(*parse_priv).ToString() + " does not match for priv " + prv);

View File

@@ -22,6 +22,7 @@ public:
~DummyDescriptor() = default;
std::string ToString(bool compat_format) const override { return desc; }
std::string ToCanonicalString() const override { return desc; }
std::optional<OutputType> GetOutputType() const override { return OutputType::UNKNOWN; }
bool IsRange() const override { return false; }