1use crate::errors;
19use crate::utils::compute_split_vars;
20use core::fmt;
21use tracing::warn;
22
23pub const MIN_PRODUCTION_BITS: usize = 128;
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum Error {
31 SecurityTooLow {
33 estimated_bits: usize,
34 min_bits: usize,
35 },
36
37 InsufficientLdtBlinding {
41 ldt_blinding_factor: usize,
42 num_queries: usize,
43 },
44}
45
46impl fmt::Display for Error {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::SecurityTooLow {
50 estimated_bits,
51 min_bits,
52 } => write!(
53 f,
54 "Security too low: estimated {estimated_bits} bits, but {min_bits} required",
55 ),
56 Self::InsufficientLdtBlinding {
57 ldt_blinding_factor,
58 num_queries,
59 } => write!(
60 f,
61 "ldt_blinding_factor ({ldt_blinding_factor}) must be >= num_queries ({num_queries})",
62 ),
63 }
64 }
65}
66
67#[derive(Clone, Copy, Debug)]
69pub struct SecurityMetrics {
70 pub relative_distance: f64,
73
74 pub num_queries: usize,
76
77 pub soundness_error: f64,
80
81 pub ldt_bits: usize,
84
85 pub security_bits: usize,
88
89 pub expansion_degree: usize,
92}
93
94#[derive(Clone, Debug)]
95pub struct Config {
96 pub expansion_degree: usize,
99
100 pub num_queries: usize,
102
103 pub matrix_seed: [u8; 32],
106
107 pub sumcheck_blinding_factor: usize,
110
111 pub ldt_blinding_factor: usize,
115
116 pub min_security_bits: usize,
119}
120
121impl Default for Config {
122 fn default() -> Self {
123 Self::prod()
124 }
125}
126
127impl Config {
128 pub fn prod() -> Self {
132 Self {
133 expansion_degree: 16,
134 num_queries: 160,
135 matrix_seed: [42u8; 32],
136 min_security_bits: MIN_PRODUCTION_BITS,
137 sumcheck_blinding_factor: 2,
138 ldt_blinding_factor: 200,
139 }
140 }
141
142 pub fn dev() -> Self {
146 Self {
147 num_queries: 4,
148 min_security_bits: 0,
149 ..Self::prod()
150 }
151 }
152
153 pub fn estimated_security_bits(&self, field_bits: usize) -> usize {
158 let delta = self.estimate_relative_distance();
159 let q = self.num_queries as f64;
160
161 let soundness_error = (1.0 - delta).powf(q);
162 let ldt_bits = (-soundness_error.log2()).floor() as usize;
163
164 ldt_bits.min(field_bits)
165 }
166
167 pub fn security_metrics(&self, field_bits: usize) -> SecurityMetrics {
169 let delta = self.estimate_relative_distance();
170 let q = self.num_queries as f64;
171 let soundness_error = (1.0 - delta).powf(q);
172 let ldt_bits = (-soundness_error.log2()).floor() as usize;
173
174 SecurityMetrics {
175 relative_distance: delta,
176 num_queries: self.num_queries,
177 soundness_error,
178 ldt_bits,
179 security_bits: ldt_bits.min(field_bits),
180 expansion_degree: self.expansion_degree,
181 }
182 }
183
184 pub fn check_security(&self, num_vars: usize, field_bits: usize) -> errors::Result<()> {
188 if self.min_security_bits > 0 && self.ldt_blinding_factor < self.num_queries {
191 return Err(Error::InsufficientLdtBlinding {
192 ldt_blinding_factor: self.ldt_blinding_factor,
193 num_queries: self.num_queries,
194 }
195 .into());
196 }
197
198 let split_vars = compute_split_vars(num_vars, self.num_queries);
199 let grid_cols = 1usize << split_vars;
200
201 if grid_cols > 0 && grid_cols < 128 && self.min_security_bits > 40 {
204 warn!("Grid width ({grid_cols}) too small for random expander guarantees");
205 }
206
207 if grid_cols > 0 && self.expansion_degree > grid_cols / 4 {
209 warn!(
210 "Expansion degree ({}) too large for grid width ({}), need < {}",
211 self.expansion_degree,
212 grid_cols,
213 grid_cols / 4
214 );
215 }
216
217 let est_bits = self.estimated_security_bits(field_bits);
218 if est_bits < self.min_security_bits {
219 return Err(Error::SecurityTooLow {
220 estimated_bits: est_bits,
221 min_bits: self.min_security_bits,
222 }
223 .into());
224 }
225
226 Ok(())
227 }
228
229 fn estimate_relative_distance(&self) -> f64 {
234 let d = self.expansion_degree as f64;
235 if d < 2.0 {
236 return 0.01;
237 }
238
239 let sqrt_term = 2.0 * (d - 1.0).sqrt();
240 let theoretical_delta = (d - sqrt_term) / d;
241
242 let correction_factor = if d >= 64.0 {
245 0.95
246 } else if d >= 32.0 {
247 0.90
248 } else if d >= 16.0 {
249 0.85
251 } else if d >= 8.0 {
252 0.75
253 } else {
254 0.60
255 };
256
257 (theoretical_delta * correction_factor).max(0.01)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn default_is_prod() {
267 assert_eq!(Config::default().min_security_bits, MIN_PRODUCTION_BITS);
268 assert_eq!(Config::default().num_queries, Config::prod().num_queries);
269 }
270
271 #[test]
272 fn prod_meets_production_floor() {
273 let prod = Config::prod();
274
275 assert!(prod.estimated_security_bits(128) >= MIN_PRODUCTION_BITS);
276 assert!(prod.check_security(10, 128).is_ok());
277 }
278
279 #[test]
280 fn dev_is_lenient_on_weak_params() {
281 let dev = Config::dev();
282
283 assert!(dev.estimated_security_bits(128) < MIN_PRODUCTION_BITS);
284 assert!(dev.check_security(10, 128).is_ok());
285 }
286
287 #[test]
288 fn prod_threshold_rejects_weak_queries() {
289 let weak = Config {
290 num_queries: 4,
291 ..Config::prod()
292 };
293
294 assert!(weak.check_security(10, 128).is_err());
295 }
296}