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