labrador/transcript/
mod.rs

1pub mod sponges;
2use crate::{
3    core::jl::Projection,
4    ring::{rq::Rq, rq_matrix::RqMatrix, rq_vector::RqVector, zq::Zq},
5};
6pub use sponges::Sponge;
7
8pub struct LabradorTranscript<S: Sponge> {
9    sponge: S,
10    pub u1: RqVector,
11    pub vector_p: Vec<Zq>,
12    pub b_ct_aggr: RqVector,
13    pub u2: RqVector,
14    pub z: RqVector,
15    pub t: RqMatrix,
16    pub g: RqMatrix,
17    pub h: RqMatrix,
18}
19
20impl<S: Sponge> LabradorTranscript<S> {
21    pub fn new(sponge: S) -> Self {
22        Self {
23            sponge,
24            u1: RqVector::new(Vec::new()),
25            vector_p: Vec::new(),
26            b_ct_aggr: RqVector::new(Vec::new()),
27            u2: RqVector::new(Vec::new()),
28            z: RqVector::new(Vec::new()),
29            t: RqMatrix::new(vec![RqVector::new(Vec::new())], false),
30            g: RqMatrix::new(vec![RqVector::new(Vec::new())], true),
31            h: RqMatrix::new(vec![RqVector::new(Vec::new())], true),
32        }
33    }
34
35    pub fn set_u1(&mut self, u1: RqVector) {
36        self.absorb_u1(&u1);
37        self.u1 = u1;
38    }
39
40    pub fn absorb_u1(&mut self, u1: &RqVector) {
41        self.sponge.absorb_rq(u1.get_elements());
42    }
43
44    pub fn set_vector_p(&mut self, p: Vec<Zq>) {
45        self.absorb_vector_p(&p);
46        self.vector_p = p;
47    }
48
49    pub fn absorb_vector_p(&mut self, p: &[Zq]) {
50        self.sponge.absorb_zq(p);
51    }
52
53    pub fn set_vector_b_ct_aggr(&mut self, input: RqVector) {
54        self.absorb_vector_b_ct_aggr(&input);
55        self.b_ct_aggr = input;
56    }
57
58    pub fn absorb_vector_b_ct_aggr(&mut self, input: &RqVector) {
59        self.sponge.absorb_rq(input.get_elements());
60    }
61
62    pub fn set_u2(&mut self, u2: RqVector) {
63        self.absorb_u2(&u2);
64        self.u2 = u2;
65    }
66
67    pub fn absorb_u2(&mut self, u2: &RqVector) {
68        self.sponge.absorb_rq(u2.get_elements());
69    }
70
71    pub fn set_recursive_part(&mut self, z: RqVector, t: RqMatrix, g: RqMatrix, h: RqMatrix) {
72        self.z = z;
73        self.t = t;
74        self.g = g;
75        self.h = h;
76    }
77
78    pub fn generate_projections(
79        &mut self,
80        security_parameter: usize,
81        rank: usize,
82        multiplicity: usize,
83    ) -> Projection {
84        // r vectors, each of length 256 * nD
85        let row_size = 2 * security_parameter;
86        let col_size = rank * Rq::DEGREE;
87
88        let matrices = (0..multiplicity)
89            .map(|_| {
90                let linear_projection_randomness = self.sponge.squeeze_zq(row_size * col_size);
91                linear_projection_randomness
92                    .chunks_exact(col_size)
93                    .map(|chunk| {
94                        let coeffs = chunk
95                            .iter()
96                            .map(|elem| {
97                                if elem.get_value() < 2_u32.pow(30) {
98                                    Zq::NEG_ONE
99                                } else if elem.get_value() < 2_u32.pow(31) {
100                                    Zq::ONE
101                                } else {
102                                    Zq::ZERO
103                                }
104                            })
105                            .collect();
106                        RqVector::new_from_zq_vector(coeffs)
107                    })
108                    .collect()
109            })
110            .collect();
111        Projection::new(matrices, security_parameter)
112    }
113
114    pub fn generate_vector_psi(
115        &mut self,
116        number_of_vectors: usize,
117        vector_length: usize,
118    ) -> Vec<Vec<Zq>> {
119        let elements = self.sponge.squeeze_zq(number_of_vectors * vector_length);
120        elements
121            .chunks_exact(vector_length)
122            .map(|chunk| chunk.into())
123            .collect()
124    }
125
126    pub fn generate_vector_omega(
127        &mut self,
128        number_of_vectors: usize,
129        security_parameter: usize,
130    ) -> Vec<Vec<Zq>> {
131        let elements = self
132            .sponge
133            .squeeze_zq(number_of_vectors * 2 * security_parameter);
134        elements
135            .chunks_exact(2 * security_parameter)
136            .map(|chunk| chunk.into())
137            .collect()
138    }
139
140    pub fn generate_rq_vector(&mut self, vector_length: usize) -> RqVector {
141        RqVector::new(self.sponge.squeeze_rq(vector_length))
142    }
143
144    pub fn generate_challenges(&mut self, op_norm: f64, multiplicity: usize) -> RqVector {
145        let mut result = Vec::new();
146        loop {
147            let candidate = self.sample_challenge();
148            if candidate.operator_norm() < op_norm {
149                result.push(candidate);
150                if result.len() >= multiplicity {
151                    return RqVector::new(result);
152                }
153            }
154        }
155    }
156
157    #[allow(clippy::as_conversions)]
158    fn sample_challenge(&mut self) -> Rq {
159        let mut coeffs = [Zq::ZERO; Rq::DEGREE];
160        let random_bits = self.sponge.squeeze_bits(10 + 31);
161        // Add 31 coefficients, each either +1 or -1.
162        for (item, random_bit) in coeffs.iter_mut().zip(&random_bits).take(31) {
163            *item = if *random_bit { Zq::new(1) } else { -Zq::new(1) };
164        }
165        // Add 10 coefficients, each either +2 or -2.
166        for (item, random_bit) in coeffs.iter_mut().zip(random_bits).skip(31).take(10) {
167            *item = if random_bit { Zq::new(2) } else { -Zq::new(2) };
168        }
169
170        // ------------------ 3. Shuffle using Fisher–Yates approach ---------
171        for i in (1..Rq::DEGREE).rev() {
172            let random_bytes = self.sponge.squeeze_bytes(4);
173            let random_index = u32::from_le_bytes(random_bytes.try_into().unwrap());
174            let random_index = (random_index as usize) % (i + 1); // uniform in 0..i
175            coeffs.swap(i, random_index);
176        }
177        Rq::new(coeffs)
178    }
179}
180
181#[cfg(test)]
182mod tests_generate_pi {
183    use super::*;
184    use crate::transcript::sponges::shake::ShakeSponge;
185
186    #[test]
187    fn test_projection_matrix_has_correct_size() {
188        let (security_parameter, rank, multiplicity) = (128, 20, 9);
189        let mut transcript = LabradorTranscript::new(ShakeSponge::default());
190        let projections = transcript.generate_projections(security_parameter, rank, multiplicity);
191        assert_eq!(projections.get_projection_matrices().len(), multiplicity); // number_of_project_matrices
192        assert_eq!(
193            projections.get_projection_matrices()[0].get_row_len(),
194            2 * security_parameter
195        );
196        assert_eq!(projections.get_projection_matrices()[0].get_col_len(), rank);
197    }
198
199    // Test the distribution of values in the random matrix
200    #[test]
201    #[allow(clippy::as_conversions)]
202    fn test_projection_matrix_is_random() {
203        let (security_parameter, rank, multiplicity) = (128, 1000, 1);
204        let mut transcript = LabradorTranscript::new(ShakeSponge::default());
205        let projections = transcript.generate_projections(security_parameter, rank, multiplicity);
206
207        for projection_matrix in projections.get_projection_matrices() {
208            let mut counts = [0.0, 0.0, 0.0]; // -1, 0, 1
209            for row in projection_matrix.get_elements() {
210                for cell in row.concatenate_coefficients() {
211                    match cell {
212                        Zq::ZERO => counts[1] += 1.0,
213                        Zq::ONE => counts[2] += 1.0,
214                        Zq::NEG_ONE => counts[0] += 1.0,
215                        _ => panic!("Should not occur"),
216                    }
217                }
218            }
219            // Number of elements in the matrix as f64 (256x4x1000)
220            #[allow(clippy::as_conversions)]
221            let total: f64 = (256 * Rq::DEGREE * rank) as f64;
222            println!("this is the total amount of elements{}", total);
223            let expected = [0.25, 0.5, 0.25];
224            for i in 0..3 {
225                let actual = counts[i] / total;
226                println!("This is the actual value {}", actual);
227                assert!(
228                    //Since its a statistical test some small error tolerance is allowed
229                    (actual - expected[i]).abs() < 0.005,
230                    "Values are not within expected proportions"
231                );
232            }
233        }
234    }
235}
236
237#[cfg(test)]
238mod test_generate_psi {
239    use super::*;
240    use crate::transcript::sponges::shake::ShakeSponge;
241
242    #[test]
243    fn test_generate_vector_psi_has_correct_size() {
244        let (k_range, l_range) = (20, 12);
245        let mut transcript = LabradorTranscript::new(ShakeSponge::default());
246        let result = transcript.generate_vector_psi(k_range, l_range);
247        assert_eq!(result.len(), k_range); // number_of_project_matrices
248        assert_eq!(result[0].len(), l_range);
249    }
250
251    // TODO: Testing randomness of the distribution is needed.
252}
253
254#[cfg(test)]
255mod test_generate_omega {
256    use super::*;
257    use crate::transcript::sponges::shake::ShakeSponge;
258
259    #[test]
260    fn test_generate_vector_omega_has_correct_size() {
261        let (security_parameter, k_range) = (128, 20);
262        let mut transcript = LabradorTranscript::new(ShakeSponge::default());
263        let result = transcript.generate_vector_omega(k_range, security_parameter);
264        assert_eq!(result.len(), k_range); // number_of_project_matrices
265        assert_eq!(result[0].len(), 256);
266    }
267
268    // TODO: Testing randomness of the distribution is needed.
269}
270
271#[cfg(test)]
272mod test_generate_rq {
273    use super::*;
274    use crate::transcript::sponges::shake::ShakeSponge;
275
276    #[test]
277    fn test_generate_rq_has_correct_size() {
278        let k_range = 20;
279        let mut transcript = LabradorTranscript::new(ShakeSponge::default());
280        let result = transcript.generate_rq_vector(k_range);
281        assert_eq!(result.get_elements().len(), k_range); // number_of_project_matrices
282    }
283
284    // TODO: Testing randomness of the distribution is needed.
285}
286
287#[cfg(test)]
288mod test_generate_challenges {
289    use super::*;
290    use crate::transcript::sponges::shake::ShakeSponge;
291
292    #[test]
293    fn test_generate_challenges_has_correct_size() {
294        let multiplicity = 9;
295        let mut transcript = LabradorTranscript::new(ShakeSponge::default());
296        let result = transcript.generate_challenges(15.0, multiplicity);
297        assert_eq!(result.get_elements().len(), multiplicity); // number_of_project_matrices
298    }
299
300    /// Test Challenge Set:
301    /// const 71 and const 15 are from Challenge Space, paper page 6.
302    /// l2 norm <= 71
303    /// operator norm <= 15
304    #[test]
305    fn test_challenge_set() {
306        let op_norm = 15.0;
307        let multiplicity = 9;
308        let mut transcript = LabradorTranscript::new(ShakeSponge::default());
309
310        let challenge_set = transcript.generate_challenges(15.0, multiplicity);
311
312        for i in 0..multiplicity {
313            // l2 norm 71 is from paper page 6, Challenge Space.
314            use crate::core::inner_product::compute_linear_combination;
315            assert_eq!(
316                compute_linear_combination(
317                    challenge_set.get_elements()[i].get_coefficients(),
318                    challenge_set.get_elements()[i].get_coefficients()
319                ),
320                Zq::new(71)
321            );
322            assert!(challenge_set.get_elements()[i].operator_norm() <= op_norm);
323        }
324
325        for i in 0..multiplicity {
326            for j in 0..multiplicity {
327                if i != j {
328                    assert_ne!(
329                        challenge_set.get_elements()[i],
330                        challenge_set.get_elements()[j]
331                    );
332                }
333            }
334        }
335    }
336
337    // TODO: Testing randomness of the distribution is needed.
338}