@@ -227,20 +227,13 @@ impl DescriptorXKey<bip32::Xpriv> {
227227 let xpub = bip32:: Xpub :: from_priv ( secp, & xprv) ;
228228
229229 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 ( ) ) )
243232 }
233+ None if !hardened_path. is_empty ( ) => {
234+ Some ( ( self . xkey . fingerprint ( secp) , hardened_path. into ( ) ) )
235+ }
236+ None => None ,
244237 } ;
245238
246239 Ok ( DescriptorXKey {
@@ -252,6 +245,85 @@ impl DescriptorXKey<bip32::Xpriv> {
252245 }
253246}
254247
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+
255327/// Descriptor Key parsing errors
256328// FIXME: replace with error enums
257329#[ derive( Debug , PartialEq , Clone , Copy ) ]
@@ -309,20 +381,17 @@ impl DescriptorSecretKey {
309381 /// If the key is an "XPrv", the hardened derivation steps will be applied
310382 /// before converting it to a public key.
311383 ///
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.
315386 pub fn to_public < C : Signing > (
316387 & self ,
317388 secp : & Secp256k1 < C > ,
318389 ) -> Result < DescriptorPublicKey , DescriptorKeyParseError > {
319390 let pk = match self {
320391 DescriptorSecretKey :: Single ( prv) => DescriptorPublicKey :: Single ( prv. to_public ( secp) ) ,
321392 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) ?)
326395 }
327396 } ;
328397
@@ -1489,6 +1558,20 @@ mod test {
14891558 DescriptorPublicKey :: from_str ( "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1;>" ) . unwrap_err ( ) ;
14901559 }
14911560
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+
14921575 #[ test]
14931576 fn test_parse_wif ( ) {
14941577 let secret_key = "[0dd03d09/0'/1/2']5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ"
0 commit comments