Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmarks/bench_eth_eip7594_peerdas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ proc benchComputeCells(b: BenchSet, ctx: ptr EthereumKZGContext, iters: int) =
var cells: ref array[CELLS_PER_EXT_BLOB, Cell]
new(cells)
bench("compute_cells (half-FFT optimization)", iters):
doAssert cttEthKzg_Success == ctx.compute_cells(cells[], b.blobs[0])
doAssert cttEthKzg_Success == ctx.compute_cells(cells[].asUnchecked(), b.blobs[0])

proc benchComputeCellsAndKZGProofsNoPrecomp(b: BenchSet, ctx: ptr EthereumKZGContext, iters: int) =
## Compute cells and proofs together using FK20 algorithm
Expand Down
3 changes: 2 additions & 1 deletion benchmarks/eth_eip7594/perf_compute_cells.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import
benchset_serialization,
constantine/eth_eip7594_peerdas,
constantine/platforms/primitives,
constantine/ethereum_eip4844_kzg_parallel,
../bench_blueprint,
std/[os, strutils, monotimes]
Expand All @@ -29,7 +30,7 @@ proc benchComputeCells(b: BenchSet, ctx: ptr EthereumKZGContext, iters: int) =
var cells: ref array[CELLS_PER_EXT_BLOB, Cell]
new(cells)
bench("compute_cells", iters):
doAssert cttEthKzg_Success == ctx.compute_cells(cells[], b.blobs[0])
doAssert cttEthKzg_Success == ctx.compute_cells(cells[].asUnchecked(), b.blobs[0])

proc main() =
echo "PeerDAS (EIP-7594) - compute_cells Benchmark"
Expand Down
21 changes: 21 additions & 0 deletions constantine-go/constantine.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,27 @@ func (ctx EthKzgContext) VerifyBlobKzgProofBatchParallel(blobs []EthBlob, commit

type EthKzgCell [2048]byte

func (ctx EthKzgContext) ComputeCells(
blob *EthBlob,
) (cells *[128]EthKzgCell, err error) {
if blob == nil {
return nil, errors.New("ComputeCells: blob is nil")
}
cells = new([128]EthKzgCell)
status := C.ctt_eth_kzg_compute_cells(
ctx.cCtx,
(*C.ctt_eth_kzg_cell)(unsafe.Pointer(cells)),
(*C.ctt_eth_kzg_blob)(unsafe.Pointer(blob)),
)
if status != C.cttEthKzg_Success {
err = errors.New(
C.GoString(C.ctt_eth_kzg_status_to_string(status)),
)
return nil, err
}
return cells, nil
}

func (ctx EthKzgContext) ComputeCellsAndKzgProofs(
blob *EthBlob,
) (cells *[128]EthKzgCell, proofs *[128]EthKzgProof, err error) {
Expand Down
56 changes: 56 additions & 0 deletions constantine-go/eth_kzg7594_peerdas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

var (
peerdasTestDir = "../tests/protocol_ethereum_eip7594_fulu_peerdas"
computeCellsTests = filepath.Join(peerdasTestDir, "compute_cells/kzg-mainnet/*/data.yaml")
computeCellsAndProofsTests = filepath.Join(peerdasTestDir, "compute_cells_and_kzg_proofs/kzg-mainnet/*/data.yaml")
verifyCellKzgProofTests = filepath.Join(peerdasTestDir, "verify_cell_kzg_proof_batch/kzg-mainnet/*/data.yaml")
recoverCellsAndProofsTests = filepath.Join(peerdasTestDir, "recover_cells_and_kzg_proofs/kzg-mainnet/*/data.yaml")
Expand All @@ -36,6 +37,61 @@ func (dst *EthKzgCell) UnmarshalText(input []byte) error {
return fromHexImpl(dst[:], input)
}

// ---- compute_cells (cells only, no proofs) ----

type computeCellsOnlyTest struct {
Input *computeTestInput `yaml:"input"`
Output *[]string `yaml:"output"` // [cells...]
}

func TestComputeCells(t *testing.T) {
ctx, tsErr := EthKzgContextNew(trustedSetupFile)
require.NoError(t, tsErr)
defer ctx.Delete()

tests, err := filepath.Glob(computeCellsTests)
require.NoError(t, err)
require.NotEmpty(t, tests)

for _, tf := range tests {
testName := filepath.Base(filepath.Dir(tf))
raw, rErr := os.ReadFile(tf)
require.NoError(t, rErr)

var test computeCellsOnlyTest
require.NoError(t, yaml.Unmarshal(raw, &test))

// Invalid input -> no output
if test.Input == nil || test.Input.Blob == nil {
require.Nil(t, test.Output, "expected no output for missing input in %s", testName)
continue
}

var blob EthBlob
if err := fromHexImpl(blob[:], []byte(*test.Input.Blob)); err != nil {
require.Nil(t, test.Output, "expected no output for invalid blob in %s", testName)
continue
}

cells, err := ctx.ComputeCells(&blob)
if err != nil {
require.Nil(t, test.Output, "expected failure for %s", testName)
continue
}

require.NotNil(t, test.Output, "expected output for %s", testName)

expCells := *test.Output
require.Len(t, expCells, 128)

for i := 0; i < 128; i++ {
expCell, err := hex.DecodeString(expCells[i][2:])
require.NoError(t, err, "failed to decode expected cell %d in %s", i, testName)
require.Equal(t, expCell, cells[i][:], "cell %d mismatch in %s", i, testName)
}
}
}

// ---- compute_cells_and_kzg_proofs ----

type computeTestInput struct {
Expand Down
10 changes: 7 additions & 3 deletions constantine/eth_eip7594_peerdas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ func compute_cells_impl(

func compute_cells*(
ctx: ptr EthereumKZGContext,
cells: var array[CELLS_PER_EXT_BLOB, Cell],
blob: Blob): cttEthKzgStatus =
cells: ptr UncheckedArray[Cell],
blob: Blob): cttEthKzgStatus {.libPrefix: prefix_eth_kzg, raises: [].} =
Comment on lines 207 to +210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Public Nim API signature breaks

When an existing Nim consumer calls compute_cells(ctx, cells, blob) with a fixed array[CELLS_PER_EXT_BLOB, Cell], the new pointer-only signature rejects the argument, causing downstream compilation to fail. Retain a typed compatibility overload or separate the C export wrapper from the existing Nim API.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

## Compute all cells for an extended blob using the half-FFT optimization.
## This is the MOST efficient known method for computing cells.
##
Expand Down Expand Up @@ -251,6 +251,10 @@ func compute_cells*(
## e. Bit-reverse to match cell ordering
## 4. Convert cells to bytes [Serialization]

# Validate FFI pointers before dereferencing
if ctx.isNil or cells.isNil:
return cttEthKzg_InputsLengthsMismatch

const N = FIELD_ELEMENTS_PER_BLOB

# Deserialize blob to polynomial (evaluation form, bit-reversed)
Expand All @@ -265,7 +269,7 @@ func compute_cells*(
defer: freeHeapAligned(poly_coef_nat)
poly_coef_nat[].lagrangeInterpolate(poly_eval_brp[], ctx.fft_desc_ext)

return compute_cells_impl(ctx, cells, poly_eval_brp[], poly_coef_nat[])
return compute_cells_impl(ctx, cast[ptr array[CELLS_PER_EXT_BLOB, Cell]](cells)[], poly_eval_brp[], poly_coef_nat[])

func compute_cells_and_kzg_proofs*(
ctx: ptr EthereumKZGContext,
Expand Down
13 changes: 13 additions & 0 deletions include/constantine/protocols/ethereum_eip7594_peerdas.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ typedef struct { byte raw[CTT_BYTES_PER_CELL]; } ctt_eth_kzg_cell;
// Ethereum EIP-7594 PeerDAS Interface
// ------------------------------------------------------------------------------------------------

/** Compute all cells for an extended blob, without KZG proofs.
*
* @param ctx KZG context (trusted setup)
* @param cells Output: array of 128 cells (caller-allocated)
* @param blob Input: the blob to compute cells for
* @return cttEthKzg_Success on success, error status otherwise
*/
ctt_eth_kzg_status ctt_eth_kzg_compute_cells(
const ctt_eth_kzg_context* ctx,
ctt_eth_kzg_cell* cells,
const ctt_eth_kzg_blob* blob
) __attribute__((warn_unused_result));

/** Compute all cells and KZG proofs for an extended blob using the FK20 algorithm.
*
* @param ctx KZG context (trusted setup)
Expand Down
2 changes: 1 addition & 1 deletion tests/eth_eip7594_peerdas/t_cells_and_kzg_proofs_opt.nim
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func compute_cells_and_kzg_proofs_naive(
poly_monomial.lagrangeInterpolate(poly_lagrange, ctx.fft_desc_ext)

# Compute cells using the public API
let cells_status = compute_cells(ctx, cells, blob)
let cells_status = compute_cells(ctx, cells.asUnchecked(), blob)
if cells_status != cttEthKzg_Success:
return cells_status

Expand Down
4 changes: 2 additions & 2 deletions tests/eth_eip7594_peerdas/t_compute_cells_opt.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import
constantine/eth_eip7594_peerdas {.all.},
constantine/ethereum_eip4844_kzg,
constantine/serialization/codecs,
constantine/platforms/allocs,
constantine/platforms/[allocs, primitives],
# Shared test utilities
../testutils/eth_consensus_utils

Expand Down Expand Up @@ -179,7 +179,7 @@ suite "EIP-7594 PeerDAS - compute_cells [" & test_case & "]":

var cells_opt: array[CELLS_PER_EXT_BLOB, Cell]

let status = compute_cells(ctx, cells_opt, blob[])
let status = compute_cells(ctx, cells_opt.asUnchecked(), blob[])

doAssert status == cttEthKzg_Success, "compute_cells failed: " & $status
let expectedCells = testData["output"].parseCells()
Expand Down
12 changes: 6 additions & 6 deletions tests/eth_eip7594_peerdas/t_peerdas_recovery.nim
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ proc test_recover_from_64_cells*(ctx: ptr EthereumKZGContext) =
echo " Blob created"

var cells: array[CELLS_PER_EXT_BLOB, Cell]
let status = ctx.compute_cells(cells, blob)
let status = ctx.compute_cells(cells.asUnchecked(), blob)
echo " compute_cells status: ", status
doAssert status == cttEthKzg_Success

Expand Down Expand Up @@ -84,7 +84,7 @@ proc test_recover_from_65_cells*(ctx: ptr EthereumKZGContext) =
let blob = build_test_blob()

var cells: array[CELLS_PER_EXT_BLOB, Cell]
let status = ctx.compute_cells(cells, blob)
let status = ctx.compute_cells(cells.asUnchecked(), blob)
doAssert status == cttEthKzg_Success

var available_indices: seq[CellIndex]
Expand Down Expand Up @@ -120,7 +120,7 @@ proc test_recover_from_all_cells*(ctx: ptr EthereumKZGContext) =
let blob = build_test_blob()

var cells: array[CELLS_PER_EXT_BLOB, Cell]
let status = ctx.compute_cells(cells, blob)
let status = ctx.compute_cells(cells.asUnchecked(), blob)
doAssert status == cttEthKzg_Success

var available_indices: seq[CellIndex]
Expand Down Expand Up @@ -153,7 +153,7 @@ proc test_recover_alternate_indices*(ctx: ptr EthereumKZGContext) =
let blob = build_test_blob()

var cells: array[CELLS_PER_EXT_BLOB, Cell]
let status = ctx.compute_cells(cells, blob)
let status = ctx.compute_cells(cells.asUnchecked(), blob)
doAssert status == cttEthKzg_Success

var available_indices: seq[CellIndex]
Expand Down Expand Up @@ -187,7 +187,7 @@ proc test_too_few_cells_error*(ctx: ptr EthereumKZGContext) =
let blob = build_test_blob()

var cells: array[CELLS_PER_EXT_BLOB, Cell]
let status = ctx.compute_cells(cells, blob)
let status = ctx.compute_cells(cells.asUnchecked(), blob)
doAssert status == cttEthKzg_Success

var available_indices: seq[CellIndex]
Expand Down Expand Up @@ -216,7 +216,7 @@ proc test_duplicate_indices_error*(ctx: ptr EthereumKZGContext) =
let blob = build_test_blob()

var cells: array[CELLS_PER_EXT_BLOB, Cell]
let status = ctx.compute_cells(cells, blob)
let status = ctx.compute_cells(cells.asUnchecked(), blob)
doAssert status == cttEthKzg_Success

var available_indices: seq[CellIndex]
Expand Down
2 changes: 1 addition & 1 deletion tests/t_eth_eip7594_peerdas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ TestVectorsDir.testGen(compute_cells, "kzg-mainnet", testVector):
parseAssign(testVector, blob, BYTES_PER_BLOB, testVector["input"]["blob"].content)

var cells: array[CELLS_PER_EXT_BLOB, Cell]
let status = compute_cells(ctx, cells, blob[])
let status = compute_cells(ctx, cells.asUnchecked(), blob[])
stdout.write "[" & $status & "]\n"

if status == cttEthKzg_Success:
Expand Down
Loading