mirror of
https://github.com/logos-blockchain/logos-blockchain-specs.git
synced 2026-04-22 02:23:24 +00:00
Update DA encoding/verifier to v1.1
This commit is contained in:
parent
2c5c3860f0
commit
5cb2f9223c
@ -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()
|
||||
|
||||
@ -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
|
||||
126
da/verifier.py
126
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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user