From 4fa4ab0b8f9d6a0e3fcad0b7ff8a1f962fb39dba Mon Sep 17 00:00:00 2001 From: Vincent Bernardoff Date: Tue, 2 Oct 2018 19:43:49 +0800 Subject: [PATCH] Ledger: add support for animal names in URI --- src/lib_signer_backends/ledger.ml | 247 +++++++++++++++++------------- 1 file changed, 141 insertions(+), 106 deletions(-) diff --git a/src/lib_signer_backends/ledger.ml b/src/lib_signer_backends/ledger.ml index 12a7ff52d..024c9c797 100644 --- a/src/lib_signer_backends/ledger.ml +++ b/src/lib_signer_backends/ledger.ml @@ -125,6 +125,39 @@ let () = (function LedgerError e -> Some e | _ -> None) (fun e -> LedgerError e) +type id = + | Animals of Ledger_names.t * Ledgerwallet_tezos.curve + | Pkh of Signature.Public_key_hash.t + +let pp_id ppf = function + | Pkh pkh -> Signature.Public_key_hash.pp ppf pkh + | Animals (cthd, curve) -> + Format.fprintf ppf "%a/%a" Ledger_names.pp cthd + Ledgerwallet_tezos.pp_curve curve + +let parse_animals animals = + match String.split '-' animals with + | [c; t; h; d] -> Some { Ledger_names.c ; t ; h ; d } + | _ -> None + +let id_of_uri uri = + let host = Uri.host uri in + match Option.apply host + ~f:Signature.Public_key_hash.of_b58check_opt with + | Some pkh -> return (Pkh pkh) + | None -> + match Option.apply host ~f:parse_animals, + Option.apply (List.hd_opt (String.split '/' (Uri.path uri))) + ~f:Ledgerwallet_tezos.curve_of_string with + | Some animals, Some curve -> + return (Animals (animals, curve)) + | _ -> + failwith "No public key hash or animal names in %a" + Uri.pp_hum uri + +let id_of_pk_uri (uri : pk_uri) = id_of_uri (uri :> Uri.t) +let id_of_sk_uri (uri : sk_uri) = id_of_uri (uri :> Uri.t) + let wrap_ledger_cmd f = let buf = Buffer.create 100 in let pp = Format.formatter_of_buffer buf in @@ -179,10 +212,12 @@ module Ledger = struct device_info : Hidapi.device_info ; version : Ledgerwallet_tezos.Version.t ; git_commit : string option ; - of_curve : (Ledgerwallet_tezos.curve * (Signature.Public_key.t * - Signature.Public_key_hash.t)) list ; - of_pkh : (Signature.Public_key_hash.t * (Signature.Public_key.t * - Ledgerwallet_tezos.curve)) list ; + of_curve : (Ledgerwallet_tezos.curve * + (Signature.Public_key.t * + Signature.Public_key_hash.t)) list ; + of_pkh : (Signature.Public_key_hash.t * + (Signature.Public_key.t * + Ledgerwallet_tezos.curve)) list ; } let create ?git_commit ~device_info ~version ~of_curve ~of_pkh () = @@ -195,26 +230,39 @@ module Ledger = struct else [ Ed25519 ; Secp256k1 ; Secp256r1 ] - let of_hidapi ?pkh device_info h = - let find_ledgers ?git_commit version = - fold_left_s begin fun (pkh_found, of_curve, of_pkh) curve -> - get_public_key h curve [] >>|? fun pk -> - let cur_pkh = Signature.Public_key.hash pk in - pkh_found || - Option.unopt_map pkh ~default:false ~f:(fun pkh -> pkh = cur_pkh), - (curve, (pk, cur_pkh)) :: of_curve, - (cur_pkh, (pk, curve)) :: of_pkh - end (false, [], []) (curves version) - >>=? fun (pkh_found, of_curve, of_pkh) -> - match pkh with - | None -> - return (Some (create ?git_commit ~device_info ~version - ~of_curve ~of_pkh ())) - | Some _ when pkh_found -> - return (Some (create ?git_commit ~device_info ~version - ~of_curve ~of_pkh ())) - | _ -> return None - in + let animals_of_pkh pkh = + pkh |> Signature.Public_key_hash.to_string |> + Ledger_names.crouching_tiger + + let find_ledgers ?id ?git_commit h device_info version = + fold_left_s begin fun (ledger_found, of_curve, of_pkh) curve -> + get_public_key h curve [] >>|? fun pk -> + let cur_pkh = Signature.Public_key.hash pk in + let cur_animals = animals_of_pkh cur_pkh in + log_info "Found PK: %a" Signature.Public_key.pp pk ; + log_info "Found PKH: %a" Signature.Public_key_hash.pp cur_pkh ; + log_info "Found Animals: %a" Ledger_names.pp cur_animals ; + ledger_found || + (match id with + | Some (Pkh pkh) when pkh = cur_pkh -> true + | Some (Animals (animals, _)) when animals = cur_animals -> true + | _ -> false), + (curve, (pk, cur_pkh)) :: of_curve, + (cur_pkh, (pk, curve)) :: of_pkh + end (false, [], []) (curves version) + >>=? fun (ledger_found, of_curve, of_pkh) -> + match id with + | None -> + return_some + (create ?git_commit ~device_info ~version + ~of_curve ~of_pkh ()) + | Some _ when ledger_found -> + return_some + (create ?git_commit ~device_info ~version + ~of_curve ~of_pkh ()) + | _ -> return None + + let of_hidapi ?id device_info h = let buf = Buffer.create 100 in let pp = Format.formatter_of_buffer buf in let version = Ledgerwallet_tezos.get_version ~pp h in @@ -231,13 +279,16 @@ module Ledger = struct } in warn "Impossible to read Tezos version, assuming %a" Ledgerwallet_tezos.Version.pp version ; - find_ledgers version + find_ledgers ?id h device_info version | Error e -> - warn "WARNING:@ The device at [%s] is not a Tezos application@ (%a)" + warn "WARNING:@ The device at [%s] is not a Tezos application@ \ + %a" device_info.Hidapi.path Ledgerwallet.Transport.pp_error e ; return None | Ok ({ major; minor; patch; _ } as version) -> + log_info "Found a %a application at [%s]" + Ledgerwallet_tezos.Version.pp version device_info.path ; begin if (major, minor, patch) >= (1, 4, 0) then wrap_ledger_cmd (fun pp -> @@ -245,29 +296,31 @@ module Ledger = struct return_some c else return_none end >>=? fun git_commit -> - find_ledgers ?git_commit version + find_ledgers ?id ?git_commit h device_info version end -let find_ledgers ?pkh () = +let find_ledgers ?id () = let ledgers = Hidapi.enumerate ~vendor_id ~product_id () in + log_info "Found %d Ledger(s)" (List.length ledgers) ; filter_map_s begin fun device_info -> + log_info "Processing Ledger at path [%s]" device_info.Hidapi.path ; match Hidapi.(open_path device_info.path) with | None -> return_none | Some h -> Lwt.finalize - (fun () -> Ledger.of_hidapi ?pkh device_info h) + (fun () -> Ledger.of_hidapi ?id device_info h) (fun () -> Hidapi.close h ; Lwt.return_unit) end ledgers -let with_ledger pkh f = - find_ledgers ~pkh () >>=? function +let with_ledger id f = + find_ledgers ~id () >>=? function | [] -> - failwith "No Ledger found for %a" Signature.Public_key_hash.pp pkh + failwith "No Ledger found for %a" pp_id id | { device_info ; version ; of_curve ; of_pkh ; _ } :: _ -> match Hidapi.open_path device_info.path with | None -> failwith "Cannot open Ledger %a at path %s" - Signature.Public_key_hash.pp pkh device_info.path + pp_id id device_info.path | Some h -> Lwt.finalize (fun () -> f h version of_curve of_pkh) @@ -289,37 +342,31 @@ let int32_of_path_element_exn x = let neuterize (sk : sk_uri) = return (make_pk_uri (sk :> Uri.t)) -let pkh_of_pk_uri (uri : pk_uri) = - let uri = (uri :> Uri.t) in - match Option.apply (Uri.host uri) - ~f:Signature.Public_key_hash.of_b58check_opt with - | None -> - failwith "No public key hash in %a" Uri.pp_hum uri - | Some pkh -> return pkh - -let pkh_of_sk_uri (uri : sk_uri) = - let uri = (uri :> Uri.t) in - match Option.apply (Uri.host uri) - ~f:Signature.Public_key_hash.of_b58check_opt with - | None -> - failwith "No public key hash in %a" Uri.pp_hum uri - | Some pkh -> return pkh - let path_of_sk_uri (uri : sk_uri) = - TzString.split_path (Uri.path (uri :> Uri.t)) |> - List.map int32_of_path_element_exn + match TzString.split_path (Uri.path (uri :> Uri.t)) with + | [] -> [] + | curve :: path when Ledgerwallet_tezos.curve_of_string curve <> None -> + List.map int32_of_path_element_exn path + | path -> List.map int32_of_path_element_exn path let path_of_pk_uri (uri : pk_uri) = - TzString.split_path (Uri.path (uri :> Uri.t)) |> - List.map int32_of_path_element_exn + match TzString.split_path (Uri.path (uri :> Uri.t)) with + | [] -> [] + | curve :: path when Ledgerwallet_tezos.curve_of_string curve <> None -> + List.map int32_of_path_element_exn path + | path -> List.map int32_of_path_element_exn path let public_key (pk_uri : pk_uri) = + let find_ledger of_pkh = function + | Pkh pkh -> snd (List.assoc pkh of_pkh) + | Animals (_, curve) -> curve + in match Hashtbl.find_opt pks pk_uri with | Some pk -> return pk | None -> - pkh_of_pk_uri pk_uri >>=? fun pkh -> - with_ledger pkh begin fun ledger _version _of_curve of_pkh -> - let _root_pk, curve = List.assoc pkh of_pkh in + id_of_pk_uri pk_uri >>=? fun id -> + with_ledger id begin fun ledger _version _of_curve of_pkh -> + let curve = find_ledger of_pkh id in let path = path_of_pk_uri pk_uri in get_public_key ledger curve path >>=? fun pk -> let pkh = Signature.Public_key.hash pk in @@ -337,15 +384,19 @@ let public_key_hash pk_uri = public_key pk_uri >>=? fun pk -> return (Hashtbl.find pkhs pk_uri, Some pk) +let curve_of_id = function + | Pkh pkh -> curve_of_pkh pkh + | Animals (_, curve) -> curve + let sign ?watermark sk_uri msg = - pkh_of_sk_uri sk_uri >>=? fun pkh -> - with_ledger pkh begin fun ledger { major; minor; patch; _ } _of_curve _of_pkh -> + id_of_sk_uri sk_uri >>=? fun id -> + with_ledger id begin fun ledger { major; minor; patch; _ } _of_curve _of_pkh -> let msg = Option.unopt_map watermark ~default:msg ~f:begin fun watermark -> MBytes.concat "" [Signature.bytes_of_watermark watermark ; msg] end in - let curve = curve_of_pkh pkh in + let curve = curve_of_id id in let path = tezos_root @ path_of_sk_uri sk_uri in let msg_len = MBytes.length msg in wrap_ledger_cmd begin fun pp -> @@ -411,43 +462,27 @@ let commands = (match git_commit with None -> "unknown" | Some c -> c) manufacturer product path >>= fun () -> let of_curve = List.rev of_curve in + begin match List.hd_opt of_curve with + | None -> + failwith "No curve available, upgrade Ledger software" + | Some (_, (_, pkh)) -> + return (Ledger_names.crouching_tiger + (Signature.Public_key_hash.to_string pkh)) + end >>=? fun animals -> cctxt#message - "@[@,To add the root key of this ledger, use one of@,\ - \ @[%a@]@,\ - Each of these tz* is a valid Tezos address.@,\ - @,\ - To use a derived address, add a hardened BIP32 path suffix \ - at the end of the URI.@,\ - For instance, to use keys at BIP32 path m/44'/1729'/0'/0', use one of@,\ - \ @[%a@]@,\ - In this case, your Tezos address will be a derived tz*.@,\ - It will be displayed when you do the import, \ - or using command `show ledger path`.@]" + "@[@,To use keys at BIP32 path \ + m/44'/1729'/0'/0' (default Tezos key path), use \ + one of@, @[%a@]@]" (Format.pp_print_list - (fun ppf (curve, (_pk, pkh)) -> + (fun ppf (curve, _) -> Format.fprintf ppf - "tezos-client import secret key ledger_%s_%s ledger://%a # %s signature" + "tezos-client import secret key \ + ledger_%s_%a_0_0 \"ledger://%a/0'/0'\" # \ + %a signature" (Sys.getenv "USER") - (match curve with - | Ledgerwallet_tezos.Ed25519 -> "ed" - | Ledgerwallet_tezos.Secp256k1 -> "secp" - | Ledgerwallet_tezos.Secp256r1 -> "p2") - Signature.Public_key_hash.pp pkh - (match curve with - | Ledgerwallet_tezos.Ed25519 -> "Ed25519" - | Ledgerwallet_tezos.Secp256k1 -> "Secp256k1" - | Ledgerwallet_tezos.Secp256r1 -> "P-256"))) - of_curve - (Format.pp_print_list - (fun ppf (curve, (_pk, pkh)) -> - Format.fprintf ppf - "tezos-client import secret key ledger_%s_%s_0_0 \"ledger://%a/0'/0'\"" - (Sys.getenv "USER") - (match curve with - | Ledgerwallet_tezos.Ed25519 -> "ed" - | Ledgerwallet_tezos.Secp256k1 -> "secp" - | Ledgerwallet_tezos.Secp256r1 -> "p2") - Signature.Public_key_hash.pp pkh)) + Ledgerwallet_tezos.pp_curve_short curve + pp_id (Animals (animals, curve)) + Ledgerwallet_tezos.pp_curve curve)) of_curve >>= fun () -> return_unit end ledgers) ; @@ -460,10 +495,10 @@ let commands = @@ stop) (fun () sk_uri (cctxt : Client_context.io_wallet) -> neuterize sk_uri >>=? fun pk_uri -> - pkh_of_pk_uri pk_uri >>=? fun pkh -> - find_ledgers ~pkh () >>=? function + id_of_pk_uri pk_uri >>=? fun id -> + find_ledgers ~id () >>=? function | [] -> - failwith "No ledger found for %a" Signature.Public_key_hash.pp pkh + failwith "No ledger found for %a" pp_id id | { Ledger.device_info ; version ; _ } :: _ -> let manufacturer = Option.unopt ~default:"(none)" device_info.manufacturer_string in @@ -500,8 +535,8 @@ let commands = @@ Public_key.alias_param @@ stop) (fun () (name, (pk_uri, _)) (cctxt : Client_context.io_wallet) -> - pkh_of_pk_uri pk_uri >>=? fun root_pkh -> - with_ledger root_pkh begin fun h _version _of_curve _to_curve -> + id_of_pk_uri pk_uri >>=? fun root_id -> + with_ledger root_id begin fun h _version _of_curve _to_curve -> wrap_ledger_cmd begin fun pp -> Ledgerwallet_tezos.get_authorized_key ~pp h end >>=? function @@ -524,10 +559,10 @@ let commands = @@ Public_key.alias_param @@ stop) (fun () (_, (pk_uri, _)) (cctxt : Client_context.io_wallet) -> - pkh_of_pk_uri pk_uri >>=? fun root_pkh -> - with_ledger root_pkh begin fun h _version _of_curve _to_curve -> + id_of_pk_uri pk_uri >>=? fun root_id -> + with_ledger root_id begin fun h _version _of_curve _of_pkh -> let path = path_of_pk_uri pk_uri in - let curve = curve_of_pkh root_pkh in + let curve = curve_of_id root_id in get_public_key ~authorize_baking:true h curve path >>=? fun pk -> let pkh = Signature.Public_key.hash pk in cctxt#message @@ -545,8 +580,8 @@ let commands = @@ Client_keys.sk_uri_param @@ stop) (fun () sk_uri (cctxt : Client_context.io_wallet) -> - pkh_of_sk_uri sk_uri >>=? fun pkh -> - with_ledger pkh begin fun h version _ _ -> + id_of_sk_uri sk_uri >>=? fun id -> + with_ledger id begin fun h version _ _ -> match version.app_class with | Tezos -> failwith "Fatal: this operation is only valid with TezBake" @@ -556,7 +591,7 @@ let commands = end >>=? fun hwm -> cctxt#message "@[%a has high water mark: %ld@]" - Signature.Public_key_hash.pp pkh hwm >>= fun () -> + pp_id id hwm >>= fun () -> return_unit end ) ; @@ -575,8 +610,8 @@ let commands = with _ -> failwith "%s is not an int32 value" s))) @@ stop) (fun () sk_uri hwm (cctxt : Client_context.io_wallet) -> - pkh_of_sk_uri sk_uri >>=? fun pkh -> - with_ledger pkh begin fun h version _ _ -> + id_of_sk_uri sk_uri >>=? fun id -> + with_ledger id begin fun h version _ _ -> match version.app_class with | Tezos -> failwith "Fatal: this operation is only valid with TezBake" @@ -589,7 +624,7 @@ let commands = end >>=? fun new_hwm -> cctxt#message "@[%a has now high water mark: %ld@]" - Signature.Public_key_hash.pp pkh new_hwm >>= fun () -> + pp_id id new_hwm >>= fun () -> return_unit end ) ;