diff --git a/src/bin_client/main_client.ml b/src/bin_client/main_client.ml index 43b61b5438913436854b7708797c15eccc39e197..e4d687962b76df7889580d8f6382d547553edb29 100644 --- a/src/bin_client/main_client.ml +++ b/src/bin_client/main_client.ml @@ -127,11 +127,11 @@ let select_commands ctxt { block ; protocol } = check_network ctxt >>= fun network -> get_commands_for_version ctxt network block protocol >>|? fun (_, commands_for_version) -> Client_rpc_commands.commands @ + Tezos_signer_backends.Ledger.commands () @ List.map (Clic.map_command (fun (o : Client_context.full) -> (o :> Client_context.io_wallet))) - (Tezos_signer_backends.Ledger.commands () @ - Client_keys_commands.commands network) @ + (Client_keys_commands.commands network) @ Client_helpers_commands.commands () @ commands_for_version diff --git a/src/bin_signer/main_signer.ml b/src/bin_signer/main_signer.ml index 8c5e0c2dfe6adbe9cb517695a4b2f80af49f17ac..bf0968983d9783dfe6b154b2448d00ae101a3eef 100644 --- a/src/bin_signer/main_signer.ml +++ b/src/bin_signer/main_signer.ml @@ -116,155 +116,159 @@ let may_setup_pidfile = function trace (failure "Failed to create the pidfile: %s" pidfile) @@ Lwt_lock_file.create ~unlink_on_exit:true pidfile -let commands base_dir require_auth = - Client_keys_commands.commands None @ +let commands base_dir require_auth : Client_context.full command list = Tezos_signer_backends.Ledger.commands () @ - [ command ~group - ~desc: "Launch a signer daemon over a TCP socket." - (args5 - pidfile_arg - magic_bytes_arg - high_watermark_switch - (default_arg - ~doc: "listening address or host name" - ~short: 'a' - ~long: "address" - ~placeholder: "host|address" - ~default: default_tcp_host - (parameter (fun _ s -> return s))) - (default_arg - ~doc: "listening TCP port or service name" - ~short: 'p' - ~long: "port" - ~placeholder: "port number" - ~default: default_tcp_port - (parameter (fun _ s -> return s)))) - (prefixes [ "launch" ; "socket" ; "signer" ] @@ stop) - (fun (pidfile, magic_bytes, check_high_watermark, host, port) cctxt -> - init_signal () ; - may_setup_pidfile pidfile >>=? fun () -> - Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> - Socket_daemon.run - cctxt (Tcp (host, port, [AI_SOCKTYPE SOCK_STREAM])) - ?magic_bytes ~check_high_watermark ~require_auth >>=? fun _ -> - return_unit) ; - command ~group - ~desc: "Launch a signer daemon over a local Unix socket." - (args4 - pidfile_arg - magic_bytes_arg - high_watermark_switch - (default_arg - ~doc: "path to the local socket file" - ~short: 's' - ~long: "socket" - ~placeholder: "path" - ~default: (Filename.concat base_dir "socket") - (parameter (fun _ s -> return s)))) - (prefixes [ "launch" ; "local" ; "signer" ] @@ stop) - (fun (pidfile, magic_bytes, check_high_watermark, path) cctxt -> - init_signal () ; - may_setup_pidfile pidfile >>=? fun () -> - Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> - Socket_daemon.run - cctxt (Unix path) ?magic_bytes ~check_high_watermark ~require_auth >>=? fun _ -> - return_unit) ; - command ~group - ~desc: "Launch a signer daemon over HTTP." - (args5 - pidfile_arg - magic_bytes_arg - high_watermark_switch - (default_arg - ~doc: "listening address or host name" - ~short: 'a' - ~long: "address" - ~placeholder: "host|address" - ~default: default_http_host - (parameter (fun _ s -> return s))) - (default_arg - ~doc: "listening HTTP port" - ~short: 'p' - ~long: "port" - ~placeholder: "port number" - ~default: default_http_port - (parameter - (fun _ x -> - try return (int_of_string x) - with Failure _ -> failwith "Invalid port %s" x)))) - (prefixes [ "launch" ; "http" ; "signer" ] @@ stop) - (fun (pidfile, magic_bytes, check_high_watermark, host, port) cctxt -> - init_signal () ; - may_setup_pidfile pidfile >>=? fun () -> - Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> - Http_daemon.run_http cctxt ~host ~port ?magic_bytes ~check_high_watermark ~require_auth) ; - command ~group - ~desc: "Launch a signer daemon over HTTPS." - (args5 - pidfile_arg - magic_bytes_arg - high_watermark_switch - (default_arg - ~doc: "listening address or host name" - ~short: 'a' - ~long: "address" - ~placeholder: "host|address" - ~default: default_https_host - (parameter (fun _ s -> return s))) - (default_arg - ~doc: "listening HTTPS port" - ~short: 'p' - ~long: "port" - ~placeholder: "port number" - ~default: default_https_port - (parameter - (fun _ x -> - try return (int_of_string x) - with Failure _ -> failwith "Invalid port %s" x)))) - (prefixes [ "launch" ; "https" ; "signer" ] @@ - param - ~name:"cert" - ~desc: "path to the TLS certificate" - (parameter (fun _ s -> - if not (Sys.file_exists s) then - failwith "No such TLS certificate file %s" s - else - return s)) @@ - param - ~name:"key" - ~desc: "path to the TLS key" - (parameter (fun _ s -> - if not (Sys.file_exists s) then - failwith "No such TLS key file %s" s - else - return s)) @@ stop) - (fun (pidfile, magic_bytes, check_high_watermark, host, port) cert key cctxt -> - init_signal () ; - may_setup_pidfile pidfile >>=? fun () -> - Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> - Http_daemon.run_https cctxt ~host ~port ~cert ~key ?magic_bytes ~check_high_watermark ~require_auth) ; - command ~group - ~desc: "Authorize a given public key to perform signing requests." - (args1 - (arg - ~doc: "an optional name for the key (defaults to the hash)" - ~short: 'N' - ~long: "name" - ~placeholder: "name" - (parameter (fun _ s -> return s)))) - (prefixes [ "add" ; "authorized" ; "key" ] @@ - param - ~name:"pk" - ~desc: "full public key (Base58 encoded)" - (parameter (fun _ s -> Lwt.return (Signature.Public_key.of_b58check s))) @@ - stop) - (fun name key cctxt -> - let pkh = Signature.Public_key.hash key in - let name = match name with - | Some name -> name - | None -> Signature.Public_key_hash.to_b58check pkh in - Handler.Authorized_key.add ~force:false cctxt name key) - ] + List.map + (Clic.map_command + (fun (o : Client_context.full) -> (o :> Client_context.io_wallet))) + (Client_keys_commands.commands None @ + Client_keys_commands.commands None @ + [ command ~group + ~desc: "Launch a signer daemon over a TCP socket." + (args5 + pidfile_arg + magic_bytes_arg + high_watermark_switch + (default_arg + ~doc: "listening address or host name" + ~short: 'a' + ~long: "address" + ~placeholder: "host|address" + ~default: default_tcp_host + (parameter (fun _ s -> return s))) + (default_arg + ~doc: "listening TCP port or service name" + ~short: 'p' + ~long: "port" + ~placeholder: "port number" + ~default: default_tcp_port + (parameter (fun _ s -> return s)))) + (prefixes [ "launch" ; "socket" ; "signer" ] @@ stop) + (fun (pidfile, magic_bytes, check_high_watermark, host, port) cctxt -> + init_signal () ; + may_setup_pidfile pidfile >>=? fun () -> + Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> + Socket_daemon.run + cctxt (Tcp (host, port, [AI_SOCKTYPE SOCK_STREAM])) + ?magic_bytes ~check_high_watermark ~require_auth >>=? fun _ -> + return_unit) ; + command ~group + ~desc: "Launch a signer daemon over a local Unix socket." + (args4 + pidfile_arg + magic_bytes_arg + high_watermark_switch + (default_arg + ~doc: "path to the local socket file" + ~short: 's' + ~long: "socket" + ~placeholder: "path" + ~default: (Filename.concat base_dir "socket") + (parameter (fun _ s -> return s)))) + (prefixes [ "launch" ; "local" ; "signer" ] @@ stop) + (fun (pidfile, magic_bytes, check_high_watermark, path) cctxt -> + init_signal () ; + may_setup_pidfile pidfile >>=? fun () -> + Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> + Socket_daemon.run + cctxt (Unix path) ?magic_bytes ~check_high_watermark ~require_auth >>=? fun _ -> + return_unit) ; + command ~group + ~desc: "Launch a signer daemon over HTTP." + (args5 + pidfile_arg + magic_bytes_arg + high_watermark_switch + (default_arg + ~doc: "listening address or host name" + ~short: 'a' + ~long: "address" + ~placeholder: "host|address" + ~default: default_http_host + (parameter (fun _ s -> return s))) + (default_arg + ~doc: "listening HTTP port" + ~short: 'p' + ~long: "port" + ~placeholder: "port number" + ~default: default_http_port + (parameter + (fun _ x -> + try return (int_of_string x) + with Failure _ -> failwith "Invalid port %s" x)))) + (prefixes [ "launch" ; "http" ; "signer" ] @@ stop) + (fun (pidfile, magic_bytes, check_high_watermark, host, port) cctxt -> + init_signal () ; + may_setup_pidfile pidfile >>=? fun () -> + Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> + Http_daemon.run_http cctxt ~host ~port ?magic_bytes ~check_high_watermark ~require_auth) ; + command ~group + ~desc: "Launch a signer daemon over HTTPS." + (args5 + pidfile_arg + magic_bytes_arg + high_watermark_switch + (default_arg + ~doc: "listening address or host name" + ~short: 'a' + ~long: "address" + ~placeholder: "host|address" + ~default: default_https_host + (parameter (fun _ s -> return s))) + (default_arg + ~doc: "listening HTTPS port" + ~short: 'p' + ~long: "port" + ~placeholder: "port number" + ~default: default_https_port + (parameter + (fun _ x -> + try return (int_of_string x) + with Failure _ -> failwith "Invalid port %s" x)))) + (prefixes [ "launch" ; "https" ; "signer" ] @@ + param + ~name:"cert" + ~desc: "path to th TLS certificate" + (parameter (fun _ s -> + if not (Sys.file_exists s) then + failwith "No such TLS certificate file %s" s + else + return s)) @@ + param + ~name:"key" + ~desc: "path to th TLS key" + (parameter (fun _ s -> + if not (Sys.file_exists s) then + failwith "No such TLS key file %s" s + else + return s)) @@ stop) + (fun (pidfile, magic_bytes, check_high_watermark, host, port) cert key cctxt -> + init_signal () ; + may_setup_pidfile pidfile >>=? fun () -> + Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> + Http_daemon.run_https cctxt ~host ~port ~cert ~key ?magic_bytes ~check_high_watermark ~require_auth) ; + command ~group + ~desc: "Authorize a given public key to perform signing requests." + (args1 + (arg + ~doc: "an optional name for the key (defaults to the hash)" + ~short: 'N' + ~long: "name" + ~placeholder: "name" + (parameter (fun _ s -> return s)))) + (prefixes [ "add" ; "authorized" ; "key" ] @@ + param + ~name:"pk" + ~desc: "full public key (Base58 encoded)" + (parameter (fun _ s -> Lwt.return (Signature.Public_key.of_b58check s))) @@ + stop) + (fun name key cctxt -> + let pkh = Signature.Public_key.hash key in + let name = match name with + | Some name -> name + | None -> Signature.Public_key_hash.to_b58check pkh in + Handler.Authorized_key.add ~force:false cctxt name key) + ]) let home = try Sys.getenv "HOME" with Not_found -> "/root" @@ -333,11 +337,13 @@ let main () = (global_options ()) () original_args >>=? fun ((base_dir, require_auth, password_filename), remaining) -> let base_dir = Option.unopt ~default:default_base_dir base_dir in - let cctxt = object - inherit Client_context_unix.unix_logger ~base_dir - inherit Client_context_unix.unix_prompter - inherit Client_context_unix.unix_wallet ~base_dir ~password_filename - end in + let cctxt = + new Client_context_unix.unix_full + ~block:Client_config.default_block + ~confirmations:None + ~password_filename + ~base_dir + ~rpc_config:RPC_client.default_config in Client_keys.register_signer (module Tezos_signer_backends.Encrypted.Make(struct let cctxt = new Client_context_unix.unix_prompter diff --git a/src/lib_signer_backends/dune b/src/lib_signer_backends/dune index ecc819e131191a03607f89bcfb8d49f704b45241..7817f02fe951ed1f13bc48a140ad55065247de62 100644 --- a/src/lib_signer_backends/dune +++ b/src/lib_signer_backends/dune @@ -6,6 +6,7 @@ tezos-client-base tezos-rpc-http tezos-signer-services + tezos-shell-services pbkdf bip39 ledgerwallet-tezos) @@ -13,6 +14,7 @@ -open Tezos_stdlib_unix -open Tezos_client_base -open Tezos_signer_services + -open Tezos_shell_services -open Tezos_rpc_http))) (alias diff --git a/src/lib_signer_backends/ledger.ml b/src/lib_signer_backends/ledger.ml index dbd4e62f2a9f1a9f6cc96248d7bc14e2020ccee1..ea29236c70dfd86776602b8548804562edc1e16a 100644 --- a/src/lib_signer_backends/ledger.ml +++ b/src/lib_signer_backends/ledger.ml @@ -195,19 +195,23 @@ let wrap_ledger_cmd f = | Ok v -> return v -let get_public_key - ?(authorize_baking=false) +let public_key_returning_instruction which ?(prompt=false) ledger curve path = let path = tezos_root @ path in - begin match authorize_baking with - | false -> wrap_ledger_cmd begin fun pp -> + begin match which with + | `Get_public_key -> wrap_ledger_cmd begin fun pp -> Ledgerwallet_tezos.get_public_key ~prompt ~pp ledger curve path end - | true -> + | `Authorize_baking -> wrap_ledger_cmd begin fun pp -> Ledgerwallet_tezos.authorize_baking ~pp ledger curve path end + | `Setup (main_chain_id, main_hwm, test_hwm) -> + wrap_ledger_cmd begin fun pp -> + Ledgerwallet_tezos.setup_baking ~pp ledger curve path + ~main_chain_id ~main_hwm ~test_hwm + end end >>|? fun pk -> let pk = Cstruct.to_bigarray pk in match curve with @@ -233,6 +237,8 @@ let get_public_key let _nb_written = write_key ~compress:true (MBytes.sub buf 1 pklen) pk in Data_encoding.Binary.of_bytes_exn Signature.Public_key.encoding buf +let get_public_key = public_key_returning_instruction `Get_public_key + module Ledger = struct type t = { device_info : Hidapi.device_info ; @@ -450,6 +456,12 @@ let curve_of_id = function | Pkh pkh -> return (curve_of_pkh pkh) | Animals (a, curve_opt) -> unopt_curve a curve_opt +(* The Ledger uses a special value 0x00000000 for the “any” chain-id: *) +let pp_ledger_chain_id fmt s = + match s with + | "\x00\x00\x00\x00" -> Format.fprintf fmt "'Unspecified'" + | other -> Format.fprintf fmt "%a" Chain_id.pp (Chain_id.of_string_exn other) + let sign ?watermark sk_uri msg = id_of_sk_uri sk_uri >>=? fun id -> with_ledger id begin fun ledger { major; minor; patch; _ } _of_curve _of_pkh -> @@ -511,7 +523,7 @@ let commands = ~desc: "List supported Ledger Nano S devices connected." no_options (fixed [ "list" ; "connected" ; "ledgers" ]) - (fun () (cctxt : Client_context.io_wallet) -> + (fun () (cctxt : Client_context.full) -> find_ledgers () >>=? function | [] -> cctxt#message "No device found." >>= fun () -> @@ -557,7 +569,7 @@ let commands = (prefixes [ "show" ; "ledger" ] @@ Client_keys.sk_uri_param @@ stop) - (fun test_sign sk_uri (cctxt : Client_context.io_wallet) -> + (fun test_sign sk_uri (cctxt : Client_context.full) -> neuterize sk_uri >>=? fun pk_uri -> id_of_pk_uri pk_uri >>=? fun id -> find_ledgers ~id () >>=? function @@ -625,7 +637,7 @@ let commands = (prefixes [ "get" ; "ledger" ; "authorized" ; "path" ; "for" ] @@ Public_key.alias_param @@ stop) - (fun () (name, (pk_uri, _)) (cctxt : Client_context.io_wallet) -> + (fun () (name, (pk_uri, _)) (cctxt : Client_context.full) -> 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 -> @@ -644,17 +656,40 @@ let commands = end) ; Clic.command ~group - ~desc: "Authorize a Ledger to bake for a key" + ~desc: "Authorize a Ledger to bake for a key (deprecated, \ + use `setup ledger ...` with recent versions of the Baking app)" no_options (prefixes [ "authorize" ; "ledger" ; "to" ; "bake" ; "for" ] @@ Public_key.alias_param @@ stop) - (fun () (_, (pk_uri, _)) (cctxt : Client_context.io_wallet) -> + (fun () (_, (pk_uri, _)) (cctxt : Client_context.full) -> id_of_pk_uri pk_uri >>=? fun root_id -> - with_ledger root_id begin fun h _version _of_curve _of_pkh -> + with_ledger root_id begin fun h version _of_curve _of_pkh -> + begin match version with + | { Ledgerwallet_tezos.Version.app_class = Tezos ; _ } -> + failwith "This command (`authorize ledger ...`) only \ + works with the Tezos Baking app" + | { Ledgerwallet_tezos.Version.app_class = TezBake ; + major ; _ } when major >= 2 -> + failwith + "This command (`authorize ledger ...`) is@ \ + not compatible with@ this version of the Ledger@ \ + Baking app (%a >= 2.0.0),@ please use the command@ \ + `setup ledger to bake for ...`@ from now on." + Ledgerwallet_tezos.Version.pp version + | _ -> + cctxt#message + "This Ledger Baking app is outdated (%a)@ running@ \ + in backwards@ compatibility mode." + Ledgerwallet_tezos.Version.pp version + >>= fun () -> + return_unit + end + >>=? fun () -> let path = path_of_pk_uri pk_uri in curve_of_id root_id >>=? fun curve -> - get_public_key ~authorize_baking:true h curve path >>=? fun pk -> + public_key_returning_instruction `Authorize_baking h curve path + >>=? fun pk -> let pkh = Signature.Public_key.hash pk in cctxt#message "@[Authorized baking for address: %a@,\ @@ -664,26 +699,147 @@ let commands = return_unit end) ; + Clic.command ~group + ~desc: "Setup a Ledger to bake for a key" + (let hwm_arg kind = + let doc = + Printf.sprintf + "Use as %s chain high watermark instead of asking the ledger." + kind in + let long = kind ^ "-hwm" in + default_arg ~doc ~long ~placeholder:"HWM" + ~default:"ASK-LEDGER" + (parameter + (fun _ -> function + | "ASK-LEDGER" -> return None + | s -> + try return (Some (Int32.of_string s)) with _ -> + failwith "Parameter %S should be a 32-bits integer" s)) + in + args3 + (default_arg + ~doc:"Use as main chain-id instead of asking the node." + ~long:"main-chain-id" ~placeholder:"ID" + ~default:"ASK-NODE" + (parameter + (fun _ -> function + | "ASK-NODE" -> return `Ask_node + | s -> + try return (`Int32 (Int32.of_string s)) + with _ -> + (try return (`Chain_id (Chain_id.of_b58check_exn s)) + with _ -> + failwith "Parameter %S should be a 32-bits integer \ + or a Base58 chain-id" s)))) + (hwm_arg "main") (hwm_arg "test")) + (prefixes [ "setup" ; "ledger" ; "to" ; "bake" ; "for" ] + @@ Public_key.alias_param + @@ stop) + (fun (chain_id_opt, main_hwm_opt, test_hwm_opt) + (_, (pk_uri, _)) (cctxt : Client_context.full) -> + id_of_pk_uri pk_uri >>=? fun root_id -> + with_ledger root_id begin fun h version _of_curve _of_pkh -> + begin + let open Ledgerwallet_tezos.Version in + match version with + | { app_class = Tezos ; _ } -> + failwith "This command (`setup ledger ...`) only \ + works with the Tezos Baking app" + | { app_class = TezBake ; + major ; _ } when major < 2 -> + failwith + "This command (`setup ledger ...`)@ is not@ compatible@ with \ + this version@ of the Ledger Baking app@ (%a < 2.0.0),@ \ + please upgrade@ your ledger@ or use the command@ \ + `authorize ledger to bake for ...`" + pp version + | _ -> return_unit + end + >>=? fun () -> + let chain_id_of_int32 i32 = + let open Int32 in + let byte n = + logand 0xFFl (shift_right i32 (n * 8)) + |> Int32.to_int |> char_of_int in + Chain_id.of_string_exn + (Stringext.of_array (Array.init 4 (fun i -> byte (3 - i)))) in + begin match chain_id_opt with + | `Ask_node -> + Chain_services.chain_id cctxt () + | `Int32 s -> return (chain_id_of_int32 s) + | `Chain_id chid -> return chid + end + >>=? fun main_chain_id -> + let path = path_of_pk_uri pk_uri in + curve_of_id root_id >>=? fun curve -> + wrap_ledger_cmd begin fun pp -> + Ledgerwallet_tezos.get_all_high_watermarks ~pp h + end + >>=? fun (`Main_hwm current_mh, `Test_hwm current_th, `Chain_id current_ci) -> + let main_hwm = Option.unopt main_hwm_opt ~default:current_mh in + let test_hwm = Option.unopt test_hwm_opt ~default:current_th in + cctxt#message "Setting up the ledger:@.\ + * Main chain ID: %a -> %a@.\ + * Main chain High Watermark: %ld -> %ld@.\ + * Test chain High Watermark: %ld -> %ld" + pp_ledger_chain_id current_ci + Chain_id.pp main_chain_id + current_mh main_hwm + current_th test_hwm + >>= fun () -> + public_key_returning_instruction + (`Setup (Chain_id.to_string main_chain_id, main_hwm, test_hwm)) + h curve path + >>=? fun pk -> + let pkh = Signature.Public_key.hash pk in + cctxt#message + "@[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 + (args1 (switch ~doc:"Prevent the fallback to the (deprecated) Ledger \ + instructions (for 1.x.y versions of the Baking app)" + ~long:"no-legacy-instructions" ())) (prefixes [ "get" ; "ledger" ; "high" ; "watermark" ; "for" ] @@ Client_keys.sk_uri_param @@ stop) - (fun () sk_uri (cctxt : Client_context.io_wallet) -> + (fun no_legacy_apdu sk_uri (cctxt : Client_context.full) -> 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" - | TezBake -> + failwith "Fatal: this operation is only valid with the \ + Tezos Baking application" + | TezBake when not no_legacy_apdu && version.major < 2 -> wrap_ledger_cmd begin fun pp -> Ledgerwallet_tezos.get_high_watermark ~pp h - end >>=? fun hwm -> - cctxt#message - "@[%a has high water mark: %ld@]" + end + >>=? fun hwm -> + cctxt#message "The high water mark for@ %a@ is %ld." pp_id id hwm >>= fun () -> return_unit + | TezBake when no_legacy_apdu && version.major < 2 -> + failwith + "Cannot get the high water mark with@ \ + `--no-legacy-instructions` and version %a" + Ledgerwallet_tezos.Version.pp version + | TezBake -> + wrap_ledger_cmd begin fun pp -> + Ledgerwallet_tezos.get_all_high_watermarks ~pp h + end + >>=? fun (`Main_hwm mh, `Test_hwm th, `Chain_id ci) -> + cctxt#message + "The high water mark values for@ %a@ are\ + @ %ld for the main-chain@ (%a)@ \ + and@ %ld for the test-chain." + pp_id id mh pp_ledger_chain_id ci th + >>= fun () -> + return_unit end ) ; @@ -700,7 +856,7 @@ let commands = 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) -> + (fun () sk_uri hwm (cctxt : Client_context.full) -> id_of_sk_uri sk_uri >>=? fun id -> with_ledger id begin fun h version _ _ -> match version.app_class with diff --git a/src/lib_signer_backends/ledger.mli b/src/lib_signer_backends/ledger.mli index 86c608634836dce26f2aa0dab6ba48dff422c689..aa4c749f330f5d0b57b36864ca5ed0d50a127a7b 100644 --- a/src/lib_signer_backends/ledger.mli +++ b/src/lib_signer_backends/ledger.mli @@ -37,4 +37,6 @@ end include Client_keys.SIGNER -val commands : unit -> Client_context.io_wallet Clic.command list + + +val commands : unit -> Client_context.full Clic.command list diff --git a/src/lib_signer_backends/tezos-signer-backends.opam b/src/lib_signer_backends/tezos-signer-backends.opam index 05fa561c1a89101cb50bf3b4985b87aef2b96235..96e885218ab5d946c43f605d841c3893a1800f37 100644 --- a/src/lib_signer_backends/tezos-signer-backends.opam +++ b/src/lib_signer_backends/tezos-signer-backends.opam @@ -13,6 +13,7 @@ depends: [ "tezos-client-base" "tezos-rpc-http" "tezos-signer-services" + "tezos-shell-services" "pbkdf" "bip39" "ledgerwallet-tezos" diff --git a/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.ml b/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.ml index 61efe165e74373e3509dba039cf6ff37c0f88199..6868bef2994e690512330eb52ef6f80c5f9c34c8 100644 --- a/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.ml +++ b/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.ml @@ -64,6 +64,8 @@ type ins = | Reset_high_watermark | Query_high_watermark | Get_authorized_key + | Setup + | Query_all_high_watermarks let int_of_ins = function | Version -> 0x00 @@ -76,6 +78,8 @@ let int_of_ins = function | Query_high_watermark -> 0x08 | Git_commit -> 0x09 | Get_authorized_key -> 0x07 + | Setup -> 0x0A + | Query_all_high_watermarks -> 0x0B type curve = | Ed25519 @@ -154,11 +158,48 @@ let get_public_key ?(prompt=true) = let authorize_baking = get_public_key_like Authorize_baking +let setup_baking ?pp ?buf h ~main_chain_id ~main_hwm ~test_hwm curve path = + let nb_derivations = List.length path in + if nb_derivations > 10 then + invalid_arg "Ledgerwallet_tezos.setup: max 10 derivations" ; + let lc = + (* [ chain-id | main-hwm | test-hwm | derivations-path ] *) + (* derivations-path = [ length | paths ] *) + (3 * 4) + 1 + (4 * nb_derivations) in + let data_init = Cstruct.create lc in + (* If the size of chain-ids changes, then all assumptions of this + binary format are broken (the ledger expects an int32). *) + assert (String.length main_chain_id = 4) ; + for ith = 0 to 3 do + Cstruct.set_uint8 data_init ith (int_of_char main_chain_id.[ith]) ; + done ; + Cstruct.BE.set_uint32 data_init 4 main_hwm ; + Cstruct.BE.set_uint32 data_init 8 test_hwm ; + Cstruct.set_uint8 data_init 12 nb_derivations ; + let (_ : Cstruct.t) = + let data = Cstruct.shift data_init (12 + 1) in + write_path data path in + let msg = "setup" in + let apdu = + Apdu.create + ~p2:(int_of_curve curve) ~lc ~data:data_init (wrap_ins Setup) in + Transport.apdu ~msg ?pp ?buf h apdu >>| fun addr -> + let keylen = Cstruct.get_uint8 addr 0 in + Cstruct.sub addr 1 keylen + let get_high_watermark ?pp ?buf h = let apdu = Apdu.create (wrap_ins Query_high_watermark) in Transport.apdu ~msg:"get_high_watermark" ?pp ?buf h apdu >>| fun hwm -> Cstruct.BE.get_uint32 hwm 0 +let get_all_high_watermarks ?pp ?buf h = + let apdu = Apdu.create (wrap_ins Query_all_high_watermarks) in + Transport.apdu ~msg:"get_high_watermark" ?pp ?buf h apdu >>| fun tuple -> + let main_hwm = Cstruct.BE.get_uint32 tuple 0 in + let test_hwm = Cstruct.BE.get_uint32 tuple 4 in + let chain_id = Cstruct.copy tuple 8 4 in + (`Main_hwm main_hwm, `Test_hwm test_hwm, `Chain_id chain_id) + let set_high_watermark ?pp ?buf h hwm = let data = Cstruct.create 4 in Cstruct.BE.set_uint32 data 0 hwm ; diff --git a/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.mli b/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.mli index 579121336e34d1dfa5eedd1f174bcc0e005403a1..2e2eefff300d878c1a3dd04cfb50ac647e70df07 100644 --- a/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.mli +++ b/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.mli @@ -65,20 +65,45 @@ val authorize_baking : (** [authorize_baking ?pp ?buf ?prompt ledger curve path] is like [get_public_key] with [prompt = true], but only works with the baking Ledger application and serves to indicate that the key from - [curve] at [path] is allowed to bake. *) + [curve] at [path] is allowed to bake. + + This is deprecated as it ignores test-chains, see {!setup_baking}. *) + +val setup_baking : + ?pp:Format.formatter -> ?buf:Cstruct.t -> Hidapi.t -> + main_chain_id: string -> main_hwm:int32 -> test_hwm:int32 -> + curve -> int32 list -> (Cstruct.t, Transport.error) result +(** [setup_baking ?pp ?buf ?prompt ledger ~main_chain_id ~main_hwm ~test_hwm curve path] + sets up the Ledger's Baking application: it informs + the device of the ID of the main chain (should be of length [4]), + sets the high watermarks for the main and test chains, {i and} + indicates that the key at the given [curve/path] is authorized for + baking. *) val get_high_watermark : ?pp:Format.formatter -> ?buf:Cstruct.t -> Hidapi.t -> (int32, Transport.error) result (** [get_high_watermark ?pp ?buf ledger] is the current value of the - high water mark on [ledger]. This works with the baking app - only. *) + high water mark for the main-chain on [ledger]. This works with + the baking app only. See {!get_all_high_watermarks} for a more + complete query. *) + +val get_all_high_watermarks : + ?pp:Format.formatter -> + ?buf:Cstruct.t -> + Hidapi.t -> + ([ `Main_hwm of int32 ] * [ `Test_hwm of int32 ] * [ `Chain_id of string ], + Transport.error) result +(** Query the high water marks for the main and test chains, as well as the ID + of the main-chain (string of length 4) recorded by the Ledger Baking app. *) val set_high_watermark : ?pp:Format.formatter -> ?buf:Cstruct.t -> Hidapi.t -> int32 -> (unit, Transport.error) result -(** [get_high_watermark ?pp ?buf ledger hwm] reset the high water - mark on [ledger] to [hwm]. This works with the baking app only. *) +(** [set_high_watermark ?pp ?buf ledger hwm] reset the high water + mark on [ledger] to [hwm] for the main-chain. + This works with the baking app only. Use {!setup_baking} to be able to also + reset all the test-chain water mark. *) val sign : ?pp:Format.formatter ->