pqc / src /lib /validator.ts
wuhp's picture
Create validator.ts
7da464c verified
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));
}
}