Skip to main content

commonware_cryptography/reed_solomon/engine/
tables.rs

1//! Lookup-tables used by [`Engine`]:s.
2//!
3//! All tables are global and each is initialized at most once.
4//!
5//! # Tables
6//!
7//! | Table        | Size    | Used in encoding | Used in decoding | By engines         |
8//! | ------------ | ------- | ---------------- | ---------------- | ------------------ |
9//! | [`Exp`]      | 128 kiB | yes              | yes              | all                |
10//! | [`Log`]      | 128 kiB | yes              | yes              | all                |
11//! | [`LogWalsh`] | 128 kiB | -                | yes              | all                |
12//! | [`Mul16`]    | 8 MiB   | yes              | yes              | [`NoSimd`]         |
13//! | [`Mul128`]   | 8 MiB   | yes              | yes              | `Avx2` `Ssse3`     |
14//! | [`Skew`]     | 128 kiB | yes              | yes              | all                |
15//!
16//! [`NoSimd`]: crate::reed_solomon::engine::NoSimd
17//! [`Engine`]: crate::reed_solomon::engine
18//!
19
20use crate::reed_solomon::engine::{
21    fwht, utils, GfElement, CANTOR_BASIS, GF_BITS, GF_MODULUS, GF_ORDER, GF_POLYNOMIAL,
22};
23#[cfg(not(feature = "std"))]
24use alloc::boxed::Box;
25#[cfg(not(feature = "std"))]
26use alloc::vec;
27#[cfg(not(feature = "std"))]
28use once_cell::race::OnceBox;
29#[cfg(feature = "std")]
30use std::sync::LazyLock;
31
32// ======================================================================
33// TYPE ALIASES - PUBLIC
34
35/// Used by [`Naive`] engine for multiplications
36/// and by all [`Engine`]:s to initialize other tables.
37///
38/// [`Naive`]: crate::reed_solomon::engine::Naive
39/// [`Engine`]: crate::reed_solomon::engine
40pub type Exp = [GfElement; GF_ORDER];
41
42/// Used by [`Naive`] engine for multiplications
43/// and by all [`Engine`]:s to initialize other tables.
44///
45/// [`Naive`]: crate::reed_solomon::engine::Naive
46/// [`Engine`]: crate::reed_solomon::engine
47pub type Log = [GfElement; GF_ORDER];
48
49/// Used by `Avx2` and `Ssse3` engines for multiplications.
50pub type Mul128 = [Multiply128lutT; GF_ORDER];
51
52/// Elements of the Mul128 table
53#[derive(Clone, Debug)]
54pub struct Multiply128lutT {
55    /// Lower half of `GfElements`
56    pub lo: [u128; 4],
57    /// Upper half of `GfElements`
58    pub hi: [u128; 4],
59}
60
61/// Used by all [`Engine`]:s in [`Engine::eval_poly`].
62///
63/// [`Engine`]: crate::reed_solomon::engine
64/// [`Engine::eval_poly`]: crate::reed_solomon::engine::Engine::eval_poly
65pub type LogWalsh = [GfElement; GF_ORDER];
66
67/// Used by [`NoSimd`] engine for multiplications.
68///
69/// [`NoSimd`]: crate::reed_solomon::engine::NoSimd
70pub type Mul16 = [[[GfElement; 16]; 4]; GF_ORDER];
71
72/// Used by all [`Engine`]:s for FFT and IFFT.
73///
74/// [`Engine`]: crate::reed_solomon::engine
75pub type Skew = [GfElement; GF_MODULUS as usize];
76
77// ======================================================================
78// ExpLog - PUBLIC
79
80/// Struct holding the [`Exp`] and [`Log`] lookup tables.
81pub struct ExpLog {
82    /// Exponentiation table.
83    pub exp: Box<Exp>,
84    /// Logarithm table.
85    pub log: Box<Log>,
86}
87
88// ======================================================================
89// STATIC - PUBLIC
90
91/// Lazily initialized exponentiation and logarithm tables.
92pub fn get_exp_log() -> &'static ExpLog {
93    #[cfg(feature = "std")]
94    {
95        static EXP_LOG: LazyLock<ExpLog> = LazyLock::new(initialize_exp_log);
96        &EXP_LOG
97    }
98    #[cfg(not(feature = "std"))]
99    {
100        static EXP_LOG: OnceBox<ExpLog> = OnceBox::new();
101        EXP_LOG.get_or_init(|| Box::new(initialize_exp_log()))
102    }
103}
104
105/// Lazily initialized logarithmic Walsh transform table.
106pub fn get_log_walsh() -> &'static LogWalsh {
107    #[cfg(feature = "std")]
108    {
109        static LOG_WALSH: LazyLock<Box<LogWalsh>> = LazyLock::new(initialize_log_walsh);
110        &LOG_WALSH
111    }
112    #[cfg(not(feature = "std"))]
113    {
114        static LOG_WALSH: OnceBox<LogWalsh> = OnceBox::new();
115        LOG_WALSH.get_or_init(initialize_log_walsh)
116    }
117}
118
119/// Lazily initialized multiplication table for the `NoSimd` engine.
120pub fn get_mul16() -> &'static Mul16 {
121    #[cfg(feature = "std")]
122    {
123        static MUL16: LazyLock<Box<Mul16>> = LazyLock::new(initialize_mul16);
124        &MUL16
125    }
126    #[cfg(not(feature = "std"))]
127    {
128        static MUL16: OnceBox<Mul16> = OnceBox::new();
129        MUL16.get_or_init(initialize_mul16)
130    }
131}
132
133/// Lazily initialized multiplication table for SIMD engines.
134pub fn get_mul128() -> &'static Mul128 {
135    #[cfg(feature = "std")]
136    {
137        static MUL128: LazyLock<Box<Mul128>> = LazyLock::new(initialize_mul128);
138        &MUL128
139    }
140    #[cfg(not(feature = "std"))]
141    {
142        static MUL128: OnceBox<Mul128> = OnceBox::new();
143        MUL128.get_or_init(initialize_mul128)
144    }
145}
146
147/// Lazily initialized skew table used in FFT and IFFT operations.
148pub fn get_skew() -> &'static Skew {
149    #[cfg(feature = "std")]
150    {
151        static SKEW: LazyLock<Box<Skew>> = LazyLock::new(initialize_skew);
152        &SKEW
153    }
154    #[cfg(not(feature = "std"))]
155    {
156        static SKEW: OnceBox<Skew> = OnceBox::new();
157        SKEW.get_or_init(initialize_skew)
158    }
159}
160
161// ======================================================================
162// FUNCTIONS - PUBLIC - math
163
164/// Calculates `x * log_m` using [`Exp`] and [`Log`] tables.
165#[inline(always)]
166pub fn mul(x: GfElement, log_m: GfElement, exp: &Exp, log: &Log) -> GfElement {
167    if x == 0 {
168        0
169    } else {
170        exp[utils::add_mod(log[x as usize], log_m) as usize]
171    }
172}
173
174// ======================================================================
175// FUNCTIONS - PRIVATE - initialize tables
176
177fn initialize_exp_log() -> ExpLog {
178    let mut exp = Box::new([0; GF_ORDER]);
179    let mut log = Box::new([0; GF_ORDER]);
180
181    // GENERATE LFSR TABLE
182
183    let mut state = 1;
184    for i in 0..GF_MODULUS {
185        exp[state] = i;
186        state <<= 1;
187        if state >= GF_ORDER {
188            state ^= GF_POLYNOMIAL;
189        }
190    }
191    exp[0] = GF_MODULUS;
192
193    // CONVERT TO CANTOR BASIS
194
195    log[0] = 0;
196    for (i, basis) in CANTOR_BASIS.iter().copied().enumerate().take(GF_BITS) {
197        let width = 1usize << i;
198        for j in 0..width {
199            log[j + width] = log[j] ^ basis;
200        }
201    }
202
203    for value in log.iter_mut() {
204        *value = exp[*value as usize];
205    }
206
207    for (i, value) in log.iter().copied().enumerate() {
208        exp[value as usize] = i as GfElement;
209    }
210
211    exp[GF_MODULUS as usize] = exp[0];
212
213    ExpLog { exp, log }
214}
215
216fn initialize_log_walsh() -> Box<LogWalsh> {
217    let log = get_exp_log().log.as_slice();
218
219    let mut log_walsh: Box<LogWalsh> = Box::new([0; GF_ORDER]);
220
221    log_walsh.copy_from_slice(log);
222    log_walsh[0] = 0;
223    fwht::fwht(log_walsh.as_mut(), GF_ORDER);
224
225    log_walsh
226}
227
228fn initialize_mul16() -> Box<Mul16> {
229    let exp = &get_exp_log().exp;
230    let log = &get_exp_log().log;
231    let mut mul16 = vec![[[0; 16]; 4]; GF_ORDER];
232
233    for log_m in 0..=GF_MODULUS {
234        let lut = &mut mul16[log_m as usize];
235        let [row0, row1, row2, row3] = lut;
236        for (i, (((x0, x1), x2), x3)) in row0
237            .iter_mut()
238            .zip(row1.iter_mut())
239            .zip(row2.iter_mut())
240            .zip(row3.iter_mut())
241            .enumerate()
242        {
243            *x0 = mul(i as GfElement, log_m, exp, log);
244            *x1 = mul((i << 4) as GfElement, log_m, exp, log);
245            *x2 = mul((i << 8) as GfElement, log_m, exp, log);
246            *x3 = mul((i << 12) as GfElement, log_m, exp, log);
247        }
248    }
249
250    mul16.into_boxed_slice().try_into().unwrap()
251}
252
253fn initialize_mul128() -> Box<Mul128> {
254    // Based on:
255    // https://github.com/catid/leopard/blob/22ddc7804998d31c8f1a2617ee720e063b1fa6cd/LeopardFF16.cpp#L375
256    let exp = &get_exp_log().exp;
257    let log = &get_exp_log().log;
258
259    let mut mul128 = vec![
260        Multiply128lutT {
261            lo: [0; 4],
262            hi: [0; 4],
263        };
264        GF_ORDER
265    ];
266
267    for log_m in 0..=GF_MODULUS {
268        for i in 0..=3 {
269            let mut prod_lo = [0u8; 16];
270            let mut prod_hi = [0u8; 16];
271            for x in 0..16 {
272                let prod = mul((x << (i * 4)) as GfElement, log_m, exp, log);
273                prod_lo[x] = prod as u8;
274                prod_hi[x] = (prod >> 8) as u8;
275            }
276            mul128[log_m as usize].lo[i] = u128::from_le_bytes(prod_lo);
277            mul128[log_m as usize].hi[i] = u128::from_le_bytes(prod_hi);
278        }
279    }
280
281    mul128.into_boxed_slice().try_into().unwrap()
282}
283
284fn initialize_skew() -> Box<Skew> {
285    let exp = &get_exp_log().exp;
286    let log = &get_exp_log().log;
287
288    let mut skew = Box::new([0; GF_MODULUS as usize]);
289
290    let mut temp = [0; GF_BITS - 1];
291
292    for (i, value) in temp.iter_mut().enumerate() {
293        *value = 1 << (i + 1);
294    }
295
296    for m in 0..GF_BITS - 1 {
297        let step: usize = 1 << (m + 1);
298
299        skew[(1 << m) - 1] = 0;
300
301        for (i, temp_i) in temp.iter().copied().enumerate().skip(m) {
302            let s: usize = 1 << (i + 1);
303            let mut j = (1 << m) - 1;
304            while j < s {
305                skew[j + s] = skew[j] ^ temp_i;
306                j += step;
307            }
308        }
309
310        temp[m] = GF_MODULUS - log[mul(temp[m], log[(temp[m] ^ 1) as usize], exp, log) as usize];
311
312        for i in m + 1..GF_BITS - 1 {
313            let sum = utils::add_mod(log[(temp[i] ^ 1) as usize], temp[m]);
314            temp[i] = mul(temp[i], sum, exp, log);
315        }
316    }
317
318    for value in skew.iter_mut() {
319        *value = log[*value as usize];
320    }
321
322    skew
323}