Skip to content

Instantly share code, notes, and snippets.

@itzmeanjan
Last active February 25, 2022 12:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itzmeanjan/4acf9338d9233e79cfbee5d311e7a0b4 to your computer and use it in GitHub Desktop.
Save itzmeanjan/4acf9338d9233e79cfbee5d311e7a0b4 to your computer and use it in GitHub Desktop.
Reed Solomon Erasure Coded Data Recovery with (I)FFT
use dusk_plonk::fft::EvaluationDomain;
use dusk_plonk::prelude::BlsScalar;
mod utils {
use dusk_plonk::fft::EvaluationDomain;
use dusk_plonk::prelude::BlsScalar;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use std::time::{SystemTime, UNIX_EPOCH};
fn expand_root_of_unity(eval_domain: EvaluationDomain) -> Vec<BlsScalar> {
let root_of_unity = eval_domain.group_gen;
let mut roots: Vec<BlsScalar> = Vec::new();
roots.push(BlsScalar::one());
roots.push(root_of_unity);
let mut i = 1;
while roots[i] != BlsScalar::one() {
roots.push(roots[i] * root_of_unity);
i += 1;
}
return roots;
}
pub fn zero_poly_fn(
eval_domain: EvaluationDomain,
missing_indices: &[u64],
length: u64,
) -> (Vec<BlsScalar>, Vec<BlsScalar>) {
let expanded_r_o_u = expand_root_of_unity(eval_domain);
let domain_stride = eval_domain.size() as u64 / length;
let mut zero_poly: Vec<BlsScalar> = Vec::with_capacity(length as usize);
let mut sub: BlsScalar;
for i in 0..missing_indices.len() {
let v = missing_indices[i as usize];
sub = BlsScalar::zero() - expanded_r_o_u[(v * domain_stride) as usize];
zero_poly.push(sub);
if i > 0 {
zero_poly[i] = zero_poly[i] + zero_poly[i - 1];
for j in (1..i).rev() {
zero_poly[j] = zero_poly[j] * sub;
zero_poly[j] = zero_poly[j] + zero_poly[j - 1];
}
zero_poly[0] = zero_poly[0] * sub
}
}
zero_poly.push(BlsScalar::one());
for _ in zero_poly.len()..zero_poly.capacity() {
zero_poly.push(BlsScalar::zero());
}
let zero_eval = eval_domain.fft(&zero_poly[..]);
(zero_poly, zero_eval)
}
// in-place shifting
pub fn shift_poly(poly: &mut [BlsScalar]) {
// primitive root of unity
let shift_factor = BlsScalar::from(5);
let mut factor_power = BlsScalar::one();
// hoping it won't panic, though it should be handled properly
//
// this is actually 1/ shift_factor --- multiplicative inverse
let inv_factor = shift_factor.invert().unwrap();
for i in 0..poly.len() {
poly[i] *= factor_power;
factor_power *= inv_factor;
}
}
// in-place unshifting
pub fn unshift_poly(poly: &mut [BlsScalar]) {
// primitive root of unity
let shift_factor = BlsScalar::from(5);
let mut factor_power = BlsScalar::one();
for i in 0..poly.len() {
poly[i] *= factor_power;
factor_power *= shift_factor;
}
}
// select a random subset of coded data to be used for
// reconstruction purpose
pub fn random_subset(data: &[BlsScalar]) -> (Vec<Option<BlsScalar>>, u64) {
let mut rng = StdRng::seed_from_u64(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
);
let mut subset: Vec<Option<BlsScalar>> = Vec::with_capacity(data.len());
let mut available = 0;
for i in 0..data.len() {
if rng.gen::<u8>() % 2 == 0 {
subset.push(Some(data[i]));
available += 1;
} else {
subset.push(None);
}
}
// already we've >=50% data available
// so just return & attempt to reconstruct back
if available >= data.len() / 2 {
(subset, available as u64)
} else {
for i in 0..data.len() {
if let None = subset[i] {
// enough data added, >=50% needs
// to be present
if available >= data.len() / 2 {
break;
}
subset[i] = Some(data[i]);
available += 1;
}
}
(subset, available as u64)
}
}
}
mod console {
use dusk_plonk::prelude::BlsScalar;
pub fn display(msg: &str, vals: &[BlsScalar]) {
println!("----- {} -----", msg);
for i in 0..vals.len() {
println!("{}_{}: {:?}", msg, i, vals[i]);
}
println!("")
}
pub fn optional_display(msg: &str, vals: &[Option<BlsScalar>]) {
println!("----- {} -----", msg);
for i in 0..vals.len() {
println!("{}_{}: {:?}", msg, i, vals[i]);
}
println!("")
}
}
mod recover {
use crate::utils::{shift_poly, unshift_poly, zero_poly_fn};
use dusk_plonk::fft::EvaluationDomain;
use dusk_plonk::prelude::BlsScalar;
pub fn reconstruct_poly(
// domain I'm working with
// all (i)ffts to be performed on it
eval_domain: EvaluationDomain,
// subset of avialable data
subset: Vec<Option<BlsScalar>>,
) -> Vec<BlsScalar> {
let mut missing_indices = Vec::new();
for i in 0..subset.len() {
if let None = subset[i] {
missing_indices.push(i as u64);
}
}
let (mut zero_poly, zero_eval) =
zero_poly_fn(eval_domain, &missing_indices[..], subset.len() as u64);
for i in 0..subset.len() {
if let None = subset[i] {
assert_eq!(
zero_eval[i],
BlsScalar::zero(),
"bad zero poly evaluation !"
);
}
}
let mut poly_evals_with_zero: Vec<BlsScalar> = Vec::new();
for i in 0..subset.len() {
if let Some(v) = subset[i] {
poly_evals_with_zero.push(v * zero_eval[i]);
} else {
poly_evals_with_zero.push(BlsScalar::zero());
}
}
let mut poly_with_zero = eval_domain.ifft(&poly_evals_with_zero[..]);
shift_poly(&mut poly_with_zero[..]);
shift_poly(&mut zero_poly[..]);
let mut eval_shifted_poly_with_zero = eval_domain.fft(&poly_with_zero[..]);
let eval_shifted_zero_poly = eval_domain.fft(&zero_poly[..]);
for i in 0..eval_shifted_poly_with_zero.len() {
eval_shifted_poly_with_zero[i] *= eval_shifted_zero_poly[i].invert().unwrap();
}
let mut shifted_reconstructed_poly = eval_domain.ifft(&eval_shifted_poly_with_zero[..]);
unshift_poly(&mut shifted_reconstructed_poly[..]);
let reconstructed_data = eval_domain.fft(&shifted_reconstructed_poly[..]);
for i in 0..subset.len() {
if let Some(v) = subset[i] {
assert_eq!(
v, reconstructed_data[i],
"failed to reconstruct correctly !"
)
}
}
reconstructed_data
}
}
fn main() {
// you may consider changing 1 << N to 1 << (N+1) or 1 << (N+2) etc.
//
// make sure vector size is power of two
let vector_size = 1 << 5;
let eval_domain = EvaluationDomain::new(vector_size * 2).unwrap();
let mut poly: Vec<BlsScalar> = Vec::new();
for i in 0..vector_size {
poly.push(BlsScalar::from(1 << i));
}
for _ in vector_size..(vector_size * 2) {
poly.push(BlsScalar::zero());
}
console::display("polynomial", &poly[..]);
// obtaining data from polynomial --- original data I've
let data = eval_domain.fft(&poly[..]);
console::display("original_data", &data[..]);
// random subset of data I've at my disposal to be used for reconstruction
//
// `available` = how many cells of vector not missing
let (subset, available) = utils::random_subset(&data[..]);
console::optional_display("available_data", &subset[..]);
// recovering back original data
let reconstructed_data = recover::reconstruct_poly(eval_domain, subset);
console::display("recovered_data", &reconstructed_data[..]);
for i in 0..data.len() {
assert_eq!(
data[i], reconstructed_data[i],
"reconstructed data doesn't match !"
);
}
// recovering back original polynomial coefficients
let back = eval_domain.ifft(&reconstructed_data[..]);
console::display("recovered_coefficients", &back[..]);
println!(
"Reconstructed back when {} % data was missing",
((data.len() as u64 - available) as f64) * 100f64 / (data.len() as f64)
);
}
@itzmeanjan
Copy link
Author

itzmeanjan commented Aug 5, 2021

Background

This code snippet is inspired from Vitalik's post & @protolambda 's Go implementation of same for performing data recovery from Reed Solomon Erasure Coded byte slice, when <=50% data is missing.

I make use of dusk_plonk for performing performing Polynomial arithmetic. I plan to use this in Polygon Avail for reconstructing whole data back from erasure coded chunks.

Use

  • Add followings dependency in cargo.toml
[dependencies]
dusk-plonk = { git = "https://github.com/dusk-network/plonk" }
  • Run with
cargo run --release --bin recovery
  • In console you'll see
----- polynomial -----
polynomial_0: 0x0000000000000000000000000000000000000000000000000000000000000001
polynomial_1: 0x0000000000000000000000000000000000000000000000000000000000000002
polynomial_2: 0x0000000000000000000000000000000000000000000000000000000000000004
polynomial_3: 0x0000000000000000000000000000000000000000000000000000000000000008
polynomial_4: 0x0000000000000000000000000000000000000000000000000000000000000010
polynomial_5: 0x0000000000000000000000000000000000000000000000000000000000000020
polynomial_6: 0x0000000000000000000000000000000000000000000000000000000000000040
polynomial_7: 0x0000000000000000000000000000000000000000000000000000000000000080
polynomial_8: 0x0000000000000000000000000000000000000000000000000000000000000000
polynomial_9: 0x0000000000000000000000000000000000000000000000000000000000000000
polynomial_10: 0x0000000000000000000000000000000000000000000000000000000000000000
polynomial_11: 0x0000000000000000000000000000000000000000000000000000000000000000
polynomial_12: 0x0000000000000000000000000000000000000000000000000000000000000000
polynomial_13: 0x0000000000000000000000000000000000000000000000000000000000000000
polynomial_14: 0x0000000000000000000000000000000000000000000000000000000000000000
polynomial_15: 0x0000000000000000000000000000000000000000000000000000000000000000

----- original_data -----
original_data_0: 0x00000000000000000000000000000000000000000000000000000000000000ff
original_data_1: 0x4b2f38eca788ed33b797386d5b5e35da69630f3284211851142758bc42a941ea
original_data_2: 0x4316a27b8612f41bbd7f9ab3765f3591fd8a3970d01f586384b707086882fa37
original_data_3: 0x4319db4fc8dc88290a22164f8955e3d1ebf3e5fd954f4fae26e271cc7bec7fe0
original_data_4: 0x73eda753299d7d0fe4a23dc5046decc74a8ba307facc5bfeff99fffeffffffce
original_data_5: 0x0a95687e9c91e50f3ff6f0f2a57a81b4d4db6ee59b6806f80841ba709fa46a6d
original_data_6: 0x62ac12bd012938759bc63fff49d6ca0c1645b7b2ea6a6c3336757c628b8ad999
original_data_7: 0x3a44ed9b012696ec3c6bf40dc55cc5129a6e322bb4983fa6ebc388a3d9569a5e
original_data_8: 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffffac
original_data_9: 0x131d6626020eadb2e394cef147f1bda013d6ba32cd2bebfa993fd1cfc5a0d0d0
original_data_10: 0x30d704d7a38a88ea37623c8d41d86084b4cb696ade77039b7ad0f8f6977d05ac
original_data_11: 0x3e5f484dadb10b55897f1cc64792fd61d3ea53fdda0dd787ef3c1c46b2fdbe58
original_data_12: 0x00000000000000384e979a430533eb3e093200fb053200000065ffffffffffcd
original_data_13: 0x0b0b9fc1e373fd75ac8a13544198971102686c55940950bb4a971b02581182de
original_data_14: 0x1141949628744514d5cb98d011354fe7dedfed7766fbefcbca02839c7475264a
original_data_15: 0x2c1d3d6ddb86d00241f3554efc3cd5894c6edb415b475120fddde946f7bf2770

----- available_data -----
available_data_0: Some(0x00000000000000000000000000000000000000000000000000000000000000ff)
available_data_1: Some(0x4b2f38eca788ed33b797386d5b5e35da69630f3284211851142758bc42a941ea)
available_data_2: Some(0x4316a27b8612f41bbd7f9ab3765f3591fd8a3970d01f586384b707086882fa37)
available_data_3: Some(0x4319db4fc8dc88290a22164f8955e3d1ebf3e5fd954f4fae26e271cc7bec7fe0)
available_data_4: None
available_data_5: Some(0x0a95687e9c91e50f3ff6f0f2a57a81b4d4db6ee59b6806f80841ba709fa46a6d)
available_data_6: None
available_data_7: None
available_data_8: Some(0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffffac)
available_data_9: Some(0x131d6626020eadb2e394cef147f1bda013d6ba32cd2bebfa993fd1cfc5a0d0d0)
available_data_10: None
available_data_11: None
available_data_12: None
available_data_13: None
available_data_14: Some(0x1141949628744514d5cb98d011354fe7dedfed7766fbefcbca02839c7475264a)
available_data_15: None

----- recovered_data -----
recovered_data_0: 0x00000000000000000000000000000000000000000000000000000000000000ff
recovered_data_1: 0x4b2f38eca788ed33b797386d5b5e35da69630f3284211851142758bc42a941ea
recovered_data_2: 0x4316a27b8612f41bbd7f9ab3765f3591fd8a3970d01f586384b707086882fa37
recovered_data_3: 0x4319db4fc8dc88290a22164f8955e3d1ebf3e5fd954f4fae26e271cc7bec7fe0
recovered_data_4: 0x73eda753299d7d0fe4a23dc5046decc74a8ba307facc5bfeff99fffeffffffce
recovered_data_5: 0x0a95687e9c91e50f3ff6f0f2a57a81b4d4db6ee59b6806f80841ba709fa46a6d
recovered_data_6: 0x62ac12bd012938759bc63fff49d6ca0c1645b7b2ea6a6c3336757c628b8ad999
recovered_data_7: 0x3a44ed9b012696ec3c6bf40dc55cc5129a6e322bb4983fa6ebc388a3d9569a5e
recovered_data_8: 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfefffffffeffffffac
recovered_data_9: 0x131d6626020eadb2e394cef147f1bda013d6ba32cd2bebfa993fd1cfc5a0d0d0
recovered_data_10: 0x30d704d7a38a88ea37623c8d41d86084b4cb696ade77039b7ad0f8f6977d05ac
recovered_data_11: 0x3e5f484dadb10b55897f1cc64792fd61d3ea53fdda0dd787ef3c1c46b2fdbe58
recovered_data_12: 0x00000000000000384e979a430533eb3e093200fb053200000065ffffffffffcd
recovered_data_13: 0x0b0b9fc1e373fd75ac8a13544198971102686c55940950bb4a971b02581182de
recovered_data_14: 0x1141949628744514d5cb98d011354fe7dedfed7766fbefcbca02839c7475264a
recovered_data_15: 0x2c1d3d6ddb86d00241f3554efc3cd5894c6edb415b475120fddde946f7bf2770

----- recovered_coefficients -----
recovered_coefficients_0: 0x0000000000000000000000000000000000000000000000000000000000000001
recovered_coefficients_1: 0x0000000000000000000000000000000000000000000000000000000000000002
recovered_coefficients_2: 0x0000000000000000000000000000000000000000000000000000000000000004
recovered_coefficients_3: 0x0000000000000000000000000000000000000000000000000000000000000008
recovered_coefficients_4: 0x0000000000000000000000000000000000000000000000000000000000000010
recovered_coefficients_5: 0x0000000000000000000000000000000000000000000000000000000000000020
recovered_coefficients_6: 0x0000000000000000000000000000000000000000000000000000000000000040
recovered_coefficients_7: 0x0000000000000000000000000000000000000000000000000000000000000080
recovered_coefficients_8: 0x0000000000000000000000000000000000000000000000000000000000000000
recovered_coefficients_9: 0x0000000000000000000000000000000000000000000000000000000000000000
recovered_coefficients_10: 0x0000000000000000000000000000000000000000000000000000000000000000
recovered_coefficients_11: 0x0000000000000000000000000000000000000000000000000000000000000000
recovered_coefficients_12: 0x0000000000000000000000000000000000000000000000000000000000000000
recovered_coefficients_13: 0x0000000000000000000000000000000000000000000000000000000000000000
recovered_coefficients_14: 0x0000000000000000000000000000000000000000000000000000000000000000
recovered_coefficients_15: 0x0000000000000000000000000000000000000000000000000000000000000000

Reconstructed back when 50 % data was missing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment