Revision 5fa6faa707c4eedc955a4f0562195703224a63fa authored by Thomas Letan on 14 August 2023, 09:49:56 UTC, committed by Thomas Letan on 14 August 2023, 09:55:15 UTC
Tztop was added at a time where the protocol couldn’t be loaded in utop.
This is no longer the case, and as a consequence, we can safely retire
tztop.
1 parent 5745a2e
Raw File
signature_manager.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2022-2023 Trili Tech  <contact@trili.tech>                  *)
(* Copyright (c) 2023 Marigold  <contact@marigold.dev>                       *)
(*                                                                           *)
(* Permission is hereby granted, free of charge, to any person obtaining a   *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense,  *)
(* and/or sell copies of the Software, and to permit persons to whom the     *)
(* Software is furnished to do so, subject to the following conditions:      *)
(*                                                                           *)
(* The above copyright notice and this permission notice shall be included   *)
(* in all copies or substantial portions of the Software.                    *)
(*                                                                           *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL   *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING   *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER       *)
(* DEALINGS IN THE SOFTWARE.                                                 *)
(*                                                                           *)
(*****************************************************************************)

module Aggregate_signature = Tezos_crypto.Aggregate_signature

type error +=
  | Cannot_convert_root_page_hash_to_bytes of string
  | Cannot_compute_aggregate_signature of string
  | Public_key_for_witness_not_available of int * string
  | Public_key_is_non_committee_member of Aggregate_signature.public_key_hash
  | Signature_verification_failed of
      (Aggregate_signature.public_key * Aggregate_signature.t * string)
  | Public_key_for_committee_member_not_available of
      Aggregate_signature.public_key_hash

let () =
  register_error_kind
    `Permanent
    ~id:"cannot_extract_root_page_hash_to_sign"
    ~title:"Cannot convert root hash page to byte sequence"
    ~description:"Cannot convert root hash page to byte sequence"
    ~pp:(fun ppf b58_hash ->
      Format.fprintf
        ppf
        "Cannot convert root hash page to byte sequence: %s"
        b58_hash)
    Data_encoding.(obj1 (req "hash" (string' Plain)))
    (function
      | Cannot_convert_root_page_hash_to_bytes b58_hash -> Some b58_hash
      | _ -> None)
    (fun b58_hash -> Cannot_convert_root_page_hash_to_bytes b58_hash) ;
  register_error_kind
    `Permanent
    ~id:"cannot_compute_root_hash_aggregate_signature"
    ~title:"Cannot compute aggregate signature of root page hash"
    ~description:"Cannot compute aggregate signature of root page hash"
    ~pp:(fun ppf b58_hash ->
      Format.fprintf
        ppf
        "Cannot compute aggregate signature of root page hash: %s"
        b58_hash)
    Data_encoding.(obj1 (req "hash" (string' Plain)))
    (function
      | Cannot_compute_aggregate_signature b58_hash -> Some b58_hash | _ -> None)
    (fun b58_hash -> Cannot_compute_aggregate_signature b58_hash) ;
  register_error_kind
    `Permanent
    ~id:"public_key_of_witness_not_available"
    ~title:
      "Public key of witness committee member not available for verifying \
       signature"
    ~description:
      "Public key of witness committee member not available for verifying \
       signature"
    ~pp:(fun ppf (witness_index, b58_hash) ->
      Format.fprintf
        ppf
        "Public key of committee member %d not available for verifying \
         signature of root page hash %s"
        witness_index
        b58_hash)
    Data_encoding.(
      obj2 (req "witness_index" int31) (req "hash" (string' Plain)))
    (function
      | Public_key_for_witness_not_available (index, hash) -> Some (index, hash)
      | _ -> None)
    (fun (index, hash) -> Public_key_for_witness_not_available (index, hash)) ;
  register_error_kind
    `Permanent
    ~id:"Public_key_is_non_committee_member"
    ~title:"Public key hash is not a committee member"
    ~description:
      "Public key hash is not associated with any committee member registered \
       with this Dac coordinator."
    ~pp:(fun ppf committee_member_pkh ->
      Format.fprintf
        ppf
        "Expected public key to be an active committee member but was not: %s"
        (Tezos_crypto.Aggregate_signature.Public_key_hash.to_short_b58check
           committee_member_pkh))
    Data_encoding.(
      obj1
        (req
           "public_key_hash"
           Tezos_crypto.Aggregate_signature.Public_key_hash.encoding))
    (function
      | Public_key_is_non_committee_member committee_member_pkh ->
          Some committee_member_pkh
      | _ -> None)
    (fun committee_member_pkh ->
      Public_key_is_non_committee_member committee_member_pkh) ;
  register_error_kind
    `Permanent
    ~id:"signature_verification_failed"
    ~title:"Signature verification failed"
    ~description:"Signature verification failed."
    ~pp:(fun ppf (pk, signature, root_hash) ->
      let pk = Aggregate_signature.Public_key.to_short_b58check pk in
      let signature = Aggregate_signature.to_short_b58check signature in
      Format.fprintf
        ppf
        "Could not verify signature \"%s\" given public key \"%s\" and \
         root_hash \"%s\": Signature verification failed."
        pk
        signature
        root_hash)
    Data_encoding.(
      obj3
        (req "public_key" Aggregate_signature.Public_key.encoding)
        (req "signature" Aggregate_signature.encoding)
        (req "root_hash" (string' Plain)))
    (function
      | Signature_verification_failed (pk, signature, root_hash) ->
          Some (pk, signature, root_hash)
      | _ -> None)
    (function
      | pk, signature, root_hash ->
          Signature_verification_failed (pk, signature, root_hash)) ;
  register_error_kind
    `Permanent
    ~id:"public_key_of_committee_member_not_available"
    ~title:"Public key of committee member not available."
    ~description:"Public key of committee member not available."
    ~pp:(fun ppf b58_hash ->
      Format.fprintf
        ppf
        "Public key of committee member %s not available."
        b58_hash)
    Data_encoding.(obj1 (req "hash" (string' Plain)))
    (function
      | Public_key_for_committee_member_not_available hash ->
          Some (Aggregate_signature.Public_key_hash.to_b58check hash)
      | _ -> None)
    (fun hash ->
      Public_key_for_committee_member_not_available
        (Aggregate_signature.Public_key_hash.of_b58check_exn hash))

(* TODO: https://gitlab.com/tezos/tezos/-/issues/5578
     Check that computed witness field is correct with respect to signatures.
*)
let compute_signatures_with_witnesses rev_indexed_signatures =
  let open Lwt_result_syntax in
  List.fold_left_es
    (fun (signatures, witnesses) signature_opt ->
      match signature_opt with
      | None -> return (signatures, witnesses)
      | Some (index, signature) ->
          let bit = Z.shift_left Z.one index in
          let witnesses = Z.logor witnesses bit in
          return (signature :: signatures, witnesses))
    ([], Z.zero)
    rev_indexed_signatures

let verify_signature ((module Plugin) : Dac_plugin.t) pk signature root_hash =
  let root_hash_bytes = Dac_plugin.hash_to_bytes root_hash in
  fail_unless
    (Aggregate_signature.check pk signature root_hash_bytes)
    (Signature_verification_failed (pk, signature, Plugin.to_hex root_hash))

let add_dac_member_signature dac_plugin signature_store signature =
  let open Lwt_result_syntax in
  let*? root_hash =
    Dac_plugin.raw_to_hash dac_plugin (Signature_repr.get_root_hash signature)
  in
  Store.Signature_store.add
    signature_store
    ~primary_key:root_hash
    ~secondary_key:(Signature_repr.get_signer_pkh signature)
    (Signature_repr.get_signature signature)

let rev_find_indexed_signatures node_store dac_members_pkh root_hash =
  let open Lwt_result_syntax in
  List.rev_mapi_es
    (fun index dac_member_pk ->
      let+ (signature_opt : Aggregate_signature.t option) =
        Store.Signature_store.find
          node_store
          ~primary_key:root_hash
          ~secondary_key:dac_member_pk
      in
      Option.map (fun signature -> (index, signature)) signature_opt)
    dac_members_pkh

let update_aggregate_sig_store node_store dac_members_pk_opt root_hash =
  let open Lwt_result_syntax in
  let* rev_indexed_signature =
    rev_find_indexed_signatures node_store dac_members_pk_opt root_hash
  in
  let* signatures, witnesses =
    compute_signatures_with_witnesses rev_indexed_signature
  in
  let raw_root_hash = Dac_plugin.hash_to_raw root_hash in
  let final_signature =
    Tezos_crypto.Aggregate_signature.aggregate_signature_opt signatures
  in
  match final_signature with
  | None ->
      tzfail
      @@ Cannot_compute_aggregate_signature
           (Hex.show @@ Dac_plugin.hash_to_hex root_hash)
  | Some aggregate_signature ->
      let* () =
        Store.Certificate_store.add
          node_store
          raw_root_hash
          Certificate_repr.(
            V0 (V0.make raw_root_hash aggregate_signature witnesses))
      in
      return @@ (aggregate_signature, witnesses)

let check_dac_member_has_signed signature_store root_hash dac_member_pkh =
  Store.Signature_store.mem
    signature_store
    ~primary_key:root_hash
    ~secondary_key:dac_member_pkh

let check_is_dac_member dac_committee signer_pkh =
  Option.is_some
  @@ List.find
       (fun pkh -> Aggregate_signature.Public_key_hash.equal signer_pkh pkh)
       dac_committee

let check_coordinator_knows_root_hash dac_plugin page_store root_hash =
  let open Lwt_result_syntax in
  let ((module Plugin) : Dac_plugin.t) = dac_plugin in
  let*! has_payload =
    Page_store.Filesystem.mem (module Plugin) page_store root_hash
  in
  match has_payload with
  | Error _ ->
      tzfail
      @@ Page_store.Cannot_read_page_from_page_storage (Plugin.to_hex root_hash)
  (* Return an HTTP 404 error when hash provided in signature is unknown *)
  | Ok false -> raise Not_found
  | Ok true -> return ()

let should_update_certificate dac_plugin get_public_key_opt ro_node_store
    committee_members signature =
  let open Lwt_result_syntax in
  let ((module Plugin) : Dac_plugin.t) = dac_plugin in
  let*? root_hash =
    Dac_plugin.raw_to_hash dac_plugin (Signature_repr.get_root_hash signature)
  in
  let signer_pkh = Signature_repr.get_signer_pkh signature in
  let* () =
    fail_unless
      (check_is_dac_member committee_members signer_pkh)
      (Public_key_is_non_committee_member signer_pkh)
  in
  let pub_key_opt = get_public_key_opt signer_pkh in
  let* pub_key =
    Option.fold_f
      ~none:(fun () ->
        tzfail (Public_key_for_committee_member_not_available signer_pkh))
      ~some:return
      pub_key_opt
  in
  let* dac_member_has_signed =
    check_dac_member_has_signed ro_node_store root_hash signer_pkh
  in
  if dac_member_has_signed then return false
  else
    let* () =
      verify_signature
        dac_plugin
        pub_key
        (Signature_repr.get_signature signature)
        root_hash
    in
    return true

let stream_certificate_update dac_plugin committee_members certificate
    certificate_streamers =
  let root_hash = Certificate_repr.get_root_hash certificate in
  let open Result_syntax in
  let* () =
    Certificate_streamers.push
      dac_plugin
      certificate_streamers
      root_hash
      certificate
  in
  if
    Certificate_repr.all_committee_members_have_signed
      committee_members
      certificate
  then
    let _ =
      Certificate_streamers.close dac_plugin certificate_streamers root_hash
    in
    return ()
  else return ()

let handle_put_dac_member_signature dac_plugin get_public_key_opt
    certificate_streamers_opt rw_node_store page_store committee_members
    committee_member_signature =
  let open Lwt_result_syntax in
  let ((module Plugin) : Dac_plugin.t) = dac_plugin in
  let raw_root_hash = Signature_repr.get_root_hash committee_member_signature in
  let*? root_hash = Dac_plugin.raw_to_hash dac_plugin raw_root_hash in
  let* () = check_coordinator_knows_root_hash dac_plugin page_store root_hash in
  let* should_update_certificate =
    should_update_certificate
      dac_plugin
      get_public_key_opt
      rw_node_store
      committee_members
      committee_member_signature
  in
  if should_update_certificate then
    let* () =
      add_dac_member_signature
        dac_plugin
        rw_node_store
        committee_member_signature
    in
    let* aggregate_signature, witnesses =
      update_aggregate_sig_store rw_node_store committee_members root_hash
    in
    let*? () =
      Option.iter_e
        (stream_certificate_update
           dac_plugin
           committee_members
           Certificate_repr.(
             V0 (V0.make raw_root_hash aggregate_signature witnesses)))
        certificate_streamers_opt
    in
    return ()
  else return ()

module Coordinator = struct
  let handle_put_dac_member_signature ctx dac_plugin rw_node_store page_store
      dac_member_signature =
    let committee_members = Node_context.Coordinator.committee_members ctx in
    let get_public_key_opt committee_member_address =
      List.find_map
        (fun Wallet_account.Coordinator.{public_key_hash; public_key} ->
          if
            Tezos_crypto.Aggregate_signature.Public_key_hash.(
              committee_member_address <> public_key_hash)
          then None
          else Some public_key)
        ctx.committee_members
    in
    handle_put_dac_member_signature
      dac_plugin
      get_public_key_opt
      (Some ctx.certificate_streamers)
      rw_node_store
      page_store
      committee_members
      dac_member_signature
end
back to top