Skip to content

Commit 9be1437

Browse files
committed
descriptors: Add ToNormalizedString and tests
1 parent 89a8299 commit 9be1437

File tree

3 files changed

+138
-40
lines changed

3 files changed

+138
-40
lines changed

src/script/descriptor.cpp

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ struct PubkeyProvider
179179
/** Get the descriptor string form including private data (if available in arg). */
180180
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
181181

182+
/** Get the descriptor string form with the xpub at the last hardened derivation */
183+
virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const = 0;
184+
182185
/** Derive a private key, if private data is available in arg. */
183186
virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0;
184187
};
@@ -212,6 +215,21 @@ class OriginPubkeyProvider final : public PubkeyProvider
212215
ret = "[" + OriginString() + "]" + std::move(sub);
213216
return true;
214217
}
218+
bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override
219+
{
220+
std::string sub;
221+
if (!m_provider->ToNormalizedString(arg, sub, priv)) return false;
222+
// If m_provider is a BIP32PubkeyProvider, we may get a string formatted like a OriginPubkeyProvider
223+
// In that case, we need to strip out the leading square bracket and fingerprint from the substring,
224+
// and append that to our own origin string.
225+
if (sub[0] == '[') {
226+
sub = sub.substr(9);
227+
ret = "[" + OriginString() + std::move(sub);
228+
} else {
229+
ret = "[" + OriginString() + "]" + std::move(sub);
230+
}
231+
return true;
232+
}
215233
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
216234
{
217235
return m_provider->GetPrivKey(pos, arg, key);
@@ -243,6 +261,12 @@ class ConstPubkeyProvider final : public PubkeyProvider
243261
ret = EncodeSecret(key);
244262
return true;
245263
}
264+
bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override
265+
{
266+
if (priv) return ToPrivateString(arg, ret);
267+
ret = ToString();
268+
return true;
269+
}
246270
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
247271
{
248272
return arg.GetKey(m_pubkey.GetID(), key);
@@ -386,6 +410,56 @@ class BIP32PubkeyProvider final : public PubkeyProvider
386410
}
387411
return true;
388412
}
413+
bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override
414+
{
415+
// For hardened derivation type, just return the typical string, nothing to normalize
416+
if (m_derive == DeriveType::HARDENED) {
417+
if (priv) return ToPrivateString(arg, out);
418+
out = ToString();
419+
return true;
420+
}
421+
// Step backwards to find the last hardened step in the path
422+
int i = (int)m_path.size() - 1;
423+
for (; i >= 0; --i) {
424+
if (m_path.at(i) >> 31) {
425+
break;
426+
}
427+
}
428+
// Either no derivation or all unhardened derivation
429+
if (i == -1) {
430+
if (priv) return ToPrivateString(arg, out);
431+
out = ToString();
432+
return true;
433+
}
434+
// Derive the xpub at the last hardened step
435+
CExtKey xprv;
436+
if (!GetExtKey(arg, xprv)) return false;
437+
KeyOriginInfo origin;
438+
int k = 0;
439+
for (; k <= i; ++k) {
440+
// Derive
441+
xprv.Derive(xprv, m_path.at(k));
442+
// Add to the path
443+
origin.path.push_back(m_path.at(k));
444+
// First derivation element, get the fingerprint for origin
445+
if (k == 0) {
446+
std::copy(xprv.vchFingerprint, xprv.vchFingerprint + 4, origin.fingerprint);
447+
}
448+
}
449+
// Build the remaining path
450+
KeyPath end_path;
451+
for (; k < (int)m_path.size(); ++k) {
452+
end_path.push_back(m_path.at(k));
453+
}
454+
// Build the string
455+
std::string origin_str = HexStr(origin.fingerprint) + FormatHDKeypath(origin.path);
456+
out = "[" + origin_str + "]" + (priv ? EncodeExtKey(xprv) : EncodeExtPubKey(xprv.Neuter())) + FormatHDKeypath(end_path);
457+
if (IsRange()) {
458+
out += "/*";
459+
assert(m_derive == DeriveType::UNHARDENED);
460+
}
461+
return true;
462+
}
389463
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
390464
{
391465
CExtKey extkey;
@@ -449,15 +523,17 @@ class DescriptorImpl : public Descriptor
449523
return false;
450524
}
451525

452-
bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv) const
526+
bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv, bool normalized) const
453527
{
454528
std::string extra = ToStringExtra();
455529
size_t pos = extra.size() > 0 ? 1 : 0;
456530
std::string ret = m_name + "(" + extra;
457531
for (const auto& pubkey : m_pubkey_args) {
458532
if (pos++) ret += ",";
459533
std::string tmp;
460-
if (priv) {
534+
if (normalized) {
535+
if (!pubkey->ToNormalizedString(*arg, tmp, priv)) return false;
536+
} else if (priv) {
461537
if (!pubkey->ToPrivateString(*arg, tmp)) return false;
462538
} else {
463539
tmp = pubkey->ToString();
@@ -467,7 +543,7 @@ class DescriptorImpl : public Descriptor
467543
if (m_subdescriptor_arg) {
468544
if (pos++) ret += ",";
469545
std::string tmp;
470-
if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv)) return false;
546+
if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv, normalized)) return false;
471547
ret += std::move(tmp);
472548
}
473549
out = std::move(ret) + ")";
@@ -477,13 +553,20 @@ class DescriptorImpl : public Descriptor
477553
std::string ToString() const final
478554
{
479555
std::string ret;
480-
ToStringHelper(nullptr, ret, false);
556+
ToStringHelper(nullptr, ret, false, false);
481557
return AddChecksum(ret);
482558
}
483559

484560
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final
485561
{
486-
bool ret = ToStringHelper(&arg, out, true);
562+
bool ret = ToStringHelper(&arg, out, true, false);
563+
out = AddChecksum(out);
564+
return ret;
565+
}
566+
567+
bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override final
568+
{
569+
bool ret = ToStringHelper(&arg, out, priv, true);
487570
out = AddChecksum(out);
488571
return ret;
489572
}

src/script/descriptor.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ struct Descriptor {
9393
/** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */
9494
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
9595

96+
/** Convert the descriptor to a normalized string. Normalized descriptors have the xpub at the last hardened step. This fails if the provided provider does not have the private keys to derive that xpub. */
97+
virtual bool ToNormalizedString(const SigningProvider& provider, std::string& out, bool priv) const = 0;
98+
9699
/** Expand a descriptor at a specified position.
97100
*
98101
* @param[in] pos The position at which to expand the descriptor. If IsRange() is false, this is ignored.

0 commit comments

Comments
 (0)