use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
pub const P: u64 = 0xFFFF_FFFF_0000_0001;
const NEG_ORDER: u64 = P.wrapping_neg();
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Goldilocks {
value: u64,
}
impl Goldilocks {
pub const ZERO: Self = Self { value: 0 };
#[inline]
pub const fn new(value: u64) -> Self {
Self { value }
}
#[inline]
pub fn as_canonical_u64(self) -> u64 {
let mut c = self.value;
if c >= P {
c -= P;
}
c
}
#[inline]
fn square(self) -> Self {
self * self
}
#[inline]
pub fn pow7(self) -> Self {
let x2 = self.square();
let x3 = x2 * self;
let x4 = x2.square();
x3 * x4
}
#[inline]
pub fn inv(self) -> Self {
if self.as_canonical_u64() == 0 {
return Self::ZERO;
}
let x = self;
let x2 = x * x;
let x3 = x2 * x;
let x4 = x2 * x2;
let x7 = x3 * x4;
let x6 = x3 * x3;
let x12 = x6 * x6;
let x15 = x12 * x3; let x30 = x15 * x15;
let x60 = x30 * x30;
let x120 = x60 * x60;
let x127 = x120 * x7; let x254 = x127 * x127;
let x255 = x254 * x; let mut t = x255;
for _ in 0..7 {
t = t * t;
}
let x_2p15m1 = t * x127; let x_2p16m2 = x_2p15m1 * x_2p15m1;
let x_2p16m1 = x_2p16m2 * x; t = x_2p16m1;
for _ in 0..15 {
t = t * t;
}
let x_2p31m1 = t * x_2p15m1; let x_2p32m2 = x_2p31m1 * x_2p31m1;
let x_epsilon = x_2p32m2 * x;
t = x_2p31m1;
for _ in 0..33 {
t = t * t;
}
t * x_epsilon
}
#[inline]
fn double(self) -> Self {
self + self
}
}
impl core::fmt::Debug for Goldilocks {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Goldilocks({})", self.as_canonical_u64())
}
}
impl Add for Goldilocks {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
let (sum, over) = self.value.overflowing_add(rhs.value);
let (mut sum, over) = sum.overflowing_add(u64::from(over) * NEG_ORDER);
if over {
sum += NEG_ORDER;
}
Self::new(sum)
}
}
impl AddAssign for Goldilocks {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl Sub for Goldilocks {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
let (diff, under) = self.value.overflowing_sub(rhs.value);
let (mut diff, under) = diff.overflowing_sub(u64::from(under) * NEG_ORDER);
if under {
diff -= NEG_ORDER;
}
Self::new(diff)
}
}
impl SubAssign for Goldilocks {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl Mul for Goldilocks {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
reduce128(u128::from(self.value) * u128::from(rhs.value))
}
}
impl MulAssign for Goldilocks {
#[inline]
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl core::iter::Sum for Goldilocks {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
reduce128(iter.map(|x| x.value as u128).sum::<u128>())
}
}
#[inline]
fn reduce128(x: u128) -> Goldilocks {
let x_lo = x as u64;
let x_hi = (x >> 64) as u64;
let x_hi_hi = x_hi >> 32;
let x_hi_lo = x_hi & NEG_ORDER;
let (mut t0, borrow) = x_lo.overflowing_sub(x_hi_hi);
if borrow {
t0 -= NEG_ORDER;
}
let t1 = x_hi_lo * NEG_ORDER;
let (res, carry) = t0.overflowing_add(t1);
Goldilocks::new(res + NEG_ORDER * u64::from(carry))
}
#[inline(always)]
pub fn apply_mat4(x: &mut [Goldilocks; 4]) {
let t01 = x[0] + x[1];
let t23 = x[2] + x[3];
let t0123 = t01 + t23;
let t01123 = t0123 + x[1];
let t01233 = t0123 + x[3];
x[3] = t01233 + x[0].double();
x[1] = t01123 + x[2].double();
x[0] = t01123 + t01;
x[2] = t01233 + t23;
}
#[inline]
pub fn mds_light_permutation(state: &mut [Goldilocks; 16]) {
for chunk in state.chunks_exact_mut(4) {
apply_mat4(chunk.try_into().unwrap());
}
let sums: [Goldilocks; 4] = core::array::from_fn(|k| {
(0..16).step_by(4).map(|j| state[j + k]).sum()
});
for (i, elem) in state.iter_mut().enumerate() {
*elem += sums[i % 4];
}
}
pub const MATRIX_DIAG_16: [Goldilocks; 16] = [
Goldilocks::new(0xde9b91a467d6afc0),
Goldilocks::new(0xc5f16b9c76a9be17),
Goldilocks::new(0x0ab0fef2d540ac55),
Goldilocks::new(0x3001d27009d05773),
Goldilocks::new(0xed23b1f906d3d9eb),
Goldilocks::new(0x5ce73743cba97054),
Goldilocks::new(0x1c3bab944af4ba24),
Goldilocks::new(0x2faa105854dbafae),
Goldilocks::new(0x53ffb3ae6d421a10),
Goldilocks::new(0xbcda9df8884ba396),
Goldilocks::new(0xfc1273e4a31807bb),
Goldilocks::new(0xc77952573d5142c0),
Goldilocks::new(0x56683339a819b85e),
Goldilocks::new(0x328fcbd8f0ddc8eb),
Goldilocks::new(0xb5101e303fce9cb7),
Goldilocks::new(0x774487b8c40089bb),
];
#[inline]
pub fn matmul_internal(state: &mut [Goldilocks; 16]) {
let sum: Goldilocks = state.iter().copied().sum();
for i in 0..16 {
state[i] *= MATRIX_DIAG_16[i];
state[i] += sum;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inv_chain_matches_fermat() {
let mut v = [0u64; 72];
v[..8].copy_from_slice(&[1, 2, 3, P - 1, P - 2, 0xFFFF_FFFF, 1 << 32, u64::MAX % P]);
let mut x = 0x9e3779b97f4a7c15u64;
for slot in v[8..].iter_mut() {
x = x.wrapping_mul(0xbf58476d1ce4e5b9).rotate_left(31);
*slot = x % P;
}
for &raw in &v {
if raw == 0 {
continue;
}
let a = Goldilocks::new(raw);
assert_eq!(
(a * a.inv()).as_canonical_u64(),
1,
"inv broken at {raw}"
);
}
assert_eq!(Goldilocks::ZERO.inv().as_canonical_u64(), 0);
}
use super::*;
#[test]
fn field_add_basic() {
let a = Goldilocks::new(1);
let b = Goldilocks::new(2);
assert_eq!((a + b).as_canonical_u64(), 3);
}
#[test]
fn field_add_wrap() {
let a = Goldilocks::new(P - 1);
let b = Goldilocks::new(1);
assert_eq!((a + b).as_canonical_u64(), 0);
}
#[test]
fn field_sub_basic() {
let a = Goldilocks::new(5);
let b = Goldilocks::new(3);
assert_eq!((a - b).as_canonical_u64(), 2);
}
#[test]
fn field_sub_wrap() {
let a = Goldilocks::new(0);
let b = Goldilocks::new(1);
assert_eq!((a - b).as_canonical_u64(), P - 1);
}
#[test]
fn field_mul_basic() {
let a = Goldilocks::new(3);
let b = Goldilocks::new(7);
assert_eq!((a * b).as_canonical_u64(), 21);
}
#[test]
fn field_mul_large() {
let a = Goldilocks::new(P - 1);
assert_eq!((a * a).as_canonical_u64(), 1);
}
#[test]
fn pow7_basic() {
let x = Goldilocks::new(2);
assert_eq!(x.pow7().as_canonical_u64(), 128);
}
#[test]
fn pow7_zero() {
assert_eq!(Goldilocks::ZERO.pow7().as_canonical_u64(), 0);
}
#[test]
fn pow7_one() {
assert_eq!(Goldilocks::new(1).pow7().as_canonical_u64(), 1);
}
#[test]
fn inv_basic() {
let x = Goldilocks::new(2);
let x_inv = x.inv();
assert_eq!((x * x_inv).as_canonical_u64(), 1);
}
#[test]
fn inv_one() {
assert_eq!(Goldilocks::new(1).inv().as_canonical_u64(), 1);
}
#[test]
fn inv_zero() {
assert_eq!(Goldilocks::ZERO.inv().as_canonical_u64(), 0);
}
#[test]
fn inv_roundtrip() {
let x = Goldilocks::new(42);
let x_inv = x.inv();
assert_eq!((x * x_inv).as_canonical_u64(), 1);
assert_eq!(x_inv.inv().as_canonical_u64(), x.as_canonical_u64());
}
#[test]
fn inv_p_minus_one() {
let a = Goldilocks::new(P - 1);
assert_eq!(a.inv().as_canonical_u64(), P - 1);
}
#[test]
fn canonical_reduces() {
let a = Goldilocks::new(P);
assert_eq!(a.as_canonical_u64(), 0);
let b = Goldilocks::new(P + 1);
assert_eq!(b.as_canonical_u64(), 1);
}
}