Skip to content

Commit 8bacdbf

Browse files
meshcolliderknst
authored andcommitted
Merge bitcoin#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
1 parent f567de0 commit 8bacdbf

File tree

7 files changed

+206
-33
lines changed

7 files changed

+206
-33
lines changed

src/script/descriptor.cpp

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

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

453-
bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv) const
527+
bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv, bool normalized) const
454528
{
455529
std::string extra = ToStringExtra();
456530
size_t pos = extra.size() > 0 ? 1 : 0;
457531
std::string ret = m_name + "(" + extra;
458532
for (const auto& pubkey : m_pubkey_args) {
459533
if (pos++) ret += ",";
460534
std::string tmp;
461-
if (priv) {
535+
if (normalized) {
536+
if (!pubkey->ToNormalizedString(*arg, tmp, priv)) return false;
537+
} else if (priv) {
462538
if (!pubkey->ToPrivateString(*arg, tmp)) return false;
463539
} else {
464540
tmp = pubkey->ToString();
@@ -468,7 +544,7 @@ class DescriptorImpl : public Descriptor
468544
if (m_subdescriptor_arg) {
469545
if (pos++) ret += ",";
470546
std::string tmp;
471-
if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv)) return false;
547+
if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv, normalized)) return false;
472548
ret += std::move(tmp);
473549
}
474550
out = std::move(ret) + ")";
@@ -478,13 +554,20 @@ class DescriptorImpl : public Descriptor
478554
std::string ToString() const final
479555
{
480556
std::string ret;
481-
ToStringHelper(nullptr, ret, false);
557+
ToStringHelper(nullptr, ret, false, false);
482558
return AddChecksum(ret);
483559
}
484560

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

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)