@@ -197,7 +197,9 @@ struct PubkeyProvider
197197 /* * Get the descriptor string form including private data (if available in arg). */
198198 virtual bool ToPrivateString (const SigningProvider& arg, std::string& out) const = 0;
199199
200- /* * Get the descriptor string form with the xpub at the last hardened derivation */
200+ /* * Get the descriptor string form with the xpub at the last hardened derivation,
201+ * and always use h for hardened derivation.
202+ */
201203 virtual bool ToNormalizedString (const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr ) const = 0;
202204
203205 /* * Derive a private key, if private data is available in arg. */
@@ -208,14 +210,15 @@ class OriginPubkeyProvider final : public PubkeyProvider
208210{
209211 KeyOriginInfo m_origin;
210212 std::unique_ptr<PubkeyProvider> m_provider;
213+ bool m_apostrophe;
211214
212- std::string OriginString () const
215+ std::string OriginString (bool normalized= false ) const
213216 {
214- return HexStr (m_origin.fingerprint ) + FormatHDKeypath (m_origin.path );
217+ return HexStr (m_origin.fingerprint ) + FormatHDKeypath (m_origin.path , /* apostrophe= */ !normalized && m_apostrophe );
215218 }
216219
217220public:
218- 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)) {}
221+ 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 ) {}
219222 bool GetPubKey (int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr , DescriptorCache* write_cache = nullptr ) const override
220223 {
221224 if (!m_provider->GetPubKey (pos, arg, key, info, read_cache, write_cache)) return false ;
@@ -242,9 +245,9 @@ class OriginPubkeyProvider final : public PubkeyProvider
242245 // and append that to our own origin string.
243246 if (sub[0 ] == ' [' ) {
244247 sub = sub.substr (9 );
245- ret = " [" + OriginString () + std::move (sub);
248+ ret = " [" + OriginString (/* normalized= */ true ) + std::move (sub);
246249 } else {
247- ret = " [" + OriginString () + " ]" + std::move (sub);
250+ ret = " [" + OriginString (/* normalized= */ true ) + " ]" + std::move (sub);
248251 }
249252 return true ;
250253 }
@@ -312,6 +315,8 @@ class BIP32PubkeyProvider final : public PubkeyProvider
312315 CExtPubKey m_root_extkey;
313316 KeyPath m_path;
314317 DeriveType m_derive;
318+ // Whether ' or h is used in harded derivation
319+ bool m_apostrophe;
315320
316321 bool GetExtKey (const SigningProvider& arg, CExtKey& ret) const
317322 {
@@ -348,7 +353,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
348353 }
349354
350355public:
351- 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) {}
356+ 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 ) {}
352357 bool IsRange () const override { return m_derive != DeriveType::NO; }
353358 size_t GetSize () const override { return 33 ; }
354359 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 @@ class BIP32PubkeyProvider final : public PubkeyProvider
416421
417422 return true ;
418423 }
419- std::string ToString () const override
424+ std::string ToString (bool normalized ) const
420425 {
421- std::string ret = EncodeExtPubKey (m_root_extkey) + FormatHDKeypath (m_path);
426+ const bool use_apostrophe = !normalized && m_apostrophe;
427+ std::string ret = EncodeExtPubKey (m_root_extkey) + FormatHDKeypath (m_path, /* apostrophe=*/ use_apostrophe);
422428 if (IsRange ()) {
423429 ret += " /*" ;
424- if (m_derive == DeriveType::HARDENED) ret += ' \' ' ;
430+ if (m_derive == DeriveType::HARDENED) ret += use_apostrophe ? ' \' ' : ' h ' ;
425431 }
426432 return ret;
427433 }
434+ std::string ToString () const override
435+ {
436+ return ToString (/* normalized=*/ false );
437+ }
428438 bool ToPrivateString (const SigningProvider& arg, std::string& out) const override
429439 {
430440 CExtKey key;
431441 if (!GetExtKey (arg, key)) return false ;
432- out = EncodeExtKey (key) + FormatHDKeypath (m_path);
442+ out = EncodeExtKey (key) + FormatHDKeypath (m_path, /* apostrophe= */ m_apostrophe );
433443 if (IsRange ()) {
434444 out += " /*" ;
435- if (m_derive == DeriveType::HARDENED) out += ' \' ' ;
445+ if (m_derive == DeriveType::HARDENED) out += m_apostrophe ? ' \' ' : ' h ' ;
436446 }
437447 return true ;
438448 }
439449 bool ToNormalizedString (const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override
440450 {
441- // For hardened derivation type, just return the typical string, nothing to normalize
442451 if (m_derive == DeriveType::HARDENED) {
443- out = ToString ();
452+ out = ToString (/* normalized=*/ true );
453+
444454 return true ;
445455 }
446456 // Step backwards to find the last hardened step in the path
@@ -1048,15 +1058,27 @@ enum class ParseScriptContext {
10481058 P2TR, // !< Inside tr() (either internal key, or BIP342 script leaf)
10491059};
10501060
1051- /* * Parse a key path, being passed a split list of elements (the first element is ignored). */
1052- [[nodiscard]] bool ParseKeyPath (const std::vector<Span<const char >>& split, KeyPath& out, std::string& error)
1061+ /* *
1062+ * Parse a key path, being passed a split list of elements (the first element is ignored).
1063+ *
1064+ * @param[in] split BIP32 path string, using either ' or h for hardened derivation
1065+ * @param[out] out the key path
1066+ * @param[out] apostrophe only updated if hardened derivation is found
1067+ * @param[out] error parsing error message
1068+ * @returns false if parsing failed
1069+ **/
1070+ [[nodiscard]] bool ParseKeyPath (const std::vector<Span<const char >>& split, KeyPath& out, bool & apostrophe, std::string& error)
10531071{
10541072 for (size_t i = 1 ; i < split.size (); ++i) {
10551073 Span<const char > elem = split[i];
10561074 bool hardened = false ;
1057- if (elem.size () > 0 && (elem[elem.size () - 1 ] == ' \' ' || elem[elem.size () - 1 ] == ' h' )) {
1058- elem = elem.first (elem.size () - 1 );
1059- hardened = true ;
1075+ if (elem.size () > 0 ) {
1076+ const char last = elem[elem.size () - 1 ];
1077+ if (last == ' \' ' || last == ' h' ) {
1078+ elem = elem.first (elem.size () - 1 );
1079+ hardened = true ;
1080+ apostrophe = last == ' \' ' ;
1081+ }
10601082 }
10611083 uint32_t p;
10621084 if (!ParseUInt32 (std::string (elem.begin (), elem.end ()), &p)) {
@@ -1072,7 +1094,7 @@ enum class ParseScriptContext {
10721094}
10731095
10741096/* * Parse a public key that excludes origin information. */
1075- std::unique_ptr<PubkeyProvider> ParsePubkeyInner (uint32_t key_exp_index, const Span<const char >& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
1097+ std::unique_ptr<PubkeyProvider> ParsePubkeyInner (uint32_t key_exp_index, const Span<const char >& sp, ParseScriptContext ctx, FlatSigningProvider& out, bool & apostrophe, std::string& error)
10761098{
10771099 using namespace spanparsing ;
10781100
@@ -1129,15 +1151,16 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
11291151 split.pop_back ();
11301152 type = DeriveType::UNHARDENED;
11311153 } else if (split.back () == Span{" *'" }.first (2 ) || split.back () == Span{" *h" }.first (2 )) {
1154+ apostrophe = split.back () == Span{" *'" }.first (2 );
11321155 split.pop_back ();
11331156 type = DeriveType::HARDENED;
11341157 }
1135- if (!ParseKeyPath (split, path, error)) return nullptr ;
1158+ if (!ParseKeyPath (split, path, apostrophe, error)) return nullptr ;
11361159 if (extkey.key .IsValid ()) {
11371160 extpubkey = extkey.Neuter ();
11381161 out.keys .emplace (extpubkey.pubkey .GetID (), extkey.key );
11391162 }
1140- return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move (path), type);
1163+ return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move (path), type, apostrophe );
11411164}
11421165
11431166/* * 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
11501173 error = " Multiple ']' characters found for a single pubkey" ;
11511174 return nullptr ;
11521175 }
1153- if (origin_split.size () == 1 ) return ParsePubkeyInner (key_exp_index, origin_split[0 ], ctx, out, error);
1176+ // This is set if either the origin or path suffix contains a hardened derivation.
1177+ bool apostrophe = false ;
1178+ if (origin_split.size () == 1 ) {
1179+ return ParsePubkeyInner (key_exp_index, origin_split[0 ], ctx, out, apostrophe, error);
1180+ }
11541181 if (origin_split[0 ].empty () || origin_split[0 ][0 ] != ' [' ) {
11551182 error = strprintf (" Key origin start '[ character expected but not found, got '%c' instead" ,
11561183 origin_split[0 ].empty () ? /* * empty, implies split char */ ' ]' : origin_split[0 ][0 ]);
@@ -1171,18 +1198,18 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
11711198 static_assert (sizeof (info.fingerprint ) == 4 , " Fingerprint must be 4 bytes" );
11721199 assert (fpr_bytes.size () == 4 );
11731200 std::copy (fpr_bytes.begin (), fpr_bytes.end (), info.fingerprint );
1174- if (!ParseKeyPath (slash_split, info.path , error)) return nullptr ;
1175- auto provider = ParsePubkeyInner (key_exp_index, origin_split[1 ], ctx, out, error);
1201+ if (!ParseKeyPath (slash_split, info.path , apostrophe, error)) return nullptr ;
1202+ auto provider = ParsePubkeyInner (key_exp_index, origin_split[1 ], ctx, out, apostrophe, error);
11761203 if (!provider) return nullptr ;
1177- return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move (info), std::move (provider));
1204+ return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move (info), std::move (provider), apostrophe );
11781205}
11791206
11801207std::unique_ptr<PubkeyProvider> InferPubkey (const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
11811208{
11821209 std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0 , pubkey, false );
11831210 KeyOriginInfo info;
11841211 if (provider.GetKeyOrigin (pubkey.GetID (), info)) {
1185- return std::make_unique<OriginPubkeyProvider>(0 , std::move (info), std::move (key_provider));
1212+ return std::make_unique<OriginPubkeyProvider>(0 , std::move (info), std::move (key_provider), /* apostrophe= */ false );
11861213 }
11871214 return key_provider;
11881215}
@@ -1195,7 +1222,7 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS
11951222 std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0 , pubkey, true );
11961223 KeyOriginInfo info;
11971224 if (provider.GetKeyOriginByXOnly (xkey, info)) {
1198- return std::make_unique<OriginPubkeyProvider>(0 , std::move (info), std::move (key_provider));
1225+ return std::make_unique<OriginPubkeyProvider>(0 , std::move (info), std::move (key_provider), /* apostrophe= */ false );
11991226 }
12001227 return key_provider;
12011228}
0 commit comments