diff --git a/src/algebra/linalg.rs b/src/algebra/linalg.rs index 5e6d6275fe0fe375222af9419ddea226aacb2e1e..5293b2c24cd70177d2eec361c1b19e9330a85514 100644 --- a/src/algebra/linalg.rs +++ b/src/algebra/linalg.rs @@ -10,7 +10,7 @@ use crate::error::KomodoError; /// a matrix defined over a finite field /// -/// internally, a matrix is just a vector of field elements that whose length is +/// internally, a matrix is just a vector of field elements whose length is /// exactly the width times the height and where elements are organized row by /// row. #[derive(Clone, PartialEq, Default, Debug, CanonicalSerialize, CanonicalDeserialize)] @@ -24,7 +24,7 @@ impl<T: Field> Matrix<T> { /// build a matrix from a diagonal of elements /// /// # Example - /// building a diagonal matrix from the diagonal $[1, 2, 3, 4]$ will give + /// building a diagonal matrix from the diagonal `[1, 2, 3, 4]` would give /// ```text /// [ /// [1, 0, 0, 0], @@ -66,12 +66,13 @@ impl<T: Field> Matrix<T> { /// build a Vandermonde matrix for some seed points /// - /// actually, this is the tranpose of the Vandermonde matrix defined in the + /// actually, this is the transpose of the Vandermonde matrix defined in the /// [Wikipedia article][article], i.e. there are as many columns as there /// are seed points and there are as many rows as there are powers of the /// seed points. /// - /// > **Note** + /// > **Note** + /// > /// > if you are sure the points are distinct and don't want to perform any /// > runtime check to ensure that condition, have a look at /// > [`Self::vandermonde_unchecked`]. @@ -153,7 +154,8 @@ impl<T: Field> Matrix<T> { /// build a matrix from a "_matrix_" of elements /// - /// > **Note** + /// > **Note** + /// > /// > if you are sure each row should have the same length and don't want to /// > perform any runtime check to ensure that condition, have a look at /// > [`Self::from_vec_vec_unchecked`]. @@ -249,6 +251,7 @@ impl<T: Field> Matrix<T> { /// extract a single column from the matrix /// /// > **Note** + /// > /// > returns `None` if the provided index is out of bounds pub(crate) fn get_col(&self, j: usize) -> Option<Vec<T>> { if j >= self.width { @@ -258,14 +261,14 @@ impl<T: Field> Matrix<T> { Some((0..self.height).map(|i| self.get(i, j)).collect()) } - // compute _row / value_ + /// compute _row = row / value_ fn divide_row_by(&mut self, row: usize, value: T) { for j in 0..self.width { self.set(row, j, self.get(row, j) / value); } } - // compute _destination = destination + source * value_ + /// compute _destination = destination + source * value_ fn multiply_row_by_and_add_to_row(&mut self, source: usize, value: T, destination: usize) { for j in 0..self.width { self.set( @@ -278,7 +281,8 @@ impl<T: Field> Matrix<T> { /// compute the inverse of the matrix /// - /// > **None** + /// > **Note** + /// > /// > the matrix should be /// > - square /// > - invertible @@ -314,6 +318,7 @@ impl<T: Field> Matrix<T> { /// swap rows `i` and `j`, inplace /// /// > **Note** + /// > /// > this function assumes both `i` and `j` are in bounds, unexpected /// > results are expected if `i` or `j` are out of bounds. fn swap_rows(&mut self, i: usize, j: usize) { @@ -324,7 +329,8 @@ impl<T: Field> Matrix<T> { /// compute the rank of the matrix /// - /// > **None** + /// > **Note** + /// > /// > see the [_Wikipedia article_](https://en.wikipedia.org/wiki/Rank_(linear_algebra)) /// > for more information /// > @@ -373,10 +379,11 @@ impl<T: Field> Matrix<T> { /// compute the matrix multiplication with another matrix /// - /// if `mat` represents a matrix $A$ and `rhs` is the representation of - /// another matrix $B$, then `mat.mul(rhs)` will compute $A \times B$ + /// if `lhs` represents a matrix $A$ and `rhs` is the representation of + /// another matrix $B$, then `lhs.mul(rhs)` will compute $A \times B$ /// /// > **Note** + /// > /// > both matrices should have compatible shapes, i.e. if `self` has shape /// > `(n, m)` and `rhs` has shape `(p, q)`, then `m == p`. pub fn mul(&self, rhs: &Self) -> Result<Self, KomodoError> { @@ -412,6 +419,7 @@ impl<T: Field> Matrix<T> { /// compute the transpose of the matrix /// /// > **Note** + /// > /// > see the [_Wikipedia article_](https://en.wikipedia.org/wiki/Transpose) pub fn transpose(&self) -> Self { let height = self.width; diff --git a/src/algebra/mod.rs b/src/algebra/mod.rs index 5a70b1568d0c38fc6600b65152da342c682622a2..3ddf02193968cf29904d64cfc80bb79351561f21 100644 --- a/src/algebra/mod.rs +++ b/src/algebra/mod.rs @@ -246,13 +246,11 @@ mod tests { split_data_template::<Fr>(&[], 1, None); split_data_template::<Fr>(&[], 8, None); - let nb_bytes = 11 * (Fr::MODULUS_BIT_SIZE as usize / 8); - split_data_template::<Fr>(&bytes()[..nb_bytes], 1, Some(11)); - split_data_template::<Fr>(&bytes()[..nb_bytes], 8, Some(16)); - - let nb_bytes = 11 * (Fr::MODULUS_BIT_SIZE as usize / 8) - 10; - split_data_template::<Fr>(&bytes()[..nb_bytes], 1, Some(11)); - split_data_template::<Fr>(&bytes()[..nb_bytes], 8, Some(16)); + const MODULUS_BYTE_SIZE: usize = Fr::MODULUS_BIT_SIZE as usize / 8; + for n in (10 * MODULUS_BYTE_SIZE + 1)..(11 * MODULUS_BYTE_SIZE) { + split_data_template::<Fr>(&bytes()[..n], 1, Some(11)); + split_data_template::<Fr>(&bytes()[..n], 8, Some(16)); + } } fn split_and_merge_template<F: PrimeField>(bytes: &[u8], modulus: usize) { @@ -264,10 +262,9 @@ mod tests { #[test] fn split_and_merge() { - split_and_merge_template::<Fr>(&bytes(), 1); - split_and_merge_template::<Fr>(&bytes(), 8); - split_and_merge_template::<Fr>(&bytes(), 64); - split_and_merge_template::<Fr>(&bytes(), 4096); + for i in 0..12 { + split_and_merge_template::<Fr>(&bytes(), 1 << i); + } } #[cfg(any(feature = "kzg", feature = "aplonk"))] diff --git a/src/error.rs b/src/error.rs index dbcf8e4954ca3e60ca55da094530f422ec94283d..cec1afd264f83ea675fed24e1cd9787863bbf193 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ //! Komodo-specific errors //! -//! there are a few linear algebra errors and some related to ZK. +//! there are a few linear algebra errors and some related to [crate::zk]. use thiserror::Error; /// An error that Komodo could end up producing. @@ -42,7 +42,7 @@ pub enum KomodoError { #[error("Degree is zero")] DegreeIsZero, /// `{0}` is the supported degree of the trusted setup and `{1}` is the actual requested - /// polynomnial degree + /// polynomial degree #[error("too many coefficients: max is {0}, found {0}")] TooFewPowersInTrustedSetup(usize, usize), /// `{0}` is a custom error message. diff --git a/src/fec.rs b/src/fec.rs index 0d6383b8f8210a5e18e246736fbde0ffcdcd0d3f..2329a21e44cb223b151e7aae8bc53233ca1d1366 100644 --- a/src/fec.rs +++ b/src/fec.rs @@ -8,7 +8,7 @@ use rs_merkle::{algorithms::Sha256, Hasher}; use crate::{algebra, algebra::linalg::Matrix, error::KomodoError}; -/// representation of a FEC shard of data. +/// representation of a [FEC](https://en.wikipedia.org/wiki/Error_correction_code) shard of data. #[derive(Debug, Default, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Shard<F: PrimeField> { /// the code parameter, required to decode @@ -57,7 +57,7 @@ impl<F: PrimeField> Shard<F> { .iter() .zip(other.data.iter()) .map(|(es, eo)| es.mul(alpha) + eo.mul(beta)) - .collect::<Vec<_>>(), + .collect(), size: self.size, } } @@ -248,7 +248,7 @@ mod tests { /// `contains_one_of(x, set)` is true iif `x` fully contains one of the lists from `set` /// - /// > **Note** + /// > **Note** /// > /// > see [`containment`] for some example fn contains_one_of(x: &[usize], set: &[Vec<usize>]) -> bool { diff --git a/src/lib.rs b/src/lib.rs index c4c0690ec36cc0b147c8b6b24101c05bf4714c78..abc68e21aca3391f532152f564f1447be93ca40d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ //! > modules marked with an `*`, e.g. [`kzg`]*, are hidden behind a _Cargo_ feature with the same //! > name //! -//! Other submodules define several fundamental building blocks to Komodo, but which are not +//! Other submodules define several fundamental building blocks to Komodo, but are not //! mandatory to explore to understand the protocols. //! //! # Example diff --git a/src/semi_avid.rs b/src/semi_avid.rs index fa844ed7487443ae3689e901f9abd2626b697896..59638ed8cbb9cd990062cc069cc14213f2cfc0c4 100644 --- a/src/semi_avid.rs +++ b/src/semi_avid.rs @@ -327,6 +327,7 @@ where #[cfg(test)] mod tests { use ark_bls12_381::{Fr, G1Projective}; + const CURVE_NAME: &str = "bls12-381"; use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; @@ -339,7 +340,7 @@ mod tests { zk::{setup, Commitment}, }; - use super::{build, prove, recode, verify}; + use super::{build, prove, recode, verify, Block}; fn bytes() -> Vec<u8> { include_bytes!("../assets/dragoon_133x133.png").to_vec() @@ -351,6 +352,7 @@ mod tests { }; } + /// verify all `n` blocks fn verify_template<F, G, P>(bytes: &[u8], encoding_mat: &Matrix<F>) -> Result<(), KomodoError> where F: PrimeField, @@ -371,9 +373,27 @@ mod tests { Ok(()) } + /// attack a block by alterring one part of its proof + fn attack<F, G>(block: Block<F, G>, c: usize, base: u128, pow: u64) -> Block<F, G> + where + F: PrimeField, + G: CurveGroup<ScalarField = F>, + { + let mut block = block; + // 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(&base.to_le_bytes()); + let mut commits: Vec<G> = block.proof.iter().map(|c| c.0.into()).collect(); + commits[c] = commits[c].mul(a.pow([pow])); + block.proof = commits.iter().map(|&c| Commitment(c.into())).collect(); + + block + } + + /// verify all `n` blocks and then make sure an attacked block does not verify fn verify_with_errors_template<F, G, P>( bytes: &[u8], encoding_mat: &Matrix<F>, + attacks: Vec<(usize, usize, u128, u64)>, ) -> Result<(), KomodoError> where F: PrimeField, @@ -391,21 +411,18 @@ mod tests { 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)?); + for (b, c, base, pow) in attacks { + assert!(!verify(&attack(blocks[b].clone(), c, base, pow), &powers)?); + } Ok(()) } + /// make sure recoded blocks still verify correctly fn verify_recoding_template<F, G, P>( bytes: &[u8], encoding_mat: &Matrix<F>, + recodings: Vec<Vec<usize>>, ) -> Result<(), KomodoError> where F: PrimeField, @@ -419,20 +436,30 @@ mod tests { 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) + let min_nb_blocks = recodings.clone().into_iter().flatten().max().unwrap() + 1; + assert!( + blocks.len() >= min_nb_blocks, + "not enough blocks, expected {}, found {}", + min_nb_blocks, + blocks.len() + ); + + for bs in recodings { + assert!(verify( + &recode( + &bs.iter().map(|&i| blocks[i].clone()).collect::<Vec<_>>(), + rng + ) .unwrap() .unwrap(), - &powers - )?); + &powers + )?); + } Ok(()) } + /// encode and decode with all `n` shards fn end_to_end_template<F, G, P>( bytes: &[u8], encoding_mat: &Matrix<F>, @@ -456,9 +483,11 @@ mod tests { Ok(()) } + /// encode and try to decode with recoded shards fn end_to_end_with_recoding_template<F, G, P>( bytes: &[u8], encoding_mat: &Matrix<F>, + recodings: Vec<(Vec<Vec<usize>>, bool)>, ) -> Result<(), KomodoError> where F: PrimeField, @@ -468,47 +497,72 @@ mod tests { { let rng = &mut test_rng(); + let max_k = recodings.iter().map(|(rs, _)| rs.len()).min().unwrap(); + assert!( + encoding_mat.height <= max_k, + "too many source shards, expected at most {}, found {}", + max_k, + encoding_mat.height + ); + 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) + let min_n = recodings + .iter() + .flat_map(|(rs, _)| rs.iter().flatten()) + .max() .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()); + + 1; + assert!( + blocks.len() >= min_n, + "not enough blocks, expected {}, found {}", + min_n, + blocks.len() + ); - let fully_recoded_shards = (0..3) - .map(|_| recode(&blocks[0..=2], rng).unwrap().unwrap().shard) - .collect(); - assert_eq!(bytes, decode(fully_recoded_shards).unwrap()); + for (rs, pass) in recodings { + let recoded_shards = rs + .iter() + .map(|bs| { + if bs.len() == 1 { + blocks[bs[0]].clone().shard + } else { + recode( + &bs.iter().map(|&i| blocks[i].clone()).collect::<Vec<_>>(), + rng, + ) + .unwrap() + .unwrap() + .shard + } + }) + .collect(); + if pass { + assert_eq!( + bytes, + decode(recoded_shards).unwrap(), + "should decode with {:?}", + rs + ); + } else { + assert!( + decode(recoded_shards).is_err(), + "should not decode with {:?}", + rs + ); + } + } 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) + /// run the `test` with a _(k, n)_ encoding and on both a random and a Vandermonde encoding + /// + /// 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>(k: usize, n: usize, test: Fun) where F: PrimeField, Fun: Fn(&[u8], &Matrix<F>) -> Result<(), KomodoError>, @@ -517,14 +571,12 @@ mod tests { { 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}") + panic!("verification failed for {CURVE_NAME} and random encoding matrix\n{test_case}") }); test( &bytes, @@ -536,42 +588,65 @@ mod tests { ), ) .unwrap_or_else(|_| { - panic!("verification failed for bls12-381 and Vandermonde encoding matrix\n{test_case}") + panic!( + "verification failed for {CURVE_NAME} and Vandermonde encoding matrix\n{test_case}" + ) }); } #[test] fn verification() { run_template::<Fr, DensePolynomial<Fr>, _>( + 3, + 6, verify_template::<Fr, G1Projective, DensePolynomial<Fr>>, ); } #[test] fn verify_with_errors() { - run_template::<Fr, DensePolynomial<Fr>, _>( - verify_with_errors_template::<Fr, G1Projective, DensePolynomial<Fr>>, - ); + run_template::<Fr, DensePolynomial<Fr>, _>(3, 6, |b, m| { + verify_with_errors_template::<Fr, G1Projective, DensePolynomial<Fr>>( + b, + m, + vec![(0, 0, 123u128, 4321u64)], + ) + }); } #[test] fn verify_recoding() { - run_template::<Fr, DensePolynomial<Fr>, _>( - verify_recoding_template::<Fr, G1Projective, DensePolynomial<Fr>>, - ); + run_template::<Fr, DensePolynomial<Fr>, _>(3, 6, |b, m| { + verify_recoding_template::<Fr, G1Projective, DensePolynomial<Fr>>( + b, + m, + vec![vec![2, 3], vec![3, 5]], + ) + }); } #[test] fn end_to_end() { run_template::<Fr, DensePolynomial<Fr>, _>( + 3, + 6, 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>>, - ); + run_template::<Fr, DensePolynomial<Fr>, _>(3, 6, |b, m| { + end_to_end_with_recoding_template::<Fr, G1Projective, DensePolynomial<Fr>>( + b, + m, + vec![ + (vec![vec![0, 1], vec![2], vec![3]], true), + (vec![vec![0, 1], vec![0], vec![1]], false), + (vec![vec![0, 1], vec![2, 3], vec![1, 4]], true), + (vec![vec![0, 1, 2], vec![0, 1, 2], vec![0, 1, 2]], true), + ], + ) + }); } }