pqc / rust-engine /src /validator.rs
wuhp's picture
Update rust-engine/src/validator.rs
2ae5160 verified
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Severity {
Critical,
Warning,
Info,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ParamDiagnostic {
pub severity: Severity,
pub message: String,
pub suggested_fix: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ValidationResult {
pub n: usize,
pub q: u64,
pub beta_or_sigma: f64,
pub is_cbd: bool,
pub noise_budget_max: f64,
pub noise_budget_rms: f64,
pub decryption_budget: f64,
pub failure_prob: f64,
pub expected_failures_in_500: f64,
pub passes: bool,
pub diagnostics: Vec<ParamDiagnostic>,
pub suggested_beta: f64,
pub suggested_q: u64,
}
pub struct ParamValidator;
impl ParamValidator {
pub fn validate_lwe(n: usize, q: u64, beta: f64) -> ValidationResult {
Self::validate_inner(n, q, beta, false)
}
pub fn validate_kyber(n: usize, q: u64, eta: usize) -> ValidationResult {
let mut r = Self::validate_inner(n, q, eta as f64, true);
r.beta_or_sigma = eta as f64;
r
}
pub fn validate_gaussian(n: usize, q: u64, sigma: f64) -> ValidationResult {
Self::validate_inner(n, q, sigma, false)
}
fn validate_inner(n: usize, q: u64, noise_param: f64, is_cbd: bool) -> ValidationResult {
let q_f = q as f64;
let n_f = n as f64;
let std_per_coeff = if is_cbd {
(noise_param / 2.0).sqrt()
} else {
noise_param
};
let beta_max = if is_cbd { noise_param } else { 3.0 * std_per_coeff };
let noise_budget_rms = std_per_coeff * (3.0 * n_f).sqrt() * beta_max;
let noise_budget_max = beta_max * beta_max * n_f * 3.0;
let decryption_budget = q_f / 4.0;
let total_std = std_per_coeff * beta_max * (3.0 * n_f).sqrt();
let z = decryption_budget / total_std.max(1e-10);
let failure_prob = 2.0 * gaussian_q_function(z);
let expected_failures_in_500 = failure_prob * 500.0;
let passes = noise_budget_rms < decryption_budget * 0.8;
let mut diagnostics = Vec::new();
if noise_budget_max >= decryption_budget && expected_failures_in_500 > 1e-3 {
diagnostics.push(ParamDiagnostic {
severity: Severity::Critical,
message: format!(
"WORST-CASE noise ({:.1}) EXCEEDS expected limit and probability of failure is high. Decryption WILL fail for some inputs.",
noise_budget_max
),
suggested_fix: Some(format!(
"Either: (a) reduce β to ≤{:.1}, or (b) increase q to ≥{}",
(decryption_budget / (n_f * 3.0)).sqrt(),
(noise_budget_max * 4.0).ceil() as u64
)),
});
} else if noise_budget_max >= decryption_budget {
diagnostics.push(ParamDiagnostic {
severity: Severity::Info,
message: format!(
"WORST-CASE noise bound ({:.1}) exceeds budget ({:.1}), but failure probability is low. Typical for practical RLWE.",
noise_budget_max, decryption_budget
),
suggested_fix: None,
});
} else if noise_budget_max > decryption_budget * 0.85 {
diagnostics.push(ParamDiagnostic {
severity: Severity::Warning,
message: format!(
"WORST-CASE noise ({:.1}) is dangerously close to budget ({:.1}). Rare failures may occur.",
noise_budget_max, decryption_budget
),
suggested_fix: Some("Increase q or decrease noise to increase margin".to_string()),
});
} else if noise_budget_rms >= decryption_budget * 0.5 {
diagnostics.push(ParamDiagnostic {
severity: Severity::Warning,
message: format!(
"RMS noise ({:.1}) is {:.0}% of budget ({:.1}). Failure rate ≈{:.2}%. Marginal parameters.",
noise_budget_rms,
100.0 * noise_budget_rms / decryption_budget,
decryption_budget,
failure_prob * 100.0
),
suggested_fix: Some("Increase q by 2x or decrease β by √2".to_string()),
});
} else {
diagnostics.push(ParamDiagnostic {
severity: Severity::Info,
message: format!(
"Noise budget OK: RMS noise {:.1} is {:.0}% of q/4={:.1}.",
noise_budget_rms,
100.0 * noise_budget_rms / decryption_budget,
decryption_budget
),
suggested_fix: None,
});
}
let estimated_security = estimate_security_bits(n, q, std_per_coeff);
if estimated_security < 80.0 {
diagnostics.push(ParamDiagnostic {
severity: Severity::Critical,
message: format!(
"Parameters provide only ~{:.0} bits of security (need ≥128).",
estimated_security
),
suggested_fix: Some("Use n≥256 and q≥3329 for any security claim".to_string()),
});
}
let suggested_beta = (decryption_budget / (n_f * 3.0)).sqrt() * 0.5;
let suggested_q = (noise_budget_max * 8.0).ceil() as u64;
ValidationResult {
n,
q,
beta_or_sigma: noise_param,
is_cbd,
noise_budget_max,
noise_budget_rms,
decryption_budget,
failure_prob,
expected_failures_in_500,
passes,
diagnostics,
suggested_beta,
suggested_q,
}
}
}
fn gaussian_q_function(z: f64) -> f64 {
if z > 10.0 { return 0.0; }
if z < 0.0 { return 1.0; }
let t = 1.0 / (1.0 + 0.2316419 * z);
let poly = t * (0.319381530
+ t * (-0.356563782
+ t * (1.781477937
+ t * (-1.821255978
+ t * 1.330274429))));
let phi = (-z * z / 2.0).exp() / (2.0 * std::f64::consts::PI).sqrt();
phi * poly
}
fn estimate_security_bits(n: usize, q: u64, sigma: f64) -> f64 {
let n_f = n as f64;
let q_f = q as f64;
let alpha = sigma / q_f;
if alpha <= 0.0 { return 0.0; }
0.265 * n_f * (-alpha.log2())
}