Here are the detailed instructions for implementing the 8 tests in Priority 1.
This plan focuses on proving your library is **mathematically correct** (KATs), **robust to garbage data** (Malformed/API Misuse), and **cryptographically sound** (Security/Lifecycle).
-----
### 1\. Category 5: KATs (2 Tests)
**Objective:** Prove your library is mathematically compliant with the official FIPS 203 (Kyber) and FIPS 204 (Dilithium) standards. This is the **most important** remaining test.
You will parse official test files and verify that your functions produce the exact same results.
**Tools Needed:**
* Add to `[dev-dependencies]` in `Cargo.toml`: `hex = "0.4"`
**How to Find the KAT files:**
1. Go to the official CRYSTALS team resource pages:
* **Kyber:** `https://pq-crystals.org/kyber/resources.shtml`
* **Dilithium:** `https://pq-crystals.org/dilithium/resources.shtml`
2. Download the **"NIST submission package for round 3 (zip)"** for both.
3. Inside the zips, find the test vector files. They are typically in a `test/` or `KAT/` directory and end in `.rsp`.
* For Kyber-1024, find `PQCkemKAT_2400.rsp` (or similar).
* For Dilithium3, find `PQCsignKAT_1952.rsp` (or similar).
These `.rsp` files are plain text. You can create a new `tests/kats.rs` file and `include_str!` them.
```rust
// In tests/kats.rs
// (Make sure to add `hex` to your [dev-dependencies])
// Put the .rsp files in your project's root or a `tests/` subfolder
const KYBER_KAT: &str = include_str!("../PQCkemKAT_2400.rsp");
const DILITHIUM_KAT: &str = include_str!("../PQCsignKAT_1952.rsp");
// You'll need a simple helper to parse the .rsp files
// This is a rough example; you can make it more robust.
fn parse_kat_file(file_content: &str, fields: &[&str]) -> Vec<Vec<Vec<u8>>> {
let mut records = Vec::new();
let mut current_record = Vec::new();
for line in file_content.lines() {
if line.starts_with('#') || line.is_empty() {
continue;
}
let parts: Vec<&str> = line.split(" = ").collect();
if fields.contains(&parts[0]) {
current_record.push(hex::decode(parts[1]).unwrap());
}
// "ss" (Kyber) or "sig" (Dilithium) is the last field.
if (parts[0] == "ss" || parts[0] == "sig") && !current_record.is_empty() {
records.push(current_record.clone());
current_record.clear();
}
}
records
}
```
-----
#### **Test 1: `test_kyber1024_kats_decapsulate`**
**Objective:** Verify that given an official Secret Key and Ciphertext, your `decapsulate` function produces the correct Shared Secret.
**Implementation:**
```rust
#[test]
fn test_kyber1024_kats_decapsulate() {
use pqc_combo::{KyberSecretKey, KyberCiphertext};
// sk, ct, ss
let fields = &["sk", "ct", "ss"];
let records = parse_kat_file(KYBER_KAT, fields);
assert!(!records.is_empty(), "Failed to parse Kyber KATs");
for record in records {
let sk_bytes = &record[0];
let ct_bytes = &record[1];
let ss_expected_bytes = &record[2];
let sk = KyberSecretKey::from_bytes(sk_bytes).unwrap();
let ct = KyberCiphertext::from_bytes(ct_bytes).unwrap();
let ss_actual = pqc_combo::decapsulate_shared_secret(&sk, &ct);
assert_eq!(ss_actual.as_bytes(), ss_expected_bytes.as_slice());
}
}
```
#### **Test 2: `test_dilithium3_kats_verify`**
**Objective:** Verify that given an official Public Key, Message, and Signature, your `verify_signature` function accepts it.
**Implementation:**
```rust
#[test]
fn test_dilithium3_kats_verify() {
use pqc_combo::{DilithiumPublicKey, DilithiumSignedMessage};
// pk, msg, sig
let fields = &["pk", "msg", "sig"];
let records = parse_kat_file(DILITHIUM_KAT, fields);
assert!(!records.is_empty(), "Failed to parse Dilithium KATs");
for record in records {
let pk_bytes = &record[0];
let msg = &record[1];
let sig_bytes = &record[2];
let pk = DilithiumPublicKey::from_bytes(pk_bytes).unwrap();
let sig = DilithiumSignedMessage::from_bytes(sig_bytes).unwrap();
let verification_result = pqc_combo::verify_signature(&pk, msg, &sig);
assert!(verification_result, "KAT verification failed");
}
}
```
-----
### 2\. Category 2: Malformed Inputs (2 Tests)
**Objective:** Finish proving your deserialization functions (`from_bytes`) are robust and will reject garbage data. Your `test_invalid_key_length` was a great start; these complete the category.
#### **Test 3: `test_deserialize_all_zeros`**
**Objective:** Ensure that a buffer of all zeros is correctly rejected (as it's statistically impossible to be a valid key/ciphertext).
**Implementation:**
```rust
#[test]
fn test_deserialize_all_zeros() {
use pqc_combo::*;
// Test Kyber PK
let zeros_ky_pk = [0u8; pqcrypto_kyber::kyber1024::PUBLICKEYBYTES];
assert!(KyberPublicKey::from_bytes(&zeros_ky_pk).is_err());
// Test Kyber SK
let zeros_ky_sk = [0u8; pqcrypto_kyber::kyber1024::SECRETKEYBYTES];
assert!(KyberSecretKey::from_bytes(&zeros_ky_sk).is_err());
// Test Kyber Ciphertext
let zeros_ky_ct = [0u8; pqcrypto_kyber::kyber1024::CIPHERTEXTBYTES];
assert!(KyberCiphertext::from_bytes(&zeros_ky_ct).is_err());
// Test Dilithium PK
let zeros_dil_pk = [0u8; pqcrypto_dilithium::dilithium3::PUBLICKEYBYTES];
assert!(DilithiumPublicKey::from_bytes(&zeros_dil_pk).is_err());
// ... continue for DilithiumSecretKey and DilithiumSignedMessage
}
```
#### **Test 4: `test_deserialize_random_bytes`**
**Objective:** Ensure that a buffer of cryptographically random bytes (of the correct length) is rejected.
**Tools Needed:**
* Add to `[dev-dependencies]`: `rand_core = "0.6"`, `rand_chacha = "0.3"`
**Implementation:**
```rust
#[test]
fn test_deserialize_random_bytes() {
use pqc_combo::*;
use rand_core::{RngCore, SeedableRng};
use rand_chacha::ChaCha8Rng;
let mut rng = ChaCha8Rng::seed_from_u64(42);
// Test Kyber PK
let mut rand_ky_pk = [0u8; pqcrypto_kyber::kyber1024::PUBLICKEYBYTES];
rng.fill_bytes(&mut rand_ky_pk);
assert!(KyberPublicKey::from_bytes(&rand_ky_pk).is_err());
// ... continue for all other key, ciphertext, and signature types
}
```
-----
### 3\. Category 4: API Misuse (2 Tests)
**Objective:** Prove that passing nonsensical (but validly typed) data to your functions fails gracefully. Your `test_invalid_key_length` covers length mismatches; these tests check for empty (zero-length) inputs.
#### **Test 5: `test_api_verify_empty_inputs`**
**Objective:** Ensure that passing an empty byte slice to `from_bytes` for keys/signatures results in an error, not a panic.
**Implementation:**
```rust
#[test]
fn test_api_verify_empty_inputs() {
use pqc_combo::*;
let empty_bytes = b"";
assert!(KyberPublicKey::from_bytes(empty_bytes).is_err());
assert!(KyberSecretKey::from_bytes(empty_bytes).is_err());
assert!(KyberCiphertext::from_bytes(empty_bytes).is_err());
assert!(DilithiumPublicKey::from_bytes(empty_bytes).is_err());
assert!(DilithiumSecretKey::from_bytes(empty_bytes).is_err());
assert!(DilithiumSignedMessage::from_bytes(empty_bytes).is_err());
}
```
#### **Test 6: `test_api_verify_empty_message_signature`**
**Objective:** Ensure that calling `verify_signature` with a valid key but an empty (default) signature struct fails.
**Implementation:**
```rust
#[test]
fn test_api_verify_empty_message_signature() {
use pqc_combo::*;
let (pk, _) = pqcrypto_dilithium::dilithium3::keypair();
let msg = b"test message";
// Create a default, empty (all-zero) signature
let empty_sig_bytes = [0u8; pqcrypto_dilithium::dilithium3::SIGNATUREBYTES];
let empty_sig = DilithiumSignedMessage::from_bytes(&empty_sig_bytes).unwrap();
let verification_result = pqc_combo::verify_signature(&pk, msg, &empty_sig);
assert!(!verification_result, "Verification succeeded with an empty/zeroed signature");
}
```
-----
### 4\. Category 6: Security (1 Test)
**Objective:** Complete your security checks. You've already confirmed Dilithium signing is deterministic. Now, you must prove the *opposite* for Kyber KEM.
#### **Test 7: `test_kyber_encapsulation_is_randomized`**
**Objective:** Prove that calling `encapsulate` twice on the same public key produces two **different** ciphertexts and shared secrets. This is a critical security property (IND-CCA2).
**Implementation:**
```rust
#[test]
fn test_kyber_encapsulation_is_randomized() {
use pqc_combo::*;
let (pk, _) = pqcrypto_kyber::kyber1024::keypair();
// Generate first pair
let (ct1, ss1) = pqc_combo::encapsulate_shared_secret(&pk, &mut rand::thread_rng()).unwrap();
// Generate second pair
let (ct2, ss2) = pqc_combo::encapsulate_shared_secret(&pk, &mut rand::thread_rng()).unwrap();
// The ciphertexts MUST be different
assert_ne!(ct1.as_bytes(), ct2.as_bytes(), "Ciphertexts were identical, KEM is not randomized!");
// The shared secrets MUST also be different
assert_ne!(ss1.as_bytes(), ss2.as_bytes(), "Shared secrets were identical, KEM is not randomized!");
}
```
-----
### 5\. Category 7: Lifecycle (1 Test)
**Objective:** Finish the "Lifecycle" tests. You've completed keypair serialization. This test completes the set by verifying ciphertext and signature serialization.
#### **Test 8: `test_ciphertext_and_signature_serialization_roundtrip`**
**Objective:** Prove that a ciphertext and signature can be serialized (`.as_bytes()`) and then deserialized (`from_bytes()`) back into an identical object.
**Implementation:**
```rust
#[test]
fn test_ciphertext_and_signature_serialization_roundtrip() {
use pqc_combo::*;
// 1. Test Kyber Ciphertext
let (pk, _) = pqcrypto_kyber::kyber1024::keypair();
let (ct_orig, _) = pqc_combo::encapsulate_shared_secret(&pk, &mut rand::thread_rng()).unwrap();
let ct_bytes = ct_orig.as_bytes();
let ct_new = KyberCiphertext::from_bytes(ct_bytes).unwrap();
assert_eq!(ct_orig.as_bytes(), ct_new.as_bytes());
// 2. Test Dilithium Signature
let (_, sk) = pqcrypto_dilithium::dilithium3::keypair();
let msg = b"test roundtrip";
let sig_orig = pqc_combo::sign_message(&sk, msg);
let sig_bytes = sig_orig.as_bytes();
let sig_new = DilithiumSignedMessage::from_bytes(sig_bytes).unwrap();
assert_eq!(sig_orig.as_bytes(), sig_new.as_bytes());
}
```