export enum Severity { Critical = "Critical", Warning = "Warning", Info = "Info", } export interface ParamDiagnostic { severity: Severity; message: String; suggestedFix?: String; } export interface ValidationResult { n: number; q: number; betaOrSigma: number; isCbd: boolean; noiseBudgetMax: number; noiseBudgetRms: number; decryptionBudget: number; failureProb: number; expectedFailuresIn500: number; passes: boolean; diagnostics: ParamDiagnostic[]; suggestedBeta: number; suggestedQ: number; } export class ParamValidator { public static validateKyber(n: number, q: number, eta: number): ValidationResult { const sigma = Math.sqrt(eta / 2.0); const r = this.validateInner(n, q, sigma, true); r.betaOrSigma = eta; return r; } public static validateGaussian(n: number, q: number, sigma: number): ValidationResult { return this.validateInner(n, q, sigma, false); } private static validateInner(n: number, q: number, noiseParam: number, isCbd: boolean): ValidationResult { const qF = q; const nF = n; const stdPerCoeff = isCbd ? Math.sqrt(noiseParam / 2.0) : noiseParam; const betaMax = isCbd ? noiseParam : 3.0 * stdPerCoeff; // noise ≈ beta * sqrt(3 * n) * std_per_coeff const noiseBudgetRms = stdPerCoeff * Math.sqrt(3.0 * nF) * betaMax; const noiseBudgetMax = betaMax * betaMax * nF * 3.0; const decryptionBudget = qF / 4.0; const totalStd = stdPerCoeff * betaMax * Math.sqrt(3.0 * nF); const z = decryptionBudget / Math.max(totalStd, 1e-10); const failureProb = 2.0 * this.gaussianQFunction(z); const expectedFailuresIn500 = failureProb * 500.0; const passes = noiseBudgetRms < decryptionBudget * 0.8; const diagnostics: ParamDiagnostic[] = []; if (noiseBudgetMax >= decryptionBudget) { diagnostics.push({ severity: Severity.Critical, message: `WORST-CASE noise (${noiseBudgetMax.toFixed(1)}) EXCEEDS decryption budget (${decryptionBudget.toFixed(1)} = q/4). Decryption WILL fail for some inputs.`, suggestedFix: `Either: (a) reduce β to ≤${Math.sqrt(decryptionBudget / (nF * 3.0)).toFixed(1)}, or (b) increase q to ≥${Math.ceil(noiseBudgetMax * 4.0)}`, }); } else if (noiseBudgetRms >= decryptionBudget * 0.5) { diagnostics.push({ severity: Severity.Warning, message: `RMS noise (${noiseBudgetRms.toFixed(1)}) is ${(100.0 * noiseBudgetRms / decryptionBudget).toFixed(0)}% of budget. Failure rate ≈${(failureProb * 100).toFixed(2)}%.`, suggestedFix: "Increase q by 2x or decrease β by √2", }); } else { diagnostics.push({ severity: Severity.Info, message: `Noise budget OK: RMS noise ${noiseBudgetRms.toFixed(1)} is ${(100.0 * noiseBudgetRms / decryptionBudget).toFixed(0)}% of q/4.`, }); } const estimatedSecurity = this.estimateSecurityBits(n, q, stdPerCoeff); if (estimatedSecurity < 80.0) { diagnostics.push({ severity: Severity.Critical, message: `Parameters provide only ~${estimatedSecurity.toFixed(0)} bits of security (need ≥128).`, suggestedFix: "Use n≥256 and q≥3329 for any security claim", }); } return { n, q, betaOrSigma: noiseParam, isCbd, noiseBudgetMax, noiseBudgetRms, decryptionBudget, failureProb, expectedFailuresIn500, passes, diagnostics, suggestedBeta: Math.sqrt(decryptionBudget / (nF * 3.0)) * 0.5, suggestedQ: Math.ceil(noiseBudgetMax * 8.0), }; } private static gaussianQFunction(z: number): number { if (z > 10.0) return 0.0; if (z < 0.0) return 1.0; const t = 1.0 / (1.0 + 0.2316419 * z); const poly = t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429)))); const phi = Math.exp(-z * z / 2.0) / Math.sqrt(2.0 * Math.PI); return phi * poly; } private static estimateSecurityBits(n: number, q: number, sigma: number): number { const nF = n; const qF = q; const alpha = sigma / qF; if (alpha <= 0.0) return 0.0; return 0.265 * nF * (-Math.log2(alpha)); } }