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