Skip to content

Commit 544d333

Browse files
Merge dashpay#5981: backport: bitcoin#19136, bitcoin#21063, bitcoin#21277, bitcoin#21302, partial bitcoin#20267 - descriptor wallets part IV
ceefab5 fix: feature_backwards compatible works now with as expected if no bdb compiled (Konstantin Akimov) b20f812 fix: follow-up fixes for functional tests used protx (Konstantin Akimov) 655146d Merge bitcoin#21302: wallet: createwallet examples for descriptor wallets (W. J. van der Laan) 99a8b60 Merge bitcoin#21063: wallet, rpc: update listdescriptors response format (fanquake) 6ee2c7c Merge bitcoin#21277: wallet: listdescriptors uses normalized descriptor form (Wladimir J. van der Laan) 8bacdbf Merge bitcoin#19136: wallet: add parent_desc to getaddressinfo (Samuel Dobson) f567de0 chore: release notes for 5965 with wallet tool improvements (Konstantin Akimov) 0daf360 chore: add TODO to implement mnemonic for descriptor wallets (Konstantin Akimov) 5016294 chore: move functional test wallet_multiwallet from category "slow 5 minutes" to "fast test" (Konstantin Akimov) ef7ce87 fix: remove workarounds introduced due to missing bitcoin#20267 (bdb is not compiled) (Konstantin Akimov) 06b2d85 partial Merge bitcoin#20267: Disable and fix tests for when BDB is not compiled (Wladimir J. van der Laan) Pull request description: ## Issue being fixed or feature implemented dashpay/dash-issues#59 ## Extra notes This commit `chore: move functional test wallet_multiwallet from category "slow 5 minutes" to "fast test"` is not directly connected to descriptor wallets, but added to this PR due to conflicts with 20267 ## What was done? It steadily improves support of descriptor wallets in Dash core. Done backports and related fixes: - partial bitcoin#20267 - bitcoin#19136 - bitcoin#21277 - bitcoin#21063 - bitcoin#21302 Beside backports and related fixes, this PR includes release notes for previous batch of backports for descriptor wallets support dashpay#5965 ## How Has This Been Tested? Run unit functional tests ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone Top commit has no ACKs. Tree-SHA512: f4b2033f8c4fa1d0f72cfc31378909703b3ae8f44748989ff00c3e71311ac80ac37837137133c7e4a166823a941ed7df10efa09c89f5b213f3c8ede7d3d6e8f4
2 parents 9240967 + ceefab5 commit 544d333

22 files changed

+612
-215
lines changed

doc/release-notes-5965.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Wallet Tool Enhancements
2+
3+
This release introduces several improvements and new features to the `dash-wallet` tool, making it more versatile and user-friendly for managing Dash wallets.
4+
5+
### Wallet Version Bump
6+
7+
Wallets created with the `dash-wallet` tool will now utilize the `FEATURE_LATEST` version of wallet which is the HD (Hierarchical Deterministic) wallets with HD chain inside.
8+
9+
### New functionality
10+
- new command line argument `-descriptors` to enable _experimental_ support of Descriptor wallets. It lets users to create descriptor wallets directly from the command line. This change aims to align the command-line interface with the `createwallet` RPC, promoting the use of descriptor wallets which offer a more robust and flexible way to manage wallet addresses and keys.
11+
- new command line argument `-usehd` which let to create non-Hierarchical Deterministic (non-HD) wallets with the `wallettool` for compatibility reasons since default version is bumped to HD version
12+
- new commands `dump` and `createfromdump` have been added, enhancing the wallet's storage migration capabilities. The `dump` command allows for exporting every key-value pair from the wallet as comma-separated hex values, facilitating a storage agnostic dump. Meanwhile, the `createfromdump` command enables the creation of a new wallet file using the records specified in a dump file. These commands are similar to BDB's `db_dump` and `db_load` tools and are crucial for manual wallet file construction for testing or migration purposes.

src/rpc/evo.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,7 @@ static UniValue protx_register_common_wrapper(const JSONRPCRequest& request,
777777
return ret;
778778
} else {
779779
// lets prove we own the collateral
780+
// TODO: make collateral works with Descriptor wallets too
780781
const LegacyScriptPubKeyMan* spk_man = wallet->GetLegacyScriptPubKeyMan();
781782
if (!spk_man) {
782783
throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command");

src/rpc/util.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,18 +158,81 @@ bool ParseBoolV(const UniValue& v, const std::string &strName)
158158
throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be true, false, yes, no, 1 or 0 (not '"+strBool+"')");
159159
}
160160

161+
namespace {
162+
163+
/**
164+
* Quote an argument for shell.
165+
*
166+
* @note This is intended for help, not for security-sensitive purposes.
167+
*/
168+
std::string ShellQuote(const std::string& s)
169+
{
170+
std::string result;
171+
result.reserve(s.size() * 2);
172+
for (const char ch: s) {
173+
if (ch == '\'') {
174+
result += "'\''";
175+
} else {
176+
result += ch;
177+
}
178+
}
179+
return "'" + result + "'";
180+
}
181+
182+
/**
183+
* Shell-quotes the argument if it needs quoting, else returns it literally, to save typing.
184+
*
185+
* @note This is intended for help, not for security-sensitive purposes.
186+
*/
187+
std::string ShellQuoteIfNeeded(const std::string& s)
188+
{
189+
for (const char ch: s) {
190+
if (ch == ' ' || ch == '\'' || ch == '"') {
191+
return ShellQuote(s);
192+
}
193+
}
194+
195+
return s;
196+
}
197+
198+
}
199+
161200
std::string HelpExampleCli(const std::string& methodname, const std::string& args)
162201
{
163202
return "> dash-cli " + methodname + " " + args + "\n";
164203
}
165204

205+
std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList& args)
206+
{
207+
std::string result = "> dash-cli -named " + methodname;
208+
for (const auto& argpair: args) {
209+
const auto& value = argpair.second.isStr()
210+
? argpair.second.get_str()
211+
: argpair.second.write();
212+
result += " " + argpair.first + "=" + ShellQuoteIfNeeded(value);
213+
}
214+
result += "\n";
215+
return result;
216+
}
217+
166218
std::string HelpExampleRpc(const std::string& methodname, const std::string& args)
167219
{
168220
return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", "
169221
"\"method\": \"" + methodname + "\", \"params\": [" + args + "]}' -H 'content-type: text/plain;'"
170222
" http://127.0.0.1:9998/\n";
171223
}
172224

225+
std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& args)
226+
{
227+
UniValue params(UniValue::VOBJ);
228+
for (const auto& param: args) {
229+
params.pushKV(param.first, param.second);
230+
}
231+
232+
return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", "
233+
"\"method\": \"" + methodname + "\", \"params\": " + params.write() + "}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n";
234+
}
235+
173236
// Converts a hex string to a public key if possible
174237
CPubKey HexToPubKey(const std::string& hex_in)
175238
{

src/rpc/util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,12 @@ extern double ParseDoubleV(const UniValue& v, const std::string &strName);
8585
extern bool ParseBoolV(const UniValue& v, const std::string &strName);
8686

8787
extern CAmount AmountFromValue(const UniValue& value);
88+
89+
using RPCArgList = std::vector<std::pair<std::string, UniValue>>;
8890
extern std::string HelpExampleCli(const std::string& methodname, const std::string& args);
91+
extern std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList& args);
8992
extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args);
93+
extern std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& args);
9094

9195
CPubKey HexToPubKey(const std::string& hex_in);
9296
CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& addr_in);

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)