reed_solomon_novelpoly/novel_poly_basis/
mod.rs

1// Encoding/erasure decoding for Reed-Solomon codes over binary extension fields
2//
3// Derived impl of `RSAErasureCode.c`.
4//
5// Lin, Han and Chung, "Novel Polynomial Basis and Its Application to Reed-Solomon Erasure Codes," FOCS14.
6// (http://arxiv.org/abs/1404.3458)
7
8use crate::errors::*;
9use crate::f2e16::*;
10use crate::Shard;
11
12mod encode;
13mod reconstruct;
14
15pub use self::encode::*;
16pub use self::reconstruct::*;
17pub use super::util::*;
18
19use super::field::f2e16;
20
21/// Params for the encoder / decoder
22/// derived from a target validator count.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct CodeParams {
25	/// total number of message symbols to send
26	/// Invariant is a power of base 2
27	n: usize,
28	/// number of information containing chunks
29	/// Invariant is a power of base 2, `k < n`
30	k: usize,
31	/// Avoid copying unnecessary chunks.
32	wanted_n: usize,
33}
34
35impl CodeParams {
36	/// Create a new reed solomon erasure encoding wrapper
37	/// `k` the intended number of data shards needed to recover.
38	/// `n` the intended number of resulting shards.
39	///
40	/// Assures that the derived paramters retain at most the given coding
41	/// rate, and as such assure recoverability with at least an equiv fraction
42	/// as provided by the input `n`, and `k` parameterset.
43	pub fn derive_parameters(n: usize, k: usize) -> Result<Self> {
44		if n < 2 {
45			return Err(Error::WantedShardCountTooLow(n));
46		}
47		if k < 1 {
48			return Err(Error::WantedPayloadShardCountTooLow(k));
49		}
50		let k_po2 = next_lower_power_of_2(k);
51		let n_po2 = next_higher_power_of_2(n);
52		// If the coding rate of the power of 2 variants, is higher,
53		// we would have to lower k by one order of magnitude base 2
54		// which is true by definition
55		assert!(n * k_po2 <= n_po2 * k);
56
57		if n_po2 > FIELD_SIZE {
58			return Err(Error::WantedShardCountTooHigh(n));
59		}
60		Ok(Self { n: n_po2, k: k_po2, wanted_n: n })
61	}
62
63	/// Check if this could use the `faster8` code path, possibly utilizing `avx` SIMD instructions
64	pub fn is_faster8(&self) -> bool {
65		#[cfg(all(target_feature = "avx", feature = "avx"))]
66		{
67			self.k >= (Additive8x::LANE << 1) && self.n % Additive8x::LANE == 0
68		}
69		#[cfg(not(all(target_feature = "avx", feature = "avx")))]
70		false
71	}
72
73	// make a reed-solomon instance.
74	pub fn make_encoder(&self) -> ReedSolomon {
75		ReedSolomon::new(self.n, self.k, self.wanted_n)
76			.expect("this struct is not created with invalid shard number; qed")
77	}
78
79	/// Return the computed `n` value.
80	pub fn n(&self) -> usize {
81		self.n
82	}
83
84	/// Return the computed `k` value.
85	pub fn k(&self) -> usize {
86		self.k
87	}
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct ReedSolomon {
92	/// The true number of total shards to be had, derived from `n_wanted`.
93	n: usize,
94	/// The amount of original data shards, that are part of the systematic code.
95	k: usize,
96	/// The size as desired by the user. Strictly smaller than `n`.
97	wanted_n: usize,
98}
99
100impl ReedSolomon {
101	/// Returns the size per shard in bytes
102	pub fn shard_len(&self, payload_size: usize) -> usize {
103		let payload_symbols = (payload_size + 1) / 2;
104		let shard_symbols_ceil = (payload_symbols + self.k - 1) / self.k;
105
106		shard_symbols_ceil * 2
107	}
108
109	pub(crate) fn new(n: usize, k: usize, wanted_n: usize) -> Result<Self> {
110		if !is_power_of_2(n) && !is_power_of_2(k) {
111			Err(Error::ParamterMustBePowerOf2 { n, k })
112		} else {
113			Ok(Self { wanted_n, n, k })
114		}
115	}
116
117	pub fn encode<S: Shard>(&self, bytes: &[u8]) -> Result<Vec<S>> {
118		if bytes.is_empty() {
119			return Err(Error::PayloadSizeIsZero);
120		}
121
122		// setup the shards, n is likely _larger_, so use the truely required number of shards
123
124		// required shard length in bytes, rounded to full symbols
125		let shard_len = self.shard_len(bytes.len());
126		assert!(shard_len > 0);
127		// collect all sub encoding runs
128
129		let validator_count = self.wanted_n;
130		let k2 = self.k * 2;
131		// prepare one wrapped shard per validator
132		let mut shards = vec![
133			<S as From<Vec<u8>>>::from(
134				#[allow(clippy::uninit_vec)]
135				{
136					let mut v = Vec::<u8>::with_capacity(shard_len);
137					unsafe { v.set_len(shard_len) }
138					v
139				}
140			);
141			validator_count
142		];
143
144		for (chunk_idx, i) in (0..bytes.len()).step_by(k2).enumerate() {
145			let end = std::cmp::min(i + k2, bytes.len());
146			assert_ne!(i, end);
147			let data_piece = &bytes[i..end];
148			assert!(!data_piece.is_empty());
149			assert!(data_piece.len() <= k2);
150			let encoding_run = f2e16::encode_sub(data_piece, self.n, self.k)?;
151			for val_idx in 0..validator_count {
152				AsMut::<[[u8; 2]]>::as_mut(&mut shards[val_idx])[chunk_idx] = encoding_run[val_idx].0.to_be_bytes();
153			}
154		}
155
156		Ok(shards)
157	}
158
159	/// Reconstruct from chunks.
160	///
161	/// The result may be padded with zeros. Truncate the output to the expected byte length.
162	pub fn reconstruct<S: Shard>(&self, received_shards: Vec<Option<S>>) -> Result<Vec<u8>> {
163		let gap = self.n.saturating_sub(received_shards.len());
164
165		let received_shards =
166			received_shards.into_iter().take(self.n).chain(std::iter::repeat(None).take(gap)).collect::<Vec<_>>();
167
168		assert_eq!(received_shards.len(), self.n);
169
170		// must be collected after expanding `received_shards` to the anticipated size
171		let mut existential_count = 0_usize;
172		let erasures = received_shards
173			.iter()
174			.map(|x| x.is_none())
175			.inspect(|erased| existential_count += !*erased as usize)
176			.collect::<Vec<bool>>();
177
178		if existential_count < self.k {
179			return Err(Error::NeedMoreShards { have: existential_count, min: self.k, all: self.n });
180		}
181
182		// obtain a sample of a shard length and assume that is the truth
183		let shard_len_in_syms = {
184			let (first_shard_idx, first_shard_len) = received_shards
185				.iter()
186				.enumerate()
187				.find_map(|(idx, shard)| {
188					shard.as_ref().map(|shard| {
189						let shard = AsRef::<[[u8; 2]]>::as_ref(shard);
190						(idx, shard.len())
191					})
192				})
193				.expect("Existential shard count is at least k shards. qed");
194
195			if first_shard_len == 0 {
196				return Err(Error::EmptyShard);
197			}
198
199			// make sure all shards have the same length as the first one
200			if let Some(other_shard_len) = received_shards[(first_shard_idx + 1)..].iter().find_map(|shard| {
201				shard.as_ref().and_then(|shard| {
202					let shard = AsRef::<[[u8; 2]]>::as_ref(shard);
203					if first_shard_len != shard.len() {
204						Some(shard.len())
205					} else {
206						None
207					}
208				})
209			}) {
210				return Err(Error::InconsistentShardLengths { first: first_shard_len, other: other_shard_len });
211			}
212
213			first_shard_len
214		};
215
216		// Evaluate error locator polynomial only once
217		let mut error_poly_in_log = [Multiplier(0); FIELD_SIZE];
218		f2e16::eval_error_polynomial(&erasures[..], &mut error_poly_in_log[..], FIELD_SIZE);
219
220		let mut acc = Vec::<u8>::with_capacity(shard_len_in_syms * 2 * self.k);
221		for i in 0..shard_len_in_syms {
222			// take the i-th element of all shards and try to recover
223			let decoding_run = Vec::<Option<Additive>>::from_iter(received_shards.iter().map(|x| {
224				x.as_ref().map(|x| {
225					let z = AsRef::<[[u8; 2]]>::as_ref(&x)[i];
226					Additive(u16::from_be_bytes(z))
227				})
228			}));
229
230			assert_eq!(decoding_run.len(), self.n);
231
232			// reconstruct from one set of symbols which was spread over all erasure chunks
233			let piece =
234				f2e16::reconstruct_sub(&decoding_run[..], &erasures, self.n, self.k, &error_poly_in_log).unwrap();
235			acc.extend_from_slice(&piece[..]);
236		}
237
238		Ok(acc)
239	}
240
241	/// Reconstruct from the set of systematic chunks.
242	/// Systematic chunks are the first `k` chunks, which contain the initial data.
243	///
244	/// Provide a vector containing chunk data. If too few chunks are provided, recovery is not
245	/// possible.
246	/// The result may be padded with zeros. Truncate the output to the expected byte length.
247	pub fn reconstruct_from_systematic<S: Shard>(&self, chunks: Vec<S>) -> Result<Vec<u8>> {
248		let Some(first_shard) = chunks.first() else {
249			return Err(Error::NeedMoreShards { have: 0, min: self.k, all: self.n });
250		};
251		if chunks.len() < self.k {
252			return Err(Error::NeedMoreShards { have: chunks.len(), min: self.k, all: self.n });
253		}
254
255		let shard_len = AsRef::<[[u8; 2]]>::as_ref(first_shard).len();
256
257		if shard_len == 0 {
258			return Err(Error::EmptyShard);
259		}
260
261		if let Some(length) = chunks.iter().find_map(|c| {
262			let length = AsRef::<[[u8; 2]]>::as_ref(c).len();
263			if length != shard_len {
264				Some(length)
265			} else {
266				None
267			}
268		}) {
269			return Err(Error::InconsistentShardLengths { first: shard_len, other: length });
270		}
271
272		let mut systematic_bytes = Vec::with_capacity(shard_len * 2 * self.k);
273
274		for i in 0..shard_len {
275			for chunk in chunks.iter().take(self.k) {
276				// No need to check for index out of bounds because i goes up to shard_len and
277				// we return an error for non uniform chunks.
278				let chunk = AsRef::<[[u8; 2]]>::as_ref(chunk)[i];
279				systematic_bytes.push(chunk[0]);
280				systematic_bytes.push(chunk[1]);
281			}
282		}
283
284		Ok(systematic_bytes)
285	}
286}
287
288#[cfg(test)]
289mod tests;