1use crate::errors;
19use crate::utils::compute_split_vars;
20use core::fmt;
21use tracing::warn;
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum Error {
26 SecurityTooLow {
28 estimated_bits: usize,
29 min_bits: usize,
30 },
31
32 InsufficientLdtBlinding {
36 ldt_blinding_factor: usize,
37 num_queries: usize,
38 },
39}
40
41impl fmt::Display for Error {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Self::SecurityTooLow {
45 estimated_bits,
46 min_bits,
47 } => write!(
48 f,
49 "Security too low: estimated {estimated_bits} bits, but {min_bits} required",
50 ),
51 Self::InsufficientLdtBlinding {
52 ldt_blinding_factor,
53 num_queries,
54 } => write!(
55 f,
56 "ldt_blinding_factor ({ldt_blinding_factor}) must be >= num_queries ({num_queries})",
57 ),
58 }
59 }
60}
61
62#[derive(Clone, Copy, Debug)]
64pub struct SecurityMetrics {
65 pub relative_distance: f64,
68
69 pub num_queries: usize,
71
72 pub soundness_error: f64,
75
76 pub ldt_bits: usize,
79
80 pub security_bits: usize,
83
84 pub expansion_degree: usize,
87}
88
89#[derive(Clone, Debug)]
90pub struct Config {
91 pub expansion_degree: usize,
94
95 pub num_queries: usize,
97
98 pub matrix_seed: [u8; 32],
101
102 pub sumcheck_blinding_factor: usize,
105
106 pub ldt_blinding_factor: usize,
110
111 pub min_security_bits: usize,
114}
115
116impl Default for Config {
117 fn default() -> Self {
118 Self {
119 expansion_degree: 16,
120 num_queries: 160,
121 matrix_seed: [42u8; 32],
122 min_security_bits: 99,
123 sumcheck_blinding_factor: 2,
124 ldt_blinding_factor: 200,
125 }
126 }
127}
128
129impl Config {
130 pub fn estimated_security_bits(&self, field_bits: usize) -> usize {
135 let delta = self.estimate_relative_distance();
136 let q = self.num_queries as f64;
137
138 let soundness_error = (1.0 - delta).powf(q);
139 let ldt_bits = (-soundness_error.log2()).floor() as usize;
140
141 ldt_bits.min(field_bits)
142 }
143
144 pub fn security_metrics(&self, field_bits: usize) -> SecurityMetrics {
146 let delta = self.estimate_relative_distance();
147 let q = self.num_queries as f64;
148 let soundness_error = (1.0 - delta).powf(q);
149 let ldt_bits = (-soundness_error.log2()).floor() as usize;
150
151 SecurityMetrics {
152 relative_distance: delta,
153 num_queries: self.num_queries,
154 soundness_error,
155 ldt_bits,
156 security_bits: ldt_bits.min(field_bits),
157 expansion_degree: self.expansion_degree,
158 }
159 }
160
161 pub fn check_security(&self, num_vars: usize, field_bits: usize) -> errors::Result<()> {
165 if self.ldt_blinding_factor < self.num_queries {
166 return Err(Error::InsufficientLdtBlinding {
167 ldt_blinding_factor: self.ldt_blinding_factor,
168 num_queries: self.num_queries,
169 }
170 .into());
171 }
172
173 let split_vars = compute_split_vars(num_vars, self.num_queries);
174 let grid_cols = 1usize << split_vars;
175
176 if grid_cols > 0 && grid_cols < 128 && self.min_security_bits > 40 {
179 warn!("Grid width ({grid_cols}) too small for random expander guarantees");
180 }
181
182 if grid_cols > 0 && self.expansion_degree > grid_cols / 4 {
184 warn!(
185 "Expansion degree ({}) too large for grid width ({}), need < {}",
186 self.expansion_degree,
187 grid_cols,
188 grid_cols / 4
189 );
190 }
191
192 let est_bits = self.estimated_security_bits(field_bits);
193 if est_bits < self.min_security_bits {
194 return Err(Error::SecurityTooLow {
195 estimated_bits: est_bits,
196 min_bits: self.min_security_bits,
197 }
198 .into());
199 }
200
201 Ok(())
202 }
203
204 fn estimate_relative_distance(&self) -> f64 {
209 let d = self.expansion_degree as f64;
210 if d < 2.0 {
211 return 0.01;
212 }
213
214 let sqrt_term = 2.0 * (d - 1.0).sqrt();
215 let theoretical_delta = (d - sqrt_term) / d;
216
217 let correction_factor = if d >= 64.0 {
220 0.95
221 } else if d >= 32.0 {
222 0.90
223 } else if d >= 16.0 {
224 0.85
226 } else if d >= 8.0 {
227 0.75
228 } else {
229 0.60
230 };
231
232 (theoretical_delta * correction_factor).max(0.01)
233 }
234}