Skip to main content

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