Skip to content

Commit f08d914

Browse files
committed
Merge bitcoin/bitcoin#27920: wallet: bugfix, always use apostrophe for spkm descriptor ID
5df988b test: add coverage for descriptor ID (furszy) 6a9510d wallet: bugfix, always use apostrophe for spkm descriptor ID (furszy) 97a965d refactor: extract descriptor ID calculation from spkm GetID() (furszy) 1d207e3 wallet: do not allow loading descriptor with an invalid ID (furszy) Pull request description: Aiming to fix #27915. As we re-write 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 (due to the storage of a descriptor with an ID that is not linked to any other descriptor record in DB), 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. ACKs for top commit: achow101: ACK 5df988b Sjors: tACK 5df988b Tree-SHA512: f63fc4aac7d21a4e515657471758d28857575e751865bfa359298f8b89b2568970029ca487a873c1786a5716325f453f06cd417ed193f3366417f6e8c2987332
2 parents 600c595 + 5df988b commit f08d914

File tree

6 files changed

+136
-73
lines changed

6 files changed

+136
-73
lines changed

src/script/descriptor.cpp

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,13 @@ struct PubkeyProvider
191191
/** Get the size of the generated public key(s) in bytes (33 or 65). */
192192
virtual size_t GetSize() const = 0;
193193

194+
enum class StringType {
195+
PUBLIC,
196+
COMPAT // string calculation that mustn't change over time to stay compatible with previous software versions
197+
};
198+
194199
/** Get the descriptor string form. */
195-
virtual std::string ToString() const = 0;
200+
virtual std::string ToString(StringType type=StringType::PUBLIC) const = 0;
196201

197202
/** Get the descriptor string form including private data (if available in arg). */
198203
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
@@ -212,9 +217,11 @@ class OriginPubkeyProvider final : public PubkeyProvider
212217
std::unique_ptr<PubkeyProvider> m_provider;
213218
bool m_apostrophe;
214219

215-
std::string OriginString(bool normalized=false) const
220+
std::string OriginString(StringType type, bool normalized=false) const
216221
{
217-
return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path, /*apostrophe=*/!normalized && m_apostrophe);
222+
// If StringType==COMPAT, always use the apostrophe to stay compatible with previous versions
223+
bool use_apostrophe = (!normalized && m_apostrophe) || type == StringType::COMPAT;
224+
return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path, use_apostrophe);
218225
}
219226

220227
public:
@@ -228,12 +235,12 @@ class OriginPubkeyProvider final : public PubkeyProvider
228235
}
229236
bool IsRange() const override { return m_provider->IsRange(); }
230237
size_t GetSize() const override { return m_provider->GetSize(); }
231-
std::string ToString() const override { return "[" + OriginString() + "]" + m_provider->ToString(); }
238+
std::string ToString(StringType type) const override { return "[" + OriginString(type) + "]" + m_provider->ToString(type); }
232239
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
233240
{
234241
std::string sub;
235242
if (!m_provider->ToPrivateString(arg, sub)) return false;
236-
ret = "[" + OriginString() + "]" + std::move(sub);
243+
ret = "[" + OriginString(StringType::PUBLIC) + "]" + std::move(sub);
237244
return true;
238245
}
239246
bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
@@ -245,9 +252,9 @@ class OriginPubkeyProvider final : public PubkeyProvider
245252
// and append that to our own origin string.
246253
if (sub[0] == '[') {
247254
sub = sub.substr(9);
248-
ret = "[" + OriginString(/*normalized=*/true) + std::move(sub);
255+
ret = "[" + OriginString(StringType::PUBLIC, /*normalized=*/true) + std::move(sub);
249256
} else {
250-
ret = "[" + OriginString(/*normalized=*/true) + "]" + std::move(sub);
257+
ret = "[" + OriginString(StringType::PUBLIC, /*normalized=*/true) + "]" + std::move(sub);
251258
}
252259
return true;
253260
}
@@ -275,7 +282,7 @@ class ConstPubkeyProvider final : public PubkeyProvider
275282
}
276283
bool IsRange() const override { return false; }
277284
size_t GetSize() const override { return m_pubkey.size(); }
278-
std::string ToString() const override { return m_xonly ? HexStr(m_pubkey).substr(2) : HexStr(m_pubkey); }
285+
std::string ToString(StringType type) const override { return m_xonly ? HexStr(m_pubkey).substr(2) : HexStr(m_pubkey); }
279286
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
280287
{
281288
CKey key;
@@ -293,7 +300,7 @@ class ConstPubkeyProvider final : public PubkeyProvider
293300
}
294301
bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
295302
{
296-
ret = ToString();
303+
ret = ToString(StringType::PUBLIC);
297304
return true;
298305
}
299306
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
@@ -421,19 +428,20 @@ class BIP32PubkeyProvider final : public PubkeyProvider
421428

422429
return true;
423430
}
424-
std::string ToString(bool normalized) const
431+
std::string ToString(StringType type, bool normalized) const
425432
{
426-
const bool use_apostrophe = !normalized && m_apostrophe;
433+
// If StringType==COMPAT, always use the apostrophe to stay compatible with previous versions
434+
const bool use_apostrophe = (!normalized && m_apostrophe) || type == StringType::COMPAT;
427435
std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path, /*apostrophe=*/use_apostrophe);
428436
if (IsRange()) {
429437
ret += "/*";
430438
if (m_derive == DeriveType::HARDENED) ret += use_apostrophe ? '\'' : 'h';
431439
}
432440
return ret;
433441
}
434-
std::string ToString() const override
442+
std::string ToString(StringType type=StringType::PUBLIC) const override
435443
{
436-
return ToString(/*normalized=*/false);
444+
return ToString(type, /*normalized=*/false);
437445
}
438446
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
439447
{
@@ -449,7 +457,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
449457
bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override
450458
{
451459
if (m_derive == DeriveType::HARDENED) {
452-
out = ToString(/*normalized=*/true);
460+
out = ToString(StringType::PUBLIC, /*normalized=*/true);
453461

454462
return true;
455463
}
@@ -556,6 +564,7 @@ class DescriptorImpl : public Descriptor
556564
PUBLIC,
557565
PRIVATE,
558566
NORMALIZED,
567+
COMPAT, // string calculation that mustn't change over time to stay compatible with previous software versions
559568
};
560569

561570
bool IsSolvable() const override
@@ -607,6 +616,9 @@ class DescriptorImpl : public Descriptor
607616
case StringType::PUBLIC:
608617
tmp = pubkey->ToString();
609618
break;
619+
case StringType::COMPAT:
620+
tmp = pubkey->ToString(PubkeyProvider::StringType::COMPAT);
621+
break;
610622
}
611623
ret += tmp;
612624
}
@@ -617,10 +629,10 @@ class DescriptorImpl : public Descriptor
617629
return true;
618630
}
619631

620-
std::string ToString() const final
632+
std::string ToString(bool compat_format) const final
621633
{
622634
std::string ret;
623-
ToStringHelper(nullptr, ret, StringType::PUBLIC);
635+
ToStringHelper(nullptr, ret, compat_format ? StringType::COMPAT : StringType::PUBLIC);
624636
return AddChecksum(ret);
625637
}
626638

@@ -1778,6 +1790,14 @@ std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const Signing
17781790
return InferScript(script, ParseScriptContext::TOP, provider);
17791791
}
17801792

1793+
uint256 DescriptorID(const Descriptor& desc)
1794+
{
1795+
std::string desc_str = desc.ToString(/*compat_format=*/true);
1796+
uint256 id;
1797+
CSHA256().Write((unsigned char*)desc_str.data(), desc_str.size()).Finalize(id.begin());
1798+
return id;
1799+
}
1800+
17811801
void DescriptorCache::CacheParentExtPubKey(uint32_t key_exp_pos, const CExtPubKey& xpub)
17821802
{
17831803
m_parent_xpubs[key_exp_pos] = xpub;

src/script/descriptor.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ struct Descriptor {
106106
virtual bool IsSolvable() const = 0;
107107

108108
/** Convert the descriptor back to a string, undoing parsing. */
109-
virtual std::string ToString() const = 0;
109+
virtual std::string ToString(bool compat_format=false) const = 0;
110110

111111
/** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */
112112
virtual bool IsSingleType() const = 0;
@@ -182,4 +182,9 @@ std::string GetDescriptorChecksum(const std::string& descriptor);
182182
*/
183183
std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const SigningProvider& provider);
184184

185+
/** Unique identifier that may not change over time, unless explicitly marked as not backwards compatible.
186+
* This is not part of BIP 380, not guaranteed to be interoperable and should not be exposed to the user.
187+
*/
188+
uint256 DescriptorID(const Descriptor& desc);
189+
185190
#endif // BITCOIN_SCRIPT_DESCRIPTOR_H

0 commit comments

Comments
 (0)