use crate::curve25519_field::{Curve25519Field, VerifyField};
use crate::jsf::NafSign;
use crate::{UnsignedModularInt, VerifyBackend};
type Res<'f, F, T> = <F as VerifyField<T>>::Residue<'f>;
type EdPoint<'f, F, T> = (Res<'f, F, T>, Res<'f, F, T>, Res<'f, F, T>, Res<'f, F, T>);
type NielsPoint<'f, F, T> = (Res<'f, F, T>, Res<'f, F, T>, Res<'f, F, T>);
use crate::{D_BYTES, G_T_BYTES, G_X_BYTES, G_Y_BYTES, MODP_SQRT_M1_BYTES, Q_BYTES};
#[inline(never)]
fn sha512_modq<T: VerifyBackend>(parts: &[&[u8]], q: &T) -> T {
#[cfg(all(feature = "sha512-hmac-sha512", feature = "sha512-sha2"))]
compile_error!(
"ed25519_heapless: enable at most one SHA-512 backend feature — both `sha512-hmac-sha512` and `sha512-sha2` were enabled"
);
#[cfg(not(any(feature = "sha512-hmac-sha512", feature = "sha512-sha2")))]
compile_error!(
"ed25519_heapless: enable exactly one of the SHA-512 backend features `sha512-hmac-sha512` or `sha512-sha2`"
);
#[cfg(all(feature = "sha512-hmac-sha512", not(feature = "sha512-sha2")))]
let hash: [u8; 64] = {
let mut compact_sha = hmac_sha512::Hash::new();
for part in parts {
compact_sha.update(part);
}
compact_sha.finalize()
};
#[cfg(all(feature = "sha512-sha2", not(feature = "sha512-hmac-sha512")))]
let hash: [u8; 64] = {
use sha2::Digest;
let mut compact_sha = sha2::Sha512::new();
for part in parts {
compact_sha.update(part);
}
compact_sha.finalize().into()
};
let hash = hash.as_slice();
let mut acc = <T as const_num_traits::WithPrecision>::zero_with_precision_of(q);
let one = T::one();
for byte_idx in (0..64).rev() {
for bit_idx in (0..8).rev() {
let (doubled, _overflow) = acc.clone().overflowing_add(acc);
acc = doubled;
if (hash[byte_idx] >> bit_idx) & 1 == 1 {
let (added, _) = acc.overflowing_add(one.clone());
acc = added;
}
if &acc >= q {
acc = acc.wrapping_sub(q.clone());
}
if &acc >= q {
acc = acc.wrapping_sub(q.clone());
}
}
}
acc
}
#[inline(never)]
fn recover_x<'f, F, T>(y_raw: T, sign: u8, d_raw: T, field: &'f F) -> Option<Res<'f, F, T>>
where
F: VerifyField<T>,
T: VerifyBackend,
for<'a> &'a T: core::ops::BitAnd<Output = T>
+ const_num_traits::WrappingAdd<Output = T>
+ const_num_traits::WrappingSub<Output = T>,
{
let x2 = {
let y = field.reduce(&y_raw);
let d = field.reduce(&d_raw);
let y2 = field.mul(&y, &y);
let one = field.one();
let numerator = field.sub(&y2, &one);
let dy2 = field.mul(&d, &y2);
let denominator = field.add(&dy2, &one);
let inv_denom = field.inv(&denominator);
field.mul(&numerator, &inv_denom)
};
let zero = field.zero();
if x2 == zero {
return if sign > 0 { None } else { Some(zero) };
}
let exp = {
let three = T::one().wrapping_add(T::one()).wrapping_add(T::one());
let p3 = <&T as const_num_traits::WrappingAdd>::wrapping_add(field.modulus(), &three);
p3 >> 3
};
let mut x = field.exp(&x2, &exp);
{
let check = field.mul(&x, &x);
let diff = field.sub(&check, &x2);
if diff != zero {
let sqrt_m1 = field.reduce(&crate::from_le_bytes::<T>(&MODP_SQRT_M1_BYTES));
x = field.mul(&x, &sqrt_m1);
}
}
{
let check = field.mul(&x, &x);
let diff = field.sub(&check, &x2);
if diff != zero {
return None;
}
}
let x_raw = field.into_raw(&x);
let one = T::one();
let parity = (&x_raw & &one) == one;
let x = if (parity as u8) != sign {
let reduced = field.reduce(&x_raw);
field.sub(&zero, &reduced)
} else {
field.reduce(&x_raw)
};
Some(x)
}
#[inline(never)]
fn decompress_edward_point<'f, F, T>(
encoded: [u8; 32],
d_raw: T,
field: &'f F,
) -> Option<EdPoint<'f, F, T>>
where
F: VerifyField<T>,
T: VerifyBackend,
for<'a> &'a T: core::ops::BitAnd<Output = T>
+ const_num_traits::WrappingAdd<Output = T>
+ const_num_traits::WrappingSub<Output = T>,
{
let mut bytes = encoded;
let sign = bytes[31] >> 7;
bytes[31] &= 0b0111_1111;
let y_raw = crate::from_le_bytes::<T>(&bytes);
if &y_raw >= field.modulus() {
return None;
}
let x = recover_x(y_raw.clone(), sign, d_raw, field)?;
let y = field.reduce(&y_raw);
let one = field.one();
let t = field.mul(&x, &y);
Some((x, y, one, t))
}
#[inline(never)]
fn point_double<'f, F, T>(pp: &EdPoint<'f, F, T>, field: &'f F) -> EdPoint<'f, F, T>
where
F: VerifyField<T>,
T: VerifyBackend,
{
let a = field.mul(&pp.0, &pp.0);
let b = field.mul(&pp.1, &pp.1);
let z_sq = field.mul(&pp.2, &pp.2);
let c = field.add(&z_sq, &z_sq);
let zero = field.zero();
let d = field.sub(&zero, &a);
let x_plus_y = field.add(&pp.0, &pp.1);
let xy_sq = field.mul(&x_plus_y, &x_plus_y);
let e_tmp = field.sub(&xy_sq, &a);
let e = field.sub(&e_tmp, &b);
let g = field.add(&d, &b);
let f = field.sub(&g, &c);
let h = field.sub(&d, &b);
(
field.mul(&e, &f),
field.mul(&g, &h),
field.mul(&f, &g),
field.mul(&e, &h),
)
}
fn to_niels<'f, F, T>(pp: &EdPoint<'f, F, T>, d_raw: T, field: &'f F) -> NielsPoint<'f, F, T>
where
F: VerifyField<T>,
T: VerifyBackend,
{
let y_plus_x = field.add(&pp.1, &pp.0);
let y_minus_x = field.sub(&pp.1, &pp.0);
let d = field.reduce(&d_raw);
let dt = field.mul(&d, &pp.3);
let two_dt = field.add(&dt, &dt);
(y_plus_x, y_minus_x, two_dt)
}
#[inline(never)]
fn point_add_niels<'f, F, T>(
pp: &EdPoint<'f, F, T>,
niels: &NielsPoint<'f, F, T>,
field: &'f F,
) -> EdPoint<'f, F, T>
where
F: VerifyField<T>,
T: VerifyBackend,
{
let pp_y_minus_x = field.sub(&pp.1, &pp.0);
let a = field.mul(&pp_y_minus_x, &niels.1);
let pp_y_plus_x = field.add(&pp.1, &pp.0);
let b = field.mul(&pp_y_plus_x, &niels.0);
let c = field.mul(&pp.3, &niels.2);
let d = field.add(&pp.2, &pp.2);
let e = field.sub(&b, &a);
let f = field.sub(&d, &c);
let g = field.add(&d, &c);
let h = field.add(&b, &a);
(
field.mul(&e, &f),
field.mul(&g, &h),
field.mul(&f, &g),
field.mul(&e, &h),
)
}
#[inline(never)]
fn point_equal<'f, F, T>(pp: &EdPoint<'f, F, T>, qq: &EdPoint<'f, F, T>, field: &'f F) -> bool
where
F: VerifyField<T>,
T: VerifyBackend,
{
let t1 = field.mul(&pp.0, &qq.2);
let t2 = field.mul(&qq.0, &pp.2);
let t3 = field.mul(&pp.1, &qq.2);
let t4 = field.mul(&qq.1, &pp.2);
let zero = field.zero();
field.sub(&t1, &t2) == zero && field.sub(&t3, &t4) == zero
}
#[inline(never)]
fn naf_double_scalar_mul<'f, F, T>(
s: T,
g: &EdPoint<'f, F, T>,
h: T,
a: &EdPoint<'f, F, T>,
d_raw: T,
field: &'f F,
) -> EdPoint<'f, F, T>
where
F: VerifyField<T>,
T: VerifyBackend,
for<'a> &'a T: core::ops::BitAnd<Output = T>,
{
let naf = crate::jsf::NafIterator::new(s, h);
let zero = field.zero();
let mut result: EdPoint<'f, F, T> = (field.zero(), field.one(), field.one(), field.zero());
let g_niels = to_niels(g, d_raw.clone(), field);
let a_niels = to_niels(a, d_raw, field);
let neg_g_niels = (
g_niels.1.clone(),
g_niels.0.clone(),
field.sub(&zero, &g_niels.2),
);
let neg_a_niels = (
a_niels.1.clone(),
a_niels.0.clone(),
field.sub(&zero, &a_niels.2),
);
for digit in naf.digits_msb_first() {
result = point_double(&result, field);
match digit.s_digit {
NafSign::Pos => result = point_add_niels(&result, &g_niels, field),
NafSign::Neg => result = point_add_niels(&result, &neg_g_niels, field),
NafSign::Zero => {}
}
match digit.h_digit {
NafSign::Pos => result = point_add_niels(&result, &a_niels, field),
NafSign::Neg => result = point_add_niels(&result, &neg_a_niels, field),
NafSign::Zero => {}
}
}
result
}
#[must_use]
pub fn verify<T>(public: [u8; 32], msg: &[u8], signature: [u8; 64]) -> bool
where
T: UnsignedModularInt + Copy + modmath::WideMul + modmath::CiosMontMul + modmath::NonCt,
for<'a> &'a T: core::ops::BitAnd<Output = T>
+ const_num_traits::WrappingAdd<Output = T>
+ const_num_traits::WrappingSub<Output = T>,
{
let field = match Curve25519Field::curve25519() {
Ok(f) => f,
Err(_) => return false,
};
verify_with_field::<Curve25519Field<T>, T>(&field, public, msg, signature)
}
#[must_use]
pub fn verify_with_field<F, T>(field: &F, public: [u8; 32], msg: &[u8], signature: [u8; 64]) -> bool
where
F: VerifyField<T>,
T: VerifyBackend,
for<'a> &'a T: core::ops::BitAnd<Output = T>
+ const_num_traits::WrappingAdd<Output = T>
+ const_num_traits::WrappingSub<Output = T>,
{
verify_inner::<F, T>(field, public, msg, signature)
}
#[inline(never)]
fn verify_inner<F, T>(field: &F, public: [u8; 32], msg: &[u8], signature: [u8; 64]) -> bool
where
F: VerifyField<T>,
T: VerifyBackend,
for<'a> &'a T: core::ops::BitAnd<Output = T>
+ const_num_traits::WrappingAdd<Output = T>
+ const_num_traits::WrappingSub<Output = T>,
{
let d = crate::from_le_bytes::<T>(&D_BYTES);
let neg_aa = match decompress_edward_point(public, d.clone(), field) {
Some(aa) => {
let zero = field.zero();
(field.sub(&zero, &aa.0), aa.1, aa.2, field.sub(&zero, &aa.3))
}
None => return false,
};
let rrs: [u8; 32] = signature[0..32].try_into().unwrap_or_default();
let rr = match decompress_edward_point(rrs, d.clone(), field) {
Some(rr) => rr,
None => return false,
};
let s_bytes: [u8; 32] = signature[32..64].try_into().unwrap_or_default();
let s = crate::from_le_bytes::<T>(&s_bytes);
let h = {
let q = crate::from_le_bytes::<T>(&Q_BYTES);
if s >= q {
return false;
}
sha512_modq(&[rrs.as_slice(), public.as_slice(), msg], &q)
};
let g: EdPoint<'_, F, T> = (
field.reduce(&crate::from_le_bytes::<T>(&G_X_BYTES)),
field.reduce(&crate::from_le_bytes::<T>(&G_Y_BYTES)),
field.one(),
field.reduce(&crate::from_le_bytes::<T>(&G_T_BYTES)),
);
let sb_minus_ha = naf_double_scalar_mul(s, &g, h, &neg_aa, d, field);
point_equal(&sb_minus_ha, &rr, field)
}