Skip to main content

midnight_circuits/instructions/
control_flow.rs

1// This file is part of MIDNIGHT-ZK.
2// Copyright (C) Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! Control flow instructions interface.
15//!
16//! It provides functions for conditionally selecting and asserting equality a
17//! pair of `Assigned` elements.
18//!
19//! The trait is parametrized by `Assigned` type.
20
21use midnight_proofs::{circuit::Layouter, plonk::Error};
22
23use super::AssertionInstructions;
24use crate::{
25    types::{AssignedBit, InnerValue},
26    CircuitField,
27};
28
29/// The set of circuit instructions for control flow operations.
30pub trait ControlFlowInstructions<F: CircuitField, Assigned>:
31    AssertionInstructions<F, Assigned>
32where
33    Assigned: InnerValue,
34{
35    /// Returns `x` if `cond = true` and `y` otherwise.
36    /// ```
37    /// # midnight_circuits::run_test_native_gadget!(chip, layouter, {
38    /// let x: AssignedNative<F> = chip.assign(&mut layouter, Value::known(F::ZERO))?;
39    /// let y: AssignedNative<F> = chip.assign(&mut layouter, Value::known(F::ONE))?;
40    /// let cond: AssignedBit<F> = chip.assign(&mut layouter, Value::known(true))?;
41    ///
42    /// let choice = chip.select(&mut layouter, &cond, &x, &y)?;
43    /// chip.assert_equal(&mut layouter, &choice, &x)?;
44    /// # });
45    /// ```
46    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    /// Equality assertion only if `cond` is set to `1`.
55    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    /// Swaps two elements `x` and `y` only if `cond` is set to `1`.
67    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}