northroot-proof-engine 0.3.0

Core proof computation and validation library for PoSH (Proof of Shape) and PoX (Proof of Execution)
Documentation
//! Fuzz tests for v0.2 engine
//!
//! Property-based and adversarial testing to ensure robustness
//! against edge cases, malformed inputs, and adversarial data.

use northroot_proof_engine::{
    canonicalize_span_shape, compute_data_shape_hash, compute_sha256, normalize_timestamp,
    validate_iso_time, validate_sha256_format, validate_span_id, validate_trace_id, SpanShape,
};
use proptest::prelude::*;

proptest! {
    #![proptest_config(ProptestConfig {
        cases: 100,
        failure_persistence: None,
        ..ProptestConfig::default()
    })]

    #[test]
    fn fuzz_canonicalization_no_panic(
        tool_id in "[a-zA-Z0-9._-]{0,50}",
        tool_version in "[a-zA-Z0-9._-]{0,20}",
        checker_ref in prop::option::of("[a-zA-Z0-9:@-]{0,50}")
    ) {
        let shape = SpanShape {
            tool_id,
            tool_version,
            param_schema: serde_json::json!({"key": "value"}),
            checker_ref,
        };
        // Should not panic
        let _result = canonicalize_span_shape(&shape);
    }

    #[test]
    fn fuzz_hash_computation_deterministic(
        text in "[a-zA-Z0-9 ]{0,1000}"
    ) {
        let hash1 = compute_sha256(&text);
        let hash2 = compute_sha256(&text);
        assert_eq!(hash1, hash2);
        assert!(hash1.starts_with("sha256:"));
        assert_eq!(hash1.len(), 71);
    }

    #[test]
    fn fuzz_validation_no_panic(
        id in "[a-zA-Z0-9_-]{0,200}"
    ) {
        let _trace_result = validate_trace_id(&id);
        let _span_result = validate_span_id(&id);
    }

    #[test]
    fn fuzz_timestamp_normalization_no_panic(
        timestamp in "[a-zA-Z0-9:+-.TZ]{0,100}"
    ) {
        let _result = normalize_timestamp(&timestamp);
    }

    #[test]
    fn fuzz_sha256_format_validation(
        hash_str in "[a-zA-Z0-9:]{0,200}"
    ) {
        let _result = validate_sha256_format(&hash_str);
    }

    #[test]
    fn fuzz_iso_time_validation(
        time_str in "[a-zA-Z0-9:+-.TZ]{0,100}"
    ) {
        let _result = validate_iso_time(&time_str);
    }

    #[test]
    fn fuzz_data_shape_hash_no_panic(
        schema_str in "[a-zA-Z0-9{}:,\"\\[\\] ]{0,500}"
    ) {
        if let Ok(schema_json) = serde_json::from_str::<serde_json::Value>(&schema_str) {
            let _result = compute_data_shape_hash(&schema_json, None);
        }
    }
}

#[test]
fn fuzz_signature_verification_malformed_data() {
    use northroot_proof_engine::{verify_signature, Signature};

    // Test with malformed signature data
    let malformed_sigs = vec![
        Signature {
            alg: "ed25519".to_string(),
            kid: "".to_string(), // Empty kid
            sig: "invalid".to_string(),
        },
        Signature {
            alg: "invalid".to_string(), // Invalid algorithm
            kid: "did:key:zTest".to_string(),
            sig: "dGVzdA".to_string(),
        },
        Signature {
            alg: "ed25519".to_string(),
            kid: "did:key:zTest".to_string(),
            sig: "not-base64url".to_string(), // Invalid encoding
        },
    ];

    let message = b"test message";
    let key_bytes = [0u8; 32]; // Dummy key bytes

    for sig in malformed_sigs {
        // Should return error, not panic
        let result = verify_signature(message, &sig, &key_bytes);
        assert!(result.is_err(), "Malformed signature should return error");
    }
}

#[test]
fn fuzz_validation_edge_cases() {
    // Empty strings
    assert!(validate_trace_id("").is_err());
    assert!(validate_span_id("").is_err());

    // Very long strings
    let long_id = "a".repeat(10000);
    let _ = validate_trace_id(&long_id); // Should not panic
    let _ = validate_span_id(&long_id); // Should not panic

    // Special characters
    let special_chars = "!@#$%^&*()[]{}|\\:;\"'<>?,./";
    assert!(validate_trace_id(special_chars).is_err());
    assert!(validate_span_id(special_chars).is_err());

    // Unicode (should be handled gracefully)
    let unicode = "测试_123";
    let _ = validate_trace_id(unicode); // Should not panic
    let _ = validate_span_id(unicode); // Should not panic
}

#[test]
fn fuzz_canonicalization_determinism() {
    use northroot_proof_engine::canonicalize_span_shape;

    let shape = SpanShape {
        tool_id: "test".to_string(),
        tool_version: "1".to_string(),
        param_schema: serde_json::json!({"key": "value"}),
        checker_ref: None,
    };

    // Run canonicalization multiple times
    let results: Vec<_> = (0..100).map(|_| canonicalize_span_shape(&shape)).collect();

    // All should succeed
    for result in &results {
        assert!(result.is_ok());
    }

    // All should be identical
    let first = results[0].as_ref().unwrap();
    for result in &results[1..] {
        assert_eq!(first, result.as_ref().unwrap());
    }
}

#[test]
fn fuzz_hash_performance_bounds() {
    use std::time::Instant;

    let test_data = "x".repeat(10000); // 10KB of data

    let start = Instant::now();
    let _hash = compute_sha256(&test_data);
    let duration = start.elapsed();

    // Hash computation should be reasonably fast (< 1ms for 10KB)
    // This is a sanity check, not a strict performance requirement
    assert!(duration.as_millis() < 100, "Hash computation took too long");
}