Raw File
sc_rollup_proof_repr.ml
(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com>                *)
(* Copyright (c) 2022 Trili Tech, <contact@trili.tech>                       *)
(*                                                                           *)
(* 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.                                                 *)
(*                                                                           *)
(*****************************************************************************)

type error += Sc_rollup_proof_check of string

type error += Sc_rollup_invalid_serialized_inbox_proof

let () =
  register_error_kind
    `Permanent
    ~id:"smart_rollup_proof_check"
    ~title:"Invalid proof"
    ~description:"An invalid proof has been submitted"
    ~pp:(fun fmt msg -> Format.fprintf fmt "Invalid proof: %s" msg)
    Data_encoding.(obj1 @@ req "reason" (string Plain))
    (function Sc_rollup_proof_check msg -> Some msg | _ -> None)
    (fun msg -> Sc_rollup_proof_check msg) ;

  register_error_kind
    `Permanent
    ~id:"smart_rollup_invalid_serialized_inbox_proof"
    ~title:"Invalid serialized inbox proof"
    ~description:"The serialized inbox proof can not be de-serialized"
    ~pp:(fun fmt () -> Format.fprintf fmt "Invalid serialized inbox proof")
    Data_encoding.unit
    (function Sc_rollup_invalid_serialized_inbox_proof -> Some () | _ -> None)
    (fun () -> Sc_rollup_invalid_serialized_inbox_proof)

type reveal_proof =
  | Raw_data_proof of string
  | Metadata_proof
  | Dal_page_proof of {
      page_id : Dal_slot_repr.Page.t;
      proof : Dal_slot_repr.History.proof;
    }
  | Dal_parameters_proof

let reveal_proof_encoding =
  let open Data_encoding in
  let case_raw_data =
    case
      ~title:"raw data proof"
      (Tag 0)
      (obj2
         (req "reveal_proof_kind" (constant "raw_data_proof"))
         (req
            "raw_data"
            Bounded.(
              string
                ~length_kind:`Uint16
                Hex
                Constants_repr.sc_rollup_message_size_limit)))
      (function Raw_data_proof s -> Some ((), s) | _ -> None)
      (fun ((), s) -> Raw_data_proof s)
  and case_metadata_proof =
    case
      ~title:"metadata proof"
      (Tag 1)
      (obj1 (req "reveal_proof_kind" (constant "metadata_proof")))
      (function Metadata_proof -> Some () | _ -> None)
      (fun () -> Metadata_proof)
  in
  let case_dal_page =
    case
      ~title:"dal page proof"
      (Tag 2)
      (obj3
         (req "reveal_proof_kind" (constant "dal_page_proof"))
         (req "dal_page_id" Dal_slot_repr.Page.encoding)
         (req "dal_proof" Dal_slot_repr.History.proof_encoding))
      (function
        | Dal_page_proof {page_id; proof} -> Some ((), page_id, proof)
        | _ -> None)
      (fun ((), page_id, proof) -> Dal_page_proof {page_id; proof})
  in
  let case_dal_parameters =
    case
      ~title:"dal parameters proof"
      (Tag 3)
      (obj1 (req "reveal_proof_kind" (constant "dal_parameters_proof")))
      (function Dal_parameters_proof -> Some () | _ -> None)
      (fun () -> Dal_parameters_proof)
  in
  union [case_raw_data; case_metadata_proof; case_dal_page; case_dal_parameters]

type input_proof =
  | Inbox_proof of {
      level : Raw_level_repr.t;
      message_counter : Z.t;
      proof : Sc_rollup_inbox_repr.serialized_proof;
    }
  | Reveal_proof of reveal_proof
  | First_inbox_message

let input_proof_encoding =
  let open Data_encoding in
  let proof_kind kind = req "input_proof_kind" (constant kind) in
  let case_inbox_proof =
    case
      ~title:"inbox proof"
      (Tag 0)
      (obj4
         (proof_kind "inbox_proof")
         (req "level" Raw_level_repr.encoding)
         (req "message_counter" Data_encoding.n)
         (req "serialized_proof" Sc_rollup_inbox_repr.serialized_proof_encoding))
      (function
        | Inbox_proof {level; message_counter; proof} ->
            Some ((), level, message_counter, proof)
        | _ -> None)
      (fun ((), level, message_counter, proof) ->
        Inbox_proof {level; message_counter; proof})
  in
  let case_reveal_proof =
    case
      ~title:"reveal proof"
      (Tag 1)
      (obj2
         (proof_kind "reveal_proof")
         (req "reveal_proof" reveal_proof_encoding))
      (function Reveal_proof s -> Some ((), s) | _ -> None)
      (fun ((), s) -> Reveal_proof s)
  in
  let first_input =
    case
      ~title:"first input"
      (Tag 2)
      (obj1 (proof_kind "first_input"))
      (function First_inbox_message -> Some () | _ -> None)
      (fun () -> First_inbox_message)
  in
  union [case_inbox_proof; case_reveal_proof; first_input]

type 'proof t = {pvm_step : 'proof; input_proof : input_proof option}

type serialized = string

let serialize_pvm_step (type state proof output)
    ~(pvm : (state, proof, output) Sc_rollups.PVM.implementation)
    (proof : proof) : serialized tzresult =
  let open Result_syntax in
  let (module PVM) = pvm in
  match Data_encoding.Binary.to_string_opt PVM.proof_encoding proof with
  | Some p -> return p
  | None -> tzfail (Sc_rollup_proof_check "Cannot serialize proof")

let unserialize_pvm_step (type state proof output)
    ~(pvm : (state, proof, output) Sc_rollups.PVM.implementation)
    (proof : string) : proof tzresult =
  let open Result_syntax in
  let (module PVM) = pvm in
  match Data_encoding.Binary.of_string_opt PVM.proof_encoding proof with
  | Some p -> return p
  | None -> tzfail (Sc_rollup_proof_check "Cannot unserialize proof")

let serialized_encoding = Data_encoding.string Hex

let encoding =
  let open Data_encoding in
  conv
    (fun {pvm_step; input_proof} -> (pvm_step, input_proof))
    (fun (pvm_step, input_proof) -> {pvm_step; input_proof})
    (obj2
       (req "pvm_step" serialized_encoding)
       (opt "input_proof" input_proof_encoding))

let pp ppf _ = Format.fprintf ppf "Refutation game proof"

let start_of_pvm_step (type state proof output)
    ~(pvm : (state, proof, output) Sc_rollups.PVM.implementation)
    (proof : proof) =
  let (module P) = pvm in
  P.proof_start_state proof

let stop_of_pvm_step (type state proof output)
    ~(pvm : (state, proof, output) Sc_rollups.PVM.implementation)
    (proof : proof) =
  let (module P) = pvm in
  P.proof_stop_state proof

(* This takes an [input] and checks if it is above the given level,
   and if it is at or below the origination level for this rollup.
   It returns [None] if this is the case.

   We use this to check that the PVM proof is obeying [commit_inbox_level]
   correctly---if the message obtained from the inbox proof is above
   [commit_inbox_level] the [input_given] in the PVM proof should be [None]. *)
let cut_at_level ~origination_level ~commit_inbox_level
    (input : Sc_rollup_PVM_sig.input) =
  match input with
  | Inbox_message {inbox_level = input_level; _} ->
      if
        Raw_level_repr.(
          input_level <= origination_level || commit_inbox_level < input_level)
      then None
      else Some input
  | Reveal _data -> Some input

let proof_error reason =
  let open Lwt_result_syntax in
  tzfail (Sc_rollup_proof_check reason)

let check p reason =
  let open Lwt_result_syntax in
  if p then return_unit else proof_error reason

let check_inbox_proof snapshot serialized_inbox_proof (level, counter) =
  match Sc_rollup_inbox_repr.of_serialized_proof serialized_inbox_proof with
  | None -> Result_syntax.tzfail Sc_rollup_invalid_serialized_inbox_proof
  | Some inbox_proof ->
      Sc_rollup_inbox_repr.verify_proof (level, counter) snapshot inbox_proof

module Dal_helpers = struct
  (* FIXME/DAL: https://gitlab.com/tezos/tezos/-/issues/3997
     The current DAL refutation integration is not resilient to DAL parameters
     changes when upgrading the protocol. The code needs to be adapted. *)

  let import_level_is_valid ~dal_activation_level ~dal_attestation_lag
      ~origination_level ~commit_inbox_level ~published_level
      ~dal_attested_slots_validity_lag =
    (* [dal_attestation_lag] is supposed to be positive. *)
    let open Raw_level_repr in
    let dal_was_activated =
      match dal_activation_level with
      | None -> false
      | Some dal_activation_level -> published_level >= dal_activation_level
    in
    let slot_published_after_origination =
      published_level > origination_level
    in
    let not_too_recent =
      add published_level dal_attestation_lag <= commit_inbox_level
    in
    (* An attested slot is not expired if its attested level (equal to
       [published_level + dal_attestation_lag]) is not further than
       [dal_attested_slots_validity_lag] from the given inbox level. *)
    let ttl_not_expired =
      Raw_level_repr.(
        add
          (add published_level dal_attestation_lag)
          dal_attested_slots_validity_lag
        >= commit_inbox_level)
    in
    dal_was_activated && slot_published_after_origination && not_too_recent
    && ttl_not_expired

  let page_id_is_valid ~dal_number_of_slots ~dal_activation_level
      ~dal_attestation_lag ~origination_level ~commit_inbox_level
      cryptobox_parameters
      Dal_slot_repr.Page.{slot_id = {published_level; index}; page_index}
      ~dal_attested_slots_validity_lag =
    let open Dal_slot_repr in
    Result.is_ok
      (Page.Index.check_is_in_range
         ~number_of_pages:(Page.pages_per_slot cryptobox_parameters)
         page_index)
    && Result.is_ok
         (Dal_slot_index_repr.check_is_in_range
            ~number_of_slots:dal_number_of_slots
            index)
    && import_level_is_valid
         ~dal_activation_level
         ~dal_attestation_lag
         ~origination_level
         ~commit_inbox_level
         ~published_level
         ~dal_attested_slots_validity_lag

  let verify ~metadata ~dal_activation_level ~dal_attestation_lag
      ~dal_number_of_slots ~commit_inbox_level dal_parameters page_id
      dal_snapshot proof ~dal_attested_slots_validity_lag =
    let open Result_syntax in
    if
      page_id_is_valid
        dal_parameters
        ~dal_activation_level
        ~origination_level:metadata.Sc_rollup_metadata_repr.origination_level
        ~dal_attestation_lag
        ~commit_inbox_level
        ~dal_number_of_slots
        page_id
        ~dal_attested_slots_validity_lag
    then
      let* input =
        Dal_slot_repr.History.verify_proof
          dal_parameters
          page_id
          dal_snapshot
          proof
      in
      return_some (Sc_rollup_PVM_sig.Reveal (Dal_page input))
    else return_none

  let produce ~metadata ~dal_activation_level ~dal_attestation_lag
      ~dal_number_of_slots ~commit_inbox_level dal_parameters page_id ~page_info
      ~get_history confirmed_slots_history ~dal_attested_slots_validity_lag =
    let open Lwt_result_syntax in
    if
      page_id_is_valid
        dal_parameters
        ~dal_number_of_slots
        ~dal_activation_level
        ~origination_level:metadata.Sc_rollup_metadata_repr.origination_level
        ~dal_attestation_lag
        ~commit_inbox_level
        page_id
        ~dal_attested_slots_validity_lag
    then
      let* proof, content_opt =
        Dal_slot_repr.History.produce_proof
          dal_parameters
          page_id
          ~page_info
          ~get_history
          confirmed_slots_history
      in
      return
        ( Some (Reveal_proof (Dal_page_proof {proof; page_id})),
          Some (Sc_rollup_PVM_sig.Reveal (Dal_page content_opt)) )
    else return (None, None)
end

let valid (type state proof output)
    ~(pvm : (state, proof, output) Sc_rollups.PVM.implementation) ~metadata
    snapshot commit_inbox_level dal_snapshot dal_parameters
    ~dal_activation_level ~dal_attestation_lag ~dal_number_of_slots
    ~is_reveal_enabled ~dal_attested_slots_validity_lag (proof : proof t) =
  let open Lwt_result_syntax in
  let (module P) = pvm in
  let origination_level = metadata.Sc_rollup_metadata_repr.origination_level in
  let* input =
    match proof.input_proof with
    | None -> return_none
    | Some (Inbox_proof {level; message_counter; proof}) ->
        let*? inbox_message =
          check_inbox_proof snapshot proof (level, Z.succ message_counter)
        in
        return
        @@ Option.map (fun i -> Sc_rollup_PVM_sig.Inbox_message i) inbox_message
    | Some First_inbox_message ->
        let*? payload =
          Sc_rollup_inbox_message_repr.(serialize (Internal Start_of_level))
        in
        let inbox_level = Raw_level_repr.succ origination_level in
        let message_counter = Z.zero in
        return_some
          Sc_rollup_PVM_sig.(
            Inbox_message {inbox_level; message_counter; payload})
    | Some (Reveal_proof (Raw_data_proof data)) ->
        return_some (Sc_rollup_PVM_sig.Reveal (Raw_data data))
    | Some (Reveal_proof Metadata_proof) ->
        return_some (Sc_rollup_PVM_sig.Reveal (Metadata metadata))
    | Some (Reveal_proof (Dal_page_proof {proof; page_id})) ->
        Dal_helpers.verify
          ~dal_number_of_slots
          ~metadata
          ~dal_activation_level
          ~dal_attested_slots_validity_lag
          dal_parameters
          ~dal_attestation_lag
          ~commit_inbox_level
          page_id
          dal_snapshot
          proof
        |> Lwt.return
    | Some (Reveal_proof Dal_parameters_proof) ->
        (* FIXME: https://gitlab.com/tezos/tezos/-/issues/6562
           Support revealing historical DAL parameters.

           Currently, we do not support revealing DAL parameters for the past.
           We ignore the given [published_level] and use the DAL parameters. *)
        return_some
          (Sc_rollup_PVM_sig.Reveal
             (Dal_parameters
                Sc_rollup_dal_parameters_repr.
                  {
                    number_of_slots = Int64.of_int dal_number_of_slots;
                    attestation_lag = Int64.of_int dal_attestation_lag;
                    slot_size = Int64.of_int dal_parameters.slot_size;
                    page_size = Int64.of_int dal_parameters.page_size;
                  }))
  in
  let input =
    Option.bind input (cut_at_level ~origination_level ~commit_inbox_level)
  in
  let* input_requested =
    P.verify_proof ~is_reveal_enabled input proof.pvm_step
  in
  let* () =
    match (proof.input_proof, input_requested) with
    | None, No_input_required -> return_unit
    | Some First_inbox_message, Initial ->
        (* If the state is [Initial], we don't need a proof of the input,
           we know it's the [Start_of_level] after the origination. *)
        return_unit
    | Some (Inbox_proof {level; message_counter; proof = _}), First_after (l, n)
      ->
        check
          (Raw_level_repr.(level = l) && Z.(equal message_counter n))
          "Level and index of inbox proof are not equal to the one expected in \
           input request."
    | ( Some (Reveal_proof (Raw_data_proof data)),
        Needs_reveal (Reveal_raw_data expected_hash) ) ->
        let scheme = Sc_rollup_reveal_hash.scheme_of_hash expected_hash in

        let data_hash = Sc_rollup_reveal_hash.hash_string ~scheme [data] in
        check
          (Sc_rollup_reveal_hash.equal data_hash expected_hash)
          "Invalid reveal"
    | Some (Reveal_proof Metadata_proof), Needs_reveal Reveal_metadata ->
        return_unit
    | ( Some (Reveal_proof (Dal_page_proof {page_id; proof = _})),
        Needs_reveal (Request_dal_page pid) ) ->
        check
          (Dal_slot_repr.Page.equal page_id pid)
          "Dal proof's page ID is not the one expected in input request."
    | ( Some (Reveal_proof Dal_parameters_proof),
        Needs_reveal Reveal_dal_parameters ) ->
        return_unit
    | None, (Initial | First_after _ | Needs_reveal _)
    | Some _, No_input_required
    | Some (Inbox_proof _), Needs_reveal _
    | _ ->
        proof_error "Inbox proof and input request are dissociated."
  in
  return (input, input_requested)

module type PVM_with_context_and_state = sig
  include Sc_rollups.PVM.S

  val context : context

  val state : state

  val proof_encoding : proof Data_encoding.t

  val reveal : Sc_rollup_reveal_hash.t -> string option Lwt.t

  module Inbox_with_history : sig
    val inbox : Sc_rollup_inbox_repr.history_proof

    val get_history :
      Sc_rollup_inbox_repr.Hash.t ->
      Sc_rollup_inbox_repr.history_proof option Lwt.t

    val get_payloads_history :
      Sc_rollup_inbox_merkelized_payload_hashes_repr.Hash.t ->
      Sc_rollup_inbox_merkelized_payload_hashes_repr.History.t Lwt.t
  end

  module Dal_with_history : sig
    val confirmed_slots_history : Dal_slot_repr.History.t

    val get_history :
      Dal_slot_repr.History.hash -> Dal_slot_repr.History.t option Lwt.t

    val page_info :
      (Dal_slot_repr.Page.content * Dal_slot_repr.Page.proof) option

    val dal_parameters : Dal_slot_repr.parameters

    val dal_attestation_lag : int

    val dal_number_of_slots : int

    val dal_activation_level : Raw_level_repr.t option

    val dal_attested_slots_validity_lag : int
  end
end

let produce ~metadata pvm_and_state commit_inbox_level ~is_reveal_enabled =
  let open Lwt_result_syntax in
  let (module P : PVM_with_context_and_state) = pvm_and_state in
  let open P in
  let*! (request : Sc_rollup_PVM_sig.input_request) =
    P.is_input_state ~is_reveal_enabled P.state
  in
  let origination_level = metadata.Sc_rollup_metadata_repr.origination_level in
  let* input_proof, input_given =
    match request with
    | No_input_required -> return (None, None)
    | Initial ->
        (* The first input of a rollup is the [Start_of_level] after its
           origination. *)
        let* input =
          let*? payload =
            Sc_rollup_inbox_message_repr.(serialize (Internal Start_of_level))
          in
          let inbox_level = Raw_level_repr.succ origination_level in
          let message_counter = Z.zero in
          return_some
            Sc_rollup_PVM_sig.(
              Inbox_message {inbox_level; message_counter; payload})
        in
        let inbox_proof = First_inbox_message in
        return (Some inbox_proof, input)
    | First_after (level, message_counter) ->
        let* inbox_proof, input =
          Inbox_with_history.(
            Sc_rollup_inbox_repr.produce_proof
              ~get_payloads_history
              ~get_history
              inbox
              (level, Z.succ message_counter))
        in
        let input =
          Option.map (fun msg -> Sc_rollup_PVM_sig.Inbox_message msg) input
        in
        let inbox_proof =
          Inbox_proof
            {
              level;
              message_counter;
              proof = Sc_rollup_inbox_repr.to_serialized_proof inbox_proof;
            }
        in
        return (Some inbox_proof, input)
    | Needs_reveal (Reveal_raw_data h) -> (
        let*! res = reveal h in
        match res with
        | None -> proof_error "No reveal"
        | Some data ->
            return
              ( Some (Reveal_proof (Raw_data_proof data)),
                Some (Sc_rollup_PVM_sig.Reveal (Raw_data data)) ))
    | Needs_reveal Reveal_metadata ->
        return
          ( Some (Reveal_proof Metadata_proof),
            Some Sc_rollup_PVM_sig.(Reveal (Metadata metadata)) )
    | Needs_reveal (Request_dal_page page_id) ->
        let open Dal_with_history in
        Dal_helpers.produce
          ~dal_number_of_slots
          ~metadata
          ~dal_activation_level
          dal_parameters
          ~dal_attestation_lag
          ~commit_inbox_level
          page_id
          ~page_info
          ~get_history
          ~dal_attested_slots_validity_lag
          confirmed_slots_history
    | Needs_reveal Reveal_dal_parameters ->
        let open Dal_with_history in
        return
          ( Some (Reveal_proof Dal_parameters_proof),
            Some
              Sc_rollup_PVM_sig.(
                Reveal
                  (Dal_parameters
                     Sc_rollup_dal_parameters_repr.
                       {
                         number_of_slots = Int64.of_int dal_number_of_slots;
                         attestation_lag = Int64.of_int dal_attestation_lag;
                         slot_size = Int64.of_int dal_parameters.slot_size;
                         page_size = Int64.of_int dal_parameters.page_size;
                       })) )
  in
  let input_given =
    Option.bind
      input_given
      (cut_at_level ~origination_level ~commit_inbox_level)
  in
  let* pvm_step_proof =
    P.produce_proof P.context ~is_reveal_enabled input_given P.state
  in
  let*? pvm_step = serialize_pvm_step ~pvm:(module P) pvm_step_proof in
  return {pvm_step; input_proof}

module Internal_for_tests = struct
  let cut_at_level = cut_at_level
end
back to top