1use ff::PrimeField;
22use midnight_proofs::{circuit::Layouter, plonk::Error};
23
24use super::AssertionInstructions;
25use crate::types::{AssignedBit, InnerValue};
26
27pub trait ControlFlowInstructions<F: PrimeField, Assigned>:
29 AssertionInstructions<F, Assigned>
30where
31 Assigned: InnerValue,
32{
33 fn select(
45 &self,
46 layouter: &mut impl Layouter<F>,
47 cond: &AssignedBit<F>,
48 x: &Assigned,
49 y: &Assigned,
50 ) -> Result<Assigned, Error>;
51
52 fn cond_assert_equal(
54 &self,
55 layouter: &mut impl Layouter<F>,
56 cond: &AssignedBit<F>,
57 x: &Assigned,
58 y: &Assigned,
59 ) -> Result<(), Error> {
60 let x = self.select(layouter, cond, x, y)?;
61 self.assert_equal(layouter, &x, y)
62 }
63
64 fn cond_swap(
66 &self,
67 layouter: &mut impl Layouter<F>,
68 cond: &AssignedBit<F>,
69 x: &Assigned,
70 y: &Assigned,
71 ) -> Result<(Assigned, Assigned), Error> {
72 let new_x = self.select(layouter, cond, y, x)?;
73 let new_y = self.select(layouter, cond, x, y)?;
74
75 Ok((new_x, new_y))
76 }
77}
78
79#[cfg(test)]
80pub(crate) mod tests {
81 use std::marker::PhantomData;
82
83 use ff::FromUniformBytes;
84 use midnight_proofs::{
85 circuit::{Layouter, SimpleFloorPlanner, Value},
86 dev::MockProver,
87 plonk::{Circuit, Column, ConstraintSystem, Fixed},
88 };
89 use rand::SeedableRng;
90 use rand_chacha::ChaCha8Rng;
91
92 use super::*;
93 use crate::{
94 instructions::{AssertionInstructions, AssignmentInstructions},
95 testing_utils::{FromScratch, Sampleable},
96 types::InnerValue,
97 utils::circuit_modeling::circuit_to_json,
98 };
99
100 #[derive(Clone, Debug)]
101 enum Operation {
102 Select,
103 CondAssertEqual,
104 CondSwap,
105 }
106
107 #[derive(Clone, Debug)]
108 struct TestCircuit<F, Assigned, ControlFlowChip>
109 where
110 Assigned: InnerValue,
111 {
112 x: Assigned::Element,
113 y: Assigned::Element,
114 cond: bool,
115 expected: Assigned::Element,
116 expected_extra: Option<Assigned::Element>,
117 operation: Operation,
118 _marker: PhantomData<(F, Assigned, ControlFlowChip)>,
119 }
120
121 impl<F, Assigned, ControlFlowChip> Circuit<F> for TestCircuit<F, Assigned, ControlFlowChip>
122 where
123 F: PrimeField,
124 Assigned: InnerValue,
125 Assigned::Element: Default,
126 ControlFlowChip: ControlFlowInstructions<F, Assigned>
127 + AssignmentInstructions<F, Assigned>
128 + AssertionInstructions<F, Assigned>
129 + FromScratch<F>,
130 {
131 type Config = (<ControlFlowChip as FromScratch<F>>::Config, Column<Fixed>);
132 type FloorPlanner = SimpleFloorPlanner;
133 type Params = ();
134
135 fn without_witnesses(&self) -> Self {
136 unreachable!()
137 }
138
139 fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
140 let committed_instance_column = meta.instance_column();
141 let instance_column = meta.instance_column();
142 let fixed_column = meta.fixed_column();
143 meta.enable_equality(fixed_column);
144 (
145 ControlFlowChip::configure_from_scratch(
146 meta,
147 &[committed_instance_column, instance_column],
148 ),
149 fixed_column,
150 )
151 }
152
153 fn synthesize(
154 &self,
155 (config, fixed_column): Self::Config,
156 mut layouter: impl Layouter<F>,
157 ) -> Result<(), Error> {
158 let chip = ControlFlowChip::new_from_scratch(&config);
159
160 let x = chip.assign_fixed(&mut layouter, self.x.clone())?;
161 let y = chip.assign_fixed(&mut layouter, self.y.clone())?;
162 let cond_value = layouter.assign_region(
163 || "Assign fixed",
164 |mut region| {
165 region.assign_fixed(
166 || "cond value",
167 fixed_column,
168 0,
169 || Value::known(if self.cond { F::ONE } else { F::ZERO }),
170 )
171 },
172 )?;
173
174 let cond = AssignedBit(cond_value);
175
176 match self.operation {
177 Operation::Select => {
178 let res = chip.select(&mut layouter, &cond, &x, &y)?;
179 chip.assert_equal_to_fixed(&mut layouter, &res, self.expected.clone())
180 }
181 Operation::CondAssertEqual => chip.cond_assert_equal(&mut layouter, &cond, &x, &y),
182 Operation::CondSwap => {
183 let (fst, snd) = chip.cond_swap(&mut layouter, &cond, &x, &y)?;
184 chip.assert_equal_to_fixed(&mut layouter, &fst, self.expected.clone())?;
185 chip.assert_equal_to_fixed(
186 &mut layouter,
187 &snd,
188 self.expected_extra.clone().unwrap(),
189 )
190 }
191 }?;
192
193 chip.load_from_scratch(&mut layouter)
194 }
195 }
196
197 #[allow(clippy::too_many_arguments)]
198 fn run<F, Assigned, ControlFlowChip>(
199 x: &Assigned::Element,
200 y: &Assigned::Element,
201 cond: bool,
202 expected: &Assigned::Element,
203 expected_extra: Option<&Assigned::Element>,
204 operation: Operation,
205 must_pass: bool,
206 cost_model: bool,
207 chip_name: &str,
208 op_name: &str,
209 ) where
210 F: PrimeField + FromUniformBytes<64> + Ord,
211 Assigned: InnerValue,
212 Assigned::Element: Default,
213 ControlFlowChip: ControlFlowInstructions<F, Assigned>
214 + AssignmentInstructions<F, Assigned>
215 + AssertionInstructions<F, Assigned>
216 + FromScratch<F>,
217 {
218 let circuit = TestCircuit::<F, Assigned, ControlFlowChip> {
219 x: x.clone(),
220 y: y.clone(),
221 cond,
222 expected: expected.clone(),
223 expected_extra: expected_extra.cloned(),
224 operation,
225 _marker: PhantomData,
226 };
227
228 let log2_nb_rows = 10;
229 let public_inputs = vec![vec![], vec![]];
230 match MockProver::run(log2_nb_rows, &circuit, public_inputs) {
231 Ok(prover) => match prover.verify() {
232 Ok(()) => assert!(must_pass),
233 Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
234 },
235 Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
236 }
237
238 if cost_model {
239 circuit_to_json(chip_name, op_name, circuit);
240 }
241 }
242
243 pub fn test_select<F, Assigned, ControlFlowChip>(name: &str)
244 where
245 F: PrimeField + FromUniformBytes<64> + Ord,
246 Assigned: InnerValue + Sampleable,
247 Assigned::Element: Default,
248 ControlFlowChip: ControlFlowInstructions<F, Assigned>
249 + AssignmentInstructions<F, Assigned>
250 + AssertionInstructions<F, Assigned>
251 + FromScratch<F>,
252 {
253 let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
254 let x = Assigned::sample_inner(&mut rng);
255 let y = Assigned::sample_inner(&mut rng);
256
257 let mut cost_model = true;
258 let mut test = |cond: bool, output: &Assigned::Element, must_pass: bool| {
259 run::<F, Assigned, ControlFlowChip>(
260 &x,
261 &y,
262 cond,
263 output,
264 None,
265 Operation::Select,
266 must_pass,
267 cost_model,
268 name,
269 "select",
270 );
271 cost_model = false;
272 };
273
274 test(true, &x, true);
275 test(false, &y, true);
276 test(true, &y, false);
277 test(false, &x, false);
278 }
279
280 pub fn test_cond_assert_equal<F, Assigned, ControlFlowChip>(name: &str)
281 where
282 F: PrimeField + FromUniformBytes<64> + Ord,
283 Assigned: InnerValue + Sampleable,
284 Assigned::Element: Default,
285 ControlFlowChip: ControlFlowInstructions<F, Assigned>
286 + AssignmentInstructions<F, Assigned>
287 + AssertionInstructions<F, Assigned>
288 + FromScratch<F>,
289 {
290 let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
291 let x = Assigned::sample_inner(&mut rng);
292 let y = Assigned::sample_inner(&mut rng);
293
294 let mut cost_model = true;
295 let mut test =
296 |inputs: (&Assigned::Element, &Assigned::Element), cond: bool, must_pass: bool| {
297 run::<F, Assigned, ControlFlowChip>(
298 inputs.0,
299 inputs.1,
300 cond,
301 &Assigned::Element::default(),
302 None,
303 Operation::CondAssertEqual,
304 must_pass,
305 cost_model,
306 name,
307 "cond_assert_equal",
308 );
309 cost_model = false;
310 };
311
312 test((&x, &x), true, true);
313 test((&x, &y), true, false);
314 test((&x, &x), false, true);
315 test((&x, &y), false, true);
316 }
317
318 pub fn test_cond_swap<F, Assigned, ControlFlowChip>(name: &str)
319 where
320 F: PrimeField + FromUniformBytes<64> + Ord,
321 Assigned: InnerValue + Sampleable,
322 Assigned::Element: Default,
323 ControlFlowChip: ControlFlowInstructions<F, Assigned>
324 + AssignmentInstructions<F, Assigned>
325 + AssertionInstructions<F, Assigned>
326 + FromScratch<F>,
327 {
328 let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
329 let x = Assigned::sample_inner(&mut rng);
330 let y = Assigned::sample_inner(&mut rng);
331
332 let mut cost_model = true;
333 let mut test =
334 |cond: bool, outputs: (&Assigned::Element, &Assigned::Element), must_pass: bool| {
335 run::<F, Assigned, ControlFlowChip>(
336 &x,
337 &y,
338 cond,
339 outputs.0,
340 Some(outputs.1),
341 Operation::CondSwap,
342 must_pass,
343 cost_model,
344 name,
345 "cond_swap",
346 );
347 cost_model = false;
348 };
349
350 test(true, (&y, &x), true);
351 test(false, (&x, &y), true);
352 test(true, (&x, &y), false);
353 test(false, (&y, &x), false);
354
355 test(true, (&x, &x), false);
356 test(true, (&y, &y), false);
357 }
358}