Skip to main content

midnight_circuits/field/native/
native_gadget.rs

1// This file is part of MIDNIGHT-ZK.
2// Copyright (C) Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! A gadget that implement all basic basic operations in the native field, i.e.
15//! basic field operations, decompositions and comparisons
16
17use std::{cell::RefCell, cmp::min, collections::HashMap, marker::PhantomData, rc::Rc};
18
19use midnight_proofs::{
20    circuit::{Layouter, Value},
21    plonk::Error,
22};
23use num_bigint::BigUint;
24use num_traits::Zero;
25#[cfg(any(test, feature = "testing"))]
26use {
27    crate::field::decomposition::chip::P2RDecompositionConfig,
28    crate::field::decomposition::pow2range::Pow2RangeChip,
29    crate::field::native::{NB_ARITH_COLS, NB_ARITH_FIXED_COLS},
30    crate::testing_utils::FromScratch,
31    crate::testing_utils::Sampleable,
32    crate::utils::ComposableChip,
33    midnight_proofs::plonk::{Advice, Column, ConstraintSystem, Fixed, Instance},
34    rand::Rng,
35    rand::RngCore,
36};
37
38use crate::{
39    field::{
40        decomposition::{chip::P2RDecompositionChip, instructions::CoreDecompositionInstructions},
41        NativeChip,
42    },
43    instructions::{
44        public_input::CommittedInstanceInstructions, ArithInstructions, AssertionInstructions,
45        AssignmentInstructions, BinaryInstructions, BitwiseInstructions, CanonicityInstructions,
46        ComparisonInstructions, ControlFlowInstructions, ConversionInstructions,
47        DecompositionInstructions, DivisionInstructions, EqualityInstructions, FieldInstructions,
48        NativeInstructions, PublicInputInstructions, RangeCheckInstructions,
49        ScalarFieldInstructions, UnsafeConversionInstructions, ZeroInstructions,
50    },
51    types::{AssignedBit, AssignedNative, InnerValue, Instantiable},
52    utils::util::big_to_fe,
53    CircuitField,
54};
55
56#[derive(Debug, Clone)]
57/// A gadget that implements all basic operations on the Native field:
58/// - Assignments
59/// - Assertions
60/// - Arithmetic
61/// - Binary
62/// - Comparison
63/// - ControlFlow
64/// - Conversions
65/// - Decomposition
66/// - Equality
67pub struct NativeGadget<F, CoreDecomposition, NativeArith>
68where
69    F: CircuitField,
70    CoreDecomposition: CoreDecompositionInstructions<F>,
71    NativeArith: ArithInstructions<F, AssignedNative<F>>,
72{
73    core_decomposition_chip: CoreDecomposition,
74    /// The underlying native field arithmetic chip.
75    pub native_chip: NativeArith,
76    constrained_cells: Rc<RefCell<HashMap<AssignedNative<F>, BigUint>>>,
77    _marker: PhantomData<F>,
78}
79
80impl<F, CoreDecomposition, NativeArith> NativeGadget<F, CoreDecomposition, NativeArith>
81where
82    F: CircuitField,
83    CoreDecomposition: CoreDecompositionInstructions<F>,
84    NativeArith: ArithInstructions<F, AssignedNative<F>>,
85{
86    /// Create a new gadget.
87    pub fn new(core_decomposition_chip: CoreDecomposition, native_chip: NativeArith) -> Self {
88        Self {
89            core_decomposition_chip,
90            native_chip,
91            constrained_cells: Rc::new(RefCell::new(HashMap::new())),
92            _marker: PhantomData,
93        }
94    }
95
96    /// Updates the (strict) upper-bound of the given assigned cell to the
97    /// minimum between its current bound and the given `bound`.
98    fn update_bound(&self, x: &AssignedNative<F>, bound: BigUint) {
99        let mut map = self.constrained_cells.borrow_mut();
100        map.entry(x.clone())
101            .and_modify(|v| *v = min(v.clone(), bound.clone()))
102            .or_insert(bound);
103    }
104}
105
106impl<F: CircuitField> Instantiable<F> for AssignedByte<F> {
107    fn as_public_input(element: &u8) -> Vec<F> {
108        vec![F::from(*element as u64)]
109    }
110
111    fn from_public_input(fields: &[F]) -> Option<u8> {
112        let [f] = fields else { return None };
113        u8::try_from(f.to_biguint()).ok()
114    }
115}
116
117/// This wrapper type on `AssignedNative<F>` is designed to enforce type safety
118/// on assigned bytes. It prevents the user from creating an `AssignedByte`
119/// without using the designated entry points, which guarantee (with
120/// constraints) that the assigned value is indeed in the range [0, 256).
121#[derive(Clone, Debug)]
122#[must_use]
123pub struct AssignedByte<F: CircuitField>(AssignedNative<F>);
124
125impl<F: CircuitField> InnerValue for AssignedByte<F> {
126    type Element = u8;
127
128    fn value(&self) -> Value<u8> {
129        self.0.value().map(|v| {
130            let bi_v = v.to_biguint();
131            #[cfg(not(test))]
132            assert!(bi_v <= BigUint::from(255u8));
133            bi_v.to_bytes_le().first().copied().unwrap_or(0u8)
134        })
135    }
136}
137
138impl<F: CircuitField> From<AssignedByte<F>> for AssignedNative<F> {
139    fn from(value: AssignedByte<F>) -> Self {
140        value.0
141    }
142}
143
144impl<F: CircuitField> From<&AssignedByte<F>> for AssignedNative<F> {
145    fn from(value: &AssignedByte<F>) -> Self {
146        value.clone().0
147    }
148}
149
150impl<F: CircuitField> From<AssignedBit<F>> for AssignedByte<F> {
151    fn from(value: AssignedBit<F>) -> Self {
152        AssignedByte(value.0)
153    }
154}
155
156#[cfg(any(test, feature = "testing"))]
157impl<F: CircuitField> Sampleable for AssignedByte<F> {
158    fn sample_inner(mut rng: impl RngCore) -> Self::Element {
159        rng.r#gen()
160    }
161}
162
163/// Struct representing bounded elements, i.e. 0 <= value < 2^bound.
164#[derive(Clone, Debug)]
165pub struct BoundedElement<F: CircuitField> {
166    value: F,
167    bound: u32,
168}
169
170impl<F: CircuitField> BoundedElement<F> {
171    /// Creates a new bounded element
172    pub fn new(value: F, bound: u32) -> Self {
173        #[cfg(not(test))]
174        {
175            use num_traits::One;
176
177            let v_as_bint = value.to_biguint();
178            let bound_as_bint = BigUint::one() << bound;
179            assert!(
180                v_as_bint < bound_as_bint,
181                "Trying to convert {:?} to an AssignedBounded less than 2^{:?}!",
182                value,
183                bound
184            );
185        }
186        BoundedElement { value, bound }
187    }
188
189    /// gets the field value of a BoundedElement
190    pub fn field_value(&self) -> F {
191        self.value
192    }
193
194    /// gets the bound of a BoundedElement
195    pub fn bound(&self) -> u32 {
196        self.bound
197    }
198}
199
200/// This type is designed to enforce type safety on assigned "small" values.
201/// It prevents the user from creating an `AssignedBounded` without using the
202/// designated entry points, which guarantee (with constraints) that the
203/// assigned value is in the desired range, `[0, 2^bound)`.
204#[derive(Clone, Debug)]
205pub struct AssignedBounded<F: CircuitField> {
206    value: AssignedNative<F>,
207    bound: u32,
208}
209
210impl<F: CircuitField> AssignedBounded<F> {
211    /// CAUTION: use only if you know what you are doing!
212    ///
213    /// This function converts an `AssignedNative` to an `AssignedBounded`
214    /// *without* adding any constraint to guarantee the number respects the
215    /// bound.
216    ///
217    /// *It should be used only when the input x is already rangechecked
218    pub(crate) fn to_assigned_bounded_unsafe(x: &AssignedNative<F>, bound: u32) -> Self {
219        // we create the element to enforce the runtime assertions
220        let _new = x.value().map(|&x| BoundedElement::new(x, bound));
221        AssignedBounded {
222            value: x.clone(),
223            bound,
224        }
225    }
226
227    /// gets the bound of an AssignedBounded
228    pub fn bound(&self) -> u32 {
229        self.bound
230    }
231}
232
233impl<F: CircuitField> InnerValue for AssignedBounded<F> {
234    type Element = BoundedElement<F>;
235
236    fn value(&self) -> Value<BoundedElement<F>> {
237        let (assigned_value, bound) = (self.value.clone(), self.bound());
238        assigned_value.value().map(|&value| BoundedElement { value, bound })
239    }
240}
241
242impl<F, CoreDecomposition, NativeArith> RangeCheckInstructions<F, AssignedNative<F>>
243    for NativeGadget<F, CoreDecomposition, NativeArith>
244where
245    F: CircuitField,
246    CoreDecomposition: CoreDecompositionInstructions<F>,
247    NativeArith: ArithInstructions<F, AssignedNative<F>>
248        + AssignmentInstructions<F, AssignedBit<F>>
249        + ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>
250        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
251        + BinaryInstructions<F>
252        + EqualityInstructions<F, AssignedNative<F>>
253        + ControlFlowInstructions<F, AssignedNative<F>>,
254{
255    fn assign_lower_than_fixed(
256        &self,
257        layouter: &mut impl Layouter<F>,
258        value: Value<F>,
259        bound: &BigUint,
260    ) -> Result<AssignedNative<F>, Error> {
261        if bound.is_zero() {
262            return self.assign_fixed(layouter, F::ZERO);
263        }
264
265        // compute largest k such that 2^k <= bound
266        let k = (bound.bits() - 1) as usize;
267        if *bound == BigUint::from(1u8) << k {
268            return self.core_decomposition_chip.assign_less_than_pow2(layouter, value, k);
269        }
270        let x = self.assign(layouter, value)?;
271        self.assert_lower_than_fixed(layouter, &x, bound)?;
272        Ok(x)
273    }
274
275    fn assert_lower_than_fixed(
276        &self,
277        layouter: &mut impl Layouter<F>,
278        x: &AssignedNative<F>,
279        bound: &BigUint,
280    ) -> Result<(), Error> {
281        if let Some(current_bound) = self.constrained_cells.borrow().get(x) {
282            if current_bound <= bound {
283                return Ok(());
284            }
285        }
286        self.update_bound(x, bound.clone());
287
288        // compute largest k such that 2^k <= bound
289        let k = (bound.bits() - 1) as usize;
290        let two_pow_k = BigUint::from(1u8) << k; // 2^k
291
292        // if the bound is a power of 2, the check is easier
293        if two_pow_k == *bound {
294            return self.core_decomposition_chip.assert_less_than_pow2(layouter, x, k);
295        }
296
297        // b := x in [0, 2^k)
298        let b_value = x.value().map(|x| x.to_biguint() < two_pow_k);
299        let b: AssignedBit<F> = self.assign(layouter, b_value)?;
300
301        let diff: F = big_to_fe(bound - two_pow_k);
302
303        // x in [0, bound) <=> x in [0, 2^k) or (x - diff) in [0, 2^k)
304        let shifted_x = self.add_constant(layouter, x, -diff)?;
305        let y = self.select(layouter, &b, x, &shifted_x)?;
306        self.core_decomposition_chip.assert_less_than_pow2(layouter, &y, k)
307    }
308}
309
310impl<F, CoreDecomposition, NativeArith> ComparisonInstructions<F, AssignedNative<F>>
311    for NativeGadget<F, CoreDecomposition, NativeArith>
312where
313    F: CircuitField,
314    CoreDecomposition: CoreDecompositionInstructions<F>,
315    NativeArith: ArithInstructions<F, AssignedNative<F>>
316        + AssignmentInstructions<F, AssignedBit<F>>
317        + ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>
318        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
319        + BinaryInstructions<F>
320        + EqualityInstructions<F, AssignedNative<F>>
321        + ControlFlowInstructions<F, AssignedNative<F>>,
322{
323    // This constant must not exceed F::NUM_BITS - 2. This restriction derives from
324    // the assumption that the following condition holds true:
325    //
326    //     x <  y    ==> 0 < y - x < 2^MAX_BOUND_IN_BITS
327    //
328    // This implies that the difference x - y should not wrap around in the field,
329    // ensuring it remains less than 2^MAX_BOUND_IN_BITS.
330    const MAX_BOUND_IN_BITS: u32 = F::NUM_BITS - 2;
331
332    fn bounded_of_element(
333        &self,
334        layouter: &mut impl Layouter<F>,
335        n: usize,
336        x: &AssignedNative<F>,
337    ) -> Result<AssignedBounded<F>, Error> {
338        #[cfg(not(test))]
339        assert!(
340            n <= Self::MAX_BOUND_IN_BITS as usize,
341            "Cannot bound an element with a bound {} > {} = MAX_BOUND",
342            n,
343            Self::MAX_BOUND_IN_BITS,
344        );
345
346        self.assert_lower_than_fixed(layouter, x, &(BigUint::from(1u32) << n))?;
347        Ok(AssignedBounded::to_assigned_bounded_unsafe(x, n as u32))
348    }
349
350    fn element_of_bounded(
351        &self,
352        _layouter: &mut impl Layouter<F>,
353        bounded: &AssignedBounded<F>,
354    ) -> Result<AssignedNative<F>, Error> {
355        Ok(bounded.value.clone())
356    }
357
358    /// Returns `true` iff the given assigned element is strictly lower than the
359    /// given bound.
360    fn lower_than_fixed(
361        &self,
362        layouter: &mut impl Layouter<F>,
363        x: &AssignedBounded<F>,
364        y: F,
365    ) -> Result<AssignedBit<F>, Error> {
366        if let Some(current_bound) = self.constrained_cells.borrow().get(&x.value) {
367            if *current_bound <= y.to_biguint() {
368                return self.assign_fixed(layouter, true);
369            }
370        }
371
372        let x_as_bint = x.value.value().map(|x| x.to_biguint());
373        let y_as_bint = y.to_biguint();
374
375        // check that we try to make a meaningful comparison, i.e. y < 2^p-1
376        #[cfg(not(test))]
377        assert!(y_as_bint < BigUint::from(1u8) << Self::MAX_BOUND_IN_BITS);
378
379        // x is already bounded by the type system so x < bound for some fixed bound.
380        // If we want to show that x < bound <= y this relation automatically holds
381        if y_as_bint >= (BigUint::from(1u8) << x.bound()) {
382            return self.assign_fixed(layouter, true);
383        }
384
385        // we will now assert the equation
386        // we know 0 <= x,y < 2^bound. There are two cases:
387        //  1. x <  y    ==>    0 < y - x < 2^bound    ==>    0 <= y - x - 1 < 2^bound
388        //  2. x >= y    ==>                                  0 <=  x - y < 2^bound
389
390        // define z = b(2y-1) + x + bx(-2) - y
391        //   - if b = 0 ==> z = x - y
392        //   - if b = 1 ==> z = 2y-1 + x -2x - y =  y - 1 - x
393
394        // assign b
395        let result_bit = x_as_bint.map(|x_as_bint| x_as_bint < y_as_bint);
396        let assigned_result = self.assign(layouter, result_bit)?;
397
398        // assign z: z is of the form "f1 a1 + f2 a2 + f a_1 a_2 + c"
399        // TODO: This can be done in a single row but the interface prevents it...
400        // Expose some more complex function maybe?
401        let b_el: AssignedNative<F> = self.convert(layouter, &assigned_result)?;
402        let x_el = self.element_of_bounded(layouter, x)?;
403        let bx = self.mul(layouter, &x_el, &b_el, None)?;
404        let terms = vec![
405            (F::from(2) * y - F::ONE, b_el),
406            (F::ONE, x_el),
407            (-F::from(2), bx),
408        ];
409        let z = self.linear_combination(layouter, terms.as_slice(), -y)?;
410
411        self.core_decomposition_chip
412            .assert_less_than_pow2(layouter, &z, x.bound() as usize)?;
413        Ok(assigned_result)
414    }
415
416    fn lower_than(
417        &self,
418        layouter: &mut impl Layouter<F>,
419        x: &AssignedBounded<F>,
420        y: &AssignedBounded<F>,
421    ) -> Result<AssignedBit<F>, Error> {
422        let x_as_bint = x.value.value().map(|x| x.to_biguint());
423        let y_as_bint = y.value.value().map(|x| x.to_biguint());
424
425        // we will now assert the equation
426        // we know 0 <= x,y < 2^bound. There are two cases:
427        //  1. x <  y    ==>    0 < y - x < 2^bound    ==>    0 <= y - x - 1 < 2^bound
428        //  2. x >= y    ==>                                  0 <=  x - y < 2^bound
429
430        // define z = 2by - b + x + bx(-2) - y
431        //   - if b = 0 ==> z = x - y
432        //   - if b = 1 ==> z = 2y-1 + x -2x - y =  y - 1 - x
433
434        // assign b
435        let result_bit =
436            x_as_bint.zip(y_as_bint).map(|(x_as_bint, y_as_bint)| x_as_bint < y_as_bint);
437        let assigned_result = self.assign(layouter, result_bit)?;
438
439        // assign z: z is of the form "f1 a1 + f2 a2 + f a_1 a_2 + c"
440        let b_el: AssignedNative<F> = self.convert(layouter, &assigned_result)?;
441        let x_el = self.element_of_bounded(layouter, x)?;
442        let y_el = self.element_of_bounded(layouter, y)?;
443
444        let bx = self.mul(layouter, &x_el, &b_el, None)?;
445        let by = self.mul(layouter, &y_el, &b_el, None)?;
446        let terms = vec![
447            (F::from(2), by),
448            (-F::ONE, b_el),
449            (F::ONE, x_el),
450            (-F::from(2), bx),
451            (-F::ONE, y_el),
452        ];
453        let z = self.linear_combination(layouter, terms.as_slice(), F::ZERO)?;
454
455        let max_bound = x.bound().max(y.bound());
456        self.core_decomposition_chip
457            .assert_less_than_pow2(layouter, &z, max_bound as usize)?;
458        Ok(assigned_result)
459    }
460
461    fn leq(
462        &self,
463        layouter: &mut impl Layouter<F>,
464        x: &AssignedBounded<F>,
465        y: &AssignedBounded<F>,
466    ) -> Result<AssignedBit<F>, Error> {
467        // This is reimplemented this way because doing x < y + 1 might break things in
468        // some weird edge case
469        let b1 = self.lower_than(layouter, x, y)?;
470        let x_el = self.element_of_bounded(layouter, x)?;
471        let y_el = self.element_of_bounded(layouter, y)?;
472        let b2 = self.is_equal(layouter, &x_el, &y_el)?;
473        self.or(layouter, &[b1, b2])
474    }
475
476    /// Returns `true` iff the given assigned element is greater than or equal
477    /// to the given bound.
478    fn geq(
479        &self,
480        layouter: &mut impl Layouter<F>,
481        x: &AssignedBounded<F>,
482        y: &AssignedBounded<F>,
483    ) -> Result<AssignedBit<F>, Error> {
484        let b = self.lower_than(layouter, x, y)?;
485        self.not(layouter, &b)
486    }
487}
488
489impl<F, CoreDecomposition, NativeArith> DivisionInstructions<F, AssignedNative<F>>
490    for NativeGadget<F, CoreDecomposition, NativeArith>
491where
492    F: CircuitField,
493    CoreDecomposition: CoreDecompositionInstructions<F>,
494    NativeArith: ArithInstructions<F, AssignedNative<F>>
495        + AssignmentInstructions<F, AssignedBit<F>>
496        + ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>
497        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
498        + BinaryInstructions<F>
499        + EqualityInstructions<F, AssignedNative<F>>
500        + ControlFlowInstructions<F, AssignedNative<F>>,
501{
502}
503
504/// Instructions for constraining bytes as public inputs.
505impl<F, CoreDecomposition, NativeArith> PublicInputInstructions<F, AssignedByte<F>>
506    for NativeGadget<F, CoreDecomposition, NativeArith>
507where
508    F: CircuitField,
509    CoreDecomposition: CoreDecompositionInstructions<F>,
510    NativeArith:
511        PublicInputInstructions<F, AssignedNative<F>> + ArithInstructions<F, AssignedNative<F>>,
512{
513    fn as_public_input(
514        &self,
515        _layouter: &mut impl Layouter<F>,
516        assigned: &AssignedByte<F>,
517    ) -> Result<Vec<AssignedNative<F>>, Error> {
518        Ok(vec![assigned.clone().into()])
519    }
520
521    fn constrain_as_public_input(
522        &self,
523        layouter: &mut impl Layouter<F>,
524        assigned: &AssignedByte<F>,
525    ) -> Result<(), Error> {
526        let assigned_as_native: AssignedNative<F> = assigned.clone().into();
527        self.constrain_as_public_input(layouter, &assigned_as_native)
528    }
529
530    fn assign_as_public_input(
531        &self,
532        layouter: &mut impl Layouter<F>,
533        value: Value<u8>,
534    ) -> Result<AssignedByte<F>, Error> {
535        // We can skip the in-circuit [0, 255]-range-check as this condition will
536        // be enforced through the public inputs bind anyway.
537        let assigned_native = self
538            .native_chip
539            .assign_as_public_input(layouter, value.map(|byte| F::from(byte as u64)))?;
540        self.convert_unsafe(layouter, &assigned_native)
541    }
542}
543
544impl<F, CD, NA, Assigned> CommittedInstanceInstructions<F, Assigned> for NativeGadget<F, CD, NA>
545where
546    F: CircuitField,
547    CD: CoreDecompositionInstructions<F>,
548
549    NA: CommittedInstanceInstructions<F, AssignedNative<F>>
550        + ArithInstructions<F, AssignedNative<F>>,
551    Assigned: Instantiable<F> + Into<AssignedNative<F>>,
552{
553    fn constrain_as_committed_public_input(
554        &self,
555        layouter: &mut impl Layouter<F>,
556        assigned: &Assigned,
557    ) -> Result<(), Error> {
558        let assigned_as_native = assigned.clone().into();
559        self.native_chip
560            .constrain_as_committed_public_input(layouter, &assigned_as_native)
561    }
562}
563
564/// The set of circuit instructions for assignment of bytes.
565impl<F, CoreDecomposition, NativeArith> AssignmentInstructions<F, AssignedByte<F>>
566    for NativeGadget<F, CoreDecomposition, NativeArith>
567where
568    F: CircuitField,
569    CoreDecomposition: CoreDecompositionInstructions<F>,
570    NativeArith: ArithInstructions<F, AssignedNative<F>>,
571{
572    fn assign(
573        &self,
574        layouter: &mut impl Layouter<F>,
575        byte: Value<u8>,
576    ) -> Result<AssignedByte<F>, Error> {
577        let byte_as_f = byte.map(|b| F::from(b as u64));
578        let assigned =
579            self.core_decomposition_chip.assign_less_than_pow2(layouter, byte_as_f, 8)?;
580        Ok(AssignedByte(assigned))
581    }
582
583    fn assign_fixed(
584        &self,
585        layouter: &mut impl Layouter<F>,
586        constant: u8,
587    ) -> Result<AssignedByte<F>, Error> {
588        let assigned = self.assign_fixed(layouter, F::from(constant as u64))?;
589        Ok(AssignedByte(assigned))
590    }
591
592    fn assign_many(
593        &self,
594        layouter: &mut impl Layouter<F>,
595        values: &[Value<u8>],
596    ) -> Result<Vec<AssignedByte<F>>, Error> {
597        let values_as_f: Vec<_> = values.iter().map(|v| v.map(|b| F::from(b as u64))).collect();
598
599        self.core_decomposition_chip
600            .assign_many_small(layouter, &values_as_f, 8)?
601            .iter()
602            .map(|assigned_native| self.convert_unsafe(layouter, assigned_native))
603            .collect()
604    }
605}
606
607/// The set of AssertionInstructions for bytes.
608impl<F, CoreDecomposition, NativeArith> AssertionInstructions<F, AssignedByte<F>>
609    for NativeGadget<F, CoreDecomposition, NativeArith>
610where
611    F: CircuitField,
612    CoreDecomposition: CoreDecompositionInstructions<F>,
613    NativeArith: ArithInstructions<F, AssignedNative<F>>,
614{
615    fn assert_equal(
616        &self,
617        layouter: &mut impl Layouter<F>,
618        byte1: &AssignedByte<F>,
619        byte2: &AssignedByte<F>,
620    ) -> Result<(), Error> {
621        let x1: AssignedNative<F> = self.convert(layouter, byte1)?;
622        let x2: AssignedNative<F> = self.convert(layouter, byte2)?;
623        self.assert_equal(layouter, &x1, &x2)
624    }
625
626    fn assert_not_equal(
627        &self,
628        layouter: &mut impl Layouter<F>,
629        byte1: &AssignedByte<F>,
630        byte2: &AssignedByte<F>,
631    ) -> Result<(), Error> {
632        let x1: AssignedNative<F> = self.convert(layouter, byte1)?;
633        let x2: AssignedNative<F> = self.convert(layouter, byte2)?;
634        self.assert_not_equal(layouter, &x1, &x2)
635    }
636
637    fn assert_equal_to_fixed(
638        &self,
639        layouter: &mut impl Layouter<F>,
640        byte: &AssignedByte<F>,
641        constant: u8,
642    ) -> Result<(), Error> {
643        let x: AssignedNative<F> = self.convert(layouter, byte)?;
644        self.assert_equal_to_fixed(layouter, &x, F::from(constant as u64))
645    }
646
647    fn assert_not_equal_to_fixed(
648        &self,
649        layouter: &mut impl Layouter<F>,
650        byte: &AssignedByte<F>,
651        constant: u8,
652    ) -> Result<(), Error> {
653        let x: AssignedNative<F> = self.convert(layouter, byte)?;
654        self.assert_not_equal_to_fixed(layouter, &x, F::from(constant as u64))
655    }
656}
657
658/// The set of EqualityInstructions for bytes.
659impl<F, CoreDecomposition, NativeArith> EqualityInstructions<F, AssignedByte<F>>
660    for NativeGadget<F, CoreDecomposition, NativeArith>
661where
662    F: CircuitField,
663    CoreDecomposition: CoreDecompositionInstructions<F>,
664    NativeArith:
665        ArithInstructions<F, AssignedNative<F>> + EqualityInstructions<F, AssignedNative<F>>,
666{
667    fn is_equal(
668        &self,
669        layouter: &mut impl Layouter<F>,
670        byte1: &AssignedByte<F>,
671        byte2: &AssignedByte<F>,
672    ) -> Result<AssignedBit<F>, Error> {
673        let x1: AssignedNative<F> = self.convert(layouter, byte1)?;
674        let x2: AssignedNative<F> = self.convert(layouter, byte2)?;
675        self.native_chip.is_equal(layouter, &x1, &x2)
676    }
677
678    fn is_not_equal(
679        &self,
680        layouter: &mut impl Layouter<F>,
681        byte1: &AssignedByte<F>,
682        byte2: &AssignedByte<F>,
683    ) -> Result<AssignedBit<F>, Error> {
684        let x1: AssignedNative<F> = self.convert(layouter, byte1)?;
685        let x2: AssignedNative<F> = self.convert(layouter, byte2)?;
686        self.native_chip.is_not_equal(layouter, &x1, &x2)
687    }
688
689    fn is_equal_to_fixed(
690        &self,
691        layouter: &mut impl Layouter<F>,
692        byte: &AssignedByte<F>,
693        constant: u8,
694    ) -> Result<AssignedBit<F>, Error> {
695        let x: AssignedNative<F> = self.convert(layouter, byte)?;
696        self.native_chip.is_equal_to_fixed(layouter, &x, F::from(constant as u64))
697    }
698
699    fn is_not_equal_to_fixed(
700        &self,
701        layouter: &mut impl Layouter<F>,
702        byte: &AssignedByte<F>,
703        constant: u8,
704    ) -> Result<AssignedBit<F>, Error> {
705        let x: AssignedNative<F> = self.convert(layouter, byte)?;
706        self.native_chip.is_not_equal_to_fixed(layouter, &x, F::from(constant as u64))
707    }
708}
709
710impl<F: CircuitField, const N: usize> AssertionInstructions<F, [AssignedByte<F>; N]>
711    for NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>
712{
713    fn assert_equal(
714        &self,
715        layouter: &mut impl Layouter<F>,
716        x: &[AssignedByte<F>; N],
717        y: &[AssignedByte<F>; N],
718    ) -> Result<(), Error> {
719        x.iter().zip(y.iter()).try_for_each(|(x, y)| self.assert_equal(layouter, x, y))
720    }
721
722    fn assert_not_equal(
723        &self,
724        layouter: &mut impl Layouter<F>,
725        x: &[AssignedByte<F>; N],
726        y: &[AssignedByte<F>; N],
727    ) -> Result<(), Error> {
728        // TODO: This can be optimized by first aggregating as many bytes as possible in
729        // a single AssignedNative and only then comparing chunk-wise.
730        let xi_eq_yi = (x.iter())
731            .zip(y.iter())
732            .map(|(x, y)| self.is_equal(layouter, x, y))
733            .collect::<Result<Vec<_>, Error>>()?;
734        let all_equal = self.and(layouter, &xi_eq_yi)?;
735        self.assert_equal_to_fixed(layouter, &all_equal, false)
736    }
737
738    fn assert_equal_to_fixed(
739        &self,
740        layouter: &mut impl Layouter<F>,
741        x: &[AssignedByte<F>; N],
742        constant: [u8; N],
743    ) -> Result<(), Error> {
744        x.iter()
745            .zip(constant.iter())
746            .try_for_each(|(x, y)| self.assert_equal_to_fixed(layouter, x, *y))
747    }
748
749    fn assert_not_equal_to_fixed(
750        &self,
751        layouter: &mut impl Layouter<F>,
752        x: &[AssignedByte<F>; N],
753        constant: [u8; N],
754    ) -> Result<(), Error> {
755        // TODO: This can be optimized by first aggregating as many bytes as possible in
756        // a single AssignedNative and only then comparing chunk-wise.
757        let xi_eq_ci = (x.iter())
758            .zip(constant.iter())
759            .map(|(x, c)| self.is_equal_to_fixed(layouter, x, *c))
760            .collect::<Result<Vec<_>, Error>>()?;
761        let all_equal = self.and(layouter, &xi_eq_ci)?;
762        self.assert_equal_to_fixed(layouter, &all_equal, false)
763    }
764}
765
766/// Conversion from AssignedNative to AssignedByte.
767impl<F, CoreDecomposition, NativeArith>
768    ConversionInstructions<F, AssignedNative<F>, AssignedByte<F>>
769    for NativeGadget<F, CoreDecomposition, NativeArith>
770where
771    F: CircuitField,
772    CoreDecomposition: CoreDecompositionInstructions<F>,
773    NativeArith: ArithInstructions<F, AssignedNative<F>>,
774{
775    fn convert_value(&self, x: &F) -> Option<u8> {
776        let b_as_bn = x.to_biguint();
777        #[cfg(not(test))]
778        assert!(
779            b_as_bn <= BigUint::from(255u8),
780            "Trying to convert {:?} to AssignedByte in-circuit",
781            x
782        );
783        b_as_bn.to_bytes_le().first().cloned()
784    }
785
786    fn convert(
787        &self,
788        layouter: &mut impl Layouter<F>,
789        x: &AssignedNative<F>,
790    ) -> Result<AssignedByte<F>, Error> {
791        if let Some(current_bound) = self.constrained_cells.borrow().get(x) {
792            if *current_bound <= BigUint::from(256u32) {
793                return self.convert_unsafe(layouter, x);
794            }
795        }
796        self.update_bound(x, BigUint::from(256u32));
797        let b_value = x.value().map(|x| {
798            <Self as ConversionInstructions<_, _, AssignedByte<F>>>::convert_value(self, x)
799                .unwrap_or(0u8)
800        });
801        let b: AssignedByte<F> = self.assign(layouter, b_value)?;
802        self.assert_equal(layouter, x, &b.0)?;
803        Ok(b)
804    }
805}
806
807/// Conversion from AssignedByte to AssignedNative.
808impl<F, CoreDecomposition, NativeArith>
809    ConversionInstructions<F, AssignedByte<F>, AssignedNative<F>>
810    for NativeGadget<F, CoreDecomposition, NativeArith>
811where
812    F: CircuitField,
813    CoreDecomposition: CoreDecompositionInstructions<F>,
814    NativeArith: ArithInstructions<F, AssignedNative<F>>,
815{
816    fn convert_value(&self, x: &u8) -> Option<F> {
817        Some(F::from(*x as u64))
818    }
819
820    fn convert(
821        &self,
822        _layouter: &mut impl Layouter<F>,
823        byte: &AssignedByte<F>,
824    ) -> Result<AssignedNative<F>, Error> {
825        self.update_bound(&byte.0, BigUint::from(256u32));
826        Ok(byte.0.clone())
827    }
828}
829
830/// Unsafe conversion from AssignedNative to AssignedByte.
831impl<F, CoreDecomposition, NativeArith>
832    UnsafeConversionInstructions<F, AssignedNative<F>, AssignedByte<F>>
833    for NativeGadget<F, CoreDecomposition, NativeArith>
834where
835    F: CircuitField,
836    CoreDecomposition: CoreDecompositionInstructions<F>,
837    NativeArith: ArithInstructions<F, AssignedNative<F>>,
838{
839    /// CAUTION: use only if you know what you are doing!
840    ///
841    /// This function converts an `AssignedNative` to an `AssignedByte`
842    /// *without* adding any constraint to guarantee the "byteness" of the
843    /// assigned value.
844    ///
845    /// *It should be used only when the input x is already guaranteed to be a
846    /// byte*
847    fn convert_unsafe(
848        &self,
849        _layouter: &mut impl Layouter<F>,
850        x: &AssignedNative<F>,
851    ) -> Result<AssignedByte<F>, Error> {
852        #[cfg(not(test))]
853        x.value().map(|&x| {
854            let x = x.to_biguint();
855            assert!(
856                x <= BigUint::from(255u8),
857                "Trying to convert {:?} to an AssignedByte!",
858                x
859            );
860        });
861        Ok(AssignedByte(x.clone()))
862    }
863}
864
865/// The set of circuit instructions for decomposition operations.
866impl<F, CoreDecomposition, NativeArith> DecompositionInstructions<F, AssignedNative<F>>
867    for NativeGadget<F, CoreDecomposition, NativeArith>
868where
869    F: CircuitField,
870    CoreDecomposition: CoreDecompositionInstructions<F>,
871    NativeArith: ArithInstructions<F, AssignedNative<F>>
872        + ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>
873        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
874        + AssignmentInstructions<F, AssignedBit<F>>
875        + BinaryInstructions<F>
876        + EqualityInstructions<F, AssignedNative<F>>
877        + ControlFlowInstructions<F, AssignedNative<F>>,
878    NativeGadget<F, CoreDecomposition, NativeArith>: CanonicityInstructions<F, AssignedNative<F>>
879        + AssertionInstructions<F, AssignedBit<F>>
880        + AssignmentInstructions<F, AssignedBit<F>>
881        + BinaryInstructions<F>
882        + EqualityInstructions<F, AssignedNative<F>>
883        + ControlFlowInstructions<F, AssignedNative<F>>,
884{
885    fn assigned_to_le_bits(
886        &self,
887        layouter: &mut impl Layouter<F>,
888        x: &AssignedNative<F>,
889        nb_bits: Option<usize>,
890        enforce_canonical: bool,
891    ) -> Result<Vec<AssignedBit<F>>, Error> {
892        let nb_bits = nb_bits.unwrap_or(F::NUM_BITS as usize);
893
894        assert!(
895            nb_bits <= F::NUM_BITS as usize,
896            "assigned_to_le_bits: why do you need the output to have more bits than necessary?"
897        );
898
899        let limbs = self
900            .core_decomposition_chip
901            .decompose_fixed_limb_size(layouter, x, nb_bits, 1)?;
902        let bits = limbs
903            .iter()
904            .map(|x| self.native_chip.convert_unsafe(layouter, x))
905            .collect::<Result<Vec<_>, Error>>()?;
906        if enforce_canonical && nb_bits == F::NUM_BITS as usize {
907            debug_assert_eq!(F::modulus().bits(), F::NUM_BITS as u64);
908            // To enforce canonicity, we leverage the fact that `F::NUM_BITS` is tight:
909            // field elements have at most 2 representations as bitstrings of length
910            // `F::NUM_BITS`, and when 2 representations exist, they differ in the LSB
911            // (since the modulus is a large prime, which is odd).
912            // Thus, we can enforce canonicity by checking that the least significant bit of
913            // our output matches `sgn0(x)`.
914            // NB: `self.sgn0` is significantly more efficient than `self.is_canonical`.
915            let b0 = self.sgn0(layouter, x)?;
916            self.assert_equal(layouter, &bits[0], &b0)?;
917        }
918        Ok(bits)
919    }
920
921    fn assigned_to_le_bytes(
922        &self,
923        layouter: &mut impl Layouter<F>,
924        x: &AssignedNative<F>,
925        nb_bytes: Option<usize>,
926    ) -> Result<Vec<AssignedByte<F>>, Error> {
927        let f_num_bytes = F::NUM_BITS.div_ceil(8);
928        let nb_bytes = nb_bytes.unwrap_or(f_num_bytes as usize);
929        if nb_bytes > f_num_bytes as usize {
930            panic!("assigned_to_le_bytes: why do you need the output to have more bytes than necessary?");
931        }
932        // If nb_bytes equals ⌈F::NUM_BITS / 8⌉, we need extra care to
933        // guarantee that the output is canonical: we split in bits enforcing canonicity
934        // and then group the bits in bytes.
935        if nb_bytes == f_num_bytes as usize {
936            let bits = self.assigned_to_le_bits(layouter, x, Some(F::NUM_BITS as usize), true)?;
937            bits.chunks(8)
938                .map(|chunk| {
939                    let terms = chunk
940                        .iter()
941                        .enumerate()
942                        .map(|(i, bit)| (F::from(1 << i), bit.clone().into()))
943                        .collect::<Vec<_>>();
944                    let byte = self.linear_combination(layouter, &terms, F::ZERO)?;
945                    self.convert_unsafe(layouter, &byte)
946                })
947                .collect::<Result<Vec<AssignedByte<F>>, Error>>()
948        }
949        // If nb_bytes < ⌈F::NUM_BITS / 8⌉, wrap-arounds are not possible, so canonicity is always
950        // guaranteed. In this case we can split in bytes more efficiently.
951        else {
952            let limbs = self.core_decomposition_chip.decompose_fixed_limb_size(
953                layouter,
954                x,
955                8 * nb_bytes,
956                8,
957            )?;
958            limbs
959                .iter()
960                .map(|x| self.convert_unsafe(layouter, x))
961                .collect::<Result<Vec<_>, Error>>()
962        }
963    }
964
965    fn assigned_to_le_chunks(
966        &self,
967        layouter: &mut impl Layouter<F>,
968        x: &AssignedNative<F>,
969        nb_bits_per_chunk: usize,
970        nb_chunks: Option<usize>,
971    ) -> Result<Vec<AssignedNative<F>>, Error> {
972        assert!(nb_bits_per_chunk < F::NUM_BITS as usize);
973        let nb_chunks = nb_chunks.unwrap_or((F::NUM_BITS as usize).div_ceil(nb_bits_per_chunk));
974        self.core_decomposition_chip.decompose_fixed_limb_size(
975            layouter,
976            x,
977            nb_bits_per_chunk * nb_chunks,
978            nb_bits_per_chunk,
979        )
980    }
981
982    fn sgn0(
983        &self,
984        layouter: &mut impl Layouter<F>,
985        x: &AssignedNative<F>,
986    ) -> Result<AssignedBit<F>, Error> {
987        // Any element in Zp can be uniquely represented as 2 * w + e, where
988        // w in [0, (p-1)/2] and e in {0, 1}, with the exception of zero, which
989        // admits two representations: (w = 0, e = 0) and (w = (p-1)/2, e = 1).
990        let x_val = x.value().copied().map(|v| v.to_biguint());
991        let w_val = x_val.clone().map(|x| &x / BigUint::from(2u8));
992        let e_val = x_val.clone().map(|x| x.bit(0));
993
994        let e: AssignedBit<F> = self.assign(layouter, e_val)?;
995        let w = self.assign_lower_than_fixed(
996            layouter,
997            w_val.map(big_to_fe::<F>),
998            &(&(F::modulus() + BigUint::from(1u8)) / BigUint::from(2u8)),
999        )?;
1000        let must_be_x = self.linear_combination(
1001            layouter,
1002            &[(F::ONE, e.clone().into()), (F::from(2), w.clone())],
1003            F::ZERO,
1004        )?;
1005        self.assert_equal(layouter, x, &must_be_x)?;
1006        // The edge case x = 0 is no problem because `x_is_not_zero` is false in that
1007        // case and we will still assign sgn0(0) = 0.
1008        let x_is_zero: AssignedBit<F> = self.is_zero(layouter, x)?;
1009        let x_is_not_zero: AssignedBit<F> = self.not(layouter, &x_is_zero)?;
1010        let sgn0 = self.and(layouter, &[x_is_not_zero, e])?;
1011
1012        Ok(sgn0)
1013    }
1014}
1015
1016// Inherit F Public Input Instructions.
1017impl<F, CoreDecomposition, NativeArith> PublicInputInstructions<F, AssignedNative<F>>
1018    for NativeGadget<F, CoreDecomposition, NativeArith>
1019where
1020    F: CircuitField,
1021    CoreDecomposition: CoreDecompositionInstructions<F>,
1022    NativeArith:
1023        PublicInputInstructions<F, AssignedNative<F>> + ArithInstructions<F, AssignedNative<F>>,
1024{
1025    fn as_public_input(
1026        &self,
1027        layouter: &mut impl Layouter<F>,
1028        assigned: &AssignedNative<F>,
1029    ) -> Result<Vec<AssignedNative<F>>, Error> {
1030        self.native_chip.as_public_input(layouter, assigned)
1031    }
1032
1033    fn constrain_as_public_input(
1034        &self,
1035        layouter: &mut impl Layouter<F>,
1036        assigned: &AssignedNative<F>,
1037    ) -> Result<(), Error> {
1038        self.native_chip.constrain_as_public_input(layouter, assigned)
1039    }
1040
1041    fn assign_as_public_input(
1042        &self,
1043        layouter: &mut impl Layouter<F>,
1044        value: Value<F>,
1045    ) -> Result<AssignedNative<F>, Error> {
1046        self.native_chip.assign_as_public_input(layouter, value)
1047    }
1048}
1049
1050// Inherit F Assignment Instructions.
1051impl<F, CoreDecomposition, NativeArith> AssignmentInstructions<F, AssignedNative<F>>
1052    for NativeGadget<F, CoreDecomposition, NativeArith>
1053where
1054    F: CircuitField,
1055    CoreDecomposition: CoreDecompositionInstructions<F>,
1056    NativeArith: ArithInstructions<F, AssignedNative<F>>,
1057{
1058    fn assign(
1059        &self,
1060        layouter: &mut impl Layouter<F>,
1061        value: Value<F>,
1062    ) -> Result<AssignedNative<F>, Error> {
1063        self.native_chip.assign(layouter, value)
1064    }
1065
1066    fn assign_fixed(
1067        &self,
1068        layouter: &mut impl Layouter<F>,
1069        constant: F,
1070    ) -> Result<AssignedNative<F>, Error> {
1071        self.native_chip.assign_fixed(layouter, constant)
1072    }
1073
1074    fn assign_many(
1075        &self,
1076        layouter: &mut impl Layouter<F>,
1077        value: &[Value<F>],
1078    ) -> Result<Vec<AssignedNative<F>>, Error> {
1079        self.native_chip.assign_many(layouter, value)
1080    }
1081}
1082
1083// Inherit Bit Public Input Instructions.
1084impl<F, CoreDecomposition, NativeArith> PublicInputInstructions<F, AssignedBit<F>>
1085    for NativeGadget<F, CoreDecomposition, NativeArith>
1086where
1087    F: CircuitField,
1088    CoreDecomposition: CoreDecompositionInstructions<F>,
1089    NativeArith:
1090        PublicInputInstructions<F, AssignedBit<F>> + ArithInstructions<F, AssignedNative<F>>,
1091{
1092    fn as_public_input(
1093        &self,
1094        layouter: &mut impl Layouter<F>,
1095        assigned: &AssignedBit<F>,
1096    ) -> Result<Vec<AssignedNative<F>>, Error> {
1097        self.native_chip.as_public_input(layouter, assigned)
1098    }
1099
1100    fn constrain_as_public_input(
1101        &self,
1102        layouter: &mut impl Layouter<F>,
1103        assigned: &AssignedBit<F>,
1104    ) -> Result<(), Error> {
1105        self.native_chip.constrain_as_public_input(layouter, assigned)
1106    }
1107
1108    fn assign_as_public_input(
1109        &self,
1110        layouter: &mut impl Layouter<F>,
1111        value: Value<bool>,
1112    ) -> Result<AssignedBit<F>, Error> {
1113        self.native_chip.assign_as_public_input(layouter, value)
1114    }
1115}
1116
1117// Inherit Bit Assignment Instructions.
1118impl<F, CoreDecomposition, NativeArith> AssignmentInstructions<F, AssignedBit<F>>
1119    for NativeGadget<F, CoreDecomposition, NativeArith>
1120where
1121    F: CircuitField,
1122    CoreDecomposition: CoreDecompositionInstructions<F>,
1123    NativeArith: ArithInstructions<F, AssignedNative<F>>
1124        + AssignmentInstructions<F, AssignedBit<F>>
1125        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>,
1126{
1127    fn assign(
1128        &self,
1129        layouter: &mut impl Layouter<F>,
1130        value: Value<bool>,
1131    ) -> Result<AssignedBit<F>, Error> {
1132        self.native_chip.assign(layouter, value)
1133    }
1134
1135    fn assign_fixed(
1136        &self,
1137        layouter: &mut impl Layouter<F>,
1138        constant: bool,
1139    ) -> Result<AssignedBit<F>, Error> {
1140        self.native_chip.assign_fixed(layouter, constant)
1141    }
1142
1143    fn assign_many(
1144        &self,
1145        layouter: &mut impl Layouter<F>,
1146        values: &[Value<bool>],
1147    ) -> Result<Vec<AssignedBit<F>>, Error> {
1148        let values_as_f: Vec<_> = values.iter().map(|v| v.map(|b| F::from(b as u64))).collect();
1149
1150        self.core_decomposition_chip
1151            .assign_many_small(layouter, &values_as_f, 1)?
1152            .iter()
1153            .map(|assigned_native| self.native_chip.convert_unsafe(layouter, assigned_native))
1154            .collect()
1155    }
1156}
1157
1158// Inherit F Assertion Instructions.
1159impl<F, CoreDecomposition, NativeArith> AssertionInstructions<F, AssignedNative<F>>
1160    for NativeGadget<F, CoreDecomposition, NativeArith>
1161where
1162    F: CircuitField,
1163    CoreDecomposition: CoreDecompositionInstructions<F>,
1164    NativeArith: ArithInstructions<F, AssignedNative<F>>,
1165{
1166    fn assert_equal(
1167        &self,
1168        layouter: &mut impl Layouter<F>,
1169        x: &AssignedNative<F>,
1170        y: &AssignedNative<F>,
1171    ) -> Result<(), Error> {
1172        let x_bound_opt = self.constrained_cells.borrow().get(x).cloned();
1173        if let Some(x_bound) = x_bound_opt {
1174            self.update_bound(y, x_bound.clone());
1175        }
1176
1177        let y_bound_opt = self.constrained_cells.borrow().get(y).cloned();
1178        if let Some(y_bound) = y_bound_opt {
1179            self.update_bound(x, y_bound.clone());
1180        }
1181
1182        self.native_chip.assert_equal(layouter, x, y)
1183    }
1184
1185    fn assert_not_equal(
1186        &self,
1187        layouter: &mut impl Layouter<F>,
1188        x: &AssignedNative<F>,
1189        y: &AssignedNative<F>,
1190    ) -> Result<(), Error> {
1191        self.native_chip.assert_not_equal(layouter, x, y)
1192    }
1193
1194    fn assert_equal_to_fixed(
1195        &self,
1196        layouter: &mut impl Layouter<F>,
1197        x: &AssignedNative<F>,
1198        constant: F,
1199    ) -> Result<(), Error> {
1200        self.native_chip.assert_equal_to_fixed(layouter, x, constant)
1201    }
1202
1203    fn assert_not_equal_to_fixed(
1204        &self,
1205        layouter: &mut impl Layouter<F>,
1206        x: &AssignedNative<F>,
1207        constant: F,
1208    ) -> Result<(), Error> {
1209        self.native_chip.assert_not_equal_to_fixed(layouter, x, constant)
1210    }
1211}
1212
1213// Inherit Bit Assertion Instructions.
1214impl<F, CoreDecomposition, NativeArith> AssertionInstructions<F, AssignedBit<F>>
1215    for NativeGadget<F, CoreDecomposition, NativeArith>
1216where
1217    F: CircuitField,
1218    CoreDecomposition: CoreDecompositionInstructions<F>,
1219    NativeArith: ArithInstructions<F, AssignedNative<F>> + AssertionInstructions<F, AssignedBit<F>>,
1220{
1221    fn assert_equal(
1222        &self,
1223        layouter: &mut impl Layouter<F>,
1224        x: &AssignedBit<F>,
1225        y: &AssignedBit<F>,
1226    ) -> Result<(), Error> {
1227        self.native_chip.assert_equal(layouter, x, y)
1228    }
1229
1230    fn assert_not_equal(
1231        &self,
1232        layouter: &mut impl Layouter<F>,
1233        x: &AssignedBit<F>,
1234        y: &AssignedBit<F>,
1235    ) -> Result<(), Error> {
1236        self.native_chip.assert_not_equal(layouter, x, y)
1237    }
1238
1239    fn assert_equal_to_fixed(
1240        &self,
1241        layouter: &mut impl Layouter<F>,
1242        x: &AssignedBit<F>,
1243        constant: bool,
1244    ) -> Result<(), Error> {
1245        self.native_chip.assert_equal_to_fixed(layouter, x, constant)
1246    }
1247
1248    fn assert_not_equal_to_fixed(
1249        &self,
1250        layouter: &mut impl Layouter<F>,
1251        x: &AssignedBit<F>,
1252        constant: bool,
1253    ) -> Result<(), Error> {
1254        self.native_chip.assert_not_equal_to_fixed(layouter, x, constant)
1255    }
1256}
1257
1258// Inherit Arith Instructions.
1259impl<F, CoreDecomposition, NativeArith> ArithInstructions<F, AssignedNative<F>>
1260    for NativeGadget<F, CoreDecomposition, NativeArith>
1261where
1262    F: CircuitField,
1263    CoreDecomposition: CoreDecompositionInstructions<F>,
1264    NativeArith: ArithInstructions<F, AssignedNative<F>>,
1265{
1266    fn linear_combination(
1267        &self,
1268        layouter: &mut impl Layouter<F>,
1269        terms: &[(F, AssignedNative<F>)],
1270        constant: F,
1271    ) -> Result<AssignedNative<F>, Error> {
1272        self.native_chip.linear_combination(layouter, terms, constant)
1273    }
1274
1275    fn mul(
1276        &self,
1277        layouter: &mut impl Layouter<F>,
1278        x: &AssignedNative<F>,
1279        y: &AssignedNative<F>,
1280        multiplying_constant: Option<F>,
1281    ) -> Result<AssignedNative<F>, Error> {
1282        self.native_chip.mul(layouter, x, y, multiplying_constant)
1283    }
1284
1285    fn div(
1286        &self,
1287        layouter: &mut impl Layouter<F>,
1288        x: &AssignedNative<F>,
1289        y: &AssignedNative<F>,
1290    ) -> Result<AssignedNative<F>, Error> {
1291        self.native_chip.div(layouter, x, y)
1292    }
1293
1294    fn inv(
1295        &self,
1296        layouter: &mut impl Layouter<F>,
1297        x: &AssignedNative<F>,
1298    ) -> Result<AssignedNative<F>, Error> {
1299        self.native_chip.inv(layouter, x)
1300    }
1301
1302    fn inv0(
1303        &self,
1304        layouter: &mut impl Layouter<F>,
1305        x: &AssignedNative<F>,
1306    ) -> Result<AssignedNative<F>, Error> {
1307        self.native_chip.inv0(layouter, x)
1308    }
1309
1310    fn add_and_mul(
1311        &self,
1312        layouter: &mut impl Layouter<F>,
1313        a_and_x: (F, &AssignedNative<F>),
1314        b_and_y: (F, &AssignedNative<F>),
1315        c_and_z: (F, &AssignedNative<F>),
1316        k: F,
1317        m: F,
1318    ) -> Result<AssignedNative<F>, Error> {
1319        self.native_chip.add_and_mul(layouter, a_and_x, b_and_y, c_and_z, k, m)
1320    }
1321
1322    fn add_constants(
1323        &self,
1324        layouter: &mut impl Layouter<F>,
1325        xs: &[AssignedNative<F>],
1326        constants: &[F],
1327    ) -> Result<Vec<AssignedNative<F>>, Error> {
1328        self.native_chip.add_constants(layouter, xs, constants)
1329    }
1330}
1331
1332// Inherit Conversion Instructions to AssignedBit.
1333impl<F, CoreDecomposition, NativeArith> ConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
1334    for NativeGadget<F, CoreDecomposition, NativeArith>
1335where
1336    F: CircuitField,
1337    CoreDecomposition: CoreDecompositionInstructions<F>,
1338    NativeArith: ArithInstructions<F, AssignedNative<F>>
1339        + ConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
1340        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>,
1341{
1342    fn convert_value(&self, x: &F) -> Option<bool> {
1343        self.native_chip.convert_value(x)
1344    }
1345
1346    fn convert(
1347        &self,
1348        layouter: &mut impl Layouter<F>,
1349        x: &AssignedNative<F>,
1350    ) -> Result<AssignedBit<F>, Error> {
1351        if let Some(current_bound) = self.constrained_cells.borrow().get(x) {
1352            if *current_bound <= BigUint::from(2u32) {
1353                return self.native_chip.convert_unsafe(layouter, x);
1354            }
1355        }
1356        self.update_bound(x, BigUint::from(2u32));
1357        self.native_chip.convert(layouter, x)
1358    }
1359}
1360
1361// Inherit Conversion Instructions from AssignedBit.
1362impl<F, CoreDecomposition, NativeArith> ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>
1363    for NativeGadget<F, CoreDecomposition, NativeArith>
1364where
1365    F: CircuitField,
1366    CoreDecomposition: CoreDecompositionInstructions<F>,
1367    NativeArith: ArithInstructions<F, AssignedNative<F>>
1368        + ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>,
1369{
1370    fn convert_value(&self, x: &bool) -> Option<F> {
1371        self.native_chip.convert_value(x)
1372    }
1373
1374    fn convert(
1375        &self,
1376        layouter: &mut impl Layouter<F>,
1377        x: &AssignedBit<F>,
1378    ) -> Result<AssignedNative<F>, Error> {
1379        let x = self.native_chip.convert(layouter, x)?;
1380        self.update_bound(&x, BigUint::from(2u32));
1381        Ok(x)
1382    }
1383}
1384
1385// Inherit Binary Instructions.
1386impl<F, CoreDecomposition, NativeArith> BinaryInstructions<F>
1387    for NativeGadget<F, CoreDecomposition, NativeArith>
1388where
1389    F: CircuitField,
1390    CoreDecomposition: CoreDecompositionInstructions<F>,
1391    NativeArith: ArithInstructions<F, AssignedNative<F>> + BinaryInstructions<F>,
1392{
1393    fn and(
1394        &self,
1395        layouter: &mut impl Layouter<F>,
1396        bits: &[AssignedBit<F>],
1397    ) -> Result<AssignedBit<F>, Error> {
1398        self.native_chip.and(layouter, bits)
1399    }
1400
1401    fn or(
1402        &self,
1403        layouter: &mut impl Layouter<F>,
1404        bits: &[AssignedBit<F>],
1405    ) -> Result<AssignedBit<F>, Error> {
1406        self.native_chip.or(layouter, bits)
1407    }
1408
1409    fn xor(
1410        &self,
1411        layouter: &mut impl Layouter<F>,
1412        bits: &[AssignedBit<F>],
1413    ) -> Result<AssignedBit<F>, Error> {
1414        self.native_chip.xor(layouter, bits)
1415    }
1416
1417    fn not(
1418        &self,
1419        layouter: &mut impl Layouter<F>,
1420        b: &AssignedBit<F>,
1421    ) -> Result<AssignedBit<F>, Error> {
1422        self.native_chip.not(layouter, b)
1423    }
1424}
1425
1426// Inherit F Equality Instructions.
1427impl<F, CoreDecomposition, NativeArith> EqualityInstructions<F, AssignedNative<F>>
1428    for NativeGadget<F, CoreDecomposition, NativeArith>
1429where
1430    F: CircuitField,
1431    CoreDecomposition: CoreDecompositionInstructions<F>,
1432    NativeArith:
1433        ArithInstructions<F, AssignedNative<F>> + EqualityInstructions<F, AssignedNative<F>>,
1434{
1435    fn is_equal(
1436        &self,
1437        layouter: &mut impl Layouter<F>,
1438        x: &AssignedNative<F>,
1439        y: &AssignedNative<F>,
1440    ) -> Result<AssignedBit<F>, Error> {
1441        self.native_chip.is_equal(layouter, x, y)
1442    }
1443
1444    fn is_not_equal(
1445        &self,
1446        layouter: &mut impl Layouter<F>,
1447        x: &AssignedNative<F>,
1448        y: &AssignedNative<F>,
1449    ) -> Result<AssignedBit<F>, Error> {
1450        self.native_chip.is_not_equal(layouter, x, y)
1451    }
1452
1453    fn is_equal_to_fixed(
1454        &self,
1455        layouter: &mut impl Layouter<F>,
1456        x: &AssignedNative<F>,
1457        constant: F,
1458    ) -> Result<AssignedBit<F>, Error> {
1459        self.native_chip.is_equal_to_fixed(layouter, x, constant)
1460    }
1461
1462    fn is_not_equal_to_fixed(
1463        &self,
1464        layouter: &mut impl Layouter<F>,
1465        x: &AssignedNative<F>,
1466        constant: F,
1467    ) -> Result<AssignedBit<F>, Error> {
1468        self.native_chip.is_not_equal_to_fixed(layouter, x, constant)
1469    }
1470}
1471
1472// Implement Bit Equality Instructions.
1473impl<F, CoreDecomposition, NativeArith> EqualityInstructions<F, AssignedBit<F>>
1474    for NativeGadget<F, CoreDecomposition, NativeArith>
1475where
1476    F: CircuitField,
1477    CoreDecomposition: CoreDecompositionInstructions<F>,
1478    NativeArith: ArithInstructions<F, AssignedNative<F>> + EqualityInstructions<F, AssignedBit<F>>,
1479{
1480    fn is_equal(
1481        &self,
1482        layouter: &mut impl Layouter<F>,
1483        x: &AssignedBit<F>,
1484        y: &AssignedBit<F>,
1485    ) -> Result<AssignedBit<F>, Error> {
1486        self.native_chip.is_equal(layouter, x, y)
1487    }
1488
1489    fn is_not_equal(
1490        &self,
1491        layouter: &mut impl Layouter<F>,
1492        x: &AssignedBit<F>,
1493        y: &AssignedBit<F>,
1494    ) -> Result<AssignedBit<F>, Error> {
1495        self.native_chip.is_not_equal(layouter, x, y)
1496    }
1497
1498    fn is_equal_to_fixed(
1499        &self,
1500        layouter: &mut impl Layouter<F>,
1501        x: &AssignedBit<F>,
1502        constant: bool,
1503    ) -> Result<AssignedBit<F>, Error> {
1504        self.native_chip.is_equal_to_fixed(layouter, x, constant)
1505    }
1506
1507    fn is_not_equal_to_fixed(
1508        &self,
1509        layouter: &mut impl Layouter<F>,
1510        x: &AssignedBit<F>,
1511        constant: bool,
1512    ) -> Result<AssignedBit<F>, Error> {
1513        self.native_chip.is_not_equal_to_fixed(layouter, x, constant)
1514    }
1515}
1516
1517// Inherit Zero Instructions.
1518impl<F, CoreDecomposition, NativeArith> ZeroInstructions<F, AssignedNative<F>>
1519    for NativeGadget<F, CoreDecomposition, NativeArith>
1520where
1521    F: CircuitField,
1522    CoreDecomposition: CoreDecompositionInstructions<F>,
1523    NativeArith: ArithInstructions<F, AssignedNative<F>>
1524        + AssertionInstructions<F, AssignedNative<F>>
1525        + EqualityInstructions<F, AssignedNative<F>>,
1526{
1527}
1528
1529// Inherit F ControlFlow Instructions.
1530impl<F, CoreDecomposition, NativeArith> ControlFlowInstructions<F, AssignedNative<F>>
1531    for NativeGadget<F, CoreDecomposition, NativeArith>
1532where
1533    F: CircuitField,
1534    CoreDecomposition: CoreDecompositionInstructions<F>,
1535    NativeArith:
1536        ArithInstructions<F, AssignedNative<F>> + ControlFlowInstructions<F, AssignedNative<F>>,
1537{
1538    fn select(
1539        &self,
1540        layouter: &mut impl Layouter<F>,
1541        bit: &AssignedBit<F>,
1542        x: &AssignedNative<F>,
1543        y: &AssignedNative<F>,
1544    ) -> Result<AssignedNative<F>, Error> {
1545        self.native_chip.select(layouter, bit, x, y)
1546    }
1547
1548    fn cond_swap(
1549        &self,
1550        layouter: &mut impl Layouter<F>,
1551        cond: &AssignedBit<F>,
1552        x: &AssignedNative<F>,
1553        y: &AssignedNative<F>,
1554    ) -> Result<(AssignedNative<F>, AssignedNative<F>), Error> {
1555        self.native_chip.cond_swap(layouter, cond, x, y)
1556    }
1557}
1558
1559// Inherit Bit ControlFlow Instructions.
1560impl<F, CoreDecomposition, NativeArith> ControlFlowInstructions<F, AssignedBit<F>>
1561    for NativeGadget<F, CoreDecomposition, NativeArith>
1562where
1563    F: CircuitField,
1564    CoreDecomposition: CoreDecompositionInstructions<F>,
1565    NativeArith:
1566        ArithInstructions<F, AssignedNative<F>> + ControlFlowInstructions<F, AssignedBit<F>>,
1567{
1568    fn select(
1569        &self,
1570        layouter: &mut impl Layouter<F>,
1571        bit: &AssignedBit<F>,
1572        x: &AssignedBit<F>,
1573        y: &AssignedBit<F>,
1574    ) -> Result<AssignedBit<F>, Error> {
1575        self.native_chip.select(layouter, bit, x, y)
1576    }
1577
1578    fn cond_swap(
1579        &self,
1580        layouter: &mut impl Layouter<F>,
1581        bit: &AssignedBit<F>,
1582        x: &AssignedBit<F>,
1583        y: &AssignedBit<F>,
1584    ) -> Result<(AssignedBit<F>, AssignedBit<F>), Error> {
1585        self.native_chip.cond_swap(layouter, bit, x, y)
1586    }
1587}
1588
1589// Implement Byte ControlFlow Instructions.
1590impl<F, CoreDecomposition, NativeArith> ControlFlowInstructions<F, AssignedByte<F>>
1591    for NativeGadget<F, CoreDecomposition, NativeArith>
1592where
1593    F: CircuitField,
1594    CoreDecomposition: CoreDecompositionInstructions<F>,
1595    NativeArith:
1596        ArithInstructions<F, AssignedNative<F>> + ControlFlowInstructions<F, AssignedNative<F>>,
1597{
1598    fn select(
1599        &self,
1600        layouter: &mut impl Layouter<F>,
1601        bit: &AssignedBit<F>,
1602        x: &AssignedByte<F>,
1603        y: &AssignedByte<F>,
1604    ) -> Result<AssignedByte<F>, Error> {
1605        let byte = self.native_chip.select(layouter, bit, &x.into(), &y.into())?;
1606        self.convert_unsafe(layouter, &byte)
1607    }
1608
1609    fn cond_swap(
1610        &self,
1611        layouter: &mut impl Layouter<F>,
1612        cond: &AssignedBit<F>,
1613        x: &AssignedByte<F>,
1614        y: &AssignedByte<F>,
1615    ) -> Result<(AssignedByte<F>, AssignedByte<F>), Error> {
1616        let (fst, snd) = (self.native_chip).cond_swap(layouter, cond, &x.into(), &y.into())?;
1617        let fst = self.convert_unsafe(layouter, &fst)?;
1618        let snd = self.convert_unsafe(layouter, &snd)?;
1619        Ok((fst, snd))
1620    }
1621}
1622
1623// Inherit Field Instructions.
1624impl<F, CoreDecomposition, NativeArith> FieldInstructions<F, AssignedNative<F>>
1625    for NativeGadget<F, CoreDecomposition, NativeArith>
1626where
1627    F: CircuitField,
1628    CoreDecomposition: CoreDecompositionInstructions<F>,
1629    NativeArith: FieldInstructions<F, AssignedNative<F>>
1630        + AssertionInstructions<F, AssignedBit<F>>
1631        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
1632        + EqualityInstructions<F, AssignedBit<F>>,
1633{
1634    fn order(&self) -> BigUint {
1635        self.native_chip.order()
1636    }
1637}
1638
1639// Implement Scalar Field Instructions.
1640impl<F, CoreDecomposition, NativeArith> ScalarFieldInstructions<F>
1641    for NativeGadget<F, CoreDecomposition, NativeArith>
1642where
1643    F: CircuitField,
1644    CoreDecomposition: CoreDecompositionInstructions<F>,
1645    NativeArith: CanonicityInstructions<F, AssignedNative<F>>
1646        + AssertionInstructions<F, AssignedBit<F>>
1647        + EqualityInstructions<F, AssignedBit<F>>
1648        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
1649        + ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>
1650        + AssignmentInstructions<F, AssignedBit<F>>
1651        + BinaryInstructions<F>,
1652{
1653    type Scalar = AssignedNative<F>;
1654}
1655
1656// Inherit Canonicity Instructions.
1657impl<F, CoreDecomposition, NativeArith> CanonicityInstructions<F, AssignedNative<F>>
1658    for NativeGadget<F, CoreDecomposition, NativeArith>
1659where
1660    F: CircuitField,
1661    CoreDecomposition: CoreDecompositionInstructions<F>,
1662    NativeArith: CanonicityInstructions<F, AssignedNative<F>>
1663        + AssertionInstructions<F, AssignedBit<F>>
1664        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
1665        + EqualityInstructions<F, AssignedBit<F>>,
1666{
1667    fn le_bits_lower_than(
1668        &self,
1669        layouter: &mut impl Layouter<F>,
1670        bits: &[AssignedBit<F>],
1671        bound: BigUint,
1672    ) -> Result<AssignedBit<F>, Error> {
1673        self.native_chip.le_bits_lower_than(layouter, bits, bound)
1674    }
1675
1676    fn le_bits_geq_than(
1677        &self,
1678        layouter: &mut impl Layouter<F>,
1679        bits: &[AssignedBit<F>],
1680        bound: BigUint,
1681    ) -> Result<AssignedBit<F>, Error> {
1682        self.native_chip.le_bits_geq_than(layouter, bits, bound)
1683    }
1684}
1685
1686// Implement Native Instructions.
1687impl<F, CoreDecomposition, NativeArith> NativeInstructions<F>
1688    for NativeGadget<F, CoreDecomposition, NativeArith>
1689where
1690    F: CircuitField,
1691    CoreDecomposition: CoreDecompositionInstructions<F>,
1692    NativeArith: CanonicityInstructions<F, AssignedNative<F>>
1693        + AssertionInstructions<F, AssignedBit<F>>
1694        + EqualityInstructions<F, AssignedBit<F>>
1695        + ControlFlowInstructions<F, AssignedBit<F>>
1696        + BinaryInstructions<F>
1697        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
1698        + ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>,
1699{
1700}
1701
1702// Implement Bitwise Instructions.
1703impl<F, CoreDecomposition, NativeArith> BitwiseInstructions<F, AssignedNative<F>>
1704    for NativeGadget<F, CoreDecomposition, NativeArith>
1705where
1706    F: CircuitField,
1707    CoreDecomposition: CoreDecompositionInstructions<F>,
1708    NativeArith: CanonicityInstructions<F, AssignedNative<F>>
1709        + AssertionInstructions<F, AssignedBit<F>>
1710        + EqualityInstructions<F, AssignedBit<F>>
1711        + ControlFlowInstructions<F, AssignedBit<F>>
1712        + BinaryInstructions<F>
1713        + UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>>
1714        + ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>>,
1715{
1716}
1717
1718// Circuit implementation for NativeGadget based on the
1719// P2RDecompositionChip and the NativeChip
1720#[cfg(any(test, feature = "testing"))]
1721impl<F: CircuitField> FromScratch<F> for NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>> {
1722    // The circuit config is simply the P2RDecompositionConfig since this contains a
1723    // NativeConfig as well
1724    type Config = P2RDecompositionConfig;
1725
1726    fn new_from_scratch(config: &Self::Config) -> Self {
1727        let max_bit_len = 8;
1728        let native_chip = NativeChip::new_from_scratch(&config.native_config);
1729        let core_decomposition_chip = P2RDecompositionChip::new(config, &max_bit_len);
1730        NativeGadget::new(core_decomposition_chip, native_chip)
1731    }
1732
1733    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
1734        self.native_chip.load_from_scratch(layouter)?;
1735        self.core_decomposition_chip.load(layouter)
1736    }
1737
1738    fn configure_from_scratch(
1739        meta: &mut ConstraintSystem<F>,
1740        advice_columns: &mut Vec<Column<Advice>>,
1741        fixed_columns: &mut Vec<Column<Fixed>>,
1742        instance_columns: &[Column<Instance>; 2],
1743    ) -> Self::Config {
1744        while advice_columns.len() < NB_ARITH_COLS {
1745            advice_columns.push(meta.advice_column());
1746        }
1747        while fixed_columns.len() < NB_ARITH_FIXED_COLS {
1748            fixed_columns.push(meta.fixed_column());
1749        }
1750        let advice_cols: [_; NB_ARITH_COLS] = advice_columns[..NB_ARITH_COLS].try_into().unwrap();
1751        let fixed_cols: [_; NB_ARITH_FIXED_COLS] =
1752            fixed_columns[..NB_ARITH_FIXED_COLS].try_into().unwrap();
1753
1754        let native_config =
1755            NativeChip::configure(meta, &(advice_cols, fixed_cols, *instance_columns));
1756        // Use hard-coded value for nr of range check cols in test
1757        let pow2range_config = Pow2RangeChip::configure(meta, &advice_cols[1..=4]);
1758
1759        P2RDecompositionConfig {
1760            native_config,
1761            pow2range_config,
1762        }
1763    }
1764}
1765
1766#[cfg(test)]
1767mod tests {
1768    use midnight_curves::Fq as BlsScalar;
1769
1770    use super::*;
1771    use crate::instructions::{bitwise, comparison, decomposition, division, range_check};
1772
1773    macro_rules! test {
1774        ($module:ident, $operation:ident) => {
1775            #[test]
1776            fn $operation() {
1777                $module::tests::$operation::<
1778                    BlsScalar,
1779                    AssignedNative<BlsScalar>,
1780                    NativeGadget<BlsScalar, P2RDecompositionChip<BlsScalar>, NativeChip<BlsScalar>>,
1781                    NativeGadget<BlsScalar, P2RDecompositionChip<BlsScalar>, NativeChip<BlsScalar>>,
1782                >("native_gadget_bls");
1783            }
1784        };
1785    }
1786
1787    test!(decomposition, test_bit_decomposition);
1788    test!(decomposition, test_byte_decomposition);
1789    test!(decomposition, test_sgn0);
1790
1791    macro_rules! test {
1792        ($module:ident, $operation:ident) => {
1793            #[test]
1794            fn $operation() {
1795                $module::tests::$operation::<
1796                    BlsScalar,
1797                    AssignedNative<BlsScalar>,
1798                    NativeGadget<BlsScalar, P2RDecompositionChip<BlsScalar>, NativeChip<BlsScalar>>,
1799                >("native_gadget_bls");
1800            }
1801        };
1802    }
1803
1804    test!(comparison, test_lower_and_greater);
1805    test!(comparison, test_assert_bounded_element);
1806    test!(range_check, test_assert_lower_than_fixed);
1807    test!(division, test_div_rem);
1808
1809    test!(bitwise, test_band);
1810    test!(bitwise, test_bor);
1811    test!(bitwise, test_bxor);
1812    test!(bitwise, test_bnot);
1813}