use crate::errors;
use crate::utils::compute_split_vars;
use core::fmt;
use tracing::warn;
pub const MIN_PRODUCTION_BITS: usize = 128;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Error {
SecurityTooLow {
estimated_bits: usize,
min_bits: usize,
},
InsufficientLdtBlinding {
ldt_blinding_factor: usize,
num_queries: usize,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SecurityTooLow {
estimated_bits,
min_bits,
} => write!(
f,
"Security too low: estimated {estimated_bits} bits, but {min_bits} required",
),
Self::InsufficientLdtBlinding {
ldt_blinding_factor,
num_queries,
} => write!(
f,
"ldt_blinding_factor ({ldt_blinding_factor}) must be >= num_queries ({num_queries})",
),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct SecurityMetrics {
pub relative_distance: f64,
pub num_queries: usize,
pub soundness_error: f64,
pub ldt_bits: usize,
pub security_bits: usize,
pub expansion_degree: usize,
}
#[derive(Clone, Debug)]
pub struct Config {
pub expansion_degree: usize,
pub num_queries: usize,
pub matrix_seed: [u8; 32],
pub sumcheck_blinding_factor: usize,
pub ldt_blinding_factor: usize,
pub min_security_bits: usize,
}
impl Default for Config {
fn default() -> Self {
Self::prod()
}
}
impl Config {
pub fn prod() -> Self {
Self {
expansion_degree: 16,
num_queries: 160,
matrix_seed: [42u8; 32],
min_security_bits: MIN_PRODUCTION_BITS,
sumcheck_blinding_factor: 2,
ldt_blinding_factor: 200,
}
}
pub fn dev() -> Self {
Self {
num_queries: 4,
min_security_bits: 0,
..Self::prod()
}
}
pub fn estimated_security_bits(&self, field_bits: usize) -> usize {
let delta = self.estimate_relative_distance();
let q = self.num_queries as f64;
let soundness_error = (1.0 - delta).powf(q);
let ldt_bits = (-soundness_error.log2()).floor() as usize;
ldt_bits.min(field_bits)
}
pub fn security_metrics(&self, field_bits: usize) -> SecurityMetrics {
let delta = self.estimate_relative_distance();
let q = self.num_queries as f64;
let soundness_error = (1.0 - delta).powf(q);
let ldt_bits = (-soundness_error.log2()).floor() as usize;
SecurityMetrics {
relative_distance: delta,
num_queries: self.num_queries,
soundness_error,
ldt_bits,
security_bits: ldt_bits.min(field_bits),
expansion_degree: self.expansion_degree,
}
}
pub fn check_security(&self, num_vars: usize, field_bits: usize) -> errors::Result<()> {
if self.min_security_bits > 0 && self.ldt_blinding_factor < self.num_queries {
return Err(Error::InsufficientLdtBlinding {
ldt_blinding_factor: self.ldt_blinding_factor,
num_queries: self.num_queries,
}
.into());
}
let split_vars = compute_split_vars(num_vars, self.num_queries);
let grid_cols = 1usize << split_vars;
if grid_cols > 0 && grid_cols < 128 && self.min_security_bits > 40 {
warn!("Grid width ({grid_cols}) too small for random expander guarantees");
}
if grid_cols > 0 && self.expansion_degree > grid_cols / 4 {
warn!(
"Expansion degree ({}) too large for grid width ({}), need < {}",
self.expansion_degree,
grid_cols,
grid_cols / 4
);
}
let est_bits = self.estimated_security_bits(field_bits);
if est_bits < self.min_security_bits {
return Err(Error::SecurityTooLow {
estimated_bits: est_bits,
min_bits: self.min_security_bits,
}
.into());
}
Ok(())
}
fn estimate_relative_distance(&self) -> f64 {
let d = self.expansion_degree as f64;
if d < 2.0 {
return 0.01;
}
let sqrt_term = 2.0 * (d - 1.0).sqrt();
let theoretical_delta = (d - sqrt_term) / d;
let correction_factor = if d >= 64.0 {
0.95
} else if d >= 32.0 {
0.90
} else if d >= 16.0 {
0.85
} else if d >= 8.0 {
0.75
} else {
0.60
};
(theoretical_delta * correction_factor).max(0.01)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_prod() {
assert_eq!(Config::default().min_security_bits, MIN_PRODUCTION_BITS);
assert_eq!(Config::default().num_queries, Config::prod().num_queries);
}
#[test]
fn prod_meets_production_floor() {
let prod = Config::prod();
assert!(prod.estimated_security_bits(128) >= MIN_PRODUCTION_BITS);
assert!(prod.check_security(10, 128).is_ok());
}
#[test]
fn dev_is_lenient_on_weak_params() {
let dev = Config::dev();
assert!(dev.estimated_security_bits(128) < MIN_PRODUCTION_BITS);
assert!(dev.check_security(10, 128).is_ok());
}
#[test]
fn prod_threshold_rejects_weak_queries() {
let weak = Config {
num_queries: 4,
..Config::prod()
};
assert!(weak.check_security(10, 128).is_err());
}
}