@@ -197,7 +197,9 @@ struct PubkeyProvider
197
197
/* * Get the descriptor string form including private data (if available in arg). */
198
198
virtual bool ToPrivateString (const SigningProvider& arg, std::string& out) const = 0;
199
199
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
+ */
201
203
virtual bool ToNormalizedString (const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr ) const = 0;
202
204
203
205
/* * Derive a private key, if private data is available in arg. */
@@ -208,14 +210,15 @@ class OriginPubkeyProvider final : public PubkeyProvider
208
210
{
209
211
KeyOriginInfo m_origin;
210
212
std::unique_ptr<PubkeyProvider> m_provider;
213
+ bool m_apostrophe;
211
214
212
- std::string OriginString () const
215
+ std::string OriginString (bool normalized= false ) const
213
216
{
214
- return HexStr (m_origin.fingerprint ) + FormatHDKeypath (m_origin.path );
217
+ return HexStr (m_origin.fingerprint ) + FormatHDKeypath (m_origin.path , /* apostrophe= */ !normalized && m_apostrophe );
215
218
}
216
219
217
220
public:
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 ) {}
219
222
bool GetPubKey (int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr , DescriptorCache* write_cache = nullptr ) const override
220
223
{
221
224
if (!m_provider->GetPubKey (pos, arg, key, info, read_cache, write_cache)) return false ;
@@ -242,9 +245,9 @@ class OriginPubkeyProvider final : public PubkeyProvider
242
245
// and append that to our own origin string.
243
246
if (sub[0 ] == ' [' ) {
244
247
sub = sub.substr (9 );
245
- ret = " [" + OriginString () + std::move (sub);
248
+ ret = " [" + OriginString (/* normalized= */ true ) + std::move (sub);
246
249
} else {
247
- ret = " [" + OriginString () + " ]" + std::move (sub);
250
+ ret = " [" + OriginString (/* normalized= */ true ) + " ]" + std::move (sub);
248
251
}
249
252
return true ;
250
253
}
@@ -312,6 +315,8 @@ class BIP32PubkeyProvider final : public PubkeyProvider
312
315
CExtPubKey m_root_extkey;
313
316
KeyPath m_path;
314
317
DeriveType m_derive;
318
+ // Whether ' or h is used in harded derivation
319
+ bool m_apostrophe;
315
320
316
321
bool GetExtKey (const SigningProvider& arg, CExtKey& ret) const
317
322
{
@@ -348,7 +353,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
348
353
}
349
354
350
355
public:
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 ) {}
352
357
bool IsRange () const override { return m_derive != DeriveType::NO; }
353
358
size_t GetSize () const override { return 33 ; }
354
359
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
416
421
417
422
return true ;
418
423
}
419
- std::string ToString () const override
424
+ std::string ToString (bool normalized ) const
420
425
{
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);
422
428
if (IsRange ()) {
423
429
ret += " /*" ;
424
- if (m_derive == DeriveType::HARDENED) ret += ' \' ' ;
430
+ if (m_derive == DeriveType::HARDENED) ret += use_apostrophe ? ' \' ' : ' h ' ;
425
431
}
426
432
return ret;
427
433
}
434
+ std::string ToString () const override
435
+ {
436
+ return ToString (/* normalized=*/ false );
437
+ }
428
438
bool ToPrivateString (const SigningProvider& arg, std::string& out) const override
429
439
{
430
440
CExtKey key;
431
441
if (!GetExtKey (arg, key)) return false ;
432
- out = EncodeExtKey (key) + FormatHDKeypath (m_path);
442
+ out = EncodeExtKey (key) + FormatHDKeypath (m_path, /* apostrophe= */ m_apostrophe );
433
443
if (IsRange ()) {
434
444
out += " /*" ;
435
- if (m_derive == DeriveType::HARDENED) out += ' \' ' ;
445
+ if (m_derive == DeriveType::HARDENED) out += m_apostrophe ? ' \' ' : ' h ' ;
436
446
}
437
447
return true ;
438
448
}
439
449
bool ToNormalizedString (const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override
440
450
{
441
- // For hardened derivation type, just return the typical string, nothing to normalize
442
451
if (m_derive == DeriveType::HARDENED) {
443
- out = ToString ();
452
+ out = ToString (/* normalized=*/ true );
453
+
444
454
return true ;
445
455
}
446
456
// Step backwards to find the last hardened step in the path
@@ -1048,15 +1058,27 @@ enum class ParseScriptContext {
1048
1058
P2TR, // !< Inside tr() (either internal key, or BIP342 script leaf)
1049
1059
};
1050
1060
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)
1053
1071
{
1054
1072
for (size_t i = 1 ; i < split.size (); ++i) {
1055
1073
Span<const char > elem = split[i];
1056
1074
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
+ }
1060
1082
}
1061
1083
uint32_t p;
1062
1084
if (!ParseUInt32 (std::string (elem.begin (), elem.end ()), &p)) {
@@ -1072,7 +1094,7 @@ enum class ParseScriptContext {
1072
1094
}
1073
1095
1074
1096
/* * 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)
1076
1098
{
1077
1099
using namespace spanparsing ;
1078
1100
@@ -1129,15 +1151,16 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
1129
1151
split.pop_back ();
1130
1152
type = DeriveType::UNHARDENED;
1131
1153
} else if (split.back () == Span{" *'" }.first (2 ) || split.back () == Span{" *h" }.first (2 )) {
1154
+ apostrophe = split.back () == Span{" *'" }.first (2 );
1132
1155
split.pop_back ();
1133
1156
type = DeriveType::HARDENED;
1134
1157
}
1135
- if (!ParseKeyPath (split, path, error)) return nullptr ;
1158
+ if (!ParseKeyPath (split, path, apostrophe, error)) return nullptr ;
1136
1159
if (extkey.key .IsValid ()) {
1137
1160
extpubkey = extkey.Neuter ();
1138
1161
out.keys .emplace (extpubkey.pubkey .GetID (), extkey.key );
1139
1162
}
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 );
1141
1164
}
1142
1165
1143
1166
/* * 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
1150
1173
error = " Multiple ']' characters found for a single pubkey" ;
1151
1174
return nullptr ;
1152
1175
}
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
+ }
1154
1181
if (origin_split[0 ].empty () || origin_split[0 ][0 ] != ' [' ) {
1155
1182
error = strprintf (" Key origin start '[ character expected but not found, got '%c' instead" ,
1156
1183
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
1171
1198
static_assert (sizeof (info.fingerprint ) == 4 , " Fingerprint must be 4 bytes" );
1172
1199
assert (fpr_bytes.size () == 4 );
1173
1200
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);
1176
1203
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 );
1178
1205
}
1179
1206
1180
1207
std::unique_ptr<PubkeyProvider> InferPubkey (const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
1181
1208
{
1182
1209
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0 , pubkey, false );
1183
1210
KeyOriginInfo info;
1184
1211
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 );
1186
1213
}
1187
1214
return key_provider;
1188
1215
}
@@ -1195,7 +1222,7 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS
1195
1222
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0 , pubkey, true );
1196
1223
KeyOriginInfo info;
1197
1224
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 );
1199
1226
}
1200
1227
return key_provider;
1201
1228
}
0 commit comments