diff --git a/field/src/extension_field/mod.rs b/field/src/extension_field/mod.rs index abd8e65c..0e2b3f15 100644 --- a/field/src/extension_field/mod.rs +++ b/field/src/extension_field/mod.rs @@ -5,6 +5,7 @@ use crate::field_types::Field; pub mod algebra; pub mod quadratic; pub mod quartic; +pub mod quintic; /// Optimal extension field trait. /// A degree `d` field extension is optimal if there exists a base field element `W`, @@ -67,6 +68,8 @@ pub trait Extendable: Field + Sized { const DTH_ROOT: Self; + /// Chosen so that when raised to the power `(p^D - 1) >> F::Extension::TWO_ADICITY)` + /// we obtain F::EXT_POWER_OF_TWO_GENERATOR. const EXT_MULTIPLICATIVE_GROUP_GENERATOR: [Self; D]; /// Chosen so that when raised to the power `1<<(Self::TWO_ADICITY-Self::BaseField::TWO_ADICITY)`, diff --git a/field/src/extension_field/quintic.rs b/field/src/extension_field/quintic.rs new file mode 100644 index 00000000..d2c29ffe --- /dev/null +++ b/field/src/extension_field/quintic.rs @@ -0,0 +1,278 @@ +use std::fmt::{Debug, Display, Formatter}; +use std::iter::{Product, Sum}; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + +use num::bigint::BigUint; +use num::traits::Pow; +use rand::Rng; +use serde::{Deserialize, Serialize}; + +use crate::extension_field::{Extendable, FieldExtension, Frobenius, OEF}; +use crate::field_types::Field; +use crate::ops::Square; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct QuinticExtension>(pub [F; 5]); + +impl> Default for QuinticExtension { + fn default() -> Self { + Self::ZERO + } +} + +impl> OEF<5> for QuinticExtension { + const W: F = F::W; + const DTH_ROOT: F = F::DTH_ROOT; +} + +impl> Frobenius<5> for QuinticExtension {} + +impl> FieldExtension<5> for QuinticExtension { + type BaseField = F; + + fn to_basefield_array(&self) -> [F; 5] { + self.0 + } + + fn from_basefield_array(arr: [F; 5]) -> Self { + Self(arr) + } + + fn from_basefield(x: F) -> Self { + x.into() + } +} + +impl> From for QuinticExtension { + fn from(x: F) -> Self { + Self([x, F::ZERO, F::ZERO, F::ZERO, F::ZERO]) + } +} + +impl> Field for QuinticExtension { + const ZERO: Self = Self([F::ZERO; 5]); + const ONE: Self = Self([F::ONE, F::ZERO, F::ZERO, F::ZERO, F::ZERO]); + const TWO: Self = Self([F::TWO, F::ZERO, F::ZERO, F::ZERO, F::ZERO]); + const NEG_ONE: Self = Self([F::NEG_ONE, F::ZERO, F::ZERO, F::ZERO, F::ZERO]); + + // `p^5 - 1 = (p - 1)(p^4 + p^3 + p^2 + p + 1)`. The `p - 1` term + // has a two-adicity of `F::TWO_ADICITY` and the term `p^4 + p^3 + + // p^2 + p + 1` is odd since it is the sum of an odd number of odd + // terms. Hence the two-adicity of `p^5 - 1` is the same as for + // `p - 1`. + const TWO_ADICITY: usize = F::TWO_ADICITY; + const CHARACTERISTIC_TWO_ADICITY: usize = F::CHARACTERISTIC_TWO_ADICITY; + + const MULTIPLICATIVE_GROUP_GENERATOR: Self = Self(F::EXT_MULTIPLICATIVE_GROUP_GENERATOR); + const POWER_OF_TWO_GENERATOR: Self = Self(F::EXT_POWER_OF_TWO_GENERATOR); + + const BITS: usize = F::BITS * 5; + + fn order() -> BigUint { + F::order().pow(5u32) + } + fn characteristic() -> BigUint { + F::characteristic() + } + + // Algorithm 11.3.4 in Handbook of Elliptic and Hyperelliptic Curve Cryptography. + fn try_inverse(&self) -> Option { + if self.is_zero() { + return None; + } + + // Writing 'a' for self: + let d = self.frobenius(); // d = a^p + let e = d * d.frobenius(); // e = a^(p + p^2) + let f = e * e.repeated_frobenius(2); // f = a^(p + p^2 + p^3 + p^4) + + // f contains a^(r-1) and a^r is in the base field. + debug_assert!(FieldExtension::<5>::is_in_basefield(&(*self * f))); + + // g = a^r is in the base field, so only compute that + // coefficient rather than the full product. The equation is + // extracted from Mul::mul(...) below. + let Self([a0, a1, a2, a3, a4]) = *self; + let Self([b0, b1, b2, b3, b4]) = f; + let g = a0 * b0 + >::W * (a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1); + + Some(FieldExtension::<5>::scalar_mul(&f, g.inverse())) + } + + fn from_biguint(n: BigUint) -> Self { + Self([F::from_biguint(n), F::ZERO, F::ZERO, F::ZERO, F::ZERO]) + } + + fn from_canonical_u64(n: u64) -> Self { + F::from_canonical_u64(n).into() + } + + fn from_noncanonical_u128(n: u128) -> Self { + F::from_noncanonical_u128(n).into() + } + + fn rand_from_rng(rng: &mut R) -> Self { + Self::from_basefield_array([ + F::rand_from_rng(rng), + F::rand_from_rng(rng), + F::rand_from_rng(rng), + F::rand_from_rng(rng), + F::rand_from_rng(rng), + ]) + } +} + +impl> Display for QuinticExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} + {}*a + {}*a^2 + {}*a^3 + {}*a^4", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4] + ) + } +} + +impl> Debug for QuinticExtension { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +impl> Neg for QuinticExtension { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + Self([-self.0[0], -self.0[1], -self.0[2], -self.0[3], -self.0[4]]) + } +} + +impl> Add for QuinticExtension { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + Self([ + self.0[0] + rhs.0[0], + self.0[1] + rhs.0[1], + self.0[2] + rhs.0[2], + self.0[3] + rhs.0[3], + self.0[4] + rhs.0[4], + ]) + } +} + +impl> AddAssign for QuinticExtension { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl> Sum for QuinticExtension { + fn sum>(iter: I) -> Self { + iter.fold(Self::ZERO, |acc, x| acc + x) + } +} + +impl> Sub for QuinticExtension { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self { + Self([ + self.0[0] - rhs.0[0], + self.0[1] - rhs.0[1], + self.0[2] - rhs.0[2], + self.0[3] - rhs.0[3], + self.0[4] - rhs.0[4], + ]) + } +} + +impl> SubAssign for QuinticExtension { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl> Mul for QuinticExtension { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self { + let Self([a0, a1, a2, a3, a4]) = self; + let Self([b0, b1, b2, b3, b4]) = rhs; + let w = >::W; + + let c0 = a0 * b0 + w * (a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1); + let c1 = a0 * b1 + a1 * b0 + w * (a2 * b4 + a3 * b3 + a4 * b2); + let c2 = a0 * b2 + a1 * b1 + a2 * b0 + w * (a3 * b4 + a4 * b3); + let c3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0 + w * a4 * b4; + let c4 = a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + + Self([c0, c1, c2, c3, c4]) + } +} + +impl> MulAssign for QuinticExtension { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl> Square for QuinticExtension { + #[inline(always)] + fn square(&self) -> Self { + let Self([a0, a1, a2, a3, a4]) = *self; + let w = >::W; + let double_w = >::W.double(); + + let c0 = a0.square() + double_w * (a1 * a4 + a2 * a3); + let double_a0 = a0.double(); + let c1 = double_a0 * a1 + double_w * a2 * a4 + w * a3 * a3; + let c2 = double_a0 * a2 + a1 * a1 + double_w * a4 * a3; + let double_a1 = a1.double(); + let c3 = double_a0 * a3 + double_a1 * a2 + w * a4 * a4; + let c4 = double_a0 * a4 + double_a1 * a3 + a2 * a2; + + Self([c0, c1, c2, c3, c4]) + } +} + +impl> Product for QuinticExtension { + fn product>(iter: I) -> Self { + iter.fold(Self::ONE, |acc, x| acc * x) + } +} + +impl> Div for QuinticExtension { + type Output = Self; + + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: Self) -> Self::Output { + self * rhs.inverse() + } +} + +impl> DivAssign for QuinticExtension { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +#[cfg(test)] +mod tests { + mod goldilocks { + use crate::{test_field_arithmetic, test_field_extension}; + + test_field_extension!(crate::goldilocks_field::GoldilocksField, 5); + test_field_arithmetic!( + crate::extension_field::quintic::QuinticExtension< + crate::goldilocks_field::GoldilocksField, + > + ); + } +} diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs index 6c033bb2..af958629 100644 --- a/field/src/goldilocks_field.rs +++ b/field/src/goldilocks_field.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; use crate::extension_field::quadratic::QuadraticExtension; use crate::extension_field::quartic::QuarticExtension; +use crate::extension_field::quintic::QuinticExtension; use crate::extension_field::{Extendable, Frobenius}; use crate::field_types::{Field, Field64, PrimeField, PrimeField64}; use crate::inversion::try_inverse_u64; @@ -317,6 +318,31 @@ impl Extendable<4> for GoldilocksField { [Self(0), Self(0), Self(0), Self(12587610116473453104)]; } +impl Extendable<5> for GoldilocksField { + type Extension = QuinticExtension; + + const W: Self = Self(3); + + // DTH_ROOT = W^((ORDER - 1)/5) + const DTH_ROOT: Self = Self(1041288259238279555); + + const EXT_MULTIPLICATIVE_GROUP_GENERATOR: [Self; 5] = [ + Self(2899034827742553394), + Self(13012057356839176729), + Self(14593811582388663055), + Self(7722900811313895436), + Self(4557222484695340057), + ]; + + const EXT_POWER_OF_TWO_GENERATOR: [Self; 5] = [ + Self::POWER_OF_TWO_GENERATOR, + Self(0), + Self(0), + Self(0), + Self(0), + ]; +} + /// Fast addition modulo ORDER for x86-64. /// This function is marked unsafe for the following reasons: /// - It is only correct if x + y < 2**64 + ORDER = 0x1ffffffff00000001. diff --git a/plonky2/benches/field_arithmetic.rs b/plonky2/benches/field_arithmetic.rs index 8308e427..0e4383ee 100644 --- a/plonky2/benches/field_arithmetic.rs +++ b/plonky2/benches/field_arithmetic.rs @@ -1,5 +1,6 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use plonky2::field::extension_field::quartic::QuarticExtension; +use plonky2::field::extension_field::quintic::QuinticExtension; use plonky2::field::field_types::Field; use plonky2::field::goldilocks_field::GoldilocksField; use tynm::type_name; @@ -175,6 +176,7 @@ pub(crate) fn bench_field(c: &mut Criterion) { fn criterion_benchmark(c: &mut Criterion) { bench_field::(c); bench_field::>(c); + bench_field::>(c); } criterion_group!(benches, criterion_benchmark);