diff --git a/Cargo.toml b/Cargo.toml index 73a8316f..cc0a7425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,11 @@ optional = true version = "0.13" default-features = false +[dependencies.pasta_curves] +version = "0.5" +default-features = false +optional = true + [dependencies.group] version = "0.13" default-features = false @@ -68,7 +73,7 @@ default = ["groups", "pairings", "alloc", "bits"] bits = ["ff/bits"] groups = ["group"] pairings = ["groups", "pairing"] -alloc = ["group/alloc"] +alloc = ["group/alloc", "pasta_curves/alloc"] experimental = ["digest"] nightly = ["subtle/nightly"] basefield = [] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b54a9f45..fb11b593 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.61.0" +channel = "1.66.0" components = [ "clippy", "rustfmt" ] diff --git a/src/fp.rs b/src/fp.rs index 840e80fa..7b815e21 100644 --- a/src/fp.rs +++ b/src/fp.rs @@ -1,8 +1,11 @@ //! This module provides an implementation of the BLS12-381 base field `GF(p)` //! where `p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab` +#![allow(clippy::needless_borrow)] +use core::cmp::Ordering; use core::fmt; use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use ff::{Field, PrimeField, WithSmallOrderMulGroup}; use rand_core::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -54,6 +57,27 @@ impl PartialEq for Fp { } } +impl Ord for Fp { + fn cmp(&self, other: &Self) -> Ordering { + let left = self.to_repr().0; + let right = other.to_repr().0; + left.iter() + .zip(right.iter()) + .rev() + .find_map(|(left_byte, right_byte)| match left_byte.cmp(right_byte) { + Ordering::Equal => None, + res => Some(res), + }) + .unwrap_or(Ordering::Equal) + } +} + +impl PartialOrd for Fp { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl ConditionallySelectable for Fp { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { Fp([ @@ -67,6 +91,12 @@ impl ConditionallySelectable for Fp { } } +impl From for Fp { + fn from(value: u64) -> Self { + Self([value, 0, 0, 0, 0, 0]) * R2 + } +} + /// p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 const MODULUS: [u64; 6] = [ 0xb9fe_ffff_ffff_aaab, @@ -110,6 +140,85 @@ const R3: Fp = Fp([ 0x0aa6_3460_9175_5d4d, ]); +/// Fp(1/2) +/// sage: modulus = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab +/// sage: hex(((1 / 2) * (2^384)) % modulus) +/// '0x17fbb8571a006596d3916126f2d14ca26e22d1ec31ebb502633cb57c253c276f855000053ab000011804000000015554' +const TWO_INV: Fp = Fp([ + 0x1804000000015554, + 0x855000053ab00001, + 0x633cb57c253c276f, + 0x6e22d1ec31ebb502, + 0xd3916126f2d14ca2, + 0x17fbb8571a006596, +]); + +/// Generator of the field. +/// sage: modulus = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab +/// sage: hex((2 * (2^384)) % modulus) +/// '0x11ebab9dbb81e28c6cf28d7901622c038b256521ed1f9bcb57605e0db0ddbb51b93c0018d6c40005321300000006554f' +const GENERATOR: Fp = Fp([ + 0x321300000006554f, + 0xb93c0018d6c40005, + 0x57605e0db0ddbb51, + 0x8b256521ed1f9bcb, + 0x6cf28d7901622c03, + 0x11ebab9dbb81e28c, +]); + +/// Primitive root of unity +/// sage: modulus = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab +/// sage: hex((GF(modulus)(2) ^ ((modulus - 1) >> 1)) * (2^384)) +/// '0x40ab3263eff0206ef148d1ea0f4c069eca8f3318332bb7a07e83a49a2e99d6932b7fff2ed47fffd43f5fffffffcaaae' +const ROOT_OF_UNITY: Fp = Fp([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x07e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x40ab3263eff0206, +]); + +/// Inverse of the primitive root of unity. +/// sage: modulus = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab +/// sage: hex((1 / (GF(modulus)(2) ^ ((modulus - 1) >> 1))) * (2^384)) +/// '0x40ab3263eff0206ef148d1ea0f4c069eca8f3318332bb7a07e83a49a2e99d6932b7fff2ed47fffd43f5fffffffcaaae' +const ROOT_OF_UNITY_INV: Fp = Fp([ + 0x43f5fffffffcaaae, + 0x32b7fff2ed47fffd, + 0x07e83a49a2e99d69, + 0xeca8f3318332bb7a, + 0xef148d1ea0f4c069, + 0x40ab3263eff0206, +]); + +/// DELTA +/// DELTA +/// sage: modulus = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab +/// sage: hex((GF(modulus)(2) ^ (2 ^ 1)) * (2^384)) +/// '0x9d645513d83de7e8ec9733bbf78ab2fb1d37ebee6ba24d7478fe97a6b0a807f53cc0032fc34000aaa270000000cfff3' +const DELTA: Fp = Fp([ + 0xaa270000000cfff3, + 0x53cc0032fc34000a, + 0x478fe97a6b0a807f, + 0xb1d37ebee6ba24d7, + 0x8ec9733bbf78ab2f, + 0x9d645513d83de7e, +]); + +/// ZETA +/// sage: modulus = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab +/// sage: hex((1 / (GF(modulus)(2) ^ ((modulus - 1) // 3))) * (2^384)) +/// '0x18f020655463874103f97d6e83d050d28eb60ebe01bacb9e587042afd3851b955dab22461fcda5d2cd03c9e48671f071' +const ZETA: Fp = Fp([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741, +]); + impl<'a> Neg for &'a Fp { type Output = Fp; @@ -158,6 +267,131 @@ impl<'a, 'b> Mul<&'b Fp> for &'a Fp { impl_binops_additive!(Fp, Fp); impl_binops_multiplicative!(Fp, Fp); +impl core::iter::Sum for Fp +where + T: core::borrow::Borrow, +{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Self::zero(), |acc, item| acc + item.borrow()) + } +} + +impl core::iter::Product for Fp +where + T: core::borrow::Borrow, +{ + fn product(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Self::one(), |acc, item| acc * item.borrow()) + } +} + +impl Field for Fp { + const ZERO: Self = Fp::zero(); + const ONE: Self = Fp::one(); + + fn random(rng: impl RngCore) -> Self { + Self::random(rng) + } + + fn square(&self) -> Self { + self.square() + } + + fn double(&self) -> Self { + self.add(self) + } + + fn invert(&self) -> CtOption { + self.invert() + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + ff::helpers::sqrt_ratio_generic(num, div) + } + + fn sqrt(&self) -> CtOption { + self.sqrt() + } +} + +/// This struct represents the [`Fp`] struct serialized as an array of 48 bytes +/// in Little Endian. +#[derive(Debug, Clone, Copy)] +pub struct ReprFp(pub(crate) [u8; 48]); + +impl AsMut<[u8]> for ReprFp { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } +} + +impl AsRef<[u8]> for ReprFp { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsRef<[u8; 48]> for ReprFp { + fn as_ref(&self) -> &[u8; 48] { + &self.0 + } +} + +impl From<[u8; 48]> for ReprFp { + fn from(value: [u8; 48]) -> Self { + // This uses little endian and so, assumes the array passed in is + // in that format. + Self(value) + } +} + +impl Default for ReprFp { + fn default() -> Self { + Self([0u8; 48]) + } +} + +impl PrimeField for Fp { + type Repr = ReprFp; + + fn from_repr(mut r: Self::Repr) -> CtOption { + // This uses little endian and so, assumes the array passed in is + // in that format. + r.0.reverse(); + Self::from_bytes(&r.0) + } + + fn to_repr(&self) -> Self::Repr { + let mut le_bytes = self.to_bytes(); + le_bytes.reverse(); + ReprFp(le_bytes) + } + + fn is_odd(&self) -> Choice { + Choice::from(self.to_bytes()[0] & 1) + } + + const MODULUS: &'static str = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab"; + const NUM_BITS: u32 = 381; + const CAPACITY: u32 = Self::NUM_BITS - 1; + const TWO_INV: Self = TWO_INV; + const MULTIPLICATIVE_GENERATOR: Self = GENERATOR; + const S: u32 = 1; + const ROOT_OF_UNITY: Self = ROOT_OF_UNITY; + const ROOT_OF_UNITY_INV: Self = ROOT_OF_UNITY_INV; + const DELTA: Self = DELTA; +} + +impl WithSmallOrderMulGroup<3> for Fp { + const ZETA: Self = ZETA; +} + impl Fp { /// Returns zero, the additive identity. #[inline] @@ -171,11 +405,6 @@ impl Fp { R } - /// Checks if a value is equal to zero in constant time. - pub fn is_zero(&self) -> Choice { - self.ct_eq(&Fp::zero()) - } - /// Attempts to convert a big-endian byte representation of /// a scalar into an `Fp`, failing if the input is not canonical. pub fn from_bytes(bytes: &[u8; 48]) -> CtOption { @@ -395,7 +624,7 @@ impl Fp { // Attempt to subtract the modulus, to ensure the value // is smaller than the modulus. - (&Fp([d0, d1, d2, d3, d4, d5])).subtract_p() + (Fp([d0, d1, d2, d3, d4, d5])).subtract_p() } #[inline] @@ -669,6 +898,27 @@ impl Fp { } } +#[test] +fn test_constants() { + assert_eq!(Fp::ONE.double(), GENERATOR); + assert_eq!(Fp::ONE, Fp::ONE.double() * TWO_INV); + assert_eq!(Fp::ONE, ROOT_OF_UNITY.pow([1 << Fp::S])); + assert_eq!(Fp::ONE, ROOT_OF_UNITY * ROOT_OF_UNITY_INV); + // sage: modulus = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab + // sage: hex((modulus - 1) >> 1) + // '0xd0088f51cbff34d258dd3db21a5d66bb23ba5c279c2895fb39869507b587b120f55ffff58a9ffffdcff7fffffffd555' + let t = [ + 0xdcff7fffffffd555, + 0x0f55ffff58a9ffff, + 0xb39869507b587b12, + 0xb23ba5c279c2895f, + 0x258dd3db21a5d66b, + 0xd0088f51cbff34d, + ]; + assert_eq!(Fp::ONE, DELTA.pow(t)); + assert_eq!(Fp::ONE, ZETA.pow([3])); +} + #[test] fn test_conditional_selection() { let a = Fp([1, 2, 3, 4, 5, 6]); @@ -987,6 +1237,13 @@ fn test_lexicographic_largest() { )); } +#[test] +fn test_repr() { + for value in [Fp::ZERO, Fp::ONE, Fp::ROOT_OF_UNITY] { + assert_eq!(Fp::from_repr(value.to_repr()).unwrap(), value); + } +} + #[cfg(feature = "zeroize")] #[test] fn test_zeroize() { diff --git a/src/fp2.rs b/src/fp2.rs index 1ad856fc..41a0200b 100644 --- a/src/fp2.rs +++ b/src/fp2.rs @@ -1,7 +1,9 @@ //! This module implements arithmetic over the quadratic extension field Fp2. +#![allow(clippy::needless_borrow)] use core::fmt; use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use ff::Field; use rand_core::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; diff --git a/src/g1.rs b/src/g1.rs index 0e05f1d4..a83b2f3f 100644 --- a/src/g1.rs +++ b/src/g1.rs @@ -1,10 +1,13 @@ //! This module provides an implementation of the $\mathbb{G}_1$ group of BLS12-381. +#[cfg(feature = "alloc")] +use alloc::boxed::Box; use core::borrow::Borrow; use core::fmt; use core::iter::Sum; use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use group::{ + ff::Field, prime::{PrimeCurve, PrimeCurveAffine, PrimeGroup}, Curve, Group, GroupEncoding, UncompressedEncoding, }; @@ -14,6 +17,9 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; #[cfg(feature = "alloc")] use group::WnafGroup; +#[cfg(feature = "alloc")] +use pasta_curves::arithmetic::{Coordinates, CurveAffine, CurveExt}; + use crate::fp::Fp; use crate::Scalar; @@ -149,6 +155,20 @@ impl<'a, 'b> Sub<&'b G1Projective> for &'a G1Affine { } } +impl Add for G1Affine { + type Output = G1Projective; + fn add(self, rhs: G1Affine) -> Self::Output { + self.to_curve() + rhs.to_curve() + } +} + +impl Sub for G1Affine { + type Output = G1Projective; + fn sub(self, rhs: G1Affine) -> Self::Output { + self + -rhs + } +} + impl<'a, 'b> Sub<&'b G1Affine> for &'a G1Projective { type Output = G1Projective; @@ -436,6 +456,50 @@ fn endomorphism(p: &G1Affine) -> G1Affine { res } +#[cfg(feature = "alloc")] +impl CurveAffine for G1Affine { + /// The scalar field of this elliptic curve. + type ScalarExt = crate::Scalar; + /// The base field over which this elliptic curve is constructed. + type Base = crate::fp::Fp; + /// The projective form of the curve + type CurveExt = G1Projective; + + /// Gets the coordinates of this point. + /// + /// Returns None if this is the identity. + fn coordinates(&self) -> CtOption> { + Coordinates::from_xy(self.x, self.y) + } + + /// Obtains a point given $(x, y)$, failing if it is not on the + /// curve. + fn from_xy(x: Self::Base, y: Self::Base) -> CtOption { + let p: G1Affine = Self { + x, + y, + infinity: x.ct_eq(&Self::Base::ZERO) & y.ct_eq(&Self::Base::ZERO), + }; + CtOption::new(p, p.is_on_curve()) + } + + /// Returns whether or not this element is on the curve; should + /// always be true unless an "unchecked" API was used. + fn is_on_curve(&self) -> Choice { + self.is_on_curve() + } + + /// Returns the curve constant $a$. + fn a() -> Self::Base { + Self::Base::ZERO + } + + /// Returns the curve constant $b$. + fn b() -> Self::Base { + B + } +} + /// This is an element of $\mathbb{G}_1$ represented in the projective coordinate space. #[cfg_attr(docsrs, doc(cfg(feature = "groups")))] #[derive(Copy, Clone, Debug)] @@ -587,6 +651,61 @@ impl<'a, 'b> Mul<&'b G1Affine> for &'a Scalar { } } +#[cfg(feature = "alloc")] +impl CurveExt for G1Projective { + type ScalarExt = Scalar; + type Base = Fp; + type AffineExt = G1Affine; + + const CURVE_ID: &'static str = "Bls12-381"; + + fn endo(&self) -> Self { + endomorphism(&G1Affine::from(self)).into() + } + + fn jacobian_coordinates(&self) -> (Fp, Fp, Fp) { + // Homogenous to Jacobian + let x = self.x * self.z; + let y = self.y * self.z.square(); + (x, y, self.z) + } + + fn hash_to_curve<'a>(_domain_prefix: &'a str) -> Box Self + 'a> { + // XXX: TODO + unimplemented!() + } + + fn is_on_curve(&self) -> Choice { + // Check (Y/Z)^2 = (X/Z)^3 + b + // <=> Z Y^2 - X^3 = Z^3 b + + (self.z * self.y.square() - self.x.square() * self.x) + .ct_eq(&(self.z.square() * self.z * G1Affine::b())) + | self.z.is_zero() + } + + fn b() -> Self::Base { + B + } + + fn a() -> Self::Base { + Self::Base::ZERO + } + + fn new_jacobian(x: Self::Base, y: Self::Base, z: Self::Base) -> CtOption { + // Jacobian to homogenous + let z_inv = z.invert().unwrap_or(Fp::zero()); + let p_x = x * z_inv; + let p_y = y * z_inv.square(); + let p = Self { + x: p_x, + y: Fp::conditional_select(&p_y, &Fp::one(), z.is_zero()), + z, + }; + CtOption::new(p, p.is_on_curve()) + } +} + impl_binops_additive!(G1Projective, G1Projective); impl_binops_multiplicative!(G1Projective, Scalar); impl_binops_multiplicative_mixed!(G1Affine, Scalar, G1Projective); diff --git a/src/g2.rs b/src/g2.rs index 838db3d9..909c15bc 100644 --- a/src/g2.rs +++ b/src/g2.rs @@ -258,8 +258,8 @@ impl G2Affine { let mut res = [0; 96]; - (&mut res[0..48]).copy_from_slice(&x.c1.to_bytes()[..]); - (&mut res[48..96]).copy_from_slice(&x.c0.to_bytes()[..]); + res[0..48].copy_from_slice(&x.c1.to_bytes()[..]); + res[48..96].copy_from_slice(&x.c0.to_bytes()[..]); // This point is in compressed form, so we set the most significant bit. res[0] |= 1u8 << 7; diff --git a/src/hash_to_curve/expand_msg.rs b/src/hash_to_curve/expand_msg.rs index 7187d46b..c32162e8 100644 --- a/src/hash_to_curve/expand_msg.rs +++ b/src/hash_to_curve/expand_msg.rs @@ -41,7 +41,7 @@ impl<'x, L: ArrayLength> ExpandMsgDst<'x, L> { let mut data = GenericArray::::default(); H::default() .chain(OVERSIZE_DST_SALT) - .chain(&dst) + .chain(dst) .finalize_xof_dirty() .read(&mut data); Self::Hashed(data) @@ -56,7 +56,7 @@ impl<'x, L: ArrayLength> ExpandMsgDst<'x, L> { H: Digest, { if dst.len() > 255 { - Self::Hashed(H::new().chain(OVERSIZE_DST_SALT).chain(&dst).finalize()) + Self::Hashed(H::new().chain(OVERSIZE_DST_SALT).chain(dst).finalize()) } else { Self::Raw(dst) } diff --git a/src/hash_to_curve/map_g1.rs b/src/hash_to_curve/map_g1.rs index b865f624..33dfcec1 100644 --- a/src/hash_to_curve/map_g1.rs +++ b/src/hash_to_curve/map_g1.rs @@ -7,6 +7,7 @@ use super::{HashToField, MapToCurve, Sgn0}; use crate::fp::Fp; use crate::g1::G1Projective; use crate::generic_array::{typenum::U64, GenericArray}; +use ff::Field; /// Coefficients of the 11-isogeny x map's numerator const ISO11_XNUM: [Fp; 12] = [ @@ -841,7 +842,7 @@ fn test_encode_to_curve_10() { for case in cases { let g = >>::encode_to_curve( - &case.msg, DOMAIN, + case.msg, DOMAIN, ); let aff = G1Affine::from(g); let g_uncompressed = aff.to_uncompressed(); @@ -922,7 +923,7 @@ fn test_hash_to_curve_10() { for case in cases { let g = >>::hash_to_curve( - &case.msg, DOMAIN, + case.msg, DOMAIN, ); let g_uncompressed = G1Affine::from(g).to_uncompressed(); diff --git a/src/hash_to_curve/map_g2.rs b/src/hash_to_curve/map_g2.rs index 3e4ba1ab..2d6d30ce 100644 --- a/src/hash_to_curve/map_g2.rs +++ b/src/hash_to_curve/map_g2.rs @@ -9,6 +9,7 @@ use crate::generic_array::{ GenericArray, }; use crate::{fp::Fp, fp2::Fp2, g2::G2Projective}; +use ff::Field; /// Coefficients of the 3-isogeny x map's numerator const ISO3_XNUM: [Fp2; 4] = [ @@ -609,7 +610,7 @@ fn test_encode_to_curve_10() { for case in cases { let g = >>::encode_to_curve( - &case.msg, DOMAIN, + case.msg, DOMAIN, ); let g_uncompressed = G2Affine::from(g).to_uncompressed(); @@ -699,7 +700,7 @@ fn test_hash_to_curve_10() { for case in cases { let g = >>::hash_to_curve( - &case.msg, DOMAIN, + case.msg, DOMAIN, ); let g_uncompressed = G2Affine::from(g).to_uncompressed(); diff --git a/src/scalar.rs b/src/scalar.rs index 59be5dca..93eaee4d 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -1,11 +1,13 @@ //! This module provides an implementation of the BLS12-381 scalar field $\mathbb{F}_q$ //! where `q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` +#![allow(clippy::needless_borrow)] +use core::cmp::Ordering; use core::fmt; use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use rand_core::RngCore; -use ff::{Field, PrimeField}; +use ff::{Field, PrimeField, WithSmallOrderMulGroup}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; #[cfg(feature = "bits")] @@ -60,6 +62,27 @@ impl PartialEq for Scalar { } } +impl Ord for Scalar { + fn cmp(&self, other: &Self) -> Ordering { + let left = self.to_repr(); + let right = other.to_repr(); + left.iter() + .zip(right.iter()) + .rev() + .find_map(|(left_byte, right_byte)| match left_byte.cmp(right_byte) { + Ordering::Equal => None, + res => Some(res), + }) + .unwrap_or(Ordering::Equal) + } +} + +impl PartialOrd for Scalar { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl ConditionallySelectable for Scalar { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { Scalar([ @@ -221,6 +244,12 @@ const DELTA: Scalar = Scalar([ 0x6185_d066_27c0_67cb, ]); +/// ZETA +// sage: modulus = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +//sage: GF(modulus).primitive_element() ^ ((modulus - 1) // 3) +//228988810152649578064853576960394133503 +const ZETA: Scalar = Scalar::from_raw([0x00000000ffffffff, 0xac45a4010001a402, 0, 0]); + impl Default for Scalar { #[inline] fn default() -> Self { @@ -671,7 +700,7 @@ impl Field for Scalar { // (t - 1) // 2 = 6104339283789297388802252303364915521546564123189034618274734669823 ff::helpers::sqrt_tonelli_shanks( self, - &[ + [ 0x7fff_2dff_7fff_ffff, 0x04d0_ec02_a9de_d201, 0x94ce_bea4_199c_ec04, @@ -712,6 +741,10 @@ impl PrimeField for Scalar { const DELTA: Self = DELTA; } +impl WithSmallOrderMulGroup<3> for Scalar { + const ZETA: Self = ZETA; +} + #[cfg(all(feature = "bits", not(target_pointer_width = "64")))] type ReprBits = [u32; 8];