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<
181 const FULL_ROUNDS: usize,
182 G: KimchiCurve<FULL_ROUNDS, ScalarField = F>,
183 >(
184 &self,
185 row: usize,
186 witness: &[Vec<F>; COLUMNS],
187 cs: &ConstraintSystem<F>,
188 ) -> Result<(), String> {
189 // assignments
190 let curr: [F; COLUMNS] = array::from_fn(|i| witness[i][row]);
191 let mut next: [F; COLUMNS] = array::from_fn(|_| F::zero());
192 if self.typ != GateType::Zero {
193 next = array::from_fn(|i| witness[i][row + 1]);
194 }
195
196 // column polynomials
197 let polys = {
198 let mut h = std::collections::HashSet::new();
199 for i in 0..COLUMNS {
200 h.insert(Column::Witness(i)); // column witness polynomials
201 }
202 // gate selector polynomials
203 h.insert(Column::Index(GateType::CairoClaim));
204 h.insert(Column::Index(GateType::CairoInstruction));
205 h.insert(Column::Index(GateType::CairoFlags));
206 h.insert(Column::Index(GateType::CairoTransition));
207 h
208 };
209
210 // assign powers of alpha to these gates
211 let mut alphas = Alphas::<F>::default();
212 alphas.register(ArgumentType::Gate(self.typ), Instruction::<F>::CONSTRAINTS);
213
214 // Get constraints for this circuit gate
215 let constraints =
216 circuit_gate_combined_constraints(self.typ, &alphas, &mut Cache::default());
217
218 // Linearize
219 let linearized = constraints.linearize(polys).unwrap();
220
221 // Setup proof evaluations
222 let rng = &mut o1_utils::tests::make_test_rng(None);
223 let evals = ProofEvaluations::dummy_with_witness_evaluations(curr, next);
224
225 // Setup circuit constants
226 let constants = expr::Constants {
227 endo_coefficient: cs.endo,
228 mds: &G::sponge_params().mds,
229 zk_rows: 3,
230 };
231 let challenges = BerkeleyChallenges {
232 alpha: F::rand(rng),
233 beta: F::rand(rng),
234 gamma: F::rand(rng),
235 joint_combiner: F::zero(),
236 };
237
238 let pt = F::rand(rng);
239
240 // Evaluate constraints
241 match linearized
242 .constant_term
243 .evaluate_(cs.domain.d1, pt, &evals, &constants, &challenges)
244 {
245 Ok(x) => {
246 if x == F::zero() {
247 Ok(())
248 } else {
249 Err(format!("Invalid {:?} constraint", self.typ))
250 }
251 }
252 Err(x) => {
253 error!("{x:?}");
254 Err(format!("Failed to evaluate {:?} constraint", self.typ))
255 }
256 }
257 }
258}
259
260pub mod witness {
261 use super::*;
262
263 /// Returns the witness of an execution of a Cairo program in `CircuitGate` format
264 pub fn cairo_witness<F: Field>(prog: &CairoProgram<F>) -> [Vec<F>; COLUMNS] {
265 // 0: 1 row for final check CairoClaim gate
266 // 4i+1: 1 row per instruction for CairoInstruction gate
267 // 4i+2: 1 row per instruction for Flags argument
268 // 4i+3: 1 row per instruction for CairoTransition gate
269 // 4i+4: 1 row per instruction for Auxiliary gate (Zero)
270 // ...
271 // 4n-3: 1 row for last instruction
272 // 4n-2: 1 row for Auxiliary argument (no constraints)
273 let n = prog.trace().len();
274 let rows = 4 * n - 1;
275 let mut table: Vec<[F; COLUMNS]> = Vec::new();
276 table.resize(rows, [F::zero(); COLUMNS]);
277 for (i, inst) in prog.trace().iter().enumerate() {
278 if i == 0 {
279 let claim_wit = claim_witness(prog);
280 table[i] = claim_wit;
281 }
282 let ins_wit = instruction_witness(inst);
283 let flg_wit = flag_witness(inst);
284 table[4 * i + 1] = ins_wit;
285 table[4 * i + 2] = flg_wit;
286 if i != n - 1 {
287 // all but last instruction
288 let tra_wit = transition_witness(inst, &prog.trace()[i + 1]);
289 let aux_wit = auxiliary_witness(&prog.trace()[i + 1]);
290 table[4 * i + 3] = tra_wit;
291 table[4 * i + 4] = aux_wit;
292 }
293 }
294
295 let mut witness: [Vec<F>; COLUMNS] = Default::default();
296 for col in 0..COLUMNS {
297 // initialize column with zeroes
298 witness[col].resize(table.len(), F::zero());
299 for (row, wit) in table.iter().enumerate() {
300 witness[col][row] = wit[col];
301 }
302 }
303 witness
304 }
305
306 fn claim_witness<F: Field>(prog: &CairoProgram<F>) -> [F; COLUMNS] {
307 let last = prog.trace().len() - 1;
308 [
309 prog.ini().pc(), // initial pc from public input
310 prog.ini().ap(), // initial ap from public input
311 prog.fin().pc(), // final pc from public input
312 prog.fin().ap(), // final ap from public input
313 prog.trace()[last].pc(), // real last pc
314 prog.trace()[last].ap(), // real last ap
315 F::zero(),
316 F::zero(),
317 F::zero(),
318 F::zero(),
319 F::zero(),
320 F::zero(),
321 F::zero(),
322 F::zero(),
323 F::zero(),
324 ]
325 }
326
327 fn instruction_witness<F: Field>(inst: &CairoInstruction<F>) -> [F; COLUMNS] {
328 [
329 inst.pc(),
330 inst.ap(),
331 inst.fp(),
332 inst.size(),
333 inst.res(),
334 inst.dst(),
335 inst.op1(),
336 inst.op0(),
337 inst.off_dst(),
338 inst.off_op1(),
339 inst.off_op0(),
340 inst.adr_dst(),
341 inst.adr_op1(),
342 inst.adr_op0(),
343 inst.instr(),
344 ]
345 }
346
347 fn flag_witness<F: Field>(inst: &CairoInstruction<F>) -> [F; COLUMNS] {
348 [
349 inst.f_dst_fp(),
350 inst.f_op0_fp(),
351 inst.f_op1_val(),
352 inst.f_op1_fp(),
353 inst.f_op1_ap(),
354 inst.f_res_add(),
355 inst.f_res_mul(),
356 inst.f_pc_abs(),
357 inst.f_pc_rel(),
358 inst.f_pc_jnz(),
359 inst.f_ap_add(),
360 inst.f_ap_one(),
361 inst.f_opc_call(),
362 inst.f_opc_ret(),
363 inst.f_opc_aeq(),
364 ]
365 }
366
367 fn transition_witness<F: Field>(
368 curr: &CairoInstruction<F>,
369 next: &CairoInstruction<F>,
370 ) -> [F; COLUMNS] {
371 [
372 curr.pc(),
373 curr.ap(),
374 curr.fp(),
375 curr.size(),
376 curr.res(),
377 curr.dst(),
378 curr.op1(),
379 next.pc(),
380 next.ap(),
381 next.fp(),
382 F::zero(),
383 F::zero(),
384 F::zero(),
385 F::zero(),
386 F::zero(),
387 ]
388 }
389
390 fn auxiliary_witness<F: Field>(next: &CairoInstruction<F>) -> [F; COLUMNS] {
391 [
392 next.pc(),
393 next.ap(),
394 next.fp(),
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 F::zero(),
405 F::zero(),
406 F::zero(),
407 ]
408 }
409}
410
411pub mod testing {
412 use super::*;
413
414 /// verifies that the Cairo gate constraints are solved by the witness depending on its type
415 ///
416 /// # Errors
417 ///
418 /// Will give error if `gate` is not `Cairo`-related gate or `zero` gate.
419 pub fn ensure_cairo_gate<F: PrimeField>(
420 gate: &CircuitGate<F>,
421 row: usize,
422 witness: &[Vec<F>; COLUMNS],
423 //_cs: &ConstraintSystem<F>,
424 ) -> Result<(), String> {
425 // assignments
426 let this: [F; COLUMNS] = array::from_fn(|i| witness[i][row]);
427
428 match gate.typ {
429 GateType::CairoClaim => {
430 let next: [F; COLUMNS] = array::from_fn(|i| witness[i][row + 1]);
431 ensure_claim(&this, &next) // CircuitGate::ensure_transition(&this),
432 }
433 GateType::CairoInstruction => {
434 let next: [F; COLUMNS] = array::from_fn(|i| witness[i][row + 1]);
435 ensure_instruction(&this, &next)
436 }
437 GateType::CairoFlags => {
438 let next: [F; COLUMNS] = array::from_fn(|i| witness[i][row + 1]);
439 ensure_flags(&this, &next)
440 }
441 GateType::CairoTransition => {
442 let next: [F; COLUMNS] = array::from_fn(|i| witness[i][row + 1]);
443 ensure_transition(&this, &next)
444 }
445 GateType::Zero => Ok(()),
446 _ => Err(
447 "Incorrect GateType: expected CairoInstruction, CairoFlags, CairoTransition, or CairoClaim"
448 .to_string(),
449 ),
450 }
451 }
452
453 fn ensure_instruction<F: FftField>(vars: &[F], flags: &[F]) -> Result<(), String> {
454 let pc = vars[0];
455 let ap = vars[1];
456 let fp = vars[2];
457 let size = vars[3];
458 let res = vars[4];
459 let dst = vars[5];
460 let op1 = vars[6];
461 let op0 = vars[7];
462 let off_dst = vars[8];
463 let off_op1 = vars[9];
464 let off_op0 = vars[10];
465 let adr_dst = vars[11];
466 let adr_op1 = vars[12];
467 let adr_op0 = vars[13];
468 let instr = vars[14];
469
470 let f_dst_fp = flags[0];
471 let f_op0_fp = flags[1];
472 let f_op1_val = flags[2];
473 let f_op1_fp = flags[3];
474 let f_op1_ap = flags[4];
475 let f_res_add = flags[5];
476 let f_res_mul = flags[6];
477 let f_pc_abs = flags[7];
478 let f_pc_rel = flags[8];
479 let f_pc_jnz = flags[9];
480 let f_ap_inc = flags[10];
481 let f_ap_one = flags[11];
482 let f_opc_call = flags[12];
483 let f_opc_ret = flags[13];
484 let f_opc_aeq = flags[14];
485
486 let zero = F::zero();
487 let one = F::one();
488
489 // FLAGS RELATED
490
491 // check last flag is a zero
492 // f15 == 0
493 //ensure_eq!(zero, f15, "last flag is nonzero");
494
495 // check booleanity of flags
496 // fi * (1-fi) == 0 for i=[0..15)
497 for &flag in flags.iter().take(NUM_FLAGS - 1) {
498 ensure_eq!(zero, flag * (one - flag), "non-boolean flags");
499 }
500
501 // well formness of instruction
502 let shape = {
503 let shift = F::from(2u32.pow(15)); // 2^15;
504 let pow16 = shift.double(); // 2^16
505 let dst_sft = off_dst + shift;
506 let op0_sft = off_op0 + shift;
507 let op1_sft = off_op1 + shift;
508 // recompose instruction as: flags[14..0] | op1_sft | op0_sft | dst_sft
509 let mut aux = flags[14];
510 for i in (0..14).rev() {
511 aux = aux * F::from(2u32) + flags[i];
512 }
513 // complete with "flags" * 2^48 + op1_sft * 2^32 + op0_sft * 2^16 + dst_sft
514 ((aux * pow16 + op1_sft) * pow16 + op0_sft) * pow16 + dst_sft
515 };
516 ensure_eq!(
517 zero,
518 instr - shape,
519 "wrong decomposition of the instruction"
520 );
521
522 // check no two flags of same set are nonzero
523 let op1_set = f_op1_ap + f_op1_fp + f_op1_val;
524 let res_set = f_res_mul + f_res_add;
525 let pc_set = f_pc_jnz + f_pc_rel + f_pc_abs;
526 let ap_set = f_ap_one + f_ap_inc;
527 let opcode_set = f_opc_aeq + f_opc_ret + f_opc_call;
528 ensure_eq!(
529 zero,
530 op1_set * (one - op1_set),
531 "invalid format of `op1_src`"
532 );
533
534 ensure_eq!(
535 zero,
536 res_set * (one - res_set),
537 "invalid format of `res_log`"
538 );
539 ensure_eq!(zero, pc_set * (one - pc_set), "invalid format of `pc_up`");
540 ensure_eq!(zero, ap_set * (one - ap_set), "invalid format of `ap_up`");
541 ensure_eq!(
542 zero,
543 opcode_set * (one - opcode_set),
544 "invalid format of `opcode`"
545 );
546
547 // OPERANDS RELATED
548
549 // * Destination address
550 // if dst_reg = 0 : dst_dir = ap + off_dst
551 // if dst_reg = 1 : dst_dir = fp + off_dst
552 ensure_eq!(
553 adr_dst,
554 f_dst_fp * fp + (one - f_dst_fp) * ap + off_dst,
555 "invalid destination address"
556 );
557
558 // * First operand address
559 // if op0_reg = 0 : op0_dir = ap + off_dst
560 // if op0_reg = 1 : op0_dir = fp + off_dst
561 ensure_eq!(
562 adr_op0,
563 f_op0_fp * fp + (one - f_op0_fp) * ap + off_op0,
564 "invalid first operand address"
565 );
566
567 // * Second operand address
568 ensure_eq!(
569 adr_op1, // op1_dir = ..
570 (f_op1_ap * ap // if op1_src == 4 : ap
571 + f_op1_fp * fp // if op1_src == 2 : fp
572 + f_op1_val * pc // if op1_src == 1 : pc
573 + (one - f_op1_fp - f_op1_ap - f_op1_val) * op0 // if op1_src == 0 : op0
574 + off_op1), // + off_op1
575 "invalid second operand address"
576 );
577
578 // OPERATIONS RELATED
579
580 // * Check value of result
581 ensure_eq!(
582 (one - f_pc_jnz) * res, // if pc_up != 4 : res = .. // no res in conditional jumps
583 f_res_mul * op0 * op1 // if res_log = 2 : op0 * op1
584 + f_res_add * (op0 + op1) // if res_log = 1 : op0 + op1
585 + (one - f_res_add - f_res_mul) * op1, // if res_log = 0 : op1
586 "invalid result"
587 );
588
589 // * Check storage of current fp for a call instruction
590 ensure_eq!(
591 zero,
592 f_opc_call * (dst - fp),
593 "current fp after call not stored"
594 ); // if opcode = 1 : dst = fp
595
596 // * Check storage of next instruction after a call instruction
597 ensure_eq!(
598 zero,
599 f_opc_call * (op0 - (pc + size)),
600 "next instruction after call not stored"
601 ); // if opcode = 1 : op0 = pc + size
602
603 // * Check destination = result after assert-equal
604 ensure_eq!(zero, f_opc_aeq * (dst - res), "false assert equal"); // if opcode = 4 : dst = res
605
606 Ok(())
607 }
608
609 fn ensure_flags<F: FftField>(curr: &[F], next: &[F]) -> Result<(), String> {
610 let f_pc_abs = curr[7];
611 let f_pc_rel = curr[8];
612 let f_pc_jnz = curr[9];
613 let f_ap_inc = curr[10];
614 let f_ap_one = curr[11];
615 let f_opc_call = curr[12];
616 let f_opc_ret = curr[13];
617 let pc = next[0];
618 let ap = next[1];
619 let fp = next[2];
620 let size = next[3];
621 let res = next[4];
622 let dst = next[5];
623 let op1 = next[6];
624 let pcup = next[7];
625 let apup = next[8];
626 let fpup = next[9];
627
628 let zero = F::zero();
629 let one = F::one();
630 let two = F::from(2u16);
631
632 // REGISTERS RELATED
633
634 // * Check next allocation pointer
635 ensure_eq!(
636 apup, // next_ap =
637 ap // ap +
638 + f_ap_inc * res // if ap_up == 1 : res
639 + f_ap_one // if ap_up == 2 : 1
640 + f_opc_call.double(), // if opcode == 1 : 2
641 "wrong ap update"
642 );
643
644 // * Check next frame pointer
645 ensure_eq!(
646 fpup, // next_fp =
647 f_opc_call * (ap + two) // if opcode == 1 : ap + 2
648 + f_opc_ret * dst // if opcode == 2 : dst
649 + (one - f_opc_call - f_opc_ret) * fp, // if opcode == 4 or 0 : fp
650 "wrong fp update"
651 );
652
653 // * Check next program counter
654 ensure_eq!(
655 zero,
656 f_pc_jnz * (dst * res - one) * (pcup - (pc + size)), // <=> pc_up = 4 and dst = 0 : next_pc = pc + size // no jump
657 "wrong pc update"
658 );
659 ensure_eq!(
660 zero,
661 f_pc_jnz * dst * (pcup - (pc + op1)) // <=> pc_up = 4 and dst != 0 : next_pc = pc + op1 // condition holds
662 + (one - f_pc_jnz) * pcup // <=> pc_up = {0,1,2} : next_pc = ... // not a conditional jump
663 - (one - f_pc_abs - f_pc_rel - f_pc_jnz) * (pc + size) // <=> pc_up = 0 : next_pc = pc + size // common case
664 - f_pc_abs * res // <=> pc_up = 1 : next_pc = res // absolute jump
665 - f_pc_rel * (pc + res), // <=> pc_up = 2 : next_pc = pc + res // relative jump
666 "wrong pc update"
667 );
668
669 Ok(())
670 }
671
672 fn ensure_transition<F: FftField>(curr: &[F], next: &[F]) -> Result<(), String> {
673 let pcup = curr[7];
674 let apup = curr[8];
675 let fpup = curr[9];
676 let next_pc = next[0];
677 let next_ap = next[1];
678 let next_fp = next[2];
679
680 // REGISTERS RELATED
681
682 // * Check next allocation pointer
683 ensure_eq!(next_ap, apup, "wrong next allocation pointer");
684
685 // * Check next frame pointer
686 ensure_eq!(next_fp, fpup, "wrong next frame pointer");
687
688 // * Check next program counter
689 ensure_eq!(next_pc, pcup, "wrong next program counter");
690
691 Ok(())
692 }
693
694 fn ensure_claim<F: FftField>(claim: &[F], next: &[F]) -> Result<(), String> {
695 let pc_ini = claim[0];
696 let ap_ini = claim[1];
697 let pc_fin = claim[2];
698 let ap_fin = claim[3];
699 let pc_n = claim[4];
700 let ap_n = claim[5];
701 let pc0 = next[0];
702 let ap0 = next[1];
703 let fp0 = next[2];
704
705 // * Check initial pc, ap, fp and final pc, ap
706 ensure_eq!(F::zero(), pc0 - pc_ini, "wrong initial pc");
707 ensure_eq!(F::zero(), ap0 - ap_ini, "wrong initial ap");
708 ensure_eq!(F::zero(), fp0 - ap_ini, "wrong initial fp");
709 ensure_eq!(F::zero(), pc_n - pc_fin, "wrong final pc");
710 ensure_eq!(F::zero(), ap_n - ap_fin, "wrong final ap");
711
712 Ok(())
713 }
714}
715
716//~ The Kimchi 15 columns could be:
717//~ GateType Claim Instruction Zero | (Flags+Transition+Aux)
718//~ row -> 0 4i+1 4i+2 4i+3 4n-2
719//~ ---------------------------------------------------------------------------------
720//~ 0 · ® pc_ini pc fDST_FP © pc © next_pc
721//~ 1 · ® ap_ini ap fOP0_FP © ap © next_ap
722//~ c 2 · ® pc_fin fp fOP1_VAL © fp © next_fp
723//~ o 3 · ® ap_fin size fOP1_FP © size
724//~ l 4 · © pc\[n-1\] res fOP1_AP © res
725//~ | 5 · © ap\[n-1\] dst fRES_ADD © dst
726//~ v 6 · op1 fRES_MUL © op1
727//~ 7 op0 fPC_ABS pcup
728//~ 8 off_dst fPC_REL apup
729//~ 9 off_op1 fPC_JNZ fpup
730//~ 10 off_op0 fAP_ADD
731//~ 11 adr_dst fAP_ONE
732//~ 12 adr_op1 fOPC_CALL
733//~ 13 adr_op0 fOPC_RET
734//~ 14 instr
735
736// CONSTRAINTS-RELATED
737
738/// Returns the expression corresponding to the literal "2"
739fn two<F: Field, T: ExprOps<F, BerkeleyChallengeTerm>>() -> T {
740 T::literal(2u64.into()) // 2
741}
742
743/// Combines the constraints for the Cairo gates depending on its type
744///
745/// # Panics
746///
747/// Will panic if the `typ` is not `Cairo`-related gate type or `zero` gate type.
748pub fn circuit_gate_combined_constraints<F: PrimeField>(
749 typ: GateType,
750 alphas: &Alphas<F>,
751 cache: &mut Cache,
752) -> E<F> {
753 match typ {
754 GateType::CairoClaim => Claim::combined_constraints(alphas, cache),
755 GateType::CairoInstruction => Instruction::combined_constraints(alphas, cache),
756 GateType::CairoFlags => Flags::combined_constraints(alphas, cache),
757 GateType::CairoTransition => Transition::combined_constraints(alphas, cache),
758 GateType::Zero => E::literal(F::zero()),
759 _ => panic!("invalid gate type"),
760 }
761}
762
763pub struct Claim<F>(PhantomData<F>);
764
765impl<F> Argument<F> for Claim<F>
766where
767 F: PrimeField,
768{
769 const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CairoClaim);
770 const CONSTRAINTS: u32 = 5;
771
772 /// Generates the constraints for the Cairo initial claim and first memory checks
773 /// Accesses Curr and Next rows
774 fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
775 env: &ArgumentEnv<F, T>,
776 _cache: &mut Cache,
777 ) -> Vec<T> {
778 let pc_ini = env.witness_curr(0); // copy from public input
779 let ap_ini = env.witness_curr(1); // copy from public input
780 let pc_fin = env.witness_curr(2); // copy from public input
781 let ap_fin = env.witness_curr(3); // copy from public input
782 let pc_n = env.witness_curr(4); // copy from public input
783 let ap_n = env.witness_curr(5); // copy from public input
784
785 // load address / value pairs from next row
786 let pc0 = env.witness_next(0);
787 let ap0 = env.witness_next(1);
788 let fp0 = env.witness_next(2);
789
790 // Initial claim
791 let mut constraints: Vec<T> = vec![ap0 - ap_ini.clone()]; // ap0 = ini_ap
792 constraints.push(fp0 - ap_ini); // fp0 = ini_ap
793 constraints.push(pc0 - pc_ini); // pc0 = ini_pc
794
795 // Final claim
796 constraints.push(ap_n - ap_fin);
797 constraints.push(pc_n - pc_fin);
798
799 constraints
800 }
801}
802
803pub struct Instruction<F>(PhantomData<F>);
804
805impl<F> Argument<F> for Instruction<F>
806where
807 F: PrimeField,
808{
809 const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CairoInstruction);
810 const CONSTRAINTS: u32 = 28;
811
812 /// Generates the constraints for the Cairo instruction
813 /// Accesses Curr and Next rows
814 fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
815 env: &ArgumentEnv<F, T>,
816 cache: &mut Cache,
817 ) -> Vec<T> {
818 // load all variables of the witness corresponding to Cairoinstruction gates
819 let pc = env.witness_curr(0);
820 let ap = env.witness_curr(1);
821 let fp = env.witness_curr(2);
822 let size = env.witness_curr(3);
823 let res = env.witness_curr(4);
824 let dst = env.witness_curr(5);
825 let op1 = env.witness_curr(6);
826 let op0 = env.witness_curr(7);
827 let off_dst = env.witness_curr(8);
828 let off_op1 = env.witness_curr(9);
829 let off_op0 = env.witness_curr(10);
830 let adr_dst = env.witness_curr(11);
831 let adr_op1 = env.witness_curr(12);
832 let adr_op0 = env.witness_curr(13);
833 let instr = env.witness_curr(14);
834 // Load flags from the following row
835 let f_dst_fp = env.witness_next(0);
836 let f_op0_fp = env.witness_next(1);
837 let f_op1_val = env.witness_next(2);
838 let f_op1_fp = env.witness_next(3);
839 let f_op1_ap = env.witness_next(4);
840 let f_res_add = env.witness_next(5);
841 let f_res_mul = env.witness_next(6);
842 let f_pc_abs = env.witness_next(7);
843 let f_pc_rel = env.witness_next(8);
844 let f_pc_jnz = env.witness_next(9);
845 let f_ap_add = env.witness_next(10);
846 let f_ap_one = env.witness_next(11);
847 let f_opc_call = env.witness_next(12);
848 let f_opc_ret = env.witness_next(13);
849 let f_opc_aeq = env.witness_next(14);
850
851 // collect flags in its natural ordering
852 let flags: Vec<T> = (0..NUM_FLAGS - 1).map(|i| env.witness_next(i)).collect();
853
854 // LIST OF CONSTRAINTS
855 // -------------------
856 let mut constraints: Vec<T> = vec![];
857
858 // INSTRUCTIONS RELATED
859
860 // * Check last flag is always zero is redundant with wellformness check
861
862 // * Check booleanity of all flags
863 // fi * (1-fi) == 0 for i=[0..15)
864 for flag in flags.iter().take(NUM_FLAGS - 1) {
865 constraints.push(flag.clone() * (T::one() - flag.clone()));
866 }
867
868 // * Check no two flagbits of the same flagset are nonzero
869 // TODO(querolita): perhaps these are redundant considering all of the logics below
870 let op1_src = cache.cache(f_op1_ap.clone() + f_op1_fp.clone() + f_op1_val.clone());
871 let res_log = cache.cache(f_res_mul.clone() + f_res_add.clone());
872 let pc_up = cache.cache(f_pc_jnz.clone() + f_pc_rel + f_pc_abs);
873 let ap_up = cache.cache(f_ap_one + f_ap_add);
874 let opcode = cache.cache(f_opc_aeq.clone() + f_opc_ret + f_opc_call.clone());
875 constraints.push(op1_src.clone() * (T::one() - op1_src));
876 constraints.push(res_log.clone() * (T::one() - res_log));
877 constraints.push(pc_up.clone() * (T::one() - pc_up));
878 constraints.push(ap_up.clone() * (T::one() - ap_up));
879 constraints.push(opcode.clone() * (T::one() - opcode));
880
881 // * Shape of instruction
882 let shape = {
883 let shift: T = cache.cache(two::<F, T>().pow(15)); // 2^15;
884 let double_shift = shift.double();
885 let pow16 = cache.cache(double_shift); // 2^16
886 let dst_sft = off_dst.clone() + shift.clone();
887 let op0_sft = off_op0.clone() + shift.clone();
888 let op1_sft = off_op1.clone() + shift;
889 // recompose instruction as: flags[14..0] | op1_sft | op0_sft | dst_sft
890 let mut aux: T = flags[14].clone();
891 for i in (0..14).rev() {
892 aux = aux * two() + flags[i].clone();
893 }
894 // complete with "flags" * 2^48 + op1_sft * 2^32 + op0_sft * 2^16 + dst_sft
895 aux = ((aux * pow16.clone() + op1_sft) * pow16.clone() + op0_sft) * pow16 + dst_sft;
896 aux
897 };
898 constraints.push(instr - shape);
899
900 // OPERANDS RELATED
901
902 // * Destination address
903 // if dst_fp = 0 : dst_dir = ap + off_dst
904 // if dst_fp = 1 : dst_dir = fp + off_dst
905 constraints.push(
906 f_dst_fp.clone() * fp.clone() + (T::one() - f_dst_fp) * ap.clone() + off_dst - adr_dst,
907 );
908
909 // * First operand address
910 // if op0_fp = 0 : op0_dir = ap + off_dst
911 // if op0_fp = 1 : op0_dir = fp + off_dst
912 constraints.push(
913 f_op0_fp.clone() * fp.clone() + (T::one() - f_op0_fp) * ap.clone() + off_op0 - adr_op0,
914 );
915
916 // * Second operand address
917 constraints.push(
918 adr_op1 // op1_dir = ..
919 - (f_op1_ap.clone() * ap // if op1_src == 4 : ap
920 + f_op1_fp.clone() * fp.clone() // if op1_src == 2 : fp
921 + f_op1_val.clone() * pc.clone() // if op1_src == 1 : pc
922 + (T::one() - f_op1_fp - f_op1_ap - f_op1_val) * op0.clone() // if op1_src == 0 : op0
923 + off_op1), // + off_op1
924 );
925
926 // OPERATIONS-RELATED
927
928 // * Check value of result
929 constraints.push(
930 (T::one() - f_pc_jnz) * res.clone() // if pc_up != 4 : res = .. // no res in conditional jumps
931 - (f_res_mul.clone() * op0.clone() * op1.clone() // if res_log = 2 : op0 * op1
932 + f_res_add.clone() * (op0.clone() + op1.clone()) // if res_log = 1 : op0 + op1
933 + (T::one() - f_res_add - f_res_mul) * op1), // if res_log = 0 : op1
934 );
935
936 // * Check storage of current fp for a call instruction
937 // <=> assert_eq!(dst, fp);
938 constraints.push(f_opc_call.clone() * (dst.clone() - fp)); // if opcode = 1 : dst = fp
939
940 // * Check storage of next instruction after a call instruction
941 // <=> assert_eq!(op0, pc + size); // checks [ap+1] contains instruction after call
942 constraints.push(f_opc_call * (op0 - (pc + size))); // if opcode = 1 : op0 = pc + size
943
944 // * Check destination = result after assert-equal
945 // <=> assert_eq!(res, dst);
946 constraints.push(f_opc_aeq * (dst - res)); // if opcode = 4 : dst = res
947
948 constraints
949 }
950}
951
952pub struct Flags<F>(PhantomData<F>);
953
954impl<F> Argument<F> for Flags<F>
955where
956 F: PrimeField,
957{
958 const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CairoFlags);
959 const CONSTRAINTS: u32 = 4;
960
961 /// Generates the constraints for the Cairo flags
962 /// Accesses Curr and Next rows
963 fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
964 env: &ArgumentEnv<F, T>,
965 _cache: &mut Cache,
966 ) -> Vec<T> {
967 // Load current row
968 let f_pc_abs = env.witness_curr(7);
969 let f_pc_rel = env.witness_curr(8);
970 let f_pc_jnz = env.witness_curr(9);
971 let f_ap_add = env.witness_curr(10);
972 let f_ap_one = env.witness_curr(11);
973 let f_opc_call = env.witness_curr(12);
974 let f_opc_ret = env.witness_curr(13);
975 // Load next row
976 let pc = env.witness_next(0);
977 let ap = env.witness_next(1);
978 let fp = env.witness_next(2);
979 let size = env.witness_next(3);
980 let res = env.witness_next(4);
981 let dst = env.witness_next(5);
982 let op1 = env.witness_next(6);
983 let pcup = env.witness_next(7);
984 let apup = env.witness_next(8);
985 let fpup = env.witness_next(9);
986
987 // REGISTERS-RELATED
988 // * Check next allocation pointer
989 // next_ap =
990 // ap +
991 // if ap_up == 1 : res
992 // if ap_up == 2 : 1
993 // if opcode == 1 : 2
994 let mut constraints: Vec<T> =
995 vec![apup - (ap.clone() + f_ap_add * res.clone() + f_ap_one + f_opc_call.double())];
996
997 // * Check next frame pointer
998 constraints.push(
999 fpup // next_fp =
1000 - (f_opc_call.clone() * (ap + two()) // if opcode == 1 : ap + 2
1001 + f_opc_ret.clone() * dst.clone() // if opcode == 2 : dst
1002 + (T::one() - f_opc_call - f_opc_ret) * fp ), // if opcode == 4 or 0 : fp
1003 );
1004
1005 // * Check next program counter (pc update)
1006 constraints.push(
1007 f_pc_jnz.clone()
1008 * (dst.clone() * res.clone() - T::one())
1009 * (pcup.clone() - (pc.clone() + size.clone())),
1010 ); // <=> pc_up = 4 and dst = 0 : next_pc = pc + size // no jump
1011 constraints.push(
1012 f_pc_jnz.clone() * dst * (pcup.clone() - (pc.clone() + op1)) // <=> pc_up = 4 and dst != 0 : next_pc = pc + op1 // condition holds
1013 + (T::one() - f_pc_jnz.clone()) * pcup // <=> pc_up = {0,1,2} : next_pc = ... // not a conditional jump
1014 - (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
1015 - f_pc_abs * res.clone() // <=> pc_up = 1 : next_pc = res // absolute jump
1016 - f_pc_rel * (pc + res), // <=> pc_up = 2 : next_pc = pc + res // relative jump
1017 );
1018 constraints
1019 }
1020}
1021
1022pub struct Transition<F>(PhantomData<F>);
1023
1024impl<F> Argument<F> for Transition<F>
1025where
1026 F: PrimeField,
1027{
1028 const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CairoTransition);
1029 const CONSTRAINTS: u32 = 3;
1030
1031 /// Generates the constraints for the Cairo transition
1032 /// Accesses Curr and Next rows (Next only first 3 entries)
1033 fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
1034 env: &ArgumentEnv<F, T>,
1035 _cache: &mut Cache,
1036 ) -> Vec<T> {
1037 // load computed updated registers
1038 let pcup = env.witness_curr(7);
1039 let apup = env.witness_curr(8);
1040 let fpup = env.witness_curr(9);
1041 // load next registers
1042 let next_pc = env.witness_next(0);
1043 let next_ap = env.witness_next(1);
1044 let next_fp = env.witness_next(2);
1045
1046 // * Check equality (like a copy constraint)
1047
1048 let constraints: Vec<T> = vec![next_pc - pcup, next_ap - apup, next_fp - fpup];
1049
1050 constraints
1051 }
1052}