commonware_cryptography/reed_solomon/
engine.rs1mod cpu_features {
38 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
39 cpufeatures::new!(has_avx512, "avx512f", "gfni");
40 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
41 cpufeatures::new!(has_avx2, "avx2");
42 #[cfg(target_arch = "aarch64")]
43 cpufeatures::new!(has_neon, "neon");
44
45 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
46 pub(super) use self::{has_avx2::get as avx2, has_avx512::get as avx512};
47 #[cfg(target_arch = "aarch64")]
48 pub(super) use has_neon::get as neon;
49}
50
51#[cfg(target_arch = "aarch64")]
52pub use self::engine_neon::Neon;
53pub(crate) use self::shards::Shards;
54#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
55pub use self::{engine_avx2::Avx2, engine_avx512::Avx512};
56pub use self::{
57 engine_default::DefaultEngine, engine_naive::Naive, engine_scalar::Scalar, shards::ShardsRefMut,
58};
59pub(crate) use utils::{fft_skew_end, formal_derivative, ifft_skew_end, xor_within};
60
61mod engine_default;
62mod engine_naive;
63mod engine_scalar;
64
65#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
66mod engine_avx2;
67#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
68mod engine_avx512;
69
70#[cfg(target_arch = "aarch64")]
71mod engine_neon;
72
73mod fwht;
74mod shards;
75
76pub mod tables;
77pub mod utils;
78
79pub const GF_BITS: usize = 16;
81
82pub const GF_ORDER: usize = 65536;
84
85pub const GF_MODULUS: GfElement = 65535;
87
88pub const GF_POLYNOMIAL: usize = 0x1002D;
90
91pub const SHARD_CHUNK_BYTES: usize = 64;
97
98pub const CANTOR_BASIS: [GfElement; GF_BITS] = [
100 0x0001, 0xACCA, 0x3C0E, 0x163E, 0xC582, 0xED2E, 0x914C, 0x4012, 0x6C98, 0x10D8, 0x6A72, 0xB900,
101 0xFDB8, 0xFB34, 0xFF38, 0x991E,
102];
103
104pub type GfElement = u16;
106
107pub trait Engine {
116 fn fft(
130 &self,
131 data: &mut ShardsRefMut<'_>,
132 pos: usize,
133 size: usize,
134 truncated_size: usize,
135 skew_delta: usize,
136 );
137
138 fn ifft(
150 &self,
151 data: &mut ShardsRefMut<'_>,
152 pos: usize,
153 size: usize,
154 truncated_size: usize,
155 skew_delta: usize,
156 );
157
158 fn mul(&self, x: &mut [[u8; SHARD_CHUNK_BYTES]], log_m: GfElement);
163
164 fn eval_poly(erasures: &mut [GfElement; GF_ORDER], truncated_size: usize)
176 where
177 Self: Sized,
178 {
179 utils::eval_poly(erasures, truncated_size);
180 }
181}
182
183#[inline]
185fn validate_transform(
186 data: &ShardsRefMut<'_>,
187 pos: usize,
188 size: usize,
189 truncated_size: usize,
190 skew_delta: usize,
191) {
192 assert!(size.is_power_of_two() && size <= GF_ORDER);
193 assert!(truncated_size <= size);
194 assert!(pos <= data.len() && size <= data.len() - pos);
195
196 assert!(size == 1 || skew_delta <= GF_ORDER - size);
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use std::panic::{AssertUnwindSafe, catch_unwind};
205
206 fn engines() -> Vec<Box<dyn Engine>> {
208 let mut engines: Vec<Box<dyn Engine>> =
209 vec![Box::new(Scalar::new()), Box::new(Naive::new())];
210 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
211 {
212 if cpu_features::avx512() {
213 engines.push(Box::new(Avx512::new()));
214 }
215 if cpu_features::avx2() {
216 engines.push(Box::new(Avx2::new()));
217 }
218 }
219 #[cfg(target_arch = "aarch64")]
220 if cpu_features::neon() {
221 engines.push(Box::new(Neon::new()));
222 }
223 engines
224 }
225
226 fn invalid_transform(
229 shard_count: usize,
230 pos: usize,
231 size: usize,
232 truncated_size: usize,
233 skew_delta: usize,
234 ) {
235 for engine in engines() {
236 for inverse in [false, true] {
237 let mut shards = ShardsRefMut::new(shard_count, 0, &mut []);
238 let result = catch_unwind(AssertUnwindSafe(|| {
239 if inverse {
240 engine.ifft(&mut shards, pos, size, truncated_size, skew_delta);
241 } else {
242 engine.fft(&mut shards, pos, size, truncated_size, skew_delta);
243 }
244 }));
245 assert!(
246 result.is_err(),
247 "invalid transform accepted (inverse={inverse})"
248 );
249 }
250 }
251 }
252
253 #[test]
254 fn transform_zero_size() {
255 invalid_transform(4, 0, 0, 0, 0);
256 }
257
258 #[test]
259 fn transform_non_power_of_two_size() {
260 invalid_transform(4, 0, 3, 0, 0);
261 }
262
263 #[test]
264 fn transform_size_exceeds_field() {
265 invalid_transform(GF_ORDER * 2, 0, GF_ORDER * 2, 0, 0);
266 }
267
268 #[test]
269 fn transform_range_out_of_bounds() {
270 for pos in [3, 4, usize::MAX] {
271 invalid_transform(4, pos, 2, 0, 0);
272 }
273 }
274
275 #[test]
276 fn transform_truncation_out_of_bounds() {
277 invalid_transform(4, 0, 2, 3, 0);
278 }
279
280 #[test]
281 fn transform_skew_out_of_bounds() {
282 for skew_delta in [GF_ORDER - 1, usize::MAX] {
283 invalid_transform(4, 0, 2, 0, skew_delta);
284 }
285 }
286
287 #[test]
288 fn transform_empty_shards_and_identity() {
289 for engine in engines() {
290 let mut empty = ShardsRefMut::new(GF_ORDER + 3, 0, &mut []);
291 engine.fft(&mut empty, 3, GF_ORDER, 0, 0);
292 engine.ifft(&mut empty, 3, GF_ORDER, 0, 0);
293
294 for truncated_size in [0, 1] {
295 let mut data = [[17; SHARD_CHUNK_BYTES]; 3];
296 let mut shards = ShardsRefMut::new(3, 1, &mut data);
297 engine.fft(&mut shards, 1, 1, truncated_size, usize::MAX);
298 engine.ifft(&mut shards, 1, 1, 1, usize::MAX);
299 assert_eq!(data, [[17; SHARD_CHUNK_BYTES]; 3]);
300 }
301 }
302 }
303
304 #[test]
305 fn eval_poly_feature_guard() {
306 let mut input = Box::new([0; GF_ORDER]);
307 input[..4].copy_from_slice(&[1, 0, 1, 1]);
308 let mut expected = input.clone();
309 Scalar::eval_poly(&mut expected, 4);
310
311 type Evaluate = fn(&mut [GfElement; GF_ORDER], usize);
312 let evaluators: &[(Evaluate, bool)] = &[
313 (DefaultEngine::eval_poly, true),
314 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
315 (Avx2::eval_poly, cpu_features::avx2()),
316 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
317 (Avx512::eval_poly, cpu_features::avx512()),
318 #[cfg(target_arch = "aarch64")]
319 (Neon::eval_poly, cpu_features::neon()),
320 ];
321
322 for (evaluate, supported) in evaluators {
323 let mut actual = input.clone();
324 if *supported {
325 evaluate(&mut actual, 4);
326 assert_eq!(actual, expected);
327 } else {
328 assert!(catch_unwind(AssertUnwindSafe(|| evaluate(&mut actual, 4))).is_err());
329 assert_eq!(actual, input);
330 }
331 }
332 }
333
334 #[test]
335 #[should_panic]
336 fn eval_poly_truncation_out_of_bounds() {
337 utils::eval_poly(&mut [0; GF_ORDER], GF_ORDER + 1);
338 }
339}