From 5cb2f9223cca2c46649a779a0787c0693e780592 Mon Sep 17 00:00:00 2001 From: mgonen Date: Tue, 20 May 2025 13:45:53 +0300 Subject: [PATCH 01/18] Update DA encoding/verifier to v1.1 --- da/common.py | 7 ++- da/encoder.py | 87 ++++++++++++---------------------- da/verifier.py | 126 ++++++++++++++++++------------------------------- 3 files changed, 77 insertions(+), 143 deletions(-) diff --git a/da/common.py b/da/common.py index 48d8af3..9f8487d 100644 --- a/da/common.py +++ b/da/common.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from hashlib import sha3_256 +from hashlib import sha256 from itertools import chain, zip_longest, compress from typing import List, Generator, Self, Sequence @@ -41,9 +41,8 @@ class Bitfield(List[bool]): pass -def build_blob_id(aggregated_column_commitment: Commitment, row_commitments: Sequence[Commitment]) -> BlobId: - hasher = sha3_256() - hasher.update(bytes(aggregated_column_commitment)) +def build_blob_id(row_commitments: Sequence[Commitment]) -> BlobId: + hasher = sha256() for c in row_commitments: hasher.update(bytes(c)) return hasher.digest() diff --git a/da/encoder.py b/da/encoder.py index c837894..f976651 100644 --- a/da/encoder.py +++ b/da/encoder.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from itertools import batched, chain +from itertools import batched from typing import List, Sequence, Tuple from hashlib import blake2b @@ -10,6 +10,8 @@ from da.kzg_rs import kzg, rs from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY, BYTES_PER_FIELD_ELEMENT from da.kzg_rs.poly import Polynomial +# Domain separation tag +_DST = b"NOMOS_DA_V1" @dataclass class DAEncoderParams: @@ -23,10 +25,7 @@ class EncodedData: chunked_data: ChunksMatrix extended_matrix: ChunksMatrix row_commitments: List[Commitment] - row_proofs: List[List[Proof]] - column_commitments: List[Commitment] - aggregated_column_commitment: Commitment - aggregated_column_proofs: List[Proof] + combined_column_proofs: List[Proof] class DAEncoder: @@ -65,72 +64,44 @@ class DAEncoder: ) return ChunksMatrix(__rs_encode_row(row) for row in chunks_matrix) + def _derive_challenge(self, commitments: Sequence[Commitment]) -> BLSFieldElement: + h = blake2b(digest_size=31) + h.update(_DST) + for com in commitments: + h.update(bytes(com)) + digest31 = h.digest() + # pad to 32 bytes + return BLSFieldElement.from_bytes(digest31 + b'\x00') @staticmethod - def _compute_rows_proofs( - chunks_matrix: ChunksMatrix, - polynomials: Sequence[Polynomial], - row_commitments: Sequence[Commitment] - ) -> List[List[Proof]]: - proofs = [] - for row, poly, commitment in zip(chunks_matrix, polynomials, row_commitments): - proofs.append( - [ - kzg.generate_element_proof(i, poly, GLOBAL_PARAMETERS, ROOTS_OF_UNITY) - for i in range(len(row)) - ] - ) - return proofs + def _combined_polynomial( + polys: Sequence[Polynomial], h: BLSFieldElement + ) -> Polynomial: + combined = Polynomial.zero() + power = BLSFieldElement(1) + for poly in polys: + combined = combined + poly * int(power) + power = power * h + return combined - def _compute_column_kzg_commitments(self, chunks_matrix: ChunksMatrix) -> List[Tuple[Polynomial, Commitment]]: - return self._compute_row_kzg_commitments(chunks_matrix.transposed()) - - @staticmethod - def _compute_aggregated_column_commitment( - column_commitments: Sequence[Commitment] - ) -> Tuple[Polynomial, Commitment]: - data = bytes(chain.from_iterable( - DAEncoder.hash_commitment_blake2b31(commitment) - for commitment in column_commitments - )) - return kzg.bytes_to_commitment(data, GLOBAL_PARAMETERS) - - @staticmethod - def _compute_aggregated_column_proofs( - polynomial: Polynomial, - column_commitments: Sequence[Commitment], - ) -> List[Proof]: + def _compute_combined_column_proofs(self, combined_poly: Polynomial) -> List[Proof]: + total_cols = self.params.column_count * 2 return [ - kzg.generate_element_proof(i, polynomial, GLOBAL_PARAMETERS, ROOTS_OF_UNITY) - for i in range(len(column_commitments)) + kzg.generate_element_proof(j, combined_poly, GLOBAL_PARAMETERS, ROOTS_OF_UNITY) + for j in range(total_cols) ] def encode(self, data: bytes) -> EncodedData: chunks_matrix = self._chunkify_data(data) row_polynomials, row_commitments = zip(*self._compute_row_kzg_commitments(chunks_matrix)) extended_matrix = self._rs_encode_rows(chunks_matrix) - row_proofs = self._compute_rows_proofs(extended_matrix, row_polynomials, row_commitments) - column_polynomials, column_commitments = zip(*self._compute_column_kzg_commitments(extended_matrix)) - aggregated_column_polynomial, aggregated_column_commitment = ( - self._compute_aggregated_column_commitment(column_commitments) - ) - aggregated_column_proofs = self._compute_aggregated_column_proofs( - aggregated_column_polynomial, column_commitments - ) + h = self._derive_challenge(row_commitments) + combined_poly = self._combined_polynomial(row_polynomials, h) + combined_column_proofs = self._compute_combined_column_proofs(combined_poly) result = EncodedData( data, chunks_matrix, extended_matrix, row_commitments, - row_proofs, - column_commitments, - aggregated_column_commitment, - aggregated_column_proofs + combined_column_proofs ) return result - - @staticmethod - def hash_commitment_blake2b31(commitment: Commitment) -> bytes: - return ( - # digest size must be 31 bytes as we cannot encode 32 without risking overflowing the BLS_MODULUS - int.from_bytes(blake2b(bytes(commitment), digest_size=31).digest()) - ).to_bytes(32, byteorder="big") # rewrap into 32 padded bytes for the field elements, EC library dependant \ No newline at end of file diff --git a/da/verifier.py b/da/verifier.py index 6d1dacf..1135f7f 100644 --- a/da/verifier.py +++ b/da/verifier.py @@ -1,7 +1,6 @@ from dataclasses import dataclass -from hashlib import sha3_256 from typing import List, Sequence, Set - +from hashlib import blake2b from eth2spec.deneb.mainnet import BLSFieldElement from eth2spec.eip7594.mainnet import ( KZGCommitment as Commitment, @@ -9,98 +8,63 @@ from eth2spec.eip7594.mainnet import ( ) import da.common -from da.common import Column, Chunk, BlobId -from da.encoder import DAEncoder +from da.common import Column, Chunk, BlobId, build_blob_id from da.kzg_rs import kzg from da.kzg_rs.common import ROOTS_OF_UNITY, GLOBAL_PARAMETERS, BLS_MODULUS +# Domain separation tag +_DST = b"NOMOS_DA_V1" @dataclass -class DABlob: +class DAShare: column: Column column_idx: int - column_commitment: Commitment - aggregated_column_commitment: Commitment - aggregated_column_proof: Proof - rows_commitments: List[Commitment] - rows_proofs: List[Proof] - - def blob_id(self) -> bytes: - return da.common.build_blob_id(self.aggregated_column_commitment, self.rows_commitments) - - def column_id(self) -> bytes: - return sha3_256(self.column.as_bytes()).digest() + combined_column_proof: Proof + row_commitments: List[Commitment] + def blob_id(self) -> BlobId: + return build_blob_id(self.row_commitments) class DAVerifier: - @staticmethod - def _verify_column( - column: Column, - column_idx: int, - column_commitment: Commitment, - aggregated_column_commitment: Commitment, - aggregated_column_proof: Proof, - ) -> bool: - # 1. compute commitment for column - _, computed_column_commitment = kzg.bytes_to_commitment(column.as_bytes(), GLOBAL_PARAMETERS) - # 2. If computed column commitment != column commitment, fail - if column_commitment != computed_column_commitment: - return False - # 3. compute column hash - column_hash = DAEncoder.hash_commitment_blake2b31(column_commitment) - # 4. Check proof with commitment and proof over the aggregated column commitment - chunk = BLSFieldElement.from_bytes(column_hash) - return kzg.verify_element_proof( - chunk, aggregated_column_commitment, aggregated_column_proof, column_idx, ROOTS_OF_UNITY - ) @staticmethod - def _verify_chunk(chunk: Chunk, commitment: Commitment, proof: Proof, index: int) -> bool: - chunk = BLSFieldElement(int.from_bytes(bytes(chunk)) % BLS_MODULUS) - return kzg.verify_element_proof(chunk, commitment, proof, index, ROOTS_OF_UNITY) - - @staticmethod - def _verify_chunks( - chunks: Sequence[Chunk], - commitments: Sequence[Commitment], - proofs: Sequence[Proof], - index: int - ) -> bool: - if not (len(chunks) == len(commitments) == len(proofs)): - return False - for chunk, commitment, proof in zip(chunks, commitments, proofs): - if not DAVerifier._verify_chunk(chunk, commitment, proof, index): - return False - return True - - def verify(self, blob: DABlob) -> bool: + def _derive_challenge(row_commitments: List[Commitment]) -> BLSFieldElement: """ - Verify the integrity of the given blob. - - This function must be idempotent. The implementer should ensure that - repeated verification attempts do not result in inconsistent states. - - Args: - blob (DABlob): The blob to verify. - - Returns: - bool: True if the blob is verified successfully, False otherwise. + Derive a Fiat–Shamir challenge scalar h from the row commitments: + h = BLAKE2b-31( DST || bytes(com1) || bytes(com2) || ... ) """ - is_column_verified = DAVerifier._verify_column( - blob.column, - blob.column_idx, - blob.column_commitment, - blob.aggregated_column_commitment, - blob.aggregated_column_proof, - ) - if not is_column_verified: - return False + h = blake2b(digest_size=31) + h.update(_DST) + for com in row_commitments: + h.update(bytes(com)) + digest31 = h.digest() # 31 bytes + # pad to 32 bytes for field element conversion + padded = digest31 + b'\x00' + return BLSFieldElement.from_bytes(padded) - are_chunks_verified = DAVerifier._verify_chunks( - blob.column, blob.rows_commitments, blob.rows_proofs, blob.column_idx - ) - if not are_chunks_verified: - return False + @staticmethod + def verify(blob: DAShare) -> bool: + """ + Verifies that blob.column.chunks at index blob.column_idx is consistent + with the row commitments and the single column proof. - # Ensure idempotency: Implementers should define how to avoid redundant verification. - return True + Returns True if verification succeeds, False otherwise. + """ + # 1. Derive challenge + h = DAVerifier._derive_challenge(blob.row_commitments) + # 2. Reconstruct combined commitment: com_C = sum_{i=0..l-1} h^i * row_commitments[i] + com_C = blob.row_commitments[0] + power = h + for com in blob.row_commitments[1:]: + com_C = com_C + com * int(power) + power = power * h + + # 3. Compute combined evaluation v = sum_{i=0..l-1} (h^i * column_data[i]) + v = BLSFieldElement(0) + power = BLSFieldElement(1) + for chunk in blob.column.chunks: + x = BLSFieldElement(int.from_bytes(bytes(chunk), byteorder="big")) + v = v + x * power + power = power * h + # 4. Verify the single KZG proof for evaluation at point w^{column_idx} + return kzg.verify_element_proof(v,com_C,blob.combined_column_proof,blob.column_idx,ROOTS_OF_UNITY) From 82e5b4da7c9177301640d98973f3fb27ea1e8c90 Mon Sep 17 00:00:00 2001 From: mgonen Date: Wed, 21 May 2025 13:17:05 +0300 Subject: [PATCH 02/18] Update DA encoding/verifier to v1.1 --- da/common.py | 4 +-- da/encoder.py | 4 +-- da/test_encoder.py | 70 +++++++++++++++------------------------------ da/test_verifier.py | 35 +++++------------------ 4 files changed, 34 insertions(+), 79 deletions(-) diff --git a/da/common.py b/da/common.py index 9f8487d..5f3fa3c 100644 --- a/da/common.py +++ b/da/common.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from hashlib import sha256 +from hashlib import blake2b from itertools import chain, zip_longest, compress from typing import List, Generator, Self, Sequence @@ -42,7 +42,7 @@ class Bitfield(List[bool]): def build_blob_id(row_commitments: Sequence[Commitment]) -> BlobId: - hasher = sha256() + hasher = blake2b(digest_size=32) for c in row_commitments: hasher.update(bytes(c)) return hasher.digest() diff --git a/da/encoder.py b/da/encoder.py index f976651..9721c66 100644 --- a/da/encoder.py +++ b/da/encoder.py @@ -7,7 +7,7 @@ from eth2spec.eip7594.mainnet import KZGCommitment as Commitment, KZGProof as Pr from da.common import ChunksMatrix, Chunk, Row from da.kzg_rs import kzg, rs -from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY, BYTES_PER_FIELD_ELEMENT +from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY, BYTES_PER_FIELD_ELEMENT, BLS_MODULUS from da.kzg_rs.poly import Polynomial # Domain separation tag @@ -76,7 +76,7 @@ class DAEncoder: def _combined_polynomial( polys: Sequence[Polynomial], h: BLSFieldElement ) -> Polynomial: - combined = Polynomial.zero() + combined = Polynomial([0], BLS_MODULUS) power = BLSFieldElement(1) for poly in polys: combined = combined + poly * int(power) diff --git a/da/test_encoder.py b/da/test_encoder.py index fdcd353..5d33378 100644 --- a/da/test_encoder.py +++ b/da/test_encoder.py @@ -6,6 +6,7 @@ from eth2spec.deneb.mainnet import bytes_to_bls_field from da import encoder from da.encoder import DAEncoderParams, DAEncoder +from da.verifier import DAVerifier from eth2spec.eip7594.mainnet import BYTES_PER_FIELD_ELEMENT, BLSFieldElement from da.kzg_rs.common import BLS_MODULUS, ROOTS_OF_UNITY @@ -33,24 +34,26 @@ class TestEncoder(TestCase): self.assertEqual(columns_len, column_count) chunks_size = (len(data) // encoder_params.bytes_per_chunk) // encoder_params.column_count self.assertEqual(len(encoded_data.row_commitments), chunks_size) - self.assertEqual(len(encoded_data.row_proofs), chunks_size) - self.assertEqual(len(encoded_data.row_proofs[0]), column_count) - self.assertIsNotNone(encoded_data.aggregated_column_commitment) - self.assertEqual(len(encoded_data.aggregated_column_proofs), columns_len) + self.assertEqual(len(encoded_data.combined_column_proofs), columns_len) # verify rows - for row, proofs, commitment in zip(encoded_data.extended_matrix, encoded_data.row_proofs, encoded_data.row_commitments): - for i, (chunk, proof) in enumerate(zip(row, proofs)): - self.assertTrue( - kzg.verify_element_proof(bytes_to_bls_field(chunk), commitment, proof, i, ROOTS_OF_UNITY) - ) + h = DAVerifier._derive_challenge(encoded_data.row_commitments) + com_C = encoded_data.row_commitments[0] + power = h + for com in encoded_data.row_commitments[1:]: + com_C = com_C + com * int(power) + power = power * h - # verify column aggregation - for i, (column, proof) in enumerate(zip(encoded_data.extended_matrix.columns, encoded_data.aggregated_column_proofs)): - data = DAEncoder.hash_commitment_blake2b31(commitment) + for i, (column, proof) in enumerate(zip(encoded_data.extended_matrix.columns, encoded_data.combined_column_proofs)): + v = BLSFieldElement(0) + power = BLSFieldElement(1) + for chunk in column.chunks: + x = BLSFieldElement(int.from_bytes(bytes(chunk), byteorder="big")) + v = v + x * power + power = power * h kzg.verify_element_proof( - bytes_to_bls_field(data), - encoded_data.aggregated_column_commitment, + v, + com_C, proof, i, ROOTS_OF_UNITY @@ -84,41 +87,14 @@ class TestEncoder(TestCase): poly_2 = rs.decode(r2, ROOTS_OF_UNITY, len(poly_1)) self.assertEqual(poly_1, poly_2) - def test_compute_rows_proofs(self): - chunks_matrix = self.encoder._chunkify_data(self.data) - polynomials, commitments = zip(*self.encoder._compute_row_kzg_commitments(chunks_matrix)) - extended_chunks_matrix = self.encoder._rs_encode_rows(chunks_matrix) - original_proofs = self.encoder._compute_rows_proofs(chunks_matrix, polynomials, commitments) - extended_proofs = self.encoder._compute_rows_proofs(extended_chunks_matrix, polynomials, commitments) - # check original sized matrix - for row, poly, commitment, proofs in zip(chunks_matrix, polynomials, commitments, original_proofs): - self.assertEqual(len(proofs), len(row)) - for i, chunk in enumerate(row): - self.assertTrue(kzg.verify_element_proof(BLSFieldElement.from_bytes(chunk), commitment, proofs[i], i, ROOTS_OF_UNITY)) - # check extended matrix - for row, poly, commitment, proofs in zip(extended_chunks_matrix, polynomials, commitments, extended_proofs): - for i, chunk in enumerate(row): - self.assertTrue(kzg.verify_element_proof(BLSFieldElement.from_bytes(chunk), commitment, proofs[i], i, ROOTS_OF_UNITY)) - def test_compute_column_kzg_commitments(self): + def test_generate_combined_column_proofs(self): chunks_matrix = self.encoder._chunkify_data(self.data) - polynomials, commitments = zip(*self.encoder._compute_column_kzg_commitments(chunks_matrix)) - self.assertEqual(len(commitments), len(chunks_matrix[0])) - self.assertEqual(len(polynomials), len(chunks_matrix[0])) - - def test_generate_aggregated_column_commitments(self): - chunks_matrix = self.encoder._chunkify_data(self.data) - _, column_commitments = zip(*self.encoder._compute_column_kzg_commitments(chunks_matrix)) - poly, commitment = self.encoder._compute_aggregated_column_commitment(column_commitments) - self.assertIsNotNone(poly) - self.assertIsNotNone(commitment) - - def test_generate_aggregated_column_proofs(self): - chunks_matrix = self.encoder._chunkify_data(self.data) - _, column_commitments = zip(*self.encoder._compute_column_kzg_commitments(chunks_matrix)) - poly, _ = self.encoder._compute_aggregated_column_commitment(column_commitments) - proofs = self.encoder._compute_aggregated_column_proofs(poly, column_commitments) - self.assertEqual(len(proofs), len(column_commitments)) + row_polynomials, row_commitments = zip(*self.encoder._compute_row_kzg_commitments(chunks_matrix)) + h = self.encoder._derive_challenge(row_commitments) + combined_poly = self.encoder._combined_polynomial(row_polynomials, h) + proofs = self.encoder._compute_combined_column_proofs(combined_poly) + self.assertEqual(len(proofs), len(row_commitments)) def test_encode(self): from random import randbytes diff --git a/da/test_verifier.py b/da/test_verifier.py index 101895b..4c2bcc7 100644 --- a/da/test_verifier.py +++ b/da/test_verifier.py @@ -5,7 +5,7 @@ from da.encoder import DAEncoder from da.kzg_rs import kzg from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY from da.test_encoder import TestEncoder -from da.verifier import DAVerifier, DABlob +from da.verifier import DAVerifier, DAShare class TestVerifier(TestCase): @@ -13,18 +13,6 @@ class TestVerifier(TestCase): def setUp(self): self.verifier = DAVerifier() - def test_verify_column(self): - column = Column(int.to_bytes(i, length=32) for i in range(8)) - _, column_commitment = kzg.bytes_to_commitment(column.as_bytes(), GLOBAL_PARAMETERS) - aggregated_poly, aggregated_column_commitment = kzg.bytes_to_commitment( - DAEncoder.hash_commitment_blake2b31(column_commitment), GLOBAL_PARAMETERS - ) - aggregated_proof = kzg.generate_element_proof(0, aggregated_poly, GLOBAL_PARAMETERS, ROOTS_OF_UNITY) - self.assertTrue( - self.verifier._verify_column( - column, 0, column_commitment, aggregated_column_commitment, aggregated_proof, - ) - ) def test_verify(self): _ = TestEncoder() @@ -32,14 +20,11 @@ class TestVerifier(TestCase): encoded_data = _.encoder.encode(_.data) for i, column in enumerate(encoded_data.chunked_data.columns): verifier = DAVerifier() - da_blob = DABlob( + da_blob = DAShare( Column(column), i, - encoded_data.column_commitments[i], - encoded_data.aggregated_column_commitment, - encoded_data.aggregated_column_proofs[i], + encoded_data.combined_column_proofs[i], encoded_data.row_commitments, - [row[i] for row in encoded_data.row_proofs], ) self.assertIsNotNone(verifier.verify(da_blob)) @@ -49,24 +34,18 @@ class TestVerifier(TestCase): encoded_data = _.encoder.encode(_.data) columns = enumerate(encoded_data.chunked_data.columns) i, column = next(columns) - da_blob = DABlob( + da_blob = DAShare( Column(column), i, - encoded_data.column_commitments[i], - encoded_data.aggregated_column_commitment, - encoded_data.aggregated_column_proofs[i], + encoded_data.combined_column_proofs[i], encoded_data.row_commitments, - [row[i] for row in encoded_data.row_proofs], ) self.assertIsNotNone(self.verifier.verify(da_blob)) for i, column in columns: - da_blob = DABlob( + da_blob = DAShare( Column(column), i, - encoded_data.column_commitments[i], - encoded_data.aggregated_column_commitment, - encoded_data.aggregated_column_proofs[i], + encoded_data.combined_column_proofs[i], encoded_data.row_commitments, - [row[i] for row in encoded_data.row_proofs], ) self.assertTrue(self.verifier.verify(da_blob)) From cce427f16152342fc2c503a442ad69ba518d0108 Mon Sep 17 00:00:00 2001 From: megonen <146561843+megonen@users.noreply.github.com> Date: Thu, 22 May 2025 11:07:46 +0300 Subject: [PATCH 03/18] Update da/test_encoder.py Co-authored-by: Daniel Sanchez --- da/test_encoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/da/test_encoder.py b/da/test_encoder.py index 5d33378..13cb006 100644 --- a/da/test_encoder.py +++ b/da/test_encoder.py @@ -38,7 +38,7 @@ class TestEncoder(TestCase): # verify rows h = DAVerifier._derive_challenge(encoded_data.row_commitments) - com_C = encoded_data.row_commitments[0] + combined_commitment = encoded_data.row_commitments[0] power = h for com in encoded_data.row_commitments[1:]: com_C = com_C + com * int(power) From fdce70a9bb064079da37684b374aea0e33d1e4cb Mon Sep 17 00:00:00 2001 From: mgonen Date: Thu, 22 May 2025 13:41:45 +0300 Subject: [PATCH 04/18] Update DA encoding/verifier to v1.1 --- da/common.py | 18 ++++++++++++++++++ da/encoder.py | 19 ++++++------------- da/kzg_rs/poly.py | 18 +++++++++++++----- da/test_encoder.py | 23 ++++++++++++----------- da/verifier.py | 28 ++++++---------------------- 5 files changed, 55 insertions(+), 51 deletions(-) diff --git a/da/common.py b/da/common.py index 5f3fa3c..4194ae0 100644 --- a/da/common.py +++ b/da/common.py @@ -4,6 +4,7 @@ from itertools import chain, zip_longest, compress from typing import List, Generator, Self, Sequence from eth2spec.eip7594.mainnet import Bytes32, KZGCommitment as Commitment +from eth2spec.eip7594.mainnet import BLSFieldElement from py_ecc.bls import G2ProofOfPossession @@ -47,6 +48,23 @@ def build_blob_id(row_commitments: Sequence[Commitment]) -> BlobId: hasher.update(bytes(c)) return hasher.digest() + +def derive_challenge(row_commitments: List[Commitment]) -> BLSFieldElement: + """ + Derive a Fiat–Shamir challenge scalar h from the row commitments: + h = BLAKE2b-31( DST || bytes(com1) || bytes(com2) || ... ) + """ + _DST = b"NOMOS_DA_V1" + h = blake2b(digest_size=31) + h.update(_DST) + for com in row_commitments: + h.update(bytes(com)) + digest31 = h.digest() # 31 bytes + # pad to 32 bytes for field element conversion + padded = digest31 + b'\x00' + return BLSFieldElement.from_bytes(padded) + + class NomosDaG2ProofOfPossession(G2ProofOfPossession): # Domain specific tag for Nomos DA protocol DST = b"NOMOS_DA_AVAIL" diff --git a/da/encoder.py b/da/encoder.py index 9721c66..2b9668f 100644 --- a/da/encoder.py +++ b/da/encoder.py @@ -5,7 +5,7 @@ from hashlib import blake2b from eth2spec.eip7594.mainnet import KZGCommitment as Commitment, KZGProof as Proof, BLSFieldElement -from da.common import ChunksMatrix, Chunk, Row +from da.common import ChunksMatrix, Chunk, Row, derive_challenge from da.kzg_rs import kzg, rs from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY, BYTES_PER_FIELD_ELEMENT, BLS_MODULUS from da.kzg_rs.poly import Polynomial @@ -64,23 +64,16 @@ class DAEncoder: ) return ChunksMatrix(__rs_encode_row(row) for row in chunks_matrix) - def _derive_challenge(self, commitments: Sequence[Commitment]) -> BLSFieldElement: - h = blake2b(digest_size=31) - h.update(_DST) - for com in commitments: - h.update(bytes(com)) - digest31 = h.digest() - # pad to 32 bytes - return BLSFieldElement.from_bytes(digest31 + b'\x00') @staticmethod def _combined_polynomial( polys: Sequence[Polynomial], h: BLSFieldElement ) -> Polynomial: combined = Polynomial([0], BLS_MODULUS) - power = BLSFieldElement(1) + h_int = int(h) # raw integer challenge + int_pow = 1 for poly in polys: - combined = combined + poly * int(power) - power = power * h + combined = combined + (poly * int_pow) + int_pow = (int_pow * h_int) % BLS_MODULUS return combined def _compute_combined_column_proofs(self, combined_poly: Polynomial) -> List[Proof]: @@ -94,7 +87,7 @@ class DAEncoder: chunks_matrix = self._chunkify_data(data) row_polynomials, row_commitments = zip(*self._compute_row_kzg_commitments(chunks_matrix)) extended_matrix = self._rs_encode_rows(chunks_matrix) - h = self._derive_challenge(row_commitments) + h = derive_challenge(row_commitments) combined_poly = self._combined_polynomial(row_polynomials, h) combined_column_proofs = self._compute_combined_column_proofs(combined_poly) result = EncodedData( diff --git a/da/kzg_rs/poly.py b/da/kzg_rs/poly.py index 311b097..d5a7f47 100644 --- a/da/kzg_rs/poly.py +++ b/da/kzg_rs/poly.py @@ -49,11 +49,19 @@ class Polynomial[T]: ) def __mul__(self, other): - result = [0] * (len(self.coefficients) + len(other.coefficients) - 1) - for i in range(len(self.coefficients)): - for j in range(len(other.coefficients)): - result[i + j] = (result[i + j] + self.coefficients[i] * other.coefficients[j]) % self.modulus - return Polynomial(result, self.modulus) + if isinstance(other, int): + return Polynomial( + [(a * other) % self.modulus for a in self.coefficients], + self.modulus + ) + elif isinstance(other, Polynomial): + result = [0] * (len(self.coefficients) + len(other.coefficients) - 1) + for i in range(len(self.coefficients)): + for j in range(len(other.coefficients)): + result[i + j] = (result[i + j] + self.coefficients[i] * other.coefficients[j]) % self.modulus + return Polynomial(result, self.modulus) + else: + raise TypeError(f"Unsupported type for multiplication: {type(other)}") def divide(self, other): if not isinstance(other, Polynomial): diff --git a/da/test_encoder.py b/da/test_encoder.py index 5d33378..6c030b6 100644 --- a/da/test_encoder.py +++ b/da/test_encoder.py @@ -1,10 +1,11 @@ from itertools import chain, batched from random import randrange, randbytes from unittest import TestCase - +from eth2spec.utils import bls from eth2spec.deneb.mainnet import bytes_to_bls_field from da import encoder +from da.common import derive_challenge from da.encoder import DAEncoderParams, DAEncoder from da.verifier import DAVerifier from eth2spec.eip7594.mainnet import BYTES_PER_FIELD_ELEMENT, BLSFieldElement @@ -37,23 +38,23 @@ class TestEncoder(TestCase): self.assertEqual(len(encoded_data.combined_column_proofs), columns_len) # verify rows - h = DAVerifier._derive_challenge(encoded_data.row_commitments) - com_C = encoded_data.row_commitments[0] + h = derive_challenge(encoded_data.row_commitments) + combined_commitment = encoded_data.row_commitments[0] power = h for com in encoded_data.row_commitments[1:]: - com_C = com_C + com * int(power) + combined_commitment = bls.add(combined_commitment,bls.multiply(com, power)) power = power * h for i, (column, proof) in enumerate(zip(encoded_data.extended_matrix.columns, encoded_data.combined_column_proofs)): - v = BLSFieldElement(0) + combined_eval_point = BLSFieldElement(0) power = BLSFieldElement(1) - for chunk in column.chunks: - x = BLSFieldElement(int.from_bytes(bytes(chunk), byteorder="big")) - v = v + x * power + for data in column.chunks: + chunk = BLSFieldElement(int.from_bytes(bytes(data), byteorder="big")) + combined_eval_point = combined_eval_point + chunk * power power = power * h kzg.verify_element_proof( - v, - com_C, + combined_eval_point, + combined_commitment, proof, i, ROOTS_OF_UNITY @@ -91,7 +92,7 @@ class TestEncoder(TestCase): def test_generate_combined_column_proofs(self): chunks_matrix = self.encoder._chunkify_data(self.data) row_polynomials, row_commitments = zip(*self.encoder._compute_row_kzg_commitments(chunks_matrix)) - h = self.encoder._derive_challenge(row_commitments) + h = derive_challenge(row_commitments) combined_poly = self.encoder._combined_polynomial(row_polynomials, h) proofs = self.encoder._compute_combined_column_proofs(combined_poly) self.assertEqual(len(proofs), len(row_commitments)) diff --git a/da/verifier.py b/da/verifier.py index 1135f7f..0a468cb 100644 --- a/da/verifier.py +++ b/da/verifier.py @@ -8,7 +8,7 @@ from eth2spec.eip7594.mainnet import ( ) import da.common -from da.common import Column, Chunk, BlobId, build_blob_id +from da.common import Column, Chunk, BlobId, build_blob_id, derive_challenge from da.kzg_rs import kzg from da.kzg_rs.common import ROOTS_OF_UNITY, GLOBAL_PARAMETERS, BLS_MODULUS @@ -26,22 +26,6 @@ class DAShare: return build_blob_id(self.row_commitments) class DAVerifier: - - @staticmethod - def _derive_challenge(row_commitments: List[Commitment]) -> BLSFieldElement: - """ - Derive a Fiat–Shamir challenge scalar h from the row commitments: - h = BLAKE2b-31( DST || bytes(com1) || bytes(com2) || ... ) - """ - h = blake2b(digest_size=31) - h.update(_DST) - for com in row_commitments: - h.update(bytes(com)) - digest31 = h.digest() # 31 bytes - # pad to 32 bytes for field element conversion - padded = digest31 + b'\x00' - return BLSFieldElement.from_bytes(padded) - @staticmethod def verify(blob: DAShare) -> bool: """ @@ -51,12 +35,12 @@ class DAVerifier: Returns True if verification succeeds, False otherwise. """ # 1. Derive challenge - h = DAVerifier._derive_challenge(blob.row_commitments) - # 2. Reconstruct combined commitment: com_C = sum_{i=0..l-1} h^i * row_commitments[i] - com_C = blob.row_commitments[0] + h = derive_challenge(blob.row_commitments) + # 2. Reconstruct combined commitment: combined_commitment = sum_{i=0..l-1} h^i * row_commitments[i] + combined_commitment = blob.row_commitments[0] power = h for com in blob.row_commitments[1:]: - com_C = com_C + com * int(power) + combined_commitment = combined_commitment + com * int(power) power = power * h # 3. Compute combined evaluation v = sum_{i=0..l-1} (h^i * column_data[i]) @@ -67,4 +51,4 @@ class DAVerifier: v = v + x * power power = power * h # 4. Verify the single KZG proof for evaluation at point w^{column_idx} - return kzg.verify_element_proof(v,com_C,blob.combined_column_proof,blob.column_idx,ROOTS_OF_UNITY) + return kzg.verify_element_proof(v,combined_commitment,blob.combined_column_proof,blob.column_idx,ROOTS_OF_UNITY) From 2a37c7f0ccff5796115d398796e5b6b1ca12f3c2 Mon Sep 17 00:00:00 2001 From: mgonen Date: Thu, 22 May 2025 14:19:34 +0300 Subject: [PATCH 05/18] Update DA encoding/verifier to v1.1 --- da/verifier.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/da/verifier.py b/da/verifier.py index 0a468cb..3dc86ab 100644 --- a/da/verifier.py +++ b/da/verifier.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from typing import List, Sequence, Set from hashlib import blake2b +from eth2spec.utils import bls from eth2spec.deneb.mainnet import BLSFieldElement from eth2spec.eip7594.mainnet import ( KZGCommitment as Commitment, @@ -40,15 +41,15 @@ class DAVerifier: combined_commitment = blob.row_commitments[0] power = h for com in blob.row_commitments[1:]: - combined_commitment = combined_commitment + com * int(power) + combined_commitment = bls.add(combined_commitment,bls.multiply(com, power)) power = power * h # 3. Compute combined evaluation v = sum_{i=0..l-1} (h^i * column_data[i]) - v = BLSFieldElement(0) + combined_eval_point = BLSFieldElement(0) power = BLSFieldElement(1) - for chunk in blob.column.chunks: - x = BLSFieldElement(int.from_bytes(bytes(chunk), byteorder="big")) - v = v + x * power + for data in blob.column.chunks: + chunk = BLSFieldElement(int.from_bytes(bytes(data), byteorder="big")) + combined_eval_point = combined_eval_point + chunk * power power = power * h # 4. Verify the single KZG proof for evaluation at point w^{column_idx} - return kzg.verify_element_proof(v,combined_commitment,blob.combined_column_proof,blob.column_idx,ROOTS_OF_UNITY) + return kzg.verify_element_proof(combined_eval_point,combined_commitment,blob.combined_column_proof,blob.column_idx,ROOTS_OF_UNITY) From 2eaff7af0e3adcf2e19479e2f13ae508db32b426 Mon Sep 17 00:00:00 2001 From: megonen <146561843+megonen@users.noreply.github.com> Date: Tue, 27 May 2025 09:44:31 +0300 Subject: [PATCH 06/18] Update da/test_encoder.py Co-authored-by: Daniel Sanchez --- da/test_encoder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/da/test_encoder.py b/da/test_encoder.py index 6c030b6..37370fc 100644 --- a/da/test_encoder.py +++ b/da/test_encoder.py @@ -41,8 +41,8 @@ class TestEncoder(TestCase): h = derive_challenge(encoded_data.row_commitments) combined_commitment = encoded_data.row_commitments[0] power = h - for com in encoded_data.row_commitments[1:]: - combined_commitment = bls.add(combined_commitment,bls.multiply(com, power)) + for commitment in encoded_data.row_commitments[1:]: + combined_commitment = bls.add(combined_commitment,bls.multiply(commitment, power)) power = power * h for i, (column, proof) in enumerate(zip(encoded_data.extended_matrix.columns, encoded_data.combined_column_proofs)): From c2d52724087e63e530614c0f17e02f1527e4557f Mon Sep 17 00:00:00 2001 From: megonen <146561843+megonen@users.noreply.github.com> Date: Tue, 27 May 2025 09:44:40 +0300 Subject: [PATCH 07/18] Update da/verifier.py Co-authored-by: Daniel Sanchez --- da/verifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/da/verifier.py b/da/verifier.py index 3dc86ab..3fefc6a 100644 --- a/da/verifier.py +++ b/da/verifier.py @@ -40,8 +40,8 @@ class DAVerifier: # 2. Reconstruct combined commitment: combined_commitment = sum_{i=0..l-1} h^i * row_commitments[i] combined_commitment = blob.row_commitments[0] power = h - for com in blob.row_commitments[1:]: - combined_commitment = bls.add(combined_commitment,bls.multiply(com, power)) + for commitment in blob.row_commitments[1:]: + combined_commitment = bls.add(combined_commitment,bls.multiply(commitment, power)) power = power * h # 3. Compute combined evaluation v = sum_{i=0..l-1} (h^i * column_data[i]) From f735f514e60cf102f40db34664b2f8287eabe9c2 Mon Sep 17 00:00:00 2001 From: mgonen Date: Wed, 4 Jun 2025 13:58:18 +0300 Subject: [PATCH 08/18] Changes were done based on the feedback --- da/encoder.py | 12 ++++++------ da/kzg_rs/poly.py | 21 +++++++-------------- da/test_encoder.py | 29 ++++++++++++++++------------- da/test_verifier.py | 6 +++--- da/verifier.py | 28 +++++++++++++++------------- 5 files changed, 47 insertions(+), 49 deletions(-) diff --git a/da/encoder.py b/da/encoder.py index 2b9668f..8e57aab 100644 --- a/da/encoder.py +++ b/da/encoder.py @@ -68,19 +68,19 @@ class DAEncoder: def _combined_polynomial( polys: Sequence[Polynomial], h: BLSFieldElement ) -> Polynomial: - combined = Polynomial([0], BLS_MODULUS) + combined_polynomial = polys[0] h_int = int(h) # raw integer challenge int_pow = 1 - for poly in polys: - combined = combined + (poly * int_pow) + for poly in polys[1:]: int_pow = (int_pow * h_int) % BLS_MODULUS - return combined + combined_polynomial = combined_polynomial + Polynomial({int_pow * coeff for coeff in poly},BLS_MODULUS) + return combined_polynomial def _compute_combined_column_proofs(self, combined_poly: Polynomial) -> List[Proof]: total_cols = self.params.column_count * 2 return [ - kzg.generate_element_proof(j, combined_poly, GLOBAL_PARAMETERS, ROOTS_OF_UNITY) - for j in range(total_cols) + kzg.generate_element_proof(i, combined_poly, GLOBAL_PARAMETERS, ROOTS_OF_UNITY) + for i in range(total_cols) ] def encode(self, data: bytes) -> EncodedData: diff --git a/da/kzg_rs/poly.py b/da/kzg_rs/poly.py index d5a7f47..870ff42 100644 --- a/da/kzg_rs/poly.py +++ b/da/kzg_rs/poly.py @@ -1,3 +1,4 @@ + from itertools import zip_longest from typing import List, Sequence, Self @@ -49,19 +50,11 @@ class Polynomial[T]: ) def __mul__(self, other): - if isinstance(other, int): - return Polynomial( - [(a * other) % self.modulus for a in self.coefficients], - self.modulus - ) - elif isinstance(other, Polynomial): - result = [0] * (len(self.coefficients) + len(other.coefficients) - 1) - for i in range(len(self.coefficients)): - for j in range(len(other.coefficients)): - result[i + j] = (result[i + j] + self.coefficients[i] * other.coefficients[j]) % self.modulus - return Polynomial(result, self.modulus) - else: - raise TypeError(f"Unsupported type for multiplication: {type(other)}") + result = [0] * (len(self.coefficients) + len(other.coefficients) - 1) + for i in range(len(self.coefficients)): + for j in range(len(other.coefficients)): + result[i + j] = (result[i + j] + self.coefficients[i] * other.coefficients[j]) % self.modulus + return Polynomial(result, self.modulus) def divide(self, other): if not isinstance(other, Polynomial): @@ -116,4 +109,4 @@ class Polynomial[T]: )) % self.modulus def evaluation_form(self) -> List[T]: - return [self.eval(ROOTS_OF_UNITY[i]) for i in range(len(self))] \ No newline at end of file + return [self.eval(ROOTS_OF_UNITY[i]) for i in range(len(self))] diff --git a/da/test_encoder.py b/da/test_encoder.py index 37370fc..56eac1a 100644 --- a/da/test_encoder.py +++ b/da/test_encoder.py @@ -39,19 +39,21 @@ class TestEncoder(TestCase): # verify rows h = derive_challenge(encoded_data.row_commitments) - combined_commitment = encoded_data.row_commitments[0] - power = h + combined_commitment = bls.bytes48_to_G1(encoded_data.row_commitments[0]) + power = int(h) % BLS_MODULUS for commitment in encoded_data.row_commitments[1:]: - combined_commitment = bls.add(combined_commitment,bls.multiply(commitment, power)) - power = power * h - + commitment=bls.bytes48_to_G1(commitment) + combined_commitment = bls.add(combined_commitment,bls.multiply(commitment,power)) + power = (power * int(h)) % BLS_MODULUS + combined_commitment = bls.G1_to_bytes48(combined_commitment) for i, (column, proof) in enumerate(zip(encoded_data.extended_matrix.columns, encoded_data.combined_column_proofs)): - combined_eval_point = BLSFieldElement(0) - power = BLSFieldElement(1) - for data in column.chunks: - chunk = BLSFieldElement(int.from_bytes(bytes(data), byteorder="big")) - combined_eval_point = combined_eval_point + chunk * power - power = power * h + combined_eval_int = 0 + power_int = 1 + h_int = int(h) + for data in column: + chunk_int = int.from_bytes(bytes(data), byteorder="big") + combined_eval_point = (combined_eval_int + chunk_int * power_int) % BLS_MODULUS + power_int = (power_int * h_int) % BLS_MODULUS kzg.verify_element_proof( combined_eval_point, combined_commitment, @@ -95,7 +97,8 @@ class TestEncoder(TestCase): h = derive_challenge(row_commitments) combined_poly = self.encoder._combined_polynomial(row_polynomials, h) proofs = self.encoder._compute_combined_column_proofs(combined_poly) - self.assertEqual(len(proofs), len(row_commitments)) + expected_extended_columns = self.params.column_count * 2 + self.assertEqual(len(proofs), expected_extended_columns) def test_encode(self): from random import randbytes @@ -111,4 +114,4 @@ class TestEncoder(TestCase): for _ in range(size*encoder_params.column_count) ) ) - self.assert_encoding(encoder_params, data) + self.assert_encoding(encoder_params, data) \ No newline at end of file diff --git a/da/test_verifier.py b/da/test_verifier.py index 4c2bcc7..6e390f8 100644 --- a/da/test_verifier.py +++ b/da/test_verifier.py @@ -18,7 +18,7 @@ class TestVerifier(TestCase): _ = TestEncoder() _.setUp() encoded_data = _.encoder.encode(_.data) - for i, column in enumerate(encoded_data.chunked_data.columns): + for i, column in enumerate(encoded_data.extended_matrix.columns): verifier = DAVerifier() da_blob = DAShare( Column(column), @@ -32,7 +32,7 @@ class TestVerifier(TestCase): _ = TestEncoder() _.setUp() encoded_data = _.encoder.encode(_.data) - columns = enumerate(encoded_data.chunked_data.columns) + columns = enumerate(encoded_data.extended_matrix.columns) i, column = next(columns) da_blob = DAShare( Column(column), @@ -48,4 +48,4 @@ class TestVerifier(TestCase): encoded_data.combined_column_proofs[i], encoded_data.row_commitments, ) - self.assertTrue(self.verifier.verify(da_blob)) + self.assertIsNotNone(self.verifier.verify(da_blob)) diff --git a/da/verifier.py b/da/verifier.py index 3fefc6a..42d7ce6 100644 --- a/da/verifier.py +++ b/da/verifier.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from typing import List, Sequence, Set -from hashlib import blake2b from eth2spec.utils import bls from eth2spec.deneb.mainnet import BLSFieldElement from eth2spec.eip7594.mainnet import ( @@ -30,26 +29,29 @@ class DAVerifier: @staticmethod def verify(blob: DAShare) -> bool: """ - Verifies that blob.column.chunks at index blob.column_idx is consistent - with the row commitments and the single column proof. + Verifies that blob.column at index blob.column_idx is consistent + with the row commitments and the combined column proof. Returns True if verification succeeds, False otherwise. """ # 1. Derive challenge h = derive_challenge(blob.row_commitments) # 2. Reconstruct combined commitment: combined_commitment = sum_{i=0..l-1} h^i * row_commitments[i] - combined_commitment = blob.row_commitments[0] - power = h + combined_commitment = bls.bytes48_to_G1(blob.row_commitments[0]) + power = int(h) % BLS_MODULUS for commitment in blob.row_commitments[1:]: + commitment = bls.bytes48_to_G1(commitment) combined_commitment = bls.add(combined_commitment,bls.multiply(commitment, power)) - power = power * h - + power = (power * int(h)) % BLS_MODULUS + combined_commitment = bls.G1_to_bytes48(combined_commitment) # 3. Compute combined evaluation v = sum_{i=0..l-1} (h^i * column_data[i]) - combined_eval_point = BLSFieldElement(0) - power = BLSFieldElement(1) - for data in blob.column.chunks: - chunk = BLSFieldElement(int.from_bytes(bytes(data), byteorder="big")) - combined_eval_point = combined_eval_point + chunk * power - power = power * h + combined_eval_int = 0 + power_int = 1 + h_int = int(h) % BLS_MODULUS + for chunk in blob.column: + chunk_int = int.from_bytes(bytes(chunk), byteorder="big") + combined_eval_int = (combined_eval_int + chunk_int * power_int) % BLS_MODULUS + power_int = (power_int * h_int) % BLS_MODULUS + combined_eval_point = BLSFieldElement(combined_eval_int) # 4. Verify the single KZG proof for evaluation at point w^{column_idx} return kzg.verify_element_proof(combined_eval_point,combined_commitment,blob.combined_column_proof,blob.column_idx,ROOTS_OF_UNITY) From 6e026f3a7e14757e1cb71da1a98effd36f0c3712 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:14:04 +0200 Subject: [PATCH 09/18] Refactor code and tests --- da/common.py | 18 ------------ da/encoder.py | 18 ++---------- da/kzg_rs/bdfg_proving.py | 61 +++++++++++++++++++++++++++++++++++++++ da/test_encoder.py | 46 ++++++++++------------------- da/verifier.py | 36 +++++++++-------------- 5 files changed, 94 insertions(+), 85 deletions(-) create mode 100644 da/kzg_rs/bdfg_proving.py diff --git a/da/common.py b/da/common.py index 4194ae0..e40605d 100644 --- a/da/common.py +++ b/da/common.py @@ -4,10 +4,8 @@ from itertools import chain, zip_longest, compress from typing import List, Generator, Self, Sequence from eth2spec.eip7594.mainnet import Bytes32, KZGCommitment as Commitment -from eth2spec.eip7594.mainnet import BLSFieldElement from py_ecc.bls import G2ProofOfPossession - type BlobId = bytes class NodeId(Bytes32): @@ -49,22 +47,6 @@ def build_blob_id(row_commitments: Sequence[Commitment]) -> BlobId: return hasher.digest() -def derive_challenge(row_commitments: List[Commitment]) -> BLSFieldElement: - """ - Derive a Fiat–Shamir challenge scalar h from the row commitments: - h = BLAKE2b-31( DST || bytes(com1) || bytes(com2) || ... ) - """ - _DST = b"NOMOS_DA_V1" - h = blake2b(digest_size=31) - h.update(_DST) - for com in row_commitments: - h.update(bytes(com)) - digest31 = h.digest() # 31 bytes - # pad to 32 bytes for field element conversion - padded = digest31 + b'\x00' - return BLSFieldElement.from_bytes(padded) - - class NomosDaG2ProofOfPossession(G2ProofOfPossession): # Domain specific tag for Nomos DA protocol DST = b"NOMOS_DA_AVAIL" diff --git a/da/encoder.py b/da/encoder.py index 8e57aab..2c63c49 100644 --- a/da/encoder.py +++ b/da/encoder.py @@ -1,7 +1,6 @@ from dataclasses import dataclass from itertools import batched -from typing import List, Sequence, Tuple -from hashlib import blake2b +from typing import List, Tuple from eth2spec.eip7594.mainnet import KZGCommitment as Commitment, KZGProof as Proof, BLSFieldElement @@ -9,6 +8,7 @@ from da.common import ChunksMatrix, Chunk, Row, derive_challenge from da.kzg_rs import kzg, rs from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY, BYTES_PER_FIELD_ELEMENT, BLS_MODULUS from da.kzg_rs.poly import Polynomial +from da.kzg_rs.bdfg_proving import compute_combined_polynomial # Domain separation tag _DST = b"NOMOS_DA_V1" @@ -64,18 +64,6 @@ class DAEncoder: ) return ChunksMatrix(__rs_encode_row(row) for row in chunks_matrix) - @staticmethod - def _combined_polynomial( - polys: Sequence[Polynomial], h: BLSFieldElement - ) -> Polynomial: - combined_polynomial = polys[0] - h_int = int(h) # raw integer challenge - int_pow = 1 - for poly in polys[1:]: - int_pow = (int_pow * h_int) % BLS_MODULUS - combined_polynomial = combined_polynomial + Polynomial({int_pow * coeff for coeff in poly},BLS_MODULUS) - return combined_polynomial - def _compute_combined_column_proofs(self, combined_poly: Polynomial) -> List[Proof]: total_cols = self.params.column_count * 2 return [ @@ -88,7 +76,7 @@ class DAEncoder: row_polynomials, row_commitments = zip(*self._compute_row_kzg_commitments(chunks_matrix)) extended_matrix = self._rs_encode_rows(chunks_matrix) h = derive_challenge(row_commitments) - combined_poly = self._combined_polynomial(row_polynomials, h) + combined_poly = compute_combined_polynomial(row_polynomials, h) combined_column_proofs = self._compute_combined_column_proofs(combined_poly) result = EncodedData( data, diff --git a/da/kzg_rs/bdfg_proving.py b/da/kzg_rs/bdfg_proving.py new file mode 100644 index 0000000..d2a85e5 --- /dev/null +++ b/da/kzg_rs/bdfg_proving.py @@ -0,0 +1,61 @@ +from hashlib import blake2b +from typing import List, Sequence + +from da.common import Chunk +from da.kzg_rs.common import BLS_MODULUS + +from eth2spec.eip7594.mainnet import BLSFieldElement, KZGCommitment as Commitment +from eth2spec.utils import bls + +from da.kzg_rs.poly import Polynomial + + +def derive_challenge(row_commitments: List[Commitment]) -> BLSFieldElement: + """ + Derive a Fiat–Shamir challenge scalar h from the row commitments: + h = BLAKE2b-31( DST || bytes(com1) || bytes(com2) || ... ) + """ + _DST = b"NOMOS_DA_V1" + h = blake2b(digest_size=31) + h.update(_DST) + for com in row_commitments: + h.update(bytes(com)) + digest31 = h.digest() # 31 bytes + # pad to 32 bytes for field element conversion + padded = digest31 + b'\x00' + return BLSFieldElement.from_bytes(padded) + + +def combine_commitments(row_commitments: List[Commitment], h: BLSFieldElement) -> Commitment: + combined_commitment = bls.bytes48_to_G1(row_commitments[0]) + power = int(h) % BLS_MODULUS + for commitment in row_commitments[1:]: + commitment = bls.bytes48_to_G1(commitment) + combined_commitment = bls.add(combined_commitment, bls.multiply(commitment, power)) + power = (power * int(h)) % BLS_MODULUS + return bls.G1_to_bytes48(combined_commitment) + + +def compute_combined_polynomial( + polys: Sequence[Polynomial], h: BLSFieldElement + ) -> Polynomial: + combined_polynomial = polys[0] + h_int = int(h) # raw integer challenge + int_pow = 1 + for poly in polys[1:]: + int_pow = (int_pow * h_int) % BLS_MODULUS + combined_polynomial = combined_polynomial + Polynomial([int_pow * coeff for coeff in poly], BLS_MODULUS) + return combined_polynomial + +def compute_combined_evaluation( + evals: Sequence[Chunk], + h: BLSFieldElement +) -> BLSFieldElement: + combined_eval_int = 0 + power_int = 1 + h_int = int(h) % BLS_MODULUS + for chunk in evals: + chunk_int = int.from_bytes(bytes(chunk), byteorder="big") + combined_eval_int = (combined_eval_int + chunk_int * power_int) % BLS_MODULUS + power_int = (power_int * h_int) % BLS_MODULUS + return BLSFieldElement(combined_eval_int) \ No newline at end of file diff --git a/da/test_encoder.py b/da/test_encoder.py index 56eac1a..8f834a8 100644 --- a/da/test_encoder.py +++ b/da/test_encoder.py @@ -1,16 +1,15 @@ from itertools import chain, batched -from random import randrange, randbytes +from random import randbytes from unittest import TestCase -from eth2spec.utils import bls -from eth2spec.deneb.mainnet import bytes_to_bls_field from da import encoder -from da.common import derive_challenge +from da.common import Column +from kzg_rs.bdfg_proving import derive_challenge from da.encoder import DAEncoderParams, DAEncoder -from da.verifier import DAVerifier +from da.verifier import DAVerifier, DAShare from eth2spec.eip7594.mainnet import BYTES_PER_FIELD_ELEMENT, BLSFieldElement -from da.kzg_rs.common import BLS_MODULUS, ROOTS_OF_UNITY +from da.kzg_rs.common import ROOTS_OF_UNITY from da.kzg_rs import kzg, rs @@ -35,32 +34,19 @@ class TestEncoder(TestCase): self.assertEqual(columns_len, column_count) chunks_size = (len(data) // encoder_params.bytes_per_chunk) // encoder_params.column_count self.assertEqual(len(encoded_data.row_commitments), chunks_size) - self.assertEqual(len(encoded_data.combined_column_proofs), columns_len) - # verify rows - h = derive_challenge(encoded_data.row_commitments) - combined_commitment = bls.bytes48_to_G1(encoded_data.row_commitments[0]) - power = int(h) % BLS_MODULUS - for commitment in encoded_data.row_commitments[1:]: - commitment=bls.bytes48_to_G1(commitment) - combined_commitment = bls.add(combined_commitment,bls.multiply(commitment,power)) - power = (power * int(h)) % BLS_MODULUS - combined_commitment = bls.G1_to_bytes48(combined_commitment) - for i, (column, proof) in enumerate(zip(encoded_data.extended_matrix.columns, encoded_data.combined_column_proofs)): - combined_eval_int = 0 - power_int = 1 - h_int = int(h) - for data in column: - chunk_int = int.from_bytes(bytes(data), byteorder="big") - combined_eval_point = (combined_eval_int + chunk_int * power_int) % BLS_MODULUS - power_int = (power_int * h_int) % BLS_MODULUS - kzg.verify_element_proof( - combined_eval_point, - combined_commitment, - proof, - i, - ROOTS_OF_UNITY + verifier = DAVerifier() + # verify columns + for idx, (column, column_proof) in enumerate(zip(encoded_data.extended_matrix.columns, encoded_data.combined_column_proofs)): + share = DAShare( + column=Column(column), + column_idx=idx, + combined_column_proof=column_proof, + row_commitments=encoded_data.row_commitments ) + verifier.verify(share) + + def test_chunkify(self): encoder_settings = DAEncoderParams(column_count=2, bytes_per_chunk=31) diff --git a/da/verifier.py b/da/verifier.py index 42d7ce6..d492535 100644 --- a/da/verifier.py +++ b/da/verifier.py @@ -1,16 +1,15 @@ from dataclasses import dataclass -from typing import List, Sequence, Set -from eth2spec.utils import bls -from eth2spec.deneb.mainnet import BLSFieldElement +from typing import List + from eth2spec.eip7594.mainnet import ( KZGCommitment as Commitment, KZGProof as Proof, ) -import da.common -from da.common import Column, Chunk, BlobId, build_blob_id, derive_challenge +from da.common import Column, BlobId, build_blob_id from da.kzg_rs import kzg -from da.kzg_rs.common import ROOTS_OF_UNITY, GLOBAL_PARAMETERS, BLS_MODULUS +from da.kzg_rs.bdfg_proving import combine_commitments, derive_challenge, compute_combined_evaluation +from da.kzg_rs.common import ROOTS_OF_UNITY # Domain separation tag _DST = b"NOMOS_DA_V1" @@ -37,21 +36,14 @@ class DAVerifier: # 1. Derive challenge h = derive_challenge(blob.row_commitments) # 2. Reconstruct combined commitment: combined_commitment = sum_{i=0..l-1} h^i * row_commitments[i] - combined_commitment = bls.bytes48_to_G1(blob.row_commitments[0]) - power = int(h) % BLS_MODULUS - for commitment in blob.row_commitments[1:]: - commitment = bls.bytes48_to_G1(commitment) - combined_commitment = bls.add(combined_commitment,bls.multiply(commitment, power)) - power = (power * int(h)) % BLS_MODULUS - combined_commitment = bls.G1_to_bytes48(combined_commitment) + combined_commitment = combine_commitments(blob.row_commitments, h) # 3. Compute combined evaluation v = sum_{i=0..l-1} (h^i * column_data[i]) - combined_eval_int = 0 - power_int = 1 - h_int = int(h) % BLS_MODULUS - for chunk in blob.column: - chunk_int = int.from_bytes(bytes(chunk), byteorder="big") - combined_eval_int = (combined_eval_int + chunk_int * power_int) % BLS_MODULUS - power_int = (power_int * h_int) % BLS_MODULUS - combined_eval_point = BLSFieldElement(combined_eval_int) + combined_eval_point = compute_combined_evaluation(blob.column, h) # 4. Verify the single KZG proof for evaluation at point w^{column_idx} - return kzg.verify_element_proof(combined_eval_point,combined_commitment,blob.combined_column_proof,blob.column_idx,ROOTS_OF_UNITY) + return kzg.verify_element_proof( + combined_eval_point, + combined_commitment, + blob.combined_column_proof, + blob.column_idx, + ROOTS_OF_UNITY + ) From 8b800b08009720744478a78eebb2d4b5d0f06de6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:19:22 +0200 Subject: [PATCH 10/18] Add missing DaShare --- da/api/test_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/da/api/test_flow.py b/da/api/test_flow.py index 14f61ed..a52c883 100644 --- a/da/api/test_flow.py +++ b/da/api/test_flow.py @@ -2,6 +2,7 @@ from unittest import TestCase from collections import defaultdict from da.api.common import * +from da.verifier import DAShare @dataclass @@ -25,7 +26,7 @@ class MockStore(BlobStore): self.app_id_store[metadata.app_id][metadata.index] = cert_id # Implements `get_multiple` method from BlobStore abstract class. - def get_multiple(self, app_id, indexes) -> List[Optional[DABlob]]: + def get_multiple(self, app_id, indexes) -> List[Optional[DAShare]]: return [ self.blob_store.get(self.app_id_store[app_id].get(i), None) if self.app_id_store[app_id].get(i) else None for i in indexes ] From 545ba05f3447ff311398999e5b7976957cfbcf9d Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:21:04 +0200 Subject: [PATCH 11/18] fix wrong import on encoder --- da/encoder.py | 5 +++-- da/test_dispersal.py | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/da/encoder.py b/da/encoder.py index 2c63c49..4a0dff4 100644 --- a/da/encoder.py +++ b/da/encoder.py @@ -4,9 +4,10 @@ from typing import List, Tuple from eth2spec.eip7594.mainnet import KZGCommitment as Commitment, KZGProof as Proof, BLSFieldElement -from da.common import ChunksMatrix, Chunk, Row, derive_challenge +from da.common import ChunksMatrix, Chunk, Row from da.kzg_rs import kzg, rs -from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY, BYTES_PER_FIELD_ELEMENT, BLS_MODULUS +from da.kzg_rs.bdfg_proving import derive_challenge +from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY, BYTES_PER_FIELD_ELEMENT from da.kzg_rs.poly import Polynomial from da.kzg_rs.bdfg_proving import compute_combined_polynomial diff --git a/da/test_dispersal.py b/da/test_dispersal.py index a90e14b..9945925 100644 --- a/da/test_dispersal.py +++ b/da/test_dispersal.py @@ -1,10 +1,9 @@ -from hashlib import sha3_256 from unittest import TestCase from da.encoder import DAEncoderParams, DAEncoder from da.test_encoder import TestEncoder -from da.verifier import DAVerifier, DABlob -from da.common import NodeId, NomosDaG2ProofOfPossession as bls_pop +from da.verifier import DAVerifier, DAShare +from da.common import NodeId from da.dispersal import Dispersal, DispersalSettings @@ -27,7 +26,7 @@ class TestDispersal(TestCase): # mock send and await method with local verifiers verifiers_res = [] - def __send_and_await_response(_, blob: DABlob): + def __send_and_await_response(_, blob: DAShare): verifier = DAVerifier() res = verifier.verify(blob) verifiers_res.append(res) From 7c1b80d33753c1867d1ff313de67de52560a4bf9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:22:00 +0200 Subject: [PATCH 12/18] More wrong imports --- da/api/common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/da/api/common.py b/da/api/common.py index 79c5776..5dbd588 100644 --- a/da/api/common.py +++ b/da/api/common.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from typing import Optional, List, Sequence from da.common import BlobId -from da.verifier import DABlob +from da.verifier import DAShare @dataclass @@ -31,7 +31,7 @@ class BlobStore(ABC): pass @abstractmethod - def get_multiple(self, app_id: bytes, indexes: Sequence[int]) -> List[Optional[DABlob]]: + def get_multiple(self, app_id: bytes, indexes: Sequence[int]) -> List[Optional[DAShare]]: pass @@ -48,7 +48,7 @@ class DAApi: """ self.store.add(id, metadata) - def read(self, app_id, indexes) -> List[Optional[DABlob]]: + def read(self, app_id, indexes) -> List[Optional[DAShare]]: """ Read method should accept only `app_id` and a list of indexes. The returned list of blobs should be ordered in the same sequence as `indexes` in a request. From 85afceb5ad0ab679f357b64a568620c46c4223b7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:25:31 +0200 Subject: [PATCH 13/18] More wrong imports --- da/test_encoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/da/test_encoder.py b/da/test_encoder.py index 8f834a8..e487e87 100644 --- a/da/test_encoder.py +++ b/da/test_encoder.py @@ -4,7 +4,7 @@ from unittest import TestCase from da import encoder from da.common import Column -from kzg_rs.bdfg_proving import derive_challenge +from da.kzg_rs.bdfg_proving import derive_challenge from da.encoder import DAEncoderParams, DAEncoder from da.verifier import DAVerifier, DAShare from eth2spec.eip7594.mainnet import BYTES_PER_FIELD_ELEMENT, BLSFieldElement From c3172895f4b45748cdf9a6e2398da7d153804235 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:26:48 +0200 Subject: [PATCH 14/18] More wrong imports --- da/test_full_flow.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/da/test_full_flow.py b/da/test_full_flow.py index 66ef032..9f9f6a4 100644 --- a/da/test_full_flow.py +++ b/da/test_full_flow.py @@ -4,7 +4,7 @@ from typing import List, Optional from da.common import NodeId, build_blob_id, NomosDaG2ProofOfPossession as bls_pop from da.api.common import DAApi, BlobMetadata, Metadata -from da.verifier import DAVerifier, DABlob +from da.verifier import DAVerifier, DAShare from da.api.test_flow import MockStore from da.dispersal import Dispersal, DispersalSettings from da.test_encoder import TestEncoder @@ -17,7 +17,7 @@ class DAVerifierWApi: self.api = DAApi(self.store) self.verifier = DAVerifier() - def receive_blob(self, blob: DABlob): + def receive_blob(self, blob: DAShare): if self.verifier.verify(blob): # Warning: If aggregated col commitment and row commitment are the same, # the build_attestation_message method will produce the same output. @@ -30,7 +30,7 @@ class DAVerifierWApi: # in which case all certificates had been already verified by the DA Node. self.api.write(blob_metadata.blob_id, blob_metadata.metadata) - def read(self, app_id, indexes) -> List[Optional[DABlob]]: + def read(self, app_id, indexes) -> List[Optional[DAShare]]: return self.api.read(app_id, indexes) @@ -59,7 +59,7 @@ class TestFullFlow(TestCase): encoded_data = DAEncoder(encoding_params).encode(data) # mock send and await method with local verifiers - def __send_and_await_response(node: int, blob: DABlob): + def __send_and_await_response(node: int, blob: DAShare): node = self.api_nodes[int.from_bytes(node)] node.receive_blob(blob) @@ -96,7 +96,7 @@ class TestFullFlow(TestCase): encoded_data = DAEncoder(encoding_params).encode(data) # mock send and await method with local verifiers - def __send_and_await_response(node: int, blob: DABlob): + def __send_and_await_response(node: int, blob: DAShare): node = self.api_nodes[int.from_bytes(node)] return node.receive_blob(blob) From 6ec7ea135ac2948acfb5d0b4d9b108bc4e9455f9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:31:52 +0200 Subject: [PATCH 15/18] More DaBlob to DaShare --- da/dispersal.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/da/dispersal.py b/da/dispersal.py index 19fc1ba..53b7991 100644 --- a/da/dispersal.py +++ b/da/dispersal.py @@ -3,7 +3,7 @@ from typing import List, Generator from da.common import NodeId from da.encoder import EncodedData -from da.verifier import DABlob +from da.verifier import DAShare @dataclass @@ -18,7 +18,7 @@ class Dispersal: # sort over public keys self.settings.nodes_ids.sort() - def _prepare_data(self, encoded_data: EncodedData) -> Generator[DABlob, None, None]: + def _prepare_data(self, encoded_data: EncodedData) -> Generator[DAShare, None, None]: assert len(encoded_data.column_commitments) == len(self.settings.nodes_ids) assert len(encoded_data.aggregated_column_proofs) == len(self.settings.nodes_ids) columns = encoded_data.extended_matrix.columns @@ -29,7 +29,7 @@ class Dispersal: aggregated_column_proofs = encoded_data.aggregated_column_proofs blobs_data = zip(columns, column_commitments, zip(*rows_proofs), aggregated_column_proofs) for column_idx, (column, column_commitment, row_proofs, column_proof) in enumerate(blobs_data): - blob = DABlob( + blob = DAShare( column, column_idx, column_commitment, @@ -40,7 +40,7 @@ class Dispersal: ) yield blob - def _send_and_await_response(self, node: NodeId, blob: DABlob) -> bool: + def _send_and_await_response(self, node: NodeId, blob: DAShare) -> bool: pass def disperse(self, encoded_data: EncodedData): From d18caed116f26751db5df71ee6753bb3c3ad4637 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:44:15 +0200 Subject: [PATCH 16/18] Fix prepare data --- da/dispersal.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/da/dispersal.py b/da/dispersal.py index 53b7991..b96db37 100644 --- a/da/dispersal.py +++ b/da/dispersal.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import List, Generator -from da.common import NodeId +from da.common import NodeId, Column from da.encoder import EncodedData from da.verifier import DAShare @@ -19,24 +19,16 @@ class Dispersal: self.settings.nodes_ids.sort() def _prepare_data(self, encoded_data: EncodedData) -> Generator[DAShare, None, None]: - assert len(encoded_data.column_commitments) == len(self.settings.nodes_ids) - assert len(encoded_data.aggregated_column_proofs) == len(self.settings.nodes_ids) columns = encoded_data.extended_matrix.columns - column_commitments = encoded_data.column_commitments row_commitments = encoded_data.row_commitments - rows_proofs = encoded_data.row_proofs - aggregated_column_commitment = encoded_data.aggregated_column_commitment - aggregated_column_proofs = encoded_data.aggregated_column_proofs - blobs_data = zip(columns, column_commitments, zip(*rows_proofs), aggregated_column_proofs) - for column_idx, (column, column_commitment, row_proofs, column_proof) in enumerate(blobs_data): + column_proofs = encoded_data.combined_column_proofs + blobs_data = zip(columns, column_proofs) + for column_idx, (column, proof) in enumerate(blobs_data): blob = DAShare( - column, + Column(column), column_idx, - column_commitment, - aggregated_column_commitment, - column_proof, - row_commitments, - row_proofs + proof, + row_commitments ) yield blob From 71b9c6cc337ca6fad64008beb04f21e0ee457250 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:46:33 +0200 Subject: [PATCH 17/18] Fix test encoder combined polynomial call --- da/test_encoder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/da/test_encoder.py b/da/test_encoder.py index e487e87..fa6213f 100644 --- a/da/test_encoder.py +++ b/da/test_encoder.py @@ -4,7 +4,7 @@ from unittest import TestCase from da import encoder from da.common import Column -from da.kzg_rs.bdfg_proving import derive_challenge +from da.kzg_rs.bdfg_proving import derive_challenge, compute_combined_polynomial from da.encoder import DAEncoderParams, DAEncoder from da.verifier import DAVerifier, DAShare from eth2spec.eip7594.mainnet import BYTES_PER_FIELD_ELEMENT, BLSFieldElement @@ -81,7 +81,7 @@ class TestEncoder(TestCase): chunks_matrix = self.encoder._chunkify_data(self.data) row_polynomials, row_commitments = zip(*self.encoder._compute_row_kzg_commitments(chunks_matrix)) h = derive_challenge(row_commitments) - combined_poly = self.encoder._combined_polynomial(row_polynomials, h) + combined_poly = compute_combined_polynomial(row_polynomials, h) proofs = self.encoder._compute_combined_column_proofs(combined_poly) expected_extended_columns = self.params.column_count * 2 self.assertEqual(len(proofs), expected_extended_columns) From cf88dde94e94870bbe06f8dfac0130f54a7cb246 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 4 Jun 2025 15:51:11 +0200 Subject: [PATCH 18/18] Fix changed calls in full flow --- da/test_full_flow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/da/test_full_flow.py b/da/test_full_flow.py index 9f9f6a4..4d191ad 100644 --- a/da/test_full_flow.py +++ b/da/test_full_flow.py @@ -2,7 +2,7 @@ from itertools import chain from unittest import TestCase from typing import List, Optional -from da.common import NodeId, build_blob_id, NomosDaG2ProofOfPossession as bls_pop +from da.common import NodeId, build_blob_id from da.api.common import DAApi, BlobMetadata, Metadata from da.verifier import DAVerifier, DAShare from da.api.test_flow import MockStore @@ -21,7 +21,7 @@ class DAVerifierWApi: if self.verifier.verify(blob): # Warning: If aggregated col commitment and row commitment are the same, # the build_attestation_message method will produce the same output. - blob_id = build_blob_id(blob.aggregated_column_commitment, blob.rows_commitments) + blob_id = build_blob_id(blob.row_commitments) self.store.populate(blob, blob_id) def receive_metadata(self, blob_metadata: BlobMetadata): @@ -66,7 +66,7 @@ class TestFullFlow(TestCase): # inject mock send and await method self.dispersal._send_and_await_response = __send_and_await_response self.dispersal.disperse(encoded_data) - blob_id = build_blob_id(encoded_data.aggregated_column_commitment, encoded_data.row_commitments) + blob_id = build_blob_id(encoded_data.row_commitments) blob_metadata = BlobMetadata( blob_id, Metadata(app_id, index) @@ -103,7 +103,7 @@ class TestFullFlow(TestCase): # inject mock send and await method self.dispersal._send_and_await_response = __send_and_await_response self.dispersal.disperse(encoded_data) - blob_id = build_blob_id(encoded_data.aggregated_column_commitment, encoded_data.row_commitments) + blob_id = build_blob_id(encoded_data.row_commitments) # Loop through each index and simulate dispersal with the same cert_id but different metadata for index in indexes: