Skip to main content

midnight_circuits/instructions/
zero.rs

1// This file is part of MIDNIGHT-ZK.
2// Copyright (C) 2025 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//! Zero instructions interface.
15//!
16//! It provides an interface for comparing assigned values with zero.
17//!
18//! Implementors of this trait need to implement [AssertionInstructions]
19//! and [EqualityInstructions]. The trait is parametrized by `assigned`
20//! values that implement `InnerConstants`, which gives access to zero.
21
22use ff::PrimeField;
23use midnight_proofs::{circuit::Layouter, plonk::Error};
24
25use crate::{
26    instructions::{AssertionInstructions, EqualityInstructions},
27    types::{AssignedBit, InnerConstants},
28};
29
30/// The set of circuit instructions for zero equality and assertions.
31pub trait ZeroInstructions<F, Assigned>:
32    AssertionInstructions<F, Assigned> + EqualityInstructions<F, Assigned>
33where
34    F: PrimeField,
35    Assigned: InnerConstants,
36{
37    /// Enforces that the given assigned element is zero.
38    ///
39    /// ```
40    /// # midnight_circuits::run_test_native_gadget!(chip, layouter, {
41    /// let x: AssignedNative<F> = chip.assign(&mut layouter, Value::known(F::ZERO))?;
42    ///
43    /// // we can now assert that the value is zero (this, obviously, discloses the value)
44    /// chip.assert_zero(&mut layouter, &x)?;
45    /// # });
46    /// ```
47    fn assert_zero(&self, layouter: &mut impl Layouter<F>, x: &Assigned) -> Result<(), Error> {
48        self.assert_equal_to_fixed(layouter, x, Assigned::inner_zero())
49    }
50
51    /// Asserts that the given element is non-zero.
52    fn assert_non_zero(&self, layouter: &mut impl Layouter<F>, x: &Assigned) -> Result<(), Error> {
53        self.assert_not_equal_to_fixed(layouter, x, Assigned::inner_zero())
54    }
55
56    /// Returns `1` iff the given element equals zero (the additive identity).
57    ///
58    /// ```
59    /// # midnight_circuits::run_test_native_gadget!(chip, layouter, {
60    /// let x: AssignedNative<F> = chip.assign(&mut layouter, Value::known(F::ZERO))?;
61    ///
62    /// // the following value should be constrained further
63    /// let cond: AssignedBit<F> = chip.is_zero(&mut layouter, &x)?;
64    /// # });
65    /// ```
66    fn is_zero(
67        &self,
68        layouter: &mut impl Layouter<F>,
69        x: &Assigned,
70    ) -> Result<AssignedBit<F>, Error> {
71        self.is_equal_to_fixed(layouter, x, Assigned::inner_zero())
72    }
73}
74
75#[cfg(test)]
76pub(crate) mod tests {
77    use std::marker::PhantomData;
78
79    use ff::FromUniformBytes;
80    use midnight_proofs::{
81        circuit::{Layouter, SimpleFloorPlanner},
82        dev::MockProver,
83        plonk::{Circuit, ConstraintSystem},
84    };
85    use rand::SeedableRng;
86    use rand_chacha::ChaCha8Rng;
87
88    use super::*;
89    use crate::{
90        instructions::AssignmentInstructions,
91        testing_utils::{FromScratch, Sampleable},
92        types::{AssignedNative, InnerValue},
93        utils::circuit_modeling::circuit_to_json,
94    };
95
96    #[derive(Clone, Debug)]
97    enum Operation {
98        Assert,
99        AssertNon,
100        IsZero,
101    }
102
103    #[derive(Clone, Debug)]
104    struct TestCircuit<F, Assigned, ZeroChip>
105    where
106        Assigned: InnerValue,
107    {
108        x: Assigned::Element,
109        expected: Option<bool>,
110        operation: Operation,
111        _marker: PhantomData<(F, Assigned, ZeroChip)>,
112    }
113
114    impl<F, Assigned, ZeroChip> Circuit<F> for TestCircuit<F, Assigned, ZeroChip>
115    where
116        F: PrimeField,
117        Assigned: InnerConstants,
118        ZeroChip:
119            ZeroInstructions<F, Assigned> + AssignmentInstructions<F, Assigned> + FromScratch<F>,
120    {
121        type Config = <ZeroChip as FromScratch<F>>::Config;
122        type FloorPlanner = SimpleFloorPlanner;
123        type Params = ();
124
125        fn without_witnesses(&self) -> Self {
126            unreachable!()
127        }
128
129        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
130            let committed_instance_column = meta.instance_column();
131            let instance_column = meta.instance_column();
132            let constants_column = meta.fixed_column();
133            meta.enable_constant(constants_column);
134            ZeroChip::configure_from_scratch(meta, &[committed_instance_column, instance_column])
135        }
136
137        fn synthesize(
138            &self,
139            config: Self::Config,
140            mut layouter: impl Layouter<F>,
141        ) -> Result<(), Error> {
142            let chip = ZeroChip::new_from_scratch(&config);
143
144            let x = chip.assign_fixed(&mut layouter, self.x.clone())?;
145            match self.operation {
146                Operation::Assert => chip.assert_zero(&mut layouter, &x),
147                Operation::AssertNon => chip.assert_non_zero(&mut layouter, &x),
148                Operation::IsZero => {
149                    let res = chip.is_zero(&mut layouter, &x)?;
150                    let res_as_value: AssignedNative<F> = res.into();
151                    layouter.assign_region(
152                        || "assert contains fixed",
153                        |mut region| {
154                            region.constrain_constant(
155                                res_as_value.cell(),
156                                if self.expected.unwrap() {
157                                    F::ONE
158                                } else {
159                                    F::ZERO
160                                },
161                            )
162                        },
163                    )
164                }
165            }?;
166
167            chip.load_from_scratch(&mut layouter)
168        }
169    }
170
171    fn run<F, Assigned, ZeroChip>(
172        x: &Assigned::Element,
173        expected: Option<bool>,
174        operation: Operation,
175        must_pass: bool,
176        cost_model: bool,
177        chip_name: &str,
178        op_name: &str,
179    ) where
180        F: PrimeField + FromUniformBytes<64> + Ord,
181        Assigned: InnerConstants,
182        ZeroChip:
183            ZeroInstructions<F, Assigned> + AssignmentInstructions<F, Assigned> + FromScratch<F>,
184    {
185        let circuit = TestCircuit::<F, Assigned, ZeroChip> {
186            x: x.clone(),
187            expected,
188            operation,
189            _marker: PhantomData,
190        };
191
192        let log2_nb_rows = 10;
193        let public_inputs = vec![vec![], vec![]];
194        match MockProver::run(log2_nb_rows, &circuit, public_inputs) {
195            Ok(prover) => match prover.verify() {
196                Ok(()) => assert!(must_pass),
197                Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
198            },
199            Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
200        }
201
202        if cost_model {
203            circuit_to_json(chip_name, op_name, circuit);
204        }
205    }
206
207    pub fn test_zero_assertions<F, Assigned, ZeroChip>(name: &str)
208    where
209        F: PrimeField + FromUniformBytes<64> + Ord,
210        Assigned: InnerConstants + Sampleable,
211        ZeroChip:
212            ZeroInstructions<F, Assigned> + AssignmentInstructions<F, Assigned> + FromScratch<F>,
213    {
214        let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
215        let mut cost_model = true;
216        [
217            (Assigned::sample_inner(&mut rng), false),
218            (Assigned::inner_zero(), true),
219            (Assigned::inner_one(), false),
220        ]
221        .into_iter()
222        .for_each(|(x, is_zero)| {
223            run::<F, Assigned, ZeroChip>(
224                &x,
225                None,
226                Operation::Assert,
227                is_zero,
228                cost_model,
229                name,
230                "assert_zero",
231            );
232            run::<F, Assigned, ZeroChip>(
233                &x,
234                None,
235                Operation::AssertNon,
236                !is_zero,
237                cost_model,
238                name,
239                "assert_non_zero",
240            );
241            cost_model = false;
242        });
243    }
244
245    pub fn test_is_zero<F, Assigned, ZeroChip>(name: &str)
246    where
247        F: PrimeField + FromUniformBytes<64> + Ord,
248        Assigned: InnerConstants + Sampleable,
249        ZeroChip:
250            ZeroInstructions<F, Assigned> + AssignmentInstructions<F, Assigned> + FromScratch<F>,
251    {
252        let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
253        let mut cost_model = true;
254        [
255            (Assigned::sample_inner(&mut rng), false),
256            (Assigned::inner_zero(), true),
257            (Assigned::inner_one(), false),
258        ]
259        .into_iter()
260        .for_each(|(x, expected)| {
261            run::<F, Assigned, ZeroChip>(
262                &x,
263                Some(expected),
264                Operation::IsZero,
265                true,
266                cost_model,
267                name,
268                "is_zero",
269            );
270            run::<F, Assigned, ZeroChip>(
271                &x,
272                Some(!expected),
273                Operation::IsZero,
274                false,
275                false,
276                "",
277                "",
278            );
279            cost_model = false;
280        });
281    }
282}