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