Skip to main content

commonware_cryptography/reed_solomon/rate/
rate_high.rs

1use crate::reed_solomon::{
2    DecoderResult, EncoderResult, Error,
3    engine::{self, Engine, GF_MODULUS, GF_ORDER, SHARD_CHUNK_BYTES},
4    rate::{DecoderWork, EncoderWork, Rate, RateDecoder, RateEncoder},
5};
6use core::marker::PhantomData;
7
8// ======================================================================
9// HighRate - PUBLIC
10
11/// Reed-Solomon encoder/decoder generator using only high rate.
12pub struct HighRate<E: Engine>(PhantomData<E>);
13
14impl<E: Engine> Rate<E> for HighRate<E> {
15    type RateEncoder = HighRateEncoder<E>;
16    type RateDecoder = HighRateDecoder<E>;
17
18    fn supports(original_count: usize, recovery_count: usize) -> bool {
19        original_count > 0
20            && recovery_count > 0
21            && original_count < GF_ORDER
22            && recovery_count < GF_ORDER
23            && recovery_count.next_power_of_two() + original_count <= GF_ORDER
24    }
25}
26
27// ======================================================================
28// HighRateEncoder - PUBLIC
29
30/// Reed-Solomon encoder using only high rate.
31pub struct HighRateEncoder<E: Engine> {
32    engine: E,
33    work: EncoderWork,
34}
35
36impl<E: Engine> RateEncoder<E> for HighRateEncoder<E> {
37    type Rate = HighRate<E>;
38
39    fn add_original_shard<T: AsRef<[u8]>>(&mut self, original_shard: T) -> Result<(), Error> {
40        self.work.add_original_shard(original_shard)
41    }
42
43    fn encode(&mut self) -> Result<EncoderResult<'_>, Error> {
44        let (mut work, original_count, recovery_count) = self.work.encode_begin()?;
45        let chunk_size = recovery_count.next_power_of_two();
46        let engine = &self.engine;
47
48        // FIRST CHUNK
49
50        let first_count = core::cmp::min(original_count, chunk_size);
51
52        work.zero(first_count..chunk_size);
53        engine::ifft_skew_end(engine, &mut work, 0, chunk_size, first_count);
54
55        if original_count > chunk_size {
56            // FULL CHUNKS
57
58            let mut chunk_start = chunk_size;
59            while chunk_start + chunk_size <= original_count {
60                engine::ifft_skew_end(engine, &mut work, chunk_start, chunk_size, chunk_size);
61                engine::xor_within(&mut work, 0, chunk_start, chunk_size);
62                chunk_start += chunk_size;
63            }
64
65            // FINAL PARTIAL CHUNK
66
67            let last_count = original_count % chunk_size;
68            if last_count > 0 {
69                work.zero(chunk_start + last_count..);
70                engine::ifft_skew_end(engine, &mut work, chunk_start, chunk_size, last_count);
71                engine::xor_within(&mut work, 0, chunk_start, chunk_size);
72            }
73        }
74
75        // FFT
76
77        engine.fft(&mut work, 0, chunk_size, recovery_count, 0);
78
79        // UNDO LAST CHUNK ENCODING
80
81        self.work.undo_last_chunk_encoding();
82
83        // DONE
84
85        Ok(EncoderResult::new(&mut self.work))
86    }
87
88    fn into_parts(self) -> (E, EncoderWork) {
89        (self.engine, self.work)
90    }
91
92    fn new(
93        original_count: usize,
94        recovery_count: usize,
95        shard_bytes: usize,
96        engine: E,
97        work: Option<EncoderWork>,
98    ) -> Result<Self, Error> {
99        let mut work = work.unwrap_or_default();
100        Self::reset_work(original_count, recovery_count, shard_bytes, &mut work)?;
101        Ok(Self { engine, work })
102    }
103
104    fn reset(
105        &mut self,
106        original_count: usize,
107        recovery_count: usize,
108        shard_bytes: usize,
109    ) -> Result<(), Error> {
110        Self::reset_work(original_count, recovery_count, shard_bytes, &mut self.work)
111    }
112}
113
114// ======================================================================
115// HighRateEncoder - PRIVATE
116
117impl<E: Engine> HighRateEncoder<E> {
118    fn reset_work(
119        original_count: usize,
120        recovery_count: usize,
121        shard_bytes: usize,
122        work: &mut EncoderWork,
123    ) -> Result<(), Error> {
124        Self::validate(original_count, recovery_count, shard_bytes)?;
125        work.reset(
126            original_count,
127            recovery_count,
128            shard_bytes,
129            Self::work_count(original_count, recovery_count),
130        );
131        Ok(())
132    }
133
134    fn work_count(original_count: usize, recovery_count: usize) -> usize {
135        assert!(Self::supports(original_count, recovery_count));
136
137        let chunk_size = recovery_count.next_power_of_two();
138
139        original_count.next_multiple_of(chunk_size)
140    }
141}
142
143// ======================================================================
144// HighRateDecoder - PUBLIC
145
146/// Reed-Solomon decoder using only high rate.
147pub struct HighRateDecoder<E: Engine> {
148    engine: E,
149    work: DecoderWork,
150}
151
152impl<E: Engine> RateDecoder<E> for HighRateDecoder<E> {
153    type Rate = HighRate<E>;
154
155    fn add_original_shard<T: AsRef<[u8]>>(
156        &mut self,
157        index: usize,
158        original_shard: T,
159    ) -> Result<(), Error> {
160        self.work.add_original_shard(index, original_shard)
161    }
162
163    fn add_recovery_shard<T: AsRef<[u8]>>(
164        &mut self,
165        index: usize,
166        recovery_shard: T,
167    ) -> Result<(), Error> {
168        self.work.add_recovery_shard(index, recovery_shard)
169    }
170
171    fn decode(&mut self, compute_recovery: bool) -> Result<Option<DecoderResult<'_>>, Error> {
172        let Some((mut work, original_count, recovery_count, received)) =
173            self.work.decode_begin()?
174        else {
175            // Every original was provided: nothing to reconstruct. Clear the received state and
176            // report nothing.
177            self.work.reset_received();
178            return Ok(None);
179        };
180
181        let chunk_size = recovery_count.next_power_of_two();
182        let original_end = chunk_size + original_count;
183        let work_count = work.len();
184
185        // ERASURE LOCATIONS
186
187        let mut erasures = [0; GF_ORDER];
188
189        for i in 0..recovery_count {
190            if !received[i] {
191                erasures[i] = 1;
192            }
193        }
194
195        erasures[recovery_count..chunk_size].fill(1);
196
197        for i in chunk_size..original_end {
198            if !received[i] {
199                erasures[i] = 1;
200            }
201        }
202
203        // EVALUATE POLYNOMIAL
204
205        E::eval_poly(&mut erasures, original_end);
206
207        // MULTIPLY SHARDS
208
209        // work[               .. recovery_count] = recovery * erasures
210        // work[recovery_count .. chunk_size    ] = 0
211        // work[chunk_size     .. original_end  ] = original * erasures
212        // work[original_end   ..               ] = 0
213
214        for i in 0..recovery_count {
215            if received[i] {
216                self.engine.mul(&mut work[i], erasures[i]);
217            } else {
218                work[i].fill([0; SHARD_CHUNK_BYTES]);
219            }
220        }
221
222        work.zero(recovery_count..chunk_size);
223
224        for i in chunk_size..original_end {
225            if received[i] {
226                self.engine.mul(&mut work[i], erasures[i]);
227            } else {
228                work[i].fill([0; SHARD_CHUNK_BYTES]);
229            }
230        }
231
232        work.zero(original_end..);
233
234        // IFFT / FORMAL DERIVATIVE / FFT
235
236        self.engine.ifft(&mut work, 0, work_count, original_end, 0);
237        engine::formal_derivative(&mut work);
238        self.engine.fft(&mut work, 0, work_count, original_end, 0);
239
240        // REVEAL ERASURES
241
242        for i in chunk_size..original_end {
243            if !received[i] {
244                self.engine.mul(&mut work[i], GF_MODULUS - erasures[i]);
245            }
246        }
247
248        // REVEAL ERASURES (RECOVERY)
249        //
250        // Only when the caller passed `compute_recovery = true` to `decode`. Recovery shards
251        // live at `work[0..recovery_count]`. Un-scale the missing ones by the inverse locator so
252        // they hold the canonical recovery values, mirroring the original reveal above. This lets
253        // `DecoderResult::recovery` return them without a separate re-encode.
254
255        if compute_recovery {
256            for i in 0..recovery_count {
257                if !received[i] {
258                    self.engine.mul(&mut work[i], GF_MODULUS - erasures[i]);
259                }
260            }
261        }
262
263        // UNDO LAST CHUNK ENCODING
264
265        self.work.undo_last_chunk_encoding();
266        if compute_recovery {
267            self.work.undo_last_chunk_encoding_recovery();
268        }
269
270        // DONE
271
272        Ok(Some(DecoderResult::new(&mut self.work)))
273    }
274
275    fn into_parts(self) -> (E, DecoderWork) {
276        (self.engine, self.work)
277    }
278
279    fn new(
280        original_count: usize,
281        recovery_count: usize,
282        shard_bytes: usize,
283        engine: E,
284        work: Option<DecoderWork>,
285    ) -> Result<Self, Error> {
286        let mut work = work.unwrap_or_default();
287        Self::reset_work(original_count, recovery_count, shard_bytes, &mut work)?;
288        Ok(Self { engine, work })
289    }
290
291    fn reset(
292        &mut self,
293        original_count: usize,
294        recovery_count: usize,
295        shard_bytes: usize,
296    ) -> Result<(), Error> {
297        Self::reset_work(original_count, recovery_count, shard_bytes, &mut self.work)
298    }
299}
300
301// ======================================================================
302// HighRateDecoder - PRIVATE
303
304impl<E: Engine> HighRateDecoder<E> {
305    fn reset_work(
306        original_count: usize,
307        recovery_count: usize,
308        shard_bytes: usize,
309        work: &mut DecoderWork,
310    ) -> Result<(), Error> {
311        Self::validate(original_count, recovery_count, shard_bytes)?;
312
313        // work[..recovery_count     ]  =  recovery
314        // work[recovery_count_pow2..]  =  original
315        work.reset(
316            original_count,
317            recovery_count,
318            shard_bytes,
319            recovery_count.next_power_of_two(),
320            0,
321            Self::work_count(original_count, recovery_count),
322        );
323
324        Ok(())
325    }
326
327    fn work_count(original_count: usize, recovery_count: usize) -> usize {
328        assert!(Self::supports(original_count, recovery_count));
329
330        (recovery_count.next_power_of_two() + original_count).next_power_of_two()
331    }
332}
333
334// ======================================================================
335// TESTS
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340    use crate::reed_solomon::test_util;
341
342    // ============================================================
343    // ROUNDTRIPS - SINGLE ROUND
344
345    #[test]
346    fn roundtrip_all_originals_missing() {
347        roundtrip_single!(
348            HighRate,
349            3,
350            3,
351            1024,
352            test_util::EITHER_3_3,
353            &[],
354            &[test_util::range(0, 3)],
355            133,
356        );
357    }
358
359    #[test]
360    fn roundtrip_no_originals_missing() {
361        roundtrip_single!(
362            HighRate,
363            3,
364            2,
365            1024,
366            test_util::HIGH_3_2,
367            &[test_util::range(0, 3)],
368            &[],
369            132
370        );
371    }
372
373    #[test]
374    fn roundtrips_tiny() {
375        for (original_count, recovery_count, seed, recovery_hash) in test_util::HIGH_TINY {
376            roundtrip_single!(
377                HighRate,
378                *original_count,
379                *recovery_count,
380                1024,
381                recovery_hash,
382                &[test_util::range(*recovery_count, *original_count)],
383                &[test_util::range(
384                    0,
385                    core::cmp::min(*original_count, *recovery_count)
386                )],
387                *seed,
388            );
389        }
390    }
391
392    #[test]
393    #[ignore]
394    fn roundtrip_3000_30000() {
395        roundtrip_single!(
396            HighRate,
397            3000,
398            30000,
399            crate::reed_solomon::SHARD_CHUNK_BYTES,
400            test_util::HIGH_3000_30000_14,
401            &[],
402            &[test_util::range(0, 3000)],
403            14,
404        );
405    }
406
407    #[test]
408    #[ignore]
409    fn roundtrip_32768_32768() {
410        roundtrip_single!(
411            HighRate,
412            32768,
413            32768,
414            crate::reed_solomon::SHARD_CHUNK_BYTES,
415            test_util::EITHER_32768_32768_11,
416            &[],
417            &[test_util::range(0, 32768)],
418            11,
419        );
420    }
421
422    #[test]
423    #[ignore]
424    fn roundtrip_60000_3000() {
425        roundtrip_single!(
426            HighRate,
427            60000,
428            3000,
429            crate::reed_solomon::SHARD_CHUNK_BYTES,
430            test_util::HIGH_60000_3000_12,
431            &[test_util::range(3000, 60000)],
432            &[test_util::range(0, 3000)],
433            12,
434        );
435    }
436
437    #[test]
438    fn roundtrip_34000_2000_shard_size_8() {
439        roundtrip_single!(
440            HighRate,
441            34000,
442            2000,
443            8,
444            test_util::HIGH_34000_2000_123_8,
445            &[test_util::range(0, 32000)],
446            &[test_util::range(0, 2000)],
447            123
448        );
449    }
450
451    // ============================================================
452    // ROUNDTRIPS - TWO ROUNDS
453
454    #[test]
455    fn two_rounds_implicit_reset() {
456        roundtrip_two_rounds!(
457            HighRate,
458            false,
459            (
460                3,
461                2,
462                1024,
463                test_util::HIGH_3_2,
464                &[test_util::index(1)],
465                &[test_util::index(0), test_util::index(1)],
466                132
467            ),
468            (
469                3,
470                2,
471                1024,
472                test_util::HIGH_3_2_232,
473                &[test_util::index(0)],
474                &[test_util::index(0), test_util::index(1)],
475                232
476            ),
477        );
478    }
479
480    #[test]
481    fn two_rounds_explicit_reset() {
482        roundtrip_two_rounds!(
483            HighRate,
484            true,
485            (
486                3,
487                2,
488                1024,
489                test_util::HIGH_3_2,
490                &[test_util::index(1)],
491                &[test_util::index(0), test_util::index(1)],
492                132
493            ),
494            (
495                5,
496                2,
497                1024,
498                test_util::HIGH_5_2,
499                &[
500                    test_util::index(0),
501                    test_util::index(2),
502                    test_util::index(4)
503                ],
504                &[test_util::index(0), test_util::index(1)],
505                152
506            ),
507        );
508    }
509
510    // ============================================================
511    // HighRate
512
513    mod high_rate {
514        use crate::reed_solomon::{
515            Error, SHARD_CHUNK_BYTES,
516            engine::NoSimd,
517            rate::{HighRate, Rate},
518        };
519
520        #[test]
521        fn decoder() {
522            assert_eq!(
523                HighRate::<NoSimd>::decoder(4096, 61440, SHARD_CHUNK_BYTES, NoSimd::new(), None)
524                    .err(),
525                Some(Error::UnsupportedShardCount {
526                    original_count: 4096,
527                    recovery_count: 61440,
528                })
529            );
530
531            assert!(
532                HighRate::<NoSimd>::decoder(61440, 4096, SHARD_CHUNK_BYTES, NoSimd::new(), None)
533                    .is_ok()
534            );
535        }
536
537        #[test]
538        fn encoder() {
539            assert_eq!(
540                HighRate::<NoSimd>::encoder(4096, 61440, SHARD_CHUNK_BYTES, NoSimd::new(), None)
541                    .err(),
542                Some(Error::UnsupportedShardCount {
543                    original_count: 4096,
544                    recovery_count: 61440,
545                })
546            );
547
548            assert!(
549                HighRate::<NoSimd>::encoder(61440, 4096, SHARD_CHUNK_BYTES, NoSimd::new(), None)
550                    .is_ok()
551            );
552        }
553
554        #[test]
555        fn supports() {
556            assert!(!HighRate::<NoSimd>::supports(0, 1));
557            assert!(!HighRate::<NoSimd>::supports(1, 0));
558
559            assert!(!HighRate::<NoSimd>::supports(4096, 61440));
560
561            assert!(HighRate::<NoSimd>::supports(61440, 4096));
562            assert!(!HighRate::<NoSimd>::supports(61440, 4097));
563            assert!(!HighRate::<NoSimd>::supports(61441, 4096));
564
565            assert!(!HighRate::<NoSimd>::supports(usize::MAX, usize::MAX));
566        }
567
568        #[test]
569        fn validate() {
570            assert_eq!(
571                HighRate::<NoSimd>::validate(1, 1, 123).err(),
572                Some(Error::InvalidShardSize { shard_bytes: 123 })
573            );
574
575            assert_eq!(
576                HighRate::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
577                Some(Error::UnsupportedShardCount {
578                    original_count: 4096,
579                    recovery_count: 61440,
580                })
581            );
582
583            assert!(HighRate::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
584        }
585    }
586
587    // ============================================================
588    // HighRateEncoder
589
590    mod high_rate_encoder {
591        use crate::reed_solomon::{
592            Error, SHARD_CHUNK_BYTES,
593            engine::NoSimd,
594            rate::{HighRateEncoder, RateEncoder},
595        };
596
597        // ==================================================
598        // ERRORS
599
600        test_rate_encoder_errors! {HighRateEncoder}
601
602        // ==================================================
603        // supports
604
605        #[test]
606        fn supports() {
607            assert!(!HighRateEncoder::<NoSimd>::supports(4096, 61440));
608            assert!(HighRateEncoder::<NoSimd>::supports(61440, 4096));
609        }
610
611        // ==================================================
612        // validate
613
614        #[test]
615        fn validate() {
616            assert_eq!(
617                HighRateEncoder::<NoSimd>::validate(1, 1, 123).err(),
618                Some(Error::InvalidShardSize { shard_bytes: 123 })
619            );
620
621            assert_eq!(
622                HighRateEncoder::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
623                Some(Error::UnsupportedShardCount {
624                    original_count: 4096,
625                    recovery_count: 61440,
626                })
627            );
628
629            assert!(HighRateEncoder::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
630        }
631
632        // ==================================================
633        // work_count
634
635        #[test]
636        fn work_count() {
637            assert_eq!(HighRateEncoder::<NoSimd>::work_count(1, 1), 1);
638            assert_eq!(HighRateEncoder::<NoSimd>::work_count(4096, 1024), 4096);
639            assert_eq!(HighRateEncoder::<NoSimd>::work_count(4097, 1024), 5120);
640            assert_eq!(HighRateEncoder::<NoSimd>::work_count(4097, 1025), 6144);
641            assert_eq!(HighRateEncoder::<NoSimd>::work_count(32768, 32768), 32768);
642        }
643    }
644
645    // ============================================================
646    // HighRateDecoder
647
648    mod high_rate_decoder {
649        use crate::reed_solomon::{
650            Error, SHARD_CHUNK_BYTES,
651            engine::NoSimd,
652            rate::{HighRateDecoder, RateDecoder},
653        };
654
655        // ==================================================
656        // ERRORS
657
658        test_rate_decoder_errors! {HighRateDecoder}
659
660        // ==================================================
661        // supports
662
663        #[test]
664        fn supports() {
665            assert!(!HighRateDecoder::<NoSimd>::supports(4096, 61440));
666            assert!(HighRateDecoder::<NoSimd>::supports(61440, 4096));
667        }
668
669        // ==================================================
670        // validate
671
672        #[test]
673        fn validate() {
674            assert_eq!(
675                HighRateDecoder::<NoSimd>::validate(1, 1, 123).err(),
676                Some(Error::InvalidShardSize { shard_bytes: 123 })
677            );
678
679            assert_eq!(
680                HighRateDecoder::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
681                Some(Error::UnsupportedShardCount {
682                    original_count: 4096,
683                    recovery_count: 61440,
684                })
685            );
686
687            assert!(HighRateDecoder::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
688        }
689
690        // ==================================================
691        // work_count
692
693        #[test]
694        fn work_count() {
695            assert_eq!(HighRateDecoder::<NoSimd>::work_count(1, 1), 2);
696            assert_eq!(HighRateDecoder::<NoSimd>::work_count(2048, 1025), 4096);
697            assert_eq!(HighRateDecoder::<NoSimd>::work_count(2049, 1025), 8192);
698            assert_eq!(HighRateDecoder::<NoSimd>::work_count(3072, 1024), 4096);
699            assert_eq!(HighRateDecoder::<NoSimd>::work_count(3073, 1024), 8192);
700            assert_eq!(HighRateDecoder::<NoSimd>::work_count(32768, 32768), 65536);
701        }
702    }
703}