use crate::{
fields::{Field, PrimeField},
AdditiveGroup, FftField, LegendreSymbol, One, SqrtPrecomputation, ToConstraintField,
UniformRand, Zero,
};
use ark_serialize::{
CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize,
CanonicalSerializeWithFlags, Compress, EmptyFlags, Flags, SerializationError, Valid, Validate,
};
use ark_std::{
cmp::*,
fmt,
io::{Read, Write},
iter::*,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
rand::{
distributions::{Distribution, Standard},
Rng,
},
vec::*,
};
use zeroize::Zeroize;
pub trait CubicExtConfig: 'static + Send + Sync + Sized {
type BasePrimeField: PrimeField;
type BaseField: Field<BasePrimeField = Self::BasePrimeField>;
type FrobCoeff: Field;
const SQRT_PRECOMP: Option<SqrtPrecomputation<CubicExtField<Self>>>;
const DEGREE_OVER_BASE_PRIME_FIELD: usize;
const NONRESIDUE: Self::BaseField;
const FROBENIUS_COEFF_C1: &'static [Self::FrobCoeff];
const FROBENIUS_COEFF_C2: &'static [Self::FrobCoeff];
#[inline(always)]
fn mul_base_field_by_nonresidue_in_place(fe: &mut Self::BaseField) -> &mut Self::BaseField {
*fe *= &Self::NONRESIDUE;
fe
}
#[inline(always)]
fn mul_base_field_by_nonresidue(mut fe: Self::BaseField) -> Self::BaseField {
Self::mul_base_field_by_nonresidue_in_place(&mut fe);
fe
}
fn mul_base_field_by_frob_coeff(
c1: &mut Self::BaseField,
c2: &mut Self::BaseField,
power: usize,
);
}
#[derive(Educe)]
#[educe(Default, Hash, Clone, Copy, Debug, PartialEq, Eq)]
pub struct CubicExtField<P: CubicExtConfig> {
pub c0: P::BaseField,
pub c1: P::BaseField,
pub c2: P::BaseField,
}
impl<P: CubicExtConfig> CubicExtField<P> {
pub const fn new(c0: P::BaseField, c1: P::BaseField, c2: P::BaseField) -> Self {
Self { c0, c1, c2 }
}
pub fn mul_assign_by_base_field(&mut self, value: &P::BaseField) {
self.c0.mul_assign(value);
self.c1.mul_assign(value);
self.c2.mul_assign(value);
}
pub fn norm(&self) -> P::BaseField {
let index_multiplier = P::BaseField::extension_degree() as usize;
let mut self_to_p = *self;
self_to_p.frobenius_map_in_place(index_multiplier);
let mut self_to_p2 = *self;
self_to_p2.frobenius_map_in_place(2 * index_multiplier);
self_to_p *= &(self_to_p2 * self);
assert!(self_to_p.c1.is_zero() && self_to_p.c2.is_zero());
self_to_p.c0
}
}
impl<P: CubicExtConfig> Zero for CubicExtField<P> {
fn zero() -> Self {
Self::new(P::BaseField::ZERO, P::BaseField::ZERO, P::BaseField::ZERO)
}
fn is_zero(&self) -> bool {
self.c0.is_zero() && self.c1.is_zero() && self.c2.is_zero()
}
}
impl<P: CubicExtConfig> One for CubicExtField<P> {
fn one() -> Self {
Self::new(P::BaseField::ONE, P::BaseField::ZERO, P::BaseField::ZERO)
}
fn is_one(&self) -> bool {
self.c0.is_one() && self.c1.is_zero() && self.c2.is_zero()
}
}
impl<P: CubicExtConfig> AdditiveGroup for CubicExtField<P> {
type Scalar = Self;
const ZERO: Self = Self::new(P::BaseField::ZERO, P::BaseField::ZERO, P::BaseField::ZERO);
fn double(&self) -> Self {
let mut result = *self;
result.double_in_place();
result
}
fn double_in_place(&mut self) -> &mut Self {
self.c0.double_in_place();
self.c1.double_in_place();
self.c2.double_in_place();
self
}
fn neg_in_place(&mut self) -> &mut Self {
self.c0.neg_in_place();
self.c1.neg_in_place();
self.c2.neg_in_place();
self
}
}
impl<P: CubicExtConfig> Field for CubicExtField<P> {
type BasePrimeField = P::BasePrimeField;
const SQRT_PRECOMP: Option<SqrtPrecomputation<Self>> = P::SQRT_PRECOMP;
const ONE: Self = Self::new(P::BaseField::ONE, P::BaseField::ZERO, P::BaseField::ZERO);
fn extension_degree() -> u64 {
3 * P::BaseField::extension_degree()
}
fn from_base_prime_field(elem: Self::BasePrimeField) -> Self {
let fe = P::BaseField::from_base_prime_field(elem);
Self::new(fe, P::BaseField::ZERO, P::BaseField::ZERO)
}
fn to_base_prime_field_elements(&self) -> impl Iterator<Item = Self::BasePrimeField> {
self.c0
.to_base_prime_field_elements()
.chain(self.c1.to_base_prime_field_elements())
.chain(self.c2.to_base_prime_field_elements())
}
fn from_base_prime_field_elems(
elems: impl IntoIterator<Item = Self::BasePrimeField>,
) -> Option<Self> {
let mut elems = elems.into_iter();
let elems = elems.by_ref();
let base_ext_deg = P::BaseField::extension_degree() as usize;
let element = Some(Self::new(
P::BaseField::from_base_prime_field_elems(elems.take(base_ext_deg))?,
P::BaseField::from_base_prime_field_elems(elems.take(base_ext_deg))?,
P::BaseField::from_base_prime_field_elems(elems.take(base_ext_deg))?,
));
if elems.next().is_some() {
None
} else {
element
}
}
#[inline]
fn from_random_bytes_with_flags<F: Flags>(bytes: &[u8]) -> Option<(Self, F)> {
let split_at = bytes.len() / 3;
if let Some(c0) = P::BaseField::from_random_bytes(&bytes[..split_at]) {
if let Some(c1) = P::BaseField::from_random_bytes(&bytes[split_at..2 * split_at]) {
if let Some((c2, flags)) =
P::BaseField::from_random_bytes_with_flags(&bytes[2 * split_at..])
{
return Some((CubicExtField::new(c0, c1, c2), flags));
}
}
}
None
}
#[inline]
fn from_random_bytes(bytes: &[u8]) -> Option<Self> {
Self::from_random_bytes_with_flags::<EmptyFlags>(bytes).map(|f| f.0)
}
fn square(&self) -> Self {
let mut result = *self;
result.square_in_place();
result
}
fn square_in_place(&mut self) -> &mut Self {
let a = self.c0;
let b = self.c1;
let c = self.c2;
let s0 = a.square();
let ab = a * &b;
let s1 = ab.double();
let s2 = (a - &b + &c).square();
let bc = b * &c;
let s3 = bc.double();
let s4 = c.square();
self.c0 = s3;
P::mul_base_field_by_nonresidue_in_place(&mut self.c0);
self.c0 += &s0;
self.c1 = s4;
P::mul_base_field_by_nonresidue_in_place(&mut self.c1);
self.c1 += &s1;
self.c2 = s1 + &s2 + &s3 - &s0 - &s4;
self
}
fn legendre(&self) -> LegendreSymbol {
self.norm().legendre()
}
fn inverse(&self) -> Option<Self> {
if self.is_zero() {
None
} else {
let t0 = self.c0.square();
let t1 = self.c1.square();
let t2 = self.c2.square();
let t3 = self.c0 * &self.c1;
let t4 = self.c0 * &self.c2;
let t5 = self.c1 * &self.c2;
let n5 = P::mul_base_field_by_nonresidue(t5);
let s0 = t0 - &n5;
let s1 = P::mul_base_field_by_nonresidue(t2) - &t3;
let s2 = t1 - &t4; let a1 = self.c2 * &s1;
let a2 = self.c1 * &s2;
let mut a3 = a1 + &a2;
a3 = P::mul_base_field_by_nonresidue(a3);
let t6 = (self.c0 * &s0 + &a3).inverse().unwrap();
let c0 = t6 * &s0;
let c1 = t6 * &s1;
let c2 = t6 * &s2;
Some(Self::new(c0, c1, c2))
}
}
fn inverse_in_place(&mut self) -> Option<&mut Self> {
if let Some(inverse) = self.inverse() {
*self = inverse;
Some(self)
} else {
None
}
}
fn frobenius_map_in_place(&mut self, power: usize) {
self.c0.frobenius_map_in_place(power);
self.c1.frobenius_map_in_place(power);
self.c2.frobenius_map_in_place(power);
P::mul_base_field_by_frob_coeff(&mut self.c1, &mut self.c2, power);
}
fn mul_by_base_prime_field(&self, elem: &Self::BasePrimeField) -> Self {
let mut result = *self;
result.c0 = result.c0.mul_by_base_prime_field(elem);
result.c1 = result.c1.mul_by_base_prime_field(elem);
result.c2 = result.c2.mul_by_base_prime_field(elem);
result
}
}
impl<P: CubicExtConfig> Ord for CubicExtField<P> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
let c2_cmp = self.c2.cmp(&other.c2);
let c1_cmp = self.c1.cmp(&other.c1);
let c0_cmp = self.c0.cmp(&other.c0);
if c2_cmp == Ordering::Equal {
if c1_cmp == Ordering::Equal {
c0_cmp
} else {
c1_cmp
}
} else {
c2_cmp
}
}
}
impl<P: CubicExtConfig> PartialOrd for CubicExtField<P> {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<P: CubicExtConfig> Zeroize for CubicExtField<P> {
fn zeroize(&mut self) {
self.c0.zeroize();
self.c1.zeroize();
self.c2.zeroize();
}
}
impl<P: CubicExtConfig> From<u128> for CubicExtField<P> {
fn from(other: u128) -> Self {
let fe: P::BaseField = other.into();
Self::new(fe, P::BaseField::ZERO, P::BaseField::ZERO)
}
}
impl<P: CubicExtConfig> From<i128> for CubicExtField<P> {
#[inline]
fn from(val: i128) -> Self {
let abs = Self::from(val.unsigned_abs());
if val.is_positive() {
abs
} else {
-abs
}
}
}
impl<P: CubicExtConfig> From<u64> for CubicExtField<P> {
fn from(other: u64) -> Self {
let fe: P::BaseField = other.into();
Self::new(fe, P::BaseField::ZERO, P::BaseField::ZERO)
}
}
impl<P: CubicExtConfig> From<i64> for CubicExtField<P> {
#[inline]
fn from(val: i64) -> Self {
let abs = Self::from(val.unsigned_abs());
if val.is_positive() {
abs
} else {
-abs
}
}
}
impl<P: CubicExtConfig> From<u32> for CubicExtField<P> {
fn from(other: u32) -> Self {
let fe: P::BaseField = other.into();
Self::new(fe, P::BaseField::ZERO, P::BaseField::ZERO)
}
}
impl<P: CubicExtConfig> From<i32> for CubicExtField<P> {
#[inline]
fn from(val: i32) -> Self {
let abs = Self::from(val.unsigned_abs());
if val.is_positive() {
abs
} else {
-abs
}
}
}
impl<P: CubicExtConfig> From<u16> for CubicExtField<P> {
fn from(other: u16) -> Self {
let fe: P::BaseField = other.into();
Self::new(fe, P::BaseField::ZERO, P::BaseField::ZERO)
}
}
impl<P: CubicExtConfig> From<i16> for CubicExtField<P> {
#[inline]
fn from(val: i16) -> Self {
let abs = Self::from(val.unsigned_abs());
if val.is_positive() {
abs
} else {
-abs
}
}
}
impl<P: CubicExtConfig> From<u8> for CubicExtField<P> {
fn from(other: u8) -> Self {
let fe: P::BaseField = other.into();
Self::new(fe, P::BaseField::ZERO, P::BaseField::ZERO)
}
}
impl<P: CubicExtConfig> From<i8> for CubicExtField<P> {
#[inline]
fn from(val: i8) -> Self {
let abs = Self::from(val.unsigned_abs());
if val.is_positive() {
abs
} else {
-abs
}
}
}
impl<P: CubicExtConfig> From<bool> for CubicExtField<P> {
fn from(other: bool) -> Self {
Self::new(
u8::from(other).into(),
P::BaseField::ZERO,
P::BaseField::ZERO,
)
}
}
impl<P: CubicExtConfig> Neg for CubicExtField<P> {
type Output = Self;
#[inline]
fn neg(mut self) -> Self {
self.c0.neg_in_place();
self.c1.neg_in_place();
self.c2.neg_in_place();
self
}
}
impl<P: CubicExtConfig> Distribution<CubicExtField<P>> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> CubicExtField<P> {
CubicExtField::new(
UniformRand::rand(rng),
UniformRand::rand(rng),
UniformRand::rand(rng),
)
}
}
impl<'a, P: CubicExtConfig> Add<&'a CubicExtField<P>> for CubicExtField<P> {
type Output = Self;
#[inline]
fn add(mut self, other: &Self) -> Self {
self.add_assign(other);
self
}
}
impl<'a, P: CubicExtConfig> Sub<&'a CubicExtField<P>> for CubicExtField<P> {
type Output = Self;
#[inline]
fn sub(mut self, other: &Self) -> Self {
self.sub_assign(other);
self
}
}
impl<'a, P: CubicExtConfig> Mul<&'a CubicExtField<P>> for CubicExtField<P> {
type Output = Self;
#[inline]
fn mul(mut self, other: &Self) -> Self {
self.mul_assign(other);
self
}
}
impl<'a, P: CubicExtConfig> Div<&'a CubicExtField<P>> for CubicExtField<P> {
type Output = Self;
#[inline]
fn div(mut self, other: &Self) -> Self {
self.mul_assign(&other.inverse().unwrap());
self
}
}
impl_additive_ops_from_ref!(CubicExtField, CubicExtConfig);
impl_multiplicative_ops_from_ref!(CubicExtField, CubicExtConfig);
impl<'a, P: CubicExtConfig> AddAssign<&'a Self> for CubicExtField<P> {
#[inline]
fn add_assign(&mut self, other: &Self) {
self.c0.add_assign(&other.c0);
self.c1.add_assign(&other.c1);
self.c2.add_assign(&other.c2);
}
}
impl<'a, P: CubicExtConfig> SubAssign<&'a Self> for CubicExtField<P> {
#[inline]
fn sub_assign(&mut self, other: &Self) {
self.c0.sub_assign(&other.c0);
self.c1.sub_assign(&other.c1);
self.c2.sub_assign(&other.c2);
}
}
impl<'a, P: CubicExtConfig> MulAssign<&'a Self> for CubicExtField<P> {
#[inline]
#[allow(clippy::many_single_char_names)]
fn mul_assign(&mut self, other: &Self) {
let a = other.c0;
let b = other.c1;
let c = other.c2;
let d = self.c0;
let e = self.c1;
let f = self.c2;
let ad = d * &a;
let be = e * &b;
let cf = f * &c;
let x = (e + &f) * &(b + &c) - &be - &cf;
let y = (d + &e) * &(a + &b) - &ad - &be;
let z = (d + &f) * &(a + &c) - &ad + &be - &cf;
self.c0 = ad + &P::mul_base_field_by_nonresidue(x);
self.c1 = y + &P::mul_base_field_by_nonresidue(cf);
self.c2 = z;
}
}
impl<'a, P: CubicExtConfig> DivAssign<&'a Self> for CubicExtField<P> {
#[inline]
fn div_assign(&mut self, other: &Self) {
self.mul_assign(&other.inverse().unwrap());
}
}
impl<P: CubicExtConfig> fmt::Display for CubicExtField<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CubicExtField({}, {}, {})", self.c0, self.c1, self.c2)
}
}
impl<P: CubicExtConfig> CanonicalSerializeWithFlags for CubicExtField<P> {
#[inline]
fn serialize_with_flags<W: Write, F: Flags>(
&self,
mut writer: W,
flags: F,
) -> Result<(), SerializationError> {
self.c0.serialize_compressed(&mut writer)?;
self.c1.serialize_compressed(&mut writer)?;
self.c2.serialize_with_flags(&mut writer, flags)?;
Ok(())
}
#[inline]
fn serialized_size_with_flags<F: Flags>(&self) -> usize {
self.c0.compressed_size()
+ self.c1.compressed_size()
+ self.c2.serialized_size_with_flags::<F>()
}
}
impl<P: CubicExtConfig> CanonicalSerialize for CubicExtField<P> {
#[inline]
fn serialize_with_mode<W: Write>(
&self,
writer: W,
_compress: Compress,
) -> Result<(), SerializationError> {
self.serialize_with_flags(writer, EmptyFlags)
}
#[inline]
fn serialized_size(&self, _compress: Compress) -> usize {
self.serialized_size_with_flags::<EmptyFlags>()
}
}
impl<P: CubicExtConfig> CanonicalDeserializeWithFlags for CubicExtField<P> {
#[inline]
fn deserialize_with_flags<R: Read, F: Flags>(
mut reader: R,
) -> Result<(Self, F), SerializationError> {
let c0 = CanonicalDeserialize::deserialize_compressed(&mut reader)?;
let c1 = CanonicalDeserialize::deserialize_compressed(&mut reader)?;
let (c2, flags) = CanonicalDeserializeWithFlags::deserialize_with_flags(&mut reader)?;
Ok((CubicExtField::new(c0, c1, c2), flags))
}
}
impl<P: CubicExtConfig> Valid for CubicExtField<P> {
fn check(&self) -> Result<(), SerializationError> {
self.c0.check()?;
self.c1.check()?;
self.c2.check()
}
}
impl<P: CubicExtConfig> CanonicalDeserialize for CubicExtField<P> {
#[inline]
fn deserialize_with_mode<R: Read>(
mut reader: R,
compress: Compress,
validate: Validate,
) -> Result<Self, SerializationError> {
let c0: P::BaseField =
CanonicalDeserialize::deserialize_with_mode(&mut reader, compress, validate)?;
let c1: P::BaseField =
CanonicalDeserialize::deserialize_with_mode(&mut reader, compress, validate)?;
let c2: P::BaseField =
CanonicalDeserialize::deserialize_with_mode(&mut reader, compress, validate)?;
Ok(CubicExtField::new(c0, c1, c2))
}
}
impl<P: CubicExtConfig> ToConstraintField<P::BasePrimeField> for CubicExtField<P>
where
P::BaseField: ToConstraintField<P::BasePrimeField>,
{
fn to_field_elements(&self) -> Option<Vec<P::BasePrimeField>> {
let mut res = Vec::new();
let mut c0_elems = self.c0.to_field_elements()?;
let mut c1_elems = self.c1.to_field_elements()?;
let mut c2_elems = self.c2.to_field_elements()?;
res.append(&mut c0_elems);
res.append(&mut c1_elems);
res.append(&mut c2_elems);
Some(res)
}
}
#[cfg(test)]
mod cube_ext_tests {
use super::*;
use ark_std::test_rng;
use ark_test_curves::{
ark_ff::Field,
bls12_381::{Fq, Fq2, Fq6},
mnt6_753::Fq3,
};
#[test]
fn test_norm_for_towers() {
let mut rng = test_rng();
let a: Fq3 = rng.gen();
let _ = a.norm();
let a: Fq6 = rng.gen();
let _ = a.norm();
}
#[test]
fn test_from_base_prime_field_elements() {
let ext_degree = Fq6::extension_degree() as usize;
let max_num_elems_to_test = 10;
for d in 0..max_num_elems_to_test {
if d == ext_degree {
continue;
}
let mut random_coeffs = Vec::<Fq>::new();
for _ in 0..d {
random_coeffs.push(Fq::rand(&mut test_rng()));
}
let res = Fq6::from_base_prime_field_elems(random_coeffs);
assert_eq!(res, None);
}
let number_of_tests = 10;
for _ in 0..number_of_tests {
let mut random_coeffs = Vec::<Fq>::new();
for _ in 0..ext_degree {
random_coeffs.push(Fq::rand(&mut test_rng()));
}
let expected_0 = Fq2::new(random_coeffs[0], random_coeffs[1]);
let expected_1 = Fq2::new(random_coeffs[2], random_coeffs[3]);
let expected_2 = Fq2::new(random_coeffs[3], random_coeffs[4]);
let expected = Fq6::new(expected_0, expected_1, expected_2);
let actual = Fq6::from_base_prime_field_elems(random_coeffs).unwrap();
assert_eq!(actual, expected);
}
}
#[test]
fn test_from_base_prime_field_element() {
let ext_degree = Fq6::extension_degree() as usize;
let max_num_elems_to_test = 10;
for _ in 0..max_num_elems_to_test {
let mut random_coeffs = vec![Fq::zero(); ext_degree];
let random_coeff = Fq::rand(&mut test_rng());
let res = Fq6::from_base_prime_field(random_coeff);
random_coeffs[0] = random_coeff;
assert_eq!(
res,
Fq6::from_base_prime_field_elems(random_coeffs).unwrap()
);
}
}
}
impl<P: CubicExtConfig> FftField for CubicExtField<P>
where
P::BaseField: FftField,
{
const GENERATOR: Self = Self::new(
P::BaseField::GENERATOR,
P::BaseField::ZERO,
P::BaseField::ZERO,
);
const TWO_ADICITY: u32 = P::BaseField::TWO_ADICITY;
const TWO_ADIC_ROOT_OF_UNITY: Self = Self::new(
P::BaseField::TWO_ADIC_ROOT_OF_UNITY,
P::BaseField::ZERO,
P::BaseField::ZERO,
);
const SMALL_SUBGROUP_BASE: Option<u32> = P::BaseField::SMALL_SUBGROUP_BASE;
const SMALL_SUBGROUP_BASE_ADICITY: Option<u32> = P::BaseField::SMALL_SUBGROUP_BASE_ADICITY;
const LARGE_SUBGROUP_ROOT_OF_UNITY: Option<Self> =
if let Some(x) = P::BaseField::LARGE_SUBGROUP_ROOT_OF_UNITY {
Some(Self::new(x, P::BaseField::ZERO, P::BaseField::ZERO))
} else {
None
};
}