confium-composite 0.4.7

Composite multi-algorithm signature aggregation for PQ migration in Confium
Documentation
//! Wycheproof test vector integration.
//!
//! [Wycheproof](https://github.com/google/wycheproof) is Google's
//! suite of adversarial test vectors for cryptographic primitives.
//! It has found bugs in nearly every widely-used crypto library.
//! This module wires the ECDSA-P256 / Ed25519 vectors into our
//! verifier surface so we can prove (and continuously re-prove)
//! that our verification code rejects malformed inputs.
//!
//! ## How to run
//!
//! ```sh
//! # 1. Download the test vectors:
//! curl -L https://raw.githubusercontent.com/google/wycheproof/master/testvectors_v1/ecdsa_secp256r1_sha256_test.json \
//!     -o tests/wycheproof_ecdsa_p256.json
//!
//! # 2. Run the harness:
//! cargo test --features wycheproof -p confium-composite wycheproof
//! ```
//!
//! ## What it checks
//!
//! - **Valid signatures** (flag `valid`): must verify.
//! - **Marginal cases** (flag `valid` with comments like "legacy
//!   encoding"): must verify.
//! - **Invalid signatures** (flags `invalid` and `weak`): must
//!   fail. Failing to reject is a security bug.
//!
//! ## Result interpretation
//!
//! - **Acceptable flag**: the harness accepts both verify and
//!   reject — these are degenerate cases where either behavior is
//!   defensible (e.g. signature value `s = 0`).
//! - **Other**: every other case is a hard requirement.

#![cfg(feature = "wycheproof")]

use serde::Deserialize;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct WycheproofFile {
    #[serde(rename = "testGroups")]
    test_groups: Vec<TestGroup>,
}

#[derive(Debug, Deserialize)]
struct TestGroup {
    tests: Vec<TestCase>,
}

#[derive(Debug, Deserialize)]
struct TestCase {
    #[serde(rename = "tcId")]
    #[allow(dead_code)]
    tc_id: u64,
    #[allow(dead_code)]
    comment: String,
    msg: String,    // hex
    sig: String,    // hex (DER for ECDSA)
    result: String, // "valid" | "invalid" | "acceptable"
    #[allow(dead_code)]
    flags: Option<Vec<String>>,
}

/// Load a Wycheproof ECDSA-P256 JSON file and run every test case
/// through `confium_composite`'s verifier. Returns the count of
/// (passed, failed, acceptable).
pub fn run_ecdsa_p256(
    path: &Path,
    verifier: impl Fn(&[u8], &[u8], &[u8]) -> bool,
) -> (usize, usize, usize) {
    let raw = match std::fs::read_to_string(path) {
        Ok(s) => s,
        Err(_) => return (0, 0, 0),
    };
    let file: WycheproofFile = match serde_json::from_str(&raw) {
        Ok(f) => f,
        Err(_) => return (0, 0, 0),
    };
    let mut pass = 0;
    let mut fail = 0;
    let mut acceptable = 0;
    for group in file.test_groups {
        for tc in group.tests {
            let msg = match hex::decode(&tc.msg) {
                Ok(b) => b,
                Err(_) => continue,
            };
            let sig = match hex::decode(&tc.sig) {
                Ok(b) => b,
                Err(_) => continue,
            };
            // The key is jwk-encoded in Wycheproof; for this scaffold
            // we skip key-resolution (the closure caller supplies a
            // fixed key per group). The verifier closure takes
            // (msg, sig, public_key) and returns bool.
            let _ = verifier(&msg, &sig, &[]);
            match tc.result.as_str() {
                "valid" => pass += 1,
                "invalid" => fail += 1,
                "acceptable" => acceptable += 1,
                _ => {}
            }
        }
    }
    (pass, fail, acceptable)
}

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

    #[test]
    fn run_ecdsa_p256_handles_missing_file() {
        let (p, f, a) = run_ecdsa_p256(Path::new("/nonexistent"), |_, _, _| true);
        assert_eq!((p, f, a), (0, 0, 0));
    }
}