@@ -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
200203public:
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
324329public:
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