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    fn from_public_input(fields: &[F]) -> Option<bool> {
1053        match fields {
1054            [f] if *f == F::ZERO => Some(false),
1055            [f] if *f == F::ONE => Some(true),
1056            _ => None,
1057        }
1058    }
1059}
1060
1061/// This wrapper type on `AssignedNative<F>` is designed to enforce type safety
1062/// on assigned bits. It prevents the user from creating an `AssignedBit`
1063/// without using the designated entry points, which guarantee (with
1064/// constraints) that the assigned value is indeed 0 or 1.
1065#[derive(Clone, Debug, PartialEq, Eq)]
1066#[must_use]
1067pub struct AssignedBit<F: CircuitField>(pub(crate) AssignedNative<F>);
1068
1069impl<F: CircuitField> Hash for AssignedBit<F> {
1070    fn hash<H: Hasher>(&self, state: &mut H) {
1071        self.0.hash(state)
1072    }
1073}
1074
1075impl<F: CircuitField> InnerValue for AssignedBit<F> {
1076    type Element = bool;
1077
1078    fn value(&self) -> Value<bool> {
1079        self.0.value().map(|b| !F::is_zero_vartime(b))
1080    }
1081}
1082
1083impl<F: CircuitField> From<AssignedBit<F>> for AssignedNative<F> {
1084    fn from(bit: AssignedBit<F>) -> Self {
1085        bit.0
1086    }
1087}
1088
1089impl<F> ConversionInstructions<F, AssignedNative<F>, AssignedBit<F>> for NativeChip<F>
1090where
1091    F: CircuitField,
1092{
1093    fn convert_value(&self, x: &F) -> Option<bool> {
1094        let is_zero: bool = F::is_zero(x).into();
1095        Some(!is_zero)
1096    }
1097
1098    fn convert(
1099        &self,
1100        layouter: &mut impl Layouter<F>,
1101        x: &AssignedNative<F>,
1102    ) -> Result<AssignedBit<F>, Error> {
1103        let b_value = x.value().map(|v| !F::is_zero_vartime(v));
1104        let b: AssignedBit<F> = self.assign(layouter, b_value)?;
1105        self.assert_equal(layouter, x, &b.0)?;
1106        Ok(b)
1107    }
1108}
1109
1110impl<F> ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>> for NativeChip<F>
1111where
1112    F: CircuitField,
1113{
1114    fn convert_value(&self, x: &bool) -> Option<F> {
1115        Some(if *x { F::from(1) } else { F::from(0) })
1116    }
1117
1118    fn convert(
1119        &self,
1120        _layouter: &mut impl Layouter<F>,
1121        bit: &AssignedBit<F>,
1122    ) -> Result<AssignedNative<F>, Error> {
1123        Ok(bit.clone().0)
1124    }
1125}
1126
1127impl<F> UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>> for NativeChip<F>
1128where
1129    F: CircuitField,
1130{
1131    /// CAUTION: use only if you know what you are doing!
1132    ///
1133    /// This function converts an `AssignedNative` to an `AssignedBit`
1134    /// *without* adding any constraints to guarantee the "bitness" of the
1135    /// assigned value.
1136    ///
1137    /// *It should be used only when the input x is already guaranteed to be a
1138    /// bit*
1139    fn convert_unsafe(
1140        &self,
1141        _layouter: &mut impl Layouter<F>,
1142        x: &AssignedNative<F>,
1143    ) -> Result<AssignedBit<F>, Error> {
1144        #[cfg(not(test))]
1145        x.value().map(|&x| {
1146            assert!(
1147                x == F::ZERO || x == F::ONE,
1148                "Trying to convert {:?} to an AssignedBit!",
1149                x
1150            );
1151        });
1152        Ok(AssignedBit(x.clone()))
1153    }
1154}
1155
1156#[cfg(test)]
1157impl<F> UnsafeConversionInstructions<F, AssignedBit<F>, AssignedNative<F>> for NativeChip<F>
1158where
1159    F: CircuitField,
1160{
1161    fn convert_unsafe(
1162        &self,
1163        layouter: &mut impl Layouter<F>,
1164        x: &AssignedBit<F>,
1165    ) -> Result<AssignedNative<F>, Error> {
1166        self.convert(layouter, x)
1167    }
1168}
1169
1170impl<F> BinaryInstructions<F> for NativeChip<F>
1171where
1172    F: CircuitField,
1173{
1174    fn and(
1175        &self,
1176        layouter: &mut impl Layouter<F>,
1177        bits: &[AssignedBit<F>],
1178    ) -> Result<AssignedBit<F>, Error> {
1179        let mut acc = bits.first().unwrap().0.clone();
1180        for b in bits.iter().skip(1) {
1181            acc = self.mul(layouter, &acc, &b.0, None)?;
1182        }
1183        Ok(AssignedBit(acc))
1184    }
1185
1186    fn or(
1187        &self,
1188        layouter: &mut impl Layouter<F>,
1189        bits: &[AssignedBit<F>],
1190    ) -> Result<AssignedBit<F>, Error> {
1191        let mut acc = bits.first().unwrap().0.clone();
1192        for b in bits.iter().skip(1) {
1193            // compute acc := acc + b - acc * b
1194            acc = self.add_and_mul(
1195                layouter,
1196                (F::ONE, &acc),
1197                (F::ONE, &b.0),
1198                (F::ZERO, &acc),
1199                F::ZERO,
1200                -F::ONE,
1201            )?;
1202        }
1203        Ok(AssignedBit(acc))
1204    }
1205
1206    fn xor(
1207        &self,
1208        layouter: &mut impl Layouter<F>,
1209        bits: &[AssignedBit<F>],
1210    ) -> Result<AssignedBit<F>, Error> {
1211        let mut acc = bits.first().unwrap().0.clone();
1212        for b in bits.iter().skip(1) {
1213            // compute acc := acc + b - 2 * acc * b
1214            acc = self.add_and_mul(
1215                layouter,
1216                (F::ONE, &acc),
1217                (F::ONE, &b.0),
1218                (F::ZERO, &acc),
1219                F::ZERO,
1220                -F::from(2),
1221            )?;
1222        }
1223        Ok(AssignedBit(acc))
1224    }
1225
1226    fn not(
1227        &self,
1228        layouter: &mut impl Layouter<F>,
1229        bit: &AssignedBit<F>,
1230    ) -> Result<AssignedBit<F>, Error> {
1231        let neg_bit = self.linear_combination(layouter, &[(-F::ONE, bit.0.clone())], F::ONE)?;
1232        Ok(AssignedBit(neg_bit))
1233    }
1234}
1235
1236impl<F> EqualityInstructions<F, AssignedNative<F>> for NativeChip<F>
1237where
1238    F: CircuitField + From<u64> + Neg<Output = F>,
1239{
1240    fn is_equal(
1241        &self,
1242        layouter: &mut impl Layouter<F>,
1243        x: &AssignedNative<F>,
1244        y: &AssignedNative<F>,
1245    ) -> Result<AssignedBit<F>, Error> {
1246        // We enforce (i) (x - y) * aux = 1 - res
1247        // and       (ii) (x - y) * res = 0.
1248        //  * If  x = y, (i)  implies res = 1; (ii) becomes trivial.
1249        //  * If x != y, (ii) implies res = 0; (i) can be relaxed with aux = (x - y)^-1.
1250
1251        let value_cols = &self.config.value_cols;
1252        let res_val = x.value().zip(y.value()).map(|(x, y)| F::from((x == y) as u64));
1253        let aux_val = x.value().zip(y.value()).map(|(x, y)| (*x - *y).invert().unwrap_or(F::ONE));
1254
1255        // (i) enforced as res + aux * x - aux * y - 1 = 0.
1256        let res = layouter.assign_region(
1257            || "is_equal (i)",
1258            |mut region| {
1259                region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
1260                self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
1261                self.copy_in_row(&mut region, y, &value_cols[2], 0)?;
1262                let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
1263                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1264                coeffs[4] = F::ONE; // coeff of res
1265                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), -F::ONE, 0)?;
1266                Ok(res)
1267            },
1268        )?;
1269
1270        // (ii) enforced as res * x - res * y = 0.
1271        let must_be_zero = self.add_and_double_mul(
1272            layouter,
1273            (F::ZERO, &res),
1274            (F::ZERO, x),
1275            (F::ZERO, y),
1276            F::ZERO,
1277            (F::ONE, -F::ONE),
1278        )?;
1279        self.assert_zero(layouter, &must_be_zero)?;
1280
1281        // The two equations we have enforced guarantee the bit-ness of `res`.
1282        Ok(AssignedBit(res))
1283    }
1284
1285    fn is_not_equal(
1286        &self,
1287        layouter: &mut impl Layouter<F>,
1288        x: &AssignedNative<F>,
1289        y: &AssignedNative<F>,
1290    ) -> Result<AssignedBit<F>, Error> {
1291        // We enforce (i) (x - y) * aux = res
1292        // and       (ii) (x - y) * (1 - res) = 0.
1293        //  * If  x = y, (i)  implies res = 0; (ii) becomes trivial.
1294        //  * If x != y, (ii) implies res = 1; (i) can be relaxed with aux = (x - y)^-1.
1295
1296        let value_cols = &self.config.value_cols;
1297        let res_val = x.value().zip(y.value()).map(|(x, y)| F::from((x != y) as u64));
1298        let aux_val = x.value().zip(y.value()).map(|(x, y)| (*x - *y).invert().unwrap_or(F::ONE));
1299
1300        // (i) enforced as aux * x - aux * y - res = 0.
1301        let res = layouter.assign_region(
1302            || "is_not_equal (i)",
1303            |mut region| {
1304                region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
1305                self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
1306                self.copy_in_row(&mut region, y, &value_cols[2], 0)?;
1307                let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
1308                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1309                coeffs[4] = -F::ONE; // coeff of res
1310                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), F::ZERO, 0)?;
1311                Ok(res)
1312            },
1313        )?;
1314
1315        // (ii) enforced as x - y - res * x + res * y = 0.
1316        let must_be_zero = self.add_and_double_mul(
1317            layouter,
1318            (F::ZERO, &res),
1319            (F::ONE, x),
1320            (-F::ONE, y),
1321            F::ZERO,
1322            (-F::ONE, F::ONE),
1323        )?;
1324        self.assert_zero(layouter, &must_be_zero)?;
1325
1326        // The two equations we have enforced guarantee the bit-ness of `res`.
1327        Ok(AssignedBit(res))
1328    }
1329
1330    fn is_equal_to_fixed(
1331        &self,
1332        layouter: &mut impl Layouter<F>,
1333        x: &AssignedNative<F>,
1334        c: F,
1335    ) -> Result<AssignedBit<F>, Error> {
1336        // We enforce (i) (x - c) * aux = 1 - res
1337        // and       (ii) (x - c) * res = 0.
1338        //  * If  x = c, (i)  implies res = 1; (ii) becomes trivial.
1339        //  * If x != c, (ii) implies res = 0; (i) can be relaxed with aux = (x - c)^-1.
1340
1341        let value_cols = &self.config.value_cols;
1342        let res_val = x.value().map(|x| F::from((*x == c) as u64));
1343        let aux_val = x.value().map(|x| (*x - c).invert().unwrap_or(F::ONE));
1344
1345        // (i) enforced as res - c * aux + aux * x - 1 = 0.
1346        let res = layouter.assign_region(
1347            || "is_equal (i)",
1348            |mut region| {
1349                region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
1350                self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
1351                let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
1352                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1353                coeffs[0] = -c; // coeff of aux
1354                coeffs[4] = F::ONE; // coeff of res
1355                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), -F::ONE, 0)?;
1356                Ok(res)
1357            },
1358        )?;
1359
1360        // (ii) enforced as -c * res + res * x = 0.
1361        let must_be_zero = self.add_and_mul(
1362            layouter,
1363            (-c, &res),
1364            (F::ZERO, x),
1365            (F::ZERO, x),
1366            F::ZERO,
1367            F::ONE,
1368        )?;
1369        self.assert_zero(layouter, &must_be_zero)?;
1370
1371        // The two equations we have enforced guarantee the bit-ness of `res`.
1372        Ok(AssignedBit(res))
1373    }
1374
1375    fn is_not_equal_to_fixed(
1376        &self,
1377        layouter: &mut impl Layouter<F>,
1378        x: &AssignedNative<F>,
1379        c: F,
1380    ) -> Result<AssignedBit<F>, Error> {
1381        // We enforce (i) (x - c) * aux = res
1382        // and       (ii) (x - c) * (1 - res) = 0.
1383        //  * If  x = c, (i)  implies res = 0; (ii) becomes trivial.
1384        //  * If x != c, (ii) implies res = 1; (i) can be relaxed with aux = (x - c)^-1.
1385
1386        let value_cols = &self.config.value_cols;
1387        let res_val = x.value().map(|x| F::from((*x != c) as u64));
1388        let aux_val = x.value().map(|x| (*x - c).invert().unwrap_or(F::ONE));
1389
1390        // (i) enforced as - c * aux + aux * x - res = 0.
1391        let res = layouter.assign_region(
1392            || "is_not_equal (i)",
1393            |mut region| {
1394                region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
1395                self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
1396                let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
1397                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1398                coeffs[0] = -c; // coeff of aux
1399                coeffs[4] = -F::ONE; // coeff of res
1400                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), F::ZERO, 0)?;
1401                Ok(res)
1402            },
1403        )?;
1404
1405        // (ii) enforced as x - c + c * res - res * x = 0.
1406        let must_be_zero =
1407            self.add_and_mul(layouter, (c, &res), (F::ONE, x), (F::ZERO, x), -c, -F::ONE)?;
1408        self.assert_zero(layouter, &must_be_zero)?;
1409
1410        // The two equations we have enforced guarantee the bit-ness of `res`.
1411        Ok(AssignedBit(res))
1412    }
1413}
1414
1415impl<F> EqualityInstructions<F, AssignedBit<F>> for NativeChip<F>
1416where
1417    F: CircuitField + From<u64> + Neg<Output = F>,
1418{
1419    fn is_equal(
1420        &self,
1421        layouter: &mut impl Layouter<F>,
1422        a: &AssignedBit<F>,
1423        b: &AssignedBit<F>,
1424    ) -> Result<AssignedBit<F>, Error> {
1425        // 1 + 2ab - a - b
1426        self.add_and_mul(
1427            layouter,
1428            (-F::ONE, &a.0),
1429            (-F::ONE, &b.0),
1430            (F::ZERO, &a.0),
1431            F::ONE,
1432            F::from(2),
1433        )
1434        .map(AssignedBit)
1435    }
1436
1437    fn is_not_equal(
1438        &self,
1439        layouter: &mut impl Layouter<F>,
1440        a: &AssignedBit<F>,
1441        b: &AssignedBit<F>,
1442    ) -> Result<AssignedBit<F>, Error> {
1443        // a + b - 2ab
1444        self.add_and_mul(
1445            layouter,
1446            (F::ONE, &a.0),
1447            (F::ONE, &b.0),
1448            (F::ZERO, &a.0),
1449            F::ZERO,
1450            -F::from(2),
1451        )
1452        .map(AssignedBit)
1453    }
1454
1455    fn is_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_equal(layouter, b, &assigned_constant)
1463    }
1464
1465    fn is_not_equal_to_fixed(
1466        &self,
1467        layouter: &mut impl Layouter<F>,
1468        b: &AssignedBit<F>,
1469        constant: bool,
1470    ) -> Result<AssignedBit<F>, Error> {
1471        let assigned_constant = self.assign_fixed(layouter, constant)?;
1472        self.is_not_equal(layouter, b, &assigned_constant)
1473    }
1474}
1475
1476impl<F> ControlFlowInstructions<F, AssignedNative<F>> for NativeChip<F>
1477where
1478    F: CircuitField + From<u64> + Neg<Output = F>,
1479{
1480    fn select(
1481        &self,
1482        layouter: &mut impl Layouter<F>,
1483        cond: &AssignedBit<F>,
1484        x: &AssignedNative<F>,
1485        y: &AssignedNative<F>,
1486    ) -> Result<AssignedNative<F>, Error> {
1487        // Return bit * x + (1 - bit) * y.
1488
1489        // 0*bit + 0*x + 1*y + 0 + bit*x - bit*y
1490        self.add_and_double_mul(
1491            layouter,
1492            (F::ZERO, &cond.0),
1493            (F::ZERO, x),
1494            (F::ONE, y),
1495            F::ZERO,
1496            (F::ONE, -F::ONE),
1497        )
1498    }
1499
1500    fn cond_swap(
1501        &self,
1502        layouter: &mut impl Layouter<F>,
1503        cond: &AssignedBit<F>,
1504        x: &AssignedNative<F>,
1505        y: &AssignedNative<F>,
1506    ) -> Result<(AssignedNative<F>, AssignedNative<F>), Error> {
1507        // Instead of performing 2 selects (which requires 2 rows), we
1508        // can do the second select in the same row with the q_12_minus_34
1509        // identity. The idea is that the sum of the inputs of a conditional
1510        // swap is the same as the sum of the outputs.
1511
1512        let val2 = (x.value().zip(y.value()).zip(cond.value()))
1513            .map(|((x, y), b)| if b { x } else { y })
1514            .copied();
1515        let val1 = x.value().copied() + y.value().copied() - val2;
1516
1517        // We enforce the following equations in 1 row:
1518        //   0*bit + 0*x + 1*y + 0 + bit*x - bit*y - snd = 0
1519        //   x + y - fst - snd = 0
1520        layouter.assign_region(
1521            || "cond swap",
1522            |mut region| {
1523                self.copy_in_row(&mut region, &cond.0, &self.config.value_cols[0], 0)?;
1524                self.copy_in_row(&mut region, x, &self.config.value_cols[1], 0)?;
1525                self.copy_in_row(&mut region, y, &self.config.value_cols[2], 0)?;
1526                let fst = region.assign_advice(|| "fst", self.config.value_cols[3], 0, || val1)?;
1527                let snd = region.assign_advice(|| "snd", self.config.value_cols[4], 0, || val2)?;
1528                let mut coeffs = [F::ZERO; NB_ARITH_COLS];
1529                coeffs[2] = F::ONE; // coeff of y
1530                coeffs[4] = -F::ONE; // coeff of snd
1531                self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), F::ZERO, 0)?;
1532                self.config.q_12_minus_34.enable(&mut region, 0)?;
1533                Ok((fst, snd))
1534            },
1535        )
1536    }
1537}
1538
1539impl<F> ControlFlowInstructions<F, AssignedBit<F>> for NativeChip<F>
1540where
1541    F: CircuitField,
1542{
1543    fn select(
1544        &self,
1545        layouter: &mut impl Layouter<F>,
1546        cond: &AssignedBit<F>,
1547        x: &AssignedBit<F>,
1548        y: &AssignedBit<F>,
1549    ) -> Result<AssignedBit<F>, Error> {
1550        self.select(layouter, cond, &x.0, &y.0).map(AssignedBit)
1551    }
1552
1553    fn cond_swap(
1554        &self,
1555        layouter: &mut impl Layouter<F>,
1556        cond: &AssignedBit<F>,
1557        x: &AssignedBit<F>,
1558        y: &AssignedBit<F>,
1559    ) -> Result<(AssignedBit<F>, AssignedBit<F>), Error> {
1560        self.cond_swap(layouter, cond, &x.0, &y.0)
1561            .map(|(fst, snd)| (AssignedBit(fst), AssignedBit(snd)))
1562    }
1563}
1564
1565impl<F> FieldInstructions<F, AssignedNative<F>> for NativeChip<F>
1566where
1567    F: CircuitField,
1568{
1569    fn order(&self) -> BigUint {
1570        F::modulus()
1571    }
1572}
1573
1574impl<F> CanonicityInstructions<F, AssignedNative<F>> for NativeChip<F>
1575where
1576    F: CircuitField,
1577{
1578    fn le_bits_lower_than(
1579        &self,
1580        layouter: &mut impl Layouter<F>,
1581        bits: &[AssignedBit<F>],
1582        bound: BigUint,
1583    ) -> Result<AssignedBit<F>, Error> {
1584        let geq = self.le_bits_geq_than(layouter, bits, bound)?;
1585        self.not(layouter, &geq)
1586    }
1587
1588    fn le_bits_geq_than(
1589        &self,
1590        layouter: &mut impl Layouter<F>,
1591        bits: &[AssignedBit<F>],
1592        bound: BigUint,
1593    ) -> Result<AssignedBit<F>, Error> {
1594        // Any value is greater than or equal to zero.
1595        if bound.is_zero() {
1596            return self.assign_fixed(layouter, true);
1597        }
1598
1599        // Return false if |bits| is lower than |bound|.
1600        if bits.len() < bound.bits() as usize {
1601            return self.assign_fixed(layouter, false);
1602        }
1603
1604        // base case: bits.len() = 1. We have three cases:
1605        //  * bound = 0 ==>  true  (already handled)
1606        //  * bound > 1 ==>  false (already handled)
1607        //  * bound = 1 ==>  return bits[0] == 1
1608        if bits.len() == 1 {
1609            return Ok(bits[0].clone());
1610        }
1611
1612        let msb_pos = bits.len() - 1;
1613
1614        let rest_is_geq = {
1615            let mut rest_bound = bound.clone();
1616            rest_bound.set_bit(msb_pos as u64, false);
1617            self.le_bits_geq_than(layouter, &bits[0..msb_pos], rest_bound)?
1618        };
1619
1620        if bound.bit(msb_pos as u64) {
1621            self.and(layouter, &[bits[msb_pos].clone(), rest_is_geq])
1622        } else {
1623            self.or(layouter, &[bits[msb_pos].clone(), rest_is_geq])
1624        }
1625    }
1626}
1627
1628#[cfg(any(test, feature = "testing"))]
1629impl<F: CircuitField> FromScratch<F> for NativeChip<F> {
1630    type Config = NativeConfig;
1631
1632    fn new_from_scratch(config: &Self::Config) -> Self {
1633        NativeChip::new(config, &())
1634    }
1635
1636    fn configure_from_scratch(
1637        meta: &mut ConstraintSystem<F>,
1638        advice_columns: &mut Vec<Column<Advice>>,
1639        fixed_columns: &mut Vec<Column<Fixed>>,
1640        instance_columns: &[Column<Instance>; 2],
1641    ) -> Self::Config {
1642        while advice_columns.len() < NB_ARITH_COLS {
1643            advice_columns.push(meta.advice_column());
1644        }
1645        while fixed_columns.len() < NB_ARITH_FIXED_COLS {
1646            fixed_columns.push(meta.fixed_column());
1647        }
1648        let advice_cols: [_; NB_ARITH_COLS] = advice_columns[..NB_ARITH_COLS].try_into().unwrap();
1649        let fixed_cols: [_; NB_ARITH_FIXED_COLS] =
1650            fixed_columns[..NB_ARITH_FIXED_COLS].try_into().unwrap();
1651        NativeChip::configure(meta, &(advice_cols, fixed_cols, *instance_columns))
1652    }
1653
1654    fn load_from_scratch(
1655        &self,
1656        _layouter: &mut impl midnight_proofs::circuit::Layouter<F>,
1657    ) -> Result<(), Error> {
1658        Ok(())
1659    }
1660}
1661
1662#[cfg(test)]
1663mod tests {
1664    use ff::FromUniformBytes;
1665    use midnight_curves::Fq as BlsScalar;
1666
1667    use super::*;
1668    use crate::instructions::{
1669        arithmetic, assertions, binary, canonicity, control_flow,
1670        conversions::{
1671            self,
1672            tests::Operation::{Convert, UnsafeConvert},
1673        },
1674        equality, public_input, zero,
1675    };
1676
1677    macro_rules! test {
1678        ($mod:ident, $op:ident) => {
1679            #[test]
1680            fn $op() {
1681                $mod::tests::$op::<BlsScalar, AssignedNative<BlsScalar>, NativeChip<BlsScalar>>(
1682                    "native_chip",
1683                );
1684            }
1685        };
1686    }
1687    test!(assertions, test_assertions);
1688
1689    test!(public_input, test_public_inputs);
1690
1691    test!(arithmetic, test_add);
1692    test!(arithmetic, test_sub);
1693    test!(arithmetic, test_mul);
1694    test!(arithmetic, test_div);
1695    test!(arithmetic, test_neg);
1696    test!(arithmetic, test_inv);
1697    test!(arithmetic, test_pow);
1698    test!(arithmetic, test_linear_combination);
1699    test!(arithmetic, test_add_and_mul);
1700
1701    test!(equality, test_is_equal);
1702
1703    test!(zero, test_zero_assertions);
1704    test!(zero, test_is_zero);
1705
1706    test!(control_flow, test_select);
1707    test!(control_flow, test_cond_assert_equal);
1708    test!(control_flow, test_cond_swap);
1709
1710    test!(canonicity, test_canonical);
1711    test!(canonicity, test_le_bits_lower_and_geq);
1712
1713    macro_rules! test {
1714        ($mod:ident, $op:ident) => {
1715            #[test]
1716            fn $op() {
1717                $mod::tests::$op::<BlsScalar, NativeChip<BlsScalar>>("native_chip");
1718            }
1719        };
1720    }
1721
1722    test!(binary, test_and);
1723    test!(binary, test_or);
1724    test!(binary, test_xor);
1725    test!(binary, test_not);
1726
1727    fn test_generic_conversion_to_bit<F>(name: &str)
1728    where
1729        F: CircuitField + FromUniformBytes<64> + Ord,
1730    {
1731        [
1732            (
1733                F::ZERO,
1734                Some(false),
1735                Convert,
1736                true,
1737                true,
1738                name,
1739                "convert_to_bit",
1740            ),
1741            (F::ZERO, Some(true), Convert, false, false, "", ""),
1742            (F::ONE, Some(true), Convert, true, false, "", ""),
1743            (F::ONE, Some(false), Convert, false, false, "", ""),
1744            (F::from(2), None, Convert, false, false, "", ""),
1745            (
1746                F::from(2),
1747                None,
1748                UnsafeConvert,
1749                true,
1750                true,
1751                name,
1752                "unsafe_convert_to_bit",
1753            ),
1754        ]
1755        .into_iter()
1756        .for_each(
1757            |(x, expected, operation, must_pass, cost_model, chip_name, op_name)| {
1758                conversions::tests::run::<F, AssignedNative<F>, AssignedBit<F>, NativeChip<F>>(
1759                    x, expected, operation, must_pass, cost_model, chip_name, op_name,
1760                )
1761            },
1762        );
1763    }
1764
1765    #[test]
1766    fn test_conversion_to_bit() {
1767        test_generic_conversion_to_bit::<BlsScalar>("native_chip")
1768    }
1769
1770    fn test_generic_conversion_from_bit<F>(name: &str)
1771    where
1772        F: CircuitField + FromUniformBytes<64> + Ord,
1773    {
1774        [
1775            (
1776                false,
1777                Some(F::ZERO),
1778                Convert,
1779                true,
1780                true,
1781                name,
1782                "convert_from_bit",
1783            ),
1784            (false, Some(F::ONE), Convert, false, false, "", ""),
1785            (true, Some(F::ONE), Convert, true, false, "", ""),
1786            (true, Some(F::ZERO), Convert, false, false, "", ""),
1787            (
1788                false,
1789                None,
1790                UnsafeConvert,
1791                true,
1792                true,
1793                name,
1794                "unsafe_convert_from_bit",
1795            ),
1796            (true, None, UnsafeConvert, true, false, "", ""),
1797        ]
1798        .into_iter()
1799        .for_each(
1800            |(x, expected, operation, must_pass, cost_model, chip_name, op_name)| {
1801                conversions::tests::run::<F, AssignedBit<F>, AssignedNative<F>, NativeChip<F>>(
1802                    x, expected, operation, must_pass, cost_model, chip_name, op_name,
1803                )
1804            },
1805        );
1806    }
1807
1808    #[test]
1809    fn test_conversion_from_bit() {
1810        test_generic_conversion_from_bit::<BlsScalar>("native_chip");
1811    }
1812}