Skip to main content

midnight_circuits/field/native/
native_chip.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//! A chip for efficient native arithmetic.
15// `native_chip` implements several traits:
16//   - ArithInstructions
17//   - BinaryInstructions
18//   - EqualityInstructions
19//   - ControlFlowInstructions
20//
21// for the native field type, through a very simple identity of the form:
22//
23//  q_arith *
24//  {
25//  (sum_i coeff[i] * value[i])
26//    + q_next * value[0](omega) +
27//    + mul_ab * value[0] * value[1] +
28//    + mul_ac * value[0] * value[2] +
29//    + constant
30//  }
31//  = 0
32//
33// Here, `coeffs`, `q_next`, `mul_ab`, `mul_ac`, `constant` are stored in fixed
34// columns, whereas `values` are stored in advice columns.
35//
36// We also have the following identity, designed for speeding up
37// operations like conditional swaps.
38//
39//  q_12_minus_34 * (value[1] + value[2] - value[3] - value[4] = 0)
40//
41// Finally, an utilitary gate for parallel affine relation is defined for
42// performing parallel additions (by constant) x[omega] = x + c in one row.
43// The formal identities read as:
44//
45//  q_par_add * { value[i] + coeff[i] - value[i](omega) } = 0
46//
47// for all i = 0..NB_PARALLEL_ADD_COLS.
48
49use std::{
50    cell::RefCell,
51    cmp::min,
52    collections::HashMap,
53    hash::{Hash, Hasher},
54    ops::Neg,
55    rc::Rc,
56};
57
58use ff::PrimeField;
59use midnight_proofs::{
60    circuit::{Chip, Layouter, Region, Value},
61    plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Fixed, Instance, Selector},
62    poly::Rotation,
63};
64use num_bigint::BigUint;
65use num_traits::Zero;
66
67#[cfg(any(test, feature = "testing"))]
68use crate::testing_utils::FromScratch;
69use crate::{
70    instructions::{
71        public_input::CommittedInstanceInstructions, ArithInstructions, AssertionInstructions,
72        AssignmentInstructions, BinaryInstructions, CanonicityInstructions,
73        ControlFlowInstructions, ConversionInstructions, EqualityInstructions, FieldInstructions,
74        PublicInputInstructions, UnsafeConversionInstructions, ZeroInstructions,
75    },
76    types::{AssignedNative, InnerValue, Instantiable},
77    utils::{
78        util::{fe_to_big, modulus},
79        ComposableChip,
80    },
81};
82
83/// Number of columns used by the identity of the native chip.
84/// This number should NOT be smaller than 5.
85/// This limit is imposed by functions like [NativeChip::select].
86pub const NB_ARITH_COLS: usize = 5;
87
88/// Number of fixed columns used by the identity of the native chip.
89pub const NB_ARITH_FIXED_COLS: usize = NB_ARITH_COLS + 4;
90
91/// Number of additions (by constant) that can be performed in
92/// parallel in 1 row. This number should not exceed [NB_ARITH_COLS].
93///
94/// The Poseidon chip requires this number to match the Poseidon
95/// register width. Have that into consideration before modifying
96/// this number.
97const NB_PARALLEL_ADD_COLS: usize = 3;
98
99/// Config defines fixed and witness columns of the main gate
100#[derive(Clone, Debug)]
101pub struct NativeConfig {
102    q_arith: Selector,
103    q_12_minus_34: Selector,
104    q_par_add: Selector,
105    pub(crate) value_cols: [Column<Advice>; NB_ARITH_COLS],
106    coeff_cols: [Column<Fixed>; NB_ARITH_COLS],
107    q_next_col: Column<Fixed>,
108    mul_ab_col: Column<Fixed>,
109    mul_ac_col: Column<Fixed>,
110    constant_col: Column<Fixed>,
111    fixed_values_col: Column<Fixed>,
112    committed_instance_col: Column<Instance>,
113    instance_col: Column<Instance>,
114}
115
116/// Chip for Native operations
117#[derive(Clone, Debug)]
118pub struct NativeChip<F: PrimeField> {
119    config: NativeConfig,
120    cached_fixed: Rc<RefCell<HashMap<BigUint, AssignedNative<F>>>>,
121    committed_instance_offset: Rc<RefCell<usize>>,
122    instance_offset: Rc<RefCell<usize>>,
123}
124
125impl<F: PrimeField> Chip<F> for NativeChip<F> {
126    type Config = NativeConfig;
127    type Loaded = ();
128
129    fn config(&self) -> &Self::Config {
130        &self.config
131    }
132
133    fn loaded(&self) -> &Self::Loaded {
134        &()
135    }
136}
137
138impl<F: PrimeField> ComposableChip<F> for NativeChip<F> {
139    type SharedResources = (
140        [Column<Advice>; NB_ARITH_COLS],
141        [Column<Fixed>; NB_ARITH_FIXED_COLS],
142        [Column<Instance>; 2], // [committed, normal]
143    );
144
145    type InstructionDeps = ();
146    /// Creates a new NativeChip given the corresponding configuration.
147    fn new(config: &NativeConfig, _sub_chips: &()) -> Self {
148        Self {
149            config: config.clone(),
150            cached_fixed: Default::default(),
151            instance_offset: Rc::new(RefCell::new(0)),
152            committed_instance_offset: Rc::new(RefCell::new(0)),
153        }
154    }
155
156    /// Creates a NativeConfig given a constraint system and a set of
157    /// available advice columns and fixed columns.
158    fn configure(
159        meta: &mut ConstraintSystem<F>,
160        shared_res: &Self::SharedResources,
161    ) -> NativeConfig {
162        let value_columns = &shared_res.0;
163        let fixed_columns = &shared_res.1;
164
165        // It is important that the committed instance column was created before the
166        // other instance column, since committed columns go first.
167        let committed_instance_col = shared_res.2[0];
168        let instance_col = shared_res.2[1];
169
170        let q_arith = meta.selector();
171        let q_12_minus_34 = meta.selector();
172        let q_par_add = meta.selector();
173        let coeff_cols: [Column<Fixed>; NB_ARITH_COLS] = fixed_columns[4..].try_into().unwrap();
174        let q_next_col = fixed_columns[0];
175        let mul_ab_col = fixed_columns[1];
176        let mul_ac_col = fixed_columns[2];
177        let constant_col = fixed_columns[3];
178
179        let fixed_values_col = meta.fixed_column();
180        meta.enable_equality(fixed_values_col);
181
182        for col in value_columns.iter() {
183            meta.enable_equality(*col);
184        }
185        meta.enable_equality(committed_instance_col);
186        meta.enable_equality(instance_col);
187
188        meta.create_gate("arith_gate", |meta| {
189            let values = value_columns
190                .iter()
191                .map(|col| meta.query_advice(*col, Rotation::cur()))
192                .collect::<Vec<_>>();
193
194            let next_value = meta.query_advice(value_columns[0], Rotation::next());
195
196            let coeffs = coeff_cols
197                .iter()
198                .map(|col| meta.query_fixed(*col, Rotation::cur()))
199                .collect::<Vec<_>>();
200
201            let q_next_coeff = meta.query_fixed(q_next_col, Rotation::cur());
202            let mul_ab_coeff = meta.query_fixed(mul_ab_col, Rotation::cur());
203            let mul_ac_coeff = meta.query_fixed(mul_ac_col, Rotation::cur());
204            let constant = meta.query_fixed(constant_col, Rotation::cur());
205
206            let id = values
207                .iter()
208                .zip(coeffs.iter())
209                .fold(constant, |acc, (value, coeff)| acc + coeff * value)
210                + q_next_coeff * next_value
211                + mul_ab_coeff * &values[0] * &values[1]
212                + mul_ac_coeff * &values[0] * &values[2];
213
214            Constraints::with_selector(q_arith, vec![id])
215        });
216
217        meta.create_gate("12_minus_34", |meta| {
218            let v1 = meta.query_advice(value_columns[1], Rotation::cur());
219            let v2 = meta.query_advice(value_columns[2], Rotation::cur());
220            let v3 = meta.query_advice(value_columns[3], Rotation::cur());
221            let v4 = meta.query_advice(value_columns[4], Rotation::cur());
222
223            Constraints::with_selector(q_12_minus_34, vec![v1 + v2 - v3 - v4])
224        });
225
226        meta.create_gate("parallel_add_gate", |meta| {
227            let ids = (value_columns[0..NB_PARALLEL_ADD_COLS].iter())
228                .zip(coeff_cols[0..NB_PARALLEL_ADD_COLS].iter())
229                .map(|(val_col, const_col)| {
230                    let val = meta.query_advice(*val_col, Rotation::cur());
231                    let res = meta.query_advice(*val_col, Rotation::next());
232                    let c = meta.query_fixed(*const_col, Rotation::cur());
233                    val + c - res
234                })
235                .collect::<Vec<_>>();
236
237            Constraints::with_selector(q_par_add, ids)
238        });
239
240        NativeConfig {
241            q_arith,
242            q_12_minus_34,
243            q_par_add,
244            value_cols: *value_columns,
245            coeff_cols,
246            q_next_col,
247            mul_ab_col,
248            mul_ac_col,
249            constant_col,
250            fixed_values_col,
251            committed_instance_col,
252            instance_col,
253        }
254    }
255
256    fn load(&self, _layouter: &mut impl Layouter<F>) -> Result<(), Error> {
257        Ok(())
258    }
259}
260
261impl<F: PrimeField> NativeChip<F> {
262    /// Fills the arithmetic identity selectors with the given values at the
263    /// current offset. This function does not assign values, it assumes they
264    /// have already been assigned.
265    fn custom(
266        &self,
267        region: &mut Region<'_, F>,
268        coeffs: &[F; NB_ARITH_COLS],
269        q_next_coeff: F,
270        mul_coeffs: (F, F),
271        constant: F,
272        offset: usize,
273    ) -> Result<(), Error> {
274        self.config.q_arith.enable(region, offset)?;
275
276        for (i, coeff) in coeffs.iter().enumerate() {
277            region.assign_fixed(
278                || "arith coeff",
279                self.config.coeff_cols[i],
280                offset,
281                || Value::known(*coeff),
282            )?;
283        }
284
285        region.assign_fixed(
286            || "arith q_next",
287            self.config.q_next_col,
288            offset,
289            || Value::known(q_next_coeff),
290        )?;
291        region.assign_fixed(
292            || "arith mul_ab",
293            self.config.mul_ab_col,
294            offset,
295            || Value::known(mul_coeffs.0),
296        )?;
297        region.assign_fixed(
298            || "arith mul_ac",
299            self.config.mul_ac_col,
300            offset,
301            || Value::known(mul_coeffs.1),
302        )?;
303        region.assign_fixed(
304            || "arith const",
305            self.config.constant_col,
306            offset,
307            || Value::known(constant),
308        )?;
309
310        Ok(())
311    }
312
313    /// Copies the given assigned value in the current row and the given column.
314    fn copy_in_row(
315        &self,
316        region: &mut Region<'_, F>,
317        x: &AssignedNative<F>,
318        column: &Column<Advice>,
319        offset: usize,
320    ) -> Result<(), Error> {
321        let y = region.assign_advice(
322            || "arith copy_in_row",
323            *column,
324            offset,
325            || x.value().copied(),
326        )?;
327        region.constrain_equal(x.cell(), y.cell())?;
328        Ok(())
329    }
330
331    /// Computes `a*x + b*y + c*z + k + m1*x*y + m2*x*z`.
332    fn add_and_double_mul(
333        &self,
334        layouter: &mut impl Layouter<F>,
335        (a, x): (F, &AssignedNative<F>),
336        (b, y): (F, &AssignedNative<F>),
337        (c, z): (F, &AssignedNative<F>),
338        k: F,
339        (m1, m2): (F, F),
340    ) -> Result<AssignedNative<F>, Error> {
341        let res_value = x
342            .value()
343            .zip(y.value())
344            .zip(z.value())
345            .map(|((x, y), z)| a * x + b * y + c * z + k + m1 * x * y + m2 * x * z);
346        layouter.assign_region(
347            || "add and double mul",
348            |mut region| {
349                self.copy_in_row(&mut region, x, &self.config.value_cols[0], 0)?;
350                self.copy_in_row(&mut region, y, &self.config.value_cols[1], 0)?;
351                self.copy_in_row(&mut region, z, &self.config.value_cols[2], 0)?;
352                let res =
353                    region.assign_advice(|| "res", self.config.value_cols[4], 0, || res_value)?;
354                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
355                coeffs[0] = a; // coeff of x
356                coeffs[1] = b; // coeff of y
357                coeffs[2] = c; // coeff of z
358                coeffs[4] = -F::ONE; // coeff of res
359                self.custom(&mut region, &coeffs, F::ZERO, (m1, m2), k, 0)?;
360                Ok(res)
361            },
362        )
363    }
364
365    /// Assigns the given value into a variable `x`, and returns `(x, r)`, where
366    /// `r` is such that `(x - shift) * r = 1`.
367    ///
368    /// Calling this function on `value = shift` will make the circuit
369    /// unsatisfiable.
370    fn assign_with_shifted_inverse(
371        &self,
372        layouter: &mut impl Layouter<F>,
373        value: Value<F>,
374        shift: F,
375    ) -> Result<(AssignedNative<F>, AssignedNative<F>), Error> {
376        layouter.assign_region(
377            || "assign with shifted inverse",
378            |mut region| {
379                // x * r - shift * r - 1 = 0
380                let r_value = value.map(|x| (x - shift).invert().unwrap_or(F::ZERO));
381                let x = region.assign_advice(|| "x", self.config.value_cols[0], 0, || value)?;
382                let r = region.assign_advice(|| "r", self.config.value_cols[1], 0, || r_value)?;
383                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
384                coeffs[1] = -shift; // coeff of r
385                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), -F::ONE, 0)?;
386                Ok((x, r))
387            },
388        )
389    }
390
391    /// Assigns values to verify a linear combination of the given terms.
392    ///
393    /// Concretely, given terms s.t. `term_i = (c_i, a_i)` and result, it
394    /// asserts that `result = sum c_i a_i`.
395    ///
396    /// The chip uses `cols_used` columns per row to accumulate `col_used` terms
397    /// in a temporary result and one column to keep this temporary result.
398    ///
399    /// This function is called recursively via the following relation:
400    ///
401    /// `res = \sum c_j a_j + \sum c_i a_i <==> (res - \sum c_j a_j) = \sum c_i
402    /// a_i`
403    ///
404    /// - the j indices correspond to the (not more than cols_used) terms
405    ///   consumed
406    /// - the i indices correspond to the rest terms
407    ///
408    /// if the i indices are empty we directly verify the relation in a single
409    /// row, otherwise we verify the relation `new_result = res + \sum c_j
410    /// a_j` by using the `q_next_col` column to query the witnessed
411    /// `new_result` which will always be in the first column of the next row
412    ///
413    /// INVARIANT: Whenever this function is called: `result = \sum terms[i].0 *
414    /// terms[i].1`
415    ///
416    /// The function returns the assigned linear combination witness elements
417    /// and the linear combination result
418    pub(crate) fn assign_linear_combination_aux(
419        &self,
420        region: &mut Region<'_, F>,
421        terms: &[(F, Value<F>)],
422        constant: F,
423        result: &Value<F>,
424        cols_used: usize,
425        offset: &mut usize,
426    ) -> Result<(Vec<AssignedNative<F>>, AssignedNative<F>), Error> {
427        // cols_used should be less than NB_ARITH_COLS
428        assert!(cols_used < NB_ARITH_COLS);
429
430        // If |terms| <= cols_used, we assert the relation in one row.
431        // Otherwise we consume up to `cols_used` terms to reduce to a linear
432        // combination of smaller size.
433        let chunk_len = min(terms.len(), cols_used);
434
435        // Initialize the coefficients vector
436        let mut coeffs = [F::ZERO; NB_ARITH_COLS];
437
438        // assign the lc result in the first advice column
439        let assigned_result = region.assign_advice(
440            || "assign linear combination term",
441            self.config.value_cols[0],
442            *offset,
443            || *result,
444        )?;
445        coeffs[0] = -F::ONE;
446
447        // assign the first `chunk_len` terms values in the current row
448        let mut assigned_limbs = terms[0..chunk_len]
449            .iter()
450            .enumerate()
451            .map(|(i, term)| {
452                coeffs[i + 1] = term.0;
453                region.assign_advice(
454                    || "assign linear combination term",
455                    self.config.value_cols[i + 1],
456                    *offset,
457                    || term.1,
458                )
459            })
460            .collect::<Result<Vec<_>, _>>()?;
461
462        // If everything fits in this row, we add the constraint in the current row and
463        // finish.
464        if terms.len() <= cols_used {
465            self.custom(
466                region,
467                &coeffs,
468                F::ZERO,
469                (F::ZERO, F::ZERO),
470                constant,
471                *offset,
472            )?;
473            Ok((assigned_limbs, assigned_result))
474        }
475        // Otherwise, we recurse on the remaining terms with the new result.
476        else {
477            // compute the next result: `result - \sum c_j a_j` for j in the chunk
478            let chunk_result =
479                terms[..chunk_len].iter().fold(Value::known(constant), |acc, (coeff, x)| {
480                    acc.zip(*x).map(|(acc, val)| acc + *coeff * val)
481                });
482            let next_result = *result - chunk_result;
483
484            // the cells will be after the next recursive call of the following form
485            // offset:      (-1, result)       | (c_1, a_1) | (c_2, a_2) | ... | (c_j, a_j)
486            // offset+1:    (-1, new_result)   |    ...     |    ...     | ... |    ...
487            //
488            // We need to verify the constraint `result - sum c_j a_j = new_result`,
489            // equivalently `new_result - result + sum c_j a_j = 0`.
490            //
491            // We add the terms in the current row (i.e. `sum c_j a_j - result`)
492            // and use the selector q_next_coeff to also add the first advice cell of the
493            // next column
494            self.custom(
495                region,
496                &coeffs,
497                F::ONE,
498                (F::ZERO, F::ZERO),
499                constant,
500                *offset,
501            )?;
502            *offset += 1;
503            let (new_assigned_limbs, _) = self.assign_linear_combination_aux(
504                region,
505                &terms[chunk_len..],
506                F::ZERO,
507                &next_result,
508                cols_used,
509                offset,
510            )?;
511            assigned_limbs.extend_from_slice(&new_assigned_limbs);
512            Ok((assigned_limbs, assigned_result))
513        }
514    }
515
516    /// The total number of public inputs (as raw scalars) that have been
517    /// constrained so far by this chip.
518    pub fn nb_public_inputs(&self) -> usize {
519        *self.instance_offset.borrow()
520    }
521}
522
523impl<F: PrimeField> NativeChip<F> {
524    /// Performs parallel additions of `variables` and `constants` in one row,
525    /// and increments the offset.
526    pub(crate) fn add_constants_in_region(
527        &self,
528        region: &mut Region<'_, F>,
529        variables: &[AssignedNative<F>; NB_PARALLEL_ADD_COLS],
530        constants: &[F; NB_PARALLEL_ADD_COLS],
531        offset: &mut usize,
532    ) -> Result<Vec<AssignedNative<F>>, Error> {
533        self.config.q_par_add.enable(region, *offset)?;
534
535        (variables.iter())
536            .zip(self.config.value_cols)
537            .try_for_each(|(x, col)| self.copy_in_row(region, x, &col, *offset))?;
538
539        constants.iter().zip(self.config.coeff_cols).try_for_each(|(c, col)| {
540            region.assign_fixed(|| "add_consts", col, *offset, || Value::known(*c))?;
541            Ok::<(), Error>(())
542        })?;
543
544        *offset += 1;
545
546        let res_values = variables.iter().zip(constants).map(|(x, c)| x.value().map(|x| *x + *c));
547
548        res_values
549            .zip(self.config.value_cols)
550            .map(|(val, col)| region.assign_advice(|| "add_consts", col, *offset, || val))
551            .collect::<Result<Vec<_>, Error>>()
552    }
553}
554
555impl<F> AssignmentInstructions<F, AssignedNative<F>> for NativeChip<F>
556where
557    F: PrimeField,
558{
559    fn assign(
560        &self,
561        layouter: &mut impl Layouter<F>,
562        value: Value<F>,
563    ) -> Result<AssignedNative<F>, Error> {
564        layouter.assign_region(
565            || "Assign native value",
566            |mut region| {
567                region.assign_advice(|| "assign element", self.config.value_cols[0], 0, || value)
568            },
569        )
570    }
571
572    fn assign_fixed(
573        &self,
574        layouter: &mut impl Layouter<F>,
575        constant: F,
576    ) -> Result<AssignedNative<F>, Error> {
577        let constant_big = fe_to_big::<F>(constant);
578        if let Some(assigned) = self.cached_fixed.borrow().get(&constant_big) {
579            return Ok(assigned.clone());
580        };
581
582        let x = layouter.assign_region(
583            || "Assign fixed",
584            |mut region| {
585                let mut x = region.assign_fixed(
586                    || "fixed",
587                    self.config.fixed_values_col,
588                    0,
589                    || Value::known(constant),
590                )?;
591                // This is hacky but necessary because we are treating a fixed cell as an
592                // assigned one. Fixed cells do not get properly filled until the end.
593                x.update_value(constant)?;
594                Ok(x)
595            },
596        )?;
597
598        // Save the assigned constant in the cache.
599        self.cached_fixed.borrow_mut().insert(constant_big.clone(), x.clone());
600
601        Ok(x)
602    }
603
604    // This is more efficient than the blanket implementation.
605    fn assign_many(
606        &self,
607        layouter: &mut impl Layouter<F>,
608        values: &[Value<F>],
609    ) -> Result<Vec<AssignedNative<F>>, Error> {
610        layouter.assign_region(
611            || "assign_many (native)",
612            |mut region| {
613                let mut assigned = vec![];
614                for (i, chunk_values) in values.chunks(NB_ARITH_COLS).enumerate() {
615                    for (value, col) in chunk_values.iter().zip(self.config.value_cols.iter()) {
616                        let cell = region.assign_advice(|| "assign", *col, i, || *value)?;
617                        assigned.push(cell);
618                    }
619                }
620                Ok(assigned)
621            },
622        )
623    }
624}
625
626impl<F> PublicInputInstructions<F, AssignedNative<F>> for NativeChip<F>
627where
628    F: PrimeField,
629{
630    fn as_public_input(
631        &self,
632        _layouter: &mut impl Layouter<F>,
633        assigned: &AssignedNative<F>,
634    ) -> Result<Vec<AssignedNative<F>>, Error> {
635        Ok(vec![assigned.clone()])
636    }
637
638    fn constrain_as_public_input(
639        &self,
640        layouter: &mut impl Layouter<F>,
641        assigned: &AssignedNative<F>,
642    ) -> Result<(), Error> {
643        let mut offset = self.instance_offset.borrow_mut();
644        layouter.constrain_instance(assigned.cell(), self.config.instance_col, *offset)?;
645        *offset += 1;
646        Ok(())
647    }
648
649    fn assign_as_public_input(
650        &self,
651        layouter: &mut impl Layouter<F>,
652        value: Value<F>,
653    ) -> Result<AssignedNative<F>, Error> {
654        // There is nothing to optimize when assigning a public native value.
655        let assigned = self.assign(layouter, value)?;
656        self.constrain_as_public_input(layouter, &assigned)?;
657        Ok(assigned)
658    }
659}
660
661impl<F> CommittedInstanceInstructions<F, AssignedNative<F>> for NativeChip<F>
662where
663    F: PrimeField,
664{
665    fn constrain_as_committed_public_input(
666        &self,
667        layouter: &mut impl Layouter<F>,
668        assigned: &AssignedNative<F>,
669    ) -> Result<(), Error> {
670        let mut offset = self.committed_instance_offset.borrow_mut();
671        layouter.constrain_instance(
672            assigned.cell(),
673            self.config.committed_instance_col,
674            *offset,
675        )?;
676        *offset += 1;
677        Ok(())
678    }
679}
680
681impl<F> AssignmentInstructions<F, AssignedBit<F>> for NativeChip<F>
682where
683    F: PrimeField,
684{
685    fn assign(
686        &self,
687        layouter: &mut impl Layouter<F>,
688        value: Value<bool>,
689    ) -> Result<AssignedBit<F>, Error> {
690        layouter.assign_region(
691            || "Assign big",
692            |mut region| {
693                // Enforce x * x - x = 0
694                let b_value = value.map(|b| if b { F::ONE } else { F::ZERO });
695                let b = region.assign_advice(|| "b", self.config.value_cols[0], 0, || b_value)?;
696                self.copy_in_row(&mut region, &b, &self.config.value_cols[1], 0)?;
697                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
698                coeffs[0] = -F::ONE; // coeff of x
699                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), F::ZERO, 0)?;
700                Ok(AssignedBit(b))
701            },
702        )
703    }
704
705    fn assign_fixed(
706        &self,
707        layouter: &mut impl Layouter<F>,
708        bit: bool,
709    ) -> Result<AssignedBit<F>, Error> {
710        let constant = if bit { F::ONE } else { F::ZERO };
711        let x = self.assign_fixed(layouter, constant)?;
712        Ok(AssignedBit(x))
713    }
714}
715
716impl<F> PublicInputInstructions<F, AssignedBit<F>> for NativeChip<F>
717where
718    F: PrimeField,
719{
720    fn as_public_input(
721        &self,
722        _layouter: &mut impl Layouter<F>,
723        assigned: &AssignedBit<F>,
724    ) -> Result<Vec<AssignedNative<F>>, Error> {
725        Ok(vec![assigned.clone().into()])
726    }
727
728    fn constrain_as_public_input(
729        &self,
730        layouter: &mut impl Layouter<F>,
731        assigned: &AssignedBit<F>,
732    ) -> Result<(), Error> {
733        let assigned_as_native: AssignedNative<F> = assigned.clone().into();
734        self.constrain_as_public_input(layouter, &assigned_as_native)
735    }
736
737    fn assign_as_public_input(
738        &self,
739        layouter: &mut impl Layouter<F>,
740        value: Value<bool>,
741    ) -> Result<AssignedBit<F>, Error> {
742        // We could skip the in-circuit boolean assertion as this condition will be
743        // enforced through the public inputs bind anyway. But this takes 1 row anyway.
744        let assigned = self.assign(layouter, value)?;
745        self.constrain_as_public_input(layouter, &assigned)?;
746        Ok(assigned)
747    }
748}
749
750impl<F> AssertionInstructions<F, AssignedNative<F>> for NativeChip<F>
751where
752    F: PrimeField,
753{
754    fn assert_equal(
755        &self,
756        layouter: &mut impl Layouter<F>,
757        x: &AssignedNative<F>,
758        y: &AssignedNative<F>,
759    ) -> Result<(), Error> {
760        layouter.assign_region(
761            || "Assert equal",
762            |mut region| region.constrain_equal(x.cell(), y.cell()),
763        )
764    }
765
766    fn assert_not_equal(
767        &self,
768        layouter: &mut impl Layouter<F>,
769        x: &AssignedNative<F>,
770        y: &AssignedNative<F>,
771    ) -> Result<(), Error> {
772        layouter.assign_region(
773            || "Assert not equal",
774            |mut region| {
775                // We enforce (x - y) * r = 1, for a fresh r.
776                // Encoded as r * x - r * y - 1 = 0
777                let r_value = (x.value().copied() - y.value().copied())
778                    .map(|v| v.invert().unwrap_or(F::ZERO));
779                region.assign_advice(|| "r", self.config.value_cols[0], 0, || r_value)?;
780                self.copy_in_row(&mut region, x, &self.config.value_cols[1], 0)?;
781                self.copy_in_row(&mut region, y, &self.config.value_cols[2], 0)?;
782                let coeffs = [F::ZERO; NB_ARITH_COLS];
783                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), -F::ONE, 0)?;
784                Ok(())
785            },
786        )
787    }
788
789    fn assert_equal_to_fixed(
790        &self,
791        layouter: &mut impl Layouter<F>,
792        x: &AssignedNative<F>,
793        constant: F,
794    ) -> Result<(), Error> {
795        let c = self.assign_fixed(layouter, constant)?;
796        self.assert_equal(layouter, x, &c)
797    }
798
799    fn assert_not_equal_to_fixed(
800        &self,
801        layouter: &mut impl Layouter<F>,
802        x: &AssignedNative<F>,
803        constant: F,
804    ) -> Result<(), Error> {
805        // We show that (x != constant) by exhibiting an inverse of (x - constant),
806        // which we discard.
807        let (y, _) = self.assign_with_shifted_inverse(layouter, x.value().copied(), constant)?;
808        self.assert_equal(layouter, x, &y)
809    }
810}
811
812impl<F> AssertionInstructions<F, AssignedBit<F>> for NativeChip<F>
813where
814    F: PrimeField,
815{
816    fn assert_equal(
817        &self,
818        layouter: &mut impl Layouter<F>,
819        x: &AssignedBit<F>,
820        y: &AssignedBit<F>,
821    ) -> Result<(), Error> {
822        self.assert_equal(layouter, &x.0, &y.0)
823    }
824
825    fn assert_not_equal(
826        &self,
827        layouter: &mut impl Layouter<F>,
828        x: &AssignedBit<F>,
829        y: &AssignedBit<F>,
830    ) -> Result<(), Error> {
831        let diff = self.sub(layouter, &x.0, &y.0)?;
832        self.assert_non_zero(layouter, &diff)
833    }
834
835    fn assert_equal_to_fixed(
836        &self,
837        layouter: &mut impl Layouter<F>,
838        x: &AssignedBit<F>,
839        b: bool,
840    ) -> Result<(), Error> {
841        let constant = if b { F::ONE } else { F::ZERO };
842        self.assert_equal_to_fixed(layouter, &x.0, constant)
843    }
844
845    fn assert_not_equal_to_fixed(
846        &self,
847        layouter: &mut impl Layouter<F>,
848        x: &AssignedBit<F>,
849        constant: bool,
850    ) -> Result<(), Error> {
851        self.assert_equal_to_fixed(layouter, x, !constant)
852    }
853}
854
855impl<F> ZeroInstructions<F, AssignedNative<F>> for NativeChip<F> where F: PrimeField {}
856
857impl<F> ArithInstructions<F, AssignedNative<F>> for NativeChip<F>
858where
859    F: PrimeField,
860{
861    fn linear_combination(
862        &self,
863        layouter: &mut impl Layouter<F>,
864        terms: &[(F, AssignedNative<F>)],
865        constant: F,
866    ) -> Result<AssignedNative<F>, Error> {
867        let terms: Vec<_> = terms.iter().filter(|(c, _)| !F::is_zero_vartime(c)).cloned().collect();
868        if terms.is_empty() {
869            return self.assign_fixed(layouter, constant);
870        }
871
872        // Maybe a  &[(F, AssignedNative<F>)] (and correspondingly to the aux function.
873        // Do we really need slices with references?
874        let term_values = terms
875            .iter()
876            .cloned()
877            .map(|(c, assigned_t)| (c, assigned_t.value().copied()))
878            .collect::<Vec<_>>();
879
880        let result = terms.iter().fold(Value::known(constant), |acc, (coeff, x)| {
881            acc.zip(x.value()).map(|(acc, val)| acc + *coeff * val)
882        });
883
884        layouter.assign_region(
885            || "Linear combination",
886            |mut region| {
887                let mut offset = 0;
888                let (assigned_limbs, assigned_result) = self.assign_linear_combination_aux(
889                    &mut region,
890                    term_values.as_slice(),
891                    constant,
892                    &result,
893                    NB_ARITH_COLS - 1,
894                    &mut offset,
895                )?;
896
897                assert_eq!(assigned_limbs.len(), terms.len());
898
899                // assert the newly assigned values are equal to the one given as input
900                assigned_limbs.iter().zip(terms.iter()).try_for_each(|(new_av, (_, av))| {
901                    region.constrain_equal(new_av.cell(), av.cell())
902                })?;
903
904                Ok(assigned_result)
905            },
906        )
907    }
908
909    fn mul(
910        &self,
911        layouter: &mut impl Layouter<F>,
912        x: &AssignedNative<F>,
913        y: &AssignedNative<F>,
914        multiplying_constant: Option<F>,
915    ) -> Result<AssignedNative<F>, Error> {
916        if multiplying_constant == Some(F::ZERO) {
917            return self.assign_fixed(layouter, F::ZERO);
918        }
919
920        let m = multiplying_constant.unwrap_or(F::ONE);
921
922        let one: AssignedNative<F> = self.assign_fixed(layouter, F::ONE)?;
923        if m == F::ONE && x.clone() == one {
924            return Ok(y.clone());
925        }
926        if m == F::ONE && y.clone() == one {
927            return Ok(x.clone());
928        }
929
930        self.add_and_mul(
931            layouter,
932            (F::ZERO, x),
933            (F::ZERO, y),
934            (F::ZERO, x),
935            F::ZERO,
936            m,
937        )
938    }
939
940    fn div(
941        &self,
942        layouter: &mut impl Layouter<F>,
943        x: &AssignedNative<F>,
944        y: &AssignedNative<F>,
945    ) -> Result<AssignedNative<F>, Error> {
946        let y_inv = self.inv(layouter, y)?;
947        self.mul(layouter, x, &y_inv, None)
948    }
949
950    fn inv(
951        &self,
952        layouter: &mut impl Layouter<F>,
953        x: &AssignedNative<F>,
954    ) -> Result<AssignedNative<F>, Error> {
955        let (y, inv) = self.assign_with_shifted_inverse(layouter, x.value().copied(), F::ZERO)?;
956        self.assert_equal(layouter, x, &y)?;
957        Ok(inv)
958    }
959
960    fn inv0(
961        &self,
962        layouter: &mut impl Layouter<F>,
963        x: &AssignedNative<F>,
964    ) -> Result<AssignedNative<F>, Error> {
965        let is_zero = self.is_zero(layouter, x)?;
966        let zero = self.assign_fixed(layouter, F::ZERO)?;
967        let one = self.assign_fixed(layouter, F::ONE)?;
968        let invertible = self.select(layouter, &is_zero, &one, x)?;
969        let inverse = self.inv(layouter, &invertible)?;
970        self.select(layouter, &is_zero, &zero, &inverse)
971    }
972
973    fn add_and_mul(
974        &self,
975        layouter: &mut impl Layouter<F>,
976        (a, x): (F, &AssignedNative<F>),
977        (b, y): (F, &AssignedNative<F>),
978        (c, z): (F, &AssignedNative<F>),
979        k: F,
980        m: F,
981    ) -> Result<AssignedNative<F>, Error> {
982        self.add_and_double_mul(layouter, (a, x), (b, y), (c, z), k, (m, F::ZERO))
983    }
984
985    fn add_constants(
986        &self,
987        layouter: &mut impl Layouter<F>,
988        xs: &[AssignedNative<F>],
989        constants: &[F],
990    ) -> Result<Vec<AssignedNative<F>>, Error> {
991        assert_eq!(xs.len(), constants.len());
992
993        let pairs = (xs.iter().zip(constants.iter()))
994            .filter(|&(_, &c)| c != F::ZERO)
995            .collect::<Vec<_>>();
996
997        let mut non_trivial_outputs = Vec::with_capacity(pairs.len());
998
999        let mut chunks = pairs.chunks_exact(NB_PARALLEL_ADD_COLS);
1000        for chunk in chunks.by_ref() {
1001            let outputs = layouter.assign_region(
1002                || "add_constants",
1003                |mut region| {
1004                    let values = chunk.iter().map(|(x, _)| (*x).clone()).collect::<Vec<_>>();
1005                    let consts = chunk.iter().map(|(_, c)| **c).collect::<Vec<_>>();
1006                    self.add_constants_in_region(
1007                        &mut region,
1008                        &values.try_into().unwrap(),
1009                        &consts.try_into().unwrap(),
1010                        &mut 0,
1011                    )
1012                },
1013            )?;
1014            non_trivial_outputs.extend(outputs);
1015        }
1016
1017        // Proecss a final chunk of length < NB_PARALLEL_ADD_COLS, "manually".
1018        for (x, c) in chunks.remainder() {
1019            non_trivial_outputs.push(self.add_constant(layouter, x, **c)?);
1020        }
1021
1022        let mut outputs = Vec::with_capacity(xs.len());
1023        let mut j = 0;
1024        for i in 0..xs.len() {
1025            if constants[i] != F::ZERO {
1026                outputs.push(non_trivial_outputs[j].clone());
1027                j += 1;
1028            } else {
1029                outputs.push(xs[i].clone())
1030            }
1031        }
1032
1033        Ok(outputs)
1034    }
1035}
1036
1037impl<F: PrimeField> Instantiable<F> for AssignedBit<F> {
1038    fn as_public_input(element: &bool) -> Vec<F> {
1039        vec![if *element { F::ONE } else { F::ZERO }]
1040    }
1041
1042    fn from_public_input(fields: &[F]) -> Option<bool> {
1043        match fields {
1044            [f] if *f == F::ZERO => Some(false),
1045            [f] if *f == F::ONE => Some(true),
1046            _ => None,
1047        }
1048    }
1049}
1050
1051/// This wrapper type on `AssignedNative<F>` is designed to enforce type safety
1052/// on assigned bits. It prevents the user from creating an `AssignedBit`
1053/// without using the designated entry points, which guarantee (with
1054/// constraints) that the assigned value is indeed 0 or 1.
1055#[derive(Clone, Debug, PartialEq, Eq)]
1056#[must_use]
1057pub struct AssignedBit<F: PrimeField>(pub(crate) AssignedNative<F>);
1058
1059impl<F: PrimeField> Hash for AssignedBit<F> {
1060    fn hash<H: Hasher>(&self, state: &mut H) {
1061        self.0.hash(state)
1062    }
1063}
1064
1065impl<F: PrimeField> InnerValue for AssignedBit<F> {
1066    type Element = bool;
1067
1068    fn value(&self) -> Value<bool> {
1069        self.0.value().map(|b| !F::is_zero_vartime(b))
1070    }
1071}
1072
1073impl<F: PrimeField> From<AssignedBit<F>> for AssignedNative<F> {
1074    fn from(bit: AssignedBit<F>) -> Self {
1075        bit.0
1076    }
1077}
1078
1079impl<F> ConversionInstructions<F, AssignedNative<F>, AssignedBit<F>> for NativeChip<F>
1080where
1081    F: PrimeField,
1082{
1083    fn convert_value(&self, x: &F) -> Option<bool> {
1084        let is_zero: bool = F::is_zero(x).into();
1085        Some(!is_zero)
1086    }
1087
1088    fn convert(
1089        &self,
1090        layouter: &mut impl Layouter<F>,
1091        x: &AssignedNative<F>,
1092    ) -> Result<AssignedBit<F>, Error> {
1093        let b_value = x.value().map(|v| !F::is_zero_vartime(v));
1094        let b: AssignedBit<F> = self.assign(layouter, b_value)?;
1095        self.assert_equal(layouter, x, &b.0)?;
1096        Ok(b)
1097    }
1098}
1099
1100impl<F> ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>> for NativeChip<F>
1101where
1102    F: PrimeField,
1103{
1104    fn convert_value(&self, x: &bool) -> Option<F> {
1105        Some(if *x { F::from(1) } else { F::from(0) })
1106    }
1107
1108    fn convert(
1109        &self,
1110        _layouter: &mut impl Layouter<F>,
1111        bit: &AssignedBit<F>,
1112    ) -> Result<AssignedNative<F>, Error> {
1113        Ok(bit.clone().0)
1114    }
1115}
1116
1117impl<F> UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>> for NativeChip<F>
1118where
1119    F: PrimeField,
1120{
1121    /// CAUTION: use only if you know what you are doing!
1122    ///
1123    /// This function converts an `AssignedNative` to an `AssignedBit`
1124    /// *without* adding any constraints to guarantee the "bitness" of the
1125    /// assigned value.
1126    ///
1127    /// *It should be used only when the input x is already guaranteed to be a
1128    /// bit*
1129    fn convert_unsafe(
1130        &self,
1131        _layouter: &mut impl Layouter<F>,
1132        x: &AssignedNative<F>,
1133    ) -> Result<AssignedBit<F>, Error> {
1134        #[cfg(not(test))]
1135        x.value().map(|&x| {
1136            assert!(
1137                x == F::ZERO || x == F::ONE,
1138                "Trying to convert {:?} to an AssignedBit!",
1139                x
1140            );
1141        });
1142        Ok(AssignedBit(x.clone()))
1143    }
1144}
1145
1146#[cfg(test)]
1147impl<F> UnsafeConversionInstructions<F, AssignedBit<F>, AssignedNative<F>> for NativeChip<F>
1148where
1149    F: PrimeField,
1150{
1151    fn convert_unsafe(
1152        &self,
1153        layouter: &mut impl Layouter<F>,
1154        x: &AssignedBit<F>,
1155    ) -> Result<AssignedNative<F>, Error> {
1156        self.convert(layouter, x)
1157    }
1158}
1159
1160impl<F> BinaryInstructions<F> for NativeChip<F>
1161where
1162    F: PrimeField,
1163{
1164    fn and(
1165        &self,
1166        layouter: &mut impl Layouter<F>,
1167        bits: &[AssignedBit<F>],
1168    ) -> Result<AssignedBit<F>, Error> {
1169        let mut acc = bits.first().unwrap().0.clone();
1170        for b in bits.iter().skip(1) {
1171            acc = self.mul(layouter, &acc, &b.0, None)?;
1172        }
1173        Ok(AssignedBit(acc))
1174    }
1175
1176    fn or(
1177        &self,
1178        layouter: &mut impl Layouter<F>,
1179        bits: &[AssignedBit<F>],
1180    ) -> Result<AssignedBit<F>, Error> {
1181        let mut acc = bits.first().unwrap().0.clone();
1182        for b in bits.iter().skip(1) {
1183            // compute acc := acc + b - acc * b
1184            acc = self.add_and_mul(
1185                layouter,
1186                (F::ONE, &acc),
1187                (F::ONE, &b.0),
1188                (F::ZERO, &acc),
1189                F::ZERO,
1190                -F::ONE,
1191            )?;
1192        }
1193        Ok(AssignedBit(acc))
1194    }
1195
1196    fn xor(
1197        &self,
1198        layouter: &mut impl Layouter<F>,
1199        bits: &[AssignedBit<F>],
1200    ) -> Result<AssignedBit<F>, Error> {
1201        let mut acc = bits.first().unwrap().0.clone();
1202        for b in bits.iter().skip(1) {
1203            // compute acc := acc + b - 2 * acc * b
1204            acc = self.add_and_mul(
1205                layouter,
1206                (F::ONE, &acc),
1207                (F::ONE, &b.0),
1208                (F::ZERO, &acc),
1209                F::ZERO,
1210                -F::from(2),
1211            )?;
1212        }
1213        Ok(AssignedBit(acc))
1214    }
1215
1216    fn not(
1217        &self,
1218        layouter: &mut impl Layouter<F>,
1219        bit: &AssignedBit<F>,
1220    ) -> Result<AssignedBit<F>, Error> {
1221        let neg_bit = self.linear_combination(layouter, &[(-F::ONE, bit.0.clone())], F::ONE)?;
1222        Ok(AssignedBit(neg_bit))
1223    }
1224}
1225
1226impl<F> EqualityInstructions<F, AssignedNative<F>> for NativeChip<F>
1227where
1228    F: PrimeField + From<u64> + Neg<Output = F>,
1229{
1230    fn is_equal(
1231        &self,
1232        layouter: &mut impl Layouter<F>,
1233        x: &AssignedNative<F>,
1234        y: &AssignedNative<F>,
1235    ) -> Result<AssignedBit<F>, Error> {
1236        // We enforce (i) (x - y) * aux = 1 - res
1237        // and       (ii) (x - y) * res = 0.
1238        //  * If  x = y, (i)  implies res = 1; (ii) becomes trivial.
1239        //  * If x != y, (ii) implies res = 0; (i) can be relaxed with aux = (x - y)^-1.
1240
1241        let value_cols = &self.config.value_cols;
1242        let res_val = x.value().zip(y.value()).map(|(x, y)| F::from((x == y) as u64));
1243        let aux_val = x.value().zip(y.value()).map(|(x, y)| (*x - *y).invert().unwrap_or(F::ONE));
1244
1245        // (i) enforced as res + aux * x - aux * y - 1 = 0.
1246        let res = layouter.assign_region(
1247            || "is_equal (i)",
1248            |mut region| {
1249                region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
1250                self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
1251                self.copy_in_row(&mut region, y, &value_cols[2], 0)?;
1252                let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
1253                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1254                coeffs[4] = F::ONE; // coeff of res
1255                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), -F::ONE, 0)?;
1256                Ok(res)
1257            },
1258        )?;
1259
1260        // (ii) enforced as res * x - res * y = 0.
1261        let must_be_zero = self.add_and_double_mul(
1262            layouter,
1263            (F::ZERO, &res),
1264            (F::ZERO, x),
1265            (F::ZERO, y),
1266            F::ZERO,
1267            (F::ONE, -F::ONE),
1268        )?;
1269        self.assert_zero(layouter, &must_be_zero)?;
1270
1271        // The two equations we have enforced guarantee the bit-ness of `res`.
1272        Ok(AssignedBit(res))
1273    }
1274
1275    fn is_not_equal(
1276        &self,
1277        layouter: &mut impl Layouter<F>,
1278        x: &AssignedNative<F>,
1279        y: &AssignedNative<F>,
1280    ) -> Result<AssignedBit<F>, Error> {
1281        // We enforce (i) (x - y) * aux = res
1282        // and       (ii) (x - y) * (1 - res) = 0.
1283        //  * If  x = y, (i)  implies res = 0; (ii) becomes trivial.
1284        //  * If x != y, (ii) implies res = 1; (i) can be relaxed with aux = (x - y)^-1.
1285
1286        let value_cols = &self.config.value_cols;
1287        let res_val = x.value().zip(y.value()).map(|(x, y)| F::from((x != y) as u64));
1288        let aux_val = x.value().zip(y.value()).map(|(x, y)| (*x - *y).invert().unwrap_or(F::ONE));
1289
1290        // (i) enforced as aux * x - aux * y - res = 0.
1291        let res = layouter.assign_region(
1292            || "is_not_equal (i)",
1293            |mut region| {
1294                region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
1295                self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
1296                self.copy_in_row(&mut region, y, &value_cols[2], 0)?;
1297                let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
1298                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1299                coeffs[4] = -F::ONE; // coeff of res
1300                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), F::ZERO, 0)?;
1301                Ok(res)
1302            },
1303        )?;
1304
1305        // (ii) enforced as x - y - res * x + res * y = 0.
1306        let must_be_zero = self.add_and_double_mul(
1307            layouter,
1308            (F::ZERO, &res),
1309            (F::ONE, x),
1310            (-F::ONE, y),
1311            F::ZERO,
1312            (-F::ONE, F::ONE),
1313        )?;
1314        self.assert_zero(layouter, &must_be_zero)?;
1315
1316        // The two equations we have enforced guarantee the bit-ness of `res`.
1317        Ok(AssignedBit(res))
1318    }
1319
1320    fn is_equal_to_fixed(
1321        &self,
1322        layouter: &mut impl Layouter<F>,
1323        x: &AssignedNative<F>,
1324        c: F,
1325    ) -> Result<AssignedBit<F>, Error> {
1326        // We enforce (i) (x - c) * aux = 1 - res
1327        // and       (ii) (x - c) * res = 0.
1328        //  * If  x = c, (i)  implies res = 1; (ii) becomes trivial.
1329        //  * If x != c, (ii) implies res = 0; (i) can be relaxed with aux = (x - c)^-1.
1330
1331        let value_cols = &self.config.value_cols;
1332        let res_val = x.value().map(|x| F::from((*x == c) as u64));
1333        let aux_val = x.value().map(|x| (*x - c).invert().unwrap_or(F::ONE));
1334
1335        // (i) enforced as res - c * aux + aux * x - 1 = 0.
1336        let res = layouter.assign_region(
1337            || "is_equal (i)",
1338            |mut region| {
1339                region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
1340                self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
1341                let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
1342                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1343                coeffs[0] = -c; // coeff of aux
1344                coeffs[4] = F::ONE; // coeff of res
1345                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), -F::ONE, 0)?;
1346                Ok(res)
1347            },
1348        )?;
1349
1350        // (ii) enforced as -c * res + res * x = 0.
1351        let must_be_zero = self.add_and_mul(
1352            layouter,
1353            (-c, &res),
1354            (F::ZERO, x),
1355            (F::ZERO, x),
1356            F::ZERO,
1357            F::ONE,
1358        )?;
1359        self.assert_zero(layouter, &must_be_zero)?;
1360
1361        // The two equations we have enforced guarantee the bit-ness of `res`.
1362        Ok(AssignedBit(res))
1363    }
1364
1365    fn is_not_equal_to_fixed(
1366        &self,
1367        layouter: &mut impl Layouter<F>,
1368        x: &AssignedNative<F>,
1369        c: F,
1370    ) -> Result<AssignedBit<F>, Error> {
1371        // We enforce (i) (x - c) * aux = res
1372        // and       (ii) (x - c) * (1 - res) = 0.
1373        //  * If  x = c, (i)  implies res = 0; (ii) becomes trivial.
1374        //  * If x != c, (ii) implies res = 1; (i) can be relaxed with aux = (x - c)^-1.
1375
1376        let value_cols = &self.config.value_cols;
1377        let res_val = x.value().map(|x| F::from((*x != c) as u64));
1378        let aux_val = x.value().map(|x| (*x - c).invert().unwrap_or(F::ONE));
1379
1380        // (i) enforced as - c * aux + aux * x - res = 0.
1381        let res = layouter.assign_region(
1382            || "is_not_equal (i)",
1383            |mut region| {
1384                region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
1385                self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
1386                let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
1387                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1388                coeffs[0] = -c; // coeff of aux
1389                coeffs[4] = -F::ONE; // coeff of res
1390                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), F::ZERO, 0)?;
1391                Ok(res)
1392            },
1393        )?;
1394
1395        // (ii) enforced as x - c + c * res - res * x = 0.
1396        let must_be_zero =
1397            self.add_and_mul(layouter, (c, &res), (F::ONE, x), (F::ZERO, x), -c, -F::ONE)?;
1398        self.assert_zero(layouter, &must_be_zero)?;
1399
1400        // The two equations we have enforced guarantee the bit-ness of `res`.
1401        Ok(AssignedBit(res))
1402    }
1403}
1404
1405impl<F> EqualityInstructions<F, AssignedBit<F>> for NativeChip<F>
1406where
1407    F: PrimeField + From<u64> + Neg<Output = F>,
1408{
1409    fn is_equal(
1410        &self,
1411        layouter: &mut impl Layouter<F>,
1412        a: &AssignedBit<F>,
1413        b: &AssignedBit<F>,
1414    ) -> Result<AssignedBit<F>, Error> {
1415        // 1 + 2ab - a - b
1416        self.add_and_mul(
1417            layouter,
1418            (-F::ONE, &a.0),
1419            (-F::ONE, &b.0),
1420            (F::ZERO, &a.0),
1421            F::ONE,
1422            F::from(2),
1423        )
1424        .map(AssignedBit)
1425    }
1426
1427    fn is_not_equal(
1428        &self,
1429        layouter: &mut impl Layouter<F>,
1430        a: &AssignedBit<F>,
1431        b: &AssignedBit<F>,
1432    ) -> Result<AssignedBit<F>, Error> {
1433        // a + b - 2ab
1434        self.add_and_mul(
1435            layouter,
1436            (F::ONE, &a.0),
1437            (F::ONE, &b.0),
1438            (F::ZERO, &a.0),
1439            F::ZERO,
1440            -F::from(2),
1441        )
1442        .map(AssignedBit)
1443    }
1444
1445    fn is_equal_to_fixed(
1446        &self,
1447        layouter: &mut impl Layouter<F>,
1448        b: &AssignedBit<F>,
1449        constant: bool,
1450    ) -> Result<AssignedBit<F>, Error> {
1451        let assigned_constant = self.assign_fixed(layouter, constant)?;
1452        self.is_equal(layouter, b, &assigned_constant)
1453    }
1454
1455    fn is_not_equal_to_fixed(
1456        &self,
1457        layouter: &mut impl Layouter<F>,
1458        b: &AssignedBit<F>,
1459        constant: bool,
1460    ) -> Result<AssignedBit<F>, Error> {
1461        let assigned_constant = self.assign_fixed(layouter, constant)?;
1462        self.is_not_equal(layouter, b, &assigned_constant)
1463    }
1464}
1465
1466impl<F> ControlFlowInstructions<F, AssignedNative<F>> for NativeChip<F>
1467where
1468    F: PrimeField + From<u64> + Neg<Output = F>,
1469{
1470    fn select(
1471        &self,
1472        layouter: &mut impl Layouter<F>,
1473        cond: &AssignedBit<F>,
1474        x: &AssignedNative<F>,
1475        y: &AssignedNative<F>,
1476    ) -> Result<AssignedNative<F>, Error> {
1477        // Return bit * x + (1 - bit) * y.
1478
1479        // 0*bit + 0*x + 1*y + 0 + bit*x - bit*y
1480        self.add_and_double_mul(
1481            layouter,
1482            (F::ZERO, &cond.0),
1483            (F::ZERO, x),
1484            (F::ONE, y),
1485            F::ZERO,
1486            (F::ONE, -F::ONE),
1487        )
1488    }
1489
1490    fn cond_swap(
1491        &self,
1492        layouter: &mut impl Layouter<F>,
1493        cond: &AssignedBit<F>,
1494        x: &AssignedNative<F>,
1495        y: &AssignedNative<F>,
1496    ) -> Result<(AssignedNative<F>, AssignedNative<F>), Error> {
1497        // Instead of performing 2 selects (which requires 2 rows), we
1498        // can do the second select in the same row with the q_12_minus_34
1499        // identity. The idea is that the sum of the inputs of a conditional
1500        // swap is the same as the sum of the outputs.
1501
1502        let val2 = (x.value().zip(y.value()).zip(cond.value()))
1503            .map(|((x, y), b)| if b { x } else { y })
1504            .copied();
1505        let val1 = x.value().copied() + y.value().copied() - val2;
1506
1507        // We enforce the following equations in 1 row:
1508        //   0*bit + 0*x + 1*y + 0 + bit*x - bit*y - snd = 0
1509        //   x + y - fst - snd = 0
1510        layouter.assign_region(
1511            || "cond swap",
1512            |mut region| {
1513                self.copy_in_row(&mut region, &cond.0, &self.config.value_cols[0], 0)?;
1514                self.copy_in_row(&mut region, x, &self.config.value_cols[1], 0)?;
1515                self.copy_in_row(&mut region, y, &self.config.value_cols[2], 0)?;
1516                let fst = region.assign_advice(|| "fst", self.config.value_cols[3], 0, || val1)?;
1517                let snd = region.assign_advice(|| "snd", self.config.value_cols[4], 0, || val2)?;
1518                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1519                coeffs[2] = F::ONE; // coeff of y
1520                coeffs[4] = -F::ONE; // coeff of snd
1521                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), F::ZERO, 0)?;
1522                self.config.q_12_minus_34.enable(&mut region, 0)?;
1523                Ok((fst, snd))
1524            },
1525        )
1526    }
1527}
1528
1529impl<F> ControlFlowInstructions<F, AssignedBit<F>> for NativeChip<F>
1530where
1531    F: PrimeField,
1532{
1533    fn select(
1534        &self,
1535        layouter: &mut impl Layouter<F>,
1536        cond: &AssignedBit<F>,
1537        x: &AssignedBit<F>,
1538        y: &AssignedBit<F>,
1539    ) -> Result<AssignedBit<F>, Error> {
1540        self.select(layouter, cond, &x.0, &y.0).map(AssignedBit)
1541    }
1542
1543    fn cond_swap(
1544        &self,
1545        layouter: &mut impl Layouter<F>,
1546        cond: &AssignedBit<F>,
1547        x: &AssignedBit<F>,
1548        y: &AssignedBit<F>,
1549    ) -> Result<(AssignedBit<F>, AssignedBit<F>), Error> {
1550        self.cond_swap(layouter, cond, &x.0, &y.0)
1551            .map(|(fst, snd)| (AssignedBit(fst), AssignedBit(snd)))
1552    }
1553}
1554
1555impl<F> FieldInstructions<F, AssignedNative<F>> for NativeChip<F>
1556where
1557    F: PrimeField,
1558{
1559    fn order(&self) -> BigUint {
1560        modulus::<F>()
1561    }
1562}
1563
1564impl<F> CanonicityInstructions<F, AssignedNative<F>> for NativeChip<F>
1565where
1566    F: PrimeField,
1567{
1568    fn le_bits_lower_than(
1569        &self,
1570        layouter: &mut impl Layouter<F>,
1571        bits: &[AssignedBit<F>],
1572        bound: BigUint,
1573    ) -> Result<AssignedBit<F>, Error> {
1574        let geq = self.le_bits_geq_than(layouter, bits, bound)?;
1575        self.not(layouter, &geq)
1576    }
1577
1578    fn le_bits_geq_than(
1579        &self,
1580        layouter: &mut impl Layouter<F>,
1581        bits: &[AssignedBit<F>],
1582        bound: BigUint,
1583    ) -> Result<AssignedBit<F>, Error> {
1584        // Any value is greater than or equal to zero.
1585        if bound.is_zero() {
1586            return self.assign_fixed(layouter, true);
1587        }
1588
1589        // Return false if |bits| is lower than |bound|.
1590        if bits.len() < bound.bits() as usize {
1591            return self.assign_fixed(layouter, false);
1592        }
1593
1594        // base case: bits.len() = 1. We have three cases:
1595        //  * bound = 0 ==>  true  (already handled)
1596        //  * bound > 1 ==>  false (already handled)
1597        //  * bound = 1 ==>  return bits[0] == 1
1598        if bits.len() == 1 {
1599            return Ok(bits[0].clone());
1600        }
1601
1602        let msb_pos = bits.len() - 1;
1603
1604        let rest_is_geq = {
1605            let mut rest_bound = bound.clone();
1606            rest_bound.set_bit(msb_pos as u64, false);
1607            self.le_bits_geq_than(layouter, &bits[0..msb_pos], rest_bound)?
1608        };
1609
1610        if bound.bit(msb_pos as u64) {
1611            self.and(layouter, &[bits[msb_pos].clone(), rest_is_geq])
1612        } else {
1613            self.or(layouter, &[bits[msb_pos].clone(), rest_is_geq])
1614        }
1615    }
1616}
1617
1618#[cfg(any(test, feature = "testing"))]
1619impl<F: PrimeField> FromScratch<F> for NativeChip<F> {
1620    type Config = NativeConfig;
1621
1622    fn new_from_scratch(config: &Self::Config) -> Self {
1623        NativeChip::new(config, &())
1624    }
1625
1626    fn configure_from_scratch(
1627        meta: &mut ConstraintSystem<F>,
1628        instance_columns: &[Column<Instance>; 2],
1629    ) -> Self::Config {
1630        let advice_columns: [_; NB_ARITH_COLS] = core::array::from_fn(|_| meta.advice_column());
1631        let fixed_columns: [_; NB_ARITH_FIXED_COLS] = core::array::from_fn(|_| meta.fixed_column());
1632        NativeChip::configure(meta, &(advice_columns, fixed_columns, *instance_columns))
1633    }
1634
1635    fn load_from_scratch(
1636        &self,
1637        _layouter: &mut impl midnight_proofs::circuit::Layouter<F>,
1638    ) -> Result<(), Error> {
1639        Ok(())
1640    }
1641}
1642
1643#[cfg(test)]
1644mod tests {
1645    use ff::FromUniformBytes;
1646    use midnight_curves::Fq as BlsScalar;
1647
1648    use super::*;
1649    use crate::instructions::{
1650        arithmetic, assertions, binary, canonicity, control_flow,
1651        conversions::{
1652            self,
1653            tests::Operation::{Convert, UnsafeConvert},
1654        },
1655        equality, public_input, zero,
1656    };
1657
1658    macro_rules! test {
1659        ($mod:ident, $op:ident) => {
1660            #[test]
1661            fn $op() {
1662                $mod::tests::$op::<BlsScalar, AssignedNative<BlsScalar>, NativeChip<BlsScalar>>(
1663                    "native_chip",
1664                );
1665            }
1666        };
1667    }
1668    test!(assertions, test_assertions);
1669
1670    test!(public_input, test_public_inputs);
1671
1672    test!(arithmetic, test_add);
1673    test!(arithmetic, test_sub);
1674    test!(arithmetic, test_mul);
1675    test!(arithmetic, test_div);
1676    test!(arithmetic, test_neg);
1677    test!(arithmetic, test_inv);
1678    test!(arithmetic, test_pow);
1679    test!(arithmetic, test_linear_combination);
1680    test!(arithmetic, test_add_and_mul);
1681
1682    test!(equality, test_is_equal);
1683
1684    test!(zero, test_zero_assertions);
1685    test!(zero, test_is_zero);
1686
1687    test!(control_flow, test_select);
1688    test!(control_flow, test_cond_assert_equal);
1689    test!(control_flow, test_cond_swap);
1690
1691    test!(canonicity, test_canonical);
1692    test!(canonicity, test_le_bits_lower_and_geq);
1693
1694    macro_rules! test {
1695        ($mod:ident, $op:ident) => {
1696            #[test]
1697            fn $op() {
1698                $mod::tests::$op::<BlsScalar, NativeChip<BlsScalar>>("native_chip");
1699            }
1700        };
1701    }
1702
1703    test!(binary, test_and);
1704    test!(binary, test_or);
1705    test!(binary, test_xor);
1706    test!(binary, test_not);
1707
1708    fn test_generic_conversion_to_bit<F>(name: &str)
1709    where
1710        F: PrimeField + FromUniformBytes<64> + Ord,
1711    {
1712        [
1713            (
1714                F::ZERO,
1715                Some(false),
1716                Convert,
1717                true,
1718                true,
1719                name,
1720                "convert_to_bit",
1721            ),
1722            (F::ZERO, Some(true), Convert, false, false, "", ""),
1723            (F::ONE, Some(true), Convert, true, false, "", ""),
1724            (F::ONE, Some(false), Convert, false, false, "", ""),
1725            (F::from(2), None, Convert, false, false, "", ""),
1726            (
1727                F::from(2),
1728                None,
1729                UnsafeConvert,
1730                true,
1731                true,
1732                name,
1733                "unsafe_convert_to_bit",
1734            ),
1735        ]
1736        .into_iter()
1737        .for_each(
1738            |(x, expected, operation, must_pass, cost_model, chip_name, op_name)| {
1739                conversions::tests::run::<F, AssignedNative<F>, AssignedBit<F>, NativeChip<F>>(
1740                    x, expected, operation, must_pass, cost_model, chip_name, op_name,
1741                )
1742            },
1743        );
1744    }
1745
1746    #[test]
1747    fn test_conversion_to_bit() {
1748        test_generic_conversion_to_bit::<BlsScalar>("native_chip")
1749    }
1750
1751    fn test_generic_conversion_from_bit<F>(name: &str)
1752    where
1753        F: PrimeField + FromUniformBytes<64> + Ord,
1754    {
1755        [
1756            (
1757                false,
1758                Some(F::ZERO),
1759                Convert,
1760                true,
1761                true,
1762                name,
1763                "convert_from_bit",
1764            ),
1765            (false, Some(F::ONE), Convert, false, false, "", ""),
1766            (true, Some(F::ONE), Convert, true, false, "", ""),
1767            (true, Some(F::ZERO), Convert, false, false, "", ""),
1768            (
1769                false,
1770                None,
1771                UnsafeConvert,
1772                true,
1773                true,
1774                name,
1775                "unsafe_convert_from_bit",
1776            ),
1777            (true, None, UnsafeConvert, true, false, "", ""),
1778        ]
1779        .into_iter()
1780        .for_each(
1781            |(x, expected, operation, must_pass, cost_model, chip_name, op_name)| {
1782                conversions::tests::run::<F, AssignedBit<F>, AssignedNative<F>, NativeChip<F>>(
1783                    x, expected, operation, must_pass, cost_model, chip_name, op_name,
1784                )
1785            },
1786        );
1787    }
1788
1789    #[test]
1790    fn test_conversion_from_bit() {
1791        test_generic_conversion_from_bit::<BlsScalar>("native_chip");
1792    }
1793}