subspace_erasure_coding/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod tests;
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::string::String;
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::num::NonZeroUsize;
use kzg::{FFTSettings, PolyRecover, DAS, FFTG1, G1};
use rust_kzg_blst::types::fft_settings::FsFFTSettings;
use rust_kzg_blst::types::g1::FsG1;
use rust_kzg_blst::types::poly::FsPoly;
use subspace_kzg::{Commitment, Polynomial, Scalar};
/// Erasure coding abstraction.
///
/// Supports creation of parity records and recovery of missing data.
#[derive(Debug, Clone)]
pub struct ErasureCoding {
fft_settings: Arc<FsFFTSettings>,
}
impl ErasureCoding {
/// Create new erasure coding instance.
///
/// Number of shards supported is `2^scale`, half of shards are source data and the other half
/// are parity.
pub fn new(scale: NonZeroUsize) -> Result<Self, String> {
let fft_settings = Arc::new(FsFFTSettings::new(scale.get())?);
Ok(Self { fft_settings })
}
/// Max number of shards supported (both source and parity together)
pub fn max_shards(&self) -> usize {
self.fft_settings.max_width
}
/// Extend sources using erasure coding.
///
/// Returns parity data.
pub fn extend(&self, source: &[Scalar]) -> Result<Vec<Scalar>, String> {
// TODO: das_fft_extension modifies buffer internally, it needs to change to use
// pre-allocated buffer instead of allocating a new one
self.fft_settings
.das_fft_extension(Scalar::slice_to_repr(source))
.map(Scalar::vec_from_repr)
}
/// Recovery of missing shards from given shards (at least 1/2 should be `Some`).
///
/// Both in input and output source shards are interleaved with parity shards:
/// source, parity, source, parity, ...
pub fn recover(&self, shards: &[Option<Scalar>]) -> Result<Vec<Scalar>, String> {
let poly = FsPoly::recover_poly_from_samples(
Scalar::slice_option_to_repr(shards),
&self.fft_settings,
)?;
Ok(Scalar::vec_from_repr(poly.coeffs))
}
/// Recovery of missing shards from given shards (at least 1/2 should be `Some`) in form of
/// normalized polynomial (allows to not do inverse FFT afterwards if polynomial is desired).
///
/// Both in input and output source shards are interleaved with parity shards:
/// source, parity, source, parity, ...
pub fn recover_poly(&self, shards: &[Option<Scalar>]) -> Result<Polynomial, String> {
let mut poly = Polynomial::from(FsPoly::recover_poly_coeffs_from_samples(
Scalar::slice_option_to_repr(shards),
&self.fft_settings,
)?);
poly.normalize();
Ok(poly)
}
/// Recovery of source shards from given shards (at least 1/2 should be `Some`).
///
/// The same as [`ErasureCoding::recover()`], but returns only source shards in form of an
/// iterator.
pub fn recover_source(
&self,
shards: &[Option<Scalar>],
) -> Result<impl ExactSizeIterator<Item = Scalar>, String> {
Ok(self.recover(shards)?.into_iter().step_by(2))
}
/// Extend commitments using erasure coding.
///
/// Returns both source and parity commitments interleaved.
pub fn extend_commitments(
&self,
commitments: &[Commitment],
) -> Result<Vec<Commitment>, String> {
// Inverse FFT to interpolate polynomial over source commitments
let mut coeffs = self
.fft_settings
.fft_g1(Commitment::slice_to_repr(commitments), true)?;
// Double the size
coeffs.resize(coeffs.len() * 2, FsG1::identity());
// FFT to get extended commitments
self.fft_settings
.fft_g1(&coeffs, false)
.map(Commitment::vec_from_repr)
}
}