use crate::curve25519_field::Curve25519FieldCt;
use crate::{D_BYTES, SignBackend};
use modmath::ResidueCt;
use subtle::Choice;
pub(crate) fn sha512(parts: &[&[u8]]) -> zeroize::Zeroizing<[u8; 64]> {
#[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 mut compact_sha = hmac_sha512::Hash::new();
for part in parts {
compact_sha.update(part);
}
zeroize::Zeroizing::new(compact_sha.finalize())
}
#[cfg(all(feature = "sha512-sha2", not(feature = "sha512-hmac-sha512")))]
{
use sha2::Digest;
let mut compact_sha = sha2::Sha512::new();
for part in parts {
compact_sha.update(part);
}
zeroize::Zeroizing::new(compact_sha.finalize().into())
}
}
pub(crate) type EdPointCt<'f, T> = (
ResidueCt<'f, T>,
ResidueCt<'f, T>,
ResidueCt<'f, T>,
ResidueCt<'f, T>,
);
pub(crate) type NielsPointCt<'f, T> = (ResidueCt<'f, T>, ResidueCt<'f, T>, ResidueCt<'f, T>);
#[inline(never)]
pub(crate) fn point_double_ct<'f, T>(
pp: &EdPointCt<'f, T>,
field: &'f Curve25519FieldCt<T>,
) -> EdPointCt<'f, T>
where
T: SignBackend,
for<'a> &'a T:
const_num_traits::WrappingAdd<Output = T> + const_num_traits::WrappingSub<Output = T>,
{
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),
)
}
pub(crate) fn to_niels_ct<'f, T>(
pp: &EdPointCt<'f, T>,
d_raw: T,
field: &'f Curve25519FieldCt<T>,
) -> NielsPointCt<'f, T>
where
T: SignBackend,
for<'a> &'a T:
const_num_traits::WrappingAdd<Output = T> + const_num_traits::WrappingSub<Output = T>,
{
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)]
pub(crate) fn point_add_niels_ct<'f, T>(
pp: &EdPointCt<'f, T>,
niels: &NielsPointCt<'f, T>,
field: &'f Curve25519FieldCt<T>,
) -> EdPointCt<'f, T>
where
T: SignBackend,
for<'a> &'a T:
const_num_traits::WrappingAdd<Output = T> + const_num_traits::WrappingSub<Output = T>,
{
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]
pub(crate) fn point_cswap_ct<'f, T>(
choice: Choice,
a: &mut EdPointCt<'f, T>,
b: &mut EdPointCt<'f, T>,
) where
T: subtle::ConditionallySelectable + modmath::MontStorage,
{
ResidueCt::cswap(choice, &mut a.0, &mut b.0);
ResidueCt::cswap(choice, &mut a.1, &mut b.1);
ResidueCt::cswap(choice, &mut a.2, &mut b.2);
ResidueCt::cswap(choice, &mut a.3, &mut b.3);
}
#[inline(never)]
pub(crate) fn scalar_mult_ct<'f, T>(
field: &'f Curve25519FieldCt<T>,
base: &EdPointCt<'f, T>,
scalar: &[u8],
) -> EdPointCt<'f, T>
where
T: SignBackend,
for<'a> &'a T:
const_num_traits::WrappingAdd<Output = T> + const_num_traits::WrappingSub<Output = T>,
{
let d_raw = crate::from_le_bytes::<T>(&D_BYTES);
let base_niels = to_niels_ct(base, d_raw, field);
let mut acc: EdPointCt<'f, T> = (field.zero(), field.one(), field.one(), field.zero());
let bit_count = scalar.len() * 8;
for t in (0..bit_count).rev() {
acc = point_double_ct(&acc, field);
let bit = (scalar[t >> 3] >> (t & 7)) & 1;
let mut sum = point_add_niels_ct(&acc, &base_niels, field);
point_cswap_ct(Choice::from(bit), &mut acc, &mut sum);
}
acc
}
pub(crate) fn scalar_mult_blinded_ct<'f, T>(
field: &'f Curve25519FieldCt<T>,
base: &EdPointCt<'f, T>,
scalar: &[u8],
lambda: &ResidueCt<'f, T>,
) -> EdPointCt<'f, T>
where
T: SignBackend,
for<'a> &'a T:
const_num_traits::WrappingAdd<Output = T> + const_num_traits::WrappingSub<Output = T>,
{
let d_raw = crate::from_le_bytes::<T>(&D_BYTES);
let base_niels = to_niels_ct(base, d_raw, field);
let mut acc: EdPointCt<'f, T> = (field.zero(), lambda.clone(), lambda.clone(), field.zero());
let bit_count = scalar.len() * 8;
for t in (0..bit_count).rev() {
acc = point_double_ct(&acc, field);
let bit = (scalar[t >> 3] >> (t & 7)) & 1;
let mut sum = point_add_niels_ct(&acc, &base_niels, field);
point_cswap_ct(Choice::from(bit), &mut acc, &mut sum);
}
acc
}
pub(crate) fn point_compress_ct<'f, T>(
pp: &EdPointCt<'f, T>,
field: &'f Curve25519FieldCt<T>,
) -> [u8; 32]
where
T: SignBackend,
for<'a> &'a T: const_num_traits::WrappingAdd<Output = T>
+ const_num_traits::WrappingSub<Output = T>
+ const_num_traits::ToBytes<Bytes = <T as const_num_traits::ToBytes>::Bytes>,
<T as const_num_traits::ToBytes>::Bytes: zeroize::Zeroize,
{
let z_inv = field.inv(&pp.2);
let x = field.mul(&pp.0, &z_inv);
let y = field.mul(&pp.1, &z_inv);
let x_raw = field.into_raw(&x);
let y_raw = field.into_raw(&y);
let parity = (x_raw & T::one()) == T::one();
let bytes = crate::to_le_bytes_ct(&y_raw);
let bytes_slice: &[u8] = bytes.as_ref();
let mut out = [0u8; 32];
out.copy_from_slice(&bytes_slice[..32]);
out[31] |= (parity as u8) << 7;
out
}
#[inline(never)]
pub(crate) fn sha512_modq_ct<T>(parts: &[&[u8]], q: &T) -> zeroize::Zeroizing<T>
where
T: SignBackend,
for<'a> &'a T:
const_num_traits::WrappingAdd<Output = T> + const_num_traits::WrappingSub<Output = T>,
{
let hash = sha512(parts);
let zero = T::zero();
let one = T::one();
let mut acc = zeroize::Zeroizing::new(T::zero());
for byte_idx in (0..64).rev() {
for bit_idx in (0..8).rev() {
let (doubled, _overflow) = (*acc).overflowing_add(*acc);
*acc = doubled;
let bit_val = (hash[byte_idx] >> bit_idx) & 1;
let bit_t = T::conditional_select(&zero, &one, Choice::from(bit_val));
let (with_bit, _) = (*acc).overflowing_add(bit_t);
*acc = with_bit;
for _ in 0..2 {
let candidate = (*acc).wrapping_sub(*q);
let needs_sub = !acc.ct_lt(q);
*acc = T::conditional_select(&*acc, &candidate, needs_sub);
}
}
}
acc
}
pub(crate) fn base_point_ct<'f, T>(field: &'f Curve25519FieldCt<T>) -> EdPointCt<'f, T>
where
T: SignBackend,
for<'a> &'a T:
const_num_traits::WrappingAdd<Output = T> + const_num_traits::WrappingSub<Output = T>,
{
let gx = field.reduce(&crate::from_le_bytes::<T>(&crate::G_X_BYTES));
let gy = field.reduce(&crate::from_le_bytes::<T>(&crate::G_Y_BYTES));
let one = field.one();
let gt = field.reduce(&crate::from_le_bytes::<T>(&crate::G_T_BYTES));
(gx, gy, one, gt)
}
#[cfg(all(test, feature = "fixed-bigint"))]
mod tests {
use super::*;
use crate::curve25519_field::Curve25519FieldCt;
use fixed_bigint::FixedUInt;
type T = FixedUInt<u32, 16, const_num_traits::Ct>;
fn projective_eq<'f>(
a: &EdPointCt<'f, T>,
b: &EdPointCt<'f, T>,
field: &'f Curve25519FieldCt<T>,
) -> bool {
let lhs_x = field.mul(&a.0, &b.2);
let rhs_x = field.mul(&b.0, &a.2);
let lhs_y = field.mul(&a.1, &b.2);
let rhs_y = field.mul(&b.1, &a.2);
lhs_x == rhs_x && lhs_y == rhs_y
}
#[test]
fn double_matches_add_self() {
let field = Curve25519FieldCt::<T>::curve25519().unwrap();
let g = base_point_ct(&field);
let d_raw = crate::from_le_bytes::<T>(&D_BYTES);
let g_niels = to_niels_ct(&g, d_raw, &field);
let doubled = point_double_ct(&g, &field);
let added = point_add_niels_ct(&g, &g_niels, &field);
assert!(projective_eq(&doubled, &added, &field));
}
#[test]
fn scalar_mult_by_one_returns_base() {
let field = Curve25519FieldCt::<T>::curve25519().unwrap();
let g = base_point_ct(&field);
let mut scalar = [0u8; 32];
scalar[0] = 1;
let result = scalar_mult_ct(&field, &g, &scalar);
assert!(projective_eq(&result, &g, &field));
}
#[test]
fn scalar_mult_by_two_matches_double() {
let field = Curve25519FieldCt::<T>::curve25519().unwrap();
let g = base_point_ct(&field);
let mut scalar = [0u8; 32];
scalar[0] = 2;
let result = scalar_mult_ct(&field, &g, &scalar);
let doubled = point_double_ct(&g, &field);
assert!(projective_eq(&result, &doubled, &field));
}
}