Skip to content

Commit 4ae1162

Browse files
committed
blob generation via seed, only load trusted_setup once, add constants from specs, removed unnecessary wrappers
1 parent 861ca68 commit 4ae1162

File tree

2 files changed

+61
-90
lines changed

2 files changed

+61
-90
lines changed

tests/osaka/eip7594_peerdas/helper_functions.py

Lines changed: 53 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,100 +4,58 @@
44
""" # noqa: E501
55
import base64 as b64
66
import json
7-
import math
7+
from os.path import realpath
8+
from pathlib import Path
9+
from random import randrange, seed
810

911
import ckzg
10-
1112
from spec import Spec, ref_spec_7594
1213

14+
TRUSTED_SETUP_FILE_NAME = "trusted_setup.txt"
15+
TRUSTED_SETUP_PATH = Path(realpath(__file__)).parent / TRUSTED_SETUP_FILE_NAME
16+
TRUSTED_SETUP = ckzg.load_trusted_setup(str(TRUSTED_SETUP_PATH), 0)
17+
1318
REFERENCE_SPEC_GIT_PATH = ref_spec_7594.git_path
1419
REFERENCE_SPEC_VERSION = ref_spec_7594.version
1520

21+
1622
def bytes_from_hex(hex_string):
1723
"""Convert a hex string to bytes."""
1824
return bytes.fromhex(hex_string.replace("0x", ""))
1925

20-
def generate_blob_from_hex_byte(hex_byte: str) -> bytes | None:
21-
"""Take a singular hex byte string and return a valid blob built from this byte."""
22-
# preparation (remove 0x if present)
23-
if "0x" in hex_byte:
24-
hex_byte = hex_byte.replace("0x", "", 1)
25-
26-
# validity checks
27-
if len(hex_byte) != 2:
28-
print("You should have passed a singular hex byte like e.g. fe to this function")
29-
return None
30-
31-
if int(hex_byte, 16) > 115:
32-
# TODO: figure out why this happens
33-
print("For some reason bytes larger than this will lead to errors later in compute_cells(), so it's not allowed for now.") # noqa: E501
34-
return None
35-
36-
hex_byte = hex_byte.lower()
37-
38-
valid_chars = "0123456789abcdef"
39-
if (hex_byte[0] not in valid_chars) or (hex_byte[1] not in valid_chars):
40-
print("You should have passed a singular hex byte consisting of only two chars in 0-9 and a-f like e.g. fe to this function") # noqa: E501
41-
return None
42-
26+
def generate_blob_from_seed(rng_seed: int) -> bytes | None:
27+
"""Take a seed and deterministically returns a valid blob generated from this seed."""
28+
# apply RNG seed
29+
seed(rng_seed)
4330

44-
# create blob
45-
exp = int(math.log2(Spec.BYTES_PER_BLOB))
46-
blob_string: str = "0x" + hex_byte * (2**exp)
47-
blob: bytes = bytes_from_hex(blob_string)
31+
# generate blob
32+
ints: list[int] = [randrange(Spec.BLS_MODULUS) for _ in range(Spec.FIELD_ELEMENTS_PER_BLOB)] # len: 4096 # noqa: E501
33+
encoded: list[bytes] = [i.to_bytes(Spec.BYTES_PER_FIELD_ELEMENT, Spec.KZG_ENDIANNESS) for i in ints] # len: 4096 # noqa: E501
34+
blob: bytes = b"".join(encoded) # without 0x
4835

4936
return blob
5037

51-
5238
def eest_blob_to_kzg_commitment(blob: bytes) -> bytes:
5339
"""Take a blob and returns a cryptographic commitment to it. Note: Each cell seems to hold a copy of this commitment.""" # noqa: E501
5440
# sanity check
55-
expected_blob_length = 2**int(math.log2(Spec.BYTES_PER_BLOB))
56-
assert len(blob) == expected_blob_length, f"Expected blob of length {expected_blob_length} but got blob of length {len(blob)}" # noqa: E501
41+
assert len(blob) == Spec.BYTES_PER_BLOB, f"Expected blob of length {Spec.BYTES_PER_BLOB} but got blob of length {len(blob)}" # noqa: E501
5742

5843
# calculate commitment
59-
ts = ckzg.load_trusted_setup("trusted_setup.txt", 0)
60-
commitment = ckzg.blob_to_kzg_commitment(blob, ts)
44+
commitment = ckzg.blob_to_kzg_commitment(blob, TRUSTED_SETUP)
6145

62-
assert len(commitment) == Spec.KZG_COMMITMENT_LENGTH, f"Expected {Spec.KZG_COMMITMENT_LENGTH} resulting commitments but got {len(commitment)} commitments" # noqa: E501
46+
assert len(commitment) == Spec.BYTES_PER_COMMITMENT, f"Expected {Spec.BYTES_PER_COMMITMENT} resulting commitments but got {len(commitment)} commitments" # noqa: E501
6347

6448
return commitment
6549

66-
67-
def eest_compute_cells(blob: bytes) -> list[int]:
68-
"""Take a blob and returns a list of cells that are derived from this blob."""
69-
ts = ckzg.load_trusted_setup("trusted_setup.txt", 0)
70-
71-
cells = ckzg.compute_cells(blob, ts)
72-
73-
assert len(cells) == 128
74-
75-
return cells
76-
77-
78-
def eest_compute_cells_and_kzg_proofs(blob: bytes) -> tuple[list[bytes], list[bytes]]:
79-
"""Take a blob and returns a list of cells and a list of proofs derived from this blob."""
80-
ts = ckzg.load_trusted_setup("trusted_setup.txt", 0)
81-
82-
cells, proofs = ckzg.compute_cells_and_kzg_proofs(blob, ts) # both returns are of type list
83-
84-
assert len(cells) == 128
85-
assert len(proofs) == 128
86-
87-
return cells, proofs
88-
89-
9050
def eest_verify_cell_kzg_proof_batch(commitment: bytes, cell_indices: list, cells: list, proofs: list) -> bool: # noqa: E501
9151
"""Check whether all cell proofs are valid and returns True only if that is the case."""
92-
ts = ckzg.load_trusted_setup("trusted_setup.txt", 0)
93-
9452
# sanity check
9553
assert len(cell_indices) == len(cells), f"Cell Indices list (detected length {len(cell_indices)}) and Cell list (detected length {len(cells)}) should have same length." # noqa: E501
9654

9755
# each cell refers to the same commitment
9856
commitments: list[bytes] = [commitment] * len(cell_indices)
9957

100-
is_valid = ckzg.verify_cell_kzg_proof_batch(commitments, cell_indices, cells, proofs, ts)
58+
is_valid = ckzg.verify_cell_kzg_proof_batch(commitments, cell_indices, cells, proofs, TRUSTED_SETUP) # noqa: E501
10159

10260
return is_valid
10361

@@ -127,47 +85,47 @@ def eest_delete_cells_then_recover_them(cells: list[bytes], proofs: list[bytes],
12785
# print(f"Cells: {cells}\nDeletion Indices: {deletion_indices}\nRemaining indices: {remaining_indices}\nRemaining cells: {remaining_cells}") # noqa: E501
12886

12987
# try to reconstruct cells
130-
ts = ckzg.load_trusted_setup("trusted_setup.txt", 0)
131-
recovered_cells, recovered_proofs = ckzg.recover_cells_and_kzg_proofs(remaining_indices, remaining_cells, ts) # on success returns two lists of len 128 # noqa: E501
88+
recovered_cells, recovered_proofs = ckzg.recover_cells_and_kzg_proofs(remaining_indices, remaining_cells, TRUSTED_SETUP) # on success returns two lists of len 128 # noqa: E501
13289

13390
# determine success/failure
13491
assert len(recovered_cells) == len(cells), f"Failed to recover cell list. Original cell list had length {len(cells)} but recovered cell list has length {len(recovered_cells)}" # noqa: E501
13592
assert len(recovered_proofs) == len(proofs), f"Failed to recover proofs list. Original proofs list had length {len(proofs)} but recovered proofs list has length {len(recovered_proofs)}" # noqa: E501
13693

13794
for i in range(len(recovered_cells)):
138-
assert cells[i] == recovered_cells[i], f"Failed to correctly restore missing cells. At index {i} original cell was {cells[i]} but reconstructed cell does not match: {recovered_cells[i]}" # noqa: E501
139-
assert proofs[i] == recovered_proofs[i], f"Failed to correctly restore missing proofs. At index {i} original proof was {proofs[i]} but reconstructed proof does not match: {recovered_proofs[i]}" # noqa: E501
95+
assert cells[i] == recovered_cells[i], f"Failed to correctly restore missing cells. At index {i} original cell was 0x{cells[i].hex()} but reconstructed cell does not match: 0x{recovered_cells[i].hex()}" # noqa: E501
96+
assert proofs[i] == recovered_proofs[i], f"Failed to correctly restore missing proofs. At index {i} original proof was 0x{proofs[i].hex()} but reconstructed proof does not match: 0x{recovered_proofs[i].hex()}" # noqa: E501
14097

14198
# print("Successful reconstruction")
14299

143100
class PersistentBlobGenerator:
144-
# PersistentBlobGenerator("4a") creates blob_4a.json in cwd and contains blob, commitment, cells and proofs
101+
"""PersistentBlobGenerator takes an rng seed and returns a valid blob deterministically derived from it.""" # noqa: E501
102+
103+
# e.g. PersistentBlobGenerator(42) creates blob_42.json in cwd and contains blob, commitment, cells and proofs. 42 stands for the rng seed used # noqa: E501
145104
encoding: str = "utf-8"
146105

147-
def __init__(self, singular_byte: str):
106+
def __init__(self, rng_seed: int):
107+
"""Construct."""
148108
# safely derive blob from input
149-
blob: bytes | None = generate_blob_from_hex_byte(singular_byte)
150-
assert blob is not None, f"PersistentBlobGenerator received invalid input: {singular_byte}"
151-
152-
if "0x" in singular_byte:
153-
singular_byte = singular_byte.replace("0x", "", 1)
109+
blob: bytes | None = generate_blob_from_seed(rng_seed)
110+
assert blob is not None, f"PersistentBlobGenerator received invalid rng_seed: {rng_seed}"
154111

155112
commitment: bytes = eest_blob_to_kzg_commitment(blob)
156-
cells, proofs = eest_compute_cells_and_kzg_proofs(blob)
113+
cells, proofs = ckzg.compute_cells_and_kzg_proofs(blob, TRUSTED_SETUP)
157114

158115
# populate instance
159-
self.name: str = "blob_" + singular_byte
116+
self.name: str = "blob_" + str(rng_seed)
160117
self.blob: bytes = blob
161118
self.commitments: list[bytes] = [commitment] * len(cells)
162119
self.cells: list[bytes] = cells
163120
self.proofs: list[bytes] = proofs
164121

165122
def to_json(self) -> str:
123+
"""Convert object to json-compatible string."""
166124
# json does not support bytes, so we b64 encode these fields into strings
167125
# blob: bytes -> str
168126
b64_blob_str: str = b64.b64encode(self.blob).decode(self.encoding)
169127
# commitments: list[bytes] -> list[str]
170-
b64_commitment_list: list[str] = [b64.b64encode(c).decode(self.encoding) for c in self.commitments]
128+
b64_commitment_list: list[str] = [b64.b64encode(c).decode(self.encoding) for c in self.commitments] # noqa: E501
171129
# cells: list[bytes] -> list[str]
172130
b64_cell_list: list[str] = [b64.b64encode(c).decode(self.encoding) for c in self.cells]
173131
# proofs: list[bytes] -> list[str]
@@ -183,9 +141,10 @@ def to_json(self) -> str:
183141

184142

185143
return json.dumps(json_dict)
186-
144+
187145
@classmethod
188146
def from_json(cls, json_str: str) -> "PersistentBlobGenerator":
147+
"""Take json and reconstruct blob it represents."""
189148
data = json.loads(json_str)
190149

191150
# convert b64 blob back to bytes
@@ -196,9 +155,9 @@ def from_json(cls, json_str: str) -> "PersistentBlobGenerator":
196155
cells: list[bytes] = [b64.b64decode(s) for s in data["b64_cells"]]
197156
# convert b64 proofs back to list[bytes]
198157
proofs: list[bytes] = [b64.b64decode(s) for s in data["b64_proofs"]]
199-
158+
200159
# get data
201-
obj = cls("00") # dummy object
160+
obj = cls(0) # dummy object
202161
obj.name = data["name"]
203162
obj.blob = blob
204163
obj.commitments = commitments
@@ -207,7 +166,7 @@ def from_json(cls, json_str: str) -> "PersistentBlobGenerator":
207166
return obj
208167

209168

210-
original = PersistentBlobGenerator("4a")
169+
original = PersistentBlobGenerator(55)
211170
json_str = original.to_json()
212171
restored = PersistentBlobGenerator.from_json(json_str)
213172
assert original.name == restored.name
@@ -217,15 +176,12 @@ def from_json(cls, json_str: str) -> "PersistentBlobGenerator":
217176
assert original.proofs == restored.proofs
218177
print("It works")
219178

220-
# TODO: add write to file / read from file
221-
222179
""" Example Usage
223-
my_byte = "42" # 0x73 (115) or lower works, 0x74 (116) or higher fails
224180
# generate blob
225-
blob: bytes | None = generate_blob_from_hex_byte(my_byte)
181+
blob: bytes | None = generate_blob_from_seed(5)
226182
assert blob is not None, "blob is None"
227183
# get cells and proofs
228-
cells, proofs = eest_compute_cells_and_kzg_proofs(blob)
184+
cells, proofs = ckzg.compute_cells_and_kzg_proofs(blob, TRUSTED_SETUP)
229185
# delete some cells and recover them again
230186
deletion_indices: list[int] = [5, 42, 100]
231187
eest_delete_cells_then_recover_them(cells, proofs, deletion_indices)
@@ -238,3 +194,15 @@ def from_json(cls, json_str: str) -> "PersistentBlobGenerator":
238194
is_valid = eest_verify_cell_kzg_proof_batch(commitment, my_cell_indices, cells, proofs)
239195
print("Success")
240196
"""
197+
198+
# - blob is now deterministically generated via seed
199+
# - trusted_setup is only loaded once
200+
# - rename constants to match deneb specs
201+
# - removed two unnecessary wrapper functions
202+
203+
# TODO: make PersistentBlobGenerator use a pydantic model
204+
# TODO: PersistentBlobGenerator needs functions for writing/reading to/from file
205+
# TODO: uv lock
206+
207+
# ckzg.compute_cells(blob, TRUSTED_SETUP) returns a list of length 128
208+
# ckzg.compute_cells_and_kzg_proofs(blob, TRUSTED_SETUP)

tests/osaka/eip7594_peerdas/spec.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ class Spec:
2323

2424
FIELD_ELEMENTS_PER_BLOB = 4096
2525
BYTES_PER_FIELD_ELEMENT = 32
26-
BYTES_PER_BLOB = FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT
27-
28-
# below are mine, not sure if they are defined in specs somewhere
29-
KZG_PROOF_LENGTH = 48
30-
KZG_COMMITMENT_LENGTH = 48
26+
BYTES_PER_BLOB = FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT # 131072
3127
CELL_LENGTH = 2048
28+
BLS_MODULUS = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 # EIP-2537: Main subgroup order = q # noqa: E501
29+
# due to BLS_MODULUS every blob byte (uint256) must be smaller than 116
30+
31+
# deneb constants that have not changed (https://github.com/ethereum/consensus-specs/blob/cc6996c22692d70e41b7a453d925172ee4b719ad/specs/deneb/polynomial-commitments.md?plain=1#L78)
32+
BYTES_PER_PROOF = 48
33+
BYTES_PER_COMMITMENT = 48
34+
KZG_ENDIANNESS = "big"

0 commit comments

Comments
 (0)