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}