@@ -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
@@ -1049,15 +1059,27 @@ enum class ParseScriptContext {
1049
1059
P2TR, // !< Inside tr() (either internal key, or BIP342 script leaf)
1050
1060
};
1051
1061
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)
1054
1072
{
1055
1073
for (size_t i = 1 ; i < split.size (); ++i) {
1056
1074
Span<const char > elem = split[i];
1057
1075
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
+ }
1061
1083
}
1062
1084
uint32_t p;
1063
1085
if (!ParseUInt32 (std::string (elem.begin (), elem.end ()), &p)) {
@@ -1073,7 +1095,7 @@ enum class ParseScriptContext {
1073
1095
}
1074
1096
1075
1097
/* * 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)
1077
1099
{
1078
1100
using namespace spanparsing ;
1079
1101
@@ -1130,15 +1152,16 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
1130
1152
split.pop_back ();
1131
1153
type = DeriveType::UNHARDENED;
1132
1154
} else if (split.back () == Span{" *'" }.first (2 ) || split.back () == Span{" *h" }.first (2 )) {
1155
+ apostrophe = split.back () == Span{" *'" }.first (2 );
1133
1156
split.pop_back ();
1134
1157
type = DeriveType::HARDENED;
1135
1158
}
1136
- if (!ParseKeyPath (split, path, error)) return nullptr ;
1159
+ if (!ParseKeyPath (split, path, apostrophe, error)) return nullptr ;
1137
1160
if (extkey.key .IsValid ()) {
1138
1161
extpubkey = extkey.Neuter ();
1139
1162
out.keys .emplace (extpubkey.pubkey .GetID (), extkey.key );
1140
1163
}
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 );
1142
1165
}
1143
1166
1144
1167
/* * 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
1151
1174
error = " Multiple ']' characters found for a single pubkey" ;
1152
1175
return nullptr ;
1153
1176
}
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
+ }
1155
1182
if (origin_split[0 ].empty () || origin_split[0 ][0 ] != ' [' ) {
1156
1183
error = strprintf (" Key origin start '[ character expected but not found, got '%c' instead" ,
1157
1184
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
1172
1199
static_assert (sizeof (info.fingerprint ) == 4 , " Fingerprint must be 4 bytes" );
1173
1200
assert (fpr_bytes.size () == 4 );
1174
1201
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);
1177
1204
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 );
1179
1206
}
1180
1207
1181
1208
std::unique_ptr<PubkeyProvider> InferPubkey (const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
1182
1209
{
1183
1210
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0 , pubkey, false );
1184
1211
KeyOriginInfo info;
1185
1212
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 );
1187
1214
}
1188
1215
return key_provider;
1189
1216
}
@@ -1196,7 +1223,7 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS
1196
1223
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0 , pubkey, true );
1197
1224
KeyOriginInfo info;
1198
1225
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 );
1200
1227
}
1201
1228
return key_provider;
1202
1229
}
0 commit comments