Skip to content

Commit 6ef4468

Browse files
committed
backport: Dash adaptations for bitcoin#26076 (Switch hardened derivation marker to h)
Adapt Dash-specific wallet code, CoinJoin descriptor detection, and functional tests to use the 'h' hardened derivation marker introduced by bitcoin#26076. Updates both descriptor and legacy wallet test paths.
1 parent 1a275f0 commit 6ef4468

19 files changed

+127
-97
lines changed

doc/release-notes-26076.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
RPC
2+
---
3+
4+
- The `listdescriptors`, `decodepsbt` and similar RPC methods now show `h` rather than apostrophe (`'`) to indicate
5+
hardened derivation. This does not apply when using the `private` parameter, which
6+
matches the marker used when descriptor was generated or imported. Newly created
7+
wallets use `h`. This change makes it easier to handle descriptor strings manually.
8+
E.g. the `importdescriptors` RPC call is easiest to use `h` as the marker: `'["desc": ".../0h/..."]'`.
9+
With this change `listdescriptors` will use `h`, so you can copy-paste the result,
10+
without having to add escape characters or switch `'` to 'h' manually.
11+
Note that this changes the descriptor checksum.
12+
For legacy wallets the `hdkeypath` field in `getaddressinfo` is unchanged,
13+
nor is the serialization format of wallet dumps. (#26076)

src/script/descriptor.cpp

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +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 */
183+
/** Get the descriptor string form with the xpub at the last hardened derivation,
184+
* and always use h for hardened derivation.
185+
*/
184186
virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const = 0;
185187

186188
/** Derive a private key, if private data is available in arg. */
@@ -191,14 +193,15 @@ class OriginPubkeyProvider final : public PubkeyProvider
191193
{
192194
KeyOriginInfo m_origin;
193195
std::unique_ptr<PubkeyProvider> m_provider;
196+
bool m_apostrophe;
194197

195-
std::string OriginString() const
198+
std::string OriginString(bool normalized=false) const
196199
{
197-
return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path);
200+
return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path, /*apostrophe=*/!normalized && m_apostrophe);
198201
}
199202

200203
public:
201-
OriginPubkeyProvider(uint32_t exp_index, KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider) : PubkeyProvider(exp_index), m_origin(std::move(info)), m_provider(std::move(provider)) {}
204+
OriginPubkeyProvider(uint32_t exp_index, KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider, bool apostrophe) : PubkeyProvider(exp_index), m_origin(std::move(info)), m_provider(std::move(provider)), m_apostrophe(apostrophe) {}
202205
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
203206
{
204207
if (!m_provider->GetPubKey(pos, arg, key, info, read_cache, write_cache)) return false;
@@ -225,9 +228,9 @@ class OriginPubkeyProvider final : public PubkeyProvider
225228
// and append that to our own origin string.
226229
if (sub[0] == '[') {
227230
sub = sub.substr(9);
228-
ret = "[" + OriginString() + std::move(sub);
231+
ret = "[" + OriginString(/*normalized=*/true) + std::move(sub);
229232
} else {
230-
ret = "[" + OriginString() + "]" + std::move(sub);
233+
ret = "[" + OriginString(/*normalized=*/true) + "]" + std::move(sub);
231234
}
232235
return true;
233236
}
@@ -286,6 +289,8 @@ class BIP32PubkeyProvider final : public PubkeyProvider
286289
CExtPubKey m_root_extkey;
287290
KeyPath m_path;
288291
DeriveType m_derive;
292+
// Whether ' or h is used in harded derivation
293+
bool m_apostrophe;
289294

290295
bool GetExtKey(const SigningProvider& arg, CExtKey& ret) const
291296
{
@@ -322,7 +327,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
322327
}
323328

324329
public:
325-
BIP32PubkeyProvider(uint32_t exp_index, const CExtPubKey& extkey, KeyPath path, DeriveType derive) : PubkeyProvider(exp_index), m_root_extkey(extkey), m_path(std::move(path)), m_derive(derive) {}
330+
BIP32PubkeyProvider(uint32_t exp_index, const CExtPubKey& extkey, KeyPath path, DeriveType derive, bool apostrophe) : PubkeyProvider(exp_index), m_root_extkey(extkey), m_path(std::move(path)), m_derive(derive), m_apostrophe(apostrophe) {}
326331
bool IsRange() const override { return m_derive != DeriveType::NO; }
327332
size_t GetSize() const override { return 33; }
328333
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key_out, KeyOriginInfo& final_info_out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
@@ -390,31 +395,35 @@ class BIP32PubkeyProvider final : public PubkeyProvider
390395

391396
return true;
392397
}
393-
std::string ToString() const override
398+
std::string ToString(bool normalized) const
394399
{
395-
std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path);
400+
const bool use_apostrophe = !normalized && m_apostrophe;
401+
std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path, /*apostrophe=*/use_apostrophe);
396402
if (IsRange()) {
397403
ret += "/*";
398-
if (m_derive == DeriveType::HARDENED) ret += '\'';
404+
if (m_derive == DeriveType::HARDENED) ret += use_apostrophe ? '\'' : 'h';
399405
}
400406
return ret;
401407
}
408+
std::string ToString() const override
409+
{
410+
return ToString(/*normalized=*/false);
411+
}
402412
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
403413
{
404414
CExtKey key;
405415
if (!GetExtKey(arg, key)) return false;
406-
out = EncodeExtKey(key) + FormatHDKeypath(m_path);
416+
out = EncodeExtKey(key) + FormatHDKeypath(m_path, /*apostrophe=*/m_apostrophe);
407417
if (IsRange()) {
408418
out += "/*";
409-
if (m_derive == DeriveType::HARDENED) out += '\'';
419+
if (m_derive == DeriveType::HARDENED) out += m_apostrophe ? '\'' : 'h';
410420
}
411421
return true;
412422
}
413423
bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override
414424
{
415-
// For hardened derivation type, just return the typical string, nothing to normalize
416425
if (m_derive == DeriveType::HARDENED) {
417-
out = ToString();
426+
out = ToString(/*normalized=*/true);
418427
return true;
419428
}
420429
// Step backwards to find the last hardened step in the path
@@ -426,7 +435,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
426435
}
427436
// Either no derivation or all unhardened derivation
428437
if (i == -1) {
429-
out = ToString();
438+
out = ToString(/*normalized=*/true);
430439
return true;
431440
}
432441
// Get the path to the last hardened stup
@@ -806,15 +815,27 @@ enum class ParseScriptContext {
806815
P2SH, //!< Inside sh() (script becomes P2SH redeemScript)
807816
};
808817

809-
/** Parse a key path, being passed a split list of elements (the first element is ignored). */
810-
[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, std::string& error)
818+
/**
819+
* Parse a key path, being passed a split list of elements (the first element is ignored).
820+
*
821+
* @param[in] split BIP32 path string, using either ' or h for hardened derivation
822+
* @param[out] out the key path
823+
* @param[out] apostrophe only updated if hardened derivation is found
824+
* @param[out] error parsing error message
825+
* @returns false if parsing failed
826+
**/
827+
[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, bool& apostrophe, std::string& error)
811828
{
812829
for (size_t i = 1; i < split.size(); ++i) {
813830
Span<const char> elem = split[i];
814831
bool hardened = false;
815-
if (elem.size() > 0 && (elem[elem.size() - 1] == '\'' || elem[elem.size() - 1] == 'h')) {
816-
elem = elem.first(elem.size() - 1);
817-
hardened = true;
832+
if (elem.size() > 0) {
833+
const char last = elem[elem.size() - 1];
834+
if (last == '\'' || last == 'h') {
835+
elem = elem.first(elem.size() - 1);
836+
hardened = true;
837+
apostrophe = last == '\'';
838+
}
818839
}
819840
uint32_t p;
820841
if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p)) {
@@ -830,7 +851,7 @@ enum class ParseScriptContext {
830851
}
831852

832853
/** Parse a public key that excludes origin information. */
833-
std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
854+
std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, bool& apostrophe, std::string& error)
834855
{
835856
using namespace spanparsing;
836857

@@ -880,15 +901,16 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
880901
split.pop_back();
881902
type = DeriveType::UNHARDENED;
882903
} else if (split.back() == Span{"*'"}.first(2) || split.back() == Span{"*h"}.first(2)) {
904+
apostrophe = split.back() == Span{"*'"}.first(2);
883905
split.pop_back();
884906
type = DeriveType::HARDENED;
885907
}
886-
if (!ParseKeyPath(split, path, error)) return nullptr;
908+
if (!ParseKeyPath(split, path, apostrophe, error)) return nullptr;
887909
if (extkey.key.IsValid()) {
888910
extpubkey = extkey.Neuter();
889911
out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key);
890912
}
891-
return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type);
913+
return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe);
892914
}
893915

894916
/** Parse a public key including origin information (if enabled). */
@@ -901,7 +923,8 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
901923
error = "Multiple ']' characters found for a single pubkey";
902924
return nullptr;
903925
}
904-
if (origin_split.size() == 1) return ParsePubkeyInner(key_exp_index, origin_split[0], ctx, out, error);
926+
bool apostrophe = false;
927+
if (origin_split.size() == 1) return ParsePubkeyInner(key_exp_index, origin_split[0], ctx, out, apostrophe, error);
905928
if (origin_split[0].empty() || origin_split[0][0] != '[') {
906929
error = strprintf("Key origin start '[ character expected but not found, got '%c' instead",
907930
origin_split[0].empty() ? /** empty, implies split char */ ']' : origin_split[0][0]);
@@ -922,10 +945,10 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
922945
static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes");
923946
assert(fpr_bytes.size() == 4);
924947
std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint);
925-
if (!ParseKeyPath(slash_split, info.path, error)) return nullptr;
926-
auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, error);
948+
if (!ParseKeyPath(slash_split, info.path, apostrophe, error)) return nullptr;
949+
auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, apostrophe, error);
927950
if (!provider) return nullptr;
928-
return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider));
951+
return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider), apostrophe);
929952
}
930953

931954
/** Parse a script in a particular context. */
@@ -1058,7 +1081,7 @@ std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptCo
10581081
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey);
10591082
KeyOriginInfo info;
10601083
if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
1061-
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
1084+
return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider), /*apostrophe=*/false);
10621085
}
10631086
return key_provider;
10641087
}

0 commit comments

Comments
 (0)