use crate::internal::subtle::{Choice, ConstantTimeEq, CtOption};
const N: [u64; 4] = [
0xBFD2_5E8C_D036_4141,
0xBAAE_DCE6_AF48_A03B,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFE,
];
const R: [u64; 4] = [
0xdf1f_2c66_1a85_ebe1,
0x5d7f_7e9e_41d3_06ee,
0x6b7d_eef0_443e_7d84,
0x59e2_6b14_1a7c_5b65,
];
const R2: [u64; 4] = [
0x6e2e_5476_4329_1250,
0x1ad4_c521_d76b_831d,
0x2845_4783_74cb_a971,
0x48cf_1a4e_144c_4447,
];
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Scalar {
limbs: [u64; 4],
}
impl Scalar {
pub const ZERO: Self = Self {
limbs: [0, 0, 0, 0],
};
pub const ONE: Self = Self {
limbs: [
0xdf1f_2c66_1a85_ebe1,
0x5d7f_7e9e_41d3_06ee,
0x6b7d_eef0_443e_7d84,
0x59e2_6b14_1a7c_5b65,
],
};
pub const fn from_limbs(limbs: [u64; 4]) -> Self {
Self { limbs }
}
pub fn from_repr(bytes: &[u8; 32]) -> CtOption<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],
]);
}
let is_valid = is_lt_n(&limbs);
let val = Self::from_limbs(limbs).to_montgomery();
CtOption::new(val, is_valid)
}
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(R))
}
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_n(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_n(borrow)
}
pub fn mul(&self, rhs: &Self) -> Self {
let mut t = [0u128; 8];
for i in 0..4 {
let mut carry = 0u128;
for j in 0..4 {
let product = u128::from(self.limbs[i]) * u128::from(rhs.limbs[j]);
let (sum, c) = t[i + j].overflowing_add(product);
let (sum2, c2) = sum.overflowing_add(carry);
t[i + j] = sum2;
carry = u128::from(c) + u128::from(c2) + (product >> 64);
}
t[i + 4] = t[i + 4].wrapping_add(carry);
}
Self::montgomery_reduce_scalar(&t)
}
fn montgomery_reduce_scalar(t: &[u128; 8]) -> Self {
let mut r = [0u64; 4];
for i in 0..4 {
r[i] = t[i] as u64;
}
Self::from_limbs(r).reduce()
}
fn reduce(&self) -> Self {
self.reduce_if_carry_or_gte_n(0)
}
fn reduce_if_carry_or_gte_n(&self, carry: u64) -> Self {
let mut result = self.limbs;
let gte_n = self.is_gte_n() | Choice::from_bool(carry != 0);
let mut borrow = 0u64;
let mut sub_result = [0u64; 4];
for i in 0..4 {
let (n_plus_borrow, n_carry) = N[i].overflowing_add(borrow);
let (diff, b) = result[i].overflowing_sub(n_plus_borrow);
sub_result[i] = diff;
borrow = u64::from(b || n_carry);
}
for i in 0..4 {
result[i] = if gte_n.is_true() {
sub_result[i]
} else {
result[i]
};
}
Self::from_limbs(result)
}
fn conditional_add_n(&self, borrow: u64) -> Self {
if borrow == 0 {
*self
} else {
let mut result = [0u64; 4];
let mut carry = 0u64;
for i in 0..4 {
let (sum1, c1) = self.limbs[i].overflowing_add(N[i]);
let (sum2, c2) = sum1.overflowing_add(carry);
result[i] = sum2;
carry = u64::from(c1) + u64::from(c2);
}
Self::from_limbs(result)
}
}
fn is_gte_n(&self) -> Choice {
for i in (0..4).rev() {
if self.limbs[i] > N[i] {
return Choice::from_bool(true);
} else if self.limbs[i] < N[i] {
return Choice::from_bool(false);
}
}
Choice::from_bool(true)
}
pub fn invert(&self) -> CtOption<Self> {
let is_zero = self.ct_eq(&Self::ZERO);
let exponent: [u64; 4] = [
0xBFD2_5E8C_D036_413F,
0xBAAE_DCE6_AF48_A03B,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFE,
];
let result = self.pow(&exponent);
CtOption::new(result, !is_zero)
}
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).add(&base);
}
base = (&base).mul(&base);
}
}
result
}
pub fn neg(&self) -> Self {
if self.is_zero().is_true() {
*self
} else {
Self::from_limbs(N).sub(self)
}
}
pub fn is_zero(&self) -> Choice {
self.ct_eq(&Self::ZERO)
}
}
impl ConstantTimeEq for Scalar {
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)
}
}
fn is_lt_n(limbs: &[u64; 4]) -> Choice {
for i in (0..4).rev() {
if limbs[i] > N[i] {
return Choice::from_bool(false);
} else if limbs[i] < N[i] {
return Choice::from_bool(true);
}
}
Choice::from_bool(false) }