use crate::internal::subtle::{Choice, ConstantTimeEq};
pub(crate) const P: [u64; 4] = [
0xFFFF_FFFE_FFFF_FC2F,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
];
const R: [u64; 4] = [0x0000_0001_0000_03d1, 0x0, 0x0, 0x0];
const R2: [u64; 4] = [0x0000_07a2_000e_90a1, 0x0000_0000_0000_0001, 0x0, 0x0];
const INV: u64 = 0xd838_091d_d225_3531;
#[derive(Clone, Copy, Debug)]
pub struct FieldElement {
limbs: [u64; 4],
}
impl FieldElement {
pub const TWO: Self = Self {
limbs: [0x0000_0002_0000_07a2, 0, 0, 0],
};
pub const THREE: Self = Self {
limbs: [0x0000_0003_0000_0b73, 0, 0, 0],
};
pub const FOUR: Self = Self {
limbs: [0x0000_0004_0000_0f44, 0, 0, 0],
};
pub const EIGHT: Self = Self {
limbs: [0x0000_0008_0000_1e88, 0, 0, 0],
};
pub const ZERO: Self = Self {
limbs: [0, 0, 0, 0],
};
pub const ONE: Self = Self { limbs: R };
pub const fn from_limbs(limbs: [u64; 4]) -> Self {
Self { limbs }
}
pub fn from_bytes(bytes: &[u8; 32]) -> Self {
let mut limbs = [0u64; 4];
for i in 0..4 {
let offset = i * 8;
limbs[3 - i] = u64::from_be_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
]);
}
Self::from_limbs(limbs).to_montgomery()
}
pub fn to_bytes(&self) -> [u8; 32] {
let normalized = self.normalize();
let mut bytes = [0u8; 32];
for i in 0..4 {
let offset = i * 8;
let limb_bytes = normalized.limbs[3 - i].to_be_bytes();
bytes[offset..offset + 8].copy_from_slice(&limb_bytes);
}
bytes
}
fn to_montgomery(&self) -> Self {
self.mul(&Self::from_limbs(R2))
}
fn normalize(&self) -> Self {
self.mul(&Self::from_limbs([1, 0, 0, 0]))
}
pub fn is_zero(&self) -> Choice {
self.ct_eq(&Self::ZERO)
}
pub fn add(&self, rhs: &Self) -> Self {
let mut result = [0u64; 4];
let mut carry = 0u64;
for i in 0..4 {
let (sum1, c1) = self.limbs[i].overflowing_add(rhs.limbs[i]);
let (sum2, c2) = sum1.overflowing_add(carry);
result[i] = sum2;
carry = u64::from(c1) + u64::from(c2);
}
let res = Self::from_limbs(result);
res.reduce_if_carry_or_gte_p(carry)
}
pub fn sub(&self, rhs: &Self) -> Self {
let mut result = [0u64; 4];
let mut borrow = 0u64;
for i in 0..4 {
let (diff1, b1) = self.limbs[i].overflowing_sub(rhs.limbs[i]);
let (diff2, b2) = diff1.overflowing_sub(borrow);
result[i] = diff2;
borrow = u64::from(b1) + u64::from(b2);
}
let res = Self::from_limbs(result);
res.conditional_add_p(borrow)
}
pub fn mul(&self, rhs: &Self) -> Self {
let mut t = [0u64; 9];
for i in 0..4 {
let mut carry = 0u64;
for j in 0..4 {
let prod = u128::from(self.limbs[i]) * u128::from(rhs.limbs[j]);
let sum = prod + u128::from(t[i + j]) + u128::from(carry);
t[i + j] = sum as u64;
carry = (sum >> 64) as u64;
}
t[i + 4] = t[i + 4].wrapping_add(carry);
}
for i in 0..4 {
let u = t[i].wrapping_mul(INV);
let mut carry: u128 = 0;
for j in 0..4 {
let prod = u128::from(u) * u128::from(P[j]) + u128::from(t[i + j]) + carry;
t[i + j] = prod as u64;
carry = prod >> 64;
}
let mut k = i + 4;
while carry != 0 {
let sum = u128::from(t[k]) + carry;
t[k] = sum as u64;
carry = sum >> 64;
k += 1;
}
}
let mut result = Self::from_limbs([t[4], t[5], t[6], t[7]]);
if t[8] != 0 {
result = result.add(&Self::from_limbs(R));
}
if bool::from(result.is_gte_p()) {
result = result.sub(&Self::from_limbs(P));
}
result
}
#[allow(dead_code)]
fn reduce(&self) -> Self {
self.reduce_if_carry_or_gte_p(0)
}
fn reduce_if_carry_or_gte_p(&self, carry: u64) -> Self {
let mut result = self.limbs;
let gte_p = self.is_gte_p() | Choice::from_bool(carry != 0);
let p_limbs = P;
let mut borrow = 0u64;
let mut sub_result = [0u64; 4];
for i in 0..4 {
let (p_plus_borrow, carry) = p_limbs[i].overflowing_add(borrow);
let (diff, b) = result[i].overflowing_sub(p_plus_borrow);
sub_result[i] = diff;
borrow = u64::from(b | carry);
}
for i in 0..4 {
result[i] = if gte_p.is_true() {
sub_result[i]
} else {
result[i]
};
}
Self::from_limbs(result)
}
fn conditional_add_p(&self, borrow: u64) -> Self {
if borrow == 0 {
*self
} else {
let mut result = [0u64; 4];
let mut carry = 0u64;
for i in 0..4 {
let (sum, c1) = self.limbs[i].overflowing_add(P[i]);
let (sum2, c2) = sum.overflowing_add(carry);
result[i] = sum2;
carry = u64::from(c1) + u64::from(c2);
}
Self::from_limbs(result)
}
}
fn is_gte_p(&self) -> Choice {
for i in (0..4).rev() {
if self.limbs[i] > P[i] {
return Choice::from_bool(true);
} else if self.limbs[i] < P[i] {
return Choice::from_bool(false);
}
}
Choice::from_bool(true) }
pub fn invert(&self) -> Self {
let exponent: [u64; 4] = [
0xFFFF_FFFE_FFFF_FC2D,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
];
self.pow(&exponent)
}
fn pow(&self, exp: &[u64; 4]) -> Self {
let mut result = Self::ONE;
let mut base = *self;
for word in exp.iter() {
for i in 0..64 {
if (word >> i) & 1 == 1 {
result = result.mul(&base);
}
base = base.mul(&base);
}
}
result
}
pub fn neg(&self) -> Self {
if self.is_zero().is_true() {
*self
} else {
Self::from_limbs(P).sub(self)
}
}
pub fn sqrt(&self) -> Self {
let sqrt_exp: [u64; 4] = [
0xFFFF_FFFF_BFFF_FF0C,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
0x3FFF_FFFF_FFFF_FFFF,
];
self.pow(&sqrt_exp)
}
}
impl ConstantTimeEq for FieldElement {
fn ct_eq(&self, other: &Self) -> Choice {
let mut result = 1u8;
for i in 0..4 {
result &= u8::from(self.limbs[i] == other.limbs[i]);
}
Choice(result)
}
}
impl Default for FieldElement {
fn default() -> Self {
Self::ZERO
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::RngCore;
#[test]
fn mul_small_values() {
let a = FieldElement::from_bytes(&{
let mut b = [0u8; 32];
b[31] = 6;
b
});
let b = FieldElement::from_bytes(&{
let mut b = [0u8; 32];
b[31] = 7;
b
});
let c = a.mul(&b).to_bytes();
assert_eq!(c[31], 42);
assert!(c[..31].iter().all(|&x| x == 0));
}
#[test]
fn mul_identity_roundtrip() {
let mut rng = rand::thread_rng();
for _ in 0..64 {
let mut ab = [0u8; 32];
rng.fill_bytes(&mut ab);
let a = FieldElement::from_bytes(&ab);
assert_eq!(a.mul(&FieldElement::ONE).to_bytes(), ab);
}
}
#[test]
fn mul_is_associative_and_distributive() {
let mut rng = rand::thread_rng();
for _ in 0..64 {
let mut ab = [0u8; 32];
let mut bb = [0u8; 32];
let mut cb = [0u8; 32];
rng.fill_bytes(&mut ab);
rng.fill_bytes(&mut bb);
rng.fill_bytes(&mut cb);
let a = FieldElement::from_bytes(&ab);
let b = FieldElement::from_bytes(&bb);
let c = FieldElement::from_bytes(&cb);
assert!(bool::from(a.mul(&b).mul(&c).ct_eq(&a.mul(&b.mul(&c)))));
assert!(bool::from(
a.add(&b).mul(&c).ct_eq(&a.mul(&c).add(&b.mul(&c)))
));
}
}
#[test]
fn invert_roundtrip() {
let mut rng = rand::thread_rng();
for _ in 0..64 {
let mut ab = [0u8; 32];
rng.fill_bytes(&mut ab);
let a = FieldElement::from_bytes(&ab);
if bool::from(a.is_zero()) {
continue;
}
let inv = a.invert();
assert!(bool::from(a.mul(&inv).ct_eq(&FieldElement::ONE)));
}
}
}