Skip to main content

midnight_circuits/field/foreign/
field_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//! `field_chip` is a chip for performing arithmetic over emulated fields.
15//!  See [here](https://github.com/midnightntwrk/midnight-circuits/wiki/Foreign-Field-Arithmetic)
16//!  for a description of the techniques that we use in this implementation.
17
18use std::{
19    cmp::{max, min},
20    fmt::Debug,
21    hash::{Hash, Hasher},
22    marker::PhantomData,
23};
24
25use midnight_proofs::{
26    circuit::{Chip, Layouter, Value},
27    plonk::{Advice, Column, ConstraintSystem, Error},
28};
29use num_bigint::{BigInt as BI, BigUint, ToBigInt};
30use num_integer::Integer;
31use num_traits::{One, Signed, Zero};
32#[cfg(any(test, feature = "testing"))]
33use {
34    crate::testing_utils::{FromScratch, Sampleable},
35    midnight_proofs::plonk::{Fixed, Instance},
36    rand::RngCore,
37};
38
39use super::gates::{
40    mul::{self, MulConfig},
41    norm::{self, NormConfig},
42};
43use crate::{
44    field::foreign::{
45        params::{check_params, FieldEmulationParams},
46        util::{bi_from_limbs, bi_to_limbs},
47    },
48    instructions::{
49        ArithInstructions, AssertionInstructions, AssignmentInstructions, CanonicityInstructions,
50        ControlFlowInstructions, ConversionInstructions, DecompositionInstructions,
51        EqualityInstructions, FieldInstructions, NativeInstructions, PublicInputInstructions,
52        ScalarFieldInstructions, ZeroInstructions,
53    },
54    types::{AssignedBit, AssignedByte, AssignedNative, InnerConstants, InnerValue, Instantiable},
55    utils::util::bigint_to_fe,
56    CircuitField,
57};
58
59/// Type for assigned emulated field elements of K over native field F.
60//  - `limb_values` is a vector of assigned cells representing the emulated element in base `base`.
61//  - `limb_bounds` is a vector of BigInt pairs containing a lower bound and an upper bound on the
62//    values of every limb in `limb_values`. Both bounds are inclusive. The lower bound can be
63//    negative; if that is the case, the limb value may have wrapped-around the native modulus below
64//    zero, this will be corrected later in the identities.
65//
66// The integer x represented by limbs [x0, ..., x_{n-1}] is defined as
67//   x := 1 + sum_i base^i xi
68//
69// The +1 shift is introduced so that integer 0 has a unique representation in
70// limbs form, this greatly simplifies comparisons with zero.
71//
72// An AssignedField is well-formed if limb_bounds = (0, base-1).
73//
74// We will perform additions or subtractions with AssignedField even if they
75// are not well-formed, by operating limb-wise and updating the bounds
76// accordingly.
77//
78// However, for multiplication, well-formedness of inputs is a requirement.
79// We can use the `normalize` function to make a AssignedField well-formed as long as the
80// `limb_bounds` have a moderate size.
81#[derive(Clone, Debug)]
82#[must_use]
83pub struct AssignedField<F, K, P>
84where
85    F: CircuitField,
86    K: CircuitField,
87    P: FieldEmulationParams<F, K>,
88{
89    limb_values: Vec<AssignedNative<F>>,
90    limb_bounds: Vec<(BI, BI)>,
91    _marker: PhantomData<(K, P)>,
92}
93
94impl<F, K, P> PartialEq for AssignedField<F, K, P>
95where
96    F: CircuitField,
97    K: CircuitField,
98    P: FieldEmulationParams<F, K>,
99{
100    fn eq(&self, other: &Self) -> bool {
101        self.limb_values
102            .iter()
103            .zip(other.limb_values.iter())
104            .all(|(s, o)| s.cell() == o.cell())
105    }
106}
107
108impl<F: CircuitField, K: CircuitField, P: FieldEmulationParams<F, K>> Eq
109    for AssignedField<F, K, P>
110{
111}
112
113impl<F, K, P> Hash for AssignedField<F, K, P>
114where
115    F: CircuitField,
116    K: CircuitField,
117    P: FieldEmulationParams<F, K>,
118{
119    fn hash<H: Hasher>(&self, state: &mut H) {
120        self.limb_values.iter().for_each(|elem| elem.hash(state));
121    }
122}
123
124impl<F, K, P> Instantiable<F> for AssignedField<F, K, P>
125where
126    F: CircuitField,
127    K: CircuitField,
128    P: FieldEmulationParams<F, K>,
129{
130    fn as_public_input(element: &K) -> Vec<F> {
131        // We shift the value of x by 1 for the unique-zero representation.
132        let element_as_bi = (*element - K::ONE).to_biguint().into();
133        let base = BI::from(2).pow(P::LOG2_BASE);
134        let nb_limbs_per_batch = (F::CAPACITY / P::LOG2_BASE) as usize;
135        bi_to_limbs(P::NB_LIMBS, &base, &element_as_bi)
136            .chunks(nb_limbs_per_batch)
137            .map(|chunk| bigint_to_fe::<F>(&bi_from_limbs(&base, chunk)))
138            .collect()
139    }
140}
141
142impl<F, K, P> InnerValue for AssignedField<F, K, P>
143where
144    F: CircuitField,
145    K: CircuitField,
146    P: FieldEmulationParams<F, K>,
147{
148    type Element = K;
149
150    fn value(&self) -> Value<K> {
151        let bi_limbs = self
152            .limb_values
153            .iter()
154            .zip(self.limb_bounds.iter())
155            .map(|(xi, (lower_bound, _))| {
156                // We add a shift of |lbound| to correct possible wrap-arounds below 0, and
157                // shift back after the conversion from F to BigInt
158                let shift = BI::abs(lower_bound);
159                let fe_shift = bigint_to_fe::<F>(&shift);
160                xi.value().map(|xv| {
161                    let bi: BI = (*xv + fe_shift).to_biguint().into();
162                    bi - &shift
163                })
164            })
165            .collect::<Vec<_>>();
166        let bi_limbs: Value<Vec<BI>> = Value::from_iter(bi_limbs);
167        let base = BI::from(2).pow(P::LOG2_BASE);
168        bi_limbs.map(|limbs| bigint_to_fe::<K>(&(BI::one() + bi_from_limbs(&base, &limbs))))
169    }
170}
171
172impl<F: CircuitField, K: CircuitField, P> InnerConstants for AssignedField<F, K, P>
173where
174    F: CircuitField,
175    K: CircuitField,
176    P: FieldEmulationParams<F, K>,
177{
178    fn inner_zero() -> K {
179        K::ZERO
180    }
181
182    fn inner_one() -> K {
183        K::ONE
184    }
185}
186
187impl<F, K, P> AssignedField<F, K, P>
188where
189    F: CircuitField,
190    K: CircuitField,
191    P: FieldEmulationParams<F, K>,
192{
193    /// Create an assigned value with well-formed bounds given its limbs.
194    /// This function does not guarantee that the limbs actually meet the
195    /// claimed bounds, it is the responsibility of the caller to make sure
196    /// that was asserted elsewhere.
197    /// DO NOT use this function unless you know what you are doing.
198    pub(crate) fn from_limbs_unsafe(limb_values: Vec<AssignedNative<F>>) -> Self {
199        debug_assert!(limb_values.len() as u32 == P::NB_LIMBS);
200        Self {
201            limb_values,
202            limb_bounds: well_formed_bounds::<F, K, P>(),
203            _marker: PhantomData,
204        }
205    }
206}
207
208#[cfg(any(test, feature = "testing"))]
209impl<F, K, P> Sampleable for AssignedField<F, K, P>
210where
211    F: CircuitField,
212    K: CircuitField,
213    P: FieldEmulationParams<F, K>,
214{
215    fn sample_inner(rng: impl RngCore) -> K {
216        K::random(rng)
217    }
218}
219
220/// Number of columns required by this chip.
221pub fn nb_field_chip_columns<F, K, P>() -> usize
222where
223    F: CircuitField,
224    K: CircuitField,
225    P: FieldEmulationParams<F, K>,
226{
227    P::NB_LIMBS as usize + max(P::NB_LIMBS as usize, 1 + P::moduli().len())
228}
229
230/// Creates a vector of upper-bounds (one per limb), specifiying the maximum
231/// size (log2) that each limb should take for the emulated field element to be
232/// considered well-formed.
233/// All such bounds will be equal to the base, except possibly the bound for
234/// the most significant limb, which may be smaller in order to guarantee that
235/// 0 has a unique representation.
236pub fn well_formed_log2_bounds<F, K, P>() -> Vec<u32>
237where
238    F: CircuitField,
239    K: CircuitField,
240    P: FieldEmulationParams<F, K>,
241{
242    // Let m be the emulated modulus.
243    // We want that m <= base^(nb_limbs - 1) * msl_bound < 2m,
244    // therefore msl_bound must be the first power of 2 higher than or equal to
245    // m / base^(nb_limbs - 1).
246    let m = &K::modulus().to_bigint().unwrap();
247    let log2_msl_bound = m.bits() as u32 - (P::NB_LIMBS - 1) * P::LOG2_BASE;
248    let mut bounds = vec![log2_msl_bound];
249    bounds.resize(P::NB_LIMBS as usize, P::LOG2_BASE);
250    bounds.into_iter().rev().collect::<Vec<_>>()
251}
252
253/// Foreign Field Chip configuration.
254// - q_mul is the se to enable the emulated multiplication gate.
255// - q_norm is the selector to enable the normalization gate.
256// - x and y are the inputs (in limbs form).
257// - z is the output (in limbs form).
258// - u, u_mul_bounds, u_norm_bounds, v, vs_mul_bounds and vs_norm_bounds parameters involved in the
259//   identities, refer to [mul_bounds] and [normalization_bounds] for more details.
260#[derive(Clone, Debug)]
261pub struct FieldChipConfig {
262    mul_config: mul::MulConfig,
263    norm_config: norm::NormConfig,
264    /// Column for input x
265    pub x_cols: Vec<Column<Advice>>,
266    /// Column for input y
267    pub y_cols: Vec<Column<Advice>>,
268    /// Column for input/output z
269    pub z_cols: Vec<Column<Advice>>,
270    /// Column for auxiliary value u (quotient by the emulated modulus)
271    pub u_col: Column<Advice>,
272    /// Column for auxiliary values vj (quotients by the auxiliary moduli)
273    pub v_cols: Vec<Column<Advice>>,
274}
275
276/// ['FieldChip'] for operations on field K emulated over native field F.
277#[derive(Clone, Debug)]
278pub struct FieldChip<F, K, P, N>
279where
280    F: CircuitField,
281    K: CircuitField,
282    P: FieldEmulationParams<F, K>,
283    N: NativeInstructions<F>,
284{
285    config: FieldChipConfig,
286    pub(crate) native_gadget: N,
287    _marker: PhantomData<(F, K, P, N)>,
288}
289
290impl<F, K, P> AssignedField<F, K, P>
291where
292    F: CircuitField,
293    K: CircuitField,
294    P: FieldEmulationParams<F, K>,
295{
296    /// The modulus defining the domain of this emulated field element.
297    pub fn modulus(&self) -> BI {
298        K::modulus().to_bigint().unwrap().clone()
299    }
300
301    /// Tells whether the given emulated field element is well-formed, i.e., the
302    /// limb_bounds match expected range.
303    ///
304    /// AssignedField whose range is more restricted but included in the
305    /// expected one are also considered well-formed.
306    pub fn is_well_formed(&self) -> bool {
307        self.limb_bounds.iter().zip(well_formed_log2_bounds::<F, K, P>()).all(
308            |((lower, upper), expected_upper)| {
309                assert!(lower <= upper);
310                !BI::is_negative(lower) && upper.bits() <= expected_upper as u64
311            },
312        )
313    }
314
315    /// The limb values associated to the given AssignedField.
316    /// Recall that the integer represented by limbs [x0, ..., x_{n-1}] is
317    /// x := 1 + sum_i base^i xi
318    pub fn limb_values(&self) -> Vec<AssignedNative<F>> {
319        self.limb_values.clone()
320    }
321
322    /// The limb values (in BigInt form) associated to the given AssignedField.
323    pub fn bigint_limbs(&self) -> Value<Vec<BI>> {
324        let limbs = self
325            .limb_values
326            .iter()
327            .zip(self.limb_bounds.iter())
328            .map(|(xi, (lbound, _))| {
329                // We add a shift of |lbound| to correct possible wrap-arounds below 0, and
330                // shift back after the conversion from F to BigInt
331                let shift = BI::abs(lbound);
332                let fe_shift = bigint_to_fe::<F>(&shift);
333                xi.value().map(|xv| {
334                    let bi: BI = (*xv + fe_shift).to_biguint().into();
335                    bi - &shift
336                })
337            })
338            .collect::<Vec<_>>();
339        Value::from_iter(limbs)
340    }
341}
342
343/// A vector of `NB_LIMBS` bounds of the form [0, base), except for possibly the
344/// most significant limb, which may be of the form [0, 2^k) with 2^k <= base.
345/// This is so that there exist emulated field elements with a unique
346/// representation (even if some of them have two representations).
347fn well_formed_bounds<F, K, P>() -> Vec<(BI, BI)>
348where
349    F: CircuitField,
350    K: CircuitField,
351    P: FieldEmulationParams<F, K>,
352{
353    well_formed_log2_bounds::<F, K, P>()
354        .into_iter()
355        .map(|log2_base| (BI::zero(), BI::from(2).pow(log2_base) - BI::one()))
356        .collect()
357}
358
359// The limbs of emulated zero.
360fn limbs_of_zero<F, K, P>() -> Vec<BI>
361where
362    F: CircuitField,
363    K: CircuitField,
364    P: FieldEmulationParams<F, K>,
365{
366    bi_to_limbs(
367        P::NB_LIMBS,
368        &BI::from(2).pow(P::LOG2_BASE),
369        &(K::modulus().to_bigint().unwrap() - BI::one()),
370    )
371}
372
373impl<F, K, P, N> Chip<F> for FieldChip<F, K, P, N>
374where
375    F: CircuitField,
376    K: CircuitField,
377    P: FieldEmulationParams<F, K>,
378    N: NativeInstructions<F>,
379{
380    type Config = FieldChipConfig;
381    type Loaded = ();
382    fn config(&self) -> &Self::Config {
383        &self.config
384    }
385    fn loaded(&self) -> &Self::Loaded {
386        &()
387    }
388}
389
390impl<F, K, P, N> AssignmentInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
391where
392    F: CircuitField,
393    K: CircuitField,
394    P: FieldEmulationParams<F, K>,
395    N: NativeInstructions<F>,
396{
397    fn assign(
398        &self,
399        layouter: &mut impl Layouter<F>,
400        x: Value<K>,
401    ) -> Result<AssignedField<F, K, P>, Error> {
402        let base = BI::from(2).pow(P::LOG2_BASE);
403        // We shift the value of x by 1, remember that limbs {xi}_i represent integer
404        //   1 + sum_i base^i xi
405        let x = x.map(|v| {
406            let bi = (v - K::ONE).to_biguint().into();
407            bi_to_limbs(P::NB_LIMBS, &base, &bi)
408        });
409
410        // Range-check the cells in the range [0, base)
411        let x_cells = (0..P::NB_LIMBS)
412            .map(|i| x.clone().map(|limbs| bigint_to_fe::<F>(&limbs[i as usize])))
413            .zip(well_formed_log2_bounds::<F, K, P>().iter())
414            .map(|(xi_value, log2_bound)| {
415                self.native_gadget.assign_lower_than_fixed(
416                    layouter,
417                    xi_value,
418                    &(BigUint::one() << *log2_bound),
419                )
420            })
421            .collect::<Result<Vec<_>, Error>>()?;
422
423        Ok(AssignedField::<F, K, P> {
424            limb_values: x_cells,
425            limb_bounds: well_formed_bounds::<F, K, P>(),
426            _marker: PhantomData,
427        })
428    }
429
430    fn assign_fixed(
431        &self,
432        layouter: &mut impl Layouter<F>,
433        constant: K,
434    ) -> Result<AssignedField<F, K, P>, Error> {
435        let base = BI::from(2).pow(P::LOG2_BASE);
436        // We shift the value of x by 1, remember that limbs {xi}_i represent integer
437        //   1 + sum_i base^i xi
438        let constant = (constant - K::ONE).to_biguint().into();
439        let constant_limbs = bi_to_limbs(P::NB_LIMBS, &base, &constant);
440        let constant_cells = constant_limbs
441            .iter()
442            .map(|x| self.native_gadget.assign_fixed(layouter, bigint_to_fe::<F>(x)))
443            .collect::<Result<Vec<_>, _>>()?;
444
445        // All limbs will be in the range [0, base) by construction, no range-checks are
446        // needed.
447        // WARNING: We use "loose" bounds (`well_formed_bounds::<F, K, P>()`) here even
448        // if we know for certain that the cells contain a constant. This is to
449        // avoid a potential completeness issue when calling `assert_equal`.
450        // (Using tight bounds could result in an unsatisfiable equal assertion between
451        // two equal field elements: one in canonical form and the other one in the
452        // non-canonical (but well-formed) form, making the assertion fail when it
453        // should not.)
454        Ok(AssignedField::<F, K, P> {
455            limb_values: constant_cells,
456            limb_bounds: well_formed_bounds::<F, K, P>(),
457            _marker: PhantomData,
458        })
459    }
460}
461
462// This conversion should be treated as opaque, it is useful for dealing with
463// public inputs.
464impl<F, K, P> From<AssignedField<F, K, P>> for Vec<AssignedNative<F>>
465where
466    F: CircuitField,
467    K: CircuitField,
468    P: FieldEmulationParams<F, K>,
469{
470    fn from(x: AssignedField<F, K, P>) -> Self {
471        x.limb_values()
472    }
473}
474
475impl<F, K, P, N> PublicInputInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
476where
477    F: CircuitField,
478    K: CircuitField,
479    P: FieldEmulationParams<F, K>,
480    N: NativeInstructions<F>,
481{
482    fn as_public_input(
483        &self,
484        layouter: &mut impl Layouter<F>,
485        assigned: &AssignedField<F, K, P>,
486    ) -> Result<Vec<AssignedNative<F>>, Error> {
487        let assigned = self.normalize(layouter, assigned)?;
488        let nb_limbs_per_batch = (F::CAPACITY / P::LOG2_BASE) as usize;
489        let base = BI::from(2).pow(P::LOG2_BASE);
490        assigned
491            .limb_values
492            .chunks(nb_limbs_per_batch)
493            .map(|chunk| {
494                let terms: Vec<(F, AssignedNative<F>)> = chunk
495                    .iter()
496                    .enumerate()
497                    .map(|(i, limb)| (bigint_to_fe::<F>(&base.pow(i as u32)), limb.clone()))
498                    .collect();
499                self.native_gadget.linear_combination(layouter, &terms, F::ZERO)
500            })
501            .collect()
502    }
503
504    fn constrain_as_public_input(
505        &self,
506        layouter: &mut impl Layouter<F>,
507        assigned: &AssignedField<F, K, P>,
508    ) -> Result<(), Error> {
509        self.as_public_input(layouter, assigned)?
510            .iter()
511            .try_for_each(|c| self.native_gadget.constrain_as_public_input(layouter, c))
512    }
513
514    fn assign_as_public_input(
515        &self,
516        layouter: &mut impl Layouter<F>,
517        value: Value<K>,
518    ) -> Result<AssignedField<F, K, P>, Error> {
519        // Do NOT optimize this implementation. Since we batch various limbs when
520        // constraing as public inputs, we cannot skip the range-checks on the limbs.
521        let assigned = self.assign(layouter, value)?;
522        self.constrain_as_public_input(layouter, &assigned)?;
523        Ok(assigned)
524    }
525}
526
527impl<F, K, P, N> AssertionInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
528where
529    F: CircuitField,
530    K: CircuitField,
531    P: FieldEmulationParams<F, K>,
532    N: NativeInstructions<F>,
533{
534    fn assert_equal(
535        &self,
536        layouter: &mut impl Layouter<F>,
537        x: &AssignedField<F, K, P>,
538        y: &AssignedField<F, K, P>,
539    ) -> Result<(), Error> {
540        // We normalize the x and y before comparing them.
541        // Even though field elements may admit several representations in
542        // well-formed limbs form, an honest prover will use the canonical one,
543        // which allows them to always pass the following equality assertion if the two
544        // emulated field elements are indeed equal.
545        let x = self.normalize(layouter, x)?;
546        let y = self.normalize(layouter, y)?;
547        x.limb_values
548            .iter()
549            .zip(y.limb_values.iter())
550            .map(|(xi, yi)| self.native_gadget.assert_equal(layouter, xi, yi))
551            .collect::<Result<Vec<_>, _>>()?;
552        Ok(())
553    }
554
555    fn assert_not_equal(
556        &self,
557        layouter: &mut impl Layouter<F>,
558        x: &AssignedField<F, K, P>,
559        y: &AssignedField<F, K, P>,
560    ) -> Result<(), Error> {
561        let diff = self.sub(layouter, x, y)?;
562        self.assert_non_zero(layouter, &diff)
563    }
564
565    fn assert_equal_to_fixed(
566        &self,
567        layouter: &mut impl Layouter<F>,
568        x: &AssignedField<F, K, P>,
569        constant: K,
570    ) -> Result<(), Error> {
571        // We normalize x before comparing it to the constant.
572        // Even though field elements may admit several representations in
573        // well-formed limbs form, an honest prover will use the canonical one,
574        // which allows them to always pass the following equality assertion if the x
575        // is indeed equal to the given constant.
576        let x = self.normalize(layouter, x)?;
577        let constant_limbs = {
578            let constant = (constant - K::ONE).to_biguint().into();
579            let base = BI::from(2).pow(P::LOG2_BASE);
580            bi_to_limbs(P::NB_LIMBS, &base, &constant)
581        };
582        x.limb_values
583            .iter()
584            .zip(constant_limbs.iter())
585            .map(|(xi, ki)| {
586                self.native_gadget.assert_equal_to_fixed(layouter, xi, bigint_to_fe::<F>(ki))
587            })
588            .collect::<Result<Vec<_>, _>>()?;
589        Ok(())
590    }
591
592    fn assert_not_equal_to_fixed(
593        &self,
594        layouter: &mut impl Layouter<F>,
595        x: &AssignedField<F, K, P>,
596        constant: K,
597    ) -> Result<(), Error> {
598        let diff = self.add_constant(layouter, x, -constant)?;
599        self.assert_non_zero(layouter, &diff)
600    }
601}
602
603impl<F, K, P, N> EqualityInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
604where
605    F: CircuitField,
606    K: CircuitField,
607    P: FieldEmulationParams<F, K>,
608    N: NativeInstructions<F>,
609{
610    fn is_equal(
611        &self,
612        layouter: &mut impl Layouter<F>,
613        x: &AssignedField<F, K, P>,
614        y: &AssignedField<F, K, P>,
615    ) -> Result<AssignedBit<F>, Error> {
616        let diff = self.sub(layouter, x, y)?;
617        self.is_zero(layouter, &diff)
618    }
619
620    fn is_not_equal(
621        &self,
622        layouter: &mut impl Layouter<F>,
623        x: &AssignedField<F, K, P>,
624        y: &AssignedField<F, K, P>,
625    ) -> Result<AssignedBit<F>, Error> {
626        let b = self.is_equal(layouter, x, y)?;
627        self.native_gadget.not(layouter, &b)
628    }
629
630    fn is_equal_to_fixed(
631        &self,
632        layouter: &mut impl Layouter<F>,
633        x: &AssignedField<F, K, P>,
634        constant: <AssignedField<F, K, P> as InnerValue>::Element,
635    ) -> Result<AssignedBit<F>, Error> {
636        let diff = self.add_constant(layouter, x, -constant)?;
637        self.is_zero(layouter, &diff)
638    }
639
640    fn is_not_equal_to_fixed(
641        &self,
642        layouter: &mut impl Layouter<F>,
643        x: &AssignedField<F, K, P>,
644        constant: <AssignedField<F, K, P> as InnerValue>::Element,
645    ) -> Result<AssignedBit<F>, Error> {
646        let b = self.is_equal_to_fixed(layouter, x, constant)?;
647        self.native_gadget.not(layouter, &b)
648    }
649}
650
651impl<F, K, P, N> ZeroInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
652where
653    F: CircuitField,
654    K: CircuitField,
655    P: FieldEmulationParams<F, K>,
656    N: NativeInstructions<F>,
657{
658    fn assert_non_zero(
659        &self,
660        layouter: &mut impl Layouter<F>,
661        x: &AssignedField<F, K, P>,
662    ) -> Result<(), Error> {
663        let b = self.is_zero(layouter, x)?;
664        self.native_gadget.assert_equal_to_fixed(layouter, &b, false)
665    }
666
667    fn is_zero(
668        &self,
669        layouter: &mut impl Layouter<F>,
670        x: &AssignedField<F, K, P>,
671    ) -> Result<AssignedBit<F>, Error> {
672        // Zero has a unique representation in limbs form, we can simply make sure that
673        // the limbs of x are all equal to the limbs of zero.
674        let x = self.normalize(layouter, x)?;
675        let bs = x
676            .limb_values
677            .iter()
678            .zip(limbs_of_zero::<F, K, P>().iter())
679            .map(|(xi, ci)| {
680                self.native_gadget.is_equal_to_fixed(layouter, xi, bigint_to_fe::<F>(ci))
681            })
682            .collect::<Result<Vec<_>, _>>()?;
683        self.native_gadget.and(layouter, &bs)
684    }
685}
686
687impl<F, K, P, N> ControlFlowInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
688where
689    F: CircuitField,
690    K: CircuitField,
691    P: FieldEmulationParams<F, K>,
692    N: NativeInstructions<F>,
693{
694    fn select(
695        &self,
696        layouter: &mut impl Layouter<F>,
697        cond: &AssignedBit<F>,
698        x: &AssignedField<F, K, P>,
699        y: &AssignedField<F, K, P>,
700    ) -> Result<AssignedField<F, K, P>, Error> {
701        let z_limb_values = x
702            .limb_values
703            .iter()
704            .zip(y.limb_values.iter())
705            .map(|(xi, yi)| self.native_gadget.select(layouter, cond, xi, yi))
706            .collect::<Result<Vec<_>, _>>()?;
707        let z_limb_bounds = x
708            .limb_bounds
709            .iter()
710            .zip(y.limb_bounds.iter())
711            .map(|(xi_bounds, yi_bounds)| {
712                (
713                    min(&xi_bounds.0, &yi_bounds.0).clone(),
714                    max(&xi_bounds.1, &yi_bounds.1).clone(),
715                )
716            })
717            .collect::<Vec<_>>();
718        Ok(AssignedField::<F, K, P> {
719            limb_values: z_limb_values,
720            limb_bounds: z_limb_bounds,
721            _marker: PhantomData,
722        })
723    }
724}
725
726impl<F, K, P, N> ArithInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
727where
728    F: CircuitField,
729    K: CircuitField,
730    P: FieldEmulationParams<F, K>,
731    N: NativeInstructions<F>,
732{
733    fn linear_combination(
734        &self,
735        layouter: &mut impl Layouter<F>,
736        terms: &[(K, AssignedField<F, K, P>)],
737        constant: K,
738    ) -> Result<AssignedField<F, K, P>, Error> {
739        if terms.is_empty() {
740            return self.assign_fixed(layouter, constant);
741        }
742
743        // Fast path: when all coefficients are +1 or -1, delegate to sum.
744        if terms.iter().all(|(c, _)| *c == K::ONE || *c == -K::ONE) {
745            let (pos, neg): (Vec<_>, Vec<_>) = terms.iter().partition(|(c, _)| *c == K::ONE);
746            let pos: Vec<_> = pos.into_iter().map(|(_, x)| x.clone()).collect();
747            let neg: Vec<_> = neg.into_iter().map(|(_, x)| x.clone()).collect();
748            return self.sum(layouter, &pos, &neg, constant);
749        }
750
751        // General path: fold over mul_by_constant and add.
752        let init: AssignedField<F, K, P> = self.assign_fixed(layouter, constant)?;
753        let res = terms.iter().try_fold(init, |acc, (c, x)| {
754            let prod = self.mul_by_constant(layouter, x, *c)?;
755            self.add(layouter, &acc, &prod)
756        })?;
757        self.normalize_if_approaching_limit(layouter, &res)
758    }
759
760    fn add(
761        &self,
762        layouter: &mut impl Layouter<F>,
763        x: &AssignedField<F, K, P>,
764        y: &AssignedField<F, K, P>,
765    ) -> Result<AssignedField<F, K, P>, Error> {
766        let zero: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ZERO)?;
767
768        if x == &zero {
769            return Ok(y.clone());
770        }
771
772        if y == &zero {
773            return Ok(x.clone());
774        }
775
776        // Note that x := 1 + sum_i base^i xi and y := 1 + sum_i base^i yi.
777        // Thus z = (x + y) is equal to 2 + sum_i base^i (xi + yi).
778        // Observe there is a 2 instead of the implicit 1, thus we cannot simply add the
779        // limbs of x and y pair-wise. We also need to add a factor of +1 to the
780        // least-significant limb to account for this difference.
781
782        let mut constants = vec![BI::one()];
783        constants.resize(P::NB_LIMBS as usize, BI::zero());
784
785        let z_limb_values = x
786            .limb_values
787            .iter()
788            .zip(y.limb_values.iter())
789            .zip(constants.iter().map(|ci| bigint_to_fe::<F>(ci)))
790            .map(|((xi, yi), ci)| {
791                self.native_gadget.linear_combination(
792                    layouter,
793                    &[(F::ONE, xi.clone()), (F::ONE, yi.clone())],
794                    ci,
795                )
796            })
797            .collect::<Result<Vec<_>, _>>()?;
798
799        let z = AssignedField::<F, K, P> {
800            limb_values: z_limb_values,
801            limb_bounds: x
802                .limb_bounds
803                .iter()
804                .zip(y.limb_bounds.iter())
805                .zip(constants.iter())
806                .map(|((xi_bounds, yi_bounds), ci)| {
807                    (
808                        &xi_bounds.0 + &yi_bounds.0 + ci,
809                        &xi_bounds.1 + &yi_bounds.1 + ci,
810                    )
811                })
812                .collect(),
813            _marker: PhantomData,
814        };
815        self.normalize_if_approaching_limit(layouter, &z)
816    }
817
818    fn sub(
819        &self,
820        layouter: &mut impl Layouter<F>,
821        x: &AssignedField<F, K, P>,
822        y: &AssignedField<F, K, P>,
823    ) -> Result<AssignedField<F, K, P>, Error> {
824        let zero: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ZERO)?;
825
826        if y == &zero {
827            return Ok(x.clone());
828        }
829
830        // Note that x := 1 + sum_i base^i xi and y := 1 + sum_i base^i yi.
831        // Thus z = (x - y) is equal to 0 + sum_i base^i (xi + yi).
832        // Observe there is a 0 instead of the implicit 1, thus we cannot simply
833        // subtract the limbs of x and y pair-wise. We also need to add a factor
834        // of -1 to the least-significant limb to account for this difference.
835
836        let mut constants = vec![BI::from(-1)];
837        constants.resize(P::NB_LIMBS as usize, BI::zero());
838
839        let z_limb_values = x
840            .limb_values
841            .iter()
842            .zip(y.limb_values.iter())
843            .zip(constants.iter().map(|ci| bigint_to_fe::<F>(ci)))
844            .map(|((xi, yi), ci)| {
845                self.native_gadget.linear_combination(
846                    layouter,
847                    &[(F::ONE, xi.clone()), (-F::ONE, yi.clone())],
848                    ci,
849                )
850            })
851            .collect::<Result<Vec<_>, _>>()?;
852
853        let z = AssignedField::<F, K, P> {
854            limb_values: z_limb_values,
855            limb_bounds: x
856                .limb_bounds
857                .iter()
858                .zip(y.limb_bounds.iter())
859                .zip(constants.iter())
860                .map(|((xi_bounds, yi_bounds), ci)| {
861                    (
862                        &xi_bounds.0 - &yi_bounds.1 + ci,
863                        &xi_bounds.1 - &yi_bounds.0 + ci,
864                    )
865                })
866                .collect(),
867            _marker: PhantomData,
868        };
869        self.normalize_if_approaching_limit(layouter, &z)
870    }
871
872    fn mul(
873        &self,
874        layouter: &mut impl Layouter<F>,
875        x: &AssignedField<F, K, P>,
876        y: &AssignedField<F, K, P>,
877        multiplying_constant: Option<K>,
878    ) -> Result<AssignedField<F, K, P>, Error> {
879        let zero: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ZERO)?;
880        let one: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ONE)?;
881
882        if x == &zero || y == &zero {
883            return Ok(zero);
884        }
885
886        if x == &one {
887            return Ok(y.clone());
888        }
889
890        if y == &one {
891            return Ok(x.clone());
892        }
893
894        let y = match multiplying_constant {
895            None => y.clone(),
896            Some(k) => self.mul_by_constant(layouter, y, k)?,
897        };
898        self.assign_mul(layouter, x, &y, false)
899    }
900
901    fn div(
902        &self,
903        layouter: &mut impl Layouter<F>,
904        x: &AssignedField<F, K, P>,
905        y: &AssignedField<F, K, P>,
906    ) -> Result<AssignedField<F, K, P>, Error> {
907        let one: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ONE)?;
908        if y == &one {
909            return Ok(x.clone());
910        }
911
912        let y = self.normalize(layouter, y)?;
913        self.assert_non_zero(layouter, &y)?;
914        self.assign_mul(layouter, x, &y, true)
915    }
916
917    fn neg(
918        &self,
919        layouter: &mut impl Layouter<F>,
920        x: &AssignedField<F, K, P>,
921    ) -> Result<AssignedField<F, K, P>, Error> {
922        let zero: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ZERO)?;
923
924        if x == &zero {
925            return Ok(zero);
926        }
927
928        // Note that x := 1 + sum_i base^i xi.
929        // Thus z = -x is equal to -1 + sum_i base^i (xi + yi).
930        // Observe there is a -1 instead of the implicit 1, thus we cannot simply negate
931        // the limbs of x. We also need to add a factor of -2 to the least-significant
932        // limb to account for this difference.
933
934        let mut constants = vec![BI::from(-2)];
935        constants.resize(P::NB_LIMBS as usize, BI::zero());
936
937        let z_limb_values = x
938            .limb_values
939            .iter()
940            .zip(constants.iter().map(|ci| bigint_to_fe::<F>(ci)))
941            .map(|(xi, ci)| {
942                self.native_gadget.linear_combination(layouter, &[(-F::ONE, xi.clone())], ci)
943            })
944            .collect::<Result<Vec<_>, _>>()?;
945
946        let z = AssignedField::<F, K, P> {
947            limb_values: z_limb_values,
948            limb_bounds: x
949                .limb_bounds
950                .iter()
951                .zip(constants.iter())
952                .map(|(xi_bounds, ci)| (-&xi_bounds.1 + ci, -&xi_bounds.0 + ci))
953                .collect(),
954            _marker: PhantomData,
955        };
956        self.normalize_if_approaching_limit(layouter, &z)
957    }
958
959    fn inv(
960        &self,
961        layouter: &mut impl Layouter<F>,
962        x: &AssignedField<F, K, P>,
963    ) -> Result<AssignedField<F, K, P>, Error> {
964        let one: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ONE)?;
965
966        if x == &one {
967            return Ok(one);
968        }
969
970        // We do not need to assert that x != 0 because the equation enforced by
971        // [assign_mul] will be 1 = z * x, which is unsatisfiable if x = 0.
972        self.assign_mul(layouter, &one, x, true)
973    }
974
975    fn inv0(
976        &self,
977        layouter: &mut impl Layouter<F>,
978        x: &AssignedField<F, K, P>,
979    ) -> Result<AssignedField<F, K, P>, Error> {
980        let is_zero = self.is_zero(layouter, x)?;
981        let zero = self.assign_fixed(layouter, K::ZERO)?;
982        let one = self.assign_fixed(layouter, K::ONE)?;
983        let invertible = self.select(layouter, &is_zero, &one, x)?;
984        let inverse = self.assign_mul(layouter, &one, &invertible, true)?;
985        self.select(layouter, &is_zero, &zero, &inverse)
986    }
987
988    fn add_constant(
989        &self,
990        layouter: &mut impl Layouter<F>,
991        x: &AssignedField<F, K, P>,
992        k: K,
993    ) -> Result<AssignedField<F, K, P>, Error> {
994        // The following is more efficient than simply:
995        //
996        //   let constant = self.assign_fixed(layouter, k)?;
997        //   self.add(layouter, x, &assign_fixed)
998        //
999        // as we do not create cells for the constant limbs.
1000
1001        if k.is_zero().into() {
1002            return Ok(x.clone());
1003        }
1004
1005        let base = BI::from(2).pow(P::LOG2_BASE);
1006        let k_limbs = bi_to_limbs(P::NB_LIMBS, &base, &k.to_biguint().into());
1007
1008        let z_limb_values = {
1009            self.native_gadget.add_constants(
1010                layouter,
1011                &x.limb_values,
1012                &k_limbs.iter().map(bigint_to_fe::<F>).collect::<Vec<_>>(),
1013            )?
1014        };
1015
1016        let z = AssignedField::<F, K, P> {
1017            limb_values: z_limb_values,
1018            limb_bounds: x
1019                .limb_bounds
1020                .iter()
1021                .zip(k_limbs.iter())
1022                .map(|(xi_bound, ki)| (&xi_bound.0 + ki, &xi_bound.1 + ki))
1023                .collect(),
1024            _marker: PhantomData,
1025        };
1026        self.normalize_if_approaching_limit(layouter, &z)
1027    }
1028
1029    fn mul_by_constant(
1030        &self,
1031        layouter: &mut impl Layouter<F>,
1032        x: &AssignedField<F, K, P>,
1033        k: K,
1034    ) -> Result<AssignedField<F, K, P>, Error> {
1035        if k.is_zero().into() {
1036            return self.assign_fixed(layouter, K::ZERO);
1037        }
1038
1039        if k == K::ONE {
1040            return Ok(x.clone());
1041        }
1042
1043        if k == -K::ONE {
1044            return self.neg(layouter, x);
1045        }
1046
1047        // If the constant is too big, we should multiply normally instead.
1048        // This threshold is just a heuristic, it will allow us to perform about 1000
1049        // sums after this multiplication without normalization.
1050        // We will get an error when compiling the circuit if the max_limb_bound is
1051        // violated, so the choice of this threshold is not critical for soundness.
1052        let threshold =
1053            P::max_limb_bound().div_floor(&(BI::from(1000) * BI::from(2).pow(P::LOG2_BASE)));
1054        if BI::from(k.to_biguint()) > threshold {
1055            let assigned_k = self.assign_fixed(layouter, k)?;
1056            return self.assign_mul(layouter, x, &assigned_k, false);
1057        }
1058
1059        // At this point we know that k is small enough (k <= threshold) thus we can
1060        // proceed by multiplying the constant by every limb.
1061
1062        // Note that x := 1 + sum_i base^i xi.
1063        // Thus z = k * x is equal to k + sum_i base^i (k * xi).
1064        // Observe there is a k instead of the implicit 1, thus we cannot simply
1065        // multiply the limbs of x by k. We also need to add a factor of (k-1)
1066        // to the least-significant limb to account for this difference.
1067
1068        // Yes, we convert it to F (the wrong - but native - field), but this is fine
1069        // because the constant has been verified to be small.
1070        let k_as_bigint: BI = k.to_biguint().into();
1071        let kv = bigint_to_fe::<F>(&k_as_bigint);
1072        // We've also checked k != 0 and k != 1, so it is fine to subtract one here.
1073        // (for the unique-zero representation).
1074        let mut constants = vec![k_as_bigint.clone() - BI::one()];
1075        constants.resize(P::NB_LIMBS as usize, BI::zero());
1076
1077        let z_limb_values = x
1078            .limb_values
1079            .iter()
1080            .zip(constants.iter().map(|ci| bigint_to_fe::<F>(ci)))
1081            .map(|(xi, ci)| {
1082                self.native_gadget.linear_combination(layouter, &[(kv, xi.clone())], ci)
1083            })
1084            .collect::<Result<Vec<_>, _>>()?;
1085
1086        let limb_bounds = x
1087            .limb_bounds
1088            .iter()
1089            .zip(constants.iter())
1090            .map(|(xi_bounds, ci)| {
1091                (
1092                    &xi_bounds.0 * k_as_bigint.clone() + ci,
1093                    &xi_bounds.1 * k_as_bigint.clone() + ci,
1094                )
1095            })
1096            .collect();
1097
1098        let z = AssignedField::<F, K, P> {
1099            limb_values: z_limb_values,
1100            limb_bounds,
1101            _marker: PhantomData,
1102        };
1103        self.normalize_if_approaching_limit(layouter, &z)
1104    }
1105}
1106
1107impl<F, K, P, N> FieldInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
1108where
1109    F: CircuitField,
1110    K: CircuitField,
1111    P: FieldEmulationParams<F, K>,
1112    N: NativeInstructions<F>,
1113{
1114    fn order(&self) -> BigUint {
1115        K::modulus()
1116    }
1117}
1118
1119impl<F, K, P, N> ScalarFieldInstructions<F> for FieldChip<F, K, P, N>
1120where
1121    F: CircuitField,
1122    K: CircuitField,
1123    P: FieldEmulationParams<F, K>,
1124    N: NativeInstructions<F>,
1125{
1126    type Scalar = AssignedField<F, K, P>;
1127}
1128
1129impl<F, K, P, N> DecompositionInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
1130where
1131    F: CircuitField,
1132    K: CircuitField,
1133    P: FieldEmulationParams<F, K>,
1134    N: NativeInstructions<F>,
1135{
1136    fn assigned_to_le_bits(
1137        &self,
1138        layouter: &mut impl Layouter<F>,
1139        x: &AssignedField<F, K, P>,
1140        nb_bits: Option<usize>,
1141        enforce_canonical: bool,
1142    ) -> Result<Vec<AssignedBit<F>>, Error> {
1143        // Add one to account for the extra +1 in the unique-zero representation.
1144        let mut x = self.add_constant(layouter, x, K::ONE)?;
1145        if enforce_canonical {
1146            x = self.make_canonical(layouter, &x)?;
1147        };
1148        let mut bits = vec![];
1149        x.limb_values
1150            .iter()
1151            .zip(well_formed_log2_bounds::<F, K, P>().iter())
1152            .map(|(cell, log2_bound)| {
1153                self.native_gadget.assigned_to_le_bits(
1154                    layouter,
1155                    cell,
1156                    Some(*log2_bound as usize),
1157                    true,
1158                )
1159            })
1160            .collect::<Result<Vec<_>, _>>()?
1161            .into_iter()
1162            .for_each(|new_bits| bits.extend(new_bits));
1163
1164        let nb_bits = min(
1165            K::NUM_BITS as usize,
1166            nb_bits.unwrap_or(K::NUM_BITS as usize),
1167        );
1168
1169        // Drop the most significant bits up to the desired length, but make sure
1170        // they encode 0.
1171        bits[nb_bits..]
1172            .iter()
1173            .try_for_each(|byte| self.native_gadget.assert_equal_to_fixed(layouter, byte, false))?;
1174        bits = bits[0..nb_bits].to_vec();
1175
1176        // The case nb_bits > K::NUM_BITS cannot happen since by above definition
1177        // nb_bits = min(K::NUM_BITS,...), and thus nb_bits <= K::NUM_BITS.
1178        if enforce_canonical && nb_bits == K::NUM_BITS as usize {
1179            let canonical = self.is_canonical(layouter, &bits)?;
1180            self.assert_equal_to_fixed(layouter, &canonical, true)?;
1181        }
1182        Ok(bits)
1183    }
1184
1185    fn assigned_to_le_bytes(
1186        &self,
1187        layouter: &mut impl Layouter<F>,
1188        x: &AssignedField<F, K, P>,
1189        nb_bytes: Option<usize>,
1190    ) -> Result<Vec<AssignedByte<F>>, Error> {
1191        let nb_bytes = nb_bytes.unwrap_or(K::NUM_BITS.div_ceil(8) as usize);
1192        // The following could be further optimzed when 8 divides LOG2_BASE.
1193        let bits = self.assigned_to_le_bits(layouter, x, Some(nb_bytes * 8), true)?;
1194        let bytes = bits
1195            .chunks(8)
1196            .map(|chunk| {
1197                let terms = chunk
1198                    .iter()
1199                    .enumerate()
1200                    .map(|(i, bit)| (F::from(1 << i), bit.clone().into()))
1201                    .collect::<Vec<_>>();
1202                let byte = self.native_gadget.linear_combination(layouter, &terms, F::ZERO)?;
1203                self.native_gadget.convert_unsafe(layouter, &byte)
1204            })
1205            .collect::<Result<Vec<AssignedByte<F>>, Error>>()?;
1206
1207        // Drop the most significant bytes up to the desired length, but make sure
1208        // they encode 0.
1209        bytes[nb_bytes..]
1210            .iter()
1211            .try_for_each(|byte| self.native_gadget.assert_equal_to_fixed(layouter, byte, 0u8))?;
1212        Ok(bytes[0..nb_bytes].to_vec())
1213    }
1214
1215    fn assigned_from_le_bits(
1216        &self,
1217        layouter: &mut impl Layouter<F>,
1218        bits: &[AssignedBit<F>],
1219    ) -> Result<AssignedField<F, K, P>, Error> {
1220        let mut coeff = K::ONE;
1221        let mut terms = vec![];
1222        for chunk in bits.chunks(P::LOG2_BASE as usize) {
1223            let mut native_coeff = F::ONE;
1224            let mut native_terms = vec![];
1225            for b in chunk.iter() {
1226                let bit: AssignedNative<F> = b.clone().into();
1227                native_terms.push((native_coeff, bit));
1228                native_coeff = native_coeff + native_coeff;
1229            }
1230            let term = {
1231                let limb =
1232                    self.native_gadget.linear_combination(layouter, &native_terms, F::ZERO)?;
1233                self.assigned_field_from_limb(layouter, &limb)?
1234            };
1235            terms.push((coeff, term));
1236            coeff = bigint_to_fe::<K>(&BI::from(2).pow(P::LOG2_BASE)) * coeff;
1237        }
1238        let x = self.linear_combination(layouter, &terms, K::ZERO)?;
1239        self.normalize(layouter, &x)
1240    }
1241
1242    fn assigned_from_le_bytes(
1243        &self,
1244        layouter: &mut impl Layouter<F>,
1245        bytes: &[AssignedByte<F>],
1246    ) -> Result<AssignedField<F, K, P>, Error> {
1247        let mut coeff = K::ONE;
1248        let mut terms = vec![];
1249        let nb_bytes_per_chunk = P::LOG2_BASE / 8;
1250        for chunk in bytes.chunks(nb_bytes_per_chunk as usize) {
1251            let mut native_coeff = F::ONE;
1252            let mut native_terms = vec![];
1253            for b in chunk.iter() {
1254                let byte: AssignedNative<F> = b.clone().into();
1255                native_terms.push((native_coeff, byte));
1256                native_coeff = F::from(256) * native_coeff;
1257            }
1258            let term = {
1259                let limb =
1260                    self.native_gadget.linear_combination(layouter, &native_terms, F::ZERO)?;
1261                self.assigned_field_from_limb(layouter, &limb)?
1262            };
1263            terms.push((coeff, term));
1264            coeff = bigint_to_fe::<K>(&BI::from(2).pow(8 * nb_bytes_per_chunk)) * coeff;
1265        }
1266        let x = self.linear_combination(layouter, &terms, K::ZERO)?;
1267        self.normalize(layouter, &x)
1268    }
1269
1270    fn assigned_to_le_chunks(
1271        &self,
1272        layouter: &mut impl Layouter<F>,
1273        x: &AssignedField<F, K, P>,
1274        nb_bits_per_chunk: usize,
1275        nb_chunks: Option<usize>,
1276    ) -> Result<Vec<AssignedNative<F>>, Error> {
1277        assert!(nb_bits_per_chunk < F::NUM_BITS as usize);
1278        if P::LOG2_BASE % (nb_bits_per_chunk as u32) == 0 {
1279            let nb_chunks_per_limb = (P::LOG2_BASE / (nb_bits_per_chunk as u32)) as usize;
1280            let mut nb_missing_chunks =
1281                nb_chunks.unwrap_or(nb_chunks_per_limb * P::NB_LIMBS as usize);
1282            // Add one to account for the extra +1 in the unique-zero representation.
1283            let x = self.add_constant(layouter, x, K::ONE)?;
1284            let x = self.normalize(layouter, &x)?;
1285            let chunks = x
1286                .limb_values
1287                .iter()
1288                .map(|limb| {
1289                    let nb_chunks_on_this_limb = min(nb_missing_chunks, nb_chunks_per_limb);
1290                    nb_missing_chunks -= nb_chunks_on_this_limb;
1291                    self.native_gadget.assigned_to_le_chunks(
1292                        layouter,
1293                        limb,
1294                        nb_bits_per_chunk,
1295                        Some(nb_chunks_on_this_limb),
1296                    )
1297                })
1298                .collect::<Result<Vec<_>, Error>>()?
1299                .concat();
1300            assert_eq!(nb_missing_chunks, 0);
1301            Ok(chunks)
1302        }
1303        // When nb_bits_per_chunk does not divide P::LOG2_BASE we cannot proceed as above,
1304        // let's split in bits and then aggregate chunks, this is a bit less efficient.
1305        else {
1306            let bits = self.assigned_to_le_bits(layouter, x, None, false)?;
1307            bits.chunks(nb_bits_per_chunk)
1308                .map(|bits_of_chunk| {
1309                    self.native_gadget.assigned_from_le_bits(layouter, bits_of_chunk)
1310                })
1311                .collect::<Result<Vec<_>, Error>>()
1312        }
1313    }
1314}
1315
1316impl<F, K, P, N> FieldChip<F, K, P, N>
1317where
1318    F: CircuitField,
1319    K: CircuitField,
1320    P: FieldEmulationParams<F, K>,
1321    N: NativeInstructions<F>,
1322{
1323    /// Given config creates new emulated field chip.
1324    pub fn new(config: &FieldChipConfig, native_gadget: &N) -> Self {
1325        Self {
1326            config: config.clone(),
1327            native_gadget: native_gadget.clone(),
1328            _marker: PhantomData,
1329        }
1330    }
1331
1332    /// Configures the emulated field chip.
1333    /// `advice_columns` should contain at least as many columns as this chip
1334    /// requires, namely `nb_field_chip_columns::<P>()`.
1335    pub fn configure(
1336        meta: &mut ConstraintSystem<F>,
1337        advice_columns: &[Column<Advice>],
1338        nb_parallel_range_checks: usize,
1339        max_bit_len: u32,
1340    ) -> FieldChipConfig {
1341        check_params::<F, K, P>();
1342
1343        let nb_limbs = P::NB_LIMBS;
1344        let x_cols = advice_columns[..(nb_limbs as usize)].to_vec();
1345        let y_cols = x_cols.clone();
1346        let z_cols = advice_columns[(nb_limbs as usize)..(2 * nb_limbs as usize)].to_vec();
1347
1348        x_cols.iter().chain(z_cols.iter()).for_each(|&col| meta.enable_equality(col));
1349
1350        let u_col = advice_columns[nb_limbs as usize];
1351        let v_cols = advice_columns
1352            [(nb_limbs as usize + 1)..(nb_limbs as usize + 1 + P::moduli().len())]
1353            .to_vec();
1354
1355        let mul_config = MulConfig::configure::<F, K, P>(
1356            meta,
1357            &x_cols,
1358            &z_cols,
1359            nb_parallel_range_checks,
1360            max_bit_len,
1361        );
1362        let norm_config = NormConfig::configure::<F, K, P>(
1363            meta,
1364            &x_cols,
1365            &z_cols,
1366            nb_parallel_range_checks,
1367            max_bit_len,
1368        );
1369
1370        FieldChipConfig {
1371            mul_config,
1372            norm_config,
1373            x_cols,
1374            y_cols,
1375            z_cols,
1376            u_col,
1377            v_cols,
1378        }
1379    }
1380
1381    // Creates a new AssignedField z, asserted to be well-formed and satisfy
1382    // the emulated field relation x * y = z.
1383    // If the [division] flag is set, the relation becomes z * y = x, in order to
1384    // model the division (z = x / y).
1385    // WARNING: When used for division, we must independently assert that y != 0
1386    // (note that when y = 0, any value for z would satisfy the relation if x = 0).
1387    fn assign_mul(
1388        &self,
1389        layouter: &mut impl Layouter<F>,
1390        x: &AssignedField<F, K, P>,
1391        y: &AssignedField<F, K, P>,
1392        division: bool,
1393    ) -> Result<AssignedField<F, K, P>, Error> {
1394        let base = BI::from(2).pow(P::LOG2_BASE);
1395        let nb_limbs = P::NB_LIMBS;
1396
1397        let x = self.normalize(layouter, x)?;
1398        let y = self.normalize(layouter, y)?;
1399
1400        y.value().error_if_known_and(|yv| division && K::is_zero(yv).into())?;
1401
1402        let zv = x
1403            .value()
1404            .zip(y.value())
1405            .map(|(xv, yv)| {
1406                if division {
1407                    xv * yv.invert().unwrap()
1408                } else {
1409                    xv * yv
1410                }
1411            })
1412            .map(|z| bi_to_limbs(nb_limbs, &base, &(z - K::ONE).to_biguint().into()));
1413        let z_values = (0..nb_limbs)
1414            .map(|i| zv.clone().map(|zs| bigint_to_fe::<F>(&zs[i as usize])))
1415            .collect::<Vec<_>>();
1416
1417        // Assign and range-check the z limbs
1418        let z_limbs = z_values
1419            .iter()
1420            .zip(well_formed_log2_bounds::<F, K, P>().iter())
1421            .map(|(&z_value, &log2_bound)| {
1422                self.native_gadget.assign_lower_than_fixed(
1423                    layouter,
1424                    z_value,
1425                    &(BigUint::one() << log2_bound),
1426                )
1427            })
1428            .collect::<Result<Vec<_>, Error>>()?;
1429
1430        let z = AssignedField::<F, K, P> {
1431            limb_values: z_limbs,
1432            limb_bounds: well_formed_bounds::<F, K, P>(),
1433            _marker: PhantomData,
1434        };
1435
1436        // Divisions z = x / y are modeled as multiplications z * y = x.
1437        // We swap x and z when division = true.
1438        let (l, r) = if !division { (&x, &z) } else { (&z, &x) };
1439        mul::assert_mul::<F, K, P, N>(
1440            layouter,
1441            l,
1442            &y,
1443            r,
1444            &self.config.mul_config,
1445            &self.native_gadget,
1446        )?;
1447
1448        Ok(z)
1449    }
1450}
1451
1452impl<F, K, P, N> FieldChip<F, K, P, N>
1453where
1454    F: CircuitField,
1455    K: CircuitField,
1456    P: FieldEmulationParams<F, K>,
1457    N: NativeInstructions<F>,
1458{
1459    /// Computes `sum(positives) - sum(negatives) + constant` in the emulated
1460    /// field. Each result limb is produced in a single native
1461    /// `linear_combination` call, avoiding the per-term overhead of the
1462    /// generic `linear_combination` path.
1463    pub fn sum(
1464        &self,
1465        layouter: &mut impl Layouter<F>,
1466        positives: &[AssignedField<F, K, P>],
1467        negatives: &[AssignedField<F, K, P>],
1468        constant: K,
1469    ) -> Result<AssignedField<F, K, P>, Error> {
1470        if positives.is_empty() && negatives.is_empty() {
1471            return self.assign_fixed(layouter, constant);
1472        }
1473
1474        let base = BI::from(2).pow(P::LOG2_BASE);
1475        let p = positives.len();
1476        let n = negatives.len();
1477
1478        // In the +1 representation, each term is stored as 1 + sum_i B^i * t_i.
1479        // sum(pos) - sum(neg) + c
1480        //   = (P - N + c) + sum_i B^i * (sum_j pos_{j,i} - sum_k neg_{k,i})
1481        // In +1 form (1 + sum_i B^i * z_i):
1482        //   z_i = sum_j pos_{j,i} - sum_k neg_{k,i} + offset_i
1483        // where sum_i B^i * offset_i = P - N - 1 + c.
1484        //
1485        // Since (c - 1) has base-B limbs c_i with sum B^i c_i = c - 1,
1486        // we get P - N - 1 + c = (P - N) + (c - 1) = (P - N) + sum B^i c_i.
1487        // So offset_0 = c_0 + (P - N), offset_i = c_i for i > 0.
1488        // Note: offset_0 may be negative — bounds tracking handles this.
1489        let constant_repr: BI = (constant - K::ONE).to_biguint().into();
1490        let constant_limbs = bi_to_limbs(P::NB_LIMBS, &base, &constant_repr);
1491        let mut offsets = constant_limbs;
1492        offsets[0] += BI::from(p) - BI::from(n);
1493
1494        let z_limb_values = (0..P::NB_LIMBS as usize)
1495            .map(|i| {
1496                let pos_terms: Vec<(F, AssignedNative<F>)> =
1497                    positives.iter().map(|x| (F::ONE, x.limb_values[i].clone())).collect();
1498                let neg_terms: Vec<(F, AssignedNative<F>)> =
1499                    negatives.iter().map(|x| (-F::ONE, x.limb_values[i].clone())).collect();
1500                let native_terms: Vec<_> = pos_terms.into_iter().chain(neg_terms).collect();
1501                self.native_gadget.linear_combination(
1502                    layouter,
1503                    &native_terms,
1504                    bigint_to_fe::<F>(&offsets[i]),
1505                )
1506            })
1507            .collect::<Result<Vec<_>, _>>()?;
1508
1509        // Bounds: positive terms contribute (lo, hi), negated terms contribute
1510        // (-hi, -lo).
1511        let all_terms = positives
1512            .iter()
1513            .zip(std::iter::repeat(false))
1514            .chain(negatives.iter().zip(std::iter::repeat(true)));
1515        let init_bounds: Vec<(BI, BI)> = (0..P::NB_LIMBS as usize)
1516            .map(|i| (offsets[i].clone(), offsets[i].clone()))
1517            .collect();
1518        let z_bounds = all_terms.fold(init_bounds, |acc, (x, negated)| {
1519            acc.into_iter()
1520                .zip(x.limb_bounds.iter())
1521                .map(|((lo, hi), b)| {
1522                    if negated {
1523                        (lo - &b.1, hi - &b.0)
1524                    } else {
1525                        (lo + &b.0, hi + &b.1)
1526                    }
1527                })
1528                .collect()
1529        });
1530
1531        let z = AssignedField::<F, K, P> {
1532            limb_values: z_limb_values,
1533            limb_bounds: z_bounds,
1534            _marker: PhantomData,
1535        };
1536        self.normalize_if_approaching_limit(layouter, &z)
1537    }
1538
1539    /// Normalizes the given assigned field element, but only if its bounds
1540    /// exceed the limits of the well-formed bounds.
1541    pub(crate) fn normalize(
1542        &self,
1543        layouter: &mut impl Layouter<F>,
1544        x: &AssignedField<F, K, P>,
1545    ) -> Result<AssignedField<F, K, P>, Error> {
1546        if x.is_well_formed() {
1547            Ok(x.clone())
1548        } else {
1549            self.make_canonical(layouter, x)
1550        }
1551    }
1552
1553    /// Normalizes the given assigned field element, but only if its bounds
1554    /// are approaching the limits of the well-formed bounds.
1555    pub(crate) fn normalize_if_approaching_limit(
1556        &self,
1557        layouter: &mut impl Layouter<F>,
1558        x: &AssignedField<F, K, P>,
1559    ) -> Result<AssignedField<F, K, P>, Error> {
1560        // This threshold was chosen empirically.
1561        let threshold: BI = P::max_limb_bound() / 10;
1562        let dangerous_lower_bounds = x.limb_bounds.iter().any(|b| b.0 < -threshold.clone());
1563        let dangerous_upper_bounds = x.limb_bounds.iter().any(|b| b.1 > threshold);
1564        if dangerous_lower_bounds || dangerous_upper_bounds {
1565            self.make_canonical(layouter, x)
1566        } else {
1567            Ok(x.clone())
1568        }
1569    }
1570
1571    /// Converts the given assigned field element into an equivalent one
1572    /// represented in canonical form, through the normalization procedure.
1573    fn make_canonical(
1574        &self,
1575        layouter: &mut impl Layouter<F>,
1576        x: &AssignedField<F, K, P>,
1577    ) -> Result<AssignedField<F, K, P>, Error> {
1578        let max_limb_bound = P::max_limb_bound();
1579        x.limb_bounds.iter().for_each(|(lower, upper)| {
1580            if lower < &(-&max_limb_bound) || upper > &max_limb_bound {
1581                panic!(
1582                    "make_canonical: the limb bounds of the input: [{}, {}] exceed the
1583                     maximum limb bound value {}; consider applying a normalization
1584                     earlier, when the bounds are still within the permited range;
1585                     increasing the [max_limb_bound] of your FieldEmulationParams could also
1586                     help, if possible.",
1587                    lower, upper, max_limb_bound
1588                );
1589            }
1590        });
1591        let z_limbs = norm::normalize::<F, K, P, N>(
1592            layouter,
1593            x,
1594            &self.config.norm_config,
1595            &self.native_gadget,
1596        )?;
1597        let z = AssignedField::<F, K, P> {
1598            limb_values: z_limbs,
1599            limb_bounds: well_formed_bounds::<F, K, P>(),
1600            _marker: PhantomData,
1601        };
1602        Ok(z)
1603    }
1604
1605    /// This function should only be called once the limb has been asserted to
1606    /// be in the range [0, base).
1607    fn assigned_field_from_limb(
1608        &self,
1609        layouter: &mut impl Layouter<F>,
1610        limb: &AssignedNative<F>,
1611    ) -> Result<AssignedField<F, K, P>, Error> {
1612        // Subtract one for the unique-zero representation.
1613        let least_significant_limb = self.native_gadget.add_constant(layouter, limb, -F::ONE)?;
1614        let mut limb_values = vec![least_significant_limb];
1615        let mut limb_bounds = well_formed_bounds::<F, K, P>();
1616        let zero = self.native_gadget.assign_fixed(layouter, F::ZERO)?;
1617        limb_values.resize(P::NB_LIMBS as usize, zero);
1618        limb_bounds[0] = (limb_bounds[0].clone().0 - 1, limb_bounds[0].clone().1 - 1);
1619        Ok(AssignedField::<F, K, P> {
1620            limb_values,
1621            limb_bounds,
1622            _marker: PhantomData,
1623        })
1624    }
1625}
1626
1627// Inherit Bit Assignment Instructions from NativeGadget.
1628impl<F, K, P, N> AssignmentInstructions<F, AssignedBit<F>> for FieldChip<F, K, P, N>
1629where
1630    F: CircuitField,
1631    K: CircuitField,
1632    P: FieldEmulationParams<F, K>,
1633    N: NativeInstructions<F>,
1634{
1635    fn assign(
1636        &self,
1637        layouter: &mut impl Layouter<F>,
1638        value: Value<bool>,
1639    ) -> Result<AssignedBit<F>, Error> {
1640        self.native_gadget.assign(layouter, value)
1641    }
1642
1643    fn assign_fixed(
1644        &self,
1645        layouter: &mut impl Layouter<F>,
1646        constant: bool,
1647    ) -> Result<AssignedBit<F>, Error> {
1648        self.native_gadget.assign_fixed(layouter, constant)
1649    }
1650}
1651
1652// Inherit Bit Assertion Instructions from NativeGadget.
1653impl<F, K, P, N> AssertionInstructions<F, AssignedBit<F>> for FieldChip<F, K, P, N>
1654where
1655    F: CircuitField,
1656    K: CircuitField,
1657    P: FieldEmulationParams<F, K>,
1658    N: NativeInstructions<F>,
1659{
1660    fn assert_equal(
1661        &self,
1662        layouter: &mut impl Layouter<F>,
1663        x: &AssignedBit<F>,
1664        y: &AssignedBit<F>,
1665    ) -> Result<(), Error> {
1666        self.native_gadget.assert_equal(layouter, x, y)
1667    }
1668
1669    fn assert_not_equal(
1670        &self,
1671        layouter: &mut impl Layouter<F>,
1672        x: &AssignedBit<F>,
1673        y: &AssignedBit<F>,
1674    ) -> Result<(), Error> {
1675        self.native_gadget.assert_not_equal(layouter, x, y)
1676    }
1677
1678    fn assert_equal_to_fixed(
1679        &self,
1680        layouter: &mut impl Layouter<F>,
1681        x: &AssignedBit<F>,
1682        constant: bool,
1683    ) -> Result<(), Error> {
1684        self.native_gadget.assert_equal_to_fixed(layouter, x, constant)
1685    }
1686
1687    fn assert_not_equal_to_fixed(
1688        &self,
1689        layouter: &mut impl Layouter<F>,
1690        x: &AssignedBit<F>,
1691        constant: bool,
1692    ) -> Result<(), Error> {
1693        self.native_gadget.assert_not_equal_to_fixed(layouter, x, constant)
1694    }
1695}
1696
1697impl<F, K, P, N> ConversionInstructions<F, AssignedBit<F>, AssignedField<F, K, P>>
1698    for FieldChip<F, K, P, N>
1699where
1700    F: CircuitField,
1701    K: CircuitField,
1702    P: FieldEmulationParams<F, K>,
1703    N: NativeInstructions<F>,
1704{
1705    fn convert_value(&self, x: &bool) -> Option<K> {
1706        Some(if *x { K::ONE } else { K::ZERO })
1707    }
1708
1709    fn convert(
1710        &self,
1711        layouter: &mut impl Layouter<F>,
1712        x: &AssignedBit<F>,
1713    ) -> Result<AssignedField<F, K, P>, Error> {
1714        let x: AssignedNative<F> = x.clone().into();
1715        self.assigned_field_from_limb(layouter, &x)
1716    }
1717}
1718
1719impl<F, K, P, N> ConversionInstructions<F, AssignedByte<F>, AssignedField<F, K, P>>
1720    for FieldChip<F, K, P, N>
1721where
1722    F: CircuitField,
1723    K: CircuitField,
1724    P: FieldEmulationParams<F, K>,
1725    N: NativeInstructions<F>,
1726{
1727    fn convert_value(&self, x: &u8) -> Option<K> {
1728        Some(K::from(*x as u64))
1729    }
1730
1731    fn convert(
1732        &self,
1733        layouter: &mut impl Layouter<F>,
1734        x: &AssignedByte<F>,
1735    ) -> Result<AssignedField<F, K, P>, Error> {
1736        let x: AssignedNative<F> = x.clone().into();
1737        self.assigned_field_from_limb(layouter, &x)
1738    }
1739}
1740
1741// Inherit Canonicity Instructions from NativeGadget.
1742impl<F, K, P, N> CanonicityInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
1743where
1744    F: CircuitField,
1745    K: CircuitField,
1746    P: FieldEmulationParams<F, K>,
1747    N: NativeInstructions<F>,
1748{
1749    fn le_bits_lower_than(
1750        &self,
1751        layouter: &mut impl Layouter<F>,
1752        bits: &[AssignedBit<F>],
1753        bound: BigUint,
1754    ) -> Result<AssignedBit<F>, Error> {
1755        self.native_gadget.le_bits_lower_than(layouter, bits, bound)
1756    }
1757
1758    fn le_bits_geq_than(
1759        &self,
1760        layouter: &mut impl Layouter<F>,
1761        bits: &[AssignedBit<F>],
1762        bound: BigUint,
1763    ) -> Result<AssignedBit<F>, Error> {
1764        self.native_gadget.le_bits_geq_than(layouter, bits, bound)
1765    }
1766}
1767
1768#[derive(Clone, Debug)]
1769#[cfg(any(test, feature = "testing"))]
1770/// Configuration used to implement `FromScratch` for the Foreign field chip.
1771/// This should only be used for testing.
1772pub struct FieldChipConfigForTests<F, N>
1773where
1774    F: CircuitField,
1775    N: NativeInstructions<F> + FromScratch<F>,
1776{
1777    native_gadget_config: <N as FromScratch<F>>::Config,
1778    field_chip_config: FieldChipConfig,
1779}
1780
1781#[cfg(any(test, feature = "testing"))]
1782impl<F, K, P, N> FromScratch<F> for FieldChip<F, K, P, N>
1783where
1784    F: CircuitField,
1785    K: CircuitField,
1786    P: FieldEmulationParams<F, K>,
1787    N: NativeInstructions<F> + FromScratch<F>,
1788{
1789    type Config = FieldChipConfigForTests<F, N>;
1790
1791    fn new_from_scratch(config: &FieldChipConfigForTests<F, N>) -> Self {
1792        let native_gadget = <N as FromScratch<F>>::new_from_scratch(&config.native_gadget_config);
1793        FieldChip::new(&config.field_chip_config, &native_gadget)
1794    }
1795
1796    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
1797        self.native_gadget.load_from_scratch(layouter)
1798    }
1799
1800    fn configure_from_scratch(
1801        meta: &mut ConstraintSystem<F>,
1802        advice_columns: &mut Vec<Column<Advice>>,
1803        fixed_columns: &mut Vec<Column<Fixed>>,
1804        instance_columns: &[Column<Instance>; 2],
1805    ) -> FieldChipConfigForTests<F, N> {
1806        let native_gadget_config = <N as FromScratch<F>>::configure_from_scratch(
1807            meta,
1808            advice_columns,
1809            fixed_columns,
1810            instance_columns,
1811        );
1812        // Use hard-coded pow2range values matching NativeGadget::configure_from_scratch
1813        let nb_parallel_range_checks = 4;
1814        let max_bit_len = 8;
1815        let field_chip_config = {
1816            let nb_fc_cols = nb_field_chip_columns::<F, K, P>();
1817            while advice_columns.len() < nb_fc_cols {
1818                advice_columns.push(meta.advice_column());
1819            }
1820            FieldChip::<F, K, P, N>::configure(
1821                meta,
1822                &advice_columns[..nb_fc_cols],
1823                nb_parallel_range_checks,
1824                max_bit_len,
1825            )
1826        };
1827        FieldChipConfigForTests {
1828            native_gadget_config,
1829            field_chip_config,
1830        }
1831    }
1832}
1833
1834#[cfg(test)]
1835mod tests {
1836    use midnight_curves::{
1837        k256::{Fp as K256Base, Fq as K256Scalar},
1838        p256::{Fp as P256Base, Fq as P256Scalar},
1839        Fq as BlsScalar,
1840    };
1841
1842    use super::*;
1843    use crate::{
1844        field::{
1845            decomposition::chip::P2RDecompositionChip, foreign::params::MultiEmulationParams,
1846            NativeChip, NativeGadget,
1847        },
1848        instructions::{
1849            arithmetic, assertions, control_flow, decomposition, equality, public_input, zero,
1850        },
1851    };
1852
1853    macro_rules! test_generic {
1854        ($mod:ident, $op:ident, $native:ident, $emulated:ident, $name:expr) => {
1855            $mod::tests::$op::<
1856                $native,
1857                AssignedField<$native, $emulated, MultiEmulationParams>,
1858                FieldChip<
1859                    $native,
1860                    $emulated,
1861                    MultiEmulationParams,
1862                    NativeGadget<$native, P2RDecompositionChip<$native>, NativeChip<$native>>,
1863                >,
1864            >($name);
1865        };
1866    }
1867
1868    macro_rules! test {
1869        ($mod:ident, $op:ident) => {
1870            #[test]
1871            fn $op() {
1872                test_generic!($mod, $op, BlsScalar, K256Base, "field_chip_k256_base");
1873                test_generic!($mod, $op, BlsScalar, K256Scalar, "field_chip_k256_scalar");
1874                test_generic!($mod, $op, BlsScalar, P256Base, "field_chip_p256_base");
1875                test_generic!($mod, $op, BlsScalar, P256Scalar, "field_chip_p256_scalar");
1876            }
1877        };
1878    }
1879
1880    test!(assertions, test_assertions);
1881
1882    test!(public_input, test_public_inputs);
1883
1884    test!(equality, test_is_equal);
1885
1886    test!(zero, test_zero_assertions);
1887    test!(zero, test_is_zero);
1888
1889    test!(control_flow, test_select);
1890    test!(control_flow, test_cond_assert_equal);
1891    test!(control_flow, test_cond_swap);
1892
1893    test!(arithmetic, test_add);
1894    test!(arithmetic, test_sub);
1895    test!(arithmetic, test_mul);
1896    test!(arithmetic, test_div);
1897    test!(arithmetic, test_neg);
1898    test!(arithmetic, test_inv);
1899    test!(arithmetic, test_pow);
1900    test!(arithmetic, test_linear_combination);
1901    test!(arithmetic, test_add_and_mul);
1902
1903    macro_rules! test_generic {
1904        ($mod:ident, $op:ident, $native:ident, $emulated:ident, $name:expr) => {
1905            $mod::tests::$op::<
1906                $native,
1907                AssignedField<$native, $emulated, MultiEmulationParams>,
1908                FieldChip<
1909                    $native,
1910                    $emulated,
1911                    MultiEmulationParams,
1912                    NativeGadget<$native, P2RDecompositionChip<$native>, NativeChip<$native>>,
1913                >,
1914                NativeGadget<$native, P2RDecompositionChip<$native>, NativeChip<$native>>,
1915            >($name);
1916        };
1917    }
1918
1919    macro_rules! test {
1920        ($mod:ident, $op:ident) => {
1921            #[test]
1922            fn $op() {
1923                test_generic!($mod, $op, BlsScalar, K256Base, "field_chip_k256_base");
1924                test_generic!($mod, $op, BlsScalar, K256Scalar, "field_chip_k256_scalar");
1925                test_generic!($mod, $op, BlsScalar, P256Base, "field_chip_p256_base");
1926                test_generic!($mod, $op, BlsScalar, P256Scalar, "field_chip_p256_scalar");
1927            }
1928        };
1929    }
1930
1931    test!(decomposition, test_bit_decomposition);
1932    test!(decomposition, test_byte_decomposition);
1933    test!(decomposition, test_sgn0);
1934}