arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/circuits/nonnative.rs
folding-schemes/src/folding/circuits/nonnative.rsRaw
  1use ark_ec::{AffineRepr, CurveGroup};
  2use ark_ff::{BigInteger, PrimeField};
  3use ark_r1cs_std::{
  4    alloc::{AllocVar, AllocationMode},
  5    boolean::Boolean,
  6    fields::{
  7        fp::FpVar,
  8        nonnative::{
  9            params::{get_params, OptimizationType},
 10            AllocatedNonNativeFieldVar, NonNativeFieldVar,
 11        },
 12        FieldVar,
 13    },
 14    ToBitsGadget, ToConstraintFieldGadget,
 15};
 16use ark_relations::r1cs::{ConstraintSystemRef, Namespace, OptimizationGoal, SynthesisError};
 17use ark_std::Zero;
 18use core::borrow::Borrow;
 19use std::marker::PhantomData;
 20
 21/// Compose a vector boolean into a `NonNativeFieldVar`
 22pub fn nonnative_field_var_from_le_bits<TargetField: PrimeField, BaseField: PrimeField>(
 23    cs: ConstraintSystemRef<BaseField>,
 24    bits: &[Boolean<BaseField>],
 25) -> Result<NonNativeFieldVar<TargetField, BaseField>, SynthesisError> {
 26    let params = get_params(
 27        TargetField::MODULUS_BIT_SIZE as usize,
 28        BaseField::MODULUS_BIT_SIZE as usize,
 29        match cs.optimization_goal() {
 30            OptimizationGoal::None => OptimizationType::Constraints,
 31            OptimizationGoal::Constraints => OptimizationType::Constraints,
 32            OptimizationGoal::Weight => OptimizationType::Weight,
 33        },
 34    );
 35
 36    // push the lower limbs first
 37    let mut limbs = bits
 38        .chunks(params.bits_per_limb)
 39        .map(Boolean::le_bits_to_fp_var)
 40        .collect::<Result<Vec<_>, _>>()?;
 41    limbs.resize(params.num_limbs, FpVar::zero());
 42    limbs.reverse();
 43
 44    Ok(AllocatedNonNativeFieldVar {
 45        cs,
 46        limbs,
 47        num_of_additions_over_normal_form: BaseField::one(),
 48        is_in_the_normal_form: false,
 49        target_phantom: PhantomData,
 50    }
 51    .into())
 52}
 53
 54/// A more efficient version of `NonNativeFieldVar::to_constraint_field`
 55pub fn nonnative_field_var_to_constraint_field<TargetField: PrimeField, BaseField: PrimeField>(
 56    f: &NonNativeFieldVar<TargetField, BaseField>,
 57) -> Result<Vec<FpVar<BaseField>>, SynthesisError> {
 58    let bits = f.to_bits_le()?;
 59
 60    let bits_per_limb = BaseField::MODULUS_BIT_SIZE as usize - 1;
 61    let num_limbs = (TargetField::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb);
 62
 63    let mut limbs = bits
 64        .chunks(bits_per_limb)
 65        .map(|chunk| {
 66            let mut limb = FpVar::<BaseField>::zero();
 67            let mut w = BaseField::one();
 68            for b in chunk.iter() {
 69                limb += FpVar::from(b.clone()) * w;
 70                w.double_in_place();
 71            }
 72            limb
 73        })
 74        .collect::<Vec<FpVar<BaseField>>>();
 75    limbs.resize(num_limbs, FpVar::zero());
 76    limbs.reverse();
 77
 78    Ok(limbs)
 79}
 80
 81/// The out-circuit counterpart of `nonnative_field_var_to_constraint_field`
 82pub fn nonnative_field_to_field_elements<TargetField: PrimeField, BaseField: PrimeField>(
 83    f: &TargetField,
 84) -> Vec<BaseField> {
 85    let bits = f.into_bigint().to_bits_le();
 86
 87    let bits_per_limb = BaseField::MODULUS_BIT_SIZE as usize - 1;
 88    let num_limbs = (TargetField::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb);
 89
 90    let mut limbs = bits
 91        .chunks(bits_per_limb)
 92        .map(|chunk| {
 93            let mut limb = BaseField::zero();
 94            let mut w = BaseField::one();
 95            for &b in chunk.iter() {
 96                limb += BaseField::from(b) * w;
 97                w.double_in_place();
 98            }
 99            limb
100        })
101        .collect::<Vec<BaseField>>();
102    limbs.resize(num_limbs, BaseField::zero());
103    limbs.reverse();
104
105    limbs
106}
107
108/// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native
109/// field, over the constraint field. It is not intended to perform operations, but just to contain
110/// the affine coordinates in order to perform hash operations of the point.
111#[derive(Debug, Clone)]
112pub struct NonNativeAffineVar<C: CurveGroup>
113where
114    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
115{
116    pub x: NonNativeFieldVar<C::BaseField, C::ScalarField>,
117    pub y: NonNativeFieldVar<C::BaseField, C::ScalarField>,
118}
119
120impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C>
121where
122    C: CurveGroup,
123    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
124{
125    fn new_variable<T: Borrow<C>>(
126        cs: impl Into<Namespace<C::ScalarField>>,
127        f: impl FnOnce() -> Result<T, SynthesisError>,
128        mode: AllocationMode,
129    ) -> Result<Self, SynthesisError> {
130        f().and_then(|val| {
131            let cs = cs.into();
132
133            let affine = val.borrow().into_affine();
134            let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
135            let xy = affine.xy().unwrap_or(zero_point);
136
137            let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
138                cs.clone(),
139                || Ok(xy.0),
140                mode,
141            )?;
142            let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
143                cs.clone(),
144                || Ok(xy.1),
145                mode,
146            )?;
147
148            Ok(Self { x, y })
149        })
150    }
151}
152
153impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C>
154where
155    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
156{
157    // A more efficient version of `point_to_nonnative_limbs_custom_opt`.
158    // Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in
159    // the circuit.
160    fn to_constraint_field(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> {
161        let x = nonnative_field_var_to_constraint_field(&self.x)?;
162        let y = nonnative_field_var_to_constraint_field(&self.y)?;
163        Ok([x, y].concat())
164    }
165}
166
167/// The out-circuit counterpart of `NonNativeAffineVar::to_constraint_field`
168#[allow(clippy::type_complexity)]
169pub fn nonnative_affine_to_field_elements<C: CurveGroup>(
170    p: C,
171) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
172where
173    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
174{
175    let affine = p.into_affine();
176    if affine.is_zero() {
177        let x = nonnative_field_to_field_elements(&C::BaseField::zero());
178        let y = nonnative_field_to_field_elements(&C::BaseField::zero());
179        return Ok((x, y));
180    }
181
182    let (x, y) = affine.xy().unwrap();
183    let x = nonnative_field_to_field_elements(x);
184    let y = nonnative_field_to_field_elements(y);
185    Ok((x, y))
186}
187
188impl<C: CurveGroup> NonNativeAffineVar<C>
189where
190    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
191{
192    // A wrapper of `point_to_nonnative_limbs_custom_opt` with constraints-focused optimization
193    // type (which is the default optimization type for arkworks' Groth16).
194    // Used for extracting a list of field elements of type `C::ScalarField` from the public input
195    // `p`, in exactly the same way as how `NonNativeAffineVar` is represented as limbs of type
196    // `FpVar` in-circuit.
197    #[allow(clippy::type_complexity)]
198    pub fn inputize(p: C) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> {
199        point_to_nonnative_limbs_custom_opt(p, OptimizationType::Constraints)
200    }
201}
202
203// Used to compute (outside the circuit) the limbs representation of a point.
204// For `OptimizationType::Constraints`, the result matches the one used in-circuit.
205// For `OptimizationType::Weight`, the result vector is more dense and is suitable for hashing.
206// It is possible to further optimize the length of the result vector (see
207// `nonnative_affine_to_field_elements`)
208#[allow(clippy::type_complexity)]
209fn point_to_nonnative_limbs_custom_opt<C: CurveGroup>(
210    p: C,
211    optimization_type: OptimizationType,
212) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
213where
214    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
215{
216    let affine = p.into_affine();
217    if affine.is_zero() {
218        let x =
219            AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
220                &C::BaseField::zero(),
221                optimization_type,
222            )?;
223        let y =
224            AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
225                &C::BaseField::zero(),
226                optimization_type,
227            )?;
228        return Ok((x, y));
229    }
230
231    let (x, y) = affine.xy().unwrap();
232    let x = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
233        x,
234        optimization_type,
235    )?;
236    let y = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
237        y,
238        optimization_type,
239    )?;
240    Ok((x, y))
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use ark_pallas::{Fr, Projective};
247    use ark_r1cs_std::R1CSVar;
248    use ark_relations::r1cs::ConstraintSystem;
249    use ark_std::UniformRand;
250
251    #[test]
252    fn test_alloc_zero() {
253        let cs = ConstraintSystem::<Fr>::new_ref();
254
255        // dealing with the 'zero' point should not panic when doing the unwrap
256        let p = Projective::zero();
257        assert!(NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).is_ok());
258    }
259
260    #[test]
261    fn test_arkworks_to_constraint_field() {
262        let cs = ConstraintSystem::<Fr>::new_ref();
263
264        // check that point_to_nonnative_limbs returns the expected values
265        let mut rng = ark_std::test_rng();
266        let p = Projective::rand(&mut rng);
267        let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
268        let (x, y) = point_to_nonnative_limbs_custom_opt(p, OptimizationType::Weight).unwrap();
269        assert_eq!(pVar.x.to_constraint_field().unwrap().value().unwrap(), x);
270        assert_eq!(pVar.y.to_constraint_field().unwrap().value().unwrap(), y);
271    }
272
273    #[test]
274    fn test_improved_to_constraint_field() {
275        let cs = ConstraintSystem::<Fr>::new_ref();
276
277        // check that point_to_nonnative_limbs returns the expected values
278        let mut rng = ark_std::test_rng();
279        let p = Projective::rand(&mut rng);
280        let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
281        let (x, y) = nonnative_affine_to_field_elements(p).unwrap();
282        assert_eq!(
283            pVar.to_constraint_field().unwrap().value().unwrap(),
284            [x, y].concat()
285        );
286    }
287
288    #[test]
289    fn test_inputize() {
290        let cs = ConstraintSystem::<Fr>::new_ref();
291
292        // check that point_to_nonnative_limbs returns the expected values
293        let mut rng = ark_std::test_rng();
294        let p = Projective::rand(&mut rng);
295        let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
296        let (x, y) = NonNativeAffineVar::inputize(p).unwrap();
297
298        match (pVar.x, pVar.y) {
299            (NonNativeFieldVar::Var(p_x), NonNativeFieldVar::Var(p_y)) => {
300                assert_eq!(p_x.limbs.value().unwrap(), x);
301                assert_eq!(p_y.limbs.value().unwrap(), y);
302            }
303            _ => unreachable!(),
304        }
305    }
306}