diff --git a/src/bin_signer/http_daemon.ml b/src/bin_signer/http_daemon.ml index 48f3801b993482b9e1e51a4412c743aa74212541..c93098cb946c8db4bcc9833f575e2abd84a4da38 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_backends/unix/ledger.available.ml b/src/lib_signer_backends/unix/ledger.available.ml index 2bf526f9229b18ba0491bebaa9854b5e373aa4f0..8a798c045a9f67061870e12aea02189054f82006 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)) diff --git a/src/lib_signer_services/signer_services.ml b/src/lib_signer_services/signer_services.ml index 215cddc128767e320131ef382879ddfca935ba72..4c95ab6b5abc1b26b26cf1991d778ade19e6fff3 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 diff --git a/src/proto_alpha/lib_client/client_proto_utils.ml b/src/proto_alpha/lib_client/client_proto_utils.ml index be6844cc5cf7a8fbbe36d7c56415db8784587f10..2340dce9b5643b1c84d6ba2199b25f98613c220e 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 c535e4b24ecb9e4eb1eb02cb16b454b9f6647112..b2f8835d5b927e192b8534fc131722902699c9af 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/src/proto_alpha/lib_delegate/baking_actions.ml b/src/proto_alpha/lib_delegate/baking_actions.ml index 81dfefc9f74b02bff03fdde1ef009eb296c2e954..e3fa1f1713edca6aeef1e7b1e8d8787bea79344c 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 () -> diff --git a/src/proto_alpha/lib_delegate/baking_commands.ml b/src/proto_alpha/lib_delegate/baking_commands.ml index 78d6ec9e61a1bfcea816b01223c67e5e329f29b6..f08b2b1153f21ca56a9579de9bd7d3316ba9ce1b 100644 --- a/src/proto_alpha/lib_delegate/baking_commands.ml +++ b/src/proto_alpha/lib_delegate/baking_commands.ml @@ -163,6 +163,29 @@ 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"; "1"]) + (fun _ctxt -> function + | "random" -> return Baking_configuration.Random + | 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: 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 + 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) = @@ -309,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 = @@ -321,7 +359,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 +367,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 +382,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 -> @@ -357,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 @@ -365,6 +406,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/baking_configuration.ml b/src/proto_alpha/lib_delegate/baking_configuration.ml index d7b24e86ddc72ca562d14b86a2fe74a515742dca..fc1d53aa049eca6a968ffceb41d3db10fdc5e21a 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 bb90f6f5cf4a9cc180d12585699632d11da8db28..12bd1029c8d9a61a68f9040e494ace3debff0f71 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 170bedfdb697121c9f51bc91a3c7e25a173cccce..c5ff8625dec6beea0d376121d81e7393f6bcaa5d 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,15 +199,17 @@ 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 -> - 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 start_level when start_level <= Raw_level.to_int32 level -> + deterministic_nonce delegate level + | Deterministic _ | Random -> ( match Nonce.of_bytes (Rand.generate Constants.nonce_length) with | Error _errs -> assert false | Ok nonce -> return nonce)) @@ -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 baa6ebf23f188e5ac2e7d04f132af1628a85b626..7c3b039715332aeebc40f19fc971d829d569ba56 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 488d16c8457f253e9fa80d4a8267539562b4df80..b77a54b03a8808b8f010a17f9301969027954aaf 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 -> diff --git a/src/proto_alpha/lib_delegate/client_daemon.ml b/src/proto_alpha/lib_delegate/client_daemon.ml index cb5d92e4b6bc8f7b9922039a3fdcd4e5884320e1..c816dfc3e05bedd5a9b68b76856a31223c5ec507 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 0e6f799861058fffabcd8e5f5cdca36a0ad0e9e9..13bdaff17fcedeae38b84c3d00c557d487d5f039 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 -> diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 69802ef467b0dfdbe60845cd6abd0e1363fadc4e..5a4bea404e74d21d6203bab68d55234c45fb5754 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 ad6eccf851a5488604fdb8cdb2abf85975acc173..865444343c446df1d5022dff1c187ca66279eab1 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 5bfc4054c68bb5259cbbae65200a5d0537f44e6c..42be7b919b7e751bfcfee7e4064660ae78c0b722 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 0b5926f387f5739ae86379290e958bc48abc9c9f..0d4812db32a4078460ec553e67a7af3682e95cb8 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 b595ce324e69e1bb1cf7ab2c982e49a92cc6c79c..b8c0ab4783ad920ea9f661411d44e38ba1ca2842 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 60e8d8f0dff655027c7d8a9b659fdf16e35ebf1c..6d621a8e6c87812ed34d07c9434d941f7aa36d16 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 e85ce95de2e0c82a89af190c02c9f389863b8a4e..d10ce95dadca09d6298c9d3eee8daa36875c179a 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 diff --git a/tezt/lib_tezos/client.ml b/tezt/lib_tezos/client.ml index d8485eb2f4bdd50bee708aff05435477b6b86bc1..cddeb91f34da81b42dfd557711d3c17ba91296fa 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 8a6a32dad16161a481f093255b639cc7dc75eff0..aa6e3e0b75c65a536bbd30cadd3e5935fca443e0 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 97fb3a5cb627d6396369ed9e191ad7fc3cedff0c..b4f926ea9d93f7cd947acc51969dcb1d318cca13 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 ()