Update to arkworks libraries (#3)

Co-authored-by: Nicholas Ward <npward@berkeley.edu>
This commit is contained in:
Pratyush Mishra
2020-10-19 11:07:59 -07:00
committed by GitHub
parent cf4301cb75
commit 636f93a3e5
122 changed files with 910 additions and 9328 deletions

View File

@@ -0,0 +1,579 @@
use ark_ff::{
fields::{CubicExtField, CubicExtParameters, Field},
Zero,
};
use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError};
use core::{borrow::Borrow, marker::PhantomData};
use crate::{
fields::{fp::FpVar, FieldOpsBounds, FieldVar},
prelude::*,
ToConstraintFieldGadget, Vec,
};
/// This struct is the `R1CS` equivalent of the cubic extension field type
/// in `ark-ff`, i.e. `ark_ff::CubicExtField`.
#[derive(Derivative)]
#[derivative(Debug(bound = "BF: core::fmt::Debug"), Clone(bound = "BF: Clone"))]
#[must_use]
pub struct CubicExtVar<BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarParams<BF>>
where
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
{
/// The zero-th coefficient of this field element.
pub c0: BF,
/// The first coefficient of this field element.
pub c1: BF,
/// The second coefficient of this field element.
pub c2: BF,
#[derivative(Debug = "ignore")]
_params: PhantomData<P>,
}
/// This trait describes parameters that are used to implement arithmetic for
/// `CubicExtVar`.
pub trait CubicExtVarParams<BF: FieldVar<Self::BaseField, Self::BasePrimeField>>:
CubicExtParameters
where
for<'a> &'a BF: FieldOpsBounds<'a, Self::BaseField, BF>,
{
/// Multiply the base field of the `CubicExtVar` by the appropriate
/// Frobenius coefficient. This is equivalent to
/// `Self::mul_base_field_by_frob_coeff(c1, c2, power)`.
fn mul_base_field_vars_by_frob_coeff(c1: &mut BF, c2: &mut BF, power: usize);
}
impl<BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarParams<BF>> CubicExtVar<BF, P>
where
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
{
/// Constructs a `CubicExtVar` from the underlying coefficients.
#[inline]
pub fn new(c0: BF, c1: BF, c2: BF) -> Self {
let _params = PhantomData;
Self {
c0,
c1,
c2,
_params,
}
}
/// Multiplies a variable of the base field by the cubic nonresidue
/// `P::NONRESIDUE` that is used to construct the extension field.
#[inline]
pub fn mul_base_field_by_nonresidue(fe: &BF) -> Result<BF, SynthesisError> {
Ok(fe * P::NONRESIDUE)
}
/// Multiplies `self` by a constant from the base field.
#[inline]
pub fn mul_by_base_field_constant(&self, fe: P::BaseField) -> Self {
let c0 = &self.c0 * fe;
let c1 = &self.c1 * fe;
let c2 = &self.c2 * fe;
Self::new(c0, c1, c2)
}
/// Sets `self = self.mul_by_base_field_constant(fe)`.
#[inline]
pub fn mul_assign_by_base_field_constant(&mut self, fe: P::BaseField) {
*self = (&*self).mul_by_base_field_constant(fe);
}
}
impl<BF, P> R1CSVar<P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
type Value = CubicExtField<P>;
fn cs(&self) -> ConstraintSystemRef<P::BasePrimeField> {
[&self.c0, &self.c1, &self.c2].cs()
}
#[inline]
fn value(&self) -> Result<Self::Value, SynthesisError> {
match (self.c0.value(), self.c1.value(), self.c2.value()) {
(Ok(c0), Ok(c1), Ok(c2)) => Ok(CubicExtField::new(c0, c1, c2)),
(..) => Err(SynthesisError::AssignmentMissing),
}
}
}
impl<BF, P> From<Boolean<P::BasePrimeField>> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
fn from(other: Boolean<P::BasePrimeField>) -> Self {
let c0 = BF::from(other);
let c1 = BF::zero();
let c2 = BF::zero();
Self::new(c0, c1, c2)
}
}
impl<'a, BF, P> FieldOpsBounds<'a, CubicExtField<P>, CubicExtVar<BF, P>> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
}
impl<'a, BF, P> FieldOpsBounds<'a, CubicExtField<P>, CubicExtVar<BF, P>> for &'a CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
}
impl<BF, P> FieldVar<CubicExtField<P>, P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
fn constant(other: CubicExtField<P>) -> Self {
let c0 = BF::constant(other.c0);
let c1 = BF::constant(other.c1);
let c2 = BF::constant(other.c2);
Self::new(c0, c1, c2)
}
fn zero() -> Self {
let c0 = BF::zero();
let c1 = BF::zero();
let c2 = BF::zero();
Self::new(c0, c1, c2)
}
fn one() -> Self {
let c0 = BF::one();
let c1 = BF::zero();
let c2 = BF::zero();
Self::new(c0, c1, c2)
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn double(&self) -> Result<Self, SynthesisError> {
let c0 = self.c0.double()?;
let c1 = self.c1.double()?;
let c2 = self.c2.double()?;
Ok(Self::new(c0, c1, c2))
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn negate(&self) -> Result<Self, SynthesisError> {
let mut result = self.clone();
result.c0.negate_in_place()?;
result.c1.negate_in_place()?;
result.c2.negate_in_place()?;
Ok(result)
}
/// Use the Chung-Hasan asymmetric squaring formula.
///
/// (Devegili OhEig Scott Dahab --- Multiplication and Squaring on
/// Abstract Pairing-Friendly
/// Fields.pdf; Section 4 (CH-SQR2))
#[inline]
#[tracing::instrument(target = "r1cs")]
fn square(&self) -> Result<Self, SynthesisError> {
let a = self.c0.clone();
let b = self.c1.clone();
let c = self.c2.clone();
let s0 = a.square()?;
let ab = &a * &b;
let s1 = ab.double()?;
let s2 = (&a - &b + &c).square()?;
let s3 = (&b * &c).double()?;
let s4 = c.square()?;
let c0 = Self::mul_base_field_by_nonresidue(&s3)? + &s0;
let c1 = Self::mul_base_field_by_nonresidue(&s4)? + &s1;
let c2 = s1 + &s2 + &s3 - &s0 - &s4;
Ok(Self::new(c0, c1, c2))
}
#[tracing::instrument(target = "r1cs")]
fn mul_equals(&self, other: &Self, result: &Self) -> Result<(), SynthesisError> {
// Karatsuba multiplication for cubic extensions:
// v0 = A.c0 * B.c0
// v1 = A.c1 * B.c1
// v2 = A.c2 * B.c2
// result.c0 = v0 + β((a1 + a2)(b1 + b2) v1 v2)
// result.c1 = (a0 + a1)(b0 + b1) v0 v1 + βv2
// result.c2 = (a0 + a2)(b0 + b2) v0 + v1 v2,
// We enforce this with six constraints:
//
// v0 = A.c0 * B.c0
// v1 = A.c1 * B.c1
// v2 = A.c2 * B.c2
//
// result.c0 - v0 + \beta*(v1 + v2) = β(a1 + a2)(b1 + b2))
// result.c1 + v0 + v1 - βv2 = (a0 + a1)(b0 + b1)
// result.c2 + v0 - v1 + v2 = (a0 + a2)(b0 + b2)
// Reference:
// "Multiplication and Squaring on Pairing-Friendly Fields"
// Devegili, OhEigeartaigh, Scott, Dahab
//
// This implementation adapted from
// https://github.com/ZencashOfficial/ginger-lib/blob/development/r1cs/gadgets/std/src/fields/fp3.rs
let v0 = &self.c0 * &other.c0;
let v1 = &self.c1 * &other.c1;
let v2 = &self.c2 * &other.c2;
// Check c0
let nr_a1_plus_a2 = (&self.c1 + &self.c2) * P::NONRESIDUE;
let b1_plus_b2 = &other.c1 + &other.c2;
let nr_v1 = &v1 * P::NONRESIDUE;
let nr_v2 = &v2 * P::NONRESIDUE;
let to_check = &result.c0 - &v0 + &nr_v1 + &nr_v2;
nr_a1_plus_a2.mul_equals(&b1_plus_b2, &to_check)?;
// Check c1
let a0_plus_a1 = &self.c0 + &self.c1;
let b0_plus_b1 = &other.c0 + &other.c1;
let to_check = &result.c1 - &nr_v2 + &v0 + &v1;
a0_plus_a1.mul_equals(&b0_plus_b1, &to_check)?;
// Check c2
let a0_plus_a2 = &self.c0 + &self.c2;
let b0_plus_b2 = &other.c0 + &other.c2;
let to_check = &result.c2 + &v0 - &v1 + &v2;
a0_plus_a2.mul_equals(&b0_plus_b2, &to_check)?;
Ok(())
}
#[tracing::instrument(target = "r1cs")]
fn frobenius_map(&self, power: usize) -> Result<Self, SynthesisError> {
let mut result = self.clone();
result.c0.frobenius_map_in_place(power)?;
result.c1.frobenius_map_in_place(power)?;
result.c2.frobenius_map_in_place(power)?;
P::mul_base_field_vars_by_frob_coeff(&mut result.c1, &mut result.c2, power);
Ok(result)
}
#[tracing::instrument(target = "r1cs")]
fn inverse(&self) -> Result<Self, SynthesisError> {
let mode = if self.is_constant() {
AllocationMode::Constant
} else {
AllocationMode::Witness
};
let inverse = Self::new_variable(
self.cs(),
|| {
self.value()
.map(|f| f.inverse().unwrap_or(CubicExtField::zero()))
},
mode,
)?;
self.mul_equals(&inverse, &Self::one())?;
Ok(inverse)
}
}
impl_bounded_ops!(
CubicExtVar<BF, P>,
CubicExtField<P>,
Add,
add,
AddAssign,
add_assign,
|this: &'a CubicExtVar<BF, P>, other: &'a CubicExtVar<BF, P>| {
let c0 = &this.c0 + &other.c0;
let c1 = &this.c1 + &other.c1;
let c2 = &this.c2 + &other.c2;
CubicExtVar::new(c0, c1, c2)
},
|this: &'a CubicExtVar<BF, P>, other: CubicExtField<P>| {
this + CubicExtVar::constant(other)
},
(BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarParams<BF>),
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
);
impl_bounded_ops!(
CubicExtVar<BF, P>,
CubicExtField<P>,
Sub,
sub,
SubAssign,
sub_assign,
|this: &'a CubicExtVar<BF, P>, other: &'a CubicExtVar<BF, P>| {
let c0 = &this.c0 - &other.c0;
let c1 = &this.c1 - &other.c1;
let c2 = &this.c2 - &other.c2;
CubicExtVar::new(c0, c1, c2)
},
|this: &'a CubicExtVar<BF, P>, other: CubicExtField<P>| {
this - CubicExtVar::constant(other)
},
(BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarParams<BF>),
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
);
impl_bounded_ops!(
CubicExtVar<BF, P>,
CubicExtField<P>,
Mul,
mul,
MulAssign,
mul_assign,
|this: &'a CubicExtVar<BF, P>, other: &'a CubicExtVar<BF, P>| {
// Karatsuba multiplication for cubic extensions:
// v0 = A.c0 * B.c0
// v1 = A.c1 * B.c1
// v2 = A.c2 * B.c2
// result.c0 = v0 + β((a1 + a2)(b1 + b2) v1 v2)
// result.c1 = (a0 + a1)(b0 + b1) v0 v1 + βv2
// result.c2 = (a0 + a2)(b0 + b2) v0 + v1 v2,
//
// Reference:
// "Multiplication and Squaring on Pairing-Friendly Fields"
// Devegili, OhEigeartaigh, Scott, Dahab
let v0 = &this.c0 * &other.c0;
let v1 = &this.c1 * &other.c1;
let v2 = &this.c2 * &other.c2;
let c0 =
(((&this.c1 + &this.c2) * (&other.c1 + &other.c2) - &v1 - &v2) * P::NONRESIDUE) + &v0 ;
let c1 =
(&this.c0 + &this.c1) * (&other.c0 + &other.c1) - &v0 - &v1 + (&v2 * P::NONRESIDUE);
let c2 =
(&this.c0 + &this.c2) * (&other.c0 + &other.c2) - &v0 + &v1 - &v2;
CubicExtVar::new(c0, c1, c2)
},
|this: &'a CubicExtVar<BF, P>, other: CubicExtField<P>| {
this * CubicExtVar::constant(other)
},
(BF: FieldVar<P::BaseField, P::BasePrimeField>, P: CubicExtVarParams<BF>),
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
);
impl<BF, P> EqGadget<P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
#[tracing::instrument(target = "r1cs")]
fn is_eq(&self, other: &Self) -> Result<Boolean<P::BasePrimeField>, SynthesisError> {
let b0 = self.c0.is_eq(&other.c0)?;
let b1 = self.c1.is_eq(&other.c1)?;
let b2 = self.c2.is_eq(&other.c2)?;
b0.and(&b1)?.and(&b2)
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn conditional_enforce_equal(
&self,
other: &Self,
condition: &Boolean<P::BasePrimeField>,
) -> Result<(), SynthesisError> {
self.c0.conditional_enforce_equal(&other.c0, condition)?;
self.c1.conditional_enforce_equal(&other.c1, condition)?;
self.c2.conditional_enforce_equal(&other.c2, condition)?;
Ok(())
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn conditional_enforce_not_equal(
&self,
other: &Self,
condition: &Boolean<P::BasePrimeField>,
) -> Result<(), SynthesisError> {
let is_equal = self.is_eq(other)?;
is_equal
.and(condition)?
.enforce_equal(&Boolean::Constant(false))
}
}
impl<BF, P> ToBitsGadget<P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
#[tracing::instrument(target = "r1cs")]
fn to_bits_le(&self) -> Result<Vec<Boolean<P::BasePrimeField>>, SynthesisError> {
let mut c0 = self.c0.to_bits_le()?;
let mut c1 = self.c1.to_bits_le()?;
let mut c2 = self.c2.to_bits_le()?;
c0.append(&mut c1);
c0.append(&mut c2);
Ok(c0)
}
#[tracing::instrument(target = "r1cs")]
fn to_non_unique_bits_le(&self) -> Result<Vec<Boolean<P::BasePrimeField>>, SynthesisError> {
let mut c0 = self.c0.to_non_unique_bits_le()?;
let mut c1 = self.c1.to_non_unique_bits_le()?;
let mut c2 = self.c2.to_non_unique_bits_le()?;
c0.append(&mut c1);
c0.append(&mut c2);
Ok(c0)
}
}
impl<BF, P> ToBytesGadget<P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
#[tracing::instrument(target = "r1cs")]
fn to_bytes(&self) -> Result<Vec<UInt8<P::BasePrimeField>>, SynthesisError> {
let mut c0 = self.c0.to_bytes()?;
let mut c1 = self.c1.to_bytes()?;
let mut c2 = self.c2.to_bytes()?;
c0.append(&mut c1);
c0.append(&mut c2);
Ok(c0)
}
#[tracing::instrument(target = "r1cs")]
fn to_non_unique_bytes(&self) -> Result<Vec<UInt8<P::BasePrimeField>>, SynthesisError> {
let mut c0 = self.c0.to_non_unique_bytes()?;
let mut c1 = self.c1.to_non_unique_bytes()?;
let mut c2 = self.c2.to_non_unique_bytes()?;
c0.append(&mut c1);
c0.append(&mut c2);
Ok(c0)
}
}
impl<BF, P> ToConstraintFieldGadget<P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
BF: ToConstraintFieldGadget<P::BasePrimeField>,
{
#[tracing::instrument(target = "r1cs")]
fn to_constraint_field(&self) -> Result<Vec<FpVar<P::BasePrimeField>>, SynthesisError> {
let mut res = Vec::new();
res.extend_from_slice(&self.c0.to_constraint_field()?);
res.extend_from_slice(&self.c1.to_constraint_field()?);
res.extend_from_slice(&self.c2.to_constraint_field()?);
Ok(res)
}
}
impl<BF, P> CondSelectGadget<P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
#[inline]
#[tracing::instrument(target = "r1cs")]
fn conditionally_select(
cond: &Boolean<P::BasePrimeField>,
true_value: &Self,
false_value: &Self,
) -> Result<Self, SynthesisError> {
let c0 = BF::conditionally_select(cond, &true_value.c0, &false_value.c0)?;
let c1 = BF::conditionally_select(cond, &true_value.c1, &false_value.c1)?;
let c2 = BF::conditionally_select(cond, &true_value.c2, &false_value.c2)?;
Ok(Self::new(c0, c1, c2))
}
}
impl<BF, P> TwoBitLookupGadget<P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>
+ TwoBitLookupGadget<P::BasePrimeField, TableConstant = P::BaseField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
type TableConstant = CubicExtField<P>;
#[tracing::instrument(target = "r1cs")]
fn two_bit_lookup(
b: &[Boolean<P::BasePrimeField>],
c: &[Self::TableConstant],
) -> Result<Self, SynthesisError> {
let c0s = c.iter().map(|f| f.c0).collect::<Vec<_>>();
let c1s = c.iter().map(|f| f.c1).collect::<Vec<_>>();
let c2s = c.iter().map(|f| f.c2).collect::<Vec<_>>();
let c0 = BF::two_bit_lookup(b, &c0s)?;
let c1 = BF::two_bit_lookup(b, &c1s)?;
let c2 = BF::two_bit_lookup(b, &c2s)?;
Ok(Self::new(c0, c1, c2))
}
}
impl<BF, P> ThreeBitCondNegLookupGadget<P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>
+ ThreeBitCondNegLookupGadget<P::BasePrimeField, TableConstant = P::BaseField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
type TableConstant = CubicExtField<P>;
#[tracing::instrument(target = "r1cs")]
fn three_bit_cond_neg_lookup(
b: &[Boolean<P::BasePrimeField>],
b0b1: &Boolean<P::BasePrimeField>,
c: &[Self::TableConstant],
) -> Result<Self, SynthesisError> {
let c0s = c.iter().map(|f| f.c0).collect::<Vec<_>>();
let c1s = c.iter().map(|f| f.c1).collect::<Vec<_>>();
let c2s = c.iter().map(|f| f.c2).collect::<Vec<_>>();
let c0 = BF::three_bit_cond_neg_lookup(b, b0b1, &c0s)?;
let c1 = BF::three_bit_cond_neg_lookup(b, b0b1, &c1s)?;
let c2 = BF::three_bit_cond_neg_lookup(b, b0b1, &c2s)?;
Ok(Self::new(c0, c1, c2))
}
}
impl<BF, P> AllocVar<CubicExtField<P>, P::BasePrimeField> for CubicExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: CubicExtVarParams<BF>,
{
fn new_variable<T: Borrow<CubicExtField<P>>>(
cs: impl Into<Namespace<P::BasePrimeField>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
let ns = cs.into();
let cs = ns.cs();
use SynthesisError::*;
let (c0, c1, c2) = match f() {
Ok(fe) => (Ok(fe.borrow().c0), Ok(fe.borrow().c1), Ok(fe.borrow().c2)),
Err(_) => (
Err(AssignmentMissing),
Err(AssignmentMissing),
Err(AssignmentMissing),
),
};
let c0 = BF::new_variable(ark_relations::ns!(cs, "c0"), || c0, mode)?;
let c1 = BF::new_variable(ark_relations::ns!(cs, "c1"), || c1, mode)?;
let c2 = BF::new_variable(ark_relations::ns!(cs, "c2"), || c2, mode)?;
Ok(Self::new(c0, c1, c2))
}
}

247
src/fields/fp/cmp.rs Normal file
View File

@@ -0,0 +1,247 @@
use crate::{
boolean::Boolean,
fields::{fp::FpVar, FieldVar},
prelude::*,
ToBitsGadget,
};
use ark_ff::PrimeField;
use ark_relations::r1cs::{SynthesisError, Variable};
use core::cmp::Ordering;
impl<F: PrimeField> FpVar<F> {
/// This function enforces the ordering between `self` and `other`. The
/// constraint system will not be satisfied otherwise. If `self` should
/// also be checked for equality, e.g. `self <= other` instead of `self <
/// other`, set `should_also_check_quality` to `true`. This variant
/// verifies `self` and `other` are `<= (p-1)/2`.
#[tracing::instrument(target = "r1cs")]
pub fn enforce_cmp(
&self,
other: &FpVar<F>,
ordering: Ordering,
should_also_check_equality: bool,
) -> Result<(), SynthesisError> {
let (left, right) = self.process_cmp_inputs(other, ordering, should_also_check_equality)?;
left.enforce_smaller_than(&right)
}
/// This function enforces the ordering between `self` and `other`. The
/// constraint system will not be satisfied otherwise. If `self` should
/// also be checked for equality, e.g. `self <= other` instead of `self <
/// other`, set `should_also_check_quality` to `true`. This variant
/// assumes `self` and `other` are `<= (p-1)/2` and does not generate
/// constraints to verify that.
#[tracing::instrument(target = "r1cs")]
pub fn enforce_cmp_unchecked(
&self,
other: &FpVar<F>,
ordering: Ordering,
should_also_check_equality: bool,
) -> Result<(), SynthesisError> {
let (left, right) = self.process_cmp_inputs(other, ordering, should_also_check_equality)?;
left.enforce_smaller_than_unchecked(&right)
}
/// This function checks the ordering between `self` and `other`. It outputs
/// self `Boolean` that contains the result - `1` if true, `0`
/// otherwise. The constraint system will be satisfied in any case. If
/// `self` should also be checked for equality, e.g. `self <= other`
/// instead of `self < other`, set `should_also_check_quality` to
/// `true`. This variant verifies `self` and `other` are `<= (p-1)/2`.
#[tracing::instrument(target = "r1cs")]
pub fn is_cmp(
&self,
other: &FpVar<F>,
ordering: Ordering,
should_also_check_equality: bool,
) -> Result<Boolean<F>, SynthesisError> {
let (left, right) = self.process_cmp_inputs(other, ordering, should_also_check_equality)?;
left.is_smaller_than(&right)
}
/// This function checks the ordering between `self` and `other`. It outputs
/// a `Boolean` that contains the result - `1` if true, `0` otherwise.
/// The constraint system will be satisfied in any case. If `self`
/// should also be checked for equality, e.g. `self <= other` instead of
/// `self < other`, set `should_also_check_quality` to `true`. This
/// variant assumes `self` and `other` are `<= (p-1)/2` and does not
/// generate constraints to verify that.
#[tracing::instrument(target = "r1cs")]
pub fn is_cmp_unchecked(
&self,
other: &FpVar<F>,
ordering: Ordering,
should_also_check_equality: bool,
) -> Result<Boolean<F>, SynthesisError> {
let (left, right) = self.process_cmp_inputs(other, ordering, should_also_check_equality)?;
left.is_smaller_than_unchecked(&right)
}
fn process_cmp_inputs(
&self,
other: &Self,
ordering: Ordering,
should_also_check_equality: bool,
) -> Result<(Self, Self), SynthesisError> {
let (left, right) = match ordering {
Ordering::Less => (self, other),
Ordering::Greater => (other, self),
Ordering::Equal => Err(SynthesisError::Unsatisfiable)?,
};
let right_for_check = if should_also_check_equality {
right + F::one()
} else {
right.clone()
};
Ok((left.clone(), right_for_check))
}
/// Helper function to enforce that `self <= (p-1)/2`.
#[tracing::instrument(target = "r1cs")]
pub fn enforce_smaller_or_equal_than_mod_minus_one_div_two(
&self,
) -> Result<(), SynthesisError> {
// It's okay to use `to_non_unique_bits` bits here because we're enforcing
// self <= (p-1)/2, which implies self < p.
let _ = Boolean::enforce_smaller_or_equal_than_le(
&self.to_non_unique_bits_le()?,
F::modulus_minus_one_div_two(),
)?;
Ok(())
}
/// Helper function to check `self < other` and output a result bit. This
/// function verifies `self` and `other` are `<= (p-1)/2`.
fn is_smaller_than(&self, other: &FpVar<F>) -> Result<Boolean<F>, SynthesisError> {
self.enforce_smaller_or_equal_than_mod_minus_one_div_two()?;
other.enforce_smaller_or_equal_than_mod_minus_one_div_two()?;
self.is_smaller_than_unchecked(other)
}
/// Helper function to check `self < other` and output a result bit. This
/// function assumes `self` and `other` are `<= (p-1)/2` and does not
/// generate constraints to verify that.
fn is_smaller_than_unchecked(&self, other: &FpVar<F>) -> Result<Boolean<F>, SynthesisError> {
Ok((self - other)
.double()?
.to_bits_le()?
.first()
.unwrap()
.clone())
}
/// Helper function to enforce `self < other`. This function verifies `self`
/// and `other` are `<= (p-1)/2`.
fn enforce_smaller_than(&self, other: &FpVar<F>) -> Result<(), SynthesisError> {
self.enforce_smaller_or_equal_than_mod_minus_one_div_two()?;
other.enforce_smaller_or_equal_than_mod_minus_one_div_two()?;
self.enforce_smaller_than_unchecked(other)
}
/// Helper function to enforce `self < other`. This function assumes `self`
/// and `other` are `<= (p-1)/2` and does not generate constraints to
/// verify that.
fn enforce_smaller_than_unchecked(&self, other: &FpVar<F>) -> Result<(), SynthesisError> {
let is_smaller_than = self.is_smaller_than_unchecked(other)?;
let lc_one = lc!() + Variable::One;
[self, other]
.cs()
.enforce_constraint(is_smaller_than.lc(), lc_one.clone(), lc_one)
}
}
#[cfg(test)]
mod test {
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use std::cmp::Ordering;
use crate::{alloc::AllocVar, fields::fp::FpVar};
use ark_ff::{PrimeField, UniformRand};
use ark_relations::r1cs::ConstraintSystem;
use ark_test_curves::bls12_381::Fr;
#[test]
fn test_cmp() {
let mut rng = &mut XorShiftRng::from_seed([
0x5d, 0xbe, 0x62, 0x59, 0x8d, 0x31, 0x3d, 0x76, 0x32, 0x37, 0xdb, 0x17, 0xe5, 0xbc,
0x06, 0x54,
]);
fn rand_in_range<R: Rng>(rng: &mut R) -> Fr {
let pminusonedivtwo: Fr = Fr::modulus_minus_one_div_two().into();
let mut r;
loop {
r = Fr::rand(rng);
if r <= pminusonedivtwo {
break;
}
}
r
}
for i in 0..10 {
let cs = ConstraintSystem::<Fr>::new_ref();
let a = rand_in_range(&mut rng);
let a_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(a)).unwrap();
let b = rand_in_range(&mut rng);
let b_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(b)).unwrap();
match a.cmp(&b) {
Ordering::Less => {
a_var.enforce_cmp(&b_var, Ordering::Less, false).unwrap();
a_var.enforce_cmp(&b_var, Ordering::Less, true).unwrap();
}
Ordering::Greater => {
a_var.enforce_cmp(&b_var, Ordering::Greater, false).unwrap();
a_var.enforce_cmp(&b_var, Ordering::Greater, true).unwrap();
}
_ => {}
}
if i == 0 {
println!("number of constraints: {}", cs.num_constraints());
}
assert!(cs.is_satisfied().unwrap());
}
println!("Finished with satisfaction tests");
for _i in 0..10 {
let cs = ConstraintSystem::<Fr>::new_ref();
let a = rand_in_range(&mut rng);
let a_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(a)).unwrap();
let b = rand_in_range(&mut rng);
let b_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(b)).unwrap();
match b.cmp(&a) {
Ordering::Less => {
a_var.enforce_cmp(&b_var, Ordering::Less, false).unwrap();
a_var.enforce_cmp(&b_var, Ordering::Less, true).unwrap();
}
Ordering::Greater => {
a_var.enforce_cmp(&b_var, Ordering::Greater, false).unwrap();
a_var.enforce_cmp(&b_var, Ordering::Greater, true).unwrap();
}
_ => {}
}
assert!(!cs.is_satisfied().unwrap());
}
for _i in 0..10 {
let cs = ConstraintSystem::<Fr>::new_ref();
let a = rand_in_range(&mut rng);
let a_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(a)).unwrap();
a_var.enforce_cmp(&a_var, Ordering::Less, false).unwrap();
assert!(!cs.is_satisfied().unwrap());
}
for _i in 0..10 {
let cs = ConstraintSystem::<Fr>::new_ref();
let a = rand_in_range(&mut rng);
let a_var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(a)).unwrap();
a_var.enforce_cmp(&a_var, Ordering::Less, true).unwrap();
assert!(cs.is_satisfied().unwrap());
}
}
}

1015
src/fields/fp/mod.rs Normal file

File diff suppressed because it is too large Load Diff

171
src/fields/fp12.rs Normal file
View File

@@ -0,0 +1,171 @@
use crate::fields::{fp2::Fp2Var, fp6_3over2::Fp6Var, quadratic_extension::*, FieldVar};
use ark_ff::fields::{fp12_2over3over2::*, fp6_3over2::Fp6Parameters, Field, QuadExtParameters};
use ark_relations::r1cs::SynthesisError;
/// A degree-12 extension field constructed as the tower of a
/// quadratic extension over a cubic extension over a quadratic extension field.
/// This is the R1CS equivalent of `ark_ff::fp12_2over3over2::Fp12<P>`.
pub type Fp12Var<P> = QuadExtVar<Fp6Var<<P as Fp12Parameters>::Fp6Params>, Fp12ParamsWrapper<P>>;
type Fp2Params<P> = <<P as Fp12Parameters>::Fp6Params as Fp6Parameters>::Fp2Params;
impl<P: Fp12Parameters> QuadExtVarParams<Fp6Var<P::Fp6Params>> for Fp12ParamsWrapper<P> {
fn mul_base_field_var_by_frob_coeff(fe: &mut Fp6Var<P::Fp6Params>, power: usize) {
fe.c0 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
fe.c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
fe.c2 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
}
}
impl<P: Fp12Parameters> Fp12Var<P> {
/// Multiplies by a sparse element of the form `(c0 = (c0, c1, 0), c1 = (0,
/// d1, 0))`.
#[inline]
pub fn mul_by_014(
&self,
c0: &Fp2Var<Fp2Params<P>>,
c1: &Fp2Var<Fp2Params<P>>,
d1: &Fp2Var<Fp2Params<P>>,
) -> Result<Self, SynthesisError> {
let v0 = self.c0.mul_by_c0_c1_0(&c0, &c1)?;
let v1 = self.c1.mul_by_0_c1_0(&d1)?;
let new_c0 = Self::mul_base_field_by_nonresidue(&v1)? + &v0;
let new_c1 = (&self.c0 + &self.c1).mul_by_c0_c1_0(&c0, &(c1 + d1))? - &v0 - &v1;
Ok(Self::new(new_c0, new_c1))
}
/// Multiplies by a sparse element of the form `(c0 = (c0, 0, 0), c1 = (d0,
/// d1, 0))`.
#[inline]
pub fn mul_by_034(
&self,
c0: &Fp2Var<Fp2Params<P>>,
d0: &Fp2Var<Fp2Params<P>>,
d1: &Fp2Var<Fp2Params<P>>,
) -> Result<Self, SynthesisError> {
let a0 = &self.c0.c0 * c0;
let a1 = &self.c0.c1 * c0;
let a2 = &self.c0.c2 * c0;
let a = Fp6Var::new(a0, a1, a2);
let b = self.c1.mul_by_c0_c1_0(&d0, &d1)?;
let c0 = c0 + d0;
let c1 = d1;
let e = (&self.c0 + &self.c1).mul_by_c0_c1_0(&c0, &c1)?;
let new_c1 = e - (&a + &b);
let new_c0 = Self::mul_base_field_by_nonresidue(&b)? + &a;
Ok(Self::new(new_c0, new_c1))
}
/// Squares `self` when `self` is in the cyclotomic subgroup.
pub fn cyclotomic_square(&self) -> Result<Self, SynthesisError> {
if characteristic_square_mod_6_is_one(Fp12::<P>::characteristic()) {
let fp2_nr = <P::Fp6Params as Fp6Parameters>::NONRESIDUE;
let z0 = &self.c0.c0;
let z4 = &self.c0.c1;
let z3 = &self.c0.c2;
let z2 = &self.c1.c0;
let z1 = &self.c1.c1;
let z5 = &self.c1.c2;
// t0 + t1*y = (z0 + z1*y)^2 = a^2
let tmp = z0 * z1;
let t0 = {
let tmp1 = z0 + z1;
let tmp2 = z1 * fp2_nr + z0;
let tmp4 = &tmp * fp2_nr + &tmp;
tmp1 * tmp2 - tmp4
};
let t1 = tmp.double()?;
// t2 + t3*y = (z2 + z3*y)^2 = b^2
let tmp = z2 * z3;
let t2 = {
// (z2 + &z3) * &(z2 + &(fp2_nr * &z3)) - &tmp - &(tmp * &fp2_nr);
let tmp1 = z2 + z3;
let tmp2 = z3 * fp2_nr + z2;
let tmp4 = &tmp * fp2_nr + &tmp;
tmp1 * tmp2 - tmp4
};
let t3 = tmp.double()?;
// t4 + t5*y = (z4 + z5*y)^2 = c^2
let tmp = z4 * z5;
let t4 = {
// (z4 + &z5) * &(z4 + &(fp2_nr * &z5)) - &tmp - &(tmp * &fp2_nr);
let tmp1 = z4 + z5;
let tmp2 = (z5 * fp2_nr) + z4;
let tmp4 = (&tmp * fp2_nr) + &tmp;
(tmp1 * tmp2) - tmp4
};
let t5 = tmp.double()?;
// for A
// z0 = 3 * t0 - 2 * z0
let c0_c0 = (&t0 - z0).double()? + &t0;
// z1 = 3 * t1 + 2 * z1
let c1_c1 = (&t1 + z1).double()? + &t1;
// for B
// z2 = 3 * (xi * t5) + 2 * z2
let c1_c0 = {
let tmp = &t5 * fp2_nr;
(z2 + &tmp).double()? + &tmp
};
// z3 = 3 * t4 - 2 * z3
let c0_c2 = (&t4 - z3).double()? + &t4;
// for C
// z4 = 3 * t2 - 2 * z4
let c0_c1 = (&t2 - z4).double()? + &t2;
// z5 = 3 * t3 + 2 * z5
let c1_c2 = (&t3 + z5).double()? + &t3;
let c0 = Fp6Var::new(c0_c0, c0_c1, c0_c2);
let c1 = Fp6Var::new(c1_c0, c1_c1, c1_c2);
Ok(Self::new(c0, c1))
} else {
self.square()
}
}
/// Like `Self::cyclotomic_exp`, but additionally uses cyclotomic squaring.
pub fn optimized_cyclotomic_exp(
&self,
exponent: impl AsRef<[u64]>,
) -> Result<Self, SynthesisError> {
use ark_ff::biginteger::arithmetic::find_wnaf;
let mut res = Self::one();
let self_inverse = self.unitary_inverse()?;
let mut found_nonzero = false;
let naf = find_wnaf(exponent.as_ref());
for &value in naf.iter().rev() {
if found_nonzero {
res = res.cyclotomic_square()?;
}
if value != 0 {
found_nonzero = true;
if value > 0 {
res *= self;
} else {
res *= &self_inverse;
}
}
}
Ok(res)
}
}

12
src/fields/fp2.rs Normal file
View File

@@ -0,0 +1,12 @@
use crate::fields::{fp::FpVar, quadratic_extension::*};
use ark_ff::fields::{Fp2Parameters, Fp2ParamsWrapper, QuadExtParameters};
/// A quadratic extension field constructed over a prime field.
/// This is the R1CS equivalent of `ark_ff::Fp2<P>`.
pub type Fp2Var<P> = QuadExtVar<FpVar<<P as Fp2Parameters>::Fp>, Fp2ParamsWrapper<P>>;
impl<P: Fp2Parameters> QuadExtVarParams<FpVar<P::Fp>> for Fp2ParamsWrapper<P> {
fn mul_base_field_var_by_frob_coeff(fe: &mut FpVar<P::Fp>, power: usize) {
*fe *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
}
}

17
src/fields/fp3.rs Normal file
View File

@@ -0,0 +1,17 @@
use crate::fields::{cubic_extension::*, fp::FpVar};
use ark_ff::fields::{CubicExtParameters, Fp3Parameters, Fp3ParamsWrapper};
/// A cubic extension field constructed over a prime field.
/// This is the R1CS equivalent of `ark_ff::Fp3<P>`.
pub type Fp3Var<P> = CubicExtVar<FpVar<<P as Fp3Parameters>::Fp>, Fp3ParamsWrapper<P>>;
impl<P: Fp3Parameters> CubicExtVarParams<FpVar<P::Fp>> for Fp3ParamsWrapper<P> {
fn mul_base_field_vars_by_frob_coeff(
c1: &mut FpVar<P::Fp>,
c2: &mut FpVar<P::Fp>,
power: usize,
) {
*c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
*c2 *= Self::FROBENIUS_COEFF_C2[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
}
}

14
src/fields/fp4.rs Normal file
View File

@@ -0,0 +1,14 @@
use crate::fields::{fp2::Fp2Var, quadratic_extension::*};
use ark_ff::fields::{Fp4Parameters, Fp4ParamsWrapper, QuadExtParameters};
/// A quartic extension field constructed as the tower of a
/// quadratic extension over a quadratic extension field.
/// This is the R1CS equivalent of `ark_ff::Fp4<P>`.
pub type Fp4Var<P> = QuadExtVar<Fp2Var<<P as Fp4Parameters>::Fp2Params>, Fp4ParamsWrapper<P>>;
impl<P: Fp4Parameters> QuadExtVarParams<Fp2Var<P::Fp2Params>> for Fp4ParamsWrapper<P> {
fn mul_base_field_var_by_frob_coeff(fe: &mut Fp2Var<P::Fp2Params>, power: usize) {
fe.c0 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
fe.c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
}
}

15
src/fields/fp6_2over3.rs Normal file
View File

@@ -0,0 +1,15 @@
use crate::fields::{fp3::Fp3Var, quadratic_extension::*};
use ark_ff::fields::{fp6_2over3::*, QuadExtParameters};
/// A sextic extension field constructed as the tower of a
/// quadratic extension over a cubic extension field.
/// This is the R1CS equivalent of `ark_ff::fp6_2over3::Fp6<P>`.
pub type Fp6Var<P> = QuadExtVar<Fp3Var<<P as Fp6Parameters>::Fp3Params>, Fp6ParamsWrapper<P>>;
impl<P: Fp6Parameters> QuadExtVarParams<Fp3Var<P::Fp3Params>> for Fp6ParamsWrapper<P> {
fn mul_base_field_var_by_frob_coeff(fe: &mut Fp3Var<P::Fp3Params>, power: usize) {
fe.c0 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
fe.c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
fe.c2 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
}
}

85
src/fields/fp6_3over2.rs Normal file
View File

@@ -0,0 +1,85 @@
use crate::fields::{cubic_extension::*, fp2::*};
use ark_ff::fields::{fp6_3over2::*, CubicExtParameters, Fp2};
use ark_relations::r1cs::SynthesisError;
use core::ops::MulAssign;
/// A sextic extension field constructed as the tower of a
/// cubic extension over a quadratic extension field.
/// This is the R1CS equivalent of `ark_ff::fp6_3over3::Fp6<P>`.
pub type Fp6Var<P> = CubicExtVar<Fp2Var<<P as Fp6Parameters>::Fp2Params>, Fp6ParamsWrapper<P>>;
impl<P: Fp6Parameters> CubicExtVarParams<Fp2Var<P::Fp2Params>> for Fp6ParamsWrapper<P> {
fn mul_base_field_vars_by_frob_coeff(
c1: &mut Fp2Var<P::Fp2Params>,
c2: &mut Fp2Var<P::Fp2Params>,
power: usize,
) {
*c1 *= Self::FROBENIUS_COEFF_C1[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
*c2 *= Self::FROBENIUS_COEFF_C2[power % Self::DEGREE_OVER_BASE_PRIME_FIELD];
}
}
impl<P: Fp6Parameters> Fp6Var<P> {
/// Multiplies `self` by a sparse element which has `c0 == c2 == zero`.
pub fn mul_by_0_c1_0(&self, c1: &Fp2Var<P::Fp2Params>) -> Result<Self, SynthesisError> {
// Karatsuba multiplication
// v0 = a0 * b0 = 0
// v1 = a1 * b1
let v1 = &self.c1 * c1;
// v2 = a2 * b2 = 0
let a1_plus_a2 = &self.c1 + &self.c2;
let b1_plus_b2 = c1.clone();
let a0_plus_a1 = &self.c0 + &self.c1;
// c0 = (NONRESIDUE * ((a1 + a2)*(b1 + b2) - v1 - v2)) + v0
// = NONRESIDUE * ((a1 + a2) * b1 - v1)
let c0 = &(a1_plus_a2 * &b1_plus_b2 - &v1) * P::NONRESIDUE;
// c1 = (a0 + a1) * (b0 + b1) - v0 - v1 + NONRESIDUE * v2
// = (a0 + a1) * b1 - v1
let c1 = a0_plus_a1 * c1 - &v1;
// c2 = (a0 + a2) * (b0 + b2) - v0 - v2 + v1
// = v1
let c2 = v1;
Ok(Self::new(c0, c1, c2))
}
/// Multiplies `self` by a sparse element which has `c2 == zero`.
pub fn mul_by_c0_c1_0(
&self,
c0: &Fp2Var<P::Fp2Params>,
c1: &Fp2Var<P::Fp2Params>,
) -> Result<Self, SynthesisError> {
let v0 = &self.c0 * c0;
let v1 = &self.c1 * c1;
// v2 = 0.
let a1_plus_a2 = &self.c1 + &self.c2;
let a0_plus_a1 = &self.c0 + &self.c1;
let a0_plus_a2 = &self.c0 + &self.c2;
let b1_plus_b2 = c1.clone();
let b0_plus_b1 = c0 + c1;
let b0_plus_b2 = c0.clone();
let c0 = (&a1_plus_a2 * &b1_plus_b2 - &v1) * P::NONRESIDUE + &v0;
let c1 = a0_plus_a1 * &b0_plus_b1 - &v0 - &v1;
let c2 = a0_plus_a2 * &b0_plus_b2 - &v0 + &v1;
Ok(Self::new(c0, c1, c2))
}
}
impl<P: Fp6Parameters> MulAssign<Fp2<P::Fp2Params>> for Fp6Var<P> {
fn mul_assign(&mut self, other: Fp2<P::Fp2Params>) {
self.c0 *= other;
self.c1 *= other;
self.c2 *= other;
}
}

207
src/fields/mod.rs Normal file
View File

@@ -0,0 +1,207 @@
use ark_ff::{prelude::*, BitIteratorBE};
use ark_relations::r1cs::SynthesisError;
use core::{
fmt::Debug,
ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign},
};
use crate::{prelude::*, Assignment};
/// This module contains a generic implementation of cubic extension field
/// variables. That is, it implements the R1CS equivalent of
/// `ark_ff::CubicExtField`.
pub mod cubic_extension;
/// This module contains a generic implementation of quadratic extension field
/// variables. That is, it implements the R1CS equivalent of
/// `ark_ff::QuadExtField`.
pub mod quadratic_extension;
/// This module contains a generic implementation of prime field variables.
/// That is, it implements the R1CS equivalent of `ark_ff::Fp*`.
pub mod fp;
/// This module contains a generic implementation of the degree-12 tower
/// extension field. That is, it implements the R1CS equivalent of
/// `ark_ff::Fp12`
pub mod fp12;
/// This module contains a generic implementation of the degree-2 tower
/// extension field. That is, it implements the R1CS equivalent of
/// `ark_ff::Fp2`
pub mod fp2;
/// This module contains a generic implementation of the degree-3 tower
/// extension field. That is, it implements the R1CS equivalent of
/// `ark_ff::Fp3`
pub mod fp3;
/// This module contains a generic implementation of the degree-4 tower
/// extension field. That is, it implements the R1CS equivalent of
/// `ark_ff::Fp4`
pub mod fp4;
/// This module contains a generic implementation of the degree-6 tower
/// extension field. That is, it implements the R1CS equivalent of
/// `ark_ff::fp6_2over3::Fp6`
pub mod fp6_2over3;
/// This module contains a generic implementation of the degree-6 tower
/// extension field. That is, it implements the R1CS equivalent of
/// `ark_ff::fp6_3over2::Fp6`
pub mod fp6_3over2;
/// This trait is a hack used to work around the lack of implied bounds.
pub trait FieldOpsBounds<'a, F, T: 'a>:
Sized
+ Add<&'a T, Output = T>
+ Sub<&'a T, Output = T>
+ Mul<&'a T, Output = T>
+ Add<T, Output = T>
+ Sub<T, Output = T>
+ Mul<T, Output = T>
+ Add<F, Output = T>
+ Sub<F, Output = T>
+ Mul<F, Output = T>
{
}
/// A variable representing a field. Corresponds to the native type `F`.
pub trait FieldVar<F: Field, ConstraintF: Field>:
'static
+ Clone
+ From<Boolean<ConstraintF>>
+ R1CSVar<ConstraintF, Value = F>
+ EqGadget<ConstraintF>
+ ToBitsGadget<ConstraintF>
+ AllocVar<F, ConstraintF>
+ ToBytesGadget<ConstraintF>
+ CondSelectGadget<ConstraintF>
+ for<'a> FieldOpsBounds<'a, F, Self>
+ for<'a> AddAssign<&'a Self>
+ for<'a> SubAssign<&'a Self>
+ for<'a> MulAssign<&'a Self>
+ AddAssign<Self>
+ SubAssign<Self>
+ MulAssign<Self>
+ AddAssign<F>
+ SubAssign<F>
+ MulAssign<F>
+ Debug
{
/// Returns the constant `F::zero()`.
fn zero() -> Self;
/// Returns a `Boolean` representing whether `self == Self::zero()`.
fn is_zero(&self) -> Result<Boolean<ConstraintF>, SynthesisError> {
self.is_eq(&Self::zero())
}
/// Returns the constant `F::one()`.
fn one() -> Self;
/// Returns a `Boolean` representing whether `self == Self::one()`.
fn is_one(&self) -> Result<Boolean<ConstraintF>, SynthesisError> {
self.is_eq(&Self::one())
}
/// Returns a constant with value `v`.
///
/// This *should not* allocate any variables.
fn constant(v: F) -> Self;
/// Computes `self + self`.
fn double(&self) -> Result<Self, SynthesisError> {
Ok(self.clone() + self)
}
/// Sets `self = self + self`.
fn double_in_place(&mut self) -> Result<&mut Self, SynthesisError> {
*self += self.double()?;
Ok(self)
}
/// Coputes `-self`.
fn negate(&self) -> Result<Self, SynthesisError>;
/// Sets `self = -self`.
#[inline]
fn negate_in_place(&mut self) -> Result<&mut Self, SynthesisError> {
*self = self.negate()?;
Ok(self)
}
/// Computes `self * self`.
///
/// A default implementation is provided which just invokes the underlying
/// multiplication routine. However, this method should be specialized
/// for extension fields, where faster algorithms exist for squaring.
fn square(&self) -> Result<Self, SynthesisError> {
Ok(self.clone() * self)
}
/// Sets `self = self.square()`.
fn square_in_place(&mut self) -> Result<&mut Self, SynthesisError> {
*self = self.square()?;
Ok(self)
}
/// Enforces that `self * other == result`.
fn mul_equals(&self, other: &Self, result: &Self) -> Result<(), SynthesisError> {
let actual_result = self.clone() * other;
result.enforce_equal(&actual_result)
}
/// Enforces that `self * self == result`.
fn square_equals(&self, result: &Self) -> Result<(), SynthesisError> {
let actual_result = self.square()?;
result.enforce_equal(&actual_result)
}
/// Computes `result` such that `self * result == Self::one()`.
fn inverse(&self) -> Result<Self, SynthesisError>;
/// Returns `(self / denominator)`. but requires fewer constraints than
/// `self * denominator.inverse()`.
/// It is up to the caller to ensure that denominator is non-zero,
/// since in that case the result is unconstrained.
fn mul_by_inverse(&self, denominator: &Self) -> Result<Self, SynthesisError> {
let result = Self::new_witness(self.cs(), || {
let denominator_inv_native = denominator.value()?.inverse().get()?;
let result = self.value()? * &denominator_inv_native;
Ok(result)
})?;
result.mul_equals(&denominator, &self)?;
Ok(result)
}
/// Computes the frobenius map over `self`.
fn frobenius_map(&self, power: usize) -> Result<Self, SynthesisError>;
/// Sets `self = self.frobenius_map()`.
fn frobenius_map_in_place(&mut self, power: usize) -> Result<&mut Self, SynthesisError> {
*self = self.frobenius_map(power)?;
Ok(self)
}
/// Comptues `self^bits`, where `bits` is a *little-endian* bit-wise
/// decomposition of the exponent.
fn pow_le(&self, bits: &[Boolean<ConstraintF>]) -> Result<Self, SynthesisError> {
let mut res = Self::one();
let mut power = self.clone();
for bit in bits {
let tmp = res.clone() * &power;
res = bit.select(&tmp, &res)?;
power.square_in_place()?;
}
Ok(res)
}
/// Computes `self^S`, where S is interpreted as an little-endian
/// u64-decomposition of an integer.
fn pow_by_constant<S: AsRef<[u64]>>(&self, exp: S) -> Result<Self, SynthesisError> {
let mut res = Self::one();
for i in BitIteratorBE::without_leading_zeros(exp) {
res.square_in_place()?;
if i {
res *= self;
}
}
Ok(res)
}
}

View File

@@ -0,0 +1,561 @@
use ark_ff::{
fields::{Field, QuadExtField, QuadExtParameters},
Zero,
};
use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError};
use core::{borrow::Borrow, marker::PhantomData};
use crate::{
fields::{fp::FpVar, FieldOpsBounds, FieldVar},
prelude::*,
ToConstraintFieldGadget, Vec,
};
/// This struct is the `R1CS` equivalent of the quadratic extension field type
/// in `ark-ff`, i.e. `ark_ff::QuadExtField`.
#[derive(Derivative)]
#[derivative(Debug(bound = "BF: core::fmt::Debug"), Clone(bound = "BF: Clone"))]
#[must_use]
pub struct QuadExtVar<BF: FieldVar<P::BaseField, P::BasePrimeField>, P: QuadExtVarParams<BF>>
where
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
{
/// The zero-th coefficient of this field element.
pub c0: BF,
/// The first coefficient of this field element.
pub c1: BF,
#[derivative(Debug = "ignore")]
_params: PhantomData<P>,
}
/// This trait describes parameters that are used to implement arithmetic for
/// `QuadExtVar`.
pub trait QuadExtVarParams<BF: FieldVar<Self::BaseField, Self::BasePrimeField>>:
QuadExtParameters
where
for<'a> &'a BF: FieldOpsBounds<'a, Self::BaseField, BF>,
{
/// Multiply the base field of the `QuadExtVar` by the appropriate Frobenius
/// coefficient. This is equivalent to
/// `Self::mul_base_field_by_frob_coeff(power)`.
fn mul_base_field_var_by_frob_coeff(fe: &mut BF, power: usize);
}
impl<BF: FieldVar<P::BaseField, P::BasePrimeField>, P: QuadExtVarParams<BF>> QuadExtVar<BF, P>
where
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
{
/// Constructs a `QuadExtVar` from the underlying coefficients.
pub fn new(c0: BF, c1: BF) -> Self {
Self {
c0,
c1,
_params: PhantomData,
}
}
/// Multiplies a variable of the base field by the quadratic nonresidue
/// `P::NONRESIDUE` that is used to construct the extension field.
#[inline]
pub fn mul_base_field_by_nonresidue(fe: &BF) -> Result<BF, SynthesisError> {
Ok(fe * P::NONRESIDUE)
}
/// Multiplies `self` by a constant from the base field.
#[inline]
pub fn mul_by_base_field_constant(&self, fe: P::BaseField) -> Self {
let c0 = self.c0.clone() * fe;
let c1 = self.c1.clone() * fe;
QuadExtVar::new(c0, c1)
}
/// Sets `self = self.mul_by_base_field_constant(fe)`.
#[inline]
pub fn mul_assign_by_base_field_constant(&mut self, fe: P::BaseField) {
*self = (&*self).mul_by_base_field_constant(fe);
}
/// This is only to be used when the element is *known* to be in the
/// cyclotomic subgroup.
#[inline]
pub fn unitary_inverse(&self) -> Result<Self, SynthesisError> {
Ok(Self::new(self.c0.clone(), self.c1.negate()?))
}
/// This is only to be used when the element is *known* to be in the
/// cyclotomic subgroup.
#[inline]
#[tracing::instrument(target = "r1cs", skip(exponent))]
pub fn cyclotomic_exp(&self, exponent: impl AsRef<[u64]>) -> Result<Self, SynthesisError>
where
Self: FieldVar<QuadExtField<P>, P::BasePrimeField>,
{
let mut res = Self::one();
let self_inverse = self.unitary_inverse()?;
let mut found_nonzero = false;
let naf = ark_ff::biginteger::arithmetic::find_wnaf(exponent.as_ref());
for &value in naf.iter().rev() {
if found_nonzero {
res.square_in_place()?;
}
if value != 0 {
found_nonzero = true;
if value > 0 {
res *= self;
} else {
res *= &self_inverse;
}
}
}
Ok(res)
}
}
impl<BF, P> R1CSVar<P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
type Value = QuadExtField<P>;
fn cs(&self) -> ConstraintSystemRef<P::BasePrimeField> {
[&self.c0, &self.c1].cs()
}
#[inline]
fn value(&self) -> Result<Self::Value, SynthesisError> {
match (self.c0.value(), self.c1.value()) {
(Ok(c0), Ok(c1)) => Ok(QuadExtField::new(c0, c1)),
(..) => Err(SynthesisError::AssignmentMissing),
}
}
}
impl<BF, P> From<Boolean<P::BasePrimeField>> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
fn from(other: Boolean<P::BasePrimeField>) -> Self {
let c0 = BF::from(other);
let c1 = BF::zero();
Self::new(c0, c1)
}
}
impl<'a, BF, P> FieldOpsBounds<'a, QuadExtField<P>, QuadExtVar<BF, P>> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
}
impl<'a, BF, P> FieldOpsBounds<'a, QuadExtField<P>, QuadExtVar<BF, P>> for &'a QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
}
impl<BF, P> FieldVar<QuadExtField<P>, P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
fn constant(other: QuadExtField<P>) -> Self {
let c0 = BF::constant(other.c0);
let c1 = BF::constant(other.c1);
Self::new(c0, c1)
}
fn zero() -> Self {
let c0 = BF::zero();
let c1 = BF::zero();
Self::new(c0, c1)
}
fn one() -> Self {
let c0 = BF::one();
let c1 = BF::zero();
Self::new(c0, c1)
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn double(&self) -> Result<Self, SynthesisError> {
let c0 = self.c0.double()?;
let c1 = self.c1.double()?;
Ok(Self::new(c0, c1))
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn negate(&self) -> Result<Self, SynthesisError> {
let mut result = self.clone();
result.c0.negate_in_place()?;
result.c1.negate_in_place()?;
Ok(result)
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn square(&self) -> Result<Self, SynthesisError> {
// From Libsnark/fp2_gadget.tcc
// Complex multiplication for Fp2:
// "Multiplication and Squaring on Pairing-Friendly Fields"
// Devegili, OhEigeartaigh, Scott, Dahab
// v0 = c0 - c1
let mut v0 = &self.c0 - &self.c1;
// v3 = c0 - beta * c1
let v3 = &self.c0 - &Self::mul_base_field_by_nonresidue(&self.c1)?;
// v2 = c0 * c1
let v2 = &self.c0 * &self.c1;
// v0 = (v0 * v3) + v2
v0 *= &v3;
v0 += &v2;
let c0 = &v0 + &Self::mul_base_field_by_nonresidue(&v2)?;
let c1 = v2.double()?;
Ok(Self::new(c0, c1))
}
#[tracing::instrument(target = "r1cs")]
fn mul_equals(&self, 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
// Compute v1
let v1 = &self.c1 * &other.c1;
// Perform second check
let non_residue_times_v1 = Self::mul_base_field_by_nonresidue(&v1)?;
let rhs = &result.c0 - &non_residue_times_v1;
self.c0.mul_equals(&other.c0, &rhs)?;
// Last check
let a0_plus_a1 = &self.c0 + &self.c1;
let b0_plus_b1 = &other.c0 + &other.c1;
let one_minus_non_residue_v1 = &v1 - &non_residue_times_v1;
let tmp = &(&result.c1 + &result.c0) + &one_minus_non_residue_v1;
a0_plus_a1.mul_equals(&b0_plus_b1, &tmp)?;
Ok(())
}
#[tracing::instrument(target = "r1cs")]
fn frobenius_map(&self, power: usize) -> Result<Self, SynthesisError> {
let mut result = self.clone();
result.c0.frobenius_map_in_place(power)?;
result.c1.frobenius_map_in_place(power)?;
P::mul_base_field_var_by_frob_coeff(&mut result.c1, power);
Ok(result)
}
#[tracing::instrument(target = "r1cs")]
fn inverse(&self) -> Result<Self, SynthesisError> {
let mode = if self.is_constant() {
AllocationMode::Constant
} else {
AllocationMode::Witness
};
let inverse = Self::new_variable(
self.cs(),
|| {
self.value()
.map(|f| f.inverse().unwrap_or(QuadExtField::zero()))
},
mode,
)?;
self.mul_equals(&inverse, &Self::one())?;
Ok(inverse)
}
}
impl_bounded_ops!(
QuadExtVar<BF, P>,
QuadExtField<P>,
Add,
add,
AddAssign,
add_assign,
|this: &'a QuadExtVar<BF, P>, other: &'a QuadExtVar<BF, P>| {
let c0 = &this.c0 + &other.c0;
let c1 = &this.c1 + &other.c1;
QuadExtVar::new(c0, c1)
},
|this: &'a QuadExtVar<BF, P>, other: QuadExtField<P>| {
this + QuadExtVar::constant(other)
},
(BF: FieldVar<P::BaseField, P::BasePrimeField>, P: QuadExtVarParams<BF>),
for <'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>
);
impl_bounded_ops!(
QuadExtVar<BF, P>,
QuadExtField<P>,
Sub,
sub,
SubAssign,
sub_assign,
|this: &'a QuadExtVar<BF, P>, other: &'a QuadExtVar<BF, P>| {
let c0 = &this.c0 - &other.c0;
let c1 = &this.c1 - &other.c1;
QuadExtVar::new(c0, c1)
},
|this: &'a QuadExtVar<BF, P>, other: QuadExtField<P>| {
this - QuadExtVar::constant(other)
},
(BF: FieldVar<P::BaseField, P::BasePrimeField>, P: QuadExtVarParams<BF>),
for <'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>
);
impl_bounded_ops!(
QuadExtVar<BF, P>,
QuadExtField<P>,
Mul,
mul,
MulAssign,
mul_assign,
|this: &'a QuadExtVar<BF, P>, other: &'a QuadExtVar<BF, P>| {
// 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 mut result = this.clone();
let v0 = &this.c0 * &other.c0;
let v1 = &this.c1 * &other.c1;
result.c1 += &this.c0;
result.c1 *= &other.c0 + &other.c1;
result.c1 -= &v0;
result.c1 -= &v1;
result.c0 = v0 + &QuadExtVar::<BF, P>::mul_base_field_by_nonresidue(&v1).unwrap();
result
},
|this: &'a QuadExtVar<BF, P>, other: QuadExtField<P>| {
this * QuadExtVar::constant(other)
},
(BF: FieldVar<P::BaseField, P::BasePrimeField>, P: QuadExtVarParams<BF>),
for <'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>
);
impl<BF, P> EqGadget<P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
#[tracing::instrument(target = "r1cs")]
fn is_eq(&self, other: &Self) -> Result<Boolean<P::BasePrimeField>, SynthesisError> {
let b0 = self.c0.is_eq(&other.c0)?;
let b1 = self.c1.is_eq(&other.c1)?;
b0.and(&b1)
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn conditional_enforce_equal(
&self,
other: &Self,
condition: &Boolean<P::BasePrimeField>,
) -> Result<(), SynthesisError> {
self.c0.conditional_enforce_equal(&other.c0, condition)?;
self.c1.conditional_enforce_equal(&other.c1, condition)?;
Ok(())
}
#[inline]
#[tracing::instrument(target = "r1cs")]
fn conditional_enforce_not_equal(
&self,
other: &Self,
condition: &Boolean<P::BasePrimeField>,
) -> Result<(), SynthesisError> {
let is_equal = self.is_eq(other)?;
is_equal
.and(condition)?
.enforce_equal(&Boolean::Constant(false))
}
}
impl<BF, P> ToBitsGadget<P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
#[tracing::instrument(target = "r1cs")]
fn to_bits_le(&self) -> Result<Vec<Boolean<P::BasePrimeField>>, SynthesisError> {
let mut c0 = self.c0.to_bits_le()?;
let mut c1 = self.c1.to_bits_le()?;
c0.append(&mut c1);
Ok(c0)
}
#[tracing::instrument(target = "r1cs")]
fn to_non_unique_bits_le(&self) -> Result<Vec<Boolean<P::BasePrimeField>>, SynthesisError> {
let mut c0 = self.c0.to_non_unique_bits_le()?;
let mut c1 = self.c1.to_non_unique_bits_le()?;
c0.append(&mut c1);
Ok(c0)
}
}
impl<BF, P> ToBytesGadget<P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
#[tracing::instrument(target = "r1cs")]
fn to_bytes(&self) -> Result<Vec<UInt8<P::BasePrimeField>>, SynthesisError> {
let mut c0 = self.c0.to_bytes()?;
let mut c1 = self.c1.to_bytes()?;
c0.append(&mut c1);
Ok(c0)
}
#[tracing::instrument(target = "r1cs")]
fn to_non_unique_bytes(&self) -> Result<Vec<UInt8<P::BasePrimeField>>, SynthesisError> {
let mut c0 = self.c0.to_non_unique_bytes()?;
let mut c1 = self.c1.to_non_unique_bytes()?;
c0.append(&mut c1);
Ok(c0)
}
}
impl<BF, P> ToConstraintFieldGadget<P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'a> &'a BF: FieldOpsBounds<'a, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
BF: ToConstraintFieldGadget<P::BasePrimeField>,
{
#[tracing::instrument(target = "r1cs")]
fn to_constraint_field(&self) -> Result<Vec<FpVar<P::BasePrimeField>>, SynthesisError> {
let mut res = Vec::new();
res.extend_from_slice(&self.c0.to_constraint_field()?);
res.extend_from_slice(&self.c1.to_constraint_field()?);
Ok(res)
}
}
impl<BF, P> CondSelectGadget<P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
#[inline]
fn conditionally_select(
cond: &Boolean<P::BasePrimeField>,
true_value: &Self,
false_value: &Self,
) -> Result<Self, SynthesisError> {
let c0 = BF::conditionally_select(cond, &true_value.c0, &false_value.c0)?;
let c1 = BF::conditionally_select(cond, &true_value.c1, &false_value.c1)?;
Ok(Self::new(c0, c1))
}
}
impl<BF, P> TwoBitLookupGadget<P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>
+ TwoBitLookupGadget<P::BasePrimeField, TableConstant = P::BaseField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
type TableConstant = QuadExtField<P>;
#[tracing::instrument(target = "r1cs")]
fn two_bit_lookup(
b: &[Boolean<P::BasePrimeField>],
c: &[Self::TableConstant],
) -> Result<Self, SynthesisError> {
let c0s = c.iter().map(|f| f.c0).collect::<Vec<_>>();
let c1s = c.iter().map(|f| f.c1).collect::<Vec<_>>();
let c0 = BF::two_bit_lookup(b, &c0s)?;
let c1 = BF::two_bit_lookup(b, &c1s)?;
Ok(Self::new(c0, c1))
}
}
impl<BF, P> ThreeBitCondNegLookupGadget<P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>
+ ThreeBitCondNegLookupGadget<P::BasePrimeField, TableConstant = P::BaseField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
type TableConstant = QuadExtField<P>;
#[tracing::instrument(target = "r1cs")]
fn three_bit_cond_neg_lookup(
b: &[Boolean<P::BasePrimeField>],
b0b1: &Boolean<P::BasePrimeField>,
c: &[Self::TableConstant],
) -> Result<Self, SynthesisError> {
let c0s = c.iter().map(|f| f.c0).collect::<Vec<_>>();
let c1s = c.iter().map(|f| f.c1).collect::<Vec<_>>();
let c0 = BF::three_bit_cond_neg_lookup(b, b0b1, &c0s)?;
let c1 = BF::three_bit_cond_neg_lookup(b, b0b1, &c1s)?;
Ok(Self::new(c0, c1))
}
}
impl<BF, P> AllocVar<QuadExtField<P>, P::BasePrimeField> for QuadExtVar<BF, P>
where
BF: FieldVar<P::BaseField, P::BasePrimeField>,
for<'b> &'b BF: FieldOpsBounds<'b, P::BaseField, BF>,
P: QuadExtVarParams<BF>,
{
fn new_variable<T: Borrow<QuadExtField<P>>>(
cs: impl Into<Namespace<P::BasePrimeField>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
let ns = cs.into();
let cs = ns.cs();
let (c0, c1) = match f() {
Ok(fe) => (Ok(fe.borrow().c0), Ok(fe.borrow().c1)),
Err(_) => (
Err(SynthesisError::AssignmentMissing),
Err(SynthesisError::AssignmentMissing),
),
};
let c0 = BF::new_variable(ark_relations::ns!(cs, "c0"), || c0, mode)?;
let c1 = BF::new_variable(ark_relations::ns!(cs, "c1"), || c1, mode)?;
Ok(Self::new(c0, c1))
}
}