Repository: logos-co/nomos
Pull request #1259: feat(da): DA v1.1
Author: login: danielSanchezQ
Labels: name: da
Created: 2025-04-24T10:32:20Z | Merged: 2025-05-16T09:35:02Z

## 1. What does this PR implement?

Implemented aggregated proving for DA encoding.  This schema is a much lightweight design. Encoding times are improved greatly while maintaining verification ones.

## 2. Does the code have enough context to be clearly understood?

Yes. In fact is a much simpler process, a lot of code is removed.

## 3. Who are the specification authors and who is accountable for this PR?

@danielSanchezQ @thomaslavaur @megonen 

## 4. Is the specification accurate and complete?

~~No, this implies changing both encoding and verification specs and remains to be done.~~
Specification was reviwed and adapted to the new encoding schema.

## 5. Does the implementation introduce changes in the specification?

Yes. But it is in sync now.

## Checklist


* [x] 1. Description added.
* [x] 2. Context and links to Specification document(s) added.
* [x] 3. Main contact(s) (developers and specification authors) added
* [x] 4. Implementation and Specification are 100% in sync including changes. This is critical.
* [x] 5. Link PR to a specific milestone.



Diff:
diff --git a/nomos-da/kzgrs-backend/benches/encoder.rs b/nomos-da/kzgrs-backend/benches/encoder.rs
index 6932a1127d..8dfc0be162 100644
--- a/nomos-da/kzgrs-backend/benches/encoder.rs
+++ b/nomos-da/kzgrs-backend/benches/encoder.rs
@@ -19,7 +19,9 @@ pub fn rand_data(elements_count: usize) -> Vec<u8> {
     rand::thread_rng().fill_bytes(&mut buff);
     buff
 }
-#[divan::bench(consts = [32, 64, 128, 256, 512, 1024], args = [128, 256, 512, 1024, 2048, 4096], sample_count = 1, sample_size = 1)]
+
+#[divan::bench(consts = [32, 64, 128, 256, 512, 1024], args = [128, 256, 512, 1024, 2048, 4096], sample_count = 1, sample_size = 30
+)]
 fn encode<const SIZE: usize>(bencher: Bencher, column_size: usize) {
     bencher
         .with_inputs(|| {
diff --git a/nomos-da/kzgrs-backend/benches/verifier.rs b/nomos-da/kzgrs-backend/benches/verifier.rs
index c059298781..d80e65b060 100644
--- a/nomos-da/kzgrs-backend/benches/verifier.rs
+++ b/nomos-da/kzgrs-backend/benches/verifier.rs
@@ -5,7 +5,6 @@ use kzgrs_backend::{
     common::{share::DaShare, Chunk},
     encoder::{DaEncoder, DaEncoderParams},
     global::GLOBAL_PARAMETERS,
-    verifier::DaVerifier,
 };
 use nomos_core::da::{blob::Share as _, DaEncoder as _};
 use rand::{thread_rng, RngCore as _};
@@ -22,7 +21,9 @@ pub fn rand_data(elements_count: usize) -> Vec<u8> {
     thread_rng().fill_bytes(&mut buff);
     buff
 }
-#[divan::bench(consts = [32, 64, 128, 256, 512, 1024], args = [128, 256, 512, 1024, 2048, 4096], sample_count = 1, sample_size = 30)]
+
+#[divan::bench(consts = [32, 64, 128, 256, 512, 1024], args = [128, 256, 512, 1024, 2048, 4096], sample_count = 1, sample_size = 30
+)]
 fn verify<const SIZE: usize>(bencher: Bencher, column_size: usize) {
     bencher
         .with_inputs(|| {
@@ -31,31 +32,22 @@ fn verify<const SIZE: usize>(bencher: Bencher, column_size: usize) {
             let encoder = DaEncoder::new(params);
             let data = rand_data(SIZE * KB / DaEncoderParams::MAX_BLS12_381_ENCODING_CHUNK_SIZE);
             let encoded_data = encoder.encode(&data).unwrap();
-            let verifier = DaVerifier::new(GLOBAL_PARAMETERS.clone());
+            let verifier = kzgrs_backend::verifier::DaVerifier {
+                global_parameters: GLOBAL_PARAMETERS.clone(),
+            };
             let da_share = DaShare {
                 column: encoded_data.extended_data.columns().next().unwrap(),
                 share_idx: 0,
-                column_commitment: encoded_data.column_commitments.first().copied().unwrap(),
-                aggregated_column_commitment: encoded_data.aggregated_column_commitment,
-                aggregated_column_proof: encoded_data
-                    .aggregated_column_proofs
-                    .first()
-                    .copied()
-                    .unwrap(),
-                rows_commitments: encoded_data.row_commitments.clone(),
-                rows_proofs: encoded_data
-                    .rows_proofs
-                    .iter()
-                    .map(|row| row.iter().next().copied().unwrap())
-                    .collect(),
+                combined_column_proof: encoded_data.combined_column_proofs[0],
+                rows_commitments: encoded_data.row_commitments,
             };
-            (verifier, da_share)
+            let (light_share, commitments) = da_share.into_share_and_commitments();
+            (verifier, light_share, commitments)
         })
-        .input_counter(|(_, share)| {
-            BytesCount::new(share.column.iter().map(Chunk::len).sum::<usize>())
+        .input_counter(|(_, light_share, _)| {
+            BytesCount::new(light_share.column.iter().map(Chunk::len).sum::<usize>())
         })
-        .bench_values(|(verifier, share)| {
-            let (light_share, commitments) = share.into_share_and_commitments();
-            black_box(verifier.verify(&commitments, &light_share, column_size))
+        .bench_values(|(verifier, light_share, commitments)| {
+            black_box(verifier.verify(&light_share, &commitments, column_size))
         });
 }
diff --git a/nomos-da/kzgrs-backend/src/common/mod.rs b/nomos-da/kzgrs-backend/src/common/mod.rs
index cbec416269..80c28ce76c 100644
--- a/nomos-da/kzgrs-backend/src/common/mod.rs
+++ b/nomos-da/kzgrs-backend/src/common/mod.rs
@@ -159,15 +159,8 @@ pub fn hash_commitment<const HASH_SIZE: usize>(commitment: &Commitment) -> [u8;
 }
 
 #[must_use]
-pub fn build_blob_id(
-    aggregated_column_commitment: &Commitment,
-    rows_commitments: &[Commitment],
-) -> [u8; 32] {
+pub fn build_blob_id(rows_commitments: &[Commitment]) -> [u8; 32] {
     let mut hasher = Sha3_256::new();
-    Digest::update(
-        &mut hasher,
-        commitment_to_bytes(aggregated_column_commitment),
-    );
     for c in rows_commitments {
         Digest::update(&mut hasher, commitment_to_bytes(c));
     }
diff --git a/nomos-da/kzgrs-backend/src/common/share.rs b/nomos-da/kzgrs-backend/src/common/share.rs
index b581384fdd..0d0518e40a 100644
--- a/nomos-da/kzgrs-backend/src/common/share.rs
+++ b/nomos-da/kzgrs-backend/src/common/share.rs
@@ -17,33 +17,18 @@ pub struct DaShare {
         serialize_with = "serialize_canonical",
         deserialize_with = "deserialize_canonical"
     )]
-    pub column_commitment: Commitment,
-    #[serde(
-        serialize_with = "serialize_canonical",
-        deserialize_with = "deserialize_canonical"
-    )]
-    pub aggregated_column_commitment: Commitment,
-    #[serde(
-        serialize_with = "serialize_canonical",
-        deserialize_with = "deserialize_canonical"
-    )]
-    pub aggregated_column_proof: Proof,
+    pub combined_column_proof: Proof,
     #[serde(
         serialize_with = "serialize_vec_canonical",
         deserialize_with = "deserialize_vec_canonical"
     )]
     pub rows_commitments: Vec<Commitment>,
-    #[serde(
-        serialize_with = "serialize_vec_canonical",
-        deserialize_with = "deserialize_vec_canonical"
-    )]
-    pub rows_proofs: Vec<Proof>,
 }
 
 impl DaShare {
     #[must_use]
     pub fn blob_id(&self) -> Vec<u8> {
-        build_blob_id(&self.aggregated_column_commitment, &self.rows_commitments).into()
+        build_blob_id(&self.rows_commitments).into()
     }
 
     #[must_use]
@@ -66,7 +51,9 @@ impl blob::Share for DaShare {
     type SharesCommitments = DaSharesCommitments;
 
     fn blob_id(&self) -> Self::BlobId {
-        build_blob_id(&self.aggregated_column_commitment, &self.rows_commitments)
+        self.blob_id()
+            .try_into()
+            .expect("Blob ID should be 32 bytes")
     }
 
     fn share_idx(&self) -> Self::ShareIndex {
@@ -78,12 +65,9 @@ impl blob::Share for DaShare {
             DaLightShare {
                 share_idx: self.share_idx,
                 column: self.column,
-                column_commitment: self.column_commitment,
-                aggregated_column_proof: self.aggregated_column_proof,
-                rows_proofs: self.rows_proofs,
+                combined_column_proof: self.combined_column_proof,
             },
             DaSharesCommitments {
-                aggregated_column_commitment: self.aggregated_column_commitment,
                 rows_commitments: self.rows_commitments,
             },
         )
@@ -96,11 +80,8 @@ impl blob::Share for DaShare {
         Self {
             column: light_share.column,
             share_idx: light_share.share_idx,
-            column_commitment: light_share.column_commitment,
-            aggregated_column_commitment: shares_commitments.aggregated_column_commitment,
-            aggregated_column_proof: light_share.aggregated_column_proof,
+            combined_column_proof: light_share.combined_column_proof,
             rows_commitments: shares_commitments.rows_commitments,
-            rows_proofs: light_share.rows_proofs,
         }
     }
 }
@@ -113,17 +94,7 @@ pub struct DaLightShare {
         serialize_with = "serialize_canonical",
         deserialize_with = "deserialize_canonical"
     )]
-    pub column_commitment: Commitment,
-    #[serde(
-        serialize_with = "serialize_canonical",
-        deserialize_with = "deserialize_canonical"
-    )]
-    pub aggregated_column_proof: Proof,
-    #[serde(
-        serialize_with = "serialize_vec_canonical",
-        deserialize_with = "deserialize_vec_canonical"
-    )]
-    pub rows_proofs: Vec<Proof>,
+    pub combined_column_proof: Proof,
 }
 
 impl blob::LightShare for DaLightShare {
@@ -136,11 +107,6 @@ impl blob::LightShare for DaLightShare {
 
 #[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
 pub struct DaSharesCommitments {
-    #[serde(
-        serialize_with = "serialize_canonical",
-        deserialize_with = "deserialize_canonical"
-    )]
-    pub aggregated_column_commitment: Commitment,
     #[serde(
         serialize_with = "serialize_vec_canonical",
         deserialize_with = "deserialize_vec_canonical"
diff --git a/nomos-da/kzgrs-backend/src/dispersal.rs b/nomos-da/kzgrs-backend/src/dispersal.rs
index 9c5b181435..d42dbf8e9e 100644
--- a/nomos-da/kzgrs-backend/src/dispersal.rs
+++ b/nomos-da/kzgrs-backend/src/dispersal.rs
@@ -131,18 +131,11 @@ mod tests {
                 share_idx: i
                     .try_into()
                     .expect("Column index shouldn't overflow the target type"),
-                column_commitment: encoded_data.column_commitments[i],
-                aggregated_column_commitment: encoded_data.aggregated_column_commitment,
-                aggregated_column_proof: encoded_data.aggregated_column_proofs[i],
+                combined_column_proof: encoded_data.combined_column_proofs[i],
                 rows_commitments: encoded_data.row_commitments.clone(),
-                rows_proofs: encoded_data
-                    .rows_proofs
-                    .iter()
-                    .map(|proofs| proofs.get(i).copied().unwrap())
-                    .collect(),
             };
             let (light_share, commitments) = da_share.into_share_and_commitments();
-            attestations.push(verifier.verify(&commitments, &light_share, domain_size));
+            attestations.push(verifier.verify(&light_share, &commitments, domain_size));
         }
         attestations
     }
diff --git a/nomos-da/kzgrs-backend/src/encoder.rs b/nomos-da/kzgrs-backend/src/encoder.rs
index db412081b6..317be3e0f1 100644
--- a/nomos-da/kzgrs-backend/src/encoder.rs
+++ b/nomos-da/kzgrs-backend/src/encoder.rs
@@ -3,18 +3,15 @@ use std::ops::Div as _;
 use ark_ff::{BigInteger as _, PrimeField as _};
 use ark_poly::EvaluationDomain as _;
 use kzgrs::{
-    bytes_to_polynomial, commit_polynomial,
-    common::bytes_to_polynomial_unchecked,
-    encode,
-    fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache},
-    Commitment, Evaluations, GlobalParameters, KzgRsError, Polynomial, PolynomialEvaluationDomain,
-    Proof, BYTES_PER_FIELD_ELEMENT,
+    bdfg_proving, commit_polynomial, common::bytes_to_polynomial_unchecked, encode,
+    fk20::Toeplitz1Cache, Commitment, Evaluations, GlobalParameters, KzgRsError, Polynomial,
+    PolynomialEvaluationDomain, Proof, BYTES_PER_FIELD_ELEMENT,
 };
 #[cfg(feature = "parallel")]
 use rayon::iter::{IntoParallelRefIterator as _, ParallelIterator as _};
 
 use crate::{
-    common::{hash_commitment, share::DaShare, Chunk, ChunksMatrix, Row},
+    common::{share::DaShare, Chunk, ChunksMatrix, Row},
     global::GLOBAL_PARAMETERS,
 };
 
@@ -53,12 +50,8 @@ pub struct EncodedData {
     pub chunked_data: ChunksMatrix,
     pub extended_data: ChunksMatrix,
     pub row_commitments: Vec<Commitment>,
-    pub rows_proofs: Vec<Vec<Proof>>,
-    pub column_commitments: Vec<Commitment>,
-    pub aggregated_column_commitment: Commitment,
-    pub aggregated_column_proofs: Vec<Proof>,
+    pub combined_column_proofs: Vec<Proof>,
 }
-
 impl EncodedData {
     /// Returns a `DaShare` for the given index.
     /// If the index is out of bounds, returns `None`.
@@ -68,15 +61,8 @@ impl EncodedData {
         Some(DaShare {
             column,
             share_idx: index.try_into().unwrap(),
-            column_commitment: self.column_commitments[index],
-            aggregated_column_commitment: self.aggregated_column_commitment,
-            aggregated_column_proof: self.aggregated_column_proofs[index],
+            combined_column_proof: self.combined_column_proofs[index],
             rows_commitments: self.row_commitments.clone(),
-            rows_proofs: self
-                .rows_proofs
-                .iter()
-                .map(|proofs| proofs.get(index).copied().unwrap())
-                .collect(),
         })
     }
 
@@ -218,63 +204,6 @@ impl DaEncoder {
         .collect()
     }
 
-    fn compute_rows_proofs(
-        global_parameters: &GlobalParameters,
-        polynomials: &[Polynomial],
-        toeplitz1cache: Option<&Toeplitz1Cache>,
-    ) -> Vec<Vec<Proof>> {
-        {
-            #[cfg(not(feature = "parallel"))]
-            {
-                polynomials.iter()
-            }
-            #[cfg(feature = "parallel")]
-            {
-                polynomials.par_iter()
-            }
-        }
-        .map(|poly| fk20_batch_generate_elements_proofs(poly, global_parameters, toeplitz1cache))
-        .collect()
-    }
-
-    #[expect(clippy::type_complexity, reason = "TODO: Address this at some point.")]
-    fn compute_kzg_column_commitments(
-        global_parameters: &GlobalParameters,
-        matrix: &ChunksMatrix,
-        polynomial_evaluation_domain: PolynomialEvaluationDomain,
-    ) -> Result<Vec<((Evaluations, Polynomial), Commitment)>, KzgRsError> {
-        Self::compute_kzg_row_commitments(
-            global_parameters,
-            &matrix.transposed(),
-            polynomial_evaluation_domain,
-        )
-    }
-
-    fn compute_aggregated_column_commitment(
-        global_parameters: &GlobalParameters,
-
-        commitments: &[Commitment],
-        polynomial_evaluation_domain: PolynomialEvaluationDomain,
-    ) -> Result<((Evaluations, Polynomial), Commitment), KzgRsError> {
-        let hashes: Vec<u8> = commitments
-            .iter()
-            .flat_map(hash_commitment::<{ DaEncoderParams::MAX_BLS12_381_ENCODING_CHUNK_SIZE }>)
-            .collect();
-        let (evals, poly) = bytes_to_polynomial::<
-            { DaEncoderParams::MAX_BLS12_381_ENCODING_CHUNK_SIZE },
-        >(hashes.as_ref(), polynomial_evaluation_domain)?;
-        let commitment = commit_polynomial(&poly, global_parameters)?;
-        Ok(((evals, poly), commitment))
-    }
-
-    fn compute_aggregated_column_proofs(
-        global_parameters: &GlobalParameters,
-        polynomial: &Polynomial,
-        toeplitz1cache: Option<&Toeplitz1Cache>,
-    ) -> Vec<Proof> {
-        fk20_batch_generate_elements_proofs(polynomial, global_parameters, toeplitz1cache)
-    }
-
     fn evals_to_chunk_matrix(evals: &[Evaluations]) -> ChunksMatrix {
         ChunksMatrix(
             evals
@@ -300,8 +229,6 @@ impl nomos_core::da::DaEncoder for DaEncoder {
         let chunked_data = self.chunkify(data);
         let row_domain = PolynomialEvaluationDomain::new(self.params.column_count)
             .expect("Domain should be able to build");
-        let column_domain = PolynomialEvaluationDomain::new(chunked_data.len())
-            .expect("Domain should be able to build");
         let (row_polynomials, row_commitments): (Vec<_>, Vec<_>) =
             Self::compute_kzg_row_commitments(global_parameters, &chunked_data, row_domain)?
                 .into_iter()
@@ -309,35 +236,20 @@ impl nomos_core::da::DaEncoder for DaEncoder {
         let (_, row_polynomials): (Vec<_>, Vec<_>) = row_polynomials.into_iter().unzip();
         let encoded_evaluations = Self::rs_encode_rows(&row_polynomials, row_domain);
         let extended_data = Self::evals_to_chunk_matrix(&encoded_evaluations);
-        let rows_proofs = Self::compute_rows_proofs(
-            global_parameters,
-            &row_polynomials,
-            self.params.toeplitz1cache.as_ref(),
-        );
-        let (_column_polynomials, column_commitments): (Vec<_>, Vec<_>) =
-            Self::compute_kzg_column_commitments(global_parameters, &extended_data, column_domain)?
-                .into_iter()
-                .unzip();
-        let ((_aggregated_evals, aggregated_polynomial), aggregated_column_commitment) =
-            Self::compute_aggregated_column_commitment(
-                global_parameters,
-                &column_commitments,
-                row_domain,
-            )?;
-        let aggregated_column_proofs = Self::compute_aggregated_column_proofs(
-            global_parameters,
-            &aggregated_polynomial,
+        let combined_column_proofs = bdfg_proving::generate_combined_proof(
+            &encoded_evaluations,
+            &row_commitments,
+            row_domain,
+            &self.params.global_parameters,
             self.params.toeplitz1cache.as_ref(),
         );
+
         Ok(EncodedData {
             data: data.to_vec(),
             chunked_data,
             extended_data,
             row_commitments,
-            rows_proofs,
-            column_commitments,
-            aggregated_column_commitment,
-            aggregated_column_proofs,
+            combined_column_proofs,
         })
     }
 }
@@ -350,13 +262,14 @@ pub mod test {
     use ark_poly::{EvaluationDomain as _, GeneralEvaluationDomain};
     use itertools::izip;
     use kzgrs::{
-        common::bytes_to_polynomial_unchecked, decode, verify_element_proof, FieldElement,
-        PolynomialEvaluationDomain, BYTES_PER_FIELD_ELEMENT,
+        common::bytes_to_polynomial_unchecked, decode, FieldElement, PolynomialEvaluationDomain,
+        BYTES_PER_FIELD_ELEMENT,
     };
     use nomos_core::da::DaEncoder as _;
     use rand::RngCore as _;
 
     use crate::{
+        common::Chunk,
         encoder::{DaEncoder, DaEncoderParams},
         global::GLOBAL_PARAMETERS,
     };
@@ -451,101 +364,6 @@ pub mod test {
         }
     }
 
-    #[test]
-    fn test_compute_row_proofs() {
-        let data = rand_data(32);
-        let domain = GeneralEvaluationDomain::new(DOMAIN_SIZE).unwrap();
-        let matrix = ENCODER.chunkify(data.as_ref());
-        let (poly_data, commitments): (Vec<_>, Vec<_>) =
-            DaEncoder::compute_kzg_row_commitments(&GLOBAL_PARAMETERS, &matrix, domain)
-                .unwrap()
-                .into_iter()
-                .unzip();
-        let (_evals, polynomials): (Vec<_>, Vec<_>) = poly_data.into_iter().unzip();
-        let extended_evaluations = DaEncoder::rs_encode_rows(&polynomials, domain);
-        let extended_matrix = DaEncoder::evals_to_chunk_matrix(&extended_evaluations);
-        let proofs = DaEncoder::compute_rows_proofs(&GLOBAL_PARAMETERS, &polynomials, None);
-
-        let checks = izip!(matrix.iter(), &commitments, &proofs);
-        for (row, commitment, proofs) in checks {
-            assert_eq!(proofs.len(), row.len() * 2);
-            for (i, chunk) in row.iter().enumerate() {
-                let element = FieldElement::from_le_bytes_mod_order(chunk.as_bytes().as_ref());
-                assert!(verify_element_proof(
-                    i,
-                    &element,
-                    commitment,
-                    &proofs[i],
-                    domain,
-                    &GLOBAL_PARAMETERS
-                ));
-            }
-        }
-        let checks = izip!(extended_matrix.iter(), &commitments, &proofs);
-        for (row, commitment, proofs) in checks {
-            assert_eq!(proofs.len(), row.len());
-            for (i, chunk) in row.iter().enumerate() {
-                let element = FieldElement::from_le_bytes_mod_order(chunk.as_bytes().as_ref());
-                assert!(verify_element_proof(
-                    i,
-                    &element,
-                    commitment,
-                    &proofs[i],
-                    domain,
-                    &GLOBAL_PARAMETERS
-                ));
-            }
-        }
-    }
-
-    #[test]
-    fn test_compute_column_kzg_commitments() {
-        let data = rand_data(32);
-        let domain = GeneralEvaluationDomain::new(DOMAIN_SIZE).unwrap();
-        let matrix = ENCODER.chunkify(data.as_ref());
-        let commitments_data =
-            DaEncoder::compute_kzg_column_commitments(&GLOBAL_PARAMETERS, &matrix, domain).unwrap();
-        assert_eq!(commitments_data.len(), matrix.columns().count());
-    }
-
-    #[test]
-    fn test_compute_aggregated_column_kzg_commitment() {
-        let data = rand_data(32);
-        let matrix = ENCODER.chunkify(data.as_ref());
-        let domain = GeneralEvaluationDomain::new(DOMAIN_SIZE).unwrap();
-        let (_, commitments): (Vec<_>, Vec<_>) =
-            DaEncoder::compute_kzg_column_commitments(&GLOBAL_PARAMETERS, &matrix, domain)
-                .unwrap()
-                .into_iter()
-                .unzip();
-        let _ = DaEncoder::compute_aggregated_column_commitment(
-            &GLOBAL_PARAMETERS,
-            &commitments,
-            domain,
-        )
-        .unwrap();
-    }
-
-    #[test]
-    fn test_compute_aggregated_column_kzg_proofs() {
-        let data = rand_data(32);
-        let matrix = ENCODER.chunkify(data.as_ref());
-        let domain = GeneralEvaluationDomain::new(DOMAIN_SIZE).unwrap();
-        let (_poly_data, commitments): (Vec<_>, Vec<_>) =
-            DaEncoder::compute_kzg_column_commitments(&GLOBAL_PARAMETERS, &matrix, domain)
-                .unwrap()
-                .into_iter()
-                .unzip();
-        let ((_evals, polynomial), _aggregated_commitment) =
-            DaEncoder::compute_aggregated_column_commitment(
-                &GLOBAL_PARAMETERS,
-                &commitments,
-                domain,
-            )
-            .unwrap();
-        DaEncoder::compute_aggregated_column_proofs(&GLOBAL_PARAMETERS, &polynomial, None);
-    }
-
     #[test]
     fn test_full_encode_flow() {
         let data = rand_data(32);
@@ -553,26 +371,24 @@ pub mod test {
         let encoding_data = ENCODER.encode(&data).unwrap();
         assert_eq!(encoding_data.data, data);
         assert_eq!(encoding_data.row_commitments.len(), 4);
-        assert_eq!(encoding_data.column_commitments.len(), 16);
-        assert_eq!(encoding_data.rows_proofs.len(), 4);
-        assert_eq!(encoding_data.rows_proofs[0].len(), 16);
-        assert_eq!(encoding_data.aggregated_column_proofs.len(), 16);
-        for (row, proofs, commitment) in izip!(
-            encoding_data.extended_data.rows(),
-            encoding_data.rows_proofs,
-            encoding_data.row_commitments
+        assert_eq!(encoding_data.combined_column_proofs.len(), 16);
+        for (column, proof, idx) in izip!(
+            encoding_data.extended_data.columns(),
+            encoding_data.combined_column_proofs,
+            0usize..
         ) {
-            for (chunk_idx, chunk) in row.iter().enumerate() {
-                let element = FieldElement::from_le_bytes_mod_order(chunk.as_bytes().as_ref());
-                assert!(verify_element_proof(
-                    chunk_idx,
-                    &element,
-                    &commitment,
-                    &proofs[chunk_idx],
-                    domain,
-                    &GLOBAL_PARAMETERS
-                ));
-            }
+            let column: Vec<FieldElement> = column
+                .iter()
+                .map(|Chunk(b)| FieldElement::from_le_bytes_mod_order(b))
+                .collect();
+            assert!(kzgrs::bdfg_proving::verify_column(
+                idx,
+                &column,
+                &encoding_data.row_commitments,
+                &proof,
+                domain,
+                &GLOBAL_PARAMETERS,
+            ));
         }
     }
 
diff --git a/nomos-da/kzgrs-backend/src/reconstruction.rs b/nomos-da/kzgrs-backend/src/reconstruction.rs
index 1e5104d124..701a4cca1d 100644
--- a/nomos-da/kzgrs-backend/src/reconstruction.rs
+++ b/nomos-da/kzgrs-backend/src/reconstruction.rs
@@ -32,7 +32,7 @@ pub fn reconstruct_without_missing_data(shares: &[DaShare]) -> Vec<u8> {
 
 #[cfg(test)]
 mod test {
-    use kzgrs::{Commitment, Proof};
+    use kzgrs::Proof;
     use nomos_core::da::DaEncoder as _;
 
     use crate::{
@@ -54,11 +54,8 @@ mod test {
             .map(|(idx, column)| DaShare {
                 column,
                 share_idx: idx as ShareIndex,
-                column_commitment: Commitment::default(),
-                aggregated_column_commitment: Commitment::default(),
-                aggregated_column_proof: Proof::default(),
+                combined_column_proof: Proof::default(),
                 rows_commitments: vec![],
-                rows_proofs: vec![],
             })
             .collect();
         assert_eq!(data, reconstruct_without_missing_data(&shares));
diff --git a/nomos-da/kzgrs-backend/src/testutils/blob.rs b/nomos-da/kzgrs-backend/src/testutils/blob.rs
index 68f2b92b05..d34007335c 100644
--- a/nomos-da/kzgrs-backend/src/testutils/blob.rs
+++ b/nomos-da/kzgrs-backend/src/testutils/blob.rs
@@ -18,21 +18,12 @@ pub fn get_da_share(data: Option<Vec<u8>>) -> DaShare {
     let columns: Vec<_> = encoded_data.extended_data.columns().collect();
 
     let index = 0;
-    let da_share = DaShare {
+    DaShare {
         column: columns[index].clone(),
         share_idx: index
             .try_into()
             .expect("Column index shouldn't overflow the target type"),
-        column_commitment: encoded_data.column_commitments[index],
-        aggregated_column_commitment: encoded_data.aggregated_column_commitment,
-        aggregated_column_proof: encoded_data.aggregated_column_proofs[index],
+        combined_column_proof: encoded_data.combined_column_proofs[index],
         rows_commitments: encoded_data.row_commitments.clone(),
-        rows_proofs: encoded_data
-            .rows_proofs
-            .iter()
-            .map(|proofs| proofs.get(index).copied().unwrap())
-            .collect(),
-    };
-
-    da_share
+    }
 }
diff --git a/nomos-da/kzgrs-backend/src/verifier.rs b/nomos-da/kzgrs-backend/src/verifier.rs
index 4f8ea4d85f..003f67bf83 100644
--- a/nomos-da/kzgrs-backend/src/verifier.rs
+++ b/nomos-da/kzgrs-backend/src/verifier.rs
@@ -1,22 +1,14 @@
+use ark_ff::PrimeField as _;
 use ark_poly::EvaluationDomain as _;
-use itertools::{izip, Itertools as _};
-use kzgrs::{
-    bytes_to_polynomial, commit_polynomial, common::field_element_from_bytes_le,
-    verify_element_proof, Commitment, GlobalParameters, PolynomialEvaluationDomain, Proof,
-    BYTES_PER_FIELD_ELEMENT,
-};
+use kzgrs::{FieldElement, GlobalParameters, PolynomialEvaluationDomain};
 
-use crate::{
-    common::{
-        hash_commitment,
-        share::{DaLightShare, DaSharesCommitments},
-        Chunk, Column,
-    },
-    encoder::DaEncoderParams,
+use crate::common::{
+    share::{DaLightShare, DaSharesCommitments},
+    Chunk,
 };
 
 pub struct DaVerifier {
-    global_parameters: GlobalParameters,
+    pub global_parameters: GlobalParameters,
 }
 
 impl DaVerifier {
@@ -25,377 +17,51 @@ impl DaVerifier {
         Self { global_parameters }
     }
 
-    fn verify_column(
-        global_parameters: &GlobalParameters,
-        column: &Column,
-        column_commitment: &Commitment,
-        aggregated_column_commitment: &Commitment,
-        aggregated_column_proof: &Proof,
-        index: usize,
-        rows_domain: PolynomialEvaluationDomain,
-    ) -> bool {
-        let column_domain =
-            PolynomialEvaluationDomain::new(column.len()).expect("Domain should be able to build");
-        // 1. compute commitment for column
-        let Ok((_, polynomial)) = bytes_to_polynomial::<BYTES_PER_FIELD_ELEMENT>(
-            column.as_bytes().as_slice(),
-            column_domain,
-        ) else {
-            return false;
-        };
-        let Ok(computed_column_commitment) = commit_polynomial(&polynomial, global_parameters)
-        else {
-            return false;
-        };
-        // 2. if computed column commitment != column commitment, fail
-        if &computed_column_commitment != column_commitment {
-            return false;
-        }
-        // 3. compute column hash
-        let commitment_hash = hash_commitment::<
-            { DaEncoderParams::MAX_BLS12_381_ENCODING_CHUNK_SIZE },
-        >(column_commitment);
-        // 4. check proof with commitment and proof over the aggregated column
-        //    commitment
-        let element = field_element_from_bytes_le(commitment_hash.as_slice());
-        verify_element_proof(
-            index,
-            &element,
-            aggregated_column_commitment,
-            aggregated_column_proof,
-            rows_domain,
-            global_parameters,
-        )
-    }
-
-    fn verify_chunk(
-        global_parameters: &GlobalParameters,
-        chunk: &Chunk,
-        commitment: &Commitment,
-        proof: &Proof,
-        index: usize,
-        domain: PolynomialEvaluationDomain,
-    ) -> bool {
-        let element = field_element_from_bytes_le(chunk.as_bytes().as_slice());
-        verify_element_proof(
-            index,
-            &element,
-            commitment,
-            proof,
-            domain,
-            global_parameters,
-        )
-    }
-
-    fn verify_chunks(
-        global_parameters: &GlobalParameters,
-        chunks: &[Chunk],
-        commitments: &[Commitment],
-        proofs: &[Proof],
-        index: usize,
-        domain: PolynomialEvaluationDomain,
-    ) -> bool {
-        if ![chunks.len(), commitments.len(), proofs.len()]
-            .iter()
-            .all_equal()
-        {
-            return false;
-        }
-        for (chunk, commitment, proof) in izip!(chunks, commitments, proofs) {
-            if !Self::verify_chunk(global_parameters, chunk, commitment, proof, index, domain) {
-                return false;
-            }
-        }
-        true
-    }
-
     #[must_use]
     pub fn verify(
         &self,
+        share: &DaLightShare,
         commitments: &DaSharesCommitments,
-        light_share: &DaLightShare,
         rows_domain_size: usize,
     ) -> bool {
+        let column: Vec<FieldElement> = share
+            .column
+            .iter()
+            .map(|Chunk(b)| FieldElement::from_le_bytes_mod_order(b))
+            .collect();
         let rows_domain = PolynomialEvaluationDomain::new(rows_domain_size)
             .expect("Domain should be able to build");
-        let share_col_idx = &light_share.share_idx;
-        let index = share_col_idx;
-
-        let is_column_verified = Self::verify_column(
-            &self.global_parameters,
-            &light_share.column,
-            &light_share.column_commitment,
-            &commitments.aggregated_column_commitment,
-            &light_share.aggregated_column_proof,
-            *index as usize,
-            rows_domain,
-        );
-        if !is_column_verified {
-            return false;
-        }
-
-        let are_chunks_verified = Self::verify_chunks(
-            &self.global_parameters,
-            light_share.column.as_ref(),
+        kzgrs::bdfg_proving::verify_column(
+            share.share_idx as usize,
+            &column,
             &commitments.rows_commitments,
-            &light_share.rows_proofs,
-            *index as usize,
+            &share.combined_column_proof,
             rows_domain,
-        );
-        if !are_chunks_verified {
-            return false;
-        }
-        true
+            &self.global_parameters,
+        )
     }
 }
 
 #[cfg(test)]
 mod test {
-    use std::sync::LazyLock;
-
-    use ark_bls12_381::Fr;
-    use ark_poly::{EvaluationDomain as _, GeneralEvaluationDomain};
-    use kzgrs::{
-        bytes_to_polynomial, commit_polynomial, generate_element_proof,
-        global_parameters_from_randomness, Commitment, GlobalParameters,
-        PolynomialEvaluationDomain, Proof, BYTES_PER_FIELD_ELEMENT,
-    };
     use nomos_core::da::{blob::Share as _, DaEncoder as _};
 
     use crate::{
-        common::{hash_commitment, share::DaShare, Chunk, Column},
-        encoder::{
-            test::{rand_data, ENCODER},
-            DaEncoderParams,
-        },
+        encoder::{test::rand_data, DaEncoder, DaEncoderParams},
         global::GLOBAL_PARAMETERS,
         verifier::DaVerifier,
     };
 
-    pub struct ColumnVerifyData {
-        pub column: Column,
-        pub column_commitment: Commitment,
-        pub aggregated_commitment: Commitment,
-        pub column_proof: Proof,
-        pub domain: GeneralEvaluationDomain<Fr>,
-    }
-
-    fn prepare_column(
-        with_new_global_params: bool,
-    ) -> Result<ColumnVerifyData, Box<dyn std::error::Error>> {
-        pub static NEW_GLOBAL_PARAMETERS: LazyLock<GlobalParameters> = LazyLock::new(|| {
-            let mut rng = rand::thread_rng();
-            global_parameters_from_randomness(&mut rng)
-        });
-
-        let global_params = if with_new_global_params {
-            &NEW_GLOBAL_PARAMETERS
-        } else {
-            &GLOBAL_PARAMETERS
-        };
-
-        let column: Column = (0..10).map(|i| Chunk(vec![i; 32])).collect();
-        let domain = GeneralEvaluationDomain::new(10).unwrap();
-        let (_, column_poly) =
-            bytes_to_polynomial::<BYTES_PER_FIELD_ELEMENT>(column.as_bytes().as_slice(), domain)?;
-        let column_commitment = commit_polynomial(&column_poly, global_params)?;
-
-        let (aggregated_evals, aggregated_poly) =
-            bytes_to_polynomial::<{ DaEncoderParams::MAX_BLS12_381_ENCODING_CHUNK_SIZE }>(
-                hash_commitment::<{ DaEncoderParams::MAX_BLS12_381_ENCODING_CHUNK_SIZE }>(
-                    &column_commitment,
-                )
-                .as_slice(),
-                domain,
-            )?;
-
-        let aggregated_commitment = commit_polynomial(&aggregated_poly, global_params)?;
-
-        let column_proof = generate_element_proof(
-            0,
-            &aggregated_poly,
-            &aggregated_evals,
-            global_params,
-            domain,
-        )?;
-
-        Ok(ColumnVerifyData {
-            column,
-            column_commitment,
-            aggregated_commitment,
-            column_proof,
-            domain,
-        })
-    }
-
-    #[test]
-    fn test_verify_column() {
-        let column_data = prepare_column(false).unwrap();
-
-        assert!(DaVerifier::verify_column(
-            &GLOBAL_PARAMETERS,
-            &column_data.column,
-            &column_data.column_commitment,
-            &column_data.aggregated_commitment,
-            &column_data.column_proof,
-            0,
-            column_data.domain
-        ));
-    }
-
-    #[test]
-    fn test_verify_column_error_cases() {
-        // Test bytes_to_polynomial() returned error
-        let column_data = prepare_column(false).unwrap();
-
-        let column2: Column = (0..10)
-            .flat_map(|i| {
-                if i % 2 == 0 {
-                    vec![Chunk(vec![i; 16])]
-                } else {
-                    vec![Chunk(vec![i; 32])]
-                }
-            })
-            .collect();
-
-        assert!(bytes_to_polynomial::<BYTES_PER_FIELD_ELEMENT>(
-            column2.as_bytes().as_slice(),
-            column_data.domain
-        )
-        .is_err());
-
-        assert!(!DaVerifier::verify_column(
-            &GLOBAL_PARAMETERS,
-            &column2,
-            &column_data.column_commitment,
-            &column_data.aggregated_commitment,
-            &column_data.column_proof,
-            0,
-            column_data.domain
-        ));
-
-        // Test alter GLOBAL_PARAMETERS so that computed_column_commitment !=
-        // column_commitment
-        let column_data2 = prepare_column(true).unwrap();
-
-        assert!(!DaVerifier::verify_column(
-            &GLOBAL_PARAMETERS,
-            &column_data2.column,
-            &column_data2.column_commitment,
-            &column_data2.aggregated_commitment,
-            &column_data2.column_proof,
-            0,
-            column_data2.domain
-        ));
-    }
-
-    #[test]
-    fn test_verify_chunks_error_cases() {
-        let encoder = &ENCODER;
-        let data = rand_data(32);
-        let domain_size = 16usize;
-        let rows_domain = PolynomialEvaluationDomain::new(domain_size).unwrap();
-        let encoded_data = encoder.encode(&data).unwrap();
-        let column = encoded_data.extended_data.columns().next().unwrap();
-        let index = 0usize;
-
-        let da_share = DaShare {
-            column,
-            share_idx: index.try_into().unwrap(),
-            column_commitment: encoded_data.column_commitments[index],
-            aggregated_column_commitment: encoded_data.aggregated_column_commitment,
-            aggregated_column_proof: encoded_data.aggregated_column_proofs[index],
-            rows_commitments: encoded_data.row_commitments.clone(),
-            rows_proofs: encoded_data
-                .rows_proofs
-                .iter()
-                .map(|proofs| proofs.get(index).copied().unwrap())
-                .collect(),
-        };
-        // Happy case
-        let chunks_verified = DaVerifier::verify_chunks(
-            &GLOBAL_PARAMETERS,
-            da_share.column.as_ref(),
-            &da_share.rows_commitments,
-            &da_share.rows_proofs,
-            index,
-            rows_domain,
-        );
-        assert!(chunks_verified);
-
-        // Chunks altered
-        let mut column_w_missing_chunk = da_share.column.as_ref().to_vec();
-        column_w_missing_chunk.pop();
-
-        let chunks_not_verified = !DaVerifier::verify_chunks(
-            &GLOBAL_PARAMETERS,
-            column_w_missing_chunk.as_ref(),
-            &da_share.rows_commitments,
-            &da_share.rows_proofs,
-            index,
-            rows_domain,
-        );
-        assert!(chunks_not_verified);
-
-        // Proofs altered
-        let mut modified_proofs = da_share.rows_proofs.clone();
-        modified_proofs.swap(0, 1);
-
-        let chunks_not_verified = !DaVerifier::verify_chunks(
-            &GLOBAL_PARAMETERS,
-            da_share.column.as_ref(),
-            &da_share.rows_commitments,
-            &modified_proofs,
-            index,
-            rows_domain,
-        );
-        assert!(chunks_not_verified);
-
-        // Commitments altered
-        let mut modified_commitments = da_share.rows_commitments.clone();
-        modified_commitments.swap(0, 1);
-
-        let chunks_not_verified = !DaVerifier::verify_chunks(
-            &GLOBAL_PARAMETERS,
-            da_share.column.as_ref(),
-            &modified_commitments,
-            &da_share.rows_proofs,
-            index,
-            rows_domain,
-        );
-        assert!(chunks_not_verified);
-    }
-
     #[test]
     fn test_verify() {
-        let encoder = &ENCODER;
+        let encoder = DaEncoder::new(DaEncoderParams::default_with(2));
         let data = rand_data(32);
-        let domain_size = 16usize;
-        let verifiers: Vec<DaVerifier> =
-            std::iter::repeat_with(|| DaVerifier::new(GLOBAL_PARAMETERS.clone()))
-                .take(16)
-                .collect();
+        let domain_size = 2usize;
+        let verifier = DaVerifier::new(GLOBAL_PARAMETERS.clone());
         let encoded_data = encoder.encode(&data).unwrap();
-        for (i, column) in encoded_data.extended_data.columns().enumerate() {
-            println!("{i}");
-            let verifier = &verifiers[i];
-            let da_share = DaShare {
-                column,
-                share_idx: i
-                    .try_into()
-                    .expect("Column index shouldn't overflow the target type"),
-                column_commitment: encoded_data.column_commitments[i],
-                aggregated_column_commitment: encoded_data.aggregated_column_commitment,
-                aggregated_column_proof: encoded_data.aggregated_column_proofs[i],
-                rows_commitments: encoded_data.row_commitments.clone(),
-                rows_proofs: encoded_data
-                    .rows_proofs
-                    .iter()
-                    .map(|proofs| proofs.get(i).copied().unwrap())
-                    .collect(),
-            };
-            let (light_share, commitments) = da_share.into_share_and_commitments();
-            assert!(verifier.verify(&commitments, &light_share, domain_size));
+        for share in &encoded_data {
+            let (light_share, commitments) = share.into_share_and_commitments();
+            assert!(verifier.verify(&light_share, &commitments, domain_size));
         }
     }
 }
diff --git a/nomos-da/kzgrs/Cargo.toml b/nomos-da/kzgrs/Cargo.toml
index 5c859c2d79..80c47a6b62 100644
--- a/nomos-da/kzgrs/Cargo.toml
+++ b/nomos-da/kzgrs/Cargo.toml
@@ -13,6 +13,7 @@ ark-ff          = { version = "0.4.2" }
 ark-poly        = { version = "0.4.2" }
 ark-poly-commit = { version = "0.4.0" }
 ark-serialize   = { version = "0.4" }
+blake2          = "0.10"
 blst            = "0.3.11"
 num-bigint      = "0.4.4"
 num-traits      = "0.2.18"
diff --git a/nomos-da/kzgrs/src/bdfg_proving.rs b/nomos-da/kzgrs/src/bdfg_proving.rs
new file mode 100644
index 0000000000..45937e1086
--- /dev/null
+++ b/nomos-da/kzgrs/src/bdfg_proving.rs
@@ -0,0 +1,248 @@
+use std::{io::Cursor, ops::Mul as _};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup as _;
+use ark_ff::{Field as _, PrimeField as _};
+use ark_poly::EvaluationDomain as _;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize as _;
+use blake2::{
+    digest::{Update as _, VariableOutput as _},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::{IntoParallelIterator as _, ParallelIterator as _};
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    // add dst for hashing
+    hasher.update(b"NOMOS_DA_V1");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
+            .expect("serialization");
+        hasher.update(&buffer.into_inner());
+    }
+    let mut buffer = [0; 32];
+    hasher
+        .finalize_variable(&mut buffer)
+        .expect("Hashing should succeed");
+    buffer.to_vec()
+}
+
+/// Computes an aggregated polynomial from a set of row polynomials (in
+/// evaluation form) and a hash of the aggregated commitments.
+///
+/// This function takes multiple polynomials (in evaluation form) and combines
+/// them into a single polynomial by applying a linear combination based on a
+/// scalar `h`, derived from the provided hash of aggregated commitments. This
+/// process allows combining multiple row polynomials into a single polynomial
+/// that commits to the entire expanded data.
+///
+/// # Arguments
+///
+/// * `polynomials` - A slice of `Evaluations` representing the row polynomials
+///   in evaluation form.
+/// * `aggregated_commitments_hash` - A byte slice representing the hash of
+///   aggregated commitments (used as the scalar `h`).
+/// * `domain` - The evaluation domain of the row polynomials (column count of
+///   the encoded data matrix).
+///
+/// # Returns
+///
+/// An `Evaluations`, polynomial in evaluation aggregated from the linear
+/// combination of the row polynomials.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The evaluation domain size does not match the size of the polynomials.
+/// - Arithmetic over the field operations fails.
+///
+/// # Parallelism
+///
+/// If the `parallel` feature is enabled, this function will perform parallel
+/// computations to improve performance during the evaluation process.
+#[must_use]
+pub fn compute_combined_polynomial(
+    polynomials: &[Evaluations],
+    aggregated_commitments_hash: &[u8],
+    domain: PolynomialEvaluationDomain,
+) -> Evaluations {
+    let h = Fr::from_le_bytes_mod_order(aggregated_commitments_hash);
+    let h_roots = compute_h_roots(h, polynomials.len());
+    let evals: Vec<Fr> = {
+        {
+            #[cfg(not(feature = "parallel"))]
+            {
+                0..domain.size()
+            }
+            #[cfg(feature = "parallel")]
+            {
+                use rayon::iter::IntoParallelIterator as _;
+                (0..domain.size()).into_par_iter()
+            }
+        }
+        .map(|column| {
+            polynomials
+                .iter()
+                .enumerate()
+                .map(|(i, poly)| poly.evals[column].mul(h_roots[i]))
+                .sum()
+        })
+        .collect()
+    };
+    Evaluations::from_vec_and_domain(evals, domain)
+}
+
+/// Generates an aggregated proof for a set of row polynomials and their
+/// commitments.
+///
+/// This function computes an aggregated polynomial by combining row polynomials
+/// (in evaluation form) using a hash derived from the commitments. Then, it
+/// interpolates the aggregated polynomial and generates proofs for its entries
+/// using the FK20 algorithm.
+///
+/// # Arguments
+///
+/// * `polynomials` - A slice of `Evaluations` representing the row polynomials
+///   in evaluation form.
+/// * `commitments` - A slice of `Commitment` corresponding to the row
+///   polynomials.
+/// * `domain` - The evaluation domain of the polynomials, defining their
+///   dimensionality.
+/// * `global_parameters` - Reference to the global KZG parameters used for
+///   proof generation.
+/// * `toeplitz1cache` - Optional cache for optimizing the Toeplitz
+///   multiplication step.
+///
+/// # Returns
+///
+/// A `Vec<Proof>` containing the aggregated proofs, one for each row.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The row polynomial serialization fails during commitment hashing.
+/// - The proof generation routines encounter invalid inputs or fail due to
+///   arithmetic operations.
+///
+/// # Parallelism
+///
+/// If the `parallel` feature is enabled, parts of the computation may utilize
+/// parallelism to enhance performance.
+#[must_use]
+pub fn generate_combined_proof(
+    polynomials: &[Evaluations],
+    commitments: &[Commitment],
+    domain: PolynomialEvaluationDomain,
+    global_parameters: &GlobalParameters,
+    toeplitz1cache: Option<&Toeplitz1Cache>,
+) -> Vec<Proof> {
+    let rows_commitments_hash = generate_row_commitments_hash(commitments);
+    let aggregated_poly_evals =
+        compute_combined_polynomial(polynomials, &rows_commitments_hash, domain);
+    let aggregated_polynomial = aggregated_poly_evals.interpolate();
+    fk20_batch_generate_elements_proofs(&aggregated_polynomial, global_parameters, toeplitz1cache)
+}
+
+/// Verifies a single column against its aggregated proof and row commitments.
+///
+/// This function aggregates the column elements using a hash derived from the
+/// row commitments, computes an aggregated commitment, and verifies the
+/// correctness of the provided proof for the column.
+///
+/// # Arguments
+///
+/// * `column_idx` - The index of the column being verified.
+/// * `column` - A slice containing the elements of the column being verified.
+/// * `row_commitments` - A slice containing the commitments for all rows.
+/// * `column_proof` - A reference to the proof corresponding to the column.
+/// * `domain` - The evaluation domain of the data matrix (defining the
+///   dimensions).
+/// * `global_parameters` - A reference to the global KZG parameters used for
+///   the verification process.
+///
+/// # Returns
+///
+/// A boolean indicating whether the proof is valid (`true`) or not (`false`)
+/// for the given column.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - Row commitment hashing fails.
+/// - Arithmetic operations (e.g., field multiplications, aggregations) fail.
+#[must_use]
+pub fn verify_column(
+    column_idx: usize,
+    column: &[Fr],
+    row_commitments: &[Commitment],
+    column_proof: &Proof,
+    domain: PolynomialEvaluationDomain,
+    global_parameters: &GlobalParameters,
+) -> bool {
+    let row_commitments_hash = generate_row_commitments_hash(row_commitments);
+    let h = Fr::from_le_bytes_mod_order(&row_commitments_hash);
+    let h_roots = compute_h_roots(h, column.len());
+    let aggregated_elements: Fr = column
+        .iter()
+        .enumerate()
+        .map(|(i, x)| x.mul(&h_roots[i]))
+        .sum();
+    let aggregated_commitments: G1Projective = row_commitments
+        .iter()
+        .enumerate()
+        .map(|(i, c)| c.0.mul(&h_roots[i]))
+        .sum();
+    let commitment = KzgCommitment(aggregated_commitments.into_affine());
+    kzg::verify_element_proof(
+        column_idx,
+        &aggregated_elements,
+        &commitment,
+        column_proof,
+        domain,
+        global_parameters,
+    )
+}
+
+fn compute_h_roots(h: Fr, size: usize) -> Vec<Fr> {
+    {
+        #[cfg(feature = "parallel")]
+        {
+            (0..size as u64).into_par_iter()
+        }
+        #[cfg(not(feature = "parallel"))]
+        {
+            0..size as u64
+        }
+    }
+    .map(|i| h.pow([i]))
+    .collect()
+}
diff --git a/nomos-da/kzgrs/src/lib.rs b/nomos-da/kzgrs/src/lib.rs
index 91d6745e58..acb467751e 100644
--- a/nomos-da/kzgrs/src/lib.rs
+++ b/nomos-da/kzgrs/src/lib.rs
@@ -2,6 +2,8 @@ pub mod common;
 pub mod fk20;
 pub mod global_parameters;
 pub mod kzg;
+
+pub mod bdfg_proving;
 pub mod rs;
 
 use std::mem;
diff --git a/nomos-da/network/core/src/protocols/dispersal/mod.rs b/nomos-da/network/core/src/protocols/dispersal/mod.rs
index 2af7d22952..7a514f4481 100644
--- a/nomos-da/network/core/src/protocols/dispersal/mod.rs
+++ b/nomos-da/network/core/src/protocols/dispersal/mod.rs
@@ -4,7 +4,7 @@ pub mod validator;
 #[cfg(test)]
 pub mod test {
     use futures::StreamExt as _;
-    use kzgrs::{Commitment, Proof};
+    use kzgrs::Proof;
     use kzgrs_backend::common::{share::DaShare, Column};
     use libp2p::{
         swarm::{dial_opts::DialOpts, SwarmEvent},
@@ -95,11 +95,8 @@ pub mod test {
                     DaShare {
                         share_idx: 0,
                         column: Column(vec![]),
-                        column_commitment: Commitment::default(),
-                        aggregated_column_commitment: Commitment::default(),
-                        aggregated_column_proof: Proof::default(),
+                        combined_column_proof: Proof::default(),
                         rows_commitments: vec![],
-                        rows_proofs: vec![],
                     },
                 ))
                 .unwrap();
diff --git a/nomos-da/network/core/src/protocols/replication/mod.rs b/nomos-da/network/core/src/protocols/replication/mod.rs
index 22f7e0ba02..5bb88709ad 100644
--- a/nomos-da/network/core/src/protocols/replication/mod.rs
+++ b/nomos-da/network/core/src/protocols/replication/mod.rs
@@ -171,6 +171,7 @@ mod test {
     }
 
     #[tokio::test]
+    #[ignore = "Create new fixtures"]
     async fn test_connects_and_receives_replication_messages() {
         let _ = tracing_subscriber::fmt()
             .with_env_filter(EnvFilter::from_default_env())
diff --git a/nomos-da/network/core/src/protocols/sampling/mod.rs b/nomos-da/network/core/src/protocols/sampling/mod.rs
index 5ea631ed5b..c2ae0b32c2 100644
--- a/nomos-da/network/core/src/protocols/sampling/mod.rs
+++ b/nomos-da/network/core/src/protocols/sampling/mod.rs
@@ -5,7 +5,7 @@ mod test {
     use std::time::Duration;
 
     use futures::StreamExt as _;
-    use kzgrs::{Commitment, Proof};
+    use kzgrs::Proof;
     use kzgrs_backend::common::{share::DaLightShare, Column};
     use libp2p::{identity::Keypair, swarm::SwarmEvent, Multiaddr, PeerId, Swarm};
     use log::debug;
@@ -47,9 +47,7 @@ mod test {
                                 share: Box::new(DaLightShare {
                                     column: Column(vec![]),
                                     share_idx: 0,
-                                    column_commitment: Commitment::default(),
-                                    aggregated_column_proof: Proof::default(),
-                                    rows_proofs: vec![],
+                                    combined_column_proof: Proof::default(),
                                 }),
                             })
                             .unwrap();
diff --git a/nomos-da/network/messages/src/sampling.rs b/nomos-da/network/messages/src/sampling.rs
index 920206bc71..8c350cd7b8 100644
--- a/nomos-da/network/messages/src/sampling.rs
+++ b/nomos-da/network/messages/src/sampling.rs
@@ -49,7 +49,6 @@ impl SampleRequest {
     }
 }
 
-#[expect(clippy::large_enum_variant, reason = "repr(C) attribute")]
 #[repr(C)]
 #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
 pub enum SampleResponse {
diff --git a/nomos-services/cryptarchia-consensus/src/lib.rs b/nomos-services/cryptarchia-consensus/src/lib.rs
index 5efe31bd0c..e2816ec71f 100644
--- a/nomos-services/cryptarchia-consensus/src/lib.rs
+++ b/nomos-services/cryptarchia-consensus/src/lib.rs
@@ -645,6 +645,14 @@ where
                             ).await;
 
                             if let Some(block) = block {
+                                // apply our own block
+                                cryptarchia = Self::process_block(
+                                    cryptarchia,
+                                    &mut leader,
+                                    block.clone(),
+                                    &relays,
+                                    &mut self.block_subscription_sender
+                                ).await;
                                 blend_adapter.blend(block).await;
                             }
                         }
diff --git a/nomos-services/data-availability/dispersal/src/backend/kzgrs.rs b/nomos-services/data-availability/dispersal/src/backend/kzgrs.rs
index 119cd877c6..31eeeaa22b 100644
--- a/nomos-services/data-availability/dispersal/src/backend/kzgrs.rs
+++ b/nomos-services/data-availability/dispersal/src/backend/kzgrs.rs
@@ -92,11 +92,8 @@ where
 
     async fn disperse(&self, encoded_data: Self::EncodedData) -> Result<(), Self::Error> {
         let adapter = self.adapter.as_ref();
-        let num_columns = encoded_data.column_commitments.len();
-        let blob_id = build_blob_id(
-            &encoded_data.aggregated_column_commitment,
-            &encoded_data.row_commitments,
-        );
+        let num_columns = encoded_data.combined_column_proofs.len();
+        let blob_id = build_blob_id(&encoded_data.row_commitments);
 
         let responses_stream = adapter.dispersal_events_stream().await?;
         for (subnetwork_id, share) in encoded_data.into_iter().enumerate() {
@@ -169,10 +166,7 @@ where
         // this is a REALLY heavy task, so we should try not to block the thread here
         let heavy_task = tokio::task::spawn_blocking(move || encoder.encode(&data));
         let encoded_data = heavy_task.await??;
-        let blob_id = build_blob_id(
-            &encoded_data.aggregated_column_commitment,
-            &encoded_data.row_commitments,
-        );
+        let blob_id = build_blob_id(&encoded_data.row_commitments);
         Ok((blob_id, encoded_data))
     }
 
diff --git a/nomos-services/data-availability/network/src/backends/mock/executor.rs b/nomos-services/data-availability/network/src/backends/mock/executor.rs
index 8cbba9807b..d96e7f9885 100644
--- a/nomos-services/data-availability/network/src/backends/mock/executor.rs
+++ b/nomos-services/data-availability/network/src/backends/mock/executor.rs
@@ -89,8 +89,7 @@ impl<RuntimeServiceId> NetworkBackend<RuntimeServiceId> for MockExecutorBackend
                 share,
                 subnetwork_id,
             } => {
-                let blob_id =
-                    build_blob_id(&share.aggregated_column_commitment, &share.rows_commitments);
+                let blob_id = build_blob_id(&share.rows_commitments);
 
                 let success_message = DisperseMessage::DispersalSuccess {
                     blob_id,
diff --git a/nomos-services/data-availability/sampling/src/backend/kzgrs.rs b/nomos-services/data-availability/sampling/src/backend/kzgrs.rs
index 5baae8d778..0c5a43003e 100644
--- a/nomos-services/data-availability/sampling/src/backend/kzgrs.rs
+++ b/nomos-services/data-availability/sampling/src/backend/kzgrs.rs
@@ -164,7 +164,7 @@ mod test {
         time::{Duration, Instant},
     };
 
-    use kzgrs::{Commitment, Proof};
+    use kzgrs::Proof;
     use kzgrs_backend::common::{share::DaShare, Column};
     use nomos_core::da::BlobId;
     use rand::{prelude::*, rngs::StdRng};
@@ -219,7 +219,6 @@ mod test {
     }
 
     #[tokio::test]
-    #[expect(clippy::too_many_lines, reason = "TODO: Test function")]
     async fn test_sampler() {
         // fictitious number of subnets
         let subnet_num: usize = 42;
@@ -233,11 +232,8 @@ mod test {
         let share = DaShare {
             share_idx: 42,
             column: Column(vec![]),
-            column_commitment: Commitment::default(),
-            aggregated_column_commitment: Commitment::default(),
-            aggregated_column_proof: Proof::default(),
+            combined_column_proof: Proof::default(),
             rows_commitments: vec![],
-            rows_proofs: vec![],
         };
         let share2 = share.clone();
         let mut share3 = share2.clone();
diff --git a/nomos-services/data-availability/sampling/src/lib.rs b/nomos-services/data-availability/sampling/src/lib.rs
index d849a28400..049013e7a7 100644
--- a/nomos-services/data-availability/sampling/src/lib.rs
+++ b/nomos-services/data-availability/sampling/src/lib.rs
@@ -195,6 +195,7 @@ where
                     if let Some(commitments) =
                         Self::request_commitments(storage_adapter, api_adapter, blob_id).await
                     {
+                        info_with_id!(blob_id, "Got commitments");
                         sampler.add_commitments(&blob_id, commitments);
                     } else {
                         error_with_id!(blob_id, "Error getting commitments");
diff --git a/nomos-services/data-availability/verifier/src/backend/kzgrs.rs b/nomos-services/data-availability/verifier/src/backend/kzgrs.rs
index 93024f0b6c..a1400cbd9e 100644
--- a/nomos-services/data-availability/verifier/src/backend/kzgrs.rs
+++ b/nomos-services/data-availability/verifier/src/backend/kzgrs.rs
@@ -56,7 +56,7 @@ impl DaVerifier for KzgrsDaVerifier {
         // TODO: Prepare the domain depending the size, if fixed, so fixed domain, if
         // not it needs to come with some metadata.
         self.verifier
-            .verify(commitments, light_share, self.domain_size)
+            .verify(light_share, commitments, self.domain_size)
             .then_some(())
             .ok_or(KzgrsDaVerifierError::VerificationError)
     }
diff --git a/tests/kzgrs/kzgrs_test_params b/tests/kzgrs/kzgrs_test_params
index ce727bccbf..0389d616a5 100644
Binary files a/tests/kzgrs/kzgrs_test_params and b/tests/kzgrs/kzgrs_test_params differ


Reviews:
user: login: thomaslavaur
body: LGTM = Let's go to the Moon!!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T18:13:26Z
user: login: danielSanchezQ
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T07:39:45Z
user: login: danielSanchezQ
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T07:40:13Z
user: login: danielSanchezQ
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T08:19:33Z
user: login: bacv
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T08:52:59Z
user: login: megonen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T06:29:03Z
user: login: danielSanchezQ
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T07:48:26Z
user: login: danielSanchezQ
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T09:40:45Z
user: login: megonen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T11:02:03Z
user: login: thomaslavaur
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T11:24:37Z
user: login: megonen
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T06:50:20Z

Review comments:
diff_hunk: @@ -0,0 +1,229 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: thomaslavaur
body: Should we add a DST to the hash function for the Fiat-Shamir transform? 
created_at: 2025-04-30T18:10:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,229 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
+            .expect("serialization");
+        hasher.update(&buffer.into_inner());
+    }
+    let mut buffer = [0; 32];
+    hasher
+        .finalize_variable(&mut buffer)
+        .expect("Hashing should succeed");
+    buffer.to_vec()
+}
+
+/// Computes an aggregated polynomial from a set of row polynomials (in
+/// evaluation form) and a hash of the aggregated commitments.
+///
+/// This function takes multiple polynomials (in evaluation form) and combines
+/// them into a single polynomial by applying a linear combination based on a
+/// scalar `h`, derived from the provided hash of aggregated commitments. This
+/// process allows combining multiple row polynomials into a single polynomial
+/// that commits to the entire expanded data.
+///
+/// # Arguments
+///
+/// * `polynomials` - A slice of `Evaluations` representing the row polynomials
+///   in evaluation form.
+/// * `aggregated_commitments_hash` - A byte slice representing the hash of
+///   aggregated commitments (used as the scalar `h`).
+/// * `domain` - The evaluation domain of the row polynomials (column count of
+///   the encoded data matrix).
+///
+/// # Returns
+///
+/// An `Evaluations`, polynomial in evaluation aggregated from the linear
+/// combination of the row polynomials.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The evaluation domain size does not match the size of the polynomials.
+/// - Arithmetic over the field operations fails.
+///
+/// # Parallelism
+///
+/// If the `parallel` feature is enabled, this function will perform parallel
+/// computations to improve performance during the evaluation process.
+#[must_use]
+pub fn compute_aggregated_polynomial(
+    polynomials: &[Evaluations],
+    aggregated_commitments_hash: &[u8],
+    domain: PolynomialEvaluationDomain,
+) -> Evaluations {
+    let h = Fr::from_le_bytes_mod_order(aggregated_commitments_hash);
+    let evals: Vec<Fr> = {
+        {
+            #[cfg(not(feature = "parallel"))]
+            {
+                0..domain.size()
+            }
+            #[cfg(feature = "parallel")]
+            {
+                use rayon::iter::IntoParallelIterator;
+                (0..domain.size()).into_par_iter()
+            }
+        }
+        .map(|column| {
+            polynomials
+                .iter()
+                .enumerate()
+                .map(|(i, poly)| poly.evals[column].mul(h.pow([i as u64])))
+                .sum()
+        })
+        .collect()
+    };
+    Evaluations::from_vec_and_domain(evals, domain)
+}
+
+/// Generates an aggregated proof for a set of row polynomials and their
+/// commitments.
+///
+/// This function computes an aggregated polynomial by combining row polynomials
+/// (in evaluation form) using a hash derived from the commitments. Then, it
+/// interpolates the aggregated polynomial and generates proofs for its entries
+/// using the FK20 algorithm.
+///
+/// # Arguments
+///
+/// * `polynomials` - A slice of `Evaluations` representing the row polynomials
+///   in evaluation form.
+/// * `commitments` - A slice of `Commitment` corresponding to the row
+///   polynomials.
+/// * `domain` - The evaluation domain of the polynomials, defining their
+///   dimensionality.
+/// * `global_parameters` - Reference to the global KZG parameters used for
+///   proof generation.
+/// * `toeplitz1cache` - Optional cache for optimizing the Toeplitz
+///   multiplication step.
+///
+/// # Returns
+///
+/// A `Vec<Proof>` containing the aggregated proofs, one for each row.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The row polynomial serialization fails during commitment hashing.
+/// - The proof generation routines encounter invalid inputs or fail due to
+///   arithmetic operations.
+///
+/// # Parallelism
+///
+/// If the `parallel` feature is enabled, parts of the computation may utilize
+/// parallelism to enhance performance.
+#[must_use]
+pub fn generate_aggregated_proof(
+    polynomials: &[Evaluations],
+    commitments: &[Commitment],
+    domain: PolynomialEvaluationDomain,
+    global_parameters: &GlobalParameters,
+    toeplitz1cache: Option<&Toeplitz1Cache>,
+) -> Vec<Proof> {
+    let rows_commitments_hash = generate_row_commitments_hash(commitments);
+    let aggregated_poly_evals =
+        compute_aggregated_polynomial(polynomials, &rows_commitments_hash, domain);
+    let aggregated_polynomial = aggregated_poly_evals.interpolate();
+    fk20_batch_generate_elements_proofs(&aggregated_polynomial, global_parameters, toeplitz1cache)
+}
+
+/// Verifies a single column against its aggregated proof and row commitments.
+///
+/// This function aggregates the column elements using a hash derived from the
+/// row commitments, computes an aggregated commitment, and verifies the
+/// correctness of the provided proof for the column.
+///
+/// # Arguments
+///
+/// * `column_idx` - The index of the column being verified.
+/// * `column` - A slice containing the elements of the column being verified.
+/// * `row_commitments` - A slice containing the commitments for all rows.
+/// * `column_proof` - A reference to the proof corresponding to the column.
+/// * `domain` - The evaluation domain of the data matrix (defining the
+///   dimensions).
+/// * `global_parameters` - A reference to the global KZG parameters used for
+///   the verification process.
+///
+/// # Returns
+///
+/// A boolean indicating whether the proof is valid (`true`) or not (`false`)
+/// for the given column.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - Row commitment hashing fails.
+/// - Arithmetic operations (e.g., field multiplications, aggregations) fail.
+#[must_use]
+pub fn verify_column(
+    column_idx: usize,
+    column: &[Fr],
+    row_commitments: &[Commitment],
+    column_proof: &Proof,
+    domain: PolynomialEvaluationDomain,
+    global_parameters: &GlobalParameters,
+) -> bool {
+    let row_commitments_hash = generate_row_commitments_hash(row_commitments);
+    let h = Fr::from_le_bytes_mod_order(&row_commitments_hash);
+    let aggregated_elements: Fr = column
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: thomaslavaur
body: We can gain a little bit of performance here by computing the powers of h before (to avoid computing it twice)
created_at: 2025-04-30T18:12:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,229 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: danielSanchezQ
body: I'm not sure how it is relate to fiat shamir actually. But it makes sense in case we upgrade protocols. Will do.
created_at: 2025-05-02T07:39:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,229 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
+            .expect("serialization");
+        hasher.update(&buffer.into_inner());
+    }
+    let mut buffer = [0; 32];
+    hasher
+        .finalize_variable(&mut buffer)
+        .expect("Hashing should succeed");
+    buffer.to_vec()
+}
+
+/// Computes an aggregated polynomial from a set of row polynomials (in
+/// evaluation form) and a hash of the aggregated commitments.
+///
+/// This function takes multiple polynomials (in evaluation form) and combines
+/// them into a single polynomial by applying a linear combination based on a
+/// scalar `h`, derived from the provided hash of aggregated commitments. This
+/// process allows combining multiple row polynomials into a single polynomial
+/// that commits to the entire expanded data.
+///
+/// # Arguments
+///
+/// * `polynomials` - A slice of `Evaluations` representing the row polynomials
+///   in evaluation form.
+/// * `aggregated_commitments_hash` - A byte slice representing the hash of
+///   aggregated commitments (used as the scalar `h`).
+/// * `domain` - The evaluation domain of the row polynomials (column count of
+///   the encoded data matrix).
+///
+/// # Returns
+///
+/// An `Evaluations`, polynomial in evaluation aggregated from the linear
+/// combination of the row polynomials.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The evaluation domain size does not match the size of the polynomials.
+/// - Arithmetic over the field operations fails.
+///
+/// # Parallelism
+///
+/// If the `parallel` feature is enabled, this function will perform parallel
+/// computations to improve performance during the evaluation process.
+#[must_use]
+pub fn compute_aggregated_polynomial(
+    polynomials: &[Evaluations],
+    aggregated_commitments_hash: &[u8],
+    domain: PolynomialEvaluationDomain,
+) -> Evaluations {
+    let h = Fr::from_le_bytes_mod_order(aggregated_commitments_hash);
+    let evals: Vec<Fr> = {
+        {
+            #[cfg(not(feature = "parallel"))]
+            {
+                0..domain.size()
+            }
+            #[cfg(feature = "parallel")]
+            {
+                use rayon::iter::IntoParallelIterator;
+                (0..domain.size()).into_par_iter()
+            }
+        }
+        .map(|column| {
+            polynomials
+                .iter()
+                .enumerate()
+                .map(|(i, poly)| poly.evals[column].mul(h.pow([i as u64])))
+                .sum()
+        })
+        .collect()
+    };
+    Evaluations::from_vec_and_domain(evals, domain)
+}
+
+/// Generates an aggregated proof for a set of row polynomials and their
+/// commitments.
+///
+/// This function computes an aggregated polynomial by combining row polynomials
+/// (in evaluation form) using a hash derived from the commitments. Then, it
+/// interpolates the aggregated polynomial and generates proofs for its entries
+/// using the FK20 algorithm.
+///
+/// # Arguments
+///
+/// * `polynomials` - A slice of `Evaluations` representing the row polynomials
+///   in evaluation form.
+/// * `commitments` - A slice of `Commitment` corresponding to the row
+///   polynomials.
+/// * `domain` - The evaluation domain of the polynomials, defining their
+///   dimensionality.
+/// * `global_parameters` - Reference to the global KZG parameters used for
+///   proof generation.
+/// * `toeplitz1cache` - Optional cache for optimizing the Toeplitz
+///   multiplication step.
+///
+/// # Returns
+///
+/// A `Vec<Proof>` containing the aggregated proofs, one for each row.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The row polynomial serialization fails during commitment hashing.
+/// - The proof generation routines encounter invalid inputs or fail due to
+///   arithmetic operations.
+///
+/// # Parallelism
+///
+/// If the `parallel` feature is enabled, parts of the computation may utilize
+/// parallelism to enhance performance.
+#[must_use]
+pub fn generate_aggregated_proof(
+    polynomials: &[Evaluations],
+    commitments: &[Commitment],
+    domain: PolynomialEvaluationDomain,
+    global_parameters: &GlobalParameters,
+    toeplitz1cache: Option<&Toeplitz1Cache>,
+) -> Vec<Proof> {
+    let rows_commitments_hash = generate_row_commitments_hash(commitments);
+    let aggregated_poly_evals =
+        compute_aggregated_polynomial(polynomials, &rows_commitments_hash, domain);
+    let aggregated_polynomial = aggregated_poly_evals.interpolate();
+    fk20_batch_generate_elements_proofs(&aggregated_polynomial, global_parameters, toeplitz1cache)
+}
+
+/// Verifies a single column against its aggregated proof and row commitments.
+///
+/// This function aggregates the column elements using a hash derived from the
+/// row commitments, computes an aggregated commitment, and verifies the
+/// correctness of the provided proof for the column.
+///
+/// # Arguments
+///
+/// * `column_idx` - The index of the column being verified.
+/// * `column` - A slice containing the elements of the column being verified.
+/// * `row_commitments` - A slice containing the commitments for all rows.
+/// * `column_proof` - A reference to the proof corresponding to the column.
+/// * `domain` - The evaluation domain of the data matrix (defining the
+///   dimensions).
+/// * `global_parameters` - A reference to the global KZG parameters used for
+///   the verification process.
+///
+/// # Returns
+///
+/// A boolean indicating whether the proof is valid (`true`) or not (`false`)
+/// for the given column.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - Row commitment hashing fails.
+/// - Arithmetic operations (e.g., field multiplications, aggregations) fail.
+#[must_use]
+pub fn verify_column(
+    column_idx: usize,
+    column: &[Fr],
+    row_commitments: &[Commitment],
+    column_proof: &Proof,
+    domain: PolynomialEvaluationDomain,
+    global_parameters: &GlobalParameters,
+) -> bool {
+    let row_commitments_hash = generate_row_commitments_hash(row_commitments);
+    let h = Fr::from_le_bytes_mod_order(&row_commitments_hash);
+    let aggregated_elements: Fr = column
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: danielSanchezQ
body: Yes, forgot to optimize that. Thanks for the remainder!
created_at: 2025-05-02T07:40:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,248 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    // add dst for hashing
+    hasher.update(b"NOMOS_DA_V1");
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: danielSanchezQ
body: This needs to be added to specs
created_at: 2025-05-02T08:19:32Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,229 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: megonen
body: Blake is used in another hashing step to convert the data to 31 bytes. Even though the hash input sizes are different in both cases, using a DST might be a good idea for edge cases (in case of 1 row commitment calculation)
created_at: 2025-05-05T06:29:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -171,6 +171,7 @@ mod test {
     }
 
     #[tokio::test]
+    #[ignore = "Create new fixtures"]
path: nomos-da/network/core/src/protocols/replication/mod.rs
user: login: danielSanchezQ
body: @CHr15F0x , can you generate the new fixtures? Do we have a place which explains how to actually do this?
created_at: 2025-05-05T07:48:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,229 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: danielSanchezQ
body: > Blake is used in another hashing step to convert the data to 31 bytes. Even though the hash input sizes are different in both cases, using a DST might be a good idea for edge cases (in case of 1 row commitment calculation)

Uhm, it was used for the column hashing, but we do not do that anymore.  I added DST already. Or where you referring to somewhere else?
created_at: 2025-05-05T09:40:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,229 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: megonen
body: Ahh right, it was being used there and no needed anymore. So in that case, Blake will only be used here. When you’re using a hash for just one purpose, there’s actually no need for a DST. What do you think? @thomaslavaur. Since it’s already implemented, we might as well leave it as is 
created_at: 2025-05-05T11:02:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,229 @@
+use std::{io::Cursor, ops::Mul};
+
+use ark_bls12_381::{Fr, G1Projective};
+use ark_ec::CurveGroup;
+use ark_ff::{Field, PrimeField};
+use ark_poly::EvaluationDomain;
+use ark_poly_commit::kzg10::Commitment as KzgCommitment;
+use ark_serialize::CanonicalSerialize;
+use blake2::{
+    digest::{Update, VariableOutput},
+    Blake2bVar,
+};
+#[cfg(feature = "parallel")]
+use rayon::iter::ParallelIterator;
+
+use super::{kzg, Commitment, Evaluations, GlobalParameters, PolynomialEvaluationDomain, Proof};
+use crate::fk20::{fk20_batch_generate_elements_proofs, Toeplitz1Cache};
+
+/// Generate a hash of the row commitments using the `Blake2bVar` hashing
+/// algorithm.
+///
+/// This function hashes a list of commitments into a constant-size (32 bytes)
+/// hash vector. The hashing process involves serializing each commitment in an
+/// uncompressed format and feeding the serialized data into the `Blake2bVar`
+/// hasher.
+///
+/// # Arguments
+///
+/// * `commitments` - A slice of commitments to be hashed.
+///
+/// # Returns
+///
+/// A `Vec<u8>` representing the 32-byte hash of the input commitments.
+///
+/// # Panics
+///
+/// This function will panic if:
+/// - The hasher fails to be constructed.
+/// - Any commitment fails to serialize properly.
+/// - The hash finalization process fails.
+#[must_use]
+pub fn generate_row_commitments_hash(commitments: &[Commitment]) -> Vec<u8> {
+    let mut hasher = Blake2bVar::new(32).expect("Hasher should be able to build");
+    for c in commitments {
+        let mut buffer = Cursor::new(Vec::new());
+        c.serialize_uncompressed(&mut buffer)
path: nomos-da/kzgrs/src/bdfg_proving.rs
user: login: thomaslavaur
body: I think we should keep it.
It's good to avoid mixing different protocols and it provides upgradeability security for future versions
created_at: 2025-05-05T11:24:37Z
author_association: CONTRIBUTOR

PR comments:
user: login: danielSanchezQ
created_at: 2025-05-05T09:54:36Z
body: @megonen @thomaslavaur , ping when the specs are in sync with this implementation. Otherwise this PR is stalled.

author_association: COLLABORATOR
user: login: megonen
created_at: 2025-05-12T06:49:04Z
body: > ping when the specs are in sync with this implementation. Otherwise this PR is stalled.

@danielSanchezQ The spec is almost ready. It’s aligned with the implementation. The only difference is in naming: since the new commitments are generated using random linear combinations, they’re referred to as combined polynomial/commitment/proof in the spec. That seemed more appropriate than aggregated.
Should we keep the term aggregated in the implementation, or maybe add a note in the spec saying that aggregated and combined are used interchangeably?
author_association: CONTRIBUTOR
user: login: danielSanchezQ
created_at: 2025-05-14T16:22:08Z
body: > > ping when the specs are in sync with this implementation. Otherwise this PR is stalled.
> 
> @danielSanchezQ The spec is almost ready. It’s aligned with the implementation. The only difference is in naming: since the new commitments are generated using random linear combinations, they’re referred to as combined polynomial/commitment/proof in the spec. That seemed more appropriate than aggregated. Should we keep the term aggregated in the implementation, or maybe add a note in the spec saying that aggregated and combined are used interchangeably?

I'll change the nomenclature to combined. This must be reflected in any other spec as well, is that the case?
author_association: COLLABORATOR
user: login: megonen
created_at: 2025-05-15T07:54:15Z
body: > I'll change the nomenclature to combined. This must be reflected in any other spec as well, is that the case?

Yeah, I will also update them 👍 
author_association: CONTRIBUTOR