kimchi/circuits/polynomials/turshi.rs
1//! This implements the constraints of the Cairo gates
2//!
3//! Cairo programs can have the following assembly-like instructions:
4//! - Memory access: \[x\]
5//! - Assert equal: <`left_hand_op`> = <`right_hand_op`>
6//! · val
7//! · \[reg1 + off_op1\]
8//! · \[reg0 + off_op0\] +|* \[reg1 + off_op1\]
9//! · \[reg0 + off_op0\] +|* val
10//! · \[\[reg0 + off_op0\] + off_op1\]
11//! - Jumps
12//! · jmp abs <`address`> // unconditional absolute jump
13//! · jmp rel <`offset`> // unconditional relative jump
14//! · jmp rel <`offset`> if <`op`> != 0 // conditional jump
15//! - Functions
16//! · call abs <`address`> // calls a function (absolute location)
17//! · call rel <`offset`> // calls a function (relative location)
18//! · ret // returns to execution after the call
19//! - Increments
20//! · ap += <`op`>
21//! · ap++
22//!
23//! A Cairo program runs across a number of state transitions.
24//! Each state transition has the following structure:
25//!
26//! * Has access to a read-only memory
27//! * Input: 3 types of registers
28//! - pc (= program counter): address of current instruction
29//! - ap (= allocation pointer): first free memory address
30//! - fp (= frame pointer): beginning of stack (for function arguments)
31//! * Output:
32//! - `next_pc`: address of next instruction
33//! - `next_ap`: address of next free memory slot
34//! - `next_fp`: pointer to stack (can remain the same as fp)
35//!
36//! Cairo words are field elements of characteristic > 2^64
37//! Cairo instructions are stored as words (63 or 64 bits - actual instruction or immediate value)
38//! Instructions with immediate values are stored in 2 words
39//! - The first word stores instruction
40//! - The second word stores the value
41//!
42//! Words of instructions consist of
43//! * 3 signed offsets of 16 bits each, in the range [-2^15,2^15) biased representation
44//! - `off_dst` (= offset from destination address): used to compute address of assignment
45//! - `off_op0` (= offset from first operand): used to compute address of first operand in instruction
46//! - `off_op1` (= offset from second operand): used to compute address of second operand in instruction
47//! * 15 bits of flags divided into 7 groups
48//! When multiple bits, at most one can be 1 and the rest must be 0
49//! - `dst_reg` \[0\] = fDST_REG : indicates what pointer `off_dst` refers to ( 0 => ap , 1 => fp )
50//! - `op0_reg` \[1\] = fOP0_REG : indicates what pointer `off_op0` refers to ( 0 => ap , 1 => fp )
51//! - `op1_src` \[2..4\] : encodes the type of second operand
52//! · 0: indicates `off_op1` is b in the double indexing \[\[ point + a \] + b \]
53//! · 1: indicates `off_op1` is an immediate value = `fOP1_VAL` = 1
54//! · 2: indicates offset `off_op1` relative to fp = `fOP1_FP` = 1
55//! · 4: indicates offset `off_op1` relative to ap = `fOP1_AP` = 1
56//! - `res_logic` \[5..6\]: defines (if any) arithmetic operation in right part
57//! · 0: right part is single operand
58//! · 1: right part is addition = `fRES_ADD` = 1
59//! · 2: right part is multiplication = `fRES_MUL` = 1
60//! - `pc_update` \[7..9\]: defines the type of update for the pc
61//! · 0 = regular increase by size of current instruction
62//! · 1 = absolute jump to res address = `fPC_ABS_JMP` = 1
63//! · 2 = relative jump of step res = `fPC_REL_JMP` = 1
64//! · 4 = conditional jump (jnz) with step in op1 = `fPC_JNZ` = 1
65//! - `ap_update` \[10..11\]: defines the type of update for the ap
66//! · 0: means the new ap is the same, same free position
67//! · 1: means there is an ap+=<`op`> instruction = `fAP_INC` = 1
68//! · 2: means there is an ap++ instruction = `fAP_ADD1` = 1
69//! - opcode \[12..14\]: encodes type of assembly instruction
70//! · 0: jumps or increments instruction
71//! · 1: call instruction = `fOPC_CALL` = 1
72//! · 2: return instruction = `fOPC_RET` = 1
73//! · 4: assert equal instruction (assignment to value or check equality) = `fOPC_ASSEQ` = 1
74//! * in little-endian form = leftmost least significant bit
75//!
76//! The transition function uses 4 auxiliary values:
77//! - dst: left part of instruction, destination
78//! - op0: content of first operand of right part
79//! - op1: content of second operand of right part
80//! - res: result of the operation in the right part
81
82use crate::{
83 alphas::Alphas,
84 circuits::{
85 argument::{Argument, ArgumentEnv, ArgumentType},
86 berkeley_columns::{BerkeleyChallengeTerm, BerkeleyChallenges, Column, E},
87 constraints::ConstraintSystem,
88 expr::{self, constraints::ExprOps, Cache},
89 gate::{CircuitGate, GateType},
90 wires::{GateWires, Wire, COLUMNS},
91 },
92 curve::KimchiCurve,
93 proof::ProofEvaluations,
94};
95use ark_ff::{FftField, Field, PrimeField};
96use core::{array, marker::PhantomData};
97use log::error;
98use turshi::{
99 runner::{CairoInstruction, CairoProgram, Pointers},
100 word::{FlagBits, Offsets},
101};
102
103const NUM_FLAGS: usize = 16;
104pub const CIRCUIT_GATE_COUNT: usize = 4;
105
106// GATE-RELATED
107
108impl<F: PrimeField> CircuitGate<F> {
109 /// This function creates a `CairoClaim` gate
110 pub fn create_cairo_claim(wires: GateWires) -> Self {
111 CircuitGate::new(GateType::CairoClaim, wires, vec![])
112 }
113 /// This function creates a `CairoInstruction` gate
114 pub fn create_cairo_instruction(wires: GateWires) -> Self {
115 CircuitGate::new(GateType::CairoInstruction, wires, vec![])
116 }
117
118 /// This function creates a `CairoFlags` gate
119 pub fn create_cairo_flags(wires: GateWires) -> Self {
120 CircuitGate::new(GateType::CairoFlags, wires, vec![])
121 }
122
123 /// This function creates a `CairoTransition` gate
124 pub fn create_cairo_transition(wires: GateWires) -> Self {
125 CircuitGate::new(GateType::CairoTransition, wires, vec![])
126 }
127
128 /// Gadget generator of the whole cairo circuits from an absolute row and number of instructions
129 /// Returns a vector of gates, and the next available row after the gadget
130 pub fn create_cairo_gadget(
131 // the absolute row in the circuit
132 row: usize,
133 // number of instructions
134 num: usize,
135 ) -> (Vec<Self>, usize) {
136 // 0: 1 row for final check CairoClaim gate
137 // 4i+1: 1 row per instruction for CairoInstruction gate
138 // 4i+2: 1 row per instruction for Flags argument
139 // 4i+3: 1 row per instruction for CairoTransition gate
140 // 4i+4: 1 row per instruction for Cairo Auxiliary gate
141 // ...
142 // 4n-3: 1 row for last instruction
143 // 4n-2: 1 row for Auxiliary argument (no constraints)
144 let mut gates: Vec<CircuitGate<F>> = Vec::new();
145 if num > 0 {
146 let claim_gate = Wire::for_row(row);
147 gates.push(CircuitGate::create_cairo_claim(claim_gate));
148 }
149 let last = num - 1;
150 for i in 0..last {
151 let ins_gate = Wire::for_row(row + 4 * i + 1);
152 let flg_gate = Wire::for_row(row + 4 * i + 2);
153 let tra_gate = Wire::for_row(row + 4 * i + 3);
154 let aux_gate = Wire::for_row(row + 4 * i + 4);
155 gates.push(CircuitGate::create_cairo_instruction(ins_gate));
156 gates.push(CircuitGate::create_cairo_flags(flg_gate));
157 gates.push(CircuitGate::create_cairo_transition(tra_gate));
158 gates.push(CircuitGate::zero(aux_gate));
159 }
160 // next available row after the full
161 let next = row + 4 * last + 3;
162 // the final instruction
163 let ins_gate = Wire::for_row(next - 2);
164 let aux_gate = Wire::for_row(next - 1);
165 gates.push(CircuitGate::create_cairo_instruction(ins_gate));
166 gates.push(CircuitGate::zero(aux_gate));
167
168 (gates, next)
169 }
170
171 /// verifies that the Cairo gate constraints are solved by the witness depending on its type
172 ///
173 /// # Errors
174 ///
175 /// Will give error if `constraint evaluation` is invalid.
176 ///
177 /// # Panics
178 ///
179 /// Will panic if `constraint linearization` fails.
180 pub fn verify_cairo_gate<G: KimchiCurve<ScalarField = F>>(
181 &self,
182 row: usize,
183 witness: &[Vec<F>; COLUMNS],
184 cs: &ConstraintSystem<F>,
185 ) -> Result<(), String> {
186 // assignments
187 let curr: [F; COLUMNS] = array::from_fn(|i| witness[i][row]);
188 let mut next: [F; COLUMNS] = array::from_fn(|_| F::zero());
189 if self.typ != GateType::Zero {
190 next = array::from_fn(|i| witness[i][row + 1]);
191 }
192
193 // column polynomials
194 let polys = {
195 let mut h = std::collections::HashSet::new();
196 for i in 0..COLUMNS {
197 h.insert(Column::Witness(i)); // column witness polynomials
198 }
199 // gate selector polynomials
200 h.insert(Column::Index(GateType::CairoClaim));
201 h.insert(Column::Index(GateType::CairoInstruction));
202 h.insert(Column::Index(GateType::CairoFlags));
203 h.insert(Column::Index(GateType::CairoTransition));
204 h
205 };
206
207 // assign powers of alpha to these gates
208 let mut alphas = Alphas::<F>::default();
209 alphas.register(ArgumentType::Gate(self.typ), Instruction::<F>::CONSTRAINTS);
210
211 // Get constraints for this circuit gate
212 let constraints =
213 circuit_gate_combined_constraints(self.typ, &alphas, &mut Cache::default());
214
215 // Linearize
216 let linearized = constraints.linearize(polys).unwrap();
217
218 // Setup proof evaluations
219 let rng = &mut o1_utils::tests::make_test_rng(None);
220 let evals = ProofEvaluations::dummy_with_witness_evaluations(curr, next);
221
222 // Setup circuit constants
223 let constants = expr::Constants {
224 endo_coefficient: cs.endo,
225 mds: &G::sponge_params().mds,
226 zk_rows: 3,
227 };
228 let challenges = BerkeleyChallenges {
229 alpha: F::rand(rng),
230 beta: F::rand(rng),
231 gamma: F::rand(rng),
232 joint_combiner: F::zero(),
233 };
234
235 let pt = F::rand(rng);
236
237 // Evaluate constraints
238 match linearized
239 .constant_term
240 .evaluate_(cs.domain.d1, pt, &evals, &constants, &challenges)
241 {
242 Ok(x) => {
243 if x == F::zero() {
244 Ok(())
245 } else {
246 Err(format!("Invalid {:?} constraint", self.typ))
247 }
248 }
249 Err(x) => {
250 error!("{x:?}");
251 Err(format!("Failed to evaluate {:?} constraint", self.typ))
252 }
253 }
254 }
255}
256
257pub mod witness {
258 use super::*;
259
260 /// Returns the witness of an execution of a Cairo program in `CircuitGate` format
261 pub fn cairo_witness<F: Field>(prog: &CairoProgram<F>) -> [Vec<F>; COLUMNS] {
262 // 0: 1 row for final check CairoClaim gate
263 // 4i+1: 1 row per instruction for CairoInstruction gate
264 // 4i+2: 1 row per instruction for Flags argument
265 // 4i+3: 1 row per instruction for CairoTransition gate
266 // 4i+4: 1 row per instruction for Auxiliary gate (Zero)
267 // ...
268 // 4n-3: 1 row for last instruction
269 // 4n-2: 1 row for Auxiliary argument (no constraints)
270 let n = prog.trace().len();
271 let rows = 4 * n - 1;
272 let mut table: Vec<[F; COLUMNS]> = Vec::new();
273 table.resize(rows, [F::zero(); COLUMNS]);
274 for (i, inst) in prog.trace().iter().enumerate() {
275 if i == 0 {
276 let claim_wit = claim_witness(prog);
277 table[i] = claim_wit;
278 }
279 let ins_wit = instruction_witness(inst);
280 let flg_wit = flag_witness(inst);
281 table[4 * i + 1] = ins_wit;
282 table[4 * i + 2] = flg_wit;
283 if i != n - 1 {
284 // all but last instruction
285 let tra_wit = transition_witness(inst, &prog.trace()[i + 1]);
286 let aux_wit = auxiliary_witness(&prog.trace()[i + 1]);
287 table[4 * i + 3] = tra_wit;
288 table[4 * i + 4] = aux_wit;
289 }
290 }
291
292 let mut witness: [Vec<F>; COLUMNS] = Default::default();
293 for col in 0..COLUMNS {
294 // initialize column with zeroes
295 witness[col].resize(table.len(), F::zero());
296 for (row, wit) in table.iter().enumerate() {
297 witness[col][row] = wit[col];
298 }
299 }
300 witness
301 }
302
303 fn claim_witness<F: Field>(prog: &CairoProgram<F>) -> [F; COLUMNS] {
304 let last = prog.trace().len() - 1;
305 [
306 prog.ini().pc(), // initial pc from public input
307 prog.ini().ap(), // initial ap from public input
308 prog.fin().pc(), // final pc from public input
309 prog.fin().ap(), // final ap from public input
310 prog.trace()[last].pc(), // real last pc
311 prog.trace()[last].ap(), // real last ap
312 F::zero(),
313 F::zero(),
314 F::zero(),
315 F::zero(),
316 F::zero(),
317 F::zero(),
318 F::zero(),
319 F::zero(),
320 F::zero(),
321 ]
322 }
323
324 fn instruction_witness<F: Field>(inst: &CairoInstruction<F>) -> [F; COLUMNS] {
325 [
326 inst.pc(),
327 inst.ap(),
328 inst.fp(),
329 inst.size(),
330 inst.res(),
331 inst.dst(),
332 inst.op1(),
333 inst.op0(),
334 inst.off_dst(),
335 inst.off_op1(),
336 inst.off_op0(),
337 inst.adr_dst(),
338 inst.adr_op1(),
339 inst.adr_op0(),
340 inst.instr(),
341 ]
342 }
343
344 fn flag_witness<F: Field>(inst: &CairoInstruction<F>) -> [F; COLUMNS] {
345 [
346 inst.f_dst_fp(),
347 inst.f_op0_fp(),
348 inst.f_op1_val(),
349 inst.f_op1_fp(),
350 inst.f_op1_ap(),
351 inst.f_res_add(),
352 inst.f_res_mul(),
353 inst.f_pc_abs(),
354 inst.f_pc_rel(),
355 inst.f_pc_jnz(),
356 inst.f_ap_add(),
357 inst.f_ap_one(),
358 inst.f_opc_call(),
359 inst.f_opc_ret(),
360 inst.f_opc_aeq(),
361 ]
362 }
363
364 fn transition_witness<F: Field>(
365 curr: &CairoInstruction<F>,
366 next: &CairoInstruction<F>,
367 ) -> [F; COLUMNS] {
368 [
369 curr.pc(),
370 curr.ap(),
371 curr.fp(),
372 curr.size(),
373 curr.res(),
374 curr.dst(),
375 curr.op1(),
376 next.pc(),
377 next.ap(),
378 next.fp(),
379 F::zero(),
380 F::zero(),
381 F::zero(),
382 F::zero(),
383 F::zero(),
384 ]
385 }
386
387 fn auxiliary_witness<F: Field>(next: &CairoInstruction<F>) -> [F; COLUMNS] {
388 [
389 next.pc(),
390 next.ap(),
391 next.fp(),
392 F::zero(),
393 F::zero(),
394 F::zero(),
395 F::zero(),
396 F::zero(),
397 F::zero(),
398 F::zero(),
399 F::zero(),
400 F::zero(),
401 F::zero(),
402 F::zero(),
403 F::zero(),
404 ]
405 }
406}
407
408pub mod testing {
409 use super::*;
410
411 /// verifies that the Cairo gate constraints are solved by the witness depending on its type
412 ///
413 /// # Errors
414 ///
415 /// Will give error if `gate` is not `Cairo`-related gate or `zero` gate.
416 pub fn ensure_cairo_gate<F: PrimeField>(
417 gate: &CircuitGate<F>,
418 row: usize,
419 witness: &[Vec<F>; COLUMNS],
420 //_cs: &ConstraintSystem<F>,
421 ) -> Result<(), String> {
422 // assignments
423 let this: [F; COLUMNS] = array::from_fn(|i| witness[i][row]);
424
425 match gate.typ {
426 GateType::CairoClaim => {
427 let next: [F; COLUMNS] = array::from_fn(|i| witness[i][row + 1]);
428 ensure_claim(&this, &next) // CircuitGate::ensure_transition(&this),
429 }
430 GateType::CairoInstruction => {
431 let next: [F; COLUMNS] = array::from_fn(|i| witness[i][row + 1]);
432 ensure_instruction(&this, &next)
433 }
434 GateType::CairoFlags => {
435 let next: [F; COLUMNS] = array::from_fn(|i| witness[i][row + 1]);
436 ensure_flags(&this, &next)
437 }
438 GateType::CairoTransition => {
439 let next: [F; COLUMNS] = array::from_fn(|i| witness[i][row + 1]);
440 ensure_transition(&this, &next)
441 }
442 GateType::Zero => Ok(()),
443 _ => Err(
444 "Incorrect GateType: expected CairoInstruction, CairoFlags, CairoTransition, or CairoClaim"
445 .to_string(),
446 ),
447 }
448 }
449
450 fn ensure_instruction<F: FftField>(vars: &[F], flags: &[F]) -> Result<(), String> {
451 let pc = vars[0];
452 let ap = vars[1];
453 let fp = vars[2];
454 let size = vars[3];
455 let res = vars[4];
456 let dst = vars[5];
457 let op1 = vars[6];
458 let op0 = vars[7];
459 let off_dst = vars[8];
460 let off_op1 = vars[9];
461 let off_op0 = vars[10];
462 let adr_dst = vars[11];
463 let adr_op1 = vars[12];
464 let adr_op0 = vars[13];
465 let instr = vars[14];
466
467 let f_dst_fp = flags[0];
468 let f_op0_fp = flags[1];
469 let f_op1_val = flags[2];
470 let f_op1_fp = flags[3];
471 let f_op1_ap = flags[4];
472 let f_res_add = flags[5];
473 let f_res_mul = flags[6];
474 let f_pc_abs = flags[7];
475 let f_pc_rel = flags[8];
476 let f_pc_jnz = flags[9];
477 let f_ap_inc = flags[10];
478 let f_ap_one = flags[11];
479 let f_opc_call = flags[12];
480 let f_opc_ret = flags[13];
481 let f_opc_aeq = flags[14];
482
483 let zero = F::zero();
484 let one = F::one();
485
486 // FLAGS RELATED
487
488 // check last flag is a zero
489 // f15 == 0
490 //ensure_eq!(zero, f15, "last flag is nonzero");
491
492 // check booleanity of flags
493 // fi * (1-fi) == 0 for i=[0..15)
494 for &flag in flags.iter().take(NUM_FLAGS - 1) {
495 ensure_eq!(zero, flag * (one - flag), "non-boolean flags");
496 }
497
498 // well formness of instruction
499 let shape = {
500 let shift = F::from(2u32.pow(15)); // 2^15;
501 let pow16 = shift.double(); // 2^16
502 let dst_sft = off_dst + shift;
503 let op0_sft = off_op0 + shift;
504 let op1_sft = off_op1 + shift;
505 // recompose instruction as: flags[14..0] | op1_sft | op0_sft | dst_sft
506 let mut aux = flags[14];
507 for i in (0..14).rev() {
508 aux = aux * F::from(2u32) + flags[i];
509 }
510 // complete with "flags" * 2^48 + op1_sft * 2^32 + op0_sft * 2^16 + dst_sft
511 ((aux * pow16 + op1_sft) * pow16 + op0_sft) * pow16 + dst_sft
512 };
513 ensure_eq!(
514 zero,
515 instr - shape,
516 "wrong decomposition of the instruction"
517 );
518
519 // check no two flags of same set are nonzero
520 let op1_set = f_op1_ap + f_op1_fp + f_op1_val;
521 let res_set = f_res_mul + f_res_add;
522 let pc_set = f_pc_jnz + f_pc_rel + f_pc_abs;
523 let ap_set = f_ap_one + f_ap_inc;
524 let opcode_set = f_opc_aeq + f_opc_ret + f_opc_call;
525 ensure_eq!(
526 zero,
527 op1_set * (one - op1_set),
528 "invalid format of `op1_src`"
529 );
530
531 ensure_eq!(
532 zero,
533 res_set * (one - res_set),
534 "invalid format of `res_log`"
535 );
536 ensure_eq!(zero, pc_set * (one - pc_set), "invalid format of `pc_up`");
537 ensure_eq!(zero, ap_set * (one - ap_set), "invalid format of `ap_up`");
538 ensure_eq!(
539 zero,
540 opcode_set * (one - opcode_set),
541 "invalid format of `opcode`"
542 );
543
544 // OPERANDS RELATED
545
546 // * Destination address
547 // if dst_reg = 0 : dst_dir = ap + off_dst
548 // if dst_reg = 1 : dst_dir = fp + off_dst
549 ensure_eq!(
550 adr_dst,
551 f_dst_fp * fp + (one - f_dst_fp) * ap + off_dst,
552 "invalid destination address"
553 );
554
555 // * First operand address
556 // if op0_reg = 0 : op0_dir = ap + off_dst
557 // if op0_reg = 1 : op0_dir = fp + off_dst
558 ensure_eq!(
559 adr_op0,
560 f_op0_fp * fp + (one - f_op0_fp) * ap + off_op0,
561 "invalid first operand address"
562 );
563
564 // * Second operand address
565 ensure_eq!(
566 adr_op1, // op1_dir = ..
567 (f_op1_ap * ap // if op1_src == 4 : ap
568 + f_op1_fp * fp // if op1_src == 2 : fp
569 + f_op1_val * pc // if op1_src == 1 : pc
570 + (one - f_op1_fp - f_op1_ap - f_op1_val) * op0 // if op1_src == 0 : op0
571 + off_op1), // + off_op1
572 "invalid second operand address"
573 );
574
575 // OPERATIONS RELATED
576
577 // * Check value of result
578 ensure_eq!(
579 (one - f_pc_jnz) * res, // if pc_up != 4 : res = .. // no res in conditional jumps
580 f_res_mul * op0 * op1 // if res_log = 2 : op0 * op1
581 + f_res_add * (op0 + op1) // if res_log = 1 : op0 + op1
582 + (one - f_res_add - f_res_mul) * op1, // if res_log = 0 : op1
583 "invalid result"
584 );
585
586 // * Check storage of current fp for a call instruction
587 ensure_eq!(
588 zero,
589 f_opc_call * (dst - fp),
590 "current fp after call not stored"
591 ); // if opcode = 1 : dst = fp
592
593 // * Check storage of next instruction after a call instruction
594 ensure_eq!(
595 zero,
596 f_opc_call * (op0 - (pc + size)),
597 "next instruction after call not stored"
598 ); // if opcode = 1 : op0 = pc + size
599
600 // * Check destination = result after assert-equal
601 ensure_eq!(zero, f_opc_aeq * (dst - res), "false assert equal"); // if opcode = 4 : dst = res
602
603 Ok(())
604 }
605
606 fn ensure_flags<F: FftField>(curr: &[F], next: &[F]) -> Result<(), String> {
607 let f_pc_abs = curr[7];
608 let f_pc_rel = curr[8];
609 let f_pc_jnz = curr[9];
610 let f_ap_inc = curr[10];
611 let f_ap_one = curr[11];
612 let f_opc_call = curr[12];
613 let f_opc_ret = curr[13];
614 let pc = next[0];
615 let ap = next[1];
616 let fp = next[2];
617 let size = next[3];
618 let res = next[4];
619 let dst = next[5];
620 let op1 = next[6];
621 let pcup = next[7];
622 let apup = next[8];
623 let fpup = next[9];
624
625 let zero = F::zero();
626 let one = F::one();
627 let two = F::from(2u16);
628
629 // REGISTERS RELATED
630
631 // * Check next allocation pointer
632 ensure_eq!(
633 apup, // next_ap =
634 ap // ap +
635 + f_ap_inc * res // if ap_up == 1 : res
636 + f_ap_one // if ap_up == 2 : 1
637 + f_opc_call.double(), // if opcode == 1 : 2
638 "wrong ap update"
639 );
640
641 // * Check next frame pointer
642 ensure_eq!(
643 fpup, // next_fp =
644 f_opc_call * (ap + two) // if opcode == 1 : ap + 2
645 + f_opc_ret * dst // if opcode == 2 : dst
646 + (one - f_opc_call - f_opc_ret) * fp, // if opcode == 4 or 0 : fp
647 "wrong fp update"
648 );
649
650 // * Check next program counter
651 ensure_eq!(
652 zero,
653 f_pc_jnz * (dst * res - one) * (pcup - (pc + size)), // <=> pc_up = 4 and dst = 0 : next_pc = pc + size // no jump
654 "wrong pc update"
655 );
656 ensure_eq!(
657 zero,
658 f_pc_jnz * dst * (pcup - (pc + op1)) // <=> pc_up = 4 and dst != 0 : next_pc = pc + op1 // condition holds
659 + (one - f_pc_jnz) * pcup // <=> pc_up = {0,1,2} : next_pc = ... // not a conditional jump
660 - (one - f_pc_abs - f_pc_rel - f_pc_jnz) * (pc + size) // <=> pc_up = 0 : next_pc = pc + size // common case
661 - f_pc_abs * res // <=> pc_up = 1 : next_pc = res // absolute jump
662 - f_pc_rel * (pc + res), // <=> pc_up = 2 : next_pc = pc + res // relative jump
663 "wrong pc update"
664 );
665
666 Ok(())
667 }
668
669 fn ensure_transition<F: FftField>(curr: &[F], next: &[F]) -> Result<(), String> {
670 let pcup = curr[7];
671 let apup = curr[8];
672 let fpup = curr[9];
673 let next_pc = next[0];
674 let next_ap = next[1];
675 let next_fp = next[2];
676
677 // REGISTERS RELATED
678
679 // * Check next allocation pointer
680 ensure_eq!(next_ap, apup, "wrong next allocation pointer");
681
682 // * Check next frame pointer
683 ensure_eq!(next_fp, fpup, "wrong next frame pointer");
684
685 // * Check next program counter
686 ensure_eq!(next_pc, pcup, "wrong next program counter");
687
688 Ok(())
689 }
690
691 fn ensure_claim<F: FftField>(claim: &[F], next: &[F]) -> Result<(), String> {
692 let pc_ini = claim[0];
693 let ap_ini = claim[1];
694 let pc_fin = claim[2];
695 let ap_fin = claim[3];
696 let pc_n = claim[4];
697 let ap_n = claim[5];
698 let pc0 = next[0];
699 let ap0 = next[1];
700 let fp0 = next[2];
701
702 // * Check initial pc, ap, fp and final pc, ap
703 ensure_eq!(F::zero(), pc0 - pc_ini, "wrong initial pc");
704 ensure_eq!(F::zero(), ap0 - ap_ini, "wrong initial ap");
705 ensure_eq!(F::zero(), fp0 - ap_ini, "wrong initial fp");
706 ensure_eq!(F::zero(), pc_n - pc_fin, "wrong final pc");
707 ensure_eq!(F::zero(), ap_n - ap_fin, "wrong final ap");
708
709 Ok(())
710 }
711}
712
713//~ The Kimchi 15 columns could be:
714//~ GateType Claim Instruction Zero | (Flags+Transition+Aux)
715//~ row -> 0 4i+1 4i+2 4i+3 4n-2
716//~ ---------------------------------------------------------------------------------
717//~ 0 · ® pc_ini pc fDST_FP © pc © next_pc
718//~ 1 · ® ap_ini ap fOP0_FP © ap © next_ap
719//~ c 2 · ® pc_fin fp fOP1_VAL © fp © next_fp
720//~ o 3 · ® ap_fin size fOP1_FP © size
721//~ l 4 · © pc\[n-1\] res fOP1_AP © res
722//~ | 5 · © ap\[n-1\] dst fRES_ADD © dst
723//~ v 6 · op1 fRES_MUL © op1
724//~ 7 op0 fPC_ABS pcup
725//~ 8 off_dst fPC_REL apup
726//~ 9 off_op1 fPC_JNZ fpup
727//~ 10 off_op0 fAP_ADD
728//~ 11 adr_dst fAP_ONE
729//~ 12 adr_op1 fOPC_CALL
730//~ 13 adr_op0 fOPC_RET
731//~ 14 instr
732
733// CONSTRAINTS-RELATED
734
735/// Returns the expression corresponding to the literal "2"
736fn two<F: Field, T: ExprOps<F, BerkeleyChallengeTerm>>() -> T {
737 T::literal(2u64.into()) // 2
738}
739
740/// Combines the constraints for the Cairo gates depending on its type
741///
742/// # Panics
743///
744/// Will panic if the `typ` is not `Cairo`-related gate type or `zero` gate type.
745pub fn circuit_gate_combined_constraints<F: PrimeField>(
746 typ: GateType,
747 alphas: &Alphas<F>,
748 cache: &mut Cache,
749) -> E<F> {
750 match typ {
751 GateType::CairoClaim => Claim::combined_constraints(alphas, cache),
752 GateType::CairoInstruction => Instruction::combined_constraints(alphas, cache),
753 GateType::CairoFlags => Flags::combined_constraints(alphas, cache),
754 GateType::CairoTransition => Transition::combined_constraints(alphas, cache),
755 GateType::Zero => E::literal(F::zero()),
756 _ => panic!("invalid gate type"),
757 }
758}
759
760pub struct Claim<F>(PhantomData<F>);
761
762impl<F> Argument<F> for Claim<F>
763where
764 F: PrimeField,
765{
766 const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CairoClaim);
767 const CONSTRAINTS: u32 = 5;
768
769 /// Generates the constraints for the Cairo initial claim and first memory checks
770 /// Accesses Curr and Next rows
771 fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
772 env: &ArgumentEnv<F, T>,
773 _cache: &mut Cache,
774 ) -> Vec<T> {
775 let pc_ini = env.witness_curr(0); // copy from public input
776 let ap_ini = env.witness_curr(1); // copy from public input
777 let pc_fin = env.witness_curr(2); // copy from public input
778 let ap_fin = env.witness_curr(3); // copy from public input
779 let pc_n = env.witness_curr(4); // copy from public input
780 let ap_n = env.witness_curr(5); // copy from public input
781
782 // load address / value pairs from next row
783 let pc0 = env.witness_next(0);
784 let ap0 = env.witness_next(1);
785 let fp0 = env.witness_next(2);
786
787 // Initial claim
788 let mut constraints: Vec<T> = vec![ap0 - ap_ini.clone()]; // ap0 = ini_ap
789 constraints.push(fp0 - ap_ini); // fp0 = ini_ap
790 constraints.push(pc0 - pc_ini); // pc0 = ini_pc
791
792 // Final claim
793 constraints.push(ap_n - ap_fin);
794 constraints.push(pc_n - pc_fin);
795
796 constraints
797 }
798}
799
800pub struct Instruction<F>(PhantomData<F>);
801
802impl<F> Argument<F> for Instruction<F>
803where
804 F: PrimeField,
805{
806 const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CairoInstruction);
807 const CONSTRAINTS: u32 = 28;
808
809 /// Generates the constraints for the Cairo instruction
810 /// Accesses Curr and Next rows
811 fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
812 env: &ArgumentEnv<F, T>,
813 cache: &mut Cache,
814 ) -> Vec<T> {
815 // load all variables of the witness corresponding to Cairoinstruction gates
816 let pc = env.witness_curr(0);
817 let ap = env.witness_curr(1);
818 let fp = env.witness_curr(2);
819 let size = env.witness_curr(3);
820 let res = env.witness_curr(4);
821 let dst = env.witness_curr(5);
822 let op1 = env.witness_curr(6);
823 let op0 = env.witness_curr(7);
824 let off_dst = env.witness_curr(8);
825 let off_op1 = env.witness_curr(9);
826 let off_op0 = env.witness_curr(10);
827 let adr_dst = env.witness_curr(11);
828 let adr_op1 = env.witness_curr(12);
829 let adr_op0 = env.witness_curr(13);
830 let instr = env.witness_curr(14);
831 // Load flags from the following row
832 let f_dst_fp = env.witness_next(0);
833 let f_op0_fp = env.witness_next(1);
834 let f_op1_val = env.witness_next(2);
835 let f_op1_fp = env.witness_next(3);
836 let f_op1_ap = env.witness_next(4);
837 let f_res_add = env.witness_next(5);
838 let f_res_mul = env.witness_next(6);
839 let f_pc_abs = env.witness_next(7);
840 let f_pc_rel = env.witness_next(8);
841 let f_pc_jnz = env.witness_next(9);
842 let f_ap_add = env.witness_next(10);
843 let f_ap_one = env.witness_next(11);
844 let f_opc_call = env.witness_next(12);
845 let f_opc_ret = env.witness_next(13);
846 let f_opc_aeq = env.witness_next(14);
847
848 // collect flags in its natural ordering
849 let flags: Vec<T> = (0..NUM_FLAGS - 1).map(|i| env.witness_next(i)).collect();
850
851 // LIST OF CONSTRAINTS
852 // -------------------
853 let mut constraints: Vec<T> = vec![];
854
855 // INSTRUCTIONS RELATED
856
857 // * Check last flag is always zero is redundant with wellformness check
858
859 // * Check booleanity of all flags
860 // fi * (1-fi) == 0 for i=[0..15)
861 for flag in flags.iter().take(NUM_FLAGS - 1) {
862 constraints.push(flag.clone() * (T::one() - flag.clone()));
863 }
864
865 // * Check no two flagbits of the same flagset are nonzero
866 // TODO(querolita): perhaps these are redundant considering all of the logics below
867 let op1_src = cache.cache(f_op1_ap.clone() + f_op1_fp.clone() + f_op1_val.clone());
868 let res_log = cache.cache(f_res_mul.clone() + f_res_add.clone());
869 let pc_up = cache.cache(f_pc_jnz.clone() + f_pc_rel + f_pc_abs);
870 let ap_up = cache.cache(f_ap_one + f_ap_add);
871 let opcode = cache.cache(f_opc_aeq.clone() + f_opc_ret + f_opc_call.clone());
872 constraints.push(op1_src.clone() * (T::one() - op1_src));
873 constraints.push(res_log.clone() * (T::one() - res_log));
874 constraints.push(pc_up.clone() * (T::one() - pc_up));
875 constraints.push(ap_up.clone() * (T::one() - ap_up));
876 constraints.push(opcode.clone() * (T::one() - opcode));
877
878 // * Shape of instruction
879 let shape = {
880 let shift: T = cache.cache(two::<F, T>().pow(15)); // 2^15;
881 let double_shift = shift.double();
882 let pow16 = cache.cache(double_shift); // 2^16
883 let dst_sft = off_dst.clone() + shift.clone();
884 let op0_sft = off_op0.clone() + shift.clone();
885 let op1_sft = off_op1.clone() + shift;
886 // recompose instruction as: flags[14..0] | op1_sft | op0_sft | dst_sft
887 let mut aux: T = flags[14].clone();
888 for i in (0..14).rev() {
889 aux = aux * two() + flags[i].clone();
890 }
891 // complete with "flags" * 2^48 + op1_sft * 2^32 + op0_sft * 2^16 + dst_sft
892 aux = ((aux * pow16.clone() + op1_sft) * pow16.clone() + op0_sft) * pow16 + dst_sft;
893 aux
894 };
895 constraints.push(instr - shape);
896
897 // OPERANDS RELATED
898
899 // * Destination address
900 // if dst_fp = 0 : dst_dir = ap + off_dst
901 // if dst_fp = 1 : dst_dir = fp + off_dst
902 constraints.push(
903 f_dst_fp.clone() * fp.clone() + (T::one() - f_dst_fp) * ap.clone() + off_dst - adr_dst,
904 );
905
906 // * First operand address
907 // if op0_fp = 0 : op0_dir = ap + off_dst
908 // if op0_fp = 1 : op0_dir = fp + off_dst
909 constraints.push(
910 f_op0_fp.clone() * fp.clone() + (T::one() - f_op0_fp) * ap.clone() + off_op0 - adr_op0,
911 );
912
913 // * Second operand address
914 constraints.push(
915 adr_op1 // op1_dir = ..
916 - (f_op1_ap.clone() * ap // if op1_src == 4 : ap
917 + f_op1_fp.clone() * fp.clone() // if op1_src == 2 : fp
918 + f_op1_val.clone() * pc.clone() // if op1_src == 1 : pc
919 + (T::one() - f_op1_fp - f_op1_ap - f_op1_val) * op0.clone() // if op1_src == 0 : op0
920 + off_op1), // + off_op1
921 );
922
923 // OPERATIONS-RELATED
924
925 // * Check value of result
926 constraints.push(
927 (T::one() - f_pc_jnz) * res.clone() // if pc_up != 4 : res = .. // no res in conditional jumps
928 - (f_res_mul.clone() * op0.clone() * op1.clone() // if res_log = 2 : op0 * op1
929 + f_res_add.clone() * (op0.clone() + op1.clone()) // if res_log = 1 : op0 + op1
930 + (T::one() - f_res_add - f_res_mul) * op1), // if res_log = 0 : op1
931 );
932
933 // * Check storage of current fp for a call instruction
934 // <=> assert_eq!(dst, fp);
935 constraints.push(f_opc_call.clone() * (dst.clone() - fp)); // if opcode = 1 : dst = fp
936
937 // * Check storage of next instruction after a call instruction
938 // <=> assert_eq!(op0, pc + size); // checks [ap+1] contains instruction after call
939 constraints.push(f_opc_call * (op0 - (pc + size))); // if opcode = 1 : op0 = pc + size
940
941 // * Check destination = result after assert-equal
942 // <=> assert_eq!(res, dst);
943 constraints.push(f_opc_aeq * (dst - res)); // if opcode = 4 : dst = res
944
945 constraints
946 }
947}
948
949pub struct Flags<F>(PhantomData<F>);
950
951impl<F> Argument<F> for Flags<F>
952where
953 F: PrimeField,
954{
955 const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CairoFlags);
956 const CONSTRAINTS: u32 = 4;
957
958 /// Generates the constraints for the Cairo flags
959 /// Accesses Curr and Next rows
960 fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
961 env: &ArgumentEnv<F, T>,
962 _cache: &mut Cache,
963 ) -> Vec<T> {
964 // Load current row
965 let f_pc_abs = env.witness_curr(7);
966 let f_pc_rel = env.witness_curr(8);
967 let f_pc_jnz = env.witness_curr(9);
968 let f_ap_add = env.witness_curr(10);
969 let f_ap_one = env.witness_curr(11);
970 let f_opc_call = env.witness_curr(12);
971 let f_opc_ret = env.witness_curr(13);
972 // Load next row
973 let pc = env.witness_next(0);
974 let ap = env.witness_next(1);
975 let fp = env.witness_next(2);
976 let size = env.witness_next(3);
977 let res = env.witness_next(4);
978 let dst = env.witness_next(5);
979 let op1 = env.witness_next(6);
980 let pcup = env.witness_next(7);
981 let apup = env.witness_next(8);
982 let fpup = env.witness_next(9);
983
984 // REGISTERS-RELATED
985 // * Check next allocation pointer
986 // next_ap =
987 // ap +
988 // if ap_up == 1 : res
989 // if ap_up == 2 : 1
990 // if opcode == 1 : 2
991 let mut constraints: Vec<T> =
992 vec![apup - (ap.clone() + f_ap_add * res.clone() + f_ap_one + f_opc_call.double())];
993
994 // * Check next frame pointer
995 constraints.push(
996 fpup // next_fp =
997 - (f_opc_call.clone() * (ap + two()) // if opcode == 1 : ap + 2
998 + f_opc_ret.clone() * dst.clone() // if opcode == 2 : dst
999 + (T::one() - f_opc_call - f_opc_ret) * fp ), // if opcode == 4 or 0 : fp
1000 );
1001
1002 // * Check next program counter (pc update)
1003 constraints.push(
1004 f_pc_jnz.clone()
1005 * (dst.clone() * res.clone() - T::one())
1006 * (pcup.clone() - (pc.clone() + size.clone())),
1007 ); // <=> pc_up = 4 and dst = 0 : next_pc = pc + size // no jump
1008 constraints.push(
1009 f_pc_jnz.clone() * dst * (pcup.clone() - (pc.clone() + op1)) // <=> pc_up = 4 and dst != 0 : next_pc = pc + op1 // condition holds
1010 + (T::one() - f_pc_jnz.clone()) * pcup // <=> pc_up = {0,1,2} : next_pc = ... // not a conditional jump
1011 - (T::one() - f_pc_abs.clone() - f_pc_rel.clone() - f_pc_jnz) * (pc.clone() + size) // <=> pc_up = 0 : next_pc = pc + size // common case
1012 - f_pc_abs * res.clone() // <=> pc_up = 1 : next_pc = res // absolute jump
1013 - f_pc_rel * (pc + res), // <=> pc_up = 2 : next_pc = pc + res // relative jump
1014 );
1015 constraints
1016 }
1017}
1018
1019pub struct Transition<F>(PhantomData<F>);
1020
1021impl<F> Argument<F> for Transition<F>
1022where
1023 F: PrimeField,
1024{
1025 const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CairoTransition);
1026 const CONSTRAINTS: u32 = 3;
1027
1028 /// Generates the constraints for the Cairo transition
1029 /// Accesses Curr and Next rows (Next only first 3 entries)
1030 fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
1031 env: &ArgumentEnv<F, T>,
1032 _cache: &mut Cache,
1033 ) -> Vec<T> {
1034 // load computed updated registers
1035 let pcup = env.witness_curr(7);
1036 let apup = env.witness_curr(8);
1037 let fpup = env.witness_curr(9);
1038 // load next registers
1039 let next_pc = env.witness_next(0);
1040 let next_ap = env.witness_next(1);
1041 let next_fp = env.witness_next(2);
1042
1043 // * Check equality (like a copy constraint)
1044
1045 let constraints: Vec<T> = vec![next_pc - pcup, next_ap - apup, next_fp - fpup];
1046
1047 constraints
1048 }
1049}