Signer/Ledger: sync with ocaml-ledger-wallet

This commit is contained in:
Vincent Bernardoff 2018-06-28 20:46:32 +02:00 committed by Benjamin Canou
parent aceee178e1
commit bda245f221

View File

@ -48,16 +48,56 @@ let curve_of_pkh :
let secp256k1_ctx =
Libsecp256k1.External.Context.create ~sign:false ~verify:false ()
let get_public_key ledger curve path =
let path = tezos_root @ path in
type error +=
| LedgerError of Ledgerwallet.Transport.error
let error_encoding =
let open Data_encoding in
conv
(fun e -> Format.asprintf "%a" Ledgerwallet.Transport.pp_error e)
(fun _ ->invalid_arg "Ledger error is not deserializable")
(obj1 (req "ledger-error" string))
let () =
register_error_kind
`Permanent
~id: "signer.ledger"
~title: "Ledger error"
~description: "Error when communication to a Ledger Nano S device"
~pp:(fun ppf e ->
Format.fprintf ppf "Ledger %a" Ledgerwallet.Transport.pp_error e)
error_encoding
(function LedgerError e -> Some e | _ -> None)
(fun e -> LedgerError e)
let wrap_ledger_cmd f =
let buf = Buffer.create 100 in
let pp = Format.formatter_of_buffer buf in
let pk = Ledgerwallet_tezos.get_public_key ~pp ledger curve path in
Format.pp_print_flush pp () ;
let res = f pp in
debug "%s" (Buffer.contents buf) ;
match res with
| Error err ->
fail (LedgerError err)
| Ok v ->
return v
let get_public_key
?(authorize_baking=false)
?(prompt=false)
ledger curve path =
let path = tezos_root @ path in
begin match authorize_baking with
| false -> wrap_ledger_cmd begin fun pp ->
Ledgerwallet_tezos.get_public_key ~prompt ~pp ledger curve path
end
| true ->
wrap_ledger_cmd begin fun pp ->
Ledgerwallet_tezos.authorize_baking ~pp ledger curve path
end
end >>|? fun pk ->
let pk = Cstruct.to_bigarray pk in
match curve with
| Ed25519 ->
| Ledgerwallet_tezos.Ed25519 ->
MBytes.set_int8 pk 0 0 ; (* hackish, but works. *)
Data_encoding.Binary.of_bytes_exn Signature.Public_key.encoding pk
| Secp256k1 ->
@ -82,31 +122,59 @@ let get_public_key ledger curve path =
module Ledger = struct
type t = {
device_info : Hidapi.device_info ;
version : Ledgerwallet_tezos.Version.t ;
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 ~device_info ~of_curve ~of_pkh =
{ device_info ; of_curve ; of_pkh }
let create ~device_info ~version ~of_curve ~of_pkh =
{ device_info ; version ; of_curve ; of_pkh }
let curves { Ledgerwallet_tezos.Version.minor ; patch ; _ } =
let open Ledgerwallet_tezos in
Ed25519 :: Secp256k1 ::
(if minor > 0 && patch > 0 then [Secp256r1] else [])
let of_hidapi ?pkh device_info h =
let curves = [ Ledgerwallet_tezos.Ed25519 ; Secp256k1 ; Secp256r1 ] in
let pkh_found, of_curve, of_pkh =
List.fold_left begin fun (pkh_found, of_curve, of_pkh) curve ->
let pk = get_public_key h curve [] in
let find_ledgers 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 in
match pkh with
| None -> return_some (create ~device_info ~of_curve ~of_pkh)
| Some _ when pkh_found ->
return_some (create ~device_info ~of_curve ~of_pkh)
| _ -> return_none
end (false, [], []) (curves version)
>>=? fun (pkh_found, of_curve, of_pkh) ->
match pkh with
| None -> return (Some (create ~device_info ~version ~of_curve ~of_pkh))
| Some _ when pkh_found ->
return (Some (create ~device_info ~version ~of_curve ~of_pkh))
| _ -> return None
in
let buf = Buffer.create 100 in
let pp = Format.formatter_of_buffer buf in
let version = Ledgerwallet_tezos.get_version ~pp h in
debug "%s" (Buffer.contents buf) ;
match version with
| Error (AppError { status = Ledgerwallet.Transport.Status.Ins_not_supported ; _ })
| Error (AppError { status = Ledgerwallet_tezos.Version.Tezos_impossible_to_read_version ; _ }) ->
(* version is < 0.1.1. Assume it is 0.0.1, Tezos app. *)
let version =
{ Ledgerwallet_tezos.Version.app_class = Tezos ;
major = 0 ;
minor = 0 ;
patch = 1 ;
} in
warn "Impossible to read Tezos version, assuming %a"
Ledgerwallet_tezos.Version.pp version ;
find_ledgers version
| Error e ->
warn "%a" Ledgerwallet.Transport.pp_error e ;
return None
| Ok version -> find_ledgers version
end
let find_ledgers ?pkh () =
@ -124,14 +192,14 @@ let with_ledger pkh f =
find_ledgers ~pkh () >>=? function
| [] ->
failwith "No Ledger found for %a" Signature.Public_key_hash.pp pkh
| { device_info ; of_curve ; of_pkh } :: _ ->
| { 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
| Some h ->
Lwt.finalize
(fun () -> f h of_curve of_pkh)
(fun () -> f h version of_curve of_pkh)
(fun () -> Hidapi.close h; Lwt.return_unit)
let int32_of_path_element x =
@ -179,10 +247,10 @@ let public_key (pk_uri : pk_uri) =
| Some pk -> return pk
| None ->
pkh_of_pk_uri pk_uri >>=? fun pkh ->
with_ledger pkh begin fun ledger _of_curve of_pkh ->
with_ledger pkh begin fun ledger _version _of_curve of_pkh ->
let _root_pk, curve = List.assoc pkh of_pkh in
let path = path_of_pk_uri pk_uri in
let pk = get_public_key ledger curve path in
get_public_key ledger curve path >>=? fun pk ->
let pkh = Signature.Public_key.hash pk in
Hashtbl.replace pks pk_uri pk ;
Hashtbl.replace pkhs pk_uri pkh ;
@ -200,7 +268,7 @@ let public_key_hash pk_uri =
let sign ?watermark sk_uri msg =
pkh_of_sk_uri sk_uri >>=? fun pkh ->
with_ledger pkh begin fun ledger _of_curve _of_pkh ->
with_ledger pkh begin fun ledger _version _of_curve _of_pkh ->
let msg = Option.unopt_map watermark
~default:msg ~f:begin fun watermark ->
MBytes.concat "" [Signature.bytes_of_watermark watermark ;
@ -208,11 +276,16 @@ let sign ?watermark sk_uri msg =
end in
let curve = curve_of_pkh pkh in
let path = tezos_root @ path_of_sk_uri sk_uri in
let buf = Buffer.create 100 in
let pp = Format.formatter_of_buffer buf in
let signature = Ledgerwallet_tezos.sign ~pp ledger curve path (Cstruct.of_bigarray msg) in
Format.pp_print_flush pp () ;
debug "%s" (Buffer.contents buf) ;
let msg_len = MBytes.length msg in
wrap_ledger_cmd begin fun pp ->
if msg_len > 1024 then
Ledgerwallet_tezos.sign ~hash_on_ledger:false
~pp ledger curve path
(Cstruct.of_bigarray (Blake2B.(to_bytes (hash_bytes [ msg ]))))
else
Ledgerwallet_tezos.sign
~pp ledger curve path (Cstruct.of_bigarray msg)
end >>=? fun signature ->
match curve with
| Ed25519 ->
let signature = Cstruct.to_bigarray signature in
@ -259,10 +332,11 @@ let commands =
iter_s begin fun { Ledger.device_info = { Hidapi.path ;
manufacturer_string ;
product_string ; _ } ;
of_curve ; _ } ->
of_curve ; version ; _ } ->
let manufacturer = Option.unopt ~default:"(none)" manufacturer_string in
let product = Option.unopt ~default:"(none)" product_string in
cctxt#message "Found a valid Tezos application running on %s %s at [%s]."
cctxt#message "Found a %a application running on %s %s at [%s]."
Ledgerwallet_tezos.Version.pp version
manufacturer product path >>= fun () ->
let of_curve = List.rev of_curve in
cctxt#message
@ -318,7 +392,7 @@ let commands =
find_ledgers ~pkh () >>=? function
| [] ->
failwith "No ledger found for %a" Signature.Public_key_hash.pp pkh
| { Ledger.device_info; _ } :: _ ->
| { Ledger.device_info ; version ; _ } :: _ ->
let manufacturer =
Option.unopt ~default:"(none)" device_info.manufacturer_string in
let product =
@ -328,20 +402,100 @@ let commands =
public_key pk_uri >>=? fun pk ->
public_key_hash pk_uri >>=? fun (pkh, _) ->
let pkh_bytes = Signature.Public_key_hash.to_bytes pkh in
sign ~watermark:Generic_operation
sk_uri pkh_bytes >>=? fun signature ->
match Signature.check ~watermark:Generic_operation
pk signature pkh_bytes with
| false ->
failwith "Fatal: Ledger cannot sign with %a"
Signature.Public_key_hash.pp pkh
| true ->
cctxt#message
"@[<v 0>Tezos address at this path: %a@,\
Corresponding full public key: %a@]"
Signature.Public_key_hash.pp pkh
Signature.Public_key.pp pk >>= fun () ->
return_unit
)
match version.app_class with
| TezBake -> return_unit
| Tezos ->
sign ~watermark:Generic_operation
sk_uri pkh_bytes >>=? fun signature ->
match Signature.check ~watermark:Generic_operation
pk signature pkh_bytes with
| false ->
failwith "Fatal: Ledger cannot sign with %a"
Signature.Public_key_hash.pp pkh
| true ->
cctxt#message
"@[<v 0>Tezos address at this path: %a@,\
Corresponding full public key: %a@]"
Signature.Public_key_hash.pp pkh
Signature.Public_key.pp pk >>= fun () ->
return_unit
) ;
Clic.command ~group
~desc: "Authorize a Ledger to bake for a key"
no_options
(prefixes [ "authorize" ; "ledger" ; "to" ; "bake" ; "for" ]
@@ 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 ->
let path = path_of_pk_uri pk_uri in
let curve = curve_of_pkh root_pkh in
get_public_key ~authorize_baking:true h curve path >>=? fun pk ->
let pkh = Signature.Public_key.hash pk in
cctxt#message
"@[<v 0>Authorized baking for address: %a@,\
Corresponding full public key: %a@]"
Signature.Public_key_hash.pp pkh
Signature.Public_key.pp pk >>= fun () ->
return_unit
end) ;
Clic.command ~group
~desc: "Get high water mark of a Ledger"
no_options
(prefixes [ "get" ; "ledger" ; "high" ; "watermark" ; "for" ]
@@ 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 _ _ ->
match version.app_class with
| Tezos ->
failwith "Fatal: this operation is only valid with TezBake"
| TezBake ->
wrap_ledger_cmd begin fun pp ->
Ledgerwallet_tezos.get_high_watermark ~pp h
end >>=? fun hwm ->
cctxt#message
"@[<v 0>%a has high water mark: %ld@]"
Signature.Public_key_hash.pp pkh hwm >>= fun () ->
return_unit
end
) ;
Clic.command ~group
~desc: "Set high water mark of a Ledger"
no_options
(prefixes [ "set" ; "ledger" ; "high" ; "watermark" ; "for" ]
@@ Client_keys.sk_uri_param
@@ (prefix "to")
@@ (param
~name: "high watermark"
~desc: "High watermark"
(parameter (fun _ctx s ->
try return (Int32.of_string s)
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 _ _ ->
match version.app_class with
| Tezos ->
failwith "Fatal: this operation is only valid with TezBake"
| TezBake ->
wrap_ledger_cmd begin fun pp ->
Ledgerwallet_tezos.set_high_watermark ~pp h hwm
end >>=? fun () ->
wrap_ledger_cmd begin fun pp ->
Ledgerwallet_tezos.get_high_watermark ~pp h
end >>=? fun new_hwm ->
cctxt#message
"@[<v 0>%a has now high water mark: %ld@]"
Signature.Public_key_hash.pp pkh new_hwm >>= fun () ->
return_unit
end
) ;
]