//! Ed25519 signing and verification with typed keys and signatures.
//!
//! # Quick Start
//!
//! ```rust
//! use rscrypto::auth::ed25519::{Ed25519Keypair, Ed25519SecretKey};
//!
//! let secret = Ed25519SecretKey::from_bytes([7u8; Ed25519SecretKey::LENGTH]);
//! let keypair = Ed25519Keypair::from_secret_key(secret.clone());
//! let public = keypair.public_key();
//! let sig = secret.sign(b"auth");
//!
//! assert_eq!(secret.as_bytes().len(), 32);
//! assert_eq!(public.as_bytes().len(), 32);
//! assert_eq!(sig.as_bytes().len(), 64);
//! assert!(public.verify(b"auth", &sig).is_ok());
//! ```
//!
//! The public API keeps secret keys, public keys, signatures, and keypairs as
//! distinct types instead of passing raw byte slices through signing and
//! verification calls.
//!
//! # Post-Quantum Migration
//!
//! Ed25519 is a classical signature scheme. For new deployments with a
//! long-lived trust horizon, plan a hybrid migration path rather than
//! treating Ed25519 as the end state. The repository roadmap tracks
//! `ML-DSA` as the post-quantum signature direction.

use core::{
  fmt,
  hash::{Hash, Hasher},
};

use crate::{
  SecretBytes,
  hashes::crypto::Sha512,
  traits::{Digest, VerificationError, ct},
};

pub(crate) mod hash;
use self::constants::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
#[cfg(all(
  target_arch = "aarch64",
  any(target_os = "macos", target_os = "linux"),
  not(feature = "portable-only"),
  not(miri)
))]
mod aarch64_asm;
#[cfg(target_arch = "x86_64")]
pub(crate) use crate::auth::curve25519_edwards::point_avx2;
#[cfg(all(
  target_arch = "x86_64",
  target_os = "linux",
  not(feature = "portable-only"),
  not(miri)
))]
mod x86_64_asm;
pub(crate) use crate::auth::curve25519_edwards::{constants, field, point, scalar};

// Keep the planned internal layout explicit at compile time.
const _: usize = core::mem::size_of::<field::FieldElement>();
const _: usize = core::mem::size_of::<hash::ExpandedSecret>();
const _: usize = core::mem::size_of::<point::ExtendedPoint>();
const _: usize = core::mem::size_of::<scalar::Scalar>();
const _: field::FieldElement = field::FieldElement::ONE;
const _: fn([u64; constants::FIELD_LIMBS]) -> field::FieldElement = field::FieldElement::from_limbs;
const _: fn(u64) -> field::FieldElement = field::FieldElement::from_small;
const _: fn(&field::FieldElement) -> &[u64; constants::FIELD_LIMBS] = field::FieldElement::limbs;
const _: fn(&field::FieldElement, &field::FieldElement) -> field::FieldElement = field::FieldElement::add;
const _: fn(&field::FieldElement, &field::FieldElement) -> field::FieldElement = field::FieldElement::sub;
const _: fn(&field::FieldElement, &field::FieldElement) -> field::FieldElement = field::FieldElement::mul;
const _: fn(&field::FieldElement) -> field::FieldElement = field::FieldElement::square;
const _: fn(&field::FieldElement) -> field::FieldElement = field::FieldElement::neg;
const _: fn(&field::FieldElement) -> field::FieldElement = field::FieldElement::invert;
const _: fn(&field::FieldElement) -> field::FieldElement = field::FieldElement::normalize;
const _: fn(&[u8; 32]) -> Option<field::FieldElement> = field::FieldElement::from_bytes;
const _: fn(field::FieldElement) -> [u8; 32] = field::FieldElement::to_bytes;
const _: fn(&field::FieldElement) -> bool = field::FieldElement::is_zero;
const _: fn(&field::FieldElement) -> bool = field::FieldElement::is_negative;
const _: fn(&field::FieldElement) -> Option<field::FieldElement> = field::FieldElement::sqrt;
const _: point::ExtendedPoint = point::ExtendedPoint::identity();
const _: fn(field::FieldElement, field::FieldElement) -> point::ExtendedPoint = point::ExtendedPoint::from_affine;
const _: fn(&point::ExtendedPoint, &point::ExtendedPoint) -> point::ExtendedPoint = point::ExtendedPoint::add;
const _: fn(&point::ExtendedPoint) -> point::ExtendedPoint = point::ExtendedPoint::double;
const _: fn(point::ExtendedPoint) -> Option<[u8; 32]> = point::ExtendedPoint::to_bytes;
const _: fn(&[u8; 32]) -> Option<point::ExtendedPoint> = point::ExtendedPoint::from_bytes;
const _: fn() -> point::ExtendedPoint = point::ExtendedPoint::basepoint;
const _: fn(&point::ExtendedPoint, &[u8; 32]) -> point::ExtendedPoint = point::ExtendedPoint::scalar_mul;
const _: fn(&point::ExtendedPoint, &[u8; 32]) -> point::ExtendedPoint = point::ExtendedPoint::scalar_mul_vartime;
const _: fn(&[u8; 32]) -> point::ExtendedPoint = point::ExtendedPoint::scalar_mul_basepoint;
#[cfg(target_arch = "x86_64")]
const _: unsafe fn(&point::ExtendedPoint, &[u8; 32]) -> point::ExtendedPoint = point_avx2::scalar_mul_vartime_avx2;
#[cfg(target_arch = "x86_64")]
const _: unsafe fn(&point::ExtendedPoint, &[u8; 32]) -> point::ExtendedPoint = point_avx2::scalar_mul_vartime_ifma;
const _: fn(&point::ExtendedPoint) -> point::ExtendedPoint = point::ExtendedPoint::mul_by_cofactor;
const _: fn(&point::ExtendedPoint) -> bool = point::ExtendedPoint::is_small_order;
const _: fn(point::ExtendedPoint) -> Option<(field::FieldElement, field::FieldElement)> =
  point::ExtendedPoint::to_affine;
const _: fn(
  &point::ExtendedPoint,
) -> (
  &field::FieldElement,
  &field::FieldElement,
  &field::FieldElement,
  &field::FieldElement,
) = point::ExtendedPoint::components;
const _: fn(&Ed25519SecretKey) -> hash::ExpandedSecret = hash::ExpandedSecret::from_secret_key;
const _: fn(&hash::ExpandedSecret) -> &[u8; SECRET_KEY_LENGTH] = hash::ExpandedSecret::scalar_bytes;
const _: fn(&hash::ExpandedSecret) -> scalar::Scalar = hash::ExpandedSecret::scalar_words;
const _: fn(&hash::ExpandedSecret) -> &[u8; SECRET_KEY_LENGTH] = hash::ExpandedSecret::nonce_prefix;
const _: fn(&hash::ExpandedSecret) -> [u8; SECRET_KEY_LENGTH] = hash::ExpandedSecret::public_key_bytes;
const _: fn(&mut [u8; SECRET_KEY_LENGTH]) = scalar::clamp_secret_scalar;
const _: fn(&[u8; SECRET_KEY_LENGTH]) -> scalar::Scalar = scalar::decode_words_le;
const _: fn(&[u8; SECRET_KEY_LENGTH]) -> Option<scalar::Scalar> = scalar::from_canonical_bytes;
const _: fn(&scalar::Scalar) -> [u8; SECRET_KEY_LENGTH] = scalar::to_bytes;
const _: fn(&[u8]) -> scalar::Scalar = scalar::reduce_bytes_mod_order;
const _: fn(&scalar::Scalar, &scalar::Scalar) -> scalar::Scalar = scalar::add_mod;
const _: fn(&scalar::Scalar, &scalar::Scalar) -> scalar::Scalar = scalar::mul_mod;
const _: fn(&scalar::Scalar, &scalar::Scalar, &scalar::Scalar) -> scalar::Scalar = scalar::mul_add_mod;

/// Ed25519 secret key bytes.
///
/// Provides typed signing and public-key derivation instead of vague `&[u8]`
/// parameters at the call site.
#[derive(Clone)]
pub struct Ed25519SecretKey([u8; Self::LENGTH]);

impl PartialEq for Ed25519SecretKey {
  fn eq(&self, other: &Self) -> bool {
    ct::constant_time_eq(&self.0, &other.0)
  }
}

impl Eq for Ed25519SecretKey {}

impl Ed25519SecretKey {
  /// Secret key length in bytes.
  pub const LENGTH: usize = SECRET_KEY_LENGTH;

  /// Construct a secret key from its byte representation.
  #[inline]
  #[must_use]
  pub const fn from_bytes(bytes: [u8; Self::LENGTH]) -> Self {
    Self(bytes)
  }

  /// Explicitly extract the secret key bytes into a zeroizing wrapper.
  #[inline]
  #[must_use]
  pub fn expose_secret(&self) -> SecretBytes<{ Self::LENGTH }> {
    SecretBytes::new(self.0)
  }

  /// Borrow the secret key bytes.
  #[inline]
  #[must_use]
  pub const fn as_bytes(&self) -> &[u8; Self::LENGTH] {
    &self.0
  }

  /// Derive the matching Ed25519 public key.
  #[must_use]
  pub fn public_key(&self) -> Ed25519PublicKey {
    let expanded = hash::ExpandedSecret::from_secret_key(self);
    public_key_from_scalar(expanded.scalar_bytes())
  }

  /// Sign a message with this secret key.
  #[must_use]
  pub fn sign(&self, message: &[u8]) -> Ed25519Signature {
    let public = self.public_key();
    sign_with_secret(self, &public, message)
  }
}

impl fmt::Debug for Ed25519SecretKey {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    f.write_str("Ed25519SecretKey(****)")
  }
}

impl Ed25519SecretKey {
  /// Construct a secret key by filling bytes from the provided closure.
  ///
  /// ```rust
  /// # use rscrypto::Ed25519SecretKey;
  /// let sk = Ed25519SecretKey::generate(|buf| buf.fill(0xA5));
  /// assert_eq!(sk.as_bytes(), &[0xA5; Ed25519SecretKey::LENGTH]);
  /// ```
  #[inline]
  #[must_use]
  pub fn generate(fill: impl FnOnce(&mut [u8; Self::LENGTH])) -> Self {
    let mut bytes = [0u8; Self::LENGTH];
    fill(&mut bytes);
    Self(bytes)
  }

  impl_getrandom!();
}

impl_hex_fmt_secret!(Ed25519SecretKey);
impl_serde_secret_bytes!(Ed25519SecretKey);

impl Drop for Ed25519SecretKey {
  fn drop(&mut self) {
    ct::zeroize(&mut self.0);
  }
}

impl_ct_eq!(Ed25519SecretKey);

/// Ed25519 public key bytes.
#[derive(Clone, Copy)]
pub struct Ed25519PublicKey {
  bytes: [u8; Self::LENGTH],
  point: Option<point::ExtendedPoint>,
  small_order: bool,
}

impl Ed25519PublicKey {
  /// Public key length in bytes.
  pub const LENGTH: usize = PUBLIC_KEY_LENGTH;

  /// Construct a public key from its byte representation.
  #[must_use]
  pub fn from_bytes(bytes: [u8; Self::LENGTH]) -> Self {
    let point = point::ExtendedPoint::from_bytes(&bytes);
    Self {
      small_order: point.is_some_and(|point| point.is_small_order()),
      point,
      bytes,
    }
  }

  #[inline]
  #[must_use]
  fn from_basepoint_mul(bytes: [u8; Self::LENGTH], point: point::ExtendedPoint) -> Self {
    // `[a]B` for clamped Ed25519 secret scalars is in the prime-order subgroup,
    // so weak-key validation is only needed for untrusted encoded public keys.
    Self {
      bytes,
      small_order: false,
      point: Some(point),
    }
  }

  /// Return the public key bytes.
  #[inline]
  #[must_use]
  pub const fn to_bytes(self) -> [u8; Self::LENGTH] {
    self.bytes
  }

  /// Borrow the public key bytes.
  #[inline]
  #[must_use]
  pub const fn as_bytes(&self) -> &[u8; Self::LENGTH] {
    &self.bytes
  }

  #[inline]
  #[must_use]
  const fn point(&self) -> Option<point::ExtendedPoint> {
    self.point
  }

  #[inline]
  #[must_use]
  const fn is_small_order(&self) -> bool {
    self.small_order
  }
}

impl PartialEq for Ed25519PublicKey {
  fn eq(&self, other: &Self) -> bool {
    self.bytes == other.bytes
  }
}

impl Eq for Ed25519PublicKey {}

impl Hash for Ed25519PublicKey {
  fn hash<H: Hasher>(&self, state: &mut H) {
    self.bytes.hash(state);
  }
}

impl fmt::Debug for Ed25519PublicKey {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "Ed25519PublicKey(")?;
    crate::hex::fmt_hex_lower(&self.bytes, f)?;
    write!(f, ")")
  }
}

impl_hex_fmt!(Ed25519PublicKey);
impl_serde_bytes!(Ed25519PublicKey);

impl_ct_eq!(Ed25519PublicKey, bytes);

impl Ed25519PublicKey {
  /// Verify a message/signature pair against this public key.
  ///
  /// # Errors
  ///
  /// Returns [`VerificationError`] if the public key bytes are invalid or
  /// weak, the signature is non-canonical, or the signature does not match
  /// `message`.
  #[must_use = "signature verification must be checked; a dropped Result silently accepts a forged signature"]
  pub fn verify(&self, message: &[u8], signature: &Ed25519Signature) -> Result<(), VerificationError> {
    verify(message, self, signature)
  }
}

/// Ed25519 signature bytes.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Ed25519Signature([u8; Self::LENGTH]);

impl Ed25519Signature {
  /// Signature length in bytes.
  pub const LENGTH: usize = SIGNATURE_LENGTH;

  /// Construct a signature from its byte representation.
  #[inline]
  #[must_use]
  pub const fn from_bytes(bytes: [u8; Self::LENGTH]) -> Self {
    Self(bytes)
  }

  /// Return the signature bytes.
  #[inline]
  #[must_use]
  pub const fn to_bytes(self) -> [u8; Self::LENGTH] {
    self.0
  }

  /// Borrow the signature bytes.
  #[inline]
  #[must_use]
  pub const fn as_bytes(&self) -> &[u8; Self::LENGTH] {
    &self.0
  }
}

impl Default for Ed25519Signature {
  #[inline]
  fn default() -> Self {
    Self([0u8; Self::LENGTH])
  }
}

impl fmt::Debug for Ed25519Signature {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "Ed25519Signature(")?;
    crate::hex::fmt_hex_lower(&self.0, f)?;
    write!(f, ")")
  }
}

impl_hex_fmt!(Ed25519Signature);
impl_serde_bytes!(Ed25519Signature);

impl_ct_eq!(Ed25519Signature);

/// Ed25519 keypair with typed secret and public halves.
///
/// # Examples
///
/// ```rust
/// use rscrypto::{Ed25519Keypair, Ed25519SecretKey};
///
/// let secret = Ed25519SecretKey::from_bytes([7u8; Ed25519SecretKey::LENGTH]);
/// let keypair = Ed25519Keypair::from_secret_key(secret);
/// let signature = keypair.sign(b"auth");
///
/// assert!(keypair.public_key().verify(b"auth", &signature).is_ok());
/// ```
#[derive(Clone)]
pub struct Ed25519Keypair {
  secret: Ed25519SecretKey,
  public: Ed25519PublicKey,
  expanded: hash::ExpandedSecret,
}

#[cfg(feature = "diag")]
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct DiagEd25519VerifyScalars {
  pub r_bytes: [u8; PUBLIC_KEY_LENGTH],
  pub s_canonical: [u8; SECRET_KEY_LENGTH],
  pub neg_challenge: [u8; SECRET_KEY_LENGTH],
  pub public_key: [u8; PUBLIC_KEY_LENGTH],
}

impl PartialEq for Ed25519Keypair {
  fn eq(&self, other: &Self) -> bool {
    let secret_eq = self.secret == other.secret;
    let public_eq = self.public == other.public;
    secret_eq & public_eq
  }
}

impl Eq for Ed25519Keypair {}

impl Ed25519Keypair {
  /// Derive a keypair from a secret key.
  #[must_use]
  pub fn from_secret_key(secret: Ed25519SecretKey) -> Self {
    let expanded = hash::ExpandedSecret::from_secret_key(&secret);
    let public = public_key_from_scalar(expanded.scalar_bytes());
    Self {
      secret,
      public,
      expanded,
    }
  }

  /// Borrow the secret key.
  #[must_use]
  pub const fn secret_key(&self) -> &Ed25519SecretKey {
    &self.secret
  }

  /// Return the public key.
  #[must_use]
  pub const fn public_key(&self) -> Ed25519PublicKey {
    self.public
  }

  /// Sign a message with the keypair secret key.
  #[must_use]
  pub fn sign(&self, message: &[u8]) -> Ed25519Signature {
    sign_with_expanded(&self.expanded, &self.public, message)
  }
}

impl fmt::Debug for Ed25519Keypair {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    f.debug_struct("Ed25519Keypair")
      .field("public", &self.public)
      .finish_non_exhaustive()
  }
}

/// Verify a message/signature pair against an Ed25519 public key.
///
/// # Errors
///
/// Returns [`VerificationError`] if the public key bytes are invalid or weak,
/// the signature is non-canonical, or the signature does not match `message`.
#[must_use = "signature verification must be checked; a dropped Result silently accepts a forged signature"]
pub fn verify(
  message: &[u8],
  public_key: &Ed25519PublicKey,
  signature: &Ed25519Signature,
) -> Result<(), VerificationError> {
  let (r_bytes, s_bytes) = split_signature(signature);
  if public_key.is_small_order() {
    return Err(VerificationError::new());
  }
  let a_point = public_key.point().ok_or(VerificationError::new())?;
  let s = scalar::from_canonical_bytes(&s_bytes).ok_or(VerificationError::new())?;

  let challenge_digest = hash_challenge(&r_bytes, public_key.as_bytes(), message);
  let challenge = scalar::reduce_bytes_mod_order(&challenge_digest);
  let neg_challenge = scalar::negate_mod(&challenge);
  let neg_challenge_bytes = scalar::to_bytes(&neg_challenge);
  let s_canonical = scalar::to_bytes(&s);

  // Strict Ed25519 verification: compute [s]B + [-h]A and compare it
  // directly with R after rejecting weak points.
  #[cfg(all(
    target_arch = "aarch64",
    any(target_os = "macos", target_os = "linux"),
    not(feature = "portable-only"),
    not(miri)
  ))]
  if let Some(result) = verify_aarch64_encoded_r(&r_bytes, &s_canonical, &neg_challenge_bytes, public_key.as_bytes()) {
    return result;
  }

  let r_point = point::ExtendedPoint::from_bytes(&r_bytes).ok_or(VerificationError::new())?;
  if r_point.is_small_order() {
    return Err(VerificationError::new());
  }

  let combined = straus_dispatch(&s_canonical, &neg_challenge_bytes, &a_point);

  if combined.equals_projective(&r_point) {
    Ok(())
  } else {
    Err(VerificationError::new())
  }
}

#[must_use]
fn sign_with_secret(secret: &Ed25519SecretKey, public: &Ed25519PublicKey, message: &[u8]) -> Ed25519Signature {
  let expanded = hash::ExpandedSecret::from_secret_key(secret);
  sign_with_expanded(&expanded, public, message)
}

#[must_use]
fn sign_with_expanded(expanded: &hash::ExpandedSecret, public: &Ed25519PublicKey, message: &[u8]) -> Ed25519Signature {
  let mut secret_scalar = expanded.scalar_words();

  let mut nonce_hasher = Sha512::new();
  nonce_hasher.update(expanded.nonce_prefix());
  nonce_hasher.update(message);
  let mut nonce_digest = nonce_hasher.finalize();
  let mut nonce_scalar = scalar::reduce_64_bytes_mod_order_secret(&nonce_digest);
  let mut nonce_bytes = scalar::to_bytes(&nonce_scalar);
  let r_encoded = basepoint_mul_encoded_dispatch(&nonce_bytes);

  let challenge_digest = hash_challenge(&r_encoded, public.as_bytes(), message);
  let challenge = scalar::reduce_bytes_mod_order(&challenge_digest);
  let mut s = scalar::mul_add_mod_secret(&challenge, &secret_scalar, &nonce_scalar);
  let mut s_bytes = scalar::to_bytes(&s);

  let signature = assemble_signature(&r_encoded, &s_bytes);

  ct::zeroize_words_no_fence(&mut secret_scalar);
  ct::zeroize_no_fence(&mut nonce_digest);
  ct::zeroize_words_no_fence(&mut nonce_scalar);
  ct::zeroize_no_fence(&mut nonce_bytes);
  ct::zeroize_words_no_fence(&mut s);
  ct::zeroize_no_fence(&mut s_bytes);
  core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);

  signature
}

#[inline]
#[must_use]
fn basepoint_mul_encoded_dispatch(scalar_bytes: &[u8; SECRET_KEY_LENGTH]) -> [u8; PUBLIC_KEY_LENGTH] {
  #[cfg(all(
    target_arch = "aarch64",
    any(target_os = "macos", target_os = "linux"),
    not(feature = "portable-only"),
    not(miri)
  ))]
  {
    aarch64_asm::basepoint_mul_encoded(scalar_bytes)
  }

  #[cfg(all(
    target_arch = "x86_64",
    target_os = "linux",
    not(feature = "portable-only"),
    not(miri)
  ))]
  {
    x86_64_asm::basepoint_mul_encoded(scalar_bytes)
  }

  #[cfg(not(any(
    all(
      target_arch = "aarch64",
      any(target_os = "macos", target_os = "linux"),
      not(feature = "portable-only"),
      not(miri)
    ),
    all(
      target_arch = "x86_64",
      target_os = "linux",
      not(feature = "portable-only"),
      not(miri)
    )
  )))]
  {
    basepoint_mul_dispatch(scalar_bytes).to_bytes().unwrap_or_default()
  }
}

#[must_use]
fn assemble_signature(r_bytes: &[u8; 32], s_bytes: &[u8; 32]) -> Ed25519Signature {
  let mut bytes = [0u8; Ed25519Signature::LENGTH];
  let (r_dst, s_dst) = bytes.as_mut_slice().split_at_mut(SECRET_KEY_LENGTH);

  for (dst, src) in r_dst.iter_mut().zip(r_bytes.iter().copied()) {
    *dst = src;
  }
  for (dst, src) in s_dst.iter_mut().zip(s_bytes.iter().copied()) {
    *dst = src;
  }

  Ed25519Signature::from_bytes(bytes)
}

#[must_use]
fn split_signature(signature: &Ed25519Signature) -> ([u8; 32], [u8; 32]) {
  let mut r_bytes = [0u8; 32];
  let mut s_bytes = [0u8; 32];
  let (r_src, s_src) = signature.as_bytes().as_slice().split_at(SECRET_KEY_LENGTH);

  for (dst, src) in r_bytes.iter_mut().zip(r_src.iter().copied()) {
    *dst = src;
  }
  for (dst, src) in s_bytes.iter_mut().zip(s_src.iter().copied()) {
    *dst = src;
  }

  (r_bytes, s_bytes)
}

#[inline]
#[must_use]
fn hash_challenge(r_bytes: &[u8; PUBLIC_KEY_LENGTH], public_key: &[u8; PUBLIC_KEY_LENGTH], message: &[u8]) -> [u8; 64] {
  let mut prefix = [0u8; 64];
  prefix[..PUBLIC_KEY_LENGTH].copy_from_slice(r_bytes);
  prefix[PUBLIC_KEY_LENGTH..].copy_from_slice(public_key);
  Sha512::digest_64_byte_prefix(&prefix, message)
}

#[cfg(feature = "diag")]
#[doc(hidden)]
#[must_use]
pub fn diag_ed25519_verify_scalars(
  public_key: &Ed25519PublicKey,
  signature: &Ed25519Signature,
  message: &[u8],
) -> Option<DiagEd25519VerifyScalars> {
  let (r_bytes, s_bytes) = split_signature(signature);
  let s = scalar::from_canonical_bytes(&s_bytes)?;
  let challenge_digest = hash_challenge(&r_bytes, public_key.as_bytes(), message);
  let challenge = scalar::reduce_bytes_mod_order(&challenge_digest);
  let neg_challenge = scalar::negate_mod(&challenge);

  Some(DiagEd25519VerifyScalars {
    r_bytes,
    s_canonical: scalar::to_bytes(&s),
    neg_challenge: scalar::to_bytes(&neg_challenge),
    public_key: *public_key.as_bytes(),
  })
}

#[cfg(feature = "diag")]
#[doc(hidden)]
#[must_use]
pub fn diag_ed25519_verify_challenge_reduce_digest(
  public_key: &Ed25519PublicKey,
  signature: &Ed25519Signature,
  message: &[u8],
) -> [u8; SECRET_KEY_LENGTH] {
  let (r_bytes, _) = split_signature(signature);
  let challenge_digest = hash_challenge(&r_bytes, public_key.as_bytes(), message);
  let challenge = scalar::reduce_bytes_mod_order(&challenge_digest);
  scalar::to_bytes(&challenge)
}

#[cfg(feature = "diag")]
#[doc(hidden)]
#[must_use]
pub fn diag_ed25519_verify_public_decode_digest(public_key: &[u8; PUBLIC_KEY_LENGTH]) -> [u8; PUBLIC_KEY_LENGTH] {
  point::ExtendedPoint::from_bytes(public_key)
    .and_then(point::ExtendedPoint::to_bytes)
    .unwrap_or_default()
}

#[cfg(feature = "diag")]
#[doc(hidden)]
#[must_use]
pub fn diag_ed25519_verify_r_decode_digest(r_bytes: &[u8; PUBLIC_KEY_LENGTH]) -> [u8; PUBLIC_KEY_LENGTH] {
  point::ExtendedPoint::from_bytes(r_bytes)
    .filter(|point| !point.is_small_order())
    .and_then(point::ExtendedPoint::to_bytes)
    .unwrap_or_default()
}

#[cfg(feature = "diag")]
#[doc(hidden)]
#[must_use]
pub fn diag_ed25519_verify_portable_double_scalar_digest(
  s_canonical: &[u8; SECRET_KEY_LENGTH],
  neg_challenge: &[u8; SECRET_KEY_LENGTH],
  public_key: &[u8; PUBLIC_KEY_LENGTH],
) -> [u8; PUBLIC_KEY_LENGTH] {
  point::ExtendedPoint::from_bytes(public_key)
    .map(|a_point| point::straus_wnaf_basepoint_vartime(s_canonical, neg_challenge, &a_point))
    .and_then(point::ExtendedPoint::to_bytes)
    .unwrap_or_default()
}

#[cfg(all(
  feature = "diag",
  target_arch = "aarch64",
  any(target_os = "macos", target_os = "linux"),
  not(feature = "portable-only"),
  not(miri)
))]
#[doc(hidden)]
#[must_use]
pub fn diag_ed25519_verify_aarch64_asm_double_scalar_digest(
  s_canonical: &[u8; SECRET_KEY_LENGTH],
  neg_challenge: &[u8; SECRET_KEY_LENGTH],
  public_key: &[u8; PUBLIC_KEY_LENGTH],
) -> [u8; PUBLIC_KEY_LENGTH] {
  aarch64_asm::double_scalar_basepoint_encoded(s_canonical, neg_challenge, public_key).unwrap_or_default()
}

#[cfg(all(
  target_arch = "aarch64",
  any(target_os = "macos", target_os = "linux"),
  not(feature = "portable-only"),
  not(miri)
))]
const SMALL_ORDER_ENCODINGS: [[u8; PUBLIC_KEY_LENGTH]; 8] = [
  [
    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  ],
  [
    0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53,
    0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a,
  ],
  [
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
  ],
  [
    0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac,
    0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05,
  ],
  [
    0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
  ],
  [
    0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac,
    0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85,
  ],
  [
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  ],
  [
    0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53,
    0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa,
  ],
];

#[cfg(all(
  target_arch = "aarch64",
  any(target_os = "macos", target_os = "linux"),
  not(feature = "portable-only"),
  not(miri)
))]
fn verify_aarch64_encoded_r(
  r_bytes: &[u8; PUBLIC_KEY_LENGTH],
  s_canonical: &[u8; SECRET_KEY_LENGTH],
  neg_challenge_bytes: &[u8; SECRET_KEY_LENGTH],
  public_key: &[u8; PUBLIC_KEY_LENGTH],
) -> Option<Result<(), VerificationError>> {
  if is_small_order_encoded(r_bytes) {
    return Some(Err(VerificationError::new()));
  }

  aarch64_asm::double_scalar_basepoint_encoded(s_canonical, neg_challenge_bytes, public_key).map(|combined| {
    if ct::constant_time_eq(&combined, r_bytes) {
      Ok(())
    } else {
      Err(VerificationError::new())
    }
  })
}

#[cfg(all(
  target_arch = "aarch64",
  any(target_os = "macos", target_os = "linux"),
  not(feature = "portable-only"),
  not(miri)
))]
#[must_use]
fn is_small_order_encoded(bytes: &[u8; PUBLIC_KEY_LENGTH]) -> bool {
  SMALL_ORDER_ENCODINGS
    .iter()
    .any(|small_order| ct::constant_time_eq(bytes, small_order))
}

// Dispatch: AArch64 assembly verify fast path → IFMA → AVX2 → portable

#[cfg(not(all(
  target_arch = "x86_64",
  target_os = "linux",
  not(feature = "portable-only"),
  not(miri)
)))]
pub(crate) use crate::auth::curve25519_edwards::basepoint_mul_dispatch;

#[inline]
#[must_use]
fn public_key_from_scalar(scalar_bytes: &[u8; SECRET_KEY_LENGTH]) -> Ed25519PublicKey {
  #[cfg(all(
    target_arch = "x86_64",
    target_os = "linux",
    not(feature = "portable-only"),
    not(miri)
  ))]
  {
    let (bytes, point) = x86_64_asm::basepoint_mul_public(scalar_bytes);
    Ed25519PublicKey::from_basepoint_mul(bytes, point)
  }

  #[cfg(not(all(
    target_arch = "x86_64",
    target_os = "linux",
    not(feature = "portable-only"),
    not(miri)
  )))]
  {
    let point = basepoint_mul_dispatch(scalar_bytes);
    Ed25519PublicKey::from_basepoint_mul(point.to_bytes().unwrap_or_default(), point)
  }
}

/// Dispatch `[s]B + [-h]A` (Straus double-scalar mul) to the fastest
/// available path.
///
/// Uses wNAF-based Straus on IFMA platforms: wNAF(8) for the basepoint
/// scalar (~28 additions) + wNAF(5) for the public-key scalar (~43
/// additions) = ~71 total, vs ~128 with the old radix-16 approach.
///
/// AVX2 wNAF Straus remains as the fallback for pre-IFMA hardware.
#[must_use]
fn straus_dispatch(s: &[u8; 32], h: &[u8; 32], a: &point::ExtendedPoint) -> point::ExtendedPoint {
  #[cfg(target_arch = "x86_64")]
  {
    use crate::platform::caps::x86;

    let caps = crate::platform::caps();

    // IFMA + wNAF path: ~45% fewer additions than radix-16.
    if caps.has(x86::AVX512IFMA) && caps.has(x86::AVX512VL) && caps.has(x86::AVX2) {
      // SAFETY: AVX-512 IFMA + VL + AVX2 confirmed by runtime detection.
      return unsafe { point_avx2::straus_wnaf_vartime_ifma(s, h, a) };
    }

    // AVX2 wNAF fallback for pre-IFMA hardware.
    if caps.has(x86::AVX2) {
      // SAFETY: AVX2 confirmed by runtime detection.
      return unsafe { point_avx2::straus_wnaf_vartime_avx2(s, h, a) };
    }
  }
  point::straus_wnaf_basepoint_vartime(s, h, a)
}

#[cfg(test)]
mod tests {
  use alloc::format;

  use super::{
    Ed25519Keypair, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature, constants, field, hash, point, scalar, verify,
  };

  #[test]
  fn internal_layout_matches_phase_2b_plan() {
    assert_eq!(constants::FIELD_LIMBS, 5);
    assert_eq!(constants::SCALAR_LIMBS, 4);
    assert_eq!(
      core::mem::size_of::<field::FieldElement>(),
      5 * core::mem::size_of::<u64>()
    );
    assert_eq!(core::mem::size_of::<scalar::Scalar>(), 4 * core::mem::size_of::<u64>());
    assert_eq!(
      core::mem::size_of::<point::ExtendedPoint>(),
      4 * core::mem::size_of::<field::FieldElement>()
    );
  }

  #[test]
  fn secret_key_roundtrips_bytes() {
    let key = Ed25519SecretKey::from_bytes([7u8; Ed25519SecretKey::LENGTH]);
    assert_eq!(*key.as_bytes(), [7u8; Ed25519SecretKey::LENGTH]);
    assert_eq!(key.as_bytes(), &[7u8; Ed25519SecretKey::LENGTH]);
  }

  #[test]
  fn public_key_roundtrips_bytes() {
    let key = Ed25519PublicKey::from_bytes([9u8; Ed25519PublicKey::LENGTH]);
    assert_eq!(key.to_bytes(), [9u8; Ed25519PublicKey::LENGTH]);
    assert_eq!(key.as_bytes(), &[9u8; Ed25519PublicKey::LENGTH]);
  }

  #[test]
  fn signature_roundtrips_bytes() {
    let sig = Ed25519Signature::from_bytes([3u8; Ed25519Signature::LENGTH]);
    assert_eq!(sig.to_bytes(), [3u8; Ed25519Signature::LENGTH]);
    assert_eq!(sig.as_bytes(), &[3u8; Ed25519Signature::LENGTH]);
  }

  #[test]
  fn secret_key_debug_is_redacted() {
    let dbg = format!("{:?}", Ed25519SecretKey::from_bytes([0u8; 32]));
    assert_eq!(dbg, "Ed25519SecretKey(****)");
  }

  #[test]
  fn keypair_equality_requires_matching_secret_and_public_halves() {
    let first = Ed25519Keypair::from_secret_key(Ed25519SecretKey::from_bytes([1u8; Ed25519SecretKey::LENGTH]));
    let second = Ed25519Keypair::from_secret_key(Ed25519SecretKey::from_bytes([2u8; Ed25519SecretKey::LENGTH]));

    let mut secret_mismatch = first.clone();
    secret_mismatch.secret = second.secret.clone();
    let mut public_mismatch = first.clone();
    public_mismatch.public = second.public;

    assert_eq!(first, first);
    assert_ne!(first, secret_mismatch);
    assert_ne!(first, public_mismatch);
    assert_ne!(first, second);
  }

  #[test]
  fn secret_key_expansion_produces_distinct_scalar_and_nonce_material() {
    let secret = Ed25519SecretKey::from_bytes([5u8; Ed25519SecretKey::LENGTH]);
    let expanded = hash::ExpandedSecret::from_secret_key(&secret);

    assert_ne!(expanded.scalar_bytes(), expanded.nonce_prefix());
  }

  fn decode_hex<const N: usize>(hex: &str) -> [u8; N] {
    let bytes = hex.as_bytes();
    let mut out = [0u8; N];

    for (dst, chunk) in out.iter_mut().zip(bytes.chunks_exact(2)) {
      *dst = match chunk[0] {
        b'0'..=b'9' => chunk[0] - b'0',
        b'a'..=b'f' => chunk[0] - b'a' + 10,
        b'A'..=b'F' => chunk[0] - b'A' + 10,
        _ => panic!("invalid hex"),
      } << 4
        | match chunk[1] {
          b'0'..=b'9' => chunk[1] - b'0',
          b'a'..=b'f' => chunk[1] - b'a' + 10,
          b'A'..=b'F' => chunk[1] - b'A' + 10,
          _ => panic!("invalid hex"),
        };
    }

    out
  }

  #[test]
  fn secret_key_derives_public_key_from_rfc8032_vector_1() {
    let secret = Ed25519SecretKey::from_bytes(decode_hex(
      "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
    ));
    let expected = Ed25519PublicKey::from_bytes(decode_hex(
      "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
    ));

    assert_eq!(secret.public_key(), expected);
  }

  #[test]
  fn keypair_signs_and_public_key_verifies() {
    let secret = Ed25519SecretKey::from_bytes([0x55; Ed25519SecretKey::LENGTH]);
    let keypair = Ed25519Keypair::from_secret_key(secret);
    let message = b"rscrypto-ed25519";
    let signature = keypair.sign(message);

    assert!(keypair.public_key().verify(message, &signature).is_ok());
    assert!(verify(message, &keypair.public_key(), &signature).is_ok());
  }

  #[test]
  fn signing_matches_rfc8032_vector_1() {
    let secret = Ed25519SecretKey::from_bytes(decode_hex(
      "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
    ));
    let keypair = Ed25519Keypair::from_secret_key(secret);
    let signature = keypair.sign(b"");
    let expected = Ed25519Signature::from_bytes(decode_hex(
      "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155\
       5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
    ));

    assert_eq!(signature, expected);
  }

  #[test]
  fn verify_rejects_modified_message() {
    let secret = Ed25519SecretKey::from_bytes([0x77; Ed25519SecretKey::LENGTH]);
    let keypair = Ed25519Keypair::from_secret_key(secret);
    let signature = keypair.sign(b"message");

    assert!(keypair.public_key().verify(b"message!", &signature).is_err());
  }

  #[test]
  fn verify_rejects_noncanonical_s_component() {
    let public = Ed25519PublicKey::from_bytes(decode_hex(
      "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
    ));
    let signature = Ed25519Signature::from_bytes(decode_hex(
      "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155\
       edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010",
    ));

    assert!(public.verify(b"", &signature).is_err());
  }

  #[test]
  fn verify_rejects_small_order_public_key() {
    let secret = Ed25519SecretKey::from_bytes([0x33; Ed25519SecretKey::LENGTH]);
    let keypair = Ed25519Keypair::from_secret_key(secret);
    let signature = keypair.sign(b"strict verify");
    let public = Ed25519PublicKey::from_bytes([
      1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    ]);

    assert!(public.is_small_order());
    assert!(public.verify(b"strict verify", &signature).is_err());
  }

  #[test]
  fn verify_rejects_small_order_r_component() {
    let secret = Ed25519SecretKey::from_bytes([0x44; Ed25519SecretKey::LENGTH]);
    let keypair = Ed25519Keypair::from_secret_key(secret);
    let mut signature_bytes = keypair.sign(b"strict verify").to_bytes();
    signature_bytes[..32].copy_from_slice(&[
      1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    ]);
    let signature = Ed25519Signature::from_bytes(signature_bytes);

    assert!(keypair.public_key().verify(b"strict verify", &signature).is_err());
  }

  #[test]
  fn challenge_hash_matches_streaming_sha512_at_boundaries() {
    use crate::{hashes::crypto::Sha512, traits::Digest};

    let r = [0x31u8; Ed25519Signature::LENGTH / 2];
    let public = [0xA7u8; Ed25519PublicKey::LENGTH];
    let long_message = [0x5Cu8; 257];
    let messages: [&[u8]; 9] = [
      b"",
      b"a",
      &[0x11; 47],
      &[0x22; 48],
      &[0x33; 49],
      &[0x44; 63],
      &[0x55; 64],
      &[0x66; 65],
      &long_message,
    ];

    for message in messages {
      let mut streaming = Sha512::new();
      streaming.update(&r);
      streaming.update(&public);
      streaming.update(message);

      assert_eq!(super::hash_challenge(&r, &public, message), streaming.finalize());
    }
  }

  #[cfg(all(
    target_arch = "aarch64",
    any(target_os = "macos", target_os = "linux"),
    not(feature = "portable-only"),
    not(miri)
  ))]
  #[test]
  fn aarch64_small_order_encoding_table_matches_point_detection() {
    for encoded in super::SMALL_ORDER_ENCODINGS {
      assert!(super::is_small_order_encoded(&encoded));
      assert!(point::ExtendedPoint::from_bytes(&encoded).is_some_and(|point| point.is_small_order()));
    }
  }

  #[cfg(all(
    target_arch = "aarch64",
    any(target_os = "macos", target_os = "linux"),
    not(feature = "portable-only"),
    not(miri)
  ))]
  #[test]
  fn aarch64_verify_rejects_all_small_order_r_encodings() {
    let secret = Ed25519SecretKey::from_bytes([0x46; Ed25519SecretKey::LENGTH]);
    let keypair = Ed25519Keypair::from_secret_key(secret);

    for encoded in super::SMALL_ORDER_ENCODINGS {
      let mut signature_bytes = keypair.sign(b"strict verify").to_bytes();
      signature_bytes[..32].copy_from_slice(&encoded);
      let signature = Ed25519Signature::from_bytes(signature_bytes);

      assert!(keypair.public_key().verify(b"strict verify", &signature).is_err());
    }
  }

  #[cfg(any(
    all(
      target_arch = "aarch64",
      any(target_os = "macos", target_os = "linux"),
      not(feature = "portable-only"),
      not(miri)
    ),
    all(
      target_arch = "x86_64",
      target_os = "linux",
      not(feature = "portable-only"),
      not(miri)
    )
  ))]
  #[test]
  fn asm_basepoint_mul_encoded_matches_portable() {
    use crate::hashes::crypto::Sha512;

    let mut one = [0u8; Ed25519SecretKey::LENGTH];
    one[0] = 1;
    let digest = Sha512::digest(b"rscrypto-ed25519-signing-nonce-test");
    let reduced = scalar::to_bytes(&scalar::reduce_bytes_mod_order(&digest));

    for scalar_bytes in [[0u8; Ed25519SecretKey::LENGTH], one, reduced] {
      let portable = point::ExtendedPoint::scalar_mul_basepoint(&scalar_bytes)
        .to_bytes()
        .unwrap();
      let asm = super::basepoint_mul_encoded_dispatch(&scalar_bytes);

      assert_eq!(asm, portable);

      #[cfg(all(
        target_arch = "x86_64",
        target_os = "linux",
        not(feature = "portable-only"),
        not(miri)
      ))]
      {
        let (asm_public, asm_point) = super::x86_64_asm::basepoint_mul_public(&scalar_bytes);
        assert_eq!(asm_public, portable);
        assert_eq!(asm_point.to_bytes().unwrap(), portable);
      }
    }
  }

  #[cfg(all(
    target_arch = "aarch64",
    any(target_os = "macos", target_os = "linux"),
    not(feature = "portable-only"),
    not(miri)
  ))]
  #[test]
  fn aarch64_double_scalar_matches_portable_verify_core() {
    let secret = Ed25519SecretKey::from_bytes([0x91; Ed25519SecretKey::LENGTH]);
    let keypair = Ed25519Keypair::from_secret_key(secret);
    let long_message = [0xA5u8; 257];
    let messages: [&[u8]; 4] = [b"", b"a", b"rscrypto-ed25519-aarch64", &long_message];

    for message in messages {
      let public = keypair.public_key();
      let signature = keypair.sign(message);
      let (r_bytes, s_bytes) = super::split_signature(&signature);
      let a_point = public.point().unwrap();
      let s = scalar::from_canonical_bytes(&s_bytes).unwrap();

      let challenge_digest = super::hash_challenge(&r_bytes, public.as_bytes(), message);
      let challenge = scalar::reduce_bytes_mod_order(&challenge_digest);
      let neg_challenge = scalar::negate_mod(&challenge);
      let neg_challenge_bytes = scalar::to_bytes(&neg_challenge);
      let s_canonical = scalar::to_bytes(&s);

      let portable = point::straus_wnaf_basepoint_vartime(&s_canonical, &neg_challenge_bytes, &a_point)
        .to_bytes()
        .unwrap();
      let asm =
        super::aarch64_asm::double_scalar_basepoint_encoded(&s_canonical, &neg_challenge_bytes, public.as_bytes())
          .unwrap();

      assert_eq!(asm, portable);
      assert_eq!(asm, r_bytes);
    }
  }

  #[cfg(all(
    target_arch = "aarch64",
    any(target_os = "macos", target_os = "linux"),
    not(feature = "portable-only"),
    not(miri)
  ))]
  #[test]
  fn aarch64_verify_backend_rejects_invalid_public_encoding() {
    let s = [0u8; Ed25519SecretKey::LENGTH];
    let h = [0u8; Ed25519SecretKey::LENGTH];
    let invalid_public_key = [0xffu8; Ed25519PublicKey::LENGTH];

    assert!(super::aarch64_asm::double_scalar_basepoint_encoded(&s, &h, &invalid_public_key).is_none());
  }
}
