From f345a98ed567fce97d0622d97d0413a75191e99b Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 6 May 2022 13:44:40 -0400 Subject: [PATCH 1/9] Baker: Add a command-line arg for deterministic nonces in the baker --- .../lib_delegate/baking_commands.ml | 28 +++++++++++++++++-- src/proto_alpha/lib_delegate/client_daemon.ml | 3 +- .../lib_delegate/client_daemon.mli | 1 + 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_commands.ml b/src/proto_alpha/lib_delegate/baking_commands.ml index 78d6ec9e61a1..68b2c0be9753 100644 --- a/src/proto_alpha/lib_delegate/baking_commands.ml +++ b/src/proto_alpha/lib_delegate/baking_commands.ml @@ -163,6 +163,25 @@ let liquidity_baking_toggle_vote_arg = ~placeholder:"vote" liquidity_baking_toggle_vote_parameter +let baking_nonce_mode_parameter = + Clic.parameter + ~autocomplete:(fun _ctxt -> return ["random"; "deterministic"]) + (fun _ctxt -> function + | "random" -> return Baking_configuration.Random + | "deterministic" -> return Baking_configuration.Deterministic + | s -> + failwith + "unexpected vote: %s, expected either \"random\" or \ + \"deterministic\"." + s) + +let baking_nonce_mode_arg = + Clic.arg + ~doc:"Baking nonce generation mode" + ~long:"baking-nonce-mode" + ~placeholder:"nonce-mode" + baking_nonce_mode_parameter + let get_delegates (cctxt : Protocol_client_context.full) (pkhs : Signature.public_key_hash list) = let proj_delegate (alias, public_key_hash, public_key, secret_key_uri) = @@ -321,7 +340,7 @@ let baker_commands () : Protocol_client_context.full Clic.command list = command ~group ~desc:"Launch the baker daemon." - (args8 + (args9 pidfile_arg minimal_fees_arg minimal_nanotez_per_gas_unit_arg @@ -329,7 +348,8 @@ let baker_commands () : Protocol_client_context.full Clic.command list = keep_alive_arg liquidity_baking_toggle_vote_arg per_block_vote_file_arg - operations_arg) + operations_arg + baking_nonce_mode_arg) (prefixes ["run"; "with"; "local"; "node"] @@ param ~name:"node_data_path" @@ -343,7 +363,8 @@ let baker_commands () : Protocol_client_context.full Clic.command list = keep_alive, liquidity_baking_toggle_vote, per_block_vote_file, - extra_operations ) + extra_operations, + nonce ) node_data_path sources cctxt -> @@ -365,6 +386,7 @@ let baker_commands () : Protocol_client_context.full Clic.command list = ~liquidity_baking_toggle_vote ?per_block_vote_file ?extra_operations + ?nonce ~chain:cctxt#chain ~context_path ~keep_alive diff --git a/src/proto_alpha/lib_delegate/client_daemon.ml b/src/proto_alpha/lib_delegate/client_daemon.ml index cb5d92e4b6bc..c816dfc3e05b 100644 --- a/src/proto_alpha/lib_delegate/client_daemon.ml +++ b/src/proto_alpha/lib_delegate/client_daemon.ml @@ -70,7 +70,7 @@ module Baker = struct let run (cctxt : Protocol_client_context.full) ?minimal_fees ?minimal_nanotez_per_gas_unit ?minimal_nanotez_per_byte ~liquidity_baking_toggle_vote ?per_block_vote_file ?extra_operations - ~chain ~context_path ~keep_alive delegates = + ?nonce ~chain ~context_path ~keep_alive delegates = let process () = Config_services.user_activated_upgrades cctxt >>=? fun user_activated_upgrades -> @@ -84,6 +84,7 @@ module Baker = struct ?extra_operations ~context_path ~user_activated_upgrades + ?nonce () in cctxt#message diff --git a/src/proto_alpha/lib_delegate/client_daemon.mli b/src/proto_alpha/lib_delegate/client_daemon.mli index 0e6f79986105..13bdaff17fce 100644 --- a/src/proto_alpha/lib_delegate/client_daemon.mli +++ b/src/proto_alpha/lib_delegate/client_daemon.mli @@ -36,6 +36,7 @@ module Baker : sig Protocol.Alpha_context.Liquidity_baking.liquidity_baking_toggle_vote -> ?per_block_vote_file:string -> ?extra_operations:Baking_configuration.Operations_source.t -> + ?nonce:Baking_configuration.nonce_config -> chain:Shell_services.chain -> context_path:string -> keep_alive:bool -> -- GitLab From 8ad9e4b4004030c82e6cf6ccd7289ca59b24f6fc Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 6 May 2022 14:00:37 -0400 Subject: [PATCH 2/9] Baker: Start time for deterministic nonce generation One problem with deterministic nonce generation is that previous nonces were generated using the random method. So we need to have a cut-over time, so that future nonces can be generated deterministically. --- .../lib_delegate/baking_commands.ml | 20 +++++++++++-------- .../lib_delegate/baking_configuration.ml | 8 ++++---- .../lib_delegate/baking_configuration.mli | 2 +- src/proto_alpha/lib_delegate/baking_nonces.ml | 4 ++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_commands.ml b/src/proto_alpha/lib_delegate/baking_commands.ml index 68b2c0be9753..940a3aaf90db 100644 --- a/src/proto_alpha/lib_delegate/baking_commands.ml +++ b/src/proto_alpha/lib_delegate/baking_commands.ml @@ -165,19 +165,23 @@ let liquidity_baking_toggle_vote_arg = let baking_nonce_mode_parameter = Clic.parameter - ~autocomplete:(fun _ctxt -> return ["random"; "deterministic"]) + ~autocomplete:(fun _ctxt -> return ["random"; "1"]) (fun _ctxt -> function | "random" -> return Baking_configuration.Random - | "deterministic" -> return Baking_configuration.Deterministic - | s -> - failwith - "unexpected vote: %s, expected either \"random\" or \ - \"deterministic\"." - s) + | s -> ( + match Int32.of_string_opt s with + | Some block -> return (Baking_configuration.Deterministic block) + | None -> + failwith + "unexpected vote: %s, expected either \"random\" or \ + \"deterministic\"." + s)) let baking_nonce_mode_arg = Clic.arg - ~doc:"Baking nonce generation mode" + ~doc: + "Baking nonce generation mode: either \"random\" or a block number after \ + which deterministic generation should be used (use \"1\" for always)" ~long:"baking-nonce-mode" ~placeholder:"nonce-mode" baking_nonce_mode_parameter diff --git a/src/proto_alpha/lib_delegate/baking_configuration.ml b/src/proto_alpha/lib_delegate/baking_configuration.ml index d7b24e86ddc7..fc1d53aa049e 100644 --- a/src/proto_alpha/lib_delegate/baking_configuration.ml +++ b/src/proto_alpha/lib_delegate/baking_configuration.ml @@ -72,7 +72,7 @@ type validation_config = | Node | ContextIndex of Abstract_context_index.t -type nonce_config = Deterministic | Random +type nonce_config = Deterministic of int32 | Random type state_recorder_config = Filesystem | Disabled @@ -206,9 +206,9 @@ let nonce_config_encoding = case ~title:"Deterministic" (Tag 0) - (constant "deterministic") - (function Deterministic -> Some () | _ -> None) - (fun () -> Deterministic); + (obj1 (req "deterministic" int32)) + (function Deterministic block -> Some block | _ -> None) + (fun block -> Deterministic block); case ~title:"Random" (Tag 1) diff --git a/src/proto_alpha/lib_delegate/baking_configuration.mli b/src/proto_alpha/lib_delegate/baking_configuration.mli index bb90f6f5cf4a..12bd1029c8d9 100644 --- a/src/proto_alpha/lib_delegate/baking_configuration.mli +++ b/src/proto_alpha/lib_delegate/baking_configuration.mli @@ -47,7 +47,7 @@ type validation_config = | Node | ContextIndex of Abstract_context_index.t -type nonce_config = Deterministic | Random +type nonce_config = Deterministic of int32 | Random type state_recorder_config = Filesystem | Disabled diff --git a/src/proto_alpha/lib_delegate/baking_nonces.ml b/src/proto_alpha/lib_delegate/baking_nonces.ml index 170bedfdb697..a0a96b669bc5 100644 --- a/src/proto_alpha/lib_delegate/baking_nonces.ml +++ b/src/proto_alpha/lib_delegate/baking_nonces.ml @@ -201,12 +201,12 @@ let get_unrevealed_nonces ({cctxt; chain; _} as state) nonces = let generate_seed_nonce (nonce_config : Baking_configuration.nonce_config) (delegate : Baking_state.delegate) level = (match nonce_config with - | Deterministic -> + | Deterministic start_level when start_level <= Raw_level.to_int32 level -> let data = Data_encoding.Binary.to_bytes_exn Raw_level.encoding level in Client_keys.deterministic_nonce delegate.secret_key_uri data >>=? fun nonce -> return (Data_encoding.Binary.of_bytes_exn Nonce.encoding nonce) - | Random -> ( + | Deterministic _ | Random -> ( match Nonce.of_bytes (Rand.generate Constants.nonce_length) with | Error _errs -> assert false | Ok nonce -> return nonce)) -- GitLab From f17d602be6e051ef79c0decfd9b36aa22b358218 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 9 May 2022 11:39:57 -0400 Subject: [PATCH 3/9] Baker: Faster deterministic nonce generation This is necessary because ledger operations can take over a second each, and a baker might need to (re-) generate a bunch of nonces at the beginning of a cycle. See https://gitlab.com/tezos/tezos/-/merge_requests/1245#note_337305534 for benchmarks. This code is safe to modify, because deterministic nonces were previously unused. --- .../unix/ledger.available.ml | 156 ++++++++++++++++-- 1 file changed, 143 insertions(+), 13 deletions(-) diff --git a/src/lib_signer_backends/unix/ledger.available.ml b/src/lib_signer_backends/unix/ledger.available.ml index 2bf526f9229b..8a798c045a9f 100644 --- a/src/lib_signer_backends/unix/ledger.available.ml +++ b/src/lib_signer_backends/unix/ledger.available.ml @@ -348,19 +348,144 @@ module Ledger_commands = struct let signature = P256.of_bytes_exn (Bigstring.to_bytes buf) in return (Signature.of_p256 signature) - let get_deterministic_nonce hid curve path msg = + module Ledger_key = struct + type t = { + device_info : Hidapi.device_info; + curve : Ledgerwallet_tezos.curve; + path : int32 list; + } + + include Compare.Make (struct + type nonrec t = t + + let compare_device_info + ({ + path = path_a; + vendor_id = vendor_id_a; + product_id = product_id_a; + serial_number = serial_number_a; + release_number = release_number_a; + manufacturer_string = manufacturer_string_a; + product_string = product_string_a; + usage_page = usage_page_a; + usage = usage_a; + interface_number = interface_number_a; + } : + Hidapi.device_info) + ({ + path = path_b; + vendor_id = vendor_id_b; + product_id = product_id_b; + serial_number = serial_number_b; + release_number = release_number_b; + manufacturer_string = manufacturer_string_b; + product_string = product_string_b; + usage_page = usage_page_b; + usage = usage_b; + interface_number = interface_number_b; + } : + Hidapi.device_info) = + Compare.or_else (String.compare path_a path_b) (fun () -> + Compare.or_else + (Compare.Int.compare vendor_id_a vendor_id_b) + (fun () -> + Compare.or_else + (Compare.Int.compare product_id_a product_id_b) + (fun () -> + Compare.or_else + (Option.compare + Compare.String.compare + serial_number_a + serial_number_b) + (fun () -> + Compare.or_else + (Compare.Int.compare + release_number_a + release_number_b) + (fun () -> + Compare.or_else + (Option.compare + Compare.String.compare + manufacturer_string_a + manufacturer_string_b) + (fun () -> + Compare.or_else + (Option.compare + Compare.String.compare + product_string_a + product_string_b) + (fun () -> + Compare.or_else + (Compare.Int.compare + usage_page_a + usage_page_b) + (fun () -> + Compare.or_else + (Compare.Int.compare usage_a usage_b) + (fun () -> + Compare.Int.compare + interface_number_a + interface_number_b))))))))) + + let compare_curve a_curve b_curve = + let curve_to_index (curve : Ledgerwallet_tezos.curve) = + match curve with + | Ed25519 -> 0 + | Secp256k1 -> 1 + | Secp256r1 -> 2 + | Bip32_ed25519 -> 3 + in + Compare.Int.compare (curve_to_index a_curve) (curve_to_index b_curve) + + let compare {device_info = a_device_info; curve = a_curve; path = a_path} + {device_info = b_device_info; curve = b_curve; path = b_path} = + Compare.or_else + (List.compare Compare.Int32.compare a_path b_path) + (fun () -> + Compare.or_else (compare_curve a_curve b_curve) (fun () -> + compare_device_info a_device_info b_device_info)) + end) + end + + module Ledger_nonce_map = Map.Make (Ledger_key) + + (** Map ledger keys to their root deterministic nonces*) + let ledger_nonce_map = ref Ledger_nonce_map.empty + + let get_deterministic_nonce hid device_info curve path msg = + (* The Ledger is quite slow to generate deterministic nonces, so we + use the following hack: generate a single deterministic nonce, + cache it, and use it to generate a key for HMAC. Then use HMAC + to generate further determinstic nonces. *) let open Lwt_result_syntax in - let path = Bip32_path.tezos_root @ path in - let* nonce = - wrap_ledger_cmd (fun pp -> - Ledgerwallet_tezos.get_deterministic_nonce - ~pp - hid - curve - path - (Cstruct.of_bytes msg)) + let ledger_nonce_map_key = Ledger_key.{device_info; curve; path} in + let cached_ledger_nonce = + Ledger_nonce_map.find ledger_nonce_map_key !ledger_nonce_map in - return (Bigstring.of_bytes (Cstruct.to_bytes nonce)) + let* key = + match cached_ledger_nonce with + | Some cached -> return cached + | None -> + let path = Bip32_path.tezos_root @ path in + let* nonce = + wrap_ledger_cmd (fun pp -> + Ledgerwallet_tezos.get_deterministic_nonce + ~pp + hid + curve + path + (Cstruct.of_bytes + (Bytes.of_string + "Deterministic derivation of nonces for RANDAO"))) + in + + let nonce = Cstruct.to_bytes nonce in + ledger_nonce_map := + Ledger_nonce_map.add ledger_nonce_map_key nonce !ledger_nonce_map ; + return nonce + in + let bytes = Hacl.Hash.SHA256.HMAC.digest ~key ~msg in + return (Bigstring.of_bytes bytes) end (** Identification of a ledger's root key through crouching-tigers @@ -793,9 +918,14 @@ module Signer_implementation : Client_keys.SIGNER = struct let* {curve; path; _} = Ledger_uri.full_account ledger_uri in use_ledger_or_fail ~ledger_uri - (fun hidapi (_version, _git_commit) _device_info _ledger_id -> + (fun hidapi (_version, _git_commit) device_info _ledger_id -> let* bytes = - Ledger_commands.get_deterministic_nonce hidapi curve path msg + Ledger_commands.get_deterministic_nonce + hidapi + device_info + curve + path + msg in return_some (Bigstring.to_bytes bytes)) -- GitLab From 32c191ab634d2f427858e4615a4385a7fb8aaccf Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 10 May 2022 12:36:59 -0400 Subject: [PATCH 4/9] Proto, RPC: add an RPC for unrevealed nonces --- .../lib_protocol/alpha_context.mli | 6 +++ .../lib_protocol/alpha_services.ml | 48 ++++++++++++++++++- .../lib_protocol/alpha_services.mli | 13 +++++ src/proto_alpha/lib_protocol/level_repr.ml | 2 + src/proto_alpha/lib_protocol/level_repr.mli | 2 + src/proto_alpha/lib_protocol/nonce_storage.ml | 2 + .../lib_protocol/nonce_storage.mli | 2 + 7 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 69802ef467b0..5a4bea404e74 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -1126,11 +1126,15 @@ module Level : sig val levels_in_current_cycle : context -> ?offset:int32 -> unit -> level list + val levels_with_commitments_in_cycle : context -> Cycle.t -> level list + val last_allowed_fork_level : context -> Raw_level.t val dawn_of_a_new_cycle : context -> Cycle.t option val may_snapshot_rolls : context -> bool + + val to_raw : level -> Raw_level.t end module Fitness : sig @@ -1188,6 +1192,8 @@ module Nonce : sig val get : context -> Level.t -> status tzresult Lwt.t + val find : context -> Level.t -> status option tzresult Lwt.t + val of_bytes : bytes -> nonce tzresult val hash : nonce -> Nonce_hash.t diff --git a/src/proto_alpha/lib_protocol/alpha_services.ml b/src/proto_alpha/lib_protocol/alpha_services.ml index ad6eccf851a5..865444343c44 100644 --- a/src/proto_alpha/lib_protocol/alpha_services.ml +++ b/src/proto_alpha/lib_protocol/alpha_services.ml @@ -77,6 +77,20 @@ module Nonce = struct (fun () -> Forgotten); ] + type unrevealed_nonce = { + level : Raw_level.t; + delegate : Signature.Public_key_hash.t; + } + + let unrevealed_nonce_encoding = + let open Data_encoding in + conv + (fun {level; delegate} -> (level, delegate)) + (fun (level, delegate) -> {level; delegate}) + (obj2 + (req "level" Raw_level.encoding) + (req "delegate" Signature.Public_key_hash.encoding)) + module S = struct let get = RPC_service.get_service @@ -84,6 +98,14 @@ module Nonce = struct ~query:RPC_query.empty ~output:info_encoding RPC_path.(custom_root / "context" / "nonces" /: Raw_level.rpc_arg) + + let get_unrevealed_nonces = + RPC_service.get_service + ~description:"Levels with unrevealed nonces in this level's cycle." + ~query:RPC_query.empty + ~output:(Data_encoding.list unrevealed_nonce_encoding) + RPC_path.( + custom_root / "context" / "unrevealed_nonces" /: Raw_level.rpc_arg) end let register () = @@ -93,9 +115,33 @@ module Nonce = struct Nonce.get ctxt level >|= function | Ok (Revealed nonce) -> ok (Revealed nonce) | Ok (Unrevealed {nonce_hash; _}) -> ok (Missing nonce_hash) - | Error _ -> ok Forgotten) + | Error _ -> ok Forgotten) ; + register1 + ~chunked:false + S.get_unrevealed_nonces + (fun ctxt raw_level () () -> + let level = Level.from_raw ctxt raw_level in + let cycle = level.cycle in + match Cycle.pred cycle with + | Some cycle -> + let levels = Level.levels_with_commitments_in_cycle ctxt cycle in + List.fold_left_es + (fun acc level -> + Nonce.find ctxt level >>=? function + | Some (Unrevealed {delegate; _}) -> + let level = Level.to_raw level in + let unrevealed : unrevealed_nonce = {level; delegate} in + return (unrevealed :: acc) + | _ -> return acc) + [] + levels + >|=? List.rev + | None -> return []) let get ctxt block level = RPC_context.make_call1 S.get ctxt block level () () + + let get_unrevealed_nonces ctxt block level = + RPC_context.make_call1 S.get_unrevealed_nonces ctxt block level () () end type error += No_available_snapshots of {min_cycle : int32} diff --git a/src/proto_alpha/lib_protocol/alpha_services.mli b/src/proto_alpha/lib_protocol/alpha_services.mli index 5bfc4054c68b..42be7b919b7e 100644 --- a/src/proto_alpha/lib_protocol/alpha_services.mli +++ b/src/proto_alpha/lib_protocol/alpha_services.mli @@ -44,6 +44,19 @@ module Nonce : sig val get : 'a #RPC_context.simple -> 'a -> Raw_level.t -> info shell_tzresult Lwt.t + + type unrevealed_nonce = { + level : Raw_level.t; + delegate : Signature.Public_key_hash.t; + } + + (** Get all levels in this level's cycle which have an unrevealed + nonce. *) + val get_unrevealed_nonces : + 'a #RPC_context.simple -> + 'a -> + Raw_level.t -> + unrevealed_nonce list shell_tzresult Lwt.t end module Snapshot_index : sig diff --git a/src/proto_alpha/lib_protocol/level_repr.ml b/src/proto_alpha/lib_protocol/level_repr.ml index 0b5926f387f5..0d4812db32a4 100644 --- a/src/proto_alpha/lib_protocol/level_repr.ml +++ b/src/proto_alpha/lib_protocol/level_repr.ml @@ -335,6 +335,8 @@ let last_of_cycle ~cycle_eras level = let era = era_of_level ~cycle_eras level.level in Compare.Int32.(Int32.succ level.cycle_position = era.blocks_per_cycle) +let to_raw level = level.level + module Internal_for_tests = struct let add_level level n = let raw_level = level.level in diff --git a/src/proto_alpha/lib_protocol/level_repr.mli b/src/proto_alpha/lib_protocol/level_repr.mli index b595ce324e69..b8c0ab4783ad 100644 --- a/src/proto_alpha/lib_protocol/level_repr.mli +++ b/src/proto_alpha/lib_protocol/level_repr.mli @@ -109,6 +109,8 @@ val first_level_in_cycle_from_eras : (** Returns true if the given level is the last of a cycle. *) val last_of_cycle : cycle_eras:cycle_eras -> level -> bool +val to_raw : level -> Raw_level_repr.t + module Internal_for_tests : sig val add_level : t -> int -> t end diff --git a/src/proto_alpha/lib_protocol/nonce_storage.ml b/src/proto_alpha/lib_protocol/nonce_storage.ml index 60e8d8f0dff6..6d621a8e6c87 100644 --- a/src/proto_alpha/lib_protocol/nonce_storage.ml +++ b/src/proto_alpha/lib_protocol/nonce_storage.ml @@ -118,6 +118,8 @@ type status = Storage.Seed.nonce_status = let get = Storage.Seed.Nonce.get +let find = Storage.Seed.Nonce.find + type nonce_presence = No_nonce_expected | Nonce_expected of status let check ctxt level = diff --git a/src/proto_alpha/lib_protocol/nonce_storage.mli b/src/proto_alpha/lib_protocol/nonce_storage.mli index e85ce95de2e0..d10ce95dadca 100644 --- a/src/proto_alpha/lib_protocol/nonce_storage.mli +++ b/src/proto_alpha/lib_protocol/nonce_storage.mli @@ -45,6 +45,8 @@ type status = Unrevealed of unrevealed | Revealed of Seed_repr.nonce val get : Raw_context.t -> Level_repr.t -> status tzresult Lwt.t +val find : Raw_context.t -> Level_repr.t -> status option tzresult Lwt.t + type nonce_presence = No_nonce_expected | Nonce_expected of status val check : Raw_context.t -> Level_repr.t -> nonce_presence tzresult Lwt.t -- GitLab From d63ac356a34fe87125ee12a1d2ffab93ed69497f Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 10 May 2022 15:28:13 -0400 Subject: [PATCH 5/9] Baker: when nonce mode is deterministic, generate revealed nonces --- src/proto_alpha/lib_delegate/baking_nonces.ml | 119 ++++++++++++------ .../lib_delegate/baking_nonces.mli | 7 +- .../lib_delegate/baking_scheduling.ml | 1 + 3 files changed, 85 insertions(+), 42 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_nonces.ml b/src/proto_alpha/lib_delegate/baking_nonces.ml index a0a96b669bc5..c5ff8625dec6 100644 --- a/src/proto_alpha/lib_delegate/baking_nonces.ml +++ b/src/proto_alpha/lib_delegate/baking_nonces.ml @@ -33,6 +33,7 @@ type state = { constants : Constants.t; config : Baking_configuration.nonce_config; nonces_location : [`Nonce] Baking_files.location; + delegates : Baking_state.delegate list; mutable last_predecessor : Block_hash.t; } @@ -198,14 +199,16 @@ let get_unrevealed_nonces ({cctxt; chain; _} as state) nonces = (* Nonce creation *) +let deterministic_nonce (delegate : Baking_state.delegate) level = + let data = Data_encoding.Binary.to_bytes_exn Raw_level.encoding level in + Client_keys.deterministic_nonce delegate.secret_key_uri data >>=? fun nonce -> + return @@ Data_encoding.Binary.of_bytes_exn Nonce.encoding nonce + let generate_seed_nonce (nonce_config : Baking_configuration.nonce_config) (delegate : Baking_state.delegate) level = (match nonce_config with | Deterministic start_level when start_level <= Raw_level.to_int32 level -> - let data = Data_encoding.Binary.to_bytes_exn Raw_level.encoding level in - Client_keys.deterministic_nonce delegate.secret_key_uri data - >>=? fun nonce -> - return (Data_encoding.Binary.of_bytes_exn Nonce.encoding nonce) + deterministic_nonce delegate level | Deterministic _ | Random -> ( match Nonce.of_bytes (Rand.generate Constants.nonce_length) with | Error _errs -> assert false @@ -247,9 +250,66 @@ let inject_seed_nonce_revelation (cctxt : #Protocol_client_context.full) ~chain >>= fun () -> return_unit) nonces -(** [reveal_potential_nonces] reveal registered nonces *) -let reveal_potential_nonces - ({cctxt; chain; nonces_location; last_predecessor; _} as state) new_proposal +let reveal_potential_nonces_random ({cctxt; chain; nonces_location; _} as state) + block branch = + load cctxt nonces_location >>= function + | Error err -> Events.(emit cannot_read_nonces err) >>= fun () -> return_unit + | Ok nonces -> ( + get_unrevealed_nonces state nonces >>= function + | Error err -> + Events.(emit cannot_retrieve_unrevealed_nonces err) >>= fun () -> + return_unit + | Ok nonces_to_reveal -> ( + inject_seed_nonce_revelation + cctxt + ~chain + ~block + ~branch + nonces_to_reveal + >>= function + | Error err -> + Events.(emit cannot_inject_nonces err) >>= fun () -> return_unit + | Ok () -> + (* If some nonces are to be revealed it means: + - We entered a new cycle and we can clear old nonces ; + - A revelation was not included yet in the cycle beginning. + So, it is safe to only filter outdated_nonces there *) + filter_outdated_nonces state nonces >>=? fun live_nonces -> + save cctxt nonces_location live_nonces >>=? fun () -> return_unit) + ) + +let reveal_potential_nonces_deterministic {cctxt; chain; delegates; _} block + branch level = + Lwt.return (Environment.wrap_tzresult (Raw_level.of_int32 level)) + >>=? fun level -> + Alpha_services.Nonce.get_unrevealed_nonces cctxt (chain, `Head 0) level + >>=? fun unrevealed -> + List.filter_map_es + (fun ({level; delegate} : Alpha_services.Nonce.unrevealed_nonce) -> + let delegate = + List.find + (fun (putative : Baking_state.delegate) -> + delegate = putative.public_key_hash) + delegates + in + Option.map_es + (fun delegate -> + deterministic_nonce delegate level >|=? fun nonce -> (level, nonce)) + delegate) + unrevealed + >>=? fun nonces_to_reveal -> + match nonces_to_reveal with + | [] -> return_unit + | nonces_to_reveal -> ( + inject_seed_nonce_revelation cctxt ~chain ~block ~branch nonces_to_reveal + >>= function + | Error err -> + Events.(emit cannot_inject_nonces err) >>= fun () -> return_unit + | Ok () -> return_unit) + +(** [reveal_potential_nonces_random] reveal registered nonces *) +let reveal_potential_nonces (nonce_config : Baking_configuration.nonce_config) + ({cctxt; last_predecessor; _} as state) new_proposal (head_level : Level.t) = let new_predecessor_hash = new_proposal.Baking_state.predecessor.hash in if @@ -262,37 +322,20 @@ let reveal_potential_nonces let branch = new_predecessor_hash in (* improve concurrency *) cctxt#with_lock @@ fun () -> - load cctxt nonces_location >>= function - | Error err -> - Events.(emit cannot_read_nonces err) >>= fun () -> return_unit - | Ok nonces -> ( - get_unrevealed_nonces state nonces >>= function - | Error err -> - Events.(emit cannot_retrieve_unrevealed_nonces err) >>= fun () -> - return_unit - | Ok [] -> return_unit - | Ok nonces_to_reveal -> ( - inject_seed_nonce_revelation - cctxt - ~chain - ~block - ~branch - nonces_to_reveal - >>= function - | Error err -> - Events.(emit cannot_inject_nonces err) >>= fun () -> return_unit - | Ok () -> - (* If some nonces are to be revealed it means: - - We entered a new cycle and we can clear old nonces ; - - A revelation was not included yet in the cycle beginning. - So, it is safe to only filter outdated_nonces there *) - filter_outdated_nonces state nonces >>=? fun live_nonces -> - save cctxt nonces_location live_nonces >>=? fun () -> - return_unit))) + let head_raw_level = Raw_level.to_int32 head_level.level in + let beginning_of_this_cycle = + Int32.sub head_raw_level head_level.cycle_position + in + match nonce_config with + | Deterministic start_level when start_level <= beginning_of_this_cycle -> + reveal_potential_nonces_deterministic state block branch head_raw_level + | Deterministic _ | Random -> + reveal_potential_nonces_random state block branch) else return_unit (* We suppose that the block stream is cloned by the caller *) -let start_revelation_worker cctxt config chain_id constants block_stream = +let start_revelation_worker cctxt config chain_id constants block_stream + delegates = let nonces_location = Baking_files.resolve_location ~chain_id `Nonce in may_migrate cctxt nonces_location >>= fun () -> let chain = `Hash chain_id in @@ -306,6 +349,7 @@ let start_revelation_worker cctxt config chain_id constants block_stream = config; nonces_location; last_predecessor = Block_hash.zero; + delegates; } in let rec worker_loop () = @@ -320,8 +364,9 @@ let start_revelation_worker cctxt config chain_id constants block_stream = | Some new_proposal -> if !should_shutdown then return_unit else - reveal_potential_nonces state new_proposal >>=? fun () -> - worker_loop () + Plugin.RPC.current_level cctxt (chain, `Head 0) >>=? fun head_level -> + reveal_potential_nonces config state new_proposal head_level + >>=? fun () -> worker_loop () in Lwt.dont_wait (fun () -> diff --git a/src/proto_alpha/lib_delegate/baking_nonces.mli b/src/proto_alpha/lib_delegate/baking_nonces.mli index baa6ebf23f18..7c3b03971533 100644 --- a/src/proto_alpha/lib_delegate/baking_nonces.mli +++ b/src/proto_alpha/lib_delegate/baking_nonces.mli @@ -32,6 +32,7 @@ type state = { constants : Constants.t; config : Baking_configuration.nonce_config; nonces_location : [`Nonce] Baking_files.location; + delegates : Baking_state.delegate list; mutable last_predecessor : Block_hash.t; } @@ -79,9 +80,6 @@ val blocks_from_current_cycle : unit -> Block_hash.t list tzresult Lwt.t -val get_unrevealed_nonces : - t -> Nonce.t Block_hash.Map.t -> (Raw_level.t * Nonce.t) list tzresult Lwt.t - val generate_seed_nonce : Baking_configuration.nonce_config -> Baking_state.delegate -> @@ -103,12 +101,11 @@ val inject_seed_nonce_revelation : (Raw_level.t * Nonce.t) list -> unit tzresult Lwt.t -val reveal_potential_nonces : t -> Baking_state.proposal -> unit tzresult Lwt.t - val start_revelation_worker : Protocol_client_context.full -> Baking_configuration.nonce_config -> Chain_id.t -> Constants.t -> Baking_state.proposal Lwt_stream.t -> + Baking_state.delegate list -> Lwt_canceler.t Lwt.t diff --git a/src/proto_alpha/lib_delegate/baking_scheduling.ml b/src/proto_alpha/lib_delegate/baking_scheduling.ml index 488d16c8457f..b77a54b03a88 100644 --- a/src/proto_alpha/lib_delegate/baking_scheduling.ml +++ b/src/proto_alpha/lib_delegate/baking_scheduling.ml @@ -770,6 +770,7 @@ let run cctxt ?canceler ?(stop_on_event = fun _ -> false) initial_state.global_state.chain_id initial_state.global_state.constants cloned_block_stream + delegates >>= fun revelation_worker_canceler -> Option.iter (fun canceler -> -- GitLab From a885fa10d5b6712e1ba14c21ff487aa9f7cb60db Mon Sep 17 00:00:00 2001 From: David Turner Date: Wed, 11 May 2022 09:56:20 -0400 Subject: [PATCH 6/9] Test deterministic nonce generation for all tz1, tz2, and tz3 --- .../lib_client/client_proto_utils.ml | 5 ++++ .../lib_client/client_proto_utils.mli | 6 +++++ tezt/lib_tezos/client.ml | 21 +++++++++++++--- tezt/lib_tezos/client.mli | 5 +++- tezt/tests/client_keys.ml | 25 ++++++++++++++++++- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/proto_alpha/lib_client/client_proto_utils.ml b/src/proto_alpha/lib_client/client_proto_utils.ml index be6844cc5cf7..2340dce9b564 100644 --- a/src/proto_alpha/lib_client/client_proto_utils.ml +++ b/src/proto_alpha/lib_client/client_proto_utils.ml @@ -53,3 +53,8 @@ let check_message (cctxt : #full) ~block ~key_locator ~quiet ~message ~signature key_locator signature bytes + +let gen_nonce (cctxt : #full) ~src_sk ~seed = + cctxt#message "nonce: @[%s@]" seed >>= fun () -> + let seed_bytes = Hex.to_bytes_exn (Hex.of_string seed) in + Client_keys.deterministic_nonce src_sk seed_bytes diff --git a/src/proto_alpha/lib_client/client_proto_utils.mli b/src/proto_alpha/lib_client/client_proto_utils.mli index c535e4b24ecb..b2f8835d5b92 100644 --- a/src/proto_alpha/lib_client/client_proto_utils.mli +++ b/src/proto_alpha/lib_client/client_proto_utils.mli @@ -38,3 +38,9 @@ val check_message : message:string -> signature:Signature.t -> bool tzresult Lwt.t + +val gen_nonce : + #Protocol_client_context.full -> + src_sk:Client_keys.sk_uri -> + seed:string -> + bytes tzresult Lwt.t diff --git a/tezt/lib_tezos/client.ml b/tezt/lib_tezos/client.ml index d8485eb2f4bd..cddeb91f34da 100644 --- a/tezt/lib_tezos/client.ml +++ b/tezt/lib_tezos/client.ml @@ -607,7 +607,10 @@ let propose_for ?endpoint ?(minimal_timestamp = true) ?protocol ?key ?force let id = ref 0 -let spawn_gen_keys ?alias client = +let spawn_gen_keys ?alias ?signature_algorithm client = + let signature_algorithm_arg = + match signature_algorithm with None -> [] | Some arg -> ["--sig"; arg] + in let alias = match alias with | None -> @@ -615,10 +618,11 @@ let spawn_gen_keys ?alias client = sf "tezt_%d" !id | Some alias -> alias in - (spawn_command client ["gen"; "keys"; alias], alias) + ( spawn_command client (["gen"; "keys"; alias] @ signature_algorithm_arg), + alias ) -let gen_keys ?alias client = - let p, alias = spawn_gen_keys ?alias client in +let gen_keys ?alias ?signature_algorithm client = + let p, alias = spawn_gen_keys ?alias ?signature_algorithm client in let* () = Process.check p in return alias @@ -702,6 +706,15 @@ let spawn_bls_import_secret_key ?hooks ?(force = false) let bls_import_secret_key ?hooks ?force key sc_client = spawn_bls_import_secret_key ?hooks ?force key sc_client |> Process.check +let spawn_gen_nonce ?hooks client alias seed = + spawn_command ?hooks client ["generate"; "nonce"; "for"; alias; "from"; seed] + +let gen_nonce ?hooks sc_client ~alias seed = + let* out = + spawn_gen_nonce ?hooks sc_client alias seed |> Process.check_and_read_stdout + in + return (String.trim out) + let spawn_transfer ?hooks ?log_output ?endpoint ?(wait = "none") ?burn_cap ?fee ?gas_limit ?storage_limit ?counter ?arg ?(simulation = false) ?(force = false) ~amount ~giver ~receiver client = diff --git a/tezt/lib_tezos/client.mli b/tezt/lib_tezos/client.mli index 8a6a32dad161..aa6e3e0b75c6 100644 --- a/tezt/lib_tezos/client.mli +++ b/tezt/lib_tezos/client.mli @@ -489,7 +489,7 @@ val spawn_show_address : alias:string -> t -> Process.t (** Run [tezos-client gen keys] and return the key alias. The default value for [alias] is a fresh alias of the form [tezt_]. *) -val gen_keys : ?alias:string -> t -> string Lwt.t +val gen_keys : ?alias:string -> ?signature_algorithm:string -> t -> string Lwt.t (** A helper to run [tezos-client gen keys] followed by [tezos-client show address] to get the generated key. *) @@ -541,6 +541,9 @@ val bls_import_secret_key : t -> unit Lwt.t +val gen_nonce : + ?hooks:Process.hooks -> t -> alias:string -> string -> string Lwt.t + (** Run [tezos-client transfer amount from giver to receiver]. *) val transfer : ?hooks:Process.hooks -> diff --git a/tezt/tests/client_keys.ml b/tezt/tests/client_keys.ml index 97fb3a5cb627..b4f926ea9d93 100644 --- a/tezt/tests/client_keys.ml +++ b/tezt/tests/client_keys.ml @@ -95,6 +95,28 @@ let test_bls_gen_keys () = let* _account = Client.bls_show_address ~alias client in return ()) +let test_deterministic_nonce signature_algorithm = + let* client = Client.init () in + let alias = "key_for" ^ signature_algorithm in + let* alias = Client.gen_keys ~alias ~signature_algorithm client in + let* maybe_nonce1a = Client.gen_nonce client ~alias "deadbeef" in + let* maybe_nonce1b = Client.gen_nonce client ~alias "deadbeef" in + let* maybe_nonce2 = Client.gen_nonce client ~alias "c0ffee" in + assert (maybe_nonce1a = maybe_nonce1b) ; + assert (maybe_nonce1a != maybe_nonce2) ; + return () + +let test_deterministic_nonce () = + Test.register + ~__FILE__ + ~tags:["client"; "keys"] + ~title:"Generate deterministic nonces" + (fun () -> + let* () = test_deterministic_nonce "ed25519" in + let* () = test_deterministic_nonce "secp256k1" in + let* () = test_deterministic_nonce "p256" in + return ()) + let test_bls_list_keys () = Test.register ~__FILE__ @@ -129,4 +151,5 @@ let register_protocol_independent () = test_bls_import_secret_key () ; test_bls_show_address () ; test_bls_gen_keys () ; - test_bls_list_keys () + test_bls_list_keys () ; + test_deterministic_nonce () -- GitLab From 401664f463b8dc3da056cfdb488612be8b799333 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 16 May 2022 13:03:06 -0400 Subject: [PATCH 7/9] Signer: support deterministic nonces --- src/bin_signer/http_daemon.ml | 17 +++++++++++++++++ src/lib_signer_services/signer_services.ml | 5 +++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/bin_signer/http_daemon.ml b/src/bin_signer/http_daemon.ml index 48f3801b9934..c93098cb946c 100644 --- a/src/bin_signer/http_daemon.ml +++ b/src/bin_signer/http_daemon.ml @@ -42,6 +42,23 @@ let run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes RPC_directory.register1 dir Signer_services.public_key (fun pkh () () -> Handler.public_key cctxt pkh) in + let dir = + RPC_directory.register1 + dir + Signer_services.deterministic_nonce + (fun pkh signature data -> + Handler.deterministic_nonce ~require_auth cctxt {pkh; data; signature}) + in + let dir = + RPC_directory.register1 + dir + Signer_services.deterministic_nonce_hash + (fun pkh signature data -> + Handler.deterministic_nonce_hash + ~require_auth + cctxt + {pkh; data; signature}) + in let dir = RPC_directory.register0 dir Signer_services.authorized_keys (fun () () -> if require_auth then diff --git a/src/lib_signer_services/signer_services.ml b/src/lib_signer_services/signer_services.ml index 215cddc12876..4c95ab6b5abc 100644 --- a/src/lib_signer_services/signer_services.ml +++ b/src/lib_signer_services/signer_services.ml @@ -52,7 +52,7 @@ let deterministic_nonce = ~query ~input:Data_encoding.bytes ~output:Data_encoding.(obj1 (req "deterministic_nonce" bytes)) - RPC_path.(root / "keys" /: Signature.Public_key_hash.rpc_arg) + RPC_path.(root / "deterministic_nonce" /: Signature.Public_key_hash.rpc_arg) let deterministic_nonce_hash = RPC_service.post_service @@ -62,7 +62,8 @@ let deterministic_nonce_hash = ~query ~input:Data_encoding.bytes ~output:Data_encoding.(obj1 (req "deterministic_nonce_hash" bytes)) - RPC_path.(root / "keys" /: Signature.Public_key_hash.rpc_arg) + RPC_path.( + root / "deterministic_nonce_hash" /: Signature.Public_key_hash.rpc_arg) let supports_deterministic_nonces = RPC_service.get_service -- GitLab From 5107df17b9d6f69b0517e63517868171d083f680 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 16 May 2022 13:18:40 -0400 Subject: [PATCH 8/9] Baker: Do not write nonces in deterministic mode --- src/proto_alpha/lib_delegate/baking_actions.ml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/proto_alpha/lib_delegate/baking_actions.ml b/src/proto_alpha/lib_delegate/baking_actions.ml index 81dfefc9f74b..e3fa1f1713ed 100644 --- a/src/proto_alpha/lib_delegate/baking_actions.ml +++ b/src/proto_alpha/lib_delegate/baking_actions.ml @@ -294,11 +294,15 @@ let inject_block ~state_recorder state block_to_bake ~updated_state = >>=? fun {unsigned_block_header; operations} -> sign_block_header state delegate unsigned_block_header >>=? fun signed_block_header -> - (match seed_nonce_opt with - | None -> + (match (seed_nonce_opt, state.global_state.config.nonce) with + | None, _ -> (* Nothing to do *) return_unit - | Some (_, nonce) -> + | _, Deterministic start_level + when start_level <= Raw_level.to_int32 @@ Level.to_raw injection_level -> + (* No need to write deterministic nonces *) + return_unit + | Some (_, nonce), _ -> let block_hash = Block_header.hash signed_block_header in Baking_nonces.register_nonce cctxt ~chain_id block_hash nonce) >>=? fun () -> -- GitLab From 3d9fb6c815d6b5155fa28766f6a686a83ce87acd Mon Sep 17 00:00:00 2001 From: David Turner Date: Wed, 18 May 2022 15:58:13 -0400 Subject: [PATCH 9/9] Baker: die early if signer does not support deterministic nonces --- src/proto_alpha/lib_delegate/baking_commands.ml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/proto_alpha/lib_delegate/baking_commands.ml b/src/proto_alpha/lib_delegate/baking_commands.ml index 940a3aaf90db..f08b2b1153f2 100644 --- a/src/proto_alpha/lib_delegate/baking_commands.ml +++ b/src/proto_alpha/lib_delegate/baking_commands.ml @@ -332,6 +332,21 @@ let per_block_vote_file_arg = ~placeholder:"filename" (Clic.parameter (fun _ s -> return s)) +(** [test_nonce nonce delegates] If nonce is Deterministic, check that all + signers for all delegates support determinsitic nonces. Else, just + return. *) +let test_nonce (nonce : Baking_configuration.nonce_config option) + (delegates : Baking_state.delegate list) = + match nonce with + | None | Some Random -> return_unit + | Some (Deterministic _) -> + let data = Bytes.make 1 'c' in + List.iter_es + (fun (delegate : Baking_state.delegate) -> + Client_keys.deterministic_nonce delegate.secret_key_uri data + >|=? fun _ -> ()) + delegates + let baker_commands () : Protocol_client_context.full Clic.command list = let open Clic in let group = @@ -382,6 +397,7 @@ let baker_commands () : Protocol_client_context.full Clic.command list = may_lock_pidfile pidfile @@ fun () -> get_delegates cctxt sources >>=? fun delegates -> let context_path = Filename.Infix.(node_data_path // "context") in + test_nonce nonce delegates >>=? fun () -> Client_daemon.Baker.run cctxt ~minimal_fees -- GitLab