use crate::crypto::adaptive_crypto::{CipherSuite, CryptoSession};
use crate::crypto::hybrid_kem::HybridSecretKey;
use crate::crypto::hybrid_sign::HybridSigningKey;
use hkdf::Hkdf;
use sha2::Sha256;
use std::sync::OnceLock;
#[cfg_attr(test, allow(dead_code))]
static POST_RESULT: OnceLock<Result<(), SelfTestError>> = OnceLock::new();
#[cfg(test)]
static FORCE_POST_FAIL: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn ensure_post_passed() -> Result<(), SelfTestError> {
#[cfg(test)]
{
if FORCE_POST_FAIL.load(std::sync::atomic::Ordering::SeqCst) {
return Err(SelfTestError::Aead {
algorithm: "AES-256-GCM",
stage: AeadStage::Decrypt,
});
}
return run_post();
}
#[cfg(not(test))]
{
*POST_RESULT.get_or_init(run_post)
}
}
#[cfg(test)]
pub fn set_force_post_fail(enable: bool) {
FORCE_POST_FAIL.store(enable, std::sync::atomic::Ordering::SeqCst);
}
#[cfg(test)]
pub fn tests_serial_guard() -> &'static std::sync::Mutex<()> {
static G: std::sync::OnceLock<std::sync::Mutex<()>> = std::sync::OnceLock::new();
G.get_or_init(|| std::sync::Mutex::new(()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AeadStage {
Init,
Encrypt,
Decrypt,
Mismatch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KemStage {
Generate,
Encapsulate,
Decapsulate,
Mismatch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignStage {
Generate,
Verify,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelfTestError {
Aead {
algorithm: &'static str,
stage: AeadStage,
},
Hkdf,
HybridKem { stage: KemStage },
HybridSign { stage: SignStage },
NegativeVerify,
}
pub fn run_post() -> Result<(), SelfTestError> {
test_aead(CipherSuite::Aes256Gcm, "AES-256-GCM")?;
#[cfg(not(feature = "fips"))]
test_aead(CipherSuite::ChaCha20Poly1305, "ChaCha20-Poly1305")?;
test_hkdf_sha256()?;
test_hybrid_kem()?;
test_hybrid_sign()?;
test_negative_verify()?;
Ok(())
}
fn test_aead(suite: CipherSuite, name: &'static str) -> Result<(), SelfTestError> {
let shared_secret = [0x42u8; 32];
let aad = b"phantom-self-test-aad";
let plaintext = b"phantom-protocol self-test payload";
let local =
CryptoSession::with_suite(&shared_secret, suite).map_err(|_| SelfTestError::Aead {
algorithm: name,
stage: AeadStage::Init,
})?;
let peer =
CryptoSession::with_suite_peer(&shared_secret, suite).map_err(|_| SelfTestError::Aead {
algorithm: name,
stage: AeadStage::Init,
})?;
let ciphertext = local
.encrypt(aad, plaintext)
.map_err(|_| SelfTestError::Aead {
algorithm: name,
stage: AeadStage::Encrypt,
})?;
let recovered = peer
.decrypt(aad, &ciphertext)
.map_err(|_| SelfTestError::Aead {
algorithm: name,
stage: AeadStage::Decrypt,
})?;
if recovered != plaintext {
return Err(SelfTestError::Aead {
algorithm: name,
stage: AeadStage::Mismatch,
});
}
Ok(())
}
fn test_hkdf_sha256() -> Result<(), SelfTestError> {
let ikm = [0x11u8; 32];
let hk = Hkdf::<Sha256>::new(None, &ikm);
let mut output = [0u8; 32];
hk.expand(b"phantom-rekey-v1", &mut output)
.map_err(|_| SelfTestError::Hkdf)?;
const KAT: [u8; 32] = [
0x41, 0x90, 0x72, 0xe4, 0xca, 0x1b, 0xa9, 0xca, 0xdc, 0x1b, 0x02, 0xd3, 0x75, 0xb0, 0xf8,
0x84, 0x70, 0xa7, 0x0f, 0xe9, 0x57, 0x13, 0x1d, 0x7b, 0x5b, 0x35, 0xe5, 0x74, 0x14, 0x34,
0xe4, 0x10,
];
if output != KAT {
return Err(SelfTestError::Hkdf);
}
Ok(())
}
fn test_hybrid_kem() -> Result<(), SelfTestError> {
let (sk, pk) = HybridSecretKey::generate();
let (ss_encap, ct) = pk.encapsulate().map_err(|_| SelfTestError::HybridKem {
stage: KemStage::Encapsulate,
})?;
let ss_decap = sk.decapsulate(&ct).map_err(|_| SelfTestError::HybridKem {
stage: KemStage::Decapsulate,
})?;
if ss_encap != ss_decap {
return Err(SelfTestError::HybridKem {
stage: KemStage::Mismatch,
});
}
Ok(())
}
fn test_hybrid_sign() -> Result<(), SelfTestError> {
let (sk, pk) = HybridSigningKey::generate();
let message = b"phantom-protocol self-test signature input";
let sig = sk.sign(message);
pk.verify(message, &sig)
.map_err(|_| SelfTestError::HybridSign {
stage: SignStage::Verify,
})?;
Ok(())
}
fn test_negative_verify() -> Result<(), SelfTestError> {
let (sk, pk) = HybridSigningKey::generate();
let message = b"phantom-protocol self-test negative input";
let sig = sk.sign(message);
let mut sig_bytes = sig.to_bytes();
let idx = sig_bytes.len() / 2;
sig_bytes[idx] ^= 0x01;
let tampered = match crate::crypto::hybrid_sign::HybridSignature::from_bytes(&sig_bytes) {
Ok(s) => s,
Err(_) => return Ok(()),
};
if pk.verify(message, &tampered).is_ok() {
return Err(SelfTestError::NegativeVerify);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn post_succeeds_on_clean_build() {
run_post().expect("self-tests must pass on a clean build");
}
#[test]
fn aead_test_passes_for_both_suites() {
test_aead(CipherSuite::Aes256Gcm, "AES-256-GCM").unwrap();
#[cfg(not(feature = "fips"))]
test_aead(CipherSuite::ChaCha20Poly1305, "ChaCha20-Poly1305").unwrap();
}
#[test]
fn hkdf_kat_locks_the_construction() {
test_hkdf_sha256().unwrap();
}
#[test]
fn hybrid_kem_round_trip_consistent() {
test_hybrid_kem().unwrap();
}
#[test]
fn hybrid_sign_round_trip_consistent() {
test_hybrid_sign().unwrap();
}
#[test]
fn negative_verify_rejects_tampered_signature() {
test_negative_verify().unwrap();
}
#[test]
fn full_post_under_a_loop_is_stable() {
for _ in 0..3 {
run_post().unwrap();
}
}
#[test]
fn ensure_post_passed_succeeds_on_clean_build() {
let _guard = tests_serial_guard().lock().unwrap();
set_force_post_fail(false);
assert!(ensure_post_passed().is_ok());
}
#[test]
fn force_post_fail_returns_error_via_ensure_post_passed() {
let _guard = tests_serial_guard().lock().unwrap();
set_force_post_fail(true);
let result = ensure_post_passed();
set_force_post_fail(false);
match result {
Err(SelfTestError::Aead {
algorithm: "AES-256-GCM",
stage: AeadStage::Decrypt,
}) => {}
other => panic!("expected fault-injected AEAD Decrypt failure, got {other:?}"),
}
assert!(ensure_post_passed().is_ok());
}
}