labrador/relation/
env_params.rs

1#![allow(clippy::as_conversions)]
2use crate::ring::{rq::Rq, zq::Zq};
3
4/// Security Parameter
5pub const SECURITY_PARAMETER: usize = 128;
6pub const OPERATOR_NORM: f64 = 15.0;
7
8// Example Environment parameters used for LaBRADOR, can be expanded as required by testing.
9#[derive(Clone)]
10pub struct EnvironmentParameters {
11    /// Relation R Parameters
12    pub rank: usize, // size of each witness s_i
13    pub multiplicity: usize, // number of witness elements
14
15    /// Decomposition Parameters
16    pub b: Zq, // z decomposition base
17    pub b_1: usize, // t_i decomposition base
18    pub t_1: usize, // t_i number of parts
19    pub b_2: usize, // g_ij decomposition base
20    pub t_2: usize, // g_ij number of parts
21
22    /// Norm Bounds
23    pub beta: Zq, // Bound for witness s_i
24    pub gamma: Zq,   // Bound for z
25    pub gamma_1: Zq, // Bound for t
26    pub gamma_2: Zq, // Bound for g and h
27    pub beta_prime: Zq,
28
29    /// Commitment Matrices Sizes
30    pub kappa: usize, // Number of rows in A
31    pub kappa_1: usize, // Number of rows in B
32    pub kappa_2: usize, // Number of rows in C
33
34    /// Function Families Sizes
35    pub constraint_k: usize, // Number of constraints of the form f
36    pub constraint_l: usize,     // Number of constraints of the form f'
37    pub const_agg_length: usize, // Number of functions in the first aggregation step
38
39    /// Other Parameters
40    pub log_q: usize, // Size of log(q) in bits, where q is the modulo
41}
42
43impl EnvironmentParameters {
44    #[allow(clippy::too_many_arguments)]
45    pub fn new(
46        rank: usize,
47        multiplicity: usize,
48        beta: f64,
49        kappa: usize,
50        kappa_1: usize,
51        kappa_2: usize,
52        constraint_k: usize,
53        constraint_l: usize,
54        tau: f64,
55        modulo_q: usize,
56    ) -> Self {
57        let std_s = Self::compute_std_s(rank, multiplicity, beta);
58
59        // balanced radix for z‑split
60        let base_b = Self::compute_base_b(std_s, multiplicity, tau);
61
62        // Decomposition parameters for t and h
63        let parts_t1 = Self::compute_t1(modulo_q, base_b);
64        let base_b1 = Self::compute_b1(modulo_q, parts_t1);
65
66        // Decomposition parameters for g
67        let parts_t2 = Self::compute_t2(rank, std_s, base_b);
68        let base_b2 = Self::compute_b2(rank, std_s, parts_t2);
69
70        let gamma = Self::compute_gamma(beta, tau);
71        let gamma_1 =
72            Self::compute_gamma1(base_b1, parts_t1, multiplicity, kappa, base_b2, parts_t2);
73        let gamma_2 = Self::compute_gamma2(base_b1, parts_t1, multiplicity);
74        let beta_prime = Self::compute_beta_prime(base_b, gamma, gamma_1, gamma_2);
75        let log_q = (modulo_q as f64).log2() as usize;
76        EnvironmentParameters {
77            rank,
78            multiplicity,
79            b: Zq::new(base_b as u32),
80            b_1: base_b1,
81            t_1: parts_t1,
82            b_2: base_b2,
83            t_2: parts_t2,
84            beta: Zq::new(beta as u32),
85            gamma: Zq::new(gamma as u32),
86            gamma_1: Zq::new(gamma_1 as u32),
87            gamma_2: Zq::new(gamma_2 as u32),
88            beta_prime: Zq::new(beta_prime as u32),
89            kappa,
90            kappa_1,
91            kappa_2,
92            constraint_k,
93            constraint_l,
94            const_agg_length: usize::div_ceil(SECURITY_PARAMETER, log_q),
95            log_q, // Size of log(q) in bits, where q is the modulo
96        }
97    }
98
99    // s = β / √(r·n·d)
100    fn compute_std_s(n: usize, r: usize, beta: f64) -> f64 {
101        beta / ((r * n * Rq::DEGREE) as f64).sqrt()
102    }
103
104    /// b = round( √(√(12 r τ) · s) ), but at least 2
105    fn compute_base_b(s: f64, multiplicity: usize, tau: f64) -> usize {
106        let raw = ((12.0 * multiplicity as f64 * tau).sqrt() * s).sqrt();
107        let mut b = raw.round() as usize;
108        if b < 2 {
109            b = 2;
110        }
111        b
112    }
113
114    /// t₁ = round( log_q / log_b )
115    fn compute_t1(q: usize, b: usize) -> usize {
116        let log_q = (q as f64).ln();
117        let log_b = (b as f64).ln();
118        (log_q / log_b).round() as usize
119    }
120
121    /// b₁ = ceil( q^(1/t₁) )
122    fn compute_b1(q: usize, t1: usize) -> usize {
123        let root = (q as f64).powf(1.0 / t1 as f64);
124        root.ceil() as usize
125    }
126
127    /// t₂ = ceil( log(√(24 n d) · s^2) / log(b) )
128    fn compute_t2(n: usize, std_s: f64, b: usize) -> usize {
129        (((24.0 * n as f64 * Rq::DEGREE as f64).sqrt() * std_s * std_s).ln() / (b as f64).ln())
130            .round() as usize
131    }
132
133    /// t₂ = ceil( log(√(24 n d) · s^2) / log(b) )
134    fn compute_b2(n: usize, std_s: f64, t2: usize) -> usize {
135        ((24.0 * n as f64 * Rq::DEGREE as f64).sqrt() * std_s * std_s)
136            .powf(1.0 / t2 as f64)
137            .round() as usize
138    }
139
140    /// γ = β·√τ
141    fn compute_gamma(beta: f64, tau: f64) -> f64 {
142        beta * (tau).sqrt()
143    }
144
145    /// γ₁ = √( (b₁² t₁ /12) · r κ d  +  (b₂² t₂ /12) · (r²+r)/2 · d )
146    #[allow(clippy::too_many_arguments)]
147    fn compute_gamma1(
148        b1: usize,
149        t1: usize,
150        multiplicity: usize,
151        kappa: usize,
152        b2: usize,
153        t2: usize,
154    ) -> f64 {
155        let term1 =
156            (b1.pow(2) * t1) as f64 / 12.0 * multiplicity as f64 * kappa as f64 * Rq::DEGREE as f64;
157        let term2 = (b2.pow(2) * t2) as f64 / 12.0
158            * ((multiplicity * multiplicity + multiplicity) as f64)
159            / 2.0
160            * Rq::DEGREE as f64;
161        (term1 + term2).sqrt()
162    }
163
164    /// γ₂ = √( (b₁² t₁ /12) · (r²+r)/2 · d )
165    fn compute_gamma2(b1: usize, t1: usize, multiplicity: usize) -> f64 {
166        let term = (b1.pow(2) * t1) as f64 / 12.0
167            * ((multiplicity * multiplicity + multiplicity) as f64)
168            / 2.0
169            * Rq::DEGREE as f64;
170        term.sqrt()
171    }
172
173    /// β' = √( 2 γ² / b²  +  γ₁²  +  γ₂² )
174    fn compute_beta_prime(b: usize, gamma: f64, gamma1: f64, gamma2: f64) -> f64 {
175        let part_z = 2.0 * (gamma * gamma) / (b * b) as f64;
176        (part_z + gamma1 * gamma1 + gamma2 * gamma2).sqrt()
177    }
178}
179
180impl Default for EnvironmentParameters {
181    fn default() -> Self {
182        Self::new(5, 3, 65535.0, 4, 5, 5, 5, 5, 65535.0, (1u64 << 32) as usize)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use crate::ring::zq::Zq;
189
190    use super::EnvironmentParameters;
191    use super::Rq;
192
193    #[test]
194    fn test_std_s_matches_formula() {
195        let (n, r, beta) = (12, 5, 42.0);
196        let result = EnvironmentParameters::compute_std_s(n, r, beta);
197        let expected = beta / ((r * n * Rq::DEGREE) as f64).sqrt();
198        assert_eq!(result, expected);
199    }
200
201    #[test]
202    fn test_compute_base_b_not_below_two() {
203        let b = EnvironmentParameters::compute_base_b(0.001, 1, 0.0001);
204        assert!(b >= 2);
205    }
206
207    #[test]
208    fn test_base_b_formula_and_floor() {
209        let s = 0.17;
210        let (r, tau) = (3, 12.0);
211        let result = EnvironmentParameters::compute_base_b(s, r, tau);
212        let expected = ((12.0 * r as f64 * tau).sqrt() * s).sqrt().round().max(2.0) as usize;
213        assert_eq!(result, expected, "balanced radix b");
214    }
215
216    #[test]
217    fn test_t1_rounding() {
218        let (q, b) = (257usize, 4usize); // ln(q)/ln(b)=~4.006
219        let result = EnvironmentParameters::compute_t1(q, b);
220        assert_eq!(result, 4, "t1 = round(log_q / log_b)");
221    }
222
223    #[test]
224    fn test_b1_is_ceil_root() {
225        let (q, t1) = (1usize << 20, 5usize);
226        let result = EnvironmentParameters::compute_b1(q, t1);
227        let expected = (q as f64).powf(1.0 / t1 as f64).ceil() as usize;
228        assert_eq!(result, expected);
229    }
230
231    #[test]
232    fn test_t1_and_b1_are_consistent() {
233        let q = 1usize << 23;
234        let b = 137usize;
235        let t1 = EnvironmentParameters::compute_t1(q, b);
236        let b1 = EnvironmentParameters::compute_b1(q, t1);
237        assert!(b1.pow(t1 as u32) >= q);
238    }
239
240    #[test]
241    fn test_t2_b2_pair_match() {
242        let (n, s, b) = (10usize, 0.12, 150usize);
243        let t2: usize = EnvironmentParameters::compute_t2(n, s, b);
244        let b2 = EnvironmentParameters::compute_b2(n, s, t2);
245        // recompute tmp and double-check injectivity analogue
246        let tmp_ln = ((24.0 * n as f64 * Rq::DEGREE as f64).sqrt() * s * s).ln();
247        let expected_t2 = (tmp_ln / (b as f64).ln()).round() as usize;
248        assert_eq!(t2, expected_t2, "t2 formula");
249        let lhs = (b2 as f64).powf(t2 as f64);
250        let rhs = (24.0 * n as f64 * Rq::DEGREE as f64).sqrt() * s * s;
251        assert!(lhs >= rhs - 1.0);
252    }
253
254    #[test]
255    fn test_gamma_functions() {
256        let (beta, tau) = (32.0, 49.0);
257        let gamma = EnvironmentParameters::compute_gamma(beta, tau);
258        assert_eq!(gamma, beta * tau.sqrt());
259
260        // simple numeric spot-check for γ₁, γ₂
261        let (b1, t1, r, kappa, b2, t2) = (7, 3, 2, 4, 5, 2);
262        let manual1 = ((b1 * b1 * t1) as f64 / 12.0 * r as f64 * kappa as f64 * Rq::DEGREE as f64
263            + (b2 * b2 * t2) as f64 / 12.0 * ((r * r + r) as f64) / 2.0 * Rq::DEGREE as f64)
264            .sqrt();
265        let got1 = EnvironmentParameters::compute_gamma1(b1, t1, r, kappa, b2, t2);
266        assert_eq!(got1, manual1);
267
268        let manual2 = ((b1.pow(2) * t1) as f64 / 12.0 * ((r * r + r) as f64) / 2.0
269            * Rq::DEGREE as f64)
270            .sqrt();
271        let got2 = EnvironmentParameters::compute_gamma2(b1, t1, r);
272        assert_eq!(got2, manual2);
273    }
274
275    #[test]
276    fn beta_prime_formula() {
277        let (b, gamma, g1, g2) = (11usize, 3.3, 7.7, 2.2);
278        let expected = ((2.0 * gamma * gamma) / (b * b) as f64 + g1 * g1 + g2 * g2).sqrt();
279        let result = EnvironmentParameters::compute_beta_prime(b, gamma, g1, g2);
280        assert_eq!(expected, result);
281    }
282
283    #[test]
284    fn test_gamma_and_beta_prime_are_positive() {
285        let beta = 100.0;
286        let tau = 64.0;
287        let gamma = EnvironmentParameters::compute_gamma(beta, tau);
288        assert!(gamma > 0.0);
289        let beta_p = EnvironmentParameters::compute_beta_prime(3, gamma, 2.0, 1.0);
290        assert!(beta_p > 0.0);
291    }
292
293    #[test]
294    fn default_instance_is_consistent() {
295        let p = EnvironmentParameters::default();
296
297        assert!(p.b >= Zq::TWO && p.b_1 >= 2 && p.b_2 >= 2);
298        assert!(p.t_1 > 0 && p.t_2 > 0);
299
300        assert!((p.b_1 as u64).pow(p.t_1 as u32) >= p.log_q as u64);
301        assert!((p.b_2 as u64).pow(p.t_2 as u32) > 1);
302
303        // c)  norm bounds are non-negative
304        assert!(p.gamma > Zq::ZERO && p.gamma_1 > Zq::ZERO && p.gamma_2 > Zq::ZERO);
305        assert!(p.beta_prime >= p.gamma_1.max(p.gamma_2));
306    }
307
308    #[test]
309    fn random_parameter_sets_hold_invariants() {
310        for seed in 1..=20 {
311            let rank = 2 + seed as usize % 8; // 2..9
312            let multiplicity = 1 + seed as usize % 5; // 1..5
313            let beta = 10.0 + seed as f64;
314            let tau = 32.0 + seed as f64;
315            let q = (1usize << 20) + (seed as usize * 12345);
316            let params =
317                EnvironmentParameters::new(rank, multiplicity, beta, 4, 4, 4, 3, 3, tau, q);
318
319            // main invariants
320            assert!(params.b >= Zq::TWO);
321            assert!(params.t_1 >= 1 && params.t_2 >= 1);
322            assert!((params.b_1 as u64).pow(params.t_1 as u32) >= q as u64);
323            assert!(params.gamma > Zq::ZERO);
324            assert!(params.beta_prime >= params.gamma_1.max(params.gamma_2));
325        }
326    }
327}