@@ -252,6 +252,89 @@ impl DescriptorXKey<bip32::Xpriv> {
252252 }
253253}
254254
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+
255338/// Descriptor Key parsing errors
256339// FIXME: replace with error enums
257340#[ derive( Debug , PartialEq , Clone , Copy ) ]
@@ -309,21 +392,16 @@ impl DescriptorSecretKey {
309392 /// If the key is an "XPrv", the hardened derivation steps will be applied
310393 /// before converting it to a public key.
311394 ///
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.
315397 pub fn to_public < C : Signing > (
316398 & self ,
317399 secp : & Secp256k1 < C > ,
318400 ) -> Result < DescriptorPublicKey , DescriptorKeyParseError > {
319401 let pk = match self {
320402 DescriptorSecretKey :: Single ( prv) => DescriptorPublicKey :: Single ( prv. to_public ( secp) ) ,
321403 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- ) )
326- }
404+ DescriptorSecretKey :: MultiXPrv ( xprv) => DescriptorPublicKey :: MultiXPub ( xprv. to_public ( secp) ?) ,
327405 } ;
328406
329407 Ok ( pk)
@@ -1489,6 +1567,20 @@ mod test {
14891567 DescriptorPublicKey :: from_str ( "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1;>" ) . unwrap_err ( ) ;
14901568 }
14911569
1570+ #[ test]
1571+ fn test_multixprv_to_public ( ) {
1572+ let secp = secp256k1:: Secp256k1 :: signing_only ( ) ;
1573+
1574+ // Works if all hardended derivation steps are part of the shared path
1575+ let xprv = get_multipath_xprv ( "[01020304/5]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1'/2'/3/<4;5>/6" ) ;
1576+ let xpub = DescriptorPublicKey :: MultiXPub ( xprv. to_public ( & secp) . unwrap ( ) ) ; // wrap in a DescriptorPublicKey to have Display
1577+ assert_eq ! ( xpub. to_string( ) , "[01020304/5/1'/2']tpubDBTRkEMEFkUbk3WTz6CFSULyswkTPpPr38AWibf5TVkB5GxuBxbSbmdFGr3jmswwemknyYxAGoX7BJnKfyPy4WXaHmcrxZhfzFwoUFvFtm5/3/<4;5>/6" ) ;
1578+
1579+ // Fails if they're part of the multi-path specifier or following it
1580+ get_multipath_xprv ( "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3';4'>/5" ) . to_public ( & secp) . unwrap_err ( ) ;
1581+ get_multipath_xprv ( "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3;4>/5/6'" ) . to_public ( & secp) . unwrap_err ( ) ;
1582+ }
1583+
14921584 #[ test]
14931585 fn test_parse_wif ( ) {
14941586 let secret_key = "[0dd03d09/0'/1/2']5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
0 commit comments