@@ -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
@@ -1049,15 +1059,27 @@ enum class ParseScriptContext {
10491059 P2TR, // !< Inside tr() (either internal key, or BIP342 script leaf)
10501060};
10511061
1052- /* * Parse a key path, being passed a split list of elements (the first element is ignored). */
1053- [[nodiscard]] bool ParseKeyPath (const std::vector<Span<const char >>& split, KeyPath& out, std::string& error)
1062+ /* *
1063+ * Parse a key path, being passed a split list of elements (the first element is ignored).
1064+ *
1065+ * @param[in] split BIP32 path string, using either ' or h for hardened derivation
1066+ * @param[out] out the key path
1067+ * @param[out] apostrophe only updated if hardened derivation is found
1068+ * @param[out] error parsing error message
1069+ * @returns false if parsing failed
1070+ **/
1071+ [[nodiscard]] bool ParseKeyPath (const std::vector<Span<const char >>& split, KeyPath& out, bool & apostrophe, std::string& error)
10541072{
10551073 for (size_t i = 1 ; i < split.size (); ++i) {
10561074 Span<const char > elem = split[i];
10571075 bool hardened = false ;
1058- if (elem.size () > 0 && (elem[elem.size () - 1 ] == ' \' ' || elem[elem.size () - 1 ] == ' h' )) {
1059- elem = elem.first (elem.size () - 1 );
1060- hardened = true ;
1076+ if (elem.size () > 0 ) {
1077+ const char last = elem[elem.size () - 1 ];
1078+ if (last == ' \' ' || last == ' h' ) {
1079+ elem = elem.first (elem.size () - 1 );
1080+ hardened = true ;
1081+ apostrophe = last == ' \' ' ;
1082+ }
10611083 }
10621084 uint32_t p;
10631085 if (!ParseUInt32 (std::string (elem.begin (), elem.end ()), &p)) {
@@ -1073,7 +1095,7 @@ enum class ParseScriptContext {
10731095}
10741096
10751097/* * Parse a public key that excludes origin information. */
1076- std::unique_ptr<PubkeyProvider> ParsePubkeyInner (uint32_t key_exp_index, const Span<const char >& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
1098+ std::unique_ptr<PubkeyProvider> ParsePubkeyInner (uint32_t key_exp_index, const Span<const char >& sp, ParseScriptContext ctx, FlatSigningProvider& out, bool & apostrophe, std::string& error)
10771099{
10781100 using namespace spanparsing ;
10791101
@@ -1130,15 +1152,16 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
11301152 split.pop_back ();
11311153 type = DeriveType::UNHARDENED;
11321154 } else if (split.back () == Span{" *'" }.first (2 ) || split.back () == Span{" *h" }.first (2 )) {
1155+ apostrophe = split.back () == Span{" *'" }.first (2 );
11331156 split.pop_back ();
11341157 type = DeriveType::HARDENED;
11351158 }
1136- if (!ParseKeyPath (split, path, error)) return nullptr ;
1159+ if (!ParseKeyPath (split, path, apostrophe, error)) return nullptr ;
11371160 if (extkey.key .IsValid ()) {
11381161 extpubkey = extkey.Neuter ();
11391162 out.keys .emplace (extpubkey.pubkey .GetID (), extkey.key );
11401163 }
1141- return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move (path), type);
1164+ return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move (path), type, apostrophe );
11421165}
11431166
11441167/* * Parse a public key including origin information (if enabled). */
@@ -1151,7 +1174,11 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
11511174 error = " Multiple ']' characters found for a single pubkey" ;
11521175 return nullptr ;
11531176 }
1154- if (origin_split.size () == 1 ) return ParsePubkeyInner (key_exp_index, origin_split[0 ], ctx, out, error);
1177+ // This is set if either the origin or path suffix contains a hardened derivation.
1178+ bool apostrophe = false ;
1179+ if (origin_split.size () == 1 ) {
1180+ return ParsePubkeyInner (key_exp_index, origin_split[0 ], ctx, out, apostrophe, error);
1181+ }
11551182 if (origin_split[0 ].empty () || origin_split[0 ][0 ] != ' [' ) {
11561183 error = strprintf (" Key origin start '[ character expected but not found, got '%c' instead" ,
11571184 origin_split[0 ].empty () ? /* * empty, implies split char */ ' ]' : origin_split[0 ][0 ]);
@@ -1172,18 +1199,18 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
11721199 static_assert (sizeof (info.fingerprint ) == 4 , " Fingerprint must be 4 bytes" );
11731200 assert (fpr_bytes.size () == 4 );
11741201 std::copy (fpr_bytes.begin (), fpr_bytes.end (), info.fingerprint );
1175- if (!ParseKeyPath (slash_split, info.path , error)) return nullptr ;
1176- auto provider = ParsePubkeyInner (key_exp_index, origin_split[1 ], ctx, out, error);
1202+ if (!ParseKeyPath (slash_split, info.path , apostrophe, error)) return nullptr ;
1203+ auto provider = ParsePubkeyInner (key_exp_index, origin_split[1 ], ctx, out, apostrophe, error);
11771204 if (!provider) return nullptr ;
1178- return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move (info), std::move (provider));
1205+ return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move (info), std::move (provider), apostrophe );
11791206}
11801207
11811208std::unique_ptr<PubkeyProvider> InferPubkey (const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
11821209{
11831210 std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0 , pubkey, false );
11841211 KeyOriginInfo info;
11851212 if (provider.GetKeyOrigin (pubkey.GetID (), info)) {
1186- return std::make_unique<OriginPubkeyProvider>(0 , std::move (info), std::move (key_provider));
1213+ return std::make_unique<OriginPubkeyProvider>(0 , std::move (info), std::move (key_provider), /* apostrophe= */ false );
11871214 }
11881215 return key_provider;
11891216}
@@ -1196,7 +1223,7 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS
11961223 std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0 , pubkey, true );
11971224 KeyOriginInfo info;
11981225 if (provider.GetKeyOriginByXOnly (xkey, info)) {
1199- return std::make_unique<OriginPubkeyProvider>(0 , std::move (info), std::move (key_provider));
1226+ return std::make_unique<OriginPubkeyProvider>(0 , std::move (info), std::move (key_provider), /* apostrophe= */ false );
12001227 }
12011228 return key_provider;
12021229}
0 commit comments