pub const P: u64 = 0xFFFF_FFFF_0000_0001;
const EPSILON: u64 = 0xFFFF_FFFF;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Fp(u64);
const fn mask(bit: bool) -> u64 {
0u64.wrapping_sub(bit as u64)
}
const fn canon(x: u64) -> u64 {
let (y, borrow) = x.overflowing_sub(P);
let keep = mask(borrow);
(x & keep) | (y & !keep)
}
const fn reduce128(x: u128) -> u64 {
let low = x as u64;
let high = (x >> 64) as u64;
let high_high = high >> 32;
let high_low = high & EPSILON;
let (t0, borrow) = low.overflowing_sub(high_high);
let t0 = t0.wrapping_sub(EPSILON & mask(borrow));
let t1 = high_low * EPSILON;
let (t2, carry) = t0.overflowing_add(t1);
canon(t2.wrapping_add(EPSILON & mask(carry)))
}
impl Fp {
pub const ZERO: Fp = Fp(0);
pub const ONE: Fp = Fp(1);
#[must_use]
pub const fn new(value: u64) -> Fp {
Fp(canon(value))
}
#[must_use]
pub const fn canonical(value: u64) -> Option<Fp> {
match value < P {
true => Some(Fp(value)),
false => None,
}
}
#[must_use]
pub const fn value(self) -> u64 {
self.0
}
#[must_use]
pub const fn add(self, rhs: Fp) -> Fp {
let (sum, over) = self.0.overflowing_add(rhs.0);
Fp(canon(sum.wrapping_add(EPSILON & mask(over))))
}
#[must_use]
pub const fn sub(self, rhs: Fp) -> Fp {
let (difference, borrow) = self.0.overflowing_sub(rhs.0);
Fp(difference.wrapping_sub(EPSILON & mask(borrow)))
}
#[must_use]
pub const fn mul(self, rhs: Fp) -> Fp {
Fp(reduce128(self.0 as u128 * rhs.0 as u128))
}
#[must_use]
pub const fn square(self) -> Fp {
self.mul(self)
}
#[must_use]
pub const fn mul_add(self, x: Fp, y: Fp) -> Fp {
Fp(reduce128(self.0 as u128 + x.0 as u128 * y.0 as u128))
}
#[must_use]
pub const fn neg(self) -> Fp {
Fp::ZERO.sub(self)
}
#[must_use]
pub fn pow(self, mut exponent: u64) -> Fp {
let (mut base, mut power) = (self, Fp::ONE);
while exponent > 0 {
if exponent & 1 == 1 {
power = power.mul(base);
}
base = base.square();
exponent >>= 1;
}
power
}
#[must_use]
pub(crate) fn exp_power_of_2(self, n: u32) -> Fp {
(0..n).fold(self, |x, _| x.square())
}
#[must_use]
pub(crate) fn sqrt(self) -> Option<Fp> {
const TWO_ADICITY: u32 = 32;
const POWER_OF_TWO_GENERATOR: Fp = Fp::new(7_277_203_076_849_721_926);
if self.value() == 0 {
return Some(Fp::ZERO);
}
if self.pow((P - 1) >> 1).value() != 1 {
return None;
}
let t = (P - 1) >> TWO_ADICITY;
let mut z = POWER_OF_TWO_GENERATOR;
let mut w = self.pow((t - 1) / 2);
let mut x = self.mul(w);
let mut b = x.mul(w);
let mut v = TWO_ADICITY;
while b.value() != 1 {
let mut k = 0;
let mut b2k = b;
while b2k.value() != 1 {
b2k = b2k.square();
k += 1;
}
w = z.exp_power_of_2(v.checked_sub(k + 1)?);
z = w.square();
b = b.mul(z);
x = x.mul(w);
v = k;
}
Some(x)
}
#[must_use]
pub fn inverse_or_zero(self) -> Fp {
self.pow(P - 2)
}
#[must_use]
pub const fn from_u128(value: u128) -> Fp {
Fp(reduce128(value))
}
#[must_use]
pub fn from_le_chunks(bytes: &[u8]) -> Option<Vec<Fp>> {
bytes
.chunks(8)
.map(|chunk| {
let mut word = [0u8; 8];
word[..chunk.len()].copy_from_slice(chunk);
Fp::canonical(u64::from_le_bytes(word))
})
.collect()
}
}
#[cfg(test)]
#[path = "tests/goldilocks.rs"]
mod tests;