#include "eip4844/eip4844.h"
#include "common/alloc.h"
#include "common/ec.h"
#include "common/fr.h"
#include "common/lincomb.h"
#include "common/ret.h"
#include "common/utils.h"
#include "setup/settings.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define DOMAIN_STR_LENGTH 16
#define CHALLENGE_INPUT_SIZE (DOMAIN_STR_LENGTH + 16 + BYTES_PER_BLOB + BYTES_PER_COMMITMENT)
static const char *FIAT_SHAMIR_PROTOCOL_DOMAIN = "FSBLOBVERIFY_V1_";
static const char *RANDOM_CHALLENGE_DOMAIN_VERIFY_BLOB_KZG_PROOF_BATCH = "RCKZGBATCH___V1_";
static bool fr_is_zero(const fr_t *fr) {
uint64_t a[4];
blst_uint64_from_fr(a, fr);
return a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0;
}
static C_KZG_RET fr_batch_inv(fr_t *out, const fr_t *a, int len) {
int i;
assert(len > 0);
assert(a != out);
fr_t accumulator = FR_ONE;
for (i = 0; i < len; i++) {
out[i] = accumulator;
fr_mul(&accumulator, &accumulator, &a[i]);
}
if (fr_is_zero(&accumulator)) {
return C_KZG_BADARGS;
}
fr_inv(&accumulator, &accumulator);
for (i = len - 1; i >= 0; i--) {
fr_mul(&out[i], &out[i], &accumulator);
fr_mul(&accumulator, &accumulator, &a[i]);
}
return C_KZG_OK;
}
static void g2_mul(g2_t *out, const g2_t *a, const fr_t *b) {
blst_scalar s;
blst_scalar_from_fr(&s, b);
blst_p2_mult(out, a, s.b, BITS_PER_FIELD_ELEMENT);
}
static void g2_sub(g2_t *out, const g2_t *a, const g2_t *b) {
g2_t bneg = *b;
blst_p2_cneg(&bneg, true);
blst_p2_add_or_double(out, a, &bneg);
}
void compute_challenge(fr_t *eval_challenge_out, const Blob *blob, const g1_t *commitment) {
Bytes32 eval_challenge;
uint8_t bytes[CHALLENGE_INPUT_SIZE];
uint8_t *offset = bytes;
memcpy(offset, FIAT_SHAMIR_PROTOCOL_DOMAIN, DOMAIN_STR_LENGTH);
offset += DOMAIN_STR_LENGTH;
bytes_from_uint64(offset, 0);
offset += sizeof(uint64_t);
bytes_from_uint64(offset, FIELD_ELEMENTS_PER_BLOB);
offset += sizeof(uint64_t);
memcpy(offset, blob->bytes, BYTES_PER_BLOB);
offset += BYTES_PER_BLOB;
bytes_from_g1((Bytes48 *)offset, commitment);
offset += BYTES_PER_COMMITMENT;
assert(offset == bytes + CHALLENGE_INPUT_SIZE);
blst_sha256(eval_challenge.bytes, bytes, CHALLENGE_INPUT_SIZE);
hash_to_bls_field(eval_challenge_out, &eval_challenge);
}
static C_KZG_RET evaluate_polynomial_in_evaluation_form(
fr_t *out, const fr_t *poly, const fr_t *x, const KZGSettings *s
) {
C_KZG_RET ret;
fr_t tmp;
fr_t *inverses_in = NULL;
fr_t *inverses = NULL;
uint64_t i;
const fr_t *brp_roots_of_unity = s->brp_roots_of_unity;
ret = new_fr_array(&inverses_in, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&inverses, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
for (i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) {
if (fr_equal(x, &brp_roots_of_unity[i])) {
*out = poly[i];
ret = C_KZG_OK;
goto out;
}
fr_sub(&inverses_in[i], x, &brp_roots_of_unity[i]);
}
ret = fr_batch_inv(inverses, inverses_in, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
*out = FR_ZERO;
for (i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) {
fr_mul(&tmp, &inverses[i], &brp_roots_of_unity[i]);
fr_mul(&tmp, &tmp, &poly[i]);
fr_add(out, out, &tmp);
}
fr_from_uint64(&tmp, FIELD_ELEMENTS_PER_BLOB);
fr_div(out, out, &tmp);
fr_pow(&tmp, x, FIELD_ELEMENTS_PER_BLOB);
fr_sub(&tmp, &tmp, &FR_ONE);
fr_mul(out, out, &tmp);
out:
c_kzg_free(inverses_in);
c_kzg_free(inverses);
return ret;
}
static C_KZG_RET poly_to_kzg_commitment(g1_t *out, const fr_t *poly, const KZGSettings *s) {
return g1_lincomb_fast(out, s->g1_values_lagrange_brp, poly, FIELD_ELEMENTS_PER_BLOB);
}
C_KZG_RET blob_to_kzg_commitment(KZGCommitment *out, const Blob *blob, const KZGSettings *s) {
C_KZG_RET ret;
fr_t *poly = NULL;
g1_t commitment;
ret = new_fr_array(&poly, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
ret = blob_to_polynomial(poly, blob);
if (ret != C_KZG_OK) goto out;
ret = poly_to_kzg_commitment(&commitment, poly, s);
if (ret != C_KZG_OK) goto out;
bytes_from_g1(out, &commitment);
out:
c_kzg_free(poly);
return ret;
}
static C_KZG_RET verify_kzg_proof_impl(
bool *ok,
const g1_t *commitment,
const fr_t *z,
const fr_t *y,
const g1_t *proof,
const KZGSettings *s
);
C_KZG_RET verify_kzg_proof(
bool *ok,
const Bytes48 *commitment_bytes,
const Bytes32 *z_bytes,
const Bytes32 *y_bytes,
const Bytes48 *proof_bytes,
const KZGSettings *s
) {
C_KZG_RET ret;
fr_t z_fr, y_fr;
g1_t commitment_g1, proof_g1;
*ok = false;
ret = bytes_to_kzg_commitment(&commitment_g1, commitment_bytes);
if (ret != C_KZG_OK) return ret;
ret = bytes_to_bls_field(&z_fr, z_bytes);
if (ret != C_KZG_OK) return ret;
ret = bytes_to_bls_field(&y_fr, y_bytes);
if (ret != C_KZG_OK) return ret;
ret = bytes_to_kzg_proof(&proof_g1, proof_bytes);
if (ret != C_KZG_OK) return ret;
return verify_kzg_proof_impl(ok, &commitment_g1, &z_fr, &y_fr, &proof_g1, s);
}
static C_KZG_RET verify_kzg_proof_impl(
bool *ok,
const g1_t *commitment,
const fr_t *z,
const fr_t *y,
const g1_t *proof,
const KZGSettings *s
) {
g2_t x_g2, X_minus_z;
g1_t y_g1, P_minus_y;
g2_mul(&x_g2, blst_p2_generator(), z);
g2_sub(&X_minus_z, &s->g2_values_monomial[1], &x_g2);
g1_mul(&y_g1, blst_p1_generator(), y);
g1_sub(&P_minus_y, commitment, &y_g1);
*ok = pairings_verify(&P_minus_y, blst_p2_generator(), proof, &X_minus_z);
return C_KZG_OK;
}
static C_KZG_RET compute_kzg_proof_impl(
KZGProof *proof_out, fr_t *y_out, const fr_t *poly, const fr_t *z, const KZGSettings *s
);
C_KZG_RET compute_kzg_proof(
KZGProof *proof_out,
Bytes32 *y_out,
const Blob *blob,
const Bytes32 *z_bytes,
const KZGSettings *s
) {
C_KZG_RET ret;
fr_t *poly = NULL;
fr_t frz, fry;
ret = new_fr_array(&poly, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
ret = blob_to_polynomial(poly, blob);
if (ret != C_KZG_OK) goto out;
ret = bytes_to_bls_field(&frz, z_bytes);
if (ret != C_KZG_OK) goto out;
ret = compute_kzg_proof_impl(proof_out, &fry, poly, &frz, s);
if (ret != C_KZG_OK) goto out;
bytes_from_bls_field(y_out, &fry);
out:
c_kzg_free(poly);
return ret;
}
static C_KZG_RET compute_kzg_proof_impl(
KZGProof *proof_out, fr_t *y_out, const fr_t *poly, const fr_t *z, const KZGSettings *s
) {
C_KZG_RET ret;
fr_t *inverses_in = NULL;
fr_t *inverses = NULL;
fr_t *q_poly = NULL;
ret = evaluate_polynomial_in_evaluation_form(y_out, poly, z, s);
if (ret != C_KZG_OK) goto out;
fr_t tmp;
const fr_t *brp_roots_of_unity = s->brp_roots_of_unity;
uint64_t i;
uint64_t m = 0;
ret = new_fr_array(&inverses_in, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&inverses, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&q_poly, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
for (i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) {
if (fr_equal(z, &brp_roots_of_unity[i])) {
m = i + 1;
inverses_in[i] = FR_ONE;
continue;
}
fr_sub(&q_poly[i], &poly[i], y_out);
fr_sub(&inverses_in[i], &brp_roots_of_unity[i], z);
}
ret = fr_batch_inv(inverses, inverses_in, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
for (i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) {
fr_mul(&q_poly[i], &q_poly[i], &inverses[i]);
}
if (m != 0) {
q_poly[--m] = FR_ZERO;
for (i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) {
if (i == m) continue;
fr_sub(&tmp, z, &brp_roots_of_unity[i]);
fr_mul(&inverses_in[i], &tmp, z);
}
ret = fr_batch_inv(inverses, inverses_in, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
for (i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) {
if (i == m) continue;
fr_sub(&tmp, &poly[i], y_out);
fr_mul(&tmp, &tmp, &brp_roots_of_unity[i]);
fr_mul(&tmp, &tmp, &inverses[i]);
fr_add(&q_poly[m], &q_poly[m], &tmp);
}
}
g1_t out_g1;
ret = g1_lincomb_fast(&out_g1, s->g1_values_lagrange_brp, q_poly, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
bytes_from_g1(proof_out, &out_g1);
out:
c_kzg_free(inverses_in);
c_kzg_free(inverses);
c_kzg_free(q_poly);
return ret;
}
C_KZG_RET compute_blob_kzg_proof(
KZGProof *out, const Blob *blob, const Bytes48 *commitment_bytes, const KZGSettings *s
) {
C_KZG_RET ret;
fr_t *poly = NULL;
g1_t commitment_g1;
fr_t evaluation_challenge_fr;
fr_t y;
ret = new_fr_array(&poly, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
ret = bytes_to_kzg_commitment(&commitment_g1, commitment_bytes);
if (ret != C_KZG_OK) goto out;
ret = blob_to_polynomial(poly, blob);
if (ret != C_KZG_OK) goto out;
compute_challenge(&evaluation_challenge_fr, blob, &commitment_g1);
ret = compute_kzg_proof_impl(out, &y, poly, &evaluation_challenge_fr, s);
if (ret != C_KZG_OK) goto out;
out:
c_kzg_free(poly);
return ret;
}
C_KZG_RET verify_blob_kzg_proof(
bool *ok,
const Blob *blob,
const Bytes48 *commitment_bytes,
const Bytes48 *proof_bytes,
const KZGSettings *s
) {
C_KZG_RET ret;
fr_t *poly = NULL;
fr_t evaluation_challenge_fr, y_fr;
g1_t commitment_g1, proof_g1;
*ok = false;
ret = new_fr_array(&poly, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
ret = bytes_to_kzg_commitment(&commitment_g1, commitment_bytes);
if (ret != C_KZG_OK) goto out;
ret = blob_to_polynomial(poly, blob);
if (ret != C_KZG_OK) goto out;
ret = bytes_to_kzg_proof(&proof_g1, proof_bytes);
if (ret != C_KZG_OK) goto out;
compute_challenge(&evaluation_challenge_fr, blob, &commitment_g1);
ret = evaluate_polynomial_in_evaluation_form(&y_fr, poly, &evaluation_challenge_fr, s);
if (ret != C_KZG_OK) goto out;
ret = verify_kzg_proof_impl(ok, &commitment_g1, &evaluation_challenge_fr, &y_fr, &proof_g1, s);
if (ret != C_KZG_OK) goto out;
out:
c_kzg_free(poly);
return ret;
}
static C_KZG_RET compute_r_powers_for_verify_kzg_proof_batch(
fr_t *r_powers_out,
const g1_t *commitments_g1,
const fr_t *zs_fr,
const fr_t *ys_fr,
const g1_t *proofs_g1,
size_t n
) {
C_KZG_RET ret;
uint8_t *bytes = NULL;
blst_p1_affine *commitments_affine = NULL;
blst_p1_affine *proofs_affine = NULL;
Bytes32 r_bytes;
fr_t r;
size_t input_size = DOMAIN_STR_LENGTH + sizeof(uint64_t) + sizeof(uint64_t) +
(n *
(BYTES_PER_COMMITMENT + 2 * BYTES_PER_FIELD_ELEMENT + BYTES_PER_PROOF));
ret = c_kzg_malloc((void **)&bytes, input_size);
if (ret != C_KZG_OK) goto out;
ret = c_kzg_malloc((void **)&commitments_affine, n * sizeof(blst_p1_affine));
if (ret != C_KZG_OK) goto out;
ret = c_kzg_malloc((void **)&proofs_affine, n * sizeof(blst_p1_affine));
if (ret != C_KZG_OK) goto out;
const blst_p1 *commitments_arg[2] = {commitments_g1, NULL};
blst_p1s_to_affine(commitments_affine, commitments_arg, n);
const blst_p1 *proofs_arg[2] = {proofs_g1, NULL};
blst_p1s_to_affine(proofs_affine, proofs_arg, n);
uint8_t *offset = bytes;
assert(strlen(RANDOM_CHALLENGE_DOMAIN_VERIFY_BLOB_KZG_PROOF_BATCH) == DOMAIN_STR_LENGTH);
memcpy(offset, RANDOM_CHALLENGE_DOMAIN_VERIFY_BLOB_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, n);
offset += sizeof(uint64_t);
for (size_t i = 0; i < n; i++) {
blst_p1_affine_compress(offset, &commitments_affine[i]);
offset += BYTES_PER_COMMITMENT;
bytes_from_bls_field((Bytes32 *)offset, &zs_fr[i]);
offset += BYTES_PER_FIELD_ELEMENT;
bytes_from_bls_field((Bytes32 *)offset, &ys_fr[i]);
offset += BYTES_PER_FIELD_ELEMENT;
blst_p1_affine_compress(offset, &proofs_affine[i]);
offset += BYTES_PER_PROOF;
}
blst_sha256(r_bytes.bytes, bytes, input_size);
hash_to_bls_field(&r, &r_bytes);
compute_powers(r_powers_out, &r, n);
assert(offset == bytes + input_size);
out:
c_kzg_free(bytes);
c_kzg_free(commitments_affine);
c_kzg_free(proofs_affine);
return ret;
}
static C_KZG_RET verify_kzg_proof_batch(
bool *ok,
const g1_t *commitments_g1,
const fr_t *zs_fr,
const fr_t *ys_fr,
const g1_t *proofs_g1,
size_t n,
const KZGSettings *s
) {
C_KZG_RET ret;
g1_t proof_lincomb, proof_z_lincomb, C_minus_y_lincomb, rhs_g1;
fr_t *r_powers = NULL;
g1_t *C_minus_y = NULL;
fr_t *r_times_z = NULL;
assert(n > 0);
*ok = false;
ret = new_fr_array(&r_powers, n);
if (ret != C_KZG_OK) goto out;
ret = new_g1_array(&C_minus_y, n);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&r_times_z, n);
if (ret != C_KZG_OK) goto out;
ret = compute_r_powers_for_verify_kzg_proof_batch(
r_powers, commitments_g1, zs_fr, ys_fr, proofs_g1, n
);
if (ret != C_KZG_OK) goto out;
g1_lincomb_naive(&proof_lincomb, proofs_g1, r_powers, n);
for (size_t i = 0; i < n; i++) {
g1_t ys_encrypted;
g1_mul(&ys_encrypted, blst_p1_generator(), &ys_fr[i]);
g1_sub(&C_minus_y[i], &commitments_g1[i], &ys_encrypted);
fr_mul(&r_times_z[i], &r_powers[i], &zs_fr[i]);
}
g1_lincomb_naive(&proof_z_lincomb, proofs_g1, r_times_z, n);
g1_lincomb_naive(&C_minus_y_lincomb, C_minus_y, r_powers, n);
g1_add(&rhs_g1, &C_minus_y_lincomb, &proof_z_lincomb);
*ok = pairings_verify(&proof_lincomb, &s->g2_values_monomial[1], &rhs_g1, blst_p2_generator());
out:
c_kzg_free(r_powers);
c_kzg_free(C_minus_y);
c_kzg_free(r_times_z);
return ret;
}
C_KZG_RET verify_blob_kzg_proof_batch(
bool *ok,
const Blob *blobs,
const Bytes48 *commitments_bytes,
const Bytes48 *proofs_bytes,
uint64_t n,
const KZGSettings *s
) {
C_KZG_RET ret;
g1_t *commitments_g1 = NULL;
g1_t *proofs_g1 = NULL;
fr_t *evaluation_challenges_fr = NULL;
fr_t *ys_fr = NULL;
fr_t *poly = NULL;
if (n == 0) {
*ok = true;
return C_KZG_OK;
}
if (n == 1) {
return verify_blob_kzg_proof(ok, &blobs[0], &commitments_bytes[0], &proofs_bytes[0], s);
}
ret = new_g1_array(&commitments_g1, n);
if (ret != C_KZG_OK) goto out;
ret = new_g1_array(&proofs_g1, n);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&evaluation_challenges_fr, n);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&ys_fr, n);
if (ret != C_KZG_OK) goto out;
ret = new_fr_array(&poly, FIELD_ELEMENTS_PER_BLOB);
if (ret != C_KZG_OK) goto out;
for (size_t i = 0; i < n; i++) {
ret = bytes_to_kzg_commitment(&commitments_g1[i], &commitments_bytes[i]);
if (ret != C_KZG_OK) goto out;
ret = blob_to_polynomial(poly, &blobs[i]);
if (ret != C_KZG_OK) goto out;
compute_challenge(&evaluation_challenges_fr[i], &blobs[i], &commitments_g1[i]);
ret = evaluate_polynomial_in_evaluation_form(
&ys_fr[i], poly, &evaluation_challenges_fr[i], s
);
if (ret != C_KZG_OK) goto out;
ret = bytes_to_kzg_proof(&proofs_g1[i], &proofs_bytes[i]);
if (ret != C_KZG_OK) goto out;
}
ret = verify_kzg_proof_batch(
ok, commitments_g1, evaluation_challenges_fr, ys_fr, proofs_g1, n, s
);
out:
c_kzg_free(commitments_g1);
c_kzg_free(proofs_g1);
c_kzg_free(evaluation_challenges_fr);
c_kzg_free(ys_fr);
c_kzg_free(poly);
return ret;
}