Update DA encoding/verifier to v1.1

This commit is contained in:
mgonen 2025-05-22 13:41:45 +03:00
parent 82e5b4da7c
commit fdce70a9bb
5 changed files with 55 additions and 51 deletions

View File

@ -4,6 +4,7 @@ from itertools import chain, zip_longest, compress
from typing import List, Generator, Self, Sequence from typing import List, Generator, Self, Sequence
from eth2spec.eip7594.mainnet import Bytes32, KZGCommitment as Commitment from eth2spec.eip7594.mainnet import Bytes32, KZGCommitment as Commitment
from eth2spec.eip7594.mainnet import BLSFieldElement
from py_ecc.bls import G2ProofOfPossession from py_ecc.bls import G2ProofOfPossession
@ -47,6 +48,23 @@ def build_blob_id(row_commitments: Sequence[Commitment]) -> BlobId:
hasher.update(bytes(c)) hasher.update(bytes(c))
return hasher.digest() return hasher.digest()
def derive_challenge(row_commitments: List[Commitment]) -> BLSFieldElement:
"""
Derive a FiatShamir 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): class NomosDaG2ProofOfPossession(G2ProofOfPossession):
# Domain specific tag for Nomos DA protocol # Domain specific tag for Nomos DA protocol
DST = b"NOMOS_DA_AVAIL" DST = b"NOMOS_DA_AVAIL"

View File

@ -5,7 +5,7 @@ from hashlib import blake2b
from eth2spec.eip7594.mainnet import KZGCommitment as Commitment, KZGProof as Proof, BLSFieldElement 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 import kzg, rs
from da.kzg_rs.common import GLOBAL_PARAMETERS, ROOTS_OF_UNITY, BYTES_PER_FIELD_ELEMENT, BLS_MODULUS 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.poly import Polynomial
@ -64,23 +64,16 @@ class DAEncoder:
) )
return ChunksMatrix(__rs_encode_row(row) for row in chunks_matrix) 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 @staticmethod
def _combined_polynomial( def _combined_polynomial(
polys: Sequence[Polynomial], h: BLSFieldElement polys: Sequence[Polynomial], h: BLSFieldElement
) -> Polynomial: ) -> Polynomial:
combined = Polynomial([0], BLS_MODULUS) combined = Polynomial([0], BLS_MODULUS)
power = BLSFieldElement(1) h_int = int(h) # raw integer challenge
int_pow = 1
for poly in polys: for poly in polys:
combined = combined + poly * int(power) combined = combined + (poly * int_pow)
power = power * h int_pow = (int_pow * h_int) % BLS_MODULUS
return combined return combined
def _compute_combined_column_proofs(self, combined_poly: Polynomial) -> List[Proof]: def _compute_combined_column_proofs(self, combined_poly: Polynomial) -> List[Proof]:
@ -94,7 +87,7 @@ class DAEncoder:
chunks_matrix = self._chunkify_data(data) chunks_matrix = self._chunkify_data(data)
row_polynomials, row_commitments = zip(*self._compute_row_kzg_commitments(chunks_matrix)) row_polynomials, row_commitments = zip(*self._compute_row_kzg_commitments(chunks_matrix))
extended_matrix = self._rs_encode_rows(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_poly = self._combined_polynomial(row_polynomials, h)
combined_column_proofs = self._compute_combined_column_proofs(combined_poly) combined_column_proofs = self._compute_combined_column_proofs(combined_poly)
result = EncodedData( result = EncodedData(

View File

@ -49,11 +49,19 @@ class Polynomial[T]:
) )
def __mul__(self, other): def __mul__(self, other):
result = [0] * (len(self.coefficients) + len(other.coefficients) - 1) if isinstance(other, int):
for i in range(len(self.coefficients)): return Polynomial(
for j in range(len(other.coefficients)): [(a * other) % self.modulus for a in self.coefficients],
result[i + j] = (result[i + j] + self.coefficients[i] * other.coefficients[j]) % self.modulus self.modulus
return Polynomial(result, 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): def divide(self, other):
if not isinstance(other, Polynomial): if not isinstance(other, Polynomial):

View File

@ -1,10 +1,11 @@
from itertools import chain, batched from itertools import chain, batched
from random import randrange, randbytes from random import randrange, randbytes
from unittest import TestCase from unittest import TestCase
from eth2spec.utils import bls
from eth2spec.deneb.mainnet import bytes_to_bls_field from eth2spec.deneb.mainnet import bytes_to_bls_field
from da import encoder from da import encoder
from da.common import derive_challenge
from da.encoder import DAEncoderParams, DAEncoder from da.encoder import DAEncoderParams, DAEncoder
from da.verifier import DAVerifier from da.verifier import DAVerifier
from eth2spec.eip7594.mainnet import BYTES_PER_FIELD_ELEMENT, BLSFieldElement 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) self.assertEqual(len(encoded_data.combined_column_proofs), columns_len)
# verify rows # verify rows
h = DAVerifier._derive_challenge(encoded_data.row_commitments) h = derive_challenge(encoded_data.row_commitments)
com_C = encoded_data.row_commitments[0] combined_commitment = encoded_data.row_commitments[0]
power = h power = h
for com in encoded_data.row_commitments[1:]: 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 power = power * h
for i, (column, proof) in enumerate(zip(encoded_data.extended_matrix.columns, encoded_data.combined_column_proofs)): 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) power = BLSFieldElement(1)
for chunk in column.chunks: for data in column.chunks:
x = BLSFieldElement(int.from_bytes(bytes(chunk), byteorder="big")) chunk = BLSFieldElement(int.from_bytes(bytes(data), byteorder="big"))
v = v + x * power combined_eval_point = combined_eval_point + chunk * power
power = power * h power = power * h
kzg.verify_element_proof( kzg.verify_element_proof(
v, combined_eval_point,
com_C, combined_commitment,
proof, proof,
i, i,
ROOTS_OF_UNITY ROOTS_OF_UNITY
@ -91,7 +92,7 @@ class TestEncoder(TestCase):
def test_generate_combined_column_proofs(self): def test_generate_combined_column_proofs(self):
chunks_matrix = self.encoder._chunkify_data(self.data) chunks_matrix = self.encoder._chunkify_data(self.data)
row_polynomials, row_commitments = zip(*self.encoder._compute_row_kzg_commitments(chunks_matrix)) 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) combined_poly = self.encoder._combined_polynomial(row_polynomials, h)
proofs = self.encoder._compute_combined_column_proofs(combined_poly) proofs = self.encoder._compute_combined_column_proofs(combined_poly)
self.assertEqual(len(proofs), len(row_commitments)) self.assertEqual(len(proofs), len(row_commitments))

View File

@ -8,7 +8,7 @@ from eth2spec.eip7594.mainnet import (
) )
import da.common 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 import kzg
from da.kzg_rs.common import ROOTS_OF_UNITY, GLOBAL_PARAMETERS, BLS_MODULUS 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) return build_blob_id(self.row_commitments)
class DAVerifier: class DAVerifier:
@staticmethod
def _derive_challenge(row_commitments: List[Commitment]) -> BLSFieldElement:
"""
Derive a FiatShamir 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 @staticmethod
def verify(blob: DAShare) -> bool: def verify(blob: DAShare) -> bool:
""" """
@ -51,12 +35,12 @@ class DAVerifier:
Returns True if verification succeeds, False otherwise. Returns True if verification succeeds, False otherwise.
""" """
# 1. Derive challenge # 1. Derive challenge
h = DAVerifier._derive_challenge(blob.row_commitments) h = derive_challenge(blob.row_commitments)
# 2. Reconstruct combined commitment: com_C = sum_{i=0..l-1} h^i * row_commitments[i] # 2. Reconstruct combined commitment: combined_commitment = sum_{i=0..l-1} h^i * row_commitments[i]
com_C = blob.row_commitments[0] combined_commitment = blob.row_commitments[0]
power = h power = h
for com in blob.row_commitments[1:]: for com in blob.row_commitments[1:]:
com_C = com_C + com * int(power) combined_commitment = combined_commitment + com * int(power)
power = power * h power = power * h
# 3. Compute combined evaluation v = sum_{i=0..l-1} (h^i * column_data[i]) # 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 v = v + x * power
power = power * h power = power * h
# 4. Verify the single KZG proof for evaluation at point w^{column_idx} # 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)