ark_r1cs_std/fields/emulated_fp/
field_var.rs

1use super::{params::OptimizationType, AllocatedEmulatedFpVar, MulResultVar};
2use crate::{
3    boolean::Boolean,
4    convert::{ToBitsGadget, ToBytesGadget, ToConstraintFieldGadget},
5    fields::{fp::FpVar, FieldVar},
6    prelude::*,
7    R1CSVar,
8};
9use ark_ff::{BigInteger, PrimeField};
10use ark_relations::r1cs::{ConstraintSystemRef, Namespace, Result as R1CSResult, SynthesisError};
11use ark_std::{
12    borrow::Borrow,
13    hash::{Hash, Hasher},
14    vec::Vec,
15};
16
17/// A gadget for representing non-native (`TargetF`) field elements over the
18/// constraint field (`BaseF`).
19#[derive(Clone, Debug)]
20#[must_use]
21pub enum EmulatedFpVar<TargetF: PrimeField, BaseF: PrimeField> {
22    /// Constant
23    Constant(TargetF),
24    /// Allocated gadget
25    Var(AllocatedEmulatedFpVar<TargetF, BaseF>),
26}
27
28impl<TargetF: PrimeField, BaseF: PrimeField> PartialEq for EmulatedFpVar<TargetF, BaseF> {
29    fn eq(&self, other: &Self) -> bool {
30        self.value()
31            .unwrap_or_default()
32            .eq(&other.value().unwrap_or_default())
33    }
34}
35
36impl<TargetF: PrimeField, BaseF: PrimeField> Eq for EmulatedFpVar<TargetF, BaseF> {}
37
38impl<TargetF: PrimeField, BaseF: PrimeField> Hash for EmulatedFpVar<TargetF, BaseF> {
39    fn hash<H: Hasher>(&self, state: &mut H) {
40        self.value().unwrap_or_default().hash(state);
41    }
42}
43
44impl<TargetF: PrimeField, BaseF: PrimeField> R1CSVar<BaseF> for EmulatedFpVar<TargetF, BaseF> {
45    type Value = TargetF;
46
47    fn cs(&self) -> ConstraintSystemRef<BaseF> {
48        match self {
49            Self::Constant(_) => ConstraintSystemRef::None,
50            Self::Var(a) => a.cs(),
51        }
52    }
53
54    fn value(&self) -> R1CSResult<Self::Value> {
55        match self {
56            Self::Constant(v) => Ok(*v),
57            Self::Var(v) => v.value(),
58        }
59    }
60}
61
62impl<TargetF: PrimeField, BaseF: PrimeField> From<Boolean<BaseF>>
63    for EmulatedFpVar<TargetF, BaseF>
64{
65    fn from(other: Boolean<BaseF>) -> Self {
66        if let Boolean::Constant(b) = other {
67            Self::Constant(<TargetF as From<u128>>::from(b as u128))
68        } else {
69            // `other` is a variable
70            let one = Self::Constant(TargetF::one());
71            let zero = Self::Constant(TargetF::zero());
72            Self::conditionally_select(&other, &one, &zero).unwrap()
73        }
74    }
75}
76
77impl<TargetF: PrimeField, BaseF: PrimeField> From<AllocatedEmulatedFpVar<TargetF, BaseF>>
78    for EmulatedFpVar<TargetF, BaseF>
79{
80    fn from(other: AllocatedEmulatedFpVar<TargetF, BaseF>) -> Self {
81        Self::Var(other)
82    }
83}
84
85impl<'a, TargetF: PrimeField, BaseF: PrimeField> FieldOpsBounds<'a, TargetF, Self>
86    for EmulatedFpVar<TargetF, BaseF>
87{
88}
89
90impl<'a, TargetF: PrimeField, BaseF: PrimeField>
91    FieldOpsBounds<'a, TargetF, EmulatedFpVar<TargetF, BaseF>>
92    for &'a EmulatedFpVar<TargetF, BaseF>
93{
94}
95
96impl<TargetF: PrimeField, BaseF: PrimeField> FieldVar<TargetF, BaseF>
97    for EmulatedFpVar<TargetF, BaseF>
98{
99    fn zero() -> Self {
100        Self::Constant(TargetF::zero())
101    }
102
103    fn one() -> Self {
104        Self::Constant(TargetF::one())
105    }
106
107    fn constant(v: TargetF) -> Self {
108        Self::Constant(v)
109    }
110
111    #[tracing::instrument(target = "r1cs")]
112    fn negate(&self) -> R1CSResult<Self> {
113        match self {
114            Self::Constant(c) => Ok(Self::Constant(-*c)),
115            Self::Var(v) => Ok(Self::Var(v.negate()?)),
116        }
117    }
118
119    #[tracing::instrument(target = "r1cs")]
120    fn inverse(&self) -> R1CSResult<Self> {
121        match self {
122            Self::Constant(c) => Ok(Self::Constant(c.inverse().unwrap_or_default())),
123            Self::Var(v) => Ok(Self::Var(v.inverse()?)),
124        }
125    }
126
127    #[tracing::instrument(target = "r1cs")]
128    fn frobenius_map(&self, power: usize) -> R1CSResult<Self> {
129        match self {
130            Self::Constant(c) => Ok(Self::Constant({
131                let mut tmp = *c;
132                tmp.frobenius_map_in_place(power);
133                tmp
134            })),
135            Self::Var(v) => Ok(Self::Var(v.frobenius_map(power)?)),
136        }
137    }
138}
139
140impl_bounded_ops!(
141    EmulatedFpVar<TargetF, BaseF>,
142    TargetF,
143    Add,
144    add,
145    AddAssign,
146    add_assign,
147    |this: &'a EmulatedFpVar<TargetF, BaseF>, other: &'a EmulatedFpVar<TargetF, BaseF>| {
148        use EmulatedFpVar::*;
149        match (this, other) {
150            (Constant(c1), Constant(c2)) => Constant(*c1 + c2),
151            (Constant(c), Var(v)) | (Var(v), Constant(c)) => Var(v.add_constant(c).unwrap()),
152            (Var(v1), Var(v2)) => Var(v1.add(v2).unwrap()),
153        }
154    },
155    |this: &'a EmulatedFpVar<TargetF, BaseF>, other: TargetF| { this + &EmulatedFpVar::Constant(other) },
156    (TargetF: PrimeField, BaseF: PrimeField),
157);
158
159impl_bounded_ops!(
160    EmulatedFpVar<TargetF, BaseF>,
161    TargetF,
162    Sub,
163    sub,
164    SubAssign,
165    sub_assign,
166    |this: &'a EmulatedFpVar<TargetF, BaseF>, other: &'a EmulatedFpVar<TargetF, BaseF>| {
167        use EmulatedFpVar::*;
168        match (this, other) {
169            (Constant(c1), Constant(c2)) => Constant(*c1 - c2),
170            (Var(v), Constant(c)) => Var(v.sub_constant(c).unwrap()),
171            (Constant(c), Var(v)) => Var(v.sub_constant(c).unwrap().negate().unwrap()),
172            (Var(v1), Var(v2)) => Var(v1.sub(v2).unwrap()),
173        }
174    },
175    |this: &'a EmulatedFpVar<TargetF, BaseF>, other: TargetF| {
176        this - &EmulatedFpVar::Constant(other)
177    },
178    (TargetF: PrimeField, BaseF: PrimeField),
179);
180
181impl_bounded_ops!(
182    EmulatedFpVar<TargetF, BaseF>,
183    TargetF,
184    Mul,
185    mul,
186    MulAssign,
187    mul_assign,
188    |this: &'a EmulatedFpVar<TargetF, BaseF>, other: &'a EmulatedFpVar<TargetF, BaseF>| {
189        use EmulatedFpVar::*;
190        match (this, other) {
191            (Constant(c1), Constant(c2)) => Constant(*c1 * c2),
192            (Constant(c), Var(v)) | (Var(v), Constant(c)) => Var(v.mul_constant(c).unwrap()),
193            (Var(v1), Var(v2)) => Var(v1.mul(v2).unwrap()),
194        }
195    },
196    |this: &'a EmulatedFpVar<TargetF, BaseF>, other: TargetF| {
197        if other.is_zero() {
198            EmulatedFpVar::zero()
199        } else {
200            this * &EmulatedFpVar::Constant(other)
201        }
202    },
203    (TargetF: PrimeField, BaseF: PrimeField),
204);
205
206/// *************************************************************************
207/// *************************************************************************
208
209impl<TargetF: PrimeField, BaseF: PrimeField> EqGadget<BaseF> for EmulatedFpVar<TargetF, BaseF> {
210    #[tracing::instrument(target = "r1cs")]
211    fn is_eq(&self, other: &Self) -> R1CSResult<Boolean<BaseF>> {
212        let cs = self.cs().or(other.cs());
213
214        if cs == ConstraintSystemRef::None {
215            Ok(Boolean::Constant(self.value()? == other.value()?))
216        } else {
217            let should_enforce_equal =
218                Boolean::new_witness(cs, || Ok(self.value()? == other.value()?))?;
219
220            self.conditional_enforce_equal(other, &should_enforce_equal)?;
221            self.conditional_enforce_not_equal(other, &!&should_enforce_equal)?;
222
223            Ok(should_enforce_equal)
224        }
225    }
226
227    #[tracing::instrument(target = "r1cs")]
228    fn conditional_enforce_equal(
229        &self,
230        other: &Self,
231        should_enforce: &Boolean<BaseF>,
232    ) -> R1CSResult<()> {
233        match (self, other) {
234            (Self::Constant(c1), Self::Constant(c2)) => {
235                if c1 != c2 {
236                    should_enforce.enforce_equal(&Boolean::FALSE)?;
237                }
238                Ok(())
239            },
240            (Self::Constant(c), Self::Var(v)) | (Self::Var(v), Self::Constant(c)) => {
241                let cs = v.cs();
242                let c = AllocatedEmulatedFpVar::new_constant(cs, c)?;
243                c.conditional_enforce_equal(v, should_enforce)
244            },
245            (Self::Var(v1), Self::Var(v2)) => v1.conditional_enforce_equal(v2, should_enforce),
246        }
247    }
248
249    #[tracing::instrument(target = "r1cs")]
250    fn conditional_enforce_not_equal(
251        &self,
252        other: &Self,
253        should_enforce: &Boolean<BaseF>,
254    ) -> R1CSResult<()> {
255        match (self, other) {
256            (Self::Constant(c1), Self::Constant(c2)) => {
257                if c1 == c2 {
258                    should_enforce.enforce_equal(&Boolean::FALSE)?;
259                }
260                Ok(())
261            },
262            (Self::Constant(c), Self::Var(v)) | (Self::Var(v), Self::Constant(c)) => {
263                let cs = v.cs();
264                let c = AllocatedEmulatedFpVar::new_constant(cs, c)?;
265                c.conditional_enforce_not_equal(v, should_enforce)
266            },
267            (Self::Var(v1), Self::Var(v2)) => v1.conditional_enforce_not_equal(v2, should_enforce),
268        }
269    }
270}
271
272impl<TargetF: PrimeField, BaseF: PrimeField> ToBitsGadget<BaseF> for EmulatedFpVar<TargetF, BaseF> {
273    #[tracing::instrument(target = "r1cs")]
274    fn to_bits_le(&self) -> R1CSResult<Vec<Boolean<BaseF>>> {
275        match self {
276            Self::Constant(_) => self.to_non_unique_bits_le(),
277            Self::Var(v) => v.to_bits_le(),
278        }
279    }
280
281    #[tracing::instrument(target = "r1cs")]
282    fn to_non_unique_bits_le(&self) -> R1CSResult<Vec<Boolean<BaseF>>> {
283        use ark_ff::BitIteratorLE;
284        match self {
285            Self::Constant(c) => Ok(BitIteratorLE::new(&c.into_bigint())
286                .take((TargetF::MODULUS_BIT_SIZE) as usize)
287                .map(Boolean::constant)
288                .collect::<Vec<_>>()),
289            Self::Var(v) => v.to_non_unique_bits_le(),
290        }
291    }
292}
293
294impl<TargetF: PrimeField, BaseF: PrimeField> ToBytesGadget<BaseF>
295    for EmulatedFpVar<TargetF, BaseF>
296{
297    /// Outputs the unique byte decomposition of `self` in *little-endian*
298    /// form.
299    #[tracing::instrument(target = "r1cs")]
300    fn to_bytes_le(&self) -> R1CSResult<Vec<UInt8<BaseF>>> {
301        match self {
302            Self::Constant(c) => Ok(UInt8::constant_vec(
303                c.into_bigint().to_bytes_le().as_slice(),
304            )),
305
306            Self::Var(v) => v.to_bytes_le(),
307        }
308    }
309
310    #[tracing::instrument(target = "r1cs")]
311    fn to_non_unique_bytes_le(&self) -> R1CSResult<Vec<UInt8<BaseF>>> {
312        match self {
313            Self::Constant(c) => Ok(UInt8::constant_vec(
314                c.into_bigint().to_bytes_le().as_slice(),
315            )),
316            Self::Var(v) => v.to_non_unique_bytes_le(),
317        }
318    }
319}
320
321impl<TargetF: PrimeField, BaseF: PrimeField> CondSelectGadget<BaseF>
322    for EmulatedFpVar<TargetF, BaseF>
323{
324    #[tracing::instrument(target = "r1cs")]
325    fn conditionally_select(
326        cond: &Boolean<BaseF>,
327        true_value: &Self,
328        false_value: &Self,
329    ) -> R1CSResult<Self> {
330        match cond {
331            &Boolean::Constant(true) => Ok(true_value.clone()),
332            &Boolean::Constant(false) => Ok(false_value.clone()),
333            _ => {
334                let cs = cond.cs();
335                let true_value = match true_value {
336                    Self::Constant(f) => AllocatedEmulatedFpVar::new_constant(cs.clone(), f)?,
337                    Self::Var(v) => v.clone(),
338                };
339                let false_value = match false_value {
340                    Self::Constant(f) => AllocatedEmulatedFpVar::new_constant(cs, f)?,
341                    Self::Var(v) => v.clone(),
342                };
343                cond.select(&true_value, &false_value).map(Self::Var)
344            },
345        }
346    }
347}
348
349/// Uses two bits to perform a lookup into a table
350/// `b` is little-endian: `b[0]` is LSB.
351impl<TargetF: PrimeField, BaseF: PrimeField> TwoBitLookupGadget<BaseF>
352    for EmulatedFpVar<TargetF, BaseF>
353{
354    type TableConstant = TargetF;
355
356    #[tracing::instrument(target = "r1cs")]
357    fn two_bit_lookup(b: &[Boolean<BaseF>], c: &[Self::TableConstant]) -> R1CSResult<Self> {
358        debug_assert_eq!(b.len(), 2);
359        debug_assert_eq!(c.len(), 4);
360        if b.cs().is_none() {
361            // We're in the constant case
362
363            let lsb = b[0].value()? as usize;
364            let msb = b[1].value()? as usize;
365            let index = lsb + (msb << 1);
366            Ok(Self::Constant(c[index]))
367        } else {
368            AllocatedEmulatedFpVar::two_bit_lookup(b, c).map(Self::Var)
369        }
370    }
371}
372
373impl<TargetF: PrimeField, BaseF: PrimeField> ThreeBitCondNegLookupGadget<BaseF>
374    for EmulatedFpVar<TargetF, BaseF>
375{
376    type TableConstant = TargetF;
377
378    #[tracing::instrument(target = "r1cs")]
379    fn three_bit_cond_neg_lookup(
380        b: &[Boolean<BaseF>],
381        b0b1: &Boolean<BaseF>,
382        c: &[Self::TableConstant],
383    ) -> R1CSResult<Self> {
384        debug_assert_eq!(b.len(), 3);
385        debug_assert_eq!(c.len(), 4);
386
387        if b.cs().or(b0b1.cs()).is_none() {
388            // We're in the constant case
389
390            let lsb = b[0].value()? as usize;
391            let msb = b[1].value()? as usize;
392            let index = lsb + (msb << 1);
393            let intermediate = c[index];
394
395            let is_negative = b[2].value()?;
396            let y = if is_negative {
397                -intermediate
398            } else {
399                intermediate
400            };
401            Ok(Self::Constant(y))
402        } else {
403            AllocatedEmulatedFpVar::three_bit_cond_neg_lookup(b, b0b1, c).map(Self::Var)
404        }
405    }
406}
407
408impl<TargetF: PrimeField, BaseF: PrimeField> AllocVar<TargetF, BaseF>
409    for EmulatedFpVar<TargetF, BaseF>
410{
411    fn new_variable<T: Borrow<TargetF>>(
412        cs: impl Into<Namespace<BaseF>>,
413        f: impl FnOnce() -> Result<T, SynthesisError>,
414        mode: AllocationMode,
415    ) -> R1CSResult<Self> {
416        let ns = cs.into();
417        let cs = ns.cs();
418
419        if cs == ConstraintSystemRef::None || mode == AllocationMode::Constant {
420            Ok(Self::Constant(*f()?.borrow()))
421        } else {
422            AllocatedEmulatedFpVar::new_variable(cs, f, mode).map(Self::Var)
423        }
424    }
425}
426
427impl<TargetF: PrimeField, BaseF: PrimeField> ToConstraintFieldGadget<BaseF>
428    for EmulatedFpVar<TargetF, BaseF>
429{
430    #[tracing::instrument(target = "r1cs")]
431    fn to_constraint_field(&self) -> R1CSResult<Vec<FpVar<BaseF>>> {
432        // Use one group element to represent the optimization type.
433        //
434        // By default, the constant is converted in the weight-optimized type, because
435        // it results in fewer elements.
436        match self {
437            Self::Constant(c) => Ok(AllocatedEmulatedFpVar::get_limbs_representations(
438                c,
439                OptimizationType::Weight,
440            )?
441            .into_iter()
442            .map(FpVar::constant)
443            .collect()),
444            Self::Var(v) => v.to_constraint_field(),
445        }
446    }
447}
448
449impl<TargetF: PrimeField, BaseF: PrimeField> EmulatedFpVar<TargetF, BaseF> {
450    /// The `mul_without_reduce` for `EmulatedFpVar`
451    #[tracing::instrument(target = "r1cs")]
452    pub fn mul_without_reduce(&self, other: &Self) -> R1CSResult<MulResultVar<TargetF, BaseF>> {
453        match self {
454            Self::Constant(c) => match other {
455                Self::Constant(other_c) => Ok(MulResultVar::Constant(*c * other_c)),
456                Self::Var(other_v) => {
457                    let self_v =
458                        AllocatedEmulatedFpVar::<TargetF, BaseF>::new_constant(self.cs(), c)?;
459                    Ok(MulResultVar::Var(other_v.mul_without_reduce(&self_v)?))
460                },
461            },
462            Self::Var(v) => {
463                let other_v = match other {
464                    Self::Constant(other_c) => {
465                        AllocatedEmulatedFpVar::<TargetF, BaseF>::new_constant(self.cs(), other_c)?
466                    },
467                    Self::Var(other_v) => other_v.clone(),
468                };
469                Ok(MulResultVar::Var(v.mul_without_reduce(&other_v)?))
470            },
471        }
472    }
473}