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