From fdce70a9bb064079da37684b374aea0e33d1e4cb Mon Sep 17 00:00:00 2001 From: mgonen Date: Thu, 22 May 2025 13:41:45 +0300 Subject: [PATCH] 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)