use algebra::{
fields::{Fp2, Fp2Parameters, Fp4, Fp4Parameters},
BigInteger, PrimeField,
};
use core::{borrow::Borrow, marker::PhantomData};
use r1cs_core::{ConstraintSystem, SynthesisError};
use crate::{prelude::*, Vec};
type Fp2Gadget
=
super::fp2::Fp2Gadget<
::Fp2Params, ConstraintF>;
type Fp2GadgetVariable
= as FieldGadget<
Fp2<::Fp2Params>,
ConstraintF,
>>::Variable;
#[derive(Derivative)]
#[derivative(Debug(bound = "ConstraintF: PrimeField"))]
#[must_use]
pub struct Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
pub c0: Fp2Gadget,
pub c1: Fp2Gadget
,
#[derivative(Debug = "ignore")]
_params: PhantomData
,
}
impl
Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
pub fn new(c0: Fp2Gadget, c1: Fp2Gadget
) -> Self {
Self {
c0,
c1,
_params: PhantomData,
}
}
/// Multiply a Fp2Gadget by quadratic nonresidue P::NONRESIDUE.
#[inline]
pub fn mul_fp2_gadget_by_nonresidue>(
cs: CS,
fe: &Fp2Gadget,
) -> Result, SynthesisError> {
let new_c0 = Fp2Gadget::::mul_fp_gadget_by_nonresidue(cs, &fe.c1)?;
let new_c1 = fe.c0.clone();
Ok(Fp2Gadget::
::new(new_c0, new_c1))
}
/// Multiply a Fp4Gadget by an element of fp.
#[inline]
pub fn mul_by_fp_constant_in_place>(
&mut self,
mut cs: CS,
fe: &<::Fp2Params as Fp2Parameters>::Fp,
) -> Result<&mut Self, SynthesisError> {
self.c0.mul_by_fp_constant_in_place(cs.ns(|| "c0"), fe)?;
self.c1.mul_by_fp_constant_in_place(cs.ns(|| "c1"), fe)?;
Ok(self)
}
/// Multiply a Fp4Gadget by an element of fp.
#[inline]
pub fn mul_by_fp_constant>(
&self,
cs: CS,
fe: &<::Fp2Params as Fp2Parameters>::Fp,
) -> Result {
let mut result = self.clone();
result.mul_by_fp_constant_in_place(cs, fe)?;
Ok(result)
}
pub fn unitary_inverse>(
&self,
cs: CS,
) -> Result {
Ok(Self::new(self.c0.clone(), self.c1.negate(cs)?))
}
#[inline]
pub fn cyclotomic_exp, B: BigInteger>(
&self,
mut cs: CS,
exponent: &B,
) -> Result {
let mut res = Self::one(cs.ns(|| "one"))?;
let self_inverse = self.unitary_inverse(cs.ns(|| "unitary inverse"))?;
let mut found_nonzero = false;
let naf = exponent.find_wnaf();
for (i, &value) in naf.iter().rev().enumerate() {
if found_nonzero {
res.square_in_place(cs.ns(|| format!("square {}", i)))?;
}
if value != 0 {
found_nonzero = true;
if value > 0 {
res.mul_in_place(cs.ns(|| format!("res *= self {}", i)), &self)?;
} else {
res.mul_in_place(
cs.ns(|| format!("res *= self_inverse {}", i)),
&self_inverse,
)?;
}
}
}
Ok(res)
}
}
impl FieldGadget, ConstraintF> for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
type Variable = (
Fp2GadgetVariable,
Fp2GadgetVariable
,
);
#[inline]
fn get_value(&self) -> Option> {
match (self.c0.get_value(), self.c1.get_value()) {
(Some(c0), Some(c1)) => Some(Fp4::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 = Fp2Gadget::::zero(cs.ns(|| "c0"))?;
let c1 = Fp2Gadget::
::zero(cs.ns(|| "c1"))?;
Ok(Self::new(c0, c1))
}
#[inline]
fn one>(mut cs: CS) -> Result {
let c0 = Fp2Gadget::::one(cs.ns(|| "c0"))?;
let c1 = Fp2Gadget::
::zero(cs.ns(|| "c1"))?;
Ok(Self::new(c0, c1))
}
#[inline]
fn conditionally_add_constant>(
&self,
mut cs: CS,
bit: &Boolean,
coeff: Fp4,
) -> 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 Fp4:
// 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 =
Self::mul_fp2_gadget_by_nonresidue(mul_cs.ns(|| "first mul_by_nr"), &v1)?;
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/fp4_gadget.tcc
// Complex multiplication for Fp4:
// 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::mul_fp2_gadget_by_nonresidue(cs.ns(|| "non_residue * a1"), &self.c1)?;
let a0_plus_non_residue_c1 = self
.c0
.add(cs.ns(|| "a0 + non_residue * a1"), &non_residue_c1)?;
let one_plus_non_residue_v0 =
Self::mul_fp2_gadget_by_nonresidue(cs.ns(|| "non_residue * v0"), &v0)?
.add(cs.ns(|| "plus v0"), &v0)?;
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))
}
fn mul_equals>(
&self,
mut cs: CS,
other: &Self,
result: &Self,
) -> Result<(), SynthesisError> {
// Karatsuba multiplication for Fp4:
// 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 =
Self::mul_fp2_gadget_by_nonresidue(mul_cs.ns(|| "nr * v1"), &v1)?;
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,
mut cs: CS,
power: usize,
) -> Result<&mut Self, SynthesisError> {
self.c0
.frobenius_map_in_place(cs.ns(|| "frob_map1"), power)?;
self.c1
.frobenius_map_in_place(cs.ns(|| "frob_map2"), power)?;
self.c1
.mul_by_fp_constant_in_place(cs.ns(|| "mul"), &P::FROBENIUS_COEFF_FP4_C1[power % 4])?;
Ok(self)
}
#[inline]
fn add_constant>(
&self,
cs: CS,
other: &Fp4,
) -> 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: &Fp4,
) -> 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: &Fp4,
) -> 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 mut v1 = Self::mul_fp2_gadget_by_nonresidue(&mut cs.ns(|| "v1"), a1)?;
let beta_v1 = v1.mul_by_constant_in_place(&mut cs.ns(|| "beta * v1"), &b1)?;
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 * Fp2Gadget::::cost_of_mul()
}
fn cost_of_mul_equals() -> usize {
Self::cost_of_mul()
}
}
impl
PartialEq for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
fn eq(&self, other: &Self) -> bool {
self.c0 == other.c0 && self.c1 == other.c1
}
}
impl Eq for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
}
impl EqGadget for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
}
impl ConditionalEqGadget for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
#[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 * as ConditionalEqGadget>::cost()
}
}
impl NEqGadget for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
#[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 * as NEqGadget>::cost()
}
}
impl ToBitsGadget for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
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 ToBytesGadget for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
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 Clone for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
fn clone(&self) -> Self {
Self {
c0: self.c0.clone(),
c1: self.c1.clone(),
_params: PhantomData,
}
}
}
impl CondSelectGadget for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
#[inline]
fn conditionally_select>(
mut cs: CS,
cond: &Boolean,
first: &Self,
second: &Self,
) -> Result {
let c0 = Fp2Gadget::::conditionally_select(
&mut cs.ns(|| "c0"),
cond,
&first.c0,
&second.c0,
)?;
let c1 = Fp2Gadget::
::conditionally_select(
&mut cs.ns(|| "c1"),
cond,
&first.c1,
&second.c1,
)?;
Ok(Self::new(c0, c1))
}
fn cost() -> usize {
2 * as CondSelectGadget>::cost()
}
}
impl TwoBitLookupGadget for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
type TableConstant = Fp4;
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 = Fp2Gadget::::two_bit_lookup(cs.ns(|| "Lookup c0"), b, &c0s)?;
let c1 = Fp2Gadget::
::two_bit_lookup(cs.ns(|| "Lookup c1"), b, &c1s)?;
Ok(Self::new(c0, c1))
}
fn cost() -> usize {
2 * as TwoBitLookupGadget>::cost()
}
}
impl ThreeBitCondNegLookupGadget
for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
type TableConstant = Fp4;
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 = Fp2Gadget::::three_bit_cond_neg_lookup(
cs.ns(|| "Lookup c0"),
b,
b0b1,
&c0s,
)?;
let c1 = Fp2Gadget::
::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 AllocGadget, ConstraintF> for Fp4Gadget
where
P: Fp4Parameters,
P::Fp2Params: Fp2Parameters,
{
#[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 = Fp2Gadget::::alloc(&mut cs.ns(|| "c0"), || c0)?;
let c1 = Fp2Gadget::
::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 = Fp2Gadget::::alloc_input(&mut cs.ns(|| "c0"), || c0)?;
let c1 = Fp2Gadget::
::alloc_input(&mut cs.ns(|| "c1"), || c1)?;
Ok(Self::new(c0, c1))
}
}