use nebu::field::Goldilocks;
const N: usize = 16;
const B: u32 = 16;
const MASK: u64 = 0xFFFF;
const Q: [u64; N] = [
0xFC2F, 0xFFFF, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
];
const Q_MINUS_2: [u64; N] = [
0xFC2D, 0xFFFF, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
];
const Q_PLUS_1_DIV4: [u64; N] = [
0xFF0C, 0xBFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0x3FFF,
];
const R: [u64; 3] = [0x03D1, 0x0000, 0x0001];
#[derive(Clone, Copy, Debug)]
pub struct Fe {
limbs: [u64; N],
}
impl Fe {
pub const ZERO: Fe = Fe { limbs: [0; N] };
pub const ONE: Fe = Fe {
limbs: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
};
pub fn from_bytes(be: &[u8; 32]) -> Fe {
let mut limbs = [0u64; N];
for i in 0..N {
let hi = be[30 - 2 * i] as u64;
let lo = be[31 - 2 * i] as u64;
limbs[i] = (hi << 8) | lo;
}
reduce_wide(&limbs)
}
pub fn to_bytes(&self) -> [u8; 32] {
let mut out = [0u8; 32];
for i in 0..N {
out[30 - 2 * i] = (self.limbs[i] >> 8) as u8;
out[31 - 2 * i] = (self.limbs[i] & 0xFF) as u8;
}
out
}
pub fn is_zero(&self) -> bool {
self.limbs.iter().all(|&l| l == 0)
}
pub fn add(&self, other: &Fe) -> Fe {
let mut wide = [0u64; N + 1];
for i in 0..N {
wide[i] = self.limbs[i] + other.limbs[i];
}
normalize(&mut wide);
reduce_wide(&wide)
}
pub fn sub(&self, other: &Fe) -> Fe {
let neg = sub_from_q(&other.limbs); let mut wide = [0u64; N + 1];
for i in 0..N {
wide[i] = self.limbs[i] + neg[i];
}
normalize(&mut wide);
reduce_wide(&wide)
}
pub fn mul(&self, other: &Fe) -> Fe {
let product = schoolbook(&self.limbs, &other.limbs); reduce_wide(&product)
}
pub fn sqr(&self) -> Fe {
self.mul(self)
}
pub fn inv(&self) -> Fe {
self.pow(&Q_MINUS_2)
}
pub fn sqrt(&self) -> Option<Fe> {
let cand = self.pow(&Q_PLUS_1_DIV4);
if cand.sqr() == *self { Some(cand) } else { None }
}
pub fn parity(&self) -> u8 {
(self.limbs[0] & 1) as u8
}
pub fn neg(&self) -> Fe {
Fe::ZERO.sub(self)
}
pub fn pow(&self, exp: &[u64; N]) -> Fe {
let mut result = Fe::ONE;
for i in (0..N).rev() {
for bit in (0..B).rev() {
result = result.sqr();
if (exp[i] >> bit) & 1 == 1 {
result = result.mul(self);
}
}
}
result
}
}
impl PartialEq for Fe {
fn eq(&self, other: &Fe) -> bool {
self.limbs == other.limbs
}
}
impl Eq for Fe {}
fn normalize(limbs: &mut [u64]) {
let mut carry = 0u64;
for limb in limbs.iter_mut() {
let v = *limb + carry;
*limb = v & MASK;
carry = v >> B;
}
debug_assert_eq!(carry, 0, "normalize overflowed its limb vector");
}
pub(crate) fn schoolbook(a: &[u64; N], b: &[u64; N]) -> [u64; 2 * N] {
let mut cols = [0u64; 2 * N];
for i in 0..N {
let ai = Goldilocks::new(a[i]);
for j in 0..N {
let prod = ai * Goldilocks::new(b[j]); let acc = Goldilocks::new(cols[i + j]) + prod; cols[i + j] = acc.as_u64();
}
}
normalize(&mut cols);
cols
}
fn sub_from_q(x: &[u64; N]) -> [u64; N] {
let mut out = [0u64; N];
let mut borrow = 0i64;
for i in 0..N {
let mut v = Q[i] as i64 - x[i] as i64 - borrow;
if v < 0 {
v += 1 << B;
borrow = 1;
} else {
borrow = 0;
}
out[i] = v as u64;
}
out
}
fn reduce_wide(input: &[u64]) -> Fe {
let mut acc: Vec<u64> = input.to_vec();
normalize_vec(&mut acc);
while acc.len() > N && acc[N..].iter().any(|&l| l != 0) {
let hi = acc[N..].to_vec();
let mut low: Vec<u64> = acc[..N].to_vec();
let folded = mul_by_r(&hi); add_into(&mut low, &folded);
normalize_vec(&mut low);
acc = low;
}
acc.resize(N, 0);
let mut limbs = [0u64; N];
limbs.copy_from_slice(&acc[..N]);
while geq_q(&limbs) {
limbs = sub_q(&limbs);
}
Fe { limbs }
}
fn mul_by_r(hi: &[u64]) -> Vec<u64> {
let mut scaled = vec![0u64; hi.len() + 1];
for (i, &h) in hi.iter().enumerate() {
let acc = Goldilocks::new(scaled[i]) + Goldilocks::new(h) * Goldilocks::new(R[0]);
scaled[i] = acc.as_u64();
}
normalize_vec(&mut scaled);
let mut shifted = vec![0u64; hi.len() + 2];
for (i, &h) in hi.iter().enumerate() {
shifted[i + 2] = h;
}
add_into(&mut scaled, &shifted);
normalize_vec(&mut scaled);
scaled
}
pub(crate) fn add_into(dst: &mut Vec<u64>, src: &[u64]) {
if dst.len() < src.len() {
dst.resize(src.len(), 0);
}
for (i, &s) in src.iter().enumerate() {
dst[i] += s;
}
}
pub(crate) fn normalize_vec(limbs: &mut Vec<u64>) {
let mut carry = 0u64;
for limb in limbs.iter_mut() {
let v = *limb + carry;
*limb = v & MASK;
carry = v >> B;
}
while carry != 0 {
limbs.push(carry & MASK);
carry >>= B;
}
}
fn geq_q(x: &[u64; N]) -> bool {
for i in (0..N).rev() {
if x[i] != Q[i] {
return x[i] > Q[i];
}
}
true }
fn sub_q(x: &[u64; N]) -> [u64; N] {
let mut out = [0u64; N];
let mut borrow = 0i64;
for i in 0..N {
let mut v = x[i] as i64 - Q[i] as i64 - borrow;
if v < 0 {
v += 1 << B;
borrow = 1;
} else {
borrow = 0;
}
out[i] = v as u64;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigUint;
fn q_big() -> BigUint {
(BigUint::from(1u8) << 256u32) - (BigUint::from(1u8) << 32u32) - BigUint::from(977u32)
}
fn to_big(fe: &Fe) -> BigUint {
BigUint::from_bytes_be(&fe.to_bytes())
}
fn from_big(x: &BigUint) -> Fe {
let bytes = x.to_bytes_be();
let mut be = [0u8; 32];
be[32 - bytes.len()..].copy_from_slice(&bytes);
Fe::from_bytes(&be)
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn bytes32(&mut self) -> [u8; 32] {
let mut b = [0u8; 32];
for chunk in b.chunks_mut(8) {
chunk.copy_from_slice(&self.next().to_le_bytes());
}
b
}
}
#[test]
fn constants_reduce_correctly() {
assert_eq!(to_big(&Fe::ZERO), BigUint::from(0u8));
assert_eq!(to_big(&Fe::ONE), BigUint::from(1u8));
let mut q_be = [0u8; 32];
let qb = q_big().to_bytes_be();
q_be[32 - qb.len()..].copy_from_slice(&qb);
assert!(Fe::from_bytes(&q_be).is_zero(), "q ≡ 0");
}
#[test]
fn bytes_round_trip_below_q() {
let v = &q_big() - BigUint::from(12345u32);
assert_eq!(to_big(&from_big(&v)), v);
}
#[test]
fn add_sub_mul_match_bigint() {
let q = q_big();
let mut rng = Rng(0x9E3779B97F4A7C15);
for _ in 0..4000 {
let a = from_big(&(BigUint::from_bytes_be(&rng.bytes32()) % &q));
let b = from_big(&(BigUint::from_bytes_be(&rng.bytes32()) % &q));
let ba = to_big(&a);
let bb = to_big(&b);
assert_eq!(to_big(&a.add(&b)), (&ba + &bb) % &q, "add");
assert_eq!(to_big(&a.sub(&b)), (&ba + &q - &bb) % &q, "sub");
assert_eq!(to_big(&a.mul(&b)), (&ba * &bb) % &q, "mul");
}
}
#[test]
fn edge_values() {
let q = q_big();
let one = Fe::ONE;
let qm1 = from_big(&(&q - BigUint::from(1u8)));
assert!(qm1.add(&one).is_zero());
assert_eq!(to_big(&qm1.mul(&qm1)), BigUint::from(1u8));
assert_eq!(Fe::ZERO.sub(&one), qm1);
}
#[test]
fn inverse_is_a_true_inverse() {
let mut rng = Rng(0xD1B54A32D192ED03);
for _ in 0..200 {
let q = q_big();
let a = from_big(&(BigUint::from_bytes_be(&rng.bytes32()) % &q));
if a.is_zero() {
continue;
}
assert_eq!(a.mul(&a.inv()), Fe::ONE, "a·a⁻¹ = 1");
}
}
#[test]
fn schoolbook_columns_stay_inside_goldilocks() {
let max_col = 16u128 * (0xFFFFu128 * 0xFFFFu128);
assert!(max_col < nebu::field::P as u128, "columns never wrap the native field");
}
}