@@ -252,6 +252,89 @@ impl DescriptorXKey<bip32::Xpriv> {
252
252
}
253
253
}
254
254
255
+ impl DescriptorMultiXKey < bip32:: Xpriv > {
256
+ /// Returns the public version of this multi-key, applying all the hardened derivation steps that
257
+ /// are shared among all derivation paths before turning it into a public key.
258
+ ///
259
+ /// Errors if there are hardened derivation steps that are not shared among all paths.
260
+ fn to_public < C : Signing > (
261
+ & self ,
262
+ secp : & Secp256k1 < C > ,
263
+ ) -> Result < DescriptorMultiXKey < bip32:: Xpub > , DescriptorKeyParseError > {
264
+ let deriv_paths = self . derivation_paths . paths ( ) ;
265
+
266
+ let shared_prefix: Vec < _ > = deriv_paths[ 0 ]
267
+ . into_iter ( )
268
+ . enumerate ( )
269
+ . take_while ( |( index, child_num) | {
270
+ deriv_paths[ 1 ..] . iter ( ) . all ( |other_path| {
271
+ other_path. len ( ) > * index && other_path[ * index] == * * child_num
272
+ } )
273
+ } )
274
+ . map ( |( _, child_num) | * child_num)
275
+ . collect ( ) ;
276
+
277
+ let suffixes: Vec < Vec < _ > > = deriv_paths
278
+ . iter ( )
279
+ . map ( |path| {
280
+ path. into_iter ( )
281
+ . skip ( shared_prefix. len ( ) )
282
+ . map ( |child_num| {
283
+ if child_num. is_normal ( ) {
284
+ Ok ( * child_num)
285
+ } else {
286
+ Err ( DescriptorKeyParseError ( "Can't make a multi-xpriv with hardened derivation steps that are not shared among all paths into a public key." ) )
287
+ }
288
+ } )
289
+ . collect ( )
290
+ } )
291
+ . collect :: < Result < _ , _ > > ( ) ?;
292
+
293
+ let unhardened = shared_prefix
294
+ . iter ( )
295
+ . rev ( )
296
+ . take_while ( |c| c. is_normal ( ) )
297
+ . count ( ) ;
298
+ let last_hardened_idx = shared_prefix. len ( ) - unhardened;
299
+ let hardened_path = & shared_prefix[ ..last_hardened_idx] ;
300
+ let unhardened_path = & shared_prefix[ last_hardened_idx..] ;
301
+
302
+ let xprv = self
303
+ . xkey
304
+ . derive_priv ( secp, & hardened_path)
305
+ . map_err ( |_| DescriptorKeyParseError ( "Unable to derive the hardened steps" ) ) ?;
306
+ let xpub = bip32:: Xpub :: from_priv ( secp, & xprv) ;
307
+
308
+ let origin = match & self . origin {
309
+ Some ( ( fingerprint, path) ) => Some ( (
310
+ * fingerprint,
311
+ path. into_iter ( )
312
+ . chain ( hardened_path. iter ( ) )
313
+ . copied ( )
314
+ . collect ( ) ,
315
+ ) ) ,
316
+ None if !hardened_path. is_empty ( ) => {
317
+ Some ( ( self . xkey . fingerprint ( secp) , hardened_path. into ( ) ) )
318
+ }
319
+ None => None ,
320
+ } ;
321
+ let new_deriv_paths = suffixes
322
+ . into_iter ( )
323
+ . map ( |suffix| {
324
+ let path = unhardened_path. iter ( ) . copied ( ) . chain ( suffix) ;
325
+ path. collect :: < Vec < _ > > ( ) . into ( )
326
+ } )
327
+ . collect ( ) ;
328
+
329
+ Ok ( DescriptorMultiXKey {
330
+ origin,
331
+ xkey : xpub,
332
+ derivation_paths : DerivPaths :: new ( new_deriv_paths) . expect ( "not empty" ) ,
333
+ wildcard : self . wildcard ,
334
+ } )
335
+ }
336
+ }
337
+
255
338
/// Descriptor Key parsing errors
256
339
// FIXME: replace with error enums
257
340
#[ derive( Debug , PartialEq , Clone , Copy ) ]
@@ -309,20 +392,17 @@ impl DescriptorSecretKey {
309
392
/// If the key is an "XPrv", the hardened derivation steps will be applied
310
393
/// before converting it to a public key.
311
394
///
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.
395
+ /// It will return an error if the key is a "multi-xpriv" that includes
396
+ /// hardened derivation steps not shared for all paths.
315
397
pub fn to_public < C : Signing > (
316
398
& self ,
317
399
secp : & Secp256k1 < C > ,
318
400
) -> Result < DescriptorPublicKey , DescriptorKeyParseError > {
319
401
let pk = match self {
320
402
DescriptorSecretKey :: Single ( prv) => DescriptorPublicKey :: Single ( prv. to_public ( secp) ) ,
321
403
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
- ) )
404
+ DescriptorSecretKey :: MultiXPrv ( xprv) => {
405
+ DescriptorPublicKey :: MultiXPub ( xprv. to_public ( secp) ?)
326
406
}
327
407
} ;
328
408
@@ -1489,6 +1569,20 @@ mod test {
1489
1569
DescriptorPublicKey :: from_str ( "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1;>" ) . unwrap_err ( ) ;
1490
1570
}
1491
1571
1572
+ #[ test]
1573
+ fn test_multixprv_to_public ( ) {
1574
+ let secp = secp256k1:: Secp256k1 :: signing_only ( ) ;
1575
+
1576
+ // Works if all hardended derivation steps are part of the shared path
1577
+ let xprv = get_multipath_xprv ( "[01020304/5]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1'/2'/3/<4;5>/6" ) ;
1578
+ let xpub = DescriptorPublicKey :: MultiXPub ( xprv. to_public ( & secp) . unwrap ( ) ) ; // wrap in a DescriptorPublicKey to have Display
1579
+ assert_eq ! ( xpub. to_string( ) , "[01020304/5/1'/2']tpubDBTRkEMEFkUbk3WTz6CFSULyswkTPpPr38AWibf5TVkB5GxuBxbSbmdFGr3jmswwemknyYxAGoX7BJnKfyPy4WXaHmcrxZhfzFwoUFvFtm5/3/<4;5>/6" ) ;
1580
+
1581
+ // Fails if they're part of the multi-path specifier or following it
1582
+ get_multipath_xprv ( "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3';4'>/5" ) . to_public ( & secp) . unwrap_err ( ) ;
1583
+ get_multipath_xprv ( "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3;4>/5/6'" ) . to_public ( & secp) . unwrap_err ( ) ;
1584
+ }
1585
+
1492
1586
#[ test]
1493
1587
fn test_parse_wif ( ) {
1494
1588
let secret_key = "[0dd03d09/0'/1/2']5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
0 commit comments