//! EdDSA digital signatures (RFC 8032).
//!
//! # Algorithms
//!
//! - **Ed25519** — Edwards curve over `GF(2^255 - 19)` (pure +
//!   `Ed25519ctx` + `Ed25519ph`).
//! - **Ed448** — planned (RFC 8032 Appendix A port pending).
//!
//! This module implements `Fe25519` field arithmetic, extended-
//! coordinate point operations on the twisted Edwards form, and
//! the full sign / verify protocol for Ed25519.
//!
//! # Side-channel posture
//!
//! Per `arcana/doc/sca/countermeasures/eddsa.rst`:
//!
//! | Threat                                    | Status     | Roadmap item                                                            |
//! |-------------------------------------------|------------|-------------------------------------------------------------------------|
//! | SPA on scalar mul                         | partial    | `T1-F` — audit pass mirroring Weierstrass commit `76191c1`              |
//! | DPA on scalar mul                         | vulnerable | `T2-A` (Z-rerandomization, shared with Weierstrass plan)                |
//! | **Single-fault on RFC 8032 deterministic**| vulnerable | `T1-D` — hedged Ed25519 (CFRG `det-sigs-with-noise`, Romailler 2017)    |
//! | Template attacks (Samwel et al. 2018)     | vulnerable | `T2-A` + `T2-B` (Z-rerand + scalar blinding)                            |
//! | DPA on intermediate SHA-512 keyed digest  | partial    | `T2-D` — masked SHA-512, shared with HMAC consumer                      |
//!
//! ## Romailler-Pelissier 2017 — single-fault key recovery
//!
//! RFC 8032 derives the per-signature nonce `r` deterministically
//! from `(seed, message)`. Two signatures of the same message
//! produce the same `r`, which is fine cryptographically but
//! **fragile under fault** (FDTC 2017,
//! `romailler2017eddsa_fault`):
//!
//! ```text
//!   Sign M  →  (R, s)         (normal)
//!   Sign M  →  (R', s')       (one fault during SHA-512 → r' ≠ r)
//!
//!   k  = H(R  ‖ A ‖ M),   s  = r  + k * a mod ℓ
//!   k' = H(R' ‖ A ‖ M),   s' = r' + k' * a mod ℓ
//!
//!   →  a = (s - s') / (k - k') mod ℓ
//! ```
//!
//! One well-placed fault recovers the whole secret scalar `a`.
//! The standard fix is **hedged signing**: derive `r` from
//! `H(prefix ‖ ρ ‖ M)` with `ρ` 32 bytes of fresh randomness;
//! deterministic mode (`ρ = 0^32`) stays available for KAT
//! determinism. Roadmap item `T1-D`.
//!
//! # Zeroize-on-Drop
//!
//! [`Ed25519SecretKey`] currently does **not** implement `Drop`
//! with `silentops::ct_zeroize`. Roadmap item `T2-E`.

use crate::Hasher;
use crate::hash::sha512::Sha512;

// ============================================================
// Field arithmetic for GF(2^255 - 19)
// ============================================================

/// Element of GF(2^255 - 19), stored as four 64-bit limbs in little-endian order.
#[derive(Clone, Copy, Debug)]
struct Fe25519([u64; 4]);

/// p = 2^255 - 19
const P: [u64; 4] = [
    0xFFFF_FFFF_FFFF_FFED,
    0xFFFF_FFFF_FFFF_FFFF,
    0xFFFF_FFFF_FFFF_FFFF,
    0x7FFF_FFFF_FFFF_FFFF,
];

impl Fe25519 {
    const ZERO: Fe25519 = Fe25519([0, 0, 0, 0]);
    const ONE: Fe25519 = Fe25519([1, 0, 0, 0]);

    /// Reduce a 4-limb value mod p.  The input must be < 2*p.
    fn reduce(mut limbs: [u64; 4]) -> Self {
        // Try subtracting p; if it underflows, keep the original.
        let (r0, borrow) = limbs[0].overflowing_sub(P[0]);
        let (r1, borrow) = sbb(limbs[1], P[1], borrow);
        let (r2, borrow) = sbb(limbs[2], P[2], borrow);
        let (r3, borrow) = sbb(limbs[3], P[3], borrow);

        // If borrow, the subtraction underflowed => limbs < p, keep limbs.
        let mask = if borrow { u64::MAX } else { 0 };
        limbs[0] = (limbs[0] & mask) | (r0 & !mask);
        limbs[1] = (limbs[1] & mask) | (r1 & !mask);
        limbs[2] = (limbs[2] & mask) | (r2 & !mask);
        limbs[3] = (limbs[3] & mask) | (r3 & !mask);

        Fe25519(limbs)
    }

    fn add(self, rhs: Self) -> Self {
        let (r0, carry) = self.0[0].overflowing_add(rhs.0[0]);
        let (r1, carry) = adc(self.0[1], rhs.0[1], carry);
        let (r2, carry) = adc(self.0[2], rhs.0[2], carry);
        let (r3, _carry) = adc(self.0[3], rhs.0[3], carry);
        Fe25519::reduce([r0, r1, r2, r3])
    }

    fn sub(self, rhs: Self) -> Self {
        let (r0, borrow) = self.0[0].overflowing_sub(rhs.0[0]);
        let (r1, borrow) = sbb(self.0[1], rhs.0[1], borrow);
        let (r2, borrow) = sbb(self.0[2], rhs.0[2], borrow);
        let (r3, borrow) = sbb(self.0[3], rhs.0[3], borrow);

        // If borrow, add p.
        let mask = if borrow { u64::MAX } else { 0 };
        let (r0, carry) = r0.overflowing_add(P[0] & mask);
        let (r1, carry) = adc(r1, P[1] & mask, carry);
        let (r2, carry) = adc(r2, P[2] & mask, carry);
        let (r3, _) = adc(r3, P[3] & mask, carry);

        Fe25519([r0, r1, r2, r3])
    }

    fn neg(self) -> Self {
        Fe25519::ZERO.sub(self)
    }

    /// Multiply two field elements: schoolbook 256x256 -> 512, then reduce mod p.
    fn mul(self, rhs: Self) -> Self {
        let wide = mul256(self.0, rhs.0);
        fe_reduce_wide(wide)
    }

    fn square(self) -> Self {
        self.mul(self)
    }

    /// Compute self^exp mod p via square-and-multiply.
    fn pow(self, exp: [u64; 4]) -> Self {
        let mut result = Fe25519::ONE;
        let mut base = self;
        for i in 0..4 {
            let mut e = exp[i];
            for _ in 0..64 {
                if e & 1 == 1 {
                    result = result.mul(base);
                }
                base = base.square();
                e >>= 1;
            }
        }
        result
    }

    /// Modular inverse via Fermat's little theorem: a^(p-2) mod p.
    fn inv(self) -> Self {
        // p - 2 = 2^255 - 21
        let pm2: [u64; 4] = [
            0xFFFF_FFFF_FFFF_FFEB,
            0xFFFF_FFFF_FFFF_FFFF,
            0xFFFF_FFFF_FFFF_FFFF,
            0x7FFF_FFFF_FFFF_FFFF,
        ];
        self.pow(pm2)
    }

    /// Constant-time equality (on fully reduced values).
    fn equals(self, other: Self) -> bool {
        let a = Fe25519::reduce(self.0);
        let b = Fe25519::reduce(other.0);
        let mut acc = 0u64;
        for i in 0..4 {
            acc |= a.0[i] ^ b.0[i];
        }
        acc == 0
    }

    fn is_zero(self) -> bool {
        self.equals(Fe25519::ZERO)
    }

    /// Encode to 32 bytes, little-endian.
    fn to_bytes(self) -> [u8; 32] {
        let r = Fe25519::reduce(self.0);
        let mut out = [0u8; 32];
        out[0..8].copy_from_slice(&r.0[0].to_le_bytes());
        out[8..16].copy_from_slice(&r.0[1].to_le_bytes());
        out[16..24].copy_from_slice(&r.0[2].to_le_bytes());
        out[24..32].copy_from_slice(&r.0[3].to_le_bytes());
        out
    }

    /// Decode from 32 bytes, little-endian.  Clears bit 255.
    fn from_bytes(bytes: &[u8; 32]) -> Self {
        let mut limbs = [0u64; 4];
        limbs[0] = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
        limbs[1] = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
        limbs[2] = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
        limbs[3] = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
        limbs[3] &= 0x7FFF_FFFF_FFFF_FFFF;
        Fe25519::reduce(limbs)
    }

    fn from_u64(v: u64) -> Self {
        Fe25519([v, 0, 0, 0])
    }

    /// Return 1 if the fully-reduced value is odd, 0 otherwise.
    fn is_odd(self) -> u8 {
        let r = Fe25519::reduce(self.0);
        (r.0[0] & 1) as u8
    }

    /// Constant-time conditional select: choice=0 -> a, choice=1 -> b.
    fn ct_select(a: Self, b: Self, choice: u8) -> Self {
        let mask = (choice as u64).wrapping_neg();
        Fe25519([
            a.0[0] ^ (mask & (a.0[0] ^ b.0[0])),
            a.0[1] ^ (mask & (a.0[1] ^ b.0[1])),
            a.0[2] ^ (mask & (a.0[2] ^ b.0[2])),
            a.0[3] ^ (mask & (a.0[3] ^ b.0[3])),
        ])
    }
}

// ---- Low-level helpers ----

#[inline(always)]
fn adc(a: u64, b: u64, carry_in: bool) -> (u64, bool) {
    let (s1, c1) = a.overflowing_add(b);
    let (s2, c2) = s1.overflowing_add(carry_in as u64);
    (s2, c1 | c2)
}

#[inline(always)]
fn sbb(a: u64, b: u64, borrow_in: bool) -> (u64, bool) {
    let (s1, b1) = a.overflowing_sub(b);
    let (s2, b2) = s1.overflowing_sub(borrow_in as u64);
    (s2, b1 | b2)
}

#[inline(always)]
fn mul64(a: u64, b: u64) -> u128 {
    (a as u128) * (b as u128)
}

/// Schoolbook 256x256 -> 512 bit multiply.
fn mul256(a: [u64; 4], b: [u64; 4]) -> [u64; 8] {
    let mut r = [0u64; 8];
    for i in 0..4 {
        let mut carry = 0u64;
        for j in 0..4 {
            let uv = mul64(a[i], b[j]) + r[i + j] as u128 + carry as u128;
            r[i + j] = uv as u64;
            carry = (uv >> 64) as u64;
        }
        r[i + 4] = carry;
    }
    r
}

/// Reduce a 512-bit result mod p = 2^255 - 19.
///
/// Since 2^255 = 19 (mod p), we split at bit 255 and fold with factor 19.
fn fe_reduce_wide(w: [u64; 8]) -> Fe25519 {
    // Extract bit 255 from w[3].
    let low3_top_bit = (w[3] >> 63) & 1;
    let low = [w[0], w[1], w[2], w[3] & 0x7FFF_FFFF_FFFF_FFFF];

    // high = bits [255..512) of w, shifted down.
    let high = [
        (w[4] << 1) | low3_top_bit,
        (w[5] << 1) | (w[4] >> 63),
        (w[6] << 1) | (w[5] >> 63),
        (w[7] << 1) | (w[6] >> 63),
        w[7] >> 63,
    ];

    // w = low + high * 2^255 = low + 19 * high (mod p).
    let mut acc = [0u64; 5];
    let mut carry = 0u128;
    for i in 0..5 {
        carry += low.get(i).copied().unwrap_or(0) as u128 + 19u128 * high[i] as u128;
        acc[i] = carry as u64;
        carry >>= 64;
    }

    // Split at bit 255 again.
    let top_bit = (acc[3] >> 63) & 1;
    acc[3] &= 0x7FFF_FFFF_FFFF_FFFF;
    let extra = (acc[4] << 1) | top_bit;

    // Add 19 * extra to lower 255 bits.
    let add = extra as u128 * 19;
    let (r0, c) = acc[0].overflowing_add(add as u64);
    let (r1, c) = adc(acc[1], (add >> 64) as u64, c);
    let (r2, c) = adc(acc[2], 0, c);
    let (r3, _) = adc(acc[3], 0, c);

    Fe25519::reduce([r0, r1, r2, r3])
}

// ============================================================
// Scalar arithmetic mod L (group order)
// ============================================================

/// L = 2^252 + 27742317777372353535851937790883648493
const L: [u64; 4] = [
    0x5812631A5CF5D3ED,
    0x14DEF9DEA2F79CD6,
    0x0000000000000000,
    0x1000000000000000,
];

/// Reduce a 64-byte (512-bit) little-endian value mod L.
fn sc_reduce(input: &[u8; 64]) -> [u8; 32] {
    let mut a = [0u64; 8];
    for i in 0..8 {
        a[i] = u64::from_le_bytes(input[i * 8..(i + 1) * 8].try_into().unwrap());
    }

    let result = bn_mod(&a, 8);

    let mut out = [0u8; 32];
    for i in 0..4 {
        out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
    }
    out
}

/// Add two scalars mod L.  Both inputs must already be < L.
fn sc_add(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
    let mut al = [0u64; 4];
    let mut bl = [0u64; 4];
    for i in 0..4 {
        al[i] = u64::from_le_bytes(a[i * 8..(i + 1) * 8].try_into().unwrap());
        bl[i] = u64::from_le_bytes(b[i * 8..(i + 1) * 8].try_into().unwrap());
    }

    let (r0, carry) = al[0].overflowing_add(bl[0]);
    let (r1, carry) = adc(al[1], bl[1], carry);
    let (r2, carry) = adc(al[2], bl[2], carry);
    let (r3, carry) = adc(al[3], bl[3], carry);

    let mut result = [r0, r1, r2, r3];

    // Subtract L if result >= L (carry set, or no borrow on subtraction).
    let (s0, borrow) = result[0].overflowing_sub(L[0]);
    let (s1, borrow) = sbb(result[1], L[1], borrow);
    let (s2, borrow) = sbb(result[2], L[2], borrow);
    let (s3, borrow) = sbb(result[3], L[3], borrow);

    let use_sub = carry | !borrow;
    let mask = if use_sub { 0u64 } else { u64::MAX };
    result[0] = (result[0] & mask) | (s0 & !mask);
    result[1] = (result[1] & mask) | (s1 & !mask);
    result[2] = (result[2] & mask) | (s2 & !mask);
    result[3] = (result[3] & mask) | (s3 & !mask);

    let mut out = [0u8; 32];
    for i in 0..4 {
        out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
    }
    out
}

/// Multiply two 256-bit scalars mod L.
fn sc_mul(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
    let mut al = [0u64; 4];
    let mut bl = [0u64; 4];
    for i in 0..4 {
        al[i] = u64::from_le_bytes(a[i * 8..(i + 1) * 8].try_into().unwrap());
        bl[i] = u64::from_le_bytes(b[i * 8..(i + 1) * 8].try_into().unwrap());
    }

    let wide = mul256(al, bl);
    let result = bn_mod(&wide, 8);

    let mut out = [0u8; 32];
    for i in 0..4 {
        out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
    }
    out
}

/// Big-number modular reduction: compute a[0..limbs] mod L via bit-by-bit
/// shift-and-subtract.  Simple but obviously correct.
fn bn_mod(a: &[u64], limbs: usize) -> [u64; 4] {
    // Working register: 5 limbs (320 bits) is enough since L is 253 bits and
    // we only ever hold a value < 2*L during the loop.
    let mut r = [0u64; 5];

    // Process input bits from MSB down to LSB.
    for word_idx in (0..limbs).rev() {
        for bit_idx in (0..64).rev() {
            // Shift r left by 1.
            let mut carry = 0u64;
            for i in 0..5 {
                let new_carry = r[i] >> 63;
                r[i] = (r[i] << 1) | carry;
                carry = new_carry;
            }
            // Bring in the next bit from a.
            r[0] |= (a[word_idx] >> bit_idx) & 1;

            // If r >= L, subtract L.
            let (s0, b) = r[0].overflowing_sub(L[0]);
            let (s1, b) = sbb(r[1], L[1], b);
            let (s2, b) = sbb(r[2], L[2], b);
            let (s3, b) = sbb(r[3], L[3], b);
            let (s4, b) = sbb(r[4], 0, b);
            if !b {
                r[0] = s0;
                r[1] = s1;
                r[2] = s2;
                r[3] = s3;
                r[4] = s4;
            }
        }
    }

    [r[0], r[1], r[2], r[3]]
}

// ============================================================
// Extended Edwards point (X : Y : Z : T) with T = X*Y/Z
// ============================================================

/// Point on the Ed25519 curve in extended coordinates.
#[derive(Clone, Copy, Debug)]
struct ExtPoint {
    x: Fe25519,
    y: Fe25519,
    z: Fe25519,
    t: Fe25519,
}

/// d = -121665/121666 mod p
/// = 37095705934669439343138083508754565189542113879843219016388785533085940283555
const D: Fe25519 = Fe25519([
    0x75EB4DCA135978A3,
    0x00700A4D4141D8AB,
    0x8CC740797779E898,
    0x52036CEE2B6FFE73,
]);

/// 2*d mod p (kept for reference/tests).
#[cfg(test)]
const D2: Fe25519 = Fe25519([
    0xEBD69B9426B2F159,
    0x00E0149A8283B156,
    0x198E80F2EEF3D130,
    0x2406D9DC56DFFCE7,
]);

/// Base point x-coordinate.
/// Bx = 15112221349535807912866137220509078750507884956996801397906370244768422529236
const B_X: Fe25519 = Fe25519([
    0xC9562D608F25D51A,
    0x692CC7609525A7B2,
    0xC0A4E231FDD6DC5C,
    0x216936D3CD6E53FE,
]);

/// Base point y-coordinate.
/// By = 46316835694926478169428394003475163141307993866256225615783033890098355573289
const B_Y: Fe25519 = Fe25519([
    0x6666666666666658,
    0x6666666666666666,
    0x6666666666666666,
    0x6666666666666666,
]);

impl ExtPoint {
    const IDENTITY: ExtPoint = ExtPoint {
        x: Fe25519::ZERO,
        y: Fe25519::ONE,
        z: Fe25519::ONE,
        t: Fe25519::ZERO,
    };

    fn from_affine(x: Fe25519, y: Fe25519) -> Self {
        ExtPoint {
            x,
            y,
            z: Fe25519::ONE,
            t: x.mul(y),
        }
    }

    fn basepoint() -> Self {
        ExtPoint::from_affine(B_X, B_Y)
    }

    /// Point doubling using the unified doubling formula for twisted Edwards
    /// curves with a = -1.  Reference: HHCD08 (Hisil et al.), Section 4.
    fn double(self) -> Self {
        let a = self.x.square();
        let b = self.y.square();
        let c = self.z.square().add(self.z.square()); // 2*Z^2
        let d = a.neg(); // -X^2  (a = -1 in the twisted Edwards equation)
        let e = self.x.add(self.y).square().sub(a).sub(b);
        let g = d.add(b);
        let f = g.sub(c);
        let h = d.sub(b);

        ExtPoint {
            x: e.mul(f),
            y: g.mul(h),
            t: e.mul(h),
            z: f.mul(g),
        }
    }

    /// Point addition using the unified addition formula for twisted Edwards
    /// curves with a = -1.  Reference: HHCD08, Section 3.1.
    fn add(self, other: Self) -> Self {
        let a = self.x.mul(other.x);
        let b = self.y.mul(other.y);
        let c = self.t.mul(other.t).mul(D);
        let dd = self.z.mul(other.z);

        let e = (self.x.add(self.y)).mul(other.x.add(other.y)).sub(a).sub(b);
        let f = dd.sub(c);
        let g = dd.add(c);
        let h = b.add(a); // b - (-1)*a = b + a, since curve coeff a = -1

        ExtPoint {
            x: e.mul(f),
            y: g.mul(h),
            t: e.mul(h),
            z: f.mul(g),
        }
    }

    /// Constant-time scalar multiplication (left-to-right double-and-add with
    /// conditional select on every bit).
    fn scalar_mul(self, scalar: &[u8; 32]) -> Self {
        let mut result = ExtPoint::IDENTITY;
        for byte_idx in (0..32).rev() {
            for bit_idx in (0..8).rev() {
                result = result.double();
                let bit = (scalar[byte_idx] >> bit_idx) & 1;
                let candidate = result.add(self);
                result = ExtPoint::ct_select(result, candidate, bit);
            }
        }
        result
    }

    fn ct_select(a: Self, b: Self, choice: u8) -> Self {
        ExtPoint {
            x: Fe25519::ct_select(a.x, b.x, choice),
            y: Fe25519::ct_select(a.y, b.y, choice),
            z: Fe25519::ct_select(a.z, b.z, choice),
            t: Fe25519::ct_select(a.t, b.t, choice),
        }
    }

    /// Compress a point to 32 bytes (RFC 8032 encoding):
    /// encode y in little-endian, put the sign of x into the top bit.
    fn compress(self) -> [u8; 32] {
        let z_inv = self.z.inv();
        let x = self.x.mul(z_inv);
        let y = self.y.mul(z_inv);
        let mut bytes = y.to_bytes();
        bytes[31] |= x.is_odd() << 7;
        bytes
    }

    /// Decompress a 32-byte encoded point.
    fn decompress(bytes: &[u8; 32]) -> Option<Self> {
        let x_sign = (bytes[31] >> 7) & 1;
        let mut y_bytes = *bytes;
        y_bytes[31] &= 0x7F;
        let y = Fe25519::from_bytes(&y_bytes);

        // x^2 = (y^2 - 1) / (d * y^2 + 1)
        let y2 = y.square();
        let u = y2.sub(Fe25519::ONE); // numerator
        let v = D.mul(y2).add(Fe25519::ONE); // denominator

        // Compute candidate: x = u * v^3 * (u * v^7)^((p-5)/8)
        let v3 = v.square().mul(v);
        let v7 = v3.square().mul(v);
        let uv7 = u.mul(v7);

        // (p-5)/8 = (2^255 - 24) / 8 = 2^252 - 3
        let exp: [u64; 4] = [
            0xFFFF_FFFF_FFFF_FFFD,
            0xFFFF_FFFF_FFFF_FFFF,
            0xFFFF_FFFF_FFFF_FFFF,
            0x0FFF_FFFF_FFFF_FFFF,
        ];
        let uv7_pow = uv7.pow(exp);
        let mut x = u.mul(v3).mul(uv7_pow);

        // Verify: v * x^2 should equal u.
        let check = v.mul(x.square());
        if check.equals(u) {
            // ok
        } else if check.equals(u.neg()) {
            // Multiply x by sqrt(-1) = 2^((p-1)/4).
            let sqrt_m1 = compute_sqrt_m1();
            x = x.mul(sqrt_m1);
        } else {
            return None;
        }

        if x.is_zero() && x_sign == 1 {
            return None;
        }

        if x.is_odd() != x_sign {
            x = x.neg();
        }

        let t = x.mul(y);
        Some(ExtPoint {
            x,
            y,
            z: Fe25519::ONE,
            t,
        })
    }

    /// Check projective equality: X1*Z2 == X2*Z1 and Y1*Z2 == Y2*Z1.
    fn equals(self, other: Self) -> bool {
        let lhs_x = self.x.mul(other.z);
        let rhs_x = other.x.mul(self.z);
        let lhs_y = self.y.mul(other.z);
        let rhs_y = other.y.mul(self.z);
        lhs_x.equals(rhs_x) && lhs_y.equals(rhs_y)
    }
}

/// Compute sqrt(-1) mod p = 2^((p-1)/4).
fn compute_sqrt_m1() -> Fe25519 {
    // (p-1)/4 = (2^255 - 20)/4 = 2^253 - 5
    let exp: [u64; 4] = [
        0xFFFF_FFFF_FFFF_FFFB,
        0xFFFF_FFFF_FFFF_FFFF,
        0xFFFF_FFFF_FFFF_FFFF,
        0x1FFF_FFFF_FFFF_FFFF,
    ];
    Fe25519::from_u64(2).pow(exp)
}

// ============================================================
// Ed25519 public API
// ============================================================

/// Ed25519 public key (32 bytes, compressed point encoding).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ed25519PublicKey(pub [u8; 32]);

/// Ed25519 secret key (the original 32-byte seed).
#[derive(Clone)]
pub struct Ed25519SecretKey(pub [u8; 32]);

/// Ed25519 signature (64 bytes: R || S).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ed25519Signature(pub [u8; 64]);

/// Generate an Ed25519 key pair from a 32-byte secret seed.
///
/// Returns (public_key, secret_key).
pub fn ed25519_keygen(secret: &[u8; 32]) -> (Ed25519PublicKey, Ed25519SecretKey) {
    let hash = Sha512::hash(secret);

    let mut a = [0u8; 32];
    a.copy_from_slice(&hash[..32]);
    a[0] &= 248;
    a[31] &= 127;
    a[31] |= 64;

    let big_a = ExtPoint::basepoint().scalar_mul(&a);
    let pk_bytes = big_a.compress();

    (Ed25519PublicKey(pk_bytes), Ed25519SecretKey(*secret))
}

// ----------------------------------------------------------------------------
// Generic sign / verify core (RFC 8032 §5.1)
//
// The three Ed25519 variants (pure, ctx, ph) all share the same algebraic
// structure -- they differ only in two places:
//
//  1. Whether a `dom2(F, C)` prefix is prepended to the SHA-512 inputs.
//     (None for pure Ed25519, Some for ctx and ph.)
//  2. Whether the "message" bytes fed to the two SHA-512 calls are the
//     raw application message (pure / ctx) or a 64-byte SHA-512 digest
//     of it (ph). The implementation is the same either way: the caller
//     is responsible for hashing first when in ph mode and just passes
//     a 64-byte slice as `message_or_hash`.
//
// Both helpers are kept private to the module: the public surface is the
// six wrappers below (sign / verify / ctx_sign / ctx_verify / ph_sign /
// ph_verify) which give each variant a clear, unambiguous name.
// ----------------------------------------------------------------------------

/// Update a SHA-512 hasher with the optional `dom2` domain-separation
/// prefix (RFC 8032 §5.1.6). For pure Ed25519 (`dom2 == None`) this is
/// a no-op, preserving byte-for-byte the existing test vectors.
fn update_dom2(hasher: &mut Sha512, dom2: Option<&[u8]>) {
    if let Some(prefix) = dom2 {
        hasher.update(prefix);
    }
}

/// Generic Ed25519 sign core. Used by all three variants.
fn ed25519_sign_internal(sk: &Ed25519SecretKey, message_or_hash: &[u8], dom2: Option<&[u8]>) -> Ed25519Signature {
    let hash = Sha512::hash(&sk.0);

    let mut a = [0u8; 32];
    a.copy_from_slice(&hash[..32]);
    a[0] &= 248;
    a[31] &= 127;
    a[31] |= 64;

    let prefix = &hash[32..64];

    // Public key A = a * B.
    let big_a = ExtPoint::basepoint().scalar_mul(&a);
    let pk_bytes = big_a.compress();

    // r = SHA-512(dom2 || prefix || M) mod L  -- M is `message_or_hash`.
    let mut hasher = Sha512::new();
    update_dom2(&mut hasher, dom2);
    hasher.update(prefix);
    hasher.update(message_or_hash);
    let r_hash = hasher.finalize();
    let mut r_wide = [0u8; 64];
    r_wide.copy_from_slice(&r_hash);
    let r_scalar = sc_reduce(&r_wide);

    // R = r * B.
    let big_r = ExtPoint::basepoint().scalar_mul(&r_scalar);
    let r_bytes = big_r.compress();

    // S = (r + SHA-512(dom2 || R || A || M) * a) mod L.
    let mut hasher = Sha512::new();
    update_dom2(&mut hasher, dom2);
    hasher.update(&r_bytes);
    hasher.update(&pk_bytes);
    hasher.update(message_or_hash);
    let k_hash = hasher.finalize();
    let mut k_wide = [0u8; 64];
    k_wide.copy_from_slice(&k_hash);
    let k = sc_reduce(&k_wide);

    let ka = sc_mul(&k, &a);
    let s = sc_add(&r_scalar, &ka);

    let mut sig = [0u8; 64];
    sig[..32].copy_from_slice(&r_bytes);
    sig[32..].copy_from_slice(&s);

    Ed25519Signature(sig)
}

/// Generic Ed25519 verify core. Used by all three variants.
fn ed25519_verify_internal(
    pk: &Ed25519PublicKey,
    message_or_hash: &[u8],
    sig: &Ed25519Signature,
    dom2: Option<&[u8]>,
) -> bool {
    let r_bytes: [u8; 32] = match sig.0[..32].try_into() {
        Ok(b) => b,
        Err(_) => return false,
    };
    let big_r = match ExtPoint::decompress(&r_bytes) {
        Some(p) => p,
        None => return false,
    };

    let big_a = match ExtPoint::decompress(&pk.0) {
        Some(p) => p,
        None => return false,
    };

    let s_bytes: [u8; 32] = match sig.0[32..].try_into() {
        Ok(b) => b,
        Err(_) => return false,
    };

    // Check S < L.
    {
        let mut sl = [0u64; 4];
        for i in 0..4 {
            sl[i] = u64::from_le_bytes(s_bytes[i * 8..(i + 1) * 8].try_into().unwrap());
        }
        let (_, borrow) = sl[0].overflowing_sub(L[0]);
        let (_, borrow) = sbb(sl[1], L[1], borrow);
        let (_, borrow) = sbb(sl[2], L[2], borrow);
        let (_, borrow) = sbb(sl[3], L[3], borrow);
        if !borrow {
            return false; // S >= L
        }
    }

    // k = SHA-512(dom2 || R || A || M) mod L.
    let mut hasher = Sha512::new();
    update_dom2(&mut hasher, dom2);
    hasher.update(&r_bytes);
    hasher.update(&pk.0);
    hasher.update(message_or_hash);
    let k_hash = hasher.finalize();
    let mut k_wide = [0u8; 64];
    k_wide.copy_from_slice(&k_hash);
    let k = sc_reduce(&k_wide);

    // Cofactored verification: [8][S]B == [8]R + [8][k]A.
    let sb = ExtPoint::basepoint().scalar_mul(&s_bytes);
    let ka = big_a.scalar_mul(&k);
    let rhs = big_r.add(ka);

    // Multiply both sides by cofactor 8 (three doublings).
    let lhs = sb.double().double().double();
    let rhs = rhs.double().double().double();

    lhs.equals(rhs)
}

// ----------------------------------------------------------------------------
// dom2 prefix construction (RFC 8032 §5.1.6 / §5.1.7)
//
//   dom2(F, C) = "SigEd25519 no Ed25519 collisions" || octet(F) || octet(len(C)) || C
//
// where F = 0 for Ed25519ctx and F = 1 for Ed25519ph. The literal string
// is exactly 32 bytes; F and len(C) take one byte each; the context C
// follows verbatim. Maximum context length is 255 bytes.
// ----------------------------------------------------------------------------

const DOM2_LITERAL: &[u8] = b"SigEd25519 no Ed25519 collisions";

/// Build the `dom2(F, C)` prefix used by Ed25519ctx (F=0) and
/// Ed25519ph (F=1). Returns `None` if `context.len() > 255` (the
/// max RFC 8032 allows since `len(C)` must fit in one octet).
fn build_dom2(f: u8, context: &[u8]) -> Option<Vec<u8>> {
    if context.len() > 255 {
        return None;
    }
    let mut out = Vec::with_capacity(DOM2_LITERAL.len() + 2 + context.len());
    out.extend_from_slice(DOM2_LITERAL);
    out.push(f);
    out.push(context.len() as u8);
    out.extend_from_slice(context);
    Some(out)
}

// ----------------------------------------------------------------------------
// Ed25519 (pure) -- public API. RFC 8032 §5.1
// ----------------------------------------------------------------------------

/// Sign a message with Ed25519 (pure mode, no prehashing).
///
/// This is RFC 8032 §5.1 -- the original / canonical Ed25519 mode.
/// **Identical bytes** to what was produced before this commit's
/// generic-core refactor; the existing pinned RFC 8032 §7.1 test
/// vectors still pass byte-exact.
pub fn ed25519_sign(sk: &Ed25519SecretKey, msg: &[u8]) -> Ed25519Signature {
    ed25519_sign_internal(sk, msg, None)
}

/// Verify an Ed25519 signature (pure mode).
pub fn ed25519_verify(pk: &Ed25519PublicKey, msg: &[u8], sig: &Ed25519Signature) -> bool {
    ed25519_verify_internal(pk, msg, sig, None)
}

// ----------------------------------------------------------------------------
// Ed25519ctx -- RFC 8032 §5.1.6
//
// Same as pure Ed25519 except both SHA-512 calls are prefixed by
// `dom2(0, context)`. The context is a domain-separation tag chosen
// by the calling protocol (max 255 bytes); a non-empty context is
// REQUIRED by the RFC. Two ctx signatures with different contexts
// are guaranteed to be unrelated even if the message bytes are
// identical -- this is the property protocol designers want when
// the same key is used for multiple purposes.
// ----------------------------------------------------------------------------

/// Sign a message with Ed25519ctx (RFC 8032 §5.1.6).
///
/// `context` is a non-empty domain-separation tag (max 255 bytes).
/// Returns `None` if the context is empty (the RFC mandates a
/// non-empty context for ctx mode) or longer than 255 bytes.
pub fn ed25519ctx_sign(sk: &Ed25519SecretKey, msg: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
    if context.is_empty() {
        return None;
    }
    let dom2 = build_dom2(0, context)?;
    Some(ed25519_sign_internal(sk, msg, Some(&dom2)))
}

/// Verify an Ed25519ctx signature.
///
/// The same `context` used at sign time must be supplied here. A
/// signature produced under one context will NOT verify under a
/// different context, even if the (sk, msg) are the same -- that's
/// the whole point of ctx mode.
pub fn ed25519ctx_verify(pk: &Ed25519PublicKey, msg: &[u8], context: &[u8], sig: &Ed25519Signature) -> bool {
    if context.is_empty() || context.len() > 255 {
        return false;
    }
    let dom2 = match build_dom2(0, context) {
        Some(d) => d,
        None => return false,
    };
    ed25519_verify_internal(pk, msg, sig, Some(&dom2))
}

// ----------------------------------------------------------------------------
// Ed25519ph -- RFC 8032 §5.1.7
//
// "Pre-hashed" Ed25519: the message is hashed with SHA-512 first, and
// the resulting 64-byte digest is what gets fed into the two internal
// SHA-512 calls (with the `dom2(1, context)` prefix). This lets a
// signer sign very large messages without making two passes over them
// in the application layer (e.g. file signing, where the application
// already has a streaming SHA-512 of the file at hand).
//
// The context is OPTIONAL in Ed25519ph (zero-length context is allowed).
// ----------------------------------------------------------------------------

/// Sign a message with Ed25519ph (RFC 8032 §5.1.7, pre-hashed mode).
///
/// The message is internally hashed with SHA-512 before being fed
/// into the Ed25519 algebraic core. `context` may be empty (unlike
/// ctx mode); maximum context length is still 255 bytes.
pub fn ed25519ph_sign(sk: &Ed25519SecretKey, msg: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
    let dom2 = build_dom2(1, context)?;
    let m_prime = Sha512::hash(msg);
    Some(ed25519_sign_internal(sk, &m_prime, Some(&dom2)))
}

/// Verify an Ed25519ph signature.
pub fn ed25519ph_verify(pk: &Ed25519PublicKey, msg: &[u8], context: &[u8], sig: &Ed25519Signature) -> bool {
    if context.len() > 255 {
        return false;
    }
    let dom2 = match build_dom2(1, context) {
        Some(d) => d,
        None => return false,
    };
    let m_prime = Sha512::hash(msg);
    ed25519_verify_internal(pk, &m_prime, sig, Some(&dom2))
}

/// Variant of [`ed25519ph_sign`] that takes a precomputed SHA-512
/// digest of the message instead of the message itself. Useful when
/// the caller already has the digest from a streaming hash (e.g. a
/// file scanner that produced the digest as it read the file).
///
/// The `prehashed` argument **must** be exactly 64 bytes (the
/// SHA-512 output length). Returns `None` for any other length.
pub fn ed25519ph_sign_prehashed(sk: &Ed25519SecretKey, prehashed: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
    if prehashed.len() != 64 {
        return None;
    }
    let dom2 = build_dom2(1, context)?;
    Some(ed25519_sign_internal(sk, prehashed, Some(&dom2)))
}

/// Verify a precomputed-digest Ed25519ph signature. Counterpart to
/// [`ed25519ph_sign_prehashed`].
pub fn ed25519ph_verify_prehashed(
    pk: &Ed25519PublicKey,
    prehashed: &[u8],
    context: &[u8],
    sig: &Ed25519Signature,
) -> bool {
    if prehashed.len() != 64 || context.len() > 255 {
        return false;
    }
    let dom2 = match build_dom2(1, context) {
        Some(d) => d,
        None => return false,
    };
    ed25519_verify_internal(pk, prehashed, sig, Some(&dom2))
}

// ============================================================
// Ed448 (TODO)
// ============================================================

// TODO: Implement Ed448 (RFC 8032 Section 5.2) using the Goldilocks curve.
// Ed448-Goldilocks: -x^2 + y^2 = 1 - 39081*x^2*y^2 over GF(2^448 - 2^224 - 1).
// Requirements:
//   - Field arithmetic for GF(2^448 - 2^224 - 1)
//   - SHAKE256 instead of SHA-512
//   - Different scalar clamping and point encoding
//   - 57-byte keys and 114-byte signatures

// ============================================================
// Tests
// ============================================================

#[cfg(test)]
mod tests {
    use super::*;

    fn hex_to_bytes(s: &str) -> Vec<u8> {
        (0..s.len())
            .step_by(2)
            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
            .collect()
    }

    #[test]
    fn test_fe25519_basic() {
        let a = Fe25519::ONE;
        let b = Fe25519::ONE;
        let c = a.add(b);
        assert_eq!(c.0[0], 2);
        assert_eq!(c.0[1], 0);

        let d = c.sub(a);
        assert!(d.equals(Fe25519::ONE));
    }

    #[test]
    fn test_fe25519_mul() {
        let a = Fe25519::from_u64(7);
        let b = Fe25519::from_u64(13);
        let c = a.mul(b);
        assert!(c.equals(Fe25519::from_u64(91)));
    }

    #[test]
    fn test_fe25519_inv() {
        let a = Fe25519::from_u64(42);
        let b = a.inv();
        let c = a.mul(b);
        assert!(c.equals(Fe25519::ONE));
    }

    #[test]
    fn test_basepoint_on_curve() {
        // Verify: -x^2 + y^2 == 1 + d*x^2*y^2
        let x2 = B_X.square();
        let y2 = B_Y.square();
        let lhs = y2.sub(x2);
        let rhs = Fe25519::ONE.add(D.mul(x2).mul(y2));
        assert!(lhs.equals(rhs), "Base point not on curve!");
    }

    #[test]
    fn test_d_constant() {
        // d = -121665 * inv(121666) mod p
        let n = Fe25519::from_u64(121665).neg();
        let d_inv = Fe25519::from_u64(121666).inv();
        let d_computed = n.mul(d_inv);
        assert!(d_computed.equals(D), "D constant is wrong");
    }

    #[test]
    fn test_d2_constant() {
        let d2_computed = D.add(D);
        assert!(d2_computed.equals(D2), "D2 constant is wrong");
    }

    #[test]
    fn test_basepoint_compress_decompress() {
        let bp = ExtPoint::basepoint();
        let compressed = bp.compress();
        let decompressed = ExtPoint::decompress(&compressed).expect("decompress failed");
        assert!(bp.equals(decompressed));
    }

    fn naive_scalar_mul(p: ExtPoint, scalar: &[u8; 32]) -> ExtPoint {
        // Compute by iterating MSB to LSB, doubling and conditionally
        // adding -- but using a simple non-CT branch (for testing only).
        let mut acc = ExtPoint::IDENTITY;
        let mut started = false;
        for byte_idx in (0..32).rev() {
            for bit_idx in (0..8).rev() {
                if started {
                    acc = acc.double();
                }
                let bit = (scalar[byte_idx] >> bit_idx) & 1;
                if bit == 1 {
                    if !started {
                        acc = p;
                        started = true;
                    } else {
                        acc = acc.add(p);
                    }
                }
            }
        }
        acc
    }

    #[test]
    fn test_scalar_mul_vs_naive() {
        let bp = ExtPoint::basepoint();
        let mut s = [0u8; 32];
        // Some pseudo-random scalar with many bits set.
        for i in 0..32 {
            s[i] = (i as u8) ^ 0xa5;
        }
        s[31] &= 0x7f;
        let r1 = bp.scalar_mul(&s);
        let r2 = naive_scalar_mul(bp, &s);
        assert!(r1.equals(r2));
    }

    #[test]
    fn test_sc_reduce_basic() {
        // Test 1: input = 1
        let mut x = [0u8; 64];
        x[0] = 1;
        let r = sc_reduce(&x);
        assert_eq!(r[0], 1);
        for i in 1..32 {
            assert_eq!(r[i], 0);
        }

        // Test 2: input = L (encoded LE in 64 bytes)
        let mut x = [0u8; 64];
        x[0..8].copy_from_slice(&L[0].to_le_bytes());
        x[8..16].copy_from_slice(&L[1].to_le_bytes());
        x[16..24].copy_from_slice(&L[2].to_le_bytes());
        x[24..32].copy_from_slice(&L[3].to_le_bytes());
        let r = sc_reduce(&x);
        for i in 0..32 {
            assert_eq!(r[i], 0, "L mod L should be 0, byte {}", i);
        }

        // Test 3: input = L+1 → result = 1
        let mut x = [0u8; 64];
        x[0..8].copy_from_slice(&(L[0] + 1).to_le_bytes());
        x[8..16].copy_from_slice(&L[1].to_le_bytes());
        x[16..24].copy_from_slice(&L[2].to_le_bytes());
        x[24..32].copy_from_slice(&L[3].to_le_bytes());
        let r = sc_reduce(&x);
        assert_eq!(r[0], 1);
    }

    #[test]
    fn test_fe_pow_fermat() {
        // a^(p-1) == 1 mod p for a != 0.
        let a = Fe25519::from_u64(7);
        let pm1: [u64; 4] = [
            0xFFFF_FFFF_FFFF_FFEC,
            0xFFFF_FFFF_FFFF_FFFF,
            0xFFFF_FFFF_FFFF_FFFF,
            0x7FFF_FFFF_FFFF_FFFF,
        ];
        let r = a.pow(pm1);
        assert!(r.equals(Fe25519::ONE));
    }

    #[test]
    fn test_scalar_mul_two() {
        let bp = ExtPoint::basepoint();
        let mut two = [0u8; 32];
        two[0] = 2;
        let r = bp.scalar_mul(&two);
        assert!(r.equals(bp.double()));
    }

    #[test]
    fn test_scalar_mul_three() {
        let bp = ExtPoint::basepoint();
        let mut three = [0u8; 32];
        three[0] = 3;
        let r = bp.scalar_mul(&three);
        assert!(r.equals(bp.double().add(bp)));
    }

    #[test]
    fn test_scalar_mul_identity() {
        let bp = ExtPoint::basepoint();
        let mut one = [0u8; 32];
        one[0] = 1;
        let result = bp.scalar_mul(&one);
        assert!(result.equals(bp));
    }

    #[test]
    fn test_point_add_double() {
        let bp = ExtPoint::basepoint();
        let bp2_add = bp.add(bp);
        let bp2_dbl = bp.double();
        assert!(bp2_add.equals(bp2_dbl));
    }

    /// RFC 8032, Section 7.1 - Test Vector 1 (empty message).
    #[test]
    fn test_ed25519_vector1() {
        let sk_hex = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";
        let pk_hex = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
        let sig_hex = "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155\
                        5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b";

        let sk_bytes = hex_to_bytes(sk_hex);
        let pk_bytes = hex_to_bytes(pk_hex);
        let sig_bytes = hex_to_bytes(sig_hex);

        let mut sk = [0u8; 32];
        sk.copy_from_slice(&sk_bytes);

        let (pk, secret) = ed25519_keygen(&sk);
        assert_eq!(&pk.0[..], &pk_bytes[..], "Public key mismatch");

        let signature = ed25519_sign(&secret, b"");
        assert_eq!(&signature.0[..], &sig_bytes[..], "Signature mismatch");

        assert!(ed25519_verify(&pk, b"", &signature), "Verification failed");
    }

    /// RFC 8032, Section 7.1 - Test Vector 2 (message = 0x72).
    #[test]
    fn test_ed25519_vector2() {
        let sk_hex = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb";
        let pk_hex = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c";
        let sig_hex = "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da\
                        085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00";

        let sk_bytes = hex_to_bytes(sk_hex);
        let pk_bytes = hex_to_bytes(pk_hex);
        let sig_bytes = hex_to_bytes(sig_hex);

        let mut sk = [0u8; 32];
        sk.copy_from_slice(&sk_bytes);

        let (pk, secret) = ed25519_keygen(&sk);
        assert_eq!(&pk.0[..], &pk_bytes[..], "Public key mismatch");

        let msg = [0x72u8];
        let signature = ed25519_sign(&secret, &msg);
        assert_eq!(&signature.0[..], &sig_bytes[..], "Signature mismatch");

        assert!(ed25519_verify(&pk, &msg, &signature), "Verification failed");
    }

    // ----- RFC 8032 §7.2 -- Ed25519ctx test vector ("foo" context) ---------
    //
    //  SECRET KEY: 0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6
    //  PUBLIC KEY: dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292
    //  MESSAGE   : f726936d19c800494e3fdaff20b276a8
    //  CONTEXT   : 666f6f                  (= "foo")
    //  SIGNATURE : 55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a
    //              8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d
    #[test]
    fn rfc8032_section_7_2_ed25519ctx_vector() {
        let sk_bytes = hex_to_bytes("0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6");
        let pk_bytes = hex_to_bytes("dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292");
        let msg = hex_to_bytes("f726936d19c800494e3fdaff20b276a8");
        let context = hex_to_bytes("666f6f"); // "foo"
        let sig_bytes = hex_to_bytes(
            "55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a\
             8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d",
        );

        let mut sk_arr = [0u8; 32];
        sk_arr.copy_from_slice(&sk_bytes);
        let (pk, secret) = ed25519_keygen(&sk_arr);
        assert_eq!(pk.0.as_slice(), pk_bytes.as_slice(), "pk mismatch");

        // Sign and check byte-exact against the pinned signature.
        let signature = ed25519ctx_sign(&secret, &msg, &context).expect("ctx_sign");
        assert_eq!(
            signature.0.as_slice(),
            sig_bytes.as_slice(),
            "Ed25519ctx signature mismatch"
        );

        // Verify must accept with the same context.
        assert!(ed25519ctx_verify(&pk, &msg, &context, &signature));
    }

    // ----- RFC 8032 §7.3 -- Ed25519ph test vector ("abc", no context) ------
    //
    //  SECRET KEY: 833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42
    //  PUBLIC KEY: ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf
    //  MESSAGE   : 616263                   (= "abc")
    //  CONTEXT   : (empty)
    //  SIGNATURE : 98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae41
    //              31f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406
    #[test]
    fn rfc8032_section_7_3_ed25519ph_vector() {
        let sk_bytes = hex_to_bytes("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42");
        let pk_bytes = hex_to_bytes("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf");
        let msg = hex_to_bytes("616263"); // "abc"
        let sig_bytes = hex_to_bytes(
            "98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae41\
             31f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406",
        );

        let mut sk_arr = [0u8; 32];
        sk_arr.copy_from_slice(&sk_bytes);
        let (pk, secret) = ed25519_keygen(&sk_arr);
        assert_eq!(pk.0.as_slice(), pk_bytes.as_slice(), "pk mismatch");

        // Empty context is allowed in ph mode.
        let signature = ed25519ph_sign(&secret, &msg, b"").expect("ph_sign");
        assert_eq!(
            signature.0.as_slice(),
            sig_bytes.as_slice(),
            "Ed25519ph signature mismatch"
        );

        assert!(ed25519ph_verify(&pk, &msg, b"", &signature));
    }

    /// Pre-hashed entry point: feeding the SHA-512 of `"abc"` to
    /// `ed25519ph_sign_prehashed` must produce the same byte-exact
    /// signature as `ed25519ph_sign(b"abc", _)`. Same for verify.
    #[test]
    fn ed25519ph_prehashed_matches_message_form() {
        let sk_bytes = hex_to_bytes("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42");
        let mut sk_arr = [0u8; 32];
        sk_arr.copy_from_slice(&sk_bytes);
        let (pk, secret) = ed25519_keygen(&sk_arr);

        let from_msg = ed25519ph_sign(&secret, b"abc", b"").expect("ph_sign");

        // Pre-hash with SHA-512 ourselves and feed the digest in.
        let digest = Sha512::hash(b"abc");
        let from_digest = ed25519ph_sign_prehashed(&secret, &digest, b"").expect("ph_sign_prehashed");

        assert_eq!(from_msg.0, from_digest.0);

        // Both forms verify equivalently.
        assert!(ed25519ph_verify(&pk, b"abc", b"", &from_msg));
        assert!(ed25519ph_verify_prehashed(&pk, &digest, b"", &from_msg));
    }

    // ----- Cross-mode rejection tests --------------------------------------
    //
    // The dom2 prefix is what makes pure / ctx / ph mutually unforgeable.
    // A signature produced under one mode must NOT verify under another --
    // and ctx signatures with different `context` values must be mutually
    // unforgeable too. These tests catch any regression in the prefix
    // handling that would silently let one mode's signature pass another
    // mode's verifier.

    fn make_test_keys() -> (Ed25519PublicKey, Ed25519SecretKey) {
        let sk_bytes = hex_to_bytes("0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6");
        let mut sk = [0u8; 32];
        sk.copy_from_slice(&sk_bytes);
        ed25519_keygen(&sk)
    }

    #[test]
    fn ed25519ctx_requires_nonempty_context() {
        let (_pk, sk) = make_test_keys();
        // ctx_sign with empty context must return None per RFC 8032 §5.1.
        assert!(ed25519ctx_sign(&sk, b"any message", b"").is_none());
    }

    #[test]
    fn ed25519ctx_signature_does_not_verify_as_pure() {
        let (pk, sk) = make_test_keys();
        let msg = b"shared message";
        let sig = ed25519ctx_sign(&sk, msg, b"appA").expect("ctx_sign");
        // The same (pk, msg, sig) must be REJECTED by pure verify
        // because pure verify doesn't apply the dom2 prefix.
        assert!(!ed25519_verify(&pk, msg, &sig));
    }

    #[test]
    fn pure_signature_does_not_verify_as_ctx() {
        let (pk, sk) = make_test_keys();
        let msg = b"shared message";
        let sig = ed25519_sign(&sk, msg);
        // Pure signature presented to ctx verify must be rejected
        // (ctx verify will hash with the dom2 prefix).
        assert!(!ed25519ctx_verify(&pk, msg, b"appA", &sig));
    }

    #[test]
    fn ed25519ctx_different_contexts_dont_cross() {
        let (pk, sk) = make_test_keys();
        let msg = b"shared message";
        let sig_a = ed25519ctx_sign(&sk, msg, b"appA").expect("ctx_sign A");
        let sig_b = ed25519ctx_sign(&sk, msg, b"appB").expect("ctx_sign B");

        // Each verifies under its own context.
        assert!(ed25519ctx_verify(&pk, msg, b"appA", &sig_a));
        assert!(ed25519ctx_verify(&pk, msg, b"appB", &sig_b));

        // Cross-context must fail.
        assert!(!ed25519ctx_verify(&pk, msg, b"appB", &sig_a));
        assert!(!ed25519ctx_verify(&pk, msg, b"appA", &sig_b));

        // And the two signatures must actually be different (otherwise
        // the previous assertion would just be a fluke).
        assert_ne!(sig_a.0, sig_b.0);
    }

    #[test]
    fn ed25519ph_signature_does_not_verify_as_pure() {
        let (pk, sk) = make_test_keys();
        let msg = b"shared message";
        let sig = ed25519ph_sign(&sk, msg, b"").expect("ph_sign");
        // ph verify pre-hashes the message; pure verify doesn't.
        // The same signature presented to pure verify must reject.
        assert!(!ed25519_verify(&pk, msg, &sig));
    }

    #[test]
    fn ed25519ph_signature_does_not_verify_as_ctx() {
        let (pk, sk) = make_test_keys();
        let msg = b"shared message";
        let sig = ed25519ph_sign(&sk, msg, b"context").expect("ph_sign");
        // ph and ctx use different F bytes (1 vs 0) in the dom2 prefix,
        // so even with the same context they don't cross.
        assert!(!ed25519ctx_verify(&pk, msg, b"context", &sig));
    }

    #[test]
    fn ed25519ctx_max_context_length() {
        let (pk, sk) = make_test_keys();
        let msg = b"x";
        // 255-byte context is allowed.
        let ctx_max = vec![0xa5u8; 255];
        let sig = ed25519ctx_sign(&sk, msg, &ctx_max).expect("ctx max len");
        assert!(ed25519ctx_verify(&pk, msg, &ctx_max, &sig));

        // 256-byte context is NOT allowed (the length octet would
        // overflow).
        let ctx_too_long = vec![0xa5u8; 256];
        assert!(ed25519ctx_sign(&sk, msg, &ctx_too_long).is_none());
        assert!(!ed25519ctx_verify(&pk, msg, &ctx_too_long, &sig));
    }
}
