use core::fmt::Debug;
use core::iter::Sum;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use std::sync::OnceLock;
use p3_field::{Field, PrimeCharacteristicRing, PrimeField64, TwoAdicField};
pub use p3_goldilocks::Goldilocks;
use spongefish::{
ByteArray, Decoding, Encoding, NargDeserialize, VerificationError, VerificationResult,
};
mod ntt;
const Q0: u64 = 0xFFFF_FFFF_0000_0001;
const W: u64 = 7;
const GL_NEG_ONE: u64 = 0xFFFF_FFFF_0000_0000;
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub struct Goldilocks4 {
pub c: [Goldilocks; 4],
}
impl Goldilocks4 {
#[inline]
pub const fn new(c: [Goldilocks; 4]) -> Self {
Self { c }
}
pub const ZERO: Self = Self {
c: [Goldilocks::new(0); 4],
};
pub const ONE: Self = Self {
c: [
Goldilocks::new(1),
Goldilocks::new(0),
Goldilocks::new(0),
Goldilocks::new(0),
],
};
#[inline]
pub fn is_zero(&self) -> bool {
self.c.iter().all(|x| x.is_zero())
}
#[inline]
pub fn inverse(self) -> Self {
self.try_inverse()
.expect("tried to invert zero in Goldilocks4")
}
pub fn try_inverse(self) -> Option<Self> {
if self.is_zero() {
return None;
}
let [c0, c1, c2, c3] = self.c;
let w = Goldilocks::new(W);
let p0_sq = c0.square() + w * c1.square();
let p1_sq = Goldilocks::new(2) * c0 * c1;
let q0_sq = c2.square() + w * c3.square();
let q1_sq = Goldilocks::new(2) * c2 * c3;
let xq0_sq = w * q1_sq;
let xq1_sq = q0_sq;
let n0 = p0_sq - xq0_sq;
let n1 = p1_sq - xq1_sq;
let norm = n0.square() - w * n1.square();
let norm_inv = norm.try_inverse()?;
let n0_inv = n0 * norm_inv;
let n1_inv = Goldilocks::new(GL_NEG_ONE) * n1 * norm_inv;
let neg_c2 = -c2;
let neg_c3 = -c3;
let r0 = c0 * n0_inv + w * c1 * n1_inv;
let r1 = c0 * n1_inv + c1 * n0_inv;
let r2 = neg_c2 * n0_inv + w * neg_c3 * n1_inv;
let r3 = neg_c3 * n0_inv + neg_c2 * n1_inv;
Some(Self::new([r0, r1, r2, r3]))
}
#[inline]
pub fn pow(self, mut exp: u64) -> Self {
if exp == 0 {
return Self::ONE;
}
let mut result = Self::ONE;
let mut base = self;
while exp > 0 {
if exp & 1 == 1 {
result *= base;
}
base *= base;
exp >>= 1;
}
result
}
pub fn to_bytes(self) -> [u8; 32] {
let mut out = [0u8; 32];
for i in 0..4 {
out[i * 8..(i + 1) * 8].copy_from_slice(&self.c[i].as_canonical_u64().to_le_bytes());
}
out
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
if b.len() != 32 {
return None;
}
let mut c = [Goldilocks::new(0); 4];
for i in 0..4 {
let v = u64::from_le_bytes(b[i * 8..(i + 1) * 8].try_into().unwrap());
if v >= Q0 {
return None;
}
c[i] = Goldilocks::new(v);
}
Some(Self { c })
}
pub fn two_adic_generator(log_n: usize) -> Self {
assert!(
log_n <= 34,
"two_adic_generator: log_n = {log_n} > 34 exceeds the 2-adicity \
of F_{{q₀⁴}}^× (which is 34)."
);
if log_n <= 32 {
let g_base = Goldilocks::two_adic_generator(log_n);
return Self::new([
g_base,
Goldilocks::new(0),
Goldilocks::new(0),
Goldilocks::new(0),
]);
}
let mut g = *omega_34();
for _ in log_n..34 {
g = g * g;
}
g
}
pub fn ntt(mut coeffs: Vec<Goldilocks4>) -> Vec<Goldilocks4> {
ntt::ntt_in_place(&mut coeffs);
coeffs
}
}
impl Neg for Goldilocks4 {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self::new(self.c.map(|x| -x))
}
}
impl Add for Goldilocks4 {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self::new(core::array::from_fn(|i| self.c[i] + rhs.c[i]))
}
}
impl AddAssign for Goldilocks4 {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl Sub for Goldilocks4 {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self::new(core::array::from_fn(|i| self.c[i] - rhs.c[i]))
}
}
impl SubAssign for Goldilocks4 {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl Mul for Goldilocks4 {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
let [a0, a1, a2, a3] = self.c;
let [b0, b1, b2, b3] = rhs.c;
let w = Goldilocks::new(W);
let r0 = a0 * b0 + w * a1 * b1 + w * a2 * b3 + w * a3 * b2;
let r1 = a0 * b1 + a1 * b0 + a2 * b2 + w * a3 * b3;
let r2 = a0 * b2 + w * a1 * b3 + a2 * b0 + w * a3 * b1;
let r3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
Self::new([r0, r1, r2, r3])
}
}
impl MulAssign for Goldilocks4 {
#[inline]
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl Sum for Goldilocks4 {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Goldilocks4::ZERO, |acc, x| acc + x)
}
}
impl zeroize::Zeroize for Goldilocks4 {
fn zeroize(&mut self) {
for limb in &mut self.c {
*limb = Goldilocks::ZERO;
}
}
}
impl Encoding<[u8]> for Goldilocks4 {
fn encode(&self) -> impl AsRef<[u8]> {
self.to_bytes()
}
}
impl Decoding<[u8]> for Goldilocks4 {
type Repr = ByteArray<64>;
fn decode(buf: Self::Repr) -> Self {
let bytes: &[u8; 64] = buf.as_ref();
let q0_u128 = Q0 as u128;
let mut c = [Goldilocks::new(0); 4];
for i in 0..4 {
let raw = u128::from_le_bytes(bytes[i * 16..(i + 1) * 16].try_into().unwrap());
let val = (raw % q0_u128) as u64;
c[i] = Goldilocks::new(val);
}
Goldilocks4::new(c)
}
}
impl NargDeserialize for Goldilocks4 {
fn deserialize_from_narg(buf: &mut &[u8]) -> VerificationResult<Self> {
if buf.len() < 32 {
return Err(VerificationError);
}
let (head, tail) = buf.split_at(32);
let mut c = [Goldilocks::new(0); 4];
for i in 0..4 {
let v = u64::from_le_bytes(head[i * 8..(i + 1) * 8].try_into().unwrap());
if v >= Q0 {
return Err(VerificationError);
}
c[i] = Goldilocks::new(v);
}
*buf = tail;
Ok(Goldilocks4::new(c))
}
}
pub trait SumcheckField:
Sized
+ Copy
+ Send
+ Sync
+ PartialEq
+ Debug
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Neg<Output = Self>
+ AddAssign
+ SubAssign
+ MulAssign
+ Sum
+ 'static
{
const ZERO: Self;
const ONE: Self;
fn inverse(&self) -> Option<Self>;
#[inline]
fn double(&self) -> Self {
*self + *self
}
}
impl SumcheckField for Goldilocks4 {
const ZERO: Self = Goldilocks4::ZERO;
const ONE: Self = Goldilocks4::ONE;
#[inline]
fn inverse(&self) -> Option<Self> {
self.try_inverse()
}
}
fn omega_34() -> &'static Goldilocks4 {
static CELL: OnceLock<Goldilocks4> = OnceLock::new();
CELL.get_or_init(|| {
let b = Goldilocks::new(15_659_105_665_374_529_263);
let zero = Goldilocks::new(0);
if let Some(c) = goldilocks_sqrt(b) {
let candidate = Goldilocks4::new([zero, zero, c, zero]);
debug_assert_eq!(
candidate * candidate,
Goldilocks4::new([zero, b, zero, zero]),
"omega_34² ≠ omega_33 (case Y)"
);
return candidate;
}
let seven_inv = Goldilocks::new(7).inverse();
let target = b * seven_inv;
let d = goldilocks_sqrt(target).expect("either b or b/7 must be a square in F_{q₀}");
let candidate = Goldilocks4::new([zero, zero, zero, d]);
debug_assert_eq!(
candidate * candidate,
Goldilocks4::new([zero, b, zero, zero]),
"omega_34² ≠ omega_33 (case XY)"
);
candidate
})
}
fn goldilocks_sqrt(target: Goldilocks) -> Option<Goldilocks> {
if target.is_zero() {
return Some(Goldilocks::new(0));
}
let s: u32 = 32;
let m_odd: u64 = (1u64 << 32) - 1;
let q_minus_one_half_low: u64 = m_odd; let euler = {
let mut acc = target.exp_u64(q_minus_one_half_low);
acc = acc.exp_power_of_2(31);
acc
};
if !euler.is_one() {
return None;
}
let z = Goldilocks::new(7); let mut m_curr: u32 = s;
let mut c = z.exp_u64(m_odd);
let mut t = target.exp_u64(m_odd);
let mut r = target.exp_u64(m_odd.div_ceil(2));
loop {
if t.is_one() {
return Some(r);
}
let mut i: u32 = 0;
let mut tmp = t;
while !tmp.is_one() {
tmp = tmp.square();
i += 1;
if i >= m_curr {
return None; }
}
let b = c.exp_power_of_2((m_curr - i - 1) as usize);
m_curr = i;
c = b.square();
t *= c;
r *= b;
}
}
pub fn psi(i: u64, t: u64) -> Goldilocks4 {
assert!(
t.is_power_of_two(),
"psi: t must be a power of two, got {t}"
);
let log_t = t.trailing_zeros() as usize;
Goldilocks4::two_adic_generator(log_t).pow(i)
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
use std::collections::HashSet;
fn random_gl(rng: &mut ChaCha20Rng) -> Goldilocks {
use rand::Rng;
loop {
let v: u64 = rng.next_u64();
if v < Q0 {
return Goldilocks::new(v);
}
}
}
fn random_g4(rng: &mut ChaCha20Rng) -> Goldilocks4 {
Goldilocks4::new([
random_gl(rng),
random_gl(rng),
random_gl(rng),
random_gl(rng),
])
}
fn random_nonzero_g4(rng: &mut ChaCha20Rng) -> Goldilocks4 {
loop {
let v = random_g4(rng);
if !v.is_zero() {
return v;
}
}
}
#[test]
fn addition_commutative() {
let mut rng = ChaCha20Rng::seed_from_u64(0);
for _ in 0..100 {
let a = random_g4(&mut rng);
let b = random_g4(&mut rng);
assert_eq!(a + b, b + a);
}
}
#[test]
fn multiplicative_inverse() {
let mut rng = ChaCha20Rng::seed_from_u64(0);
for _ in 0..100 {
let a = random_nonzero_g4(&mut rng);
assert_eq!(a * a.inverse(), Goldilocks4::ONE);
}
}
#[test]
fn distributivity() {
let mut rng = ChaCha20Rng::seed_from_u64(0);
for _ in 0..100 {
let a = random_g4(&mut rng);
let b = random_g4(&mut rng);
let c = random_g4(&mut rng);
assert_eq!(a * (b + c), a * b + a * c);
}
}
#[test]
fn pow_zero_is_one() {
let mut rng = ChaCha20Rng::seed_from_u64(10);
for _ in 0..20 {
let a = random_g4(&mut rng);
assert_eq!(a.pow(0), Goldilocks4::ONE);
}
assert_eq!(Goldilocks4::ZERO.pow(0), Goldilocks4::ONE);
}
#[test]
fn two_adic_generator_order() {
let log_n = 8usize;
let g = Goldilocks4::two_adic_generator(log_n);
let order = 1u64 << log_n;
assert_eq!(g.pow(order), Goldilocks4::ONE);
assert_ne!(g.pow(order / 2), Goldilocks4::ONE);
}
#[test]
fn two_adic_generator_extension_levels_have_correct_order() {
for log_n in [33usize, 34] {
let g = Goldilocks4::two_adic_generator(log_n);
let order = 1u64 << log_n;
assert_eq!(
g.pow(order),
Goldilocks4::ONE,
"g^{order} ≠ 1 at log_n={log_n}"
);
assert_ne!(
g.pow(order / 2),
Goldilocks4::ONE,
"g^{} = 1 at log_n={log_n} — order is smaller than 2^{log_n}",
order / 2
);
}
}
#[test]
fn omega_34_squared_is_omega_33() {
let omega_34 = Goldilocks4::two_adic_generator(34);
let omega_33 = Goldilocks4::two_adic_generator(33);
assert_eq!(omega_34 * omega_34, omega_33);
}
#[test]
fn omega_33_squared_is_g_32() {
let omega_33 = Goldilocks4::two_adic_generator(33);
let g_32 = Goldilocks4::two_adic_generator(32);
assert_eq!(omega_33 * omega_33, g_32);
}
#[test]
fn goldilocks_sqrt_round_trips_on_squares() {
assert_eq!(
goldilocks_sqrt(Goldilocks::new(0)),
Some(Goldilocks::new(0))
);
let mut rng = ChaCha20Rng::seed_from_u64(7);
for _ in 0..50 {
let a = random_gl(&mut rng);
let sq = a * a;
let root = goldilocks_sqrt(sq).expect("squares are squares");
assert_eq!(root * root, sq);
}
assert_eq!(goldilocks_sqrt(Goldilocks::new(7)), None);
}
#[test]
fn to_bytes_round_trip() {
let mut rng = ChaCha20Rng::seed_from_u64(3);
for _ in 0..100 {
let a = random_g4(&mut rng);
let bytes = a.to_bytes();
assert_eq!(bytes.len(), 32);
let back = Goldilocks4::from_bytes(&bytes).unwrap();
assert_eq!(a, back);
}
}
#[test]
fn from_bytes_rejects_non_canonical() {
let mut buf = [0u8; 32];
buf[..8].copy_from_slice(&Q0.to_le_bytes());
assert!(Goldilocks4::from_bytes(&buf).is_none());
}
#[test]
fn narg_deserialize_rejects_non_canonical() {
let mut buf = [0u8; 32];
buf[..8].copy_from_slice(&Q0.to_le_bytes());
let mut slice: &[u8] = &buf;
assert!(Goldilocks4::deserialize_from_narg(&mut slice).is_err());
}
#[test]
fn narg_deserialize_rejects_short_buffer() {
let buf = [0u8; 16];
let mut slice: &[u8] = &buf;
assert!(Goldilocks4::deserialize_from_narg(&mut slice).is_err());
}
#[test]
fn psi_zero_is_one() {
assert_eq!(psi(0, 1 << 22), Goldilocks4::ONE);
}
#[test]
fn psi_is_injective() {
let t = 1u64 << 22;
let mut seen = HashSet::new();
for i in 0..1000u64 {
let v = psi(i, t);
assert!(seen.insert(v.to_bytes()), "ψ({i}) collides");
}
}
#[test]
fn psi_is_multiplicative() {
let t = 1u64 << 22;
assert_eq!(psi(1, t) * psi(1, t), psi(2, t));
assert_eq!(psi(3, t) * psi(5, t), psi(8, t));
}
#[test]
fn psi_works_at_extension_2_adicity_ceiling() {
let t = 1u64 << 34;
assert_eq!(psi(0, t), Goldilocks4::ONE);
assert_eq!(psi(1, t).pow(t), Goldilocks4::ONE);
assert_ne!(psi(1, t).pow(t / 2), Goldilocks4::ONE);
assert_eq!(psi(7, t) * psi(11, t), psi(18, t));
}
}