sonobe/folding-schemes/src/folding/circuits/nonnative/affine.rs
folding-schemes/src/folding/circuits/nonnative/affine.rsRaw
1use ark_ec::{AffineRepr, CurveGroup};
2use ark_r1cs_std::{
3 alloc::{AllocVar, AllocationMode},
4 fields::fp::FpVar,
5 ToConstraintFieldGadget,
6};
7use ark_relations::r1cs::{Namespace, SynthesisError};
8use ark_std::Zero;
9use core::borrow::Borrow;
10
11use super::uint::{nonnative_field_to_field_elements, NonNativeUintVar};
12
13/// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native
14/// field, over the constraint field. It is not intended to perform operations, but just to contain
15/// the affine coordinates in order to perform hash operations of the point.
16#[derive(Debug, Clone)]
17pub struct NonNativeAffineVar<C: CurveGroup>
18where
19 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
20{
21 pub x: NonNativeUintVar<C::ScalarField>,
22 pub y: NonNativeUintVar<C::ScalarField>,
23}
24
25impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C>
26where
27 C: CurveGroup,
28 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
29{
30 fn new_variable<T: Borrow<C>>(
31 cs: impl Into<Namespace<C::ScalarField>>,
32 f: impl FnOnce() -> Result<T, SynthesisError>,
33 mode: AllocationMode,
34 ) -> Result<Self, SynthesisError> {
35 f().and_then(|val| {
36 let cs = cs.into();
37
38 let affine = val.borrow().into_affine();
39 let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
40 let xy = affine.xy().unwrap_or(zero_point);
41
42 let x = NonNativeUintVar::new_variable(cs.clone(), || Ok(*xy.0), mode)?;
43 let y = NonNativeUintVar::new_variable(cs.clone(), || Ok(*xy.1), mode)?;
44
45 Ok(Self { x, y })
46 })
47 }
48}
49
50impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C>
51where
52 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
53{
54 // Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in
55 // the circuit.
56 fn to_constraint_field(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> {
57 let x = self.x.to_constraint_field()?;
58 let y = self.y.to_constraint_field()?;
59 Ok([x, y].concat())
60 }
61}
62
63/// The out-circuit counterpart of `NonNativeAffineVar::to_constraint_field`
64#[allow(clippy::type_complexity)]
65pub fn nonnative_affine_to_field_elements<C: CurveGroup>(
66 p: C,
67) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
68where
69 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
70{
71 let affine = p.into_affine();
72 if affine.is_zero() {
73 let x = nonnative_field_to_field_elements(&C::BaseField::zero());
74 let y = nonnative_field_to_field_elements(&C::BaseField::zero());
75 return Ok((x, y));
76 }
77
78 let (x, y) = affine.xy().unwrap();
79 let x = nonnative_field_to_field_elements(x);
80 let y = nonnative_field_to_field_elements(y);
81 Ok((x, y))
82}
83
84impl<C: CurveGroup> NonNativeAffineVar<C>
85where
86 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
87{
88 // A wrapper of `point_to_nonnative_limbs_custom_opt` with constraints-focused optimization
89 // type (which is the default optimization type for arkworks' Groth16).
90 // Used for extracting a list of field elements of type `C::ScalarField` from the public input
91 // `p`, in exactly the same way as how `NonNativeAffineVar` is represented as limbs of type
92 // `FpVar` in-circuit.
93 #[allow(clippy::type_complexity)]
94 pub fn inputize(p: C) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> {
95 let affine = p.into_affine();
96 if affine.is_zero() {
97 let x = NonNativeUintVar::inputize(C::BaseField::zero());
98 let y = NonNativeUintVar::inputize(C::BaseField::zero());
99 return Ok((x, y));
100 }
101
102 let (x, y) = affine.xy().unwrap();
103 let x = NonNativeUintVar::inputize(*x);
104 let y = NonNativeUintVar::inputize(*y);
105 Ok((x, y))
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use ark_pallas::{Fr, Projective};
113 use ark_r1cs_std::R1CSVar;
114 use ark_relations::r1cs::ConstraintSystem;
115 use ark_std::UniformRand;
116
117 #[test]
118 fn test_alloc_zero() {
119 let cs = ConstraintSystem::<Fr>::new_ref();
120
121 // dealing with the 'zero' point should not panic when doing the unwrap
122 let p = Projective::zero();
123 assert!(NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).is_ok());
124 }
125
126 #[test]
127 fn test_improved_to_constraint_field() {
128 let cs = ConstraintSystem::<Fr>::new_ref();
129
130 // check that point_to_nonnative_limbs returns the expected values
131 let mut rng = ark_std::test_rng();
132 let p = Projective::rand(&mut rng);
133 let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
134 let (x, y) = nonnative_affine_to_field_elements(p).unwrap();
135 assert_eq!(
136 pVar.to_constraint_field().unwrap().value().unwrap(),
137 [x, y].concat()
138 );
139 }
140
141 #[test]
142 fn test_inputize() {
143 let cs = ConstraintSystem::<Fr>::new_ref();
144
145 // check that point_to_nonnative_limbs returns the expected values
146 let mut rng = ark_std::test_rng();
147 let p = Projective::rand(&mut rng);
148 let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
149 let (x, y) = NonNativeAffineVar::inputize(p).unwrap();
150
151 assert_eq!(pVar.x.0.value().unwrap(), x);
152 assert_eq!(pVar.y.0.value().unwrap(), y);
153 }
154}