1use crate::{code::LinearCode, pcs::structs::ZipTypes, utils::shuffle_seeded};
2use crypto_primitives::{
3 BaseFieldConfig, FixedConfig, ProjectPrimitiveIntegersWithConfig, SemiringConfig,
4};
5use std::{fmt::Debug, marker::PhantomData};
6use zinc_poly::ConstCoeffBitWidth;
7use zinc_utils::{add, from_ref::FromRef, mul};
8
9pub trait RaaConfig: Copy + Send + Sync {
10 const PERMUTE_IN_PLACE: bool;
13 const CHECK_FOR_OVERFLOWS: bool;
16}
17
18#[derive(Clone)]
21pub struct RaaCode<Zt: ZipTypes, Config: RaaConfig, const REP: usize> {
22 pub(crate) row_len: usize,
23 pub(crate) perm_1_seed: u64,
25
26 pub(crate) perm_2_seed: u64,
28
29 pub(crate) perm_1: Vec<usize>,
31
32 pub(crate) perm_2: Vec<usize>,
34
35 phantom: PhantomData<(Zt, Config)>,
36}
37
38impl<Zt: ZipTypes, Config: RaaConfig, const REP: usize> RaaCode<Zt, Config, REP> {
39 pub fn new(row_len: usize) -> Self {
40 assert!(
41 REP.is_power_of_two(),
42 "Repetition factor must be a power of two"
43 );
44 assert!(
45 row_len.is_power_of_two(),
46 "Row length must be a power of two"
47 );
48
49 let codeword_width_bits = {
54 let initial_bits =
55 u32::try_from(Zt::Eval::COEFF_BIT_WIDTH).expect("Size of EvalR type is too large");
56
57 let row_len_log = row_len.ilog2();
58 let rep_factor_log = REP.ilog2();
59 add!(
60 initial_bits,
61 add!(mul!(row_len_log, 2), mul!(rep_factor_log, 2))
62 )
63 };
64 let codeword_type_bits =
65 u32::try_from(Zt::Cw::COEFF_BIT_WIDTH).expect("Size of CwR type is too large");
66 assert!(
67 codeword_type_bits >= codeword_width_bits,
68 "Cannot fit {codeword_width_bits}-bit wide codeword entries in {} bits entries",
69 codeword_type_bits
70 );
71
72 const PERM_1_SEED: u64 = 1;
74 const PERM_2_SEED: u64 = 2;
75
76 let codeword_len = mul!(row_len, REP);
77
78 let mut perm_1: Vec<usize> = (0..codeword_len).collect();
79 shuffle_seeded(&mut perm_1, PERM_1_SEED);
80 let mut perm_2: Vec<usize> = (0..codeword_len).collect();
81 shuffle_seeded(&mut perm_2, PERM_2_SEED);
82
83 Self {
84 row_len,
85 perm_1_seed: PERM_1_SEED,
86 perm_2_seed: PERM_2_SEED,
87 perm_1,
88 perm_2,
89 phantom: PhantomData,
90 }
91 }
92
93 fn encode_inner<In, C, Map>(&self, cfg: &C, row: &[In], map: Map) -> Vec<C::Element>
95 where
96 C: SemiringConfig,
97 Map: Fn(&In) -> C::Element + Clone,
98 {
99 debug_assert_eq!(
100 row.len(),
101 self.row_len,
102 "Row length must match the code's row length"
103 );
104
105 let mut result: Vec<C::Element> = repeat(row, REP, map);
106 if Config::PERMUTE_IN_PLACE {
107 shuffle_seeded(&mut result, self.perm_1_seed);
108 } else {
109 result = clone_shuffled(&result, &self.perm_1);
110 }
111 if Config::CHECK_FOR_OVERFLOWS {
112 accumulate(cfg, &mut result);
113 } else {
114 accumulate_unchecked(cfg, &mut result);
115 }
116 if Config::PERMUTE_IN_PLACE {
117 shuffle_seeded(&mut result, self.perm_2_seed);
118 } else {
119 result = clone_shuffled(&result, &self.perm_2);
120 }
121 if Config::CHECK_FOR_OVERFLOWS {
122 accumulate(cfg, &mut result);
123 } else {
124 accumulate_unchecked(cfg, &mut result);
125 }
126 debug_assert_eq!(result.len(), self.codeword_len());
127 result
128 }
129}
130
131impl<Zt: ZipTypes, Config: RaaConfig, const REP: usize> LinearCode<Zt>
132 for RaaCode<Zt, Config, REP>
133{
134 const REPETITION_FACTOR: usize = REP;
135
136 fn row_len(&self) -> usize {
137 self.row_len
138 }
139
140 #[allow(clippy::arithmetic_side_effects)]
141 fn codeword_len(&self) -> usize {
142 self.row_len * REP
143 }
144
145 fn params_string(&self) -> String {
146 format!("row_len={}, rate=1/{REP}", self.row_len())
147 }
148
149 fn encode(&self, row: &[Zt::Eval]) -> Vec<Zt::Cw> {
150 self.encode_inner(&FixedConfig::default(), row, Zt::Cw::from_ref)
151 }
152
153 fn encode_wide(&self, row: &[Zt::CombR]) -> Vec<Zt::CombR> {
154 self.encode_inner(&FixedConfig::default(), row, |v| v.clone())
155 }
156
157 fn encode_f<C>(&self, cfg: &C, row: &[C::Element]) -> Vec<C::Element>
158 where
159 C: BaseFieldConfig + ProjectPrimitiveIntegersWithConfig,
160 {
161 self.encode_inner(cfg, row, |v| v.clone())
162 }
163}
164
165impl<Zt: ZipTypes, Config: RaaConfig, const REP: usize> Debug for RaaCode<Zt, Config, REP> {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 f.debug_struct("RaaCode")
168 .field("row_len", &self.row_len)
169 .field("perm_1_seed", &self.perm_1_seed)
170 .field("perm_2_seed", &self.perm_2_seed)
171 .finish()
172 }
173}
174
175impl<Zt: ZipTypes, Config: RaaConfig, const REP: usize> PartialEq for RaaCode<Zt, Config, REP> {
176 fn eq(&self, other: &Self) -> bool {
177 self.row_len == other.row_len
178 && self.perm_1_seed == other.perm_1_seed
179 && self.perm_2_seed == other.perm_2_seed
180 }
181}
182
183impl<Zt: ZipTypes, Config: RaaConfig, const REP: usize> Eq for RaaCode<Zt, Config, REP> {}
184
185#[allow(clippy::arithmetic_side_effects)]
187#[inline]
188pub(crate) fn repeat<In, Out: Clone>(
189 input: &[In],
190 repetition_factor: usize,
191 map: impl Fn(&In) -> Out + Clone,
192) -> Vec<Out> {
193 input
194 .iter()
195 .map(map)
196 .cycle()
197 .take(input.len() * repetition_factor)
198 .collect()
199}
200
201pub(crate) fn accumulate<C: SemiringConfig>(cfg: &C, input: &mut [C::Element]) {
212 if let Some(first) = input.first().cloned() {
213 let mut acc = first;
214 for curr in input.iter_mut().skip(1) {
215 acc = cfg.checked_add(curr, &acc).expect("Accumulation overflow");
216 *curr = acc.clone();
217 }
218 }
219}
220
221pub(crate) fn accumulate_unchecked<C: SemiringConfig>(cfg: &C, input: &mut [C::Element]) {
222 if let Some(first) = input.first().cloned() {
223 let mut acc = first;
224 for i in 1..input.len() {
225 unsafe {
227 cfg.add_assign(&mut acc, input.get_unchecked(i));
228 *input.get_unchecked_mut(i) = acc.clone();
229 };
230 }
231 }
232}
233
234pub(crate) fn clone_shuffled<T>(data: &[T], perm: &[usize]) -> Vec<T>
236where
237 T: Clone,
238{
239 perm.iter().map(|&i| data[i].clone()).collect()
240}
241
242#[cfg(test)]
243#[allow(clippy::arithmetic_side_effects, clippy::clone_on_copy)]
244mod tests {
245 use super::*;
246 use crate::{code::LinearCode, pcs::test_utils::TestZipTypes, utils::shuffle_seeded};
247 use crypto_primitives::{crypto_bigint_int::Int, crypto_bigint_uint::U64};
248 use num_traits::Zero;
249
250 const REPETITION_FACTOR: usize = 4;
251
252 const INT_LIMBS: usize = U64::LIMBS;
254
255 const N: usize = INT_LIMBS;
256 const K: usize = INT_LIMBS * 4;
257 const M: usize = INT_LIMBS * 8;
258
259 #[derive(Clone, Copy)]
260 struct RaaConfigGeneric<const PERMUTE_IN_PLACE: bool, const CHECK_FOR_OVERFLOWS: bool>;
261
262 impl<const PERMUTE_IN_PLACE: bool, const CHECK_FOR_OVERFLOWS: bool> RaaConfig
263 for RaaConfigGeneric<PERMUTE_IN_PLACE, CHECK_FOR_OVERFLOWS>
264 {
265 const PERMUTE_IN_PLACE: bool = PERMUTE_IN_PLACE;
266 const CHECK_FOR_OVERFLOWS: bool = CHECK_FOR_OVERFLOWS;
267 }
268
269 macro_rules! cfg {
270 () => {
271 &FixedConfig::default()
272 };
273 }
274
275 macro_rules! test_raa {
276 ($zt:ty, $row_len:expr, $f:expr) => {
277 test_raa!($zt, $row_len, $f, RaaConfigGeneric<false, false>);
278 test_raa!($zt, $row_len, $f, RaaConfigGeneric<false, true>);
279 test_raa!($zt, $row_len, $f, RaaConfigGeneric<true, false>);
280 test_raa!($zt, $row_len, $f, RaaConfigGeneric<true, true>);
281 };
282
283 ($zt:ty, $row_len:expr, $f:expr, $config:ty) => {
284 {
285 let code = RaaCode::<$zt, $config, REPETITION_FACTOR>::new($row_len);
286 $f(&code)
287 }
288 };
289 }
290
291 #[test]
292 fn repeat_function_duplicates_row_correctly() {
293 type I = Int<N>;
294 let input = [10, 20].map(I::from);
295
296 let repetition_factor = 3;
297
298 let repeated_output = repeat::<_, I>(&input, repetition_factor, |v| v.clone());
299
300 let expected_output: Vec<_> = [10, 20, 10, 20, 10, 20].into_iter().map(I::from).collect();
301 assert_eq!(
302 repeated_output, expected_output,
303 "Failed on repetition factor > 1"
304 );
305
306 let empty_input: Vec<I> = vec![];
307 let repeated_empty = repeat::<_, I>(&empty_input, 5, |v| v.clone());
308 assert!(repeated_empty.is_empty(), "Failed on empty input vector");
309
310 let repeated_once = repeat::<_, I>(&input, 1, |v| v.clone());
311 assert_eq!(repeated_once, input, "Failed on repetition factor of 1");
312 }
313
314 #[test]
315 fn accumulate_function_computes_cumulative_sum() {
316 type I = Int<N>;
317 let mut input1: Vec<I> = [1, 2, 3, 4].into_iter().map(I::from).collect();
318 let expected1: Vec<I> = [1, 3, 6, 10].into_iter().map(I::from).collect();
319 accumulate(cfg!(), &mut input1);
320 assert_eq!(input1, expected1, "Failed on positive integers");
321
322 let mut input1: Vec<I> = [1, 2, 3, 4].into_iter().map(I::from).collect();
323 accumulate_unchecked(cfg!(), &mut input1);
324 assert_eq!(input1, expected1, "Failed on positive integers");
325
326 let mut input2: Vec<I> = [5, 0, 2, 0].into_iter().map(I::from).collect();
327 let expected2: Vec<I> = [5, 5, 7, 7].into_iter().map(I::from).collect();
328 accumulate(cfg!(), &mut input2);
329 assert_eq!(input2, expected2, "Failed on vector with zeros");
330
331 let mut input3: Vec<I> = [-1, 5, -10, 2].into_iter().map(I::from).collect();
332 let expected3: Vec<I> = [-1, 4, -6, -4].into_iter().map(I::from).collect();
333 accumulate(cfg!(), &mut input3);
334 assert_eq!(input3, expected3, "Failed on vector with negative numbers");
335
336 let mut empty_input: Vec<I> = vec![];
337 let expected_empty: Vec<I> = vec![];
338 accumulate(cfg!(), &mut empty_input);
339 assert_eq!(empty_input, expected_empty, "Failed on empty vector");
340 }
341
342 #[test]
343 fn shuffle_is_deterministic_for_a_given_seed() {
344 type I = Int<N>;
345 let original: Vec<I> = (1..=10).map(I::from).collect();
346 let mut vec1 = original.clone();
347 let mut vec2 = original.clone();
348 let mut vec3 = original.clone();
349
350 let seed1 = 12345;
351 let seed2 = 54321;
352
353 shuffle_seeded(&mut vec1, seed1);
354 shuffle_seeded(&mut vec2, seed1);
355 shuffle_seeded(&mut vec3, seed2);
356
357 assert_eq!(
358 vec1, vec2,
359 "Shuffling with the same seed should produce the same result"
360 );
361 assert_ne!(
362 vec1, vec3,
363 "Shuffling with different seeds should produce different results"
364 );
365 assert_ne!(
366 vec1, original,
367 "Shuffled vector should not be the same as the original"
368 );
369 assert_ne!(
370 vec3, original,
371 "Shuffled vector should not be the same as the original"
372 );
373 }
374
375 #[test]
376 fn encoding_preserves_linearity() {
377 test_raa!(TestZipTypes<N, K, M>, 4, |code: &RaaCode<_, _, _>| {
378 let a: Vec<Int<N>> = (1..=4).map(Int::<N>::from).collect();
379 let b: Vec<Int<N>> = (5..=8).map(Int::<N>::from).collect();
380 let sum_ab: Vec<Int<N>> = a.iter().zip(b.iter()).map(|(x, y)| *x + y).collect();
381
382 let encode_a: Vec<Int<K>> = code.encode(&a);
383 let encode_b: Vec<Int<K>> = code.encode(&b);
384 let encode_sum_ab: Vec<Int<K>> = code.encode(&sum_ab);
385
386 let sum_encode_ab: Vec<Int<K>> = encode_a
387 .iter()
388 .zip(encode_b.iter())
389 .map(|(x, y)| *x + y)
390 .collect();
391
392 assert_eq!(encode_sum_ab, sum_encode_ab);
393 });
394 }
395
396 #[test]
399 fn encoding_produces_predictable_results() {
400 let a: Vec<Int<N>> = (1..=4).map(Int::<N>::from).collect();
401
402 test_raa!(TestZipTypes<N, K, M>, 4, |code: &RaaCode<_, _, _>| {
403 let encode_a: Vec<Int<K>> = code.encode(&a);
404 assert_eq!(
405 encode_a,
406 [
407 0x1E, 0x36, 0x39, 0x5A, 0x70, 0x7E, 0xA5, 0xC1, 0xCB, 0xDC, 0xF9, 0x11E, 0x124,
408 0x14C, 0x14D, 0x160
409 ]
410 .map(Int::<K>::from)
411 );
412 });
413 }
414
415 #[test]
416 fn encoding_zero_vector_results_in_zero_codeword() {
417 test_raa!(TestZipTypes<N, K, M>, 4, |code: &RaaCode<_, _, _>| {
418 let zero_vector: Vec<_> = vec![Int::<N>::zero(); code.row_len()];
419 let encoded_vector: Vec<Int<K>> = code.encode(&zero_vector);
420
421 let expected_codeword: Vec<Int<K>> = vec![Int::zero(); code.codeword_len()];
422
423 assert_eq!(
424 encoded_vector, expected_codeword,
425 "Encoding a zero vector should result in a zero codeword"
426 );
427 });
428 }
429
430 #[test]
431 fn in_place_permutation_should_not_affect_order() {
432 let data: Vec<Int<N>> = (1..=1024).map(Int::<N>::from).collect();
433 let row_len = data.len();
434 let codeword_1: Vec<Int<K>> = {
435 let code_in_place = RaaCode::<
436 TestZipTypes<N, K, M>,
437 RaaConfigGeneric<true, true>,
438 REPETITION_FACTOR,
439 >::new(row_len);
440 code_in_place.encode(&data)
441 };
442
443 let codeword_2: Vec<Int<K>> = {
444 let code_cloning = RaaCode::<
445 TestZipTypes<N, K, M>,
446 RaaConfigGeneric<false, true>,
447 REPETITION_FACTOR,
448 >::new(row_len);
449 code_cloning.encode(&data)
450 };
451 assert_eq!(
452 codeword_1, codeword_2,
453 "In-place permutation should not affect the final codeword"
454 );
455 }
456
457 #[test]
458 #[should_panic]
459 fn constructor_panics_on_insufficient_codeword_width() {
460 const N: usize = 1;
461 const K: usize = 1;
462
463 let _code = RaaCode::<
464 TestZipTypes<N, K, N>,
465 RaaConfigGeneric<false, true>,
466 REPETITION_FACTOR,
467 >::new(1 << 15);
468 }
469
470 #[test]
471 #[should_panic(expected = "Row length must match the code's row length")]
472 #[cfg(debug_assertions)]
473 fn encode_panics_on_mismatched_row_length() {
474 test_raa!(TestZipTypes<N, K, M>, 4, |code: &RaaCode<_, _, _>| {
475 let incorrect_row = vec![Int::<N>::from(1), Int::<N>::from(2), Int::<N>::from(3)];
476 let _: Vec<Int<K>> = code.encode(&incorrect_row);
477 });
478 }
479}