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}