diff --git a/bindings/java/build.gradle b/bindings/java/build.gradle index 2a8dec7e..04911a01 100644 --- a/bindings/java/build.gradle +++ b/bindings/java/build.gradle @@ -2,7 +2,7 @@ plugins { id "application" id "java-test-fixtures" id "me.champeau.jmh" version "0.7.0" - id "com.diffplug.spotless" version "6.17.0" + id "com.diffplug.spotless" version "6.25.0" } repositories { @@ -45,4 +45,4 @@ spotless { test { useJUnitPlatform() -} \ No newline at end of file +} diff --git a/bindings/java/src/main/java/ethereum/ckzg4844/CKZG4844JNI.java b/bindings/java/src/main/java/ethereum/ckzg4844/CKZG4844JNI.java index 34da7b78..43b18c8f 100644 --- a/bindings/java/src/main/java/ethereum/ckzg4844/CKZG4844JNI.java +++ b/bindings/java/src/main/java/ethereum/ckzg4844/CKZG4844JNI.java @@ -47,28 +47,40 @@ public static void loadNativeLibrary() { public static final BigInteger BLS_MODULUS = new BigInteger( "52435875175126190479447740508185965837690552500527637822603658699938581184513"); + /** The number of bytes in a g1 point. */ protected static final int BYTES_PER_G1 = 48; + /** The number of bytes in a g2 point. */ protected static final int BYTES_PER_G2 = 96; + /** The number of bytes in a BLS scalar field element. */ public static final int BYTES_PER_FIELD_ELEMENT = 32; + /** The number of bits in a BLS scalar field element. */ protected static final int BITS_PER_FIELD_ELEMENT = 255; + /** The number of field elements in a blob. */ public static final int FIELD_ELEMENTS_PER_BLOB = 4096; + /** The number of field elements in an extended blob. */ protected static final int FIELD_ELEMENTS_PER_EXT_BLOB = FIELD_ELEMENTS_PER_BLOB * 2; + /** The number of field elements in a cell. */ public static final int FIELD_ELEMENTS_PER_CELL = 64; + /** The number of bytes in a KZG commitment. */ public static final int BYTES_PER_COMMITMENT = 48; + /** The number of bytes in a KZG proof. */ public static final int BYTES_PER_PROOF = 48; + /** The number of bytes in a blob. */ public static final int BYTES_PER_BLOB = FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT; + /** The number of bytes in a single cell. */ public static final int BYTES_PER_CELL = BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_CELL; + /** The number of cells in an extended blob. */ public static final int CELLS_PER_EXT_BLOB = FIELD_ELEMENTS_PER_EXT_BLOB / FIELD_ELEMENTS_PER_CELL; diff --git a/bindings/python/README.md b/bindings/python/README.md index 44fefb05..f780c715 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -7,7 +7,7 @@ This directory contains Python bindings for the C-KZG-4844 library. These bindings require `python3`, `PyYAML` and `make`. ``` sudo apt install python3 python3-pip -python3 -m pip install PyYAML +python3 -m pip install build PyYAML ``` ## Build & test diff --git a/bindings/rust/src/bindings/generated.rs b/bindings/rust/src/bindings/generated.rs index 1ac5cce2..71fa7428 100644 --- a/bindings/rust/src/bindings/generated.rs +++ b/bindings/rust/src/bindings/generated.rs @@ -7,9 +7,9 @@ pub const BYTES_PER_COMMITMENT: usize = 48; pub const BYTES_PER_PROOF: usize = 48; pub const FIELD_ELEMENTS_PER_BLOB: usize = 4096; pub const BYTES_PER_BLOB: usize = 131072; -pub const FIELD_ELEMENTS_PER_EXT_BLOB: usize = 8192; pub const FIELD_ELEMENTS_PER_CELL: usize = 64; pub const BYTES_PER_CELL: usize = 2048; +pub const FIELD_ELEMENTS_PER_EXT_BLOB: usize = 8192; pub const CELLS_PER_EXT_BLOB: usize = 128; pub type limb_t = u64; #[repr(C)] diff --git a/src/eip4844/blob.h b/src/eip4844/blob.h index 9c582ffc..a117965f 100644 --- a/src/eip4844/blob.h +++ b/src/eip4844/blob.h @@ -28,9 +28,6 @@ /** The number of field elements in a blob. */ #define FIELD_ELEMENTS_PER_BLOB 4096 -/** The number of field elements in an extended blob */ -#define FIELD_ELEMENTS_PER_EXT_BLOB (FIELD_ELEMENTS_PER_BLOB * 2) - /** The number of bytes in a blob. */ #define BYTES_PER_BLOB (FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT) diff --git a/src/eip4844/eip4844.c b/src/eip4844/eip4844.c index 4448cd32..9fe2ebb1 100644 --- a/src/eip4844/eip4844.c +++ b/src/eip4844/eip4844.c @@ -25,7 +25,7 @@ #include /* For assert */ #include /* For NULL */ -#include /* For memcpy */ +#include /* For memcpy & strlen */ //////////////////////////////////////////////////////////////////////////////////////////////////// // Macros @@ -612,6 +612,9 @@ static C_KZG_RET compute_r_powers_for_verify_kzg_proof_batch( /* Pointer tracking `bytes` for writing on top of it */ uint8_t *offset = bytes; + /* Ensure that the domain string is the correct length */ + assert(strlen(RANDOM_CHALLENGE_DOMAIN_VERIFY_BLOB_KZG_PROOF_BATCH) == DOMAIN_STR_LENGTH); + /* Copy domain separator */ memcpy(offset, RANDOM_CHALLENGE_DOMAIN_VERIFY_BLOB_KZG_PROOF_BATCH, DOMAIN_STR_LENGTH); offset += DOMAIN_STR_LENGTH; diff --git a/src/eip7594/cell.c b/src/eip7594/cell.c index 1dccb7df..3e8ef4ed 100644 --- a/src/eip7594/cell.c +++ b/src/eip7594/cell.c @@ -26,7 +26,7 @@ */ void print_cell(const Cell *cell) { for (size_t i = 0; i < FIELD_ELEMENTS_PER_CELL; i++) { - const Bytes32 *field = (const Bytes32 *)&cell->bytes[i * BYTES_PER_FIELD_ELEMENT]; - print_bytes32(field); + const Bytes32 *element_bytes = (const Bytes32 *)&cell->bytes[i * BYTES_PER_FIELD_ELEMENT]; + print_bytes32(element_bytes); } } diff --git a/src/eip7594/cell.h b/src/eip7594/cell.h index c6b7a727..07ac8df0 100644 --- a/src/eip7594/cell.h +++ b/src/eip7594/cell.h @@ -30,6 +30,12 @@ /** The number of bytes in a single cell. */ #define BYTES_PER_CELL (FIELD_ELEMENTS_PER_CELL * BYTES_PER_FIELD_ELEMENT) +/** The number of field elements in an extended blob. */ +#define FIELD_ELEMENTS_PER_EXT_BLOB (FIELD_ELEMENTS_PER_BLOB * 2) + +/** The number of cells in a blob. */ +#define CELLS_PER_BLOB (FIELD_ELEMENTS_PER_BLOB / FIELD_ELEMENTS_PER_CELL) + /** The number of cells in an extended blob. */ #define CELLS_PER_EXT_BLOB (FIELD_ELEMENTS_PER_EXT_BLOB / FIELD_ELEMENTS_PER_CELL) diff --git a/src/eip7594/eip7594.c b/src/eip7594/eip7594.c index fc4486c5..2d69fa3e 100644 --- a/src/eip7594/eip7594.c +++ b/src/eip7594/eip7594.c @@ -25,7 +25,7 @@ #include "eip7594/recovery.h" #include /* For assert */ -#include /* For memcpy */ +#include /* For memcpy & strlen */ //////////////////////////////////////////////////////////////////////////////////////////////////// // Macros @@ -41,6 +41,27 @@ /** The domain separator for verify_cell_kzg_proof_batch's random challenge. */ static const char *RANDOM_CHALLENGE_DOMAIN_VERIFY_CELL_KZG_PROOF_BATCH = "RCKZGCBATCH__V1_"; +/** + * This is a precomputed map of cell index to reverse-bits-limited cell index. + * + * for (size_t i = 0; i < CELLS_PER_EXT_BLOB; i++) + * printf("%#04llx,\n", reverse_bits_limited(CELLS_PER_EXT_BLOB, i)); + * + * Because of the way our evaluation domain is defined, we can use CELL_INDICES_RBL to find the + * coset factor of a cell. In particular, for cell i, its coset factor is + * roots_of_unity[CELLS_INDICES_RBL[i]]. + */ +static const uint64_t CELL_INDICES_RBL[CELLS_PER_EXT_BLOB] = { + 0x00, 0x40, 0x20, 0x60, 0x10, 0x50, 0x30, 0x70, 0x08, 0x48, 0x28, 0x68, 0x18, 0x58, 0x38, 0x78, + 0x04, 0x44, 0x24, 0x64, 0x14, 0x54, 0x34, 0x74, 0x0c, 0x4c, 0x2c, 0x6c, 0x1c, 0x5c, 0x3c, 0x7c, + 0x02, 0x42, 0x22, 0x62, 0x12, 0x52, 0x32, 0x72, 0x0a, 0x4a, 0x2a, 0x6a, 0x1a, 0x5a, 0x3a, 0x7a, + 0x06, 0x46, 0x26, 0x66, 0x16, 0x56, 0x36, 0x76, 0x0e, 0x4e, 0x2e, 0x6e, 0x1e, 0x5e, 0x3e, 0x7e, + 0x01, 0x41, 0x21, 0x61, 0x11, 0x51, 0x31, 0x71, 0x09, 0x49, 0x29, 0x69, 0x19, 0x59, 0x39, 0x79, + 0x05, 0x45, 0x25, 0x65, 0x15, 0x55, 0x35, 0x75, 0x0d, 0x4d, 0x2d, 0x6d, 0x1d, 0x5d, 0x3d, 0x7d, + 0x03, 0x43, 0x23, 0x63, 0x13, 0x53, 0x33, 0x73, 0x0b, 0x4b, 0x2b, 0x6b, 0x1b, 0x5b, 0x3b, 0x7b, + 0x07, 0x47, 0x27, 0x67, 0x17, 0x57, 0x37, 0x77, 0x0f, 0x4f, 0x2f, 0x6f, 0x1f, 0x5f, 0x3f, 0x7f, +}; + //////////////////////////////////////////////////////////////////////////////////////////////////// // Compute //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -90,9 +111,9 @@ C_KZG_RET compute_cells_and_kzg_proofs( ret = poly_lagrange_to_monomial(poly_monomial, poly_lagrange, FIELD_ELEMENTS_PER_BLOB, s); if (ret != C_KZG_OK) goto out; - /* Ensure the upper half of the field elements are zero */ + /* Ensure the upper half of the field elements are still zero */ for (size_t i = FIELD_ELEMENTS_PER_BLOB; i < FIELD_ELEMENTS_PER_EXT_BLOB; i++) { - poly_monomial[i] = FR_ZERO; + assert(fr_equal(&poly_monomial[i], &FR_ZERO)); } if (cells != NULL) { @@ -123,8 +144,8 @@ C_KZG_RET compute_cells_and_kzg_proofs( ret = new_g1_array(&proofs_g1, CELLS_PER_EXT_BLOB); if (ret != C_KZG_OK) goto out; - /* Compute the proofs, provide only the first half */ - ret = compute_fk20_proofs(proofs_g1, poly_monomial, FIELD_ELEMENTS_PER_BLOB, s); + /* Compute the proofs, only uses the first half of the polynomial */ + ret = compute_fk20_cell_proofs(proofs_g1, poly_monomial, s); if (ret != C_KZG_OK) goto out; /* Bit-reverse the proofs */ @@ -154,11 +175,12 @@ C_KZG_RET compute_cells_and_kzg_proofs( * * @param[out] recovered_cells An array of CELLS_PER_EXT_BLOB cells * @param[out] recovered_proofs An array of CELLS_PER_EXT_BLOB proofs - * @param[in] cell_indices The cell indices for the available cells - * @param[in] cells The available cells we recover from + * @param[in] cell_indices The cell indices for the available cells, length `num_cells` + * @param[in] cells The available cells we recover from, length `num_cells` * @param[in] num_cells The number of available cells provided * @param[in] s The trusted setup * + * @remark At least 50% of CELLS_PER_EXT_BLOB cells must be provided. * @remark Recovery is faster if there are fewer missing cells. * @remark If recovered_proofs is NULL, they will not be recomputed. */ @@ -259,10 +281,8 @@ C_KZG_RET recover_cells_and_kzg_proofs( ); if (ret != C_KZG_OK) goto out; - /* Compute the proofs, provide only the first half */ - ret = compute_fk20_proofs( - recovered_proofs_g1, recovered_cells_fr, FIELD_ELEMENTS_PER_BLOB, s - ); + /* Compute the proofs, only uses the first half of the polynomial */ + ret = compute_fk20_cell_proofs(recovered_proofs_g1, recovered_cells_fr, s); if (ret != C_KZG_OK) goto out; /* Bit-reverse the proofs */ @@ -357,13 +377,13 @@ static void deduplicate_commitments( * Compute random linear combination challenge scalars for verify_cell_kzg_proof_batch. In this, we * must hash EVERYTHING that the prover can control. * - * @param[out] r_powers_out The output challenges - * @param[in] commitments_bytes The input commitments + * @param[out] r_powers_out The output challenges, length `num_cells` + * @param[in] commitments_bytes The input commitments, length `num_commitments` * @param[in] num_commitments The number of commitments - * @param[in] commitment_indices The cell commitment indices - * @param[in] cell_indices The cell indices - * @param[in] cells The cell - * @param[in] proofs_bytes The cell proof + * @param[in] commitment_indices The cell commitment indices, length `num_cells` + * @param[in] cell_indices The cell indices, length `num_cells` + * @param[in] cells The cell, length `num_cells` + * @param[in] proofs_bytes The cell proof, length `num_cells` * @param[in] num_cells The number of cells */ static C_KZG_RET compute_r_powers_for_verify_cell_kzg_proof_batch( @@ -399,6 +419,9 @@ static C_KZG_RET compute_r_powers_for_verify_cell_kzg_proof_batch( /* Pointer tracking `bytes` for writing on top of it */ uint8_t *offset = bytes; + /* Ensure that the domain string is the correct length */ + assert(strlen(RANDOM_CHALLENGE_DOMAIN_VERIFY_CELL_KZG_PROOF_BATCH) == DOMAIN_STR_LENGTH); + /* Copy domain separator */ memcpy(offset, RANDOM_CHALLENGE_DOMAIN_VERIFY_CELL_KZG_PROOF_BATCH, DOMAIN_STR_LENGTH); offset += DOMAIN_STR_LENGTH; @@ -458,9 +481,9 @@ static C_KZG_RET compute_r_powers_for_verify_cell_kzg_proof_batch( * Compute the sum of the commitments weighted by the powers of r. * * @param[out] sum_of_commitments_out The resulting G1 sum of the commitments - * @param[in] unique_commitments Array of unique commitments - * @param[in] commitment_indices Indices mapping to unique commitments - * @param[in] r_powers Array of powers of r used for weighting + * @param[in] unique_commitments Array of unique commitments, length `num_commitments` + * @param[in] commitment_indices Indices mapping to unique commitments, length `num_cells` + * @param[in] r_powers Array of powers of r used for weighting, length `num_cells` * @param[in] num_commitments The number of unique commitments * @param[in] num_cells The number of cells */ @@ -511,15 +534,77 @@ static C_KZG_RET compute_weighted_sum_of_commitments( return ret; } +/** + * Compute the inverse coset factor h_k^{-1}, + * where `h_k` is the coset factor for cell with index `k`. + * + * @param[out] inv_coset_factor_out Pointer to store the computed inverse coset factor + * @param[in] cell_index The index of the cell + * @param[in] s The trusted setup + */ +static void get_inv_coset_shift_for_cell( + fr_t *inv_coset_factor_out, uint64_t cell_index, const KZGSettings *s +) { + /* + * Get the cell index in reverse-bit order. + * This index points to this cell's coset factor h_k in the roots_of_unity array. + */ + uint64_t cell_idx_rbl = CELL_INDICES_RBL[cell_index]; + + /* + * Observe that for every element in roots_of_unity, we can find its inverse by + * accessing its reflected element. + * + * For example, consider a multiplicative subgroup with eight elements: + * roots = {w^0, w^1, w^2, ... w^7, w^0} + * For a root of unity in roots[i], we can find its inverse in roots[-i]. + */ + assert(cell_idx_rbl <= FIELD_ELEMENTS_PER_EXT_BLOB); + uint64_t inv_coset_factor_idx = FIELD_ELEMENTS_PER_EXT_BLOB - cell_idx_rbl; + + /* Get h_k^{-1} using the index */ + assert(inv_coset_factor_idx < FIELD_ELEMENTS_PER_EXT_BLOB + 1); + *inv_coset_factor_out = s->roots_of_unity[inv_coset_factor_idx]; +} + +/** + * Compute h_k^{n}, where `h_k` is the coset factor for cell with index `k`. + * + * @param[out] coset_factor_out Pointer to store h_k^{n} + * @param[in] cell_index The index of the cell + * @param[in] s The trusted setup + */ +static void get_coset_shift_pow_for_cell( + fr_t *coset_factor_out, uint64_t cell_index, const KZGSettings *s +) { + /* + * Get the cell index in reverse-bit order. + * This index points to this cell's coset factor h_k in the roots_of_unity array. + */ + uint64_t cell_idx_rbl = CELL_INDICES_RBL[cell_index]; + + /* + * Get the index to h_k^n in the roots_of_unity array. + * + * Multiplying the index of h_k by n, effectively raises h_k to the n-th power, + * because advancing in the roots_of_unity array corresponds to increasing exponents. + */ + uint64_t h_k_pow_idx = cell_idx_rbl * FIELD_ELEMENTS_PER_CELL; + + /* Get h_k^n using the index */ + assert(h_k_pow_idx < FIELD_ELEMENTS_PER_EXT_BLOB + 1); + *coset_factor_out = s->roots_of_unity[h_k_pow_idx]; +} + /** * Aggregate columns, compute the sum of interpolation polynomials, and commit to the result. * * This function computes `RLI = [sum_k r^k interpolation_poly_k(s)]` from the spec. * * @param[out] commitment_out Commitment to the aggregated interpolation poly - * @param[in] r_powers Precomputed powers of the random challenge - * @param[in] cell_indices Indices of the cells - * @param[in] cells Array of cells + * @param[in] r_powers Precomputed powers of the random challenge, length `num_cells` + * @param[in] cell_indices Indices of the cells, length `num_cells` + * @param[in] cells Array of cells, length `num_cells` * @param[in] num_cells Number of cells * @param[in] s The trusted setup */ @@ -647,10 +732,9 @@ static C_KZG_RET compute_commitment_to_aggregated_interpolation_poly( ); if (ret != C_KZG_OK) goto out; - /* Now divide by the coset shift factor */ - uint64_t pos = reverse_bits_limited(CELLS_PER_EXT_BLOB, i); + /* Shift the poly by h_k^{-1} where h_k is the coset factor for this cell */ fr_t inv_coset_factor; - blst_fr_eucl_inverse(&inv_coset_factor, &s->roots_of_unity[pos]); + get_inv_coset_shift_for_cell(&inv_coset_factor, i, s); shift_poly(column_interpolation_poly, FIELD_ELEMENTS_PER_CELL, &inv_coset_factor); /* Update the aggregated poly */ @@ -687,11 +771,11 @@ static C_KZG_RET compute_commitment_to_aggregated_interpolation_poly( * Compute weighted sum of proofs. * * @param[out] weighted_proof_lincomb The resulting G1 sum of the proofs scaled by coset factors - * @param[in] proofs_g1 Array of G1 elements representing the proofs - * @param[in] r_powers Array of powers of r used for weighting - * @param[in] cell_indices Array of cell indices + * @param[in] proofs_g1 Array of proofs, length `num_cells` + * @param[in] r_powers Array of powers of r used for weighting, length `num_cells` + * @param[in] cell_indices Array of cell indices, length `num_cells` * @param[in] num_cells The number of cells - * @param[in] s The trusted setup containing roots of unity + * @param[in] s The trusted setup */ static C_KZG_RET computed_weighted_sum_of_proofs( g1_t *weighted_proof_sum_out, @@ -702,19 +786,18 @@ static C_KZG_RET computed_weighted_sum_of_proofs( const KZGSettings *s ) { C_KZG_RET ret; - fr_t coset_factor_pow; 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++) { - uint64_t pos = reverse_bits_limited(CELLS_PER_EXT_BLOB, cell_indices[i]); - fr_t coset_factor = s->roots_of_unity[pos]; - // Compute h_k^n, with h_k and n as in the spec. - fr_pow(&coset_factor_pow, &coset_factor, FIELD_ELEMENTS_PER_CELL); - // Scale the power of r by h_k^n - blst_fr_mul(&weighted_powers_of_r[i], &r_powers[i], &coset_factor_pow); + /* Get scaling factor h_k^n where h_k is the coset factor for this cell */ + fr_t h_k_pow; + get_coset_shift_pow_for_cell(&h_k_pow, cell_indices[i], s); + + /* Scale the power of r by h_k^n */ + blst_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); @@ -728,10 +811,10 @@ static C_KZG_RET computed_weighted_sum_of_proofs( * Given some cells, verify that their proofs are valid. * * @param[out] ok True if the proofs are valid - * @param[in] commitments_bytes The commitments for the cells - * @param[in] cell_indices The cell indices for the cells - * @param[in] cells The cells to check - * @param[in] proofs_bytes The proofs for the cells + * @param[in] commitments_bytes The commitments for the cells, length `num_cells` + * @param[in] cell_indices The indices for the cells, length `num_cells` + * @param[in] cells The cells to check, length `num_cells` + * @param[in] proofs_bytes The proofs for the cells, length `num_cells` * @param[in] num_cells The number of cells provided * @param[in] s The trusted setup */ @@ -854,6 +937,7 @@ C_KZG_RET verify_cell_kzg_proof_batch( ); if (ret != C_KZG_OK) goto out; + /* Subtract commitment from sum by adding the negated commitment */ blst_p1_cneg(&interpolation_poly_commit, true); blst_p1_add(&final_g1_sum, &final_g1_sum, &interpolation_poly_commit); diff --git a/src/eip7594/fft.c b/src/eip7594/fft.c index efc094ed..91ddd83d 100644 --- a/src/eip7594/fft.c +++ b/src/eip7594/fft.c @@ -17,7 +17,7 @@ #include "eip7594/fft.h" #include "common/alloc.h" #include "common/utils.h" -#include "eip4844/blob.h" +#include "eip7594/cell.h" #include "eip7594/poly.h" #include /* For memcpy */ @@ -60,10 +60,10 @@ static const fr_t INV_RECOVERY_SHIFT_FACTOR = { * * Recursively divide and conquer. * - * @param[out] out The results (length `n`) - * @param[in] in The input data (length `n * stride`) + * @param[out] out The results, length `n` + * @param[in] in The input data, length `n * stride` * @param[in] stride The input data stride - * @param[in] roots Roots of unity (length `n * roots_stride`) + * @param[in] roots Roots of unity, length `n * roots_stride` * @param[in] roots_stride The stride interval among the roots of unity * @param[in] n Length of the FFT, must be a power of two */ @@ -88,15 +88,19 @@ static void fr_fft_fast( /** * The entry point for forward FFT over field elements. * - * @param[out] out The results (array of length n) - * @param[in] in The input data (array of length n) + * @param[out] out The results, length `n` + * @param[in] in The input data, length `n` * @param[in] n Length of the arrays * @param[in] s The trusted setup * + * @remark Will do nothing if given a zero length array. * @remark The array lengths must be a power of two. * @remark Use fr_ifft() for inverse transformation. */ C_KZG_RET fr_fft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s) { + /* Handle zero length input */ + if (n == 0) return C_KZG_OK; + /* Ensure the length is valid */ if (n > FIELD_ELEMENTS_PER_EXT_BLOB || !is_power_of_two(n)) { return C_KZG_BADARGS; @@ -111,15 +115,19 @@ C_KZG_RET fr_fft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s) { /** * The entry point for inverse FFT over field elements. * - * @param[out] out The results (array of length n) - * @param[in] in The input data (array of length n) + * @param[out] out The results, length `n` + * @param[in] in The input data, length `n` * @param[in] n Length of the arrays * @param[in] s The trusted setup * + * @remark Will do nothing if given a zero length array. * @remark The array lengths must be a power of two. * @remark Use fr_fft() for forward transformation. */ C_KZG_RET fr_ifft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s) { + /* Handle zero length input */ + if (n == 0) return C_KZG_OK; + /* Ensure the length is valid */ if (n > FIELD_ELEMENTS_PER_EXT_BLOB || !is_power_of_two(n)) { return C_KZG_BADARGS; @@ -128,11 +136,11 @@ C_KZG_RET fr_ifft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s) { size_t stride = FIELD_ELEMENTS_PER_EXT_BLOB / n; fr_fft_fast(out, in, 1, s->reverse_roots_of_unity, stride, n); - fr_t inv_len; - fr_from_uint64(&inv_len, n); - blst_fr_inverse(&inv_len, &inv_len); + fr_t inv_n; + fr_from_uint64(&inv_n, n); + blst_fr_eucl_inverse(&inv_n, &inv_n); for (size_t i = 0; i < n; i++) { - blst_fr_mul(&out[i], &out[i], &inv_len); + blst_fr_mul(&out[i], &out[i], &inv_n); } return C_KZG_OK; } @@ -146,10 +154,10 @@ C_KZG_RET fr_ifft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s) { * * Recursively divide and conquer. * - * @param[out] out The results (length `n`) - * @param[in] in The input data (length `n * stride`) + * @param[out] out The results, length `n` + * @param[in] in The input data, length `n * stride` * @param[in] stride The input data stride - * @param[in] roots Roots of unity (length `n * roots_stride`) + * @param[in] roots Roots of unity, length `n * roots_stride` * @param[in] roots_stride The stride interval among the roots of unity * @param[in] n Length of the FFT, must be a power of two */ @@ -162,19 +170,14 @@ static void g1_fft_fast( g1_fft_fast(out, in, stride * 2, roots, roots_stride * 2, half); g1_fft_fast(out + half, in + stride, stride * 2, roots, roots_stride * 2, half); for (size_t i = 0; i < half; i++) { - /* If the point is infinity, we can skip the calculation */ - if (blst_p1_is_inf(&out[i + half])) { - out[i + half] = out[i]; + /* If the scalar is one, we can skip the multiplication */ + if (fr_is_one(&roots[i * roots_stride])) { + y_times_root = out[i + half]; } else { - /* If the scalar is one, we can skip the multiplication */ - if (fr_is_one(&roots[i * roots_stride])) { - y_times_root = out[i + half]; - } else { - g1_mul(&y_times_root, &out[i + half], &roots[i * roots_stride]); - } - g1_sub(&out[i + half], &out[i], &y_times_root); - blst_p1_add_or_double(&out[i], &out[i], &y_times_root); + g1_mul(&y_times_root, &out[i + half], &roots[i * roots_stride]); } + g1_sub(&out[i + half], &out[i], &y_times_root); + blst_p1_add_or_double(&out[i], &out[i], &y_times_root); } } else { *out = *in; @@ -184,15 +187,19 @@ static void g1_fft_fast( /** * The entry point for forward FFT over G1 points. * - * @param[out] out The results (array of length n) - * @param[in] in The input data (array of length n) + * @param[out] out The results, length `n` + * @param[in] in The input data, length `n` * @param[in] n Length of the arrays * @param[in] s The trusted setup * + * @remark Will do nothing if given a zero length array. * @remark The array lengths must be a power of two. * @remark Use g1_ifft() for inverse transformation. */ C_KZG_RET g1_fft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { + /* Handle zero length input */ + if (n == 0) return C_KZG_OK; + /* Ensure the length is valid */ if (n > FIELD_ELEMENTS_PER_EXT_BLOB || !is_power_of_two(n)) { return C_KZG_BADARGS; @@ -207,15 +214,19 @@ C_KZG_RET g1_fft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { /** * The entry point for inverse FFT over G1 points. * - * @param[out] out The results (array of length n) - * @param[in] in The input data (array of length n) + * @param[out] out The results, length `n` + * @param[in] in The input data, length `n` * @param[in] n Length of the arrays * @param[in] s The trusted setup * + * @remark Will do nothing if given a zero length array. * @remark The array lengths must be a power of two. * @remark Use g1_fft() for forward transformation. */ C_KZG_RET g1_ifft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { + /* Handle zero length input */ + if (n == 0) return C_KZG_OK; + /* Ensure the length is valid */ if (n > FIELD_ELEMENTS_PER_EXT_BLOB || !is_power_of_two(n)) { return C_KZG_BADARGS; @@ -224,11 +235,11 @@ C_KZG_RET g1_ifft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { size_t stride = FIELD_ELEMENTS_PER_EXT_BLOB / n; g1_fft_fast(out, in, 1, s->reverse_roots_of_unity, stride, n); - fr_t inv_len; - fr_from_uint64(&inv_len, n); - blst_fr_eucl_inverse(&inv_len, &inv_len); + fr_t inv_n; + fr_from_uint64(&inv_n, n); + blst_fr_eucl_inverse(&inv_n, &inv_n); for (size_t i = 0; i < n; i++) { - g1_mul(&out[i], &out[i], &inv_len); + g1_mul(&out[i], &out[i], &inv_n); } return C_KZG_OK; @@ -241,19 +252,21 @@ C_KZG_RET g1_ifft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { /** * Do an FFT over a coset of the roots of unity. * - * @param[out] out The results (array of length n) - * @param[in] in The input data (array of length n) + * @param[out] out The results, length `n` + * @param[in] in The input data, length `n` * @param[in] n Length of the arrays * @param[in] s The trusted setup * + * @remark Will do nothing if given a zero length array. * @remark The coset shift factor is RECOVERY_SHIFT_FACTOR. */ C_KZG_RET coset_fft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s) { - C_KZG_RET ret; - fr_t *in_shifted = NULL; + /* Handle zero length input */ + if (n == 0) return C_KZG_OK; /* Create some room to shift the polynomial */ - ret = new_fr_array(&in_shifted, n); + fr_t *in_shifted = NULL; + C_KZG_RET ret = new_fr_array(&in_shifted, n); if (ret != C_KZG_OK) goto out; /* Shift the poly */ @@ -271,18 +284,20 @@ C_KZG_RET coset_fft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s) { /** * Do an inverse FFT over a coset of the roots of unity. * - * @param[out] out The results (array of length n) - * @param[in] in The input data (array of length n) + * @param[out] out The results, length `n` + * @param[in] in The input data, length `n` * @param[in] n Length of the arrays * @param[in] s The trusted setup * + * @remark Will do nothing if given a zero length array. * @remark The coset shift factor is RECOVERY_SHIFT_FACTOR. In this function we use its inverse to * implement the IFFT. */ C_KZG_RET coset_ifft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s) { - C_KZG_RET ret; + /* Handle zero length input */ + if (n == 0) return C_KZG_OK; - ret = fr_ifft(out, in, n, s); + C_KZG_RET ret = fr_ifft(out, in, n, s); if (ret != C_KZG_OK) goto out; shift_poly(out, n, &INV_RECOVERY_SHIFT_FACTOR); diff --git a/src/eip7594/fk20.c b/src/eip7594/fk20.c index 493e7fb7..15b007ae 100644 --- a/src/eip7594/fk20.c +++ b/src/eip7594/fk20.c @@ -25,45 +25,40 @@ /** * Reorder and extend polynomial coefficients for the toeplitz method, strided version. * - * @param[out] out The reordered polynomial, size `n * 2 / stride` - * @param[in] in The input polynomial, size `n` - * @param[in] n The size of the input polynomial + * @param[out] out The reordered polynomial, length `CELLS_PER_EXT_BLOB` + * @param[in] in The input polynomial, length `FIELD_ELEMENTS_PER_BLOB` * @param[in] offset The offset - * @param[in] stride The stride */ -static C_KZG_RET toeplitz_coeffs_stride( - fr_t *out, const fr_t *in, size_t n, size_t offset, size_t stride -) { - size_t k, k2; - - if (stride == 0) return C_KZG_BADARGS; +static void toeplitz_coeffs_stride(fr_t *out, const fr_t *in, size_t offset) { + /* Calculate starting indices */ + size_t out_start = CELLS_PER_BLOB + 2; + size_t in_start = CELLS_PER_EXT_BLOB - offset - 1; - k = n / stride; - k2 = k * 2; + /* Set the first element */ + out[0] = in[FIELD_ELEMENTS_PER_BLOB - 1 - offset]; - out[0] = in[n - 1 - offset]; - for (size_t i = 1; i <= k + 1 && i < k2; i++) { + /* Initialize these elements to zero */ + for (size_t i = 1; i < out_start; i++) { out[i] = FR_ZERO; } - for (size_t i = k + 2, j = 2 * stride - offset - 1; i < k2; i++, j += stride) { - out[i] = in[j]; - } - return C_KZG_OK; + /* Copy elements with a fixed stride */ + for (size_t i = 0; i < CELLS_PER_EXT_BLOB - out_start; i++) { + out[out_start + i] = in[in_start + i * FIELD_ELEMENTS_PER_CELL]; + } } /** * Compute FK20 cell-proofs for a polynomial. * * @param[out] out An array of CELLS_PER_EXT_BLOB proofs - * @param[in] p The polynomial, an array of coefficients - * @param[in] n The length of the polynomial + * @param[in] p The polynomial, an array of FIELD_ELEMENTS_PER_BLOB coefficients * @param[in] s The trusted setup * * @remark The polynomial should have FIELD_ELEMENTS_PER_BLOB coefficients. Only the lower half of * the extended polynomial is supplied because the upper half is assumed to be zero. */ -C_KZG_RET compute_fk20_proofs(g1_t *out, const fr_t *p, size_t n, const KZGSettings *s) { +C_KZG_RET compute_fk20_cell_proofs(g1_t *out, const fr_t *p, const KZGSettings *s) { C_KZG_RET ret; size_t k, k2; @@ -77,7 +72,7 @@ C_KZG_RET compute_fk20_proofs(g1_t *out, const fr_t *p, size_t n, const KZGSetti bool precompute = s->wbits != 0; /* Initialize length variables */ - k = n / FIELD_ELEMENTS_PER_CELL; + k = FIELD_ELEMENTS_PER_BLOB / FIELD_ELEMENTS_PER_CELL; k2 = k * 2; /* Do allocations */ @@ -113,8 +108,7 @@ C_KZG_RET compute_fk20_proofs(g1_t *out, const fr_t *p, size_t n, const KZGSetti /* Compute toeplitz coefficients and organize by column */ for (size_t i = 0; i < FIELD_ELEMENTS_PER_CELL; i++) { - ret = toeplitz_coeffs_stride(toeplitz_coeffs, p, n, i, FIELD_ELEMENTS_PER_CELL); - if (ret != C_KZG_OK) goto out; + toeplitz_coeffs_stride(toeplitz_coeffs, p, i); ret = fr_fft(toeplitz_coeffs_fft, toeplitz_coeffs, k2, s); if (ret != C_KZG_OK) goto out; for (size_t j = 0; j < k2; j++) { diff --git a/src/eip7594/fk20.h b/src/eip7594/fk20.h index 76db037e..b2f9c8e8 100644 --- a/src/eip7594/fk20.h +++ b/src/eip7594/fk20.h @@ -29,7 +29,7 @@ extern "C" { #endif -C_KZG_RET compute_fk20_proofs(g1_t *out, const fr_t *p, size_t n, const KZGSettings *s); +C_KZG_RET compute_fk20_cell_proofs(g1_t *out, const fr_t *p, const KZGSettings *s); #ifdef __cplusplus } diff --git a/src/eip7594/poly.c b/src/eip7594/poly.c index 8a4f36c0..ca1aebb5 100644 --- a/src/eip7594/poly.c +++ b/src/eip7594/poly.c @@ -31,7 +31,7 @@ * Multiplies each coefficient by `shift_factor ^ i`. Equivalent to creating a polynomial that * evaluates at `x * shift_factor` rather than `x`. * - * @param[in,out] p The polynomial coefficients to be scaled + * @param[in,out] p The polynomial coefficients to be scaled, length `len` * @param[in] len Length of the polynomial coefficients * @param[in] shift_factor Shift factor */ diff --git a/src/eip7594/recovery.c b/src/eip7594/recovery.c index f620e50a..fa20306e 100644 --- a/src/eip7594/recovery.c +++ b/src/eip7594/recovery.c @@ -35,9 +35,9 @@ * Uses straightforward long multiplication to calculate the product of `(x - r_i)` where `r_i` is * the i'th root. This results in a poly of degree roots_len. * - * @param[in,out] poly The zero polynomial for roots + * @param[in,out] poly The zero polynomial for roots, length `poly_len` * @param[in,out] poly_len The length of poly - * @param[in] roots The array of roots + * @param[in] roots The array of roots, length `roots_len` * @param[in] roots_len The number of roots * * @remark These do not have to be roots of unity. They are roots of a polynomial. @@ -87,11 +87,8 @@ static C_KZG_RET compute_vanishing_polynomial_from_roots( * @param[in] len_missing_cells The number of missing cell indices * @param[in] s The trusted setup * - * @remark When all of the cells are missing, this algorithm has an edge case. We return - * C_KZG_BADARGS in that case. - * @remark When none of the cells are missing, recovery is trivial. We expect the caller to handle - * this case, and return C_KZG_BADARGS if not. - * @remark `missing_cell_indices` are assumed to be less than `CELLS_PER_EXT_BLOB`. + * @remark If no cells are missing, recovery is trivial; we expect the caller to handle this. + * @remark If all cells are missing, we return C_KZG_BADARGS; the algorithm has an edge case. */ static C_KZG_RET vanishing_polynomial_for_missing_cells( fr_t *vanishing_poly, @@ -105,7 +102,7 @@ static C_KZG_RET vanishing_polynomial_for_missing_cells( size_t short_vanishing_poly_len = 0; /* Return early if none or all of the cells are missing */ - if (len_missing_cells == 0 || len_missing_cells == CELLS_PER_EXT_BLOB) { + if (len_missing_cells == 0 || len_missing_cells >= CELLS_PER_EXT_BLOB) { ret = C_KZG_BADARGS; goto out; } @@ -192,7 +189,7 @@ static bool is_in_array(const uint64_t *arr, size_t arr_size, uint64_t value) { * equal to zero. * * @param[out] reconstructed_data_out Array of size FIELD_ELEMENTS_PER_EXT_BLOB to recover cells - * @param[in] cell_indices An array with the available cell indices we have + * @param[in] cell_indices An array with the available cell indices, length `num_cells` * @param[in] num_cells The size of the `cell_indices` array * @param[in] cells An array of size FIELD_ELEMENTS_PER_EXT_BLOB with the cells * @param[in] s The trusted setup @@ -281,6 +278,12 @@ C_KZG_RET recover_cells( */ for (size_t i = 0; i < FIELD_ELEMENTS_PER_EXT_BLOB; i++) { if (fr_is_null(&cells_brp[i])) { + /* + * We handle this situation differently because FR_NULL is an invalid value. The right + * hand side, vanishing_poly_eval[i], will always be zero when cells_brp[i] is null, so + * the multiplication would still be result in zero, but we shouldn't depend on blst + * handling invalid values like this. + */ extended_evaluation_times_zero[i] = FR_ZERO; } else { blst_fr_mul(&extended_evaluation_times_zero[i], &cells_brp[i], &vanishing_poly_eval[i]); diff --git a/src/test/tests.c b/src/test/tests.c index 1c3d9c3a..dbc4adfb 100644 --- a/src/test/tests.c +++ b/src/test/tests.c @@ -49,7 +49,7 @@ static void get_rand_fr(fr_t *out) { } static void get_rand_blob(Blob *out) { - for (int i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) { + for (size_t i = 0; i < FIELD_ELEMENTS_PER_BLOB; i++) { get_rand_field_element((Bytes32 *)&out->bytes[i * 32]); } } @@ -353,7 +353,7 @@ static void test_g1_mul__test_different_bit_lengths(void) { /* blst_p1_mult needs it to be little-endian */ blst_lendian_from_scalar(b.bytes, &scalar); - for (int i = 1; i < 255; i++) { + for (size_t i = 1; i < 255; i++) { get_rand_g1(&g); blst_p1_mult(&check, &g, b.bytes, 256); @@ -1150,7 +1150,7 @@ static void test_compute_and_verify_kzg_proof__succeeds_round_trip(void) { } static void test_compute_and_verify_kzg_proof__succeeds_within_domain(void) { - for (int i = 0; i < 25; i++) { + for (size_t i = 0; i < 25; i++) { C_KZG_RET ret; Blob blob; KZGCommitment c; @@ -1838,6 +1838,52 @@ static void test_deduplicate_commitments__one_commitment(void) { ASSERT_EQUALS(indices[0], 0); } +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Tests for coset shift factors +//////////////////////////////////////////////////////////////////////////////////////////////////// + +static void test_shift_factors__succeeds(void) { + fr_t expected_inv_coset_factor, computed_inv_coset_factor, h_k; + fr_t expected_coset_factor_pow, computed_coset_factor_pow; + static uint64_t n = FIELD_ELEMENTS_PER_CELL; + + /* Loop over all cells */ + for (uint64_t cell_index = 0; cell_index < CELLS_PER_EXT_BLOB; cell_index++) { + /* Get the cell index in reverse-bit order */ + uint64_t cell_idx_rbl = reverse_bits_limited(CELLS_PER_EXT_BLOB, cell_index); + + /* Ensure the index is within bounds */ + assert(cell_idx_rbl < FIELD_ELEMENTS_PER_EXT_BLOB + 1); + + /* Get h_k for this cell */ + h_k = s.roots_of_unity[cell_idx_rbl]; + + /* First we test get_inv_coset_shift_for_cell() */ + + /* Compute the expected inverse coset factor */ + blst_fr_eucl_inverse(&expected_inv_coset_factor, &h_k); + + /* Call the function we are testing */ + get_inv_coset_shift_for_cell(&computed_inv_coset_factor, cell_index, &s); + + /* Compare the expected and computed inverse coset factors */ + bool ok = fr_equal(&expected_inv_coset_factor, &computed_inv_coset_factor); + ASSERT_EQUALS(ok, true); + + /* Now we test get_coset_shift_pow_for_cell() */ + + /* Compute the expected coset factor h_k^n */ + fr_pow(&expected_coset_factor_pow, &h_k, n); + + /* Now call the function we are testing */ + get_coset_shift_pow_for_cell(&computed_coset_factor_pow, cell_index, &s); + + /* Compare the expected and computed inverse coset factors */ + ok = fr_equal(&expected_coset_factor_pow, &computed_coset_factor_pow); + ASSERT_EQUALS(ok, true); + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // Tests for recover_cells_and_kzg_proofs //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2032,7 +2078,7 @@ static void profile_blob_to_kzg_commitment(void) { get_rand_blob(&blob); ProfilerStart("blob_to_kzg_commitment.prof"); - for (int i = 0; i < 1000; i++) { + for (size_t i = 0; i < 1000; i++) { blob_to_kzg_commitment(&c, &blob, &s); } ProfilerStop(); @@ -2047,7 +2093,7 @@ static void profile_compute_kzg_proof(void) { get_rand_field_element(&z); ProfilerStart("compute_kzg_proof.prof"); - for (int i = 0; i < 100; i++) { + for (size_t i = 0; i < 100; i++) { compute_kzg_proof(&proof_out, &y_out, &blob, &z, &s); } ProfilerStop(); @@ -2062,7 +2108,7 @@ static void profile_compute_blob_kzg_proof(void) { get_rand_g1_bytes(&commitment); ProfilerStart("compute_blob_kzg_proof.prof"); - for (int i = 0; i < 10; i++) { + for (size_t i = 0; i < 10; i++) { compute_blob_kzg_proof(&out, &blob, &commitment, &s); } ProfilerStop(); @@ -2079,7 +2125,7 @@ static void profile_verify_kzg_proof(void) { get_rand_g1_bytes(&proof); ProfilerStart("verify_kzg_proof.prof"); - for (int i = 0; i < 5000; i++) { + for (size_t i = 0; i < 5000; i++) { verify_kzg_proof(&out, &commitment, &z, &y, &proof, &s); } ProfilerStop(); @@ -2095,7 +2141,7 @@ static void profile_verify_blob_kzg_proof(void) { get_rand_g1_bytes(&proof); ProfilerStart("verify_blob_kzg_proof.prof"); - for (int i = 0; i < 5000; i++) { + for (size_t i = 0; i < 5000; i++) { verify_blob_kzg_proof(&out, &blob, &commitment, &proof, &s); } ProfilerStop(); @@ -2108,14 +2154,14 @@ static void profile_verify_blob_kzg_proof_batch(void) { Bytes48 proofs[n]; bool out; - for (int i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { get_rand_blob(&blobs[i]); get_rand_g1_bytes(&commitments[i]); get_rand_g1_bytes(&proofs[i]); } ProfilerStart("verify_blob_kzg_proof_batch.prof"); - for (int i = 0; i < 1000; i++) { + for (size_t i = 0; i < 1000; i++) { verify_blob_kzg_proof_batch(&out, blobs, commitments, proofs, n, &s); } ProfilerStop(); @@ -2130,7 +2176,7 @@ static void profile_compute_cells_and_kzg_proofs(void) { get_rand_blob(&blob); ProfilerStart("compute_cells_and_kzg_proofs.prof"); - for (int i = 0; i < 5; i++) { + for (size_t i = 0; i < 5; i++) { compute_cells_and_kzg_proofs(cells, proofs, &blob, &s); } ProfilerStop(); @@ -2158,7 +2204,7 @@ static void profile_recover_cells_and_kzg_proofs(void) { } ProfilerStart("recover_cells_and_kzg_proofs.prof"); - for (int i = 0; i < 5; i++) { + for (size_t i = 0; i < 5; i++) { recover_cells_and_kzg_proofs(cells, NULL, cell_indices, cells, CELLS_PER_EXT_BLOB / 2, &s); } ProfilerStop(); @@ -2191,7 +2237,7 @@ static void profile_verify_cell_kzg_proof_batch(void) { } ProfilerStart("verify_cell_kzg_proof_batch.prof"); - for (int i = 0; i < 100; i++) { + for (size_t i = 0; i < 100; i++) { verify_cell_kzg_proof_batch( &ok, commitments, cell_indices, cells, proofs, CELLS_PER_EXT_BLOB, &s ); @@ -2313,6 +2359,7 @@ int main(void) { RUN(test_deduplicate_commitments__no_commitments); RUN(test_deduplicate_commitments__one_commitment); RUN(test_recover_cells_and_kzg_proofs__succeeds_random_blob); + RUN(test_shift_factors__succeeds); RUN(test_compute_vanishing_polynomial_from_roots); RUN(test_vanishing_polynomial_for_missing_cells); RUN(test_verify_cell_kzg_proof_batch__succeeds_random_blob);