1use crate::ring::poly::PolyVector;
2use crate::ring::poly::ZqVector;
3use crate::ring::rq::Rq;
4use crate::ring::rq_vector::RqVector;
5use crate::ring::zq::Zq;
6use rand::prelude::*;
7use rand::rng;
8
9pub const SECURITY_LEVEL: usize = 128;
11pub const PROJECTION_MATRIX_SIZE: usize = 2 * SECURITY_LEVEL;
13pub struct ProjectionMatrix<const D: usize> {
15 matrix: Vec<ZqVector>,
16}
17
18impl<const D: usize> ProjectionMatrix<D> {
19 pub fn new(n: usize) -> Self {
22 let mut matrix = vec![ZqVector::new(vec![Zq::ZERO; n * D]); PROJECTION_MATRIX_SIZE];
23 let mut rng = rng();
24 for row in matrix.iter_mut() {
25 for elem in row.iter_mut() {
26 let rand_val: f64 = rng.random();
27 *elem = if rand_val < 0.25 {
28 Zq::MAX } else if rand_val < 0.75 {
30 Zq::ZERO
31 } else {
32 Zq::ONE
33 };
34 }
35 }
36
37 ProjectionMatrix { matrix }
38 }
39
40 pub fn get_matrix(&self) -> &Vec<ZqVector> {
42 &self.matrix
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct ProjectionVector<const N: usize, const D: usize> {
49 projection: Rq<PROJECTION_MATRIX_SIZE>,
50}
51
52impl<const N: usize, const D: usize> ProjectionVector<N, D> {
53 pub fn new(matrix: &ProjectionMatrix<D>, s_i: &RqVector<N, D>) -> Self {
55 let mut projection = Rq::new([Zq::ZERO; PROJECTION_MATRIX_SIZE]);
56 let coefficients = s_i.concatenate_coefficients();
57 for (i, item) in projection.iter_mut().enumerate() {
58 *item = matrix.get_matrix()[i]
59 .iter()
60 .zip(coefficients.iter())
61 .map(|(m, s)| *m * *s)
62 .sum::<Zq>();
63 }
64 ProjectionVector { projection }
65 }
66
67 pub fn get_projection(&self) -> &Rq<PROJECTION_MATRIX_SIZE> {
69 &self.projection
70 }
71
72 pub fn norm_squared(&self) -> Zq {
74 self.projection.iter().map(|coeff| *coeff * *coeff).sum()
75 }
76}
77
78#[derive(Debug, Clone)]
79pub struct Projections {
80 pub projection: ZqVector,
81}
82
83impl Projections {
84 pub fn new(matrices: &[Vec<ZqVector>], witness: &[PolyVector]) -> Self {
86 let mut temp = Vec::with_capacity(PROJECTION_MATRIX_SIZE);
87 let coefficients: Vec<ZqVector> = witness
88 .iter()
89 .map(|s_i| s_i.concatenate_coefficients(PROJECTION_MATRIX_SIZE))
90 .collect();
91
92 for j in 0..PROJECTION_MATRIX_SIZE {
93 let mut sum = Zq::ZERO;
94 for i in 0..matrices.len() {
95 let pi = &matrices[i][j];
96 let s_i = &coefficients[i];
97 let inner = pi.inner_product(s_i);
98 sum += inner;
99 }
100 temp.push(sum);
101 }
102 let projection = ZqVector::new(temp);
103
104 Projections { projection }
105 }
106
107 pub fn get_projection(&self) -> &ZqVector {
109 &self.projection
110 }
111
112 pub fn norm_squared(&self) -> Zq {
114 self.projection.iter().map(|coeff| *coeff * *coeff).sum()
115 }
116}
117
118const UPPER_BOUND_FACTOR: Zq = Zq::new(128);
122const LOWER_BOUND_FACTOR: Zq = Zq::new(30);
123
124pub fn verify_upper_bound<const N: usize, const D: usize>(
126 projection: ProjectionVector<N, D>,
127 beta_squared: Zq,
128) -> bool {
129 projection.norm_squared() < (UPPER_BOUND_FACTOR * beta_squared)
130}
131pub fn verify_lower_bound<const N: usize, const D: usize>(
133 projection: ProjectionVector<N, D>,
134 beta_squared: Zq,
135) -> 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::<4>::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 * 4,
158 "Matrix should have {} columns",
159 n * 4
160 );
161
162 let n2 = 1;
163 let matrix = ProjectionMatrix::<4>::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 * 4,
174 "Matrix should have {} columns",
175 n2 * 4
176 );
177 }
178
179 #[test]
181 fn test_random_distribution_matrix() {
182 let n = 1000;
184 let matrix = ProjectionMatrix::<4>::new(n);
185 let mut counts = [0.0, 0.0, 0.0]; for row in matrix.matrix.iter() {
187 for &elem in row.iter() {
188 if elem == Zq::MAX {
189 counts[0] += 1.0;
190 } else if elem == Zq::ZERO {
191 counts[1] += 1.0;
192 } else if elem == Zq::ONE {
193 counts[2] += 1.0;
194 }
195 }
196 }
197 let total: f64 = 1024000.0;
199 println!("this is the total amount of elements{}", total);
200 let expected = [0.25, 0.5, 0.25];
201 for i in 0..3 {
202 let actual = counts[i] / total;
203 println!("This is the actual value {}", actual);
204 assert!(
205 (actual - expected[i]).abs() < 0.005,
207 "Values are not within expected proportions"
208 );
209 }
210 }
211
212 #[test]
214 #[cfg(not(feature = "skip-slow-tests"))]
215 fn test_probability_is_close_to_half() {
216 let trials: f64 = 10000.0;
218 let mut success_count: f64 = 0.0;
219 let n = 5;
220 for _ in 0..10000 {
221 let mut rng = rng();
222 let polynomials = RqVector::<5, 5>::random_ternary(&mut rng);
224 let matrix = ProjectionMatrix::new(n);
226 let projection = ProjectionVector::new(&matrix, &polynomials);
228 let beta = polynomials.compute_norm_squared();
229 let test: bool = verify_upper_bound(projection, beta);
231 if test {
232 success_count += 1.0;
233 }
234 }
235
236 let observed_probability = success_count / trials;
237
238 let tolerance = 0.05;
240 assert!(
241 (observed_probability - 0.5).abs() < tolerance,
242 "Observed probability {} is not close to 0.5",
243 observed_probability
244 );
245 }
246
247 #[test]
249 #[cfg(not(feature = "skip-slow-tests"))]
250 fn average_value() {
251 let trials: u128 = 100000;
253 let n = 3;
254 let mut rng = rng();
255 let polynomials = RqVector::<3, 3>::random_ternary(&mut rng);
256 let mut matrix = ProjectionMatrix::new(n);
257 let mut projection = ProjectionVector::new(&matrix, &polynomials);
258 let mut norm_sum = projection.norm_squared();
259 let norm_value = (Zq::new(SECURITY_LEVEL.try_into().unwrap())
260 * RqVector::compute_norm_squared(&polynomials))
261 .to_u128();
262 for _ in 0..trials {
264 matrix = ProjectionMatrix::new(n);
265 projection = ProjectionVector::new(&matrix, &polynomials);
266 norm_sum += projection.norm_squared();
267 }
268
269 let average = norm_sum.to_u128() / trials;
271 let difference = if norm_value <= average {
272 average - norm_value
273 } else {
274 norm_value - average
275 };
276
277 let tolerance: u128 = 2;
279 assert!(
280 difference < tolerance,
281 "Average norm value {} is not equal to {}.",
282 average,
283 Zq::new(SECURITY_LEVEL.try_into().unwrap()) * polynomials.compute_norm_squared(),
284 );
285 }
286
287 #[test]
289 fn test_lower_bound() {
290 let n = 5;
291 let mut rng = rng();
292 let polynomials = RqVector::<5, 64>::random_ternary(&mut rng);
294 let matrix = ProjectionMatrix::new(n);
296 let projection = ProjectionVector::new(&matrix, &polynomials);
298 let beta = polynomials.compute_norm_squared();
299 assert!(verify_lower_bound(projection, beta));
301 }
302
303 #[test]
305 fn test_vector_concatenation() {
306 let polynomials: RqVector<2, 4> = RqVector::from(vec![
307 vec![Zq::ONE, Zq::ZERO, Zq::ZERO, Zq::MAX].into(),
308 vec![Zq::new(6), Zq::ZERO, Zq::new(5), Zq::new(3)].into(),
309 ]);
310 let vector = vec![
311 Zq::ONE,
312 Zq::ZERO,
313 Zq::ZERO,
314 Zq::MAX,
315 Zq::new(6),
316 Zq::ZERO,
317 Zq::new(5),
318 Zq::new(3),
319 ];
320 assert!(polynomials.concatenate_coefficients() == vector);
321 }
322}