//! Internal utility functions for the ECMQV implementation.

#![allow(dead_code, unreachable_patterns)]

use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;

use crate::error::AgreementError;
use crate::key::PublicKey;
use crate::params::CurveParameters;

/// Constant-time equality comparison of two public keys.
///
/// Returns `Choice::from(1)` if the keys are equal, otherwise `Choice::from(0)`.
pub fn ct_eq_pubkeys(a: &PublicKey, b: &PublicKey) -> Choice {
    let a_bytes = a.to_bytes();
    let b_bytes = b.to_bytes();
    if a_bytes.len() != b_bytes.len() {
        return Choice::from(0);
    }
    let mut result = Choice::from(1);
    for (x, y) in a_bytes.iter().zip(b_bytes.iter()) {
        result &= x.ct_eq(y);
    }
    result
}

/// Securely zeroize a slice of bytes.
///
/// This uses the `zeroize` crate to prevent compiler optimizations
pub fn zeroize_bytes(bytes: &mut [u8]) {
    bytes.zeroize();
}

/// Ensures that the given curve is supported in the current build.
///
/// # Errors
/// Returns `UnsupportedCurve` if the curve feature is not enabled.
pub fn ensure_curve_supported(curve: &CurveParameters) -> Result<(), AgreementError> {
    match curve {
        #[cfg(feature = "curve25519")]
        CurveParameters::Curve25519 => Ok(()),
        #[cfg(feature = "secp256k1")]
        CurveParameters::Secp256k1 => Ok(()),
        #[cfg(feature = "secp256r1")]
        CurveParameters::Secp256r1 => Ok(()),
        _ => Err(AgreementError::UnsupportedCurve),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::key::EphemeralKey;
    use crate::params::CurveParameters;
    use rand::rngs::OsRng;
    use alloc::vec;

    #[test]
    fn test_ct_eq_pubkeys() {
        let params = CurveParameters::Curve25519;
        let mut rng = OsRng;
        let e1 = EphemeralKey::generate(&mut rng, params).unwrap();
        let e2 = EphemeralKey::generate(&mut rng, params).unwrap();
        let p1 = e1.public();
        let p2 = e2.public();
        assert_eq!(ct_eq_pubkeys(&p1, &p1).unwrap_u8(), 1);
        assert_eq!(ct_eq_pubkeys(&p1, &p2).unwrap_u8(), 0);
    }

    #[test]
    fn test_zeroize_bytes() {
        let mut data = vec![1, 2, 3, 4];
        zeroize_bytes(&mut data);
        assert_eq!(data, vec![0, 0, 0, 0]);
    }
}