Skip to content

Commit db656db

Browse files
committed
Merge #19136: wallet: add parent_desc to getaddressinfo
de6b389 tests: Test getaddressinfo parent_desc (Andrew Chow) e4ac869 rpc: Add parent descriptor to getaddressinfo output (Andrew Chow) bbe4a36 wallet: Add GetDescriptorString to DescriptorScriptPubKeyMan (Andrew Chow) 9be1437 descriptors: Add ToNormalizedString and tests (Andrew Chow) Pull request description: Adds `parent_desc` field to the `getaddressinfo` RPC to export a public descriptor. Using the given address, `getaddressinfo` will look up which `DescriptorScriptPubKeyMan` can be used to produce that address. It will then return the descriptor for that `DescriptorScriptPubKeyMan` in the `parent_desc` field. The descriptor will be in a normalized form where the xpub at the last hardened step is derived so that the descriptor can be imported to other wallets. Tests are added to check that the correct descriptor is being returned for the wallet's addresses and that these descriptors can be imported and used in other wallets. As part of this PR, a `ToNormalizedString` function is added to the descriptor classes. This really only has an effect on `BIP32PubkeyProvider`s that have hardened derivation steps. Tests are added to check that normalized descriptors are returned. ACKs for top commit: Sjors: utACK de6b389 S3RK: Tested ACK de6b389 jonatack: Tested ACK de6b389 modulo a few minor comments fjahr: Code review ACK de6b389 meshcollider: Tested ACK de6b389 Tree-SHA512: a633e4a39f2abbd95afd7488484cfa66fdd2651dac59fe59f2b80a0940a2a4a13acf889c534a6948903d701484a2ba1218e3081feafe0b9a720dccfa9e43ca2b
2 parents 9017d55 + de6b389 commit db656db

File tree

7 files changed

+219
-40
lines changed

7 files changed

+219
-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)