@@ -227,20 +227,13 @@ impl DescriptorXKey<bip32::Xpriv> {
227
227
let xpub = bip32:: Xpub :: from_priv ( secp, & xprv) ;
228
228
229
229
let origin = match & self . origin {
230
- Some ( ( fingerprint, path) ) => Some ( (
231
- * fingerprint,
232
- path. into_iter ( )
233
- . chain ( hardened_path. iter ( ) )
234
- . cloned ( )
235
- . collect ( ) ,
236
- ) ) ,
237
- None => {
238
- if hardened_path. is_empty ( ) {
239
- None
240
- } else {
241
- Some ( ( self . xkey . fingerprint ( secp) , hardened_path. into ( ) ) )
242
- }
230
+ Some ( ( fingerprint, path) ) => {
231
+ Some ( ( * fingerprint, path. into_iter ( ) . chain ( hardened_path) . copied ( ) . collect ( ) ) )
243
232
}
233
+ None if !hardened_path. is_empty ( ) => {
234
+ Some ( ( self . xkey . fingerprint ( secp) , hardened_path. into ( ) ) )
235
+ }
236
+ None => None ,
244
237
} ;
245
238
246
239
Ok ( DescriptorXKey {
@@ -252,6 +245,85 @@ impl DescriptorXKey<bip32::Xpriv> {
252
245
}
253
246
}
254
247
248
+ impl DescriptorMultiXKey < bip32:: Xpriv > {
249
+ /// Returns the public version of this multi-key, applying all the hardened derivation steps that
250
+ /// are shared among all derivation paths before turning it into a public key.
251
+ ///
252
+ /// Errors if there are hardened derivation steps that are not shared among all paths.
253
+ fn to_public < C : Signing > (
254
+ & self ,
255
+ secp : & Secp256k1 < C > ,
256
+ ) -> Result < DescriptorMultiXKey < bip32:: Xpub > , DescriptorKeyParseError > {
257
+ let deriv_paths = self . derivation_paths . paths ( ) ;
258
+
259
+ let shared_prefix: Vec < _ > = deriv_paths[ 0 ]
260
+ . into_iter ( )
261
+ . enumerate ( )
262
+ . take_while ( |( index, child_num) | {
263
+ deriv_paths[ 1 ..] . iter ( ) . all ( |other_path| {
264
+ other_path. len ( ) > * index && other_path[ * index] == * * child_num
265
+ } )
266
+ } )
267
+ . map ( |( _, child_num) | * child_num)
268
+ . collect ( ) ;
269
+
270
+ let suffixes: Vec < Vec < _ > > = deriv_paths
271
+ . iter ( )
272
+ . map ( |path| {
273
+ path. into_iter ( )
274
+ . skip ( shared_prefix. len ( ) )
275
+ . map ( |child_num| {
276
+ if child_num. is_normal ( ) {
277
+ Ok ( * child_num)
278
+ } else {
279
+ Err ( DescriptorKeyParseError ( "Can't make a multi-xpriv with hardened derivation steps that are not shared among all paths into a public key." ) )
280
+ }
281
+ } )
282
+ . collect ( )
283
+ } )
284
+ . collect :: < Result < _ , _ > > ( ) ?;
285
+
286
+ let unhardened = shared_prefix
287
+ . iter ( )
288
+ . rev ( )
289
+ . take_while ( |c| c. is_normal ( ) )
290
+ . count ( ) ;
291
+ let last_hardened_idx = shared_prefix. len ( ) - unhardened;
292
+ let hardened_path = & shared_prefix[ ..last_hardened_idx] ;
293
+ let unhardened_path = & shared_prefix[ last_hardened_idx..] ;
294
+
295
+ let xprv = self
296
+ . xkey
297
+ . derive_priv ( secp, & hardened_path)
298
+ . map_err ( |_| DescriptorKeyParseError ( "Unable to derive the hardened steps" ) ) ?;
299
+ let xpub = bip32:: Xpub :: from_priv ( secp, & xprv) ;
300
+
301
+ let origin = match & self . origin {
302
+ Some ( ( fingerprint, path) ) => {
303
+ Some ( ( * fingerprint, path. into_iter ( ) . chain ( hardened_path) . copied ( ) . collect ( ) ) )
304
+ }
305
+ None if !hardened_path. is_empty ( ) => {
306
+ Some ( ( self . xkey . fingerprint ( secp) , hardened_path. into ( ) ) )
307
+ }
308
+ None => None ,
309
+ } ;
310
+ let new_deriv_paths = suffixes
311
+ . into_iter ( )
312
+ . map ( |suffix| {
313
+ let path = unhardened_path. iter ( ) . copied ( ) . chain ( suffix) ;
314
+ path. collect :: < Vec < _ > > ( ) . into ( )
315
+ } )
316
+ . collect ( ) ;
317
+
318
+ Ok ( DescriptorMultiXKey {
319
+ origin,
320
+ xkey : xpub,
321
+ derivation_paths : DerivPaths :: new ( new_deriv_paths) . expect ( "not empty" ) ,
322
+ wildcard : self . wildcard ,
323
+ } )
324
+ }
325
+ }
326
+
255
327
/// Descriptor Key parsing errors
256
328
// FIXME: replace with error enums
257
329
#[ derive( Debug , PartialEq , Clone , Copy ) ]
@@ -309,20 +381,17 @@ impl DescriptorSecretKey {
309
381
/// If the key is an "XPrv", the hardened derivation steps will be applied
310
382
/// before converting it to a public key.
311
383
///
312
- /// It will return an error if the key is a "multi-xpriv", as we wouldn't
313
- /// always be able to apply hardened derivation steps if there are multiple
314
- /// paths.
384
+ /// It will return an error if the key is a "multi-xpriv" that includes
385
+ /// hardened derivation steps not shared for all paths.
315
386
pub fn to_public < C : Signing > (
316
387
& self ,
317
388
secp : & Secp256k1 < C > ,
318
389
) -> Result < DescriptorPublicKey , DescriptorKeyParseError > {
319
390
let pk = match self {
320
391
DescriptorSecretKey :: Single ( prv) => DescriptorPublicKey :: Single ( prv. to_public ( secp) ) ,
321
392
DescriptorSecretKey :: XPrv ( xprv) => DescriptorPublicKey :: XPub ( xprv. to_public ( secp) ?) ,
322
- DescriptorSecretKey :: MultiXPrv ( _) => {
323
- return Err ( DescriptorKeyParseError (
324
- "Can't make an extended private key with multiple paths into a public key." ,
325
- ) )
393
+ DescriptorSecretKey :: MultiXPrv ( xprv) => {
394
+ DescriptorPublicKey :: MultiXPub ( xprv. to_public ( secp) ?)
326
395
}
327
396
} ;
328
397
@@ -1489,6 +1558,20 @@ mod test {
1489
1558
DescriptorPublicKey :: from_str ( "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1;>" ) . unwrap_err ( ) ;
1490
1559
}
1491
1560
1561
+ #[ test]
1562
+ fn test_multixprv_to_public ( ) {
1563
+ let secp = secp256k1:: Secp256k1 :: signing_only ( ) ;
1564
+
1565
+ // Works if all hardended derivation steps are part of the shared path
1566
+ let xprv = get_multipath_xprv ( "[01020304/5]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1'/2'/3/<4;5>/6" ) ;
1567
+ let xpub = DescriptorPublicKey :: MultiXPub ( xprv. to_public ( & secp) . unwrap ( ) ) ; // wrap in a DescriptorPublicKey to have Display
1568
+ assert_eq ! ( xpub. to_string( ) , "[01020304/5/1'/2']tpubDBTRkEMEFkUbk3WTz6CFSULyswkTPpPr38AWibf5TVkB5GxuBxbSbmdFGr3jmswwemknyYxAGoX7BJnKfyPy4WXaHmcrxZhfzFwoUFvFtm5/3/<4;5>/6" ) ;
1569
+
1570
+ // Fails if they're part of the multi-path specifier or following it
1571
+ get_multipath_xprv ( "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3';4'>/5" ) . to_public ( & secp) . unwrap_err ( ) ;
1572
+ get_multipath_xprv ( "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3;4>/5/6'" ) . to_public ( & secp) . unwrap_err ( ) ;
1573
+ }
1574
+
1492
1575
#[ test]
1493
1576
fn test_parse_wif ( ) {
1494
1577
let secret_key = "[0dd03d09/0'/1/2']5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
0 commit comments