spectral_vm 0.1.6

HYPERION: Production-ready zero-knowledge virtual machine with spectral analysis
Documentation
/*
 * ═══════════════════════════════════════════════════════════════════════════
 * TECHNICAL MANIFEST: Spectral Privacy (PARSEVAL-COMPATIBLE)
 * SOVEREIGN SPECTRAL ROLE: Zero-Knowledge Property Enforcement
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * COMPLEXITY: O(k) for k blinding points
 * FIELD: Goldilocks (2^64 - 2^32 + 1)
 * DOMAIN: Time Domain trace extension
 *
 * ARCHITECTURAL INVARIANTS:
 * - Blinding points are APPENDED to trace (never overwrite execution data)
 * - BOOLEAN BLINDING: Points in {0, 1} for Parseval compatibility
 * - Total trace size MUST remain power of two for FWHT
 *
 * SECURITY PROPERTIES:
 * - Information-theoretic ZK: Query paths reveal zero trace information
 * - FRI Compatibility: Blinding preserves folding soundness
 * - PARSEVAL COMPATIBLE: Boolean blinding preserves energy equation
 * ═══════════════════════════════════════════════════════════════════════════
 */

use crate::field::Goldilocks;
use rand::{Rng, rng};

/// Spectral Privacy Layer.
/// Provides Zero-Knowledge guarantees through Parseval-compatible entropy shielding.
pub struct SpectralPrivacy;

/// Blinding metadata for Parseval verification.
#[derive(Debug, Clone)]
pub struct BlindingMetadata {
    /// Number of blinding points added.
    pub count: usize,
    /// Hamming weight of blinding points (sum of 1s).
    pub hamming_weight: u64,
    /// Energy contribution: For boolean, equals hamming_weight.
    pub energy: Goldilocks,
}

impl SpectralPrivacy {
    /// Boolean Entropy Shielding (PARSEVAL-COMPATIBLE).
    /// CRITICAL: Blinding points are restricted to {0, 1} to preserve
    /// the Parseval energy equation: E_total = Hamming_weight * N
    ///
    /// ZK GUARANTEE: Random boolean distribution still hides trace structure
    /// because verifier cannot distinguish trace 1s from blinding 1s.
    ///
    /// COMPLEXITY: O(k) where k = number of blinding points.
    /// RETURNS: BlindingMetadata for energy verification.
    pub fn shield_with_boolean_entropy(trace: &mut Vec<Goldilocks>, k: usize) -> BlindingMetadata {
        let mut hamming_weight = 0u64;

        for _ in 0..k {
            // Random boolean: 0 or 1
            let rand_bit: u64 = rng().random::<bool>() as u64;
            trace.push(Goldilocks::new(rand_bit));
            hamming_weight += rand_bit;
        }

        // For boolean values: energy = hamming_weight (since 1² = 1, 0² = 0)
        let energy = Goldilocks::new(hamming_weight);

        BlindingMetadata {
            count: k,
            hamming_weight,
            energy,
        }
    }

    /// Legacy method: FRI-aware entropy shielding (NON-BOOLEAN).
    /// WARNING: This breaks Parseval invariant! Use only when Parseval check is disabled.
    #[deprecated(note = "Use shield_with_boolean_entropy for Parseval compatibility")]
    pub fn shield_with_entropy(trace: &mut Vec<Goldilocks>, k: usize) {
        for _ in 0..k {
            let rand_val: u64 = rng().random();
            let noise = Goldilocks::new(rand_val);
            trace.push(noise);
        }
    }

    /// Pads trace to next power of two with boolean blinding.
    /// RETURNS: Total BlindingMetadata for all padding operations.
    pub fn pad_to_power_of_two(trace: &mut Vec<Goldilocks>) -> BlindingMetadata {
        let target_len = trace.len().next_power_of_two();
        let padding_needed = target_len - trace.len();

        if padding_needed == 0 {
            return BlindingMetadata {
                count: 0,
                hamming_weight: 0,
                energy: Goldilocks::new(0),
            };
        }

        Self::shield_with_boolean_entropy(trace, padding_needed)
    }

    /// Backward compatibility: Legacy method name.
    #[deprecated(note = "Use shield_with_boolean_entropy instead")]
    pub fn add_blinding_points(trace: &mut Vec<Goldilocks>, k: usize) {
        #[allow(deprecated)]
        Self::shield_with_entropy(trace, k);
    }

    /// Applies Zero-Knowledge Blinding to Spectral Manifolds while preserving linear constraints.
    ///
    /// Given a set of Manifolds [M_0, M_1, ..., M_k], and coefficients [c_0, ..., c_k]
    /// such that sum(c_i * M_i) = 0 (Constraint), this function injects random noise B_i
    /// such that sum(c_i * B_i) = 0.
    ///
    /// ALGORITHM:
    /// 1. Generate random noise B_0, ..., B_{k-1}.
    /// 2. Compute B_k = - (1/c_k) * sum_{i=0}^{k-1} (c_i * B_i).
    /// 3. Add B_i to M_i.
    ///
    /// SECURITY:
    /// - Adds (k-1)*N bits of entropy (scaled by field size).
    /// - Preserves linear relationships exactly.
    pub fn apply_linear_constraint_preserving_blind(
        manifolds: &mut [crate::signal::SpectralManifold],
        coeffs: &[Goldilocks],
    ) {
        assert_eq!(manifolds.len(), coeffs.len());
        let k = manifolds.len();
        assert!(k >= 2, "Need at least 2 manifolds to balance equation");

        let n = manifolds[0].values.len();
        // Check all same length
        for m in manifolds.iter() {
            assert_eq!(
                m.values.len(),
                n,
                "All manifolds must have same spectral length"
            );
        }

        // Prepare random noise vectors for 0..k-1
        // We accumulate the weighted sum to compute the last noise vector
        let mut weighted_sum = vec![Goldilocks::new(0); n];
        let mut noises: Vec<Vec<Goldilocks>> = Vec::with_capacity(k);

        for i in 0..k - 1 {
            let mut noise_vec = Vec::with_capacity(n);
            for j in 0..n {
                let r = Goldilocks::new(rng().random());
                noise_vec.push(r);

                // Accumulate: weighted_sum[j] += c_i * r
                let term = coeffs[i].mul(r);
                weighted_sum[j] = weighted_sum[j].add(term);
            }
            noises.push(noise_vec);
        }

        // Compute B_k
        // c_k * B_k = -weighted_sum
        // B_k = (-weighted_sum) * inv(c_k)
        let c_k = coeffs[k - 1];
        let inv_c_k = c_k.inv();

        let mut final_noise = Vec::with_capacity(n);
        for j in 0..n {
            let sum = weighted_sum[j];
            // Negate sum: 0 - sum
            let neg_sum = Goldilocks::new(0).sub(sum);
            let b_k = neg_sum.mul(inv_c_k);
            final_noise.push(b_k);
        }
        noises.push(final_noise);

        // Apply noise
        for (i, m) in manifolds.iter_mut().enumerate() {
            for j in 0..n {
                m.values[j] = m.values[j].add(noises[i][j]);
            }
        }
    }
}

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

    #[test]
    fn test_boolean_blinding_energy() {
        let mut trace = vec![Goldilocks::new(1), Goldilocks::new(0)];
        let meta = SpectralPrivacy::shield_with_boolean_entropy(&mut trace, 10);

        // All blinding values must be 0 or 1
        for i in 2..trace.len() {
            assert!(
                trace[i].0 == 0 || trace[i].0 == 1,
                "Blinding point must be boolean"
            );
        }

        // Energy should equal hamming weight
        assert_eq!(meta.energy.0, meta.hamming_weight);
    }

    #[test]
    fn test_pad_to_power_of_two() {
        let mut trace = vec![Goldilocks::new(1); 5];
        let meta = SpectralPrivacy::pad_to_power_of_two(&mut trace);

        assert_eq!(trace.len(), 8, "Should pad to 8");
        assert_eq!(meta.count, 3, "Should add 3 padding points");
    }
}