Skip to main content

commonware_cryptography/reed_solomon/rate/
rate_high.rs

1use crate::reed_solomon::{
2    engine::{self, Engine, GF_MODULUS, GF_ORDER, SHARD_CHUNK_BYTES},
3    rate::{DecoderWork, EncoderWork, Rate, RateDecoder, RateEncoder},
4    DecoderResult, EncoderResult, Error,
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            engine::NoSimd,
516            rate::{HighRate, Rate},
517            Error, SHARD_CHUNK_BYTES,
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!(HighRate::<NoSimd>::decoder(
532                61440,
533                4096,
534                SHARD_CHUNK_BYTES,
535                NoSimd::new(),
536                None
537            )
538            .is_ok());
539        }
540
541        #[test]
542        fn encoder() {
543            assert_eq!(
544                HighRate::<NoSimd>::encoder(4096, 61440, SHARD_CHUNK_BYTES, NoSimd::new(), None)
545                    .err(),
546                Some(Error::UnsupportedShardCount {
547                    original_count: 4096,
548                    recovery_count: 61440,
549                })
550            );
551
552            assert!(HighRate::<NoSimd>::encoder(
553                61440,
554                4096,
555                SHARD_CHUNK_BYTES,
556                NoSimd::new(),
557                None
558            )
559            .is_ok());
560        }
561
562        #[test]
563        fn supports() {
564            assert!(!HighRate::<NoSimd>::supports(0, 1));
565            assert!(!HighRate::<NoSimd>::supports(1, 0));
566
567            assert!(!HighRate::<NoSimd>::supports(4096, 61440));
568
569            assert!(HighRate::<NoSimd>::supports(61440, 4096));
570            assert!(!HighRate::<NoSimd>::supports(61440, 4097));
571            assert!(!HighRate::<NoSimd>::supports(61441, 4096));
572
573            assert!(!HighRate::<NoSimd>::supports(usize::MAX, usize::MAX));
574        }
575
576        #[test]
577        fn validate() {
578            assert_eq!(
579                HighRate::<NoSimd>::validate(1, 1, 123).err(),
580                Some(Error::InvalidShardSize { shard_bytes: 123 })
581            );
582
583            assert_eq!(
584                HighRate::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
585                Some(Error::UnsupportedShardCount {
586                    original_count: 4096,
587                    recovery_count: 61440,
588                })
589            );
590
591            assert!(HighRate::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
592        }
593    }
594
595    // ============================================================
596    // HighRateEncoder
597
598    mod high_rate_encoder {
599        use crate::reed_solomon::{
600            engine::NoSimd,
601            rate::{HighRateEncoder, RateEncoder},
602            Error, SHARD_CHUNK_BYTES,
603        };
604
605        // ==================================================
606        // ERRORS
607
608        test_rate_encoder_errors! {HighRateEncoder}
609
610        // ==================================================
611        // supports
612
613        #[test]
614        fn supports() {
615            assert!(!HighRateEncoder::<NoSimd>::supports(4096, 61440));
616            assert!(HighRateEncoder::<NoSimd>::supports(61440, 4096));
617        }
618
619        // ==================================================
620        // validate
621
622        #[test]
623        fn validate() {
624            assert_eq!(
625                HighRateEncoder::<NoSimd>::validate(1, 1, 123).err(),
626                Some(Error::InvalidShardSize { shard_bytes: 123 })
627            );
628
629            assert_eq!(
630                HighRateEncoder::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
631                Some(Error::UnsupportedShardCount {
632                    original_count: 4096,
633                    recovery_count: 61440,
634                })
635            );
636
637            assert!(HighRateEncoder::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
638        }
639
640        // ==================================================
641        // work_count
642
643        #[test]
644        fn work_count() {
645            assert_eq!(HighRateEncoder::<NoSimd>::work_count(1, 1), 1);
646            assert_eq!(HighRateEncoder::<NoSimd>::work_count(4096, 1024), 4096);
647            assert_eq!(HighRateEncoder::<NoSimd>::work_count(4097, 1024), 5120);
648            assert_eq!(HighRateEncoder::<NoSimd>::work_count(4097, 1025), 6144);
649            assert_eq!(HighRateEncoder::<NoSimd>::work_count(32768, 32768), 32768);
650        }
651    }
652
653    // ============================================================
654    // HighRateDecoder
655
656    mod high_rate_decoder {
657        use crate::reed_solomon::{
658            engine::NoSimd,
659            rate::{HighRateDecoder, RateDecoder},
660            Error, SHARD_CHUNK_BYTES,
661        };
662
663        // ==================================================
664        // ERRORS
665
666        test_rate_decoder_errors! {HighRateDecoder}
667
668        // ==================================================
669        // supports
670
671        #[test]
672        fn supports() {
673            assert!(!HighRateDecoder::<NoSimd>::supports(4096, 61440));
674            assert!(HighRateDecoder::<NoSimd>::supports(61440, 4096));
675        }
676
677        // ==================================================
678        // validate
679
680        #[test]
681        fn validate() {
682            assert_eq!(
683                HighRateDecoder::<NoSimd>::validate(1, 1, 123).err(),
684                Some(Error::InvalidShardSize { shard_bytes: 123 })
685            );
686
687            assert_eq!(
688                HighRateDecoder::<NoSimd>::validate(4096, 61440, SHARD_CHUNK_BYTES).err(),
689                Some(Error::UnsupportedShardCount {
690                    original_count: 4096,
691                    recovery_count: 61440,
692                })
693            );
694
695            assert!(HighRateDecoder::<NoSimd>::validate(61440, 4096, SHARD_CHUNK_BYTES).is_ok());
696        }
697
698        // ==================================================
699        // work_count
700
701        #[test]
702        fn work_count() {
703            assert_eq!(HighRateDecoder::<NoSimd>::work_count(1, 1), 2);
704            assert_eq!(HighRateDecoder::<NoSimd>::work_count(2048, 1025), 4096);
705            assert_eq!(HighRateDecoder::<NoSimd>::work_count(2049, 1025), 8192);
706            assert_eq!(HighRateDecoder::<NoSimd>::work_count(3072, 1024), 4096);
707            assert_eq!(HighRateDecoder::<NoSimd>::work_count(3073, 1024), 8192);
708            assert_eq!(HighRateDecoder::<NoSimd>::work_count(32768, 32768), 65536);
709        }
710    }
711}