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 = 71.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    /// Squred values of norm bounds
23    pub beta_sq: u128, // Bound for witness s_i
24    pub gamma_sq: u128,   // Bound for z
25    pub gamma_1_sq: u128, // Bound for t
26    pub gamma_2_sq: u128, // Bound for g and h
27    pub beta_prime_sq: u128,
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_sq: u128,
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_sq);
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_sq = Self::compute_gamma_sq(beta_sq, tau);
71        let gamma_1_sq =
72            Self::compute_gamma1_sq(base_b1, parts_t1, multiplicity, kappa, base_b2, parts_t2);
73        let gamma_2_sq = Self::compute_gamma2_sq(base_b1, parts_t1, multiplicity);
74        let beta_prime_sq = Self::compute_beta_prime_sq(base_b, gamma_sq, gamma_1_sq, gamma_2_sq);
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_sq,
85            gamma_sq,
86            gamma_1_sq,
87            gamma_2_sq,
88            beta_prime_sq,
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_sq: u128) -> f64 {
101        ((beta_sq as f64) / ((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_sq(beta_sq: u128, tau: f64) -> u128 {
142        beta_sq * (tau as u128)
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_sq(
148        b1: usize,
149        t1: usize,
150        multiplicity: usize,
151        kappa: usize,
152        b2: usize,
153        t2: usize,
154    ) -> u128 {
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) as u128
162    }
163
164    /// γ₂ = √( (b₁² t₁ /12) · (r²+r)/2 · d )
165    fn compute_gamma2_sq(b1: usize, t1: usize, multiplicity: usize) -> u128 {
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 as u128
171    }
172
173    /// β' = √( 2 γ² / b²  +  γ₁²  +  γ₂² )
174    fn compute_beta_prime_sq(b: usize, gamma_sq: u128, gamma1_sq: u128, gamma2_sq: u128) -> u128 {
175        let part_z = 2.0 * gamma_sq as f64 / (b * b) as f64;
176        part_z as u128 + gamma1_sq + gamma2_sq
177    }
178}
179
180impl Default for EnvironmentParameters {
181    fn default() -> Self {
182        Self::new(
183            5,
184            3,
185            65535 * 65535,
186            4,
187            5,
188            5,
189            5,
190            5,
191            OPERATOR_NORM,
192            (1u64 << 32) as usize,
193        )
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use crate::ring::zq::Zq;
200
201    use super::EnvironmentParameters;
202    use super::Rq;
203
204    #[test]
205    fn test_std_s_matches_formula() {
206        let (n, r, beta_sq) = (12, 5, 42 * 42);
207        let result = EnvironmentParameters::compute_std_s(n, r, beta_sq);
208        let expected = (beta_sq as f64 / ((r * n * Rq::DEGREE) as f64)).sqrt();
209        assert_eq!(result, expected);
210    }
211
212    #[test]
213    fn test_compute_base_b_not_below_two() {
214        let b = EnvironmentParameters::compute_base_b(0.001, 1, 0.0001);
215        assert!(b >= 2);
216    }
217
218    #[test]
219    fn test_base_b_formula_and_floor() {
220        let s = 0.17;
221        let (r, tau) = (3, 12.0);
222        let result = EnvironmentParameters::compute_base_b(s, r, tau);
223        let expected = ((12.0 * r as f64 * tau).sqrt() * s).sqrt().round().max(2.0) as usize;
224        assert_eq!(result, expected, "balanced radix b");
225    }
226
227    #[test]
228    fn test_t1_rounding() {
229        let (q, b) = (257usize, 4usize); // ln(q)/ln(b)=~4.006
230        let result = EnvironmentParameters::compute_t1(q, b);
231        assert_eq!(result, 4, "t1 = round(log_q / log_b)");
232    }
233
234    #[test]
235    fn test_b1_is_ceil_root() {
236        let (q, t1) = (1usize << 20, 5usize);
237        let result = EnvironmentParameters::compute_b1(q, t1);
238        let expected = (q as f64).powf(1.0 / t1 as f64).ceil() as usize;
239        assert_eq!(result, expected);
240    }
241
242    #[test]
243    fn test_t1_and_b1_are_consistent() {
244        let q = 1usize << 23;
245        let b = 137usize;
246        let t1 = EnvironmentParameters::compute_t1(q, b);
247        let b1 = EnvironmentParameters::compute_b1(q, t1);
248        assert!(b1.pow(t1 as u32) >= q);
249    }
250
251    #[test]
252    fn test_t2_b2_pair_match() {
253        let (n, s, b) = (10usize, 0.12, 150usize);
254        let t2: usize = EnvironmentParameters::compute_t2(n, s, b);
255        let b2 = EnvironmentParameters::compute_b2(n, s, t2);
256        // recompute tmp and double-check injectivity analogue
257        let tmp_ln = ((24.0 * n as f64 * Rq::DEGREE as f64).sqrt() * s * s).ln();
258        let expected_t2 = (tmp_ln / (b as f64).ln()).round() as usize;
259        assert_eq!(t2, expected_t2, "t2 formula");
260        let lhs = (b2 as f64).powf(t2 as f64);
261        let rhs = (24.0 * n as f64 * Rq::DEGREE as f64).sqrt() * s * s;
262        assert!(lhs >= rhs - 1.0);
263    }
264
265    #[test]
266    fn test_gamma_functions() {
267        let (beta_sq, tau) = (32, 49.0);
268        let gamma_sq = EnvironmentParameters::compute_gamma_sq(beta_sq, tau);
269        assert_eq!(gamma_sq, beta_sq * tau as u128);
270
271        // simple numeric spot-check for γ₁, γ₂
272        let (b1, t1, r, kappa, b2, t2) = (7, 3, 2, 4, 5, 2);
273        let manual1 = (b1 * b1 * t1) as f64 / 12.0 * r as f64 * kappa as f64 * Rq::DEGREE as f64
274            + (b2 * b2 * t2) as f64 / 12.0 * ((r * r + r) as f64) / 2.0 * Rq::DEGREE as f64;
275        let got1 = EnvironmentParameters::compute_gamma1_sq(b1, t1, r, kappa, b2, t2);
276        assert_eq!(got1, manual1 as u128);
277
278        let manual2 =
279            (b1.pow(2) * t1) as f64 / 12.0 * ((r * r + r) as f64) / 2.0 * Rq::DEGREE as f64;
280        let got2 = EnvironmentParameters::compute_gamma2_sq(b1, t1, r);
281        assert_eq!(got2, manual2 as u128);
282    }
283
284    #[test]
285    fn beta_prime_formula() {
286        let (b, gamma_sq, g1_sq, g2_sq) = (11usize, 3 * 3, 7 * 7, 2 * 2);
287        let expected = ((2 * gamma_sq) as f64 / (b * b) as f64) as u128 + g1_sq + g2_sq;
288        let result = EnvironmentParameters::compute_beta_prime_sq(b, gamma_sq, g1_sq, g2_sq);
289        assert_eq!(expected, result);
290    }
291
292    #[test]
293    fn test_gamma_and_beta_prime_are_positive() {
294        let beta_sq = 100 * 100;
295        let tau = 64.0;
296        let gamma_sq = EnvironmentParameters::compute_gamma_sq(beta_sq, tau);
297        assert!(gamma_sq > 0);
298        let beta_p = EnvironmentParameters::compute_beta_prime_sq(3, gamma_sq, 2, 1);
299        assert!(beta_p > 0);
300    }
301
302    #[test]
303    fn default_instance_is_consistent() {
304        let p = EnvironmentParameters::default();
305
306        assert!(p.b >= Zq::TWO && p.b_1 >= 2 && p.b_2 >= 2);
307        assert!(p.t_1 > 0 && p.t_2 > 0);
308
309        assert!((p.b_1 as u64).pow(p.t_1 as u32) >= p.log_q as u64);
310        assert!((p.b_2 as u64).pow(p.t_2 as u32) > 1);
311
312        // c)  norm bounds are non-negative
313        assert!(p.gamma_sq > 0 && p.gamma_1_sq > 0 && p.gamma_2_sq > 0);
314        assert!(p.beta_prime_sq >= p.gamma_1_sq.max(p.gamma_2_sq));
315    }
316
317    #[test]
318    fn random_parameter_sets_hold_invariants() {
319        for seed in 1..=20 {
320            let rank = 2 + seed as usize % 8; // 2..9
321            let multiplicity = 1 + seed as usize % 5; // 1..5
322            let beta_sq = (10 + seed) * (10 + seed);
323            let tau = 32.0 + seed as f64;
324            let q = (1usize << 20) + (seed as usize * 12345);
325            let params =
326                EnvironmentParameters::new(rank, multiplicity, beta_sq, 4, 4, 4, 3, 3, tau, q);
327
328            // main invariants
329            assert!(params.b >= Zq::TWO);
330            assert!(params.t_1 >= 1 && params.t_2 >= 1);
331            assert!((params.b_1 as u64).pow(params.t_1 as u32) >= q as u64);
332            assert!(params.gamma_sq > 0);
333            assert!(params.beta_prime_sq >= params.gamma_1_sq.max(params.gamma_2_sq));
334        }
335    }
336}