1use crate::ring::rq::Rq;
2use crate::ring::rq_vector::RqVector;
3use crate::ring::zq::Zq;
4use rand::prelude::*;
5use rand::rng;
6
7pub const SECURITY_LEVEL: usize = 128;
9pub const PROJECTION_MATRIX_SIZE: usize = 2 * SECURITY_LEVEL;
11pub struct ProjectionMatrix {
13 matrix: Vec<Vec<Zq>>,
14}
15
16impl ProjectionMatrix {
17 pub fn new(n: usize) -> Self {
20 let mut matrix = vec![vec![Zq::ZERO; n * Rq::DEGREE]; PROJECTION_MATRIX_SIZE];
21 let mut rng = rng();
22 for row in matrix.iter_mut() {
23 for elem in row.iter_mut() {
24 let rand_val: f64 = rng.random();
25 *elem = if rand_val < 0.25 {
26 Zq::MAX } else if rand_val < 0.75 {
28 Zq::ZERO
29 } else {
30 Zq::ONE
31 };
32 }
33 }
34
35 ProjectionMatrix { matrix }
36 }
37
38 pub fn get_matrix(&self) -> &Vec<Vec<Zq>> {
40 &self.matrix
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct ProjectionVector {
47 projection: Vec<Zq>,
48}
49
50impl ProjectionVector {
51 pub fn new(matrix: &ProjectionMatrix, s_i: &RqVector) -> Self {
53 let mut projection = vec![Zq::ZERO; PROJECTION_MATRIX_SIZE];
54 let coefficients = s_i.concatenate_coefficients();
55 for (i, item) in projection.iter_mut().enumerate() {
56 *item = matrix.get_matrix()[i]
57 .iter()
58 .zip(coefficients.iter())
59 .map(|(m, s)| *m * *s)
60 .sum::<Zq>();
61 }
62 ProjectionVector { projection }
63 }
64
65 pub fn get_projection(&self) -> &Vec<Zq> {
67 &self.projection
68 }
69
70 pub fn norm_squared(&self) -> Zq {
72 self.projection.iter().map(|coeff| *coeff * *coeff).sum()
73 }
74}
75
76#[derive(Debug, Clone)]
77pub struct Projections {
78 pub projection: Vec<Zq>,
79}
80
81pub fn inner_product(first: &[Zq], second: &[Zq]) -> Zq {
82 first
83 .iter()
84 .zip(second.iter())
85 .map(|(&a, &b)| a * b)
86 .fold(Zq::ZERO, |acc, x| acc + x)
87}
88
89impl Projections {
90 pub fn new(matrices: &[Vec<Vec<Zq>>], witness: &[RqVector]) -> Self {
92 let mut temp = Vec::with_capacity(PROJECTION_MATRIX_SIZE);
93 let coefficients: Vec<Vec<Zq>> = witness
94 .iter()
95 .map(|s_i| s_i.concatenate_coefficients())
96 .collect();
97
98 for j in 0..PROJECTION_MATRIX_SIZE {
99 let mut sum = Zq::ZERO;
100 for i in 0..matrices.len() {
101 let pi = &matrices[i][j];
102 let s_i = &coefficients[i];
103 let inner = inner_product(pi, s_i);
104 sum += inner;
105 }
106 temp.push(sum);
107 }
108 let projection = temp;
109
110 Projections { projection }
111 }
112
113 pub fn get_projection(&self) -> &Vec<Zq> {
115 &self.projection
116 }
117
118 pub fn norm_squared(&self) -> Zq {
120 self.projection.iter().map(|coeff| *coeff * *coeff).sum()
121 }
122}
123
124const UPPER_BOUND_FACTOR: Zq = Zq::new(128);
128const LOWER_BOUND_FACTOR: Zq = Zq::new(30);
129
130pub fn verify_upper_bound(projection: ProjectionVector, beta_squared: Zq) -> bool {
132 projection.norm_squared() < (UPPER_BOUND_FACTOR * beta_squared)
133}
134pub fn verify_lower_bound(projection: ProjectionVector, beta_squared: Zq) -> bool {
136 projection.norm_squared() > (LOWER_BOUND_FACTOR * beta_squared)
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
145 fn test_size_projection_matrix() {
146 let n = 10;
147 let matrix = ProjectionMatrix::new(n);
148
149 assert_eq!(
150 matrix.matrix.len(),
151 PROJECTION_MATRIX_SIZE,
152 "Matrix should have {} rows",
153 PROJECTION_MATRIX_SIZE
154 );
155 assert_eq!(
156 matrix.matrix[0].len(),
157 n * Rq::DEGREE,
158 "Matrix should have {} columns",
159 n * Rq::DEGREE
160 );
161
162 let n2 = 1;
163 let matrix = ProjectionMatrix::new(n2);
164
165 assert_eq!(
166 matrix.matrix.len(),
167 PROJECTION_MATRIX_SIZE,
168 "Matrix should have {} rows",
169 PROJECTION_MATRIX_SIZE
170 );
171 assert_eq!(
172 matrix.matrix[0].len(),
173 n2 * Rq::DEGREE,
174 "Matrix should have {} columns",
175 n2 * Rq::DEGREE
176 );
177 }
178
179 #[test]
181 #[allow(clippy::as_conversions)]
182 fn test_random_distribution_matrix() {
183 let n = 1000;
185 let matrix = ProjectionMatrix::new(n);
186 let mut counts = [0.0, 0.0, 0.0]; for row in matrix.matrix.iter() {
188 for &elem in row.iter() {
189 if elem == Zq::MAX {
190 counts[0] += 1.0;
191 } else if elem == Zq::ZERO {
192 counts[1] += 1.0;
193 } else if elem == Zq::ONE {
194 counts[2] += 1.0;
195 }
196 }
197 }
198 #[allow(clippy::as_conversions)]
200 let total: f64 = (256 * Rq::DEGREE * n) as f64;
201 println!("this is the total amount of elements{}", total);
202 let expected = [0.25, 0.5, 0.25];
203 for i in 0..3 {
204 let actual = counts[i] / total;
205 println!("This is the actual value {}", actual);
206 assert!(
207 (actual - expected[i]).abs() < 0.005,
209 "Values are not within expected proportions"
210 );
211 }
212 }
213
214 #[test]
216 #[cfg(not(feature = "skip-slow-tests"))]
217 fn test_probability_is_close_to_half() {
218 let trials: f64 = 1000.0;
220 let mut success_count: f64 = 0.0;
221 let n = 5;
222 for _ in 0..1000 {
223 let mut rng = rng();
224 let polynomials = RqVector::random_ternary(&mut rng, 5);
226 let matrix = ProjectionMatrix::new(n);
228 let projection = ProjectionVector::new(&matrix, &polynomials);
230 let beta = polynomials.compute_norm_squared();
231 let test: bool = verify_upper_bound(projection, beta);
233 if test {
234 success_count += 1.0;
235 }
236 }
237
238 let observed_probability = success_count / trials;
239
240 let tolerance = 0.05;
242 assert!(
243 (observed_probability - 0.5).abs() < tolerance,
244 "Observed probability {} is not close to 0.5",
245 observed_probability
246 );
247 }
248
249 #[test]
251 #[cfg(not(feature = "skip-slow-tests"))]
252 fn average_value() {
253 let trials: u128 = 20000;
255 let n = 3;
256 let mut rng = rng();
257 let polynomials = RqVector::random_ternary(&mut rng, 3);
258 let mut matrix = ProjectionMatrix::new(n);
259 let mut projection = ProjectionVector::new(&matrix, &polynomials);
260 let mut norm_sum = projection.norm_squared();
261 let norm_value = (Zq::new(SECURITY_LEVEL.try_into().unwrap())
262 * RqVector::compute_norm_squared(&polynomials))
263 .to_u128();
264 for _ in 0..trials {
266 matrix = ProjectionMatrix::new(n);
267 projection = ProjectionVector::new(&matrix, &polynomials);
268 norm_sum += projection.norm_squared();
269 }
270
271 let average = norm_sum.to_u128() / trials;
273 let difference = if norm_value <= average {
274 average - norm_value
275 } else {
276 norm_value - average
277 };
278
279 let tolerance: u128 = 20;
281 assert!(
282 difference < tolerance,
283 "Average norm value {} is not equal to {}.",
284 average,
285 Zq::new(SECURITY_LEVEL.try_into().unwrap()) * polynomials.compute_norm_squared(),
286 );
287 }
288
289 #[test]
291 fn test_lower_bound() {
292 let n = 5;
293 let mut rng = rng();
294 let polynomials = RqVector::random_ternary(&mut rng, 5);
296 let matrix = ProjectionMatrix::new(n);
298 let projection = ProjectionVector::new(&matrix, &polynomials);
300 let beta = polynomials.compute_norm_squared();
301 assert!(verify_lower_bound(projection, beta));
303 }
304
305 #[test]
307 fn test_vector_concatenation() {
308 let mut vec1 = vec![Zq::new(10); Rq::DEGREE];
309 let mut vec2 = vec![Zq::new(12); Rq::DEGREE];
310 let polynomials = RqVector::from(vec![vec1.clone().into(), vec2.clone().into()]);
311
312 vec1.append(&mut vec2);
313 assert!(polynomials.concatenate_coefficients() == vec1);
314 }
315}