From 0977677f493a09ea5435e030234e116df667b5a1 Mon Sep 17 00:00:00 2001 From: STEVAN Antoine <antoine.stevan@isae-supaero.fr> Date: Thu, 1 Aug 2024 07:47:16 +0000 Subject: [PATCH] isolate Semi-AVID (dragoon/komodo!160) this is a refactor to prepare the addition of other cryptographic methods. ## changelog - moves Semi-AVID code from `lib.rs` to `semi_avid.rs` --- src/fs.rs | 2 +- src/lib.rs | 442 +---------------------------------------------- src/main.rs | 4 +- src/semi_avid.rs | 441 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 444 insertions(+), 445 deletions(-) create mode 100644 src/semi_avid.rs diff --git a/src/fs.rs b/src/fs.rs index 4d3fa506..3b59d588 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -13,7 +13,7 @@ use anyhow::Result; use rs_merkle::{algorithms::Sha256, Hasher}; use tracing::info; -use crate::Block; +use crate::semi_avid::Block; /// dump any serializable object to the disk /// diff --git a/src/lib.rs b/src/lib.rs index 8ee4fe5c..f84dd84a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,448 +1,8 @@ //! Komodo: Cryptographically-proven Erasure Coding -use ark_ec::CurveGroup; -use ark_ff::PrimeField; -use ark_poly::DenseUVPolynomial; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::ops::Div; -use ark_std::rand::RngCore; - -use tracing::{debug, info}; - pub mod error; pub mod fec; pub mod field; pub mod fs; pub mod linalg; +pub mod semi_avid; pub mod zk; - -use crate::{ - error::KomodoError, - fec::Shard, - zk::{Commitment, Powers}, -}; - -/// representation of a block of proven data. -/// -/// this is a wrapper around a [`fec::Shard`] with some additional cryptographic -/// information that allows to prove the integrity of said shard. -#[derive(Debug, Default, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct Block<F: PrimeField, G: CurveGroup<ScalarField = F>> { - pub shard: fec::Shard<F>, - proof: Vec<Commitment<F, G>>, -} - -impl<F: PrimeField, G: CurveGroup<ScalarField = F>> std::fmt::Display for Block<F, G> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{{")?; - write!(f, "shard: {{")?; - write!(f, "k: {},", self.shard.k)?; - write!(f, "comb: [")?; - for x in &self.shard.linear_combination { - if x.is_zero() { - write!(f, "0,")?; - } else { - write!(f, r#""{}","#, x)?; - } - } - write!(f, "]")?; - write!(f, ",")?; - write!(f, "bytes: [")?; - for x in &self.shard.data { - if x.is_zero() { - write!(f, "0,")?; - } else { - write!(f, r#""{}","#, x)?; - } - } - write!(f, "]")?; - write!(f, ",")?; - write!( - f, - "hash: {:?},", - self.shard - .hash - .iter() - .map(|x| format!("{:x}", x)) - .collect::<Vec<_>>() - .join("") - )?; - write!(f, "size: {},", self.shard.size)?; - write!(f, "}}")?; - write!(f, ",")?; - write!(f, "commits: [")?; - for commit in &self.proof { - write!(f, r#""{}","#, commit.0)?; - } - write!(f, "]")?; - write!(f, "}}")?; - - Ok(()) - } -} - -/// compute a recoded block from an arbitrary set of blocks -/// -/// coefficients will be drawn at random, one for each block. -/// -/// if the blocks appear to come from different data, e.g. if the commits are -/// different, an error will be returned. -/// -/// > **Note** -/// > this is a wrapper around [`fec::recode_random`]. -pub fn recode<F: PrimeField, G: CurveGroup<ScalarField = F>>( - blocks: &[Block<F, G>], - rng: &mut impl RngCore, -) -> Result<Option<Block<F, G>>, KomodoError> { - for (i, (b1, b2)) in blocks.iter().zip(blocks.iter().skip(1)).enumerate() { - if b1.proof != b2.proof { - return Err(KomodoError::IncompatibleBlocks(format!( - "proofs are not the same at {}: {:?} vs {:?}", - i, b1.proof, b2.proof - ))); - } - } - let shard = match fec::recode_random( - &blocks.iter().map(|b| b.shard.clone()).collect::<Vec<_>>(), - rng, - )? { - Some(s) => s, - None => return Ok(None), - }; - - Ok(Some(Block { - shard, - proof: blocks[0].proof.clone(), - })) -} - -/// compute the Semi-AVID proof for some data -pub fn prove<F, G, P>( - bytes: &[u8], - powers: &Powers<F, G>, - k: usize, -) -> Result<Vec<Commitment<F, G>>, KomodoError> -where - F: PrimeField, - G: CurveGroup<ScalarField = F>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, -{ - info!("encoding and proving {} bytes", bytes.len()); - - debug!("splitting bytes into polynomials"); - let elements = field::split_data_into_field_elements(bytes, k); - let polynomials = elements - .chunks(k) - .map(|c| P::from_coefficients_vec(c.to_vec())) - .collect::<Vec<_>>(); - info!( - "data is composed of {} polynomials and {} elements", - polynomials.len(), - elements.len() - ); - - debug!("transposing the polynomials to commit"); - let polynomials_to_commit = (0..polynomials[0].coeffs().len()) - .map(|i| P::from_coefficients_vec(polynomials.iter().map(|p| p.coeffs()[i]).collect())) - .collect::<Vec<P>>(); - - debug!("committing the polynomials"); - let commits = zk::batch_commit(powers, &polynomials_to_commit)?; - - Ok(commits) -} - -/// attach a Semi-AVID proof to a collection of encoded shards -#[inline(always)] -pub fn build<F, G, P>(shards: &[Shard<F>], proof: &[Commitment<F, G>]) -> Vec<Block<F, G>> -where - F: PrimeField, - G: CurveGroup<ScalarField = F>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, -{ - shards - .iter() - .map(|s| Block { - shard: s.clone(), - proof: proof.to_vec(), - }) - .collect::<Vec<_>>() -} - -/// verify that a single block of encoded and proven data is valid -pub fn verify<F, G, P>( - block: &Block<F, G>, - verifier_key: &Powers<F, G>, -) -> Result<bool, KomodoError> -where - F: PrimeField, - G: CurveGroup<ScalarField = F>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, -{ - let elements = block.shard.data.clone(); - let polynomial = P::from_coefficients_vec(elements); - let commit = zk::commit(verifier_key, &polynomial)?; - - let rhs = block - .shard - .linear_combination - .iter() - .enumerate() - .map(|(i, w)| block.proof[i].0.into() * w) - .sum(); - Ok(commit.0.into() == rhs) -} - -#[cfg(test)] -mod tests { - use ark_bls12_381::{Fr, G1Projective}; - use ark_ec::CurveGroup; - use ark_ff::PrimeField; - use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; - use ark_std::{ops::Div, test_rng}; - - use crate::{ - build, - error::KomodoError, - fec::{decode, encode, Shard}, - linalg::Matrix, - prove, recode, verify, - zk::{setup, Commitment}, - }; - - fn bytes() -> Vec<u8> { - include_bytes!("../tests/dragoon_133x133.png").to_vec() - } - - macro_rules! full { - ($b:ident, $p:ident, $m:ident) => { - build::<F, G, P>(&encode($b, $m)?, &prove($b, &$p, $m.height)?) - }; - } - - fn verify_template<F, G, P>(bytes: &[u8], encoding_mat: &Matrix<F>) -> Result<(), KomodoError> - where - F: PrimeField, - G: CurveGroup<ScalarField = F>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, - { - let rng = &mut test_rng(); - - let powers = setup::<F, G>(bytes.len(), rng)?; - - let blocks = full!(bytes, powers, encoding_mat); - - for block in &blocks { - assert!(verify(block, &powers)?); - } - - Ok(()) - } - - fn verify_with_errors_template<F, G, P>( - bytes: &[u8], - encoding_mat: &Matrix<F>, - ) -> Result<(), KomodoError> - where - F: PrimeField, - G: CurveGroup<ScalarField = F>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, - { - let rng = &mut test_rng(); - - let powers = setup(bytes.len(), rng)?; - - let blocks = full!(bytes, powers, encoding_mat); - - for block in &blocks { - assert!(verify(block, &powers)?); - } - - let mut corrupted_block = blocks[0].clone(); - // modify a field in the struct b to corrupt the block proof without corrupting the data serialization - let a = F::from_le_bytes_mod_order(&123u128.to_le_bytes()); - let mut commits: Vec<G> = corrupted_block.proof.iter().map(|c| c.0.into()).collect(); - commits[0] = commits[0].mul(a.pow([4321_u64])); - corrupted_block.proof = commits.iter().map(|&c| Commitment(c.into())).collect(); - - assert!(!verify(&corrupted_block, &powers)?); - - Ok(()) - } - - fn verify_recoding_template<F, G, P>( - bytes: &[u8], - encoding_mat: &Matrix<F>, - ) -> Result<(), KomodoError> - where - F: PrimeField, - G: CurveGroup<ScalarField = F>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, - { - let rng = &mut test_rng(); - - let powers = setup::<F, G>(bytes.len(), rng)?; - - let blocks = full!(bytes, powers, encoding_mat); - - assert!(verify( - &recode(&blocks[2..=3], rng).unwrap().unwrap(), - &powers - )?); - assert!(verify( - &recode(&[blocks[3].clone(), blocks[5].clone()], rng) - .unwrap() - .unwrap(), - &powers - )?); - - Ok(()) - } - - fn end_to_end_template<F, G, P>( - bytes: &[u8], - encoding_mat: &Matrix<F>, - ) -> Result<(), KomodoError> - where - F: PrimeField, - G: CurveGroup<ScalarField = F>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, - { - let rng = &mut test_rng(); - - let powers = setup::<F, G>(bytes.len(), rng)?; - - let blocks = full!(bytes, powers, encoding_mat); - - let shards: Vec<Shard<F>> = blocks.iter().map(|b| b.shard.clone()).collect(); - - assert_eq!(bytes, decode(shards).unwrap()); - - Ok(()) - } - - fn end_to_end_with_recoding_template<F, G, P>( - bytes: &[u8], - encoding_mat: &Matrix<F>, - ) -> Result<(), KomodoError> - where - F: PrimeField, - G: CurveGroup<ScalarField = F>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, - { - let rng = &mut test_rng(); - - let powers = setup::<F, G>(bytes.len(), rng)?; - - let blocks = full!(bytes, powers, encoding_mat); - - let b_0_1 = recode(&blocks[0..=1], rng).unwrap().unwrap(); - let shards = vec![ - b_0_1.shard, - blocks[2].shard.clone(), - blocks[3].shard.clone(), - ]; - assert_eq!(bytes, decode(shards).unwrap()); - - let b_0_1 = recode(&[blocks[0].clone(), blocks[1].clone()], rng) - .unwrap() - .unwrap(); - let shards = vec![ - blocks[0].shard.clone(), - blocks[1].shard.clone(), - b_0_1.shard, - ]; - assert!(decode(shards).is_err()); - - let b_0_1 = recode(&blocks[0..=1], rng).unwrap().unwrap(); - let b_2_3 = recode(&blocks[2..=3], rng).unwrap().unwrap(); - let b_1_4 = recode(&[blocks[1].clone(), blocks[4].clone()], rng) - .unwrap() - .unwrap(); - let shards = vec![b_0_1.shard, b_2_3.shard, b_1_4.shard]; - assert_eq!(bytes, decode(shards).unwrap()); - - let fully_recoded_shards = (0..3) - .map(|_| recode(&blocks[0..=2], rng).unwrap().unwrap().shard) - .collect(); - assert_eq!(bytes, decode(fully_recoded_shards).unwrap()); - - Ok(()) - } - - // NOTE: this is part of an experiment, to be honest, to be able to see how - // much these tests could be refactored and simplified - fn run_template<F, P, Fun>(test: Fun) - where - F: PrimeField, - Fun: Fn(&[u8], &Matrix<F>) -> Result<(), KomodoError>, - P: DenseUVPolynomial<F>, - for<'a, 'b> &'a P: Div<&'b P, Output = P>, - { - let mut rng = ark_std::test_rng(); - - let (k, n) = (3, 6_usize); - - let bytes = bytes(); - - let test_case = format!("TEST | data: {} bytes, k: {}, n: {}", bytes.len(), k, n); - - test(&bytes, &Matrix::random(k, n, &mut rng)).unwrap_or_else(|_| { - panic!("verification failed for bls12-381 and random encoding matrix\n{test_case}") - }); - test( - &bytes, - &Matrix::vandermonde_unchecked( - &(0..n) - .map(|i| F::from_le_bytes_mod_order(&i.to_le_bytes())) - .collect::<Vec<_>>(), - k, - ), - ) - .unwrap_or_else(|_| { - panic!("verification failed for bls12-381 and Vandermonde encoding matrix\n{test_case}") - }); - } - - #[test] - fn verification() { - run_template::<Fr, DensePolynomial<Fr>, _>( - verify_template::<Fr, G1Projective, DensePolynomial<Fr>>, - ); - } - - #[test] - fn verify_with_errors() { - run_template::<Fr, DensePolynomial<Fr>, _>( - verify_with_errors_template::<Fr, G1Projective, DensePolynomial<Fr>>, - ); - } - - #[test] - fn verify_recoding() { - run_template::<Fr, DensePolynomial<Fr>, _>( - verify_recoding_template::<Fr, G1Projective, DensePolynomial<Fr>>, - ); - } - - #[test] - fn end_to_end() { - run_template::<Fr, DensePolynomial<Fr>, _>( - end_to_end_template::<Fr, G1Projective, DensePolynomial<Fr>>, - ); - } - - #[test] - fn end_to_end_with_recoding() { - run_template::<Fr, DensePolynomial<Fr>, _>( - end_to_end_with_recoding_template::<Fr, G1Projective, DensePolynomial<Fr>>, - ); - } -} diff --git a/src/main.rs b/src/main.rs index 4344e22e..cc262431 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,14 +13,12 @@ use ark_std::rand::RngCore; use tracing::{info, warn}; use komodo::{ - build, error::KomodoError, fec::{self, decode, Shard}, fs, linalg::Matrix, - prove, recode, verify, + semi_avid::{build, prove, recode, verify, Block}, zk::{self, Powers}, - Block, }; const COMPRESS: Compress = Compress::Yes; diff --git a/src/semi_avid.rs b/src/semi_avid.rs new file mode 100644 index 00000000..ee44af38 --- /dev/null +++ b/src/semi_avid.rs @@ -0,0 +1,441 @@ +use ark_ec::CurveGroup; +use ark_ff::PrimeField; +use ark_poly::DenseUVPolynomial; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::ops::Div; +use ark_std::rand::RngCore; + +use tracing::{debug, info}; + +use crate::{ + error::KomodoError, + fec::{self, Shard}, + field, + zk::{self, Commitment, Powers}, +}; + +/// representation of a block of proven data. +/// +/// this is a wrapper around a [`fec::Shard`] with some additional cryptographic +/// information that allows to prove the integrity of said shard. +#[derive(Debug, Default, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct Block<F: PrimeField, G: CurveGroup<ScalarField = F>> { + pub shard: fec::Shard<F>, + proof: Vec<Commitment<F, G>>, +} + +impl<F: PrimeField, G: CurveGroup<ScalarField = F>> std::fmt::Display for Block<F, G> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{{")?; + write!(f, "shard: {{")?; + write!(f, "k: {},", self.shard.k)?; + write!(f, "comb: [")?; + for x in &self.shard.linear_combination { + if x.is_zero() { + write!(f, "0,")?; + } else { + write!(f, r#""{}","#, x)?; + } + } + write!(f, "]")?; + write!(f, ",")?; + write!(f, "bytes: [")?; + for x in &self.shard.data { + if x.is_zero() { + write!(f, "0,")?; + } else { + write!(f, r#""{}","#, x)?; + } + } + write!(f, "]")?; + write!(f, ",")?; + write!( + f, + "hash: {:?},", + self.shard + .hash + .iter() + .map(|x| format!("{:x}", x)) + .collect::<Vec<_>>() + .join("") + )?; + write!(f, "size: {},", self.shard.size)?; + write!(f, "}}")?; + write!(f, ",")?; + write!(f, "commits: [")?; + for commit in &self.proof { + write!(f, r#""{}","#, commit.0)?; + } + write!(f, "]")?; + write!(f, "}}")?; + + Ok(()) + } +} + +/// compute a recoded block from an arbitrary set of blocks +/// +/// coefficients will be drawn at random, one for each block. +/// +/// if the blocks appear to come from different data, e.g. if the commits are +/// different, an error will be returned. +/// +/// > **Note** +/// > this is a wrapper around [`fec::recode_random`]. +pub fn recode<F: PrimeField, G: CurveGroup<ScalarField = F>>( + blocks: &[Block<F, G>], + rng: &mut impl RngCore, +) -> Result<Option<Block<F, G>>, KomodoError> { + for (i, (b1, b2)) in blocks.iter().zip(blocks.iter().skip(1)).enumerate() { + if b1.proof != b2.proof { + return Err(KomodoError::IncompatibleBlocks(format!( + "proofs are not the same at {}: {:?} vs {:?}", + i, b1.proof, b2.proof + ))); + } + } + let shard = match fec::recode_random( + &blocks.iter().map(|b| b.shard.clone()).collect::<Vec<_>>(), + rng, + )? { + Some(s) => s, + None => return Ok(None), + }; + + Ok(Some(Block { + shard, + proof: blocks[0].proof.clone(), + })) +} + +/// compute the Semi-AVID proof for some data +pub fn prove<F, G, P>( + bytes: &[u8], + powers: &Powers<F, G>, + k: usize, +) -> Result<Vec<Commitment<F, G>>, KomodoError> +where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, +{ + info!("encoding and proving {} bytes", bytes.len()); + + debug!("splitting bytes into polynomials"); + let elements = field::split_data_into_field_elements(bytes, k); + let polynomials = elements + .chunks(k) + .map(|c| P::from_coefficients_vec(c.to_vec())) + .collect::<Vec<_>>(); + info!( + "data is composed of {} polynomials and {} elements", + polynomials.len(), + elements.len() + ); + + debug!("transposing the polynomials to commit"); + let polynomials_to_commit = (0..polynomials[0].coeffs().len()) + .map(|i| P::from_coefficients_vec(polynomials.iter().map(|p| p.coeffs()[i]).collect())) + .collect::<Vec<P>>(); + + debug!("committing the polynomials"); + let commits = zk::batch_commit(powers, &polynomials_to_commit)?; + + Ok(commits) +} + +/// attach a Semi-AVID proof to a collection of encoded shards +#[inline(always)] +pub fn build<F, G, P>(shards: &[Shard<F>], proof: &[Commitment<F, G>]) -> Vec<Block<F, G>> +where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, +{ + shards + .iter() + .map(|s| Block { + shard: s.clone(), + proof: proof.to_vec(), + }) + .collect::<Vec<_>>() +} + +/// verify that a single block of encoded and proven data is valid +pub fn verify<F, G, P>( + block: &Block<F, G>, + verifier_key: &Powers<F, G>, +) -> Result<bool, KomodoError> +where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, +{ + let elements = block.shard.data.clone(); + let polynomial = P::from_coefficients_vec(elements); + let commit = zk::commit(verifier_key, &polynomial)?; + + let rhs = block + .shard + .linear_combination + .iter() + .enumerate() + .map(|(i, w)| block.proof[i].0.into() * w) + .sum(); + Ok(commit.0.into() == rhs) +} + +#[cfg(test)] +mod tests { + use ark_bls12_381::{Fr, G1Projective}; + use ark_ec::CurveGroup; + use ark_ff::PrimeField; + use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; + use ark_std::{ops::Div, test_rng}; + + use crate::{ + error::KomodoError, + fec::{decode, encode, Shard}, + linalg::Matrix, + zk::{setup, Commitment}, + }; + + use super::{build, prove, recode, verify}; + + fn bytes() -> Vec<u8> { + include_bytes!("../tests/dragoon_133x133.png").to_vec() + } + + macro_rules! full { + ($b:ident, $p:ident, $m:ident) => { + build::<F, G, P>(&encode($b, $m)?, &prove($b, &$p, $m.height)?) + }; + } + + fn verify_template<F, G, P>(bytes: &[u8], encoding_mat: &Matrix<F>) -> Result<(), KomodoError> + where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, + { + let rng = &mut test_rng(); + + let powers = setup::<F, G>(bytes.len(), rng)?; + + let blocks = full!(bytes, powers, encoding_mat); + + for block in &blocks { + assert!(verify(block, &powers)?); + } + + Ok(()) + } + + fn verify_with_errors_template<F, G, P>( + bytes: &[u8], + encoding_mat: &Matrix<F>, + ) -> Result<(), KomodoError> + where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, + { + let rng = &mut test_rng(); + + let powers = setup(bytes.len(), rng)?; + + let blocks = full!(bytes, powers, encoding_mat); + + for block in &blocks { + assert!(verify(block, &powers)?); + } + + let mut corrupted_block = blocks[0].clone(); + // modify a field in the struct b to corrupt the block proof without corrupting the data serialization + let a = F::from_le_bytes_mod_order(&123u128.to_le_bytes()); + let mut commits: Vec<G> = corrupted_block.proof.iter().map(|c| c.0.into()).collect(); + commits[0] = commits[0].mul(a.pow([4321_u64])); + corrupted_block.proof = commits.iter().map(|&c| Commitment(c.into())).collect(); + + assert!(!verify(&corrupted_block, &powers)?); + + Ok(()) + } + + fn verify_recoding_template<F, G, P>( + bytes: &[u8], + encoding_mat: &Matrix<F>, + ) -> Result<(), KomodoError> + where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, + { + let rng = &mut test_rng(); + + let powers = setup::<F, G>(bytes.len(), rng)?; + + let blocks = full!(bytes, powers, encoding_mat); + + assert!(verify( + &recode(&blocks[2..=3], rng).unwrap().unwrap(), + &powers + )?); + assert!(verify( + &recode(&[blocks[3].clone(), blocks[5].clone()], rng) + .unwrap() + .unwrap(), + &powers + )?); + + Ok(()) + } + + fn end_to_end_template<F, G, P>( + bytes: &[u8], + encoding_mat: &Matrix<F>, + ) -> Result<(), KomodoError> + where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, + { + let rng = &mut test_rng(); + + let powers = setup::<F, G>(bytes.len(), rng)?; + + let blocks = full!(bytes, powers, encoding_mat); + + let shards: Vec<Shard<F>> = blocks.iter().map(|b| b.shard.clone()).collect(); + + assert_eq!(bytes, decode(shards).unwrap()); + + Ok(()) + } + + fn end_to_end_with_recoding_template<F, G, P>( + bytes: &[u8], + encoding_mat: &Matrix<F>, + ) -> Result<(), KomodoError> + where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, + { + let rng = &mut test_rng(); + + let powers = setup::<F, G>(bytes.len(), rng)?; + + let blocks = full!(bytes, powers, encoding_mat); + + let b_0_1 = recode(&blocks[0..=1], rng).unwrap().unwrap(); + let shards = vec![ + b_0_1.shard, + blocks[2].shard.clone(), + blocks[3].shard.clone(), + ]; + assert_eq!(bytes, decode(shards).unwrap()); + + let b_0_1 = recode(&[blocks[0].clone(), blocks[1].clone()], rng) + .unwrap() + .unwrap(); + let shards = vec![ + blocks[0].shard.clone(), + blocks[1].shard.clone(), + b_0_1.shard, + ]; + assert!(decode(shards).is_err()); + + let b_0_1 = recode(&blocks[0..=1], rng).unwrap().unwrap(); + let b_2_3 = recode(&blocks[2..=3], rng).unwrap().unwrap(); + let b_1_4 = recode(&[blocks[1].clone(), blocks[4].clone()], rng) + .unwrap() + .unwrap(); + let shards = vec![b_0_1.shard, b_2_3.shard, b_1_4.shard]; + assert_eq!(bytes, decode(shards).unwrap()); + + let fully_recoded_shards = (0..3) + .map(|_| recode(&blocks[0..=2], rng).unwrap().unwrap().shard) + .collect(); + assert_eq!(bytes, decode(fully_recoded_shards).unwrap()); + + Ok(()) + } + + // NOTE: this is part of an experiment, to be honest, to be able to see how + // much these tests could be refactored and simplified + fn run_template<F, P, Fun>(test: Fun) + where + F: PrimeField, + Fun: Fn(&[u8], &Matrix<F>) -> Result<(), KomodoError>, + P: DenseUVPolynomial<F>, + for<'a, 'b> &'a P: Div<&'b P, Output = P>, + { + let mut rng = ark_std::test_rng(); + + let (k, n) = (3, 6_usize); + + let bytes = bytes(); + + let test_case = format!("TEST | data: {} bytes, k: {}, n: {}", bytes.len(), k, n); + + test(&bytes, &Matrix::random(k, n, &mut rng)).unwrap_or_else(|_| { + panic!("verification failed for bls12-381 and random encoding matrix\n{test_case}") + }); + test( + &bytes, + &Matrix::vandermonde_unchecked( + &(0..n) + .map(|i| F::from_le_bytes_mod_order(&i.to_le_bytes())) + .collect::<Vec<_>>(), + k, + ), + ) + .unwrap_or_else(|_| { + panic!("verification failed for bls12-381 and Vandermonde encoding matrix\n{test_case}") + }); + } + + #[test] + fn verification() { + run_template::<Fr, DensePolynomial<Fr>, _>( + verify_template::<Fr, G1Projective, DensePolynomial<Fr>>, + ); + } + + #[test] + fn verify_with_errors() { + run_template::<Fr, DensePolynomial<Fr>, _>( + verify_with_errors_template::<Fr, G1Projective, DensePolynomial<Fr>>, + ); + } + + #[test] + fn verify_recoding() { + run_template::<Fr, DensePolynomial<Fr>, _>( + verify_recoding_template::<Fr, G1Projective, DensePolynomial<Fr>>, + ); + } + + #[test] + fn end_to_end() { + run_template::<Fr, DensePolynomial<Fr>, _>( + end_to_end_template::<Fr, G1Projective, DensePolynomial<Fr>>, + ); + } + + #[test] + fn end_to_end_with_recoding() { + run_template::<Fr, DensePolynomial<Fr>, _>( + end_to_end_with_recoding_template::<Fr, G1Projective, DensePolynomial<Fr>>, + ); + } +} -- GitLab