Skip to main content

sonobe_primitives/algebra/group/
emulated.rs

1//! This module provides implementation of in-circuit variables for emulated
2//! elliptic curve points.
3//!
4//! This is useful when we want to express points whose coordinates lie in a
5//! different field than the circuit's constraint field.
6//!
7//! Note that currently this module only provides the representation of such
8//! points, without any arithmetic operations.
9
10use ark_ec::{AffineRepr, short_weierstrass::SWFlags};
11use ark_ff::Zero;
12use ark_r1cs_std::{
13    GR1CSVar,
14    alloc::{AllocVar, AllocationMode},
15    eq::EqGadget,
16    fields::fp::FpVar,
17    prelude::Boolean,
18    select::CondSelectGadget,
19};
20use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError};
21use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags};
22use ark_std::borrow::Borrow;
23
24use crate::{
25    algebra::{field::emulated::EmulatedFieldVar, group::SonobeCurve},
26    traits::SonobeField,
27    transcripts::AbsorbableVar,
28};
29
30/// [`EmulatedAffineVar`] defines an in-circuit elliptic curve point with its
31/// affine representation, where the coordinates are in the curve's base field
32/// `Target::BaseField` and are emulated over the constraint field `Base` in the
33/// circuit.
34#[derive(Debug, Clone)]
35pub struct EmulatedAffineVar<Base: SonobeField, Target: SonobeCurve> {
36    /// [`EmulatedAffineVar::x`] is the x-coordinate of the point's affine
37    /// representation.
38    pub x: EmulatedFieldVar<Base, Target::BaseField>,
39    /// [`EmulatedAffineVar::y`] is the y-coordinate of the point's affine
40    /// representation.
41    pub y: EmulatedFieldVar<Base, Target::BaseField>,
42}
43
44impl<Base: SonobeField, Target: SonobeCurve> AllocVar<Target, Base>
45    for EmulatedAffineVar<Base, Target>
46{
47    fn new_variable<T: Borrow<Target>>(
48        cs: impl Into<Namespace<Base>>,
49        f: impl FnOnce() -> Result<T, SynthesisError>,
50        mode: AllocationMode,
51    ) -> Result<Self, SynthesisError> {
52        f().and_then(|val| {
53            let cs = cs.into();
54
55            let affine = val.borrow().into_affine();
56            let (x, y) = affine.xy().unwrap_or_default();
57
58            let x = EmulatedFieldVar::new_variable(cs.clone(), || Ok(x), mode)?;
59            let y = EmulatedFieldVar::new_variable(cs.clone(), || Ok(y), mode)?;
60
61            Ok(Self { x, y })
62        })
63    }
64}
65
66impl<Base: SonobeField, Target: SonobeCurve> GR1CSVar<Base> for EmulatedAffineVar<Base, Target> {
67    type Value = Target;
68
69    fn cs(&self) -> ConstraintSystemRef<Base> {
70        self.x.cs().or(self.y.cs())
71    }
72
73    fn value(&self) -> Result<Self::Value, SynthesisError> {
74        let x = self.x.value()?;
75        let y = self.y.value()?;
76        // Below is a workaround to convert the `x` and `y` coordinates to a
77        // point. This is because the `SonobeCurve` trait does not provide a
78        // method to construct a point from `BaseField` elements.
79        let mut bytes = vec![];
80        // `unwrap` below is safe because serialization of a `PrimeField` value
81        // only fails if the serialization flag has more than 8 bits, but here
82        // we call `serialize_uncompressed` which uses an empty flag.
83        x.serialize_uncompressed(&mut bytes).unwrap();
84        // `unwrap` below is also safe, because the bit size of `SWFlags` is 2.
85        y.serialize_with_flags(
86            &mut bytes,
87            if x.is_zero() && y.is_zero() {
88                SWFlags::PointAtInfinity
89            } else if y <= -y {
90                SWFlags::YIsPositive
91            } else {
92                SWFlags::YIsNegative
93            },
94        )
95        .unwrap();
96        // `unwrap` below is safe because `bytes` is constructed from the `x`
97        // and `y` coordinates of a valid point, and these coordinates are
98        // serialized in the same way as the `SonobeCurve` implementation.
99        Ok(Target::deserialize_uncompressed_unchecked(&bytes[..]).unwrap())
100    }
101}
102
103impl<Base: SonobeField, Target: SonobeCurve> EqGadget<Base> for EmulatedAffineVar<Base, Target> {
104    fn is_eq(&self, other: &Self) -> Result<Boolean<Base>, SynthesisError> {
105        Ok(self.x.is_eq(&other.x)? & self.y.is_eq(&other.y)?)
106    }
107
108    fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> {
109        self.x.enforce_equal(&other.x)?;
110        self.y.enforce_equal(&other.y)?;
111        Ok(())
112    }
113}
114
115impl<Base: SonobeField, Target: SonobeCurve> EmulatedAffineVar<Base, Target> {
116    /// [`EmulatedAffineVar::zero`] allocates the zero point (point at infinity)
117    /// of the curve as a constant.
118    pub fn zero() -> Self {
119        // `unwrap` below is safe because we are allocating a constant value,
120        // which is guaranteed to succeed.
121        Self::new_constant(ConstraintSystemRef::None, Target::zero()).unwrap()
122    }
123}
124
125impl<Base: SonobeField, Target: SonobeCurve> AbsorbableVar<Base>
126    for EmulatedAffineVar<Base, Target>
127{
128    fn absorb_into(&self, dest: &mut Vec<FpVar<Base>>) -> Result<(), SynthesisError> {
129        (&self.x, &self.y).absorb_into(dest)
130    }
131}
132
133impl<Base: SonobeField, Target: SonobeCurve> CondSelectGadget<Base>
134    for EmulatedAffineVar<Base, Target>
135{
136    fn conditionally_select(
137        cond: &Boolean<Base>,
138        true_value: &Self,
139        false_value: &Self,
140    ) -> Result<Self, SynthesisError> {
141        Ok(Self {
142            x: cond.select(&true_value.x, &false_value.x)?,
143            y: cond.select(&true_value.y, &false_value.y)?,
144        })
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use ark_pallas::{Fq, Fr, PallasConfig, Projective};
151    use ark_r1cs_std::groups::curves::short_weierstrass::ProjectiveVar;
152    use ark_relations::gr1cs::ConstraintSystem;
153    use ark_std::{UniformRand, error::Error, rand::thread_rng};
154    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
155    use wasm_bindgen_test::wasm_bindgen_test as test;
156
157    use super::*;
158    use crate::{
159        traits::{Inputize, InputizeEmulated},
160        transcripts::Absorbable,
161    };
162
163    #[test]
164    fn test_alloc_zero() {
165        let cs = ConstraintSystem::<Fr>::new_ref();
166
167        // dealing with the 'zero' point should not panic when doing the unwrap
168        let p = Projective::zero();
169        assert!(EmulatedAffineVar::<Fr, Projective>::new_witness(cs.clone(), || Ok(p)).is_ok());
170    }
171
172    #[test]
173    fn test_to_hash_preimage() -> Result<(), Box<dyn Error>> {
174        let cs = ConstraintSystem::<Fr>::new_ref();
175
176        let mut rng = thread_rng();
177        let p = Projective::rand(&mut rng);
178        let p_var = EmulatedAffineVar::<Fr, Projective>::new_witness(cs.clone(), || Ok(p))?;
179
180        let mut v = vec![];
181        let mut v_var = vec![];
182        p.absorb_into(&mut v);
183        p_var.absorb_into(&mut v_var)?;
184
185        assert_eq!(v_var.value()?, v);
186        Ok(())
187    }
188
189    #[test]
190    fn test_inputize() -> Result<(), Box<dyn Error>> {
191        let mut rng = thread_rng();
192        let p = Projective::rand(&mut rng);
193
194        let cs = ConstraintSystem::<Fr>::new_ref();
195        let p_var = EmulatedAffineVar::<Fr, Projective>::new_witness(cs.clone(), || Ok(p))?;
196        assert_eq!(
197            [p_var.x.limbs.value()?, p_var.y.limbs.value()?].concat(),
198            p.inputize_emulated()
199        );
200
201        let cs = ConstraintSystem::<Fq>::new_ref();
202        let p_var = ProjectiveVar::<PallasConfig, FpVar<Fq>>::new_witness(cs.clone(), || Ok(p))?;
203        assert_eq!(
204            vec![p_var.x.value()?, p_var.y.value()?, p_var.z.value()?],
205            p.inputize()
206        );
207        Ok(())
208    }
209}