use crate::ct::{Choice, ConstantTimeEq, ConstantTimeLess};
use crate::ec::curve25519::field::{Fe, Field};
use crate::ec::curve25519::point::Point;
use crate::ec::curve25519::scalar::{
scalar_add, scalar_invert, scalar_mul, scalar_negate, scalar_reduce_wide, scalar_sub,
};
#[derive(Clone)]
pub struct Scalar(pub(crate) Fe);
impl Drop for Scalar {
fn drop(&mut self) {
self.0 = Fe::ZERO;
let _ = core::hint::black_box(&self.0);
}
}
impl Scalar {
pub const ZERO: Scalar = Scalar(Fe::ZERO);
pub const ONE: Scalar = Scalar(Fe::ONE);
pub fn from_bytes_canonical(bytes: &[u8; 32]) -> Option<Scalar> {
let f = Field::new();
let v = Fe::from_le_bytes(bytes);
if bool::from(v.ct_lt(&f.l)) {
Some(Scalar(v))
} else {
None
}
}
pub fn from_bytes_mod_order(bytes: &[u8; 64]) -> Scalar {
let f = Field::new();
Scalar(scalar_reduce_wide(bytes, &f.l8))
}
pub fn to_bytes(&self) -> [u8; 32] {
let mut out = [0u8; 32];
self.0.write_le_bytes(&mut out);
out
}
pub fn add(&self, rhs: &Scalar) -> Scalar {
let f = Field::new();
Scalar(scalar_add(&self.0, &rhs.0, &f.l))
}
pub fn sub(&self, rhs: &Scalar) -> Scalar {
let f = Field::new();
Scalar(scalar_sub(&self.0, &rhs.0, &f.l))
}
pub fn mul(&self, rhs: &Scalar) -> Scalar {
let f = Field::new();
Scalar(scalar_mul(&self.0, &rhs.0, &f.l8))
}
pub fn negate(&self) -> Scalar {
let f = Field::new();
Scalar(scalar_negate(&self.0, &f.l))
}
pub fn invert(&self) -> Scalar {
let f = Field::new();
Scalar(scalar_invert(&self.0, &f.l, &f.l8))
}
pub fn ct_eq(&self, other: &Scalar) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl PartialEq for Scalar {
fn eq(&self, other: &Scalar) -> bool {
bool::from(self.ct_eq(other))
}
}
impl Eq for Scalar {}
impl core::fmt::Debug for Scalar {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Scalar(…)")
}
}
#[derive(Clone, Copy, Debug)]
pub struct EdwardsPoint(pub(crate) Point);
impl EdwardsPoint {
pub fn generator() -> EdwardsPoint {
EdwardsPoint(Field::new().base())
}
pub fn identity() -> EdwardsPoint {
EdwardsPoint(Field::new().identity())
}
pub fn add(&self, rhs: &EdwardsPoint) -> EdwardsPoint {
EdwardsPoint(Field::new().point_add(&self.0, &rhs.0))
}
pub fn sub(&self, rhs: &EdwardsPoint) -> EdwardsPoint {
let f = Field::new();
EdwardsPoint(f.point_add(&self.0, &f.point_negate(&rhs.0)))
}
pub fn double(&self) -> EdwardsPoint {
EdwardsPoint(Field::new().point_double(&self.0))
}
pub fn negate(&self) -> EdwardsPoint {
EdwardsPoint(Field::new().point_negate(&self.0))
}
pub fn mul(&self, scalar: &Scalar) -> EdwardsPoint {
let f = Field::new();
let bytes = scalar_bytes(&scalar.0);
EdwardsPoint(f.scalar_mult(&bytes, &self.0))
}
pub fn mul_base(scalar: &Scalar) -> EdwardsPoint {
let f = Field::new();
let bytes = scalar_bytes(&scalar.0);
EdwardsPoint(f.scalar_mult(&bytes, &f.base()))
}
pub fn mul_by_cofactor(&self) -> EdwardsPoint {
let f = Field::new();
EdwardsPoint(f.point_double(&f.point_double(&f.point_double(&self.0))))
}
pub fn ct_eq(&self, other: &EdwardsPoint) -> Choice {
Field::new().point_ct_eq(&self.0, &other.0)
}
pub fn compress(&self) -> [u8; 32] {
Field::new().encode(&self.0)
}
pub fn decompress(bytes: &[u8; 32]) -> Option<EdwardsPoint> {
Field::new().decode(bytes).map(EdwardsPoint)
}
pub fn to_affine(&self) -> ([u8; 32], [u8; 32]) {
Field::new().to_affine_bytes(&self.0)
}
pub fn x_bytes(&self) -> [u8; 32] {
self.to_affine().0
}
pub fn y_bytes(&self) -> [u8; 32] {
self.to_affine().1
}
pub fn is_small_order(&self) -> Choice {
let f = Field::new();
let eightp = self.mul_by_cofactor();
f.point_ct_eq(&eightp.0, &f.identity())
}
pub fn is_torsion_free(&self) -> Choice {
let f = Field::new();
let lp = f.scalar_mult(&scalar_bytes(&f.l), &self.0);
f.point_ct_eq(&lp, &f.identity())
}
}
impl PartialEq for EdwardsPoint {
fn eq(&self, other: &EdwardsPoint) -> bool {
bool::from(self.ct_eq(other))
}
}
impl Eq for EdwardsPoint {}
fn scalar_bytes(v: &Fe) -> [u8; 32] {
let mut b = [0u8; 32];
v.write_le_bytes(&mut b);
b
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ec::curve25519::field::Field;
fn backend_mul_base(s: &Scalar) -> [u8; 32] {
let f = Field::new();
let mut bytes = [0u8; 32];
s.0.write_le_bytes(&mut bytes);
f.encode(&f.scalar_mult(&bytes, &f.base()))
}
#[test]
fn mul_base_matches_backend() {
for seed in [1u64, 2, 7, 0xdead_beef, 0x1234_5678_9abc_def0] {
let s = Scalar::from_bytes_mod_order(&{
let mut b = [0u8; 64];
b[..8].copy_from_slice(&seed.to_le_bytes());
b
});
let viapoint = EdwardsPoint::mul_base(&s).compress();
assert_eq!(
viapoint,
backend_mul_base(&s),
"mul_base != [s]B (seed {seed})"
);
}
}
#[test]
fn generator_is_basepoint_and_mul_base_one() {
let g = EdwardsPoint::generator();
let one = Scalar::ONE;
assert_eq!(EdwardsPoint::mul_base(&one), g);
}
#[test]
fn add_double_consistency() {
let g = EdwardsPoint::generator();
let g2 = g.add(&g);
assert_eq!(g2, g.double());
let two = Scalar::ONE.add(&Scalar::ONE);
assert_eq!(EdwardsPoint::mul_base(&two), g2);
let g3 = g2.add(&g);
let three = two.add(&Scalar::ONE);
assert_eq!(EdwardsPoint::mul_base(&three), g3);
}
#[test]
fn known_multiples_via_mul() {
let g = EdwardsPoint::generator();
for k in 1u64..16 {
let s = Scalar::from_bytes_mod_order(&{
let mut b = [0u8; 64];
b[..8].copy_from_slice(&k.to_le_bytes());
b
});
assert_eq!(g.mul(&s), EdwardsPoint::mul_base(&s), "[{k}]B mismatch");
}
}
#[test]
fn compress_decompress_roundtrip() {
for k in 1u64..10 {
let s = Scalar::from_bytes_mod_order(&{
let mut b = [0u8; 64];
b[..8].copy_from_slice(&k.to_le_bytes());
b
});
let p = EdwardsPoint::mul_base(&s);
let enc = p.compress();
let dec = EdwardsPoint::decompress(&enc).expect("roundtrip decode");
assert_eq!(dec, p);
assert_eq!(dec.compress(), enc);
}
}
#[test]
fn affine_coords_match_compression() {
for k in 1u64..12 {
let s = Scalar::from_bytes_mod_order(&{
let mut b = [0u8; 64];
b[..8].copy_from_slice(&k.to_le_bytes());
b
});
let p = EdwardsPoint::mul_base(&s);
let (x, y) = p.to_affine();
assert_eq!(p.x_bytes(), x);
assert_eq!(p.y_bytes(), y);
assert_eq!(y[31] & 0x80, 0, "[{k}]B: y must be < 2^255");
let mut rebuilt = y;
rebuilt[31] |= (x[0] & 1) << 7;
assert_eq!(rebuilt, p.compress(), "[{k}]B affine vs compress");
assert_eq!(EdwardsPoint::decompress(&rebuilt).unwrap(), p);
}
}
#[test]
fn affine_identity() {
let (x, y) = EdwardsPoint::identity().to_affine();
assert_eq!(x, [0u8; 32]);
let mut one = [0u8; 32];
one[0] = 1;
assert_eq!(y, one);
}
#[test]
fn negate_and_sub() {
let g = EdwardsPoint::generator();
let two = Scalar::ONE.add(&Scalar::ONE);
let g2 = EdwardsPoint::mul_base(&two);
assert_eq!(g2.sub(&g), g);
assert_eq!(g.add(&g.negate()), EdwardsPoint::identity());
}
#[test]
fn scalar_arithmetic_identities() {
let a = Scalar::from_bytes_mod_order(&{
let mut b = [0u8; 64];
b[..8].copy_from_slice(&0x0123_4567u64.to_le_bytes());
b
});
let b = Scalar::from_bytes_mod_order(&{
let mut bb = [0u8; 64];
bb[..8].copy_from_slice(&0x89ab_cdefu64.to_le_bytes());
bb
});
assert_eq!(a.add(&Scalar::ZERO), a);
assert_eq!(a.mul(&Scalar::ONE), a);
assert_eq!(a.sub(&a), Scalar::ZERO);
assert_eq!(a.add(&a.negate()), Scalar::ZERO);
assert_eq!(a.mul(&a.invert()), Scalar::ONE);
assert_eq!(a.add(&b).mul(&a), a.mul(&a).add(&b.mul(&a)));
}
#[test]
fn canonical_rejects_l_and_above() {
let f = Field::new();
let mut lbytes = [0u8; 32];
f.l.write_le_bytes(&mut lbytes);
assert!(Scalar::from_bytes_canonical(&lbytes).is_none());
let lm1 = f.l.wrapping_sub(&Fe::from_u64(1));
let mut lm1b = [0u8; 32];
lm1.write_le_bytes(&mut lm1b);
assert!(Scalar::from_bytes_canonical(&lm1b).is_some());
let s = Scalar::from_bytes_canonical(&lm1b).unwrap();
assert_eq!(s.to_bytes(), lm1b);
}
#[test]
fn torsion_and_small_order() {
let g = EdwardsPoint::generator();
assert!(bool::from(g.is_torsion_free()));
assert!(!bool::from(g.is_small_order()));
assert!(bool::from(EdwardsPoint::identity().is_small_order()));
}
}