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),
+                ],
+            )
+        });
     }
 }