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 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 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):
# Domain specific tag for Nomos DA protocol
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 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(

View File

@ -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):

View File

@ -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))

View File

@ -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 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
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)