Skip to main content

midnight_circuits/instructions/
canonicity.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//! Canonicity instructions interface.
15//!
16//! It provides functions for checking if assigned bits, representing a value
17//! `k`, are canonical with respect to some field order or a given bound `n`,
18//! i.e. iff `|bits| <= n::NUM_BITS` and `k`, interpreted in little-endian,
19//! is strictly lower than `n`.
20//!
21//! The implementors of this trait need to implement [FieldInstructions]
22//! where the notion of `canonical` makes sense.
23
24use midnight_proofs::{circuit::Layouter, plonk::Error};
25use num_bigint::BigUint;
26
27use crate::{
28    instructions::{AssignmentInstructions, FieldInstructions},
29    types::{AssignedBit, InnerConstants, Instantiable},
30    CircuitField,
31};
32
33/// The set of circuit instructions for canonicity assertions.
34pub trait CanonicityInstructions<F, Assigned>:
35    FieldInstructions<F, Assigned> + AssignmentInstructions<F, AssignedBit<F>>
36where
37    F: CircuitField,
38    Assigned::Element: CircuitField,
39    Assigned: Instantiable<F> + InnerConstants + Clone,
40{
41    /// Returns `true` iff the given sequence of bits is canonical in the
42    /// underlying field `Assigned::Element`. Namely, iff
43    /// `|bits| <= Assigned::Element::NUM_BITS` and the integer represented by
44    /// the given sequence of assigned bits, interpreted in little-endian,
45    /// is strictly lower than the order of `Assigned::Element`.
46    /// ```
47    /// # midnight_circuits::run_test_native_gadget!(chip, layouter, {
48    /// let x: Vec<AssignedBit<F>> = chip.assign_many(
49    ///     &mut layouter,
50    ///     &[
51    ///         Value::known(true),
52    ///         Value::known(false),
53    ///         Value::known(false),
54    ///         Value::known(true),
55    ///         Value::known(false),
56    ///     ],
57    /// )?;
58    ///
59    /// let check: AssignedBit<F> = chip.is_canonical(&mut layouter, &x)?;
60    /// // This is not sufficient to check that the value is canonical,
61    /// // we need to check that the output is true.
62    /// chip.assert_equal_to_fixed(&mut layouter, &check, true)?;
63    /// # });
64    /// ```
65    fn is_canonical(
66        &self,
67        layouter: &mut impl Layouter<F>,
68        bits: &[AssignedBit<F>],
69    ) -> Result<AssignedBit<F>, Error> {
70        let order = self.order();
71        if bits.len() > order.bits() as usize {
72            self.assign_fixed(layouter, false)
73        } else {
74            self.le_bits_lower_than(layouter, bits, order)
75        }
76    }
77
78    /// Returns `true` iff the integer represented by the given sequence of
79    /// assigned bits, interpreted in little-endian, is strictly lower than the
80    /// given bound.
81    /// ```
82    /// # use num_bigint::BigUint;
83    /// # midnight_circuits::run_test_native_gadget!(chip, layouter, {
84    /// let x: Vec<AssignedBit<F>> = chip.assign_many(
85    ///     &mut layouter,
86    ///     &[
87    ///         Value::known(true),
88    ///         Value::known(false),
89    ///         Value::known(false),
90    ///         Value::known(true),
91    ///         Value::known(true),
92    ///     ],
93    /// )?;
94    ///
95    /// // assert the value is less than 32
96    /// let check1: AssignedBit<F> = chip.le_bits_lower_than(&mut layouter, &x, BigUint::from(32u8))?;
97    /// chip.assert_equal_to_fixed(&mut layouter, &check1, true)?;
98    ///
99    /// // we can also compare the number with non-powers of two
100    /// let check2: AssignedBit<F> = chip.le_bits_lower_than(&mut layouter, &x, BigUint::from(17u8))?;
101    /// chip.assert_equal_to_fixed(&mut layouter, &check2, false)?;
102    /// # });
103    /// ```
104    fn le_bits_lower_than(
105        &self,
106        layouter: &mut impl Layouter<F>,
107        bits: &[AssignedBit<F>],
108        bound: BigUint,
109    ) -> Result<AssignedBit<F>, Error>;
110
111    /// Returns `true` iff the integer represented by the given sequence of
112    /// assigned bits, interpreted in little-endian, is greater than or equal
113    /// to the given bound.
114    fn le_bits_geq_than(
115        &self,
116        layouter: &mut impl Layouter<F>,
117        bits: &[AssignedBit<F>],
118        bound: BigUint,
119    ) -> Result<AssignedBit<F>, Error>;
120}
121
122#[cfg(test)]
123pub(crate) mod tests {
124    use std::marker::PhantomData;
125
126    use ff::{FromUniformBytes, PrimeField};
127    use midnight_proofs::{
128        circuit::{Layouter, SimpleFloorPlanner},
129        dev::MockProver,
130        plonk::{Circuit, ConstraintSystem},
131    };
132    use num_traits::{One, Zero};
133    use rand::{RngCore, SeedableRng};
134    use rand_chacha::ChaCha8Rng;
135
136    use super::*;
137    use crate::{
138        instructions::{AssertionInstructions, AssignmentInstructions},
139        types::InnerValue,
140        utils::{
141            circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
142            util::FromScratch,
143        },
144    };
145
146    #[derive(Clone, Debug)]
147    enum Operation {
148        Canonical,
149        Lower,
150        Geq,
151    }
152
153    #[derive(Clone, Debug)]
154    struct TestCircuit<F, Assigned, CanonicityChip>
155    where
156        Assigned: InnerValue,
157    {
158        bits: Vec<bool>,
159        bound: BigUint,
160        expected: bool,
161        operation: Operation,
162        _marker: PhantomData<(F, Assigned, CanonicityChip)>,
163    }
164
165    impl<F, Assigned, CanonicityChip> Circuit<F> for TestCircuit<F, Assigned, CanonicityChip>
166    where
167        F: CircuitField,
168        Assigned::Element: CircuitField,
169        Assigned: Instantiable<F> + InnerConstants + Clone,
170        CanonicityChip: CanonicityInstructions<F, Assigned>
171            + AssertionInstructions<F, Assigned>
172            + AssertionInstructions<F, AssignedBit<F>>
173            + AssignmentInstructions<F, Assigned>
174            + FromScratch<F>,
175    {
176        type Config = <CanonicityChip as FromScratch<F>>::Config;
177        type FloorPlanner = SimpleFloorPlanner;
178        type Params = ();
179
180        fn without_witnesses(&self) -> Self {
181            unreachable!()
182        }
183
184        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
185            let committed_instance_column = meta.instance_column();
186            let instance_column = meta.instance_column();
187            CanonicityChip::configure_from_scratch(
188                meta,
189                &mut vec![],
190                &mut vec![],
191                &[committed_instance_column, instance_column],
192            )
193        }
194
195        fn synthesize(
196            &self,
197            config: Self::Config,
198            mut layouter: impl Layouter<F>,
199        ) -> Result<(), Error> {
200            let chip = CanonicityChip::new_from_scratch(&config);
201
202            let bits = self
203                .bits
204                .iter()
205                .map(|b| chip.assign_fixed(&mut layouter, *b))
206                .collect::<Result<Vec<_>, Error>>()?;
207            let bound = self.bound.clone();
208
209            cost_measure_start(&mut layouter);
210            let res = match self.operation {
211                Operation::Canonical => chip.is_canonical(&mut layouter, &bits),
212                Operation::Lower => chip.le_bits_lower_than(&mut layouter, &bits, bound),
213                Operation::Geq => chip.le_bits_geq_than(&mut layouter, &bits, bound),
214            }?;
215            cost_measure_end(&mut layouter);
216
217            chip.assert_equal_to_fixed(&mut layouter, &res, self.expected)?;
218
219            chip.load_from_scratch(&mut layouter)
220        }
221    }
222
223    #[allow(clippy::too_many_arguments)]
224    fn run<F, Assigned, CanonicityChip>(
225        bits: &[u8],
226        bound: Option<&BigUint>,
227        expected: bool,
228        operation: Operation,
229        must_pass: bool,
230        cost_model: bool,
231        chip_name: &str,
232        op_name: &str,
233    ) where
234        F: CircuitField + FromUniformBytes<64> + Ord,
235        Assigned::Element: CircuitField,
236        Assigned: Instantiable<F> + InnerConstants + Clone,
237        CanonicityChip: CanonicityInstructions<F, Assigned>
238            + AssertionInstructions<F, Assigned>
239            + AssertionInstructions<F, AssignedBit<F>>
240            + AssignmentInstructions<F, Assigned>
241            + FromScratch<F>,
242    {
243        let circuit = TestCircuit::<F, Assigned, CanonicityChip> {
244            bits: bits.iter().map(|b| *b != 0).collect::<Vec<_>>(),
245            bound: bound.unwrap_or(&BigUint::default()).clone(),
246            expected,
247            operation,
248            _marker: PhantomData,
249        };
250        let public_inputs = vec![vec![], vec![]];
251        match MockProver::run(&circuit, public_inputs) {
252            Ok(prover) => match prover.verify() {
253                Ok(()) => assert!(must_pass),
254                Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
255            },
256            Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
257        }
258
259        if cost_model {
260            circuit_to_json(chip_name, op_name, circuit);
261        }
262    }
263
264    /// The output type is u8 instead of bool because, for readability, we
265    /// express the test vectors with integers `0` and `1` instead of
266    /// `false` and `true` (respectively).
267    fn decompose_biguint(n: &BigUint) -> Vec<u8> {
268        (0..(n.bits() as usize)).map(|i| if n.bit(i as u64) { 1 } else { 0 }).collect()
269    }
270
271    pub fn test_canonical<F, Assigned, CanonicityChip>(name: &str)
272    where
273        F: CircuitField + FromUniformBytes<64> + Ord,
274        Assigned::Element: CircuitField,
275        Assigned: Instantiable<F> + InnerConstants + Clone,
276        CanonicityChip: CanonicityInstructions<F, Assigned>
277            + AssertionInstructions<F, Assigned>
278            + AssertionInstructions<F, AssignedBit<F>>
279            + AssignmentInstructions<F, Assigned>
280            + FromScratch<F>,
281    {
282        let m = Assigned::Element::modulus();
283        let mut cost_model = true;
284        [
285            (vec![0], true),
286            (vec![1], true),
287            (vec![1, 0, 1], true),
288            (decompose_biguint(&m), false),
289            (decompose_biguint(&(m - BigUint::one())), true),
290            (vec![0; Assigned::Element::NUM_BITS as usize], true),
291            (vec![1; Assigned::Element::NUM_BITS as usize], false),
292            (vec![0; 1 + Assigned::Element::NUM_BITS as usize], false),
293        ]
294        .iter()
295        .for_each(|(bits, expected)| {
296            run::<F, Assigned, CanonicityChip>(
297                bits,
298                None,
299                *expected,
300                Operation::Canonical,
301                true,
302                cost_model,
303                name,
304                "canonical",
305            );
306            cost_model = false;
307            run::<F, Assigned, CanonicityChip>(
308                bits,
309                None,
310                !expected,
311                Operation::Canonical,
312                false,
313                false,
314                "",
315                "",
316            );
317        });
318    }
319
320    pub fn test_le_bits_lower_and_geq<F, Assigned, CanonChip>(name: &str)
321    where
322        F: CircuitField + FromUniformBytes<64> + Ord,
323        Assigned::Element: CircuitField,
324        Assigned: Instantiable<F> + InnerConstants + Clone,
325        CanonChip: CanonicityInstructions<F, Assigned>
326            + AssertionInstructions<F, Assigned>
327            + AssertionInstructions<F, AssignedBit<F>>
328            + AssignmentInstructions<F, Assigned>
329            + FromScratch<F>,
330    {
331        let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
332        let r: BigUint = rng.next_u64().into();
333        let m = Assigned::Element::modulus();
334        let mut cost_model = true;
335        [
336            (decompose_biguint(&r), r.clone() - BigUint::one(), true),
337            (decompose_biguint(&r), r.clone(), true),
338            (decompose_biguint(&r), r + BigUint::one(), false),
339            (decompose_biguint(&m), m.clone(), true),
340            (decompose_biguint(&m), m + BigUint::one(), false),
341            (vec![0], BigUint::zero(), true),
342            (vec![1], BigUint::zero(), true),
343            (vec![1, 0, 1], BigUint::from(5u64), true),
344            (vec![1, 0, 1], BigUint::from(6u64), false),
345            (vec![1, 1, 1, 0, 0, 0], BigUint::from(7u64), true),
346            (vec![1, 1, 1, 0, 0, 0], BigUint::from(8u64), false),
347        ]
348        .iter()
349        .for_each(|(bits, bound, geq)| {
350            run::<_, _, CanonChip>(
351                bits,
352                Some(bound),
353                !geq,
354                Operation::Lower,
355                true,
356                cost_model,
357                name,
358                "lt",
359            );
360            run::<_, _, CanonChip>(
361                bits,
362                Some(bound),
363                *geq,
364                Operation::Geq,
365                true,
366                cost_model,
367                name,
368                "geq",
369            );
370            cost_model = false;
371            run::<_, _, CanonChip>(
372                bits,
373                Some(bound),
374                *geq,
375                Operation::Lower,
376                false,
377                false,
378                "",
379                "",
380            );
381            run::<_, _, CanonChip>(
382                bits,
383                Some(bound),
384                !geq,
385                Operation::Geq,
386                false,
387                false,
388                "",
389                "",
390            );
391        });
392    }
393}