123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632(*****************************************************************************)(* *)(* Open Source 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. *)(* *)(*****************************************************************************)(** Dummy ZK Rollup for testing the ZKRU integration in the protocol.
The library Plompiler is used to build the circuits (in a module V as
verifier) and the corresponding functions to produce the inputs for the
circuits (in a module P as prover).
The state of this rollup is a boolean value, which will be
represented with a scalar value of [zero] for [false] and
[one] for [true].
This RU has only one operation, with [op_code] 0. In addition to the
common header (see {!Zk_rollup_operation_repr}), this operation has
as payload one scalar representing a boolean value.
The transition function [f] for this rollup is:
{[
f : operation -> state -> state
f (Op b) s = if b = s then not s else s
]}
That is, the state bool is flipped only if the operation's payload is
equal to the current state.
The operation can be used publicly or in a private batch. The circuits
that describe the RU are:
- ["op"]: for a single public operation.
- ["batch-"[N]]: for a batch of [N] private operations. [N] is determined
by the [batch_size] parameter to the [Operator] functor.
- ["fee"]: the trivial fees circuit, since this RU has no concept of fees.
NB: the "op" circuit does not add any constraints over the operation's
[exit_validity] other than it being in [{0, 1}]. This means that the dummy
rollup can be used to test deposits/withdrawals, but the rollup will not
perform any monetary bookkeeping.
*)openPlompiler(** Helper types and modules *)(** Empty types to represent bounds *)typebalancetypeamounttypefeetypeop_code(** Bounds required for the dummy rollup. *)moduleBound:sigtype'at=privateZ.tvalbound_balance:balancetvalbound_amount:amounttvalbound_fee:feetvalbound_op_code:op_codetvalv:'at->Z.tend=structtype'at=Z.t(** These bounds are exclusive. *)(** Upper bound for ticket balance, as found in the price field of an
operation's header *)letbound_balance=Z.(shift_leftone20)(** Upper bound for ticket amount, used for fee circuit *)letbound_amount=Z.(shift_leftone20)(** Upper bound for fee amount of one public operation *)letbound_fee=Z.(shift_leftone10)(** Upper bound for op code *)letbound_op_code=Z.oneletvx=xend(** Modules to manipulate bounded integers, both as OCaml values and in circuit
representation.
*)moduleBounded=Bounded.Make(Bound)(** Types used for the Dummy Rollup circuits.
This module is split into:
- P: concrete OCaml version of the types,
- V: Plompiler's circuit representation for P's types, and
- Encodings: conversion between P and V.
*)moduleTypes=structmoduleP=structtypestate=boolmoduleBounded=Bounded.Ptype'aticket={id:S.t;amount:'aBounded.t}typetezos_pkh=Environment.Signature.Public_key_hash.ttypeheader={op_code:op_codeBounded.t;price:balanceticket;l1_dst:tezos_pkh;rollup_id:tezos_pkh;}typeop={header:header;payload:bool}(** Dummy values for these types. Useful to get the circuit without having
the actual inputs. *)moduleDummy=structletop_code=Bounded.make~bound:Bound.bound_op_codeZ.zeroletbalance=Bounded.make~bound:Bound.bound_balanceZ.zerolettezos_pkh=Environment.Signature.Public_key_hash.zeroletticket_balance={id=S.zero;amount=balance}letheader={op_code;price=ticket_balance;l1_dst=tezos_pkh;rollup_id=tezos_pkh;}endendmoduleV(L:LIB)=structopenLmoduleBounded_u=Bounded.V(L)type'aticket_u={id:scalarrepr;amount:'aBounded_u.t}typetezos_pkh_u=scalarreprtypeheader_u={op_code:op_codeBounded_u.t;price:balanceticket_u;l1_dst:tezos_pkh_u;rollup_id:tezos_pkh_u;}typeop_u={header:header_u;payload:boolrepr}endmoduleEncodings(L:LIB)=structmoduleBounded_e=Bounded.Encoding(L)openPopenV(L)openL.Encodingsletop_code_encoding~safety=Bounded_e.encoding~safetyBound.bound_op_codeletencoding_to_scalarex=letbs=Data_encoding.Binary.to_bytes_exnexinletz=Z.of_bits@@Bytes.to_stringbsinBls12_381.Fr.of_zzletencoding_of_scalarex=letz=Bls12_381.Fr.to_zxinletbs=Bytes.of_string@@Z.to_bitszinData_encoding.Binary.of_bytes_exnebslettezos_pkh_encoding:(tezos_pkh,tezos_pkh_u,_)encoding=conv(funpkhu->pkhu)(funw->w)(encoding_to_scalarSignature.Public_key_hash.encoding)(encoding_of_scalarSignature.Public_key_hash.encoding)scalar_encodingletamount_encoding~safety=Bounded_e.encoding~safetyBound.bound_amountletfee_encoding~safety=Bounded_e.encoding~safetyBound.bound_feeletticket_encoding~safety(bound:'aBound.t):('aticket,'aticket_u,_)encoding=conv(fun{id;amount}->(id,amount))(fun(id,amount)->{id;amount})(fun({id;amount}:'aticket)->(id,amount))(fun(id,amount)->{id;amount})(obj2_encodingscalar_encoding(Bounded_e.encoding~safetybound))letticket_balance_encoding~safety=ticket_encoding~safetyBound.bound_balanceletheader_encoding~safety:(header,header_u,_)encoding=conv(fun{op_code;price;l1_dst;rollup_id}->(op_code,(price,(l1_dst,rollup_id))))(fun(op_code,(price,(l1_dst,rollup_id)))->{op_code;price;l1_dst;rollup_id})(fun({op_code;price;l1_dst;rollup_id}:header)->(op_code,(price,(l1_dst,rollup_id))))(fun(op_code,(price,(l1_dst,rollup_id)))->{op_code;price;l1_dst;rollup_id})(obj4_encoding(op_code_encoding~safety)(* We use an Unsafe Bounded scalar encoding here to be able to
detect that an out-of-range value has been passed.
This encoding is unsafe in the sense that such value will cause
a failure in proving, instead of a circuit that can prove that
the argument is out-of-range.
This is enough for Protocol testing purposes, while keeping
the dummy circuit small.
*)(ticket_balance_encoding~safety:Unsafe)tezos_pkh_encodingtezos_pkh_encoding)letop_encoding:(op,op_u,_)encoding=conv(fun{header;payload}->(header,payload))(fun(header,payload)->{header;payload})(fun({header;payload}:op)->(header,payload))(fun(header,payload)->{header;payload})(obj2_encoding(header_encoding~safety:NoCheck)bool_encoding)endend(** Plompiler circuits for the dummy rollup *)moduleV(L:LIB)=structopenLmoduleE=Types.Encodings(L)moduleEncodings=L.EncodingsopenEncodingsopenTypes.V(L)letcoerce(typea)(x:aBounded_u.t)=fst(x:aBounded_u.t:>scalarrepr*Z.t)(** Common logic for the state transition function *)letlogic_op~old_state~rollup_idop=ignorerollup_id;let*valid=equalold_stateop.payloadinlet*new_state=Bool.bnotold_stateinlet*expected_new_state=Bool.ifthenelsevalidnew_stateold_stateinNum.assert_eq_const(coerceop.header.op_code)S.zero(* >* assert_equal rollup_id op.header.rollup_id *)>*retexpected_new_state(** Circuit definition for one public operation *)letpredicate_op?(kind=`Public)~old_state~new_state~fee~exit_validity~rollup_idop=let*old_state=input~kind:`Public@@Input.boolold_stateinlet*new_state=input~kind:`Public@@Input.boolnew_stateinlet*(_fee:scalarrepr)=input~kind:`Public@@E.((fee_encoding~safety:Bounded_e.Unsafe).input)feeinlet*(_exit_validity:boolrepr)=input~kind:`Public@@Input.boolexit_validityinlet*rollup_id=input~kind:`Public@@E.(tezos_pkh_encoding.input)rollup_idinlet*op=input~kind@@E.op_encoding.inputopinletop=E.op_encoding.decodeopinlet*expected_new_state=logic_op~old_state~rollup_idopinassert_equalexpected_new_statenew_state(** Circuit definition for a batch of private operations *)letpredicate_batch~old_state~new_state~fees~rollup_idops=let*old_state=input~kind:`Public@@Input.boolold_stateinlet*new_state=input~kind:`Public@@Input.boolnew_stateinlet*(_fees:scalarrepr)=input~kind:`Public@@E.((amount_encoding~safety:Bounded_e.Unsafe).input)feesinlet*rollup_id=input~kind:`Public@@E.(tezos_pkh_encoding.input)rollup_idinlet*ops=input@@(Encodings.list_encodingE.op_encoding).inputopsinletops=(Encodings.list_encodingE.op_encoding).decodeopsinlet*computed_final_state=foldM(funold_stateop->logic_op~old_state~rollup_idop)old_stateopsinassert_equalcomputed_final_statenew_state(** Fee circuit *)letpredicate_fees~old_state~new_state~fees=let*old_state=input~kind:`Public@@Input.boolold_stateinlet*new_state=input~kind:`Public@@Input.boolnew_stateinlet*(_fees:scalarrepr)=input~kind:`Public@@E.((amount_encoding~safety:Bounded_e.Unsafe).input)feesinassert_equalold_statenew_stateend(** Basic rollup operator for generating Updates. *)moduleOperator(Params:sigvalbatch_size:intend):sigopenProtocol.Alpha_context(** Initial state of the rollup *)valinit_state:Zk_rollup.State.t(** Map associating every circuit identifier to its kind *)valcircuits:[`Public|`Private|`Fee]Plonk.SMap.t(** Commitment to the circuits *)vallazy_pp:(Plonk.Main_protocol.prover_public_parameters*Plonk.Main_protocol.verifier_public_parameters)lazy_t(** [craft_update state ~zk_rollup ?private_ops ?exit_validities public_ops]
will apply first the [public_ops], then the [private_ops]. While doing so,
the public inputs for every circuit will be collected. A Plonk proof of
correctness of the application these operations is created. *)valcraft_update:Zk_rollup.State.t->zk_rollup:Zk_rollup.t->?private_ops:Zk_rollup.Operation.tlistlist->?exit_validities:boollist->Zk_rollup.Operation.tlist->Zk_rollup.State.t*Zk_rollup.Update.tmoduleInternal_for_tests:sigvaltrue_op:Zk_rollup.Operation.tvalfalse_op:Zk_rollup.Operation.tvalpending:Zk_rollup.Operation.tlistvalprivate_ops:Zk_rollup.Operation.tlistlistvallazy_update_data:Zk_rollup.Update.tlazy_tendend=structopenProtocol.Alpha_contextmoduleSMap=Plonk.SMapmoduleDummy=Types.P.DummymoduleT=Types.PmoduleVC=V(LibCircuit)letlazy_srs=lazy(letopenOctez_bls12_381_polynomialin(Srs.generate_insecure91,Srs.generate_insecure11))letdummy_l1_dst=Hex.to_bytes_exn(`Hex"0002298c03ed7d454a101eb7022bc95f7e5f41ac78")letdummy_rollup_id=letaddress=Zk_rollup.Address.of_b58check_exn"epx18RJJqrYuJQqhB636BWvukU3XBNQGbtm8C"inData_encoding.Binary.to_bytes_exnZk_rollup.Address.encodingaddressletdummy_ticket_hash=Bytes.make32'0'letof_proto_state:Zk_rollup.State.t->Types.P.state=funs->Bls12_381.Fr.is_ones.(0)letto_proto_state:Types.P.state->Zk_rollup.State.t=funs->ifsthen[|Bls12_381.Fr.one|]else[|Bls12_381.Fr.zero|]letdummy_op=T.{header=Dummy.header;payload=false}letbatch_name="batch-"^string_of_intParams.batch_size(* Circuits that define the rollup, alongside their public input size and
solver *)letcircuit_map=letget_circuit_namec=letr=LibCircuit.get_cs~optimize:truecin(Plonk.Circuit.to_plonkr,r.public_input_size,r.solver)inSMap.of_list@@List.map(fun(n,c)->(n,get_circuitnc))[("op",VC.predicate_op~old_state:false~new_state:true~fee:(T.Bounded.make~bound:Bound.bound_feeZ.zero)~exit_validity:false~rollup_id:Dummy.tezos_pkhdummy_op);(batch_name,VC.predicate_batch~old_state:false~new_state:true~fees:(T.Bounded.make~bound:Bound.bound_amountZ.zero)~rollup_id:Dummy.tezos_pkh(Stdlib.List.initParams.batch_size(Fun.constdummy_op)));("fee",VC.predicate_fees~old_state:false~new_state:false~fees:(T.Bounded.make~bound:Bound.bound_amountZ.zero));]letcircuits=SMap.(add"op"`Public@@addbatch_name`Private@@add"fee"`Feeempty)letlazy_pp=lazy(letsrs=Lazy.forcelazy_srsinPlonk.Main_protocol.setup~zero_knowledge:false(SMap.map(fun(a,b,_)->(a,b))circuit_map)~srs)letinsertsxm=matchSMap.find_optsmwith|None->SMap.adds[x]m|Somel->SMap.adds(x::l)mletcraft_update:Zk_rollup.State.t->zk_rollup:Zk_rollup.t->?private_ops:Zk_rollup.Operation.tlistlist->?exit_validities:boollist->Zk_rollup.Operation.tlist->Zk_rollup.State.t*Zk_rollup.Update.t=funs~zk_rollup?(private_ops=[])?exit_validitiespending->letprover_pp,public_parameters=Lazy.forcelazy_ppinlets=of_proto_statesinletrev_inputs=SMap.emptyinletexit_validities=matchexit_validitieswith|None->List.map(Fun.consttrue)pending|Somel->assert(List.lengthl=List.lengthpending);linlet_circ,_pi_size,op_solver=SMap.find"op"circuit_mapin(* Process the public operations *)lets,rev_inputs,rev_pending_pis=Stdlib.List.fold_left2(fun(s,rev_inputs,rev_pending_pis)opexit_validity->letnew_state=ifs=of_proto_stateZk_rollup.Operation.(op.payload)thennotselsesinletfee=Bls12_381.Fr.zeroinletpi_to_send=Zk_rollup.Update.{new_state=to_proto_statenew_state;fee;exit_validity}inletexit_validity_s=ifexit_validitythenBls12_381.Fr.oneelseBls12_381.Fr.zeroinletpublic_inputs=Array.concat[to_proto_states;to_proto_statenew_state;[|fee;exit_validity_s;Zk_rollup.to_scalarzk_rollup|];Zk_rollup.Operation.to_scalar_arrayop;]inletprivate_inputs=Solver.solveop_solverpublic_inputsin(new_state,insert"op"Plonk.Main_protocol.{witness=private_inputs;input_commitments=[]}rev_inputs,("op",pi_to_send)::rev_pending_pis))(s,rev_inputs,[])pendingexit_validitiesinletpending_pis=List.revrev_pending_pisinlet_circ,_pi_size,batch_solver=SMap.findbatch_namecircuit_mapin(* Process the private operation batches *)lets,rev_inputs,rev_private_pis=ifprivate_ops=[]then(s,rev_inputs,[])elseList.fold_left(fun(s,rev_inputs,rev_private_pis)batch->letnew_state=List.fold_left(funsop->ifs=of_proto_stateZk_rollup.Operation.(op.payload)thennotselses)sbatchinletfees=Bls12_381.Fr.zeroinletpi_to_send:Zk_rollup.Update.private_inner_pi=Zk_rollup.Update.{new_state=to_proto_statenew_state;fees}inletpublic_inputs=Array.concat[to_proto_states;to_proto_statenew_state;[|fees;Zk_rollup.to_scalarzk_rollup|];]inletinitial=Array.concat([public_inputs]@List.mapZk_rollup.Operation.to_scalar_arraybatch)inletprivate_inputs=Solver.solvebatch_solverinitialin(new_state,insertbatch_namePlonk.Main_protocol.{witness=private_inputs;input_commitments=[]}rev_inputs,(batch_name,pi_to_send)::rev_private_pis))(s,rev_inputs,[])private_opsinletprivate_pis=List.revrev_private_pisin(* Dummy fee circuit *)let_circ,_pi_size,fee_solver=SMap.find"fee"circuit_mapinletrev_inputs,fee_pi=letfee_pi=Zk_rollup.Update.{new_state=to_proto_states}inletfees=Bls12_381.Fr.zeroinletpublic_inputs=Array.concat[to_proto_states;to_proto_states;[|fees|]]inletprivate_inputs=Solver.solvefee_solverpublic_inputsin(insert"fee"Plonk.Main_protocol.{witness=private_inputs;input_commitments=[]}rev_inputs,fee_pi)inletinputs=SMap.mapList.revrev_inputsinletproof=Plonk.Main_protocol.proveprover_pp~inputsinletverifier_inputs=Plonk.Main_protocol.to_verifier_inputsprover_ppinputsinassert(Plonk.Main_protocol.verifypublic_parameters~inputs:verifier_inputsproof);(to_proto_states,Zk_rollup.Update.{pending_pis;private_pis;fee_pi;proof})letinit_state=to_proto_statefalsemoduleInternal_for_tests=structlettrue_op=Zk_rollup.Operation.{op_code=0;price=(letid=Data_encoding.Binary.of_bytes_exnTicket_hash.encodingdummy_ticket_hashin{id;amount=Z.zero});l1_dst=Data_encoding.Binary.of_bytes_exnSignature.Public_key_hash.encodingdummy_l1_dst;rollup_id=Data_encoding.Binary.of_bytes_exnZk_rollup.Address.encodingdummy_rollup_id;payload=[|Bls12_381.Fr.one|];}letfalse_op={true_opwithpayload=[|Bls12_381.Fr.zero|]}letpending=[false_op;true_op;true_op]letn_batches=2letprivate_ops=Stdlib.List.initn_batches@@Fun.const@@Stdlib.List.initParams.batch_size(funi->ifimod2=0thenfalse_opelsetrue_op)letlazy_update_data=lazy(snd@@craft_updateinit_state~zk_rollup:(Data_encoding.Binary.of_bytes_exnZk_rollup.Address.encodingdummy_rollup_id)~private_opspending)endend