main_protocol.ml
(*****************************************************************************)
(* *)
(* MIT License *)
(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com> *)
(* *)
(* 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. *)
(* *)
(*****************************************************************************)
(**
aPlonK is a {e PlonK}-based proving system.
As such, it provides a way to create {e succinct cryptographic proofs}
about a given predicate, which can be then verified with a low
computational cost.
In this system, a predicate is represented by an {e arithmetic circuit},
i.e. a collection of arithmetic {e gates} operating over a {e prime field},
connected through {e wires} holding {e scalars} from this field.
For example, the following diagram illustrates a simple circuit checking that
the addition of two scalars ([w1] and [w2]) is equal to [w0]. Here,
the [add] gate can be seen as taking two inputs and producing an output,
while the [eq] gate just takes two inputs and asserts they're equal.
{[
(w0)│ w1│ w2│
│ └───┐ ┌───┘
│ ┌─┴───┴─┐
│ │ add │
│ └───┬───┘
└──────┐ ┌───┘w3
┌─┴───┴─┐
│ eq │
└───────┘
]}
The wires of a circuit are called {e prover inputs}, since the prover needs
an assignment of all wires to produce a proof.
The predicate also declares a subset of the wires called {e verifier inputs}.
In our example, wire [w0] is the only verifier input, which is
indicated by the parenthesis.
A proof for a given [w0] would prove the following statement:
[∃ w1, w2, w3: w3 = w1 + w2 ∧ w0 = w3]
This means that the verifier only needs a (typically small) subset of the
inputs alongside the (succinct) proof to check the validity of the statement.
A more interesting example would be to replace the [add] gate
by a more complicated hash circuit. This would prove the knowledge of the
pre-image of a hash.
A simplified view of aPlonk's API consists of the following three functions:
{[
val setup : circuit -> srs ->
(prover_public_parameters, verifier_public_parameters)
val prove : prover_public_parameters -> prover_inputs ->
private_inputs -> proof
val verify : verifier_public_parameters -> verifier_inputs ->
proof -> bool
]}
In addition to the prove and verify, the interface provides a function
to setup the system. The setup function requires a {e Structured Reference String}.
Two large SRSs were generated by the ZCash and Filecoin
projects and are both used in aPlonK.
Notice also that the circuit is used during setup only and, independently
from its size, the resulting {e verifier_public_parameters} will be a
succinct piece of data that will be posted on-chain to allow
verification and they are bound to the specific circuit that generated
them.
The {e prover_public_parameters}'s size is linear in the size of the circuit.
*)
open Bls
open Utils
open Identities
include Main_protocol_intf
module Make_impl (PP : Polynomial_protocol.S) = struct
module RangeCheck = Range_check_gate.Range_check_gate (PP)
module Perm = Permutation_gate.Permutation_gate (PP)
module Plook = Plookup_gate.Plookup_gate (PP)
module Gates = Custom_gates
module Commitment = PP.PC.Commitment
module Input_commitment = Input_commitment.Make (PP.PC.Commitment)
module PP = PP
exception Entry_not_in_table = Plook.Entry_not_in_table
exception Rest_not_null = Poly.Rest_not_null
type scalar = Scalar.t [@@deriving repr]
type circuit_map = (Circuit.t * int) SMap.t
type proof = {
perm_and_plook : Commitment.t;
wires_cm : Commitment.t;
pp_proof : PP.proof;
}
[@@deriving repr]
type circuit_prover_input = {
witness : scalar array;
input_commitments : Input_commitment.t list;
}
[@@deriving repr]
type prover_inputs = circuit_prover_input list SMap.t [@@deriving repr]
type public_inputs = scalar array list [@@deriving repr]
type verifier_inputs =
(public_inputs * Input_commitment.public list list) SMap.t
[@@deriving repr]
let check_circuit_name map =
SMap.iter
(fun name _ ->
if name = "" then ()
else if Char.compare name.[0] '9' <= 0 then
failwith
(Printf.sprintf "check_circuit_name : circuit name (= '%s')" name
^ " must not begin with '\\', '#', '$', '%', '&', ''', '(', ')', \
'*', '+', ',', '-', '.', '/' or a digit.")
else if String.contains name SMap.Aggregation.sep.[0] then
failwith
(Printf.sprintf
"check_circuit_name : circuit name (= '%s') mustn't contain '%s'"
name
SMap.Aggregation.sep))
map
let check_circuits circuits_map inputs =
check_circuit_name inputs ;
SMap.(
iter (fun k _ ->
if not (mem k circuits_map) then
failwith
(Printf.sprintf
"check_circuits : circuit %s not found in public parameters."
k)))
inputs
(* Returns the wire names as a string list *)
let wire_names nb_wires = List.init nb_wires Plompiler.Csir.wire_name
(* Convert the wire arrays into maps, keyed as in [wire_names] *)
let name_wires wires_array =
SMap.map
(List.map (fun array ->
let n = Array.length array in
List.combine (wire_names n) (Array.to_list array) |> SMap.of_list))
wires_array
let hash_verifier_inputs verifier_inputs =
let open Utils.Hash in
let bytes_of_com c = Plompiler.Utils.to_bytes Input_commitment.public_t c in
let st = init () in
SMap.iter
(fun key (public_inputs_list, input_commitments_list) ->
update st (Bytes.of_string key) ;
List.iter
(fun pi -> Array.iter (fun s -> update st (Scalar.to_bytes s)) pi)
public_inputs_list ;
List.iter
(List.iter (fun c -> update st (bytes_of_com c)))
input_commitments_list)
verifier_inputs ;
finish st
type gate_randomness = {beta : scalar; gamma : scalar; delta : scalar}
let build_gates_randomness transcript =
let betas_gammas, transcript = Fr_generation.random_fr_list transcript 3 in
( {
beta = List.hd betas_gammas;
gamma = List.nth betas_gammas 1;
delta = List.nth betas_gammas 2;
},
transcript )
module Prover = struct
(* - n : upper-bound on the number of constraints in a circuit
- domain : n powers of an n-th root of unity
- pp_public_parameters : polynomial protocol prover parameters
- g_map : preprocessed polynomials for each circuit, prefixed with the
circuit name ; note that this is circuit specific, but we need
them together in order to commit to the whole g_map once.
- g_prover_aux : auxiliary information returned when commiting to g_map.
Note that the rest of the commitment is not needed by
the prover.
- evaluations : FFT evaluations of commonly used polynomials for all
circuits, e.g. L1, Si1, Si2, Si3, X (Lnp1 for plookup).
Each evaluation is on a domain that may be bigger than
[domain] the size is [4n] for most polynomials and [8n]
for some of them. For that, there must exist a [8n]-th
root of unity in the scalar field. These evaluations
does not contain g_map’s.
- zk : true if we want to hide witness polynomial by adding multiple of
(X^n - 1). This increases polynomials degree & may increase the
the size of the evaluations domain & FFTs.
- nb_of_t_chunks : number of T polynomials returned by PP
- eval_points : used for the PC query ; note that the input commitments
are not here, their eval_points have to be added at
proof/verification time
*)
type common_prover_pp = {
n : int;
domain : Domain.t;
pp_public_parameters : PP.prover_public_parameters;
g_map : Poly.t SMap.t;
g_prover_aux : Commitment.prover_aux;
evaluations : Evaluations.t SMap.t;
zk : bool;
nb_of_t_chunks : int;
eval_points : eval_point list list;
}
[@@deriving repr]
(* - circuit_size : number of constraints in the circuit
- input_com_sizes : the size of each input_commitment
- public_input_size : the size of the public inputs
- gates : map from selector names to an array of selector coefficients
(one selector coefficient per constraint).
The length of such array is the domain size (also n). If there
are fewer constraints than n, the array is padded with 0's,
except qpub, that should be [||] if there are public inputs.
- tables : [tables] is a list of scalar arrays, one per wire, so the list
length is Plompiler.Csir.nb_wires_arch.
Each array is the concatenation of one of the columns of all
Plookup tables. (One column per wire, tables which use fewer
columns are completed with dummy ones).
Each scalar array is a concatenation of tables
- wires : an array where each of the components corresponds to a wire in
the architecture and contains an array of indices (one index
per constraint representing the variable that such wire takes
at that constraint).
Again, the length of such array is the domain size (i.e. n).
If there are fewer constraints than n, the array is padded with
the last array element.
- permutation: indices corresponding to a permutation that preserves
[wires]. Its length is [n * Plompiler.Csir.nb_wires_arch] and
should be maximal in the sense that it splits in as many cycles
as there are variables in the circuit.
- evaluations : similar as the common_pp evaluations, but for the
circuit-specific polynomials (selectors, Ss_i, plookup polys),
that are contained in common_pp.g_map.
- alpha : scalar used by plookup.
- ultra : flag to specify whether plookup is being used.
*)
type circuit_prover_pp = {
circuit_size : int;
input_com_sizes : int list;
public_input_size : int;
gates : Scalar.t array SMap.t;
tables : Scalar.t array list;
wires : int array array;
permutation : int array;
rc_permutations : int array SMap.t;
evaluations : Evaluations.t SMap.t;
alpha : Scalar.t option;
ultra : bool;
range_checks : (int * int) list SMap.t;
}
[@@deriving repr]
type public_parameters = {
common_pp : common_prover_pp;
circuits_map : circuit_prover_pp SMap.t;
transcript : PP.transcript;
}
[@@deriving repr]
(* [build_all_wire_keys nb_proofs nb_wires] returns a list of prefixed wires name *)
let build_all_wires_keys pp nb_proofs_map nb_wires =
let names = wire_names nb_wires in
List.concat_map (fun (circuit_name, n) ->
let wires_keys =
List.concat_map
(SMap.Aggregation.build_all_names circuit_name n)
names
in
let c = SMap.find circuit_name pp.circuits_map in
let rc_keys =
SMap.mapi
(fun wire _ ->
let n = SMap.find circuit_name nb_proofs_map in
List.concat_map
(SMap.Aggregation.build_all_names circuit_name n)
(RangeCheck.z_names wire))
c.range_checks
|> SMap.values |> List.concat
in
wires_keys @ rc_keys)
@@ SMap.bindings nb_proofs_map
(* TODO #5551
Handle Plookup
*)
(* For the distributed_prover *)
let build_all_keys_z pp =
List.concat_map
(fun (c_name, c) ->
let perm =
List.map (SMap.Aggregation.add_prefix c_name) Perm.shared_z_names
in
if SMap.is_empty c.range_checks then perm
else
perm
@ (SMap.mapi
(fun wire _ ->
List.map
(SMap.Aggregation.add_prefix c_name)
(RangeCheck.shared_z_names wire))
c.range_checks
|> SMap.values |> List.concat))
(SMap.bindings pp.circuits_map)
let enforce_wire_values wire_indices wire_values =
try
Array.map
(fun l ->
let w_array = Array.map (fun index -> wire_values.(index)) l in
Evaluations.of_array (Array.length w_array - 1, w_array))
wire_indices
with Invalid_argument _ ->
failwith
"Compute_wire_polynomial : x's length does not match with circuit. \
Either your witness is too short, or some indexes are greater than \
the witness size."
let blind ~pp f_map =
let nb_extra_blinds =
List.fold_left
(fun acc e -> max acc (List.length e))
0
pp.common_pp.eval_points
in
let blind_list_map l =
List.map
(fun unblinded ->
if pp.common_pp.zk then
let blinded =
SMap.map
(Poly.blind ~nb_blinds:(1 + nb_extra_blinds) pp.common_pp.n)
unblinded
|> SMap.to_pair
in
(fst blinded, Some (snd blinded))
else (unblinded, None))
l
|> List.split
in
SMap.map blind_list_map f_map |> SMap.to_pair
let update_wires_with_rc_1 ?shifts_map pp
(all_f_wires, f_wires, f_blinds, wires_list_map) =
let rc_z_evals, rc_map =
SMap.mapi
(fun name w_list ->
let circuit_pp = SMap.find name pp.circuits_map in
if SMap.is_empty circuit_pp.range_checks then ([], [])
else
List.map
(fun values ->
RangeCheck.f_map_contribution_1
~range_checks:circuit_pp.range_checks
~domain:pp.common_pp.domain
~values)
w_list
|> List.split)
wires_list_map
|> SMap.filter (fun _ x -> x <> ([], []))
|> SMap.to_pair
in
let rc_map, rc_blinds = blind ~pp rc_map in
let all_rc_z = SMap.Aggregation.gather_maps ?shifts_map rc_map in
let wires_list_map =
SMap.Aggregation.add_map_list_map wires_list_map rc_z_evals
in
let f_wires = SMap.Aggregation.add_map_list_map f_wires rc_map in
let all_f_wires = SMap.union_disjoint all_f_wires all_rc_z in
let f_blinds =
SMap.mapi
(fun k l1 ->
match SMap.find_opt k rc_blinds with
| Some l2 ->
List.map2
(fun a -> function
| Some b -> Some (SMap.union_disjoint (Option.get a) b)
| None -> None)
l1
l2
| None -> l1)
f_blinds
in
(all_f_wires, f_wires, f_blinds, wires_list_map)
(* returns informations about wires, including commitment, blinds &
different useful representations of wires *)
let commit_to_wires ?all_keys ?shifts_map pp
(inputs_map : circuit_prover_input list SMap.t) =
(* wires values map list map *)
let wires_list_map =
SMap.mapi
(fun name input ->
let circuit_pp = SMap.find name pp.circuits_map in
List.map
(fun w -> enforce_wire_values circuit_pp.wires w.witness)
input)
inputs_map
|> name_wires
in
(* wire-polynomials array list map & there blinds *)
let f_wires, f_blinds =
let f_wires =
SMap.map
(List.map
(SMap.map (Evaluations.interpolation_fft pp.common_pp.domain)))
wires_list_map
in
blind ~pp f_wires
in
(* all wire-polynomials gathered in a map *)
let all_f_wires = SMap.Aggregation.gather_maps ?shifts_map f_wires in
let all_f_wires, f_wires, f_blinds, wires_list_map =
update_wires_with_rc_1
?shifts_map
pp
(all_f_wires, f_wires, f_blinds, wires_list_map)
in
let cm_wires, cm_aux_wires =
Commitment.commit
?all_keys
pp.common_pp.pp_public_parameters
all_f_wires
in
(wires_list_map, f_wires, f_blinds, all_f_wires, cm_wires, cm_aux_wires)
(* For each circuits, compute the shared Z polynomial *)
let build_f_map_perm pp {beta; gamma; _} batched_wires =
SMap.mapi
(fun name values ->
let circuit_pp = SMap.find name pp.circuits_map in
(* Removing everything that is not wires *)
let values =
let wires_names =
wire_names Plompiler.Csir.nb_wires_arch
|> List.map String.capitalize_ascii
in
SMap.filter (fun k _ -> List.mem k wires_names) values
in
let zs =
Perm.f_map_contribution
~permutation:circuit_pp.permutation
~values
~beta
~gamma
~domain:pp.common_pp.domain
()
in
if pp.common_pp.zk then
SMap.map
(fun f -> fst (Poly.blind ~nb_blinds:3 pp.common_pp.n f))
zs
else zs)
batched_wires
|> SMap.Aggregation.smap_of_smap_smap
(* For each circuit, computes Plookup-specific polynomials *)
let build_f_map_plook ?shifts_map pp rd wires_list_map =
SMap.mapi
(fun name w_list ->
let circuit_pp = SMap.find name pp.circuits_map in
if not circuit_pp.ultra then []
else
List.map
(fun wires ->
let plook_map =
Plook.f_map_contribution
~wires
~gates:circuit_pp.gates
~tables:circuit_pp.tables
~alpha:circuit_pp.alpha
~beta:rd.beta
~gamma:rd.gamma
~domain:pp.common_pp.domain
in
if pp.common_pp.zk then
SMap.map
(fun f -> fst (Poly.blind ~nb_blinds:3 pp.common_pp.n f))
plook_map
else plook_map)
w_list)
wires_list_map
|> SMap.Aggregation.gather_maps ?shifts_map
let build_f_map_rc_2 pp rd batched_values =
SMap.mapi
(fun name values ->
let circuit_pp = SMap.find name pp.circuits_map in
if SMap.is_empty circuit_pp.range_checks then SMap.empty
else
let zs =
RangeCheck.f_map_contribution_2
~permutations:circuit_pp.rc_permutations
~beta:rd.beta
~gamma:rd.gamma
~domain:pp.common_pp.domain
~values
in
if pp.common_pp.zk then
SMap.map
(* 3 blinds because the polynomial is evaluated at x and gx *)
(fun f -> fst (Poly.blind ~nb_blinds:3 pp.common_pp.n f))
zs
else zs)
batched_values
|> SMap.Aggregation.smap_of_smap_smap
let format_input_com (inputs_map : circuit_prover_input list SMap.t) =
SMap.mapi
(fun name l ->
let n = List.length l in
List.mapi
(fun i w ->
(* For each proof, transforms the list of inputs commitments into
a map of the form {com_label0 -> ic0 ; com_label1 -> ic1 ; …} *)
(List.mapi (fun j ic ->
let name =
SMap.Aggregation.add_prefix
~n
~i
name
(Gates.com_label ^ string_of_int j)
in
( Input_commitment.(SMap.singleton name ic.prover_aux.poly),
ic.prover_aux.pc_prover_aux )))
w.input_commitments)
l
|> List.concat)
inputs_map
|> SMap.values |> List.concat
let build_evaluations pp f_map =
(* rebuild g_maps’ evaluations *)
let evaluations =
SMap.fold
(fun _ pp evals -> SMap.union_disjoint pp.evaluations evals)
pp.circuits_map
pp.common_pp.evaluations
in
Evaluations.compute_evaluations_update_map ~evaluations f_map
let build_perm_rc2_identities pp rd =
SMap.mapi
(fun circuit_name circuit_pp ->
(* Using the batched wires *)
let wires_names =
List.map
String.capitalize_ascii
(wire_names @@ Array.length circuit_pp.wires)
in
let circuit_prefix = SMap.Aggregation.add_prefix circuit_name in
let perm_id =
Perm.prover_identities
~circuit_prefix
~wires_names
~beta:rd.beta
~gamma:rd.gamma
~n:pp.common_pp.n
()
in
let range_checks = circuit_pp.range_checks in
if SMap.is_empty range_checks then perm_id
else
let rc2_ids =
RangeCheck.prover_identities_2
~range_checks
~circuit_prefix
~beta:rd.beta
~gamma:rd.gamma
~domain_size:pp.common_pp.n
()
in
merge_prover_identities [perm_id; rc2_ids])
pp.circuits_map
|> SMap.values |> merge_prover_identities
let build_gates_plook_rc1_identities ?shifts_map pp rd inputs_map =
let identities_map =
SMap.mapi
(fun circuit_name inputs_list ->
let circuit_pp = SMap.find circuit_name pp.circuits_map in
let wires_names = wire_names @@ Array.length circuit_pp.wires in
(* Identities that must be computed for each proof *)
let shift, nb_proofs =
match shifts_map with
| None -> (0, List.length inputs_list)
| Some shifts_map -> SMap.find circuit_name shifts_map
in
let circuit_prefix = SMap.Aggregation.add_prefix circuit_name in
let proof_prefix i =
SMap.Aggregation.add_prefix
~n:nb_proofs
~i:(i + shift)
circuit_name
in
let gates_identities =
List.mapi
(fun i inputs ->
let input_coms_size =
List.fold_left ( + ) 0 circuit_pp.input_com_sizes
in
let public_inputs =
Array.sub
inputs.witness
input_coms_size
circuit_pp.public_input_size
in
Gates.aggregate_prover_identities
~circuit_prefix
~input_coms_size
~proof_prefix:(proof_prefix i)
~gates:circuit_pp.gates
~public_inputs
~domain:pp.common_pp.domain
())
inputs_list
in
(* for plookup and rc, we do a mapi on the input_list & ignore the
input (instead of doing a List.init nb_proofs) because for the
distributed prover, the number of inputs given will differ from
nb_proofs *)
let plookup_identities =
if not circuit_pp.ultra then []
else
List.mapi
(fun i _ ->
Plook.prover_identities
~circuit_prefix
~proof_prefix:(proof_prefix i)
~wires_names
~alpha:circuit_pp.alpha
~beta:rd.beta
~gamma:rd.gamma
~n:pp.common_pp.n
())
inputs_list
in
let rc1_identities =
let range_checks = circuit_pp.range_checks in
if SMap.is_empty range_checks then []
else
List.mapi
(fun i _ ->
RangeCheck.prover_identities_1
~range_checks
~circuit_prefix
~proof_prefix:(proof_prefix i)
~domain_size:pp.common_pp.n
())
inputs_list
in
merge_prover_identities
(rc1_identities @ plookup_identities @ gates_identities))
inputs_map
in
merge_prover_identities (SMap.values identities_map)
let prove_parameters ~pp_prove pp ~inputs_map =
let wires_list_map, f_wires, f_blinds, all_f_wires, cm_wires, cm_aux_wires
=
commit_to_wires pp inputs_map
in
(* compute beta & gamma (common for all proofs) *)
let transcript = Transcript.expand Commitment.t cm_wires pp.transcript in
let rd, transcript = build_gates_randomness transcript in
let batched_wires =
Perm.Shared_argument.build_batched_wires_values
~delta:rd.delta
~wires:wires_list_map
()
in
(* The new batched_wires is used for the RC shared perm argument *)
let f_map_contributions =
let f_map_perm = build_f_map_perm pp rd batched_wires in
let f_map_plook = build_f_map_plook pp rd wires_list_map in
let f_map_rc2 = build_f_map_rc_2 pp rd batched_wires in
SMap.union_disjoint_list [f_map_perm; f_map_plook; f_map_rc2]
in
let input_com_secrets = format_input_com inputs_map in
let identities =
let gates_plook_rc1_ids =
build_gates_plook_rc1_identities pp rd inputs_map
in
let perm_rc2_ids = build_perm_rc2_identities pp rd in
merge_prover_identities [perm_rc2_ids; gates_plook_rc1_ids]
in
let eval_points =
pp.common_pp.eval_points @ List.map (Fun.const [X]) input_com_secrets
in
(* Here we introduced the use_batched_wires because f_wires does not contain rc stuff, which will be missing if batched wires polys are deduced from f_wires *)
let batched_wires_polys =
Perm.Shared_argument.build_batched_witness_polys
~use_batched_wires:
(SMap.exists
(fun _ c -> not (SMap.is_empty c.range_checks))
pp.circuits_map)
~zero_knowledge:pp.common_pp.zk
~domain:pp.common_pp.domain
~delta:rd.delta
~batched_wires
~f:(f_wires, f_blinds)
()
in
let evaluations =
build_evaluations
pp
(SMap.union_disjoint_list
(all_f_wires :: f_map_contributions :: batched_wires_polys
:: List.map fst input_com_secrets))
in
let cm_perm_plook_rc, perm_plook_rc_prv_aux =
Commitment.commit pp.common_pp.pp_public_parameters f_map_contributions
in
let transcript =
Transcript.expand Commitment.t cm_perm_plook_rc transcript
in
let secrets =
[
(pp.common_pp.g_map, pp.common_pp.g_prover_aux);
(f_map_contributions, perm_plook_rc_prv_aux);
(all_f_wires, cm_aux_wires);
]
@ input_com_secrets
in
let generator = Domain.get pp.common_pp.domain 1 in
let pp_proof, _transcript =
pp_prove
pp.common_pp.pp_public_parameters
transcript
~n:pp.common_pp.n
~generator
~secrets
~eval_points
~evaluations
~identities
~nb_of_t_chunks:pp.common_pp.nb_of_t_chunks
in
(pp_proof, (cm_perm_plook_rc, cm_wires, rd))
end
module Verifier = struct
(* - n : upper-bound on the number of constraints in a circuit
- generator : primitive n-th root of unity
- pp_public_parameters : polynomial protocol verifier parameters
- cm_g : commitment to the preprocessed polynomials
- eval_points : used for the PC query ; note that the input commitments
are not here, their eval_points have to be added at
proof/verification time
*)
type common_verifier_pp = {
n : int;
generator : Scalar.t;
pp_public_parameters : PP.verifier_public_parameters;
cm_g : Commitment.t;
eval_points : eval_point list list;
}
[@@deriving repr]
(* - gates : map of selector names ; they are binded to unit because we
just need their name for the verification
- alpha : scalar used by plookup.
- ultra : flag to specify whether plookup is being used.
- input_com_sizes : the size of each input_commitment
*)
type circuit_verifier_pp = {
gates : unit SMap.t;
alpha : Scalar.t option;
ultra : bool;
input_com_sizes : int list;
range_checks : bool SMap.t;
}
[@@deriving repr]
let circuit_verifier_pp_of_circuit_prover_pp
(prover_pp : Prover.circuit_prover_pp) =
{
gates = SMap.map (Fun.const ()) prover_pp.gates;
alpha = prover_pp.alpha;
ultra = prover_pp.ultra;
input_com_sizes = prover_pp.input_com_sizes;
range_checks = SMap.map (fun l -> l <> []) prover_pp.range_checks;
}
let build_identities circuits_map (n, generator) rd public_inputs_map =
let identities_map =
SMap.mapi
(fun circuit_name pi_list ->
let circuit_pp = SMap.find circuit_name circuits_map in
let nb_proofs = List.length pi_list in
let circuit_prefix = SMap.Aggregation.add_prefix circuit_name in
let proof_prefix i =
SMap.Aggregation.add_prefix ~n:nb_proofs ~i circuit_name
in
let wires_names = wire_names Plompiler.Csir.nb_wires_arch in
let perm_identities =
Perm.verifier_identities
~circuit_prefix
~wires_names
~nb_proofs
~generator
~n
~beta:rd.beta
~gamma:rd.gamma
~delta:rd.delta
()
in
let gates_identities =
List.mapi
(fun i public_inputs ->
Gates.aggregate_verifier_identities
~circuit_prefix
~input_com_sizes:circuit_pp.input_com_sizes
~gates:circuit_pp.gates
~public_inputs
~generator
~size_domain:n
()
~proof_prefix:(proof_prefix i))
pi_list
in
let plookup_identities =
if not circuit_pp.ultra then []
else
List.init nb_proofs (fun i ->
Plook.verifier_identities
~circuit_prefix
~wires_names
~generator
~n
~alpha:circuit_pp.alpha
~beta:rd.beta
~gamma:rd.gamma
~proof_prefix:(proof_prefix i)
())
in
let range_checks = circuit_pp.range_checks in
if SMap.is_empty range_checks then
merge_verifier_identities
(perm_identities :: (plookup_identities @ gates_identities))
else
let rc1_identities =
List.init nb_proofs (fun i ->
RangeCheck.verifier_identities_1
~range_checks
~circuit_prefix
~proof_prefix:(proof_prefix i)
())
in
let rc2_identities =
RangeCheck.verifier_identities_2
~range_checks
~circuit_prefix
~nb_proofs
~beta:rd.beta
~gamma:rd.gamma
~delta:rd.delta
~domain_size:n
~generator
()
in
merge_verifier_identities
(perm_identities :: rc2_identities
:: (rc1_identities @ plookup_identities @ gates_identities)))
public_inputs_map
in
merge_verifier_identities (SMap.values identities_map)
let format_input_com (inputs_map : verifier_inputs) =
SMap.mapi
(fun name (_, l) ->
let n = List.length l in
List.mapi
(fun i ic ->
let rename j s =
SMap.Aggregation.add_prefix ~n ~i name s ^ string_of_int j
in
List.mapi (fun j -> Commitment.rename (rename j)) ic)
l
|> List.concat)
inputs_map
|> SMap.values |> List.concat
(* Assumes the same circuit, i.e. the public parameters are fixed *)
let verify_parameters ((common_pp, circuits_map), transcript) inputs_map
proof =
(* The transcript is the same as the provers's transcript since the proof
is already aggregated *)
let transcript =
Transcript.expand Commitment.t proof.wires_cm transcript
in
(* Get the same randomness for all proofs *)
(* Step 1a : compute beta & gamma *)
let rd, transcript = build_gates_randomness transcript in
(* Step 3 : compute verifier’s identities *)
let identities =
build_identities
circuits_map
(common_pp.n, common_pp.generator)
rd
(SMap.map fst inputs_map)
in
let input_commitments = format_input_com inputs_map in
let commitments =
[common_pp.cm_g; proof.perm_and_plook; proof.wires_cm]
@ input_commitments
in
let eval_points =
common_pp.eval_points @ List.map (Fun.const [X]) input_commitments
in
let transcript =
Transcript.expand Commitment.t proof.perm_and_plook transcript
in
(transcript, identities, rd, commitments, eval_points)
end
type prover_public_parameters = Prover.public_parameters = {
common_pp : Prover.common_prover_pp;
circuits_map : Prover.circuit_prover_pp SMap.t;
transcript : PP.transcript;
}
[@@deriving repr]
type verifier_public_parameters = {
common_pp : Verifier.common_verifier_pp;
circuits_map : Verifier.circuit_verifier_pp SMap.t;
transcript : PP.transcript;
}
[@@deriving repr]
module Preprocess = struct
let eval_points (circuits_map : Prover.circuit_prover_pp SMap.t) =
let open Prover in
let used_ultra = SMap.exists (fun _ c -> c.ultra) circuits_map in
(* We need gX evaluation for wires if there is a next gate, or if there is a range check gate (RC.Z needs to be evaluated at X & gX and is committed with wires) *)
let gx_wires =
SMap.exists
(fun _ c ->
Gates.exists_gx_composition ~gates:c.gates
|| c.range_checks <> SMap.empty)
circuits_map
in
let g_points = if used_ultra then [X; GX] else [X] in
let f_points = if gx_wires then [X; GX] else [X] in
[g_points; [X; GX]; f_points]
let degree_evaluations ~nb_wires ~gates ~ultra =
let min_deg =
(* minimum size needed for permutation gate ; if we are in the gate case, nb_wires = 0 => min_perm = 1 which is the minimum degree anyway *)
let min_perm = Perm.polynomials_degree ~nb_wires in
if ultra then max (Plook.polynomials_degree ()) min_perm else min_perm
in
max min_deg (Gates.aggregate_polynomials_degree ~gates)
let domain_evaluations ~zero_knowledge ~n len_evals =
let zk_factor = if zero_knowledge then if n <= 2 then 4 else 2 else 1 in
let len_evals = zk_factor * len_evals * n in
Domain.build_power_of_two Z.(log2up (of_int len_evals))
(* Function preprocessing the circuit wires and selector polynomials;
Inputs:
- n: the number of constraints in the circuits + the number of public inputs
- domain: the interpolation domain for polynomials of size n
- l, the number of public inputs + the number of elements committed in input_commitments
Outputs:
- interpolated_gates: selector polynomials, prepended with 0/1s for the public inputs,
interpolated on the domain
- extended_gates: gates with "q_pub" selector if there is public_inputs
- extended_wires: circuits wires prepended with wires corresponding to the public inputs
- extended_tables: formatted tables
- adapted_range_checks: range check with indexes shifted regarding public_inputs
*)
let preprocessing n domain (c : Circuit.t) =
let l = List.fold_left ( + ) c.public_input_size c.input_com_sizes in
let ql_name = Plompiler.Csir.linear_selector_name 0 in
(* Updating selectors for public inputs. *)
let gates =
(* Define ql if undefined as it is the gate taking the public input in. *)
if l > 0 && (not @@ SMap.mem ql_name c.gates) then
SMap.add
ql_name
(Array.init c.circuit_size (fun _ -> Scalar.zero))
c.gates
else c.gates
in
(* other preprocessed things in article are computed in prove of permutations *)
let extended_gates =
(* Adding 0s/1s for public inputs & input_commitments *)
SMap.mapi
(fun label gate ->
let length_poly = Array.length gate in
Array.init n (fun i ->
if i < l && label = ql_name then Scalar.one
else if l <= i && i < l + length_poly then gate.(i - l)
else Scalar.zero))
gates
in
let extended_gates, _, _ =
List.fold_left
(fun (acc, i, k) size ->
let acc =
SMap.add
("qcom" ^ string_of_int i)
(Array.init n (fun j ->
if j >= k && j < k + size then Scalar.(negate one)
else Scalar.zero))
acc
in
(acc, i + 1, k + size))
(extended_gates, 0, 0)
c.input_com_sizes
in
let interpolated_gates =
SMap.map (Evaluations.interpolation_fft2 domain) extended_gates
in
let extended_gates =
if c.public_input_size = 0 then extended_gates
else SMap.add_unique "qpub" [||] extended_gates
in
let extended_wires =
let li_array = Array.init l (fun i -> i) in
(* Adding public inputs and resizing *)
Array.map (fun w -> Array.pad (Array.append li_array w) n) c.wires
in
let adapted_range_checks =
SMap.map (List.map (fun (i, n) -> (l + i, n))) c.range_checks
in
let extended_tables =
if not c.ultra then []
else
Plook.format_tables
~tables:c.tables
~nb_columns:Plompiler.Csir.nb_wires_arch
~length_not_padded:c.table_size
~length_padded:n
in
( interpolated_gates,
extended_gates,
extended_wires,
extended_tables,
adapted_range_checks )
let preprocess_map domain domain_evals n circuits_map =
(* Preprocessing wires, gates and tables *)
SMap.fold
(fun circuit_name (circuit, _) (prv, vrf, all_g_maps) ->
(* Generating alpha for Plookup *)
let alpha =
if Circuit.(circuit.ultra) then Some (Scalar.random ()) else None
in
let gates_poly, gates, wires, tables, range_checks =
preprocessing n domain circuit
in
(* Generating permutation *)
let permutation = Perm.build_permutation wires in
let rc_permutations =
RangeCheck.build_permutations ~range_checks ~size_domain:n
in
let g_map_perm =
Perm.preprocessing
~domain
~nb_wires:Plompiler.Csir.nb_wires_arch
~permutation
()
in
let g_map_plook =
if circuit.ultra then Plook.preprocessing ~domain ~tables ~alpha ()
else SMap.empty
in
let g_map_range_check =
RangeCheck.preprocessing
~permutations:rc_permutations
~range_checks:circuit.range_checks
~domain
in
let circuit_g_map =
SMap.union_disjoint_list
[g_map_plook; g_map_perm; gates_poly; g_map_range_check]
|> SMap.Aggregation.prefix_map circuit_name
in
let evaluations =
Evaluations.compute_evaluations ~domain:domain_evals circuit_g_map
in
let prover_pp =
Prover.
{
circuit_size = circuit.circuit_size;
input_com_sizes = circuit.input_com_sizes;
public_input_size = circuit.public_input_size;
gates;
tables;
wires;
evaluations;
permutation;
rc_permutations;
alpha;
ultra = circuit.ultra;
range_checks;
}
in
let verifier_pp =
let gates = SMap.map (fun _ -> ()) gates in
Verifier.
{
gates;
alpha;
ultra = circuit.ultra;
input_com_sizes = circuit.input_com_sizes;
range_checks = SMap.map (fun rc -> rc <> []) range_checks;
}
in
( SMap.add_unique circuit_name prover_pp prv,
SMap.add_unique circuit_name verifier_pp vrf,
SMap.union_disjoint all_g_maps circuit_g_map ))
circuits_map
SMap.(empty, empty, empty)
let compute_sizes circuit_name
( Circuit.
{
public_input_size;
input_com_sizes;
circuit_size;
table_size;
nb_lookups;
ultra;
gates;
range_checks;
_;
},
nb_proofs ) =
(* Computing domain *)
(* For TurboPlonk, we want a domain of size a power of two
higher than or equal to the number of constraints plus public inputs.
As for UltraPlonk, a domain of size stricly higher than the number of constraints
(to be sure we pad the last lookup).
For range-checks, we want to ensure that the domain size is greater than the "size" of the "biggest" range-checks
*)
let nb_max_constraints =
let base_size =
circuit_size
+ List.fold_left ( + ) public_input_size input_com_sizes
+ if ultra then 1 else 0
in
let size_with_rc, biggest_rc_wire =
SMap.fold
(fun wire r (acc, rc_wire) ->
let sum_bounds =
(List.fold_left (fun sum (_, bound) -> sum + bound)) 0 r
in
if sum_bounds > acc then (sum_bounds, wire) else (acc, rc_wire))
range_checks
(base_size, "")
in
(* if the circuit size has been increased because of the range checks we raise a warning *)
let _print_warning =
match biggest_rc_wire with
| "" -> ()
| w ->
Printf.printf
"\n\
WARNING: Circuit %s's size has been increased to %d (initial \
size was %d) because of the range-checks on the %s wire."
circuit_name
size_with_rc
base_size
w
in
size_with_rc
in
(* For UltraPlonk, we want a domain of size a power of two
higher than the number of records and strictly higher than the number of lookups *)
let nb_rec_look = if ultra then max (nb_lookups + 1) table_size else 0 in
let max_nb = max nb_max_constraints nb_rec_look in
let log = Z.(log2up (of_int max_nb)) in
let n = Int.shift_left 1 log in
let pack_size =
(* L1, Ssi, selectors, ultra stuff *)
let nb_g_map_polys =
let ultra_polys = if ultra then 2 else 0 in
1 + Plompiler.Csir.nb_wires_arch + SMap.cardinal gates + ultra_polys
in
let nb_extra_polys = if ultra then 5 else 1 in
let online =
max Plompiler.Csir.nb_wires_arch nb_extra_polys * nb_proofs
in
(* 5 stands for the max number of T polynomials, if we split T *)
let offline = max nb_g_map_polys 5 in
(* Multiply by 2 to have more than needed, just in case *)
2 * max online offline
in
(log, n, pack_size)
let get_sizes ~zero_knowledge circuits_map =
let log, n, total_pack, some_ultra =
SMap.fold
(fun k (c, nb_proofs) (acc_log, acc_n, acc_pack_size, acc_ultra) ->
let log, n, pack_size = compute_sizes k (c, nb_proofs) in
( max acc_log log,
max acc_n n,
acc_pack_size + pack_size,
acc_ultra || c.ultra ))
circuits_map
(0, 0, 0, false)
in
let degree_evals =
SMap.fold
(fun _ ((c : Circuit.t), _) acc_deg_eval ->
let deg_eval =
degree_evaluations
~nb_wires:Plompiler.Csir.nb_wires_arch
~gates:c.gates
~ultra:c.ultra
in
max acc_deg_eval deg_eval)
circuits_map
0
in
let domain_evals = domain_evaluations ~zero_knowledge ~n degree_evals in
let domain = Domain.build_power_of_two log in
let total_pack = 1 lsl Z.(log2up (of_int total_pack)) in
(* Each t chunk should be of degree [n].
We remove 1 because t is divided by (X^n - 1) *)
let nb_of_t_chunks =
degree_evals + (if zero_knowledge then 1 else 0) - 1
in
(domain, n, total_pack, domain_evals, some_ultra, nb_of_t_chunks)
let setup_circuits ~zero_knowledge circuits_map ~srs =
let domain, n, pack_size, domain_evals, some_ultra, nb_of_t_chunks =
get_sizes ~zero_knowledge circuits_map
in
let evaluations =
(* Add X evaluations, which is the domain needed for other evaluations *)
let evaluations =
SMap.singleton "X" (Evaluations.of_domain domain_evals)
in
(* Add L₁, Sid₁, Sid₂, Sid₃, Sid₄, Sid₅ *)
let evaluations =
Perm.common_preprocessing
~nb_wires:Plompiler.Csir.nb_wires_arch
~domain
~evaluations
in
(* if ultra add L_n_plus_1 *)
if some_ultra then Plook.common_preprocessing ~n ~domain ~evaluations
else evaluations
in
let pp_prv, pp_vrf, g_map =
preprocess_map domain domain_evals n circuits_map
in
(* Generating public parameters *)
let pp_prover, pp_verifier = PP.setup ~setup_params:pack_size ~srs in
let cm_g, g_prover_aux = Commitment.commit pp_prover g_map in
(* Generating transcript *)
let transcript =
PP.PC.Public_parameters.to_bytes n pp_prover
|> Transcript.expand Commitment.t cm_g
in
let eval_points = eval_points pp_prv in
let common_prv =
Prover.
{
n;
domain;
pp_public_parameters = pp_prover;
evaluations;
g_map;
g_prover_aux;
zk = zero_knowledge;
nb_of_t_chunks;
eval_points;
}
in
let common_vrf =
Verifier.
{
n;
generator = Domain.get domain 1;
pp_public_parameters = pp_verifier;
cm_g;
eval_points;
}
in
( ({common_pp = common_prv; circuits_map = pp_prv; transcript}
: prover_public_parameters),
{common_pp = common_vrf; circuits_map = pp_vrf; transcript} )
end
let to_verifier_inputs (pp : prover_public_parameters) =
let extract name (secrets : circuit_prover_input list) :
public_inputs * Input_commitment.public list list =
let c = SMap.find name pp.circuits_map in
let ic_size = List.fold_left ( + ) 0 c.input_com_sizes in
let public_inputs =
List.map
(fun s -> Array.sub s.witness ic_size c.public_input_size)
secrets
in
let input_commitments =
List.map
(fun s ->
List.map
(fun (c : Input_commitment.t) -> c.public)
s.input_commitments)
secrets
in
(public_inputs, input_commitments)
in
SMap.mapi extract
let expand_transcript_with_verifier_inputs transcript verifier_inputs =
Transcript.expand
Repr.bytes
(hash_verifier_inputs verifier_inputs)
transcript
let input_commit ?size ?shift (pp : prover_public_parameters) secret =
Input_commitment.commit
?size
?shift
pp.common_pp.pp_public_parameters
pp.common_pp.n
secret
let update_prover_public_parameters x (pp : prover_public_parameters) =
{pp with transcript = Transcript.expand Repr.bytes x pp.transcript}
let update_verifier_public_parameters x (pp : verifier_public_parameters) =
{pp with transcript = Transcript.expand Repr.bytes x pp.transcript}
(* [filter_pp_circuits pp inputs_map] returns [pp]
without the circuits that don’t appear in [inputs_map]’s keys
*)
let filter_prv_pp_circuits (pp : prover_public_parameters) inputs_map =
check_circuits pp.circuits_map inputs_map ;
{pp with circuits_map = SMap.sub_map inputs_map pp.circuits_map}
let filter_vrf_pp_circuits pp inputs_map =
check_circuits pp.circuits_map inputs_map ;
{pp with circuits_map = SMap.sub_map inputs_map pp.circuits_map}
let setup ~zero_knowledge circuits_map ~srs =
check_circuit_name circuits_map ;
Preprocess.setup_circuits ~zero_knowledge circuits_map ~srs
(* inputs is a map between circuit names to list of public inputs *)
let prove (pp : prover_public_parameters) ~inputs =
check_circuit_name pp.circuits_map ;
(* add the verifier inputs to the transcript *)
let pp =
{
pp with
transcript =
expand_transcript_with_verifier_inputs
pp.transcript
(to_verifier_inputs pp inputs);
}
in
let pp_proof, (perm_and_plook, wires_cm, _) =
Prover.prove_parameters
~pp_prove:PP.prove
(filter_prv_pp_circuits pp inputs)
~inputs_map:inputs
in
{perm_and_plook; wires_cm; pp_proof}
let verify pp ~inputs proof =
check_circuit_name pp.circuits_map ;
let circuits_map = SMap.sub_map inputs pp.circuits_map in
(* add the verifier inputs to the transcript *)
let transcript =
expand_transcript_with_verifier_inputs pp.transcript inputs
in
let transcript, identities, _, commitments, eval_points =
Verifier.verify_parameters
((pp.common_pp, circuits_map), transcript)
inputs
proof
in
PP.verify
pp.common_pp.pp_public_parameters
transcript
~n:pp.common_pp.n
~generator:pp.common_pp.generator
~commitments
~identities
~eval_points
proof.pp_proof
|> fst
let scalar_encoding =
Data_encoding.(
conv
Scalar.to_bytes
Scalar.of_bytes_exn
(Fixed.bytes Scalar.size_in_bytes))
let data_encoding_of_repr repr =
Data_encoding.conv
(Plompiler.Utils.to_bytes repr)
(Plompiler.Utils.of_bytes repr)
Data_encoding.bytes
let proof_encoding = data_encoding_of_repr proof_t
let verifier_public_parameters_encoding =
data_encoding_of_repr verifier_public_parameters_t
module Internal_for_tests = struct
let mutate_vi verifier_inputs =
(* TODO we could do a more randomized search *)
let key, (public_inputs_list, input_commitments_list) =
SMap.choose verifier_inputs
in
match public_inputs_list with
| head :: tail ->
if head = [||] then None (* empty public inputs *)
else
let new_head = Array.copy head in
new_head.(0) <- Scalar.(add one new_head.(0)) ;
let public_inputs_list = new_head :: tail in
let inputs = (public_inputs_list, input_commitments_list) in
Some (SMap.add key inputs verifier_inputs)
| [] -> failwith "mutate_vi : all circuits should have verifier inputs."
end
end
module Make : functor (PP : Polynomial_protocol.S) ->
S with type public_inputs = Scalar.t array list =
Make_impl
include Make (Polynomial_protocol)