use algebra::{ fields::{Fp2, Fp2Parameters}, PrimeField, }; use core::{borrow::Borrow, marker::PhantomData}; use r1cs_core::{ConstraintSystem, ConstraintVar, SynthesisError}; use crate::{fields::fp::FpGadget, prelude::*, Vec}; #[derive(Derivative)] #[derivative(Debug(bound = "P: Fp2Parameters, ConstraintF: PrimeField"))] #[must_use] pub struct Fp2Gadget, ConstraintF: PrimeField> { pub c0: FpGadget, pub c1: FpGadget, #[derivative(Debug = "ignore")] _params: PhantomData

, } impl, ConstraintF: PrimeField> Fp2Gadget { pub fn new(c0: FpGadget, c1: FpGadget) -> Self { Self { c0, c1, _params: PhantomData, } } /// Multiply a FpGadget by quadratic nonresidue P::NONRESIDUE. #[inline] pub fn mul_fp_gadget_by_nonresidue>( cs: CS, fe: &FpGadget, ) -> Result, SynthesisError> { fe.mul_by_constant(cs, &P::NONRESIDUE) } /// Multiply a Fp2Gadget by an element of fp. #[inline] pub fn mul_by_fp_constant_in_place>( &mut self, mut cs: CS, fe: &P::Fp, ) -> Result<&mut Self, SynthesisError> { self.c0.mul_by_constant_in_place(cs.ns(|| "c0"), fe)?; self.c1.mul_by_constant_in_place(cs.ns(|| "c1"), fe)?; Ok(self) } /// Multiply a Fp2Gadget by an element of fp. #[inline] pub fn mul_by_fp_constant>( &self, cs: CS, fe: &P::Fp, ) -> Result { let mut result = self.clone(); result.mul_by_fp_constant_in_place(cs, fe)?; Ok(result) } } impl, ConstraintF: PrimeField> FieldGadget, ConstraintF> for Fp2Gadget { type Variable = (ConstraintVar, ConstraintVar); #[inline] fn get_value(&self) -> Option> { match (self.c0.value, self.c1.value) { (Some(c0), Some(c1)) => Some(Fp2::new(c0, c1)), (..) => None, } } #[inline] fn get_variable(&self) -> Self::Variable { (self.c0.get_variable(), self.c1.get_variable()) } #[inline] fn zero>(mut cs: CS) -> Result { let c0 = FpGadget::zero(cs.ns(|| "c0"))?; let c1 = FpGadget::zero(cs.ns(|| "c1"))?; Ok(Self::new(c0, c1)) } #[inline] fn one>(mut cs: CS) -> Result { let c0 = FpGadget::one(cs.ns(|| "c0"))?; let c1 = FpGadget::zero(cs.ns(|| "c1"))?; Ok(Self::new(c0, c1)) } #[inline] fn conditionally_add_constant>( &self, mut cs: CS, bit: &Boolean, coeff: Fp2

, ) -> Result { let c0 = self .c0 .conditionally_add_constant(cs.ns(|| "c0"), bit, coeff.c0)?; let c1 = self .c1 .conditionally_add_constant(cs.ns(|| "c1"), bit, coeff.c1)?; Ok(Self::new(c0, c1)) } #[inline] fn add>( &self, mut cs: CS, other: &Self, ) -> Result { let c0 = self.c0.add(&mut cs.ns(|| "add c0"), &other.c0)?; let c1 = self.c1.add(&mut cs.ns(|| "add c1"), &other.c1)?; Ok(Self::new(c0, c1)) } #[inline] fn sub>( &self, mut cs: CS, other: &Self, ) -> Result { let c0 = self.c0.sub(&mut cs.ns(|| "sub c0"), &other.c0)?; let c1 = self.c1.sub(&mut cs.ns(|| "sub c1"), &other.c1)?; Ok(Self::new(c0, c1)) } #[inline] fn double>(&self, cs: CS) -> Result { let mut result = self.clone(); result.double_in_place(cs)?; Ok(result) } #[inline] fn double_in_place>( &mut self, mut cs: CS, ) -> Result<&mut Self, SynthesisError> { self.c0.double_in_place(&mut cs.ns(|| "double c0"))?; self.c1.double_in_place(&mut cs.ns(|| "double c1"))?; Ok(self) } #[inline] fn negate>(&self, cs: CS) -> Result { let mut result = self.clone(); result.negate_in_place(cs)?; Ok(result) } #[inline] fn negate_in_place>( &mut self, mut cs: CS, ) -> Result<&mut Self, SynthesisError> { self.c0.negate_in_place(&mut cs.ns(|| "negate c0"))?; self.c1.negate_in_place(&mut cs.ns(|| "negate c1"))?; Ok(self) } #[inline] fn mul>( &self, mut cs: CS, other: &Self, ) -> Result { // Karatsuba multiplication for Fp2: // v0 = A.c0 * B.c0 // v1 = A.c1 * B.c1 // result.c0 = v0 + non_residue * v1 // result.c1 = (A.c0 + A.c1) * (B.c0 + B.c1) - v0 - v1 // Enforced with 3 constraints: // A.c1 * B.c1 = v1 // A.c0 * B.c0 = result.c0 - non_residue * v1 // (A.c0+A.c1)*(B.c0+B.c1) = result.c1 + result.c0 + (1 - non_residue) * v1 // Reference: // "Multiplication and Squaring on Pairing-Friendly Fields" // Devegili, OhEigeartaigh, Scott, Dahab let mul_cs = &mut cs.ns(|| "mul"); let v0 = self.c0.mul(mul_cs.ns(|| "v0"), &other.c0)?; let v1 = self.c1.mul(mul_cs.ns(|| "v1"), &other.c1)?; let c0 = { let non_residue_times_v1 = v1.mul_by_constant(mul_cs.ns(|| "non_residue * v0"), &P::NONRESIDUE)?; v0.add(mul_cs.ns(|| "v0 + beta * v1"), &non_residue_times_v1)? }; let c1 = { let a0_plus_a1 = self.c0.add(mul_cs.ns(|| "a0 + a1"), &self.c1)?; let b0_plus_b1 = other.c0.add(mul_cs.ns(|| "b0 + b1"), &other.c1)?; let a0_plus_a1_times_b0_plus_b1 = a0_plus_a1.mul(&mut mul_cs.ns(|| "(a0 + a1) * (b0 + b1)"), &b0_plus_b1)?; a0_plus_a1_times_b0_plus_b1 .sub(mul_cs.ns(|| "res - v0"), &v0)? .sub(mul_cs.ns(|| "res - v0 - v1"), &v1)? }; Ok(Self::new(c0, c1)) } #[inline] fn square>( &self, mut cs: CS, ) -> Result { // From Libsnark/fp2_gadget.tcc // Complex multiplication for Fp2: // v0 = A.c0 * A.c1 // result.c0 = (A.c0 + A.c1) * (A.c0 + non_residue * A.c1) - (1 + // non_residue) * v0 result.c1 = 2 * v0 // Enforced with 2 constraints: // (2*A.c0) * A.c1 = result.c1 // (A.c0 + A.c1) * (A.c0 + non_residue * A.c1) = result.c0 + result.c1 * (1 // + non_residue)/2 Reference: // "Multiplication and Squaring on Pairing-Friendly Fields" // Devegili, OhEigeartaigh, Scott, Dahab let mut v0 = self.c0.mul(cs.ns(|| "v0"), &self.c1)?; let a0_plus_a1 = self.c0.add(cs.ns(|| "a0 + a1"), &self.c1)?; let non_residue_c1 = self .c1 .mul_by_constant(cs.ns(|| "non_residue * a1"), &P::NONRESIDUE)?; let a0_plus_non_residue_c1 = self .c0 .add(cs.ns(|| "a0 + non_residue * a1"), &non_residue_c1)?; let one_plus_non_residue_v0 = v0.mul_by_constant( cs.ns(|| "1 + non_residue * v0"), &(P::Fp::one() + &P::NONRESIDUE), )?; let c0 = a0_plus_a1 .mul( cs.ns(|| "(a0 + a1) * (a0 + non_residue * a1)"), &a0_plus_non_residue_c1, )? .sub(cs.ns(|| "- (1 + non_residue) v0"), &one_plus_non_residue_v0)?; v0.double_in_place(cs.ns(|| "2v0"))?; let c1 = v0; Ok(Self::new(c0, c1)) } #[inline] fn square_in_place>( &mut self, mut cs: CS, ) -> Result<&mut Self, SynthesisError> { // From Libsnark/fp2_gadget.tcc // Complex multiplication for Fp2: // v0 = A.c0 * A.c1 // result.c0 = (A.c0 + A.c1) * (A.c0 + non_residue * A.c1) - (1 + // non_residue) * v0 result.c1 = 2 * v0 // Enforced with 2 constraints: // (2*A.c0) * A.c1 = result.c1 // (A.c0 + A.c1) * (A.c0 + non_residue * A.c1) = result.c0 + result.c1 * (1 // + non_residue)/2 Reference: // "Multiplication and Squaring on Pairing-Friendly Fields" // Devegili, OhEigeartaigh, Scott, Dahab let mut v0 = self.c0.mul(cs.ns(|| "v0"), &self.c1)?; let a0_plus_a1 = self.c0.add(cs.ns(|| "a0 + a1"), &self.c1)?; let _ = self .c1 .mul_by_constant_in_place(cs.ns(|| "non_residue * a1"), &P::NONRESIDUE)?; let a0_plus_non_residue_c1 = self.c0.add(cs.ns(|| "a0 + non_residue * a1"), &self.c1)?; let one_plus_non_residue_v0 = v0.mul_by_constant( cs.ns(|| "1 + non_residue * v0"), &(P::Fp::one() + &P::NONRESIDUE), )?; self.c0 = a0_plus_a1 .mul( cs.ns(|| "(a0 + a1) * (a0 + non_residue * a1)"), &a0_plus_non_residue_c1, )? .sub(cs.ns(|| "- (1 + non_residue) v0"), &one_plus_non_residue_v0)?; v0.double_in_place(cs.ns(|| "2v0"))?; self.c1 = v0; Ok(self) } fn mul_equals>( &self, mut cs: CS, other: &Self, result: &Self, ) -> Result<(), SynthesisError> { // Karatsuba multiplication for Fp2: // v0 = A.c0 * B.c0 // v1 = A.c1 * B.c1 // result.c0 = v0 + non_residue * v1 // result.c1 = (A.c0 + A.c1) * (B.c0 + B.c1) - v0 - v1 // Enforced with 3 constraints: // A.c1 * B.c1 = v1 // A.c0 * B.c0 = result.c0 - non_residue * v1 // (A.c0+A.c1)*(B.c0+B.c1) = result.c1 + result.c0 + (1 - non_residue) * v1 // Reference: // "Multiplication and Squaring on Pairing-Friendly Fields" // Devegili, OhEigeartaigh, Scott, Dahab let mul_cs = &mut cs.ns(|| "mul"); // Compute v1 let mut v1 = self.c1.mul(mul_cs.ns(|| "v1"), &other.c1)?; // Perform second check let non_residue_times_v1 = v1.mul_by_constant(mul_cs.ns(|| "non_residue * v0"), &P::NONRESIDUE)?; let rhs = result .c0 .sub(mul_cs.ns(|| "sub from result.c0"), &non_residue_times_v1)?; self.c0 .mul_equals(mul_cs.ns(|| "second check"), &other.c0, &rhs)?; // Last check let a0_plus_a1 = self.c0.add(mul_cs.ns(|| "a0 + a1"), &self.c1)?; let b0_plus_b1 = other.c0.add(mul_cs.ns(|| "b0 + b1"), &other.c1)?; let one_minus_non_residue_v1 = v1.sub_in_place(mul_cs.ns(|| "sub from v1"), &non_residue_times_v1)?; let result_c1_plus_result_c0_plus_one_minus_non_residue_v1 = result .c1 .add(mul_cs.ns(|| "c1 + c0"), &result.c0)? .add(mul_cs.ns(|| "rest of stuff"), one_minus_non_residue_v1)?; a0_plus_a1.mul_equals( mul_cs.ns(|| "third check"), &b0_plus_b1, &result_c1_plus_result_c0_plus_one_minus_non_residue_v1, )?; Ok(()) } fn frobenius_map>( &self, cs: CS, power: usize, ) -> Result { let mut result = self.clone(); let _ = result.frobenius_map_in_place(cs, power)?; Ok(result) } fn frobenius_map_in_place>( &mut self, cs: CS, power: usize, ) -> Result<&mut Self, SynthesisError> { self.c1 .mul_by_constant_in_place(cs, &P::FROBENIUS_COEFF_FP2_C1[power % 2])?; Ok(self) } #[inline] fn add_constant>( &self, cs: CS, other: &Fp2

, ) -> Result { let mut result = self.clone(); let _ = result.add_constant_in_place(cs, other)?; Ok(result) } #[inline] fn add_constant_in_place>( &mut self, mut cs: CS, other: &Fp2

, ) -> Result<&mut Self, SynthesisError> { self.c0.add_constant_in_place(cs.ns(|| "c0"), &other.c0)?; self.c1.add_constant_in_place(cs.ns(|| "c1"), &other.c1)?; Ok(self) } fn mul_by_constant>( &self, mut cs: CS, fe: &Fp2

, ) -> Result { // Karatsuba multiplication (see mul above). // Doesn't need any constraints; returns linear combinations of // `self`'s variables. // // (The operations below are guaranteed to return linear combinations) let (a0, a1) = (&self.c0, &self.c1); let (b0, b1) = (fe.c0, fe.c1); let mut v0 = a0.mul_by_constant(&mut cs.ns(|| "v0"), &b0)?; let beta_v1 = a1.mul_by_constant(&mut cs.ns(|| "v1"), &(b1 * &P::NONRESIDUE))?; v0.add_in_place(&mut cs.ns(|| "c0"), &beta_v1)?; let c0 = v0; let mut a0b1 = a0.mul_by_constant(&mut cs.ns(|| "a0b1"), &b1)?; let a1b0 = a1.mul_by_constant(&mut cs.ns(|| "a1b0"), &b0)?; a0b1.add_in_place(&mut cs.ns(|| "c1"), &a1b0)?; let c1 = a0b1; Ok(Self::new(c0, c1)) } fn cost_of_mul() -> usize { 3 * FpGadget::::cost_of_mul() } fn cost_of_mul_equals() -> usize { Self::cost_of_mul() } } impl, ConstraintF: PrimeField> PartialEq for Fp2Gadget { fn eq(&self, other: &Self) -> bool { self.c0 == other.c0 && self.c1 == other.c1 } } impl, ConstraintF: PrimeField> Eq for Fp2Gadget {} impl, ConstraintF: PrimeField> EqGadget for Fp2Gadget { } impl, ConstraintF: PrimeField> ConditionalEqGadget for Fp2Gadget { #[inline] fn conditional_enforce_equal>( &self, mut cs: CS, other: &Self, condition: &Boolean, ) -> Result<(), SynthesisError> { self.c0 .conditional_enforce_equal(&mut cs.ns(|| "c0"), &other.c0, condition)?; self.c1 .conditional_enforce_equal(&mut cs.ns(|| "c1"), &other.c1, condition)?; Ok(()) } fn cost() -> usize { 2 } } impl, ConstraintF: PrimeField> NEqGadget for Fp2Gadget { #[inline] fn enforce_not_equal>( &self, mut cs: CS, other: &Self, ) -> Result<(), SynthesisError> { self.c0.enforce_not_equal(&mut cs.ns(|| "c0"), &other.c0)?; self.c1.enforce_not_equal(&mut cs.ns(|| "c1"), &other.c1)?; Ok(()) } fn cost() -> usize { 2 } } impl, ConstraintF: PrimeField> ToBitsGadget for Fp2Gadget { fn to_bits>( &self, mut cs: CS, ) -> Result, SynthesisError> { let mut c0 = self.c0.to_bits(cs.ns(|| "c0"))?; let mut c1 = self.c1.to_bits(cs.ns(|| "c1"))?; c0.append(&mut c1); Ok(c0) } fn to_non_unique_bits>( &self, mut cs: CS, ) -> Result, SynthesisError> { let mut c0 = self.c0.to_non_unique_bits(cs.ns(|| "c0"))?; let mut c1 = self.c1.to_non_unique_bits(cs.ns(|| "c1"))?; c0.append(&mut c1); Ok(c0) } } impl, ConstraintF: PrimeField> ToBytesGadget for Fp2Gadget { fn to_bytes>( &self, mut cs: CS, ) -> Result, SynthesisError> { let mut c0 = self.c0.to_bytes(cs.ns(|| "c0"))?; let mut c1 = self.c1.to_bytes(cs.ns(|| "c1"))?; c0.append(&mut c1); Ok(c0) } fn to_non_unique_bytes>( &self, mut cs: CS, ) -> Result, SynthesisError> { let mut c0 = self.c0.to_non_unique_bytes(cs.ns(|| "c0"))?; let mut c1 = self.c1.to_non_unique_bytes(cs.ns(|| "c1"))?; c0.append(&mut c1); Ok(c0) } } impl, ConstraintF: PrimeField> Clone for Fp2Gadget { fn clone(&self) -> Self { Self { c0: self.c0.clone(), c1: self.c1.clone(), _params: PhantomData, } } } impl, ConstraintF: PrimeField> CondSelectGadget for Fp2Gadget { #[inline] fn conditionally_select>( mut cs: CS, cond: &Boolean, true_value: &Self, false_value: &Self, ) -> Result { let c0 = FpGadget::::conditionally_select( &mut cs.ns(|| "c0"), cond, &true_value.c0, &false_value.c0, )?; let c1 = FpGadget::::conditionally_select( &mut cs.ns(|| "c1"), cond, &true_value.c1, &false_value.c1, )?; Ok(Self::new(c0, c1)) } fn cost() -> usize { 2 } } impl, ConstraintF: PrimeField> TwoBitLookupGadget for Fp2Gadget { type TableConstant = Fp2

; fn two_bit_lookup>( mut cs: CS, b: &[Boolean], c: &[Self::TableConstant], ) -> Result { let c0s = c.iter().map(|f| f.c0).collect::>(); let c1s = c.iter().map(|f| f.c1).collect::>(); let c0 = FpGadget::two_bit_lookup(cs.ns(|| "Lookup c0"), b, &c0s)?; let c1 = FpGadget::two_bit_lookup(cs.ns(|| "Lookup c1"), b, &c1s)?; Ok(Self::new(c0, c1)) } fn cost() -> usize { 2 * as TwoBitLookupGadget>::cost() } } impl, ConstraintF: PrimeField> ThreeBitCondNegLookupGadget for Fp2Gadget { type TableConstant = Fp2

; fn three_bit_cond_neg_lookup>( mut cs: CS, b: &[Boolean], b0b1: &Boolean, c: &[Self::TableConstant], ) -> Result { let c0s = c.iter().map(|f| f.c0).collect::>(); let c1s = c.iter().map(|f| f.c1).collect::>(); let c0 = FpGadget::three_bit_cond_neg_lookup(cs.ns(|| "Lookup c0"), b, b0b1, &c0s)?; let c1 = FpGadget::three_bit_cond_neg_lookup(cs.ns(|| "Lookup c1"), b, b0b1, &c1s)?; Ok(Self::new(c0, c1)) } fn cost() -> usize { 2 * as ThreeBitCondNegLookupGadget>::cost() } } impl, ConstraintF: PrimeField> AllocGadget, ConstraintF> for Fp2Gadget { #[inline] fn alloc>( mut cs: CS, value_gen: F, ) -> Result where F: FnOnce() -> Result, T: Borrow>, { let (c0, c1) = match value_gen() { Ok(fe) => { let fe = *fe.borrow(); (Ok(fe.c0), Ok(fe.c1)) } Err(_) => ( Err(SynthesisError::AssignmentMissing), Err(SynthesisError::AssignmentMissing), ), }; let c0 = FpGadget::alloc(&mut cs.ns(|| "c0"), || c0)?; let c1 = FpGadget::alloc(&mut cs.ns(|| "c1"), || c1)?; Ok(Self::new(c0, c1)) } #[inline] fn alloc_input>( mut cs: CS, value_gen: F, ) -> Result where F: FnOnce() -> Result, T: Borrow>, { let (c0, c1) = match value_gen() { Ok(fe) => { let fe = *fe.borrow(); (Ok(fe.c0), Ok(fe.c1)) } Err(_) => ( Err(SynthesisError::AssignmentMissing), Err(SynthesisError::AssignmentMissing), ), }; let c0 = FpGadget::alloc_input(&mut cs.ns(|| "c0"), || c0)?; let c1 = FpGadget::alloc_input(&mut cs.ns(|| "c1"), || c1)?; Ok(Self::new(c0, c1)) } }