#include "eip7594/eip7594.h"
#include "common/alloc.h"
#include "common/ec.h"
#include "common/fr.h"
#include "common/lincomb.h"
#include "common/utils.h"
#include "eip7594/fft.h"
#include "eip7594/fk20.h"
#include "eip7594/poly.h"
#include "eip7594/recovery.h"
#include <assert.h>
#include <string.h>
#define DOMAIN_STR_LENGTH 16
static const char *RANDOM_CHALLENGE_DOMAIN_VERIFY_CELL_KZG_PROOF_BATCH = "RCKZGCBATCH__V1_";
C_KZG_RET compute_cells_and_kzg_proofs(
Cell *cells, KZGProof *proofs, const Blob *blob, const KZGSettings *s
) {
C_KZG_RET ret;
fr_t *poly_monomial = NULL;
fr_t *poly_lagrange = NULL;
fr_t *data_fr = NULL;
g1_t *proofs_g1 = NULL;
blst_p1_affine *proofs_affine = NULL;
if (cells == NULL && proofs == NULL) {
return C_KZG_BADARGS;
}
ret = new_fr_array(&poly_monomial, FIELD_ELEMENTS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&poly_lagrange, FIELD_ELEMENTS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = blob_to_polynomial(poly_lagrange, blob);
if (ret != C_KZG_OK) goto out;
ret = poly_lagrange_to_monomial(poly_monomial, poly_lagrange, FIELD_ELEMENTS_PER_BLOB, s);
if (ret != C_KZG_OK) goto out;
for (size_t i = FIELD_ELEMENTS_PER_BLOB; i < FIELD_ELEMENTS_PER_EXT_BLOB; i++) {
assert(fr_equal(&poly_monomial[i], &FR_ZERO));
}
if (cells != NULL) {
ret = new_fr_array(&data_fr, FIELD_ELEMENTS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = fr_fft(data_fr, poly_monomial, FIELD_ELEMENTS_PER_EXT_BLOB, s);
if (ret != C_KZG_OK) goto out;
ret = bit_reversal_permutation(data_fr, sizeof(fr_t), FIELD_ELEMENTS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) {
for (size_t j = 0; j < FIELD_ELEMENTS_PER_CELL; j++) {
size_t index = i * FIELD_ELEMENTS_PER_CELL + j;
size_t offset = j * BYTES_PER_FIELD_ELEMENT;
bytes_from_bls_field((Bytes32 *)&cells[i].bytes[offset], &data_fr[index]);
}
}
}
if (proofs != NULL) {
ret = new_g1_array(&proofs_g1, CELLS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = compute_fk20_cell_proofs(proofs_g1, poly_monomial, s);
if (ret != C_KZG_OK) goto out;
ret = bit_reversal_permutation(proofs_g1, sizeof(g1_t), CELLS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = c_kzg_malloc((void **)&proofs_affine, CELLS_PER_EXT_BLOB * sizeof(blst_p1_affine));
if (ret != C_KZG_OK) goto out;
const blst_p1 *proofs_arg[2] = {proofs_g1, NULL};
blst_p1s_to_affine(proofs_affine, proofs_arg, CELLS_PER_EXT_BLOB);
for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) {
blst_p1_affine_compress(proofs[i].bytes, &proofs_affine[i]);
}
}
out:
c_kzg_free(poly_monomial);
c_kzg_free(poly_lagrange);
c_kzg_free(data_fr);
c_kzg_free(proofs_g1);
c_kzg_free(proofs_affine);
return ret;
}
C_KZG_RET recover_cells_and_kzg_proofs(
Cell *recovered_cells,
KZGProof *recovered_proofs,
const uint64_t *cell_indices,
const Cell *cells,
uint64_t num_cells,
const KZGSettings *s
) {
C_KZG_RET ret;
fr_t *recovered_cells_fr = NULL;
g1_t *recovered_proofs_g1 = NULL;
blst_p1_affine *recovered_proofs_affine = NULL;
if (num_cells > CELLS_PER_EXT_BLOB) {
ret = C_KZG_BADARGS;
goto out;
}
if (num_cells < CELLS_PER_BLOB) {
ret = C_KZG_BADARGS;
goto out;
}
for (size_t i = 0; i < num_cells; i++) {
if (cell_indices[i] >= CELLS_PER_EXT_BLOB) {
ret = C_KZG_BADARGS;
goto out;
}
if (i > 0 && cell_indices[i] <= cell_indices[i - 1]) {
ret = C_KZG_BADARGS;
goto out;
}
}
ret = new_fr_array(&recovered_cells_fr, FIELD_ELEMENTS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = new_g1_array(&recovered_proofs_g1, CELLS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
for (size_t i = 0; i < FIELD_ELEMENTS_PER_EXT_BLOB; i++) {
recovered_cells_fr[i] = FR_ZERO;
}
for (size_t i = 0; i < num_cells; i++) {
size_t index = cell_indices[i] * FIELD_ELEMENTS_PER_CELL;
for (size_t j = 0; j < FIELD_ELEMENTS_PER_CELL; j++) {
fr_t *ptr = &recovered_cells_fr[index + j];
size_t offset = j * BYTES_PER_FIELD_ELEMENT;
ret = bytes_to_bls_field(ptr, (const Bytes32 *)&cells[i].bytes[offset]);
if (ret != C_KZG_OK) goto out;
}
}
if (num_cells == CELLS_PER_EXT_BLOB) {
for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) {
recovered_cells[i] = cells[i];
}
} else {
ret = recover_cells(recovered_cells_fr, cell_indices, num_cells, recovered_cells_fr, s);
if (ret != C_KZG_OK) goto out;
for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) {
for (size_t j = 0; j < FIELD_ELEMENTS_PER_CELL; j++) {
size_t index = i * FIELD_ELEMENTS_PER_CELL + j;
size_t offset = j * BYTES_PER_FIELD_ELEMENT;
bytes_from_bls_field(
(Bytes32 *)&recovered_cells[i].bytes[offset], &recovered_cells_fr[index]
);
}
}
}
if (recovered_proofs != NULL) {
ret = poly_lagrange_to_monomial(
recovered_cells_fr, recovered_cells_fr, FIELD_ELEMENTS_PER_EXT_BLOB, s
);
if (ret != C_KZG_OK) goto out;
ret = compute_fk20_cell_proofs(recovered_proofs_g1, recovered_cells_fr, s);
if (ret != C_KZG_OK) goto out;
ret = bit_reversal_permutation(recovered_proofs_g1, sizeof(g1_t), CELLS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = c_kzg_malloc(
(void **)&recovered_proofs_affine, CELLS_PER_EXT_BLOB * sizeof(blst_p1_affine)
);
if (ret != C_KZG_OK) goto out;
const blst_p1 *recovered_proofs_arg[2] = {recovered_proofs_g1, NULL};
blst_p1s_to_affine(recovered_proofs_affine, recovered_proofs_arg, CELLS_PER_EXT_BLOB);
for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) {
blst_p1_affine_compress(recovered_proofs[i].bytes, &recovered_proofs_affine[i]);
}
}
out:
c_kzg_free(recovered_cells_fr);
c_kzg_free(recovered_proofs_g1);
c_kzg_free(recovered_proofs_affine);
return ret;
}
static bool commitments_equal(const Bytes48 *a, const Bytes48 *b) {
return memcmp(a->bytes, b->bytes, BYTES_PER_COMMITMENT) == 0;
}
static void commitments_copy(Bytes48 *dst, const Bytes48 *src) {
memcpy(dst->bytes, src->bytes, BYTES_PER_COMMITMENT);
}
static void deduplicate_commitments(
Bytes48 *commitments_out, uint64_t *indices_out, size_t *count_out
) {
if (*count_out == 0) return;
indices_out[0] = 0;
size_t new_count = 1;
for (size_t i = 1; i < *count_out; i++) {
bool exist = false;
for (size_t j = 0; j < new_count; j++) {
if (commitments_equal(&commitments_out[i], &commitments_out[j])) {
indices_out[i] = j;
exist = true;
break;
}
}
if (!exist) {
commitments_copy(&commitments_out[new_count], &commitments_out[i]);
indices_out[i] = new_count;
new_count++;
}
}
*count_out = new_count;
}
C_KZG_RET compute_verify_cell_kzg_proof_batch_challenge(
fr_t *challenge_out,
const Bytes48 *commitments_bytes,
uint64_t num_commitments,
const uint64_t *commitment_indices,
const uint64_t *cell_indices,
const Cell *cells,
const Bytes48 *proofs_bytes,
uint64_t num_cells
) {
C_KZG_RET ret;
uint8_t *bytes = NULL;
Bytes32 r_bytes;
size_t input_size = DOMAIN_STR_LENGTH
+ sizeof(uint64_t)
+ sizeof(uint64_t)
+ sizeof(uint64_t)
+ sizeof(uint64_t)
+ (num_commitments * BYTES_PER_COMMITMENT)
+ (num_cells * sizeof(uint64_t))
+ (num_cells * sizeof(uint64_t))
+ (num_cells * BYTES_PER_CELL)
+ (num_cells * BYTES_PER_PROOF);
ret = c_kzg_malloc((void **)&bytes, input_size);
if (ret != C_KZG_OK) goto out;
uint8_t *offset = bytes;
assert(strlen(RANDOM_CHALLENGE_DOMAIN_VERIFY_CELL_KZG_PROOF_BATCH) == DOMAIN_STR_LENGTH);
memcpy(offset, RANDOM_CHALLENGE_DOMAIN_VERIFY_CELL_KZG_PROOF_BATCH, DOMAIN_STR_LENGTH);
offset += DOMAIN_STR_LENGTH;
bytes_from_uint64(offset, FIELD_ELEMENTS_PER_BLOB);
offset += sizeof(uint64_t);
bytes_from_uint64(offset, FIELD_ELEMENTS_PER_CELL);
offset += sizeof(uint64_t);
bytes_from_uint64(offset, num_commitments);
offset += sizeof(uint64_t);
bytes_from_uint64(offset, num_cells);
offset += sizeof(uint64_t);
for (size_t i = 0; i < num_commitments; i++) {
memcpy(offset, &commitments_bytes[i], BYTES_PER_COMMITMENT);
offset += BYTES_PER_COMMITMENT;
}
for (size_t i = 0; i < num_cells; i++) {
bytes_from_uint64(offset, commitment_indices[i]);
offset += sizeof(uint64_t);
bytes_from_uint64(offset, cell_indices[i]);
offset += sizeof(uint64_t);
memcpy(offset, &cells[i], BYTES_PER_CELL);
offset += BYTES_PER_CELL;
memcpy(offset, &proofs_bytes[i], BYTES_PER_PROOF);
offset += BYTES_PER_PROOF;
}
assert(offset == bytes + input_size);
blst_sha256(r_bytes.bytes, bytes, input_size);
hash_to_bls_field(challenge_out, &r_bytes);
out:
c_kzg_free(bytes);
return ret;
}
static C_KZG_RET compute_weighted_sum_of_commitments(
g1_t *sum_of_commitments_out,
const Bytes48 *unique_commitments,
const uint64_t *commitment_indices,
const fr_t *r_powers,
size_t num_commitments,
uint64_t num_cells
) {
C_KZG_RET ret;
g1_t *commitments_g1 = NULL;
fr_t *commitment_weights = NULL;
ret = new_fr_array(&commitment_weights, num_commitments);
if (ret != C_KZG_OK) goto out;
ret = new_g1_array(&commitments_g1, num_commitments);
if (ret != C_KZG_OK) goto out;
for (size_t i = 0; i < num_commitments; i++) {
ret = bytes_to_kzg_commitment(&commitments_g1[i], &unique_commitments[i]);
if (ret != C_KZG_OK) goto out;
commitment_weights[i] = FR_ZERO;
}
for (uint64_t i = 0; i < num_cells; i++) {
fr_add(
&commitment_weights[commitment_indices[i]],
&commitment_weights[commitment_indices[i]],
&r_powers[i]
);
}
ret = g1_lincomb_fast(
sum_of_commitments_out, commitments_g1, commitment_weights, num_commitments
);
if (ret != C_KZG_OK) goto out;
out:
c_kzg_free(commitment_weights);
c_kzg_free(commitments_g1);
return ret;
}
static void get_inv_coset_shift_for_cell(
fr_t *inv_coset_factor_out, uint64_t cell_index, const KZGSettings *s
) {
uint64_t cell_idx_rbl = reverse_bits_limited(CELLS_PER_EXT_BLOB, cell_index);
assert(cell_idx_rbl <= FIELD_ELEMENTS_PER_EXT_BLOB);
uint64_t inv_coset_factor_idx = FIELD_ELEMENTS_PER_EXT_BLOB - cell_idx_rbl;
assert(inv_coset_factor_idx < FIELD_ELEMENTS_PER_EXT_BLOB + 1);
*inv_coset_factor_out = s->roots_of_unity[inv_coset_factor_idx];
}
static void get_coset_shift_pow_for_cell(
fr_t *coset_factor_out, uint64_t cell_index, const KZGSettings *s
) {
uint64_t cell_idx_rbl = reverse_bits_limited(CELLS_PER_EXT_BLOB, cell_index);
uint64_t h_k_pow_idx = cell_idx_rbl * FIELD_ELEMENTS_PER_CELL;
assert(h_k_pow_idx < FIELD_ELEMENTS_PER_EXT_BLOB + 1);
*coset_factor_out = s->roots_of_unity[h_k_pow_idx];
}
static C_KZG_RET compute_commitment_to_aggregated_interpolation_poly(
g1_t *commitment_out,
const fr_t *r_powers,
const uint64_t *cell_indices,
const Cell *cells,
uint64_t num_cells,
const KZGSettings *s
) {
C_KZG_RET ret;
bool *is_cell_used = NULL;
fr_t *aggregated_column_cells = NULL;
fr_t *column_interpolation_poly = NULL;
fr_t *aggregated_interpolation_poly = NULL;
ret = new_bool_array(&is_cell_used, CELLS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&aggregated_column_cells, FIELD_ELEMENTS_PER_EXT_BLOB);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&column_interpolation_poly, FIELD_ELEMENTS_PER_CELL);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&aggregated_interpolation_poly, FIELD_ELEMENTS_PER_CELL);
if (ret != C_KZG_OK) goto out;
for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) {
for (size_t j = 0; j < FIELD_ELEMENTS_PER_CELL; j++) {
size_t index = i * FIELD_ELEMENTS_PER_CELL + j;
aggregated_column_cells[index] = FR_ZERO;
}
}
for (uint64_t cell_index = 0; cell_index < num_cells; cell_index++) {
uint64_t column_index = cell_indices[cell_index];
for (size_t fr_index = 0; fr_index < FIELD_ELEMENTS_PER_CELL; fr_index++) {
fr_t original_fr, scaled_fr;
size_t offset = fr_index * BYTES_PER_FIELD_ELEMENT;
ret = bytes_to_bls_field(
&original_fr, (const Bytes32 *)&cells[cell_index].bytes[offset]
);
if (ret != C_KZG_OK) goto out;
fr_mul(&scaled_fr, &original_fr, &r_powers[cell_index]);
size_t array_index = column_index * FIELD_ELEMENTS_PER_CELL + fr_index;
fr_add(
&aggregated_column_cells[array_index],
&aggregated_column_cells[array_index],
&scaled_fr
);
}
}
for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) {
is_cell_used[i] = false;
}
for (uint64_t i = 0; i < num_cells; i++) {
is_cell_used[cell_indices[i]] = true;
}
for (size_t i = 0; i < FIELD_ELEMENTS_PER_CELL; i++) {
aggregated_interpolation_poly[i] = FR_ZERO;
}
for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) {
if (!is_cell_used[i]) continue;
size_t index = i * FIELD_ELEMENTS_PER_CELL;
ret = bit_reversal_permutation(
&aggregated_column_cells[index], sizeof(fr_t), FIELD_ELEMENTS_PER_CELL
);
if (ret != C_KZG_OK) goto out;
ret = fr_ifft(
column_interpolation_poly, &aggregated_column_cells[index], FIELD_ELEMENTS_PER_CELL, s
);
if (ret != C_KZG_OK) goto out;
fr_t inv_coset_factor;
get_inv_coset_shift_for_cell(&inv_coset_factor, i, s);
shift_poly(column_interpolation_poly, FIELD_ELEMENTS_PER_CELL, &inv_coset_factor);
for (size_t k = 0; k < FIELD_ELEMENTS_PER_CELL; k++) {
fr_add(
&aggregated_interpolation_poly[k],
&aggregated_interpolation_poly[k],
&column_interpolation_poly[k]
);
}
}
ret = g1_lincomb_fast(
commitment_out,
s->g1_values_monomial,
aggregated_interpolation_poly,
FIELD_ELEMENTS_PER_CELL
);
if (ret != C_KZG_OK) goto out;
out:
c_kzg_free(is_cell_used);
c_kzg_free(aggregated_column_cells);
c_kzg_free(column_interpolation_poly);
c_kzg_free(aggregated_interpolation_poly);
return ret;
}
static C_KZG_RET computed_weighted_sum_of_proofs(
g1_t *weighted_proof_sum_out,
const g1_t *proofs_g1,
const fr_t *r_powers,
const uint64_t *cell_indices,
uint64_t num_cells,
const KZGSettings *s
) {
C_KZG_RET ret;
fr_t *weighted_powers_of_r = NULL;
ret = new_fr_array(&weighted_powers_of_r, num_cells);
if (ret != C_KZG_OK) goto out;
for (uint64_t i = 0; i < num_cells; i++) {
fr_t h_k_pow;
get_coset_shift_pow_for_cell(&h_k_pow, cell_indices[i], s);
fr_mul(&weighted_powers_of_r[i], &r_powers[i], &h_k_pow);
}
ret = g1_lincomb_fast(weighted_proof_sum_out, proofs_g1, weighted_powers_of_r, num_cells);
out:
c_kzg_free(weighted_powers_of_r);
return ret;
}
C_KZG_RET verify_cell_kzg_proof_batch(
bool *ok,
const Bytes48 *commitments_bytes,
const uint64_t *cell_indices,
const Cell *cells,
const Bytes48 *proofs_bytes,
uint64_t num_cells,
const KZGSettings *s
) {
C_KZG_RET ret;
fr_t r;
g1_t interpolation_poly_commit;
g1_t final_g1_sum;
g1_t proof_lincomb;
g1_t weighted_sum_of_proofs;
g2_t power_of_s = s->g2_values_monomial[FIELD_ELEMENTS_PER_CELL];
size_t num_commitments;
Bytes48 *unique_commitments = NULL;
uint64_t *commitment_indices = NULL;
fr_t *r_powers = NULL;
g1_t *proofs_g1 = NULL;
*ok = false;
if (num_cells == 0) {
*ok = true;
return C_KZG_OK;
}
for (size_t i = 0; i < num_cells; i++) {
if (cell_indices[i] >= CELLS_PER_EXT_BLOB) return C_KZG_BADARGS;
}
ret = c_kzg_calloc((void **)&unique_commitments, num_cells, sizeof(Bytes48));
if (ret != C_KZG_OK) goto out;
ret = c_kzg_calloc((void **)&commitment_indices, num_cells, sizeof(uint64_t));
if (ret != C_KZG_OK) goto out;
num_commitments = num_cells;
memcpy(unique_commitments, commitments_bytes, num_cells * sizeof(Bytes48));
deduplicate_commitments(unique_commitments, commitment_indices, &num_commitments);
ret = new_fr_array(&r_powers, num_cells);
if (ret != C_KZG_OK) goto out;
ret = new_g1_array(&proofs_g1, num_cells);
if (ret != C_KZG_OK) goto out;
ret = compute_verify_cell_kzg_proof_batch_challenge(
&r,
unique_commitments,
num_commitments,
commitment_indices,
cell_indices,
cells,
proofs_bytes,
num_cells
);
if (ret != C_KZG_OK) goto out;
compute_powers(r_powers, &r, num_cells);
for (size_t i = 0; i < num_cells; i++) {
ret = bytes_to_kzg_proof(&proofs_g1[i], &proofs_bytes[i]);
if (ret != C_KZG_OK) goto out;
}
ret = g1_lincomb_fast(&proof_lincomb, proofs_g1, r_powers, num_cells);
if (ret != C_KZG_OK) goto out;
ret = compute_weighted_sum_of_commitments(
&final_g1_sum, unique_commitments, commitment_indices, r_powers, num_commitments, num_cells
);
if (ret != C_KZG_OK) goto out;
ret = compute_commitment_to_aggregated_interpolation_poly(
&interpolation_poly_commit, r_powers, cell_indices, cells, num_cells, s
);
if (ret != C_KZG_OK) goto out;
g1_sub(&final_g1_sum, &final_g1_sum, &interpolation_poly_commit);
ret = computed_weighted_sum_of_proofs(
&weighted_sum_of_proofs, proofs_g1, r_powers, cell_indices, num_cells, s
);
if (ret != C_KZG_OK) goto out;
g1_add(&final_g1_sum, &final_g1_sum, &weighted_sum_of_proofs);
*ok = pairings_verify(&final_g1_sum, blst_p2_generator(), &proof_lincomb, &power_of_s);
out:
c_kzg_free(unique_commitments);
c_kzg_free(commitment_indices);
c_kzg_free(r_powers);
c_kzg_free(proofs_g1);
return ret;
}