1use crate::{
2 convert::{ToBitsGadget, ToBytesGadget, ToConstraintFieldGadget},
3 fields::{fp::FpVar, FieldOpsBounds, FieldVar},
4 prelude::*,
5 Vec,
6};
7use ark_ff::{
8 fields::{CubicExtField, Field},
9 CubicExtConfig, Zero,
10};
11use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError};
12use core::{borrow::Borrow, marker::PhantomData};
13use educe::Educe;
14
15#[derive(Educe)]
18#[educe(Debug, Clone)]
19#[must_use]
20pub struct CubicExtVar<BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarConfig<BF>>
21where
22 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
23{
24 pub c0: BF,
26 pub c1: BF,
28 pub c2: BF,
30 #[educe(Debug(ignore))]
31 _params: PhantomData<P>,
32}
33
34pub trait CubicExtVarConfig<BF: FieldVar<Self::BaseField, Self::BasePrimeField>>:
37 CubicExtConfig
38where
39 for<'a> &'a BF: FieldOpsBounds<'a, Self::BaseField, BF>,
40{
41 fn mul_base_field_vars_by_frob_coeff(c1: &mut BF, c2: &mut BF, power: usize);
45}
46
47impl<BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarConfig<BF>> CubicExtVar<BF, P>
48where
49 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
50{
51 #[inline]
53 pub fn new(c0: BF, c1: BF, c2: BF) -> Self {
54 let _params = PhantomData;
55 Self {
56 c0,
57 c1,
58 c2,
59 _params,
60 }
61 }
62
63 #[inline]
66 pub fn mul_base_field_by_nonresidue(fe: &BF) -> Result<BF, SynthesisError> {
67 Ok(fe * P::NONRESIDUE)
68 }
69
70 #[inline]
72 pub fn mul_by_base_field_constant(&self, fe: P::BaseField) -> Self {
73 let c0 = &self.c0 * fe;
74 let c1 = &self.c1 * fe;
75 let c2 = &self.c2 * fe;
76 Self::new(c0, c1, c2)
77 }
78
79 #[inline]
81 pub fn mul_assign_by_base_field_constant(&mut self, fe: P::BaseField) {
82 *self = (&*self).mul_by_base_field_constant(fe);
83 }
84}
85
86impl<BF, P> R1CSVar<P::BasePrimeField> for CubicExtVar<BF, P>
87where
88 BF: FieldVar<P::BaseField, P::BasePrimeField>,
89 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
90 P: CubicExtVarConfig<BF>,
91{
92 type Value = CubicExtField<P>;
93
94 fn cs(&self) -> ConstraintSystemRef<P::BasePrimeField> {
95 [&self.c0, &self.c1, &self.c2].cs()
96 }
97
98 #[inline]
99 fn value(&self) -> Result<Self::Value, SynthesisError> {
100 match (self.c0.value(), self.c1.value(), self.c2.value()) {
101 (Ok(c0), Ok(c1), Ok(c2)) => Ok(CubicExtField::new(c0, c1, c2)),
102 (..) => Err(SynthesisError::AssignmentMissing),
103 }
104 }
105}
106
107impl<BF, P> From<Boolean<P::BasePrimeField>> for CubicExtVar<BF, P>
108where
109 BF: FieldVar<P::BaseField, P::BasePrimeField>,
110 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
111 P: CubicExtVarConfig<BF>,
112{
113 fn from(other: Boolean<P::BasePrimeField>) -> Self {
114 let c0 = BF::from(other);
115 let c1 = BF::zero();
116 let c2 = BF::zero();
117 Self::new(c0, c1, c2)
118 }
119}
120
121impl<'a, BF, P> FieldOpsBounds<'a, CubicExtField<P>, CubicExtVar<BF, P>> for CubicExtVar<BF, P>
122where
123 BF: FieldVar<P::BaseField, P::BasePrimeField>,
124 for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
125 P: CubicExtVarConfig<BF>,
126{
127}
128impl<'a, BF, P> FieldOpsBounds<'a, CubicExtField<P>, CubicExtVar<BF, P>> for &'a CubicExtVar<BF, P>
129where
130 BF: FieldVar<P::BaseField, P::BasePrimeField>,
131 for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
132 P: CubicExtVarConfig<BF>,
133{
134}
135
136impl<BF, P> FieldVar<CubicExtField<P>, P::BasePrimeField> for CubicExtVar<BF, P>
137where
138 BF: FieldVar<P::BaseField, P::BasePrimeField>,
139 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
140 P: CubicExtVarConfig<BF>,
141{
142 fn constant(other: CubicExtField<P>) -> Self {
143 let c0 = BF::constant(other.c0);
144 let c1 = BF::constant(other.c1);
145 let c2 = BF::constant(other.c2);
146 Self::new(c0, c1, c2)
147 }
148
149 fn zero() -> Self {
150 let c0 = BF::zero();
151 let c1 = BF::zero();
152 let c2 = BF::zero();
153 Self::new(c0, c1, c2)
154 }
155
156 fn one() -> Self {
157 let c0 = BF::one();
158 let c1 = BF::zero();
159 let c2 = BF::zero();
160 Self::new(c0, c1, c2)
161 }
162
163 #[inline]
164 #[tracing::instrument(target = "r1cs")]
165 fn double(&self) -> Result<Self, SynthesisError> {
166 let c0 = self.c0.double()?;
167 let c1 = self.c1.double()?;
168 let c2 = self.c2.double()?;
169 Ok(Self::new(c0, c1, c2))
170 }
171
172 #[inline]
173 #[tracing::instrument(target = "r1cs")]
174 fn negate(&self) -> Result<Self, SynthesisError> {
175 let mut result = self.clone();
176 result.c0.negate_in_place()?;
177 result.c1.negate_in_place()?;
178 result.c2.negate_in_place()?;
179 Ok(result)
180 }
181
182 #[inline]
188 #[tracing::instrument(target = "r1cs")]
189 fn square(&self) -> Result<Self, SynthesisError> {
190 let a = self.c0.clone();
191 let b = self.c1.clone();
192 let c = self.c2.clone();
193
194 let s0 = a.square()?;
195 let ab = &a * &b;
196 let s1 = ab.double()?;
197 let s2 = (&a - &b + &c).square()?;
198 let s3 = (&b * &c).double()?;
199 let s4 = c.square()?;
200
201 let c0 = Self::mul_base_field_by_nonresidue(&s3)? + &s0;
202 let c1 = Self::mul_base_field_by_nonresidue(&s4)? + &s1;
203 let c2 = s1 + &s2 + &s3 - &s0 - &s4;
204
205 Ok(Self::new(c0, c1, c2))
206 }
207
208 #[tracing::instrument(target = "r1cs")]
209 fn mul_equals(&self, other: &Self, result: &Self) -> Result<(), SynthesisError> {
210 let v0 = &self.c0 * &other.c0;
233 let v1 = &self.c1 * &other.c1;
234 let v2 = &self.c2 * &other.c2;
235
236 let nr_a1_plus_a2 = (&self.c1 + &self.c2) * P::NONRESIDUE;
238 let b1_plus_b2 = &other.c1 + &other.c2;
239 let nr_v1 = &v1 * P::NONRESIDUE;
240 let nr_v2 = &v2 * P::NONRESIDUE;
241 let to_check = &result.c0 - &v0 + &nr_v1 + &nr_v2;
242 nr_a1_plus_a2.mul_equals(&b1_plus_b2, &to_check)?;
243
244 let a0_plus_a1 = &self.c0 + &self.c1;
246 let b0_plus_b1 = &other.c0 + &other.c1;
247 let to_check = &result.c1 - &nr_v2 + &v0 + &v1;
248 a0_plus_a1.mul_equals(&b0_plus_b1, &to_check)?;
249
250 let a0_plus_a2 = &self.c0 + &self.c2;
252 let b0_plus_b2 = &other.c0 + &other.c2;
253 let to_check = &result.c2 + &v0 - &v1 + &v2;
254 a0_plus_a2.mul_equals(&b0_plus_b2, &to_check)?;
255 Ok(())
256 }
257
258 #[tracing::instrument(target = "r1cs")]
259 fn frobenius_map(&self, power: usize) -> Result<Self, SynthesisError> {
260 let mut result = self.clone();
261 result.c0.frobenius_map_in_place(power)?;
262 result.c1.frobenius_map_in_place(power)?;
263 result.c2.frobenius_map_in_place(power)?;
264
265 P::mul_base_field_vars_by_frob_coeff(&mut result.c1, &mut result.c2, power);
266 Ok(result)
267 }
268
269 #[tracing::instrument(target = "r1cs")]
270 fn inverse(&self) -> Result<Self, SynthesisError> {
271 let mode = if self.is_constant() {
272 AllocationMode::Constant
273 } else {
274 AllocationMode::Witness
275 };
276 let inverse = Self::new_variable(
277 self.cs(),
278 || {
279 self.value()
280 .map(|f| f.inverse().unwrap_or_else(CubicExtField::zero))
281 },
282 mode,
283 )?;
284 self.mul_equals(&inverse, &Self::one())?;
285 Ok(inverse)
286 }
287}
288
289impl_bounded_ops!(
290 CubicExtVar<BF, P>,
291 CubicExtField<P>,
292 Add,
293 add,
294 AddAssign,
295 add_assign,
296 |this: &'a CubicExtVar<BF, P>, other: &'a CubicExtVar<BF, P>| {
297 let c0 = &this.c0 + &other.c0;
298 let c1 = &this.c1 + &other.c1;
299 let c2 = &this.c2 + &other.c2;
300 CubicExtVar::new(c0, c1, c2)
301 },
302 |this: &'a CubicExtVar<BF, P>, other: CubicExtField<P>| {
303 this + CubicExtVar::constant(other)
304 },
305 (BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarConfig<BF>),
306 for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
307);
308impl_bounded_ops!(
309 CubicExtVar<BF, P>,
310 CubicExtField<P>,
311 Sub,
312 sub,
313 SubAssign,
314 sub_assign,
315 |this: &'a CubicExtVar<BF, P>, other: &'a CubicExtVar<BF, P>| {
316 let c0 = &this.c0 - &other.c0;
317 let c1 = &this.c1 - &other.c1;
318 let c2 = &this.c2 - &other.c2;
319 CubicExtVar::new(c0, c1, c2)
320 },
321 |this: &'a CubicExtVar<BF, P>, other: CubicExtField<P>| {
322 this - CubicExtVar::constant(other)
323 },
324 (BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarConfig<BF>),
325 for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
326);
327impl_bounded_ops!(
328 CubicExtVar<BF, P>,
329 CubicExtField<P>,
330 Mul,
331 mul,
332 MulAssign,
333 mul_assign,
334 |this: &'a CubicExtVar<BF, P>, other: &'a CubicExtVar<BF, P>| {
335 let v0 = &this.c0 * &other.c0;
347 let v1 = &this.c1 * &other.c1;
348 let v2 = &this.c2 * &other.c2;
349 let c0 =
350 (((&this.c1 + &this.c2) * (&other.c1 + &other.c2) - &v1 - &v2) * P::NONRESIDUE) + &v0 ;
351 let c1 =
352 (&this.c0 + &this.c1) * (&other.c0 + &other.c1) - &v0 - &v1 + (&v2 * P::NONRESIDUE);
353 let c2 =
354 (&this.c0 + &this.c2) * (&other.c0 + &other.c2) - &v0 + &v1 - &v2;
355
356 CubicExtVar::new(c0, c1, c2)
357 },
358 |this: &'a CubicExtVar<BF, P>, other: CubicExtField<P>| {
359 this * CubicExtVar::constant(other)
360 },
361 (BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarConfig<BF>),
362 for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
363);
364
365impl<BF, P> EqGadget<P::BasePrimeField> for CubicExtVar<BF, P>
366where
367 BF: FieldVar<P::BaseField, P::BasePrimeField>,
368 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
369 P: CubicExtVarConfig<BF>,
370{
371 #[tracing::instrument(target = "r1cs")]
372 fn is_eq(&self, other: &Self) -> Result<Boolean<P::BasePrimeField>, SynthesisError> {
373 let b0 = self.c0.is_eq(&other.c0)?;
374 let b1 = self.c1.is_eq(&other.c1)?;
375 let b2 = self.c2.is_eq(&other.c2)?;
376 Ok(b0 & b1 & b2)
377 }
378
379 #[inline]
380 #[tracing::instrument(target = "r1cs")]
381 fn conditional_enforce_equal(
382 &self,
383 other: &Self,
384 condition: &Boolean<P::BasePrimeField>,
385 ) -> Result<(), SynthesisError> {
386 self.c0.conditional_enforce_equal(&other.c0, condition)?;
387 self.c1.conditional_enforce_equal(&other.c1, condition)?;
388 self.c2.conditional_enforce_equal(&other.c2, condition)?;
389 Ok(())
390 }
391
392 #[inline]
393 #[tracing::instrument(target = "r1cs")]
394 fn conditional_enforce_not_equal(
395 &self,
396 other: &Self,
397 condition: &Boolean<P::BasePrimeField>,
398 ) -> Result<(), SynthesisError> {
399 let is_equal = self.is_eq(other)?;
400 (is_equal & condition).enforce_equal(&Boolean::FALSE)
401 }
402}
403
404impl<BF, P> ToBitsGadget<P::BasePrimeField> for CubicExtVar<BF, P>
405where
406 BF: FieldVar<P::BaseField, P::BasePrimeField>,
407 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
408 P: CubicExtVarConfig<BF>,
409{
410 #[tracing::instrument(target = "r1cs")]
411 fn to_bits_le(&self) -> Result<Vec<Boolean<P::BasePrimeField>>, SynthesisError> {
412 let mut c0 = self.c0.to_bits_le()?;
413 let mut c1 = self.c1.to_bits_le()?;
414 let mut c2 = self.c2.to_bits_le()?;
415 c0.append(&mut c1);
416 c0.append(&mut c2);
417 Ok(c0)
418 }
419
420 #[tracing::instrument(target = "r1cs")]
421 fn to_non_unique_bits_le(&self) -> Result<Vec<Boolean<P::BasePrimeField>>, SynthesisError> {
422 let mut c0 = self.c0.to_non_unique_bits_le()?;
423 let mut c1 = self.c1.to_non_unique_bits_le()?;
424 let mut c2 = self.c2.to_non_unique_bits_le()?;
425 c0.append(&mut c1);
426 c0.append(&mut c2);
427 Ok(c0)
428 }
429}
430
431impl<BF, P> ToBytesGadget<P::BasePrimeField> for CubicExtVar<BF, P>
432where
433 BF: FieldVar<P::BaseField, P::BasePrimeField>,
434 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
435 P: CubicExtVarConfig<BF>,
436{
437 #[tracing::instrument(target = "r1cs")]
438 fn to_bytes_le(&self) -> Result<Vec<UInt8<P::BasePrimeField>>, SynthesisError> {
439 let mut c0 = self.c0.to_bytes_le()?;
440 let mut c1 = self.c1.to_bytes_le()?;
441 let mut c2 = self.c2.to_bytes_le()?;
442 c0.append(&mut c1);
443 c0.append(&mut c2);
444
445 Ok(c0)
446 }
447
448 #[tracing::instrument(target = "r1cs")]
449 fn to_non_unique_bytes_le(&self) -> Result<Vec<UInt8<P::BasePrimeField>>, SynthesisError> {
450 let mut c0 = self.c0.to_non_unique_bytes_le()?;
451 let mut c1 = self.c1.to_non_unique_bytes_le()?;
452 let mut c2 = self.c2.to_non_unique_bytes_le()?;
453
454 c0.append(&mut c1);
455 c0.append(&mut c2);
456
457 Ok(c0)
458 }
459}
460
461impl<BF, P> ToConstraintFieldGadget<P::BasePrimeField> for CubicExtVar<BF, P>
462where
463 BF: FieldVar<P::BaseField, P::BasePrimeField>,
464 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
465 P: CubicExtVarConfig<BF>,
466 BF: ToConstraintFieldGadget<P::BasePrimeField>,
467{
468 #[tracing::instrument(target = "r1cs")]
469 fn to_constraint_field(&self) -> Result<Vec<FpVar<P::BasePrimeField>>, SynthesisError> {
470 let mut res = Vec::new();
471
472 res.extend_from_slice(&self.c0.to_constraint_field()?);
473 res.extend_from_slice(&self.c1.to_constraint_field()?);
474 res.extend_from_slice(&self.c2.to_constraint_field()?);
475
476 Ok(res)
477 }
478}
479
480impl<BF, P> CondSelectGadget<P::BasePrimeField> for CubicExtVar<BF, P>
481where
482 BF: FieldVar<P::BaseField, P::BasePrimeField>,
483 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
484 P: CubicExtVarConfig<BF>,
485{
486 #[inline]
487 #[tracing::instrument(target = "r1cs")]
488 fn conditionally_select(
489 cond: &Boolean<P::BasePrimeField>,
490 true_value: &Self,
491 false_value: &Self,
492 ) -> Result<Self, SynthesisError> {
493 let c0 = BF::conditionally_select(cond, &true_value.c0, &false_value.c0)?;
494 let c1 = BF::conditionally_select(cond, &true_value.c1, &false_value.c1)?;
495 let c2 = BF::conditionally_select(cond, &true_value.c2, &false_value.c2)?;
496 Ok(Self::new(c0, c1, c2))
497 }
498}
499
500impl<BF, P> TwoBitLookupGadget<P::BasePrimeField> for CubicExtVar<BF, P>
501where
502 BF: FieldVar<P::BaseField, P::BasePrimeField>
503 + TwoBitLookupGadget<P::BasePrimeField, TableConstant = P::BaseField>,
504 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
505 P: CubicExtVarConfig<BF>,
506{
507 type TableConstant = CubicExtField<P>;
508
509 #[tracing::instrument(target = "r1cs")]
510 fn two_bit_lookup(
511 b: &[Boolean<P::BasePrimeField>],
512 c: &[Self::TableConstant],
513 ) -> Result<Self, SynthesisError> {
514 let c0s = c.iter().map(|f| f.c0).collect::<Vec<_>>();
515 let c1s = c.iter().map(|f| f.c1).collect::<Vec<_>>();
516 let c2s = c.iter().map(|f| f.c2).collect::<Vec<_>>();
517 let c0 = BF::two_bit_lookup(b, &c0s)?;
518 let c1 = BF::two_bit_lookup(b, &c1s)?;
519 let c2 = BF::two_bit_lookup(b, &c2s)?;
520 Ok(Self::new(c0, c1, c2))
521 }
522}
523
524impl<BF, P> ThreeBitCondNegLookupGadget<P::BasePrimeField> for CubicExtVar<BF, P>
525where
526 BF: FieldVar<P::BaseField, P::BasePrimeField>
527 + ThreeBitCondNegLookupGadget<P::BasePrimeField, TableConstant = P::BaseField>,
528 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
529 P: CubicExtVarConfig<BF>,
530{
531 type TableConstant = CubicExtField<P>;
532
533 #[tracing::instrument(target = "r1cs")]
534 fn three_bit_cond_neg_lookup(
535 b: &[Boolean<P::BasePrimeField>],
536 b0b1: &Boolean<P::BasePrimeField>,
537 c: &[Self::TableConstant],
538 ) -> Result<Self, SynthesisError> {
539 let c0s = c.iter().map(|f| f.c0).collect::<Vec<_>>();
540 let c1s = c.iter().map(|f| f.c1).collect::<Vec<_>>();
541 let c2s = c.iter().map(|f| f.c2).collect::<Vec<_>>();
542 let c0 = BF::three_bit_cond_neg_lookup(b, b0b1, &c0s)?;
543 let c1 = BF::three_bit_cond_neg_lookup(b, b0b1, &c1s)?;
544 let c2 = BF::three_bit_cond_neg_lookup(b, b0b1, &c2s)?;
545 Ok(Self::new(c0, c1, c2))
546 }
547}
548
549impl<BF, P> AllocVar<CubicExtField<P>, P::BasePrimeField> for CubicExtVar<BF, P>
550where
551 BF: FieldVar<P::BaseField, P::BasePrimeField>,
552 for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
553 P: CubicExtVarConfig<BF>,
554{
555 fn new_variable<T: Borrow<CubicExtField<P>>>(
556 cs: impl Into<Namespace<P::BasePrimeField>>,
557 f: impl FnOnce() -> Result<T, SynthesisError>,
558 mode: AllocationMode,
559 ) -> Result<Self, SynthesisError> {
560 let ns = cs.into();
561 let cs = ns.cs();
562
563 use SynthesisError::*;
564 let (c0, c1, c2) = match f() {
565 Ok(fe) => (Ok(fe.borrow().c0), Ok(fe.borrow().c1), Ok(fe.borrow().c2)),
566 Err(_) => (
567 Err(AssignmentMissing),
568 Err(AssignmentMissing),
569 Err(AssignmentMissing),
570 ),
571 };
572
573 let c0 = BF::new_variable(ark_relations::ns!(cs, "c0"), || c0, mode)?;
574 let c1 = BF::new_variable(ark_relations::ns!(cs, "c1"), || c1, mode)?;
575 let c2 = BF::new_variable(ark_relations::ns!(cs, "c2"), || c2, mode)?;
576 Ok(Self::new(c0, c1, c2))
577 }
578}