wallet: bugfix, always use apostrophe for spkm descriptor ID

As we update the descriptor's db record every time that
the wallet is loaded (at `TopUp` time), if the spkm ID differs
from the one in db, the wallet will enter in an unrecoverable
corruption state, and no soft version will be able to open
it anymore.

Because we cannot change the past, to stay compatible between
releases, we need to always use the apostrophe version for the
spkm IDs.
This commit is contained in:
furszy 2023-06-20 12:34:26 -03:00
parent 97a965d98f
commit 6a9510d2da
No known key found for this signature in database
GPG key ID: 5DD23CCC686AA623
3 changed files with 31 additions and 19 deletions

View file

@ -191,8 +191,13 @@ public:
/** Get the size of the generated public key(s) in bytes (33 or 65). */ /** Get the size of the generated public key(s) in bytes (33 or 65). */
virtual size_t GetSize() const = 0; 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. */ /** 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). */ /** Get the descriptor string form including private data (if available in arg). */
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0; virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
@ -212,9 +217,11 @@ class OriginPubkeyProvider final : public PubkeyProvider
std::unique_ptr<PubkeyProvider> m_provider; std::unique_ptr<PubkeyProvider> m_provider;
bool m_apostrophe; 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: public:
@ -228,12 +235,12 @@ public:
} }
bool IsRange() const override { return m_provider->IsRange(); } bool IsRange() const override { return m_provider->IsRange(); }
size_t GetSize() const override { return m_provider->GetSize(); } 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 bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
{ {
std::string sub; std::string sub;
if (!m_provider->ToPrivateString(arg, sub)) return false; if (!m_provider->ToPrivateString(arg, sub)) return false;
ret = "[" + OriginString() + "]" + std::move(sub); ret = "[" + OriginString(StringType::PUBLIC) + "]" + std::move(sub);
return true; return true;
} }
bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override 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. // and append that to our own origin string.
if (sub[0] == '[') { if (sub[0] == '[') {
sub = sub.substr(9); sub = sub.substr(9);
ret = "[" + OriginString(/*normalized=*/true) + std::move(sub); ret = "[" + OriginString(StringType::PUBLIC, /*normalized=*/true) + std::move(sub);
} else { } else {
ret = "[" + OriginString(/*normalized=*/true) + "]" + std::move(sub); ret = "[" + OriginString(StringType::PUBLIC, /*normalized=*/true) + "]" + std::move(sub);
} }
return true; return true;
} }
@ -275,7 +282,7 @@ public:
} }
bool IsRange() const override { return false; } bool IsRange() const override { return false; }
size_t GetSize() const override { return m_pubkey.size(); } 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 bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
{ {
CKey key; CKey key;
@ -293,7 +300,7 @@ public:
} }
bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
{ {
ret = ToString(); ret = ToString(StringType::PUBLIC);
return true; return true;
} }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
@ -421,9 +428,10 @@ public:
return true; 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); std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path, /*apostrophe=*/use_apostrophe);
if (IsRange()) { if (IsRange()) {
ret += "/*"; ret += "/*";
@ -431,9 +439,9 @@ public:
} }
return ret; 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 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 bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override
{ {
if (m_derive == DeriveType::HARDENED) { if (m_derive == DeriveType::HARDENED) {
out = ToString(/*normalized=*/true); out = ToString(StringType::PUBLIC, /*normalized=*/true);
return true; return true;
} }
@ -556,6 +564,7 @@ public:
PUBLIC, PUBLIC,
PRIVATE, PRIVATE,
NORMALIZED, NORMALIZED,
COMPAT, // string calculation that mustn't change over time to stay compatible with previous software versions
}; };
bool IsSolvable() const override bool IsSolvable() const override
@ -607,6 +616,9 @@ public:
case StringType::PUBLIC: case StringType::PUBLIC:
tmp = pubkey->ToString(); tmp = pubkey->ToString();
break; break;
case StringType::COMPAT:
tmp = pubkey->ToString(PubkeyProvider::StringType::COMPAT);
break;
} }
ret += tmp; ret += tmp;
} }
@ -617,10 +629,10 @@ public:
return true; return true;
} }
std::string ToString() const final std::string ToString(bool compat_format) const final
{ {
std::string ret; std::string ret;
ToStringHelper(nullptr, ret, StringType::PUBLIC); ToStringHelper(nullptr, ret, compat_format ? StringType::COMPAT : StringType::PUBLIC);
return AddChecksum(ret); return AddChecksum(ret);
} }
@ -1780,7 +1792,7 @@ std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const Signing
uint256 DescriptorID(const Descriptor& desc) uint256 DescriptorID(const Descriptor& desc)
{ {
std::string desc_str = desc.ToString(); std::string desc_str = desc.ToString(/*compat_format=*/true);
uint256 id; uint256 id;
CSHA256().Write((unsigned char*)desc_str.data(), desc_str.size()).Finalize(id.begin()); CSHA256().Write((unsigned char*)desc_str.data(), desc_str.size()).Finalize(id.begin());
return id; return id;

View file

@ -106,7 +106,7 @@ struct Descriptor {
virtual bool IsSolvable() const = 0; virtual bool IsSolvable() const = 0;
/** Convert the descriptor back to a string, undoing parsing. */ /** 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) */ /** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */
virtual bool IsSingleType() const = 0; virtual bool IsSingleType() const = 0;

View file

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