Skip to content

Commit 8c576e2

Browse files
authored
feat: Implement compute_cells peerdas test case (#1281)
1 parent 2fe1c03 commit 8c576e2

4 files changed

Lines changed: 32 additions & 233 deletions

File tree

Lines changed: 15 additions & 230 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
use alloy_primitives::B256;
21
use anyhow::{Ok, Result, anyhow, ensure};
3-
use discv5::enr::k256::{
4-
Scalar,
5-
elliptic_curve::{Field, PrimeField},
6-
};
72
use ream_consensus_misc::{
8-
constants::beacon::{
9-
BYTES_PER_BLOB, BYTES_PER_FIELD_ELEMENT, CELLS_PER_EXT_BLOB, FIELD_ELEMENTS_PER_BLOB,
10-
FIELD_ELEMENTS_PER_CELL, FIELD_ELEMENTS_PER_EXT_BLOB,
11-
},
12-
polynomial_commitments::kzg_proof::KZGProof,
3+
constants::beacon::CELLS_PER_EXT_BLOB, polynomial_commitments::kzg_proof::KZGProof,
134
};
145
use ream_execution_rpc_types::get_blobs::Blob;
15-
use rust_eth_kzg::{Cell as KZGCell, CellIndex, DASContext, KZGProof as Proof};
6+
use rust_eth_kzg::{Cell as KZGCell, DASContext, KZGProof as Proof};
167
use ssz_types::FixedVector;
178

189
use crate::data_column_sidecar::Cell;
@@ -144,231 +135,25 @@ fn convert_kzg_proof(kzg_proof: Proof) -> KZGProof {
144135
KZGProof::from(kzg_proof)
145136
}
146137

147-
/// Convert untrusted bytes to a trusted and validated BLS scalar field element.
148-
/// This function does not accept inputs greater than the BLS modulus.
149-
pub fn bytes_to_bls_field(b: B256) -> anyhow::Result<Scalar> {
150-
ensure!(b.len() == 32, "Invalid input length for field element");
151-
152-
let field_element = Scalar::from_repr((*b).into())
153-
.into_option()
154-
.ok_or_else(|| anyhow!("Bytes exceed the field modulus"))?;
155-
Ok(field_element)
156-
}
157-
158-
/// Convert a blob to list of BLS field scalars.
159-
pub fn blob_to_polynomial(blob: Blob) -> anyhow::Result<[Scalar; FIELD_ELEMENTS_PER_BLOB]> {
160-
let mut polynomial = [Scalar::default(); FIELD_ELEMENTS_PER_BLOB];
161-
162-
for (i, polynomial_element) in polynomial.iter_mut().enumerate() {
163-
let start = i * BYTES_PER_FIELD_ELEMENT;
164-
let end = (i + 1) * BYTES_PER_FIELD_ELEMENT;
165-
166-
let chunk: B256 = blob.inner[start..end]
167-
.try_into()
168-
.map_err(|err| anyhow!("Invalid chunk size at index {err:?}"))?;
169-
170-
*polynomial_element = bytes_to_bls_field(chunk)?;
171-
}
172-
173-
Ok(polynomial)
174-
}
175-
176-
/// Return ``x`` to power of [0, n-1], if n > 0. When n==0, an empty array is returned.
177-
pub fn compute_powers(x: Scalar, n: u64) -> anyhow::Result<Vec<Scalar>> {
178-
let mut powers = Vec::with_capacity(n as usize);
179-
let mut current_power = Scalar::ONE;
180-
181-
for _ in 0..n {
182-
powers.push(current_power);
183-
current_power *= x;
184-
}
185-
186-
Ok(powers)
187-
}
188-
189-
/// Return roots of unity of ``order``.
190-
pub fn compute_roots_of_unity(order: u64) -> anyhow::Result<Vec<Scalar>> {
191-
ensure!(
192-
order <= (1 << Scalar::S),
193-
"Order exceeds maximum supported by field"
194-
);
195-
let ratio = (1 << Scalar::S) / order;
196-
let exponent: [u64; 4] = [ratio, 0, 0, 0];
197-
198-
let root = Scalar::ROOT_OF_UNITY.pow_vartime(exponent);
199-
200-
Ok(compute_powers(root, order)?)
201-
}
202-
203-
fn _fft_field(vals: Vec<Scalar>, roots_of_unity: Vec<Scalar>) -> anyhow::Result<Vec<Scalar>> {
204-
let n = vals.len();
205-
if n <= 1 {
206-
return Ok(vals);
207-
}
208-
let mut evens = Vec::with_capacity(n / 2);
209-
let mut odds = Vec::with_capacity(n / 2);
210-
for (i, val) in vals.into_iter().enumerate() {
211-
if i % 2 == 0 {
212-
evens.push(val);
213-
} else {
214-
odds.push(val);
215-
}
216-
}
217-
let next_roots: Vec<Scalar> = roots_of_unity.iter().step_by(2).cloned().collect();
218-
let left = _fft_field(evens, next_roots.clone())?;
219-
let right = _fft_field(odds, next_roots)?;
220-
221-
let mut result = vec![Scalar::ZERO; n];
222-
for i in 0..(n / 2) {
223-
let root = roots_of_unity[i];
224-
let y_times_root = right[i] * root;
225-
result[i] = left[i] + y_times_root;
226-
result[i + (n / 2)] = left[i] - y_times_root;
227-
}
228-
229-
Ok(result)
230-
}
231-
232-
pub fn fft_field(
233-
vals: Vec<Scalar>,
234-
roots_of_unity: Vec<Scalar>,
235-
inv: bool,
236-
) -> anyhow::Result<Vec<Scalar>> {
237-
if inv {
238-
let n = vals.len() as u64;
239-
let invlen = Scalar::from(n).invert().unwrap();
240-
241-
let mut reversed_roots = Vec::with_capacity(roots_of_unity.len());
242-
if !roots_of_unity.is_empty() {
243-
reversed_roots.push(roots_of_unity[0]);
244-
reversed_roots.extend(roots_of_unity.iter().skip(1).rev());
245-
}
246-
247-
let fft_result = _fft_field(vals, reversed_roots)?;
248-
let result = fft_result.into_iter().map(|x| x * invlen).collect();
249-
250-
Ok(result)
251-
} else {
252-
Ok(_fft_field(vals, roots_of_unity)?)
253-
}
254-
}
255-
256-
/// Reverse the bit order of an integer ``n``.
257-
pub fn reverse_bits(n: usize, order: usize) -> anyhow::Result<usize> {
258-
ensure!(order.is_power_of_two(), "Order must be a power of two");
259-
260-
let width = order.trailing_zeros();
261-
let mut result = 0;
262-
let mut temp_n = n;
263-
264-
for _ in 0..width {
265-
result = (result << 1) | (temp_n & 1);
266-
temp_n >>= 1;
267-
}
268-
269-
Ok(result)
270-
}
271-
272-
/// Return a copy with bit-reversed permutation. The permutation is an involution (inverts itself).
273-
///
274-
/// The input and output are a sequence of generic type ``T`` objects.
275-
pub fn bit_reversal_permutation<T: Clone>(sequence: Vec<T>) -> anyhow::Result<Vec<T>> {
276-
let n = sequence.len();
277-
let mut result = Vec::with_capacity(n);
278-
279-
for i in 0..n {
280-
let rev_idx = reverse_bits(i, n)?;
281-
result.push(sequence[rev_idx].clone());
282-
}
283-
284-
Ok(result)
285-
}
286-
287-
/// Interpolates a polynomial (given in evaluation form) to a polynomial in coefficient form.
288-
pub fn polynomial_eval_to_coeff(
289-
polynomial: [Scalar; FIELD_ELEMENTS_PER_BLOB],
290-
) -> anyhow::Result<Vec<Scalar>> {
291-
let roots_of_unity = compute_roots_of_unity(FIELD_ELEMENTS_PER_BLOB as u64)?;
292-
let rearranged = bit_reversal_permutation(polynomial.to_vec())?;
293-
294-
fft_field(rearranged, roots_of_unity, true)
295-
}
296-
297-
/// Get the coset for a given ``cell_index``.
298-
/// Precisely, consider the group of roots of unity of order FIELD_ELEMENTS_PER_CELL *
299-
/// CELLS_PER_EXT_BLOB. Let G = {1, g, g^2, ...} denote its subgroup of order
300-
/// FIELD_ELEMENTS_PER_CELL. Then, the coset is defined as h * G = {h, hg, hg^2, ...}.
301-
/// This function, returns the coset.
302-
pub fn coset_for_cell(cell_index: CellIndex) -> anyhow::Result<[Scalar; FIELD_ELEMENTS_PER_CELL]> {
303-
ensure!(
304-
cell_index < CELLS_PER_EXT_BLOB,
305-
"Cell index great then CELLS_PER_EXT_BLOB"
306-
);
307-
let roots = compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB as u64)?;
308-
let roots_of_unity_brp = bit_reversal_permutation(roots)?;
309-
310-
let start = (cell_index as usize) * FIELD_ELEMENTS_PER_CELL;
311-
let end = start + FIELD_ELEMENTS_PER_CELL;
312-
313-
let coset_slice = &roots_of_unity_brp[start..end];
314-
315-
let coset: [Scalar; FIELD_ELEMENTS_PER_CELL] = coset_slice
316-
.try_into()
317-
.map_err(|err| anyhow!("Slice length mismatch for coset conversion {err:?}"))?;
318-
319-
Ok(coset)
320-
}
321-
322-
/// Evaluate a coefficient form polynomial at ``z`` using Horner's schema.
323-
pub fn evaluate_polynomialcoeff(polynomial_coeff: &[Scalar], z: Scalar) -> anyhow::Result<Scalar> {
324-
let mut y = Scalar::ZERO;
325-
for coef in polynomial_coeff.iter().rev() {
326-
y = (y * z) + *coef;
327-
}
328-
329-
Ok(y)
330-
}
331-
332-
pub fn bls_field_to_bytes(x: Scalar) -> B256 {
333-
let bytes: [u8; 32] = x.to_bytes().into();
334-
B256::from(bytes)
335-
}
336-
337-
/// Convert a trusted ``CosetEval`` into an untrusted ``Cell``.
338-
pub fn coset_evals_to_cell(coset_evals: [Scalar; FIELD_ELEMENTS_PER_CELL]) -> anyhow::Result<Cell> {
339-
let mut cell_bytes = Vec::with_capacity(FIELD_ELEMENTS_PER_CELL * 32);
340-
for eval in coset_evals {
341-
let bytes: B256 = bls_field_to_bytes(eval);
342-
cell_bytes.extend_from_slice(bytes.as_slice());
343-
}
344-
345-
let cell = cell_bytes
138+
pub fn compute_cells(
139+
blob: Blob,
140+
das_context: &DASContext,
141+
) -> anyhow::Result<[Cell; CELLS_PER_EXT_BLOB as usize]> {
142+
let blob_data: Vec<u8> = blob.inner.into();
143+
let blob_bytes: &[u8; 131072] = blob_data
144+
.as_slice()
346145
.try_into()
347-
.map_err(|err| anyhow!("Failed to convert bytes to Cell FixedVector {err:?}"))?;
348-
Ok(cell)
349-
}
350-
351-
pub fn compute_cells(blob: Blob) -> anyhow::Result<[Cell; CELLS_PER_EXT_BLOB as usize]> {
352-
ensure!(blob.inner.len() == BYTES_PER_BLOB, "Invalid blob length");
353-
let polynomial = blob_to_polynomial(blob)?;
354-
let polynomial_coeff = polynomial_eval_to_coeff(polynomial)?;
355-
let mut cells = Vec::with_capacity(CELLS_PER_EXT_BLOB as usize);
146+
.map_err(|err| anyhow!("Invalid blob size {err:?}"))?;
356147

357-
for i in 0..CELLS_PER_EXT_BLOB {
358-
let coset = coset_for_cell(i as CellIndex)?;
359-
360-
let mut ys = [Scalar::ZERO; FIELD_ELEMENTS_PER_CELL];
361-
for (j, &z) in coset.iter().enumerate() {
362-
ys[j] = evaluate_polynomialcoeff(&polynomial_coeff, z)?;
363-
}
148+
let kzg_cells = das_context
149+
.compute_cells(blob_bytes)
150+
.map_err(|err| anyhow!("KZG error: {err:?}"))?;
364151

365-
let cell = coset_evals_to_cell(ys)?;
366-
cells.push(cell);
367-
}
152+
let cells: Vec<Cell> = kzg_cells.into_iter().map(convert_cell).collect();
368153

369154
let final_cells: [Cell; CELLS_PER_EXT_BLOB as usize] = cells
370155
.try_into()
371-
.map_err(|err| anyhow!("Failed to convert cells to fixed array {err:?}"))?;
156+
.map_err(|err| anyhow!("Failed to convert to fixed array {err:?}"))?;
372157

373158
Ok(final_cells)
374159
}

crates/common/consensus/misc/src/constants/beacon.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ pub const ETH1_FOLLOW_DISTANCE: u64 = 2048;
3939
pub const EXECUTION_PAYLOAD_INDEX: u64 = 9;
4040
pub const FAR_FUTURE_EPOCH: u64 = 18446744073709551615;
4141
pub const FIELD_ELEMENTS_PER_BLOB: usize = 4096;
42-
pub const FIELD_ELEMENTS_PER_CELL: usize = 64;
43-
pub const FIELD_ELEMENTS_PER_EXT_BLOB: usize = 8192;
4442
pub const FINALIZED_CHECKPOINT_INDEX: u64 = 20;
4543
pub const FULU_FORK_EPOCH: u64 = 411392;
4644
pub const GENESIS_SLOT: u64 = 0;

testing/ef-tests/src/macros/peerdas.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ macro_rules! test_peerdas_kzg {
1515
use ream_execution_rpc_types::get_blobs::Blob;
1616
use rust_eth_kzg::{DASContext, UsePrecomp, TrustedSetup};
1717
use ream_polynomial_commitments::handlers::verify_cell_kzg_proof_batch;
18-
use ream_consensus_beacon::matrix_entry::{compute_cells_and_kzg_proofs, recover_cells_and_kzg_proofs};
18+
use ream_consensus_beacon::matrix_entry::{compute_cells_and_kzg_proofs, recover_cells_and_kzg_proofs, compute_cells};
1919

2020
#[derive(Debug, Deserialize)]
2121
pub struct KzgInput {
@@ -104,6 +104,21 @@ macro_rules! test_peerdas_kzg {
104104
)).unwrap()
105105
}).map_err(|err| anyhow!("Failed to get recover_cells_and_kzg_proofs {err:?}"))
106106
},
107+
"compute_cells" => {
108+
let blob_hex = input.blob.as_ref().expect("No blob");
109+
let blob = Blob::from_ssz_bytes(
110+
&const_hex::decode(blob_hex.strip_prefix("0x").unwrap_or(blob_hex))?
111+
).map_err(|err| anyhow!("Failed to decode {err:?}"))?;
112+
113+
compute_cells(blob, &context).map(|cells| {
114+
let output: Vec<String> = cells
115+
.iter()
116+
.map(|cell| const_hex::encode_prefixed(cell.as_ref()))
117+
.collect();
118+
119+
to_value(output).unwrap()
120+
}).map_err(|err| anyhow!("Failed to execute compute_cells: {err:?}"))
121+
},
107122
_ => Err(anyhow!("Unknown function path: {path}")),
108123
}
109124
})();

testing/ef-tests/tests/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,4 @@ test_sanity_blocks!(test_finality, "finality/finality");
226226
test_peerdas_kzg!(verify_cell_kzg_proof_batch);
227227
test_peerdas_kzg!(compute_cells_and_kzg_proofs);
228228
test_peerdas_kzg!(recover_cells_and_kzg_proofs);
229+
test_peerdas_kzg!(compute_cells);

0 commit comments

Comments
 (0)