Source file dummy_zk_rollup.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
(** 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.
*)
open Plompiler
(** Helper types and modules *)
(** Empty types to represent bounds *)
type balance
type amount
type fee
type op_code
(** Bounds required for the dummy rollup. *)
module Bound : sig
type 'a t = private Z.t
val bound_balance : balance t
val bound_amount : amount t
val bound_fee : fee t
val bound_op_code : op_code t
val v : 'a t -> Z.t
end = struct
type 'a t = Z.t
(** These bounds are exclusive. *)
(** Upper bound for ticket balance, as found in the price field of an
operation's header *)
let bound_balance = Z.(shift_left one 20)
(** Upper bound for ticket amount, used for fee circuit *)
let bound_amount = Z.(shift_left one 20)
(** Upper bound for fee amount of one public operation *)
let bound_fee = Z.(shift_left one 10)
(** Upper bound for op code *)
let bound_op_code = Z.one
let v x = x
end
(** Modules to manipulate bounded integers, both as OCaml values and in circuit
representation.
*)
module Bounded = 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.
*)
module Types = struct
module P = struct
type state = bool
module Bounded = Bounded.P
type 'a ticket = {id : S.t; amount : 'a Bounded.t}
type tezos_pkh = Environment.Signature.Public_key_hash.t
type op = {header : header; payload : bool}
(** Dummy values for these types. Useful to get the circuit without having
the actual inputs. *)
module Dummy = struct
let op_code = Bounded.make ~bound:Bound.bound_op_code Z.zero
let balance = Bounded.make ~bound:Bound.bound_balance Z.zero
let tezos_pkh = Environment.Signature.Public_key_hash.zero
let ticket_balance = {id = S.zero; amount = balance}
let =
{
op_code;
price = ticket_balance;
l1_dst = tezos_pkh;
rollup_id = tezos_pkh;
}
end
end
module V (L : LIB) = struct
open L
module Bounded_u = Bounded.V (L)
type 'a ticket_u = {id : scalar repr; amount : 'a Bounded_u.t}
type tezos_pkh_u = scalar repr
type op_u = {header : header_u; payload : bool repr}
end
module Encodings (L : LIB) = struct
module Bounded_e = Bounded.Encoding (L)
open P
open V (L)
open L.Encodings
let op_code_encoding ~safety =
Bounded_e.encoding ~safety Bound.bound_op_code
let encoding_to_scalar e x =
let bs = Data_encoding.Binary.to_bytes_exn e x in
let z = Z.of_bits @@ Bytes.to_string bs in
Bls12_381.Fr.of_z z
let encoding_of_scalar e x =
let z = Bls12_381.Fr.to_z x in
let bs = Bytes.of_string @@ Z.to_bits z in
Data_encoding.Binary.of_bytes_exn e bs
let tezos_pkh_encoding : (tezos_pkh, tezos_pkh_u, _) encoding =
conv
(fun pkhu -> pkhu)
(fun w -> w)
(encoding_to_scalar Signature.Public_key_hash.encoding)
(encoding_of_scalar Signature.Public_key_hash.encoding)
scalar_encoding
let amount_encoding ~safety = Bounded_e.encoding ~safety Bound.bound_amount
let fee_encoding ~safety = Bounded_e.encoding ~safety Bound.bound_fee
let ticket_encoding ~safety (bound : 'a Bound.t) :
('a ticket, 'a ticket_u, _) encoding =
conv
(fun {id; amount} -> (id, amount))
(fun (id, amount) -> {id; amount})
(fun ({id; amount} : 'a ticket) -> (id, amount))
(fun (id, amount) -> {id; amount})
(obj2_encoding scalar_encoding (Bounded_e.encoding ~safety bound))
let ticket_balance_encoding ~safety =
ticket_encoding ~safety Bound.bound_balance
let ~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)
(ticket_balance_encoding ~safety:Unsafe)
tezos_pkh_encoding
tezos_pkh_encoding)
let op_encoding : (op, op_u, _) encoding =
conv
(fun {; payload} -> (header, payload))
(fun (, payload) -> {header; payload})
(fun ({; payload} : op) -> (header, payload))
(fun (, payload) -> {header; payload})
(obj2_encoding (header_encoding ~safety:NoCheck) bool_encoding)
end
end
(** Plompiler circuits for the dummy rollup *)
module V (L : LIB) = struct
open L
module E = Types.Encodings (L)
module Encodings = L.Encodings
open Encodings
open Types.V (L)
let coerce (type a) (x : a Bounded_u.t) =
fst (x : a Bounded_u.t :> scalar repr * Z.t)
(** Common logic for the state transition function *)
let logic_op ~old_state ~rollup_id op =
ignore rollup_id ;
let* valid = equal old_state op.payload in
let* new_state = Bool.bnot old_state in
let* expected_new_state = Bool.ifthenelse valid new_state old_state in
Num.assert_eq_const (coerce op.header.op_code) S.zero
>* ret expected_new_state
(** Circuit definition for one public operation *)
let predicate_op ?(kind = `Public) ~old_state ~new_state ~fee ~exit_validity
~rollup_id op =
let* old_state = input ~kind:`Public @@ Input.bool old_state in
let* new_state = input ~kind:`Public @@ Input.bool new_state in
let* (_fee : scalar repr) =
input ~kind:`Public
@@ E.((fee_encoding ~safety:Bounded_e.Unsafe).input) fee
in
let* (_exit_validity : bool repr) =
input ~kind:`Public @@ Input.bool exit_validity
in
let* rollup_id =
input ~kind:`Public @@ E.(tezos_pkh_encoding.input) rollup_id
in
let* op = input ~kind @@ E.op_encoding.input op in
let op = E.op_encoding.decode op in
let* expected_new_state = logic_op ~old_state ~rollup_id op in
assert_equal expected_new_state new_state
(** Circuit definition for a batch of private operations *)
let predicate_batch ~old_state ~new_state ~fees ~rollup_id ops =
let* old_state = input ~kind:`Public @@ Input.bool old_state in
let* new_state = input ~kind:`Public @@ Input.bool new_state in
let* (_fees : scalar repr) =
input ~kind:`Public
@@ E.((amount_encoding ~safety:Bounded_e.Unsafe).input) fees
in
let* rollup_id =
input ~kind:`Public @@ E.(tezos_pkh_encoding.input) rollup_id
in
let* ops = input @@ (Encodings.list_encoding E.op_encoding).input ops in
let ops = (Encodings.list_encoding E.op_encoding).decode ops in
let* computed_final_state =
foldM
(fun old_state op -> logic_op ~old_state ~rollup_id op)
old_state
ops
in
assert_equal computed_final_state new_state
(** Fee circuit *)
let predicate_fees ~old_state ~new_state ~fees =
let* old_state = input ~kind:`Public @@ Input.bool old_state in
let* new_state = input ~kind:`Public @@ Input.bool new_state in
let* (_fees : scalar repr) =
input ~kind:`Public
@@ E.((amount_encoding ~safety:Bounded_e.Unsafe).input) fees
in
assert_equal old_state new_state
end
(** Basic rollup operator for generating Updates. *)
module Operator (Params : sig
val batch_size : int
end) : sig
open Protocol.Alpha_context
(** Initial state of the rollup *)
val init_state : Zk_rollup.State.t
(** Map associating every circuit identifier to its kind *)
val circuits : [`Public | `Private | `Fee] Kzg.SMap.t
(** Commitment to the circuits *)
val lazy_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. *)
val craft_update :
Zk_rollup.State.t ->
zk_rollup:Zk_rollup.t ->
?private_ops:Zk_rollup.Operation.t list list ->
?exit_validities:bool list ->
Zk_rollup.Operation.t list ->
Zk_rollup.State.t * Zk_rollup.Update.t
module Internal_for_tests : sig
val true_op : Zk_rollup.Operation.t
val false_op : Zk_rollup.Operation.t
val pending : Zk_rollup.Operation.t list
val private_ops : Zk_rollup.Operation.t list list
val lazy_update_data : Zk_rollup.Update.t lazy_t
end
end = struct
open Protocol.Alpha_context
module SMap = Kzg.SMap
module Dummy = Types.P.Dummy
module T = Types.P
module VC = V (LibCircuit)
let lazy_srs =
lazy
(let open Octez_bls12_381_polynomial in
(Srs.generate_insecure 9 1, Srs.generate_insecure 1 1))
let dummy_l1_dst =
Hex.to_bytes_exn (`Hex "0002298c03ed7d454a101eb7022bc95f7e5f41ac78")
let dummy_rollup_id =
let address =
Zk_rollup.Address.of_b58check_exn "epx18RJJqrYuJQqhB636BWvukU3XBNQGbtm8C"
in
Data_encoding.Binary.to_bytes_exn Zk_rollup.Address.encoding address
let dummy_ticket_hash = Bytes.make 32 '0'
let of_proto_state : Zk_rollup.State.t -> Types.P.state =
fun s -> Bls12_381.Fr.is_one s.(0)
let to_proto_state : Types.P.state -> Zk_rollup.State.t =
fun s -> if s then [|Bls12_381.Fr.one|] else [|Bls12_381.Fr.zero|]
let dummy_op = T.{header = Dummy.header; payload = false}
let batch_name = "batch-" ^ string_of_int Params.batch_size
let circuit_map =
let get_circuit _name c =
let r = LibCircuit.get_cs ~optimize:true c in
(Plonk.Circuit.to_plonk r, r.public_input_size, r.solver)
in
SMap.of_list
@@ List.map
(fun (n, c) -> (n, get_circuit n c))
[
( "op",
VC.predicate_op
~old_state:false
~new_state:true
~fee:(T.Bounded.make ~bound:Bound.bound_fee Z.zero)
~exit_validity:false
~rollup_id:Dummy.tezos_pkh
dummy_op );
( batch_name,
VC.predicate_batch
~old_state:false
~new_state:true
~fees:(T.Bounded.make ~bound:Bound.bound_amount Z.zero)
~rollup_id:Dummy.tezos_pkh
(Stdlib.List.init Params.batch_size (Fun.const dummy_op)) );
( "fee",
VC.predicate_fees
~old_state:false
~new_state:false
~fees:(T.Bounded.make ~bound:Bound.bound_amount Z.zero) );
]
let circuits =
SMap.(add "op" `Public @@ add batch_name `Private @@ add "fee" `Fee empty)
let lazy_pp =
lazy
(let srs = Lazy.force lazy_srs in
Plonk.Main_protocol.setup
~zero_knowledge:false
(SMap.map (fun (a, b, _) -> (a, b)) circuit_map)
~srs)
let insert s x m =
match SMap.find_opt s m with
| None -> SMap.add s [x] m
| Some l -> SMap.add s (x :: l) m
let craft_update :
Zk_rollup.State.t ->
zk_rollup:Zk_rollup.t ->
?private_ops:Zk_rollup.Operation.t list list ->
?exit_validities:bool list ->
Zk_rollup.Operation.t list ->
Zk_rollup.State.t * Zk_rollup.Update.t =
fun s ~zk_rollup ?(private_ops = []) ?exit_validities pending ->
let prover_pp, public_parameters = Lazy.force lazy_pp in
let s = of_proto_state s in
let rev_inputs = SMap.empty in
let exit_validities =
match exit_validities with
| None -> List.map (Fun.const true) pending
| Some l ->
assert (List.length l = List.length pending) ;
l
in
let _circ, _pi_size, op_solver = SMap.find "op" circuit_map in
let s, rev_inputs, rev_pending_pis =
Stdlib.List.fold_left2
(fun (s, rev_inputs, rev_pending_pis) op exit_validity ->
let new_state =
if s = of_proto_state Zk_rollup.Operation.(op.payload) then not s
else s
in
let fee = Bls12_381.Fr.zero in
let pi_to_send =
Zk_rollup.Update.
{new_state = to_proto_state new_state; fee; exit_validity}
in
let exit_validity_s =
if exit_validity then Bls12_381.Fr.one else Bls12_381.Fr.zero
in
let public_inputs =
Array.concat
[
to_proto_state s;
to_proto_state new_state;
[|fee; exit_validity_s; Zk_rollup.to_scalar zk_rollup|];
Zk_rollup.Operation.to_scalar_array op;
]
in
let private_inputs = Solver.solve op_solver public_inputs in
( new_state,
insert
"op"
Plonk.Main_protocol.
{witness = private_inputs; input_commitments = []}
rev_inputs,
("op", pi_to_send) :: rev_pending_pis ))
(s, rev_inputs, [])
pending
exit_validities
in
let pending_pis = List.rev rev_pending_pis in
let _circ, _pi_size, batch_solver = SMap.find batch_name circuit_map in
let s, rev_inputs, rev_private_pis =
if private_ops = [] then (s, rev_inputs, [])
else
List.fold_left
(fun (s, rev_inputs, rev_private_pis) batch ->
let new_state =
List.fold_left
(fun s op ->
if s = of_proto_state Zk_rollup.Operation.(op.payload) then
not s
else s)
s
batch
in
let fees = Bls12_381.Fr.zero in
let pi_to_send : Zk_rollup.Update.private_inner_pi =
Zk_rollup.Update.{new_state = to_proto_state new_state; fees}
in
let public_inputs =
Array.concat
[
to_proto_state s;
to_proto_state new_state;
[|fees; Zk_rollup.to_scalar zk_rollup|];
]
in
let initial =
Array.concat
([public_inputs]
@ List.map Zk_rollup.Operation.to_scalar_array batch)
in
let private_inputs = Solver.solve batch_solver initial in
( new_state,
insert
batch_name
Plonk.Main_protocol.
{witness = private_inputs; input_commitments = []}
rev_inputs,
(batch_name, pi_to_send) :: rev_private_pis ))
(s, rev_inputs, [])
private_ops
in
let private_pis = List.rev rev_private_pis in
let _circ, _pi_size, fee_solver = SMap.find "fee" circuit_map in
let rev_inputs, fee_pi =
let fee_pi = Zk_rollup.Update.{new_state = to_proto_state s} in
let fees = Bls12_381.Fr.zero in
let public_inputs =
Array.concat [to_proto_state s; to_proto_state s; [|fees|]]
in
let private_inputs = Solver.solve fee_solver public_inputs in
( insert
"fee"
Plonk.Main_protocol.{witness = private_inputs; input_commitments = []}
rev_inputs,
fee_pi )
in
let inputs = SMap.map List.rev rev_inputs in
let proof = Plonk.Main_protocol.prove prover_pp ~inputs in
let verifier_inputs =
Plonk.Main_protocol.to_verifier_inputs prover_pp inputs
in
assert (
Plonk.Main_protocol.verify public_parameters ~inputs:verifier_inputs proof) ;
( to_proto_state s,
Zk_rollup.Update.{pending_pis; private_pis; fee_pi; proof} )
let init_state = to_proto_state false
module Internal_for_tests = struct
let true_op =
Zk_rollup.Operation.
{
op_code = 0;
price =
(let id =
Data_encoding.Binary.of_bytes_exn
Ticket_hash.encoding
dummy_ticket_hash
in
{id; amount = Z.zero});
l1_dst =
Data_encoding.Binary.of_bytes_exn
Signature.Public_key_hash.encoding
dummy_l1_dst;
rollup_id =
Data_encoding.Binary.of_bytes_exn
Zk_rollup.Address.encoding
dummy_rollup_id;
payload = [|Bls12_381.Fr.one|];
}
let false_op = {true_op with payload = [|Bls12_381.Fr.zero|]}
let pending = [false_op; true_op; true_op]
let n_batches = 2
let private_ops =
Stdlib.List.init n_batches @@ Fun.const
@@ Stdlib.List.init Params.batch_size (fun i ->
if i mod 2 = 0 then false_op else true_op)
let lazy_update_data =
lazy
(snd
@@ craft_update
init_state
~zk_rollup:
(Data_encoding.Binary.of_bytes_exn
Zk_rollup.Address.encoding
dummy_rollup_id)
~private_ops
pending)
end
end