commonware_cryptography/reed_solomon/engine/
tables.rs1use crate::reed_solomon::engine::{
27 CANTOR_BASIS, GF_BITS, GF_MODULUS, GF_ORDER, GF_POLYNOMIAL, GfElement, fwht, utils,
28};
29#[cfg(not(feature = "std"))]
30use alloc::boxed::Box;
31#[cfg(not(feature = "std"))]
32use alloc::vec;
33#[cfg(not(feature = "std"))]
34use alloc::vec::Vec;
35#[cfg(not(feature = "std"))]
36use once_cell::race::OnceBox;
37#[cfg(feature = "std")]
38use std::sync::{LazyLock, OnceLock};
39
40pub type Exp = [GfElement; GF_ORDER];
48
49pub type Log = [GfElement; GF_ORDER];
58
59pub type Mul128 = [Multiply128lutT; GF_ORDER];
63
64#[cfg(any(test, target_arch = "x86", target_arch = "x86_64"))]
68pub(crate) type MulGfni = [MultiplyGfni; GF_ORDER];
69
70#[cfg(any(test, target_arch = "x86", target_arch = "x86_64"))]
76#[derive(Clone, Debug)]
77pub(crate) struct MultiplyGfni {
78 pub(crate) low_from_low: u64,
80 pub(crate) low_from_high: u64,
82 pub(crate) high_from_low: u64,
84 pub(crate) high_from_high: u64,
86}
87
88#[derive(Clone, Debug)]
93pub struct Multiply128lutT {
94 pub lo: [u128; 4],
96 pub hi: [u128; 4],
98}
99
100pub type LogWalsh = [GfElement; GF_ORDER];
105
106pub type Mul16 = [[[GfElement; 16]; 4]; GF_ORDER];
113
114pub type Skew = [GfElement; GF_MODULUS as usize];
121
122pub struct ExpLog {
124 pub exp: Box<Exp>,
126 pub log: Box<Log>,
128}
129
130pub fn get_exp_log() -> &'static ExpLog {
132 #[cfg(feature = "std")]
133 {
134 static EXP_LOG: LazyLock<ExpLog> = LazyLock::new(initialize_exp_log);
135 &EXP_LOG
136 }
137 #[cfg(not(feature = "std"))]
138 {
139 static EXP_LOG: OnceBox<ExpLog> = OnceBox::new();
140 EXP_LOG.get_or_init(|| Box::new(initialize_exp_log()))
141 }
142}
143
144pub fn get_log_walsh() -> &'static LogWalsh {
146 #[cfg(feature = "std")]
147 {
148 static LOG_WALSH: LazyLock<Box<LogWalsh>> = LazyLock::new(initialize_log_walsh);
149 &LOG_WALSH
150 }
151 #[cfg(not(feature = "std"))]
152 {
153 static LOG_WALSH: OnceBox<LogWalsh> = OnceBox::new();
154 LOG_WALSH.get_or_init(initialize_log_walsh)
155 }
156}
157
158pub(crate) fn get_short_log_walsh(n: usize) -> &'static [GfElement] {
168 assert!(n.is_power_of_two() && n < GF_ORDER);
169 let level = n.trailing_zeros() as usize;
170 #[cfg(feature = "std")]
171 {
172 static KERNELS: [OnceLock<Vec<GfElement>>; GF_BITS] = [const { OnceLock::new() }; GF_BITS];
173 KERNELS[level].get_or_init(|| initialize_short_log_walsh(n))
174 }
175 #[cfg(not(feature = "std"))]
176 {
177 static KERNELS: [OnceBox<Vec<GfElement>>; GF_BITS] = [const { OnceBox::new() }; GF_BITS];
178 KERNELS[level].get_or_init(|| Box::new(initialize_short_log_walsh(n)))
179 }
180}
181
182pub fn get_mul16() -> &'static Mul16 {
184 #[cfg(feature = "std")]
185 {
186 static MUL16: LazyLock<Box<Mul16>> = LazyLock::new(initialize_mul16);
187 &MUL16
188 }
189 #[cfg(not(feature = "std"))]
190 {
191 static MUL16: OnceBox<Mul16> = OnceBox::new();
192 MUL16.get_or_init(initialize_mul16)
193 }
194}
195
196pub fn get_mul128() -> &'static Mul128 {
198 #[cfg(feature = "std")]
199 {
200 static MUL128: LazyLock<Box<Mul128>> = LazyLock::new(initialize_mul128);
201 &MUL128
202 }
203 #[cfg(not(feature = "std"))]
204 {
205 static MUL128: OnceBox<Mul128> = OnceBox::new();
206 MUL128.get_or_init(initialize_mul128)
207 }
208}
209
210#[cfg(any(test, target_arch = "x86", target_arch = "x86_64"))]
212pub(crate) fn get_mul_gfni() -> &'static MulGfni {
213 #[cfg(feature = "std")]
214 {
215 static MUL_GFNI: LazyLock<Box<MulGfni>> = LazyLock::new(initialize_mul_gfni);
216 &MUL_GFNI
217 }
218 #[cfg(not(feature = "std"))]
219 {
220 static MUL_GFNI: OnceBox<MulGfni> = OnceBox::new();
221 MUL_GFNI.get_or_init(initialize_mul_gfni)
222 }
223}
224
225pub fn get_skew() -> &'static Skew {
227 #[cfg(feature = "std")]
228 {
229 static SKEW: LazyLock<Box<Skew>> = LazyLock::new(initialize_skew);
230 &SKEW
231 }
232 #[cfg(not(feature = "std"))]
233 {
234 static SKEW: OnceBox<Skew> = OnceBox::new();
235 SKEW.get_or_init(initialize_skew)
236 }
237}
238
239#[inline(always)]
241pub fn mul(x: GfElement, log_m: GfElement, exp: &Exp, log: &Log) -> GfElement {
242 if x == 0 {
243 0
244 } else {
245 exp[utils::add_mod(log[x as usize], log_m) as usize]
246 }
247}
248
249fn initialize_exp_log() -> ExpLog {
251 let mut exp = Box::new([0; GF_ORDER]);
252 let mut log = Box::new([0; GF_ORDER]);
253
254 let mut state = 1;
256 for i in 0..GF_MODULUS {
257 exp[state] = i;
258 state <<= 1;
259 if state >= GF_ORDER {
260 state ^= GF_POLYNOMIAL;
261 }
262 }
263 exp[0] = GF_MODULUS;
264
265 log[0] = 0;
267 for (i, basis) in CANTOR_BASIS.iter().copied().enumerate().take(GF_BITS) {
268 let width = 1usize << i;
269 for j in 0..width {
270 log[j + width] = log[j] ^ basis;
271 }
272 }
273 for value in log.iter_mut() {
274 *value = exp[*value as usize];
275 }
276 for (i, value) in log.iter().copied().enumerate() {
277 exp[value as usize] = i as GfElement;
278 }
279
280 exp[GF_MODULUS as usize] = exp[0];
281
282 ExpLog { exp, log }
283}
284
285fn initialize_log_walsh() -> Box<LogWalsh> {
287 let log = get_exp_log().log.as_slice();
288
289 let mut log_walsh: Box<LogWalsh> = Box::new([0; GF_ORDER]);
290
291 log_walsh.copy_from_slice(log);
292 log_walsh[0] = 0;
293 fwht::fwht(log_walsh.as_mut(), GF_ORDER);
294
295 log_walsh
296}
297
298fn initialize_short_log_walsh(n: usize) -> Vec<GfElement> {
303 let log = &get_exp_log().log;
304 let mut kernel = log[..n].to_vec();
305 kernel[0] = 0;
306 fwht::fwht(&mut kernel, n);
307
308 for factor in &mut kernel {
311 *factor = factor.rotate_right(n.trailing_zeros());
312 }
313 kernel
314}
315
316fn initialize_mul16() -> Box<Mul16> {
318 let exp = &get_exp_log().exp;
319 let log = &get_exp_log().log;
320 let mut mul16 = vec![[[0; 16]; 4]; GF_ORDER];
321
322 for log_m in 0..=GF_MODULUS {
323 let lut = &mut mul16[log_m as usize];
324 let [row0, row1, row2, row3] = lut;
325 for (i, (((x0, x1), x2), x3)) in row0
326 .iter_mut()
327 .zip(row1.iter_mut())
328 .zip(row2.iter_mut())
329 .zip(row3.iter_mut())
330 .enumerate()
331 {
332 *x0 = mul(i as GfElement, log_m, exp, log);
333 *x1 = mul((i << 4) as GfElement, log_m, exp, log);
334 *x2 = mul((i << 8) as GfElement, log_m, exp, log);
335 *x3 = mul((i << 12) as GfElement, log_m, exp, log);
336 }
337 }
338
339 mul16.into_boxed_slice().try_into().unwrap()
340}
341
342fn initialize_mul128() -> Box<Mul128> {
344 let exp = &get_exp_log().exp;
347 let log = &get_exp_log().log;
348
349 let mut mul128 = vec![
350 Multiply128lutT {
351 lo: [0; 4],
352 hi: [0; 4],
353 };
354 GF_ORDER
355 ];
356
357 for log_m in 0..=GF_MODULUS {
358 for i in 0..=3 {
359 let mut prod_lo = [0u8; 16];
360 let mut prod_hi = [0u8; 16];
361 for x in 0..16 {
362 let prod = mul((x << (i * 4)) as GfElement, log_m, exp, log);
363 prod_lo[x] = prod as u8;
364 prod_hi[x] = (prod >> 8) as u8;
365 }
366 mul128[log_m as usize].lo[i] = u128::from_ne_bytes(prod_lo);
367 mul128[log_m as usize].hi[i] = u128::from_ne_bytes(prod_hi);
368 }
369 }
370
371 mul128.into_boxed_slice().try_into().unwrap()
372}
373
374#[cfg(any(test, target_arch = "x86", target_arch = "x86_64"))]
376fn initialize_mul_gfni() -> Box<MulGfni> {
377 let exp = &get_exp_log().exp;
378 let log = &get_exp_log().log;
379 let mut table = vec![
380 MultiplyGfni {
381 low_from_low: 0,
382 low_from_high: 0,
383 high_from_low: 0,
384 high_from_high: 0,
385 };
386 GF_ORDER
387 ];
388
389 let mut basis_matrices = [[0u64; 4]; GF_BITS];
390 for (coefficient_bit, matrix) in basis_matrices.iter_mut().enumerate() {
391 let log_m = log[1 << coefficient_bit];
392 let mut rows = [[0u8; 8]; 4];
393 for input_bit in 0..16 {
394 let product = mul(1u16 << input_bit, log_m, exp, log);
395 for output_bit in 0..16 {
396 let block = (output_bit / 8) * 2 + input_bit / 8;
398 rows[block][output_bit % 8] |=
399 (((product >> output_bit) & 1) as u8) << (input_bit % 8);
400 }
401 }
402
403 *matrix = rows.map(u64::from_be_bytes);
405 }
406
407 let mut current = [0u64; 4];
411 for step in 1..GF_ORDER {
412 let toggled_bit = step.trailing_zeros() as usize;
413 for (block, basis) in current.iter_mut().zip(basis_matrices[toggled_bit]) {
414 *block ^= basis;
415 }
416
417 let coefficient = step ^ (step >> 1);
418 let [low_from_low, low_from_high, high_from_low, high_from_high] = current;
419 table[log[coefficient] as usize] = MultiplyGfni {
420 low_from_low,
421 low_from_high,
422 high_from_low,
423 high_from_high,
424 };
425 }
426
427 let identity = table[0].clone();
430 table[GF_MODULUS as usize] = identity;
431
432 table.into_boxed_slice().try_into().unwrap()
433}
434
435fn initialize_skew() -> Box<Skew> {
439 let exp = &get_exp_log().exp;
440 let log = &get_exp_log().log;
441
442 let mut skew = Box::new([0; GF_MODULUS as usize]);
443
444 let mut temp = [0; GF_BITS - 1];
445
446 for (i, value) in temp.iter_mut().enumerate() {
447 *value = 1 << (i + 1);
448 }
449
450 for m in 0..GF_BITS - 1 {
451 let step: usize = 1 << (m + 1);
452
453 skew[(1 << m) - 1] = 0;
454
455 for (i, temp_i) in temp.iter().copied().enumerate().skip(m) {
456 let s: usize = 1 << (i + 1);
457 let mut j = (1 << m) - 1;
458 while j < s {
459 skew[j + s] = skew[j] ^ temp_i;
460 j += step;
461 }
462 }
463
464 temp[m] = GF_MODULUS - log[mul(temp[m], log[(temp[m] ^ 1) as usize], exp, log) as usize];
465
466 for i in m + 1..GF_BITS - 1 {
467 let sum = utils::add_mod(log[(temp[i] ^ 1) as usize], temp[m]);
468 temp[i] = mul(temp[i], sum, exp, log);
469 }
470 }
471
472 for value in skew.iter_mut() {
473 *value = log[*value as usize];
474 }
475
476 skew
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn mul128_byte_layout() {
485 let scalar = get_mul16();
486 for (vector, scalar) in get_mul128().iter().zip(scalar.iter()) {
487 for ((lo, hi), products) in vector.lo.iter().zip(&vector.hi).zip(scalar) {
488 let lo = lo.to_ne_bytes();
489 let hi = hi.to_ne_bytes();
490 for (index, product) in products.iter().enumerate() {
491 assert_eq!(lo[index], *product as u8);
492 assert_eq!(hi[index], (product >> 8) as u8);
493 }
494 }
495 }
496 }
497
498 fn affine(matrix: u64, input: u8) -> u8 {
500 let matrix = matrix.to_le_bytes();
501 let mut output = 0;
502 for output_bit in 0..8 {
503 output |= ((matrix[7 - output_bit] & input).count_ones() as u8 & 1) << output_bit;
504 }
505 output
506 }
507
508 #[test]
509 fn mul_gfni_matrix_semantics() {
510 assert_eq!(
511 core::mem::size_of::<MultiplyGfni>(),
512 4 * core::mem::size_of::<u64>()
513 );
514 assert_eq!(core::mem::size_of::<MulGfni>(), 2 * 1024 * 1024);
515
516 let exp_log = get_exp_log();
517 for (log_m, matrices) in get_mul_gfni().iter().enumerate() {
518 for input_bit in 0..16 {
519 let input = 1u16 << input_bit;
520 let input_lo = input as u8;
521 let input_hi = (input >> 8) as u8;
522 let output_lo = affine(matrices.low_from_low, input_lo)
523 ^ affine(matrices.low_from_high, input_hi);
524 let output_hi = affine(matrices.high_from_low, input_lo)
525 ^ affine(matrices.high_from_high, input_hi);
526 let actual = output_lo as u16 | (output_hi as u16) << 8;
527 let expected = mul(input, log_m as GfElement, &exp_log.exp, &exp_log.log);
528 assert_eq!(actual, expected, "log_m={log_m} input_bit={input_bit}");
529 }
530 }
531
532 for log_m in [0, GF_MODULUS as usize] {
533 let identity = &get_mul_gfni()[log_m];
534 assert_eq!(identity.low_from_low, 0x0102_0408_1020_4080);
535 assert_eq!(identity.low_from_high, 0);
536 assert_eq!(identity.high_from_low, 0);
537 assert_eq!(identity.high_from_high, 0x0102_0408_1020_4080);
538 }
539 }
540}