diff options
author | Sjors Provoost <sjors@sprovoost.nl> | 2023-04-04 18:33:08 +0200 |
---|---|---|
committer | Sjors Provoost <sjors@sprovoost.nl> | 2023-04-04 18:33:08 +0200 |
commit | bd13dc2f46ea10302a928fcf0f53b7aed77ad260 (patch) | |
tree | b7c0773b762bd3374bbc0e675e6f8b54b1e779d8 /src/script/descriptor.cpp | |
parent | fe1b3256888bd0e70d0c9655f565e139ec87b606 (diff) |
Switch hardened derivation marker to h in descriptors
This makes it easier to handle descriptor strings manually. E.g. an RPC call that takes an array of descriptors can now use '["desc": ".../0h/..."]'.
Both markers can still be parsed. The default for new descriptors is changed to h. In normalized form h is also used. For private keys the chosen marker is preserved in a round trip.
The hdkeypath field in getaddressinfo is also impacted by this change.
Diffstat (limited to 'src/script/descriptor.cpp')
-rw-r--r-- | src/script/descriptor.cpp | 83 |
1 files changed, 55 insertions, 28 deletions
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 857fee1818..cdb86f761e 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -197,7 +197,9 @@ public: /** Get the descriptor string form including private data (if available in arg). */ virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0; - /** Get the descriptor string form with the xpub at the last hardened derivation */ + /** Get the descriptor string form with the xpub at the last hardened derivation, + * and always use h for hardened derivation. + */ virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const = 0; /** Derive a private key, if private data is available in arg. */ @@ -208,14 +210,15 @@ class OriginPubkeyProvider final : public PubkeyProvider { KeyOriginInfo m_origin; std::unique_ptr<PubkeyProvider> m_provider; + bool m_apostrophe; - std::string OriginString() const + std::string OriginString(bool normalized=false) const { - return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path); + return HexStr(m_origin.fingerprint) + FormatHDKeypath(m_origin.path, /*apostrophe=*/!normalized && m_apostrophe); } public: - 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)) {} + 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) {} bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override { if (!m_provider->GetPubKey(pos, arg, key, info, read_cache, write_cache)) return false; @@ -242,9 +245,9 @@ public: // and append that to our own origin string. if (sub[0] == '[') { sub = sub.substr(9); - ret = "[" + OriginString() + std::move(sub); + ret = "[" + OriginString(/*normalized=*/true) + std::move(sub); } else { - ret = "[" + OriginString() + "]" + std::move(sub); + ret = "[" + OriginString(/*normalized=*/true) + "]" + std::move(sub); } return true; } @@ -312,6 +315,8 @@ class BIP32PubkeyProvider final : public PubkeyProvider CExtPubKey m_root_extkey; KeyPath m_path; DeriveType m_derive; + // Whether ' or h is used in harded derivation + bool m_apostrophe; bool GetExtKey(const SigningProvider& arg, CExtKey& ret) const { @@ -348,7 +353,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider } public: - 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) {} + 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) {} bool IsRange() const override { return m_derive != DeriveType::NO; } size_t GetSize() const override { return 33; } 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 @@ -416,31 +421,36 @@ public: return true; } - std::string ToString() const override + std::string ToString(bool normalized) const { - std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path); + const bool use_apostrophe = !normalized && m_apostrophe; + std::string ret = EncodeExtPubKey(m_root_extkey) + FormatHDKeypath(m_path, /*apostrophe=*/use_apostrophe); if (IsRange()) { ret += "/*"; - if (m_derive == DeriveType::HARDENED) ret += '\''; + if (m_derive == DeriveType::HARDENED) ret += use_apostrophe ? '\'' : 'h'; } return ret; } + std::string ToString() const override + { + return ToString(/*normalized=*/false); + } bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { CExtKey key; if (!GetExtKey(arg, key)) return false; - out = EncodeExtKey(key) + FormatHDKeypath(m_path); + out = EncodeExtKey(key) + FormatHDKeypath(m_path, /*apostrophe=*/m_apostrophe); if (IsRange()) { out += "/*"; - if (m_derive == DeriveType::HARDENED) out += '\''; + if (m_derive == DeriveType::HARDENED) out += m_apostrophe ? '\'' : 'h'; } return true; } bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override { - // For hardened derivation type, just return the typical string, nothing to normalize if (m_derive == DeriveType::HARDENED) { - out = ToString(); + out = ToString(/*normalized=*/true); + return true; } // Step backwards to find the last hardened step in the path @@ -1048,15 +1058,27 @@ enum class ParseScriptContext { P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf) }; -/** Parse a key path, being passed a split list of elements (the first element is ignored). */ -[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, std::string& error) +/** + * Parse a key path, being passed a split list of elements (the first element is ignored). + * + * @param[in] split BIP32 path string, using either ' or h for hardened derivation + * @param[out] out the key path + * @param[out] apostrophe only updated if hardened derivation is found + * @param[out] error parsing error message + * @returns false if parsing failed + **/ +[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, bool& apostrophe, std::string& error) { for (size_t i = 1; i < split.size(); ++i) { Span<const char> elem = split[i]; bool hardened = false; - if (elem.size() > 0 && (elem[elem.size() - 1] == '\'' || elem[elem.size() - 1] == 'h')) { - elem = elem.first(elem.size() - 1); - hardened = true; + if (elem.size() > 0) { + const char last = elem[elem.size() - 1]; + if (last == '\'' || last == 'h') { + elem = elem.first(elem.size() - 1); + hardened = true; + apostrophe = last == '\''; + } } uint32_t p; if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p)) { @@ -1072,7 +1094,7 @@ enum class ParseScriptContext { } /** Parse a public key that excludes origin information. */ -std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) +std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, bool& apostrophe, std::string& error) { using namespace spanparsing; @@ -1129,15 +1151,16 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S split.pop_back(); type = DeriveType::UNHARDENED; } else if (split.back() == Span{"*'"}.first(2) || split.back() == Span{"*h"}.first(2)) { + apostrophe = split.back() == Span{"*'"}.first(2); split.pop_back(); type = DeriveType::HARDENED; } - if (!ParseKeyPath(split, path, error)) return nullptr; + if (!ParseKeyPath(split, path, apostrophe, error)) return nullptr; if (extkey.key.IsValid()) { extpubkey = extkey.Neuter(); out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key); } - return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type); + return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe); } /** Parse a public key including origin information (if enabled). */ @@ -1150,7 +1173,11 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c error = "Multiple ']' characters found for a single pubkey"; return nullptr; } - if (origin_split.size() == 1) return ParsePubkeyInner(key_exp_index, origin_split[0], ctx, out, error); + // This is set if either the origin or path suffix contains a hardened derivation. + bool apostrophe = false; + if (origin_split.size() == 1) { + return ParsePubkeyInner(key_exp_index, origin_split[0], ctx, out, apostrophe, error); + } if (origin_split[0].empty() || origin_split[0][0] != '[') { error = strprintf("Key origin start '[ character expected but not found, got '%c' instead", origin_split[0].empty() ? /** empty, implies split char */ ']' : origin_split[0][0]); @@ -1171,10 +1198,10 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes"); assert(fpr_bytes.size() == 4); std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint); - if (!ParseKeyPath(slash_split, info.path, error)) return nullptr; - auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, error); + if (!ParseKeyPath(slash_split, info.path, apostrophe, error)) return nullptr; + auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, apostrophe, error); if (!provider) return nullptr; - return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider)); + return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider), apostrophe); } std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider) @@ -1182,7 +1209,7 @@ std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptCo std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false); KeyOriginInfo info; if (provider.GetKeyOrigin(pubkey.GetID(), info)) { - return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider), /*apostrophe=*/false); } return key_provider; } @@ -1195,7 +1222,7 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true); KeyOriginInfo info; if (provider.GetKeyOriginByXOnly(xkey, info)) { - return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider), /*apostrophe=*/false); } return key_provider; } |